Arrays are ubiquitous in JavaScript, and we can do a lot of great things with the new feature in ECMAScript 6 that extends operators.

1. Iterate over an empty array

Arrays created directly in JavaScript are loose to the point that they have a lot of holes. Try creating an array using the array constructor and you’ll see in a flash.

const arr = new Array(4); [undefined, undefined, undefined] // Empty x 4Copy the code

You will find that it is very difficult to loop through some transformations through a loose array.

const arr = new Array(4);
arr.map((elem, index) => index);
[undefined, undefined, undefined, undefined]
Copy the code

To solve this problem, you can use array.apply when creating a new Array.

const arr = Array.apply(null, new Array(4));
arr.map((elem, index) => index);
[0, 1, 2, 3]
Copy the code

2. Pass an empty argument to the method

JavaScript will report an error if you want to call a method without filling in one of its arguments.

method('parameter1', , 'parameter3'); // Uncaught SyntaxError: Unexpected token ,
Copy the code

One common solution is to pass NULL or undefined.

method('parameter1', null, 'parameter3') // or
method('parameter1', undefined, 'parameter3');
Copy the code

According to the introduction of extension operators in ES6, there is a cleaner way to pass empty arguments to a method. As mentioned above, arrays are loose, so it is possible to pass a null value to them, which is the advantage we take.

method(... ['parameter1', , 'parameter3']); // The code executes...Copy the code

3. Array deduplication

I’ve never understood why arrays don’t provide a built-in function that allows us to easily retrieve the value after refactoring. The extension operator helps us by combining Set with the extension operator to generate a non-repeating array.

const arr = [...new Set([1, 2, 3, 3])]; / / [1, 2, 3]Copy the code

4. Fetch array elements from back to front

If you want to fetch the elements of an array from back to front, you can write:

var arr = [1, 2, 3, 4]

console.log(arr.slice(-1)) // [4]
console.log(arr.slice(-2)) // [3, 4]
console.log(arr.slice(-3)) // [2, 3, 4]
console.log(arr.slice(-4)) // [1, 2, 3, 4]
Copy the code

5. Short circuit conditionals

If you want to execute a function when a conditional logic value is true, it looks like this:

if (condition) {
  dosomething()
}
Copy the code

At this point, you can use a short circuit like this:

condition && dosomething()
Copy the code

6. With an operator “| |” to set the default values

If you must assign a default value to a variable, you can simply write:

var a

console.log(a) // undefined
a = a || 'default value'
console.log(a) // default value
a = a || 'new value'
console.log(a) // default value
Copy the code

7. Use object.is () in equality comparison

We all know that JavasSript is weakly typed, and that when we use == for comparison, in some cases we can have unexpected results due to type conversions or “convert one of the two operands to the other and then compare”. Something like this:

0 == ' ' //true
null == undefined //true
[1] == true //true
Copy the code

So JavaScript gives us the congruent operator ===, which is stricter than the incongruent operator and does not cast. But using === for comparison is not the best solution. You might get:

NaN === NaN //false
Copy the code

The new object.is () method is available in ES6, which has some features of ===, but is better and more accurate, and works well in some special cases:

Object.is(0 , ' '); //false
Object.is(null, undefined); //false
Object.is([1], true); //false
Object.is(NaN, NaN); //true
Copy the code

8. Give a function Bind object

We often need to bind an object to a method’s this. In JS, you can use the bind method if you want to call a function and specify its this.

Bind the grammar

fun.bind(thisArg[, arg1[, arg2[, ...]]])
Copy the code

parameter

thisArg

This parameter is referred to as this when the binding function is called.

Arg1, arg2,…

When the binding function is called, these arguments are passed to the bound method before the arguments.

The return value

Returns a copy of the original function modified with the specified this value and initialization arguments

Instance in JS

const myCar = {
 brand: 'Ford',
 type: 'Sedan',
 color: 'Red'
};

const getBrand = function () {
 console.log(this.brand);
};

