如何在JavaScript中“正确”创建自定义对象?


471

我不知道最好的方法是创建具有属性和方法的JavaScript对象。

我已经看到了示例,该示例中的人员使用var self = this然后self.在所有功能中使用以确保范围始终正确。

然后,我看到了.prototype用于添加属性的示例,而其他示例则是内联的。

有人可以给我一个带有某些属性和方法的JavaScript对象的正确示例吗?


13
没有“最佳”方法。
三联画

这不是 self保留字吗?如果不是,应该这样;因为self是预定义变量,引用了当前窗口。self === window
Shaz

2
@Shaz:它不是保留字,它不像window浏览器对象模型中的其他属性(例如documentframes;您当然可以将标识符用作变量名。是的,虽然从风格上讲,我还是希望var that= this避免任何可能的混乱。即使window.self最终毫无意义,所以几乎没有任何理由要触摸它。
bobince 2011年

7
缩小JS后,分配this给局部变量(例如self)可减小文件大小。
Patrick Fisher

Classjs的新链接:github.com/divio/classjs
Nikola

Answers:


889

有两种用于在JavaScript中实现类和实例的模型:原型方式和闭包方式。两者都有优点和缺点,并且有很多扩展的变体。许多程序员和库使用不同的方法和类处理实用程序功能来覆盖该语言的某些较丑陋的部分。

结果是,在混合公司中,您将拥有各种各样的元类,它们的行为略有不同。更糟糕的是,大多数JavaScript教程材料都很糟糕,并且在某种程度上折衷以覆盖所有基础,使您非常困惑。(可能作者也很困惑。JavaScript的对象模型与大多数编程语言有很大不同,并且在许多地方,设计不当也是直接的。)

让我们从原型方式开始。这是您可以获得的最多JavaScript原生语言:最少的开销代码,instanceof将与此类对象的实例一起使用。

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

通过将方法new Shape写入prototype此构造函数的查找中,我们可以将方法添加到创建的实例中:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

现在可以对其进行子类化,您最多可以调用JavaScript进行子类化的内容。我们通过完全替换该怪异的魔术prototype属性来做到这一点:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

在向其中添加方法之前:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

这个示例将起作用,并且在许多教程中您将看到类似的代码。但是,这new Shape()很丑陋:即使没有创建实际的Shape,我们也要实例化基类。由于JavaScript非常草率,因此它可以在这种简单情况下工作:它允许传入零个参数,在这种情况下x,它y成为undefined并被分配给原型的this.xand this.y。如果构造函数执行任何更复杂的操作,则它的外观会平坦。

因此,我们需要做的是找到一种创建原型对象的方法,该对象在类级别包含我们想要的方法和其他成员,而无需调用基类的构造函数。为此,我们将不得不开始编写辅助代码。这是我所知道的最简单的方法:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

这会将基类原型中的成员转移到一个新的构造函数,该函数不执行任何操作,然后使用该构造函数。现在我们可以简单地写:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

而不是new Shape()错误。现在,对于构建类,我们有了一组可接受的原语。

在此模型下,我们可以考虑一些改进和扩展。例如,这里是语法糖版本:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

两种版本都具有无法继承构造函数的缺点,就像许多语言一样。因此,即使您的子类在构造过程中未添加任何内容,它也必须记住使用基本所需的任何参数来调用基本构造函数。使用可以稍微自动化apply一下,但是仍然需要写出:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

因此,一个常见的扩展是将初始化内容分解为自己的函数,而不是构造函数本身。然后,该函数可以从基础继承而来:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

现在,每个类都有相同的构造函数样板。也许我们可以将其移到其自己的帮助器函数中,所以我们不必继续键入它,例如,而不是Function.prototype.subclass,将其舍入并让基类的Function吐出子类:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

...虽然看起来语法稍微有些笨拙,但开始看起来与其他语言有点相似。如果愿意,您可以添加一些其他功能。也许您想makeSubclass记住一个类名并toString使用它提供一个默认值。也许您想让构造函数检测在没有new运算符的情况下意外调用了它(否则通常会导致非常烦人的调试):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

也许您想传递所有新成员并将其makeSubclass添加到原型中,以免您不得不编写Class.prototype...太多代码。许多类系统都这样做,例如:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

在对象系统中,您可能认为有许多潜在的功能是理想的,没有人真正同意一个特定的公式。


然后是封闭方式。通过完全不使用继承,避免了JavaScript基于原型的继承问题。代替:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

现在,每个的实例Shape都有其toString方法的副本(以及我们添加的任何其他方法或其他类成员)。

每个实例都有自己的每个类成员的副本的坏处是效率较低。如果您要处理大量的子类实例,那么原型继承可能会为您提供更好的服务。如您所见,调用基类的方法也有点烦人:我们必须记住该方法在子类构造函数重写它之前就被忘了。

[而且因为这里没有继承,所以instanceof运算符将无法工作;如果需要,您将必须提供自己的类嗅探机制。尽管您可以通过与原型继承类似的方式摆弄原型对象,但是这有点棘手,只是为了instanceof工作而并不值得。]

每个实例都有自己的方法的好处是,该方法可以绑定到拥有它的特定实例。这是有用的,因为JavaScript this在方法调用中进行了奇怪的绑定方式,如果您将方法与其所有者分离,则会产生以下结果:

var ts= mycircle.toString;
alert(ts());

那么this该方法内部将不会像预期的那样是Circle实例(它实际上是全局window对象,从而导致广泛的调试麻烦)。实际上,这通常发生在采用某个方法并将其分配给时setTimeoutonclick或者EventListener通常情况下。

使用原型方法,您必须为每个此类分配包括一个闭包:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

或者,将来(或现在,如果您破解Function.prototype),也可以使用function.bind()

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

如果您的实例是通过闭包方式完成的,则绑定是通过实例变量的闭包免费完成的(通常称为thatself,但我个人不建议后者,因为self在JavaScript中已经具有另一种不同的含义)。但是,您没有1, 1在上面的代码段中免费获取参数,因此您仍然需要另一个闭包,或者bind()如果需要这样做的话。

闭包方法也有很多变体。您可能希望this完全省略,创建一个新的that并返回它,而不是使用new运算符:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

哪种方法合适?都。哪个是“最佳”?那要看你的情况了。FWIW,当我做大量面向对象的事情时,我倾向于为真正的JavaScript继承创建原型,并为简单的一次性页面效果使用闭包。

但是,这两种方式对于大多数程序员来说都是违反直觉的。两者都有许多潜在的混乱变化。如果您使用其他人的代码/库,那么您将同时遇到这两种情况(以及许多中间方案和通常不完善的方案)。没有一个普遍接受的答案。欢迎来到JavaScript对象的美好世界。

[这是“为什么JavaScript不是我最喜欢的编程语言”的第94部分。]


13
从“ class” def到对象实例化的非常好的逐步过渡。绕过旁路也很不错new

8
似乎JavaScript不是您最喜欢的语言,因为您希望像使用类一样使用它。
乔纳森·芬伯格

59
我当然也这样做,每个人也是如此:对于当今程序员面临的大多数常见问题,类和实例模型是更自然的模型。我确实同意,从理论上讲,基于原型的继承可以潜在地提供更灵活的工作方式,但是JavaScript完全不能兑现这一承诺。它笨拙的构造函数功能给我们带来了两全其美的局面,使类类继承变得困难,同时也没有提供原型可以提供的灵活性或简单性。简而言之,它是便便。
bobince

4
Bob,我认为这是一个了不起的答案-我一直在努力解决这两种模式,而且我认为您编写的代码比Resig更简洁,并且比Crockford解释得更深入。我想不出有什么更高的赞誉了……。–
詹姆斯·韦斯特盖特

4
在我看来,将经典继承范例绘制到javascript之类的原型语言上似乎总是一个方形钉和一个圆孔。是否有时确实需要这样做,或者这只是人们将其作为自己想要的方式而不是简单地使用该语言的一种方式?
2011年

90

我相当频繁地使用这种模式-我发现它在需要时给了我很大的灵活性。在使用中,它非常类似于Java样式的类。

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

这将使用创建时调用的匿名函数,并返回一个新的构造函数。因为匿名函数仅被调用一次,所以您可以在其中创建私有静态变量(它们在闭包内部,对于该类的其他成员可见)。构造函数基本上是一个标准的Javascript对象-您在其中定义私有属性,并将公共属性附加到this变量中。

基本上,这种方法将Crockfordian方法与标准Javascript对象结合在一起,以创建功能更强大的类。

您可以像使用其他任何Javascript对象一样使用它:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
这看起来很有趣,因为它非常接近我的C#“ home-turf”。我还认为我开始理解为什么privateStaticVariable真正是私有的(因为它是在函数的范围内定义的,并且只要有引用就保持活动?)
Michael Stum

由于未使用this它,还需要使用实例化它new吗?
Jordan Parmer

实际上,this 确实会在constructor函数中使用,Foo在示例中将变为。
ShZ 2013年

4
这里的问题是,每个对象都具有自己的所有私有和公共功能的副本。
virtualnobi 2013年

2
@virtualnobi:此模式不会阻止您编写原型方法:constructor.prototype.myMethod = function () { ... }
2014年

25

道格拉斯·克罗克福德(Douglas Crockford)《好零件》中广泛讨论了该主题。他建议避免操作员创建新对象。相反,他建议创建定制的构造函数。例如:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

在Javascript中,函数是对象,可用于与new运算符一起构造对象。按照约定,打算用作构造函数的函数以大写字母开头。您经常会看到类似的东西:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

如果您在实例化一个新对象时忘记使用new运算符,则得到的是一个普通的函数调用,并且函数绑定到全局对象而不是新对象。


5
是我还是我认为Crockford对新运营商的抨击完全没有道理?
meder omuraliev 2009年

3
@meder:不只是你。至少,我认为新操作符没有错。而且总有一个隐含newvar that = {};
Tim Down

17
克罗克福德(Crockford)是个老古董,我在很多方面都与他不同意,但他至少在推动对JavaScript进行批判性研究,值得听他说些什么。
bobince

2
@bobince:同意。大约5年前,他在封盖方面的著作使我大开眼界,并鼓励采取周到的方法。
Tim Down

20
我同意克罗克福德。与new运算符有关的问题是,JavaScript将使“ this”的上下文与其他调用函数时的上下文非常不同。尽管有适当的大小写约定,但由于开发人员忘记使用new或忘记使用大写字母等,较大的代码库仍会出现问题。为求实际,您可以不用new关键字就可以做所有需要做的事情-为什么要使用new关键字和在代码中引入更多的故障点?JS是一种原型语言,而不是基于类的语言。那么为什么我们要它像一种静态类型的语言一样起作用呢?我当然不会。
约书亚·拉米雷斯

13

继续bobince的答案

在es6中,您现在可以实际创建一个 class

现在,您可以执行以下操作:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

因此,可以延伸到一个圆圈(如其他答案所示):

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

最终在es6中变得更干净,更易于阅读。


这是一个有效的例子:


6

您也可以使用结构以这种方式进行操作:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

然后 :

var counter1 = createCounter();
counter1.increaseBy(4);

6
我不喜欢这种方式,因为空格很重要。返回值之后的卷曲必须在同一行上,以实现跨浏览器的兼容性。
geowa4,2009年

5

另一种方式是http://jsfiddle.net/nnUY4/ (我不知道这种处理对象创建和显示功能是否遵循任何特定模式)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

当在构造函数调用期间使用关闭“ this”的技巧时,是为了编写一个函数,该函数可以被其他不想在对象上调用方法的对象用作回调。它与“使范围正确”无关。

这是一个普通的JavaScript对象:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

阅读Douglas Crockford对JavaScript的评价,可能会使您受益匪浅约翰·雷西格John Resig)也很聪明。祝好运!


