我想知道创建具有属性和方法的JavaScript对象的最佳方法是什么。
我见过有人用var self = this然后用self的例子。在所有函数中确保作用域总是正确的。
然后我看到了使用.prototype来添加属性的例子,而其他人则是内联的。
谁能给我一个适当的例子,一个JavaScript对象的一些属性和方法?
我想知道创建具有属性和方法的JavaScript对象的最佳方法是什么。
我见过有人用var self = this然后用self的例子。在所有函数中确保作用域总是正确的。
然后我看到了使用.prototype来添加属性的例子,而其他人则是内联的。
谁能给我一个适当的例子,一个JavaScript对象的一些属性和方法?
当前回答
Douglas Crockford在《The Good Parts》中广泛讨论了这个话题。他建议避免使用new操作符来创建新对象。相反,他建议创建定制的构造函数。例如:
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操作符,则得到的是一个普通的函数调用,并且它绑定到全局对象,而不是绑定到新对象。
其他回答
在JavaScript中实现类和实例有两种模型:原型方法和闭包方法。两者都有优点和缺点,并且有很多扩展的变体。许多程序员和库都有不同的方法和类处理实用函数来掩盖语言中较难看的部分。
结果是,在混合公司中,您将拥有一堆元类,它们的行为都略有不同。更糟糕的是,大多数JavaScript教程材料都很糟糕,提供了一些介于两者之间的折衷方案,覆盖了所有基础,让您非常困惑。(可能作者也很困惑。JavaScript的对象模型与大多数编程语言非常不同,而且在很多地方设计得很糟糕。)
让我们从原型开始。这是您能得到的最原生的javascript:开销代码最少,instanceof将处理这类对象的实例。
function Shape(x, y) {
this.x= x;
this.y= y;
}
我们可以向new Shape创建的实例中添加方法,方法是将它们写入这个构造函数的原型查找:
Shape.prototype.toString= function() {
return 'Shape at '+this.x+', '+this.y;
};
现在要子类化它,尽可能多地调用JavaScript子类化。我们通过完全替换奇怪的神奇原型属性来做到这一点:
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;
}
这个示例可以工作,您将在许多教程中看到类似的代码。但是,这个新的Shape()是丑陋的:我们正在实例化基类,即使没有实际的Shape被创建。它恰好在这个简单的情况下工作,因为JavaScript是如此草率:它允许传入零参数,在这种情况下x和y成为未定义的,并被分配给原型的this。X和y。如果构造函数要做更复杂的事情,它就会彻底失败。
因此,我们需要找到一种方法来创建一个原型对象,其中包含我们在类级别上需要的方法和其他成员,而不调用基类的构造函数。要做到这一点,我们必须开始编写helper代码。这是我所知道的最简单的方法:
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);
而不是新的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。子类,把它反过来,让基类的函数吐出子类:
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());
那么方法内部的这个将不是预期的Circle实例(它实际上是全局窗口对象,导致广泛的调试麻烦)。实际上,这通常发生在一个方法被获取并分配给setTimeout, onclick或EventListener时。
使用原型的方式,你必须为每一个这样的赋值包含一个闭包:
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
或者,在未来(或者现在如果你破解了Function.prototype),你也可以用function.bind():
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
如果你的实例是通过闭包的方式完成的,绑定是通过实例变量的闭包免费完成的(通常称为self或self,尽管我个人不建议使用后者,因为self在JavaScript中已经有另一个不同的含义)。不过,您并不是免费获得上述代码片段中的参数1,1,因此如果需要这样做,仍然需要另一个闭包或bind()。
闭包方法也有很多变体。你可能更喜欢完全省略这个,创建一个新的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部分]
Douglas Crockford在《The Good Parts》中广泛讨论了这个话题。他建议避免使用new操作符来创建新对象。相反,他建议创建定制的构造函数。例如:
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操作符,则得到的是一个普通的函数调用,并且它绑定到全局对象,而不是绑定到新对象。
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;
};
首先,您可以更改向实例添加方法的偏好,而不是向构造函数的原型对象添加方法。我几乎总是在构造函数内部声明方法,因为我经常使用构造函数劫持来实现继承和装饰。
以下是我如何决定哪些声明应该写在哪里:
Never declare a method directly on the context object (this) Let var declarations take precedence over function declarations Let primitives take precedence over objects ({} and []) Let public declarations take precedence over private declarations Prefer Function.prototype.bind over thus, self, vm, etc Avoid declaring a Class within another Class, unless: It should be obvious that the two are inseparable The Inner class implements The Command Pattern The Inner class implements The Singleton Pattern The Inner class implements The State Pattern The Inner Class implements another Design Pattern that warrants this Always return this from within the Lexical Scope of the Closure Space.
以下是为什么这些方法有帮助:
Constructor Hijackingvar Super = function Super() {
...
this.inherited = true;
...
};
var Klass = function Klass() {
...
// export precepts
Super.apply(this); // extends this with property `inherited`
...
};
Model Design
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
Design Patterns
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(或.call或.apply)而不是thus。在我们的Singleton类中,我们甚至不这样命名它,因为INSTANCE传递了更多的信息。对于Model,我们返回这个,这样就可以使用.call调用构造函数来返回传递给它的实例。多余的是,我们将它分配给变量updated,尽管它在其他场景中很有用。
此外,我更喜欢使用new关键字而不是{括号}来构造对象文字:
Preferredvar klass = new (function Klass(Base) {
...
// export precepts
Base.apply(this); //
this.override = x;
...
})(Super);
Not Preferred
var klass = Super.apply({
override: x
});
如您所见,后者无法覆盖其超类的“override”属性。
如果我确实向类的原型对象添加了方法,我更喜欢对象文字——不管是否使用new关键字:
PreferredKlass.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() {...}
};
Not Preferred
Klass.prototype.method = function m() {...};
你也可以试试这个
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();
除了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手机版一样支持。