在 js 里,this 指向一直是一个让人又爱又恨的问题,用的好的那真是一柄利刃,用不好的那网页飘红。我们在开发过程中经常会改变this 的指向,常用的就是call、apply 和 bind。
简述 call、apply、bind 方法的区别
call和apply是改变后页面加载之后就立即执行,bind不会立即执行,而是返回一个 新函数,适合回调场景使用。call、apply、bind第一个参数都是相同的(总是指函数运行时的this)apply的第二个参数是一个数组call和bind的第二个参数是一 一列出的
call和apply只是临时的修改一次,也就是调用call和apply方法的那次;后面再次调用原函数的时候,它的指向还是原来的指向function fn() { console.log(this); }; fn(); // window const obj = { name: 'constRen', }; fn.call(obj); // {name: 'constRen'} fn.apply(obj); // {name: 'constRen'} fn(); // windowbind是永久修改函数this指向,但它修改的不是原函数;而是返回的新函数,新函数的this被永远改变了function fn() { console.log(this); }; fn(); // window const obj = { name: 'constRen', }; let newfn = fn.bind(obj); newfn(); // {name: 'constRen'} fn(); // window newfn() // {name: 'constRen'}
很多人都搞不清楚他们的用法,或者是容易混淆,我想了一个形象好记的
1. call()
被借的对象.方法.call(借给谁, 参数1, 参数2, 参数3)
// 被借的对象===>就理解为 一个 有很多工具的人
// 方法 ===>就是工具
// 借给谁===> 就像你没有斧头,你想砍树,你可以向那个有很多工具的人 借用一下他的斧头
2. apply()
被借的对象.方法.call(借给谁, [参数1, 参数2, 参数3])
以上两个方法是一组的,它们只有参数不同,其他功能一样,都必须"马上执行"。
3. bind() //一般用于回调函数中,用于永久性的改变this的指向
被借的对象.方法.bind(借给谁, 参数1, 参数2, 参数3)
手写实现
实现Call
Function.prototype.myCall = function (ctx, ...args) {
// 1. 将方法挂载到 ctx 这个 this 就是那个方法!! 因为是 say 调用的 myCall
// 注意:有一种特殊情况就是 ctx 本身就有一个fn方法,这样就会被覆盖 这儿就需要一个第一无二的变量
const fn = Symbol();
ctx[fn] = this;
// 2. 调用这个方法
ctx[fn](...args);
// 3.调用完之后删除方法 不然对象就被改变了 多了一个方法
delete ctx[fn]
};
function say(...args) {
console.log('...args', ...args); // p1 p2 p3
console.log('this.name', this.name); // constRen
};
say.myCall({ name: "constRen" }, 'p1', 'p2', 'p3')
实现apply
Function.prototype.myApply = function (ctx, args = []) {
// 因为 apply 的第二个参数是一个数组 所以这儿需要限制
if (args && !Array.isArray(args)) {
throw '第二个参数必须是数组'
}
const fn = Symbol();
ctx[fn] = this;
ctx[fn](...args);
delete ctx[fn]
}
function say(...args) {
console.log('...args', ...args); // p1 p2 p3
console.log('this.name', this.name); // constRen
};
say.myApply({ name: "constRen" }, ['p1', 'p2', 'p3'])
实现bind
Function.prototype.myBind = function (ctx, ...args1) {
// 注意: bind 是返回一个新函数
return (...args2) => {
const fn = Symbol();
ctx[fn] = this;
ctx[fn](...args1.concat(...args2));
delete ctx[fn]
}
}
function say(...args) {
console.log('...args', ...args); // 'p1', 'p2', 'p3', 1, 23
console.log('this.name', this.name); // constRen
};
const sayFn = say.myBind({ name: "constRen" }, 'p1', 'p2', 'p3')
sayFn(1, 23) //
总结
我觉得这个就是一个借鸡生蛋的过程,借鸡生蛋大家都听说过吧?原理其实就和这个差不多
就三步
- 将方法挂载到对象上 (把鸡借过来)
- 执行这个方法 (让鸡生蛋)
- 从这个对象上删除刚刚挂载的方法 (把鸡还回去)