Рукописный код для фронтенд-интервью

JavaScript

Эта серия будет посвящена интервью точки зренияJavaScript,Node.js(npmпакет) и фреймворк для обобщения общих реализаций моделирования. Конкретный исходный код находится вгитхаб проектвкл., долгосрочные обновления и техническое обслуживание

Дедупликация массива

Самый примитивный метод дедупликации (одномерного) массива состоит в использовании двухуровневого цикла, который зацикливает исходный массив и новый массив соответственно; или мы можем использоватьindexOfчтобы упростить внутренний цикл; или вы можете отсортировать исходный массив, а затем дедуплицировать его, что сократит один цикл, просто сравните два числа до и после; конечно, мы можем использоватьES5,ES6метод для упрощения написания метода дедупликации, например, мы можем использоватьfilterчтобы упростить внутренний цикл или использоватьSet,Map, оператор расширения Это более простые в использовании методы, но их эффективность не должна быть выше, чем у оригинального метода. Дедупликация двумерного массива может быть основана на вышеописанном методе, чтобы определить, является ли элемент массивом, и если да, то выполняется рекурсивная обработка.

двойной цикл

var array = [1, 1, '1', '1'];

function unique(array) {
    var res = [];
    for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
        for (var j = 0, resLen = res.length; j < resLen; j++ ) {
            if (array[i] === res[j]) {
                break;
            }
        }
        if (j === resLen) {
            res.push(array[i])
        }
    }
    return res;
}

console.log(unique(array)); // [1, "1"]

Использовать indexOf

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
        var current = array[i];
        if (res.indexOf(current) === -1) {
            res.push(current)
        }
    }
    return res;
}

console.log(unique(array));

Дедупликация после сортировки

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
        // 如果是第一个元素或者相邻的元素不相同
        if (!i || seen !== sortedArray[i]) {
            res.push(sortedArray[i])
        }
        seen = sortedArray[i];
    }
    return res;
}

console.log(unique(array));

filter

filterМожет использоваться для упрощения внешнего цикла

Используйте индексОф:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    var res = array.filter(function(item, index, array){
        return array.indexOf(item) === index;
    })
    return res;
}

console.log(unique(array));

Сортировка для удаления дубликатов:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    return array.concat().sort().filter(function(item, index, array){
        return !index || item !== array[index - 1]
    })
}

console.log(unique(array));

подход ES6

Задавать:

var array = [1, 2, 1, 1, '1'];

function unique(array) {
   return Array.from(new Set(array));
}

console.log(unique(array)); // [1, 2, "1"]

снова упростить

function unique(array) {
    return [...new Set(array)];
}

//或者
var unique = (a) => [...new Set(a)]

Карта:

function unique (arr) {
    const seen = new Map()
    return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}

Типовое суждение

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

  • typeofДля шести основных типов данныхUndefined,Null,Boolean,Number,String,Object(верхний регистр) возвращает результат

    undefined,object,boolean,number,string,object(нижний регистр), вы можете видетьNullа такжеObjectтипы возвращаютсяobjectнить;typeofНо он может определить тип функции;Подводить итоги,typeofМожет обнаружить шесть типов, но не может обнаружитьnullтип иObjectТипы подразделений, такие какArray,Function,Date,RegExp,ErrorЖдать.

  • Object.prototype.toStringочень мощный, он может определять основные типы данных иObjectтип подразделения под, даже как

Math,JSON,argumentsОн может определить их конкретный тип, он возвращает форму результата, например[object Number] (обратите внимание, что последний тип данных в верхнем регистре).так,Object.prototype.toStringВ принципе, все типы могут быть обнаружены, но иногда нужно учитывать совместимость низкоуровневых браузеров.

Общий API

    
// 该类型判断函数可以判断六种基本数据类型以及Boolean Number String Function Array Date RegExp Object Error,
// 其他类型因为遇到类型判断的情况较少所以都会返回object,不在进行详细的判断
//  比如ES6新增的Symbol,Map,Set等类型
var classtype = {};


"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item) {
    classtype["[object " + item + "]"] = item.toLowerCase();
})


