предисловие
Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, что можно рассматривать как небольшое поощрение для меня, ведь у меня нет денег, чтобы писать вещи, и я могу продолжать на своем собственном энтузиазме и всеобщем поощрении.
Давно ничего не писал.По разным причинам в последнее время не находил времени зацикливаться на своем писании.Похоже на бег.Однажды расслабившись,трудно снова взяться за дело. В последнее время мне хочется успокоиться и написать что-нибудь снова, а выбор темы стал для меня головной болью.В последнее время я периодически испытываю головокружение от проблемы наследования JavaScript на работе.Я понимаю, что даже если много знаний очень простой, его нужно часто пересматривать и практиковать, иначе даже знакомые вещи будут часто заставлять вас чувствовать себя незнакомыми, поэтому выберите такую самую базовую статью, как начало этого года.
Добрый
JavaScript не похож на сам язык Java, имеет концепцию классов, JavaScript как прототип на основе (ProtoType
) язык, (рекомендовано, что я писал ранееЦепочка областей JavaScript и цепочка прототипов, как я это знаю), по сей день все еще есть много людей, которые не рекомендуют широкое использование объектно-ориентированных функций в JavaScript. Но на данный момент многие интерфейсные фреймворки, такие как React, имеют концепцию, основанную на классах. Во-первых, понятно, что целью существования классов является генерация объектов, а процесс генерации объектов в JavaScript не такой громоздкий, как в других языках, мы можем легко создать объект через синтаксис объектного литерала:
var person = {
name: "MrErHu",
sayName: function(){
alert(this.name);
}
};
Все выглядит так идеально, но когда мы захотим создать бесчисленное количество похожих объектов, мы обнаружим, что метод объектных литералов не может быть удовлетворен.Конечно, вы обязательно подумаете об использовании фабричного шаблона для создания серии объектов. :
function createObject(name){
return {
"name": name,
"sayName": function(){
alert(this.name);
}
}
}
Но у этого метода есть существенная проблема: между различными объектами, сгенерированными фабричным шаблоном, нет связи, и тип объекта не может быть идентифицирован, в это время появляется функция конструктора. Между конструкторами и обычными функциями в JavaScript нет никакой разницы, за исключением того, что конструктор передается черезnew
вызывается оператор.
function Person(name, age, job){
this.name = name;
this.sayName = function(){
alert(this.name);
};
}
var obj = new Person();
obj.sayName();
мы знаемnew
Оператор выполнит следующие четыре шага:
- Создать совершенно новый объект
- новые внутренние свойства объекта
[[Prototype]]
(неформальный атрибут__proto__
) подключается к прототипу конструктора - Конструктор
this
привяжет новый объект - Если функция не возвращает никакого другого объекта, то
new
Вызовы функций в выражениях автоматически возвращают этот новый объект
Таким образом, мы можем выполнять суждение о типах объектов, сгенерированных конструктором. Однако есть проблема с режимом простого конструктора, то есть методы каждого объекта независимы друг от друга, а функция по сути является объектом, поэтому это вызовет много траты памяти. рассмотрениеnew
Третий шаг оператора, мы заново генерируем внутренние свойства объекта[[Prototype]]
будет подключен к прототипу конструктора, поэтому, используя эту возможность, мы можем смешиватьШаблон конструктораа такжережим прототипа, чтобы решить вышеуказанную проблему.
function Person(name, age, job){
this.name = name;
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var obj = new Person();
obj.sayName();
ПроходимsayName
Функция помещается в прототип конструктора, чтобы использовался сгенерированный объектsayName
Функция может найти соответствующий метод, просматривая цепочку прототипов.Все объекты используют один метод для решения вышеуказанной проблемы.Даже если вы думаете, что поиск цепочки прототипов может занять некоторое время, на самом деле эту проблему можно игнорировать для текущий движок JavaScript. Для модификации прототипа конструктора для борьбы с вышеперечисленными методами также могут быть:
Person.prototype.sayName = function(){
alert(this.name);
}
Мы знаем, что в прототипе функцииconstructor
Свойство является самой функцией выполнения, если вы замените исходный прототип новым объектом иconstructor
Вам важнее не забыть добавить его вручную, поэтому первое не точно, т.к.constructor
не является перечислимым, поэтому более точным способом его записи будет:
Object.defineProperty(Person, "constructor", {
configurable: false,
enumerable: false,
writable: true,
value: Person
});
До сих пор нам казалось, что создание класса в JavaScript слишком хлопотно. На самом деле это гораздо больше. Например, создаваемый нами класс может вызываться напрямую, вызывая загрязнение глобальной среды, например:
Person('MrErHu');
console.log(window.name); //MrErHu
Однако мы вступили в эру ES6, и все меняется. ES6 реализует для нас концепцию классов в JavaScript. Приведенный выше код можно реализовать с помощью краткого класса.
class Person {
constructor(name){
this.name = name;
}
sayName(){
alert(this.name);
}
}
С помощью вышеизложенного мы определяем класс и используем его так же, как и раньше:
let person = new Person('MrErHu');
person.sayName(); //MrErHu
Мы видим, что в классеconstructor
Функция принимает на себя функцию предыдущего конструктора, и здесь можно инициализировать свойства экземпляра в классе. метод классаsayName
Это эквивалентно тому, что мы определили в прототипе конструктора ранее. По сути, в ES6 классы — это просто синтаксический сахар для функций:
typeof Person //"function"
По сравнению с методом класса, который мы создали выше, есть несколько аспектов класса в ES6, которые отличаются от нашего пользовательского класса. первого класса нетпеременное продвижение, поэтому его нельзя использовать сначала, а затем определить:
let person = new Person('MrErHu')
class Person { //...... }
Использование выше неверно. Таким образом, класс больше похож на функциональное выражение.
Во-вторых, весь код в объявлении класса автоматически запускается в строгом режиме, и класс нельзя вывести из строгого режима. Эквивалентно всему коду в объявлении класса, который выполняется в режиме "строгого использования".
Кроме того, все методы в классе неперечислимы.
Наконец, классы нельзя вызывать напрямую, они должны бытьnew
вызов оператора. На самом деле у функций есть внутренние свойства[[Constructor]]
а также[[Call]]
, Конечно, к этим двум методам нельзя получить доступ извне, и они существуют только в движке JavaScript. Когда мы вызываем функцию напрямую, мы фактически вызываем внутреннее свойство[[Call]]
, все, что он делает, это напрямую выполняет тело функции. когда мы проходимnew
Когда оператор вызывается, он фактически вызывает внутреннее свойство[[Constructor]]
, все, что он делает, это создает новый объект экземпляра и выполняет функцию над объектом экземпляра (привязкаthis
) и, наконец, возвращает новый экземпляр объекта. Потому что класс не содержит внутренних свойств[[Call]]
, поэтому его нельзя вызвать напрямую. Кстати, могу упомянуть, что в ES6мета-свойство new.target
Так называемыймета-атрибутозначаетнеобъектные свойства, может предоставить нам некоторую дополнительную информацию.new.target
Является одним из метаболических, когда вызов[[Constructor]]
имущество,new.target
то естьnew
цель оператора, если вызов[[Call]]
Атрибуты,new.target
то естьundefined
. На самом деле это свойство очень полезно, например, мы можем определитьnew
Функция, вызываемая оператором:
function Person(){
if(new.target === undefined){
throw('该函数必须通过new操作符调用');
}
}
Или мы можем создать функцию в JavaScript, аналогичную виртуальной функции в C++:
class Person {
constructor() {
if (new.target === Person) {
throw new Error('本类不能实例化');
}
}
}
наследовать
В эпоху отсутствия ES6 для реализации наследования требуется много работы. С одной стороны, нам нужно создать свойства родительского класса в производном классе, а с другой стороны, нам нужно наследовать методы родительского класса, такие как следующие методы реализации:
function Rectangle(width, height){
this.width = width;
this.height = height;
}
Rectangle.prototype.getArea = function(){
return this.width * this.height;
}
function Square(length){
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: false,
writable: false,
configurable: false
}
});
var square = new Square(3);
console.log(square.getArea());
console.log(square instanceof Square);
console.log(square instanceof Rectangle);
Первый подклассSquare
Чтобы создать родительский классRectangle
свойства, мы находимся вSquare
в функцииRectangle.call(this, length, length)
Вызывается метод, цель создать свойства родительского класса в подклассе, чтобы наследовать метод родительского класса, даемSquare
Присваивается новый прототип. кроме как черезObject.create
образом, вы также должны были видеть следующий путь:
Square.prototype = new Rectangle();
Object.defineProperty(Square.prototype, "constructor", {
value: Square,
enumerable: false,
writable: false,
configurable: false
});
Object.create
Новый метод в ES5 для создания нового объекта. Созданный объект наследует прототип другого объекта, а некоторые свойства можно указать при создании нового объекта.Object.create
Способ указания атрибутов такой же, какObject.defineProperty
Точно так же все они представлены в виде дескрипторов атрибутов. Поэтому видно, что поObject.create
а такжеnew
Принципиально нет разницы в способе реализации наследования.
Но ES6 может сильно упростить этапы наследования:
class Rectangle{
constructor(width, height){
this.width = width;
this.height = height;
}
getArea(){
return this.width * this.height;
}
}
class Square extends Rectangle{
construct(length){
super(length, length);
}
}
Мы видим, что реализовать наследование классов через ES6 очень просто.Square
вызвать конструкторsuper
Его цель — вызвать конструктор родительского класса. конечно вызовsuper
Функция необязательна, если вы укажете конструктор по умолчанию, он будет вызван автоматическиsuper
функцию и передать все параметры.
Мало того, наследование классов ES6 дает больше новых возможностей, прежде всегоextends
Любой тип выражения может быть унаследован, если выражение в конечном итоге возвращает наследуемую функцию (то есть,extends
может унаследовать[[Constructor]]
функции внутренних свойств , таких какnull
и генераторные функции, стрелочные функции не обладают этим свойством, поэтому они не могут быть унаследованы). Например:
class A{}
class B{}
function getParentClass(type){
if(//...){
return A;
}
if(//...){
return B;
}
}
class C extends getParentClass(//...){
}
Вы можете видеть, что мы добились динамического наследования с помощью приведенного выше кода и можем наследовать разные классы в соответствии с различными условиями суждения. Наследование ES6 отличается от наследования классов, реализованного в ES5. ES5 заключается в том, чтобы сначала создать экземпляр подкласса, а затем создать свойства родительского класса на основе экземпляра подкласса. ES6 как раз наоборот: сначала создается экземпляр родительского класса, а затем расширяются свойства дочернего класса на основе экземпляра родительского класса. Используя это свойство, мы можем делать то, чего не может ES5: наследовать нативные объекты.
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
var colors = new MyArray();
colors[0] = "red";
colors.length // 0
colors.length = 0;
colors[0] // "red"
Как видите, унаследовано от нативных объектовArray
изMyArray
в случаеlength
не так оригинальноArray
экземпляр класса
Также есть возможность динамически отражать количество элементов в массиве или путем измененияlength
свойство, тем самым изменяя данные в массиве. Причина в том, что традиционный способ реализации наследования массива заключается в том, чтобы сначала создать подкласс, а затем расширить свойства и методы родительского класса на основе подкласса, поэтому нет связанного метода наследования, но ES6 может легко достичь это:
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined
Мы можем видеть сквозьextends
осуществленныйMyArray
Массив, созданный классом, можно использовать как собственный массив, используяlength
Свойства отражают изменения массива и изменяют элементы массива. Мало того, в ES6 мы можем использоватьSymbol.species
Свойства позволяют нам изменить возвращаемый тип экземпляра методов, унаследованных от собственных объектов, когда мы наследуем от собственных объектов. Например,Array.prototype.slice
вернулся быArray
экземпляр типа, установивSymbol.species
свойство, мы можем заставить его возвращать пользовательский тип объекта:
class MyArray extends Array {
static get [Symbol.species](){
return MyArray;
}
constructor(...args) {
super(...args);
}
}
let items = new MyArray(1,2,3,4);
subitems = items.slice(1,3);
subitems instanceof MyArray; // true
Последнее замечание,extends
Реализация наследования может наследовать статические функции-члены родительского класса, например:
class Rectangle{
// ......
static create(width, height){
return new Rectangle(width, height);
}
}
class Square extends Rectangle{
//......
}
let rect = Square.create(3,4);
rect instanceof Square; // true