js面向?qū)ο缶幊蘋OP及函數(shù)式編程FP區(qū)別
寫在前面
瀏覽下文我覺得還是要有些基礎(chǔ)的!下文涉及的知識(shí)點(diǎn)太多,基本上每一個(gè)拿出來都能寫幾篇文章,我在寫文章的過程中只是做了簡單的實(shí)現(xiàn),我只是提供了一個(gè)思路,更多的細(xì)節(jié)還是需要自己去鉆研的,文章內(nèi)容也不少,辛苦,如果有其他的看法或者意見,歡迎指點(diǎn),最后紙上得來終覺淺,絕知此事要躬行
javscript 中函數(shù)和對(duì)象的關(guān)系
javscript 一切皆為對(duì)象,但基本類型之外,函數(shù)是對(duì)象,對(duì)象是由函數(shù)創(chuàng)建而來, 從而衍生出我對(duì)這兩種編程方式的探討。下面對(duì)類型判斷和原型做了一個(gè)簡單的表述,這里不是重點(diǎn),不做具體的表述,感興趣的可以自己百度/谷歌。
// 類型判斷
// 基本類型
console.log(typeof 1) // | ==> number
console.log(typeof '2') // | ==> string
console.log(typeof undefined) // | ==> undfined
// null 類型判斷【特殊】
console.log(typeof null) // | ==> object
console.log(Object.prototype.toString.call(null)) // | ==> [object Null]
// 報(bào)錯(cuò)【null 不是一個(gè)對(duì)象】TypeError: Right-hand side of 'instanceof' is not an object
console.log(null instanceof null)
console.log(typeof Symbol()) // | ==> symbol 【ES6 新類型】
console.log(typeof false) // | ==> boolean
console.log(typeof BigInt(9007199254740991n)) // | ==> bigint 【新類型】
// 引用類型 - 對(duì)象
console.log(typeof (() => {})) // | ==> function
console.log((() => {}) instanceof Object) // true
console.log(typeof []) // | ==> object
console.log(typeof {}) // | ==> object
console.log(typeof (/\./)) // | ==> object
console.log(typeof new Date()) // | ==> object
console.log(typeof new String()) // | ==> object
console.log(typeof new Number()) // | ==> object
// 原型鏈
// fn ====> function fn () {}
// Object ====> function Object () {}
// Function ====> function Funtion()
new fn() - __proto__ --|
↑ ↓
---→ fn ----------- fn.prototype -------- __proto__ -----→ Object.prototype -- __proto__--→ null
| ↑
|--------- __proto__ ------→ Function.prototype --- __proto__ -----|
↑
Function -------→ __proto__
|
Object
面向?qū)ο缶幊蹋∣OP)
在面向?qū)ο缶幊讨凶畛R姷谋憩F(xiàn)形式就是類,提供了面向?qū)ο蟮?3? 大特點(diǎn)和 5?? 大原則,這東西網(wǎng)上特別多,我只做簡單的羅列,下面我會(huì)對(duì)特點(diǎn)進(jìn)行實(shí)現(xiàn),我的理解: 原則是面向?qū)ο缶幊痰囊?guī)范,而特點(diǎn)是面向?qū)ο缶幊痰膶?shí)現(xiàn),前提是你已經(jīng)仔細(xì)理解過下面對(duì)核心概念。
三大特點(diǎn)
- 繼承
- 多態(tài)
- 封裝
五大原則
- 單一 【一個(gè)類應(yīng)該有且只有一個(gè)去改變它的理由,這意味著一個(gè)類應(yīng)該只有一項(xiàng)工作】
- 開放封閉 【對(duì)象或?qū)嶓w應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改封閉?!?/li>
- 里氏替換 【即對(duì)父類的調(diào)用同樣適用于子類】
- 依賴倒置 【高層次的模塊不應(yīng)該依賴于低層次的模塊】
- 接口隔離 【不應(yīng)強(qiáng)迫客戶端實(shí)現(xiàn)一個(gè)它用不上的接口,或是說客戶端不應(yīng)該被迫依賴它們不使用的方法】
繼承
繼承是面向?qū)ο笠粋€(gè)特點(diǎn),可以實(shí)現(xiàn)子類調(diào)用自己沒有的屬性方法【父類屬性方法】
/** ES6 **/
class Parent {}
class Child extends Parent { constructor () { super() } }
/** ES5 **/
function parent () { this.run () {} }
parent.prototype.eat = function () {}
function child () {}
// 原型式繼承
child.prototype = parent.prototype
child.prototype.constructor = child
// 原型鏈繼承
child.prototype = new parent()
child.prototype.constructor = child
// 構(gòu)造器繼承
function boyChild (..arg) { parent.apply(this, arg) }
// 組合繼承
function boyChild (..arg) { parent.apply(this, arg) }
boyChild.prototype = new parent()
child.prototype.constructor = child
// 寄生組合繼承
function child (..arg) { parent.apply(this, arg) }
// ${1}
(
function () {
function transmit () {};
transmit.prototype = parent.prototype
child.prototype = new prototype()
child.prototype.constructor = child
}
)()
// ${2}
child.prototype = Object.create(parent.prototype)
// ......
// 總結(jié)
// 繼承的方式方法多種多樣,不外乎,就是通過,某一種方式將不屬于自己的屬性方法可以調(diào)用,沿著原型的方式和拷貝賦值就可以總結(jié)出很多種不同的繼承方式,每種方式的優(yōu)缺點(diǎn),多是考慮,繼承的屬性方法的完整性和對(duì)實(shí)例化對(duì)象的影響,如實(shí)例上方法和原型鏈上方法是否都可以調(diào)用有或者引用傳遞改變同一原型鏈問題。
/** 上面為對(duì)實(shí)例對(duì)繼承,下面說一說對(duì)于接口對(duì)繼承 **/
// ES6 中并沒有提供接口這個(gè)概念,但是 Typescript 中對(duì)于接口又很好對(duì)支持,typescript 是 javascript 對(duì)超集,對(duì)面向?qū)ο筇峁┝朔浅:脤?duì)支持
// Typescript 【一時(shí)用一時(shí)爽,一直用一直爽】
// 很推薦用這個(gè),他能避免很多低級(jí)錯(cuò)誤,提供類型檢查,特別是寫過 java 轉(zhuǎn)前端的。
interface parent { run: () => void }
class child implements parent { run () {} }
// 轉(zhuǎn)碼后
var child = /** @class */ (function () {
function child() {
}
child.prototype.run = function () { };
return child;
}());
多態(tài)
多態(tài)是面向?qū)ο笠粋€(gè)特點(diǎn),可以實(shí)現(xiàn)子類有不同對(duì)表現(xiàn)形態(tài),可以實(shí)現(xiàn)同一種表現(xiàn)形式,可以有不同對(duì)狀態(tài)
/** ES6 **/
// ${1} 重寫
class Animal {
eat () { console.log('animal eat') }
}
class Pig extends Animal {
constructor () { super() }
eat () { console.log('pig eat grass') }
}
class Tiger extends Animal {
constructor () { super() }
eat () { console.log('tiger eat pig') }
}
// ${2} 重載,模擬實(shí)現(xiàn)
class Animal {
eat () {
if (typeof arg === '') {
console.log('操作 one')
} else if (typeof arg === '') {
console.log('操作 two')
} else {
console.log('操作 three')
}
}
}
/** ES5 【提供實(shí)現(xiàn)一種】**/
// 原理就是沿著原型鏈往上找,只要在父類前定義重寫這個(gè)方法即可
// ${1} 重寫
function animal () { this.eat = function () { console.log('Animal eat') } }
function pig () {
animal.call(this)
this.eat = function () { console.log('pig eat grass') }
}
function tiger () {
animal.call(this)
this.eat = function () { console.log('tiger eat pig') }
}
// ${2} 重載
function animal () {
eat () {
if (typeof arg === '') {
console.log('操作 one')
} else if (typeof arg === '') {
console.log('操作 two')
} else {
console.log('操作 three')
}
}
}
封裝
封裝是面向?qū)ο笠粋€(gè)特點(diǎn),將屬性和方法封裝這對(duì)象中,可以利用私有或者公有屬性,對(duì)外提供可以訪問的方法或?qū)傩?/p>
/** ES6 **/
// ES6 沒有提供真正的私有方法和屬性,有一個(gè)還在提案階段
// 在屬性和方法前面加 #
class Animal {
#height = ''
#eat () {}
}
// 模擬實(shí)現(xiàn) 【提供一種實(shí)現(xiàn)】
class Animal {
constructor () { this.height = '50' }
get height() { return undefined }
set height (value) { return undefined }
}
/** ES5 **/
const animal = (function (arg) {
let height = 50
function eat () {console.log(height)}
return { eat }
})([])
/** Typescript **/
class Animal {
public height: number
private name: string
protected color: string
constructor (height: number, name: string, color: string) {
this.height = height
this.name = name
this.color = color
}
private eat ():void { console.log(this.name) }
}
函數(shù)編程編程(FP)
函數(shù)式編程提倡函數(shù)是第一公民【指的是函數(shù)與其他數(shù)據(jù)類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數(shù),傳入另一個(gè)函數(shù),或者作為別的函數(shù)的返回值】,純粹的函數(shù)式編程,是純函數(shù)【如果傳入的參數(shù)相同,就會(huì)返回相同的結(jié)果,不依賴于外部的數(shù)據(jù)狀態(tài)【如下實(shí)例】】,函數(shù)編程特點(diǎn)
// 純函數(shù)
const add = (one, two) => { return one + two }
// 非純函數(shù)
let two = 1
const add = (one) => { return one + two }
- 閉包和高階函數(shù)
- 柯里化
- 偏函數(shù)
- 組合和管道
- 函子
閉包和高階函數(shù)
閉包理解 函數(shù)內(nèi)部還有其他函數(shù),可以使父函數(shù)數(shù)據(jù)狀態(tài)得以保存 高階函數(shù)理解 函數(shù)可以通過變量傳遞給其他函數(shù)
// 利用封包實(shí)現(xiàn)一個(gè)只能調(diào)用一次的 map 高階函數(shù)
const map = (fn) => {
let once = false
return (arr) => { return once? null: (once = true, arr.map(fn)) }
}
const fn = (item) => item + 10
const arrMap = map(fn)
arrMap([1, 2, 3]) // [11, 12, 13]
arrMap([4, 5, 6]) // null
柯里化
柯里化理解 柯里化是將一個(gè)多元函數(shù)轉(zhuǎn)換為嵌套一元函數(shù)的過程
function curry (fn) {
return curryN (...arg) {
if (arguments.length < fn.length) {
return function () {
return curryN.call(null, ...arg.concat(...arguments))
}
}
return fn.call(null, ...arguments)
}
}
const add = curry ((x, y, z) => x + y + z)
console.log(add(2)(3)(4)) // 9
偏函數(shù)
偏函數(shù)理解 初始化時(shí)指定原函數(shù)的一些參數(shù)并創(chuàng)建一個(gè)新函數(shù),這個(gè)函數(shù)用于接收剩余參數(shù)
function proto(fn, ...pagram) {
return (...args) => {
args.forEach((item, index) => { if (item && !pagram[index]) pagram[index] = item })
return fn.apply(null, pagram)
}
}
let add = proto((x, y) => { console.log(x + y) }, undefined, 10)
add(2) // 12
組合和管道
組合和管道理解 將一個(gè)函數(shù)的輸出作為另一個(gè)函數(shù)的輸入,像流水一樣從函數(shù)隊(duì)列從左到右流動(dòng)或者從右到左流動(dòng)
// 單個(gè)參數(shù),簡單組合
const compose = (fn, fnc) => (arg) => fn(fnc(arg))
// 多個(gè)參數(shù),借助偏函數(shù)實(shí)現(xiàn)
function mapArr(arr, fn) { return arr.map(fn) }
function filte (arr, fn) { return arr.filter(fn) }
let map = proto(mapArr, undefined, (item) => { return item + 10 })
let filter = proto(filte, undefined, (item) => { return item })
let mapFilter = compose(map, filter)
console.log(mapFilter([1, false, 9, 4])) // [11, 19, 14]
// 多個(gè)函數(shù)組合
const reduce = (arr, fn, value) => {
let initValue = value? value: arr[0]
arr.forEach((item) => { initValue += fn(initValue, item) })
return initValue
}
const compose = (...arg) => (value) => reduce(arg.reverse(), (acc, fn) => fn(acc), value)
let add = compose(() => { return 1 }, () => { return 2 }, () => { return 3 })
add(6) // 12
函子
函子的定義 函子是一個(gè)普通對(duì)象(在其他語言中,可能是一個(gè)類),它實(shí)現(xiàn)了 map 函數(shù),在遍歷每個(gè)對(duì)象值的時(shí)候生成一個(gè)新對(duì)象 很抽象,簡單來說 函子是一個(gè)持有值的容器。嗨難懂,上代碼。
- 如圖[網(wǎng)上所盜]

// 實(shí)現(xiàn)一個(gè)基本定義的函子,滿足定義
// 實(shí)現(xiàn) map 方法,在遍歷對(duì)象的時(shí)候生成一個(gè)新對(duì)象
function container (value) { this.value = value }
container.prototype.of = function (value) { return new container(value) }
container.prototype.map = function(fn) { return new container().of(fn(this.value)) }
new container().of([1, 5, 7, 3]).map((arr) => { return arr.filter((item) => item === 5)})
console.log(
new container().of([1, 5]).map((arr) => { return arr.filter((item) => item === 5)}).value
) // 5
寫在最后
到此面向?qū)ο蠛秃瘮?shù)式編程的基本思想就都簡單實(shí)現(xiàn)了,更多的需要自行深入學(xué)習(xí)
上面兩種編程方式在學(xué)習(xí)實(shí)踐的過程中給我提供了很多解決問題和組織代碼框架的思維,在很多開源庫中也能看見它們實(shí)現(xiàn)的影子,當(dāng)然真正理解這兩種編程方式,談何容易,更多的是要不斷的實(shí)踐和思考總結(jié),慢慢積累
以上就是js面向?qū)ο缶幊蘋OP及函數(shù)式編程FP區(qū)別的詳細(xì)內(nèi)容,更多關(guān)于js面向?qū)ο驩OP函數(shù)式FP區(qū)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript架構(gòu)搭建前端監(jiān)控如何采集異常數(shù)據(jù)
這篇文章主要為大家介紹了JavaScript架構(gòu)搭建前端監(jiān)控如何采集異常數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
微信小程序 跳轉(zhuǎn)傳參數(shù)與傳對(duì)象詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序 跳轉(zhuǎn)傳參數(shù)與傳對(duì)象詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
jscpd統(tǒng)計(jì)項(xiàng)目中的代碼重復(fù)度使用詳解

