Страницы

Поиск по вопросам

Показаны сообщения с ярлыком ecmascript. Показать все сообщения
Показаны сообщения с ярлыком ecmascript. Показать все сообщения

пятница, 14 февраля 2020 г.

Почему не записывается новое значение в глобальную переменую?

#javascript #global_variables #ecmascript




var arr = ["V", "l", "a", "d", "i", "m", "i", "r"];

var join = function (x) {
  return window.x = x.join('');
}

join(arr);

console.log(arr);




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


Ответы

Ответ 1



Тоже отбитый вариант var arr = ["V", "l", "a", "d", "i", "m", "i", "r"]; var join = function(x) { for (var key in window) if (window[key] == x) window[key] = x.join(''); } join(arr); console.log(arr); Не особо правильно конечно но var arr = ["V", "l", "a", "d", "i", "m", "i", "r"]; var join = function (x) { window[x] = window[x].join(''); } join("arr"); console.log(arr);

Ответ 2



Такой код плохой, так как var join = function (x /* аргумент */) { return window.x /* глобальная переменная */ = x.join(''); } идёт путаница с x, в одном месте это название параметра в другом глобальная переменная. Конечно сам код запустится и скомпилируется, но таково кода следует избегать так как его сложно читать и сложно отлаживать. Так же таких конструкций тоже желательно избегать return window.x = x.join(''); лучше сделать отдельными операциями (одна строчка на одну операцию или группу операций единых по смыслу) что бы более была ясна суть, это конечно не ошибка рекомендую делать так window.x = x.join(''); return window.x; Заметим что результат попадает в возвратку функции и в x глобальную переменную, поэтому что бы вывести этот результат в консоль оттуда и надо брать console.log(x); или var rez = join(arr); console.log(rez); итого var arr = ["V", "l", "a", "d", "i", "m", "i", "r"]; var join = function(arr) { window.x = arr.join(''); return window.x; } join(arr); console.log(x);

Ответ 3



var arr = ["V", "l", "a", "d", "i", "m", "i", "r"]; var join = function (x) { return window.x = x.join(''); } join(arr); console.log(x); // <- ошибка

воскресенье, 9 февраля 2020 г.

EcmaScript Спецификация или язык программирования?

#javascript #ecmascript_6 #ecmascript


Русская версия


  ECMAScript — это встраиваемый расширяемый не имеющий средств
  ввода-вывода язык программирования, используемый в качестве основы для
  построения других скриптовых языков. Стандартизирован международной
  организацией ECMA в спецификации ECMA-262. Расширения языка:
  JavaScript, JScript и ActionScript.


Английская версия


  ECMAScript (or ES) is a trademarked scripting-language specification
  standardized by Ecma International in ECMA-262 and ISO/IEC 16262.



Так всё-таки EcmaScript это спецификация или язык программирования? Получается в
Вики ошибка или что?

Текст написан про EcmaScript в русской вики


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

Получается язык как они говорят EcmaScript возник на основе нескольких технологий
аля: JavaScript и JScript. То есть слияние этих всех технологий или языков? Или наоборот
на примере JavaScript(a): Это EcmaScript + DOM, BOM и еще что-нибудь? Я правильно понимаю?


Да я копаю в основы и тд. Уж лучше так медленней понимать и знать что да как, чем
после 20 минут JavaScript(a) идти во фреймворки и  пытаться понять как там всё работает -_- 
    


Ответы

Ответ 1



Для разбора откроем саму спецификацию (последний черновик) Название явно указывает: ECMAScript® 2019 Language Specification Спецификация языка ECMAScript® 2019 Далее во введении можно увидеть следующее: Этот Ecma стандарт определяет язык ECMAScript 2019. Это десятая редакция спецификации языка ECMAScript. ECMAScript основан на нескольких технологиях, таких как JavaScript (Netscape) и JScript (Microsoft). Настоящий стандарт определяет язык программирования общего назначения ECMAScript 2019. Далее идет секция соответствия, в которой указывается определенные пункты, которым должна соответствовать реализация ECMAScript. Из всего выше сказанного можно сделать вывод: Есть языковая спецификация Спецификация описывает язык ECMAScript Есть конкретные реализации этой спецификации, например JavaScript

Ответ 2



Спецификация языка - это документ, который описывает язык. Чтобы язык начал свое существование, он должен быть описан. В самой спецификации ECMAScript во введении написано: This Ecma Standard defines the ECMAScript 2015 Language. Так что никакого противоречия нет. В приведенной вами цитате Язык возник на основе нескольких технологий, самыми известными из которых являются языки JavaScript и JScript. язык программирования определяется как вид информационной технологии.

суббота, 8 февраля 2020 г.

Интересные возможности vanilla.js - доступ по id

#javascript #ecmascript




OneOne!





Может для кого то я и не открыл Америку, но сам столкнулся впервые.
Т.е. я не должен писать document.getElementById('oneOne');
Т.к. любые "переменные" и функции, вызванные через объект window - это его свойства
и методы, значит все ИД всех елементов заносятся в свойства этого объекта.

Вопрос:

Когда это вообще появилось?

На сколько это хорошо в использовании (явно удобней, чем через get)?

Какие ограничения и по какой причине не используется массово?

Или просто сейчас мало кто использует чистый JS?

У кого есть ссылки на спецификацию и / или статьи на эту тему?
    


Ответы

Ответ 1



Если элементу назначен специальный атрибут id, то можно получить его прямо по переменной с именем из значения id. Это поведение соответствует стандарту. Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты. Более правильной и общепринятой практикой является доступ к элементу вызовом document.getElementById("идентификатор"). Отсюда As a general rule, relying on this will lead to brittle code. Which IDs end up mapping to this API can vary over time, as new features are added to the Web platform, for example. Instead of this, use document.getElementById() or document.querySelector(). Спецификация P.S. К vanilla.js это отношения не имеет.

Ответ 2



Я бы забыл об этом способе, как о страшном сне. Вот посмотрите на проблему 1:
теперь пробуем найти элемент: console.log(document.getElementById('test')); // нашли console.log(test); // нашли // ищем какой-то другой элемент, которого может не быть console.log(document.getElementById('test2')); // упс, такого нет, null console.log(test2); // Uncaught ReferenceError: test2 is not defined - проблема Так что, как видите, поскольку это не геттер, то нам не гарантируется получение осмысленного результата. Проблема 2: var test = "Ohhh!"; console.log(document.getElementById('test')); // нода console.log(test); // Ohhh! Как видите, поскольку это глобальный объект window, то очень очень легко перезаписать подобную переменную чем то другим.

пятница, 31 января 2020 г.

Различная реакция на переопределение идентификатора в браузерах Opera и Firefox

#javascript #ecmascript


Как объяснить различную реакцию на выполнение следующего кода в разных браузерах?

var func = function() { console.log('1'); };
function func() { console.log('2'); }
func();


При выполнении в Opera 36.0.2130.32 выдается ошибка:


  "SyntaxError: Identifier 'func' has already been declared at
  https://static.jsbin.com/js/prod/runner-3.35.11.min.js:1:13891 at
  https://static.jsbin.com/js/prod/runner-3.35.11.min.js:1:10820"


При выполнении в Firefox 44.0.2 ошибка не выдается, и в консоль выводится значение "2".
Вот ссылка на jsbin: https://jsbin.com/hijobojawe/1/edit?js,console,output
    


Ответы

Ответ 1



