【Шаг за шагом】Углубленный анализ часто задаваемых вопросов на интервью / Еженедельник 07

внешний интерфейс JavaScript
【Шаг за шагом】Углубленный анализ часто задаваемых вопросов на интервью / Еженедельник 07

Список вопросов для интервью на этой неделе:

1. РеализуйтеJSON.stringify

JSON.stringify([, replacer [, space])Метод заключается в преобразовании значения JavaScript (объекта или массива) в строку JSON. Смоделируйте реализацию здесь, игнорируя необязательный второй параметрreplacerи третий параметрspace, если вы не понимаете роль этих двух параметров, рекомендуется прочитатьMDNдокументация.

JSON.stringify()Преобразуйте значение в соответствующееJSONФормат:

  1. Основные типы данных:

    • undefined остается неопределенным после преобразования (тип такжеundefined)
    • логическое значение, преобразованное в строку"false"/"true"
    • тип номера (кромеNaNа такжеInfinity) преобразуется в числовое значение типа string
    • После преобразования символовundefined
    • Строка после нулевого преобразования"null"
    • строка остается строкой после преобразования
    • NaNа такжеInfinityПосле преобразования это строка"null"
  2. Если это тип функции

    • после преобразованияundefined
  3. Если это тип объекта (не функция)

    • Если естьtoJSON()метод, затем сериализоватьtoJSON()Возвращаемое значение.

    • если массив

      • Если значение атрибута появляетсяundefined, любую функцию иsymbol, преобразуется в строку"null"
    • еслиRegExpобъект. вернуть{}(тип строка)

    • еслиDateобъект, возвратDateизtoJSONстроковое значение

    • если это обычный объект;

      • Если значение атрибута появляетсяundefined, произвольные функции и значения символов игнорируются.
      • все сsymbolСвойства, являющиеся ключами свойств, полностью игнорируются.
  4. Выполнение этого метода для объектов, содержащих циклические ссылки (объекты, ссылающиеся друг на друга, образующие бесконечный цикл), вызовет ошибку.

Реализация моделирования

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, '"');
        }
    }
}

Тестовый код:

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));

2. РеализоватьJSON.parse

JSON.parse(JSON.parse(text[, reviver])Методы используются для анализа строки JSON и создания значения или объекта JavaScript, описываемого строкой. Предусмотрена дополнительная функция оживления для выполнения преобразования полученного объекта перед возвратом. Смоделируйте реализацию здесь, игнорируя необязательный второй параметрreviver, если вы не понимаете назначение этого параметра, рекомендуется прочитатьMDNдокументация.

Первый способ оценки

Самый простой и интуитивно понятный способ — позвонитьeval

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

позвонить напрямуюevalсуществуетXSSУязвимости, которых может не быть в данныхjsonданные, но исполняемыеJavaScriptкод. Поэтому при вызовеevalПеред этим данные необходимо проверить.

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 + ")");
}

JSONявляется подмножеством JS и может быть передано непосредственноevalбегать.

второй способnew Function

Functionа такжеevalИмеет ту же функцию строкового параметра.

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

3. Реализуйте шаблон наблюдателя

Шаблон наблюдателя определяет зависимость между объектами по принципу «один ко многим».При изменении состояния объекта все объекты, зависящие от него, будут уведомлены и автоматически обновлены. Паттерн наблюдателя — это поведенческий паттерн, а поведенческий паттерн связан с общением между объектами, а паттерн наблюдателя — это общение между наблюдателем и наблюдаемым.

Наблюдатель (Observer) напрямую подписывается (Subscribe) субъекта (Subject), и когда субъект активируется, он вызывает событие (Fire Event) в наблюдателе.

    //有一家猎人工会,其中每个猎人都具有发布任务(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有困难时,会自动通知给他们(观察者)

4. Используйте CSS для центрирования элемента по горизонтали и вертикали

родительский элемент.container

дочерний элемент.box

использоватьflexмакет

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

Дочерний элемент представляет собой одну строку текста

