【磨破嘴皮】一文弄懂Js面向对象编程的演进与实现

使用对象,在js中实现类 工厂函数

使用工厂函数实现类,虽然可以实现类的效果,但是

  • 我们无法判断,某个实例是否属于某类。
  • 创建出对象都独享一份自己的方法
javascript 复制代码
const Human = function (name, general, old) {
    return {
        name: name,
        general: general,
        old: old,
        eat: function() {
        },
        sleep: function() {
        },
        learn: function() {
        }
    }
}
const OtherClass = function () {
    return {
        other: 'otherclass'
    }
}
const xiaowang = Human('小王', '男', 22)
const xiaoming = Human('小明', '男', 100)
const other = OtherClass()
// 我们无法判断,某个实例是否属于某类。
console.log(xiaowang instanceof Human) // false
console.log(other instanceof Human) // false
// 各自独享自己的一份方法
xiaowang.eat === xiaoming.eat // false

针对上面的问题,我们可以使用构造函数的方式来解决这些问题,并且更优雅的实现类。

JS中类的实现

使用构造函数来创建类,此时可以通过new 关键字调用此方法,让我们可以使用 instanceOf 对实例是否属于类进行判断。

javascript 复制代码
function Human (name, general, old) {
    this.name= name;
    this.general= general
    this.old = old 
    this.eat = function() {}
    this.sleep = function() {}
    this.learn = function() {}
}
const xiaowang = new Human('小王', '男', 22)
xiaowang instanceof Human // true

此时的构造函数仍有一个问题,所有的实例的方法都不共享,所以我们需要进行下面的改造

题外话:那么为什么new关键字能够让构造函数创建实例

new 关键字做了下面几件事

  • 检查调用的对象是否为方法
  • 利用方法的prototype,创建一个新对象
  • 将方法中this指向刚刚新建的对象
  • 将对象返回出去
javascript 复制代码
function myNew (func, ...arg) {
  if(!func || func instanceof Function) return false
  // 创建this空间 使用prototype是为了保留func上的属性,例如arguments, callee, 以及prototype上的一些公有属性和公有方法
  const obj = Object.create(func.prototype)  
  // 修改this指向
  func.apply(obj, args)
  return obj
}

优化:让所有对象都共享一份方法

javascript 复制代码
function Human (name, general, old) {
    this.name= name;
    this.general= general
    this.old = old 
}
// 将需要共享的方法挂载到构造函数的prototype上
Human.prototype.eat = function() {}
Human.prototype.sleep = function() {}
Human.prototype.learn = function() {}

const xiaowang = new Human('小王', '男', 22)
const xiaoming = new Human('小明', '男', 22)
xiaowang.eat === xiaoming.eat // true
xiaowang instanceof Human // true

优化:类安全 防止类被作为普通方法调用

在构造函数中,利用insantceof判断函数中当前的this 是否为函数的实例

javascript 复制代码
function Human (name, general, old) {
    if(!this instanceof Human) {
        return new Human(...arguments)
    }
    this.name= name;
    this.general= general
    this.old = old 
}
// 将需要共享的方法挂载到构造函数的prototype上
Human.prototype.eat = function() {}
Human.prototype.sleep = function() {}
Human.prototype.learn = function() {}

优化:实现类方法的链式调用

在方法中,将执行此方法的对象返回,即在方法末尾 return this

javascript 复制代码
function Human (name, general, old) {
    if(!this instanceof Human) {
        return new Human(...arguments)
    }
    this.name= name;
    this.general= general
    this.old = old 
}
Human.prototype.eat = function() {
    return this
}
Human.prototype.sleep = function() {
    return this
}
Human.prototype.learn = function() {
    return this
}
const xiaowang = new Human('小王', '男', 22)

xiaowang.eat().sleep().learn()

优化:实现私有属性、私有方法、对象公有属性、对象公有方法、set get方法(特权方法、类静态公有属性、类静态公有方法、公有属性、公有方法)

