引言
今天,我们来从0开始学习一下node。node是一个什么样的东西,它被打造出来是干什么用的。
node,是一个能读懂js的工具,它的出现让js又上了一个台阶,让js不仅能做前端也能做后端。
js 不能直接做后端开发是因为 js 内部不具有可以联调操作系统的API。
node 中用 c++ 开发了很多可以跟操作系统交互的模块,node 中内置js引擎,所以node可以读懂js,也就是说可以通过调用node中的各个模块来实现跟操作系统交互。
npm
在node中还存在一个叫npm的东西,它是node的包管理工具,它就相当于是一个node的仓库。一些程序员会将他们写好的方法或封装好的包上传到这个仓库中,当我们想使用时,就能直接下载下来,非常方便。
npm就是官方打造的包管理工具,还有一些非官方打造的包管理工具,比如pnpm、yarn。pnpm是目前最主流最好用的包管理工具,我们可以直接在终端中输入npm i pnpm -g
来下载它。
我们来体验一下npm。我们在npm中输入npm init
,进行初始化,它会弹出一大堆问题,直接全选默认。我们会发现我们的文件夹多了一个json格式的文件。
这个文件里面有一个对象,你会发现,对象里放的就是刚刚问我们的一些问题。
{
"name": "nodebase",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\"&& exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
}
这个json其实非常的重要。当我们往git上上传文件时,通常一些引入的包我们并不会上传。因为没有必要,这些包谁都能拿到,并不是我们自己写的代码。而我们下载了哪些包都会记录在这个json文件中。比如我随便下一个包,koa, 在json文件中我们就能查看到。
{
"name": "nodebase",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\"&& exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"koa": "^2.15.3"
},
}
而当别人拿到我们上传的代码后,他是没有这些包的,但json文件他是有的,他就能查看到自己缺少了哪些包。只要他在终端中输入npm i
,就能自动下载那些缺少的包,非常方便。
如果我们想运行一个node指令,可以在json文件中的"scripts"中这样写。假如我们想运行index.js,可以写成"dev": "node index.js"。dev可以随便叫。
这样当我们想运行index.js时,直接在终端输入npm run dev
就行了。
模块化语法
我们再来学习一下模块化语法。我们可以将一些公用的方法写到别的js文件中,当我在这个文件中想使用这些方法时,可以引入进来。我们通常将这些js文件放到utils文件夹下。
1.CommonJS 规范
我们先来学习一下 CommonJS 语法。我们在utils文件夹下创建一个add.js文件,里面写一个加法函数。
function add(a, b) {
return a + b;
}
然后我想在index.js文件中使用它,可以这样引入。
function add(a, b) {
return a + b;
}
module.exports = {
add:add
}
这叫抛出,我们抛出了一个对象,里面key为add值为add函数,如果key和value相同可以简写。只写一个add。
当我们想在index.js中使用这个方法时,就这样写:
const { add } = require('./utils/add.js')
console.log(add(1, 2));
引入utils文件夹下的add.js文件。因为我们引入的是一个对象,所以我们要用对象的解构,这个add就相当于add.js文件中的add方法了。这里的add可以随便叫。
这样我们就能使用add方法了。
它还有另外一种写法。在add.js文件中,我们可以这样写:
exports.add = function (a, b) {
return a + b;
}
直接在add方法前面点乘exports。index.js中的写法不变,这样抛出的还是一个对象。
我们习惯性用第一种方法,因为当我们引入多个方法时,我们可以直接这样写:
function add(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
module.exports = {
add,
minus
}
就不用在每个方法前面点乘exports了。
2. ESmodules 规范
还有第二种模块化语法,ESmodules。
在使用这个语法之前,我们需要先在package.json中将type改成‘module’,node是默认支持CommonJS语法的,所以我们要手动更改一下。
我们再在utils文件夹下创建一个formate.js文件,里面随便写了一个格式化日期的方法。
function formatDate(date, format) {
if (!date instanceof Date) {
throw new Error('Invalid date');
}
const options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
};
return date.toLocaleString('en-US', options).replace(',', format);
}
我们这么将这个方法抛出:
function formatDate(date, format) {
if (!date instanceof Date) {
throw new Error('Invalid date');
}
const options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
};
return date.toLocaleString('en-US', options).replace(',', format);
}
export default formatDate
然后在index.js中这样写:
import formatDate from "./utils/formate.js";
console.log(formatDate(new Date));
这样就成功引入了formatDate方法,这里不需要用括号括起,因为抛出的不是一个对象,就是一个方法。
当然ESmodules也有第二种写法,我们可以直接在formatDate方法前面加上export。
export function formatDate(date, format) {
if (!date instanceof Date) {
throw new Error('Invalid date');
}
const options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
};
return date.toLocaleString('en-US', options).replace(',', format);
}
用这种方法抛出的就是一个对象了,所以我们要在index.js中进行解构。
import { formatDate } from "./utils/formate.js";
console.log(formatDate(new Date));
并且这里formatDate不能随便叫,只能取成那个方法的名字。这样同样也能引入。
模块
我们再来看一下node中常用的一些模块。
global
我们知道,在浏览器中有一个全局对象window,而在node中也有一个全局对象,叫global。
我们可以输出global看看。
console.log(global);
在global身上有些这样的东西。
我们来看一些window环境下没有的global方法。
console.log(__filename);
得到当前这个文件在电脑中的绝对路径。
console.log(__dirname);
得到当前文件夹在电脑中的绝对路径。
console.log(process.argv);
process叫进程。process上有一个方法argv,它能读懂我们在终端中输入的代码。比如我在终端中敲一个hello。
它就会出现在这个数组中。
console.log(process.cwd());
这个方法能获取当前工作的绝对路径。
console.log(process.env);
它能读取当前执行环境的环境变量。
console.log('hello');
process.exit()
console.log('world');
process.exit()可以中断当前的进程,所以只会输出hello。
process.stdout.write('hello')
process.stdout.write(' ')
process.stdout.write('world')
process.stdout.write('\n')
stdout能输出一个标准的流类型,这个流类型的资源可以进行一个合并展示。
process.stdin.on('data', function (data) {
console.log(`用户输入: ${data}`);
})
stdin是往node中输入一个流类型,当data有了,后面的函数就能触发。
比如我往终端中输入一个hello,后面的函数就触发了。这个方法能让我们与进程进行交互。
const buf = Buffer.from('你好')
console.log(buf);
Buffer是node自带的一个对象,它身上有一个方法from能将一种数据类型转换成Buffer流类型,我们输出看一下。
转换成16进制了。当我们想转回字符串时,可以用buf.toString()。
path
我们再来看一下path模块,提供了一些什么API供我们使用。
join方法能将我们输入的字符串自动拼接成相对路径。
console.log(path.join('a', 'b', 'c'));
resolve方法能将我们输入的字符串拼接成绝对路径。
console.log(path.resolve('a', 'b', 'c'));
dirname方法能拿到当前文件夹所在文件夹的绝对路径。
console.log(path.dirname(process.cwd()));
basename方法能拿到一个路径中的文件名。
console.log(path.basename('/a/b/c.js'));
extname方法能获取一个路径的扩展名。
console.log(path.extname('/a/b/c.js'));
normalize方法能调整不规范的路径。
console.log(path.normalize('a//b/c.js'));
parse方法能将一个路径解析成对象。
console.log(path.parse('/home/article/index/readme,md'));
path.sep代表‘\’。
console.log(path.sep);
fs
我们再来看一下fs模块,文件系统。
这个模块里面有很多方法并且非常强大。它能让js与操作系统进行交互并且读取某一个盘中的文件。
readFile方法能读取到一个文件资源,这个方法是异步的。在node里面,所有方法都打造了同步和异步版本,取决于你想使用哪个。
我在同级文件夹下面创建了一个temp.html,用这个方法能读取到。
fs.readFile('./temp.html', 'utf-8', (err, data) => {
if (!err) {
console.log(data);
}
})
这个方法还有同步版本。
let html = ''
html = fs.readFileSync('./temp.html', 'utf-8')
console.log(html);
readFileSync方法就是同步的。
http
创建 HTTP Server
const server = http.createServer((req, res) => {
res.end('hello world')
})
监听端口
server.listen(3000, () => {
console.log('Listening on port 3000');
})