npm(Node 包管理工具)是一个命令行工具,用于安装、创建和分享为 Node.js 编写的 JavaScript 代码包。在 npm 上有许多开放源码软件包,所以在项目启动之前,需要一些时间来探索,这样你就不会最后重新创建轮子来处理像日期或从 API 获取数据这样的事项。

在这个课程中,你将学习使用 npm 的基本知识,包括如何使用 package.json 和如何管理已安装的依赖项。

package.json 配置

package.json 文件是所有 Node.js 项目和 npm 包的枢纽, 和 HTML 文档中的 <head> 部分用来描述网页的配置信息(元数据)一样,它存储你的项目的相关信息。 它由单个 JSON 对象组成,并以键值对的形式存储项目信息, ==且至少包含两个必填字段:“name” 和 “version”——但是最好提供有关项目的其他信息==,这将对用户或者维护者有所帮助。

如果你能找到项目的文件树,那么可以在文件树的最外层找到 package.json, 在接下来的几个挑战中你将完善这个文件。

给 package.json 添加作者

在这个文件中最常见的信息之一是 author 字段, 它说明了项目的创建者,可以包含一个带有联系人信息或其他信息的字符串或对象。 对于较大的项目,建议使用对象;但是在我们的项目中,一个简单的字符串就够了,比如下面的例子:

1
"author": "Jane Doe",

给 package.json 添加描述

一个好的 package.json 文件的下一部分就是 description 字段——简短精悍的的项目描述。

如果有一天你打算向 npm 发布一个软件包,当用户决定是否安装你的软件包时,这个字符串就能向用户表明你的想法。 然而,这并不是使用描述的唯一场景:它也是一种很好的总结项目的方式, 可以帮助其它开发者、维护者甚至自己在未来快速地了解项目,对于任何一个 Node.js 项目来说都非常重要。

无论项目计划是什么,都建议使用描述。 类似这样:

1
"description": "A project that does something awesome",

注意: 请记住使用双引号(”)包裹字段名并且使用逗号(,)分隔字段。

给 package.json 添加关键词

在 keywords 字段中可以使用相关的关键字描述项目。 下面是一个示例:

1
"keywords": [ "descriptive", "related", "words" ],

正如你所见的,这个字段的结构是一个由双引号字符串组成的数组。

给 package.json 添加许可证

license 字段将告知用户允许他们拿这个项目干什么。

开源项目常见的协议有 MIT 和 BSD 等。 许可证信息并不是必须的。 大多数国家的版权法会默认让你拥有自己创作的作品的所有权。 但是,明确说明用户可以做什么和不能做什么会是一个很好的做法。 这里有一个 license 字段的例子:

1
"license": "MIT",

给 package.json 添加版本号

version 是 package.json 文件中必填字段之一, 这个字段描述了当前项目的版本, 下面是一个示例:

1
"version": "1.2.0",

使用 npm 的外部包扩展项目

强大的依赖管理特性是使用包管理器的最大原因之一。 每当在新的计算机上开始一个项目时,无需手动,npm 会自动安装所有的依赖项。 但是 npm 如何准确地知道项目需要哪些依赖呢? 来看看 package.json 文件中 dependencies 这一部分。

在这部分,你的项目需要按照下面这种格式来存储依赖包:

1
2
3
4
"dependencies": {
"package-name": "version",
"express": "4.14.0"
}

通过语义化版本来管理 npm 依赖关系

在 package.json 文件的依赖项中,npm 包的 Versions 遵循语义化版本(SemVer,Semantic Versioning),它是一种旨在使管理依赖项更加容易的软件版本控制的行业标准。 在 npm 上发布的库、框架或其它工具都应该使用语义化版本,以便让用户清晰地知道如果项目升级将带来哪些改变。

在使用外部依赖项(大多数情况都是这样)进行软件开发时,了解语义化版本会很有用。 这些数字保存着项目的偶然发生的破坏性改变,不会让人对项目昨天还正常,今天却无法运行而百思不解。 根据官网,这是语义化版本的工作方式:

1
"package": "MAJOR.MINOR.PATCH"

当做了不兼容的 API 修改,应该增加主版本号(MAJOR); 当新增了向下兼容的新功能时,应该增加次版本号(MINOR); 当修复了向下兼容的 bug 时,应该增加修订号(PATCH)。 这意味着修订号是用来修复错误的,次版本号则是添加了新功能,但它们都没有破坏之前的功能。 主版本号(MAJOR)是添加了不兼容早期版本的更改。

用波浪号维持依赖项的最新修订号

在上一个挑战中,npm 只包含特定版本的依赖包。 如果想让项目各个部分保持相互兼容,锁定依赖包版本是一个行之有效的办法。 但是大多数情况下,我们并不希望错过依赖项的问题修复,因为它们通常包含重要的安全补丁,而且它们理论上也会兼容我们既有的代码。

可以在依赖项的版本号前加一个波浪号(~),以让 npm 依赖项更新到最新的修订版。 这里有一个允许升级到任何 1.3.x 的例子:

1
"package": "~1.3.8"

用脱字符(^)来使用依赖项的最新次要版本

和上一个挑战中我们学到的用波浪号来安装最新的修订版依赖一样,脱字符(^)也允许 npm 来安装功能更新。 它们的不同之处在于:脱字符允许次版本和修订版更新。

你当前的 @freecodecamp/example 版本应该是“~1.2.13”,它允许 npm 安装到最新的 1.2.x 版本。 如果使用插入符号(^)作为版本前缀,npm 将被允许更新到任何 1.x.x 版本。

1
"package": "^1.3.8"

这会将依赖包更新到任意的 1.x.x 版本。

注意: 原来的版本号不用更改。

Node.js 是一个 JavaScript 运行时,它允许开发人员在 JavaScript 中写入后端(服务器侧)程序。Node.js 有几个内置的模块(小型、独立的程序)来帮助实现这一点,一些核心模块包括:

  • 服务器一样运作的 HTTP
  • 一个读取和修改文件的模块的文件系统

从 npm下载和管理软件包。这些软件包是较小模块的集合,可以帮助你构建更大更复杂的程序。

Express 是一个轻量级的 Web 应用程序框架,是 npm 上最流行的包之一。 Express 可以更轻松地为你的应用程序创建服务器和处理路由,例如在人们访问特定端点(如 /blog)时将人们引导到正确页面。

Node 和 Express 的基础知识,包括如何创建服务器、处理不同的文件,以及处理不同的浏览器请求。

启动一个 Express 服务

在 myApp.js 文件的前两行中,你可以看到创建一个 Express 应用对象很简单。 这个对象有几种方法,在后面的挑战中将学习到其中的许多部分。 一个基础的方法是 app.listen(port)。 它处于运行状态时告诉服务器监听指定的端口。 出于测试的原因,需要应用在后台运行,所以在 server.js 中已经添加了这个方法。

让我们在服务端输出第一个字符串! 在 Express 中,路由采用这种结构:app.METHOD(PATH, HANDLER), METHOD 是 http 请求方法的小写形式, PATH 是服务器上的相对路径(它可以是一个字符串,甚至可以是正则表达式), HANDLER 是匹配路由时 Express 调用的函数, 处理函数采用这种形式:function(req, res) {...},其中 req 是请求对象,res 是响应对象, 例如:

1
2
3
function(req, res) {
res.send('Response String');
}

将会响应一个字符串“Response String”。

提供 HTML 文件服务

通过 res.sendFile(path) 方法给请求响应一个文件, 可以把它放到路由处理 app.get('/', ...) 中。 在后台,这个方法会根据你想发送的文件的类型,设置适当的消息头信息来告诉浏览器如何处理它, 然后读取并发送文件, 此方法需要文件的绝对路径。 建议使用 Node. js 的全局变量 __dirname 来计算出这个文件的绝对路径:

1
absolutePath = __dirname + relativePath/file.ext

提供静态资源服务

HTML 服务器通常有一个或多个用户可以访问的目录。 你可以将应用程序所需的静态资源 (样式表、脚本、图片) 放在那里。

在 Express 中可以使用中间件 express.static(path) 来设置此功能,它的参数 path 就是包含静态资源文件的绝对路径。

如果你不知道什么是中间件……别担心,我们将在后面详细讨论。 其实,中间件就是一个拦截路由处理方法并在里面添加一些信息的函数。 使用 app.use(path, middlewareFunction) 方法来加载一个中间件, 它的第一个参数 path 是可选的, 如果没设置第一个参数,那么所有的请求都会经过这个中间件处理。

在指定路由上提供 JSON 服务

HTML 服务器提供 HTML 服务,而 API 提供数据服务。 REST(REpresentational State Transfer)API 允许以简单的方式进行数据交换,对于客户端不必要知道服务器的细节。 客户只需要知道资源在哪里(URL),以及想执行的动作(动词)。 GET 动词常被用来获取无需修改的信息。 如今,网络上的移动数据首选格式是 JSON, 简而言之,JSON 是一种可以方便地用字符串表示 JavaScript 对象的方式,因此它很容易传输。

