我想知道创建一个拥有属性和方法的JavaScript对象的最佳方式是什么。
我看过一些例子,其中的人使用了 var self = this
,然后在所有函数中使用 self.
确保作用域始终正确。
接着,我又看到有人使用 .prototype
添加属性,而其他人则在代码中直接添加属性。
能否给我一个适当的JavaScript对象示例,其中包含一些属性和方法?
我想知道创建一个拥有属性和方法的JavaScript对象的最佳方式是什么。
我看过一些例子,其中的人使用了 var self = this
,然后在所有函数中使用 self.
确保作用域始终正确。
接着,我又看到有人使用 .prototype
添加属性,而其他人则在代码中直接添加属性。
能否给我一个适当的JavaScript对象示例,其中包含一些属性和方法?
function Shape(x, y) {
this.x= x;
this.y= y;
}
prototype
查找中,为由new Shape
创建的实例添加方法: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()
真的很丑:我们实例化了基类,即使没有实际的形状要创建。在这种简单的情况下它能够工作,因为JavaScript非常松散:它允许传入零个参数,在这种情况下x
和y
变成undefined
并赋值给原型的this.x
和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
。请注意保留HTML标记。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
工作并不值得。]this
的方式很奇怪,这意味着如果你从其所有者分离一个方法:var ts= mycircle.toString;
alert(ts());
如果方法被赋值给setTimeout
, onclick
或一般的EventListener
,那么方法内部的this
将不会是预期的Circle实例(实际上它将是全局的window
对象,导致广泛的调试问题)。使用原型方式时,你需要为每个这样的赋值包含一个闭包:
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
或者,在未来(或者现在如果你hack了Function.prototype),你也可以使用function.bind()
来实现:
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
that
或self
,但我个人建议不要使用后者,因为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
new
的处理方式。 - Crescent Fresh我经常使用这个模式 - 我发现当我需要更多的灵活性时,它给了我相当大的帮助。在使用上,它与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
constructor.prototype.myMethod = function () { ... }
。 - Nicolas Le Thierry d'EnnequinDouglas 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操作符,那么你得到的是一个普通函数调用,this将绑定到全局对象而不是新对象。
在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}`;
}
}
这里是它实际运作的一个好例子:
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}`;
}
}
let c = new Circle(1, 2, 4);
console.log('' + c, c);
function createCounter () {
var count = 0;
return {
increaseBy: function(nb) {
count += nb;
},
reset: function {
count = 0;
}
}
}
var counter1 = createCounter();
counter1.increaseBy(4);
在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));
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();
Object.create
时,A
的构造函数不会被运行,这更加符合直觉,并且更类似于基于类的继承。B
的构造函数中添加它来可选地运行A
的构造函数,以创建B
的新实例:function B(arg1, arg2) {
A(arg1, arg2); // This is optional
}
B
的所有参数传递给 A
,你也可以使用 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
的 polyfill,也可以在这里找到一个Object.assign
的 polyfill。
另一种方法是使用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"
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也很出色。祝你好运!
this
包裹起来与“使范围正确”有着千丝万缕的联系。 - Roatin Marthself=this
虽然不能强制this
持久化,但可以通过闭包轻松实现“正确”的作用域限定。 - Crescent Fresh闭包
非常灵活。 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
}
}
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值传递到构造函数中,并且可以通过闭包对其进行操作。 现在,不要使用类/构造函数,而是将您的VAT作为参数传递给函数。因为您只关心计算本身,所以返回一个新函数,即计算方法:
function calculator(vat) {
return function(item) {
return item * vat;
}
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110
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;
};
首先,你可以更改你的偏好,将方法添加到实例而不是构造函数的prototype
对象中。我几乎总是在构造函数内声明方法,因为我经常使用构造函数劫持来进行继承和修饰器方面的操作。
以下是我如何决定在哪里编写声明:
this
)上声明方法var
声明优先于function
声明{}
和[]
)public
声明优先于private
声明Function.prototype.bind
而不是thus
、self
、vm
、etc
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
(或 .call
或 .apply
)而不是 thus
。在我们的 Singleton
类中,我们甚至没有将其命名为 thus
,因为 INSTANCE
传达了更多信息。对于 Model
,我们返回 this
,以便我们可以使用 .call
调用构造函数,从而返回我们传递给它的实例。虽然在其他情况下它很有用,但我们将其赋值给变量 updated
是多余的。
同时,我更喜欢使用 new
关键字构建对象字面量而不是 {括号}:
var klass = new (function Klass(Base) {
...
// export precepts
Base.apply(this); //
this.override = x;
...
})(Super);
var klass = Super.apply({
override: x
});
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() {...};
window
属性如document
或frames
更具保留意义;你肯定可以将其作为变量名重新使用该标识符。尽管如此,从风格上讲,我更喜欢使用var that= this
以避免任何可能的混淆。尽管window.self
最终是毫无意义的,所以很少有理由去碰它。 - bobincethis
赋值给一个本地变量(例如self
)可以减小文件大小。 - Patrick Fisher