前言
虽然仍然处于疫情中,但为了自己技术职业规划,还是选择去尝试心目中的大厂。以下为真实的大厂面试记录,希望起到抛砖引玉的作用
1. 函数柯里化(字节三面)【压场面】
//给定一个计算体积的函数
function cube(l, w, h){
return l * w * h
}
function currying(){
//TODO:传入函数可以随意调用
}
//实现如下多种方式调用
let func = currying(cube)
func(1)(2)(3)
func(1,2,3)
func(1,2)(3)
第一道题个人觉得,此前没有接触过函数柯里化,急的满头大汉也是很正常的。函数柯里化
这里贴一下我的手写代码(面试后写出)
function currying (fn){
let Myargs = [] //存放陆续传入的参数
return function f() {
if(Myargs.length + arguments.length >= 3){
Myargs = Myargs.concat(Array.from(arguments));
return fn( ...Myargs) //返回传入的 fn的调用结果,并将保存下来的参数传入
}else{ //未到达指定的参数个数,将参数存起来
Myargs = Myargs.concat(Array.from(arguments))
}
return f
}
}
2. 实现一个简单的bind方法(字节一面)
少废话,简单题直接上代码
Function.prototype.myBind = function(context){
//闭包this,这里指向调用者,也就是this指向的是sayName函数
return () => {
/**
*这里的this由于是箭头函数,故而指向上面的闭包this
*调用函数的时候传入obj被context接收,‘1’被arguments接收(这里arguments也是闭包的arguments,箭头函数无arguments)
*使用[...arguments]将类数组arguments转为数组,使用slice(1)将0下标以外的都截取下来
**/
this.call(context, ...[...arguments].slice(1))
}
}
let obj = {name: 'hxb'}
function sayName (){
console.log(this.name)
}
const say = sayName.myBind(obj, '1')
say() //'hxb'
3. 构造函数理解(字节二面)
function Foo() {
getName = function(){ alert(1); };
return this;
}
Foo.getName = function() { alert(2); };
Foo.prototype.getName = function(){ alert(3); };
var getName = function() { alert(4); };
function getName(){ alert(5); }
//请解释下方各自输出
Foo.getName(); // 2 Foo为复杂数据类型,所以属于直接调用函数对象的方法
getName(); // 4 最后一行getName函数声明和getName变量都会发生变量提升,但是getName函数声明会提升到 var getName前面,输出4
Foo().getName(); // 1 普通函数调用,返回this为window,调用时对getName进行了覆盖
getName(); // 1 上一步的调用对getName进行了覆盖
new Foo.getName(); // 2 将Foo.getName当作一个构造函数来调用,执行函数内部代码输出2
new Foo().getName(); // 3 实例化了一个对象,此时实例化的这个对象,自身没有getName方法,将访问Foo.prototype上面的getName方法
4. 事件循环的应用(字节/快手)
事件循环机制,相比大家都很熟悉了,读完下面的几种例子,说不定你还需要重新学习一下Promise
// 字节二面
new Promise((res, rej) => {
console.log(1);
throw new Error('abc'); //抛出错误,下面的代码不执行
res(2);
console.log(3);
}).catch((e) => {
console.log('catch');
}).then((t) => {
console.log(t);
});
// 输出 1 --> catch --> undefined
稍稍变形一下
new Promise((res, rej) => {
console.log(1);
res(2); //状态发生转变后,后续的错误也无法捕获
throw new Error('abc');
console.log(3);
}).catch((e) => {
console.log('catch');
}).then((t) => {
console.log(t);
});
// 输出 1 --> 2
看看快手的事件循环怎么考察
//快手一面
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a); //输出一个Promise
console.log('after1');
await a //TODO:此处求大佬指点迷津
resolve(true);
console.log('after2');
}).then(data => {console.info(data)})
console.log('end');
// 输出 promise1 --> undefined --> end --> promise2 --> promise3 --> promise4 --> Promise{<pending>} --> after1
自己也稍稍变形一下加深理解
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a); //输出一个Promise
console.log('after1');
await Promise.resolve(1) //TODO:此处求大佬指点迷津
resolve(true);
console.log('after2');
}).then(data => {console.info(data)})
//输出: promise1 --> undefined --> end --> promise2 --> promise3 --> promise4 --> Promise{<pending>} --> after1 --> after2 --> true
这此变形估计是最好理解的
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await Promise.resolve(1);
console.log(a); //输出一个Promise
console.log('after1');
await Promise.resolve(1) //TODO:此处求大佬指点迷津
resolve(true);
console.log('after2');
}).then(data => {console.info(data)})
//输出: promise1 --> undefined --> end --> promise2 --> Promise{<pending>} --> after1 --> promise3 --> after2 --> promise4 --> true
个人觉得Promise这个ES6的特性还需要大家好好研究一下,由于时间有限暂时先放这四个题。