函数表达式
定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。
函数声明会带来函数声明提升的效果,而函数表达式后面部分实际上是一个匿名函数。匿名函数相当于一个值,在把函数当成值使用的情况下都可以使用匿名函数。
1递归
递归时调用自身我们会用arguments.callee()代替函数名以方便复用,但是在严格模式下,不能通过脚本访问此属性。因此用函数表达式可以达成相同的结果。
var factorial = function f(num) { if (num < 1) { return 1 }else { return num * f(num - 1); } }
2.闭包
闭包就是指有权访问另一个函数作用域中变量的函数。
function creatComparision(peopertyName) { return function(object1,object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; return value1 - value2; }; }
这其中内部函数被返回了,在其他地方被调用的时候它依然可以访问到变量propertyName。这就是因为它的作用域链中包含了creatComparision的作用域。
一般来讲当函数执行完毕之后,局部活动对象就会被销毁,内存中仅存全局作用域,但是闭包的情况不同。
在以上的例子中,当返回的匿名函数在外部执行的时候,它的作用域链第一层是闭包的活动对象,然后是creatComparision的活动对象,最后是全局活动对象。
换句话说,在creatComparision()函数执行完成之后,creatComparision的作用域链被销毁,但是其活动对象依然保留提供组成闭包的作用域链。
因此,在外部使用函数表达式调用此匿名函数之后,应该显式地接触对匿名函数的引用,以便释放内存。
闭包与变量
作用域链的作用机制会造成这样的问题,即闭包会保留其作用域链的活动对象,闭包中的变量反映活动对象中实际的值,导致任何变量都只能取得最后的值。
function creatFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function() { return i; } } return result; //[10,10,10,10,10,10,10,10,10,10] }
要解决这样的困扰,就是让闭包中的变量不直接引用活动对象中,转而通过另一个匿名函数包裹的方式间接取得:
function creatFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function(num) { return function() { return i; } }(i); } return result; //[1,2,3,4,5,6,7,8,9,10] }
这种情况下,讲作用域中的i先传给num参数,因为函数参数是按值传递的,所以对每个循环的i变量都做了一个保存。
关于this对象
简单的理解,不考虑call()和apply()的情况下,匿名函数的执行环境具有全局性。(具体请见我的另一篇文章匿名函数的this指向为什么是window?)
所以当你在闭包中使用匿名函数的时候,this并不会沿用闭包的执行环境,因此需要做一些工作。
var name = 'Window'; var object = { name: 'Eric', getName: function() { var that = this; //将当前执行环境this传递给一个变量,这样在闭包中引用变量就不会造成this与预期不同 return function() { return that.name; //'Eric' }; } }
还有另一点关于this的思考,上面的代码实际上是不需要使用闭包的,那不使用闭包中的匿名函数是不是就不会造成this的困扰呢?
var name = 'Window'; var object = { name: 'Eric', getName: function() { return this.name; } } object.getName(); //'Eric' (object.getName)(); //'Eric' (1,object.getName)(); //'Window' (object.getName = object.getName)(); //'Window'
实际上,在函数执行语句中‘object.getName()’,括号左边的是一个referenceType,它的base属性为执行环境this提供引用。
任何会导致referenceType提前getValue的操作都会使base属性破坏(例如上面函数执行语句第三行的分组运算符和第四行的赋值操作),从而this = null,这种情况下this就指向全局环境了。
内存泄露
在使用引用计数作为垃圾回收策略的浏览器中,除了循环引用,闭包也会引起计数无法清除的问题。例如闭包的作用域链中保存了一个HTML元素,而闭包中引用了这个元素,这就意味着这元素无法被销毁。
要解决这样的问题,我们就需要解决去引用这个元素的方式,改直接引用为间接引用:
function assignHander() { var element = document.getElementById('baba'); var id = element; //在进入到闭包之前,将所需要的值先复制,这样闭包中就不需要对元素直接引用了 element.onclick = function() { alert(id); } element = null; }
注:实际上,我们发现由闭包带来的问题换言之就是作用域链带来的问题,解决问题的思路就是在进入闭包之前替换引用的方式,解除耦合关系。
3.模仿块级作用域
(function () { //模仿块级作用域,这里叫私有作用域 for (var i = 0; i < 10; i++) { alert(i); } })(); (function() { //利用私有作用于,可以执行函数之后不会占用空间,并且不会导致重名问题(多组开发时)。 var now = new Date(); if (now.getMonth() == 0 && now.getDate() == 1) { alert('Happy New Year!'); } })();
因为没有指向匿名函数的引用,所以函数在执行完毕之后就销毁其作用域链了。
4.私有变量
因为函数内部的变量或者方法外部是不能访问的,所以基于这个原理,利用函数我们可以实现私有变量,最简单的是利用构造函数实现:
function MyObject() { var name = "Eric"; //这和this.name = "Eric"是不一样的,这是定义一个变量而不是添加属性。 var sayName = function() { alert(this.name); //注意匿名函数中this }; this.publicMethod = function() {
console.log(name); //'Eric' console.log(this.name); //Undefined sayName(); //'Window' } } var object1 = new MyObject; object1.publicMethod();
实例中,你只能通过特权方法publicMethod()访问到name和sayName,并不能直接访问到它们,这样就完成了私有变量的实现。
但是,构造函数遗留的问题依然没有解决,那就是每个实例都要完整的复制所有的属性、方法,浪费大量的空间。
静态私有变量
为了解决单纯的构造函数的实例不能使用同一个属性方法引用的问题,我们引入原型模型。
为了解决变量外部不可见,成为私有变量,我们引入私有作用域。
(function() { //模仿块级作用域,使外部不可见name和sayName var name = 'Eric'; var sayName = function() { alert(name); }; MyObject = function() {}; //为了使构造函数外部调用,不能使用var或者函数声明的方式,因为它们都会使构造函数只在内部有效。 MyObject.prototype.publicMethod = function() { //为了使共有方法有同一个引用,使用原型模式 console.log(name); sayName(); } })(); var object1 = new MyObject(); object1.publicMethod();
同时,在以上的函数中,特权方法作为一个闭包,始终保存着对包含作用域的引用。因此这个私有作用域不会被销毁。
这个模式会增进代码的复用,但是每个实例都没有自己的私有变量。
模块模式
在单例的情况下,使用模块模式维护其私有变量,并且需要进行某些初始化是非常有用。
不用字面量的方式创建单例,而是通过为单例添加私有变量和特权方法让其得到增强。
var application = function() { //定义私有变量和方法 var components = new Array(); //初始化 components.push(new BaseComponent()); //BaseComponent()返回包含基类对象的数组 //特权方法 return { getComponentsLength: function() { return components.length; }, registerComponent: function(component) { if (typeof(component) == 'Object') { components.push(component); } } }; }();
增强的模块模式
如果单例必须是某种类型的实例,同时还必须添加某些属性和方法对其增强。
比如上面的例子中,application必须是BaseComponent的实例。
var application = function() { //定义私有变量和方法 var components = new Array(); //初始化 components.push(new BaseComponent()); //BaseComponent()返回包含基类对象的数组 //创建对象,application的一个局部副本 var app = new BaseComponent(); //特权方法 app.getComponentsLength = function() { return components.length; }; app.registerComponent = function(component) { if (typeof(component) == 'Object') { components.push(component); } }; //返回这个副本 return app; }();
这个模式下,application是BaseComponent的实例,并且有自己的私有变量和方法,可以通过特权函数访问它们。