Javascript语言的继承机制,它没有”子类”和”父类”的概念,也没有”类”(class)和”实例”(instance)的区分,全靠一种很奇特的”原型链”(prototype chain)模式,来实现继承。
这部分知识也是JavaScript里的核心重点之一,同时也是一个难点。我把学习笔记整理了一下,方便大家学习,同时自己也加深印象。这部分代码的细节很多,需要反复推敲。那我们就开始吧。
小试身手
原型链例子(要点写在注释里,可以把代码复制到浏览器里测试,下同)
- function foo(){}
- foo.prototype.z = 3;
-
- var obj =new foo();
- obj.y = 2;
- obj.x = 1;
-
- obj.x;
- obj.y;
- obj.z;
-
-
-
- typeof obj.toString;
-
-
-
-
'z' in obj;
- obj.hasOwnProperty('z');

刚才我们访问x,y和z,分别通过原型链去查找,我们可以知道:当我们访问对象的某属性时,而该对象上没有相应属性时,那么它会通过原型链向上查找,一直找到null还没有话,就会返回undefined。
基于原型的继承

- function Foo(){
- this.y = 2;
- }
-
- Foo.prototype.x = 1;
- var obj3 = new Foo();
- obj3.y;
- obj3.x;
prototype属性与原型


我们再来看看Foo.prototype是什么样的结构,当我们用函数声明去创建一个空函数的时候,那么这个函数就有个prototype属性,并且它默认有两个属性,constructor和__proto__,
constructor属性会指向它本身Foo,__proto__是在chrome中暴露的(不是一个标准属性,知道就行),那么Foo.prototype的原型会指向Object.prototype。因此Object.prototype上
的一些方法toString,valueOf才会被每个一般的对象所使用。
-
function Foo(){}
-
typeof Foo.prototype;
- Foo.prototype.x = 1;
-
var obj3 = new Foo();
总结一下:我们这里有个Foo函数,这个函数有个prototype的对象属性,它的作用就是当使用new Foo()去构造实例的时候,这个构造器的prototype属性会用作new出来的这些对象的原型。
所以我们要搞清楚,prototype和原型是两回事,prototype是函数对象上的预设属性,原型通常是构造器上的prototype属性。
实现一个class继承另外一个class
-
function Person(name, age) {
- this.name = name;
- this.age = age;
- }
-
- Person.prototype.hi = function() {
- console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now.')
- };
-
- Person.prototype.LEGS_NUM = 2;
- Person.prototype.ARMS_NUM = 2;
- Person.prototype.walk = function() {
- console.log(this.name + ' is walking...');
- };
-
-
function Student(name, age, className) {
- Person.call(this, name, age);
- this.className = className;
- }
-
-
-
- Student.prototype = Object.create(Person.prototype);
- Student.prototype.constructor = Student;
-
- Student.prototype.hi = function() {
- console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now, and from ' + this.className + '.');
- }
- Student.prototype.learn = function(subject) {
- console.log(this.name + 'is learning ' + subject + ' at' + this.className + '.');
- };
-
-
-
var yun = new Student('Yunyun', 22, 'Class 3,Grade 2');
- yun.hi();
- console.log(yun.ARMS_NUM);
- yun.walk();
- yun.learn('math');

结合图我们来倒过来分析一下上面代码:我们先通过new
Student创建了一个Student的实例yun,yun的原型指向构造器的prototype属性(这里就是Student.prototype),
Student.prototype上有hi方法和learn方法,Student.prototype是通过Object.create(Person.prototype)构造的,所以这里的Student.prototype是空对象,并且这个对象的原型指向Person.prototype,接着我们在Person.prototype上也设置了LEGS_NUM,ARMS_NUM属性以及hi,walk方法。然后我们直接定义了一个Person函数,Person.prototype就是一个预置的对象,它本身也会有它的原型,它的原型就是Object.prototype,也正是因为这样,我们随便一个对象才会有hasOwnProperty,valueOf,toString这样些公共的函数,这些函数都是从Object.prototype上来的。这样子就实现了基于原型链的继承。
那我们调用hi,walk,learn方法的时候发生了什么呢?比如我们调用hi方法的时候,我们首先看这个对象yun上有没有hi方法,但是在这个实例中没有所以会向上查找,查找到yun的原型也就是Student.protoype上有这hi方法,所以最终调用的是Student.prototype.hi,调用其他方法也是类似的。
改变prototype
我们知道JavaScript中的prototype原型不像Java中的class,Java中的class一旦写好就很难动态的去改变了,但是JavaScript中的原型实际上也是普通的对象,那就意味着在程序运行的阶段,我们也可以动态的给prototype添加或删除些属性。

在上述代码的基础上,我们已经有yun这个实例了,我们接着来进行实验:
- tudent.prototype.x = 101;
- yun.x;
-
- Student.prototype = {y:2};
- yun.y;
- yun.x;
-
var Tom = new Student('Tom',3,'Class LOL KengB');
- Tom.x;
- Tom.y;
所以说当动态修改prototype的时候,是会影响所有已创建或新创建的实例的,但是修改整个prototype赋值为新的对象的话,对已创建的实例是不会影响的,但是会影响后续的实例。
实现继承的方式
实现继承有多种方式,下面我们还是以Person和Student来分析
-
function Person() {
- }
-
-
function Student() {
- }
-
- Student.prototype = Person.prototype;
-
-
- Student.prototype = new Person();
-
-
- Student.prototype = Object.create(Person.prototype);
-
-
作者:牧云云
来源:51CTO