JavaScriptwhat-about-this

10次阅读

共计 2383 个字符,预计需要花费 6 分钟才能阅读完成。

About “this”

P1:如何使用 this?

  • this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

这话说起来容易,理解起来缺常常出现问题。请看如下几段代码

var a =1;
var obj = {
    a = 0,
    say: function(){ console.log(this.a) }
}
var fun = obj.say;
obj.say(); // 0
fun();     // 1

为什么会出现在这种情况?
明明我的 fun 和 obj.say 指向了同一个函数,而且都在 window 对象中调用,为什么出现了不同的结果?

本质上是这样的:

 obj.say()  等价于 obj.say.call(obj) // 你不写.call(obj),浏览器自动帮你写
 fun() 等价于 fun.call(window) 
  1. 把 obj 当作 this 传入了 say 函数中,自然输出的是 obj 内的 a
  2. 在 window 对象中执行 fun(), 就相当于把 window 当作 this 传入了该函数中,输出的就是 window 里的 a!
  • 更加普遍化的,this 其实时时刻刻都伴随着我们的代码

    • 你调用 fun(), 实际上是执行了 fun.call(undefined)
    • 你调用 obj.fun(), 实际上执行了 obj.fun.call(undefined)

如果你传的是 null 或者 undefined,那么默认将 window 传入(严格模式下默认是 undefined)

至此我们基本讲述了 this 的用法

P2:若脱离了 call 方法,我们如何判断 this 指向?

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

    1. 由 new 调用,则绑定到新创建的对象。
    2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
    3. 由上下文对象调用?绑定到那个上下文对象。

      function say(){ console.log(this.name) }
      let person = {name:'Jam', age: 18, say: say}
      person.say() // 'Jam'
    4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。

P3:箭头函数中的 this

  • 箭头函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象,箭头函数中没有自己的 this 的,而箭头函数会默认使用父级的 this。

我不知道其他人怎样理解,但如此的定义确实让我难以理解。何为定义时所在的对象?如何使用父级的 this?接下来我用实例带你理解。

let obj = {prin: function(){
        // 父级作用域 此处保存着父级的 this
        setTimeout(()=>{console.log(this.num);
        },1000)
    }
}
let obj1 = {num:1};
let obj2 = {num:2};
  1. 首先我们直接执行 prin 方法试一下

    obj.prin(); // undefined

    分析
    obj.prin() 等价于 obj.prin.call(obj),
    我们将 obj 作为 this,传到 prin 函数中此时箭头函数向上查找父级作用域中保存的 this,
    顺理成章的找到了 obj,于是去 obj 的环境内部寻找 num
    遗憾的是 obj 内部并没有 num 属性,故打印 undefined

  2. 尝试改变父级中 this 的指向

    obj.prin.call(obj1); // 1
    obj.prin.call(obj2); // 2

    分析
    父级中的 this 被赋值为 obj1,箭头函数顺着 obj1 寻找,找到了 num 属性,打印了它的值
    同理父级中的 this 被赋值为 obj2,箭头函数顺着 obj2 寻找,找到了 num 属性,打印了它的值

  3. 那我们不如直接给 obj 一个 num 属性试试?

    obj.num = 0;
    obj.prin(); // 0

    分析
    当 obj 中拥有了属性 num,那么箭头函数一找就找到了它!

  4. 我们再来最后一个例子加深理解!当我把 prin 函数也用箭头函数书写呢?

    var num = 10;
    //“祖级作用域”window
    var obj = {
        num = 5;
        prin: ()=>{
            // 父级作用域 此处保存着父级的 this
            setTimeout(()=>{console.log(this.num);
            },1000)
        }
    }

    你猜猜会输出什么呢?
    ·
    ·
    ·
    答案是 10!

    分析
    setTimeout 中的箭头函数到父级作用域中寻找 this,发现父级作用域也是箭头函数,于是再向外查 找,找到了“祖级作用域”window,恰好 window 有 num 属性,于是 setTimeout 中的箭头函数快乐地把 window.num 打印了出来!

P4:其他一些总结

  1. 使用 let,const 声明的变量可能会不符合上述结论。
    原因是:let,const 在全局作用域下声明的变量并不会像 var 一样,被添加为 window 的属性,所以即使在全局中声明了 let number = 1, 箭头函数等等也不会在全局作用域中找到 number!
  2. 在配置对象属性的 getter,setter 时,一定要使用 this 的格式,不然就会出现一些奇怪的现象。举个例子:

    let obj = {get number():{return this._number_;}
        set number(tmp){this._number_ = tmp*2;}
    
    }
    obj.number = 1;    //set 赋值
    console.log(obj.number); // 2
    console.log(number) // undefined

    上方是正确的例子。但我为什么要加最后一行呢?因为如果你没有使用 this 而是直接return _number_, _number = tmp*2,那么 number 函数在找不到_number_变量的情况下就会强行在 window 对象上挂载一个_number_,污染了全局作用域。

  3. 待补充

本文借鉴了 @方应杭 老师的思想 https://zhuanlan.zhihu.com/p/…

正文完
 0