亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

從JS對(duì)象開始,談一談“不可變數(shù)據(jù)”和函數(shù)式編程

Batkid / 3431人閱讀

摘要:下面,我就從基本對(duì)象說起,聊一聊不可變數(shù)據(jù)和的一切??勺兒凸蚕硎侨f惡之源不可變數(shù)據(jù)其實(shí)是函數(shù)式編程相關(guān)的重要概念。相對(duì)的,函數(shù)式編程中認(rèn)為可變性是萬惡之源。針對(duì)于此,我推薦一款已經(jīng)大名鼎鼎的類庫來處理不可變數(shù)據(jù)。

作為前端開發(fā)者,你會(huì)感受到JS中對(duì)象(Object)這個(gè)概念的強(qiáng)大。我們說“JS中一切皆對(duì)象”。最核心的特性,例如從String,到數(shù)組,再到瀏覽器的APIs,“對(duì)象”這個(gè)概念無處不在。在這里你可以了解到JS Objects中的一切。

同時(shí),隨著React的強(qiáng)勢崛起,不管你有沒有關(guān)注過這個(gè)框架,也一定聽說過一個(gè)概念—不可變數(shù)據(jù)(immutable.js)。究竟什么是不可變數(shù)據(jù)?這篇文章會(huì)從JS源頭—對(duì)象談起,讓你逐漸了解這個(gè)函數(shù)式編程里的重要概念。

JS中的對(duì)象是那么美妙:我們可以隨意復(fù)制他們,改變并刪除他們的某項(xiàng)屬性等。但是要記住一句話:

“伴隨著特權(quán),隨之而來的是更大的責(zé)任。”
(With great power comes great responsibility)

的確,JS Objects里概念太多了,我們切不可隨意使用對(duì)象。下面,我就從基本對(duì)象說起,聊一聊不可變數(shù)據(jù)和JS的一切。

這篇文章緣起于Daniel Leite在2017年3月16日的新鮮出爐文章:Things you should know about Objects and Immutability in JavaScript,我進(jìn)行了大致翻譯并進(jìn)行大范圍“改造”,同時(shí)改寫了用到的例子,進(jìn)行了大量更多的擴(kuò)展。

“可變和共享”是萬惡之源

不可變數(shù)據(jù)其實(shí)是函數(shù)式編程相關(guān)的重要概念。相對(duì)的,函數(shù)式編程中認(rèn)為可變性是萬惡之源。但是,為什么會(huì)有這樣的結(jié)論呢?

這個(gè)問題可能很多程序員都會(huì)有。其實(shí),如果你的代碼邏輯可變,不要驚慌,這并不是“政治錯(cuò)誤”的。比如JS中的數(shù)組操作,很對(duì)都會(huì)對(duì)原數(shù)組進(jìn)行直接改變,這當(dāng)然并沒有什么問題。比如:

let arr = [1, 2, 3, 4, 5];
arr.splice(1, 1); // 返回[2];
console.log(arr); // [1, 3, 4, 5];

這是我們常用的“刪除數(shù)組某一項(xiàng)”的操作。好吧,他一點(diǎn)問題也沒有。

問題其實(shí)出現(xiàn)在“濫用”可變性上,這樣會(huì)給你的程序帶來“副作用”。先不必關(guān)心什么是“副作用”,他又是一個(gè)函數(shù)式編程的概念。

我們先來看一下代碼實(shí)例:

const student1 = {
    school: "Baidu",
    name: "HOU Ce",
    birthdate: "1995-12-15",
}

const changeStudent = (student, newName, newBday) => {
    const newStudent = student;
    newStudent.name = newName;
    newStudent.birthdate = newBday;
    return newStudent;
}

const student2 = changeStudent(student1, "YAN Haijing", "1990-11-10");

// both students will have the name properties
console.log(student1, student2);
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"} 
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"}

