How to “properly” create a custom object in JavaScript?
我想知道创建一个具有属性和方法的javascript对象的最佳方法是什么。
我看到过一些例子,人们使用
然后,我看到了使用
有人能给我一个有一些属性和方法的javascript对象的适当例子吗?
在JavaScript中有两种实现类和实例的模型:原型方法和关闭方法。两者都有优点和缺点,并且有许多扩展的变体。许多程序员和库有不同的方法和类处理实用程序函数,可以覆盖语言中一些更糟糕的部分。好的。
其结果是,在混合公司中,您将看到元类的混乱,所有的行为都略有不同。更糟糕的是,大多数的javascript教程材料都很糟糕,并且提供了某种中间折衷来覆盖所有的基础,这让您非常困惑。(可能作者也很困惑。Javascript的对象模型与大多数编程语言非常不同,而且在许多地方设计得很糟糕。)好的。
让我们从原型的方式开始。这是您能得到的最本机的javascript:开销代码最少,instanceof将用于此类对象的实例。好的。
1 2 3 4 | function Shape(x, y) { this.x= x; this.y= y; } |
我们可以通过将方法写入该构造函数函数的
1 2 3 | Shape.prototype.toString= function() { return 'Shape at '+this.x+', '+this.y; }; |
现在要对它进行子类化,尽可能多地调用javascript所做的子类化。我们完全取代了神奇的
1 2 3 4 5 | 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(); |
在向其中添加方法之前:好的。
1 2 3 | Circle.prototype.toString= function() { return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r; } |
这个示例可以工作,您将在许多教程中看到类似的代码。但是,老兄,那个
所以我们需要做的是找到一种方法来创建一个原型对象,它包含我们在类级别上想要的方法和其他成员,而不调用基类的构造函数。要做到这一点,我们必须开始编写助手代码。这是我所知道的最简单的方法:好的。
1 2 3 4 5 | function subclassOf(base) { _subclassOf.prototype= base.prototype; return new _subclassOf(); } function _subclassOf() {}; |
这会将原型中的基类成员转移到一个不执行任何操作的新构造函数函数,然后使用该构造函数。现在我们可以简单地写:好的。
1 2 3 4 5 | function Circle(x, y, r) { Shape.call(this, x, y); this.r= r; } Circle.prototype= subclassOf(Shape); |
而不是
在这个模型下,我们可以考虑一些改进和扩展。例如,这里有一个语法糖版本:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); |
任何一个版本都有一个缺点,即构造函数函数不能继承,因为它在许多语言中都是如此。因此,即使您的子类没有向构造过程中添加任何内容,它也必须记住使用基需要的任何参数调用基构造函数。这可以使用
1 2 3 4 | function Point() { Shape.apply(this, arguments); } Point.subclass(Shape); |
所以一个常见的扩展是将初始化内容分解成它自己的函数,而不是构造函数本身。然后,此函数可以从基继承,这很好:好的。
1 2 3 4 5 6 7 8 9 | 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! |
现在我们已经为每个类提供了相同的构造函数函数样板。也许我们可以把它移到它自己的助手函数中,这样我们就不必不断地输入它,例如,不必输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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; }; |
…这看起来有点像其他语言,尽管语法有点笨拙。如果你愿意的话,你可以在一些额外的功能上撒点水。也许您希望
1 2 3 4 5 | Function.prototype.makeSubclass= function() { function Class() { if (!(this instanceof Class)) throw('Constructor called without"new"'); ... |
也许你想把所有的新成员都传进来,让
1 2 3 4 5 6 7 | Circle= Shape.makeSubclass({ _init: function(x, y, z) { Shape.prototype._init.call(this, x, y); this.r= r; }, ... }); |
在一个对象系统中,您可能认为有许多潜在的特性是可取的,没有人真正同意一个特定的公式。好的。
那么,封闭的方式。这样就避免了javascript基于原型继承的问题,完全不使用继承。而是:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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(); |
现在,
每一个拥有自己的类成员副本的实例的坏处在于它的效率较低。如果您处理大量的子类实例,原型继承可能会更好地为您服务。同样,调用基类的方法也有点烦人,如您所见:在子类构造函数重写它之前,我们必须记住该方法是什么,否则它会丢失。好的。
[另外,因为这里没有继承,所以
每个拥有自己方法的实例的好处是,该方法随后可能绑定到拥有它的特定实例。这是很有用的,因为javascript在方法调用中以奇怪的方式绑定
1 2 | var ts= mycircle.toString; alert(ts()); |
那么方法内部的
使用原型方法,您必须为每个这样的任务包括一个闭包:好的。
1 2 3 | setTimeout(function() { mycircle.move(1, 1); }, 1000); |
或者,在将来(或者现在,如果您对function.prototype进行了黑客攻击),您也可以使用
1 | setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000); |
如果您的实例是以闭包的方式完成的,那么绑定是通过对实例变量(通常称为
闭包方法也有很多变体。您可能更喜欢完全省略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
哪条路"合适"?两者都有。哪一个是"最好的"?这取决于你的情况。当我做大量的面向对象的工作时,我倾向于原型化以实现真正的JavaScript继承,并为简单的一次性页面效果提供闭包。好的。
但是对于大多数程序员来说,这两种方式都是非常违反直觉的。两者都有许多潜在的混乱变化。如果您使用其他人的代码/库,那么您将同时遇到这两种方案(以及许多中间方案和通常被破坏的方案)。没有一个公认的答案。欢迎来到Javascript对象的精彩世界。好的。
[这是为什么javascript不是我最喜欢的编程语言的第94部分。]好的。好啊。
我经常使用这个模式——我发现它在我需要的时候给了我很大的灵活性。在使用中,它与Java风格的类非常相似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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对象—您在它内部定义私有属性,公共属性附加到
基本上,这种方法将Crockfordian方法与标准的JavaScript对象结合起来,创建了一个更强大的类。
您可以像使用任何其他javascript对象一样使用它:
1 2 3 | Foo.publicStaticMethod(); //calling a static method var test = new Foo(); //instantiation test.publicMethod(); //calling a method |
道格拉斯·克罗克福德在好的部分广泛讨论了这个话题。他建议避免新操作员创建新对象。相反,他建议创建定制的构造函数。例如:
1 2 3 4 5 6 7 8 9 10 11 12 | 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中,函数是一个对象,可以用来与新的操作符一起构造对象。按照惯例,用作构造函数的函数以大写字母开头。你经常看到这样的事情:
1 2 3 4 5 6 7 | function Person() { this.name ="John"; return this; } var person = new Person(); alert("name:" + person.name);** |
如果在实例化新对象时忘记使用new运算符,则得到的是一个普通的函数调用,它绑定到全局对象而不是新对象。
继续回答Bobince的问题
在ES6中,您现在可以实际创建一个
现在你可以做:
1 2 3 4 5 6 7 8 9 10 | class Shape { constructor(x, y) { this.x = x; this.y = y; } toString() { return `Shape at ${this.x}, ${this.y}`; } } |
因此,延伸到一个圆(如另一个答案中所示),您可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 | 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}`; } } |
最终在ES6中变得更干净,也更容易阅读。
下面是一个很好的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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); |
您也可以这样做,使用结构:
1 2 3 4 5 6 7 8 9 10 11 12 | function createCounter () { var count = 0; return { increaseBy: function(nb) { count += nb; }, reset: function { count = 0; } } } |
然后:
1 2 | var counter1 = createCounter(); counter1.increaseBy(4); |
另一种方法是http://jsfiddle.net/nnuy4/(我不知道这种处理对象创建和显示功能是否遵循任何特定的模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 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" |
下面是一个闭包示例:
1 2 3 4 5 | function outer(outerArg) { return inner(innerArg) { return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context } } |
不久前,我看到了Mozilla关于关闭的文章。这就是我所看到的:"一个闭包允许您将一些数据(环境)与操作该数据的函数相关联。这与面向对象编程有明显的相似之处,对象允许我们将一些数据(对象的属性)与一个或多个方法相关联"。这是我第一次读到闭包和经典OOP之间的并行性,没有参考原型。
怎么用?
假设您要计算某些项目的增值税。增值税在申请期间很可能保持稳定。在OOP(伪代码)中执行此操作的一种方法:
1 2 3 4 5 6 7 8 9 | 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作为参数传递到函数中。因为您唯一感兴趣的是计算本身,所以返回一个新函数,即calculate方法:
1 2 3 4 5 6 7 8 | function calculator(vat) { return function(item) { return item * vat; } } var calculate = calculator(1.10); var jsBook = 100; //100$ calculate(jsBook); //110 |
在您的项目中,确定最适合计算增值税的顶级值。作为经验法则,无论何时传递相同的参数,都有一种方法可以使用闭包来改进它。无需创建传统对象。
https://developer.mozilla.org/en-us/docs/web/javascript/guide/closures
当一个人在构造函数调用期间使用关闭"this"的技巧时,其目的是编写一个函数,该函数可被不想在对象上调用方法的其他对象用作回调。这与"使范围正确"无关。
下面是一个普通的javascript对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); }; |
你可能会从阅读道格拉斯·克罗克福德关于JavaScript的文章中得到很多收获。约翰·雷西格也很聪明。祝你好运!
创建对象
在javascript中创建对象的最简单方法是使用以下语法:
1 2 3 4 5 6 7 8 9 10 | var test = { a : 5, b : 10, f : function(c) { return this.a + this.b + c; } } console.log(test); console.log(test.f(3)); |
这对于以结构化方式存储数据非常有用。
但是,对于更复杂的用例,最好创建函数实例:
1 2 3 4 5 6 7 8 9 10 11 | 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)); |
这允许您创建多个对象,这些对象共享相同的"蓝图",类似于如何在Java中使用类。
然而,使用原型仍然可以更有效地实现这一点。
只要函数的不同实例共享相同的方法或属性,就可以将它们移动到该对象的原型中。这样,函数的每个实例都可以访问该方法或属性,但不需要为每个实例复制该方法或属性。
在我们的例子中,将方法
1 2 3 4 5 6 7 8 9 10 11 12 | 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中进行继承的一个简单但有效的方法是使用以下两个行程序:
1 2 | B.prototype = Object.create(A.prototype); B.prototype.constructor = B; |
类似于这样做:
1 | B.prototype = new A(); |
两者的主要区别在于,在使用
创建
1 2 3 | function B(arg1, arg2) { A(arg1, arg2); // This is optional } |
如果你想把
1 2 3 | function B() { A.apply(this, arguments); // This is optional } |
如果要将另一个对象混合到
1 2 | B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype); B.prototype.constructor = B; |
演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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()); |
您可以在这里找到一个用于
我想说的是,我们可以使用标题或字符串来声明一个对象。有不同的方法来调用每一种类型。见下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var test = { useTitle :"Here we use 'a Title' to declare an Object", 'useString':"Here we use 'a String' to declare an Object", onTitle : function() { return this.useTitle; }, onString : function(type) { return this[type]; } } console.log(test.onTitle()); console.log(test.onString('useString')); |
很适合我的模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; }; |
首先,您可以更改向实例添加方法的首选项,而不是更改构造函数的
以下是我如何决定哪些声明是书面的:
- 从不直接在上下文对象(
this 上声明方法) - 让
var 声明优先于function 声明。 - 让原语优先于对象(
{} 和[] ) - 让
public 声明优先于private 声明。 - 比
thus 、self 、vm 、etc 更喜欢Function.prototype.bind 。 - 避免在另一个类中声明类,除非:
- 很明显这两者是不可分割的
- 内部类实现命令模式
- 内部类实现了单例模式
- 内部类实现状态模式
- 内部类实现了另一个保证这一点的设计模式。
- 总是从闭包空间的词法范围内返回
this 。
这就是为什么这些帮助:
施工人员劫持1 2 3 4 5 6 7 8 9 10 11 | var Super = function Super() { ... this.inherited = true; ... }; var Klass = function Klass() { ... // export precepts Super.apply(this); // extends this with property `inherited` ... }; |
模型设计
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
设计模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
如你所见,我真的不需要
除此之外,我更喜欢使用
1 2 3 4 5 6 7 | var klass = new (function Klass(Base) { ... // export precepts Base.apply(this); // this.override = x; ... })(Super); |
不优选
1 2 3 | var klass = Super.apply({ override: x }); |
如您所见,后者无法重写其超类的"override"属性。
如果我确实向类的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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() {...} }; |
不优选
1 | Klass.prototype.method = function m() {...}; |
你也可以试试这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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。
The Object.defineProperty() method defines a new property directly on
an object, or modifies an existing property on an object, and returns
the object.
Source: Mozilla
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 Mobile。
1 2 3 4 5 6 7 8 9 10 11 12 | var Person = function (lastname, age, job){ this.name = name; this.age = age; this.job = job; this.changeName = function(name){ this.lastname = name; } } var myWorker = new Person('Adeola', 23, 'Web Developer'); myWorker.changeName('Timmy'); console.log("New Worker" + myWorker.lastname); |
基本上,JS中没有类的概念,所以我们使用函数作为与现有设计模式相关的类构造函数。
1 2 3 4 5 6 7 8 9 | //Constructor Pattern function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.doSomething = function(){ alert('I am Happy'); } } |
到目前为止,JS还不知道您想要创建一个对象,所以这里出现了新的关键字。
1 2 | var person1 = new Person('Arv', 30, 'Software'); person1.name //Arv |
参考:Web开发人员的专业JS-nik z