0.this存在的意义
-
this的存在意义:提供了一种灵活的方式来引用当前对象,支持面向对象编程,简化代码。 -
所以实际上可以认为 js 里的this只有一种指向,那就是指向当前函数的调用对象,所有修改this指向其实应该是修改调用者对象**(虽然事实上js的底层实现可能并不是这样)**。箭头函数没有this,它不绑定this,它使用的this来自于闭包特性下的父级函数作用域里的this (更确切的说是父级词法作用域) (接下来会解释为什么可以这么认为)
1.this 绑定概述
在 JavaScript 中,this 关键字的绑定行为在不同的上下文中可能会有所不同。理解 this 的绑定规则对于编写和调试 JavaScript 代码至关重要。以下是 JavaScript 中 this 绑定的几种常见情况:
1. 全局上下文
在全局执行上下文中(即不在任何函数内部),this 指向全局对象。在浏览器中,全局对象是 window 对象;在 Node.js 环境中,全局对象是 global 对象。
console.log(this); // 在浏览器中输出: Window {...}
console.log(this); // 在 Node.js 中输出: Object [global] {...}
2. 函数上下文
在函数内部,this 的值取决于函数的调用方式。
2.1 简单调用
在普通函数调用中,this 默认指向全局对象(在严格模式下为 undefined)。
function globalFunction() {
console.log(this);
}
globalFunction(); // 在浏览器中输出: Window {...}
在严格模式下:
'use strict';
function globalFunction() {
console.log(this);
}
globalFunction(); // 输出: undefined
2.2 作为对象的方法调用
当函数作为对象的方法被调用时,this 指向调用该方法的对象。
const obj = {
name: 'Alice',
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
obj.greet(); // 输出: Hello, Alice!
2.3 构造函数调用
当使用 new 关键字调用函数时,this 指向新创建的对象。
function Person(name) {
this.name = name;
}
const person = new Person('Bob');
console.log(person.name); // 输出: Bob
2.4 使用 call、apply 和 bind
call和apply方法可以显式地设置函数调用时的this值。bind方法返回一个新的函数,并永久地绑定this值。
function greet() {
console.log(`Hello, ${this.name}!`);
}
const person = { name: 'Charlie' };
greet.call(person); // 输出: Hello, Charlie!
greet.apply(person); // 输出: Hello, Charlie!
const boundGreet = greet.bind(person);
boundGreet(); // 输出: Hello, Charlie!
3. 箭头函数
箭头函数不绑定自己的 this,它会捕获其所在上下文的 this 值。
const obj = {
name: 'David',
greet: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}!`);
}, 1000);
}
};
obj.greet(); // 输出: Hello, David!
在这个例子中,箭头函数中的 this 继承自 greet 方法中的 this,即 obj。
4. 类中的 this
在类的方法中,this 指向类的实例。
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const person = new Person('Eve');
person.greet(); // 输出: Hello, Eve!
5. DOM 事件处理程序
在 DOM 事件处理程序中,this 指向触发事件的 DOM 元素。
const button = document.querySelector('button');
button.addEventListener('click', function() {
console.log(this); // 输出: <button>Click me</button>
});
总结
-
全局上下文:
this指向全局对象。 -
函数上下文
:取决于函数的调用方式。
- 简单调用:指向全局对象(严格模式下为
undefined)。 - 作为对象的方法调用:指向调用该方法的对象。
- 构造函数调用:指向新创建的对象。
- 使用
call、apply和bind:显式设置this值。
- 简单调用:指向全局对象(严格模式下为
-
箭头函数:不绑定自己的
this,继承自所在上下文的this。 -
类中的
this:指向类的实例。 -
DOM 事件处理程序:指向触发事件的 DOM 元素。
2.this的显示绑定方法
在 JavaScript 中,this 的显示绑定可以通过 call、apply 和 bind 方法来实现。这些方法允许你显式地指定函数调用时 this 的值。下面是每种方法的详细说明和示例:
1. call 方法
call 方法调用一个函数,并显式地设置 this 的值。call 方法的第一个参数是要绑定的 this 值,后续参数是传递给函数的参数。
语法:
function.call(thisArg, arg1, arg2, ...)
示例:
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello'); // 输出: Hello, Alice!
2. apply 方法
apply 方法类似于 call 方法,但它接受一个数组(或类数组对象)作为参数列表。
语法:
function.apply(thisArg, [argsArray])
示例:
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const person = { name: 'Bob' };
greet.apply(person, ['Hi']); // 输出: Hi, Bob!
3. bind 方法
bind 方法创建一个新的函数,并永久地绑定 this 的值。新函数可以像普通函数一样被调用。
语法:
function.bind(thisArg, arg1, arg2, ...)
示例:
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const person = { name: 'Charlie' };
const boundGreet = greet.bind(person, 'Hey');
boundGreet(); // 输出: Hey, Charlie!
示例代码
以下是将 call、apply 和 bind 方法结合在一起的完整示例:
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
// 使用 call 方法
greet.call(person1, 'Hello'); // 输出: Hello, Alice!
greet.call(person2, 'Hi'); // 输出: Hi, Bob!
// 使用 apply 方法
greet.apply(person1, ['Hello']); // 输出: Hello, Alice!
greet.apply(person2, ['Hi']); // 输出: Hi, Bob!
// 使用 bind 方法
const boundGreet1 = greet.bind(person1, 'Hey');
const boundGreet2 = greet.bind(person2, 'Greetings');
boundGreet1(); // 输出: Hey, Alice!
boundGreet2(); // 输出: Greetings, Bob!
总结
call方法:调用函数并设置this值,参数以逗号分隔。apply方法:调用函数并设置this值,参数以数组形式传递。bind方法:创建一个新的函数,并永久地绑定this值,可以预先设置部分参数
快速记忆call apply bind方法参数的方法
"call".length比"apply".length短,所以参数一样的情况下,传参形式转化的字符串长度也更短
-
fn.call(this,a,b)
-
fn.apply(this,[a,b])
-
"a,b".length<"[a,b]".length
这之后 因为 "call".length === "bind".length < "apply".length 所以 bind的传参形式转化的字符串长度和call一样
3.实现显示绑定方法
核心思路: 更改函数执行时的调用对象
call的实现:
- 使用this获取调用函数 (this默认指向调用对象,函数也是一个对象,所以this指向调用函数)
- 如果 content 为 null 或 undefined,使用全局对象(在浏览器中为 window)
- 使用一个不太可能冲突的临时属性名将函数添加到 context 对象上
- 调用函数并传递参数 (这是call对文章开始的话的解释,这里改变了实际调用者,本质上this的指向并没有被更改,更改的是调用者)
- 删除临时添加的属性
- 返回函数的返回值
function myCall(content, ...args) {
//使用this获取调用函数
const fn = this //this指向调用对象,函数也是一个对象,所以this指向调用函数
// 如果 content 为 null 或 undefined,使用全局对象(在浏览器中为 window)
content = content ? content : globalThis
// 使用一个不太可能冲突的临时属性名
const uniqueKey = Symbol('fn');
// 将函数添加到 context 对象上
content[uniqueKey] = fn
// 调用函数并传递参数
const result = content[uniqueKey](...args)
// 删除临时添加的属性
delete content[uniqueKey];
// 返回函数的返回值
return result
}
Function.prototype.myCall = myCall
apply的实现
和call一样,但是要加判断第二参数不能不为数组 (这是apply对文章开始的话的解释)
function myApply(content, array) {
//不是数组就报错
if (!(array instanceof Array)) {
throw TypeError("CreateListFromArrayLike called on non-object")
}
//使用this获取调用函数
const fn = this //this指向调用对象,函数也是一个对象,所以this指向调用函数
// 如果 content 为 null 或 undefined,使用全局对象(在浏览器中为 window)
content = content ? content : globalThis
// 使用一个不太可能冲突的临时属性名
const uniqueKey = Symbol('fn');
// 将函数添加到 context 对象上
content[uniqueKey] = fn
// 调用函数并传递参数
const result = content[uniqueKey](...array)
// 删除临时添加的属性
delete content[uniqueKey];
// 返回函数的返回值
return result
}
Function.prototype.myApply = myApply
bind的实现
返回一个新函数,里面逻辑基本同call,但是要考虑如果对bind出来以后的函数使用了new操作符的情况**(这是bind对文章开始的话的解释)**
function myBind(content, ...args) {
// 使用 this 获取调用函数
const fn = this;
return function boundFn(...args2) {
// 如果 new 调用了 boundFn,则 this 指向新创建的对象
const isConstructorCall = this instanceof boundFn;
const finalContext = isConstructorCall ? this : (content || globalThis);
// 使用一个不太可能冲突的临时属性名
const uniqueKey = Symbol('fn');
// 将函数添加到 context 对象上
finalContext[uniqueKey] = fn;
// 调用函数并传递参数
const result = finalContext[uniqueKey](...args, ...args2);
// 删除临时添加的属性
delete finalContext[uniqueKey];
// 如果是构造函数调用,返回新创建的对象;否则返回函数的返回值
return isConstructorCall ? this : result;
}
}
Function.prototype.myBind = myBind
new操作符的实现在原型和原型链里提到过,它里面会调用apply或者call或者bind,那么其实也是通过修改函数调用对象实现的修改this指向,或者说是修改了调用者.