JS 中call、apply、bind 的作用与区别及源码分析

作用

call、apply、bind 方法都可以改变函数运行时的上下文

区别

1、 call、apply 方法返回的是函数的调用, 而 bind 方法返回的是函数方便之后调用。
2、apply以数组的形式传参,call、bind 以序列化的形式传参
示例代码:

function product () {}
product.prototype={
  name:'product',
  price: 5,
  getPrice(number, discount){
   console.log(this.price*number*discount)
   return this.price*number*discount
  }
}
let apple = {
    price: 8
}
let rice = new product
rice.getPrice(10, 0.8) // 40
rice.getPrice.apply(apple, [10, 0.8]) // 64
rice.getPrice.call(apple, 10 , 0.8) // 64
rice.getPrice.bind(apple, 10 )(0.8) // 64 等同于rice.getPrice.bind(apple, 10, 0.8)()

源码分析

Bind

Function.prototype.bind = function (oThis) { // oThis:将要被绑定的上下文, 示例中指向o
      if (typeof this !== "function") {
        // closest thing possible to the ECMAScript 5 internal IsCallable function
        throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
      }
      var aArgs = Array.prototype.slice.call(arguments, 1),  // 将bind进来的arguments转换为数组,去掉第一个参数(oThis), 示例中结果为[1]
          fToBind = this, // fToBind指调用bind的函数对象, 示例中指向test.printValue
          fNOP = function () {},
          fBound = function () {
            return fToBind.apply(this instanceof fNOP  // 调用bind的对象 是否是 fNOP 的实例
                                  ? this // 如果调用bind的对象是fNOP的实例,将fToBind 的上下文指向调用bind的对象
                                  : oThis || this, // 调用bind的对象不是fNOP的实例,且存在oThis则将上下文指向oThis
                                aArgs.concat(Array.prototype.slice.call(arguments))); // 拼接bind进来的参数与bind之后调用的参数(参数的柯里化), 示例中则是拼接[1]和[2,3] 作为test的参数。
          };

      fNOP.prototype = this.prototype || this.__proto__; // 重写fNOP的原型,使得fNOP继承调用bind的对象的属性和方法
      fBound.prototype = new fNOP(); // 重写fBound的原型,使得fBound继承fNOP的方法和属性
      return fBound;
    };

1、Array.prototype.slice.call(arguments, 1)
作用:将arguments 转换为数组并去掉第一个参数
自定义slice方法示例:

  Array.prototype.Myslice = function (begin,end){
    let start = begin || 0; // Array.prototype.slice.call(arguments, 1)这里 begin 为 1
    let len = this.length || 0;    //获取上下文的长度, 源码中 arguments 的长度
    // 开始索引:
    // 1、没有参数从0开始有就从索引处开始,
    // 2、如果该参数为负数则表示从原数组中的倒数第几个元素开始提取
    start = (start >= 0) ? start : Math.max(0, len + start);
    // 结束索引:
    // 1没有参数默认取到数组末尾,如果大于数组长度,取到数组末尾
    // 2、如果该参数为负数则表示在原数组中的倒数第几个元素结束抽取
    end = (typeof end == 'number') ?  // Array.prototype.slice.call(arguments, 1) 这里 end 为 undefined
        end>=0?Math.min(end, len):Math.max(end+len,0)
        : len; //
    let result = new Array();
    for (let i = start; i < end; i++) {
      result.push(this[i])
    }
    return result;
  }
  function testMySliceCall() {
    // arguments 是属于函数内部变量,其值是一个具有长度属性的类数组对象(ex:{'1':1,'2':2,'3':3,'4':4})
    return Array.prototype.Myslice.call(arguments,1);
  }
  console.log(testMySliceCall(1, 2, 3, 4)); // [2,3,4]

2、原型链

// 先创建一个临时性的构造函数
let fNOP = function () {}
 // 重写fNOP的原型,使得fNOP继承调用bind的对象的属性和方法
fNOP.prototype = this.prototype;
// 重写fBound的原型,使得fBound继承fNOP的方法和属性
fBound.prototype = new fNOP();
// 返回兼具调用bind的对象的属性和方法,同时又具有即将被指定为上下文的对象的属性和方法的对象
return fBound;

在这里插入图片描述

apply

Function.prototype.apply = function (context, arr) {
    context = Object(context) || window; // 示例中context 指向 apple 对象
    context.fn = this; // 将函数添加到将要被绑定的上下文,示例中给apple 对象 添加 getPrice 方法
    let result;
    if (!arr) { // 没有传参,直接返回调用
        result = context.fn();
    }
    else {
        if (!(arr instanceof Array)) throw new Error('params must be array');
        result = context.fn(...arr);// rice.getPrice(10, 0.8) ...Array ES6语法用于序列化数组元素
    }
    delete context.fn
    return result; // 返回函数的调用
}

call

Function.prototype.call = function (context) {
  context = Object(context) || window;
  context.fn = this;
  let args = [];
  // arguments 为伪数组,有长度属性,但不是数组,示例中为{'1':10, '2':0.8}
  for (let i = 1, i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  let result = context.fn(...args);
  delete context.fn;
  return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值