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 相关文章推荐
取得窗口大小 兼容所有浏览器的js代码
Aug 09 Javascript
jquery事件机制扩展插件 jquery鼠标右键事件。
Dec 26 Javascript
js实现照片墙功能实例
Feb 05 Javascript
jQuery中使用each处理json数据
Apr 23 Javascript
JS实现的仿东京商城菜单、仿Win右键菜单及仿淘宝TAB特效合集
Sep 28 Javascript
javascript实现自动填写表单实例简析
Dec 02 Javascript
浅谈jQuery双事件多重加载的问题
Oct 05 Javascript
微信小程序 欢迎界面开发的实例详解
Nov 30 Javascript
jQuery实现级联下拉框实战(5)
Feb 08 Javascript
JavaScript 函数的定义-调用、注意事项
Apr 16 Javascript
详解webpack-dev-server使用方法
Sep 14 Javascript
Vue组件跨层级获取组件操作
Jul 27 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 isset()与empty()的使用区别详解
2010/08/29 PHP
11个PHPer必须要了解的编程规范
2014/09/22 PHP
discuz图片顺序混乱解决方案
2015/07/29 PHP
php使用curl实现ftp文件下载功能
2017/05/16 PHP
jQuery 幻灯片插件(带缩略图功能)
2011/01/24 Javascript
extjs实现选择多表自定义查询功能 前台部分(ext源码)
2011/12/20 Javascript
javascript学习笔记(十七) 检测浏览器插件代码
2012/06/20 Javascript
Javascript弹出窗口的各种方法总结
2013/11/11 Javascript
JavaScript创建一个object对象并操作对象属性的用法
2015/03/23 Javascript
使用 TypeScript 重新编写的 JavaScript 坦克大战游戏代码
2015/04/07 Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
2015/10/28 Javascript
jQuery插件之jQuery.Form.js用法实例分析(附demo示例源码)
2016/01/04 Javascript
一系列Bootstrap导航条使用方法分享
2016/04/29 Javascript
BootStrap创建响应式导航条实例代码
2016/05/31 Javascript
PHP自动加载autoload和命名空间的应用小结
2017/12/01 Javascript
Angular4 ElementRef的应用
2018/02/26 Javascript
vue axios请求超时的正确处理方法
2018/04/02 Javascript
JavaScript 下载svg图片为png格式
2018/06/21 Javascript
webstrom Debug 调试vue项目的方法步骤
2018/07/17 Javascript
vue+element项目中过滤输入框特殊字符小结
2019/08/07 Javascript
详解vuejs中执行npm run dev出现页面cannot GET/问题
2020/04/26 Javascript
[57:50]DOTA2上海特级锦标赛主赛事日 - 4 胜者组决赛Secret VS Liquid第二局
2016/03/05 DOTA
[48:18]DOTA2-DPC中国联赛 正赛 RNG vs Dynasty BO3 第二场 1月29日
2021/03/11 DOTA
python3生成随机数实例
2014/10/20 Python
python求列表交集的方法汇总
2014/11/10 Python
以windows service方式运行Python程序的方法
2015/06/03 Python
从零开始学Python第八周:详解网络编程基础(socket)
2016/12/14 Python
python+pyqt实现右下角弹出框
2017/10/26 Python
Python做智能家居温湿度报警系统
2018/09/25 Python
python粘包问题及socket套接字编程详解
2019/06/29 Python
python删除列表元素的三种方法(remove,pop,del)
2019/07/22 Python
10款最佳Python开发工具推荐,每一款都是神器
2020/10/15 Python
Python3.9.1中使用split()的处理方法(推荐)
2021/02/07 Python
css3模拟jq点击事件的实例代码
2017/07/06 HTML / CSS
机关职员工作检讨书
2014/10/23 职场文书
MySQL常用慢查询分析工具详解
2022/08/14 MySQL