浅谈JS的作用域链(三)
前面两篇文章介绍了JavaScript执行上下文中两个重要属性:VO/AO和scope chain。本文就来看看执行上下文中的this。
首先看看下面两个对this的概括:
- this是执行上下文(Execution Context)的一个重要属性,是一个与执行上下文相关的特殊对象。因此,它可以叫作上下文对象(也就是用来指明执行上下文是在哪个上下文中被触发的对象)。
- this不是变量对象(Variable Object)的一个属性,所以跟变量不同,this从不会参与到标识符解析过程。也就是说,在代码中当访问this的时候,它的值是直接从执行上下文中获取的,并不需要任何作用域链查找。this的值只在进入上下文的时候进行一次确定。
关于this最困惑的应该是,同一个函数,当在不同的上下文进行调用的时候,this的值就可能会不同。也就是说,this的值是通过函数调用表达式(也就是函数被调用的方式)的caller所提供的。
下面就看看在不同场景中,this的值。
全局上下文
在全局上下文(Global Context)中,this总是global object,在浏览器中就是window对象。
1
2
3
4
5
6
7
8
9
10
|
console.log( this === window); this .name = "Will" ; this .age = 28 ; this .getInfo = function(){ console.log( this .name + " is " + this .age + " years old" ); }; window.getInfo(); // true // Will is 28 years old |
函数上下文
在一个函数中,this的情况就比较多了,this的值直接受函数调用方式的影响。
Invoke function as Function
当通过正常的方式调用一个函数的时候,this的值就会被设置为global object(浏览器中的window对象)。
但是,当使用"strict mode"执行下面代码的时候,this就会被设置为"undefined"。
1
2
3
4
5
6
7
8
|
function gFunc(){ return this ; } console.log(gFunc()); console.log( this === window.gFunc()); // window // true |
Invoke function as Method
当函数作为一个对象方法来执行的时候,this的值就是该方法所属的对象。
在下面的例子中,创建了一个obj对象,并设置name属性的值为"obj"。所以但调用该对象的func方法的时候,方法中的this就表示obj这个对象。
1
2
3
4
5
6
7
8
9
|
var obj = { name: "obj" , func: function () { console.log( this + ":" + this .name); } }; obj.func(); // [object Object]:obj |
为了验证"方法中的this代表方法所属的对象"这句话,再看下面一个例子。
在对象obj中,创建了一个内嵌对象nestedObj,当调用内嵌对象的方法的时候,方法中的this就代表nestedObj。
1
2
3
4
5
6
7
8
9
10
11
12
|
var obj = { name: "obj" , nestedObj: { name: "nestedObj" , func: function () { console.log( this + ":" + this .name); } } } obj.nestedObj.func(); // [object Object]:nestedObj |
对于上面例子中的方法,通常称为绑定方法,也就是说这些方法都是个特定的对象关联的。
但是,当我们进行下面操作的时候,temp将是一个全局作用里面的函数,并没有绑定到obj对象上。所以,temp中的this表示的是window对象。
1
2
3
4
5
6
7
8
9
10
11
|
var name = "Will" ; var obj = { name: "obj" , func: function () { console.log( this + ":" + this .name); } }; temp = obj.func; temp(); // [object Window]:Will |
Invoke function as Constructor
在JavaScript中,函数可以作为构造器来直接创建对象,在这种情况下,this就代表了新建的对象。
1
2
3
4
5
6
7
8
9
10
11
|
function Staff(name, age){ this .name = name; this .age = age; this .getInfo = function(){ console.log( this .name + " is " + this .age + " years old" ); }; } var will = new Staff( "Will" , 28 ); will.getInfo(); // Will is 28 years old |
Invoke context-less function
对于有些没有上下文的函数,也就是说这些函数没有绑定到特定的对象上,那么这些上下文无关的函数将会被默认的绑定到global object上。
在这个例子中,函数f和匿名函数表达式在被调用的过程中并没有被关联到任何对象,所以他们的this都代表global object。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var context = "global" ; var obj = { context: "object" , method: function () { console.log( this + ":" + this .context); function f() { var context = "function" ; console.log( this + ":" + this .context); }; f(); (function(){ var context = "function" ; console.log( this + ":" + this .context); })(); } }; obj.method(); // [object Object]:object // [object Window]:global // [object Window]:global |
call/apply/bind改变this
this本身是不可变的,但是 JavaScript中提供了call/apply/bind三个函数来在函数调用时设置this的值。
这三个函数的原型如下:
fun.apply(obj1 [, argsArray])
- Sets obj1 as the value of this inside fun() and calls fun() passing elements of argsArray as its arguments.
fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- Sets obj1 as the value of this inside fun() and calls fun() passing arg1, arg2, arg3, ... as its arguments.
fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- Returns the reference to the function fun with this inside fun() bound to obj1 and parameters of fun bound to the parameters specified arg1, arg2, arg3, ....
下面看一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function add(numA, numB){ console.log( this .original + numA + numB); } add( 1 , 2 ); var obj = {original: 10 }; add.apply(obj, [ 1 , 2 ]); add.call(obj, 1 , 2 ); var f1 = add.bind(obj); f1( 2 , 3 ); var f2 = add.bind(obj, 2 ); f2( 3 ); // NaN // 13 // 13 // 15 // 15 |
当直接调用add函数的时候,this将代表window,当执行"this.original"的时候,由于window对象并没有"original"属性,所以会得到"undefined"。
通过call/apply/bind,达到的效果就是把add函数绑定到了obj对象上,当调用add的时候,this就代表了obj这个对象。
DOM event handler
当一个函数被当作event handler的时候,this会被设置为触发事件的页面元素(element)。
1
2
3
4
5
|
var body = document.getElementsByTagName( "body" )[ 0 ]; body.addEventListener( "click" , function(){ console.log( this ); }); // <body>…</body> |
In-line event handler
当代码通过in-line handler执行的时候,this同样指向拥有该handler的页面元素。
看下面的代码:
1
2
3
4
|
document.write( '<button onclick="console.log(this)">Show this</button>' ); // <button onclick="console.log(this)">Show this</button> document.write( '<button onclick="(function(){console.log(this);})()">Show this</button>' ); // window |
在第一行代码中,正如上面in-line handler所描述的,this将指向"button"这个element。但是,对于第二行代码中的匿名函数,是一个上下文无关(context-less)的函数,所以this会被默认的设置为window。
前面我们已经介绍过了bind函数,所以,通过下面的修改就能改变上面例子中第二行代码的行为:
document.write('<button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>'); // <button onclick="((function(){console.log(this);}).bind(this))()">Show this</button>
保存this
在JavaScript代码中,同一个上下文中可能会出现多个this,为了使用外层的this,就需要对this进行暂存了。
看下面的例子,根据前面的介绍,在body元素的click handler中,this肯定是指向body这个元素,所以为了使用"greeting"这个方法,就是要对指向bar对象的this进行暂存,这里用了一个self变量。
有了self,我们就可以在click handler中使用bar对象的"greeting"方法了。
当阅读一些JavaScript库代码的时候,如果遇到类似self,me,that的时候,他们可能就是对this的暂存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var bar = { name: "bar" , body: document.getElementsByTagName( "body" )[ 0 ], greeting: function(){ console.log( "Hi there, I'm " + this + ":" + this .name); }, anotherMethod: function () { var self = this ; this .body.addEventListener( "click" , function(){ self.greeting(); }); } }; bar.anotherMethod(); // Hi there, I'm [object Object]:bar |
同样,对于上面的例子,也可以使用bind来设置this达到相同的效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
var bar = { name: "bar" , body: document.getElementsByTagName( "body" )[ 0 ], greeting: function(){ console.log( "Hi there, I'm " + this + ":" + this .name); }, anotherMethod: function () { this .body.addEventListener( "click" , (function(){ this .greeting(); }).bind( this )); } }; bar.anotherMethod(); // Hi there, I'm [object Object]:bar |
总结
本文介绍了执行上下文中的this属性,this的值直接影响着代码的运行结果。
在函数调用中,this是由激活上下文代码的调用者(caller)来提供的,即调用函数的父上下文(parent context ),也就是说this取决于调用函数的方式,指向调用时所在函数所绑定的对象。
浅谈JS的作用域链(三)的更多相关文章
- 浅谈JS的作用域链(一)
JS的执行环境 执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念. 在JavaScript中有三种代码运行环境: Global Code JavaScrip ...
- 浅谈JS的作用域链(二)
上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现. 本文就看看Exec ...
- 浅谈 js eval作用域
原文:浅谈 js eval作用域 就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console. ...
- 浅谈js变量作用域
变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...
- 浅谈JS中的闭包
浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...
- 浅谈JS之AJAX
0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HT ...
- JS 之作用域链和闭包
1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...
- 浅谈 js 语句块与标签
原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...
- 浅谈JS面向对象
浅谈JS面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...
随机推荐
- weblogic CVE-2018-2628漏洞利用工具
weblogic CVE-2018-2628漏洞利用 漏洞环境: Windows2018R2 weblogic10.3.6 漏洞利用过程: 搭建好存在CVE-2018-2628漏洞的weblogic平 ...
- Hibernate 中的 idclass mapping 问题
关于出现 idclass mapping 运行错误 @IdClass 注释通常用于定义包含复合键id的Class.即多个属性的关键复合. @IdClass(CountrylanguageEntityP ...
- 【PAT】B1064 朋友数(20 分)
以前写的,逻辑不好,过后再改 #include<stdio.h> #include<algorithm> #include<math.h> using namesp ...
- 记录:一个SQL SERVER奇怪的问题。
今天遇到了一个奇怪的问题.始终没搞清楚是怎么回事.先记一下 1.首先有张表a,包含字段 编号.日期(varchar(250)),数值 发生日期字段有非正常日期字符串,有NULL,空字符串,可能是误触键 ...
- WPFのclipToBounds与maskToBounds的区别
UIView.clipsToBounds : 让子 View 只显示父 View 的 Frame 部分,子视图超出frame的部分不显示,默认为NO,设置为YES就会把超出的部分裁掉: maskToB ...
- Linux安装solr
solr官网下载地址为:http://mirror.bit.edu.cn/apache/lucene/solr/ 以7.6.0版本作为实践 1.下载 wget http://mirror.bit.ed ...
- QT中子目录调用另一个子目录
解决办法: 第一:建立一个subdirs项目,子项目管理 第二:添加二个子项目例如:A子项目 B子项目 第三:现在A子项目调用 B子项目 在A子项目中:右键>添加库>内部库(把A添加进 ...
- WebSocket原理与实践(三)--解析数据帧
WebSocket原理与实践(三)--解析数据帧 1-1 理解数据帧的含义: 在WebSocket协议中,数据是通过帧序列来传输的.为了数据安全原因,客户端必须掩码(mask)它发送到服务器的所有 ...
- docker执行第一个应用
概念科普 Docker image:镜像是只读的,镜像中包含有需要运行的文件.镜像用来创建container,一个镜像可以运行多个container:镜像可以通过Dockerfile创建,也可以从Do ...
- 对 String 字符串的理解
1.通过构造方法创建的字符串对象和直接赋值方式创建的字符串对象区别? 通过构造方法创建字符串对象是在堆内存. 直接赋值方式创建对象是在方法区的常量池. ==: 基本数据类型,比较的是基本数据类型的值是 ...