JavaScript—手写模拟实现call和apply
JavaScript—手写模拟实现call和apply
Call
Call()方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或者方法。
举个例子
var foo = { value:1 } function bar(){ console.log(this.value) } bar()//undefine bar.call(foo);//1
如果直接执行bar() 就相当于是 window.bar() 会从window找value,因为没有定义 所以打印 undefine
注意两点
1.call改变了this的指向,指向到foo
2.bar 函数执行了
模拟实现第一步
如果想实现bar函数中调用this改this指向 把 foo函数改一下结构
var foo = { value:1 bar = function(){ console.log(this.value) } } foo.bar() // 1
进行这样的操作就可以把this指向 foo
但是这样的话相当于给foo多加了一个bar的函数属性 所以要在使用过bar()之后使用delete 把 foo中的bar这个删除
所以模拟的步骤可以分为以下三步:
1.将bar函数设为foo的属性
2.执行foo中的bar函数
3.删除foo中的bar函数
我们用代码实现一下
// 第一步 foo.fn = bar() // 第二步 foo.fn() //第三步 delete foo.fn
上述代码中 fu是foo新建的属性,然后执行,然后删除
根据这个思路,尝试实现第一版的call2函数:
// 第一版 Function.prototype.call2 = function(context){ context.fn = this context.fn() delete context.fn } //测试一下 var foo = { value:1 } var bar = function(){ console.log(this.value) } bar.call2(foo) // 1
打印结果为 1 ,最初版的call模拟实现完成!!!
但是可能会有疑问就是为什么要 context.fn = this呢,这个this到底是谁呢
解释一下:
this指向调用它的对象,call2 是 bar调用的 所以 第一版中 context.fn = this 其中的this就是bar();如果在控制台打印 console.log(this)的话结果如下
ƒ (){ console.log(this.value) }
模拟实现第二步
开始的时候讲了,call函数是可以指定参数进行传递的。举个例子:
var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call(foo, 'sxh', 18); // sxh // 18 // 1
所以我们的这些参数应该怎么获取呢,所以JS函数执行提供一个 对象叫 Arguments,使用Arguments可以获取到函数传入的参数。所以既然可以获取到参数,我们在第一版上优化一下:
Function.prototype.call2 = function(context){ context.fn = this // 添加一个接受参数的数组 let argArr = [] // 从一开始 因为第一个参数是context 也就是this for(let i = 1;i<arguments.length;i++){ argArr.push(arguments[i]) } context.fn(...argArr)
delete context.fn } // 测试一下 var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call2(foo, 'sxh', 18); // sxh // 18 // 1
OKOK 现在已经完成90%!!!
模拟实现第三步
现在大致功能已经完成,但是还有几个点需要注意
1.this参数可以传递null,如果当是null的话,在浏览器环境下视为指向window
举个例子:
var value = 1; function bar() { console.log(this.value); } bar.call(null); // 1
虽然这个例子本身不适用call 最后根据作用域链还是会找到全局中的value 最后 打印 1;但是还是得优化一下 如果传输this = null 应该怎么进行判断
2.函数是可以有返回值的
举个例子:
var obj = { value: 1 } function bar(name, age) { return { value: this.value, name: name, age: age } } console.log(bar.call(obj, 'sxh', 18)); // Object { // value: 1, // name: 'sxh', // age: 18 // }
这是需要注意两个点,所以现在优化一下call2 完成第三版代码:
Function.prototype.call2= function(context){
// 新创建一个context 判断形参数据是否为null,如果为null 赋值为window var context = context||window; context.fn = this
var argArr = []
for(let i =1;i<arguments.length;i++){
argArr.push(arguments[i])
}
var result; if(argArr.length === 0){ result = context.fn() }else{ result = context.fn(...argArr) } delete context.fn return result }
// 测试一下 var value = 2; var foo = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.call2(null); // 2 console.log(bar.call2(foo , 'sxh', 18));
// {value: 1, name: 'sxh', age: 18}
OKOK, 完成了手写call!!!
Apply
Apply的使用方式跟call 大同小异
举个例子:
var foo = { value: 1 } function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } console.log(bar.call(foo,'sxh',18)) // 1 {value: 1, name: 'sxh', age: 18} console.log(bar.apply(foo,['sxh',18])) // 1 {value: 1, name: 'sxh', age: 18}
区别在于传参形式不同:
call 传参按照个数
apply 传参是多个参数按照数组形式传入
所以我们直接实现apply 好吧
Function.prototype.apply2 = function(context,argArr){ // 新创建一个context 判断形参数据是否为null,如果为null 赋值为window var context = context||window; context.fn = this var result; if(argArr.length === 0){ result = context.fn() }else{ result = context.fn(...argArr) } delete context.fn return result } var foo = { value:1 } var bar = function (name,age) { console.log(this.value) console.log(name,age) } bar.apply2(foo,['sxh',18]) // 1 // sxh 18
上述是Apply手写实现 根据第二个参数不用使用arguments函数截取 直接就可以使用。
借鉴:https://juejin.cn/post/6844903476477034510
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)