【JS】深入探讨 this 的绑定规则

219 阅读7分钟

序言

在 JavaScript 中,this 关键字是一个非常重要和特殊的关键字,它代表当前函数的执行上下文,也就是函数被调用时所处的环境。this的指向是动态确定的,根据函数的调用方式不同而有所不同。this 的绑定规则是开发过程中常常会遇到的一个重要问题。本文将深入讨论 JavaScript 中 this 的五种绑定规则,并介绍 ES6 新增的箭头函数对于 this 的独特处理方式。

this 的五种绑定规则

1. 默认绑定

当函数在全局作用域中被调用时,this 会指向全局对象(浏览器中是 window 对象)。这意味着函数在哪个词法作用域里生效,this 就指向哪里。

console.log(this);

这段代码如果在JS中用Node输出结果是是一个空对象 {},因为this在这里指向的是全局;如果在浏览器中执行这段代码打印这段代码打印的是Window,因为Window代表浏览器的环境

var a = 1
console.log(window.a); //window代表浏览器环境

那么我们将这段代码放到浏览器中去执行可以拿到吗?答案是可以的,输出的结果是1。函数的默认绑定有些地方说函数在哪里调用this就指向谁,但是这句话存在很多诟病,应该是在默认绑定情况下,函数在哪个词法作用域生效this就指向哪,让我们通过下面这个例子来理解this的默认绑定指向:

var b = 2
function foo() { 
    var b = 1
    bar()
}

function bar() {
    baz()
}
function baz(){
    console.log(this.b);
}
foo()

我们先调用foo函数,然后再调用bar函数,最后再调用baz函数打印this.b,那么这个this指向哪里呢?

baz函数在bar函数中被调用是不是就指向bar函数呢,错因为this指向的是一个词法作用域,但是bar是一个函数只有他自己的作用域,而没有词法作用域baz函数的词法作用域全局,所以this.b最后打印的是 2

2. 隐式绑定

当函数被一个对象所拥有,再调用时,此时this会指向该对象

function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
}

var obj2 = {
    a:3,
    obj:obj
}
obj2.obj.foo()

我们通过obj2.obj.foo()调用foo函数。在这个例子中,foo 函数是作为 obj 对象拥有的一个属性进行调用的,因此函数内部的 this 将指向调用它的对象,也就是 obj 对象,这也被称为就近原则,所以最终输出的结果就是 2

3. 隐式丢失

当函数被多个对象链式调用时,this指向引用函数的对象

我们上面的例子就发生了隐式丢失,让我们继续通过这个例子来理解这个概念

function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
}

var obj2 = {
    a:3,
    obj:obj
}
obj2.obj.foo()

我们是通过对象obj2来调用它里面的对象obj,再通过对象obj调用函数foo,有没有思考过,是obj2在调用foo函数,还是obj在调用foo函数?这里我们this的指向就相当于一种就近原则foo函数被对象obj引用,于是this就指向对象obj,于是就造成了对象obj2调用的丢失,这就是this发生隐式绑定隐式丢失

4. 显示绑定

我们还可以通过显示绑定来控制this的指向,这么做可以确保函数中的 this 指向我们所期望的对象,而不受到默认绑定规则的影响。

JavaScript提供了三种显示绑定的方法:call、apply 或者 bind 方法

1. call方法

call 方法可以立即执行函数,并且允许我们将一个对象绑定到函数体内的 this,并传入参数列表。

function greet() {
    console.log(this.name);
}

var person = { name: 'Alice' };
greet.call(person); // 输出:Alice

2. apply方法

apply 方法与 call 类似,但是它接受一个包含多个参数的数组作为参数。

function multiply(a, b) {
    return this.value * a * b;
}

var context = { value: 10 };
var result = multiply.apply(context, [2, 3]); // 传入参数数组 [2, 3]
console.log(result); // 输出:60

3. bind方法

bind 方法会创建一个新的函数,称为绑定函数,它会将原始函数绑定到指定的对象上。绑定函数的 this 始终指向传入的对象,无论它如何被调用。

function foo(n,m){
    console.log(this.a,n,m);
}

var obj = {
    a:2
}

var bar = foo.bind(obj,100,200) //声明一个变量承载新创建的函数
bar() //调用函数 输出 2 100 200

5. new绑定

当使用 new 关键字调用构造函数时,会创建一个新的对象,并将这个新对象绑定到 this 上。

// 定义一个构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 使用 new 关键字创建新对象并绑定构造函数
var person1 = new Person('Alice', 25);
var person2 = new Person('Bob', 30);

console.log(person1.name); // 输出:'Alice'
console.log(person2.age); // 输出:30

箭头函数 (ES6新增函数)

箭头函数是ES6引入的一种新的函数定义方式,它提供了一种更简洁的语法形式,并且在某些情况下可以改变函数内部的this绑定。

与普通函数不同,箭头函数没有this这个概念,写在箭头函数中的this也是它外层函数的this,并且无法通过 call、apply、bind 方法来改变 this 的指向。

下面是一些箭头函数的示例:

var a = 1
var baz  = () => {
    console.log(this.a);
}
baz()

在这段代码中,箭头函数 baz 被定义在全局作用域下,并且尝试打印 this.a 的值。

由于箭头函数的特性,它的 this 始终指向其定义时所处的上下文(词法作用域),而不是运行时的上下文(函数作用域)。在浏览器中,全局上下文中的 this 指向全局对象(通常是 window 对象),因此在这个例子中,this.a 实际上会尝试访问全局对象的 a 属性。

因为全局范围内确实有一个名为 a 的变量被定义并赋值为 1,所以在这种情况下,当调用 baz() 时,会打印出 1

总之,由于箭头函数的特性,它不会改变 this 的指向,因此在全局范围内使用箭头函数时,它会继承全局上下文中的this值,而且可以正常访问全局变量

var obj = {
    name : 'TOM',
    show: function(){
        var bar = () => {
            console.log(this.name);
        }
        bar()
    }
}
obj.show()

在这段代码中,对象 obj 包含一个名为 name 的属性和一个名为 show 的方法。show 方法内部定义了一个箭头函数 bar,用于输出 this.name 的值。在 show 方法被调用时,bar 函数也被调用。

由于箭头函数 bar 被定义在 show 方法内部,它继承了 show 方法的执行上下文,因此箭头函数的 this 始终指向其定义时的外层作用域,也就是对象 obj。因此,当 bar 函数被调用时,它能够访问到对象 objname 属性,并正确地输出 'TOM'

因此,当你调用 obj.show() 时,会打印出 'TOM'

总结

下面是我们今天内容的笔记:

# this的绑定规则
1. 默认绑定 --- 函数在哪个词法作用域里生效,this就指向哪里

2. 隐式绑定 --- 当函数被一个对象所拥有,再调用时,此时this会指向该对象

3. 隐式丢失 --- 当函数被多个对象链式调用时,this指向引用函数的对象

4. 显示绑定 --- call,apply,bind

5. new绑定 ---  当使用 new 关键字调用构造函数时,会创建一个新的对象,并将这个新对象绑定到 this 上。

# 箭头函数 (ES6新增函数)
箭头函数没有this这个概念,写在箭头函数中的this也是它外层函数的this

总之。JavaScript中的 this 可以根据执行上下文不同而有不同的指向,需要根据具体的使用场景来理解和确定它的值。对于箭头函数来说,它会继承外层作用域的 this 值,而不会被上述规则所影响。

到这里我们今天的文章就结束了,喜欢的小伙伴的可以点赞+关注,作者后续会持续更新类似干货文章。