ES6.md 10 KB

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

Синтаксический сахар - ряд усовершенствований в синтаксисе языка, не несущих семантического смысла. Обычно синтаксический сахар легко заменяется более пространным и менее прозрачным синтаксисом, и при этом код работает так же. Отличия между ES5 и ES6 в-основном в сахаре и заключаются.

Стрелочные функции.

Синтаксически это =>, параметры до этой пары символов, выражение-результат функции - после:

var sqr = x => x*x //один параметр, скобки не нужны
alert(sqr(2))

var mul = (x,y) => x*y //два параметра - нужны скобки
alert(mul(2,5))

var objectCreator = (x,y) => ({x, y}) //создает объект {x: x, y: y}. Если забыть скобки - то JS воспримет {x,y} как блок кода, и без return вернет undefined
console.log(objectCreator(4,5))

var longFunc = (x,y) => { //почти обычная функция
    let result = prompt(x,y)
    return result;
}

longFunc('check', 'text')

({arrowFuncMaker: function(){ 
        return () => {console.log(this), this.oldSchoolSklerotikMaker()()}
    },
  oldSchoolSklerotikMaker: function(){
        return function(){
            console.log(this)
        }
  }
}).arrowFuncMaker()() //стрелочные функции помнят this на момент создания.

Свойства стрелочных функций:

  • Они анонимные. Синтаксис не предусматривает имени функции.
  • Они помнят this.

В целом стрелочные функции могут быть заменены следующим синтаксисом ES5:

var func = (function(x, y, z){
    console.log(this)
    return x + y + z
}).bind(this) //bind прибивает this при объявлении функции

Параметры по умолчанию.

Знакомы некоторым по другим языкам:

var pow = sqr = (x, power = 2) => Math.pow(x, power)
console.log(pow(2,5))
console.log(sqr(2)) //квадрат по умолчанию

В ES5 для этого обычно использовали проверку на undefined:

var pow = sqr = function(x, power){ 
                    return Math.pow(x, power || 2)
                }
console.log(pow(2,5))
console.log(sqr(2)) //квадрат по умолчанию

rest/spread ...

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

var func = function(a,b,...rest){
    console.log(a,b,rest,arguments)
}

func(1,2,3)
func(...[1,2,3,4,5])

Подобные возможности в ES5 реализуются через arguments и apply

Литералы объектов

    var name = 'John'
var age  = Math.round(Math.random()*100)
var obj = {
    name, //name: name
    ["a" + 'ge']: age, //выражение в ключе,
    greet() {
        alert(this.name + ', ' + this.age)
    }
}
obj.greet()

Деструктуризация

Деструктуризация позволяет доставать значения полей объектов и массивов по нескольку за одну операцию и избавляет вас от заведения временной переменной для хранения массива или объекта:

var [a,b,c] = [1,2,3] //создается три переменных из массива
var {rand1: x, rand2: y, rand3: z} = {rand1: Math.random(), rand2: Math.random(), rand3: Math.random()}

Восьмеричные и двоичные литералы

console.log(0b1010)
console.log(0777)
console.log(0o555)

for .. of

Работает как for in (почти), но перебирает значения, а не ключи:

for (let word of ['foo','bar']){
    console.log(word)
}

Шаблонизация строк `

Символ ` (backtick, обратная кавычка, находится на Ё) позволяет вставить выражение в строку без использования конкатенации:

alert(`Привет, ${prompt('Имя?')}`)

ES6 ООП.

В синтаксисе ES6 появились классы как их привыкли видеть в других языках:

class Parent {
    constructor(name){
        console.log(`parent constructor: ${name}`)
        this.name = name;
    }


    someMethod(){
        console.log('Parent method')
    }
}

class Child extends Parent {
    constructor(name){
        super(name) //обязательный вызов родительского констуктора
        console.log(`child constructor: ${name}`)
    }

    someMethod(){
        console.log(`Child method ${this.name}`)
        super.someMethod() //обращение к родительскому методу
    }
}

let child = new Child('Vasya')
child.someMethod()

Методы в классе попадают в прототип, constructor - аналог функции конструктора из ES5:


function Parent(name){
    console.log(`parent constructor: ${name}`)
    this.name = name;
}

Parent.prototype.someMethod = function(){
    console.log('Parent method')
}



function Child(name){
    Parent.apply(this, arguments)
    console.log(`child constructor: ${name}`)
}

Child.prototype = Object.create(Parent.prototype)
Child.prototype.someMethod = function(){
    console.log(`Child method ${this.name}`)
    this.__proto__.__proto__.someMethod.call(this)
}

let child = new Child('Vasya')
child.someMethod()

Синтаксис ES6 нагляднее, однако скрывает внутреннюю реализацию. Что в ES5, что в ES6 классов нет на уровне интерпретатора, ООП все еще остается прототипным.

Отличия:

  • В ES6 нельзя вызвать класс без new, в отличие от функции-конструктора.
  • Имя класса объявлено через let.
  • Методы лежат в прототипы
  • Методы работают в strict режиме.
  • Методы не перечислимы, for in их не видит.
  • super используется для вызова конструктора (super()) или метода (super.someMethod) предка.

Class Expression

Как и с анонимными функциями, класс может быть анонимный:

function classCreator(methodName, method){
    let myClass = class {
        constructor(name){
            this._name = name;
        }

        get name(){
            return this._name;
        }

        set name(newName){
            this._name = newName;
        }
    }
    myClass.prototype[methodName] = method;
    return myClass
}

var Random = classCreator('random', Math.random)
var Counter = classCreator('inc', function(){
    if (!this.counter){
        this.counter = 0;
    }
    return this.counter++
})

var rand = new Random('random')

console.log(rand.name)
console.log(rand.random())

var counter = new Counter('counter')

console.log(counter.name)
counter.name = 'looks like string key in object, but it\'s a set method call';
console.log(counter.name)
console.log(counter.inc())
console.log(counter.inc())
console.log(counter.inc())

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

Так же обратите внимание на геттер (свойство) name. При использовании объекта это выглядит как обычные данные в объекте, а по факту при чтении или записи в такое свойство вызываются методы get name() или set name() объекта класса.

Статические методы и свойства

Класс так же может обладать статическими (т. е. общими для всего класса, а не для каждого объекта) методами. Свойства не предусмотрены, однако легко решаются через статический геттер или сеттер. На самом деле методы в прототипе тоже находятся в единственном экземпляре, однако за счет работы с this кажется что они привязаны к объекту.

class Singleton {

    static get instance(){
        if (!Singleton._instance){
            Singleton._instance = new Singleton()
        }
        return Singleton._instance;
    }
}

console.log(Singleton.instance);

Выше вы видите реализацию паттерна Singleton, которая заключается в том, что в классе существует только один экземпляр (объект). Данный объект лежит (фактически) в _instance, однако доступен по статическому геттеру instance. Последний при надобности создает экземпляр (при первом обращении).