введение
JSПредварительно в серию запланировано 27 статей, от основ до прототипов, асинхронности, шаблонов проектирования, архитектурных шаблонов и т. д. Это первая статья: краткое изложение var, let, const, деструктуризации, расширения и функций.
letво многом сvarпохоже, ноletЭто может помочь вам избежать некоторых распространенных проблем в JavaScript.constправдаletУсовершенствование для предотвращения переназначения переменной.
один,varутверждение
Мы всегда проходили черезvarКлючевые слова определяют переменные JavaScript.
var num = 1;
определяетnumзначение1Переменные.
Мы также можем определить переменные внутри функций:
function f() {
var message = "Hello, An!";
return message;
}
И мы также можем получить доступ к той же переменной внутри других функций.
function f() {
var num = 10;
return function g() {
var b = num + 1;
return b;
}
}
var g = f();
g(); // 11;
В приведенном выше примереgможет быть полученfопределяется в функцииnumПеременная. в любое времяgПри вызове он имеет доступ кfвнутреннийnumПеременная. даже когдаgсуществуетfВызывается после того, как он был выполнен, он все еще может быть доступен и измененnum.
function f() {
var num = 1;
num = 2;
var b = g();
num = 3;
return b;
function g() {
return num;
}
}
f(); // 2
правила области видимости
Для тех, кто знаком с другими языками,varОбъявите какие-то странные правила области видимости. См. пример ниже:
function f(init) {
if (init) {
var x = 10;
}
return x;
}
f(true); // 10
f(false); // undefined
В этом примере переменнаяxопределяется вifвнутри оператора, но мы можем получить к нему доступ вне оператора.
Это потому чтоvarДоступ к объявлению можно получить в любом месте внутри содержащей его функции, модуля, пространства имен или глобальной области видимости, и содержащий его блок кода не оказывает на него никакого влияния. некоторые люди называют этоvarобъем или объем функции. Параметры функции также используют область действия функции.
Эти правила области действия могут вызвать некоторые ошибки. Один из них заключается в том, что многократное объявление одной и той же переменной не приводит к ошибке:
function sumArr(arrList) {
var sum = 0;
for (var i = 0; i < arrList.length; i++) {
var arr = arrList[i];
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
return sum;
}
Здесь легко увидеть некоторые проблемы, внутренний слойforЦикл перезаписывает переменнуюi, потому что всеiОба относятся к переменным в пределах одной и той же области действия функции. Опытные разработчики прекрасно понимают, что эти проблемы могут быть пропущены во время проверки кода и приведут к бесконечным неприятностям.
Захват переменных странностей
Быстро подумайте о том, что вернет следующий код:
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
представлять,setTimeoutФункция выполняется с задержкой в несколько миллисекунд (ожидание завершения выполнения другого кода).
Что ж, взгляните на результаты:
10
10
10
10
10
10
10
10
10
10
Многие программисты JavaScript уже знакомы с этим поведением, но если вы запутались, вы не одиноки. Большинство людей ожидают, что вывод будет выглядеть так:
0
1
2
3
4
5
6
7
8
9
Помните переменную захвата, о которой мы упоминали выше?
мы переходим к
setTimeoutКаждое функциональное выражение фактически относится к одной и той же функции в той же области видимости.i.
Давайте на минутку задумаемся, почему это так.setTimeoutВыполняет функцию через несколько миллисекунд и находится вforПосле окончания цикла.forПосле того, как петля закончилась,iценность10. Итак, когда функция вызывается, она печатает10!
Распространенным обходным путем является использование выражения немедленно выполняемой функции (IIFE) для захвата каждой итерации.iЗначение:
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
Мы привыкли к этой странной форме. параметрiбудет охватыватьforв петлеi, но поскольку у нас одинаковое имя, нам не нужно менятьforкод внутри тела цикла.
два,letутверждение
теперь ты знаешьvarЕсть некоторые проблемы, именно поэтомуletоператор для объявления переменной. Помимо разных имён,letа такжеvarНаписание последовательное.
let hello = "Hello,An!";
Основное отличие не в синтаксисе, а в семантике, которую мы рассмотрим далее.
область действия блока
при использованииletОбъявите переменную, она использует лексическую область или область блокировки. Вместо того, чтобы использоватьvarобъявленные переменные могут быть доступны вне содержащей их функции, поскольку переменные в области блока доступны внутри содержащего блока илиforОн недоступен вне цикла.
function f(input) {
let a = 100;
if (input) {
// a 被正常引用
let b = a + 1;
return b;
}
return b;
}
Здесь мы определяем 2 переменныеaа такжеb.aОбласть примененияfтело функции, в то время какbОбласть примененияifв блоке операторов.
существуетcatchПеременные, объявленные в операторах, имеют те же правила области видимости.
try {
throw "oh no!";
}
catch (e) {
console.log("Oh well.");
}
// Error: 'e' doesn't exist here
console.log(e);
Другая особенность переменных с блочной областью видимости заключается в том, что они не могут быть прочитаны или записаны до тех пор, пока они не будут объявлены. Хотя эти переменные всегда «существуют» в своей области видимости, область до кода, в котором она объявлена, принадлежитВременная мертвая зона. это просто используется, чтобы показать, что мы не можемletДоступ к ним перед утверждением:
a++;
// Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
Обратите внимание, что мы все еще можем получить блочную переменную до ее объявления. Просто мы не можем вызвать эту функцию до объявления переменной.
function foo() {
return a;
}
// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();
// Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
Дополнительную информацию о временных мертвых зонах см. здесь.Mozilla Developer Network.
Переопределить и заблокировать
Мы упоминали об использованииvarКогда вы объявляете это, не имеет значения, сколько раз вы это объявляете, вы получаете только 1.
function f(x) {
var x;
var x;
if (true) {
var x;
}
}
В приведенном выше примере всеxДекларации фактически относятся к одному и тому жеx, и это вполне допустимый код. Это часто становится источником ошибок. Хорошо то,letЗаявления не будут такими распущенными.
let x = 10;
let x = 20;
// Uncaught SyntaxError: Identifier 'x' has already been declared
Не требуется, чтобы оба объявления были блочными, чтобы выдавать ложное предупреждение.
function f(x) {
let x = 100;
// Uncaught SyntaxError: Identifier 'x' has already been declared
}
function g() {
let x = 100;
var x = 100;
// Uncaught SyntaxError: Identifier 'x' has already been declared
}
Это не означает, что переменные с областью видимости блока нельзя объявлять с переменными области видимости функции. Вместо этого переменные блочной области необходимо объявлять в отдельных блоках.
function f(condition, x) {
if (condition) {
let x = 100;
return x;
}
return x;
}
f(false, 0); // 0
f(true, 0); // 100
Действие по введению нового имени во вложенную область действия называетсящит. Это палка о двух концах, она может случайно создать новые проблемы, и в то же время она может исправить некоторые ошибки. Например, предположим, что теперь мы используемletпереписать предыдущийsumArrфункция.
function sumArr(arrList) {
let sum = 0;
for (let i = 0; i < arrList.length; i++) {
var arr = arrList[i];
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
return sum;
}
На этом этапе вы получите правильный результат, потому что внутренний циклiМожет блокировать внешний циклi.
Как правило, маскирования следует избегать, потому что нам нужно писать чистый код. В то же время есть некоторые сценарии, которые подходят для его использования, и вам нужно тщательно спланировать.
Получение переменных блочной области
Когда мы впервые заговорили о приобретенииvarПри объявлении переменной мы кратко рассмотрели, как ведет себя переменная после ее получения. Интуитивно понятно, что каждый раз, когда вы входите в область видимости, создается среда переменных. Даже после завершения выполнения кода в области видимости среда и ее захваченные переменные все еще существуют.
function theCityThatAlwaysSleeps() {
let getCity;
if (true) {
let city = "Seattle";
getCity = function() {
return city;
}
}
return getCity();
}
потому что мы ужеcityполученный в окружающей средеcity, так что даже еслиifМы все еще можем получить к нему доступ после завершения выполнения оператора.
ПередуматьsetTimeoutнапример, нам, наконец, нужно использовать немедленно выполняемое функциональное выражение, чтобы каждый раз получатьforСостояние внутри итерации цикла. По сути, мы создаем новое окружение переменной для полученной переменной.
когдаletОбъявления имеют совершенно другое поведение, когда они появляются в теле цикла. В цикл вводится не только новое переменное окружение, но и такая новая область видимости создается для каждой итерации. Это то, что мы делаем, когда используем непосредственные функциональные выражения, поэтому вsetTimeoutВ примере мы используем толькоletЗаявление подойдет.
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
выведет ожидаемый результат:
0
1
2
3
4
5
6
7
8
9
три,constутверждение
constОбъявления — это еще один способ объявления переменных.
const numLivesForCat = 9;
они сletОбъявления похожи, но, как следует из названия, их нельзя изменить после присвоения значения. Другими словами, у них одинаковыеletТе же правила области видимости, но их нельзя переназначить.
Это хорошо понятно, значения, на которые они ссылаются,неизменный.
const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
}
// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat
};
// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
Если вы не используете специальные методы, чтобы избежать этого, на самом делеconstВнутреннее состояние переменной можно изменить.
Четыре,let vs. const
Теперь, когда у нас есть два объявления с одинаковыми областями действия, мы, естественно, спрашиваем, какое из них следует использовать. Как и на большинство общих вопросов, ответ таков: это зависит.
использоватьпринцип наименьших привилегий, следует использовать все переменные, кроме тех, которые вы планируете изменитьconst. Основной принцип заключается в том, что если переменную не нужно записывать в нее, другие люди, использующие код, тоже не могут в нее писать, и подумайте, почему эти переменные нужно переназначать. использоватьconstЭто также позволяет нам более легко рассуждать о потоке данных.
Используйте свое собственное суждение и обсудите его с членом команды, если это уместно.
5. Деконструкция
деструктурирующий массив
Самая простая деструктуризация — это присваивание деструктуризации массива:
let input = [1, 2];
let [first, second] = input;
console.log(first); // 1
console.log(second); // 2
Это создает 2 именованные переменныеfirstа такжеsecond. Эквивалентно использованию индекса, но более удобно:
first = input[0];
second = input[1];
Деструктуризация лучше работает с объявленными переменными:
[first, second] = [second, first];
Применяется к параметрам функции:
function f([first, second]) {
console.log(first);
console.log(second);
}
f(input);
вы можете использовать в массиве...Синтаксис для создания остальных переменных:
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [ 2, 3, 4 ]
Конечно, поскольку это JavaScript, вы можете игнорировать завершающие элементы, которые вам не нужны:
let [first] = [1, 2, 3, 4];
console.log(first); // 1
или другие элементы:
let [, second, , fourth] = [1, 2, 3, 4];
деструктуризация объекта
Вы также можете деструктурировать объекты:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
Это проходитo.a and o.bсозданныйaа такжеb. Обратите внимание, если вам не нужноcВы можете игнорировать это.
Как и при деструктуризации массива, вы можете использовать присваивание без объявления:
({ a, b } = { a: "baz", b: 101 });
Обратите внимание, что нам нужно заключить его в круглые скобки, так как Javascript обычно начинается с{Исходный оператор анализируется как блок.
вы можете использовать в объекте...Синтаксис для создания остальных переменных:
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
6. Развернуть
Оператор распространения противоположен деструктурированию. Это позволяет вам расширить массив в другой массив или объект в другой объект. Например:
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
это сделаетbothPlusценность[0, 1, 2, 3, 4, 5]. Развернуть созданную операциюfirstа такжеsecondМелкая копия . Они не изменяются операцией раскрутки.
Вы также можете расширять объекты:
let defaults = { food: "spicy", price: "?", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
searchценность{ food: "rich", price: "?", ambiance: "noisy" }. Расширение объекта сложнее, чем расширение массива. Как и расширение массива, оно обрабатывается слева направо, но результатом остается объект. Это означает, что свойства, которые появляются после развернутого объекта, переопределяют предыдущие свойства. Итак, если мы изменим приведенный выше пример, чтобы расширить в конце:
let defaults = { food: "spicy", price: "?", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
Так,defaultsвнутреннийfoodсвойство переопределяетfood: "rich", здесь это не тот результат, который нам нужен.
Расширение объекта имеет и другие неожиданные ограничения. Во-первых, он содержит только объектысобственные перечисляемые свойства. По сути, когда вы расширяете экземпляр объекта, вы теряете его методы:
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
Семь, новый, этот, класс, функция
это и новое
новое ключевое словообъект созданна самом деле постоянное присвоение новому объекту this и будет__proto__Указывает на объект, на который указывает прототип класса.
var SuperType = function (name) {
var nose = 'nose' // 私有属性
function say () {} // 私有方法
// 特权方法
this.getName = function () {}
this.setName = function () {}
this.mouse = 'mouse' // 对象公有属性
this.listen = function () {} // 对象公有方法
// 构造器
this.setName(name)
}
SuperType.age = 10 // 类静态公有属性(对象不能访问)
SuperType.read = function () {} // 类静态公有方法(对象无法访问)
SuperType.prototype = { // 对象赋值(也可以一一赋值)
isMan: 'true', // 公有属性
write: function () {} // 公有方法
}
var instance = new SuperType()
Увеличение перед вызовом функцииnew, что эквивалентноSuperTypeкак конструктор (хотя это просто функция), затем создайте объект {} и поместитеSuperType середина thisуказывает на этот объект, чтобы к нему можно было получить доступ через что-то вродеthis.mouseустановить что-то в виде , а затем вернуть объект.
В частности, если вызову функции предшествуетnewоператор, вы можете использовать любую функцию в качестве конструктора класса.
Добавить новое
В приведенном выше примере мы видим, что: определено внутри конструкторачастная переменная или метод, и определяемый классомСтатические общедоступные свойства и методы,существуетnewобъекты экземпляра будутнедоступный.
без нового
если ты позвонишьSuperType() ВремянетДобавить new,один из них thisуказывало бы на что-то глобальное и бесполезное (например,window или undefined), поэтому наш код вылетит или сделает что-то вроде установкиwindow.mouseТакие глупости.
let instance1 = SuperType();
console.log(instance1.mouse);
// Uncaught TypeError: Cannot read property 'mouse' of undefined
console.log(window.mouse);
// mouse
функция, класс
функция
function Bottle(name) {
this.name = name;
}
// + new
let bottle = new Bottle('bottle'); // ✅ 有效: Bottle {name: "bottle"}
console.log(bottle.name) // bottle
// 不加 new
let bottle1 = Bottle('bottle'); // 🔴 这种调用方法让人很难理解
console.log(bottle1.name); // Uncaught TypeError: Cannot read property 'name' of undefined
console.log(window.name); // bottle
Добрый
class Bottle {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello, ' + this.name);
}
}
// + new
let bottle = new Bottle('bottle');
bottle.sayHello(); // ✅ 依然有效,打印:Hello, bottle
// 不加 new
let bottle1 = Bottle('bottle'); // 🔴 立即失败
// Uncaught TypeError: Class constructor Bottle cannot be invoked without 'new'
Использование контраста
let fun = new Fun();
// ✅ 如果 Fun 是个函数:有效
// ✅ 如果 Fun 是个类:依然有效
let fun1 = Fun(); // 我们忘记使用 `new`
// 😳 如果 Fun 是个长得像构造函数的方法:令人困惑的行为
// 🔴 如果 Fun 是个类:立即失败
который
| new Fun() | Fun | |
|---|---|---|
| class | ✅this Является Funпример |
🔴TypeError
|
| function | ✅this Является Funпример |
😳this Да window или undefined
|
Странность использования нового
возврат недействителен
function Bottle() {
return 'Hello, AnGe';
}
Bottle(); // ✅ 'Hello, AnGe'
new Bottle(); // 😳 Bottle {}
стрелочная функция
Для функций со стрелками используйтеnewсообщит об ошибке 🔴
const Bottle = () => {console.log('Hello, AnGe')};
new Bottle(); // Uncaught TypeError: Bottle is not a constructor
Это поведение по замыслу соответствует дизайну стрелочных функций. Побочным эффектом стрелочных функций является то, что онинетмой собственный thisстоимость -thisРазбор из ближайшей обычной функции:
function AnGe() {
this.name = 'AnGe'
return () => {console.log('Hello, ' + this.name)};
}
let anGe = new AnGe();
console.log(anGe()); // Hello, AnGe
Так что **функции со стрелками не имеют собственного this. ** Но это значит, что он совершенно бесполезен как конструктор!
Резюме: стрелочные функции
- это указывает на среду, в которой он был определен.
- Не создается с помощью new.
- это неизменно.
- объект без аргументов.
разрешить использоватьnewВызванная функция возвращает другой объект впокрытие newВозвращаемое значение
Давайте посмотрим на пример:
function Vector(x, y) {
this.x = x;
this.y = y;
}
var v1 = new Vector(0, 0);
var v2 = new Vector(0, 0);
console.log(v1 === v2); // false
v1.x = 1;
console.log(v2); // Vector {x: 0, y: 0}
Для этого примера, на первый взгляд, сказать особо нечего.
Затем посмотрите на следующий пример и подумайте, почемуb === cдляtrue😲:
let zeroVector = null;
// 创建了一个懒变量 zeroVector = null;
function Vector(x, y) {
if (zeroVector !== null) {
// 复用同一个实例
return zeroVector;
}
zeroVector = this;
this.x = x;
this.y = y;
}
var v1 = new Vector(0, 0);
var v2 = new Vector(0, 0);
console.log(v1 === v2); // true
v1.x = 1;
console.log(v2); // Vector {x: 1, y: 0}
Это потому, что JavaScript позволяет использоватьnewВызванная функция возвращает другой объект впокрытие newВозвращаемое значение. Это может быть полезно, когда мы повторно используем такие компоненты, как «шаблон объединения объектов».
Ссылаться на:
TypeScript Variable Declarations
серия статей
- Первая серия JS: var, let, const, деструктуризация, расширение, новое, это, класс, функция
- JS Series II: Углубленный конструктор, прототип,proto, [[Prototype]] и цепочка прототипов
- JS Series Three: Шесть реализаций наследования
- JS Series 4: Глубокое погружение в instanceof Operator
Хотите увидеть больше статей в серии,Нажмите, чтобы перейти на домашнюю страницу блога github.
Прогулка в конце, добро пожаловать, чтобы обратить внимание: джентльмен переднего конца бутылки, ежедневное обновление