Loading

JS函数表达式

函数表达式是定义函数的一种方式,另一种是之前提到的函数声明。

//函数声明
function functionName(args){
    //函数体
}


//函数表达式
var functionName = function(args){
    //函数体
};

 函数声明和函数表达式之间的区别,主要是函数声明提升,意思是在执行代码之前会读取函数声明。

没有名字的函数表达式也叫匿名函数。

 

一、递归

递归是一个函数通过名字调用自身。

因为函数名可能会发生改变,如果函数内部调用自身的语句仍使用原来的名字,就会发生错误,在这种情况下,使用arguments.callee可以解决这个问题。arguments.callee是一个指向正在执行的函数的指针。

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

除了使用arguments.callee,还可以使用命名函数表达式来达成同样的结果。

var factorial = (funtion f(num) {
    if num (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    }
});

 

二、闭包

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

关于作用域:当某个函数被调用时,会创建一个执行环境及对应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。作用域链就是由一组有序(按调用顺序由内到外)的活动对象的引用组成。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。   作用域链本质上是一个指向变量对象的指针列表,他只引用但不包含实际对象。  一般来讲,当函数执行完毕后,局部活动对象会被销毁,内存中仅保存全局作用域。但是闭包的情况又有所不同。

创建闭包必须维护额外的作用域:匿名函数作为返回值在函数内部定义,如果在外部通过函数表达式引用并调用这个匿名函数,就形成了闭包。在函数执行后,因为匿名函数仍被引用,所以匿名函数的活动对象和作用域链依然存在,这导致匿名函数所在的函数的活动对象仍然处于被引用状态,留在内存中。  可以通过解除对匿名函数的引用(设置引用值为null)来解决,这样就释放了内存。

1. 闭包与变量

闭包保存的是整个变量对象,而不是某个特殊变量。所以对于具有同一变量名的值,闭包只会取得最后一个值。关于这个问题,下面的例子可以清晰地说明:

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

这个函数会返回一个函数数组。表面上看,似乎数组内每个函数都应该返回自己的索引值。但实际上,每个函数都返回10,因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以他们引用的都是用一个变量i=10。这个问题可以通过创建另一个匿名函数解决。

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

每循环一次,外层的匿名函数就执行一次,将索引值传入当前位置的函数里(虽然都叫num,但是每个num都是按值传递,都只是一个副本)。

2. 关于this对象

每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

this对象是在运行时基于函数的执行环境绑定的。虽然匿名函数在某个函数内定义,但一般匿名函数都是在全局函数中执行,所以其this对象通常指向window。

如果想访问外部作用域的this对象,可以先在外部函数中将this的值保存在一个闭包能访问到的变量里。

3. 内存泄漏

如果闭包创建了一个循环引用,就会导致无法减少变量的引用数,因此占用的内存就永远不会回收。

 

三、 模仿块级作用域

JS中只有执行环境(函数作用域)的概念,而没有块级作用域的概念。所以如果想实现块级作用域,就要通过匿名函数来模仿。具体步骤是先定义一个函数,然后立即调用它。这样即可以执行其中的代码,又不会在内存中留下对该函数的引用。结果就是函数内部的所有变量都会立即被销毁。

因为函数声明后面不能跟圆括号,所以我们选择使用函数表达式的方式。代码如下:

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

 

四、私有变量

JS中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量(私有变量)。

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

特权方法是有权访问私有变量和私有函数的公有方法。有两种在对象上创建特权方法的方式。第一种是在构造函数中定义特权方法,基本模式如下:

function MyObject(){

    //私有变量和私有函数


    //特权方法
    this.func = function (){
    //有权访问私有变量和函数
    };
}

但是这种方式针对每个实例都会创建同样一组新方法,而是用私有静态变量来实现特权方法可以避免这个问题。

1. 静态私有变量

在私有作用域中定义私有变量和函数,并省略var构造全局函数及下及相应的特权方法。

(function(){

    var name = "";
    
    Person = function(value){                
        name = value;                
    };
    
    Person.prototype.getName = function(){
        return name;
    };
    
    Person.prototype.setName = function (value){
        name = value;
    };
})();

2. 模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。模块模式则视为单例创建私有变量和特权方法。单例,指只有一个实例的对象。

在私有作用域中定义私有变量和函数,并返回公有变量和特权方法,由单例接收。

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);
            }
        }
    };
}();

3. 增强的模块模式

和模块模式类似,不同的是显式创建要返回的对象,再返回之前可以添加某些属性和方法对其加以增强。这种模式适合那些单例必须是某种类型的实例的情况。

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;
}();

 

posted @ 2018-09-06 17:51  美味的糯米  阅读(1799)  评论(0编辑  收藏  举报