title | date |
---|---|
编写高质量JS代码的68个有效方法(三) |
2014/10/30 |
##No.11、熟练掌握闭包 Tips:
-
函数可以引用定义在其外部的作用域变量。
-
闭包比创建它们的函数有更长的生命周期。
-
闭包在内部存储其外部变量的引用,并能读写这些变量。
//第一个事实:JavaScript允许你引用在当前函数以外定义的变量。 function testClosures(){ var all = 'Test'; function test(m){ return all + ' and ' + m; } return test('closures'); } testClosures(); //'Test and closures'
//第二个事实:即使外部函数已返回,当前函数仍然可以引用在外部函数所定义的变量。 function testClosures(){ var all = 'Test'; function test(m){ return all + ' and ' + m; } return test; } var t = testClosures(); t('closures'); //'Test and closures'
//第三个事实:闭包可以更新外部变量的值 function TestClass(){ var all; return { set: function(value){ all = value; }, get: function(){ return all; } }; } var t = new TestClass(); t.set('555'); t.get();
闭包的优缺点: 优点: 变量保护、封装性,能够实现字段的可访问性(示例如下)
function ModelClass(){
//Property
var name,age=23;
return {
setName: function(value){ //设置名称
name = value;
},
getName: function(){ //获取名称
return name;
},
getAge: function(){ //只读
return age;
}
};
}
缺点: 常驻内存,会增加内存使用量,使用不当和容易造成内存泄露。
- 代码块中的函数申明会提升到函数顶部
- 重复申明变量被视为单个变量
- 考虑手动提升局部变量的申明,避免混淆(将函数内所需变量集中申明到函数顶部)
JavaScript支持词法作用域,而不支持块级作用域
function test(){
alert(a); //undefined
var a = 1;
alert(a); //1
}
test();
以上代码等价于:
function test(){
var a;
alert(a); //undefined
a = 1;
alert(a); //1
}
test();
一个例外是 try...catch :catch块中的变量作用域只在catch中。
function test(){
var x = '1';
try{
throw ''
}catch(x){
alert('error');
x = '2';
}
alert(x); // 1
}
test();
- 理解绑定与赋值的区别
- 闭包通过引用而不是值捕获它们的外部变量
- 使用立即调用的函数表达式(IIFE)来创建具有作用域
- 当心在立即调用的函数表达式中包裹代码块可能改变其行为的情形
看看以下代码段输出什么?
function test(){
var arr = [1,2,3,4,5];
var result = [];
for(var i = 0, len = arr.length; i < len; i++){
result[i] = function(){
return arr[i];
}
}
return result;
}
var result = test();
result[0]();
可以通过立即调用表达式来解决JavaScript缺少块级作用域。如上代码可修改为:
function test(){
var arr = [1,2,3,4,5];
var result = [];
for(var i = 0, len = arr.length; i < len; i++){
(function(){
var j = i;
result[i] = function(){
return arr[j];
}
})(i);
}
return result
}
var result = test();
result[0]();
##No.14、当心命名函数表达式笨拙的作用域
- 在Error对象和调试器中使用命名函数表达式改进栈跟踪
- 在ES3和有问题的JS环境中,函数表达式作用域会被Object.prototype污染
- 谨记在错误百出的JS环境中会提升命名函数表达式声明,并导致命名函数表达式的重复存储
- 考虑避免使用命名函数表达式或在发布前删除函数名
- 如果将代码发布到正确实现的ES5的环境中,没什么好担心的
匿名和命名函数表达式的官方区别在于后者会绑定到与其函数名相同的变量上,该变量将作为该函数内部的一个局部变量。这可以用来写递归函数表达式。
var f = function find(tree, key){
if(!tree){
return null;
}
if(tree.key === key){
return tree.value;
}
//函数内部可以访问find
return find(tree.left, key) || find(tree.right, key);
}
结论:尽量避免使用命名函数表达式
-
始终将函数声明置于程序或被包含的函数的最外层以避免不可移植的行为
-
使用var声明和有条件赋值语句替代有条件的函数声明
function f(){ return 'global'; } function test(x){ var result = []; if(x){ function f(){ return 'local'; } result.push(f()); } result.push(f()); return result; } test(true); test(false);
结论:尽量将函数块定义为变量,防止函数提前