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

我不知道最好的方法是建立具备属性和方法的JavaScript对象。 程序员

我看过一些示例,该人员先使用var self = this ,而后再使用self. 在全部功能中确保范围始终正确。 编程

而后,我看到了使用.prototype添加属性的示例,而其余示例则是内联的。 闭包

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


#1楼

当在构造函数调用期间使用关闭“ 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 )也很聪明。 祝好运! ui


#2楼

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

function createCounter () {
    var count = 0;

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

而后 : spa

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

#3楼

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

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运算符,则获得的是一个普通的函数调用, 调用绑定到全局对象而不是新对象。


#4楼

我至关频繁地使用这种模式-我发现它在须要时给了我很大的灵活性。 在使用中,它很是相似于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

#5楼

有两种用于在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很是草率,所以它能够工做在这种简单状况下:它容许传入零个参数,在这种状况下, xy变为undefined ,并分配给原型的this.xthis.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对象,从而致使普遍的调试麻烦)。 实际上,一般在采用方法并将其分配给setTimeoutonclickEventListener时一般会发生这种状况。

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

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部分。]

相关文章
相关标签/搜索