От мелкого до глубокого: 66 баллов знаний JavaScript на собеседовании

JavaScript
От мелкого до глубокого: 66 баллов знаний JavaScript на собеседовании

предисловие

Я просто хочу встретиться с инженером по резюме, но интервьюер попросил меня бросить вызов инженеру-ракетчику, а в этом году ситуация еще хуже.两男, но как бы ни была трудна жизнь, мы должны продолжать, и мы должны продолжать находить работу. В недавнем интервью я подытожил, и я буду пересматривать его каждый раз, когда я возвращаюсь с интервью.Ниже приведены основные моменты интервью, с которыми я столкнулся за последние несколько дней. Но сегодняшняя тема — это 66 пунктов знания JavaScript, написанные в заголовке, от мелкого к более глубокому, распределенные на неделю, каждый (zhěng) день (lǐ) целиком (bù) (yì) 10 (qiú) слева (diǎn) справа (zàn) ), надеюсь, будет чем-то полезен тем, кто ищет работу.Если что-то не так с утверждением в тексте, пожалуйста, укажите на это.

HTML и CSS:

  • ядро браузера
  • Блочная модель, гибкая верстка, верстка в две/три колонки, центрирование по горизонтали/вертикали;
  • BFC, чистый поплавок;
  • CSS3 анимация, H5 новые функции.

JavaScript:

  • Наследование, цепочка прототипов, это указание, шаблон проектирования, вызов, применение, привязка;
  • новая реализация, троттлинг против сотрясений, let, var, const разница, временная мертвая зона, событие, цикл;
  • Обещание использования и реализации, обещание параллельного выполнения и последовательного выполнения;
  • Преимущества и недостатки async/await;
  • Замыкания, сборка мусора и утечки памяти, методы массива, нарушение порядка массива, выравнивание массива, делегирование событий, прослушивание событий, модель событий

Vue:

  • Принцип двусторонней привязки данных Vue;
  • Разница между принципом вычислений vue, вычислением и просмотром;
  • Схема структуры компилятора Vue, жизненный цикл, взаимодействие компонентов Vue;
  • Понимание режима mvvm и режима mvc;
  • vue дом diff, vuex, vue-маршрутизатор

Интернет:

  • HTTP1, HTTP2, HTTPS, общие коды состояния http;
  • Просмотрите, что произошло с момента ввода URL-адреса до нажатия Enter;
  • Фронтальная безопасность (CSRF, XSS)
  • Внешний междоменный интерфейс, кеш браузера, файл cookie, сеанс, токен, локальное хранилище, хранилище сеансов;
  • TCP-соединение (трехстороннее рукопожатие, четырехсторонняя волна)

связанные с производительностью

  • Как оптимизировать изображения
  • 500 изображений, как добиться оптимизации предварительной загрузки
  • Ленивая загрузка конкретной реализации
  • Способы уменьшить http-запросы
  • Как webpack настраивает большие проекты

Кроме того, я также разбираю более полный набор вопросов для интервью. Сначала я дам предварительный просмотр:

Перейдем к делу:

1. Познакомить с типами данных js и способом хранения значений

Подробности смотрите в моих предыдущих статьях:«Внешние пакеты» могут быть наиболее подробным объяснением типов данных JavaScript.

Всего в JavaScript имеется 8 типов данных, включая 7 основных типов данных: Undefined, Null, Boolean, Number, String, Symbol (новое в es6, представляющее уникальное значение) и BigInt (новое в es10);

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

Примитивные типы данных: хранятся непосредственно вкуча(стек), занимающий мало места и имеющий фиксированный размер, относится к часто используемым данным, поэтому хранится в стеке.

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

2. Что делают операторы && , || и !! соответственно

  • &&Вызывается логическим И, находит первое ложное выражение в своих операндах и возвращает его, а если ложных выражений не найдено, возвращает последнее истинное выражение. Он использует короткие замыкания, чтобы предотвратить ненужную работу.
  • ||Называется логическим или, находит первое выражение правды в его операндах и возвращает его. Это также использует короткое замыкание для предотвращения ненужной работы. Перед поддержкой параметров функции ES6 по умолчанию были поддержаны параметры, он использовался для инициализации значений параметров по умолчанию в функциях.
  • !!Оператор к значению правой стороны отливается в качестве логического значения, который является простым способом преобразования значения для логического значения.

3. преобразование типа данных js

В JS всего три случая преобразования типов, а именно:

  • Преобразование в логическое значение (вызов метода Boolean())
  • Преобразование в число (вызов методов Number(), parseInt() и parseFloat())
  • Преобразование в строку (вызов метода .toString() или String())
null和underfined没有.toString方法

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

4. Оценка типа данных в JS (typeof, instanceof, конструктор, Object.prototype.toString.call()

(1) тип

typeof отображает правильный тип для примитивных типов, кроме null

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object     []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object     null 的数据类型被 typeof 解释为 object

Для объектов, за исключением функций, typeof будет отображать объект, поэтому typeof не может точно определить тип переменной, поэтому, если вы хотите определить правильный тип объекта, вы можете рассмотреть возможность использования instanceof.

(2) экземпляр

instanceof может правильно определить тип объекта, потому что внутренний механизм должен определить, можно ли найти прототип типа в цепочке прототипов объекта.

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true    
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);

Видно, что прямое литеральное значение определяет тип данных, а instanceof может точно определить ссылочный тип данных (Array, Function, Object), в то время как базовый тип данных нельзя точно определить по instanceof.

Давайте посмотрим на объяснение экземпляра в MDN: оператор InstanceOF используется для проверки того, имеет ли объект прототипа конструктора в цепочке прототипа. Это означает судить о том, является ли объектом экземпляром типа данных (например, массив). Пожалуйста, сосредоточиться на том, является ли объект экземпляром типа данных. Здесь буквальное значение, 2, True, STR 'не является экземпляром, поэтому значение предиката является ложным.

(3) конструктор

console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
这里有一个坑,如果我创建一个对象,更改它的原型,constructor就会变得不可靠了
function Fn(){};
 
Fn.prototype=new Array();
 
var f=new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

(4) Object.prototype.toString.call()Используйте метод-прототип toString объекта Object, используйте вызов, чтобы изменить циветтного кота на принца, и позаимствуйте метод toString объекта Object.

var a = Object.prototype.toString;
 
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));

5. Какие есть встроенные объекты js?

Встроенные объекты в js в основном относятся к объектам, созданным js в глобальной области до выполнения программы. Определены некоторые глобально значимые свойства, функции и конструкторы, используемые для создания экземпляров других объектов. числовой объект. Как правило, мы часто используем значения глобальных переменных, таких как NaN и undefined, а глобальные функции, такие как parseInt() и parseFloat(), используются для создания экземпляров структуры объектов. Конструкторы, такие как Date, Object и т. д., а также отдельные встроенные объекты, такие как объекты Math, обеспечивающие математические вычисления.

Задействованные точки знаний:

全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在
全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。

标准内置对象的分类

