使用对象,在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指向 构造函数