this(执行上下文)
🚩 这个专栏是一个 JS 进阶系列,当前内容为 JS 执行机制,建议按顺序阅读
执行上下文&作用域
词法环境&变量环境
this(上下文对象) 🔹
概述
🌍 前提概要:
在上文 执行上下文&作用域 中,我们已经知道
- 执行上下文的
thisValue
和this
等价- 全局执行上下文的
this
指向全局对象
而函数执行上下文中的 thisValue
指向 ,我却一笔带过 ,现在打算放在这篇文章帮它整明白!
💡 我们每次调用函数时,解析器都会将一个 上下文对象 作为 隐含参数 传递进函数。 使用 this 来引用上下文对象,根据函数的 调用方式 不同,this 的值也不同。
函数中的 this
普通函数调用
在普通函数调用中,this
指向全局对象(非严格模式)或 undefined
(严格模式)。
🌰举个栗子
let a = {
b: function () {
let func = function () {
console.log(this); //Window
}
func(); //普通函数调用
},
}
a.b(); //打印 Window
方法调用
当函数作为对象的方法调用时,this
指向调用该方法的对象。
🌰举个栗子
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
obj.greet(); // 输出: Alice
构造函数调用
当函数作为构造函数调用(使用 new
关键字)时,this
指向新创建的对象。
🌰举个栗子
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出: Alice
箭头函数中的 this
箭头函数没有自己的 this
,它会捕获其所在上下文的 this
值。这意味着箭头函数中的 this
在定义时就已经确定,不会因为调用方式而改变。
🌰举个栗子
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(this.name);
}, 100);
}
};
obj.greet(); // 输出: Alice
类中的 this
类本质上是基于构造函数和原型继承的语法糖 , 在类的方法中,this
指向类的实例。
🌰举个栗子
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(this.name);
}
}
const alice = new Person('Alice');
alice.greet(); // 输出: Alice
使用 call
、apply
和 bind
call、apply、bind 方法:这三种方法可以明确指定 this
的值 , 即显式设置this
call、apply、bind 原理
call
作用:
call
方法用于调用一个函数,并设置函数执行时的 this
值,同时可以传递参数列表。
语法:
func.call(thisArg, arg1, arg2, ...)
thisArg
:函数执行时的this
值。arg1, arg2, ...
:传递给函数的参数列表。
原理:
- 将函数的
this
绑定到thisArg
。 - 立即执行函数,并传入参数。
🌰举个例子:
const person1 = {
name: '张三',
introduce: function (city, country) {
console.log(`${this.name} is from ${city}, ${country}`);
},
};
const person2 = {
name: '李四',
};
person1.introduce.call(person2, '上海', '中国')
打印:李四 is from 上海, 中国
apply
作用:
apply
方法与 call
类似,用于调用一个函数并设置 this
值,但它的参数是以数组(或类数组对象)的形式传递的。
语法:
func.apply(thisArg, [argsArray])
thisArg
:函数执行时的this
值。argsArray
:传递给函数的参数数组。
原理:
apply
的原理与 call
几乎相同,唯一的区别是参数传递方式。
🌰仍然是上面那个例子:
参数传递 必须是参数数组
person1.introduce.apply(person2, ['上海', '中国']);
bind
作用:
bind
方法用于创建一个新函数,并将原函数的 this
值绑定到指定的 thisArg
。与 call
和 apply
不同,bind
不会立即执行函数,而是返回一个绑定了 this
的新函数。
语法:
func.bind(thisArg, arg1, arg2, ...): newFunc
thisArg
:新函数执行时的this
值。arg1, arg2, ...
:预先传递给新函数的参数(可选)。return newFunc
: 返回一个绑定了this
的新函数。
原理:
bind
的原理可以理解为:
- 返回一个新函数。
bind
不会改变原函数,而是返回一个新的函数。
- 新函数执行时,
this
被绑定到thisArg
。- 新函数的
this
值会被固定为传入的 第一个参数。例如f.bind(obj)
,实际上可以理解为obj.f()
,这时,f 函数体内的this
自然指向的是obj
- 新函数的
- 可以预先传递部分参数(柯里化)。
- 可以在调用新函数时,传入额外的参数,这些参数会在调用时被传递给原函数。
预设参数
案例1:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2); // 固定第一个参数为2
console.log(double(5)); // 输出: 10
在这个例子中:
null
是固定的this
值,因为我们不需要设置this
。2
是预设的第一个参数(对应原函数的a
),而在调用double(5)
时,5
会作为第二个参数(对应原函数的b
)传递。double
函数实际上是multiply
函数的一个“封装”,它将第一个参数固定为2
,而this
的值被设置为null
,这在这里并不重要,因为multiply
函数并没有使用this
。
案例 2
function f(y, z){
return this.x + y + z;
}
let m = f.bind({x : 1}, 2);
console.log(m(3));
//6
在这个例子中:
- 参数
{x : 1}
:- 这里
bind
方法会把它的第一个实参绑定给f
函数体内的this
,所以这里的this
即指向{x : 1}对象
,
- 这里
- **参数 **
2
:- 从第二个参数起,会依次传递给原始函数,这里的第二个参数
2
,即是 f 函数的y
参数,
- 从第二个参数起,会依次传递给原始函数,这里的第二个参数
- **参数 **
3
:- 最后调用 m(3)的时候,这里的 3 便是最后一个参数 z 了,所以执行结果为 1 + 2 + 3 = 6
分步处理参数的过程其实是一个典型的 函数柯里化 的过程(Curry)
案例
分析代码:
let a = {
b: function () {
let func = function () {
console.log(this.c);//undefined
}
func(); //普通函数调用
},
c : 'Hello!'
}
a.b(); // undefined
undefined
的 原因:
😃
func
函数是在a.b()
方法内定义的,调用时是作为一个普通函数调用,而不是作为对象的方法调用。这导致this
的指向变成了全局对象(在浏览器中是window
),而不是对象a
。
🤔在学习完所有this指向的场景后,你能想出不同的方法去使得this.c
能正常访问吗?
解决方法
赋值
可以通过赋值的方式将 this 赋值给 that
let a = {
b: function () {
let self = this //将 this 赋值给 that
let func = function () {
console.log(self.c);//修改成self
}
func();
},
c: 'Hello!'
}
a.b(); //输出: Hello!
使用箭头函数
let a = {
b: function () {
let func = () => {
console.log(this.c);
}
func();
},
c: 'Hello!'
}
a.b(); // 输出: Hello!
使用call / apply
let a = {
b: function () {
let func = function () {
console.log(this.c);
}
func.call(this); //call
},
c: 'Hello!'
}
a.b(); // 输出: Hello!
使用 bind方法
//写法一
let a = {
b: function() {
let func = function() {
console.log(this.c);
}.bind(this); //返回新函数 覆盖
func();
},
c: 'Hello!'
}
a.b(); // 输出: Hello!
//写法二
let a = {
b : function(){
let func = function(){
console.log(this.c);
}
func.bind(this)(); //立即执行
},
c : 'Hello!'
}
a.b(); // 输出: Hello!