Позвольте вам понять ямы и детали JS, подверженного ошибкам

JavaScript

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

Контекст выполнения

Правильная интерпретация var и let

При выполнении кода JS будет сгенерирована среда выполнения. Пока код не написан в функции, он находится в глобальной среде выполнения. Код в функции будет генерировать среду выполнения функции. Есть только два среды выполнения.

Далее давайте рассмотрим шаблонный пример,var

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {
    console.log('call b')
}

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

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

b() // call b second

function b() {
    console.log('call b fist')
}
function b() {
    console.log('call b second')
}
var b = 'Hello world'

varбудет генерировать много ошибок, поэтому это было введено в ES6let.letнельзя использовать перед объявлением, но об этом не часто говорятletне увеличится,letОн был улучшен, и память также открыла для него место на первом этапе, но из-за особенностей этого объявления его нельзя использовать до объявления.

объем

function b() {
    console.log(value)
}

function a() {
    var value = 2
    b()
}

var value = 1
a()

Рассмотрим, что выводит функция b. Могли бы вы подумать, что функция b вызывается в функции a, а соответствующая функция b не объявлена?valueТогда вы должны искать его в функции. На самом деле ответ должен быть 1.

Когда сгенерирован первый этап генерации среды выполнения,[[Scope]]Свойство, которое является указателем, есть соответствующий список областей, JS будет искать переменные до глобальной среды через этот список. Указатель — это расположение узла в объявлении функции, поскольку b объявлено в глобальной среде, поэтомуvalueобъявления будут искаться в глобальной среде. Если b объявлено в a, то значение журнала равно 2.

асинхронный

JS — синхронный язык, вы когда-нибудь задумывались, почему JS написан асинхронно? На самом деле асинхронность JS отличается от асинхронности других языков и по сути является синхронной. Поскольку в браузере будет несколько очередей для хранения асинхронных уведомлений, и каждая очередь имеет свой приоритет, JS будет генерировать стек выполнения при выполнении кода: синхронный код находится в стеке выполнения, а асинхронный код — в очереди. Существует Event Loop, который будет циклически проверять, пуст ли стек выполнения.Когда он пуст, он проверяет в очереди, есть ли уведомления, которые нужно обработать, и если есть, то оно будет взято в очередь. стек выполнения для выполнения.

function sleep() {
  var ms = 2000 + new Date().getTime()
  while( new Date() < ms) {}
  console.log('sleep finish')
}

document.addEventListener('click', function() {
  console.log('click')
})

sleep()
setTimeout(function() {
    console.log('timeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise');
});
console.log('finish')

Приведенный выше код, если выsleepвызывается во время клика, только еслиsleepНа другие асинхронные события не будет ответа, пока не завершится выполнение и не завершится журнал. так что обратите вниманиеsetTimeoutДело не в том, как долго вы настраиваете JS на своевременный ответ, аsetTimeoutТак же есть небольшая деталь: если второй параметр установлен в 0, некоторые люди могут подумать, что это не асинхронно, но на самом деле это асинхронно. Это связано с тем, что стандарт HTML5 предусматривает, что второй параметр этой функции не должен быть меньше 4 миллисекунд, и нехватка будет автоматически увеличиваться.

Следующий вывод построен на Chrome, разные браузеры будут иметь разные выходные данные

promise // promise 会进入 Microtask Queue 中,这个 Queue 会优先执行
timeout // setTimeout 会进入 task Queue 中
click // 点击事件会进入 Event Queue 中

Типы

Исходное значение

JS имеет в общей сложности 6 примитивных значений, которыеBoolean, Null, Undefined, Number, String, Symbol, значения этих типов неизменны.

Есть подверженный ошибкам момент: хотяtypeof nullимеет тип объекта, но Null не является объектом, что является давней ошибкой в ​​языке JS.

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

Для объектов прямое присвоение объекта другому объекту является поверхностной копией. Два объекта указывают на один и тот же адрес. Если какой-либо объект изменится, другой объект также будет изменен.

var a = [1, 2]
var b = a
b.push(3)
console.log(a, b) // -> 都是 [1, 2, 3]

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

функции и объекты

this

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

function foo() {
  console.log(this.a)
}
var a = 2
foo() 

var obj = {
  a: 2,
  foo: foo
}
obj.foo() 

// 以上两者情况 this 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况

// 以下情况是优先级最高的,this 只会绑定在 c 上
var c = new foo()
c.a = 3
console.log(c.a)

// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new

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

function a() {
    return () => {
        return () => {
            console.log(this)
        }
    }
}
console.log(a()()())

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

Давайте посмотрим на другой пример, многие думают, что это проблема JS.

var a = {
    name: 'js',
    log: function() {
        console.log(this)
        function setName() {
            this.name = 'javaScript'
            console.log(this)
        }
        setName()
    }
}
a.log()

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

Замыкания и немедленно выполняемые функции

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

function a(name) {
    // 这就是闭包,因为他使用了父函数的参数
    return function() {
        console.log(name)
    }
}
var b = a('js')
b() // -> js

Теперь давайте посмотрим на вопрос интервью

function a() {
    var array = []

    for(var i = 0; i < 3; i++) {
        array.push(
            function() {
                console.log(i)
            }
        )
    }

    return array
}

var b = a()
b[0]()
b[1]()
b[2]()

Эта тема потому чтоiбыл повышен, поэтомуi = 3, когда функция a выполняется, переменная i в функции a сохраняется в памяти. В массив помещается только объявление, и никакая функция не выполняется. Поэтому, когда функция выполняется, выводятся 3 3s.

Если мы хотим вывести 0, 1, 2, есть два простых способа. Первый находится в цикле for, используяletОбъявите переменную и сохраните каждое значение i, чтобы после выполнения функции a три переменные, объявленные с помощью let, сохранялись в памяти, что решает проблему.

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

function a() {
    var array = []

    for(var i = 0; i < 3; i++) {
        array.push(
            (function(j) {
                return function() {
                    console.log(j)
                }
            }(i))
        )
    }

    return array
}

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

function() {} ()

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

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

true && function() {} ()
new && function() {} ()

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

Прототипы, цепочки прототипов и принцип instanceof

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

function P() {
    console.log('object')
}

var p = new P()

Цепочка прототипов основана на__proto__Искать до Объекта.instanceofПринцип также оценивается по цепочке прототипов.

p instanceof P // true
p instanceof Object // true