From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../global_objects/object/assign/index.html | 321 +++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 files/zh-cn/web/javascript/reference/global_objects/object/assign/index.html (limited to 'files/zh-cn/web/javascript/reference/global_objects/object/assign/index.html') diff --git a/files/zh-cn/web/javascript/reference/global_objects/object/assign/index.html b/files/zh-cn/web/javascript/reference/global_objects/object/assign/index.html new file mode 100644 index 0000000000..13cc1854d6 --- /dev/null +++ b/files/zh-cn/web/javascript/reference/global_objects/object/assign/index.html @@ -0,0 +1,321 @@ +--- +title: Object.assign() +slug: Web/JavaScript/Reference/Global_Objects/Object/assign +tags: + - ECMAScript 2015 + - JavaScript + - Method + - Object + - Object.assign + - polyfill +translation_of: Web/JavaScript/Reference/Global_Objects/Object/assign +--- +
{{JSRef}}
+ +

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

+ +
{{EmbedInteractiveExample("pages/js/object-assign.html")}}
+ + + +

语法

+ +
Object.assign(target, ...sources)
+ +

参数

+ +
+
target
+
目标对象。
+
sources
+
源对象。
+
+ +

返回值

+ +

目标对象。

+ +

描述

+ +

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

+ +

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用{{jsxref("Object.getOwnPropertyDescriptor()")}}和{{jsxref("Object.defineProperty()")}} 。

+ +

{{jsxref("String")}}类型和 {{jsxref("Symbol")}} 类型的属性都会被拷贝。

+ +

在出现错误的情况下,例如,如果属性不可写,会引发{{jsxref("TypeError")}},如果在引发错误之前添加了任何属性,则可以更改target对象。

+ +
+

注意,Object.assign 不会在那些source对象值为 {{jsxref("null")}} 或 {{jsxref("undefined")}} 的时候抛出错误。

+
+ +

Polyfill

+ +

这个 polyfill 不支持 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 || target === undefined) {
+        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 && nextSource !== undefined) {
+          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
+  });
+}
+ +

示例

+ +

复制一个对象

+ +
const obj = { a: 1 };
+const copy = Object.assign({}, obj);
+console.log(copy); // { a: 1 }
+
+ +

深拷贝问题

+ +

针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是(可枚举)属性值。

+ +

假如源值是一个对象的引用,它仅仅会复制其引用值。

+ +
const log = console.log;
+
+function test() {
+  'use strict';
+  let obj1 = { a: 0 , b: { c: 0}};
+  let obj2 = Object.assign({}, obj1);
+  log(JSON.stringify(obj2));
+  // { a: 0, b: { c: 0}}
+
+  obj1.a = 1;
+  log(JSON.stringify(obj1));
+  // { a: 1, b: { c: 0}}
+  log(JSON.stringify(obj2));
+  // { a: 0, b: { c: 0}}
+
+  obj2.a = 2;
+  log(JSON.stringify(obj1));
+  // { a: 1, b: { c: 0}}
+  log(JSON.stringify(obj2));
+  // { a: 2, b: { c: 0}}
+
+  obj2.b.c = 3;
+  log(JSON.stringify(obj1));
+  // { a: 1, b: { c: 3}}
+  log(JSON.stringify(obj2));
+  // { a: 2, b: { c: 3}}
+
+  // Deep Clone
+  obj1 = { a: 0 , b: { c: 0}};
+  let obj3 = JSON.parse(JSON.stringify(obj1));
+  obj1.a = 4;
+  obj1.b.c = 4;
+  log(JSON.stringify(obj3));
+  // { a: 0, b: { c: 0}}
+}
+
+test();
+
+ +

合并对象

+ +
const o1 = { a: 1 };
+const o2 = { b: 2 };
+const o3 = { c: 3 };
+
+const obj = Object.assign(o1, o2, o3);
+console.log(obj); // { a: 1, b: 2, c: 3 }
+console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
+
+ +

合并具有相同属性的对象

+ +
const o1 = { a: 1, b: 1, c: 1 };
+const o2 = { b: 2, c: 2 };
+const o3 = { c: 3 };
+
+const obj = Object.assign({}, o1, o2, o3);
+console.log(obj); // { a: 1, b: 2, c: 3 }
+ +

属性被后续参数中具有相同属性的其他对象覆盖。

+ +

拷贝 symbol 类型的属性

+ +
const o1 = { a: 1 };
+const o2 = { [Symbol('foo')]: 2 };
+
+const obj = Object.assign({}, o1, o2);
+console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
+Object.getOwnPropertySymbols(obj); // [Symbol(foo)]
+ +

继承属性和不可枚举属性是不能拷贝的

+ +
const obj = Object.create({foo: 1}, { // foo 是个继承属性。
+    bar: {
+        value: 2  // bar 是个不可枚举属性。
+    },
+    baz: {
+        value: 3,
+        enumerable: true  // baz 是个自身可枚举属性。
+    }
+});
+
+const copy = Object.assign({}, obj);
+console.log(copy); // { baz: 3 }
+
+ +

原始类型会被包装为对象

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

异常会打断后续拷贝任务

+ +
const target = Object.defineProperty({}, "foo", {
+    value: 1,
+    writable: false
+}); // target 的 foo 属性是个只读属性。
+
+Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
+// TypeError: "foo" is read-only
+// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
+
+console.log(target.bar);  // 2,说明第一个源对象拷贝成功了。
+console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
+console.log(target.foo);  // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
+console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
+console.log(target.baz);  // undefined,第三个源对象更是不会被拷贝到的。
+
+ +

拷贝访问器

+ +
const obj = {
+  foo: 1,
+  get bar() {
+    return 2;
+  }
+};
+
+let copy = Object.assign({}, obj);
+console.log(copy); // { foo: 1, bar: 2 } copy.bar的值来自obj.bar的getter函数的返回值
+
+// 下面这个函数会拷贝所有自有属性的属性描述符
+function completeAssign(target, ...sources) {
+  sources.forEach(source => {
+    let descriptors = Object.keys(source).reduce((descriptors, key) => {
+      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
+      return descriptors;
+    }, {});
+
+    // Object.assign 默认也会拷贝可枚举的Symbols
+    Object.getOwnPropertySymbols(source).forEach(sym => {
+      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
+      if (descriptor.enumerable) {
+        descriptors[sym] = descriptor;
+      }
+    });
+    Object.defineProperties(target, descriptors);
+  });
+  return target;
+}
+
+copy = completeAssign({}, obj);
+console.log(copy);
+// { foo:1, get bar() { return 2 } }
+
+ +

Polyfill

+ +

此{{Glossary("Polyfill","polyfill")}}不支持 symbol 属性,因为ES5 中根本没有 symbol :

+ +
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');
+      }
+
+      let 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 (let 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
+  });
+}
+ +

规范

+ + + + + + + + + + + + + + + + + + + +
规范名称规范状态备注
{{SpecName('ES6', '#sec-object.assign', 'Object.assign')}}{{Spec2('ES6')}}Initial definition.
{{SpecName('ESDraft', '#sec-object.assign', 'Object.assign')}}{{Spec2('ESDraft')}}
+ +

浏览器兼容

+ + + +

{{Compat("javascript.builtins.Object.assign")}}

+ +

相关链接

+ + -- cgit v1.2.3-54-g00ecf