Finished chapter 6.7: Extending Objects
parent
35212b2b19
commit
e381a49e37
|
@ -344,10 +344,102 @@ delete o.x; // Delete the property x
|
||||||
|
|
||||||
## 6.6 Enumerating Properties
|
## 6.6 Enumerating Properties
|
||||||
|
|
||||||
|
We want to iterate through or obtain a list of all properties of an object using:
|
||||||
|
* `for/in` loop.
|
||||||
|
* Get an array of property names for an object and then loop through that array with `for/of` loop.
|
||||||
|
|
||||||
|
|
||||||
|
**Method 1: Use `for/in` loop to enumerate properties**
|
||||||
|
1. runs the body of the loop once for each enumerable property (own or inherited) of the specified obj.
|
||||||
|
2. assigning the name of the property to the loop variable.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
* Inherited built-in methods are not enumerable (e.g. "toString")
|
||||||
|
* Properties that added by code are enumerable by default.
|
||||||
|
```js
|
||||||
|
let o = {x: 1, y: 2, z: 3}; // Three enumerable own properties
|
||||||
|
o.propertyIsEnumerable("toString") // => false: not enumerable
|
||||||
|
for(let p in o) { // Loop through the properties
|
||||||
|
console.log(p); // Prints x, y, and z, but not toString
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Trick: Stop enumerating inherited properties with `for/in`, add an explicit check inside loop body
|
||||||
|
```js
|
||||||
|
for(let p in o) {
|
||||||
|
if (!o.hasOwnProperty(p)) continue; // Skip inherited properties
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let p in o) {
|
||||||
|
if (typeof o[p] === "function") continue; // Skip all methods
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Method 2: Get an array of property names for an object and then loop through that array with `for/of` loop.**
|
||||||
|
|
||||||
|
Four ways to get an array of property names:
|
||||||
|
* `Object.keys()`: returns an array of the names of the enumerable own properties of an object, excluding non-enumerable properties, inherited properties, properties whose name is Symbol
|
||||||
|
* `Object.getOwnPropertyNames()`: returns same as `Ojbect.keys()` + non-enum own properties (as long as name are strings)
|
||||||
|
* `Object.getOwnPropertySymbols()`: returns own properties whose names are Symboles (no matter they are enumerable or not)
|
||||||
|
* `Reflect.ownKeys()`: returns all own property names, both enum or non-enum, and both string and Symbol.
|
||||||
|
|
||||||
### 6.6.1 Property Enumeration Order
|
### 6.6.1 Property Enumeration Order
|
||||||
|
|
||||||
|
Order Summary:
|
||||||
|
1. String properties whose names are non-negative integers, numeric order from smallest to largest. i.e. arrays are enumerated in order
|
||||||
|
2. All remaining properties with string names. In order they were added to object.
|
||||||
|
3. Properties whose names are Symbol in order they were added to obj.
|
||||||
|
|
||||||
## 6.7 Extending Objects
|
## 6.7 Extending Objects
|
||||||
|
|
||||||
|
Common practice in pure JS to copy properties from one obj to another:
|
||||||
|
```js
|
||||||
|
let target = {x: 1}, source = {y: 2, z: 3};
|
||||||
|
for(let key of Object.keys(source)) {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
target // => {x: 1, y: 2, z: 3}
|
||||||
|
```
|
||||||
|
|
||||||
|
Various JS frameworks have developed utility function `extend()` to cover this operation, and it's standarized in ES6 as `Object.assign()`
|
||||||
|
|
||||||
|
**`Object.assign()`**:
|
||||||
|
* Syntax: `Object.assign(target_obj, src_obj_1, src_obj_2)`
|
||||||
|
* Operations: Copy enumerable own properties from 2nd and subsequent args (i.e. **source object**) to 1st arg (i.e. **target object**)
|
||||||
|
* target obj is modified & returned
|
||||||
|
* source obj is not changed
|
||||||
|
* Copy order follows order of arg, so 1st source object property will overwrite target obj property, while 2nd source obj property will overwrite 1st obj property
|
||||||
|
* How copy works: using ordinary property get/set operations.
|
||||||
|
* if source has getter method, and target has setter, these 2 methods will be invoked. But themselves won't be copied.
|
||||||
|
|
||||||
|
Trick to use `.assign()`:
|
||||||
|
* Given obj `o`, and source `defaults`. Directly assign will overwrite `o`'s original properties if there are same property names
|
||||||
|
```js
|
||||||
|
Object.assign(o, defaults); // overwrites everything in o with defaults
|
||||||
|
```
|
||||||
|
* Correct way of safely copy properties while keeping target's original property values: create a new object, copy the defaults into it, and then override those defaults with the properties in `o`
|
||||||
|
```js
|
||||||
|
o = Object.assign({}, defaults, o);
|
||||||
|
```
|
||||||
|
|
||||||
|
A function can be created to solve this problem via copies properties only if they are missing:
|
||||||
|
```js
|
||||||
|
// Like Object.assign() but doesn't override existing properties
|
||||||
|
// (and also doesn't handle Symbol properties)
|
||||||
|
function merge(target, ...sources) {
|
||||||
|
for(let source of sources) {
|
||||||
|
for(let key of Object.keys(source)) {
|
||||||
|
if (!(key in target)) { // This is different than Object.assign()
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
Object.assign({x: 1}, {x: 2, y: 2}, {y: 3, z: 4}) // => {x: 2, y: 3, z: 4}
|
||||||
|
merge({x: 1}, {x: 2, y: 2}, {y: 3, z: 4}) // => {x: 1, y: 2, z: 4}
|
||||||
|
```
|
||||||
|
|
||||||
## 6.8 Serializing Objects
|
## 6.8 Serializing Objects
|
||||||
|
|
||||||
## 6.9 Object Methods
|
## 6.9 Object Methods
|
||||||
|
|
Loading…
Reference in New Issue