js 面向對象編程(OOP) ps 函數式編程(FP)

寫在前面

瀏覽下文我以爲仍是要有些基礎的!下文涉及的知識點太多,基本上每個拿出來都能寫幾篇文章,我在寫文章的過程當中只是作了簡單的實現,我只是提供了一個思路,更多的細節仍是須要本身去鑽研的,文章內容也很多,辛苦,若是有其餘的見解或者意見,歡迎指點,最後紙上得來終覺淺,絕知此事要躬行javascript

javscript 中函數和對象的關係

javscript 一切皆爲對象,但基本類型以外,函數是對象,對象是由函數建立而來, 從而衍生出我對這兩種編程方式的探討。下面對類型判斷和原型作了一個簡單的表述,這裏不是重點,不作具體的表述,感興趣的能夠本身百度/谷歌。前端

// 類型判斷
// 基本類型
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]
// 報錯【null 不是一個對象】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 【新類型】

// 引用類型 - 對象
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
                                     
複製代碼

面向對象編程(OOP)

在面向對象編程中最多見的表現形式就是類,提供了面向對象的 3⃣ 大特色和 5⃣️ 大原則,這東西網上特別多,我只作簡單的羅列,下面我會對特色進行實現,個人理解: 原則是面向對象編程的規範,而特色是面向對象編程的實現,前提是你已經仔細理解過下面對核心概念。java

  1. 三大特色
  • 繼承
  • 多態
  • 封裝
  1. 五大原則
  • 單一 【一個類應該有且只有一個去改變它的理由,這意味着一個類應該只有一項工做】
  • 開放封閉 【對象或實體應該對擴展開放,對修改封閉。】
  • 里氏替換 【即對父類的調用一樣適用於子類】
  • 依賴倒置 【高層次的模塊不該該依賴於低層次的模塊】
  • 接口隔離 【不該強迫客戶端實現一個它用不上的接口,或是說客戶端不該該被迫依賴它們不使用的方法】

繼承

繼承是面向對象一個特色,能夠實現子類調用本身沒有的屬性方法【父類屬性方法】git

/** 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

// 構造器繼承
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)

// ......

// 總結
// 繼承的方式方法多種多樣,不外乎,就是經過,某一種方式將不屬於本身的屬性方法能夠調用,沿着原型的方式和拷貝賦值就能夠總結出不少種不一樣的繼承方式,每種方式的優缺點,可能是考慮,繼承的屬性方法的完整性和對實例化對象的影響,如實例上方法和原型鏈上方法是否均可以調用有或者引用傳遞改變同一原型鏈問題。
複製代碼
/** 上面爲對實例對繼承,下面說一說對於接口對繼承 **/
// ES6 中並無提供接口這個概念,可是 Typescript 中對於接口又很好對支持,typescript 是 javascript 對超集,對面向對象提供了很是好對支持

// Typescript 【一時用一時爽,一直用一直爽】
// 很推薦用這個,他能避免不少低級錯誤,提供類型檢查,特別是寫過 java 轉前端的。

interface parent { run: () => void }
class child implements parent { run () {} }

// 轉碼後
var child = /** @class */ (function () {
    function child() {
    }
    child.prototype.run = function () { };
    return child;
}());
複製代碼

多態

多態是面向對象一個特色,能夠實現子類有不一樣對錶現形態,能夠實現同一種表現形式,能夠有不一樣對狀態github

/** 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} 重載,模擬實現
class Animal {
  eat () { 
    if (typeof arg === '') {
      console.log('操做 one')
    } else if (typeof arg === '') {
      console.log('操做 two')
    } else {
      console.log('操做 three')
    }
  }
}


/** ES5 【提供實現一種】**/
// 原理就是沿着原型鏈往上找,只要在父類前定義重寫這個方法便可
// ${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')
    }
  }
}

複製代碼

封裝

封裝是面向對象一個特色,將屬性和方法封裝這對象中,能夠利用私有或者公有屬性,對外提供能夠訪問的方法或屬性typescript

/** ES6 **/
// ES6 沒有提供真正的私有方法和屬性,有一個還在提案階段
// 在屬性和方法前面加 #
class Animal {
  #height = ''
  #eat () {}
}

// 模擬實現 【提供一種實現】

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) }
}
複製代碼

函數編程編程(FP)

函數式編程提倡函數是第一公民【指的是函數與其餘數據類型同樣,處於平等地位,能夠賦值給其餘變量,也能夠做爲參數,傳入另外一個函數,或者做爲別的函數的返回值】,純粹的函數式編程,是純函數【若是傳入的參數相同,就會返回相同的結果,不依賴於外部的數據狀態【以下實例】】,函數編程特色編程

// 純函數
const add = (one, two) => { return one + two }
// 非純函數
let two = 1
const add = (one) => { return one + two }
複製代碼
  • 閉包和高階函數
  • 柯里化
  • 偏函數
  • 組合和管道
  • 函子

閉包和高階函數

閉包理解 函數內部還有其餘函數,可使父函數數據狀態得以保存 高階函數理解 函數能夠經過變量傳遞給其餘函數數組

// 利用封包實現一個只能調用一次的 map 高階函數
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
複製代碼

柯里化

柯里化理解 柯里化是將一個多元函數轉換爲嵌套一元函數的過程閉包

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
複製代碼

偏函數

偏函數理解 初始化時指定原函數的一些參數並建立一個新函數,這個函數用於接收剩餘參數app

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
複製代碼

組合和管道

組合和管道理解 將一個函數的輸出做爲另外一個函數的輸入,像流水同樣從函數隊列從左到右流動或者從右到左流動

// 單個參數,簡單組合
const compose = (fn, fnc) => (arg) => fn(fnc(arg))
// 多個參數,藉助偏函數實現
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]
// 多個函數組合
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
複製代碼

函子

函子的定義 函子是一個普通對象(在其餘語言中,多是一個類),它實現了 map 函數,在遍歷每一個對象值的時候生成一個新對象 很抽象,簡單來講 函子是一個持有值的容器。嗨難懂,上代碼。

  • 如圖[網上所盜]

// 實現一個基本定義的函子,知足定義
// 實現 map 方法,在遍歷對象的時候生成一個新對象
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
複製代碼

寫在最後

到此面向對象和函數式編程的基本思想就都簡單實現了,更多的須要自行深刻學習

上面兩種編程方式在學習實踐的過程當中給我提供了不少解決問題和組織代碼框架的思惟,在不少開源庫中也能看見它們實現的影子,固然真正理解這兩種編程方式,談何容易,更多的是要不斷的實踐和思考總結,慢慢積累。最後歡迎關注個人 Blog,我通常會在每週寫一篇原創的文章

相關文章
相關標籤/搜索