Javascript bind的应用场景及实现思路

#Javascript bind的应用场景

###this的指向问题
在深入了解Js中的bind函数前我们先来看一段代码

1
2
const $ = document.querySelector;
const div = $('div');

以上代码将产生如下的错误
VM723:2 Uncaught TypeError: Illegal invocation
at :2:1

我们来深入了解下为什么会报错

  1. querySelector 这个方法是document对象下的方法,window对象下并没有这个方法
  2. 正确的操作应该时什么样的呢?

###bind方法的使用

可以使用bind解决上面的问题

1
2
3
const $ = document.querySelector.bind(document);
const div = $('div');
// 返回结果 <div>......</div>

使用call方法或者apply方法也是可以的

1
2
3
4
5
const $ = document.querySelector;
const div = $.call(document,'div');
// call 和 apply 函数的缺陷是无法返回一个新的方法
//是立即执行的
// 返回结果 <div>......</div>

bind方法返回的是一个函数,而且可以在绑定的时候指定返回函数的入参
可以实现类似curry化的效果

1
2
3
4
5
6
7
8
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

###bind方法的一些细节
不管bind多少次this总是指向第一个bind的对象

1
2
3
4
5
6
7
8
9
10
11
12
const 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
13
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

###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
14
Function.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
14
Function.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
23
Function.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 官方文档中的pollyfill

1
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
27
if (!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;
};
}