手写bind函数
# 手写bind函数
bind()
方法创建一个新的函数,在调用时设置this
关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
目标函数调用bind后,不会立即执行,而是返回一个新的函数;调用新函数才会执行目标函数。
#
call()
、apply()
、bind()
的区别
- call与apply: 第一个参数都是重定义this对象;第二个参数,call是直接放进去,用逗号隔开;apply则是放在一个数组里。
- bind除了返回的是函数以外,参数和call一样。
- 第一个参数传null或undefined时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global。
- bind返回的是函数,bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
[TOC]
# 一、bind函数实现
# 1.1 目标函数调用后返回新函数
目标函数:调用bind的函数,func.bind
, func就称之为是目标函数。
新函数:bind返回的函数,let newFunc = func.bind()
,newFunc就称之为新函数。
# 1.2 调用新函数,也就调用了目标函数,目标函数this为传入bind的第一个参数
# 1.3 把新函数当做构造函数,是可以找到目标函数的原型,所以新函数继承目标函数的原型
# 1.4 新函数当作构造函数后,目标函数this指向原型对象
function Parent() {}
Parent.prototype.parentMethod = function parentMethod() {};
function Child() {}
Child.prototype = Object.create(Parent.prototype); // re-define child prototype to Parent prototype
Child.prototype.constructor = Child; // return original constructor to Child
2
3
4
5
6
7
8
原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。
alert(Cat.prototype.constructor == Animal); //true
更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
alert(cat1.constructor == Cat.prototype.constructor); // true
因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!
alert(cat1.constructor == Animal); // true
这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。
这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,
o.prototype = {};
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
o.prototype.constructor = o;
# 二、polyfill1
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');
}
// slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。
// 原始数组不会被改变。
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
// 如果这个函数作为了构造函数,那么目标函数的this,应该执行的是实例对象
// 如果这个不是作为构造函数,目标函数中的this还指向oThis
return fToBind.apply(this instanceof fBound ?
this :
oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
// 这里的arguments是指fBound的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
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
28
29
30
31
32
33
34
35
36
# 三、polyfill2
/*
1. 返回的函数作为构造函数,新函数要继承目标函数的原型
2. 一旦把新函数当成构造函数了,目标函数的this应该指向实例对象
*/
Function.prototype.customeBind = function (thisArg,...list){
let self = this; // 目标函数
// 自己实现的bind函数,如果把返回的新函数当成了构造函数,此时会遇到问题,就是找不到目标函数原型上的方法,解决:放新函数继承目标函数的原型
let Bound = function(...arg2){
// 如果这个函数作为了构造函数,那么目标函数的this,应该执行的是实例对象
// 如果这个不是作为构造函数,目标函数中的this还指向thisArg
let thisArgSelf = this instanceof Bound ? this : thisArg;
self.apply(thisArgSelf,[...list,...arg2])
}
// 原型继承
// Object.create 用来创建以某一个对象作为原型的对象的
Bound.prototype = Object.create(self.prototype);
Bound.prototype.constructor = self;
// 返回的新函数
return Bound
}
function func(...arg){
console.log(this);
console.log(arg)
}
func.prototype.miaov = function (){
console.log(123)
}
let newFunc = func.bind({a:1},1,2,3,4);
let newFunc2 = func.customeBind({a:1},1,2,3,4)
// 用new关键字来调用定义的构造函数
// 构造函数里没有显式调用return时,默认是返回this对象,也就是新创建的实例对象。
let f1 = new newFunc(5,6,7,8)
// 如果新函数作为构造函数,则忽略第一个值
console.log(f1.miaov);
console.log('-------------------')
let f2 = new newFunc2(5,6,7,8);
console.log('-------------------')
let newFunc3 = func.bind({a:1},1,2,3,4);
let newFunc4 = func.customeBind({a:1},1,2,3,4)
newFunc3(5,6,7,8)
console.log('-------------------')
newFunc4(5,6,7,8);
// result
// func {}
// [1,2,3,4,5,6,7,8]
// function (){
// console.log(123)
// }
// -------------------
// Bound {}
// [1,2,3,4,5,6,7,8]
// -------------------
// {a:1}
// [1,2,3,4,5,6,7,8]
// -------------------
// {a:1}
// [1,2,3,4,5,6,7,8]
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69