導(dǎo)航
[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運算符
[深入07] 瀏覽器緩存機(jī)制(http緩存機(jī)制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)
[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI
[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應(yīng)式 和 初始化渲染
前置知識
函數(shù)的參數(shù)
- length:函數(shù)的legth屬性棠隐,返回函數(shù)預(yù)期的參數(shù)個數(shù)(形參)
- arguments:arguments對象赊瞬,包含了程序運行時的所有參數(shù)(實參)
類似數(shù)組的對象轉(zhuǎn)換成數(shù)組
- [].slice.call(類似數(shù)組的對象)
- [].slice.apply(類似數(shù)組的對象)
- Array.prototype.slice.call(類似數(shù)組的對象, x)
// x是綁定this后傳入slice函數(shù)的參數(shù)
- Array.from()
偏函數(shù)和柯里化的概念
- 柯里化 curry:
- 將接收多個參數(shù)的函數(shù)诈胜,轉(zhuǎn)換成接收一個單一參數(shù)的函數(shù),并返回接收余下參數(shù)顾瞻,并返回最終結(jié)果的新函數(shù)
- <font color=red>即當(dāng)參數(shù)小于預(yù)期參數(shù)時,返回一個可以接收剩余參數(shù)的函數(shù)德绿,參數(shù)大于等于預(yù)期參數(shù)時荷荤,返回最終結(jié)果</font>
- 偏函數(shù) partial application:
- 是固定一個或多個參數(shù)退渗,產(chǎn)生另一個較小元的函數(shù) n元函數(shù) => 轉(zhuǎn)換成n-x元函數(shù)
柯里化 curry
- 柯里化函數(shù),他接收函數(shù)A作為參數(shù)蕴纳,運行后能夠返回一個新的函數(shù)氓辣,并且這個新的函數(shù)能夠處理函數(shù)A的剩余參數(shù)
1. 柯里化階段一
- 需求:
將add(1,2,3)轉(zhuǎn)化成curryAdd(1)(2)(3)
- <font color=red>缺點:只能處理3個參數(shù)的情況,不能處理任意多個參數(shù)的情況袱蚓,毫無擴(kuò)展性</font>
需求: 將add(1,2,3)轉(zhuǎn)化成curryAdd(1)(2)(3)
缺點:只能處理3個參數(shù)的情況钞啸,不能處理任意多個參數(shù)的情況,毫無擴(kuò)展性
function curryAdd(a) {
return function(b) {
return function(c) {
return a+b+c
}
}
}
const res = curryAdd(1)(2)(3)
console.log(res, 'res1')
2. 柯里化階段二
- 需求:
處理任意多個參數(shù)相加
- 缺點:
-
1. 處理相加邏輯的代碼喇潘,只是在沒有參數(shù)時才會執(zhí)行体斩,其他部分都在處理怎么收集所有參數(shù),會多一次沒有參數(shù)的調(diào)用
- 更合理的方式是通過判斷函數(shù)可以接收參數(shù)的總和颖低,來判斷是否參數(shù)收集完畢
2. 相加邏輯可以單獨抽離
-
function curryAdd() {
let params_arr = [] // 用于收集所有實參
function closure() {
const args = Array.prototype.slice.call(arguments) // 每次調(diào)用閉包函數(shù)傳入的實參絮吵,可以是多個
if (args.length) {
params_arr = params_arr.concat(args)
// concat返回一個拼接過后的新數(shù)組,不改變原數(shù)組
return closure
// 如果還有參數(shù)忱屑,則繼續(xù)返回閉包函數(shù)蹬敲,則繼續(xù)繼續(xù)傳參調(diào)用
}
return params_arr.reduce((total, current) => total + current)
// 如果沒有再傳入?yún)?shù),則相加所有傳入的參數(shù)莺戒,缺點是要多一次沒有參數(shù)的調(diào)用
}
return closure // 第一次調(diào)用curryAdd返回的閉包
}
const fn = curryAdd()
const res = fn(1,2)(3)(4)()
console.log(res, 'res'); // 10
3. 柯里化階段三
function add(a,b,c,d,e) {
return Array.prototype.slice.call(arguments).reduce((total, current) => total + current)
// 注意:這里拿到的是實參的實際個數(shù)伴嗡,即實參可能大于形參,當(dāng)實參 (大于等于) 形參時从铲,執(zhí)行相加
}
function curryAdd(fn) {
let paramsArr = []
const paramsMaxLength = fn.length // function.length返回函數(shù)的形參個數(shù)瘪校,預(yù)期的參數(shù)個數(shù)為最大參數(shù)個數(shù),即相加執(zhí)行條件
function closure() {
const args = Array.prototype.slice.call(arguments)
paramsArr = paramsArr.concat(args)
if (paramsArr.length < paramsMaxLength) {
return closure
}
// 當(dāng)參數(shù)個數(shù) 大于等于 最大的期望個數(shù)名段,即形參的個數(shù)時阱扬,執(zhí)行相加函數(shù)
return fn.apply(this, paramsArr)
}
return closure
}
const fn = curryAdd(add)
const res = fn(1,2,3)(4)(5,6)
console.log(res, 'res');
4.柯里化變通版
- 上面版本的缺點:上面的版本需要知道add的參數(shù)length
function add() {
return Array.from(arguments).reduce((total, current) => total + current)
}
function currentAdd(fn) {
let paramsArr = []
function closure() { // 該閉包函數(shù)只負(fù)責(zé)收集參數(shù),處理相加可以在閉包上掛載新的方法getSum
const args = Array.from(arguments)
paramsArr = paramsArr.concat(args)
return closure
}
closure.getSum = function() {
return fn.apply(this, paramsArr) // getSum負(fù)責(zé)計算伸辟,利用了閉包中的變量paramsArr
}
return closure
}
const fn = currentAdd(add)
const resAdd = fn(1)(2,3)
const res = resAdd.getSum(); // 該方法的缺點就是需要單獨再調(diào)用getSum函數(shù)
console.log(res, 'res')
偏函數(shù) partial
- 將一個或者多個參數(shù)麻惶,固定到一個函數(shù)上,并產(chǎn)生返回一個更小元的函數(shù)
function add (a, b) {
return a + b
}
function partial (fn) {...}
const addPartial = partial(add, 1) // ------------------ 實現(xiàn)固定一部分參數(shù)1
const res = addPartial(2) // 3 -------------------------- 只傳一部分參數(shù) 2
偏函數(shù)實現(xiàn)方式1
- <font color=red>通過bind方法實現(xiàn)</font>
bind方法綁定this指向信夫,同時也可以傳入fn的部分和全部參數(shù)窃蹋,并返回一個新函數(shù),新函數(shù)可以傳入?yún)?shù)作為fn的剩余參數(shù)
function add(a,b,c,d) {
return a+b+c+d
}
function partail() {
const params = Array.prototype.slice.call(arguments)
const fn = params.shift() // 刪除數(shù)組第一個元素忙迁,返回該元素脐彩,改變原數(shù)組
return fn.bind(this, ...params)
// 該params執(zhí)行shift后已經(jīng)改變\
// params數(shù)組展開后的所有成員,都會作為fn的參數(shù)
// 并且bind返回的新函數(shù)還可以傳參
}
const fn = partail(add, 1, 2) // 固定了 1姊扔,2兩個參數(shù)
const res = fn(3,4) // 除了固定的參數(shù)惠奸,剩下的參數(shù)在這里傳入
console.log(res, 'res')
偏函數(shù)實現(xiàn)方式2
function add(a,b,c,d) {
return Array.from(arguments).reduce((total, current) => total + current)
// 相加實參
// 因為實參可能大于形參
}
function partialAdd(fn) {
let paramsFixed = Array.from(arguments).slice(1)
// 除去fn的剩余參數(shù)
// 注意:該方法和curry很相似,current第一調(diào)用是不需要傳fn參數(shù)的恰梢,聲明的是空數(shù)組佛南,而在partial中需要傳固定的參數(shù)
const paramsMaxLength = fn.length // 形參個數(shù)
function closure() {
const args = Array.from(arguments)
paramsFixed = paramsFixed.concat(args)
if (paramsFixed.length < paramsMaxLength) {
return closure
}
return fn.apply(this, paramsFixed) // 大于等于時
}
return closure
}
const fn = partialAdd(add, 2)
const res = fn(3)(4)(5)
console.log(res, 'res') // 14
函數(shù)記憶
- 函數(shù)記憶:
指將上次的(計算結(jié)果)緩存起來梗掰,當(dāng)下次調(diào)用時,如果遇到相同的(參數(shù))嗅回,就直接返回(緩存中的數(shù)據(jù))
- 實現(xiàn)原理:將參數(shù)和對應(yīng)的結(jié)果保存在對象中及穗,再次調(diào)用時,判斷對象key是否存在绵载,存在返回緩存的值
- 注意:函數(shù)是需要返回值的
function memorize(fn) {
const cache = {}
return function() {
const key = Array.prototype.join.call(arguments, ',')
if (key in cache) {
return cache[key]
}
return cache[key] = fn.apply(this, arguments)
}
}
我的簡書:http://www.reibang.com/p/eb583d76452f
尾調(diào)用
尾調(diào)用: 函數(shù)執(zhí)行的最后一個步驟埂陆,是返回另一個函數(shù)的調(diào)用,叫尾調(diào)用
優(yōu)點:
1. 尾調(diào)用娃豹,當(dāng)里層函數(shù)被調(diào)用時焚虱,外層函數(shù)已經(jīng)執(zhí)行完,出棧了懂版,不會造成內(nèi)存泄漏
2. 在遞歸中鹃栽,尾調(diào)用使得棧中只有一個函數(shù)在運行,不會造成性能問題
f(x) {
return g(x)
}
// 尾調(diào)用躯畴,因為返回g(x)調(diào)用的時候民鼓,f(x)已經(jīng)執(zhí)行完
f(x) {
return g(x) + 1
}
// 非尾調(diào)用,因為返回 g(x) 調(diào)用時蓬抄,f(x)并未執(zhí)行完丰嘉,當(dāng)g(x)執(zhí)行完后,還有執(zhí)行 g(x)+1倡鲸,f(x)才執(zhí)行完
// 函數(shù)只有執(zhí)行完后才會出棧(執(zhí)行上下文調(diào)用棧)
const a = x => x ? f() : g();
// f()和g()都是尾調(diào)用
const a = () => f() || g()
// f()非尾調(diào)用供嚎,還要接著判斷
const a = () => f() && g();
// f()非尾調(diào)用
尾遞歸
遞歸 -- 尾遞歸和尾調(diào)用
1. 構(gòu)成遞歸的條件
- 邊界條件
- 遞歸前進(jìn)段
- 遞歸返回段
- 當(dāng)邊界條件不滿足時黄娘,遞歸前進(jìn)
- 當(dāng)邊界條件滿足時峭状,遞歸返回
2.
Recursive:遞歸
factorial:階乘
3. 尾調(diào)用和非尾調(diào)用
- 尾調(diào)用和非尾調(diào)用的區(qū)別是 執(zhí)行上下文棧不一樣
- 為調(diào)用:調(diào)用在函數(shù)結(jié)尾處
- 尾調(diào)用的執(zhí)行上下文棧,外層函數(shù)執(zhí)行完就出棧逼争,不會一層一層嵌套优床,不造成內(nèi)存溢出
- 尾調(diào)用自身就叫尾遞歸
// 尾調(diào)用
// 因為調(diào)用g(x)時,f(x)已經(jīng)執(zhí)行完了誓焦,就會出棧胆敞,不會壓棧,不會造成內(nèi)存溢出
function f(x){
return g(x);
}
// 非尾調(diào)用
// 因為調(diào)用g(x)時杂伟,f(x)并未執(zhí)行完移层,g(x)+1需要g(x)函數(shù)執(zhí)行完,才會相加赫粥,返回后f(x)才會執(zhí)行完
function f(x){
return g(x) + 1;
}
------------------------------------------------------------------------------------
+++(例1)階乘
// recursive遞歸
function factorial (n) {
if (n < 2) return n
return n * factorial(n-1)
}
const res = factorial(3)
// 1. 3 => 3 * factorial(2) => 3 * 2 * factorial(1) => 3 * 2 * 1
(分析)
1. 每次返回一個遞歸的函數(shù)观话,都會創(chuàng)建一個閉包
2. 所以維護(hù)這么多執(zhí)行上下文棧,開銷大越平,用以造成內(nèi)存泄漏
3. 優(yōu)化方法:尾調(diào)用
+++(例1升級)階乘優(yōu)化
function factorial(n, res) {
if (n === 1) {
return res
}
return factorial(n-1, n * res)
}
(分析)
第一次:factorial(3, 4* 1)
第二次:factorial(2, 3* 4)
第三次:factorial(1, 2* 12)
第四次:24
+++(例1再升級)階乘優(yōu)化频蛔,多傳了一個參數(shù)灵迫,可以用函數(shù)柯里化或者偏函數(shù)來實現(xiàn)
function factorial(res, n) {
if (n === 1) return res;
return factorial(n * res, n-1)
}
function curring (fn) {
let par_arr = Array.prototype.slice.call(arguments, 1)
const closure = function () {
par_arr = par_arr.concat(Array.prototype.slice.call(arguments))
console.log(par_arr, 'par_arr')
if (par_arr.length < fn.length) {
return closure
}
return fn.apply(null, par_arr)
}
return closure
}
const curringFactorial = curring(factorial, 1)
const res = curringFactorial(4)
console.log(res)