1. 调用位置:this
的起点是函数如何被调用
关键原则 :this
的值由 函数调用时的上下文 决定,而非函数定义的位置。找到函数的 调用位置(Call-site) 是理解 this
的第一步。
javascript
function sayHello() {
console.log("Hello, " + this.name);
}
const user = {
name: "Lucy",
greet: sayHello
};
user.greet(); // 调用位置在 user.greet(),this 指向 user → "Hello, Lucy"
sayHello(); // 直接调用,非严格模式下 this 指向全局对象(浏览器中是 window)→ "Hello, undefined"
2. 四大绑定规则:决定 this
的四大天王
(1) 默认绑定:没有修饰的独立调用
- 非严格模式下,
this
指向全局对象(浏览器中是window
)。 - 严格模式下(
'use strict'
),this
为undefined
。
javascript
function showThis() {
console.log(this);
}
showThis(); // 浏览器中输出 window(非严格模式)或 undefined(严格模式)
(2) 隐式绑定:通过对象调用方法
this
指向调用该方法的对象。
javascript
const cat = {
name: "Mimi",
meow: function() {
console.log(this.name + " says meow!");
}
};
cat.meow(); // "Mimi says meow!" → this 指向 cat
因为调用函数时 this 被绑定到 cat ,所以 this.name 等于 cat.name。
隐式丢失陷阱 :
将方法赋值给变量后调用,this
会丢失绑定!
javascript
const meowLater = cat.meow;
meowLater(); // "undefined says meow!" → this 指向全局对象
这里就等于直接引用函数本身,那就是应用了默认绑定,this 就指向全局作用域了。
(3) 显式绑定:用 call
/apply
/bind
强制指定 this
- 直接控制
this
的指向。
javascript
function introduce(lang) {
console.log(`I speak ${lang}. My name is ${this.name}`);
}
const person = { name: "Bob" };
// call 立即调用
introduce.call(person, "Chinese"); // "I speak Chinese. My name is Bob"
// bind 返回绑定后的函数
const boundIntro = introduce.bind(person, "English");
boundIntro(); // "I speak English. My name is Bob"
精确控制 this 指向
显式绑定是通过 call()
、apply()
和 bind()
方法强制指定函数调用时的 this
值。
1. call() 方法
call()
立即调用函数,并显式设置 this
值:
javascript
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: 'Alice' };
// 第一个参数设置 this,后续参数作为函数参数
greet.call(person, 'Hello', '!'); // 输出: "Hello, Alice!"
2. apply() 方法
apply()
与 call()
类似,但接受参数数组:
javascript
const args = ['Hi', '!!'];
greet.apply(person, args); // 输出: "Hi, Alice!!"
3. bind() 方法(硬绑定)
bind()
创建一个新函数,永久绑定 this
值:
javascript
const boundGreet = greet.bind(person);
boundGreet('Hey', '...'); // 输出: "Hey, Alice..."
硬绑定的本质与特性
硬绑定是指通过 bind()
方法创建一个新函数,该函数的 this
值被永久绑定到指定对象,无法通过其他方式修改。
关键特点:
-
永久性绑定:一旦绑定就无法更改
javascriptconst boundFn = greet.bind(person); boundFn.call({name: 'Bob'}, 'Hi', '!'); // 仍然输出: "Hi, Alice!"
-
参数预设:可以预先设置部分参数
javascriptconst greetAlice = greet.bind(person, 'Hello'); greetAlice('!!!'); // 输出: "Hello, Alice!!!"
-
new 操作符例外:当绑定函数被用作构造函数时,绑定会被忽略
javascriptfunction Person(name) { this.name = name; } const BoundPerson = Person.bind({name: 'Ignored'}); const p = new BoundPerson('Actual'); console.log(p.name); // 输出: "Actual"
-
无 prototype 属性:绑定函数没有 prototype 属性
javascriptconsole.log(boundGreet.prototype); // 输出: undefined
(4) new 绑定:构造函数中的 this
this
指向新创建的对象。
javascript
function Dog(name) {
this.name = name;
this.bark = function() {
console.log(this.name + " barks!");
};
}
const myDog = new Dog("Buddy");
myDog.bark(); // "Buddy barks!"
3. 优先级:四大规则的较量
优先级排序 :
new 绑定
> 显式绑定(bind/call/apply)
> 隐式绑定(对象调用)
> 默认绑定
示例:new
如何碾压 bind
javascript
function Phone(brand) {
this.brand = brand;
}
const obj = {};
const BoundPhone = Phone.bind(obj); // 硬绑定到 obj
const iphone = new BoundPhone("Apple");
console.log(obj.brand); // undefined → new 覆盖了 bind 的 this
console.log(iphone.brand); // "Apple" → this 指向新对象
4. 使用 null
或 undefined
传入 call
?小心全局对象!
- 非严格模式 :
call(null)
或call(undefined)
时,this
会指向全局对象。 - 严格模式 :
this
严格为null
或undefined
。
javascript
function logThis() {
console.log(this);
}
logThis.call(null); // 非严格模式:输出 window
logThis.call(undefined); // 非严格模式:输出 window
"use strict";
logThis.call(null); // 输出 null
总是使用 null 来忽略 this 的绑定,可能会产生一些副作用。如果某个函数确实使用了 this (比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局作用域
(在浏览器中这个对象是 window),这将导致不可预计的后果(比如修改全局对象)。
5. 间接引用:隐式绑定的天敌
将对象方法赋值给变量或作为参数传递时,会触发间接引用,导致 this
丢失。
javascript
const car = {
brand: "Tesla",
start: function() { console.log(this.brand + " starts!"); }
};
const startCar = car.start;
startCar(); // "undefined starts!" → this 指向全局对象
// 事件监听中的经典问题
button.addEventListener("click", car.start); // this 指向 button,而非 car!
6. 软绑定:更灵活的 this
控制
硬绑定(bind
)的缺点是永久性绑定,无法覆盖。软绑定(Soft Binding) 允许默认绑定一个对象,但允许显式修改。
实现软绑定(参考《你不知道的JavaScript》):
javascript
Function.prototype.softBind = function(obj) {
const fn = this;
return function(...args) {
// 如果 this 是全局对象或 undefined,则使用 obj
const boundThis = (!this || this === window) ? obj : this;
return fn.apply(boundThis, args);
};
};
function showBrand() {
console.log(this.brand);
}
const defaultBrand = { brand: "Default" };
const softShow = showBrand.softBind(defaultBrand);
const car1 = { brand: "BMW" };
softShow.call(car1); // "BMW" → 显式绑定优先
softShow(); // "Default" → 默认使用 defaultBrand
7. 箭头函数:this
的词法捕获
箭头函数没有自己的 this
,而是 继承外层作用域的 this
,且无法通过 call
/bind
修改。
javascript
const counter = {
count: 0,
increment: function() {
// 传统函数:this 由调用方式决定
setTimeout(function() {
this.count++; // 错误!this 指向 window
}, 100);
// 箭头函数:继承外层 this
setTimeout(() => {
this.count++; // 正确!this 指向 counter
}, 100);
}
};
总结
- 调用位置决定一切:时刻关注函数如何被调用。
- 优先级是核心 :
new
> 显式 > 隐式 > 默认。 - 箭头函数是规则破坏者 :它直接继承外层
this
。 - 软绑定提供灵活性:在需要默认值但允许覆盖时使用。
理解 this
的规则后,你会发现它不再是玄学,而是一套逻辑清晰的机制。下次遇到 this
的"诡异行为"时,冷静分析调用位置,一切