7 函数表达式

  • 递归
  • 闭包
  • 模仿块级作用域
  • 私有变量

Firefox、Safari、Chrome和Opera都给函数定义了一个非标准的name属性:


In [2]:
// golbal define function alert
if((typeof alert) === 'undefined') {
    global.alert = function(message) {
        console.log(message);
    }
}

function functionName(){
    //noop
}

//works only in Firefox, Safari, Chrome, and Opera
alert(functionName.name); //"functionName"


functionName
Out[2]:
undefined

关于函数声明,它的一个重要特性就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明:


In [3]:
sayHi();
function sayHi(){
    alert("Hi!");
}


Hi!
Out[3]:
undefined

下面代码不推荐:


In [6]:
var condition = true;
    
//never do this!
if(condition){
    function sayHi(){
        alert("Hi!");
    }
} else {
    function sayHi(){
        alert("Yo!");
    }
}

sayHi();


Hi!
Out[6]:
undefined

可以这样做:


In [7]:
var condition = true;
var sayHi;

//never do this!
if(condition){
    sayHi = function(){
        alert("Hi!");
    }
} else {
    sayHi = function(){
        alert("Yo!");
    }
}

sayHi();


Hi!
Out[7]:
undefined

递归

以下代码将factorial的函数指针赋给anotherFactorial,然后将factorial设置为null。这时指向函数体的只有anotherFactorial,但是在函数体内硬编码了factorial:


In [8]:
function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //error!


TypeError: factorial is not a function
    at factorial (evalmachine.<anonymous>:5:22)
    at evalmachine.<anonymous>:11:7
    at ContextifyScript.Script.runInThisContext (vm.js:25:33)
    at Object.exports.runInThisContext (vm.js:77:17)
    at run ([eval]:608:19)
    at onRunRequest ([eval]:379:22)
    at onMessage ([eval]:347:17)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
    at process.nextTick (internal/child_process.js:719:12)

arguments.callee是一个指向正在执行的函数的指针:


In [9]:
function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //24


24
Out[9]:
undefined

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

function createComparisonFunction(propertyName){
    return function(obj1, obj2){
        var val1 = obj1[propertyName]; // 访问外部函数变量propertyName
        var val2 = obj2[propertyName]; // 访问外部函数变量propertyName

        return val1 - val2;
    }
}

闭包与变量

闭包只能取得包含函数中任何变量的最后一个值:


In [12]:
function createFunctions(){
    var result = new Array();

    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }

    return result;
}

var funcs = createFunctions();

//every function outputs 10
for (var i=0; i < funcs.length; i++){
    alert(funcs[i]() + "<br />");
}


10<br />
10<br />
10<br />
10<br />
10<br />
10<br />
10<br />
10<br />
10<br />
10<br />
Out[12]:
undefined

因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()返回后,变量i的值是10,此时每个函数引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。可以通过创建另一个匿名函数强制让闭包的行为符合预期:


In [13]:
function createFunctions(){
    var result = new Array();

    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }

    return result;
}

var funcs = createFunctions();

//every function outputs 10
for (var i=0; i < funcs.length; i++){
    alert(funcs[i]() + "<br />");
}


0<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
Out[13]:
undefined

关于this对象

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性:


In [14]:
var name = "The Window";
        
var object = {
    name : "My Object",

    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};

alert(object.getNameFunc()());  //"The Window"


The Window
Out[14]:
undefined

In [15]:
var name = "The Window";
            
var object = {
    name : "My Object",

    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};

alert(object.getNameFunc()());  //"MyObject"


My Object
Out[15]:
undefined

下面第一行代码跟平常一样调用object.getName(),第二行代码在调用这个方法前先给它加上了括号,不改变了执行环境。第三行代码赋值行为,改变了执行环境:


In [16]:
var name = "The Window";
            
var object = {
    name : "My Object",

    getName: function(){
        return this.name;
    }
};

alert(object.getName());     //"My Object"
alert((object.getName)());   //"My Object"
alert((object.getName = object.getName)());   //"The Window" in non-strict mode


My Object
My Object
The Window
Out[16]:
undefined

模仿块级作用域

JavaScript没有块级作用域。


In [17]:
function outputNumbers(count){
    for (var i=0; i < count; i++){
        alert(i);
    }

    alert(i);   //count
}

