preface
In this post, we will focus on the important but uncommon methods in ES6. This does not mean that we do not use them, but that we may not use them directly, but that we use them underhand when calling other apis or syntactic sugar. So in addition to introducing these methods, I’ll also introduce how these methods are used in our daily calls to the API or syntax sugar.
Iterators are iterators.
The beginning of the beginning: cycle
Let’s start with the very beginning of the cycle.
So let’s look at an array. There are many ways to loop through an array. Let’s see.
Let arr = [' Henry Ruth ', 'Stoic ',' Friends of Time ']; // First method, we can use the for loop, right for(let I = 0; i<arr.length; i++){ console.log(arr[i]); ForEach (item=>{console.log(item); // forEach(item=>{console.log(item); }); For in for(let I in arr){console.log(arr[I]); for in for(let I in arr){console.log(arr[I]); }Copy the code
Hey, loop arrays use these three methods, but what if we want to loop through a string?
let str = 'abcd'; // You can use the for loop for(let I =0; i<str.length; i++){ console.log(str[i]); } //for in loop for(let k in STR){console.log(STR [k]); }Copy the code
ForEach can only iterate over arrays, not strings, and if you want to use forEach, you can only convert strings to arrays.
What if we want to iterate over a map object?
let map = new Map(); Map. set('first', 'first'); Map. set('second', 'second'); Map. set('third', 'third'); Entries () for (let item of map.entries()) {console.log(item[0], item[1]); ForEach ((val,key)=>{console.log(val,key); })Copy the code
For each set of data mentioned above, we found that there is no loop that can solve the above three data types at once.
While forEach loops cannot loop strings, strings can be converted to arrays and output using forEach, but this is not elegant, and converting it to arrays each time it is used is particularly troublesome. And forEach loops have a disadvantage: you can’t break out of the loop with a break, continue statement, or return from the function body.
For loops, on the other hand, can be complicated in some cases by writing code and not looping objects.
By contrast, for… The disadvantage of IN is that it not only iterates over the current object, but also over enumerable properties on the prototype chain. And the for… In is designed primarily for traversing objects and does not work well for traversing groups of numbers.
For example, the following code is 👇
Array.prototype.protoValue = 'hello';
let arr = [1, 2, 3, 4];
for(let i in arr) {
console.log(arr[i]); // 1 2 3 4 hello
}
Copy the code
We added a Hello to the array prototype, and the for in traversal not only traverses the ARR, but also traverses the protoValue property on the prototype.
So, is there a better cycle that solves this cycle problem? ES6 is for… Of circulation.
for… Of cycle occurs
What problem does it solve? First of all, of course, it can solve our problems mentioned above
Let’s loop through the array, string, and map with “for of”.
for(let v of arr) { console.log(v); // 1 2 3 4 } for(let v of str) { console.log(v); // a b c d e } for(let v of map) { console.log(v); / / (2) the "first", "first"] / / (2) the "second", "the second"] / / (2)/" third ", "3"}Copy the code
Ah, they all loop perfectly to get their values.
Check out the pros:
-
Simple and direct traversal number group.
-
It avoids all the drawbacks of a for-in loop.
-
Unlike forEach loops, it can use break, continue, and return statements.
Ruan Yifeng’s Introduction to ECMAScript 6 says:
ES6 uses C++, Java, C#, and Python to introduce for… The of loop, as a unified way to traverse all data structures. A data structure with an iterator interface can use for… The of loop iterates through its members. That is, not all objects can use for… Only objects that implement the Iterator interface can be used for… Of to iterate over the values.
So what is Iterator? Let’s move on
The Iterator Iterator
concept
It is an interface that provides a unified access mechanism for various data structures. The Iterator interface can be deployed on any data structure to complete traversal (that is, processing all members of the data structure in turn).
role
-
To provide a unified and simple access interface for various data structures;
-
To enable the members of a data structure to be arranged in some order;
-
Create a new traversal command for… The of loop is an Iterator interface for… Of consumption.
Now, Iterator was created primarily to use for… Of methods. But the concrete Iterator concept is a bit abstract, if you want to describe it directly:
Is an object with a next() method, and each call to next() returns a result object with two properties, as follows
{value: indicates the current value, done: indicates whether the traversal is complete}Copy the code
Here are some more concepts, let’s comb through them:
Iterator is a special object:
-
It has the next() method, which returns a result object when called
-
The result object has two property values: value and done.
-
Value indicates a specific return value. Done is a Boolean that indicates whether the collection has been traversed, true if it has not, and false if it has.
-
There is an internal pointer to the starting position of the data structure. Each time the next() method is called, the pointer moves back one position until it points to the last position.
Simulation of the Iterator
Then we can simulate an iterator based on the above concepts
function createIterator(arr){
let i = 0;
return {
next: function(){
let done = i >= arr.length;
let value = !done ? arr[i++] : undefined;
return {
value,
done
}
}
}
}
var iterator = new createIterator([1,2,3]);
console.log(iterator.next());//{ value: 1, done: false }
console.log(iterator.next());//{ value: 2, done: false }
console.log(iterator.next());//{ value: 3, done: false }
console.log(iterator.next());//{ value: undefined, done: true }
Copy the code
Process:
-
Creates a pointer object that points to the start of the current data structure. That is, the traverser object is essentially a pointer object.
-
The first time we call the next method on the pointer object, the next method internally holds the value of pointer I through a closure, and each call to I is +1, pointing to the next one. So the first time the pointer points to the first member of the data structure, it prints 1.
-
A second call to the next method of the pointer object, which points to the second member of the data structure, outputs 2.
-
The third call to the next method of the pointer object, with the array length 3, outputs 3 at the end of the data structure.
-
The fourth call to the next method of the pointer object, at which point the traversal is complete, and the output done is true to indicate completion, and value is undefined. The NTH call after that is the same result.
Iterator: Iterator: Iterator: Iterator: Iterator: Iterator: Iterator Of consumption. We try using for… The Iterator object is created above the of loop
Var iterator = new createIterator([1,2,3]); for (let value of iterator) { console.log(value); // TypeError: iterator is not iterable }Copy the code
If we create an iterator that is not iterable, such a structure cannot be iterated by a for of loop, what kind of structure can be iterated over?
Iterable Iterable
How to implement
ES6 also introduces a new object called Symbol, whose value is unique.
ES6 states that the default Iterator interface is deployed in the symbol. Iterator property of a data structure, or that a data structure can be considered “iterable” as long as it has the symbol. Iterator property.
We use this to add a Symbol. Iterator property to our iterator.
var arr = [1, 2, 3]; arr[Symbol.iterator] = function() { var _this = this; var i = 0; return { next: function() { var done = (i >= _this.length); var value = ! done ? _this[i++] : undefined; return { done: done, value: value }; }}; } // You can do this for... For (var item of arr){console.log(item); // 1,2,3}Copy the code
So you can see, for… The of iterator is the Symbol. Iterator property of the object.
A primitive data structure with an Iterator interface
In ES6, all collection objects, including arrays, arguments objects, typedArray, DOM NodeList objects, maps and sets, and strings, are iterable. Both have default iterators, so you don’t have to write your own traverser generator functions, for… The of loop iterates through them automatically. In addition, the Iterator interface for other data structures (mainly objects) needs to be deployed on the Symbol. Iterator property. The of loop traverses.
Consider a few examples of iterable data structures:
An array of
Let arr = [1, 2, 3]; let iteratorObj = arr[Symbol.iterator](); console.log(iteratorObj.next()); //{ value: 1, done: false } console.log(iteratorObj.next()); //{ value: 2, done: false } console.log(iteratorObj.next()); //{ value: 3, done: false } console.log(iteratorObj.next()); //{ value: undefined, done: true }Copy the code
In the above code, the variable arr is an array with a native traverser interface deployed on the Symbol. Iterator property of the ARR. So, by calling this property, you get the traverser object.
arguments
We know that normal objects do not deploy this interface by default, so the arguments property is not on the prototype, but on the property of itself.
function test(){ let obj = arguments[Symbol.iterator](); console.log(arguments); console.log(obj.next()); console.log(obj.next()); console.log(obj.next()); } the test (1, 2, 3);Copy the code
NodeList
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
const nodeList = document.getElementsByClassName('test')
for (const node of nodeList) {
console.log(node);
}
Copy the code
Map
We can prove it directly from the prototype
console.log(Map.prototype.hasOwnProperty(Symbol.iterator)); // true
Copy the code
The familiar context for calling the Iterator interface
There are situations where the Iterator interface (symbol. Iterator method) is called by default, except for the for… Of loops, and there are a couple of other occasions.
1. Deconstruct assignment
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
Copy the code
Deconstructing assignment is also common in our everyday coding. Destruct assignment can be used when we need to get some properties of an object.
submitForm(form) { this.$refs[form].validate((valid) => { if (! Valid) {console.log(" Please enter it correctly "); } else { this.$proxy({ url: "", method: 'post', params: { first_name: this.form.firstName, last_name: this.form.lastName, age: this.form.age, gender: this.form.gender, } }) } }) }Copy the code
This way, if you deconstruct all the attributes you need, you don’t have to link them one by one, and it looks a lot more elegant and less distracting.
submitForm(form) { this.$refs[form].validate((valid) => { if (! Valid) {console.log(" Please enter it correctly "); } else { const { firstName, lastName, age, gender } = this.form; this.$proxy({ url: "", method: 'post', params: { first_name: firstName, last_name: lastName, age, gender, } }) } }) }Copy the code
2. Extend operators
Extension operators also use the underlying Iterator interface, so how can we use extension operators?
A. Reference components
We usually refer to components in files like this one by one, and then register them in components.
<script>
import componentOne from './components/ComponentOne.vue';
import componentTwo from './components/ComponentTwo.vue';
import componentThree from './components/ComponentThree.vue';
import componentFour from './components/ComponentFour.vue';
import componentFive from './components/ComponentFive.vue';
import componentSix from './components/ComponentSix.vue';
import componentSeven from './components/ComponentSeven.vue';
import componentEight from './components/ComponentEight.vue';
export default {
components: {
componentOne,
componentTwo,
componentThree,
componentFour,
componentFive,
componentSix,
componentSeven,
componentEight,
}
}
</script>
Copy the code
What if we have too many components to reference?
Imagine import files from componentOne to componentTwenty. It would be particularly unfriendly to just reference components and register them on a single screen.
So what do you do in this situation?
Use a separate index.js file to import exported components, then import the index file in the files that need to use those components, and then register it in components. So there’s only two lines of code in our.vue file.
./components/index.js
import componentOne from './components/ComponentOne.vue';
import componentTwo from './components/ComponentTwo.vue';
import componentThree from './components/ComponentThree.vue';
import componentFour from './components/ComponentFour.vue';
import componentFive from './components/ComponentFive.vue';
import componentSix from './components/ComponentSix.vue';
import componentSeven from './components/ComponentSeven.vue';
import componentEight from './components/ComponentEight.vue';
export default {
componentOne,
componentTwo,
componentThree,
componentFour,
componentFive,
componentSix,
componentSeven,
componentEight,
};
Copy the code
xx.vue
<script> import components from './components'; export default { components: { ... components }, } </script>Copy the code
You can see before and after, does it look a lot cleaner.
B. Another option is to use the extension operator shallow copy, which is also a deep copy if our object has only one layer
We can use this to merge and assign simple objects
This code shows that we actually need to export an object. We could have said return obj, but then we would have added an access link to external access, such as accessing property1, and the direct return would be obj.obj.property1. We can access property1 using only obJ. property1 after we copy it using the extended operator.
module.exports = () => { const obj = { property1: {}, property2: {}, property3: {}, } return { ... obj } }Copy the code
👇 below this code is in the variable frequency may be returned to the original value, this is the initial value can be written out alone, then at the time of use with extended operator copy can be directly, because there is only one layer, can be thought of as a deep copy, so also don’t have to worry about changes to the initial value.
const initFormData = { id: null, text: null, type: 'text', status: 0, } export default { data() { return { formData: {... initFormData }, }; }}Copy the code
3, yield* keyword
Yield * is followed by a traversable structure that also calls iterator functions when executed.
let foo = function* () { yield 1; Yield * (2 and 4]; yield 5; };Copy the code
Yield is a Generator and will be covered later, so I won’t describe it too much here.
conclusion
The emergence of ES6 brings many new data structures, such as Map and Set. Therefore, a unified data acquisition method for for the convenience of data acquisition is added. When for of is executed, the engine automatically calls the iterator of the object to evaluate it.
Not all objects support this approach, but only those that implement the Iterator interface. Such objects are called iterables.
Iterator it is simply a method that returns an iterator object.
An iterable is an object that deploys the Iterator interface and has the correct Iterator methods.
In fact, ES6 many places are used Iterator, usually can pay more attention to observe, think more step, will be helpful to everyone to understand the code.
Ii. Generator
Why a Generator
In JavaScript, asynchronous programming scenarios are used a lot, and there are often situations where multiple asynchronous operations need to be completed step by step. The more asynchronous you are, the more nested you are. This makes your code less maintainable and harder to read. There is a term called callback hell.
The Generator function is an asynchronous programming solution proposed in ES6. It avoids the nesting of callbacks and has a completely different syntactic behavior from traditional functions. In addition, the features of the Generator are convenient for use in certain scenarios.
concept
Syntactically, the Generator function is first understood as a state machine or an Iterator object Generator. It returns a traverser object that traverses each state within the Generator in turn. Generator functions generate an object, but cannot be called with a new command in front of it.
In layman’s terms, a Generator function can pause a function at any time and resume it at any time.
Relationship to the Iterator interface
Iterator the symbol. iterator method of any object is equal to the iterator generator function of that object. Calling this function returns an iterator object for that object.
Since a Generator function is an Iterator Generator, you can assign Generator to the Symbol. Iterator property of an object so that the object has an Iterator interface.
var myIterable = {}; myIterable[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }; console.log([...myIterable]); / / [1, 2, 3]Copy the code
In the code above, the Generator function assigns the Symbol. Iterator property, thus giving the myIterable object an Iterator interface that can be… Operator traversal.
After the Generator function executes, an iterator object is returned. The object itself also has the symbol. iterator property, which returns itself upon execution.
The characteristics of
-
There is an asterisk between the function keyword and the function name.
-
Inside the function body, yield expressions are used to define different internal states
-
Normal functions are executed in execution-end mode, while generators are executed in execution-pause-end mode. A generator can pause itself in the middle of execution, either immediately or after a period of time. The biggest difference is that it is not guaranteed to run to the end like normal functions.
Yied keyword
In the generator function, there is a special new keyword: yield. Since Generator functions return an Iterator, only a call to the next method iterates through the next internal state, and the yield keyword is the pause flag. Because of it, the execution-pause-end execution pattern can be realized. The yield is followed by the js expression.
The yield keyword has a low priority, and almost any expression after yield evaluates first and then yields a value to the outside world. And yield is the right associative operator, so that’s it
Yield, yield 2 is equivalent to (yield (yield 2)).Copy the code
Conclusion:
-
It can specify the return value when the method is called and the order in which it is called.
-
Each time the yield statement is executed, the function stops execution and does not continue until the next method is called again
-
The yield keyword can only be used internally within the generator, causing syntax errors elsewhere
Run the generator function
function* gen(){ yield 1; yield 2; yield 3; } let generator = gen(); // The generator returns a generator object console.log(generator.next()) pointing to the internal state; // {value: 1, done: false} console.log(generator.next()); // {value: 2, done: false} console.log(generator.next()); // {value: 3, done: false} console.log(generator.next()); // {value: undefined, done: true}Copy the code
First, a Generator function, with or without yield statements, does not execute any statements when called, and does not return the result of the function execution. Instead, it returns a Generator object pointing to the internal state, which can also be thought of as an Iterator object. The internal statement is executed only when next() is called.
Inside this function there are three yield expressions, which means that the function has three states: 1, 2, and 3. The Generator function executes at this point, calls the next method, and the internal logic of the function starts executing, stops at yield, returns the Iterator, and calls the next method again, starting at yield and ending at last. So we can understand that the yield statement is just a flag for the function to pause.
Yield expression *
Yield * can place an iterable object in a generator. When the generator function reaches yield*, it delegates control to the iterator until execution is complete. For example, arrays are also iterable, so yield* can also be delegated to arrays:
function* gen1() {
yield 2;
yield 3;
}
function* gen2() {
yield 1;
yield* gen1();
yield* [4, 5];
}
const generator = gen2();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next()); // {value: 2, done: false}
console.log(generator.next()); // {value: 3, done: false}
console.log(generator.next()); // {value: 4, done: false}
console.log(generator.next()); // {value: 5, done: false}
console.log(generator.next()); // {value: undefined, done: true}
Copy the code
Commonly used Generator syntax sugar-async functions
The ES2017 standard introduces async functions to make asynchronous operations more convenient, and async functions are syntactic sugar of Generator functions.
-
Async corresponds to *
-
Await corresponds to yield
Async and await are semantic clearer than * and yield. Async means that there is an asynchronous operation in a function, and await means that the following expression needs to wait for the result.
I feel that the async await function is already well wrapped, and there is no problem with using it. It is friendlier than Generator in all aspects, so it is good to know that the underlying layer of Generator is used when using it.
conclusion
The Generator is a pause-and-resume function that fully implements Iterator, and is ideally suited for simple state machines because it can store context. In addition, through the cooperation of some process control code, it is easier to carry out asynchronous operation.
Async/Await is the syntax-sugar of the generator for asynchronous operations. Compared with Async/Await, this syntax-sugar has better application and semantics. It can be used to process asynchronous processes by writing synchronous code, thus improving the simplicity and readability of the code.
Third, the Proxy
Concepts and Usage
Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of “meta programming”, that is, programming a programming language.
Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.
var obj = new Proxy({}, { get: function (target, propKey, receiver) { console.log(`getting ${propKey}! `); return Reflect.get(target, propKey, receiver); }, set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}! `); return Reflect.set(target, propKey, value, receiver); }});Copy the code
For example, the 👆 code above puts a layer of interception on an empty object and redefines the read and set behavior of the property. To read and write properties of obj, the object with interception behavior set, the following result is obtained.
obj.count = 1 // setting count! ++obj.count // getting count! // setting count! / / 2Copy the code
Use of Proxy in Vue3
With this usage, let’s look at the use of proxy in VUE3
How does VUe2 implement the data response
Recall the flow of vue rendering. When new vue () initializes the vue, it iterates through the data in the data, hijacking the getter and setter with Object.definedProperty(), and doing the data dependency collection in the getter. Listen for changes to the data in the setter and notify where you subscribe to the current data.
Its partial source code would look like 👇
let childOb = ! shallow && observe(val); / / on the depth of the data in the data traversal, add response to objects of every attribute type Object. DefineProperty (obj, key, {enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; If (dep.target) {// Do dependency collection dep.depend(); if (childOb) { childOb.dep.depend(); IsArray (value) {if (array.isarray (value)) {if (array.isarray (value)) {if (array.isarray (value)) { dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal ! == newVal && value ! == value)) { return; } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV ! == 'production' && customSetter) { customSetter(); } if (getter && ! setter) return; if (setter) { setter.call(obj, newVal); } else { val = newVal; } // The new value must be observed again to ensure that the data response childOb =! shallow && observe(newVal); // Notify all observers of the data change dep.notify(); }});Copy the code
Vue2 implementation of the data response problem
<ul id="example"> <li v-for="item in items"> {{ item }} </li> </ul> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#example', data: { items: ['a', 'b', 'c'], }, }); SetTimeout (() => {vm.items[1] = 'x'; vm.items[3] = 'd'; console.log(vm.items); / / the printing results for [' a ', 'x', 'c', 'd'], but there is no update page content}, 500); SetTimeout (() => {vm.$set(vm. Items, 1, 'x1'); vm.$set(vm.items, 3, 'd1'); console.log(vm.items); / / the printing results for [' a ', 'the x1,' c ', 'd1], page content update}, 2000); </script>Copy the code
-
Undetected additions and deletions of object properties: when you add a newProperty to an object, the newProperty does not have a mechanism for vue to detect data updates (because it was added after initialization). Vue. Set lets vue know that you’ve added a property and it’ll do something to you, Set interior is also handled by calling Object.defineProperty()
-
The change of array subscripts cannot be monitored. As a result, the value of the array is directly set by the array subscripts and cannot respond in real time.
-
When there is a lot of data in the data and the hierarchy is very deep, there is a performance problem because all the data in the data has to be traversed and set to be responsive.
Vue3 is implemented using proxy
Why can use Proxy to solve the above problems? Let’s look directly at the createReactiveObject function in the source code
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: <any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any>) {// If (! isObject(target)) { return target; } // If the target is already a proxy, return // unless readonly if (target[reactiveFlags.raw] &&! (isReadonly && target[ReactiveFlags.IS_REACTIVE])) { return target; } // Target already has a corresponding proxy object const existingProxy = proxymap.get (target); if (existingProxy) { return existingProxy; } const targetType = getTargetType(target); const targetType = getTargetType(target); if (targetType === TargetType.INVALID) { return target; } const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers); proxyMap.set(target, proxy); return proxy; }Copy the code
In the logical part of this function, you can see that the underlying data type is not converted to a proxy object, but is returned directly to the original value.
In addition, the generated proxy object will be cached into the incoming proxyMap. When the proxy object already exists, it will not be generated repeatedly and will directly return the existing object.
Vue3 will only generate proxy for Array, Object, Map, Set, WeakMap, WeakSet. Other objects will be marked as INVALID and return the original value.
When the target object passes the type verification, a Proxy object Proxy is generated using new Proxy(). Handler parameters are passed with targetType parameters such as get, set, and whether it can be changed. The generated proxy object is eventually returned.
So looking back at the Reactive API, we might get a proxy object, or we might just get the raw value of the target object that was passed in.
Therefore, proxy can solve the problem of data response in VUe2 mainly by converting the object into proxy. As proxy is an interception object, it “intercepts” the object, and external access to the object must pass this interception first. No matter what attributes of the object are accessed, previously defined or newly added, it goes to the interception.
Four, to finish with two small desserts
The optional chain operator (? .).
Allows you to read the value of a property located deep in the chain of connected objects without explicitly validating that each reference in the chain is valid. ? The. Operator functions like the. Chain operator, except that it does not cause errors when references are nullish (null or undefined), and the short-circuit returns undefined. When used with a function call, returns undefined if the given function does not exist.
A piece of junk code:
<code-card
v-if="card.info && card.info.show"
/>
Copy the code
Show: cannot read property ‘show ‘of undefined, so the entire page cannot be rendered. To avoid this error, I added a check to card.info to make sure I only read show when it exists.
But what if we use? . The code will look like this.
<code-card
v-if="card.info?.show"
/>
Copy the code
Good. And I don’t even have to worry about the value, because return? I can use undefined directly.
Well, the definition says it can also be used with functions, so let’s see how it can be used with functions.
I believe you must have encountered this error. X is not a function. For example, it’s still my code
delete(value, callback) { this.$axios .post('delete', {id: value}) .then(({ data }) => { if (data.status ! == 0) { if (callback) callback('fail'); return; } if (callback) callback('success'); }); }Copy the code
This is the code for a menu to remove a page or a group or a navigation. A callback function is used when a child component calls a function of a parent component that needs to perform a different behavior depending on the result of the function’s final processing. The problem is that all three children are firing the parent’s function, and not all children need a callback. So it’s written like this, using two ifs, which at first glance looks fine. But we do? .After this, we can just use it like this.
delete(value, callback) { this.$axios .post('delete', {id: value}) .then(({ data }) => { if (data.status ! == 0) { const a = callback? . (); return; } const b = callback? . (); }); }Copy the code
It’s not a good idea to use it in this case, but just to make sense of it, in case you need the return value of this function, right? You can use it.
Null-value merge operator
Null-value merge operator (??) Is a logical operator that returns the right-hand operand if the left-hand operand is null or undefined, otherwise returns the left-hand operand.
With the logical or operator (| |), logical or operator will be on the left operand is false value returns the right operand. That is to say, if you use the | | to set default values for certain variables, may encounter unexpected behavior. For example, false (for example, ” or 0). A bug from the last two weeks.
this.$http
.post('', {
url: '',
method: 'post',
headers: {},
data: {
selected: selected || -1,
}
});
Copy the code
This doesn’t seem to be a problem, but in the case where our selected might have a value of -1, 0, 1, our selected 0 would be assigned -1, which would result in only two of the three values that should have been selected. In cases like this, where the value could be 0 or “,?? It comes in handy.
We can use it like this.
this.$http .post('', { url: '', method: 'post', headers: {}, data: { selected: selected ?? 1,}});Copy the code