JS复习笔记之造bind轮子

本文详细解析了Function.prototype.bind方法的实现原理,并通过逐步构建,最终实现了一个功能全面的bind方法,包括支持柯里化及处理作为构造函数时this指向的问题。

Function.prototype.bind方法的定义,MDN上如是说:

bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项

官方文字性的语句,没用过不容易理解,根据我的理解和使用经历,简单提要下:

  1. 创建一个新函数。
  2. 可以传参。
  3. 新函数被调用时,其内的this指向bind()的第一个参数。
  4. 可以柯里化。
  5. 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。换句话说,new可以使bind绑定的this失效。

接下来根据这些提要,一步一步仿写出一个bind来。

第一步

上一篇文章介绍了 JS复习笔记之造call.apply轮子 的实现,现在我们可以利用 call来绑定this ,并且 return一个新函数 。

所以:

// 第一版代码
Function.prototype.mybind = function (obj) {
  var self = this;
  return function () {
    return self.apply(obj)
  }
}

// 例子
const obj = {
  value: 1
}

function bar () {
  console.log(this.value);
  return this.value;
}

const newBar = bar.bind(obj);
const newBar2 = bar.mybind(obj);

console.log(newBar());
console.log('*******华丽的分界线*******');
console.log(newBar2());

复制代码

输出是:

1
1
*******华丽的分界线*******
1
1
复制代码

第一步我们实现了摘要的第1、3点和不完整的第2点。

接下来第二步我们实现 柯里化 和 完整的第2点 。

第二步

利用 模拟bind方法的arguments 与 返回新函数的arguments 参数拼接成一个数组传入 aplly() 来实现。

// Second Codes
Function.prototype.mybind = function (obj) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1); // 切出从第一位开始到最后的函数参数数组args
  return function () {
    var newArgs = Array.prototype.slice.call(arguments);
    return self.apply(obj, args.concat(newArgs));
  }
}

// Example
const obj = {
  value: 1
}

function bar (name, age) {
  console.log(this.value);
  console.log(name);
  console.log(age);
}

const newBar = bar.bind(obj, 'Sadhu');
const newBar2 = bar.mybind(obj, 'Sadhu');

newBar(17);
console.log('*******华丽的分界线*******');
newBar2(17);
复制代码

输出:

1
Sadhu
17
*******华丽的分界线*******
1
Sadhu
17
复制代码

第三步

实现摘要的第5点,new可以使bind绑定的this失效。具体什么意思呢?

举个例子:

const obj = {
  value: 1
}

const value = 2;

function bar (name, age) {
  this.name = name;
  this.age = age;
  console.log(this.value);
}

bar.prototype.habit = 'coding'; // 划重点

const newBar = bar.bind(obj, 'sadhu');

const newObj = new newBar(17);
console.log('*******华丽的分界线*******');
console.log(newObj.name);
console.log(newObj.age);
console.log(newObj.habit);
// undefined 
//*******华丽的分界线*******
// sadhu
// 17
// coding
复制代码

此处全局和obj中都有 value 。但是new调用newBar()执行

console.log(this.value);

时依然返回 undefined。说明bind绑定的this失效了。其实此时这里的this已经指向了 newObj 实例,这里可以看造new轮子的文章。

还有个需要关注的要实现一点,我上述代码中 划重点 了。根据输出的情况来看,意味着:

意味着绑定函数bar的prototype属性 等于 newObj.__proto__等于 newBar.prototype

好,搞清楚了需求,接下来来实现。

// codes
Function.prototype.mybind = function (obj) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  
  var fBound = function () {
    var newArgs = Array.prototype.slice.call(arguments);
    // 当fBound为构造函数时,它的this指向实例。 instanceof判断为true。
    // 当fBound为普通函数时,它的this默认指向winodw。 instanceof判断为false。
    return self.apply(this instanceof fBound ? this : obj, args.concat(newArgs));
  }
  // 这样返回函数fBound的实例就可以访问到绑定函数的prototype属性上的值。
  fBound.prototype = this.prototype;
  return fBound;
}

复制代码

测试:

// Example
const value = 2;

const obj = {
  value: 1
}

function bar (name, age) {
  this.name = name;
  this.age = age;
  console.log(this.value);
}

bar.prototype.habit = 'coding'; // 划重点

const newBar = bar.bind(obj, 'sadhu');
const newBar2 = bar.mybind(obj, 'sadhu')

const newObj2 = new newBar2(17);
console.log('*******华丽的分界线*******');
console.log(newObj2.name);
console.log(newObj2.age);
console.log(newObj2.habit);
// undefined 
//*******华丽的分界线*******
// sadhu
// 17
// coding
复制代码

根据输出结果看,目前为止的模拟,输出是正确的。

这样就完了吗?有没有发现代码中哪里有点不对劲?

提示: fBound.prototype = this.prototype;

接下来我们进行优化。

轮子代码优化

在第三步的写法中,我们使用了fBound.prototype = this.prototype,那么我们直接修改 fBound.prototype 的时候也会直接修改了绑定函数的 prototype 。

此时我们可以通过一个空函数来进行中转。

Function.prototype.mybind = function (obj) {
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var fNOP = function() {};

  var fBound = function () {
    var newArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fBound ? this : obj, args.concat(newArgs));
  }
  
  fNOP.prototype = this.prototype; // 他俩指向的是同一原型对象。
  fBound.prototype = new fNOP(); // 之后要找原型链上的属性就是 fBound实例.__proto__ === fBound.prototype, fBound.prototype.__ptoto__ === FNOP.prototype === this.prototype。
  return fBound;
}
复制代码

此时对fBound.prototype的操作就不会同步到绑定函数的prototype的修改了。

到此为止代码完成百分之98了,再来最后的细节优化。

最终代码

最后优化内容:

  1. 调用bind的不是函数就报错。
  2. 做个兼容。
Function.prototype.mybind = Function.prototype.bind || function (obj) {
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  
  var fNOP = function () {};

  var fBound = function () {
    var newArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fBuond ? this : obj, args.concat(newArgs));
  }
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
}
复制代码

参考:

  1. MDN
  2. JavaScript深入之bind的模拟实现

转载于:https://juejin.im/post/5ca9aa715188254351722d62

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值