研磨JavaScript系列(四):代码的时空
对于过程式编程来说,代码执行的时间与数据标识的空间是密不可分的。我们只有把指令执行的具体时刻与标识映射的具体地址结合起来,才能确定程序在执行瞬间的上下文状态。于是,代码时刻与数据标识的结构,就形成了作用域的概念。在一个作用域中的上下文状态,对于另一个作用域来说是不适用的。
当定义了一个个标识符,写下一条条语句时,我们只得到了程序的一个静态映像。还必须把这些元素放到动态的作用域环境去思考,才能理解整个程序的运行世界。下面我们来讨论一下JavaScript的作用域。
任何程序都会在一个原始的环境中开始运行,这个原始的环境就被称为全局环境。全局环境中包含了一些预定义的元素,这些元素对于我们的程序来说是自然存在的。它们本来就在那儿了,我们拿来就可使用。
在JavaScript里的全局环境就是一个对象,这个对象是JavaScript运行环境的根。对于浏览器中的JavaScript来说,这个根对象就是window。对于全局的JavaScript语句来说,window对象就相当于当前的作用域。
var myName = "Kevin"; //定义了window作用域下的一个变量myName myName = "Kevin"; //定义了window对象的一个属性myName
在这里,window作用域的一个变量myName和window对象的一个属性myName几乎是等价的。对于全局的JavaScript语句来说,加不加var都无所谓,两种写法非常相似。
但是,对于一个函数体内的语句,有没有var就要小心了。
<script type="text/javascript">
myName = "Kevin";
var yourName = "Rose"; alert(myName + " like " + yourName); //显示Kevin like Rose changeName(); function changeName(){
alert("yourName is " + yourName); //显示yourName is undefined
alert("myName is " + myName); //显示myName is Kevin var yourName = "Marry";
myName = "Steven"; alert(myName + " like " + yourName); //显示Steven like Marry
} alert(myName + " like " + yourName); //显示Steven like Rose
</script>
这里我们看到,显然用var修饰的yourName标识符在函数内外是两个东西。外面的Rose不会因为changeName函数内改成了Mary而改变,回到外面还是Rose,然而,myName没有用var修饰,就是一个东西了,函数内的修改也就在函数外表现出来了。
还有,我们要注意的是changeName函数中的一句话,我们本是希望输出最外面的yourName的值,但此时的输出却表明yourName的值是undefined。而第二句中输出的myName却又是我们所期望的值。这是为什么呢?
这是因为使用var定义的是作用域上的一个变量,没有var的标识符却可能是全局跟对象的一个属性。当代码运行在全局作用域时,作用域就是跟对象window,所以,有没有var都无所谓。
但是,当代码运行进入一个函数时,JavaScript会创建一个新的作用域,来作为当前作用域的子作用域。然后,将当前全局作用域切换为这个新建的子作用域,开始执行函数逻辑。JavaScript执行引擎会把此函数内的逻辑代码,当一个代码段单元来分析和执行。
在第一步的预编译分析中,JavaScript执行引擎将所有定义式函数直接创建为作用域上的函数变量,并将其值初始化为定义的函数代码逻辑,也就是为其建立了可调用的函数变量。而对于所有var定义的变量,也会在第一步的预编译中创建起来,并将初始值设为undefined。
随后,JavaScript开始解释执行代码。当遇到对函数名或变量名的使用时,JavaScript执行引擎会首先在当前作用域查找函数或变量,如果没有找到,就到上层作用域查找,依次类推。因此,前面的语句引用后面语句定义的var变量时,该变量其实已经存在,只是初始值为undefined而已。
因此,用var定义的变量只对本作用域有效,尽管此时上层作用域有同名的东西,都与本作用域的var变量无关。退出本作用域之后,此var变量消失,回到原来的作用域该有啥就还有啥。
其实,函数在每次调用时都会产生一个子作用域,退出函数时,这个子作用域就会消失,下次调用相同函数时又是另一个子作用域了。在运行的函数内再调用另外的函数是,又产生了另一个作用域,这就会随着函数调用的深入自然形成一种叫作用域链的东西。甚至在递归调用的情况下,作用域链会变得很长很长。
JavaScript查找标示符时,除非指明特定对象,否则会沿着作用域链寻找符合这一标识符的变量或属性,甚至会自动创建该标识符。正是由于JavaScript的这种灵活性,我们就可能在不经意间覆盖了原来的变量或属性,或者无意中创建了大量无用的全局属性,甚至在毫不知情的情况下影响了其他的代码逻辑,从而造成许多很难排除的Bug。
因此,当我们在编写和调试JavaScript代码时,我们的思绪应该尽量放到具体的作用域时空上去思考。这样,我们就能明白为什么标识符尽量不要与已有的东西重名?为什么变量一定要加var?为什么访问属性一定要指明对象?理解和注意这些问题,也就能轻易地排除某些奇怪的bug。
虽然,我们的代码没有办法访问到JavaScript内部的作用域对象,但JavaScript还是给我们提供了与作用域相关的某些可用元素,我们还是能知道当前作用域上下文中德某些信息的。JavaScript提供的调用上下文信息由几个,一个是函数本身,一个是函数的caller属性,另外还有this关键字和arguments隐含对象。
<script type="text/javascript">
function func(){
alert(func.toString()); //函数体内可以使用自身的标识符
} func();
</script>
函数自身有一个caller属性,其标示调用当前函数的上层函数。这就是提供了一个可以追溯函数调用来源的线索,特别对于调试JavaScript来说是不可多得宝贝。
<script type="text/javascript">
function whoCallerMe(){
alert("My Caller is " + whoCallerMe.caller); //输出自己的caller
} function callerA(){
whoCallerMe();
} function callerB(){
whoCallerMe();
} alert(whoCallerMe.caller); //输出null whoCallerMe(); //输出My Caller is null callerA(); //输出My Caller is function callerA(){whoCallerMe();} callerB(); //输出My Caller is function callerB(){whoCallerMe();}
</script>
函数的caller属性是null,表示函数没有被调用或者是被全局代码调用。函数的caller属性值实际上是动态变化的。函数的初始caller值都是null,当调一个函数时,如果代码已经运行在某个函数体内,JavaScript执行引擎将被函数的caller属性设置为当前函数。在退出被调用函数作用域时,被调函数的caller属性会恢复为null值。
this关键字表示函数正在服务的这个对象。
arguments对象,从字面上看是表示调用当前函数的参数们。除了我们可以直接使用函数参数定义列表中的那些标示符莱访问之外,还可以用arguments对象按数组方式来访问参数。
有一个质的注意的情况是,用eval()函数动态执行的代码并不创建新的作用域,其代码就是在当前作用域中执行的。eval()中的动态代码可以访问当前作用域的this,arguments等对象,因此可以使用eval()实现一些高级的多态和动态扩展方面的应用。
文章声明:本文部分内容参考自《悟透JavaScript》,这是一本学习JavaScript非常好的书。
研磨JavaScript系列(四):代码的时空的更多相关文章
- 研磨JavaScript系列
JavaScript是一种基于对象和事件驱动并具有相对安全性的客户端脚本语言.同时也是一种广泛用于客户端Web开发的脚本语言,常用来给HTML网页添加动态功能,比如响应用户的各种操作.它最初由网景公司 ...
- 研磨JavaScript系列(一):回归简单
想要理解JavaScript,你得首先放下对象和类的概念,回到数据和代码的本原.编程世界只有数据和代码两种基本元素,而这两种元素又有着纠缠不清的关系.JavaScript就是把数据和代码都简化到最原始 ...
- 研磨JavaScript系列(五):奇妙的对象
在JavaScript中,只有object和function两种东西有对象化的能力.我们先来说说函数的对象化能力. 任何一个函数都可以为其动态地添加或去除属性,这些属性可以是简单类型,可以是对象,也可 ...
- 研磨JavaScript系列(三):函数的魔力
JavaScript的代码中就只有function一种形式,function就是函数的类型.在其他的编程语言中可能还存在Procedure或者是method等代码概念,在JavaScript中只有fu ...
- 研磨JavaScript系列(二):没有类
object就是对象的类型.在JavaScript中不管多么复杂的数据和代码.都可以组织成object形式的对象. 但JavaScript没有"类"概念. 看下面这段JavaScr ...
- 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点
深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...
- JavaScript 系列博客(四)
JavaScript 系列博客之(四) 前言 本篇介绍 JavaScript 中的对象.在第一篇博客中已经说到 JavaScript 是一种''对象模型''语言.所以可以这样说,对象是 JavaScr ...
- 深入理解JavaScript系列(46):代码复用模式(推荐篇)
介绍 本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用. 模式1:原型继承 原型继承是让父对象作为子对象的原型,从而达到继承的目的: function object(o) { fun ...
- 深入理解JavaScript系列(45):代码复用模式(避免篇)
介绍 任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量 ...
随机推荐
- maven使用nexus3.3在windows下搭建私服
1. 私服简介 私服是指私有服务器,是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构建.有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库:否则 ...
- Display PowerPoint slide show within a VB form or control window
The example below shows how to use VB form/control as a container application to display a PowerPoin ...
- mongo实践-透过js shell操作mongo
mongo实践-通过js shell操作mongo 保存命令: j={name:"wangjingjing",age:15} db.user.save(j); 查询命令: var ...
- 小L 的二叉树(洛谷 U4727)
题目背景 勤奋又善于思考的小L接触了信息学竞赛,开始的学习十分顺利.但是,小L对数据结构的掌握实在十分渣渣. 所以,小L当时卡在了二叉树. 题目描述 在计算机科学中,二叉树是每个结点最多有两个子结点的 ...
- java复习volatile关键字解析
转自https://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...
- [bzoj 1041][HAOI2008]圆周上的整点(枚举)
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1041 分析:实质上是求(a,b,c)勾股数的个数,其中c是确定的. 对于勾股数有一组通式: a ...
- AI小记-K近邻算法
K近邻算法和其他机器学习模型比,有个特点:即非参数化的局部模型. 其他机器学习模型一般都是基于训练数据,得出一般性知识,这些知识的表现是一个全局性模型的结构和参数.模型你和好了后,不再依赖训练数据,直 ...
- Ubuntu 16.04安装设备管理器Hardinfo和lshw设备信息命令
安装: sudo apt-get install hardinfo 启动: 实际上这些信息都可以通过lshw进行查看,参考:https://linux.die.net/man/1/lshw
- Maven奇怪的问题,当找不到Maven输出的提示错误时可以试下这个方法
Maven有时会输出一些奇怪的错误,尤其是用Eclipse自动下载的包,然后根据提示的错误在网上找不到时,可以试下直接删除.m2文件夹,即本地仓库.然后再重新在控制台下执行打包命令来下载包.
- SiteMesh2-简介
简介: SiteMesh类似与ASP.NET的模板页. SiteMesh是由一个基于Web页面布局.装饰以及与现存Web应用整合的框架.它能帮助我们在由大量页面构成的项目中创建一致的页面布局和外观,如 ...