function type(obj) {
    // 解决IE6中null和undefined会被Object.prototype.toString识别成[object Object]
    if (obj == null) {
        return obj + "";
    }

    //如果是typeof后类型为object下的细分类型(Array,Function,Date,RegExp,Error)或者是Object类型,则要利用Object.prototype.toString
    //由于ES6新增的Symbol,Map,Set等类型不在classtype列表中,所以使用type函数,返回的结果会是object
    return typeof obj === "object" || typeof obj === "function" ?
        classtype[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

Оценка пустых объектов

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

function isEmptyObject( obj ) {
        var name;
        for ( name in obj ) {
            return false;
        }
        return true;
}

console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true

мы можем видетьisEmptyObjectНа самом деле судят не только пустые объекты. Но с тех порjQueryНаписано так, наверное, потому, что учитывая реальное развитиеisEmptyObject Достаточно судить {} и {a: 1}. Если действительно только судить {}, то можно совместить с тем, что было написано в предыдущей статье.type Функция отфильтровывает неподходящие случаи.

Определить объект окна

Windowобъект имеетwindowАтрибут указывает сам на себя, и по этому признаку можно определить, является ли онWindowобъект

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}

Массив суждений

isArrayЭто встроенная функция оценки типа данных типа массива, но будут проблемы с совместимостью.polyfillследующим образом

isArray = Array.isArray || function(array){
  return Object.prototype.toString.call(array) === '[object Array]';
}

Массив классов суждения

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

  1. это массив

  2. длина 0 Например, в следующем случае, если мы удалим оценку длины === 0, будет напечатаноfalse, но мы все знаемargumentsэто массивоподобный объект, вот что должно быть возвращеноtrueиз

    function a(){
        console.log(isArrayLike(arguments))
    }
    a();
    
  3. lengthsсвойство является числовым типом больше 0, иobj[length - 1]должен существовать (учитывая случай arr = [3])

function isArrayLike(obj) {

    // obj 必须有 length属性
    var length = !!obj && "length" in obj && obj.length;
    var typeRes = type(obj);

    // 排除掉函数和 Window 对象
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }

    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}

Судейство NaN

определить, является ли числоNaNВы не можете просто использовать === для оценки, потому чтоNaNне равно никакому числу, в том числе и самому себе, обратите внимание, что вES6изisNaNИспользуется только значение в типе значенияNaNвернусьtrue

isNaN: function(value){
  return isNumber(value) && isNaN(value);
}

Определение элементов DOM

использоватьDOMконкретный объектnodeTypeАтрибуты(

isElement: function(obj){
  return !!(obj && obj.nodeType === 1);
    // 两次感叹号将值转化为布尔值
}

Объект оценки аргументов

в браузерах младших версийargumentобъект черезObject.prototype.toStringВозвращенный после суждения[object Object], поэтому он должен быть совместим

isArguments: function(obj){
  return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}

глубокая копия

Если это массив, реализуется неглубокая копия, что лучше, чемslice,concatВозвращает свойства нового массива для реализации; реализация глубокой копии может использовать преимущества用JSON.parseа такжеJSON.stringifyЧтобы добиться этого, но есть проблема, функция не может быть скопирована (массив, возвращаемый после копирования,null). Все вышеперечисленные методы — это навыки.Давайте рассмотрим, как реализовать глубокую и поверхностную копию объекта или массива. Поверхностная копия копирует только один слой, чего можно добиться с помощью Object.assign().Обратите внимание, что когда целевой объект имеет только один слой, это глубокая копия, методы массива concat(), slice() и т. д. Глубокая копия — это полная копия, многослойная копия, методы реализации включают библиотеку loadash, метод расширения jQuery, JSON.parse (JSON.stringify()) или рукописный рекурсивный метод.

мелкая копия

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

var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

глубокая копия

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

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

Плоский

рекурсия

Прокрутите элементы массива, и если это все еще массив, вызовите этот метод рекурсивно

// 方法 1
var arr = [1, [2, [3, 4]]];

function flatten(arr) {
    var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(flatten(arr[i]))
        }
        else {
            result.push(arr[i])
        }
    }
    return result;
}


console.log(flatten(arr))

toString()

Если элементы массива все числа, вы можете использовать этот метод

// 方法2
var arr = [1, [2, [3, 4]]];

function flatten(arr) {
    return arr.toString().split(',').map(function(item){
        return +item // +会使字符串发生类型转换
    })
}

console.log(flatten(arr))

reduce()

// 方法3
var arr = [1, [2, [3, 4]]];

function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}

console.log(flatten(arr))

...

// 扁平化一维数组
var arr = [1, [2, [3, 4]]];
console.log([].concat(...arr)); // [1, 2, [3, 4]]

// 可以扁平化多维数组
var arr = [1, [2, [3, 4]]];

function flatten(arr) {

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

    return arr;
}

console.log(flatten(arr))

карри

Универсальная версия

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}

function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);

версия ES6

const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])

let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10

Защита от тряски и дросселирования

Стабилизатор

function debounce(fn, wait) {
    var timeout = null;
    return function() {
        if(timeout !== null) 
        {
                clearTimeout(timeout);
        }
        timeout = setTimeout(fn, wait);
    }
}
// 处理函数
function handle() {
    console.log(Math.random()); 
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));

дросселирование

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

   var throttle = function(func, delay) {
            var prev = 0;
            return function() {
                var context = this;
                var args = arguments;
                var now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now();
                }
            }
        }
        function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));

Реализовано с таймером

   var throttle = function(func, delay) {
            var timer = null;
            return function() {
                var context = this;
                var args = arguments;
                if (!timer) {
                    timer = setTimeout(function() {
                        func.apply(context, args);
                        timer = null;
                    }, delay);
                }
            }
        }
        function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));

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

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

var throttle = function(func, delay) {
     var timer = null;
     var startTime = 0;
     return function() {
             var curTime = Date.now();
             var remaining = delay - (curTime - startTime);
             var context = this;
             var args = arguments;
             clearTimeout(timer);
              if (remaining <= 0) {
                    func.apply(context, args);
                    startTime = Date.now();
              } else {
                    timer = setTimeout(func, remaining);
              }
      }
}
function handle() {
      console.log(Math.random());
}
 window.addEventListener('scroll', throttle(handle, 1000));

имитировать новый

  • newПолученный экземпляр может быть доступенConstructorсвойства в , вы также можете получить доступ кConstructor.prototypeсвойства в , первое может бытьapply, последнее может быть достигнуто путем добавления экземпляраprotoсвойство указывает на конструкторprototypeреализовать
  • Нам также нужно определить, является ли возвращаемое значение объектом. Если это объект, мы вернем объект. Если нет, мы вернем то, что должны вернуть.
function New(){
    var obj=new Object();
    //取出第一个参数,就是我们要传入的构造函数;此外因为shift会修改原数组,所以arguments会被去除第一个参数
    Constructor=[].shift.call(arguments);
    //将obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性
    obj._proto_=Constructor.prototype;
    //使用apply改变构造函数this的指向到新建的对象,这样obj就可以访问到构造函数中的属性
    var ret=Constructor.apply(obj,arguments);
    //要返回obj
    return typeof ret === 'object' ? ret:obj;
}
function Otaku(name,age){
	this.name=name;
	this.age=age;
	this.habit='Games'
}

Otaku.prototype.sayYourName=function(){
    console.log("I am" + this.name);
}

var person=objectFactory(Otaku,'Kevin','18')

console.log(person.name)//Kevin
console.log(person.habit)//Games
console.log(person.strength)//60

имитировать вызов

  • call()Метод вызывает функцию или метод с указанным значением this и рядом указанных значений параметров.
  • Шаги симуляции: установить функцию как свойство объекта -> выполнить функцию -> удалить функцию
  • thisМожно передавать параметрыnull, когдаnullКогда это считаетсяwindow
  • Функции могут иметь возвращаемые значения

Простая версия

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
}
foo.bar() // 1

Идеальная версия

Function.prototype.call2 = function(context) {
    var context=context||window
    context.fn = this;
    let args = [...arguments].slice(1);
    let result = context.fn(...args);
    delete context.fn;
    return result;
}
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
//表示bar函数的执行环境是foo,即bar函数里面的this代表foo,this.value相当于foo.value,然后给bar函数传递两个参数
bar.call2(foo, 'black', '18') // black 18 1

имитировать применять

  • apply()реализация иcall()Похожие, но разные параметры формы
Function.prototype.apply2 = function(context = window) {
    context.fn = this
    let result;
    // 判断是否有第二个参数
    if(arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

имитировать привязку

Function.prototype.bind2=function(context){
    var self=this
    var args=Array.prototype.slice.call(arguments,1);
    
    var fNOP=function(){};
    var fBound=function(){
        var bindArgs=Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindAt))
    }
}

имитировать instanceof

function instanceOf(left,right) {

    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto === null) return false
        if(proto === prototype) return true
        proto = proto.__proto__;
    }
}

Макет JSON.stringify

JSON.stringify(value[, replacer [, space]])

  • Boolean | Number| StringТипы автоматически преобразуются в соответствующие примитивные значения.

  • undefined, любую функцию иsymbol, игнорируется (при наличии в значении свойства объекта, не являющегося массивом), или преобразуется вnull(при наличии в массиве).

  • Неперечислимые свойства игнорируются

  • Если значение свойства объекта косвенным образом указывает на сам объект (циклическая ссылка), свойство также игнорируется.

