основные функции js

внешний интерфейс JavaScript
основные функции js

предисловие

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

В этой серии есть еще два:

Кому интересно, могут посмотреть.

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

содержание

легенда



создать функцию

Объявления функций и выражения функций(Объявление функции и выражение функции)

Есть два способа определить (создать) функции: выражения функций и объявления функций:

 
// 函数表达式
var foo = function(...){}
 
// 函数表达式
(function(){...})
 
// 函数表达式
setTimeout(funciton timer(){...},200)
 
// 函数声明
function(){...}
 

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

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

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

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

 
// 函数表达式
var foo = function bar(){}
console.log(bar) // ReferenceError: bar is not defined
 

 
// 函数表达式
(function bar(){})
console.log(bar) // ReferenceError: bar is not defined
 

 
//  函数声明
function bar(){}
console.log(bar) // function bar(){}
 

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

  • Анонимные функции не отображают осмысленные имена функций в трассировках стека, что затрудняет отладку.
  • Если имя функции отсутствует, можно использовать только имя с истекшим сроком действия, когда функция должна ссылаться на себя.arguments.calleeссылки, например, в рекурсии. Другой пример, когда функция должна ссылаться на себя, — это когда прослушиватель событий должен отменить привязку после запуска события.
  • Анонимные функции опускают имена функций, которые важны для удобочитаемости/понятности кода. Описательное имя делает код понятным.

стрелочная функция(Функция стрелки)

Стрелочные функции — это новая функция в ES6.Стрелочные функции — это новый синтаксис для определения функций с помощью стрелок (=>), но они немного отличаются от традиционных функций JavaScript, в основном в следующих аспектах:

  • нет this, super, аргументов и привязок new.targetЭти значения внутри стрелочной функции напрямую берутся из внешней нестрелочной функции времени.
  • нельзя вызывать с новым ключевым словомСтрелочные функции неContructметод, поэтому его нельзя использовать в качестве конструктора.Если стрелочная функция вызывается через ключевое слово new, программа выдаст ошибку.
  • нет прототипаПоскольку стрелочные функции нельзя вызывать с помощью нового ключевого слова, нет необходимости создавать прототипы, поэтому для стрелочных функций нет свойства прототипа.
  • Привязка этого не может быть измененаЗначение this внутри функции не может быть изменено и остается неизменным на протяжении всего цикла объявления функции.
  • объект arguments не поддерживаетсяСтрелочные функции не имеют привязки аргументов, поэтому вы должны обращаться к аргументам функции как через именованные аргументы, так и через неопределенные аргументы.
  • Повторяющиеся именованные параметры не поддерживаютсяСтрелочные функции не поддерживают повторяющиеся именованные параметры ни в строгом, ни в нестрогом режиме, в то время как традиционные функции не задают повторяющиеся именованные параметры только в строгом режиме.

Следует отметить, что, поскольку привязки this нет, на значение this стрелочных функций не влияют методы call(), apply(), bind().

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

 
const arr = [4,5,6,1,2,3]
const result = arr.sort((a, b)=> a - b) 
 
console.log(result)     // [1, 2, 3, 4, 5, 6]
 

Конструкторы и классы(Конструктор и класс)

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

Например:

 
const arr = new Array(2,3,4)
 

Массив здесь является конструктором.

До появления функции класса ES6 мы часто использовали конструкторы для имитации функций класса. Идея в основном такова: сначала создайте конструктор, затем определите другой метод и скопируйте его в прототип конструктора.

 
function Person(name) {
    this.name = name
}
Person.prototype.sayName = function() {
    console.log(this.name)
}
 
const person = new Person('Tumars')
person.sayName()  // "Tumars"
 
console.log(person instance Person)  // true
console.log(person instance Object) // true
 

В приведенном выше коде Person является конструктором, который создает свойство с именем name при выполнении; метод sayName() добавляется к прототипу Person, поэтому все экземпляры объекта Person будут использовать этот метод. Из-за прототипного наследования объект person является экземпляром Person и экземпляром Object.