1
呃,关闭周围this与“使范围正确”有关。
Roatin Marth,2009年

3
乔纳森是对的。js函数的范围是您要设计的范围。self = this技巧是将其绑定到特定实例的一种方法,因此在另一个上下文中调用时,它不会更改。但这有时就是您真正想要的。取决于上下文。
Marco Marco

我想你们实际上都是在说同样的话。self=this尽管不会强制this保留,但可以轻松地通过闭包实现“正确”的作用域。

2
这样做的原因是为了使嵌套函数可以访问构造函数中存在的该函数的范围。当嵌套函数位于构造函数内部时,它们的“ this”作用域将还原为全局作用域。
2012年

4

Closure是多功能的。在创建对象时,bobince很好地总结了原型方法与封闭方法。但是,您可以模仿OOP以功能编程方式使用闭包的某些方面。记住函数是JavaScript中的对象 ; 因此以不同的方式将函数用作对象。

这是关闭的示例:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

前一段时间,我碰到了Mozilla关于Closure的文章。这是我的看法:“闭包可让您将某些数据(环境)与对该数据进行操作的函数相关联。这与面向对象编程(其中对象允许我们将某些数据(对象的属性)相关联”具有明显的相似之处)使用一种或多种方法 ”。这是我第一次阅读闭包与经典OOP之间的并行性,而没有引用原型。

