我正在学习构建 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
的值不同,但其他属性都很好。
这可能是什么问题?
最佳答案
问题在于 extend()
函数 - 它显然不是为了支持复制 DOM 节点(如 document.body
)而编写的。我建议改用 Object.assign()
- 它是 ES6 中可用的 native 函数,并且有适用于旧版浏览器的垫片(例如 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()
方法,使其仅复制对 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()
的函数,我希望它能够进行深复制或浅复制,没有特殊异常(exception)。因此,我建议使用 jQuery 对象或一些类似的包装对象,而不是 DOM 节点本身。
更新
如上所述,extend()
方法还有另一个问题,导致它无法正确克隆 jQuery 对象。问题在于它不保留对象的原型(prototype),而只是以空对象 {}
开头。您可以使用 Object.create()
来解决这个问题,但仍然需要额外的工作来解决它的其他一些问题(请参阅下面的评论)。因此,我建议仅使用现有的第 3 方克隆功能。
例如,您可以使用 lodash 中的 cloneDeepWith
函数:
settings = _.cloneDeepWith(this.defaults, options);
(注意:显然 lodash 只是复制 DOM 节点的引用并深度克隆其他所有内容,因此在这方面,它的工作方式类似于我上面编写的 extend()
的修改版本。所以它可能对您来说是一个方便的选择。)
或者 jQuery 的 extend()
方法使用 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 。)
关于Javascript 将元素作为值传递,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42546341/