diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index ca995defcc..cecb3d9b39 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -1,13 +1,13 @@ -可以使用 `Object.keys` 列出所有可枚举键值,然后输出。 +可以使用 `Object.keys` 获取所有可枚举的键,并输出其列表。 -为了使 `toString` 不可枚举,我们使用属性描述器来定义它。`Object.create` 语法允许我们为一个对象提供属性描述器作为第二参数。 +为了使 `toString` 不可枚举,我们使用一个属性描述器来定义它。`Object.create` 语法允许我们为一个对象提供属性描述器作为第二参数。 ```js run *!* let dictionary = Object.create(null, { -  toString: { // 定义 toString 方法 -    value() { // value 是一个函数 + toString: { // 定义 toString 属性 + value() { // value 是一个 function return Object.keys(this).join(); } } @@ -17,13 +17,15 @@ let dictionary = Object.create(null, { dictionary.apple = "Apple"; dictionary.__proto__ = "test"; -// apple 和 __proto__ 在循环内 +// apple 和 __proto__ 在循环中 for(let key in dictionary) { -  alert(key); // "apple",然后 "__proto__" + alert(key); // "apple",然后是 "__proto__" } -// 通过 toString 得到逗号分隔的属性值 -alert(dictionary.toString()); // "apple,__proto__" +// 通过 toString 处理获得的以逗号分隔的属性列表 +alert(dictionary); // "apple,__proto__" ``` -当我们使用描述器创建一个属性,它的标识默认是 `false`。因此在以上代码中,`dictonary.toString` 是不可枚举的。 +当我们使用描述器创建一个属性,它的标识默认是 `false`。因此在上面这段代码中,`dictonary.toString` 是不可枚举的。 + +请阅读 [](info:property-descriptors) 一章进行回顾。 diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md index dd8b6cc440..79bb5cc40b 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md @@ -2,30 +2,30 @@ importance: 5 --- -# 给字典对象添加 toString 方法 +# 为 dictionary 添加 toString 方法 -有一个对象 `dictionary`,通过 `Object.create(null)` 创建,用来存储任意键值对。 +这儿有一个通过 `Object.create(null)` 创建的,用来存储任意 `key/value` 对的对象 `dictionary`。 -为该对象添加方法 `dictionary.toString()`,返回所有键的列表,用逗号隔开。你的 `toString` 方法不能对该对象使用 `for...in`。 +为该对象添加 `dictionary.toString()` 方法,该方法应该返回以逗号分隔的所有键的列表。你的 `toString` 方法不应该在使用 `for...in` 循环遍历数组的时候显现出来。 -以下是它的运行例子: +它的工作方式如下: ```js let dictionary = Object.create(null); *!* -// 添加 dictionary.toString 方法的代码 +// 你的添加 dictionary.toString 方法的代码 */!* // 添加一些数据 dictionary.apple = "Apple"; -dictionary.__proto__ = "test"; // __proto__ 在这里是正常参数 +dictionary.__proto__ = "test"; // 这里 __proto__ 是一个常规的属性键 -// 只有 apple 和 __proto__ 在循环内 +// 在循环中只有 apple 和 __proto__ for(let key in dictionary) { -  alert(key); // "apple",然后 "__proto__" + alert(key); // "apple", then "__proto__" } -// your toString in action +// 你的 toString 方法在发挥作用 alert(dictionary); // "apple,__proto__" ``` diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md index 546ddb722d..a06cc060c8 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md @@ -1,7 +1,7 @@ -第一个调用中 `this == rabbit`,其他的 `this` 等同于 `Rabbit.prototype`,因为它是逗号之前的对象。 +第一个调用中 `this == rabbit`,其他的 `this` 等同于 `Rabbit.prototype`,因为 `this` 就点符号前面的对象。 -因此只有第一个调用显示 `Rabbit`,其他的都是 `undefined`: +所以,只有第一个调用显示 `Rabbit`,其他的都显示的是 `undefined`: ```js run function Rabbit(name) { diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md index 796952dfa9..b400e35da8 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md @@ -17,7 +17,7 @@ Rabbit.prototype.sayHi = function() { let rabbit = new Rabbit("Rabbit"); ``` -以下调用得到的结果是否相同? +以下调用做的是相同的事儿还是不同的? ```js rabbit.sayHi(); diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index ff06c39163..96749d73ea 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -1,37 +1,42 @@ -# 原型方法 +# 原型方法,没有 __proto__ 的对象 -本章节我们会讨论原型(prototype)的附加方法。 +在这部分内容的第一章中,我们提到了设置原型的现代方法。 -获取/设置原型的方式有很多,我们已知的有: +`__proto__` 被认为是过时且不推荐使用的(deprecated),这里的不推荐使用是指 JavaScript 规范中规定,__proto__ 必须仅在浏览器环境下才能得到支持。 -- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用 `proto` 作为 `[[Prototype]]` 和可选的属性描述来创建一个空对象。 -- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) —— 返回 `obj` 对象的 `[[Prototype]]`。 -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) —— 将 `obj` 对象的 `[[Prototype]]` 设置为 `proto`。 +现代的方法有: -举个例子: +- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用给定的 `proto` 作为 `[[Prototype]]` 和可选的属性描述来创建一个空对象。 +- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) —— 返回对象 `obj` 的 `[[Prototype]]`。 +- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) —— 将对象 `obj` 的 `[[Prototype]]` 设置为 `proto`。 + +应该使用这些方法来代替 `__proto__`。 + +例如: ```js run let animal = { eats: true }; -// 以 animal 为原型创建一个新对象 +// 创建一个以 animal 为原型的新对象 *!* let rabbit = Object.create(animal); */!* alert(rabbit.eats); // true + *!* -alert(Object.getPrototypeOf(rabbit) === animal); // 获取 rabbit 的原型 +alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* -Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型更改为 {} +Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型修改为 {} */!* ``` -`Object.create` 有一个可选的第二参数:属性描述。我们可以给新对象提供额外的属性,就像这样: +`Object.create` 有一个可选的第二参数:属性描述器。我们可以在此处为新对象提供额外的属性,就像这样: ```js run let animal = { @@ -47,38 +52,44 @@ let rabbit = Object.create(animal, { alert(rabbit.jumps); // true ``` -参数的格式同 章节中讨论的相同。 +描述器的格式与 一章中所讲的一样。 -我们可以利用 `Object.create` 来实现比 `for..in` 循环赋值属性方式更强大的对象复制功能: +我们可以使用 `Object.create` 来实现比复制 `for..in` 循环中的属性更强大的对象克隆方式: ```js -// obj 对象的浅复制 +// 完全相同的对象 obj 的浅拷贝 let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -这样实现了 `obj` 的完整复制,包含了所有属性:可枚举的和不可枚举的,数据属性以及 setters/getters —— 所有属性,以及正确的 `[[Prototype]]`。 +此调用可以对 `obj` 进行真正准确地拷贝,包括所有的属性:可枚举和不可枚举的,数据属性和 setters/getters —— 包括所有内容,并带有正确的 `[[Prototype]]`。 # 原型简史 -如果我们计算有多少种方式来管理 `[[Prototype]]`,答案是很多!很多种方式! +如果我们数一下有多少种处理 `[[Prototype]]` 的方式,答案是有很多!很多种方法做的都是同一件事儿! -为什么会出现这样的情况? +为什么会出现这种情况? -这里有历史遗留问题。 +这是历史原因。 -- 在上古时代 `prototype` 作为一个构造函数的属性来运行。 -- 之后在 2012 年: `Object.create` 出现在标准中。它允许利用给定的原型来创建对象,但是不能 get/set 原型。因此许多浏览器厂商实现了非标准属性 `__proto__`,允许任何时候 get/set 原型。 -- 之后在 2015 年: `Object.setPrototypeOf` 和 `Object.getPrototypeOf` 被加入到标准中。 `__proto__` 在几乎所有地方都得到实现,因此它成了标准以外的替代方案 B,在非浏览器环境下,它的支持性是不确定的,可选的。 +- 构造函数的 `"prototype"` 属性自古以来就起作用。 +- 之后,在 2012 年,`Object.create` 出现在标准中。它提供了使用给定原型创建对象的能力。但没有提供 get/set 它的能力。因此,许多浏览器厂商实现了非标准的 `__proto__` 访问器,该访问器允许用户随时 get/set 原型。 +- 之后,在 2015 年,`Object.setPrototypeOf` 和 `Object.getPrototypeOf` 被加入到标准中,执行与 `__proto__` 相同的功能。由于 `__proto__` 实际上已经在所有地方都得到了实现,但它已过时,所以被加入到该标准的附件 B 中,即:在非浏览器环境下,它的支持是可选的。 目前为止,我们拥有了所有这些方式。 -从技术上来讲,我们可以在任何时候 get/set `[[Prototype]]`。但是通常我们只在创建对象的时候设置它一次,自那之后不再修改:`rabbit` 继承自 `animal`,之后不再改变。对此 JavaScript 引擎做了高度的优化。运行中利用 `Object.setPrototypeOf` 或者 `obj.__proto__=` 来更改 prototype 是一个非常缓慢的操作。但是,这是可行的。 +为什么将 `__proto__` 替换成函数 `getPrototypeOf/setPrototypeOf`?这是一个有趣的问题,需要我们理解为什么 `__proto__` 不好。继续阅读,你就会知道答案。 + +```warn header="如果速度很重要,就请不要修改已存在的对象的 `[[Prototype]]`" +从技术上来讲,我们可以在任何时候 get/set `[[Prototype]]`。但是通常我们只在创建对象的时候设置它一次,自那之后不再修改:`rabbit` 继承自 `animal`,之后不再更改。 + +并且,JavaScript 引擎对此进行了高度优化。用 `Object.setPrototypeOf` 或 `obj.__proto__=` “即时”更改原型是一个非常缓慢的操作,因为它破坏了对象属性访问操作的内部优化。因此,除非你知道自己在做什么,或者 JavaScript 的执行速度对你来说完全不重要,否则请避免使用它。 +``` -## 「极简」对象 +## "Very plain" objects [#very-plain] -我们知道,对象可以当做关联数组来存储键值对。 +我们知道,对象可以用作关联数组(associative arrays)来存储键/值对。 -...但是如果我们尝试存储**用户提供的**键(比如说:一个用户输入的字典),我们可以发现一个有趣的错误:所有的键都运行正常,除了 `"__proto__"`。 +……但是如果我们尝试在其中存储 **用户提供的** 键(例如:一个用户输入的字典),我们可以发现一个有趣的小故障:所有的键都正常工作,除了 `"__proto__"`。 看一下这个例子: @@ -88,34 +99,36 @@ let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; -alert(obj[key]); // [object Object],而不是 "some value"! +alert(obj[key]); // [object Object],并不是 "some value"! ``` -这里如果用户输入 `__proto__`,那么赋值将会被忽略! +这里如果用户输入 `__proto__`,那么赋值会被忽略! -我们不应该感到惊讶。`__proto__` 属性很特别:它必须是对象或者 `null` 值,字符串不能成为 prototype。 +我们不应该对此感到惊讶。`__proto__` 属性很特别:它必须是对象或者 `null`。字符串不能成为 prototype。 -但是我们并不想实现这样的行为,对吗?我们想要存储键值对,然而键名为 `"__proto__"` 没有被正确存储。所以这是一个错误。在这里,结果并没有很严重。但是在其他用例中,prototype 可能被改变,因此可能导致完全意想不到的结果。 +但是我们不是 **打算** 实现这种行为,对吗?我们想要存储键值对,然而键名为 `"__proto__"` 的键值对没有被正确存储。所以这是一个 bug。 -最可怕的是 —— 通常开发者完全不会考虑到这一点。这让类似的 bug 很难被发现,甚至使得它们容易遭到攻击,特别是当 JavaScript 被用在服务端的时候。 +在这里,后果并没有很严重。但是在其他情况下,我们可能会对对象进行赋值操作,然后原型可能就被更改了。结果,可能会导致完全意想不到的结果。 -这样的情况只出现在 `__proto__` 上,所有其他的属性都是正常被「赋值」。 +最可怕的是 —— 通常开发者完全不会考虑到这一点。这让此类 bug 很难被发现,甚至变成漏洞,尤其是在 JavaScript 被用在服务端的时候。 -怎么样避免这个错误呢? +为默认情况下为函数的 `toString` 以及其他内建方法执行赋值操作,也会出现意想不到的结果。 + +我们怎么避免这样的问题呢? 首先,我们可以改用 `Map`,那么一切都迎刃而解。 -但是 `Object` 同样可以运行得很好,因为语言制造者很早以前就注意到这一点。 +但是 `Object` 在这里同样可以运行得很好,因为 JavaScript 语言的制造者很早就注意到了这个问题。 -`__proto__` 根本不是一个对象的属性,只是 `Object.prototype` 的访问属性: +`__proto__` 不是一个对象的属性,只是 `Object.prototype` 的访问器属性: ![](object-prototype-2.svg) -因此,如果 `obj.__proto__` 被读取或者赋值,那么对应的 getter/setter 从它的原型被调用,它会获取/设置 `[[Prototype]]`。 +因此,如果 `obj.__proto__` 被读取或者赋值,那么对应的 getter/setter 会被从它的原型中调用,它会 set/get `[[Prototype]]`。 -就像开头所说:`__proto__` 是访问 `[[Prototype]]` 的方式,而不是 `[[prototype]]` 本身。 +就像在本部分教程的开头所说的那样:`__proto__` 是一种访问 `[[Prototype]]` 的方式,而不是 `[[prototype]]` 本身。 -现在,我们想要使用一个对象作为关联数组,我们可以用一个小技巧: +现在,我们想要将一个对象用作关联数组,我们可以使用一些小技巧: ```js run *!* @@ -132,122 +145,61 @@ alert(obj[key]); // "some value" ![](object-prototype-null.svg) -因此,它没有继承 `__proto__` 的 getter/setter 方法。现在它像正常的数据属性一样运行,因此以上的例子运行正确。 +因此,它没有继承 `__proto__` 的 getter/setter 方法。现在,它被作为正常的数据属性进行处理,因此上面的这个示例能够正常工作。 -我们可以叫这样的对象「极简」或者「纯字典对象」,因此它们甚至比通常的简单对象 `{...}` 还要简单。 +我们可以把这样的对象称为 "very plain" 或 "pure dictionary" 对象,因为它们甚至比通常的普通对象(plain object)`{...}` 还要简单。 -这样的对象有一个缺点是缺少内置的对象方法,比如说 `toString`: +缺点是这样的对象没有任何内建的对象的方法,例如 `toString`: ```js run *!* let obj = Object.create(null); */!* -alert(obj); // Error (没有 toString 方法) +alert(obj); // Error (no toString) ``` -...但是它们通常对关联数组而言还是很友好。 +……但是它们通常对关联数组而言还是很友好。 -请注意,和对象关系最密切的方法是 `Object.something(...)`,比如 `Object.keys(obj)` —— 它们不在 prototype 中,因此在极简对象中它们还是可以继续使用: +请注意,大多数与对象相关的方法都是 `Object.something(...)`,例如 `Object.keys(obj)` —— 它们不在 prototype 中,因此在 "very plain" 对象中它们还是可以继续使用: ```js run let chineseDictionary = Object.create(null); -chineseDictionary.hello = "ni hao"; -chineseDictionary.bye = "zai jian"; +chineseDictionary.hello = "你好"; +chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` -## 获取所有属性 - -获取一个对象的键/值有很多种方法。 - -我们已知的有: - -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- 返回一个数组,包含所有可枚举字符串属性名称/值/键值对。这些方法只会列出**可枚举**属性,而且它们**键名为字符串形式**。 - -如果我们想要 symbol 属性: +## 总结 -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) —— 返回包含所有 symbol 属性名称的数组。 +设置和直接访问原型的现代方法有: -如果我们想要非可枚举属性: +- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用给定的 `proto` 作为 `[[Prototype]]`(可以是 `null`)和可选的属性描述来创建一个空对象。 +- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) —— 返回对象 `obj` 的 `[[Prototype]]`(与 `__proto__` 的 getter 相同)。 +- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) —— 将对象 `obj` 的 `[[Prototype]]` 设置为 `proto`(与 `__proto__` 的 setter 相同)。 -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) —— 返回包含所有字符串属性名的数组。 +如果要将一个用户生成的键放入一个对象,那么内建的 `__proto__` getter/setter 是不安全的。因为用户可能会输入 `"__proto__"` 作为键,这会导致一个 error,虽然我们希望这个问题不会造成什么大影响,但通常会造成不可预料的后果。 -如果我们想要**所有**属性: +因此,我们可以使用 `Object.create(null)` 创建一个没有 `__proto__` 的 "very plain" 对象,或者对此类场景坚持使用 `Map` 对象就可以了。 -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) —— 返回包含所有属性名称的数组。 +此外,`Object.create` 提供了一种简单的方式来浅拷贝一个对象的所有描述符: -这些方法和它们返回的属性有些不同,但它们都是对对象本身进行操作。prototype 的属性没有包含在内。 - -`for...in` 循环有所不同:它会对继承得来的属性也进行循环。 - -举个例子: - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; - -*!* -// 这里只有自身的键 -alert(Object.keys(rabbit)); // jumps -*/!* - -*!* -// 这里包含了继承得来的键 -for(let prop in rabbit) alert(prop); // jumps,然后 eats -*/!* -``` - -如果我们想要区分继承属性,有一个内置方法 [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty):如果 `obj` 有名为 `key` 的自身属性(而非继承),返回值为 `true`。 - -因此我们可以找出继承属性(或者对它们进行一些操作): - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; - -for(let prop in rabbit) { - let isOwn = rabbit.hasOwnProperty(prop); - alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false -} +```js +let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -这个例子中我们有以下继承链:`rabbit`,然后 `animal`,然后 `Object.prototype` (因为 `animal` 是个字面量对象 `{...}`,因此默认是这样),然后最终到达 `null`: - -![](rabbit-animal-object.svg) - -请注意:这里有一个有趣的现象。`rabbit.hasOwnProperty` 这个方法来自哪里?观察继承链我们发现这个方法由 `Object.prototype.hasOwnProperty` 提供。换句话说,它是继承得来的。 - -...但是如果说 `for...in` 列出了所有继承属性,为什么 `hasOwnProperty` 这个方法没有出现在其中?答案很简单:它是不可枚举的。就像所有其他在 `Object.prototype` 中的属性一样。这是为什么它们没有被列出的原因。 - -## 小结 -以下是我们在本章节讨论的方法 —— 作为一个总结: +此外,我们还明确了 `__proto__` 是 `[[Prototype]]` 的 getter/setter,就像其他方法一样,它位于 `Object.prototype`。 -- [Object.create(proto[, descriptors])](mdn:js/Object/create) —— 利用给定的 `proto` 作为 `[[Prototype]]` 来创建一个空对象。 -- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) —— 返回 `obj` 的 `[[Prototype]]`(和 `__proto__` getter 相同)。 -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) —— 将 `obj` 的 `[[Prototype]]` 设置为 `proto`(和 `__proto__` setter 相同)。 -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) —— 返回包含自身属性的名称/值/键值对的数组。 -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) —— 返回包含所有自身 symbol 属性名称的数组。 -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) —— 返回包含所有自身字符串属性名称的数组。 -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) —— 返回包含所有自身属性名称的数组。 -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty):如果 `obj` 拥有名为 `key` 的自身属性(非继承得来),返回 `true`。 +我们可以通过 `Object.create(null)` 来创建没有原型的对象。这样的对象被用作 "pure dictionaries",对于它们而言,使用 `"__proto__"` 作为键是没有问题的。 -同时我们还明确了 `__proto__` 是 `[[Prototype]]` 的 getter/setter,位置在 `Object.prototype`,和其他方法相同。 +其他方法: -我们可以不借助 prototype 创建一个对象,那就是 `Object.create(null)`。这些对象被用作是「纯字典」,对于它们而言 `"__proto__"` 作为键没有问题。 +- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) —— 返回一个可枚举的由自身的字符串属性名/值/键值对组成的数组。 +- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) —— 返回一个由自身所有的 symbol 类型的键组成的数组。 +- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) —— 返回一个由自身所有的字符串键组成的数组。 +- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) —— 返回一个由自身所有键组成的数组。 +- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty):如果 `obj` 拥有名为 `key` 的自身的属性(非继承而来的),则返回 `true`。 -所有返回对象属性的方法(如 `Object.keys` 以及其他)—— 都返回「自身」属性。如果我们想继承它们,我们可以使用 `for...in`。 +所有返回对象属性的方法(如 `Object.keys` 及其他)—— 都返回“自身”的属性。如果我们想继承它们,我们可以使用 `for...in`。