成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Javascript難點(diǎn)知識運(yùn)用---遞歸,閉包,柯里化等

hqman / 3516人閱讀

摘要:作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對象。對語句來說,會將指定的對象添加到作用域鏈中。

前言

ps: 2018/05/13 經(jīng)指正之后發(fā)現(xiàn)惰性加載函數(shù)細(xì)節(jié)有問題,已改正
在這里也補(bǔ)充一下,這些都是根據(jù)自己理解寫的例子,不一定說的都對,有些只能查看不能運(yùn)行的要謹(jǐn)慎,因?yàn)槲铱赡苤皇菍⒎椒ㄋ悸穼懗鰜?沒有實(shí)際跑過的.

面向?qū)ο缶幊?&& 面向過程編程 面向?qū)ο缶幊?Object Oriented Programming,OOP)

是一種以事物為中心的編程思想,把構(gòu)成問題事務(wù)分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為,三大特點(diǎn)缺一不可。

特點(diǎn) 作用
封裝 將其說明(用戶可見的外部接口)與實(shí)現(xiàn)(用戶不可見的內(nèi)部實(shí)現(xiàn))顯式地分開,其內(nèi)部實(shí)現(xiàn)按其具體定義的作用域提供保護(hù)
繼承 子類自動共享父類數(shù)據(jù)結(jié)構(gòu)和方法的機(jī)制
多態(tài) 相同的操作或函數(shù)、過程可作用于多種類型的對象上并獲得不同的結(jié)果。不同的對象,收到同一消息可以產(chǎn)生不同的結(jié)果
面向過程編程(Procedure Oriented Programming, POP)

是一種以過程為中心的編程思想,分析出解決問題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時候一個一個依次調(diào)用就可以了。

百度百科有個很形象的比喻,

例如五子棋:
面向過程的設(shè)計(jì)思路步驟:
1、開始游戲,
2、黑子先走,
3、繪制畫面,
4、判斷輸贏,
5、輪到白子,
6、繪制畫面,
7、判斷輸贏,
8、返回步驟2,
9、輸出最后結(jié)果

面向?qū)ο蟮脑O(shè)計(jì)思路步驟:整個五子棋可以分為
1、黑白雙方,這兩方的行為是一模一樣的,
2、棋盤系統(tǒng),負(fù)責(zé)繪制畫面,
3、規(guī)則系統(tǒng),負(fù)責(zé)判定諸如犯規(guī)、輸贏等。
第一類對象(玩家對象)負(fù)責(zé)接受用戶輸入,并告知第二類對象(棋盤對象)棋子布局的變化,棋盤對象接收到了棋子的變化就要負(fù)責(zé)在屏幕上面顯示出這種變化,同時利用第三類對象(規(guī)則系統(tǒng))來對棋局進(jìn)行判定。

面向?qū)ο笫且怨δ軄韯澐謫栴},而不是步驟。同樣是繪制棋局,這樣的行為在面向過程的設(shè)計(jì)中分散在了多個步驟中,很可能出現(xiàn)不同的繪制版本,因?yàn)橥ǔTO(shè)計(jì)人員會考慮到實(shí)際情況進(jìn)行各種各樣的簡化。而面向?qū)ο蟮脑O(shè)計(jì)中,繪圖只可能在棋盤對象中出現(xiàn),從而保證了繪圖的統(tǒng)一。

(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

基本類型和引用類型

之前已經(jīng)寫過這個文章,就不復(fù)述了
詳情可以參考我之前寫的文章關(guān)於Javascript基本類型和引用類型小知識

執(zhí)行環(huán)境(execution context)及作用域(scope)

來自Javascript高級程序設(shè)計(jì)3:

解析
執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數(shù)據(jù)時會在后臺使用它。

全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境。根據(jù) ECMAScript 實(shí)現(xiàn)所在的宿主環(huán)境不同,表示執(zhí)行環(huán)境的對象也不一樣。在 Web 瀏覽器中,全局執(zhí)行環(huán)境被認(rèn)為是 window 對象,因此所有全局變量和函數(shù)都是作為 window (全局)對象的屬性和方法創(chuàng)建的。某個執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀(全局執(zhí)行環(huán)境直到應(yīng)用程序退出——例如關(guān)閉網(wǎng)頁或?yàn)g覽器——時才會被銷毀)。

每個函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個函數(shù)時,函數(shù)的環(huán)境就會被推入一個環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。 ECMAScript 程序中的執(zhí)行流正是由這個方便的機(jī)制控制著。

