Създаване на simbols

Simbols са уникални сред JavaScript примитивните типове, с това че не разполагат с литерална форма, като true за булев тип или 42 за числа. Можете да създадете simbol с помощта на глобалната Symbol функция, както в този пример:

let firstName = Symbol();
let person = {};

person[firstName] = "Nicholas";
console.log(person[firstName]);     // "Nicholas"
	 		

В този пример simbol firstName е създаден и се използва за присвояване от новото свойство на person обекта. Този simbol трябва да се използва всеки път, когато искате да получите достъп до същото свойство. Именуването на променливата на simbol подходящо е добра идея, така че лесно да разберем какво представлява simbol.

worning
Тъй като simbols са примитивни стойности, извикването на new Symbol() хвърля грешка. Възможно е да се създаде инстанция на Symbol чрез new Object(yourSymbol), но не е ясно, кога това би било полезно.

Функцията Symbol приема незадължителен аргумент с описанието на simbol. Самото описание не може да се използва за достъп до свойството, но се използва с цел на отстраняване на грешки. Например:

let firstName = Symbol("first name");
let person = {};

person[firstName] = "Nicholas";

console.log("first name" in person);    // false
console.log(person[firstName]);         // "Nicholas"
console.log(firstName);                 // "Symbol(first name)"
	 		

Описанието на simbol се съхранява във вътрешно свойство, наречено [[Description]]. Това свойство се чете, когато метода toString() за simbols се извика пряко или косвено. В този пример, console.log извиква косвено simbol метод toString върху firstName, така че описанието да бъде отпечатано на козолата. Няма друг възможен начин за достъп до [[Description]] директно с код. Затова е препоръчително винаги да се предостави описание за четене на simbol, така грешките се отстраняват по лесно.

Идентифициране на simbols

Тъй като, simbolsте са примитивни стойности, можете да използвате оператора typeof, за да определите дали дадена променлива съдържа simbol. ECMAScript 6 разширява typeof да връща "symbol", когато се използва за simbol. Например:

let symbol = Symbol("test symbol");
console.log(typeof symbol);    // "symbol"
	 		

Докато има и други косвени начини за определяне дали дадена променлива е simbol, typeof е най-точен и предпочитан начин затова.

Използване на simbols

Можете да използвате simbols навсякъде, където трябва да изчислявате име на свойство. Вече виждяхте използването на скоби нотация със simbols в тази глава, но можете да използвате simbols за изчисляване на имена на свойства за обекти, както с Object.defineProperty() така и с Object.defineProperties(), като това:

let firstName = Symbol("first name");
// използва изчисляване на свойство за обект литерал 
let person = {
    [firstName]: "Nicholas"
};

// прави свойството само за четене
Object.defineProperty(person, firstName, { writable: false  });

let lastName = Symbol("last name");

Object.defineProperties(person, {
    [lastName]: {
        value: "Zakas",
        writable: false 
    }
});

console.log(person[firstName]);     // "Nicholas"
console.log(person[lastName]);      // "Zakas"
	 		

Този пример първо използва изчисляване на свойство за обект, за създаване на firstName simbol свойството. Свойството е създадено не номерирано, за разлика от изчислените свойства създадени с помощта на nonsymbol имена. Следващия ред, прави свойството само за четене. По-късно, свойството на simbol само за четене lastName е създадено с помощта на Object.defineProperties() метода. Изчисляването на свойство за обект се използва отново, само че този път е част от втория аргумент на Object.defineProperties().

Докато simbols могат да бъдат използвани на всяко място, където е позволено изчисляване на имена за свойства, трябва да има система за споделяне на тези simbols между различните части от код, за да ги използваме ефективно.

Споделяне на simbols

Може да откриете, че искате различни части от вашия код да използват едни и същи simbols. Да предположим, че имате два различни типа обекти във вашето приложение, които трябва да използват едно и също simbol свойство, да представлява уникален идентификатор. Следенето на simbols във файлове или големи база код данни, може да бъде трудно и податливо на грешки. Ето защо ECMAScript 6 осигурява глобален регистър на simbols, до който може да получите достъп по всяко време.

