前言
在 JavaScript 中,this
是一个非常重要且有些让人困惑的概念。了解 this
的绑定规则是编写高质量代码的关键。在本文中,我们将深入探讨 this
的五种绑定规则
正文
我们先要知道this的作用是什么?this
在 JavaScript 中的作用是用来引用当前函数执行的上下文,也就是当前函数被调用时所处的环境。通过使用 this
,我们可以在函数内部访问和操作当前对象的属性和方法,以及引用当前函数被调用时的上下文信息。
在某种程度上,this
可以简化代码,因为它使得我们可以更灵活地使用函数,而不需要显式地传递参数。通过正确地理解 this
的绑定规则,我们可以避免在代码中频繁地传递对象或参数,从而简化代码逻辑并提高代码的可读性和可维护性。
总的来说,this
的作用是帮助我们更方便地操作当前对象的属性和方法,简化代码逻辑,以及提高代码的可读性和可维护性。
1.this的默认绑定
我们首先需要知道,this是不能引用词法作用域里的东西的,来看下面这段代码:
function bar() {
var b = 3
console.log(this.b);
}
bar()
这段代码执行结果是undefined,在这段代码中,函数 bar
内部定义了一个局部变量 b
,然后尝试通过 this.b
来访问一个属性 b
。由于 b
并没有作为对象的属性被定义,因此 this.b
会返回 undefined
。
当函数 bar()
被调用时,它的执行上下文中的 this
默认会指向全局对象(在浏览器环境中通常是 window
对象)。由于全局对象并没有一个名为 b
的属性,因此 this.b
的值为 undefined
。
好,然后把这段代码变一下,变成下面这样:
var b = 3
function bar() {
console.log(this.b);
}
bar()
这段代码在浏览器里执行的结果就是3,这里的this是指向window的,所以可以得到默认绑定规则—函数在哪个词法作用域里生效,this就指向哪里。 但有些地方会说函数在哪里调用,this就指向哪里。这句话不全对,这里再看一个demo:
function foo() {
var b = 1
bar()
}
function bar() {
console.log(this.b);
}
foo()
这里按照“函数在哪里调用,this就指向哪里”这句话来说执行结果应该是1,但实际执行结果是undefined,这是因为函数虽然有作用域但没有词法作用域,函数天生就有作用域,且作用域是一个可见的属性[[scope]]
2.this的隐式绑定
隐式绑定—当函数被一个对象所拥有,再调用时,此时this会指向该对象。 直接看代码:
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
obj.foo()
当调用 obj.foo()
时,函数 foo
被对象 obj
所拥有,因此在函数执行时,this
会隐式绑定到对象 obj
上。这意味着在函数 foo
中的 this.a
将会指向对象 obj
中的属性 a
,即 2
。
因此,执行 obj.foo()
的结果会是输出 2
。这就是所谓的隐式绑定规则:当函数被一个对象所拥有,再调用时,this
会指向该对象,从而可以访问该对象的属性和方法。
3.隐式丢失
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
var obj2 = {
a: 3,
obj: obj,
}
obj2.obj.foo()
想想这段代码的执行结果是2还是3,结果是2,当执行 obj2.obj.foo()
时,函数 foo
虽然被对象 obj
所拥有,但是实际上是通过 obj2
对象的属性 obj
进行链式调用的。在链式调用中,this
指向的是最后一层调用的对象,即 obj
对象。
因此,虽然 foo
函数本来是被对象 obj
所拥有的,但由于通过 obj2.obj
进行了链式调用,最终 this
指向的是对象 obj
,因此 this.a
会访问对象 obj
中的属性 a
,即输出 2
。
这种情况被称为隐式绑定的隐式丢失,因为虽然函数最初是被一个对象所拥有的,但由于链式调用的方式,this
最终指向了引用函数的对象,而不是最初拥有函数的对象。
这就是隐式绑定的隐式丢失—当函数被多个对象链式调用时,this指向引用函数的对象。
4.this的显示绑定
显式绑定—使用call、apply、bind方法,绑定this
function foo(n){
console.log(this.a, n);
}
var obj = {
a: 2
}
foo.call(obj, 100)
通过调用 foo.call(obj, 100)
,使用了显式绑定的方式来调用函数 foo
。在这里,call
方法是 JavaScript 中用于改变函数执行上下文的方法,第一个参数是要绑定到函数中的 this
值,这里是对象 obj
。第二个参数及之后的参数会传递给函数 foo
。
因此,在调用 foo.call(obj, 100)
时,函数 foo
的执行上下文中的 this
被显式绑定到了对象 obj
,所以 this.a
将会指向对象 obj
中的属性 a
,即 2
,同时参数 n
为 100
。因此,执行 foo.call(obj, 100)
的结果会是输出 2 100
。这就是显式绑定的方法,通过 call
、apply
或 bind
来明确指定函数执行时的 this
值。
function foo(n, m){
console.log(this.a, n, m);
}
var obj = {
a: 2
}
// foo.call(obj, 100, 200)
// foo.apply(obj, [100, 200]) apply和call不一样的就是要用数组
var bar = foo.bind(obj) // bind调用完成后会返回一个函数体,所以后面要bar调用.var bar = foo.bind(obj, 100, 200)这样调用参数也行
bar(100, 200) // 语法问题,接受就行了
通过使用显式绑定的方式,可以通过 call
、apply
或 bind
方法来指定函数执行时的 this
值为对象 obj
。
- 使用
foo.call(obj, 100, 200)
:通过call
方法将函数foo
的执行上下文中的this
绑定到对象obj
,并传递参数100
和200
给函数foo
。这样执行foo.call(obj, 100, 200)
的结果会是输出2 100 200
。 - 使用
foo.apply(obj, [100, 200])
:与call
类似,apply
方法也可以将函数foo
的执行上下文中的this
绑定到对象obj
,不同之处在于参数的传递方式,apply
接受一个包含参数的数组。这样执行foo.apply(obj, [100, 200])
的结果也会是输出2 100 200
。 - 使用
var bar = foo.bind(obj)
:bind
方法会创建一个新的函数,该函数的this
值永久绑定到指定的对象obj
。在这里,通过var bar = foo.bind(obj)
创建了一个新的函数bar
,然后调用bar(100, 200)
时,函数bar
的执行上下文中的this
被绑定到了对象obj
,同时传递了参数100
和200
给函数foo
。这样执行bar(100, 200)
的结果也会是输出2 100 200
。
5.new绑定
new是做了一件什么事情?第一创建this对象,第二执行函数体逻辑,往this上放属性,第三将this的隐式原型赋给构造函数的显示原型,第四return。
当使用 new
关键字来调用一个函数时,会创建一个新的对象,并将这个新对象作为函数的执行上下文,即将函数内部的 this
指向这个新对象。这种方式被称为 new 绑定。
例如,假设有一个构造函数 Person
,定义如下:
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
}
}
然后通过 new
关键字来创建一个新的实例对象:
var person1 = new Person("Alice", 30);
person1.greet(); // 输出:Hello, my name is Alice and I am 30 years old.
在这个例子中,通过 new Person("Alice", 30)
创建了一个新的 Person
实例对象 person1
。在构造函数 Person
中,this
被绑定到了新创建的实例对象 person1
,因此在 greet
方法中使用 this.name
和 this.age
访问实例对象的属性。
箭头函数
在 JavaScript 中,箭头函数是一种特殊的函数形式,它没有自己的 this 绑定。当使用箭头函数时,箭头函数会捕获在定义时所处的上下文的 this 值,并且不会被任何方式所改变。这意味着箭头函数内部的 this 始终指向箭头函数定义时所处的作用域的 this 值,而不是调用箭头函数时的上下文。这种特性使得箭头函数在处理 this 绑定时更加简洁和可靠。
举个例子,假设我们有一个对象 obj,其中包含一个方法 foo,方法内部使用箭头函数来定义一个定时器:
var obj = {
a: 2,
foo: function() {
setTimeout(() => {
console.log(this.a);
}, 100);
}
};
obj.foo();
在这个例子中,箭头函数内部的 this 指向的是 obj 对象,而不是 setTimeout 函数的调用者。因此,箭头函数绑定可以帮助我们避免在定时器或回调函数中出现 this 指向混乱的情况,简化了代码的编写和理解。箭头函数的绑定方式在某些情况下非常有用,特别是在需要保持一致的 this 指向时。
总结
所以,在 JavaScript 中,this 是一个非常重要且常见的概念,它用于指代当前执行上下文中的对象。在编写 JavaScript 代码时,正确理解和处理 this 的指向是至关重要的。在实际开发中,我们经常会遇到不同的 this 绑定情况,例如隐式绑定、显式绑定、默认绑定、new 绑定以及箭头函数绑定。每种绑定方式都有自己的特点和用途,了解它们可以帮助我们更好地控制代码的行为,避免出现意外的错误。通过合适地选择和应用不同的绑定方式,我们可以确保代码的逻辑正确性和可维护性,从而提升开发效率和代码质量。因此,在编写 JavaScript 代码时,务必注意对 this 的正确理解和使用,以确保代码的稳定性和可靠性。
这就是文章的全部内容了,觉得有帮助的请关注一下叭。
原文链接:https://juejin.cn/post/7335226083615391794 作者:moyu84