我想知道创建具有属性和方法的JavaScript对象的最佳方法是什么。
我见过有人用var self = this然后用self的例子。在所有函数中确保作用域总是正确的。
然后我看到了使用.prototype来添加属性的例子,而其他人则是内联的。
谁能给我一个适当的例子,一个JavaScript对象的一些属性和方法?
我想知道创建具有属性和方法的JavaScript对象的最佳方法是什么。
我见过有人用var self = this然后用self的例子。在所有函数中确保作用域总是正确的。
然后我看到了使用.prototype来添加属性的例子,而其他人则是内联的。
谁能给我一个适当的例子,一个JavaScript对象的一些属性和方法?
当一个人在构造函数调用期间使用关闭“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的文章,您可能会学到很多东西。约翰·瑞西格也很出色。好运!
你也可以这样做,使用结构:
function createCounter () {
var count = 0;
return {
increaseBy: function(nb) {
count += nb;
},
reset: function {
count = 0;
}
}
}
然后:
var counter1 = createCounter();
counter1.increaseBy(4);
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操作符,则得到的是一个普通的函数调用,并且它绑定到全局对象,而不是绑定到新对象。
我经常使用这种模式——我发现当我需要它的时候,它给了我相当大的灵活性。在使用中,它非常类似于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
在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部分]
另一种方法是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"
闭包是万能的。Bobince已经很好地总结了创建对象时的原型与闭包方法。但是,您可以用函数式编程的方式使用闭包来模拟OOP的某些方面。记住,函数在JavaScript中是对象;所以用另一种方式使用function作为对象。
这里有一个闭包的例子:
function outer(outerArg) {
return inner(innerArg) {
return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context
}
}
前段时间我看到Mozilla关于Closure的文章。下面是让我眼前一亮的:“闭包让你将一些数据(环境)与对这些数据进行操作的函数相关联。这与面向对象编程有明显的相似之处,在面向对象编程中,对象允许我们将一些数据(对象的属性)与一个或多个方法关联起来。”这是我第一次在没有参考原型的情况下看到闭包和经典OOP之间的相似之处。
How?
假设您想要计算某些项目的增值税。增值税可能在应用程序的生命周期内保持稳定。在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;
}
}
基本上,你将一个VAT值传递给你的构造函数,你的计算方法可以通过闭包对它进行操作。 现在不再使用类/构造函数,而是将VAT作为参数传递到函数中。因为你唯一感兴趣的东西是计算本身,返回一个新函数,这是计算方法:
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
基本上在JS中没有类的概念,所以我们使用函数作为与现有设计模式相关的类构造函数。
//Constructor Pattern
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.doSomething = function(){
alert('I am Happy');
}
}
到目前为止,JS还不知道你想要创建一个对象,所以这里有一个新的关键字。
var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv
参考:专业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);
继续鲍恩斯的回答
在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}`;
}
}
最终在es6中更干净,更容易阅读。
下面是一个很好的例子:
类形状{ 构造函数(x, y) { 这一点。X = X; 这一点。Y = Y; } toString () { 返回' Shape at ${this。x} $ {this.y} '; } } 类Circle扩展形状{ 构造函数(x, y, r) { 超级(x, y); 这一点。R = R; } toString () { let shapeString = super.toString(); 返回半径为${this.r}的圆形${shapeString}; } } let c = new Circle(1,2,4); Console.log (" + c, c);
除了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();
创建对象
在JavaScript中创建对象最简单的方法是使用以下语法:
Var检验= { A: 5, B: 10, F:函数(c) { 返回。A +这个。B + c; } } console.log(测试); console.log (test.f (3));
这对于以结构化的方式存储数据非常有用。
然而,对于更复杂的用例,创建函数的实例通常更好:
函数测试(a, b) { 这一点。A = A; 这一点。B = B; 这一点。F =函数(c) { 返回。A +这个。B + c; }; } var test = new test (5,10); console.log(测试); console.log (test.f (3));
这允许你创建多个对象共享相同的“蓝图”,类似于你如何使用类在eg。Java。
然而,通过使用原型,这仍然可以更有效地完成。
当一个函数的不同实例共享相同的方法或属性时,您可以将它们移动到该对象的原型中。这样,函数的每个实例都可以访问该方法或属性,但不需要为每个实例复制该方法或属性。
在我们的例子中,将方法f移动到原型中是有意义的:
函数测试(a, b) { 这一点。A = A; 这一点。B = B; } Test.prototype.f =函数(c) { 返回。A +这个。B + c; }; var test = new test (5,10); console.log(测试); console.log (test.f (3));
继承
在JavaScript中进行继承的一个简单而有效的方法是使用以下两行代码:
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
这类似于这样做:
B.prototype = new A();
两者之间的主要区别是,A的构造函数在使用Object时不运行。创建,这更直观,更类似于基于类的继承。
你总是可以选择在创建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的构造函数链中混合另一个对象,你可以combine object。用Object创建。分配:
B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;
Demo
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());
Note
对象。create可以在包括IE9+在内的所有现代浏览器中安全使用。对象。assign不能在任何版本的IE和一些移动浏览器中工作。建议对对象进行填充。创建和/或对象。如果您希望使用它们并支持未实现它们的浏览器,则分配。
你可以为Object找到一个polyfill。建立在这里 还有一个是Object。分配。
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() {...};
我想提一下,我们可以使用标题或字符串来声明对象。 每种类型都有不同的调用方法。见下文:
Var检验= { useTitle: "这里我们使用'a Title'来声明一个对象", 'useString': "这里我们使用'a String'来声明一个对象", onTitle:函数(){ 返回this.useTitle; }, onString:函数类型{ 返回(类型); } } console.log (test.onTitle ()); console.log (test.onString (' useString '));