Skip to main content

原型,继承

https://juejin.cn/post/6844903696111763470

1. proto

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。

https://upload-images.jianshu.io/upload_images/1490251-e7476a8697e97aab.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp

2 继承

// ES5
function Parent(name, age) {
this.name = name
this.age = age
}
Parent.prototype.say = function () {
console.log('I am' + this.name)
}

function Child(name, age, sex) {
Parent.call(this, name, age)
this.sex = sex
}

Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
//父类
class Person {
//constructor是构造方法
constructor(skin, language) {
this.skin = skin
this.language = language
}
say() {
console.log('我是父类')
}
}

//子类
class Chinese extends Person {
constructor(skin, language, positon) {
//console.log(this);//报错
super(skin, language)
//super();相当于父类的构造函数
//console.log(this);调用super后得到了this,不报错,this指向子类,相当于调用了父类.prototype.constructor.call(this)
this.positon = positon
}
aboutMe() {
console.log(`${this.skin} ${this.language} ${this.positon}`)
}
}

//调用只能通过new的方法得到实例,再调用里面的方法
let obj = new Chinese('红色', '中文', '香港')
obj.aboutMe()
obj.say()

3. 继承的方式

1. 原型链继承

//原型链继承

// 父类
// 拥有属性 name
function parents() {
this.name = ['Tesla']
}
// 在父类的原型对象上添加一个getName方法
parents.prototype.getName = function () {
console.log(this.name)
}
//子类
function child() {}
//子类的原型对象 指向 父类的实例对象
child.prototype = new parents()

// 创建一个子类的实例对象,如果它有父类的属性和方法,那么就证明继承实现了
let child1 = new child()

child1.getName() // => ["Tesla"]

// 创建一个子类的实例对象,在child1修改name前实现继承
let child2 = new child()

// 修改子类的实例对象child1的name属性
child1.name.push('Jie')

// 创建子类的另一个实例对象,在child1修改name后实现继承
let child3 = new child()

child1.getName() // => ["Tesla", "Jie"]
child2.getName() // => ["Tesla", "Jie"]
child3.getName() // => ["Tesla", "Jie"]

缺点:

  • 原型链继承里面,使用的都是同一个内存里的值,这样修改该内存里的值,其他继承的子类实例里的值都会变化
  • 创建子类型的时候,不能像父类型的构造函数中传递参数

1. 借用构造函数

在子类中,使用 call 方法构造函数,实现继承

function parents() {
this.name = ['JoseyDong']
}
parents.prototype.getName = function () {
console.log(this.name)
}
// 在子类中,使用call方法构造函数,实现继承
function child() {
parents.call(this)
}

let child1 = new child()
let child2 = new child()

child1.name.push('xixi')

let child3 = new child()

console.log(child1.name) // => ["JoseyDong", "xixi"]
console.log(child1.getName()) //报错
console.log(child2.name) // => ["JoseyDong"]
console.log(child3.name) // => ["JoseyDong"]

缺点:

  • 子类不能继承父类的原型属性与原型方法

1. 组合继承

//组合继承

// 偶像练习生大赛开始报名了
// 初赛,我们找了一类练习生
// 这类练习生都有名字这个属性,但名字的值不同,并且都有爱好,而爱好是相同的
// 只有会唱跳rap的练习生才可进入初赛
function student(name) {
this.name = name
this.hobbies = ['sing', 'dance', 'rap']
}

// 我们在student那类里面找到更特殊的一类进入复赛
// 当然,我们已经知道初赛时有了name属性了,而不同练习生名字的值不同,所以使用构造函数方法继承
// 同时,我们想再让练习生们再介绍下自己的年龄,每个子类还可以自己新增属性
// 当然啦,具体的名字年龄就由每个练习生实例来定
// 类只告诉你,有这个属性

function greatStudent(name, age) {
student.call(this, name) // //借用构造函数继承属性,二次调用
this.age = age
}

