通过上一期的《JavaScript中的this到底指向谁?》,我们知道了在默认情况下this的指向,在文末也提到了我们是可以通过种种手段来修改this 的默认指向,很明显啦,修改 JavaScript 中的 this 指向就是本期的重点。要实现这个需求,就要使用函数上下文调用模式,接下来就和大家共同探讨下函数上下文调用模式 -- call、 apply 、 bind 。
语法:函数名 .call( 期望函数内部 this 指向谁 , 参数 1, 参数 2....); 第 14 行普通方式调用 getSum() 函数, this 指向 window; 第 15 行用 call() 的方式调用 getSum() 函数 , 第 1 个参数传入 ”期望getSum 函数中的 this 指向 ”,后面的参数依次传入getSum函数的参数即可。此时getSum函数一样被执行,不同的是getSum 函数中的 this 就指向了 obj 对象,同时也把 100,200 分别赋值给形参 a 和 b. 执行结果如下: 这样我们就轻松实现了修改函数内部this指向.
语法:函数名 .apply(this 的新指向 , 数组或者伪数组 ); 注意 apply() 和 call() 的区别在于 apply() 只有 2 个参数,第一个参数是 this 的新指向,第二个参数是数组或者伪数组,调用的时候会把第二个参数(数组或伪数组)的元素依次的赋值给被调用函数的形参。 第 24 行普通方式调用 getSum() 函数, this 指向 window; 第 25 行用 apply() 的方式调用 getSum() 函数,此时 getSum() 函数中的 this 就指向了 obj 对象,同时把数组的元素 100,200,300 依次赋值给 getSum() 函数的形参 a,b,c ; 执行结果如下:
语法:函数名 .bind( this 的新指向 ,可以写参数也可以不写参数 ); 需要注意的是函数用 bind() 的方式调用并不会执行该函数,而是会返回一个函数体一模一样但是修改了 this 指向后的函数。 第 34 行普通方式调用 getSum() 函数, this 指向 window; 第 36 行虽然用 bind() 的方式调用 getSum() 函数,但是此时并不会执行 getSum() 函数,而是会返回一个和 getSum() 函数的函数体一模一样但是 this 已经修改成 obj 对象的函数了,这个函数被 fn 变量接收。 执行结果如下:(只有一次函数体被执行,就是 34 行的调用)。 此时如果调用 fn() 函数,就相当于执行 getSum() 函数,但是 getSum 函数中的 this 已经被修改成了 obj 对象,调用代码和执行结果如下: 前面介绍 bind() 的语法说除了 this 的新指向,参数可以写也可以不写,所以 36 行 38 行代码也可以写成如下这样: 执行结果一样,如下:
2.1 大家都知道 javascript 中的函数(普通函数、构造函数)本质上是一个对象,是由 Function() 构造函数实例化出来的对象,而 call 、 apply 、 bind 这三个方法都是定义在 Function.prototype 原型中的,那么意味着 javascript 中的所有函数都可以点出这三个方法来。
2.2 如果使用函数上下文模式调用函数,第一个参数不是指向一个对象,而是指向一个基本数据类型的值,那么函数中 this 的指向又该指向谁呢?代码如下: 52 行 53 行 54 行,他们使用 call() 的方式调用 foo() 函数, this 分别指向 Number 包装类型对象、 String 包装类型对象和 Boolean 包装类型对象; 55 行, 56 行, 57 行, 58 行都指向 window 对象。 执行结果如下: (当然前提是非严格模式下,严格模式下修改 this 为 null 或者 undefined 都不允许指向 window ,关于严格模式另起篇章再究)。
2.3 哪个函数使用上下文模式调用,修改的 this 就是哪个函数的,代码如下: 上述这道题不要以为第 73 行 testOne 函数被上下文模式调用,所以在 testOne 函数的函数体中调用 testTwo 函数,他的 this 也指向 obj 对象,不是的。哪个函数被上下文模式调用,那哪个函数的 this 才会发生改变。 testOne 函数被上下文模式调用,所以 testOne 函数的 this 指向 obj 对象; 而在 testOne 函数的函数体中第 67 行调用 testTwo 函数还是普通调用,所以 testTwo 函数的 this 还是 window 对象。执行结果如下:
如果要求出一个整数数组中的最大值,传统的做法是遍历数组,元素两两比较,最后得到最大值,但是这样较繁琐,所以我们联想到 js 中的 Math 对象提供的 max() 方法可以求一堆数中的最大值,所以我们就用 apply 的方式调用 max() 方法,不修改它 this 的指向,只是利用 apply() 方法的语法特点把 arr 数组的元素依次的交给 Max() 方法,这样就能得到 arr 数组中的最大值。执行结果如下:
原始的做法需要遍历这个伪数组 weiArr ,然后把元素一个一个的往声明的真数组 arr 中添加,需要用到遍历所以较复杂。执行结果如下: 用函数上下文调用模式来处理该问题就较容易: 做法 1 利用 apply() 方法的特性把 weiArr 这个伪数组中的元素依次的交给 push 方法,减去了遍历这个伪数组步骤。执行结果如下: 做法 2 使用 call 的方式调用 slice 方法,修改 slice 方法中的 this 为 weiArr 这个伪数组, 大家在上一篇 this 的指向中都知道谁调用方法,方法中的 this 就是谁, 那现在 slice 方法中的 this 被修改成了 weiArr 这个伪数组,那给人的感觉就是 weiArr 这个伪数组在调用这个方法。(有同学可能会问那为什么 weiArr 不直接调用 slice 方法呢?原因是因为他是伪数组,不能直接点出数组的方法来。) 而 slice 这个方法就是用来做截取的,题中只给一个参数 0 ,意味着从第 0 个元素开始截取一直到末尾。 所以 arr2 就是一个拥有和伪数组相同元素的真数组。 执行结果如下:
做法 3 利用的是数组的 concat 方法, concat 方法允许数组 arr3 连接一个个的数值作为他的新元素,所以这里用 apply 的方式调用 concat ,就是为了把 weiArr 这个伪数组里面的元素一个个的交给 concat 函数。执行结果如下:
在 Student 构造函数里面, 121 行(注释)、 122 行(注释)、 123 行都可以借用 Person 构造函数中的赋值代码给自己实例化出来的对象赋值。 执行结果如下:
这里都是在借用 Object.prototype 原型中的 toString 方法,而这个 toString 方法明确规定了返回的结果是: ”[object type]” ,其中 type 是数据的类型。所以用这种方式可以检测所有数据的数据类型。 执行结果如下:
好啦,本期干货就全部交给大家了,大家要不要动动小手自己试一下呢?
|