与jQuery的extend方法等效的JavaScript


92

背景

我有一个将config对象作为参数的函数。在函数中,我也有default对象。这些对象中的每个对象都包含本质上用作属性的其余代码设置的属性。为了避免必须指定config对象中的所有设置,我使用jQuery的extend方法来填充一个新对象,如果未在对象中指定settings默认值,则使用该default对象的任何默认值config

var config = {key1: value1};
var default = {key1: default1, key2: default2, key 3: default 3};

var settings = $.extend(default, config);

//resulting properties of settings:
settings = {key1: value1, key2: default2, key 3: default 3};

问题

这很好用,但是我想在不需要jQuery的情况下重现此功能。是否有使用普通ol'javascript做到同样优雅(或接近)的方法?


编辑:不重复的理由

这个问题不是“ 如何动态合并两个JavaScript对象的属性? ”问题的重复。那个问题只是想创建一个包含来自两个单独对象的所有键和值的对象-我特别想解决在两个对象共享一些但不是全部键并且哪个对象将获得优先权的情况下如何执行此操作(如果键重复,则结果对象的默认值)。更具体地说,我想解决使用jQuery方法来实现这一点的问题,并找到一种无需jQuery的替代方法。尽管两个问题的许多答案都重叠,但这并不意味着问题本身是相同的。



好主意@RocketHazmat,请注意,如果你copypasta该功能也有一些其他的jQuery的依赖像jQuery.isPlainObjectjQuery.isFunction
安迪Raddatz

Answers:


131

要在代码中获得结果,您可以执行以下操作:

function extend(a, b){
    for(var key in b)
        if(b.hasOwnProperty(key))
            a[key] = b[key];
    return a;
}

请记住,在那里扩展的方式将修改默认对象。如果您不想要,请使用

$.extend({}, default, config)

模仿jQuery功能的更强大的解决方案如下:

function extend(){
    for(var i=1; i<arguments.length; i++)
        for(var key in arguments[i])
            if(arguments[i].hasOwnProperty(key))
                arguments[0][key] = arguments[i][key];
    return arguments[0];
}

1
我想看一个实际的代码示例,也许是摆弄,因为普通的js酷得多,但是却很抽象,谢谢一百万。
2014年

3
这不会像$ .extend那样递归
ekkis

30

您可以使用Object.assign。

var defaults = {key1: "default1", key2: "default2", key3: "defaults3"};
var config = {key1: "value1"};

var settings = Object.assign({}, defaults, config); // values in config override values in defaults
console.log(settings); // Object {key1: "value1", key2: "default2", key3: "defaults3"}

它将所有可枚举的自身属性的值从一个或多个源对象复制到目标对象,然后返回目标对象。

Object.assign(target, ...sources)

它适用于除IE(但包括Edge)之外的所有桌面浏览器。它减轻了移动支持。

在这里亲自查看:https : //developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

关于深复制

但是,Object.assign没有jQuery的extend方法具有的深层选项。

注意:您通常可以使用JSON达到类似效果

var config = {key1: "value1"};
    var defaults = {key1: "default1", key2: "default2", keyDeep: {
        kd1: "default3",
        kd2: "default4"
    }};
    var settings = JSON.parse(JSON.stringify(Object.assign({}, defaults, config))); 
    console.log(settings.keyDeep); // Object {kd1: "default3", kd2: "default4"}

是的,但是在这里它会跳过默认值中的key1,而不是将其添加到新对象中。
Ionut Necula

@Anonymous实际上它确实添加了它,但是它被config数组中的key1值覆盖。
2016年

我以为是这样 因此,它毕竟没有添加。
Ionut Necula


4

这与我尝试消除jQuery依赖性时想出的深度复制方法略有不同。它主要是为小巧而设计的,因此它可能并没有一个人期望的所有功能。应该完全与ES5兼容(由于使用Object.keys从IE9开始):

function extend(obj1, obj2) {
    var keys = Object.keys(obj2);
    for (var i = 0; i < keys.length; i += 1) {
      var val = obj2[keys[i]];
      obj1[keys[i]] = ['string', 'number', 'array', 'boolean'].indexOf(typeof val) === -1 ? extend(obj1[keys[i]] || {}, val) : val;
    }
    return obj1;
  }

您可能想知道第五行到底是做什么的...如果obj2.key是对象文字(即,如果它不是普通类型),则我们递归地对其调用extend。如果obj1中尚不存在具有该名称的属性,则我们首先将其初始化为空对象。否则,我们只需将obj1.key设置为obj2.key。

这是我的一些摩卡/柴测试,应该可以证明在这里正常工作的情况:

it('should extend a given flat object with another flat object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 20.16,
  };
  const obj2 = {
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 42,
    prop3: true,
    prop4: 77.123,
    propNew1: 'newVal1',
    propNew2: 71,
  });
});

it('should deep-extend a given flat object with a nested object', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: 'val2',
  };
  const obj2 = {
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'val1',
    prop2: 'val2',
    propNew1: 'newVal1',
    propNew2: {
      propNewDeep1: 'newDeepVal1',
      propNewDeep2: 42,
      propNewDeep3: true,
      propNewDeep4: 20.16,
    },
  });
});

it('should deep-extend a given nested object with another nested object and deep-overwrite members', () => {
  const obj1 = {
    prop1: 'val1',
    prop2: {
      propDeep1: 'deepVal1',
      propDeep2: 42,
      propDeep3: true,
      propDeep4: {
        propDeeper1: 'deeperVal1',
        propDeeper2: 777,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  };
  const obj2 = {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
      },
    },
  };
  assert.deepEqual(utils.extend(obj1, obj2), {
    prop1: 'newVal1',
    prop2: {
      propDeep1: 'newDeepVal1',
      propDeep2: 84,
      propDeep3: false,
      propDeep4: {
        propDeeper1: 'newDeeperVal1',
        propDeeper2: 888,
        propDeeper3: 'I will survive',
      },
    },
    prop3: 'lone survivor',
  });
});

对于对此实施的反馈或评论,我感到很高兴。提前致谢!


2

您可以使用for语句遍历对象的属性。

var settings = extend(default, config);

function extend(a, b){
    var c = {};
    for(var p in a)
        c[p] = (b[p] == null) ? a[p] : b[p];
    return c;
}

2
这不会考虑到中不存在的属性a。尽管在示例中未列出,但$.extend实际上可以通过合并所有对象的所有属性来实现。
Felix Kling 2012年

1
当我阅读jquery基本代码时,它实际上会执行递归功能来复制或克隆值。因此,它是深度克隆/复制,而不仅仅是像上面的代码那样在第一层进行复制。
2014年

2

我更喜欢以下代码,该代码使用我的通用forEachIn,并且不会破坏第一个对象:

function forEachIn(obj, fn) {
    var index = 0;
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            fn(obj[key], key, index++);
        }
    }
}

function extend() {
    var result = {};
    for (var i = 0; i < arguments.length; i++) {
        forEachIn(arguments[i],
            function(obj, key) {
                result[key] = obj;
            });
    }
    return result;
}

如果您确实想将内容合并到第一个对象中,则可以执行以下操作:

obj1 = extend(obj1, obj2);

这是到目前为止最好的答案。它进行深度复制,不破坏第一个对象,支持无限个对象,并且可以在扩展时更改类型(即,此处的许多答案不会将字符串扩展到对象,此答案会)。这得到了我书中的所有复选标记。全面扩展替换,非常容易理解。
尼克·斯蒂尔

0

当我使用纯JavaScript开发时,它对我有很大帮助。

function extends(defaults, selfConfig){
     selfConfig = JSON.parse(JSON.stringify(defaults));
     for (var item in config) {
         if (config.hasOwnProperty(item)) {
             selfConfig[item] = config[item];
         }
     }
     return selfConfig;
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.