Javascript学习笔记之函数篇(六) : 作用域与命名空间
在之前的介绍中,我们已经知道Javascript没有块级作用,只有函数级作用域。
functiontest(){//ascope
for(vari=0;i<10;i++){//notascope
//count
}
console.log(i);//10
}
Javascript中也没有显示的命名空间,这就意味着一切都定义在全局作用域中。每一次引用一个变量时,Javascript会往上遍历整个全局作用域直到找到该变量。如果遍历完整个全局作用域仍然没有找到该变量,则抛出一个ReferenceError错误。
请输入图片描述
隐式全局变量
//scriptA foo='42'; //scriptB varfoo='42'
上面的两个例子产生不一样的效果。第一个将在全局作用域中定义变量foo,而第二个则在当前作用域定义变量foo。
我们一定要注意,如果不使用关键字var将会带来意想不到的影响。
//globalscope
varfoo=42;
functiontest(){
//localscope
foo=21;
}
test();
foo;//21
由于在函数test内没用var来定义变量foo,所以将覆盖函数外部的全局变量foo。尽管看上去不是什么大问题,但是如果有成千上万行代码时,这将是个难以追踪的bug。
//globalscope
varitems=[/*somelist*/];
for(vari=0;i<10;i++){
subLoop();
}
functionsubLoop(){
//scopeofsubLoop
for(i=0;i<10;i++){//missingvarstatement
//doamazingstuff!
}
}
上例中,外部的循环将会在执行第一次的时候就停止,这是因为subloop函数内部的变量i将会覆盖外部的全局变量i。我们只需要在函数内部加上一个var就可以避免这个错误,所以我们在定义变量时一定不要忘记加上关键字var。除非我们确实希望对外部的全局变量造成影响。
局部变量
Javascript中局部变量只可以通过两个方式产生,一是通过关键字var来声明,一是作为函数的形参。
//globalscope
varfoo=1;
varbar=2;
vari=2;
functiontest(i){
//localscopeofthefunctiontest
i=5;
varfoo=3;
bar=4;
}
test(10);
此时,函数test内部的变量i和foo是局部变量,而bar则会覆盖外部的全局变量bar。
提升(Hoisting)
Javascript将会提升变量声明,这就意味着var表达式和函数声明都将被提升到作用域的顶部。
bar();
varbar=function(){};
varsomeValue=42;
test();
functiontest(data){
if(false){
goo=1;
}else{
vargoo=2;
}
for(vari=0;i<100;i++){
vare=data[i];
}
}
上面的代码在运行之前,var表达式和函数test的声明都将提升至顶部,因此程序将正常运行并不会报错。
//varstatementsgotmovedhere
varbar,someValue;//defaultto'undefined'
//thefunctiondeclarationgotmoveduptoo
functiontest(data){
vargoo,i,e;//missingblockscopemovesthesehere
if(false){
goo=1;
}else{
goo=2;
}
for(i=0;i<100;i++){
e=data[i];
}
}
bar();//failswithaTypeErrorsincebarisstill'undefined'
someValue=42;//assignmentsarenotaffectedbyhoisting
bar=function(){};
test();
由于Javascript没有块级作用域,这不仅将提升var表达式,同时也会使得if结构变得不够直观。
在上例中,尽管看上去if在对全局变量goo进行操作,实际上,由于变量goo被提升,所以修改的是局部变量。
如果没有对提升规则有所了解,你可能会认为下面的代码将会抛出ReferenceError错误。
//checkwhetherSomeImportantThinghasbeeninitialized
if(!SomeImportantThing){
varSomeImportantThing={};
}
当然上面的代码是没有错误的,因为在代码在运行前,var表达式已经被提升到顶部。
varSomeImportantThing;
//othercodemightinitializeSomeImportantThinghere,ornot
//makesureit'sthere
if(!SomeImportantThing){
SomeImportantThing={};
}
这里要推荐下@nightire凡哥的博文《理解JavaScript(二)》,里面对提升的讲解非常透彻。
名称解析顺序
当尝试在一个函数作用域内访问一个foo变量时,Javascript将会按照下面的顺序查找:
当前作用域内是否有varfoo的定义。
函数形参中是否有foo变量。
函数自身的名称是否为foo。
跳到外层定义域,再从第一部开始查找起。
命名空间
一个最常见的问题就是命名冲突,这是因为Javascript只有一个全局作用域所带来的。但这个问题可以通过匿名的外部函数解决。
(function(){
//aselfcontained"namespace"
window.foo=function(){
//anexposedclosure
};
})();//executethefunctionimmediately
上例中的匿名函数被认为是表达式,所以它们会被执行。
(//evaluatethefunctioninsidetheparentheses
function(){}
)//andreturnthefunctionobject
()//calltheresultoftheevaluation
当然我们也可以用其他方式来调用函数表达式,不同的结构,但是同样的效果。
//Afewotherstylesfordirectlyinvokingthe
!function(){}()
+function(){}()
(function(){}());
//andsoon...
总结
建议大家使用匿名的外部函数来将代码封装到空间内,这样不仅可以解决命名空间的冲突,同时也有利于程序的模块化。
此外,使用全局变量不是一个好习惯,这将带来高成本的维护代价而且容易产生错误。
命名空间同类型、函数、变量、模板等都属于实体(entity)。
实体的主要的共性是,可以具有名称。(此外,标签也可以具有名称,但它不是实体。)
而命名空间作用域是作用域中的一类统称,和块作用域、类作用域、函数原型作用域、函数作用域(仅对标签有效)并列。命名空间内声明的名称在命名空间作用域中。全局名称被认为在隐含的全局命名空间作用域中。
命名空间作用确实就是作用域,但是,他又不同于简单的作用域,你可以分多次在多处声明同一个命名空间,但是里面的内容不能重定义,他们最终都会合成一个命名空间,就像std,到处宏定义