JavaScript大杂烩4 - 理解JavaScript对象的继承机制
JavaScript是单根的完全面向对象的语言
JavaScript是单根的面向对象语言,它只有单一的根Object,所有的其他对象都是直接或者间接的从Object对象继承。而在JavaScript的众多讨论中,JavaScript的继承机制也是最让人津津乐道的,在了解它的机制之前,先让我们温习一下继承的本质。
继承的本质
继承的本质是重用,就是这么简单,重用是软件编程技术向前发展最原始的动力。从语法上来讲,继承就是"D是B"的描述,其中B是基类,描述共性,D是子类,描述特性。例如"猫是动物",动物就是基类,猫是子类,动物的共性,比如会呼吸,猫也会,但是猫的特性,如打呼噜,别的动物就不一定有了。
JavaScript的原型继承机制
前面我们已经总结过了原型的作用,原型链继承是JavaScript实现继承的主要方式。这种继承的典型实现方式如下:
function Base(name) {
// 基对象构造函数实例成员
this.name = name;
};
// 基对象原型中的实例成员
Base.prototype.showName = function() {
alert(this.name);
};
// 子对象的构造函数
function Derived(name, age) {
// 关键点1:获取基对象构造函数中的成员
Base.call(this, name);
// 定义子对象自己的成员
this.age = age;
};
// 关键点2:获取基对象原型上的成员
Derived.prototype = new Base();
// 关键点3:将子对象的原型的构造函数属性设回正确的值
// 这样可以防止new对象的挂接对象出错
Derived.prototype.constructor = Derived;
// 扩展子类自己的原型成员
Derived.prototype.showAge = function() {
alert(this.age);
}; var d = new Derived('Frank', 10);
d.showName();
d.showAge();
// 验证继承关系
alert(d instanceof Base);
上面的实现关键点已经标了出来:
关键点1:使用call方式来调用基对象的构造函数,这样子对象就能按照基对象构造函数的逻辑来构造一份基对象构造函数中的成员。
关键点2:使用new方式来创建一个新的Base对象,然后把它挂到Derived对象的原型上,这样从"Derived对象->Derived原型->Base对象->Base原型->Object对象->Object原型"就形成了链条了。
关键点3:new操作符要求构造函数对象的原型的constructor属性要指向构造函数,而在关键点2中把Derived对象的原型完全换成Base对象了,这样有问题了。修复这个问题也很简单,直接把这个属性重新设一下就行了。
上面的实现近乎完美,只有一个问题:Base对象的构造函数被调用了2次,一次在构造函数中,一次在构造原型链中,这样的效率并不高。而根据上面的几点说明我们知道第一次调用是为了复制基对象构造函数中的成员,必不可少;而第二次的调用纯粹是为了把原型链接上,构造函数中的成员并没有使用上,于是这里就存在一个优化的契机:既然构造函数中的成员没有使用到,那我就用一个空对象来辅助创建原型链不就就可以了,看下面的代码:
// 利用空对象来挂接原型链
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
} function Base(name) {
this.name = name;
}; Base.prototype.showName = function() {
alert(this.name);
}; function Derived(name, age) {
Base.call(this, name);
this.age = age;
};
// 挂接原型链
extend(Derived, Base);
Derived.prototype.showAge = function() {
alert(this.age);
}; var d = new Derived('Frank', 10);
d.showName();
d.showAge();
alert(d instanceof Base);
这个版本的实现据说是YUI的实现继承的方式,个人并没参看其源代码,有这个爱好的同学可以自行研究一下。
复制继承
既然继承的本质是复用,那么最直接的想法应该是复制基对象的所有成员,在JavaScript中确实可以这么做,看下面的代码:
// 浅拷贝实现
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
return c;
}
// 深拷贝实现,最为安全
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
// 基对象
var Base = {
name: 'Frank',
showName: function() {
alert(this.name);
}
}
// 拷贝基对象的成员
var Derived = extendCopy(Base);
// 扩展子对象自己的成员
Derived.age = 10;
Derived.showAge = function() {
alert(this.age);
};
// 测试基对象的成员
Derived.showName();
在上面的代码中,我们需要注意两个问题:
1. 我们从前面已经了解过,JavaScript有简单的“值类型”和复杂的“引用类型”,这样复制成员的时候就也有所谓的“浅拷贝”和“深拷贝”的说法。上面的代码实现了两种算法,深拷贝本质上就是为引用类型创建新的对象,这样修改的时候就不会误操作,修改了其他对象的成员。
2. 这种对象的扩展方式不能使用instanceof来检查继承关系,例如下面的代码是无效的:
alert(Derived instanceof Base);
运行这段代码会返回异常:Uncaught TypeError: Expecting a function in instanceof check, but got #<Object> 。这是因为instanceof只能用于检查函数实现的那种继承关系。
JavaScript的多态性
谈完了JavaScript的继承机制,那就不能不说说与之密切相关的多态性。继承与多态从来都是面向对象语言中不可分割的两个概念。
由于JavaScript是脚本语言,动态语言,所以静态的类型约束关系被压缩到了极致。这一方面体现最为明显的一点就是我们可以随意的给对象添加和删除成员,而另一个方面,很多语言都遵循“针对接口”的编程,这一点在动态语言中的表现也大为不同。在JavaScript这些动态语言中,我们不需要事先定义好一些接口,例如下面的例子:
var tank = {
run : function () {
alert('tank run');
}
}; var person = {
run : function () {
alert('person run');
}
};
// 针对接口(run方法)的对象编程
function trigger(target) {
target.run();
} trigger(tank);
trigger(person);
很多人对于这种使用方式不以为然,但是个人觉得这正是动态语言快捷编程的特点,很多时候还是很方便的。
JavaScript大杂烩4 - 理解JavaScript对象的继承机制的更多相关文章
- JavaScript大杂烩3 - 理解JavaScript对象的封装性
JavaScript是面向对象的 JavaScript是一种基于对象的语言,你遇到的所有东西,包括字符串,数字,数组,函数等等,都是对象. 面向过程还是面向对象? JavaScript同时兼有的面向过 ...
- JavaScript大杂烩1 - 理解JavaScript的类型系统
随着硬件水平的逐渐提高,浏览器的处理能力越来越强大,本人坚信,客户端会越来越瘦,瘦到只用浏览器就够了,服务端会越来越丰满:虽然很多大型的程序,比如3D软件,客户端仍然会存在,但是未来的主流必将是浏览器 ...
- JavaScript大杂烩6 - 理解JavaScript中的this
在JavaScript开发中,this是很常用的一个关键字,但同时也是一个很容易引入bug的一个关键字,在这里我们就专门总结一下页面中可能出现的this关键字(包括几种在其他页面文件中出现的this) ...
- JavaScript大杂烩2 - 理解JavaScript的函数
JavaScript中的字面量 书接上回,我们已经知道在JavaScript中存在轻量级的string,number,boolean与重量级的String,Number,Boolean,而且也知道了之 ...
- Javascript学习6 - 类、对象、继承
原文:Javascript学习6 - 类.对象.继承 Javasciprt并不像C++一样支持真正的类,也不是用class关键字来定义类.Javascript定义类也是使用function关键字来完成 ...
- javascript之面向对象程序设计(对象和继承)
总结的文章略长,慎点. 知识点预热 引用类型:引用类型的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起.在其他面向对象语言中被称为类,虽然 ...
- JavaScript大杂烩12 - 理解Ajax
AJAX缘由 再次谈起这个话题,我深深的记得就在前几年,AJAX被炒的如火如荼,就好像不懂AJAX,就不会Web开发一样.要理解AJAX为什么会出现,就要先了解Web开发面临的问题. 我们先来回忆一下 ...
- JavaScript大杂烩10 - 理解DOM
操作DOM 终于到了JavaScript最为核心的部分了,通常来说,操作DOM,为页面提供更为友好的行为是JavaScript根本目标. DOM树 - HTML结构的抽象 既然DOM是操纵HTML ...
- JavaScript大杂烩7 - 理解内置集合
JavaScript内置了很多对象,简单的类型如String,Number,Boolean (相应的"值类型"拥有相同的方法),复杂一点的如Function,Object,Arra ...
随机推荐
- 从github clone文件: Failed to receive SOCKS4 connect request ack.
安装了代理,能上网,也能从github上下载文件,就是无法从github上clone文件, 查了很久资料后,终于发现使用sudo可以解决问题.不过,不知道原因是什么? 比如:git clone htt ...
- Sublime Text3自定义全部字体大小、字体类型和背景颜色
一.定义侧栏的背景颜色.字体大小和间距 Sublime Text3的Afterglow主题链接为:https://github.com/YabataDesign/afterglow-theme 1.按 ...
- 阿里P9架构师讲解从单机至亿级流量大型网站系统架构的演进过程
阶段一.单机构建网站 网站的初期,我们经常会在单机上跑我们所有的程序和软件.此时我们使用一个容器,如tomcat.jetty.jboos,然后直接使用JSP/servlet技术,或者使用一些开源的框架 ...
- Go语言总结
- APUE习题3.2用dup实现dup2以及shell中重定向符号的使用
习题3.2的要求是不使用fcntl()而编写一个同dup2()功能相同的函数.直觉上是不断使用dup()直到返回指定的文件描述符. #include <stdio.h>#include & ...
- CentOS 7 开启 SNMP 实现服务器性能监控
1.检测是否有 SNMP 服务 service snmpd status 2.若没有则安装 yum install -y net-snmp 3.编辑 SNMP 的配置文件,设置安全的验证方式 vi / ...
- windows上使用tensorboard
因为我的环境变量设置的不是python3.5,所以走了一些弯路. 启动tensorboard后,graphs里总是什么都没有 最后再stackoverflow里找到答案 https://stackov ...
- AngularJS初始化Select选择框
一.引入 之前一个离职的同事负责的项目大量的引入了AngularJS的JS框架,后来我接手相关他项目里的功能.由于对AngularJS不是太熟,在他的功能上进行二次开发就比较费劲了,印象比较深的一个就 ...
- Linux免密码登录设置
Linux免密码登录设置 假设要登录的机器为192.168.1.100,当前登录的机器为192.168.1.101. 首先在101的机器上生成密钥(如果已经生成可以跳过): $ ssh-keygen ...
- MySQL学习(三) SQL基础查询
其实在数据库最经常用的当属查询操作 基本语法 SELECT [ALL | DISTINCT | DISTINCTROW ] 字段列表 AS 字段别名 [FROM 表名 WHERE 条件表示式 GROU ...