美文网首页
彻底深刻理解js之this

彻底深刻理解js之this

作者: 幸宇 | 来源:发表于2018-07-14 11:11 被阅读5次

    http://0313.name/archives/77#more-77
    1.this的指向是在函数被调用的时候确定的;
    2.在函数执行过程中不可修改this的指向;

    var a = 10;
    var obj = {
        a: 20
    }
    
    function fn () {
        this = obj; // 这句话试图修改this,运行后会报错
        console.log(this.a);
    }
    
    fn();
    

    两种情况的this指向
    1.全局对象中的this
    全局环境中的this指向它本身

    // 通过this绑定到全局对象
    this.a2 = 20;
    
    // 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
    var a1 = 10;
    
    // 仅仅只有赋值操作,标识符会隐式绑定到全局对象
    a3 = 30;
    
    // 输出结果会全部符合预期
    console.log(a1);
    console.log(a2);
    console.log(a3);
    

    2.函数中的this
    结论:在一个函数上下文中,this由调用者提供,由调用函数的方式来决定,

    如果调用者函数在一个对象中,被某个对象所拥有,那么该函数在调用时,内部的this指向该对象;

    但若函数独立调用,在严格模式下use strict,该this指向undefined;非严格模式下自动指向全局对象;

    //demo1
    var a=20;
    function fn(){
    console.log(this.a)
    }
    fn();//20  调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
    
    //demo2
    var a=20;
    function fn(){
      function foo(){
      console.log(this.a)
    }
    foo();
    }
    fn()  //20  调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
    
    //demo3
    var a=20;
    var obj={
      a:10,
      c:this.a+20,
      fn:function(){
        return this.a;
      }
    }
    console.log(obj.c);   //40
    console.log(obj.fn())  //10
    

    这个地方需要注意,当调用者不是一个函数时,无论在什么地方调用,这里的this都指向全局对象,所以obj.c输出为40,而obj.fn()在对象中,被对象所拥有,内部的this就指向该对象;

    若把obj放在函数中返回,则遵循以上结论,当obj在函数环境中声明时,这个this在严格模式下指向undefined,非严格模式下自动转向全局对象,可运行如下例子查看区别:

       var a=20;
            function foo(){
                var obj={
                    a:10,
                    c:this.a+20,
                    fn:function(){
                        return this.a;
                    }
                }            
                return obj.c;  //40
                // return obj.fn;  10
            }
    
            console.log(foo()) //40
    

    如下加深理解:

    var a=20;
    var foo={
      a:10,
    getA:function(){
      return this.a;
    }
    }
    console.log(foo.getA())  //10
    var test = foo.getA;
    console.log(test());//20 test()作为调用则尽管与foo.getA的引用相同,但它是独立调用,非严格模式下自动指向全局;
    

    修改

    var a=20;
    function getA(){
      return this.a;
    }
    var foo={
      a:10,
    getA:getA
    }
    console.log(foo.getA()); //10
    

    独立调用:

           function foo(){
                console.log(this.a)
            }
            function active(fn){
                fn();  //真实调用者,为独立调用
            }
            var a=20;
            var obj={
                a:10,
                getA:foo
            }
            active(obj.getA)  //20
    

    手动设置this指向的问题:

    使用call,apply显示指定this
    JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。

    如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。

    function fn(){
      console.log(this.a);
    }
    var obj={
      a:10
    }
    fn.call(obj)//10
    

    而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。

    function fn(num1,num2){
      console.log(this.a+num1+num2);
    }
    var obj={
      a:10
    }
    fn.call(obj,100,100) //210
    fn.apply(obj,[20,20]) //50
    

    call()和apply将类数组转成数组

    function exam(a,b,c,d){
      
      var arg=[].slice.call(arguments);
    console.log(arg);
    }
    exam(1,3,4,5) // [1,3,4,5]
    //也常常使用该方法将DOM中的nodelist转换为数组
    // [].slice.call( document.getElementsByTagName('li') );
    

    根据自己的需要灵活修改this指向

      var foo={
        name:'zx',
    showname:function(){
      console.log(this.name)
    }
    }
    var fn={
    name:'zqq'
    }
    foo.showname.call(fn)  //zqq
    

    实现继承

     var person={
          init:function(name,age){
              this.name=name;
              this.age=age;
              this.gender=['man','woman']
          }
      }
    
    
    var student={
      init:function(name,age,hight){
        person.call(this,name,age);
        this.hight=hight;
     }
    }
    
    student.prototype.message=function(){
        console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';')
    }
    
    new student.init('xiaom', 12, '150cm').message()
    

    简单给有面向对象基础的朋友解释一下。在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了基层。Student的构造函数等同于下。

    var Student = function(name, age, high) {
        this.name = name;
        this.age  = age;
        this.gender = ['man', 'woman'];
        // Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承
        this.high = high;
    }
    

    在向其他执行上下文的传递中,确保this的指向保持不变
    如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。

    var obj = {
        a: 20,
        getA: function() {
            setTimeout(function() {
                console.log(this.a)
            }, 1000)
        }
    }
    
    obj.getA();
    

    常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。

    var obj = {
        a: 20,
        getA: function() {
            var self = this;
            setTimeout(function() {
                console.log(self.a)
            }, 1000)
        }
    }
    

    另外就是借助闭包与apply方法,封装一个bind方法。

    function bind(fn, obj) {
        return function() {
            return fn.apply(obj, arguments);
        }
    }
    
    var obj = {
        a: 20,
        getA: function() {
            setTimeout(bind(function() {
                console.log(this.a)
            }, this), 1000)
        }
    }
    
    obj.getA();
    

    当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。

    var obj = {
        a: 20,
        getA: function() {
            setTimeout(function() {
                console.log(this.a)
            }.bind(this), 1000)
        }
    }
    

    相关文章

      网友评论

          本文标题:彻底深刻理解js之this

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