Różnica między „module.exports” a „eksportami” w systemie modułów CommonJs

276

Na tej stronie ( http://docs.nodejitsu.com/articles/getting-started/what-is-require ) stwierdza się, że „Jeśli chcesz ustawić obiekt eksportu na funkcję lub nowy obiekt, musisz: użyj obiektu module.exports. ”

Moje pytanie brzmi: dlaczego.

// right
module.exports = function () {
  console.log("hello world")
}
// wrong
exports = function () {
  console.log("hello world")
}

I console.logged wynik ( result=require(example.js)), a pierwszy to [Function]drugi {}.

Czy mógłbyś wyjaśnić przyczynę? Czytałem post tutaj: module.exports vs eksportu w node.js . Jest to pomocne, ale nie wyjaśnia powodu, dla którego został zaprojektowany w ten sposób. Czy wystąpi problem, jeśli odniesienie do eksportu zostanie zwrócone bezpośrednio?

Xiao Peng - ZenUML.com
źródło
11
Zawsze używaj module.exports.
Gabriel Llamas
1
Myślę, że przestrzeganie wyżej wymienionych rad pozwala uniknąć tego problemu.
Vitalii Korsakov,
@GabrielLlamas, więc dlaczego wiele pakietów używa tylko exports, na przykład github.com/tj/consolidate.js/blob/master/lib/consolidate.js ?
CodyBugstein
3
@Imray Jeśli zawsze używać module.exports, nigdy nie będzie źle, ale można użyć exports, jeśli nie jesteś zastąpienie domyślnego eksportowany obiekt, to znaczy, jeśli po prostu dołączyć właściwości tak: var foo = require('foo').foo. Tę foowłaściwość można wyeksportować w następujący sposób: exports.foo = ...i oczywiście również za pomocą module.exports. To osobisty wybór, ale obecnie go używam module.exportsi exportsodpowiednio.
Gabriel Llamas
Wolę exports.myFunc = function () {}, więc nie muszę utrzymywać listy eksportów na dole pliku. Kiedy deklarujesz w ES6, jest to bliższe powszechnej praktyce eksportowania.
SacWebDeveloper

Odpowiedzi:

624

modulejest zwykłym obiektem JavaScript z exportswłaściwością. exportsjest zwykłą zmienną JavaScript, którą można ustawić na module.exports. Na końcu pliku node.js po prostu „powróci” module.exportsdo requirefunkcji. Uproszczony sposób wyświetlania pliku JS w węźle może być następujący:

var module = { exports: {} };
var exports = module.exports;

// your code

return module.exports;

Jeśli włączysz właściwość, na exportsprzykład exports.a = 9;, która również zostanie ustawiona, module.exports.aponieważ obiekty są przekazywane w JavaScript jako referencje, co oznacza, że ​​jeśli ustawisz wiele zmiennych dla tego samego obiektu, wszystkie będą tym samym obiektem; więc wtedy exportsi module.exportssą tym samym przedmiotem.
Ale jeśli ustawisz exportsna coś nowego, to nie będzie już ustawione module.exports, tak exportsi module.exportsnie są już tego samego obiektu.

przystanek autobusowy
źródło
11
Racja, to tylko podstawy typów referencyjnych.
Vitalii Korsakov
18
Czemu!? Dlaczego można to przeczytać tylko tutaj. Powinien to być slogan dla każdego modułowego języka JavaScript. Dzięki
lima_fil,
8
Pięknie wyjaśnione!
Aakash Verma
3
super, najlepsza odpowiedź !!
Jan
5
Świetne wyjaśnienie. Dokumentuje module.exportsto także: nodejs.org/api/modules.html#modules_module_exports
Brian Morearty
52

Odpowiedź Renee jest dobrze wyjaśniona. Dodatek do odpowiedzi z przykładem:

Węzeł robi wiele rzeczy w twoim pliku, a jednym z ważnych jest OWIJANIE pliku. Wewnątrz kodu źródłowego nodejs zwracany jest „module.exports”. Cofnijmy się i zrozummy opakowanie. Załóżmy, że masz

greet.js

var greet = function () {
   console.log('Hello World');
};

module.exports = greet;

powyższy kod jest zawinięty jako IIFE (Natychmiastowe wywołanie funkcji) w kodzie źródłowym nodejs w następujący sposób:

(function (exports, require, module, __filename, __dirname) { //add by node

      var greet = function () {
         console.log('Hello World');
      };

      module.exports = greet;

}).apply();                                                  //add by node

return module.exports;                                      //add by node

a powyższa funkcja jest wywoływana (.apply ()) i zwraca moduł.exports. W tej chwili moduł. Eksportuje i eksportuje wskazując to samo odwołanie.

Teraz wyobraź sobie, że ponownie napisałeś greet.js jako

exports = function () {
   console.log('Hello World');
};
console.log(exports);
console.log(module.exports);

wyjście będzie

[Function]
{}

powodem jest to: module.exports jest pustym obiektem. Nie ustawiliśmy niczego w module.exports, zamiast tego ustawiamy export = function () ..... w nowym pliku greet.js. Tak więc moduł.exports jest pusty.

Technicznie eksport i moduł.eksport powinny wskazywać to samo odniesienie (to prawda !!). Ale używamy „=” przy przypisywaniu funkcji () .... do eksportu, co tworzy kolejny obiekt w pamięci. Tak więc moduł. Eksport i eksport dają różne wyniki. Jeśli chodzi o eksport, nie możemy go pominąć.

Teraz wyobraź sobie, że piszesz ponownie (to się nazywa mutacja) greet.js (odnosząc się do odpowiedzi Renee) jako

exports.a = function() {
    console.log("Hello");
}

console.log(exports);
console.log(module.exports);

wyjście będzie

{ a: [Function] }
{ a: [Function] }

Jak widać moduł. Eksport i eksport wskazują na to samo odwołanie, które jest funkcją. Jeśli ustawisz właściwość dla eksportu, zostanie ona ustawiona w module.exports, ponieważ w JS obiekty są przekazywane przez odniesienie.

Wniosek zawsze dotyczy użycia modułu. Eksportuje, aby uniknąć nieporozumień. Mam nadzieję że to pomoże. Miłego kodowania :)

