第一次模拟面试
es6新增了哪些内容
回答这个问题的时候最好说自己熟悉的内容,比如let const,箭头函数,Promise等等,方便面试官接着问之后的东西
- 块级作用域变量声明(let、const)
- 解构赋值,可以方便地从数组或对象中提取值并赋给变量
- 箭头函数,可以使用更简洁的语法来定义函数,并且自动绑定 this
- 默认参数值,函数的参数可以设置默认值
- 模板字符串,使用反引号(`)定义字符串,可以方便地插入变量和换行符
- 扩展运算符,用于将数组或对象展开为逗号分隔的序列,或将序列合并为数组或对象
- 类和继承,可以使用 class 关键字来定义类和继承
- Promise,提供了一种简洁的方式来处理异步操作
- 模块化,使用 import 和 export 关键字来导入和导出模块。
- 对象字面量的增强,可以使用简写属性、方法和计算属性名。
- Set 和 Map,提供了两种新的数据结构,可以用于存储一组唯一的值和键值对。
- Symbol,一种新的数据类型,表示独一无二的值。
- 迭代器和生成器,提供了一种简洁的方式来遍历数据集合。
- Proxy 和 Reflect,提供了一种元编程机制,可以用来拦截和修改对象的默认行为。
这些特性使得 ES6 更加现代化、强大和易用,为 JavaScript 开发带来了很多便利。
CSS部分:
css实现三角形:
<div class="div"></div>
<style>
.div{
width:0;
height:0;
border-bottom:50px solid black;
border-left:50px solid transparent;
border-right:50px solid transparent;
}
</style>
具体代码:https://stackblitz.com/edit/web-platform-wqhwfy?file=index.html
BFC是什么:
块格式化上下文(Block Formatting Context,BFC)是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。
通俗理解:
- BFC 是一个独立的布局环境,可以理解为一个容器,在这个容器中按照一定规则进行物品摆放,并且不会影响其它环境中的物品。
- 如果一个元素符合触发 BFC 的条件,则 BFC 中的元素布局不受外部影响。
- 浮动元素会创建 BFC,则浮动元素内部子元素主要受该浮动元素影响,所以两个浮动元素之间是互不影响的。
这里简单列举几个触发BFC
使用的CSS
属性:
- 根元素(html),或包含body的元素
- 设置浮动(float),且值不为none(为
left
、right
), - 设置定位(position), 不为static或relative(为
absolute
、fixed
) - 设置 display 为这些值
inline-block
、flex
、grid
、table
、table-cell
、table-caption
- 设置 overflow,且值不为visible (为
auto
、scroll
、hidden
)
水平 垂直居中:
.box {
display: flex;
}
.item {
margin: auto;
}
演示代码地址:https://stackblitz.com/edit/web-platform-sjpbjm?file=index.html
选择器权重:
- 内联样式,如: style=”…”,权值为
1000
。 - ID选择器,如:#content,权值为
0100
。 - 类,伪类、属性选择器,如.content,权值为
0010
。 - 类型选择器、伪元素选择器,如div p,权值为
0001
。 - 通配符、子选择器、相邻选择器等。如
* > +
,权值为0000
。 - 继承的样式没有权值
!important>行内样式>ID选择器 > 类选择器 | 属性选择器 | 伪类选择器 > 元素选择器
正确的回答方式就是按照顺序来说,然后说权值是多少,比如1000 0100 0010 0001
上面的四个就是大厂面试最最常见的四种css面试题
JavaScript部分:
0.1+0.2==?
0.1 + 0.2 // 0.30000000000000004
简单概括:浮点数转二进制时丢失了精度,计算完再转回十进制时和理论结果不同。
具体原理:
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...
(1100循环),0.2的二进制是:0.00110011001100...
(1100循环),这两个数的二进制都是无限循环的数。
一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。
根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004
。
JavaScript数据类型:
上面的是基本数据类型,还有个数据类型就是object
箭头函数和普通函数的区别:
箭头函数无 this,无 prototype
(显示原型)
箭头函数无原型函数
箭头函数有几个使用注意点。
- 箭头函数没有自己的
this
对象。 - 不可以当作构造函数,也就是说,不可以对箭头函数使用
new
命令,否则会抛出一个错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替。 - 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数
箭头函数里面的this指向哪?
箭头函数本身是没有this的,箭头函数的this指向它所在上下文的对象,如果在箭头函数里使用了this,它会指向最近一层作用域内的this
原型和原型链
原型:构造函数.显示prototype=实例对象.隐式__proto__,构造函数的显示原型中含有该构造器 原型链:在此基础上,构造函数的显示原型指向另外一个构造函数的显示原型
在这里的隐式原型我们就可以使用student._proto_
,便可以访问到隐式原型了
而Student.prototype
便是显示原型
通过原型链一层层往上找,形成链式结构,直到找到null
如何构造一条原型链
JavaScript中的原型链是通过对象的prototype
属性来实现的。
可以使用以下代码构造一条原型链:
Copy code// 创建父类
function Parent() {
this.name = "Parent";
}
// 父类的方法
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 创建子类
function Child() {
this.name = "Child";
}
// 设置子类的原型对象为父类的实例
Child.prototype = new Parent();
// 创建子类实例
var child = new Child();
// 调用父类方法
child.sayName(); // "Child"
这样,我们就构造出了一条原型链:Child.prototype
指向Parent
的实例,Parent.prototype
指向Object.prototype
,Object.prototype
指向null
。
手写部分:
发布订阅模式
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
1. 实现思路
- 创建一个对象
- 在该对象上创建一个缓存列表(调度中心)
- on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
- emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
- off 方法可以根据 event 值取消订阅(取消订阅)
- once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
2.代码实现
let eventEmitter = {
// 缓存列表
list: {},
// 订阅
on (event, fn) {
let _this = this;
// 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
// 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
(_this.list[event] || (_this.list[event] = [])).push(fn);
return _this;
},
// 监听一次
once (event, fn) {
// 先绑定,调用后删除
let _this = this;
function on () {
_this.off(event, on);
fn.apply(_this, arguments);
}
on.fn = fn;
_this.on(event, on);
return _this;
},
// 取消订阅
off (event, fn) {
let _this = this;
let fns = _this.list[event];
// 如果缓存列表中没有相应的 fn,返回false
if (!fns) return false;
if (!fn) {
// 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
fns && (fns.length = 0);
} else {
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
let cb;
for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
cb = fns[i];
if (cb === fn || cb.fn === fn) {
fns.splice(i, 1);
break
}
}
}
return _this;
},
// 发布
emit () {
let _this = this;
// 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
let event = [].shift.call(arguments),
fns = [..._this.list[event]];
// 如果缓存列表里没有 fn 就返回 false
if (!fns || fns.length === 0) {
return false;
}
// 遍历 event 值对应的缓存列表,依次执行 fn
fns.forEach(fn => {
fn.apply(_this, arguments);
});
return _this;
}
};
function user1 (content) {
console.log('用户1订阅了:', content);
}
function user2 (content) {
console.log('用户2订阅了:', content);
}
function user3 (content) {
console.log('用户3订阅了:', content);
}
function user4 (content) {
console.log('用户4订阅了:', content);
}
// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);
// 取消user2方法的订阅
eventEmitter.off('article1', user2);
eventEmitter.once('article2', user4)
// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
// eventEmitter.on('article1', user3).emit('article1', 'test111');
/*
用户1订阅了: Javascript 发布-订阅模式
用户3订阅了: Javascript 发布-订阅模式
用户1订阅了: Javascript 发布-订阅模式
用户3订阅了: Javascript 发布-订阅模式
用户4订阅了: Javascript 观察者模式
*/
bind是什么,如何实现:
bind()
方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
js当中实现最简单的bind:
Function.prototype.fakeBind = function(obj) {
return (...args) => this.apply(obj, args)
}
测试一下:
function f (arg) {
console.log(this.a, arg)
}
// output: 3, 4
f.bind({ a: 3 })(4)
// output: 3, 4
f.fakeBind({ a: 3 })(4)
实现call
Function.prototype.myCall=function(thisArg,...args){
thisArg.t=this
return thisArg.t(...args)
}
实现apply
Function.prototype.myApply=function(thisarg,args){
let fnName=Symbol()
thisarg[fnName]=this //this指向被调用的函数fn
const result = thisarg[fnName](...args)
delete thisarg[fnName]
return result
}
手撕instanceof:
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。
A.instance of(B) 沿着A的隐式原型链去寻找B的显示原型
function my_instanceof(A,B)
{
while(true)
{
if(A._proto_=== B.prototype)
{
return true;
}
A = A._proto_;
if(A._proto_ === null)
{
return false;
}
}
具体代码(包含测试):https://stackblitz.com/edit/js-udyzw9?file=index.js
手撕promise.all
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(88);
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(99);
}, 3000);
});
let arr = [p1, p2, 3, 4];
function myPromiseALL(array) {
//返回的是一个Promise,直接用async包裹
let result = [];
let flag = 0;
return new Promise((resolve, reject) => {
for (let index in array) {
if (array[index] instanceof Promise) {
array[index].then(
(res) => {
result[index] = res;
flag++;
if (flag == array.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
} else {
result[index] = array[index];
flag++;
if (flag == array.length) {
resolve(result);
}
}
}
});
}
Promise.myPromiseAll = myPromiseALL;
myPromiseALL(arr).then(
(res) => {
console.log(res, "res");
},
(err) => {
console.log(err, "err");
}
);
//[88,99,3,4] 'res'
//将p2改为reject
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(99);
}, 3000);
});
//99 'err'
手撕promise.race()
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(88);
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(99);
}, 2000);
});
let arr = [p1, p2];
function myPromiseRace(array) {
return new Promise((resolve, reject) => {
for (let item of array) {
if (item instanceof Promise) {
item.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
} else {
resolve(item);
}
}
});
}
Promise.myPromiseRace = myPromiseRace;
myPromiseRace(arr).then(
(res) => {
console.log(res, "res");
},
(err) => {
console.log(err, "err");
}
);
计算机网络
关键渲染路径
什么是关键渲染路径?
GUI线程中把htmlcss代码解析到渲染在页面上的这条流水线
过程
1.HTML解析生成DOM树
2.CSS解析生成CSSOM树
3.DOM树和CSSOM树合成 渲染render树
4.根据render树和视口进行Layout布局,计算每个节点的位置和大小(布局render树)
5.Paint绘制或栅格化,将每个节点绘制到页面上(绘制render树,像素px信息)
6.转交给合成进程,光栅化线程池,GPU进程合成,显示在屏幕上
重新定义重溯和回流
回流:Layout阶段计算出节点的位置大小发送改变,然后重新布局渲染
重溯:Layout阶段未作出改变,只重新进行Paint渲染/栅格化
输入URL到页面渲染
1.DNS解析
将域名解析成ip地址
2.发送TCP连接
传包 TCP三次握手、四次挥手 2MSL的原因
3.发送http请求
特殊请求报文 keep-alive https http2 先进先出 队头阻塞
4.服务器获取请求并返回http响应报文
特殊响应报文 状态码 浏览器缓存
5.浏览器解析并渲染页面
HTML-DOM CSS-CSSOM 渲染树 减少重溯和回流-重绘和重排 js解析 事件循环
6.断开连接
四次挥手的原因
Https安全的原因:
因为在中间加入了一层SSL/TLS(运输 安全 协议)协议
使用CA证书给客户端公钥认证 想当于打了一层身份证
特点:非对称加密
vue部分:
vue2和vue3响应式原理
Vue2
数据劫持核心: defineProperty setter和getter 遍历添加响应式: Oberserver类和defineReactive 优化: ob 发布订阅模式: Dep类和Watcher类 添加订阅:dep.addSub(wtacher实例) 发布通知: dep.notify( )->sub[i].undate()->wtacher.run()->watcher.get()->getter watcher种类: 计算属性watcher、用户自定义watcher。渲染watcher
Vue3
数据劫持核心: Proxy sethandler和gethandler
nextTick()解决了什么问题
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise