supce's blog

函数表达式

闭包


闭包,在JavaScript中很有意思。JavaScript是链式作用域,所以可以将函数内部与函数外部连接起来。由于在Javascript中,只有函数内部的子函数才能读取函数内部的变量,因此可以把内部的函数返回,通过返回的函数就能够读取到函数内部的变量。
于是,可以把闭包简单理解成定义在一个函数内部的函数,并且该内部函数访问了外部的变量。

console.log("-----闭包-----");
function fun(){
    var a = 1;
    return function f(){
        console.log(a);
    };
}
var result = fun()();

闭包与变量

链式作用域会带来一个问题,在闭包中,取得的包含函数的变量,是其最终值。举个例子

console.log("-----闭包与变量-----");
function createFunctions(){
    var array = new Array();
    for(var i=0;i<10;i++){
        array[i] = function(){
            return i;
        }
    }
    return array;
}
var result = createFunctions();
console.log(result[0]());  //10

上面的例子中,得到的是10并不是0
可以通过建立一个匿名函数。并且立即执行该匿名函数来解决

function createFunctions(){
    var array = new Array();
    for(var i=0;i<10;i++){
        array[i] = function(num){
            return function(){
                return num;
            }
        }(i);
    }
    return array;
}
var result = createFunctions();
console.log(result[0]());  //0

关于this对象

console.log("-----关于this对象-----");
var name = "The Windows";
var o = {
    name : "The O",
    getNameFun : function(){
        return function(){
            return this.name;
        };
    }
}
console.log(o.getNameFun()());  //The Windows
var object = {
    name : "The Object",
    getNameFun : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
}
console.log(object.getNameFun()());  //The Object
var myObject = {
    name :  "myObject",
    getName : function(){
        return this.name;
    }
}
console.log(myObject.getName());
console.log((fun=myObject.getName)());  //The Windows

模仿块级作用域

JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含在函数中而非语句中创建的

function fun(num){
    for(var i=0;i<num;i++){
        //do something
    }
    console.log(i);
}
fun(5);  //5

可以看出变量i并不会如同在C++和Java等语言中,随着循环的结束而销毁,即便是重新声明,不初始化,也不会改变它的值。
可以利用匿名函数来膜(模?)仿块级作用域来解决这个问题

(function(){
    //块级作用域
})();

使用这种方式,可以限制向全局作用域中添加过多的变量和函数,同时减少闭包占用内存的问题。

私有变量

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

console.log("---私有变量---");
function Person(name){
    this.age = 22;
    this.getName = function(){
        return name;
    };
    this.setName = function(value){
        name = value;
    }
}
var person = new Person("Nico");
console.log(person.age);         //22
console.log(person.name);        //undefined
console.log(person.getName());   //Nico

上面的例子中有个问题,就是每个实例都会创建一组相同的方法。为了解决这个问题,可以使用静态私有变量。

静态私有变量

console.log("-----静态私有变量-----");
(function(){
    var name = "";
    Person = function(value){
        name = value;
    }
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function(value){
        name = value;
    }
})();
var staticPerson1 = new Person("Nico");
var staticPerson2 = new Person("Sora");
console.log(staticPerson1.getName());   //Sora

这个例子中的 Person 构造函数与 getName() 和 setName()方法一样,都有权访问私有变量 name 。在这种模式下,变量 name就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用 setName() 会影响所有实例。而调用 setName() 或新建一个 Person 实例都会赋予 name属性一个新值。结果就是所有实例都会返回相同的值。
以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

模块模式

模块模式主要是为单列创建私有变量和特权方法的。其语法如下:

console.log("-----模块模式-----");
var single = function(){
    var privateProperty = "private";
    function privateFun(){
        return "privateFun"
    };
    return {
        publicProperty : "public",
        publicFun : function(){
            return "publicFun";
        },
        getPrivate : function(){
            return privateProperty;
        }
    };
}();
console.log(single.publicFun());   //publicFun
console.log(single.getPrivate());  //private

模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。
由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上来讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。例如:

var application = function(){
    var components = new Array();   //私有变量
    components.push(new BaseComponent());  //私有变量初始化
    //公共方法
    return {
        getComponentCount : function(){
            return components.length;
        },
        registerComponent : function(component){
            if(typeof component == "object"){
                components.push(component);
            }
        }
    };
}();

对于必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,适合模块模式。