diff --git a/notes/chap6_objects.md b/notes/chap6_objects.md index 4331af2..84a4289 100644 --- a/notes/chap6_objects.md +++ b/notes/chap6_objects.md @@ -344,10 +344,102 @@ delete o.x; // Delete the property x ## 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 +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 +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.9 Object Methods