Javascript this 的一些学习总结


Posted in Javascript onAugust 31, 2012

1.1.1 摘要
相信有C++、C#或Java等编程经验的各位,对于this关键字再熟悉不过了。由于Javascript是一种面向对象的编程语言,它和C++、C#或Java一样都包含this关键字,接下来我们将向大家介绍Javascript中的this关键字。

本文目录

•全局代码中的this
•函数中的this
•引用类型
•函数调用以及非引用类型
•引用类型以及this的null值
•函数作为构造器被调用时this的值
•手动设置函数调用时this的值

1.1.2 正文

由于许多面向对象的编程语言都包含this关键字,我们会很自然地把this和面向对象的编程方式联系在一起,this通常指向利用构造器新创建出来的对象。而在ECMAScript中,this不仅仅只用来表示创建出来的对象,也是执行上下文的一个属性:

activeExecutionContext = { 
// Variable object. 
VO: {...}, 
this: thisValue 
};

全局代码中的this
// Global scope 
// The implicit property of 
// the global object 
foo1 = "abc"; 
alert(foo1); // abc // The explicit property of 
// the global object 
this.foo2 = "def"; 
alert(foo2); // def 
// The implicit property of 
// the global object 
var foo3 = "ijk"; 
alert(foo3); // ijk

前面我们通过显式和隐式定义了全局属性foo1、foo2和foo3,由于this在全局上下文中,所以它的值是全局对象本身(在浏览器中是window object);接下来我们将介绍函数中的this。

函数中的this

当this在函数代码中,情况就复杂多了,并且会引发很多的问题。

函数代码中this值的第一个特性(同时也是最主要的特性)就是:它并非静态的绑定在函数上。

正如此前提到的,this的值是在进入执行上下文(Excution context)的阶段确定的,并且在函数代码中的话,其值每次都不尽相同。

然而,一旦进入执行代码阶段,其值就不能改变了。如果要想给this赋一个新的值是不可能的,因为在那时this根本就不是变量了。

接下来,我们通过具体的例子说明函数中的this。

首先我们定义两个对象foo和person,foo包含一个属性name,而person包含属性name和方法say(),具体的定义如下:

// Defines foo object. 
var foo = { 
name: "Foo" 
}; // Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush 
// foo and person object refer to 
// the same function say 
foo.say = person.say; 
foo.say(); // My name is Foo.

通过上面的代码,我们发现调用person的say()方法时,this指向person对象,当通过赋值方式使得foo的say()方法指向peson中的say()方法时。我们调用foo的say()方法,发现this不是指向person对象,而不是指向foo对象,这究竟是什么原因呢?

首先,我们必须知道this的值在函数中是非静态的,它的值确定在函数调用时,具体代码执行前,this的值是由激活上下文代码的调用者决定的,比如说,调用函数的外层上下文;更重要的是,this的值是由调用表达式的形式决定的,所以说this并非静态的绑定在函数上。

由于this并非静态地绑定在函数上,那么我们是否可以在函数中动态地修改this的值呢?

// Defines foo object. 
var foo = { 
name: "Foo" 
}; // Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
this = foo; // ReferenceError 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush

现在我们在方法say()中,动态地修改this的值,当我们重新执行以上代码,发现this的值引用错误。这是由于一旦进入执行代码阶段(函数调用时,具体代码执行前),this的值就确定了,所以不能改变了。

引用类型

前面我们提到this的值是由激活上下文代码的调用者决定的,更重要的是,this的值是由调用表达式的形式决定的;那么表达式的形式是如何影响this的值呢?

首先,让我们介绍一个内部类型——引用类型,它的值可以用伪代码表示为一个拥有两个属性的对象分别是:base属性(属性所属的对象)以及该base对象中的propertyName属性:

// Reference type. 
var valueOfReferenceType = { 
base: mybase, 
propertyName : 'mybasepropertyName' 
};

引用类型的值只有可能是以下两种情况:

•当处理一个标识符的时候
•或者进行属性访问的时候
标识符其实就是变量名、函数名、函数参数名以及全局对象的未受限的属性。

// Declares varible. 
var foo = 23; // Declares a function 
function say() { 
// Your code. 
}

中间过程中,对应的引用类型如下:
// Reference type. 
var fooReference = { 
base: global, 
propertyName: 'foo' 
}; var sayReference = { 
base: global, 
propertyName: 'say' 
};

我们知道Javascript中属性访问有两种方式:点符号和中括号符号:
// Invokes the say method. 
foo.say(); 
foo['say']();

由于say()方法是标识符,所以它对应于foo对象引用类型如下:
// Reference type. 
var fooSayReference = { 
base: foo, 
propertyName: 'say' 
};

我们发现say()方法的base属性值为foo对象,那么它对应的this属性也将指向foo对象。

