★ 原型、原型链、继承

2019-12-18  本文已影响0人  行走的蛋白质




构造函数、原型与实例之间的关系

所以三者的关系是:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针 __ proto __。也就是说实例可以通过内部指针访问到原型对象,原型对象通过 constructor 指针找到构造函数。

如下示例:

function Dog(name) {
    this.name = name
    this.type = 'Dog'
}
Dog.prototype.speak = function() {
    console.log('wangwangwang')
}
var doggie = new Dog('taidi')
doggie.speak() // wangwangwang

console.log(Dog.prototype.constructor === Dog) //true
console.log(doggie.__proto__.constructor === Dog) // true
doggie.prototype // undefined 只有函数对象才有 prototype 属性

如下图所示:

原型-构造函数-实例.png
原型链

前面我们说到所有的实例都有一个内部指针 __ proto __ 指向它的原型对象,并且可以访问原型对象上的所有属性和方法。doggie 实例指向了 Dog 的原型对象,可以访问 Dog 原型对象上的所有属性和方法;如果 Dog 原型对象变成了某一个类的实例 some1,这个实例又会指向一个新的原型对象 Some1,那么此时 doggie 就能访问 some1 的实例属性和 Some1 原型对象上的所有属性和方法。同理新的原型对象 Some1 恰巧又是另外一个对象的实例 some2,这个实例 some2 又会指向新的原型对象 Some2,那么此时 doggie 就能访问 some2 的实例属性和 Some2 原型对象上的所有属性和方法。

// 定义一个 Animal 函数作为 Dog 的父类
function Animal() {
    this.superType = 'Animal'   
}

Animal.prototype.superSpeak = function() {
    console.log(this.superType)
}

function Dog(name) {
    this.name = name
    this.type = 'Dog'
}

// 改变 Dog 的 prototype 指针指向 Animal 的实例
Dog.prototype = new Animal()
// 等同于下面注释部分
/*
    var animal = new Animal()
    Dog.prototype = animal
*/

Dog.prototype.speak = function () {
    console.log(this.type)
}

var doggie = new Dog('taidi')
doggie.superSpeak() // Animal

console.log(doggie.__proto__.constructor === Animal) // true

以上代码,首先定义了一个 Animal 构造函数,通过new Animal()得到实例,会包含一个实例属性 superType 和一个原型属性 superSpeak。另外又定义了一个Dog构造函数。然后情况发生变化,代码中加粗那一行,将Dog的原型对象覆盖成了 animal 实例。当 doggie 去访问superSpeak属性时,js会先在doggie的实例属性中查找,发现找不到,然后,js就会去doggie 的原型对象上去找,doggie的原型对象已经被我们改成了一个animal实例,那就是去animal实例上去找。先找animal的实例属性,发现还是没有 superSpeack, 最后去 animal 的原型对象上去找,诶,这才找到。

流程如下图所示:

原型链.png

这就说明我们可以通过原型链的方式,实现 Dog 继承 Animal 的所有属性和方法。

总结:当重写了 Dog.prototype 指向的原型对象后,实例的内部指针也发生了改变,指向了新的原型对象,然后就能实现类与类之间的继承了。
MDN详解-继承与原型链请戳这里

查找性能
new 一个对象发生了什么?
function _new(fun, ...args) {
    const newObj = Object.create(fun.prototype)
    const result = fun.apply(newObj, args)
    return typeof result == 'object' ? result : newObj
}
继承

父类代码如下:

// 定义一个动物类
function Animal(name = 'Animal') {
    // 属性
    this.name = name
    // 实例方法
    this.sleep = function () {
        console.log(`${this.name} is sleeping`)
    }
}
// 原型方法
Animal.prototype.eat = function(food) {
    console.log(`${this.name} is eating ${food}`)
}
function Cat() {

}
Cat.prototype = new Animal()
Cat.prototype.name = 'cat'

var cat = new Cat()
console.log(cat.name) // cat
console.log(cat.eat('fish')) // cat is eating fish
console.log(cat.sleep()) // cat is sleeping
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码: 示例1)
  4. 创建子类实例时,无法向父类构造函数传参
function Cat(name = 'Tom') {
    Animal.call(this)
}

var cat = new Cat()
console.log(cat.name) // Tom
// console.log(cat.eat('fish')) // 报错找不到 eat 方法
console.log(cat.sleep()) // Tom is sleeping
console.log(cat instanceof Animal) // false
console.log(cat instanceof Cat) // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Cat(name = 'Tom') {
    var animal = new Animal()
    animal.name = name
    return animal
}

var cat = new Cat()
console.log(cat.name) // Tom
console.log(cat.eat('fish')) // Tom is eating fish
console.log(cat.sleep()) // Tom is sleeping
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承
function Cat(name = 'Tom') {
    var animal = new Animal()
    for(var o in animal) {
        Cat.prototype[o] = animal[o]
    }
    Cat.prototype.name = name
}

var cat = new Cat()
console.log(cat.name) // Tom
console.log(cat.eat('fish')) // Tom is eating fish
console.log(cat.sleep()) // Tom is sleeping
console.log(cat instanceof Animal) // false
console.log(cat instanceof Cat) // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
function Cat(name = 'Tom') {
    Animal.call(this)
    this.name = name
}
Cat.prototype = new Animal()

var cat = new Cat()
console.log(cat.name) // Tom
console.log(cat.eat('fish')) // Tom is eating fish
console.log(cat.sleep()) // Tom is sleeping
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
function Cat(name = 'Tom') {
    Animal.call(this)
    this.name = name
}
(function() {
    // 创建一个没有实例方法的类
    var Trans = function () {}
    Trans.prototype = new Animal()
    //将实例作为子类的原型
    Cat.prototype = new Trans()
})()

var cat = new Cat()
console.log(cat.name) // Tom
console.log(cat.eat('fish')) // Tom is eating fish
console.log(cat.sleep()) // Tom is sleeping
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

原型题

function fun() {
    this.a = 0
    this.b = function() {
        console.log(this.a)
    }
}
fun.prototype = {
    b: function() {
        this.a = 20
        console.log(this.a)
    },
    c: function() {
        this.a = 30
        console.log(this.a)
    }
}
var my_fun = new fun()
my_fun.b() // 0
my_fun.c() // 30
function Fn() {
    var n = 10
    this.m = 20
    this.aa = function() {
        console.log(this.m)
    }
}
Fn.prototype.bb = function() {
    console.log(this.n)
}
var f1 = new Fn
Fn.prototype = {
    aa: function() {
        console.log(this.m + 10)
    }
}
var f2 = new Fn
console.log(f1.constructor) // Fn
console.log(f2.constructor) // Object
f1.bb() // undefined
f1.aa() // 20
f2.bb // TypeError
f2.aa() // 20
f2.__proto__.aa() // NaN
上一篇 下一篇

猜你喜欢

热点阅读