js 的继承

1 继承方式

参考链接1
参考链接2

1.1 原型链继承

需要两个构造函数来完成一个原型链继承

基本原理:使用类似作用域的原型链,进行继承查找

 // SuperType 构造函数称为超类
 function SuperType (){
     this.name='super';
     this.friend=[];
     this.property = true; 
}
SuperType.prototype.getName=function(){
    return this.name;
}
SuperType.prototype.getSuperValue = function(){
 return this.property;
}; 
// SubType 构造函数称为子类
function SubType(name,age){
   this.name=name;
   this.age=age;
   this.subproperty = false; 
}
// 继承了superType
SubType.prototype=new SuperType();
SubType.prototype.constrcutor=SubType;
SubType.prototype.getAge=function(){
    return this.age;
}
SubType.prototype.getSubValue = function (){
 return this.subproperty;
}; 
var child = new SubType('shiny',12);
console.log(child.getName())//shiny
console.log(child.getAge())//12

定义两个构造函数,分别为父类(SuperType)、子类(SubType),为了实现子类能够使用父类的属性(本身和原型上面的属性)。重写子类的原型,让子类的原型指向父类实例,这样子类的构造函数就是父类的实例地址,实现子类可以使用父类的本身和原型上的属性
在这里插入图片描述
优点: 实现简单。

缺点

  1. 不能向父类传递参数。
  2. 引用类型值的原型属性会被所有实例共享。
function Parent() {
    this.likeFood = ['香蕉', '榴莲', '芒果'];  // 引用属性
    this.name="aaa"  // 普通属性
}

Parent.prototype.getName = function () {
    return this.name
}

function Child(name) {
    this.name = name
}
Child.prototype = new Parent()

var nan = new Child('nan')
nan.name="bbbb"
var bei = new Child('bei')

nan.likeFood.push('木瓜')
console.log(nan.likeFood)      // [ '香蕉', '榴莲', '芒果', '木瓜' ]
console.log(bei.likeFood)    // [ '香蕉', '榴莲', '芒果', '木瓜' ]
console.log(nan.name)   // bbbb
console.log(bei.name)   // bei

修改nan对象的值,普通属性不会互相影响,引用类型的属性会互相影响。

1.2 借用构造函数继承

基本原理:子类构造函数内部调用父类构造函数,并传入 this指针。使用call apply方法,通过执行方法修改tihs (上下文),是的父级的this变成子类实例的this,这样每个实例都会得到父类的属性,实现引用属性备份

例子1
// 把父类当中一个函数使用
function SuperType(name){
    this.name=name
    this.friend=['a','b']
}
SuperType.prototype.getFriend=function(){
    return this.firend
}
function SubType(name){
    // 执行父类函数
    SuperType.call(this,name);
}
var child = new SubType('shiny')
var childRed = new SubType('red')
console.log(child.name)//shiny
console.log(childRed.name)//red
// 修改引用类型的属性
child.friend.push('c')
console.log(child.friend)//a,b,c
console.log(childRed.friend)//a,b
// 子类不能调用父类原型的方法
console.log(childRed.getFriend)//undefined


例子2
function Parent(name) {
    this.name=name  // 普通属性
    this.likeFood = ['香蕉', '榴莲', '芒果'];  // 引用属性
}
function Child(name) {
    Parent.call(this,name)
}

Parent.prototype.getName = function () {
    return this.name
}

var nan = new Child('nan')
nan.likeFood.push('木瓜')
var bei = new Child('bei')

console.log(nan.likeFood)  // [ '香蕉', '榴莲', '芒果', '木瓜' ]
console.log(bei.likeFood)  // [ '香蕉', '榴莲', '芒果' ]
console.log(nan.name)   // nan
console.log(bei.name)   // bei
console.log(nan.getName()) // TypeError: nan.getName is not a function  超类的原型定义的方法对于子类是不可使用的,子类的实例只是得到了父类的this绑定的属性

优点

  1. 解决了原型链继承的 引用类型操作问题
  2. 解决了父类传递参数问题

缺点

  1. 超类的原型定义的方法对于子类是不可使用的,子类的实例只是得到了父类的this绑定的属性

使用场景:

  父类中需要一些子类使用共享的引用类型,并且子类可能会操作父类共享的引用类型 但是父类的非this绑定的属性和方法是不可以使用的(放在父类prototype的属性和方法)

1.3 组合继承

基本原理:组合继承(原型链继承+借用构造函数继承)

  1. 使用原型链的继承实现,通过原型查找功能来满足原型链共享方法
  2. 使用借用构造函数方法,使用实例备份父类共享引用类型备份
