手写bind实现
bind
的作用就不再多说了,不清楚可以去 MDN 了解一下,本文主要是模拟实现一个和 bind
一样功能的函数。
使用 bind
,我们可以轻松地改变函数的执行上下文,返回具有新的上下文的函数。基于此,可以实现如下所示(为了简便起见,本文全部使用 ES6 语法)。
function fnBind(fn, context, ...bindArgs) {
if (typeof fn !== 'function') {
throw new Error('只能bind一个function');
}
return function (...args) {
return fn.apply(context || window, [...bindArgs, ...args]);
};
}
测试一下效果
window.age = 10;
function fn(name) {
return {
name,
age: this.age,
};
}
console.log(fn('leevare')); // {name: 'leevare', age: 10}
const newFn = fnBind(fn, { age: 20 });
console.log(newFn('leevare')); // {name: 'leevare', age: 20}
可以看到绑定作用域前 age
是从 window
上获取的,而使用 bind
之后,获取的是绑定作用域中的 age
。
看起来好像是实现了,可实际上永远没有这么简单,我们用 MDN 上的一个示例来测试一下。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return this.x + ',' + this.y;
};
const p = new Point(1, 2);
console.log(p.toString()); // 1,2
const emptyObj = {};
const YAxisPoint = Point.bind(emptyObj, 0);
const axisPoint = new YAxisPoint(5);
axisPoint.toString(); // 0,5
const MyYAxisPoint = fnBind(Point, emptyObj, 0);
const myAxisPoint = new MyYAxisPoint(5);
myAxisPoint.toString(); // [object Object]
使用 js 的 bind
返回的是 0,5
,而用我们实现的返回了[object Object]
。为什么会出现这样的结果?
在 MDN 上找到下面一段描述:
绑定函数自动适应于使用
new
操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的this
就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。
原来如此,我们在实现的时候并没有区分使用场景,把所有传入的 function 的作用域都改变了,如上面的例子,我们把类的作用域改成了 emptyObj
,但是实际上不应该去修改的,所以需要对 fnBind
函数进行一些改造。
那么,如何才能检测出是否通过 new
操作符去调用了 bind
函数呢?之前的文章讲解过关于使用 new
实例化一个对象的时候到底发生了什么(参见这里),所以,我们可以通过返回一个 function class 来解决这个问题。当这个 function 使用 new
实例化时,它将会是一个类,不然则是一个普通的函数。
function fnBind(fn, context, ...bindArgs) {
if (typeof fn !== 'function') {
throw new Error('只能bind一个function');
}
const noopFn = function () {};
const withContextFn = function (...args) {
// 通过判断this是否为父类实例即可判断是否通过new进行了实例化
return fn.apply(this instanceof noopFn ? this : context, [...bindArgs, ...args]);
};
// 寄生组合继承
noopFn.prototype = fn.prototype;
withContextFn.prototype = new noopFn();
withContextFn.prototype.constructor = withContextFn;
return withContextFn;
}
这里用到了寄生组合继承,之前的文章讲解过,这里就不再赘述。所以,判断是否是通过 new
实例化的关键是判断 function 中的 this
是否是父类(即 noopFn
)的实例,如果是,我们就不修改它的上下文,依然绑定它本来的上下文 this
,否则绑定。
此时再运行上面的示例,输出了正确的结果
const MyYAxisPoint = fnBind(Point, emptyObj, 0);
const myAxisPoint = new MyYAxisPoint(5);
myAxisPoint.toString(); // 0,5
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=3059