原型链继承
特点:
- 基本思想:利用原型链让子类继承父类的属性和方法
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