#Javascript bind的应用场景
###this的指向问题
在深入了解Js中的bind函数前我们先来看一段代码1
2const $ = document.querySelector;
const div = $('div');
以上代码将产生如下的错误
VM723:2 Uncaught TypeError: Illegal invocation
at
我们来深入了解下为什么会报错
- querySelector 这个方法是document对象下的方法,window对象下并没有这个方法
- 正确的操作应该时什么样的呢?
###bind方法的使用
可以使用bind解决上面的问题1
2
3const $ = document.querySelector.bind(document);
const div = $('div');
// 返回结果 <div>......</div>
使用call方法或者apply方法也是可以的1
2
3
4
5const $ = document.querySelector;
const div = $.call(document,'div');
// call 和 apply 函数的缺陷是无法返回一个新的方法
//是立即执行的
// 返回结果 <div>......</div>
bind方法返回的是一个函数,而且可以在绑定的时候指定返回函数的入参
可以实现类似curry化的效果1
2
3
4
5
6
7
8const sum = (...args) => args.reduce((prev,next) => prev+next, 0);
// 此处传入一个2 会包装出一新的sub函数,并且默认第一个入参是2;
const sum2 = sum.bind(null, 2);
console.log(sum2(3));
// log 5
const sum1_2_3_4 = sum.bind(null, 1,2,3,4);
console.log(sum1_2_3_4(5));
// log 15
###bind方法的一些细节
不管bind多少次this总是指向第一个bind的对象1
2
3
4
5
6
7
8
9
10
11
12const sayName = function () {
console.log(this.name);
}
const p1 = {
name:'vidy'
}
const p2= {
name: 'lily'
}
const say = sayName.bind(p1).bind(p2);
say();
// log 'vidy' p1's name;
bind也支持绑定构造函数1
2
3
4
5
6
7
8
9
10
11
12
13function Person (address, name) {
this.address = address;
this.name = name;
}
const p = new Person('Hujian','vidy');
const HujianPerson = Person.bind(null,'Hujian');
const vidy = new HujianPerson('vidy');
console.log(vidy.address)
// log Hujian
console.log(vidy instanceof Person)
// log true
console.log(vidy instanceof HujianPerson);
// log true
###bind方法的pollyfill
bind方法在一些比较老旧的浏览器中并不支持
下面我们尝试着一步一步的去实现这个特性
#####step1
我们先来看看bind函数的原型1
2
3
4
5
6
7
8/**
* For a given function, creates a bound function that has the same body as the original function.
* The this object of the bound function is associated with the specified object,
and has the specified initial parameters.
* @param thisArg An object to which the this keyword can refer inside the new function.
* @param argArray A list of arguments to be passed to the new function.
*/
bind(this: Function, thisArg: any, ...argArray: any[]): any;
####step1
**创建与原始函数相同主体的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14Function.prototype.bind = function (thisArg) {
const self = this;
return function () {
self.apply(thisArg, arguments);
};
};
const say = function () {
console.log(this.name)
}
const obj = {
name: 'vidy'
}
const newSay = say.bind(obj)
// log vidy
####step2
**实现curry化效果1
2
3
4
5
6
7
8
9
10
11
12
13
14Function.prototype.bind = function (thisArg, ...outerArgs) {
const self = this;
return function (...innerArgs) {
return self.apply(thisArg, outerArgs.concat(innerArgs));
};
};
const sum = (...args) => args.reduce((prev,next) => prev+next, 0);
// 此处传入一个2 会包装出一新的sub函数,并且默认第一个入参是2;
const sum2 = sum.bind(null, 2);
console.log(sum2(3));
// log 5
const sum1_2_3_4 = sum.bind(null, 1,2,3,4);
console.log(sum1_2_3_4(5));
// log 15
####step3
**绑定构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Function.prototype.bind = function (thisArg, ...outerArgs) {
const self = this;
const fNOP = function () {};
const bindFn = function (...innerArgs) {
return self.apply(this instanceof fNOP && thisArg? this: thisArg|| window, outerArgs.concat(innerArgs));
};
fNOP.prototype = self.prototype;
bindFn.prototype = new fNOP();
return bindFn;
};
function Person (address, name) {
this.address = address;
this.name = name;
}
const p = new Person('Hujian','vidy');
const HujianPerson = Person.bind(null,'Hujian');
const vidy = new HujianPerson('vidy');
console.log(vidy.address)
// log Hujian
console.log(vidy instanceof Person)
// log true
console.log(vidy instanceof HujianPerson);
// log true
我们来看看这中间发生了什么事情
当我们使用1
const HujianPerson = Person.bind(null,'Hujian');
我们看看bind函数里面是如何操作的
this指向的是Person构造函数
我们构建了一个新的fNOP 构造函数,并似fNOP的prototype指向this.
这样我们就可以向上追溯Person.
使得vidy instanceof Person 得以成立.
然后我们让绑定后的函数的prototype指向fNOP实例.
当我们使用1
const vidy = new HujianPerson('vidy');
创建出来的HujianPerson实例
因此vidy instanceof HujianPerson 得以成立
最后我们来看看 mozilla 官方文档中的pollyfill1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}