【磨破嘴皮】一文弄懂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指向 构造函数

相关推荐
豐儀麟阁贵6 分钟前
8.5在方法中抛出异常
java·开发语言·前端·算法
zengyuhan50336 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休39 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running1 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔1 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654261 小时前
Android的自定义View
前端
WILLF1 小时前
HTML iframe 标签
前端·javascript
枫,为落叶1 小时前
Axios使用教程(一)
前端
小章鱼学前端1 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah1 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript