Почему класс ES6 является синтаксическим сахаром?

JavaScript

0. Предисловие

Читаем эту статью с вопросами:

  • Почему класс ES6 является синтаксическим сахаром?
  • Является ли класс синтаксическим сахаром для прототипов?
  • Так как же использовать прототипы для реализации синтаксического сахара класса?

1. ООП на основе прототипа

Давайте сначала рассмотрим пример прототипа:

function Person (name, sex) {
	this.name = name
	this.sex = sex
}

function Man (name) {
	this.name = name
}

Man.prototype = new Person('', 'male')

let Jy = new Man('Jy')

console.log(Jy.name, Jy.sex) // Jy, male

Это очень простой пример использования прототипов. Давайте запомним этот пример, ниже мы перепишем этот пример, используя Class.

Советы: Брендан Эйх добавил new, this и т. д., чтобы сделать его более похожим на ООП Java Заинтересованные читатели могут самостоятельно проверить соответствующую информацию.

2. ООП класса ES6

class Person {
	constructor (name, sex) {
		this.name = name
		this.sex = sex
	}
}

class Man extends Person {
	constructor (name) {
		super('', 'male')
		this.name = name
	}
}

let Jy = new Man('Jy')

console.log(Jy.name, Jy.sex) // Jy, 'male'

Мы переписали пример, чтобы использовать слова «класс», «конструктор», «расширяет», «супер», а затем, в частности, то, что спецификация ES6 делает с ними.

3. Класс ООП, реализованный с использованием Prototype (спецификация ES6)

До ES6 объект JS фактически представлял собой набор свойств, а свойство представляло собой набор пар ключ-значение (ключ, значение).

file

Вы сказали, что с обычными свойствами все в порядке, разве у вас нет методов ниже объекта? Как он стал набором свойств?

По сути, определение метода, появившееся в спецификации ES5, — это «функция, являющаяся значением свойства», которая является просто свойством функции объекта и не может называться методом.До появления ES6 существовали метод Определения в спецификации.

Все, что мы можем думать об ООП в ES3: прототип, новое, это, конструктор, экземпляр, даже не канонический__proto__Атрибуты.

К счастью, в ES5 мы добавили множество методов для его завершения и завершения:

  • Object.defineProperty
  • Object.freeze
  • Object.create
  • Object.getPrototypeOf
  • Object.setPrototypeOf
  • isPrototypeOf
  • ......

Давайте посмотрим на кусок кода:

let obj = {
	name: 'Jy',
	speak () { // Note: it's not speak: function () {}
		console.log(this.name, super.name)
	}
}

obj.speak() // Jy, undefined

Object.setPrototypeOf(obj,  { name: 'super' })

obj.speak() // Jy, super

let speak = obj.speak
speak() // undefined, super

obj.speak уже является методом, определенным в ES6, он имеет атрибут [[homeObject]], homeObject указывает на объект, где вызывается метод (ссылаясь на obj в коде), это внутренние слоты, привязанные к объекту, то есть вы, если вы не можете его изменить, это равносильно написанию его до смерти.

Итак, в чем польза от homeObject? Он тесно связан с super.При разборе ключевого слова super будет найден прототип homeObject.

Вкратце ее можно представить в виде следующих двух формул:

  • let homeObj = Method[[HomeObject]] = obj
  • super = Object.getPrototypeOf(homeObj)

Примечание: homeObject статически привязан к внутренним слотам, а super находится в динамическом поиске.

После разговора о супер, давайте поговорим о расширениях и конструкторе.

class A extends B { }

class A extends B {
	constructor (...args) {
		super(args)
	}
}

class C extends null { }

extends в основном делает следующие две вещи:

  • Object.setPrototypeOf(A, B)
  • Object.setPrototypeOf(A.prototype, B.prototype)

Если родительский класс имеет значение null, выполните Object.setPrototypeOf(C.prototype, null)

Разница между первой и второй частями приведенного выше кода заключается в том, объявлен ли конструктор явно, поэтому эквивалентны ли эти две части кода? Ответ эквивалентен.

Спецификация определяет это так:

file

Третья часть кода наследует null, она не сообщит о синтаксической ошибке, но мы не можем создать новый C, потому что конструктор null будет вызываться при новом, а конструктора null нет.

Глядя на это, все объявления классов и спецификаций ES6 используют прототипы для работы, поэтому можем ли мы сказать, что класс — это синтаксический сахар прототипов?

4. скомпилированный класс babel

В наших реальных проектах babel используется для компиляции кодов ES6 и 7, поэтому в этом разделе мы проанализируем следующий скомпилированный код babel, в котором будут опущены некоторые связанные коды для отчетов об ошибках и определения типов, чтобы лучше представить использование прототипов для достижения Тема ООП.

Перед компиляцией:

class A extends B {}

console.log(new A)

После компиляции:

"use strict";

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    return o.__proto__ || Object.getPrototypeOf(o);
  };
  return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };
  return _setPrototypeOf(o, p);
}

var A =
  /*#__PURE__*/
  function (_B) {
    _inherits(A, _B);

    function A() {

      return _getPrototypeOf(A).apply(this, arguments);
    }

    return A;
  }(B);

console.log(new A());

Мы сосредоточимся на методе _inherits, который делает те же две вещи, что и расширения выше:

  • Object.setPrototypeOf(subClass, superClass)
  • Object.setPrototypeOf(subClass.prototype, superClass.prototype)

Просто он использует метод Object.create, разницу между двумя методами можно посмотреть на MDN.

Глядя на внутреннюю часть функции A, она на самом деле выполняет функцию конструктора B для достижения эффекта супер(аргументов) Это согласуется со спецификацией: если конструктор не объявлен явно, конструктор будет добавлен автоматически.

5. Резюме

Теперь мы, наконец, понимаем, почему класс является синтаксическим сахаром для прототипов и как использовать прототипы для реализации синтаксического сахара класса.

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

ООП класса ES6 неполный, например, абстрактный класс, интерфейс, приватный и т. д. еще нет, но некоторые функции уже есть в предложении, вы можете их принять, или TypeScript — хороший выбор, если ваш проект использует TS, вы Добро пожаловать, чтобы поделиться своими чувствами в области комментариев.

6. Справочные ссылки