预编译与函数词法作用域(Precompiled & Scoped)
预编译
Javascript脚本的宿主在执行代码之前对脚本做了预编译处理,比如浏览器对Js进行了预编译,编译器会扫描所有的声明(变量、函数、对象,无论它们是否嵌套在其他函数中,都会被扫描),对声明进行编译,编译期间会忽略任何可执行的语句,直到编译结束后才会调用解释器对代码进行执行。
1.为声明的变量(变量式函数被视为变量)划分内存空间,标识符=Undefined(未初始化),执行时才会发生初始化。
2.为所有声明式函数划分内存空间,空间保存了代码体,但代码体的变量被设为Undefined。
aler(aa); //print 10
var aa = 20;
alert(aa);//print 20
var bb = function () {
alert("hello");
}
bb();//print hello
var bb = function () {
alert("world");
}
bb();//print world
function fun() {
alert("sam");
}
fun();//print leo,由于编译器会将声明式函数的代码体存储在内存中,所以最后一个声明的fun的代码体就覆盖了前一个
function fun() {
alert("leo");
}
函数词法作用域
1.闭包的作用域
Js中只有闭包才会遵循子承父的原则,也即闭包函数可以获取在父函数里定义的变量,但父函数不能获取闭包函数中定义的变量,闭包中的变量只在闭包中有效。而在浏览器中,你定义的任何函数都是作为window的构造函数的闭包。
alert(x)
function bb() { var x = 10 }
}
aa()//return x未定义,因为x只在闭包函数bb的作用域内
var aa = function () {
var x = 10
function bb() {alert(x) }
}
aa()//alert 10
var t = "s"
window.say = function () { //全局的say方法,同时又作为了aa的闭包
alert(t); //t在哪里被定义?当前函数未定义,于是向上查找,发现t在aa中定义
}
window.say();
}
aa(); //由此可见全局函数作为闭包使用时,变量作用域的查找也是按照逐层向上的规则进行的
2.块的作用域
Js的块作用域是开放的,也即没有子承父的原则,在一个函数中的子块里定义的变量,父块也可以访问,但离开函数的作用域,则不能再访问。此处需要明确函数闭包作用域和块作用域的区别,前者的父函数不能访问闭包函数里的变量,后者的函数可以访问其代码体内的任意块里的变量。
for (var i = 1; i < 11; i++) {
var g = 100;
}
alert(g); //可以访问到g
}
aa();
这与C#不同。为了不引起混乱,建议在函数代码体一开始的位置就把需要用到的变量都声明一次,不要在分支语句中创建临时变量,除非有必要这样做。
if (true) { var x = 100; } //子块
{ var y = 200; } //子块
alert(x); //print 100
alert(y); //print 200
}
aa();
3.作用域查找
假设声明了一个全局变量,而后又在某个函数中声明一个与全局变量同名的变量,那么在在函数中试图输出这个变量时,应该输出全局变量还是局部变量呢?答案是局部变量,因为解释器执行到输出变量时,它会问自己,这个变量再哪里被定义的,首先它会采取就近原则,先查找该变量是否在当前作用域中被定义,如果有则获取它,否则逐层向上查找祖先作用域,直到找到为止,如果任何作用域都找不到该变量,则会报错。
function f() {
alert(global);
var global = "part";
alert(global); //print "hello"
}
f();
//预编译:全局blobal赋值undefined,内存存储函数f的代码体,代码体alert(undefined),再声明一个局部blobal=undefined,再alert(undefined)
//解释执行:全局global="global",调用函数f,代码体内部alert(undefined),因为查找是就近原则,当前函数内部定义了一个global,但此时在内存中其值为undefined
//所以第一次会弹出提示框显示undefined,接下来为局部变量global赋值,也即为已经存在于内存中的局部blobal赋值为part,最后执行弹出框显示global的值为part
4.深入理解作用域
var i;
var anchor;
for (i = 1; i <= 3; i++) {
anchor = ADS.$("anchor" + i); //根据ID获取超链接元素
ADS.addEvent(anchor, "click", function () { //为每一个超链接绑定click事件
alert("my ID is" + i);
})
}
}
ADS.addEvent(window, "load", initAnchors);
//当点击任何超链接时,都输出3
//编译器编译时,代码体的i=undefined
//解释器执行时,每循环一次为超链接注册click事件,但事件并未触发,所以匿名的事件处理函数其在内存中的代码体中的alert('my id is undefined'),而循环完成后,i=3
//接下来,click发生,事件处理器开始执行,此时alert时,i=3
//要解决这个问题很简单,将注册事件的代码封装到另一个函数中:
function initAnchors() {
var i;
var anchor;
for (i = 1; i <= 3; i++) {
anchor = ADS.$("anchor" + i); //根据ID获取超链接元素
registerEvent(anchor, i);
}
}
ADS.addEvent(window, "load", initAnchors);
function registerEvent(linkObjArray, i) {
ADS.addEvent(linkObjArray, "click", function () { //为每一个超链接绑定click事件
alert("my ID is" + i);
})
}
特殊值
1.xx未定义
所有未声明就被引用的变量=xx未定义。
2.undefined
所有已声明但未赋值的变量/无返回值的函数=Undefined。Undefined==null。
3.null
null本身就是一个对象,所以它可以赋值给任何变量而不会抛错,这个对象不能定义任何属性与方法,它表示空。null!="",null !=0,但1 + null = 1。
4.false
false == 0,false=" ",0="",false != null。
5.NaN
not a number的意思,即非数字。