如何编写高质量JS代码(3)

javascriptd的函数值包含了比调用它们时所执行所需要的代码还要多的信息。而且,javascript函数值还在内部存储它们可能会引用的定义在其封闭作用域的变量。那些在其所涵盖的作用域内跟踪变量的函数被称为闭包。

make函数就是一个闭包,其代码引用了两个外部变量:magicIngredient和filling。每当make函数被调用时,其代码都能引用这两个变量,因为闭包存储了这两个变量。

函数可以引用在其作用域内的任何变量,包括参数和外部函数变量。我们可以利用这一点来编写更加通用的sandwichMaker函数。

复制代码 代码如下:


function makeSandwich(magicIngredient){
   function make(filling){
        return magicIngredient + " and " + filling;
   }
   return make; 
}
var f = sandwichMaker(”ham“);
f("cheese");                      // "ham and cheese"
f("mustard");               // "ham and mustard"

闭包是javascript最优雅、最有表现力的特性之一,也是许多习惯用法的核心。

c)闭包可以更新外部变量的值。事实上,闭包存储的是外部变量的引用,而不是它们的值的副本。因此,对于任何具有访问这些外部变量的闭包,都可以进行更新。

复制代码 代码如下:


function box(){
    var val = undefined;
    return {
         set : function(newval) {val = newval;},
         get : function (){return val;},
         type : function(){return typeof val;}
    };
}
var b = box();
b.type(); //undefined
b.set(98.6);
b.get();//98.6
b.type();//number

该例子产生一个包含三个闭包的对象。这三个闭包是set,type和get属性,它们都共享访问val变量,set闭包更新val的值。随后调用get和type查看更新的结果。

1.4理解变量声明提升

javascript支持此法作用域(对变量foo的引用会被绑定到声明foo变量最近的作用域中),但不支持块级作用域(变量定义的作用域并不是离其最近的封闭语句或代码块)。

不明白这个特性将会导致一些微妙的bug:

复制代码 代码如下:


function isWinner(player,others){
    var highest = 0;
    for(var i = 0,n = others.length ;i<n;i++){
          var player = others[i];
          if(player.score > highest){
                   highest = player.score;
          }
    }
    return player.score > highest;
}

1.5 当心命名函数表达式笨拙的作用域

复制代码 代码如下:


function double(x){ return x*2; }
var f = function(x){ return x*2; }

同一段函数代码也可以作为一个表达式,却具有截然不同的含义。匿名函数和命名函数表达式的官方区别在于后者会绑定到与其函数名相同的变量上,该变量作为该函数的一个局部变量。这可以用来写递归函数表达式。

复制代码 代码如下:


var f = function find(tree,key){
  //....
  return find(tree.left , key) ||
             find(tree.right,key);   
}

值得注意的是,变量find的作用域只在其自身函数中,不像函数声明,命名函数表达式不能通过其内部的函数名在外部被引用。

复制代码 代码如下:


find(myTree,"foo");//error : find is not defined;
var constructor = function(){ return null; }
var f= function(){
    return constructor();
};
f();//{}(in ES3 environments)

该程序看起来会产生null,但其实会产生一个新的对象。

因为命名函数变量作用域内继承了Object.prototype.constructor(即Oject的构造函数),就像with语句一样,这个作用域会因Object.prototype的动态改变而受到影响。在系统中避免对象污染函数表达式作用域的办法是避免任何时候在Object.prototype中添加属性,以避免使用任何与标准Object.prototype属性同名的局部变量。

在流行的javascript引擎中另外一个缺点是对命名函数表达式的声明进行提升。

复制代码 代码如下:


var f = function g(){return 17;}
g(); //17 (in nonconformat environment)

一些javascript环境甚至把f和g这两个函数作为不同的对象,从而导致不必要的内存分配。

1.6 当心局部块函数声明笨拙的作用域

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgxjwx.html