Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。

这部分知识也是JavaScript里的核心重点之一,同时也是一个难点。我把学习笔记整理了一下,方便大家学习,同时自己也加深印象。这部分代码的细节很多,需要反复推敲。那我们就开始吧。

小试身手

原型链例子(要点写在注释里,可以把代码复制到浏览器里测试,下同)

function foo(){}              //通过function foo(){}定义一个函数对象
foo.prototype.z = 3;          //函数默认带个prototype对象属性   (typeof foo.prototype;//"object")

var obj =new foo();           //我们通过new foo()构造器的方式构造了一个新的对象
obj.y = 2;                    //通过赋值添加两个属性给obj
obj.x = 1;                    //通过这种方式构造对象,对象的原型会指向构造函数的prototype属性,也就是foo.prototype

obj.x; // 1                 //当访问obj.x时,发现obj上有x属性,所以返回1
obj.y; // 2                 //当访问obj.y时,发现obj上有y属性,所以返回2
obj.z; // 3                 //当访问obj.z时,发现obj上没有z属性,那怎么办呢?它不会停止查找,它会查找它的原型,也就是foo.prototype,这时找到z了,所以返回3
//我们用字面量创建的对象或者函数的默认prototype对象,实际上它也是有原型的,它的原型指向Object.prototype,然后Object.prototype也是有原型的,它的原型指向null。                                   //那这里的Object.prototype有什么作用呢?typeof obj.toString; // ‘function'  

//我们发现typeof obj.toString是一个函数,但是不管在对象上还是对象的原型上都没有toString方法,因为在它原型链的末端null之前都有个Object.prototype方法,//而toString正是Object.prototype上面的方法。这也解释了为什么JS基本上所有对象都有toString方法
'z' in obj; // true               //obj.z是从foo.prototype继承而来的,所以'z' in obj返回了true
obj.hasOwnProperty('z'); // false   //但是obj.hasOwnProperty('z')返回了false,表示z不是obj直接对象上的,而是对象的原型链上面的属性。(hsaOwnProperty也是Object.prototype上的方法)

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

基于原型的继承

function Foo(){
   this.y = 2;
}

Foo.prototype.x = 1;
var obj3 = new Foo();  //①当使用new去调用的时候,函数会作为构造器去调用②this会指向一个对象(这里是obj3),而这个对象的原型会指向构造器的prototype属性(这里是Foo.prototype)
obj3.x; //1    //可以看到y是对象上的,x是原型链上的原型(也就是Foo.prototype上)

prototype属性与原型

                     

我们再来看看Foo.prototype是什么样的结构,当我们用函数声明去创建一个空函数的时候,那么这个函数就有个prototype属性,并且它默认有两个属性,constructor和__proto__,

constructor属性会指向它本身Foo,__proto__是在chrome中暴露的(不是一个标准属性,知道就行),那么Foo.prototype的原型会指向Object.prototype。因此Object.prototype上

的一些方法toString,valueOf才会被每个一般的对象所使用。

function Foo(){}
typeof Foo.prototype; // "object"
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指向全局对象(this知识点整理)
   this.age = age;      //使用new调用Peoson的话,this会指向原型为Person.prototype的空对象,通过this.name给空对象赋值,最后this作为return值
}

Person.prototype.hi = function() {   //通过Person.prototype.hi创建所有Person实例共享的方法,(可以参考上节的左图:对象的原型会指向构造器的prototype属性,所以想让obj1,obj2,obj3共享一些方法的话,只需在原型对象上一次性地添加属性和方法就可以了);
   console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now.')//这里的this是全局对象
};

Person.prototype.LEGS_NUM = 2;   //再设置一些对Person类的所有实例共享的数据
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);  //在Student这个子类里面先调用一下父类
  this.className = className;
}
//下一步就是我们怎么去把Student的实例继承Person.prototype的一些方法
Student.prototype = Object.create(Person.prototype);    //Object.create():创建一个空对象,并且这个对象的原型指向它的参数  //这样子我们可以在访问Student.prototype的时候可以向上查找到Person.prototype,又可以在不影响Person的情况下,创建自己的方法
Student.prototype.constructor = Student;  //保持一致性,不设置的话constructor会指向Person

