JavaScript中有两种定义函数的方式:函数声明和函数表达式。使用函数表达式无须对函数命名,从而实现动态编程,也即匿名函数。有了匿名函数,JavaScript函数有了更强大的用处。
递归
递归是一种很常见的算法,经典例子就是阶乘。也不扯其他的,直接说递归的最佳实践,上代码:
// 最佳实践,函数表达式
var factorial = (function f(num) {
if (num <= 1) {
return 1;
} else {
return num * f(num - 1);
}
});
// 缺点:
// factorial存在被修改的可能
// 导致 return num * factorial(num - 1) 报错
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
// 缺点:
// arguments.callee,规范已经不推荐使用
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
递归就是这样,好多人还在使用arguments.callee的方式,改回函数表达式的方式吧,这才是最佳实践。
啰嗦一句,好多人觉得递归难写,其实你将其分为两个步骤就会清晰很多了。
边界条件,通常是if-else。递归调用。 按这个模式,找几个经典的递归练练手,就熟悉了。
闭包
很多人经常觉得闭包很复杂,很容易掉到坑里,其实不然。
那么闭包是什么呢?如果一个函数可以访问另一个函数作用域中的变量,那么前者就是闭包。由于JavaScript函数可以返回函数,自然,创建闭包的常用方式就是在一个函数内部创建另一个函数!
这并没有什么神奇的,在父函数中定义子函数就可以创建闭包,而子函数可以访问父函数的作用域。
我们通常是因为被闭包坑了,才会被闭包吓到,尤其是面试题里一堆闭包。
闭包的定义前面提了,如何创建闭包也说了,那么我们说说闭包的缺陷以及如何解决?
/* 我们通过subFuncs返回函数数组,然后分别调用执行 */
// 返回函数的数组subFuncs,而这些函数对superFunc的变量有引用
// 这就是一个典型的闭包
// 那么有什么问题呢?
// 当我们回头执行subFuncs中的函数的时候,我们得到的i其实一直都是10,为什么?
// 因为当我们返回subFuncs之后,superFunc中的i=10
// 所以当执行subFuncs中的函数的时候,输出i都为10。
//
// 以上,就是闭包最大的坑,一句话理解就是:
// 子函数对父函数变量的引用,是父函数运行结束之后的变量的状态
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i < 10; i++) {
subFuncs[i] = function() {
return i;
};
}
return subFuncs;
}
// 那么,如何解决上诉的闭包坑呢?
// 其实原理很简单,既然闭包坑的本质是:子函数对父函数变量的引用,是父函数运行结束之后的变量的状态
// 那么我们解决这个问题的方式就是:子函数对父函数变量的引用,使用运行时的状态
// 如何做呢?
// 在函数表达式的基础上,加上自执行即可。
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i < 10; i++) {
subFuncs[i] = function(num) {
return function() {
return num;
};
}(i);
}
return subFuncs;
}
综上,闭包本身不是什么复杂的机制,就是子函数可以访问父函数的作用域。
而由于JavaScript函数的特殊性,我们可以返回函数,如果我们将作为闭包的函数返回,那么该函数引用的父函数变量是父函数运行结束之后的状态,而不是运行时的状态,这便是闭包最大的坑。而为了解决这个坑,我们常用的方式就是让函数表达式自执行。
此外,由于闭包引用了祖先函数的作用域,所以滥用闭包会有内存问题。
好像把闭包说得一无是处,那么闭包有什么用处呢? 主要是封装吧...
封装
闭包可以封装私有变量或者封装块级作用域。
➙ 封装块级作用域
JavaScript并没有块级作用域的概念,只有全局作用域和函数作用域,那么如果想要创建块级作用域的话,我们可以通过闭包来模拟。
创建并立即调用一个函数,就可以封装一个块级作用域。该函数可以立即执行其中的代码,内部变量执行结束就会被立即销毁。
function outputNumbers(count) {
// 在函数作用域下,利用闭包封装块级作用域
// 这样的话,i在外部不可用,便有了类似块级作用域
(function() {
for (var i = 0; i < count; i++) {
alert(i);
}
})();
alert(i); //导致一个错误!
}
// 在全局作用域下,利用闭包封装块级作用域
// 这样的话,代码块不会对全局作用域造成污染
(function() {
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1) {
alert("Happy new year!");
}
})();
// 是的,封装块级作用域的核心就是这个:函数表达式 + 自执行!
(function() {
//这里是块级作用域
})();
➙ 封装私有变量
JavaScript也没有私有变量的概念,我们也可以使用闭包来实现公有方法,通过隐藏变量暴露方法的方式来实现封装私有变量。
(function() {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//构造函数
MyObject = function() {};
//公有/特权方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
};
})();