怎么样?

假设您要计算某些商品的增值税。增值税可能会在申请有效期内保持稳定。在OOP(伪代码)中执行此操作的一种方法:

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

基本上,您将VAT值传递给构造函数,然后您的calculate方法可以通过闭包对其进行操作。现在,不使用类/构造函数,而是将VAT作为参数传递给函数。因为您唯一感兴趣的是计算本身,所以返回一个新函数,即calculate方法:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

在您的项目中,确定最适合用来计算增值税的顶级值。根据经验,每当您不断传递相同的参数时,就有一种使用闭包改进它的方法。无需创建传统对象。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Closures


3

创建一个对象

在JavaScript中创建对象的最简单方法是使用以下语法:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

这对于以结构化方式存储数据非常有用。

但是,对于更复杂的用例,通常最好创建函数实例:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

这使您可以创建共享同一“蓝图”的多个对象,类似于您在eg中使用类的方式。Java。

但是,仍然可以通过使用原型来更高效地完成此操作。

每当函数的不同实例共享相同的方法或属性时,就可以将它们移至该对象的原型。这样,函数的每个实例都可以访问该方法或属性,但是不必为每个实例都复制它。

在我们的例子中,将方法f移至原型是有意义的:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

遗产

在JavaScript中进行继承的一种简单但有效的方法是使用以下两种方法:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