(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。

例如 Infinity、NaN、undefined、null 字面量

(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。

例如 eval()、parseFloat()、parseInt() 等

(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。

例如 Object、Function、Boolean、Symbol、Error 等

(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。

例如 Number、Math、Date

(5)字符串,用来表示和操作字符串的对象。

例如 String、RegExp

(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array

(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。

例如 Map、Set、WeakMap、WeakSet

(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。

例如 SIMD 等

(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。

例如 JSON 等

(10)控制抽象对象

例如 Promise、Generator 等

(11)反射

例如 Reflect、Proxy

(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。

例如 Intl、Intl.Collator 等

(13)WebAssembly

(14)其他

例如 arguments

Для получения подробной информации см.:«Классификация стандартных встроенных объектов»

«Сводка всех встроенных свойств и методов объектов в JS»

6. В чем разница между undefined и undeclared?

Переменная, объявленная в области видимости, но еще не получившая значение, не определена. И наоборот, переменные, которые не были объявлены в области видимости, не объявляются.

Для ссылок на необъявленные переменные браузер сообщит об ошибке ссылки, такой как ReferenceError: b is notdefined. Но мы можем использовать тип механизм безопасности eof, чтобы избежать ошибок, потому что для необъявленных (или не определенных) переменных typeof вернет «undefined».

7. В чем разница между null и undefined?

Прежде всего, undefined и null являются базовыми типами данных, а два базовых типа данных имеют только одно значение, undefined и NULL.

неопределенный означает неопределенный, Значение null — это пустой объект (на самом деле это не настоящий объект, см. нижеУведомление! ). Как правило, когда переменная объявлена, но еще не определена, она возвращает undefined, null В основном используется для присваивания некоторым переменным, которые могут возвращать объект, в качестве инициализации.

其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

undefined не является зарезервированным словом в js, а это значит, что мы можем использовать undefined в качестве имени переменной, что очень опасно, это Это повлияет на наше суждение о неопределенных значениях. Но есть несколько способов получить безопасные неопределенные значения, например, void 0.

Когда мы используем typeof для оценки двух типов, нулевая типизация вернет «object», что является проблемой, оставшейся от истории. Когда мы используем double и т.д. Возвращает true при сравнении значений двух типов, false при использовании трех знаков равенства.

Для получения подробной информации см.:

«Глубокое понимание JavaScript undefined и null»

8. Каков результат valueOf и toString {} и []?

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"

[] 的 valueOf 结果为 [] ,toString 的结果为 ""

9. Область действия Javascript и цепочка областей видимости

Объем:Область действия — это область, в которой определяются переменные. В ней есть набор правил для доступа к переменным. Этот набор правил определяет, как движок браузера выполняет поиск переменных на основе переменных (идентификаторов) в текущей области и вложенных областях.

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

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

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

Процесс создания цепочки областей действия связан с установлением контекста выполнения. …

Для получения подробной информации см.:«Глубокое понимание цепочки областей видимости в JavaScript»

Смотрите также мою статью:«Фронт-конечный пакет» растягивается в точках и закрытиях и закрытиях

10. Сколько способов создания объектов в javascript?

我们一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js
和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象
创建方式,我了解到的方式有这么几种:

(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。

(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此我们可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。

(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。

(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。

嗯我目前了解到的就是这么几种方式。

Для получения подробной информации см.:«Создание объектов для глубокого понимания JavaScript»

11. Несколько реализаций наследования JavaScript?

我了解的 js 中实现继承的几种方式有:

(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

Для получения подробной информации см.:«Наследование глубокого понимания JavaScript»

12. Реализация наследования паразитарного состава?

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log("My name is " + this.name + ".");
};

function Student(name, grade) {
  Person.call(this, name);
  this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayMyGrade = function() {
  console.log("My grade is " + this.grade + ".");
  
};

13. Расскажите о своем понимании этого, позвоните, подайте заявку и привяжите

Подробнее читайте в моей предыдущей статье:Статья "Front-end package" досконально разбирается в этом, вызовите, примените и привяжите в JavaScript

  1. В браузере это указывает на объект окна в глобальной области видимости;
  2. В функции это всегда указывает на объект, вызвавший его последним;
  3. В конструкторе this указывает на новый объект из new;
  4. Это в вызове, применении и привязке строго привязано к указанному объекту;
  5. Это особенность стрелочных функций. Это this родительской области, а не this во время вызова. Вы должны знать, что все первые четыре метода определяются во время вызова, то есть являются динамическими, а this стрелочной функции является статической. , она определяется при выполнении оператора;
  6. apply, call и bind — это все API, встроенные в функции с помощью js. Вызов их может указать выполнение this для функции, а также может передавать параметры.

14. Прототип JavaScript, цепочка прототипов? Каковы характеристики?

В js мы используем конструктор для создания нового объекта.Каждый конструктор имеет значение свойства прототипа, которое представляет собой пару Например, этот объект содержит свойства и методы, которые могут быть общими для всех экземпляров этого конструктора. Когда мы используем конструктор для создания нового объекта, внутри объекта Будет содержать указатель на значение свойства прототипа конструктора, которое в ES5 называется прототипом объекта. Обычно мы Это значение не должно было быть получено, но теперь оно реализовано в браузерах.protoсвойство, чтобы дать нам доступ к этому свойству, но нам лучше не использовать это атрибут, потому что он не указан в спецификации. ES5 добавил новый метод Object.getPrototypeOf(), мы можем использовать этот метод для получения Прототип слона.

Когда мы обращаемся к свойству объекта, если свойство не существует внутри объекта, то оно переходит к своему объекту-прототипу, чтобы найти это свойство, что опять же Будет свой собственный прототип, поэтому я продолжаю его искать, что и является концепцией цепочки прототипов. Концом цепочки прототипов обычно является Object.prototype, так что это Вот почему наши вновь созданные объекты могут использовать toString() и другие методы.

Функции:

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

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

«Глубокое понимание JavaScript прототипа и цепочки прототипов»

Смотрите также, что я написал:"Front-end package" - глубокое понимание прототипа JavaScript и цепочки прототипов

15. Как получить прототип в js?

  • p.proto
  • p.constructor.prototype
  • Object.getPrototypeOf(p)

16. Что такое замыкание и зачем его использовать?

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

Замыкания имеют два распространенных применения.

  • Первое использование замыканий — позволить нам получить доступ к переменным внутри функции извне функции. Используя замыкания, мы можем получить доступ к переменным внутри функции извне, вызвав функцию замыкания извне, и мы можем использовать этот метод для создания закрытых переменных.
  • Другая цель функции состоит в том, чтобы сохранить переменный объект в контексте функции, которая завершила выполнение в памяти.Поскольку функция закрытия сохраняет ссылку на переменный объект, переменный объект не будет повторно использован.
function a(){
    var n = 0;
    function add(){
       n++;
       console.log(n);
    }
    return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1();    //1
a1();    //2  第二次调用n变量还在内存中


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

17. Что такое DOM и BOM?

DOMОтносится к объектной модели документа, которая относится к обработке документа как к объекту, который в основном определяет методы и интерфейсы для обработки содержимого веб-страницы.

BOMОтносится к объектной модели браузера, которая относится к обращению с браузером как к объекту, который в основном определяет методы и интерфейсы для взаимодействия с браузером. Спецификация Ядром является окно, а объект окна имеет двойную роль, это не только интерфейс для доступа к окну браузера через js, но и глобальный (глобальный) объект. Это означает, что любой объект, переменная и функция, определенные на веб-странице, существуют как свойство или метод глобального объекта. объект окна содержит локати На объекте, объекте навигатора, объекте экрана и других подобъектах, а наиболее фундаментальным объектом объекта документа DOM также является пара окон спецификации. дочерний объект иконки.

Релевантная информация:

《В чем разница между DOM, DOCUMENT, BOM, WINDOW?》

«Оконный объект»

«Что такое DOM и BOM и как они связаны? 》

«Краткий обзор обучения JavaScript (3) Подробное объяснение спецификации и модели модели»

18. Каковы три модели событий?

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

  1. Модель уровня DOM0:, эта модель не будет распространяться, поэтому не существует понятия потока событий, но некоторые браузеры теперь поддерживают его в виде всплывающего окна.Он может напрямую определять функцию прослушивателя на веб-странице или указывать функцию прослушивателя через атрибут js. Этот метод совместим со всеми браузерами.
  2. Модель событий IE:В этой модели события событие имеет два процесса: этап обработки события и этап всплытия события. Фаза обработки события сначала выполнит событие прослушивателя, привязанное к целевому элементу. Затем идет этап всплытия событий, который относится к всплыванию события от целевого элемента к документу, проверяя, привязаны ли проходящие узлы к функции прослушивателя событий, и выполняя ее, если это так. Эта модель использует attachEvent для добавления функций прослушивания.Можно добавить несколько функций прослушивания, которые будут выполняться последовательно.
  3. Модель события уровня DOM2:В событийной модели есть три процесса для события, первый процесс — фаза захвата события. Захват означает, что события распространяются от документа до целевого элемента, проверяя, привязаны ли передающие узлы к функции прослушивателя событий, и выполняя их, если это так. Последние две фазы аналогичны двум фазам событийной модели IE. В этой модели событий функцией, связанной с событием, является addEventListener, где третий параметр может указывать, выполняется ли событие на этапе захвата.

Релевантная информация:

«Когда элемент DOM связывает несколько событий, сначала выполните всплытие или захват»

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

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

Используя делегаты событий, нам не нужно привязывать событие прослушивателя для каждого дочернего элемента, что снижает потребление памяти. А с помощью event proxy мы также можем реализовать динамическую привязку событий, например, если добавляется дочерняя нода, нам не нужно добавлять для нее отдельно событие слушателя, событие, которое произойдет, будет передано слушателю функция в родительском элементе для обработки.

Релевантная информация:

"Подробное объяснение делегирования событий JavaScript"

20. Что такое распространение событий?

когдамероприятиеПри происходящем на элементе DOM, событие не происходит на этом элементе. В разделе «Когда происходит событие на элементе DOM, событие не имело совсем не происходит на этом элементе.

Распространение события состоит из трех фаз:

  1. Фаза захвата — событие начинается с окна и переходит к каждому элементу, пока не достигнет события целевого элемента или event.target.
  2. Целевая фаза — событие достигло целевого элемента.
  3. Фаза всплытия — событие всплывает из целевого элемента, а затем поднимается к каждому элементу, пока не достигнет окна.

21. Что такое захват событий?

Когда событие происходит в элементе DOM, оно точно не происходит в этом элементе. На этапе захвата событие начинается с окна и доходит до элемента, вызвавшего событие.window----> document----> html----> body ---->目标元素

Предположим, у вас есть следующая структура HTML:

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

Соответствующий JS-код:

function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});

addEventListenerметод имеет третий необязательный параметрuseCapture, значение по умолчанию которого равноfalse, событие произойдет на этапе всплытия, если true, событие произойдет на этапе захвата. Если вы нажметеchildэлементы, он будет напечатан на консоли отдельноwindow,document,html,grandparentа такжеparent,Этозахват событий.

22. Что такое всплывающее окно событий?

Всплывание событий — это полная противоположность захвату событий.当前元素---->body ----> html---->document ---->window.当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window为止。

Предположим, у вас есть следующая структура HTML:

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

Соответствующий JS-код:

function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});

addEventListenerметод имеет третий необязательный параметрuseCapture, значение по умолчанию которого равноfalse, событие произойдет на этапе всплытия, если true, событие произойдет на этапе захвата. Если вы нажметеchildэлементы, он будет напечатан на консоли отдельноchild,parent,grandparent,html,documentа такжеwindow,Этовсплывающее окно события.

23. Работа с DOM — как добавлять, удалять, перемещать, копировать, создавать и находить узлы?

(1) Создать новый узел

  createDocumentFragment()    //创建一个DOM片段
  createElement()   //创建一个具体的元素
  createTextNode()   //创建一个文本节点

(2) Добавить, удалить, заменить, вставить

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

(3) Найти

getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();

(4) Операция атрибута

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

Релевантная информация:

Обзор DOM

«Краткое описание манипуляций DOM с нативным JavaScript»

«Набор API-интерфейсов, связанных с узлами DOM в собственном JS»

24. Какие есть нативные методы js для массивов и строк, перечислите их

25. Часто используемые регулярные выражения (только для коллекции, не глубоко)

//(1)匹配 16 进制颜色值
var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

//(2)匹配日期,如 yyyy-mm-dd 格式
var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

//(3)匹配 qq 号
var qq = /^[1-9][0-9]{4,10}$/g;

//(4)手机号码正则
var phone = /^1[34578]\d{9}$/g;

//(5)用户名正则
var username = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

//(6)Email正则
var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

//(7)身份证号(18位)正则
var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;

//(8)URL正则
var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;

// (9)ipv4地址正则
var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

// (10)//车牌号正则
var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;

// (11)强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):var pwd = /^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$/

26. Что такое Ajax?Как создать Ajax?

Мое понимание ajax заключается в том, что это метод асинхронной связи, путем прямой инициации http-связи с сервером из сценария js, а затем в соответствии с данными, возвращаемыми сервером, обновить соответствующую часть веб-страницы без обновления всей страницы. .

Создайте шаги:

Интервью Почерк (Родной):

//1:创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax请求地址
xhr.open('get','index.xml',true);
//3:发送请求
xhr.send(null); // 严谨写法
//4:监听请求,接受响应
xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
          console.log(xhr.responsetXML)
}

написание jQuery

  $.ajax({
          type:'post',
          url:'',
          async:ture,//async 异步  sync  同步
          data:data,//针对post请求
          dataType:'jsonp',
          success:function (msg) {

          },
          error:function (error) {

          }
        })

реализация пакета обещаний:

// promise 封装实现:

function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();

    // 新建一个 http 请求
    xhr.open("GET", url, true);

    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;

      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };

    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };

    // 设置响应的数据类型
    xhr.responseType = "json";

    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");

    // 发送 http 请求
    xhr.send(null);
  });

  return promise;
}

27. Какие есть способы ленивой загрузки js?

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

Вот несколько способов, которым я научился:

  1. Поместите js-скрипт в конец документа, чтобы js-скрипт максимально загружался и выполнялся в конце.
  2. Добавьте атрибут defer в скрипт js, этот атрибут сделает загрузку скрипта и синтаксический анализ документа синхронными, а затем выполнит файл скрипта после завершения синтаксического анализа документа, так что рендеринг страницы будет не быть заблокированным. Несколько сценариев с установленным атрибутом defer выполняются последовательно последними по спецификации, но в некоторых браузерах это может быть не так.
  3. Добавьте атрибут ASYNC в скрипт JS. Этот атрибут сделает сценарий нагрузки асинхронно и не заблокирует процесс анализа страницы. Однако скрипт JS будет выполнен сразу после нагрузки сценария. Если документ не проанализирован в На этот раз он также будет блокировать. Порядок выполнения скриптов с несколькими атрибутами ASYNC непредсказуемы и, как правило, не выполняется последовательно в порядке кода.
  4. В способе динамического создания тегов DOM мы можем отслеживать событие загрузки документа, а затем динамически создавать теги сценария для импорта сценариев js при загрузке документа.

Релевантная информация:

"Несколько способов ленивой загрузки JS"

"HTML 5<script> asyncАтрибуты"

28. Расскажите о своем понимании модульной разработки?

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

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

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

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

Релевантная информация:«О модульной разработке»

«Модульное программирование на JavaScript (1): написание модулей»

«Фронт-конец модуль: Commonjs, AMD, CMD, ES6»

«Грамматика модуля»

29. Несколько спецификаций модулей js?

Существует четыре вида схем загрузки модулей, которые относительно хорошо разработаны в js:

  • Первое — это решение CommonJS, которое вводит модули через require и определяет выходной интерфейс модуля через module.exports. Это решение для загрузки модулей является решением на стороне сервера. Оно вводит модули синхронно. Поскольку файлы хранятся на локальном диске на стороне сервера, чтение происходит очень быстро, поэтому с синхронной загрузкой проблем не возникает. Но если это на стороне браузера, так как модуль загружается с помощью сетевых запросов, целесообразнее использовать асинхронную загрузку.
  • Второй — схема AMD, которая использует асинхронную загрузку для загрузки модулей. Загрузка модулей не влияет на выполнение последующих операторов. Все операторы, которые зависят от этого модуля, определены в функции обратного вызова, а обратный вызов выполняется после загрузки завершена функция. require.js реализует спецификацию AMD.
  • Третье — решение CMD.И это решение, и решение AMD предназначены для решения проблемы асинхронной загрузки модулей.Sea.js реализует спецификацию CMD. Разница между ним и require.js заключается в том, что обработка зависимостей отличается, когда модуль определен, и время выполнения зависимого модуля отличается.
  • Четвертая схема — это схема, предложенная ES6, которая использует форму импорта и экспорта для импорта и экспорта модулей.

30. В чем разница между спецификациями AMD и CMD?

Основное различие между ними двоякое.

  1. Первый аспект заключается в том, что зависимости обрабатываются по-разному во время определения модуля. AMD уважает предварительную зависимость и объявляет модули, от которых она зависит, при определении модулей. И CMD учитывает ближайшую зависимость и требует ее только при использовании модуля.
  2. Второй аспект заключается в том, что время выполнения зависимых модулей обрабатывается по-разному. Во-первых, AMD и CMD загружают модули асинхронно, но разница между ними в том, что

Время выполнения модуля: AMD напрямую выполняет зависимый модуль после загрузки зависимого модуля, и порядок выполнения зависимого модуля не обязательно совпадает с написанным нами порядком. в то время как CMD После загрузки зависимого модуля он не будет выполняться, а только загружаться.После загрузки всех зависимых модулей входим в логику callback-функции и сталкиваемся с оператором require Когда соответствующий модуль выполняется, порядок выполнения модуля соответствует порядку, который мы написали.

// CMD
define(function(require, exports, module) {
  var a = require("./a");
  a.doSomething();
  // 此处略去 100 行
  var b = require("./b"); // 依赖可以就近书写
  b.doSomething();
  // ...
});

// AMD 默认推荐
define(["./a", "./b"], function(a, b) {
  // 依赖必须一开始就写好
  a.doSomething();
  // 此处略去 100 行
  b.doSomething();
  // ...
});

Релевантная информация:

«Модульность интерфейса, разница между AMD и CMD»

31. Различия между модулями ES6 и модулями CommonJS, AMD и CMD.

  • 1.CommonJSВывод модуля — это копия значения, а вывод модуля ES6 — ссылка на значение.CommonJSВыход модуля — это значение

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

  • 2.CommonJSМодули загружаются во время выполнения, модули ES6 представляют собой скомпилированные интерфейсы.CommonJSМодуль - это объект, то есть при вводе сначала загружается весь модуль, генерируется объект, а затем из объекта считывается метод, такая загрузка называется "runtime load". Модуль ES6 не является объектом, его внешний интерфейс — это просто статическое определение, которое будет сгенерировано на этапе статического анализа кода.

32. Каков основной принцип requireJS?

Основной принцип require.js заключается в асинхронном импорте модулей путем динамического создания сценариев сценариев, затем отслеживании события загрузки каждого сценария и вызове функции обратного вызова, если каждый сценарий загружен. ````

Для получения подробной информации см.:"Использование и принципиальный анализ requireJS"

Каков основной принцип requireJS? 》

"требуется принципиальный анализ JS"

33. Расскажите о механизме работы JS

1. js один поток

Главной особенностью языка JavaScript является то, что он является однопоточным, то есть одновременно может выполняться только одно действие.

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

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

2. Цикл событий js

При выполнении js-кода возникает множество задач, которые обычно делятся на две категории:

  • Синхронизировать задачу
  • асинхронная задача

Когда мы открываем веб-сайт, процесс рендеринга веб-страницы представляет собой множество синхронных задач, таких как рендеринг скелета страницы и элементов страницы. Задачи, которые занимают много ресурсов и занимают много времени, например загрузка изображений и музыки, являются асинхронными задачами. , мы используем карту, чтобы проиллюстрировать:Поясним эту картинку:

  • Синхронные и асинхронные задачи входят в разные «места» выполнения соответственно, входят в основной поток синхронно и входят асинхронно.Event Tableи зарегистрируйте функцию.
  • Когда указанное дело сделано,Event Tableпереместит эту функцию вEvent Queue.
  • Задача в основном потоке пуста после выполнения и перейдет кEvent QueueПрочитайте соответствующую функцию и введите основной поток для выполнения.
  • Вышеупомянутый процесс будет продолжать повторяться, о чем часто говорятEvent Loop(цикл событий).

Когда стек выполнения основного потока пуст? js-движок существуетmonitoring processПроцесс будет постоянно проверять, не пуст ли стек выполнения основного потока, и как только он будет пуст, он перейдет кEvent QueueТам он проверяет, есть ли функция, ожидающая вызова.

Выше приведен общий процесс запуска js.

Следует отметить, что помимо синхронных задач и асинхронных задач, задачи также могут быть дополнительно подразделены на макрозадачи (macro tasks) и микрозадачи (microtasks), а js-движок будет отдавать приоритет выполнению микрозадач.

微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。

宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲
染等。

Как отвечать на собеседовании? Вот ответ, который я лично рекомендую:

  1. Прежде всего, js работает в одном потоке, когда код выполняется, контекст выполнения различных функций помещается в стек выполнения, чтобы обеспечить упорядоченное выполнение кода.
  2. При выполнении синхронного кода, если встречается асинхронное событие, движок js не будет ждать его возвращаемого результата, а приостановит событие и продолжит выполнение других задач в стеке выполнения.
  3. После выполнения синхронного события обратный вызов, соответствующий асинхронному событию, добавляется в другую очередь задач, отличную от текущего стека выполнения, для ожидания выполнения.
  4. Очередь задач можно разделить на выравнивание макрозадач и выравнивание микрозадач.Когда события в текущем стеке выполнения выполняются, механизм js сначала определяет, есть ли задачи в выравнивании микрозадач, которые могут быть выполнены, и если да, то очередь микрозадач помещается в начало очереди, а событие помещается в стек для выполнения.
  5. Когда все задачи в столбце пары микрозадач выполнены, оцениваются задачи в столбце пары макрозадач.

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

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function(resolve, reject) {
  console.log(2);
  resolve()
}).then(function() {
  console.log(3)
});
process.nextTick(function () {
  console.log(4)
})
console.log(5)

Первый раунд: основной поток начинает выполняться, когда встречается setTimeout, функция обратного вызова setTimeout выбрасывается в очередь задач макроса, и немедленно выполняется новый промис, выход 2, затем функция обратного вызова выбрасывается в микрозадачу очереди, а затем продолжает Execute, сталкиваемся с process.nextTick, также бросаем callback-функцию в очередь задач, продолжаем выполнение, выводим 5, когда все задачи синхронизации выполнены, смотрим, есть ли микрозадачи, которые можно выполнить, и находим что там две микрозадачи, потом функция и nextTick Mission, какую из них выполнить первой? Асинхронная задача, указанная в process.nextTick, всегда выполняется перед всеми асинхронными задачами, поэтому сначала выполните process.nextTick для вывода 4, а затем выполните функцию then для вывода 3, и первый цикл выполнения завершится. Второй раунд: начиная с очереди задач макроса, найден обратный вызов setTimeout, и выполняется выход 1, поэтому результат 25431

Релевантная информация:

"Механизм цикла событий браузера (цикл событий)"

«Подробное объяснение механизма цикла событий в JavaScript»

«Что такое цикл событий? 》

«На этот раз досконально изучите механизм выполнения JavaScript»

34. Что является предметом аргументов?

Объект arguments — это набор значений параметров, передаваемых в функцию. Это массивоподобный объект, потому что он имеет свойство длины, и мы можем использовать аргументы записи индекса массива [1] для доступа к отдельным значениям, но у него нет встроенных методов в массивах, таких как: forEach, reduce, filter и карта.

Мы можем использовать Array.prototype.slice для преобразования объекта arguments в массив.

function one() {
  return Array.prototype.slice.call(arguments);
}

Примечание. В стрелочных функциях нет объекта аргументов.

function one() {
  return arguments;
}
const two = function () {
  return arguments;
}
const three = function three() {
  return arguments;
}

const four = () => arguments;

four(); // Throws an error  - arguments is not defined

Когда мы вызываем функцию Four, она выдаетReferenceError: arguments is not defined error. Используя остаточный синтаксис, эту проблему можно решить.

const four = (...args) => args;

Это автоматически помещает все значения параметров в массив.

35. Почему при вызове этой функции в кодеbстановится глобальной переменной?

function myFunc() {
  let a = b = 0;
}

myFunc();

Причина в том, что операторы присваивания оцениваются справа налево. Это означает, что когда в выражении появляется несколько операторов присваивания, они вычисляются справа налево. Таким образом, приведенный выше код становится таким:

function myFunc() {
  let a = (b = 0);
}

myFunc();

Сначала оценивается выражение b = 0, в этом случае b не объявляется. Поэтому движок JS создает глобальную переменную b вне этой функции, а затем возвращаемое значение выражения b = 0 равно 0, и присваивает его новой локальной переменной a.

Мы можем решить эту проблему, объявив переменную перед ее назначением.

function myFunc() {
  let a,b;
  a = b = 0;
}
myFunc();

36. Кратко представим механизм сборки мусора двигателя V8.

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。

新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步:

(1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。

(2)如果对象不存活,则释放对象的空间。

(3)最后将 From 空间和 To 空间角色进行交换。

新生代对象晋升到老生代有两个条件:

(1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。

(2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。

老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。

由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行。

Релевантная информация:

"Глубокое понимание принципа сборки мусора V8"

«Сборка мусора в JavaScript»

37. Какие операции могут вызвать утечку памяти?

  • 1. Неожиданная глобальная переменная
  • 2. Забытый таймер или функция обратного вызова
  • 3. Разыменование из DOM
  • 4. Закрытие
  • В первом случае мы случайно создаем глобальную переменную, используя необъявленную переменную, и эта переменная остается в памяти и не может быть повторно использована.
  • Второй случай заключается в том, что мы устанавливаемsetIntervalТаймер, и забудьте его отменить, если в функции цикла есть ссылка на внешнюю переменную, то эта переменная останется в памяти и не может быть перезапущена.
  • Третий случай — мы получаем ссылку на DOM-элемент, а последний элемент удаляется, так как мы сохраняем ссылку на этот элемент, он не может быть переработан.
  • Четвертый случай — неразумное использование замыканий, в результате чего некоторые переменные остаются в памяти.

Релевантная информация:

Учебное пособие по утечке памяти в JavaScript

4 типа утечек памяти в JavaScript и как их избежать

«Предотвратить возникновение четырех типов утечек памяти в js»

"Типичные утечки памяти JavaScript и способы устранения неполадок в Chrome"

以下38~46条是ECMAScript 2015(ES6)中常考的基础知识点

38. Что такое ECMAScript?

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

ECMAScript и Javascript по существу связаны с языком, один из них — название самого языка, а другой — ограничения языка. Просто человек, который изобрел JavaScript (Netscape), передал его в ECMA (Европейскую ассоциацию производителей компьютеров). человек ECMA.Поэтому и родилась такая волшебная штука, и имя этой штуке называется ECMAScript.

javaScript = ECMAScript + DOM + BOM (я думаю, что это обобщенный JavaScript)

ECMAScript говорит, что должен делать JavaScript!

JavaScript (JavaScript в узком смысле) должен спрашивать ECMAScript, могу ли я это сделать! Если нет, то я не прав! Я могу быть прав!

—— Внезапно я чувствую, что JavaScript настолько недостоин, почему я должен заниматься собой, чтобы сдерживать себя?

Жаль, что этот человек был создан, и он был создан исключительно для ограничения JavaScript.

39. Каковы новые функции ECMAScript 2015 (ES6)?

  • область действия блока
  • Добрый
  • стрелочная функция
  • строка шаблона
  • Усовершенствованные литералы объектов
  • деструктуризация объекта
  • Promise
  • модуль
  • Symbol
  • Набор прокси
  • параметры функции по умолчанию
  • отдыхать и расширяться

40. var,letа такжеconstКакая разница?

Переменные, объявленные с помощью var, будут смонтированы в окне, а переменные, объявленные с помощью let и const, — нет:

var a = 100;
console.log(a,window.a);    // 100 100

let b = 10;
console.log(b,window.b);    // 10 undefined

const c = 1;
console.log(c,window.c);    // 1 undefined

Существует продвижение переменных для переменных объявления var и нет продвижения переменных для let и const:

console.log(a); // undefined  ===>  a已声明还没赋值,默认得到undefined值
var a = 100;

console.log(b); // 报错:b is not defined  ===> 找不到b这个变量
let b = 10;

console.log(c); // 报错:c is not defined  ===> 找不到c这个变量
const c = 10;

Объявления let и const формируют область действия блока


if(1){
  var a = 100;
  let b = 10;
}

console.log(a); // 100
console.log(b)  // 报错:b is not defined  ===> 找不到b这个变量

-------------------------------------------------------------

if(1){
  var a = 100;
  const c = 1;
}
console.log(a); // 100
console.log(c)  // 报错:c is not defined  ===> 找不到c这个变量

Let и const не могут объявлять переменные с одинаковыми именами в одной и той же области видимости, но var может

var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;

//  控制台报错:Identifier 'a' has already been declared  ===> 标识符a已经被声明了。

Временная мертвая зона

var a = 100;

if(1){
    a = 10;
    //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
    // 而这时,还未到声明时候,所以控制台Error:a is not defined
    let a = 1;
}

const


/*
*   1、一旦声明必须赋值,不能使用null占位。
*
*   2、声明后不能再修改
*
*   3、如果声明的是复合类型数据,可以修改其属性
*
* */

const a = 100; 

const list = [];
list[0] = 10;
console.log(list);  // [10]

const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}

41. Что такое стрелочная функция?

Выражения стрелочных функций имеют более лаконичный синтаксис, чем выражения функций, и не имеют собственногоthis,arguments,super或new.target. Выражения стрелочных функций более полезны там, где в противном случае потребовалась бы анонимная функция, и ее нельзя использовать в качестве конструктора.

//ES5 Version
var getCurrentDate = function (){
  return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();

В этом случае версия ES5 имеетfunction(){}объявлять и возвращать ключевые слова, необходимые для создания функций и возвращаемых значений соответственно. В версии со стрелочной функцией нам нужны только квадратные скобки () и нет оператора возврата, потому что стрелочные функции имеют неявный возврат, если у нас есть только одно выражение или значение для возврата.

//ES5 Version
function greet(name) {
  return 'Hello ' + name + '!';
}

//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;

Мы также можем использовать те же параметры в стрелочных функциях, что и в функциональных выражениях и объявлениях функций. Скобки можно опустить, если у нас есть параметр в стрелочной функции.

const getArgs = () => arguments

const getArgs2 = (...rest) => rest

Стрелочные функции не могут получить доступ к объекту arguments. Таким образом, вызов первой функции getArgs вызывает ошибку. Вместо этого мы можем использовать параметр rest, чтобы получить все параметры, переданные в стрелочной функции.

const data = {
  result: 0,
  nums: [1, 2, 3, 4, 5],
  computeResult() {
    // 这里的“this”指的是“data”对象
    const addAll = () => {
      return this.nums.reduce((total, cur) => total + cur, 0)
    };
    this.result = addAll();
  }
};

Стрелочные функции не имеют собственного значения this. Он захватывает значение this функции с лексической областью видимости, в этом примере функция addAll скопирует это значение в методе calculateResult, если мы объявим стрелочную функцию в глобальной области видимости, это значение будет объектом окна.

42. Что такое класс?

Классы — это новый способ написания конструкторов в JS. Это синтаксический сахар для использования конструкторов, под капотом которого все еще используется прототип и наследование на основе прототипа.

 //ES5 Version
   function Person(firstName, lastName, age, address){
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
      this.address = address;
   }

   Person.self = function(){
     return this;
   }

   Person.prototype.toString = function(){
     return "[object Person]";
   }

   Person.prototype.getFullName = function (){
     return this.firstName + " " + this.lastName;
   }  

   //ES6 Version
   class Person {
        constructor(firstName, lastName, age, address){
            this.lastName = lastName;
            this.firstName = firstName;
            this.age = age;
            this.address = address;
        }

        static self() {
           return this;
        }

        toString(){
           return "[object Person]";
        }

        getFullName(){
           return `${this.firstName} ${this.lastName}`;
        }
   }

Переопределите метод и наследуйте от другого класса.

//ES5 Version
Employee.prototype = Object.create(Person.prototype);

function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
  Person.call(this, firstName, lastName, age, address);
  this.jobTitle = jobTitle;
  this.yearStarted = yearStarted;
}

Employee.prototype.describe = function () {
  return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}

Employee.prototype.toString = function () {
  return "[object Employee]";
}

//ES6 Version
class Employee extends Person { //Inherits from "Person" class
  constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
    super(firstName, lastName, age, address);
    this.jobTitle = jobTitle;
    this.yearStarted = yearStarted;
  }

  describe() {
    return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
  }

  toString() { // Overriding the "toString" method of "Person"
    return "[object Employee]";
  }
}

Итак, как мы узнаем, что он использует прототип внутри?

class Something {

}

function AnotherSomething(){

}
const as = new AnotherSomething();
const s = new Something();

console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true

Релевантная информация:

«ECMAScript 6 реализует класс, каково значение разработки интерфейса JavaScript? 》

«Основная грамматика класса»

43. Что такое шаблонная строка?

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

//ES5 Version
var greet = 'Hi I\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;

В ES5 нам нужно использовать escape-символы для достижения многострочного эффекта, и нам не нужно быть такими хлопотными в строках шаблона:

//ES5 Version
var lastWords = '\n'
  + '   I  \n'
  + '   Am  \n'
  + 'Iron Man \n';


//ES6 Version
let lastWords = `
    I
    Am
  Iron Man   
`;

В версии ES5 нам нужно добавить \n, чтобы добавить новую строку в строку. В строках шаблона нам не нужно этого делать.

//ES5 Version
function greet(name) {
  return 'Hello ' + name + '!';
}


//ES6 Version
function greet(name) {
  return `Hello ${name} !`;
}

В версии ES5, если вам нужно добавить выражение или значение в строку, вам нужно использовать+оператор. В строке шаблона s мы можем использовать${expr}Встраивает выражение, что делает его чище, чем версия ES5.

44. Что такое деструктуризация объекта?

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

const employee = {
  firstName: "Marko",
  lastName: "Polo",
  position: "Software Developer",
  yearHired: 2017
};

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

var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;

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

{ firstName, lastName, position, yearHired } = employee;

Мы также можем псевдоним свойств:

let { firstName: fName, lastName: lName, position, yearHired } = employee;

Конечно, если значение свойства не определено, мы также можем указать значение по умолчанию:

let { firstName = "Mark", lastName: lName, position, yearHired } = employee;

45. Что такоеSetОбъект, как это работает?

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

Мы можем создавать экземпляры Set с помощью конструктора Set.

const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);

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

set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一个“k”不会被添加到set对象中,因为它已经存在了

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

set2.has("a") // true
set2.has("z") // true

Мы можем использовать свойство size, чтобы получить длину экземпляра Set.

set2.size // returns 10

Данные в наборе можно удалить с помощью метода очистки.

set2.clear();

Мы можем использовать объект Set для удаления повторяющихся элементов в массиве.

const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]

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

  • Структура данных карты. Он подобен объекту и также представляет собой набор пар ключ-значение, но область действия «ключей» не ограничивается строками, и в качестве ключей могут использоваться различные типы значений (включая объекты).

  • Структура WeakMap аналогична структуре Map и также используется для создания набора пар ключ-значение. Но WeakMap принимает в качестве ключей только объекты (кроме null), и не принимает в качестве ключей другие типы значений. И объект, на который указывает имя ключа WeakMap, не включается в механизм сборки мусора.

46. ​​Что такое прокси?

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

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

高能预警⚡⚡⚡,


以下47~64条是JavaScript中比较难的高级知识及相关手写实现,各位看官需慢慢细品

47. Напишите общую функцию прослушивания событий

const EventUtils = {
  // 视能力分别使用dom0||dom2||IE方式 来绑定事件
  // 添加事件
  addEvent: function(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },

  // 移除事件
  removeEvent: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  },

  // 获取事件目标
  getTarget: function(event) {
    return event.target || event.srcElement;
  },

  // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
  getEvent: function(event) {
    return event || window.event;
  },

  // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)
  stopPropagation: function(event) {
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
  },

  // 取消事件的默认行为
  preventDefault: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  }
};

48. Что такое функциональное программирование Какие особенности JavaScript делают его кандидатом в функциональные языки?

Функциональное программирование (часто сокращенно FP) — это процесс создания программного обеспечения путем написания чистых функций, избегая общего состояния, изменяемых данных и побочных эффектов. Численное программирование является декларативным, а не императивным, и состояние приложения передается через чистые функции. В отличие от объектно-ориентированного программирования, состояние приложения в объектно-ориентированном программировании часто является общим и совмещенным с методами объекта.

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

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

49. Что такое функции высшего порядка?

Функции высшего порядка — это просто функции, которые принимают функции в качестве аргументов или возвращают значение.

function higherOrderFunction(param,callback){
    return callback(param);
}

50. Почему функции называются гражданами первого сорта?

В JavaScript функции не только имеют все, как используются традиционные функции (объявлены и вызываются), но и могут вести себя как простые значения:

  • назначать(var func = function(){}),
  • Передаваемые параметры (function func(x,callback){callback();}),
  • вернуть(function(){return function(){}}),

Такие функции также называются функциями первого уровня (First-class Function). Мало того, функция в JavaScript также действует как конструктор класса, а также является экземпляром класса Function. Такое множество идентификаторов делает функции JavaScript очень важными.

51. Ручная реализацияArray.prototype.map 方法

Метод map() создает новый массив, результат которого является результатом вызова предоставленной функции для каждого элемента массива.

function map(arr, mapCallback) {
  // 首先,检查传递的参数是否正确。
  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { 
    return [];
  } else {
    let result = [];
    // 每次调用此函数时,我们都会创建一个 result 数组
    // 因为我们不想改变原始数组。
    for (let i = 0, len = arr.length; i < len; i++) {
      result.push(mapCallback(arr[i], i, arr)); 
      // 将 mapCallback 返回的结果 push 到 result 数组中
    }
    return result;
  }
}

52. Ручная реализацияArray.prototype.filterметод

filter() метод создает новый массив, содержащий все элементы теста, реализованного предоставленной функцией.

function filter(arr, filterCallback) {
  // 首先,检查传递的参数是否正确。
  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') 
  {
    return [];
  } else {
    let result = [];
     // 每次调用此函数时,我们都会创建一个 result 数组
     // 因为我们不想改变原始数组。
    for (let i = 0, len = arr.length; i < len; i++) {
      // 检查 filterCallback 的返回值是否是真值
      if (filterCallback(arr[i], i, arr)) { 
      // 如果条件为真,则将数组元素 push 到 result 中
        result.push(arr[i]);
      }
    }
    return result; // return the result array
  }
}

53. Ручная реализацияArray.prototype.reduceметод

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


function reduce(arr, reduceCallback, initialValue) {
  // 首先,检查传递的参数是否正确。
  if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') 
  {
    return [];
  } else {
    // 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
    let hasInitialValue = initialValue !== undefined;
    let value = hasInitialValue ? initialValue : arr[0];
   、

    // 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
    for (let i = hasInitialValue ? 1 : 0, len = arr.length; i < len; i++) {
      value = reduceCallback(value, arr[i], i, arr); 
    }
    return value;
  }
}

54. Глубина копирования JS

Глубина копирования JavaScript всегда сложна, если бы сейчас интервьюер попросил меня написать глубокую копию, я мог бы просто написать базовую версию. Поэтому, прежде чем писать эту статью, я прочитал в разных кругах фаворитов «Гангстер», написанных Боуэном. Теперь я вижу конкретную вставку ссылки, здесь только краткое содержание.

  • Мелкая копия:Создает новый объект с точной копией значений свойств исходного объекта. Если атрибут является примитивным типом, копируется значение примитивного типа, а если атрибут является ссылочным типом, копируется адрес памяти, поэтому, если один объект изменит этот адрес, это повлияет на другой объект.
  • Глубокая копия:Из памяти извлекается полная копия объекта, а из кучи памяти открывается новая область для хранения нового объекта, при этом модификация нового объекта не влияет на исходный объект.

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

  • Метод Object.assign():Используется для копирования значений всех перечисляемых свойств из одного или нескольких исходных объектов в целевой объект. Он вернет целевой объект.
  • **Array.prototype.slice(): метод **slice() возвращает новый объект массива, который является неглубокой копией исходного массива, определяемого началом и концом (исключая конец). Исходный массив не будет изменен.
  • спред оператор...:
let a = {
    name: "Jake",
    flag: {
        title: "better day by day",
        time: "2020-05-31"
    }
}
let b = {...a};

Реализация глубокой копии:

  • Нищее издание:JSON.parse(JSON.stringify(object)), имеет много недостатков (игнорирует undefined, symbol, functions; не может разрешать циклические ссылки; не может обрабатывать обычные, новые Date())
  • Базовая версия (достаточно для собеседования):Поверхностное копирование + рекурсия (рассматриваются только два обычных типа данных объекта и массива)
function cloneDeep(target,map = new WeakMap()) {
  if(typeOf taret ==='object'){
     let cloneTarget = Array.isArray(target) ? [] : {};
      
     if(map.get(target)) {
        return target;
    }
     map.set(target, cloneTarget);
     for(const key in target){
        cloneTarget[key] = cloneDeep(target[key], map);
     }
     return cloneTarget
  }else{
       return target
  }
 
}

  • Окончательный версия:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
    return Object.prototype.toString.call(target);
}

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        case funcTag:
            return cloneFunction(targe);
        default:
            return null;
    }
}

