本文共 1320 字,读完只需 5 分钟

概述

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 有什么特点?

  1. 被函数调用(函数也是对象),相当于 call 和 apply 是函数的属性
  2. 如果没有传入需要 this 指向对象,那么 this 指向全局对象
  3. 函数执行了
  4. 最后都改变了 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] = thisvar 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专题之模拟实现call和apply相关推荐

  1. JavaScript专题之模拟实现new

    本文共 1230 字,读完只需 5 分钟 写在前面 最近工作太忙,快接近两周没更新博客,总感觉有一些事情等着自己去做,虽然工作内容对自己提升挺大,但我总觉得,一直埋着头走路,偶尔也需要抬起头来,看看现 ...

  2. javascript专题

    JavaScript之变量及作用域 JavaScript之声明提升 JavaScript之执行上下文 JavaScript之变量对象 JavaScript之原型与原型链 JavaScript之作用域链 ...

  3. JavaScript 专题之函数柯里化

    JavaScript 专题系列第十三篇,讲解函数柯里化以及如何实现一个 curry 函数 定义 维基百科中对柯里化 (Currying) 的定义为: In mathematics and comput ...

  4. JavaScript函数重载模拟

    我们从结果向实现推,首先看我们要实现什么样的效果: css(hi,"color","red") css([hi,hello],"color" ...

  5. web前端html怎么求最大值和最小值,第8篇-JavaScript专题之如何求数组的最大值和最小值...

    前言 取出数组中的最大值或者最小值是开发中常见的需求,但你能想出几种方法来实现这个需求呢? Math.max JavaScript 提供了 Math.max 函数返回一组数中的最大值,用法是: 值得注 ...

  6. JavaScript专题之从零实现jQuery的extend

    JavaScritp 专题系列第七篇,讲解如何从零实现一个 jQuery 的 extend 函数 前言 jQuery 的 extend 是 jQuery 中应用非常多的一个函数,今天我们一边看 jQu ...

  7. JavaScript 专题之如何判断两个对象相等

    JavaScript 专题系列第十二篇,讲解如何判断两个参数是否相等 前言 虽然标题写的是如何判断两个对象相等,但本篇我们不仅仅判断两个对象相等,实际上,我们要做到的是如何判断两个参数相等,而这必然会 ...

  8. 【成长之路】JavaScript中,模拟 call 的底层实现

    很多人都说IT人员的黄金年龄在 35岁以下,过了保质期就会被淘汰,但是,事实真的是这样吗?我见过40岁以上的IT人员,他们并没有被这个行业所淘汰,相反,他们可以比年轻的IT人员拥有更高的工作效率,因为 ...

  9. JavaScript 专题(九)数组中查找指定元素

    JavaScript 专题(九)数组中查找指定元素 上一篇文章中,我们了解了数组扁平化的思想,并学习了 lodash 是如何处理数组扁平化的. 这次我们来讨论在数组中查找元素时所用的一些方法,并且参考 ...

最新文章

  1. Convert .Net Program To Mono
  2. java ide 2017_Intellij ide 2017.2新建javaweb项目,并且部署
  3. iconfont 图标转为字体_阿里字体库iconfont使用方法
  4. CSS中Float概念相关文章
  5. ActiveReports 9 新功能:借助目录(TOC)控件为报表添加目录功能
  6. Eclipse之文件【默认编码格式设置】,防止乱码等问题
  7. onnx 测试_用于ONNX的TensorRT后端
  8. PHP与MYSQL数据库链接方法
  9. php判断搜索引擎来路,php实现判断访问来路是否为搜索引擎机器人的方法
  10. winform 报表的基本使用
  11. Cordova for iOS
  12. Hystrix熔断机制原理剖析
  13. Qt —— QWebEngineView加载谷歌离线地图(包含离线地图瓦片下载制作)
  14. [英文邮件] 请求 + 感谢 + 邮件结尾 的表达整理
  15. 电力通信网与运营商5G网络融合方案
  16. 使用开源软件FFmpeg将各种格式视频转换成MP4视频格式(最简单方法)
  17. Sql中的offset 用法
  18. pr模板.mogrt格式安装方法
  19. Cocos2dx游戏教程(十五):“见缝插针”,愿你走出半生,归来仍是少年
  20. 修改seting里面的休眠时间列表

热门文章

  1. TensorFlow基础12-(keras.Sequential模型以及使用Sequential模型 实现手写数字识别)
  2. python笔记3(numpy数组)
  3. 【JVM】类加载器:双亲委派机制、沙箱安全机制
  4. 图像拼接--A multiresolution spline with application to image mosaics
  5. 人体姿态估计--RMPE: Regional Multi-Person Pose Estimation
  6. 人脸对齐--Face Alignment by Explicit Shape Regression
  7. 字符串位加密 php,PHP字符串加密增强版
  8. 物流信息管理系统MySQL设计,物流管理系统的SQL数据库设计(含代码)
  9. git服务器查看用户信息,git 查看当前git用户_新Git用户使用方法
  10. 服务器比普通电脑响应速度快吗,云服务器比普通的快吗