this

作者: _daraDu | 来源:发表于2020-09-25 15:01 被阅读0次

    一、使用this的原因(why)

    对于前端开发者来说,this关键字是JavaScript中最复杂的机制之一,不同的位置使用,指向是不一样的,所以它到底有什么作用呢?为什么要花大量的时间去研究他呢

    • this提供了一种优雅的方式来隐式的“传递”一个对象的引用,可以方便我们简洁的设计API。特别是当结构越来越复杂时。
    function GetObj (name) {
        this.name = name
    }
    GetObj.prototype.showName = function (){
    alert(this.name)  // this指代GetObj这个对象
    }
    let p1 = new GetObj('张三')
    p1.showName()
    

    二、了解this(what----this到底是什么)

    1、对this的误解
    • 认为this指向函数自身
    function foo(num) {
       console.log('foo===', num)  // foo: 6 // foo: 7 // foo: 8 // foo: 9 
      // 记录 foo 被调用的次数 
      this.count++
    }
    foo.count = 0
    for(var i = 0; i< 10; i++){
       if (i > 5) {         
          foo( i );    
       } 
    }
     console.log( foo.count ); // 0 
    

    this并没有指向自身的foo函数,而是指向了window。

    • 认为this的作用域指向函数的作用域
      在某种情况下它 是正确的,但是在其他情况下它却是错误的
    function foo(){ 
        var a = 2; 
        this.bar(); 
    } 
    function bar() { 
        console.log(this); //window
        console.log(this.a); // undefined
    } 
    foo();
    

    这个a作用域存在于foo函数中,所以在window这个作用域中找不到。但是由于var定义的a,在词法分析阶段会将a进行变量提升,所以window中会有一个a,但是没有值

    2、与自然语言的对比

    实际上js中的this和我们自然语言中的代词有类似性。比如英语中我们写"小明,你吃了么?"

    注意上面的代词"小明",我们当然可以这样写:"小明,小明吃了么?" ,这种情况下我们没有使用this去重用代替小明。
    主要和执行时候的上下文环境有关联

    3、那this到底是什么呢?

    this只跟函数的调用位置有关,是在函数被调用时发生绑定的;this的指向取决于函数在哪里被调用。

    this指向的最终对象,跟调用位置以及应用的绑定规则有关

    三、绑定规则(how--如何寻找函数的调用位置)

    1、 默认绑定
    • 最常见的绑定规则,独立函数调用时默认绑定,也可以看做无法应用其他绑定规则时的默认规则
    function foo() {      
        console.log( this.a ); // this指向window
    } 
    var a = 2; 
     
    foo(); // 2
    
    • this指向
      • 在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。
      • 普通函数是在全局环境中被调用:
        非严格模式下:指向全局对象window
        严格模式下:指向undefined(使用严格模式的时候,全局对象无法使用默认绑定)
    function foo() {      
      "use strict";      
      console.log( this );  // undefined
      console.log( this.a );  // Uncaught TypeError: Cannot read property 'a' of undefined
    } 
     "use strict";  
    console.log( this ) // window
    var a = 2; 
    foo(); 
    
    2、 隐式绑定
    • 当函数的调用存在上下文对象,或者说是否被某个对象拥有或者包含。this将会被绑定到这个上下文对象。
      this 始终会指向直接调用函数的上一级对象
    function foo() {      
        console.log( this.a );   // 指向obj
    } 
    var obj = {      
        a: 2,     
        foo: foo  //--->foo: function () {      console.log( this.a );  } 
    }; 
    obj.foo(); // 2
    
    • 函数嵌套,每个function函数(非箭头函数)在每次调用时都会在函数内生成一个自己的this。
      当两个函数嵌套定义时,内层函数中的this与外层函数中的this是完全独立的。函数内this的值是在函数调用时才确定的,函数的调用方式不同,this也就不同
      1,当函数直接调用时 fn(); 函数内this的值是window对象,(在js严格模式"use strict";下,函数内this的值是null)
      2,当把这个函数赋值给一个对象的方法obj.abc = fn;
      调用obj.abc()时,函数内this的值是obj对象,也就是函数所在的对象。
    var obj = {
        y: function() {
            console.log(this === obj);   // true
            console.log(this);   // Object {y: function}
            fn();  // 嵌套的函数不是对象的方法,直接调用,所以this指向window
    
            function fn() {
                console.log(this === obj);   // false
                console.log(this);   // Window 全局对象
            }
        }
    }
    
    obj.y();  
    
    • 问题:被隐式绑定的函数会丢失绑定对象,会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
      1、 函数引用传递给新的全局变量
    function foo() {      
        console.log( this.a ); 
    } 
    var obj = {      
        a: 2,     
        foo: foo  
    }; 
    var bar = obj.foo; // bar是全局变量
    var a = "oops, global"; // a 是全局对象的属性 
    bar(); // "oops, global",bar() 其实是一个不带任何修饰的函数调用
    

    2、将函数作为参数传入回调函数中

    function foo() {      
        console.log( this.a ); 
    } 
    function doFoo(fn) {     // fn 其实引用的是 foo 
        fn(); // <-- 调用位置! 
    } 
    var obj = {     
        a: 2,     
        foo: foo  
    }; 
    var a = "oops, global"; // a 是全局对象的属性 
    doFoo( obj.foo ); // "oops, global"
    
    3、显式绑定
    使用 call()和apply() 方法进行强制绑定
    • 他们的第一个参数都是指定函数运行时的this指向,第一个参数不传(参数为空)。或者为null、undefined。默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。
    var x = 1;
    
    var obj = {
      x: 2
    }
    
    function fn() {
        console.log(this);
        console.log(this.x);
    }
    
    fn.call(obj)
    // Object {x: 2}
    // 2
    fn.apply(obj)     
    // Object {x: 2}
    // 2
    
    fn.call()         
    // Window 全局对象
    // 1
    
    fn.apply(null)    
    // Window 全局对象
    // 1
    
    fn.call(undefined)    
    // Window 全局对象
    // 1
    
    • 使用 call() 和 apply() 传入的this对象为原始值(字符串类型,布尔类型或者数字类型),这个原始值就会被转换成它的对象形式(new String()、new Bollean()、new Number())
    • 区别
      call()的第二个以及后续的参数是一个列表
      apply()的第二个参数是一个数组,所有的参数放在这个数组中
      参数列表和参数数组都将作为函数的参数进行执行
    var x = 1
    var obj = {
        x: 2
    }
    
    function sum (y, z) {
        console.log(this.x + y +z)
    }
    sum(3, 4) // 8
    sum.call(obj, 3, 4) // 9
    sum.apply(obj, [3, 4]) // 9
    
    使用 bind() 方法进行强制绑定

    调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,新函数的 this 会永久的指向 bind 传入的第一个参数,无论这个函数是如何被调用的。

    var x = 1
    var obj1 = {
        x: 2
    }
    var obj2 = {
        x: 3
    }
    
    function fn () {
        console.log(this)
        console.log(this.x)
    }
    var a = fn.bind(obj1)
    var b = a.bind(obj2)
    
    fn() // window 1
    
    a() // {x: 2} 2
    
    b() // {x: 2} 2 
    
    a.call(obj2) // {x: 2} 2
    

    在上面的例子中,虽然我们尝试给函数 a 重新指定 this 的指向,但是它依旧指向第一次 bind 传入的对象,即使是使用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。

    4、new绑定

    使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象

    var x = 1;
    
    function Fn() {
       this.x = 2;
        console.log(this);  // Fn {x: 2}
    }
    
    var obj = new Fn();   // obj和Fn(..)调用中的this进行绑定
    console.log(obj.x)   // 2
    

    绑定规则优先级

    如果某个调用位置可以应用多条规则该怎么办?为了 解决这个问题就必须给这些规则设定优先级。
    毫无疑问,默认绑定的优先级别最低。
    隐式绑定和显示绑定谁的优先级别更高呢?

    function fn(){
        console.log(this.a)
    }
    let obj1 = {
        a: 1,
        fn: fn
    }
    let obj2 = {
        a: 2,
        fn: fn
    }
    obj1.fn()//1
    obj2.fn()//2
    obj1.fn.call(obj2)//2
    obj2.fn.apply(obj1)//1
    
    • 可以看到,显式绑定优先级更高

    隐式绑定和new()绑定谁的优先级别更高呢?

    function foo(something) {      
        this.a = something; 
    } 
    var obj1 = {      
        foo: foo 
    }; 
    var obj2 = {}; 
    obj1.foo( 2 );  
    console.log( obj1.a ); // 2 
    
    obj1.foo.call( obj2, 3 );  
    console.log( obj2.a ); // 3 
     
    var bar = new obj1.foo( 4 );  
    console.log( obj1.a ); // 2  
    console.log( bar.a ); // 4
    
    • 可以看到 new 绑定比隐式绑定优先级高。

    但是 new 绑定和显式绑定谁的优先级更高呢

    function foo(something) {      
        this.a = something; 
    } 
    
    var obj1 = {}; 
    
    var bar = foo.bind( obj1 );  
    bar( 2 ); 
    console.log( obj1.a ); // 2 
    
    var baz = new bar(3);  
    console.log( obj1.a ); // 2  
    console.log( baz.a ); // 3
    

    bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。

    当某个函数调用应用了这四种规则中的多条,那么优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

    5、箭头函数中this指向

    箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
    箭头函数没有自己的this绑定。箭头函数中使用的this,其实是直接包含它的那个函数或函数表达式中的this

    var a = 1;
    let obj = {
        a: 2,
        fn1: () => {
            console.log('fn1',this.a) // 1 this指向window
            let fn3 = () => {
                console.log('fn3',this.a) // 1 this指向window
            }
            fn3()
            
            let fn4 = () => {
                console.log('fn4',this.a) // 1 this指向window
            }
            fn4.call(obj)
            // 由于箭头函数没有自己的this指针,通过 call() 或apply() 方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略。
            fn4.apply(obj)
        },
        fn2: function() {
            console.log('fn2',this.a) // 2 this指向obj
        }
    }
    obj.fn1()
    obj.fn2()
    

    箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。- 从父作用域继承this
    同 bind 一样,箭头函数也很“顽固”,无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略。

    图片转自掘金小册-前端面试之道

    image.png

    总结

    如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后 就可以顺序应用下面这四条规则来判断 this 的绑定对象。

    1. 由 new 调用?绑定到新创建的对象。
    2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
    3. 由上下文对象调用?绑定到那个上下文对象。
    4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
      一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑 定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null),以保护全局对象。
      ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。

    https://www.cnblogs.com/kidsitcn/p/10985338.html
    https://zhuanlan.zhihu.com/p/71490991
    https://zhuanlan.zhihu.com/p/28536635
    嵌套函数指向

    相关文章

      网友评论

          本文标题:this

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