我试图让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中工作之前,我必须做npm install pg。这是关于什么的?我在目录中找不到pg文件。它把它放在哪里,JavaScript是如何找到它的?


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

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

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

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

In browser JavaScript, scripts are added via the <script> element. When they execute, they all have direct access to the global scope, a "shared space" among all scripts. Any script can freely define/modify/remove/call anything on the global scope. In Node.js, each module has its own scope. A module cannot directly access things defined in another module unless it chooses to expose them. To expose things from a module, they must be assigned to exports or module.exports. For a module to access another module's exports or module.exports, it must use require().

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

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

需要(),模块。exports和exports是特定于Node.js的模块系统的api。浏览器不实现这个模块系统。

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

NPM是一个包存储库服务,用于托管已发布的JavaScript模块。NPM install是一个命令,允许你从它们的存储库下载包。

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

npm命令行把所有下载的模块放在你运行npm install的node_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 (CommonJS和Node)。

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

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

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

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

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

作为一个简单的例子,我们将创建一个URL,它以JSON的形式返回关于给定名称的beatles的一些事实。

/* 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');

我注意到,虽然其他答案解释了需要什么,它是用来加载节点模块,他们没有给出一个完整的答复,如何加载节点模块时,在浏览器中工作。

这很简单。如你所述,使用npm安装你的模块,模块本身将位于通常称为node_modules的文件夹中。

现在最简单的方法加载到你的应用程序是引用它从你的html与一个script标签指向这个目录。例如,如果你的node_modules目录在项目的根目录中,与你的index.html处于同一级别,你可以在index.html中这样写:

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

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

还有其他方法在大型项目中更广泛地使用,例如require.js这样的模块加载器。在这两者中,我自己没有使用过Require,但我认为它是被很多人认为是可以走的路。


你知道当你在浏览器中运行JavaScript时,你可以访问像“window”或Math这样的变量吗?你不需要声明这些变量,它们已经被写出来供你随时使用。

当你在Node.js环境中运行一个文件时,你可以使用一个变量。它叫做“模块”,它是一个对象。它有一个名为“exports”的属性。它是这样工作的:

在我们命名为example.js的文件中,你这样写:

example.js

module.exports = "some code";

现在,您希望将字符串“some code”放在另一个文件中。

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

在这个文件中,你写:

otherFile.js

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

require()语句会转到您放入其中的文件中,查找存储在模块中的任何数据。出口的财产。let str =…你的部分代码意味着require语句返回的任何东西都存储在STR变量中。

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

Let string = "some code";

或-

让我们

注意:

如果它是一个本地文件,它应该是example.js的文件路径。此外,.js扩展名是默认添加的,所以我不需要编写它。

当需要node.js库(比如Express)时,也可以做类似的事情。在express.js文件中,有一个名为“module”的对象,其属性名为“exports”。

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

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 moduleName = require("module-name");

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

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


还要注意,默认情况下,导出是一个对象。如模块。因此,你可以在给module.exports赋值之前写module.exports.myfunction =() =>{}。但是你也可以通过写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();

Necromancing。 恕我直言,现有的答案还有很多不尽如人意之处。

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

When you dig a little deeper, it turns out it is really quite simple: Require is simply a (non-standard) function defined at global scope. (global scope = window-object in browser, global-object in NodeJS). Note that by default, the "require function" is only implemented in NodeJS, not in the browser. Also, note that to add to the confusion, for the browser, there is RequireJS, which, despite the name containing the characters "require", RequireJS absolutely does NOT implement require/CommonJS - instead RequireJS implements AMD, which is something similar, but not the same (aka incompatible). That last one is just one important thing you have to realize on your way to understanding require.

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

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

让我们把require函数的最小化实现称为“myRequire”:

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简单实现require:

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 
    }
};

基本上,require所做的就是下载一个javascript文件,在一个匿名的命名空间(又名Function)中求值,参数为“require”,“exports”和“module”,并返回导出,这意味着一个对象的公共函数和属性。

注意,这个计算是递归的:您需要文件,文件本身也可以需要文件。

这样,模块中使用的所有“全局”变量都是需求包装器函数名称空间中的变量,并且不会用不需要的变量污染全局作用域。

此外,通过这种方式,您可以在不依赖于名称空间的情况下重用代码,从而获得JavaScript中的“模块化”。"modularity"加引号,因为这并不完全正确,因为你仍然可以编写window.bla/global。呸,因此仍然污染全球范围…此外,这也建立了私人功能和公共功能之间的分离,公共功能是出口。

现在不要说

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

你也可以说:

function privateSomething()
{
    return 42:
}


function privateSomething2()
{
    return 21:
}


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

并返回一个对象。

另外,因为你的模块是在一个带参数的函数中计算的 "require", "exports"和"module",你的模块可以使用未声明的变量"require", "exports"和"module",这一开始可能会让人吃惊。require形参当然是一个指针,指向保存在变量中的require函数。 很酷,对吧? 从这个角度看,“要求”就失去了魔力,变得简单了。

现在,真正的require函数当然会做更多的检查和奇怪的事情,但这是归根结底的本质。

此外,在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";

如果你需要一个动态的非静态的导入(例如加载一个基于浏览器类型的polyfill),有ECMA-import函数/关键字:

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

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

var something = require("something");

就变成了

var something = await import("something");

因为import返回一个承诺(异步)。

基本上,不像require, import替换fs。使用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方法来动态同步导入(没有承诺)。

现在,为了理解其影响,如果你不知道promises/async-await是什么,你可能会想要阅读一下。

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

"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");
    });
}

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

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

同时请注意,在JavaScript中,没有办法从同步函数(你知道的那些)调用异步函数(以阻塞的方式)。所以如果你想使用await (aka ECMA-import),你所有的代码都需要是异步的,这很可能是一个问题,如果一切都不是异步的……

这种简化的require实现失败的一个例子是,当你需要一个不是有效的JavaScript文件时,例如,当你需要css, html, txt, svg和图像或其他二进制文件时。 原因很简单: 如果你把HTML放到JavaScript函数体中,你当然会得到

SyntaxError: Unexpected token '<'

因为函数("bla", "<doctype…")

Now, if you wanted to extend this to for example include non-modules, you could just check the downloaded file-contents for code.indexOf("module.exports") == -1, and then e.g. eval("jquery content") instead of Func (which works fine as long as you're in the browser). Since downloads with Fetch/XmlHttpRequests are subject to the same-origin-policy, and integrity is ensured by SSL/TLS, the use of eval here is rather harmless, provided you checked the JS files before you added them to your site, but that much should be standard-operating-procedure.

注意,有几种require类功能的实现:

the CommonJS (CJS) format, used in Node.js, uses a require function and module.exports to define dependencies and modules. The npm ecosystem is built upon this format. (this is what is implemented above) the Asynchronous Module Definition (AMD) format, used in browsers, uses a define function to define modules. (basically, this is overcomplicated archaic crap that you wouldn't ever want to use). Also, AMD is the format that is implemented by RequireJS (note that despite the name containing the characters "require", AMD absolutely is NOT CommonJS). the ES Module (ESM) format. As of ES6 (ES2015), JavaScript supports a native module format. It uses an export keyword to export a module’s public API and an import keyword to import it. This is the one you should use if you don't give a flying f*ck about archaic browsers, such as Safari and IE/EdgeHTML. the System.register format, designed to support ES6 modules within ES5. (the one you should use, if you need support for older browsers (Safari & IE & old versions of Chrome on mobile phones/tablets), because it can load all formats [for some, plugins are required], can handle cyclic-dependencies, and CSS and HTML - don't define your modules as system.register, though - the format is rather complicated, and remember, it can read the other easier formats) the Universal Module Definition (UMD) format, compatible to all the above mentioned formats (except ECMA), used both in the browser and in Node.js. It’s especially useful if you write modules that can be used in both NodeJS and the browser. It's somewhat flawed, as it doesn't support the latest ECMA modules, though (maybe this will get fixed) - use System.register instead.

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

关于module-Scope的重要提示: 模块只计算一次,然后由require缓存。 这意味着你的所有模块都有一个单例作用域。 如果你想要一个非单例作用域,你必须这样做:

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

或者简单地

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

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