重学JS系列:原型继承

在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的, 所以本文未用 类 实现 构造函数 和 原型

目录

  • 原型
  • example
  • 继承的优缺点

原型的概念:

  • 所有对象都有一个属性 __proto__指向一个对象, 也就是原型
  • 每个对象的原型都可以通过 constructor找到构造函数,构造函数也可以通过 prototype找到原型
  • 所有函数都可以通过__proto__找到 Function对象
  • 所有对象都可以通过__proto__找到Object对象
  • 对象之间通过__proto__连接起来, 这样称之为原型链。当前对象上不存在的属性可以通过原型链一层层往上查找,直至顶层Object对象。
  • 如果还没有 则为 null。根据定义, null没有原型。所以null为原型链的最后一个环节。

尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。

实原型的最重要的概念就是这些了,如果看很多大篇幅介绍原型的文章,会很容易迷糊。


example 常见的两种继承

1.引用类型的属性被所有实例共享。

function Parent() {
    this.names= ['gavin', 'kevin']
}

function Child() {

}

Child.prototype = new Parent()

var Child1 = new Child()

Child1.names.push('yayu')
console.log(Child1.names)   //output: gavin、 kevin、 yayu

var Child2 = new Child()
console.log(Child2.names)   //output: gavin、 kevin、 yayu

2.通过 new操作符 (经典继承) 子对象可以向父对象传递参数

let F = function (a, b) {
   this.a = a;
   this.b = b;
}
F.prototype.getVal = function () {
	console.log(this.a, this.b)
}
let o = new F(1, 2);
console.log(o.a, o.b) //output: 1, 2

3.通过 Object.create继承一个对象

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// 当调用 o.m 时,'this' 指向了 o.

var p = Object.create(o);
// p是一个继承自 o 的对象

p.a = 4; // 创建 p 的自身属性 'a'
console.log(p.m()); // 5
// 调用 p.m 时,'this' 指向了 p
// 又因为 p 继承了 o 的 m 函数
// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a'

继承的优缺点

优点:

  • 原型中的方法,子对象都可以访问。

问题:

  • 1、多个实例对引用类型的操作会被篡改
  • 2、子类型的原型上的 constructor 属性被重写了
  • 3、给子类型原型添加属性和方法必须在替换原型之后

问题一

原型链继承方案中,原型实际上会变成另一个类型的实例,如下代码,Cat.prototype变成了 Animal的一个实例,所以 Animal的实例属性 names就变成了 Cat.prototype的属性。
而原型属性上的引用类型值会被所有实例共享,所以多个实例对引用类型的操作会被篡改。如下代码,改变了 instance1.names后影响了 instance2

function Animal(){
  this.names = ["cat", "dog"];
}
function Cat(){}

Cat.prototype = new Animal();

var instance1 = new Cat();
instance1.names.push("tiger");
console.log(instance1.names); // ["cat", "dog", "tiger"]

var instance2 = new Cat(); 
console.log(instance2.names); // ["cat", "dog", "tiger"]

问题二、

子类型原型上的 constructor 属性被重写了,执行 Cat.prototype = new Animal()后原型被覆盖, Cat.prototype上丢失了 constructor属性,Cat.prototype指向了 Animal.prototype,而Animal.prototype.constructor指向了Animal,所以Cat.prototype.constructor指向了 Animal
在这里插入图片描述

// 新增,重写 Cat.prototype 的 constructor 属性,指向自己的构造函数 Cat
Cat.prototype.constructor = Cat; 

我们可以看到,虽然 已经有 value 属性,但是 constructor现在是指向 Cat
在这里插入图片描述

问题三、

给子类型原型添加属性和方法必须在替换原型之后,因为子类型的原型会被覆盖。

// 木易杨
function Animal() {
    this.value = 'animal';
}

Animal.prototype.run = function() {
    return this.value + ' is runing';
}

function Cat() {}
Cat.prototype = new Animal(); 
Cat.prototype.constructor = Cat; 

// 新增  在 constructor 指向 cat 之后
Cat.prototype.getValue = function() {
  return this.value;
}

var instance = new Cat();
instance.value = 'cat'; 
console.log(instance.getValue()); // cat

哦 对了,既然说是顺着原型链往上找,那么自己的方法是优先使用的。所以即使原型顶层有相同的方法,也不会用。所以引入了 继承的第三个概念 多态

多态 即 继承而来的方法 不好用, 那就不用你的, 我自己定义一个。



小结

  • 每个对象都拥有一个原型对象,通过 __proto__指针指向一个原型,并从中 继承方法和属性,同时原型对象也可能拥有原型, 这样一层一层, 最终指向 null,这种关系被称为 原型链
  • 当访问一个对象的属性 || 方法 时,它会以自身为起点,向上遍历整条 原型链 知道找到 匹配的属性 || 方法 / 到达原型链的尾端 null
  • 原型链的构建依赖于 __proto__,一层一层最终链接到 null
  • instanceof 原理就是一层一层查找 __proto__,如果和 constructor.prototype相同则返回 true,如果一直没有查找成功则返回 false
  • 原型链接继承的本质是 重写原型对象,代之以一个新类型的实例
参考
掘金 木易杨说: https://juejin.im/post/5ca9cebb6fb9a05e505c5f81
掘进 yck:     https://juejin.im/post/5c99cb69e51d4556a91f87af
web docs:    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章