const getType = function () {
 console.log(this.type);
};

const getColor = function () {
 console.log(this.color);
};

getBrand(); // object not bind,undefined

getBrand(myCar); // object not bind,undefined

getType.bind(myCar)(); // Sedan

let boundGetColor = getColor.bind(myCar);
boundGetColor(); // Red
Copy the code

9. Obtain file extensions

Solution 1: Regular expression

function getFileExtension1(filename) {
  return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename)[0] : undefined;
}
Copy the code

Solution 2: String split method

function getFileExtension2(filename) {
  return filename.split('.').pop();
}
Copy the code

While these two solutions do not solve some edge cases, there is a more powerful solution.

Solution 3: String slice, lastIndexOf methods

function getFileExtension3(filename) {
  return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}

console.log(getFileExtension3(''));                            // ''
console.log(getFileExtension3('filename'));                    // ''
console.log(getFileExtension3('filename.txt'));                // 'txt'
console.log(getFileExtension3('.hiddenfile'));                 // ''
console.log(getFileExtension3('filename.with.many.dots.ext')); // 'ext'
Copy the code

How does this work?

  • The string.lastIndexof () method returns the last position of the specified value (‘.’ in this case) in the String in which the method was called, or -1 if it is not found.
  • For ‘filename’ and ‘.hiddenfile’, the return value of lastIndexOf is 0 and -1, respectively. The unsigned right-shift operator (»>) converts -1 to 4294967295 and -2 to 4294967294. This method ensures that the filename remains the same in the edge case.
  • String.prototype.slice() extracts the file extension from the index calculated above. If the index is larger than the length of the file name, the result is “”.

10. Prevent unapply attacks

Override the prototype method of the built-in object. External code can be overridden to expose and modify functions with bound parameters. This is a serious security issue when polyfill is used under the ES5 approach.

