Статья JavaScript «Приходит предложение», уровень няни консолидирует вашу систему знаний js

внешний интерфейс JavaScript
Статья JavaScript «Приходит предложение», уровень няни консолидирует вашу систему знаний js

🧐 Предисловие

каждый знает,jsМожно сказать, что доля фронтенд-интервью очень велика. Практически в каждом интервью более 40% вопросовjsтема.jsНе только исследуйте основные способности фронтенд-персонала, но, что более важно, можно сказать, что фронтенд основан наjsИсходя из этого, мы также проверяем наши способности к кодированию и логическое мышление. Если это сказано в начале интервьюjsНа самом деле это довольно опасно.

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

Давайте начнем объяснение этой статьи~📚

🥳Сессия карты разума

Перед тем, как начать, используйте интеллект-карту, чтобы понять содержание полного текста.Подробности смотрите на картинке ниже👇

js面试复习框架

Интеллект-карта готова, пора приступить к ее созданиюjsсистема знаний

😏 1. JS спецификация

1. Назовите несколько основных спецификаций JavaScript.

  • for-inПеременные в циклах должны быть явно ограничены областью действия с помощью ключевого слова let, чтобы избежать загрязнения области видимости.
for(let i in obj){
    
}
  • При сравнении логических/числовых значений используйте=== / !==сравнивать
  • switchзаявление должно иметьdefaultветвь;
  • не используйте глобальные функции;
  • Используйте замену литерала объектаnew ArrayВ этой форме пример объектного литерала приведен ниже.
let person = {
	name:'张三',
	age:13,
	like:['打篮球','打排球']
}

2. Знание нативного JavaScript.

типы данных, операции, объекты,Function, наследование, замыкания, область действия, цепочка прототипов, события,RegExp,JSON,Ajax,DOM,BOM, утечки памяти, асинхронная загрузка, шаблонизатор, интерфейсMVC, маршрутизация, модульность,Canvas,ECMAScript.

3. Расскажите о своем понимании JS.

основан на прототипединамический язык, основные чертыthis,прототипа такжеСеть прототипов.

JS строго делится на:Секция языковых стандартов(ECMAScript)+Раздел хост-среды.

Секция языковых стандартов

  • 2015年выпускатьES6, вводит множество фич, позволяющих писать масштабные проекты, сам стандарт2015年После этого номер года используется в качестве кодового имени и обновляется каждый год.

Раздел хост-среды

  • В среду хостинга браузера входитDOM + BOMЖдать
  • существуетNode, среда хостинга включает в себя некоторые файлы, базы данных, сети, взаимодействие с операционной системой и т.д.

4. Собственный узел перетаскивания JS

  • Привязать к узлу, который нужно перетащитьmousedown,mousemove,mouseupмероприятие.
  • mousedownПосле запуска события начните перетаскивание.
  • mousemove, надо пройтиevent.clientXа такжеclientYПолучите положение перетаскивания и в режиме реального времениобновить местоположение.
  • mouseup, перетаскивание заканчивается.
  • требует вниманияграница браузера,настраиватьдиапазон перетаскивания.

5. Расскажите о своем понимании ES6

  • Новая строка шаблона (дляJavaScriptобеспечивает простую интерполяцию строк).
  • Функция стрелки.
  • for-of(Используется для перебора данных — например, значений в массиве).
  • argumentsОбъекты можно прекрасно заменить с неопределенными параметрами и параметрами по умолчанию.
  • ES6Будуpromiseобъектов в спецификацию, предоставляя собственныйpromiseобъект.
  • повысилсяletа такжеconstкоманда, используемая для объявления переменных.
  • Также происходит введениеmoduleКонцепция модуля.

6. Знаете ли вы класс ES6?

ES6серединаclassДа, добавляйте методы непосредственно к функциональному объекту этого класса, а не к объекту-прототипу этого функционального объекта.

7. Расскажите о своем понимании AMD и Commonjs

  • CommonJSспецификация серверного модуля,Node.jsпринял эту спецификацию.
  • CommonJSКанонический загрузочный модуль является синхронным, то есть только после завершения загрузки можно выполнять последующие операции.AMDОбычно модули загружаются асинхронно, что позволяет указывать функции обратного вызова.
  • AMDРекомендуемый стиль — возвращать объект как объект модуля.CommonJSстиль черезmodule.exportsилиexportsНазначение атрибута для достижения цели предоставления объекта модуля.

8. Как понять модульность интерфейса

Модульность интерфейса — это независимый модуль в программировании сложных файлов, таких как js-файлы и т. д. Разделение на независимые модули способствует повторному использованию (повторному использованию) и обслуживанию (итерации версий), что приведет к взаимным зависимостям между модулями. есть общие спецификации JS, спецификации AMD, CMD и т. д., а также webpack, инструмент для упаковки js (мутации и т. д.).

9. Идеи объектно-ориентированного программирования

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

10. Вы использовали TypeScript? Что оно делает?

TypeScriptдляJSДобавьте поддержку типов и предоставьте последнюю версиюESПоддержка грамматики способствует совместной работе в команде и отладке, а также разработке крупномасштабных проектов.

11. Вы использовали PWA? Каков принцип использования serviceWorker?

渐进式网络应用(PWA)это гугл2015年底предложенная концепция. в принципевеб приложение, но выглядеть и чувствовать себя так же, как原生appаналогичный. служба поддержкиPWAвеб-сайт может предоставитьРаботать в оффлайне,Отправить уведомлениеа такжеДоступ к оборудованию устройстваи другие функции.

Service WorkerЭто сценарий, который браузер запускает в фоновом режиме независимо от веб-страницы, что открывает дверь к функциям, не требующим веб-страницы или взаимодействия с пользователем. Теперь они включают такие функции, как push-уведомления и фоновая синхронизация. будущее,Service WorkerБудут поддерживаться дополнительные функции, такие как периодическая синхронизация или геозона.

Примечание: Прогрессивное сетевое приложение

😲 Во-вторых, тип данных

1. В: 0,1+0,2 === 0,3? Почему?

В обычном математическом логическом мышлении0.1+0.2=0.3Эта логика верна, но вJavaScriptсередина0.1+0.2 !== 0.3,Почему это? Этот вопрос также иногда используется в качестве вопроса на собеседовании, чтобы проверить мнение интервьюера.JavaScriptУровень понимания ценности.

0.1 + 0.2 == 0.3 // false

существуетJS, двоичное число с плавающей запятой0.1а также0.2не точны, поэтому в сумме они не совсем равны0.3, но относительно близко0.3числа0.30000000000000004, поэтому результат условного суждения равенfalse.

Причина в том, что вJSСреди нихIEEE 754стандарт двойной точности, поэтому компьютер внутрикодирование данных хранения0.1 не является точным внутри компьютера, когда0.1, но ошибка округления0.1. Когда код компилируется или анализируется,0.1было округлено до внутреннего числа компьютера настолько близко, что до начала расчета возникает небольшая ошибка округления. То есть0.1 + 0.2не равно0.3причина.

Так как же избежать таких проблем?

Наиболее распространенным методом являетсяПреобразование чисел с плавающей запятой в целочисленные вычисления,потому чтоцелое числоможно точно представить.

Обычно расчетное число увеличивают в 10 N раз и делят на10изNмощность, обычно используется1000Вот и все.

(0.1*1000 + 0.2*1000)/1000 == 0.3 //true

2. Какие есть типы данных js? Где именно он существует? Каков метод суждения?

(1) тип данных js

jsТипы данных включаютбазовый тип данныха такжессылочный тип данных.

(2) конкретные хранятся?

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

основные типы данныхNumer,Boolean,String,null,undefined,Symbol(новое в ES6),BigInt(ES2020)Эквивалентно, они хранятся в стеке в памяти, то есть прямой доступ к переменной может быть сохранен вкучаЗначение соответствующей переменной в .

Если значение одной переменной присваивается другой переменной, эти две переменные сохраняются в памяти какнезависимый, изменение значения любой из переменных не повлияет на другую переменную. Это базовый тип данных.

Тип справочных данных:

Как насчет ссылочных типов данных?Object ,Array,Functionд., они присутствуют в памяти встек и кучаСреди них, когда мы хотим получить доступ к значению ссылочного типа, нам нужно сначала получить доступ к переменной вкучаАдрес в (указывающий на значение в куче), а потом через этот адрес доступ к хранимому вкучаданные в . Это ссылочный тип данных.

(3) Общие методы оценки: typeof, instanceof, ===

1) тип:

определение: возвращает строковое представление типа данных (нижний регистр).

Применение: тип + переменная

могу судить:

  • undefined/number/string/boolean/function (возвращает 'undefined'/'number'/'string'/'boolean'/'function')

  • null и объект, объект и массив (null, array, object вернет 'object' )

<script type="text/javascript">
    console.log(typeof "Tony");                // 返回 string 
    console.log(typeof 5.01);                  // 返回 number
    console.log(typeof false);                 // 返回 boolean
    console.log(typeof undefined);             // 返回 undefined
    console.log(typeof null);                  // 返回 object
    console.log(typeof [1,2,3,4]);             // 返回 object
    console.log(typeof {name:'John', age:34}); // 返回 object
</script>

2) пример:

определение: определить конкретный тип объекта

Применение:b instanceof A→ показатьbтак или иначеAэкземпляр объекта

могу судить:

Специально используется для определения типа данных объекта:Object , Arrayа такжеFunction

судитьString,Number,BooleanКогда эти три типа данных напрямую назначаются какfalse, Вызовите данные, созданные конструктором, какtrue

<script type="text/javascript">
    let str = new String("hello world")     //console.log(str instanceof String);  → true
    str = "hello world"                     //console.log(str instanceof String);  → false

    let num = new Number(44)                //console.log(num instanceof Number);  → true
    num = 44                                //console.log(num instanceof Number);  → false

    let bool = new Boolean(true)            //console.log(bool instanceof Boolean);  → true
    bool = true                             //console.log(bool instanceof Boolean);  → false

</script>
<script type="text/javascript">
    var items = []; 
    var object = {}; 
    
    function reflect(value) { 
        return value;
    } 
 
    console.log(items instanceof Array);        // true 
    console.log(items instanceof Object);       // true 
    console.log(object instanceof Object);      // true 
    console.log(object instanceof Array);       // false 
    console.log(reflect instanceof Function);   // true 
    console.log(reflect instanceof Object);     // true 

3) ===:

могу судить:undefined,null

<script type="text/javascript">
    let str;
    console.log(typeof str, str === undefined);   //'undefined', true

    let str2 = null;
    console.log(typeof str2, str2 === null); // 'object', true

</script>

3. Что такое мелкое копирование? Что такое глубокая копия? Объясните и напишите код отдельно.

(1) Поверхностная копия

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

// 分析
function shallowCopy(obj){
    let copyObj = {};
    for(let i in obj){
        copyObj[i] = obj[i];
    }
    return copyObj;
}

// 实例
let a = {
    name: '张三',
    age: 19,
    like: ['打篮球', '唱歌', '跳舞']
}

let b = shallowCopy(a);

a.name = '李四';
a.like[0] = '打打乒乓球';
console.log(a);
console.log(b);

(2) Глубокое копирование

определение: Глубокое копирование означает, что все данные внутри вновь скопированного объекта существуют независимо и не изменятся при изменении исходного объекта.

Есть два способа глубокого копирования: рекурсивное копирование и глубокое копирование с использованием функций JSON.

  • Принцип реализации рекурсивного копирования таков.: Получить каждый элемент в переменной, если он встречаетсяОсновное значение типа, получить напрямую; если встретитсязначение ссылочного типа, затем продолжайте получать каждый элемент внутри значения.
  • Принцип реализации глубокой копии JSON таков:: преобразовать значение переменной в строковую форму, а затем преобразовать его в объект и присвоить новой переменной.

ограничение: Ограничение глубокого копирования заключается в том, что оно будет игнорироватьundefined, не может сериализовать функции, не может разрешать объекты с циклическими ссылками.

Код реализации метода рекурсивного копирования:

// 分析
function deepCopy(obj){
    // 判断是否为引用数据类型
    if(typeof obj === 'object'){
        let result = obj.constructor === Array ? [] : {};
		// 对引用类型继续进行遍历,如果遍历没有结束的话
        for(let i in obj){
            result[i] =  typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
        }

        return result;
    }
    // 为基本数据类型,直接赋值返回
    else{
        return obj;
    }
}

// 实例 - 利用递归函数做深拷贝
let c = {
    name:'张三',
    age:12,
    like:[
        '打乒乓球',
        '打羽毛球',
        '打太极'
    ]
}

let d = deepCopy(c);

c.name = '李四';
c.like[0] = '打篮球';
console.log(c);
console.log(d);

Код реализации глубокого копирования JSON:

// 实例 - 利用json函数做深拷贝
let e = {
    name: '张三',
    age: 19,
    like:['打羽毛球', '唱歌', '跳舞']
}

let f = JSON.parse(JSON.stringify(e));

// 注意: JSON函数做深度拷贝时不能拷贝正则,Date,方法函数等

e.name = '李四';
e.like[0] = '打乒乓球';

// console.log(e);
// console.log(f);

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

4. Как представляются целые числа JS?

JS Integer Pass.Numberвведите для представления, следуйтеIEEE 754стандарт, проход64位представлять число, т.1+11+52(бит знака + бит экспоненты + значащий бит дробной части), максимальное безопасное число равно 253- 1, что соответствует16位Десятичная дробь.

Примечание: 1 десятичное число соответствует 4 двоичным цифрам

5. Насколько велика память Number? Что делать, если фон отправляет число, превышающее максимальное?

Math.pow(2,53), 53 допустимое число, если фон отправляет число, превышающее максимальное число, произойдет усечение, равноеJSМаксимально поддерживаемый сейф номер 253- 1.

6. Что такое NAN и что будет выведено при использовании typeof?

Не число, что означает не число.

typeof NaN === 'number'; //true

7. Какая польза от Symbol?

  • может использоваться для представленияуникальныйпеременные,Предотвращение конфликтов имен.
  • Помимо,Symbolтакже можно использоватьимитация частногоАтрибуты.

8. Разница между null и undefined

  • undefinedУказывает, что значение не существует.
  • undefinedявляется необработанным значением, которое означает «нет» или «отсутствующее значение», то есть здесь должно быть значение, ноеще не определено. вернется при попытке прочитатьundefined.
  • Например, когда переменная объявлена, но ей не присвоено значение, она равнаundefined.
  • nullУказывает, что объект определен со значением "null".
  • nullявляется объектом (пустой объект, нетлюбые свойства и методы).
  • Например, при использовании в качестве параметра функции это означает, что параметр функции не является объектом.
  • проверкаnull, обязательно используйте===,потому что==неразличимыйnullа такжеundefined.

9, неявное преобразование JS, преобразование отображения

Как правило, он вызывается при преобразовании небазового типа.valueOf,еслиvalueOfне может вернуть значение примитивного типа, оно будет вызваноtoString.

(1) Строки и числа

  • Оператор "+", если какой-либо из них является строкой, преобразуется в строку, а затем выполняет конкатенацию строк.
  • Оператор "-", преобразование в числа, вычитание (-a, a*1, a/1) может выполнять неявное преобразование типов.
[] + {} 和 {} + []

(2) Логическое преобразование в число

  • 1 + правда = 2;
  • 1 + false = 1;

(3) Преобразование в логическое значение

  • второй за
  • while
  • if
  • Тернарное выражение
  • Количество операндов слева от || (логическое ИЛИ) и && (логическое И)

(4) Символ

  • не может быть преобразован в число
  • Может быть преобразовано в логическое значение (оба значения верны)
  • Можно преобразовать в строку "Символ(круто)"

(5) Свободное равенство и строгое равенство

Нестрогое равенство допускает приведение типов, а строгое равенство — нет.

① Строки и числа

  • преобразовать в число, а затем сравнить

② другой тип Логический тип

  • Boolean сначала конвертируют в цифру, а потом продолжают сравнивать

③ Объект и не объект

  • объект исполненияToPrimitive(объект), а затем перейти к сравнению

④ Список ложных значений

  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""

10. Познакомьтесь со встроенными объектами js

  • ObjectдаJavascriptРодительский объект всех объектов в;
  • Другие объекты класса инкапсуляции данных:Object,Array,Boolean,Numberа такжеString;
  • Другие объекты:Function,Arguments,Math,Date,RegExp,Error.

11. Какие методы есть в js для определения объектов

  • Литералы объекта:let obj = {};
  • Конструктор:let obj = new Object();
  • Объект.создать():let obj = Object.create(object.prototype);

12. Как определить, является ли объект пустым объектом?

Object.keys(obj).length === 0

13. Рукописный вопрос: получить параметр url getUrlParams(url)

//封装函数getUrlParams, 将URL地址的参数解析为对象
function getUrlParams(url){
    let obj = {};

    if(url.indexOf('?') === -1){
        return obj;
    }

    let first_res = url.split('?')[1];
    let second_res = first_res.split('&');
    
    for(let i in second_res){
        third = second_res[i].split('=');
        obj[third[0]] = third[1];
    }
    return obj;
}


// 测试代码

let URL = 'https://www.sogou.com/web?ie=UTF-8&query=搜索内容&_em=3';
console.log(getUrlParams(URL));

14. Какие функции может вызывать массив?

  • pushдобавить элемент в конец массива
  • popУдалить и вернуть последний элемент массива
  • spliceдобавить/удалить элементы
  • sliceВозвращает выбранный элемент
  • shiftудалить первый элемент и вернуться
  • unshiftДобавьте один или несколько элементов в начало массива и верните новую длину
  • sortСортировать элементы массива
  • findВозвращает первый элемент массива, прошедший проверку
  • findIndex
  • map/filter/reduceРавный метод функционального программирования
  • Методы в цепочке прототипов:toString/valueOf

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

