NodeJS的深度扩展(类似于jQuery的)

43

我正在处理NodeJS中对象的深拷贝问题。我的自定义扩展功能不太好用。Underscore的扩展功能是平的。虽然在stackexchange上有一些相当简单的扩展变体,但没有一个接近jQuery.extend(true,{},obj,obj,obj)..(大多数实际上很糟糕,并且会破坏异步代码的优势)。因此,我的问题是:是否有适用于NodeJS的良好深度副本?有人移植了jQuery的吗?


避免这样做。深拷贝不好。更倾向于浅拷贝。 - Raynos
3
为什么浅拷贝会成为一连串异步回调的噩梦,请解释一下? - itsatony
1
此外,我们的数据库结构(mongoDB)具有相当深层次的对象,我真的不想在结构转换方面折腾...在代码和数据库中使用完全相同的对象非常方便... - itsatony
当然可以。只是不要深拷贝它们。我使用来自mongo的对象,从来不会深拷贝它们:\ - Raynos
4
我不同意Raynos的观点,你应该根据自己的用例来判断这种行为是否正确。只是要注意有陷阱,理智使用。这是关于Underscore项目深层复制/扩展问题的辩论:https://github.com/documentcloud/underscore/issues/162 - Richard Marr
你可以使用这个插件 https://github.com/maxmara/dextend - Vlad Miller
11个回答

28

已经进行了移植。node-extend

请注意,该项目没有测试并且不太受欢迎,因此自行承担风险。

如上所述,您可能不需要进行深度复制。尝试更改数据结构,使您仅需要浅层复制。

几个月后

相反,我撰写了一个较小的模块,建议您使用xtend。它没有包含jQuery的实现,也没有像node-extend那样存在错误。


4
抱歉,仅仅因为你从未使用过深拷贝,就认为它们不好并且在所有情况下都应该避免使用,这种观点合理吗? - Josh
3
@itsatony xtend只会按设计进行浅层扩展。 - Raynos
5
在尝试了几个模块之后,我选择了node.extend,因为它可以正确地使用原型来克隆对象。而xtend和node-extend(带有连字符的那个)都无法做到这一点。 - donnut
3
@Raynos,你应该告诉别人你是所推广的库的作者。 - benweet
2
即使您是100%公正的,根据此处所述,您也必须“披露您的从属关系”。(http://meta.stackoverflow.com/help/behavior) - benweet
显示剩余7条评论

15

你需要jQuery,那就直接使用它:

function extend() {
    var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false,
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty,
        push = Array.prototype.push,
        slice = Array.prototype.slice,
        trim = String.prototype.trim,
        indexOf = Array.prototype.indexOf,
        class2type = {
          "[object Boolean]": "boolean",
          "[object Number]": "number",
          "[object String]": "string",
          "[object Function]": "function",
          "[object Array]": "array",
          "[object Date]": "date",
          "[object RegExp]": "regexp",
          "[object Object]": "object"
        },
        jQuery = {
          isFunction: function (obj) {
            return jQuery.type(obj) === "function"
          },
          isArray: Array.isArray ||
          function (obj) {
            return jQuery.type(obj) === "array"
          },
          isWindow: function (obj) {
            return obj != null && obj == obj.window
          },
          isNumeric: function (obj) {
            return !isNaN(parseFloat(obj)) && isFinite(obj)
          },
          type: function (obj) {
            return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
          },
          isPlainObject: function (obj) {
            if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) {
              return false
            }
            try {
              if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
                return false
              }
            } catch (e) {
              return false
            }
            var key;
            for (key in obj) {}
            return key === undefined || hasOwn.call(obj, key)
          }
        };
      if (typeof target === "boolean") {
        deep = target;
        target = arguments[1] || {};
        i = 2;
      }
      if (typeof target !== "object" && !jQuery.isFunction(target)) {
        target = {}
      }
      if (length === i) {
        target = this;
        --i;
      }
      for (i; i < length; i++) {
        if ((options = arguments[i]) != null) {
          for (name in options) {
            src = target[name];
            copy = options[name];
            if (target === copy) {
              continue
            }
            if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
              if (copyIsArray) {
                copyIsArray = false;
                clone = src && jQuery.isArray(src) ? src : []
              } else {
                clone = src && jQuery.isPlainObject(src) ? src : {};
              }
              // WARNING: RECURSION
              target[name] = extend(deep, clone, copy);
            } else if (copy !== undefined) {
              target[name] = copy;
            }
          }
        }
      }
      return target;
    }

