Javascript传递元素作为值

时间:2017-03-02 03:33:06

标签: javascript

我正在学习构建一个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.parentthis.defaults.parent的值不同,但其他属性也没问题。

这可能是什么问题?

1 个答案:

答案 0 :(得分:2)

问题是extend()函数 - 它显然不支持复制像document.body这样的DOM节点。我建议使用Object.assign() - 它是ES6中提供的本机功能,并且旧版浏览器可以使用填充程序(例如MDN上的this one)。

用法:

settings = Object.assign({}, this.defaults, options);

请注意,这只会产生浅色副本,因此在某些情况下可能无效。如果要复制嵌套对象,并且希望确保不在this.defaultsoptions中修改原始对象,则可能需要进行深度复制/克隆。

我不确定你在哪里获得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。)