[Interview] The Native JS You Must Know in Winter Job Hunting Season (Part 2)

  Front end, javascript, node.js, Programmer

In the cold winter of the Internet, major companies have reduced HC and even adopted “layoff” measures. Under such a big environment, it is necessary to make more efforts to get a better job.

A year ago, perhaps you could get approval by figuring out closure, this, prototype chain. But now, obviously not. This article sorts out some high-frequency native JS problems that have certain difficulties in interviews. Some knowledge points may have never been noticed before, or you may have seen them but have not studied them carefully, but they are very important. This article will present knowledge points in the form of real face-to-face questions. When reading, we suggest not to look at my answers first, but to think about them first. Although, all the answers in this article were given only after I read all kinds of materials, thought and verified (It is by no means copied and pasted.)。 However, due to limited level, my answer is not necessarily the best. If you have a better answer, please leave me a message.

This article is long, but it is full of dry goods! He also ambushed a cute facial expression bag, hoping his friends could read it.

The little sister who wrote the article sincerely wishes everyone to find the right job.

If you have not read the previous article [the previous article and the novella are not dependent, you can read the previous article after reading this article], you can stampThe native JS you must understand during the winter job-hunting season (part one)

The little sister spent nearly 100 hours to finish this article, which is long in length. I hope you will spend more patience in reading and try to really master relevant knowledge points.

1. Say something about JS asynchronous development history

The earliest solution to asynchrony is callback function, such as callback of event and callback in setInterval/setTimeout. However, there is a common problem with callback functions, which is the problem of callback to hell (which will be illustrated later).

In order to solve the problem of returning to hell, the community has proposed a Promise solution, which ES6 has written into the language standard. Promise solves the problem of callback to hell, but Promise also has some problems, such as errors can’t be try catch, and using Promise’s chain call does not fundamentally solve the problem of callback to hell, but it is written in a different way.

Generator function is introduced into ES6. Generator is an asynchronous programming solution. Generator function is the implementation of synergetics in ES6. Its greatest feature is that it can hand over the execution right of the function. Generator function can be seen as the container of asynchronous tasks. Any place that needs to be suspended is indicated by yield statement. But Generator is more complicated to use.

ES7 also proposes a new asynchronous solution: async/aware, async is the syntax sugar of Generator function, async/aware makes asynchronous code look like synchronous code, and the development goal of asynchronous programming is to make asynchronous logic code look like synchronous.

1. callback function: callback

//node读取文件
fs.readFile(xxx, 'utf-8', function(err, data) {
    //code
});

Use scenario of callback function (including but not limited to):

  1. Event callback
  2. Node API
  3. Callback function in setTimeout/setInterval

Asynchronous callback nesting will make the code difficult to maintain, and it is not convenient to handle errors in a unified way, and cannot try catch and callback hell (e.g. read the content of a text first, then read b according to the content of a text, and then read c … according to the content of b).

fs.readFile(A, 'utf-8', function(err, data) {
    fs.readFile(B, 'utf-8', function(err, data) {
        fs.readFile(C, 'utf-8', function(err, data) {
            fs.readFile(D, 'utf-8', function(err, data) {
                //....
            });
        });
    });
});

2.Promise

Promise mainly solves the problem of callback to hell. Promise was first proposed and implemented by the community. ES6 incorporated it into the language standard, unified the usage, and provided the Promise object in the original.

Then let’s look at how Promise solves the problem of callback to hell, still taking readFile above as an example.

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}
read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

To run the code to see the effect, please stamp (the younger sister uses VS’s Code Runner to execute the code):https://github.com/YvetteLau/ …

Think about how you handled asynchronous concurrency problems before Promise, assuming that there is a requirement to read the contents of three files and output the final result after reading successfully. After having Promise, how to deal with it? Code stamp:https://github.com/YvetteLau/ …

Note: you can use bluebird to promise the interface;

Extension:What are the advantages and problems of Promise?

<! -Advantages:

  1. Once the state changes, it will not change again, and this result can be obtained at any time.
  2. Asynchronous operations can be expressed in the flow of synchronous operations, thus avoiding callback functions nested layer by layer.

Disadvantages:

  1. Promise cannot be cancelled
  2. When in the pending state, it is impossible to know where the current progress is.
  3. Error cannot be trycatch->

3.Generator

Generator function is an asynchronous programming solution provided by ES6. The whole Generator function is an encapsulated asynchronous task, or a container of asynchronous tasks. Where asynchronous operation needs to be paused, it is indicated with yield statement.

The Generator function is generally used with yield or Promise. The Generator function returns an iterator. For students who don’t know about generators and iterators, please make up your own foundation. Let’s look at the simple use of Generator:

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,传递的参数无效
t.next(2); //a输出2;
t.next(3); //b输出3; 
t.next(4); //c输出4;
t.next(5); //d输出5;

In order to make everyone better understand how the above code is executed, I drew a diagram, corresponding to each next method call:

Still taking readFile above as an example, use the Generator+co library to implement:

const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);

function* read() {
    yield readFile(A, 'utf-8');
    yield readFile(B, 'utf-8');
    yield readFile(C, 'utf-8');
    //....
}
co(read()).then(data => {
    //code
}).catch(err => {
    //code
});

How to implement without co library? Can you write the simplest my_co by yourself? Please stamp:https://github.com/YvetteLau/ …

PS: If you are not familiar with Generator/yield, it is recommended to read ES6 related documents.

4.async/await

Async/await concept is introduced in ES7. Async is actually a syntax sugar. Its implementation is to wrap the Generator function and the Automatic Executor (co) in a function.

Async/await has the advantage of clear code and can handle the problem of callback to hell without writing many then chains like Promise. Errors can be try catch.

Still taking readFile above as an example, use the Generator+co library to implement:

const fs = require('fs');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);


async function read() {
    await readFile(A, 'utf-8');
    await readFile(B, 'utf-8');
    await readFile(C, 'utf-8');
    //code
}

read().then((data) => {
    //code
}).catch(err => {
    //code
});