Это массивоподобный класс, который относится к категории утиных типов, но выглядит как массив.

  • ... оператор
  • Array.from
  • Array.prototype.slice.apply(arguments)

16. Рукописный вопрос: Как определить тип массива?

// 方法一:instanceof方法
let arr = [1, 2, 3];
console.log(arr instanceof Array);

// 方法二:constructor方法
let arr = [1, 2, 3];
console.log(arr.constructor === Array);

// 方法三:isArray方法
let arr = [1, 2, 3];
console.log(Array.isArray(arr));

// 方法四:Object.prototype方法
let arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr) === '[object Array]');

// 方法五:Array.__proto__方法
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype);

// 方法六:Object.getPrototypeOf方法
let arr = [1, 2, 3];
console.log(Object.getPrototypeOf(arr) === Array.prototype);

// 方法七:Array.prototype.isPrototypeOf方法
let arr = [1, 2, 3];
console.log(Array.prototype.isPrototypeOf(arr));

17. Рукописный вопрос: сортировка быстро перемешивает массив

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(() => Math.random() - 0.5);
//利用sort,返回结果为大于等于0时被交换位置,小于0时交换位置。

18. Рукописный вопрос: Операция дедупликации массива

/*   数组去重:让数组所有元素都独一无二,没有重复元素   */

// 创建一个含有重复元素的数组
let arr = [1, 1, 2, 3, 3, 6, 7, 2, 9, 9]

// 第一种方法:利用 Set数据结构 + Array.from() 函数
function removeRepeat1(arr) {
    return Array.from(new Set(arr))
}

// 第二种方法: 利用 Set数据结构 + ...扩展运算符
function removeRepeat2(arr) {
    return [...new Set(arr)]
}

// 第三种方法: 利用 indexOf 函数
function removeRepeat3(arr) {
    let new_arr = []

    for(let i in arr) {

        let item = arr[i]

        if(new_arr.indexOf(item) === -1) {
            new_arr.push(item)
        }
    }

    return new_arr
}

// 第四种方法: 利用 includes 函数
function removeRepeat4(arr) {
    let new_arr = []

    for(let i in arr) {

        let item = arr[i]

        if(!new_arr.includes(item)) {
            new_arr.push(item)
        }
    }

    return new_arr
}

// 第五种方法: 利用 filter 函数
function removeRepeat5(arr) {
    return arr.filter((value, index) => {
        return arr.indexOf(value) === index
    })
}

// 第六种方法: 利用 Map 数据结构
function removeRepeat6(arr) {

    let map = new Map()
    let new_arr = []

    for(let i in arr) {
        
        let item = arr[i]

        if(!map.has(item)) {
            map.set(item, true)
            new_arr.push(item)
        }
    }

    return new_arr
}

// 测试方法
console.log(removeRepeat1(arr));
console.log(removeRepeat2(arr));
console.log(removeRepeat3(arr));
console.log(removeRepeat4(arr));
console.log(removeRepeat5(arr));
console.log(removeRepeat6(arr));

19. Рукописный вопрос: выравнивание массивов

/*  数组扁平化就是将多维数组转成一维数组   */

// 多维数组
let arr = [1, 2, [3, 4, [6, 7]]]

// 第一种方法:利用 flat() 函数
function flatArr1(arr) {
    return arr.flat(Infinity)
}

// 第二种方法: 正则匹配
function flatArr2(arr) {
    return JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']')
}

// 第三种方法:利用 reduce() 遍历所有的元素
function flatArr3(arr) {
    return arr.reduce((i, j) => {
        return i.concat(Array.isArray(j)? flatArr3(j) : j)
    }, [])
}

// 第四种方法:直接使用递归函数
function flatArr4(arr) {
    let new_arr = []                                                     

    function innerArr(v) {
        for(let i in v) {
            let item = v[i]

            if(Array.isArray(item)) {
                innerArr(item)
            } else {
                new_arr.push(item)
            }
        }
    }

    innerArr(arr)

    return new_arr
}

// 方法测试
console.log(flatArr1(arr));
console.log(flatArr2(arr));
console.log(flatArr3(arr));
console.log(flatArr4(arr));

20.newКакой конкретно оператор это делал?

newПроцесс объекта:

  • Создаватьпустой объект;
  • на новом объекте[prototype]связывание (т.son. __ proto __ =father.prototype);
  • новые объекты и вызовы функцийthisбудет связан;
  • Выполнить метод в конструкторе;
  • если функциянет возвращаемого значенияЭтот новый объект автоматически возвращается.

21. Рукописные вопросы: рукописный новый метод

function father(name){
    this.name = name;
    this.sayname = function(){
        console.log(this.name);
    }
}

function myNew(ctx, ...args){ //...args为ES6展开符,也可以使用arguments
    // 先用Oject创建一个空的对象
    let obj = new Object();
    // 新对象会执行prototype连接
    obj.__proto__ = ctx.prototype;
    // 新对象和函数调用的this绑定起来
    let res = ctx.call(obj, ...args);
    // 判断函数返回值如果是null或者undefined则返回obj,否则就返回res
    return res instanceof Object ? res : obj;
}

let son = myNew(father, 'Bob');
son.sayname();

22. Как js реализует наследование?

  • Наследование цепочки прототипов
  • Кража наследования конструктора
  • наследование композиции
  • прототипное наследование
  • Наследование
  • Наследование паразитарного сочетания
  • наследование классов

23. Механизм сборки мусора в JS

Проще говоря, механизм сборки мусора таков:Очистите бесполезные переменные, освободите больше памяти и покажите лучшую производительность.

необходимость:из-занить,объекта такжемножествоФиксированного размера нет, поэтому они могут быть динамически выделены только тогда, когда известен их размер.

JavaScriptКаждый раз, когда программа создает строку, массив или объект, интерпретатор долженВыделить памятьхранить этоорганизация. Всякий раз, когда память динамически распределяется таким образом, она в конечном итоге освобождается, чтобы ее можно было использовать повторно, в противном случаеJavaScriptИнтерпретатор займет всю доступную память в системе, что приведет к сбою системы.

Этот отрывок объясняет, зачем системе нужна сборка мусора,JSВ отличие отC/C++, который имеет свой собственный набормеханизм сбора мусора(GarbageCollection).

JavaScriptИнтерпретатор может определить, когда программа больше не использует объект.Когда он определяет, что объект бесполезен, он знает, что объект больше не нужен, и может освободить занимаемую им память.

Например:

var a="hello world"; 
var b="world"; 
var a=b; 
//这时,会释放掉"hello world",释放内存以便再引用

Методы сбора мусора: метод маркировки, метод подсчета ссылок.

разметка

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

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

подсчет ссылок

Другой менее распространенный метод –подсчет ссылок, подсчет ссылок означает, сколько раз на каждое значение не ссылаются. Когда переменная объявляется и ей присваивается значение ссылочного типа, число ссылок на это значение равно1; И наоборот, если переменная, содержащая ссылку на это значение, получает другое значение, количество ссылок на исходное значение ссылки уменьшается.1, когда количество ссылок на это значение равно0, это означает, что нет никакого способа получить доступ к этому значению, поэтому занятая память освобождается, так что, когда сборщик мусора снова запустится, количество ссылок будет освобождено.0этих значений.

Будут утечки памяти с подсчетом ссылок,Вот почему:

function problem() { 
    var objA = new Object();
    var objB = new Object();
    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}

В этом примереobjAа такжеobjBСсылайтесь друг на друга через их соответствующие свойства, в этом случае количество ссылок на оба объекта равно2, в стратегии использования подсчета ссылок после выполнения функции оба объекта покидают область видимости, а после выполнения функции, потому что счетчик не0, такие взаимные ссылки приведут к утечкам памяти, если их будет большое количество.

особенно вDOMОбъекты также подвержены этой проблеме:

var element=document.getElementById(’‘); 
var myObj=new Object(); 
myObj.element=element; 
element.someObject=myObj;

Таким образом, не будет процесса сборки мусора.

🤐Три, объем, цепочка прототипов, замыкание

1 Область применения

(1) Что такое масштаб?

ES5Есть только две области видимости:глобальная областьа такжеобъем функции. существуетJavascript, мы определяем область каксвод правил, который определяет, как движок выполняет поиск переменных (имя переменной и функции) на основе имен идентификаторов в текущей области и вложенных подобластях.

объем, то есть при обращении к переменной, когда компилятор будет выполнять этот код, он сначала будет начинать стекущий объемНайдите, есть ли этот идентификатор в , если не найден, то пойдетродительская областьНайти, продолжить, если родительская область не найденаПогляди,доглобальная областьдо. понимается как заявленное в этом контекстеПеременнаяи объявилОбъем, его можно разделить наобласть действия блокаа такжеобъем функции.