Sdembla
źródło
To także jest piękna wnikliwa odpowiedź i uzupełnia odpowiedź @ goto-bus-stop. :)
varun
23

Ponadto jedna rzecz, która może pomóc zrozumieć:

math.js

this.add = function (a, b) {
    return a + b;
};

client.js

var math = require('./math');
console.log(math.add(2,2); // 4;

Świetnie, w tym przypadku:

console.log(this === module.exports); // true
console.log(this === exports); // true
console.log(module.exports === exports); // true

Zatem domyślnie „to” jest w rzeczywistości równe module.exports.

Jeśli jednak zmienisz implementację na:

math.js

var add = function (a, b) {
    return a + b;
};

module.exports = {
    add: add
};

W takim przypadku będzie działać dobrze, jednak „to” nie jest już równe modułowi. Eksportuje, ponieważ utworzono nowy obiekt.

console.log(this === module.exports); // false
console.log(this === exports); // true
console.log(module.exports === exports); // false

A teraz to, co zwróci wymaganie, jest zdefiniowane w module. Eksportuje, a nie eksportuje.

Innym sposobem na to byłoby:

math.js

module.exports.add = function (a, b) {
    return a + b;
};

Lub:

math.js

exports.add = function (a, b) {
    return a + b;
};
Rodrigo Branas
źródło
15

Odpowiedź Rene na temat związku między exportsi module.exportsjest dość jasna, wszystko dotyczy odwołań do javascript. Chcę tylko dodać, że:

Widzimy to w wielu modułach węzłów:

var app = exports = module.exports = {};

Zapewni to, że nawet jeśli zmienimy module.exports, nadal będziemy mogli używać eksportu, sprawiając, że te dwie zmienne wskazują ten sam obiekt.

fengshuo
źródło
Zdezorientowałem się tym wyjaśnieniem, uprzejmy to rozwinąć?
GuyFreakz
6
@GuyFreakz Nie jestem pewien, czy to przemawia do zamieszania, ale module.exportsi exportsto tylko osobne zmienne inicjowane odwoływać się do tego samego obiektu. Jeśli zmienisz to, do czego odwołuje się jedna zmienna, dwie zmienne nie będą już odnosiły się do tej samej rzeczy. Wiersz powyższego kodu zapewnia, że ​​obie zmienne są inicjowane w tym samym nowym obiekcie.
Andrew Palmer,
Rzeczywisty przypadek użycia, którego wszyscy inni przegapili na @fengshuo. Dzięki!
Aakash Verma
0

myTest.js

module.exports.get = function () {};

exports.put = function () {};

console.log(module.exports)
// output: { get: [Function], put: [Function] }

exportsi module.exportssą takie same i odnoszą się do tego samego obiektu. Możesz dodać właściwości na dwa sposoby, zgodnie ze swoją wygodą.

Shashwat Gupta
źródło