在JavaScript中,函数是个很是重要的对象,函数一般有三种表现形式:函数声明,函数表达式和函数构造器建立的函数。html
本文中主要看看函数表达式及其相关的知识点。前端
首先,看看函数表达式的表现形式,函数表达式(Function Expression, FE)有下面四个特色:缓存
下面就经过一些例子来看看函数表达式的这四个特色。安全
例子一:在下面代码中,"add"是一个函数对象,"sub"是一个普通JavaScript变量,可是被赋值了一个函数表达式" function (a, b){ return a - b; } ":模块化
function add(a, b){ return a + b; } var sub = function (a, b){ return a - b; } console.log(add(1, 3)); // 4 console.log(sub(5, 1)); // 4
经过这个例子,能够直观的看到函数表达式的前两个特色:函数
例子二:为了解释函数表达式另外两个特色,继续看看下面的例子。this
console.log(add(1, 3)); // 4 console.log(sub); // undefined console.log(sub(5, 1)); // Uncaught TypeError: sub is not a function(…) function add(a, b){ return a + b; } var sub = function (a, b){ return a - b; }
在这个例子中,调整了代码的执行顺序,此次函数"add"执行正常,可是对函数表达式的执行失败了。spa
对于这个例子,能够参考"JavaScript的执行上下文"一文中的内容,当代码开始执行的时候,能够获得下图所示的Global VO。code
在Global VO中,对"add"函数表现为JavaScript的"Hoisting"效果,因此即便在"add"定义以前依然可使用;server
可是对于"sub"这个变量,根据"Execution Context"的初始化过程,"sub"会被初始化为"undefined",只有执行到" var sub = function (a, b){ return a - b; } "语句的时候,VO中的"sub"才会被赋值。
经过上面这个例子,能够看到了函数表达式的第四个特色
例子三:对上面的例子进一步改动,此次给函数表达式加上了一个名字"_sub",也就是说,这里使用的是一个命名函数表达式。
var sub = function _sub(a, b){ console.log(typeof _sub); return a - b; } console.log(sub(5, 1)); // function // 4 console.log(typeof _sub) // undefined console.log(_sub(5, 1)); // Uncaught ReferenceError: _sub is not defined(…)
根据这段代码的运行结果,能够看到"_sub"这个函数名,只能在"_sub"这个函数内部使用;当在函数外部访问"_sub"的时候,就是获得"Uncaught ReferenceError: _sub is not defined(…)"错误。
因此经过这个能够看到函数表达式的第三个特色:
到了这里,确定会有一个问题,"_sub"不在VO中,那在哪里?
其实对于命名函数表达式,JavaScript解释器额外的作了一些事情:
下面是表示这一过程的伪代码:
specialObject = {}; Scope = specialObject + Scope; _sub = new FunctionExpression; _sub.[[Scope]] = Scope; specialObject. _sub = _sub; // {DontDelete}, {ReadOnly} delete Scope[0]; // 从做用域链中删除特殊对象specialObject
这一小节可能有些钻牛角尖,可是这里想演示递归调用可能出现的问题,以及经过命名函数表达式以更安全的方式执行递归。
下面看一个求阶乘的例子,因为函数对象也是能够被改变的,因此可能会出现下面的状况引发错误。
function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } console.log(factorial(5)) // 120 newFunc = factorial factorial = null console.log(newFunc(5)); // Uncaught TypeError: factorial is not a function(…)
这时,能够利用函数的arguments对象的callee属性来解决上面的问题,也就是说在函数中,老是使用"arguments.callee"来递归调用函数。
function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } }
可是上面的用法也有些问题,当在严格模式的时候"arguments.callee"就不能正常的工做了。
比较好的解决办法就是使用命名函数表达式,这样不管"factorial"怎么改变,都不会影响函数表达式" function f(num){…} "
var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } });
在JavaScript中,没有块做用域,只有函数做用域,函数内部能够访问外部的变量和函数,可是函数内部的变量和函数在函数外是不能访问的。
因此,经过函数(一般直接使用函数表达式),能够模块化JavaScript代码。
为了可以到达下面的目的,咱们能够经过函数表达式来创建模块。
下面看一个简单的例子:
var Calc = (function(){ var _a, _b; return{ add: function(){ return _a + _b; }, sub: function(){ return _a - _b; }, set: function(a, b){ _a = a; _b = b; } } }()); Calc.set(10, 4); console.log(Calc.add()); // 14 console.log(Calc.sub()); // 6
代码中经过匿名函数表达式建立了一个"Calc"模块,这是一种经常使用的建立模块的方式:
除了返回一个对象的方式,有的模块也会使用另一种方式,将包含模块公共接口的对象做为全局变量的一个属性。
这样在代码的其余地方,就能够直接经过全局变量的这个属性来使用模块了。
例以下面的例子:
(function(){ var _a, _b; var root = this; var _ = { add: function(){ return _a + _b; }, sub: function(){ return _a - _b; }, set: function(a, b){ _a = a; _b = b; } } root._ = _; }.call(this)); _.set(10, 4); console.log(_.add()); // 14 console.log(_.sub()); // 6
在上面两个例子中,都使用了匿名的函数表达式,而且都是当即执行的。若是去看看JavaScript一些开源库的代码,例如JQuery、underscore等等,都会发现相似的当即执行的匿名函数代码。
当即调用的函数表达式一般表现为下面的形式:
(function () { /* code */ })(); (function () { /* code */ } ()); 在underscore这个JavaScript库中,使用的是下面的方式: (function () { // Establish the root object, `window` in the browser, or `exports` on the server. var root = this; /* code */ } .call(this));
在这里,underscore模块直接对全局变量this进行了缓存,方便模块内部使用。
本文简单介绍了JavaScript中的函数表达式,并经过三个例子解释了函数表达式的四个特色。
经过函数表达式能够方便的创建JavaScript模块,经过模块能够实现下面的效果: