diff --git a/README.md b/README.md index fe3d625..ad56d63 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install Usage ----- - var deepExtend = require('deep-extend'); + var deepExtend = require('deep-extend')(); var obj1 = { a: 1, b: 2, @@ -56,3 +56,44 @@ Usage e: { one: 1, two: 2 }, h: /abc/g } */ + +Custom Clone Logic +------------------ + +The deep-extend module will by default extend all types of objects. But +if you clone a custom object it will no longer be an instance of its +original class. If this is important to you, you can supply a custom cloning +function which you can use to implement your own cloning logic. + +It will be called for every value in the object. If you return a falsy +value the default behavior of deep-extend will be used. If you return a +truthy value that value will be used as a replacement for the original +value. + + var Foo = function(val) { + this.foo = val; + }; + var Bar = function(val) { + this.bar = val; + }; + + var cloner = function (obj) { + // if given an instance of Foo, return a clone of it + if (obj instanceof Foo) return new Foo(obj.foo); + }; + + var deepExtend = require('deep-extend')(cloner); + var obj = { + a: new Foo(1), + b: new Bar(2) + }; + + var clone = deepExtend({}, obj); + + console.log(clone); + /* + { a: { foo: 1 }, + b: { bar: 2 } } + */ + console.log(clone.a instanceof Foo); // true + console.log(clone.b instanceof Bar); // false diff --git a/index.js b/index.js index d5660c3..b0db4c5 100644 --- a/index.js +++ b/index.js @@ -32,62 +32,73 @@ * If you wish to clone object, simply use that: * deepExtend({}, yourObj_1, [yourObj_N]) - first arg is new empty object */ -var deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) { - if (arguments.length < 1 || typeof arguments[0] !== 'object') { - return false; - } - if (arguments.length < 2) return arguments[0]; +var noop = function () {}; - var target = arguments[0]; +module.exports = function (cloner) { + if (!cloner) cloner = noop; - // convert arguments to array and cut off target object - var args = Array.prototype.slice.call(arguments, 1); + return function deepExtend(/*obj_1, [obj_2], [obj_N]*/) { + if (arguments.length < 1 || typeof arguments[0] !== 'object') { + return false; + } - var key, val, src, clone, tmpBuf; + if (arguments.length < 2) return arguments[0]; - args.forEach(function (obj) { - if (typeof obj !== 'object') return; + var target = arguments[0]; - for (key in obj) { - if ( ! (key in obj)) continue; + // convert arguments to array and cut off target object + var args = Array.prototype.slice.call(arguments, 1); - src = target[key]; - val = obj[key]; + var key, val, src, clone, tmpBuf, custom; - if (val === target) continue; + args.forEach(function (obj) { + if (typeof obj !== 'object') return; - if (typeof val !== 'object' || val === null) { - target[key] = val; - continue; - } else if (val instanceof Buffer) { - tmpBuf = new Buffer(val.length); - val.copy(tmpBuf); - target[key] = tmpBuf; - continue; - } else if (val instanceof Date) { - target[key] = new Date(val.getTime()); - continue; - } else if (val instanceof RegExp) { - target[key] = new RegExp(val); - continue; - } + for (key in obj) { + if ( ! (key in obj)) continue; - if (typeof src !== 'object' || src === null) { - clone = (Array.isArray(val)) ? [] : {}; - target[key] = deepExtend(clone, val); - continue; - } + src = target[key]; + val = obj[key]; + + if (val === target) continue; - if (Array.isArray(val)) { - clone = (Array.isArray(src)) ? src : []; - } else { - clone = (!Array.isArray(src)) ? src : {}; + if (custom = cloner(val)) { + target[key] = custom; + continue; + } else if (typeof val !== 'object' || val === null) { + target[key] = val; + continue; + } else if (val instanceof Buffer) { + tmpBuf = new Buffer(val.length); + val.copy(tmpBuf); + target[key] = tmpBuf; + continue; + } else if (val instanceof Date) { + target[key] = new Date(val.getTime()); + continue; + } else if (val instanceof RegExp) { + target[key] = new RegExp(val); + continue; + } + + if (typeof src !== 'object' || src === null) { + clone = (Array.isArray(val)) ? [] : {}; + target[key] = deepExtend(clone, val); + continue; + } + + if (Array.isArray(val)) { + clone = (Array.isArray(src)) ? src : []; + } else { + clone = (!Array.isArray(src)) ? src : {}; + } + + target[key] = deepExtend(clone, val); } + }); - target[key] = deepExtend(clone, val); - } - }); + return target; + } +}; - return target; -} diff --git a/test/cloner.spec.js b/test/cloner.spec.js new file mode 100644 index 0000000..cae13fd --- /dev/null +++ b/test/cloner.spec.js @@ -0,0 +1,32 @@ +var should = require('should'); +var Extend = require('../index'); + +var Foo = function(val) { + this.foo = val; +}; +var Bar = function(val) { + this.bar = val; +}; +var cloner = function(obj) { + if (obj instanceof Foo) return new Foo(obj.foo); +}; + +var extend = Extend(cloner); + +describe('deep-extend with custom cloner', function() { + + it('can extend on 1 level', function() { + var obj = { + a: new Foo(1), + b: new Bar(2) + }; + var clone = extend({}, obj); + clone.should.eql({ + a: { foo: 1 }, + b: { bar: 2 } + }); + clone.a.should.be.an.instanceof(Foo); + clone.b.should.not.be.an.instanceof(Bar); + }); + +}); diff --git a/test/index.spec.js b/test/index.spec.js index 069d17f..be3f118 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,5 +1,5 @@ var should = require('should'); -var extend = require('../index'); +var extend = require('../index')(); describe('deep-extend', function() {