JavaScript学习 6、函数表达式

 前文说过定义函数的方式有两种,一种是函数声明、一种是函数表达式。二者最大的区别是函数声明提高,即函数的声明在执行代码前会先被读取。编程

递归

 递归函数是在一个函数中经过名字调用自身的状况。前面咱们讲过的一个计算乘阶的函数:数组

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

console.log(factorial(5));    //120

咱们知道,函数名只是一个引用,因此也能够进行赋值,当factorial 被赋值为null或者其余函数引用时,就会发生错误,以下:闭包

var anotherFactorial = factorial;
factorial = null;
anotherFactorial(5);   //TypeError: factorial is not a function

前文讲过,使用arguments.callee 能够解决问题。函数

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

console.log(factorial(5));    //120
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //120

可是在严格模式下,使用arguments.callee 会致使错误。不过可使用命名函数表达式来达到相同的效果。this

"use strict"
var factorial = (function f(num){
    if(num <= 1){
        return 1;
    }else{
        return num*f(num - 1);
    }
});

console.log(factorial(5));    //120
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5));   //120

这种方式在严格模式和非严格模式下都能行得通。spa

闭包

 闭包是指有权访问另外一个函数做用域中的变量的函数。建立闭包的常见方式,就是在一个函数内部建立另外一个函数。prototype

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 obj1 = {
    name: "Lilei",
    age: 18
};
var obj2 = {
    name: "HanMeimei",
    age: 17
};
console.log(compareNames(obj1, obj2));
compareNames = null;

这里须要理解compareNames 函数的做用域链,函数的做用域链保存在内部的[[Scope]] 属性中,当函数被调用的时候就为函数建立一个执行环境,而后经过复制函数的 [[Scope]] 属性中的对象构建其执行环境的做用域链。做用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。指针

createComparisonFunction 函数做用域链包括自身的变量 和 全局变量,compareNames 函数的做用域链包括 自身变量 和 createComparisionFunction 的变量 和 全局变量。code

当createComparisonFunction 退出时,返回一个compareNames 函数,自身的做用域链销毁, 可是自身的活动对像因为被compareNames 引用,因此仍然会留在内存中,知道compareNames 函数被销毁( compareNames = null; )对象

1.闭包与变量

做用域链的这种配置机制引出了一个值得注意的反作用,即闭包只能取得包含函数中任何变量的最后一个值。

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

var res = createFunctions();
res.forEach(function(value, index, array){
    console.log(value.call());   // 10 10 10 ... 10 
});

函数返回十个函数组成的数组,数组中的十个函数的做用域链都是自身和createFunctions 的对象,读取变量 i 值时,找到做用域链的 createFunctions 对象,在数组中的函数调用的时候,createFunctions 对象中的 i 已经变成了10, 因此返回的函数返回值都是 10。

解决办法是再建立一个匿名函数,在做用域链中隔离开自身和 createFunctions 的对象。

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 res = createFunctions();
res.forEach(function(value, index, array){
    console.log(value.call());   // 0 1 2 ... 9
});

本例中,咱们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将当即执行该匿名函数的结果赋给了属猪。这里的匿名函数有一个参数 num, 也就是最终的函数要返回的值。在调用每一个匿名函数时,咱们传入了变量 i 。 因为函数参数是按值传递的,因此就会将变量i的当前值复制给 num。而在这个匿名函数内部,有建立并返回了一个访问num 的闭包。这样一来,result 数组中每个函数都有本身 num 变量的一个副本,所以就能够返回各自不一样的数值了。

2.闭包的this对象

在闭包中使用 this 翠系那个也可能会致使一些问题。匿名函数的执行环境具备全局性,所以其this对象一般指向 global。

var global = function(){
    return this;
}();
global.name = "The global";

var object = {
    name: "My Object",
    getNameFunc: function(){
        return function(){
            return this.name;
        };
    }
}
console.log(object.getNameFunc()());  //The global

前面说过,每一个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。 内部函数在搜索这两个变量时,只会搜索到其活动对象位置,所以永远不可能直接访问外部函数中的者两个变量。

模仿块级做用域

 如前所述,JavaScript 中没有块级做用域的概念,这意味着在块语句中定义的变量,其实是在包含函数中而非语句中建立的。即便后面从新声明变量,也于事无补。

for(var i = 0; i<10; i++){

}
var i;
console.log(i);  //10

 

匿名函数能够解决这个问题,用做块级做用域(一般被称为私有做用域)的匿名函数的语法以下:

(function(){
    //todo code 块级做用域
})();

 

以上代码定义并当即调用了一个匿名函数。将函数声明包含在一对全括号中,表示它其实是一个函数表达式。而金穗气候的另外一对圆括号会当即调用这个函数。