我們發(fā)現(xiàn),盡管創(chuàng)建了一個(gè)新的對(duì)象student2,但是老的對(duì)象student1也被改動(dòng)了。這是因?yàn)镴S對(duì)象中的賦值是“引用賦值”,即在賦值過程中,傳遞的是在內(nèi)存中的引用(memory reference)。具體說就是“棧存儲(chǔ)”和“堆存儲(chǔ)”的問題。具體圖我就不畫了,理解不了可以單找我。

不可變數(shù)據(jù)的強(qiáng)大和實(shí)現(xiàn)

我們說的“不可變”,其實(shí)是指保持一個(gè)對(duì)象狀態(tài)不變。這樣做的好處是使得開發(fā)更加簡單,可回溯,測試友好,減少了任何可能的副作用。
函數(shù)式編程認(rèn)為:

只有純的沒有副作用的函數(shù),才是合格的函數(shù)。

好吧,現(xiàn)在開始解釋下“副作用”(Side effect):

在計(jì)算機(jī)科學(xué)中,函數(shù)副作用指當(dāng)調(diào)用函數(shù)時(shí),除了返回函數(shù)值之外,還對(duì)主調(diào)用函數(shù)產(chǎn)生附加的影響。例如修改全局變量(函數(shù)外的變量)或修改參數(shù)。
-維基百科

函數(shù)副作用會(huì)給程序設(shè)計(jì)帶來不必要的麻煩,給程序帶來十分難以查找的錯(cuò)誤,并降低程序的可讀性。嚴(yán)格的函數(shù)式語言要求函數(shù)必須無副作用。

那么我們避免副作用,創(chuàng)建不可變數(shù)據(jù)的主要實(shí)現(xiàn)思路就是:一次更新過程中,不應(yīng)該改變原有對(duì)象,只需要新創(chuàng)建一個(gè)對(duì)象用來承載新的數(shù)據(jù)狀態(tài)。

我們使用純函數(shù)(pure functions)來實(shí)現(xiàn)不可變性。純函數(shù)指無副作用的函數(shù)。
那么,具體怎么構(gòu)造一個(gè)純函數(shù)呢?我們可以看一下代碼實(shí)現(xiàn),我對(duì)上例進(jìn)行改造:

const student1 = {
    school: "Baidu", 
    name: "HOU Ce",
    birthdate: "1995-12-15",
}

const changeStudent = (student, newName, newBday) => {
    return {
        ...student, // 使用解構(gòu)
        name: newName, // 覆蓋name屬性
        birthdate: newBday // 覆蓋birthdate屬性
    }
}

const student2 = changeStudent(student1, "YAN Haijing", "1990-11-10");

// both students will have the name properties
console.log(student1, student2);
// Object {school: "Baidu", name: "HOU Ce", birthdate: "1995-12-15"} 
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"}

需要注意的是,我使用了ES6中的解構(gòu)(destructuring)賦值。
這樣,我們達(dá)到了想要的效果:根據(jù)參數(shù),產(chǎn)生了一個(gè)新對(duì)象,并正確賦值,最重要的就是并沒有改變原對(duì)象。

創(chuàng)建純函數(shù),過濾副作用

現(xiàn)在,我們知道了“不可變”到底指的是什么。接下來,我們就要分析一下純函數(shù)應(yīng)該如何實(shí)現(xiàn),進(jìn)而生產(chǎn)不可變數(shù)據(jù)。

其實(shí)創(chuàng)建不可變數(shù)據(jù)方式有很多,在使用原生JS的基礎(chǔ)上,我推薦的方法是使用現(xiàn)有的Objects API和ES6當(dāng)中的解構(gòu)賦值(上例已經(jīng)演示)?,F(xiàn)在看一下Objects.assign的實(shí)現(xiàn)方式:

const student1 = {
    school: "Baidu", 
    name: "HOU Ce",
    birthdate: "1995-12-15",
}

const changeStudent = (student, newName, newBday) => Object.assign({}, student, {name: newName, birthdate: newBday})

const student2 = changeStudent(student1, "YAN Haijing", "1990-11-10");

