[从jQuery看JavaScript]-变量与作用域链
jQuery片段:
- var
- // Will speed up references to window, and allows munging its name.
- window = this,
- // Will speed up references to undefined, and allows munging its name.
- undefined,
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
- // Map over the $ in case of overwrite
- _$ = window.$,
- jQuery = window.jQuery = window.$ = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context );
- },
- // A simple way to check for HTML strings or ID strings
- // (both of which we optimize for)
- quickExpr = /^[^<]*(<(.|/s)+>)[^>]*$|^#([/w-]+)$/,
- // Is it a simple selector
- isSimple = /^.[^:#/[/.,]*$/;
从这一节开始,我们剥掉jQuery的外衣,看看里面藏着些什么。前一节中曾经提及,如果单看外层的匿名函数,不看里面的实现的话,这个实现肯定不是闭包。但是,如果把jQuery的实现加上的话,这个肯定就是一种闭包应用。万丈高楼平地起,要了解闭包应用,就首先要了解它的基础。而这一节,我们遇到的片段,就是这个基础的所在——变量。(虽然这个片段包含众多知识点,但请容许我一个个慢慢分说。)
- 声明变量
变量的英文名为variable,其前三个字母正是我们在JS声明变量的关键字——var。那么,我们先来看一下如何去声明一个变量:
- /*
- * 声明变量的格式为
- * var 变量名 初始化变量表达式列表(可选)
- */
- var a=1, b, c="test", d=a+b;// 虽然b还没有初始化,但是声明是合法的
- alert(a);// "1"
- alert(b);// "undefined"
- alert(c);// "test"
- alert(d);// "NaN"
- alert(e);// 这里将引发编译错误:"e"未定义
- // 同样地,如果在初始化中使用未定义的变量,也会引发编译错误。
如上例所示,声明变量需要使用var关键字,然后在空格后紧跟变量的名字。在声明变量的同时,我们也可以选择帮变量初始化。初始化的值可以是任何类型的值或表达式,但是,如果你尝试使用未定义的变量名来初始化,JS的编译器将会判定发生编译错误,并阻止程序继续往下运行。无论你是否对声明的变量进行初始化,你都可以继续声明第二个变量而无须使用var关键字。你所需要的只是运算符“,”。(关于运算符将在稍后章节详细讨论。)但当你没有对声明的变量进行初始化时,变量将会被赋予值“undefined”——undefined也是JS的固有类型之一。PS:JS中使用的运算符必须是半角的英文字符,甚至空格也一样。
- 重复声明的变量?!
当我们声明了一个变量,而又在后续的代码中再次对他进行声明,结果会怎么样呢?或许在很多其他语言中,这都会引起重复定义的错误,但在JS中,这完全是合法的。并且,由于JS是弱数据类型,所以变量能被赋予任何类型的值。请看以下例子:
- var a=1;
- alert(typeof a); // "number"
- var a;
- alert(typeof a); // "number"
- var a="1";
- alert(typeof a); // "string"
- a=new String("1");
- alert(typeof a); // "object"
看完上面的例子,你可能会产生两个疑问:
a)为什么第二个a还是number?
b)为什么第四个a是object?
为了解答第一个问题,我们首先要了解声明一个变量到底是怎么运作的。而第二个问题,我们将他放到下一节再讨论。
- var 变量声明的工作步骤
当我们使用var关键字去声明变量的时候,JS解释器将会进行如下操作:
1)预编译javascript代码块中所有非函数块内的var关键字;
2)生成变量名标识并在其所在作用域分配空间;
3)按代码顺序运行至第一个var关键字所在行;
4)按变量声明列表表达式次序计算初始化表达式的值;
5)每计算完一条初始化表达式,就将其计算结果赋予给对应的声明变量;
6)继续运行后续代码至下一var关键字;
7)重复4-7步到代码块结束;
8)继续编译运行下一个代码块。
PS:JS将以一个代码块,也就是一个script标签为单位去运行一段JS代码。
正是因为var的工作方式,实际上程序执行时,解释器是根本看不到var关键字的。他执行的只是初始化表达式的赋值语句而已——所以问题a的答案就是例子中的第三句实际上什么事也没有做。所以,你一点也不用为代码中会否出现重复定义的变量名而烦恼。你真正需要担心的是,初始化语句所产生的变量的值的变化是否如你预期。除此之外,请不要尝试使用保留字作为变量名。这几乎在所有语言中都必须遵循的规范。
另外,在函数块中声明变量的工作步骤也是类似的,但不同的是,他们是在函数运行时才创建的。
- 没有var的变量声明?!
很多朋友都应该有这个经验——“我们根本不需要使用var来声明变量也能直接赋值啊!”。这是因为JS解释器在遇到赋值表达式的时候,会先在作用域链中寻找这个变量是否已经声明。如果这个变量没有声明,则隐式强制为其在全局(Global)作用域中声明,并将表达式的值赋予给该变量。
但究竟为什么会这样呢?其实一切都源自于变量的获取规则和作用域链的化合作用外加赋值运算符的催化作用。
- 作用域链
每个运行时的上下文都有与其对应的一个作用域。而作用域链正是把这些作用域连接起来的桥梁。它的作用与程序寻找某一变量标识有关:
1)JS解释器会按调用的顺序把作用域加进作用域链(像栈般早进入的作用域会在作用域链的底部);
2)然后在程序寻找某一变量标识时进入作用域链中的第一个索引,并在其中寻找该变量标识;
3)如果没有找到该标识,则前往下一个索引继续寻找;
4)如果已经找到该标识,则将该标识及其值返回;
5)当搜索到最后一个索引仍未能找到该标识,则在最后的索引上创建该标识,并使其值为null,最后返回该标识与值。
PS:而上述的第5步发生的前提是该标识处于赋值运算符表达式左侧。
因此,当你没有使用var声明变量而直接使用对该变量作初始化操作(简单赋值)时,JS会自动为你创建该空值标识,并让它可以顺利执行赋值语句。
- 变量与作用域链
从上面的描述,我们可以很轻易的看到变量与作用域链的关系。因为只要程序需要寻找变量,就必须通过作用域链。而前面所谈及的闭包问题正是由此而来的。回想一下我们前面的示例代码:
- var abc=function(y){
- var x=y;// 这个是局部变量
- return function(){
- alert(x++);// 就是这里调用了闭包特性中的一级函数局部变量的x,并对它进行操作
- alert(y--);// 引用的参数变量也是自由变量
- }}(5);// 初始化
- abc();// "5" "5"
- abc();// "6" "4"
- abc();// "7" "3"
- alert(x);// 报错!“x”未定义!
再联系一下我们上面说的,自己回答一下这个问题:究竟这个闭包中的变量声明和初始化是如何执行的?我们将在“闭包”的那一节再解答这个问题。
最后说明一下,标题中的英文是为了大家能更好的查找英文文献用的——因为我的整理只会是中文版。^0^y
- 关于保留关键字(补遗)
在ECMA-262规范中,规定了如下关键字作为保留关键字:
break else new var
case finally return void
catch for switch while
continue function this with
default if throw
delete in try
do instanceof typeof
这些关键字不能并声明为变量名。但其实还有如下预留关键字是作为未来扩展使用的:
abstract enum int short
boolean export interface static
byte extends long super
char final native synchronized
class float package throws
const goto private transient
debugger implements protected volatile
double import public
但实测中,以上预留关键字,仅有部分浏览器对部分关键字有作保留。例如IE中的class。(实测浏览器为IE6,FF3,Chrome,Opera)。但为了以后的兼容性,以上预留关键字最好还是别用来作为变量标识为好。
- 关于变量标识(补遗)
变量的标识必须要符合以下要求:
1)不能是保留关键字;
2)必须以英文字母或下划线(_)或美元符号($)开头;
3)后续字母除了可以为英文字母或下划线和美元符号外,还能是数字。
此外,JS是区分大小写的,变量a和变量A是两个不同的变量标识。
[从jQuery看JavaScript]-变量与作用域链的更多相关文章
- javascript的关键所在---作用域链
javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...
- 理解JavaScript中的作用域链
理解了作用域链,闭包就不难理解了,所以本文主要谈一谈我对作用域链的理解. 关于JavaScript中变量的作用域,全局变量在程序中始终都有定义.局部变量在声明它的函数体内以及其内部所嵌套的函数内始 ...
- javascript变量的作用域
javascript变量的作用域 基本类型和引用类型 基本类型值指的是简单的数据段,而引用类型值指的是那个可能由多个值组成的对象 讲一个值赋值给变量时,javascript解析器首先要确定是基本类型 ...
- javascript 函数初探 (三)--- javascript 变量的作用域
javascript 变量的作用域: 这是一个至关重要的问题.特别是当我们从别的语言转向javascript时,必须要明白一点,即在javascript中,变量的定义并不是以代码块作为作用域的,而是以 ...
- 深入理解 JavaScript 变量的作用域和作用域链
一个变量的作用域(scope)是程序源代码中定义这个变量的区域.简单的说,作用域就是变量与函数的可访问范围.全局变量拥有全局作用域,在JavaScript代码中的任何地方都有定义.局部变量是在函数体内 ...
- JavaScript 执行环境(执行上下文) 变量对象 作用域链 上下文 块级作用域 私有变量和特权方法
总结自<高程三>第四章 理解Javascript_12_执行模型浅析 JS的执行环境与作用域 javascript高级程序第三版学习笔记[执行环境.作用域] 在javascript ...
- javascript闭包和作用域链
最近在学习前端知识,看到javascript闭包这里总是云里雾里.于是翻阅了好多资料记录下来本人对闭包的理解. 首先,什么是闭包?看了各位大牛的定义和描述各式各样,我个人认为最容易一种说法: 外部函数 ...
- javascript笔记:javascript的关键所在---作用域链
javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于 ...
- JavaScript变量的作用域和函数的作用域的区别
变量作用域和函数作用域都涉及到变量值的变化,本文旨在让大家明白他们之间的区别 变量的作用域: 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接 ...
随机推荐
- Android 中发送短信
import android.net.Uri; //调用Android系统API发送短信 Uri uri = Uri.parse("smsto:" + strSmsPhone_va ...
- Asp.net 用户控件和自定义控件注册
在ASPX页中注册用户控件的方法 <%@ Register Src="ListPicker.ascx" TagName="ListPicker" Tag ...
- PHP笔试题
1.不用新变量直接交换现有两个变量的值 (1)list($a,$b)=array($b,$a); (2)a=a+b,b=a-b,a=a-b 2.PHP数字金额转大小格式,同时说明思路 function ...
- QT4编程过程中遇到的问题及解决办法
1.QLineEdit显示内容的格式函数: QLineEdit *lineEditPassword = new QLineEdit: lineEditPassword -> setEchoMod ...
- Synplify9.6.2破解(转帖)
Synplify9.6.2破解(转帖) 转载自:http://www.cnblogs.com/mark-sun/archive/2012/02/26/2368773.html Abstract本文 ...
- [sh]shell命令缩写
命令缩写: ls:list(列出目录内容) cd:Change Directory(改变目录) su:switch user 切换用户 rpm:redhat package manager 红帽子打包 ...
- eclipse中的项目受svn管理
1.我们在启动Eclipse的时候都会有例如以下图提示: 假设我们直接这样输入目录的名字,这个文件会在eclipse安装目录的同一级自己主动生成这样一个名字叫做njgzw的目录.接下来我们每次启动都用 ...
- jvm 性能调优 经验总结---转
最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一记录. 一.JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为: New(年轻代) Tenured(年老 ...
- SqlServer 如何知道是否发生了索引碎片
--如何知道是否发生了索引碎片 SELECT object_name(dt.object_id) Tablename,si.name IndexName,dt.avg_fragmentation_in ...
- 将ip地址转换成C段地址的UDF
将ip地址转换成C段地址的UDF,最重要的是判断IP地址的正则表达式. package cn.cnnic.ops.Study; import java.util.regex.Pattern; impo ...