原文地址

 

本文内容

  • 目的
  • 继承的第一步——最简单的继承
  • 私有变量/成员和原型
  • 三种继承方式及其优劣
    • 基本的原型继承
    • 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 是私有变量,从外部无法访问,只能通过 getDaysetDay 函数访问。

  • 坏处

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 变量、getDaysetDay 函数,有一个已初始化、具有对原型属性和方法进行完全控制的构造函数,反过来,又可以访问私有变量和方法。这个结构非常好,因为,在同一个代码块中,你有构造函数,属性和方法。

  • 坏处

唯一真正的缺点是,私有变量被限制到作用域,因此对于所有实例都是相同的。此外,构造函数外有私有变量,这点显得有点怪异。

三种方法的代码执行结果

对上面三种语法都使用下面代码测试。

代码段 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 提供很多方法来做相同的事情,不同的解决方案有不同的优点和缺点。我也认为,一个重要的经验是,在代码所能做的(提供预期结果)与对运行时、执行和重用来说最优之间,是不同的。选择对你来说,最合适的方式。

 

下载 Demo

JavaScript 继承——三种继承方法及其优劣的更多相关文章

  1. JS面向对象(3) -- Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法

    相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...

  2. js oop中的三种继承方法

    JS OOP 中的三种继承方法: 很多读者关于js opp的继承比较模糊,本文总结了oop中的三种继承方法,以助于读者进行区分. <继承使用一个子类继承另一个父类,子类可以自动拥有父类的属性和方 ...

  3. 【Hibernate框架】三种继承映射

    一.综述 大家都知道,hibernate作为ORM框架的一个具体实现,最大的一个优点就是是我们的开发更加的能体现出"面向对象"的思想.在面向对象开发中,类与类之间是可以相互继承的( ...

  4. js的三种继承方式及其优缺点

    [转] 第一种,prototype的方式: //父类 function person(){ this.hair = 'black'; this.eye = 'black'; this.skin = ' ...

  5. C++继承(一) 三种继承方式

    继承定义 继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一. 继承就是不修改原有的类,直接利用原来的类的属性和方法并进行扩展.原来的类称为基类,继承的类称为派生类,他们的关系就像父子 ...

  6. C++中的三种继承关系

    C++中的三种继承关系 先看类中声明成员时的三种访问权限 public : 可以被任意实体访问 protected : 只允许子类及本类的成员函数访问 private : 只允许本类的成员函数访问 在 ...

  7. JavaScript的3种继承方式

    JavaScript的继承方式有多种,这里列举3种,分别是原型继承.类继承以及混合继承. 1.原型继承 优点:既继承了父类的模板,又继承了父类的原型对象: 缺点:不是子类实例传参,而是需要通过父类实例 ...

  8. C++中的三种继承public,protected,private

    ( c++默认class是private继承且class内的成员默认都是private struct 默认位public 继承,struct内成员默认是public  ) 三种访问权限 public: ...

  9. JavaScript的7种继承模式

    <JavaScript模式>一书中,对于JavaScript的几种继承模式讲解得很清楚,给我提供了很大帮助.总结一下,有如下7种模式. 继承模式1--设置原型(默认模式) 实现方式: // ...

随机推荐

  1. 【iOS开发-91】GCD的同步异步串行并行、NSOperation和NSOperationQueue一级用dispatch_once实现单例

    (1)GCD实现的同步异步.串行并行. --同步sync应用场景:用户登录,利用堵塞 --串行异步应用场景:下载等耗时间的任务 /** * 由于是异步.所以开通了子线程.可是由于是串行队列,所以仅仅须 ...

  2. AngularJS订阅API服务

    本篇使用AngularJS实现订阅某个API服务. 首页大致是: 其中,what's on显示首页内容,Search通过输入关键词调用API服务显示到页面,MyShows显示订阅的内容. Sarch页 ...

  3. Windows/Linux下引用jar包,并用javac/java编译运行

    Windows/Linux下引用jar包,并用javac/java编译运行,有需要的朋友可以参考下. 1> Windows 假设要引用的jar放在D:/test目录下,名字为t1.jar, ja ...

  4. 一文犀利看懂中美贸易战 z

    如今的中国面对着前所未有的经济全球化的大环境.面对着如何成为创新性国家的重任. 钛媒体注:美国东部时间 7 月 6 日凌晨0:01 分,美国正式开始对 340 亿美元的中国产品加征 25% 的关税,这 ...

  5. TabHost的初步使用

    本文主要参考自:http://blog.csdn.net/wulianghuan/article/details/8588947 (里面有TabHost第二种定义的方式,继承TabActivity) ...

  6. 自定义各式各样的圆形ProgressBar

         上面三个图分别是 开始时的样子,走进度时候的样子,最后完成的样子 这是我在两个大神的Demo基础上修改后的结果,我们先来看看自定义view是怎么做到的. 1.自己写一个类继承View类,然后 ...

  7. [转]专访企业QQ SaaS团队,谈企业级LNMP架构设计

    FROM : http://www.csdn.net/article/2014-08-20/2821302-interview-tencent-b-qq-shuai-wang 对比IaaS和PaaS, ...

  8. spring学习之@SessionAttributes

    一.@ModelAttribute 在默认情况下,ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁.如果希望在多个请求中共享 M ...

  9. EF更新指定字段.或个更新整个实体

    EF更新指定字段.或个更新整个实体 更新整个实体: public bool Update(Company compay) { if (compay != null) { dbContext.Entry ...

  10. spring boot成功启动后访问报错404的问题

    Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as ...