来自《javascript高级程序设计 第三版:作者Nicholas C. Zakas》的学习笔记(七)

  

  直接切入主题~

  定义函数的方式有两种:

  • 函数声明

    function functionName(arg0,arg1,arg2) {
    //函数体
    }

    函数声明有一个重要的特征——函数声明提升,意思是在执行代码之前会先读取函数声明,允许把函数声明放在调用它的语句后面

    sayHi();
    
    function sayHi() {
    alert("hi");
    }
  • 函数表达式
    var functionName = function(arg0,arg1,arg2) {
    //函数体
    };

    如上创建的其实就是匿名函数。这种情况不支持函数声明提升,即

    sayHi();   //错误,函数不存在
    var sayHi = function() {
    alert("hi");
    };

    还有执行下列代码会产生意想不到的结果:

    //不要这样
    if (condition) {
    function sayHi() {
    alert("hi!");
    }else {
    function sayHi() {
    alert("hello");
    }
    }
    }

    因为不同的浏览器处理方式不同,大多数浏览器会返回第二个声明,忽略condition;Firefox会在condition为true时返回第一个声明。所以这种使用方式很危险。不过,你可以这么改进:

    var sayHi;
    
    if (condition) {
    sayHi = function() {
    alert("hi");
    };
    }esle {
    sayHi = function() {
    alert("hello");
    }
    }

    当然,这时应该好好想想为什么可以这么改进~~因为函数表达式不存在函数声明提前,所以sayHi在任何条件下只会执行一次(if条件的互斥性)。

  • 你可能很想知道匿名函数的用途,在这里就先介绍一点(当然匿名函数的作用可不止这一点)
    function createComparisonFunction(propetyName) {
    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;
    }
    };
    }
  • 递归
    function factorial(num) {
    if (num <= 1) {
    return 1;
    }else {
    return num*factorial(num -1);
    }
    } //上面是一个很简单的递归阶乘函数 var anotherFactorial = factorial;
    factorial = null;
    alert(anotherFactorial(4)); //出错了,原因很明显,函数定义中用了自身的函数名(就是指针啦),当factorial置为null时,整个函数失效了

    这么改进:使用arguments.callee(),它指向正在执行的函数的指针。

    function factorial(num) {
    if (num <= 1) {
    return 1;
    }else {
    return num*arguments.callee(num - 1);
    }
    } //但是在严格模式下,不能通过脚本访问arguments.callee(),又有了如下改进 var factorial = (function f(num) { //这种情况下,即便是将factorial赋给另一个变量函数名字f仍然是有效的
    if (num <= 1) {
    return 1;
    }else {
    return num*f(num - 1);
    }
    });

  闭包

  如果有人叫你解释一下闭包,你该怎么说。~如果你知道怎么说了,说明你已经完全掌握了它。

  凡事都是有一个起源的,一种技术的产生往往就是为了解决一个问题(装B了)。=。=我总是觉得,所谓的代码简洁是有不同level的,对于个人来说,只要你能把程序中每一条语句存在意义说出来和不可删除的存在在,对你来说就是简洁(当然,你的功力决定了你主观上的简洁的大众level),扯远了。。。。

  闭包作为js语言的特点和难点,要理解它,首先要理解js特殊的变量作用域。

  一、变量作用域

  js变量的作用域无非就是两种:全局变量和局部变量。

  js语言的特殊之处就在于函数内部可以直接读取全局变量(其实很多语言,如C++也是)。如:

var n = 999;
function f1() {
alert(n);
} f1(); //

  而另一方面,在函数外部自然无法读取函数内部分局部变量:

function f1() {
var n = 999;
} alert(n); //error

  这里有一个地方需要注意,函数内部声明变量的时候,一定要使用命令var命令。如果不用的话,你是实际上声明了一个全局变量:

function f1() {
n = 999;
} f1(); alert(n); //

  那如何从外部读取局部变量呢?

  出于种种原因,我们有时候需要得到函数内部的局部变量。怎么变通?~.~

  那就在函数的内部在定义一个函数。

