[Step-By-Step] Deep Analysis of High Frequency Interview Questions/Weekly 07

  Front end, html5, Interview, javascript, Programmer

This week’s interview questions list:

More quality articles to stamp on: https://github.com/YvetteLau/ …

31. Achieving aJSON.stringify

JSON.stringify([, replacer [, space])The method is to convert a JavaScript value (object or array) into a JSON string. The implementation is simulated here without considering the optional second parameter.replacerAnd the third parameterspaceIf you do not know the function of these two parameters, it is recommended to readMDNDocuments.

JSON.stringify()Converts a value to a correspondingJSONFormat:

  1. Basic data type:

    • Undefined is still undefined after conversion (type is alsoundefined)
    • Boolean value conversion is followed by a string"false"/"true"
    • Number type (exceptNaNAndInfinityThe conversion is followed by a numeric value of string type
    • Symbol conversion is followed byundefined
    • Null conversion is followed by a string"null"
    • String is still string after string conversion
    • NaNAndInfinityThe conversion is followed by a string"null"
  2. If it is a function type

    • After the conversion isundefined
  3. If it is an object type (not a function)

    • If sotoJSON()Method, then serializationtoJSON()The return value of the.
    • If it is an array

      -if' undefined', arbitrary function and' symbol' appear in attribute value, convert to string ` "null"'
    • If it isRegExpObject.

      Returns ` {} ` (type string)
    • If it isDateObject, returningDateThetoJSONString value
    • If it is an ordinary object;

      -Ignore if' undefined', arbitrary function and symbol value appear in attribute value.
      -All attributes with' symbol' as the attribute key will be completely ignored.
  4. Executing this method on an object that contains circular references (objects refer to each other to form an infinite loop) will throw an error.

Simulation implementation

function jsonStringify(data) {
    let dataType = typeof data;
    if (dataType !== 'object') {
        let result = data;
        //data 可能是 string/number/null/undefined/boolean
        if (Number.isNaN(data) || data === Infinity) {
            //NaN 和 Infinity 序列化返回 "null"
            result = "null";
        } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
            //function 、undefined 、symbol 序列化返回 undefined
            return undefined;
        } else if (dataType === 'string') {
            result = '"' + data + '"';
        }
        //boolean 返回 String()
        return String(result);
    } else if (dataType === 'object') {
        if (data === null) {
            return "null";
        } else if (data.toJSON && typeof data.toJSON === 'function') {
            return jsonStringify(data.toJSON());
        } else if (data instanceof Array) {
            let result = [];
            //如果是数组
            //toJSON 方法可以存在于原型链中
            data.forEach((item, index) => {
                if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
                    result[index] = "null";
                } else {
                    result[index] = jsonStringify(item);
                }
            });
            result = "[" + result + "]";
            return result.replace(/'/g, '"');

        } else {
            //普通对象
            /**
             * 循环引用抛错(暂未检测,循环引用时,堆栈溢出)
             * symbol key 忽略
             * undefined、函数、symbol 为属性值,被忽略
             */
            let result = [];
            Object.keys(data).forEach((item, index) => {
                if (typeof item !== 'symbol') {
                    //key 如果是symbol对象,忽略
                    if (data[item] !== undefined && typeof data[item] !== 'function'
                        && typeof data[item] !== 'symbol') {
                        //键值如果是 undefined、函数、symbol 为属性值,忽略
                        result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
                    }
                }
            });
            return ("{" + result + "}").replace(/'/g, '"');
        }
    }
}

Test code:

let sym = Symbol(10);
console.log(jsonStringify(sym) === JSON.stringify(sym));
let nul = null;
console.log(jsonStringify(nul) === JSON.stringify(nul));
let und = undefined;
console.log(jsonStringify(undefined) === JSON.stringify(undefined));
let boo = false;
console.log(jsonStringify(boo) === JSON.stringify(boo));
let nan = NaN;
console.log(jsonStringify(nan) === JSON.stringify(nan));
let inf = Infinity;
console.log(jsonStringify(Infinity) === JSON.stringify(Infinity));
let str = "hello";
console.log(jsonStringify(str) === JSON.stringify(str));
let reg = new RegExp("\w");
console.log(jsonStringify(reg) === JSON.stringify(reg));
let date = new Date();
console.log(jsonStringify(date) === JSON.stringify(date));
let obj = {
    name: '刘小夕',
    age: 22,
    hobbie: ['coding', 'writing'],
    date: new Date(),
    unq: Symbol(10),
    sayHello: function () {
        console.log("hello")
    },
    more: {
        brother: 'Star',
        age: 20,
        hobbie: [null],
        info: {
            money: undefined,
            job: null,
            others: []
        }
    }
}
console.log(jsonStringify(obj) === JSON.stringify(obj));


function SuperType(name, age) {
    this.name = name;
    this.age = age;
}
let per = new SuperType('小姐姐', 20);
console.log(jsonStringify(per) === JSON.stringify(per));

function SubType(info) {
    this.info = info;
}
SubType.prototype.toJSON = function () {
    return {
        name: '钱钱钱',
        mount: 'many',
        say: function () {
            console.log('我偏不说!');
        },
        more: null,
        reg: new RegExp("\w")
    }
}
let sub = new SubType('hi');
console.log(jsonStringify(sub) === JSON.stringify(sub));
let map = new Map();
map.set('name', '小姐姐');
console.log(jsonStringify(map) === JSON.stringify(map));
let set = new Set([1, 2, 3, 4, 5, 1, 2, 3]);
console.log(jsonStringify(set) === JSON.stringify(set));

32. Achieve aJSON.parse

JSON.parse(JSON.parse(text[, reviver])The method is used to parse JSON strings and construct JavaScript values or objects described by the strings. Provides an optional reviver function to perform transformations on the resulting object before returning. The implementation is simulated here without considering the optional second parameter.reviverIf you do not know the function of this parameter, it is recommended to readMDNDocuments.

First approach eval

The simplest and most intuitive way is to calleval

var json = '{"name":"小姐姐", "age":20}';
var obj = eval("(" + json + ")");  // obj 就是 json 反序列化之后得到的对象

Direct callevalexistXSSVulnerabilities, data may not bejsonData, but executableJavaScriptCode. Therefore, in callingevalBefore, the data needs to be verified.

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

if (
    rx_one.test(
        json
            .replace(rx_two, "@")
            .replace(rx_three, "]")
            .replace(rx_four, "")
    )
) {
    var obj = eval("(" +json + ")");
}

JSONIt is a subset of JS and can be handed directly toevalRun.

Second waysnew Function

FunctionAndevalHas the same string parameter characteristics.

var json = '{"name":"小姐姐", "age":20}';
var obj = (new Function('return ' + json))();

33. Implementing an Observer Model

Observer mode defines a one-to-many dependency between objects. When the state of an object changes, all objects that depend on it will be notified and automatically updated. Observer mode belongs to behavioral mode. Behavioral mode focuses on the communication between objects. Observer mode is the communication between observer and observed.

The Observer Subscribe to the Subject directly, and when the subject is activated, it will fire Fire Event in the observer.

    //有一家猎人工会,其中每个猎人都具有发布任务(publish),订阅任务(subscribe)的功能
    //他们都有一个订阅列表来记录谁订阅了自己
    //定义一个猎人类
    //包括姓名,级别,订阅列表
    function Hunter(name, level){
        this.name = name
        this.level = level
        this.list = []
    }
    Hunter.prototype.publish = function (money){
        console.log(this.level + '猎人' + this.name + '寻求帮助')
        this.list.forEach(function(item, index){
            item(money)
        })
    }
    Hunter.prototype.subscribe = function (targrt, fn){
        console.log(this.level + '猎人' + this.name + '订阅了' + targrt.name)
        targrt.list.push(fn)
    }
    
    //猎人工会走来了几个猎人
    let hunterMing = new Hunter('小明', '黄金')
    let hunterJin = new Hunter('小金', '白银')
    let hunterZhang = new Hunter('小张', '黄金')
    let hunterPeter = new Hunter('Peter', '青铜')
    
    //Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter
    hunterMing.subscribe(hunterPeter, function(money){
        console.log('小明表示:' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')
    });
    hunterJin.subscribe(hunterPeter, function(){
        console.log('小金表示:给予帮助')
    });
    hunterZhang.subscribe(hunterPeter, function(){
        console.log('小张表示:给予帮助')
    });
    
    //Peter遇到困难,赏金198寻求帮助
    hunterPeter.publish(198);
    
    //猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)

34. Use CSS to center an element horizontally and vertically

Parent element.container

daughter element.box

utilizeflexLayout

/* 无需知道被居中元素的宽高 */
.container {
    display: flex;
    align-items: center;
    justify-content: center;
}

The child element is single-line text

The that sets the parent elementtext-alignAndline-height = height

.container {
    height: 100px;
    line-height: 100px;
    text-align: center;
}

utilizeabsolute+transform

/* 无需知道被居中元素的宽高 */
/* 设置父元素非 `static` 定位 */
.container {
    position: relative;
}
/* 子元素绝对定位,使用 translate的好处是无需知道子元素的宽高 */
/* 如果知道宽高,也可以使用 margin 设置 */
.box {
    position: absolute;
    left: -50%;
    top: -50%;
    transform: translate(-50%, -50%);
}

utilizegridLayout

/* 无需知道被居中元素的宽高 */
.container {
    display: grid;
}
.box {
    justify-self: center; 
    align-self: center;
}

With absolute positioning summargin:auto

/* 无需知道被居中元素的宽高 */
.box {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    margin: auto;
}
.container {
    position: relative;
}

35. ES6 modules andCommonJSWhat are the differences between modules?

1. CommonJS 模块是运行时加载,ES6模块是编译时输出接口。
  • When compiling ES6 module, it can determine the dependency of the module and the input and output variables. ES6 module is not an object, its external interface is only a static definition, which will be generated in the static analysis phase of code.
  • CommonJSWhat is loaded is an object that will not be generated until the script is run.
2. CommonJS 模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
- `CommonJS` 输出的是一个值的拷贝(注意基本数据类型/复杂数据类型)
    
- ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

The CommonJS module outputs a copy of the value.

The output value of the module is the basic data type, and changes within the module will not affect this value.

//name.js
let name = 'William';
setTimeout(() => { name = 'Yvette'; }, 300);
module.exports = name;

//index.js
const name = require('./name');
console.log(name); //William
//name.js 模块加载后,它的内部变化就影响不到 name
//name 是一个基本数据类型。将其复制出一份之后,二者之间互不影响。
setTimeout(() => console.log(name), 500); //William

The value output by the module is a complex data type

  1. When the module outputs an object and the attribute value is a simple data type:
//name.js
let name = 'William';
setTimeout(() => { name = 'Yvette'; }, 300);
module.exports = { name };

//index.js
const { name } = require('./name');
console.log(name); //William
//name 是一个原始类型的值,会被缓存。
setTimeout(() => console.log(name), 500); //William

The module outputs objects:

//name.js
let name = 'William';
let hobbies = ['coding'];
setTimeout(() => { 
    name = 'Yvette';
    hobbies.push('reading');
}, 300);
module.exports = { name, hobbies };

//index.js
const { name, hobbies } = require('./name');
console.log(name); //William
console.log(hobbies); //['coding']
/*
 * name 的值没有受到影响,因为 {name: name} 属性值 name 存的是个字符串
 *     300ms后 name 变量重新赋值,但是不会影响 {name: name}
 * 
 * hobbies 的值会被影响,因为 {hobbies: hobbies} 属性值 hobbies 中存的是
 *     数组的堆内存地址,因此当 hobbies 对象的值被改变时,存在栈内存中的地址并
       没有发生变化,因此 hoobies 对象值的改变会影响 {hobbies: hobbies} 
 * xx = { name, hobbies } 也因此改变 (复杂数据类型,拷贝的栈内存中存的地址)  
 */
setTimeout(() => {
    console.log(name);//William
    console.log(hobbies);//['coding', 'reading']
}, 500);

The Operation Mechanism of ES6 Module and Its ApplicationCommonJSNot the same. JS engine encountered module load command when static analysis of scriptimport, a read-only reference is generated. 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
let name = 'William';
setTimeout(() => { name = 'Yvette'; hobbies.push('writing'); }, 300);
export { name };
export var hobbies = ['coding'];

//index.js
import { name, hobbies } from './name';
console.log(name, hobbies); //William ["coding"]
//name 和 hobbie 都会被模块内部的变化所影响
setTimeout(() => {
    console.log(name, hobbies); //Yvette ["coding", "writing"]
}, 500); //Yvette

ES6 module is a dynamic reference and does not cache values. Variables in the module are bound to the module in which they are located. Therefore, the above example is also easy to understand.

Thenexport defaultWhat is export like?

//name.js
let name = 'William';
let hobbies = ['coding']
setTimeout(() => { name = 'Yvette'; hobbies.push('writing'); }, 300);
export default { name, hobbies };

//index.js
import info from './name';
console.log(info.name, info.hobbies); //William ["coding"]
//name 不会被模块内部的变化所影响
//hobbie 会被模块内部的变化所影响
setTimeout(() => {
    console.log(info.name, info.hobbies); //William ["coding", "writing"]
}, 500); //Yvette

Let’s see why.

export defaultCan be understood as assigning variables todefault, and finally exporteddefault(It is only convenient to understand and does not represent the final implementation. If you are interested in this, you can read the code compiled by the webpack).

Base type variablename, assigned todefaultAfter that, read-only references anddefaultAssociation, the original variable at this timenameAny modification of is related todefaultNot at all.

Complex data type variableshobbies, assigned todefaultAfter that, read-only references anddefaultAssociation,defaultAndhobbiesIs stored in the heap memory address of the same object, when the value of this object changes, at this timedefaultThe value of will also change.

3. ES6 模块自动采用严格模式,无论模块头部是否写了 "use strict";
4. require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。
5. ES6 模块的输入变量是只读的,不能对其进行重新赋值
import name from './name';
name = 'Star'; //抛错
6. 当使用require命令加载某个模块时,就会运行整个模块的代码。
7. 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

Reference articles:

[1]JSON.parse Three Implementations
[2]ES6 documentation
[3]JSON-js
[4]Differences between CommonJS Module and ES6 Module
[5]Publish/Subscribe Mode and Observer Mode

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 to move forward.https://github.com/YvetteLau/ …

Recommend to pay attention to my public number:

clipboard.png