欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記9 js函數(shù)(下)

 更新時(shí)間:2012年10月11日 14:56:30   作者:  
函數(shù)是一種對(duì)象,擁有一般對(duì)象具有的所有特征,除了函數(shù)可以有自己的屬性和方法外,還可以做為一個(gè)引用類型的值去使用,實(shí)際上我們前面的例子中已經(jīng)有過將函數(shù)作為一個(gè)對(duì)象屬性的值,又比如函數(shù)也可以作為另一個(gè)函數(shù)的參數(shù)或者返回值,異步處理中的回調(diào)函數(shù)就是一個(gè)典型的用法
再接著看函數(shù)——具有魔幻色彩的對(duì)象。

9、作為值的函數(shù)

  在一般的編程語言中,如果要將函數(shù)作為值來使用,需要使用類似函數(shù)指針或者代理的方式來實(shí)現(xiàn),但是在ECMAScript中,函數(shù)是一種對(duì)象,擁有一般對(duì)象具有的所有特征,除了函數(shù)可以有自己的屬性和方法外,還可以做為一個(gè)引用類型的值去使用,實(shí)際上我們前面的例子中已經(jīng)有過將函數(shù)作為一個(gè)對(duì)象屬性的值,又比如函數(shù)也可以作為另一個(gè)函數(shù)的參數(shù)或者返回值,異步處理中的回調(diào)函數(shù)就是一個(gè)典型的用法。

復(fù)制代碼 代碼如下:

var name = 'linjisong';
var person = {name:'oulinhai'};
function getName(){
return this.name;
}
function sum(){
var total = 0,
l = arguments.length;
for(; l; l--)
{
total += arguments[l-1];
}
return total;
}

// 定義調(diào)用函數(shù)的函數(shù),使用函數(shù)作為形式參數(shù)
function callFn(fn,arguments,scope){
arguments = arguments || [];
scope = scope || window;
return fn.apply(scope, arguments);
}
// 調(diào)用callFn,使用函數(shù)作為實(shí)際參數(shù)
console.info(callFn(getName));//linjisong
console.info(callFn(getName,'',person));//oulinhai
console.info(callFn(sum,[1,2,3,4]));//10

再看一個(gè)使用函數(shù)作為返回值的典型例子,這個(gè)例子出自于原書第5章:
復(fù)制代碼 代碼如下:

function createComparisonFunction(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 data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];

data.sort(createComparisonFunction("name"));
console.info(data[0].name); //Nicholas

data.sort(createComparisonFunction("age"));
console.info(data[0].name); //Zachary


10、閉包(Closure)

  閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。對(duì)象是帶函數(shù)的數(shù)據(jù),而閉包是帶數(shù)據(jù)的函數(shù)。

  首先閉包是一個(gè)函數(shù),然后閉包是一個(gè)帶有數(shù)據(jù)的函數(shù),那么,帶有的是什么數(shù)據(jù)呢?我們往上看看函數(shù)作為返回值的例子,返回的是一個(gè)匿名函數(shù),而隨著這個(gè)匿名函數(shù)被返回,外層的createComparisonFunction()函數(shù)代碼也就執(zhí)行完成,按照前面的結(jié)論,外層函數(shù)的執(zhí)行環(huán)境會(huì)被彈出棧并銷毀,但是接下來的排序中可以看到在返回的匿名函數(shù)中依舊可以訪問處于createComparisonFunction()作用域中的propertyName,這說明盡管createComparisonFunction()對(duì)應(yīng)的執(zhí)行環(huán)境已經(jīng)被銷毀,但是這個(gè)執(zhí)行環(huán)境相對(duì)應(yīng)的活動(dòng)對(duì)象并沒有被銷毀,而是作為返回的匿名函數(shù)的作用域鏈中的一個(gè)對(duì)象了,換句話說,返回的匿名函數(shù)構(gòu)成的閉包帶有的數(shù)據(jù)就是:外層函數(shù)相應(yīng)的活動(dòng)對(duì)象。由于活動(dòng)對(duì)象的屬性(也就是外層函數(shù)中定義的變量、函數(shù)和形式參數(shù))會(huì)隨著外層函數(shù)的代碼執(zhí)行而變化,因此最終返回的匿名函數(shù)構(gòu)成的閉包帶有的數(shù)據(jù)是外層函數(shù)代碼執(zhí)行完成之后的活動(dòng)對(duì)象,也就是最終狀態(tài)。

  希望好好理解一下上面這段話,反復(fù)理解一下。雖然我已經(jīng)盡我所能描述的更易于理解一些,但是閉包的概念還是有些抽象,下面看一個(gè)例子,這個(gè)例子來自原書第7章:
