This article is to see the “JS advanced programming” chapter 23 “advanced skills” do reading share. In accordance with the ideas in the book according to their own understanding and experience, to expand the extension, at the same time to point out some problems in the book. Safe type detection, lazy loading functions, frozen objects, timers, and more will be discussed.

1. Type detection of security

The problem is how to safely detect the type of a variable, such as whether a variable is an array. The usual way to do this is to use instanceof, as shown in the following code:

let data = [1.2.3];
console.log(data instanceof Array); //trueCopy the code

However, the above judgment will fail under certain conditions — namely, when determining a parent window variable within an iframe. To verify this, write a demo as follows:

<script>
    window.global = {
        arrayData: [1.2.3]}console.log("parent arrayData installof Array: " + 
          (window.global.arrayData instanceof Array));
</script>
<iframe src="iframe.html"></iframe>Copy the code

Determine the variable type of the parent window in iframe.html:

<script>
    console.log("iframe window.parent.global.arrayData instanceof Array: " + 
        (window.parent.global.arrayData instanceof Array));
</script>
Copy the code

Using window.parent in iframe to get the global window object of the parent window, whether it is cross-domain or not, can then get the variables of the parent window, and then use instanceof to determine. The final running results are as follows:

You can see that the parent window is correct and the child window is false, so a variable is Array, but it’s not Array. Why is that? Since this is a parent window problem, try changing Array to the parent window’s Array, window.parent.Array, as shown below:

This time returns true, and then transforms the other judgments, such as the one above, to know that the root cause is the last one above:

Array ! == window.parent.Array

They are two functions, the parent window, defines a child window and defines a memory address, the memory address is not the same as the Object of this equation to judge is not established, and the window. The parent. ArrayData. Constructor returns the parent window of Array, more time is in the child window, The Array of the sub-window is used, and the two arrays are not equal, so the judgment is not valid.

So what to do?

Since you cannot use the memory address of an Object, you can use the string method. Because strings are basic types, string comparison is fine as long as each character is equal. ES5 provides such a method Object. The prototype. ToString, we first started, try different variables return values:

As you can see, if an Array returns “[object Array]”, ES5 specifies this function:

