Наследование JavaScript: вызов прототипного объекта в HTML-документе — Как использовать модули

#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:

Черт возьми, то, что вы создаете, — это не иерархия, это набор миксинов (и это прекрасно!). Проблема не в этом, а просто в терминологии. 🙂

Есть две проблемы:

  1. Все находится непосредственно в объекте, а не в прикрепленных к нему подобъектах, так что этого не было бы factory.personFactory.talk(); , это было бы factory.talk(); (за исключением того, что то, что personMammalFactory возвращает, не является фабрикой, поэтому я бы назвал это как-то иначе, например personMammal ).

  2. Поскольку ваши файлы являются модулями, они не создают никаких глобальных переменных; код сценария, который вы пытаетесь использовать 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;
  

Запись правильного вывода: 1.4