代码改变世界

【JavaScript】之函数表达式

2017-03-24 12:23  SiestaKc  阅读(162)  评论(0编辑  收藏  举报

今天主要复习了《JavaScript高级程序》中 函数表达式 这一章,主要是自己对闭包和this的概念还是理不清楚,导致在做小demo的时候这一块完成时懵懂不知的,先做个知识梳理,再继续加强实践吧..

一、函数表达式的特征

函数声明,在执行代码之前会先读取函数声明(同时意味着可以将函数声明放在调用它的语句的后面)

function functionName (arg0,arg1,arg2){

             //函数体

}

使用函数表达式定义函数时,无需对函数命名,从而也可以创建匿名函数。

函数表达式的特点:

  • 函数表达式不同于函数声明。函数声明要求一定要有名字,函数表达式不需要,没有名字的函数表达式也叫匿名函数。
  • 使用函数实现递归,递归函数应该始终使用argument.callee来递归地调用自身,不要使用函数名——函数名可能会发生变化。
function factorial(num){
   if(num<=1){
      return 1;
}else{
     return num*arguments.callee(num-1);
     }
}

二、闭包

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

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;
            }
}
//内部函数访问了外部函数中的变量propertyName   
   

如何彻底地理解闭包?

知道有关如何创建作用域以及作用域链有什么作用的细节。

变量对象:后台的每个执行环境都有一个表示变量的对象。

全局环境的变量对象始终存在,而像compare()函数这样的局部环境的对象变量,则只在函数执行的过程中存在。

function compare(value1,value2){   //①定义了compare函数
    if(value1<value2){
        return -1;
}else if(value1>value2){
        return 1;
}else{
       return 0;
    }
}
var result = compare(5,10);  //②全局作用域中调用compare(),这时,会创建一个包含arguments、value1、value2的活动对象

全局执行环境的变量对象(result、compare)在compare()执行环境的作用域链中处于第二位。

一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅存全局作用域(全局变量对象)。

    1. 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域、全局作用域。
    2. 通常,函数的作用域及其所有变量都会在函数执行后被销毁。
    3. 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

关于this对象

this对象是运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象

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

不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The Window";

var object = {
        name: "My Object",
        
       getNameFunc: function(){
       
              var that = this;
              
              return function(){
                       return that.name;
            };
      }
};
alert(object.getNameFunc()());   //"My Object"  

当闭包的作用域保存着无法被销毁的元素时,其内存永远不会被回收,这就导致了内存泄漏。所以我们使用闭包模仿块级作用域来解决问题。

三、模仿块级作用域

  1. 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中存下对该函数的引用。
  2. 结果就是函数内部的所有变量都会被理解销毁,不会造成内存泄漏或变量污染全局作用域
function outputNumbers(count){
   (function(){
           for(var i =0;i<count;i++){
               alert(i);   //匿名函数是一个闭包,它能够访问包含作用域中的所有变量
      })();
      alert(i);
}

四、私有变量

任何函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。

私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。

  • 我们可以使用闭包来实现能够有权访问私有变量的共有方法,该方法也叫特权方法
  • 而实现特权方法,我们使用构造函数模式、原型模式来实现,也可以使用模块模式、增强的模块模式来实现实例的特权方法。

原型模式

(function MyObject(){

      //私有变量和私有函数
      var privateVariable = 10;
      
      function privateFunction(){
            return false;
}
      //构造函数

MyObject = function(){ //函数声明只能创建局部函数,所以使用函数表达式
};
      //公有/特权方法
      MyObject.property.publicMethod = function(){   

privateVariable
++; return privateFcuntion();
};
})();

这个模式中,私有变量和函数都由实例共享,由于特权方法是在原型上定义的,因为所有实例都使用同一个函数。

而特权方法,作为一个闭包,总是保存着对包含作用域的引用。

(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"

以这种方式创建静态私有变量会因为使用原型而增进代码的复用,但每个实例都没有自己的私有变量。

模块模式

为单例创建私有变量和特权方法,单例,指的就是只有一个实例的对象。

//在web应用程序中,我们经常需要使用一个实例来管理应用程序级的信息,这个简单的例子创建了一个用于管理组件的application对象。

var
application = function(){ //私有变量和函数 var components = new Array(); //先声明了一个私有的components数组 //初始化 compenents.push(new BaseCompenent()); //并向该数组中添加了一个BaseComponent的新实例 //公共 return { //返回对象的getComponentCount()和registerComponent()方法,它们都是有权访问数组component的特权方法 getCompenentCount : function(){ return compenents.length; }, registerComponent : function(component){ if(typeof component =="object"){ components.push(component); } } }; }();

简而言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开以一些能够访问这些私有数据的方法,那么就可以使用模块模式。