Executable code, please stamp:https://github.com/YvetteLau/ …

Think about how async/await handles asynchronous concurrency issues.https://github.com/YvetteLau/ …

If you have a better answer or idea, please leave a message under github corresponding to this topic:Let’s talk about the history of JS asynchronous development.


2. Talk about the understanding of async/await. What is the implementation principle of async/await?

Async/await is the grammar sugar of Generator, which makes asynchronous operation more convenient. Let’s compare the pictures:

The async function is to replace the asterisk (*) of the Generator function with async and the yield with await.

We say async is the grammar sugar of Generator, so where is this sugar sweet?

1) The 1)async function has a built-in actuator. After the function is called, it will automatically execute and output the final result. The Generator needs to call next or cooperate with the co module.

2) Better semantics, async and await, are clearer than asterisks and yield. Async indicates that there are asynchronous operations in the function, await indicates that the expression immediately following needs to wait for the result.

3) Wider applicability. The co module stipulates that the yield command can only be followed by a Thunk function or a Promise object, while the await command of the async function can be followed by a Promise object and a value of the original type.

4) The return value is Promise, the return value of async function is Promise object, and the return value of Generator is Iterator. The Promise object is more convenient to use.

The implementation principle of async function is to package the Generator function and the automatic executor in one function.

The specific code is as follows (slightly different from the implementation of spawn, which I think is easier to understand). If you want to know how to write my_co step by step, you can stamp it:https://github.com/YvetteLau/ …

function my_co(it) {
    return new Promise((resolve, reject) => {
        function next(data) {
            try {
                var { value, done } = it.next(data);
            }catch(e){
                return reject(e);
            }
            if (!done) { 
                //done为true,表示迭代完成
                //value 不一定是 Promise,可能是一个普通值。使用 Promise.resolve 进行包装。
                Promise.resolve(value).then(val => {
                    next(val);
                }, reject);
            } else {
                resolve(value);
            }
        }
        next(); //执行一次next
    });
}
function* test() {
    yield new Promise((resolve, reject) => {
        setTimeout(resolve, 100);
    });
    yield new Promise((resolve, reject) => {
        // throw Error(1);
        resolve(10)
    });
    yield 10;
    return 1000;
}

my_co(test()).then(data => {
    console.log(data); //输出1000
}).catch((err) => {
    console.log('err: ', err);
});

If you have a better answer or idea, please leave a message under github corresponding to this topic:Talking about the understanding of async/await, what is the implementation principle of async/await?


3. What should I pay attention to when using async/await?

  1. The run result of the Promise object following the await command may be rejected, which is equivalent to the Promise object returned by the async function being Rejected. Therefore, it is necessary to add error handling, which can add catch method to each await Promise. You can also put await’s code intry...catchChina.
  2. Asynchronous operations following multiple await commands are best triggered simultaneously if there is no secondary relationship.
//下面两种写法都可以同时触发
//法一
async function f1() {
    await Promise.all([
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        }),
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        })
    ])
}
//法二
async function f2() {
    let fn1 = new Promise((resolve) => {
            setTimeout(resolve, 800);
        });
    
    let fn2 = new Promise((resolve) => {
            setTimeout(resolve, 800);
        })
    await fn1;
    await fn2;
}
  1. Await command can only be used in async function. If it is used in ordinary function, an error will be reported.
  2. Async function can keep running stack.
/**
* 函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。
* 等到b()运行结束,可能a()早就* 运行结束了,b()所在的上下文环境已经消失了。
* 如果b()或c()报错,错误堆栈将不包括a()。
*/
function b() {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 200)
    });
}
function c() {
    throw Error(10);
}
const a = () => {
    b().then(() => c());
};
a();
/**
* 改成async函数
*/
const m = async () => {
    await b();
    c();
};
m();

The error message is as follows. It can be seen that the async function can keep the running stack.

If you have a better answer or idea, please leave a message under github corresponding to this topic:What should I pay attention to when using async/await?


How to realize Promise.race?

Before implementing the code, we need to understand the characteristics of Promise.race:

  1. Race returned a Promise.

Its status is the same as that of the first completed Promise. It can be resolves or rejects, depending on which state the first Promise is in.

  1. If the parameter passed in is non-iterative, an error will be thrown.
  2. If the passed parameter array is empty, the returned promise will wait forever.
  3. If the iteration contains one or more non-commitment values and/or resolved/rejected commitments, Promise.race will resolve to the first value found in the iteration.