Когато искате да създадете simbol, който да бъде споделен, трябва да използвате Symbol.for() вместо Symbol(). Метода Symbol.for() приема един единствен параметър, който е string идентификатор за simbol, който искате да създадете. Този параметър се използва, и като описание на simbol. Например:

let uid = Symbol.for("uid");
let object = {};

object[uid] = "12345";

console.log(object[uid]);       // "12345"
console.log(uid);               // "Symbol(uid)"
	    	

Метода Symbol.for() първо търси в глобалния регистър за simbols, за да види дали simbol с ключ "uid" съществува. Ако е така, метода връща съществуващия simbol. Ако няма съществуващ такъв simbol, тогава създава нов simbol и го регистрира в глобалния регистър за simbols, използвайки указания ключ. След това връща новия simbol. Това означава, че следващите извиквания на Symbol.for() ще използват един и същи ключ за връщане на същия simbol:

let uid = Symbol.for("uid");
let object = {
    [uid]: "12345"
};

console.log(object[uid]);       // "12345"
console.log(uid);               // "Symbol(uid)"

let uid2 = Symbol.for("uid");

console.log(uid === uid2);      // true
console.log(object[uid2]);      // "12345"
console.log(uid2);              // "Symbol(uid)"
	    		

В този пример, uid и uid2 съдържат един и същи simbol, така че могат да се използват, като взаимозаменяеми. Първото извикване на Symbol.for() създава simbol, а второто извлича simbol от регистъра за глобални simbols.

Друг уникален аспект на споделените simbols е, че можете да извлечете ключа, свързан със simbol в регистъра за глобални simbols с помощта на Symbol.keyFor(), например:

let uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid));    // "uid"

let uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2));   // "uid"

let uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3));   // undefined
	    	

Забележете, че и двете uid и uid2 връщат ключ "uid". Simbol uid3 не съществува в регистъра за глобални simbols, така че няма ключ свързан с него и Symbol.keyFor() връща undefined.

worning
Регистърът за глобални simbols е споделена среда, точно както глобалния обхват. Така че, не могат да се правят предположения, какво присъства или не в тази среда. Вие трябва да използвате пространството на имена за ключове на simbols, за да се намали вероятноста от съвпадения при използване на компонентите от трета страна. Например, jQuery кода може да използва "jquery.", за да сложи префикс на всички ключове, като "jquery.element" и други подобни.

Simbol коригиране на типа

Коригирането на типа е значителна част от JavaScript и има много гъвкаво отношение към способноста на езика да коригира един тип данни в друг. Simbols, обаче не са толкова гъвкави, когато става въпрос за коригиране, защото не съществува логическа еквивалентност на simbol в другите типове. По-конкретно, simbols не могат да бъдат коригирани в strings или numbers, така че не могат случайно да се използват, като свойства, които в противен случай биха се очаквали да се държат, като simbols.

Примерите в тази глава използват console.log() за да покажат на изхода, че simbol работи, защото console.log() извиква String() върху simbols, за да създаде полезен резултат. Можете да използвате String() директно за да получите същия резултат. Например:

let uid = Symbol.for("uid"),
    desc = String(uid);

console.log(desc);              // "Symbol(uid)"
	    	

Функцията String() извиква uid.toString() и връща описание на simbol в string. Ако се опитате да свържете директно simbol със string, ще бъде хвърлена грешка:

let uid = Symbol.for("uid"),
    desc = uid + "";            // error!
	    	

Конкатенацията на uid с празен string изисква, първо uid да се коригира в string. Грешка се хвърля, когато е открито коригиране, предотвратявайки използването му по този начин.

По същия начин, не може да се коригира simbol в номер. Всички математически оператори предизвикват грешка, когато се прилагат за simbol. Например:

let uid = Symbol.for("uid"),
    sum = uid / 1;            // error!
	    	

Този пример се опитва да раздели simbol с 1, което води до грешка. Грешка се хвърля независимо от използвания математически оператор (логическите оператори не хвърлят грешка, защото всички simbols се считат за еквивалентни на true, точно както всяка друга не-празна стойност в JavaScript).

Извличане на simbol от обект

