zoukankan      html  css  js  c++  java
  • 对this的理解与总结

    this既不指向函数自身,也不指向函数的词法作用域!它指向谁完全取决于它在哪里被调用,被谁调用!

    绑定规则

    总体来说,this的绑定规则有:

    1. 默认绑定(严格模式/非严格模式)
    2. 隐式绑定
    3. 显式绑定
    4. new存在时绑定
    5. 箭头函数绑定

    1.默认绑定:

    默认绑定就是没有应用其他绑定规则时的绑定方式。

    在非严格模式下,直接调用函数默认this指向全局对象,即window。这里要注意!即使是在某个函数中,如果自执行某个函数或者是使用setTimeout内部执行某个函数,它的this都指向全局变量!

            var value = 1;
            var obj = {
                value: 2
            }
            function funA() {
                setTimeout(function () {
                    console.log(this.value)
                }, 100)
            }
    
            funA.call(obj)  // 1,这里setTimeou相当于window调用这个函数,即默认调用。

    如果想输出obj内部定义的value,可以在setTimeout外层设变量var that = this; 然后console.log(that.value)即可。将this设定指向本函数的this。

    在严格模式下,并不能将全局对象用于默认绑定,this会绑定到undefined

            function foo() {
                'use strict'   //  严格模式下会输出undefined,非严格模式输出2
    
                console.log(this.a);
            }
            var a = 2;
            foo()

    但是在严格模式下,调用函数不影响默认绑定!

            function foo() {
                console.log(this.a);
            }
            var a = 2;
            (function () {
                'use strict'
    
                foo()   // 在严格模式下调用函数并不影响默认绑定!所以输出2
            })()

    2.隐式绑定:

    就是前面有对象引用时,this会指向这个对象,如果有多层,则只有上一层起作用!并不会一直向上寻找!!!切记!!!

            var obj = {
                a: 1,
                b: 2,
                getA: function () {
                    console.log(this.a)  
                },
                getB: {
                    b: 4,
                    subGetA: function () {
                        console.log(this.a)
                    },
                    subGetB: function () {
                        console.log(this.b)
                    }
                }
            }
    
            obj.getA()      // 1
            obj.getB.subGetA()   // undefined,这里因为getB中并没有a,因此输出undefined
            obj.getB.subGetB()   // 4

    有时候会有隐式丢失的情况出现,比如:

    // 代码同上,加如下语句:
    
    var a = "global"
    var other = obj.getA   // 将obj.getA赋值给other,other执行的时候其实是相当于 other(){ console.log(this.a) }
    
    other()   // "global"

    此时,other函数实际上是var other = function(){ consle.log(this.a) },相当于默认绑定this,this指向window!(自我感觉这时的other其实与obj和getA摆脱了关系,刚才那一步也就是进行了赋值操作而已~)

    间接引用时(最容易发生在赋值操作!!),调用函数会应用默认绑定规则:

        function foo() {
            console.log(this.a)
        };
    
        var a = 1;
    
        var obj = {
            a: 3,
            foo: foo
        };
    
        var fun = {
            a: 2
        };
    
        (fun.foo = obj.foo)()  // 1
    
        // 如果将上面一句拆成如下两句来写,结果又不同
        fun.foo = obj.foo
        fun.foo()              // 2

     (fun.foo = obj.foo)得到的结果是( function foo(){ console.log(this.a) } ),所以这个函数执行等同于自执行foo这个函数,this指向全局对象

    两句拆开后,第一句是将obj的foo函数赋值给fun,此时,fun对象内部也有了一个foo函数,那fun再调用foo则是应用了this的隐式调用规则,this指向调用对象fun,因此这时输出2

    函数中传入参数也属于隐式丢失的一种

            var obj = {
                a: 1,
                getA: function () {
                    console.log(this.a)
                }
            }
    
            var a = "global";
    
            function getFn(fn) {  // 这里的fn = obj.getA,相当于上面一例当中的other = obj.getA
                fn()
            }
    
            getFn(obj.getA)   // global

    此时,obj.getA当作参数传到getFn中,这时在getFn中调用它等同于默认调用,this指向全局对象或undefined。

    与此情况类似的还有使用定时器调用函数,setTimeout(function(){ ... //在函数中的this也存在隐式丢失 }, time),原理同参数传递!

    3.显示绑定:使用call()/apply()

    call()和applay()的区别:前者接受的是若干参数的列表,而后者接受的是一个包含多个参数的数组

    fn.call(obj, arg1, arg2,...)

    fn.apply(obj, [arg1, arg2,...])

    延伸一下:

    fn.bind(obj, [arg1, [arg2, [, ...]]])

    bind的特性:

    1. 指定this
    2. 返回一个函数
    3. 可以指定参数
    4. 柯里化

    call() applay()和bind的区别:call和apply是直接执行了前面的fn函数,而bind()返回的是一个新函数,当再次调用它时,它的this值会绑定到obj上。

    call()和apply()中的第一个参数为一个对象,使用call()或applay()会默认将this绑定到这个对象上。

        function foo() {
            console.log(this.a)
        }
    
        var obj = {
            a: 3
        }
    
        var fun = function () {
            foo.call(obj)  // 强制把foo的this绑定到了obj上
        }
    
        fun()  // 3
        setTimeout(fun, 1000)  // 3
        fun.call(window)   // 3 fun函数内部已经存在硬绑定,不可能再修改它的this

    显示绑定无法解决丢失绑定的问题。?????啥意思????

    有时候把null或者undefined作为this的绑定对象传入call()、apply()的时候,实际应用的是默认规则!即相当于没有应用显示绑定!!

    但是,有以下两种情况可以利用null:

    1. 使用apply()展开一个数组,因为apply的语法是:apply(obj, [para1, para2, para3...]),参数放在一个数组当中传入到函数中
    2. 利用bind预先设置一些参数
        function foo(a, b) {
            console.log("a:" + a + ",b:" + b);
        }
    
        // 1. 利用null把数组“展开”成参数
        foo.apply(null, [2, 3]); // a:2,b:3
    
        // 2. 利用bind(..)进行柯里化(即预先设置一些参数)
        var bar = foo.bind(null, 2);
        bar(3); // a:2,b:3 

    但是,传入null会带来一些副作用:比如某个函数确实使用了this,那默认绑定规则会把this绑定到全局对象中

    所以,可以用var empty = Object.create(null);生成一个空对象,用它代替null,此时的empty不会创建Object.prototype这个委托,比{}更空!

    4.new绑定:

    构造函数和普通函数的差别就在于:构造函数是被new操作符调用的函数,会生成一个实例对象。该实例对象有两个特性:1. 能访问到构造函数内部的属性;2. 能访问到原型里的属性

    使用new调用函数,会自动执行如下操作:

    1. 创建一个新对象
    2. 将构造函数的作用域赋给新对象(因此,this指向了这个新对象)
    3. 执行构造函数中的代码(为这个新对象添加属性)
    4. 返回新对象
            function create() {
                // 创建一个对象
                var obj = new Object();
    
                // 将arguments的第一个参数,即作为要传入的构造函数
                var Constructor = [].shift.call(arguments);
    
                // 将obj的原型指向构造函数
                obj.__proto__ = Constructor.prototype;
    
                // 改变Constructor的this指向,指到obj对象,并给obj添加新属性arguments
                Constructor.apply(obj, arguments);
                // 返回obj
                return obj;
            }

    解析:

    1. [].shift的作用是将arguments这个伪数组对象转成数组,等价于:Array.prototype.shift;因为arguments只能获取索引值和length,没有数组的一系列方法,因此加call是通过显示绑定让arguments变相有shift这个方法
    2. var Constructor = [].shift.call(arguments);这句其实得到的就是create这个函数,并将其赋给Constructor
    3. 但是还有一个问题是:需要判断return的值是不是对象!!!

    如果函数没有return返回值 or return的是String/Number/null,那么new表达式中的函数调用会自动返回这个新对象;

    如果函数return了一个Object类型,则this指向return返回的对象。

    所以create()得到如下优化代码:

            function create() {
                var obj = new Object();
                var Constructor = [].shift.call(arguments);
                obj.__proto__ = Constructor.prototype;
                var result = Constructor.apply(obj, arguments);      
                return typeof result == 'object' ? result : obj;
            }

    __proto__是访问器属性,通过它可以访问到对象的内部属性[[prototyoe]],

    但是在使用时并不推荐使用__proto__,原因有两点:

    1. __proto__在ES6时才被标准化,存在浏览器兼容问题
    2. 通过改变一个对象的[[prototype]]去改变和继承对象的属性会造成严重的性能问题,所以应该尽量避免去改变一个对象的[[prototype]]属性

    因此,如果要创建一个对象并且继承这个对象的[[prototype]],这里推荐使用Object.create(),因此代码优化如下:

            function create() {
                var Constructor = [].shift.call(arguments);
                var obj = Object.create(Constructor.prototype);
                var result = Constructor.apply(obj, arguments);
    
                return result instanceof Object ? result : obj;
            }

    5.箭头函数(不按照以上四种绑定规则,而是由外层(函数或者全局)作用域来决定this)

    以上四种绑定规则实际上总结为:this总是指向调用该函数的对象!

    箭头函数的this总结:

    1. 箭头函数不绑定this
    2. 其this寻值行为与普通变量相同,是在作用域中逐级寻找
    3. 无法通过bind、call、apply来直接修改(可以间接修改)
    4. 改变作用域中的this指向可以改变箭头函数的this
    function foo() {
        return (a) => {
            console.log( this.a );      // this指向的是foo(),因为箭头函数在foo函数这个作用域中
        };
    }
    
    var obj1 = {
        a: 2
    };
    
    var obj2 = {
        a: 3
    }
    
    var bar = foo.call( obj1 );
    bar.call( obj2 );        // 2,不是3!这里实际是foo.call(obj1).(obj2)

    foo的this已经绑定到了obj1上,foo()的内部创建了一个箭头函数会捕捉foo()的this,箭头函数的绑定无法被修改!!即使使用new也不会修改!

    并且我们知道通过硬绑定后不能再次修改它的绑定,所以这里是把foo里的this指向obj1而不是obj2

    几种绑定方式的优先级:

    new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

     

  • 相关阅读:
    QBoxLayout中setSpacing(int)和addSpacing(int)的区别
    BZOJ1017 树形DP
    树形DP 2415HDU
    树形DP基础题 HDU1520
    最小生成树个数 并查集压缩路径
    伸展树基本概念基本题目
    BZOJ1010单调性DP优化
    BZOJ1009 矩阵快速幂+DP+KMP
    字典树(数组实现)
    poj 1611 The Suspects(并查集输出集合个数)
  • 原文地址:https://www.cnblogs.com/ningyn0712/p/11730138.html
Copyright © 2011-2022 走看看