(2) Что такое цепочка областей действия?

  • Цепочку областей действия можно рассматривать как цепочку, последовательно соединяющую переменные объекты.
  • Области различаются в каждой среде выполнения.
  • Когда мы обращаемся к переменной, она будет следовать цепочке областей видимости текущей среды выполнения, начиная с начала цепочки областей видимости и по очереди просматривая соответствующие переменные, пока не будет найден конец цепочки областей видимости и ошибка сообщаетсяundefined.
  • Цепочка объемов гарантируетУпорядоченный доступ к переменным.
  • Уведомление: Цепочка областей видимости может толькодоступ вверх,прибытьwindowОбъект прекращен.

2. Цепочка прототипов

(1) Что такое прототип? Что такое цепочка прототипов?

  • Прототипы и цепочки прототипов:существуетJavascript, каждый объект инициализирует внутри себя свойство, котороеprototype(прототип). Когда мы обращаемся к свойству объекта, если свойство не существует внутри объекта, то оно перейдет кprototypeискать это свойство, этоprototypeбудет иметь свой собственныйprototype, поэтому продолжайте искать так, чтобы пошаговый поиск выглядел как цепочка, и через[[prototype]]соединение атрибута, процесс этого соединения называетсяСеть прототипов.
  • связь:instance.constructor.prototype === instance.__ proto __;

(2) Что такое наследование цепочки прототипов?

Наследование цепочки прототипов, аналогично наследованию классов, т.е. когда есть два конструктораAа такжеB, который будет конструкторомAобъекта-прототипа через его[[prototype]]Когда свойство подключается к объекту-прототипу другого конструктора B, этот процесс вызываетсяпрототипное наследование.

(3) Рукописные вопросы: принцип экземпляра цепочки прототипов

// 判断A是否为B的实例
const instanceOf = (A, B) =>{
    // 定义一个指针P指向A
    let p = A;
    // 当P存在时则继续执行
    while(p){
        // 判断P值是否等于B的prototype对象,是则说明A是B的实例
        if(p === B.prototype){
            return true;
        }
        // 不断遍历A的原型链,直到找到B的原型为止
        p = p.__proto__;
    }
    return false;
}

console.log(instanceOf([], Array));

3. Закрытие

(1) Что такое замыкание?

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

(2) Процесс выполнения кода js

Прочитав определение замыкания, давайте разберемсяjsполное выполнение кода,детали следующим образом:

JavaScriptВесь процесс выполнения кода делится на два этапа,этап компиляции кодаа такжеэтап выполнения кода. Этап компиляции состоит изпереводчикГотово, переводим код в исполняемый код, этот этапправила области видимостибудет в порядке. Этап выполнения состоит издвигательГотово, основная задача — выполнить исполняемый код, на этом этапе создается контекст выполнения.

闭包是什么

(3) Как вообще генерировать замыкания?

  • функция передается как возвращаемое значение
  • Функция возвращается как параметр

(4) Суть генерации замыканий

  • В текущей среде есть указательродительская областьцитаты

(5) Характеристики затворов

  • Функция внутри функции
  • Внутренние функции могут ссылаться на внешние параметры и переменные.
  • Параметры и переменные не будут собираться механизмом сборки мусора.

(6) Преимущества и недостатки затворов

  • преимущество: возможность реализации инкапсуляции, кэширования и т. д.
  • недостаток:потреблять память;Неправильное использование приведет к переполнению памяти.

(7) Решение

  • Перед выходом из функции удалите все неиспользуемые локальные переменные.

(8) пусть закрытие

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

(9) Сценарии применения затворов

  • каррирование функций
  • модуль

(10) Рукописные вопросы: каррирование функций

1) Что такое карри

Каррирование означает наличие функции, которая принимает функциюA, и может вернуть новую функцию, которая обрабатывает функциюAизостальные параметры.

2) Реализация кода

Ниже приведены три конкретных метода реализации.код показывает, как показано ниже:

/**
 *  函数柯里化:将一个接收多个参数的函数变为接收任意参数返回一个函数的形式,便于之后继续调用,直到最后一次调用,才返回结果值  
    例子:有一个add函数,用于返回所有参数的和,add(1, 2, 3, 4, 5) 返回的是15
          现在要将其变为类似 add(1)(2)(3)(4)(5) 或者 add(1)(2, 3, 4)(5) 的形式,并且功能相同
 */

// 普通的add()函数
function add(){
    let sum = 0;
    let args = [...arguments];
    for(let i in args){
        sum += args[i];
    }
    return sum;
}

// 第一种add()函数柯里化方式
// 缺点:最后返回的结果是函数类型,但会被隐式转化为字符串,调用toString()方法

function add1(){
    // 创建数组,用于存放之后接收的所有参数
    let args = [...arguments];

    function getArgs(){
        args.push(...arguments);
        return getArgs;
    }

    getArgs.toString = function(){
        return args.reduce((a,b) => {
            return a + b;
        })
    }

    return getArgs;
}

// 第二种add()函数柯里化方式
// 缺点:需要在最后再自调用一次,即不传参调用表示已没有参数了
function add2(){
    let args = [...arguments];

    return function(){
        // 长度为0时直接把所有数进行相加
        if(arguments.length === 0){
            return args.reduce((a,b) => {
                return a + b;
            })
        }else{
            // 定义一个_args为了用来遍历
            let _args = [...arguments];
            // 长度不为0时要进行遍历
            for(let i = 0; i < _args.length; i++){
                args.push(_args[i]);
            }
            return arguments.callee;
        }
    }
}

// 第三种add()函数柯里化方式
// 缺点:在刚开始传参之前,设定总共需要传入参数的个数
function add3(length){
    // slice(1)表示从第二个元素开始取值
    let args = [...arguments].slice(1);

    return function(){
        args = args.concat([...arguments]);
        if(arguments.length < length){
            return add3.apply(this, [length - arguments.length].concat(args));
        }else{
            // 返回想要实现的目的
            return args.reduce((a,b) => a + b);
        }
    }
}

// 测试代码
let res = add(1,2,3,4,5);
let res1 = add1(1)(2)(3)(4)(5);
let res2 = add2(1)(2,3,4)(5)();
let res3 = add3(5);

console.log(res);
console.log(res1);
console.log(res2);
console.log(res3(1)(2,3)(4)(5));

(11) Дополнение

Подробное дополнение к статье 👇

4. Переменный объект

(1) Переменный объект

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

(2) Активные объекты

Активный объект (АО): Когда контекст, в котором находится переменный объект,active ECкогда это становитсяАктивный объект.

(3) Вариативное продвижение

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

Во время создания контекста выполнения выполняются три вещи:Создать переменный объект,Создайте цепочку областей действия,Убедитесь, что это указывает на, в процессе создания вариативного объекта первым будетargumentsСоздайте свойство со значениемarguments, затем сканируетfunctionОбъявление функции, создайте свойство с тем же именем, значением является ссылка на функцию, а затем отсканируйте кодvarОбъявление переменной, создайте свойство с тем же именем, значениеundefined, то есть переменное продвижение.

Конкретные примеры приведены ниже:

js (b) //call b
console.log(a) //undefined
let a = 'Hello World';
function b(){
console.log('call b');
}
b(); // call b second 
function b() { 
    console.log('call b fist');
}
function b() { 
    console.log('call b second');
}
var b = 'Hello world';

😜 4. События

1, модель событий

Возникновение событий, определенных в W3C, проходит через три этапа: захват, наценок и пузырька.

  • Всплывающие события: при использовании всплывающих событий дочерний элемент срабатывает первым, а родительский элемент срабатывает позже.
  • Захваченные события: при использовании захвата событий родительский элемент срабатывает первым, а дочерний элемент срабатывает позже.
  • DOMПотоковая передача событий: поддерживаются обе модели событий: захват событий и всплывающие события.

2. Как реализуется событие?

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

Например, при нажатии кнопки это событие (Event), а сегмент кода, отвечающий за обработку события, обычно называют обработчиком события (Event Handler), что является действием «инициации отображения диалогового окна».

существуетWebконец, то, что мы обычно видим,DOMмероприятие:

  • События уровня DOM0, прямо вhtmlПривязать к элементуon-event,Напримерonclick, в случае отменыdom.onclick = null,У одного и того же события может быть только один обработчик., задняя часть перезапишет переднюю.
  • События уровня DOM2,пройти черезaddEventListenerЗарегистрируйтесь на мероприятия черезremoveEventListenerЧтобы удалить события, событие может иметь несколько обработчиков событий, выполняющихся последовательно, перехватывающих события и всплывающих событий.
  • События уровня DOM3, добавляя типы событий, такие какUIсобытия, события фокуса, события мыши.

3. Как добавить мониторинг событий?

