原型继承
编程中对象继承,有类继承和原型继承:
- 类继承形式上就是,
extends
关键字,继承之后,子类就会拥有父类的属性和方法,如下:
// 以下是 ES6 class 语法,语法上同类继承一样,但实际上仍然是原型继承
// 但可以说明类继承的套路
class Animal {
constructor (x, y) {
this.x = x;
this.y = y;
}
run () {
console.log('running')
}
}
class Dog extends Animal {
constructor (x, y, z) {
super(x, y)
this.z = z;
}
}
const dog1 = new Dog(1, 2, 3);
dog1.run();
console.log(dog1.x);
- 原型继承则是另外一种形式,要从父类拿到属性或者方法,关键是设置构造函数的
prototype
属性, 如下:
// 构造函数 Animal
function Animal(x, y) {
this.x = x;
this.y = y
}
Animal.prototype.run = function () {
console.log('running')
}
// 构造函数 Dog
function Dog (x, y, z) {
Animal.apply(this, [x, y]);
this.z = z;
}
Dog.prototype = new Animal();
const dog1 = new Dog(1, 2, 3);
dog1.run();
console.log(dog1.x);
原型
上一小节是从继承的层面,介绍原型继承,但是没有具体说什么是原型。只提到构造函数的 prototype
, 那么 prototype
是什么?它的作用又是什么?
我们先说清楚原型,再回来解释上面的 prototype
的作用是什么。
在javascript里面,对象都有一个隐藏对象 “[[Prototype]]”, 获取该对象可以通过 target.__proto__
或 Object.getPrototype(target)
拿到。该对象就是我们说的原型。
它的作用就是用来存放一些方法和属性,当以它为原型的对象,访问本身没有的一些属性或者方法,就会来到原型上面查找。如下:
const animal = {
run() {
console.log('running');
}
}
const dog = {
__proto__: animal
}
dog.run(); // 我们没有在dog定义run方法
这就是原型了,简单吧。那么上面继承过程中,prototype
是什么, 它有什么用呢?
“类” 和 “类的实例” 的关系, 就像是工业生产中,模具和具体产品的关系,类是一个模具,实例就是通过类复刻出来的具有类的属性和方法的具体产品。我们实现继标的目的,就是为了复用一些公共的方法或者属性。那么 构造函数的 prototype
属性,就是为将生产出来的实例指定原型所需要用到的,那些实例本身没有的属性或方法,就来这里查找。
也就是先有某原型,然后才有以该原型为原型的对象。
构造函数的 prototype
和实例对象的原型,其实是指向同一个对象的。
原型链
上面两节,我们搞清楚原型是什么,以及 构造函数 prototype
是什么了。那么原型链又是什么呢?
有道是:万事万物皆对象。那么原型本身也是一个对象,如果查找属性或方法时,到原型还没找到呢,那么就去原型的原型继续找。而javascript 运行环境中是预设了一些对象来作为原型的,如图:
查找属性或方法时,向上追溯,经过的原型,就形成了一条链,所谓原型链。
至于运行环境预设了哪些原型,已经他们的关系如何,为什么?等等,大家可以看看这篇文章:JavaScript 世界万物诞生记, 真的精彩!