我们来创建一个简单的 API,创建一个路径为 /json 且返回数据是 JSON 格式的路由, 可以像之前那样用 app.get() 方法来做。 然后在路由处理部分使用 res.json() 方法,并传入一个对象作为参数, 这个方法会结束请求响应循环(request-response loop),然后返回数据。 一个有效的 JavaScript 对象会转化为字符串,然后会设置适当的消息头来告诉浏览器:“这是一个 JSON 数据”,最后将数据返回给客户端。 一个有效的对象通常是这种结构:{key: data}, data 可以是数字、字符串、嵌套对象或数组, data 也可以是变量或者函数返回值,在这种情况下,它们先求值再转成字符串。

使用 .env 文件

.env 文件是一个用于将环境变量传给应用程序的隐藏文件, 这是一个除了开发者之外没人可以访问的私密文件,它可以用来存储你想保密或者隐藏的数据, 例如,它可以存储第三方服务的 API 密钥或者数据库 URI, 也可以使用它来存储配置选项, 通过设置配置选项,你可以改变应用程序的行为,而无需重写一些代码。

在应用程序中可以通过 process.env.VAR_NAME 访问到环境变量。 process.env 对象是 Node 程序中的一个全局对象,可以给这个变量传字符串。 习惯上,变量名全部大写,单词之间用下划线分隔。 .env 是一个 shell 文件,因此不需要用给变量名和值加引号。 还有一点需要注意,当你给变量赋值时等号两侧不能有空格,例如:VAR_NAME=value。 通常来讲,每一个变量定义会独占一行。

Replit.com

Replit.com是一个在线编程环境和编程社区。它提供了一个简单的用户界面,允许用户在浏览器中编写,编译和运行代码。它还提供了一些常用编程语言的支持,如JavaScript,Python,Ruby等等。 Replit.com支持在线协作和版本控制。它还具有与社区共享代码和解决方案的功能。

什么是 HTTP 和 HTTPS

HTTP(HyperText Transfer Protocol)是一个用于分发超文本的协议,是在计算机与 Internet 之间传送超文本文档的标准。当你在浏览器中输入一个网址时,你实际上是发送了一个 HTTP 请求。服务器收到请求后,会将所请求的网页返回给浏览器,浏览器再将其渲染并显示给用户。

HTTPS(HyperText Transfer Protocol Secure)是在传输层使用加密的 HTTP,它通常用于在网络上传送敏感信息(如在线银行或电子商务)。 HTTPS 在传送数据时使用了安全套接层(SSL)或者传输层安全(TLS)来加密数据。

// TODO 进一步补充相关原理
// 用 nodejs 实现一个 http

模块化方案

// TODO 解释 esm
// TODO 解释 cjs

UMD

UMD 是 Universal Module Definition 的缩写。这是一种模块定义的方式,它可以被各种模块加载器或者浏览器直接使用。

UMD 是为了解决 JavaScript 在不同环境下模块加载和使用的问题而创建的。它可以在各种模块加载器之间进行模块的加载和使用,也可以在没有模块加载器的环境下直接使用。

常见的模块加载器有 CommonJS 和 AMD。使用 UMD 可以使得你的模块在这两种模块加载器之间进行切换,同时也可以在没有模块加载器的环境下使用。

UMD 的一个缺点是代码可能会变得冗长,因为它需要支持多种不同的加载方式。

转到某目录

方法一:
直接把相关目录拖到命令行(仅限 cmd )

方法二:

1
cd <target-path>

方法三:
文件右击, 点击 Git Bash here, 这种方法需要安装 Git Bash

生成项目结构

使用 tree 命令

1
2
tree -L 1
# 表示展开一层

查看 ip 地址

1
ipconfig

题目

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例 1:
输入:

1
2
3
4
5
6
grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]

输出:1

示例 2:
输入:

1
2
3
4
5
6
grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]

输出:3  

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 ‘0’ 或 ‘1’

解1

思路:将相连的 1 加入同一个集合,如果两个 1 相连则把他们所属的集合合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function numsIslands(grid){
// 建立 `1`的坐标到其所属岛屿的映射
const posToIlands = {};
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid.length; j++){
if(grid[i][j] === '0') return;

posToIlands[`${i}-${j}`] = [`${i}-${j}`];
if(grid[i - 1][j] === '1'){
const merged = posToIlands[`${i - 1}-${j}`].concat([`${i}-${j}`])
posToIlands[`${i}-${j}`] = merged;
posToIlands[`${i}-${j}`] = merged;
}
if(grid[i][j - 1] === '1'){
const merged = posToIlands[`${i - 1}-${j}`].concat([`${i}-${j}`])
posToIlands[`${i}-${j}`] = merged;
posToIlands[`${i}-${j}`] = merged;
}
}
}

for(let key in posToIlands){

}
}
0%