如何“正确”在JavaScript中创建自定义对象? [英] How to "properly" create a custom object in JavaScript?

查看:82
本文介绍了如何“正确”在JavaScript中创建自定义对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道创建一个具有属性和方法的JavaScript对象的最佳方法是什么。

I wonder about what the best way is to create an JavaScript object that has properties and methods.

我见过这个人使用的例子 var self = this 然后在所有函数中使用 self。以确保范围始终正确。

I have seen examples where the person used var self = this and then uses self. in all functions to make sure the scope is always correct.

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

Then I have seen examples of using .prototype to add properties, while others do it inline.

有人能给我一个带有一些属性和方法的JavaScript对象的正确例子吗?

Can someone give me a proper example of a JavaScript object with some properties and methods?

推荐答案

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

There are two models for implementing classes and instances in JavaScript: the prototyping way, and the closure way. Both have advantages and drawbacks, and there are plenty of extended variations. Many programmers and libraries have different approaches and class-handling utility functions to paper over some of the uglier parts of the language.

结果是在混合公司中你会有各种类型的混搭,都表现得略有不同。更糟糕的是,大多数JavaScript教程材料都很糟糕,并提供某种中间折衷以覆盖所有基础,让你非常困惑。 (也许作者也很困惑.JavaScript的对象模型与大多数编程语言非常不同,并且很多地方都设计得非常糟糕。)

The result is that in mixed company you will have a mishmash of metaclasses, all behaving slightly differently. What's worse, most JavaScript tutorial material is terrible and serves up some kind of in-between compromise to cover all bases, leaving you very confused. (Probably the author is also confused. JavaScript's object model is very different to most programming languages, and in many places straight-up badly designed.)

让我们从原型方式。这是您可以获得的最多JavaScript原生:有最少的开销代码,instanceof将使用这种对象的实例。

Let's start with the prototype way. This is the most JavaScript-native you can get: there is a minimum of overhead code and instanceof will work with instances of this kind of object.

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

我们可以为 new创建的实例添加方法通过将它们写入原型查找此构造函数来形成

We can add methods to the instance created by new Shape by writing them to the prototype lookup of this constructor function:

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

现在将它子类化,尽可能多地调用JavaScript做的子类化。我们通过完全替换那个奇怪的魔法原型属性来实现这一点:

Now to subclass it, in as much as you can call what JavaScript does subclassing. We do that by completely replacing that weird magic prototype property:

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.x 这个.Y 。如果构造函数做了更复杂的事情,那么它就会落在它的脸上。

This example will work and you will see code like it in many tutorials. But man, that new Shape() is ugly: we're instantiating the base class even though no actual Shape is to be created. It happens to work in this simple case because JavaScript is so sloppy: it allows zero arguments to be passed in, in which case x and y become undefined and are assigned to the prototype's this.x and this.y. If the constructor function were doing anything more complicated, it would fall flat on its face.

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

So what we need to do is find a way to create a prototype object which contains the methods and other members we want at a class level, without calling the base class's constructor function. To do this we are going to have to start writing helper code. This is the simplest approach I know of:

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

这会将基类的成员在其原型中转移到新的构造函数,该函数什么都不做,然后使用它构造函数。现在我们可以写简单:

This transfers the base class's members in its prototype to a new constructor function which does nothing, then uses that constructor. Now we can write simply:

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

而不是新的Shape()错误。我们现在有一组可接受的原语来构建类。

instead of the new Shape() wrongness. We now have an acceptable set of primitives to built classes.

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

There are a few refinements and extensions we can consider under this model. For example here is a syntactical-sugar version:

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 稍微自动化,但仍然需要写出:

Either version has the drawback that the constructor function cannot be inherited, as it is in many languages. So even if your subclass adds nothing to the construction process, it must remember to call the base constructor with whatever arguments the base wanted. This can be slightly automated using apply, but still you have to write out:

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

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

So a common extension is to break out the initialisation stuff into its own function rather than the constructor itself. This function can then inherit from the base just fine:

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 ,将它转向并放置基类的函数吐出子类:

Now we've just got the same constructor function boilerplate for each class. Maybe we can move that out into its own helper function so we don't have to keep typing it, for example instead of Function.prototype.subclass, turning it round and letting the base class's Function spit out subclasses:

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 运算符的情况下意外调用它(否则通常会导致非常烦人的调试):

...which is starting to look a bit more like other languages, albeit with slightly clumsier syntax. You can sprinkle in a few extra features if you like. Maybe you want makeSubclass to take and remember a class name and provide a default toString using it. Maybe you want to make the constructor detect when it has accidentally been called without the new operator (which would otherwise often result in very annoying debugging):

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

