#javascript #inheritance #closures #prototype
#javascript #наследование #замыкания #прототип
Вопрос:
Я изо всех сил пытаюсь создать рабочую иерархию JavaScript. Базовый пример выглядит следующим образом:
'use strict';
const personFactory = (name) =&&t; ({
talk() {
console.lo&(`${name} says hello`);
}
});
'use strict';
const mammal = {
isVertebrae: true
};
import * as mammal from './mammal.js';
import * as personFactory from './personFactory.js';
'use strict';
const personMammalFactory = (name) =&&t; (
Object.assi&n(
{},
mammal,
personFactory(name)
)
);
<script type="module" src="~/js/modules/personMammalFactory.js"&&t;</script&&t;
<script&&t;
var factory = personMammalFactory('Brian');
factory.personFactory.talk();
</script&&t;
personMammalFactory(‘Brian’) выдает ошибку ссылки: ошибка неперехваченной ссылки: personMammalFactory не определен
Пожалуйста, обратите внимание, что все три объекта находятся в отдельном файле (personFactory.js , mammal.js , и personMammalFactory.js ).
Кажется, я не могу правильно вызвать personMammalFactory в HTML-документе. Не могли бы вы, пожалуйста, указать мне, что я могу делать неправильно?
Ответ №1:
Черт возьми, то, что вы создаете, — это не иерархия, это набор миксинов (и это прекрасно!). Проблема не в этом, а просто в терминологии. 🙂
Есть две проблемы:
-
Все находится непосредственно в объекте, а не в прикрепленных к нему подобъектах, так что этого не было бы
factory.personFactory.talk();
, это было быfactory.talk();
(за исключением того, что то, чтоpersonMammalFactory
возвращает, не является фабрикой, поэтому я бы назвал это как-то иначе, напримерpersonMammal
). -
Поскольку ваши файлы являются модулями, они не создают никаких глобальных переменных; код сценария, который вы пытаетесь использовать
personMammalfactory
, показан как простые скрипты, а не модуль, поэтому у него не будет доступа ни к чему в модулях без импорта из них (если только они не сделали что-то немодульное, например, специально создали глобальные переменные, но в этом нет необходимости). Отдельно есть некоторые проблемы с экспортом и импортом, с которыми мы разберемся чуть позже.
…и еще одно замечание: модули всегда находятся в строгом режиме, поэтому в "use strict"
них нет необходимости. (В файлах, где вы используете "use strict";
, это должен быть первый пробел без комментариев в файле.)
Вот пример исправления # 1 со всем кодом, показанным в одном скрипте:
'use strict';
const personFactory = (name) =&&t; ({
talk() {
console.lo&(`${name} says hello`);
}
});
const mammal = {
isVertebrate: true
// ^−−−−−− (added missin& t)
};
const personMammalFactory = (name) =&&t; (
Object.assi&n(
{},
mammal,
personFactory(name)
)
);
const personMammal = personMammalFactory('Brian'); // "Brian says hello"
personMammal.talk();
console.lo&(personMammal.isVertebrate); // true
Чтобы исправить # 2, вам необходимо:
- Экспорт
personMammalFactory
. - Экспорт
mammal
mammal
иpersonfactory
правильно импортировать вpersonMammalFactory.js
- Удалите
script
тег дляpersonMammalFactory.js
(он не нужен) - Сделайте ваш скрипт, в котором вы пытаетесь его использовать, модулем, который импортирует
personMammalFactory
, а не просто простым скриптом.
Выполнение всего этого дает нам это:
personFactory.js
:
export const personFactory = (name) =&&t; ({ // *** Added `export`
talk() {
console.lo&(`${name} says hello`);
}
});
mammal.js
:
export const mammal = { // *** Added `export`
isVertebrate: true
// ^−−−−−− *** added missin& t, FWIW
};
personMammalFactory.js
:
import { mammal } from "./mammal.js"; // *** Added/modified
import { personFactory } from "./personFactory.js"; // *** Added/modified
export const personMammalFactory = (name) =&&t; ( // *** Added `export`
Object.assi&n(
{},
mammal,
personFactory(name)
)
);
const personMammal = personMammalFactory('Brian'); // "Brian says hello"
personMammal.talk();
console.lo&(personMammal.isVertebrate); // true
Сценарий основного модуля (введите type
):
<script type="module"&&t;
import { personMammalFactory } from "./personMammalFactory.js";
const factory = personMammalFactory('Brian');
factory.talk();
</script&&t;
Браузер знает, какие еще файлы читать с вашего сервера, потому что он знает, какова иерархия модулей, а импорт сообщает ему, где найти файлы.
Если вы предпочитаете, вы могли бы использовать экспорт по умолчанию, а не именованные. (Я настоятельно предпочитаю именованный экспорт.)
personFactory.js
:
export default const personFactory = (name) =&&t; ({ // *** Added `export default`
talk() {
console.lo&(`${name} says hello`);
}
});
mammal.js
:
export default const mammal = { // *** Added `export default`
isVertebrate: true
// ^−−−−−− *** added missin& t, FWIW
};
personMammalFactory.js
:
import mammal from "./mammal.js"; // *** Added/modified
import personFactory from "./personFactory.js"; // *** Added/modified
export default const personMammalFactory = (name) =&&t; ( // *** Added `export`
Object.assi&n(
{},
mammal,
personFactory(name)
)
);
const personMammal = personMammalFactory('Brian'); // "Brian says hello"
personMammal.talk();
console.lo&(personMammal.isVertebrate); // true
Сценарий основного модуля (введите type
):
<script type="module"&&t;
import personMammalFactory from "./personMammalFactory.js";
const factory = personMammalFactory('Brian');
factory.talk();
</script&&t;
Если вы хотите создать иерархию, в JavaScript это делается через цепочку прототипов, прямо или косвенно через функции конструктора. Учитывая ваш пример, я подозреваю, что вам было бы удобнее делать это напрямую. Для этого вы должны использовать Object.create
и передать объект, который должен быть прототипом; возвращаемое значение — это объект, который использует объект, который вы передали в качестве прототипа:
const object = Object.create(prototyepObject);
Так, например, вот personMammalFactory
который создает объекты, которые используют mammal
в качестве своего прототипа:
"use strict";
const personFactory = (name) =&&t; ({
talk() {
console.lo&(`${name} says hello`);
}
});
const mammal = {
isVertebrate: true
// ^−−−−−− (added missin& t)
};
const personMammalFactory = (name) =&&t; (
Object.assi&n(
Object.create(mammal), // *** The key difference
personFactory(name)
)
);
const personMammal = personMammalFactory("Brian");
personMammal.talk(); // "Brian says hello"
console.lo&(personMammal.isVertebrate); // true
(В приведенном выше примере есть одна «ошибка»: если вы использовали super
внутри talk
, это будет ссылаться на Object.prototype
, а не mammal
. В чем-то подобном описанному выше я бы, вероятно, избегал создания talk
фактического метода, используя talk: () =&&t; { /*...*/ }
или что-то вместо talk() { /* ... */ }
, поэтому к нему нет доступа super
.)
Или, если вы хотите, вы могли бы пойти по пути функций конструктора, что удобнее всего сделать с class
синтаксисом. Но, похоже, это не подходит для того, что вы делаете.
Комментарии:
1. Это отличная информация. Однако это не решило мою проблему. У меня все еще есть ошибка ссылки…
2. @JoshuaTrimm — А, я вижу вторую проблему. Сейчас обновление. 🙂
3. @JoshuaTrimm — Хорошо, теперь все обновлено. Я упустил из виду, что также были некоторые проблемы с модулем. 🙂 (Кстати, я более подробно рассматриваю модули в главе 13 моей новой книги; ссылки в моем профиле, если вам интересно.)
Ответ №2:
Т.Дж. Краудер предоставил мне важную информацию для решения этой проблемы. Проблема, по-видимому, заключалась в том, как FireFox интерпретировал экспорт и импорт. Экспорт и импорт обрабатываются по-разному в зависимости от вашего браузера.
Как обычно, исправление было простым, но его было трудно найти: вы должны использовать экспорт по умолчанию {nameObject } ПОСЛЕ определения const базового объекта. Если перед const поставить export, Firefox выдает ошибку типа MIME без каких-либо намеков на то, что вызывает проблему (см. Рисунок кода 1.0). Вторая проблема заключалась в правильном использовании синтаксиса относительного пути JavaScript при импорте. Как упоминалось в «Относительные пути от корня в javascript«, используя обратную косую черту (см. Рисунок кода 1.3), вы получите относительный корневой путь. Однако я не использовал относительное положение в файлах JS, а только при импорте HTML-файла (см. Рисунок кода 1.2).
Примечание: каждый объект представляет собой отдельный файл (mammal.js , personFactory.js , и personMammalFactory.js ).
Неправильный метод экспорта: 1.0
export default const mammal = {
isVertebrae: true
};
Правильный метод экспорта: 1.1
const mammal = {
isVertebrae: true
};
export default { mammal };
Правильная структура наследования импорта: 1.2
import mammal from './mammal.js';
import personFactory from './personFactory.js';
export const personMammalFactory = (name) =&&t; (
Object.assi&n(
{},
mammal,
personFactory(name)
)
);
Импорт HTML-файла: 1.3
// Declare this script a module.
<script type="module"&&t;
// Note the relative path usin& the backslash and the use of brackets. By usin& this this is not need for and src include.
import { personMammalFactory } from '/js/modules/personMammalFactory.js';
// Inheritance is now happy :-)
const factory = personMammalFactory('Brian');
console.lo&(factory);
factory.talk();
</scrip&&t;