前言
在 JavaScript 中,this 是一个关键字,用于指代当前函数执行的上下文或对象。具体来说,this 可以用来引用当前执行环境中的对象,例如函数、方法、构造函数等。
this 的值在运行时动态确定,取决于调用函数的方式和上下文环境。在不同的情况下,this 可能指向全局对象(浏览器中为 window 对象),也可能指向函数所属的对象,或者是通过 call、apply 或 bind 方法显式绑定的对象,还有可能是通过 new 操作符创建的新对象等。
了解 this 的使用规则对于编写优雅的 JavaScript 代码至关重要。理解 this 的行为有助于避免一些常见的错误,同时也能帮助开发人员更好地控制函数内部的上下文,提高代码的可读性和可维护性。
初步认识this
javascript
let obj = {
name: '小明',
say: function() {
console.log(this.name);
}
}
obj.say() // 输出:小明
在这段代码中,say 函数内部使用了 this 关键字来引用当前对象。当 obj.say() 被调用时,this 在 say 函数的上下文中指向 obj 对象。
这是因为 this.name 解析为调用 say 方法的对象 obj 的 name 属性。在JavaScript中,当一个函数作为对象的方法被调用时,this 通常指向那个对象。所以在这个例子中,this 指向 obj,this.name 就是 obj.name。
根据上面的例子,我相信你对this有一个大概的了解。
默认绑定原则
什么是默认绑定原则呢?
javascript
function foo() {
var a = 1
console.log(this.a); // 输出:undefined
}
foo()
在JavaScript中,函数的 this 值取决于函数是如何被调用的。对于普通函数调用,this 通常指向全局对象。在浏览器环境中,全局对象是 window。
在上面的代码中,foo 是作为一个普通函数被调用的(不是作为某个对象的方法或通过 new 关键字构造的实例)。在这种情况下,由于 a 是在 foo 函数内部声明的局部变量,它不是 window 或 global 对象的属性,所以 this.a 将会是 undefined。
要使上面代码输出 1 ,则可以:
javascript
var a = 1
function foo() {
console.log(this.a); 输出:1
}
foo()
在这个例子中,变量a被定义为全局变量,其值为1。函数foo()内部的this指向的是全局对象。 当在foo()函数中调用console.log(this.a)时,它会访问全局作用域中的变量a,因为this指向的是全局对象。因此,输出结果将是1。
根据上面例子我们再进行拓展:
scss
var a = 1;
function foo() {
console.log(this.a);
}
function bar() {
var a = 2;
foo();
}
bar()
在这个例子中,foo()函数在bar()函数的作用域内被调用。然而,由于foo()函数是独立的函数,并没有绑定到任何对象上,因此它的this指向的是全局对象。
因此,当在foo()函数中打印this.a时,它会访问全局作用域中的变量a,而不是bar()函数的作用域中的变量a。在全局作用域中,变量a的值为1,所以输出结果是1。
即使foo()函数在bar()函数内部被调用,但这并不影响foo()函数的this指向。foo()函数的this值是在函数被调用时确定的,而不是在定义函数时确定的。
再看下面的代码中,this会指向谁:
scss
function foo() {
function bar() {
console.log(this);
}
function baz () {
bar()
}
baz()
}
foo()
在这个例子中,`foo()`函数内部定义了两个函数:`bar()`和`baz()`。在`baz()`函数中调用了`bar()`函数。因为`bar()`函数是在全局作用域中定义的,所以它的执行上下文中的`this`指向全局对象(在浏览器环境中通常是`window`对象)。
因此,当在bar()函数中调用console.log(this)时,它会输出全局对象。
总结:函数在哪个词法作用域生效,this就指向哪里,而独立调用的函数this都指向window。
隐式绑定原则
JavaScript 中的隐式绑定原则是指当函数作为对象的方法被调用时,函数内部的 this 指向该对象。具体来说,当函数通过对象来调用时,函数内部的 this 指向该对象。这种绑定方式称为隐式绑定。
css
function foo() {
console.log(this.a);
}
var obj = {
a: 1 ,
foo: foo
}
var obj2 = {
a: 2,
obj: obj
}
obj2.obj.foo()
在这个例子中,函数foo()被定义为一个函数表达式,并且可以通过不同的对象进行调用。在最后一行代码中,我们看到了一个嵌套的对象引用,它会影响 this 的指向。
首先,让我们逐步分析代码:
- 定义函数
foo(),该函数内部使用this来访问变量a。 - 创建名为
obj的对象,其中包含属性a和一个指向foo()函数的方法。 - 创建名为
obj2的对象,其中包含属性a和另一个对象obj作为属性。 - 在
obj2对象上调用obj.foo(),这将触发foo()函数的执行。
根据隐式绑定规则,当函数作为对象的方法被调用时,函数内部的 this 指向调用该方法的对象。
因此,在obj2.obj.foo()调用中,函数内部的 this 指向obj对象,因为调用链中最后一个对象是obj。所以输出结果将是1,因为obj对象的a属性值为1。
因此,最终的输出结果将是1。
显示绑定
示绑定是通过call()、apply()或bind()方法来直接指定函数中的 this 指向的过程,它可以被用来覆盖隐式绑定规则。
call ( )
call() 是 JavaScript 中的一个方法,它用于在指定的 this 值和参数列表下调用函数。使用 call() 方法,我们可以显式地设置函数内的 this 值,并将函数作为对象的方法调用。
call() 方法接受两个参数,第一个参数是要设置为函数上下文的对象,第二个参数及其后的参数是传递给函数的参数列表。
以下是 call() 方法的语法:
scss
functionName.call(thisValue, arg1, arg2, ...)
functionName:要调用的函数名称。thisValue:要绑定到函数内部的 this 值,如果不传入,则默认为全局对象。arg1, arg2, ...:要传递给函数的参数列表。
话不多说,我们进入实战:
javascript
function foo() {
console.log(this.a);
}
var obj = {
a: 1
}
foo()
前面我们已经得知,这里会输出undefined,那我们如何让这段代码输出1呢?
javascript
function foo() {
console.log(this.a);输出:1
}
var obj = {
a: 1
}
foo.call(obj)
在这个例子中,我们定义了一个名为 foo 的函数和一个名为 obj 的对象。接着,我们使用 call 方法来显式地调用 foo 函数,并将 obj 对象作为 this 值传递给它。
通过调用 foo.call(obj),我们改变了 foo 函数的执行上下文,使得函数内部的 this 指向了 obj 对象。这意味着在函数内部,通过 this.a 可以访问到 obj 对象的属性 a。
因此,当 foo 函数被调用时,它会打印出 obj.a 的值,即输出结果为 1。
再看一个例子:
javascript
function foo(x,y) {
console.log(this.a, x + y);
}
var obj = {
a: 1
}
foo.call(obj,4,5)
通过调用 foo.call(obj, 4, 5),我们改变了 foo 函数的执行上下文,使得函数内部的 this 指向了 obj 对象。在函数内部,this.a 可以访问到 obj 对象的属性 a。 此外,我们还传递了参数 4 和 5 给 foo 函数,它们会被作为 x 和 y 的值传递进来。
因此,当 foo 函数被调用时,它会打印出 obj.a 的值(即 1)和 x + y 的和(即 9)。
apply ()
apply() 是 JavaScript 中的一个方法,它与 call() 方法类似,用于在指定的 this 值和参数数组下调用函数。使用 apply() 方法,我们可以显式地设置函数内的 this 值,并将函数作为对象的方法调用。
apply() 方法接受两个参数,第一个参数是要设置为函数上下文的对象,第二个参数是一个参数数组,这些参数将作为数组元素传递给函数。
以下是 apply() 方法的语法:
arduino
functionName.apply(thisValue, [arg1, arg2, ...])
functionName:要调用的函数名称。thisValue:要设置为函数上下文的对象。如果不需要设置上下文,可以传入null或undefined。[arg1, arg2, ...]:作为参数数组传递给函数的参数列表。
以下是一个使用 apply() 方法的例子:
javascript
function foo(x,y) {
console.log(this.a, x + y);
}
var obj = {
a: 1
}
foo.apply(obj,[4,5])
这段代码定义了一个名为 foo 的函数,它接受两个参数 x 和 y,并在控制台中输出 this.a 和 x + y 的值。然后创建了一个名为 obj 的对象,并将其赋值为 {a: 1}。
接下来,使用 apply() 方法调用 foo 函数,并将 obj 对象作为上下文,将参数数组 [4, 5] 传递给函数。因此,this.a 将被设置为 obj.a,也就是 1,而 x + y 的值为 9。因此,控制台将输出 1 9。
总之,使用 apply() 方法可以显式地设置函数内的 this 值,并将函数作为对象的方法调用。同时,它还可以接受一个参数数组来传递函数的参数。
bind()
bind() 是 JavaScript 中的一个方法,它用于创建一个新函数 ,并将指定的对象作为新函数的上下文(this 值)。与 call() 和 apply() 方法不同,bind() 方法不会立即调用函数,而是返回一个新函数,可以在稍后的时间点调用。
bind() 方法接受一个参数,即要设置为新函数上下文的对象。
以下是 bind() 方法的语法:
bash
javascript复制代码
functionName.bind(thisValue)
functionName:要绑定上下文的函数名称。thisValue:要设置为函数上下文的对象。
以下是一个使用 bind() 方法的例子:
javascript
function foo(x,y) {
console.log(this.a, x + y);
}
var obj = {
a: 1
}
let bar = foo.bind(obj,4,5)
bar()
使用 bind() 方法将 obj 对象作为 foo 函数的上下文,并绑定参数 4 和 5,返回一个新函数 bar。此时,bar 函数已经绑定了上下文和部分参数。
接着,调用 bar(),没有传入任何参数。由于 bar 函数已经绑定了上下文 obj 和部分参数 4 和 5,所以在控制台中输出的是 1 9,其中 this.a 的值为 1,x + y 的值为 9。
综上所述,通过使用 bind() 方法,我们可以创建一个新函数,并绑定指定的上下文和参数。这个新函数可以稍后被调用,保持绑定的上下文和参数不变。如果在调用新函数时不传入参数,那么绑定的参数将会被使用。
箭头函数
箭头函数是ES6引入的一种新的函数语法格式。它使用箭头(=>)来定义函数,相比传统的函数声明或函数表达式,有一些特殊的行为和用法。
下面是箭头函数的表示:
ini
var functionName = () => {};
以下是一个示例:
ini
var obj = {
a: 1
}
var foo = () => {
console.log(this.a);
}
foo.call(obj)
在这段代码中,使用箭头函数 foo 并尝试通过 call 方法来设置 this 指向对象 obj。然而,需要注意的是,箭头函数无法通过 call 方法来改变其 this 值,因为箭头函数的 this 值是由外层作用域决定的。
因此,当执行 foo.call(obj) 时,在浏览器环境下,外层作用域的 this 值通常是全局对象 window,所以代码会尝试打印 window.a 的值。
全局对象中没有 a 这个属性,那么输出结果将是 undefined。
再看下面代码:
javascript
var obj = {
a: 1
}
function foo() {
var bar = () => {
console.log(this);
}
bar ()
}
foo.call(obj)
在执行 foo 函数时,它会定义并调用 bar 函数。由于 bar 是一个箭头函数,它会继承外层作用域(即 foo 函数)的 this 值。因此,当 bar 函数被调用时,它的 this 值就是 foo 函数的 this 值,也就是对象 obj。
最终执行 console.log(this) 时,将会打印对象 obj 的信息,即 {a: 1}。
箭头函数没有自己的 this 绑定机制,它会继承外层普通函数的 this 值。 具体来说,箭头函数中的 this 是在定义函数时确定的,而不是在运行时确定的。