События отслеживаются через onclick и addEventListener.

4. Что такое делегирование событий?

(1) Определение

  • Делегация мероприятия, также известная какделегация мероприятия,ДаJavascriptобычно используется впривязать событиеНавык.
  • Брокер событий — этоСобытие, которое изначально нужно было связатьПорученородительский элемент, пусть родительский элемент играет роль прослушивателя событий.

(2) Принцип

  • Принцип делегирования событийDOMСобытие элемента всплывает.

(3) Преимущества

  • Преимущество использования делегатов событий состоит в том, что вы можетеПовысить производительность.
  • Это может сэкономить много памяти и уменьшить количество регистраций событий, например, вulВсе на агентеliизclickмероприятие.
  • может быть достигнуто, когдаДобавить дочерний объектНет необходимости привязывать его снова.

(4) Дополнение

Подробное дополнение к статье (событие)👇

5. Расскажите о цикле событий

(1) Определение

первый,jsдаодин потокДа, основная задачаобрабатывать взаимодействие с пользователем, а взаимодействие пользователя есть не что иное, как ответDOMКак обрабатывать ответ на событие?

различные браузерыWeb APIЭто предоставит отдельное рабочее пространство для асинхронного кода.Когда асинхронный код завершится, обратный вызов в коде будет отправлен наTask Queue(очередь задач), подождите, пока стек вызовов не станет пустым, а затем поместите функцию обратного вызова из очереди в стек вызовов для выполнения.Когда стек пуст и очередь задач также пуста, стек вызовов будет продолжать определять, в очереди задач есть код, нужно выполнить, процесс завершенEvent loop.

Следует также отметить, что,jsУ движка есть приоритеты в процессе исполнения,jsДвижок будет выполняться первым в цикле событийjsОсновная задача ветки, а потом пойду искать есть лимикрозадачи microtask(promise), если есть, то сначала выполните микрозадачу, если нет, то идите искатьзадача макроса macrotask(setTimeout、setInterval)выполнить.

事件循环与任务队列

(2) Часто используемые макрозадачи и микрозадачи

1) Обычно используемые макрозадачи и микрозадачи:

имя Пример (обычно используется)
задача макроса сценарий, setTimeout, setInterval, setImmediate, ввод-вывод, визуализация пользовательского интерфейса
микрозадачи process.nextTick(), Обещание

подал апелляциюsetTimeoutа такжеsetIntervalи т. д. — это все источники задач, и то, что действительно входит в очередь задач, — это задачи, которые они распределяют.

2) Приоритет

  • setTimeout = setInterval очередь
  • setTimeout > setImmediate
  • process.nextTick > Promise
for(const macroTask of macroTaskQueue){
    // 2.再执行宏任务
    handleMacroTask();
    for(const microTask of microTaskQueue){
        // 1.先执行微任务
        handleMicroTask();
    }
}

(3) Сколько времени занимает setTimeout(fn, 0) для выполнения цикла событий?

setTimeoutприводить в порядокочередьВнутри, а затем дождитесь, пока стек вызовов функций опустеет, прежде чем начать выполнение, а порядок, в котором эти операции попадают в очередь, определяется установленным временем задержки.

(4) Дополнение

Подробное дополнение к статье (цикл событий)👇

🤪 Пять, этот вопрос

1. Опишите это (расскажите о понимании этого предмета)

  • this, контекст, в котором выполняется функция, всегда указывает на функциюпрямой абонент(а не косвенный вызывающий абонент), доступ к которому можно получить черезapply,call,bindИзменятьthisуказывает на.
  • Если естьnewключевые слова,thisнаправлениеnewобъект, который вышел.
  • В случае,thisУказывает на объект, вызвавший это событие, в частности,IEсерединаattachEventсерединаthisвсегда указывает на глобальный объектwindow.
  • дляанонимная функцияилифункция вызывается напрямуюСказать,thisнаправлениеглобальный контекст(Браузерwindow,NodeJSдляglobal) остальная часть функции вызывает, это кто назвал это,thisуказать кому.
  • дляes6Стрелочная функция , направление стрелочной функции зависит отгде объявлена ​​стрелочная функция, где объявлено,thisуказать куда.

2. Четыре правила этой привязки

Четыре правила этой привязки следуют следующему порядку:

Новые привязки > Явные привязки > Неявные привязки > Привязки по умолчанию

Четыре правила описаны ниже.

(1) Новая привязка

  • Новая привязка:newВызов функции создает совершенно новый объект и связывает этот объект с вызовом функции.this.NewПри привязке, еслиnewжестко связанную функцию, затем используйтеnewВновь созданный объект заменяет эту жесткую привязкуthis.Конкретный код реализации выглядит следующим образом:
function foo(a) { 
    this.a = a; 
}
var bar = new foo(2); 
console.log(bar.a); //2

(2) Явная привязка

  • показать привязку: запустив функциюcallа такжеapply, чтобы показать границуthis.Конкретный код реализации выглядит следующим образом:
function foo() { 
    console.log(this.a); 
}
var obj = { 
    a: 2 
};
foo.call(obj); //2
  • показать привязкужесткий переплет
function foo(something) { 
    console.log(this.a, something); 
    return this.a + something; 
}
function bind(fn, obj) { 
    return function() { 
        return fn.apply(obj, arguments); 
    }; 
}
var obj = { 
    a: 2 
}
var bar = bind(foo, obj);

(3) Неявное связывание

  • неявное связывание: Есть ли у вызывающего местоположенияобъект контекста, или независимо от того, принадлежит ли он объекту или содержится в нем, правила неявной привязки будутthisПривязать к этому объекту контекста. Более того, цепочка свойств объекта имеет только верхний слой или последний слой в иерархии.местоположение звонкаФункция.Конкретный код реализации выглядит следующим образом:
function foo() { 
    console.log(this.a); 
}
var obj = { 
    a: 2, 
    foo: foo, 
}
obj.foo(); // 2

(4) Привязка по умолчанию

  • привязка по умолчанию: никаких других модификаторов (bind,apply,call), в нестрогом режиме определяет указатели наглобальный объект, который в строгом режиме определяет указатели наundefined.Конкретный код реализации выглядит следующим образом:
function foo() { 
    console.log(this.a); 
}
var a = 2; 
foo(); //undefined

3. Если конструктор связывает объект, будет ли экземпляр, созданный этим конструктором, наследовать свойства объекта? Почему?

не унаследует, потому что согласноthisСвяжите четыре правила,newСвязывание имеет приоритет передbindпоказать привязку, черезnewПри вызове конструктора создается новый объект, который заменяетbindпривязка объекта для этой функции какthis, и если эта функция не возвращает объект, возвращает только что созданный объект.

4. В чем разница между стрелочными функциями и обычными функциями? Можно ли использовать стрелочные функции в качестве конструкторов?

(1) Стрелочная функция и определение обычной функции

Обычная функцияпройти черезfunctionопределения ключевых слов,thisне могут быть объединенылексический охватИспользование, связывание во время выполнения зависит только от того, как вызывается функция, где она вызывается и где вызывается. (в зависимости от вызывающего абонента и от того, работает ли он независимо).

Использование функции стрелки называетсяжирная стрелаоперация=>чтобы определить, что стрелочные функции не применяют обычные функцииthisЧетыре правила привязки определяются в соответствии с областью действия внешнего слоя (функционального или глобального).this,а такжеПривязки функций стрелок не могут быть изменены(newни один).

(2) Разница между стрелочными функциями и обычными функциями

  • Стрелочные функции часто используются вПерезвонитев том числеобработчик событияилитаймер.
  • функция стрелки иvar self = this, пытаются заменить традиционныеthisрабочий механизм, будетthisКрепления оттянуты назад, чтобылексический охват.
  • нет прототипа, нетthis,нетsuper,нетarguments,нетnew.target.
  • не могу пройтиnewвызов по ключевому слову.
    • Внутри функции есть два метода:[[Call]]а также[[Construct]], черезnewПри вызове функции выполняется[[construct]]метод, создайте экземпляр объекта, а затем выполните тело функции, изменивthisПривязать к этому объекту экземпляра.
    • При прямом вызове выполнить[[Call]]метод, выполнить напрямуютело функции.
    • Стрелочные функции не[[Construct]]метод,нельзя использовать как вызов конструктора, когда используешьnewСообщается об ошибке при вызове функции.
function foo(){
    return (a) => {
        console.log(this.a);
    }
}

let obj1 = {
    a: 2
};

let obj2 = {
    a: 3
};

let bar1 = foo.call(obj1);
let bar2 = bar1.call(obj2);

console.log(bar1); // object
console.log(bar2); // 2 undefind

5. Знаете ли вы, что такое apply, call и bind?

(1) Разница между тремя

  • apply,call,bindВсе три являются методами функций, каждый из которых может изменить значение функции.thisнаправление.
  • applyа такжеcallменяются функцииthisуказать на и наВходящие параметрыФункция выполняется сразу после вызова.
  • bindменяет функциюthisУкажите и верните новую функцию после передачи параметров, которая не будет вызываться и выполняться немедленно.

(2) Способ передачи параметров

applyПереданный параметрмножествоФорма,callПереданный параметрВводите по порядку и через запятую,bindВходящий параметр может быть либомножествоформа также может бытьПроходить по одному по порядку.Подробнее см. ниже:

apply: 
Array.prototype.apply(this, [args1, args2]) 
ES6 之前用来展开数组调用,  foo.apply(null, []) , ES6 之后使用 ... 操作符;

call: 
Array.prototype.call(this, args1, args2)。

bind: 
Array.prototype.bind(this, args1, args2);
Array.prototype.bind(this, [args1, args2])。

(3) Рукописное применение, вызов, связывание

применять:

// 实现apply函数,在函数原型上封装myApply函数 , 实现和原生apply函数一样的效果

Function.prototype.myApply = function(context){

    // 存储要转移的目标对象
    _this = context ? Object(context) : window;

    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let key = Symbol('key');
    _this[key] = this;

    // 将数组里存储的参数拆分开,作为参数调用函数
    let res = arguments[1] ? _this[key](...arguments[1]) : _this[key]();

    // 删除
    delete _this[key];

    // 返回函数返回值
    return res;
}

// 测试代码
let obj = {
    'name': '张三'
}

function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}

showName.myApply(obj, [7,8,9]);

вызов:

// 实现call函数,在函数原型上封装myCall函数 , 实现和原生call函数一样的效果

Function.prototype.myCall = function(context){

    // 存储要转移的目标对象
    let _this = context ? Object(context) : window;

    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let  key = Symbol('key');
    _this[key] = this;

    // 创建空数组,存储多个传入参数
    let args = [];

    // 将所有传入的参数添加到新数组中
    for(let i =1; i < arguments.length; i++){
        args.push(arguments[i]);
    }

    // 将新数组拆开作为多个参数传入,并调用函数
    let res = _this[key](...args);

    // 删除
    delete _this[key];

    // 返回函数返回值
    return res;
}

let obj = {
    'name': '张三'
}

function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}

showName.myCall(obj, 7, 8, 9);

связывать:

// 实现Bind函数,在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果

Function.prototype.myBind = function(context){

    // 存储要转移的目标对象
    let _this = context ? Object(context) : window;

    // 在转移this的对象上设定一个独一无二的属性,并将函数赋值给它
    let key = Symbol('key');
    _this[key] = this;

    // 创建函数闭包
    return function(){

        // 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
        let args = [].concat(...arguments);

        // 调用函数
        let res = _this[key](...args);

        // 删除
        delete _this[key];

        // 返回函数返回值
        return res;
    }
}

// 测试代码
let obj = {
    'name' : '张三'
}

function showName(first, second, third){
    console.log(first, second, third);
    console.log(this.name);
}

showName.myBind(obj)([7,8,9]);

😋 Шесть, проблема с Аяксом

1. Принцип Аякса

  • AjaxПринцип просто в том, что вПользовательа такжесервердобавил один междусредний слой(движок AJAX), черезXMLHTTPRequestобъект, чтобы инициировать асинхронный запрос к серверу, получить данные с сервера, а затем использоватьjavascriptработатьDOMИ обновить страницу.
  • Асинхронные действия пользователя и ответы сервера. Самый ответственный шаг в этомПолучить данные запроса с сервера.
  • AjaxВ процессе участвуют толькоJavascript,XMLHttpRequestа такжеDOMXMLHttpRequestдаajaxосновной механизм.

2. Ajax решает проблемы с кешированием браузера

  • существуетajaxдобавить перед отправкой запросаanyAjaxObj.setRequestHeader("If-Modified-Since","0").
  • существуетajaxдобавить перед отправкой запросаanyAjaxObj.setRequestHeader("Cache-Control","no-cache").
  • существуетURLзатем случайное число:"fresh=" + Math.random().
  • существуетURLс последующей отметкой времени:"nowtime=" + new Date().getTime().

3. однопоточный js

  • один поток: есть только один поток, который может делать только одно.

  • причина:избегатьDOMКонфликт рендеринга.

    • Браузер должен отображатьDOM;
    • JSможно изменитьDOMструктура;
    • JSПри выполнении браузерDOMрендеринг будет приостановлен;
    • два абзацаJSНе может выполняться одновременно (оба измененыDOMконфликт);
    • WebworkerМногопоточность поддерживается, но недоступнаDOM.
  • решение:асинхронный.

4. Реализация асинхронного программирования

(1) Функция обратного вызова

  • Достоинства: Простой, понятный
  • Недостатки: неудобен в обслуживании, высокая связанность кода

(2) Мониторинг событий (применяется режим управления по времени в зависимости от того, происходит ли событие)

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

(3) Публикация/подписка (режим наблюдателя)

  • Аналогично прослушивателям событий, но доступ к ним можно получить через "Центр сообщений», чтобы увидеть, сколько сейчас издателей, сколько подписчиков

(4) Объект обещания

  • Преимущество: можно использоватьthenметод, проводитьЦепное письмо; может писать ошибки, когдаПерезвоните;
  • Недостатки: относительно сложно написать и понять

(5) Генераторная функция

  • Преимущества: внутри и снаружи функцииобмен данными,механизм обработки ошибок
  • недостаток:Управление процессомнеудобно

(6) асинхронная функция

  • Достоинства: встроенный исполнитель, лучшая семантика, более широкая применимость, возвратPromise, структура ясна.
  • Недостаток: механизм обработки ошибок

5. Проблема с загрузкой JS-скрипта, асинхронность, проблема с отсрочкой

  • Если вы полагаетесь на другие сценарии иDOMВ результате с помощьюdefer.
  • если сDOMи другие зависимости сценария не являются сильными, используйтеasync.
  • Резюме: сильные зависимостиdefer, зависимость не сильнаяasync.

6. О window.onload и DOMContentLoaded

window.addEventListener('load', function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
});

document.addEventListener('DOMContentLoaded', function(){
    //DOM 渲染完即可执行,此时图片、视频还可能没加载完 -> 尽量选择此方法
});

7. Разница между ajax, axios и fetch

(1) аякс

  • сама направлена ​​наMVCпрограммирование, не соответствующее текущему интерфейсуMVVMволна.
  • местныйXHRразвивать,XHRСама структура непонятна, уже естьfetchальтернатива.
  • JQueryВесь проект слишком большой, просто используйтеajaxно представить всюJQueryОчень необоснованно (вы не можете пользоваться персонализированным пакетом услугCDNСлужить).

(2) аксиомы

  • Создать из браузераXMLHttpRequest.
  • отnode.jsпроблемаhttpпросить.
  • служба поддержкиPromise API.
  • Перехват запросов и ответов.
  • Преобразование данных запроса и ответа.
  • Отмените запрос.
  • автоматическое преобразованиеJSONданные.
  • Поддержка клиентов предотвращаетCSRF/XSRF.

(3) принести

  • fetchвернутьpromiseне будет помечен какreject, даже еслиhttpКод состояния ответа404или500. будет помечен только в случае сбоя сети или если запрос заблокированreject.
  • Сообщать об ошибках только для сетевых запросов, да400,500Все запросы считаются успешными и должны быть инкапсулированы для обработки.
  • здесь дляcookieОбработка особенная, разные браузерыcredentialsЗначение по умолчанию отличается, что делает значение по умолчаниюcookieстать неуправляемым.
  • без собственногоabort, контроля тайм-аута нет, можно использоватьAbortControllerИсправить проблему с запросом на отмену.
  • Нет возможности изначально отслеживать ход выполнения запроса, иXHRМогу.

8. Рукописные вопросы: рукописные функции Ajax

/*
	1. get()方法
	   参数:url(请求的地址)、data(携带数据)、callback(成功回调函数)、dataType(返回数据类型)
	2. post()方法
	   参数:url(请求的地址)、data(携带数据)、callback(成功回调函数)、dataType(返回数据类型)
	3. ajax()方法
	   参数:obj(对象中包含了各种参数),其中有url、data、dataType、async、type
*/

