One, foreword
In business development, it is inevitable to operate on complex nested objects, and two pain points are commonly encountered in this scenario:
- A lack of robustness handling when working with nested objects triggers a runtime error that leads to a blank page
- The way the robustness is handled is not elegant, with lots of bloated code blocks
Optional chain operators
ECMA’s new optional chain operator specification allows properties deep in the chain of objects to be read without explicitly validating that each reference in the chain is valid.
const adventurer = {
name: 'Alice'.cat: {
name: 'Dinah'}}; adventurer.someNonExistentMethod? . ();// undefinedadventurer.dog? .name;// undefined
Copy the code
Instead of using the optional chain operator, the browser throws the following exception:
Uncaught TypeError: adventurer.someNonExistentMethod is not a function
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Copy the code
The optional chain operator will not cause an error if the reference is null or undefined, and will short-circuit the expression to return undefined.
let potentiallyNullObj = null;
let x = 0;
letprop = potentiallyNullObj? .[x++];console.log(x); // x will not be incremented and will still print 0
Copy the code
For example, in the code above, the x++ expression is not executed, which is a form of internal optimization in JavaScript.
Optional chain operators can be combined with functions, but this can happen:
const foo = {
customMethod: () = > {}
}
foo.customMethod = 1; foo? .customMethod? . ();// foo? .customMethod is not a function
Copy the code
So in the case of functions, you need to be able to ensure that the method properties won’t be tampered with, otherwise you still need to use Typeof to keep your code robust.
In front-end development scenarios, where data needs to be mapped to the UI in most cases, displaying undefined is definitely not appropriate, so the specification also provides a null value merge operator.
let customer = {
name: "Carl".details: { age: 82}};letcustomerCity = customer? .city ??Dark City;
console.log(customerCity); // Dark City
Copy the code
In the harsh reality of development, the server may return NULL for fields with no data. So this kind of uncertain scenarios, using the Boolean or operator (| |) will be more reliable.
Babel compiler
Because the optional chain operator is currently in Stage 4, this feature code can only be compiled using Babel or TypeScript.
constfoo = {} foo? .bar? .nameCopy the code
Compiling the above code with Babel yields the following:
var _foo$bar;
const foo = {};
foo === null || foo === void 0 ? void 0 : (_foo$bar = foo.bar) === null || _foo$bar === void 0 ? void 0 : _foo$bar.name;
Copy the code
The key points of implementation are as follows:
- Implement the short-circuit calculation feature of optional chain operators using logic or operators
- Simulate each reference to the object chain with temporary variables
The optional chain operator does not create temporary variables.
4. Set the property value at the bottom of the object chain
As mentioned earlier, the optional chain operator can only read properties deep in the object chain, but in business development, you cannot avoid setting properties deep in the object chain.
First we need to define a protocol for how the chain of objects should be referenced:
dotProp.set(object, 'foo.baz'.'x');
Copy the code
The next step is to parse out the attribute name referenced at each level for the delimiter:
const getPathSegments = path= > path.split('. ');
Copy the code
To enhance the robustness of the code, the parsed path can be validated again:
const disallowedKeys = new Set([
'__proto__'.'prototype'.'constructor'
]);
const isValidPath = pathSegments= >! pathSegments.some(segment= > disallowedKeys.has(segment));
const getPathSegments = path= > {
const paths = path.split('. ');
if(! isValidPath(paths)) {return [];
}
return paths;
};
Copy the code
Once you have a reference path for the chain of objects, you only need to find the final assignment node, especially to ensure that the upper level is referenced as an object during traversal to avoid TypeError exceptions.
set(object, path, value) {
if(! isObject(object) ||typeofpath ! = ='string') {
return object;
}
const root = object;
const pathArray = getPathSegments(path);
for (let i = 0; i < pathArray.length; i++) {
const p = pathArray[i];
if(! isObject(object[p])) { object[p] = {}; }if (i === pathArray.length - 1) {
object[p] = value;
}
object = object[p];
}
return root;
}
Copy the code
Although the above code implements the function of setting property values deep in the object chain, its disadvantages are also obvious, lacking the intelligent hints of the IDE. It is possible to have unexpected results due to spelling errors.
For the object chain protocol convention can also refer to the Ramda library simple and crude way:
const ramda = require('ramda');
const obj = {
a: {
b: [1.2.3]
}
}
ramda.assocPath(['a'.'b'.1].20, obj); // { a: { b: [ 1, 20, 3 ] } }
Copy the code
Write at the end
References and related source code:
- Developer.mozilla.org/zh-CN/docs/…
- Github.com/sindresorhu…
Finally, if this article is helpful to you, please like, bookmark and share.