This means that the function returns a value starting with “[object”, followed by the name of the variable type and a close parenthesis. Therefore, since it is a standard syntax specification, this function can be used to safely determine whether a variable is an array.

It could be written like this:

Object.prototype.toString.call([1.2.3= = =])"[object Array]"Copy the code

Note that call is used instead of calling directly. The first argument to call is context, and you pass the array to it as the execution context.

ES6 class also returns function:

Function and class are essentially the same, but the way they are written is different.

Does that mean you can no longer use instanceof to determine the type of a variable? No, this method is used when you need to check the variable types of the parent page. Variables on this page can still be determined using instanceof or constructor methods, as long as you ensure that the variable does not cross the page. Since most people rarely know how to write iframe code, there’s no need to go the extra mile and just keep it simple.

2. Lazy loading function

Sometimes it is necessary to make some compatibility judgments in the code, or make some UA judgments, as shown in the following code:

/ / the type of UA
getUAType: function() {
    let ua = window.navigator.userAgent;
    if (ua.match(/renren/i)) {
        return 0;
    }
    else if (ua.match(/MicroMessenger/i)) {
        return 1;
    }
    else if (ua.match(/weibo/i)) {
        return 2;
    }
    return - 1;
}Copy the code

The purpose of this function is to determine the environment in which the user opened the page, so that the statistics of which channel is better.

One of the characteristics of this type of judgment is that the result is dead, no matter how many times the judgment is executed, the same result will be returned, for example, the user’s UA is not likely to change on the page (except for debugging Settings). So, for optimization purposes, we have lazy functions. The above code can be changed to:

/ / the type of UA
getUAType: function() {
    let ua = window.navigator.userAgent;
    if(ua.match(/renren/i)) {
        pageData.getUAType = (a)= > 0;
        return 0;
    }
    else if(ua.match(/MicroMessenger/i)) {
        pageData.getUAType = (a)= > 1;
        return 1;
    }
    else if(ua.match(/weibo/i)) {
        pageData.getUAType = (a)= > 2;
        return 2;
    }
    return - 1;
}
Copy the code

After each check, the getUAType function is reassigned to a new function that returns an identified variable so that subsequent fetches do not need to be checked. This is what lazy functions do. You might say how much time can these judgments optimize, and it makes almost no difference to the user. This is true, but as an aspiring coder, you will try to optimize your code as much as possible, not just for the completion of requirements. And when you add up these optimizations to a quantity, there’s a qualitative change. When I was in college, my C++ teacher gave an example and asked her to have a look at a system that was slow. One of the optimizations she made was to change the double precision of decimals into single precision, which was much faster in the end.

We have a simpler implementation of the above example, which is to create a variable and store it:

let ua = window.navigator.userAgent;
let UAType = ua.match(/renren/i)?0 :
                ua.match(/MicroMessenger/i)?1 :
                ua.match(/weibo/i)?2 : - 1;Copy the code

The disadvantage of not even writing a function is that a judgment is performed even if the variable UAType is not used, but we think the probability of this variable being used is very high.

Here’s another useful example: Since Safari’s traceless browsing disables local storage, we need to do a compatibility check:

Data.localStorageEnabled = true;
// Traceless browsing in Safari disables localStorage
try{
    window.localStorage.trySetData = 1;
} catch(e) {
    Data.localStorageEnabled = false;
}

setLocalData: function(key, value) { 
    if (Data.localStorageEnabled) {
        window.localStorage[key] = value;
    }
    else {   
        util.setCookie("_L_" + key, value, 1000); }}Copy the code

When setting up local data, you need to determine whether localStorage is supported. If so, use localStorage, otherwise use cookies. This can be modified with an inert function:

setLocalData: function(key, value) {
    if(Data.localStorageEnabled) {
        util.setLocalData = function(key, value){
            return window.localStorage[key]; }}else {
        util.setLocalData = function(key, value){
            return util.getCookie("_L_"+ key); }}return util.setLocalData(key, value);
}
Copy the code

It’s possible to eliminate an if/else judgment, but it doesn’t seem particularly economical, since the idea of an inert function is introduced in order to eliminate an if/else judgment, so you might want to weigh whether it’s worth it to have three or five judgments.

3. Function binding

When a function is passed as an argument to another function, the execution context of the function changes, as shown in the following code:

class DrawTool {
    constructor() {
        this.points = [];
    }
    handleMouseClick(event) {
        this.points.push(event.latLng);
    }
    init() {
        $map.on('click'.this.handleMouseClick); }}Copy the code

The click callback does not refer to an instance of DrawTool, so this.points will return undefined. The first solution is to use closures that cache this as that:

class DrawTool {
    constructor() {
        this.points = [];
    }
    handleMouseClick(event) {
        this.points.push(event.latLng);
    }
    init() {
        let that = this;
        $map.on('click', event => that.handleMouseClick(event)); }}Copy the code

Since the callback function is executed with that, and that is a real example pointing to the DrawTool, there is no problem. Instead if it doesn’t have that it uses this, so it depends where this points.

Since we’re using the arrow function, and the arrow function’s this refers to the parent context, we don’t need to create a closure ourselves, we can just use this:

init() {
    $map.on('click', 
            event => this.handleMouseClick(event));
}Copy the code

The second method is to bind using ES5’s bind function as follows:

init() {
    $map.on('click'.this.handleMouseClick.bind(this));
}Copy the code

This bind may seem like a magic trick, but it actually takes just one line of code to implement a bind function:

Function.prototype.bind = function(context) {
    return (a)= > this.call(context);
}Copy the code

Return a function whose this refers to the original function, and call(context) to bind to the execution context.

4. Mr Currie

Currization is the combination of function and parameter values to produce a new function, as shown in the following code:

function add(a, b) {
    return a + b;
}

let add1 = add.curry(1);
console.log(add1(5)); / / 6
console.log(add1(2)); / / 3Copy the code

How do you implement such a curry function? The key is to return a function that has some closure variables that record the default arguments at the time of creation. Then, when executing the return function, the newly passed arguments and the default arguments are combined into a complete argument list to call the original function, so the following code is generated:

Function.prototype.curry = function() {
    let defaultArgs = arguments;
    let that = this;
    return function() {
        return that.apply(this, 
                          defaultArgs.concat(arguments));    }};
Copy the code

Array.prototype.slice: array.prototype.slice: array.prototype.slice: array.prototype.slice: array.prototype.slice: array.prototype.slice:

Function.prototype.curry = function() {
    let slice = Array.prototype.slice;
    let defaultArgs = slice.call(arguments);
    let that = this;
    return function() {
        return that.apply(this, 
                          defaultArgs.concat(slice.call(arguments)));    }};
Copy the code

Here’s a useful example of currization. When you want to sort an array in descending order, you write:

let data = [1.5.2.3.10];
data.sort((a, b) = > b - a); // [10, 5, 3, 2, 1]Copy the code

Pass a function argument to sort, but if you have a lot of descending operations, writing one function argument at a time is a bit annoying, so you can use currization to solidify the argument:

Array.prototype.sortDescending = 
                 Array.prototype.sort.curry((a, b) = > b - a);
Copy the code

This is much more convenient:

let data = [1.5.2.3.10];
data.sortDescending();

console.log(data); // [10, 5, 3, 2, 1]Copy the code

5. Prevent tampering with objects

Sometimes you may be afraid that your object will be altered by mistake, so you need to protect it.

(1) — object.seal prevents adding and deleting attributes

When sealing an object, attributes cannot be added or removed:

Exceptions will be thrown when using strict mode:

(2) Object.freeze Freezes objects

The value of this attribute cannot be changed, as shown below:

You can also use object. isFrozen, Object.isSealed, and Object.isExtensible to determine the status of the current Object.

DefineProperty Freezes a single attribute

Set Enumable /writable to false, as shown below, and this property will not be traversable or writable:

6. The timer

How to implement a JS version of the sleep function? Sleep functions are available in C/C++/Java, but not in JS. The sleep function puts the thread to sleep and wakes it up after a specified time. You can’t write a while loop and constantly determine if the difference between the current time and the start time is the specified time, because that would take up CPU and not sleep.

This implementation is relatively simple, we can use setTimeout + callback:

function sleep(millionSeconds, callback) {
    setTimeout(callback, millionSeconds);
}
/ / sleep for 2 seconds
sleep(2000, () = >console.log("sleep recover"));Copy the code

But instead of writing my code like a waterfall, I have to get a callback function to pass values as arguments. So I thought of Promise, now USE Promise to rewrite:

function sleep(millionSeconds) {
    return new Promise(resolve= > 
                             setTimeout(resolve, millionSeconds));
}
sleep(2000).then((a)= > console.log("sleep recover"));Copy the code

But there seems to be no solution to the above problem, still need to pass a function argument.

While using promises is essentially the same, it has a resolve parameter that allows you to tell it when the asynchrony ends and then it can execute the THEN, especially if callbacks are more complex.

ES7 has added two new properties async/await to handle asynchronous cases, making asynchronous code write like synchronous code, like async version of sleep:

function sleep(millionSeconds) {
    return new Promise(resolve= > 
                           setTimeout(resolve, millionSeconds));
}

async function init() {
    await sleep(2000);
    console.log("sleep recover");
}

init();Copy the code

Sleep’s implementation remains the same as the simple Promise version. But before the call to sleep, add an await, so that the following code will not be executed until the sleep asynchron completes. We also need to wrap the code logic in an async function, which returns a Promise object that can be used when all asynchrons are finished:

init().then((a)= > console.log("init finished"));Copy the code

ES7’s new properties make our code simpler and more elegant.

Another important topic about timers is the difference between setTimeout and setInterval. As shown below:

SetTimeout starts the timer when the current execution unit is complete, while setInterval starts the timer immediately after the timer is set. You can use a practical example to illustrate, this example I mentioned in the article “JS and Multithreading”, here is a code to actually run, as shown in the following code:

let scriptBegin = Date.now();
fun1();
fun2();

// A 20ms unit of work needs to be executed
function act(functionName) {
    console.log(functionName, Date.now() - scriptBegin);
    let begin = Date.now();
    while(Date.now() - begin < 20);
}
function fun1() {
    let fun3 = (a)= > act("fun3");
    setTimeout(fun3, 0);
    act("fun1");
}
function fun2() {
    act("fun2 - 1");
    var fun4 = (a)= > act("fun4");
    setInterval(fun4, 20);
    act("fun2 - 2");
}
Copy the code

The execution model for this code looks like this:

Console output:

Consistent with the above model analysis.

We move on to the last topic, function throttling

Function throttling

The purpose of throttling is not to trigger execution too quickly, for example:

  • — Listen for input to trigger a search
  • — Listen to resize for responsive adjustment
  • — Listen for Mousemove to adjust position

To see how many times the resize/mousemove event can be triggered in 1s, we wrote the following driver code:

let begin = 0;
let count = 0;
window.onresize = function() {
    count++;
    let now = Date.now();
    if(! begin) { begin = now;return;
    }
    if((now - begin) % 3000 < 60) {
        console.log(now - begin,
           count / (now - begin) * 1000); }};Copy the code

When the window is pulled fast, the resize event is triggered about 40 times for 1s:

It’s important to note that the faster you pull, the faster it will trigger. In fact, the faster you pull, the slower it will trigger, because the page has to be redrawn when you pull, and the faster it changes, the more it has to be redrawn, so it will trigger less.

The Mousemove event on my computer’s Chrome fires about 60 times for 1s:

If you need to listen for the REsize event to make DOM adjustments, this adjustment can be time-consuming. It takes 40 adjustments in 1s, so you may not be able to respond, and you don’t need to adjust so often, so you need to throttle back.

How do you implement a throttling? Here’s how:

function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId = setTimeout(function() {
        method.call(context);
    }, 100);
}Copy the code