function jsonStringify(obj) {
    let type = typeof obj;
    if (type !== "object") {
        if (/string|undefined|function/.test(type)) {
            obj = '"' + obj + '"';
        }
        return String(obj);
    } else {
        let json = []
        let arr = Array.isArray(obj)
        for (let k in obj) {
            let v = obj[k];
            let type = typeof v;
            if (/string|undefined|function/.test(type)) {
                v = '"' + v + '"';
            } else if (type === "object") {
                v = jsonStringify(v);
            }
            json.push((arr ? "" : '"' + k + '":') + String(v));
        }
        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
    }
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"

Макет JSON.parse

JSON.parse(text[, reviver])

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

использовать оценку

function jsonParse(opt) {
    return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}

Избегайте использования в ненужных ситуацияхeval, eval() — опасная функция, код, который он выполняет, имеет права исполнителя. Если строковый код, который вы запускаете с помощью eval(), манипулируется и модифицируется злоумышленниками (людьми с плохими намерениями), вы можете в конечном итоге запустить вредоносный код на компьютере пользователя с разрешениями вашей веб-страницы/расширения.

Использовать новую функцию()

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

var func = new Function(arg1, arg2, ..., functionBody)

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();

создать объект

Самый простой способ создать пользовательский объект — это создатьObject, а затем добавляя к нему свойства и методы, ранние разработчики часто использовали этот шаблон для создания объектов, а более поздние литеральные методы объекта стали предпочтительным шаблоном для создания объектов. несмотря на то чтоobject构造函数или对象字面量Все методы можно использовать для создания объектов, но эти методы используют один и тот же интерфейс для создания множества объектов, что приводит к большому количеству повторяющегося кода. Для решения этой проблемы люди стали использовать различные шаблоны для создания объектов, среди которых обычно рекомендуются четыре метода, в том числе构造函数模式,原型模式,构造函数和原型组合模式,动态原型模式, другие способы, в том числе工厂模式,寄生构造函数模式,稳妥构造函数模式Обычно используется меньше. Среди этих методов следует выделить наиболее используемые и рекомендуемые.Композиция и динамический прототип

Шаблоны конструктора и прототипа композиции

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

  1. Устранены недостатки режима прототипа для эталонных объектов.
  2. Устранен недостаток, заключающийся в том, что в режиме прототипа нет возможности передавать параметры.
  3. Устраняет недостаток шаблона конструктора, который не может совместно использовать методы.
function Person(name) {
  this.name = name
  this.friends = ['lilei']
}
Person.prototype.say = function() {
  console.log(this.name)
}

var person1 = new Person('hanmeimei')
person1.say() //hanmeimei

Шаблон динамического прототипа

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

  1. Объект-прототип может быть изменен при первом вызове конструктора.
  2. Изменения могут быть отражены во всех экземплярах
function Person(name) {
  this.name = name
    // 检测say 是不是一个函数
    // 实际上只在当前第一次时候没有创建的时候在原型上添加sayName方法
    //因为构造函数执行时,里面的代码都会执行一遍,而原型有一个就行,不用每次都重复,所以仅在第一执行时生成一个原型,后面执行就不必在生成,所以就不会执行if包裹的函数,
//其次为什么不能再使用字面量的写法,我们都知道,使用构造函数其实是把new出来的对象作用域绑定在构造函数上,而字面量的写法,会重新生成一个新对象,就切断了两者的联系!
  if(typeof this.say != 'function') {
    Person.prototype.say = function(
    alert(this.name)
  }
}

наследовать

原型链继承Мало того, что это принесет ссылочные дефекты, мы не сможем инициализировать унаследованные свойства для разных экземпляров;构造函数继承Этот метод позволяет избежать дефекта наследования классов, но мы не можем получить общий метод родительского класса, то есть метод, связанный с прототипом;组合继承Решает проблемы двух вышеуказанных методов, но дважды вызывает конструктор родительского класса;寄生组合式继承Усиливающая часть заключается в сокращении избыточного вызова конструктора родительского класса на основе комбинированного наследования.Рекомендуется использовать наследование композиции, паразитную композицию и наследование расширений ES6.Рекомендуется использовать наследование ES6 непосредственно в реальном производстве.

наследование композиции

// 声明父类   
function Animal(color) {    
  this.name = 'animal';    
  this.type = ['pig','cat'];    
  this.color = color;   
}     

// 添加共有方法  
Animal.prototype.greet = function(sound) {    
  console.log(sound);   
}     

// 声明子类   
function Dog(color) { 
  // 构造函数继承    
  Animal.apply(this, arguments);   
}   

// 类式继承
Dog.prototype = new Animal();   

var dog = new Dog('白色');   
var dog2 = new Dog('黑色');     

dog.type.push('dog');   
console.log(dog.color); // "白色"
console.log(dog.type);  // ["pig", "cat", "dog"]

console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color);  // "黑色"
dog.greet('汪汪');  // "汪汪"

Примечание: комбинированное наследование с использованием вышеуказанного метода приведет к тому, что конструктор родительского класса будет вызываться дважды.На самом деле, мы можем оптимизировать комбинированное наследование через Dog.prototype = Animal.prototype; Dog.prototype.constructor = Dog.Конечно, окончательный метод оптимизации представляет собой следующую паразитную комбинацию. Если вы хотите понять конкретную оптимизацию комбинированного наследования, вы можете обратиться кГлубокое понимание цепочки прототипов JavaScript и наследования

Наследование паразитарного состава

function Animal(color) {
  this.color = color;
  this.name = 'animal';
  this.type = ['pig', 'cat'];
}

Animal.prototype.greet = function(sound) {
  console.log(sound);
}


function Dog(color) {
  Animal.apply(this, arguments);
  this.name = 'dog';
}
/* 注意下面两行 */
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.getName = function() {
  console.log(this.name);
}


var dog = new Dog('白色');   
var dog2 = new Dog('黑色');     

dog.type.push('dog');   
console.log(dog.color);   // "白色"
console.log(dog.type);   // ["pig", "cat", "dog"]

console.log(dog2.type);  // ["pig", "cat"]
console.log(dog2.color);  // "黑色"
dog.greet('汪汪');  //  "汪汪"

Неглубокая копия Object.create() работает как следующая функция:

function create(obj) {
  function F() {};
  F.prototype = obj;
  return new F();
}

Следует отметить, что поскольку прототип Animal копируется и присваивается Dog.prototype,constructorСвойства также были переопределены, поэтому мы исправим это:

Dog.prototype.constructor = Dog;

расширяет наследование

class Animal {   
  constructor(color) {   
    this.color = color;   
  }   
  greet(sound) {   
    console.log(sound);   
  }  
}   

class Dog extends Animal {   
  constructor(color) {   
    super(color);   
    this.color = color;   
  }  
}   

let dog = new Dog('黑色');  
dog.greet('汪汪');  // "汪汪"
console.log(dog.color); // "黑色"

реализовать карту

Основные моменты: 1. Каковы параметры callback-функции и как работать с возвращаемым значением. 2. Не изменяйте исходный массив.

Array.prototype.MyMap = function(fn, context){
  var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}

внедрить уменьшить

Основные моменты:

1. Как быть с не переданным начальным значением

2. Каковы параметры callback-функции и как работать с возвращаемым значением.

Array.prototype.myReduce = function(fn, initialValue) {
  var arr = Array.prototype.slice.call(this);
  var res, startIndex;
  res = initialValue ? initialValue : arr[0];
  startIndex = initialValue ? 0 : 1;
  for(var i = startIndex; i < arr.length; i++) {
    res = fn.call(null, res, arr[i], i, this);
  }
  return res;
}

Реализовать Object.create

function create(proto) {
    function F() {};
    F.prototype = proto;
    F.prototype.constructor = F;
    
    return new F();
}

имитация аякса

  • ajaxПроцесс запроса: создатьXMLHttpRequestОбъект, подключение к серверу, отправка запроса, получение данных ответа
  • после созданияXMLHttpRequestЭкземпляры объектов имеют множество методов и свойств.
    • openМетод аналогичен инициализации и не инициирует реальный запрос;sendсторона отправляет запрос и принимает необязательный параметр
    • Когда метод запросаpost, параметры тела запроса могут быть переданы; когда метод запросаgetможно опустить или передатьnull;
    • не важно какgetа такжеpost, параметры должны пройтиencodeURIComponentсплайсинг после кодирования

Универсальная версия

//对请求data进行格式化处理
function formateData(data) {
    let arr = [];
    for (let key in data) {
        //避免有&,=,?字符,对这些字符进行序列化
        arr.push(encodeURIComponent(key) + '=' + data[key])
    }
    return arr.join('&');
}

function ajax(params) {
    //先对params进行处理,防止为空
    params = params || {};
    params.data = params.data || {};

    //普通GET,POST请求
    params.type = (params.type || 'GET').toUpperCase();
    params.data = formateData(params.data);
    //如果是在ie6浏览器,那么XMLHttoRequest是不存在的,应该调用ActiveXObject;
    let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
    if (params.type === 'GET') {
        xhr.open(params.type, params.url + '?' + params.data, true);
        xhr.send();
    } else {
        xhr.open(params.type, params.url, true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
        xhr.send(params.data);
    }
    // 这里有两种写法,第一种写法:当xhr.readyState===4的时候,会触发onload事件,直接通过onload事件 进行回调函数处理
    xhr.onload = function () {
        if (xhr.status === 200 || xhr.status === 304 || xhr.status === 206) {
            var res;

            if (params.success && params.success instanceof Function) {
                res = JSON.parse(xhr.responseText);
                params.success.call(xhr, res);
            }
        } else {
            if (params.error && params.error instanceof Function) {
                res = xhr.responseText;
                params.error.call(xhr, res);
            }
        }

    }
    //第二种写法,当xhr.readyState===4时候,说明请求成功返回了,进行成功回调
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            // 进行onload里面的处理函数
        }
    }

}