Хорошая демонстрация неочевидного момента т.н. "var hoisting". Дело в том, что на стадии инициализации все объявления переменных, которые могут быть где угодно в теле функции, перемещаются наверх, в то время, как их назначение остаётся на месте. Было: function x(){ // ..здесь много кода var v = 'test'; // внезапно объявляем ещё одну переменную // ..здесь дальше код function f(){ /* тело ф-ии */ } // объявили функцию } Стало: function x(){ function f(){ /* тело ф-ии */ } // объявили функцию var v; // ..здесь много кода var v = 'test'; // внезапно объявляем ещё одну переменную // ..здесь дальше код } Объявления функций так же могут находиться где-то ниже по коду внутри контекста, и при этом тоже «переезжают» вверх при исполнении. Причём, как пишут некоторые источники, становятся до объявления всех переменных. Сначала function, потом var, потом v = 'test'. В вашем суперпримере ещё один вопрос: какое объявление function() сработает раньше – в правой части назначения переменной, или самостоятельное. Раньше сработает самостоятельное – function declaration. Назначение func = function(){..} выполнится в последнюю очередь. Итак, сначала объявляется ф-я с 2, затем объявляется переменная func (и вызывает ошибку, т.к. с таким именем уже есть ф-я), а потом эта переменная получает значение ф-ии с 1. По-моему, это правильная реализация JS стандартов. Тесты: FireFox 45.0.1, ScratchPad, консоль FireBug: "1" Chrome 49.0.2623.87 (64-bit), Uncaught SyntaxError: Identifier 'func' has already been declared Safari 9.1 (11601.5.17.1), консоль: 1 Opera 34.0, консоль: 1 Opera 36.0, консоль: Uncaught SyntaxError: Identifier 'func' has already been declared

Ответ 2



Я так понимаю, что должно быть вообще "1". В моем ИЕ как раз выводится единица.Что тут происходит? При выполнении скрипта существует 2 стадии. Стадия инициализации и стадия выполнения. Function Declaration (2 строка) происходит на стадии инициализации. Далее по-ходу выполнения скрипта - тело функции переписывается в function expression (1 строка), которые как раз выполняются на стадии выполнения. В итоге мы должны видеть (И видим в ИЕ) единицу.А то, что мы видим на практике - видимо различия реализации JS движков, у каждого производителя он свой. Мы видим, что Opera(и Хром) не дают переписать объявленную функцию. Firefox просто игнорирует перезапись, а IE спокойно перезаписывает. Почитать.

четверг, 2 января 2020 г.

Фазы работы скрипта js

#javascript #ecmascript


На learn.js написано: 


  Выполнение скрипта происходит в две фазы:
  
  На первой фазе происходит инициализация, подготовка к запуску.
  
  Во время инициализации скрипт сканируется на предмет объявления
  функций вида Function Declaration, а затем – на предмет объявления
  переменных var. Каждое такое объявление добавляется в window.
  
  Функции, объявленные как Function Declaration, создаются сразу
  работающими, а переменные – равными undefined.
  
  На второй фазе – собственно, выполнение.
  
  Присваивание (=) значений переменных происходит, когда поток
  выполнения доходит до соответствующей строчки кода, до этого они
  undefined.


А также мне отвечали на stackoverflow, что:


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



Верно ли, что речь идет об одних и тех же фазах?
Если да, значит в первой фазе работает не только синтаксический  анализатор, но и
происходит инициализация? 
Расталкуйте пожалуйста, какие фазы есть вообще? И что происходит во время каждой фазы?

    


Ответы

Ответ 1



Сам же пишешь: Работа любого скрипта в javascript состоит из двух фаз. Первым вступает в дело синтаксический анализатор, который просматривает весь входной текст и подготавливает скрипт к выполнению, переводит его во внутреннее представление. На второй фазе это внутреннее представление начинает выполняться. Выполнение скрипта происходит в две фазы Итак. Верно ли, что речь идет об одних и тех же фазах? Нет. Одна фаза синтаксического анализа и ещё две фазы выполнения. Если да, значит в первой фазе работает не только синтаксический анализатор, но и происходит инициализация? Нет, это невозможно. Во-первых, скрипт может вообще не выполняться, если в нём синтаксическая ошибка: try { console.log(never executed) } catch (e) { console.log("And this is never executed too") } Во-вторых, все переменные принадлежат выполняющейся функции и каждый её запуск порождает новый комплект переменных. А при парсинге функция вообще не выполняется. Возможно, она вообще никогда и не выполнится. function f() { var a = Math.random() return () => a } var f1 = f(), f2 = f() console.log(f1(), f2()) // Они разные Расталкуйте пожалуйста, какие фазы есть вообще? И что происходит во время каждой фазы? Вроде как это все. И ещё кое-что: Каждое такое объявление добавляется в window. window тут только для кода, расположенного непосредственно на верхнем уровне (не в функциях).

Ответ 2



Речь идет о разных фазах. В первом определении описывается поведение переменных в фазах выполнения скрипта. Во втором определении описывается внутренний механизм реализации конкретного рантайма. Обе фазы из первого определения происходят во второй фазе второго (выполнение). Нет, в первой фазе первого определения синтаксический анализатор большинства рантаймов закончил свою работу и идет выполнение скомпилированного кода. Любой процесс можно поделить на какие-то фазы в зависимости от назначения этого деления. Из первого определения мы можем понять, что будет если обратиться к переменной до ее описания в var. Из второго получить некое представление о внутренней работе рантайма (например понять, что весь скрипт не будет выполнен, если синтаксический анализатор забракует даже его последнюю строчку).

вторник, 24 декабря 2019 г.

Принцип работы Array.prototype.concat()

#javascript #ecmascript


Для скрипта в Adobe Photoshop, есть диалоговое окно с элементами выбора и управления:

var dlg = new Window('dialog{text: "Выбор размеров", bounds: [' + fBounds(150,200,370,150)
+ '], \
            panel_s: Panel{text: "Стандартные:", bounds: [' + fBounds(5,5,203,85) + '], \
                checkbox_16:  Checkbox{bounds: [' + fBounds(5,13,55,14) + '],   text:
"16 x 16"},   \
                checkbox_24:  Checkbox{bounds: [' + fBounds(5,33,55,14) + '],   text:
"24 x 24"},   \
                checkbox_32:  Checkbox{bounds: [' + fBounds(5,53,55,14) + '],   text:
"32 x 32"},   \
                checkbox_48:  Checkbox{bounds: [' + fBounds(67,13,55,14) + '],  text:
"48 x 48"},   \
                checkbox_64:  Checkbox{bounds: [' + fBounds(67,33,55,14) + '],  text:
"64 x 64"},   \
                checkbox_96:  Checkbox{bounds: [' + fBounds(67,53,55,14) + '],  text:
"96 x 96"},   \
                checkbox_128: Checkbox{bounds: [' + fBounds(129,13,65,14) + '], text:
"128 x 128"}, \
                checkbox_256: Checkbox{bounds: [' + fBounds(129,33,65,14) + '], text:
"256 x 256"}, \
                checkbox_512: Checkbox{bounds: [' + fBounds(129,53,65,14) + '], text:
"512 x 512"}  \
            }, \
            panel_o: Panel{text: "Другие:", bounds: [' + fBounds(206,5,69,64) + '],\
                checkbox_19: Checkbox{bounds: [' + fBounds(5,13,55,14) + '], text:
"19 x 19"}, \
                checkbox_38: Checkbox{bounds: [' + fBounds(5,33,55,14) + '], text:
"38 x 38"}  \
            }, \
            panel_u: Panel{text: "Свои:", bounds: [' + fBounds(273,5,92,85) + '],\
                checkbox_u1: Checkbox{bounds:   [' + fBounds(5,13,14,14) + ']}, 
                                               \
                static_u1:   StaticText{bounds: [' + fBounds(21,13,31,14) + '], text:
". . . x", justify: "right"},             \
                edittext_u1: EditText{bounds:   [' + fBounds(53,11,30,19) + '], text:
". . .", properties: {multiline: false}}, \
                \
                checkbox_u2: Checkbox{bounds:   [' + fBounds(5,33,14,14) + ']}, 
                                               \
                static_u2:   StaticText{bounds: [' + fBounds(21,33,31,14) + '], text:
". . . x", justify: "right"},             \
                edittext_u2: EditText{bounds:   [' + fBounds(53,31,30,19) + '], text:
". . .", properties: {multiline: false}}, \
                \
                checkbox_u3: Checkbox{bounds:   [' + fBounds(5,53,14,14) + ']}, 
                                               \
                static_u3:   StaticText{bounds: [' + fBounds(21,53,31,14) + '], text:
". . . x", justify: "right"},             \
                edittext_u3: EditText{bounds:   [' + fBounds(53,51,30,19) + '], text:
". . .", properties: {multiline: false}}  \
            }, \
            button_all:    Button{bounds:       [' + fBounds(208,70,64,19) + '],
text: "Все"},          \
            static_save:   StaticText{bounds:   [' + fBounds(5,99,75,14) + '],  
text: "Сохранить в:"}, \
            drop_save:     DropDownList{bounds: [' + fBounds(78,95,287,23) + '],
properties: {items: ["папку рядом с исходным файлом", "-", "Выбрать папку..."]}, helpTip:"Ooops!!!"}, \
            progress_save: Progressbar{bounds:  [' + fBounds(0,0,370,4) + '],   
value:50},             \
            button_run:    Button{bounds:       [' + fBounds(4,122,70,22) + '], 
text: "Создать"}       \
        };'
    );

    // -- Выбор всех значений

    dlg.show();


Есть обработчик кнопки, для выбора всех чекбоксов:

// -- Выбор всех значений
dlg.button_all.addEventListener('click', function() {

    alert(dlg.panel_s.children + '\r\n' + dlg.panel_s.children.length);
    // Коллекция (9 элементов) и количество (9) отображаются верно

    alert(dlg.panel_o.children + '\r\n' + dlg.panel_o.children.length);
    // Коллекция (2 элемента) и количество (2) отображаются верно

    // Объединяем обе коллекции и три отдельных элемента
    var aPanel = [].concat(
        dlg.panel_s.children, 
        dlg.panel_o.children, 
        dlg.panel_u.checkbox_u1, 
        dlg.panel_u.checkbox_u2, 
        dlg.panel_u.checkbox_u3
    );

    alert(aPanel + '\r\n' + aPanel.length);
    // Массив отображается верно (14 элементов), но количество не совпадает (5) !
});


