了解原型链和继承

136 阅读5分钟

之前写的有关对象的文章——对象

原型和原型链

原型

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性

如果无法在对象本身找到需要的属性,就会继续访问对象的prototype值

原型与实例

  • 每个构造函数都有一个prototype属性,这个属性指向一个原型对象
  • 每个原型对象都包含一个指向构造函数的指针constructor
  • 实例都包含一个指向原型对象的指针__proto__
  • 对象的[[prototype]]属性指向它的原型对象,可以通过obj._proto_或者Object.getPrototypeOf(obj)访问

对象体系结构图

  • Function.prototype === 被创建函数.__proto__;
  • 函数.prototype === 被创建对象.__proto__;
  • Object.prototype === 任意函数.prototype.__proto__;
function People(){}
var p = new People();
p.__proto__ === People.prototype; //true
People.__proto__ === Function.prototype; // true People是Function创造的,任何函数都是Function创造的
People.prototype._proto_ === Object.prototype;// true 基本的对象都是Object创建的

// 鸡生蛋蛋生鸡
Function.prototype === Function.__proto__;//true function本身也是个函数。
Function.prototype === Object.__proto__;//true object是个函数
Function.prototype.__proto__ === Object.prototype;//true

Function.__proto__ === Object.prototype;//false
Function.__proto__.__proto__ === Object.prototype;//true
Function.prototype.__proto__ === Object.prototype;//true

确定原型与实例的关系

instanceof

检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

A instanceof B。如果函数B在对象A的原型链 (prototype chain) 中被发现,那么instanceof操作符将返回true,否则返回false.

Object instanceof Function;//true
Function.__proto__.__proto__ === Object.prototype;//true

Function instanceof Object;//true
Object.__proto__ === Function.prototype;//true

Function instanceof Function;//true
Function.__proto__ === Function.prototype;//true

Object instanceof Object;//false

isPrototypeOf()

检查一个对象是否存在于另一个对象的原型链上

Object.prototype.isPrototypeOf(Function);//true

继承

原型链继承

利用原型让一个引用类型继承另一个引用类型的属性和方法

原型链作为实现继承的主要方法

function SuperType(){
  this.prototype = true
}
SuperType.prototype.getSuperValue = function(){
  return this.prototype
}
function SubType(){
  this.Subprototype = false
}
SubType.prototype = new SuperType()
console.log(SubType.prototype instanceof SuperType) //true
//这是赋值,SubType.prototype是Super的实例
//subType继承了superType,通过创建SuperType实例,将该实例赋给SubType.prototype实现的
console.log(SubType.prototype)

//给原型添加新方法一定要在替换原型的语句之后
SubType.prototype.getSubValue = function(){
  return this.Subprototype
}
//通过原型链实现继承时,不能使用对象字面量创建原型方法,会导致原型链重写
// SubType.prototype = {
//	getSubValue : function(){
// 		return this.Subprototype
//       }
//	}

var instance = new SubType()
//instance.__proto__ == SubType.prototype
console.log(instance)
console.log(instance.__proto__ == SubType.prototype)
console.log(instance.getSuperValue())
//instance继承了SubType,SubType继承了SuperType,SuperType继承了Object

优点:

  • 超类新增原型方法/属性,子类都能访问到
  • 实例是子类的实例,也是父类的实例

缺点:

function SuperType(){
  this.colors = ["red","blue","green"]
}
function SubType(){  
}
SubType.prototype = new SuperType()
//SubType的所有实例都会共享colors属性
SubType.prototype.colors //["red", "blue", "green"]

var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)//["red", "blue", "green", "black"]

var instance2 = new SubType()
console.log(instance2.colors)//["red", "blue", "green", "black"]

//引用类型值的原型属性修改
SubType.prototype.colors//["red", "blue", "green", "black"]
  • 无法实现多继承
  • 想要为子类添加原型方法,必选在创建超类实例赋给子类原型SubType.prototype = new SuperType()之后
  • 在通过原型链实现继承时,原型实际上会成为另一个类型的实例**(引用类型值的原型属性会被所有实例共享)**
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数(没有办法在不影响所有对象实例的情况下,向超类传递参数)

借用构造函数实现继承

又叫伪造对象,或者经典继承。

在子类构造函数内部通过call()/apply()方法调用超类构造函数,解决了原型中包含引用类型值所带来的问题

function SuperType(){
  this.colors = ["red","blue","green"]
}
function SubType(){
//继承了SuperType
  SuperType.call(this)
}
SubType.prototype = new SuperType()

