30 вопросов на собеседовании по JS помогли мне пробежать 20 000 000 000 000 000 000 рублей.

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

«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!"

предисловие

Я просто хочу столкнуться с инженером CV, но я вызов, что интервьюер происходит, чтобы сделать Rocket Engineer, но борьба в тяжелой жизни, мы должны продолжать искать работу. Обычно не обращают внимания на накопление интервью - всегда неполный ответ.

1. Каковы примитивные типы данных JS? Что такое справочные типы данных?

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • bigint

Тип ссылочных данных: Объект Объект (включая обычный объект-Объект, объект-массив-Массив, обычный объект-RegExp, объект даты Дата, математическая функция-Математика, объект-функция-Функция)

2. Укажите результат следующей операции и объясните, почему.

function test(person) {
person.age = 26
person = {
name: 'hzj',
age: 18
}
return person
}
const p1 = {
name: 'fyq',
age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?

p1:{name: “fyq”, age: 26}
p2:{name: “hzj”, age: 18}

Причина: когда функция передает параметры, передается значение адреса памяти объекта в куче, а фактическим параметром person в тестовой функции является объект p1. Адрес памяти при вызове person.age = 26 изменяет значение p1, но тогда человек становится другим пространством памяти. адрес, и в конце адрес этого другого пространства памяти возвращается и присваивается p2.

3. Является ли null объектом? Почему?

Вывод: null не является объектом. Объяснение: Хотя typeof null выводит объект, это просто давняя ошибка в JS. В начальной версии JS использовалась 32-битная система.Из соображений производительности информация о типе переменной хранится в младших битах.Начало 000 представляет объект, но ноль представляет собой все нули, поэтому оценивается неправильно как объект 。

4.'1'.tostring () Почему можно позвонить?

На самом деле в процессе выполнения этого оператора выполняются следующие действия:

var s = new Object('1');
s.toString();
s = null;

Шаг 1: Создайте экземпляр класса Object. Обратите внимание, почему не String? В связи с появлением Symbol и BigInt вызов для них new будет приводить к ошибке.В настоящее время спецификация ES6 не рекомендует использовать new для создания классов-оболочек для базовых типов. Шаг 2: вызовите метод экземпляра. Шаг 3: Уничтожьте экземпляр сразу после выполнения метода. Весь процесс отражает природу базового типа упаковки, а базовый тип упаковки относится к базовым типам данных, включая Boolean, Number и String.

Почему 5.0.1+0.2 не равно 0.3?

0.1 и 0.2 будут бесконечно зацикливаться после преобразования в двоичное. Из-за ограничения стандартных цифр лишние цифры будут усечены. В это время уже произошла потеря точности. Двоичное число при преобразовании в десятичное становится 0,30000000000000004.

6. Что такое BigInt?

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

7. Зачем вам нужен BigInt?

В JS все числа представлены в 64-битном формате двойной точности с плавающей запятой, так в чем же проблема? Это приводит к тому, что число в JS не может точно представлять очень большие целые числа, оно будет округлять очень большие целые числа, а точнее, число в JS. Тип Number может безопасно представлять только -9007199254740991(-(2^53-1)) и 9007199254740991((2^53-1)), любое целочисленное значение за пределами этого диапазона может потерять точность.

console.log(999999999999999); //=>10000000000000000

Есть также некоторые проблемы с безопасностью:

9007199254740992 === 9007199254740993; // → true 居然是true!

8. Как создать и использовать BigInt?

Чтобы создать BigInt, просто добавьте n в конец числа.

console.log( 9007199254740995n ); // → 9007199254740995n
console.log( 9007199254740995 ); // → 9007199254740996

Другой способ создать BigInt — использовать конструктор BigInt().

BigInt("9007199254740995"); // → 9007199254740995n

Простое использование следующим образом:

10n + 20n; // → 30n
10n - 20n; // → -10n
+10n; // → TypeError: Cannot convert a BigInt value to a number
-10n; // → -10n
10n * 20n; // → 200n
20n / 10n; // → 2n
23n % 10n; // → 3n
10n ** 3n; // → 1000n
const x = 10n;
++x; // → 11n
--x; // → 9n
console.log(typeof x); //"bigint"

Моменты, которых следует опасаться

(1) BigInt не поддерживает унарный оператор плюс, что может быть связано с тем, что некоторые программы могут полагаться на +, чтобы всегда генерировать инвариант числа или генерировать исключение. Кроме того, изменение поведения + также нарушает код asm.js.

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

10 + 10n; // → TypeError

(3) BigInt нельзя передать веб-API и встроенным функциям JS, которые ожидают число типа Number. Попытка сделать это вызовет TypeError.

(4) Когда тип Boolean встречается с типом BigInt, BigInt обрабатывается так же, как и Number, другими словами, пока он не равен 0n, BigInt рассматривается как истинное значение.

(5) Массивы, все элементы которых имеют тип BigInt, можно сортировать.

(6) BigInt может нормально выполнять побитовые операции, такие как |, &, > и ^

9. Может ли введите тип судить тип правильно?

Для примитивных типов все, кроме null, могут вызывать typeof для отображения правильного типа.

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'

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

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

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

const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str1 = 'hello world'
str1 instanceof String // false
var str2 = new String('hello world')
str2 instanceof String // true

10. Может ли instanceof судить об основном типе данных?

могу. Например так:

class PrimitiveNumber {
    static [Symbol.hasInstance](x) {
        return typeof x === 'number'
    }
}
console.log(111 instanceof PrimitiveNumber) // true

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

11. Можно ли реализовать функцию instanceof вручную?

function myInstanceof(left, right) {
//基本数据类型直接返回false
    if(typeof left !== 'object' || left === null) return false;
    //getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(left);
    while(true) {
    //查找到尽头,还没找到
    if(proto == null) return false;
    //找到相同的原型对象
    if(proto == right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
    }
}

console.log(myInstanceof("111", String)); //false
console.log(myInstanceof(new String("111"), String));//true

12. В чем разница между Object.is и ===?

Объект исправляет некоторые ошибки особого случая, основанные на строгом равенстве, в частности, +0 и -0, NaN и NaN. исходный код, например Вниз:

function is(x, y) {
if (x === y) {
//运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不
一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
//NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
//两个都是NaN的时候返回true
return x !== x && y !== y;
}

13. Каков результат [] == ![]? Почему?

В == обе стороны должны быть преобразованы в числа, а затем сравнены.

[] преобразуется в число 0.

! [] впервые преобразуется в логическое значение, поскольку [] в качестве типов оформления преобразуется в логическое значение true, Итак! [] - ложный, который, в свою очередь, преобразуется в число и становится 0. 0 == 0, результат правда

14. Какой процесс выполняется на основе какого процесса?

При преобразовании объекта к примитивному типу будет вызвана встроенная функция [ToPrimitive], для которой логика следующая:

  1. Если метод Symbol.toPrimitive(), сначала вызовите его, а затем верните
  2. вызов valueOf(), который возвращает значение при приведении к примитивному типу
  3. Вызов toString(), который возвращает значение при приведении к примитивному типу
  4. Если ни один из исходных типов не будет возвращен, будет сообщено об ошибке
var obj = {
value: 3,
valueOf() {
return 4;
},
toString() {
return '5'
},
[Symbol.toPrimitive]() {
return 6
}
}
console.log(obj + 1); // 输出7

15. Как сделать условие if(a == 1 && a == 2) истинным?

var a = {
    value: 0,
    valueOf: function() {
    this.value++;
    return this.value;
   }
};
console.log(a == 1 && a == 2);//true

16. Что такое замыкание?

Определение замыкания в Красной книге: замыкание — это функция, которая имеет доступ к переменным в области видимости другой функции. MDN определяет замыкания следующим образом: Замыкания — это функции, которые могут обращаться к свободным переменным. (где свободные переменные относятся к переменным, которые используются в функции, но не являются ни аргументами параметра функции, ни локальными переменными функции, которые Real — это переменная в области действия другой функции. )

17. Причина закрытия?

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

var a = 1;
function f1() {
var a = 2
function f2() {
var a = 3;
console.log(a);//3
}
}

В этом коде область f1 указывает на глобальную область (окно) и себя, а область действия f2 указывает на глобальную область. (окно), f1 и себя. И область видимости снизу вверх до тех пор, пока не будет найдено окно глобальной области видимости, если глобальная область видимости Если нет, то сообщит об ошибке. Это так просто! Суть замыкания в том, что есть ссылка на родительскую область видимости в текущей среде. Или возьмем приведенный выше пример:

function f1() {
var a = 2
function f2() {
console.log(a);//2
}
return f2;
}
var x = f1();
x();

Здесь X получит переменную в родительском объеме и выходе 2. Поскольку в текущей среде есть ссылка на F2, а F2 просто относится к Область окна, F1 и F2. Таким образом, F2 может получить доступ к переменным в объеме F1. Это то, что только обратная функция может рассматриваться как закрытие? , Возвращаясь к сущности закрытия, нам нужно только сделать ссылку на родительскую область существовать, поэтому мы также можем сделать это:

var f3;
function f1() {
var a = 2
f3 = function() {
console.log(a);
}
}
f1();
f3();

Пусть f1 выполняется, и после присвоения значения f3, это означает, что теперь у f3 есть права доступа к областям окна, f1 и самой f3, и она все еще ищется снизу вверх.Недавно в f1 был найден a, поэтому 2 выводится. Здесь внешняя переменная f3 имеет ссылку на родительскую область видимости, поэтому генерируется замыкание, форма изменилась, но суть не изменилась.

18. Как решить следующую проблему с выводом цикла?

for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)
}

Почему все 6 выводятся? Как его улучшить, чтобы он выдавал 1, 2, 3, 4, 5? (чем больше методов, тем лучше) Поскольку setTimeout — это задача макроса, из-за однопоточного механизма eventLoop в JS задача макроса выполняется после выполнения задачи синхронизации основного потока. Поэтому после окончания цикла колбэки в setTimeout выполняются последовательно, но при выводе i текущая область видимости недоступна.Переходим на верхний уровень и находим i.В это время цикл закончился, и у i стать 6. Таким образом, все 6 будут выведены.

Решение:

1. Используйте IIFE (немедленное выполнение функционального выражения), чтобы передавать переменную i в это время таймеру каждый раз, когда цикл for

for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}