function f1() {
var n = 999;
function f2() {
alert(n); //
}
}

  在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是js语言特有的“链式作用域”结构(chain scope)。子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的。反之则不成立。

  可是呢,灵感就是这么来的,既然f2可以读取f1中局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它们内部变量了:

function f1() {
var n = 999;
function f2() {
alert(n);
}
return f2;
} var result = f1();
result(); //999 相当于f2()

  现在将是引入闭包概念的好时候:上述f2函数就是闭包。闭包的官方描述:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因为这些变量也是表达式的一部分。其实完全可以这么理解:闭包就是能够读取其他函数内部变量的函数。由于在js中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在函数内部分函数”。闭包就是将函数内部和函数外部连接起来的一座桥梁。

  二、闭包的用途

  它的最大的两个用处就是:

  • 可以读取函数内部的变量
  • 并且可以让这些变量的值始终保存在内存中
    function f1() {
    var n=999;
    nAdd=function() {
    n+=1;
    }
    function f2() {
    alert(n);
    }
    return f2;
    }
    var result=f1();
    result(); //
    nAdd();
    result(); //

  在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

  为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

  这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

  三、使用闭包的注意点

  1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。内存泄露

function assignHandler() {
var element = document.getElementById("someElement");
element.onclick = function() {
alert(element.id);
};
}
//在闭包的作用域中保存着的这个html元素,无法被销毁

  2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

  四、思考题

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); //The Window(在非严格模式下)

  这题的官方解释应该是很这样的:在闭包中使用this对象可能会导致一些问题。我们知道this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此其this对象通常指向window。

  上例中匿名函数为什么匿名函数没有取得其包含作用域的this对象呢。~~~每个函数在被调用时,其活动对象都会自动取得两个特殊值;this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远也不可能直接访问外部函数的这两个变量,不过把外部函数作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象:

var name = "The Window";

var object = {
name:"My Object",
getNameFunc:function() {
var that = this; ////////////////////////////////////////////see
return function() {
return that.name;
};
}
};

  注:this和arguments也存在同样的问题。如果想访问作用域中的arguments对象,必须将对象的引用保存到另一个闭包能够访问的变量中。

function outerFun() {
var a=0;
function innerFun() {
a++;
alert(a);
}
}
innerFun(); //上面的代码是错误的.innerFun()的作用域在outerFun()内部,所在outerFun()外部调用它是错误的.
改成如下,也就是闭包: function outerFun() {
var a=0;
function innerFun() {
a++;
alert(a);
}
return innerFun; //注意这里
}
var obj=outerFun();
obj(); //结果为1
obj(); //结果为2
var obj2=outerFun();
obj2(); //结果为1
obj2(); //结果为2

  再来重新声明一遍什么是闭包:当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们。

function outerFun() {
var a =0;
alert(a);
}
var a=4;
outerFun(); //
alert(a); //4  因为在函数内部使用了var关键字 维护a的作用域在outFun()内部
function outerFun() {
//没有var
a =0;
alert(a);
}
var a=4;
outerFun(); //
alert(a); //

  作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4;  并改变其值。其实,刚看到这种解释的时候我是有疑问的,函数声明具有提前的作用,而且作用域的方向是向上的,直到搜索到同名变量就应该停止。=。=其实是脑子短路了。应该是只要看outerFunn()的调用就可以了。因为当调用outerFun()的时候,函数内部的a = 0,是全局变量定义,覆盖了前面的var a = 4。当然,可能会问,如果outerFun()在前,var a = 4在后面,结果会是怎么样的?~动手试试不就知道了==

