1、this
this
关键字是一个非常重要的概念,它代表函数执行时的上下文环境,即函数调用时所属的对象。this
的值并不是在函数定义时决定的,而是由函数的调用方式和执行环境动态决定的
1.1、全局上下文
全局作用域中,非严格模式下(non-strict mode),
this
指向全局对象。在浏览器中,这通常是window
对象;在Node.js环境中,这是global
对象。在严格模式(strict mode,通过在函数开头声明'use strict';
)下,this
会是undefined
。
1.2、函数调用
1.2.1、函数调用模式
如果函数直接被调用,没有作为对象的属性,非严格模式下this
指向全局对象;严格模式下为undefined
。
function test() {
consonsole.log(this);//this非严格模式下this指向全局对象,通常是window;严格模式下为undefined。
}
test()
1.2.2、对象方法调用模式
如果函数作为某个对象的一个属性被调用,this
指向该对象
let obj = {
name: "zhangs",
func: function() {
console.log(this.name)
}
}
obj.func();//这里的this指向obj
1.2.3、构造函数调用模式
使用new
关键字调用函数时,this
指向新创建的实例对象
function Person(name){
this.name = name;
}
const person = new Person("zhangsan");
console.log(person.name);//this指向新创建的对象person
1.2.4 事件处理器
在DOM事件处理程序中,this
通常指向触发事件的元素
1.2.5、apply、call和bind调用模式
1.2.5.1、call()
call()
方法可以让你改变函数执行时的上下文(this
值),并直接传递参数列表。
// call()
// call()方法可以让你改变函数执行时的上下文(this值),并直接传递参数列表。
function greet(greeting, timeOfDay) {
console.log(`${greeting}, ${this.name}. Good ${timeOfDay}!`);
}
const user = { name: "Alice" };
// 使用call()改变greet函数的上下文为user对象
greet.call(user, "Hello", "Morning"); // 输出: Hello, Alice. Good Morning!
1.2.5.2、apply()
apply()
与call()
类似,也是用来改变函数执行时的上下文,但它接受一个参数数组(或类数组对象)而不是单独的参数。
//apply()与call()类似,也是用来改变函数执行时的上下文,但它接受一个参数数组(或类数组对象)而不是单独的参数。
function greet(greeting, timeOfDay) {
console.log(`${greeting}, ${this.name}. Good ${timeOfDay}!`);
}
const user = { name: "Bob" };
// 使用apply()改变greet函数的上下文为user对象
greet.apply(user, ["Hi", "Afternoon"]); // 输出: Hi, Bob. Good Afternoon!
1.2.5.3、bind()
bind()
方法创建一个新的函数,在这个新函数中,this
的值被永久绑定到了传入的值。它不会立即调用函数,而是返回一个已绑定this
值的新函数供以后调用。
function greet(greeting, timeOfDay) {
console.log(`${greeting}, ${this.name}. Good ${timeOfDay}!`);
}
const user = { name: "Charlie" };
// 使用bind()创建一个新函数,其中的this被绑定到user对象
const boundGreet = greet.bind(user);
总结:
call()
和apply()
都立即调用了函数,并且可以改变函数调用时的this
值,主要区别在于参数传递的方式。bind()
则创建了一个新的函数副本,在这个副本中this
值被预先设定,但并不立即执行,需要你手动调用返回的函数
1.3、箭头函数
箭头函数不绑定自己的
this
,它会捕获其所在上下文的this
值作为自己的this
值。这意味着箭头函数中的this
是定义时所在上下文的this
,而非调用时的上下文。
举例1:在对象方法中使用箭头函数
尽管箭头函数被定义在person
对象的方法中,但由于箭头函数不绑定自己的this
,它实际上捕获了外部的this
,在全局作用域中这通常是window
对象(在浏览器环境下,严格模式下为undefined
)
const person = {
name: "Alice",
greet: () => {
console.log(`Hello, my name is ${this.name}`); // 注意这里的this并不指向person对象
}
};
person.greet(); // 输出: Hello, my name is undefined
举例2:正确使用箭头函数作为回调
箭头函数保持了定义时的上下文
const person = {
name: "Bob",
introduce: function() {
setTimeout(() => {
console.log(`My name is ${this.name}`); // 箭头函数保留了person的this
}, 1000);
}
};
person.introduce(); // 1秒后输出: My name is Bob
举例3:比较常规函数和箭头函数
const regularFunction = function() {
console.log(this);
};
const arrowFunction = () => {
console.log(this);
};
// 常规函数作为对象方法
const obj = {
regMethod: regularFunction,
arrowMethod: arrowFunction
};
obj.regMethod(); // 输出: obj对象,因为被对象调用
obj.arrowMethod(); // 输出: 全局对象或undefined(严格模式)
// 直接调用
regularFunction(); // 输出: 全局对象或undefined(严格模式)
arrowFunction(); // 同上,输出: 全局对象或undefined(严格模式)
1.4、模块和闭包
在模块或闭包中,
this
的行为遵循上述规则,但在复杂作用域结构中需要特别注意,以确保正确理解函数调用的上下文。
1.4.1、模块中的this
在ES6模块中,顶层的this
通常指向undefined
(在严格模式下),这与传统脚本文件中的全局对象不同
// myModule.js
export function sayHello() {
console.log(`Hello from module, this is: ${this}`);
}
// 使用模块
import { sayHello } from './myModule';
sayHello(); // 输出: Hello from module, this is: undefined
1.4.2、闭包中的this
闭包指的是有权访问另一个函数作用域中的变量的函数,即使在外部函数已经关闭(执行完毕)的情况下也是如此。在闭包中处理this
时,需要注意闭包内部函数如何访问外部函数的this
function User(name) {
this.name = name;
// 内部函数形成了一个闭包
this.greet = function() {
// 使用普通函数,this会随着调用方式变化
const innerFunction = function() {
console.log(`Hello, my name is ${this.name}`);
};
// 使用闭包时,直接调用innerFunction会导致this指向window或undefined
// 为了保持this的正确性,可以使用箭头函数或者bind()
innerFunction.call(this); // 保证了this指向User实例
};
}
const user = new User("Alice");
user.greet(); // 输出: Hello, my name is Alice
1.4.3、使用箭头函数维持闭包内的this
箭头函数不绑定自己的this
,所以它们常被用来在闭包中维持外部的this
上下文
function User(name) {
this.name = name;
// 使用箭头函数维持外部this
this.greet = () => {
setTimeout(() => {
console.log(`Hello again, I'm ${this.name}`); // this仍然指向User实例
}, 1000);
};
}
const user = new User("Bob");
user.greet(); // 1秒后输出: Hello again, I'm Bob
2、bind()、apply()、call()
2.1、bind()实现
Function.prototype.myBind = function(context, ...args) {
const func = this; //保存对原始函数的引用
return function(...laterArgs) { //返回一个新的函数,即绑定函数
return func.apply(context, [...args, ...laterArgs]); //在正确上下文中调用原始函数
};
};
2.1.1、手写实现bind()详细解释
举例代码:
function greet(prefix, suffix) {
console.log(`${prefix}, ${this.name}${suffix}!`);
}
const user = {
name: 'Alice'
};
// 使用myBind创建一个绑定到user对象的greet函数版本
const boundGreet = greet.myBind(user, 'Hello');
// 现在,即使在全局上下文中调用boundGreet,它仍然知道name属性来自于user对象
boundGreet('!'); // 输出: Hello, Alice!
当使用myBind()方法时,传递的第一个参数context,是用来决定将来函数被调用时,内部的this关键字应当指向哪个对象,这里是user对象。
- const func = this;在myBind内部,this指的是调用myBind的函数,这里是greet函数,赋值给fun,为后续使用。
- 返回新的匿名函数:myBind返回一个新的函数
return function(...laterArgs) {...}
),这个函数可以接收新的参数(...lateArgs),这个就是所谓的绑定函数,也就是boundGreet()- return func.apply(context, [...args, ...laterArgs]);当我们调用这个 绑定函数(boundGreet)时,它通过apply方法调用原始的greet函数。这里context(即user对象),被设定为this的值,确保greet内部的this.name能够正确的引用到user.name。同时,apply方法将通过myBind预设的参数("Hello")和绑定函数时传递的参数("!")合并后一起传递给greet函数
2.2、apply()实现
Function.prototype.myApply = function(context = window, args) {
context.fn = this; // 或 context['fn'] = this;
const result = context.fn(...(Array.isArray(args) ? args : []));
delete context.fn; // 清除添加的属性
return result;
};
2.2.1、手写实现apply()详细解释
举例代码
function greet(prefix) {
console.log(`${prefix}, ${this.name}!`);
}
const user = { name: 'Alice' };
// 使用自定义的myApply方法
greet.myApply(user, ['Hello']);
// 如果使用原生的apply方法,这将是
// greet.apply(user, ['Hello']);
greet.myApply(user,["Hello"])时,this在myApply内部指向greet函数本身,而context接收了user对象,args接收了数组["Hello"]
- context指的是user对象,context.fn = this,将greet函数临时挂载到user对象的fn属性,确保当user.fn(...args)调用函数时,确保this被绑定到了uerr
- 执行函数context.fn(...(Array.isArray(args)? args :[])),将args参数展开作为参数传给context.fn,实际调用的是挂载在context上面的greet函数,由于fn是user对象的属性,这里的this自然指向的是user
- 清理临时挂载到context的fn属性,防止对原对象造成不必要的影响或污染
- 返回结果,myApply()函数调用结果,如果greet函数有返回值,这里会返回那个值
2.3、call()实现
Function.prototype.myCall = function(context = window, ...args) {
context.fn = this; // 或 context['fn'] = this;
const result = context.fn(...args);
delete context.fn; // 清除添加的属性,避免污染
return result;
};
2.3.1、手写call()详细解释
举例代码
function greet(greeting, timeOfDay) {
console.log(`${greeting}, ${this.name}. Good ${timeOfDay}!`);
}
const user = { name: "Alice" };
greet.myCall(user, "Hello", "Morning"); // 输出: Hello, Alice. Good Morning!
详细解释:同apply,区别在于一个传数组(类数组对象),另一个传参数
2.4、call()和apply()区别
- call:接收一个参数列表,可以直接将参数作为单独的参数传入,比如func.call(context,arg1,arg2,arg3)
- apply:接收两个参数,第一个是上下文,第二个是数组或类数组对象,它包含了要传递给函数的所有参数,比如,func.apply(context,[arg1,arg2,arg3])