mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
Implement Object.assign routine (#2678)
Related part of the standard: ECMA-262 v6, 19.1.2.1 JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu
This commit is contained in:
parent
52fdf4ef3f
commit
2c031c1220
@ -922,6 +922,126 @@ ecma_builtin_object_object_define_property (ecma_value_t this_arg, /**< 'this' a
|
||||
return ret_value;
|
||||
} /* ecma_builtin_object_object_define_property */
|
||||
|
||||
#ifndef CONFIG_DISABLE_ES2015_BUILTIN
|
||||
/**
|
||||
* The Object object's 'assign' routine
|
||||
*
|
||||
* See also:
|
||||
* ECMA-262 v6, 19.1.2.1
|
||||
*
|
||||
* @return ecma value
|
||||
* Returned value must be freed with ecma_free_value.
|
||||
*/
|
||||
static ecma_value_t
|
||||
ecma_builtin_object_object_assign (ecma_value_t this_arg, /**< 'this' argument */
|
||||
const ecma_value_t arguments_list_p[], /**< arguments list */
|
||||
ecma_length_t arguments_list_len) /**< number of arguments */
|
||||
{
|
||||
JERRY_UNUSED (this_arg);
|
||||
|
||||
ecma_value_t target = arguments_list_len > 0 ? arguments_list_p[0] : ECMA_VALUE_UNDEFINED;
|
||||
|
||||
/* 1. */
|
||||
ecma_value_t to_value = ecma_op_to_object (target);
|
||||
|
||||
if (ECMA_IS_VALUE_ERROR (to_value))
|
||||
{
|
||||
return to_value;
|
||||
}
|
||||
|
||||
ecma_object_t *to_obj_p = ecma_get_object_from_value (to_value);
|
||||
|
||||
/* 2. */
|
||||
if (arguments_list_len == 1)
|
||||
{
|
||||
return to_value;
|
||||
}
|
||||
|
||||
ecma_value_t ret_value = ECMA_VALUE_EMPTY;
|
||||
|
||||
/* 4-5. */
|
||||
for (uint32_t i = 1; i < arguments_list_len && ecma_is_value_empty (ret_value); i++)
|
||||
{
|
||||
ecma_value_t next_source = arguments_list_p[i];
|
||||
|
||||
/* 5.a */
|
||||
if (ecma_is_value_undefined (next_source) || ecma_is_value_null (next_source))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 5.b.i */
|
||||
ecma_value_t from_value = ecma_op_to_object (next_source);
|
||||
/* null and undefied cases are handled above, so this must be a valid object */
|
||||
JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (from_value));
|
||||
|
||||
ecma_object_t *from_obj_p = ecma_get_object_from_value (from_value);
|
||||
|
||||
/* 5.b.iii */
|
||||
/* TODO: extends this collection if symbols will be supported */
|
||||
ecma_collection_header_t *props_p = ecma_op_object_get_property_names (from_obj_p, ECMA_LIST_ENUMERABLE);
|
||||
|
||||
ecma_value_t *ecma_value_p = ecma_collection_iterator_init (props_p);
|
||||
|
||||
while (ecma_value_p != NULL && ecma_is_value_empty (ret_value))
|
||||
{
|
||||
ecma_string_t *property_name_p = ecma_get_string_from_value (*ecma_value_p);
|
||||
|
||||
/* 5.c.i-ii */
|
||||
ecma_property_descriptor_t prop_desc;
|
||||
|
||||
if (!ecma_op_object_get_own_property_descriptor (from_obj_p, property_name_p, &prop_desc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 5.c.iii */
|
||||
if (prop_desc.is_enumerable
|
||||
&& ((prop_desc.is_value_defined && !ecma_is_value_undefined (prop_desc.value))
|
||||
|| prop_desc.is_get_defined))
|
||||
{
|
||||
/* 5.c.iii.1 */
|
||||
ecma_value_t prop_value = ecma_op_object_get (from_obj_p, property_name_p);
|
||||
|
||||
/* 5.c.iii.2 */
|
||||
if (ECMA_IS_VALUE_ERROR (prop_value))
|
||||
{
|
||||
ret_value = prop_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 5.c.iii.3 */
|
||||
ecma_value_t status = ecma_op_object_put (to_obj_p, property_name_p, prop_value, true);
|
||||
|
||||
/* 5.c.iii.4 */
|
||||
if (ECMA_IS_VALUE_ERROR (status))
|
||||
{
|
||||
ret_value = status;
|
||||
}
|
||||
}
|
||||
|
||||
ecma_free_value (prop_value);
|
||||
ecma_free_property_descriptor (&prop_desc);
|
||||
}
|
||||
|
||||
ecma_value_p = ecma_collection_iterator_next (ecma_value_p);
|
||||
}
|
||||
|
||||
ecma_deref_object (from_obj_p);
|
||||
ecma_free_values_collection (props_p, 0);
|
||||
}
|
||||
|
||||
/* 6. */
|
||||
if (ecma_is_value_empty (ret_value))
|
||||
{
|
||||
return to_value;
|
||||
}
|
||||
|
||||
ecma_deref_object (to_obj_p);
|
||||
return ret_value;
|
||||
} /* ecma_builtin_object_object_assign */
|
||||
#endif /* !CONFIG_DISABLE_ES2015_BUILTIN */
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
|
||||
@ -53,6 +53,7 @@ ROUTINE (LIT_MAGIC_STRING_DEFINE_PROPERTY_UL, ecma_builtin_object_object_define_
|
||||
|
||||
#ifndef CONFIG_DISABLE_ES2015_BUILTIN
|
||||
ROUTINE (LIT_MAGIC_STRING_SET_PROTOTYPE_OF_UL, ecma_builtin_object_object_set_prototype_of, 2, 2)
|
||||
ROUTINE (LIT_MAGIC_STRING_ASSIGN, ecma_builtin_object_object_assign, NON_FIXED, 2)
|
||||
#endif /* !CONFIG_DISABLE_ES2015_BUILTIN */
|
||||
|
||||
#include "ecma-builtin-helpers-macro-undefs.inc.h"
|
||||
|
||||
@ -216,6 +216,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_NUMBER_UL, "Number")
|
||||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_OBJECT_UL, "Object")
|
||||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_REGEXP_UL, "RegExp")
|
||||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_STRING_UL, "String")
|
||||
#if !defined (CONFIG_DISABLE_ES2015_BUILTIN)
|
||||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ASSIGN, "assign")
|
||||
#endif
|
||||
#if !defined (CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN)
|
||||
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_BUFFER, "buffer")
|
||||
#endif
|
||||
|
||||
@ -110,6 +110,7 @@ LIT_MAGIC_STRING_NUMBER_UL = "Number"
|
||||
LIT_MAGIC_STRING_OBJECT_UL = "Object"
|
||||
LIT_MAGIC_STRING_REGEXP_UL = "RegExp"
|
||||
LIT_MAGIC_STRING_STRING_UL = "String"
|
||||
LIT_MAGIC_STRING_ASSIGN = "assign"
|
||||
LIT_MAGIC_STRING_BUFFER = "buffer"
|
||||
LIT_MAGIC_STRING_CALLEE = "callee"
|
||||
LIT_MAGIC_STRING_CALLER = "caller"
|
||||
|
||||
158
tests/jerry/es2015/object-assign.js
Normal file
158
tests/jerry/es2015/object-assign.js
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright JS Foundation and other contributors, http://js.foundation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
var object1 = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
};
|
||||
|
||||
var object2 = Object.assign ({c: 4, d: 5}, object1);
|
||||
|
||||
assert (JSON.stringify (object2) === '{"c":3,"d":5,"a":1,"b":2}');
|
||||
assert (object2.c === 3);
|
||||
assert (object2.d === 5);
|
||||
|
||||
// Cloning an object
|
||||
var obj = { a: 1 };
|
||||
var copy = Object.assign ({}, obj);
|
||||
assert (JSON.stringify (copy) === '{"a":1}'); // { a: 1 }
|
||||
|
||||
// Warning for Deep Clone
|
||||
function deepClone() {
|
||||
'use strict';
|
||||
|
||||
var obj1 = { a: 0 , b: { c: 0}};
|
||||
var obj2 = Object.assign ({}, obj1);
|
||||
assert (JSON.stringify (obj2) === '{"a":0,"b":{"c":0}}');
|
||||
|
||||
obj1.a = 1;
|
||||
assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":0}}');
|
||||
assert (JSON.stringify (obj2) === '{"a":0,"b":{"c":0}}');
|
||||
|
||||
obj2.a = 2;
|
||||
assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":0}}');
|
||||
assert (JSON.stringify (obj2) === '{"a":2,"b":{"c":0}}');
|
||||
|
||||
obj2.b.c = 3;
|
||||
assert (JSON.stringify (obj1) === '{"a":1,"b":{"c":3}}');
|
||||
assert (JSON.stringify (obj2) === '{"a":2,"b":{"c":3}}');
|
||||
|
||||
// Deep Clone
|
||||
obj1 = { a: 0 , b: { c: 0}};
|
||||
var obj3 = JSON.parse (JSON.stringify (obj1));
|
||||
obj1.a = 4;
|
||||
obj1.b.c = 4;
|
||||
assert (JSON.stringify (obj3) === '{"a":0,"b":{"c":0}}');
|
||||
}
|
||||
|
||||
deepClone();
|
||||
|
||||
// Merging objects
|
||||
var o1 = { a: 1 };
|
||||
var o2 = { b: 2 };
|
||||
var o3 = { c: 3 };
|
||||
|
||||
var obj = Object.assign (o1, o2, o3);
|
||||
assert (JSON.stringify (obj) === '{"a":1,"b":2,"c":3}');
|
||||
assert (JSON.stringify (o1) === '{"a":1,"b":2,"c":3}'); //target object itself is changed.
|
||||
|
||||
//Merging objects with same properties
|
||||
var o1 = { a: 1, b: 1, c: 1 };
|
||||
var o2 = { b: 2, c: 2 };
|
||||
var o3 = { c: 3 };
|
||||
|
||||
var obj = Object.assign ({}, o1, o2, o3);
|
||||
assert (JSON.stringify (obj) === '{"a":1,"b":2,"c":3}');
|
||||
|
||||
// Properties on the prototype chain and non-enumerable properties cannot be copied
|
||||
var obj = Object.create({ foo: 1 }, { // foo is on obj's prototype chain.
|
||||
bar: {
|
||||
value: 2 // bar is a non-enumerable property.
|
||||
},
|
||||
baz: {
|
||||
value: 3,
|
||||
enumerable: true // baz is an own enumerable property.
|
||||
}
|
||||
});
|
||||
|
||||
var copy = Object.assign ({}, obj);
|
||||
assert (JSON.stringify (copy) === '{"baz":3}');
|
||||
|
||||
// Primitives will be wrapped to objects
|
||||
var v1 = 'abc';
|
||||
var v2 = true;
|
||||
var v3 = 10;
|
||||
|
||||
var obj = Object.assign ({}, v1, null, v2, undefined, v3);
|
||||
// Primitives will be wrapped, null and undefined will be ignored.
|
||||
// Note, only string wrappers can have own enumerable properties.
|
||||
assert (JSON.stringify (obj) === '{"0":"a","1":"b","2":"c"}');
|
||||
|
||||
//Exceptions will interrupt the ongoing copying task
|
||||
var target = Object.defineProperty ({}, 'foo', {
|
||||
value: 1,
|
||||
writable: false
|
||||
}); // target.foo is a read-only property
|
||||
|
||||
try {
|
||||
// TypeError: "foo" is read-only,the Exception is thrown when assigning target.foo
|
||||
Object.assign (target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
assert (target.bar === 2); // the first source was copied successfully.
|
||||
assert (target.foo2 === 3); // the first property of the second source was copied successfully.
|
||||
assert (target.foo === 1); // exception is thrown here.
|
||||
assert (target.foo3 === undefined); // assign method has finished, foo3 will not be copied.
|
||||
assert (target.baz === undefined); // the third source will not be copied either.
|
||||
|
||||
// Copying accessors
|
||||
var obj = {
|
||||
foo: 1,
|
||||
get bar() {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
var copy = Object.assign ({}, obj);
|
||||
assert (JSON.stringify (copy) === '{"foo":1,"bar":2}');
|
||||
assert (copy.bar === 2); // the value of copy.bar is obj.bar's getter's return value.
|
||||
|
||||
// This is an assign function that copies full descriptors
|
||||
function completeAssign (target, sources) {
|
||||
sources.forEach (source => {
|
||||
var descriptors = Object.keys (source).reduce ((descriptors, key) => {
|
||||
descriptors[key] = Object.getOwnPropertyDescriptor (source, key);
|
||||
return descriptors;
|
||||
}, {});
|
||||
|
||||
Object.defineProperties (target, descriptors);
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
var copy = completeAssign ({}, [obj]);
|
||||
assert (JSON.stringify (copy) === '{"foo":1}');
|
||||
assert (copy.bar === 2);
|
||||
|
||||
// Test when target is not coercible to object
|
||||
try {
|
||||
Object.assign.call (undefined);
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user