Student.prototype.hi = function() {    //通过Student.prototype.hi这样子的赋值可以覆盖我们基类Person.prototype.hi
  console.log('Hi, my name is ' + this.name + ',I am ' + this.age + ' years old now, and from ' + this.className + '.');
}
Student.prototype.learn = function(subject) {    //同时,我们又有自己的learn方法
  console.log(this.name + 'is learning ' + subject + ' at' + this.className + '.');
};

//test
var yun = new Student('Yunyun', 22, 'Class 3,Grade 2');
yun.hi(); //Hi,my name is Yunyun,I'm 22 years old now,and from Class 3, Grade 2.
console.log(yun.ARMS_NUM); // 2     //我们本身对象是没有的,对象的原型也就是Student.prototype也没有,但是我们用了继承,继续向上查找,找到了Person.prototype.ARMS_NUM,所以返回2
yun.walk(); //Yunyun is walking...
yun.learn('math'); //Yunyun is learning math at Class 3,Grade 2.

结合图我们来倒过来分析一下上面代码:我们先通过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这个实例了,我们接着来进行实验:

Student.prototype.x = 101;        //通过Student.prototype.x把yun的原型动态地添加一个属性x
yun.x;   //101                    //那我们发现所有的实例都会受到影响
//接着我们做个有趣的实验
Student.prototype = {y:2};        //我们直接修改构造器的prototype属性,把它赋值为一个新的对象
yun.y;  //undefined
yun.x;  //101                     //所以我们得出:当我们修改Student.prototype值的时候,并不能修改已经实例化的对象
var Tom = new Student('Tom',3,'Class LOL KengB');  Tom.x; //undefined                //但当我们创建一个新的实例时,这一次x就不见了,Tom.y; //2                        //并且y是新的值

所以说当动态修改prototype的时候,是会影响所有已创建或新创建的实例的,但是修改整个prototype赋值为新的对象的话,对已创建的实例是不会影响的,但是会影响后续的实例。

实现继承的方式

实现继承有多种方式,下面我们还是以Person和Student来分析

function Person() {
}

function Student() {
}

Student.prototype = Person.prototype; // 我们可不可用这种方式呢?这种方法是错误的:因为子类Student有自己的一些方法//,如果通过这样子赋值,改变Student的同时也改变了Person。

Student.prototype = new Person(); //这种方式是可以实现的,但是调用构造函数有时候也是有问题的,比如要传进Person一个name和age//,这里的Student是个类,还没实例化,这时候有些奇怪了,传什么都不是。

Student.prototype = Object.create(Person.prototype); //相对来说这中方式是比较理想的,这里我们创建了一个空的对象//,并且对象的原型指向Person.prototype,这样我们既保证了继承了Person.prototype上的方法,并且Student.prototype又有自己空的对象。//但是Object.create是ES5以后才有的

