[Расширенная фаза 4-2] Принцип и реализация Object.assign

внешний интерфейс JavaScript

Обновление: Спасибо за вашу поддержку. Недавно я бросил сводку данных для всех, чтобы прочитать систему. В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть

------ Далее идет текст ------

введение

В последней статье были представлены присваивание, поверхностное копирование и глубокое копирование, что дало много знаний о присваивании и поверхностном копировании, а также о различиях между ними.Из-за нехватки места была представлена ​​только одна общая схема глубокого копирования.

В этой статье сначала будет представлено мелкое копированиеObject.assignПринцип реализации, а затем вам придется вручную реализовать мелкую копию, и оставить вопрос интервью в конце статьи, с нетерпением жду ваших комментариев.

мелкая копияObject.assign

В предыдущей статье было представлено его определение и использование, в основном путем объединения всехперечислимое свойствоЗначение копируется из одного или нескольких исходных объектов в целевой объект, и целевой объект возвращается. (из МДН)

Синтаксис следующий:

Object.assign(target, ...sources)

вtargetявляется целевым объектом,sourcesИсходный объект, их может быть несколько, возвращает измененный целевой объектtarget.

Если свойство в целевом объекте имеет тот же ключ, свойство будет перезаписано свойством в исходном объекте. Свойства более поздних исходных объектов аналогичным образом переопределяют более ранние свойства.

Пример 1

Мы знаем, что мелкая копия — это копирование первого слоя.базовое значение типа, и первый слойадрес ссылочного типа.

// 木易杨
// 第一步
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "muyiy",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign(a, b);
console.log(c);
// {
// 	name: "muyiy",
//  age: 18,
// 	book: {title: "You Don't Know JS", price: "45"}
// } 
console.log(a === c);
// true

// 第二步
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

// 第三步
console.log(a);
// {
// 	name: "muyiy",
//  age: 18,
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

1. На первом этапе используйтеObject.assignСкопируйте значение исходного объекта b в целевой объект a, где возвращаемое значение определено как объект c, видно, что b заменит значение тем же ключом в a, то есть, если атрибуты в целевом объекте объекта (a) имеют один и тот же ключ, свойство будет переопределено свойством в исходном объекте (b). Здесь следует отметить, что возвращаемый объект c является целевым объектом a.

2. На втором шаге измените значение базового типа (имя) и значение ссылочного типа (книга) исходного объекта b.

3. На третьем этапе значение базового типа целевого объекта a не изменилось после поверхностной копии, но изменилось значение ссылочного типа, посколькуObject.assign()Значение свойства копируется. Если значение свойства исходного объекта является ссылкой на объект, оно такжекопировать только эту ссылку.

Пример 2

Stringтип иSymbolсвойства типа копируются без пропуска тех, чьи значенияnullилиundefinedисходный объект.

