likes
comments
collection
share

探索 this 的指向之谜

作者站长头像
站长
· 阅读数 19

前言

在 JavaScript 的广阔天地里,this 关键字如同一把双刃剑,既能让你的代码流畅自如,也能因误用而让人一头雾水。本文将带领你穿越 this 的迷雾,深入探讨 this 的几种绑定规则、优先级以及箭头函数中的 this 行为,并通过代码示例进行解释说明帮助读者更好了解 (v^_^)v

在JavaScript中,this关键字是一个非常重要的概念,它代表了函数执行时的上下文对象,使用this可以让对象中的函数有能力访问对象自己的属性。当我们要传递的参数越来越复杂时,显示传递上下文对象会让代码更混乱,这时使用this关键字来隐式传递对象引用可以显著提升代码质量,减少上下文参数的传递,让代码更好维护。例如下面的代码中我们用this关键字就可以用更优雅的方式来隐式传参:

/*// 显示传参
function identify(context) {
     return context.name.toUpperCase()   // toUpperCase()方法:将小写转成大写字符串
 }
 function speak(context) {
     var greeting = 'Hello I am ' + identify(context)
     console.log(greeting);
 }
*/
// 用 this 隐式传参
function identify() {
    return this.name.toUpperCase()  
}
function speak() {
    var greeting = 'Hello I am ' + identify(this)
    console.log(greeting);
}

var me = {
    name: 'hhh'
}
speak(me)   // Hello I am HHH

this 的绑定规则

1. 默认绑定

当一个函数独立调用,不带任何修饰符时(即不是作为对象的方法被调用),this指向全局对象(在浏览器环境中是window,在严格模式下是undefined

下面代码如果在非严格模式下(即浏览器环境下)运行的得到的结果是 2。因为foo是独立调用的,函数中的this指向全局,在浏览器环境下,在全局通过var声明的变量相当于在 window 对象上添加了一个属性,所以this.a 相当于 window.a 也就是 2。

但是如果在严格模式下运行(例如node.js)得到的是undefined,因为node的全局是global空对象,全局声明的a并不会挂在上面,所以this.a相对于global.a,也就是undefined。

function foo() {
    console.log(this.a);    // this指向全局作用域,相当于打印window.a = 1   2
}
var a = 2   
foo() 

2. 隐式绑定

当函数的引用有上下文对象时(即当函数作为某个对象的属性被调用时),this指向引用它的对象。

下列代码中foo在第7行没有独立调用,this指向foo的引用对象obj,所以this.a相对于obj.a,也就是1。

var obj = {  
    a: 1,  
    foo: function() {  
        console.log(this.a); // 1,this指向obj  
    }  
};  
obj.foo();

3. 隐式丢失

当一个函数被多个对象链式调用时,函数的this指向就近的那个对象。

下列代码中foo被obj引用,obj又被obj2引用,所以第13行是一个链式调用。遇到这种情况时我们不用管函数的对象在哪个词法作用域中,我们只要看该函数是被谁调用了,this遵循就近原则。据此我们知道this还是指向obj的,因此this.a相对于obj.a,也就是1。

var obj = {
    a:1,
    foo: foo    // foo的引用
}
var obj2 = {
    a:2,
    obj: obj
}
function foo() {
    console.log(this.a); // 1
}

obj2.obj.foo() 

4. 显示绑定

通过callapplybind方法,我们可以显式地设置函数调用的this值。

  • call:call 方法调用一个函数,其具有一个指定的 this 值和参数的列表。例如在下面代码中可以执行 foo.call(obj, 2, 3)语句来让foo中的this指向obj。

  • apply:apply 方法调用一个函数,其具有一个指定的 this 值,以及一个数组提供的参数。例如在下面代码中可以执行 foo.apply(obj, [2, 3])语句来让foo中的this指向obj。

  • bind:bind 方法创建一个新的函数,在 bind 被调用时,这个新函数的 this 被指定为 bind 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

var obj = {
    a: 1
}
function foo(x, y) {
    console.log(this.a, x + y);    
}

// 使用bind创建一个新函数bar,其this指向obj,并预置了第一个参数2  
var bar = foo.bind(obj, 2);  
// 调用bar,传入剩余的参数3  
bar(3); // 输出: 1 5   
  
// 如果在bind时直接传入所有参数,则调用时不需要再传入任何参数  
var baz = foo.bind(obj, 2, 3);  
baz(); // 输出: 1 5  

5. new 绑定

使用new关键字创建实例对象时,this 指向新创建的实例对象。

为了让大家更好了解new绑定,我将以下面代码为例来讲解一下 new 创建实例对象的过程。

1.创建一个新的空对象 var obj = {};  (对象名obj只是指代一下)
2.将这个新对象的内部 [[Prototype]] 链接到构造函数的 prototype 属性  
3. 将构造函数中的 this 指向这个新对象obj
4. 执行构造函数中的代码,此时 this 指向新对象  
5. 如果构造函数返回一个对象,则返回该对象;否则,返回步骤 1 中创建的新对象。
function Person() {  
    this.name = 'Tom';  
}  
var p = new Person();  
console.log(p.name); // Tom

this绑定的优先级

当多种绑定规则同时作用时,this的绑定优先级为:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定(严格模式下绑定到undefined,否则绑定到全局对象)。

箭头函数中的 this

箭头函数不绑定自己的 this,它会捕获其所在上下文的 this 值作为自己的 this 值,这意味着在箭头函数中,this 的值由外层(非箭头)函数或全局对象决定。

下列代码中,���头函数 fn 在 obj 的 b 方法内部定义,因此它捕获了 b 方法执行时的 this 值,即 obj ,所以 this.a 相对于 obj.a,也就是1。

var obj = {
    a:1,
    b: function() {
        const fn = () => {
            console.log(this.a);  // 1
        }
        fn()
    }
}
obj.b()

结语

希望这篇文章能够帮助你更好地理解this的指向,感谢观看!`・ω・´)ゞ敬礼っ

转载自:https://juejin.cn/post/7398503284075200549
评论
请登录