Обратите внимание, что instanceof часто используется для обнаружения взаимосвязи между экземплярами и конструкторами, но на самом деле вопрос, на который здесь отвечает instanceof, таков: существуют ли какие-либо указатели на Person.prototype и Object.prototype во всей цепочке [[ProtoType]] объекта person .
Когда мы используем new для вызова функции, мы фактически генерируем объект, связываем цепочку [[ProtoType]] внутри объекта со свойством прототипа конструктора и связываем this функции с объектом.

Многие библиотеки JavaScript, имитирующие классы, разработаны на основе этого шаблона, и классы в ES6 заимствуют аналогичный подход. Классы в ES6 — это синтаксический сахар для конструкторов, который упрощает написание конструкторов. Мы используем класс, чтобы воспроизвести предыдущий пример:

 
class PersonCLass {
    constructor(name) {
        this.name = name
    }
 
    // 等价于 Person.prototype.sayName()
    sayName() {
        console.log(this.name)
    }
}
 
const person = new Person('Tumars')
person.sayName()  // "Tumars"
 
console.log(person instance PersonClass)  // true
console.log(person instance Object) // true
 

Поведение определения PersonClass с помощью синтаксиса объявления класса аналогично предыдущему процессу создания конструктора Person, за исключением того, что конструктор определяется непосредственно в классе с помощью специального имени метода конструктора, и поскольку этот класс использует синтаксис введения для извлечения метод определения, нет необходимости добавлять ключевое слово function.

Но по существу, персонажи все еще является функцией, и SALNAME также является методом на PersonClass.Prototype. Декларация Personclass фактически создает функцию с поведением метода конструктора.

Обратите внимание, что свойствам класса не могут быть присвоены новые значения, в этом примере PersonClass.prototype является таким доступным для чтения свойством класса, а Person.prototype доступен для чтения и записи.

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

Атрибуты

length

Свойство length представляет количество именованных аргументов, которые функция ожидает получить. Такие как:

 
function sum (n1, n2) {
    return n1 + n2
}
console.log(sum.length) // 2
 

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

 
function sum (n1, n2, ...rest) {
    return n1 + n2 + rest.reduce((prev, next)=> prev + next)
}
console.log(sum.length) // 2
 

После добавления параметра переменной ...rest здесь значение свойства length по-прежнему равно 2.

name

Возвращает имя экземпляра атрибута имени функции. Например:

 
function foo () {}
bar = function(){}
 
console.log(foo.name) // foo
console.log(bar.name) // bar
 

В этом коде значением атрибута name функции foo() является "foo", что соответствует имени функции при ее объявлении; значением атрибута name выражения анонимной функции bar() является "bar", что соответствует анонимной функции присваивается имя переменной.

  • Функции, созданные с помощью Function.bind(), будут иметь префикс «bound» к имени функции:

 
function foo() {}; 
foo.bind({}).name; // "bound foo"
 

  • При доступе к свойствам через методы доступа get и set «get» или «set» будут отображаться перед именем функции:

 
var o = { 
    get foo(){}, 
    set foo(x){} 
}; 
var descriptor = Object.getOwnPropertyDescriptor(o, "foo"); 
descriptor.get.name; // "get foo" 
descriptor.set.name; // "set foo";
 

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

 
var sym1 = Symbol("foo"); 
var sym2 = Symbol(); 
var o = { 
    [sym1]: function(){}, 
    [sym2]: function(){} 
}; 
 
o[sym1].name; // "[foo]"
o[sym2].name; // ""
 

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

prototype

Свойство Function.prototype хранит объект-прототип функции.
Обычно используется для добавления общедоступных методов и свойств в экземпляры функций.

мета-свойство new.target

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

Чтобы решить проблему определения того, вызывается ли функция с помощью ключевого слова new, в ECMAScript6 было введено мета-свойство new.target. При вызове метода [[Construct]] функции new.target возвращает ссылку на конструктор или функцию, при вызове метода [[Call]] значение new.target не определено.

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

 
function Person(name) {
    if(typeof new.target !== "undefined") {
        this.name = name
    } else {
        throw new Error("必须通过 new 关键字来调用 Person")
    }
}
 
const person = new Person("Tumars")
const notPerson1 = Person("Tumars")  // Uncaught Error: 必须通过 new 关键字来调用 Person
const notPerson2 = Person.call(person, "Tumars") // Uncaught Error: 必须通过 new 关键字来调用 Person
 

Вы также можете проверить, вызывается ли new.target конкретным конструктором:

 
function Person(name) {
    if(new.target === Person) {
        this.name = name
    } else {
        throw new Error("必须通过 new Person() 生成该实例")
    }
}
function AnotherPerson(name) {
    Person.call(this, name);
}
var person = new Person("Tumars");
var anotherPerson = new AnotherPerson("Tumars");  // error!
 

В этом коде для корректной работы программы new.target должен указывать на Person, иначе будет выброшена ошибка.

метод экземпляра

применить(), вызвать()

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

Метод apply() вызывает функцию с указанным значением this и аргументами, представленными в виде массива (или объекта, подобного массиву).
Метод call() вызывает функцию с указанным значением this и отдельно заданными параметрами (списком параметров).

bind()

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

основная концепция

Многократное использование функций(Позвони и построй)

Функции JavaScript имеют два разных внутренних метода: [[Call]] и [[Construct]]. Когда функция вызывается через ключевое слово new, выполняется функция [[Construct]], которая отвечает за создание нового объекта, обычно называемого экземпляром, а затем выполняет тело функции, чтобы привязать его к экземпляру; если не через new Если ключевое слово вызывает функцию, выполняется функция [[Call]], тем самым непосредственно выполняя тело функции в коде. Функции с методом [[Construct]] вместе называются конструкторами.

Помните, что не все функции имеют метод [[Construct]], поэтому не все функции можно вызывать с помощью new , например, стрелочные функции не имеют метода [[Construct]].

параметры по умолчанию(параметр по умолчанию)

Для именованных параметров функции, если явно не передается значение, значение по умолчанию не определено. Новая функция в ES6 может предоставить начальное значение по умолчанию для параметра, переданного в value. Например:

 
function makeRequest(url, timeout=20000, callback=function(){}) {
    // 函数的其余部分
}
 

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

Влияние параметров по умолчанию на объект arguments

Если функция использует значения параметров по умолчанию, объект arguments остается отдельным от именованных параметров, и изменение именованных параметров не повлияет на объект arguments. Например:

 
// 无默认参数且非 strict mode 下
function foo(a, b ) {
    console.log(a === arguments[0]) // true
    console.log(b === arguments[1]) // true
 
    a = 1
    b = 2 
 
    onsole.log(a === arguments[0]) // true
    console.log(b === arguments[1]) // true
}
 
foo()
 
// 有默认参数下
function bar(a, b ) {
    console.log(a === arguments[0]) // true
    console.log(b === arguments[1]) // true
 
    a = 1
    b = 2 
 
    console.log(a === arguments[0]) // false
    console.log(b === arguments[1]) // false
}
 
bar()
 

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

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

 
function getValue() {
    return  5
}
 
function add(num1, num2 = getValue()) {
    return num1 + num2
}
 
console.log(add(1, 1))  // 2
console.log(add(1))         //6
 

Как и в приведенном выше примере, значение параметра num2 по умолчанию может быть передано в виде выражения. Помните, что метод getValue() не вызывается при первом анализе функции, а только тогда, когда вызывается функция add() без передачи второго параметра.

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

 
fucntion add(num1, num2 = num1) {
    return num1 + num2
}
 
console.log(add(1))         // 2
 

Сделав еще один шаг, вы можете передать параметр num1 функции, чтобы получить значение num2:

 
function getValue(value) {
    return  value + 5
}
 
function add(num1, num2 = getValue(num)) {
    return num1 + num2
}
 
console.log(add(1))         // 7
 

неопределенный параметр(остальные параметры)

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

Например:

функция foo(число, …ключи) {
console.log(keys)
}

foo(1,2,3,4,5) // [2, 3, 4, 5]

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

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

Также есть два ограничения на использование неопределенных параметров:

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

Объем функций и среда выполнения(Область действия и контекст выполнения)

Область действия функции является наиболее распространенной единицей области действия.

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

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

В определенной степени вы можете понять цепочку областей видимости [[Scope]] функций так же, как вы понимаете цепочку прототипов [[Prototype]] объектов. В конце цепочки прототипов находится Object.prototype, а в конце цепочки областей видимости — глобальная область.