обещанная версия

// 使用promise实现一个简单的ajax

/**
 * 首先,可能会使用到的xhr方法或者说属性
 * onloadstart // 开始发送时触发
 * onloadend   // 发送结束时触发,无论成功不成功
 * onload      // 得到响应
 * onprogress  // 从服务器上下载数据,每50ms触发一次
 * onuploadprogress // 上传到服务器的回调
 * onerror     // 请求错误时触发
 * onabort     // 调用abort时候触发
 * status      // 返回状态码
 * setRequestHeader // 设置请求头
 * responseType // 请求传入的数据
 */

// 默认的ajax参数
let ajaxDefaultOptions = {
  url: '#', // 请求地址,默认为空
  method: 'GET', // 请求方式,默认为GET请求
  async: true, // 请求同步还是异步,默认异步
  timeout: 0, // 请求的超时时间
  dataType: 'text', // 请求的数据格式,默认为text
  data: null, // 请求的参数,默认为空
  headers: {}, // 请求头,默认为空
  onprogress: function () {}, // 从服务器下载数据的回调
  onuploadprogress: function () {}, // 处理上传文件到服务器的回调
  xhr: null // 允许函数外部创建xhr传入,但是必须不能是使用过的
};

function _ajax(paramOptions) {
  let options = {};
  for (const key in ajaxDefaultOptions) {
    options[key] = ajaxDefaultOptions[key];
  }
  // 如果传入的是否异步与默认值相同,就使用默认值,否则使用传入的参数
  options.async = paramOptions.async === ajaxDefaultOptions.async ? ajaxDefaultOptions.async : paramOptions.async;
  // 判断传入的method是否为GET或者POST,否则传入GET 或者可将判断写在promise内部,reject出去
  options.method = paramOptions.method ? ("GET" || "POST") : "GET";
  // 如果外部传入xhr,否则创建一个
  let xhr = options.xhr || new XMLHttpRequest();
  // return promise对象
  return new Promise(function (resolve, reject) {
    xhr.open(options.method, options.url, options.async);
    xhr.timeout = options.timeout;
    // 设置请求头
    for (const key in options.headers) {
      xhr.setRequestHeader(key, options.headers[key]);
    }
    // 注册xhr对象事件
    xhr.responseType = options.dataType;
    xhr.onprogress = options.onprogress;
    xhr.onuploadprogress = options.onuploadprogress;
    // 开始注册事件
    // 请求成功
    xhr.onloadend = function () {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        resolve(xhr);
      } else {
        reject({
          errorType: "status_error",
          xhr: xhr
        });
      }
    };
    // 请求超时
    xhr.ontimeout = function () {
      reject({
        errorType: "timeout_error",
        xhr: xhr
      });
    }
    // 请求错误
    xhr.onerror = function () {
      reject({
        errorType: "onerror",
        xhr: xhr
      });
    }
    // abort错误(未明白,只知道是三种异常中的一种)
    xhr.onabort = function () {
      reject({
        errorType: "onabort",
        xhr: xhr
      });
    }
    // 捕获异常
    try {
      xhr.send(options.data);
    } catch (error) {
      reject({
        errorType: "send_error",
        error: error
      });
    }
  });
}