let $ = {
	createXHR: function() {
		if(window.XMLHttpRequest) {
			return new XMLHttpRequest()
		} else {
			return new ActiveXObject()
		} 
	},
	get: function(url, data, callback, dataType) {
		let dataType = dataType.toLowerCase()
		if(data) {
			url += '?'
			Object.keys(data).forEach(key => url += `${key}=${data[key]}&`)
			url = url.slice(0, -1)
		}
		let xhr = this.createXHR()

		xhr.open('get', url)
		xhr.send()
		xhr.onreadystatechange = function() {
			if(xhr.readyState === 4) {
				if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
					let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
					callback(res, xhr.status, xhr)
				}
			}
		}
	},
	post: function(url, data, callback, dataType) {
		let dataType = dataType.toLowerCase()

		let xhr = this.createXHR()

		let str = ''
		if(data) {
			Object.keys(data).forEach(key => str += `${key}=${data[key]}&`)
			str = str.slice(0, -1)
		}
		xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
		xhr.send(str)
		xhr.onreadystatechange = function() {
			if(xhr.readyState === 4) {
				if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
					let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
					callback(res, xhr.status, xhr)
				}
			}
		}
	},
	ajax: function(params) {
		// 初始化参数
		let type = params.type ? params.type.toLowerCase() : 'get'
		let isAsync = params.isAsync ? params.isAsync : 'true'
		let url = params.url
		let data = params.data ? params.data : {}
		let dataType = params.dataType.toLowerCase()

		let xhr = this.createXHR()
		
		let str = ''
		
		// 拼接字符串
		Object.keys(data).forEach(key => str += `${key}=${data[key]}&`)
		str = str.slice(0, -1)
		
		if(type === 'get') url += `?${str}`;

		return new Promise((resolve, reject) => {
			// 创建请求
			xhr.open(type, url, isAsync)

			if(type === 'post') {
				xhr.setRequestHeader('Content-Type', 'application/x-www-form-rulencoded')
				xhr.send(str)
			} else {
				xhr.send()
			}

			xhr.onreadystatechange = function() {
				if(xhr.readyState === 4) {
					if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
						let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText
						resolve(res) // 请求成功,返回数据
					} else {
						reject(xhr.status) // 请求失败,返回状态码
					}
				}
			}
		})	
	}
}

9. Рукописные вопросы: принцип рукописного обещания

class MyPromise{
    constructor(fn){
        this.resolvedCallbacks = [];
        this.rejectCallbacks = [];
        
        // pending 即在等待状态
        this.state = 'PENDING';
        this.value = '';
        
        fn(this.resolve.bind(this), this.reject.bind(this));
    }

    resolve(value){
        if(this.state === 'PENDING'){
            this.state = 'RESOLVED';
            this.value = value;

            this.resolvedCallbacks.map(cb => cb(value));
        }
    }

    reject(value){
        if(this.state === 'PENDING'){
            this.state = 'REJECTED';
            this.value = value;

            this.rejectCallbacks.map(cb => cb(value));
        }
    }

    then(onFulfilled, onRejected){
        if(this.state === 'PENDING'){
            this.resolvedCallbacks.map(cb => cb(onFulfilled));
            this.rejectCallbacks.map(cb => cb(onRejected));
        }

        if(this.state === 'RESOLVED'){
            onFulfilled(this.value);
        }

        if(this.state === 'REJECTED'){
            onRejected(this.value);
        }
    }
}

10. Рукописные вопросы: Рукописный Promise.all на основе Promise

/**
 * promise的三种状态:
 * 1.pending:等待状态,比如正在网络请求,或定时器没有到时间;
 * 2.fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调then函数
 * 3.reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调catch函数
 */
/*----------------------------------------------------------------- */
// 函数.then()
// 函数then是Promise中的一个方法,它会在Promise处于fulfill状态时调用触发
// resolve和reject是默认传入的函数参数
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 在Promise中调用resolve函数,会使Promise变为fulfill状态
        // resolve函数可以传入一个参数,作为then函数的默认传入参数
        resolve('成功');
    }, 1000);
})
.then(data => {
    console.log(data); //结果输出成功
});

/*----------------------------------------------------------------- */
// 函数 .catch()
// 函数catch是Promise的一个方法。它会在Promise处于reject状态时调用触发
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 在Promise调用reject函数,会使Promise变为reject状态
        // reject函数可以传入一个参数,作为catch函数的默认传入参数
        reject('失败');
    }, 1000)
})
.catch(err => {
    console.log(err); //结果输出:失败
})

/*----------------------------------------------------------------- */
// 函数.finally()
// 函数finally是Promise中的一个方法,它会在Promise的最后触发,无论Promise处于什么状态
new Promise((resolve, reject) => {
    setTimeout(() => {
       resolve('成功啦!')
    },1000)
})
.then(data => {
    console.log(data);
})
.finally(() => {
    console.log('Promise结束');
})

/* 
    结果输出:成功啦!
            Promise结束
*/

/*----------------------------------------------------------------- */
// 函数all()
// 函数all是Promise中的一个方法,它用于将多个promise实例,包装成一个新的promise实例
Promise.all([
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('我是第一个异步请求的数据');
        });
    }, 1000),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('我是第二个异步请求的数据');
        }, 1000);
    })
])
.then(results => {
    console.log(results); // ['我是第一个异步请求的数据', '我是第二个异步请求的数据']
})

/*----------------------------------------------------------------- */
// 实际应用
let string1 = 'I am';
new Promise((resolve, reject) => {
    setTimeout(() => {
        let string2 = string1 + 'Monday';
        resolve(string2);
    }, 1000);
})
.then(data => {
    return new Promise((resolve, reject) => {
        let string3 = data + 'in CSDN';
        resolve(string3);
    })
})
.then(data => {
    console.log(data);
})
.finally(() => {
    console.log('Promise结束');
})

/*
    输出结果:
    I am Monday in CSDN!
    Promise结束
*/

Подробное дополнение к статье (Обещание)👇

🥰 7. Дополнение рукописных вопросов

1. Оптимизация производительности

(1) Рукописная функция дросселирования

дросселирование: Выполняется один раз через равные промежутки времени, обычно используется в местах с высокой частотой срабатывания для уменьшения частоты. - Например: скольжение мышью, перетаскивание

С точки зрения непрофессионала, дросселирование — это переход от частого запуска выполнения к однократному выполнению через равные промежутки времени.

//封装节流函数,实现节流
function throttle(func, delay=500) {
    let timer = null;
    let status = false;

    return function (...args) {
        
        if(status) return;
        
        status = true;

        timer = setTimeout(() => {
            func.apply(this, args)
            status = false
        }, delay);
    }
}

(2) Функция защиты от сотрясений, написанная от руки

Стабилизатор: Запускается непрерывно в течение определенного периода времени, не выполняется до тех пор, пока последнее выполнение не превысит ограниченное время. -- Такие как:input,scrollпрокрутить

С точки зрения непрофессионала, защита от сотрясения — это от частого срабатывания триггера до последнего срабатывания.

//封装防抖函数,实现防抖
function denounce(func, delay=500){
    let timer = null;
    return function(...args){
        
        // 如果有值,清除定时器,之后继续执行
        if(timer){
            clearTimeout(timer);
        }

        timer = setTimeout(() => {
            func.apply(this, args);
        },delay);
    }
}

(3) Ленивая загрузка картинок

определение:

Ленивая загрузка выделяет слово «ленивая» Lazy означает задержку, поэтому «ленивая загрузка» означает «ленивая загрузка».ленивая загрузкаНапример, когда мы загружаем страницу, страница очень длинная и достаточно длинная, чтобы поместиться в видимой области нашего браузера, тогда ленивая загрузка заключается в том, чтобы сначала загрузить содержимое видимой области, а другие части входят в видимая область для загрузки.

Код:

let img = document.getElementsByTagName('img'); // 获取img标签相关的
let num = img.length; // 记录有多少张图片
let count = 0; // 计数器,从第一张图片开始计数

lazyload(); // 首次加载别忘了加载图片

function lazyload() {
    let viewHeight = document.documentElement.clientHeight; // clientHeight 获取屏幕可视区域的高度
    let scrollHeight = document.documentElement.scrollTop || document.body.scrollTop; // 滚动条卷去的高度
    for (let i = 0; i < num; i++) {
        // 元素现在已经出现在视觉区域中
        if (img[i].offsetTop < scrollHeight + viewHeight) {
            // 当src不存在时,跳出本轮循环,继续下一轮
            if (img[i].getAttribute('src') !== 'default.jpg') {
                continue;
            } else {
                // 当src属性存在时,获取src的值,并将其赋值给img
                img[i].src = img[i].getAttribute('data-src');
                count++;
            }
        }
    }
}

2. Нативный почерк API

(1) Foreach

Применение:

forEach()Метод выполняет заданную функцию один раз для каждого элемента массива. РоднойAPIКонкретный анализ выглядит следующим образом:

arr.forEach(function(currentValue, currentIndex, arr) {}, thisArg)

//currentValue  必需。当前元素
//currentIndex  可选。当前元素的索引
//arr           可选。当前元素所属的数组对象。
//thisArg       可选参数。当执行回调函数时,用作 this 的值。

Код:

Array.prototype.myForEach = function (fn, thisArg) {
    if (typeof fn !== 'function') {
        throw new Error('参数必须为函数');
    }
    if (!Array.isArray(this)) {
        throw new Error('只能对数组使用forEach方法');
    }
    let arr = this;
    for (let i = 0; i < arr.length; i++) {
        fn.call(thisArg, arr[i], i, arr);
    }
}

