美文网首页
《JavaScript设计模式与开发实践》之this 、 cal

《JavaScript设计模式与开发实践》之this 、 cal

作者: 我是白夜 | 来源:发表于2017-06-27 18:31 被阅读0次

    this 、 call 和 apply

    this

    跟别的语言大相径庭的是,JavaScript的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

    this 的指向

    • 作为对象的方法调用。

      当函数作为对象的方法被调用时, this 指向该对象。

    var obj = {
      a: 1,
      getA: function() {
        console.log(this === obj); //true
        return this.a; //1
      }
    }
    
    console.log(obj.getA());
    
    • 作为普通函数调用。

      this 总是指向全局对象window。

    window.name = 'globalName';
    var getName = function(){
    return this.name;
    };
    console.log( getName() ); // 输出:globalName
    
    • 构造器调用。

      当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。

     var MyClass = function() {
       this.name = 'steven';
       return { // 显式地返回一个对象
         name: 'anne'
       }
     };
    var obj = new MyClass();
    console.log("使用构造器调用:" + obj.name); // 输出:anne
    
    • Function.prototype.call 或 Function.prototype.apply 调用。

      可以动态地改变传入函数的 this

    var obj1 = {
    name: 'steven',
    getName: function() {
    return this.name;
    }
    };
    
    var obj2 = {
    name: 'anne'
    }
    console.log(obj1.getName.call(obj2));// 输出:anne
    

    丢失的 this

    ​一个例子:使用getId变量来代替document.getElementById

    var getId = function(id){
      return document.getElementById(id);
    };
    console.log(getId('div1').id)
    

    ​ 如果getId直接 来引用 document.getElementById 之后,再调用getId ,此时就成了普通函数调用,函数内部的 this指向了window ,而不是原来的 document,会出现报错,情况如下:

     var getId = document.getElementById;
    console.log(getId('div1').id); //Uncaught TypeError: Illegal invocation (非法调用)
    

    ​ 我们可以尝试利用 apply 把 document 当作 this 传入 getId 函数,帮助“修正” this:

     var getId = (function(obj) {
       return function() {
         return obj.apply(document, arguments);
       }
     })(document.getElementById);
    console.log(getId('div1').id)
    

    call和apply

    call和apply 的区别

    Function.prototype.call 和 Function.prototype.apply 都是非常常用的方法。它们的作用一模一样,区别仅在于传入参数形式的不同

    • apply 接受两个参数:

      • 第一个参数指定了函数体内 this 对象的指向
      • 第二个参数为一个带下标的集合(数组或类数组), apply方法把这个集合中的元素作为参数传递给被调用的函数
      var func = function( a, b, c ){
      alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
      };
      func.apply( null, [ 1, 2, 3 ] );//1参为null,函数体内的 this 会指向默认的宿主对象,即window
      
    • call 传入的参数数量不固定

      • 第一个参数指定了函数体内 this 对象的指向
      • 从第二个参数开始往后,每个参数被依次传入函数
      var func = function( a, b, c ){
      alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
      };
      func.call( null, 1, 2, 3 );//1参为null,函数体内的 this 会指向默认的宿主对象,即window
      

      当调用一个函数时,JavaScript 的解释器并不会计较形参和实参在数量、类型以及顺序上的区别,JavaScript的参数在内部就是用一个数组来表示的。从这个意义上说, applycall的使用率更高,我们不必关心具体有多少参数被传入函数,只要用 apply 一股脑地推过去就可以了。

      当使用 call或者 apply的时候,如果我们传入的第一个参数为 null,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window ,但如果是在严格模式下,函数体内的 this还是为 null

      有时候我们使用 call或者 apply的目的不在于指定 this指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null来代替某个具体的对象。

    call 和 apply 的用途

    改变 this 指向

    var obj1 = {
      name: 'steven'
    };
    var obj2 = {
      name: 'anne'
    };
    window.name = 'window';
    var getName = function(name) {
      console.log(this.name);
    };
    getName(); //'window'
    getName.apply(obj1); //'steven'
    getName.apply(obj2); //'anne'
    

    在执行getName.apply(obj1)时, getName函数体内的 this就指向 obj1对象,实际相当于

    var getName = function(name) {
      console.log(obj1.name);
    };
    

    一个点击的实例场景:

    document.getElementById('div1').onclick = function() {
      var _that = this;  //需要额外申明一个中转变量来存储对象的this
      var foo = function() {
        alert(_that.id);
      }
      return foo();
    };
    

    在使用applycall后:

    document.getElementById('div1').onclick = function() {
      var foo = function() {
        alert(this.id);
      }
      foo.apply(this);
      return foo();
    };
    

    Function.prototype.bind

    大部分现代浏览器都实现了内置的 Function.prototype.bind用来指定函数内部的 this 指向,如果没有的话模拟起来不困难:

    Function.prototype.bind = function(context) {
       var self = this; //保存原函数
       return function() {
         // 这句代码才是执行原来的 func 函数,并且指定 context对象为 func 函数体内的 this ,也是我们想修正的 this 对象
         return self.apply(context, arguments);
       }
     }
    var obj = {
       name: 'steven'
     };
    var func = function() {
      alert(this.name); //输出'steven'
    }.bind(obj);
    
    func();
    

    再稍微复杂一些,使得可以往func函数内预先填写一些参数

    Function.prototype.bind = function(){
    var self = this, // 保存原函数
    context = [].shift.call( arguments ), // 需要绑定的 this 上下文
    args = [].slice.call( arguments ); // 剩余的参数转成数组
    return function(){ // 返回一个新的函数
    return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
    // 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
    // 并且组合两次分别传入的参数,作为新函数的参数
    }
    };
    var obj = {
    name: 'sven'
    };
    var func = function( a, b, c, d ){
    alert ( this.name ); // 输出:sven
    alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
    }.bind( obj, 1, 2 );
    func( 3, 4 );
    

    借用其他对象的方法

    • 场景一:鸠占鹊巢
     var A = function(name) {
       this.name = name;
     };
    
    var B = function() {
      A.apply(this, arguments);
    };
    
    B.prototype.getName = function() {
      return this.name;
    }
    
    var b = new B('steven');
    console.log(b.getName());
    
    • 场景二:arguments中添加一个新的元素,通常会借用Array.prototype.push
    (function(){
      Array.prototype.push.call(arguments,3);
      console.log(arguments);
    })(1,2,5,c)
    

    在操作 arguments的时候,我们经常非常频繁地找Array.prototype对象借用方法,例如:

    • 想把 arguments 转成真正的数组的时候,可以借用 Array.prototype.slice 方法。
    • 想截去arguments 列表中的头一个元素时,可以借用Array.prototype.shift方法。

    我们甚至可以把“任意”对象传入 Array.prototype.push,但是对象要满足以下两个条件:

    1. 对象本身要可以存取属性
    2. 对象的 length 属性可读写

    相关文章

      网友评论

          本文标题:《JavaScript设计模式与开发实践》之this 、 cal

          本文链接:https://www.haomeiwen.com/subject/xlgecxtx.html