الفرق بين المراجعتين لصفحة: «JavaScript/Object/assign»
لا ملخص تعديل |
ط استبدال النص - '\[\[تصنيف:(.*)\]\]' ب'{{SUBPAGENAME}}' |
||
سطر 231: | سطر 231: | ||
* مسودة المعيار [https://tc39.github.io/ecma262/#sec-object.assign ECMAScript Latest Draft]. | * مسودة المعيار [https://tc39.github.io/ecma262/#sec-object.assign ECMAScript Latest Draft]. | ||
* معيار [http://www.ecma-international.org/ecma-262/6.0/#sec-object.assign ECMAScript 2015 (6th Edition)]. | * معيار [http://www.ecma-international.org/ecma-262/6.0/#sec-object.assign ECMAScript 2015 (6th Edition)]. | ||
[[تصنيف:JavaScript]] | [[تصنيف:JavaScript|{{SUBPAGENAME}}]] | ||
[[تصنيف:JavaScript Global Objects]] | [[تصنيف:JavaScript Global Objects|{{SUBPAGENAME}}]] | ||
[[تصنيف:JavaScript Object]] | [[تصنيف:JavaScript Object|{{SUBPAGENAME}}]] |
المراجعة الحالية بتاريخ 15:44، 28 يناير 2018
الدالة Object.assign()
تُستخدَم لنسخ قيمة جميع الخاصيات القابلة للإحصاء التابعة للكائن مباشرة (enumerable own properties) من كائنٍ مصدريٍ (source object) أو أكثر، وستُعيد الكائن الهدف (target object).
البنية العامة
Object.assign(target, ...sources)
target
الكائن الهدف.
sources
الكائنات المصدرية.
القيمة المعادة
الكائن الهدف.
الوصف
سيستعاض عن الخاصيات في الكائن الهدف بالخاصيات الموجودة في الكائنات المصدرية إن كانت تحمل المفتاح نفسه؛ وستُستخدَم قيمة الخاصيات الموجودة الكائنات المصدرية الأخيرة إذا وجدت خاصيات لها نفس المفتاح في الكائنات المصدرية الأولى.
لا تنسخ الدالة Object.assign()
إلا الخاصيات القابلة للإحصاء والتي يملكها الكائن (enumerable own properties) من الكائن المصدري إلى الكائن الهدف، وستستخدم [[Get]]
على المصدر و [[Set]]
على الهدف، وبالتالي ستستدعى دوال setter و getter، وبالتالي ستُسنِد الخاصيات (assign) ولن تنسخها أو تُنشِئ خاصيات جديدة، وقد يكون ذلك غير مناسبٍ لغرض دمج الخاصيات الجديدة في سلسلة prototype إذا كان الكائنات المصدرية تحتوي على دوال getter؛ فلنسخ تعريف الخاصيات إلى خاصية prototype
فيجب استخدام الدالتين Object.getOwnPropertyDescriptor()
و Object.defineProperty()
بدلًا منها.
لاحظ أنَّ الخاصيات ذات المفاتيح من النوع String
و Symbol
ستُنسَخ إلى الكائن الهدف عند استخدام هذه الدالة.
في حال حدوث خطأ (مثلًا: إذا كانت الخاصية غير قابلة للكتابة) فسيرمى الخطأ TypeError
، وسيتغيّر الكائن الهدف target
إذا أُضيفَت أيّة خاصيات قبل وقوع الخطأ.
لاحظ أنَّ الدالة Object.assign()
لن ترمي خطأً إذا كانت إحدى القيم المصدرية هي null
أو undefined
.
أمثلة
إنشاء نسخة من كائن
var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
النسخ العميق للكائنات
إذا أردنا نسخ الخاصيات نسخًا عميقًا (deep clone)، فيجب علينا استخدام بدائل أخرى لأنَّ الدالة Object.assign()
ستنسخ قيم الكائنات فقط، ولو كانت القيمة هي مرجعية إلى كائنٍ آخر، فلن تُنسَخ إلا المرجعية إلى ذاك الكائن.
function test() {
'use strict';
let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}}
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}
// نسخ عميق
obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
}
test();
دمج الكائنات
لاحظ أنَّ الكائن الهدف سيتغيّر في المثال الآتي:
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 } هذا هو الكائن الهدف
دمج الكائنات التي تملك نفس الخاصيات
إذا كانت هنالك خاصيةٌ موجودةٌ في أكثر من كائن، فستستخدم قيمة آخر نسخة منها بين الكائنات المصدرية؛ فلاحظ كيف ستكون قيمة الخاصية b
تساوي 2
في المثال الآتي، وهي آتيةٌ من الكائن o2
، بينما قيمة الخاصية c
ستساوي 3
لأنها موجودة في الكائن o3
:
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);
console.log(obj); // { a: 1, b: 2, c: 3 }
نسخ الخاصيات غير القابلة للإحصاء أو الموجودة في سلسلة prototype
سنستخدم في المثال الآتي الدالة Object.create()
لإنشاء الكائن obj
مع تحديد الكائن الذي يملك الخاصية foo
كقيمة للخاصية prototype، وسنُعرِّف الخاصية bar
على أنها خاصية غير قابلة للإحصاء (non-enumerable)، بينما الخاصية baz
هي خاصيةٌ تابعةٌ للكائن obj
وقابلةٌ للإحصاء؛ لاحظ ما هي الخاصيات التي سيمتلكها الكائن copy
المنسوخ من الكائن obj
:
var obj = Object.create({ foo: 1 }, { // هذه الخاصية موجودة في سلسلة prototype
bar: {
value: 2 // هذه الخاصية غير قابلة للإحصاء
},
baz: {
value: 3,
enumerable: true // هذه الخاصية تابعة للكائن وقابلة للإحصاء
}
});
var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }
ستحوّل القيم الأولية إلى كائنات
لاحظ كيف سيتم تجاهل القيمتين null
و undefined
، ولن توجد خاصيات قابلة للإحصاء إلا لكائنات السلاسل النصية:
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
ستقاطع الاستثناءات عملية النسخ
سنُعرِّف في المثال الآتي الخاصية foo
للكائن target
عبر الدالة Object.defineProperty()
لجعلها قابلةً للقراءة فقط، ثم سنحاول نسخ عدِّة كائنات باستخدام الدالة Object.assign()
؛ وأحد الكائنات المصدرية فيه خاصية باسم foo
، وسيرمى الخطأ TypeError
عند محاولة نسخ قيمتها إلى الكائن الهدف:
var target = Object.defineProperty({}, 'foo', {
value: 1,
writable: false
}); // هذه الخاصية غير قابلة للكتابة
Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
// TypeError: "foo" is read-only
// سيرمى الاستثناء عند محاولة إسناد الخاصية target.foo
console.log(target.bar); // 2, نُسِخَ أوّل كائن مصدري بنجاح
console.log(target.foo2); // 3, أول خاصية من خاصيات الكائن الثاني نُسِخَت بنجاح
console.log(target.foo); // 1, رُمِيَ الاستثناء هنا
console.log(target.foo3); // undefined, توقفت عملية النسخ
console.log(target.baz); // undefined, توقفت عملية النسخ
نسخ دوال getter
سنُعرِّف دالة getter في الكائن obj
، ثم سننسخه عبر الدالة Object.assign()
التي ستأخذ ناتج دالة getter وتضعه في الكائن الهدف:
var obj = {
foo: 1,
get bar() {
return 2;
}
};
var copy = Object.assign({}, obj);
console.log(copy);
// { foo: 1, bar: 2 }
هذه دالة ستنسخ دوال getter و setter كما هي:
function completeAssign(target, ...sources) {
sources.forEach(source => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
Object.getOwnPropertySymbols(source).forEach(sym => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
var copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }
تعويض نقص دعم المتصفحات
الشيفرة الآتية تُعوِّض نقص دعم المتصفحات للدالة Object.assign()
، لاحظ أنَّ هذه الشيفرة لا تدعم الخاصيات المرتبطة مع مفاتيح من النوع Symbol
، لكن معيار ES5 ليس فيه رموز (symbols) من الأساس:
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
دعم المتصفحات
الميزة | Chrome | Firefox | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
الدعم الأساسي | 45 | 34 | غير مدعومة | 32 | 9 |
مصادر ومواصفات
- مسودة المعيار ECMAScript Latest Draft.
- معيار ECMAScript 2015 (6th Edition).