这个JavaScript“需要”是什么?

我正在尝试让JavaScript读取/写入PostgreSQL数据库。我在GitHub上找到了这个项目。我能够在Node中运行以下示例代码。

var pg = require('pg'); //native libpq bindings = `var pg = require('pg').native`
var conString = "tcp://postgres:1234@localhost/postgres";


var client = new pg.Client(conString);
client.connect();


//queries are queued and executed one after another once the connection becomes available
client.query("CREATE TEMP TABLE beatles(name varchar(10), height integer, birthday timestamptz)");
client.query("INSERT INTO beatles(name, height, birthday) values($1, $2, $3)", ['Ringo', 67, new Date(1945, 11, 2)]);
client.query("INSERT INTO beatles(name, height, birthday) values($1, $2, $3)", ['John', 68, new Date(1944, 10, 13)]);


//queries can be executed either via text/parameter values passed as individual arguments
//or by passing an options object containing text, (optional) parameter values, and (optional) query name
client.query({
name: 'insert beatle',
text: "INSERT INTO beatles(name, height, birthday) values($1, $2, $3)",
values: ['George', 70, new Date(1946, 02, 14)]
});


//subsequent queries with the same name will be executed without re-parsing the query plan by postgres
client.query({
name: 'insert beatle',
values: ['Paul', 63, new Date(1945, 04, 03)]
});
var query = client.query("SELECT * FROM beatles WHERE name = $1", ['John']);


//can stream row results back 1 at a time
query.on('row', function(row) {
console.log(row);
console.log("Beatle name: %s", row.name); //Beatle name: John
console.log("Beatle birth year: %d", row.birthday.getYear()); //dates are returned as javascript dates
console.log("Beatle height: %d' %d\"", Math.floor(row.height/12), row.height%12); //integers are returned as javascript ints
});


//fired after last row is emitted
query.on('end', function() {
client.end();
});

接下来,我试图让它在网页上运行,但似乎什么也没发生。我检查了JavaScript控制台,它只是说“需要未定义”。

那么这个“需求”是什么?为什么它在Node中工作而在网页中不工作?

此外,在我让它在Node中工作之前,我必须执行npm install pg。这是怎么回事?我在目录中查找,没有找到文件pg。它放在哪里,JavaScript如何找到它?

1020078 次浏览

那么这个“要求”是什么呢?

require()不是标准JavaScript API的一部分。但在Node.js,它是一个具有特殊用途的内置函数:来加载模块

模块是一种将应用程序拆分为单独文件而不是将所有应用程序放在一个文件中的方法。这个概念也存在于其他语法和行为略有不同的语言中,例如C的include、Python的import等。

Node.js模块和浏览器JavaScript之间的一个很大的区别是如何从另一个脚本的代码访问一个脚本的代码。

  • 在浏览器JavaScript中,脚本通过<script>元素添加。当它们执行时,它们都可以直接访问全局范围,这是所有脚本之间的“共享空间”。任何脚本都可以自由定义/修改/删除/调用全局范围上的任何内容。

  • Node.js,每个模块都有自己的作用域。一个模块不能直接访问另一个模块中定义的内容,除非它选择公开它们。要公开一个模块中的内容,必须将它们分配给exportsmodule.exports。一个模块要访问另一个模块的exportsmodule.exports必须使用require()

在您的代码中,var pg = require('pg');加载pg模块,这是Node.js.的PostgreSQL客户端这允许您的代码通过pg变量访问PostgreSQL客户端API的功能。

为什么它在节点中工作而在网页中不工作?

require()module.exportsexports是模块系统的API,特定于Node.js.浏览器不实现此模块系统。

另外,在我让它在node中工作之前,我必须做npm install pg。那是关于什么的?

NPM是一个包存储库服务,托管已发布的JavaScript模块。npm install是一个命令,可让您从其存储库下载包。

它把它放在哪里,Javascript如何找到它?

