js 原型链和继承

439 阅读4分钟

原型链

    首先,js数据类型分为基本类型和引用类型,引用类型又包含对象、数组、函数,这里的原型链和原型主要针对的就是引用类型。引用类型我们将对象和数组称为普通对象,函数称为函数对象。

    在js中,每个对象包括普通对象和函数对象都有__proto__属性,它指向的是原型对象,而函数对象还有自己特有的prototype属性,它指向的生成它本身这个构造函数的原型对象。

                 

我们知道如果我们去new一个构造函数生成一个实例对象时,这时候原型链就已经形成了即是

          实例对象.__proto__ === 构造函数.prototype ===原型对象

接下来有张关于原型链的神图,如果能够理解肯定能完全理解原型链了


如图,f1、f2是Foo的实例,它的__proto__指向Foo的prototype也就是原型对象,原型对象的constructor指向的是Foo,然后又出现了一个公式就是

                f1.__proto__.constructor===Foo.prototype.constructor === Foo

原型对象的__proto__指向的是Object.prototype,Object.prototype.__proto__指向的是null。没错如果从实例对象出发去原型链上查找最终会找到null,看起来是不是很绕,你肯定会有个疑问,原型链有什么用?

原型链的作用就是当我们去获取对象的属性或者方法的时候,如果它的自身上是没得,那么便会沿着它的原型链上去找,如果能找到就调用,如果直到找到null还没找到,就会返回undefined。

那么现在我们对原型链也已经有了较深的理解下面要记住几个特殊的例子

  1. Function.__proto__ === Function.prototype
  2. Function.prototype.__proto__ === Object.prototype
  3. Object.__proto__ === Function.prototype
  4. Object.prototype.__proto__ === null
 好了原型链部分就结束了,下面就是与它息息相关的继承

继承

       接下来我们使用Person子类新建p1实例,Person上有自己的属性和run方法去继承School父类,School父类也有自己的属性和方法。

1.原型链继承

function Person(name, age,classes) {
    this.name = name
    this.age = age
}
Person.prototype.run = function () {
    console.log(this.name + '--------run')
}
function School(userClass) {
    this.userClass = userClass
}
School.prototype.getClass = function () {
    console.log(this.userClass + '---------班级')
    console.log(this.name)
}
Person.prototype = new School('14班')  //继承代码
Person.prototype.constructor = Person  //继承代码
let p1 = new Person('pepsi', '23')
console.dir(p1)
p1.getClass()

原型链继承通过Person.prototype原型对象去实例School方法达到原型链挂载上去,但本身p1.__proto__ 是指向Person.prototype,现在它们统一指向School.prototype,Person.prototype.constructor也指向了School,所以为了维持它的原型链我们重新让它指向Person子类。

优点:

  1. 可以使用公有属性和方法

缺点:

  1. 不能往父类传参,就不能有私有属性,如果是父类公共属性,实例更改大家都会更改
  2. p1.__proto__ !== Person.prototype,原型链被破坏

2.构造函数继承

function Person(name, age,classes) {
    this.name = name
    this.age = age
    School.call(this,classes) //继承代码
}
Person.prototype.run = function () {
    console.log(this.name + '--------run')
}
function School(userClass) {
    this.userClass = userClass
}
School.prototype.getClass = function () {
    console.log(this.userClass + '---------班级')
    console.log(this.name)
}
let p1 = new Person('pepsi', '23', '5班')
console.dir(p1)
// p1.getClass() //报错

优点:

  1. 可以传参,使用父类私有属性
  2. 原型也没有破坏

缺点:

  1. 没有实现原型继承,不能使用父类公有方法,每次创建一个 p1 实例对象时候都需要执行一遍 School函数。

3.组合继承

function Person(name, age, classes) {
    this.name = name
    this.age = age
    School.call(this, classes) //继承代码
}
Person.prototype.run = function () {
    console.log(this.name + '--------run')
}
function School(userClass) {
    this.userClass = userClass
}
School.prototype.getClass = function () {
    console.log(this.userClass + '---------班级')
    console.log(this.name)
}
Person.prototype = new School()  //继承代码
Person.prototype.constructor = Person  //继承代码
let p1 = new Person('pepsi', '23', '5班')
console.log(p1.__proto__ === Person.prototype)
console.dir(p1)
p1.getClass()

优点:

  1. 可以传参,使用父类私有属性,可以使用父类方法
  2. 原型也没有破坏

缺点:

  1. 父类的构造函数被调用了两次。

4.寄生继承

let parent = {
    name: 'parent',
    share: [1, 2, 3],
    log: function() {
        return this.name
    }
}

function create(obj) {
    let clone = Object.create(obj) // 本质上还是 Object.create
    clone.print = function() { // 增加一些属性或方法
        console.log(this.name)
    }
    return clone
}

let child = create(parent)

寄生继承是依托于一个对象而生的一种继承方式,因此称之为寄生。没用到构造函数,只是对象去继承对象。

5.寄生组合式继承

function Person(name, age, classes) {
    this.name = name
    this.age = age
    School.call(this, classes) //继承代码
}
Person.prototype.run = function () {
    console.log(this.name + '--------run')
}
function School(userClass) {
    this.userClass = userClass
}
School.prototype.getClass = function () {
    console.log(this.userClass + '---------班级')
    console.log(this.name)
}
// 寄生组合式继承的核心方法
function inherit(child, parent) {
    // 继承父类的原型
    const p = Object.create(parent.prototype)
    // 重写子类的原型
    child.prototype = p
    // 重写被污染的子类的constructor
    p.constructor = child
}
inherit(Person,School)
let p1 = new Person('pepsi', '23', '5班')
console.log(p1.__proto__ === Person.prototype)
console.dir(p1)
p1.getClass()

优点:

  1. 父类的构造函数只执行了一次!
  2. 子类可以传递动态参数给父类!
  3. 子类继承了父类的属性和方法,同时,属性没有被创建在原型链上,因此多个子类不会共享同一个属性。

缺点:

寄生组合继承是es6 class出来之前的最优解

6.ES6继承

class School {
    constructor(userClass) {
        this.userClass = userClass
    }
    getClass() { 
       console.log(this.userClass + '---------班级')
    }
}
class Person extends School {
    constructor(name, age, classes) {
        super(classes) //继承实现
        this.name = name
        this.age = age
    } 
   run() { 
       console.log(this.name + '--------run')
    }
}
let p1 = new Person('pepsi', '23', '5班')
console.log(p1.__proto__ === Person.prototype)
console.dir(p1)
p1.getClass()

Nice!!!ES6一同江山,就非常的perfect!