一. 前言
不知道大家还记不记得前几篇的文章:《面试官: 能解释一下 JavaScript 中的 this 吗》
那今天这篇文章虽然是介绍 JavaScript 中 bind,apply 和 call 函数, 但是多少也和 this 有点关联.
假如在前面那场面试末尾, 面试官不依不饶继续问你 JavaScript 中的 this, 那看完本篇文章后一定还会有收获.
(本篇文章不会站在 this 的角度去回答问题, 而是重于解释 bind,apply 和 call 这三个函数的用法和使用场景)
二. 正戏开始
面试官: 能解释一下 JavaScript 中 bind,apply 和 call 这三个函数的用法吗?
我:(这三个函数我也只是了解它们的用法, 仅此而已)
我: 这三个函数都是用于改变函数运行时内部 this 的指向的, 只是每个函数的用法不一样.
面试官: 那你分别说一下具体都怎么用吧.
我:(接着我边回忆之前做过的小练习边回答面试官)
(以下描述和回答均基于浏览环境)
首先是一个很简单的示例
- var objMM = {
- name: 'MM',
- age: 18,
- getPresonInfo: function(addr){
- console.log(this.name + "年龄" + this.age + "地址:" + addr);
- }
- };
- var objZZ = {
- name: 'ZZ',
- age: 28,
- getPresonInfo: function(addr){
- console.log(this.name + "年龄" + this.age + "地址:" + addr);
- }
- }
- objMM.getPresonInfo('上海');
- objZZ.getPresonInfo('深圳');
18 行和 20 行的打印信息分别为:
bind 方法
首先是 bind 方法, 它的基本语法为 targetFunction.bind(thisArg,arg1,arg2,...).
第一个参数 thisArg 会作为目标函数 targetFunction 运行时的 this 值传递给目标函数.
后面的参数列表 arg1,arg2,... 是传递给目标函数的参数.
bind 方法的返回值是一个目标函数的一个拷贝.
这个拷贝出来的函数运行时 this 指向的就是调用 bind 传递的 thisArg 参数.
并且拷贝函数还拥有调用 bind 时传递的 arg1,arg2,... 多个参数.
感觉这段描述把我自己都说晕了.
所以如果觉得语言描述不清楚, 就写一个简单的用法示例:
- var copyF = objMM.getPresonInfo.bind(objZZ,'远方');
- copyF();
这个示例是要在前面第一个示例的基础上运行的.
在结合前面那段晦涩难懂的文字描述, 可以这样理解这两行代码:
copyF 为 objMM.getPresonInfo 函数的一个拷贝.
copyF 运行时内部的 this 指向 objZZ;
copy 函数拥有一个参数'远方'
这样调用 copy 函数的结果就很显而易见了.
apply 方法
apply 方法的基本语法为 targetFunction.apply(thisArg,[arg1,arg2]).
第一个参数的作用同 bind 方法.
第二个参数的作用也是和 bind 方法相同, 只是将参数列表变为数组的形式进行传递.
apply 方法的返回值和 bind 方法就完全不同了, 它会直接调用并执行目标函数.
那话不多说, 在写一个示例
objMM.getPresonInfo.bind(objZZ,['你管我在哪']);
打印结果:
call 方法
call 方法的基本语法为 targetFunction.call(thisArg,arg1,arg2,...).
第一个参数的作用同 bind 方法, 也同 apply 方法.
第二个参数的作用也是和 bind,apply 相同, 只是形式同 bind 方法是参数列表形式.
call 方法的返回值同 apply 方法, 也是直接调用并执行目标函数.
objMM.getPresonInfo.call(objZZ,'我爱在哪在哪');
打印结果:
面试官: 那这些函数你平时用过吗, 具体有什么使用场景.
我:(这下惨了, 平时还真没咋用过, 如实回答) 平时在写代码的时候, 基本没咋用过.
面试官: 那好吧
我:(凉凉)......
三. 自我反思
回家后深刻进行了自我反思: 平时好像还真的没有使用过这个三个函数呀, 不过没关系, 现在学还来得及.
于是我开始各种搜罗, 然后依旧个人理解, 将其分为两种使用场景.
(怎么分类不重要, 后面的示例才重要)
1. 使用场景一: 借用函数
大概意思就是借用现有方法去自己的需求.
在文章开始的第一个示例的基础上, 稍作一下修改
- var objMM = {
- name: 'MM',
- age: 18,
- getPresonInfo: function(addr){
- console.log(this.name + "年龄" + this.age + "地址:" + addr);
- }
- };
- var objZZ = {
- name: 'ZZ',
- age: 28
- }
这个代码中, objZZ 已经没有 getPersonInfo 这个方法了, 假如我们想像 objMM 那样去打印对象自身的信息怎么办呢?
此时这三个函数就能派上用场了.
- objMM.getPresonInfo.bind(objZZ,'我爱在哪在哪')(); // ZZ 年龄 28 地址: 我爱在哪在哪
- objMM.getPresonInfo.apply(objZZ,['我爱在哪在哪']); // ZZ 年龄 28 地址: 我爱在哪在哪
- objMM.getPresonInfo.call(objZZ,'我爱在哪在哪'); // ZZ 年龄 28 地址: 我爱在哪在哪
在就是 JavaScript 里面有很多工具对象 (我自己这样叫), 比如 Math.
Math 类有两个函数 max 和 min, 一般情况下依照这两个函数的语法只能这样使用:
- var maxNum = Math.max(23,197,88,35,109,11);
- console.log(maxNum); // 197
- var minNum = Math.min(23,197,88,35,109,11);
- console.log(minNum); //11
假设现在代码里面有一个数组变量要求出最大最小值, 我们又不想自己去实现.
那我们就只能借助 Math 提供的 max 和 min 方法, 使用 apply 函数去实现这个功能.
- var arr = [23,197,88,35,109,11];
- var maxNum = Math.max.apply(Math,arr);
- console.log(maxNum); // 197
- var minNum = Math.min.apply(Math,arr);
- console.log(minNum); //11
除了 Math 类之外, 数组也有很多 API, 比如最常见的 forEach.
这个方法也只能是数组类型的变量才能使用, 那非数组类型的变量要使用怎么办呢?
- var divCollections = document.getElementsByTagName('html');
- Array.prototype.forEach.bind(divCollections,function(item){
- console.log(item);
- })();
- Array.prototype.forEach.apply(divCollections,[function(item){
- console.log(item);
- }]);
- Array.prototype.forEach.call(divCollections,function(item){
- console.log(item);
- });
借用函数的最后一个使用场景就是数据类型判断.
我们知道 JavaScript 中使用 typeof 可以判断一个变量的类型, 但是仅限于基础的类型.
比如: number,string,boolean,undefined 类型.
其他类型的例如: array,object,null 使用 typeof 判断类型打印均为 "object"
所以我们可以借助 Object 对象提供的一个函数, 准确的知道一个数据的类型.
- Object.prototype.toString.call(1); // "[Object Number]"
- Object.prototype.toString.call('1'); // "[Object String]"
- Object.prototype.toString.call(true); // "[Object Boolean]"
- Object.prototype.toString.call(undefined); // "[Object Undefined]"
- Object.prototype.toString.call([]); // "[Object Array]"
- Object.prototype.toString.call({}); // "[Object Object]"
- Object.prototype.toString.call(null); // "[Object Null]"
2. 使用场景二: 实现继承
我们都知道, JavaScript 中最简单的继承代码是通过将子类原型指向父类实例实现的.
- function Father(name,age){
- this.name = name;
- this.age = age;
- this.sayInfo = function(){
- console.log(this.name + "年龄:"+ this.age);
- }
- }
- function Son(name,age){
- this.name = name;
- this.age = age;
- }
- // 将子类原型指向父类实例
- Son.prototype = new Father('我是你爸爸',);
- var s = new Son('Son',1)
- s.sayInfo(); // 打印: Son 年龄: 1
那我们可以借助这三个函数实现 JavaScript 中的继承.
- (这里只写 call 方法的实现)
- function Father(name,age){
- this.name = name;
- this.age = age;
- this.sayInfo = function(){
- console.log(this.name + "年龄:"+ this.age);
- }
- }
- function Son(name,age){
- Father.call(this,name,age)
- }
- var s = new Son('Son',1)
- s.sayInfo(); // 打印: Son 年龄: 1
可以看到使用 call 实现继承时, 只需要在子类 Son 中调用父类的构造函数, 并且按照 call 函数的语法传入所需参数即可.
后面直接使用 Son 的实例就能调用 sayInfo 函数.
这种方式说来有点意思, 因为前面 es5 语法的继承是子类原型指向父类实例, 也就是通过原型链实现的.
而这种方式的原理又是什么呢?
好奇心驱使, 我分别打印了前面 es5 中原型链实现继承后创建的实例 s 和使用 call 实现继承后创建的实例 s
从结果可以看到, 使用 call 实现继承, 实例化后的对象 s 本身已经拥有了 sayInfo 方法.
所以说原型链式的继承和 call 实现的继承还是有本质的区别的.
那到底里, 关于 bind,apply,call 函数的使用场景就整理完了, 下次遇到面试官问应该就不虚了.
(使用场景有可能不全, 欢迎大家补充)
四. 总结
本篇到此就基本结束了, 结合前一篇关于 this 的文章, JavaScript 中的 this 基本就没啥大问题了.
当然实际的项目千变万化, 还是需要谨慎使用 this.
介于本篇文章主要还是解释 JavaScript 中 bind,apply 和 call 函数的用法, 因此后续会在补一篇总结《使用原生 JavaScript 实现 bind,apply 和 call 函数》.
最近作者新开通了一个微信公众号.
微信公众号会分享一些自己日常的东西, 包括个人总结呀, 吸猫日常呀, 同时也会分享一些博客上的前端技术文章.
来源: https://www.cnblogs.com/HouJiao/p/12283748.html