对象的继承方式

关于对象的继承方式的学习和总结

原型链的继承方式

概念: 父元素作为子类的原型

缺点: 父元素的原型属性方法的变化会影响到子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function User (name: string) {
  this.name = name
  this.getName = function (): string {
    return this.name
  }
  this.setName = function (name: string): void {
    this.name = name
  }
}

User.prototype.test = function () {
  console.log('test user')
}

function Student (age: number) {
  this.age = age
}

Student.prototype = new User('dw')

const s = new Student(21)
s.test()   // 'test user'

// 此时 s 的值为 { age: 21 },  但是由于继承了User对象,拥有User对象的属性和方法

对象在获取一个属性或者方法的时候,会先访问自身是否有这个属性,如果有,则直接返回,如果没有,则寻找父级对象,继续执行属性和方法的查找,如果有则返回,没有则重复执行这类操作,直至最顶层,如果没有则返回undefined

1
2
3
4
5
6
// 因此,s 为 { age: 21 }
s.name === 'dw'   // true  此时访问的是父元素的name属性, 因为s 自身并没有这个属性, 他是继承 User,因此能拿到User实例的属性和方法

s.name = 'dwdwdwdw'     // 此时 s 的值为 { age: 21, name: 'dwdwdwdw' }
s.setName('newDw')    // 此时 s 的值为 { age: 21, name: 'newDw' }
Student.prototype    // name的值还是 dw

s.name 和 s.setName 只会作用自身的对象属性,但是在访问的时候,如果自身没有该属性和方法,则会往上层层查找

构造继承方式

概念: 使用call,apply的方式把父类的this制定的属性方法创建在子级的对象,不涉及对象继承,原型链继承

缺点:

  • 只能继承(Student)的实例属性和方法,父类(User)原型扩展不会影响到子类(子类无法访问其原型上扩展的属性或者方法)
  • 每个子类都会拿到父类的所有属性和方法并存在子类的实例中,这样会增加不必要的内存问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function User (name: string) {
  this.name = name
  this.getName = function (): string {
    return this.name
  }
  this.setName = function (name: string): void {
    this.name = name
  }
}

User.prototype.test = function () {
  console.log('test user')
}

function Student (name: string, age: number) {
  User.call(this, name)
  this.age = age
}

const s = new Student('dw', 26)
s.test()  // VM424:1 Uncaught TypeError: s.test is not a function

// s 并不像原型链继承的方式只有age属性,而是所有的属性和方法都存在s对象上
// {
//   age: 26,
//   getName: function () {},
//   name: 'dw',
//   setName: function (name) {}
// }

这种继承方式并不存在对象继承,而仅仅是将 User 的方法和属性设置到 Student 构造函数之上, 实现方式就是利用call apply 指定this的指向执行调用机制

原型链 + 构造继承组合的方式

概念: 使用call方法调用父类构造函数,继承父类的属性,然后再通过父类实例作为子类的原型

缺点:

  • 无论什么情况下,都会调用两次父类构造函数
  • 父类中(User)的属性和方法既存在于子类(Student)的实例化对象中,又存在于子类的原型中(Student.prototype)说是会内存占用
  • 一组在实例上,一组在 Student 原型中,这是不合理的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  function User (name, sex) {
    this.name = name
    this.sex = sex
    console.log('name-sex', name, sex)
    this.setSex = function () {
      console.log('this is User setSex')
    }
  }

  User.prototype.test = function () {
    console.log('this is User test')
  }

  function Student (name, sex, grade) {
    console.log(name, sex)
    // 继承 User的属性
    User.call(this, name, sex)
    this.grade = grade
    this.setGrade = function () {
      console.log('this is Student setGrade')
    }
  }

  // 设置原型
  Student.prototype = new User()
  console.log(Student.prototype.constructor)
  console.log(Student.prototype)
  // 设置构造函数由Student初始化
  Student.prototype.constructor = Student
  console.log(Student.prototype.constructor)
  console.log(Student.prototype)
  Student.prototype.setStudent = function () {
    console.log('this is Student setStudent')
  }
  // 用于测试 Student.prototype.constructor = Student 
  // 可查看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor
  Student.prototype.test = function () {
    console.log('this is Student test')
    return new this.constructor()
  }
  const c1 = new Student('张三', '男', '三年级')
  const c2 = new Student('王洁', '女', '四年级')

  console.log(c1);
  console.log(c1.constructor);

  console.log(c2);
  console.log(c2.constructor);

Student类通过设置原型链继承的方式定义prototype为User的实例,这样在实例化Student的时候,c1是可以访问到User的属性方法了,其次在实例化Student的时候,执行User.call,保证实例的对象有User的副本

寄生组合式继承

概念: 使用call方法调用父类构造函数,继承父类的属性,通过设置中间函数,创建父类原型的副本,存放在子类的原型上,避免两次调用父类构造函数

缺点: 暂无,可能就稍微麻烦一点吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function User (name, sex) {
  this.name = name
  this.sex = sex
   this.sayHello = function () {
    console.log(`Hello ${this.name}`)
  }
}
User.prototype.test = function () {
  console.log('this is User test')
}

User.prototype.saySex = function () {
  console.log(`my Sex is ${this.sex}`)
}

function warp (Child, Parent) {
  const prototype = Object.create(Parent.prototype)
  prototype.constructor = Child
  console.log(Parent.prototype)
  console.log(Object.create(Parent.prototype))
  console.log(prototype)
  Child.prototype = prototype    // 这里的目的是为了拿到原型属性,以及定义constructor指向自身
}

function Student (name, sex, age) {
  User.call(this, name, sex)  // 这里初始化构造函数属性继承与User
  this.age = age
}

Student.prototype.sayAge = function () {
  console.log(`${name}的年龄是:${this.age}`)
}

warp(Student, User)
const v1 = new Student('dw', '男', 22)
const v2 = new Student('zg', '女', 12)

寄生组合式继承是组合式继承的简化版本

上一篇 : 关于递归下一篇 : 对象实例化的时候经历了什么?