Loading

JS 手写之 Function.prototype.bind

Function.prototype.bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

语法

function.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  • thisArg - 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用 new 运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg。
  • arg1, arg2, ... - 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

描述

bind() 函数会创建一个新的绑定函数(bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
绑定函数具有以下内部属性:

  • [[BoundTargetFunction]] - 包装的函数对象
  • [[BoundThis]] - 在调用包装函数时始终作为 this 值传递的值。
  • [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
  • [[Call]] - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个 this 值和一个包含通过调用表达式传递给函数的参数的列表。

当调用绑定函数时,它调用 [[BoundTargetFunction]] 上的内部方法 [[Call]],就像这样 Call(boundThis, args)。其中,boundThis 是 [[BoundThis]],args 是 [[BoundArguments]] 加上通过函数调用传入的参数列表。

绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。

Function.prototype.myBind

Function.prototype.myBind = function () {
  // 将类数组转为数组
  var args = Array.prototype.slice.call(arguments);
  // 在参数数组中取出首个参数,也就是 context,现在参数数组中都是入参了
  var context = args.splice(0, 1)[0];
  // 保存this
  var fn = this;
  // 判断是否使用了 new 操作符, 如果用了 new 第一个参数就无效了
  var Fn = function () {};
  // 结果方法,最后会返回出去
  var res = function res() {
    // 这里的 arguments 其实是调用时的入参
    let rest = Array.prototype.slice.call(arguments);
    // this只和运行的时候有关系,所以这里的this和上面的fn不是一码事
    // new res() 和 res()在调用的时候,res中的this是不同的东西
    return fn.apply(this instanceof Fn ? this : context, args.concat(rest));
  };
  // 当使用了 new 关键字后,原型链的继承关系需要调整
  if (this.prototype) {
    Fn.prototype = this.prototype;
  }
  res.prototype = new Fn();
  // 将构造函数设置为自身,因为一个构造函数原型的构造构造器就是构造函数本身
  res.prototype.constructor = res;
  return res;
};

测试

  1. 修改 this 指向
function fn() {
  console.log(this);
}

var a = fn.myBind({ name: "Frank" });
a(); // {name: "Frank"}
fn(); // window
  1. bind 允许传递多个参数
function fn() {
  console.log(this, arguments);
}

var a = fn.myBind({ name: "Frank" }, "a", [1, 2, 3], 2);
a(); // {name: "Frank"} Arguments(3) ["a", Array(3), 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
fn(); // window Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
  1. bind 返回的方法也允许传入多个参数
function fn() {
  console.log(this, arguments);
}

var a = fn.myBind(null, "a", [1, 2, 3], 2);
a(1, 2, 3, 4); // window Arguments(7) ["a", Array(3), 2, 1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
fn(1, 2, 3, 4); // window Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  1. 对 bind 返回的方法使用 new 关键字,生成的新对象和 调用 bind 传入的上下文无关
function fn() {
  this.name = "frank";
}

var a = fn.myBind({ name: "json" });
var b = new a();
console.log(b);
// res {name: "frank"}
//  name: "frank"
//  __proto__: fn

附原型链

function fn() {
  this.name = "frank";
}

var a = new (fn.myBind({ name: "json" }))();
a.__proto__.constructor;
// ƒ res() {...}

var b = new fn();
b.__proto__.constructor = fn;
fn.prototype.constructor = fn;
b.__proto__ === fn.prototype;
posted @ 2021-05-29 23:26  Frank-Link  阅读(201)  评论(0编辑  收藏  举报