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

внешний интерфейс JavaScript

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

  • Что такое закрытие? Какова роль закрытия?
  • Методы достижения Promise.all
  • Какие существуют методы асинхронной загрузки js-скриптов?
  • Пожалуйста, реализуйте функцию flattenDeep для выравнивания вложенных массивов.
  • Каковы характеристики итерируемых объектов?

15. Что такое замыкание? Какова роль замыканий?

Что такое закрытие?

Замыкания – это функции, которые имеют доступ к переменным в области видимости другой функции. Самый распространенный способ создания замыкания — создать функцию внутри другой функции.

создать замыкание

function foo() {
    var a = 2;
    return function fn() {
        console.log(a);
    }
}
let func = foo();
func(); //输出2

Замыкания позволяют функции продолжать доступ к лексической области видимости, в которой она была определена. Благодаря fn внутренняя область видимости foo не будет уничтожена после выполнения foo().

Независимо от того, каким образом внутренняя функция передается за пределы лексической области, в которой она находится, она будет содержать ссылку на область исходного определения, и замыкание будет использоваться везде, где функция выполняется. Такие как:

function foo() {
    var a = 2;
    function inner() {
        console.log(a);
    }
    outer(inner);
}
function outer(fn){
    fn(); //闭包
}
foo();

Роль замыканий

  1. Возможность доступа к лексической области, в которой была определена функция (предотвращает ее повторное использование).

  2. частная переменная

function base() {
    let x = 10; //私有变量
    return {
        getX: function() {
            return x;
        }
    }
}
let obj = base();
console.log(obj.getX()); //10
  1. Объем ложного блока
var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = (function(j){
        return function () {
            console.log(j);
        }
    })(i);
}
a[6](); // 6
  1. Создать модуль
function coolModule() {
    let name = 'Yvette';
    let age = 20;
    function sayName() {
        console.log(name);
    }
    function sayAge() {
        console.log(age);
    }
    return {
        sayName,
        sayAge
    }
}
let info = coolModule();
info.sayName(); //'Yvette'

Шаблон модуля имеет два обязательных условия (из «JavaScript, которого вы не знаете»)

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

Недостатки закрытия

Закрытие приведет к тому, что переменные функции будут храниться в памяти, слишком много закрытий может привести к утечке памяти.

16. Реализуйте метод Promise.all

Перед реализацией метода Promise.all мы должны сначала узнать функции и характеристики Promise.all, потому что мы сможем написать реализацию, когда поймем функции и характеристики Promise.all.

Обещание.все функции

Promise.all(iterable)Возвращает новый экземпляр Promise. Этот экземпляр находится вiterableвсе параметрыpromiseВсеfulfilledили параметр не содержитpromise, государство становитсяfulfilled; если параметрpromiseесть сбойrejected, обратный вызов этого экземпляра не удался, причина сбоя - первый сбойpromiseрезультат возврата.

let p = Promise.all([p1, p2, p3]);

Состояние p определяется p1,p2,p3, разделенными на следующие два случая:

(1) Только состояния p1, p2 и p3 становятсяfulfilled, состояние p станетfulfilled, в это время возвращаемые значения p1, p2 и p3 образуют массив, который передается функции обратного вызова p.

(2) До тех пор, пока один из p1, p2 и p3rejected, состояние p становитсяrejected, возвращаемое значение первого отклоненного экземпляра будет передано функции обратного вызова p.

Возможности Promise.all

Возвращаемое значение Promise.all является экземпляром обещания.

  • Если переданный параметр является пустым итерируемым объектом,Promise.allМогуСинхронизироватьвозвращает завершенный статусpromise
  • Если переданный параметр не содержит обещаний,Promise.allМогуасинхронныйвозвращает завершенный статусpromise
  • В других случаяхPromise.allвернутьОбработка (в ожидании)состояниеpromise.

Состояние обещания, возвращенного Promise.all

  • Если все обещания в переданных параметрах выполняются,Promise.allвернутьpromiseСтановится полным асинхронно.
  • Если один из переданных параметровpromiseпотерпеть неудачу,Promise.allАсинхронно отправляет результат сбоя в функцию обратного вызова состояния сбоя, независимо от другихpromiseГотово
  • При любых условиях,Promise.allвернутьpromiseРезультатом статуса завершения является массив

Реализация Promise.all

Рассмотрим только случай, когда входящий параметр является массивом

/** 仅考虑 promises 传入的是数组的情况时 */
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            resolve([]);
        } else {
            let result = [];
            let index = 0;
            for (let i = 0;  i < promises.length; i++ ) {
                //考虑到 i 可能是 thenable 对象也可能是普通值
                Promise.resolve(promises[i]).then(data => {
                    result[i] = data;
                    if (++index === promises.length) {
                        //所有的 promises 状态都是 fulfilled,promise.all返回的实例才变成 fulfilled 态
                        resolve(result);
                    }
                }, err => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

Можно протестировать с кодом на MDN

Рассмотрим итерируемые объекты

Promise.all = function (promises) {
    /** promises 是一个可迭代对象,省略对参数类型的判断 */
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            //如果传入的参数是空的可迭代对象
            return resolve([]);
        } else {
            let result = [];
            let index = 0;
            let j = 0;
            for (let value of promises) {
                (function (i) {
                    Promise.resolve(value).then(data => {
                        result[i] = data; //保证顺序
                        index++;
                        if (index === j) {
                            //此时的j是length.
                            resolve(result);
                        }
                    }, err => {
                        //某个promise失败
                        reject(err);
                        return;
                    });
                })(j)
                j++; //length
            }
        }
    });
}

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

let p2 = Promise.all({
    a: 1,
    [Symbol.iterator]() {
        let index = 0;
        return {
            next() {
                index++;
                if (index == 1) {
                    return {
                        value: new Promise((resolve, reject) => {
                            setTimeout(resolve, 100, 'foo');
                        }), done: false
                    }
                } else if (index == 2) {
                    return {
                        value: new Promise((resolve, reject) => {
                            resolve(222);
                        }), done: false
                    }
                } else if(index === 3) {
                    return {
                        value: 3, done: false
                    }
                }else {
                    return { done: true }
                }

            }
        }

    }
});
setTimeout(() => {
    console.log(p2)
}, 200);

17. Какие существуют методы асинхронной загрузки js-скриптов?

<script>добавлено на ярлыкasync(html5) илиdefer(html4), сценарий будет загружаться асинхронно.

<script src="../XXX.js" defer></script>

deferа такжеasyncРазница в следующем:

  • deferДождаться, пока вся страница нормально отрисуется в памяти (полностью сгенерируется DOM-структура и исполнятся другие скрипты), и выполнить перед window.onload;
  • asyncПосле завершения загрузки механизм рендеринга прервет рендеринг и после выполнения этого скрипта продолжит рендеринг.
  • Если есть несколькоdeferСкрипты загружаются в том порядке, в котором они появляются на странице
  • несколькоasyncСкрипты не гарантируют порядок загрузки

динамически созданныйscriptЭтикетка

динамически созданныйscript,настраиватьsrcОн не начнет загрузку, но при добавлении в документ JS-файл начнет скачиваться.

let script = document.createElement('script');
script.src = 'XXX.js';
// 添加到html文件中才会开始下载
document.body.append(script);

XHR загружает JS асинхронно

let xhr = new XMLHttpRequest();
xhr.open("get", "js/xxx.js",true);
xhr.send();
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        eval(xhr.responseText);
    }
}

18. Пожалуйста, реализуйте функцию flattenDeep для выравнивания вложенных массивов.

Использование Array.prototype.flat

ES6 добавляет новые экземпляры массиваflatметод для «выравнивания» вложенного массива в одномерный массив. Этот метод возвращает новый массив и не влияет на исходный массив.

flatПо умолчанию будет "сведен" только один слой. Если вы хотите "сгладить" многоуровневый вложенный массив, вам нужно датьflatПередайте целое число, представляющее количество слоев, которые вы хотите свести.

function flattenDeep(arr, deepLength) {
    return arr.flat(deepLength);
}
console.log(flattenDeep([1, [2, [3, [4]], 5]], 3));

Когда переданное целое число больше, чем количество вложенных уровней массива, массив будет сведен в одномерный массив.Максимальное число, которое может представлять JS, равноMath.pow(2, 53) - 1, поэтому мы можем определитьflattenDeepфункция

function flattenDeep(arr) {
    //当然,大多时候我们并不会有这么多层级的嵌套
    return arr.flat(Math.pow(2,53) - 1); 
}
console.log(flattenDeep([1, [2, [3, [4]], 5]]));

Использование сокращения и объединения

function flattenDeep(arr){
    return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
console.log(flattenDeep([1, [2, [3, [4]], 5]]));

Используйте стек для бесконечного удаления вложенных многоуровневых вложенных массивов

function flattenDeep(input) {
    const stack = [...input];
    const res = [];
    while (stack.length) {
        // 使用 pop 从 stack 中取出并移除值
        const next = stack.pop();
        if (Array.isArray(next)) {
            // 使用 push 送回内层数组中的元素,不会改动原始输入 original input
            stack.push(...next);
        } else {
            res.push(next);
        }
    }
    // 使用 reverse 恢复原数组的顺序
    return res.reverse();
}
console.log(flattenDeep([1, [2, [3, [4]], 5]]));

19. Каковы характеристики итерируемых объектов

ES6 указывает, что по умолчаниюIteratorИнтерфейс развернут в структуре данныхSymbol.iteratorАтрибуты, с другой точки зрения, также могут рассматриваться как структура данных, если ониSymbol.iteratorАтрибуты(Symbol.iteratorМетод соответствует функции генерации обходчика, которая возвращает объект обходчика), то его можно считать итерируемым.

Особенности итерируемых объектов

  • имеютSymbol.iteratorАтрибуты,Symbol.iterator()Возвращает объект итератора
  • можно использоватьfor ... ofсделать петлю
let arry = [1, 2, 3, 4];
let iter = arry[Symbol.iterator]();
console.log(iter.next()); //{ value: 1, done: false }
console.log(iter.next()); //{ value: 2, done: false }
console.log(iter.next()); //{ value: 3, done: false }

Родной имеетIteratorСтруктура данных интерфейса:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • объект аргументов функции
  • Объект NodeList

Настройка итерируемого объекта

Мы сказали выше, что объект может иметь только правильноеSymbol.iteratorсвойство, то оно является итерируемым, поэтому мы можем добавлять объекты, добавляяSymbol.iteratorСделайте его итерируемым.

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 函数中
        }
    }
};

for (var key of obj) {
    console.log(key); //Yvette 18 engineer
}

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

[1] MDN Promise.all

[2] Promise

[3] Iterator

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

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