在了解js继承时,首先我们先来说明一下几个概念
-
原型是什么?
一个对象,名为prototype为原型对象 -
原型的作用?
共享方法或属性 -
原型对象的custructor属性指向谁?作用是什么?
指向该原型的构造函数,在改变原型对象的引用时,我们需要手动调用constructor让他指向原来的构造函数 -
每个对象中都有一个内部指针
__proto__,它指向原型对象 -
原型链成员的查找规则?
当前实例对象--->构造函数的原型--->Object的原型
es6的继承方式
- 通过
extends - 子类如果想要调用父类中的方法可以通过
super.方法名()来调用
class Person{
constructor(name,age,sex){
this.name = name,
this.age = age,
this.sex = sex;
}
show(){
return `我叫${this.name},今年${this.age},性别${this.sex}`;
}
}
// 继承
class smallPerson extends Person{
constructor(name,age,sex,habbit){
super(name,age,sex);
this.habbit = habbit;
this.name = name;
this.age = age;
this.sex = sex;
}
showme(){
return super.show()+`爱好是${this.habbit}`;
}
show(){
console.log(`我叫${this.name},今年${this.age},性别${this.sex},爱好是${this.habbit}`);
}
}
let p1 = new smallPerson('zh',20,'nan','study');
p1.show();
console.log(p1.showme());
let p = new Person('zh',20,'nan');
console.log(p.show());
原型链继承
function Parent(money) {
this.money = money
}
Parent.prototype.showMoney = function() {
console.log(this.money)
}
function Son() {
}
Son.prototype = new Parent(10000000)
Son.prototype.showMoney = function() {
console.log(10000000000)
}
// Son.prototype.constructor = Son
let son = new Son()
son.showMoney()
// 这里的constructor指向的是Parent,因为Son的原型指向了另一个对象,
// 所以内部的constructor属性,指向另一个对象的构造函数
console.log(son.constructor)//Parent
上面的例子,父亲有一千万,但是可以被儿子继承,但是儿子重新赚到了更多的钱而不会影响父亲
-
确定实例和原型的关系
- 通过
instanceof
这个是判断后者是否出现在该实例的原型链上 - 通过
isPrototypeOf()方法
判断调用该方法的对象是否出现在出入的实例对象的原型链上
- 通过
-
如果想要给继承者添加自己的方法,一定要将代码写在替换原型语句的后面,且不能用对象字面量的形式来创建自己的方法,这样会重写原型链
-
原型链继承出现的问题
- 对于引用类型的值,当子类的一个实例改变引用类型的值时,通过该子类创建的实例也会改变,但是父类的不会改变。因为父类创建的对象和子类通过原型链继承的不是同一个对象。但是给对象添加新的属性,只会影响该对象自己,不会影响任何其他对象。
function Parent() { this.name = 'zh' this.friends = ['111', '222'] } Parent.prototype.showMoney = function() { console.log(this.money) } function Son() { } Son.prototype = new Parent() let son1 = new Son() let son2 = new Son() let p = new Parent() son1.friends.push('333') son1.name = "son1" console.log(son1.name, son1.friends) console.log(son2.name, son2.friends) console.log(p.friends) //['111', '222']
2. 不能向父类的构造函数中传参
3.打印对象的时候继承的属性是看不到的。
借用构造函数(经典继承/伪造对象)
- 在子类的构造函数中调用父类的构造函数,利用(
call(),apply()) - 问题:虽然解决了共享引用类型的问题,但是子类无法获取父类定义的方法。
组合继承(伪经典继承)
最常用的继承模式
- 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
function Parent(money) {
this.money = money
this.friends = ['111', '222']
}
Parent.prototype.showMoney = function() {
console.log(this.money)
}
function Son(money, age) {
Parent.call(this,money)
this.age = age
}
Son.prototype = new Parent()
Son.prototype.showAge = function() {
console.log(this.age)
}
let son1 = new Son(1000000,20)
son1.friends.push('333')
son1.showAge()//20
son1.showMoney()//1000000
console.log(son1.friends)//["111", "222", "333"]
let son2 = new Son(1000000000, 39)
son2.showMoney()//1000000000
son2.showAge()//39
console.log(son2.friends)//["111", "222"]
- 也可以通过
instanceof, isPrototypeOf()来识别基于组合继承创建的对象
- 该方式出现的问题:
- 父类构造函数至少执行两次
- 通过继承父类方法使用的构造函数他会创建多余的属性,这些属性没有存在的必要。
原型式继承
//原型式继承
function object(obj) {
// 创建一个临时构造函数
function Foo() {}
// 将传入的对象,作为构造函数的原型
Foo.prototype = obj
// 返回构造函数的实例
return new Foo()
}
let obj = {
name: 'zh',
friends: ['hy','jcl','zxh','hcy']
}
let foo = object(obj)
let foo1 = object(obj)
foo.friends.push('ze')
foo1.name = 'hy'
console.log(foo.friends)//["hy", "jcl", "zxh", "hcy", "ze"]
console.log(foo.name)//zh
console.log(foo1.friends)//["hy", "jcl", "zxh", "hcy", "ze"]
console.log(foo1.name)//hy
- 该继承的本质是对传入的对象执行一次浅复制
- 以一个对象为基础,传入函数中,返回一个实例,然后再根据具体需求对得到的对象加以修饰
- 类似
Object.create()方法
- 或者通过这样
function createObject(o) {
const newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
寄生式继承
- 思路与寄生构造函数和工厂函数类似,即创建一个仅用于封装继承过程的函数,内部对对象做一些增强。
function createObject(obj) {
let o = Object.create(obj)
o.showName = function() {
alert(o.name)
}
return o
}
let obj = {
name: 'zh'
}
let o = createObject(obj)
o.showName()
- 问题:由于做不到函数复用而降低效率
寄生组合式继承
function Parent(money) {
this.money = money
this.friends = ['111', '222']
}
Parent.prototype.showMoney = function() {
console.log(this.money)
}
function Son(money, age) {
Parent.call(this,money)
this.age = age
}
// 定义寄生组合式模型函数
function createObject(subType, superType) {
subType.prototype = Objec.create(superType.prototype)
// 因为constructor是不可枚举属性
Object.defineProperty(subType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: subType
})
}
//调用模型
createObject(Son, Parent)
Son.prototype.showAge = function() {
console.log(this.age)
}
let son1 = new Son(1000000,20)
son1.friends.push('333')
son1.showAge()
son1.showMoney()
console.log(son1.friends)
let son2 = new Son(1000000000, 39)
son2.showMoney()
son2.showAge()
console.log(son2.friends)
- 不必为了指定子类型的原型而调用父类的构造函数,我们只是需要父类的原型的一个副本而已。
总结
-
原型链继承
缺点:
- 引用类型的属性被所有实例共享
- 在创建 子类 的实例时,不能向 父类 传参
- 打印对象时,原型上的属性不能显示
-
借用构造函数(经典继承)
优点:
- 避免了引用类型的属性被所有实例共享,因为每次创建对象,都会给该对象分配属性。
- 可以在 子类 中向 父类 传参 缺点:
- 方法都在构造函数中定义,每次创建实例都会创建一遍方法。
-
组合继承
优点:
- 融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
-
原型式继承
缺点:
- 包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
-
寄生式继承
缺点:
- 跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
-
寄生组合式继承
优点:
- 这种方式的高效率体现它只调用了一次 父类 构造函数,并且因此避免了在 父类的prototype 上面创建不必要的、多余的属性。
- 与此同时,原型链还能保持不变。
- 因此,还能够正常使用 instanceof 和 isPrototypeOf。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式
下面我们来分析一下组合式继承的内存分析
// 父类: 公共属性和方法
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
this.sno = sno
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
var stu1 = new Student("why", 18, ["lilei"], 111)
var stu2 = new Student("kobe", 30, ["james"], 112)
下面我们来分析一下原型链继承的内存分析
// 父类: 公共属性和方法
function Person() {
this.name = "why"
this.friends = []
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student() {
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
// name/sno
var stu1 = new Student()
var stu2 = new Student()
stu1.name = "kobe"