我正在学习构建一个Javascript构造函数并遇到以下问题:
var x = new Constructor({
B: '1000',
child: document.getElementById('id'), // assume there is a div#id
});
function Constructor(options) {
var settings;
this.defaults = {
A: 'value A',
B: 2,
parent: document.body,
child: document.getElementById('anotherId'), // assume there is a div#anotherId
}
settings = extend({}, this.defaults, options);
console.log(this.defaults.A) // outputs 'value A'
console.log(this.defaults.B) // outputs 2
console.log(this.defaults.parent) // outputs <body>...</body>
console.log(this.defaults.child) // outputs <div id="id">...</div>
console.log(settings.A) // outputs 'value A'
console.log(settings.B) // outputs '1000'
console.log(settings.parent) // outputs empty object
console.log(settings.child) // outputs empty object
}
function extend(out) {
out = out || {};
for (var i = 1; i < arguments.length; i++) {
var obj = arguments[i];
if (!obj) continue;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
out[key] = (typeof obj[key] === 'object') ? extend(out[key], obj[key])
: obj[key];
}
}
}
return out;
}
问题是settings.parent
与this.defaults.parent
的值不同,但其他属性也没问题。
这可能是什么问题?
答案 0 :(得分:2)
问题是extend()
函数 - 它显然不支持复制像document.body
这样的DOM节点。我建议使用Object.assign()
- 它是ES6中提供的本机功能,并且旧版浏览器可以使用填充程序(例如MDN上的this one)。
用法:
settings = Object.assign({}, this.defaults, options);
请注意,这只会产生浅色副本,因此在某些情况下可能无效。如果要复制嵌套对象,并且希望确保不在this.defaults
或options
中修改原始对象,则可能需要进行深度复制/克隆。
我不确定你在哪里获得extend()
功能,但它确实做了一些基本的深度复制。问题是DOM节点不能以与常规对象相同的方式克隆。有一种克隆它们的方法 - 你调用clone()
方法 - 但目的是创建一个DOM节点的实际副本,然后你可以在文档中的其他地方添加,这不是你的意思我想在这里做。
您使用的是任何DOM库,例如jQuery吗?如果是这样,你可以做到这一点并避免整个问题:
this.defaults = {
A: 'value A',
B: 2,
parent: $('body'),
child: $('#anotherId'), // assume there is a div#anotherId
}
这是有效的,因为jQuery对象是包装一个或多个DOM节点的常规JS对象。
更新:实际上,为了实现此功能,您仍需要修改extend()
功能或使用其他克隆功能,因为extend()
中还有另一个错误。请参阅下面的“更新”。
或者,您可以更改extend()
方法,以便它只复制对DOM节点的引用,但深度复制所有其他类型的对象。这是你如何做到的:
function extend(out){
out = out || {};
for (var i = 1; i < arguments.length; i++){
var obj = arguments[i];
if (!obj) continue;
for (var key in obj){
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
//don't deep-copy DOM nodes
if (obj.nodeType && obj.nodeName && obj.cloneNode) {
out[key] = obj[key];
}
else out[key] = extend(out[key], obj[key]);
}
else out[key] = obj[key];
}
}
}
return out;
}
然而,我认为这是一个不寻常的解决方案;就个人而言,如果我遇到一个名为extend()
的函数,我希望它能够进行深拷贝或浅拷贝而不会有特殊的例外。所以我建议使用jQuery对象或类似的包装器对象而不是DOM节点本身。
<强>更新强>
如上所述,extend()
方法有另一个问题导致它无法正确克隆jQuery对象。问题是它不保留对象的原型,而只是从空对象{}
开始。您可以使用Object.create()
来修复此问题,但仍需要额外的工作来解决其他问题(请参阅下面的评论)。所以我建议只使用现有的第三方克隆功能。
例如,您可以使用lodash中的cloneDeepWith
函数:
settings = _.cloneDeepWith(this.defaults, options);
(注意:显然lodash只复制DOM节点的引用并深入克隆其他所有内容,所以在这方面它的工作方式就像我上面写的extend()
的修改版本。所以它可能是一个方便的选项。你。)
使用extend()
选项的jQuery deep
方法:
settings = $.extend(true /* setting the first argument to true makes it deep copy */,
{}, this.defaults, options);
我还编写了自己的deepCopy()
函数,这个函数非常强大...它是我的simpleOO
库的一部分,随着时间的推移它将变得无关紧要,因为Javascript有类,但这里是文档的链接如果您有兴趣,请使用deepCopy
方法:
https://github.com/mbrowne/simpleoo.js/wiki/Additional-Examples#deep-copying-cloning-objects
(我的deepCopy
函数也可单独使用;请参阅我的答案中的第二个示例:https://stackoverflow.com/a/13333781/560114。)