47

我了解基本的 JavaScript 伪类:

function Foo(bar) {
    this._bar = bar;
}

Foo.prototype.getBar = function() {
    return this._bar;
};

var foo = new Foo('bar');
alert(foo.getBar()); // 'bar'
alert(foo._bar); // 'bar'

我也了解模块模式,它可以模拟封装:

var Foo = (function() {
    var _bar;

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
})();

Foo.setBar('bar');
alert(Foo.getBar()); // 'bar'
alert(Foo._bar); // undefined

但是这两种模式都有类似 OOP 的属性。前者不提供封装。后者不提供实例化。两种模式都可以修改以支持伪继承。

我想知道的是是否有任何模式允许:

  • 遗产
  • 封装(支持“私有”属性/方法)
  • 实例化(可以有多个“类”实例,每个实例都有自己的状态)
4

8 回答 8

73

那这个呢 :

var Foo = (function() {
    // "private" variables 
    var _bar;

    // constructor
    function Foo() {};

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return _bar;
    };
    Foo.prototype.setBar = function(bar) {
        _bar = bar;
    };

    return Foo;
})();

现在我们有了实例化、封装和继承。
但是,还是有问题。该private变量是static因为它在所有Foo. 快速演示:

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'b' :(    

更好的方法可能是使用私有变量的约定:任何私有变量都应该以下划线开头。这个约定是众所周知的并且被广泛使用,因此当另一个程序员使用或更改您的代码并看到以下划线开头的变量时,他会知道它是私有的,仅供内部使用并且他不会修改它。
这是使用此约定的重写:

var Foo = (function() {
    // constructor
    function Foo() {
        this._bar = "some value";
    };

    // add the methods to the prototype so that all of the 
    // Foo instances can access the private static
    Foo.prototype.getBar = function() {
        return this._bar;
    };
    Foo.prototype.setBar = function(bar) {
        this._bar = bar;
    };

    return Foo;
})();

现在我们有了实例化、继承,但是我们失去了封装,转而支持约定:

var a = new Foo();
var b = new Foo();
a.setBar('a');
b.setBar('b');
alert(a.getBar()); // alerts 'a' :) 
alert(b.getBar()); // alerts 'b' :) 

但是可以访问私有变量:

delete a._bar;
b._bar = null;
alert(a.getBar()); // alerts undefined :(
alert(b.getBar()); // alerts null :(
于 2012-09-26T21:16:38.327 回答
5

闭包是你的朋友!

只需将以下小函数添加到您的顶级命名空间,您就可以开始 OOP,完成

  • 封装,具有静态和实例、私有和公共变量和方法
  • 遗产
  • 类级注入(例如,用于单例服务)
  • 没有限制,没有框架,只是简单的旧 Javascript

function clazz(_class, _super) {
    var _prototype = Object.create((_super || function() {}).prototype);
    var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop();
    _deps.push(_super);
    _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor;
    _prototype.constructor.prototype = _prototype;
    return _prototype.constructor;
}

上面的函数简单地将给定类的原型和最终的父构造函数连接起来,并返回生成的构造函数,为实例化做好准备。

现在,您可以最自然地在几行代码中声明您的基类(即扩展 {}),包括静态、实例、公共和私有属性和方法:

MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions declared here are private static variables and methods
    // properties of 'this' declared here are public static variables and methods
    return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) {
        // local variables and functions declared here are private instance variables and methods
        // properties of 'this' declared here are public instance variables and methods
    };
});

延长课程?也更自然:

MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype
    // local variables and functions are private static variables and methods
    // properties of this are public static variables and methods
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        // local variables and functions are private instance variables and methods
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // properties of 'this' are public instance variables and methods
    };
}, MyBaseClass); // extend MyBaseClass

换句话说,将父类的构造函数传递给 clazz 函数,并添加_super.call(this, arg1, ...)到子类的构造函数中,该构造函数使用所需的参数调用父类的构造函数。与任何标准继承方案一样,父构造函数调用必须首先出现在子构造函数中。

请注意,您可以自由地使用 显式命名构造函数this.constructor = function(arg1, ...) {...},或者this.constructor = function MyBaseClass(arg1, ...) {...}如果您需要从构造函数内部的代码简单访问构造函数,或者甚至return function MyBaseClass(arg1, ...) {...}像上面的代码一样简单地返回构造函数。哪个你觉得最舒服。

只需像通常从构造函数中一样从此类类中实例化对象:myObj = new MyBaseClass();

请注意闭包如何很好地封装类的所有功能,包括其原型和构造函数,为静态和实例、私有和公共属性和方法提供自然命名空间。类闭包中的代码完全没有约束。没有框架,没有约束,只是简单的旧 Javascript。闭包规则!

哦,如果您想将单例依赖项(例如服务)注入到您的类(即原型)中,clazz请使用 AngularJS 为您执行此操作:

DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype
    // the injected _service dependency is available anywhere in this class
    return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) {
        _super.apply(this, arguments); // or _super.call(this, arg1, ...)
        // the injected _service dependency is also available in the constructor
    };
}], MyBaseClass); // extend MyBaseClass

正如上面的代码试图说明的那样,要将单例注入到一个类中,只需将类闭包作为最后一个条目放入包含所有依赖项的数组中。还要将对应的参数添加到参数前面的类闭包中,_super并且与数组中的顺序相同。clazz将数组中的依赖项作为参数注入到类闭包中。然后依赖关系在类闭包中的任何地方都可用,包括构造函数。

事实上,由于依赖项被注入到原型中,它们甚至在从类实例化任何对象之前就可用于静态方法。这对于连接您的应用程序或单元和端到端测试非常强大。它还消除了将单例注入构造函数的需要,否则会不必要地破坏构造函数的代码。

检查这个小提琴:http: //jsfiddle.net/5uzmyvdq/1/

欢迎反馈和建议!

于 2015-03-22T09:58:39.003 回答
3

Javascript当然是OOP。您总是有多态性,但是您必须牺牲封装或实例化,这是您遇到的问题。

试试这个来复习你的选择。 http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/ 还有一个我收藏的老问题: JavaScript 是面向对象的吗?

于 2012-09-26T21:12:36.927 回答
2

JavaScript 类是在 ECMAScript 6 中引入的,是 JavaScript 现有的基于原型的继承的语法糖。类语法并未向 JavaScript 引入新的面向对象的继承模型。JavaScript 类提供了一种更简单、更清晰的语法来创建对象和处理继承。

您可以在此链接中查看更多信息Mozilla 社区

Github

于 2016-01-29T14:46:46.930 回答
0

我最近在思考这个特定的主题以及各种方法的局限性。我能想出的最佳解决方案如下。

它似乎解决了继承、实例化和封装的问题(至少来自 Google Chrome v.24 上的测试),尽管可能以内存使用为代价。

function ParentClass(instanceProperty) {
  // private
  var _super = Object.create(null),
      privateProperty = "private " + instanceProperty;
  // public
  var api = Object.create(_super);
  api.constructor = this.constructor;
  api.publicMethod = function() {
     console.log( "publicMethod on ParentClass" );
     console.log( privateProperty );
  };
  api.publicMethod2 = function() {
     console.log( "publicMethod2 on ParentClass" );
     console.log( privateProperty );
  };
  return api;
}

function SubClass(instanceProperty) {
    // private
    var _super = ParentClass.call( this, instanceProperty ),
        privateProperty = "private sub " + instanceProperty;
    // public
    var api = Object.create(_super);
    api.constructor = this.constructor;
    api.publicMethod = function() {
       _super.publicMethod.call(this); // call method on ParentClass
        console.log( "publicMethod on SubClass" );
        console.log( privateProperty );
    }
    return api;
}

var par1 = new ParentClass(0),
    par2 = new ParentClass(1),
    sub1 = new SubClass(2),
    sub2 = new SubClass(3);

par1.publicMethod();
par2.publicMethod();
sub1.publicMethod();
sub2.publicMethod();
par1.publicMethod2();
par2.publicMethod2();
sub1.publicMethod2();
sub2.publicMethod2();
于 2013-02-19T13:21:02.737 回答
0

许多 JS 类的一个问题是它们不保护它们的字段和方法,这意味着任何使用它的人都可能不小心替换了一个方法。例如代码:

function Class(){
    var name="Luis";
    var lName="Potter";
}

Class.prototype.changeName=function(){
    this.name="BOSS";
    console.log(this.name);
};

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced");
};
test.changeName();
test.changeName();

将输出:

ugly
BOSS
replaced 
replaced 

如您所见,changeName 函数被覆盖。以下代码将保护类方法和字段,并使用 getter 和 setter 来访问它们,使其更像是其他语言中的“常规”类。

function Class(){
    var name="Luis";
    var lName="Potter";

    function getName(){
         console.log("called getter"); 
         return name;
    };

    function setName(val){
         console.log("called setter"); 
         name = val
    };

    function getLName(){
         return lName
    };

    function setLName(val){
        lName = val;
    };

    Object.defineProperties(this,{
        name:{
            get:getName, 
            set:setName, 
            enumerable:true, 
            configurable:false
        },
        lastName:{
            get:getLName, 
            set:setLName, 
            enumerable:true, 
            configurable:false
        }
    });
}

Class.prototype.changeName=function(){
    this.name="BOSS";
};   

Object.defineProperty(Class.prototype, "changeName", {
    writable:false, 
    configurable:false
});

var test= new Class();
console.log(test.name);
test.name="ugly";
console.log(test.name);
test.changeName();
test.changeName=function(){
    console.log("replaced")
};
test.changeName();
test.changeName();

这输出:

called getter
Luis
called setter 
called getter 
ugly 
called setter 
called setter 
called setter 

现在,您的类方法不能被随机值或函数替换,并且 getter 和 setter 中的代码在尝试读取或写入字段时始终运行。

于 2014-09-04T22:41:39.807 回答
-1

此闭包允许实例化和封装,但不允许继承。

function Foo(){
    var _bar = "foo";

    return {
        getBar: function() {
            return _bar;
        },
        setBar: function(bar) {
            _bar = bar;
        }
    };
};

a = Foo();
b = Foo();

a.setBar("bar");
alert(a.getBar()); // "bar"
alert(b.getBar()); // "foo"
于 2014-01-03T03:08:38.983 回答