復(fù)制代碼 代碼如下:

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

var funcs = createFunctions();
for (var i=0,l=funcs.length; i < l; i++){
console.info(funcs[i]());//每一個(gè)函數(shù)都輸出10
}

這里由于閉包帶有的數(shù)據(jù)是createFunctions相應(yīng)的活動(dòng)對(duì)象的最終狀態(tài),而在createFunctions()代碼執(zhí)行完成之后,活動(dòng)對(duì)象的屬性i已經(jīng)變成10,因此在下面的調(diào)用中每一個(gè)返回的函數(shù)都輸出10了,要處理這種問題,可以采用匿名函數(shù)作用域來保存狀態(tài):
復(fù)制代碼 代碼如下:

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

將每一個(gè)狀態(tài)都使用一個(gè)立即調(diào)用的匿名函數(shù)來保存(保存在匿名函數(shù)相應(yīng)的活動(dòng)對(duì)象中),然后在最終返回的函數(shù)被調(diào)用時(shí),就可以通過閉包帶有的數(shù)據(jù)(相應(yīng)的匿名函數(shù)活動(dòng)對(duì)象中的數(shù)據(jù))來正確訪問了,輸出結(jié)果變成0,1,...9。當(dāng)然,這樣做,就創(chuàng)建了10個(gè)閉包,在性能上會(huì)有較大影響,因此建議不要濫用閉包,另外,由于閉包會(huì)保存其它執(zhí)行環(huán)境的活動(dòng)對(duì)象作為自身作用域鏈中的一環(huán),這也可能會(huì)造成內(nèi)存泄露。盡管閉包存在效率和內(nèi)存的隱患,但是閉包的功能是在太強(qiáng)大,下面就來看看閉包的應(yīng)用——首先讓我們回到昨天所說的函數(shù)綁定方法bind()。

(1)函數(shù)綁定與柯里化(currying)

A、再看this,先看一個(gè)例子(原書第22章):
復(fù)制代碼 代碼如下:

<button id='my-btn' title='Button'>Hello</button>
<script type="text/javascript">
var handler = {
title:'Event',
handleClick:function(event){
console.info(this.title);
}
};
var btn = document.getElementById('my-btn');//獲取頁(yè)面按鈕
btn.onclick = handler.handleClick;//給頁(yè)面按鈕添加事件處理函數(shù)
</script>

如果你去點(diǎn)擊“Hello”按鈕,控制臺(tái)打印的是什么呢?竟然是Button,而不是期望中的Event,原因就是這里在點(diǎn)擊按鈕的時(shí)候,處理函數(shù)內(nèi)部屬性this指向了按鈕對(duì)象??梢允褂瞄]包來解決這個(gè)問題:
復(fù)制代碼 代碼如下:

btn.onclick = function(event){
handler.handleClick(event);//形成一個(gè)閉包,調(diào)用函數(shù)的就是對(duì)象handler了,函數(shù)內(nèi)部屬性this指向handler對(duì)象,因此會(huì)輸出Event}

B、上面的解決方案并不優(yōu)雅,在ES5中新增了函數(shù)綁定方法bind(),我們使用這個(gè)方法來改寫一下:
復(fù)制代碼 代碼如下:

if(!Function.prototype.bind){//bind為ES5中新增,為了保證運(yùn)行正常,在不支持的瀏覽器上添加這個(gè)方法
Function.prototype.bind = function(scope){
var that = this;//調(diào)用bind()方法的函數(shù)對(duì)象
return function(){
that.apply(scope, arguments);//使用apply方法,指定that函數(shù)對(duì)象的內(nèi)部屬性this
};
};
}
btn.onclick = handler.handleClick.bind(handler);//使用bind()方法時(shí)只需要使用一條語句即可

這里添加的bind()方法中,主要技術(shù)也是創(chuàng)建一個(gè)閉包,保存綁定時(shí)的參數(shù)作為函數(shù)實(shí)際調(diào)用時(shí)的內(nèi)部屬性this。如果你不確定是瀏覽器本身就支持bind()還是我們這里的bind()起了作用,你可以把特性檢測(cè)的條件判斷去掉,然后換個(gè)方法名稱試試。
C、上面對(duì)函數(shù)使用bind()方法時(shí),只使用了第一個(gè)參數(shù),如果調(diào)用bind()時(shí)傳入多個(gè)參數(shù)并且將第2個(gè)參數(shù)開始作為函數(shù)實(shí)際調(diào)用時(shí)的參數(shù),那我們就可以給函數(shù)綁定默認(rèn)參數(shù)了。
復(fù)制代碼 代碼如下:

if(!Function.prototype.bind){
Function.prototype.bind = function(scope){
var that = this;//調(diào)用bind()方法的函數(shù)對(duì)象
var args = Array.prototype.slice.call(arguments,1);//從第2個(gè)參數(shù)開始組成的參數(shù)數(shù)組
return function(){
var innerArgs = Array.prototype.slice.apply(arguments);
that.apply(scope, args.concat(innerArgs));//使用apply方法,指定that函數(shù)對(duì)象的內(nèi)部屬性this,并且填充綁定時(shí)傳入的參數(shù)
};
};
}

D、柯里化:在上面綁定時(shí),第一個(gè)參數(shù)都是用來設(shè)置函數(shù)調(diào)用時(shí)的內(nèi)部屬性this,如果把所有綁定時(shí)的參數(shù)都作為預(yù)填的參數(shù),則稱之為函數(shù)柯里化。
復(fù)制代碼 代碼如下:

if(!Function.prototype.curry){
Function.prototype.curry = function(){
var that = this;//調(diào)用curry()方法的函數(shù)對(duì)象
var args = Array.prototype.slice.call(arguments);//預(yù)填參數(shù)數(shù)組
return function(){
var innerArgs = Array.prototype.slice.apply(arguments);//實(shí)際調(diào)用時(shí)參數(shù)數(shù)組
that.apply(this, args.concat(innerArgs));//使用apply方法,并且加入預(yù)填的參數(shù)
};
};
}

(2)利用閉包緩存

  還記得前面使用遞歸實(shí)現(xiàn)斐波那契數(shù)列的函數(shù)嗎?使用閉包緩存來改寫一下:
復(fù)制代碼 代碼如下:

var fibonacci = (function(){//使用閉包緩存,遞歸
var cache = [];
function f(n){
if(1 == n || 2 == n){
return 1;
}else{
cache[n] = cache[n] || (f(n-1) + f(n-2));
return cache[n];
}
}
return f;
})();

var f2 = function(n){//不使用閉包緩存,直接遞歸
if(1 == n || 2 == n){
return 1;
}else{
return f2(n-1) + f2(n-2);
}
};

下面是測(cè)試代碼以及我機(jī)器上的運(yùn)行結(jié)果:
復(fù)制代碼 代碼如下:

var test = function(n){
var start = new Date().getTime();
console.info(fibonacci(n));
console.info(new Date().getTime() - start);

start = new Date().getTime();
console.info(f2(n));
console.info(new Date().getTime() - start);
};
test(10);//55,2,55,2
test(20);//6765,1,6765,7
test(30);//832040,2,832040,643

可以看到,n值越大,使用緩存計(jì)算的優(yōu)勢(shì)越明顯。作為練習(xí),你可以嘗試自己修改一下計(jì)算階乘的函數(shù)。

(3)模仿塊級(jí)作用域

  在ECMAScript中,有語句塊,但是卻沒有相應(yīng)的塊級(jí)作用域,但我們可以使用閉包來模仿塊級(jí)作用域,一般格式為:
復(fù)制代碼 代碼如下:

(function(){
//這里是塊語句
})();

上面這種模式也稱為立即調(diào)用的函數(shù)表達(dá)式,這種模式已經(jīng)非常流行了,特別是由于jQuery源碼使用這種方式而大規(guī)模普及起來。
  閉包還有很多有趣的應(yīng)用,比如模仿私有變量和私有函數(shù)、模塊模式等,這里先不討論了,在深入理解對(duì)象之后再看這些內(nèi)容。

  關(guān)于函數(shù),就先說這些,在網(wǎng)上也有很多非常棒的文章,有興趣的可以自己搜索一下閱讀。這里推薦一篇文章,《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》譯者的一篇譯文:命名函數(shù)表達(dá)式探秘。

相關(guān)文章

最新評(píng)論