Методите Object.keys() и Object.getOwnPropertyNames() могат да извличат всички имена на свойства в даден обект. Първия метод връща всички номерирани имена на свойства, а втория връща всички свойства, независимо от номерирането. Нито един от тези методи, не връща свойства на simbol, за да се запази тяхната ECMAScript 5 функционалност. Вместо тях е добавен метода Object.getOwnPropertySymbols() в ECMAScript 6, който позволява извличане на свойства на simbols от даден обект.

Върнатата стойност от Object.getOwnPropertySymbols() е array от собствени simbol свойства, например:

let uid = Symbol.for("uid");
let object = {
    [uid]: "12345"
};

let symbols = Object.getOwnPropertySymbols(object);

console.log(symbols.length);        // 1
console.log(symbols[0]);            // "Symbol(uid)"
console.log(object[symbols[0]]);    // "12345"
	    	

В този код, object има едно единствено simbol свойство, наречено uid. Върнатия array от Object.getOwnPropertySymbols() съдържа само този simbol.

Всички обекти започват с нулеви simbol свойства, но обектите могат да наследяват simbol качества от своите прототипи. ECMAScript 6 предварително настройва няколко такива свойства, с помощта на well-known simbols.

Излагане на вътрешни операции с Well-Known simbols

Важна тема за ECMAScript 5 е да изложи и определи някои "магически" части на JavaScript - частите, които програмистите не могат да подражават в момента. ECMAScript 6 следва тази традиция чрез излагане на повече от преди на вътрешната логика на езика, главно чрез използване на прототипа на simbol свойства, за определяне на основното поведение на някои обекти.

ECMAScript 6 включва предварително зададени simbols, наречени well-known simbols, които представляват общо поведение в JavaScript и които преди това са били обмисляни само за вътрешни операции. Всеки well-known simbol е представен от свойство на Symbol обекта, например като Symbol.create

Well-known simbols са:

  • Symbol.hasInstance - метод използван от instanceof да определи наследството на даден обект.
  • Symbol.isConcatSpreadable - булева стойност, показваща дали употребата на Array.prototype.concat() ще изглади елементите на колекцията, ако колекцията се подаде, като параметър на Array.prototype.concat().
  • Symbol.iterator - метод, който връща iterator (итераторите са обхванати в Глава 8 - Iterators и Generators).
  • Symbol.match - метод използван от String.prototype.match() за сравняване на strings.
  • Symbol.replace - метод използван от String.prototype.replace() за заместване на substrings.
  • Symbol.search - метод използван от String.prototype.search() за намиране на substrings.
  • Symbol.species - конструктор за правене на производни обекти (производни обекти са обхванати в Глава 9 - Класове).
  • Symbol.split - метод използван от String.prototype.split() за разделяне на strings.
  • Symbol.toPrimitive - метод който връща представянето на примитивна стойност в обекта.
  • Symbol.toStringTag - string използван от Object.prototype.toString() за създаване на описание на обект.
  • Symbol.unscopables - обект, чиито свойства са имената на свойствата на обекта, които не трябва да бъдат включени в with изявление.

Някои често използвани well-known simbols са обсъдени по-долу в раздела, а други са разгледани в останалата част от книгата, за да ги държи в правилния контекст.

info
Заменящия метод дефиниран с well-known simbol, променя обикновен обект в екзотичен, защото това променя някои вътрешни поведения по подразбиране. Това няма практическо отражение върху вашия код, като резултат, просто променя начина, по който спецификация описва обекта.

Symbol.hasInstance

Всяка функция има Symbol.hasInstance метод, които определя дали даден обект е инстанция на тази функция. Метода е дефиниран от Function.prototype, така че всички функции наследяват това поведение по подразбиране за instanceof свойството. В Symbol.hasInstance, самото свойство се определя, като nonwritable и nonconfigurable, като nonenumerable гарантира, че не се презаписва по погрешка.

Метода Symbol.hasInstance приема един единствен аргумент: стойността за проверка. Той връща true, ако подадената стойност е инстанция на функцията. За да разберете, как Symbol.hasInstance работи, нека да разгледаме следния код:

obj instanceof Array;
	    	

Този код е еквивалентен на:

Array[Symbol.hasInstance](obj);
	    	

По същество, ECMAScript 6 предефинира instanceof оператора, като кратък запис на синтаксиса за извикване на този метод. И сега, когато имате участващ извикан метод, всъщност можете да промените начина на работа на instanceof.

Да предположим, че искате да дефинирате функция, която претендира, че няма обект, като инстанция. Можете да го направите hardcoding връщайки от Symbol.hasInstance false, като:

function MyObject() {
    // ...
}

Object.defineProperty(MyObject, Symbol.hasInstance, {
    value: function(v) {
        return false;
    }
});

let obj = new MyObject();

console.log(obj instanceof MyObject);       // false
	    	

Трябва да използвате Object.defineProperty() за презаписване на nonwritable свойство, така че, този пример да използва този метод, за да презапише Symbol.hasInstance с новата функция. Новата функцията винаги ще връща false, въпреки, че obj всъщност е инстанция на MyObject, защото оператора instanceof връща false след извикването на Object.defineProperty().

Разбира се, може също да инспектира стойността и да реши дали стойността трябва да се разглежда, като нейна инстанция въз основа на всяко произволно състояние. Например, може би числа със стойност между 1 и 100, трябва да се считат за инстанция на специален вид номер. За да се постигне това поведение, може да се напише код, подобен на този::

function SpecialNumber() {
    // ...
}

Object.defineProperty(SpecialNumber, Symbol.hasInstance, {
    value: function(v) {
        return (v instanceof Number) && (v >= 1 && v <= 100);
    }
});

let two = new Number(2),
    zero = new Number(0);

console.log(two instanceof SpecialNumber);    // true
console.log(zero instanceof SpecialNumber);   // false
	    	

Този код дефинира Symbol.hasInstance метод, който връща true, ако стойността е инстанция на Number и е в диапазона между 1 и 100. По този начин, позволява на SpecialNumber да претендира, че two е нейна инстанция въпреки, че няма пряка връзка между функцията SpecialNumber и two променливата. Имайте в предвид, че левият операнд на instanceof трябва да бъде обект, за да задейства извикването към Symbol.hasInstance, докато nonobjects причинява instanceof просто да връща false през цялото време.

worning
Можете също така да замените по подразбиране Symbol.hasInstance свойството, на всички вградени функции, като Date и Error. Това не се препоръчва, тъй като ефектите върху вашия код може да бъдат неочаквани и объркващи. Добра идея е, да заменяте Symbol.hasInstance на вашите собствени функции само когато е необходимо.

Symbol.isConcatSpreadable

JavaScript arrays имат concat() метод, който има за цел да залепи два arrays заедно, например:

let colors1 = [ "red", "green" ],
    colors2 = colors1.concat([ "blue", "black" ]);

console.log(colors2.length);    // 4
console.log(colors2);        // ["red","green","blue","black"]
	    	

Този код конкатенира нов array от края на colors1 и създава colors2, в единичен array с всички елементи от двата arrays. Обаче, concat() може да приема non-arrays аргументи и в този случай тези аргументи просто се добавят към края на array. Например:

let colors1 = [ "red", "green" ],
    colors2 = colors1.concat([ "blue", "black" ], "brown");

console.log(colors2.length);    // 5
console.log(colors2);           // ["red","green","blue","black","brown"]
	    	

Тука, допълнителния аргумент "brown" се подава на concat() и се превръща в петия елемент на colors2. Защо array от аргументи се третира по различен начин от string аргументи? Спецификацията казва, че arrays автоматично се разделят на техните отделни елементи, а всички други типове не. Преди ECMAScript 6, нямаше начин да се коригира това поведение.

Свойството Symbol.isConcatSpreadable е булева стойност, показваща дали даден обект има lenght свойство и цифрови ключове и дали тези номерирани стойности на свойства трябва да бъдат добавяни поотделно към резултата с concat(). За разлика от други well-known simbols, това simbol свойство не се дава на всички стандартни обекти по подразбиране. Вместо това, то е достъпно, като начин да се увеличи работата на concat() върху някои типове обекти, като поведение на късо съединение по подразбиране. Можте да определите всеки тип да се държи, както arrays в concat(), също като:

let collection = {
    0: "Hello",
    1: "world",
    length: 2,
    [Symbol.isConcatSpreadable]: true
};