установить родительский элементtext-alignа такжеline-height = height

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

использоватьabsolute + transform

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

использоватьgridмакет

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

Используйте абсолютное позиционирование иmargin:auto

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

5. Модули ES6 иCommonJSЧем отличаются модули?

1. CommonJSМодули загружаются во время выполнения, модули ES6 представляют собой скомпилированные интерфейсы.
  • Когда модуль ES6 скомпилирован, он может определить зависимости модуля, а также входные и выходные переменные. Модуль ES6 не является объектом, его внешний интерфейс — это просто статическое определение, которое будет сгенерировано на этапе статического анализа кода.
  • CommonJSЗагружается объект, который не создается до завершения выполнения сценария.
2. CommonJSВывод модуля — это копия значения, а вывод модуля ES6 — ссылка на значение.
- `CommonJS` 输出的是一个值的拷贝(注意基本数据类型/复杂数据类型)
    
- ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

Модули CommonJS выводят копию значения.

Значение, выводимое модулем, является базовым типом данных, и изменения в модуле не повлияют на это значение.

//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

Значение, выводимое модулем, представляет собой сложный тип данных.

  1. Выход модуля — это объект, а значение атрибута — простой тип данных:
//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

Выход модуля представляет собой объект:

//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);

Как работают модули ES6CommonJSРазные. Когда движок JS статически анализирует скрипт, он встречает команду загрузки модуляimport, создается ссылка только для чтения. Когда скрипт действительно выполняется, в соответствии с этой ссылкой только для чтения перейдите к загруженному модулю, чтобы получить значение.

//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 динамически ссылаются и не кэшируют значения.Переменные в модулях привязаны к модулю, в котором они расположены. Таким образом, приведенный выше пример также легко понять.

Такexport defaultЧто с экспортом?

//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

Давайте посмотрим, почему.

export defaultМожно понимать как присвоение переменнойdefaultи, наконец, экспортdefault(Это только для удобства понимания, и не представляет окончательную реализацию. Если вам интересна эта часть, вы можете прочитать код, скомпилированный webpack).

переменная базового типаname, присвоеноdefaultПосле этого ссылка только для чтения создается с помощьюdefaultассоциация, в это время исходная переменнаяnameЛюбая модификацияdefaultЭто не имеет значения.

переменная сложного типа данныхhobbies, присвоеноdefaultПосле этого ссылка только для чтения создается с помощьюdefaultассоциация,defaultа такжеhobbiesХранится в куче адрес памяти того же объекта, когда значение этого объекта изменяется, в это времяdefaultзначение также изменится.

3. Модули ES6 автоматически используют строгий режим, независимо от того, написан ли заголовок модуля."use strict";
4. require может выполнять динамическую загрузку,importЗаявление не может быть сделано,importЗаявление должно быть на объеме верхнего уровня.
5. Входные переменные модулей ES6 доступны только для чтения и не могут быть переназначены
import name from './name';
name = 'Star'; //抛错
6. Когда модуль загружается с помощью команды require, запускается весь код модуля.
7. При использовании команды require для загрузки того же модуля модуль не будет выполняться повторно, но будет извлечено значение из кеша. То есть независимо от того, сколько раз был загружен модуль CommonJS, он будет запущен только один раз при первой загрузке, если он будет загружен позже, он вернет результат первого запуска, если только система кеш очищается вручную.

Справочная статья:

[1] Три реализации JSON.parse

[2] Документация по ES6

[3] JSON-js

[4] Разница между модулями Commonjs и модулями ES6

[5] Шаблон публикации-подписки и шаблон наблюдателя

Спасибо за вашу готовность потратить свое драгоценное время на чтение этой статьи. Если эта статья дала вам небольшую помощь или вдохновение, пожалуйста, не скупитесь на лайки и звезды. Вы, безусловно, самая большая движущая сила для меня, чтобы двигаться вперед .GitHub.com/Иветт Л.А. Ю/Б…

Подпишитесь на официальный аккаунт и присоединитесь к группе технического обмена