JavaScript面向对象编程的最佳实践?

13

我已经厌倦了在Javascript中看到各种不同的面向对象编程技术。请问有人能告诉我,如果我想要在一个大型项目上工作,并希望我的代码具备未来性,应该使用哪种技术呢?


1
可能是Javascript OO syntax的重复问题。 - spender
3
我喜欢这个问题已经有了三种不同的JavaScript面向对象表示法作为答案,而这个问题的目的是提供一个单一的表示法供 OP 使用。有各种不同的表示法-我认为为了明确起见,应该在答案本身中添加每种表示法的优缺点,供 OP 做出决定。 - cthulhu
如果你需要一个快速而简单的解决方案,可以看看 https://github.com/haroldiedema/joii。它允许你像预期的那样使用类、接口和 traits/mix-ins。它非常小巧轻便,只实现了你需要的功能,并且甚至支持 IE6。 - Harold
可能是https://dev59.com/D3NA5IYBdhLWcg3wku7O的重复问题,面向对象JavaScript最佳实践。 - Saif
9个回答

12

以下是我提出的一些快速指南,如果有其他有意义的补充,请编辑此答案。

  1. 为你的对象命名空间,以确保它们永远不会与第三方JavaScript库冲突。
  2. window['Andrew']['JS'] = {
        addEvent: function(el,evName) {/*代码*/},
        Rectangle: function(width,height) {/*代码*/}
    };
    然后,你可以使用如下方式创建一个矩形对象:
    var myRect = new Andrew.JS.Rectangle(14,11);
    这样,你的代码就不会与任何其他人的Rectangle发生冲突或被干扰。

  3. 使用一致的命名策略,具体而言包括:
    • 对象名应该大写,其他所有内容(变量、函数)应该以小写字母开头。例如:
      var myRect = new Andrew.JS.Rectangle(14,11);
      document.write(myRect.getArea());
    • 确保所有内容都有意义,即方法使用动词,参数使用名词和形容词。

  4. 确保所有方法和参数与它们所属的对象相关。例如: 在这个例子中,矩形的面积可以使用方法inSquareFeet()转换为平方英尺。
    myRect.getAreaObject().inSquareFeet();
    确保inSquareFeet()是由getAreaObject()返回的对象的方法,而不是Andrew.JS.Rectangle的一个方法。

  5. 使用构造函数,或者更具体地说,尽可能做到一旦构建对象就不需要任何进一步的初始化即可使用,因此不要像这样:
    var Person = function()
    {
        this.name = "";
        this.sayHello = function ()
        {
            alert(this.name + " says 'Hello!'");
            return this;
        }
    }
    var bob = new Person(); bob.name = "Bob Poulton"; bob.sayHello();
    应该改为:
  6. function Person(name) {
        this.name = name;
    }
    Person.prototype.sayHello = function() { alert(this.name + " says 'Hello!'"); };
    var bob = new Person("Bob Poulton"); bob.sayHello();
var Person = function(name)
{
    this.name = name;
    this.sayHello = function ()
    {
        alert(this.name + " 说:'你好!'");
        return this;
    }
}
var bob = new Person("Bob Poulton"); bob.sayHello();

为什么要使用 window['Andrew']['JS'] = ...,而不是 window.Andrew.JS = ...,后者打字更少,更容易阅读? - slebetman
2
最好将sayHello分配给构造函数的原型,就像这样Person.prototype.sayHello = function() {...}。这样,该方法将对所有Person实例通用。你现在所做的方式不太有效,因为每个Person实例都将包含自己的sayHello副本。 - Vitaly

7

1
//Create and define Global NameSpace Object
( function(GlobalObject, $, undefined) 
{
    GlobalObject.Method = function()
    {
        ///<summary></summary>
    }

    GlobalObject.Functionality = {};

}) (GlobalObject = GlobalObject || {}, jQuery);

//New object for specific functionality
( function(Events, $, undefined)
{
    //Member Variables 
    var Variable; // (Used for) , (type)

    // Initialize
    Events.Init = function()
    {
        ///<summary></summary>
    }

    // public method
    Events.PublicMethod = function(oParam)
    {
        ///<summary></summary>
        ///<param type=""></param>
    }

    // protected method (typically define in global object, but can be made available from here)
    GlobalObject.Functionality.ProtectedMethod = function()
    {
        ///<summary></summary>
    }

    // internal method (typically define in global object, but can be made available from here)
    GlobalObject.InternalMethod = function()
    {
        ///<summary></summary>
    }

    // private method
    var privateMethod = function()
    {
        ///<summary></summary>
    }
}) (GlobalObject.Funcitonality.Events = GlobalObject.Funcitonality.Events || {}, jQuery )

// Reusable "class" object
var oMultiInstanceClass = function()
{
    // Memeber Variables again
    var oMember = null; // 

    // Public method
    this.Init = function(oParam)
    {
        oMember = oParam;

        for ( n = 1; i < oMemeber.length; i += 1 )
        { 
           new this.SubClass.Init(oMember[i]); // you get the point, yeah?
        }
    }

    this.Subclass = function()
    {
        this.Init = function() { }
    }
}

这种方法的优点在于它可以自动初始化全局对象,使您能够保持代码的完整性,并按照您的定义将每个功能片段组织成特定的分组。

该结构是坚实的,呈现了所有基本的面向对象编程语法要素,而不需要关键字。

甚至还有一些巧妙的方法来设置接口。如果你选择走得更远,一个 简单的搜索 将为你提供一些很好的教程和提示。

使用 JavaScript 和 Visual Studio,甚至可以设置Intellisense,然后定义每个部分并引用它们,使编写JavaScript更加清晰和易于管理。

根据您的实际情况使用这三种方法有助于保持全局命名空间的干净,保持代码组织并保持每个对象的关注点分离... 如果使用正确。请记住,如果您不利用使用对象背后的逻辑,面向对象设计将毫无用处!


1

我对面向对象编程的理想对象就像使用原型实例方法一样:

例如:

var Users = function()
{
    var _instance;
    this.prototype.getUsername = function(){/*...*/}
    this.prototype.getFirstname = function(){/*...*/}
    this.prototype.getSecurityHash = function(){/*...*/}
    /*...*/

    /*Static Methods as such*/
    return { /*Return a small Object*/
        GetInstance : function()
        {
            if(_instance == null)
            {
                _instnance = new Users(arguments);
            }
            return _instnance; //Return the object
        },
        New: function()
        {
            _instnance = null; //unset It
            return this.GetInstnace(arguments);
        }
    }
}

然后我通常会使用类似于:

Firstname = Users.GetInstance('Robert','Pitt').getFirstname();
Username  = Users.GetInstance().getUsername(); //Returns the above object.

Me  = Users.New('Robert',null); //Deletes the above object and creates a new instance.
Father = Users.New('Peter','Piper'); //New Object
Me.AddFather(Father); //Me Object.

这就是我在构建JavaScript面向对象风格架构时所走的路。


如所写,Users 没有 GetInstance 属性。你是不是想在将结果分配给 Users 之前调用匿名函数? - outis

1
由于你正在进行大规模项目的开发,我建议使用像mootools http://mootools.net/ 这样的javascript框架。
它具有良好的类和继承结构。

1

只是提供给你的信息,我认为YUI在这个主题上提供了一些很棒的教程。


0
我使用这种模式,并建议您也使用它:
function Person(firstname, lastname, age)
{
  var self = this;
  var _ = {};

  // Private members.  
  var firstname  = firstname;
  var lastname = lastname;
  var age = age || 'unknown';

  // Private methods.
  function first_letter_to_uppercase(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  }

  // Public members and methods.
  _.get_age = function()
  {
    return age;
  }

  _.get_name = function()
  {
    return first_letter_to_uppercase(firstname) + ' ' +
      first_letter_to_uppercase(lastname);
  }
  return _;
}

var p = new Person('vasya', 'pupkin', 23);
alert("It's "  + p.get_name() + ', he is ' + p.get_age() + ' years old.') 

2
通过从函数 Person 返回一个对象,您会失去继承。 您无法通过将属性添加到“Person.prototype”来扩展实例的行为。 - outis
1
“var firstname = firstname” 这一部分是不必要的,因为参数 “firstname” 已经是一个私有变量。 - slebetman

0
function foo() {
  var bar = function() { console.log("i'm a private method"); return 1; };
  var iAmAPrivateVariable = 1;

  return {
    publicMethod: function() { alert(iAmAPrivateVariable); },
    publicVariable: bar()
  }
}

//usage
var thing = foo()

这是一种功能性的方法,比其他任何你看到的方法(如封装)都更具优势。

一般来说,你不应该在JavaScript中使用面向对象编程,因为它并不是一个非常适合面向对象编程的语言,有很多原因。想象一下带有花括号和分号的Scheme,你就会像专业人士一样编写这种语言。尽管如此,有时候面向对象编程更适合。在这些情况下,上述方法通常是最好的选择。

将继承引入其中

function parent() {
  return { parentVariable: 2 };
}

function foo() {
  var bar = function() { console.log("i'm a private method"); return 1; };
  var iAmAPrivateVariable = 1;

  me = parent();
  me.publicMethod = function() { alert(iAmAPrivateVariable); };
  me.publicVariable = bar();

  return me;
}

这使得事情变得有点复杂,但在仍然采用面向对象的功能方法(在这种情况下,使用装饰器函数而不是真正的继承)的同时实现了期望的最终结果。我喜欢整个方法的原因是我们仍然真正地按照这种语言中对象的预期方式处理它们——一个属性袋,您可以随意附加东西。

另一个注意点是,这与您在大多数工作中通常看到的情况大不相同,并且往往很难解释a)正在发生什么,以及b)为什么向同事介绍这个好主意。


-2
你可以试试一个简单、实用和快速的对象:
var foo = {
    foo1: null,
    foo2: true,
    foo3: 24,
    foo4: new Array(),

    nameOfFunction1: function(){
      alert("foo1");
    },

    nameOfFunction2: function(){
      alert("foo2");
    },
}

要使用它,您必须创建此对象的实例并像Java中的对象一样使用:

foo.nameOfFunction2();

另外,您也可以查看此链接以获取其他解决方案:http://www.javascriptkit.com/javatutors/oopjs.shtml

希望这能回答您的问题。


1
由于 foo 不是一个函数,所以 new foo() 会失败。 - outis
是的,你说得对。抱歉,我已经重写了这个调用。 - ferran87

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接