在了解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
这个是判断后者是否出现在该实例的原型链上
b. 通过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"
网络异常,图片无法展示
|