想要彻底理解,最好的方式就是手写,而手写call、apply、bind也是非常高频的面试题
定义
call 和 apply 只有传参的区别,call、apply 和 bind 的区别是 bind 不会立即执行函数,而 call、apply 会立即执行 因此我们先实现 call,再实现 apply,最后用 call 或者 apply 就能实现 bind
call 实现过程
首先先用这个例子复习一下 call 的功能
const student = {
name: "lwp",
sayHello: function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
},
};
const lwpSayHello = student.sayHello;
student.sayHello()//my name is lwp,I am 29 old: 谁调用,this指向谁
lwpSayHello(29); //my name is undefined,I am 29 old: 普通函数调用,this指向全局
lwpSayHello.call(student, 29); //my name is lwp,I am 29 old: 通过this绑定,绑定谁,this就指向谁
首先四行核心代码快速实现功能
Function.prototype.myCall = function (context, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
lwpSayHello.myCall(student, 29); //my name is lwp,I am 29 old
比较不好理解的是这句context.fn = this;,而这句话是实现 call 的核心
为了更好理解 context.fn,我们这样写
Function.prototype.myCall = function (context, ...args) {
var fn = this;
const result = fn(...args);
return result;
};
lwpSayHello.myCall(student, 29); //my name is undefined,I am 29 old
然后你会发现 this 指向是错误的
现在我开始一步一步解析代码,请大家集中精神跟紧我的脚步,我要开车了
Function.prototype.myCall = function (context, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
var student = {
name: "lwp",
sayHello: function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
},
};
var lwpSayHello = student.sayHello;
lwpSayHello.call(student, 29)
//想要绑定this,在这个例子我们需要做的就是让sayHello函数里面的this指向student
context.fn = this;
//那么,这句代码我们可以理解为两个步骤
//第一步 把student代入到context
context=student={
name: "lwp",
sayHello: function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
},
}
//第二步,把this代入,谁调用this,this指向谁,lwpSayHello.call,所以this指向lwpSayHello
context={
name: "lwp",
sayHello: function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
}
fn:lwpSayHello
}
//第三步,把lwpSayHello拷贝进去,也就是student.sayHello,也就是
function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
}
//那么最终
context={
name: "lwp",
sayHello: function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
}
fn:function (age) {
console.log(`my name is ${this.name},I am ${age} old`);
}
}
//最后我们再理解myCall里面的这句话
const result = context.fn(...args)
//因此this.name就能正确变成lwp
//总结一下,借助context,新增fn函数,那么context.fn函数里面的this也就是context,目的就是为了改变this指向,最后把多余的fn删掉就行
还可以这样理解
想要让 this 指向 student,那么在 student 去增加一个属性 fn,把函数赋值给 fn,调用完成之后再删除,那么在调用函数的时候,调用函数的对象就是 student,那么在函数内部访问的 this 也就是 student
当时我也理解不了为什么要用 context.fn,看了这个视频才恍然大悟的,所以如果看了我的解释还看不懂,你可以看视频:www.bilibili.com/video/BV15s…
也可以按照我的思路,自己写一下代码,用例子执行一下去理解
这里面还有很多问题,比如说 fn 会不会同名,然后覆盖,那么我们这里可以借助 symbol的唯一性
Function.prototype.myCall = function (context, ...args) {
const key = Symbol("changeThis");
context[key] = this;
const result = context[key](...args);
delete context.fn;
return result;
};
但是仍然有问题,打印this的时候会发现多了一个symbol属性
最后通过Object.defineProperty,然后考虑一下边界问题得到满分代码
Function.prototype.myCall = function (context, ...args) {
var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const key = Symbol("changeThis");
Object.defineProperty(context, key, {
enumerable: false,
value: this,
});
const result = context[key](...args);
return result;
};
到此为止 call 就实现了,接下来实现 apply 和 bind 也会变得非常简单
apply实现过程
apply和call也就是一个传参问题,无需解释
Function.prototype.myApply = function (context, args) {
var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const key = Symbol("changeThis");
Object.defineProperty(context, key, {
enumerable: false,
value: this,
});
const result = context[key](...args);
return result;
};
bind实现过程
bind只是在apply基础上加了一层函数
Function.prototype.myBind = function (context, ...args) {
const context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const that = this;
return function (...innerArgs) {
return that.apply(context, [...args, innerArgs]);
};
};
这样调用的时候才会有结果
var student = {
name: "lwp",
sayHello: function (age, gender) {
console.log(
`my name is ${this.name},I am ${age} old,my gender is ${gender}`
);
},
};
var lwpSayHello = student.sayHello;
lwpSayHello(); //my name is undefined,I am undefined old
const say = lwpSayHello.myBind(student, 29);
say("男"); //my name is lwp,my gender is 男
但是如果遇到 new,就会出现问题 因此最终代码
Function.prototype.myBind = function (context, ...args) {
const _context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const that = this;
return function F(...innerArgs) {
return that.apply(this instanceof F ? this : _context, [
...args,
innerArgs,
]);
};
};
完整代码整理
call
Function.prototype.myCall = function (context, ...args) {
var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const key = Symbol("changeThis");
Object.defineProperty(context, key, {
enumerable: false,
value: this,
});
const result = context[key](...args);
return result;
};
apply
Function.prototype.myApply = function (context, args) {
var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const key = Symbol("changeThis");
Object.defineProperty(context, key, {
enumerable: false,
value: this,
});
const result = context[key](...args);
return result;
};
bind
Function.prototype.myBind = function (context, ...args) {
const _context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
const that = this;
return function F(...innerArgs) {
return that.apply(this instanceof F ? this : _context, [
...args,
innerArgs,
]);
};
};
打波广告
很多面经找起来很麻烦,也找不全,然后还得自己找答案,有些面试宝典虽然完整,但是看不懂,因此干脆自己进行整理,整理最全的前端面试题以及最容易理解的答案,附上参考链接或者视频链接,再加上自己调试的代码,我不相信这样还看不懂。 地址
也可以一起讨论:wx:lan_weipeng备注前端面试群