序言
在 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 函数被调用时,它能够访问到对象 obj 的 name 属性,并正确地输出 '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 值,而不会被上述规则所影响。
到这里我们今天的文章就结束了,喜欢的小伙伴的可以点赞+关注,作者后续会持续更新类似干货文章。