面试复习计划JavaScript篇二

257 阅读5分钟

引言

该篇包含以下知识点: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中的原型和继承