Promise.race = function (promises) {
    //promises 必须是一个可遍历的数据结构,否则抛错
    return new Promise((resolve, reject) => {
        if (typeof promises[Symbol.iterator] !== 'function') {
            //真实不是这个错误
            Promise.reject('args is not iteratable!');
        }
        if (promises.length === 0) {
            return;
        } else {
            for (let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then((data) => {
                    resolve(data);
                    return;
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

Test code:

//一直在等待态
Promise.race([]).then((data) => {
    console.log('success ', data);
}, (err) => {
    console.log('err ', err);
});
//抛错
Promise.race().then((data) => {
    console.log('success ', data);
}, (err) => {
    console.log('err ', err);
});
Promise.race([
    new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
    new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }),
    new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
]).then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
});

Extension:The implementation principle of promise.all/promise.reject/promise.resolve/promise.prototype.finally/promise.prototype.catch, if not, stamp:Promise source code implementation

If you have a better answer or idea, please leave a message under github corresponding to this topic:How to realize Promise.race?


5. What are the characteristics of traversable data structures?

If an object wants to have an Iterator interface that can be called by the for…of loop, it must deploy the traverser generation method on its Symbol.iterator attribute (or the object on the prototype chain has this method)

PS:The basic feature of the traverser object is to have the next method. Each time the next method is called, an information object representing the current member is returned, with two attributes: value and done.

//如为对象添加Iterator 接口;
let obj = {
    name: "Yvette",
    age: 18,
    job: 'engineer',
    [Symbol.iterator]() {
        const self = this;
        const keys = Object.keys(self);
        let index = 0;
        return {
            next() {
                if (index < keys.length) {
                    return {
                        value: self[keys[index++]],
                        done: false
                    };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
};

for(let item of obj) {
    console.log(item); //Yvette  18  engineer
}

Use the Generator function (traverser object generation function)-Symbol.iterator method, which can be simplified as follows:

let obj = {
    name: "Yvette",
    age: 18,
    job: 'engineer',
    * [Symbol.iterator] () {
        const self = this;
        const keys = Object.keys(self);
        for (let index = 0;index < keys.length; index++) {
            yield self[keys[index]];//yield表达式仅能使用在 Generator 函数中
        } 
    }
};

The native data structure with Iterator interface is as follows.

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • Arguments object for function
  • NodeList object
  • The array, Set, and Map of ES6 all deploy the following three methods: entries()/keys()/values (), which return the traverser object after calling.

If you have a better answer or idea, please leave a message under github corresponding to this topic:What are the characteristics of traversable data structures?


6. What is the difference between 6.requestAnimationFrame and setTimeout/setInterval? What are the benefits of using requestAnimationFrame?

Before requestAnimationFrame, we mainly used setTimeout/setInterval to write JS animation.

The key to writing animation is to set the cycle interval. On the one hand, the cycle interval is short enough for the animation effect to appear smooth and smooth. On the other hand, the cycle interval should be long enough to ensure the browser has the ability to render the changes.

The refresh frequency of most computer monitors is 60HZ, that is, 60 redraws per second. Most browsers will limit redrawing operations to no more than the redrawing frequency of the display, because the user experience will not improve even if that frequency is exceeded. Therefore, the best cycle interval for the smoothest animation is 1000ms/60, which is about 16.7 ms.

SetTimeout/setInterval has a significant defect in that the time is inaccurate. setTimeout/setInterval can only ensure that the delay or interval is not less than the set time. Because they actually just add tasks to the task queue, but if the previous tasks have not been completed, they must wait.

RequestAnimationFrame only has the system time interval, which keeps the best rendering efficiency and does not cause over-rendering and increase overhead because the interval time is too short. Also, because the interval time is too long, the use of animation Caton is not smooth, so that various web page animation effects can have a unified refresh mechanism, thus saving system resources, improving system performance and improving visual effects.

To sum up, the advantages of requestAnimationFrame and setTimeout/setInterval when writing animation are as follows:

1.requestAnimationFrame does not need to set the time, and the system time interval is adopted to achieve the best animation effect.

2.requestAnimationFrame will gather all DOM operations in each frame and finish in one redraw or reflow.

3. When requestAnimationFrame () is running on the background tab or hidden<iframe>At the same time, requestAnimationFrame () will be suspended to improve performance and battery life (in most browsers).

RequestAnimationFrame Use (Try to use requestAnimationFrame to write a moving ball from A to B):

function step(timestamp) {
    //code...
    window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);

If you have a better answer or idea, please leave a message under github corresponding to this topic:What is the difference between requestAnimationFrame and setTimeout/setInterval? What are the benefits of using requestAnimationFrame?


7. What are the rules for 7.JS type conversion?

The rules of type conversion cannot be explained in a few words. I really want to cry out ~
! [](https://timgsa.baidu.com/timg …
)

Type conversion in JS is divided into forced type conversion and implicit type conversion.

  • Forced type conversion is performed by Number (), parseInt (), parseFloat (), toString (), String (), Boolean ().
  • Logical operators (&&,| |,! ), operators (+,-,*,/), relational operators (>, <, < =, > =), equality operators (= =), or if/while conditions may be implicitly type converted.

Force type conversion

1.Number () converts any type of parameter to a numeric type

The rules are as follows:

  • If it is a boolean value, true and false are converted to 1 and 0 respectively
  • If it is a number, return to itself
  • If null, returns 0
  • If it is undefined, returnNAN
  • If it is a string, follow the following rules:

    1. If the string contains only numbers (or hexadecimal digit string beginning with ` 0X`/`0x', plus or minus sign is allowed), it is converted to decimal
    2. If the string contains a valid floating-point format, convert it to a floating-point value
    3. If it is an empty string, convert it to 0
    4. If the string is not in the above format, it will all return `nan'
  • If it is Symbol, throw an error
  • If is an object, the of the object is calledvalueOf()Method, and then convert the returned value according to the previous rules. If the result of the conversion isNaNThe of the object is calledtoString()Method to convert the returned string value again according to the previous rules.

Some built-in objects call the defaultvalueOfThe behavior of:

Object Return value
Array Array itself (object type)
Boolean Boolean (Original Type)
Date The number of milliseconds elapsed from midnight on January 1, 1970 UTC to the encapsulated date
Function Function itself (object type)
Number Numeric Value (Original Type)
Object The object itself (object type)
String String Value (Original Type)
Number('0111'); //111
Number('0X11') //17
Number(null); //0
Number(''); //0
Number('1a'); //NaN
Number(-0X11);//-17

2.parseInt(param, radix)

If the first parameter passes in a string type:

  1. Ignore the space before the string until the first non-empty character is found. If it is an empty string, return NaN
  2. If the first character is not a numeric symbol or a sign, NaN is returned
  3. If the first character is a number/sign, continue parsing until the string is parsed or a non-numeric symbol is encountered

If the Number type passed in by the first parameter is:

  1. If the number starts with 0, it will be interpreted as octal (if it is an octal number); If it starts with 0x, it is parsed as hexadecimal

If the first argument is null, undefined, or an object type:

  1. Return to NaN

If the first parameter is an array:

1. 去数组的第一个元素,按照上面的规则进行解析

If the first parameter is of Symbol type:

1. 抛出错误

If the radix parameter is specified, it will be resolved based on radix.

parseInt('0111'); //111
parseInt(0111); //八进制数 73
parseInt('');//NaN
parseInt('0X11'); //17
parseInt('1a') //1
parseInt('a1'); //NaN
parseInt(['10aa','aaa']);//10

parseInt([]);//NaN; parseInt(undefined);

parseFloat

Rules andparseIntBasically the same, accept a Number type or string, if it is in a string, then only the first decimal point is valid.

toString()

The rules are as follows:

  • If it is Number type, output a number string
  • If null or undefined, throw wrong
  • If it is an array, expand the array to output. Empty array, return''
  • If it is an object, returns[object Object]
  • If it is Date, returns the literal representation of the date
  • If it is a function, output the corresponding string (demo below)
  • If it is Symbol, the Symbol string is output.
let arry = [];
let obj = {a:1};
let sym = Symbol(100);
let date = new Date();
let fn = function() {console.log('稳住,我们能赢!')}
let str = 'hello world';
console.log([].toString()); // ''
console.log([1, 2, 3, undefined, 5, 6].toString());//1,2,3,,5,6
console.log(arry.toString()); // 1,2,3
console.log(obj.toString()); // [object Object]
console.log(date.toString()); // Sun Apr 21 2019 16:11:39 GMT+0800 (CST)
console.log(fn.toString());// function () {console.log('稳住,我们能赢!')}
console.log(str.toString());// 'hello world'
console.log(sym.toString());// Symbol(100)
console.log(undefined.toString());// 抛错
console.log(null.toString());// 抛错

String()

String()The conversion rules of andtoString()Basically the same, the biggest difference is thatnullAndundefined, using String for conversion, null and undefined correspond to strings'null'And'undefined'

Boolean

Except undefined, null, false,”, 0 (including +0, -0) and NaN converted to false, all others are true.

Implicit type conversion

&& 、|| 、 ! , if/while

Data needs to be converted to Boolean, and the conversion rules are the same as Boolean cast

Operator:+-*/

+The number operator can be used not only for numeric addition but also for string concatenation.

Only if+When numbers are on both sides of the sign, an addition operation is performed. If both sides are strings, splice directly without implicit type conversion.

In addition to the above, if the operand is an object, a numeric value, or a Boolean value, the toString () method is called to obtain a string value (toString conversion rule). For undefined and null, call String () to explicitly convert to string, and then splice.

console.log({}+10); //[object Object]10
console.log([1, 2, 3, undefined, 5, 6] + 10);//1,2,3,,5,610

-*/The operator is for an operation. If one of the operation values is not a numeric value, the Number () function is implicitly called for conversion. If one of the conversions is other than NaN, the result is NaN.

Relationship operators: = =, >, <, < =, > =

>,<,<=,>=

  1. If both operation values are numerical values, numerical comparison is made
  2. If both operation values are strings, the character encoding values corresponding to the strings are compared
  3. If one of the parties is of Symbol type, an error is thrown
  4. Except for the above cases, Number () is used for type conversion and then comparison.

Note:NaN is a very special value. It is not equal to any type of value, including itself, and returns false when it is larger than any type of value.

console.log(10 > {});//返回false.
/**
 *{}.valueOf ---> {}
 *{}.toString() ---> '[object Object]' ---> NaN
 *NaN 和 任何类型比大小,都返回 false
 */

Equal operator:==

  1. If the types are the same, no type conversion is required.
  2. If one of the operation values is null or undefined, then the other operator must be null or undefined to return true, otherwise all return false.
  3. False if one of them is Symbol type.
  4. Whether the two operation values are string and number will convert the string to number
  5. If an operation value is boolean, it is converted to number
  6. If one operation value is object and the other is string, number or symbol, then the object will be converted to the original type and then judged (calling the valueOf/toString method of object to convert)

How does the object convert to the original data type

If the [Symbol.toPrimitive] interface is deployed, this interface is called, and if it does not return the underlying data type, an error is thrown.

If the [Symbol.toPrimitive] interface is not deployed, the valueOf () is returned first. if the value returned is not of the underlying type, then the value of toString () is returned. if the value returned is not of the underlying type, an exception is thrown.

//先调用 valueOf, 后调用 toString
let obj = {
    [Symbol.toPrimitive]() {
        return 200;
    },
    valueOf() {
        return 300;
    },
    toString() {
        return 'Hello';
    }
}
//如果 valueOf 返回的不是基本数据类型,则会调用 toString, 
//如果 toString 返回的也不是基本数据类型,会抛出错误
console.log(obj + 200); //400

If you have a better answer or idea, please leave a message under github corresponding to this topic:What are the rules for JS type conversion?


8. briefly describe the understanding of webWorker?

HTML5 puts forward the Web Worker standard, which indicates that js allows multithreading, but sub-threads are completely controlled by the main thread and cannot operate dom. only the main thread can operate dom, so js is still a single-threaded language in essence.

Web worker starts a sub-thread based on js single-thread execution to process the program without affecting the execution of the main thread. When the sub-thread finishes executing, it returns to the main thread without affecting the execution of the main thread. The sub-thread and the main thread provide data interaction interfaces postMessage and onmessage to send and receive data.

var worker = new Worker('./worker.js'); //创建一个子线程
worker.postMessage('Hello');
worker.onmessage = function (e) {
    console.log(e.data); //Hi
    worker.terminate(); //结束线程
};
//worker.js
onmessage = function (e) {
    console.log(e.data); //Hello
    postMessage("Hi"); //向主进程发送消息
};

This is only the simplest example code. In a project, some code that takes a long time is usually run in sub-threads.

If you have a better answer or idea, please leave a message under github corresponding to this topic:Briefly describe the understanding of webWorker


9. What is the difference between 9.ES6 module and CommonJS module?

  1. When compiling ES6 module, it can determine the dependency of the module and the input and output variables.

    CommonJS module, loaded at runtime.

  2. ES6 module automatically adopts strict mode, regardless of whether the module header is written or not"use strict";
  3. Require can be loaded dynamically, import statement cannot, import statement must be in top-level scope.
  4. This at the top of the ES6 module points to undefined, while this at the top of the ommonJS module points to the current module.
  5. CommonJS module outputs a copy of the value while ES6 module outputs a reference to the value.

CommonJS module outputs a copy of the value, that is, once a value is output, changes within the module will not affect the value. For example:

//name.js
var name = 'William';
setTimeout(() => name = 'Yvette', 200);
module.exports = {
    name
};
//index.js
const name = require('./name');
console.log(name); //William
setTimeout(() => console.log(name), 300); //William

Compare ES6 modules to see:

The operation mechanism of ES6 module is different from CommonJS. When JS engine statically analyzes scripts, it will generate a read-only reference when it encounters module load command import. Wait until the script is actually executed, then according to this read-only reference, go to the loaded module to get the value.

//name.js
var name = 'William';
setTimeout(() => name = 'Yvette', 200);
export { name };
//index.js
import { name } from './name';
console.log(name); //William
setTimeout(() => console.log(name), 300); //Yvette

If you have a better answer or idea, please leave a message under github corresponding to this topic:What is the difference between ES6 module and CommonJS module?


10. What is the principle of browser event proxy mechanism?

Before talking about the principle of browser event proxy mechanism, let’s first understand the concept of event flow. In early browsers, IE used event capture event flow, while Netscape used event capture. “DOM2 level event” divides the event flow into three stages: capture stage, target stage and bubbling stage. Modern browsers also follow this standard.

What is the event agent?

Event proxy is also called event proxy. An event is bound to an ancestor DOM element. When an event of a descendant DOM element is triggered, the event bound to the ancestor DOM is triggered by the principle of event bubbling. Because events bubble from the target element to the document object layer by layer.

Why do you want event proxies?

  1. The number of events added to the page will affect the running performance of the page. If too many events are added, the performance of the page will decrease. Using event proxy can greatly reduce the number of registered events.
  2. At the time of the event proxy, a child sun element was added dynamically, and there was no need to bind it again.
  3. There is no need to worry that a DOM element registered with an event may not be able to recycle its event handler after it is removed. We can avoid this problem by delegating the event handler to a higher level element.

For example, proxy all click events in the page to document:

AddEventListener accepts 3 parameters, which are the name of the event to be processed, the function of the event handler, and a Boolean value. Boolean value defaults to false. Indicates that the event handler is called in the bubbling phase; if set to true, it indicates that the event handler is called in the capturing phase.

document.addEventListener('click', function (e) {
    console.log(e.target);
    /**
    * 捕获阶段调用调用事件处理程序,eventPhase是 1; 
    * 处于目标,eventPhase是2 
    * 冒泡阶段调用事件处理程序,eventPhase是 1;
    */ 
    console.log(e.eventPhase);
    
});

If you have a better answer or idea, please leave a message under github corresponding to this topic:What is the principle of browser event proxy mechanism?


11. How does JS customize events?

Custom DOM Events (regardless of pre-IE9 versions)

There are three ways to customize events, one is to usenew Event(), the other iscreateEvent('CustomEvent'), the other isnew customEvent()

  1. Usenew Event()

Not availableevent.detail

let btn = document.querySelector('#btn');
let ev = new Event('alert', {
    bubbles: true,    //事件是否冒泡;默认值false
    cancelable: true, //事件能否被取消;默认值false
    composed: false
});
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable); //true
    console.log(event.detail); //undefined
}, false);
btn.dispatchEvent(ev);
  1. UsecreateEvent('CustomEvent')(DOM3)

To create a custom event, you can callcreateEvent('CustomEvent'), the returned object has the initCustomEvent method and accepts the following four parameters:

  • Type: string representing the type of event triggered, such as’ alert’
  • Bubbles: Boolean: Indicates whether the event bubbled
  • Cancelable: Boolean value that indicates whether the event can be canceled
  • Detail: Any value stored in the detail property of the event object
let btn = document.querySelector('#btn');
let ev = btn.createEvent('CustomEvent');
ev.initCustomEvent('alert', true, true, 'button');
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable);//true
    console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);
  1. Usenew customEvent()(DOM4)

Compared withcreateEvent('CustomEvent')More convenient

var btn = document.querySelector('#btn');
/*
 * 第一个参数是事件类型
 * 第二个参数是一个对象
 */
var ev = new CustomEvent('alert', {
    bubbles: 'true',
    cancelable: 'true',
    detail: 'button'
});
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable);//true
    console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);

Custom Non-DOM Events (Observer Mode)

The EventTarget type has a separate property handlers for storing event handlers (observers).

AddHandler () is used to register an event handler for a given type of event;

Fire () is used to trigger an event;

RemoveHandler () is used to unregister an event handler of an event type.

function EventTarget(){
    this.handlers = {};
}

EventTarget.prototype = {
    constructor:EventTarget,
    addHandler:function(type,handler){
        if(typeof this.handlers[type] === "undefined"){
            this.handlers[type] = [];
        }
        this.handlers[type].push(handler);
    },
    fire:function(event){
        if(!event.target){
            event.target = this;
        }
        if(this.handlers[event.type] instanceof Array){
            const handlers = this.handlers[event.type];
            handlers.forEach((handler)=>{
                handler(event);
            });
        }
    },
    removeHandler:function(type,handler){
        if(this.handlers[type] instanceof Array){
            const handlers = this.handlers[type];
            for(var i = 0,len = handlers.length; i < len; i++){
                if(handlers[i] === handler){
                    break;
                }
            }
            handlers.splice(i,1);
        }
    }
}
//使用
function handleMessage(event){
    console.log(event.message);
}
//创建一个新对象
var target = new EventTarget();
//添加一个事件处理程序
target.addHandler("message", handleMessage);
//触发事件
target.fire({type:"message", message:"Hi"}); //Hi
//删除事件处理程序
target.removeHandler("message",handleMessage);
//再次触发事件,没有事件处理程序
target.fire({type:"message",message: "Hi"});

If you have a better answer or idea, please leave a message under github corresponding to this topic:How does js customize events?


12. What are the cross-domain approaches? What is the principle?

Before we talk about cross-domain methods, we should first understand what cross-domain is. browsers have a homology policy. only when the “protocol”, “domain name” and “port number” are the same can we call it homology. one difference is cross-domain.

What is the function of homologous strategy? Homologous policies limit how documents or scripts loaded from the same source interact with resources from another source. This is an important security mechanism for isolating potentially malicious files.

Then why do we need to cross-domain? First, the front end and the server are deployed separately. Interface requests need to be cross-domain. Second, we may load pages from other websites as iframe embedded.

What are the cross-domain methods?

Common cross-domain methods

  1. jsonp

Although browsers have homologous policies, however<script>The src attribute of the tag is not constrained by the homologous policy, and scripts on any server can be obtained and executed. Jsonp realizes cross-domain by inserting script tags. Parameters can only be passed in through url and can only support get requests.

Implementation principle:

Step1: Create callback Method

Step2: Insert script Label

Step3: The background receives the request, parses the callback method passed by the front end, returns the call of the method, and the data is passed into the method as a parameter

Step4: The front end executes the method call returned by the service end

The following code only illustrates jsonp principle, please use mature library in the project. Take a look at the simple implementation of the front-end and the service-end respectively:

//前端代码
function jsonp({url, params, cb}) {
    return new Promise((resolve, reject) => {
        //创建script标签
        let script = document.createElement('script');
        //将回调函数挂在 window 上
        window[cb] = function(data) {
            resolve(data);
            //代码执行后,删除插入的script标签
            document.body.removeChild(script);
        }
        //回调函数加在请求地址上
        params = {...params, cb} //wb=b&cb=show
        let arrs = [];
        for(let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
    });
}
//使用
function sayHi(data) {
    console.log(data);
}
jsonp({
    url: 'http://localhost:3000/say',
    params: {
        //code
    },
    cb: 'sayHi'
}).then(data => {
    console.log(data);
});
//express启动一个后台服务
let express = require('express');
let app = express();

app.get('/say', (req, res) => {
    let {cb} = req.query; //获取传来的callback函数名,cb是key
    res.send(`${cb}('Hello!')`);
});
app.listen(3000);

From today on, the principle of jsonp will be clear to the heart ~

  1. cors

Jsonp can only support get requests, cors can support multiple requests. Cors doesn’t need any front-end work.

Simple cross-domain request:

As long as the access-control-allow-origin header set by the server matches the request source, the browser allows cross-domain

1) the requested method is get, head or post.
2) the content-type is a value in application/x-www-form-urlencoded, multipart/form-data or text/plain, or it can be set without setting. the general default is application/x-www-form-urlencoded.
3) There is no customized HTTP header in the request, such as x-token. (should be these kinds of header Accept, Accept-Language, Content-Language, Last-Event-ID, Content-Type)

