ES6 как синтаксический сахар

В информатике синтаксический сахар - это синтаксис языка программирования, который разработан для облегчения чтения и выражения. Это делает язык «слаще» для человеческого употребления: вещи могут быть выражены более ясно, лаконично или в альтернативном стиле, который некоторые могут предпочесть.

- википедия

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

Например, если мы сравним две версии функции из ES5 и ES6

// ES5 way
function test(param) {
  param = param || {};
  var foo = param.foo || 42;
  return 'The test result is ' + foo + '.';
}
// ES6 way
// using arrow function
// destructuring of the param
// default values
// template string
 const test = ({foo = 42} = {}) => 
  `The test result is ${foo}.`;

Если вы внимательно посмотрите на пример ES6, вы сразу увидите, что он намного более сортировщик, но помимо этого, он более декларативен. Значение по умолчанию параметра foo является частью сигнатуры функции, а не записывается как часть кода. Так что, если кто-то смотрит на сигнатуру функции, может сразу узнать, каковы значения по умолчанию.

Реализация ES6 также верна, если ES5 содержит скрытую ошибку. Можете ли вы найти ошибку в ES5 версии функционального теста? Дайте мне знать в комментариях.

Есть и другие функции, которые не очевидны в качестве синтаксического сахара, но они имеют совершенно другую реализацию с использованием существующих функций.

Классы ES6 полностью реализованы на основе прототипного механизма, обеспечивающего наследование в JavaScript. Итак, когда вы берете классы ES6 и используете babel для их компиляции, вы получаете код ES5, который использует прототип. Точно так же в ES6 есть совершенно новая реализация для итераторов, генераторов, отдыха и распространения и т. Д.

Что сложнее реализовать в ES5

Давайте рассмотрим вещи ES6, которые сложнее реализовать в ES5, прежде чем переходить к вещам ES6, которые вы не можете реализовать в ES5. Самый первый пример - это карта ES6, проверьте пример ниже:

var map = {};
var k1 = {}, 
    k2 = {};
map[k1] = 10;
console.log(map[k1]); // 10
console.log(map[k2]); // 10
console.log(map['[object Object]']); // 10

Реализовать словарь или карту в ES5 - это самый простой способ создать объект и присвоить этому объекту ключ и значение, но вы видите приведенный выше пример, в котором будут серьезные проблемы, если вы хотите использовать объект в качестве ключа.

Проблема с использованием объектов в качестве карты заключается в том, что все ключи по умолчанию являются строковыми, поэтому, когда вы передаете объект в качестве ключа, он будет преобразован в строку по умолчанию (‘[object Object]’) с использованием метода toString.

Теперь посмотрим на код карты ES6.

const map = new Map();
const k1 = {},
      k2 = {};
map.set(k1, 10);
console.log(map.get(k1)); // 10
console.log(map.get(k2)); // undefined

Карта ES6 может использовать ссылки на строки и объекты в качестве ключа, который решает проблему ключей, которые не являются строками, преобразованных в строки. Поэтому, когда вы устанавливаете значение карты с помощью ключа k1, карта ES6 использует свою ссылку в качестве ключа для хранения значения, и когда вы пытаетесь получить доступ к значению в ключе k2, она возвращает undefined, поскольку это не та же ссылка, что и k1

Итак, вопрос в том, можем ли мы реализовать Map в ES5? Простой ответ - да

function MyMap() {
  this.keys = [];
  this.values = [];
}
Map.prototype = {
  get: function(key) {
    var index = this.keys.indexOf(key);
    if(index !== -1) {
      return this.values[index];
    }
  },
  set: function(key, value) {
    var index = this.keys.indexOf(key);
    if(index !== -1) {
      this.values[index] = value;
    } else {
      this.keys.push(key);
      this.values.push(value);
    }
  },
  size: function() {
    return this.keys.length;
  }
};

Его реализация заключается в использовании двух массивов: один предназначен для ключей, а другой - для значений. Таким образом, мы легко, когда вызывается метод set, мы просто помещаем ключ и значение в эти массивы, поскольку мы нажимаем ключ и значение в том же индексе массива, поэтому при вызове метода get мы просто находим индекс ключа из ключей array и вернуть значение из массива values.

Проблема с этой реализацией заключается в производительности, чтобы получить любое простое значение для ключа из карты, которую мы имеем, перебираем массив ключей, чтобы найти индекс, который имеет тенденцию иметь O(N) временную сложность, где N - длина массива ключей.

Таким образом, вы можете реализовать Map с помощью ES5, но со значительным снижением производительности.

В ES5 ничего нельзя сделать

Давайте возьмем еще один пример карты ES6 с другим сценарием.

WeakMap или WeakSet

