Обновление: Спасибо за вашу поддержку. Недавно я бросил сводку данных для всех, чтобы прочитать систему. В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть
------ Далее идет текст ------
введение
В последней статье были представлены присваивание, поверхностное копирование и глубокое копирование, что дало много знаний о присваивании и поверхностном копировании, а также о различиях между ними.Из-за нехватки места была представлена только одна общая схема глубокого копирования.
В этой статье сначала будет представлено мелкое копирование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
свойства монтированияa
After перечислимо, поэтому здесь вы должны использовать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];
}
}
Вопросы для размышления по этому выпуску
如何实现一个深拷贝?