function Parent(name) {
    this.name=name  // 普通属性
    this.likeFood = ['香蕉', '榴莲', '芒果'];  // 引用属性
}
function Child(name,age) {
    Parent.call(this,name) // 构造函数继承
    this.age=age
}

Parent.prototype.getName = function () {
    return this.name
}

// 原型链继承
Child.prototype=new Parent()
Child.prototype.constructor=Child
Child.prototype.getAge = function(){
    return this.age;
}

var nan = new Child('nan',12)
nan.likeFood.push('木瓜')
var bei = new Child('bei',13)

console.log(nan.likeFood)  // [ '香蕉', '榴莲', '芒果', '木瓜' ]
console.log(bei.likeFood) // [ '香蕉', '榴莲', '芒果' ]
console.log(nan.name)   // nan
console.log(bei.name)   // bei
console.log(nan.getName()) // nan
console.log(nan.getAge()) // 12

优点:

  1. 解决了原型链继承引用类型的实例操作导致引用改变,修改属性不互相影响
  2. 解决了借构造函数继承方式的,父类原型子类实例可以使用(子类可以调用父类原型的方法)

缺点:

  1. 父类的构造函数被实例换了两次 Child.prototype=new Parent() Parent.call(this,name)
  2. 实例会有父类的构造函数的一些this属性、子类的构造函数(prototype)上也有一份实例的上有的属性

使用场景

  得到原型链继承和构造函数继承的优点,是被开发人员认可的一种继承方式,但是也有他的缺点

1.4 原型式继承

基本原理:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

原型式继承,其实就是和原型链继承差别不大,只是省去了构造函数这一部,但是原型式继承也是有缺点的(不能够给备份的对象添加属性)

核心: 我们需要创建一个临时的构造函数,并将作为父类的对象作为构造函数的原型,并返回一个新对象。

// 在object()函数内部,先创建了一个临时性构造函数,然后将传入的对象作为这个构造函数的原型
// 最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象进行了一次浅复制

function object(o){
    function F(){};
    F.prototype=o;
    return new F()
}
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var personShiny = object(person);
var personRed = object(person);
console.log(personShiny.name)//Nicholas
console.log(personRed.name)//Nicholas
personShiny.friends.push('red');
personRed.friends.push('shiny');
console.log(personShiny.friends)// [ 'Shelby', 'Court', 'Van', 'red', 'shiny' ]
console.log(personShiny.friends) // [ 'Shelby', 'Court', 'Van', 'red', 'shiny' ]
console.log(personRed.friends) // [ 'Shelby', 'Court', 'Van, 'red', 'shiny' ]

父类的属性对于子类来说都是共享的。所以,如果我们只是想一个对象和另一个对象保持一致,这将是不二之选。

ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与 object()方法的行为相同。

function object(o){
    function F(){};
    F.prototype=o;
    return new F()
}
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var personShiny = Object.create(person);
var personRed = Object.create(person);
console.log(personShiny.name)//Nicholas
console.log(personRed.name)//Nicholas
personShiny.friends.push('red');
personRed.friends.push('shiny');
console.log(personShiny.friends)// [ 'Shelby', 'Court', 'Van', 'red', 'shiny' ]
console.log(personShiny.friends) // [ 'Shelby', 'Court', 'Van', 'red', 'shiny' ]
console.log(personShiny.friends)//[ 'Shelby', 'Court', 'Van', 'red', 'shiny' ]

优点:

  1. 项目1再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分

缺点:

  1. 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
    谨慎定义方法,以免定义方法也继承对象原型的方法重名
  2. 谨慎定义方法,以免定义方法也继承对象原型的方法重名
  3. 无法直接给父级构造函数使用参数

使用场景:

  在不使用构造函数的情况下,只想让一个对象与另一个对象保持类似的情况下

1.5 寄生继承

基本原理:备份一个对象,然后给备份的对象进行属性添加,并返回

类似构造函数,通过一个执行方法,里面创建一个对象,为该对象添加属性和方法,然后返回

代码实现:
// 和工厂模式非常类似,创建一个对象,增强一些功能并返回该对象
function createAnother(o){
   var clone = Object(o);
   clone.sayHi=function(){
     console.log('hi')
}
   return clone
}
var person = {
   name:'shiny',
   friends:['a','b']
}
var personShiny = createAnother(person);
console.log(personShiny.sayHi())//Ho

优点:

  1. 再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分
  2. 可以给备份的对象添加一些属性

缺点:

  1. 父类被实例化两次、子类实例和子类的构造函数都有相同的属性

使用场景:

  在考不使用构造函数的情况下实现继承,前面示 范继承模式时使用的 object()函数不是必需的;任何能够返回新对象的函数都适用于此模式

