一、类的继承与对象的原型继承:

JavsScript不存在“类”的概念,都是“对象”。Java中如果想拥有某个类的属性和方法,需要使用extends关键字继承这个类。但是JavaScript不同于Java中的类的继承,如果想拥有像某个对象的属性和方法,需要使用prototype指定对象的原型对象。例如:

var tom = {name: 'tom',play: function () {console.log(this.name + ' like playing...');}
};

现在要创建另一个对象Jim,但是Jim除了像tom一样,有名字、喜欢玩之外,他还爱学习,最笨的办法可以这样定义:

var jim = {name: 'jim',play: function () {console.log(this.name + ' like playing...');},study: function () {console.log(this.name + ' like studying...');}
};

但如果使用原型可以这样定义:

var jim = {name: 'jim',study: function () {console.log(this.name + ' like studying...');}
};
jim.__proto__ = tom;jim.name      //jim
jim.play();   //jim like playing...
jim.study();  //jim like studying...

在编写JavaScript代码时,不要轻易用__proto__去改变对象的原型,按照标准__proto__是不对外公开的,但是chrome的引擎却将他暴露了出来成为了一个公有属性,我们可以对其进行访问和赋值。但IE浏览器是不能访问这个属性的,所以不推荐大家直接操作这个属性,以免造成浏览器兼容问题。通常我们可以使用Object.create(obj),传入原型对象即可创建一个以指定对象为原型的新对象,比如:

var jim = Object.create(tom);
jim.name = 'jim';
jim.study = function(){console.log(this.name + ' like studying...');};
jim.name      //jim
jim.play();   //jim like playing...
jim.study();  //jim like studying...

二、原型:

JavaScript对每个创建的对象都会设置一个原型__proto__,指向它的原型对象。当我们访问某个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。prototype是函数的一个属性,只有Function类型的对象才具有prototype属性。例如创建一个数组对象:

var arr = [1, 2, 3];

其原型链为:arr ——> Array.prototype ——> Object.prototype ——> null,Array.prototype定义了indexOf()、shift()等方法,因此在所有的Array对象上都可以直接调用这些方法。再比如创建一个函数结象:

function foo() {return 0;
}

函数也是一个对象,它的原型链是:foo ——> Function.prototype ——> Object.prototype ——> null,由于Function.prototype定义了apply()等方法,因此所有函数都可以调用apply()方法。很容易想到,如果原型链很长,那么访问一个对象的属性就会因为花很多时间查找而变得更慢,因此要注意不要把原型链搞得太长。

三、构造函数:

可以把构造函数理解为一个普通的函数,但是这个普通的函数可以在调用时使用new关键字调用,并返回新创建的对象(不需要显式写return this),this则会自动指向这个新创建的对象。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写。另外:新创建的对象的constructor属性始终指向创建该对象的构造函数本身。例如:

function Student(name) {this.name = name;this.study = function () {console.log(this.name + ' is studying ...');}
}
var tom = new Student('tom');
tom.name         //tom
tom.study()      //tom is studying ...
tom.constructor  //Student(name) {//    this.name = name;//    this.study = function () {//        console.log(this.name + ' is studying ...');//    }//}

其原型链为:tom ——> Student.prototype ——> Object.prototype ——> null,用关键字new创建的对象还获得了一个constructor属性,该属性指向这个构造函数本身。关系如下图所示:

tom.constructor === Student.prototype.constructor;     //true
Student.prototype.constructor === Student;             //true
Object.getPrototypeOf(tom) === Student.prototype;      //true
tom instanceof Student;                                //true

如果修改Student的prototype的某个属性,Student创建的对象的constructor依然为Student

//修改Student原型的某个属性,而不是全部覆盖其原型
Student.prototype.study = function() {console.log(this.name + ' is not studying ...');
};
var b = new Student("Bob");
b.constructor===Student  //true

但如果覆盖掉Student的prototype,Student创建的对象的constructor则变为Object

//覆盖Student的原型
Student.prototype = {study: function(){console.log(this.name + ' is not studying ...');}
};
var b = new Student("Bob");
b.constructor===Student  //false
b.constructor===Object   //true

这是因为覆盖Student的prototype时实际做的操作是:

Student.prototype = new Object({study: function(){console.log(this.name + ' is not studying ...');}
});  

所以对象的constructor指向的是Object而非Student,可以用以下方法修正这个错误:

Student.prototype.constructor=Student;

四、原型链的维护:

方式1:

基于上面的示例,如果创建SeniorStudent对象,例如:

function SeniorStudent(name) {Student.call(this, name);this.seniorStudy = function () {console.log(this.name + ' is seniorStudying ...');}
}

要使原型链变为:new SeniorStudent() ——> SeniorStudent.prototype ——> Student.prototype ——> Object.prototype ——> null,因为现在的SeniorStudent.prototype和Student.prototype的原型链都是到Object.prototype的,所以需要重新维护原型链:需要借助一个中间对象来实现正确的原型链,这个中间对象的prototype要指向Student.prototype,SeniorStudent.prototype要指向这个中间对象的对象,SeniorStudent.prototype的constructor属性要指向SeniorStudent本身,关系如下图所示:

代码实现如下:

//空函数F:
function F() {
}
//把F.prototype指向Student.prototype
F.prototype = Student.prototype;
//把SeniorStudent.prototype指向中间对象F的对象
SeniorStudent.prototype = new F();
//把SeniorStudent.prototype的constructor修复为SeniorStudent
SeniorStudent.prototype.constructor = SeniorStudent;

可以验证该原型链已修改为期望的顺序:

