原文地址

 

本文内容

  • 目的
  • 继承的第一步——最简单的继承
  • 私有变量/成员和原型
  • 三种继承方式及其优劣
    • 基本的原型继承
    • 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. Python yield使用

    https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/ 您可能听说过,带有 yield 的函数在 Python 中被称 ...

  2. [Unity3D]Unity3D游戏开发之角色控制漫谈

    各位朋友,大家好.我是秦元培,欢迎大家关注我的博客,我的博客地址blog.csdn.net/qinyuanpei.今天呢,我们来说说Unity3D中的角色控制,这篇文章并非关注于Unity3D中的某项 ...

  3. 在ASP.NET Web API中使用OData的Action和Function

    本篇体验OData的Action和Function功能.上下文信息参考"ASP.NET Web API基于OData的增删改查,以及处理实体间关系".在本文之前,我存在的疑惑包括: ...

  4. Visual Studio 2013 sqlce 配置(转)

    Visual Studio 2013 把內建 SQL CE 的管理工具拿掉了 下载SQL Server Compact Toolbox by ErikEJ并安装 打开VS2013,新建一工程,在“视图 ...

  5. lastlog

    [root@li739-39 ~]# lastlogUsername Port From Latestroot pts/1 183.15.253.245 Thu Oct 29 05:57:29 +00 ...

  6. C#编程(十九)----------部分类

    部分类 C#中使用关键字partial把类,结构或结构放在多个文件中.一般情况下,一个类全部驻留在单个文件中.但有时候,多个开发人员需要访问同一个类,或者某种类型的代码生成器生成了一个类的某部分,所以 ...

  7. [开源]Google code Android开源项目(一)

    [Android分享] [开源]Google code Android开源项目(一) [复制链接]     449122717 2 主题 2 好友 816 积分 No.4 中级开发者 升级  19.3 ...

  8. log4j1 修改FileAppender解决当天的文件没有日期后缀

    直接上代码: /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license ...

  9. jQuery Ajax 上传文件夹及文件

    我们先来看一下文件夹结构 这是上传处理的: 看一下系统日志: 升级 HTML5文件实现拖拽上传提示效果改进(支持三种状态提示) 拖拽过程详解: 1:文件未拖出文件选择框的时候提示:将要上传的文件或文件 ...

  10. 安装veloeclipse插件报错解决方案

    步骤: 1.把Eclipse安装目录下的artifacts.xml打开,搜索veloeclipse,把它相关的项删除: 2.Help 3. Install New Software 4.Work Wi ...