Как стать автором
Обновить

Комментарии 11

Интересно, для чего может понадобиться WeakRef?

at(-1) является эквивалентом arr[arr.length - 1] для получения последнего элемента, но не для его установки

Странно, почему нет - это ж удобно.

WeakRef нужен для передачи объектов в сторонние классы/модули без увеличения счетчика ссылок. Необходимо для корректной работы GC если мы не уверены, что 3-я сторона будет корректно чистить ссылки на объект.

Эм, так тогда GC будет точно некорректно чистить. Вернее совсем не будет. И как вообще эта третья сторона может контролировать GC?

Варианта было два - хранить/держать ссылку и не давать собрать GC или же не хранить ссылку - не мешать GC. WeakRef же третий вариант - ссылка хранится, но ненадежно - может пропасть. Удобно для всякого рода кэширования - само очистится GC.

3-я сторона может спокойно вызвать const value = ref.deref(); и держать ссылку сколько её угодно. Так что для подобных вещей WeakRef бесполезен.

at это функция, в js она не может вернуть rvalue, в смысле указатель на внутренности массива. Максимум что могли сделать это at(-1, newvalue)

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

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

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

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

Под спойлером перечисление ошибок найденных как в переводе так и в оригинале.

Символы / Symbols: примитивы

 В оригинальном тексте никаких заявлений о том что Symbol является примитивом - нет. Что соответствует спецификации, в которой нет описания "примитивов" или "примитивных типов" или "простых типов".

Тип Symbol - это тип, который возвращает Primitive Value, чего совершенно недостаточно, чтобы записывать его (как и любой другой подобный тип) в группу примитивов или примитивных типов.

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

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

  2. В арсенале языка существуют штатные средства изменения поведения типа для разных ситуаций в которых он может использоваться.

Как первый пункт, так и второй характерен для любого из JavaScript типов, а именно:

  1. Любой из типов, которые пытаются отнести в JS к примитивным, имеет сложную обьектную структуру как на уровне спецификации, так и на уровне ее реализаций в конкретных runTime.
    Доступ к которой, в рамках спецификации, осуществляется путем использования конструктора типа, в момент доступа к любому из его свойств.
    И форсируя использование конструктора - напрямую в случае конкреткных реализаций например V8.

  2. Поведение подобных типов, может быть легко изменено путем использования штатных средств предоставляемых языком. Вне зависимости от того, определено ли значение при помощи конструктора типа или его литерала.
    То есть программист может перепрограммировать поведение типа (например String) таким образом, что доступ к его Primitive Value, в разных ситуациях, будет приводить к разным результатам в зависимости от желания программиста.
    Например доступ к символу строки по индексу, может возвращать то, что угодно программисту, но не то, что было задано при инициализации связи со строкой

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

Optional chaining

Оператор опциональной последовательности / Optional chaining (?.): обычно используется для безопасного доступа к свойству потенциально несуществующего/неопределенного (undefined) объекта,

Неудачный перевод выражения undefined object допускающий интерпретацию его как существование некоторого обьекта с типом undefined чего в JS быть не может.

И придуманная переводчиком часть, которая вводит в заблуждение:

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

Optional chaining (?.) не может предоставить доступа к несуществующему идентификатору с каким бы типом данных он не был связан.

Заявленные примеры создают ложное впечатление будто бы это так, а на самом деле optional chaining, в заявленных примерах применяется либо к undefined либо к null, что совершенно не отвечает формулировке - потенциально не существующему. Потому как, что undefined что null - это реально существующие данные.

В чем легко убедиться на простом примере:

anyNonExistIdentifier ?. (); 
Uncaught ReferenceError: anyNonExistIdentifier is not defined
undefined .? ();  // undefined
null?.(); // undeifned

Иными словами правильно было бы заявить, что Optional chaining это возможность языка, применяемая исключительно к CallExpression или MemberExpression и позволяющая произвести окончательное вычисление выражения в том случае, если как первое так и второе определено и не возвращает тип Undefined или Null

Например:

(()=>{
	doThing?.(); // undefined
	var doThing;
})();

(()=>{
	doLetThing?.(); 
    Uncaught ReferenceError: Cannot access 'doLetThing' before initialization
	let doLetThing;
})();

Оператор нулевого слияния / Nullish coalescing operator (??):

Оператор нулевого слияния / Nullish coalescing operator (??): является альтернативой оператора ||. Отличие между этими операторами состоит в том, что || применяется ко всем ложным значениям, а ?? — только к undefined и null

Спецификация ничего не знает про термин Nullish - это жаргон.
Спецификация определяет еще один Binary Logical Operator со своей логикой поведения, которая заключается в том, что если вычисление левой части выражения приводит к результату null или undefined то следует вычислить правую часть выражения.

Например:

var theThing = globalThis.someNonExistProperty ?? "Yo";
console.log(theThing); // Yo

var theThing = null ?? "Yo";
console.log(theThing); // Yo

var theThing = undefined ?? "Yo";
console.log(theThing); // Yo

var theThing = nonExistIdentifier ?? "Yo";
Uncaught ReferenceError: someNonExistIdentifier is not defined

