![cb74d0c505b5bc4c6c3db673c37b597c.png](https://img-blog.csdnimg.cn/img_convert/cb74d0c505b5bc4c6c3db673c37b597c.png)
浅谈函数上下文调用模式
修改JavaScript中的this指向就是本期的重点。要实现这个需求,就要使用函数上下文调用模式,接下来就和大家共同探讨下函数上下文调用模式 -- call、apply、bind。
一、我们先从语法上了解下这三种方法。
1.1: call();
语法:函数名.call(期望函数内部this指向谁, 参数1,参数2....);
![9b36653500e639e35e3788b7424dbd47.png](https://img-blog.csdnimg.cn/img_convert/9b36653500e639e35e3788b7424dbd47.png)
第14行普通方式调用getSum()函数,this指向window;
第15行用call()的方式调用getSum()函数, 第1个参数传入”期望getSum函数中的this指向”,后面的参数依次传入getSum函数的参数即可。此时getSum函数一样被执行,不同的是getSum函数中的this就指向了obj对象,同时也把100,200分别赋值给形参a和b. 执行结果如下:
![4792d6c21dd7e634d47b7e1d0c6050ec.png](https://img-blog.csdnimg.cn/img_convert/4792d6c21dd7e634d47b7e1d0c6050ec.png)
这样我们就轻松实现了修改函数内部this指向.
1.2:apply();
语法:函数名.apply(this的新指向 , 数组或者伪数组);
注意apply()和call()的区别在于apply()只有2个参数,第一个参数是this的新指向,第二个参数是数组或者伪数组,调用的时候会把第二个参数(数组或伪数组)的元素依次的赋值给被调用函数的形参。
![ecaf784fbd1f2e517f7fdcc8d469f129.png](https://img-blog.csdnimg.cn/img_convert/ecaf784fbd1f2e517f7fdcc8d469f129.png)
第24行普通方式调用getSum()函数,this指向window;
第25行用apply()的方式调用getSum()函数,此时getSum()函数中的this就指向了obj对象,同时把数组的元素100,200,300依次赋值给getSum()函数的形参a,b,c ; 执行结果如下:
![ae7c19a78fa400aa92649d338b3c3610.png](https://img-blog.csdnimg.cn/img_convert/ae7c19a78fa400aa92649d338b3c3610.png)
1.3:bind();
语法:函数名.bind( this的新指向 ,可以写参数也可以不写参数 );
需要注意的是函数用bind()的方式调用并不会执行该函数,而是会返回一个函数体一模一样但是修改了this指向后的函数。
![db3575e799bc3992e1e5c158e59f394f.png](https://img-blog.csdnimg.cn/img_convert/db3575e799bc3992e1e5c158e59f394f.png)
第34行普通方式调用getSum()函数,this指向window;
第36行虽然用bind()的方式调用getSum()函数,但是此时并不会执行getSum()函数,而是会返回一个和getSum()函数的函数体一模一样但是this已经修改成obj对象的函数了,这个函数被fn变量接收。 执行结果如下:(只有一次函数体被执行,就是34行的调用)。
![302e8206d1b8f6d80f298931f7c284a8.png](https://img-blog.csdnimg.cn/img_convert/302e8206d1b8f6d80f298931f7c284a8.png)
此时如果调用fn()函数,就相当于执行getSum()函数,但是getSum函数中的this已经被修改成了obj对象,调用代码和执行结果如下:
![915da9f3bcb26f1318732f02bcef10e9.png](https://img-blog.csdnimg.cn/img_convert/915da9f3bcb26f1318732f02bcef10e9.png)
![ce4a3566b703f71218657df883641487.png](https://img-blog.csdnimg.cn/img_convert/ce4a3566b703f71218657df883641487.png)
前面介绍bind()的语法说除了this的新指向,参数可以写也可以不写,所以36行38行代码也可以写成如下这样:
![27e73d0c07554da20a61a7fdfbee8786.png](https://img-blog.csdnimg.cn/img_convert/27e73d0c07554da20a61a7fdfbee8786.png)
执行结果一样,如下:
![e0d50fb0b749a07d0a08fe233436124b.png](https://img-blog.csdnimg.cn/img_convert/e0d50fb0b749a07d0a08fe233436124b.png)
二、上下文调用模式注意细节:
2.1 大家都知道javascript中的函数(普通函数、构造函数)本质上是一个对象,是由Function()构造函数实例化出来的对象,而call、apply、bind这三个方法都是定义在Function.prototype原型中的,那么意味着javascript中的所有函数都可以点出这三个方法来。
![c5598583a46748f2da98aa8745151c34.png](https://img-blog.csdnimg.cn/img_convert/c5598583a46748f2da98aa8745151c34.png)
2.2 如果使用函数上下文模式调用函数,第一个参数不是指向一个对象,而是指向一个基本数据类型的值,那么函数中this的指向又该指向谁呢?代码如下:
![ded25c4f2a1cffd2c12ffc3eb8513754.png](https://img-blog.csdnimg.cn/img_convert/ded25c4f2a1cffd2c12ffc3eb8513754.png)
52行53行54行,他们使用call()的方式调用foo()函数,this分别指向Number包装类型对象、String包装类型对象和Boolean包装类型对象;
55行,56行,57行,58行都指向window对象。 执行结果如下: (当然前提是非严格模式下,严格模式下修改this为null或者undefined都不允许指向window,关于严格模式另起篇章再究)。
![39ecf5cd98d545d92a43c0e6c3366097.png](https://img-blog.csdnimg.cn/img_convert/39ecf5cd98d545d92a43c0e6c3366097.png)
2.3 哪个函数使用上下文模式调用,修改的this就是哪个函数的,代码如下:
![355d1a98da326fe36a0af5ee661f6896.png](https://img-blog.csdnimg.cn/img_convert/355d1a98da326fe36a0af5ee661f6896.png)
上述这道题不要以为第73行testOne函数被上下文模式调用,所以在testOne函数的函数体中调用testTwo函数,他的this也指向obj对象,不是的。哪个函数被上下文模式调用,那哪个函数的this才会发生改变。
testOne函数被上下文模式调用,所以testOne函数的this指向obj对象; 而在testOne函数的函数体中第67行调用testTwo函数还是普通调用,所以testTwo函数的this还是window对象。执行结果如下:
![a2437384043f2554a455327feb6be432.png](https://img-blog.csdnimg.cn/img_convert/a2437384043f2554a455327feb6be432.png)
- 函数上下文调用模式的使用场景究竟
3.1 元素都是整数的数组求最大值。
![ed1712be64a55376b79c796a579b1f82.png](https://img-blog.csdnimg.cn/img_convert/ed1712be64a55376b79c796a579b1f82.png)
如果要求出一个整数数组中的最大值,传统的做法是遍历数组,元素两两比较,最后得到最大值,但是这样较繁琐,所以我们联想到js中的Math对象提供的max()方法可以求一堆数中的最大值,所以我们就用apply的方式调用max()方法,不修改它this的指向,只是利用apply()方法的语法特点把arr数组的元素依次的交给Max()方法,这样就能得到arr数组中的最大值。执行结果如下:
![64158d7dd7a9878df3191878edd704c5.png](https://img-blog.csdnimg.cn/img_convert/64158d7dd7a9878df3191878edd704c5.png)
3.2 伪数组转换成真数组
![a18b60719bd981a16442ebbe2a1610f0.png](https://img-blog.csdnimg.cn/img_convert/a18b60719bd981a16442ebbe2a1610f0.png)
原始的做法需要遍历这个伪数组weiArr,然后把元素一个一个的往声明的真数组arr中添加,需要用到遍历所以较复杂。执行结果如下:
![5be3657c49bd4c5efa7c4ed1293395d6.png](https://img-blog.csdnimg.cn/img_convert/5be3657c49bd4c5efa7c4ed1293395d6.png)
用函数上下文调用模式来处理该问题就较容易:
![68940fa395234a8219fc421cc26a411c.png](https://img-blog.csdnimg.cn/img_convert/68940fa395234a8219fc421cc26a411c.png)
做法1利用apply()方法的特性把weiArr这个伪数组中的元素依次的交给push方法,减去了遍历这个伪数组步骤。执行结果如下:
![b122562a695837e50f04b5f26a99f881.png](https://img-blog.csdnimg.cn/img_convert/b122562a695837e50f04b5f26a99f881.png)
![c5f07c8953d83a2b689a78627b0172b5.png](https://img-blog.csdnimg.cn/img_convert/c5f07c8953d83a2b689a78627b0172b5.png)
做法2使用call的方式调用slice方法,修改slice方法中的this为weiArr这个伪数组, 大家在上一篇this的指向中都知道谁调用方法,方法中的this就是谁, 那现在slice方法中的this被修改成了weiArr这个伪数组,那给人的感觉就是weiArr这个伪数组在调用这个方法。(有同学可能会问那为什么weiArr不直接调用slice方法呢?原因是因为他是伪数组,不能直接点出数组的方法来。) 而slice这个方法就是用来做截取的,题中只给一个参数0,意味着从第0个元素开始截取一直到末尾。 所以arr2就是一个拥有和伪数组相同元素的真数组。 执行结果如下:
![ce48bfaac692c321ea6e69aa2b59ec66.png](https://img-blog.csdnimg.cn/img_convert/ce48bfaac692c321ea6e69aa2b59ec66.png)
![5b3efaccf4342130e0ba59302474664f.png](https://img-blog.csdnimg.cn/img_convert/5b3efaccf4342130e0ba59302474664f.png)
做法3利用的是数组的concat方法,concat方法允许数组arr3连接一个个的数值作为他的新元素,所以这里用apply的方式调用concat,就是为了把weiArr这个伪数组里面的元素一个个的交给concat函数。执行结果如下:
![9fcde603b67b3875db9377ff0d6d2990.png](https://img-blog.csdnimg.cn/img_convert/9fcde603b67b3875db9377ff0d6d2990.png)
3.3 借用构造函数继承。
![b9965e2fad55a20a5f5527d95d61fa8f.png](https://img-blog.csdnimg.cn/img_convert/b9965e2fad55a20a5f5527d95d61fa8f.png)
在Student构造函数里面,121行(注释)、122行(注释)、123行都可以借用Person构造函数中的赋值代码给自己实例化出来的对象赋值。 执行结果如下:
![e020db590087e7b5d80ccf0b4b3e5efa.png](https://img-blog.csdnimg.cn/img_convert/e020db590087e7b5d80ccf0b4b3e5efa.png)
3.4 检测数据类型。
![45c59322b9f3b70a698e228451bbfefc.png](https://img-blog.csdnimg.cn/img_convert/45c59322b9f3b70a698e228451bbfefc.png)
这里都是在借用Object.prototype原型中的toString方法,而这个toString方法明确规定了返回的结果是:”[object type]”,其中type是数据的类型。所以用这种方式可以检测所有数据的数据类型。 执行结果如下:
![d818e124e51a2b020f5a9f41b30e6cea.png](https://img-blog.csdnimg.cn/img_convert/d818e124e51a2b020f5a9f41b30e6cea.png)
好啦,本期干货就全部交给大家了,大家要不要动动小手自己试一下呢?
下一期预告: 《带你揭开BFC的面纱!》