1.6 寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

基本原理子类构造函数内通过call、apply方法进行修改父类构造函数的this和执行父类构造函数,使的子类的实例拥有父类构造函数的一些属性,结合子类的原型修改成父类构造函数的原型,并把父类的原型的constructor指向子类构造函数

 function inheritPrototype({SubType,SuperType}){
      const prototype = Object(SuperType.prototype); //创建对象
      prototype.constrcutor=SubType; //增强对象
      SubType.prototype=prototype; //指定对象
}

这个示类的inheritPrototype()函数实现了寄生组合式继承的最简单形式
函数接收两个参数:子类型构造函数和超类型构造函数
在函数内部,第一步是创建超类型原型的一个副本
第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性
第三步将创建的对象(即副本)赋值给子类型的原型
这样我们调用inheritPrototype函数的语句,就可以替换前面例子中为子类型原型赋值的语句了
function inheritPrototype({SubType,SuperType}){
     const prototype = Object(SuperType.prototype);
     prototype.constrcutor=SubType;
     SubType.prototype=prototype;
 }
function SuperType(name){
    this.name=name;
    this.friends=["red","blue","green"]
}
SuperType.prototype.getName=function(){
    return this.name;
}
function SubType(name,age){
    this.age=age;
    SuperType.call(this,name)
}
inheritPrototype({SubType,SuperType});
SubType.prototype.getAge=function(){
    return this.age
}
var SubTypeShiny = new SubType('Shiny',23);
SubTypeShiny .friends.push('c')
var SubTypeRed = new SubType('Red',21);
SubTypeRed .friends.push('d')
console.log(SubTypeShiny.getName())//Shiny
console.log(SubTypeShiny.getAge())//22
console.log(SubTypeShiny.friends)//[ 'red', 'blue', 'green', 'c' ]
console.log( SubTypeRed.getName())//Red
console.log( SubTypeRed.getAge())//21
console.log( SubTypeRed.friends)//[ 'red', 'blue', 'green', 'd' ]

优点

  1. 在少一次实例化父类的情况下,实现了原型链继承和借用构造函数
  2. 减少了原型链查找的次数(子类直接继承超类的prototype,而不是父类的实例)

组合继承和寄生组合继承的原型图对比

在这里插入图片描述

2 相关知识

2.1 Object.create()

参考链接
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};
const me = Object.create(person); // me.__proto__ === person
me.name = "Matthew"; // name属性被设置在新对象me上,而不是现有对象person上
me.isHuman = true; // 继承的属性可以被重写
me.printIntroduction(); // My name is Matthew. Am I human? true

Object.create(proto[, propertiesObject])

proto必填参数,是新对象的原型对象,如上面代码里新对象me的__proto__指向person。注意,如果这个参数是null,那新对象就彻彻底底是个空对象,没有继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。

var a = Object.create(null);
console.dir(a); // {}
console.log(a.__proto__); // undefined
console.log(a.__proto__ === Object.prototype); // false
console.log(a instanceof Object); // false 没有继承`Object.prototype`上的任何属性和方法,所以原型链上不会出现Object

propertiesObject是可选参数,指定要添加到新对象上的可枚举的属性(即其自定义的属性和方法,可用hasOwnProperty()获取的,而不是原型对象上的)的描述符及相应的属性名称。

var bb = Object.create(null, {
    a: {
        value: 2,
        writable: true,
        configurable: true
    }
});
console.dir(bb); // {a: 2}
console.log(bb.__proto__); // undefined
console.log(bb.__proto__ === Object.prototype); // false
console.log(bb instanceof Object); // false 没有继承`Object.prototype`上的任何属性和方法,所以原型链上不会出现Object

// ----------------------------------------------------------

var cc = Object.create({b: 1}, {
    a: {
        value: 3,
        writable: true,
        configurable: true
    }
});
console.log(cc); // {a: 3}
console.log(cc.hasOwnProperty('a'), cc.hasOwnProperty('b')); // true false 说明第二个参数设置的是新对象自身可枚举的属性
console.log(cc.__proto__); // {b: 1} 新对象cc的__proto__指向{b: 1}
console.log(cc.__proto__ === Object.protorype); // false
console.log(cc instanceof Object); // true cc是对象,原型链上肯定会出现Object

Object.create()创建的对象的原型指向传入的对象。跟字面量和new关键字创建有区别。

自己实现一个Object.create()

Object.mycreate = function(proto, properties) {
    function F() {};
    F.prototype = proto;
    if(properties) {
        Object.defineProperties(F, properties);
    }
    return new F();
}
var hh = Object.mycreate({a: 11}, {mm: {value: 10}});
console.dir(hh);

总结:Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值