«Интервью должно встречаться» написанные от руки 35 высокочастотных вопросов интервью JS (2)

JavaScript
«Интервью должно встречаться» написанные от руки 35 высокочастотных вопросов интервью JS (2)

предисловие

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

Статьи по Теме:«Интервью должно быть выполнено» рукописные вопросы интервью, которые должны быть выполнены в расширенном интерфейсе (1)

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

1. Наследование конструктора

  • Идеи:

Выполнить конструктор родительского класса в конструкторе дочернего класса. и привязать к нему подклассыthis, пусть конструктор родительского класса подвешивает свойства и методы членов в子类的thisЭто позволяет избежать совместного использования экземпляра прототипа между экземплярами и может передавать параметры конструктору родительского класса.

  • выполнить:
// 父类
function Parent(){
  this.name = 'Cherry';
}
// 父类的原型方法
Parent.prototype.getName = function() {
  return this.name;
};
// 子类
function Child(){
  Parent.call(this);
  this.type = 'child';
};
const child = new Child();
console.log(child); // Child { name: 'Cherry', type: 'child' }
console.log(child.getName()); // 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法

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

2. Наследование цепочки прототипов

  • Идеи:

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

  • выполнить:
function Parent() {
  this.name = 'Cherry';
  this.play = [1, 2, 3];
}
Parent.prototype.getName = function() {
  return this.name;
}
function Child() {
  this.type = 'child';
}
// 子类的原型对象指向父类实例
Child.prototype = new Parent();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
Child.prototype.constructor = Child;

const child = new Child();
console.log(child);  // Parent { type: 'child' }
console.log(child.getName()); // Cherry

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

const child1 = new Child();
const child2 = new Child();
child1.play.push(4);
console.log(child1.play, child2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]

В приведенном выше примере, хотя я изменил толькоchild1изplayсвойства, ноchild2изplay属性也跟着变了。 Причина в том, чтоНа два экземпляра ссылается один и тот же объект-прототип.

Из этого мы можем сделать вывод, что использование наследования цепочки прототипов имеет следующие два недостатка:

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

3. Композиционное наследование

Поскольку наследование цепочки прототипов и наследование конструктора имеют дополнительные преимущества и недостатки, почему бы нам не использовать их вместе?наследование композиции.

function Parent () {
  this.name = 'Cherry';
  this.play = [1, 2, 3];
}
Parent.prototype.getName = function() {
  return this.name;
}
function Child() {
  // 构造函数继承
  Parent.call(this);
  this.type = 'child';
}
//原型链继承
Child.prototype = new Parent();
// 如果不指定 Child.prototype.constructor 为 Child,根据原型链规则会默认向上查找,最后会指向 Parent
Child.prototype.constructor = Child;

const child1 = new Child();
const child2 = new Child();
console.log(child1); // Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }
console.log(child1.getName()); // Cherry
child1.play.push(4);
console.log(child1.play, child2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3 ]

Мы можем видеть через вывод консоли, что предыдущие проблемы были решены. Но тут добавилась новая проблема, то есть конструктор Parent будет выполняться еще разChild.prototype = new Parent();Хотя это не влияет на наследование родительского класса, когда дочерний класс создает экземпляр, в прототипе будет две копии одних и тех же свойств и методов, что не элегантно. Итак, как решить эту проблему?

4. Паразитарная композиция наследство

Чтобы решить проблему двукратного выполнения конструктора, мы指向父类实例изменить на指向父类原型, минус одно выполнение конструктора.

function Parent () {
  this.name = 'Cherry';
  this.play = [1, 2, 3];
}

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

function Child() {
  Parent.call(this);
  this.type = 'child';
}
// 将`指向父类实例`改为`指向父类原型`
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;

const child1 = new Child();
const child2 = new Child();
console.log(child1); // Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }
console.log(child1.getName()); // Cherry
child1.play.push(4);
console.log(child1.play, child2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3 ]

Но с этим методом есть проблема: поскольку прототип подкласса и прототип родительского класса указывают на один и тот же объект, наши операции над прототипом подкласса будут влиять на прототип родительского класса, например, присвоивChild.prototypeдобавить одинgetName()метод, тоParent.prototypeтакже добавляется или перезаписывается с помощьюgetName()метод, чтобы решить эту проблему, мы дадимParent.prototypeСделайте неглубокую копию.

function Parent () {
  this.name = 'Cherry';
  this.play = [1, 2, 3];
}

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

