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 ...
随机推荐
- 使用node.js + json-server + mock.js 搭建本地开发mock数据服务
在开发过程中,前后端不论是否分离,接口多半是滞后于页面开发的.所以建立一个REST风格的API接口,给前端页面提供虚拟的数据,是非常有必要的.对比过多种mock工具后,我最终选择了使用 json se ...
- 卡尔曼滤波+单目标追踪+python-opencv
很好的入门资料 向面试官一句话解释卡尔曼滤波: 用上一次的最优状态估计和最优估计误差去计算这一次的先验状态估计和先验误差估计: 用1得到的本次先验误差估计和测量噪声,得到卡尔曼增益: 用1,2步骤得到 ...
- [每天解决一问题系列 - 0009] File System Redirector
问题描述: 在64位操作系统下,知道Wow64是干什么的,但一直不知道是怎么工作的 相关解释: https://msdn.microsoft.com/en-us/library/windows/des ...
- Android的Touch事件分发机制简单探析
前言 Android中关于触摸事件的分发传递是一个很值得研究的东西.曾不见你引入了一个ListView的滑动功能,ListView就不听你手指的指唤来滚动了:也不知道为啥Button设置了onClic ...
- ckeditor 在dwz里面使用
在ckeditor的配置的过程中,所有的配置的地方都配置了,但是就是不显示编辑器(编辑器代码如下),很郁闷哦 1 <textarea id="editor1" name=&q ...
- Angular2入门:TypeScript的类型 - let , var, const
一.let 二.const
- idea集成uglifyjs2
项目中可能会多次修改某些*.js文件,但是引用的是*.min.js, 所以需要再改完源码后生成压缩的min.js uglifyjs是个不错的工具,但是单独用略显麻烦,如果能整合到idea就好了.正好i ...
- 突发奇想想学习做一个HTML5小游戏
前言: 最近一期文化馆轮到我分享了,分享了两个,一个是关于童年教科书的回忆,一个是关于免费电子书的.最后我觉得应该会不敌web,只能说是自己在这中间回忆了一下那个只是会学习的年代,那个充满梦想的年代. ...
- 偏流角(Draft Angle)在等距螺旋中的作用
劳动改变人,思维改变世界.我们可以接着聊螺旋线了. 在飞行程序设计中,偏流角(Draft Angle简写为DA)通常指得是受侧风影响航向偏移的最大角度.用速度向量来表示时,是图1中的三角形关系: 图1 ...
- MyBatis从入门到放弃五:调用存储过程(SQLServer2012)
前言 如果是相对于复杂的SQL逻辑我们肯定是基于存储过程开发,这篇学习下执行存储过程,调用存储过程如果参数较多我们可以创建parameterMap. 搭建开发环境 开发环境和上篇文章保持相同 创建存储 ...