Предположим, вы хотите добавить данные поверх существующего объекта, не изменяя существующие, например, некоторые дополнительные данные в элементе dom

var map = new MyMap();
var ele = document.getElementById('ele');
map.set(ele, {x: 10, y: 'hey'});
console.log(map.get(ele).x); // 10

Аналогично методу .data из jQuery.

Проблема с описанным выше сценарием - это утечка памяти, которую можно увидеть, когда мы удалим элемент из DOM.

console.log(map.size()); // 1
ele.remove();
console.log(map.size()); // 1 not removed

Фактически, элемент удаляется из DOM, но его ссылка присутствует на карте, которая не может быть обработана сборщиком мусора, даже если его значение также не сборщик мусора. Таким образом, проверка размера карты остается прежней.

Чтобы реализовать исправление этой проблемы, мы внедрили наблюдателя для каждого объекта, переменных, элементов DOM и, что не так, если они будут удалены, мы можем указать их на карте и удалить, если они существуют. Что явно не очень хорошая реализация.

В ES6 это решается с помощью WeakMap, WeakMap использует слабые ссылки или слабо удерживаемые ссылки и не предотвращает сборку мусора в случае, если не осталось других ссылок для ключевых объектов. WeakMaps также достаточно умен, чтобы знать, когда ключевой объект собирается сборщиком мусора и больше не существует в памяти, они просто удаляют значения с карты, и если это единственная ссылка для значения, они также собирают сборщик мусора.

WeakSet также работает аналогично WeakMap на наборах, а не на картах.

Таким образом, WeakMap или WeakSet не могут быть реализованы в ES5, поскольку JavaScript не обеспечивает прямого контроля над сборщиком мусора.

Ознакомьтесь с приведенными ниже ссылками, чтобы узнать больше о WeakMap и WeakSet.





Прокси

Объект Proxy используется для определения настраиваемого поведения для основных операций (например, поиска свойств, присвоения, перечисления, вызова функций и т. Д.).

- MDN

Используя ES6 Proxy, мы можем управлять основным поведением объекта, таким как поиск его свойств, присвоение свойств или значений и т. Д. Если объект является функцией, мы также можем управлять его вызовом и т. Д. Что означает все операции, которые вы можете выполнить с объект ES6 дает вам полный контроль над ними в соответствии с вашими потребностями, также известный как метапрограммирование.

const pobj = {
  foo: 'hey',
  '1': 'one'
};
const double = new Proxy(pobj, {
  get(target, key) {
    return isNaN(key) ? target[key] : 2 * key;
  }
});
console.log(doble[3]); // 6
console.log(doble['1']); // one
console.log(doble['foo']); // hey

В приведенном выше примере прокси используется для изменения поведения средства получения свойств для исходного объекта pobj. Метод Proxy принимает два параметра: первый - это исходный объект, а второй - определение функции для его поведения. В котором я только что определил get для управления доступом к свойству из объекта.

Прокси-сервер не может быть реализован в ES5, поскольку JavaScriot не поддерживает перегрузку операторов, что ограничивает нас перегрузкой квадратных скобок.

Чтобы узнать больше о прокси-сервере ES6



Тип разговора

Когда дело доходит до преобразования объекта в строку и т. Д. JavaScript не предоставляет подходящих методов для этого, давайте рассмотрим пример.

const obj = {
  valueOf() { return 10;},
  toString() { return 'hello';}
};
console.log('' + obj); // 10
console.log(String(obj)); // hello

Итак, в приведенном выше примере мы пытаемся преобразовать объект в строку, и есть два распространенных способа сделать это, связавшись с объектом со строкой или используя глобальный конструктор объекта String в качестве функции. Это выглядит взаимозаменяемым, и многие разработчики думают, что любой из них даст одинаковый результат. Но они этого не делают. Присоединение объекта к строке вызовет метод valueOf объекта, а использование конструктора String вызовет метод toString объекта, который может привести к двум различным значениям, как в приведенном выше примере.

Это означает, что объект теряет контроль над своим диалогом и ведет себя по-разному в зависимости от способа преобразования. Для выполнения того же самого ES6 выставьте метод, который был внутренним в движке JavaScript, который называется примитивным механизмом и использует метод Symbol.toPremitive для доступа.

const obj = {
  [Symbol.toPrimitive](hint){
    // hint: 'default' | 'string' | 'number'
    
    if(hint === 'number') {
      return 10;
    }
    return null;
  }
};
console.log(new Number(obj)); // [Number: 10]

Узнать больше о Symbol.toPrimitive



Есть несколько других функций ES6, которые не могут быть реализованы в ES5, такие как оптимизация хвостового вызова и т. Д.

Делитесь своими мыслями в комментариях, а также делитесь с друзьями, не забывайте звонить :)