//创建lilei对象
var lilei = new SeniorStudent('LiLei');lilei.name;            //LiLei
lilei.study();         //LiLei is studying ...
lilei.seniorStudy();   //LiLei is seniorStudying ...//验证原型
lilei.__proto__ === SeniorStudent.prototype;     //true
lilei.__proto__.__proto__ === Student.prototype; //true//验证继承关系
lilei instanceof SeniorStudent;  //true
lilei instanceof Student;        //true

如果把修改原型链这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码如下:

function inherits(Child, Parent) {var F = function () {};F.prototype = Parent.prototype;Child.prototype = new F();Child.prototype.constructor = Child;
}

当然,如果不想用中间对象这么麻烦,只需要最核心的一行代码也可以实现,只是没有像原生的原型链那么完美。

SeniorStudent.prototype = new Student();

方式2:

使用新关键字class,从ES6开始正式被引入到JavaScript中。首先用关键字class创建Student类。

class Student {constructor(name) {this.name = name;}study() {console.log(this.name + ' is studying ...');}
}
var tom = new Student('tom');
tom.name      //tom
tom.study()   //tom is studying ...

如果要继承Student类创建一个新类,可以这样写:

class SeniorStudent extends Student {constructor(name) {super(name); //记得用super调用父类的构造方法!}seniorStudy () {console.log(this.name + ' is seniorStudying ...');}
}

ES6引入的class关键字和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码,继承的方式更接近Java类的继承方式!

参考:Douglas Crockford《JavaScript语言精粹》

http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499763408e24c210985d34edcabbca944b4239e20000

JavaScript之继承和原型相关推荐

  1. JavaScript之继承(原型链)

    JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...

  2. JavaScript的继承,原型和原型链

    前言 想必,学过 java 和 C++ 的小伙伴们,对于继承这个词应该不陌生,最近我也是一直在巩固JavaScript的知识,今天就来一起学习一下JavaScript里的继承吧. 继承是什么? 首先我 ...

  3. JavaScript 设计模式学习第五篇-继承与原型链

    JavaScript 是一种灵活的语言,兼容并包含面向对象风格.函数式风格等编程风格.我们知道面向对象风格有三大特性和六大原则,三大特性是封装.继承.多态,六大原则是单一职责原则(SRP).开放封闭原 ...

  4. 一篇JavaScript技术栈带你了解继承和原型链

    作者 | Jeskson 来源 | 达达前端小酒馆 1 在学习JavaScript中,我们知道它是一种灵活的语言,具有面向对象,函数式风格的编程模式,面向对象具有两点要记住,三大特性,六大原则. 那么 ...

  5. 浅谈JavaScript继承与原型链

    对于使用过基于类的语言(如java或C++)的开发人员来说,JavaScript有点令人困惑,因为它是动态的,并且本身不提供一个class实现. 在(es5/es6)中引入class关键字,但那只是语 ...

  6. JavaScript对象继承方式

    一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数 成为 Children 的方法, ...

  7. javascript构造函数继承

    一.传统prototy继承 function Parent() {this.name = "thisIsName"; } Parent.prototype.sayName = fu ...

  8. JavaScript实现继承的方式,不正确的是:

    JavaScript实现继承的方式,不正确的是:DA.原型链继承 B.构造函数继承 C.组合继承 D.关联继承 解析 javaScript实现继承共6种方式: 原型链继承.借用构造函数继承.组合继承. ...

  9. javascript设计模式-继承

    javascript继承分为两种:类式继承(原型链.extend函数).原型式继承(对继承而来的成员的读和写的不对等性.clone函数). 类式继承-->prototype继承: 1 funct ...

  10. JavaScript六种继承方式的递进推演

    1. 原型链继承 function Parent1() {this.name = "Parent1"this.son = [1] } // 需要继承的子类 function Chi ...

最新文章

  1. 如何成为Java程序员
  2. Bootstrap 与 Jquery validate 结合使用——简单实现
  3. 一位来自《seo实战密码》读者的来信
  4. 项目: 用数组实现反弹球消砖块
  5. PG SQL数据库读写分离的思路
  6. Hebb负向规则与矛盾解对
  7. tomcat ajp协议安全限制绕过漏洞_Apache Tomcat文件包含漏洞(CVE20201938)复现
  8. des和aes相比较有哪些特点_栓流气力输送相比较传统的高速气力输送方式而言,有哪些优势?...
  9. 机器学习基石作业一中的PLA和POCKET_PLA实现
  10. 疯狂的华为MateX2:375万人在线抢,转手一台赚2万
  11. 内存:DDR2与DDR
  12. 推荐一个国外的关于奥运报道的网站.
  13. 通过命令行安装silverlight4
  14. kafka从入门到精通:马士兵java集合
  15. IMDB 2003.07.12 最新排名
  16. html网页版国际象棋,棋友推荐的十五大国外国际象棋网站
  17. matlab积分器,MATLAB_SIMULINK__积分器相关操作
  18. Django+Vue开发生鲜电商平台之2.开发环境搭建
  19. 数据分析预测的方法有哪些
  20. Googlebot(谷歌机器人)深入了解

热门文章

  1. 正则表达式 '^[a-zA-Z0-9''-'\s]{1,30}$' 代表什么意思?
  2. [USACO09NOV]Job Hunt
  3. 百度分词ai php,百度分词技术
  4. newifi虚拟服务器,简单几个步骤,newifi mini变身网络打印服务器,轻松省下100+-win7默认网关不可用...
  5. linux命令中ll和ls的区别
  6. Java关于Eclipse下载速度慢的解决办法!
  7. 大黑熊丨逗比与正经的对话描写
  8. fpc:lazarus 安装电子表格程式 FPSpreadsheet
  9. Sqlite日期查询
  10. ESP8266连得上WIFI却连不上手机热点