前言
当谈论 JavaScript 中的 this
关键字时,你可能会发现这个概念相当令人困惑。在不同的上下文中,this
的含义会发生变化,这使得它成为 JavaScript 开发人员经常会遇到的难题之一。了解 this
在不同情境下的行为是编写高效、可维护代码的关键所在。本文将深入探讨 this
在 JavaScript 中的多重身份,从全局范围到函数调用,再到对象方法和构造函数,我们将一一解析 this
在各种情境下的行为,帮助大家更好地理解和应用这个关键概念。
正文
- 为什么要有this
为了让对象中的函数有能力访问对象自己的属性
- 我们来看下面这段代码
let obj = {
myname:'涛哥',
age:18,
bar: function(){
console.log(myname);
}
}
obj.bar()
在执行这段代码时我们会发现并没有打印出 涛哥 而是报错了,这是为什么呢?
这时我们在
console.log(myname);
改为console.log(this。myname);
我们通过this来访问myname,终于也成功打印出来了
- this的妙用:可以显著提升代码质量,减少上下文参数的传递
function identify(){
return this.name.toUpperCase()
}
function speak(){
var greeting = 'Hello i am' + identify.call(this)
console.log(greeting);
}
var me = {
name:'xiaoxiao',
}
speak.call(me)
在这段代码中我们在 speak()
函数中,我们创建了一个变量 greeting
,其值为 'Hello i am'
与 identify.call(this)
的结果的拼接。identify.call(this)
的调用意味着我们将 identify()
函数中的 this
绑定到了 speak()
函数中的 this
。这种方式称为显式绑定,通过 call()
方法我们可以手动指定函数内 this
的指向。
- 然后,我们在
me
对象上调用了speak()
函数,这样就将speak()
函数中的this
绑定到了me
对象上。因此,identify.call(this)
中的this
将指向me
对象,于是identify()
函数返回的是me
对象的name
属性的大写形式。 - 最后,
console.log(greeting)
将会打印出'Hello i am XIAOXIAO'
,其中'XIAOXIAO'
是me
对象的name
属性的大写形式。
我们可以在
speak()
函数内部控制identify()
函数中this
的指向。这种灵活性使得我们能够在不同的上下文中重复利用函数,并且可以动态地改变函数内部this
的指向。
this的绑定规则
- [ ] 1.默认绑定: 当一个函数独立调用,不带任何修饰符的时候函数在哪个词法作用域下生效,函数中的this就指向哪里 --(只要是默认绑定,this一定指向window)*
function foo(){
console.log(this);
}
foo();
这段代码打印出来了window:
var obj = {
a: 1,
foo: function(){
console.log(this);//obj
}
}
obj.foo()
而这段代码打印出了obj里的对象:
- 当函数的引用有下文对象时(当函数被某个对象所拥有时)函数的this指向引它的对象
const obj = {
name: 'John',
greet: function() {
console.log('Hello, ' + this.name);
}
};
obj.greet(); // Output: Hello, John
在这个例子中,greet
函数作为 obj
对象的方法被调用。在 greet
函数内部,this
关键字指向调用它的对象,也就是 obj
对象,因此 this.name
将输出 John
。
- 隐式丢失:当一个函数被多个对象链式调用时,函数的this指向就近的那个对象
const obj1 = {
name: 'John',
greet: function() {
console.log('Hello, ' + this.name);
}
};
const obj2 = {
name: 'Alice'
};
obj1.greet.call(obj2); // Output: Hello, Alice
在这个例子中,greet
函数本来是作为 obj1
对象的方法,但是通过 call
方法将 greet
函数从 obj1
对象中提取出来,并在 obj2
对象上调用。在这种情况下,this
关键字在 greet
函数内部指向了 obj2
对象,而不是 obj1
对象,因此输出的结果是 Hello, Alice
,而不是 Hello, John
。
这里发生了隐式丢失,因为原本 greet
函数的期望是在 obj1
上下文中被调用,但是在调用时上下文变成了 obj2
。这种情况下,需要特别注意函数被调用的上下文,以避免隐式丢失问题。
显示绑定:call apply bind
- call:
call
方法调用一个函数,其第一个参数是要设置为函数执行上下文的对象。之后的参数是传递给函数的参数列表
function greet(name) {
console.log(`Hello, ${name}! My name is ${this.name}.`);
}
const person = {
name: 'Alice'
};
greet.call(person, 'Bob');
这会打印出:Hello, Bob! My name is Alice.
。在这个例子中,call
方法设置了 greet
函数的执行上下文为 person
对象,并传递了一个参数 'Bob'
给 greet
函数。
apply: apply
方法和 call
方法类似,不同之处在于它的第二个参数是一个数组,数组中的元素会被作为参数传递给函数。
function greet(name, age) {
console.log(`Hello, ${name}! I am ${age} years old. My name is ${this.name}.`);
}
const person = {
name: 'Alice'
};
greet.apply(person, ['Bob', 30]);
这会打印出:Hello, Bob! I am 30 years old. My name is Alice.
。apply
方法设置了 greet
函数的执行上下文为 person
对象,并传递了两个参数 'Bob'
和 30
给 greet
函数。
bind: bind
方法创建一个新的函数,并将指定的对象绑定为该函数的执行上下文。与 call
和 apply
不同,bind
不会立即执行函数,而是返回一个新的函数,你可以在之后调用该函数。
function greet(name) {
console.log(`Hello, ${name}! My name is ${this.name}.`);
}
const person = {
name: 'Alice'
};
const greetPerson = greet.bind(person);
greetPerson('Bob');
这会打印出:Hello, Bob! My name is Alice.
。bind
方法创建了一个新的函数 greetPerson
,并将 person
对象绑定为该函数的执行上下文。之后调用 greetPerson
函数时,person
对象会成为 greet
函数的 this
值。
总的来说,这三个方法都允许你在调用函数时显式设置函数内部的
this
值,并且可以传递参数给函数。call
和apply
在设置函数上下文和传递参数方面非常相似,而bind
则创建了一个新的函数,并将指定的对象绑定为该函数的执行上下文。
- new 绑定: this指向创建的实例对象
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Alice', 30);
console.log(person1.name); // 输出:Alice
console.log(person1.age); // 输出:30
new
绑定是一种创建新对象的方式,它会将构造函数的 this
绑定到新创建的实例对象上。当你使用 new
关键字来调用一个函数时,JavaScript 会执行以下步骤:
- 创建一个空的普通 JavaScript 对象(即空对象
{}
)。 - 将这个空对象的原型链接到构造函数的原型对象上。
- 执行构造函数,并将这个空对象绑定到构造函数内部的
this
上。 - 如果构造函数没有显式返回一个对象,则返回这个新创建的对象。
总结
总的来说,this
的值在 JavaScript 中是动态的,并且取决于函数的调用方式。理解 this
的行为对于正确地使用 JavaScript 函数和面向对象编程非常重要。