摘要:一函數(shù)聲明函數(shù)表達(dá)式匿名函數(shù)與自執(zhí)行函數(shù)關(guān)于函數(shù)在實(shí)際開發(fā)中的應(yīng)用,大體可以總結(jié)為函數(shù)聲明函數(shù)表達(dá)式匿名函數(shù)自執(zhí)行函數(shù)。而匿名函數(shù),顧名思義,就是指的沒有被顯示進(jìn)行賦值操作的函數(shù)。而函數(shù)自執(zhí)行,其實(shí)是匿名函數(shù)的一種應(yīng)用。
縱觀JavaScript中所有必須需要掌握的重點(diǎn)知識中,函數(shù)是我們在初學(xué)的時候最容易忽視的一個知識點(diǎn)。在學(xué)習(xí)的過程中,可能會有很多人、很多文章告訴你面向?qū)ο蠛苤匾秃苤匾墒菂s很少有人告訴你,面向?qū)ο笾兴械闹攸c(diǎn)難點(diǎn),幾乎都與函數(shù)息息相關(guān)。
包括我之前幾篇文章介紹的執(zhí)行上下文,變量對象,閉包,this等,都是圍繞函數(shù)的細(xì)節(jié)來展開。
我知道很多人在學(xué)習(xí)中,很急切的希望自己快一點(diǎn)開始學(xué)習(xí)面向?qū)ο螅瑢W(xué)習(xí)模塊,學(xué)習(xí)流行框架,然后迅速成為高手。但是我可以很負(fù)責(zé)的告訴你,關(guān)于函數(shù)的這些基礎(chǔ)東西沒理解到一定程度,那么你的學(xué)習(xí)進(jìn)展一定是舉步維艱的。
所以,大家一定要重視函數(shù)!
當(dāng)然,關(guān)于函數(shù)的重點(diǎn),難點(diǎn)在前面幾篇文章都已經(jīng)說得差不多了,這篇文章主要總結(jié)一下函數(shù)的基礎(chǔ)知識,并初步學(xué)習(xí)函數(shù)式編程的思維。
關(guān)于函數(shù)在實(shí)際開發(fā)中的應(yīng)用,大體可以總結(jié)為函數(shù)聲明、函數(shù)表達(dá)式、匿名函數(shù)、自執(zhí)行函數(shù)。
函數(shù)聲明
我們知道,JavaScript中,有兩種聲明方式,一個是使用var的變量聲明,另一個是使用function的函數(shù)聲明。
在前端基礎(chǔ)進(jìn)階(三):變量對象詳解中我有提到過,變量對象的創(chuàng)建過程中,函數(shù)聲明比變量聲明具有更為優(yōu)先的執(zhí)行順序,即我們常常提到的函數(shù)聲明提前。因此我們在執(zhí)行上下文中,無論在什么位置聲明了函數(shù),我們都可以在同一個執(zhí)行上下文中直接使用該函數(shù)。
fn(); // function function fn() { console.log("function"); }
函數(shù)表達(dá)式
與函數(shù)聲明不同,函數(shù)表達(dá)式使用了var進(jìn)行聲明,那么我們在確認(rèn)他是否可以正確使用的時候就必須依照var的規(guī)則進(jìn)行判斷,即變量聲明。我們知道使用var進(jìn)行變量聲明,其實(shí)是進(jìn)行了兩步操作。
// 變量聲明 var a = 20; // 實(shí)際執(zhí)行順序 var a = undefined; // 變量聲明,初始值undefined,變量提升,提升順序次于function聲明 a = 20; // 變量賦值,該操作不會提升
同樣的道理,當(dāng)我們使用變量聲明的方式來聲明函數(shù)時,就是我們常常說的函數(shù)表達(dá)式。函數(shù)表達(dá)的提升方式與變量聲明一致。
fn(); // 報(bào)錯 var fn = function() { console.log("function"); }
上例子的執(zhí)行順序?yàn)椋?/p>
var fn = undefined; // 變量聲明提升 fn(); // 執(zhí)行報(bào)錯 fn = function() { // 賦值操作,此時將后邊函數(shù)的引用賦值給fn console.log("function"); }
因此,由于聲明方式的不同,導(dǎo)致了函數(shù)聲明與函數(shù)表達(dá)式在使用上的一些差異需要我們注意,除此之外,這兩種形式的函數(shù)在使用上并無不同。
關(guān)于上面例子中,函數(shù)表達(dá)式中的賦值操作,在其他一些地方也會被經(jīng)常使用,我們清楚其中的關(guān)系即可。
在構(gòu)造函數(shù)中添加方法 function Person(name) { this.name = name; this.age = age; // 在構(gòu)造函數(shù)內(nèi)部中添加方法 this.getAge = function() { return this.age; } this. } // 給原型添加方法 Person.prototype.getName = function() { return this.name; } // 在對象中添加方法 var a = { m: 20, getM: function() { return this.m; } }
匿名函數(shù)
在上面我們大概講述了函數(shù)表達(dá)式中的賦值操作。而匿名函數(shù),顧名思義,就是指的沒有被顯示進(jìn)行賦值操作的函數(shù)。它的使用場景,多作為一個參數(shù)傳入另一個函數(shù)中。
var a = 10; var fn = function(bar, num) { return bar() + num; } fn(function() { return a; }, 20)
在上面的例子中,fn的第一個參數(shù)傳入了一個匿名函數(shù)。雖然該匿名函數(shù)沒有顯示的進(jìn)行賦值操作,我們沒有辦法在外部執(zhí)行上下文中引用到它,但是在fn函數(shù)內(nèi)部,我們將該匿名函數(shù)賦值給了變量bar,保存在了fn變量對象的arguments對象中。
// 變量對象在fn上下文執(zhí)行過程中的創(chuàng)建階段 VO(fn) = { arguments: { bar: undefined, num: undefined, length: 2 } } // 變量對象在fn上下文執(zhí)行過程中的執(zhí)行階段 // 變量對象變?yōu)榛顒訉ο螅⑼瓿少x值操作與執(zhí)行可執(zhí)行代碼 VO -> AO AO(fn) = { arguments: { bar: function() { return a }, num: 20, length: 2 } }
由于匿名函數(shù)傳入另一個函數(shù)之后,最終會在另一個函數(shù)中執(zhí)行,因此我們也常常稱這個匿名函數(shù)為回調(diào)函數(shù)。關(guān)于匿名函數(shù)更多的內(nèi)容,我會在下一篇深入探討柯里化的文章中進(jìn)行更加詳細(xì)講解。
匿名函數(shù)的這個應(yīng)用場景幾乎承擔(dān)了函數(shù)的所有難以理解的知識點(diǎn),因此我們一定要對它的這些細(xì)節(jié)了解的足夠清楚,如果對于變量對象的演變過程你還看不太明白,一定要回過頭去看這篇文章:前端基礎(chǔ)進(jìn)階(三):變量對象詳解
函數(shù)自執(zhí)行與塊級作用域
在ES5中,沒有塊級作用域,因此我們常常使用函數(shù)自執(zhí)行的方式來模仿塊級作用域,這樣就提供了一個獨(dú)立的執(zhí)行上下文,結(jié)合閉包,就為模塊化提供了基礎(chǔ)。而函數(shù)自執(zhí)行,其實(shí)是匿名函數(shù)的一種應(yīng)用。
(function() { // ... })();
一個模塊往往可以包括:私有變量、私有方法、公有變量、公有方法。
根據(jù)作用域鏈的單向訪問,外面可能很容易知道在這個獨(dú)立的模塊中,外部執(zhí)行環(huán)境是無法訪問內(nèi)部的任何變量與方法的,因此我們可以很容易的創(chuàng)建屬于這個模塊的私有變量與私有方法。
(function() { // 私有變量 var age = 20; var name = "Tom"; // 私有方法 function getName() { return `your name is ` + name; } })();
但是共有方法和變量應(yīng)該怎么辦?大家還記得我們前面講到過的閉包的特性嗎?沒錯,利用閉包,我們可以訪問到執(zhí)行上下文內(nèi)部的變量和方法,因此,我們只需要根據(jù)閉包的定義,創(chuàng)建一個閉包,將你認(rèn)為需要公開的變量和方法開放出來即可。
如果你對閉包了解不夠,前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包應(yīng)該可以幫到你。
(function() { // 私有變量 var age = 20; var name = "Tom"; // 私有方法 function getName() { return `your name is ` + name; } // 共有方法 function getAge() { return age; } // 將引用保存在外部執(zhí)行環(huán)境的變量中,形成閉包,防止該執(zhí)行環(huán)境被垃圾回收 window.getAge = getAge; })();
當(dāng)然,閉包在模塊中的重要作用,我們也在講解閉包的時候已經(jīng)強(qiáng)調(diào)過,但是這個知識點(diǎn)真的太重要,需要我們反復(fù)理解并且徹底掌握,因此為了幫助大家進(jìn)一步理解閉包,我們來看看jQuery中,是如何利用我們模塊與閉包的。
// 使用函數(shù)自執(zhí)行的方式創(chuàng)建模塊 (function(window, undefined) { // 聲明jQuery構(gòu)造函數(shù) var jQuery = function(name) { // 主動在構(gòu)造函數(shù)中,返回一個jQuery實(shí)例 return new jQuery.fn.init(name); } // 添加原型方法 jQuery.prototype = jQuery.fn = { constructor: jQuery, init:function() { ... }, css: function() { ... } } jQuery.fn.init.prototype = jQuery.fn; // 將jQuery改名為$,并將引用保存在window上,形成閉包,對外開發(fā)jQuery構(gòu)造函數(shù),這樣我們就可以訪問所有掛載在jQuery原型上的方法了 window.jQuery = window.$ = jQuery; })(window); // 在使用時,我們直接執(zhí)行了構(gòu)造函數(shù),因?yàn)樵趈Query的構(gòu)造函數(shù)中通過一些手段,返回的是jQuery的實(shí)例,所以我們就不用再每次用的時候在自己new了 $("#div1");
在這里,我們只需要看懂閉包與模塊的部分就行了,至于內(nèi)部的原型鏈?zhǔn)侨绾卫@的,為什么會這樣寫,我在講面向?qū)ο蟮臅r候會為大家慢慢分析。舉這個例子的目的所在,就是希望大家能夠重視函數(shù),因?yàn)樵趯?shí)際開發(fā)中,它無處不在。
接下來我要分享一個高級的,非常有用的模塊的應(yīng)用。當(dāng)我們的項(xiàng)目越來越大,那么需要保存的數(shù)據(jù)與狀態(tài)就越來越多,因此,我們需要一個專門的模塊來維護(hù)這些數(shù)據(jù),這個時候,有一個叫做狀態(tài)管理器的東西就應(yīng)運(yùn)而生。對于狀態(tài)管理器,最出名的,我想非redux莫屬了。雖然對于還在學(xué)習(xí)中的大家來說,redux是一個有點(diǎn)高深莫測的東西,但是在我們學(xué)習(xí)之前,可以先通過簡單的方式,讓大家大致了解狀態(tài)管理器的實(shí)現(xiàn)原理,為我們未來的學(xué)習(xí)奠定堅(jiān)實(shí)的基礎(chǔ)。
先來直接看代碼。
// 自執(zhí)行創(chuàng)建模塊 (function() { // states 結(jié)構(gòu)預(yù)覽 // states = { // a: 1, // b: 2, // m: 30, // o: {} // } var states = {}; // 私有變量,用來存儲狀態(tài)與數(shù)據(jù) // 判斷數(shù)據(jù)類型 function type(elem) { if(elem == null) { return elem + ""; } return toString.call(elem).replace(/[[]]/g, "").split(" ")[1].toLowerCase(); } /** * @Param name 屬性名 * @Description 通過屬性名獲取保存在states中的值 */ function get(name) { return states[name] ? states[name] : ""; } function getStates() { return states; } /* * @param options {object} 鍵值對 * @param target {object} 屬性值為對象的屬性,只在函數(shù)實(shí)現(xiàn)時遞歸中傳入 * @desc 通過傳入鍵值對的方式修改state樹,使用方式與小程序的data或者react中的setStates類似 */ function set(options, target) { var keys = Object.keys(options); var o = target ? target : states; keys.map(function(item) { if(typeof o[item] == "undefined") { o[item] = options[item]; } else { type(o[item]) == "object" ? set(options[item], o[item]) : o[item] = options[item]; } return item; }) } // 對外提供接口 window.get = get; window.set = set; window.getStates = getStates; })() // 具體使用如下 set({ a: 20 }); // 保存 屬性a set({ b: 100 }); // 保存屬性b set({ c: 10 }); // 保存屬性c // 保存屬性o, 它的值為一個對象 set({ o: { m: 10, n: 20 } }) // 修改對象o 的m值 set({ o: { m: 1000 } }) // 給對象o中增加一個c屬性 set({ o: { c: 100 } }) console.log(getStates())
demo實(shí)例在線地址
我之所以說這是一個高級應(yīng)用,是因?yàn)樵趩雾搼?yīng)用中,我們很可能會用到這樣的思路。根據(jù)我們提到過的知識,理解這個例子其實(shí)很簡單,其中的難點(diǎn)估計(jì)就在于set方法的處理上,因?yàn)闉榱司哂懈嗟倪m用性,因此做了很多適配,用到了遞歸等知識。如果你暫時看不懂,沒有關(guān)系,知道如何使用就行了,上面的代碼可以直接運(yùn)用于實(shí)際開發(fā)。記住,當(dāng)你需要保存的狀態(tài)太多的時候,你就想到這一段代碼就行了。
函數(shù)自執(zhí)行的方式另外還有其他幾種寫法,諸如!function(){}(),+function(){}()
還記得基本數(shù)據(jù)類型與引用數(shù)據(jù)類型在復(fù)制上的差異嗎?基本數(shù)據(jù)類型復(fù)制,是直接值發(fā)生了復(fù)制,因此改變后,各自相互不影響。但是引用數(shù)據(jù)類型的復(fù)制,是保存在變量對象中的引用發(fā)生了復(fù)制,因此復(fù)制之后的這兩個引用實(shí)際訪問的實(shí)際是同一個堆內(nèi)存中的值。當(dāng)改變其中一個時,另外一個自然也被改變。如下例。
var a = 20; var b = a; b = 10; console.log(a); // 20 var m = { a: 1, b: 2 } var n = m; n.a = 5; console.log(m.a) // 5
當(dāng)值作為函數(shù)的參數(shù)傳遞進(jìn)入函數(shù)內(nèi)部時,也有同樣的差異。我們知道,函數(shù)的參數(shù)在進(jìn)入函數(shù)后,實(shí)際是被保存在了函數(shù)的變量對象中,因此,這個時候相當(dāng)于發(fā)生了一次復(fù)制。如下例。
var a = 20; function fn(a) { a = a + 10; return a; } fn(a); console.log(a); // 20
var a = { m: 10, n: 20 } function fn(a) { a.m = 20; return a; } fn(a); console.log(a); // { m: 20, n: 20 }
正是由于這樣的不同,導(dǎo)致了許多人在理解函數(shù)參數(shù)的傳遞方式時,就有許多困惑。到底是按值傳遞還是按引用傳遞?實(shí)際上結(jié)論仍然是按值傳遞,只不過當(dāng)我們期望傳遞一個引用類型時,真正傳遞的,只是這個引用類型保存在變量對象中的引用而已。為了說明這個問題,我們看看下面這個例子。
var person = { name: "Nicholas", age: 20 } function setName(obj) { // 傳入一個引用 obj = {}; // 將傳入的引用指向另外的值 obj.name = "Greg"; // 修改引用的name屬性 } setName(person); console.log(person.name); // Nicholas 未被改變
在上面的例子中,如果person是按引用傳遞,那么person就會自動被修改為指向其name屬性值為Gerg的新對象。但是我們從結(jié)果中看到,person對象并未發(fā)生任何改變,因此只是在函數(shù)內(nèi)部引用被修改而已。
雖然JavaScript并不是一門純函數(shù)式編程的語言,但是它使用了許多函數(shù)式編程的特性。因此了解這些特性可以讓我們更加了解自己寫的代碼。
當(dāng)我們想要使用一個函數(shù)時,通常情況下其實(shí)就是想要將一些功能,邏輯等封裝起來。相信大家對于封裝這個概念并不陌生。
我們通常通過函數(shù)封裝來完成一件事情。例如,我想要計(jì)算任意三個數(shù)的和,我們就可以將這三個數(shù)作為參數(shù),封裝一個簡單的函數(shù)。
function add(a, b, c) { return a + b + c; }
當(dāng)我們想要計(jì)算三個數(shù)的和時,直接調(diào)用該方法即可。
add(1, 2, 3); // 6
當(dāng)然,當(dāng)我們想要做的事情比較簡單的時候,可能還看不出來封裝成為函數(shù)之后帶來的便利。如果我們想要做的事情稍微復(fù)雜一點(diǎn)呢。例如我想要計(jì)算一個數(shù)組中的所有子項(xiàng)目的和。
function mergeArr(arr) { var result = 0; for(var i = 0; i < arr.length; i++) { result += arr[i] } return result; }
如果我們不通過函數(shù)封裝的方式,那么再每次想要實(shí)現(xiàn)這個功能時,就不得不重新使用一次for循環(huán),這樣的后果就是我們的代碼中充斥著越來越多的重復(fù)代碼。而封裝之后,當(dāng)我們想要再次做這件事情的時候,只需要一句話就可以了。
mergeArr([1, 2, 3, 4, 5]);
當(dāng)然,我相信大家對于函數(shù)封裝的意義都應(yīng)該有非常明確的認(rèn)知,但是我們要面臨的問題是,當(dāng)我們想要去封裝一個函數(shù)時,如何做才是最佳實(shí)踐呢?
函數(shù)式編程能給我們答案。
我們在初學(xué)時,往往會不由自主的使用命令式編程的風(fēng)格來完成我們想要干的事情。因?yàn)槊钍骄幊谈拥暮唵危卑住@缥覀儸F(xiàn)在有一個數(shù)組,array = [1, 3, "h", 5, "m", "4"],現(xiàn)在想要找出這個數(shù)組中的所有類型為number的子項(xiàng)。當(dāng)我們使用命令式編程思維時,可能就會直接這樣做。
var array = [1, 3, "h", 5, "m", "4"]; var res = []; for(var i = 0; i < array.length; i ++) { if (typeof array[i] === "number") { res.push(array[i]); } }
在這種實(shí)現(xiàn)方式中,我們平鋪直敘的實(shí)現(xiàn)了我們的目的。這樣做的問題在于,當(dāng)我們在另外的時刻,想要找出另外一個數(shù)組中所有的子項(xiàng)時,我們不得不把同樣的邏輯再寫一次。當(dāng)出現(xiàn)次數(shù)變多時,我們的代碼也變得更加糟糕且難以維護(hù)。
而函數(shù)式編程的思維則建議我們將這種會多次出現(xiàn)的功能封裝起來以備調(diào)用。
function getNumbers(array) { var res = []; array.forEach(function(item) { if (typeof item === "number") { res.push(item); } }) return res; } // 以上是我們的封裝,以下是功能實(shí)現(xiàn) var array = [1, 3, "h", 5, "m", "4"]; var res = getNumbers(array);
因此當(dāng)我們將功能封裝之后,我們實(shí)現(xiàn)同樣的功能時,只需要寫一行代碼。而如果未來需求變動,或者稍作修改,我們只需要對getNumbers方法進(jìn)行調(diào)整就可以了。而且我們在使用時,只需要關(guān)心這個方法能做什么,而不用關(guān)心他具體是怎么實(shí)現(xiàn)的。這也是函數(shù)式編程思維與命令式不同的地方之一。
函數(shù)式編程思維還具有以下幾個特征。
函數(shù)是第一等公民
所謂"第一等公民"(first class),指的是函數(shù)與其他數(shù)據(jù)類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數(shù),傳入另一個函數(shù),或者作為別的函數(shù)的返回值。這些場景,我們應(yīng)該見過很多。
var a = function foo() {} // 賦值 function fn(function() {}, num) {} // 函數(shù)作為參數(shù) // 函數(shù)作為返回值 function var() { return function() { ... ... } }
當(dāng)然,這都是JavaScript的基本概念。但是我想很多人,甚至包括正在閱讀的你自己都可能會無視這些概念。可以用一個簡單的例子來驗(yàn)證一下。
我們先自定義這樣一個函數(shù)。
function delay() { console.log("5000ms之后執(zhí)行該方法."); }
現(xiàn)在要做的是,如果要求你結(jié)合setTimeout方法,讓delay方法延遲5000ms執(zhí)行,應(yīng)該怎么做?
其實(shí)很簡單,對不對,直接這樣就可以了。
var timer = setTimeout(function() { delay(); }, 5000);
那么現(xiàn)在問題來了,如果你對函數(shù)是一等公民有一個深刻的認(rèn)知,我想你會發(fā)現(xiàn)上面這種寫法其實(shí)是有一些問題的。所以思考一下,問題出在哪里?
函數(shù)既然能夠作為一個參數(shù)傳入另外一個函數(shù),那么我們是不是可以直接將delay作為setTimeout的第一個參數(shù),而不用額外的多加一層匿名函數(shù)呢?
因此,其實(shí)最正確的解法應(yīng)該這樣寫。
var timer = setTimeout(delay, 5000);
當(dāng)然,如果你已經(jīng)提前想到這樣做了,那么恭喜你,說明你在JavaScript上比普通人更有天賦。其實(shí)第一種糟糕的方式很多人都在用,包括有多年工作經(jīng)驗(yàn)的人也沒有完全避免。而他們甚至還不知道自己問題出在什么地方。
在未來的實(shí)踐中,你還會遇到更多類似的場景。為了驗(yàn)證讀者朋友們的理解,我們不妨來思考一下如何優(yōu)化下面的代碼。
function getUser(path, callback) { return $.get(path, function(info) { return callback(info); }) } getUser("/api/user", function(resp) { // resp為成功請求之后返回的數(shù)據(jù) console.log(resp); })
優(yōu)化的原理和setTimeout的例子一模一樣,我這里賣個關(guān)子,不打算告訴大家結(jié)論,僅提示一句,getUser優(yōu)化之后,僅有一句代碼。考驗(yàn)大家學(xué)習(xí)成果的時候到了 ^ ^。
只用"表達(dá)式",不用"語句"
"表達(dá)式"(expression)是一個單純的運(yùn)算過程,總是有返回值;"語句"(statement)是執(zhí)行某種操作,沒有返回值。函數(shù)式編程要求,只使用表達(dá)式,不使用語句。也就是說,每一步都是單純的運(yùn)算,而且都有返回值。
假如我們的項(xiàng)目中,多處需要改變某個元素的背景色。因此我們可以這樣封裝一下。
var ele = document.querySelector(".test"); function setBackgroundColor(color) { ele.style.backgroundColor = color; } // 多處使用 setBackgroundColor("red"); setBackgroundColor("#ccc");
我們可以很明顯的感受到,setBackgroundColor封裝的僅僅只是一條語句。這并不是理想的效果。函數(shù)式編程期望一個函數(shù)有輸入,也有輸出。因此良好的習(xí)慣應(yīng)該如下做。
function setBackgroundColor(ele, color) { ele.style.backgroundColor = color; return color; } // 多處使用 var ele = document.querySelector(".test"); setBackgroundColor(ele, "red"); setBackgroundColor(ele, "#ccc");
了解這一點(diǎn),可以讓我們自己在封裝函數(shù)的時候養(yǎng)成良好的習(xí)慣。
純函數(shù)
相同的輸入總會得到相同的輸出,并且不會產(chǎn)生副作用的函數(shù),就是純函數(shù)。
所謂"副作用"(side effect),指的是函數(shù)內(nèi)部與外部互動(最典型的情況,就是修改全局變量的值),產(chǎn)生運(yùn)算以外的其他結(jié)果。
函數(shù)式編程強(qiáng)調(diào)沒有"副作用",意味著函數(shù)要保持獨(dú)立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。
即所謂的只要是同樣的參數(shù)傳入,返回的結(jié)果一定是相等的。
例如我們期望封裝一個函數(shù),能夠得到傳入數(shù)組的最后一項(xiàng)。那么可以通過下面兩種方式來實(shí)現(xiàn)。
function getLast(arr) { return arr[arr.length]; } function getLast_(arr) { return arr.pop(); } var source = [1, 2, 3, 4]; var last = getLast(source); // 返回結(jié)果4 原數(shù)組不變 var last_ = getLast_(source); // 返回結(jié)果4 原數(shù)據(jù)最后一項(xiàng)被刪除
getLast與getLast_雖然同樣能夠獲得數(shù)組的最后一項(xiàng)值,但是getLast_改變了原數(shù)組。而當(dāng)原始數(shù)組被改變,那么當(dāng)我們再次調(diào)用該方法時,得到的結(jié)果就會變得不一樣。這樣不可預(yù)測的封裝方式,在我們看來是非常糟糕的。它會把我們的數(shù)據(jù)搞得非常混亂。在JavaScript原生支持的數(shù)據(jù)方法中,也有許多不純的方法,我們在使用時需要非常警惕,我們要清晰的知道原始數(shù)據(jù)的改變是否會留下隱患。
var source = [1, 2, 3, 4, 5]; source.slice(1, 3); // 純函數(shù) 返回[2, 3] source不變 source.splice(1, 3); // 不純的 返回[2, 3, 4] source被改變 source.pop(); // 不純的 source.push(6); // 不純的 source.shift(); // 不純的 source.unshift(1); // 不純的 source.reverse(); // 不純的 // 我也不能短時間知道現(xiàn)在source被改變成了什么樣子,干脆重新約定一下 source = [1, 2, 3, 4, 5]; source.concat([6, 7]); // 純函數(shù) 返回[1, 2, 3, 4, 5, 6, 7] source不變 source.join("-"); // 純函數(shù) 返回1-2-3-4-5 source不變
閉包
閉包是函數(shù)式編程語言的重要特性,我也在前面幾篇文章中說了很多關(guān)于閉包的內(nèi)容。這里不再贅述。
柯里化
下一章。
前端基礎(chǔ)進(jìn)階系列目錄
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/90548.html
摘要:文章分享持續(xù)更新更多資源請文章轉(zhuǎn)自一前端文章基礎(chǔ)篇,,前端基礎(chǔ)進(jìn)階一內(nèi)存空間詳細(xì)圖解前端基礎(chǔ)進(jìn)階二執(zhí)行上下文詳細(xì)圖解前端基礎(chǔ)進(jìn)階三變量對象詳解前端基礎(chǔ)進(jìn)階四詳細(xì)圖解作用域鏈與閉包前端基礎(chǔ)進(jìn)階五全方位解讀前端基礎(chǔ)進(jìn)階六在開發(fā)者工具中觀察函數(shù)調(diào) 文章分享(持續(xù)更新) 更多資源請Star:https://github.com/maidishike... 文章轉(zhuǎn)自:https://gith...
摘要:不過其實(shí)簡書文章評論里有很多大家的問題以及解答,對于進(jìn)一步理解文中知識幫助很大的,算是有點(diǎn)可惜吧。不過也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯誤,請?jiān)谠u論里告訴我,我會及時更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...
摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅(qū)動,事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進(jìn)階、CSS進(jìn)階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對...
摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅(qū)動,事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進(jìn)階、CSS進(jìn)階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對...
閱讀 1008·2021-10-27 14:15
閱讀 2763·2021-10-25 09:45
閱讀 1921·2021-09-02 09:45
閱讀 3357·2019-08-30 15:55
閱讀 1798·2019-08-29 16:05
閱讀 3189·2019-08-28 18:13
閱讀 3109·2019-08-26 13:58
閱讀 442·2019-08-26 12:01