Например, есть такой кусок кода:

 
function add(n1, n2) {
    var sum = n1 = n2
    return sum
}
 

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

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

 
var total = add(5, 10)
 

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

Каждый контекст выполнения имеет свою собственную цепочку областей действия для разрешения идентификаторов, и когда контекст выполнения создается, его цепочка областей видимости инициализируется объектом в свойстве [[Scope]] текущей выполняемой функции. Эти значения копируются в цепочку областей видимости среды выполнения в том порядке, в котором они появляются. После завершения этого процесса для среды выполнения создается новый объект, называемый «объектом активации». Активный объект — это объект переменных среды выполнения функции, включая все локальные переменные, именованные параметры, наборы параметров и this.Затем этот объект помещается во внешний конец цепочки доменов действий.Когда контекст выполнения уничтожается, активный объект также уничтожается.

Как показано на рисунке:

Область видимости функции также следует правилам маскировки лексической области видимости:

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

Закрытие

закрытие области(Закрытие области)

Закрытие — очень распространенное понятие, давайте посмотрим на определение закрытия:

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

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

Приведу пример трех типичных замыканий, давайте посмотрим на первое:

 
function foo() {
    var id = 'b'
    alert(id)
}
 
function bar() {
    var id = 'a'
    document.getElementById('my-btn').onclick = function handle() {
        alert(id)
    }
}
 
foo()
bar()
 

В приведенном выше коде после выполнения функции foo среда выполнения foo() и ее активный объект (активный объект является ее внутренней областью) уничтожаются вместе, а идентификатор внутренней переменной также будет удален из памяти мусором. механизм сбора. После выполнения панели функций, поскольку дескриптор функции, привязанный к событию onclick, хранит ссылку на идентификатор переменной, активный объект (внутренняя область) функции bar() не уничтожается вместе с уничтожением среды выполнения функции bar(). .

Действие по сохранению ссылки на внутреннюю область видимости функции после завершения выполнения функции называется замыканием.

Давайте посмотрим на второй пример, который является вариацией приведенного выше примера:

 
function foo() {
    var id = 'tumars'
    return function bar() {
        console.log(id)
    }
}
 
var baz = foo()
 
baz() // tumars
 

Здесь, после выполнения foo, мы все еще можем получить доступ к внутренней области видимости foo() извне через полосу возвращаемых значений foo(), тем самым формируя замыкание. Благодаря объявлению bar у него есть замыкание, покрывающее внутреннюю область действия foo() , что делает эту область действия живой и доступной для ссылки с помощью bar() в любое время после этого.

bar() по-прежнему содержит ссылку на эту область видимости, и эта ссылка называется замыканием.

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

Давайте посмотрим на пример третьего типичного замыкания:

 
for(var i = 1;i <= 3; i++) {
    console.log('i:'+i)
    setTimeout(()=>console.log(i), i*1000)
}
 

Подумайте, что может быть выведено, и поместите это в консоль, чтобы увидеть.
Вы можете видеть, что этот код выводит 4 каждую 1 секунду, почему это так?

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

 
setTimeout(()=>console.log(1), 1*1000)
setTimeout(()=>console.log(2), 2*1000)
setTimeout(()=>console.log(3), 3*1000)
 

Но на самом деле, поскольку i и setTimeout находятся в одной области видимости, код на самом деле выполняется так:

 
var i
i = 1 
setTimeout(()=>console.log(i), i*1000)
 
i++ 
setTimeout(()=>console.log(i), i*1000)
 
i++
setTimeout(()=>console.log(i), i*1000)
 
// 停止运行
 

Вы понимаете? Вам может быть проще написать так:

 
var i
i = 1 
i++
i++
 
1秒后执行: console.log(i)
2秒后执行: console.log(i)
3秒后执行: console.log(i)
 

Таким образом, код будет выводить 3 каждую секунду в течение 3 секунд подряд, потому что при выполнении console.log i становится равным 3.
Основной причиной этого результата является то, что три console.log используют i в одной и той же области.

Мы используем некоторые методы для добавления области действия в setTimeout, чтобы каждая область, содержащая setTimeout, имела свое собственное значение i, например, использование IIFE для создания отдельной области действия для каждого цикла:

 
for(var i = 1; i <= 3; i++) {
    (function(j) {
        setTimeout(()=>console.log(j), j*1000)
    })(i)
}
 

Как вы можете видеть здесь, IIFE используется для создания области закрытия для каждой итерации, каждой(function(j){..})()Существует j, принадлежащий этой области во внутренней области, и i присваивается j на каждой итерации, так что даже если i становится равным 3 в конце, j в каждой области остается неизменным.

Вы также можете использовать es6letОбласть действия на уровне блоков для решения этой проблемы:

 
for(var i = 1; i <= 3; i++) {
    let j = i
    setTimeout(()=>console.log(j), j*1000)
}
 

letсчитал бы периферийным{ .. }это блочная область, используйтеletДоступ к определенным переменным возможен только в пределах этой области на уровне блока.

Обратите внимание, еще одно замечание: объявление let в начале цикла for также имеет особое поведение. Такое поведение указывает на то, что переменная объявляется не только один раз во время цикла, но и на каждой итерации. Каждая последующая итерация инициализирует переменную начальным значением в конце предыдущей итерации. Поэтому при использовании цикла let для создания области блока просто напишите это так:

 
for(let i = 1; i <= 3; i++) {
    setTimeout(()=>console.log(i), i*1000)
}
 

модуль(Модуль)

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

Модули имеют две основные характеристики:

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

Общий лексический поиск:

 
function foo() {
    console.log(a) // 这里会输出 2
}
 
function bar() {
    var a = 3
    foo() 
}
 
var a = 2
 
bar()
 

Поиск закрытия:

 
function bar() {
    var a = 3
    return function foo() {
        console.log(a) // 这里会输出 3
    }()
}
 
var a = 2
 
bar()
 

стек вызовов(Стек вызовов)

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

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

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


Схематическая диаграмма структуры кадра стека

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

через стек вызовов

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

метод первый:

можно легко пройти черезconsole.trace()Этот метод смотрит на кадр вызова текущей функции:


Как просмотреть текущий стек вызовов функций

Способ второй:

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

Второй элемент в стеке — это то место, где была вызвана функция.

это лексическое(лексический-это)

Зачем использовать это?

это обеспечивает более элегантный способ неявной «передачи» ссылки на объект, поэтому API-интерфейсы могут быть более краткими и простыми для повторного использования.

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

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

Эта привязка имеет следующие четыре правила привязки:

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

    • Создайте (или сконструируйте) совершенно новый объект.
    • Этот новый объект будет связан с [[ прототипом ]]. (то есть связывание цепочки [[ProtoType]] нового объекта с объектом, на который указывает свойство прототипа конструктора, которое часто называют прототипом)
    • Этот новый объект будет привязан к this вызова функции.
    • Если функция не возвращает другой объект, то вызов функции в новом выражении автоматически возвращает новый объект.

    Новая привязка изменяет this явной привязки.

Имейте в виду некоторые часто упускаемые из виду проблемы с привязкой:
– При явной привязке передача значения null или undefined будет использовать привязку по умолчанию для привязки к глобальному объекту, поэтому лучше передать, например, пустой объект.someFunc.call(Object.creat(null)).
– Косвенная ссылка на функцию (скорее всего, происходит во время назначения, особенно при назначении функции в качестве параметра), в это время используется ссылка на целевую функцию, объект контекста отсутствует, и будет применена привязка по умолчанию.

Вот пример двух отсутствующих привязок:

 
// 示例一
var o = {
    name: 'Tumars',
    sayName: function () {
        console.log(this.name)
    }
}
var bar = o.sayName
var name = 'im global name'
 
bar() //  'im global name'
 

Хотя bar является ссылкой на o.sayName, на самом деле она ссылается на анонимную функцию, на которую указывает сам sayName, поэтому
bar() на самом деле является вызовом функции без каких-либо украшений, поэтому применяются привязки по умолчанию.

 
// 示例二
var o = {
    name: 'Tumars',
    sayName: function () {
        console.log(this.name)
    }
}
 
function foo(func) {
    func()
}
 
var name = 'im global name'
 
foo(o.sayName)
 

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

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

Общий метод

непосредственная функция(ИИФЭ)

 
(function foo(){})()
 

Эта функция будет рассматриваться как функциональное выражение, а не как стандартное объявление функции.

первый здесь( )превращает функцию в выражение, второе( )После выполнения функции этот режим называется IIFE (Immediately Invoked Function Expression).

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

 
(function IIFE(def) {
    def(window)
})(function def(global) {
    console.log(global)
})
 

карри(Карри)

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

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

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

 
function curring(fn) {
    var stored_args = Array.prototype.slice.call(arguments, 1)
    return function () {
        var new_args = Array.prototype.slice.call(arguments)
            args = stored_args.concat(new_args)
        return fn.apply(null, args)
    }
}
 

 
function currying(fn) {
    var args = [].slice.call(arguments, 1)
    return function () {
        var new_args = [].slice.call(arguments)
        args = args.concat(new_args)
        return fn.apply(null, args)
    }
 
}
 
var cost = (function(){ var money = 0;
    return function(){
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            money += arguments[ i ]; }
            return money; }
        })();
    var cost = currying( cost );
 
    cost( 100 ); 
    cost( 200 ); 
    cost( 300 );
 
    cost()
 

рекурсия(Рекурсия)

Рекурсия может упростить сложные алгоритмы.

Давайте посмотрим на факториальную функцию, реализованную рекурсивным алгоритмом:

 
function factorial(n){
    return n == 0 ? 1 : n * factorial(n-1)
}
 

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

повторять(Итерация)

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

 
function merge(left, right) {
    var result = []
    while(left.length > 0 && right.length > 0) {
        result.push(left[0] < right[0] ? left.shift() : right.shift())
    }
 
    return result.concat(left).concat(right)
}
 
function mergeSort(items) {
    if (items.length === 1) {
        return items
    }
 
    var middle = Math.floor(items.length / 2),
        left = items.slice(0, middle),
        right = items.slice(middle)
 
    return merge(mergeSort(left), mergeSort(right))
}
 

Реализация этого кода проста и интуитивно понятна, но функция mergeSort() будет вызывать очень частые самовызовы, массив длины n в конечном итоге вызовет mergeSort() 2*n - 1 раз, а переполнение стека может произойти при некоторые браузеры.

Алгоритм сортировки слиянием также может быть реализован итеративно:

 
function megerSort(items) {
    if(items.length == 1) {
        return items
    }
    var work = [], len = items.length
    for (var i = 0; i < len; i++) {
        work.push(items[i])
    }
 
    works.push([])  // 如果数组长度为奇数
 
    for (var lim = len; lim > 1; lim = (lim+1)/2) {
        for (var j = 0, k = 0; k < lim; j++, k+=2) {
            work[j] = merge(work[k], work[k+1])
        }
        work[j] = []    // 如果数组长度为奇数
    }
 
    return work[0]
 
}
 

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

Память(Запоминание)

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

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

 
var fibonacci = function (n) {
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
}
 

Теперь нам нужно использовать эту функцию, чтобы найти первые 9 чисел последовательности Фибоначчи:

 
fibonacci(0) // 0 
fibonacci(1) // 1
fibonacci(2) // 1
fibonacci(3) // 2
fibonacci(4) // 3
fibonacci(5) // 5
fibonacci(6) // 8
fibonacci(7) // 13
fibonacci(8) // 21
 

Функция фибоначчи вызывается в общей сложности 167 раз, мы вызываем ее напрямую 9 раз, и она вызывает себя 158 раз для вычисления значения, которое, возможно, только что было вычислено.Если мы сделаем так, чтобы функция имела функцию запоминания (запоминания), мы можем значительно сократить объем вычислений.

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

 
var fibonacci = (function() {
    var cache = [0 ,1]
    var fib = function(n) {
        var result = cache[n]
        if(typeof result !== 'number') {
            result = fib(n-1) + fib(n-2)
            cache[n] = result
        }
        return result
    }
    return fib
})()
 

Снова взяв первые 9 чисел, функция fib вызывается только 23 раза, мы вызываем ее 9 раз, и она вызывает себя 14 раз, чтобы получить ранее сохраненный результат.

