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(); //
用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()
就变成在全局环境执行。
感谢
感谢两位老师的讲解!
最新文章
- dedecms有条件sql注入(x0day)
- Redhat下如何搭建NFS
- Git一套简流
- Python之迭代器和生成器
- Action类为何要继承ActionSupport
- MIME(Multipurpose Internet Mail Extensions)的简介
- CSS布局——居中
- Delphi中JSon SuperObject 使用:数据集与JSON对象互转
- 2.Perl 多线程:Threads(线程返回值)
- easyui弹出窗关闭前调用确认窗口,先关闭页面后调用弹出窗口
- 使用 Router 实现的模块化,如何优雅的回到主页面
- WPF笔记(2.4 Grid)
- 【English】九、kids/children/toddlers 三个单词的区别
- 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading)
- qt手写输入法资料
- webform的代码设计文件莫名出错的解决
- 安装IDEA的历程
- python3 安装 google-visualization-python(windows10)
- spring boot(九):Spring Boot中Redis的使用
- Docker mysql 主从