假设,我们直接调用say()方法,它对应的引用类型如下:

// Reference type. 
var sayReference = { 
base: global, 
propertyName: 'say' 
};

由于say()方法的base属性值为global(通常来说是window object),那么它对应的this属性也将指向global。

函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。

函数调用以及非引用类型
前面我们提到,当调用括号左侧为非引用类型的时,this的值会设置为null,并最终隐式转换为全局对象。

现在我们定义了一个匿名自执行函数,具体实现如下:

// Declares anonymous function 
(function () { 
alert(this); // null => global 
})();

由于括号()左边的匿名函数是非引用类型对象(它既不是标识符也不属于属性访问),因此,this的值设置为全局对象。
// Declares object. 
var foo = { 
bar: function () { 
alert(this); 
} 
}; (foo.bar)(); // foo. 
(foo.bar = foo.bar)(); // global? 
(false || foo.bar)(); // global? 
(foo.bar, foo.bar)(); // global

这里注意到四个表达式中,只有第一个表达式this是指向foo对象的,而其他三个表达式则执行global。

现在我们又有疑问了:为什么属性访问,但是最终this的值不是引用类型对象而是全局对象呢?

我们注意到表达式二是赋值(assignment operator),与表达式一组操作符不同的是,它会触发调用GetValue方法(参见11.13.1中的第三步)。 最后返回的时候就是一个函数对象了(而不是引用类型的值了),这就意味着this的值会设置为null,最终会变成全局对象。

第三和第四种情况也是类似的——逗号操作符和OR逻辑表达式都会触发调用GetValue方法,于是相应地就会丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了。

引用类型以及this的null值
对于前面提及的情形,还有例外的情况,当调用表达式左侧是引用类型的值,但是this的值却是null,最终变为全局对象(global object)。 发生这种情况的条件是当引用类型值的base对象恰好为活跃对象(activation object)。

当内部子函数在父函数中被调用的时候就会发生这种情况,通过下面的示意代码介绍活跃对象:

// Declares foo function. 
function foo() { 
function bar() { 
alert(this); // global 
} 
// The same as AO.bar(). 
bar(); 
}

由于活跃对象(activation object)总是会返回this值为——null(用伪代码来表示AO.bar()就相当于null.bar()),然后,this的值最终会由null转变为全局对象。

当函数调用包含在with语句的代码块中,并且with对象包含一个函数属性的时候,就会出现例外的情况。with语句会将该对象添加到作用域链的最前面,在活跃对象的之前。 相应地,在引用类型的值(标识符或者属性访问)的情况下,base对象就不再是活跃对象了,而是with语句的对象。另外,值得一提的是,它不仅仅只针对内部函数,全局函数也是如此, 原因就是with对象掩盖了作用域链中更高层的对象(全局对象或者活跃对象):

函数作为构造器被调用时this的值
函数作为构造函数时,我们通过new操作符创建实例对象是,它会调用Foo()函数的内部[[Construct]]方法;在对象创建之后,会调用内部的[[Call]]方法,然后所有Foo()函数中this的值会设置为新创建的对象。

// Declares constructor 
function Foo() { 
// The new object. 
alert(this); 
this.x = 10; 
} var foo = new Foo(); 
foo.x = 23; 
alert(foo.x); // 23

手动设置函数调用时this的值
Function.prototype原型上定义了两个方法,允许手动指定函数调用时this的值。这两个方法分别是:.apply()和.call()。这两个方法都接受第一个参数作为调用上下文中this的值,而这两个方法的区别是传递的参数,对于.apply()方法来说,第二个参数接受数组类型(或者是类数组的对象,比如arguments), 而.call()方法接受任意多的参数(通过逗号分隔);这两个方法只有第一个参数是必要的——this的值。

通过示例代码介绍call()方法和apply()方法的使用:

var myObject = {}; 
var myFunction = function(param1, param2) { 
//setviacall()'this'points to my Object when function is invoked 
this.foo = param1; 
this.bar = param2; //logs Object{foo = 'foo', bar = 'bar'} 
console.log(this); 
}; 
// invokes function, set this value to myObject 
myFunction.call(myObject, 'foo', 'bar'); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

call()方法第一个参数是必要的this值,接着我们可以传递任意多个参数,接着介绍apply()方法的使用。
var myObject = {}; 
var myFunction = function(param1, param2) { //set via apply(), this points to my Object when function is invoked 
this.foo=param1; 
this.bar=param2; 
// logs Object{foo='foo', bar='bar'} 
console.log(this); 
}; 
// invoke function, set this value 
myFunction.apply(myObject, ['foo', 'bar']); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

通过与call()方法对比,我们发现apply()方法和call()方法没有太大的区别,只是方法签名不一样。

