bind
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。(引用MDN)
bind的用法
let foo = {
a: 100,
bar: function () {
console.log(this.a);
}
};
let a = foo.a;
console.log(a); // 100
let bar = foo.bar;
// Cannot read properties of undefined
// 此时this指向了window
// bar()
let bindBar = foo.bar.bind(foo);
bindBar(); // 100
通过上面对bind函数的介绍结合代码例子,相信你已经明白了bind函数的使用方法,bindBar即是bind函数返回的一个新函数。它是指向永远的指向了foo这个对象上。
手写bind函数
Function.prototype.mybind = function (thisArg, ...argArray) {
// 接受绑定的this值和预传入参数
// 判断绑定的函数
if (typeof this !== "function") {
throw new Error("调用bind方法必须是一个函数");
}
// 获取到真实需要调用的函数
var fn = this;
// 绑定this
thisArg = thisArg !== null && thisArg !== undefined ? Object(thisArg) : window;
// 返回函数
return function(...args) {
// 考虑函数返回值
return fn.apply(thisArg,[...argArray,...args])
}
};
-
步骤分析
-
判断边界情况,如果执行bind函数的值非函数则抛出异常
-
获取当前调用
bind函数的this值 -
处理边界情况,如果指向的
this值没有传入时默认指向window。 -
定义返回一个匿名函数,
- 接受
bind后续传入的参数 - 将用户函数放到
this中进行调用 - 处理参数,将预传入参数与传入的函数进行合并操作
- 接受
-
在bind函数中有一个特点,当bind函数返回的新函数作为构造函数时它之前绑定的this值会失效,但是传入的参数不生效,目前我们的实现的bind并没有这个特点。(例如下面这个例子)
var foo = {
name: "_island"
};
function Person(age) {
console.log(this);
console.log(this.name);
console.log(age);
}
let bar = Person.bind(foo,18);
bar()
// {name: "_island"}
// _island
// 18
let newBar=new bar();
// Person {constructor: Object}
// <constructor>: "Person"
// name: "Person"
// undefined
// 18
准确来说,JavaScript中的构造函数只是使用new 调用的普通函数
从上面我们可以发现,当bar函数使用new关键字去调用时,之前的绑定的foo已经失效了。
由于使用了new关键字,此时的this是执行了newBar对象。。
new绑定补充
当我们使用了new关键字去调用一个函数时会发生些什么事情?
- 以构造器的
prototype属性作为原型,创建一个新的对象(这个对象被绑定到构造函数中的this) - 使用第一步创建的对象(也就是
this)和传入的参数给构造器执行 - 如果构造器没有返回对象,则返回第一步创建的对象
接着优化下bind函数
Function.prototype.mybind = function (thisArg, ...argArray) {
// ...
function proxyFn(...args) {
// 当this是实例时,让this指向实例,否则指向原本的目标值
return fn.apply(this instanceof proxyFn ? this : thisArg, [
// 考虑函数返回值
...argArray,
...args,
]);
}
// 返回函数
return proxyFn;
};
这里我们多出了在apply时判断了当前this是否属于proxyFn本身,也即是当作为构造函数时,如果是将绑定函数的this指实例。
将this指向实例之后,我们需要把它的原型对象也赋值过去,这也是为什么上面的代码不直接返回proxyFn这个函数。
Function.prototype.mybind = function (thisArg, ...argArray) {
// ..
// 创建一个中转的函数
var fNOP = function () {};
function proxyFn(...args) {
// 当this是实例时,让this指向实例,否则指向原本的目标值
return fn.apply(this instanceof fNOP ? this : thisArg, [
// 考虑函数返回值
...argArray,
...args,
]);
}
// 使返回的函数原型也有当前this中的原型
// 这里也可以使用Object.create()
fNOP.prototype = this.prototype;
proxyFn.prototype = new fNOP();
// 返回函数
return proxyFn;
};
这里创建了一个中转函数fNOP,先将当前bind函数的this赋值给空函数的原型对象,在通过new关键字调用fNOP函数,目的是为了避免在修改proxyFn原型时也把绑定的函数原型一起被修改。
最终bind函数代码
Function.prototype.mybind = function (thisArg, ...argArray) {
// 接受绑定的this值和预传入参数
// 判断绑定的函数
if (typeof this !== "function") {
throw new Error("调用bind方法必须是一个函数");
}
// 获取到真实需要调用的函数
var fn = this;
// 绑定this
thisArg =
thisArg !== null && thisArg !== undefined ? Object(thisArg) : window;
// 创建一个中转的函数
var fNOP = function () {};
function proxyFn(...args) {
// 当this是实例时,让this指向实例,否则指向原本的目标值
return fn.apply(this instanceof fNOP ? this : thisArg, [
// 考虑函数返回值
...argArray,
...args,
]);
}
// 使返回的函数原型也有当前this中的原型
// 这里也可以使用Object.create()
fNOP.prototype = this.prototype;
proxyFn.prototype = new fNOP();
// 返回函数
return proxyFn;
};