手写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
1
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;
    };
}
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
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]
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
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