2

我最近一直在做的一个项目让我需要创建一个函数,该函数可以返回 JSON 对象的完整副本,递归地复制任何内部对象。经过几次失败的尝试,我想出了这个:

function copyObj(obj) {
    var copy;
    if (obj instanceof Array) {
        copy = [];
        for (var i in obj) {
            copy.push(copyObj(obj[i]));
        }
    }
    else if (obj instanceof Object) {
        copy = {};
        for (var prop in obj) {
            copy[prop] = copyObj(obj[prop]);
        }
    }
    else {
        copy = obj;
    }

    return copy;
}

该函数非常适合我的目的,即复制仅包含原始类型、数组和嵌套的通用 JSON 对象的对象。例如,它将返回一个完美的副本:{ prop1:0, prop2:'test', prop3:[1, 2, 3], prop4:{ subprop1:['a', 'b', 'c'], subprop2:false } }.

不过,这个函数有一点让我烦恼——它无法处理任何其他类型的对象(例如RegExp对象)。我想通过添加处理它们的能力来改进它,但同时我真的宁愿不只是拥有一堵巨大的else if (obj instanceof [insert object type here]). 因此,我的问题是:在 JavaScript 中是否有一种简单的方法来区分通用对象(即声明为的对象var obj = { })和具有适当原型/构造函数的对象?如果是这样,是否还有一种复制此类对象的简单通用方法?我对问题的第二部分的期望是否定的,我仍然需要特殊处理来调用构造函数,但我仍然想确定地知道这两种方式。

PS 如果有人对上下文感到好奇,该项目需要我操作服务器上的大量项目,但对于不同的连接客户端以不同的方式。我能想到的最简单的处理方法是创建一个主列表,然后让服务器克隆一个新副本以进行操作,而无需更改每个连接的新客户端的主列表,因此需要copyObj().

编辑:我可能应该在原始问题中提到这一点 - 这是使用 node.js 作为服务器而不是在浏览器中运行的,因此浏览器交叉兼容性不是问题。

编辑 2:为了不让评论过于混乱,我会在这里提及:我尝试使用上面的示例对象对我的copyObj()函数进行快速基准测试。JSON.parse(JSON.stringify(obj))我的版本的运行时间似乎是 JSON 方法所用时间的大约 75%(我的 100 万份副本耗时约 3.2 秒,JSON 耗时约 4.4 秒)。所以这让我觉得花时间写自己的感觉好多了。

编辑 3:根据 Vitum.us 的答案中的对象类型列表,我将我的copyObj()函数的更新版本放在一起。我没有对它进行广泛的测试,性能比旧版本差大约 2 倍,但我认为它实际上应该适用于所有内置类型(假设列表是完整的)。

function copyObjNew(obj) {
    var copy;
    if (obj.constructor === Object) {
        // Generic objects
        copy = {};
        for (var prop in obj) {
            copy[prop] = copyObjNew(obj[prop]);
        }
    }
    else if (obj.constructor === Array) {
        // Arrays
        copy = [];
        for (var i in obj) {
            copy.push(copyObjNew(obj[i]));
        }
    }
    else if (obj.constructor === Number || obj.constructor === String || obj.constructor === Boolean) {
        // Primitives
        copy = obj;
    }
    else {
        // Any other type of object
        copy = new obj.constructor(obj);
    }

    return copy;
}

正如迈克建议的那样,我现在正在使用该.constructor物业,而且它似乎正在发挥作用。到目前为止,我已经使用RegExpDate对象对其进行了测试,它们似乎都可以正确复制。你们有没有看到任何明显(或微妙)不正确的地方?

4

2 回答 2

0

检测普通 JS 对象(用{}or创建new Object)的一种方法是使用 jQuery 方法jQuery.isPlainObject。但是,文档说“主机对象有许多不一致的地方,这些不一致很难可靠地检测跨平台。因此,在某些情况下,$.isPlainObject() 可能会在不同的浏览器之间进行不一致的评估。 ”这是否可靠应该测试 node.js。

编辑:回应您的评论:您可以将 jQuery 与 node.js 一起使用,请参阅此问题:我可以将 jQuery 与 Node.js 一起使用吗?

否则,也可以将该方法的 jQuery 实现复制到您的项目中,因为 MIT 许可证 ( https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt ) 似乎允许这样做。jQuery实现:

isPlainObject: function( obj ) {
        // Not plain objects:
        // - Any object or value whose internal [[Class]] property is not "[object Object]"
        // - DOM nodes
        // - window
        if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
            return false;
        }

        // Support: Firefox <20
        // The try/catch suppresses exceptions thrown when attempting to access
        // the "constructor" property of certain host objects, ie. |window.location|
        // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
        try {
            if ( obj.constructor &&
                    !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
                return false;
            }
        } catch ( e ) {
            return false;
        }

        // If the function hasn't returned already, we're confident that
        // |obj| is a plain object, created by {} or constructed with new Object
        return true;
    }
于 2013-10-30T16:18:17.383 回答
0

您可以使用它来检测对象是否是正则表达式

Object.prototype.toString.call( regexpObject ) == "[object RegExp]"

这是规范中提到的获取对象类的方式。

ECMAScript 5,第 8.6.2 节对象内部属性和方法

[[Class]] 内部属性的值由本规范为每种内置对象定义。宿主对象的 [[Class]] 内部属性的值可以是除“Arguments”、“Array”、“Boolean”、“Date”、“Error”、“Function”、“JSON”之一之外的任何 String 值、“数学”、“数字”、“对象”、“正则表达式”和“字符串”。[[Class]] 内部属性的值在内部用于区分不同种类的对象。请注意,除了通过 Object.prototype.toString(参见 15.2.4.2)外,本规范没有为程序提供任何访问该值的方法。

RegExp 是在第 15.10 节 RegExp(RegularExpression)Objects规范中定义的一类对象:

RegExp 对象包含一个正则表达式和相关的标志。

然后,您可以使用复制 RegExp 对象new RegExp()

var oldObject = /[a-z]+/;
var newObject = new RegExp(oldObject);
于 2013-10-30T16:47:19.233 回答