Из комментариев в коде обработчика видно, где возникает проблема, и вопрос следующий...Метод
Array.prototype.concat(), согласно документации, должен объединять массивы, раскрывая
их до первого уровня вложенности. Собственно, так и происходит, если верить последнему
всплывающему окну (см. код обработчика) - там отображаются все элементы в порядке добавления.Но,
при обращении к aPanel[0], возвращается не первый элемент (checkbox_16), а вся коллекция
dlg.panel_s.children . Соответственно при этом, длина массива aPanel составляет 5,
а добраться до нужного элемента можно только указав вложенность в коллекции, т.е. aPanel[0][0]
.Это баг или хитрая логика? И как правильно объединить массивы с помощью concat() ?
    


Ответы

Ответ 1



Поправочка Действительно, согласно справке MDN Метод concat создаёт новый массив, состоящий из элементов в объекте, на котором он был вызван, за которыми по порядку следуют, для каждого аргумента, все его элементы (если аргумент является массивом), либо сам аргумент (если он массивом не является) Только вот про первый уровень вложенности, как видите, тут ни слова.Вот Вам наглядный пример: let x = [].concat(1, 2, [3, 4], [5, 6, [7, 8]]); console.log(x); // Выведем полученный массив console.log(x.length); // Длина равняется не 8, как если бы все раскрывалось // до первого уровня вложенности, а 7! console.log(x[6]); // 6-ой элемент является массивом Однако это не отменяет Вашей проблемы, так как в Вашем случае, если бы элементы dlg.{foo}.children были массивами, то их разворачивание все равно бы произошло Так в чем же дело? Не буду Вас томить и наконец скажу, в чем же проблема. Еще раз обратите внимание на строчку: если аргумент является массивом К чему я клоню? Указанные Вами элементы не являются массивами! Да, они являются коллекциями и подобны массивам, но все же спецификация строга. Давайте посмотрим, как метод concat ведет себя с объектом arguments, который, как и Ваши элементы, является по сути коллекцией, но не массивом: function test() { console.log(Array.isArray(arguments)); // false return [].concat(arguments, arguments); } console.log(test(1, 2, 3).toString()); // [object Arguments], [object Arguments] Как видите, concat не стал разбираться, коллекция ли это или обычный объект. Для него важно лишь то, что данная конструкция не является массивом. P.S. - чтобы иметь возможность проверять, является ли объект массивом или нет, советую посмотреть в сторону функции Array.isArray() Что делать? Лучшим вариантом будет не полениться и все добавить руками: var aPanel = []; for (let i = 0; i < dlg.panel_s.children.length; i++) aPanel.push(dlg.panel_s.children[i]); for (let i = 0; i < dlg.panel_o.children.length; i++) aPanel.push(dlg.panel_o.children[i]); aPanel.push(dlg.panel_u.checkbox_u1, dlg.panel_u.checkbox_u2, dlg.panel_u.checkbox_u3); Другой вариант Можете определить свой метод на уровне прототипа, который как раз и будет разворачивать и массивы, и коллекции до первого уровня: Array.prototype.fullConcat = function() { const exclude = ["string"]; // Пусть строка и является коллекцией, но мы хотим считать ее цельным объектом function getElements(item) { // Рекурсивно получим все элементы внутри заданного (если таковые есть) let result = []; if (item.length === undefined || exclude.includes(typeof item)) result.push(item); else { for (let i = 0; i < item.length; i++) { let add = getElements(item[i]); for (let j = 0; j < add.length; j++) result.push(add[j]); } } return result; } let concated = []; for (let i = 0; i < arguments.length; i++) { // Начнем объединение let item = arguments[i]; if (item.length === undefined || exclude.includes(typeof item)) concated.push(item); else { for (let i = 0; i < item.length; i++) { let add = getElements(item[i]); for (let j = 0; j < add.length; j++) concated.push(add[j]); } } } // Не забудем и про контекст this, который может оказаться совсем не пустым return this.length == 0 ? concated : [].fullConcat(this, concated); }; function testArguments() { return [].fullConcat(arguments, arguments); } // Проверим, как метод расправится со вложенностью console.log([1, 2, 3].fullConcat([4], [5, 6, [7, 8, [9, 10]]], "hello", "world")); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "hello", "world" // Проверим, как метод расправится с коллекцией в лице arguments console.log(testArguments(1, 2, 3, [4, 5])); // 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 UPD: Большое спасибо @Grundy за то, что обратил мое внимание на следующий момент: Давайте же глянем не справку MDN, а на спецификацию ECMAScript. Обратим внимание на строчки: Repeat, while items is not empty a. Remove the first element from items and let E be the value of the element. b. Let spreadable be ? IsConcatSpreadable(E). c. If spreadable is true, then // copy items from within the object d. Else: // copy the object Или на русском: Повторять, пока items не пуст a. Удалить первый элемент из items, присвоить E значение элемента. b. Присвоить spreadable значение IsConcatSpreadable (E). c. Если spreadable является true, тогда // копировать элементы изнутри объекта d. Иначе: // копировать сам объект То есть за то, будет ли элемент добавлен к результирующему массиву в явном виде, или же он будет предварительно развернут как коллекция, отвечает результат выполнения некой функции IsConcatSpreadable(O). Давайте же взглянем, что это и с чем ее едят: If Type(O) is not Object, return false. Let spreadable be ? Get(O, @@isConcatSpreadable). If spreadable is not undefined, return ToBoolean(spreadable). Return ? IsArray(O). Или на русском: Если тип O не Object, вернуть false. Присвоить spreadable значение свойства @@isConcatSpreadable объекта O. Если spreadable не undefined, вернуть ToBoolean (spreadable). Вернуть IsArray(O). То есть проверка на то, является ли объект массивом, идет в самую последнюю очередь. До этого проверяется, обладает ли объект свойством Symbol.isConcatSpreadable, и если он таковым обладает, то его булевое значение и будет показателем того, будет ли он раскрываться при concat. Давайте теперь применим это на практике: // Создадим собственную коллекцию var userCollection = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; // Убедимся, что она не является массивом console.log(Array.isArray(userCollection)); // false // Посмотрим, что вернет нам concat console.log([].concat(userCollection, userCollection)); // Array [ Object(3), Object(3) ] // Теперь маленькая хитрость: // Установим внутреннее свойство, отвечающее // за результат IsConcatSpreadable(O), равным true userCollection[Symbol.isConcatSpreadable] = true; // Посмотрим, что теперь вернет нам concat console.log([].concat(userCollection, userCollection)); // Array [ "a", "b", "c", "a", "b", "c" ] // Или же сразу создадим коллекцию с указанным свойством: // Создадим собственную коллекцию var userCollection_1 = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.isConcatSpreadable]: true }; // Посмотрим, что вернет нам concat console.log([].concat(userCollection_1, userCollection_1)); // Array [ "a", "b", "c", "a", "b", "c" ] Работает как часы. Давайте для чистоты эксперимента проверим это на существующем перечисляемом типе. Вспомним опять же про наш arguments: // arguments не указан, как spreadable function nonSpreadable() { return [].concat(arguments, arguments); } // arguments указан, как spreadable function spreadable() { arguments[Symbol.isConcatSpreadable] = true; return [].concat(arguments, arguments); } // Какой же массив вернется при объединении 2-х arguments? console.log(nonSpreadable(1, 2)); // Array [ Arguments, Arguments ] // А теперь?) console.log(spreadable(1, 2)); // Array [ 1, 2, 1, 2 ] console.log(spreadable(1, 2, 3)); // Array [ 1, 2, 3, 1, 2, 3 ] Как видите, нам удалось с помощью concat объединить внутренние значения arguments в массив Для очистки совести напомню: лезть таким вот образом во внутренние свойства объекта, изменяя логику его работы, - неправильно и кощунственно! Почему? Давайте представим ситуацию: Вы видите массив. Как он должен вести себя с методом concat? Правильно, согласно уже не раз указанной справке он обязан раскрыться при объединении! А что если кто-то с полученным Вами массивом поигрался следующим образом: // Создадим обычные массивы var arr_0 = [1, 2]; var arr_1 = [3, 4]; // Убедимся, что это именно массивы, а не что-либо еще console.log(Array.isArray(arr_0)); // true console.log(Array.isArray(arr_1)); // true // Объединим их var arr_2 = [].concat(arr_0, arr_1); console.log(arr_2); // Array [ 1, 2, 3, 4 ] console.log(arr_2.length); // 4, как и полагается // А теперь сделаем парочку богопротивных действий arr_0[Symbol.isConcatSpreadable] = arr_1[Symbol.isConcatSpreadable] = false; // Объединим их снова var arr_3 = [].concat(arr_0, arr_1); console.log(arr_3); // Array [ Array(2), Array(2) ] console.log(arr_3.length); // 2, что-то пошло не так) Только что мы изменили поведение стандартного типа, что очень и очень неправильно! Так что старайтесь избегать таких вот подходов! Все, совесть очищена, так что можем злоупотреблять дальше :) Еще один вариант решения Вашей проблемы: На основе манипулирования результатом исполнения IsConcatSpreadable(O) Вашу задачу можно решить таким вот образом: // Колдуем над isConcatSpreadable: dlg.panel_s.children[Symbol.isConcatSpreadable] = dlg.panel_0.children[Symbol.isConcatSpreadable] = true; // Объединяем обе коллекции и три отдельных элемента var aPanel = [].concat( dlg.panel_s.children, dlg.panel_o.children, dlg.panel_u.checkbox_u1, dlg.panel_u.checkbox_u2, dlg.panel_u.checkbox_u3 ); И живой пример для наглядности: // Сформируем свои коллекции var userCollection_0 = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; var userCollection_1 = { 0: 'd', 1: 'e', 2: 'f', 3: 'g', length: 4 }; // Колдуем над isConcatSpreadable userCollection_0[Symbol.isConcatSpreadable] = userCollection_1[Symbol.isConcatSpreadable] = true; // Объединяем обе коллекции и три отдельных элемента var aPanel = [].concat( userCollection_0, userCollection_1, 'h', 'i', 'j' ); // Посмотрим, что внутри console.log(aPanel); // Array [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" ] // Убедимся, что длина также совпадает console.log(aPanel.length); // 10 из 10) Надеюсь, мой ответ помог Вам в разрешении проблемы! Удачи в Ваших начинаниях!