Мы можем обобщить эту технику и написать функцию для создания функции с памятью:

 
function memoizer(cache, fn) {
    cache = cache || {}
    isArray = Array.isArray(cache)
 
    return function recur(arg) {
        var result = cache[arg]
        if( typeof cache[arg] !== 'number') {
            result = cache[arg] = fn(recur, arg)
        }
        return result
    }
}
 

Мы можем напрямую использовать функцию memoizer для определения функции фибоначчи с мемоизацией:

 
var fibonacci = memoizer([0,1], function (recur,n) {
    return recur(n-1) + recur(n-2)
})
 

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

 
var factorial = memoizer([1, 1], function(recur, n) {
    return n * recur(n - 1)
})
 

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

оптимизация хвостового вызова(Оптимизация хвостового вызова)

ES6 поддерживает оптимизацию хвостовых вызовов, но только в строгом режиме, нормальный режим недопустим.

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

«Оптимизация хвостового вызова» (Tail call Optimization), то есть сохраняется только кадр вызова внутренней функции. Если последней операцией в функции является вызов функции, то она будет управляться «переходом» (jump) вместо «вызова подпрограммы».

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

Функция вызывает сама себя, что называется рекурсией. Если хвост вызывает сам себя, это называется хвостовой рекурсией.

Рекурсия очень требовательна к памяти, потому что она должна сохранять сотни кадров вызовов одновременно, и она склонна к ошибкам "переполнения стека". Но для хвостовой рекурсии, поскольку существует только один кадр вызова, ошибка «переполнение стека» никогда не возникнет.

Мы изменили функцию Фибоначчи с рекурсии на хвостовую рекурсию:

 
// 普通的斐波那契函数
function fibonacci(n) {
    return n < 2 ? n : fibonacci(n-1) + fibonacci(n-2)
}
 
fibonacci(100) // 堆栈溢出
 
 
// 尾递归的斐波那契函数
function fibonacci(n, ac1 = 1, ac2 = 1) {
    if(n < 2){return ac2}
    return fibonacci(n - 1, ac2, ac1 + ac2)
}
 
fibonacci(100) //573147844013817200000
 

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

Следует отметить, что в ECMAScript 6, если нет кода остановки хвостового рекурсивного кода, это может быть выполнено. Поэтому очень важно иметь граничные условия, которые останавливают рекурсию.

Функция подавления дребезга и функция дросселирования(Отказ от дребезга и дроссельная заслонка)

Функция debounce (дебаунс)

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

Например, если к функции выполнения события, которое выполняется непрерывно в течение короткого времени, например onscroll, добавлена ​​функция устранения дребезга на 100 мс, функция выполнения будет выполняться только после того, как пользователь прекратит прокрутку на 100 мс.

Сценарии приложений функции Debsis Bey:

  • Каждый раз, когда изменение размера/прокрутки запускает событие статистики
  • Проверка ввода текста (отправьте запрос AJAX для проверки после непрерывного ввода текста, просто подтвердите один раз)

Ниже приведена основная форма шаблона:

 
var processor = {
    timeoutId: null,
 
    // 实际进行处理的方法
    performProcessing: function() {
        // 实际执行的代码
        console.log('run')
    },
 
    // 初始处理调用的方法
    process: function() {
        clearTimeout(this.timeoutId);
        var that = this;
        this.timeoutId = setTimeout(function() {
            that.performProcessing()
        }, 100)
    }
}
 
// 尝试开始执行
processor.process()   // 延迟100ms执行,但 50ms 后被中断,不会执行
setTimeout(processor.process.bind(processor),50)    // run
setTimeout(processor.process.bind(processor),160)   // run
 

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

Этот шаблон можно упростить, используя следующие две функции debounce():

 
// 将定时器 id 存为函数的一个属性
function debounce(fn, duration, context) {
    clearTimeout(fn.tId);
    fn.tId = setTimeout(function() {
        fn.call(context);
    }, duration)
} 
 
 
var foo = function() {console.log('run')}
window.onscroll = function() {
    debounce(foo, 150)
}
 
 
// 使用闭包将定时器作为私有变量
function debounce(fn, delay) {
    var timer = null;
    return function(...args) {
        var context = this;
        clearTimeout(timer);
 
        timer = setTimeout(function() {
            fn.apply(context, args)
        }, delay)
 
    }
}
 
