Function.prototype.bind方法的定义,MDN上如是说:
bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项
官方文字性的语句,没用过不容易理解,根据我的理解和使用经历,简单提要下:
- 创建一个新函数。
- 可以传参。
- 新函数被调用时,其内的this指向bind()的第一个参数。
- 可以柯里化。
- 当 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了,再来最后的细节优化。
最终代码
最后优化内容:
- 调用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;
}
复制代码
参考: