編者薦語: 本文旨在幫助你們掌握JavaScript中的幾大重要高階編程技巧,如:高階函數
、高級單例模式
、惰性函數思想
、柯理化函數思想
、compose扁平化函數思想
。javascript
這一篇,咱們主要來說解下,在JavaScript中,高階編程思想都有哪些,它們在項目中又有哪些實際性的用途呢?前端
用單獨的實例來管理當前事物的相關特徵,泛指屬性
和方法
,相似於實現分組的特色,把一個實例的全部特徵描述綁定在一個分組裏。vue
來看一下簡單的單例設計模式:java
let module1 = (function () {
function tools() {}
function share() {}
return {
name: 'house',
tools
};
})();
module1.tools();
複製代碼
這裏咱們能夠把module1模塊定義的方法暴露出來,給外部模塊用。react
還有一種基於閉包實現的單例模式稱爲:高級單例設計模式,在vue/react出來以前,是團隊協做最經常使用的模塊化思想,經常使用來以此模塊劃分
。編程
閉包形式的單例模式以下:設計模式
let A = (function () {
function fn() {
B.getXxx();
}
function query() {}
return {
query
}
})();
let B = (function () {
function fn() {}
function getXxx() {}
A.query();
return {
getXxx
}
})();
複製代碼
在上面這個例子中,咱們能夠分別定義A、B
兩個模塊,先在本身模塊內聲明函數,再暴露出去,給對方的模塊使用,這即是最先的模塊化思想,也是高級單例設計模式
。數組
咱們先來看這樣一個例子,封裝一個獲取元素樣式的函數。瀏覽器
獲取樣式,咱們一般想到window.getComputedStyle
和element.currentStyle
這兩個API,但他們的執行也是有條件的。markdown
好比:前者只有Google
瀏覽器兼容,後者IE
瀏覽器兼容。
function getCss(element, attr) {
if ('getComputedStyle' in window) {
return window.getComputedStyle(element)[attr];
}
return element.currentStyle[attr];
}
getCss(document.body, 'margin');
getCss(document.body, 'padding');
getCss(document.body, 'width');
複製代碼
從這個例子中,咱們能夠看出來,若是須要3次獲取元素的樣式,明顯每一次進入函數都須要判斷該方法兼容與否,這就形成了沒必要要的浪費。最好的解決方案——惰性函數
思想。
通俗的來講,惰性函數
初始化第一次渲染的時候執行,若是第二遍執行仍是同樣的效果,咱們須要用閉包的思想解決這一問題。
function getCss(element, attr) {
if ('getComputedStyle' in window) {
getCss = function (element, attr) {
return window.getComputedStyle(element)[attr];
};
} else {
getCss = function (element, attr) {
return element.currentStyle[attr];
};
}
// 爲了第一次也能拿到值
return getCss(element, attr);
}
getCss(document.body, 'margin');
getCss(document.body, 'padding');
getCss(document.body, 'width');
複製代碼
在這個函數中,當咱們第一次執行getCss函數的時候,就已經能夠肯定getComputedStyle
兼容與否了,因此在第二次就不必再判斷
了,根據第一次判斷完返回的結果,直接肯定第二次,第三次的函數執行到底用哪一個API
,這樣以來每次函數執行,都將少了一層判斷
,必定程度上提升了js的運行速度。
那麼咱們來看,這個惰性
體如今哪裏呢?
過程:其實,是第一次getCss
函數執行完建立了全局做用域
下的私有執行上下文
,而在其裏面從新生成了getCss
函數,將其引用地址從新賦值給全局函數getCss
,致使全局
下的getCss
不能被釋放,造成了一個閉包
。在之後每次執行時,都執行裏面的小函數getCss
。
柯理化函數含義是給函數分步傳遞參數,每次傳遞部分參數,並返回一個更具體的函數接收剩下的參數,這中間可嵌套多層這樣的接收部分參數的函數,直至返回最後結果。
// 原函數
function add(a, b, c) {
return a + b + c;
}
// 柯里化函數
function addCurrying(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
// 調用原函數
add(1, 2, 3); // 6
// 調用柯里化函數
addCurrying(1)(2)(3) // 6
複製代碼
被柯里化的函數 addCurrying
每次的返回值都爲一個函數
,並使用下一個參數做爲形參
,直到三個參數都被傳入後,返回的最後一個函數內部執行求和操做
,實際上是充分的利用了閉包的特性
來實現的。
上面的柯里化函數沒涉及到高階函數
,也不具有通用性,沒法轉換形參個數任意或未知的函數。
咱們來封裝一個通用的柯里化轉換函數,能夠將任意函數轉換成柯里化。
// add的參數不固定,看有幾個數字累計相加
function add (a,b,c,d) {
return a+b+c+d
}
function currying (fn, ...args) {
// fn.length 回調函數的參數的總和
// args.length currying函數 後面的參數總和
// 如:add (a,b,c,d) currying(add,1,2,3,4)
if (fn.length === args.length) {
return fn(...args)
} else {
// 繼續分步傳遞參數 newArgs 新一次傳遞的參數
return function anonymous(...newArgs) {
// 將先傳遞的參數和後傳遞的參數 結合在一塊兒
let allArgs = [...args, ...newArgs]
return currying(fn, ...allArgs)
}
}
}
let fn1 = currying(add, 1, 2) // 3
let fn2 = fn1(3) // 6
let fn3 = fn2(4) // 10
複製代碼
利用閉包保存機制
,把一些信息預先存儲下來(預處理的思想)
咱們用一道求和的題
來描述:
function currying(...outerArgs) {
return function anonymous (...innerArgs) {
let args = outerArgs.concat(innerArgs)
return args.reduce((previousValue, currentValue) => previousValue + currentValue)
}
}
let fn1 = currying(1, 2)
let fn2 = fn1(3)
let fn3 = fn2(4)
複製代碼
補充 什麼是預處理的思想:
在第一次
fn1
函數執行的時候,會建立一個函數執行上下文,將 1, 2 存儲在該上下文中的局部變量裏,在之後執行fn2 fn3
的時候,須要先取得fn1
的返回結果,使用存儲的 1, 2 參數,這種機制便造成了閉包
不理解reduce
的小夥伴看過來:
狀況1:reduce沒有第二個參數時
let Array = [10, 20, 30, 40, 50];
Array.reduce(previousValue, currentValue, currentIndex, arr){
return previousValue + currentValue;
}
複製代碼
10
),currentValue爲第二項(20
)回調函數的返回值
(30
),currentValue爲第三項(30
)回調函數的返回值
,currentValue爲第X+1
項currentValue
的索引值狀況2:reduce有第二個參數時
let Array = [10, 20, 30, 40, 50];
Array.reduce((previousValue, currentValue, currentIndex, arr){
return previousValue + currentValue;
}, 0)
複製代碼
0
),currentValue爲第一項(10
)回調函數的返回值
(10
),currentValue爲第二項(20
)回調函數的返回值
,currentValue爲第X
項currentValue
的索引值compose組合函數:把多層函數嵌套調用扁平化
compose函數常基於reduce
柯理化函數
思想解決 函數調用扁平化
的問題:
在項目中,咱們常常遇到這樣一個問題:
const fn1 = x => x + 10;
const fn2 = x => x - 10;
const fn3 = x => x * 10;
const fn4 = x => x / 10;
console.log(fn3(fn1(fn2(fn1(4)))))
複製代碼
上面這個例子中,明顯看出層級嵌套較深,這時候就須要調用函數扁平化
的思想
函數調用扁平化: 若是是多層級嵌套調用的函數,把一個函數調用完,看成另外一個函數的實參傳遞到下一個函數中
解決方案:從左到右依次執行
/** * @param funcs 存儲按照順序執行的函數(數組) => [fn1, fn3, fn2, fn4] * @param args 存儲第一個函數執行須要傳遞的實參信息(數組) => [5] */
function compose(...funcs) {
return function anonymous(...args) {
if(funcs.length === 0) return args;
if(funcs.length === 1) return funcs[0](...args);
// funcs 裏有 多個函數時
return funcs.reduce((n, func) => {
// 第一次執行:
// n:第一個函數執行的實參 func:第一個函數
// 第二次執行:
// n的值:上一次func執行後的返回值,做爲實參傳遞給下一個函數執行 func:第二個函數
return Array.isArray(n) ? func(...n) : func(n);
}, args)
}
}
let res = compose(fn1, fn3, fn2, fn4)(5)
// 執行過程:
console.log(compose()(5)); //=>5
console.log(compose(fn1)(5)); //=>5+10 = 15
console.log(compose(fn1, fn3)(5)); //=>fn1(5)=15 fn3(15)=150
console.log(compose(fn1, fn3, fn2)(5)); //=>fn1(5)=15 fn3(15)=150 fn2(150)=140
console.log(compose(fn1, fn3, fn2, fn4)(5)); //=>fn1(5)=15 fn3(15)=150 fn2(150)=140 fn4(140)=14
複製代碼
compose函數扁平化的執行過程:
compose()(5),如compose()函數中無函數參數傳遞進來,則直接返回後面的參數 5
compose(fn1, fn3, fn2, fn4)(5),先執行fn1(5),再將fn1(5)的結果傳遞到fn3中,如fn3(fn1(5)) => fn3(15),以此類推fn2(fn3(fn1(5))) => fn4(150),後來每一次函數調用的參數都是前一個函數返回的結果
,天然咱們就想到了Array.prototype.reduce
方法
設置reduce
的第二個參數,是爲了統一n
爲每一次函數的返回結果(第一次n
爲5,func
爲fn1),恰好又須要讓fn1(5)
函數執行,可見第二個參數的必要性,是爲了統一每次的回調函數返回結果都是number
回調函數:把一個函數做爲值傳遞給另一個函數,在另一個函數中把這個函數執行(這是實現函數式編程重要的知識)
函數式編程:注重結果
,不在意過程,過程交給別人處理,體現函數封裝性思想
(提倡)
命令式編程:注重過程
,須要本身去實現過程
函數式編程:把邏輯如何實現
封裝成爲API方法,咱們之後只要調取API方法,便可獲取想要的結果便可
let arr = [10, 20, 30, 40, 50];
let res = arr.reduce((n, item) => {
return n + item;
});
複製代碼
命令式編程:用代碼實現主要的邏輯,注重過程
let res = 0;
for (let i = 0; i < arr.length; i++) {
res += arr[i];
}
複製代碼
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
也能夠來個人我的博客:
前端時光屋:www.javascriptlab.top/