// 测试
let arr = [1, 2, 3, 4, 5];
arr.myForEach((item, index) => {
    console.log(item, index);
});

// 测试 thisArg
function Counter() {
    this.sum = 0;
    this.count = 0;
}

// 因为 thisArg 参数(this)传给了forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值
Counter.prototype.add = function (array) {
    array.myForEach(function (entry) {
        this.sum += entry;
        ++this.count;
    }, this);
}

const obj = new Counter();
obj.add([2, 5, 9]);

console.log(obj.count); // 3 === (1 + 1 + 1)
console.log(obj.sum); // 16 === (2 + 5 + 9)

(2) карта

Применение:

mapФункция обрабатывает каждый элемент массива по очереди и возвращает новый массив, не затрагивая исходный массив.

array.map(function(currentValue,index,arr){})

Код:

Array.prototype.myMap = function (arr, mapCallback) {
    // 检查参数是否正确
    if (!Array.isArray(arr) || !Array.length || typeof mapCallback !== 'function') {
        return [];
    } else {
        let result = [];
        for (let i = 0; len = arr.length; i++) {
            result.push(mapCallback(arr[i], i, arr));
        }
        return result;
    }
}

// 测试
let arr = [1, 2, 3, 4, 5];
arr.map((item, index) => {
    console.log(item * 2);
}); // 2, 4, 6, 8, 10

(3) фильтр

Применение:

filter()метод возвращаетМассив элементов, результат выполнения которых истинен.

arr.filter(function(item, index, arr){}, context)

Код:

Array.prototype.myFilter = function (fn, context) {
    if (typeof fn !== 'function') {
        throw new Error(`${fn} is not a function`);
    }

    let arr = this;
    let temp = [];

    for (let i = 0; i < arr.length; i++) {
        let result = fn.call(context, arr[i], i, arr);
        // 判断条件是否为真
        if (result) {
            temp.push(arr[i]);
        }
    }
    return temp;
}

// 测试
let arr = [1, 2, 3, 4, 5, 'A', 'B', 'C'];
console.log(arr.myFilter((item) => typeof item === 'string')); // [ 'A', 'B', 'C' ]

(4) уменьшить

Применение:

  • Параметры: функция обратного вызова, параметр инициализации (необязательно)
  • Параметр функции обратного вызова имеет 4 значения (res: представляет накопленное значение,cur: текущая стоимость,index: первые несколько,arr:передачаreduceмассив )
  • Возврат в целомresнакопленная стоимость
arr.reduce((res,cur, index, arr) => res+cur, 0)

Код:

/**
 * 
 * @param {fn} callback res→代表累加值,cur→目前值,index→第几个,arr→调用reduce的数组
 * @param {*} initialValue 初始化参数(可选)
 */

Array.prototype.myReduce = function (cb, initValue) {
    if (!Array.isArray(this)) {
        throw new TypeError("not a array");
    }
    // 数组为空,并且有初始值,报错
    if (this.length === 0 && arguments.length < 2) {
        throw new TypeError('Reduce of empty array with no initial value');
    }
    let arr = this;
    let res = null;
    // 判断有没有初始值
    if (arguments.length > 1) {
        res = initValue;
    } else {
        res = arr.splice(0, 1)[0]; //没有就取第一个值
    }
    arr.forEach((item, index) => {
        res = cb(res, item, index, arr); // cb 每次执行完都会返回一个新的 res值,覆盖之前的 res
    })
    return res;
};

// 测试结果
let arr = [1, 2, 3, 4];
let result = arr.myReduce((res, cur) => {
    return res + cur;
})
console.log(result); // 10

3. Другие рукописные вопросы

(1) Реализация JSONP

JSONPПринцип:JSONPпоявлениеscriptЭтикетки не подлежатТа же политика происхожденияОграничения, используемые для междоменных запросов, имеют то преимущество, чтохорошая совместимость, недостаток в том, чтоМожно использовать только дляGETпросить.

const jsonp = ({ url, params, callbackName }) => {
    const generateUrl = () => {
        let dataSrc = '';
        for (let key in params) {
            if (params.hasOwnProperty(key)) {
                dataSrc += `${key}=${params[key]}&`;
            }
        }
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
    }
    return new Promise((resolve, reject) => {
        const scriptElement = document.createElement('script')
        scriptElement.src = generateUrl()
        document.body.appendChild(scriptElement)
        window[callbackName] = data => {
            resolve(data)
            document.removeChild(scriptElement)
        }
    })
}

(2) Объект.создать

Применение:

Object.creat(object[,propertiesObject]), используется для создания нового объекта, и этот новый объект наследуетobjectхарактеристики. второй параметрpropertyObjectТакже объект, является необязательным параметром, который предназначен для уточнения для вновь созданных объектовобъект недвижимости. Объект свойств может содержать следующие значения:

Атрибуты иллюстрировать
configurable Указывает, является ли вновь созданный объект настраиваемым, то есть могут ли свойства объекта быть удалены или изменены, значение по умолчанию — false
enumerable Является ли свойство объекта перечислимым, то есть может ли оно быть перечисляемым, значение по умолчанию — false
writable Является ли объект доступным для записи или нужно ли добавлять новые свойства к объекту, по умолчанию false
get Функция получения объекта, по умолчанию не определена
set Функция установки объекта, по умолчанию не определена

Код:

/**
 * 
 * @param {*} proto 新创建对象的原型对象
 * @param {*} propertyObject 要定义其可枚举属性或修改的属性描述符的对象
 * @returns 
 */
Object.create2 = function (proto, propertyObject = undefined) {
    if (typeof proto !== 'object' && typeof proto !== 'function') {
        throw new TypeError('Object prototype may only be an Object or null.')
    }
    // 创建一个空的构造函数 F
    function F() { }
    // F 原型指向 proto
    F.prototype = proto
    // 创建 F 的实例
    const obj = new F()
    // propertiesObject有值则调用 Object.defineProperties
    if (propertyObject != undefined) {
        Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
        // 创建一个没有原型对象的对象,Object.create(null)
        obj.__proto__ = null
    }
    // 返回 这个 obj
    return obj
}

const person = {
    name: 'monday',
    printIntroduction: function() {
        console.log(`My name is ${this.name}, and my age is ${this.age}`);
    }
};

const me = Object.create2(person);

me.name = 'Tuesday'; 
me.age = 18; 

me.printIntroduction();
  

(3) Объект.назначить

Применение:

Object.assign()Методы используются для присвоения значений всех перечисляемых свойств одного или нескольких исходных объектов целевым объектам. Он вернет целевой объект.

Код:

Object.assign2 = function (target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object');
    }
    let res = Object(target);
    source.forEach(function (obj) {
        if (obj != null) {
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    res[key] = obj[key];
                }
            }
        }
    })
    return res;
}

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign2(target, source);

console.log(target); // { a: 1, b: 4, c: 5 }

console.log(returnedTarget); // { a: 1, b: 4, c: 5 }

(4) Рукописная публикация и подписка

Код:

class Subject {
    constructor(name) {
        this.name = name; // 被观察者的名字
        this.message = '今天是晴天'; // 存放一个值
        this.observers = []; // 存放所有观察者
    }

    on(observer) {
        this.observers.push(observer);
    }

    triggle(data) {
        this.message = data;
        this.observers.forEach(item => item.update(data));
    }
}

class Observer {
    constructor(name) {
        this.name = name;
    }
    update(newDate) {
        console.log(`我是观察者${this.name}: ${newDate}`);
    }
}

// 测试代码
let subject = new Subject('message');

let o1 = new Observer('小红');
let o2 = new Observer('小明');

subject.on(o1); // 我是观察者小红: 明天会下雨
subject.on(o2); // 我是观察者小明: 明天会下雨

subject.triggle('明天会下雨');

😉 Восемь, заключительные замечания

Вышеуказанные данные собираются по понедельникам в ходе осеннего процесса подготовки к поступлению.JavaScriptВсе приведенные выше вопросы интервью, приведенные выше вопросы интервью могут быть недостаточно полными, если вы хотите добавить больше контента, пожалуйста, свяжитесь с нами.vx:MondayLaboratory, я надеюсь сделать содержание статьи более совершенным и принести пользу большему количеству друзей, готовящихся к тесту~

Наконец, я желаю всем друзьям, которые читают эту статью, чтобы они смогли добиться своего любимогоoffer ~

🐣Пасхальное яйцо Еще одна вещь

(: приобретение контента в формате pdf

👉 WeChat следите за официальной учетной записью星期一研究室, щелкните панель навигации ниже简要Просмотр получения ключевых слов~

(: обновить адрес

👉Предложение поступает в колонку интервью

(: Дополнительная история

  • Если вы считаете, что эта статья полезна для вас, пожалуйста, поставьте лайк и поддержите ее ~~😉
  • Это все для этой статьи! Увидимся в следующий раз! 👋👋👋