// both students will have the name properties
console.log(student1, student2);
// Object {school: "Baidu", name: "HOU Ce", birthdate: "1995-12-15"};
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10"};

同樣,如果是處理數(shù)組相關(guān)的內(nèi)容,我們可以使用:.map, .filter或者.reduce去達(dá)成目標(biāo)。這些APIs的共同特點(diǎn)就是不會(huì)改變原數(shù)組,而是產(chǎn)生并返回一個(gè)新數(shù)組。這和純函數(shù)的思想不謀而合。

但是,再說回來,使用Object.assign請務(wù)必注意以下幾點(diǎn):
1)他的復(fù)制,是將所有可枚舉屬性,復(fù)制到目標(biāo)對(duì)象。換句話說,不可枚舉屬性是無法完成復(fù)制的。
2)對(duì)象中如果包含undefined和null類型內(nèi)容,會(huì)報(bào)錯(cuò)。
3)最重要的一點(diǎn):Object.assign方法實(shí)行的是淺拷貝,而不是深拷貝。

第三點(diǎn)很重要,也就是說,如果源對(duì)象某個(gè)屬性的值是對(duì)象,那么目標(biāo)對(duì)象拷貝得到的是這個(gè)屬性對(duì)象的引用。這也就意味著,當(dāng)對(duì)象存在嵌套時(shí),還是有問題的。比如下面代碼:

const student1 = {
    school: "Baidu", 
    name: "HOU Ce",
    birthdate: "1995-12-15",
    friends: {
        friend1: "ZHAO Wenlin",
        friend2: "CHENG Wen"
    }
}

const changeStudent = (student, newName, newBday, friends) => Object.assign({}, student, {name: newName, birthdate: newBday})

const student2 = changeStudent(student1, "YAN Haijing", "1990-11-10");

// both students will have the name properties
console.log(student1, student2); 
// Object {school: "Baidu", name: "HOU Ce", birthdate: "1995-12-15", friends: Object}
// Object {school: "Baidu", name: "YAN Haijing", birthdate: "1990-11-10", friends: Object}

student2.friends.friend1 = "MA xiao";
console.log(student1.friends.friend1); // "MA xiao"

對(duì)student2 friends列表當(dāng)中的friend1的修改,同時(shí)也影響了student1 friends列表當(dāng)中的friend1。

JS本身的蒼白無力VS不可變數(shù)據(jù)類庫

以上,我們分析了純JS如何實(shí)現(xiàn)不可變數(shù)據(jù)。這樣處理帶來的一個(gè)負(fù)面影響在于:一些經(jīng)典APIs都是shallow處理,比如上文提到的Object.assign就是典型的淺拷貝。如果遇到嵌套很深的結(jié)構(gòu),我們就需要手動(dòng)遞歸。這樣做呢,又會(huì)存在性能上的問題。

比如我自己動(dòng)手用遞歸實(shí)現(xiàn)一個(gè)深拷貝,需要考慮循環(huán)引用的“死環(huán)”問題,另外,當(dāng)使用大規(guī)模數(shù)據(jù)結(jié)構(gòu)時(shí),性能劣勢盡顯無疑。我們熟悉的jquery extends方法,某一版本(最新版本情況我不太了解)的實(shí)現(xiàn)是進(jìn)行了三層拷貝,也沒有達(dá)到完備的deep copy。

總之,實(shí)現(xiàn)不可變數(shù)據(jù),我們必然要關(guān)心性能問題。針對(duì)于此,我推薦一款已經(jīng)“大名鼎鼎”的——immutable.js類庫來處理不可變數(shù)據(jù)。

他的實(shí)現(xiàn)既保證了不可變性,又保證了性能大限度優(yōu)化。原理很有意思,下面這段話,我摘自camsong前輩的文章:

Immutable實(shí)現(xiàn)的原理是Persistent Data Structure(持久化數(shù)據(jù)結(jié)構(gòu)),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時(shí),要保證舊數(shù)據(jù)同時(shí)可用且不變。

同時(shí)為了避免deepCopy把所有節(jié)點(diǎn)都復(fù)制一遍帶來的性能損耗,Immutable使用了Structural Sharing(結(jié)構(gòu)共享),即如果對(duì)象樹中一個(gè)節(jié)點(diǎn)發(fā)生變化,只修改這個(gè)節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn),其它節(jié)點(diǎn)則進(jìn)行共享。

感興趣的讀者可以深入研究下,這是很有意思的。如果有需要,我也愿意再寫一篇immutable.js源碼分析。

總結(jié)

我們使用JavaScript操縱對(duì)象,這樣的方式很簡單便捷。但是,這樣操控的基礎(chǔ)是在JavaScript靈活機(jī)制的熟練掌握上。不然很容易使你“頭大”。

在我開發(fā)的百度某部門私信項(xiàng)目中,因?yàn)槭褂昧薘eact+Redux技術(shù)棧,并且數(shù)據(jù)結(jié)構(gòu)較為負(fù)責(zé),所以我也采用了immutable.js實(shí)現(xiàn)。

最后,在前端開發(fā)中,函數(shù)式編程越來越熱,并且在某種程度上已經(jīng)取代了“過程式”編程和面向?qū)ο笏枷搿?/p>

我的感想是在某些特定的場景下,不要畏懼變化,擁抱未來。
就像我很喜歡的葡萄牙詩人安德拉德一首詩中那樣說的:

我同樣不知道什么是海,

赤腳站在沙灘上,
急切地等待著黎明的到來。

Happy Coding!

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

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

相關(guān)文章

  • 攻克前端javascript面試:什么是函數(shù)編程?

    摘要:僅在幾年以前,僅有少數(shù)的程序員知道函數(shù)式編程是什么。函數(shù)式編程是聲明性的而不是命令式的應(yīng)用狀態(tài)流經(jīng)純函數(shù)中。函數(shù)式編程是一種編程模式。在理解軟件是如何使用函數(shù)式編程構(gòu)建時(shí),理解函數(shù)組合是非常重要的一步。不可變性是函數(shù)式編程的核心概念。 函數(shù)式編程已然變成了一個(gè)javascript語言中一個(gè)非常熱門的話題。僅在幾年以前,僅有少數(shù)的js程序員知道函數(shù)式編程是什么。但是在過去三年中,我所見過...

    wslongchen 評(píng)論0 收藏0
  • 一談Vuex

    摘要:是什么官方文檔說道是一個(gè)專為應(yīng)用程序開發(fā)的狀態(tài)管理模式。觸發(fā)之別名篇觸發(fā)之對(duì)象展開運(yùn)算符篇觸發(fā)之對(duì)象展開運(yùn)算符別名篇先引用官方文檔的說法類似于,不同在于提交的是,而不是直接變更狀態(tài)。 Vuex是什么 官方文檔說道:Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化 什么是狀態(tài)管理模式...

    nifhlheimr 評(píng)論0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡單的方式。插....

    izhuhaodev 評(píng)論0 收藏0
  • JS凍結(jié)對(duì)象的《人間詞話》 完美實(shí)現(xiàn)究竟有幾層境界?

    摘要:王國維在人間詞話里談到了治學(xué)經(jīng)驗(yàn),他說古今之成大事業(yè)大學(xué)問者,必經(jīng)過三種之境界。其中談到中凍結(jié)一個(gè)對(duì)象幾種由淺入深的實(shí)踐。王國維已先自表明,吾人可以無勞糾葛??偨Y(jié)本文先后介紹了關(guān)于凍結(jié)一個(gè)對(duì)象的三種進(jìn)階方法。 王國維在《人間詞話》里談到了治學(xué)經(jīng)驗(yàn),他說:古今之成大事業(yè)、大學(xué)問者,必經(jīng)過三種之境界。 巧合的是,最近受 git chat / git book 邀請,做了一個(gè)分享。其中談到J...

    YorkChen 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

Batkid

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<