1.1.3 总结
本文介绍Javascript中this的使用,更重要的是帮助我们能更好地理解this值在全局、函数、构造函数以及一些特例的情况中值的变化。

对于在函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。

对于特例情况,我们要记住赋值符、逗号操作符以及||逻辑表达式,会使this丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了

参考
[1] http://dmitrysoshnikov.com/ecmascript/chapter-3-this/ 英文版
[2] http://blog.goddyzhao.me/post/11218727474/this 译文
[3] https://net.tutsplus.com/tutorials/javascript-ajax/fully-understanding-the-this-keyword/

Javascript 相关文章推荐
jquery 插件 web2.0分格的分页脚本,可用于ajax无刷新分页
Dec 25 Javascript
JS 模态对话框和非模态对话框操作技巧汇总
Apr 15 Javascript
javascript生成随机颜色示例代码
May 05 Javascript
原生javascript获取元素样式
Dec 31 Javascript
jQuery实现字符串按指定长度加入特定内容的方法
Mar 11 Javascript
简介JavaScript中search()方法的使用
Jun 06 Javascript
JS 实现计算器详解及实例代码(一)
Jan 08 Javascript
浅析jsopn跨域请求原理及cors(跨域资源共享)的完美解决方法
Feb 06 Javascript
seajs下require书写约定实例分析
May 16 Javascript
vue2.0父子组件间传递数据的方法
Aug 16 Javascript
jQuery 实现扁平式小清新导航
Jul 07 jQuery
详解JS数组方法
Nov 20 Javascript
javascript动画浅析
Aug 30 #Javascript
jquery方法+js一般方法+js面向对象方法实现拖拽效果
Aug 30 #Javascript
JS跨域代码片段
Aug 30 #Javascript
JS跨域总结
Aug 30 #Javascript
js中判断Object、Array、Function等引用类型对象是否相等
Aug 29 #Javascript
xml转json的js代码
Aug 28 #Javascript
基于jquery创建的一个图片、视频缓冲的效果样式插件
Aug 28 #Javascript
You might like
php set_magic_quotes_runtime() 函数过时解决方法
2010/07/08 PHP
php和jquery实现地图区域数据统计展示数据示例
2014/02/12 PHP
PHP支付系统设计与典型案例分享
2016/08/02 PHP
PHP基于DOM创建xml文档的方法示例
2017/02/08 PHP
JavaScript接口实现代码 (Interfaces In JavaScript)
2010/06/11 Javascript
基于jquery的高性能td和input切换并可修改内容实现代码
2011/01/09 Javascript
JS选项卡动态替换banner图片路径的方法
2015/05/11 Javascript
javascript格式化日期时间方法汇总
2015/06/19 Javascript
jquery实现的伪分页效果代码
2015/10/29 Javascript
跨域资源共享 CORS 详解
2016/04/26 Javascript
再谈javascript常见错误及解决方法
2016/09/16 Javascript
详解nodejs 文本操作模块-fs模块(三)
2016/12/22 NodeJs
vue2.0中goods选购栏滚动算法的实现代码
2017/05/17 Javascript
使用webpack3.0配置webpack-dev-server教程
2018/05/29 Javascript
jQuery实现参数自定义的文字跑马灯效果
2018/08/15 jQuery
vscode 插件开发 + vue的操作方法
2020/06/05 Javascript
[20:30]职业巡回赛回顾
2018/08/09 DOTA
Python中pygame的mouse鼠标事件用法实例
2015/11/11 Python
python+pandas分析nginx日志的实例
2018/04/28 Python
python cs架构实现简单文件传输
2020/03/20 Python
Python操作redis和mongoDB的方法
2019/12/19 Python
Python timer定时器两种常用方法解析
2020/01/20 Python
如何在Python 游戏中模拟引力
2020/03/27 Python
selenium切换标签页解决get超时问题的完整代码
2020/08/30 Python
x-ua-compatible content=”IE=7, IE=9″意思理解
2013/07/22 HTML / CSS
HTML5实现签到 功能
2018/10/09 HTML / CSS
Smashbox英国官网:美国知名彩妆品牌
2017/11/13 全球购物
汽车运用工程专业毕业生推荐信
2013/12/25 职场文书
活动总结格式范文
2014/04/26 职场文书
舞蹈教育学专业求职信
2014/06/29 职场文书
2015年小学二年级班主任工作总结
2015/05/21 职场文书
党支部季度考核意见
2015/06/02 职场文书
教务处教学工作总结
2015/08/10 职场文书
创作书写之导游词实用技巧分享(干货)
2019/12/20 职场文书
oracle数据库去除重复数据
2022/05/20 Oracle
Go gorilla/sessions库安装使用
2022/08/14 Golang