(function(){
    for(var i =0; i<10; i++){
    }
})();
console.log(i);  //ReferenceError: i is not defined

 

这种作法能够减小闭包占用内存的问题,由于没有指向匿名函数的引用。只要函数执行完毕,就能够当即销毁其做用域链了。

私有变量

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

咱们把有权访问私有变量和私有函数的共有方法称为特权方法(privileged method)。

有两种在对象上建立特权方法的方式:

第一种:在构造函数中定义特权方法。

function MyObject(){
       //私有变量
    var privateVar = 10;
        //私有方法
    function privateFunc(){
        return false;
    }
        //特权方法
    this.publicMethod = function(){
        privateVar++;
        return privateFunc();
    }
}

 

 定义特却方法有一个缺点,就是你必须使用构造函数模式来达到这个目的。构造函数构建对象的缺点是对每一个实例都会建立一样一组方法。

第二种:静态私有变量

(function(){
    //私有变量 私有方法
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
        //构造函数
    MyObject = function(){};
        //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVar++;
        return privateFunc();
    }
})();

 

这个模式建立了一个私有做用域,兵在其中封装了一个构造函数及相应的方法。在私有做用域中首先定义了私有变量和私有函数,而后又定义了构造函数和公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。须要注意的是,这个模式在定义构造函数时没有使用函数声明,而是使用了函数表达式。函数声明只能建立局部函数,但那不是咱们想要的。出于一样的缘由,咱们也没有在声明MyObject 时使用var 关键字。记住:初始化未经声明的变量,老是会建立一个全局变量。所以MyObject就i成了一个全局变量,可以在私有做用域以外被访问到。可是在严格模式下,给未经声明的变量赋值会致使错误。

多查找做用域链中的一个层次,就会在必定程度上影响查找的速度。这正是使用闭包和私有变量的一个明显的不足之处。

 

模块模式

 前面的模式用于为自定义类型建立私有的变量和特权方法。而道格拉斯所说的模块模式则是为单利建立私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来建立单例对象的。

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

 

模块模式经过为单例添加私有变量和特权方法可以使其获得加强。

var singleton = function(){
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
    return {
        publicProperty:  true,
        publicMethod: function(){
            privateVar++;
            return privateFunc();
        }
    };
}();

 

这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数的内部,首先定义了私有变量和函数。而后,将一个对象字面量做为函数的值返回。返回的对象字面量中质保含能够公开的属性和方法。

 加强的模块模式

改进模块模式,就是在返回对象以前加入对其加强的代码。这种加强的模块模式适合那些单例必须是某种类型的实例,同事还必须添加某些属性和(或)方法对其甲乙加强的状况。

var singleton = function(){
    var privateVar = 10;
    function privateFunc(){
        return false;
    }
    //建立对象
    var object = new CustomType();
    object.publicProperty = true;

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

 

小结

 在JavaScript 编程中,函数表达式是一种很是有用的技术。使用函数表达式能够无需对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript 函数的强大方式。

  • 函数表达式不一样于函数声明。函数声明要求有名字,但函数表达式不须要。没有名字的函数表达式也叫匿名函数。
  • 在没法肯定如何引用函数的状况下,递归函数就会变得比较复杂。
  • 地柜函数应该始终使用 arguments.callee 来递归调用自身,不要使用函数名----函数名可能会发生变化。

当在函数内部定义了其余函数是,就建立了闭包。闭包有权访问包含函数内部的全部变量。

  • 在后台执行环境中,闭包的做用域链包含着它本身的做用域、包含函数的做用域 和全局做用域。
  • 一般,函数的做用域及其全部变量都会在函数执行结束后销毁。
  • 可是,当函数返回了一个闭包是,这个函数的做用域将会一直在内存中保存到闭包不存在为止。

使用闭包能够在JavaScript 中模仿块级做用域(JavaScript中本省没有块级做用域的概念)。

  • 建立并当即调用一个函数,这样既能够执行其中的代码,有不会在内存中留下对该函数的引用。
  • 结果就是函数内部的全部变量都会被当即销毁----除非将某些变量赋值给了包含做用域(即外部做用域)中的变量。

闭包还能够用户与在对象中建立私有变量。

  • 即便JavaScript 中没有正式的私有变量属性的概念,但可使用闭包来实现公有方法,而经过公有方法能够访问包含做用域中定义的变量。
  • 有权访问私有变量的公有方法叫特权方法。
  • 可使用构造函数模式、原型模式来实现自定义类型的特却方法,也可使用模块模式、加强的模块模式来实现单例的特权方法。

JavaScript 中的函数表达式和闭包都是及其有用的特性,利用它们能够实现不少功能。不过由于建立闭包必须维护额外的做用域,因此过分使用它们可能会占用大量内存。

相关文章
相关标签/搜索