this 值
this 的值取决于它出现的上下文:函数、类、全局
一、函数上下文
在函数内部,this 的值取决于函数如何被调用。可以将 this 看作是函数的一个隐藏参数(就像函数定义中声明的参数一样),this 是语言在函数体被执行时为你创建的绑定。
1.普通函数
对于典型的函数,this 的值是函数被访问的对象。换句话说,如果函数调用的形式是 obj.f(),那么 this 就指向 obj。
function getThis() {
return this;
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.getThis = getThis;
obj2.getThis = getThis;
console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
在这里,getThis() 函数被调用两次,每次调用时,this 的值都是调用它的对象。
不过我们不能认为是谁拥有这个getThis()函数,而应该是谁调用了这个函数,this 的值是调用者。
const obj3 = {
__proto__: obj1,
name: "obj3",
};
console.log(obj3.getThis()); // { name: 'obj3' }
向上面的代码添加这样一段,我们发现虽然obj1拥有getThis()函数,但是obj3调用了getThis()函数,所以this的值是obj3。
因为this的值是调用者,而不是拥有者。
2.回调函数
当一个函数作为回调函数传递时,this 的值取决于如何调用回调,这由 API 的实现者决定。回调函数通常以 undefined 作为 this 的值被调用(直接调用,而不附加到任何对象上),这意味着如果函数是在非严格模式,this 的值会是全局对象(globalThis)。这在迭代数组方法、Promise() 构造函数等例子中都是适用的。
function logThis() {
"use strict";
console.log(this);
}
[1, 2, 3].forEach(logThis); // undefined、undefined、undefined
3.箭头函数
箭头函数中的this
箭头函数没有自己的this绑定。当箭头函数内的this关键字被解析时,它实际上引用的是最近一层非箭头函数的作用域中的this值。这种行为称为词法作用域的this。
这意味着:
箭头函数不会创建自己的this上下文,而是继承了外部函数的this值。
如果箭头函数是在全局作用域中定义的,那么它的this将指向全局对象(在浏览器中是window,在Node.js环境中是global)。
在类的方法中定义的箭头函数会捕获类实例的this,这使得它们非常适合用作事件处理器或回调函数,因为不需要手动绑定this。
const obj = {
name: 'Alice',
traditionalMethod: function() {
console.log(this.name); // 输出 "Alice"
},
arrowMethod: () => {
console.log(this.name); // 输出 undefined (如果在浏览器中运行) 或 global.name (在Node.js中)
}
};
obj.traditionalMethod(); // 调用传统方法
obj.arrowMethod(); // 调用箭头函数方法
js中作用域分为全局作用域、函数作用域和块作用域。
obj对象本身并不是一个作用域。对象是一个数据结构,它可以包含属性和方法,但它不提供一个新的作用域。只有函数才能创建新的作用域。
arrowMethod 是一个箭头函数。
关键点在于 arrowMethod 的定义位置。虽然 arrowMethod 是 obj 的一个属性,但它的定义是在全局作用域中进行的。
因此,arrowMethod 捕获的是全局作用域中的 this,而不是 obj 的 this
如果你想让 arrowMethod 的 this 指向 obj,可以在 obj 的一个方法内部定义箭头函数,这样箭头函数会捕获该方法的 this:
const obj = {
name: 'Alice',
traditionalMethod: function() {
console.log(this.name); // 输出 "Alice"
},
arrowMethod: function() {
const innerArrow = () => {
console.log(this.name); // 输出 "Alice"
};
return innerArrow;
}
};
obj.traditionalMethod(); // 调用传统方法
obj.arrowMethod()(); // 调用箭头函数方法
在箭头函数外部加了一个普通函数,产生了一个作用域,使得箭头函数的 this 指向了这个普通函数中的 this。
总结
1、对象本身不创建新的作用域。
2、箭头函数的 this 绑定是在定义时确定的,而不是在调用时确定的
4.构造函数
当一个函数被用作构造函数(使用 new 关键字)时,无论构造函数是在哪个对象上被访问的,其 this 都会被绑定到正在构造的新对象上。除非构造函数返回另一个非原始值,不然 this 的值会成为 new 表达式的值。
function C() {
this.a = 37;
}
let o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
在第二个例子(C2)中,因为在构造过程中返回了一个对象,this 被绑定的新对象被丢弃。(这基本上使得语句 this.a = 37; 成为了死代码。它并不完全是死代码,因为它被执行了,但是它可以被消除而不产生任何外部效果。)
bind() 方法
调用 f.bind(someObject) 会创建一个新函数,这个新函数具有与 f 相同的函数体和作用域,但 this 的值永久绑定到 bind 的第一个参数,无论函数如何被调用。
也就是说,会创建一个和f一模一样的函数体和作用域,只有this被更改为了我们指定的值。
function f() {
return this.a;
}
const g = f.bind({ a: "azerty" });
console.log(g()); // azerty
const h = g.bind({ a: "yoo" }); // bind 只能生效一次!
console.log(h()); // azerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37 37 azerty azerty
call()和apply()
使用 call() 和 apply(),你可以对 this 进行传值,就像它是一个显式参数。
function add(c, d) {
return this.a + this.b + c + d;
}
const o = { a: 1, b: 3 };
// 第一个参数被绑定到隐式的 'this' 参数;
// 剩余的参数被绑定到命名参数。
add.call(o, 5, 7); // 16
// 第一个参数被绑定到隐式的 'this' 参数;
// 第二个参数是一个数组,其成员被绑定到命名参数。
add.apply(o, [10, 20]); // 34