zhuibo66 / coding-note

note
0 stars 0 forks source link

bind、call、apply有什么区别?并手写 #20

Open zhuibo66 opened 3 years ago

zhuibo66 commented 3 years ago

相同点

都是为了改变函数调用者的this的指向

第一个参数都是指向个对象(MDN说:传递的任何原始值都将转换为object)

不同点

剩余参数(第二个参数起)的传参方式不同

call和bind剩余参数,是个列表,如:arg1,arg2

apply,是个数组,如:[arg1,arg2]

返回值不同

call和apply返回值:undefined

bind返回值:是个新的函数

执行阶段不同

call和apply执行阶段是:立即执行

bind执行阶段是:因为bind返回的是个函数,所以不会立即执行,所以啥时候执行,取决于coder。

手写call

原生定义:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

Function.prototype.myCall = function (thisArg, ...args) {
  //1、如果调用者不是一个可调用的函数,则抛出TypeError
  if (typeof this !== "function") {
    throw new TypeError(`${this} must be a function`);
  }
  //2、如果绑定对象是null或者undefined,那么则将其绑定到全局对象上去(浏览器环境下全局对象是window)
  if (thisArg === null || thisArg === undefined) {
    thisArg = window; //此处为全局对象,浏览器环境下为window
  } else {
    thisArg = Object(thisArg); // 如果是普通类型则将其转换为对象包裹
  }
  const fn = Symbol("唯一标识");
  thisArg[fn] = this;
  const result = thisArg[fn](...args);
  delete thisArg[fn]; // 此处若不删除属性,thisArg则会出现Symbol属性,破坏了原来内容
  return result;
};

手写apply

原生定义:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

//判断是不是类数组
function isArrayLike(val) {
  //val.length >= 0 排除了负数 以及NaN
  if (typeof val.length === "number" && val.length >= 0) {
    if (!isFinite(val.length)) {
      throw new RangeError("Invalid array length");
    }
    return true;
  }
  return false;
}
Function.prototype.myApply = function (thisArg, argArray) {
  //1、如果调用者不是一个可调用的函数,则抛出TypeError
  if (typeof this !== "function") {
    throw new TypeError(`${this} must be a function`);
  }
  //2、如果绑定对象是null或者undefined,那么则将其绑定到全局对象上去(浏览器环境下全局对象是window)
  if (thisArg === null || thisArg === undefined) {
    thisArg = window; //此处为全局对象,浏览器环境下为window
  } else {
    thisArg = Object(thisArg); // 如果是普通类型则将其转换为对象包裹
  }

  //3、判断argArray是否是null或者undefined,(3,4,5这几步,我感觉只是为了代码容错性更高点,因为原生有)
  if (argArray === null || argArray === undefined) {
    argArray = [];
  }

  //4、判断argArray是否是个对象
  if (!(argArray instanceof Object)) {
    throw new TypeError("CreateListFromArrayLike called on non-object");
  }

  // 5、判断argArray是否是类数组对象
  const argList = [];
  if (isArrayLike(argArray)) {
    // argArrar为null或undefined已经转换为空数组
    //有可能argArray.length 不是整数
    for (let i = 0; i < Math.floor(argArray.length); i++) {
      argList[i] = argArray[i];
    }
  }
  const fn = Symbol("唯一标识");
  thisArg[fn] = this;
  const result = thisArg[fn](...args);
  delete thisArg[fn]; // 此处若不删除属性,thisArg则会出现Symbol属性,破坏了原来内容
  return result;
};

手写bind

原生定义:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

//基础版
Function.prototype.myBind = function (thisArg, ...args1) {
  //1、如果调用者不是一个可调用的函数,则抛出TypeError
  if (typeof this !== "function") {
    throw new TypeError(`${this} must be a function`);
  }
  const self = this;
  //2、返回一个新函数
  return function (...args2) {
    return self.apply(
      thisArg,
      args1.concat(args2) // 同时,返回的新函数也可以接受参数,所以把两个参数合并
    );
  };
};

//实现的比较完整版(我理解就是代码的容错性强),因为不是很理解,所以先摘抄过来先
Function.prototype.myBind = function (thisArg, ...args1) {
  //1、如果调用者不是一个可调用的函数,则抛出TypeError
  if (typeof this !== "function") {
    throw new TypeError(`${this} must be a function`);
  }
  const self = this;
  const fNOP = function () {};
  const fBound = function (...args2) {
    // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
    // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
    return self.apply(
      this instanceof fNOP ? this : thisArg,
      args1.concat(args2) // 同时,返回的新函数也可以接受参数,所以把两个参数合并
    );
  };
  // 因为修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转
  // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};

学习手写call,apply JavaScript深入之bind的模拟实现