9.2.1 Reading and Writing Inherited Properties
每个类有一个拥有一套属性的prototype对象,但也很有可能许多类的实例也继承了prototype的那些属性, 因为一个prototype的属性可以被许多的对象继承,那么JavaScript必须执行一个基本的读写属性值之间的不对称。 当你读对象o的p属性,JavaScript首先会看o是否有一个属性叫作p。如果没有,它接着就会查看o的prototype对象是否有一个属性叫作p,这就是基于prototype的继承机制。
当你对一个属性的值进行写操作,而另一个方面,JavaScript并没有用到prototype对象。看看为什么,考虑一下如果你 这样做(写操作)会发生什么,假设你去写o.p属性当o是没有这个属性,那么再想想JavaScript继续去查找p属性是否在o的 prototype对象里,然后允许你去改prototype这个属性,并且现在你已经改了这个属性了。。,那么所有的对象的p的值 都会被改动,你就惨了。。。
因此,属性的继承紧紧会发生在当你读的时候,而不是写的时候。如果你设置对象o里的属性p,p是继承与它的prototype, 将会发生的就是你直接创建一个新的属性p在对象o里。那么现在o有它自己的属性p,它不再是从它的prototype里继承p了。 当你读取p的值,JavaScript首先去查找o的属性。因为它会发现p在o里定义了,它就不再需要去查找prototype对象并且不会 找到p值在那里定义。我们有时会说这是o里的p属性隐藏了prototype里的p属性。
因为prototype属性会被一个类的所有对象所共享,使用它们去定义那些只有对于一个类的所有对象来说都是一样的属性,是很有用的。这样使得prototype对于定义方法来说是理想的。另外,那些常量值的属性(就好象数字),也很适合用prototype的属性来定义。如果你的类定义个一个有缺省值的属性,你也许会定义这个属性和它的缺省值在prototype对象,那么少有的一些对象想远离这些缺省值的就可任意创建自己的私有的,不共享的属性并定义它们之间的非缺省值。
9.2.2 Extending Built-in Types
其实并不仅仅是用户定义的类才有prototype对象。“Built-in”类,例如String和Date,也有prototype,且你能够分配值给他们。下面的例子,定义了一个新的适合所有String对象的方法。
//Returns true if the last character c
String.prototype.endsWith = function(c) {
return ( c == this.charAt(this.length - 1));
}
已经定义好的新的endsWith()方法在String prototype对象,你可以这样使用:
var message = "hello world";
message.endWith('h');//Returns false;
message.endWith('d');//Returns true;
其实最好永远不要添加属性到Object.prototype。任何属性或者方法,在循环中是可例举的,且添加到Object.prototype使得他们在每个JavaScript对象里是可见的。一个空的对象,{},会被希望是没有枚举型的属性。
这种技术在这里展示出来是为了扩展built-in 对象类型会被保证仅仅在内核JavaScript “native objects”里工作。当JavaScript被嵌入到一些上下文了,例如浏览器和Java程序,它可以访问额外的“host objects”例如表现出了浏览器的文档内容。当然,这些"host objects"是典型的没有构造函数和prototype对象,你也不能够扩展他们。 这里有一个安全的有用的去扩展built-in 类的prototype的例子:添加一个标准的方法给一个prototype当一个老的或者无法不兼容的JavaScript启用缺少了他们。就好象Function.apply()不能在IE4或5里使用。
//IE4&5执行Function.apply().
//This workaround is based on code by Aron Boodman.
if(!Function.prototype.apply) {
//Invoke this function as a method of the specified object,
//passing the specifed parameters. We have to use eval() to do this
Function.prototype.apply = function(object, parameters){
var f = this; // The function to invoke
var o = object || window; // The object to invoke it on
var args = parameters || []; // The arugmenst to pass
// Temporarily make the function into a method of o
// To do this we use a property name that is unlikely to exist.
o._$_apply_$_ = f;
// We will use eval() to invoke the method. To do this we've got
// to write the invocation as a string. First build the argument list.
var stringArgs = [];
for(var i = 0; i < args.length; i++)
stringArgs[i] = "args["+ i +"]";
// Concatenate the argument strings into a comma-separated list.
var arglist = stringArgs.join(",");
// Now build the entire method call string.
var methodcall = "o._$_apply_$_("+ arglist +")";
// Use the eval() function to make the methodcall
var result = eval(methodcall);
// Unbind the function from the object
delete o._$_apply_$_;
// And return the reuslt
return result;
}
}
另外一个例子,考虑到新的数组方法在Firefox 1.5里执行,如果你想要用新的 Array().map()方法但也想你的代码可以在不支持这个方法的平台上运行你可以这样做:
// Array.map() invokes a function f on each element of the array,
// returning a new array of the values that result from each function
// call.If map() is called with two arguments, the function f
// is invoked as a method of the second argument. When invoked, f()
// is passed 3 arguments. The first is the value of the array
// element. The second is the index of the array element, and the
// third is the array itself. In most cases it needs to use only the first argument.
if(!Array.prototype.map) {
Array.prototype.map = function(f,thisObject){
var results = [];
for(var len = this.length, i = 0; i < len; i++){
results.push(f.call(thisObject,this[i], i,this));
}
return results;
}
}