美文网首页
this指向总结2

this指向总结2

作者: 简单tao的简单 | 来源:发表于2020-04-20 15:35 被阅读0次

    this绑定的几种场景

    • 全局环境下:this 始终指向全局对象(window), 无论是否严格模式;
    • 函数上下文调用
      • this默认绑定(函数直接调用)
      • this隐式绑定(对象中的this、原型链中的this)
      • this显式绑定(call、apply、bind)
      • new绑定
    • 箭头函数的this

    一. 全局环境下

    this 始终指向全局对象(window), 无论是否严格模式;

    "use strict";
    console.log(this) //window
    console.log(this.document === document); // true
    console.log(this === window); // true
    this.a = 37;
    console.log(window.a); // 37
    

    二. 函数上下文调用

    1. this默认绑定(函数直接调用)

    this默认绑定我们可以理解为函数调用时无任何调用前缀的情景

    • 正常情况(非严格模式):this指向全局对象window
    • 严格模式:this指向undefined
    //非严格模式:this指向全局对象window
    function fn1() {
        let fn2 = function () {
            console.log(this); //window
            fn3();
        };
        console.log(this); //window
        fn2();
    };
    function fn3() {
        console.log(this); //window
    };
    fn1();
    //这个例子中无论函数声明在哪,在哪调用,由于函数调用时前面并未指定任何对象,这种情况下this指向全局对象window
    
    //严格模式:this指向undefined
    function fn() {
        console.log(this); //window
        console.log(this.name); //听风是风
    };
    function fn1() {
        "use strict";
        console.log(this); //undefined
        console.log(this.name); //TypeError: Cannot read property 'name' of undefined
    };
    var name = '听风是风'
    fn()
    fn1() 
    
    //函数以及调用都暴露在严格模式中的例子:
    "use strict";
    var name = '听风是风';
    function fn() {
        console.log(this); //undefined
        console.log(this.name);//TypeError: Cannot read property 'name' of undefined
    };
    fn();
    
    //在严格模式下调用不在严格模式中的函数,并不会影响this指向
    var name = '听风是风';
    function fn() {
        console.log(this); //window
        console.log(this.name); //听风是风
    };
    (function () {
        "use strict";
        fn();
    }());
    

    2. this隐式绑定(对象中的this、原型链中的this)

    如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上

    • this指向调用本函数的对象
    function fn() {
        console.log(this.name); //听风是风
    };
    let obj = {
        name: '听风是风',
        func: fn
    };
    obj.func()  //此时this指向obj,所以输出“听风是风”
    
    //如果函数调用前存在多个对象,this指向距离调用自己最近的对象,比如这样:
    function fn() {
        console.log(this.name); //行星飞行
    };
    let obj = {
        name: '行星飞行',
        func: fn,
    };
    let obj1 = {
        name: '听风是风',
        o: obj
    };
    obj1.o.func()  //此时this指向obj,所以输出“行星飞行”
    
    //那如果我们将obj对象的name属性注释掉
    function fn() {
        console.log(this.name); //undefined
    };
    let obj = {
        func: fn,
    };
    let obj1 = {
        name: '听风是风',
        o: obj
    };
    obj1.o.func() //此时this指向obj,由于obj对象没有name属性,所以输出undefined
    
    //以下代码输出什么?
    function Fn() {};
    Fn.prototype.name = '时间跳跃';
    function fn() {
        console.log(this.name);
    };
    let obj = new Fn();
    obj.func = fn;
    let obj1 = {
        name: '听风是风',
        o: obj
    };
    obj1.o.func() //时间跳跃
    //此时this指向obj,虽然obj对象并没有name属性,但顺着原型链,找到了产生自己的构造函数Fn,由于Fn原型链存在name属性,所以输出“时间跳跃”
    
    隐式丢失
    var name = '行星飞行';
    let obj = {
        name: '听风是风',
        fn: function () {
            console.log(this.name); ;//行星飞行
        }
    };
    
    function fn1(param) {
        param();
    };
    fn1(obj.fn)
    //这里我们将 obj.fn 这个函数传递进 fn1 中执行,这里只是单纯传递了一个函数而已,所以this指向了window,相当于函数直接调用。
    
    var name = '行星飞行';
    let obj = {
        name: '听风是风',
        fn: function () {
            console.log(this.name); //行星飞行
        }
    };
    let fn1 = obj.fn;
    fn1(); //这里也是把obj.fn这个函数赋给了fn1,直接调用这个函数,所以this指向了window
    
    var name = '行星飞行';
    let obj = {
        name: '听风是风',
        fn: function () {
            console.log(this.name); //时间跳跃
        }
    };
    let obj1 = {
        name: '时间跳跃'
    }
    obj1.fn = obj.fn;
    obj1.fn(); //这里把obj.fn这个函数赋给了obj1.fn,执行后this指向obj1,所以输出“时间跳跃”
    

    3. this显式绑定(call、apply、bind)

    显式绑定是指我们通过call、apply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程

    • 正常情况:this指向绑定的对象
    • 参数为null或者undefined:this指向window
    let obj1 = {
        name: '听风是风'
    };
    let obj2 = {
        name: '时间跳跃'
    };
    let obj3 = {
        name: 'echo'
    }
    var name = '行星飞行';
    
    function fn() {
        console.log(this.name);
    };
    fn(); //行星飞行   this指向window
    fn.call(obj1); //听风是风   this指向obj1
    fn.apply(obj2); //时间跳跃   this指向obj2
    fn.bind(obj3)(); //echo   this指向obj3
    
    //如果在使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象
    let obj1 = {
        name: '听风是风'
    };
    let obj2 = {
        name: '时间跳跃'
    };
    var name = '行星飞行';
    
    function fn() {
        console.log(this.name);
    };
    fn.call(undefined); //行星飞行   this指向window
    fn.apply(null); //行星飞行   this指向window
    fn.bind(undefined)(); //行星飞行   this指向window
    

    在js API中部分方法也内置了显式绑定,以forEach为例:

    let obj = {
        name: '听风是风'
    };
    [1, 2, 3].forEach(function () {
        console.log(this.name);//听风是风*3
    }, obj);
    

    4. new绑定

    • this指向被创建的新对象
    function C(){
      this.a = 37;
    }
    
    var o = new C();
    console.log(o.a); // logs 37
    
    
    function C2(){
      this.a = 37;
      return {a:38}; //当构造器返回的值是一个对象时,实例化化的this指向这个对象
    }
    
    var b = new C2();
    console.log(b.a); // 38
    

    this绑定优先级

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

    为什么显式绑定不和new绑定比较呢?因为不存在这种绑定同时生效的情景,如果同时写这两种代码会直接抛错

    function Fn(){
        this.name = '听风是风';
    };
    let obj = {
        name:'行星飞行'
    }
    let echo = new Fn().call(obj);//报错 call is not a function
    

    显式>隐式

    //显式>隐式
    let obj = {
        name:'行星飞行',
        fn:function () {
            console.log(this.name);
        }
    };
    obj1 = {
        name:'时间跳跃'
    };
    obj.fn.call(obj1);// 时间跳跃
    

    new绑定>隐式

    obj = {
        name: '时间跳跃',
        fn: function () {
            console.log(this.name)
        }
    };
    let echo = new obj.fn(); //undefined
    

    三. 箭头函数的this

    箭头函数中的this不适用上面介绍的四种绑定规则
    准确来说,箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁

    function fn() {
        return () => {
            console.log(this.name);
        };
    }
    let obj1 = {
        name: '听风是风'
    };
    let obj2 = {
        name: '时间跳跃'
    };
    let bar = fn.call(obj1);
    bar(); //因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
    bar.call(obj2); //因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
    

    为啥我们第一次绑定this并返回箭头函数后,再次改变this指向没生效呢?
    前面说了,箭头函数的this取决于外层作用域的this,fn函数执行时this指向了obj1,所以箭头函数的this也指向obj1。除此之外,箭头函数this还有一个特性,那就是一旦箭头函数的this绑定成功,也无法被再次修改,有点硬绑定的意思。

    当然,箭头函数的this也不是真的无法修改,我们知道箭头函数的this就像作用域继承一样从上层作用域找,因此我们可以修改外层函数this指向达到间接修改箭头函数this的目的。

    function fn() {
        return () => {
            console.log(this.name);
        };
    };
    let obj1 = {
        name: '听风是风'
    };
    let obj2 = {
        name: '时间跳跃'
    };
    fn.call(obj1)(); // 因为外层函数的this指向obj1,所以箭头函数this也指向obj1 输出“听风是风”
    fn.call(obj2)(); // 因为外层函数的this指向obj2,所以箭头函数this也指向obj2 输出“时间跳跃”
    

    this真题演练

    真题一

    var name = 'window'
    
    var obj1 = {
        name: '听风是风',
        fn1: function () {
            console.log(this.name)
        },
        fn2: () => console.log(this.name),
        fn3: function () {
            return function () {
                console.log(this.name)
            }
        },
        fn4: function () {
            return () => console.log(this.name)
        }
    }
    var obj2 = {
        name: '行星飞行'
    };
    
    obj1.fn1();//听风是风  
    obj1.fn1.call(obj2);//行星飞行
    
    obj1.fn2();//window
    obj1.fn2.call(obj2);//window
    
    obj1.fn3()();//window
    obj1.fn3().call(obj2);//行星飞行
    obj1.fn3.call(obj2)();//window
    
    obj1.fn4()();//听风是风
    obj1.fn4().call(obj2);// 听风是风
    obj1.fn4.call(obj2)();// 行星飞行
    

    第一个输出听风是风,fn1调用前有一个obj1,this为隐式绑定指向obj1,因此读取到obj1的name属性
    第二个输出行星飞行,显式绑定优先级高于隐式绑定,所以此时的this指向obj2,读取了obj2的name属性
    第三个输出window,箭头函数并没有自己的this,它的this指向由上层执行上下文中的this决定,那为什么上层执行上下文是window呢?

    • JavaScript中的上下文分为全局执行上下文,函数执行上下文,而不管是全局上下文或函数上下文的创建都会确认this指向。也就是说,this属于上下文中的一部分,很明显对象obj1并不是一个函数,它并没有权利创建自己的上下文,所以没有自己的this,那么它的外层是谁呢?当然是全局window啦,所以这里的this指向window

    第四个输出window,箭头函数的this由外部环境决定,且一旦绑定无法通过call,apply或者bind再次改变箭头函数的this,所以这里虽然使用了call方法但依旧无法修改,所以this还是指向window。
    第五个输出window,obj1.fn3()()其实可以改写成这样:

    var fn = obj1.fn3();
    fn();
    //先执行了fn3方法,返回了一个闭包fn,而fn执行时本质上等同于window.fn(),属于this默认绑定,所以this指向全局对象。
    

    第六个输出行星飞行,同样是先执行fn3返回一个闭包,但闭包执行时使用了call方法修改了this,此时指向obj2,这行代码等同于:

    var fn = obj1.fn3();
    fn.call(obj2);//显式绑定
    

    第七个输出window,obj1.fn3.call(obj2)()修改一下其实是这样,fn被调用时本质上还是被window调用:

    var fn = obj1.fn3.call(obj2);
    window.fn();//默认绑定
    

    第八个输出听风是风,fn4同样是返回一个闭包,只是这个闭包是一个箭头函数,所以箭头函数的this参考fn4的this即可,很明显此次调用fn4的this指向obj1。
    第九个输出听风是风,改写代码其实是这样,显式绑定依旧无法改变箭头函数this

    var fn = obj1.fn4();
    fn.call(obj2);//显式绑定依旧无法改变this
    

    第十个输出行星飞行,虽然无法直接改变箭头函数的this,但可以通过修改上层上下文的this达到间接修改箭头函数this的目的:

    var fn = obj1.fn4.call(obj2);//fn4的this此时指向obj2
    window.fn();//隐式绑定无法改变箭头函数this,this与fn4一样
    

    真题二

    /*非严格模式*/
    var name = 'window'
    
    function Person(name) {
      this.name = name;
      this.fn1 = function () {
        console.log(this.name);
      };
      this.fn2 = () => console.log(this.name);
      this.fn3 = function () {
        return function () {
          console.log(this.name)
        };
      };
      this.fn4 = function () {
        return () => console.log(this.name);
      };
    };
    
    var obj1 = new Person('听风是风');
    var obj2 = new Person('行星飞行');
    
    obj1.fn1(); //听风是风
    obj1.fn1.call(obj2); //行星飞行
    
    obj1.fn2(); //听风是风
    obj1.fn2.call(obj2); //听风是风
    
    obj1.fn3()(); //window
    obj1.fn3().call(obj2); //行星飞行
    obj1.fn3.call(obj2)(); //window
    
    obj1.fn4()(); //听风是风
    obj1.fn4().call(obj2); //听风是风
    obj1.fn4.call(obj2)(); //行星飞行
    

    第一个输出听风是风,与第一题一样,这里同样是隐式绑定,this指向new出来的对象obj1
    第二个输出行星飞行,显式绑定,this指向obj2
    第三个你是不是觉得是window,很遗憾,这里的箭头函数指向了obj1,输出听风是风。
    第四个输出听风是风,我们改写代码其实是这样

    var arrowFn = obj1.fn2;//箭头函数this指向obj1
    arrowFn.call(obj2);//箭头函数this无法直接改变
    

    第五个输出window,与题一相同,返回闭包本质上被window调用,this被修改。
    第六个输出行星飞行,返回闭包后利用call方法显式绑定指向obj2。
    第七个输出window,返回闭包还是被window调用
    第八个输出听风是风,返回闭包是箭头函数,this同样会指向obj1,虽然返回后也是window调用,但箭头函数无法被直接修改,还是指向obj1。
    第九个输出听风是风,箭头函数无法被直接修改
    第十个输出行星飞行,箭头函数可通过修改外层作用域this指向从而达到间接修改的目的

    相关文章

      网友评论

          本文标题:this指向总结2

          本文链接:https://www.haomeiwen.com/subject/mjxdvhtx.html