You have to setTimeout every time you do it, and if you do it too quickly you clear the previous setTimeout and reset the setTimeout, so it’s not going to execute too fast. The problem with this is that the callback may never execute because it keeps firing, it keeps clearing tids, which is a little awkward, because the code above is supposed to fire at most once in 100ms, but it may never execute. This implementation should be called anti-shock, not throttling.

Modify the above code slightly:

function throttle(method, context) {
    if (method.tId) {
        return;
    }
    method.tId = setTimeout(function() {
        method.call(context);
        method.tId = 0;
    }, 100);
}
Copy the code

This implementation is correct, executing at most one callback every 100ms, by setting tId to 0 in setTimeout to allow the next trigger to execute. Try it out:

Roughly every 100ms, that’s what we’re doing.

There is a slight problem, however, that each execution is delayed by 100ms. Sometimes the user may maximize the window and only fire the resize event once, but this time it is still delayed by 100ms. If your time is 500ms, it is delayed by half a second, so this implementation is not ideal.

Need to optimize, as shown in the following code:

function throttle(method, context) {
    // If it is the first time, execute it immediately
    if (typeof method.tId === "undefined") {
        method.call(context);
    }
    if (method.tId) {
        return;
    }
    method.tId = setTimeout(function() {
        method.call(context);
        method.tId = 0;
    }, 100);
}Copy the code