// 而大家的爱好值都相同,这个时候用原型链继承就好啦
// 每个对象都有构造函数,原型对象也是对象,也有构造函数,这里简单的把构造函数理解为谁的构造函数就要指向谁
// 第一句将子类的原型对象指向父类的实例对象时,同时也把子类的构造函数指向了父类
// 我们需要手动的将子类原型对象的构造函数指回子类
greatStudent.prototype = new student() // //借用原型链继承方法,一次调用
greatStudent.prototype.constructor = greatStudent

// 决赛 kunkun和假kunkun进入了决赛
let kunkun = new greatStudent('kunkun', '18')
let fakekun = new greatStudent('fakekun', '28')

// 有请两位选手介绍下自己的属性值
console.log(kunkun.name, kunkun.age, kunkun.hobbies) // => kunkun 18 ["sing", "dance", "rap"]
console.log(fakekun.name, fakekun.age, fakekun.hobbies) // => fakekunkun 28 ["sing", "dance", "rap"]

// 这个时候,kunkun选手说自己还有个隐藏技能是打篮球
kunkun.hobbies.push('basketball')

console.log(kunkun.name, kunkun.age, kunkun.hobbies) // => kunkun 18 ["sing", "dance", "rap", "basketball"]
console.log(fakekun.name, fakekun.age, fakekun.hobbies) // => fakekun 28 ["sing", "dance", "rap"]

// 我们可以看到,假kunkun并没有抄袭到kunkun的打篮球技能
// 并且如果这个时候新来一位选手,从初赛复赛闯进来的一匹黑马
// 可以看到黑马并没有学习到kunkun的隐藏技能
let heima = new greatStudent('heima', '20')
console.log(heima.name, heima.age, heima.hobbies) // => heima 20 ["sing", "dance", "rap"]

缺点:

  • 两次调用父构造器函数,浪费内存。

1. 原型式继承

function createObj(o) {
function F() {}
F.prototype = o
return new F()
}

使用场合:没必要构建构造函数,仅仅是想模拟一个对象的时候

1. 寄生继承

//寄生式继承

function createObj(o) {
let clone = Object.create(o)
clone.sayName = function () {
console.log('hi')
}
return clone
}

let person = {
name: 'JoseyDong',
hobbies: ['sing', 'dance', 'rap'],
}

let anotherPerson = createObj(person)
anotherPerson.sayName() // => hi

1. 寄生组合继承(最理想)

function object(o) {
function F() {}
F.prototype = o
return new F()
}

function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype) //创建对象
prototype.constructor = subType //增强对象
subType.prototype = prototype //指定对象
}
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}

function SubType(name, age) {
this.age = age
}
inheritPrototype(SubType, SuperType) //实现继承
SubType.prototype.sayAge = function () {
console.log(this.age)
}

总结

  1. 原型链继承 子类的 prototype=new Parent()

    1. 引用类型的属性被所有实例共享
    2. 在创建 Child 的实例时,不能向 Parent 传参
  2. 借用构造函数 child 中 Parent.call(this);

    1. 可以向父类传参数,
    2. 不能继承父类原型的属性和方法
  3. 组合继承 =原型链继承+构造函数

    1. 父类方法可以复用,可以向父类传参数
    2. 两次调用父类的构造函数
  4. 原型式继承 ( Object.create ) 引用类型的值会被所有实例共享

  5. 寄生继承 每次创建对象都会创建一遍方法

    function createObj(o) {
    var clone = Object.create(o)
    clone.sayName = function () {
    console.log('hi')
    }
    return clone
    }
  6. 寄生组合继承

    function object(o) {
    function F() {}
    F.prototype = o
    return new F()
    }

    function prototype(child, parent) {
    var prototype = object(parent.prototype)
    prototype.constructor = child
    child.prototype = prototype
    }

    // 当我们使用的时候:
    prototype(Child, Parent)