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,我通常会在每周写一篇原创的文章

相关文章
相关标签/搜索