function clone(target, map = new WeakMap()) {

    // 克隆原始类型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循环引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

module.exports = {
    clone
};

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

Как написать глубокий текст, который удивит интервьюера

Окончательный поиск глубокого копирования (99% людей не знают)

55. Рукописные функции вызова, применения и привязки

Этапы реализации функции вызова:

  • 1. Определяем, является ли вызывающий объект функцией, даже если мы определяем его на прототипе функции, его можно вызывать с помощью call и других методов.
  • 2. Определите, существует ли объект входящего контекста, если нет, установите его в window.
  • 3. Обработать входящие параметры и перехватить все параметры после первого параметра.
  • 4. Сделайте функцию свойством объекта контекста.
  • 5. Используйте объект контекста, чтобы вызвать этот метод и сохранить возвращенный результат.
  • 6. Удалите только что добавленное свойство.
  • 7. Вернуть результат.
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }

  // 获取参数
  let args = [...arguments].slice(1),
    result = null;

  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;

  // 将调用函数设为对象的方法
  context.fn = this;

  // 调用函数
  result = context.fn(...args);

  // 将属性删除
  delete context.fn;

  return result;
};

Шаги реализации функции применения:

    1. Чтобы определить, является ли вызывающий объект функцией, даже если мы определим его в прототипе функции, его можно вызвать с помощью call и других методов.
    1. Определите, существует ли объект входящего контекста, если нет, установите его в window.
    1. Сделайте функцию свойством объекта контекста.
    1. Определить, передается ли значение параметра в
    1. Используйте объект контекста, чтобы вызвать этот метод и сохранить возвращенный результат.
    1. Удалить недавно добавленное свойство
    1. вернуть результат

// apply 函数实现

Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  let result = null;

  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;

  // 将函数设为对象的方法
  context.fn = this;

  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }

  // 将属性删除
  delete context.fn;

  return result;
};


Этапы реализации функции привязки:

  • 1. Определяем, является ли вызывающий объект функцией, даже если мы определяем его на прототипе функции, его можно вызывать с помощью call и других методов.
  • 2. Сохраните ссылку текущей функции и получите остальные значения входящих параметров.
  • 3. Создайте функцию, которая возвращает
  • 4. Функция использует применение для привязки вызова функции.Необходимо судить, используется ли функция в качестве конструктора.В это время вам нужно передать this текущей функции в вызов применения, а в остальных случаи, проходят в указанном объекте контекста.
// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  // 获取参数
  var args = [...arguments].slice(1),
    fn = this;

  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

Справочная статья:"Рукописный вызов, применение и связывание функций"

"Углубленный вызов JavaScript и применение имитационного моделирования"

56. Реализация функции каррирования

// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function curry(fn, args) {
  // 获取函数需要的参数长度
  let length = fn.length;

  args = args || [];

  return function() {
    let subArgs = args.slice(0);

    // 拼接得到现有的所有参数
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }

    // 判断参数的长度是否已经满足函数所需参数的长度
    if (subArgs.length >= length) {
      // 如果满足,执行函数
      return fn.apply(this, subArgs);
    } else {
      // 如果不满足,递归返回科里化的函数,等待参数的传入
      return curry.call(this, fn, subArgs);
    }
  };
}

// es6 实现
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

Справочная статья:«Каррирование функций по темам JavaScript»

57. js имитирует реализацию нового оператора

Если вы ищете этот вопрос на Nuggets, вы можете найти ответы, похожие на следующие:

Честно говоря, я не понял это в первый раз, когда я прочитал это Мне нужно понять знание прототипа и цепочки прототипов, чтобы понять это. Я так думаюMDNОбъяснение нового легче понять:

newОператор создает экземпляр пользовательского типа объекта или экземпляр встроенного объекта с помощью конструктора.newКлючевые слова делают следующее:

  1. Создайте пустой простой объект JavaScript (например, {});
  2. Связать объект (т.е. установить конструктор объекта) с другим объектом;
  3. Используйте вновь созданный объект на шаге 1 в качестве контекста этого;
  4. Если функция не возвращает объект, возвращается this.

Далее смотрим на реализацию:

function Dog(name, color, age) {
  this.name = name;
  this.color = color;
  this.age = age;
}

Dog.prototype={
  getName: function() {
    return this.name
  }
}

var dog = new Dog('大黄', 'yellow', 3)

Я считаю, что приведенный выше код не нуждается в объяснении, все его понимают. Давайте посмотрим на последнюю строку сnewКод ключевого слова анализируется в соответствии с указанными выше шагами 1, 2, 3 и 4.newза кулисами.