То есть, допустимо сказать что как ?? так и ?. имеют одинаковую логику связанную с вычислением условия левой части выражения (должно быть либо undefined либо null) и разную реакцию:

  1. ?? - приведет к выполнении правой части, если левая либо:
    undefined или null

  2. ?. - приведет к применению правой части к левой, если левая не undefined или null

Например:

var theObj = {
  name: "DemiMurych",
};

console.log( theObj.name ?? "The Name is not defined" );
DemiMurych
console.log( theObj.age ?? "The Age is unknown" );
The Age is unknown

console.log( theObj ?. name  );
DemiMurych
console.log( theObj ?. age  );
undefined

Отличие между этими операторами состоит в том, что || применяется ко всем ложным значениям

Некорректно заявлять подобное. Правильно говорить, что Binary Logick Operator || приводит к выполнению правой части выражения в том случае, когда результат левой части выражения, переданный в ToBoolean вернет результат False.

Что произойдет в случае: undefined, null, +0𝔽, -0𝔽, NaN, 0ℤ и особом случае передачи обьекта имеющего установленное внутреннее свойство [[IsHTMLDDA]]

import()

import(): функциональное выражение динамического импорта — как import ... from '...', но во время выполнения кода и с возможностью использования переменных

import() - не является функциональным выражением. ImportCall это особый, самостоятельный тип выражения, на который не распространяются правила типичного CallExpression (вызова функции).

В чем легко убедиться на примере:

const theLib = import("/somePath/someLib");
Uncaught ReferenceError: improt is not defined

Promise.allSettled()

Promise.allSettled(): похож на Promise.all(), но ожидает (любого) разрешения всех промисов, а не возвращает первую ошибку, что облегчает обработку ошибок

Promise.allSettled - возвращает Promise который примет состояние fulfilled в случае если все Promise из списка переданного в Promise.allSettled будут разрешены либо вfulfilled либо в rejected. Например:

Promise.allSettled([ 
  Promise.resolve(1), 
  Promise.reject(2)
]).then(
  ( theRes)=>( 
    theRes.forEach( 
      ( {status: theStatus} )=>( 
        console.log(theStatus)
      ) 
    )  
  )
);
fulfilled
rejected

И никогда не разрешиться, если хотя бы один из промисов будет в состоянии Pending:

Promise.allSettled([ 
  Promise.resolve(1), 
  Promise.reject(2),
  new Promise( ()=>{} )

]).then(
  ( theRes)=>( 
    theRes.forEach( 
      ( {status: theStatus} )=>( 
        console.log(theStatus)
      ) 
    )  
  )
);
Promise {<pending>}

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

globalThis

globalThis: предоставляет доступ к глобальным переменным, независимо от среды выполнения кода (браузер, Node.js и др.)

Ошибка и в источнике и в переводе.
globalThis - по умолчанию, содержит в себе ссылку на глобальный обьект.

Однако, поскольку globalThis допускает возможность связать себя с чем угодно, нет никаких гарантий что globalThis всегда будет ссылаться именно на него (Global Object).

В глобальном объекте действительно будут созданы новые Property НО только для HoistableDeclaration и только для VariableStatement,
и не будут созданы для LexicalyScopedDeclaration.
В чем легко убедиться на простом примере:

var theVarIdet = 10;
console.log('variable statement: ', globalThis.theVarIdet);
variable statement:  10

let theLetIdent = 10;
console.log('lexicaly declaration: ', globalThis.theLetIdent);
lexicaly declaration:  undefined

JavaScript и глобальные переменные

Никаких глобальных переменных в Js не существует. Современный JS вообще не описывается в терминах областей видимости. Современный Js описывается в рамках:
HOST => Realm => Eviroment
Что видно не только из официальной спецификации но и из смежных спецификаций, например HTML5.

Ближе всего к концепции глобальной области видимости лежит Global Enviroment Record. Поскольку в GER так же как и в глобальной области видимости не существует родителя.

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

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

Разное

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

await верхнего уровня / Top level await: позволяет использовать ключевое слово await на верхнем уровне модулей, что избавляет от необходимости оборачивать асинхронный код в асинхронную функцию и улучшает обработку ошибок:

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

По этой причине, в случае если поставленная задача требует оптимизации потребляемых ресурсов, использование в модулях top level await противопоказано.

Причина ошибки / Error cause: при повторном выбросе исключения (re-throwing) в качестве

Словосочетание - выброс исключений ошибочно добавлено переводчиком и отсутствует в оригинальном тексте.

В JavaScript нет выброса исключений в том смысле в котором оно существует в других языках. В JavaScript throw это обычный return из текущего Job но с установленным флагом, который позволяет обработать этот return при помощи try statement.

Вероятно после этого открытия, многим станет понятным "изначально странное поведение" внутри Executor в конструкторе Promise когда вложенные throw не отлавливаются вышестоящим Catch.

Вместо ИГОГО

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

В противном случае, ошибки приводят к тому, что начинают множить невежество, которого и так больше чем нужно в среде JS разработки.

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

Не могли бы вы привести пример кода, где такая магия будет работать.

А какие use case есть у top level await ? То есть такой же fecth() только не завернутый в async функцию ?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий