深入 JS 之造 bind 轮子
Function.prototype.bind 方法的定义,MDN上如是说:
bind() 方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项
官方文字性的语句,没用过不容易理解,根据我的理解和使用经历,简单提要下:
- 创建一个新函数。
- 可以传参。
- 新函数被调用时,其内的 this 指向 bind() 的第一个参数。
- 可以柯里化。
- 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。换句话说,new可以使bind绑定的this失效。
接下来根据这些提要,一步一步仿写出一个bind来。
第一步
现在我们可以利用 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了,再来最后的细节优化。
最终代码
最后优化内容:
- 调用 bind 的不是函数就报错。
- 做个兼容。
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; }
参考
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论