<!doctype html>
<html>
<head></head>
<script>
window.onload = (function() {
function outerFun()
{
//没有var
a =0;
alert(a);
}
outerFun();
var a=4;
//outerFun();
alert(a);
});
</script>
<body> </body>
</html>

  运行结果即为0和4,佐证了我的解释,bingo~

  补充一点js的垃圾回收的知识:在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

  //以下部分是来自Nicholas C. Zakas所撰书的分享:

  如何创建作用域链以及作用域链有什么作用的细节

  当某个函数第一次被调用时,会创建一个执行环境以及相应的作用域链,并把作用域链值给一个特殊的内部属性([Scope])。然后,使用this、arguments和其它命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,.....直至作为作用域链终点的全局执行环境。可以这么理解,作用域链本质上是一个指向变量的指针列表,它只引用但不实际包含变量对象。

  内存问题:一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但闭包的情况又有所不同。其执行环境会被销毁,但是它的活动对象仍然留在内存中,直到匿名对象被销毁后,活动对象才会被销毁。作用域的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值,看代码更直接:

function createFunction() {
var result = new Array(); for (var i = 0; i < 10;i++) {
result[i] = function() {
return i;
};
}
return result;
} //这个函数表面上看起来好像是会返回自己的索引,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。

  解决上述问题,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期,:

function createFunctions() {
var result = new Array(); for(var i = 0; i < 10;i++) {
result[i] = function(num) {
return function() {
return num;
};
}(i);
}
return result;
}

  模仿块级作用域

(function() {
//这里是块级作用域
})(); var someFunction = function() {
//这里是块级作用域
};
someFunction(); function() {
//这里是块级作用域
}(); //出错! 是因为javascript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号

  无论在什么地方,只要临时需要一些变量,就可以使用私有作用域:

function outputNumbers(count) {
(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!");
}
}();

  这种做法减少了闭包占用的内存问题,因为没有指向匿名函数的引用。只要匿名函数执行完毕,就可以立即销毁其作用域链了。

  私有变量

  js没有私有成员的概念,所有成员属性都是公有的。而私有变量是指:函数的参数、局部变量和在函数内部定义的其它函数。对于能够有权访问私有变量和私有函数的公有方法称为特权方法。

  1)在构造函数中定义特权方法

function MyObject() {
//私有变量和私有函数
var privateVarible = 10;
function privateFunction() {
return false;
} //特权方法
this.publicMethod = function() {
privateVarible++;
return privateFunction();
};
}
function Person(name) {
this.getName = function() {
return name;
}; this.setName = function(value) {
name = value;
};
} var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Grey");
alert(person.getName()); //"Grey"

  构造函数的缺点就是:针对每个实例都会创建同样一组方法,而使用静态私有变量来实现特权方法就可以避免。

  静态私有变量

  

(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("carol");
alert(person1.getName()); //"carol"
person1.setName("grey");
alert(person1.getName()): //"grey" var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

  以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。到底是使用实例变量,还是静态变量,最终还是要视你的具体需求而定。

  模块模式

  为单例创建私有变量和私有函数。单例(只有一个实例的对象)。

var singleton = {
name:value,
method:function() {
//这里是方法的代码
}
};
var application = function() {
//私有变量函数
var components = new Array(); //初始化
components.push(new BaseComponent()); //公共
return {
getComponent: function() {
return components.length;
}, registerComponent:function(component) {
if (typeof component == "object") {
components.push(component);
}
}
};
};

  简而言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。

  增强的模块模式

  

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") {
component.push(component);
}
};
//返回这个副本
return app;
}();

  争取把js基础打扎实,然后动手实践=。=

