最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript高級程序設計(第3版)學習筆記9 js函數(下)

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

9、作為值的函數

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

復制代碼 代碼如下:

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;
}

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

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

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)

  閉包是指有權訪問另一個函數作用域中的變量的函數。對象是帶函數的數據,而閉包是帶數據的函數。

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

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

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]());//每一個函數都輸出10
}

這里由于閉包帶有的數據是createFunctions相應的活動對象的最終狀態(tài),而在createFunctions()代碼執(zhí)行完成之后,活動對象的屬性i已經變成10,因此在下面的調用中每一個返回的函數都輸出10了,要處理這種問題,可以采用匿名函數作用域來保存狀態(tà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;
}

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

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

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

<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');//獲取頁面按鈕
btn.onclick = handler.handleClick;//給頁面按鈕添加事件處理函數
</script>

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

btn.onclick = function(event){
handler.handleClick(event);//形成一個閉包,調用函數的就是對象handler了,函數內部屬性this指向handler對象,因此會輸出Event}

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

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

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

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

D、柯里化:在上面綁定時,第一個參數都是用來設置函數調用時的內部屬性this,如果把所有綁定時的參數都作為預填的參數,則稱之為函數柯里化。
復制代碼 代碼如下:

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

(2)利用閉包緩存

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

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);
}
};

下面是測試代碼以及我機器上的運行結果:
復制代碼 代碼如下:

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值越大,使用緩存計算的優(yōu)勢越明顯。作為練習,你可以嘗試自己修改一下計算階乘的函數。

(3)模仿塊級作用域

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

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

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

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

相關文章

最新評論

澎湖县| 白沙| 清流县| 祁阳县| 兰州市| 高唐县| 启东市| 海南省| 新田县| 明光市| 鹿邑县| 德安县| 阿拉善左旗| 苗栗市| 分宜县| 安康市| 鹰潭市| 乌鲁木齐县| 常宁市| 清河县| 鱼台县| 荣成市| 库尔勒市| 突泉县| 广安市| 松原市| 恩平市| 和政县| 南川市| 雅江县| 莎车县| 平原县| 大田县| 福贡县| 宕昌县| 鸡东县| 营山县| 昭觉县| 双桥区| 靖远县| 天祝|