From 05e3ea0a938fa048c2827869818dd085910a5026 Mon Sep 17 00:00:00 2001 From: Julien Sanchez Date: Tue, 1 Jul 2014 16:27:36 +0200 Subject: [PATCH] fix(copy): preserve property descriptors Instead of values being simply copied, this change allows to preserve definitions of own properties. Thus, enumerable (or not), writable (or not), getter/setter, etc. property definitions are preserved across copies. It improves change from b59b04f9 preserving prototype chain. As a side effect, it also includes previously ignored non-enumerable properties by using `Object.getOwnPropertyNames` to iterate **every** own properties instead of `for ... in ...` + `hasOwnProperty`. --- src/Angular.js | 18 +++++++++------- test/AngularSpec.js | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index b84b156f72e4..e3d010650e86 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -807,16 +807,18 @@ function copy(source, destination, stackSource, stackDest) { forEach(destination, function(value, key) { delete destination[key]; }); - for ( var key in source) { - if(source.hasOwnProperty(key)) { - result = copy(source[key], null, stackSource, stackDest); - if (isObject(source[key])) { - stackSource.push(source[key]); - stackDest.push(result); + forEach (Object.getOwnPropertyNames(source), function(key) { + var propertyDescriptor = Object.getOwnPropertyDescriptor(source, key); + if (propertyDescriptor.hasOwnProperty('value')) { + var sourceValue = propertyDescriptor.value; + propertyDescriptor.value = copy(sourceValue, null, stackSource, stackDest); + if (isObject(sourceValue)) { + stackSource.push(sourceValue); + stackDest.push(propertyDescriptor.value); } - destination[key] = result; } - } + Object.defineProperty(destination, key, propertyDescriptor); + }); setHashKey(destination,h); } diff --git a/test/AngularSpec.js b/test/AngularSpec.js index fea74f81212a..a329b47d8272 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -34,6 +34,57 @@ describe('angular', function() { expect(copy(new Foo()) instanceof Foo).toBe(true); }); + it("should copy own non-enumerable properties", function() { + var obj = { + a: "enumerable" + }; + Object.defineProperty(obj, 'b', { + value: "nonEnumerable", + enumerable: false + }); + expect(copy(obj).a).toEqual("enumerable"); + expect(copy(obj).b).toEqual("nonEnumerable"); + }); + + it("should preserve own property descriptors", function() { + var obj = {}; + var aDescriptor = { + value : {foo: 'a'}, + writable : true, + enumerable : true, + configurable : true + }; + var bDescriptor = { + value : {foo: 'b'}, + writable : false, + enumerable : false, + configurable : false + }; + var cDescriptor = { + get : function() { return this._c; }, + set : function(newValue) { this._c = newValue; }, + enumerable : false, + configurable : false + }; + Object.defineProperties(obj, { + a: aDescriptor, + b: bDescriptor, + c: cDescriptor + }); + obj.c = {foo: 'b'}; + + var objCopy = copy(obj); + expect(Object.getOwnPropertyDescriptor(objCopy, "a")).toEqual(aDescriptor); + expect(Object.getOwnPropertyDescriptor(objCopy, "b")).toEqual(bDescriptor); + expect(Object.getOwnPropertyDescriptor(objCopy, "c")).toEqual(cDescriptor); + expect(objCopy.a).toEqual(obj.a); + expect(objCopy.a === obj.a).toBe(false); + expect(objCopy.b).toEqual(obj.b); + expect(objCopy.b === obj.b).toBe(false); + expect(objCopy.c).toEqual(obj.c); + expect(objCopy.c === obj.c).toBe(false); + }); + it("should copy Date", function() { var date = new Date(123); expect(copy(date) instanceof Date).toBeTruthy();