javascript 复制代码
function Human (name, general, old) {
    if(!this instanceof Human) {
        return new Human(...arguments)
    }
    // 私有属性
    let name;
    let old;
    // 私有方法
    function talk() {
    }
    // 对象公有属性
    this.general= general
    // 特权方法:用于访问私有属性
    this.setName = function(name) {
        name = name
    }
    this.getName = function () {
        return name
    }
    this.setOld = function(old) {
        old = old
    }
    this.getOld = function() {
        return old
    }
    // 构造器
    this.setName(name)
    this.setOld(old)
}
// 类静态公有属性 - 实例对象无法访问
Human.isChinese = true
// 类静态公有方法 - 实例对象无法访问
Human.resetNationality = function(isChinese) {
    Human.isChinese = true
}
// 公有属性 -- 所有类构建的实例共享这一个属性
Human.prototype.isHuman = true
// 公有方法
Human.prototype.battle = function() {
}
const wangwu = new Human('王五', '女', 66)

wangwu.name // undefined
wangwu.getName() // 王五
wangwu.isChinese // undefined
wangwu.isHuman // true

题外话:可以使用 # 作为属性的开头来阻止外部访问

arduino 复制代码
const Humam = {
   '#name': '王五'
}
console.log(Humam.#name)

优化:使用闭包实现一个完整的类

javascript 复制代码
const Human = (
    function _human (name, general, old) {
        if(!this instanceof Human) {
            return new Human(...arguments)
        }
        // 私有属性
        let name;
        let old;
        // 私有方法
        function talk() {
        }
        // 对象公有属性
        this.general= general
        // 特权方法:用于访问私有属性
        this.setName = function(name) {
            name = name
        }
        this.getName = function () {
            return name
        }
        this.setOld = function(old) {
            old = old
        }
        this.getOld = function() {
            return old
        }
        // 构造器
        this.setName(name)
        this.setOld(old)
    }
    // 类静态公有属性 - 实例对象无法访问
    _human.isChinese = true
    // 类静态公有方法 - 实例对象无法访问
    _human.resetNationality = function(isChinese) {
        _human.isChinese = true
    }
    // 公有属性 -- 所有类构建的实例共享这一个属性
    _human.prototype.isHuman = true
    // 公有方法
    _human.prototype.battle = function() {
    }
    return _human
)()

类的继承

类式继承

利用js 会顺着prototype不断向上查找对象中是否存在某个属性或方法,将类的prototype指向需要继承的类的实例,从而获取到他所有的公有方法和属性

新增一个生物类,让Human继承这个类

javascript 复制代码
function Organism() {
    this.alive = true
}
Organism.prototype.breathe = function () {}

Human 继承 Organism

javascript 复制代码
function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {}
function Human() {
}
Human.prototype = new Organism()

这样就完成了Human对Organism的继承

但是存在以下几个问题

  • Human 并不是Organism 的实例(子类 并不是父类的实例)
  • 所有的Human 实例都共享一份Organism 实例的对象公有属性子类所有的实例共享 一份父类对象公有引用类型属性
javascript 复制代码
function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {}
function Human() {
}
Human.prototype = new Organism()

const xiaowang = new Human()
const xiaoming = new Human()
xiaowang.alive = false
// 小王学会讲话
xiaowang.skills.push('talk')

// 明明只有小王学习了如何讲话,但是小明也会讲话了,这显然是不符合常识的 这是因为所有子类共享一份饮用类型的对象公有属性
console.log(xiaoming.skills) // ['eat', 'sleep', 'drink', 'talk']
// 值类属性不受影响
console.log(xiaoming.alive) // true

下面我们需要使用新的继承方式来解决

  • 所有的Human 实例都共享一份Organism 实例的对象公有属性子类所有的实例共享 一份父类对象公有引用类型属性

构造函数式继承

在子类的构造函数中,执行父类利用call执行父类的构造函数

javascript 复制代码
function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {}
function Human() {
    Organism.call(this, ...arguments)
}
const xiaowang = new Human()
const xiaoming = new Human()
xiaowang.alive = false
// 小王学会了讲话
xiaowang.skills.push('talk')

// 子类实例不再共享一份饮用类型的对象公有属性
console.log(xiaoming.skills) // ['eat', 'sleep', 'drink']
// 值类属性不受影响
console.log(xiaoming.alive) // true
// 但是小明和小王都不会呼吸了,因为Human的原型上并没有 Organism的公有方法 breathe
xiaoming.breathe() // [TypeError] xiaoming.breathe is not function 

小明和小王都不会呼吸了,因为Human的原型上并没有 Organism的公有方法 breathe

为了解决这个问题,我们需要再次使用一下 类式 继承

组合继承

组合继承同时使用了类式继承与构造函数式继承

javascript 复制代码
function Organism() {
    this.alive = true
     this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {
    console.log(this.alive ? 'breathing': 'die')
}
function Human() {
    Organism.call(this, ...arguments)
}
// 继承Organism类的呼吸方法
Human.prototype = new Organism()

const xiaowang = new Human()
const xiaoming = new Human()
xiaowang.skills.push('talk')

// 小王的死亡也不影响小明了
console.log(xiaoming.alive) // true
console.log(xiaoming.skills) // ['eat', 'sleep', 'drink']
// 可以看到小明可以正常呼吸了
xiaoming.breathe() // breathing

因为在Human实例上已经存在了 alive属性,所以会遮蔽掉 Human.prototype上继承的 alive,规避掉了单独使用 类式继承 存在的问题

虽然继承能够初步实现了,但是组合继承有以下缺点

  • 父类的构造函数执行了两次

所以我们需要一种更加完美的继承方式,下面介绍原型式继承与寄生式继承

原型式继承

原型式继承就是类式继承的一个封装

  • 利用一个过渡对象的prototype,接收父类的属性值,存在的问题与类式继承一致
javascript 复制代码
// 原型式继承 的核型方法
function inheritObject(o) {
    function Foo() {}
    Foo.prototype = o
    return new Foo()
}
function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {
    console.log(this.alive ? 'breathing': 'die')
}
function Human() {
}
Human.prototype = inheritObject(new Organism())

const xiaowang = new Human()
const xiaoming = new Human()

寄生式继承

  • 寄生式继承是针对原型式继承的再次封装
javascript 复制代码
function inheritObject(o) {
    function Foo() {}
    Foo.prototype = o
    return new Foo()
}
function createObj(obj) {
    const o = new inheritObject(obj)
    o.getName = function () {}
    return o
}

function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {
    console.log(this.alive ? 'breathing': 'die')
}
function Human() {
}
Human.prototype = createObj(new Organism())

const xiaowang = new Human()
const xiaoming = new Human()

寄生组合继承 -- 终极方案

同时使用寄生式继承与构造函数继承的完美继承方案

javascript 复制代码
function inheritObject(o) {
    function Foo() {}
    Foo.prototype = o
    return new Foo()
}
function createObj(obj) {
    const o = new inheritObject(obj)
    o.getName = function () {}
    return o
}
function inhertPrototype(subClass, superClass) {
    // 后面我们会使用 构造函数,所以这里只需要获取到prototype上的公有内容即可
    const p = inheritObject(superClass.prototype)
    // 因为完全复制了 父类的 prototype,类的prototype中的 constructor也被复制下来了,所以我们需要重新🈯️定 p的constructor 指回子类
    p.constructor = subClass
    subClass.prototype = p
    return subClass
}
function Organism() {
    this.alive = true
    this.skills = ['eat', 'sleep', 'drink']
}
Organism.prototype.breathe = function () {
    console.log(this.alive ? 'breathing': 'die')
}
function Human() {
    Organism.call(this)
}
const FinallHuman = inhertPrototype(Human, Organism)

const xiaowang = new Human()
const xiaoming = new Human()

寄生组合继承用到了以下特性

  • 利用构造函数继承,将父类的对象公有属性继承
  • 利用寄生继承,继承父类的原型上的公有内容,并且修改子类prototype因为类式继承而造成的constructor指向父类的问题。

完美继承后的原型链示意图

一般类的原型链

对象实例的_proto_指向类的原型 prototype,类原型的 constructor指向 构造函数

相关推荐
su1ka1112 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬4 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*4 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_801074155 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_6 分钟前
报错合计-1
前端
caperxi8 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男8 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
susu10830189119 分钟前
前端css样式覆盖
前端·css
学习路上的小刘10 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&10 分钟前
vue3常用的组件间通信
前端·javascript·vue.js