function Child() {
  Parent.call(this);
  this.type = 'child';
}
// 给Parent.prototype做一个浅拷贝
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child1 = new Child();
const child2 = new Child();
console.log(child1); // Child { name: 'Cherry', play: [ 1, 2, 3 ], type: 'child' }
console.log(child1.getName()); // Cherry
child1.play.push(4);
console.log(child1.play, child2.play); // [ 1, 2, 3, 4 ] [ 1, 2, 3 ]

На этом мы завершили реализацию наследования в среде ES5, этот метод наследования называется寄生组合式继承, является наиболее зрелым методом наследования в настоящее время.Babel также использует паразитное наследование композиции для преобразования наследования ES6.

2. Реализовать выравнивание массива

  • концепция:

Преобразование многомерного массива в одномерный массив:

[1, [2, 3, [4, 5]]]  ------>    [1, 2, 3, 4, 5]

1. Плоский ES6 ()

let arr = [1, [2, 3, [4, 5]]];
arr.flat(Infinity);

2. Регуляризация после сериализации

let arr = [1, [2, 3, [4, 5]]];
let str = JSON.stringify(arr).replace(/(\[|\])/g, '');
str = '[' + str + ']';
JSON.parse(str);   // [1, 2, 3, 4, 5]

3. Рекурсивная обработка

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

let arr = [1, [2, 3, [4, 5]]];

function flat(arr) {
  let result = [];
  for (const item of arr) {
    item instanceof Array ? result = result.concat(flat(item)) : result.push(item)
  }
  return result;
}

flat(arr);  // [1, 2, 3, 4, 5]

4. reduce

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

let arr = [1, [2, 3, [4, 5]]];

function flat(arr) {
  return arr.reduce((prev, current) => {
    return prev.concat(current instanceof Array ? flat(current) : current)
  }, [])
}

flat(arr);  // [1, 2, 3, 4, 5]

5. Итерация + оператор распространения

Расширенный оператор es6 становится способным к двумерному массиву одномерных

// 每次while都会合并一层的元素,然后arr.some判定数组中是否存在数组,如果存在,继续进入第二次循环进行合并
let arr = [1, [1,2], [1,2,3,[4,4,4]]]

while (arr.some(Array.isArray)) {
  arr = [].concat(...arr);
}

console.log(arr); // [ 1, 1, 2, 1, 2, 3, 4, 4, 4 ]

3. Реализовать дедупликацию массива

1. Используйте метод фильтра

Метод filter может отфильтровывать элементы, не соответствующие условиям, и возвращать новый массив, любые массивы, не соответствующие условиям, не будут в отфильтрованном массиве.

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  return data.filter((value, index) => data.indexOf(value) === index);
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

Мы также можем использовать метод фильтра для извлечения повторяющихся значений из данных с помощью простой настройки.

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  return data.filter((value, index) => data.indexOf(value) !== index);
}

console.log(removeDuplicates(arr)); // [ 'apple', 'lemon' ]

2. Используйте наборы ES6

Set — это новый тип объекта в ES6 для создания набора уникальных ключей.

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  return [...new Set(data)];
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

3. Используйте метод forEach

Метод forEach может выполнять итерацию по элементам в массиве, и если элемента нет в массиве, помещать элемент в массив.

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  let unique = [];
  data.forEach(element => {
    if (!unique.includes(element)) {
      unique.push(element);
    }
  });
  return unique;
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

4. Используйте метод сокращения

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  let unique = data.reduce(function (a, b) {
    if (a.indexOf(b) < 0) a.push(b);
    return a;
  }, []);
  return unique;
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

или:

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  return data.reduce((acc, curr) => acc.includes(curr) ? acc : [...acc, curr], []);
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

5. Добавьте метод дедупликации в прототип массива

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

Array.prototype.unique = function () {
  let unique = [];
  for (let i = 0; i < this.length; i++) {
    const current = this[i];
    if (unique.indexOf(current) < 0) unique.push(current);
  }
  return unique;
}

console.log(arr.unique()); // [ 'banana', 'apple', 'orange', 'lemon' ]

6. Array.from + ES6 Set

let arr = ["banana", "apple", "orange", "lemon", "apple", "lemon"];

function removeDuplicates(data) {
  return Array.from(new Set(arr))
}

console.log(removeDuplicates(arr)); // [ 'banana', 'apple', 'orange', 'lemon' ]

7. Удалить повторяющиеся объекты из массива объектов

Иногда нам нужно удалить повторяющиеся объекты из данных объекта по имени свойства, для этого мы можем использовать Map:

let users = [
  { id: 1, name: 'susan', age: 25 },
  { id: 2, name: 'cherry', age: 28 },
  { id: 3, name: 'cindy', age: 27 },
  { id: 2, name: 'cherry', age: 28 },
  { id: 1, name: 'susan', age: 25 },
]

function uniqueByKey(data, key) {
  return [
    ...new Map(
      data.map(x => [key(x), x])
    ).values()
  ]
}

console.log(uniqueByKey(users, item => item.id));

// [ { id: 1, name: 'susan', age: 25 },
//   { id: 2, name: 'cherry', age: 28 },
//   { id: 3, name: 'cindy', age: 27 } ]

Или реализовать его с помощью сокращения:

let users = [
  { id: 1, name: 'susan', age: 25 },
  { id: 2, name: 'cherry', age: 28 },
  { id: 3, name: 'cindy', age: 27 },
  { id: 2, name: 'cherry', age: 28 },
  { id: 1, name: 'susan', age: 25 },
]

function uniqueByKey(data, key) {
  const object = {};
  data = data.reduce((prev, next) => {
    // eslint-disable-next-line no-unused-expressions
    object[next[key]]
      ? ''
      : (object[next[key]] = true && prev.push(next));
    return prev;
  }, []);
  return data;
}

console.log(uniqueByKey(users, "id"));

// [ { id: 1, name: 'susan', age: 25 },
//   { id: 2, name: 'cherry', age: 28 },
//   { id: 3, name: 'cindy', age: 27 } ]

В-четвертых, реализовать пересечение, объединение и разность массивов

1. Выезжайте на перекресток

Array.prototype.includes

let a = [1, 2, 3];
let b = [2, 4, 5];

let intersection = a.filter(v => b.includes(v));

console.log(intersection); // [ 2 ]

Array.from

let a = [1, 2, 3];
let b = [2, 4, 5];
let aSet = new Set(a);
let bSet = new Set(b);
let intersection = Array.from(new Set(a.filter(v => bSet.has(v))));

console.log(intersection); // [ 2 ]

Array.prototype.indexOf

let a = [1, 2, 3];
let b = [2, 4, 5];
let intersection = a.filter((v) => b.indexOf(v) > -1);

console.log(intersection); // [ 2 ]

2. Взять союз

Array.prototype.includes

let a = [1, 2, 3];
let b = [2, 4, 5];

let union = a.concat(b.filter(v => !a.includes(v)));

console.log(union); // [ 1, 2, 3, 4, 5 ]

Array.from

let a = [1, 2, 3];
let b = [2, 4, 5];
let aSet = new Set(a);
let bSet = new Set(b);
let union = Array.from(new Set(a.concat(b)));

console.log(union); // [ 1, 2, 3, 4, 5 ]

Array.prototype.indexOf

let a = [1, 2, 3];
let b = [2, 4, 5];
let union = a.concat(b.filter((v) => a.indexOf(v) === -1));

console.log(union); // [ 1, 2, 3, 4, 5 ]

3. Возьмите набор разностей

Array.prototype.includes

let a = [1, 2, 3];
let b = [2, 4, 5];

let difference = a.concat(b).filter(v => !a.includes(v) || !b.includes(v));

console.log(difference); // [ 1, 3, 4, 5 ]

Array.from

let a = [1, 2, 3];
let b = [2, 4, 5];
let aSet = new Set(a);
let bSet = new Set(b);
let difference = Array.from(new Set(a.concat(b).filter(v => !aSet.has(v) || !bSet.has(v))));

console.log(difference); // [ 1, 3, 4, 5 ]

Array.prototype.indexOf

let a = [1, 2, 3];
let b = [2, 4, 5];
let difference = a.filter((v) => b.indexOf(v) === -1).concat(b.filter((v) => a.indexOf(v) === -1));

console.log(difference); // [ 1, 3, 4, 5 ]

5. Внедрите модель публикации-подписки

модель публикации-подпискиОн разделен на две части:on,emit.Между публикацией и подпиской нет никакой зависимости, издатель информирует третью сторону (канал событий) об изменении, а третья сторона информирует подписчика об изменении.

  • on: это поддерживать некоторые функции в массиве

  • emit: позволить методам в массиве выполняться последовательно

let fs = require("fs");

let event = {
  arr: [],
  on(fn) {
    this.arr.push(fn);
  },
  emit() {
    this.arr.forEach(fn => fn());
  }
}

event.on(function () {
  console.log("读取了一个");
})

event.on(function () {
  if (Object.keys(school).length === 2) {
    console.log("读取完毕");
  }
})

let school = {};
fs.readFile('./name.txt', 'utf8', function (err, data) {
  school.name = data;
  event.emit();
});

fs.readFile('./age.txt', 'utf8', function (err, data) {
  school.age = data;
  event.emit();
});

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

Шаблон наблюдателяОснован на режиме выпуска подписки, разделенный на观察者а также被观察者Две части: наблюдатель должен сначала собрать наблюдателя и уведомить наблюдателя, когда состояние наблюдателя изменится.Существует связь между наблюдателем и наблюдаемым.Когда наблюдаемые данные изменяются, наблюдатель напрямую уведомляется об изменении.

Например: "Теперь есть семья, папа, мама и малыш. Мама и папа говорят малышу, что вы должны уведомлять нас о любых изменениях статуса. Когда малыш проголодается, она уведомит родителей, чтобы они пришли и разобрались с этим". Маленький ребенок здесь наблюдаемыйSubject, родители наблюдателиObserver, ребенок поддерживает очередь наблюдателей и напрямую уведомляет наблюдателей в очереди при изменении любого состояния.

class Subject {  // 被观察者:小宝宝
  constructor(name)  {
    this.name = name;
    this.state = "开心的";
    this.observer = [];
  }

  attach(o) {
    this.observer.push(o);
  }

  setState(newState) {
    this.state = newState;
    this.observer.forEach(o => o.update(this));
  }

}

class Observer { // 观察者:爸爸 妈妈
  constructor(name) {
    this.name = name;
  }

  update(baby) {
    console.log("当前"+this.name+"被通知了,当前小宝宝的状态是:"+baby.state);
  }
}

// 爸爸妈妈需要观察小宝宝的心理变化
let baby = new Subject("小宝宝");
let father = new Observer("爸爸");
let mother = new Observer("妈妈");

baby.attach(father);
baby.attach(mother);
baby.setState("我饿了");

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

7. Реализуйте одноэлементный шаблон

Паттерн Singleton — это тип творческого паттерна проектирования. Убедитесь, что в глобальном масштабе существует только один экземпляр объекта, и предоставьте ему глобальную точку доступа, такую ​​как пул потоков, глобальный кеш, объект окна и т. д.

1. Обычная реализация:

// 单例构造函数
function CreateSingleton (name) {
    this.name = name;
    this.getName();
};

// 获取实例的名字
CreateSingleton.prototype.getName = function() {
    console.log(this.name)
};
// 单例对象
var Singleton = (function(){
    var instance;
    return function (name) {
        if(!instance) {
            instance = new CreateSingleton(name);
        }
        return instance;
    }
})();

// 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b');

console.log(a === b);

2. Реализация Intercept с замыканиями и свойствами Proxy

const singletonify = (className) => {
  return new Proxy(className.prototype.constructor, {
    instance: null,
    construct: (target, argumentsList) => {
      if (!this.instance)
        this.instance = new target(...argumentsList);
      return this.instance;
    }
  });
}

class MyClass {
  constructor(msg) {
    this.msg = msg;
  }

  printMsg() {
    console.log(this.msg);
  }
}

MySingletonClass = singletonify(MyClass);

const myObj = new MySingletonClass('first');
myObj.printMsg();           // 'first'
const myObj2 = new MySingletonClass('second');
myObj2.printMsg();     // 'first'

8. Выполнение обещаний

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

Интервьюер: «Можете ли вы написать Обещание от руки?»

9. Внедрите глубокое копирование

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

Глубокое копирование против поверхностного копирования классического интервью Javascript

Десять, рукописная строка в двоичном формате

function charToBinary(text) {
  let code = "";
  for (let i of text) {
    // 字符编码
    let number = i.charCodeAt().toString(2);
    // 1 bytes = 8bit,将 number 不足8位的0补上
    for (let a = 0; a <= 8 - number.length; a++) {
       number = 0 + number;
    }
    code += number;
  }
  return code;
}

Одиннадцать, рукописный двоичный файл в Base64