var foo = function() {console.log('run')}
window.onscroll = debounce(foo, 150)
 

функция дроссельной заслонки

Регулирование функции позволяет непрерывному выполнению функции статьфиксированный период времениВыполнять с перерывами.

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

Сценарии применения функции дросселирования:

  • Реализация функции перетаскивания элемента DOM (mousemove)
  • События mousedown/keydown для стрелялок (в единицу времени может быть выпущена только одна пуля)
  • Рассчитать расстояние, на которое перемещается мышь (mousemove)
  • Canvas имитирует функцию монтажной области (mousemove).
  • Поиск Lenovo (keyup)
  • Прослушивайте события прокрутки, чтобы определить, следует ли автоматически загружать больше в нижней части страницы: после добавления debounce для прокрутки только после того, как пользователь прекратит прокрутку, будет оцениваться, достиг ли он нижней части страницы; если это дроссель, как пока страница прокручивается, она будет оцениваться один раз каждый раз

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

 
function throttle(fn, delay, mustRunDelay) {
    var timer = null, t_start = 0;
    return function(...args) {
        var context = this, t_curr = Date.now()
        clearTimeout(timer)
 
        if(!t_start) {
            t_start = t_curr
        }
 
        if(t_curr - t_start >= mustRunDelay) {
            fn.apply(context, args)
            t_start = t_curr
        } else {
            timer = setTimeout(function() {
                fn.apply(context, args)
            }, delay)
        }
    }
}
 
var foo = function() {console.log('run')}
window.onscroll = throttle(foo, 50, 100)
 

Вот несколько ссылок на функции debounce и throttling:
woohoo.alloy team.com/2012/11/ просто про…
GitHub.com/ Han Child Eating / Un...
GitHub.com/Манзи ест/ООН…
GitHub.com/Манзи ест/ООН…
CSS-tricks.com/he и -differ E…

функция разделения времени(Часть времени)

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

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

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

 
var MyArr = [...Array(10000).keys()]  // 这是一个有1万条数据的数组
 
function renderList(data) {
    data.forEach(v=>{
        var div = document.createElement( 'div' );
        div.innerHTML = v;
        document.body.appendChild( div );
    })
}
 
renderList(arr)  // 卡了一会才创建完
 

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

Мы создаем функцию timeChunk как функцию разделения времени.timeChunk принимает 3 параметра: данные, необходимые для создания узлов, функцию, инкапсулирующую дополнительное создание узлов, и количество узлов, создаваемых в каждом пакете.

 
function timeChunk(data, fn, count) {
    var arr = [], timer; 
 
    var runRender = function() {
        arr = data.splice(0, Math.min(count, data.length))
        fn(arr)
    }
 
    return function() {
        timer = setInterval(()=>{
            data.length === 0 ? clearInterval(timer) : runRender() 
        },100)  
    }   
}
 
var newRenderList = timeChunk(arr, renderList, 500)
 
newRenderList() // 每 100ms 创建个节点
 

функции ленивой загрузки(Функции ленивой загрузки)

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

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

 
var addEvent = function( elem, type, handler ){
    if ( window.addEventListener ){
        return elem.addEventListener( type, handler, false );
    }
    if ( window.attachEvent ){
        return elem.attachEvent( 'on' + type, handler );
    }
};
 

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

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

 
var addEvent = (function() {
    if ( window.addEventListener ){
        return function(elem, type, handler){
            elem.addEventListener( type, handler, false );
        }
    }
    if ( window.attachEvent ){
        return function(elem, type, handler){
            elem.attachEvent( 'on' + type, handler );
        }
    }
})()
 

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

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

 
var addEvent = function() {
    if ( window.addEventListener ){
        addEvent = function(elem, type, handler){
            elem.addEventListener( type, handler, false );
        }
    }
    if ( window.attachEvent ){
        addEvent = function(elem, type, handler){
            elem.attachEvent( 'on' + type, handler );
        }
    }
}