當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對象。如果這個環(huán)境是函數(shù),則將其活動對象(activation object)作為變量對象?;顒訉ο笤谧铋_始時只包含一個變量,即 arguments 對象(這個對象在全局環(huán)境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環(huán)境,而再下一個變量對象則來自下一個包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象。

標(biāo)識符解析是沿著作用域鏈一級一級地搜索標(biāo)識符的過程。搜索過程始終從作用域鏈的前端開始,然后逐級地向后回溯,直至找到標(biāo)識符為止(如果找不到標(biāo)識符,通常會導(dǎo)致錯誤發(fā)生).

延長作用域鏈

try-catch 語句的 catch 塊

with 語句(不推薦)

這兩個語句都會在作用域鏈的前端添加一個變量對象。
對 with 語句來說,會將指定的對象添加到作用域鏈中。
對 catch 語句來說,會創(chuàng)建一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。

沒有塊級作用域
function test() {
  if (true) {
    var num = 0;
  }
  //打印if語句內(nèi)聲明變量
  console.log(num);//0
  for (var i = 0; i < 4; i++) { }
  //打印for語句內(nèi)聲明變量
  console.log(i);//4
}
test()//0 4

在其他類 C 的語言中,由花括號封閉的代碼塊都有自己的作用域,在if ,for語句執(zhí)行完畢后被銷毀,但在 JavaScript 中, if ,for語句中的變量聲明會將變量添加到當(dāng)前的執(zhí)行環(huán)境中。如果向模擬塊狀作用域的話可以利用閉包等方法,下文會提到.
(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

垃圾收集

JavaScript 具有自動垃圾收集機(jī)制,也就是說,執(zhí)行環(huán)境會負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。這種垃圾收集機(jī)制的原理其實(shí)很簡單:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存。為此,垃圾收集器會按照固定的時間間隔(或代碼執(zhí)行中預(yù)定的收集時間),周期性地執(zhí)行這一操作。

下面我們來分析一下函數(shù)中局部變量的正常生命周期。
局部變量只在函數(shù)執(zhí)行的過程中存在。而在這個過程中,會為局部變量在棧(或堆)內(nèi)存上分配相應(yīng)的空間,以便存儲它們的值。然后在函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束。此時,局部變量就沒有存在的必要了,因此可以釋放它們的內(nèi)存以供將來使用。在這種情況下,很容易判斷變量是否還有存在的必要;
但并非所有情況下都這么容易就能得出結(jié)論。垃圾收集器必須跟蹤哪個變量有用哪個變量沒用,對于不再有用的變量打上標(biāo)記,以備將來收回其占用的內(nèi)存。用于標(biāo)識無用變量的策略可能會因?qū)崿F(xiàn)而異,但具體到瀏覽器中的實(shí)現(xiàn),則通常有兩個策略。

標(biāo)記清除
當(dāng)變量進(jìn)入環(huán)境(例如,在函數(shù)中聲明一個變量)時,就將這個變量標(biāo)記為“進(jìn)入環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會用到它們。而當(dāng)變量離開環(huán)境時,則將其標(biāo)記為“離開環(huán)境”。
可以使用任何方式來標(biāo)記變量。比如,可以通過翻轉(zhuǎn)某個特殊的位來記錄一個變量何時進(jìn)入環(huán)境,或者使用一個“進(jìn)入環(huán)境的”變量列表及一個“離開環(huán)境的”變量列表來跟蹤哪個變量發(fā)生了變化。說到底,如何標(biāo)記變量其實(shí)并不重要,關(guān)鍵在于采取什么策略。
垃圾收集器在運(yùn)行的時候會給存儲在內(nèi)存中的所有變量都加上標(biāo)記(當(dāng)然,可以使用任何標(biāo)記方式)。然后,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。

引用計(jì)數(shù)
引用計(jì)數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。當(dāng)聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數(shù)就是 1。如果同一個值又被賦給另一個變量,則該值的引用次數(shù)加 1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數(shù)減 1。當(dāng)這個值的引用次數(shù)變成 0 時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內(nèi)存空間回收回來。這樣,當(dāng)垃圾收集器下次再運(yùn)行時,它就會釋放那些引用次數(shù)為零的值所占用的內(nèi)存。
問題一, 循環(huán)引用會一直無法回收;
問題二, 低版本IE有一部分對象并不是原生 JavaScript 對象;

(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

JavaScript原型對象與原型鏈

之前已經(jīng)寫過這個文章,就不復(fù)述了,特意修改了之前的排版補(bǔ)充之類
詳情可以參考我之前寫的文章關(guān)於Javascript中的new運(yùn)算符,構(gòu)造函數(shù)與原型鏈一些理解

遞歸

一種會在函數(shù)內(nèi)部重復(fù)調(diào)用自身的寫法.

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1);
  }
}
console.log(factorial(5));//120

一開始的常規(guī)寫法,但是有個問題是內(nèi)部調(diào)用自身是使用函數(shù)名字,如果在將factorial賦值到一個變量之后,盡管還是調(diào)用原factorial函數(shù),但不是期望的調(diào)用函數(shù)自身的寫法了.

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1);
  }
}
var another = factorial;
factorial = null;

console.log(another(5)); //TypeError: factorial is not a function

如上,實(shí)際上是在another上調(diào)用factorial,而且如果factorial不存在之后會引起錯誤.

解決方案:

1, arguments.callee(不推薦)
是一個指向正在執(zhí)行的函數(shù)的指針屬性.

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * arguments.callee(num - 1);
  }
}
var another = factorial;
factorial = null;

console.log(another(5)); // 120

缺點(diǎn):

嚴(yán)格模式下,不能通過腳本訪問 arguments.callee,訪問這個屬性會導(dǎo)致錯誤;

arguments是龐大且變化的,每次訪問需要消耗大量性能;

2, 命名函數(shù)表達(dá)式

var factorial = (function f(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * f(num - 1);
  }
});
var another = factorial;
factorial = null;

console.log(another(5)); // 120

這種方式在嚴(yán)格模式和非嚴(yán)格模式下都行得通.
(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

閉包 定義:

未知來源: 指函數(shù)變量可以保存在函數(shù)作用域內(nèi),因此看起來是函數(shù)將變量“包裹”了起來;

未知來源: 指在函數(shù)聲明時的作用域以外的地方被調(diào)用的函數(shù);

官方: 一個擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式;

Javascript高級程序設(shè)計(jì)3: 閉包是指有權(quán)訪問其他函數(shù)作用域中的變量的函數(shù);

JavaScript語言精粹: JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里;

阮一峰: 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù);

這是一個很難界定的點(diǎn),每個人的說法都不同,包括各種專業(yè)資料,權(quán)威大神,但是唯一不變的是它們都有提到訪問其他作用域的能力.
例如這個例子,不僅可以在外部讀取函數(shù)內(nèi)部變量,還能修改.

function test() {
  var num = 1;
  return {
    get: function () {
      console.log(num);
    },
    add: function () {
      console.log(++num);
    }
  }
}
var result = test();
result.get(); // 1
result.add(); // 2
注意:

1, 匿名函數(shù)和閉包函數(shù)沒有必然關(guān)系;
匿名函數(shù): 不需要函數(shù)名字,沒污染全局命名空間的風(fēng)險并且執(zhí)行后自動銷毀環(huán)境.
很多人說匿名函數(shù)也是閉包的用法,但是在我看來這只不過是使用匿名函數(shù)的寫法來寫閉包,讓開發(fā)省掉多余步驟而已.例如:

//閉包寫法
function test1() {
  return function () {
    console.log(1);
  }
}
test1()(); // 1
//匿名函數(shù)寫法
var test2 = (function () {
  return function () {
    console.log(1);
  }
})()
test2(); // 1

2, 閉包所保存的是整個變量對象,而不是某個特殊的變量,所以只能取得包含函數(shù)中任何變量的最后一個值。
這就是為什么for循環(huán)返回的i永遠(yuǎn)是最后一個的原因了




    
    



    
  1. 點(diǎn)我吧!!
  2. 點(diǎn)我吧!!
  3. 點(diǎn)我吧!!
  4. 點(diǎn)我吧!!

我們可以通過創(chuàng)建閉包環(huán)境模擬塊級作用域讓行為符合預(yù)期




    
    



    
  1. 點(diǎn)我吧!!
  2. 點(diǎn)我吧!!
  3. 點(diǎn)我吧!!
  4. 點(diǎn)我吧!!

3, this指向問題
this 對象是在運(yùn)行時基于函數(shù)的執(zhí)行環(huán)境綁定的,閉包的執(zhí)行環(huán)境具有全局性,因此其 this 對象通常指向 window;

function test() {
  return function () {
    console.log(this === window);
  }
}
test()(); // true
優(yōu)點(diǎn):