// 将二进制数据每 6bit 位替换成一个 base64 字符
function binaryTobase64(code) {
  let base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  let res = '';
  // 1 bytes = 8bit,6bit 位替换成一个 base64 字符
  // 所以每 3 bytes 的数据,能成功替换成 4 个 base64 字符
    
  // 对不足 24 bit (也就是 3 bytes) 的情况进行特殊处理
  if (code.length % 24 === 8) {
    code += '0000';
    res += '=='
  }
  if (code.length % 24 === 16) {
    code += '00';
    res += '='
  }

  let encode = '';
  // code 按 6bit 一组,转换为
  for (let i = 0; i < code.length; i += 6) {
    let item = code.slice(i, i + 6);
    encode += base64Code[parseInt(item, 2)];
  }
  return encode + res;
}

12. Преобразование рукописных символов в Base64

function base64encode(text) {
  let base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  let res = '';
  let i = 0;
  while (i < text.length) {
    let char1, char2, char3, enc1, enc2, enc3, enc4;
    
    // 三个字符一组,转二进制
    char1 = text.charCodeAt(i++); 
    char2 = text.charCodeAt(i++);
    char3 = text.charCodeAt(i++);

    enc1 = char1 >> 2; // 取第 1 字节的前 6 位
    
    // 三个一组处理
    if (isNaN(char2)) {
      // 只有 1 字节的时候
      enc2 = ((char1 & 3) << 4) | (0 >> 4);
      // 第65个字符用来代替补位的 = 号
      enc3 = enc4 = 64;
    } else if (isNaN(char3)) {
      // 只有 2 字节的时候
      enc2 = ((char1 & 3) << 4) | (char2 >> 4);
      enc3 = ((char2 & 15) << 2) | (0 >> 6);
      enc4 = 64;
    } else {
      enc2 = ((char1 & 3) << 4) | (char2 >> 4); // 取第 1 个字节的后 2 位(3 = 11 << 4 = 110000) + 第 2 个字节的前 4 位
      enc3 = ((char2 & 15) << 2) | (char3 >> 6); // 取第 2 个字节的后 4 位 (15 = 1111 << 2 = 111100) + 第 3 个字节的前 2 位
      enc4 = char3 & 63; // 取最后一个字节的最后 6 位 (63 = 111111)
    }
    
    // 转base64
    res += base64Code.charAt(enc1) + base64Code.charAt(enc2) + base64Code.charAt(enc3) + base64Code.charAt(enc4)
  }

  return res;
}

Оптимальным решением:

let encodedData = window.btoa("this is a example");
console.log(encodedData); // dGhpcyBpcyBhIGV4YW1wbGU=


let decodeData = window.atob(encodedData);
console.log(decodeData); // this is a example

13. Реализуйте DIV, который можно перетаскивать

Идеи:

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

  • Привяжите событие mousedown к слою DIV, установите для флага начала перетаскивания значение true, а для флага окончания перетаскивания — значение false, в данном случае isMouseDown.

  • Оптимизация деталей при перетаскивании, например:

    • Относительное положение мыши к DIV

    • Выбран запрет на перетаскивание текста

    • Ограничьте диапазон перемещения DIV и перетащите его на границу

    • Потеря фокуса при выходе мыши из окна

    • Обработка, когда мышь перемещается по iframe

let injectedHTML = document.createElement("DIV");
    injectedHTML.innerHTML = '<dragBox id="dragBox" class="drag-box">\
  <dragBoxBar id="dragBoxBar" class="no-select"></dragBoxBar>\
  <injectedBox id="injectedBox">CONTENT</injectedBox>\
  </dragBox>';

document.body.appendChild(injectedHTML);

let isMouseDown,
  initX,
  initY,
  height = injectedBox.offsetHeight,
  width = injectedBox.offsetWidth,
  dragBoxBar = document.getElementById('dragBoxBar');


dragBoxBar.addEventListener('mousedown', function(e) {
  isMouseDown = true;
  document.body.classList.add('no-select');
  injectedBox.classList.add('pointer-events');
  initX = e.offsetX;
  initY = e.offsetY;
  dragBox.style.opacity = 0.5;
})

dragBoxBar.addEventListener('mouseup', function(e) {
  mouseupHandler();
})

document.addEventListener('mousemove', function(e) {
  if (isMouseDown) {
    let cx = e.clientX - initX,
        cy = e.clientY - initY;
    if (cx < 0) {
        cx = 0;
    }
    if (cy < 0) {
        cy = 0;
    }
    if (window.innerWidth - e.clientX + initX < width + 16) {
        cx = window.innerWidth - width;
    }
    if (e.clientY > window.innerHeight - height - dragBoxBar.offsetHeight + initY) {
        cy = window.innerHeight - dragBoxBar.offsetHeight - height;
    }
    dragBox.style.left = cx + 'px';
    dragBox.style.top = cy + 'px';
  }
})

