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