封裝性好,避免全局污染;

可以延長變量生命周期,保存當(dāng)前變量不被清除;

可以在模擬塊級作用域;

在對象中創(chuàng)建私有變量方法,只暴露出想提供的訪問權(quán)限;

缺點(diǎn):

由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除(delete是沒用的,要設(shè)成null)。

閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果把父函數(shù)當(dāng)作對象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。

(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

Currying(柯里化)

在計(jì)算機(jī)科學(xué)中,柯里化(Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。特點(diǎn)是:

參數(shù)復(fù)用;

提前返回;

延遲計(jì)算/運(yùn)行。

單說有點(diǎn)抽象,例如你有一個簡單的計(jì)算函數(shù)

function add(a, b) {
  return a + b
}

如果你想再加多寫計(jì)算量怎么辦?繼續(xù)加入?yún)⒓訁?shù),
我們分析下上面的函數(shù)有什么特點(diǎn):
1, 計(jì)算方式固定,返回所有參數(shù)相加的結(jié)果;
2, 參數(shù)不固定,可能需要增減;

既然計(jì)算方法固定,那我們就只需要在參數(shù)上想辦法解決,有沒一種方法可以讓函數(shù)保存參數(shù)直到某個時刻再執(zhí)行?自然是有的,而且還是利用之前學(xué)過的知識點(diǎn):
1, 函數(shù)作用域內(nèi)的 arguments 入?yún)⒆兞?
2, 遞歸;
3, 閉包;

我們可以先看看柯里化后執(zhí)行過程大概如此

add(a)(b)(c);
========相當(dāng)于===========
function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c
    }
  }
}
實(shí)踐開始

接下來我們就分階段寫一個方法讓原函數(shù)轉(zhuǎn)換成這種柯里化函數(shù)用法.

我們先寫一個最簡單的柯里化函數(shù)如下
function toCurry(fn) {
  //轉(zhuǎn)入?yún)?shù)組
  var args = [].slice.call(arguments),
    //提取執(zhí)行方法
    fn = args.shift();
  return function () {
    //拼接上次入?yún)⒑瓦@次入?yún)?zhí)行方法
    return fn.apply(null, args.concat([].slice.call(arguments)));
  };
}

function add(a, b) {
  return a + b
}

console.log(toCurry(add, 1)(2)); // 3

已經(jīng)初步走向柯里化的道路了,然后繼續(xù)擴(kuò)展延伸,

把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)
function toCurry(fn) {
  //保存回調(diào)函數(shù)
  return function curry() {
    var args = [].slice.call(arguments);
    //判斷目前入?yún)⑹欠襁_(dá)到函數(shù)要求入?yún)?    if (args.length < fn.length) {
      return function () {
        //遞歸調(diào)用curry,拼接當(dāng)前arguments和下次入?yún)⒌腶rguments
        return curry.apply(null, args.concat([].slice.call(arguments)))
      }
    } else {
      //執(zhí)行函數(shù)
      return fn.apply(null, args)
    }
  }
}

function add(a, b, c) {
  return a + b + c
}

var fn = toCurry(add);
console.log(fn(1, 2)(3)); //6
console.log(fn(1)(2)(3)); //6

目前不管怎么傳只要達(dá)到數(shù)量就會執(zhí)行,接下來我們要把固定入?yún)⑥D(zhuǎn)成自動入?yún)⒎绞?所以要在執(zhí)行判斷那里下功夫,我們現(xiàn)在是根據(jù)函數(shù)的入?yún)n.length來判斷是否執(zhí)行,這需要在函數(shù)預(yù)先定義好,先嘗試一下把這個改成人手控制.

//新增入?yún)?shù)目限制
function toCurry(fn, len) {
  var len = len || fn.length;
  //保存回調(diào)函數(shù)
  return function curry() {
    var args = [].slice.call(arguments);
    //判斷目前入?yún)⑹欠襁_(dá)到函數(shù)要求入?yún)?    if (args.length < len) {
      return function () {
        //遞歸調(diào)用curry,拼接當(dāng)前arguments和下次入?yún)⒌腶rguments
        return curry.apply(null, args.concat([].slice.call(arguments)))
      }
    } else {
      //執(zhí)行函數(shù)
      return fn.apply(null, args)
    }
  }
}

function add(a, b, c) {
  return a + b + c
}

var fn = toCurry(add, 3);
console.log(fn(1)(2)(3));//6

