node.js(一)


什么是node.js

Node.js虽然带了一个.js后缀,但是和Javascript没有太大的关系
Node.js = Google(chrome)浏览器+V8引擎+C++语言 编写
本质上是一个“JavaScript运行环境“

提到JavaScript,大家就会想到各种交互组件、异步请求、DOM、BOM等众多内容。它需要依赖浏览器中的JS引擎,来解析JavaScript代码

而Node.js这个运行环境,不仅仅是可以解析JS代码,并且也没有浏览器安全级的各种限制,同时还提供了许多系统级别的API,例如:1.文件的读写、2.进程的管理、3.网络通信。

优势

  1. NodeJs 语法完全是 js 语法,只要你懂 JS 基础就可以学会 Nodejs 后端开发

  2. NodeJs 超强的高并发能力

    在 Java、PHP 或者.net 等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约 2MB 内存。理论上,一个 8GB 内存的服务器可以同时连接的最大用户数为 4000 个左右。要让 Web 应用程序支持更多的用户,就 需要增加服务器的数量,而 Web 应用程序的硬件成本当然就上升了。
    Node.js 不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个 内部事件,通过非阻塞 I/O(但同样会消耗CPU资源)、事件驱动机制,让 Node.js 程序宏观上也是并行的。使用 Node.js,一个 8GB 内存的服务器,可以同时处理超过4 万用户的连接。

  3. 开发周期短、开发成本低、学习成本低

在发起一个事件后,可以继续发起其他事件,当某个事件完成后会以回调函数的方式进行响应的 I/O 就是非阻塞 I/O

其实很多地方都用到了node,比如Vue脚手架,Webpack等等都有node的身影

浏览器环境 Vs node环境

浏览器环境为了安全,禁止读取磁盘,显卡,网卡等操作,也会限制跨域请求。

而node则不仅可以解析代码,也提供了很多系统级别的API:文件读写(fs库),进程管理,网络通信(http库)

node初体验

var http = require('http');
http.createServer(function(req, res) {
    res.writeHead(200, { status : 'OK' });
    res.end('Hello, world!');
    }).listen(8080, '127.0.0.1');
console.log('server listening on port 8080');

运行

node demo.js

成功运行

CommonJS规范

我们可以把公共的功能 抽离成为一个单独的 js 文件 作为一个模块,默认情况下面这个模块里面的方法 或者属性,外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通 过 exports 或者 module.exports 暴露属性或者方法。

npm使用

npm init
npm install 包名 –g (uninstall,update)
npm install 包名 --save-dev (uninstall,update)
npm list -g (不加-g,列举当前目录下的安装包)
npm info 包名(详细信息) npm info 包名 version(获取最新版本)
npm install md5@1(安装指定版本)
npm outdated( 检查包是否已经过时)

nrm使用

NRM (npm registry manager)是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以 快速地在 npm 源间切换。

安装 nrm 在命令行执行命令,npm install -g nrm

全局安装nrm。 使用 nrm 执行命令 nrm ls 查看可选的源。 其中,带 * 的是当前使用的源,上面的输出表明当前源是官方源。

切换 nrm 如果要切换到taobao源,执行命令 nrm use taobao

yarn使用

npm install -g yarn

对比npm: 速度超快: Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。

同时利用并行下载以最大化 资源利用率,因此安装速度更快。

超级安全: 在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。

模板字符串

允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能

使用反引号来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来

例如

console.log('string text line 1\n' +
'string text line 2');
// "string text line 1
// string text line 2"

要获得同样效果的多行字符串,只需使用如下代码:

console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

我们也可以插入表达式,例如:

var a = 5;
var b = 10;
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."

现在通过模板字符串,我们可以使用一种更优雅的方式来表示:

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

响应中用到常用三种API

res.send()

发送HTTP响应。
该body参数可以是一个Buffer对象,一个String对象或一个Array。

样例:

res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.status(404).send('Sorry, we cannot find that!');
res.status(500).send({ error: 'something blew up' });
三种不同参数 express 响应时不同行为

当参数是buffer对象
该方法将Content-Type 响应头字段设置为“application / octet-stream”
如果想Content-Type设置为“text / html”,需使用下列代码进行设置

res.set('Content-Type', 'text/html');
res.send(new Buffer('<p>some html</p>'));

当参数是是String
该方法设置header 中Content-Type为“text / html”:

res.send('<p>some html</p>');

当参数是Arrayor 或 Object
以JSON表示响应,该方法设置header 中Content-Type为“text / html”

Res.status(500).json({});
res.json([body]) 发送一个json的响应

res.json()

该方法res.send()与将对象或数组作为参数相同。 即res.json()就是 res.send([body])第三种情况。同样 ‘content-type’: ‘application/json’。

res.end()

结束响应,告诉客户端所有消息已经发送。当所有要返回的内容发送完毕时,该函数必须被调用一次。如何不调用该函数,客户端将永远处于等待状态。

内置模块

url模块

parse

const url = require('url')
const urlString = 'https://www.baidu.com:443/ad/index.html?
id=8&name=mouse#tag=110'
const parsedStr = url.parse(urlString)   //解析URL
console.log(parsedStr)

解析之后 parsedStr 其实是一个json数据格式

protocol: 'https:',
slashes: true,
auth: null,
host: 'www.baidu.com:443',
port: '443',
hostname: 'www.baidu.com',
hash: '#tag=110',
search: '?id=8&name=mouse',
query: { id: '8', name: 'mouse' },
pathname: '/ad/index.html',
path: '/ad/index.html?id=8&name=mouse'

那么我们可以按需取内容,比如我们要 pathname 就可以

var pathname = url.parse(req.url).pathname

format

将一个解析后的URL对象、转成、一个格式化的URL字符串。

var url = require('url');

var a = url.format({
protocol : 'http' ,
auth : null ,
host : 'example.com:8080' ,
port : '8080' ,
hostname : 'example.com' ,
hash : null ,
search : '?a=index&t=article&m=default',
query : 'a=index&t=article&m=default',
pathname : '/one',
path : '/one?a=index&t=article&m=default',
href : 'http://example.com:8080/one?a=index&t=article&m=default'
});
console.log(a);

//输出结果:http://example.com:8080/one?a=index&t=article&m=default

resolve

主要是用于解决目标URL是基于基本URL的情况,

需要注意有无 / 的区别

const url = require('node:url');
url.resolve('/one/two/three', 'four');         // '/one/two/four'
url.resolve('http://example.com/', '/one');    // 'http://example.com/one'
url.resolve('http://example.com/one', '/two'); // 'http://example.com/two'

querystring模块

从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析,仅有四个方法

  • querystring.parse(str); parse函数:是将一个字符串反序列化为一个对象。到时候像类似于解析URL
  • querystring.escape(str); escape函数:可使传入的字符串进行url编码。
  • querystring.stringify(str); stringif函数:将一个对象序列化成一个字符串,与querystring.parse相对
  • querystring.unescape(); unescape函数:可将含有%的字符串进行解码

jsonp实现

是资料格式 JSON 的一种“使用模式”,可以让网页从别的网域获取资料。第三方产生的响应为json数据的包装(故称之为jsonp,即json padding)。一般用来解决Ajax跨域的问题

其实返回一个函数调用实现动态生成 script 标签从而进行跨域请求。node 实现此方法,需要后端调用前端的函数,保证前端有这个函数。

其实生活中有很多例子,在360搜索中,我们在搜索框随便打一个 a 字母,会有文本框显示相关搜索,原因就是发送了红箭头所指的 JS 请求

那么我们访问一下,是一个函数调用,调用 suggest_so 函数,返回了一系列的对象,也就是我们在搜索下面看到的文本框。这是一个跨域请求,采用 jsonp 这种方式实现。我猜测他是从其他服务器端口调用数据

那么我们将 callback 调用的函数名改了之后,前端也能发生变化

案例

这里前端使用LiveServer打开,在5500端口,而 node 则是默认的4000端口,构建了跨域请求。前端向后端发送请求,后端直接返回了一个对象,前端接受并打印在控制台上

前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- jsonp接口调用 -->

    <script>
        var oscript = document.createElement("script")
        oscript.src="http://localhost:4000/api/user?callback=test"

        document.body.appendChild(oscript)

        function test(obj){
            console.log(obj)
        }
    </script>
</body>
</html>

后端:

const http = require('http')
const url = require('url')

const app = http.createServer((req, res) => {
  let urlObj = url.parse(req.url, true)

  switch (urlObj.pathname) {
    case '/api/user':
      res.end(`${urlObj.query.callback}({"name": "gp145"})`)
      break
    default:
      res.end('404.')
      break
  }
})

app.listen(8080, () => {
  console.log('localhost:8080')
})

执行流程

  1. <script src="http://localhost:80/?callback=zl"></script>发送请求
  2. 服务端收到请求 urlObj.query.callback 此时的值就是 test
  3. 服务端向客户端返回数据,经过字符串拼接,返回给客户端的其实是 test({"name": "gp145"})
  4. 客户端将 test({"name": "gp145"}) 解析为调用了 test 函数且入参为 ({"name": "gp145"}) 并打印在控制台

AJax实现jsonp

$.ajax({
    type: "GET",
    url: "http://localhost:4000/api/user",
    dataType: "jsonp",	
    jsonp: "callback",				//此参数的值与服务器端的req.query.callback对应
    success: function (data) {
        console.log(data)			//输出
    },
    error: function (err) {
        console.log(err)
        console.log('请求错误')
    }
});

跨域CROS

fetch()

英语直译为带来(无方向性的)

全局的 fetch() 方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。

基本语法:

let promise = fetch(url, [options])
  • url —— 要访问的 URL。
  • options —— 可选参数:method,header 等。

当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。

为了获取 response body,我们需要使用一个其他的方法调用。

Response 提供了多种基于 promise 的方法,来以不同的格式访问 body:

  • response.text() —— 读取 response,并以文本形式返回 response,
  • response.json() —— 将 response 解析为 JSON 格式,
  • etc.

那么我们可以使用纯promise语法

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

返回一个包含响应结果的 promise(一个 Response 对象)。当然它只是一个 HTTP 响应,而不是真的 JSON。为了获取 JSON 的内容,我们需要使用 json() 方法。

fetch() 接受第二个可选参数,一个可以控制不同配置的 init 对象。

代码案例:

const http = require('http')
const url = require('url')
const querystring = require('querystring')

const app = http.createServer((req, res) => {
  let data = ''
  let urlObj = url.parse(req.url, true)

  res.writeHead(200, {
    'content-type': 'application/json;charset=utf-8',
    'Access-Control-Allow-Origin': '*'     //允许所有请求都是跨域的
  })

  req.on('data', (chunk) => {
    data += chunk
  })

  req.on('end', () => {
    responseResult(querystring.parse(data))
  })

  function responseResult(data) {
    switch (urlObj.pathname) {
      case '/api/login':
        res.end(JSON.stringify({
          message: data
        }))
        break
      default:
        res.end('404.')
        break
    }
  }
})

app.listen(8080, () => {
  console.log('localhost:8080')
})

前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        fetch("http://localhost:4000/api/login")
        .then(res=>res.json())
        .then(res=>{
            console.log(res)
        })
    </script>
</body>
</html>

get模块

在很多情况下,node是作为中间层,从其他服务器调用数据。那么请求主要分为 post 和 get

那么我们首先需要 request 这个包

代码示例:

很简单就不多说

const http = require('http');  // 引入 HTTP 模块
// 调用 request 方法发送 get 请求
const req = http.request({
  hostname: 'localhost',
  path: '/php/test/api.php',
  port: 80,
  method: 'GET'
}, res => {
  // 如果状态码不是 200 就在控制台输出状态码
  if (res.statusCode !== 200) console.log(res.statusCode);

  res.on('data', d => {
    // 把接收到的数据转为字符串在控制台输出
    console.log(d.toString());
  });
});

// 如果出错就在控制台输出错误信息
req.on('error', err => {
  console.log(err);
});

req.end();  // 结束

上面给 localhost/php/test/api.php 发送 GET 请求,然后在控制台输出接收到的数据。

下面是 HTTP 模块的简单说明:

http.request(options[, callback])

发送 HTTP 请求,options 参数是选项 需要传入一个对象,callback 参数是一个回调函数。

下面简单写一下上面用到的选项:

  • hostname :请求的 IP 或域名
  • path :请求的页面路径
  • port :服务器的端口
  • method :请求方法,默认为 GET

请求成功会触发 response 事件,也包括 response 上的 data 事件,也就是上面的 res

data 事件接收到的数据是一个 Buffer ,可以使用 toString() 方法转为字符串。

请求结束需要调用 end() 方法,end() 可以传入一个回调函数。

如果需要发送的键值对较多的话,可以使用对象来定义键值对,然后用 querystring 模块转为 URL 键值对。

例如:

const querystring = require('querystring');  // 引入 querystring 模块
// 要发送的键值对
let data = {
  id: 2,
  title: 'Tutorial',
  content: 'This is JavaScript tutorial'
};
// 转为 URL 键值对
data = querystring.stringify(data);
console.log(data);

post模块

没有https.post只有request。

代码示例:

很简单,和 get 模块差不多

const http = require('http');  // 引入 HTTP 模块

const data = 'id=1&name=Louis';  // 要发送的内容
// 使用 request 方法发送 POST 请求
const req = http.request({
  hostname: 'localhost',
  path: '/php/test/api.php',
  port: 80,
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': data.length
  }
}, res => {
  // 如果状态码不是 200 就输出状态码
  if (res.statusCode !== 200) console.log(res.statusCode);

  res.on('data', d => {
    // 把接收到的内容转为字符串在控制台输出
    console.log(d.toString())
  });
});

// 设置要发送的内容
req.write(data);
// 如果出错就在控制台输出错误信息
req.on('error', err => {
  console.log(err);
});

req.end();  // 结束

路由

为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码。

需要的数据从http请求中来,包含在request对象中,为了解析这些数据,需要额外的Node.JS模块,它们分别是url和querystring模块。

代码示例

通过不同路由加载不同的页面

我们需要注意:readFileSyncreadFile 的区别,一个是同步阻塞方式读取文件,另一个是异步读取。

例如一下代码是readFile实现:

var fs = require('fs');
function readFileAsSync(){
    new Promise((resolve, reject)=>{
        fs.readFile(filename, "utf8", function(err, data) {
                if (err) throw err;
                resolve(data);
        });
    });
}

async function callRead(){
    let data = await readFileAsSync();
    console.log(data);
}

callRead();

路由示例代码:

var fs = require("fs")
var path = require("path")

function render(res, path) {
    res.writeHead(200, { "Content-Type": "text/html;charset=utf8" })
    res.write(fs.readFileSync(path, "utf8"))
    res.end()
}


const route = {
    "/login": (req, res) => {
        render(res, "./static/login.html")
    },

    "/home": (req, res) => {
        render(res, "./static/home.html")
    },
    "/404": (req, res) => {
        res.writeHead(404, { "Content-Type": "text/html;charset=utf8" })
        res.write(fs.readFileSync("./static/404.html", "utf8"))
    }
}

读取静态资源:

function readStaticFile(req, res) {
    const myURL = new URL(req.url, 'http://127.0.0.1:3000')
    var filePathname = path.join(__dirname, "/static", myURL.pathname);

    if (fs.existsSync(filePathname)) {
        // console.log(1111)
        res.writeHead(200, { "Content-Type": `${mime.getType(myURL.pathname.split(".")[1])};charset=utf8` })
        res.write(fs.readFileSync(filePathname, "utf8"))
        res.end()
        return true
    } else {
        return false
    }
}

这里需要用到几个包:path 和 mime,mime主要是处理文件类型用 split(".") 获取文件扩展名,当然也可以使用 path.extname 获取拓展名


文章作者: 小小星仔
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小星仔 !
评论
  目录