предисловие
Последнее предварительное интервью былорулонвзлетел,отКомпьютерные принципы, принципы компиляции, структуры данных, алгоритмы, шаблоны проектирования, парадигмы программированияприбытьИнструменты компиляции, инструменты форматирования, Git, NPM, модульное тестирование, Nginx, PM2, CI/CD для понимания и использования.
Просто выберите часть, очки знаний могут бытьглубококопать,бездонныйЧто-то вроде.
Эта статья посвящена основам JS и надеется немного помочь вам.
Напоминание: эта статья относится кПередовые студентыа недавно готовился хотетьДрузья, систематически изучающие основы JS. Начальники среднего и старшего звена, проработавшие много лет, могут сразу пропустить эту статью~
Глава 1 «Повторное изучение JavaScript» Переменные и типы
1. Тип данных
7 основных типов данных:BigInt、Symbol、Undefined、Null、Boolean、Number 和 String
1 сложный тип данных:Object
Во-вторых, базовая структура данных
Попробуйте проанализировать реализацию Object с помощью исходного кода V8: корневым родительским классом всех типов данных в V8 является Object, Object является производным от HeapObject и предоставляет основные функции хранения.JSReceiver ниже используется для поиска прототипа, а JSObject ниже находится в JS Объект, Массив/Функция/Дата и т. д. наследуются от JSObject. Фиксированный массив слева — это место, где фактически хранятся данные.Перед созданием JSObject текстовые свойства прочитанного объекта будут сериализованы в константные_свойства, следующие данные:
var data = {
name: "yin",
age: 18,
"-school-": "high school",
};
будет упорядочено как:
../../v8/src/runtime/runtime-literals.cc 72 constant_properties:
0xdf9ed2aed19: [FixedArray]
– length: 6
[0]: 0x1b5ec69833d1 <String[4]: name>
[1]: 0xdf9ed2aec51 <String[3]: yin>
[2]: 0xdf9ed2aec71 <String[3]: age>
[3]: 18
[4]: 0xdf9ed2aec91 <String[8]: -school->
[5]: 0xdf9ed2aecb1 <String[11]: high school>
Это фиксированный массив с элементами 6. Поскольку у данных всего 3 атрибута, каждый атрибут имеет ключ и значение, поэтому массив имеет 6. Первый элемент — это первый ключ, второй элемент — это первое значение, третий элемент — это второй ключ, четвертый элемент — это второй ключ и так далее.
Object предоставляет функцию Print(), которая очень полезна для печати информации об объекте. Приведенный выше вывод имеет два типа данных: один типа String, а другой — целого типа. FixedArray — это класс, похожий на массив, реализованный в V8 и представляющий непрерывный участок памяти.
Ссылка из:Вууху.RR Fed.com/2017/04/04/…
3. Символ
Применение типа Symbol в реальной разработке, простой Symbol можно реализовать вручную
3.1 Использовать символ как имя свойства объекта (ключ)
До этого мы обычно использовали строки для определения или доступа к свойствам объектов, например следующий код:
let obj = {
abc: 123,
hello: "world",
};
obj["abc"]; // 123
obj["hello"]; // 'world'
Теперь символы также можно использовать для определения и доступа к свойствам объекта:
const PROP_NAME = Symbol();
const PROP_AGE = Symbol();
let obj = {
[PROP_NAME]: "一斤代码",
};
obj[PROP_AGE] = 18;
obj[PROP_NAME]; // '一斤代码'
obj[PROP_AGE]; // 18
Далее следует еще один весьма примечательный вопрос: при использовании Symbol в качестве ключа свойства объекта, в чем разница при перечислении ключа объекта? В практических приложениях нам часто нужно использовать Object.keys() или for...in для перечисления имен свойств объектов, В чем в связи с этим разница между ключом типа Symbol? Взгляните на следующий пример кода:
let obj = {
[Symbol("name")]: "一斤代码",
age: 18,
title: "Engineer",
};
Object.keys(obj); // ['age', 'title']
for (let p in obj) {
console.log(p); // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj); // ['age', 'title']
Как видно из приведенного выше кода, ключ типа Symbol не может быть пронумерован с помощью Object.keys() или for...in, и он не входит в набор имен свойств самого объекта. Следовательно, используя эту функцию, мы можем использовать Symbol для определения некоторых свойств, которые не требуют внешних операций и доступа.
Из-за этой функции при использовании JSON.stringify() для преобразования объекта в строку JSON свойство Symbol также будет исключено из вывода:
JSON.stringify(obj); // {"age":18,"title":"Engineer"}
Мы можем использовать эту функцию, чтобы лучше проектировать наши объекты данных, делая «внутренние операции» и «внешние выборочные выходные данные» более элегантными.
Однако в этом случае у нас нет возможности определить свойства объекта. Символ, не так ли? Не также. Будет специальный API для Symbol, например:
// 使用Object的API
Object.getOwnPropertySymbols(obj); // [Symbol(name)]
// 使用新增的反射API
Reflect.ownKeys(obj); // [Symbol(name), 'age', 'title']
3.2 Используйте символы вместо констант
Давайте сначала посмотрим на следующий код. Часто ли он появляется в вашем коде?
const TYPE_AUDIO = "AUDIO";
const TYPE_VIDEO = "VIDEO";
const TYPE_IMAGE = "IMAGE";
function handleFileResource(resource) {
switch (resource.type) {
case TYPE_AUDIO:
playAudio(resource);
break;
case TYPE_VIDEO:
playVideo(resource);
break;
case TYPE_IMAGE:
previewImage(resource);
break;
default:
throw new Error("Unknown type of resource");
}
}
Как и в приведенном выше коде, мы часто определяем набор констант для представления нескольких разных типов в соответствии с бизнес-логикой.Обычно мы надеемся, что эти константы имеют уникальную связь.Чтобы обеспечить это, нам нужно присвоить константы константам. уникальное значение (такое как «АУДИО», «ВИДЕО», «ИЗОБРАЖЕНИЕ» здесь) хорошо, когда констант мало, но если констант слишком много, вам, возможно, придется потратить немного времени, чтобы получить для них лучшие имя.
Теперь с символами нам не нужно быть такими хлопотными:
const TYPE_AUDIO = Symbol();
const TYPE_VIDEO = Symbol();
const TYPE_IMAGE = Symbol();
Это определение напрямую гарантирует, что значения трех констант уникальны! Это очень удобно?
3.3 Используйте Symbol для определения частных свойств/методов класса
Мы знаем, что в JavaScript у нас есть доступ к объектно-ориентированному языковому ключевому слову управления доступом private, все определенные свойства или методы в классах доступны публично. Так что это вызвало у нас некоторые проблемы при разработке нашего API.
Благодаря Symbol и модульному механизму становятся возможными частные свойства и методы класса. Например:
в файле a.js
const PASSWORD = Symbol();
class Login {
constructor(username, password) {
this.username = username;
this[PASSWORD] = password;
}
checkPassword(pwd) {
return this[PASSWORD] === pwd;
}
}
export default Login;
в файле b.js
import Login from "./a";
const login = new Login("admin", "123456");
login.checkPassword("123456"); // true
login.PASSWORD; // oh!no!
login[PASSWORD]; // oh!no!
login["PASSWORD"]; // oh!no!
Так как константа ПАРОЛЬ Символа определена в модуле, где находится a.js, внешний модуль не может получить Символ, и создать идентичный Символ невозможно (потому что Символ уникален), поэтому Символ этого ПАРОЛЯ может только быть использованным Он ограничен для использования внутри a.js, поэтому свойства класса, определенные им, не могут быть доступны вне модуля, что обеспечивает эффект приватизации.
3.4 Внедрение Symbol вручную
(function() {
var root = this;
var generateName = (function() {
var postfix = 0;
return function(descString) {
postfix++;
return "@@" + descString + "_" + postfix;
};
})();
var SymbolPolyfill = function Symbol(description) {
if (this instanceof SymbolPolyfill)
throw new TypeError("Symbol is not a constructor");
var descString =
description === undefined ? undefined : String(description);
var symbol = Object.create({
toString: function() {
return this.__Name__;
},
valueOf: function() {
return this;
},
});
Object.defineProperties(symbol, {
__Description__: {
value: descString,
writable: false,
enumerable: false,
configurable: false,
},
__Name__: {
value: generateName(descString),
writable: false,
enumerable: false,
configurable: false,
},
});
return symbol;
};
var forMap = {};
Object.defineProperties(SymbolPolyfill, {
for: {
value: function(description) {
var descString =
description === undefined ? undefined : String(description);
return forMap[descString]
? forMap[descString]
: (forMap[descString] = SymbolPolyfill(descString));
},
writable: true,
enumerable: false,
configurable: true,
},
keyFor: {
value: function(symbol) {
for (var key in forMap) {
if (forMap[key] === symbol) return key;
}
},
writable: true,
enumerable: false,
configurable: true,
},
});
root.SymbolPolyfill = SymbolPolyfill;
})();
В-четвертых, конкретная форма хранения переменных в памяти
4.1 Память стека и память кучи
Переменные в JavaScript делятся на примитивные типы и ссылочные типы:
- Базовые типы — это простые сегменты данных, хранящиеся в памяти стека, их значения имеют фиксированный размер, хранятся в пространстве стека и доступны по значению
- Тип ссылки — это объект, хранящийся в куче памяти. Размер значения не фиксирован. Адрес доступа к объекту, хранящемуся в памяти стека, указывает на объект в памяти кучи. JavaScript не допускает прямого доступа к расположению в куче куча памяти ссылка на объект операции
4.2 Комбинирование кода и диаграмм для понимания
let a1 = 0; // 栈内存
let a2 = "this is string"; // 栈内存
let a3 = null; // 栈内存
let b = { x: 10 }; // 变量b存在于栈中,{ x: 10 }作为对象存在于堆中
let c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3]作为对象存在于堆中
Когда мы хотим получить доступ к ссылочному типу данных в куче памяти:
- Получить адресную ссылку объекта из стека
- Затем получаем нужные нам данные из кучи памяти
4.3 Копирование поведения базовых типов
let a = 20;
let b = a;
b = 30;
console.log(a); // 20
Разберитесь со следующим рисунком:
Когда данные в памяти стека копируются, система автоматически присваивает новое значение новой переменной, и, наконец, эти переменные не зависят друг от друга.
4.4 Копирование поведения ссылочных типов
let a = { x: 10, y: 20 };
let b = a;
b.x = 5;
console.log(a.x); // 5
- Копия ссылочного типа также присваивает новое значение новой переменной b и сохраняет ее в памяти стека, разница в том, что это значение является просто адресным указателем ссылочного типа
- Оба из них указывают на то же значение, что такое же, как указатель адреса, доступ к определенным объектам в памяти кучи на самом деле одинаково
- Таким образом, при изменении b.x изменяется и a.x, что характерно для ссылочных типов.
Разберитесь со следующим рисунком:
Суммировать:
5. Встроенные объекты и операции упаковки и распаковки
5.1 Встроенные функции (объекты) в JS
String(), Number(), Boolean(), RegExp(), Date(), Error(), Array(), Function(), Object(), symbol(); объектно-подобный конструктор
- Переменные, создаваемые этими встроенными функциями, являются объектами, инкапсулирующими значения базовых типов, таких как:
var a = new String("abb"); // typeof(a)=object
За исключением того, что переменные, созданные с помощью Function(), выводятся как функции через typeof, все остальные являются объектами.
- Чтобы узнать реальный тип построенной переменной, можно использовать:
Object.prototype.toString.call([1, 2, 3]); // "[object,array]"
Следующее значение - это тип входящего параметра
- Если переменной назначена константная форма (то есть с использованием примитивного типа данных), не используйте этот метод для определения переменной.
5.2 Упаковка
Это преобразование базового типа в соответствующий объект. Бокс делится на имплицитный и явный
- Implicit Boxing: всякий раз, когда читается значение примитивного типа, объект, соответствующий примитивному типу, создается на заднем плане. Вызов метода на этом базовом типе на самом деле вызывает метод на объекте базового типа. Объектом этого базового типа является временным, он существует только в момент, когда выполняется линейка кода, которую называется метод, и он разрушается сразу после выполнения метода.
let num = 123;
num.toFixed(2); // '123.00'//上方代码在后台的真正步骤为
var c = new Number(123);
c.toFixed(2);
c = null;
-
Создайте экземпляр типа Number.
-
Вызовите метод экземпляра.
-
Уничтожить экземпляр.
- Явная упаковка: примитивные типы могут быть явно упакованы с помощью встроенных объектов Boolean, Object, String и т. д.
var obj = new String("123");
5.3 Распаковка
Распаковка — это противоположность упаковки, превращение объектов в значения примитивных типов. Абстрактная операция ToPrimitive вызывается внутри во время процесса распаковки. Эта операция принимает два параметра: первый параметр — это объект, который нужно преобразовать, а второй параметр PreferredType — это тип объекта, в который ожидается преобразование. Второй параметр необязателен, по умолчанию параметром является число, то есть ожидается, что объект будет преобразован в числовой тип.
-
Преобразовать число в объект
-
Сначала вызовите метод valueOf самого объекта. При возврате значения примитивного типа используйте функцию Number непосредственно для значения и возвращайте результат.
-
Если valueOf возвращает объект, продолжайте вызывать метод toString самого объекта. Если toString возвращает значение примитивного типа, используйте функцию Number для этого значения и верните результат.
-
Если toString возвращает объект, сообщается об ошибке.
-
Number([1]); //1
转换演变:
[1].valueOf(); // [1];
[1].toString(); // '1';Number('1'); //1
-
Строка для объекта
-
Сначала вызовите метод toString самого объекта. Если возвращается значение примитивного типа, используйте функцию String для этого значения и верните результат.
-
Если toString возвращает объект, продолжайте вызывать метод valueOf. Если valueOf возвращает значение примитивного типа, используйте функцию String для этого значения и верните результат.
-
Если valueOf возвращает объект, сообщается об ошибке.
-
String([1,2]) //"1,2"
转化演变:
[1,2].toString(); //"1,2"
String("1,2"); //"1,2"
-
Логический объект преобразования
Булевы объекты преобразования являются особыми, за исключением следующих шести значений, которые преобразуются в false, все остальные — в true
undefined null false 0(包括+0和-0) NaN 空字符串('')
Boolean(undefined) //false
Boolean(null) //false
Boolean(false) //false
Boolean(0) //false
Boolean(NaN) //false
Boolean('') //false
Boolean([]) //true
Boolean({}) //true
Boolean(new Date()) //true
Шесть, типы значений и ссылочные типы
6.1 Различное распределение памяти при объявлении переменных
- Примитивные значения: Простые сегменты данных хранятся в стеке, то есть их значения хранятся непосредственно там, где осуществляется доступ к переменной.
Это связано с тем, что пространство, занимаемое этими примитивными типами, фиксировано, поэтому их можно хранить в меньшей области памяти — стеке. Это хранилище облегчает быстрый поиск значения переменной.
- Ссылочное значение: объект, хранящийся в куче, то есть значение, хранящееся в переменной, является указателем (точкой) на адрес памяти, где хранится объект.
Это связано с тем, что: размер ссылки изменит значение, поэтому вы не можете поместить его в стек, в противном случае он уменьшит скорость переменных поиска. Напротив, значение переменной на стеке представляет собой адресное пространство объекта, хранящегося на куче. Адрес размер фиксирован, поэтому он хранится в стеке без какого-либо негативного воздействия на переменные производительности.
6.2 Различные механизмы распределения памяти также приводят к различным механизмам доступа
-
В JS не разрешен прямой доступ к объекту, хранящемуся в куче памяти, поэтому при обращении к объекту первое, что вы получаете, это адрес объекта в куче памяти, а затем значение в объекте получается по по этому адресу.Это легендарный доступ по ссылке.
-
Доступ к примитивным типам возможен напрямую.
6.3 Различия при копировании переменных
-
Примитивное значение: при копировании переменной, содержащей исходное значение, в другую переменную, копия исходного значения присваивается новой переменной, после чего две переменные полностью независимы, они просто имеют одно и то же значение, обе независимы.
-
Ссылочное значение: при копировании переменной, содержащей адрес памяти объекта, в другую переменную, адрес памяти будет назначен новой переменной, что означает, что обе переменные указывают на один и тот же объект в куче памяти.Любые изменения, сделанные одной, будут отразиться в другом. (При копировании объекта в куче памяти не создается новый идентичный объект, а появляется дополнительная переменная, содержащая указатель на этот объект)
6.4 Различия в передаче параметров (процесс копирования фактических параметров в формальные параметры)
Для начала следует уточнить, что все параметры всех функций в ECMAScript передаются значениями. Но почему есть разница при использовании исходного типа и ссылочного типа ссылочного типа? Это не потому, что разница связана с распределением памяти.
-
Исходное значение: просто передайте значение переменной в параметр, и тогда параметр и переменная не будут влиять друг на друга.
-
Ссылочное значение: Значение в объектной переменной — это адрес памяти объекта в куче памяти, вы всегда должны помнить об этом!
Следовательно, значение, которое она передает, также является адресом памяти, поэтому изменение этого параметра внутри функции отражается внешне, потому что все они указывают на один и тот же объект.
Семь, разница между null и undefined
7.1 Определения
Тип Null: Тип Null также имеет только одно специальное значение — null. С логической точки зрения нулевое значение представляет нулевой указатель объекта.
Неопределенный тип: undefined Type Только одно значение, то есть специальное неопределенное. Значение этой переменной не определено при использовании переменной, объявленной Var, но не инициализированной.
7.2 Сценарии применения null и undefined
null означает "нет объекта", т.е. там не должно быть никакого значения. Типичное использование:
- В качестве аргумента функции это означает, что аргумент функции не является объектом.
- как конец цепочки прототипов объектов.
console.log(null instanceof Object); // false
undefined означает "отсутствующее значение", т.е. здесь должно быть значение, но оно не определено. Типичное использование:
- Когда переменная объявлена, но ей не присвоено значение, она равна неопределенной.
- При вызове функции параметры должны быть предоставлены, а не предоставлены, этот параметр равен undefined.
- У объекта нет назначенного свойства, значение этого свойства не определено.
- Когда функция не возвращает значение, по умолчанию она возвращает неопределенное значение.
7.3 Количество преобразованных значений
Числовые (пустые) выходы 0, числовые (неопределенные) выходы NaN
Восемь, определите тип данных
Как минимум три способа судить о типах данных JavaScript, их преимуществах и недостатках, а также о том, как точно судить о типах массивов.
8.1 typeof
- Применимая сцена
typeof
Оператор может точно определить, относится ли переменная к следующим нескольким примитивным типам:
typeof "ConardLi"; // string
typeof 123; // number
typeof true; // boolean
typeof Symbol(); // symbol
typeof undefined; // undefined
Вы также можете использовать его для определения типов функций:
typeof function() {}; // function
-
Неприменимые сценарии
когда вы используете
typeof
Кажется, немного устал судить об эталонном типе:
typeof []; // object
typeof {}; // object
typeof new Date(); // object
typeof /^\d*$/; // object
Все ссылочные типы, кроме функций, оцениваются какobject
.
Кроме тогоtypeof null === 'object'
Это также может вызвать головную боль, которая находится вJavaScript
Первое издание переданоbug
, поскольку модификация вызовет большое количество проблем с совместимостью, она не исправлена...
8.2 instanceof
instanceof
Операторы могут помочь нам определить, к какому типу объекта относится ссылочный тип:
[] instanceof Array; // true
new Date() instanceof Date; // true
new RegExp() instanceof RegExp; // true
Давайте сначала рассмотрим несколько правил цепочки прототипов:
- Все ссылочные типы имеют свойства объекта, т. е. свойства можно свободно расширять.
- Все ссылочные типы имеют
__proto__
(Неявный прототип) свойство, которое является обычным объектом - Все функции имеют
prototype
Свойство (явный прототип), также обычный объект - Все типы ссылок
__proto__
value указывает на его конструкторprototype
- При попытке получить свойство объекта, если сама переменная не имеет этого свойства, она перейдет к его
__proto__
найти в
[] instanceof Array
на самом деле судитьArray.prototype
Вы в[]
в цепочке прототипов.
Итак, используйтеinstanceof
для определения типа данных это будет не очень точно, это не является первоначальным замыслом его дизайна:
[] instanceof Object // true
function(){} instanceof Object // true
Кроме того, используйтеinstanceof
также не может обнаруживать примитивные типы данных, поэтомуinstanceof
Не очень хороший выбор.
8.3 toString
Выше мы упомянули в операции распаковкиtoString
функцию, которую мы можем вызывать для выполнения преобразований из ссылочных типов.
Каждый ссылочный тип имеет
toString
метод по умолчанию,toString()
метод каждогоObject
Наследование объекта. Если этот метод не переопределен в пользовательском объекте,toString()
вернуть"[object type]"
,вtype
является типом объекта.
const obj = {};
obj.toString(); // [object Object]
Обратите внимание, что упомянутое выше如果此方法在自定义对象中未被覆盖
,toString
позволит достичь желаемого эффекта.На самом деле, большинство ссылочных типов, таких какArray、Date、RegExp
Это все переписаноtoString
метод.
Мы можем напрямую позвонитьObject
Раскрыто на прототипеtoString()
метод, используяcall
изменитьthis
Точка для достижения эффекта, который мы хотим.
8.4 jquery
Давайте посмотримjquery
Как судить о типе в исходном коде:
var class2type = {};
jQuery.each(
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(
" "
),
function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase();
}
);
type: (obj) => {
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function"
? class2type[Object.prototype.toString.call(obj)] || "object"
: typeof obj;
};
isFunction: (obj) => {
return jQuery.type(obj) === "function";
};
Примитивные типы используются напрямуюtypeof
, ссылочный тип используетObject.prototype.toString.call
Получить тип.
Определить тип массива можно с помощьюArray.isArray(value)
илиObject.prototype.toString.call(value)
.
Девять, неявное преобразование типов
Сценарии, в которых может происходить неявное преобразование типов, и принципы преобразования, как их избежать или разумно применить
потому чтоJavaScript
Это слаботипизированный язык, поэтому преобразование типов происходит очень часто.
Существует два типа преобразования типов: неявное преобразование — это преобразование типа, автоматически выполняемое программой, а обязательное преобразование — это преобразование типа, которое мы выполняем вручную.
Приведение здесь больше упоминаться не будет, давайте рассмотрим несколько вызывающих головную боль сценариев, в которых могут происходить неявные преобразования типов, и способы их преобразования:
9.1 Правила преобразования типов
Если происходит неявное преобразование, то различные типы преобразуются друг в друга в соответствии со следующими правилами:
9.2 Операторы if и логические операторы
существуетif
В операторах и логических операторах, если есть только одна переменная, переменная сначала преобразуется вBoolean
значение, только следующие случаи будут преобразованы вfalse
, остальные превращаются вtrue
:
null;
undefined;
("");
NaN;
0;
false;
9.3 Различные арифметические операторы
Мы имеем дело с различными неNumber
Типы используют математические операторы (- * /
), Затем на-Number
преобразование типа вNumber
Типы;
1 - true; // 0
1 - null; // 1
1 * undefined; // NaN
2 * ["5"]; // 10
Уведомление+
является исключением, выполнить+
оператор:
- когда одна сторона
String
тип, распознается как конкатенация строк и предпочтительно преобразует другую сторону в строковый тип. - когда одна сторона
Number
Тип, другая сторона является исходным типом, затем преобразуйте исходный тип вNumber
Типы. - когда одна сторона
Number
тип, другая сторона - ссылочный тип, ссылочный тип иNumber
Конкатенация после преобразования типа в строку.
123 + "123"; // 123123 (规则1)
123 + null; // 123 (规则2)
123 + true; // 124 (规则2)
123 + {}; // 123[object Object] (规则3)
9.4 ==
использовать==
, если обе стороны одного типа, результат сравнения и===
то же самое, иначе произойдет неявное преобразование, используйте==
Происходящее преобразование можно разделить на несколько разных случаев (только две стороны считаются разными типами):
-
NaN
NaN
Сравнение с любым другим типом всегда возвращаетfalse
(включая и себя).
NaN == NaN; // false
-
Boolean
Boolean
по сравнению с любым другим типом,Boolean
сначала преобразуется вNumber
Типы.
true == 1; // true
true == "2"; // false
true == ["1"]; // true
true == ["2"]; // false
Обратите внимание на момент, который может сбить с толку:
undefined、null
а такжеBoolean
сравни, однакоundefined、null
а такжеfalse
легко представить себе как ложные значения, но результат их сравненияfalse
, Причина вfalse
сначала преобразуется в0
:
undefined == false; // false
null == false; // false
-
Строка и число
String
а такжеNumber
сравните сначалаString
Перевести вNumber
Типы.
123 == "123"; // true
"" == 0; // true
-
нулевой и неопределенный
null == undefined
Результат сравненияtrue
,Помимо,null、undefined
И любые другие результаты сравнения стоимостиfalse
.
null == undefined; // true
null == ""; // false
null == 0; // false
null == false; // false
undefined == ""; // false
undefined == 0; // false
undefined == false; // false
-
Примитивные типы и ссылочные типы
При сравнении примитивных типов и ссылочных типов тип объекта
ToPrimitive
Правила преобразования в примитивные типы:
"[object Object]" == {}; // true
"1,2,3" == [1, 2, 3]; // true
Взгляните на сравнение ниже:
[] == ![]; // true
!
приоритет выше, чем==
,![]
сначала будет преобразовано вfalse
, а затем по третьему пункту выше,false
Перевести вNumber
Типы0
, осталось[]
Перевести в0
, обе стороны равны.
([null] == false[undefined]) == // true
false; // true
по массивуToPrimitive
правило, элементы массиваnull
илиundefined
, элемент рассматривается как пустая строка, поэтому[null]、[undefined]
будет преобразован в0
.
Итак, сказав так много, рекомендуется использовать===
Чтобы проверить, равны ли два значения...
9.5 Интересный вопрос интервью
Классический вопрос на собеседовании, как сделать:a == 1 && a == 2 && a == 3
.
В соответствии с приведенным выше преобразованием распаковки и==
Неявное преобразование , мы можем легко написать ответ:
const a = {
value: [3, 2, 1],
valueOf: function() {
return this.value.pop();
},
};
10. Десятичная точность
Причины потери десятичной точности, максимальное число, которое может хранить JavaScript, самое большое безопасное число, как JavaScript обрабатывает большие числа и как избежать потери точности.
10.1 Причины потери десятичной точности
Двоичная реализация компьютера и предел количества битов имеют некоторые числа, которые не могут быть представлены конечно. Точно так же, как некоторые иррациональные числа не могут быть представлены финитно, например пи 3,1415926..., 1,3333... и т.д. JS следуетIEEE 754Спецификация, использующая хранилище двойной точности (double Precision), занимающее 64 бита. Как показано
1 бит используется для представления бита знака
11 бит используются для представления экспоненты
52 бита для мантиссы
числа с плавающей запятой, такие как
1
2
0,1 >> 0,0001 1001 1001 1001… (1001 бесконечный цикл)
0,2 >> 0,0011 0011 0011 0011… (0011 бесконечный цикл)
В настоящее время вы можете имитировать только десятичное число для округления, но двоичное число имеет только два 0 и 1, поэтому оно становится 0, а 1 округляется. Это основная причина ошибок и потери точности в некоторых операциях с плавающей запятой на компьютере. Максимальное и минимальное безопасные значения для JS можно получить так:
console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991
Для целых чисел вероятность возникновения проблем во внешнем интерфейсе может быть относительно низкой.В конце концов, немногие предприятия должны использовать очень большие целые числа.Пока результат операции не превышает Math.pow(2, 53), точность не будет потерянный. Если оно действительно превышает максимальное безопасное число, используйте BigInt(Number) для расчета.
Для десятичных дробей все еще есть много шансов на проблемы с внешним интерфейсом, особенно когда некоторые веб-сайты электронной коммерции используют такие данные, как суммы. Решение: поставить десятичную дробь перед целым числом (умножить кратное), а затем привести обратно к исходному кратному (разделить кратное), то есть постараться не иметь дело с десятичными знаками в бизнесе.
Глава 2 «Повторное изучение JavaScript» Прототипы и цепочки прототипов
1. Шаблоны проектирования прототипов и правила создания прототипов
Понимать шаблоны прототипирования и правила прототипирования в JavaScript
1.1 режим дизайна
1.1.1 Заводской образец
Создайте объект внутри функции, назначьте свойства и методы объекту и верните объект
function Person() {
var People = new Object();
People.name = "CrazyLee";
People.age = "25";
People.sex = function() {
return "boy";
};
return People;
}
var a = Person();
console.log(a.name); // CrazyLee
console.log(a.sex()); // boy
1.1.2 Шаблон конструктора
Вместо того, чтобы воссоздавать объект внутри функции, обратитесь к нему с помощью этого
function Person() {
this.name = "CrazyLee";
this.age = "25";
this.sex = function() {
return "boy";
};
}
var a = new Person();
console.log(a.name); // CrazyLee
console.log(a.sex()); // boy
1.1.3 Режим прототипа
Свойства не определяются в функции, а свойства определяются с помощью свойства прототипа, так что все экземпляры объекта могут совместно использовать содержащиеся в нем свойства и методы.
function Parent() {
Parent.prototype.name = "carzy";
Parent.prototype.age = "24";
Parent.prototype.sex = function() {
var s = "女";
console.log(s);
};
}
var x = new Parent();
console.log(x.name); // crazy
console.log(x.sex()); // 女
1.1.4 Режим наложения
Режим прототипа + режим конструктора. В этом шаблоне шаблон конструктора используется для определения свойств экземпляра, а шаблон прототипа — для определения методов и общих свойств.
function Parent() {
this.name = "CrazyLee";
this.age = 24;
}
Parent.prototype.sayname = function() {
return this.name;
};
var x = new Parent();
console.log(x.sayname()); // Crazy  
1.1.5 Шаблон динамического прототипа
Вся информация инкапсулируется в конструкторе, а прототип инициализируется в конструкторе, что можно использовать для определения необходимости инициализации прототипа путем оценки допустимости метода.
function Parent() {
this.name = "CrazyLee";
this.age = 24;
if (typeof Parent._sayname == "undefined") {
Parent.prototype.sayname = function() {
return this.name;
};
Parent._sayname = true;
}
}
var x = new Parent();
console.log(x.sayname());
1.2 Правила прототипа
1.2.1 Правила прототипа
- Все ссылочные типы (массивы, объекты, функции) имеют объектные характеристики, которые можно свободно расширять;
var arr = [];
arr.a = 1;
- Все ссылочные типы имеют
__proto__
свойство (неявный прототип), значением свойства является обычный объект; - Все функции имеют прототип (явный прототип), и значение свойства также является общим прототипом;
- все ссылочные типы (массивы, объекты, функции), неявный прототип которых указывает на явный прототип их конструктора;
(obj.__proto__ === Object.prototype)
; - При попытке получить атрибут объекта, если сам объект не имеет этого атрибута, он перейдет к своему
__proto__
(то есть прототип его конструктора) найти;
1.2.2 Объекты-прототипы
прототип. В js — одно из свойств объекта-функции: прототип объекта-прототипа. Обычные объекты не имеют свойства прототипа, но имеют__proto__
Атрибуты. Роль прототипа заключается в добавлении унифицированного метода к каждому объекту этого класса, а методы и свойства, определенные в прототипе, являются общими для всех объектов-экземпляров.
var person = function(name){
this.name = name
};
person.prototype.getName=function(){ // 通过person.prototype设置函数对象属性
return this.name;
}
var crazy= new person(‘crazyLee’);
crazy.getName(); // crazyLee//crazy继承上属性
1.2.3 Цепочка прототипов
При попытке получить атрибут объекта f, если сам объект не имеет этого атрибута, он перейдет к своему__proto__
(т.е. прототип его конструктора)obj.__proto__
искать; когдаobj.__proto__
когда нет такой вещи, какobj.__proto__.__proto__
(то есть прототип конструктора конструктора obj прототип конструктора конструктора);
2. экземпляр
Основной принцип реализации instanceof, вручную реализовать instanceof
function instance_of(L, R) {
//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) return false;
if (O === L)
// 当 O 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
}
3. Наследование
Несколько способов реализации наследования и их преимущества и недостатки
3.1 Наследование цепочки прототипов
Основная идея наследования цепочки прототипов состоит в том, чтобы использовать прототип, чтобы позволитьОдин ссылочный тип наследует свойства и методы другого ссылочного типа..
function SuperType() {
this.name = "yanxugong";
this.colors = ["pink", "blue", "green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType() {
this.age = 22;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function() {
return this.age;
};
SubType.prototype.constructor = SubType;
let instance1 = new SubType();
instance1.colors.push("yellow");
console.log(instance1.getName()); // 'yanxugong'
console.log(instance1.colors); // ["pink", "blue", "green", "yellow"]
let instance2 = new SubType();
console.log(instance2.colors); // ["pink", "blue", "green", "yellow"]
недостаток:
- Когда наследование достигается через прототип, прототип становится экземпляром другого типа, а исходное свойство экземпляра становится текущим свойством прототипа.Свойства ссылочного типа прототипа являются общими для всех экземпляров..
- При создании экземпляра подтипани за чтоне затрагивая все экземпляры объектаПередача параметров конструктору супертипа.
3.2 Заимствование конструкторов
Заимствуя технику конструкторов, основная идея состоит в том, чтобы вызвать конструктор супертипа в конструкторе подтипа.
function SuperType(name) {
this.name = name;
this.colors = ["pink", "blue", "green"];
this.getColors = function() {
return this.colors;
};
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name) {
SuperType.call(this, name);
this.age = 22;
}
let instance1 = new SubType("yanxugong");
instance1.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.getColors()); // ["pink", "blue", "green", "yellow"]
console.log(instancel.getName); // undefined
let instance2 = new SubType("Jack");
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.getColors()); // ["pink", "blue", "green"]
console.log(instance2.getName); // undefined
преимущество:
- Параметры могут быть переданы в суперкласс
- Исправлена проблема, из-за которой значения ссылочного типа, содержащиеся в прототипах, были общими для всех экземпляров.
недостаток:
- Методы определены в конструкторе, повторное использование функции невозможно, кроме того, методы, определенные в прототипе супертипа, невидимы для подтипа.
3.3 Композиционное наследование
Комбинаторное наследование относится к шаблону наследования, который сочетает в себе цепочку прототипов и заимствованную технологию конструктора, чтобы использовать их сильные стороны.
Основная идея:
Используйте цепочку прототипов для реализации наследования свойств и методов прототипа и реализации наследования свойств экземпляра путем заимствования конструкторов, что не только реализует повторное использование функций путем определения методов в прототипе, но и гарантирует, что каждый экземпляр имеет свои собственные свойства.
function SuperType(name) {
this.name = name;
this.colors = ["pink", "blue", "green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
return this.age;
};
let instancel = new SubType("yanxugong", 20);
instancel.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.sayAge()); // 20
console.log(instancel.getName()); // yanxugong
let instance2 = new SubType("Jack", 18);
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.sayAge()); // 18
console.log(instance2.getName()); // Jack
console.log(new SuperType("po"));
недостаток:
- В любом случае конструктор супертипа вызывается дважды: один раз при создании прототипа подтипа и один раз внутри конструктора подтипа.
преимущество:
- Параметры могут быть переданы в суперкласс
- Каждый экземпляр имеет свои свойства
- повторное использование функции
3.4 Наследование прототипов
Основная идея прототипного наследования:
Прототипы позволяют создавать новые объекты на основе существующих объектов без необходимости создавать для них пользовательские типы.
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
Внутри функции object() создается новый временный конструктор, затем переданный объект используется в качестве прототипа конструктора, и, наконец, возвращается новый экземпляр временного типа, который выполняет поверхностную копию объекта.
ECMAScript5 стандартизирует прототипное наследование с добавлением метода Object.create(). Этот метод принимает два параметра: объект для использования в качестве прототипа нового объекта и (необязательно) объект для определения дополнительных свойств для нового объекта (которые могут переопределять одноименные свойства объекта-прототипа). при передаче одного параметра методы Object.create() и object() ведут себя одинаково.
var person = {
name: "yanxugong",
hobbies: ["reading", "photography"],
};
var personl = Object.create(person);
personl.name = "jack";
personl.hobbies.push("coding");
var person2 = Object.create(person);
person2.name = "Echo";
person2.hobbies.push("running");
console.log(person.hobbies); // ["reading", "photography", "coding", "running"]
console.log(person.name); // yanxugong
console.log(personl.hobbies); // ["reading", "photography", "coding", "running"]
console.log(personl.name); // jack
console.log(person2.hobbies); // ["reading", "photography", "coding", "running"]
console.log(person2.name); // Echo
Прототипное наследование хорошо работает в ситуациях, когда нет необходимости создавать конструкторы, а нужно лишь сохранить один объект похожим на другой.
недостаток:
- Подобно наследованию цепочки прототипов, свойства, содержащие значения ссылочного типа, являются общими для всех экземпляров.
3.5 Паразитарное наследование
Паразитическая наследственность — это идея, тесно связанная с прототипической наследственностью. Идея паразитического наследования аналогична паразитическому конструктору и шаблону фабрики, т.е. создать функцию, которая просто инкапсулирует процесс наследования, который каким-то образом внутренне улучшает объект, и, наконец, действует так, как будто он действительно сделал всю работу. объект.
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() {
// 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
var person = {
name: "yanxugong",
hobbies: ["reading", "photography"],
};
var personl = createAnother(person);
personl.sayHi(); // hi
personl.hobbies.push("coding");
console.log(personl.hobbies); // ["reading", "photography", "coding"]
console.log(person); // {hobbies:["reading", "photography", "coding"],name: "yanxugong"}
На основе человека возвращается новый объект personl, который не только имеет все свойства и методы человека, но и имеет собственный метод sayHi(). Паразитическое наследование также является полезным шаблоном при рассмотрении объектов, а не пользовательских типов и конструкторов.
недостаток:
- Использование паразитного наследования для добавления функций к объектам неэффективно из-за невозможности повторного использования функций.
- Подобно наследованию цепочки прототипов, свойства, содержащие значения ссылочного типа, являются общими для всех экземпляров.
3.6 Паразитическая композиционная наследственность
Так называемое паразитическое наследование композиции означает наследование свойств путем заимствования конструкторов и наследования методов путем смешивания цепочек прототипов.
Основная идея:
Вместо того, чтобы вызывать конструктор супертипа для указания прототипа подтипа, все, что нам нужно, — это копия прототипа супертипа, по существу используя паразитное наследование для наследования прототипа супертипа и последующего присвоения результата прототипу прототипа подтипа. Основная модель паразитарного композиционного наследования выглядит следующим образом:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
- Создайте копию прототипа супертипа
- Добавьте свойство конструктора в созданную копию
- Назначить вновь созданный объект прототипу подтипа
На этом этапе мы можем заменить оператор, назначенный прототипу подтипа, вызовом inheritPrototype:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
function SuperType(name) {
this.name = name;
this.colors = ["pink", "blue", "green"];
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
return this.age;
};
let instancel = new SubType("yanxugong", 20);
instancel.colors.push("yellow");
console.log(instancel.colors); // ['pink','blue','green','yellow']
console.log(instancel.sayAge()); // 20
console.log(instancel.getName()); // yanxugong
let instance2 = new SubType("Jack", 18);
console.log(instance2.colors); // ['pink','blue','green']
console.log(instance2.sayAge()); // 18
console.log(instance2.getName()); // Jack
console.log(new SuperType("po"));
преимущество:
- Конструктор суперкласса вызывается только один раз, что более эффективно. Избегайте создания ненужных, избыточных свойств в SuberType.prototype, сохраняя цепочку прототипов нетронутой.
- Поэтому паразитное наследование композиции является наиболее рациональной парадигмой наследования для ссылочных типов.
Случай прототипического наследования
Назовите хотя бы один случай, когда прототипное наследование используется в проектах с открытым исходным кодом, таких как Node.
4.1 Vue.extend( options )
-
параметр:
{Object} options
-
Применение:
Используя базовый конструктор Vue, создайте «подкласс». Параметр — это объект, содержащий параметры компонента.
data
Вариант частный случай, нужно обратить внимание -Vue.extend()
в нем должна быть функция
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: "<p>{{firstName}} {{lastName}} aka {{alias}}</p>",
data: function() {
return {
firstName: "Walter",
lastName: "White",
alias: "Heisenberg",
};
},
});
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount("#mount-point");
Результат выглядит следующим образом:
<p>Walter White aka Heisenberg</p>
4.2 Зачем использовать расширение
В проекте vue после того, как у нас есть инициализированный корневой экземпляр, все страницы в основном управляются через маршрутизатор, а компоненты также управляются черезimport
Чтобы выполнить локальную регистрацию, поэтому нам не нужно уделять внимание созданию компонентов, по сравнению сextend
Будьте немного спокойнее. Но это имеет несколько недостатков:
- Шаблоны компонентов предопределены. Что, если я хочу динамически отображать компоненты из интерфейса?
- Весь контент находится в
#app
Рендеринг, зарегистрированные компоненты визуализируются в текущей позиции. Если бы я реализовал такую программу, какwindow.alert()
Что делать, если компонент подсказки просит вызвать его как функцию JS? В этот момент,Vue.extend + vm.$mount
Комбинация пригодится.
5. Новый оператор
Может подробно описать процесс создания нового объекта, вручную реализовать новый оператор
Давайте посмотрим, что делает новый оператор Какие операции? Подумайте над следующим кодом:
// 新建一个类(构造函数)
function Otaku(name, age) {
this.name = name;
this.age = age;
// 自身的属性
this.habit = "pk";
}
// 给类的原型上添加属性和方法
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function() {
console.log("I am " + this.name);
};
// 实例化一个person对象
const person = new Otaku("乔峰", 5000);
person.sayYourName(); // I am 乔峰
console.log(person); // 打印出构造出来的实例
5.1 Анализ
Из результатов, напечатанных на консоли, мы видим, что новый оператор, вероятно, делает несколько вещей:
- возвращает (производит) новый объект
- Доступ к свойствам в конструкторе класса Otaku
- Получите доступ к свойствам и методам прототипа Otaku и установите указатель this (указывающий на вновь сгенерированный объект экземпляра)
Благодаря приведенному выше анализу и отображению мы можем узнать, что в новой банде должно быть участие Объекта, иначе генерация объектов немного неясна. Начнем с написания:
// 需要返回一个对象 借助函数来实现new操作
// 传入需要的参数: 类 + 属性
const person = new Otaku("乔峰", 5000);
const person1 = objectFactory(Otaku, "鸠摩智", 5000);
// 开始来实现objectFactory 方法
function objectFactory(obj, name, age) {}
// 这种方法将自身写死了 如此他只能构造以obj为原型,并且只有name 和 age 属性的 obj
// 在js中 函数因为arguments 使得函数参数的写法异常灵活,在函数内部可以通过arguments来获得函数的参数
function objectFactory() {
console.log(arguements); //{ '0': [Function: Otaku], '1': '鸠摩智', '2': 5000 }
// 通过arguments类数组打印出的结果,我们可以看到其中包含了构造函数以及我们调用objectfactory时传入的其他参数
// 接下来就是要想如何得到其中这个构造函数和其他的参数
// 由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:
// 1. Array.from(arguments).shift(); //转换成数组 使用数组的方法shift将第一项弹出
// 2. [].shift().call(arguments); // 通过call() 让arguments能够借用shift方法
const Constructor = [].shift.call(arguments);
const args = arguments;
// 新建一个空对象 纯洁无邪
let obj = new Object();
// 接下来的想法 给obj这个新生对象的原型指向它的构造函数的原型
// 给构造函数传入属性,注意:构造函数的this属性
// 参数传进Constructor对obj的属性赋值,this要指向obj对象
// 在Coustructor内部手动指定函数执行时的this 使用call、apply实现
let result = Constructor.apply(obj, arguments);
//确保new出来的是一个对象
return typeof result === "object" ? result : obj;
}
В приведенном выше коде слишком много комментариев, и код после удаления комментария:
function objectFactory() {
let Constructor = [].shift.call(arguments);
const obj = new Object();
obj.__proto__ = Conctructor.prototype;
let result = Constructor.apply(obj, arguments);
return typeof result === "object" ? result : obj;
}
Есть еще одна операция:
function myNew(Obj, ...args) {
var obj = Object.create(Obj.prototype); // 使用指定的原型对象及其属性去创建一个新的对象
Obj.apply(obj, args); // 绑定 this 到obj, 设置 obj 的属性
return obj; // 返回实例
}
Шестое, построение классов и наследование
Понимать основные принципы реализации построения и наследования классов ES6.
6.1 Использование класса ES6
JavaScript использует прототипное наследование, мы можем реализовать наследование классов через характеристики прототипов. ES6 дает нам синтаксический сахар, подобный объектно-ориентированному наследованию.
class Parent {
constructor(a) {
this.filed1 = a;
}
filed2 = 2;
func1 = function() {};
}
class Child extends Parent {
constructor(a, b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function() {};
}
Ниже мы используемbabel
Давайте рассмотрим, как работают классы ES6 и наследование.
6.2 Реализация класса
Перед преобразованием:
class Parent {
constructor(a) {
this.filed1 = a;
}
filed2 = 2;
func1 = function() {};
}
После преобразования:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Parent = function Parent(a) {
_classCallCheck(this, Parent);
this.filed2 = 2;
this.func1 = function() {};
this.filed1 = a;
};
Видно, что нижний слой класса по-прежнему является конструктором:
- Вызовите метод _classCallCheck, чтобы определить, есть ли новое ключевое слово перед текущим вызовом функции.
Если новое ключевое слово используется до выполнения конструктора, внутри конструктора будет создан пустой объект, а конструктор
proptype
указывает на этот пустой объект__proto__
и укажите это на пустой объект. Как и выше, в _classCallCheck: этот instanceof Parent возвращает true.
Если перед конструктором нет нового, proptype конструктора не появится в цепочке прототипов this и вернет false.
-
Назначьте этому переменные и функции внутри класса.
-
Выполнение логики внутри конструктора.
-
вернуть это (конструктор по умолчанию в конце мы сделали).
6.3 Реализация наследования
Перед преобразованием:
class Child extends Parent {
constructor(a, b) {
super(a);
this.filed3 = b;
}
filed4 = 1;
func2 = function() {};
}
После преобразования:
Давайте сначала посмотрим на внутреннюю реализацию Child, а затем посмотрим, как реализована функция, вызываемая внутри:
var Child = (function(_Parent) {
_inherits(Child, _Parent);
function Child(a, b) {
_classCallCheck(this, Child);
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
);
_this.filed4 = 1;
_this.func2 = function() {};
_this.filed3 = b;
return _this;
}
return Child;
})(Parent);
- передача
_inherits
Функции наследуют proptype родительского класса.
_inherits
Внутренняя реализация:
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof superClass
);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true,
},
});
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
-
Проверьте родительский конструктор.
-
Типичное паразитное наследование: создайте пустой объект с типом свойства родительского конструктора и укажите этому объекту тип свойства дочернего конструктора.
-
Укажите родительский конструктор на дочерний конструктор
__proto__
(Не очень понятно, что делает этот шаг, и он не кажется значимым.)
-
Используйте замыкание, чтобы сохранить ссылку на родительский класс, и выполните логику построения подкласса внутри замыкания.
-
новый чек.
-
Вызовите конструктор родительского класса с текущим this.
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
);
здесьChild.proto || Object.getPrototypeOf(Child)
На самом деле это родительский конструктор (последняя операция _inherits), а затем он меняет вызывающую функцию на текущую через вызов this и передает параметры. (Здесь я чувствую, что Parent можно передать напрямую через параметры)
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return call && (typeof call === "object" || typeof call === "function")
? call
: self;
}
Проверьте, инициализировано ли это, вызывается ли супер, и верните это, которое было назначено родительским классом.
-
Назначьте этому переменные и функции внутри класса подкласса строки.
-
Выполните логику внутри конструктора подкласса.
Видно, что ES6 фактически предоставляет нам простой способ написания «комбинированного паразитического наследования».
6.4 super
super представляет конструктор родительского класса.
super.fun1()
ЭквивалентноParent.fun1()
илиParent.prototype.fun1()
.
super()
ЭквивалентноParent.prototype.construtor()
Когда мы не пишем конструктор подкласса:
var Child = (function(_Parent) {
_inherits(Child, _Parent);
function Child() {
_classCallCheck(this, Child);
return _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)
);
}
return Child;
})(Parent);
Видно, что конструктор по умолчанию будет активно вызывать конструктор родительского класса и по умолчанию будет текущим.constructor
Передаваемые параметры передаются родительскому классу.
Итак, когда мы объявляемconstructor
должен быть активно вызван послеsuper()
, иначе родительский конструктор не может быть вызван, и наследование не может быть завершено.
Типичный пример — компонент React, мы объявляемconstructor
должен вызываться послеsuper(props)
, потому что родительский класс должен выполнять некоторые операции инициализации свойств в конструкторе.
Глава 3 "Повторное изучение JavaScript" Область видимости и замыкания
1 Область применения
Понимание области действия JavaScript, цепочки областей видимости и внутренних компонентов
1.1 Область применения
javascript имеет хорошо продуманный набор правил для хранения переменных и последующего их легкого поиска, этот набор правил называетсяобъем.
Область действия — это среда выполнения кода, глобальная среда выполнения — это глобальная область, среда выполнения функции — это частная область, и все они представляют собой стековую память.
1.2 Цепочка областей применения
Когда код выполняется в среде, создается цепочка областей видимости (цепочка, образованная областью видимости) объекта переменных.Поскольку поиск переменных реализуется по цепочке областей видимости, он также называетсяцепочка прицеловМеханизм поиска переменных.
- Передний конец цепочки областей видимости всегда является переменным объектом среды, в которой находится исполняемый в данный момент код.
- Следующий объект в цепочке областей действия — из внешней среды, а следующий объект переменной — из следующей внешней среды, вплоть до глобальной среды выполнения.
- Переменный объект глобальной среды выполнения всегда является последним объектом в цепочке областей видимости.
Внутренняя среда может получить доступ ко всем внешним средам через цепочку областей видимости, но внешняя среда не может получить доступ ни к каким переменным и функциям внутренней среды.
1.3 Внутренние принципы
-
компилировать
Возьмите var a = 2; в качестве примера, чтобы проиллюстрировать внутренний процесс компиляции javascript, который в основном включает следующие три шага:
-
токенизация
Разложите строку символов на осмысленные блоки кода, называемые токенами.
var a = 2; разлагается на следующие лексические единицы: var, a, =, 2, ;. Эти токены образуют массив потоков токенов.
[ "var": "keyword", "a": "identifier", "=": "assignment", "2": "integer", ";": "eos" (end of statement) ]
-
разбор
Преобразование массива лексических единиц в дерево, представляющее структуру синтаксиса программы, состоящую из элементов, вложенных по одному уровню за раз, это дерево называется «Абстрактное синтаксическое дерево» (Abstract Syntax Tree, AST)
Абстрактное синтаксическое дерево var a = 2 имеет узел верхнего уровня с именем VariableDeclaration, за которым следует дочерний узел с именем Identifier (его значение равно a) и дочерний узел с именем AssignmentExpression, и этот узел имеет называемый Numericliteral (его значение 2) дочерний узел
{ operation: "=", left: { keyword: "var", right: "a" } right: "2" }
-
генерация кода
Процесс преобразования AST в исполняемый код называется генерацией кода.
Абстрактное синтаксическое дерево переменной a=2 преобразуется в набор машинных инструкций для создания переменной с именем a (включая выделение памяти и т. д.) и сохранения значения 2 в
На самом деле процесс компиляции движка javascript намного сложнее, включая множество операций по оптимизации, вышеприведенные три шага представляют собой базовый обзор процесса компиляции.
Любой фрагмент кода компилируется перед выполнением, и в большинстве случаев компиляция происходит за несколько микросекунд до выполнения кода. Компилятор javascript сначала компилирует программу var a=2;, затем подготавливает ее к выполнению и обычно сразу же выполняет.
-
-
воплощать в жизнь
Короче говоря, процесс компиляции — это процесс, в котором компилятор разбивает программу на единицы токенов, затем анализирует единицы токенов в синтаксическое дерево (AST), а затем превращает синтаксическое дерево в машинные инструкции, ожидающие выполнения.
По сути, код компилируется, но также и выполняется. Далее по-прежнему используется var a = 2; в качестве примера для подробного объяснения процесса компиляции и выполнения.
-
компилировать
-
Компилятор ищет область, чтобы увидеть, существует ли уже переменная с именем a в той же коллекции области. Если это так, компилятор игнорирует объявление и продолжает компиляцию; в противном случае он просит область объявить новую переменную в коллекции текущей области видимости с именем
-
Компилятор компилирует var a = 2, этот фрагмент кода в машинные инструкции для выполнения
Согласно принципу компиляции компилятора, повторяющиеся объявления в javascript допустимы.
// test在作用域中首次出现,所以声明新变量,并将20赋值给test var test = 20; // test在作用域中已经存在,直接使用,将20的赋值替换成30 var test = 30;
-
-
воплощать в жизнь
-
Среда выполнения движка сначала запросит область, чтобы увидеть, есть ли переменная с именем a в текущем наборе областей. Если это так, движок будет использовать эту переменную, если нет, движок продолжит поиск переменной.
-
Если механизм в конце концов находит переменную a, он присваивает ей значение 2. В противном случае движок выдаст исключение
-
-
-
Запрос
В первой операции, выполняемой движком, запрашивается переменная a, которая называется LHS-запросом. На самом деле существует два типа запросов к движку: LHS-запросы и RHS-запросы.
В буквальном смысле, когда переменная появляется в левой части операции присваивания, выполняется запрос LHS, а когда переменная появляется в правой части, выполняется запрос RHS.
Точнее, запросы RHS ничем не отличаются от простого нахождения значения переменной, тогда как запросы LHS пытаются найти сам контейнер переменной, чтобы ему можно было присвоить значение.
function foo(a) { console.log(a); // 2 } foo(2);
В этот код включены всего 4 запроса, а именно:
1, foo (...) вместо foo было указано RHS
2. Параметр функции a = 2 делает ссылку LHS на a
3. console.log(...) делает ссылку RHS на объект консоли и проверяет, есть ли у него метод журнала
4. console.log(a) делает ссылку RHS на a и передает полученное значение в console.log(...)
-
вложенный
Если переменная не может быть найдена в текущей области, механизм будет продолжать поиск во внешней вложенной области до тех пор, пока переменная не будет найдена или не будет достигнута самая внешняя область (то есть глобальная область).
function foo(a) { console.log(a + b); } var b = 2; foo(2); // 4
Ссылка на строку RHS не найдена; затем механизм ищет b в глобальной области видимости, после успешного нахождения делает на нее ссылку RHS и присваивает b значение 2.
-
аномальный
Почему важно различать LHS и RHS? Поскольку два запроса ведут себя по-разному, если переменная не была объявлена (переменная не может быть найдена ни в одной области)
-
RHS
- Если запрос RHS завершится ошибкой, движок выдаст исключение ReferenceError.
// 对b进行RHS查询时,无法找到该变量。也就是说,这是一个“未声明”的变量 function foo(a) { a = b; } foo(); // ReferenceError: b is not defined
- Если запрос RHS находит переменную, но пытается выполнить необоснованную операцию со значением переменной, например, вызов функции для значения нефункционального типа или ссылка на свойство в значении null или undefined, движок выбросит другой тип исключения: исключение TypeError (ошибка типа)
function foo() { var b = 0; b(); } foo(); // TypeError: b is not a function
-
LHS
- Когда механизм выполняет запрос LHS, если переменная не может быть найдена, глобальная область создает переменную с этим именем и возвращает ее механизму.
function foo() { a = 1; } foo(); console.log(a); // 1
- Если глобальная переменная не создается и не возвращается при сбое запроса LHS в строгом режиме, механизм выдает исключение ReferenceError, аналогичное случаю сбоя запроса RHS.
function foo() { "use strict"; a = 1; } foo(); console.log(a); // ReferenceError: a is not defined
-
-
принцип
function foo(a) { console.log(a); } foo(2);
Приведенный выше фрагмент кода используется для иллюстрации внутреннего принципа области действия, которая разделена на следующие этапы:
[1] Движку требуется ссылка RHS для функции foo(...), которая ищет foo в глобальной области видимости. успешно найден и выполнен
[2] Движок должен передать параметр a=2 функции foo, создать ссылку LHS для a и найти a в области видимости функции foo. Успешно найдено, и присвойте 2
[3] Движок должен выполнить console.log(...), создать ссылку RHS для объекта консоли и найти объект консоли в области видимости функции foo. Так как консоль является встроенным объектом, она успешно найдена
[4] Движок ищет метод log(...) в объекте консоли и успешно находит его
[5] Движок должен выполнить console.log(a), сделать ссылку RHS на a, найти a в области действия функции foo, успешно найти и выполнить его.
[6] Итак, движок передает значение a, равное 2, в console.log(...)
[7] Наконец, вывод консоли 2
2. Лексическая область действия и динамическая область действия
2.1 Лексическая область
Первый этап работы компилятора, называемый токенизацией, заключается в разложении строки символов на лексические единицы. Эта концепция имеет фундаментальное значение для понимания лексической области видимости.
Проще говоря, лексическая область видимости — это область, определенная на лексической фазе, которая определяется тем, где записываются области видимости переменных и блоков при написании кода, поэтому, когда лексический анализатор обрабатывает код, область видимости остается неизменной.
- связь
Где бы ни вызывалась функция и как бы она ни вызывалась, ее лексическая область видимости определяется только тем, где функция объявлена.
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2 4 12
В этом примере есть три вложенных области. Чтобы помочь понять, представьте их как несколько пузырей, содержащихся в стадиях.
Пузырьки области действия определяются тем, где написан соответствующий код блока области действия, они включаются уровень за уровнем.
Пузырь 1 содержит всю глобальную область видимости только с одним идентификатором: foo
Bubble 2 содержит область, созданную foo, с тремя идентификаторами: a, bar и b.
Пузырь 3 содержит область, созданную bar, которая имеет только один идентификатор: c
- найти
Структура пузырей области действия и их связь друг с другом предоставляют движку достаточно информации о местоположении, чтобы использовать эту информацию для поиска местоположения идентификатора.
Во фрагменте кода движок выполняет оператор console.log(...) и ищет ссылки на три переменные a, b и c. Сначала он смотрит в самую внутреннюю область, область действия функции bar(...). Движок не может найти здесь, поэтому он поднимается на один уровень до области действия вложенного foo(...) и продолжает искать его. a находится здесь, поэтому движок использует эту ссылку. То же верно и для б. А для c движок находит его в bar(...)
[Примечание] Поиск лексической области будет искать только идентификатор первого уровня. Если код ссылается на foo.bar.baz, поиск лексической области попытается найти только идентификатор foo. После нахождения этой переменной правила доступа к атрибутам объекта взять на себя бар соответственно.и доступ к свойству baz
foo = {
bar: {
baz: 1,
},
};
console.log(foo.bar.baz); // 1
- оттенок
Поиск области начинается с самой внутренней области во время выполнения и продвигается наружу или вверх, пока не встретится первый соответствующий идентификатор.
Идентификаторы с одинаковыми именами могут быть определены на нескольких уровнях вложенных областей, это называется «эффектом затенения», и внутренний идентификатор «затеняет» внешний идентификатор.
var a = 0;
function test() {
var a = 1;
console.log(a); // 1
}
test();
Глобальные переменные автоматически являются свойствами глобального объекта, поэтому к ним можно получить доступ не напрямую через лексическое имя глобального объекта, а косвенно через ссылку на свойства глобального объекта.
var a = 0;
function test() {
var a = 1;
console.log(window.a); //0
}
test();
Этот метод позволяет получить доступ к глобальным переменным, которые затенены переменными с тем же именем. Но если неглобальные переменные затенены, к ним все равно нельзя получить доступ
2.2 Динамическая область
JavaScript использует лексическую область видимости, и его наиболее важной особенностью является то, что процесс его определения происходит на этапе написания кода.
Так зачем вводить динамическую область видимости? На самом деле динамическая область видимости является двоюродной сестрой другого важного механизма в javascript, этого . Большая часть путаницы с областью действия связана с тем, что лексическая область действия смешивается с механизмом this, и это глупо неясно.
Динамическая область видимости не зависит от того, как и где объявляются функции и области видимости, а только откуда они вызываются. Другими словами, цепочка областей видимости основана на стеке вызовов, а не на вложенности областей в коде.
var a = 2;
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
bar();
[1] Если он находится в лексической области видимости, это текущая среда javascript. Переменная a сначала ищется в функции foo() и не найдена. Поэтому следуйте по цепочке областей действия к глобальной области видимости, чтобы найти и назначить ее 2. Итак, вывод консоли 2
[2] Аналогично, если она находится в динамической области видимости, переменная a сначала ищется в foo() и не найдена. Здесь он будет искать по стеку вызовов в том месте, где вызывается функция foo(), то есть функция bar(), найдет и присвоит ей значение 3. Итак, вывод консоли 3
Короче говоря, разница между двумя областями, лексическая область определяется во время определения, а динамическая область определяется во время выполнения.
3. Контекст выполнения
Понимание стека контекста выполнения JavaScript, вы можете использовать информацию о стеке, чтобы быстро найти проблему
3.1 Контекст выполнения
- Глобальный контекст выполнения: это контекст выполнения по умолчанию и самый простой. Код, которого нет ни в одной функции, находится в глобальном контексте выполнения. Он делает две вещи: 1. Создает глобальный объект, который в браузере является объектом окна. 2. Наведите указатель this на глобальный объект. В программе может существовать только один глобальный контекст выполнения.
- Контекст выполнения функции: каждый раз, когда вызывается функция, для этой функции создается новый контекст выполнения. Каждая функция имеет свой собственный контекст выполнения, но он создается только при вызове функции. В программе может существовать любое количество контекстов выполнения функций. Всякий раз, когда создается новый контекст выполнения, он выполняет серию шагов в определенном порядке, обсуждаемом далее в этой статье.
- Контекст выполнения функции Eval: код, работающий в функции eval, также получает свой собственный контекст выполнения, но, поскольку функция eval обычно не используется разработчиками Javascript, здесь она обсуждаться не будет.
3.2 Стек выполнения
Стек выполнения, на других языках программирования, также называемые стеком вызовов, со структурой Lifo (Last In, Fix Out) для всех сохраненных контекста выполнения, созданного во время выполнения кода.
Когда механизм JavaScript впервые читает ваш сценарий, он создает глобальный контекст выполнения и помещает его в текущий стек выполнения. Всякий раз, когда происходит вызов функции, механизм создает новый контекст выполнения для функции и помещает его на вершину текущего стека выполнения.
Движок запустит функцию, контекст выполнения которой находится на вершине стека выполнения.Когда функция будет завершена, ее соответствующий контекст выполнения будет извлечен из стека выполнения, а элемент управления контекстом будет перемещен в следующий контекст выполнения стека. текущий стек выполнения.
Давайте разберемся с этим на следующем примере кода:
let a = "Hello World!";
function first() {
console.log("Inside first function");
second();
console.log("Again inside first function");
}
function second() {
console.log("Inside second function");
}
first();
console.log("Inside Global Execution Context");
Когда приведенный выше код загружается в браузер, механизм JavaScript создает глобальный контекст выполнения и помещает его в текущий стек выполнения. при звонкеfirst()
функция, механизм JavaScript создает новый контекст выполнения для функции и помещает ее на вершину текущего стека выполнения.
когда вfirst()
вызов функцииsecond()
функция, механизм Javascript создает новый контекст выполнения для функции и помещает ее на вершину текущего стека выполнения. когда second()
После выполнения функции ее контекст выполнения извлекается из текущего стека выполнения, а элемент управления контекстом перемещается в следующий контекст выполнения текущего стека выполнения, то естьfirst()
Контекст выполнения функции.
когда first()
После выполнения функции ее контекст выполнения извлекается из текущего стека выполнения, а элемент управления контекстом перемещается в глобальный контекст выполнения. Как только весь код выполнен, механизм Javascript удаляет глобальный контекст выполнения из стека выполнения.
3.3 Как создается контекст выполнения
До сих пор мы видели, как механизм JavaScript управляет контекстом выполнения, теперь давайте разберемся, как механизм JavaScript создает контекст выполнения.
Контекст выполнения создается в два этапа:1) этап создания; 2) Этап исполнения
3.4 Фаза создания
Перед выполнением любого кода JavaScript контекст выполнения находится в фазе создания. Всего на этапе создания происходит три вещи:
- Конечно thisЗначение , также известное какThis Binding.
- Лексическая средакомпонент создан.
- Переменная средакомпонент создан.
Следовательно, контекст выполнения можно представить концептуально следующим образом:
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
This Binding:
В глобальном контексте выполненияthis
Значение глобального объекта в браузере,this
Значение указывает на объект окна.
В контексте выполнения функцииthis
Значение зависит от того, как вызывается функция. Если он вызывается со ссылкой на объект, тоthis
Значение устанавливается для этого объекта, в противном случаеthis
Значение устанавливается на глобальный объект или наundefined
(в строгом режиме). Например:
let person = {
name: "peter",
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
},
};
person.calcAge();
// 'this' 指向 'person', 因为 'calcAge' 是被 'person' 对象引用调用的。
let calculateAge = person.calcAge;
calculateAge();
// 'this' 指向全局 window 对象,因为没有给出任何对象引用
3.4.1 Лексическое окружение
Официальная документация ES6 определяет лексическое окружение как:
Лексическая среда — это тип спецификации, определяющий связь идентификаторов с конкретными переменными и функциями на основе лексически вложенной структуры кода ECMAScript. Лексическое окружение состоит из записи окружения и внешнего лексического окружения, которое может быть нулевой ссылкой.
Короче говоря, лексическая среда – этоСопоставление переменной идентификатора Структура. (здесь идентификаторпредставляет имя переменной/функции,Переменнаяявляется ссылкой на фактический объект (включая объекты функционального типа) или примитивные значения)
В лексическом окружении есть два компонента: (1)запись окружающей среды(2)Ссылка на внешнюю среду
- Экологические записиэто фактическое место, где хранятся объявления переменных и функций.
- Ссылка на внешнюю средуозначает, что он имеет доступ к своему внешнему лексическому окружению.
Существует два типа лексического окружения:
- Глобальная среда (в глобальном контексте выполнения) — это лексическая среда без внешней среды. Ссылка на внешнюю среду глобальной среды:null. Он имеет глобальный объект (объект окна) и связанные с ним методы и свойства (такие как методы массива) и любые определяемые пользователем глобальные переменные,
this
Значение указывает на этот глобальный объект. - Среда функций, в которой пользовательские переменные в функциях хранятся вЭкологические записи середина. Ссылка на внешнюю среду может быть глобальной средой или внешней функциональной средой, содержащей внутреннюю функцию.
Примечание: дляфункциональная средаС точки зрения,Экологические записитакже содержитarguments
Объект, содержащий сопоставление между индексами и аргументами, переданными в функцию, и аргументами, переданными в функцию.длина (количество). Например, функция нижеarguments
Объект выглядит так:
function foo(a, b) {
var c = a + b;
}
foo(2, 3);
// arguments 对象
Arguments: {0: 2, 1: 3, length: 2},
Также существует два типа записей среды (показаны ниже):
- Декларативная запись средыХраните переменные, функции и параметры. Функциональная среда содержит записи декларативной среды.
- запись среды объектаИспользуется для определения ассоциации переменных и функций, которые появляются в глобальном контексте выполнения. Глобальная среда содержит записи объектной среды.
Абстрактно говоря, лексическое окружение в псевдокоде выглядит так:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
outer: <null>
}
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
outer: <Global or outer function environment reference>
}
}
}
3.4.2 Переменная среда:
Это также лексическая среда,EnvironmentRecord
содержит поVariableStatementsПривязка, созданная в этом контексте выполнения.
Как упоминалось выше, переменное окружение также является лексическим окружением, поэтому оно обладает всеми свойствами лексического окружения, определенными выше.
В ES6,LexicalEnvironmentкомпоненты иVariableEnvironmentРазница между компонентами заключается в том, что первый используется для хранения объявлений функций и переменных (let
а также const
) привязки, в то время как последние используются только для хранения переменных ( )var
) привязка.
Давайте объединим несколько примеров кода, чтобы понять вышеприведенные концепции:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
Контекст выполнения выглядит так:
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
Примечание: только когда встречается функцияmultiply
Контекст выполнения функции создается при вызове .
Вы могли заметитьlet
а также const
Определенная переменная не имеет никакого связанного с ней значения, ноvar
Определенная переменная устанавливается вundefined
.
Это связано с тем, что на этапе создания код сканируется и анализируется на наличие объявлений переменных и функций, где объявления функций хранятся в среде, а для переменных устанавливаются значенияundefined
(существует var
в случае ) или оставаться неинициализированным (в случаеlet
а также const
на случай, если).
Вот почему вы можете получить доступ до объявленияvar
переменная, определенная (хотя онаundefined
), но если вы обращаетесь до объявленияlet
а также const
Определенная переменная подскажет причину ошибки ссылки.
Это то, что мы называем подъемом переменных.
3.5 Фаза выполнения
Это самая легкая часть всей статьи. На этом этапе выполняются присваивания всем переменным и, наконец, выполняется код.
Примечание. На этапе выполнения, если механизм Javascript не может найти фактическое местоположение, указанное в исходном кодеlet
значение переменной, то ей будет присвоеноundefined
стоимость.
3.6 Обрезка стеков ошибок
Node.js поддерживает только эту функцию, которая реализована через Error.captureStackTrace, который принимает объект в качестве первого параметра и необязательную функцию в качестве второго параметра. Ее функция заключается в захвате текущего стека вызовов и его обрезке.Захваченный стек вызовов будет записан в атрибут стека первого параметра.Опорной точкой отсечения является второй параметр, то есть предыдущий вызов этой функции. записываться в стек вызовов, но не после.
Давайте используем код, чтобы объяснить, во-первых, захватить текущий стек вызовов и поместить его в myobj:
const myObj = {};
function c() {}
function b() {
// 把当前调用栈写到 myObj 上
Error.captureStackTrace(myObj);
c();
}
function a() {
b();
}
// 调用函数 a
a();
// 打印 myObj.stack
console.log(myObj.stack);
// 输出会是这样
// at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
// at a (repl:2:1)
// at repl:1:1 <-- Node internals below this line
// at realRunInThisContextScript (vm.js:22:35)
// at sigintHandlersWrap (vm.js:98:12)
// at ContextifyScript.Script.runInThisContext (vm.js:24:12)
// at REPLServer.defaultEval (repl.js:313:29)
// at bound (domain.js:280:14)
// at REPLServer.runBound [as eval] (domain.js:293:12)
// at REPLServer.onLine (repl.js:513:10)
В приведенном выше стеке вызовов есть только a -> b, потому что мы захватили стек вызовов до того, как b вызвал c. Теперь немного измените приведенный выше код и посмотрите, что произойдет:
const myObj = {};
function d() {
// 我们把当前调用栈存储到 myObj 上,但是会去掉 b 和 b 之后的部分
Error.captureStackTrace(myObj, b);
}
function c() {
d();
}
function b() {
c();
}
function a() {
b();
}
// 执行代码
a();
// 打印 myObj.stack
console.log(myObj.stack);
// 输出如下
// at a (repl:2:1) <-- As you can see here we only get frames before b was called
// at repl:1:1 <-- Node internals below this line
// at realRunInThisContextScript (vm.js:22:35)
// at sigintHandlersWrap (vm.js:98:12)
// at ContextifyScript.Script.runInThisContext (vm.js:24:12)
// at REPLServer.defaultEval (repl.js:313:29)
// at bound (domain.js:280:14)
// at REPLServer.runBound [as eval] (domain.js:293:12)
// at REPLServer.onLine (repl.js:513:10)
// at emitOne (events.js:101:20)
В этом коде, поскольку мы передаем b при вызове Error.captureStackTrace, стек вызовов после b будет скрыт.
Теперь вы можете спросить, какой смысл знать это? Вы можете попробовать этот трюк, если хотите скрыть от пользователя стек ошибок, не связанный с его бизнесом (например, внутреннюю реализацию библиотеки).
3.7 Отладка ошибок
3.7.1 Объект ошибки и обработка ошибок
Когда программа запускается с ошибкой, обычно генерируется объект Error.Объект Error можно использовать в качестве прототипа для наследуемых пользователем объектов ошибок.
Объект Error.Prototype содержит следующие свойства:
конструктор — указатель на конструктор экземпляра
сообщение - сообщение об ошибке
имя - неправильное имя (тип)
Выше приведены стандартные свойства Error.prototype, кроме того, разные среды выполнения имеют свои специфические свойства, например, Node, Firefox, Chrome, Edge, IE 10+, Opera и Safari 6+.
В такой среде объект Error имеет свойство стека, которое содержит трассировку стека ошибки.Трассировка стека экземпляра ошибки содержит все структуры стека, начиная с конструктора.
3.7.2 Как просмотреть стек вызовов
Просто посмотрите на стек вызовов: console.trace
a();
function a() {
b();
}
function b() {
c();
}
function c() {
let aa = 1;
}
console.trace();
3.7.3 Форма точки останова отладчика
В-четвертых, это
Принцип этого и значение нескольких различных сценариев использования
4.1 Вызов как метод объекта
В JavaScript функции также являются объектами, поэтому функции могут использоваться как свойства объекта. В этом случае функция называется методом объекта. При использовании этого вызывающего метода он естественным образом привязывается к объекту.
var test = {
a: 0,
b: 0,
get: function() {
return this.a;
},
};
4.2 Как вызов функции
Функции также можно вызывать напрямую, и в этом случае это привязывается к глобальному объекту. В браузере окно является глобальным объектом. Например, следующий пример: при вызове функции this привязывается к глобальному объекту,
Далее выполняется оператор присваивания, что эквивалентно неявному объявлению глобальной переменной, что явно не то, что нужно вызывающему.
function makeNoSense(x) {
this.x = x;
}
4.3 Вызов в качестве конструктора
JavaScript поддерживает объектно-ориентированное программирование.В отличие от основных языков объектно-ориентированного программирования, JavaScript не имеет концепции класса, а использует метод наследования на основе прототипов.
Соответственно, конструкторы в JavaScript тоже особенные, если они не вызываются с new, то они такие же, как и обычные функции. Еще одно эмпирическое правило: конструкторы начинаются с заглавной буквы.
Напомните звонящему, чтобы он использовал правильный способ вызова. При правильном вызове это привязывается к вновь созданному объекту.
function Point(x, y) {
this.x = x;
this.y = y;
}
4.4 Вызов в вызов или подать заявку, привязать
Еще раз повторим, что в JavaScript функции — это объекты, у объектов есть методы, а применения и вызовы — это методы объектов-функций.
Эти два метода чрезвычайно мощные, они позволяют переключать контекст, в котором выполняется функция, то есть объект, к которому this привязан.
Многие трюки и библиотеки JavaScript используют этот метод. Давайте рассмотрим конкретный пример:
function Point(x, y) {
this.x = x;
this.y = y;
this.moveTo = function(x, y) {
this.x = x;
this.y = y;
};
}
var p1 = new Point(0, 0);
var p2 = { x: 0, y: 0 };
p1.moveTo(1, 1);
p1.moveTo.apply(p2, [10, 10]);
5. Закрытие
Принцип реализации и функции замыканий можно перечислить как несколько практических применений замыканий в разработке.
5.1 Концепция замыканий
- Относится к функции, которая имеет доступ к переменной в области действия другой функции, обычно функции, содержащей другую функцию.
5.2 Роль замыканий
- Доступ к внутренним переменным функции, сохраняющий функцию в среде, не будет обрабатываться механизмом сборки мусора.
Поскольку переменные, объявленные внутри функции, являются локальными и доступны только внутри функции, а переменные вне функции видны внутри функции, что является характеристикой цепочки областей видимости.
Ребенок может искать переменную у родителя, искать уровень за уровнем, пока не будет найден
Поэтому мы можем создать еще одну функцию внутри функции, чтобы для внутренней функции были видны переменные внешней функции, и тогда мы можем получить доступ к его переменным.
function bar() {
//外层函数声明的变量
var value = 1;
function foo() {
console.log(value);
}
return foo();
}
var bar2 = bar;
//实际上bar()函数并没有因为执行完就被垃圾回收机制处理掉
//这就是闭包的作用,调用bar()函数,就会执行里面的foo函数,foo这时就会访问到外层的变量
bar2();
foo() содержит закрытие для области внутри bar(), так что область всегда может жить и не может быть удалена механизмом сборки мусора, что и делает замыкание, чтобы foo() могла ссылаться на нее в любое время.
Преимущества закрытия 5.3
- Облегчает локальные переменные, объявленные в контексте вызова
- Логика жесткая, и функцию можно создать в функции, чтобы избежать проблемы с передачей параметров.
5.4 Недостатки закрытия
- Благодаря использованию замыканий функция может храниться в памяти, не уничтожаясь после выполнения, а если используется большое количество замыканий, это вызовет утечку памяти и потребует много памяти.
5.5 Применение замыканий на практике
function addFn(a, b) {
return function() {
console.log(a + "+" + b);
};
}
var test = addFn(a, b);
setTimeout(test, 3000);
Как правило, первый параметр setTimeout — это функция, но она не может передавать значение. Если вы хотите передать значение, вы можете вызвать функцию, чтобы вернуть вызов внутренней функции, и передать вызов внутренней функции в setTimeout. Параметры, необходимые для выполнения внутренней функции, передаются ему внешней функцией, а доступ к внешней функции также можно получить в функции setTimeout.
6. Переполнение стека и утечки памяти
Понять, как работают переполнения стека и утечки памяти и как их предотвратить
6.1 Утечка памяти
- Запрошенная память не очищается и не уничтожается вовремя после выполнения, занимая свободную память, и если утечек памяти слишком много, последующие программы не смогут претендовать на память. Таким образом, утечка памяти вызовет переполнение внутренней памяти.
6.2 Переполнение стека
- Место в памяти выделено, а доступной памяти недостаточно
6.3 Отметить и развернуть
В некоторых программах для программирования, таких как язык C, вам нужно использовать malloc, чтобы запросить место в памяти, а затем использовать бесплатно, чтобы освободить его, вам нужно очистить его вручную. js имеет собственный механизм сборки мусора, и наиболее часто используемый метод сборки мусора — пометить и очистить.
Метод пометки и очистки: после того, как переменная входит в среду выполнения, добавьте к ней метку: войдите в среду, переменные, входящие в среду, не будут выпущены, потому что они могут использоваться до тех пор, пока поток выполнения входит в соответствующую среду. . Когда переменная покидает среду, она помечается как «выходящая из среды».
6.4 Распространенные причины утечек памяти
- Утечка памяти из-за глобальных переменных
- Закрытие
- Таймер не очищен
6.5 Решение
- Уменьшите ненужные глобальные переменные
- Уменьшите использование замыканий (поскольку замыкания могут вызвать утечку памяти)
- Избегайте бесконечных циклов
Семь, как бороться с асинхронной работой цикла
7.1 Использование самовыполняющихся функций
1. Когда в цикле используется самовыполняющаяся функция, она запускается после завершения цикла. Например, если вы определяете массив вне самовыполняющейся функции и добавляете содержимое в массив в самовыполняющейся функции, при выводе за пределы самовыполняющейся функции вы обнаружите, что в массиве ничего нет. это потому, что самовыполняющаяся функция будет зацикливаться. Она будет выполнена после запуска.
2. При использовании самовыполняющейся функции в цикле, если ajax вложен в самовыполняющуюся функцию, то индекс i в цикле не будет передан в ajax, а индекс i нужно присвоить переменная вне ajax, в поле Вы можете вызвать эту переменную непосредственно в ajax.
пример:
$.ajax({
type: "GET",
dataType: "json",
url: "***",
success: function(data) {
//console.log(data);
for (var i = 0; i < data.length; i++) {
(function(i, abbreviation) {
$.ajax({
type: "GET",
url: "/api/faults?abbreviation=" + encodeURI(abbreviation),
dataType: "json",
success: function(result) {
//获取数据后做的事情
},
});
})(i, data[i].abbreviation);
}
},
});
7.2 Использование рекурсивных функций
Так называемая рекурсивная функция заключается в вызове этой функции в теле функции. Будьте осторожны при использовании рекурсивных функций, так как неправильное обращение приведет к бесконечному циклу.
const asyncDeal = (i) = > {
if (i < 3) {
$.get('/api/changeParts/change_part_standard?part=' + data[i].change_part_name, function(res) {
//获取数据后做的事情
i++;
asyncDeal(i);
})
} else {
//异步完成后做的事情
}
};
asyncDeal(0);
7.3 Использование асинхронного/ожидания
- асинхронные/ожидающие функции
async/await более семантичен, async — это сокращение от «асинхронный», асинхронная функция используется для объявления того, что функция является асинхронной; await может рассматриваться как сокращение для асинхронного ожидания, используемого для ожидания завершения выполнения асинхронного метода. ;
async/await — решение асинхронных проблем с синхронным мышлением (код будет продолжать выполняться после того, как выйдет результат)
Традиционное вложенное вложение обратного вызова может быть заменено синхронным написанием нескольких слоев асинковых функций
- синтаксис асинхронной функции
Автоматически преобразовывать обычные функции в Promise, а возвращаемое значение также является объектом Promise.
Функция обратного вызова, указанная методом then, будет выполнена только после выполнения асинхронной операции внутри асинхронной функции.
Вы можете использовать await внутри асинхронных функций
- синтаксис ожидания
await помещается перед вызовом Promise, await заставляет код ждать, пока объект Promise не разрешится, а значение разрешения получается как результат выражения await
await только в рамках использования асинхронной функции, поскольку использование в целом будет работать с ошибкой
const asyncFunc = function(i) {
return new Promise(function(resolve) {
$.get(url, function(res) {
resolve(res);
});
});
};
const asyncDeal = async function() {
for (let i = 0; i < data.length; i++) {
let res = await asyncFunc(i);
//获取数据后做的事情
}
};
asyncDeal();
Восьмерка, модульная
Понять практические проблемы, решаемые модульностью, вы можете перечислить несколько решений модульности и понять принципы
8.1 Спецификация CommonJS (синхронная загрузка модулей)
Разрешить модулям синхронно загружать другие модули, от которых они зависят, с помощью метода require, а затем экспортировать интерфейсы, которые необходимо предоставить, с помощью exports или module.exports.
Как использовать:
// 导入
require("module");
require("../app.js");
// 导出
exports.getStoreInfo = function() {};
module.exports = someValue;
преимущество:
- Простой и удобный в использовании
- Модули на стороне сервера легко использовать повторно
недостаток:
- Синхронный метод загрузки не подходит для использования в среде браузера. Синхронный означает блокировку загрузки, а ресурсы браузера загружаются асинхронно.
- Невозможно загрузить несколько модулей параллельно без блокировки
Почему браузер не может использовать синхронную загрузку, а сервер может?
- Поскольку модули размещаются на стороне сервера, для стороны сервера, когда модуль загружается
- Со стороны браузера, так как модули размещаются на стороне сервера, время загрузки зависит от быстрой медлительности скорости сети, если нужно долго ждать, все приложение будет заблокировано.
- Следовательно, конечный модуль браузера не может быть "синхронной загрузкой" (Commonjs) и может быть только "асинхронной загрузкой" (AMD).
Обратитесь к модулям CommonJs, представляющим модульную систему node.js.
8,2 AMD (модуль асинхронной нагрузки)
Модуль загружается асинхронно, и загрузка модуля не влияет на работу следующих операторов. Все операторы, зависящие от модуля, определены в функции обратного вызова, и функция обратного вызова не будет выполняться до тех пор, пока загрузка не будет завершена.
Пример использования:
// 定义
define("module", ["dep1", "dep2"], function(d1, d2) {...});
// 加载模块
require(["module", "../app"], function(module, app) {...});
Загрузка модуля require([module], callback); Первый параметр [module] — это массив, членами которого являются загружаемые модули; второй параметр callback — функция обратного вызова после успешной загрузки.
преимущество:
- Подходит для асинхронной загрузки модулей в среде браузера.
- Несколько модулей могут быть загружены параллельно
недостаток:
- Стоимость разработки увеличивается, код сложно читать и писать, а семантика метода определения модуля не гладкая.
- Не соответствует общему модульному мышлению и является компромиссной реализацией
Реализация спецификации AMD расшифровывается как require.js.
Отношение RequireJS к модулям — предварительное выполнение. Поскольку RequireJS является реализацией спецификации AMD, все зависимые модули выполняются первыми, то есть RequireJS выполняет зависимые модули заранее, что эквивалентно продвижению запроса.
Поток выполнения RequireJS:
- Функция require проверяет зависимые модули и получает фактический путь к файлу js в соответствии с файлом конфигурации.
- В соответствии с фактическим путем файла js вставьте узел сценария в dom и привяжите событие onload, чтобы получить уведомление о загрузке модуля.
- После загрузки всех зависимых скриптов вызовите функцию обратного вызова
8.3 Спецификация CMD (модули асинхронной загрузки)
Спецификация CMD аналогична спецификации AMD, проста и обеспечивает отличную совместимость со спецификацией модулей CommonJS и Node.js; в спецификации CMD модуль представляет собой файл.
Модуль определения использует глобальную функцию define, которая получает параметр фабрики.Фабрика может быть функцией, объектом или строкой;
factory — это функция с тремя параметрами: function(require, exports, module):
- require — это метод, который принимает идентификатор модуля в качестве единственного параметра для получения интерфейса, предоставляемого другими модулями: require(id)
- exports — это объект, используемый для предоставления интерфейсов модуля внешнему миру.
- модуль — это объект, в котором хранятся некоторые свойства и методы, связанные с текущим модулем.
Пример:
define(function(require, exports, module) {
var a = require("./a");
a.doSomething();
// 依赖就近书写,什么时候用到什么时候引入
var b = require("./b");
b.doSomething();
});
преимущество:
- Зависимость рядом, отложенное выполнение
- Может быть легко запущен в Node.js
недостаток:
- Зависит от упаковки SPM, и логика загрузки модулей необъективна
- Реализуйте репрезентативную библиотеку sea.js: отношение SeaJS к модулям — ленивое выполнение, и SeaJS будет выполнять модуль только тогда, когда он действительно нужен (в зависимости от) модуля.
8.4 Различия между AMD и CMD
- Для зависимых модулей AMD — раннее выполнение, CMD — отложенное выполнение. Однако, начиная с RequireJS 2.0, он также был изменен для задержки выполнения (в зависимости от метода написания метод обработки отличается). CMD продвигает максимально ленивых.
- AMD учитывает предварительную зависимость; CMD учитывает близость зависимости и требует ее только при использовании модуля.
// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
});
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b')
// 依赖可以就近书写
b.doSomething()
// ...
});
8.5 UMD
- UMD — это слияние AMD и CommonJS.
- AMD разрабатывает модули асинхронной загрузки на основе основных принципов браузера.
- Модули CommonJS разрабатываются в соответствии с первым принципом сервера, выбирая синхронную загрузку, и его модули не нужно упаковывать.
- UMD сначала оценивает, существует ли модуль (экспорт), который поддерживает Node.js, и использует режим модуля Node.js, если он существует; при оценке того, поддерживает ли он AMD (определить существование), он использует метод AMD для загрузки модуля, если он существует.
(function(window, factory) {
if (typeof exports === "object") {
module.exports = factory();
} else if (typeof define === "function" && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function() {
//module ...
});
8.6 Модульность ES6
- На уровне языковых стандартов ES6 реализует модульную функцию, и реализация достаточно проста, она может полностью заменить спецификации CommonJS и AMD и стать общим модульным решением для браузеров и серверов.
- Идея дизайна модуля ES6: постарайтесь быть как можно более статичным, чтобы зависимости модуля и переменные ввода и вывода можно было определить во время компиляции (модули CommonJS и AMD могут определять эти вещи только во время выполнения).
Как использовать:
// 导入
import "/app";
import React from “react”;
import { Component } from “react”;
// 导出
export function multiply() {...};
export var year = 2018;
export default ...
...
преимущество:
- Простой статический анализ
- Перспективный стандарт EcmaScript недостаток:
- Нативные браузеры еще не реализовали этот стандарт
- Новое командное слово поддерживается только новой версией Node.js.
8.7 Вернемся к вопросу "Разница между require и import"
require используется со спецификацией CommonJs, а import используется со спецификацией модуля Es6, поэтому разница между ними, по сути, является разницей между двумя спецификациями;
CommonJS:
- Для базовых типов данных это репликация. То есть он будет кешироваться модулем, при этом переменные, выводимые модулем, могут быть переназначены в другом модуле.
- Для сложных типов данных это поверхностная копия. Поскольку объекты, на которые ссылаются два модуля, указывают на одно и то же пространство памяти, изменения значения этого модуля повлияют на другой модуль.
- Когда модуль загружается с помощью команды require, запускается весь код модуля.
- Когда тот же модуль загружается с помощью команды require, модуль не будет выполнен, но будет получено значение из кеша. То есть независимо от того, сколько раз загружается модуль CommonJS, он будет запускаться только один раз при первой загрузке, а при последующей загрузке он вернет результат первого запуска, если только система не кеш очищается вручную.
- Когда цикл загружается, он относится к выполнению во время загрузки. То есть, когда потребуется код скрипта, он весь будет выполняться. Как только модуль "циклически загружается", будет выводиться только исполняемая часть, а неисполненная часть не будет выводиться.
Модули ES6
- Значения в модулях ES6 — это [динамические ссылки только для чтения].
- Только для чтения, то есть не разрешается изменять значение импортируемой переменной.Импортированная переменная доступна только для чтения, будь то базовый тип данных или сложный тип данных. Когда модуль встречает команду импорта, создается ссылка только для чтения. Когда скрипт действительно выполняется, в соответствии с этой ссылкой только для чтения перейдите к загруженному модулю, чтобы получить значение.
- Для динамического, когда исходное значение изменяется, значение, загруженное импортом, также изменяется. Будь то базовый тип данных или сложный тип данных.
- Модули ES6 динамически ссылаются при циклической загрузке. Пока есть некоторая ссылка между двумя модулями, код сможет выполняться.
Наконец: require/exports необходим и необходим, потому что на самом деле написанный вами import/export в конечном итоге компилируется в require/exports для выполнения.