zoukankan      html  css  js  c++  java
  • JavaScript方法中this关键字使用注意

    问题来源 

      本文是基于廖雪峰老师JavaScript课程中的方法一节以及阮一峰老师JavaScript 的 this 原理

    所记。

      首先,我们了解一下JavaScript中的方法:在一个对象中绑定函数,称为这个对象的方法。下面,给对象xiaoming绑定一个函数:

    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
    };
    xiaoming.age;  // function xiaoming.age()
    xiaoming.age(); // 28

    从这段代码中,可以看到使用了this关键字。

    this的介绍

      在一个方法中,this指向的就是该方法绑定的对象。其中 this.birth 就是指向对象 xiaoming 的 birth 属性。

    对比出情形

      通过拆分函数,对比上面的代码。从下面的结果看出,单独调用含有this关键字的函数,返回的是NaN。

    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: getAge
    };
    xiaoming.age(); // 28, 正常结果
    getAge(); // NaN

    你以为这就完了吗?下面这种写法,还是有错:

    var fn = xiaoming.age; // 先拿到xiaoming的age函数
    fn(); // NaN

    原因是:在一个对象中调用含有this关键字的函数,此时this就是指向该对象;单独调用,例如上述的 getAge() ,此时this指向的就是全局对象: window 。所以,要保证this指向正确,必须用 obj.xxx() 的形式调用!至此,我们就了解了this问题的来源。由于这是一个巨大的设计错误,要想纠正可没那么简单。ECMA决定,在strict模式下让函数的this指向undefined,因此,在strict模式下,你会得到一个错误:"Uncaught TypeError: Cannot read property 'birth' of undefined"。

    'use strict';
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
    };
    
    var fn = xiaoming.age;
    fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined

    当你重构方法时:

    'use strict';
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            function getAgeFromBirth() {
                var y = new Date().getFullYear();
                return y - this.birth;
            }
            return getAgeFromBirth();
        }
    };
    
    xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

    结果又报错了!原因是this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)

      下面,廖老师给出了修复方法,用一个that变量首先捕获this

    'use strict';
    
    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: function () {
            var that = this; // 在方法内部一开始就捕获this
            function getAgeFromBirth() {
                var y = new Date().getFullYear();
                return y - that.birth; // 用that而不是this
            }
            return getAgeFromBirth();
        }
    };
    
    xiaoming.age(); // 28

    var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。

      下面还有一个问题来源,是阮一峰老师所写。下面写法一和写法二指向同一个函数,但是执行结果可能不一样。

    var obj = {
      foo: function () {}
    };
    
    var foo = obj.foo;
    
    // 写法一
    obj.foo();
    
    // 写法二
    foo();

    下面是结果不一样的例子:

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

    结果不一样的原因,很简单。就是 xiaoming 那个例子中的说明:如果在对象中使用this,其指向的就是对象。这里, obj.foo(); // 1 foo运行在obj环境,所以this指向obj,也就是 obj.bar = 1 ; foo(); // 2 对于foo()来说,foo运行在全局环境,所以this指向全局环境,也就是 window.bar = 2 。所以,this出现问题的情形已经很清楚了,所处对象的原因。

    深层原因

      接下来,借阮一峰老师的解释,说明 JavaScript 这样处理的原理。

    内存的数据结构

      首先,给一段代码: var obj = { foo: 5 }; 这里的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。即变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

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

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

      但是,属性的值可能是一个函数: var obj = { foo: function () {} }; 。这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

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

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

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

    环境变量

      JavaScript 允许在函数体内部,引用当前环境的其他变量。下面代码中,函数体里面使用了变量x。该变量由运行环境提供。

    var f = function () {
      console.log(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指向全局环境window的x=1。但是在obj环境中,this就是指向obj中的x=2。

    回答问题

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

     感谢

      感谢两位老师的讲解!

      

  • 相关阅读:
    Linux 下 Lua 与 LuaSQL 模块安装
    js学习笔记27----键盘事件
    js学习笔记26----事件冒泡,事件捕获
    js学习笔记25----Event对象
    js学习笔记24----焦点事件
    js学习笔记23----窗口尺寸及窗口事件
    js学习笔记22----BOM属性和方法
    VS Code 常用快捷键
    你不知道的JavaScript学习笔记1——作用域
    三种CSS方法实现loadingh点点点的效果
  • 原文地址:https://www.cnblogs.com/cjvae/p/9778280.html
Copyright © 2011-2022 走看看