共计 8616 个字符,预计需要花费 22 分钟才能阅读完成。
原型链继承
特点:
- 基本思想:利用原型链让子类继承父类的属性和方法
function Parent(name,country) {
this.name = name
this.sex = 'male'
this.country = country
this.identities = ["philosopher", "scientist", "artist"]
this.sayName = function() {return 'I am a' + this.name}
}
Parent.prototype.honor = 'Champion'
Parent.prototype.getPersonValue = function() {return this.name}
function Child(name,city) {
this.name = name
this.age = 24
this.city = city
}
// Child 的原型指向 Parent 的实例
// 意味着 Child 的原型拥有 Parent 的实例的全部属性和方法
// 也意味着 Child 的原型的__proto__属性指向 Parent 的原型
// 所以最后 根据原型链的规则,Child 的实例拥有 Parent 实例的全部属性和方法,以及 Parent 的原型的全部属性和方法
Child.prototype = new Parent('father')
// 此时 Child 的原型的 constructor 指向为 Parent
// 所以要修复 Child 的原型的 constructor 指向为 Child
// 目的是 防止原型链的混乱
Child.prototype.constructor = Child
Child.prototype.getChildValue = function() {return this.city}
Child.prototype.getParentValue = function() {return this.name}
var parent = new Parent('father')
var child1 = new Child('son','BeiJing')
var child2 = new Child('son','BeiJing')
child1.identities.push('doctor')
console.log(child1.name) //son
console.log(child1.sex) //male
console.log(child1.country) //undefined
// 所有 Child 的实例共享 identities 属性
console.log(child1.identities) //["philosopher", "scientist", "artist", "doctor"]
console.log(child2.identities) //["philosopher", "scientist", "artist", "doctor"]
console.log(child1.sayName()) //I am a son
console.log(child1.age) //24
console.log(child1.city) //BeiJing
console.log(child1.honor) //Champion
// 子类原型重写父类原型方法,子类实例调用该方法会覆盖父类原型的这个方法,// 但是父类实例调用该方法还是父类原型的这个方法
console.log('父类实例调用输出:' + parent.getParentValue()) // 父类实例调用输出:father
console.log('子类实例调用输出:' + child1.getParentValue()) // 子类实例调用输出:son
console.log(child1.getChildValue()) //BeiJing
缺点:
- 根据 child.country 的输出的值是 undefined 可知,根据 原型链继承 原理,创建子类实例时,不能向父类的构造函数传递参数
- Child.prototype.getChildValue 和 Child.prototype.getParentValue 必须是写在 Child.prototype = new Parent(‘father’) 之后,不然就会被报错
Q1:为什么要写 Child.prototype.constructor = Child?不写,好像对输出结果没影响,那为什么呢?
为什么要修复子类原型的 constructor 的指向
https://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor#
借用构造函数式继承
特点
- 基本思想:子类构造函数的内部用超类构造函数,通过 call,apply 改变对象的 this 来继承
function Parent(name,country) {
this.name = name
this.sex = 'male'
this.country = country
this.identities = ["philosopher", "scientist", "artist"]
this.sayName = function() {return 'I am a' + this.name}
}
Parent.prototype.honor = 'Champion'
Parent.prototype.getParentValue = function() {return this.name}
function Child(city) {
// 通过 call,apply 改变对象的 this 来继承
Parent.call(this,'father')
this.age = 24
this.city = city
}
Child.prototype.getChildValue = function() {return this.city}
var child = new Child('BeiJing')
child.identities.push('doctor')
console.log(child.name) //father
console.log(child.sex) //male
console.log(child.country) //undefined
console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]
console.log(child.sayName()) //I am a father
console.log(child.age) //24
console.log(child.city) //BeiJing
console.log(child.getChildValue()) //BeiJing
console.log(child.honor) //Uncaught TypeError: child.honor is not a function
console.log(child.getParentValue()) //Uncaught TypeError: child.getParentValue is not a function
分析:
相当于 Parent 这个构造函数在 Child 构造函数中执行了一遍,并且将 Parent 构造函数的 this 绑定的变量都切换到 Child 构造函数上,这样就克服了第一种方式带来的问题
缺点:
- 虽然解决了 原型链继承中不能传值的问题,子类实例无法复用父类原型对象的方法和属性
组合式继承
特点:
- 基本思想:既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
function Parent(name,country) {
this.name = name
this.sex = 'male'
this.country = country
this.identities = ["philosopher", "scientist", "artist"]
this.sayName = function() {return 'I am a' + this.name}
}
Parent.prototype.honor = 'Champion'
Parent.prototype.getParentValue = function() {return this.name}
function Child(city) {
// 通过 call,apply 改变对象的 this 来继承
// 第二次调用父类构造函数,为了获取父类构造函数上的属性
Parent.call(this,'father')
this.age = 24
this.city = city
}
// Child 的原型指向 Parent 的实例
// 意味着 Child 的原型拥有 Parent 的实例的全部属性和方法
// 也意味着 Child 的原型的__proto__属性指向 Parent 的原型
// 所以最后 根据原型链的规则,Child 的实例拥有 Parent 实例的全部属性和方法,以及 Parent 的原型的全部属性和方法
// 第一次调用父类构造函,为了获取子类原型获取父类原型上的方法
Child.prototype = new Parent()
// 此时 Child 的原型的 constructor 指向为 Parent
// 所以要修复 Child 的原型的 constructor 指向为 Child
// 目的是 防止原型链的混乱
Child.prototype.constructor = Child
Child.prototype.getChildValue = function() {return this.city}
var child = new Child('BeiJing')
child.identities.push('doctor')
console.log(child.name) //father
console.log(child.sex) //male
console.log(child.country) //undefined
console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]
console.log(child.sayName()) //I am a father
console.log(child.age) //24
console.log(child.city) //BeiJing
console.log(child.getChildValue()) //BeiJing
console.log(child.honor) //Champion
console.log(child.getParentValue()) //father
缺点:
- 组合是继承是 JS 最常用的继承模式,但组合式集成使用过程中会把父类构造函数被调用两次:第一次创建父类实例执行子类原型时候;第二次在子类构造函数内部执行时候。如果父类构造函数属性极多,会导致速度减慢
原型式继承
特点:
- 背景:道格拉斯·克罗克福德 2006 提出的一种继承方式
- 基本思想:基于一个对象,把这个对象作为原型,再根据需求将得到的对象进行修改
(借助原型基于已有的对象创建新对象,同时还不必因此创建自定义类型) - 使用场景:在没有必要大量地创建构造函数,只想让一个对象与另一个对象保持类似情况下使用
// 如果传入一个参数时,这个函数的 object(o) 和 Object.creat(o) 效果一样
function object(o){
// 创建一个临时构造函数 F
function F(){}
// 构造函数 F 的原型对象指向传入的对象
F.prototype = o
// 返回一个临时构造函数为 F 的对象
return new F()}
var person = {
name: 'xiaoming',
firends: ['lisi','zhangshan']
}
// 从返回值看,实际上 object(person) => new F()
// 意味着 var anotherPerson => new F()
// 意味着 anotherPerson.__proto__ => F.prototype
// 又因为 F.prototype = o
// 意味着 F.prototype => person
// 意味着 anotherPerson.__proto__ => person
// 意味着 anotherPerson 拥有对象 person 的方法和属性
var anotherPerson = object(person)
anotherPerson.name = 'yaoming'
anotherPerson.sayName = function() {return this.name}
anotherPerson.firends.push('wangwu')
var yetAnotherPerson = object(person)
console.log(anotherPerson.name) //yaoming
console.log(anotherPerson.firends) //["lisi", "zhangshan", "wangwu"]
console.log(anotherPerson.sayName()) //yaoming
console.log(yetAnotherPerson.name) //xiaoming
console.log(yetAnotherPerson.firends) //["lisi", "zhangshan", "wangwu"]
console.log(yetAnotherPerson.sayName()) //Uncaught TypeError: yetAnotherPerson.sayName is not a function
// ECMAScript 5 有个新的规范写法:// 如果传入一个参数,object() 和 Object.create() 效果是一样的,由此文中代码可转变为:// var anotherPerson = object(person) => var anotherPerson = Object.create(person)
Q1:为什么 yetAnotherPerson.name 为 xiaoming 而不是 yaoming?
因为 anotherPerson 实例自身添加 name 属性的值,但是没有修改 anotherPerson 实例(notherPerson.proto)原型上的 name 的值,所以 yetAnotherPerson 实例的 name 还是 xiaoming
Q2:为什么 anotherPerson.firends.push(‘wangwu’),yetAnotherPerson.firends 输出的结果跟 anotherPerson.firends 一样?
因为 anotherPerson 实例先从自身去查找属性,发现没有找到,然后根据继承关系往上找,发现在原型有这个属性,后给属性追加值,又因为 anotherPerson 实例和 yetAnotherPerson 实例指向同一个原型,所以当 anotherPerson 实例修改后,yetAnotherPerson 实例调用也发生变化
Q3:为什么 yetAnotherPerson.sayName() 会报错?
因为 anotherPerson 实例自身添加 sayName 方法,但是不是给 anotherPerson 实例(notherPerson.proto)原型上添加 sayName 方法,所以 yetAnotherPerson.sayName()会报错
缺点:
- 引用类型的属性始终都是共享相应的值,多个实例对引用类型进行操作就会把值修改
寄生式继承
特点:
- 基本思想:创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象
- 使用场景:如果考虑的对象不是自定义类型和构造函数情况下,寄生式继承还是有用的,任何能够返回新对象的函数都适用于此模式。但是使用寄生式继承给对象添加函数,由于不能做到函数复用而降低效率,这一点跟构造函数式继承的效果一样
// 如果传入一个参数时,这个函数的 object(o) 和 Object.creat(o) 效果一样
function object(o){
// 创建一个临时构造函数 F
function F(){}
// 构造函数 F 的原型对象指向传入的对象
F.prototype = o
// 返回一个临时构造函数为 F 的对象
return new F()}
var person = {
name: 'xiaoming',
firends: ['lisi','zhangshan']
}
function createAnother(o){
// 通过调用函数创建一个新对象
var clone = object(o)
// 以为对象新增方法的方式增强对象
clone.sayHi = function(){return this.name}
// 返回这个对象
return clone
}
var anotherPerson = createAnother(person)
var yetAnotherPerson = createAnother(person)
// 具有实例原型的 person 的所有属性和方法,也可以有自己的方法
console.log(anotherPerson.firends) //["lisi", "zhangshan"]
console.log(anotherPerson.sayHi()) //xiaoming
console.log(yetAnotherPerson.firends) //["lisi", "zhangshan"]
console.log(yetAnotherPerson.sayHi()) //xiaoming
// ECMAScript 5 有个新的规范写法:// 如果传入一个参数,object() 和 Object.create() 效果是一样的,由此文中代码可转变为:// var clone = object(o) => var clone = Object.create(o)
寄生组合式继承
特点:
- 背景:组合继承是 JS 最常用的继承模式,因为子类构造函数的原型上拥有了父类构造函数的实例属性,所以在原型连上出现多一份属性,造成内存消耗问题。无论什么情况下,都会去调用两次父类构造函数。寄生组合继承就是为了降低调用父类构造函数的内存开销
- 基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了指定的子类型的原型而调用超类的构造函数。换句话说,用一个 F 空的构造函数去取代执行了 Parent 这个构造函数
- 区别:寄生组合式继承 和 组合式继承区别在于,不在一次实例的调用中执行两次父类的构造函数
function inheritPrototype(Child,Parent){
// 将新创建的对象(即副本)赋值给子类型的原型
Child.prototype = Object.create(Parent.prototype)
// 修复子类的原型的 constructor 的指向,避免原型链混乱
Child.prototype.constructor = Child
}
function Parent(name,country) {
this.name = name
this.sex = 'male'
this.country = country
this.identities = ["philosopher", "scientist", "artist"]
this.sayName = function() {return 'I am a' + this.name}
}
Parent.prototype.honor = 'Champion'
Parent.prototype.getParentValue = function(){return this.age}
function Child(name,city) {
// 通过 call,apply 改变对象的 this 来继承
Parent.call(this,name)
this.age = 24
this.city = city
}
inheritPrototype(Child,Parent)
Child.prototype.getChildValue = function() {return this.name}
var child = new Child('father','Beijing')
child.identities.push('doctor')
console.log(child.name) //father
console.log(child.sex) //male
console.log(child.country) //undefined
console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]
console.log(child.sayName()) //I am a father
console.log(child.age) //24
console.log(child.city) //BeiJing
console.log(child.getChildValue()) //father
console.log(child.honor) //Champion
console.log(child.getParentValue()) //24
console.log(child.getChildValue()) //father