let messages = [ "Hi" ].concat(collection);

console.log(messages.length);    // 3
console.log(messages);           // ["hi","Hello","world"]
	    	

Обекта collection в този пример, е настроен да изглежда, като array: тoй има length свойство и два цифрови ключа. Свойството Symbol.isConcatSpreadable е определено на true за да покаже, че стойностите на свойствата трябва да се добавят, като отделни елементи към array. Когато collection се подаде на concat() метода, резултата е array, който съдържа "Hello" и "world", като отделни елементи след "hi" елемента.

info
Може също да настроите Symbol.isConcatSpreadable на false за array от подкласове, за да се предотврати елементите да бъдат слепени посредством concat(). Под-класовете се обсъждат в Глава 9.

Symbol.match, Symbol.replace, Symbol.search и Symbol.split

Винаги е имало тясна връзка между strings и регулярни изрази в JavaScript. Типа string по специално, има няколко метода, които приемат регулярни изрази, като аргументи.

  • match(regex) - определя дали даден string съвпада с регулярен израз.
  • replace(regex, replacement) - замества съвпадащия регулярен израз със заменящия
  • search(regex) - открива съвпадение на регулярния израз вътре в string
  • split(regex) - разделя string в array по съвпаденията на регулярния израз

Преди ECMAScript 6, начина по който тези методи са взаимодействали с регулярния израз е бил скрит от програмистите, не оставяйки никакъв начин да се имитира това, което регулярния израз прави с помощта на определени от програмиста обекти. ECMAScript 6 дефинира четири simbol, които отговарят на тези четири метода, ефективно отговарящи на поведението на вградения RegExp обект.

Symbol.match, Symbol.replace, Symbol.search и Symbol.split представляват методи на аргумента на регулярния израз, който трябва да се извика, като първи аргумент на методите match(), replace(), search() и split(), съответно. Четирите simbol свойства са дефинирани в RegExp.prototype , като те трябва да се използват по подразбиране при изпълнение на методите.

Знаейки това, можете да създадете обект за използване на методите на string по начин, който е подобен на регулярен израз. За да направите това, може да използвате следните simbol функции в кода:

  • Symbol.match - функция, която приема за аргумент string и връща array от съвпадения или null, ако не е намерила съвпадение.
  • Symbol.replace - функция, която приема за аргумент string и string за замяна и връща string.
  • Symbol.search - функция, която приема за аргумент string и връща номера с индекса на съвпадението или -1, ако не е намерила съвпадение.
  • Symbol.split- функция, която приема за аргумент string и връща array съдържащ парчета от string-а, разделени от съвпаденията

Възможността да зададете тези свойства на даден обект ви позволява да създавате обекти, които прилагат модел за съвпадение без регулярни изрази и ги използват в методи, които очакват регулярни изрази. Ето един пример, който показва тези simbols в действие:

// ефективно еквивалентно на /^.{10}$/
let hasLengthOf10 = {
    [Symbol.match]: function(value) {
        return value.length === 10 ? [value.substring(0, 10)] : null;
    },
    [Symbol.replace]: function(value, replacement) {
        return value.length === 10 ? replacement + value.substring(10) : value;
    },
    [Symbol.search]: function(value) {
        return value.length === 10 ? 0 : -1;
    },
    [Symbol.split]: function(value) {
        return value.length === 10 ? ["", ""] : [value];
    }
};

let message1 = "Hello world",   // 11 characters
    message2 = "Hello John";    // 10 characters


let match1 = message1.match(hasLengthOf10),
    match2 = message2.match(hasLengthOf10);

console.log(match1);            // null
console.log(match2);            // ["Hello John"]

let replace1 = message1.replace(hasLengthOf10),
    replace2 = message2.replace(hasLengthOf10);

console.log(replace1);          // "Hello world"
console.log(replace2);          // "Hello John"

let search1 = message1.search(hasLengthOf10),
    search2 = message2.search(hasLengthOf10);

console.log(search1);           // -1
console.log(search2);           // 0

let split1 = message1.split(hasLengthOf10),
    split2 = message2.split(hasLengthOf10);

console.log(split1);            // ["Hello world"]
console.log(split2);            // ["", ""]
	   	

Тука, hasLengthOf10 обекта е предназначен да работи подобно на регулярен израз, който съвпада, когато дължината ( length) на string е точно 10. Всеки от четирите метода се осъществява чрез използване на съответните simbols и след това се извикват съответните методи на двата strings. Първия string, message1, има 11 знака и така няма да съвпадне; втория string, message2, има 10 знака и така ще съвпадне. Въпреки, че не е регулярен израз hasLengthOf10 се подава на всеки string метод и се използва правилно поради допълнителните методи.

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

Symbol.toPrimitive

JavaScript често се опитва да превърне обекти в примитивни стойности по подразбиране, когато се прилагат определени операции. Например, когато се сравнява string с даден обект с помощта на двойното равно (==), обекта се превръща в примитивна стойност преди това сравняване. Точно каква примитивна стойност трябва да се използва е вътрешна операция, която ECMAScript 6 излага чрез метода Symbol.toPrimitive.

Метода Symbol.toPrimitive се определя за прототип на всеки стандартен тип и предписва точното поведение, когато обектът се превръща в примитивен тип. Когато е необходимо примитивно преобразуване, се извиква Symbol.toPrimitive с един аргумент, посочен като hint в спецификацията. Аргумента hint е един от следните три string стойности:

  • Ако hint e "number" - Symbol.toPrimitive трябва да върнe номер
  • Ако hint e "string" - трябва да върне string
  • Ако hint е "default" - операцията няма предпочитания за вида

За повечето стандартни обекти, поведението на number режим има следния приоритет:

  1. Извиква valueOf(), и ако резултата е примитивна стойност я връща.
  2. В противен случай, извиква toString() и ако резултата е примитивна стойност я връща.
  3. В противен случай хвърля грешка.

По същия начин, за повечето стандартни обекти поведението на string режим има следния приоритет:

  1. Извиква toString() и ако резултата е примитивна стойност я връща.
  2. В противен случай, извиква valueOf() и ако резултата е примитивна стойност я връща.
  3. В противен случай хвърля грешка.

В много случаи стандартните обекти третират режима по подразбиране, като равностоен на number режима (с изключение на Date, която се отнася към режима по подразбиране, като равностоен на string режима). Чрез дефиниране на Symbol.toPrimitive, можем да коригираме това поведение по подразбиране.

info
Режим по подразбиране се използва само за ==, + и при подаване на единичен аргумент към Date конструктора. Повечето операции използват string или number режим.

За да замените поведението по подразбиране, използвайте Symbol.toPrimitive и задайте функция, като негова стойност, Например:

function Temperature(degrees) {
    this.degrees = degrees;
}

Temperature.prototype[Symbol.toPrimitive] = function(hint) {

    switch (hint) {
        case "string":
            return this.degrees + "\u00b0"; // degrees symbol

        case "number":
            return this.degrees;

        case "default":
            return this.degrees + " degrees";
    }
};

let freezing = new Temperature(32);

console.log(freezing + "!");            // "32 degrees!"
console.log(freezing / 2);              // 16
console.log(String(freezing));          // "32째"
	   	

Този пример дефинира Temperature конструктор и заменя метода по подразбиране с Symbol.toPrimitive на прототипа. Различна стойност се връща в зависимост от това дали hint аргумента е string, number или default стойност. В string режима функцията Temperature() връща температурата с Unicode характера за градус. В number режима връща само цифрова стойност, а в режима по подразбиране добавя думата “degrees” след цифрите.

Всяко от твърдения в console.log предизвиква различна hint аргумент стойност. Операторът + предизвиква default режим, чрез определяне на hint към "default", операторът / предизвиква number режим, чрез определяне hint към "number" и String () функцията предизвиква string режим, чрез определяне hint към 'string'. Въпреки че е възможно да върне различни стойности за всичките три режима, много по-често използвано е да настроите default режима да е същия, като за string или number режим.

Symbol.toStringTag

Един от най-интересните проблеми на JavaScript е наличието на множество глобални среди на изпълнение. Това се случва в уеб-браузъри, когато една страница включва вградена рамка на друга страница, като всяка от тях има собствена среда на изпълнение. В повечето случаи това не е проблем, тъй като данните могат да се предават напред и назад между среди без да причиняват безпокойство. Проблемът възниква, когато се опитваме да се идентифицираме типа на обекта, с който имаме работа след като е бил прехвърлен между различните обекти.

