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 фактически представлял собой набор свойств, а свойство представляло собой набор пар ключ-значение (ключ, значение).
Вы сказали, что с обычными свойствами все в порядке, разве у вас нет методов ниже объекта? Как он стал набором свойств?
По сути, определение метода, появившееся в спецификации 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)
Разница между первой и второй частями приведенного выше кода заключается в том, объявлен ли конструктор явно, поэтому эквивалентны ли эти две части кода? Ответ эквивалентен.
Спецификация определяет это так:
Третья часть кода наследует 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, вы Добро пожаловать, чтобы поделиться своими чувствами в области комментариев.