本文共 3051 字,大约阅读时间需要 10 分钟。
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(); };})();
转载于:https://blog.51cto.com/crecent/2073035