JavaScript专题之模拟实现call和apply
栏目: JavaScript · 发布时间: 6年前
内容简介:JS 函数 call 和 apply 用来手动改变 this 的指向,call 和 apply 唯一的区别就在于函数参数的传递方式不同,call 是以逗号的形式,apply 是以数组的形式:本文就尝试用其他方式来模拟实现 call 和 apply。首先观察 call 和 apply 有什么特点?
JS 函数 call 和 apply 用来手动改变 this 的指向,call 和 apply 唯一的区别就在于函数参数的传递方式不同,call 是以逗号的形式,apply 是以数组的形式:
let person1 = { name: "person1", say: function(age, sex) { console.log(this.name + ' age: ' + age + ' sex: ' + sex); } } let person2 = { name: "person" } person1.say.call(person2, 20, "男"); person1.say.apply(person2, [20, "男"]); 复制代码
本文就尝试用其他方式来模拟实现 call 和 apply。
首先观察 call 和 apply 有什么特点?
- 被函数调用(函数也是对象),相当于 call 和 apply 是函数的属性
- 如果没有传入需要 this 指向对象,那么 this 指向全局对象
- 函数执行了
- 最后都改变了 this 的指向
一、初步实现
基于 call 函数是调用函数的属性的特点,call 的 this 指向调用函数,我们可以尝试把调用函数的作为传入的新对象的一个属性,执行后,再删除这个属性就好了。
Function.prototype.newCall = function (context) { context.fn = this; // this 指的是 say 函数 context.fn(); delete context.fn; } var person = { name: "jayChou" }; var say = function() { console.log(this.name); } say.newCall(person); // jayChou 复制代码
是不是就初步模拟实现了 call 函数呢,由于 call 还涉及到传参的问题,所以我们进入到下一环节。
二、eval 方式
在给对象临时一个函数,并执行时,传入的参数是除了 context 其余的参数。那么我们可以截取 arguments 参数数组的第一个后,将剩余的参数传入临时数组。
在前面我有讲过函数 arguments 类数组对象的特点,arguments 是不支持数组的大多数方法, 但是支持for 循环来遍历数组。
Function.prototype.newCall = function (context) { context.fn = this; let args = []; for(let i=1; i< arguments.length; i++) { args.push('arguments[' + i + ']'); } // args => [arguments[1], arguments[2], arguments[3], ...] context.fn(args.join(',')); // ??? delete context.fn; } var person = { name: "jayChou" }; var say = function(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); } say.newCall(person); 复制代码
上面传递参数的方式最后肯定是失败的,我们可以尝试 eval 的方式,将参数添加子函数的作用域中。
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码
Function.prototype.newCall = function (context) { context.fn = this; let args = []; for(var i=1; i< arguments.length; i++) { args.push('arguments[' + i + ']'); } // args => [arguments[1], arguments[2], arguments[3], ...] eval('context.fn(' + args + ')'); delete context.fn; } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); } say.newCall(person, 18, '男'); // name: jayChou,age: 18, sex: 男 复制代码
成功啦!
实现了函数参数的传递,那么函数返回值怎么处理呢。而且,如果传入的对象是 null,又该如何处理?所以还需要再做一些工作:
Function.prototype.newCall = function (context) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } context.fn = this; let args = []; for(var i=1; i< arguments.length; i++) { args.push('arguments[' + i + ']'); } // args => [arguments[1], arguments[2], arguments[3], ...] var result = eval('context.fn(' + args + ')'); // 处理返回值 delete context.fn; return result; // 返回返回值 } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); return age + sex; } var check = say.newCall(person, 18, '男'); console.log(check); // 18男 复制代码
判断传入对象的类型,如果为 null 就指向 window 对象。利用 eval 来执行字符串代码,并返回字符串代码执行的结果,就完成了模拟 call。 大功告成!
三、ES 6 实现
前面我们用的 eval 方式可以用 ES6 的解决还存在的一些问题,有没有注意到,这段代码是有问题的。
context.fn = this; 复制代码
假如对象在被 call 调用前,已经有 fn 属性怎么办?
ES6 中提供了一种新的基本数据类型,Symbol,表示独一无二的值,另外,Symbol 作为属性的时候,不能使用点运算符。所以再加上 ES 的 rest 剩余参数替代 arguments 遍历的工作就有:
Function.prototype.newCall = function (context,...params) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } let fn = Symbol(); context[fn] = this var result = context[fn](...params); delete context.fn; return result; } var person = { name: "jayChou" }; function say(age, sex) { console.log(`name: ${this.name},age: ${age}, sex: ${sex}`); return age + sex; } var check = say.newCall(person, 18, '男'); console.log(check); // 18男 复制代码
四、apply
apply 和 call 的实现原理,基本类似,区别在于 apply 的参数是以数组的形式传入。
Function.prototype.newApply = function (context, arr) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null); } context.fn = this; var result; if (!arr) { // 判断函数参数是否为空 result = context.fn(); } else { var args = []; for (var i = 0; i < arr.length; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')'); } delete context.fn; return result; } 复制代码
es6 实现
Function.prototype.newApply = function(context, parameter) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this; var result = context[fn](...parameter); delete context[fn]; return result; } 复制代码
总结
本文通过原生 JS 的 ES5 的方法和 ES 6 的方法模拟实现了 call 和 apply 的原理,旨在深入了解这两个方法的用法和区别,希望你能有所收获。
欢迎关注我的个人公众号“谢南波”,专注分享原创文章。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript专题之模拟实现new
- 智能货柜专题三:如何实现不同机器“千机千面”?
- netcore 中的动态代理与RPC实现(微服务专题)
- TensorFlow系列专题(十四): 手把手带你搭建卷积神经网络实现冰山图像分类
- React专题:可变状态
- React专题:生命周期
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。