現(xiàn)在已經(jīng)可以自定義配置執(zhí)行時機(jī),但是可不可以更進(jìn)一步,把配置這一步驟都免掉呢?這個就讓大家自由發(fā)揮吧,下面貼出一個網(wǎng)上流傳的寫法

function add() {
  var args = [].slice.call(arguments);
  var fn = function () {
    var newArgs = args.concat([].slice.call(arguments));
    return add.apply(null, newArgs);
  }
  fn.toString = function () {
    return args.reduce(function (a, b) {
      return a + b;
    })
  }
  return fn;
}
console.log(add(1)(2)(3));

這算是一種取巧的寫法,偷換toString寫法達(dá)到執(zhí)行的目的.因?yàn)槲視簳r還沒什么想法,就不說了,
接下來還有一個問題,有些函數(shù)需要用到tihs指向的時候,我們已經(jīng)早就丟失了,所以在通用函數(shù)還要保存指針

//新增入?yún)?shù)目限制
function toCurry(fn, len) {
  var len = len || fn.length;
  //保存回調(diào)函數(shù)
  return function curry() {
    var args = [].slice.call(arguments),
      self = this;
    //判斷目前入?yún)⑹欠襁_(dá)到函數(shù)要求入?yún)?    if (args.length < len) {
      return function () {
        //遞歸調(diào)用curry,拼接當(dāng)前arguments和下次入?yún)⒌腶rguments
        return curry.apply(self, args.concat([].slice.call(arguments)))
      }
    } else {
      //執(zhí)行函數(shù)
      return fn.apply(self, args)
    }
  }
}
var num = {
  name: "mike",
  add: function add(a, b, c) {
    console.log(this.name);
    return a + b + b
  }
}

num.add = toCurry(num.add, 3);
console.log(num.add(1)(2)(3));

好的,目前除了固定參數(shù)那一塊還沒其他思路,基本方法時成型了,另外再給一個延遲執(zhí)行的例子寫法大家看

var add = (function (fn) {
  var ary = [];
  return function curry() {
    var args = [].slice.call(arguments);
    if (args.length) {
      ary = ary.concat(args)
      return curry
    } else {
      return fn.apply(null, ary)
    }

    return args.length ? curry : fn.apply(null, ary)
  }
})(function () {
  console.log([].slice.call(arguments).reduce((total, cur) => total += cur));
})

add(1)(2)(3)(); // 6

有參數(shù)就保存,沒參數(shù)就執(zhí)行,這種使用場景也比較多,根據(jù)情況寫法不同,例如這里就沒有保存this指向.

總的來說,柯里化主要更改在于傳入的參數(shù)個數(shù),以及它如何影響代碼的結(jié)果.
(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)

惰性載入函數(shù)

如果平時有自己寫一些兼容不同瀏覽器差異代碼的話,肯定之后中間夾雜著很多判斷分支,作為強(qiáng)迫癥的程序員怎么可以忍受這些東西,例如:

function addEvent(element, eType, handle, bol) {
  if (element.addEventListener) {
    element.addEventListener(eType, handle, bol);
  } else if (element.attachEvent) {
    element.attachEvent("on" + eType, handle);
  } else {
    element["on" + eType] = handle;
  }
}
解決方案

1, 在第一次調(diào)用的過程中,該函數(shù)會被覆蓋為另外一個按合適方式執(zhí)行的函數(shù)

function addEvent() {
  if (document.addEventListener) {
    addEvent = function (element, eType, handle, bol) {
      element.addEventListener(eType, handle, bol);
    }
  } else if (document.attachEvent) {
    addEvent = function (element, eType, handle, bol) {
      element.attachEvent("on" + eType, handle);
    }
  } else {
    addEvent = function (element, eType, handle, bol) {
      element["on" + eType] = handle;
    }
  }
  return addEvent.apply(this, arguments);
}

除了第一次會執(zhí)行判斷,然后原函數(shù)被覆蓋成分支流程代碼再返回函數(shù)

2, 上面提過的匿名自執(zhí)行閉包寫法,比第一種好處是省去手動觸發(fā)第一次執(zhí)行.

var addEvent = (function () {
  if (document.addEventListener) {
    return function (element, eType, handle, bol) {
      element.addEventListener(eType, handle, bol);
    }
  } else if (document.attachEvent) {
    return function (element, eType, handle, bol) {
      element.attachEvent("on" + eType, handle);
    }
  } else {
    return function (element, eType, handle, bol) {
      element["on" + eType] = handle;
    }
  }
})();
尾調(diào)用優(yōu)化