/ / bind polyfill sample function to bind (fn) {var prev = Array. Prototype. Slice. The call (the arguments, 1); return function bound() { var curr = Array.prototype.slice.call(arguments, 0); var args = Array.prototype.concat.apply(prev, curr); return fn.apply(null, args); }; } function unapplyAttack() {var concat = array.prototype. concat; Array.prototype.concat = function replaceAll() { Array.prototype.concat = concat; // restore the correct version var curr = Array.prototype.slice.call(arguments, 0); var result = concat.apply([], curr); return result; }; }Copy the code

The above function declaration ignores the prev argument to bind, meaning that it is called first after unapplyAttack. Concat will throw an error.

By using Object.freeze to make objects immutable, you can prevent any built-in Object prototype methods from being overridden.

(function freezePrototypes() { if (typeof Object.freeze ! == 'function') { throw new Error('Missing Object.freeze'); } Object.freeze(Object.prototype); Object.freeze(Array.prototype); Object.freeze(Function.prototype); } ());Copy the code

You can read more about unapply attacks by clicking here.

11.Javascript multi-dimensional array flattening

Here are three different ways to convert a multi-digit group into a single array.

var arr = [[1, 2],[3, 4, 5], [6, 7, 8, 9]];
Copy the code

Expected Results:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
Copy the code

Solution 1: Use concat() and apply()

var newArr = [].concat.apply([], arr);
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
Copy the code

Solution 2: Use reduce()

var newArr = arr.reduce(function(prev, curr) { 
  return prev.concat(curr); 
},[]);
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
Copy the code

Solution 3: Use the ES6 expansion operator

var newArr = [].concat(... arr); console.log(newArr); // [1, 2, 3, 4, 5, 6, 7, 8, 9]Copy the code

12. How to use optional arguments in functions (including optional callback functions)

The second and third arguments in the instance function are optional

Function example(err, optionA, optionB, callback) {var args = new Array(arguments.length); for(var i = 0; i < args.length; ++i) { args[i] = arguments[i]; }; Shift () removes the first argument from the array and returns err = args.shift(); If (typeof args[args.length-1] === 'function') {callback = args.pop(); if (typeof args[args.length-1] === 'function') {callback = args.pop();  } // If there are still elements in args, that is the optional argument you need // You can take them out one by one like this: if (args.length > 0) optionA = args.shift(); else optionA = null; if (args.length > 0) optionB = args.shift(); else optionB = null; // Continue as normal: Check if there is an error if (err) {return callback && callback(err); } // Print the optional argument console.log('optionA:', optionA); console.log('optionB:', optionB); console.log('callback:', callback); } // ES6 function example(...) Const err = args. Shift (); const err = args. Const callback = (typeof args[args.length-1] === 'function')? args.pop() : null; Const optionA = (args. Length > 0)? Const optionA = (args. Length > 0)? args.shift() : null; const optionB = (args.length > 0) ? args.shift() : null; / /... If (err && callback) return callback(err); /* The logic you want to do */}Copy the code

13. Use TAP for quick debug

Here tap is a function that can be used for quick debugging, chain calls, anonymous functions, and printing anything you want.

function tap(x) {
    console.log(x);
    return x;
}
Copy the code

Why don’t we use console.log the old way? Let me make a pseudo-code:

bankTotalsByClient(bankInfo(1, banks), table)
            .filter(c => c.balance > 25000)
            .sort((c1, c2) => c1.balance <= c2.balance ? 1 : -1 )
            .map(c =>
                 console.log(`${c.id} | ${c.taxNumber} (${c.name}) => ${c.balance}`));
Copy the code

Now, add that you don’t get anything back from this chain call. Where is the problem? Maybe bank_info doesn’t return anything, we need to tap it:

bankTotalsByClient(tap(bankInfo(1, banks)), table)
Copy the code

Depending on our particular implementation, it might print something, or it might print nothing. We assume that the printout is correct and, therefore, bankInfo is fine.

We need to continue debugging the next function, filter.

 .filter(c => tap(c).balance > 25000)
Copy the code

Can we get c? If yes, the bankTotalsByClient is running properly. Could there be a problem with the conditions in the filter?

.filter(c => tap(c.balance > 25000))
Copy the code

We found nothing printed except false, so no client > 25000, which is why the method returns nothing.

(Attached) more advanced TAP

function tap(x, fn = x => x) {
    console.log(fn(x));
    return x;
}
Copy the code

Let’s take a look at a more powerful one. What if we want to do something before tapping? For example, we only want to position specific parameters of an object, be in a logical operation, and so on. Using the method above, add an extra argument to the call and the function will be executed when tapped.

tap(3, x => x + 2) === 3; / / 5Copy the code

14. Reverse the string

const reverseString = string => [...string].reverse().join('') reverseString('Hello Word! ') / /! "" dlroW olleH"Copy the code

15. Return the sum of any numbers

const sumNumbers = (... arr) => [...arr].reduce((accumulator, currentValue) => accumulator + currentValue, 0) sumNumbers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 55 sumNumbers(... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 55 sumNumbers(1) // 1Copy the code

16. Anti-shake and throttle

When performing operations such as window resize, scroll, input box content verification, or search, if the event processing function is called with an unlimited frequency, the burden on the browser will be increased and the user experience will be very bad. Debounce and throttle can be used to reduce the frequency of calls without compromising performance.

Function debounce

When an event is continuously triggered and no event is triggered again within a certain period of time, the event handler will execute once. If the event is triggered again before the specified time, the delay will start again. For example, if the Scroll event is continuously triggered, the Handle function is not executed. The Scroll event is delayed only when the scroll event is not triggered within 1000 milliseconds.

// function debounce(fn, delay) { let timeout = null return function () { if (timeout) { clearTimeout(timeout) } timeout = setTimeout(fn, Function handle() {console.log(math.random ())} window.addeventListener ('scroll', debounce(handle, 1000))Copy the code

When the Scroll event is continuously triggered, the event handler handle will only be called once after scroll stops for 1000 milliseconds. In other words, the event handler Handle has not been executed during the continuous triggering of the Scroll event.

Function throttle

Ensure that event handlers are called only once in a certain period of time when events are continuously raised. Throttling popular explanation is like our tap water, as soon as the valve is opened, the water pouring down, holding the good traditional virtue of thrift, we have to turn the tap down a little, it is best to be as we will according to a certain rule in a certain time interval drop by drop. For example, if the Scroll event is continuously triggered, the Handle function is not executed immediately, but only once every 1000 milliseconds.

Function throttling can be implemented in two main ways: timestamp and timer. Throttle is then implemented in two separate ways

Throttle code (timestamp) :

// function throttle(fn, delay) { let prev = Date.now() return function() { let _this = this let args = arguments let now = Date.now() if (now - prev >= delay) { fn.apply(_this, Function handle() {console.log(math.random ()); } window.addEventListener('scroll', throttle(handle, 1000));Copy the code

When a high frequency event is triggered, it will execute immediately for the first time (the interval between the scroll event binding function and the actual triggering event is generally greater than delay, if you must scroll the web page within 1000 milliseconds of loading, I can’t help it, and then no matter how frequently the event is triggered, it will execute once every delay time. The event will not be executed after the last event is triggered. (The interval between the last event and the penultimate event is less than delay. Why is it less than delay? Because greater than that is not high frequency.

Throttle code:

function throttle(fn, delay) { let timeout = null return function() { let _this = this let args = arguments if (! timeout) { timeout = setTimeout(function(){ fn.apply(_this, Function handle() {console.log(math.random ())} function handle() {console.log(math.random ())} window.addEventListener('scroll', throttle(handle, 1000));Copy the code

When the event is triggered, we set a timer. When the event is triggered again, if the timer exists, it is not executed until after the delay time, the timer executes the execution function and clears the timer, so that the next timer can be set. When the event is first raised, the function is not executed immediately, but after a delay of seconds. No matter how frequently events are triggered, they are executed only once per delay time. After the last stop, the function may be executed again due to timer delay.

Throttling with a timestamp or timer is ok. More precisely, you can use a timestamp + timer to execute an event handler immediately after the first event is fired and again after the last event is fired.

Throttle code (timestamp + timer) :

function throttle(fn, delay) { let timeout = null let startTime = Date.now() return function() { let _this = this let args = arguments let curTime = Date.now() clearTimeout(timeout) let remaining = delay - (curTime - startTime) if (remaining <= 0) { fn.apply(_this, args) startTime = Date.now() } else { timeout = setTimeout(fn, Function handle() {console.log(math.random ()); function handle() {console.log(math.random ()); } window.addEventListener('scroll', throttle(handle, 1000));Copy the code

Inside the throttling function, startTime, curTime and delay are used to calculate remaining time. Remaining <=0 indicates that the remaining event handler is executed, which ensures that the event handler can be executed immediately after the first triggering event and every delay time. If the remaining time is not reached, the remaining time is set to the remaining time. This ensures that the remaining event handler can be executed after the last event. Of course, if remaining fires another event, the timer is cancelled and a remaining is recalculated to determine the current state.

conclusion

Function stabilization: Combine several operations into one operation. The idea is to maintain a timer that fires after the delay, but if it fires again within the delay, the timer will be cancelled and reset. In this way, only the last operation can be triggered.

Function throttling: causes functions to fire only once in a given period of time. The principle is to trigger the function by judging whether a certain time has been reached.

Difference: Function throttling guarantees that a true event handler will be executed within a specified period of time, no matter how frequently the event is triggered, whereas function buffering only fires once after the last event. For example, in an infinite load scenario, we want the user to make Ajax requests every once in a while while the page is scrolling, rather than asking for data when the user stops scrolling. This scenario is suitable for throttling technology to achieve.

Part of this video is excerpted from github.com/loverajoel/…