// 木易杨
// 第一步
let a = {
    name: "muyiy",
    age: 18
}
let b = {
    b1: Symbol("muyiy"),
    b2: null,
    b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
// 	name: "muyiy",
//  age: 18,
// 	b1: Symbol(muyiy),
// 	b2: null,
// 	b3: undefined
// } 
console.log(a === c);
// true

Object.assignРеализация моделирования

реализоватьObject.assignОбщая идея такова:

1. Оценка оригиналаObjectПоддерживается ли функция, если нет, создайте функциюassignи использоватьObject.definePropertyпривязать функцию кObjectначальство.

2. Определить правильность параметров (целевой объект не может быть пустым, мы можем напрямую установить {} для передачи, но значение должно быть установлено).

3. ИспользуйтеObject()Преобразуйте его в объект, сохраните как и, наконец, верните объект в.

4. Используйтеfor..inПеребрать все перечисляемые собственные свойства. и скопируйте в новый целевой объект (используяhasOwnPropertyПолучить собственные свойства, т.е. свойства, которых нет в цепочке прототипов).

Код реализации следующий, здесь для удобства проверки используемassign2заменятьassign. Обратите внимание, что эта фиктивная реализация не поддерживаетsymbolсвойства, потому чтоES5нисколькоsymbol.

// 木易杨
if (typeof Object.assign2 != 'function') {
  // Attention 1
  Object.defineProperty(Object, "assign2", {
    value: function (target) {
      'use strict';
      if (target == null) { // Attention 2
        throw new TypeError('Cannot convert undefined or null to object');
      }

      // Attention 3
      var to = Object(target);
        
      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) {  // Attention 2
          // Attention 4
          for (var nextKey in nextSource) {
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

пройти тест

// 木易杨
// 测试用例
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "muyiy",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign2(a, b);
console.log(c);
// {
// 	name: "muyiy",
//  age: 18,
// 	book: {title: "You Don't Know JS", price: "45"}
// } 
console.log(a === c);
// true

Расширьте приведенный выше код следующим образом.

Примечание 1: перечислимость

Изначально установлен наObjectСвойства on не перечислимы, а находятся непосредственно вObjectсвойства монтированияaAfter перечислимо, поэтому здесь вы должны использоватьObject.defineProperty, и установитеenumerable: falseа такжеwritable: true, configurable: true.

// 木易杨
for(var i in Object) {
    console.log(Object[i]);
}
// 无输出

Object.keys( Object );
// []

Приведенный выше код показывает собственныйObjectСвойства on не перечислимы.

Мы можем использовать 2 метода, чтобы увидетьObject.assignЯвляется ли он перечислимым, используйтеObject.getOwnPropertyDescriptorилиObject.propertyIsEnumerableможно, из нихpropertyIsEnumerable(..)проверяет, существует ли данное имя свойства непосредственно в объекте (не в цепочке прототипов) и удовлетворяет ли оноenumerable: true. Конкретное использование заключается в следующем:

// 木易杨
// 方法1
Object.getOwnPropertyDescriptor(Object, "assign");
// {
// 	value: ƒ, 
//  writable: true, 	// 可写
//  enumerable: false,  // 不可枚举,注意这里是 false
//  configurable: true	// 可配置
// }

// 方法2
Object.propertyIsEnumerable("assign");
// false

Описание приведенного выше кодаObject.assignне перечислимо.

Вступлений так много, потому что непосредственно вObjectсвойства монтированияaПосле того, как он станет перечислимым, давайте посмотрим на следующий код.

// 木易杨
Object.a = function () {
    console.log("log a");
}

Object.getOwnPropertyDescriptor(Object, "a");
// {
// 	value: ƒ, 
//  writable: true, 
//  enumerable: true,  // 注意这里是 true
//  configurable: true
// }

Object.propertyIsEnumerable("a");
// true

Итак, чтобы достичьObject.assignдолжен использоватьObject.defineProperty, и установитеwritable: true, enumerable: false, configurable: true, конечно, по умолчанию он не установленfalse.

// 木易杨
Object.defineProperty(Object, "b", {
    value: function() {
        console.log("log b");
    }
});

Object.getOwnPropertyDescriptor(Object, "b");
// {
// 	value: ƒ, 
//  writable: false, 	// 注意这里是 false
//  enumerable: false,  // 注意这里是 false
//  configurable: false	// 注意这里是 false
// }

Таким образом, для этой реализации моделирования соответствующий код выглядит следующим образом.

// 木易杨
// 判断原生 Object 中是否存在函数 assign2
if (typeof Object.assign2 != 'function') {
  // 使用属性描述符定义新属性 assign2
  Object.defineProperty(Object, "assign2", {
    value: function (target) { 
      ...
    },
    // 默认值是 false,即 enumerable: false
    writable: true,
    configurable: true
  });
}

Примечание 2: Оценка правильности параметров

По некоторым статьям судят, правильные параметры или нет.

// 木易杨
if (target === undefined || target === null) {
	throw new TypeError('Cannot convert undefined or null to object');
}

Это конечно хорошо, но так писать не надо, т.к.undefinedиnullравны (возвышение 3 P52 ), т.е.undefined == nullвозвращениеtrue, просто судить об этом следующим образом.

// 木易杨
if (target == null) { // TypeError if undefined or null
	throw new TypeError('Cannot convert undefined or null to object');
}

Примечание 3. Примитивные типы упаковываются как объекты.

// 木易杨
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo");

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); 
// { "0": "a", "1": "b", "2": "c" }

Исходные объекты v2, v3, v4 в приведенном выше коде фактически игнорируются, поскольку они саминет перечислимых свойств.

// 木易杨
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo");
var v5 = null;

// Object.keys(..) 返回一个数组,包含所有可枚举属性
// 只会查找对象直接包含的属性,不查找[[Prototype]]链
Object.keys( v1 ); // [ '0', '1', '2' ]
Object.keys( v2 ); // []
Object.keys( v3 ); // []
Object.keys( v4 ); // []
Object.keys( v5 ); 
// TypeError: Cannot convert undefined or null to object

// Object.getOwnPropertyNames(..) 返回一个数组,包含所有属性,无论它们是否可枚举
// 只会查找对象直接包含的属性,不查找[[Prototype]]链
Object.getOwnPropertyNames( v1 ); // [ '0', '1', '2', 'length' ]
Object.getOwnPropertyNames( v2 ); // []
Object.getOwnPropertyNames( v3 ); // []
Object.getOwnPropertyNames( v4 ); // []
Object.getOwnPropertyNames( v5 ); 
// TypeError: Cannot convert undefined or null to object

Но приведенный ниже код является исполняемым.

// 木易杨
var a = "abc";
var b = {
    v1: "def",
    v2: true,
    v3: 10,
    v4: Symbol("foo"),
    v5: null,
    v6: undefined
}

var obj = Object.assign(a, b); 
console.log(obj);
// { 
//   [String: 'abc']
//   v1: 'def',
//   v2: true,
//   v3: 10,
//   v4: Symbol(foo),
//   v5: null,
//   v6: undefined 
// }

Причина проста, потому что в это времяundefined,trueи т.д. не как объекты, а как значения свойств объекта b, которые являются перечислимыми.

// 木易杨
// 接上面的代码
Object.keys( b ); // [ 'v1', 'v2', 'v3', 'v4', 'v5', 'v6' ]

На самом деле здесь видна другая проблема, то есть целевой объект является примитивным типом и будет упакован как объект Соответствующий код выше состоит в том, что целевой объект a будет упакован как[String: 'abc'], что нужно сделать, когда симуляция реализована? очень просто, используйтеObject(..)Вот и все.

// 木易杨
var a = "abc";
console.log( Object(a) );
// [String: 'abc']

Здесь было введено много знаний, давайте расширим их и посмотрим, можно ли выполнить следующий код.

// 木易杨
var a = "abc";
var b = "def";
Object.assign(a, b); 

Ответ отрицательный, будет выведена следующая ошибка.

// 木易杨
TypeError: Cannot assign to read only property '0' of object '[object String]'

причина в томObject("abc"), его дескриптор атрибута недоступен для записи, т.е.writable: false.

// 木易杨
var myObject = Object( "abc" );

Object.getOwnPropertyNames( myObject );
// [ '0', '1', '2', 'length' ]

Object.getOwnPropertyDescriptor(myObject, "0");
// { 
//   value: 'a',
//   writable: false, // 注意这里
//   enumerable: true,
//   configurable: false 
// }

Точно так же следующий код также сообщит об ошибке.

// 木易杨
var a = "abc";
var b = {
  0: "d"
};
Object.assign(a, b); 
// TypeError: Cannot assign to read only property '0' of object '[object String]'

Но это не значит, что толькоwritable: falseОн сообщит об ошибке, см. код ниже.

// 木易杨
var myObject = Object('abc'); 

Object.getOwnPropertyDescriptor(myObject, '0');
// { 
//   value: 'a',
//   writable: false, // 注意这里
//   enumerable: true,
//   configurable: false 
// }

myObject[0] = 'd';
// 'd'

myObject[0];
// 'a'

Здесь не сообщается об ошибке, потому что JS не удается автоматически изменить недоступное для записи значение свойства (сбой молчания), а сообщение об ошибке будет выведено только в строгом режиме.

// 木易杨
'use strict'
var myObject = Object('abc'); 

myObject[0] = 'd';
// TypeError: Cannot assign to read only property '0' of object '[object String]'

Итак, мы моделируем реализациюObject.assignНеобходим строгий режим.

Примечание 4: Существование

Как определить, существует ли свойство в объекте, не обращаясь к значению свойства? См. приведенный ниже код.

// 木易杨
var anotherObject = {
    a: 1
};

// 创建一个关联到 anotherObject 的对象
var myObject = Object.create( anotherObject );
myObject.b = 2;

("a" in myObject); // true
("b" in myObject); // true

myObject.hasOwnProperty( "a" ); // false
myObject.hasOwnProperty( "b" ); // true

используется здесьinоператор иhasOwnPropertyметод, разница заключается в следующем (вы не знаете объем JS P119):

1,inОператор проверяет, находится ли свойство в объекте и его[[Prototype]]в цепочке прототипов.

2,hasOwnProperty(..)только проверит, находится ли свойство вmyObjectобъект, не буду проверять[[Prototype]]Цепочка прототипов.

Object.assignМетод точно не будет копировать свойства в цепочке прототипов, поэтому нужно использоватьhasOwnProperty(..)Обработка суждения, но использование напрямуюmyObject.hasOwnProperty(..)проблематична, потому что некоторые объекты могут быть не подключены кObject.prototypeна (например, поObject.create(null)для создания), в этом случае используйтеmyObject.hasOwnProperty(..)не удастся.

// 木易杨
var myObject = Object.create( null );
myObject.b = 2;

("b" in myObject); 
// true

myObject.hasOwnProperty( "b" );
// TypeError: myObject.hasOwnProperty is not a function

Решение также очень простое, используйте методы, которые мы представили в [Расширенная фаза 3-3].callНа нем используйте следующим образом.

// 木易杨
var myObject = Object.create( null );
myObject.b = 2;

Object.prototype.hasOwnProperty.call(myObject, "b");
// true

Таким образом, для этой реализации моделирования соответствующий код выглядит следующим образом.

// 木易杨
// 使用 for..in 遍历对象 nextSource 获取属性值
// 此处会同时检查其原型链上的属性
for (var nextKey in nextSource) {
    // 使用 hasOwnProperty 判断对象 nextSource 中是否存在属性 nextKey
    // 过滤其原型链上的属性
    if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
        // 赋值给对象 to,并在遍历结束后返回对象 to
        to[nextKey] = nextSource[nextKey];
    }
}

Вопросы для размышления по этому выпуску

如何实现一个深拷贝?

Ссылаться на

Object.assign() из MDN

Серия ES2015 (2) Понимание Object.assign