并做一个小测试以展示它确实进行了深拷贝

extend(true, 
    {
        "name": "value"
    }, 
    {
        "object": "value",
        "other": "thing",
        "inception": {
            "deeper": "deeper",
            "inception": {
                "deeper": "deeper",
                "inception": {
                    "deeper": "deeper"
                }
            }
        }
    }
)

但是要记得提供归属权:https://github.com/jquery/jquery/blob/master/src/core.js


请注意,我没有带上“isPlainObject”,“isArray”或任何其他jQuery文件,因为我想指出您可以直接捕获它们的源代码并使用它们。 - jcolebrand
太棒了,非常感谢!我曾经试图自己解决它,但是可能搞砸了。你的方法可行,我的不行 :( - itsatony
像魔法一样好用!无法在Google Appscript中使用jQuery,这对我帮助很大!! - Jay Dadhania

11

请使用内置的util模块:

var extend = require('util')._extend;

var merged = extend(obj1, obj2);

12
这不是一个有文档记录的方法,并且前缀带有下划线通常意味着它不适合公开使用。 - Craig Younkins
3
util._extend 不是深层复制。 - dbkaplun
@CraigYounkins 这就是为什么隐私条约在现实世界中行不通的原因;) - Michael Franzl

11

快速且简单的深拷贝方法是使用一些 JSON 技巧。虽然这不是最高效的方法,但它确实可以非常好地完成工作。

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

3
如果一个对象只是基于数据的话,那么这样做很好;但如果你的对象是由特定构造函数生成的,带有自己的方法和继承关系,那么这样做就不可行了,因为所有这些都将丢失。 - marksyzm
2
@marksyzm 这是绝对正确的;它仅适用于复制简单值对象;对于日期、函数和某些情况下构造的对象会失败。 - Kato
不幸的是,函数丢失了。这对于除了函数之外的所有内容都完美运作。 - gabrielstuff
2
不,它并不完美地适用于所有情况,但对于函数来说是有用的。引用你之前的评论:它只适用于复制简单的值对象;对于日期、函数和某些实例化对象,它会失败。 - Kato
克隆并不一定是扩展。扩展需要一个目标。 - Michael Franzl

8

我知道这是一个老问题,但我想推荐 lodash的合并函数 作为一个好的解决方案。我总体上建议使用lodash来处理实用函数 :)


我喜欢lodash,但是lodash的extend会改变对象,这太糟糕了。 - Wtower
Lodash的变异可以通过在mergeextend的第一个参数中使用空对象来轻松避免。 var obj3 = lodash.extend(obj1, obj2) 会改变obj1var obj3 = lodash.extend({}, obj1, obj2) 不会改变obj1 - edgar.bjorntvedt

1

这适用于深度对象扩展...请注意,它会取代数组而不是它们的值,但显然可以根据你的喜好进行更新。它应该保持枚举功能和您可能希望它执行的所有其他操作

function extend(dest, from) {
    var props = Object.getOwnPropertyNames(from), destination;

    props.forEach(function (name) {
        if (typeof from[name] === 'object') {
            if (typeof dest[name] !== 'object') {
                dest[name] = {}
            }
            extend(dest[name],from[name]);
        } else {
            destination = Object.getOwnPropertyDescriptor(from, name);
            Object.defineProperty(dest, name, destination);
        }
    });
}

1
在Node.js中,您可以使用Extendify来创建一个_.extend函数,支持嵌套对象扩展(深度扩展)并且对其参数是不可变的(因此进行深拷贝)。
_.extend = extendify({
    inPlace: false,
    isDeep: true
});

1

node.extend可以深入使用,并具有熟悉的jQuery语法


1

只需安装扩展包。 文档: node扩展包 安装:

npm install extend

然后享受它:
extend ( [deep], target, object1, [objectN] )

deep是可选的。默认为false。如果切换为true,则会递归合并您的对象。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接