call,apply,bind混淆?那手写一个吧,撕得碎碎的,隔壁大妈都能看懂

512 阅读3分钟

js 里,this 指向一直是一个让人又爱又恨的问题,用的好的那真是一柄利刃,用不好的那网页飘红。我们在开发过程中经常会改变this 的指向,常用的就是call、apply 和 bind

简述 call、apply、bind 方法的区别
  1. callapply 是改变后页面加载之后就立即执行bind 不会立即执行,而是返回一个 新函数,适合回调场景使用
  2. call、apply、bind第一个参数都是相同的(总是指函数运行时的 this)
    1. apply 的第二个参数是一个数组
    2. callbind的第二个参数是一 一列出的
  3. callapply 只是临时的修改一次,也就是调用 callapply 方法的那次;后面再次调用原函数的时候,它的指向还是原来的指向
     function fn() {
       console.log(this);
       };
     fn(); // window
    
     const obj = {
        name: 'constRen',
     };
    
     fn.call(obj); // {name: 'constRen'}
     fn.apply(obj); // {name: 'constRen'}
     fn();  // window
    
    
  4. bind 是永久修改函数 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) // 
总结

我觉得这个就是一个借鸡生蛋的过程,借鸡生蛋大家都听说过吧?原理其实就和这个差不多
就三步

  1. 将方法挂载到对象上 (把鸡借过来)
  2. 执行这个方法 (让鸡生蛋)
  3. 从这个对象上删除刚刚挂载的方法 (把鸡还回去)