[Effective JavaScript 笔记]第2章:变量作用域--个人总结
前言
第二章主要讲解各种变量作用域,通过这章的学习,接触到了很多之前没有接触过的东西,比如不经常用到的eval,命名函数表达式,with语句块等,下面是一个列表,我对各节的一点点个人总结,很多都是自己的收获和认识。可能有很多认识的误区,毕竟水平有限。如果有那里认识不对的地方,还希望可以在评论中指出来,这样可以得到大家在学习的帮助,也是很不错的。
第8条:尽量少用全局对象
个人总结:
解释全局对象在JS中定义的随意性,导致所有人都可以去定义去修改,导致命名冲突。对这样的全局对象进行依赖,导致不可知的运行结果,极不利于后期的维护,以及对代码重用造成的影响。提供了几点建议,包括利于JS代码的函数产生的作用域来对变量进行划分。另外,对宿主环境的全局对象,尽量少做修改。以免对其它引用库造成影响。在对运行环境进行适配时,对全局对象进行特性检测,尽量使用环境提供的原生对象。因为平台环境中的对象的正确性和一致性,及性能都有优势。
提示
-
避免声明全局变量
-
尽量声明局部变量
-
避免对全局对象添加属性
-
使用全局对象来做平台检测
第9条:始终声明局部变量
个人总结:
这一节感觉就是上一节的补充,当你尽量少用全局对象时,那你用什么呢?就是始终声明局部变量。主要是在写代码时候不要忘记,var,如果没有这个就自动变成全局的啦。对于对自己代码书写能力不是很自信的人来说,可以用一些构建工具,里面加点代码检测功能插件。然后每次跑一下就知道代码里有没有对应的错误了,这里推荐使用gulp。可以到之前的一个文章里查看使用方法。
提示
-
始终使用var声明新的局部变量
-
考虑使用lint工具帮助检查未绑定的变量
第10条:避免使用with
个人总结:
使用with,可以节约书写一些对象的名称,在代码里可以少打点代码。我没用过with,以前只知道它会改变this的指向。从学习这节里认识到,它的使用,可能会影响你代码里对应的变量的值的查找,而且每个变量都会在with对象的原型链中查找是否存在,从而造成性能的降低。在后期代码的维护过程中,也可能破坏原来使用with引用对象的函数或方法,因为有可能后期为with的对象添加了新的属性或方法和它们中的变量重名,造成功能无法正常完成,破坏函数的预期功能。做为替代方法,可以使用一些短命名,如我经常会用的var win=window,doc=document;之类的,在有对同一对象大量方法或属性使用时可以用这个方法来减少代码量。
提示
-
避免使用with语句
-
使用简短的变量名代替重复访问的对象
-
显式地绑定局部变量到对象属性上,而不要使用with语句隐式地绑定它们。
第11条:熟练掌握闭包
个人总结:
这就是一个名词,知道什么时候会出现这种情况就行了。主要也就是两个,一个是函数可以产生作用域,二个是定义在里面的函数可以访问它外层函数的变量对象(哪怕外面的函数都已经返回了)。这个会占用内存,因为一直要保存外层函数的变量对象,如果想释放掉闭包,显示地设置为null就OK了。闭包用得较多的地方,一个是定义和调用的时间点不统一,比如:绑定事件句柄的函数的定义,定义的时间点,和调用的时间点不定(事件触发的时候),二个是就是在面试的时候,各种,变量值的求值是什么。
提示
-
函数可以引用定义在其外部作用域的变量
-
闭包比创建它们的函数有更长的生命周期
-
闭包在内部存储其外部变量的引用,并能读写这些变量
第12条:理解变量声明提升
个人总结:
变量提升,看到较多的地方,也是各种面试题。理解变量提升后,可以把考试的题目先按照JS解析的过程,把一些var,function声明都写前头去,再结合作用域访问顺序,基本都能答对。这里主要就是JS没有块级作用域,作用域的最小单位就是函数。变量声明提升就是找最近的function就好了,然后把各种声明都放到这里。
提示
-
在代码块中的变量声明会被隐式提升到封闭函数的顶部
-
重声明变量被视为单个变量
-
考虑手动提升局部变量声明,从而避免混淆
第13条:使用立即调用的函数表达式创建局部作用域
个人总结:
只要看过一些库的源码的,都应该都这个不陌生。(function(){})();这就是创建完成后就执行了,当然这里要以传入一些参数。这里面创建的变量名,只要用var,一定是局部的,这里返回出来的函数一定都是闭包,可以对此函数的内部变量进行访问。可以解决上面提到的事件绑定对外部变量的引用问题。至于书上说的,不能使用break,continue。以及对this,arguments变量的影响。对this,arguments变量的影响。this要不就指向新对象,要不就指向window。函数作为构造函数时,并使用new关键词调用时,或函数作为对象的方法时,this指向对象。直接运行的函数this指向window对象。立即运行函数是直接执行的,所以this指向window。(个人理解可能有错,请指出)。arguments就是函数参数对象,立即执行函数也是函数也有参数对象。注意这点就应该没问题了。
提示
-
理解绑定与赋值的区别
-
闭包通过引用而不是值复制它们的外部变量
-
使用立即调用的函数表达式来创建局部作用域
-
当心在立即调用的函数表达式中包裹代码块可能改变其行为的情形
第14条:当心命名函数表达式笨拙的作用域
个人总结:
命名函数表达式,在一些递归的情况下使用过,比如写一个动画函数,里面的迭代函数就是使用这个。但一般我是直接在给立即执行函数表达式一个名字。
形如
(function a(){
//....
a();
})();
它的作用域也只能在内部访问,书上说的各种环境下的解析什么的,也没遇到过。现在使用环境:浏览器,nodejs,暂时没发现什么问题。在这里只是觉得把命名函数表达式的作用域表示为对象挺有意思,但在浏览器里试了一下,没有问题,可能是都实现了ES5的原因吧。
改进栈跟踪这个真心没用过,如试一下看看。
提示
-
在Error对象和高度器中使用命名函数表达式改进栈跟踪
-
在ES3和有问题的JS环境中谨记函数表达式作用域会被Object.prototype污染
-
谨记在错误百出的JS环境中会提升命名函数表达式声明,并导致命名函数表达式的重复存储
-
考虑避免使用命名函数表达式或在发布前删除函数名
-
如果你将代码发布到正确实现的ES5环境中,那么就不会出现问题了
第15条:当心局部块函数声明笨拙的作用域
个人总结:
嵌套函数声明,立即执行函数里定义的函数就是嵌套的。因为没有块级作用域,所以在块里声明函数,会自动提升函数声明到作用域的顶部。如果要根据条件来声明一个函数,可以用函数表达式的形式对变量进行赋值,从而得到相应功能的函数。比如:事件注册的函数
function addEvent(o,e,f){
var addE;
if(document.addEventListener){
addE=function(o,e,f){
o.addEventListener(e,f,false);
}
}else if(document.attachEvent){
addE=function(o,e,f){
o.attachEvent('on'+e,f);
}
}else{
addE=function(o,e,f){
o['on'+e]=f;
}
}
addE(o,e,f);
addEvent=addE;
}
提示
-
始终将函数声明置于程序或被包含的函数的最外层以避免不可移植的行为
-
使用var声明和有条件的赋值语句替代有条件的函数声明
第16条:避免使用eval创建局部变量
个人总结:
在工作中用得真心不多,只在json数据的处理,也就是把字符串转化为json对象这个过程中使用过。还有就是通过这节文章,了解到对外部作用域的影响是不可预知的,因为你不知道将要处理的字符串中,是否包括对作用域中变量的修改(包括全局变量的修改)。建议使用立即执行函数对处理代码进行隔离。
提示
-
避免使用eval函数创建的变量污染调用者的作用域
-
如果eval函数代码可能创建全局变量,将此调用封装到嵌套的函数中以防止作用域的污染
第17条:间接调用eval函数优于直接调用
个人总结:
大多数函数只能访问定义它们所在的作用域,而不能访问除此之外的作用域。eval函数可以访问调用它时的整个作用域的能力,也就是那里调用,它就可以访问那里的作用域。功能很强大,但导致没法优化JS。因为每个调用了eval的函数都要确保在运行时整个作用域对eval函数可访问。可以使用(0,eval)(src)变成间接调用来提高性能。其它的我觉得都不用知道,毕竟这个本来用得就少,至少我用得少。
提示
-
将eval函数同一个毫无意义的字面量包裹在序列表达式中以达到强制使用间接调用eval函数的目的
-
尽可能间接调用eval函数,而不要直接调用eval函数
翻译的文章,版权归原作者所有,只用于交流与学习的目的。
原创文章,版权归作者所有,非商业转载请注明出处,并保留原文的完整链接。