这类似于这样做:

B.prototype = new A();

两者之间的主要区别是A使用时不会运行的构造函数Object.create,这更直观,更类似于基于类的继承。

您可以随时选择A在创建的新实例时运行的构造函数,方法B是将其添加到的构造函数中B

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

如果你想传递的所有参数BA,你也可以使用Function.prototype.apply()

function B() {
    A.apply(this, arguments); // This is optional
}

如果要将另一个对象混入的构造函数链中B,则可以Object.create与结合使用Object.assign

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

演示版

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


注意

Object.create可以在包括IE9 +在内的所有现代浏览器中安全使用。Object.assign不适用于任何版本的IE或某些移动浏览器。建议使用polyfill Object.create和/或Object.assign如果要使用它们并支持未实现它们的浏览器。

你可以找到一个填充工具对Object.create 这里 和一个Object.assign 在这里


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
这对已经提供的众多广泛答案有什么补充?
blm 2015年

我喜欢这个答案,因为它简洁明了,并且展示了实现的三个部分:1)定义对象,2)实例化对象的实例,3)使用实例-实例一目了然,而不需要解析通过上面所有详细的答案(当然,这些答案都是非常好的答案,其中包含了人们想要的所有相关细节)-这里是一个简单的摘要
G-Man

0

除了2009年接受的答案。如果您可以针对现代浏览器,则可以使用Object.defineProperty

Object.defineProperty()方法直接在对象上定义新属性,或修改对象上的现有属性,然后返回该对象。资料来源:Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

要查看桌面和移动设备兼容性列表,请参见Mozilla的浏览器兼容性列表。是的,IE9 +和Safari移动版都支持它。


0

您也可以尝试

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
一种为我服务的模式
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

首先,您可以更改向实例而不是构造函数的prototypeobject 添加方法的首选项。我几乎总是在构造函数内部声明方法,因为出于与继承和装饰器有关的目的,我经常使用构造函数劫持

这是我决定将哪些声明写入何处的方法:

  • 切勿直接在上下文对象(this)上声明方法
  • var声明优先于function声明
  • 让基元优先于对象({}[]
  • public声明优先于private声明
  • 身高Function.prototype.bind超过thusselfvmetc
  • 避免在另一个类中声明一个类,除非:
    • 显而易见,两者是密不可分的
    • 内部类实现命令模式
    • 内部类实现单例模式
    • 内部类实现状态模式
    • 内部类实现了另一个设计模式,可以保证这一点
  • 始终this从封闭空间的词汇范围内返回。

这就是这些帮助的原因:

构造器劫持
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
模型设计
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
设计模式
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

如您所见,thus由于我更喜欢Function.prototype.bind(or .callor .applythus,所以我真的不需要。在我们的Singleton课堂上,我们甚至没有命名它,thus因为它INSTANCE传达了更多信息。对于Model,我们返回,this以便我们可以使用构造函数.call来返回传递给它的实例。冗余地,我们将其分配给了变量updated,尽管它在其他情况下很有用。

另外,我更喜欢使用new关键字而不是{brackets} 来构造对象文字:

首选的
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
不喜欢
var klass = Super.apply({
    override: x
});

如您所见,后者无法覆盖其超类的“ override”属性。

如果我确实向类的prototype对象添加方法,则我更喜欢使用对象文字-使用或不使用new关键字:

首选的
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
不喜欢
Klass.prototype.method = function m() {...};

0

我想提一下,我们可以使用标题字符串来声明对象。
调用每种类型有不同的方法。见下文:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

基本上,JS中没有类的概念,因此我们将函数用作与现有设计模式相关的类构造函数。

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

到现在为止,JS都不知道您想创建对象,因此new关键字就来了。

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

参考:面向Web开发人员的专业JS-Nik Z


Downvote接受:有正当理由会提供更多信息,并且有机会提供改进的机会。
Airwind711 2015年

这里一个概念class在JS,就像你用你的标题中提到的function关键字。它不是设计模式,而是语言的故意特性。我并没有在这个问题上对您投反对票,但是由于简洁且几乎与该问题无关,因此看起来就像其他人一样。希望此反馈对您有所帮助。
科迪
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.