// 调用示例
_ajax({
  url: 'http://localhost:3000/suc',
  async: true,
  onprogress: function (evt) {
    console.log(evt.position / evt.total);
  },
  dataType: 'text/json'
}).then(
  function (xhr) {
    console.log(xhr.response);
  },
  function (e) {
    console.log(JSON.stringify(e))
  });

макет jsonp

// foo 函数将会被调用 传入后台返回的数据
function foo(data) {
    console.log('通过jsonp获取后台数据:', data);
    document.getElementById('data').innerHTML = data;
}
/**
 * 通过手动创建一个 script 标签发送一个 get 请求
 * 并利用浏览器对 <script> 不进行跨域限制的特性绕过跨域问题
 */
(function jsonp() {
    let head = document.getElementsByTagName('head')[0]; // 获取head元素 把js放里面
    let js = document.createElement('script');
    js.src = 'http://domain:port/testJSONP?a=1&b=2&callback=foo'; // 设置请求地址
    head.appendChild(js); // 这一步会发送请求
})();

// 后台代码
// 因为是通过 script 标签调用的 后台返回的相当于一个 js 文件
// 根据前端传入的 callback 的函数名直接调用该函数
// 返回的是 'foo(3)'
function testJSONP(callback, a, b) {
  return `${callback}(${a + b})`;
}

Моделирование модели публикации-подписки

class Pubsub {
    constructor() {
        this.handles = {}
    }
    subscribe(type, handle) {
        if (!this.handles[type]) {
            this.handles[type] = []
        }
        this.handles[type].push(handle)
    }
    unsubscribe(type, handle) {
        let pos = this.handles[type].indexOf(handle)
        if (!handle) {
            this.handles.length = 0
        } else {
            ~pos && this.handles[type].splice(pos, 1)
        }
    }
    publish() {
        let type = Array.prototype.shift.call(arguments)
        this.handles[type].forEach(handle => {
            handle.apply(this, arguments)
        })
    }
}

const pub = new Pubsub()
pub.subscribe('a', function() {console.log('a', ...arguments)})
pub.publish('a', 1, 2, 3)
// a 1 2 3

Имитация setInterval с помощью setTimeout

существуетsetTimeoutМетод вызывается сноваsetTimeout, вы можете достичь цели прерывистого вызова. Так почему же рекомендуется использоватьsetTimeoutзаменятьsetIntervalШерстяная ткань?setTimeoutстиль прерывистых вызовов и традиционныйsetIntervalВ чем разница между прерывистыми звонками?

Разница в том, чтоsetIntervalПрерывистый вызов начинает отсчет времени до выполнения предыдущего метода. Например, если прерывистое время составляет 500 мс, то последний метод будет помещен в последовательность выполнения независимо от того, выполнялся ли в это время предыдущий метод. В это время будет возникать проблема.Если время выполнения предыдущего метода превышает 500 мс, а добавление составляет 1000 мс, это означает, что после завершения выполнения предыдущего метода последний метод будет выполняться немедленно, потому что прерывистое время в это время превысило 500 мс.

«Интервальные вызовы (setInterval) редко используются в среде разработки, потому что последний, скорее всего, начнется до окончания предыдущего»

Простая версия

Recurns вызывает функцию settimeout

Предупреждение. Использование arguments.callee() запрещено в версии 5 ECMAScript (ES5) в строгом режиме. Избегайте использования arguments.callee(), когда функция должна вызывать сама себя, либо давая функциональному выражению имя, либо используя объявление функции.

var executeTimes = 0;
var intervalTime = 500;
//var intervalId = null;


setTimeout(timeOutFun,intervalTime);

function timeOutFun(){
    executeTimes++;
    console.log("doTimeOutFun——"+executeTimes);
    if(executeTimes<5){
        setTimeout(arguments.callee,intervalTime);
    }
}


<!--// 放开下面的注释运行setInterval的Demo-->
<!--intervalId = setInterval(intervalFun,intervalTime);-->
<!--function intervalFun(){-->
<!--    executeTimes++;-->
<!--    console.log("doIntervalFun——"+executeTimes);-->
<!--    if(executeTimes==5){-->
<!--        clearInterval(intervalId);-->
<!--    }-->
<!--}-->

Расширенное издание

let timeMap = {}
let id = 0 // 简单实现id唯一
const mySetInterval = (cb, time) => {
  let timeId = id // 将timeId赋予id
  id++ // id 自增实现唯一id
  let fn = () => {
    cb()
    timeMap[timeId] = setTimeout(() => {
      fn()
    }, time)
  }
  timeMap[timeId] = setTimeout(fn, time)
  return timeId // 返回timeId
}


const myClearInterval = (id) => {
  clearTimeout(timeMap[id]) // 通过timeMap[id]获取真正的id
  delete timeMap[id]
}

Макетная реализация Promise

