Object.assign()‎

من موسوعة حسوب
مراجعة 05:41، 2 يناير 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

مصادر ومواصفات