var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors)//["red", "blue", "green", "black"]

var instance2 = new SubType()
console.log(instance2.colors)//["red", "blue", "green"]
//多继承

优点:

  • 在子类型构造函数中向超类型构造函数传递参数
  • 可以实现多继承
function SuperType(name){
  this.name = name
}
function SuperBType(sex){
  this.sex = sex
}
function SubType(){
  //继承了SuperType,传递了参数
  SuperType.call(this,'Bob')
  //通过这一步可以实现多继承
  SuperBType.call(this,'male')
  //添加在子类型中应该定义的属性
  this.age = 18
}

var instance = new SubType()
console.log(instance.name)//"Bob"
console.log(instance.sex)//"Jane"
console.log(instance.age)//18

缺点:

  • 方法都在构造函数中定义,无法实现函数复用(不同实例上同名函数不相等)
  • 实例是子类的实例
function SuperType(name){
  this.name = name
}

function SubType(){
  SuperType.call(this,'Bob')
  this.age = 18
}

var instance = new SubType()
console.log(instance)
console.log(instance instanceof SuperType)//false
console.log(instance instanceof SubType)//true
//由此可得instance是子类的实例,不是超类的实例

组合继承(常用)

伪经典继承,将原型链和借用构造函数技术组合到一块

  • 原型链实现对原型属性和方法的继承
  • 借用构造函数实现对实例的继承
function SuperType(name){
  this.name = name
  this.colors = ["red","blue","white"]
}

SuperType.prototype.sayName = function(){
  console.log(this.name)
}

function SubType(name,age){
  SuperType.call(this,name)
  this.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function(){
  console.log(this.age)
}

var instance1 = new SubType('Jane',18)
console.log(instance1)
instance1.colors.push("green")
console.log(instance1)
instance1.sayName()//"Jane"
instance1.sayAge()//18

var instance2 = new SubType('Bob',20)
console.log(instance2)
instance2.sayName()//"Bob"
instance2.sayAge()//20

优点:

  • 可以继承实例属性/方法,也可以继承原型属性/方法
  • 函数可复用
  • 子类构造函数可向超类构造函数传参
  • 可以实现多继承

缺点:

  • 无论什么情况下,都会调用两次超类型构造函数

原型式继承

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

function object(o){
  function F(){}
    F.prototype = o
    return new F()
}

var person = {
  name: 'Bob',
  colors : ["red","blue","white"]
}
//object对传入其中的对象进行了一次浅复制
var instance = object(person)
instance.name = "Jane"
instance.colors.push("green")

console.log(instance.colors)//["red", "blue", "white", "green"]

等同于Object.create()

var person = {
  name: 'Bob',
  colors : ["red","blue","white"]
}

var instance = Object.create(person)
instance.name = "Jane"
instance.colors.push("green")

console.log(instance.colors)//["red", "blue", "white", "green"]

优点:

  • 多继承
  • 不用兴师动众地创建构造函数 缺点:

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象

function createAnother(obj) {
    var clone = Object(obj);
    clone.sayHi = function() {
        console.log('Hi');
    }
    return clone
}

var person = {
  name: 'Bob',
  colors : ["red","blue","white"]
}

var instance = createAnother(person)
instance.name = "Jane"
instance.colors.push("green")
console.log(instance)
console.log(instance.name)//"Jane"
console.log(instance.colors)//["red", "blue", "white", "green"]
instance.sayHi()//Hi

var instance1 = createAnother(person)
console.log(instance1.colors)//["red", "blue", "white", "green"]

优点:

  • 多继承

缺点:

  • 不能实现复用
  • 实例之间会影响

寄生组合式继承(理想类型)

借用构造函数来继承属性,通过原型链的混成形式来继承方法

解决了组合继承的缺点,不用两次调用超类构造函数

function inheritPrototype(subType,superType){
  var obj = Object(superType.prototype)
  obj.constructor = subType
  subType.prototype = obj
}

function SuperType(name){
  this.name = name
  this.colors = ["red","blue","green"]
}

SuperType.prototype.sayName = function(){
  console.log(this.name)
}
function SubType(name,age){
  SuperType.call(this,name)
  this.age = age
}
inheritPrototype(SubType,SuperType)

SubType.prototype.sayAge = function(){
  console.log(this.age)
}

var instance = new SubType('Bob',18)
console.log(instance)
instance.colors.push("white")
console.log(instance.colors)//["red", "blue", "green", "white"]

var instance1 = new SubType()
console.log(instance1.colors)//["red", "blue", "green"]