document.addEventListener('mouseup', function(e) {
  if (e.clientY > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientX > window.innerHeight) {
    mouseupHandler();
  }
});

function mouseupHandler() {
  isMouseDown = false;
  document.body.classList.remove('no-select');
  injectedBox.classList.remove('pointer-events');
  dragBox.style.opacity = 1;
}
* {
  margin: 0;
  padding: 0;
  border: none
}

body,
html {
  height: 100%;
  width: 100%;
}

.drag-box {
  user-select: none;
  background: #f0f0f0;
  z-index: 2147483647;
  position: fixed;
  left: 0;
  top: 0;
  width: 200px;
}


#dragBoxBar {
  align-items: center;
  display: flex;
  justify-content: space-between;
  background: #ccc;
  width: 100%;
  height: 40px;
  cursor: move;
  user-select: none;
}

.no-select {
  user-select: none;
}

.pointer-events {
  pointer-events: none;
}


.no-border {
  border: none;
}


#injectedBox {
  height: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  background: #eee;
}

14. Реализуйте функцию пакетного запроса multiRequest(urls, maxNum)

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log(url, "加载完成");
      resolve(img);
    };
    img.onerror = function() {
      reject(new Error('Error at:' + url));
    };
    img.src = url;
  })
}

function multiRequest(urls, maxNum) {
  const firstMaxNum = urls.splice(0, maxNum);
  let promises = firstMaxNum.map((url, index)=>{
    return loadImg(url).then(()=>{
      return index
    })
  })
  return urls.reduce((res, cur)=>{
    return res.then(()=>{
      return Promise.race(promises)
    }).then((idx)=>{
      promises[idx] = loadImg(cur).then(()=>{
        return idx
      })
    })
  }, Promise.resolve()).then(()=>{
    return Promise.all(promises)
  })  
}

multiRequest(urls, 4).then(()=>{
  console.log('finish')
})

15. Реализуйте функцию сна

Идеи: Например, sleep(1000) означает ожидание в течение 1000 миллисекунд, что может быть реализовано с точки зрения Promise, Generator, Async/Await и т. д.

//Promise
const sleep = time => {
  return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
  console.log(1)
})

//Generator
function* sleepGenerator(time) {
  yield new Promise(function(resolve,reject){
    setTimeout(resolve,time);
  })
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})

//async
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}
output();

//ES5
function sleep(callback,time) {
  if(typeof callback === 'function')
    setTimeout(callback,time)
}

function output(){
  console.log(1);
}
sleep(output,1000);

Шестнадцать, смоделируйте локальное хранилище

'use strict'
const valuesMap = new Map()

class LocalStorage {
  getItem (key) {
    const stringKey = String(key)
    if (valuesMap.has(key)) {
      return String(valuesMap.get(stringKey))
    }
    return null
  }

  setItem (key, val) {
    valuesMap.set(String(key), String(val))
  }

  removeItem (key) {
    valuesMap.delete(key)
  }

  clear () {
    valuesMap.clear()
  }

  key (i) {
    if (arguments.length === 0) {
      throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present.") // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
    }
    let arr = Array.from(valuesMap.keys())
    return arr[i]
  }

  get length () {
    return valuesMap.size
  }
}
const instance = new LocalStorage()

global.localStorage = new Proxy(instance, {
  set: function (obj, prop, value) {
    if (LocalStorage.prototype.hasOwnProperty(prop)) {
      instance[prop] = value
    } else {
      instance.setItem(prop, value)
    }
    return true
  },
  get: function (target, name) {
    if (LocalStorage.prototype.hasOwnProperty(name)) {
      return instance[name]
    }
    if (valuesMap.has(name)) {
      return instance.getItem(name)
    }
  }
})

Ссылаться на

20 000 слов | Основные приложения для интерфейса пользователя 90 вопросов

Резюме вопросов для расширенного фронтенд-интервью

7 ways to remove duplicates from an array in JavaScript

How can I implement a singleton in JavaScript

о

Автор Ци Сяошэнь, фронтенд программы Юань.

Немного литературно, как фотография. Хотя сейчас она работает с 9 до 5 и усердно учится, ее мечта — стать героиней, помогать бедным и помогать бедным и путешествовать по миру с мечами. Я надеюсь, что однажды я смогу исправить ошибку и реализовать свою мечту.

Нет публики:Большое переднее пространство, обновляйте время от времени, добро пожаловать в игру~