nodejs
课程介绍
nodejs6天
-
第1-3天:es6语法、nodejs常用api,npm包管理工具,模块化,hackerNews增删改查的效果1.0
-
第4天:express,hackerNews2.0
-
第5天:数据库mongodb (非关系型数据 ), HackerNews3.0
-
第6天:nodejs写接口,前后端分离, HackerNews4.0
浏览器的工作原理
浏览器的组成
- 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
- 浏览器引擎- 用来查询及操作渲染引擎的接口
- 渲染引擎(浏览器内核)- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
- 网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
- UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
- JS解释器- 用来解释执行JS代码
- 数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了Storage技术,这是一种轻量级完整的客户端存储技术
主流的渲染引擎
浏览器的渲染引擎也叫排版引擎,或者是浏览器内核
主流的 渲染引擎 有
- Chrome浏览器: Blink引擎(WebKit的一个分支)。
- Safari浏览器: WebKit引擎,windows版本2008年3月18日推出正式版,但苹果已于2012年7月25日停止开发Windows版的Safari。
- FireFox浏览器: Gecko引擎。
- Opera浏览器: Blink引擎(早期版使用Presto引擎)。
- Internet Explorer浏览器: Trident引擎 。
- Microsoft Edge浏览器: EdgeHTML引擎(Trident的一个分支)。
渲染引擎工作原理
渲染引擎解析的基本流程:
1. 解析HTML构建Dom树,DOM 是W3C组织推荐的处理可扩展置标语言的标准编程接口。
2. 构建渲染树,渲染树并不等同于Dom树,因为像`head`标签 或 `display: none`这样的元素就没有必要放到渲染树中了,但是它们在Dom树中。
3. 对渲染树进行布局,定位坐标和大小、确定是否换行、确定position、overflow、z-index等等,这个过程叫`layout` 或 `reflow`。
4. 绘制渲染树,调用操作系统底层API(UI Backend)进行绘图操作。
webkit内核工作流程
gecko内核工作流程
结论:浏览器能够解析HTML文件,并且显示到页面中。所以我们写的文件能够使用浏览器打开并且能够看到效果。
性能优化:重绘与回流(重排)
回流(reflow)与重绘(repaint),在性能优化的时候,经常会提起,因为涉及到浏览器底层的渲染,所以掌握的童鞋并不多,但是面试经常被提及。
回流(reflow)
:当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。
重绘(repaint)
:当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。
- 每个页面至少需要一次回流+重绘。
- 回流必将引起重绘
回流什么时候发生?
1、添加或者删除可见的DOM元素;
2、元素位置改变;
3、元素尺寸改变——边距、填充、边框、宽度和高度
4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
5、页面渲染初始化;
6、浏览器窗口尺寸改变——resize事件发生时;
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
- 如何性能优化? 尽量减少重绘与回流的次数
-
直接使用className修改样式,少用style设置样式
-
让要操作的元素进行”离线处理”,处理完后一起更新
- 使用DocumentFragment进行缓存操作,引发一次回流和重绘
- 使用display:none技术,只引发两次回流和重绘;
-
将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素为动画的 HTML 元素,例如动画,那么修改他们的 CSS 是会大大减小 reflow 。
-
完成功能是前提,在完成功能的情况下想着优化代码
-
var pNode,fragment = document.createDocumentFragment();
//动态创建20个p标签,先用DocumentFragment 对象来缓存
for(var i=0; i<20; i++){
pNode = document.createElement('p');
pNode.innerHTML = i;
fragment.appendChild(pNode);
}
document.body.appendChild(fragment);
浏览器与服务器的交互流程
- 浏览器通过地址栏发出请求
- 通过DNS服务器解析,得到域名对应的ip地址
- 根据ip地址,访问服务器具体的某个文件
- 服务器响应这个具体的文件
- 浏览器获取响应,进行显示
问题1:我们写的html页面,存放在哪儿?
问题2:我们写的html页面,在哪里解析的(显示)?
前端开发:以浏览器为宿主环境,结合 HTML、CSS、Javascript等技术,而进行的一系列开发,通常称之为前端开发。
服务器端开发:HTTP服务器可以结合某一编程语言处理业务逻辑,由此进行的开发,通常称之为服务端开发。
nodejs:服务端的javascript开发,用于开发服务端程序的
动态网页: 网页的数据是动态生成的 数据在‘动’
nodejs基本概念
为什么要学习nodejs
为什么要学习服务端的开发?
- 通过学习Node.js开发理解服务器开发、Web请求和响应过程、 了解服务器端如何与客户端配合
- 作为前端开发工程师(FE)需要具备一定的服务端开发能力
- 了解什么是服务端渲染?
- 了解服务端如何编写接口?
- 全栈工程师的必经之路
服务器端开发语言有很多,为什么要选择nodejs
- 降低编程语言切换的成本(nodejs实质上用的还是javascript)
- NodeJS是前端项目的基础设施,前端项目中用到的大量工具,都是基于nodejs实现的
- nodejs在处理高并发上有得天独厚的优势
- 对于前端工程师,面试时对于nodejs有一定的要求
参考资料:
node.js 是什么?
node.js,也叫作node,或者nodejs,指的都是一个东西。
Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。
- Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
1. nodejs不是一门新的编程语言,nodejs是在服务端运行javascript的运行环境
2. 运行环境:写得程序想要运行必须要有对应的运行环境
php代码必须要有apache服务器
在web端,浏览器就是javascript的运行环境
在node端,nodejs就是javascript的运行环境
2. javascript并不只是能运行在浏览器端,浏览器端能够运行js是因为浏览器有js解析器,因此只需要有js解析器,任何软件都可以运行js。
3. nodejs可以在服务端运行js,因为nodejs是基于chrome v8的js引擎。
- Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
- Node.js 的包管理器 npm,是全球最大的开源库生态系统。
nodejs的本质:不是一门新的编程语言,nodejs是javascript运行在服务端的运行环境,编程语言还是javascript
nodejs与浏览器的区别
相同点:nodejs与浏览器都是浏览器的运行环境,都能够解析js程序。对于ECMAScript语法来说,在nodejs和浏览器中都能运行。
不同点:nodejs无法使用DOM和BOM的操作,浏览器无法执行nodejs中的文件操作等功能
思考:
- 在浏览器端,可以使用javascript操作数据库么?
- 在nodejs端,可以使用BOM和DOM的方法么?
- 我们学习nodejs,学习什么内容?
nodejs可以干什么?
- 开发服务端程序
- 开发命令行工具(CLI),比如npm,webpack,gulp,less,sass等
- 开发桌面应用程序(借助 node-webkit、electron 等框架实现)
安装nodejs
nodejs版本
下载地址
官网术语解释
- LTS 版本:Long-term Support 版本,长期支持版,即稳定版。
- Current 版本:Latest Features 版本,最新版本,新特性会在该版本中最先加入。
查看node版本
node -v
环境变量
当要求系统运行一个程序 而没有告诉它程序所在的完整路径时,
- 首先在当前目录中查找和该字符串匹配的可执行文件 .exe
- 进入用户 path 环境变量查找
- 进入系统 path 环境变量查找
配置环境变量:
找到环境变量:计算机 --右键--> 属性 --> 高级系统设置 --> 高级 --> 环境变量
直接将可执行程序所在目录配置到PATH中
//如果是window7操作系统,注意要用分号;隔开,不要覆盖原来的内容
;D:\Program Files\feiq;
运行nodejs程序
方式一:REPL介绍
- REPL 全称: Read-Eval-Print-Loop(交互式解释器)
- R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
- E 执行 - 执行输入的数据结构
- P 打印 - 输出结果
- L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。
- 在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
- 直接在控制台输入
node
命令进入 REPL 环境
- 直接在控制台输入
- 按两次 Control + C 退出REPL界面 或者 输入
.exit
退出 REPL 界面- 按住 control 键不要放开, 然后按两下 c 键
方式二:使用node执行js文件
- 创建js文件
helloworld.js
- 写nodejs的内容:
console.log('hello nodejs')
- 打开命令窗口
cmd
- shift加右键打开命令窗口,执行
node 文件名.js
即可 - 给vscode安装
terminal
插件,直接在vscode中执行
- shift加右键打开命令窗口,执行
- 执行命令:
node helloworld.js
注意:在nodejs中是无法使用DOM和BOM的内容的,因此document, window
等内容是无法使用的。
npm上传包
npm login
npm publish
npm制作CLI
- 先创建包
- 编写代码实现相应的功能(创建文件。。。。)
- 在代码第一行加上 #! node
- 在package.json中添加 bin属性
- bin属性就是制定一个命令的名称,以及命令对应的要执行的文件
- 最终进行安装之后,命令行中就可以使用这个命令了!
- 上传包
- 全局安装包
- 使用 命令
ES6
变量相关的
let
const
- 都不可以重复声明
- 都有块级作用域
const必须在声明的时候赋值
const声明的变量不能被修改值
对象相关的
- 对象的简写
var name = "王春生";
var obj = {
name: name
}
// 简写如下
var obj = {
name
}
var obj = {
sayHello: function(){
}
}
// 简写如下
var obj = {
sayHello(){
}
}
解构赋值
// 1. 对象的解构
var obj = {
name: "",
age: 18
}
// 可以自动将对对象中指定的属性的值赋值给一个新的变量
var {对象的属性名: 要声明的变量名, 对象的属性名: 要声明的变量名, }
var {name, age} = obj;
// 2. 数组的解构
var arr = [1, 2, 3]
var [num1, num2, num3] = arr;
var [,,num3] = arr;
剩余元素
- 只能有一个
- 必须是最后一个
var arr = [1, 2, 3]
var [num1, ...num2] = arr;
// num2 获取到的就是剩余的元素 2,3组成的数组
剩余参数
// 在函数的参数列表中可以使用剩余参数来获取传入的实参
// 1. 只能有一个
// 2. 必须是最后一个
// 3. 剩余参数是一个真数组!
function test(a, ...b){
}
test(1, 2, 3, 4, 5)
箭头函数
var func = (参数) => {
// 函数体
}
// 1. 如果只有一个参数则()省略
// 2. 如果函数体只有一句代码则{}省略
// 3. 如果函数体只有一句代码并且是个返回语句则{}和return都可省略
箭头函数中没有this
在箭头函数中访问this的时候会沿着作用域链向上进行查找
使用场景: var that = this; 的情况都可以使用箭头函数来解决
箭头函数中没有arguments
如果要在箭头函数中使用不定个数的参数,则直接用剩余参数
对象扩展运算符...
var p = {
name: "",
age: 18
}
var s = {...p, 自己的属性}
数组的拆解...
function sum(a, b, c){
return a+b+c
}
var arr = [1, 2, 3];
sum(...arr)
等于
sum.apply(null,arr)
class
class 类名{
constructor(){
// 给实例添加属性
}
// 给实例添加方法,方法会被添加到原型中(实例成员)
sayHello(){}
// 给构造函数添加静态方法 直接通过类名访问(静态成员)
static 方法名(){}
}
实现继承
class Person(){
constructor(name, age){
this.name = name;
this.age = age;
}
sayHello(){}
static sayHi(){}
}
class Student extends Person{
constructor(name, age){
super(name, age)
// 写自己的属性
this.属性名 = ""
}
}
var stu = new Studnet("", 18)
模板字符串
let name='张三'
let str1= `<div><p>${name}</p></div>`
console.log(str1)
//<div><p>张三</p></div>
Promise
Promise是用来解决回调地狱问题。
使用别人提供的promise的api
只要别人说支持promise,那我们就知道可以使用.then方法了
自己封装Promise的api
function timeout(time){
return new Promise(function(resolve, reject){
setTimeout(function(){
// 只需要改变promise的状态即可
resovle(123);
}, time)
})
}
timeout(1000).then(function(){
})
.then方法可以传递两个参数,一个是成功的回调一个是失败的回调
.then方法可以连写,但是连写的时候一定要在上一个.then方法中返回一个新的promise对象
Promise.all 在所有的promise都成功之后,可以进行某些操作
Promise.race 在第一个的promise成功之后,可以进行某些操作
ES6语法:
let与const
ES6中提供了两个声明变量的关键字:const和let
let的使用
ES6 新增了let
命令,用来声明变量。它的用法类似于var
。
- let声明的变量只有在当前作用域有效
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
- 不存在变量提升
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
- 不允许重复声明
let a = 10;
let a = 1;//报错 Identifier 'a' has already been declared
const的使用
const
声明一个只读的常量。常量:值不可以改变的量
- const声明的量不可以改变
const PI = 3.1415;
PI = 3; //报错
- const声明的变量必须赋值
const num;
- 如果const声明了一个对象,仅仅保证地址不变
const obj = {name:'zs'};
obj.age = 18;//正确
obj = {};//报错
- 其他用法和let一样
1. 只能在当前代码块中使用
2. 不会提升
3. 不能重复
let与const的使用场景
1. 如果声明的变量不需要改变,那么使用const
2. 如果声明的变量需要改变,那么用let
3. 学了const和let之后,尽量别用var
ES5-数组的新方法
forEach
forEach()
方法对数组的每个元素执行一次提供的函数。功能等同于for
循环.
应用场景:为一些相同的元素,绑定事件处理器!
需求:遍历数组["张飞","关羽","赵云","马超"]
var arr = ["张飞","关羽","赵云","马超"];
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
arr.forEach(function(element, index, array){
console.log(element, index, array);
});
map
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
需求:遍历数组,求每一项的平方存在于一个数组中
var arr = [1,2,3,4,5]; // 1 4 9 16 25
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:一个新数组,每个元素都是回调函数的结果。
var newArray = arr.map(function(element, index, array){
return element * element;
});
console.log(newArray);//[1,4,9,16,25]
filter
filter
用于返回满足条件的元素,
返回一个新数组,如果在回调函数中返回true,那么就留下来,如果返回false,就扔掉
需求:遍历数组,将数组中工资超过5000的值删除[1000, 5000, 20000, 3000, 10000, 800, 1500]
var arr = [1000, 5000, 20000, 3000, 10000, 800, 1500];
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:一个新数组,存储了所有返回true的元素
var newArray = arr.filter(function(element, index, array){
if(element > 5000) {
return false;
}else {
return true;
}
});
console.log(newArray);//[1000, 5000, 3000, 800, 1500]
some
some
用于遍历数组,如果有至少一个满足条件,就返回true,否则返回false。
需求:遍历数组,判断数组是否包含奇数,[2,4,6,8,10,9]
var arr = [2,4,6,8,10,21];
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:布尔类型的值,只要有一个回调函数返回true,就返回true
var flag = arr.some(function(element, index, array){
console.log(element, index, array);
if(element %2 == 1){
return true;
}else {
return false;
}
});
console.log(flag);//true
every
every
用于遍历数组,只有当所有的元素返回true,才返回true,否则返回false。
需求:遍历数组,判断数组是否都是偶数,[2,4,6,8,10,9]
var arr = [2,4,6,8,10,21];
//第一个参数:element,数组的每一项元素
//第二个参数:index,数组的下标
//第三个参数:array,正在遍历的数组
//返回值:布尔类型的值,只有当所有的元素返回true,才返回true,否则返回false。
var flag = arr.every(function(element, index, array){
console.log(element, index, array);
if(element %2 == 0){
return true;
}else {
return false;
}
});
console.log(flag);//false
find
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
。
// 获取第一个大于10的数
var array1 = [5, 12, 8, 130, 44];
var found = array1.find(function(element) {
return element > 10;
});
console.log(found);
findexIndex
findIndex()
方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
// 获取第一个大于10的下标
var array1 = [5, 12, 8, 130, 44];
function findFirstLargeNumber(element) {
return element > 13;
}
console.log(array1.findIndex(findFirstLargeNumber));
ES6语法-字符串新方法
1. startsWith() 是否以谁开头 布尔类型
2. endsWith() 是否以谁结尾
3. includes() 是否包含
let str = "abcdef"
console.log(str.startsWith('ab')); // true
console.log(str.startsWith('bb')); // false
console.log(str.endsWith('ef')); // true
console.log(str.endsWith('ff')); // false
console.log(str.includes('ab')); // true
console.log(str.includes('bc')); // true
ES6语法- 对象简写
1- 在对象中,如果属性名和变量名相同的话,可以省略一个
2- 在对象中,方法也可以简写, 不写function
let name = 'zs'
let age = 18
// 在对象中,如果属性名和变量名相同的话,可以省略一个
// 在对象中,方法也可以简写
let obj = {
name: name,
age: age
}
let obj = {
gender: '男',
name,
age
}
// 在对象中的方法可以简写
// 不写function
let obj = {
// say: function () {}
say(n1) {
console.log(n1)
},
goodBye() {
console.log('byebye')
}
}
//调用
obj.say(1)
obj.goodBye()
ES6语法-函数默认参数(备胎)
es6的函数允许直接传递默认参数
当调用函数未传递参数时会使用默认参数
function add(n1 = 0, n2 = 0) {
// n1 = n1 || 0 // 如果n1没值,n1就是0
// n2 = n2 || 0 // 如果n2没值,n2就是0
console.log(n1 + n2);
}
add(); // 0
add(1); // 1
add(1, 2); // 2
ES6语法-箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头:
基本使用
var fn = function(x, y) {
console.log(x + y);
}
相当于
//语法: (参数列表) => {函数体}
var fn = (x, y) => {
console.log(x + y);
}
参数详解
- 如果没有参数列表,使用()表示参数列表
var sum = () => {
console.log('哈哈')
};
// 等同于:
var sum = function() {
console.log('哈哈')
};
- 如果只有一个参数,可以省略()
// 等同于:
var sum = function(n1) {
console.log('哈哈')
};
var sum = n1 => {
console.log('哈哈')
};
- 如果有多个参数,需要使用()把参数列表括起来
var sum = function(n1, n2) {
console.log('哈哈')
};
var sum = (n1, n2) => {
console.log('哈哈')
};
返回值详解
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来
var sum = function(n1) {
console.log('哈哈')
};
var sum = n1 => {
console.log('哈哈')
};
- 如果函数体只有一行一句,那么可以省略{}
let func=()=>{
console.log('hi nihao')
}
let func=()=> console.log('hi nihao')
- 如果函数体只有一行一句,并且需要返回这个值,那么可以省略{}和return
var fn = function(n1, n2) {
return n1 + n2;
}
var fn = (n1, n2) => n1 + n2;
案例1
- 有一个数组
[1,3,5,7,9,2,4,6,8,10]
,请对数组进行排序 - 有一个数组
['a','ccc','bb','dddd']
,请按照字符串长度对数组进行排序 - 有一个数组,
[57,88,99,100,33,77]
,请保留60分以上的成绩,返回一个新的数组
箭头函数的注意点
- 箭头函数内部没有this,因此箭头函数内部的this指向了外部的this(很爽)
- 箭头函数不能作为构造函数,因为箭头函数没有this
【定义一个对象,定时器打招呼】
苦口婆心一下:箭头函数刚开始用,肯定会有点不习惯,但是任何东西都有一个习惯的过程,慢慢接受就好了,多用,多练
nodeJs
global模块-全局变量
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。
在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global
,所有全局变量(除了 global 本身以外)都是 global 对象的属性。
在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。
常用的global属性
console: 用于打印日志
setTimeout/clearTimeout: 设置清除延时器
setInterval/clearInterval: 设置清除定时器
__dirname: 当前文件的路径,不包括文件名
__filename: 获取当前文件的路径,包括文件名
//与模块化相关的,模块化的时候会用到
require
exports
module
fs模块
fs模块是nodejs中最常用的一个模块,因此掌握fs模块非常的有必要,fs模块的方法非常多,用到了哪个查哪个即可。
在nodejs中,提供了fs模块,这是node的核心模块
注意:
- 除了global模块中的内容可以直接使用,其他模块都是需要加载的。
- fs模块不是全局的,不能直接使用。因此需要导入才能使用。
var fs = require("fs");
读取文件
语法:fs.readFile(path[, options], callback)
方式一:不传编码参数
//参数1: 文件的名字
//参数2: 读取文件的回调函数
//参数1:错误对象,如果读取失败,err会包含错误信息,如果读取成功,err是null
//参数2:读取成功后的数据(是一个Buffer对象)
fs.readFile("data.txt", function(err, data){
console.log(err);
console.log(data);
});
方式二:传编码参数
//参数1: 文件的路径
//参数2: 编码,如果设置了,返回一个字符串,如果没有设置,会返回一个buffer对象
//参数3: 回调函数
fs.readFile("data.txt", "utf8",function(err, data){
console.log(err);
console.log(data);
});
关于Buffer对象
1. Buffer对象是Nodejs用于处理二进制数据的。
2. 其实任意的数据在计算机底层都是二进制数据,因为计算机只认识二进制。
3. 所以读取任意的文件,返回的结果都是二进制数据,即Buffer对象
4. Buffer对象可以调用toString()方法转换成字符串。
写文件
语法:fs.writeFile(file, data[, options], callback)
//参数1:写入的文件名(如果文件不存在,会自动创建)
//参数2:写入的文件内容(注意:写入的内容会覆盖以前的内容)
//参数3:写文件后的回调函数
fs.writeFile("2.txt", "hello world, 我是一个中国人", function(err){
if(err) {
return console.log("写入文件失败", err);
}
console.log("写入文件成功");
});
注意:
- 写文件的时候,会把原来的内容给覆盖掉
追加文件
语法:fs.appendFile(path, data[, options], callback)
//参数1:追加的文件名(如果文件不存在,会自动创建)
//参数2:追加的文件内容(注意:写入的内容会覆盖以前的内容)
//参数3:追加文件后的回调函数
fs.appendFile("2.txt", "我是追加的内容", function(err){
if(err) {
return console.log("追加文件内容失败");
}
console.log("追加文件内容成功");
})
思考:如果没有appendFile,通过readFile与writeFile应该怎么实现?
文件同步与异步的说明
fs中所有的文件操作,都提供了异步和同步两种方式
异步方式:不会阻塞代码的执行
//异步方式
var fs = require("fs");
console.log(111);
fs.readFile("2.txt", "utf8", function(err, data){
if(err) {
return console.log("读取文件失败", err);
}
console.log(data);
});
console.log("222");
同步方式:会阻塞代码的执行
//同步方式
console.log(111);
var result = fs.readFileSync("2.txt", "utf-8");
console.log(result);
console.log(222);
总结:同步操作使用虽然简单,但是会影响性能,因此尽量使用异步方法,尤其是在工作过程中。
其他api(了解)
方法有很多,但是用起来都非常的简单,学会查文档
文档:http://nodejs.cn/api/fs.html
方法名 | 描述 |
---|---|
fs.readFile(path, callback) |
读取文件内容(异步) |
fs.readFileSync(path) |
读取文件内容(同步) |
fs.writeFile(path, data, callback) |
写入文件内容(异步) |
fs.writeFileSync(path, data) |
写入文件内容(同步) |
fs.appendFile(path, data, callback) |
追加文件内容(异步) |
fs.appendFileSync(path, data) |
追加文件内容(同步) |
fs.rename(oldPath, newPath, callback) |
重命名文件(异步) |
fs.renameSync(oldPath, newPath) |
重命名文件(同步) |
fs.unlink(path, callback) |
删除文件(异步) |
fs.unlinkSync(path) |
删除文件(同步) |
fs.mkdir(path, mode, callback) |
创建文件夹(异步) |
fs.mkdirSync(path, mode) |
创建文件夹(同步) |
fs.rmdir(path, callback) |
删除文件夹(异步) |
fs.rmdirSync(path) |
删除文件夹(同步) |
fs.readdir(path, option, callback) |
读取文件夹内容(异步) |
fs.readdirSync(path, option) |
读取文件夹内容(同步) |
fs.stat(path, callback) |
查看文件状态(异步) |
fs.statSync(path) |
查看文件状态(同步) |
path模块
路径操作的问题
在读写文件的时候,文件路径可以写相对路径或者绝对路径
//data.txt是相对路径,读取当前目录下的data.txt, 相对路径相对的是指向node命令的路径
//如果node命令不是在当前目录下执行就会报错, 在当前执行node命令的目录下查找data.txt,找不到
fs.readFile("data.txt", "utf8", function(err, data) {
if(err) {
console.log("读取文件失败", err);
}
console.log(data);
});
相对路径:相对于执行node命令的路径
绝对路径:__dirname
: 当前文件的目录,__filename
: 当前文件的目录,包含文件名
path模块的常用方法
关于路径,在linux系统中,路径分隔符使用的是
/
,但是在windows系统中,路径使用的\
在我们拼写路径的时候会带来很多的麻烦,经常会出现windows下写的代码,在linux操作系统下执行不了,path模块就是为了解决这个问题而存在的。
常用方法:
path.join();//拼接路径
//windows系统下
> path.join("abc","def","gg", "index.html")
"abc\def\gg\a.html"
//linux系统下
> path.join("abc","def","gg", "index.html")
'abc/def/gg/index.html'
path.basename(path[, ext]) 返回文件的最后一部分
path.dirname(path) 返回路径的目录名
path.extname(path) 获取路径的扩展名
var path = require("path");
var temp = "abc\\def\\gg\\a.html";
console.log(path.basename(temp));//a.html
console.log(path.dirname(temp));//abc\def\gg
console.log(path.extname(temp));//.html
【优化读写文件的代码】
let str = 'D:\飞秋文件\feiq\Recv Files\前端-web资料\15-班级随机点名.html';
path模块其他api(了解)
方法名 | 描述 |
---|---|
path.basename(path[, ext]) |
返回文件的最后一部分 |
path.dirname(path) |
返回路径的目录名 |
path.extname(path) |
获取路径的扩展名 |
path.isAbsolute(path) |
判断目录是否是绝对路径 |
path.join([...paths]) |
将所有的path片段拼接成一个规范的路径 |
path.normalize(path) |
规范化路径 |
path.parse(path) |
将一个路径解析成一个path对象 |
path.format(pathObj) |
讲一个path对象解析成一个规范的路径 |
http模块
创建服务器基本步骤
//1. 导入http模块,http模块是node的核心模块,作用是用来创建http服务器的。
var http = require("http");
//2. 创建服务器
var server = http.createServer();
//3. 服务器处理请求
server.on("request", function() {
console.log("我接收到请求了");
});
//4. 启动服务器,监听某个端口
server.listen(9999, function(){
console.log("服务器启动成功了, 请访问: http://localhost:9999");
});
详细说明
- 给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
- request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
- 服务器监听的端口范围为:1-65535之间,推荐使用3000以上的端口,因为3000以下的端口一般留给系统使用
request对象详解
文档地址:http://nodejs.cn/api/http.html#http_message_headers
常见属性:
method: 请求的方式
url: 请求的地址
headers: 所有的请求头信息
rawHeaders: 所有的请求头信息(数组的方式)
注意:在发送请求的时候,可能会出现两次请求的情况,这是因为谷歌浏览器会自动增加一个favicon.ico
的请求。
小结:request对象中,常用的就是method和url两个参数
response对象详解
文档地址:http://nodejs.cn/api/http.html#http_class_http_serverresponse
常见的属性和方法:
res.write(data): 给浏览器发送请求体,可以调用多次,从而提供连续的请求体
res.end(); 通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成。
res.end(data); 结束请求,并且响应一段内容,相当于res.write(data) + res.end()
res.statusCode: 响应的的状态码 200 404 500
res.statusMessage: 响应的状态信息, OK Not Found ,会根据statusCode自动设置。
res.setHeader(name, value); 设置响应头信息, 比如content-type
res.writeHead(statusCode, statusMessage, options); 设置响应头,同时可以设置状态码和状态信息。
注意:必须先设置响应头,才能设置响应体。
根据不同请求输出不同响应数据
- request.url
req.url
:获取请求路径- 例如:请求
http://127.0.0.1:3000/index
获取到的是:/index
- 例如:请求
http://127.0.0.1:3000/
获取到的是:/
- 例如:请求
http://127.0.0.1:3000
获取到的是:/
- 例如:请求
服务器响应文件
- 注意:浏览器中输入的URL地址,仅仅是一个标识,不与服务器中的目录一致。也就是说:返回什么内容是由服务端的逻辑决定
server.on('request', function(req, res) {
var url = req.url
if(url === '/') {
fs.readFile('./index.html', function(err, data) {
if(err) {
return res.end('您访问的资源不存在~')
}
res.end(data)
})
}
})
模拟Apache服务器
- 根据
req.url
读取不同的页面内容,返回给浏览器
MIME类型
- MIME(Multipurpose Internet Mail Extensions)多用途Internet邮件扩展类型 是一种表示文档性质和格式的标准化方式
- 浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档;因此服务器将正确的MIME类型附加到响应对象的头部是非常重要的
- MIME 类型
mime模块
- 作用:获取文件的MIME类型
- 安装:
npm i mime
var mime = require('mime')
// 获取路径对应的MIME类型
mime.getType('txt') // ⇨ 'text/plain'
// 根据MIME获取到文件后缀名
mime.getExtension('text/plain') // ⇨ 'txt'
npm - Node包管理工具
npm的基本概念
1. npm 是node的包管理工具,
2. 它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。
3. 来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。
npm 由三个独立的部分组成:
网站
注册表(registry)
命令行工具 (CLI)
- 作用:通过
npm
来快速安装开发中使用的包 - npm不需要安装,只要安装了node,就自带了
npm
npm基本使用
初始化包
npm init; //这个命令用于初始化一个包,创建一个package.json文件,我们的项目都应该先执行npm init
npm init -y; //快速的初始化一个包, 不能是一个中文名
安装包
npm install 包名; //安装指定的包名的最新版本到项目中
npm install 包名@版本号; //安装指定包的指定版本
npm i 包名; //简写
卸载包
npm uninstall 包名; //卸载已经安装的包
本地安装和全局安装
有两种方式用来安装 npm 包:本地安装和全局安装。选用哪种方式来安装,取决于你如何使用这个包。
- 全局安装:如果你想将其作为一个命令行工具,那么你应该将其安装到全局。这种安装方式后可以让你在任何目录下使用这个命令。比如less命令,webpack命令。
- 本地安装:如果你自己的模块依赖于某个包,并通过 Node.js 的
require
加载,那么你应该选择本地安装,这种方式也是npm install
命令的默认行为。
// 全局安装,会把npm包安装到C:\Users\cc\AppData\Roaming\npm目录下,作为命令行工具使用
npm install -g 包名;
//本地安装,会把npm包安装到当前项目的node_modules文件中,作为项目的依赖
npm install 包名;
package.json文件
package.json
作用
package.json文件就是用来描述一个包的信息的!
只要一个文件夹中有一个合格的pacakge.json文件,那么这个文件夹就可以被称为是一个包!
合格的定义: 必须包含两个属性 name version
package.json文件中的属性
name: 包名 不能有中文,不能有空格,不能有大写字母,不能有特殊字符!
version: 版本信息
description: 描述信息
author: 作者
keywords: 关键词 方便在npm网站上进行搜索
license: 开源协议 自己指定
scripts: 放的就是一些shell命令,这些命令可以通过 npm run 命令别名
进行执行
可以省略run执行的命令别名: start stop restart test config
例如npm start
dependencies:
devDependencies:
版本号说明
一般的版本号都会包含3个数字,中间用.隔开
格式: 主版本号.次版本号.修订版本号
主版本号: 当代码功能更新,更新之后,不兼容之前的版本了,那么需要更新主版本号!
此版本号: 当代码功能更新,更新之后,依然兼容之前的版本,只是新增了某些功能,那么需要更新次版本号
修订版本号: 当代码更新,更新的只是修复了某些BUG,或者优化了某些功能,那么这个时候只需要更新 修订版本号就可以了
jquery: 1.x 2.x 3.x
1.10.1 1.12.4
1.12.1 1.12.4
dependencies 和 devDependencies 的说明
这两个属性中保存的都是当前包所有的依赖信息。
dependencies: 运行时依赖项,在将代码上传到服务器时,这个包仍被需要
devDependencies: 开发时依赖项,这个依赖项只需要在开发时时候,上传到服务器的时候不需要!
问题: 为什么要将依赖项信息存储起来呢???
主要目的是为了代码共享的时候,比较方便!
在进行代码分享的时候,不需要分享node_modules,只需要分享自己的代码和pacakge.json即可,另外的程序员拿到代码之后,自己根据pacakge.json下载所有的依赖项即可!
npm install
这条命令会自动根据package.json中保存的包信息进行下载 (devDependencies+dependencies)
只下载运行时依赖项可以使用命令npm install --production
如何将依赖项的信息保存到dependencies 和 devDependencies中
在早期版本的npm中,依赖项信息不会自动保存!
-
将依赖项的信息保存到dependencies
npm install 包 --save
npm install 包 -S
-
将依赖项的信息保存到devDependencies
npm install 包 --save-dev
npm install 包 -D
package.json文件,包(项目)描述文件,用来管理组织一个包(项目),它是一个纯JSON格式的。
- 作用:描述当前项目(包)的信息,描述当前包(项目)的依赖项
- 如何生成:
npm init
或者npm init -y
- 作用
- 作为一个标准的包,必须要有
package.json
文件进行描述 - 一个项目的node_modules目录通常都会很大,不用拷贝node_modules目录,可以通过package.json文件配合
npm install
直接安装项目所有的依赖项
- 作为一个标准的包,必须要有
- 描述内容
{
"name": "03-npm", //描述了包的名字,不能有中文
"version": "1.0.0", //描述了包的的版本信息, x.y.z 如果只是修复bug,需要更新Z位。如果是新增了功能,但是向下兼容,需要更新Y位。如果有大变动,向下不兼容,需要更新X位。
"description": "", //包的描述信息
"main": "index.js", //入口文件(模块化加载规则的时候详细的讲)
"scripts": { //配置一些脚本,在vue的时候会用到,现在体会不到这些命令可以通过npm run 命令名 进行执行
可以省略run执行命令别名:start stop restart test config
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], //关键字(方便搜索)
"author": "", //作者的信息
"license": "ISC", //许可证,开源协议
"dependencies": { //重要,项目的依赖, 方便代码的共享 通过 npm install可以直接安装所有的依赖项
"bootstrap": "^3.3.7",
"jquery": "^3.3.1"
}
}
注意:一个合法的package.json,必须要有name和version两个属性
如果安装失败, 可以通过以下命令清除npm缓存:
npm cache clean -f // -f强制清除
//主要看网络
npm下载加速
nrm
- nrm:npm registry manager(npm仓库地址管理工具)
- 安装:
npm i -g nrm
# 带*表示当前正在使用的地址
# 查看仓库地址列表
nrm ls
# 查看当前正在使用的服务器
nrm current
# 测速命令
nrm test
# 切换仓库地址
nrm use taobao
nodemon 自动重启
- 作用:监视到js文件修改后,自动重启node程序
- 安装:
npm i -g nodemon
- 使用:
nodemon app.js
运行node程序
hacknews案例
- Hacker News 示例
- 路由(route):就是一套映射规则,根据url地址分配到对应的处理程序
功能划分
- 1 新闻列表页 - /index get
- 2 新闻详情页 - /details get
- 3 新闻添加页 - /submit get
- 4 新闻添加请求 - /add post
路由
路由(route):就是一套映射规则,根据url地址分配到对应的处理程序
前端渲染和后端渲染的区别
渲染工作究竟是由谁负责:
-
如果是在浏览器端进行渲染工作,则称为前端渲染
- 首先向服务器请求页面
- 将页面的基本结构渲染出来
- 通过ajax请求向后台请求数据
- 利用模板引擎将获取到数据渲染到页面中
-
如果是在服务器端进行的渲染工作,则称为后端渲染
- 首先向服务器请求页面
- 服务器会先去根目录读取页面文件
- 将数据读取到
- 将数据渲染到读取到的页面中
- 将渲染好数据的页面返回给浏览器
- 浏览器拿到的就是已经渲染好数据的页面!
art-template 模板引擎
- 文档
- 安装
npm install art-template
准备模板(1,以文件作为模板,2,以字符串变量作为模板)
- 核心方法
// 基于模板路径渲染模板以文件作为模板(准备一个index.html文件 文件中写入div{{name}}</div>)
//参数1:文件的路径
//参数2:数据
//返回值:返回渲染后的内容
// template(filename, data)
let html = template(path.join(__dirname, "pages", "index.html"), {name:"大吉大利,今晚吃鸡"});
//以字符串变量作为模板
var str="<div>{{name}}</div>"
//1,根据模板字符串创建一个渲染函数
var render=template.compile(str);
//2,使用渲染函数进行模板渲染
var result=render({name:"大吉大利,今晚吃鸡"});
console.log(result);
注意点:文件的路径必须是绝对路径
hacknews 数据处理
- 采用
后端渲染
将模板页面和数据渲染为用户能够看懂的正常页面返回
url模块
- 说明:用于 URL 处理与解析
- 注意:通过url拿到的查询参数都是字符串格式
// 导入url模块
var url = require('url')
// 解析 URL 字符串并返回一个 URL 对象
// 第一个参数:表示要解析的URL字符串
// 第二个参数:是否将query属性(查询参数)解析为一个对象,如果为:true,则query是一个对象
var ret = url.parse('http://localhost:3000/details?id=1&name=jack', true)
console.log(ret.query) // { id: '1', name: 'jack' }
querystring模块
- 用于解析与格式化 URL 查询字符串
- 注意:只在专门处理查询字符串时使用
// foo=bar&abc=xyz&abc=123
var querystring = require('querystring')
// 将查询参数转化为对象
// 第一个参数: 要解析的 URL 查询字符串
querystring.parse('foo=bar&abc=xyz') // { foo: 'bar', abc: 'xyz' }
服务端重定向
- HTTP 状态码说明
- 301 和 302
- 说明:服务端可以通过HTTP状态码让浏览器中的页面重定向
res.writeHead(302, {
'Location': '/'
})
res.end()
POST请求参数的处理
- 说明:POST请求可以发送大量数据,没有大小限制
// 给req注册一个data事件, 只要浏览器给服务器发送post请求,data事件就会触发
// post请求发送的数据量可以很大, 这个data事件会触发多次,一块一块的传输
// 要把所有的chunk都拼接起来
// data事件:用来接受客户端发送过来的POST请求数据
var result = "";
req.on('data', function (chunk) {
result += chunk;
})
// end事件:当POST数据接收完毕时,触发
req.on('end', function () {
cosnole.log(result);
})
模块化
模块化规范:
- AMD: requirejs
- CMD: seajs 浏览器端的模块
- commonJS: nodejs 服务端的模块
基本概念
在nodejs中,应用由模块组成,nodejs中采用commonJS模块规范。
- 一个js文件就是一个模块
- 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。
01-模块化的概念
用于演示每一个模块都有自己单独的作用域
node中模块分类
- 1 核心模块
- 由 node 本身提供,不需要单独安装(npm),可直接引入使用
- 2 第三方模块
- 由社区或个人提供,需要通过npm安装后使用
- 3 自定义模块
- 由我们自己创建,比如:tool.js 、 user.js
核心模块
- fs:文件操作模块
- http:网络操作模块
- path:路径操作模块
- url: 解析地址的模块
- querystring: 解析参数字符串的模块
- 基本使用:1 先引入 2 再使用
// 引入模块
var fs = require('fs');
第三方模块
- 第三方模块是由 社区或个人 提供的
- 比如:mime模块/art-template/jquery...
- 基本使用:1 先通过npm下载 2 再引入 3 最后使用
用户自定义模块
- 由开发人员创建的模块(JS文件)
- 基本使用:1 创建模块 2 引入模块
- 注意:自定义模块的路径必须以
./
获取../
开头
// 加载模块
require('./a') // 推荐使用,省略.js后缀!
require('./a.js')
模块的导入与导出
模块导入
- 通过
require("fs")
来加载模块 - 如果是第三方模块,需要先使用npm进行下载
- 如果是自定义模块,需要加上相对路径
./
或者../
,可以省略.js
后缀,如果文件名是index.js
那么index.js也可以省略。(如果没有./之类路径则认为是包名) - 模块可以被多次导入,但是只会在第一次加载
模块导出
- 在模块的内部,
module
变量代表的就是当前模块,它的exports
属性就是对外的接口,加载某个模块,加载的就是module.exports
属性,这个属性指向一个空的对象。
//module.exports指向的是一个对象,我们给对象增加属性即可。
//module.exports.num = 123;
//module.exports.age = 18;
//通过module.exports也可以导出一个值,但是多次导出会覆盖
module.exports = '123';
module.exports = "abc";
module.exports与exports
exports
是module.exports
的引用- 注意:给
module.exports
赋值会切断与exports
之间的联系- 1 直接添加属性两者皆可
- 2 赋值操作时,只能使用
module.exports
console.log( module.exports === exports ) // ==> true
// 等价操作
module.exports.num = 123
exports.num = 123
// 赋值操作:不要使用 exports = {}
module.exports = {}
require函数
require函数的作用就是用来导入模块的!!
node.js中的模块分类
- 核心模块(内置模块) fs path url http querystring
- 第三方包模块 mime art-template moment
- 目录模块
- 文件模块
在引入核心模块和第三方包模块的时候,直接给require函数传递包名称即可:
当require函数接收到一个包名称的时候
- 会先去找核心模块中是否有对应的模块,如果找到了就直接使用
- 如果没有找到,就去node_modules中查找对应的模块,如果找到了就直接使用
- 如果没有找到,就去上一级目录中的node_modules中找,如果找到了就直接使用
- 如果没找到继续往上级目录中的node_modules,找到根目录如果还没找到就报错了 can not find module xxx
在引入模块的时候,如果传递给require函数的是一个路径作为参数,那么引入的就是目录模块或者文件模块 ./ ../
- 按照路径查找对应的文件, 如果找到了直接引入
- 如果没有找到,找文件名对应的 .js .json .node
- 如果没有找到,就找路径对应的文件夹
- 如果找到了对应的文件夹
- 判断当前文件夹中是否有package.json文件
- 如果没有,就直接引用index.js
- 如果有package.json文件,则找main属性
- 如果main属性不存在 则引用index.js json node
- 如果main属性存在 则引用main属性指定的文件
- 判断当前文件夹中是否有package.json文件
- 如果找到了对应的文件夹
- 如果没找到,就直接报错了
package.json中的main属性
告诉require函数在引入当前包的时候,具体引用哪个文件!
{
"main": "文件路径"
}
require函数
require函数的作用就是用来导入模块的!!
node.js中的模块分类
- 核心模块(内置模块) fs path url http querystring
- 第三方包模块 mime art-template moment
- 目录模块
- 文件模块
在引入核心模块和第三方包模块的时候,直接给require函数传递包名称即可:
当require函数接收到一个包名称的时候
- 会先去找核心模块中是否有对应的模块,如果找到了就直接使用
- 如果没有找到,就去node_modules中查找对应的模块,如果找到了就直接使用
- 如果没有找到,就去上一级目录中的node_modules中找,如果找到了就直接使用
- 如果没找到继续往上级目录中的node_modules,找到根目录如果还没找到就报错了 can not find module xxx
在引入模块的时候,如果传递给require函数的是一个路径作为参数,那么引入的就是目录模块或者文件模块 ./ ../
- 按照路径查找对应的文件, 如果找到了直接引入
- 如果没有找到,找文件名对应的 .js .json .node
- 如果没有找到,就找路径对应的文件夹
- 如果找到了对应的文件夹
- 判断当前文件夹中是否有package.json文件
- 如果没有,就直接引用index.js
- 如果有package.json文件,则找main属性
- 如果main属性不存在 则引用index.js json node
- 如果main属性存在 则引用main属性指定的文件
- 判断当前文件夹中是否有package.json文件
- 如果找到了对应的文件夹
- 如果没找到,就直接报错了
package.json中的main属性
告诉require函数在引入当前包的时候,具体引用哪个文件!
{
"main": "文件路径"
}
CommonJS 规范参考文档
模块化改造hackerNews
Express
Express 框架
- 基于 Node.js 平台,快速、开放、极简的 web 开发框架
- express 官网
- express 中文网
起步
- 安装:
npm i express
// 导入 express
var express = require('express')
// 创建 express实例,也就是创建 express服务器
var app = express()
// 路由
app.get('/', function (req, res) {
res.send('Hello World!')
})
// 启动服务器
app.listen(3000, function () {
console.log('服务器已启动')
})
API说明
-
express()
:创建一个Express应用,并返回,即:app -
app.get()
:注册一个GET类型的路由- 注意:只要注册了路由,所有的请求都会被处理(未配置的请求路径,响应404)
-
res.send()
:发送数据给客户端,并自动设置Content-Type- 参数可以是:字符串、数组、对象、Buffer
- 注意:只能使用一次
-
req
和res
:与http模块中的作用相同,是扩展后的请求和响应对象
注册路由的三种方式
- 1
app.METHOD
:比如:app.get / app.post / app.delete / app.put - 2
app.all(path, callback)
- 注意:path 与 请求地址必须完全相同
- 注意:可以处理任意的请求类型
- 3
app.use(path, callback)
更重要的作用是处理中间件- 注意:只要是以path开头的请求地址,都可以被use处理
- 注意:可以处理任意的请求类型
- 注意:path参数可省略,默认值为:
/
模拟Apache服务器
req.path
:请求路径- 示例:URL为'example.com/users?sort=desc',path表示:
/users
- 示例:URL为'example.com/users?sort=desc',path表示:
res.sendFile()
处理静态资源
- 静态资源:图片、CSS、JavaScript 文件 等
- 如何处理?使用 express.static() 方法来托管静态资源
- 注意:
express.static()
可以调用多次 - 思考:
app.use('/web', express.static('web'))
的含义?- 访问:
http://localhost:3000/web/anoceanofsky.jpg
- 访问:
// 托管web目录下的静态资源
app.use(express.static('web'))
// 相当于:app.use('/', express.static('web'))
// 访问上述静态资源
// http://localhost:3000/anoceanofsky.jpg
// 当请求达到服务器后,express就会到web目录下,查找anoceanofsky.jpg
// 并读取该文件,返回给浏览器
request常用属性和方法
// 获取请求路基中的参数,是一个对象 ---> Get请求参数
req.query
// 获取POST请求参数,需要配置`body-parser`模块, POST请求参数
req.body
1. req.body 是用来获取post请求参数的!但是不能直接使用
2. req.query 是用来获取get请求参数的
3. req.originalUrl 是用来获取原始的url地址 类似于之前的req.url
4. req.params 是用来获取路由参数的
5. req.path 是用来获取请求的路径的 类似于 urlObj.pathname
- 获取
POST
请求参数
// 导入body-parser模块
var bodyParser = require('body-parser');
// 将POST请求参数转化为对象,存储到req.body中
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
// 此时,就可以获取到POST请求参数了
console.log(req.body)
response常用属性和方法
// send() 发送数据给客户端,并自动设置Content-Type
res.send()
// 发送文件给浏览器,并根据文件后缀名自动设置Content-Type
// 注意:文件路径必须是绝对路径
res.sendFile(path.join(__dirname, 'index.html'))
// 设置HTTP响应码
res.status(200);
// 设置响应头
res.set('Content-Type', 'text/plain')
res.set({
'Content-Type': 'text/plain',
'cute': 'fangfang'
})
// 重定向
res.redirect('/index')
express
express中使用模版引擎
安装
npm install express
npm install art-template
npm install express-art-template
给express绑定一个模版引擎
//给express设置模版引擎
//参数1: 模版引擎的后缀名, 以后的模版文件都应该是 html结尾
//参数2: 使用什么模版引擎
app.engine("html", require('express-art-template'))
通过res.render()
渲染模版引擎
//参数1; 模版文件的路径,相对路径,回去views目录下查找
//参数2: 数据
res.render(path.join(__dirname, "index.html"), {name:"zs"})
关于模版引擎的配置(了解)
//模版文件默认去aa目录下查找 默认值: views
app.set("views", path.join(__dirname,'路径'));
//设置模板引擎的默认后缀(使用render方法时就不要加html结尾了)
app.set("view engine", "html");
中间件
中间件(Middleware) 是一个函数,它可以访问请求对象req
, 响应对象res
, 可以通过next
参数将中间件传递给下一个中间件
中间件作用: 给req 和 res 拓展功能
中间件的功能包括:
- 执行任何代码。
- 修改请求和响应对象。
- 终结请求-响应循环。
- 调用堆栈中的下一个中间件。
定义一个中间件
//添加一个中间件
//中间件是啥:中间件就是一个函数,中间件可以访问到req和res,可以通过next发送给下一个中间件中间件函数必须做出选择1,要么结束整个相应2,要么调起下一个中间件 express默认有一个404中间件
app.use(function(req,res, next) {
req.aa = "大春哥";
res.bb = "很帅";
//中间件可以通过next传递给下一个中间件
next();
});
body-parser中间件的使用
- 获取get请求的参数:
req.query
- 获取post请求的参数
req.body
,但是需要借助body-parser
中间件
安装:
npm install body-parser
使用
//1. 导入body-parser中间件
var bodyParser = require('body-parser');
//使用body-parser中间件
//extended:true 表示使用qs库来解析查询字符串 extended:false表示使用querystring库来解析字符串
app.use(bodyParser.urlencoded({extended:false}));
//3. 通过req.body获取参数
app.post("/", function(req, res) {
console.log(req.body);
res.send("哈哈");
});
注意:中间件是有执行顺序的
实现自己的body-parser中间件
//bodyParser.json(); 返回一个函数(中间件),会处理json数据
app.use(function(req, res, next){
//给req增加一个body属性
var result = "";
req.on('data', function(chunk){
result += chunk;
});
req.on("end", function(){
req.body = querystring.parse(result);
next();
});
});
外置路由的使用
目的:将路由封装到一个独立的路由模块中,有利于代码的封装和模块化
/*
router.js 文件代码如下:
*/
// 1 加载 express 模块
var express = require('express')
// 2 调用 express.Router() 方法,得到一个路由容器实例
var router = express.Router()
// 3 为 router 添加不同的路由
router.get('/', function (req, res) {
res.send('hello express')
})
router.get('/add', function (req, res) {
})
// 4. 将 router 路由容器导出
module.exports = router
/*
在 app.js 文件中:
*/
var express = require('express')
// 1 加载上面自定义的路由模块
var router = require('./router')
var app = express()
// 2. 将自定义路由模块 router 通过 app.use() 方法挂载到 app 实例上
// 这样 app 实例程序就拥有了 router 的路由
app.use( router )
app.listen(3000, function () {
console.log('running...')
})
使用express重构HackerNews案例
mongodb
数据库的分类
关系型数据库;mysql oracle sql server db2
非关系数据库: mongodb redis memcache
MongoDB简介
- mongodb 官网
- mongodb 中文
- mongodb教程
- MongoDB 是一个非关系型数据库,属于文档型数据库(NoSQL -> Not Only SQL)
- 对 JavaScript 兼容较好,和 Node.js 结合最好
- MEAN:
M:mongodb E:express A:angular(vue react) n:nodejs
mongodb安装
windows版本
参考地址:
http://www.runoob.com/mongodb/mongodb-window-install.html
- 1 根据操作系统选择合适的安装程序(32位或64位)
- 2 直接安装程序
- 3 配置环境变量,通过命令:
mongod --version
看是否安装成功 - 4 注意:MongoDB最新版的安装包已经不再支持32位的windows操作系统了
解决mongodb安装时出错 “mongodb 无法启动此程序,因为计算机中丢失 api-ms-win-crt-runtime-l1-1-0.dll”,安装 vc_redist.x64.exe
https://helpx.adobe.com/tw/creative-cloud/kb/error_on_launch.html
通过指定其他电脑的ip地址,就可以使用其他电脑中的MongoDB数据库了:
var url = 'mongodb://localhost:27017'
mac版本
参考地址:http://www.runoob.com/mongodb/mongodb-osx-install.html
mongodb启动与连接
- 1 通过命令:
mongod
启动 mongodb数据库服务(不要关闭) - 2 重新开启一个cmd,输入命令:
mongo
就可以连接到mongod服务了
1 在 C中创建 data文件夹, 在data文件夹中创建 db文件夹
2 在终端中输入命令: mongod ,就可以启动mongodb数据库服务了
3. 创建 c:\data\db 的目的: 告诉 mongodb 数据存储到这个文件夹中, 但是, 只能在C盘中启动 mongod
4. 如果需要在 D盘 启动, 需要在 D中也创建 data/db 目录
# 终端1 -- 启动服务
mongod
# 终端2 -- 连接到服务
# 此时,就可以在 终端 对数据库进行操作了
mongo
# 或者 指定ip
mongo mongodb://localhost:27017
数据库存储路径的说明
- windows32位系统 安装MongoDB
- 注意:mongod 会在执行命令的磁盘根目录中查找
data/db
目录作为数据库文件存储路径 - 可以通过命令:
mongod --dbpath 路径
修改默认配置
# 64位:
mongod --dbpath C:\data\db
# 32位:
mongod --journal --storageEngine=mmapv1
mongod --dbpath c:\data\db --journal --storageEngine=mmapv1
MongoDB终端操作
数据库操作
以下的命令都是在mongo终端下完成
- 查看数据库
# 注意:自己创建的数据库,如果数据为空,不会显示
show dbs
- 切换(创建)数据库
# 如果数据库存在,切换到该数据库, 如果数据库不存在,创建新的数据库
# 如果数据库里面没有数据的话,数据库不显示
use 数据库名
use test
use users
- 查看当前使用的数据库
# 查看当前正在使用的数据库
db
- 删除当前数据库
db.dropDatabase()
mongodb术语
- 数据库:一个项目会使用一个数据库,比如letao, baixiu等
- 集合:类似于表,一个数据库可以有很多集合,比如user存放学生信息,teacher存放老师的信息
- 文档:一条数据就是一个文档,一个集合可以存放多条数据,即一个集合可以存放多个老师的信息,每个老师的信息称为一条文档
- 字段:一条数据中的属性,就是字段,比如name,age等
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
插入数据(文档)
- 语法:
db.集合名称.insert({})
- 说明: 在 mongodb 中不需要提前创建"表", 直接通过 db.表名称.inseret() 就可以往表中添加数据了
// 插入一条
db.users.insert({name: 'jack', age: 18, gender: 'male'})
// 插入多条
db.users.insertMany([{name: 'tom', age: 19}, {name: 'jerry', age: 20}])
查询数据
- 语法:
db.集合名称.find()
// 查询所有数据
db.users.find()
// 美化输出格式:
db.集合名称.find().pretty()
// 指定条件查询:
db.集合名称.find({name: 'jack'})
修改数据
- 语法:
db.集合名称.updateOne(条件, 更新后的数据)
// 修改name属性为jack的数据,将age改为20
// 第一个参数: 表示要修改哪个数据, 会根据指定的name属性, 去查找值为jack的数据
// 第二个参数: 表示修改修改后的数据, 会修改 age 属性的值为 20
db.users.updateOne({name: 'jack'}, {$set: {age: 20}})
// 修改age大于19岁的文档,将name设置为 中年人
db.users.updateMany({age: {$gt: 19}},{$set: {name: '中年人'}})
删除数据
- 语法:
db.集合名称.deleteOne(条件)
// 删除 age 为18的数据:
// 参数: 删除条件
db.users.deleteOne({age: 18})
// 删除所有name为jack的文档
db.users.deleteMany({ name: 'jack' })
MondoDB 查询语句
操作 | 格式 | 示例 | SQL语句 |
---|---|---|---|
等于 | {} | db.col.find({ name :'jack'}) | where name = 'jack' |
小于 | $lt | db.col.find({ age: {$lt:18}}) | where age < 18 |
小于或等于 | $lte | db.col.find({ age: {$lte:18}}) | where age <= 18 |
大于 | $gt | db.col.find({ age: {$gt:18}}) | where age > 18 |
大于或等于 | $gte | db.col.find({ age: {$gte:18}}) | where age >= 18 |
不等于 | $ne | db.col.find({ age: {$ne:18}}) | where age != 18 |
$in | |||
$nin |
less than equal great not
在 node 中操作 MongoDB
- 安装:
npm i mongodb
// 导入 mongodb,并获取到客户端对象
var MongoClient = require('mongodb').MongoClient
// 连接数据库服务地址
var url = 'mongodb://localhost:27017'
// 连接数据库
MongoClient.connect(url, function (err, client) {
if (err) {
return console.log('链接数据库失败', err)
}
console.log('数据库链接成功');
// 获取集合对象
var db = client.db('nodedb')
// 关闭数据库链接
client.close()
})
数据增删改查
- 添加数据:
var db = client.db('nodedb')
// 添加
db.collection('users')
// 添加一条数据
.insert({name: 'rose', age: 19}, function (err, data) {
console.log(data);
})
// 添加多条数据
.insertMany([{ name: 'tom', age: 20 }, { name: 'jerry', age: 21 }], function (err, data) {
console.log(data);
})
- 查询数据:
var db = client.db('nodedb')
// 查询
db.collection('users').find().toArray(function (err, data) {
console.log(data)
})
- 删除数据:
var db = client.db('nodedb')
db.collection('users')
// 删除一条数据:
.deleteOne({name: 'rose'}, function (err, result) {
console.log(result);
})
// 删除多条数据:
.deleteMany({age: {$lt: 20}}, function (err, result) {
console.log(result);
})
- 修改数据:
var db = client.db('nodedb')
db.collection('users')
.update({ name: 'tom' }, { $set: { age: 22 } }, function (err, result) {
console.log(result);
})
使用MongoDB实现 hacker-news
在 node 中操作 MongoDB
- 安装:
npm i mongodb
- 启动mongodb的服务
// 导入 mongodb,并获取到客户端对象
var MongoClient = require('mongodb').MongoClient
// 连接数据库服务地址
var url = 'mongodb://localhost:27017'
// 连接数据库
MongoClient.connect(url, function (err, client) {
if (err) {
return console.log('链接数据库失败', err)
}
console.log('数据库链接成功');
// 获取集合对象
var db = client.db('nodedb')
// 关闭数据库链接
client.close()
})
数据增删改查
- 查询数据
//查询
db
.collection("users")
.find({age: {$gt:20}})
.toArray(function(err, data){
if(err) {
return console.log("获取数据失败");
}
console.log(data);
});
db.collection("user").findOne({name:"小鲜肉"}, function(err, result){
if(err) {
console.log("查询单个数据失败了");
return;
}
console.log(result);
})
- 添加数据:
//添加单条
db
.collection("users")
.insertOne({name:"jim1", age: 12, gender:"女"}, function(err, info){
if(info.result.ok === 1){}
console.log("数据插入成功");
});
//添加多条数据
db
.collection("users")
.insertMany([ {"name":"hucc", "age":18, gender:"男"}, {"name":"hcc", "age":17, gender:"女"} ], function(err, info){
console.log(info.result);
});
- 删除数据:
//删除单条数据
db
.collection("users")
.deleteOne({age: {$gt:5}}, function(err, info){
if(info.result.ok === 1) {
console.log("数据删除成功");
}
})
//删除多条数据
db
.collection("users")
.deleteMany({age: {$gt:5}}, function(err, info){
if(info.result.ok === 1) {
console.log("数据删除成功");
}
})
- 修改数据:
//修改单条数据
db
.collection("users")
.updateOne({name: "tom"}, {$set: {gender:"女"}}, function(err, info){
if(info.result.ok === 1) {
console.log("数据修改成功了");
}
})
//修改多条数据
db
.collection("users")
.updateMany({ name: "tom" }, { $set: { gender: "不详" } }, function (err, info) {
if (info.result.ok === 1) {
console.log("数据修改成功了");
}
})