В каноничен пример, това е подаване на array от вградената рамка в съдържащата страница или обратно. В ECMAScript 6 терминологията, вградена рамка и съдържаща страница, всяка представлява различна област, която е среда за изпълнение на JavaScript. Всяка област има свой собствен глобален обхват с негово собствено копие на глобални обекти. В зависимост от това в коя област се създава array, се определя вида на array. Когато това е в различна среда на изпълнение instanceof Array връща false, защото array е създаден с конструктор от различни среди.

Временно решение за идентифициране на проблема

Сблъсквайки се с този проблем програмистите много скоро намериха добър начин за идентифициране на arrays. Те открили, че когато се извика стандартния метод toString() върху обект, се връща винаги предвидим string. По този начин , много JavaScript библиотеки, започнаха да включват функция, която работи подобно на това:

function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}

console.log(isArray([]));   // true
	   	

Това може да изглежда малко, като кръгово движение, но в действителност е установено, че работи добре за идентифициране на arrays във всички браузъри. Метода toString() за arrays не е много полезен за идентифициране на обект, тъй като връща string представяне на елементите на обекта, които съдържа. Но методa toString () на Object.prototype има тази идея: той включва вътрешно-дефинирано име, наречено [[class]] във върнатия резултат. Програмистите могат да използват този метод върху даден обект, за да извлекат, това което околната среда на JavaScript смята за тип на данните на обекта.

Програмистите бързо осъзнаха, че тъй като няма начин да променят това поведение, е възможно да се използва същия подход, за да направи разлика между местни обекти и тези създадени от програмисти. Най-важния от тях е ECMAScript 5 JSON обекта.

Преди ECMAScript 5, много употребяван е Douglas Crockford’s json2.js, който създава глобален JSON обект. Когато браузърите започнаха да прилагат глобалния JSON обект, се наложи да се каже дали глобалния JSON е предоставен от самата среда на JavaScript или чрез някоя друга библиотека. Използвайки същата техника, която показах с функцията isArray(), много програмисти създадоха функции, като тази:

function supportsNativeJSON() {
    return typeof JSON !== "undefined" &&
      Object.prototype.toString.call(JSON) === "[object JSON]";
}
	   	

Това, е същата характеристика на Object.prototype, която позволява на програмистите да идентифицират arrays в границите на вградената рамка и също предоставя начин да се разбере дали JSON е местен обект или не. Не-местен JSON обект ще върне [object Object], докато местния обект ще върне [object JSON]. Този подход се превърна в стандарт за идентифициране на местни обекти.

Отговора на ECMAScript 6

ECMAScript 6 предефинира това поведение чрез Symbol.toStringTag simbol. Този simbol представлява свойство на всеки обект, което определя, каква стойност трябва да се произведе, когато Object.prototype.toString.call() се извика върху него. За array, стойността която функцията връща се обяснява със съхранение на "Array" в Symbol.toStringTag свойството.

По същия начин, можете да определите Symbol.toStringTag стойността на вашите собствени обекти.

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Person";

let me = new Person("Nicholas");

console.log(me.toString());                         // "[object Person]"
console.log(Object.prototype.toString.call(me));    // "[object Person]"
	   	

В този пример, свойството Symbol.toStringTag се дефинира в Person.prototype, за да осигури поведение по подразбиране за създаване на string представяне. Тъй като, Person.prototype наследява Object.prototype.toString() метода, стойността върната от Symbol.toStringTag се използва също, като извикване на me.toString() метода. Въпреки това, все още можете да определите свой toString() метод, който осигурява различно поведение, без да засяга използването на Object.prototype.toString.call() метода. Ето как може да изглежда това:

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Person";

Person.prototype.toString = function() {
    return this.name;
};

let me = new Person("Nicholas");

console.log(me.toString());                         // "Nicholas"
console.log(Object.prototype.toString.call(me));    // "[object Person]"
	   	

Този контекст дефинира Person.prototype.toString() да връща стойността на name свойството. Тъй като Person инстанцията вече не се наследява от Object.prototype.toString() метода, извиквайки me.toString() проявява различно поведение.

info
Всички обекти наследяват Symbol.toStringTag от Object.prototype, освен ако не е посочено нещо друго. Стойността на свойството по подразбиране е "Object".

Няма ограничения с кои стойности може да бъде използван Symbol.toStringTag върху определените от програмиста обекти. Например, нищо не пречи да използваме "Array", като стойност на Symbol.toStringTag, също като:

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Array";

Person.prototype.toString = function() {
    return this.name;
};

let me = new Person("Nicholas");

console.log(me.toString());                         // "Nicholas"
console.log(Object.prototype.toString.call(me));    // "[object Array]"
	   	

В този код, резултата от извикването на Object.prototype.toString() е "[object Array]", което е същото, като което ще получим от действителен array. Това подчертава факта, че Object.prototype.toString() вече не е напълно надежден начин за идентифициране на типа на даден обект.

Възможно е да се промени string маркера на местния обект, чрез присвояване на Symbol.toStringTag на неговия прототип. Например:

Array.prototype[Symbol.toStringTag] = "Magic";

let values = [];

console.log(Object.prototype.toString.call(values));    // "[object Magic]"
	   	

Въпреки, че Symbol.toStringTag се презаписва за array в този пример, извикването на Object.prototype.toString() води до "[object Magic]". Въпреки, че не се препоръчва да се променят вградени обекти по този начин, няма нищо в езика, което да го забранява.

Symbol.unscopables

The with изявлението е една от най-спорните части в JavaScript. Първоначално предназначено за избягване на повторно писане, with изявлението по-късно става открито критикувано за правенето на кода по-трудно разбираем и за отрицателните последици при изпълнение, както и това че е склонно към грешки.

В резултат на това, with изявлението не е разширено в strict mode, което се отразява на класове и модули, които са под strict mode по подразбиране, без право на изключения.

Въпреки, че няма бъдеще за with изявлението, ECMAScript 6 все още го поддържа в nonstrict mode за обратна съвместимост и като такова, трябва да намери начин да позволи на кода да продължи да работи правилно с използването на with.

За да се разбере сложността на тази задача, да разгледаме следния код:

let values = [1, 2, 3],
    colors = ["red", "green", "blue"],
    color = "black";

with(colors) {
    push(color);
    push(...values);
}

console.log(colors);    // ["red", "green", "blue", "black", 1, 2, 3]
	   	

В този пример, двете извиквания на push() вътре в with изявлението, са еквивалентни на colors.push(), защото with изявлението добавя puhs в локалното обвързване. Препратката към color се отнася до променлива, създадена извън whit, което прави values референтна.

ECMAScript 6 добавя метода values() за arrays (Метода values() е разгледан по-подробно в Глава 8 - Iterators и Generators). Това би означавало, че в ECMAScript 6 среда, препратката към values в рамките на with изявлението, трябва да се отнася не към локалната променлива values, а към стойностите на метода values на array, което ще прекъсне кода. Ето, защо Symbol.unscopables съществува.

Symbol.unscopables се използва за Array.prototype за да посочи, кои свойства не трябва да създават обвързвания във вътрешността на with изявление. Когато е налице, Symbol.unscopables е обект, чийто ключове са идентификатори за пропускане на whit обвързвания и чийто стойности са true за прилагане на блока. Тук е по подразбиране за arrays:

// вградено в ECMAScript 6 по подразбиране
Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null), {
    copyWithin: true,
    entries: true,
    fill: true,
    find, true,
    findIndex: true,
    keys: true,
    values: true
});
	   	

Обекта Symbol.unscopables има null прототип създаден от Object.create(null) и съдържа всички нови методи за arrays в ECMAScript 6 (тези методи са разгледани подробно в Глава 8 - Iterators и Generators и в Глава 10 - Arrays). Обвързванията за тези методи не са създадени вътре в with изявлението, което позволява на стария код да продължи да работи без никакъв проблем.

По принцип, не трябва да се дефинира Symbol.unscopables за вашите обекти, освен ако не използвате with изявление или се правят промени в съществуващ обект във вашия база код.