Реализация обещаний

  • Для реализацииthenметод, нам нужно учитывать асинхронный случай, то есть когдаresolveсуществуетsetTimeoutвыполнять внутри,thenВремяstateещеpendingгосударство, нам нужноthenПри вызове успех и неудача сохраняются в соответствующих массивах.rejectилиresolve, просто позвони им
  • Еще один момент, на который следует обратить внимание, это то, как реализоватьthenцепочка методов, мы по умолчанию используем первыйthenметод возвращаетpromise, метод указан в исходном коде, т.е.thenметод возвращает новыйpromise, называетсяpromise2:promise2=new Promise((resolve,reject)=>{})
    • положи этоpromise2Возвращаемое значение передается следующемуthenсередина
    • Если возвращается обычное значение, передать обычное значение следующемуthenсередина
  • resolvePromiseРеализация функции является ключевым моментом;promiseуказано в спецификацииonFullfilled()илиonRejected()значение, то есть первоеthenВозвращаемое значение называетсяx,судитьxфункция называетсяresolvePromise. В частности, прежде всего, чтобы увидеть, является ли xpromise. еслиpromise, затем примите его результат как новыйpromise2Если успешным результатом является обычное значение, оно напрямую используется какpromise2успешный результат, поэтому сравните x иpromise2,resolvePromiseПараметрыpromise2(возвращается по умолчаниюpromise),x(Мы самиreturnОбъект),resolve,reject;resolveа такжеrejectдаpromise2из
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 异步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // 异步
        setTimeout(() => {
          // 如果报错
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    // 返回promise,完成链式
    return promise2;
  }
}



function resolvePromise(promise2, x, resolve, reject){
  // 循环引用报错
  if(x === promise2){
    // reject报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  // x不是null 且x是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+规定,声明then = x的then方法
      let then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // 就让then执行 第一个参数是this   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

Реализация методов разрешения, отклонения, гонки и гонки

//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

постскриптум:

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

Внешняя маршрутизация

Хэш-способ

class Routers {
  constructor() {
    // 储存hash与callback键值对
    this.routes = {};
    // 当前hash
    this.currentUrl = '';
    // 记录出现过的hash
    this.history = [];
    // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
    this.currentIndex = this.history.length - 1;
    this.refresh = this.refresh.bind(this);
    this.backOff = this.backOff.bind(this);
    // 默认不是后退操作
    this.isBack = false;
    window.addEventListener('load', this.refresh, false);
    window.addEventListener('hashchange', this.refresh, false);
  }

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  refresh() {
    this.currentUrl = location.hash.slice(1) || '/';
    if (!this.isBack) {
      // 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
      // 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
      // 避免再次造成指针的不匹配,我们直接截取指针之前的数组
      // 此操作同时与浏览器自带后退功能的行为保持一致
      if (this.currentIndex < this.history.length - 1)
        this.history = this.history.slice(0, this.currentIndex + 1);
      this.history.push(this.currentUrl);
      this.currentIndex++;
    }
    this.routes[this.currentUrl]();
    console.log('指针:', this.currentIndex, 'history:', this.history);
    this.isBack = false;
  }
  // 后退功能
  backOff() {
    // 后退操作设置为true
    this.isBack = true;
    this.currentIndex <= 0
      ? (this.currentIndex = 0)
      : (this.currentIndex = this.currentIndex - 1);
    location.hash = `#${this.history[this.currentIndex]}`;
    this.routes[this.history[this.currentIndex]]();
  }
}

Метод истории

class Routers {
  constructor() {
    // 储存hash与callback键值对
    this.routes = {};
    // 当前hash
    this.currentUrl = '';
    // 记录出现过的hash
    this.history = [];
    // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
    this.currentIndex = this.history.length - 1;
    this.refresh = this.refresh.bind(this);
    this.backOff = this.backOff.bind(this);
    // 默认不是后退操作
    this.isBack = false;
    window.addEventListener('load', this.refresh, false);
    window.addEventListener('hashchange', this.refresh, false);
  }

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  refresh() {
    this.currentUrl = location.hash.slice(1) || '/';
    if (!this.isBack) {
      // 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
      // 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
      // 避免再次造成指针的不匹配,我们直接截取指针之前的数组
      // 此操作同时与浏览器自带后退功能的行为保持一致
      if (this.currentIndex < this.history.length - 1)
        this.history = this.history.slice(0, this.currentIndex + 1);
      this.history.push(this.currentUrl);
      this.currentIndex++;
    }
    this.routes[this.currentUrl]();
    console.log('指针:', this.currentIndex, 'history:', this.history);
    this.isBack = false;
  }
  // 后退功能
  backOff() {
    // 后退操作设置为true
    this.isBack = true;
    this.currentIndex <= 0
      ? (this.currentIndex = 0)
      : (this.currentIndex = this.currentIndex - 1);
    location.hash = `#${this.history[this.currentIndex]}`;
    this.routes[this.history[this.currentIndex]]();
  }
}

Ленивая загрузка изображения

<script>
    // 获取所有的图片标签
    const imgs = document.getElementsByTagName('img')
    // 获取可视区域的高度
    const viewHeight = window.innerHeight || document.documentElement.clientHeight
    // num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i<imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
</script>

Использованная литература: