基础题解析(1-10)
1. 全局调用
var a = 1;
function test() {
console.log(this.a);
}
test(); // 输出: 1
解析:
- 普通函数直接调用
- 在非严格模式下,
this指向全局对象(浏览器中是window,Node.js 中是global) var a = 1会挂载到全局对象上- 所以
this.a就是window.a或global.a
2. 对象方法调用
var obj = {
a: 2,
test: function() {
console.log(this.a);
}
};
obj.test(); // 输出: 2
解析:
- 普通函数作为对象的方法调用
this指向调用该方法的对象obj- 所以
this.a就是obj.a
3. 方法赋值后调用
var a = 1;
var obj = {
a: 2,
test: function() {
console.log(this.a);
}
};
var foo = obj.test; // 只是赋值函数引用
foo(); // 输出: 1
解析:
obj.test只是将函数引用赋值给foofoo()是普通函数调用,不是方法调用this指向全局对象- 所以
this.a是全局的a
4. 多层对象
var a = 1;
var obj1 = {
a: 2,
obj2: {
a: 3,
test: function() {
console.log(this.a);
}
}
};
obj1.obj2.test(); // 输出: 3
解析:
- 函数作为对象的方法调用
- 调用者是
obj1.obj2 this指向直接调用者obj2- 所以
this.a是obj2.a
5. 回调函数
var a = 1;
var obj = {
a: 2,
test: function() {
console.log(this.a);
}
};
setTimeout(obj.test, 100); // 输出: 1
解析:
setTimeout的回调函数是obj.test的函数引用- 相当于:
setTimeout(function() { console.log(this.a); }, 100) - 回调函数是普通函数调用
this指向全局对象(浏览器中是window)- 严格模式下可能是
undefined
验证:
// 这相当于:
var callback = obj.test; // 取出函数
setTimeout(callback, 100); // 普通函数调用
6. 构造函数
function Person(name) {
this.name = name;
console.log(this); // 输出: Person { name: 'Alice' }
}
var p = new Person('Alice');
解析:
- 使用
new调用构造函数 this指向新创建的实例对象- 创建的对象是
Person的实例 - 实例的
__proto__指向Person.prototype
7. 箭头函数
var a = 1;
var obj = {
a: 2,
test: () => {
console.log(this.a);
}
};
obj.test(); // 输出: 1
解析:
- 箭头函数没有自己的
this this继承自定义时的作用域- 在全局作用域定义对象字面量,
this指向全局对象 - 所以箭头函数内的
this指向全局对象
关键:箭头函数的 this在定义时确定,不是在调用时确定。
8. 嵌套箭头函数
var a = 1;
var obj = {
a: 2,
test: function() {
return () => {
console.log(this.a);
};
}
};
obj.test()(); // 输出: 2
解析:
obj.test()调用时,test的this指向obj- 返回的箭头函数继承外层函数
test的this - 箭头函数的
this是test函数的this,即obj - 所以输出
obj.a即 2
9. 方法中的函数
var a = 1;
var obj = {
a: 2,
test: function() {
function inner() {
console.log(this.a);
}
inner();
}
};
obj.test(); // 输出: 1
解析:
obj.test()调用时,test的this指向obj- 但
inner是普通函数,不是箭头函数 inner()是普通函数调用,this指向全局对象- 这就是为什么需要箭头函数或
bind来保持this
解决方案:
// 方案1:使用箭头函数
test: function() {
const inner = () => {
console.log(this.a); // 2
};
inner();
}
// 方案2:使用 bind
test: function() {
const inner = function() {
console.log(this.a); // 2
}.bind(this);
inner();
}
// 方案3:保存 this
test: function() {
const self = this;
function inner() {
console.log(self.a); // 2
}
inner();
}
10. 立即执行函数
var a = 1;
var obj = {
a: 2,
test: (function() {
console.log(this.a); // 立即执行,输出: 1
})()
};
// 只定义 obj,test 是 undefined
解析:
- 立即执行函数 (IIFE) 是普通函数调用
- 在非严格模式下,
this指向全局对象 - 输出全局的
a即 1 - 注意:
obj.test的值是undefined,因为 IIFE 没有返回值
进阶题解析(11-20)
11. call/apply
var a = 1;
var obj1 = { a: 2 };
var obj2 = { a: 3 };
function test() {
console.log(this.a);
}
test.call(obj1); // 输出: 2
test.apply(obj2); // 输出: 3
解析:
call和apply可以显式绑定thistest.call(obj1)将this绑定到obj1test.apply(obj2)将this绑定到obj2- 区别:
call接收参数列表,apply接收参数数组
12. bind
var a = 1;
var obj = { a: 2 };
function test() {
console.log(this.a);
}
var boundTest = test.bind(obj);
boundTest(); // 输出: 2
var obj2 = { a: 3, test: boundTest };
obj2.test(); // 输出: 2(不是 3!)
解析:
bind创建新函数,永久绑定thisboundTest的this被永久绑定到obj- 即使通过
obj2.test()调用,this仍然是obj bind的绑定优先级最高
13. bind + new
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
var obj = {};
var BoundPerson = Person.bind(obj);
var p = new BoundPerson('Alice');
p.sayName(); // 输出: "Alice"
console.log(obj.name); // 输出: undefined
解析:
Person.bind(obj)创建了绑定函数BoundPerson- 但使用
new调用时,bind的绑定会被忽略 new BoundPerson('Alice')创建新实例pp的name是 'Alice'obj的name属性不存在,是undefined
关键规则:new的优先级高于 bind
14. 严格模式
'use strict';
var a = 1;
function test() {
console.log(this); // 输出: undefined
}
test();
解析:
- 严格模式下,普通函数调用的
this是undefined - 非严格模式下才是全局对象
- 这是严格模式的重要安全特性
15. 箭头函数 + call
var a = 1;
var obj = { a: 2 };
var test = () => {
console.log(this.a);
};
test.call(obj); // 输出: 1
解析:
- 箭头函数没有自己的
this - 箭头函数无法通过
call、apply、bind改变this - 箭头函数的
this继承自定义时的作用域 - 这里是在全局作用域定义的,
this指向全局对象 - 输出全局的
a即 1
16. 事件处理器
// 假设在浏览器中
var button = document.createElement('button');
button.textContent = 'Click me';
var obj = {
a: 1,
handleClick: function() {
console.log(this.a);
}
};
button.addEventListener('click', obj.handleClick);
button.click(); // 输出: undefined
解析:
- 事件处理器中,
this指向触发事件的 DOM 元素 - 这里
this指向button元素 button.a是undefined- 所以输出
undefined
如果想要输出 1:
// 方案1:使用 bind
button.addEventListener('click', obj.handleClick.bind(obj));
// 方案2:使用箭头函数
button.addEventListener('click', () => obj.handleClick());
// 方案3:修改 handleClick
handleClick: function(e) {
console.log(obj.a); // 直接引用 obj
}
17. 类中的 this
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
sayNameArrow = () => {
console.log(this.name);
}
}
const p = new Person('Bob');
const { sayName, sayNameArrow } = p;
sayName(); // 输出: undefined(错误!)
sayNameArrow(); // 输出: "Bob"
解析:
-
sayName():- 是普通方法,添加到
Person.prototype - 单独调用时,
this指向undefined(严格模式) - 所以
this.name是undefined
- 是普通方法,添加到
-
sayNameArrow:- 是箭头函数,在构造函数中定义
- 作为实例属性,不是原型方法
- 箭头函数继承构造函数中的
this - 即实例
p,所以输出 "Bob"
解决方案:
// 正确调用方法
p.sayName(); // 输出: "Bob"
// 或使用 bind
const boundSayName = p.sayName.bind(p);
boundSayName(); // 输出: "Bob"
18. 原型链上的 this
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
var p1 = new Person('Alice');
var p2 = new Person('Bob');
p1.sayName(); // 输出: "Alice"
p2.sayName(); // 输出: "Bob"
解析:
- 原型方法中的
this指向调用该方法的实例 p1.sayName()的this是p1p2.sayName()的this是p2- 所有实例共享同一个原型方法,但
this不同
19. 多层 bind
var obj1 = { a: 1 };
var obj2 = { a: 2 };
var obj3 = { a: 3 };
function test() {
console.log(this.a);
}
var test1 = test.bind(obj1);
var test2 = test1.bind(obj2);
var test3 = test2.bind(obj3);
test3(); // 输出: 1
解析:
bind只能绑定一次- 后续的
bind不会改变已经绑定的this test1的this被永久绑定到obj1- 所以最终输出
obj1.a即 1
20. 综合题
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn(); // 1️⃣
arguments[0](); // 2️⃣
}
};
obj.method(fn, 1, 2, 3);
// 输出: 10
// 输出: 4
解析: 1️⃣ fn()输出: 10
- 普通函数调用,
this指向全局对象 - 全局
length是 10 - 浏览器中
window.length表示 iframe 数量 - Node.js 中
global.length是undefined,但这里用var定义了全局变量
2️⃣ arguments[0]()输出: 4
-
特殊调用方式:
arguments[0]()相当于arguments.fn() -
这里的
this指向arguments对象 -
arguments.length是传入的参数个数 -
obj.method(fn, 1, 2, 3)传入了 4 个参数 -
但注意:
arguments[0]是fn函数 -
所以相当于:
arguments.fn = fn; arguments.fn(); // this 指向 arguments -
实际上浏览器中
arguments.length是 4,因为:- 第 1 个参数是
fn - 第 2-4 个参数是 1, 2, 3
- 第 1 个参数是
-
但需要在实际环境中验证
实际验证:
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
console.log("fn():");
fn(); // 10
console.log("arguments:", arguments);
console.log("arguments.length:", arguments.length); // 4
console.log("arguments[0]:", arguments[0]); // fn 函数
console.log("arguments[0]():");
arguments[0](); // 4
}
};
obj.method(fn, 1, 2, 3);
挑战题解析(21-25)
21. 链式调用
var obj = {
value: 1,
increment: function() {
this.value++;
return this; // 关键:返回 this
},
getValue: function() {
return this.value;
}
};
console.log(obj.increment().increment().getValue()); // 输出: 3
解析:
- 链式调用的关键是每个方法都返回
this obj.increment()的this是obj- 返回
this即返回obj - 所以可以继续调用
.increment() - 最终
value从 1 增加到 3
22. 模块模式
var calculator = (function() {
var value = 0; // 私有变量
return {
add: function(x) {
value += x;
return this; // 支持链式调用
},
getValue: function() {
return value;
},
reset: function() {
value = 0;
}
};
})();
calculator.add(5).add(3);
console.log(calculator.getValue()); // 输出: 8
解析:
- IIFE 创建闭包,
value是私有变量 - 返回对象中的方法,
this指向返回的对象 add方法返回this,支持链式调用- 方法通过闭包访问私有变量
value
23. 数组方法中的 this
var obj = {
data: [1, 2, 3, 4, 5],
multiplier: 2,
multiply: function() {
return this.data.map(function(item) {
return item * this.multiplier; // 问题:这里的 this
});
}
};
console.log(obj.multiply()); // 输出: [NaN, NaN, NaN, NaN, NaN]
解析:
map的回调函数是普通函数- 回调函数中的
this默认指向全局对象 - 严格模式下是
undefined - 所以
this.multiplier是undefined item * undefined得到NaN
24. 修复数组方法中的 this
var obj = {
data: [1, 2, 3, 4, 5],
multiplier: 2,
multiply: function() {
return this.data.map(function(item) {
return item * this.multiplier;
}, this); // 关键:传入 thisArg
}
};
console.log(obj.multiply()); // 输出: [2, 4, 6, 8, 10]
解析:
- 数组方法(
map、forEach、filter等)可以接收第二个参数thisArg thisArg会作为回调函数中的this- 这里传入
this,即外层的obj - 所以回调函数中的
this.multiplier是obj.multiplier
其他解决方案:
// 方案1:使用箭头函数
multiply: function() {
return this.data.map(item => item * this.multiplier);
}
// 方案2:保存 this
multiply: function() {
const self = this;
return this.data.map(function(item) {
return item * self.multiplier;
});
}
// 方案3:使用 bind
multiply: function() {
return this.data.map(function(item) {
return item * this.multiplier;
}.bind(this));
}
25. Proxy 中的 this
var target = {
value: 1,
getValue: function() {
return this.value;
}
};
var handler = {
get: function(obj, prop) {
if (prop === 'value') {
return 100;
}
return obj[prop];
}
};
var proxy = new Proxy(target, handler);
console.log(proxy.getValue()); // 输出: 100
解析:
proxy.getValue()调用- Proxy 的
get捕获器返回target.getValue - 然后调用
target.getValue(),this指向target target.value经过 Proxy 的get捕获返回 100- 所以输出 100
注意:Proxy 不改变方法的 this绑定 如果想让 this指向 proxy:
var handler = {
get: function(obj, prop) {
if (prop === 'value') {
return 100;
}
const value = obj[prop];
if (typeof value === 'function') {
return value.bind(obj); // 或者返回函数包装器
}
return value;
}
};
this 绑定规则总结
| 调用方式 | this 指向 | 优先级 |
|---|---|---|
new构造函数 | 新创建的实例 | 1 |
call/apply/bind | 指定的对象 | 2 |
| 对象方法调用 | 调用该方法的对象 | 3 |
| 普通函数调用 | 全局对象/undefined | 4 |
| 箭头函数 | 定义时的作用域的 this | 不参与比较 |
记忆口诀:
- 普通函数:
- new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
- 箭头函数:
- 看定义,不看调用
实际应用建议
- 使用箭头函数处理回调函数中的
this - 避免滥用箭头函数,特别是需要作为构造函数的方法
- 类方法使用普通函数,实例方法可以考虑箭头函数
- 使用 bind 提前绑定
this,避免运行时错误 - 严格模式下更安全,普通函数调用的
this是undefined