说说你对this的理解?
问题解析
this 是 JavaScript 中最令人困惑的概念之一。与其他编程语言不同,JavaScript 的 this 不是在编写时绑定的,而是在运行时根据函数的调用方式动态确定的。理解 this 的绑定规则对于编写正确的 JavaScript 代码至关重要。
核心概念
this 是什么?
this 是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。它的值是在函数被调用时确定的,而不是在函数定义时。
// this 的值取决于函数的调用方式,而不是定义方式
function showThis() {
console.log(this);
}
showThis(); // window(浏览器)或 global(Node.js)
const obj = {
show: showThis
};
obj.show(); // obj
showThis.call({a: 1}); // {a: 1}
showThis.apply([1, 2]); // [1, 2]
详细解答
一、this 的绑定规则
1. 默认绑定(Default Binding)
function foo() {
console.log(this);
}
foo(); // window(非严格模式)
// 严格模式下
function strictFoo() {
'use strict';
console.log(this); // undefined
}
strictFoo();
// 注意:严格模式只在函数内部生效
function bar() {
console.log(this); // window(非严格模式)
}
(function() {
'use strict';
bar(); // window!bar 的定义不在严格模式下
})();
2. 隐式绑定(Implicit Binding)
// 作为对象方法调用
const person = {
name: 'Alice',
sayName: function() {
console.log(this.name);
}
};
person.sayName(); // "Alice"(this 指向 person)
// 只有最后一层影响 this
const anotherPerson = {
name: 'Bob',
friend: person
};
anotherPerson.friend.sayName(); // "Alice"(this 指向 person)
// 隐式丢失
const sayName = person.sayName;
sayName(); // undefined(this 指向全局对象,严格模式下)
// 回调函数中的隐式丢失
function doSomething(callback) {
callback();
}
doSomething(person.sayName); // undefined
3. 显式绑定(Explicit Binding)
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const person = { name: 'Alice' };
// call - 参数列表
greet.call(person, 'Hello'); // "Hello, Alice"
// apply - 参数数组
greet.apply(person, ['Hi']); // "Hi, Alice"
// bind - 返回新函数
const greetAlice = greet.bind(person);
greetAlice('Hey'); // "Hey, Alice"
// bind 的柯里化
const greetHello = greet.bind(person, 'Hello');
greetHello(); // "Hello, Alice"
// 硬绑定的优先级高于隐式绑定
const obj = {
name: 'Bob',
greet: greet.bind(person)
};
obj.greet('Hi'); // "Hi, Alice"(bind 优先)
4. new 绑定(Constructor Binding)
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
const alice = new Person('Alice');
alice.sayName(); // "Alice"
// new 做了什么:
// 1. 创建一个空对象
// 2. 将对象的 __proto__ 指向 Person.prototype
// 3. 将 Person 中的 this 指向这个新对象
// 4. 执行构造函数代码
// 5. 返回这个新对象(除非构造函数返回了一个对象)
// 特殊情况:构造函数返回对象
function SpecialPerson(name) {
this.name = name;
return { name: 'Override' }; // 返回对象会覆盖默认返回
}
const special = new SpecialPerson('Alice');
console.log(special.name); // "Override"
// 返回非对象则忽略
function NormalPerson(name) {
this.name = name;
return 123; // 被忽略
}
const normal = new NormalPerson('Alice');
console.log(normal.name); // "Alice"
二、箭头函数中的 this
// 箭头函数没有自己的 this,继承外层作用域的 this
const obj = {
name: 'Alice',
regularFunction: function() {
console.log(this.name); // "Alice"
setTimeout(function() {
console.log(this.name); // undefined(this 指向全局对象)
}, 100);
setTimeout(() => {
console.log(this.name); // "Alice"(继承外层 this)
}, 100);
},
arrowFunction: () => {
console.log(this.name); // undefined(继承定义时的 this)
}
};
obj.regularFunction();
obj.arrowFunction();
// 箭头函数不能通过 call/apply/bind 改变 this
const arrow = () => console.log(this);
arrow.call({a: 1}); // window(忽略显式绑定)
// 箭头函数不能作为构造函数
const Arrow = () => {};
new Arrow(); // TypeError: Arrow is not a constructor
三、this 绑定优先级
// 优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
// 1. new 绑定 vs 显式绑定
function foo(something) {
this.a = something;
}
const obj1 = {};
const bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
const baz = new bar(3);
console.log(obj1.a); // 2(new 改变了 this 绑定)
console.log(baz.a); // 3
// 2. 显式绑定 vs 隐式绑定
function sayName() {
console.log(this.name);
}
const obj2 = {
name: 'obj2',
say: sayName
};
const obj3 = {
name: 'obj3'
};
obj2.say(); // "obj2"(隐式绑定)
obj2.say.call(obj3); // "obj3"(显式绑定优先)
// 3. 隐式绑定 vs 默认绑定
function test() {
console.log(this);
}
const obj4 = {
test: test
};
test(); // window(默认绑定)
obj4.test(); // obj4(隐式绑定优先)
深入理解
1. 常见 this 指向问题
// 问题1:对象方法中的嵌套函数
const obj = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
showFriends: function() {
this.friends.forEach(function(friend) {
// 这里的 this 指向全局对象,不是 obj
console.log(`${this.name} is friends with ${friend}`);
});
},
showFriendsFixed: function() {
const self = this; // 保存 this 引用
this.friends.forEach(function(friend) {
console.log(`${self.name} is friends with ${friend}`);
});
},
showFriendsArrow: function() {
this.friends.forEach(friend => {
// 箭头函数继承外层 this
console.log(`${this.name} is friends with ${friend}`);
});
}
};
// 问题2:DOM 事件处理函数
const button = document.getElementById('btn');
button.addEventListener('click', function() {
console.log(this); // button 元素
});
button.addEventListener('click', () => {
console.log(this); // 外层 this(通常是 window)
});
// 问题3:类中的方法
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
// 使用箭头函数确保 this 绑定
sayNameArrow = () => {
console.log(this.name);
}
}
const person = new Person('Alice');
person.sayName(); // "Alice"
const sayName = person.sayName;
sayName(); // undefined(this 丢失)
const sayNameArrow = person.sayNameArrow;
sayNameArrow(); // "Alice"(this 被绑定)
2. this 与原型链
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.sayBreed = function() {
console.log(this.breed);
};
const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayName(); // "Buddy"(this 指向 dog)
dog.sayBreed(); // "Golden Retriever"(this 指向 dog)
最佳实践
1. 使用箭头函数避免 this 问题
const obj = {
name: 'Alice',
delayedLog: function() {
// ✅ 使用箭头函数
setTimeout(() => {
console.log(this.name); // "Alice"
}, 100);
}
};
2. 使用 bind 固定 this
class Component {
constructor() {
this.name = 'Component';
// ✅ 在构造函数中绑定 this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.name);
}
}
3. 使用类字段(Class Fields)语法
class Component {
name = 'Component';
// ✅ 箭头函数作为类字段,自动绑定 this
handleClick = () => {
console.log(this.name);
}
}
4. 明确 this 的类型(TypeScript)
interface Person {
name: string;
greet(this: Person, greeting: string): void;
}
const person: Person = {
name: 'Alice',
greet(greeting: string) {
console.log(`${greeting}, ${this.name}`);
}
};
面试要点
-
this 的绑定规则有哪些?
- 默认绑定:独立函数调用,this 指向全局对象(严格模式 undefined)
- 隐式绑定:作为对象方法调用,this 指向该对象
- 显式绑定:call/apply/bind,this 指向指定的对象
- new 绑定:构造函数调用,this 指向新创建的实例
-
箭头函数的 this 有什么特点?
- 没有自己的 this,继承外层作用域的 this
- 不能通过 call/apply/bind 改变 this
- 不能作为构造函数使用
-
this 绑定的优先级?
- new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
-
如何解决回调函数中的 this 丢失问题?
- 使用箭头函数
- 使用 bind 绑定 this
- 使用变量保存 this(self/that)
- 使用类字段语法
-
setTimeout 中的 this 指向?
- 普通函数:指向全局对象(或严格模式 undefined)
- 箭头函数:继承外层 this
-
addEventListener 回调中的 this 指向?
- 普通函数:指向触发事件的 DOM 元素
- 箭头函数:继承外层 this