我懂一点Java,我喜欢整个类和OO编程风格。嗯,我读了一些关于JavaScript的文章,发现它没有像Java和c++那样的"经典"OO风格,甚至没有一个简单的构造函数。程序员有很多选择。我做了这个,你觉得怎么样?这样编写构造函数是可以的吗?
//constructor of Human
function Human(name, age, size, married) {
this.n = name;
this.a = age;
this.s = size;
this.m = married
this.printInformation = function() {
return "Name: " + this.n + ", Age: " + this.a + ", Size: " + this.s + ", Married: " + this.m;
};
}
var human1 = new Human("Lenny Linux", 42, "142cm", false);
window.alert(human1.printInformation());
它在工作,所以我想它应该没问题。但有一个问题:我也有其他选择。喜欢使用这些"对象字面量"或者它们的名字。我可以这样做:
var human1 = {
name: "Lenny Linux",
age: 42,
size: "142cm",
married: false,
printInformation: function() {
//...
}
}
这个更快,是吗?在我写完这个之后,有一个Human的"对象"另一个我需要初始化。但我更喜欢另一个。我更容易忽略我的代码。但是第一个例子不是典型的javascript,对吧?所以我必须忘记Java的一切并使用特殊的JavaScript风格?或者我可以保持我的Java代码风格吗?你觉得呢?
PS:第一个例子的另一个问题。当程序员初始化构造函数时,如果他在"已婚"变量处,我不能强迫他只使用布尔值,这让我很抓狂。但我真的很想强迫他。Age:也一样,只能是Integer…什么好主意吗?
谢谢!
您描述的两种方法实际上完全等同(除了语法),并且从Javascript的角度来看大多是ok的。也就是说,你可能不应该仅仅根据你现在的习惯来选择做什么——从长远来看,你需要习惯使用这门语言,而不是反对它。继续…
如果我想强制我的字段是某种类型,如在Java中?
Javascript是动态类型的,所以你将很难尝试应用相同的静态类型范例。你可以尝试在对象构造过程中进行运行时检查(使用typeof),但这通常不值得麻烦,因为检查仍然会在运行时进行,不检查可能会导致类似的错误,而且typeof非常有限(检查某些内容是否是数组很棘手,检查接口很烦人,并且让我们不要开始"陌生"的浏览器对象…)
最后,不要对动态类型太过紧张——你很快就会习惯的。
如果你说对象字面值方法和构造函数方法返回相同的结果,那么有什么区别呢?
首先,虽然对象字面量是一种非常整洁的语法,但有些东西需要拆分为多个语句,因此您需要一个函数来处理它们:
//Note: lowercase name since I won't be using 'new here...
//there is a good convention for only using capital names on
// "real" constructors
function create_human(name, age){
var obj = {};
obj.name = name;
obj.age = age;
//this needs to be on a separate statement
//since it involves the other fields
obj.isAdult = (obj.age >= 21);
return obj;
}
//not using 'new ...yet
var that_penguin = create_human("Lenny", 42);
请注意,对象字面值在这里仍然非常有用,在通常需要大量参数列表的情况下,使用它们来提供命名参数和默认参数是非常流行的:
function create_human(args){
var obj;
obj.name = args.name;
//...
}
var x = create_human({
name: 'Lenny',
age: 42,
//...
});
请记住:到目前为止,使用函数来构建对象与使用对象文字只是风格和组织的问题,最佳方法通常取决于您正在处理的特定情况。根据我的经验,对象字量对于创建单例和配置字典非常好,而函数对于在复杂对象中强制执行不变量或为常见对象提供速记非常有用。
那么"真正的"构造函数,new和this then是怎么处理的呢?手工显式构造对象的一个缺点是我们错过了一些我们习惯的OO的优点。通过为每个对象提供其方法的副本,我们不仅浪费了空间(在经典语言中,这些方法将存储在类中),而且还失去了差分继承(因为一切都是静态的)。Javascript通过 prototype 来处理这个问题。所有对象都有一个原型,当寻找一个属性(或方法)时,它会在原型中递归地搜索,以防它没有立即找到。
原型的一个常见用例是使类对象各自保留实例变量,但共享方法:
lenny:
name: "Lenny"
age: 42
__proto__: Person.prototype
glenda:
name: "Glenda"
age: 19
__proto__: Person.prototype
Person.prototype:
printInformation: ...
tons of methods: ...
这样我们可以访问lenny。printInformation,甚至没有注意到这个方法正在与Glenda共享。
要创建一个带有原型的对象,你可以使用object。创建(至少在较新的浏览器上)或使用构造函数和新操作符的旧方法:
function Person(name, age){
//The 'new operator provides an empty
// 'this' object with a suitable prototype.
// The constructor function just needs to fill in the
// instance variables.
this.name = name;
this.age = age;
//note: no return statement!
//and no methods as well
//(unless they need to be closures but thats another thing)...
}
//Methods in Person.prototype, will be shared by all Person instances:
Person.prototype = {
printInformation: function(){
console.log('my age is', this.age);
}
};
var lenny = new Person("Lenny", 42);
总结如果想使用语言的原型特性,请使用构造函数和new操作符。
请使用普通函数或对象字面值。
你应该从这本书中了解原型继承。
//constructor of Human
function Human(name, age, size, married) {
this.n = name;
this.a = age;
this.s = size;
this.m = married;
this.printInformation = function() {
return "Name: " + this.n + ", Age: " + this.a + ", Size: " + this.s +
", Married: " + this.m;
};
}
var human1 = new Human("Lenny Linux", 42, "142cm", false);
window.alert(human1.printInformation());
因此,printInformation
是一个很好的候选者,可以抛出Human
的原型,在Human
的实例之间创建共享方法。在定义Human
时,对this
的所有引用都将是在后台设置为空对象的对象字面量。此外,在幕后,一个隐藏的_proto_
属性被分配给对象,字面上是一个对象引用Human.prototype。因此,只要您不使Human.prototype
成为对新对象的引用,那么Human
的所有实例将具有指向Human.protoype
的相同函数指针。所以,这是你想要的效率的标准例子。
//constructor of Human
function Human(name, age, size, married) {
this.n = name;
this.a = age;
this.s = size;
this.m = married;
};
Human.prototype.printInformation = function() {
return "Name: " + this.n + ", Age: " + this.a + ", Size: " + this.s +
", Married: " + this.m;
};
var human1 = new Human("Lenny Linux", 42, "142cm", false);
window.alert(human1.printInformation());
在运行时,解释器将尝试在实例本身上找到printInformation
,而在这种情况下不会找到它。因此,它将遵循_proto_
链接。它在那里找到它,因为Human.prototype.printInformation
是一个函数。
之所以有效是因为所有实例都指向同一个对象:Human.prototype
;然而,当您在构造函数中使用this.method = function() {}
时,每个实例都将获得分配给它的新函数。