我有一个关于以下规范对象的问题。create方法:
Object.create = function(o, props) {
function F() {}
F.prototype = o;
if (typeof(props) === "object") {
for (prop in props) {
if (props.hasOwnProperty((prop))) {
F[prop] = props[prop];
}
}
}
return new F();
};
在上面代码的第3行,我们将F对象的prototype属性设置为o参数的prototype。
我本以为这意味着o和F都指向同一个原型,因此指向同一组成员。
但代码随后会复制prop-in-props循环中的所有成员。
如果我们手动复制所有成员,那么在第3行中设置原型有什么意义?
您的问题中的Object.create
版本有一个错误:循环将属性附加到构造函数F
(而不是返回的对象或其原型),这意味着它们在创建的对象中不可访问。
Object.create
的第二个参数的属性应该被复制到新创建的对象。Mozilla的Object.create
文档是这样描述的:
如果已指定但未定义,则其可枚举自身属性(即在其自身上定义的属性,而不是沿其原型链的可枚举属性)的对象指定要添加到新创建的对象的属性描述符以及相应的属性名称。
尝试使用问题中的Object.create
版本运行以下代码:
o = Object.create(
{a: "prototype's a", b: "prototype's b"},
{a: "object's a"}
);
您会发现o.a == "prototype's a"
和o.b == "prototype's b"
、"object's a"
丢失。
以下版本的函数可能更有用:
Object.create = function(o, props) {
var newObj;
// Create a constructor function, using o as the prototype
function F() {}
F.prototype = o;
// Create a new object using F as the constructor function
newObj = new F();
// Attach the properties of props to the new object
if (typeof(props) === "object") {
for (prop in props) {
if (props.hasOwnProperty((prop))) {
newObj[prop] = props[prop];
}
}
}
return newObj;
};
让我们用同样的例子来尝试一下:
o = Object.create(
{a: "prototype's a", b: "prototype's b"},
{a: "object's a"}
);
新对象o
是用具有属性a
和b
以及它自己的属性a
的原型创建的。
让我们先来看o.b
:o.hasOwnProperty("b")
将返回false
,因为o
没有名为b
的属性。这就是原型的由来;因为没有属性b
,所以在原型上查找它,并因此查找o.b === "prototype's b"
。
另一方面,o.hasOwnProperty("a")
将返回true
,因为o
确实具有a
属性。CCD_ 24,并且没有从原型中查找到任何内容。
正如@chuckj的回答所指出的,Object.create
的正确实现比这更复杂。有关更多详细信息,请参阅John Resig关于ECMAScript 5对象和属性的文章。
您提供的代码不等同于ECMA标准定义的Object.create()
。第二个参数的成员,这里称为prop
,应该是在结果对象上定义的一组描述符,而不是复制到构造函数。这更准确(但也不是绝对正确),
Object.create = function(o, props) {
function F() {}
F.prototype = o;
var result = new F();
if (typeof(props) === "object")
for (prop in props)
if (props.hasOwnProperty(prop) && typeof(props[prop].value) != "undefined")
result[prop] = props[prop].value;
return result;
};
props
参数包含要与原型o
中指定的不同的新对象的成员的属性描述符。这些值不应该从字面上复制到对象中,而是应该像调用Object.defineProperties()
一样进行处理,上面通过将描述符的value
部分分配给新对象来模拟。Object.create()
不能精确地聚合填充到ECMA3,因为它可以用于用访问器函数指定属性,尽管以上内容适用于使用value
而不是get
或set
的props
。
如果您对polyfill版本不感兴趣,以下是对Object.create()
功能的准确描述,假设__proto__
和Object.defineProperties()
都存在。
Object.create = function (o, props) {
if (typeof(o) !== "object" && o != null) throw new TypeError();
var result = new Object();
if (o != null)
result.__proto__ = o;
if (typeof(props) !== "undefined")
Object.defineProperties(result, props);
return result;
};
将F
视为返回对象的新实例的函数,而F.prototype
则是对新实例借用属性的对象的引用。
使用F
创建的实例具有独立于F.prototype
的自己的属性集。
当在F
的情况下找不到属性时,运行时将向原型链上游查看该属性是否存在于F.prototype
中。
这就是为什么你想要复制一些并继承其他的。
原型的目的是提供类代码重用/属性存储。
将F.prototype
想象为F
的类实例作为对象。