当前位置: 首页 > 面试题库 >

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

伯和蔼
2023-03-14
问题内容

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

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

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

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


问题答案:

有两种用于在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部分”。



 类似资料:
  • 问题内容: 出于某种原因,在以下代码段中似​​乎无法使用构造函数委托: 运行此给出。关于为什么的任何想法,或者是否有更好的方法来创建新的子类?我不知道的本地构造函数存在问题吗? 问题答案: 更新您的代码以将原型分配给Error.prototype和instanceof以及您的assert工作。 但是,我只是抛出您自己的对象并只检查name属性。 根据评论进行编辑 在查看了注释并试图记住为什么要分配

  • 我想创建一个如下所示的自定义对话框 我试过以下几件事。 > 我创建了AlertDialog.Builder的子类,并使用了自定义标题和自定义内容视图,但结果不是预期的。 另一个尝试是子类DialogFragment并自定义onCreateDialog中的对话框,但结果并不像预期的那样。 然后我尝试使用一个普通的对话框类。结果不如预期。 在这三种情况下,问题是当我忽略标题视图时,对话框的大小不像预期

  • 问题内容: 我需要做的就是在当前函数执行结束时执行一个回调函数。 此功能的使用者应如下所示: 我该如何实施? 问题答案: 实际上,您的代码将按原样工作,只需将回调声明为参数即可,您可以使用参数名称直接调用它。 基础知识 那会叫,这会叫,这会提醒“东西在这里”。 请注意,传递函数 引用 ()而不是调用函数并传递其结果()非常重要。在您的问题中,您可以正确执行此操作,但是值得指出,因为这是一个常见错误

  • 问题内容: 我正在尝试在Log4j2中编写自己的RewritePolicy。该文档指出: RewritePolicy是一个接口,允许实现在将LogEvent传递给Appender之前检查并可能对其进行修改。RewritePolicy声明一个必须执行的名为rewrite的方法。该方法通过LogEvent传递,并且可以返回相同事件或创建一个新事件。 这是我的 java类 : 这是我的 yaml配置 文

  • 问题内容: 我在JFrame上有一个按钮,当单击该按钮时,我希望对话框弹出并带有多个文本区域供用户输入。我一直在四处寻找解决方法,但是我一直感到困惑。有人可以帮忙吗? 问题答案: 如果您不需要太多自定义行为,则JOptionPane可以节省大量时间。它负责OK / Cancel选项的放置和本地化,并且是一种无需定义自己的类即可显示自定义对话框的快捷方法。大多数情况下,JOptionPane中的“

  • 问题内容: 有人可以告诉我如何创建与链接[here] [1]类似/完全相同的上述对话框视图,问题的重点是在图片的中心创建视图? 我已经进行了一些研究,这使我想知道我应该使用自定义xml创建自定义对话框视图还是应该使用alertdialog创建上面显示的确切视图可编程性?即使有alertdialog的可能,在给出alertdialog限制的情况下,我该如何容纳对话框图片中间显示的那么多textvie