一篇弄懂继承

文章目录

继承的串联及扩展

对象的三个特性:封装、继承、多态

  1. 封装:把客观事物抽象成类,并且类可以把自己的数据库和方法可让可信的类或者对象操作,对不可信的进行信息隐藏。这也是局部变量赋值 symbol 的应用场景
  2. 继承:继承指这样一种能力:它可以使用现有类的所有功能,并无需重新编写原来的类的情况下对这些功能进行扩展
  3. 多态:多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值它的子对象的特性以不同的方式运行。**允许将子类类型的指针赋值给父类类型的指针。

继承

很多面向对象语言都支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。接口继承在 ECMScript 中是不可能的,因为函数没有签名。实现继承是 ECMScript 唯一支持的集成方法,而这主要是通过原型链实现。

其基本思想就是通过原型继承多个引用类型的属性和方法

常用继承:组合继承,寄生组合继承

javascript 对象具有 “ 自有属性 ” 也有一些属性是从原型对象继承来的。
有两种继承方式:

  1. 寄生函数继承(构造函数继承 (call/apply)),利用 call 继承父类上的属性,用子类的原型等于父类实例去继承父类的方法。缺点:调用父类两次,造成性能浪费。
  2. 原型链继承 ( 挂载到 prototype 属性上面 )。

new 操作符

new 的源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function myNew(fun) {
return function () {
// 创建一个新对象且将其隐式原型指向构造函数原型
let obj = {
__proto__: fun.prototype
}
// 执行构造函数
fun.call(obj, ...arguments)
// 返回该对象
return obj
}
}

function person(name, age) {
this.name = name
this.age = age
}
let obj = myNew(person)('chen', 18) // {name: "chen", age: 18}

实例化的过程

1
2
3
var obj = {}
obj._proto_ = Parent.prototype
return obj

所以通过 Child.prototype = new Parent(),子类就可以得到父类共享方法

new 的过程如下:

  • 创建一个空对象 obj
  • 将创建的空对象的隐式原型指向其构造函数的显示原型
  • 使用 call 改变 this 的指向
  • 如果无返回值或者返回一个空对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话,那么直接返回该对象

所以,在 new 的过程中,我们使用 call 改变了 this 的指向

为什么不是 Child.prototype = Parent.prototype?

看到一道题是这些写的,想着想着掉坑里了,因为原型链是通过proto去寻找属性和方法了,若是通过Child.prototype = Parent.prototype去绑定的话,那么之后所有继承都只能通过这种方式绑定,不能在通过 new,因为这里没有生成proto去构成原型链,这已经不是原型链了吧?顶多算是利用 prototype 的指针进行挂载

我们想要的效果,是将 Parent.prototype 这个对象挂在 Child.prototype.proto 的这个属性上;

关系判断

  • instanceof 判断是否为另一个对象的实例
  • isPrototypeOf() 判断对象是否存在另一个对象原型链上 Chtild.isPrototypeOf(Parent)
  • Object.getPrototypeOf() ES6 中新增的方法,用于获取子类的父类 Object.getPrototypeOf(child)==Parent //true

寄生函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age, sex) {
this.name = name

this.age = age

this.sex = sex
}

function Student(name, age, sex, score) {
Person.call(this, name, age, sex)

this.score = score
}

Student.prototype = new Person() // 这里改变了原型指向,实现继承

var stu = new Student('小明', 20, '男', 99) //创建了学生对象stu

console.log(stu instanceof Student) // true

console.log(stu instanceof Person) // true

console.log(stu instanceof Object) // true

寄生函数继承:利用 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
function Parent() {
this.name = 'Parent1'
this.arr = [1, 2, 3, 4, 5]
}

Parent.prototype.say = function () {
console.log(this.name)
}

function Child() {
this.m = [1, 2, 3]
this.type = 'Child1'
}

Child.prototype = new Parent()

var s1 = new Child()
var s2 = new Child()
s1.m.push(4) //[1,2,3,4]
s2.m.push(6) //[1,2,3,6]
s1.arr.push(6)
console.log(s1.arr) //[1,2,3,4,5,6]
console.log(s2.arr) //[1,2,3,4,5,6]
//他们俩是公用的
console.log(s1.__proto__ === s2.__proto__) //true

构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Parent(name) {
this.name = name
this.friends = ['A', 'B']
}
Parent.prototype.say = function () {
log(this.name, this.friends)
}
function Person() {
Parent.apply(this, arguments)
// parent.say.bind(child, arguments)(); //child world
this.age = 23
}
var p1 = new Person('nameA')
var p2 = new Person('nameB')
log(p1.name) //nameA
log(p2.name) //nameB
p1.friends.push('C')
p2.friends.push('D')
log(p1.friends) //["A", "B", "C"]
log(p2.friends) //["A", "B", "D"]
log(p1.say()) // Uncaught TypeError: p1.say is not a function
  • 优点:子类可以向父类的构造函数中传参,子类实例中的引用类型属性互不干扰
  • 缺点:子类实例无法访问父类的原型(无法复用父类原型中的方法)

Object.create

Object.create——MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//polyfill
if (typeof Object.create !== 'function') {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto)
} else if (proto === null) {
throw new Error(
"This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."
)
}

if (typeof propertiesObject !== 'undefined')
throw new Error(
"This browser's implementation of Object.create is a shim and doesn't support a second argument."
)

function F() {}
F.prototype = proto //intanceof 的核心判断也是用的这句

return new F()
}
}

constructor 的作用

实例的 constructor 指向创建实例的构造函数
经典原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Father() {
this.color = ['red', 'green']
}
function Child() {
this.test = 1
}
/*
在执行下面这一句之前 Child.prototype.constructor = Child
执行下面这一句之后Child.prototype.constructor=Father;
而Child.constructor自始至终都是Function,因为Child是自接由Function字面量定义的
*/
Child.prototype = new Father()
Child.prototype.constructor = Child

let instance = new Child()

上面的代码中Child.prototype.constructor = Child的意义是什么呢?我们访问属性都是按照原型链查找的,为什么还需要这一句?看了有些解释是说要尽量让对象指向其构造函数,以维持这个惯例

还可以将实例的构造器的原型对象暴露出来,比如写了一个插件,别人得到的是实例化后的对象,若是别人想扩展对象就可以用 instance.constructor.prototype 去修改或扩展原型对象

分享到:
network