等效于 jQuery 扩展方法的 JavaScript

背景资料

我有一个函数,它接受一个 config对象作为参数。在函数中,我还有 default对象。这些对象中的每一个都包含一些属性,这些属性实际上是作为函数中其余代码的设置工作的。为了避免必须指定 config对象中的所有设置,我使用 jQuery 的 extend方法来填充一个新对象 settings,如果 default对象中没有指定这些默认值,那么 settings就是 default对象中的默认值:

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 的替代方法。虽然这两个问题的许多答案是重叠的,但这并不意味着问题本身是相同的。

58829 次浏览

To get the result in your code, you would do:

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

Keep in mind that the way you used extend there will modify the default object. If you don't want that, use

$.extend({}, default, config)

A more robust solution that mimics jQuery's functionality would be as follows:

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];
}

You can loop through Object's properties using for statement.

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;
}

It helps me a lot when I develop with pure 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;
}

The approach of Ivan Kuckir's can also be adapted to create a new object prototype:

Object.prototype.extend = function(b){
for(var key in b)
if(b.hasOwnProperty(key))
this[key] = b[key];
return this;
}


var settings = default.extend(config);

You can use 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"}

It copies the values of all enumerable own properties from one or more source objects to a target object and returns the target object.

Object.assign(target, ...sources)

It works in all desktop browsers except IE (but including Edge). It has mitigated mobile support.

See for yourself here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

About deep copy

However, Object.assign does not have the deep option that jQuery's extend method have.

Note: you can generally use JSON for a similar effect though

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"}

This is my slightly different approach with deep copy I came up with while trying to eliminate a jQuery dependency. It is mostly designed for being small so it might have not all feature one expects. Should be fully ES5-compatible (starting from IE9 due to usage of Object.keys):

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;
}

You may wonder what the fifth line does exactly do ... If obj2.key is an object literal (i.e. if it's no ordinary type) we recursively call extend on it. If a property with that name doesn't exist in obj1 yet, we initialize it to an empty object first. Otherwise we simply set obj1.key to obj2.key.

Here are some of my mocha/chai tests that should prove the common cases to work here:

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',
});
});

I'd be happy about feedback or comments on this implementation. Thanks in advance!

I prefer this code that uses my generic forEachIn, and does not mangle the first object:

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;
}

If you really do want to merge stuff into the first object, you can do:

obj1 = extend(obj1, obj2);

You can use the ECMA 2018 spread operator in object literals...

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


var settings = {...default, ...config}


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

BabelJS support for older browsers