js从入门到精通系列(二)原型和原型链,继承

160 阅读3分钟

原型和原型链的概念是js这门语言的基础,一定要掌握

什么是原型

  • 每一个函数都有一个prototype属性,值是一个对象,称为显示原型,当这个函数被作为构造函数时,其原型对象里保存着其实例对象所需要的公有方法和属性,原型对象还保存着一个constructor属性,这个属性会指向自己的构造函数。
  • 每个实例对象都有一个__proto__属性,被称为隐式原型,会指向创建它的构造函数的prototype(显示原型),当访问一个对象上的属性的时候首先回到对象的私有属性中去寻找,找不到就会到其隐式原型上去寻找。所以实例对象可以直接使用原型上的公有方法和属性,并且可以通过constructor访问构造器

原型,构造函数,实例对象的关系

  • 构造函数通过new操作符去创建一个实例对象
  • 构造函数中包含显示原型prototype
  • 实例对象中包含隐式原型__proto__ 隐式原型指向构造函数的显示原型prototype
  • 原型对象包含constructor属性指向构造函数,还包含一些实例可以访问的公有方法和属性

原型链又是什么

  • 首先要明白一个概念,原型也是一个对象,所以原型也可以作为实例对象拥有自己的隐式原型__proto__
  • 当我们访问对象的属性的时候,如果在其私有属性和原型对象内都找不到该属性,就会到原型对象的__proto__隐式原型中去寻找,如果还找不到就继续往下一层找,这就是原型链的概念,原型链的尽头就是Object.prototype.__proto__ 值是null

继承

继承的方式有哪些

原型链继承

  • 原理:改写原型指向超类型的实例,实现公有属性和私有属性的继承
  • 缺点:不能传递参数,私有属性如果是引用数据类型,会造成修改混乱,因为每一个子级实例修改这个对象都会影响到其它的实例
function Son(){}
function Fa(){
    this.name = "Fa"
}
Fa.prototype.age = 12
Son.prototype = new Fa()
new Son().name // "Fa"
new Son().age // 12

盗用构造函数继承

  • 原理: 在构造函数中通过call去调用超类型的构造函数并将this指向自己
  • 缺点: 不能继承原型上的属性,由于是在构造函数内部完成 所以不能复用
function Fa(){
    this.name = "Fa"
}
function Son(...args){
    Fa.call(this,...args)
}
Fa.prototype.age = 12
new Son().name // "Fa"
new Son().age // undefined

组合继承

  • 原理:结合借用构造函数和原型链继承
  • 缺点:调用了两次超类型的构造函数 所以多出来一些不必要的私有属性,造成额外的性能消耗
function Fa(){
    this.name = "Fa"
}
function Son(...args){
    Fa.call(this,...args)
}
Fa.prototype.age = 12
Son.prototype = new Fa()
new Son().name // "Fa"
new Son().age // 12

原型继承

  • 原理: 改写原型的指向,和原型链继承类似,可以继承公有和私有属性
  • 缺点:同原型链继承
function Factory(obj) {
    // 这里也是直接创建了一个实例对象,也可以采用构造函数的方式
    // function a(){}
    // a.prototype = obj
    // return new a()
    const a = {};
    a.__proto__= obj
    return a
}
const obj = Factory([1,2,3])
obj.length // 3

寄生继承

  • 原理:在原型继承的基础上对子级实例进行增强
  • 缺点:同原型链
function Factory(obj) {
    const a = {};
    a.__proto__= obj
    a.__proto__.getAge = function (){
        return 12
    }
    return a
}
const obj = Factory([1,2,3])
obj.getAge() // 12

寄生组合式继承

  • 原理:结合借用构造函数和寄生继承
  • 缺点:无
  • 寄生继承只继承超类型的原型而不是实例,全程只调用一次父级构造函数,并将constructor指向自己的构造函数
function Fa(){
    this.name = "Fa"
}
function Son(...args){
    Fa.call(this,...args)
}

Son.prototype = Object.create(Fa.prototype)
Son.prototype.constructor = Son

Fa.prototype.age = 12

extends (es6,用于class中)

  • 原理和寄生组合式继承类似,也有一点区别就不讲了