如何“正确地”在JavaScript中创build一个自定义对象?

我想知道创build一个具有属性和方法的JavaScript对象的最佳方法。

我已经看到人使用var self = this例子,然后使用self. 在所有的function,以确保范围始终是正确的。

然后我看到使用.prototype来添加属性的例子,而其他的则是内联的。

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

在JavaScript中有两种实现类和实例的模型:原型方法和封闭方式。 两者都有优点和缺点,还有很多扩展的变化。 许多程序员和图书馆都有不同的方法和类处理效用函数来对这些语言中的某些丑陋的部分进行描述。

结果就是在混合公司中,你将会有一个元类混杂,所有的行为都略有不同。 更糟糕的是,大多数JavaScript教程材料是可怕的,并提供了一种介于两者之间的妥协,以涵盖所有的基础,让你非常困惑。 (可能作者也很困惑,JavaScript的对象模型与大多数编程语言是截然不同的,在很多地方直接devise得不好)。

让我们从原型的方式开始。 这是您可以获得的最基本的JavaScript:有一个最小开销代码,instanceof将与这种对象的实例一起工作。

 function Shape(x, y) { this.x= x; this.y= y; } 

我们可以将方法添加到由new Shape创build的实例中,方法是将其写入此构造函数的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被创build,我们仍然在实例化基类。 它恰好在这种简单的情况下工作,因为JavaScript是如此草率:它允许零参数被传入,在这种情况下xy变得undefined ,并被分配到原型的this.xthis.y 如果构造函数做了更复杂的事情,它就会掉下去。

所以我们需要做的是find一种方法来创build一个原型对象,其中包含我们需要的方法和其他成员,而不需要调用基类的构造函数。 要做到这一点,我们将不得不开始编写帮手代码。 这是我所知道的最简单的方法:

 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()错误。 现在我们有一套可以接受的基本类来构build类。

在这个模型下我们可以考虑一些改进和扩展。 例如这里是一个语法糖的版本:

 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); 

任何一个版本的缺点是构造函数都不能像许多语言一样被inheritance。 所以,即使你的子类没有给构造过程增加任何东西,它也必须记住用基础想要的任何参数调用基础构造器。 这可以使用apply稍微自动化,但是你仍然必须写出:

 function Point() { Shape.apply(this, arguments); } Point.subclass(Shape); 

所以一个常见的扩展是将初始化的东西分解成它自己的函数而不是构造函数本身。 这个function可以从基地inheritance就好了:

 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! 

现在我们为每个类获得了相同的构造函数样板。 也许我们可以把它移到它自己的帮助函数中,所以我们不需要继续input它,例如,而不是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; }; 