//简单跨域请求
app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', 'XXXX');
});

Cross-domain request with Preflighted

Those who are not satisfied with simple cross-domain requests are cross-domain requests with preflight. The server needs to set Access-Control-Allow-Origin (fields that allow cross-domain resource requests), Access-Control-Allow-Methods (allowed request methods), and Access-Control-Allow-Headers (allowed request headers)

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', 'XXX');
    res.setHeader('Access-Control-Allow-Headers', 'XXX'); //允许返回的头
    res.setHeader('Access-Control-Allow-Methods', 'XXX');//允许使用put方法请求接口
    res.setHeader('Access-Control-Max-Age', 6); //预检的存活时间
    if(req.method === "OPTIONS") {
        res.end(); //如果method是OPTIONS,不做处理
    }
});

More CORS knowledge can be accessed: [HTTP Access Control (CORS)
](https://developer.mozilla.org …

  1. Nginx reverse proxy

The nginx reverse proxy is used to realize cross-domain and only the configuration of nginx needs to be modified to solve the cross-domain problem.

When website A requests an interface from website B, it sends a request to website B. nginx receives the request according to the configuration file and requests from website B instead of website A.
Nginx gets this resource and then returns it to site a to solve the cross-domain problem.

For example, the port number of nginx is 8090, and the server port number to be requested is 3000. (localhost:8090 requests localhost:3000/say)

Nginx is configured as follows:

server {
    listen       8090;

    server_name  localhost;

    location / {
        root   /Users/liuyan35/Test/Study/CORS/1-jsonp;
        index  index.html index.htm;
    }
    location /say {
        rewrite  ^/say/(.*)$ /$1 break;
        proxy_pass   http://localhost:3000;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    }
    # others
}
  1. websocket

Websocket is a persistent protocol of HTML5, which realizes full duplex communication between browser and server, and is also a cross-domain solution.

Websocket is not affected by homologous policies. As long as the server supports it, cross-domain is supported without any configuration.

The front page is on port 8080.

let socket = new WebSocket('ws://localhost:3000'); //协议是ws
socket.onopen = function() {
    socket.send('Hi,你好');
}
socket.onmessage = function(e) {
    console.log(e.data)
}

Server 3000 port. It can be seen that no cross-domain configuration is required for websocket.

let WebSocket = require('ws');
let wss = new WebSocket.Server({port: 3000});
wss.on('connection', function(ws) {
    ws.on('message', function(data) {
        console.log(data); //接受到页面发来的消息'Hi,你好'
        ws.send('Hi'); //向页面发送消息
    });
});
  1. postMessage

PostMessage is used as a cross-domain before the front-end page, such as the cross-domain of the parent page and iframe page. The window.postMessage method allows cross-window communication, regardless of whether the two windows are homologous.

It is said that there is not much communication between the two pages in the work. In my work, I only used it twice. One time, I sent the postMessage message from H5 page. The webview of ReactNative received the message and handled it accordingly. The other is a carousel page. One carousel page uses an iframe page. In order to solve the sliding event conflict, the iframe page monitors gestures and sends a message to tell the parent page whether it slides left or right.

The child page sends messages to the parent page

Parent page

window.addEventListener('message', (e) => {
    this.props.movePage(e.data);
}, false);

Sub-page (iframe):

if(/*左滑*/) {
    window.parent && window.parent.postMessage(-1, '*')
}else if(/*右滑*/){
    window.parent && window.parent.postMessage(1, '*')
}

The parent page sends a message to the child page

Parent page:

let iframe = document.querySelector('#iframe');
iframe.onload = function() {
    iframe.contentWindow.postMessage('hello', 'http://localhost:3002');
}

Subpage:

window.addEventListener('message', function(e) {
    console.log(e.data);
    e.source.postMessage('Hi', e.origin); //回消息
});
  1. Node middleware

Node middleware’s cross-domain principle and nginx agent’s cross-domain, the same source policy is the limitation of browser, the server has no same source policy.

The principle of realizing cross-domain by node middleware is as follows:

1. Accept the client request

2. Forward the request to the server.

3. Get the server response data.

4. Forward the response to the client.

Cross-domain methods are not commonly used.

The following three cross-domain methods are rarely used. If you are interested, you can consult relevant information yourself.

  1. window.name + iframe
  2. location.hash + iframe
  3. Document.domain (the primary domain needs to be the same)

If you have a better answer or idea, please leave a message under github corresponding to this topic:What are the cross-domain methods? What is the principle?


13. What are the ways of JS asynchronous loading?

  1. <script>Defer property of, new in HTML4
  2. <script>Async property of, new in HTML5

<script>The tag opens the defer property and the script will load asynchronously. When the rendering engine encounters this line of commands, it will start downloading the external script, but will not wait for it to download and execute, but will directly execute the following commands.

The difference between defer and async is that defer will not execute until the normal rendering of the entire page in memory is completed.

Once async is downloaded, the rendering engine will interrupt rendering, and continue rendering after executing this script. Defer means “execute after rendering” and async means “execute after downloading”.

If there are multiple defer scripts, they will be loaded in the order in which they appear on the page.

Multiple async scripts cannot guarantee the loading order.

  1. Dynamically insert script script
function downloadJS() { 
    varelement = document.createElement("script"); 
    element.src = "XXX.js"; 
    document.body.appendChild(element); 
}
//何时的时候,调用上述方法 
  1. Conditional dynamic script creation

For example, after the page onload,

If you have a better answer or idea, please leave a message under github corresponding to this topic:What are the ways js loads asynchronously?


14. Under what circumstances does Code A print out 1?

//?
if(a == 1 && a == 2 && a == 3) {
    console.log(1);
}

1. During type conversion, we know how the object is converted to the original data type. If [Symbol.toPrimitive] is deployed, then the return value of [Symbol.toPrimitive] () is returned. Of course, we can also deploy this function on the valueOf or toString interface with the same effect.

//利用闭包延长作用域的特性
let a = {
    [Symbol.toPrimitive]: (function() {
            let i = 1;
            return function() {
                return i++;
            }
    })()
}

(1). When comparing a == 1, [Symbol.toPrimitive] will be called, where I is 1 and equal.
(2). Continue to compare a == 2 and call [Symbol.toPrimitive], where I is 2 and equal.
(3). Continue to compare a == 3 and call [Symbol.toPrimitive], where I is 3 and equal.

2. use Object.definePropert to define the a attribute on window/global. when getting the a attribute, get will be called.

let val = 1;
Object.defineProperty(window, 'a', {
  get: function() {
    return val++;
  }
});

3. Use the characteristics of arrays.

var a = [1,2,3];
a.join = a.shift;

Array oftoStringThe method returns a string consisting of the toString () return value of each element in the array connected by calling the join () method (separated by commas).

Therefore, we can re-join the method. Returns the first element and deletes it.

If you have a better answer or idea, please leave a message under github corresponding to this topic:Under what circumstances does code a print 1?


15. What is the output of the following code?

function Foo() {
    getName = function() {console.log(1)};
    return this;
}
Foo.getName = function() {console.log(2)};
Foo.prototype.getName = function() {console.log(3)};
var getName = function() {console.log(4)};
function getName() {console.log(5)};

Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

Description:A classic face-to-face exam is only to help you review the knowledge points and deepen your understanding. In real work, it is impossible to write code like this, otherwise, you will definitely be killed.

1. First, in the precompilation phase, variable declarations and function declarations are promoted to the top of their corresponding scopes.

Therefore, the above code is compiled as follows (function declaration takes precedence over variable declaration):

function Foo() {
    getName = function() {console.log(1)};
    return this;
}
var getName;
function getName() {console.log(5)};
Foo.getName = function() {console.log(2)};
Foo.prototype.getName = function() {console.log(3)};
getName = function() {console.log(4)};

2.Foo.getName(); Directly call getName method on Foo, output 2

3.getName(); Output 4, getName Reassigned

4.Foo().getName(); Execute Foo (), window’s getName is reassigned, returning this; In browser environment, non-strict mode, this points to window, this.getName (); The output is 1.

If it is strict mode, this points to undefined, and an error will be thrown here.

If this refers to global in the node environment, node’s global variable is not hung on global, because global.getName corresponds to undefined, not a function, and an error will be thrown.

5.getName(); The nature that has been thrown wrong cannot move this step. Continue browser non-strict mode; Window.getName has been re-assigned the value, and is called again at this time. the output is 1

6.new Foo.getName(); inspectOperator precedenceNew has no parameter list, and the corresponding priority is 18. Member access operator., the corresponding priority is 19. Therefore, it is equivalent tonew (Foo.getName)(); The new operator executes the method in the constructor, so the output here is 2.

7.new Foo().getName(); New with parameter list, corresponding priority is 19, and member access operator.The priority is the same. The sibling operators are calculated from left to right.new Foo()First initialize the instantiation object of Foo, there is no getName method on the instance, so the prototype is needed to find it, and then it is found.Foo.prototype.getName, output 3

8.new new Foo().getName(); New with parameter list, priority 19, therefore equivalent tonew (new Foo()).getName(); First initialize the instantiation object of Foo, and then use getName function on its prototype as constructor to new again, and output 3

Therefore, the final results are as follows:

Foo.getName(); //2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

If you have a better answer or idea, please leave a message under github corresponding to this topic:What is the output of the following code?


16. how does implementing a two-way binding Proxy compare with Object.defineProperty?

  1. The Object.definedProperty is used to hijack the property of an object, hijack the getter and setter methods of the property, and perform specific operations when the property of the object changes. Proxy hijacked the whole object.
  2. The Proxy will return a proxy object, we just need to operate on the new object, andObject.definePropertyYou can only traverse object properties and modify them directly.
  3. Object.definedProperty does not support arrays, more precisely, does not support various API of arrays, because if only consider the situation of arry[i] = value, it can be hijacked, but this kind of hijacking is meaningless. Proxy can support various API of array.
  4. Although there are many defects in Object.defineProperty, its compatibility is better than Proxy.

PS: Vue2.x uses Object.defineProperty to implement bidirectional data binding, V3.0 uses Proxy.

//拦截器
let obj = {};
let temp = 'Yvette';
Object.defineProperty(obj, 'name', {
    get() {
        console.log("读取成功");
        return temp
    },
    set(value) {
        console.log("设置成功");
        temp = value;
    }
});

obj.name = 'Chris';
console.log(obj.name);

PS:The properties defined by Object.defineProperty cannot be enumerated, changed or configured by default [cannot delete]

We can see that Proxy will hijack the whole object, read the attributes in the object or modify the attribute values, then it will be hijacked. However, it is necessary to note that for complex data types, the reference address is monitored instead of the value. If the reference address has not changed, set will not be triggered.

let obj = {name: 'Yvette', hobbits: ['travel', 'reading'], info: {
    age: 20,
    job: 'engineer'
}};
let p = new Proxy(obj, {
    get(target, key) { //第三个参数是 proxy, 一般不使用
        console.log('读取成功');
        return Reflect.get(target, key);
    },
    set(target, key, value) {
        if(key === 'length') return true; //如果是数组长度的变化,返回。
        console.log('设置成功');
        return Reflect.set([target, key, value]);
    }
});
p.name = 20; //设置成功
p.age = 20; //设置成功; 不需要事先定义此属性
p.hobbits.push('photography'); //读取成功;注意不会触发设置成功
p.info.age = 18; //读取成功;不会触发设置成功

Finally, let’s look at the difference between Object.definedProperty and Proxy for array hijacking.

Object.definedProperty can hijack the index of an array as an attribute, but it only supports direct operation on arry[i], and does not support the API of an array, which is very chicken ribs.

let arry = []
Object.defineProperty(arry, '0', {
    get() {
        console.log("读取成功");
        return temp
    },
    set(value) {
        console.log("设置成功");
        temp = value;
    }
});

arry[0] = 10; //触发设置成功
arry.push(10); //不能被劫持

Proxy can monitor array changes and support various API. Note that changes in the array may trigger get and set more than once. If necessary, decide whether to proceed with the processing according to the key value.

let hobbits = ['travel', 'reading'];
let p = new Proxy(hobbits, {
    get(target, key) {
        // if(key === 'length') return true; //如果是数组长度的变化,返回。
        console.log('读取成功');
        return Reflect.get(target, key);
    },
    set(target, key, value) {
        // if(key === 'length') return true; //如果是数组长度的变化,返回。
        console.log('设置成功');
        return Reflect.set([target, key, value]);
    }
});
p.splice(0,1) //触发get和set,可以被劫持
p.push('photography');//触发get和set
p.slice(1); //触发get;因为 slice 是不会修改原数组的

If you have a better answer or idea, please leave a message under github corresponding to this topic:How does implementing a two-way binding Proxy compare with Object.defineProperty?


17.Object.is()And compare operator=====What’s the difference?

In the following cases, Object.is is considered equal.

两个值都是 undefined
两个值都是 null
两个值都是 true 或者都是 false
两个值是由相同个数的字符按照相同的顺序组成的字符串
两个值指向同一个对象
两个值都是数字并且
都是正零 +0
都是负零 -0
都是 NaN
都是除零和 NaN 外的其它同一个数字

Object.is () is similar to = = =, but with some minor differences, as follows:

  1. NaN is equal to NaN.
  2. -0 and +0 are not equal
console.log(Object.is(NaN, NaN));//true
console.log(NaN === NaN);//false
console.log(Object.is(-0, +0)); //false
console.log(-0 === +0); //true

Object.is and==That’s a long way off.==When the types are different, type conversion is required, which has been explained in detail above.

If you have a better answer or idea, please leave a message under github corresponding to this topic:Object.is() 与比较操作符 ===== 有什么区别?


18. What is an event cycle? What is the difference between Node event loop and JS event loop?

The last question is too long for everyone to answer and write down.

For this problem, a special article will be written later ~

Leave your answer: What is an event cycle? What is the difference between Node event loop and JS event loop?

The event-loop of the browser can be seen in my previous articles:Understand the browser’s EventLoop


Reference articles:

  1. https://www.imooc.com/article …
  2. http://es6.ruanyifeng.com/
  3. https://www.imooc.com/article …
  4. https://www.cnblogs.com/Lucky …
  5. https://www.jianshu.com/p/a76 …
  6. https://www.v2ex.com/t/351261

Follow-up writing plan (writing order uncertain)

1. The Native JS that You Must Know in Winter Job Hunting Season (Part 2)

2. “CSS You Must Know in Winter Job Hunting Season”

3. Front-end Safety You Must Know in Winter Job Hunting Season

4. Some Browser Knowledge You Must Know in Winter Job Hunting Season

5. Performance Optimization You Must Know in Winter Job Hunting Season

6. The webpack Principles You Must Know in Winter Job Hunting Season

For React technology stack:

1. React series you must understand during the winter job-hunting season

2. The ReactNative series that you must understand during the cold winter job season

The writing of this article took a lot of time. In the process, I also learned a lot of knowledge. Thank you for your friends’ willingness to spend precious time reading this article. If this article gives you some help or inspiration, please don’t be stingy with your praise and Star. Your affirmation is my greatest motivation.https://github.com/YvetteLau/ …

Recommend to pay attention to my public number:

clipboard.png