es6中开始有类的概念,但它其实是个语法糖,隐藏了原型链的内部工作机制。然而es6在类上做了相当大的改进,本文可以作为你学习es6类概念的一个先导,然后你可以发现es6向着诸如java,c#之类的语法更近了一步,当然核心还是原型链,但是在继承上做了很大的改进。
参考:https://www.cnblogs.com/mengfangui/p/9566114.html
https://dev.to/codesmith_staff/explain-javascripts-prototype-chain-like-im-five-51p
js创建对象的多种方式及优缺点:https://www.cnblogs.com/cythia/p/6958021.html
1,原型链相关概念
1.1原型链
如下,是Mozilla对原型链的定义:
When it comes to inheritance, JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. By definition, null has no prototype, and acts as the final link in this prototype chain.
but we should know the last element in a prototype chain is always Object, from which all instances like functions and arrays derive from.
1.2 prototype和 __proto__关系
在javascript中,所有的对象都会有一个__proto__属性指向它的原型。而且在javascript的世界里,一切皆对象。因此函数也有 __proto__属性(js内部联想一下Object函数和Function函数)
而prototype属性只有函数才有。
一般我们认为__proto__ === constructor.prototype 。
希望你能把如下代码 拷贝到 谷歌开发者工具控制台上 跑一下。
function Animal(){
this.name = "Animal";
this.run = function(){
console.log("Animal构造函数内的run方法在此");
}
}
Animal.prototype.eat = function(){
console.log("Animal的prototype内的eat方法在此");
}
function Carnivore(){
}
let dog= new Animal('dog');
Carnivore.prototype = Object.create(Animal.prototype);
//Object.assign(Carnivore.prototype, Animal.prototype);//mixins写法,和Object.create方法都可以
let Lion = new Carnivore();
console.log(dog.__proto__ === Animal.prototype)
结果true(说明dog是Animal函数创造的一个对象)
console.log(Animal.prototype.__proto__ === Object.prototype)
结果true(说明Animal的prototype 是Object函数创造的一个对象)
Object的prototype >> Animal的prototype >> dog ,已经形成了一条以dog为最终对象的链,(从而dog可以使用Animal的prototype上的属性和方法,也可以使用Object的prototype上的属性和方法。)
console.log(Animal.__proto__ ===Function.prototype)
结果true(说明Animal是Function函数创造的一个对象)
Function的prototype >> Animal ,形成了一条以Animal为最终对象的链,(从而Animal可以使用Function的prototype上的属性和方法)
console.log(Lion.__proto__ instanceof Animal )
或者console.log(Lion.__proto__.__proto__ ===Animal.prototype)
结果true(这个比较特殊,说明Lion的__proto__指向的是Animal 的一个实例。)
console.log(Lion.__proto__.__proto__.__proto__===Object.prototype)
结果true(最终同样指向Object.prototype)
Object的prototype >> Animal的prototype >> Animal的一个实例对象 >> Lion,
形成了一条以Lion为最终对象的链(Lion可以调用eat方法,但不能调用run方法),这样好像有点奇怪,但不要过分纠结,你只要 知道这样做是有好处的。好处如下:
构造函数中定义函数,那么每次创建对象,都会重新创建该函数,这样会导致全局变量增多,造成污染,代码结构会混乱,不易维护;
使用如上方法就可以让 构造函数的原型对象中的成员,可以被该构造函数创建出来的所有对象访问,而且,所有的对象共享该对象。所以,我们可以将构造函数中需要创建的函数,放到原型对象中存储,这样就解决 全局变量污染的问题 以及 代码结构混乱的问题。缺点:不能初始化参数。
如果我们想实现 构造函数内的方法和属性继承,应该参考如下写法(虽然不太可取) Lion可以调用run方法,但是不可以调用prototype上的eat方法。
function Animal(){
this.name = "Animal";
this.run = function(){
console.log("Animal构造函数内的run方法在此");
}
}
Animal.prototype.eat = function(){
console.log("Animal的prototype内的eat方法在此");
}
function Carnivore(name){
Animal.call(this);
this.name = name;
}
let Lion = new Carnivore("非洲狮子");
1.3 ES6 类继承 中的 原型链
class A {
static hello() {
console.log('hello world');
}
}
class B extends A {
}
var tt = new B();
B.hello() // hello world
console.log(B.__proto__ === A )
结果true(说明子类的原型是父类)
如果到此你觉得已将完全理解了,下面内容你就不用看了。
2 原型链的应用
2.1 在不修改类定义的情况下,通过prototype添加方法
javascript用首字母大写来区分构造器和函数。
function Animal(name){
this.name = name;
}
let dog= new Animal('dog');
初始化构造器的时候,对象会获得一个prototype对象作为它的属性之一,js用__proto__指向对象的原型。
构造函数声明后,你还可以给prototype对象额外添加键值对。
Dog.prototype.eat= function(){
console.log('Im eatting. ');
};
Dog.prototype.run = function(){
console.log('Im running. ');
};
这个时候我们构造器再创建一个对象,会神奇的发现 这个新对象的 eat 和run 方法都是可以调用的。
let cat = new Animal('cat');
cat.eat();
cat.run();
JavaScript的引擎首先在对象本身中查找property 。如果不存在,它将遍历第一个prototype,然后遍历下一个,直到找到所要查找的内容或为空为止
2.2 我们可以用原型链来实现类继承
我们将创建一个超类和另外两个子类。食肉动物应该从哺乳动物那里继承而狮子应该从食肉动物和哺乳动物那里继承。如下:
function Mammal(){
this.bloodTemp = 'warm';
}
function Carnivore(){
}
function Lion(name){
Mammal.call(this); //super. Inherit constructor
this.name = name;
}
Mammal.prototype.growHair = function(){
console.log('my hair is growing');
}
Carnivore.prototype = Object.create(Mammal.prototype);
//这个时候console.log(Carnivore.prototype) 会指向Mammal。
//console.log(Mammal.prototype) __proto__指向Object。
Carnivore.prototype.eatMeat = function(){
console.log('Mmm.Meat');
};
Lion.prototype = Object.create(Carnivore.prototype);
//Object.assign(Lion.prototype, Carnivore.prototype);//mixins写法,和Object.create方法都可以
Lion.prototype.pride = function(){
console.log('im king of the jungle');
};