也许你想传递所有新成员并拥有 makeSubclass 将它们添加到原型中,以节省您必须编写 Class.prototype ... 。很多类系统都这样做,例如:

Maybe you want to pass in all the new members and have makeSubclass add them to the prototype, to save you having to write Class.prototype... quite so much. A lot of class systems do that, eg:

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

在对象系统中你可能认为有很多潜在的功能,没有人真的同意一个特定的公式。

There are a lot of potential features you might consider desirable in an object system and no-one really agrees on one particular formula.

关闭方式,然后。这可以避免JavaScript基于原型的继承问题,完全不使用继承。相反:

The closure way, then. This avoids the problems of JavaScript's prototype-based inheritance, by not using inheritance at all. Instead:

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 方法的副本(以及我们添加的任何其他方法或其他类成员)。

Now every single instance of Shape will have its own copy of the toString method (and any other methods or other class members we add).

坏关于每个具有自己的每个类成员副本的实例的事情是效率较低。如果您正在处理大量的子类实例,原型继承可能会更好地为您服务。同样调用基类的方法有点烦人,你可以看到:我们必须记住在子类构造函数覆盖它之前该方法是什么,否则它会丢失。

The bad thing about every instance having its own copy of each class member is that it's less efficient. If you are dealing with large numbers of subclassed instances, prototypical inheritance may serve you better. Also calling a method of the base class is slightly annoying as you can see: we have to remember what the method was before the subclass constructor overwrote it, or it gets lost.

[另外因为这里没有继承, instanceof 运算符不起作用;如果需要,你必须提供自己的类嗅探机制。虽然你可以以与原型继承类似的方式来处理原型对象,但它有点棘手并且不值得为了获得 instanceof 工作。]

[Also because there is no inheritance here, the instanceof operator won't work; you would have to provide your own mechanism for class-sniffing if you need it. Whilst you could fiddle the prototype objects in a similar way as with prototype inheritance, it's a bit tricky and not really worth it just to get instanceof working.]

每个具有自己方法的实例的好处是该方法可以绑定到拥有它的特定实例。这很有用,因为JavaScript在方法调用中绑定这个的奇怪方式,如果你从它的所有者那里分离出一个方法,结果是:

The good thing about every instance having its own method is that the method may then be bound to the specific instance that owns it. This is useful because of JavaScript's weird way of binding this in method calls, which has the upshot that if you detach a method from its owner:

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

然后方法内部不会是预期的Circle实例(它实际上是全局窗口对象,导致广泛的调试失败)。实际上,这通常发生在采用方法并分配给 setTimeout onclick 时一般来说,Eve​​ntListener

then this inside the method won't be the Circle instance as expected (it'll actually be the global window object, causing widespread debugging woe). In reality this typically happens when a method is taken and assigned to a setTimeout, onclick or EventListener in general.

使用原型方法,你必须为每个这样的任务包括一个闭包:

With the prototype way, you have to include a closure for every such assignment:

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

或者,将来(或现在如果你攻击Function.prototype)你也可以用 function.bind()

or, in the future (or now if you hack Function.prototype) you can also do it with function.bind():

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

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

if your instances are done the closure way, the binding is done for free by the closure over the instance variable (usually called that or self, though personally I would advise against the latter as self already has another, different meaning in JavaScript). You don't get the arguments 1, 1 in the above snippet for free though, so you would still need another closure or a bind() if you need to do that.

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

There are lots of variants on the closure method too. You may prefer to omit this completely, creating a new that and returning it instead of using the new operator:

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当我做强OO的东西时,我倾向于为真正的JavaScript继承进行原型设计,并且关闭简单的一次性页面效果。

Which way is "proper"? Both. Which is "best"? That depends on your situation. FWIW I tend towards prototyping for real JavaScript inheritance when I'm doing strongly OO stuff, and closures for simple throwaway page effects.

但这两种方式都非常直观。大多数程序员。两者都有许多潜在的混乱变化。如果您使用其他人的代码/库,您将同时遇到这两种(以及许多中间和一般破坏的方案)。没有一个普遍接受的答案。欢迎来到精彩的JavaScript对象世界。

But both ways are quite counter-intuitive to most programmers. Both have many potential messy variations. You will meet both (as well as many in-between and generally broken schemes) if you use other people's code/libraries. There is no one generally-accepted answer. Welcome to the wonderful world of JavaScript objects.

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

[This has been part 94 of Why JavaScript Is Not My Favourite Programming Language.]

这篇关于如何“正确”在JavaScript中创建自定义对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