Javascript面向对象的程序设计 —— 自定义类实现继承的6种方法
许多OO语言都支持两种继承方式:
接口继承:只继承方法签名;
实现继承:继承实际的方法。
ECMAScript只支持实现继承,实现继承是继承实际的方法,依靠原型链来实现。
1、原型链
原型链是实现继承的主要方法。
基本思想:利用原型链让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
实例与原型之间的链条:
假如原型对象等于另一个类型的实例,原型对象将包含一个指向另一个原型的指针,另一个原型也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,构成了实例与原型的链条。
1 function SuperType(){
2 this.property = true;
3 }
4 SuperType.prototype.getSuperValue = function(){
5 return this.property;
6 };
7 function SubType(){
8 this.subproperty = false;
9 }
10
11 //继承了SuperType
12 SubType.prototype = new SuperType();
13
14 SubType.prototype.getSubType = function(){
15 return this.subproperty;
16 };
17
18 var instance = new SubType();
19 alert(instance.getSuperValue()); //true
20 // instance.constructor现在指向SuperType
在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。
原型链存在的问题
1 function SuperType(){
2 this.colors = ["red", "blue", "green"];
3 }
4
5 function SubType(){}
6
7 //继承了SuperType
8 SubType.prototype = new SuperType();
9
10 var instance1 = new SubType();
11 instance1.colors.push("black");
12 alert(instance1.colors); //"red,blue,green,black"
13
14 var instance2 = new SubType();
15 alert(instance2.colors); //"red,blue,green,black"
SubType的所有实例都会共享一个colors属性,对instance1.colors的修改能够通过instance2.colors反映出来。
第二个问题:在创建子类型的实例时,不能向超类型的构造函数中传递参数。没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。所以实践中很少会单独使用原型链。
2、借用构造函数
也叫伪造对象或经典继承。
在子类型构造函数的内部调用超类型构造函数。通过使用apply()和call()方法可以在(将来)新创建的对象上执行构造函数。
1 function SuperType(){
2 this.colors = ["red", "blue", "green"];
3 }
4 function SubType(){
5 //继承了SuperType
6 SuperType.call(this);
7 }
8
9 var instance1 = new SubType();
10 instance1.colors.push("black");
11 alert(instance1.colors); //"red,blue,green,black"
12
13 var instance2 = new SubType();
14 alert(instance2.colors); //"red,blue,green"
(1)、传递参数
相对原型链,借用构造函数有一个很大优势:可以在子类型构造函数中向超类型构造函数传递参数。
1 function SuperType(name){
2 this.name = name;
3 }
4 function SubType(){
5 //继承了SuperType,同时传递了参数
6 SuperType.call(this, "Anna");
7 //调用超类型构造函数后添加子类型属性,防止超类型构造函数重写子类型属性
8 this.age = 26;
9 }
10 var instance = new SubType();
11 alert(instance.name); // "Anna"
12 alert(instance.age); // 26
(2)、借用构造函数的问题
方法都在构造函数中定义,函数复用不能实现。超类型原型中定义的方法,子类型中不可见,结果所有类型都只能使用构造函数模式。很少单独使用。
3、组合继承
也叫伪经典继承,指将原型链和借用构造函数的技术组合到一块,发挥二者之长。
思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。通过在原型上定义方法实现了函数的复用,又能保证每个实例都有自己的属性。
1 function SuperType(name){
2 this.name = name;
3 this.colors = ["red", "blue", "green"];
4 }
5 SuperType.prototype.sayName = function(){
6 alert(this.name);
7 };
8 function SubType(name, age){
9 //继承属性
10 SuperType.call(this, name);
11 this.age = age;
12 }
13 //继承方法
14 SubType.prototype = new SuperType();
15 SubType.prototype.sayAge = function(){
16 alert(this.age);
17 };
18
19 var instance1 = new SubType("Anna", 26);
20 instance1.colors.push("black");
21 alert(instance1.colors); // "red,blue,green,black"
22 instance1.sayName(); // "Anna"
23 instance1.sayAge(); // 26
24
25 var instance2 = new SubType("Greg", 27);
26 alert(instance2.colors); // "red,blue,green"
27 instance2.syaName(); // "Greg"
28 instance2.sayAge(); // 27
4、原型式继承
借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。
1 function object(o){
2 function F(){}
3 P.prototype = o;
4 return new F();
5 }
6 var person = {
7 name: "Anna",
8 friends: ["Court", "Van", "Lily"]
9 };
10
11 var anotherPerson = object(person);
12 anotherPerson.name = "Greg";
13 anotherPerson.friends.push("Bob");
14
15 var yetAnotherPerson = object(person);
16 yetAnotherPerson.name = "Linda";
17 yetAnotherPerson.friends.push("Barbie");
18
19 alert(person.fiends); // "Court,Van,Bob,Barbie"
ECMAScript5新增Object.create()方法规范化了原型式继承,该方法接收2个参数:用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象。
1 var person = {
2 name: "Anna",
3 friends: ["Court", "Van", "Lily"]
4 };
5
6 var anotherPerson = Object.create(person);
7 anotherPerson.name = "Greg";
8 anotherPerson.friends.push("Bob");
9
10 var yetAnotherPerson = Object.create(person);
11 yetAnotherPerson.name = "Linda";
12 yetAnotherPerson.friends.push("Barbie");
13
14 alert(person.fiends); // "Court,Van,Bob,Barbie"
15
16 Object.create()的第二个参数的格式每个属性都是通过自己的描述符定义的,以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
17 var person = {
18 name: "Anna",
19 friends: ["Court", "Van", "Lily"]
20 };
21 var anotherPerson = Object.create(person, {
22 name: {
23 value: "Greg"
24 }
25 });
26 alert(anotherPerson.name); // "Greg"
支持Object.create()方法的浏览器:IE9+、 Firefox4+、 Safari5+、 Opera12+和Chrome。
在只想让一个对象保持类似的情况下,原型式继承完全可以胜任。但是,包含引用类型值的属性始终都会共享相应的值。
5、寄生式继承
思路:创建一个用于封装继承过程的函数,该函数的内部已某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
1 function object(o){
2 function F(){}
3 P.prototype = o;
4 return new F();
5 }
6 function createAnother(original){
7 var clone = object(original); // 通过调用函数创建一个新对象
8 clone.sayHi = function(){ // 以某种方式来增强这个对象
9 alert("hi");
10 };
11 return clone; //返回这个对象
12 }
13 var person = {
14 name: "Anna",
15 friends: ["Court", "Van", "Lily"]
16 };
17 var anotherPerson = createAnother(person);
18 anotherPerson.sayHi(); // "hi"
主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。上面示范继承模式时使用的object()函数不是必需的;任何能够返回新对象的函数都适应于此模式。
6、寄生组合式继承
组合继承是JS最常用的继承模式,但也有不足:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型会包含超类型对象的全部实例属性,但在调用子类型构造函数是会重写这些属性。
1 function SuperType(name){
2 this.name = name;
3 this.colors = ["red", "blue", "green"];
4 }
5 SuperType.prototype.sayName = function(){
6 alert(this.name);
7 };
8 function SubType(name, age){
9 //继承属性,第二次调用SuperType(),name和colors属性屏蔽了原型中的同名属性
10 SuperType.call(this, name);
11 this.age = age;
12 }
13 //继承方法,第一次调用SuperType(),得到属性:name和colors
14 SubType.prototype = new SuperType();
15 SubType.prototype.constructor = SubType;
16 SubType.prototype.sayAge = function(){
17 alert(this.age);
18 };
寄生组合式继承解决了这个问题。
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路:不必为了指定子类型的原型而调用超类型的构造函数,只需要超类型原型的副本即可。使用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。
1 function object(o){
2 function F(){}
3 P.prototype = o;
4 return new F();
5 }
6 function inheritPrototype(subType, superType){
7 var prototype = object(superType.prototype); //创建对象
8 prototype.constructor = subType; //增强对象
9 subType.prototype = prototype; //指定对象
10 }
11 function SuperType(name){
12 this.name = name;
13 this.colors = ["red", "blue", "green"];
14 }
15 SuperType.prototype.sayName = function(){
16 alert(this.name);
17 };
18 function SubType(name, age){
19 SuperType.call(this, name);
20 this.age = age;
21 }
22 inheritPrototype(SubType, SuperType);
23 SubType.prototype.sayAge = function(){
24 alert(this.age);
25 };
优点:只调用了一次SuperType构造函数,避免了SubType.prototype上创建的不必要的、多余的属性。同时,原型链可以保持不变;还能正常使用instanceof和isPrototypeOf()。
普遍认为寄生组合式继承是引用类型最理想的继承范式。
Javascript面向对象的程序设计 —— 自定义类实现继承的6种方法的更多相关文章
- javascript面向对象系列第三篇——实现继承的3种形式
× 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...
- js 创建类和继承的几种方法
在面向对象编程中,类(class)是对象(object)的模板,定义了同一组对象(又称"实例")共有的属性和方法.JavaScript语言里是没有类的概念的,但是我们通过以下方法也 ...
- JavaScript 面向对象的程序设计(一)之理解对象属性
首先,JavaScript 面向对象的程序设计,主要分三部分. 理解对象属性: 理解并创建对象: 理解继承. 本文主要从第一方面来阐述: 理解对象属性 首先我们来理解Javascript对象是什么?在 ...
- javascript面向对象精要第五章继承整理精要
javascript中使用原型链支持继承,当一个对象的[prototype]设置为另一个对象时, 就在这两个对象之间创建了一条原型对象链.如果要创建一个继承自其它对象的对象, 使用Object.cre ...
- (三)Javascript面向对象编程:非构造函数的继承
Javascript面向对象编程:非构造函数的继承 这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承". 今天是最后一个部分,介绍不使 ...
- JavaScript 面向对象编程(二):继承
Javascript面向对象编程(二):构造函数的继承 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继 ...
- Javascript 面向对象编程2:构造函数的继承
这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例.对象之间的"继承"的五种方法.比如,现在有一个"动物"对象 ...
- JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)
JavaScript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script t ...
- 自定义类在PropertyGrid上的展示方法
自定义类在PropertyGrid上的展示方法 零.引言 PropertyGrid用来显示某一对象的属性,但是并不是所有的属性都能编辑,基本数据类型(int, double等)和.Net一些封装的类型 ...
- Qt5.9一个简单的多线程实例(类QThread)(第一种方法)
Qt开启多线程,主要用到类QThread.有两种方法,第一种用一个类继承QThread,然后重新改写虚函数run().当要开启新线程时,只需要实例该类,然后调用函数start(),就可以开启一条多线程 ...
随机推荐
- PostgreSQL-分区表介绍
一.分区简介 表分区是解决一些因单表过大引用的性能问题的方式,比如某张表过大就会造成查询变慢,可能分区是一种解决方案.一般建议当单表大小超过内存就可以考虑表分区了. 表的分区就是将一个逻辑上的大表(主 ...
- mysqlbinlog输出sql
./mysqlbinlog -v --base64-output=DECODE-ROWS ~/Downloads/tymysql2|grep -A4 'ALTER' >~/Downloads/a ...
- 解决Promise的多并发问题
提起控制并发,大家应该不陌生,我们可以先来看看多并发,再去聊聊为什么要去控制它 多并发一般是指多个异步操作同时进行,而运行的环境中资源是有限的,短时间内过多的并发,会对所运行的环境造成很大的压力,比如 ...
- Java笔记(细碎小知识点)1
1.Dos命令:dir:打出当前目录结构:md:创建文件夹:cd+文件夹地址:跳转到当前目录下的对应文件夹:cd..:跳转到上一目录:rd+文件夹:删除文件夹中东西:del+文件(或 "*. ...
- Hyper-V安装Ubuntu16.04,连接网络的设置
使用Windows10自带的Hyper-V安装Ubuntu. 进入系统界面后,无法连接网络. 1.首先确定安装虚拟机的网络设置 2.在Wlan属性分享页面中选取对应的网络设置,然后回到属性页面勾选Hy ...
- OKR 是什么?
OKR OKR 是什么? OKR(Objectives and Key Results)目标与关键结果管理法,起源于英特尔,后在谷歌发扬光大. OKR 是一套协助组织进行目标管理的工具和方法,旨在促进 ...
- 【Azure Developer】在App Service上放置一个JS页面并引用msal.min.js成功获取AAD用户名示例
问题描述 在App Service上放置一个JS页面并引用msal.min.js,目的是获取AAD用户名并展示. 问题解答 示例代码 <!DOCTYPE html> <html> ...
- CC BY-SA 4.0原文及翻译
CC BY-SA 4.0原文及翻译 英文参考链接 中文参考链接 原文: Attribution 4.0 International (CC BY 4.0) This is a human-readab ...
- 【Spring】AOP实现原理
注册AOP代理创建器 在平时开发过程中,如果想开启AOP,一般会使用@EnableAspectJAutoProxy注解,这样在启动时,它会向Spring容器注册一个代理创建器用于创建代理对象,AOP使 ...
- mybatis 操作 mysql 大批量插入,数据分页处理
/** * 大批量插入,数据分页处理 * * @param tableName 临时表名称 * @param mapHead 临时表属性 Map,key = 临时表字段属性, value = 具体值. ...