立即执行函数表达式(IIFE)
函数被包含在一队括号()内,因此成为了一个表达式,通过在末尾加上另外一个括号()可以立即执行这个函数。这种模式叫做立即执行函数表达式,即IIFE
方式一
var a = 1
(function foo() {
var a = 2
console.log(a) // 2
})()
console.log(a) // 1
复制代码
方式二
var a = 1
(function foo() {
var a = 2
console.log(a) // 2
}())
console.log(a) // 1
复制代码
这种形式与方式一的功能完全一样的
方式三
第三种用法是将需要运行的函数放在第二位,在IIFE执行之后当作参数传进去。这种模式在UMD(Universal Module Definition)项目中被广泛使用,尽管这种模式显得比较冗长,但是有些同学认为更容易理解
var a = 2
(funtion IIFE(def){
def(window);
})(function def(global){
var a = 3
console.log(a) // 3
console.log(global.a) // 2
})
复制代码
函数表达式def定义在片段的第二部分,然后当作参数被传递到IIFE函数定义的第一部分。最后,参数def被调用,并将window传入当作global参数的值
进阶用法:
IIFE函数也可以普通函数调用并传递参数进去
var a = 1
(function foo(params) {
var a = 2
console.log(a) // 2
console.log(params.a) // 1
})(window)
console.log(a) // 1
复制代码
这里调用时将windows对象传进去,参数名param其实值就是window
尾调用 && 尾递归
尾调用
定义: 当一个函数执行时的最后一个步骤时返回另一个函数的调用,这就叫做尾调用
包含尾调用的表达式:
条件操作符: ? :
逻辑或: ||
逻辑与: &&
逗号: ,
复制代码
// 1
const a = x => x ? f() : g();
// f() 和 g()都在函数尾部,这两个都是尾调用
// 2
const a = () => f() || g();
// g()有可能是尾调用,f()不是
// 上面的写法等同于
const a = () => {
const fResult = f(); // not a tail call
if (fResult) {
return fResult;
} else {
return g(); // tail call
}
}
// 当f()执行结果为false的时候g()才是尾调用
// 3
const a = () => f() && g();
// g()有可能是尾调用,f()不是
// 因为上述写法和下面的写法等效:
const a = () => {
const fResult = f(); // not a tail call
if (fResult) {
return g(); // tail call
} else {
return fResult;
}
}
// 当f()的结果为true的时候,g()才是尾调用
复制代码
尾调用优化
普通函数调用栈:
function baz() {
bar()
}
function bar() {
foo()
}
function foo() {
console.log(12)
}
baz()
复制代码
图中所示的调用栈方式是因为在函数执行的时候,在调用另外一个函数时没有return这个调用,js引擎认为还没有执行完成,因为会保留这个调用栈
优化: 尾调用优化只在严格模式下有效
function baz() {
return bar()
}
function bar() {
return foo()
}
function foo() {
console.log(12)
}
baz()
复制代码
函数执行时,当返回一个函数调用时当前的执行栈就会被弹出调用栈,而所调用的执行栈则会被推到调用栈中
解决的问题:
尾调用删除了外层无用的调用栈,只保存了内层的调用栈,节省了内存,防止 爆栈
尾递归
尾递归: 当一个函数调用自身,叫做递归
如:
function foo() {
foo()
}
复制代码
尾递归: 当一个函数尾调用自己,叫做尾递归
function foo() {
return foo()
}
复制代码
注意:上面两个例子都是没有结束条件的,是死循环
作用
使用递归解决阶乘
function factorial (num) {
if (num === 1) return 1;
return num * factorial(num - 1);
}
factorial(5); // 120
factorial(10); // 3628800
factorial(500000); // Uncaught RangeError: Maximum call stack size exceeded
复制代码
由于操作系统为js引擎调用栈分配的引擎是有大小限制的,所以当计算的数值过大时回爆出栈溢出的错误
使用尾递归来计算阶乘:
'use strict';
function factorial (num, total) {
if (num === 1) return total;
return factorial(num - 1, num * total);
}
factorial(5, 1); // 120
factorial(10, 1); // 3628800
factorial(500000, 1); // 分情况
// 注意,虽然说这里启用了严格模式,但是经测试,在Chrome和Firefox下,还是会报栈溢出错误,并没有进行尾调用优化
// Safari浏览器进行了尾调用优化,factorial(500000, 1)结果为Infinity,因为结果超出了JS可表示的数字范围
// 如果在node v6版本下执行,需要加--harmony_tailcalls参数,node --harmony_tailcalls test.js
// node最新版本已经移除了--harmony_tailcalls功能
复制代码
通过尾递归将数据复杂度从O(n)降成了O(1),如果数据足够大的话,会节省很多的计算时间。 由此可见,尾调用优化对递归操作意义重大
改进版一: 使用es6默认值
'use strict';
function factorial (num, total = 1) {
if (num === 1) return total;
return factorial(num - 1, num * total);
}
factorial(5); // 120
factorial(10); // 3628800
复制代码
改进版二: 使用一个函数去包裹尾递归函数
function tailFactorial (num, total) {
if (num === 1) return total;
return tailFactorial(num - 1, num * total);
}
function factorial (num) {
return tailFactorial(num, 1);
}
factorial(5); // 120
factorial(10); // 3628800
复制代码