深入理解JavaScript中apply、call与bind的用法与实现
本文详细解析JavaScript中apply、call和bind三个函数的核心区别与使用场景。通过对比参数传递方式的差异,深入剖析它们如何改变函数内部的this指向。文章还包含手写实现这三个函数的完整代码与原理讲解,帮助开发者掌握函数绑定的底层逻辑及柯里化应用。
apply和call
apply与call在功能上高度相似,二者均用于改变函数执行时的this指向,本质上是间接调用一个函数,并将该函数绑定至指定的对象上:
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1, param2)
}
let obj = {
name: 'easylee',
}
getName.call(obj, 123, 23)
getName.apply(obj, [123, 23])
以上述示例为例,若直接调用 getName,其返回结果为 window;但若通过 call 方法执行,则可将函数绑定至 obj 对象,使其成为该对象的一个方法,此时函数内部的 this 也将指向 obj。
两者的主要区别在于参数传递方式:当函数存在多个参数时,call 要求直接依次传入各个参数,而 apply 则需将多个参数封装为一个数组进行传递。
手写call
实现原理如下:
- 首先,通过
Function.prototype.myCall将自定义的myCall方法挂载至所有函数的原型对象上,确保所有函数实例均可调用该方法。 - 在
myCall方法内部,首先利用typeof this !== "function"判断调用myCall的对象是否为函数。若调用者非函数,则抛出类型错误。 - 接着,判断是否传入了上下文对象
context。若未传入,则将context设为全局对象。此处采用一种判断全局对象的方法:先检测是否存在global对象,若存在则使用global;否则检测是否存在window对象,若存在则使用window;若两者均不存在,则将context设为undefined。 - 随后,使用
Symbol生成一个唯一键fn,用于将调用myCall的函数作为新属性绑定至上下文对象。 - 将调用
myCall的函数赋值给上下文对象的fn属性,从而实现将函数绑定至上下文对象的效果。 - 调用绑定在上下文对象上的函数,并传入
myCall方法的其他参数args。 - 调用完成后,从上下文对象中删除该绑定函数,以避免对原对象造成污染或影响。
- 最后返回函数调用的结果。
Function.prototype.myCall = function (context, ...args) {
// 判断调用myCall的是否为函数
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall - 被调用的对象必须是函数')
}
// 判断是否传入上下文对象,不传入则指定默认全局对象
context = context || (typeof global !== 'undefined' ? gloabl : typeof window !== 'undefined' ? window : undefined)
// 在上下文对象上绑定当前调用的函数,作为属性方法
// 不能直接调用this方法函数,原因在于如果不将这个方法绑定到上下文对象上
// 直接执行this函数,this函数里面的this上下文对象无法识别为绑定的对象
let fn = Symbol('key')
context[fn] = this
const result = context[fn](...args)
// 删除这个函数,避免对上下文对象造成影响
delete context[fn]
return result
}
const test = {
name: 'xxx',
hello: function () {
console.log(`hello,${this.name}!`)
},
add: function (a, b) {
return a + b
},
}
const obj = { name: 'world' }
test.hello.myCall(obj) //hello,world!
test.hello.call(obj) //hello,world!
console.log(test.add.myCall(null, 1, 2)) //3
console.log(test.add.call(null, 1, 2)) //3
手写apply
Function.prototype.myApply = function (context, argsArr) {
// 判断调用myApply的是否为函数
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myApply - 被调用的对象必须是函数");
}
// 判断传入的参数是否为数组
if (argsArr && !Array.isArray(argsArr)) {
throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组");
}
// 如果没有传入上下文对象,则默认为全局对象
//global:nodejs的全局对象
//window:浏览器的全局对象
context =
context ||
(typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 用Symbol来创建唯一的fn,防止名字冲突
let fn = Symbol("key");
// this是调用myApply的函数,将函数绑定到上下文对象的新属性上
context[fn] = this;
// 传入myApply的多个参数
const result = Array.isArray(argsArr)
? context[fn](...argsArr)
: context[fn]();
// 将增加的fn方法删除
delete context[fn];
return result;
};
// 测试一下
const test = {
name: "xxx",
hello: function () {
console.log(`hello,${this.name}!`);
},
};
const obj = { name: "world" };
test.hello.myApply(obj); //hello,world!
test.hello.apply(obj); //hello,world!
const arr = [2,3,6,5,1,7,9,5,0]
console.log(Math.max.myApply(null,arr));//9
console.log(Math.max.apply(null,arr));//9
bind
最后介绍 bind,其与前两者的主要区别在于:通过 bind 绑定的函数不会立即执行,而是返回一个新函数,需手动调用该新函数,以此实现函数内部 this 的绑定。
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1)
console.log(param2)
}
let obj = {
name: 'easylee',
}
let fn = getName.bind(obj, 123, 234) // 通过绑定创建一个新函数,然后再调用新函数
fn()
此外,bind 还支持柯里化功能,即在绑定时传入的参数将被保留,并在最终调用时直接使用。
let sum = (x, y) => x + y
let succ = sum.bind(null, 1) // 绑定时没有指定对象,但是给函数的第一个参数指定为1
succ(2) // 3, 调用时只传递了一个参数2,会直接对应到y,因为前面的1已经绑定到x上了
手写bind
实现原理如下:
- 首先,通过
Function.prototype.myBind将自定义的myBind方法添加至所有函数的原型对象上,使所有函数实例均可调用该方法。 - 在
myBind方法内部,首先通过typeof this !== "function"判断调用myBind的对象是否为函数。若调用者非函数,则抛出类型错误。 - 接着,判断是否传入了上下文对象
context。若未传入,则将context设为全局对象。此处采用一种判断全局对象的方法:先检测是否存在global对象,若存在则使用global;否则检测是否存在window对象,若存在则使用window;若两者均不存在,则将context设为undefined。 - 保存原始函数的引用,使用
_this变量表示。 - 返回一个新的闭包函数
fn作为绑定后的函数。该函数接受任意数量的参数innerArgs。(关于闭包的详细介绍可参考相关文章->闭包的应用场景)。 - 在返回的函数
fn中,首先判断是否通过new关键字调用了函数。需注意:若返回的函数被用作构造函数,即使用new关键字调用时,this的值将指向新创建的实例对象。通过检查this instanceof fn,可判断返回函数是否被作为构造函数调用。此处使用new _this(...args, ...innerArgs)来创建新对象。 - 若非通过
new调用,则使用apply方法将原始函数_this绑定至指定的上下文对象context。此处使用apply方法的目的是将参数数组args.concat(innerArgs)作为参数传递给原始函数。
Function.prototype.myBind = function (context, ...args) {
// 判断调用myBind的是否为函数
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myBind - 被调用的对象必须是函数");
}
// 如果没有传入上下文对象,则默认为全局对象
//global:nodejs的全局对象
//window:浏览器的全局对象
context =
context || (typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 保存原始函数的引用,this就是要绑定的函数
const _this = this;
// 返回一个新的函数作为绑定函数
return function fn(...innerArgs) {
// 判断返回出去的函数有没有被new
if (this instanceof fn) {
return new _this(...args, ...innerArgs);
}
// 使用apply方法将原函数绑定到指定的上下文对象上
return _this.apply(context,args.concat(innerArgs));
};
};
// 测试
const test = {
name: "xxx",
hello: function (a,b,c) {
console.log(`hello,${this.name}!`,a+b+c);
},
};
const obj = { name: "world" };
let hello1 = test.hello.myBind(obj,1);
let hello2 = test.hello.bind(obj,1);
hello1(2,3)//hello,world! 6
hello2(2,3)//hello,world! 6
console.log(new hello1(2,3));
//hello,undefined! 6
// hello {}
console.log(new hello2(2,3));
//hello,undefined! 6
// hello {}
总结而言,这三个函数均用于改变函数内部 this 对象的指向,但使用方式各异:apply 传递多个参数时使用数组形式,call 直接传递多个参数,而 bind 可将绑定时传递的参数保留至调用时直接使用,支持柯里化;同时,bind 不会直接调用,绑定后返回一个新函数,需通过调用该新函数来执行。
到此这篇关于玩转JavaScript函数:apply/call/bind技巧的文章就介绍到这了,更多相关JavaScript apply、call、bind 函数内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
