JavaScript 继承——三种继承方法及其优劣
本文内容
- 目的
- 继承的第一步——最简单的继承
- 私有变量/成员和原型
- 三种继承方式及其优劣
- 基本的原型继承
- Yahoo JavaScript 模块模式
- 创建闭包的构造函数
- 三种方法的代码执行结果
- 封装
目的
这篇文章过后,你会觉得,在 JavaScript 中编写继承的代码不存在所谓的“圣杯”。我将介绍一些常用的方法,指出他们存在的问题(优劣),其中一个可能就是你所需要的。最重要的是,这是一课,可以用不同语法完成相同的事情,让你看见美妙而神奇的 JavaScript 世界。
继承的第一步——最简单的继承
先从一个继承的例子开始,看下面代码的优劣是什么。
从一个普通对象继承
这个例子很简单,只是从一个普通的对象继承。
代码段 1:
// Basic object
var Base = {
// Public properties and methods
dayName: "Tuesday",
day: this.dayName,
getDay: function () {
return this.dayName;
},
setDay: function (newDayName) {
this.dayName = newDayName;
}
};
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
目前为止的代码还可以。让我们创建一个对象 Sub,它继承 Base 对象。
代码段 2:
// Using new Base() is not an option,
// since it isn't a constructor
Sub.prototype = Base;
function Sub() {
// Constructor
};
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
- 测试
代码段 3:
var a = new Sub();
// Returns "Tuesday"
alert(a.getDay());
var b = new Sub();
// Returns "Tuesday"
alert(b.getDay());
// Sets dayName to "Wednesday"
a.setDay("Wednesday");
// Returns "Wednesday"
alert(a.getDay());
// Returns "Tuesday"
alert(b.getDay());
// Returns undefined
alert(b.day);
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
除了最后的那个返回 undefined 外,其他都跟预期的一样。问题就在于,当执行时,Base 对象尝试设置其属性,但没有任何内部的引用。对 day 属性使用一个方法可以解决这个问题。
代码段 4:
day: function () {
return this.dayName;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
- 好处
正如上面讨论的,你可以指定 Base 对象作为祖先来从原型(prototype)继承,但因为 Base 不是真正的构造函数,你没有用 new 关键字来调用它。基本上,这意味着,直到 Sub 对象的一个实例被创建,Base 构造函数才会执行。这是好事,因为在真正需要之前,你期望任何不必要的代码在构造函数中执行。就我个人而言,我不会在构造函数中放任何重要的东西,而是放一个init 方法,这样,当它被调用时,我就能够完全了。
- 坏处
这里不好的地方是,所有的属性和方法都被声明为 Base 对象的内联,这就意味着,你不能利用原型的行为。另外,该方法没有途径放任何的私有变量。
私有变量/成员和原型
很多人都问我的一件事是,如果对象中有私有变量,那么,能否通过原型方法访问它们。答案是,很不幸——不能。然而,对于私有方法,还是能做到的,正如下面讲到的。
三种继承方式及其优劣
从这里开始,为了完成同一个结果,我将提出三种不同的语法。这三种语法都是非常不同的方式。另外,我也将展示私有变量和原型存在的问题,以及它们如何与私有方法一起工作。
基本的原型继承
首先是一般的原型继承,声明一个私有变量和私有方法。
代码段 5:
// Basic Prototype inheritance
function Base() {
// Private variable
var dayName = "Tuesday";
// Private method
function getPrivateDayName() {
return dayName;
}
// Public properties and methods
this.day = dayName;
this.getDay = function () {
return getPrivateDayName();
};
this.setDay = function (newDayName) {
dayName = newDayName;
};
};
Sub.prototype = new Base;
function Sub() {
// Constructor
};
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
- 好处
语法简单,从构造函数内可以访问所有东西。
- 坏处
若想访问私有变量和私有方法,所有的东西都需要放在构造函数内,而不是通过原型。
这不是推荐的方法。
Yahoo JavaScript 模块模式
当涉及到单件对象(singleton object)时,我个人最喜欢 Yahoo JavaScript 模块模式(Yahoo JavaScript Module Pattern)。对于原型继承,你也可以对任何 Sub 对象使用它作为原型的祖先对象,如下所示:
代码段 6:
// Yahoo JavaScript Module Pattern
var Base = function () {
// Private variable
var dayName = "Tuesday";
// Private method
var getPrivateDayName = function () {
return dayName;
}
// Public properties and methods
return {
day: dayName,
getDay: function () {
return getPrivateDayName.call(this);
},
setDay: function (newDayName) {
dayName = newDayName;
}
};
} ();
// Using new Base() is not an option,
// since it isn't a constructor
Sub.prototype = Base;
function Sub() {
// Constructor
};
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
- 好处
这个代码结构还不错,把私有和公共属性之间进行了很好地分离。变量 dayName 是私有变量,从外部无法访问,只能通过 getDay 和 setDay 函数访问。
- 坏处
Sub.prototype = Base 这不是真正的构造函数,不能用 new 调用它。另外,return {…} 中所有的公共属性和方法都内联(inline)在对象中,因此,没有利用推荐的原型方法。
创建闭包的构造函数(Closure-Created Constructor)
下面代码,创建一个闭包,里边有构造函数,私有变量和方法,并把原型属性和方法指定给对象。然后,它返回实际的构造函数对象,因此,下一次运行时,它的行为就跟一个正常的构造函数一样,同时,闭包所有的属性和方法都仍然可访问。
到目前为止,这是最优雅的代码。
代码段 7:
// Closure-created constructor
var Base = (function () {
// Constructor
function Base() {
}
// Private variable
var dayName = "Tuesday";
// Private method
function getPrivateDayName() {
return dayName;
}
// Public properties and methods
Base.prototype.day = dayName;
Base.prototype.getDay = function () {
return getPrivateDayName.call(this);
};
Base.prototype.setDay = function (newDayName) {
dayName = newDayName;
};
return Base;
})();
Sub.prototype = new Base;
function Sub() {
// Constructor
};
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
- 好处
与代码段 6 相比,该代码使用了闭包,注意代码中的,公共属性和方法:day 变量、getDay 和 setDay 函数,有一个已初始化、具有对原型属性和方法进行完全控制的构造函数,反过来,又可以访问私有变量和方法。这个结构非常好,因为,在同一个代码块中,你有构造函数,属性和方法。
- 坏处
唯一真正的缺点是,私有变量被限制到作用域,因此对于所有实例都是相同的。此外,构造函数外有私有变量,这点显得有点怪异。
三种方法的代码执行结果
对上面三种语法都使用下面代码测试。
代码段 8:
var a = new Sub();
// Returns "Tuesday"
alert(a.getDay());
var b = new Sub();
// Returns "Tuesday"
alert(b.getDay());
// Sets dayName to "Wednesday"
a.setDay("Wednesday");
// Returns "Wednesday"
alert(a.getDay());
// Returns "Wednesday"
alert(b.getDay());
// Returns "Tuesday"
alert(b.day);
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
为什么会有这样的结果?三种方法都创建一个私有变量,执行得也很好,结果也相同,但导致了,如果你改变某个实例对象的一个私有变量,那么,对所有的实例对象都改变了。也就是说,对上面三个方法,实例对象 a 若改变其私有变量 dayName,那么实例对象 b 的私有变量也会被改变。这样,它就更像一个私有的静态属性,而不是一个真正的私有属性。
So, if you want to have something private, more like a non-public constant, any of the above approaches is good, but not for actual private variables. Private variables only work really well with singleton objects in JavaScript.
所以,如果您想拥有某个私人的东西,它更像是一个非公有的常量(静态的,如 dayName 变量),而不是一个真正的私有变量,上面任何一个方法都可以。JavaScript 中,私有变量只在单件对象(singleton object)会很好的工作。
然而,对于私有方法,它的执行显得臃肿!你不公开它,而是在原型代码中使用它。
封装
正如你可以看到,JavaScript 提供很多方法来做相同的事情,不同的解决方案有不同的优点和缺点。我也认为,一个重要的经验是,在代码所能做的(提供预期结果)与对运行时、执行和重用来说最优之间,是不同的。选择对你来说,最合适的方式。
JavaScript 继承——三种继承方法及其优劣的更多相关文章
- JS面向对象(3) -- Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法
相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...
- js oop中的三种继承方法
JS OOP 中的三种继承方法: 很多读者关于js opp的继承比较模糊,本文总结了oop中的三种继承方法,以助于读者进行区分. <继承使用一个子类继承另一个父类,子类可以自动拥有父类的属性和方 ...
- 【Hibernate框架】三种继承映射
一.综述 大家都知道,hibernate作为ORM框架的一个具体实现,最大的一个优点就是是我们的开发更加的能体现出"面向对象"的思想.在面向对象开发中,类与类之间是可以相互继承的( ...
- js的三种继承方式及其优缺点
[转] 第一种,prototype的方式: //父类 function person(){ this.hair = 'black'; this.eye = 'black'; this.skin = ' ...
- C++继承(一) 三种继承方式
继承定义 继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一. 继承就是不修改原有的类,直接利用原来的类的属性和方法并进行扩展.原来的类称为基类,继承的类称为派生类,他们的关系就像父子 ...
- C++中的三种继承关系
C++中的三种继承关系 先看类中声明成员时的三种访问权限 public : 可以被任意实体访问 protected : 只允许子类及本类的成员函数访问 private : 只允许本类的成员函数访问 在 ...
- JavaScript的3种继承方式
JavaScript的继承方式有多种,这里列举3种,分别是原型继承.类继承以及混合继承. 1.原型继承 优点:既继承了父类的模板,又继承了父类的原型对象: 缺点:不是子类实例传参,而是需要通过父类实例 ...
- C++中的三种继承public,protected,private
( c++默认class是private继承且class内的成员默认都是private struct 默认位public 继承,struct内成员默认是public ) 三种访问权限 public: ...
- JavaScript的7种继承模式
<JavaScript模式>一书中,对于JavaScript的几种继承模式讲解得很清楚,给我提供了很大帮助.总结一下,有如下7种模式. 继承模式1--设置原型(默认模式) 实现方式: // ...
随机推荐
- Linux Delay Accounting
https://andrestc.com/post/linux-delay-accounting/ Ever wondered how long is your program spending wh ...
- 少女花海自拍撞亡:自拍PK火车速度,没有赢家
心理学研究,自拍是一种自我强化的过程.人们都或多或少有着自我关注的倾向,即“自恋”.而人作为有思想的群体性社会动物,有着分享和交流的欲望.尤其是现代快节奏的生活常使人感觉“亚历山大”,自拍恰恰就成为释 ...
- Revit API布置卫浴装置
//放置卫浴装置 [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public clas ...
- arcgis10.5.1 对齐要素
许可级别:BasicStandardAdvanced 摘要 标识地理处理工具用于标识搜索距离中输入要素与目标要素的不一致部分并使其与目标要素一致. 插图 用法 警告: 此工具用于修改输入数据.有关详细 ...
- java基础之static(静态)
静态的属性.方法等属于类而不是对象. 静态的方法能够由类直接调用,不须要将类实例化. 本篇主要说明:1.态的代码.成员变量要比构造方法先运行. 2. 子类的构造方法会默认去调用父类的不带參数的构造方法 ...
- FEC详解三
转自:http://blog.csdn.net/Stone_OverLooking/article/details/77752076 继续上文讲解: 3) 标准的RTP头结构如下所示: 其中第一个字节 ...
- Asp.Net 管道事件注册/HttpApplication事件注册
一.HttpApplication简介 在HttpRuntime创建了HttpContext对象之后,HttpRuntime将随后创建一个用于处理请求的对象,这个对象的类型为HttpApplicati ...
- [转]php使用 memcache 来存储 session
转自:http://koda.iteye.com/blog/466667 Memcache和PHP memcach扩展安装请见http://koda.iteye.com/blog/665761 设置s ...
- Photoshop 使用阈值调整色阶
1. 阈值原理 阈值的定义其实就是“临界点”,即过了这个临界点是一种情况(比如黑色),没有超过这个临界点是另外一种情况(比如白色),所以图像上只有黑.白两种情况出现.临界点的值由你定义: 阈值实际应用 ...
- ASP.NET MVC:WebPageBase.cs
ylbtech-funcation-Utility: ASP.NET MVC:WebPageBase.cs 充当表示 ASP.NET Razor 页的类的基类. 1.A,WebPageBase 抽象类 ...