深入浅出JavaScript之原型链&继承的更多相关文章

  1. javascript中继承(一)-----原型链继承的个人理解

    [寒暄]好久没有更新博客了,说来话长,因为我下定决心要从一个后台程序员转为Front End,其间走过了一段漫长而艰辛的时光,今天跟大家分享下自己对javascript中原型链继承的理解. 总的说来, ...

  2. 一步步学习javascript基础篇(5):面向对象设计之对象继承(原型链继承)

    上一篇介绍了对象创建的几种基本方式,今天我们看分析下对象的继承. 一.原型链继承 1.通过设置prototype指向“父类”的实例来实现继承. function Obj1() { this.name1 ...

  3. javascript原型链继承

    一.关于javascript原型的基本概念: prototype属性:每个函数都一个prototype属性,这个属性指向函数的原型对象.原型对象主要用于共享实例中所包含的的属性和方法. constru ...

  4. 【面试必备】javascript的原型和继承

    原型.闭包.作用域等知识可以说是js中面试必考的东西,通过你理解的深度也就能衡量出你基本功是否扎实.今天来复习一下javascript的原型和继承,虽说是老生常谈的话题,但对于这些知识,自己亲手写一遍 ...

  5. javaScript系列 [04]-javaScript的原型链

    [04]-javaScript的原型链 本文旨在花很少的篇幅讲清楚JavaScript语言中的原型链结构,很多朋友认为JavaScript中的原型链复杂难懂,其实不然,它们就像树上的一串猴子. 1.1 ...

  6. js继承之组合继承(结合原型链继承 和 借用构造函数继承)

    在我的前两篇文章中,我们已经介绍了 js 中实现继承的两种模式:原型链继承和借用构造函数继承.这两种模式都存在各自的缺点,所以,我们考虑是否能将这二者结合到一起,从而发挥二者之长.即在继承过程中,既可 ...

  7. Javascript的原型链图

    90%的前端或者js程序员或者老师们对Javascript懂得不比这个多 给手机看的 但是这个图里的所有褐色单向箭头链就是Javascript的原型链(颜色标注对理解js原型链很关键) 这图中的各个_ ...

  8. prototype原型链继承

    依旧是恶补js基础,上代码: 1.定义父类及父类方法 function Animal(){ this.name = "动物"; } Animal.prototype.eat = f ...

  9. JavaScript 随笔2 面向对象 原型链 继承

    第六章 面向对象的程序设计 1.创建对象的几种方式 A)工厂模式 function CreatObj(name,sex,age){ this.name=name; this.sex=sex; this ...

随机推荐

  1. Angular Service入门

    1.Angular内置service Angular为了方便开发者开发,本身提供了非常多的内置服务.可以通过https://docs.angularjs.org/api/ng/service查看Ang ...

  2. JS中修改属性

    <!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content=& ...

  3. chrome中怎么避免最小字体只能为12px

    在chrome下,fontSize的像素>=12px,因此不能通过调整html.fontSize=10px来定位rem. 但是我们可以通过设置html{font-size:625%;},p{fo ...

  4. oracle win7下 卸载

    1 右击“计算机”-->管理-->服务和应用程序-->服务,停掉所有Oracle相关的服务(以Oracle打头的,比如OracleDBConsoleorcl). 2 开始--> ...

  5. TSwitch 中文简繁显示支持(XE6 Android)

    说明: XE6 的 TSwitch 做了很多改进,包含多语显示处理,但 XE6 似乎只认定一种中文语系「zh」,它无法处理「zh_TW」.「zh_CN」.「zh_HK」.「zh_SG」等语系,不过可以 ...

  6. OC NSString(字符串)

    OC NSString(字符串) 多行文字字面量 NSString * string = @"abC" @"DEF" @"hjk" @&qu ...

  7. XSS 跨站脚本攻击之构造剖析(二)

    1.利用字符编码 (1)字符编码在跨站脚本中经常运用到,透过这种技巧,不仅能让XSS代码绕过服务端的过滤,还能更好的隐藏ShellCode (2)使用一个XSS编码工具,以便对字符串进行十进制和十六进 ...

  8. WEB语言转义总结

    后台字符串嵌入前台语言输出   web语言分为后台和前台,如果后台语言嵌入方式将字符串输出到前台语言中,需要按照前台语言的要求进行转义. 因为前台各个语言都有自身的保留字符, 用于规定自身的语法格式, ...

  9. js中const,var,let区别

    今天第一次遇到const定义的变量,查阅了相关资料整理了这篇文章.主要内容是:js中三种定义变量的方式const, var, let的区别. 1.const定义的变量不可以修改,而且必须初始化. co ...

  10. JQ写下拉列表项目移动(内附效果图和源代码)

    效果图如下: 实现功能:点击第一个按钮,让选中的对象从左边移动到右边: 点击第二个按钮,让左边的所有对象移动到右边: 点击第三个按钮,让选中的对象从右边边移动到左边:   点击第四个按钮,让右边的所有 ...