字节前端面试题
字节前端面试题
国庆前夕面试了字节的前端,面试官很nice,在这次面试中也学到了不少知识点。那时候靠着记忆记下了这些题
一、name、setName编程题,考察箭头函数中this的指向;
二、https的工作原理;
三、组件库的 polyfill的处理;
四、promise打印顺序问题;
五、JS中的原始值为什么能调用方法
六、React 中的 加上 if 判断后的 useState;
七、编程题tree,name,家谱问题;
八、三个数组,求交集,要求函数能重载; 先记录下,回头把这些问题都攻克攻克
一、箭头函数 this 指向问题
let name = "x";
let people = {
name: "y",
setName: () => {
return () => {
console.log(this.name);
};
},
};
let getName = people.setName();
console.log(getName());
console.log(people.name);
答案: x、y
箭头函数中没有 this,它的 this 指向外部词法环境,与谁调用无关。而且词法环境是在初始化的时候就确定,也就是说当代码中有箭头函数时,代码一开始还没执行时就确定了它的 this
在这代题目中,people 中的 setName 是箭头函数,它的 this 永远绑定在 window 上,所以 getName 无论怎么执行,它的this 都指向 window,所以let getName = people.setName()
和 console.log(getName())
中的 this 都指向 window,people.name 的值是 y,window 的name 是 x,所以打印 x、y
所谓外部词法环境
如果再思考一下上述代码,会觉得奇怪,setName 不是在 people 对象中吗,为什么它的 this 不是 people。其实 people 是它当前的词法环境,而非外部词法环境,也就是说在它这层环境的外层,那外层就是 window 了
我们从 this关键字 中拿出箭头函数的例子再做说明
var people = {
name: "eliane",
age: 28,
sayName: () => console.log(this.name, this),
sayName2: function () {
console.log(this.name, this);
},
};
people.sayName(); // '', Window
people.sayName2(); // elaine, {name: 'eliane', age: 28}
看到没,sayName是箭头函数,所以它的this 要从外部词法环境中找,而 sayName2 是普通函数,谁调用它,this 指向谁
至于外部词法环境,简单来说就是要形成作用域(全局作用域、函数作用域和块级作用域),以下例子也是个典型的寻找外部词法环境的例子
var foo = {
bar: {
a: () => console.log(this),
},
};
foo.bar.a(); // window
foo 是对象,bar 也是对象,都没有形成作用域,所以箭头函数a 的 this 只能找全局的 window
三、组件库的 polyfill的处理
主要是对组件库的理解
组件库主题有哪些?
组件库给别人用的时候,输出的是编译后的文件还是编译前的文件
-
用的是编译后的产物给消费者
-
如果你们做的组件的兼容目标和使用方不一样怎么办,你们会把一些 polyfill 编译进去吗
- 规定了浏览器的兼容目标
- 包体积会变大吗
- 组件引入 polyfill,业务又引入了 polyfill。这样就两份 polyfill
-
实现的方式是什么
- 我的回答:从依赖项的做处理
- 编译时检测,写一个babel插件,检测如果依赖项中已经有了,就不注入,如果没有则注入
- 还有别的思路吗?你的思路有一个怪的点,一般babel处理文件的时候不会处理 node_modules 下的文件,它只会对我开发的代码进行检测,你这种方法,不仅要对自己写的代码进行检测处理,还要对你引用的依赖性也就是 node_modules 处理,这样babel编译的时候时间就会超慢,一般默认情况下 babel 不会做相关的检测,思路是正确的,只是怪
- 另一个回答,把 polyfill 进行拆包,单独引用
- 方案也可以,但问题是 polyfill 是动态的
- 如果是你这种情况下,就要规定语法检测,要约定俗成一下
四、Promise 的编程题
setTimeout(() => {
console.log("a");
});
new Promise((resolve) => {
console.log("b");
for (let i = 0; i < 10000; i++) {
if (i === 1) console.log("c");
if (i === 9999) resolve();
}
}).then(() => {
console.log("d");
});
console.log("e");
答案: b、c、e、d、a
衍生
let p0 = new Promise((resolve) => {
resolve();
})
.then(() => {
console.log("0");
})
.then(() => {
console.log("1");
});
let p1 = new Promise((resolve) => {
resolve();
}).then(() => {
console.log("2");
});
p0.then(() => {
console.log("3");
});
答案: 0、2、1、3
分析:Promise 的事件机制分为注册函数和执行函数,当我们执行到第三行的时候,我们会把 console(‘0’) 注册,因为第二行已经 resolve 了,所以会把第四行中的回调函数放入微任务队列中,执行第五行的时候,是个同步任务,会注册 console.log(‘1’),但注册完了并不会扔到微任务队列上,因为 console.log(‘1’) 是否扔到微任务队列上,取决于 console.log(‘0’) 的执行结果,如果这里报错了,就会走到 catch 里
此时微任务队列中有 0,但没有 1,因为 console.log(0) 还没执行
相同的道理,执行到11行,我们是将console.log(2)扔到微任务队列里去
同样的道理,执行到15行,console.log(3) 取决于 console.log(1) 的结果,console.log(3)只是注册了但是没有扔到微任务队列上
同步任务执行完后,微任务队列上有两个回调函数(0和2),当console.log(0) 执行完后,再把 console.log(1) 扔到console.log(2) 的后面,当 console.log(1) 执行完了,再把console.log(3) 扔在console.log(1) 的后面
五、JS中的原始值为什么能调用方法
为什么 js 中的 原始值能调用方法,比如 str.split()
关键字:原始值包装对象(Primitive Wrapper Objects)
在调用方法的时候会把它包装成一个对象,之后把这个对象置成null。它一开始就是个原始值,就是简单类型的值,只是在运行时做某一些处理,这种设计允许开发者像使用对象一样使用原始值
总结
后续的问题我没有什么记忆了,只是能在面试中感受到这个面试官的友好