Check whether it is the first trigger. If so, execute it immediately. This solves the problem mentioned above, but the implementation is still problematic because it is only the global first time. After the user maximizes, the user cancels the maximization after a while and then there is a delay again, and the first trigger is executed twice. So what to do?

The author has an idea:

function throttle(method, context) {
    if(! method.tId) { method.call(context); method.tId =1;
        setTimeout((a)= > method.tId = 0.100); }}Copy the code

Execute immediately each time it triggers, then set a timer and set tId to 0.

This implementation is even cleaner than the previous one and can address latency issues.

— so by throttling, the number of executions is reduced to 1s and 10 executions. The throttling time can also be controlled, but at the same time, the sensitivity is lost. If you need high sensitivity, you should not use throttling, such as doing a drag and drop application. What happens if drag is throttled? Users will find themselves dragging up card by card.


The advanced techniques of the author to see the elevation of sections based on their own understanding and practice summarizes this article, my experience is that if the reading blogs are just as a bedtime reading to take a look at harvest is not very big, actually did not actually put the code of practice in the book, no combined with own coding experience, cannot understand to blend in with their own this knowledge, And turn it into your own knowledge. You may say I read after the impression ah, impression is good, but you spent so much time to read the book only to get an impression, you have not practiced the impression, this impression is how reliable. If someone asks you about this impression, you may answer with pieces that don’t connect, giving the impression of an endorsement. And sometimes there may be something wrong or out of date in the book. The truth is only in practice.