outputNumbers(5);


0
1
2
3
4
5
Out[17]:
undefined

即使像下面这样错误地重新声明同一个变量,也不会改变它的值:


In [19]:
function outputNumbers(count){
    for (var i=0; i < count; i++){
        alert(i);
    }

    var i;    //variable re-declared
    alert(i);   //count
}

outputNumbers(5);


0
1
2
3
4
5
Out[19]:
undefined

我们在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中被使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。


In [20]:
function outputNumbers(count){
    (function () {
        for (var i=0; i < count; i++){
            alert(i);
        }
    })();

    alert(i);   //causes an error
}

outputNumbers(5);


0
1
2
3
4
10
Out[20]:
undefined

私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。

我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。有两种在对象上创建特权方法的方式。第一种是在构造函数中定义特权方法:

function MyObject(){
    // 私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    // 特权方法
    this.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
}

In [21]:
function Person(name){
    this.getName = function(){
        return name;
    };

    this.setName = function (value) {
        name = value;
    };
}

var person = new Person("Nicholas");
alert(person.getName());   //"Nicholas"
person.setName("Greg");
alert(person.getName());   //"Greg"


Nicholas
Greg
Out[21]:
undefined

不过构造函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的。第6章曾经讨论过,构造函数模式的缺点是针对每个实例都会创建同样的一组新方法,而使用静态私有变量来实现特权方法可以避免这个问题。

静态私有变量

通过在私有作用域中定义私有变量或函数:

(function(){
    // 私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    // 构造函数
    MyObject = function(){
    };

    // 公有/特权方法
    this.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
})();

In [22]:
(function(){
    var name = "";

    Person = function(value){                
        name = value;                
    };

    Person.prototype.getName = function(){
        return name;
    };

    Person.prototype.setName = function (value){
        name = value;
    };
})();

var person1 = new Person("Nicholas");
alert(person1.getName());   //"Nicholas"
person1.setName("Greg");
alert(person1.getName());   //"Greg"

var person2 = new Person("Michael");
alert(person1.getName());   //"Michael"
alert(person2.getName());   //"Michael"


Nicholas
Greg
Michael
Michael
Out[22]:
undefined

以这种方式创建静态私有变量会因为使用原型而增进代码复用,当每个实例都没有自己的私有变量。到底是使用实例变量,还是静态私有变量,最终要视情况而定。

模块模式

JavaScript是以对象字面量的方式来创建单例对象的:

var singleton = {
    name : value,
    method: function() {
        //这里是方法代码
    }
};

模块模式通过为单例添加私有变量和特权方法能够使其得到增强:

var singleton = function(){
    // 私有变量和私有函数
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    // 特权/公有方法和属性
    return {
        publicProperty: true;

        publicMethod: function(){
            privateVariable++;
            return privateFunction();
        }
    };
}();

In [23]:
function BaseComponent(){
}

function OtherComponent(){
}

var application = function(){

    //private variables and functions
    var components = new Array();

    //initialization
    components.push(new BaseComponent());

    //public interface
    return {
        getComponentCount : function(){
            return components.length;
        },

        registerComponent : function(component){
            if (typeof component == "object"){
                components.push(component);
            }
        }
    };
}();

application.registerComponent(new OtherComponent());
alert(application.getComponentCount());  //2


2
Out[23]:
undefined

增强的模块模式

这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。

var singleton = function(){
    // 私有变量和私有函数
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    // 创建对象
    var object = new CustomType();

    // 添加特权/公有方法和属性
    object.publicProperty = true;

    object.publicMethod = function(){
            privateVariable++;
            return privateFunction();
    };

    return object;
}();

In [24]:
function BaseComponent(){
}

function OtherComponent(){
}

var application = function(){

    //private variables and functions
    var components = new Array();

    //initialization
    components.push(new BaseComponent());

    //create a local copy of application
    var app = new BaseComponent();

    //public interface
    app.getComponentCount = function(){
        return components.length;
    };

    app.registerComponent = function(component){
        if (typeof component == "object"){
            components.push(component);
        }
    };

    //return it
    return app;
}();

alert(application instanceof BaseComponent);
application.registerComponent(new OtherComponent());
alert(application.getComponentCount());  //2


true
2
Out[24]:
undefined

In [ ]: