JavaScript 面向对象语法

6

在JavaScript中,似乎有许多不同的实现面向对象编程的方法。

我喜欢:

function ClassA(){};
ClassA.prototype={
    someFunc:function(a,b,c){},
    otherFunc:function(){}
}
var c=new ClassA();

我从未使用过这提供的以外的功能(尽管我是一个熟练的面向对象程序员)。我怀疑这是老派的,因为我偶尔会看到新的花哨变种,这让我想知道我是否选择了最佳方法。例如,在构造函数中可以执行魔法来创建私有变量和访问器方法,这是我曾经认为不可能的事情(直到最近)。子类化呢?我不知道如何实现这一点,但现在肯定有某种常见模式。

您如何做到这一点以及为什么?

7个回答

6
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中唯一的方法)。

总的来说,在JavaScript中不应该使用面向对象编程,由于很多原因它并不是一个很好的语言。将其视为带有花括号和分号的Scheme(一种Lisp方言),您将开始像专业人士一样编写代码。尽管如此,有时候面向对象编程更适合某些情况。在这些情况下,通常以上述方法为最佳选择。

编辑:将继承加入此混合中

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;
}

这使得事情变得有些复杂,但是通过使用装饰器函数而不是真正的继承,实现了期望的最终结果,同时仍然采用了面向对象概念的功能性方法。我喜欢整个方法的原因是我们仍然按照这种语言的意图来处理对象——将其视为一个属性包,可以随意附加内容。
编辑2:
只是想给予Doug Crockford在JavaScript: The Good Parts中建议的一点简化方法的信用。如果你想将你的js技能提高到下一个水平,我强烈建议从那里开始。这与你在大多数工作中看到的情况大相径庭,并且通常很难解释a)正在发生什么,以及b)为什么向同事解释是个好主意。

我很喜欢这个。能不能在不使用原型的情况下弯曲它以提供继承? - spender
bar 将成为全局对象的属性 (window.bar),因为它没有在 foo 函数的作用域中使用 var 语句进行声明。此外,在 ECMAScript 5 严格模式下,这将导致 ReferenceError 错误,因此请始终使用 var声明 变量 ;) - Christian C. Salvadó
这种方法的唯一问题是它返回一个匿名对象,无法再被识别为 foo 的实例。 - casablanca
@casablanca:这完全是正确的,但在动态语言中99.9%的时间里,这也不是那么重要。你关心一个对象能做什么,而不是它是什么。 - Matt Briggs
正是Crockford关于使用闭包来实现私有变量的文章激发了我提出这个问题。我对那些试图过度模拟基于类的面向对象编程的库深感怀疑,因为我认为这不是JS所擅长的,否则早就有语言支持了。我非常喜欢将函数式概念应用到Javascript中。我想我会采纳你的书籍推荐。谢啦! - spender
我完全不担心那些不理解或不愿意理解的人... 我不会在我的公司雇用这样的人! - spender

5

1
是的,但他也让 $ 成为了一个变量名... 不可饶恕。 - spender

2
在JavaScript中,“子类化”通常指原型继承,其基本遵循以下模式:
function Superclass() { }
Superclass.prototype.someFunc = function() { };

function Subclass() { }
Subclass.prototype = new Superclass();
Subclass.prototype.anotherFunc = function() { };

var obj = new Subclass();

这将从obj -> Subclass.prototype -> Superclass.prototype -> Object.prototype构建一个“原型链”。
几乎所有的JavaScript面向对象编程库都基于这种技术,提供了抽象大部分原型“魔法”的函数。

这不是一个好主意;它依赖于调用父构造函数来获取子构造函数的原型。最好做一些像这样的事情(请参见我的回应):function C(){}; function clone(obj){C.prototype=obj, return new C}; Subclass.prototype=clone(Superclass.prototype);... 这样父构造函数就不会被调用,这可能会产生不必要的副作用。 - Dagg Nabbit

1

1

JavaScript中的对象与几乎所有其他高级语言都不同。它们不像Java、C++、PHP等等那样是基于类的,而是基于原型的。因此,面向对象编程的基本范式必须被大幅修改。那些无法或不想重新思考这一点,并坚持使用基于类的思维方式的人,必须在JavaScript中构建基于类的逻辑,或者使用已经构建好的来自他人的代码。


完全同意。在JavaScript中伪造类,就是试图把Java硬塞到一些只有表面相似的东西里。 - Matt Briggs

0

我喜欢做类似于

// namespace "My"
var My = new function {

  // private methods
  /**
   * Create a unique empty function.
   * @return {Function} function(){}
   */
  function createFn () {return function(){}}

  /** A reusable empty function. */
  function emptyFn () {}

  /**
   * Clone an object
   * @param {Object}  obj     Object to clone
   * @return {Object}         Cloned object
   */
  function clone (obj) { emptyFn.prototype=obj; return new emptyFn() }

  // public methods
  /**
   * Merge two objects
   * @param {Object} dst        Destination object
   * @param {Object} src        Source object
   * @param {Object} [options]  Optional settings
   * @return {Object}           Destination object
   */
  this.merge = function (dst, src, options) {
    if (!options) options={};
    for (var p in src) if (src.hasOwnProperty(p)) {
      var isDef=dst.hasOwnProperty(p);
      if ((options.noPrivate && p.charAt(0)=='_') || 
          (options.soft && isDef) || 
          (options.update && !isDef)) continue;
      dst[p]=src[p]; 
    }
    return dst;
  }

  /**
   * Extend a constructor with a subtype
   * @param {Function} superCtor      Constructor of supertype
   * @param {Function} subCtor        Constructor of subtype
   * @param {Object} [options]        Optional settings
   * @return {Function}               Constructor of subtype
   */
  this.extend = function (superCtor, subCtor, options) {
    if (!subCtor) subCtor=createFn();
    if (!options) options={};
    if (!options.noStatic) this.merge(subCtor, superCtor, options); 
    var oldProto=subCtor.prototype;
    subCtor.prototype=clone(superCtor.prototype);
    this.merge(subCtor.prototype, oldProto);
    if (!options.noCtor) subCtor.prototype.constructor=subCtor; 
    return subCtor;
  }

}

然后就是类似这样的代码...

// namespace "My.CoolApp"
My.CoolApp = new function(){

  // My.CoolApp.ClassA
  this.ClassA = new function(){

    // ClassA private static
    var count=0; 

    // ClassA constructor 
    function ClassA (arg1) {
      count++;
      this.someParam=arg1;
    }

    // ClassA public static
    My.merge(ClassA, { 
      create: function (arg1) {
        return new ClassA(arg1);
      }
    }

    // ClassA public
    My.merge(ClassA.prototype, {
      doStuff : function (arg1) {
        alert('Doing stuff with ' + arg1);
      },
      doOtherStuff : function (arg1) {
        alert('Doing other stuff with ' + arg1);
      }
    }

    return ClassA;
  }

  // My.CoolApp.ClassB
  this.ClassB = new function(){

    My.extend(My.CoolApp.ClassA, ClassB);
    // ClassB constructor
    function ClassB () {
      ClassA.apply(this, arguments);
    }

    return ClassB;
  }

}

...clone 函数是实现继承的关键。简而言之:

  • 通过将对象设置为一个临时函数的原型并使用 'new' 调用该函数来克隆对象。
  • 克隆父构造函数的原型,并将其结果设置为子类的原型。

0

JavaScript中的面向对象编程与Canvas

看看JavaScript中的面向对象编程在不同情况下可以有多么有用... 这让你可以将正方形和圆形作为对象绘制,以便您可以返回并循环或操纵它们。

function Shape(x,y,color){
  this.x = x
  this.y = y
  this.color = color
}

function Square(height,width,color){
  Shape.call(this, event.x, event.y, color)
  this.height = height
  this.width = width
  this.x -= canvas.offsetLeft + (this.height/2)
  this.y -= canvas.offsetTop + (this.width/2)
}

Square.prototype = new Shape();
Square.prototype.draw = function(color){
  ctx.fillStyle = color
  ctx.fillRect(this.x,this.y,this.height,this.width)
}

function Circle(color, width){
  Shape.call(this)
  this.x = event.x -60
  this.y = event.y -60
  this.width = width
 }

Circle.prototype = new Shape();
Circle.prototype.draw = function(color){
  ctx.beginPath()
  ctx.arc(this.x,this.y,this.width,0,2*Math.PI, false);
  ctx.fillStyle = color
  ctx.fill()
}

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