js手写call、apply、bind

89 阅读2分钟

手写js call、apply、bind实现

  1. 写这篇文章的初衷是自己对这几个api的理解,另外出去面试的时候可以回忆复习下思路
  2. 我想分三个模块直接附上代码片段
  3. 并且会注释上我的思路分析
  4. 也希望对有需要的小伙伴一点帮助
  5. 最难的是你理解这些东西,甚至在平时coding时候也知道怎么回事,就是不愿意去思考这背后的点点滴滴
  6. 其实当你坚持写博文的时候才发现开始很快,但坚持每个礼拜甚至坚持两个礼拜整理点东西,分享出去需要足够的耐心
  7. 我想着这也许也可以为你面试这份工作提供一点亮点
  • call实现

这里举个例子方便后续理解
1. 例如有一个对象这样
    var name = 'xiaohong'
    const obj = {
        name: 'xiaoming',
        sayName(age) {
            console.log(this.name + age)
        }
    }
    
    const obj2 = {
        name: 'xiaowang'
    
    }
    
    obj.sayName(); //这里输出xiaoming 指向自己出生的对象
    
    obj.sayName.call(obj2) //这里输出xiaowang //指向obj2
    
    obj.sayName.call(null) // 这里输出xiaohong 指向window
    
    //obj.sayName.call(_this, arg1,arg2,.....)
    

看到这里我想你茅塞顿开了 直接上代码

  1. call 实现1 ----> ES5
// 挂载到构造函数的原型对象上
/**
* params
* 参数1 改变this指向的对象
* 参数2~ 传递的参数集合
*/
Function.prototype.createCall = function() {
// 截取第一个参数 
 const thisArgs = Array.prototype.shift.call(arguments) || window;
 // 可以理解为在指定对象上绑定一个函数指向调用函数
 thisArgs.fn = this;
 //创建一个数组收集剩余参数
 const args = [];
 for (let i = 0, len = arguments.length; i < len; i++) {
    //为了后面eval函数执行,需要将参数包一层字符串
    args.push('arguments[' + i + ']');
 }
 //执行函数体
  const result = eval('thisArgs.fn(' + args + ')');
  //删除额外的属性
  delete thisArgs.fn;
  //返回结果
  return result;
}
  1. call实现2 ------> ES6
Function.prototype.createCallTwo = function () {
   const [thisArgs, ...args] = [...arguments]; //es6 解构参数
   if (!thisArgs) {
      thisArgs = typeof window == undefined ? global : window;
   }
   //可以理解为在指定对象上绑定一个函数指向调用函数
   thisArgs.func = this;
   const result = thisArgs.func(...args);
   delete thisArgs.func;
   return result;
  }
  • apply实现

用法fn.apply(_this, [arg1, arg2, ....])

Function.prototype._apply = function (context, arr) {
    var context = Object.create(context) || window;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn();
    } else if(Object.prototype.toString.call(arr) !== '[object Array]') {
        throw new Error('第二个参数必须是数组!!')
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        console.log(args, '====args');
        result = eval('context.fn(' + args + ')');
    }
    delete context.fn;
    return result;
 }
  • bind实现

实现思路

  1. bind函数首先可以改变this指向,然后不会立即执行函数体,而是返回一个新的函数
  2. 参数可以透传
let obj = {
    name: 'hello,bind',
    say(a, b, c) {
      console.log(this.name, a, b, c);
    }
};

let obj2 = {
    name: 'good luck'
};

// context 这里接受的bind指向对象
Function.prototype.createBind = function (context) {
     //将调用函数别名fn保存
    const fn = this;
    //将剩余参数缓存起来
    const args = Array.prototype.slice.call(arguments, 1);
    //返回一个函数
    return function () {
       //绑定this指向,及参数接收
      return fn.apply(
        context,
        args.concat(Array.prototype.slice.call(arguments))
      );
    };
};

const sayName = obj.say.bind(obj2, 4); //原版
const sayName2 = obj.say.createBind(obj2, 5); // 自定义
sayName(1, 2, 3); // good luck 4 1 2
sayName2(1, 2, 3); // good luck 5 1 2 👌

欢迎留言交流