npm cli将所有下载的模块放在您运行npm installnode_modules目录中。Node.js在模块如何查找其他模块上有非常详细的留档,其中包括查找node_modules目录。

它用于加载模块。让我们用一个简单的例子。

在文件circle_object.js中:

var Circle = function (radius) {
this.radius = radius
}
Circle.PI = 3.14


Circle.prototype = {
area: function () {
return Circle.PI * this.radius * this.radius;
}
}

我们可以通过require使用它,例如:

node> require('circle_object')
{}
node> Circle
{ [Function] PI: 3.14 }
node> var c = new Circle(3)
{ radius: 3 }
node> c.area()

require()方法用于加载和缓存JavaScript模块。因此,如果您想将本地的、相对的JavaScript模块加载到Node.js应用程序中,您可以简单地使用require()方法。

示例:

var yourModule = require( "your_module_name" ); //.js file extension is optional

好的,让我们首先区分Javascript在网络浏览器和Javascript在服务器(Common JS和Node)。

Javascript是一种传统上仅限于Web浏览器的语言,其有限的全局上下文主要由后来被称为文档对象模型(DOM)级别0(Netscape Navigator Javascript API)定义。

服务器端Javascript消除了这种限制,并允许Javascript调用各种本机代码(如Postgres库)并打开套接字。

现在require()是一个特殊的函数调用,它被定义为Common JS规范的一部分。在node中,它解析Node搜索路径中的库和模块,现在通常定义为node_modules在同一目录(或调用的javascript文件的目录)或系统范围的搜索路径中。

要尝试回答您的其余问题,我们需要在浏览器中运行的代码和数据库服务器之间使用代理。

由于我们正在讨论Node,并且您已经熟悉如何从那里运行查询,因此使用Node作为该代理是有意义的。

作为一个简单的例子,我们将创建一个URL,以JSON的形式返回有关披头士乐队的一些事实。

/* your connection code */


var express = require('express');
var app = express.createServer();
app.get('/beatles/:name', function(req, res) {
var name = req.params.name || '';
name = name.replace(/[^a-zA_Z]/, '');
if (!name.length) {
res.send({});
} else {
var query = client.query('SELECT * FROM BEATLES WHERE name =\''+name+'\' LIMIT 1');
var data = {};
query.on('row', function(row) {
data = row;
res.send(data);
});
};
});
app.listen(80, '127.0.0.1');

我注意到,虽然其他答案解释了什么是需求,并且它用于在Node中加载模块,但他们没有就如何在浏览器中工作时加载节点模块给出完整的回答。

这很简单。按照您的描述,使用npm安装您的模块,模块本身将位于一个通常称为node_modules的文件夹中。

现在,将其加载到您的应用程序中的最简单方法是从您的html引用它,并使用指向该目录的脚本标记。即,如果您的node_modules目录位于项目的根目录中,与您的index.html处于同一级别,您将在index.html中写入:

<script src="node_modules/ng"></script>

整个脚本现在将加载到页面中-因此您可以直接访问其变量和方法。

还有其他方法在更大的项目中更广泛地使用,例如像require.js这样的模块加载器。在这两种方法中,我自己没有使用过Request ire,但我认为很多人认为这是一条路。

您知道当您在浏览器中运行JavaScript时,您可以访问“windows”或Math等变量吗?您不必声明这些变量,它们是为您编写的,供您随时使用。

好吧,当你在Node.js环境中运行一个文件时,有一个变量可以使用。它被称为“模块”它是一个对象。它有一个名为“导出”的属性。它的工作原理如下:

在我们将命名为example.js的文件中,您可以这样写:

example.js

module.exports = "some code";

现在,您希望在另一个文件中使用此字符串“一些代码”。

我们将命名另一个文件otherFile.js

在这个文件中,你写:

otherFile.js

let str = require('./example.js')

该要求()语句进入您放在其中的文件,查找存储在module.exports属性上的任何数据。代码的let str=…部分意味着该要求语句返回的任何内容都存储在str变量中。

所以,在这个例子中,最终结果是在otherFile.js你现在有:

let string="一些代码";

  • 或-

让str = ('./ example.js)module.exports

备注:

写在需要语句中的文件名:如果它是一个本地文件,它应该是example.js.的文件路径另外,默认情况下添加了. js扩展名,所以我不必写它。

当需要node.js库(如Express)时,您可以执行类似的操作。在express.js文件中,有一个名为“模块”的对象,其属性名为“导出”。

所以,它看起来像沿着这些线,在引擎盖下(我有点初学者,所以其中一些细节可能不准确,但它是为了展示概念:

express.js

module.exports = function() {
//It returns an object with all of the server methods
return {
listen: function(port){},
get: function(route, function(req, res){}){}
}
}

如果你需要一个模块,它看起来像这样:

模块名称

如果您需要本地文件,它看起来像这样: const localFile=需要("./path/to/local-file");

(注意文件名开头的./)


另请注意,默认情况下,导出是一个对象…例如module.exports={}因此,您可以在将值分配给module.exports.之前编写module.exports.myfunction = () => {} 但是您也可以通过编写module.exports=“我不再是对象”来替换对象。

两种口味的module.exports/要求:

(见这里

口味1
导出文件(misc.js):

var x = 5;
var addX = function(value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

其他文件:

var misc = require('./misc');
console.log("Adding %d to 10 gives us %d", misc.x, misc.addX(10));

风味2
导出文件(user.js):

var User = function(name, email) {
this.name = name;
this.email = email;
};
module.exports = User;

其他文件:

var user = require('./user');
var u = new user();

巫术。
恕我直言,现有的答案还有很多不足之处。

一开始,这很令人困惑。
您有一个(未定义)函数“需要”,用于获取模块。
在上面提到的模块中,你可以使用require, exports and module
而不需要定义它们。 并不是说你可以在JS中使用未定义的变量,但你不能使用未定义的函数。
所以一开始看起来有点像魔术。
但是所有的魔法都是建立在欺骗的基础上的。

如果再深入一点,你会发现其实很简单:
需要只是一个(非标准)函数在全球范围内定义
(全局范围=浏览器中的窗口对象,nodejs中的全局对象)。
请注意,默认情况下,“需要函数”仅在NodeJS中实现,而不是在浏览器中实现。
另外,请注意,为了增加混淆,对于浏览器,有需要JS,其中,尽管名称包含字符"需要",需要JS绝对不实现需要/的共同JS-而需要JS实现AMD,这是类似的东西,但不相同(也就是不兼容)。
最后一个只是你在理解需求的路上必须意识到的一件重要事情。

现在,为了回答“什么是需要”这个问题,我们“只”需要知道这个函数的作用。
这也许最好用代码来解释。

这是一个Michele Nasti的简单实现,您可以找到在他的github页面的代码。

让我们称我们的需求函数的最小化实现为“myRequest ire”:

function myRequire(name)
{
console.log(`Evaluating file ${name}`);
if (!(name in myRequire.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = fs.readFileSync(name, 'utf8');
let module = { exports: {} };
myRequire.cache[name] = module;
let wrapper = Function("require, exports, module", code);
wrapper(myRequire, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequire.cache[name].exports;
}
myRequire.cache = Object.create(null);
window.require = myRequire;
const stuff = window.require('./main.js');
console.log(stuff);

现在您注意到,这里使用了对象“fs”。
为了简单起见,Michele刚刚导入了NodeJS fs模块:

const fs = require('fs');

这是不必要的。
因此,在浏览器中,您可以使用SYNCHRONOUS XmlHttpRequest:

制作一个简单的需求实现
const fs = {
file: `
// module.exports = \"Hello World\";
        

module.exports = function(){ return 5*3;};
    

`
, getFile(fileName: string, encoding: string): string
{
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
let client = new XMLHttpRequest();
// client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");


// open(method, url, async)
client.open("GET", fileName, false);
client.send();
if (client.status === 200)
return client.responseText;


return null;
}




, readFileSync: function (fileName: string, encoding: string): string
{
// this.getFile(fileName, encoding);
return this.file; // Example, getFile would fetch this file
}
};

基本上,需要这样做的是,它下载一个JavaScript文件,在一个匿名命名空间(又名函数)中对其进行评估,参数为“需要”、“导出”和“模块”,并返回导出,这意味着对象的公共函数和属性。

请注意,此评估是递归的:您需要文件,而文件本身也可以需要文件。

这样,在你的模块中使用的所有“全局”变量都将被命名空间中的变量所包围,并且不会用不需要的变量污染全局作用域。

此外,通过这种方式,您可以重用代码而不依赖命名空间,因此您可以在JavaScript中获得“模块化”。引号中的“模块化”,因为这并不完全正确,因为您仍然可以编写window.bla/global.bla,因此仍然污染全局范围……此外,这建立了私有函数和公共函数之间的分离,公共函数是导出。

而不是说

module.exports = function(){ return 5*3;};

你也可以说:

function privateSomething()
{
return 42:
}




function privateSomething2()
{
return 21:
}




module.exports = {
getRandomNumber: privateSomething
,getHalfRandomNumber: privateSomething2
};

并返回一个对象。

此外,因为您的模块在带有参数的函数中进行评估 “要求”、“出口”和“模块”,您的模块可以使用未声明的变量“要求”、“出口”和“模块”,这一开始可能会令人吃惊。那里的要求参数当然是一个指向保存在变量中的要求函数的指针。
很酷吧?
从这个角度看,需要失去了它的魔力,变得简单。

现在,真正的需求函数将做更多的检查和怪癖,当然,但这是归结为什么的本质。

此外,在2020年,您应该使用ECMA实现,而不是要求:

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";

如果您需要动态非静态导入(例如,根据浏览器类型加载一个Poly填充),则可以使用ECMA导入函数/关键字:

var promise = import("module-name");

请注意,导入不像需要那样是同步的。
相反,导入是一个承诺,所以

var something = require("something");

成为

var something = await import("something");

因为导入返回一个Promise(异步)。

因此,基本上,与需要不同,导入将fs.readFileSync替换为fs.readFileAsync。

async readFileAsync(fileName, encoding)
{
const textDecoder = new TextDecoder(encoding);
// textDecoder.ignoreBOM = true;
const response = await fetch(fileName);
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
// let json = await response.json();
// let txt = await response.text();
// let blo:Blob = response.blob();
// let ab:ArrayBuffer = await response.arrayBuffer();
// let fd = await response.formData()
// Read file almost by line
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read#Example_2_-_handling_text_line_by_line
let buffer = await response.arrayBuffer();
let file = textDecoder.decode(buffer);
return file;
} // End Function readFileAsync

这当然要求导入函数也是异步的

"use strict";
async function myRequireAsync(name) {
console.log(`Evaluating file ${name}`);
if (!(name in myRequireAsync.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = await fs.readFileAsync(name, 'utf8');
let module = { exports: {} };
myRequireAsync.cache[name] = module;
let wrapper = Function("asyncRequire, exports, module", code);
await wrapper(myRequireAsync, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequireAsync.cache[name].exports;
}
myRequireAsync.cache = Object.create(null);
window.asyncRequire = myRequireAsync;
async () => {
const asyncStuff = await window.asyncRequire('./main.js');
console.log(asyncStuff);
};

甚至更好,对吧?
嗯,是的,除了没有ECMA方式来动态同步导入(没有承诺)。

现在,为了理解影响,如果你不知道那是什么,你绝对可能想要在这里阅读Promise/async-wait

但简单地说,如果一个函数返回一个Promise,它可以被“等待”:

"use strict";
function sleep(interval)
{
return new Promise(
function (resolve, reject)
{
let wait = setTimeout(function () {
clearTimeout(wait);
//reject(new Error(`Promise timed out ! (timeout = ${timeout})`));
resolve();
}, interval);
});
}

然后,承诺通常会这样使用:

function testSleep()
{
sleep(3000).then(function ()
{
console.log("Waited for 3 seconds");
});
}

但是当你返回一个Promise时,你也可以使用wait,这意味着我们摆脱了回调(有点-实际上,它在编译器/解释器中被替换为状态机)。
通过这种方式,我们使异步代码感觉像同步的,所以我们现在可以使用try-catch进行错误处理。
请注意,如果您想在函数中使用wait,则必须将该函数声明为async(因此是async-wait)。

async function testSleep()
{
await sleep(5000);
console.log("i waited 5 seconds");
}

另外请注意,在JavaScript中,没有办法从同步函数(你知道的那些)调用异步函数(阻塞地)。因此,如果你想使用wait(又名ECMA导入),你所有的代码都需要是异步的,如果一切都不是异步的,这很可能是一个问题…

当您需要一个无效的JavaScript文件时,例如当您需要css、html、txt、svg和图像或其他二进制文件时,这种简化的需要实现失败的一个例子。
很容易看出原因:
例如,如果你将超文本标记语言放入JavaScript函数体中,你当然会理所当然地得到

SyntaxError: Unexpected token '<'

因为Function("bla", "<doctype...")

现在,如果你想扩展它,例如包括非模块,你可以只检查code.indexOf("module.exports") == -1的下载文件内容,然后例如ava(“jQuery内容”)而不是Func(只要你在浏览器中就可以正常工作)。由于使用Fetch/XmlHttpRequest下载受同源策略的约束,并且完整性由SSL/TLS确保,因此在这里使用ava是相当无害的,前提是你在将JS文件添加到站点之前检查它们,但这应该是标准操作过程。

请注意,类似要求的功能有几种实现:

  • 通用JS(CJS)格式用于Node.js,使用一个需求函数和module.exports来定义依赖和模块。npm生态系统就是建立在这种格式之上的。(这就是上面所说的)

  • 异步模块定义(AMD)格式,在浏览器中使用,使用定义函数来定义模块。(基本上,这是你永远不想使用的过于复杂的陈旧废话)。此外,AMD是由是否必填实现的格式(请注意,尽管名称包含字符“需要”,AMD绝对不是Common JS)。

  • ES模块(ESM)格式。从ES6(ES2015)开始,JavaScript支持原生模块格式。它使用导出关键字来导出模块的公共API,使用导入关键字来导入它。这是你应该使用的如果你对古老的浏览器不感兴趣,比如Safari和IE/EdgeHTML

  • 如果您需要支持旧浏览器,您应该使用的(Safari&IE&旧版本的Chrome在手机/平板电脑上),因为它可以加载所有格式[对于一些,需要插件],可以处理循环依赖,以及CSS和超文本标记语言-不要将你的模块定义为system.register格式是相当复杂的记住它可以读取其他更简单的格式

  • 通用模块定义(UMD)格式,兼容上述所有格式(ECMA除外),用于浏览器和Node.js.它特别有用如果您编写的模块可以在NodeJS和浏览器中使用。它有点缺陷,因为它不支持最新的ECMA模块,虽然(也许这会得到修复)-使用System.register代替。

关于函数参数“导出”的重要旁注:
JavaScript使用按值共享调用-这意味着对象作为指针传递,但指针值本身是按值传递的,而不是通过引用传递的。所以你不能通过分配一个新对象来覆盖导出。相反,如果你想覆盖导出,你需要将新对象分配给module.exports-因为嘿,模块是按值传递的指针,但module.exports中的导出是对原始导出指针的引用。

模块范围的重要侧记:
模块的评估是一次,然后是缓存
这意味着您的所有模块都有一个单例范围。
如果你想要一个非单例范围,你必须这样做:

var x = require("foo.js").createInstance();

或只是

var x = require("foo.js")();

使用您的模块返回的适当代码。