Шаг 1: Создайте простой пустой объект

var obj = {}

Шаг 2: Связать объект с другим объектом (цепочка прототипов)

// 设置原型链
obj.__proto__ = Dog.prototype

Шаг 3: Используйте вновь созданный объект на шаге 1 какthisКонтекст

// this指向obj对象
Dog.apply(obj, ['大黄', 'yellow', 3])

Шаг 4: Если функция не возвращает объект, верните этот

// 因为 Dog() 没有返回值,所以返回obj
var dog = obj
dog.getName() // '大黄'

Следует отметить, что если у Dog() есть return, она вернет значение return

var rtnObj = {}
function Dog(name, color, age) {
  // ...
  //返回一个对象
  return rtnObj
}

var dog = new Dog('大黄', 'yellow', 3)
console.log(dog === rtnObj) // true

Затем мы инкапсулируем вышеуказанные шаги в метод инстанцирования объекта, который имитирует операцию new:

function objectFactory(){
    var obj = {};
    //取得该方法的第一个参数(并删除第一个参数),该参数是构造函数
    var Constructor = [].shift.apply(arguments);
    //将新对象的内部属性__proto__指向构造函数的原型,这样新对象就可以访问原型中的属性和方法
    obj.__proto__ = Constructor.prototype;
    //取得构造函数的返回值
    var ret = Constructor.apply(obj, arguments);
    //如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
    return typeof ret === "object" ? ret : obj;
}

58. Что такое функция обратного вызова? Каковы недостатки функций обратного вызова

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

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

const btnAdd = document.getElementById('btnAdd');

btnAdd.addEventListener('click', function clickCallback(e) {
    // do something useless
});

В этом примере мы ждем, пока идентификатор будетbtnAddв элементеclickсобытие, которое выполняется при нажатииclickCallbackфункция. Функция обратного вызова добавляет некоторую функциональность к некоторым данным или событию.

Функции обратного вызова имеют фатальную слабость, то есть легко написать ад обратного вызова (Callback hell). Предположим, что несколько событий имеют зависимости:

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
    
        },3000)
    
    },2000)
},1000)

Это типичный ад обратных вызовов. Приведенный выше код кажется неудобным для чтения и поддержки. Чем больше событий, тем больше беспорядка. Поэтому Promise и async/await предлагаются в es6 для решения проблемы ада обратных вызовов. Конечно, функции обратного вызова также имеют несколько других недостатков, таких как невозможность использовать try catch для перехвата ошибок и невозможность прямого возврата. Следующие два должны решить эти проблемы, давайте посмотрим вниз.

59. Что такое Promise, можно ли реализовать его вручную?

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

Объект Promise является прокси-объектом (проксирует значение), и проксируемое значение может быть неизвестно при создании объекта Promise. Это позволяет вам связывать отдельные обработчики для успеха и неудачи асинхронных операций. Это позволяет асинхронным методам возвращать значения, подобные синхронным методам, но вместо немедленного возврата конечного результата выполнения — объект-обещание, представляющий будущий результат.

Обещание имеет следующие состояния:

  • pending: начальное состояние, ни состояние успеха, ни состояние отказа.
  • выполнено: означает, что операция завершена успешно.
  • отклонено: означает, что операция не удалась.

Как только обещание переходит из состояния ожидания в другое состояние, это состояние никогда не может быть изменено, а это означает, что после того, как состояние становится выполненным/отклоненным, его нельзя изменить снова. Может быть, вы не понимаете Promise, просто взглянув на концепцию, давайте дадим простой каштан;

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

В предыдущей статье мы говорили, что Promise может решить проблему ада обратных вызовов.Да, объект Promise в состоянии pending вызовет состояние выполнено/отклонено.Как только состояние изменится, будет вызван метод then объекта Promise; в противном случае сработает ловушка. Перепишем код предыдущего ада обратного вызова:

new Promise((resolve,reject) => {
     setTimeout(() => {
            console.log(1)
            resolve()
        },1000)
        
}).then((res) => {
    setTimeout(() => {
            console.log(2)
        },2000)
}).then((res) => {
    setTimeout(() => {
            console.log(3)
        },3000)
}).catch((err) => {
console.log(err)
})

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

Рукописное выполнение обещания, достаточное для интервью:

function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    function resolve(value){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //两个==="pending",保证了状态的改变是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
// 定义链式调用的then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

Есть и другие знания о Promise, такие как использование Promise.all(), Promise.race() и т. д., которые не будут расширяться из-за недостатка места.Для более глубокого понимания вы можете прочитать следующие статьи.

Релевантная информация:

«Hardcore JS» погружается в асинхронные решения

【Перевод】Обещания/Спецификация A+

60. IteratorЧто это такое, что он делает?

IteratorЭто обязательное знание для понимания статьи 61, может моего IQ не хватает😭,Iterator和GeneratorПрочитав его много раз, я до сих пор мало что о нем знаю, даже если я и понимал его в то время, то через какое-то время совершенно забыл. . .

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

Синтаксис итератора:

const obj = {
    [Symbol.iterator]:function(){}
}

[Symbol.iterator] Имя свойства является фиксированным способом записи.Пока объект со свойством принадлежит, его можно обойти с помощью итератора.

Метод обхода итератора состоит в том, чтобы сначала получить указатель итератора.Первоначально указатель указывает на первые данные, а затем, вызывая метод next, указатель изменяется так, чтобы он указывал на следующие данные. Каждый следующий будет возвращать объект с двумя свойствами

  • значение представляет данные, которые вы хотите получить
  • done Логическое значение, false указывает, что данные, на которые указывает текущий указатель, имеют значение, а true указывает, что обход завершен

Итератор имеет три роли:

  1. Обеспечить единый и удобный интерфейс доступа к различным структурам данных;
  2. Позволяет расположить элементы структуры данных в определенном порядке;
  3. ES6 создал новую команду обхода for…of цикла, а интерфейс Iterator в основном используется для for…of потребления.

Процесс обхода:

  1. Создает объект-указатель, указывающий на начало текущей структуры данных. Другими словами, объект обхода по существу является объектом указателя.
  2. Первый вызов следующего метода объекта указателя может указать указатель на первый член структуры данных.
  3. При втором вызове следующего метода объекта указателя указатель указывает на второй член структуры данных.
  4. Продолжайте вызывать следующий метод объекта указателя, пока он не укажет на конец структуры данных.

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

let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next()) 	// { value: Object { num: 1 }, done: false }
console.log(it.next()) 	// { value: 2, done: false }
console.log(it.next()) 	// { value: 3, done: false }
console.log(it.next()) 	// { value: undefined, done: true }

61. GeneratorЧто такое функция и что она делает?

Можно сказать, что функция Generator является конкретной реализацией интерфейса Iterator. Самая большая особенность Generator заключается в том, что он может контролировать выполнение функций.

function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

Приведенный выше пример является функцией-генератором, давайте проанализируем процесс ее выполнения:

  • 首先 Generator 函数调用时它会返回一个迭代器
  • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
  • 当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
  • 当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42

GeneratorФункцию вообще мало кто видел, на самом деле она чем-то с ней связана, и она вообще используется в связке с библиотекой co. Конечно, мы можем пройтиGeneratorФункции решают проблему ада обратных вызовов.

62. Что такоеasync/awaitКак это работает, какие плюсы и минусы?

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

asyncявляется асинхронным иawaitдаasync waitСокращение для асинхронного ожидания.

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

Если функция добавлена ​​async , то функция вернет Promise

async function test() {
  return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}

Вы можете видеть, что вывод представляет собой объект Promise. Таким образом, асинхронная функция возвращает объект Promise.PromIse.resolve() Инкапсулируйте его как объект Promise и верните его.

в сравнении сPromise,async/awaitЛучшее обращение с цепями

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

теперь используйтеPromiseа такжеasync/awaitреализовать эти три шага.

Использование промисов

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900

использоватьasync/await

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
}
doIt();

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

await关键字只能在async function中使用。在任何非async function的函数中使用await关键字都会抛出错误。await关键字在执行下一行代码之前等待右侧表达式(可能是一个Promise)返回。

Преимущества и недостатки:

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

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

«Hardcore JS» погружается в асинхронные решения

以上21~25条就是JavaScript中主要的异步解决方案了,难度是有的,需要好好揣摩并加以练习。

63. В чем заключается принцип instanceof и как его реализовать

instanceof может правильно определить тип объекта, потому что внутренний механизм должен определить, можно ли найти прототип типа в цепочке прототипов объекта.

Реализовать экземпляр:

  1. Сначала получите прототип типа
  2. затем получить прототип объекта
  3. Затем продолжайте цикл, чтобы определить, равен ли прототип объекта прототипу типа, пока прототип объекта не станет нулевым, потому что цепочка прототипов в конечном итоге равна нулю.
function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}

64. троттлинг и защита от встряхивания js

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

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


// 函数防抖的实现
function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = arguments;

    // 如果此时存在定时器的话,则取消之前的定时器重新记时
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    // 设置定时器,使事件间隔指定事件后执行
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

// 函数节流的实现;
function throttle(fn, delay) {
  var preTime = Date.now();

  return function() {
    var context = this,
      args = arguments,
      nowTime = Date.now();

    // 如果两次时间间隔超过了指定时间,则执行函数。
    if (nowTime - preTime >= delay) {
      preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

Для получения подробной информации см.:

"Легкое понимание регулирования функции JS и функции защиты от сотрясений"

"Дросселирование событий JavaScript и защита от сотрясений"

"JS анти-дрожание и дросселирование"

65. Что такое шаблон проектирования?

1. Концепция

Шаблон проектирования — это набор повторно используемых, наиболее известных, классифицированных и каталогизированных сводок опыта разработки кода. Шаблоны проектирования используются для повторного использования кода, облегчения его понимания другими и обеспечения надежности кода. Нет никаких сомнений в том, что шаблоны проектирования являются беспроигрышными для себя, других и систем.Шаблоны проектирования делают компиляцию кода по-настоящему инженерной.Шаблоны проектирования являются краеугольным камнем разработки программного обеспечения, точно так же, как кирпичи здания.

2. Принципы проектирования

  1. S – принцип единой ответственности

    • Программа делает только одно
    • Если функция слишком сложна, разделите ее и оставьте каждую часть независимой.
  2. O – открытый закрытый принцип

    • Открыт для расширения, закрыт для модификации
    • Расширяйте новый код вместо изменения существующего кода при добавлении требований
  3. L - принцип замены Лисков

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

    • Держите интерфейс единым и независимым
    • Как и в случае с принципом единой ответственности, здесь мы больше фокусируемся на интерфейсе.
  5. D – Принцип инверсии зависимостей

    • Интерфейсно-ориентированное программирование, основанное на абстракциях, а не на конкретных данных.
    • Пользователь обращает внимание только на интерфейс и не обращает внимания на реализацию конкретного класса.

3. Типы шаблонов проектирования

  1. Структурные модели:Упрощает проектирование системы, определяя простые отношения между компонентами в системе.
  2. Творческие шаблоны:Управляйте созданием объектов и используйте соответствующие методы для создания объектов в соответствии с реальной ситуацией. Традиционное создание объектов может вызвать проблемы с проектированием или увеличить его сложность. Шаблоны создания решают проблемы, тем или иным образом контролируя создание объектов.
  3. Поведенческие модели:Используется для выявления и реализации общих шаблонов взаимодействия между объектами, что повышает гибкость этих взаимодействий.

66. 9 распространенных шаблонов проектирования интерфейса

1. Узор фасада

Шаблон фасада — один из наиболее распространенных шаблонов проектирования, он обеспечивает унифицированный высокоуровневый интерфейс для набора интерфейсов в подсистеме, упрощая использование подсистемы. Короче говоря, шаблон проектирования внешнего вида заключается в том, чтобы абстрагировать сложную логику в нескольких подсистемах, чтобы обеспечить более унифицированный, лаконичный и простой в использовании API. Многие из наших широко используемых фреймворков и библиотек в основном следуют шаблону проектирования внешнего вида.Например, JQuery абстрагирует и инкапсулирует сложные собственные операции DOM и устраняет проблемы совместимости между браузерами, тем самым обеспечивая более продвинутый и простой в использовании интерфейс. На самом деле в нашей повседневной работе мы часто используем режим появления для развития, но не знаем об этом.

  1. Совместимые привязки событий браузера
let addMyEvent = function (el, ev, fn) {
    if (el.addEventListener) {
        el.addEventListener(ev, fn, false)
    } else if (el.attachEvent) {
        el.attachEvent('on' + ev, fn)
    } else {
        el['on' + ev] = fn
    }
}; 
  1. интерфейс пакета
let myEvent = {
    // ...
    stop: e => {
        e.stopPropagation();
        e.preventDefault();
    }
};

Сцены

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

преимущество

  • Уменьшить взаимозависимость системы.
  • Повышение гибкости.
  • улучшенная безопасность

недостаток

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

2. Шаблон прокси

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

Предполагая, что когда А получает цветы, когда он в хорошем настроении, Сяомин имеет шанс выразить успех

60%, а когда А получает цветы, когда он в плохом настроении, вероятность успеха признания Сяо Мина бесконечно близка к 0. Сяо Мин и А знакомы всего два дня, и они до сих пор не могут сказать, когда у А хорошее настроение. Если цветы подарить А не вовремя, цветы Существует высокая вероятность того, что его сразу выбросят.Этот букет цветов был куплен Сяо Мином после того, как он ел лапшу быстрого приготовления в течение 7 дней. Но друг А Б очень хорошо знает А, поэтому Сяомин просто дарит цветы Б, а Б будет следить за изменениями настроения А, а затем выбирает Выберите А, чтобы передать цветок А, когда он в хорошем настроении.Код выглядит следующим образом:

let Flower = function() {}
let xiaoming = {
  sendFlower: function(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower: function(flower) {
    console.log('收到花'+ flower)
  },
  listenGoodMood: function(fn) {
    setTimeout(function() {
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)

Сцены

  • Делегат события элемента HTML
<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
<script>
  let ul = document.querySelector('#ul');
  ul.addEventListener('click', event => {
    console.log(event.target);
  });
</script>

преимущество

  • Режим прокси может отделить прокси-объект от вызываемого объекта и уменьшить степень связанности системы. Шаблон прокси действует как посредник между клиентом и целевым объектом, который может защитить целевой объект.

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

недостаток

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

3. Заводской узор

Фабричный шаблон определяет интерфейс для создания объектов, и этот интерфейс используется подклассами, чтобы решить, какой класс создавать. Этот шаблон откладывает создание экземпляра класса до подклассов. Подклассы могут переопределять методы интерфейса, чтобы указывать свои собственные типы объектов при их создании.

class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fun() {
        console.log('fun')
    }
}

class Factory {
    create(name) {
        return new Product(name)
    }
}

// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()

Сцены

  • Фабричный шаблон идеален, если вам не нужна сильная связь между подсистемой и более крупным объектом, но вы хотите выбирать из множества подсистем во время выполнения.

  • Новая операция просто инкапсулирована.

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

преимущество

  • Процесс создания объекта может быть сложным, но нам нужно заботиться только о результате создания.

  • Конструктор и создатель разделены по принципу «открыто-закрыто».

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

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

недостаток

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

  • Учитывая масштабируемость системы, необходимо ввести уровень абстракции, который определяется в клиентском коде, что увеличивает абстракцию системы и сложность понимания

когда нет

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

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

4. Одноэлементный шаблон

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

Реализация одноэлементного паттерна требует решения следующих проблем:

  • Как я могу быть уверен, что существует только один экземпляр класса?

  • Как легко получить доступ к единственному экземпляру класса?

  • Как класс контролирует процесс создания экземпляра?

  • Как ограничить количество экземпляров класса до 1?

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

  • Скройте конструктор класса, чтобы избежать множественных экземпляров

  • Создание/получение уникальных экземпляров путем предоставления метода getInstance()

Шаблон singleton в Javascript может быть реализован следующими способами:

// 单例构造器
const FooServiceSingleton = (function () {
  // 隐藏的Class的构造函数
  function FooService() {}

  // 未初始化的单例对象
  let fooService;

  return {
    // 创建/获取单例对象的函数
    getInstance: function () {
      if (!fooService) {
        fooService = new FooService();
      }
      return fooService;
    }
  }
})();

Ключевыми моментами, которых необходимо достичь, являются:

  1. Используйте IIFE для создания локальной области видимости и выполнения ее на лету;
  2. getInstance() Для замыкания используйте замыкание, чтобы сохранить одноэлементный объект в локальной области видимости и вернуть его.

Мы можем убедиться, что одноэлементный объект был успешно создан:

const fooService1 = FooServiceSingleton.getInstance();
const fooService2 = FooServiceSingleton.getInstance();

console.log(fooService1 === fooService2); // true

Пример сценария

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

  • окно входа

  • хранить в vuex и redux

преимущество

  • Разделите пространства имен и сократите глобальные переменные

  • Улучшите модульность, организуйте свой код под именем глобальной переменной и поместите его в одном месте для удобства обслуживания.

  • и будет создан только один раз. Упрощенная отладка и обслуживание кода

недостаток

  • Поскольку одноэлементный шаблон обеспечивает единую точку доступа, это может привести к сильной связи между модулями.
  • Это не способствует модульному тестированию. Класс, который вызывает метод из синглтона, не может быть протестирован сам по себе, только как синглтон с этим синглтоном.
  • модульный тест вместе.

5. Паттерн стратегии

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

<html>
<head>
    <title>策略模式-校验表单</title>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body>
    <form id = "registerForm" method="post" action="http://xxxx.com/api/register">
        用户名:<input type="text" name="userName">
        密码:<input type="text" name="password">
        手机号码:<input type="text" name="phoneNumber">
        <button type="submit">提交</button>
    </form>
    <script type="text/javascript">
        // 策略对象
        const strategies = {
          isNoEmpty: function (value, errorMsg) {
            if (value === '') {
              return errorMsg;
            }
          },
          isNoSpace: function (value, errorMsg) {
            if (value.trim() === '') {
              return errorMsg;
            }
          },
          minLength: function (value, length, errorMsg) {
            if (value.trim().length < length) {
              return errorMsg;
            }
          },
          maxLength: function (value, length, errorMsg) {
            if (value.length > length) {
              return errorMsg;
            }
          },
          isMobile: function (value, errorMsg) {
            if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) {
              return errorMsg;
            }                
          }
        }
        
        // 验证类
        class Validator {
          constructor() {
            this.cache = []
          }
          add(dom, rules) {
            for(let i = 0, rule; rule = rules[i++];) {
              let strategyAry = rule.strategy.split(':')
              let errorMsg = rule.errorMsg
              this.cache.push(() => {
                let strategy = strategyAry.shift()
                strategyAry.unshift(dom.value)
                strategyAry.push(errorMsg)
                return strategies[strategy].apply(dom, strategyAry)
              })
            }
          }
          start() {
            for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
              let errorMsg = validatorFunc()
              if (errorMsg) {
                return errorMsg
              }
            }
          }
        }

        // 调用代码
        let registerForm = document.getElementById('registerForm')

        let validataFunc = function() {
          let validator = new Validator()
          validator.add(registerForm.userName, [{
            strategy: 'isNoEmpty',
            errorMsg: '用户名不可为空'
          }, {
            strategy: 'isNoSpace',
            errorMsg: '不允许以空白字符命名'
          }, {
            strategy: 'minLength:2',
            errorMsg: '用户名长度不能小于2位'
          }])
          validator.add(registerForm.password, [ {
            strategy: 'minLength:6',
            errorMsg: '密码长度不能小于6位'
          }])
          validator.add(registerForm.phoneNumber, [{
            strategy: 'isMobile',
            errorMsg: '请输入正确的手机号码格式'
          }])
          return validator.start()
        }

        registerForm.onsubmit = function() {
          let errorMsg = validataFunc()
          if (errorMsg) {
            alert(errorMsg)
            return false
          }
        }
    </script>
</body>
</html>

Пример сценария

  • Если в системе много классов, отличающихся только своим «поведением», то использование шаблона стратегии может динамически позволить объекту выбирать одно поведение из многих.

  • Системе необходимо динамически выбирать один из нескольких алгоритмов.

  • проверка формы

преимущество

  • Используя методы и идеи, такие как композиция, делегирование, полиморфизм и т. д., можно эффективно избежать множественных операторов условного выбора.

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

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

недостаток

  • В программу будет добавлено множество классов политик или объектов политик.

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

6. Шаблон итератора

Если вы видите это, я полагаю, вы все еще немного впечатлены итератором Iterator в ES6, который был кратко представлен в пункте 60 выше. Проще говоря, шаблон итератора обеспечивает способ упорядочивания элементов агрегатного объекта без раскрытия внутреннего представления объекта.

Шаблон итератора решает следующие проблемы:

  • Обеспечивает согласованный способ обхода различных структур данных без понимания внутренней структуры данных.

  • Предоставляет возможность обхода контейнера (коллекции) без изменения интерфейса контейнера.

Итератор обычно должен реализовать следующие интерфейсы:

  • hasNext(): определяет, закончилась ли итерация, возвращает логическое значение

  • next(): найти и вернуть следующий элемент

Реализация итератора для массива в Javascript может быть написана так:

const item = [1, 'red', false, 3.14];

function Iterator(items) {
  this.items = items;
  this.index = 0;
}

Iterator.prototype = {
  hasNext: function () {
    return this.index < this.items.length;
  },
  next: function () {
    return this.items[this.index++];
  }
}

Убедитесь, что итератор работает:

const iterator = new Iterator(item);

while(iterator.hasNext()){
  console.log(iterator.next());
}
//输出:1, red, false, 3.14

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

Например, мы реализуем класс Range для перебора диапазона чисел:

function Range(start, end) {
  return {
    [Symbol.iterator]: function () {
      return {
        next() {
          if (start < end) {
            return { value: start++, done: false };
          }
          return { done: true, value: end };
        }
      }
    }
  }
}

Подтвердите это:

for (num of Range(1, 5)) {
  console.log(num);
}
// 输出:1, 2, 3, 4

7. Шаблон наблюдателя

Шаблон наблюдателятакже известен какмодель публикации-подписки(Шаблон публикации/подписки) — это шаблон дизайна, с которым мы часто сталкиваемся, и в нашей повседневной жизни есть много применений. Например, если вы подписываетесь на канал блоггера, вы будете получать уведомление при обновлении контента; другое пример — механизм ответа на подписку на события JavaScript в . Идея паттерна Observer описана в одном предложении:Наблюдаемый объект (субъект) поддерживает набор наблюдателей (наблюдателя), при изменении состояния наблюдаемого объекта наблюдатель уведомляется об этих изменениях вызовом метода наблюдателя.

Объект Subject в шаблоне наблюдателя обычно должен реализовывать следующие API:

  • подписаться(): получить объект наблюдателя и заставить его подписаться на себя

  • unsubscribe(): получает объект наблюдателя, заставляя его отказаться от подписки

  • fire(): запустить событие, уведомить всех наблюдателей

Вручную реализуйте шаблон Observer в JavaScript:

// 被观察者
function Subject() {
  this.observers = [];
}

Subject.prototype = {
  // 订阅
  subscribe: function (observer) {
    this.observers.push(observer);
  },
  // 取消订阅
  unsubscribe: function (observerToRemove) {
    this.observers = this.observers.filter(observer => {
      return observer !== observerToRemove;
    })
  },
  // 事件触发
  fire: function () {
    this.observers.forEach(observer => {
      observer.call();
    });
  }
}

Убедитесь, что подписка прошла успешно:

const subject = new Subject();

function observer1() {
  console.log('Observer 1 Firing!');
}


function observer2() {
  console.log('Observer 2 Firing!');
}

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.fire();

//输出:
Observer 1 Firing! 
Observer 2 Firing!

Убедитесь, что отписка прошла успешно:

subject.unsubscribe(observer2);
subject.fire();

//输出:
Observer 1 Firing!

Сцены

  • DOM-события
document.body.addEventListener('click', function() {
    console.log('hello world!');
});
document.body.click()
  • vue отзывчивый

преимущество

  • Поддержка простой широковещательной связи, автоматическое уведомление всех подписанных объектов

  • Абстрактные отношения связи между целевым объектом и наблюдателем могут быть независимо расширены и повторно использованы.

  • повышенная гибкость

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

недостаток

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

8. Паттерн посредника

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

Режим посредника и режим наблюдателя имеют определенное сходство.Они представляют собой отношения «один ко многим» и централизованную коммуникацию.Разница в том, что режим посредника имеет дело с взаимодействиями между объектами на одном уровне, а режим наблюдателя – с взаимодействием между наблюдателем. и Тема. Посредническая модель чем-то похожа на посредника в браке и любви.Свидание вслепую не может общаться напрямую в начале, но должно быть проверено и сопоставлено через посредника, чтобы решить, с кем встретиться.

Сцены

  • Например, запрос корзины покупок, форма выбора продукта, форма выбора цвета, форма количества покупки и т. д. инициируют событие изменения, затем эти события могут быть перенаправлены и обработаны через посредника для реализации разделения между различные события и поддерживать только промежуточный объект.
var goods = {   //手机库存
    'red|32G': 3,
    'red|64G': 1,
    'blue|32G': 7,
    'blue|32G': 6,
};
//中介者
var mediator = (function() {
    var colorSelect = document.getElementById('colorSelect');
    var memorySelect = document.getElementById('memorySelect');
    var numSelect = document.getElementById('numSelect');
    return {
        changed: function(obj) {
            switch(obj){
                case colorSelect:
                    //TODO
                    break;
                case memorySelect:
                    //TODO
                    break;
                case numSelect:
                    //TODO
                    break;
            }
        }
    }
})();
colorSelect.onchange = function() {
    mediator.changed(this);
};
memorySelect.onchange = function() {
    mediator.changed(this);
};
numSelect.onchange = function() {
    mediator.changed(this);
};
  • чат

Класс участников чата:

function Member(name) {
  this.name = name;
  this.chatroom = null;
}

Member.prototype = {
  // 发送消息
  send: function (message, toMember) {
    this.chatroom.send(message, this, toMember);
  },
  // 接收消息
  receive: function (message, fromMember) {
    console.log(`${fromMember.name} to ${this.name}: ${message}`);
  }
}

Класс чата:

function Chatroom() {
  this.members = {};
}

Chatroom.prototype = {
  // 增加成员
  addMember: function (member) {
    this.members[member.name] = member;
    member.chatroom = this;
  },
  // 发送消息
  send: function (message, fromMember, toMember) {
    toMember.receive(message, fromMember);
  }
}

есть тест:

const chatroom = new Chatroom();
const bruce = new Member('bruce');
const frank = new Member('frank');

chatroom.addMember(bruce);
chatroom.addMember(frank);

bruce.send('Hey frank', frank);

//输出:bruce to frank: hello frank

преимущество

  • Сделайте связь между объектами слабой, а взаимодействие между ними можно будет изменить независимо друг от друга.

  • Связь «один ко многим» между посредниками и объектами заменяет ячеистую связь «многие ко многим» между объектами.

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

недостаток

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

9. Шаблон посетителя

шаблон посетителяЭто шаблон проектирования, который отделяет алгоритм от структуры объекта.С точки зрения непрофессионала, шаблон посетителя позволяет нам добавлять новую логику к объекту, не изменяя структуру объекта, а добавленная логика хранится в независимом файле посетителя. объект. Шаблон посетителя часто используется для расширения некоторых сторонних библиотек и инструментов.

// 访问者  
class Visitor {
    constructor() {}
    visitConcreteElement(ConcreteElement) {
        ConcreteElement.operation()
    }
}
// 元素类  
class ConcreteElement{
    constructor() {
    }
    operation() {
       console.log("ConcreteElement.operation invoked");  
    }
    accept(visitor) {
        visitor.visitConcreteElement(this)
    }
}
// client
let visitor = new Visitor()
let element = new ConcreteElement()
elementA.accept(visitor)

Реализация шаблона посетителя состоит из следующих элементов:

  • Объект посетителя: объект посетителя, имеетvisit()метод

  • Получающий объект: принимающий объект с accept()метод

  • visit(receiveObj): используется для получения посетителемReceiving Object

  • принять (посетитель): используется дляReceving ObjectПримите посетителя и позвонитеVisitorизvisit()предоставить доступ кReceiving Objectвозможности данных

Простая реализация кода выглядит следующим образом:

Receiving Object:

function Employee(name, salary) {
  this.name = name;
  this.salary = salary;
}

Employee.prototype = {
  getSalary: function () {
    return this.salary;
  },
  setSalary: function (salary) {
    this.salary = salary;
  },
  accept: function (visitor) {
    visitor.visit(this);
  }
}
Visitor Object:

function Visitor() { }

Visitor.prototype = {
  visit: function (employee) {
    employee.setSalary(employee.getSalary() * 2);
  }
}

Подтвердите это:

const employee = new Employee('bruce', 1000);
const visitor = new Visitor();
employee.accept(visitor);

console.log(employee.getSalary());//输出:2000

Сцены

  • Класс, соответствующий объекту в структуре объекта, меняется редко, но часто бывает необходимо определить новые операции над этой структурой объекта.

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

преимущество

  • Соблюдайте принцип единой ответственности

  • Отличная масштабируемость

  • гибкость

недостаток

  • Публикация сведений об определенных элементах для посетителей нарушает принцип Деметры.

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

  • Сложнее изменить отдельные элементы

Ссылки по теме:

Шаблон проектирования JavaScript es6 (23 вида)

«Путь фронтенд-интервью»

Шаблоны проектирования JavaScript

«Общие шаблоны проектирования в JavaScript»

💕 После прочтения трех вещей:

  1. Нравится | Можно点击——>收藏——>退出За один раз, но не забудьте поставить лайк 🤭
  2. Смотреть | точка беспокойства, в следующий раз не теряйтесь 😘
  3. Вы также можете пойти вGitHubПолучить все исходники моих статей 🤗

позже

Вышеуказанные 66 пунктов являются кратким изложением обзора за последние несколько дней.Всё в порядке от мелкого к более глубокому.Небольшая часть содержания не является оригинальной.Я разместил ссылку в конце каждой статьи для связанных ссылок.Спасибо за ваши блоги, вы мне очень помогли~

Фронтенд — это сборная солянка, и различные фреймворки возникают нескончаемым потоком, но постоянные изменения неотделимы от JS, а прагматичный фундамент — это фундамент.Если вы считаете, что эта статья вам полезна, поставьте лайк и поддержите это~