文章类型:学习笔记

1.bind()的使用

😏😏😏😏😏

1
2
3
4
5
6
7
8
9
        let obj = {
            age: 18
        }
        function getInfo(name, job) {
            console.log(name);
            console.log(job);
            console.log(this.age);
        }
        getInfo.bind(obj, '星空海绵')('Software Engineer');

bind在第一个参数之后的参数,可以直接从bind()传,也可以从bind()返回的函数上传,如上代码。

2.bind()参数

🤔🤔🤔🤔🤔

(1)thisArg

bind的第一个参数是bind被函数调用时作为this参数传给调用函数。如果bind()参数列表为空,或者thisArg为null或undefined, 执行作用域的this 就是新函数的this。如果使用 new操作符 操作绑定函数,则会 忽略这个thisArg参数 。当使用bind在setTimeout中创建一个函数时,作为thisArg传递的任何原始值都会被转换为object。-MDN

  • 1.bind的第一个参数是bind被函数调用时作为this参数传给调用函数
1
2
3
4
5
6
7
        let obj = {
            age: 18
        }
        function getInfo() {
            console.log(this); // {age: 18}
        }
        getInfo.bind(obj)();

此时调用函数 getInfo 中的 this 就是 bind() 中传入的 obj

  • 2.bind()参数列表为空,或者thisArg为null或undefined,执行作用域的this就是新函数的this
1
2
3
4
5
6
7
8
9
        let obj = {
            age: 18
        }
        function getInfo() {
            console.log(this); // Window {}
        }
        getInfo.bind()();
        getInfo.bind(null)();
        getInfo.bind(undefined)();

此时bind的参数为 空 或 null 或 undefined,因为实际上 getInfo 是全局对象调用的,在这里也就是 window 对象,所以此时this就是指向 window 对象的。

  • 3.如果使用new操作符操作绑定函数,则会忽略这个thisArg参数
1
2
3
4
5
6
7
8
        let obj = {
            age: 18
        }
        function getInfo() {
            console.log(this); // getInfo {}
        }
        let a = getInfo.bind(obj)
        let b = new a();

此时不管调用的时候传不传参数 obj,getInfo 中的 this 都是指向创建对象 b 的,这里就涉及到 new 构造函数创建对象的过程了,建议精读 MDN 这部分。

  • 4.当使用bind在setTimeout中创建一个函数时,作为thisArg传递的任何原始值都会被转换为object。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
        function A() {
            this.name = '星空海绵';
        }
        A.prototype.say = function () {
            setTimeout(this.read.bind(this), 1000)
        }
        A.prototype.read = function () {
            console.log(this.name);
        }
        let b = new A();
        b.say();

在默认情况下,使用 setTimeout() 时,this 关键字会指向 window (或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

(2)arg1, arg2, …

当bind被函数调用时,传给调用函数的参数列表

3.手写 bind()

😎😎😎😎😎

(1)不支持 new 调用 bind() 返回的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        let obj = {
            age: 18
        }
        function getInfo(name, job) {
            console.log(name);
            console.log(job);
            console.log(this);
            console.log(this.age);
        }
        // getInfo.call(obj, '星空海绵', 'Software Engineer');
        Function.prototype.myBind = function (context) {
            var _that = this,
                slice = Array.prototype.slice,
                args = slice.call(arguments, 1);
            context = context || window;
            return function () {
                return _that.apply(context, args.concat(slice.call(arguments)))
            }
        }
        var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
            b = new a(), // 上面getInfo中的this打印结果为 obj 对象
            c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
            d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象

从上面的代码中我们可以看出来,此时当使用 new 调用时,并不会忽略掉传入的第一个参数 obj,调用函数的 this 一直是传入的第一个参数,不符合上面列举的原生的 bind() 第一个参数的第3条,当使用 new 调用时,会忽略第一个参数。

(2)支持 new 调用

上面第一种其实已完成了原生bind的基本功能,可以改变this指向了,但是不支持new调用的时候,那我们来思考一下为什么。

  • 当使用 new 调用我们写的myBind函数返回的函数时,这就是new通过构造函数创建一个对象的过程,MDN new创建对象过程
    1.首先创建一个空对象({})
    2.链接该对象(设置该对象的constructor)到另一个对象
    3.将步骤1新创建的对象作为this的上下文
    4.如果该函数没有返回对象,则返回this

看了上面new创建对象的过程,主要是第3步,将新创建的对象作为this的上下文,我们再看上面的代码,返回的函数中的this如果你去打印的话发现确实是空对象,符合上面new的创建过程,但是,下面一行用apply将调用函数getInfo中的this指向改到了传入的obj,这就不对了,所以说当用new调用的时候,此时就应该忽略obj,那么我们就需要判断new调用和不调用的时候就行了,ES6中有 new.target 可以判断一个函数是否被new调用过,那么ES5中是否有方法可以判断,没有的话那么模拟一个new.target就行了

用new.target的话是下面这两种写法都行:

 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
        let obj = {
            age: 18
        }
        function getInfo(name, job) {
            console.log(name);
            console.log(job);
            console.log(this);
            console.log(this.age);
        }
        // getInfo.call(obj, '星空海绵', 'Software Engineer');
        Function.prototype.myBind = function (context) {
            var _that = this,
                slice = Array.prototype.slice,
                args = slice.call(arguments, 1),
                obj = {};
            context = context || window;
            obj.__proto__.constructor = _that.prototype.constructor;
            return function () {
                if (new.target) {
                    return _that.apply(obj, args.concat(slice.call(arguments)))
                } else {
                    return _that.apply(context, args.concat(slice.call(arguments)))
                }

            }
        }
        var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
            b = new a(), // 上面getInfo中的this打印结果为 b 对象
            c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
            d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        Function.prototype.myBind = function (context) {
            var _that = this,
                args = [].slice.call(arguments, 1);
            context = context || window;
            return function () {
                if (new.target) {
                    return new _that(...args.concat(...arguments))
                } else {
                    return _that.apply(context, args.concat([].slice.call(arguments)))
                }

            }
        }

不使用 ES6 的new.target:

 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
        let obj = {
            age: 18
        }
        function getInfo(name, job) {
            console.log(name);
            console.log(job);
            console.log(this);
            console.log(this.age);
        }
        // getInfo.call(obj, '星空海绵', 'Software Engineer');
        Function.prototype.myBind = function (context) {
            var _that = this,
                args = [].slice.call(arguments, 1);
            context = context || window;
            return function F() {
                if (this instanceof F) {
                    return new _that(...[...args, ...arguments]);
                }
                return _that.apply(context, [...args, ...arguments])
            }
        }
        // getInfo.myBind(obj, '星空海绵', 'Software Engineer')()
        var a = getInfo.myBind(obj, '星空海绵', 'Software Engineer'),
            b = new a(), // 上面getInfo中的this打印结果为 b 对象
            c = getInfo.bind(obj, '星空海绵', 'Software Engineer'),
            d = new c(); // 上面getInfo中的this打印结果为新创建的 d 对象

上面代码中this如果在F构造函数的原型链上,那就证明使用new调用了myBind返回的构造函数。

总结:手写bind遇到new调用的问题,关键是需要理解new调用构造函数创建对象的过程。