• this的原理以及几种使用场景


    一、问题由来:

    var obj = {
      foo: function () { console.log(this.bar) },
      bar: 1
    };
    var foo = obj.foo;
    var bar = 2;
    // 写法一
    obj.foo() // 1
    // 写法二
    foo() // 2

      上面代码中,虽然obj.foo和foo指向同一个函数,但是执行结果却不一样。

      这种差异的原因就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

      那么为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

      本文就来解释 JavaScript 这样处理的原理。理解了这一点,你就会彻底理解this的作用。

    二、内存的数据结构

      JavaScript之所以有this的设计,跟内存里的数据结构有关系。

    var obj = { foo:  5 };

      上面的代码将一个对象赋值给变量obj。JavaScript引擎会现在内存里面生成一个对象 { foo: 5 },然后把这个对象的内存地址赋值给变量obj

      

      也就是说obj是一个地址。后面如果要读取 obj.foo,引擎先从obj拿到内存地址,然后从改地址读出原始对象,返回它的foo属性。

      原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的

      

    {
      foo: {
        [[value]]: 5
        [[writable]]: true
        [[enumerable]]: true
        [[configurable]]: true
      }
    }

      注意,foo属性的值保存在属性描述对象的value属性里面。

    三、函数

      这样的结构是很清晰的,问题在于属性的值可能是一个函数。

    var obj = { foo: function () {} };

      这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

      

    {
      foo: {
        [[value]]: 函数的地址
        ...
      }
    }

      由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

    var f = function () {};
    var obj = { f: f };
    // 单独执行
    f()
    // obj 环境执行
    obj.f()

    四、环境变量

      JavaScript 允许在函数体内部,引用当前环境的其他变量

    var f = function () {
      console.log(x);
    };

      上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

      现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

    var f = function () {
      console.log(this.x);
    }

      上面代码中,函数体里面的this.x就是指当前运行环境的x

    var f = function () {
      console.log(this.x);
    }
    var x = 1;
    var obj = {
      f: f,
      x: 2,
    };
    // 单独执行
    f() // 1
    // obj 环境执行
    obj.f() // 2

      上面代码中,函数f在全局环境执行,this.x指向全局环境的x

       

       在obj环境执行,this.x指向obj.x

      

       回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

    五、应用场景

      作为对象方法调用

    var test = {
        a:0,
        b:0
        get:function(){
            return this.a;
        }
    }

      作为函数调用

    function makeNoSense(x) { 
     this.x = x; 
    }

      作为构造函数调用

    function Point(x, y){ 
        this.x = x; 
        this.y = y; 
    }

      声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题:this指向修改了,获取不到外部函数的环境变量,此时,可以使用替代方法

    var point = { 
     x : 0, 
     y : 0, 
     moveTo : function(x, y) { 
          var that = this; 
         // 内部函数
         var moveX = function(x) { 
         that.x = x; 
         }; 
         // 内部函数
         var moveY = function(y) { 
         that.y = y; 
         } 
         moveX(x); 
         moveY(y); 
         } 
     }; 
     point.moveTo(1, 1); 
     point.x; //==>1 
     point.y; //==>1

      或者使用箭头函数

    var point = { 
     x : 0, 
     y : 0, 
     moveTo : function(x, y) { 
         // 内部函数
         var moveX = (x) => { 
             this.x = x; 
         }; 
         // 内部函数
         var moveY = (y) => { 
             this.y = y; 
         } 
         moveX(x); 
         moveY(y); 
         } 
     }; 
     point.moveTo(1, 1); 
     point.x; //==>1 
     point.y; //==>1

      在call、apply、bind中调用

     function Point(x, y){ 
        this.x = x; 
        this.y = y; 
        this.moveTo = function(x, y){ 
            this.x = x; 
            this.y = y; 
        } 
     } 
     var p1 = new Point(0, 0); 
     var p2 = {x: 0, y: 0}; 
     p1.moveTo(1, 1); 
     p1.moveTo.apply(p2, [10, 10])

       转载自:ruanyifeng.com/blog/2018/06/javascript-this.html

  • 相关阅读:
    Responder一点也不神秘————iOS用户响应者链完全剖析
    loadView、viewDidLoad及viewDidUnload的关系
    iOS 离屏渲染的研究
    CoreData处理海量数据
    《驾驭Core Data》
    为什么都要在主线程中更新UI
    快速排序OC实现和快排思想找第n大的数(原创)
    最新版SDWebImage的使用
    UIViewContentMode各类型效果
    iOS 8 自适应 Cell
  • 原文地址:https://www.cnblogs.com/memphis-f/p/12082128.html
Copyright © 2020-2023  润新知