引言
该篇包含以下知识点:this,call和apply,以及bind。
Ⅰthis
——什么是this
与其他语言相比,函数的
this关键字在 JavaScript 中的表现略有不同。在绝大多数情况下,函数的调用方式决定了this的值(运行时绑定)。
this与其他语言行为的不同在于,this的指向在创建期间是无法绑定的,它的指向是在调用时才会绑定。另外还可以通过bind函数来设置this的值,且不考虑调用。
——this的调用位置
-
全局环境中
在全局执行环境中(在任何函数体外部)this都指向全局对象,在浏览器中window对象就是全局对象。
console.log(this); //window对象 this.sayHi = "Hello World"; console.log(sayHi); //"Hello World" console.log(window.sayHi); //"Hello World" -
函数中
在函数内部,且不启用严格模式的话,this指向全局对象。
function a(){ console.log(this); //window对象 } a();如果启用严格模式,this的值为undefined。
function a(){ "use strict" console.log(this); //undefined } a(); -
类中
在类中,this就是一个常规对象。当该类实例化时,该类构造函数中的this就会指向该实例化产生的对象。在类方法中,this指向的是该类本身。
class Person{ constructor(){ this.age = 21; }; getThis(){ console.log(this); } } let p = new Person(); console.log(p.age); //21 p.getThis(); //Person -
箭头函数中
在箭头函数中并没有this,如果要调用的话返回的是离它最近的作用域的this
let fun = ()=>{console.log(this)} //window对象 fun(); -
对象的方法中
在对象的方法中,this的指向调用方法的对象
var name = "mt" var obj = { name:"MT", getName(){ console.log(this.name); } } obj.getName(); //由于是obj调用,所以结果为"MT" var getName = obj.getName; getName(); //由于是全局调用,所以结果为"mt"
Ⅱapply和call
call方法和apply方法都会以指定的this值来调用函数,即会设置调用函数时函数体内this对象的值。
——apply方法
描述:
apply()方法调用一个具有给定this值的函数,以及以一个数组的形式提供的参数。
该方法接收两个参数:函数内this的值和一个参数数组。第二个参数可以是Array的实例,但也可以是函数中传递的argument对象。
function sum(num1,num2){
return num1 + num2;
}
function callSum1(num1,num2){
return sum.apply(this,[num1,num2]);
}
function callSum2(num1,num2){
return sum.apply(this,arguments);
}
callSum1(); //20
callSum2(); //20
作用:
-
将一个数组的各项添加到另一个数组中
var array = ['a','b']; var elements = [0,1,2]; arry.push.apply(array,elements); console.log(array);如果我们直接使用push方法的话,它会将该数组作为单个元素添加,而不是将这个数组内的每个元素添加进去,因此我们最终会得到一个数组内的数组。因此apply正派上用场。
-
使用apply和内置函数
var numbers = [5, 6, 2, 3, 7]; var max = Math.max.apply(null, numbers); var min = Math.min.apply(null, numbers); //以下为正常写法 max = -Infinity, min = +Infinity; for (var i = 0; i < numbers.length; i++) { if (numbers[i] > max) max = numbers[i]; if (numbers[i] < min) min = numbers[i]; }对于一些需要写循环以遍历数组各项的需求,我们可以用apply完成以避免循环。通过对比普通的循环写法,这种写法很高效,但是这种写法有超出JS引擎参数长度上限的风险。假如某个引擎的方法参数上限为4,在上面代码执行后,真正被传递到apply的参数为5,6,2,3,而不是完整的数组。
如果你的参数数组可能非常大,那么推荐使用下面这种混合策略,将数组切块后循环传入目标方法
function minOfArray(arr) { var min = Infinity; var QUANTUM = 32768; for (var i = 0, len = arr.length; i < len; i += QUANTUM) { var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len))); min = Math.min(submin, min); } return min; } var min = minOfArray([5, 6, 2, 3, 7]);
——call方法
描述:
call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
该方法接收的参数第一个和apply方法相同,即函数内this的值,而剩下的要传给被调用函数的参数则需要一个一个地列出来。
function sum(num1,num2){
return num1 + num2;
}
function callSum(num1,num2){
sum.call(this,num1,num2);
}
console.log(callSum(10,10)); //20
作用:
-
更改函数体内的this值
window.color = "red"; let o = { color:"blue" } function sayColor(){ console.log(this.color); } sayColor.call(this); //"red" sayColor.call(window); //"red" sayColor.call(o); //"blue"在这个例子中,可以看到,sayColor函数使用call方法更改this指向,从而返了回不同的结果。window调用sayColor打印了"red",o调用sayColor打印了"blue",由于全局作用域下的this指向为window,因此也打印了"red"
-
调用构造函数实现继承
function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5); var fun = new Toy('robot', 40);该例声明了一个Product构造函数,又由该函数派生了两个子构造函数。在这个过程中,每个子构造函数除了有自己的属性category外,还通过call方法继承了Product父构造函数中的两个属性——name,price
-
遍历NodeList
<body> <div></div> <div></div> <div></div> <script> let nodelist = document.querySelectorAll("div"); [].forEach.call(nodelist,(item,index){ item.innerHTML = index + "Hello World" }) </script> </body>使用document.querySelectorAll返回的并不是数组,而是NodeList,所以如果想使用数组的forEach,call方法就发挥作用了
Ⅲbind
描述:
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind方法和call方法一模一样,区别在于立即执行还是等待执行。
window.color = "red";
var o = {
color:"blue"
}
function sayColor(){
console.log(this.color);
}
let a = sayColor.bind(this);
let b = sayColor.bind(window);
let c = sayColor.bind(o);
a(); //"red"
B(); //"red"
C(); //"blue"
如上例所示,使用bind改变了sayColor函数内的this指针后,并没有直接调用,而是返回原函数的拷贝,该拷贝带有指定的this和参数。
作用:
-
创建绑定函数
this.x = 9; var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 81 var retrieveX = module.getX; retrieveX(); //9 var boundGetX = retrieveX.bind(module); boundGetX(); // 81在这个例子种,分别在全局和module对象中声明了变量x,当我们把module中的getX方法拿出来赋给全局变量retrieveX后,再调用这个变量时,this又会重新指会全局。这时我们通过bind用原始对象module绑定这个retrieve方法就可以将this重新指回原始对象。
-
配合setTimeout
function LateBloomer() { this.petalCount = Math.ceil(Math.random() * 12) + 1; } LateBloomer.prototype.declare = function() { console.log('I am a beautiful flower with ' + this.petalCount + ' petals!'); }; LateBloomer.prototype.bloom = function() { window.setTimeout(this.declare.bind(this), 1000); }; var flower = new LateBloomer(); flower.bloom();再默认情况下,使用window.setTimeout()时,this会指向全局对象,假设上例中setTimeout调用的方法没有使用bind改变this的话,declare方法中的this就会指向window,从而无法打印成员属性petalCount。
结语
这就是本篇所有内容了,下一篇将会复习JavaScript中的原型和继承