call
、apply
和 bind
的基本用法简介:
1. call
-
作用 :立即调用函数,并指定函数内部的
this
指向。 -
语法 :
function.call(thisArg, arg1, arg2, ...)
-
特点:
- 第一个参数是
this
的绑定值。 - 后续参数以逗号分隔,直接传递给函数。
- 第一个参数是
javascript
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello', '!'); // 输出:Hello, Alice!
2. apply
-
作用 :与
call
类似,但参数以数组形式传递。 -
语法 :
function.apply(thisArg, [argsArray])
-
特点:
- 第一个参数是
this
的绑定值。 - 第二个参数是一个数组或类数组对象。
- 第一个参数是
javascript
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Bob' };
greet.apply(obj, ['Hi', '!!']); // 输出:Hi, Bob!!
3. bind
-
作用 :返回一个新函数,并永久绑定
this
的值。 -
语法 :
function.bind(thisArg, arg1, arg2, ...)
-
特点:
- 返回的新函数不会立即执行。
- 可以预设部分参数(柯里化)。
javascript
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Charlie' };
const boundGreet = greet.bind(obj, 'Hey');
boundGreet('!!!'); // 输出:Hey, Charlie!!!
总结
call
和apply
都是立即调用函数,区别在于参数传递方式。bind
不会立即调用,而是返回一个绑定了this
的新函数。
手写call
-
检查调用者类型:
- 当调用
myCall
方法时,首先检查调用该方法的对象是否为函数。使用typeof this !== "function"
进行判断,如果不是函数,则抛出TypeError
异常,提示 "Caller is not a function"。
- 当调用
-
处理
context
参数:- 这个
context
就是指定的调用上下文对象,call
还能传递参数,我们使用es6
的rest
参数 - 对传入的
context
参数进行处理。如果context
为null
或者undefined
,则将其设置为全局对象globalThis
(在浏览器环境中是window
,在 Node.js 环境中是global
)。 - 如果
context
不是null
或undefined
,则使用Object()
函数将其转换为对象类型。 - 如果调用时没有传入额外的参数,
rest
参数默认是一个空数组[]
。
- 这个
-
创建唯一属性名:
- 使用
Symbol('temp')
创建一个唯一的符号key
,作为临时属性名。Symbol
类型的属性名是唯一的,能避免与context
对象已有的属性名冲突。
- 使用
-
将函数添加到
context
对象:- 使用
Object.defineProperty
方法将当前调用myCall
的函数(即this
)添加到context
对象上,属性名就是之前创建的key
。 - 设置该属性的
enumerable
为false
,这样在遍历context
对象时,这个临时属性不会被访问到。
其实我看过很多版本的call实现不需要用到Object.defineProperty,我只是有点强迫症,因为直接赋值的话,console.log()打印this,在node环境下会打印出这个临时属性。但是在浏览器环境下,不管该属性是不是可枚举的,都会显示这个临时属性。
- 使用
-
调用函数并获取结果:
- 使用
try
块来调用context
对象上的临时函数context[key]
,并将args
展开作为参数传入。 - 返回函数执行的结果。
- 使用
-
清理临时属性:
- 使用
finally
块确保无论函数执行是否出错,都会删除context
对象上的临时属性context[key]
,避免污染context
对象。
- 使用
完整实现
js
Function.prototype.myCall = function (context, ...args) {
// 判断调用myCall的是否为函数
if (typeof this !== "function") {
throw new TypeError("Caller is not a function");
}
// 如果 context 为 null 或 undefined,则将其设置为全局对象,否则将其转换为对象类型
context = (context === null || context === undefined) ? globalThis : Object(context);
// 创建一个唯一的 Symbol 作为临时属性名
var key = Symbol('temp');
// 使用 Object.defineProperty 方法将当前函数(即调用 myCall 的函数)添加到 context 对象上
// 并设置该属性不可枚举,避免在遍历对象时被访问到
Object.defineProperty(context, key, {
// 属性的值为当前调用 myCall 的函数
value: this,
// 设置该属性不可枚举
enumerable: false
});
try {
// 调用 context 对象上的临时函数,并传入参数,返回函数执行结果
return context[key](...args);
} finally {
// 无论函数执行是否出错,都删除 context 对象上的临时属性,避免污染对象
delete context[key];
}
};
手写apply
- 实现apply与call的思路差不多,需要注意参数传递的方式,apply是以数组作为第二个参数的,需要对args加以判断,
...
展开运算符可以展开[]
,但对undefined
或null
使用展开运算符会引发错误。
js
Function.prototype.myApply = function (context, args) {
// 检查调用 apply2 的对象是否为函数,如果不是则抛出类型错误
if (typeof this !== "function") {
throw new TypeError("Caller is not a function");
}
// apply 方法的第二个参数 args 必须是数组类型
if (!Array.isArray(args)) {
throw new TypeError("The parameter must be an array");
}
// 如果 args 未传入或者为 null、undefined,则将其设置为空数组
args = args || [];
// 处理 context 参数,如果 context 是 null 或 undefined,则将其设置为全局对象;否则将其转换为对象类型
context = (context === null || context === undefined)? globalThis : Object(context);
// 创建一个唯一的 Symbol 作为临时属性名,避免与 context 对象已有属性名冲突
var key = Symbol('temp');
// 将当前调用 apply2 的函数赋值给 context 对象的临时属性
context[key] = this;
try {
// 调用 context 对象上的临时函数,并将 args 数组展开作为参数传入,返回函数执行结果
return context[key](...args);
} finally {
// 无论函数执行是否成功,都删除 context 对象上的临时属性,避免污染对象
delete context[key];
}
}
手写bind
-
校验调用者:必须是函数。
-
处理
this
绑定:null/undefined
→ 全局对象。- 基本类型 → 包装对象。
- 对象 → 直接使用。
-
闭包保存关键变量 :原函数
fn
和context
。 -
实现绑定函数逻辑:
- 合并参数。
- 区分普通调用和
new
调用。
-
维护原型链 :确保
new
调用的实例能继承原函数的原型。 -
返回新函数:完成绑定。
js
Function.prototype.myBind = function (context, ...args) {
// 检查调用 myBind 的对象是否为函数,如果不是则抛出类型错误
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myBind - what is trying to be bound is not callable');
}
// 处理 context 参数,如果 context 为 null 或 undefined,则使用全局对象
// 否则将 context 转换为对象类型
context = (context === null || context === undefined)? globalThis : Object(context);
// 保存调用 myBind 的原函数
const fn = this;
const bound = function (...innerArgs) {
// 合并预先传入的参数和调用时传入的参数
const allArgs = args.concat(innerArgs);
// 判断是否通过 new 关键字调用绑定函数
if (this instanceof bound) {
// 如果是通过 new 关键字调用,忽略传入的 context,使用 new 调用原函数
return new fn(...allArgs);
} else {
// 如果不是通过 new 关键字调用,使用 apply 方法调用原函数,并指定 this 为 context
return fn.apply(context, allArgs);
}
};
// 维护原型关系,如果原函数有 prototype 属性,则将绑定函数的 prototype 设置为原函数 prototype 的副本
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
// 返回绑定后的新函数
return bound;
};