JS学习笔记(四)(闭包)

2017-04-04  本文已影响21人  丨ouo丨

最近学这块知识学得有些吃力。还有很多遗漏的地方,只能以后多看些书来弥补了。
<h1 id="7">第7章 函数表达式</h1>

函数定义的两种方式:函数声明,函数表达式。这个在第5章函数声明与函数表达式也有提到的
函数声明:

函数表达式:

<h2 id="7.1">7.1 递归</h2>

arguments.callee这是个很有用的属性,指向正在执行的函数的指针。可以用它实现对函数的递归调用,第5章函数内部属性也讲到过。贴一段经典代码!

function factorial(num){
    if (num <= 1){
        return 1;
    }else{
    return num * arguments.callee(num-1);
    }
}

arguments.callee比用函数名更保险。不过严格模式下会有错误。

var factorial = (function f(num){
    if (num <= 1){
        return 1;
    }else{
    return num * arguments.callee(num-1);
    }
});

以上代码就是把函数f赋值给factorial了

<h2 id="7.2">7.2 闭包</h2>

这里刚开始看的时候有许多概念不懂,导致前后不能连贯,对作用域链一直存在疑问。所以我首先列举一下一些重要的概念。之前在第4章提到过的执行环境。我看了当时做的笔记,记的不是很完整。 所以在这里也补充一下

执行环境

作用域链:

变量对象:

活动对象:

当创建一个函数时:

  1. 创建预先包含全局变量对象的作用域链
  2. 这个作用域链保存在函数内部的[[Scope]]属性中

当第一次调用函数时:

  1. 创建一个执行环境(注意要调用的时候才会有执行环境)
  2. 复制函数的[[Scope]]属性中的对象,构建这个执行环境的作用域链
  3. 创建一个活动对象,使用this、arguments和其他命名参数的值来初始化函数的活动对象,把这个活动对象推入执行环境作用域链的前端
  4. 外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象在第三位,以此类推,直到作为作用域链终点的全局执行环境

下面结合书上的例子来看一下上述的两个过程

function compare(value1, value2){  
    if(value1 < value2){  
        return -1;  
    }else if(value1 > value2){  
        return 1;  
    }else{  
         return 0;  
    }  
}
var result = compare(5, 10);
  1. 创建这个函数的时候:


    创建.png
  2. 第一次调用

一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况要特殊一点。

在另一个函数内部定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域链中。下面看另一个例子

function createComparisonFunction(propertyName){
    return function(object1, object2){
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if (value1 < value2){
            return -1;
        }else if (value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
}
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({name:"xjh"},{name:"xkld"});
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

也就是说这个时候里面的匿名函数是有权访问propertyName的

<h3 id="7.2.1">7.2.1 闭包与变量</h3>

闭包保存的是整个变量对象,不是某个特殊的值

<h3 id="7.2.2">7.2.2 关于this对象</h3>

this对象

<h3 id="7.2.3">7.2.3 内存泄漏</h3>

如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁

<h2 id="7.3">7.3 模仿块级作用域</h2>

JS中没有块级作用域的概念,任何变量都是在函数中创建的
当重复声明一个变量时,只会对后续的声明视为不见(不过可以执行后续声明的变量初始化)

模拟块级作用域(私有作用域):

(function(){        //在外面加()的目的是把函数声明转换成函数表达式。JS中函数声明后面不能加圆括号
    //块级作用域
})();

这段代码定义并调用了一个匿名函数,将函数声明包含在()中,最后的()会立即调用这个函数
定义匿名函数可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链

<h2 id="7.4">7.4 私有变量</h2>

JS没有私有成员的概念:所有对象属性都是共有的,不过有一个私有变量的概念:不能在函数的外部访问这些变量
私有变量包括函数的参数、局部变量、在函数内部定义的其他函数
可以利用闭包创建用于访问私有变量的公有方法
特权方法:有权访问私有变量和私有函数的公有方法

创建特权方法的两种方式:

  1. 在构造函数中定义特权方法
    function MyObject(){
        var privateVar = 10;
        function privateFunction(){
            return false;
        }
        this.publicMethod = function(){
            privateVar++;
            return privateFunction();
        };
    }

这个privateVar和privateFunction可以看成是私有的,因为在别的地方无法访问到这些变量(可以参考私有作用域for循环里面的i)
publicMethod因为是对象的属性(前面有this的),这样的话用构造函数创建一个实例的时候就有了这个公有的方法。而这个publicMethod的作用域链包含着MyObject的作用域链,就可以访问到对应的私有变量了
看如下代码:

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

getName()和setName()作为闭包能通过作用域链访问name。私有变量name在Person的每一个实例中都不同,每次调用构造函数都会重新创建这两个方法。这个方法和通过构造函数创建对象一样有个缺点,就是每次都要重新创建get和set这两个方法

  1. 使用静态私有变量

<h3 id="7.4.1">7.4.1 静态私有变量</h3>

(function(){
    var privateVar = 10;
    function privateFunction(){
        return false;
    }
    MyObject = function(){//定义了一个全局变量MyObject指向这个匿名构造函数
    }; 
    MyObject.prototype.publicMethod = function(){
        ...
    };
})();

以上代码主要是在私有作用域里面定义了一个构造函数。利用这个构造函数的原型访问私有变量
4.2中提到过:使用var声明的变量会自动被添加到最接近的环境中。在函数内部就是局部环境,with语句中就是函数环境。不使用var声明变量自动添加到全局环境。这里定义构造函数时不用函数声明(function MyObject())的主要原因是函数声明只能创建局部函数。而用函数表达式,变量前不加var就可以定义一个全局变量,能够在私有作用域外被访问到(严格模式下会报错)

但是这个方法每个实例都可以对name进行修改,name就成为一个静态私有变量

<h3 id="7.4.2">7.4.2 模块模式</h3>

前面两种模式主要用于为自定义类型创建私有变量和特权方法。模块模式是为单例创建私有变量和方法
JS中以对象字面量方式创建单例对象

var singleton = {
    name:value;
    method:function(){
    }
};

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

var singleton = function(){
    var privateVar = 10;
    function privateFunction(){
        return false;
    }

    return {
        publicProperty:true,
        publicMethod:function(){
            ...
        }
    }
};

以上代码返回一个对象字面量,对象字面量里的函数有权访问私有变量和函数,在外部可以通过singleton.publicMethod()这种形式访问
从本质上讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的(类比java中的单例模式)

var application = function(){
    var components = new Array();
    //初始化
    components.push(new BaseComponent());//这两个语句在第一次执行后就不再执行,因为外面只会使用return的两个方法访问该私有变量
    return {
        getComponentCount:function(){
            return components.length;
        },
        registerComponent:function(component){
            if(typeof component == "Object"){
                components.push(component);
            }
        }
    };
}();

<h3 id="7.4.3">7.4.3 增强的模块模式</h3>

适合一些单例必须是某种类型的实例,同时必须添加某些属性或方法对其加以增强的情况

var singleton = function(){
    var privateVar = 10;
    function privateFunction(){
        return false;
    }

    var object = new CustomType();
    object.publicProperty = true;
    object.publicMethod = function(){
        ...
    }
    return object;
}();

以上代码要求singleton对象必须是CustomType的实例(区别就是把公共属性和方法定义到CustomType实例中了)

<h2 id="7.5">7.5 小结</h2>

上一篇 下一篇

猜你喜欢

热点阅读