2. Передать третий параметр таймеру в качестве первого функционального параметра функции таймера.

for(var i=1;i<=5;i++){
setTimeout(function timer(j){
console.log(j)
}, 0, i)
}

3. Использование let в ES6

for(let i = 1; i <= 5; i++){
setTimeout(function timer(){
console.log(i)
},0)
}

let произвел революцию в JS, сделав область функций JS областью на уровне блоков, а цепочка областей видимости больше не существует после использования let. Объем кода указан в единицах на уровне блоков.В качестве примера возьмем приведенный выше код:

// i = 1
{
setTimeout(function timer(){
console.log(1)
},0)
}
// i = 2
{
setTimeout(function timer(){
console.log(2)
},0)
}
// i = 3
...

19. Как в JS реализовано наследование?

Первый: с помощью звонка

function Parent1(){
this.name = 'parent1';
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);

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

Второй: с помощью цепочки прототипов

function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3]
}
function Child2() {
this.type = 'child2';
}
Child2.prototype = new Parent2();
console.log(new Child2());

Третий: объединить первые два

function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

20. Почему аргументы функции не являются массивом? Как преобразовать в массив?

Поскольку аргументы сами по себе не могут вызывать методы массива, это другой тип объекта, но свойства располагаются от 0, а затем 0, 1, 2... Наконец, есть свойства callee и length. Мы также называем такие объекты массивоподобными. Распространенные типы массивов:

  • Коллекция HTMLCollection, полученная с помощью getElementsByTagName/ClassName()
  • nodeList, полученный с помощью querySelector

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

