JavaScript 继承——三种继承方法及其优劣

原文地址javascript

 

本文内容

  • 目的
  • 继承的第一步——最简单的继承
  • 私有变量/成员和原型
  • 三种继承方式及其优劣
    • 基本的原型继承
    • Yahoo JavaScript 模块模式
    • 建立闭包的构造函数
    • 三种方法的代码执行结果
  • 封装

 

目的

这篇文章事后,你会以为,在 JavaScript 中编写继承的代码不存在所谓的“圣杯”。我将介绍一些经常使用的方法,指出他们存在的问题(优劣),其中一个可能就是你所须要的。最重要的是,这是一课,能够用不一样语法完成相同的事情,让你看见美妙而神奇的 JavaScript 世界。css

 

继承的第一步——最简单的继承


先从一个继承的例子开始,看下面代码的优劣是什么。html

从一个普通对象继承

这个例子很简单,只是从一个普通的对象继承。java

代码段 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;
            }
        };

目前为止的代码还能够。让咱们建立一个对象 Sub,它继承 Base 对象。app

代码段 2:函数

        // Using new Base() is not an option, 
        // since it isn't a constructor
        Sub.prototype = Base;
        function Sub() {
            // Constructor
        };
  • 测试

代码段 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);

除了最后的那个返回 undefined 外,其余都跟预期的同样。问题就在于,当执行时,Base 对象尝试设置其属性,但没有任何内部的引用。对 day 属性使用一个方法能够解决这个问题。this

代码段 4:spa

        day: function () {
            return this.dayName;
        }
  • 好处

正如上面讨论的,你能够指定 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
        };
  • 好处

语法简单,从构造函数内能够访问全部东西。

  • 坏处

若想访问私有变量和私有方法,全部的东西都须要放在构造函数内,而不是经过原型。

这不是推荐的方法。

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
        };
  • 好处

这个代码结构还不错,把私有和公共属性之间进行了很好地分离。变量 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
        };
  • 好处

与代码段 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);

为何会有这样的结果?三种方法都建立一个私有变量,执行得也很好,结果也相同,但致使了,若是你改变某个实例对象的一个私有变量,那么,对全部的实例对象都改变了。也就是说,对上面三个方法,实例对象 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

相关文章
相关标签/搜索