
文章目錄二十三、函數(shù)式編程1. 體驗函數(shù)式編程1.1 JS 中的函數(shù)是一等公民1.2 函數(shù)的作用域鏈1.3 純函數(shù) vs 非純函數(shù)2. 數(shù)組的常用函數(shù)式方法2.1 map - 數(shù)據(jù)轉(zhuǎn)換2.2 filter - 數(shù)據(jù)篩選2.3 reduce - 數(shù)據(jù)聚合3. 閉包了解3.1 閉包的形成3.2 閉包的經(jīng)典應(yīng)用3.2.1 數(shù)據(jù)私有化模塊模式3.2.2 函數(shù)工廠4. 函數(shù)式編程之高階函數(shù)5. 函數(shù)式編程之函數(shù)組合6. 函數(shù)式編程之柯里化7. 實戰(zhàn)練習(xí)7.1 練習(xí)1實現(xiàn)一個簡單的計數(shù)器工廠7.2 練習(xí)2使用函數(shù)式方法處理數(shù)據(jù)二十四、原型對象1. prototype 是什么2. prototype 的作用2.1 實現(xiàn)繼承2.2 共享屬性和方法2.3 動態(tài)擴(kuò)展功能3. 原型鏈的終點(diǎn)二十三、函數(shù)式編程函數(shù)式編程Functional Programming, FP是一種以函數(shù)為核心的編程范式函數(shù)可以作為參數(shù)返回值變量等。1. 體驗函數(shù)式編程1.1 JS 中的函數(shù)是一等公民函數(shù)中可以嵌套函數(shù)函數(shù)可以作為方法的參數(shù)函數(shù)可以作為返回值。請看一下代碼函數(shù)中嵌套函數(shù)函數(shù)作為返回值// 外部函數(shù)functionouter(){// 內(nèi)部函數(shù)functioninner(username){console.log(Hello${username});}// 函數(shù)也是對象可以將函數(shù)返回給調(diào)用者。returninner;}// 調(diào)用者無法直接訪問內(nèi)部函數(shù)//inner(); // 報錯inner is not defined// 可以通過調(diào)用外部函數(shù)拿到內(nèi)部函數(shù)innerFun是一個指向內(nèi)部函數(shù)的一個變量constinnerFunouter();// 雖然是變量但可以當(dāng)做內(nèi)部函數(shù)使用。innerFun(Tom);函數(shù)也可以作為參數(shù)如下代碼functionfun(funParam){funParam(Tom);}fun(functionsayHello(username){console.log(hello${username});});// 當(dāng)然以上函數(shù)也可以以箭頭函數(shù)的方式傳遞fun((username){console.log(Hi,${username});});1.2 函數(shù)的作用域鏈在JavaScript中作用域鏈的工作方式是內(nèi)層函數(shù)可以訪問外層函數(shù)的變量但外層函數(shù)不能訪問內(nèi)層函數(shù)的變量。functiona(){letaVara函數(shù)中的變量;functionb(){console.log(在b函數(shù)中可以訪問a函數(shù)中的變量${aVar});letbVarb函數(shù)中的變量;console.log(在b函數(shù)中當(dāng)然可以訪問b函數(shù)中的變量${bVar});}// 調(diào)用函數(shù)bb();//console.log(在a函數(shù)中無法訪問b函數(shù)中的變量這樣會報錯${bVar});}// 調(diào)用函數(shù)aa();1.3 純函數(shù) vs 非純函數(shù)純函數(shù)以下是純函數(shù)的特點(diǎn)只要傳入的參數(shù)輸入相同函數(shù)就一定會返回相同的結(jié)果輸出不會有任何意外情況。值是穩(wěn)定的。只依賴輸入?yún)?shù)不依賴外部變量全局變量、外層作用域變量等。不會修改外部狀態(tài)不改變?nèi)肿兞?、不修改傳入的參?shù)、不執(zhí)行 I/O 操作等。純函數(shù) vs 非純函數(shù)// 純函數(shù)functionsquare(x){returnx*x;}// 非純函數(shù)lettaxRate0.1;functioncalculateTax(amount){returnamount*taxRate;// 依賴外部狀態(tài)}2. 數(shù)組的常用函數(shù)式方法2.1 map - 數(shù)據(jù)轉(zhuǎn)換map 方法遍歷數(shù)組的每個元素按照你提供的規(guī)則轉(zhuǎn)換它們并返回一個新數(shù)組。map 的參數(shù)是一個函數(shù)通過這個函數(shù)來描述轉(zhuǎn)換規(guī)則constnumbers[1,2,3];constsquaresnumbers.map(xx*x);// [1, 4, 9]2.2 filter - 數(shù)據(jù)篩選filter 的參數(shù)是一個函數(shù)通過這個函數(shù)來描述過濾規(guī)則constnumbers[1,2,3,4,5];constevensnumbers.filter(xx%20);// [2, 4]2.3 reduce - 數(shù)據(jù)聚合constnumbers[1,2,3,4];constsumnumbers.reduce((acc,x)accx,0);// 103. 閉包了解3.1 閉包的形成通俗理解閉包就像是一個背包當(dāng)一個函數(shù)出生時它會隨身攜帶一個背包里面裝著它出生時周圍的所有變量。核心特點(diǎn)函數(shù)可以記住并訪問它被創(chuàng)建時的作用域即使這個函數(shù)在其他地方執(zhí)行生活比喻就像你搬家到另一個城市但仍然記得老家的方言和習(xí)慣。代碼示例functioncreateGreeter(greeting){// 內(nèi)部函數(shù)使用了外部函數(shù)的變量這樣的內(nèi)部函數(shù)可以看做是一個閉包returnfunction(name){console.log(${greeting},${name}!);};}// 內(nèi)部函數(shù)出生sayHello、sayHi就是出生的新函數(shù)的名字。constsayHellocreateGreeter(Hello);// createGreeter函數(shù)在上面已經(jīng)執(zhí)行結(jié)束了// 但調(diào)用內(nèi)部函數(shù)的時候內(nèi)部函數(shù)還可以繼續(xù)使用當(dāng)時出生時的外部函數(shù)的變量。sayHello(Alice);// Hello, Alice!在這段代碼中閉包的體現(xiàn)是從 return function(name) {…} 開始直到這個內(nèi)部函數(shù)結(jié)束即 } 之前。具體來說閉包體現(xiàn)在內(nèi)部函數(shù) function(name) {…} 訪問了外部函數(shù) createGreeter 的參數(shù) greeting即使外部函數(shù) createGreeter 執(zhí)行完畢內(nèi)部函數(shù)仍然保持著對 greeting 的引用完整定義閉包的語法returnfunction(name){console.log(${greeting},${name}!);};閉包的關(guān)鍵特征是● 函數(shù)嵌套閉包被嵌套在一個函數(shù)中或一個對象中● 內(nèi)部函數(shù)引用了外部函數(shù)的變量● 內(nèi)部函數(shù)被返回可以在外部函數(shù)執(zhí)行完畢后繼續(xù)使用所以當(dāng)你這樣使用時constsayHellocreateGreeter(Hello);sayHello(Alice);// 仍然能訪問到 Hello 這個 greetingsayHello 函數(shù)就是一個閉包它記住了創(chuàng)建時的 greeting 值。閉包 函數(shù) 該函數(shù)訪問的外部變量3.2 閉包的經(jīng)典應(yīng)用3.2.1 數(shù)據(jù)私有化模塊模式問題如何創(chuàng)建真正的私有變量解決方案使用閉包functionperson(){letusernamejack;return{// 閉包getUsername(){returnusername;},// 閉包setUsername(newName){usernamenewName;}};}constpperson();// 這個代碼看起來p對象中是有username屬性的console.log(p.getUsername());p.setUsername(lucy);console.log(p.getUsername());// 像私有屬性無法訪問console.log(p.username);p對象沒有真正的屬性username而是通過閉包實現(xiàn)看著是私有屬性的樣子3.2.2 函數(shù)工廠應(yīng)用場景創(chuàng)建多個功能類似但配置不同的函數(shù)代碼示例functioncreateMultiplier(factor){// 閉包returnfunction(number){returnnumber*factor;};}// 創(chuàng)建一個完成兩倍的函數(shù)。// 內(nèi)部函數(shù)double出生時記住了外部函數(shù)的factor2constdoublecreateMultiplier(2);// 創(chuàng)建一個完成三倍的函數(shù)。consttriplecreateMultiplier(3);console.log(double(5));// 10console.log(triple(5));// 154. 函數(shù)式編程之高階函數(shù)定義什么叫做高階函數(shù)如果一個函數(shù)的參數(shù)是函數(shù)或者一個函數(shù)的返回值是一個函數(shù)我們把這個函數(shù)稱為高階函數(shù)。代碼示例// 該函數(shù)的第一個參數(shù)是一個函數(shù)因此 doTwice 被稱為高階函數(shù)。functiondoTwice(func,value){returnfunc(func(value));}functionadd5(x){returnx5;}console.log(doTwice(add5,10));// 20 (10515, 15520)5. 函數(shù)式編程之函數(shù)組合概念函數(shù)組合就是把多個函數(shù)合并成一個新函數(shù)這個新函數(shù)會從右到左或從左到右依次執(zhí)行這些函數(shù)。// compose函數(shù)實現(xiàn)了函數(shù)的組合。functioncompose(...fns){// 閉包functioncompute(x){// reduce()方法是從左到右// reduceRight()方法是從右到左// 遍歷數(shù)組fns然后取出數(shù)組中每一個函數(shù)f(reduceRight是從數(shù)組的右側(cè)開始取函數(shù))// 第一輪x是初始值初始值會自動賦值給累加器的第一個參數(shù)。這是累加器的固定規(guī)則。returnfns.reduceRight((v,f)f(v),x);}returncompute;}constadd1xx1;constdoublexx*2;constsquarexx*x;// transform是計算函數(shù)compute// 含義先求和將求和的結(jié)果擴(kuò)大2倍將擴(kuò)大2倍后的數(shù)據(jù)再求平方。從右向左函數(shù)組合。consttransformcompose(square,double,add1);console.log(transform(2));// ((2 1) * 2) * ((2 1) * 2) 366. 函數(shù)式編程之柯里化定義將多參數(shù)函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)的技術(shù)英文叫 Currying名字來源于數(shù)學(xué)家 Haskell Curry// 普通函數(shù)functionadd(a,b,c){returnabc;}// 柯里化版本functioncurryAdd(a){// 閉包returnfunction(b){// 閉包returnfunction(c){returnabc;};};}// 使用箭頭函數(shù)簡化constcurryAddabcabc;console.log(curryAdd(1)(2)(3));// 6// 實用示例創(chuàng)建特定功能的函數(shù)constadd5curryAdd(2)(3);// 固定前兩個參數(shù)sconsole.log(add5(10));// 15 (2310)柯里化最大的作用就是可以固定部分參數(shù)的值。7. 實戰(zhàn)練習(xí)7.1 練習(xí)1實現(xiàn)一個簡單的計數(shù)器工廠functioncreateCounter(){// 在這里實現(xiàn)letcount1;return()count;}constcounter1createCounter();console.log(counter1());// 1console.log(counter1());// 2constcounter2createCounter();console.log(counter2());// 17.2 練習(xí)2使用函數(shù)式方法處理數(shù)據(jù)constusers[{name:Alice,age:25},{name:Bob,age:30},{name:Charlie,age:20}];// 1. 獲取所有用戶的名字提示用數(shù)組的map方法constnamesusers.map(useruser.name)// 2. 獲取年齡大于25的用戶提示用數(shù)組的filter方法constuserusers.filter(useruser.age25);// 3. 計算所有用戶的平均年齡提示用數(shù)組的reduce方法constavgAge(users.map(useruser.age).reduce((a,b)ab))/users.length;二十四、原型對象在 JavaScript 中對象的 prototype 屬性是理解原型繼承Prototypal Inheritance的核心概念。以下是詳細(xì)解釋1. prototype 是什么● 函數(shù)的 prototype 屬性只有函數(shù)對象包括構(gòu)造函數(shù)擁有顯式的 prototype 屬性。返回值被稱為原型對象用于存儲該函數(shù)作為構(gòu)造函數(shù)時所有實例共享的屬性和方法。functionPerson(name){this.namename;}// 函數(shù)的 prototype 屬性Person.prototype.sayHellofunction(){console.log(Hello,${this.name}!);};● 對象的 [[Prototype]] 鏈普通對象非函數(shù)沒有 prototype 屬性但有一個內(nèi)部指針 [[Prototype]]可通過proto或 Object.getPrototypeOf() 訪問。它指向該對象的原型對象即其構(gòu)造函數(shù)的 prototype。constjohnnewPerson(John);console.log(john.__proto__Person.prototype);// trueconsole.log(Person.prototypeObject.getPrototypeOf(john));// true原型對象的作用原型對象用于共享屬性和方法。所有通過該構(gòu)造函數(shù)創(chuàng)建的實例都會繼承原型對象上的成員。2. prototype 的作用2.1 實現(xiàn)繼承當(dāng)訪問一個對象的屬性/方法時如果對象自身沒有該成員JavaScript 會通過proto向上查找其原型對象若仍未找到則繼續(xù)查找原型對象的原型對象直到頂層Object.prototype這種鏈?zhǔn)浇Y(jié)構(gòu)稱為原型鏈。示例console.log(john.toString());// 調(diào)用的是 Object.prototype.toString()查找過程檢查 john 自身是否有 toString() → 無檢查 john.proto即 Person.prototype→ 無檢查 Person.prototype.proto即 Object.prototype→ 找到調(diào)用JS 中默認(rèn)情況下繼承是幾層● 自定義構(gòu)造函數(shù)創(chuàng)建的對象三層實例 → 構(gòu)造函數(shù).prototype → Object.prototype → null● 字面量對象 {}兩層實例 → Object.prototype → null實現(xiàn)繼承關(guān)系// 父類functionShape(color){this.colorcolor;}Shape.prototype.getColorfunction(){returnthis.color;};// 子類functionCircle(color,radius){// 調(diào)用父類構(gòu)造方法Shape.call(this,color);// 子類特有屬性this.radiusradius;}// 繼承原型// 這是必須的如果不寫這一行就不算完整的原型繼承。// 如果不寫Circle的實例將無法繼承Shape原型上的方法如getColor。Circle.prototype會直接指向默認(rèn)的Object.prototype而不是Shape.prototype。原型鏈就斷了。// Object.create(Shape.prototype); 代碼作用創(chuàng)建一個新對象這個新對象的原型對象父對象指向Shape.prototypeCircle.prototypeObject.create(Shape.prototype);// 這一行不是絕對必須的但強(qiáng)烈建議寫上如果不寫Circle.prototype.constructor會指向Shape。Circle.prototype.constructorCircle;Circle.prototype.getAreafunction(){returnMath.PI*this.radius*this.radius;};constcirclenewCircle(red,5);console.log(circle.getColor());// 輸出: redconsole.log(circle.getArea());// 輸出: 78.539816339744832.2 共享屬性和方法原型鏈上的所有屬性和方法都是所有實例共享的目的是節(jié)省內(nèi)存constalicenewPerson(Alice);console.log(john.sayHelloalice.sayHello);// true2.3 動態(tài)擴(kuò)展功能即使實例已創(chuàng)建修改原型仍會影響所有實例Person.prototype.goodbyefunction(){console.log(Goodbye,${this.name}!);};john.goodbye();// 動態(tài)新增的方法對所有實例生效當(dāng)然內(nèi)置的對象也可以動態(tài)擴(kuò)展功能Array.prototype.lastfunction(){returnthis[this.length-1];}constarr[1,2,3];console.log(arr.last());// 輸出: 33. 原型鏈的終點(diǎn)原型鏈的頂端是 Object.prototype其 [[Prototype]] 為 nullconsole.log(Object.getPrototypeOf(Object.prototype));// null區(qū)分自身屬性和原型屬性O(shè)bject 的 hasOwn 方法用來區(qū)分自身屬性和原型屬性functionCar(model){this.modelmodel;}Car.prototype.brandToyota;constmyCarnewCar(Camry);console.log(Object.hasOwn(myCar,model));// true自身console.log(Object.hasOwn(myCar,brand));// false原型