js学习之函数表达式及闭包的更多相关文章

  1. JavaScript函数表达式、闭包、模仿块级作用域、私有变量

    函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...

  2. JavaScript--我发现,原来你是这样的JS:函数表达式和闭包

    一.介绍 本次博客主要介绍函数表达式的内容,主要是闭包. 二.函数表达式 定义函数的两种方式:一个是函数声明,另一个就是函数表达式. //1.函数声明写法 function fn2(){ consol ...

  3. js学习之函数声明与函数表达式区别[原创]

    作为一名js初学者,与大家分享下.Javascript中有函数声明提升的功能,会优先编译函数声明部分.比如, ff(); function ff(){ alert("hello world. ...

  4. JS立即执行函数表达式(IIFE)

    原文为 http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife ----------------- ...

  5. JavaScript高级程序设计学习笔记--函数表达式

    关于函数声明,它的一个重要特征就是函数声明提升,意思是在执行代码之间会读取函数声明,意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句 后面. sayHi(); funct ...

  6. js自执行函数表达式

    // 下面2个括弧()都会立即执行 (function () { /* code */ } ()); // 推荐使用这个(function () { /* code */ })(); // 但是这个也 ...

  7. 浅谈JavaScript的函数表达式(闭包)

    前文已经简单的介绍了函数的闭包.函数的闭包就是有权访问另一个函数作用域的函数,也就是函数内部又定义了一个函数. var Super=function(num){ var count=num; retu ...

  8. js学习:函数

    概述 函数的声明 JavaScript 有三种声明函数的方法 function 命令 function命令声明的代码区块,就是一个函数.function命令后面是函数名,函数名后面是一对圆括号,里面是 ...

  9. js基础:函数表达式和函数声明

    函数表达式和函数声明的区别.实际上,解析器在向执行环境中加载数据是,对函数表达式和函数声明并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用.而函数表达式,则必须等到解析器执行到它所 ...

随机推荐

  1. HTTPClient模块的HttpGet和HttpPost

    HttpClient常用HttpGet和HttpPost这两个类,分别对应Get方式和Post方式. 无论是使用HttpGet,还是使用HttpPost,都必须通过如下3步来访问HTTP资源. 1.创 ...

  2. 设计模式——设计模式之禅day1

    单一职责 原则的定义是:应该有且仅有一个原因引起类的变更. 单一职责原则有什么好处: 类的复杂性降低,实现什么职责都有清晰明确的定义: 可读性提高,复杂性降低,那当然可读性提高了: 可维护性提高,可读 ...

  3. asp.net php asp jsp 301重定向的代码

    介绍一下针对各类程序系统实施301重定向的代码: 1.Linux主机重定向 Godaddy的Liunx主机,Godaddy本身已经支持Apache,所以直接创建一个.htaccess文件就可以了,一般 ...

  4. Quartz 第五课 SimpleTriggers 官方文档翻译

    对于SimpleTrigger你需要知道它的启动总是在一个特殊的时间点或者有你设置的重复时间段中.直白来说,如果你想在2005年1月13日,正好上午11时23分54秒触发,然后执行五次,每十秒钟. 从 ...

  5. JavaScript学习笔记(12)——JavaScript内置对象

    1.Number Javascript只有一种数字类型,可以有小数也可以没有,也可以使用科学计数法. var z=123e-5; // 0.00123 JavaScript 不是类型语言.与许多其他编 ...

  6. jvmstat监控jvm内存

    1.下载jvmstat-3_0.zip: 2.配置环境变量JVMSTAT_JAVA_HOME为jdk目录E:\Program Files\Java\jdk1.5.0_12 3.监控本机:  jps查看 ...

  7. c# winform 点菜宝接口demo程序

    前几天写了一篇关于c#调用 win32API的文章,有同学对点菜宝接口感兴趣,所以就把写的demo程序共享出来,大家一起讨论改进,so放百度云地址: 百度云下载地址

  8. 判断IFeatureClass图形是否含有Z值信息,若有为IPoint赋Z值

    判断IFeatureClass图形是否含有Z值信息 IFeatureClass featureClass = this.pLayer.FeatureClass; string shapeFieldNa ...

  9. 进度条轮播【BackgroundColor】

    直接贴代码先看 HTML: <div class="bannar"> <div class="img"> <ul> < ...

  10. String inputStream file转化

    String --> InputStreamByteArrayInputStream stream = new ByteArrayInputStream(str.getBytes()); Inp ...