问题来源 

  本文是基于廖雪峰老师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(); //

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(); //
foo(); //

结果不一样的原因,很简单。就是 xiaoming 那个例子中的说明:如果在对象中使用this,其指向的就是对象。这里, obj.foo(); // foo运行在obj环境,所以this指向obj,也就是 obj.bar = 1 ; foo(); // 对于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() // // obj 环境执行
obj.f() //

上面代码中,函数f()在全局环境执行,this.x指向全局环境window的x=1。但是在obj环境中,this就是指向obj中的x=2。

回答问题

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

感谢

  感谢两位老师的讲解!

  

JavaScript方法中this关键字使用注意的更多相关文章

  1. 在方法中new关键字的用处

    如果在类A中有M1这个方法需方法 public virtual ovid m1() { console.writeline(“我的世界”); } 那么你在类B中继承的时候可以重写这个方法,也可以不重写 ...

  2. 深入解析Javascript中this关键字的使用

    深入解析Javascript中面向对象编程中的this关键字 在Javascript中this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如: function TestFun ...

  3. 大前端学习笔记整理【五】关于JavaScript中的关键字——this

    写在前面 工作有那么一段时间了,但是在工作中,发现自己的理论知识还是有所欠缺.特别是在javascript上,很多东西其实自己属于知道要用这个,但是不知道为什么要这么用...这种情况很是尴尬了,所以写 ...

  4. javascript中this关键字详解

    不管学习什么知识,习惯于把自己所学习的知识列成一个list,会有助于我们理清思路,是一个很好的学习方法.强烈推荐. 以下篇幅有点长,希望读者耐心阅读. 以下内容会分为如下部分: 1.涵义 1.1:th ...

  5. JavaScript中this关键字的使用比较

    JavaScript中this关键字的使用比较 this关键字在JavaScript中,用的不能说比较多,而是非常多.那么熟悉this关键字的各种用法则显得非常关键. this有时候就是我们经常说的上 ...

  6. javascript中new关键字详解

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

  7. JavaScript文件中调用AngularJS内部方法或改变$scope变量

    需要在其他JavaScript文件中调用AngularJS内部方法或改变$scope变量,同时还要保持双向数据绑定: 首先获取AngularJS application: 方法一:通过controll ...

  8. jQuery基础学习5——JavaScript方法获取页面中的元素

    给网页中的所有<p>元素添加onclick事件 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN& ...

  9. Android中通过WebView控件实现与JavaScript方法相互调用的地图应用

    在Android中通过WebView控件,可以实现要加载的页面与Android方法相互调用,我们要实现WebView中的addJavascriptInterface方法,这样html才能调用andro ...

随机推荐

  1. 解决gap 采用increapment scn 方式 操作。

    ###########1 1.查看备库的scn ⚠️如果控制文件,数据文件,数据文件头部的scn不一致,需要根据日志中的gap的起始sequence# 找到对应的scn col  current_sc ...

  2. Java中的==和equals区别

    概述: A.==可用于基本类型和引用类型:当用于基本类型时候,是比较值是否相同:当用于引用类型的时候,是比较对象是否相同. B.对于String a = “a”; Integer b = 1;这种类型 ...

  3. Spring AOP的增强处理

    就是@Before @Around @AfterReturning @AfterThrowing这几个标签的属性可以放到方法参数里面获取 例子 //正常操作@Around("service( ...

  4. 固定ip地址

    IP.  配置文件写数据库文件连接时,之前一直是就写个.  ; 毕竟之前就自己本地用.现在需要,写ip地址,但是公司点的ip的都是自动获得的.并且过一段时间还会改变. 所以,需要固定一下啊. 首先cm ...

  5. Vue.js(2.x)之列表渲染(v-for/key)

    1.v-for是Vue里的循环语句,与其他语言的循环大同小异.首先得有需要循环且不为空的数组,循环的关键字为in或of. 需要索引时的写法: v-for里的in可以使用of代替: 还可以使用v-for ...

  6. js学习的一些想法(有一些来自网络)

    javascript开发最佳实践学习 1.给变量和函数命名--变量名和函数名尽量简短 好的变量命名应该是简短易懂的,还有需要避免的陷阱就是在命名中将数值与功能结合. 匈牙利命名法就是一个不错的选择,也 ...

  7. uvm_tlm——TLM1事务级建模方法(一)

    TLM(事务级建模方法,Transaction-level modeling)是一种高级的数字系统模型化方法,它将模型间的通信细节与函数单元或通信架构的细节分离开来.通信机制(如总线或者FIFO)被建 ...

  8. System Center Configuration Manager 2016 域准备篇(Part1)

    本系列指南如何从Microsoft安装最新的Configuration Manager基准版本.较新的可用基准版本System Center Configuration Manager(当前分支)版本 ...

  9. SQL 语句 使用附加和分离

    use mastergo declare @flg int --返回0表示成功 否则表示失败declare @msg varchar(50) --显示成功或失败的消息declare @dbname v ...

  10. IDEA 编辑器如何将tabs 分行显示

    https://jingyan.baidu.com/article/49ad8bcebd9e7c5834d8faac.html