пятница, 7 июня 2019 г.

Принцип работы Array.prototype.concat()

Для скрипта в Adobe Photoshop, есть диалоговое окно с элементами выбора и управления:
var dlg = new Window('dialog{text: "Выбор размеров", bounds: [' + fBounds(150,200,370,150) + '], \ panel_s: Panel{text: "Стандартные:", bounds: [' + fBounds(5,5,203,85) + '], \ checkbox_16: Checkbox{bounds: [' + fBounds(5,13,55,14) + '], text: "16 x 16"}, \ checkbox_24: Checkbox{bounds: [' + fBounds(5,33,55,14) + '], text: "24 x 24"}, \ checkbox_32: Checkbox{bounds: [' + fBounds(5,53,55,14) + '], text: "32 x 32"}, \ checkbox_48: Checkbox{bounds: [' + fBounds(67,13,55,14) + '], text: "48 x 48"}, \ checkbox_64: Checkbox{bounds: [' + fBounds(67,33,55,14) + '], text: "64 x 64"}, \ checkbox_96: Checkbox{bounds: [' + fBounds(67,53,55,14) + '], text: "96 x 96"}, \ checkbox_128: Checkbox{bounds: [' + fBounds(129,13,65,14) + '], text: "128 x 128"}, \ checkbox_256: Checkbox{bounds: [' + fBounds(129,33,65,14) + '], text: "256 x 256"}, \ checkbox_512: Checkbox{bounds: [' + fBounds(129,53,65,14) + '], text: "512 x 512"} \ }, \ panel_o: Panel{text: "Другие:", bounds: [' + fBounds(206,5,69,64) + '],\ checkbox_19: Checkbox{bounds: [' + fBounds(5,13,55,14) + '], text: "19 x 19"}, \ checkbox_38: Checkbox{bounds: [' + fBounds(5,33,55,14) + '], text: "38 x 38"} \ }, \ panel_u: Panel{text: "Свои:", bounds: [' + fBounds(273,5,92,85) + '],\ checkbox_u1: Checkbox{bounds: [' + fBounds(5,13,14,14) + ']}, \ static_u1: StaticText{bounds: [' + fBounds(21,13,31,14) + '], text: ". . . x", justify: "right"}, \ edittext_u1: EditText{bounds: [' + fBounds(53,11,30,19) + '], text: ". . .", properties: {multiline: false}}, \ \ checkbox_u2: Checkbox{bounds: [' + fBounds(5,33,14,14) + ']}, \ static_u2: StaticText{bounds: [' + fBounds(21,33,31,14) + '], text: ". . . x", justify: "right"}, \ edittext_u2: EditText{bounds: [' + fBounds(53,31,30,19) + '], text: ". . .", properties: {multiline: false}}, \ \ checkbox_u3: Checkbox{bounds: [' + fBounds(5,53,14,14) + ']}, \ static_u3: StaticText{bounds: [' + fBounds(21,53,31,14) + '], text: ". . . x", justify: "right"}, \ edittext_u3: EditText{bounds: [' + fBounds(53,51,30,19) + '], text: ". . .", properties: {multiline: false}} \ }, \ button_all: Button{bounds: [' + fBounds(208,70,64,19) + '], text: "Все"}, \ static_save: StaticText{bounds: [' + fBounds(5,99,75,14) + '], text: "Сохранить в:"}, \ drop_save: DropDownList{bounds: [' + fBounds(78,95,287,23) + '], properties: {items: ["папку рядом с исходным файлом", "-", "Выбрать папку..."]}, helpTip:"Ooops!!!"}, \ progress_save: Progressbar{bounds: [' + fBounds(0,0,370,4) + '], value:50}, \ button_run: Button{bounds: [' + fBounds(4,122,70,22) + '], text: "Создать"} \ };' );
// -- Выбор всех значений
dlg.show();
Есть обработчик кнопки, для выбора всех чекбоксов:
// -- Выбор всех значений dlg.button_all.addEventListener('click', function() {
alert(dlg.panel_s.children + '
' + dlg.panel_s.children.length); // Коллекция (9 элементов) и количество (9) отображаются верно
alert(dlg.panel_o.children + '
' + dlg.panel_o.children.length); // Коллекция (2 элемента) и количество (2) отображаются верно
// Объединяем обе коллекции и три отдельных элемента var aPanel = [].concat( dlg.panel_s.children, dlg.panel_o.children, dlg.panel_u.checkbox_u1, dlg.panel_u.checkbox_u2, dlg.panel_u.checkbox_u3 );
alert(aPanel + '
' + aPanel.length); // Массив отображается верно (14 элементов), но количество не совпадает (5) ! });
Из комментариев в коде обработчика видно, где возникает проблема, и вопрос следующий...Метод Array.prototype.concat(), согласно документации, должен объединять массивы, раскрывая их до первого уровня вложенности. Собственно, так и происходит, если верить последнему всплывающему окну (см. код обработчика) - там отображаются все элементы в порядке добавления.Но, при обращении к aPanel[0], возвращается не первый элемент (checkbox_16), а вся коллекция dlg.panel_s.children . Соответственно при этом, длина массива aPanel составляет 5, а добраться до нужного элемента можно только указав вложенность в коллекции, т.е. aPanel[0][0] .Это баг или хитрая логика? И как правильно объединить массивы с помощью concat() ?


Ответ

Поправочка
Действительно, согласно справке MDN
Метод concat создаёт новый массив, состоящий из элементов в объекте, на котором он был вызван, за которыми по порядку следуют, для каждого аргумента, все его элементы (если аргумент является массивом), либо сам аргумент (если он массивом не является)
Только вот про первый уровень вложенности, как видите, тут ни слова.Вот Вам наглядный пример: let x = [].concat(1, 2, [3, 4], [5, 6, [7, 8]]); console.log(x); // Выведем полученный массив console.log(x.length); // Длина равняется не 8, как если бы все раскрывалось // до первого уровня вложенности, а 7! console.log(x[6]); // 6-ой элемент является массивом
Однако это не отменяет Вашей проблемы, так как в Вашем случае, если бы элементы dlg.{foo}.children были массивами, то их разворачивание все равно бы произошло

Так в чем же дело?
Не буду Вас томить и наконец скажу, в чем же проблема. Еще раз обратите внимание на строчку:
если аргумент является массивом
К чему я клоню? Указанные Вами элементы не являются массивами! Да, они являются коллекциями и подобны массивам, но все же спецификация строга.
Давайте посмотрим, как метод concat ведет себя с объектом arguments, который, как и Ваши элементы, является по сути коллекцией, но не массивом:
function test() { console.log(Array.isArray(arguments)); // false return [].concat(arguments, arguments); } console.log(test(1, 2, 3).toString()); // [object Arguments], [object Arguments]
Как видите, concat не стал разбираться, коллекция ли это или обычный объект. Для него важно лишь то, что данная конструкция не является массивом
P.S. - чтобы иметь возможность проверять, является ли объект массивом или нет, советую посмотреть в сторону функции Array.isArray()

Что делать?
Лучшим вариантом будет не полениться и все добавить руками:
var aPanel = []; for (let i = 0; i < dlg.panel_s.children.length; i++) aPanel.push(dlg.panel_s.children[i]); for (let i = 0; i < dlg.panel_o.children.length; i++) aPanel.push(dlg.panel_o.children[i]); aPanel.push(dlg.panel_u.checkbox_u1, dlg.panel_u.checkbox_u2, dlg.panel_u.checkbox_u3);

Другой вариант
Можете определить свой метод на уровне прототипа, который как раз и будет разворачивать и массивы, и коллекции до первого уровня:
Array.prototype.fullConcat = function() { const exclude = ["string"]; // Пусть строка и является коллекцией, но мы хотим считать ее цельным объектом function getElements(item) { // Рекурсивно получим все элементы внутри заданного (если таковые есть) let result = []; if (item.length === undefined || exclude.includes(typeof item)) result.push(item); else { for (let i = 0; i < item.length; i++) { let add = getElements(item[i]); for (let j = 0; j < add.length; j++) result.push(add[j]); } } return result; } let concated = []; for (let i = 0; i < arguments.length; i++) { // Начнем объединение let item = arguments[i]; if (item.length === undefined || exclude.includes(typeof item)) concated.push(item); else { for (let i = 0; i < item.length; i++) { let add = getElements(item[i]); for (let j = 0; j < add.length; j++) concated.push(add[j]); } } } // Не забудем и про контекст this, который может оказаться совсем не пустым return this.length == 0 ? concated : [].fullConcat(this, concated); }; function testArguments() { return [].fullConcat(arguments, arguments); } // Проверим, как метод расправится со вложенностью console.log([1, 2, 3].fullConcat([4], [5, 6, [7, 8, [9, 10]]], "hello", "world")); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "hello", "world" // Проверим, как метод расправится с коллекцией в лице arguments console.log(testArguments(1, 2, 3, [4, 5])); // 1, 2, 3, 4, 5, 1, 2, 3, 4, 5

UPD:
Большое спасибо @Grundy за то, что обратил мое внимание на следующий момент:
Давайте же глянем не справку MDN, а на спецификацию ECMAScript. Обратим внимание на строчки:
Repeat, while items is not empty a. Remove the first element from items and let E be the value of the element. b. Let spreadable be ? IsConcatSpreadable(E) c. If spreadable is true, then
// copy items from within the object d. Else:
// copy the object
Или на русском:
Повторять, пока items не пуст a. Удалить первый элемент из items, присвоить E значение элемента. b. Присвоить spreadable значение IsConcatSpreadable (E). c. Если spreadable является true, тогда
// копировать элементы изнутри объекта d. Иначе:
// копировать сам объект
То есть за то, будет ли элемент добавлен к результирующему массиву в явном виде, или же он будет предварительно развернут как коллекция, отвечает результат выполнения некой функции IsConcatSpreadable(O). Давайте же взглянем, что это и с чем ее едят:
If Type(O) is not Object, return false Let spreadable be ? Get(O, @@isConcatSpreadable) If spreadable is not undefined, return ToBoolean(spreadable) Return ? IsArray(O).
Или на русском:
Если тип O не Object, вернуть false. Присвоить spreadable значение свойства @@isConcatSpreadable объекта O. Если spreadable не undefined, вернуть ToBoolean (spreadable). Вернуть IsArray(O).
То есть проверка на то, является ли объект массивом, идет в самую последнюю очередь. До этого проверяется, обладает ли объект свойством Symbol.isConcatSpreadable, и если он таковым обладает, то его булевое значение и будет показателем того, будет ли он раскрываться при concat.
Давайте теперь применим это на практике:
// Создадим собственную коллекцию var userCollection = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; // Убедимся, что она не является массивом console.log(Array.isArray(userCollection)); // false // Посмотрим, что вернет нам concat console.log([].concat(userCollection, userCollection)); // Array [ Object(3), Object(3) ] // Теперь маленькая хитрость: // Установим внутреннее свойство, отвечающее // за результат IsConcatSpreadable(O), равным true userCollection[Symbol.isConcatSpreadable] = true; // Посмотрим, что теперь вернет нам concat console.log([].concat(userCollection, userCollection)); // Array [ "a", "b", "c", "a", "b", "c" ] // Или же сразу создадим коллекцию с указанным свойством: // Создадим собственную коллекцию var userCollection_1 = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.isConcatSpreadable]: true }; // Посмотрим, что вернет нам concat console.log([].concat(userCollection_1, userCollection_1)); // Array [ "a", "b", "c", "a", "b", "c" ]
Работает как часы. Давайте для чистоты эксперимента проверим это на существующем перечисляемом типе. Вспомним опять же про наш arguments
// arguments не указан, как spreadable function nonSpreadable() { return [].concat(arguments, arguments); } // arguments указан, как spreadable function spreadable() { arguments[Symbol.isConcatSpreadable] = true; return [].concat(arguments, arguments); } // Какой же массив вернется при объединении 2-х arguments? console.log(nonSpreadable(1, 2)); // Array [ Arguments, Arguments ] // А теперь?) console.log(spreadable(1, 2)); // Array [ 1, 2, 1, 2 ] console.log(spreadable(1, 2, 3)); // Array [ 1, 2, 3, 1, 2, 3 ]
Как видите, нам удалось с помощью concat объединить внутренние значения arguments в массив
Для очистки совести напомню: лезть таким вот образом во внутренние свойства объекта, изменяя логику его работы, - неправильно и кощунственно!
Почему? Давайте представим ситуацию: Вы видите массив. Как он должен вести себя с методом concat? Правильно, согласно уже не раз указанной справке он обязан раскрыться при объединении! А что если кто-то с полученным Вами массивом поигрался следующим образом: // Создадим обычные массивы var arr_0 = [1, 2]; var arr_1 = [3, 4]; // Убедимся, что это именно массивы, а не что-либо еще console.log(Array.isArray(arr_0)); // true console.log(Array.isArray(arr_1)); // true // Объединим их var arr_2 = [].concat(arr_0, arr_1); console.log(arr_2); // Array [ 1, 2, 3, 4 ] console.log(arr_2.length); // 4, как и полагается // А теперь сделаем парочку богопротивных действий arr_0[Symbol.isConcatSpreadable] = arr_1[Symbol.isConcatSpreadable] = false; // Объединим их снова var arr_3 = [].concat(arr_0, arr_1); console.log(arr_3); // Array [ Array(2), Array(2) ] console.log(arr_3.length); // 2, что-то пошло не так)
Только что мы изменили поведение стандартного типа, что очень и очень неправильно! Так что старайтесь избегать таких вот подходов!
Все, совесть очищена, так что можем злоупотреблять дальше :)

Еще один вариант решения Вашей проблемы:
На основе манипулирования результатом исполнения IsConcatSpreadable(O) Вашу задачу можно решить таким вот образом:
// Колдуем над isConcatSpreadable: dlg.panel_s.children[Symbol.isConcatSpreadable] = dlg.panel_0.children[Symbol.isConcatSpreadable] = true;
// Объединяем обе коллекции и три отдельных элемента var aPanel = [].concat( dlg.panel_s.children, dlg.panel_o.children, dlg.panel_u.checkbox_u1, dlg.panel_u.checkbox_u2, dlg.panel_u.checkbox_u3 );
И живой пример для наглядности:
// Сформируем свои коллекции var userCollection_0 = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; var userCollection_1 = { 0: 'd', 1: 'e', 2: 'f', 3: 'g', length: 4 }; // Колдуем над isConcatSpreadable userCollection_0[Symbol.isConcatSpreadable] = userCollection_1[Symbol.isConcatSpreadable] = true; // Объединяем обе коллекции и три отдельных элемента var aPanel = [].concat( userCollection_0, userCollection_1, 'h', 'i', 'j' ); // Посмотрим, что внутри console.log(aPanel); // Array [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" ] // Убедимся, что длина также совпадает console.log(aPanel.length); // 10 из 10)

Надеюсь, мой ответ помог Вам в разрешении проблемы! Удачи в Ваших начинаниях!

пятница, 12 апреля 2019 г.

Интересные возможности vanilla.js - доступ по id

OneOne!
Может для кого то я и не открыл Америку, но сам столкнулся впервые. Т.е. я не должен писать document.getElementById('oneOne'); Т.к. любые "переменные" и функции, вызванные через объект window - это его свойства и методы, значит все ИД всех елементов заносятся в свойства этого объекта.
Вопрос:
Когда это вообще появилось?
На сколько это хорошо в использовании (явно удобней, чем через get)?
Какие ограничения и по какой причине не используется массово?
Или просто сейчас мало кто использует чистый JS?
У кого есть ссылки на спецификацию и / или статьи на эту тему?


Ответ

Если элементу назначен специальный атрибут id, то можно получить его прямо по переменной с именем из значения id. Это поведение соответствует стандарту. Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты. Более правильной и общепринятой практикой является доступ к элементу вызовом document.getElementById("идентификатор").
Отсюда
As a general rule, relying on this will lead to brittle code. Which IDs end up mapping to this API can vary over time, as new features are added to the Web platform, for example. Instead of this, use document.getElementById() or document.querySelector().
Спецификация
P.S. К vanilla.js это отношения не имеет.

среда, 3 апреля 2019 г.

Различная реакция на переопределение идентификатора в браузерах Opera и Firefox

Как объяснить различную реакцию на выполнение следующего кода в разных браузерах?
var func = function() { console.log('1'); }; function func() { console.log('2'); } func();
При выполнении в Opera 36.0.2130.32 выдается ошибка:
"SyntaxError: Identifier 'func' has already been declared at https://static.jsbin.com/js/prod/runner-3.35.11.min.js:1:13891 at https://static.jsbin.com/js/prod/runner-3.35.11.min.js:1:10820"
При выполнении в Firefox 44.0.2 ошибка не выдается, и в консоль выводится значение "2". Вот ссылка на jsbin: https://jsbin.com/hijobojawe/1/edit?js,console,output


Ответ

Хорошая демонстрация неочевидного момента т.н. "var hoisting". Дело в том, что на стадии инициализации все объявления переменных, которые могут быть где угодно в теле функции, перемещаются наверх, в то время, как их назначение остаётся на месте.
Было:
function x(){ // ..здесь много кода var v = 'test'; // внезапно объявляем ещё одну переменную // ..здесь дальше код function f(){ /* тело ф-ии */ } // объявили функцию }
Стало:
function x(){ function f(){ /* тело ф-ии */ } // объявили функцию var v; // ..здесь много кода var v = 'test'; // внезапно объявляем ещё одну переменную // ..здесь дальше код }
Объявления функций так же могут находиться где-то ниже по коду внутри контекста, и при этом тоже «переезжают» вверх при исполнении. Причём, как пишут некоторые источники, становятся до объявления всех переменных.
Сначала function, потом var, потом v = 'test'
В вашем суперпримере ещё один вопрос: какое объявление function() сработает раньше – в правой части назначения переменной, или самостоятельное. Раньше сработает самостоятельное – function declaration. Назначение func = function(){..} выполнится в последнюю очередь.
Итак, сначала объявляется ф-я с 2, затем объявляется переменная func (и вызывает ошибку, т.к. с таким именем уже есть ф-я), а потом эта переменная получает значение ф-ии с 1. По-моему, это правильная реализация JS стандартов.
Тесты:
FireFox 45.0.1, ScratchPad, консоль FireBug: "1" Chrome 49.0.2623.87 (64-bit), Uncaught SyntaxError: Identifier 'func' has already been declared Safari 9.1 (11601.5.17.1), консоль: 1 Opera 34.0, консоль: 1 Opera 36.0, консоль: Uncaught SyntaxError: Identifier 'func' has already been declared