js函数表达式
定义函数的方式
定义函数表达式的方法有两种,一种是函数声明,另一种是函数表达式.
函数声明的方式,关于函数声明的方式,它的一个重要的特性就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面,like this
1 sayHi();//声明函数(function declaration hoisting) 2 function sayHi(){ 3 alert("Hi!"); 4 }
函数表达式有多种表达方式,下面是最常见的一种
var func = function(agr1,arg2){//创建匿名函数 alert(arg1 + ' ' + arg2);//函数体 }
函数表达式与其他表达式一样,使用前必须赋值。比如下面的代码就会导致错误。
var condition = true; //never do this! 不同的浏览器会做出不同的行为 不要这么做! if(condition){ function sayHi(){ alert("Hi!"); } } else { function sayHi(){ alert("Yo!"); } } sayHi();
函数可以赋值给变量,自然也可以当作返回值返回。
递归
就像每一门语言一样,任何方法或者函数解决问题的思路有递归和枚举两种基本思路,但是js中的递归还是有个小陷阱的,这里需要注意。看看下面的代码吧,只是一个简单的求阶乘的函数。
//求阶乘,看起来并没有问题 function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //error!
这里隐藏的问题就是,如果先用一个变量anotherFactorial指向factorial函数对象(函数对象哦),再将fatorial置空(相当于window对象的factorial属性=null),再执行anotherFactorial()时,会发现调用fatorial时会发生错误的,原因是null不是函数。那么为了避免这样的问题我们可以这样改进这个函数,使用arguments.callee可以解决这个问题。改进后的代码如下:
function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //24
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。首先,闭包是一个函数,然后闭包函数有权访问另一个函数的作用域中的变量。常见的创建闭包的方式就是在一个函数内部创建另一个函数。看看下面的代码
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 0; }else{ return 1; } } }
要理解闭包就必须回顾下作用链的概念,当某个函数第一次被调用时,就会创建一个执行环境(excution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(【【Scope】】)。然后,使用this,arguments和其他命名参数的值来初始化函数的活动对象(activation object),但是在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象始终处于第三位,...以此类推,直至作于作用域链重点的全局执行环境。
先从一个简单的函数分析下作用域链,代码如下:
functon compare(value1,value2){ if(value1 < value2){ return -1; }else if(value1 == value2){ return 0; }else{ return 1; } }
var result = compare(5,10);
看看JS高级编程的这个图。
后台的每个执行环境都有一个表示变量的对象--变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对像,则只在函数执行的过程中存在。在创建compare()函数时(js文件被加载时,函数对象无论是否调用,只要声明了就会作为window对象的一个属性,但是函数表达式的方式除外),会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在函数内部的[[Scope]]属性当中,当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象从而构建起执行环境的作用域链。此后又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域的前端。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
一般来讲,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是,闭包的情况就有所不同。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中,所以内部函数的作用域链实际上会包含外部函数的活动对象,这样内部函数就可以访问外部函数的变量了。更为重要的是外部函数执行完成后,其活动对象也不会被销毁,因为内部匿名函数的作用链仍然在引用这个活动对象。换句话说,当外部函数执行完成后,外部函数的作用域链会被销毁,但是外部函数的活动对象依然被内部匿名函数的作用域链所引用,这样外部函数的活动对象就不会被销毁,直到匿名函数被销毁后(解除对活动对象的引用),外部函数的活动对象才会被销毁。再看看下面的这个图:
块级作用域
JavaScript是没有块级作用域的的概念的,这就意味着在块语句中定义的变量,实际上是包含在函数中而非包含在在块语句中创建的,来看看下面的例子。
function blockTest(count){ for(var i = 0;i <= count ;i++){ console.debug(i); } //如果这是c/c++/java 那么这里应该报未定义错误 console.debug(i);//但是js不会 } blockTest(3);
在Java/C等语言中变量i只会在for循环的语句块中定义,循环一旦结束,变量i就会被销毁,而在js中并不是如此,变量i是被定义在blockTest()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它,即使像下面这样错误的重新声明同一个变量,也不会改变它的值。
function outputNumbers(count) { for (var i = 0; i < count; i++) { console.debug(i); } var i; //variable re-declared console.debug(i); //count }
JavaScript从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见。匿名函数可以用来模仿块级作用域并且避免这个问题。
用块级作用域(通常也称为私有作用域)的匿名函数的语法如下所示:
(function(){ //这里是私有作用域 })();
以上代码定义并且立即执行了一个匿名函数。将函数的声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。如果觉得这个不好理解,我们可以写成这样
like this
var blockTest = function(){//使用函数表达式来定义一个函数 //这里是块级作用域 } blockTest();//立即调用这个函数
//把上面的两步写成一步 (functio(){ //块级作用域 })//函数表达式的方法定义一个函数 ()//立即执行这个函数
这种利用匿名函数表达式来实现块级作用域的技术常常被用在函数的外部,从而限制向全局作用域中添加过多的变量和函数,一般来说,我们都应该尽量少的向全局作用域添加变量和函数。在一个由很多人员参与开发的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可以使用自己的变量,而又不用担心搞乱全局作用域。
私有变量
特权函数构造器模式
name属性在每个对象中都有一份,不会引起共享错误,getName和setName方法是用闭包形式访问name变量,这样避免了外部环境对Person的name变量的访问,只能通过函数对Person的name变量访问
function Person(name){ this.getName = function(){ return name; }; this.setName = function(value){ name = value; }; } var person = new Person('Nicholas'); console.debug(person.getName()); person.setName('kobe Bryant'); console.debug(person.getName());
或者name属性不作为参数,而是作为构造函数内的一个变量,这样对于外部环境是封闭不可见的,如果想要访问name属性(变量),可以使用闭包的形式访问,like this
//构造函数模式实现私有变量(通过闭包实现) function Person(nameStr){ var name = nameStr;//私有变量 this.age = 15;//公有变量 this.getName = function(){ return name; }; this.setName = function(value){ name = value; }; this.getInfo = function(){ console.debug(name + " " +this.age); }; } var p1 = new Person('Lebron'); var p2 = new Person('Paul'); p1.getInfo(); p2.getInfo(); p1.setName('kobe Bryant'); p1.getInfo(); p2.getInfo();
但是这种构造器模式每次调用时都会创建函数对象,耗费资源,函数不可以共享。
静态私有变量模式
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,基本模式如下
//静态私有变量 (function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction() { return false; } //构造函数 MyObject = function(){ }; //公有/特权方法 MyObject.prototype.publicMethod = function(){ privateVariable++; return privateFunction(); } })();
下面的代码中的这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中首先定义了私有变量和私有函数,并且在定义构造函数时没有使用函数声明而是使用了函数表达式的方式,因为函数声明只能创建局部函数,但我们需要在外部访问这个构造函数,所以MyObject()就成了一个全局变量,在私有作用域的外部也可以访问。需要注意的是name属性是所有实例所共享的,相当于静态共享属性
//静态私有变量 (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"); console.debug(person1.getName()); //"Nicholas" person1.setName("Greg"); console.debug(person1.getName()); //"Greg" var person2 = new Person("Michael"); console.debug(person1.getName()); //"Michael" console.debug(person2.getName()); //"Michael"
单例对象的模块模式(module pattern)
上面所说的方法都是为自定义类型私有化变量/函数的,那么对于一个对象而言或者说单例对象而言,如何实现其属性或者函数的私有化呢?先来看看不需要实现对象内属性私有化的对象该如何定义
var singleton = { name : 'value', method : function(){ //方法代码 } };
对象是以对象字面量的方式定义的,如果想要隐藏或者说私有化对象的属性或者方法那么选择使用闭包的形式,也就是道格拉斯所提出的模块模式(module pattern),其语法就像是这样
//道格拉斯所说的模块(module pattern ) var singleton = function(){ //私有变量和私有函数 var privateVariable = 20; function privateFunction(){ return false; } //特权/共有方法和属性 return { publicProperty : true, publicMethod : function(){ privateVariable++; return privateFunction(); } } }();
模块模式适用于要私有化变量,并且在初始化时需要做一定操作,并且在私有化属性的同时开放访问接口的情况使用看看下面的例子
function BaseComponent(){ } console.debug("====================="); 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); } } }; }(); console.debug(application.getComponentCount());
增强的模块模式
进一步增强的模块模式是指在返回对象志强对其增强功能,这种增强的模块模式适合那些单例必须是某种类型的实例。就像下面这样
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; }(); alert(application instanceof BaseComponent); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2
ss