call() - “逐个点名”
-
语法:func.call(thisArg, arg1, arg2, ...)
-
作用:
立即执行func 函数。- 将 func 内部的 this 指向第一个参数 thisArg。
- 将后续的参数 (arg1, arg2, ...) 逐个地传递给 func 函数。
apply() - “清单列表”
apply(this,args)的
this永远指向“调用”方法的对象本身
- 语法:func.apply(thisArg, [argsArray])
重写方法
-
作用:
立即执行func 函数。- 将 func 内部的 this 指向第一个参数 thisArg。
- 将一个数组 [argsArray] 中的元素,
**展开**后作为参数传递给 func 函数。
func.apply(thisArg, argsArray) 它的第二个参数 必须是一个
数组或类数组对象,apply会自动把这个数组「拆开」成一个个独立参数传给函数。
bind() - “专属遥控器”
-
语法:func.bind(thisArg, arg1, arg2, ...)
-
作用:
不会立即执行func 函数。- 而是返回一个全新的函数。
- 这个新函数被永久地绑定了 this 为 thisArg,并且可以预先绑定部分参数。
手写call
Function.prototype.myCall = function(thisArg, ...args) {
// 步骤 1: 处理 thisArg
// 如果 thisArg 是 null 或 undefined,this 应该指向全局对象 (浏览器中是 window)
// 使用 Object() 可以将原始类型(如 1, 'abc', true)包装成对象
const context = (thisArg === null || thisArg === undefined) ? window : Object(thisArg);
// 步骤 2: 将函数挂载到 context 上
// this 在这里指向调用 myCall 的函数本身 (例如: sayHello.myCall(...))
// 使用 Symbol 创建一个唯一的 key,防止覆盖 context 上已有的属性
const fnSymbol = Symbol('fn');
context[fnSymbol] = this; //this就是调用Call方法的函数
// 步骤 3: 执行函数并获取返回值
// 通过 context[fnSymbol](...args) 调用,此时函数内的 this 就指向了 context
const result = context[fnSymbol](...args);
// 步骤 4: 清理,删除临时添加的属性
delete context[fnSymbol];
// 步骤 5: 返回函数的执行结果
return result;
};
函数调用call改变自己this的过程:
1、函数首先调用Call方法
2、自身没有这个方法之后,通过内部原型去自己的原型对象。(也就是构造函数的原型)
3、原型上有这个方法之后。函数则开始执行这个call方法(函数是call方法的执行上下文,原型只是存放这个Call方法的仓库)所以 执行Call方法时,Call方法内部的this是指向调用Call的函数。
call方法的本质就是 让call方法第一个参数拥用了调用call方法的函数,然后让参数调用这个函数。
手写apply
Function.prototype.myApplyStrict = function(thisArg, argsArray) {
// 步骤 1: 处理 thisArg (与之前版本相同)
// 如果 thisArg 是 null 或 undefined,this 应该指向全局对象
// 使用 Object() 可以将原始类型包装成对象
const context = (thisArg === null || thisArg === undefined) ? window : Object(thisArg);
// 步骤 2: 将函数挂载到 context 上 (与之前版本相同)
// this 指向调用 myApplyStrict 的函数本身
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
let result;
// ======================================================================
// 步骤 3: 严格的参数校验和执行 (这是与简化版的关键区别)
// ======================================================================
// Case 1: 如果第二个参数为 null 或 undefined,则视为不带参数调用
// `argsArray == null` 是 `argsArray === null || argsArray === undefined` 的简写
if (argsArray == null) {
result = context[fnSymbol]();
} else {
// Case 2: 如果第二个参数存在,但不是一个对象 (e.g., a number, string, boolean)
// 那么就应该抛出 TypeError。注意:数组和类数组也是对象。
if (typeof argsArray !== 'object') {
// 这个错误信息是模仿 V8 引擎 (Chrome, Node.js) 的原生错误信息
throw new TypeError('CreateListFromArrayLike called on non-object');
}
// Case 3: 参数是对象 (如数组或类数组)
// 我们使用展开运算符 (...) 来传递参数。
// 现代 JS 的展开运算符本身就能很好地处理 "可迭代对象"。
// 如果传入一个不可迭代的普通对象 (如 { a: 1, b: 2 }),展开操作符也会抛出 TypeError,
// 这恰好与原生 apply 的行为一致。
result = context[fnSymbol](...argsArray);
}
// 步骤 4: 清理,删除临时添加的属性 (与之前版本相同)
delete context[fnSymbol];
// 步骤 5: 返回函数的执行结果 (与之前版本相同)
return result;
};
手写bind
Function.prototype.myBind = function(thisArg, ...boundArgs) {
// 获取原始函数
const self = this;
// 返回一个新函数
const fBound = function(...args) {
// 步骤 1: 合并参数
// 合并 bind 时传入的预置参数和调用时传入的新参数
const finalArgs = [...boundArgs, ...args];
// 步骤 2: 判断是否通过 new 调用
// 如果是 new 调用,fBound 的实例的 __proto__ 会指向 fBound.prototype
// 而 fBound.prototype 我们会将其指向 self.prototype
// 所以 this instanceof fBound 会为 true
// 此时,this 指向 new 创建的新对象,bind 绑定的 thisArg 无效
if (this instanceof fBound) {
// 使用 apply 将参数传递给原始构造函数,this 指向新实例
return self.apply(this, finalArgs);
} else {
// 普通调用,this 指向绑定的 thisArg
return self.apply(thisArg, finalArgs);
}
};
// 步骤 3: 维护原型链
// 让返回的函数的 prototype 指向原始函数的 prototype
// 这样通过 bind 创建的函数 new 出来的实例,才能继承原始构造函数的原型属性
if (self.prototype) {
fBound.prototype = Object.create(self.prototype);
}
return fBound;
};