一, 函数表达式
函数表达式不同于函数声明. 函数声明要求有名字, 但表达式不需要.
- var fun = function (arg0,arg1) {
- };
创建一个匿名函数(也称拉姆达函数), 赋值给变量.
二, 递归
递归函数是一个函数通过名字调用自身的情况下构成的. 例如:
- function factorial(num){
- if(num <= 1){
- return 1;
- }else{
- return num * factorial(num-1);
- }
- }
- console.log(factorial(5));
上例中函数体内调用自身函数时函数名固定了, 如果给函数保存给其他变量就会导致错误, 更好的写法:
- function factorial(num){
- if(num <= 1){
- return 1;
- }else{
- return num * arguments.callee(num-1);
- }
- }
- console.log(factorial(5));
但严格模式下 arguments.callee 不可用, 可以改写为:
- var factorial = (function f(num){
- if(num <= 1){
- return 1;
- }else{
- return num * f(num-1)
- }
- });
- console.log(factorial(5));
- var factorialA = factorial;
- console.log(factorialA(3));//6
以上代码创建了名为 f 的函数, 再赋值给 factorial , 此时把 factorial 再赋值给另一个变量, 函数 f 仍然有效, 递归可以正确调用.
三, 闭包
当在函数内部定义了其他函数, 就创建了闭包. 闭包是指有权访问包含函数内部的所有变量的函数.
1. 创建闭包的常见方式
在一个函数中创建另一个函数并返回它.
- function createComparisonFun(propertyName){
- return function(object1, object2){
- var value1 = object1[propertyName];
- var value2 = object2[propertyName];
- if(value1 <value2){
- return -1;
- }else if(value1> value2){
- return 1;
- }else{
- return 0;
- }
- }
- }
- var person1 = {age: 28},person2 = {age: 29};
- var comparAge = createComparisonFun('age');
- comparAge(person1,person2);//-1
上例中内部函数访问了外部函数的变量 propertyName , 即使这个内部函数被返回了, 或者在其他地方被调用了, 他任然可以访问变量 propertyName. 这里需要知道执行环境和作用域链, 可看这里.
内部的细节是: 在一个函数内部定义的函数 (内部函数) 会将包含函数 (即外部函数) 的活动对象添加到它的作用域链中. 因此, 在执行最后两行代码时, 首先返回内部函数, 它的作用域链被初始化为包含 createComparisonFun 函数的活动对象(activation object) 和全局变量. 即使 createComparisonFun 函数执行完毕后, 其执行环境的作用域链被销毁, 但活动对象仍然在内存中, 因为内部函数仍然在引用这个活动对象. 直到内部函数被销毁后, createComparisonFun 函数的活动对象才会被销毁.
comparAge = null;// 解除对匿名函数的引用, 以便释放内存
注: 由于闭包会携带包含它的函数的作用域, 内存占用较大, 谨慎使用.
2. 闭包与变量
作用域链的这种配置机制引出了一个问题: 即闭包只能取得包含函数中任何变量的最终的结果, 因为闭包保存的是整个变量对象而不是某个特殊的变量. 例如:
- function createFunction(){
- var result = new Array();
- for(var i=0;i<10;i++){
- result[i] = function () {
- return i;
- }
- }
- return result;
- }
- var arr = createFunction();
- arr[0]();//10
预想中, arr 的每一项应该返回自己的索引值, 但实际上, 每个函数都返回 10, 因为每个函数的作用域链中都保存着 createFunction 函数的活动对象, 所以都引用同一个变量 i , createFunction 函数执行结束后, 变量 i 的值为 10.
可以这样改写:
- function createFunction(){
- var result = new Array();
- for(var i=0;i<10;i++){
- result[i] =function (num) {
- return function () {
- return num;
- }
- }(i);
- }
- return result;
- }
- var arr = createFunction();
- arr[0]();//0
3. 关于 this 对象
在闭包中使用 this 对象, 可能会有问题. this 对象是在运行时基于函数的执行环境绑定的: 在全局函数中, this 指 window; 当函数作为某个对象的方法调用时, this 就等于那个对象. 匿名函数的执行环境具有全局性, this 通常指 window. 但有时候由于编写的方式不同, 看起来不会那么明显.
例如:
- var name = 'the window';
- var obj = {
- name: 'my obj',
- getNameFun: function () {
- return function () {
- return this.name;
- }
- }
- };
- obj.getNameFun()();//'the window'
obj.getNameFun() 返回一个匿名函数, obj.getNameFun()()会立即调用它返回的函数, 结果是返回全局的 name 属性. 这是因为: 每个函数再被调用时, 其活动对象都会自动取得两个特殊变量, this 和 arguments. 内部函数在搜索这两个变量时, 只会搜索到其活动对象为止, 不可能直接访问外部函数中的这两个变量. 对于匿名函数, 调用时内部的 this 指 window.
注: 在通过 call()和 apply()改变函数执行环境的情况下, this 会指向其他对象.
四, 私有变量
任何函数中定义的变量, 都可以认为是私有变量. 私有变量包括函数的参数, 局部变量, 在函数内部定义的其他函数. 而有权访问私有变量和私有函数的公共方法称为特权方法.
1. 静态私有变量
- (function(){
- var name = '';
- Person = function (value) {
- name = value;
- };
- Person.prototype.getName = function () {
- return name;
- };
- Person.prototype.setName = function (value) {
- name = value;
- }
- })();
- var person1 = new Person('Nicholas');
- alert(person1.getName());//'Nicholas'
- person1.setName('Greg');
- alert(person1.getName());//'Greg'
- var person2 = new Person('Michael');
- alert(person1.getName());//'Michael'
- alert(person2.getName());//'Michael'
上例中, 创建了一个私有作用域, 定义了私有变量, 构造函数和公有方法, 公有方法定义在原型上. 构造函数和公有方法都可以访问私有变量 name. 这种模式下, name 就是一个静态的, 所有实例共享的属性. 在一个实例上调用 setName(), 会影响所有实例. 而调用 setName()或新创建一个 Person 的实例都会赋予 name 属性一个新值.
注: 使用闭包和私有变量的问题: 多查找作用域链中的一个层次, 就会在一定程度上影响查找速度.
2. 模块模式
模块模式是为单例创建私有变量和特权方法的. 单例是指只有一个实例的对象. JavaScript 是以对象字面量来创建单例对象的. 模块模式通过为单例添加私有变量和特权方法能够使其得到增强.
- var singleton = function () {
- // 私有变量和私有函数
- var privateVariable = 10;
- function privateFunction () {
- return false;
- }
- // 特权方法和属性
- return {
- publicProperty: true,
- publicMethod: function () {
- privateVariable++;
- return privateFunction ();
- }
- }
- }();
上例中使用了一个返回对象的匿名函数. 在匿名函数内部, 先定义了私有变量和函数. 最后将一个对象字面量做为函数值返回. 这个对象就是单例的公共接口, 返回的对象中包含公开的属性和方法, 公共的方法有权访问上面的私有变量和函数. 这种模式在需要对单例进行初始化, 同时又需要维护其私有变量时很有用.
例如:
- var application = function () {
- // 私有变量和函数
- var components = new Array();
- // 初始化
- components.push(new baseComponent());
- // 公共方法
- return {
- getComponentCount: function(){
- return components.length;
- }
- registerComponent: function(component){
- if(typeof component == 'object'){
- components.push(component);
- }
- }
- }
- }();
3. 增强的模块模式
增强的模块模式是指: 在返回对象前加入对其增强的代码. 这种模式适合那些单例必须是某种类型的实例, 同时还必须添加某些属性或非法对其加以增强的情况.
- var application = function () {
- // 私有变量和函数
- var components = new Array();
- // 初始化
- components.push(new baseComponent());
- // 创建 application 的局部副本
- var app = new baseComponent();
- // 公共方法
- app.getComponentCount = function () {
- return components.length;
- };
- app.registerComponent = function (component) {
- if(typeof component == 'object'){
- components.push(component);
- }
- }
- // 返回这个副本
- return app;
- }();
上例增强的部分在创建了 app 的过程, 因为它必须是 baseComponent 的实例.
小结:
1. 递归函数应该始终使用 arguments.callee 来递归调用自身, 不要使用函数名 - 函数名可能会有变化.
2. 闭包原理:
a. 在后台执行环境中, 闭包的作用域链包含着自己的作用域, 包含函数的作用域和全局作用域.
b. 通常, 函数的作用域及其所有变量都会在函数执行结束后销毁. 但是, 当函数返回一个闭包是, 这个函数的作用域会一直保存到闭包不存在为止.
3. 闭包会携带包含它的函数的作用域, 内存占用较大.
来源: http://www.qdfuns.com/article/46690/68648ddfb94a6b0a991aa195af93d207.html