Array.prototype.slice.call()

function sum(a, b) {
let args = Array.prototype.slice.call(arguments);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3

Array.from()

function sum(a, b) {
let args = Array.from(arguments);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3

Оператор спреда ES6

function sum(a, b) {
let args = [...arguments];
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3

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

function sum(a, b) {
let args = Array.prototype.concat.apply([], arguments);//apply方法会把第二个参数展开
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3

21. Эффективен ли возврат forEach? Как разорвать цикл forEach?

Использование return в forEach не приведет к возврату, функция продолжит выполнение.

let nums = [1, 2, 3];
nums.forEach((item, index) => {
return;//无效
})

Метод прерывания: 1) Используйте попытку для мониторинга блоков кода и выбрасывайте исключения, где вам нужно сломаться. 2) Официально рекомендуемый метод (метод замены): заменить функцию forEach на every и some. каждый завершает цикл, когда встречает return false. некоторые завершают цикл, когда он встречает return true

22. Можете ли вы смоделировать эффект нового?

Когда new вызывается, он делает три вещи:

  1. Разрешить экземпляру доступ к закрытым свойствам
  2. Позволяет экземплярам получать доступ к свойствам в цепочке прототипов, где находится прототип конструктора (constructor.prototype).
  3. Если результат, возвращаемый конструктором, не является ссылочным типом данных
function newOperator(ctor, ...args) {
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
let obj = Object.create(ctor.prototype);
let res = ctor.apply(obj, args);
let isObject = typeof res === 'object' && res !== null;
let isFunction = typoof res === 'function';
return isObect || isFunction ? res : obj;
};

23. Можете ли вы смоделировать эффект реализации привязки?

Перед реализацией bind нам сначала нужно узнать, что он делает.

  1. Для обычных функций привяжите это, чтобы указать на
  2. Для конструкторов необходимо гарантировать, что свойства объекта-прототипа исходной функции не могут быть потеряны.
Function.prototype.bind = function (context, ...args) {
// 异常处理
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is
not callable");
}
// 保存this的值,它代表调用 bind 的函数
var self = this;
var fNOP = function () {};
var fbound = function () {
self.apply(this instanceof self ?
this :
context, args.concat(Array.prototype.slice.call(arguments)));
}
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;
}

Вы также можете использовать Object.create для работы с прототипами следующим образом:

Function.prototype.bind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is
not callable");
}
var self = this;
var fbound = function () {
self.apply(this instanceof self ?
this :
context, args.concat(Array.prototype.slice.call(arguments)));
}
fbound.prototype = Object.create(self.prototype);
return fbound;
}

24. Расскажите о механизме сборки мусора в javascript.

Определение: Относится к части выделенной памяти, которая не может быть ни использована, ни освобождена, пока процесс браузера не завершится. Языки программирования, такие как C, имеют низкоуровневые примитивы управления памятью, такие как malloc() и free(). Разработчики используют эти примитивы для явного выделения и освобождения памяти операционной системой. А JavaScript выделяет память для объектов (объектов, строк и т. д.) при их создании и «автоматически» освобождает память, когда они больше не используются.Этот процесс называется сборкой мусора.

Каждый этап жизненного цикла памяти:

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

25. Защита от сотрясений и дросселирование

1. Защита от сотрясения

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

  • Чтобы понять концепцию, вы должны сначала понять контекст, в котором она применяется. Какие в мире JS есть сцены, предотвращающие тряску?
  • Кнопки, такие как вход в систему и отправка текстовых сообщений, не позволяют пользователям нажимать слишком быстро, поэтому отправляются несколько запросов, и требуется защита от сотрясений.
  • При изменении размера окна браузера число изменений слишком частое, что приводит к слишком большому количеству вычислений. В настоящее время оно должно быть на месте один раз, поэтому используется защита от сотрясений.
  • Текстовый редактор сохраняет в реальном времени и сохраняет через одну секунду без каких-либо изменений
 function debounce (f, wait) {
 let timer
 return (...args) => {
 clearTimeout(timer)
     timer = setTimeout(() => {
       f(...args)
     }, wait)
   }
 }
2. Дросселирование

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

  • событие прокрутки, вычисление информации о положении каждую секунду и т. д.
  • События воспроизведения браузера, информация о прогрессе рассчитывается каждую секунду и т. д.
  • Поле ввода выполняет поиск в режиме реального времени и отправляет запрос на отображение выпадающего списка, а также отправляет запрос каждую секунду (также может выполнять анти-встряску)
 function throttle (f, wait) {
 let timer
 return (...args) => {
 if (timer) { return }
     timer = setTimeout(() => {
       f(...args)
       timer = null
     }, wait)
   }
 }

26. Алгоритм пузырьковой сортировки

var array = [5, 4, 3, 2, 1];
var temp = 0;
for (var i = 0; i <array.length; i++){
for (var j = 0; j <array.length - i; j++){
if (array[j] > array[j + 1]){
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
 }
}

27. Напишите функцию для удаления пробелов до и после строки. (Совместим со всеми браузерами)

function trim(str) {
    if (str & typeof str === "string") {
        return str.replace(/(^\s*)|(\s*)$/g,""); //去除前后空白符
    }
}

28. Вручную реализовать метод Array.prototype.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;
  }
}

29. Вручную реализовать метод Array.prototype.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
  }
}

30. Вручную реализовать метод Array.prototype.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;
  }
}

Отличные прошлые статьи