函數(shù)的最后一步是調(diào)用另一個函數(shù),之所以有這種寫法就是因?yàn)樯厦嬲f的執(zhí)行環(huán)境(execution context)及作用域(scope),關(guān)鍵就是最后一步能不能把損耗降到最低,例如:

//沒錯,又是我們的熟面孔函數(shù)
function add() {
  var a = 1,
    b = 2;
  return result(a + b)
}

function result(num) {
  return num;
}
console.log(add()); // 3

由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的環(huán)境變量,因?yàn)槎疾粫儆玫搅?,只要直接只保留?nèi)層函數(shù)的環(huán)境變量(代碼8)就可以了。

尾遞歸

還用上面的遞歸例子,這是一個一直累積環(huán)境變量的寫法.

function factorial(num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1);
  }
}
console.log(factorial(5));//120

如果我們能利用尾調(diào)用優(yōu)化寫法就可以讓它一直保持一層的環(huán)境變量記錄.

function factorial(num, total) {
  if (num === 1) return total;
  return factorial((num - 1), total * num);
}
console.log(factorial(5, 1)); //120

缺點(diǎn)

把運(yùn)算放在方法入?yún)⒅?可讀性語義化都是個問題;

需要傳入初始值;(可以采用上面尾調(diào)用方法,柯里化等寫法解決,實(shí)際意義不大)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106215.html

相關(guān)文章

  • JavasScript重難點(diǎn)知識

    摘要:忍者級別的函數(shù)操作對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

    forsigner 評論0 收藏0
  • 前端基礎(chǔ)進(jìn)階(八):深入詳解函數(shù)的柯里

    摘要:函數(shù)被轉(zhuǎn)化之后得到柯里化函數(shù),能夠處理的所有剩余參數(shù)。因此柯里化也被稱為部分求值。那么函數(shù)的柯里化函數(shù)則可以如下因此下面的運(yùn)算方式是等價的。而這里對于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在。額外知識補(bǔ)充無限參數(shù)的柯里化。 showImg(https://segmentfault.com/img/remote/1460000008493346); 柯里化是函數(shù)的一個比較高級的應(yīng)用,想要...

    kk_miles 評論0 收藏0
  • javascript 閉包 ---- js進(jìn)化之路

    摘要:閉包利用的,其實(shí)就是作用域嵌套情況下,內(nèi)部作用域可以訪問外部作用域這一特性。之所以要將閉包和垃圾回收策略聯(lián)系在一起,是因?yàn)檫@涉及到閉包的一些重要特性,如變量暫時存儲和內(nèi)存泄漏。因?yàn)槟涿瘮?shù)回調(diào)的閉包實(shí)際引用的是變量,而非變量的值。 本文旨在總結(jié)在js學(xué)習(xí)過程中,對閉包的思考,理解,以及一些反思,如有不足,還請大家指教 閉包---closure 閉包是js中比較特殊的一個概念,其特殊之處...

    HtmlCssJs 評論0 收藏0
  • 全本 | iKcamp翻譯 | 《JavaScript 輕量級函數(shù)式編程》|《你不知道的JS》姊妹篇

    摘要:本書主要探索函數(shù)式編程的核心思想。我們在中應(yīng)用的僅僅是一套基本的函數(shù)式編程概念的子集。我稱之為輕量級函數(shù)式編程。通常來說,關(guān)于函數(shù)式編程的書籍都熱衷于拓展閱讀者的知識面,并企圖覆蓋更多的知識點(diǎn)。,本書統(tǒng)稱為函數(shù)式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson?。 禮ou-Dont-Know-JS》作者 譯者團(tuán)隊(duì)(排名不分先后)...

    paney129 評論0 收藏0
  • JavaScript深入之類數(shù)組對象與arguments

    摘要:在客戶端中,一些方法等也返回類數(shù)組對象。對象接下來重點(diǎn)講講對象。在函數(shù)體中,指代該函數(shù)的對象。下一篇文章深入之創(chuàng)建對象的多種方式以及優(yōu)缺點(diǎn)深入系列深入系列目錄地址。 JavaScript深入系列第十三篇,講解類數(shù)組對象與對象的相似與差異以及arguments的注意要點(diǎn) 類數(shù)組對象 所謂的類數(shù)組對象: 擁有一個 length 屬性和若干索引屬性的對象 舉個例子: var array = ...

    AlienZHOU 評論0 收藏0

發(fā)表評論

0條評論

hqman

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<