…开始看起来更像其他语言,尽pipe稍微笨拙的语法。 如果你喜欢,你可以洒一些额外的function。 也许你想让makeSubclass记住一个类名,并提供一个默认的toString使用它。 也许你想让构造函数在没有new运算符的情况下被意外地调用(否则这往往会导致非常烦人的debugging):

 Function.prototype.makeSubclass= function() { function Class() { if (!(this instanceof Class)) throw('Constructor called without "new"'); ... 

也许你想传入所有新成员,并将makeSubclass添加到原型,以节省您不必编写Class.prototype...非常多。 很多class级系统都这样做,例如:

 Circle= Shape.makeSubclass({ _init: function(x, y, z) { Shape.prototype._init.call(this, x, y); this.r= r; }, ... }); 

在对象系统中,你可能会考虑很多潜在的特性,而且没有人真正同意某个特定的公式。


closures的方式 ,然后。 这避免了JavaScript基于原型的inheritance的问题,因为根本不使用inheritance。 代替:

 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方法副本(以及我们添加的任何其他方法或其他类成员)。

每个实例拥有自己的每个类成员副本的坏处是效率不高。 如果您正在处理大量的子类实例,原型inheritance可能会更好地为您服务。 同样调用一个基类的方法也有点烦人,你可以看到:我们必须记住在子类构造函数覆盖它之前该方法是什么,否则会丢失。

[也因为这里没有inheritance, instanceof操作符将不起作用。 如果你需要的话,你将不得不提供你自己的class-sniffing机制。 虽然你可以用类似于原型inheritance的方式摆弄原型对象,但是这样做有点棘手,并不是真正值得让instanceof工作。

每个拥有自己的方法的实例的好处是该方法可以绑定到拥有它的特定实例。 这很有用,因为JavaScript在方法调用中用this方式来绑定this方法,如果从方法调用者中分离一个方法,结果就是:

 var ts= mycircle.toString; alert(ts()); 

那么this方法里面的内容就不会像预期的那样成为Circle实例了(它实际上就是全局window对象,造成了广泛的debugging困境)。 实际上,这通常发生在一个方法被采用并分配给setTimeoutonclick或者EventListener的时候。

用原型的方法,你必须为每个这样的任务包含一个闭包:

 setTimeout(function() { mycircle.move(1, 1); }, 1000); 

或者,在将来(或者现在如果你破解了Function.prototype),你也可以用function.bind()来做到这一点:

 setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000); 

如果你的实例是以闭合的方式完成的,绑定是通过closures实例variables(通常称为self ,虽然我个人build议反对后者,因为self已经在JavaScript中有另外一个不同的含义)。 尽pipe你没有在上面的代码片段中获得参数1, 1所以如果你需要的话,你仍然需要另一个闭包或一个bind()

closures方法也有很多变种。 您可能更愿意完全省略this ,创build一个新的并返回它,而不是使用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当我正在做强OO的东西时,我倾向于实现真正的JavaScriptinheritance的原型,并closures简单的一次性页面效果。

但是对于大多数程序员来说,这两种方式都是非常直观的。 两者都有很多潜在的混乱变化。 如果你使用其他人的代码/库,你将遇到两者(以及许多中间和一般破碎的计划)。 没有一个普遍接受的答案。 欢迎来到JavaScript对象的精彩世界。

[这是为什么JavaScript不是我最喜欢的编程语言的第94部分]

我经常使用这种模式 – 我发现当我需要时,它给了我相当大的灵活性。 在使用中,它与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; }(); 

这使用了一个创build时调用的匿名函数,返回一个新的构造函数。 因为匿名函数只被调用一次,所以可以在其中创build私有的静态variables(它们在闭包内部,对于类的其他成员是可见的)。 构造函数基本上是一个标准的JavaScript对象 – 您可以在其中定义私有属性,公共属性附加到thisvariables。

基本上,这种方法结合了Crockfordian方法和标准的Javascript对象来创build一个更强大的类。

您可以像使用其他Javascript对象一样使用它:

 Foo.publicStaticMethod(); //calling a static method var test = new Foo(); //instantiation test.publicMethod(); //calling a method 

道格拉斯·克罗克福德(Douglas Crockford)“好的部分”The Good Parts) 他build议避免操作员创build新的对象。 相反,他build议创build定制的构造函数。 例如:

 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中,一个函数是一个对象,可以用来和运算符一起构造对象。 按照惯例,打算用作构造函数的函数以大写字母开头。 你经常看到像这样的东西:

 function Person() { this.name = "John"; return this; } var person = new Person(); alert("name: " + person.name);** 

如果您在实例化新对象时忘记使用new运算符,则得到的是一个普通的函数调用,而其绑定到全局对象而不是新对象。

继续回答鲍比的回答

在es6中你现在可以创build一个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}`; } } 

在es6结束了一些清洁,并更容易阅读。


这是一个很好的例子:

 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); 

当在构造函数调用中使用closures“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的说法,你可能会得到很多。 约翰·雷西格也很出色。 祝你好运!

另一种方法是http://jsfiddle.net/nnUY4/ (我不知道这种处理对象的创build和揭示function遵循任何特定的模式)

 // 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" 

Closure是多才多艺的。 bobince在创build对象时已经很好地总结了原型与封闭方法。 不过,你可以用函数式编程的方式来模仿OOP某些方面。 记住函数是JavaScript中的对象 ; 所以以不同的方式使用函数作为对象。

这是closures的一个例子:

 function outer(outerArg) { return inner(innerArg) { return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context } } 

前一段时间,我遇到了Mozilla关于closures的文章。 下面是我眼中的一些东西:“闭包让你把一些数据(环境)和一个对数据进行操作的函数联系起来, 这与面向对象编程有很明显的相似之处,对象允许我们把一些数据(对象的属性)用一种或多种方法 “。 这是我第一次读闭包和经典OOP之间的并行性,而没有提到原型。

怎么样?

假设你想计算一些项目的增值税。 增值税在申请期间可能保持稳定。 在OOP(伪代码)中做到这一点的一种方法:

 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; } } 

基本上,你通过一个增值税的价值进入你的构造函数,你的计算方法可以通过closures操作 。 现在,而不是使用类/构造函数,将您的增值税作为parameter passing给函数。 因为你唯一感兴趣的是计算本身,所以返回一个新的函数,这就是计算方法:

 function calculator(vat) { return function(item) { return item * vat; } } var calculate = calculator(1.10); var jsBook = 100; //100$ calculate(jsBook); //110 

在您的项目中,确定最适合计算增值税的最佳值。 作为一个经验法则,无论何时传递相同的参数,有一种方法可以使用闭包来改进它。 不需要创build传统对象。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

除了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移动。

你也可以试试这个

  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(); 

基本上,在JS中没有类的概念,所以我们使用函数作为与现有devise模式相关的类构造函数。

 //Constructor Pattern function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.doSomething = function(){ alert('I am Happy'); } } 

直到现在JS不知道你想创build一个对象,所以这里来了新的关键字。

 var person1 = new Person('Arv', 30, 'Software'); person1.name //Arv 

参考:Web开发专业JS – Nik Z

 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);