0%

019. Remove Nth Node From End Of List

难度: Medium

刷题内容

原题连接

内容描述

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

 给定一个链表: 1->2->3->4->5, 和 n = 2.

 当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

解题方案

思路 1
**- 时间复杂度: O(N)**- 空间复杂度: O(N)**

转化为数组,通过数组下标来确定删除的节点
代码:

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
26
27
28
29
30
31
32
33
34
35
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
if (!head || n === 0) {
return head;
}
const list = [];
let cur = head;
while (cur) {
list.push(cur);
cur = cur.next;
}
const index = list.length - n;

if (list.length === 1 && n === 1) {
return null;
}

if (index === 0) {
return list[1]
} else {
list[index-1].next = list[index+1];
return list[0];
}
};

思路2
**- 时间复杂度: O(N)**- 空间复杂度: O(1)**

使用快慢指针的方式,先让fast走n步,再让slow开始和fast一起走,当fast走完的时候,就是slow走到了正确的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var removeNthFromEnd = function(head, n) {
if (!head || n === 0) {
return head;
}
let dummy = new ListNode(-1);
dummy.next = head;
let fast = dummy;
let slow = dummy;
Array.from(({length:n+1})).forEach(() => {
fast = fast.next;
})
while(fast) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
};

Pinia 是什么?

  • Pinia 是一个用于 Vue 的状态管理库,类似 Vuex, 是 Vue 的另一种状态管理方案
  • Pinia 支持 Vue2 和 Vue3

本文只讲 Pinia 在 Vue3 中的使用, 在 Vue2 中使用略有差异,参考 官方文档

Pinia 优势

  • 符合直觉,易于学习
  • 极轻, 仅有 1 KB
  • 模块化设计,便于拆分状态

安装 Pinia

安装需要 @next 因为 Pinia 2 处于 beta 阶段, Pinia 2 是对应 Vue3 的版本

使用 npm

1
npm install pinia@next

使用 yarn

1
yarn add pinia@next

创建一个 pinia(根存储)并将其传递给应用程序:

1
2
import { createPinia } from 'pinia';
app.use(createPinia());

核心概念与基本使用

Store

Store 是一个保存状态和业务逻辑的实体,可以自由读取和写入,并通过导入后在 setup 中使用

创建一个 store

1
2
3
4
5
6
7
8
9
10
11
12
13
// store.js
import { defineStore } from "pinia";

// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const useStore = defineStore({
// id: 必须的,在所有 Store 中唯一
id: "myGlobalState",
// state: 返回对象的函数
state: ()=> ({
count: 1
}),
});

使用 Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// xxx.vue
<template>
<div>
{{store.count}}
</div>
</template>
<script>
// 导入 Store, 使用自己的路径
import { useStore } from "@/store/store.js";
export default {
setup() {
// 调用函数 获得Store
const store = useStore();
return {
store
}
}
}
</script>

Getters

Pinia 中的 Getters 作用与 Vuex 中的 Getters 相同,但使用略有差异
Pinia 中的 Getters 直接在 Store 上读取,形似 Store.xx,就和一般的属性读取一样

Getter基本使用

  • Getter 第一个参数是 state,是当前的状态,也可以使用 this.xx 获取状态
  • Getter 中也可以访问其他的 Getter, 或者是其他的 Store
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

例子:
// 修改 store.js
import { defineStore } from "pinia";

import { otherState } from "@/store/otherState.js";

export const useStore = defineStore({
id: "myGlobalState",
state: ()=> ({
count: 2
}),
getters: {
// 一个基本的 Getter: 计算 count 的平方
// 使用参数
countPow2(state) {
return state.count ** 2;
},
// 使用 this
/*
countPow2() {
return this.count ** 2;
},
*/
// 简单的 Getter 直接使用箭头函数
// countPow2: state=> state.count ** 2

// 获取其它 Getter, 直接通过 this
countPow2Getter() {
return this.countPow2;
}

// 使用其它 Store
otherStoreCount(state) {
// 这里是其他的 Store,调用获取 Store,就和在 setup 中一样
const otherStore = useOtherStore();
return otherStore.count;
},
}
});

// otherState.js
import { defineStore } from "pinia";

export const useStore = defineStore({
id: "otherState",
state: ()=> ({
count: 5
}),
});

actions

  • Pinia 没有 Mutations,统一在 actions 中操作 state,通过this.xx 访问相应状态

  • 虽然可以直接操作 Store,但还是推荐在 actions 中操作,保证状态不被意外改变

  • action 和普通的函数一样

  • action 同样可以像 Getter 一样访问其他的 Store,同上方式使用其它 Store,这里不在赘述,详细请移步 官方文档

    Actions

    action 基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    // store.js
    export const useStore({
    state: ()=> ({
    count: 2,
    // ...
    })
    // ...
    actinos: {
    countPlusOne() {
    this.count++;
    },
    countPlus(num) {
    this.count += num;
    }
    }
    })

    总结

  • Pinia 相比 Vuex 更加简单,而且 Pinia 可以自由扩展 官方文档 Plugins

  • Pinia 是符合直觉的状态管理方式,让使用者回到了模块导入导出的原始状态,使状态的来源更加清晰可见

  • Pinia 的使用感受类似于 Recoil,但没有那么多的概念和 API,主体非常精简,极易上手(Recoil 是 Facebook 官方出品的用于 React 状态管理库,使用 React Hooks 管理状态)

  • Pinia 2 目前还在 Beta 状态,不建议在生产环境中使用,不过相信稳定了以后会成为 Vue 生态中另一大状态管理方案

SEO

SEO是什么:搜索引擎优化(百度,360,谷歌)

站长工具,字数统计,百度指数

网站的构成

  1. 文件
    • 模板文件
    • 核心文件

  2. 数据库
    • 内容数据
    • 用户数据

    • 配置数据

基本方法

选取:
1.确定关键词,列出待选词
2.查看竞争对手的关键词
3.查询关键词热度(看下相关关键词)

工具
百度推广-关键词优化师
站长跟爱站关键词挖掘
百度指数

Url控制在3层(/一层)
导航要包含关键词标题 控制在80字符以内(3个核心关键词以内,左右) 添加本页目标关键词

一般不超过200个字符,需要包含标题关键词 对关键词补充 辅助标题 用来调整关键词密度字符限制建议控制在100字符内关键词标签, 100个字符以内 不要超过5个核心关键词 每个页面都需要

SEO需要学什么

  1. 建站
  2. SEO基础
  3. 游戏规则
  4. SEO策略
    • 秒收快排。。。。。。。。。。。。。百度霸屏
  5. 数据分析 ————————数据化
  6. 引路人

网站关键词排名的流程

  1. 放出蜘蛛(代码)
  2. 蜘蛛来爬取
  3. 页面收录(索引)
  4. 算法计算得分
  5. 排名

网站爬虫的管理规则

  1. 链接长度
  2. 网站识别回访
    • 打开速度快
    • 识别速度
      • 图片
      • 视频
  3. 有效收录
    • 有价值
  4. 爬虫越多越容易被收录
  5. 访问日志(访问次数越多站点越好)

网站tdk正确的写法

​ t:网站标题title

​ d:描述

​ k:关键词

高级SEO的核心计算数据

  1. 展现量 ———————————点击率(点击次数/展现量)
  2. 点击量 ———————————点击率
  3. 访问量————————跳出率
  4. 停留时间——————–跳出率

网站描述

  1. 规格
    • 字数
      • 60-80
    • 用户需求
      1. 用户的痛点
      2. 行业优势
      3. 包含关键词
  2. 举例
    • 空压机、空气压缩机、高压空压机、无油空压机
    • xxx公司13年行业研发,专注于打造高质量空压机品牌,主营:空气压缩机、高压空压机、无油空压机等设备研发,安全,耐用保修

网站高阶栏目打造

  1. 栏目的作用
    • 引导用户点击
  2. 正确的栏目规划
    • 数量
      • 一般6-9个不要超过9个
    • 用户需求
    • 栏目的顺序
      • 越重要的越靠前

利用锚文本提升网站的收录

  1. 锚文本是什么
    • 可以跳转的文字
  2. 锚文本的作用
    • 增加网站的点击率
    • 增加用户停留时间
    • 增加蜘蛛的爬行(存留)
      • 每一个链接都是蜘蛛爬行的入口
  3. 高质量锚文本的书写 要求
    • 文字于链接内容 匹配

    • 文字最好位要排名关键词

    • 锚文本的设置密度不能超过1%

      • 1000
      • 4
    • 不能多个链接指向一篇文章

      • 回链
    • 站在用户需求(百度检测)

      • 锚文本

      举例:seo是什么

      • 外链
      • 内链

网站文章的书写技巧

  1. 原创文章能否增加排名几率
    • 鼓励原创
    • 伪原创
  2. 文章写作技巧
    • 图文结合要1-3张
    • 字数最好超过800字
    • 不能乱写文章,可能会关系到有效收录

高级SEO外链算法

  1. 外链是什么
    • 别人的网站上放置自己的链接
    • 站外链
  2. 外链的作用
    • 吸引百度蜘蛛和用户
    • 文章引用法则(内容为王、外链为皇)
    • 百度算法(绿萝算法 (专门打击外链) )

网站SEO外链发布技巧

  1. 发布的形式
    • 图片
    • 锚文本
    • 文字
  2. 发布的技巧
    • 选择高质量的外链平台
    • 对链接有文字描述(引导点击)
    • 内容与实际相符合
    • 一段文字不要多平台发布

闪电算法:秒排的数据要求

  1. 闪电算法是什么:百度针对网站打开速度提出的一项优化规则 在2-3秒之间不加分不减分,大于3秒就会减分,小于2秒加分
  2. 影响网站数据的想关文件,
    1.模板:css/js
    2.图片
    3.服务器

H标签:一个网页只能出现一个h1标签,其他h标签可以随便用

nofollow的作用:

baidu

应用范围:

​ 外部链接:别人到你的网页上挂链接,qq客服链接,广告

​ 不重要页面

网站图片alt代码优化

​ ALT的作用:为图片做说明

​ ALT的使用规则:每一张图片,关键词

`Vue3.0

官方网站地址: v3.vuejs.org

新增特性

  • 向下兼容,Vue3 支持大多数 Vue2 的特性。
  • 性能的提升,每个人都希望使用的框架更快,更轻。打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%
  • 新推出的Composition API ,在 Vue2 中遇到的问题就是复杂组件的代码变的非常麻烦,甚至不可维护。封装不好,重用不畅。
  • 更好TypeScript支持,Vue3 的源代码就是使用TypeScript进行开发的。

Vue-cli 搭建

Vue3开发环境

安装:

1
2
3
npm install -g @vue/cli			| 推介
# OR
yarn global add @vue/cli

检查版本命令为:

1
vue --version

vue-cli 命令行创建项目

命令行中输入vue create vue3-demo

1
2
3
4
? Please pick a preset: (Use arrow keys)            //请选择预选项
> Default ([Vue 2] babel, eslint) //使用Vue2默认模板进行创建
Default (Vue 3 Preview) ([Vue 3] babel, eslint) //使用Vue3默认模板进行创建
Manually select features //手动选择(自定义)的意思

TypeScript进行开发 Vue3 的代码 不能直接使用第二项默认模板

1
2
3
4
5
6
7
8
9
10
11
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Choose Vue version
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
( ) CSS Pre-processors //CSS预处理器
(*) Linter / Formatter //格式化工具
( ) Unit Testing //单元测试
( ) E2E Testing //E2E测试
1
2
3
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 2.x
3.x (Preview)
1
Use class-style component syntax?

是否需要使用class-style

是否使用TypeScriptBabel的形式编译 JSX

1
Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n)

回车后会让你选择增加lint的特性功能。

1
2
3
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Lint on save //保存的时候进行Lint
( ) Lint and fix on commit //需要帮你进行fix(修理),这项我们不进行选择

选择这些配置文件时单独存放,还是直接存放在package.json文件里

1
Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

需不需要把这些配置保存下来,下次好直接进行使用

1
Save this as a preset for future projects? (y/N)

图形化创建项目

使用 vue ui

1
2
Starting GUI.
Ready on http://localhost:80

http://localhost:80地址拷贝到浏览器地址栏

进行选项配置

项目目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|-node_modules       -- 所有的项目依赖包都放在这个目录下
|-public -- 公共文件夹
---|favicon.ico -- 网站的显示图标
---|index.html -- 入口的html文件
|-src -- 源文件目录,编写的代码基本都在这个目录下
---|assets -- 放置静态文件的目录,比如logo.pn就放在这里
---|components -- Vue的组件文件,自定义的组件都会放到这
---|App.vue -- 根组件,这个在Vue2中也有
---|main.ts -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-.eslintrc.js -- Eslint的配置文件,不用作过多介绍
|-.gitignore -- 用来配置那些文件不归git管理
|-package.json -- 命令配置和包管理文件
|-README.md -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json -- 关于TypoScript的配置文件
|-yarn.lock -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中

package.json

1
2
3
4
5
6
7
8
9
{
//----
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
//----
}
  • serve : 在开发时用于查看效果的命令,视频中演示看一下效果
  • build : 打包打码,一般用于生产环境中使用
  • lint : 检查代码中的编写规范

main.ts 文件

1
2
3
4
import { createApp } from "vue"; // 引入vue文件,并导出`createApp`
import App from "./App.vue"; // 引入自定义组件,你在页面上看的东西基本都在这个组件里

createApp(App).mount("#app"); // 把App挂载到#app节点上,在public目录下的index.html找节点

setup()和 ref()函数

实例

1
2
3
4
5
6
7
8
9
10
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>欢迎光临红浪漫洗浴中心</h2>
<div>请选择一位美女为你服务</div>
</div>
<div>
<button> </button>
</div>
</template>

setup中加入一个girls变量,为了能让模板中进行使用,还需要返回。

1
2
3
4
5
6
7
8
9
10
11
12
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const girls = ref(["大脚", "刘英", "晓红"]);
return {
girls
};
},
});
</script>

使用v-for语法

1
2
3
<button v-for="(item, index) in girls" v-bind:key="index">
{{ index }} : {{ item }}
</button>

点击按钮的时候,触发一个方法selectGirlFun,方法绑定一个选定值selectGirl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const girls = ref(["大脚", "刘英", "晓红"]);
const selectGirl = ref("");
const selectGirlFun = (index: number) => {
selectGirl.value = girls.value[index];
};
//因为在模板中这些变量和方法都需要条用,所以需要return出去。
return {
girls,
selectGirl,
selectGirlFun,
};
},
});
</script>

修改template代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>欢迎光临红浪漫洗浴中心</h2>
<div>请选择一位美女为你服务</div>
</div>
<div>
<button
v-for="(item, index) in girls"
v-bind:key="index"
@click="selectGirlFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{ selectGirl }}】为你服务</div>
</template>
  • setup 函数的用法,可以代替 Vue2 中的 date 和 methods 属性,直接把逻辑写在 setup 里就可以
  • ref 函数的使用,要在template中使用的变量,必须用ref包装一下。
  • return出去的数据和方法,在模板中才可以使用,这样可以精准的控制暴露的变量和方法。

reactive() 函数优化程序

setup中要改变和读取一个值的时候,还要加上value

可以优化

引入一个新的 APIreactive

return返回的时候只需要返回一个data,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script lang="ts">
import { ref, reactive } from "vue";
export default {
name: "App",
setup() {
const data = reactive({
girls: ["大脚", "刘英", "晓红"],
selectGirl: "",
selectGirlFun: (index: number) => {
data.selectGirl = data.girls[index];
},
});

return {
data,
};
},
};
</script>

修改template部分的代码

字面量前要加入data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>欢迎光临红浪漫洗浴中心</h2>
<div>请选择一位美女为你服务</div>
</div>
<div>
<button
v-for="(item, index) in data.girls"
v-bind:key="index"
@click="data.selectGirlFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{ data.selectGirl }}】为你服务</div>
</template>

data 增加类型注解

接口(interface)来作类型注解。

1
2
3
4
5
interface DataProps {
girls: string[];
selectGirl: string;
selectGirlFun: (index: number) => void;
}

显示的为 data 变量作一个类型注解.

1
cosnt data: DataProps = ...

toRefs()继续优化

每次输出变量前面都要加一个data,这是可以优化的。

...扩展,运算符结构后就变成了普通变量,不再具有响应式的能力

新函数toRefs()

1
import { reactive, toRefs } from "vue";

data进行包装

1
2
3
4
5
const refData = toRefs(data);

return {
...refData,
};

template去掉 data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>欢迎光临红浪漫洗浴中心</h2>
<div>请选择一位美女为你服务</div>
</div>
<div>
<button
v-for="(item, index) in girls"
v-bind:key="index"
@click="selectGirlFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{ selectGirl }}】为你服务</div>
</template>

示例代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>欢迎光临红浪漫洗浴中心</h2>
<div>请选择一位美女为你服务</div>
</div>
<div>
<button
v-for="(item,index) in girls"
v-bind:key="index"
@click="selectGirlFunction(index)"
>{{index}}:{{item}}</button>
</div>
<div>你选择了{{selectGirl === "" ? "小白" : selectGirl}}为你服务</div>
</template>

<script lang="ts">
//reactive()
import {reactive,toRefs} from 'vue';
interface DataProps{
girls: string[];
selectGirl: string;
selectGirlFunction: (index: number) => void;
}

export default {
name: 'App',
setup(){
const data: DataProps = reactive({
girls:["大脚", "刘英", "晓红"],
selectGirl:"",
selectGirlFunction:(index: number)=>{
data.selectGirl=data.girls[index];
}
})
const refData = toRefs(data);
return{
...refData
};
}


};
</script>

Vue3.生命周期和钩子函数

函数名 作用
setup() 开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
onBeforeMount() 组件挂载到节点上之前执行的函数。
onMounted() 组件挂载完成后执行的函数。
onBeforeUpdate() 组件更新之前执行的函数。
onUpdated() 组件更新完成之后执行的函数。
onBeforeUnmount() 组件卸载之前执行的函数。
onUnmounted() 组件卸载完成后执行的函数
onActivated() 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
onDeactivated() 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
onErrorCaptured() 当捕获一个来自子孙组件的异常时激活钩子函数

引入

1
2
3
4
5
6
7
8
import {
reactive,
toRefs,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
} from "vue";
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
26
27
28
29
30
31
32
33
34
35
36
37
38
<script lang="ts">

//....
const app = {
name: "App",
setup() {
console.log("1-开始创建组件-----setup()");
const data: DataProps = reactive({
girls: ["大脚", "刘英", "晓红"],
selectGirl: "",
selectGirlFun: (index: number) => {
data.selectGirl = data.girls[index];
},
});
onBeforeMount(() => {
console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
});

onMounted(() => {
console.log("3-组件挂载到页面之后执行-----onMounted()");
});
onBeforeUpdate(() => {
console.log("4-组件更新之前-----onBeforeUpdate()");
});

onUpdated(() => {
console.log("5-组件更新之后-----onUpdated()");
});

const refData = toRefs(data);

return {
...refData,
};
},
};
export default app;
</script>

setup(),setup 这个函数是在beforeCreatecreated之前运行的

Vue2.x Vue3.x 生命周期对比

1
2
3
4
5
6
7
8
9
10
11
12
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured

onRenderTracked()和 onRenderTriggered()

onRenderTracked 状态跟踪

onRenderTracked直译过来就是状态跟踪,它会跟踪页面上所有响应式变量和方法的状态

页面有update的情况,他就会跟踪,然后生成一个event对象

1
import { .... ,onRenderTracked,} from "vue";

setup()函数中进行使用了。

1
2
3
4
onRenderTracked((event) => {
console.log("状态跟踪组件----------->");
console.log(event);
});

onRenderTriggered就是狙击枪,只精确跟踪发生变化的值,进行针对性调试。

1
import { .... ,onRenderTriggered,} from "vue";
1
2
3
4
onRenderTriggered((event) => {
console.log("状态触发组件--------------->");
console.log(event);
});

event 对象属性的详细介绍:

1
2
3
4
- key 那边变量发生了变化
- newValue 更新后变量的值
- oldValue 更新前变量的值
- target 目前页面中的响应变量和函数

使用 CDN 加速你的网站

前端面试过程中有一个万能的面试题,当他们想不到还有什么面试题要问的时候,就会问这个: 如何优化你们的网站性能。

而其中 CDN 就是其中一条,而在我看来,它是最重要的一条: 配置CDN可以使网站性瞬时得到质的提升,即使静态资源打包体积过大,并且缓存策略很差。

那什么是 CDN 呢?

CDN,全称 Content Delivery Network,内容发布网络。将网站的内容发布到最接近用户的边缘网络,使用户可以以更低的网络时延接收到网站内容,避免网络拥塞状况,提高用户访问网站的响应速度。

image

CDN 原理简述

简单来说,CDN 可以理解为 负载均衡服务器 + 缓存代理服务器,虽然实际原理以及实现算法会复杂很多。

image

如上图所示,当我们提及 CDN 时,特别是运维人员或者开发负责人时配置 CDN 时,我们可以把 CDN 划分为三个主体:

  • 用户,即你我他,每一个正在使用该网站的人
  • CDN,可理解为一个缓存节点,在当前节点可以快速响应给用户内容
  • 源站,内容的原始站点,CDN 上缓存未击中后,会回源到源站

那用户请求网站内容的具体步骤是什么?根据上图所示:

  1. 用户请求网站内容时,通过 Local DNS 进行域名解析
  2. Local DNS 请求 CDN 专用的 DNS 权威服务器 (因此当选择某家 CDN 厂商时,有时需要去域名提供商手动调整域名的 DNS 权威服务器)
  3. CDN专用DNS服务器负责负载均衡,为用户选择一个合适的 CDN 节点,作为目标节点
  4. CDN专用DNS服务器向用户返回目标节点 IP 地址
  5. 用户向目标节点的 IP 发起请求
  6. 目标节点 IP 地址如果没有响应内容,则向源站发送请求
  7. 目标节点从源站获取到内容,缓存,并返回给用户

另外由于 CDN 上存在多个节点为你提供服务,并且隐藏了源站,因此你的网站安全性大大提高,更好地避免了由于一些恶意攻击而导致源站的服务器集群宕机。

如何查看网站是否上了 CDN

如果你的网站静态资源上了 CDN,你的网络内容会被 CDN 的负载均衡系统分发到最接近用户的边缘节点。负载策略的策略可能会涉及到节点的,节点的拥塞成都,节点的运营商是否与用户网络一致,

假设当你在北京访问你的网站时,DNS 会解析你的网络内容到北京的 IP 节点,你在上海访问你的网站时,DNS 会解析到上海的 IP。而且为了解决拥塞,还会对网站内容负载均衡到多个 IP 地址。

那么此时问题就很简单了,如果你想知道某一网站是否上了 CDN,那就直接对域名进行 DNS 解析。

以下对知乎的静态资源所在的域名 static.zhihu.com 为例,使用 dig 命令进行 DNS 解析:

1
2
3
4
5
6
7
8
9
10
$ dig static.zhihu.com +short
static.zhihu.com.w.alikunlun.com.
183.201.230.244
183.201.230.248
183.201.230.240
183.201.230.242
183.201.230.245
183.201.230.241
183.201.230.246
183.201.230.243

从以上命令可以看到知乎采用了 CDN,并且根据 CNAME 记录可知采用了阿里的 CDN 服务: 对域名 CNAME 到 *.w.alikunlun.com

为你的网站配置 CDN

由于国内大部分网站采用阿里的CDN,这里以CDN为例讲解如何配置 CDN。详细配置可以参考

如何在网站上更好地利用 CDN

如果你需要更好地利用 CDN 提高网站性能,你不需要了解 CDN 的原理,但你更需要更好地了解 HTTP Cache。了解了 http 缓存的运作后,才能更好地充分利用缓存来降低网站的时延。

以下是 CDN 的缓存配置策略:

image

我们从图中可以获知:

  1. 源站和CDN(缓存服务器)均可以配置缓存策略
  2. 如果CDN没有配置缓存策略,则以源站缓存策略为主

而就我在多个项目的实践经验而言,很少为 CDN 专门配置缓存策略,因此如果想要更好地利用 CDN,则需要

为你的源站配置一个不错的缓存策略

更好的缓存策略

image

image

此时我们可以对资源进行分层次缓存的打包方案,这是一个建议方案:

  1. webpack-runtime: 应用中的 webpack 的版本比较稳定,分离出来,保证长久的永久缓存
  2. react/react-dom: react 的版本更新频次也较低
  3. vendor: 常用的第三方模块打包在一起,如 lodashclassnames 基本上每个页面都会引用到,但是它们的更新频率会更高一些。另外对低频次使用的第三方模块不要打进来
  4. pageA: A 页面,当 A 页面的组件发生变更后,它的缓存将会失效
  5. pageB: B 页面
  6. echarts: 不常用且过大的第三方模块单独打包
  7. mathjax: 不常用且过大的第三方模块单独打包
  8. jspdf: 不常用且过大的第三方模块单独打包

随着 http2 的发展,特别是多路复用,初始页面的静态资源不受资源数量的影响。因此为了更好的缓存效果以及按需加载,也有很多方案建议把所有的第三方模块进行单模块打包。

Long Term Cache

使用 webpack 等打包器进行打包时,每个资源都可生成一个带有 hash 的路径。如

  • build/main.071b73.js
  • build/main.94474e.css
  • build/logo.18bac8.png

此处对添加 hash 的资源设置永久缓存,可大幅度提高该网站的缓存能力,从而大幅度提高网站的二次加载性能。

通过在服务器端/网关端对资源设置以下 Response Header,进行强缓存一年时间,称为永久缓存,即 Long Term Cache

1
Cache-Control: public,max-age=31536000,immutable

而当源文件内容发生变更时,资源的 hash 发生变化,生成新的可永久缓存的资源地址。

因此在实践中,可对打包处理后带有 hash 资源的所有文件设置永久缓存。

如果前端通过 docker/k8s/helm 进行部署,可由团队人员自行在构建 nginx 镜像时进行添加响应头字段。此处可作为前端性能优化的 kpi/okr。

可在浏览器控制台 Network 中查看响应头来验证所属项目是否已成功添加永久缓存。

image

一个问题与更强的永久缓存

假设有两个文件: index.jslib.js,且 index 依赖于 lib,其内容如下。

index.js

1
2
// index.js
import('./lib').then(o => console.log(o))

lib.js

1
export const a = 3

由 webpack 等打包器打包后将会生生两个 chunk (为了方便讲解,以下 aaaaaa 为 hash 值)

  • index.aaaaaa.js
  • lib.aaaaaa.js

问: 假设 lib.js 文件内容发生变更,index.js 由于引用了 lib.js,可能包含其文件名,那么它的 hash 是否会发生变动

答: 不一定。打包后的 index.js 中引用 lib 时并不会包含 lib.aaaaaa.js,而是采用 chunkId 的形式,如果 chunkId 是固定的话,则不会发生变更。

1
2
3
4
5
// 打包前
import('./lib')

// 打包后,201 为固定的 chunkId (chunkIds = deterministic 时)
__webpack_require__.e(/* import() | lib */ 201)

在 webpack 中,通过 optimization.chunkIds 可设置确定的 chunId,来增强 Long Term Cache 能力。

1
2
3
4
5
{
optimization: {
chunkIds: 'deterministic'
}
}

设置该选项且 lib.js 内容发生变更后,打包 chunk 如下,仅仅 lib.js 路径发生了变更。

  • index.aaaaaa.js
  • lib.bbbbbb.js

下面的语句若没有特殊注明,默认都书写在 app/service 下。

Create

可以直接使用 insert 方法插入一条记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 插入
const result = await this.app.mysql.insert('posts', { title: 'Hello World' }); // 在 post 表中,插入 title 为 Hello World 的记录

=> INSERT INTO `posts`(`title`) VALUES('Hello World');

console.log(result);
=>
{
fieldCount: 0,
affectedRows: 1,
insertId: 3710,
serverStatus: 2,
warningCount: 2,
message: '',
protocol41: true,
changedRows: 0
}

// 判断插入成功
const insertSuccess = result.affectedRows === 1;

Read

可以直接使用 get 方法或 select 方法获取一条或多条记录。select 方法支持条件查询与结果的定制。

查询一条记录

1
2
3
const post = await this.app.mysql.get('posts', { id: 12 });

=> SELECT * FROM `posts` WHERE `id` = 12 LIMIT 0, 1;

查询全表

1
2
3
const results = await this.app.mysql.select('posts');

=> SELECT * FROM `posts`;

条件查询和结果定制

1
2
3
4
5
6
7
8
9
10
11
const results = await this.app.mysql.select('posts', { // 搜索 post 表
where: { status: 'draft', author: ['author1', 'author2'] }, // WHERE 条件
columns: ['author', 'title'], // 要查询的表字段
orders: [['created_at','desc'], ['id','desc']], // 排序方式
limit: 10, // 返回数据量
offset: 0, // 数据偏移量
});

=> SELECT `author`, `title` FROM `posts`
WHERE `status` = 'draft' AND `author` IN('author1','author2')
ORDER BY `created_at` DESC, `id` DESC LIMIT 0, 10;

Update

可以直接使用 update 方法更新数据库记录。

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
26
27
28
29
30
31
32
// 修改数据,将会根据主键 ID 查找,并更新
const row = {
id: 123,
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};
const result = await this.app.mysql.update('posts', row); // 更新 posts 表中的记录

=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE id = 123 ;

// 判断更新成功
const updateSuccess = result.affectedRows === 1;

// 如果主键是自定义的 ID 名称,如 custom_id,则需要在 `where` 里面配置
const row = {
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};

const options = {
where: {
custom_id: 456
}
};
const result = await this.app.mysql.update('posts', row, options); // 更新 posts 表中的记录

=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE custom_id = 456 ;

// 判断更新成功
const updateSuccess = result.affectedRows === 1;

Delete

可以直接使用 delete 方法删除数据库记录。

1
2
3
4
5
const result = await this.app.mysql.delete('posts', {
author: 'fengmk2',
});

=> DELETE FROM `posts` WHERE `author` = 'fengmk2';

直接执行 sql 语句

插件本身也支持拼接与直接执行 sql 语句。使用 query 可以执行合法的 sql 语句。

注意!!我们极其不建议开发者拼接 sql 语句,这样很容易引起 sql 注入!!

如果必须要自己拼接 sql 语句,请使用 mysql.escape 方法。

参考 preventing-sql-injection-in-node-js

1
2
3
4
const postId = 1;
const results = await this.app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);

=> update posts set hits = (hits + 1) where id = 1;

使用事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等。这时候使用事务处理可以方便管理这一组操作。 一个事务将一组连续的数据库操作,放在一个单一的工作单元来执行。该组内的每个单独的操作是成功,事务才能成功。如果事务中的任何操作失败,则整个事务将失败。

一般来说,事务是必须满足4个条件(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(可靠性)

  • 原子性:确保事务内的所有操作都成功完成,否则事务将被中止在故障点,以前的操作将回滚到以前的状态。
  • 一致性:对于数据库的修改是一致的。
  • 隔离性:事务是彼此独立的,不互相影响
  • 持久性:确保提交事务后,事务产生的结果可以永久存在。

因此,对于一个事务来讲,一定伴随着 beginTransaction、commit 或 rollback,分别代表事务的开始,成功和失败回滚。

egg-mysql 提供了两种类型的事务。

手动控制

优点:beginTransaction, commit 或 rollback 都由开发者来完全控制,可以做到非常细粒度的控制。
缺点:手写代码比较多,不是每个人都能写好。忘记了捕获异常和 cleanup 都会导致严重 bug。

1
2
3
4
5
6
7
8
9
10
11
const conn = await app.mysql.beginTransaction(); // 初始化事务

try {
await conn.insert(table, row1); // 第一步操作
await conn.update(table, row2); // 第二步操作
await conn.commit(); // 提交事务
} catch (err) {
// error, rollback
await conn.rollback(); // 一定记得捕获异常后回滚事务!!
throw err;
}

自动控制:Transaction with scope

1
API:beginTransactionScope(scope, ctx)

scope: 一个 generatorFunction,在这个函数里面执行这次事务的所有 sql 语句。
ctx: 当前请求的上下文对象,传入 ctx 可以保证即便在出现事务嵌套的情况下,一次请求中同时只有一个激活状态的事务。
优点:使用简单,不容易犯错,就感觉事务不存在的样子。
缺点:整个事务要么成功,要么失败,无法做细粒度控制。

1
2
3
4
5
6
7
const result = await app.mysql.beginTransactionScope(async conn => {
// don't commit or rollback by yourself
await conn.insert(table, row1);
await conn.update(table, row2);
return { success: true };
}, ctx); // ctx 是当前请求的上下文,如果是在 service 文件中,可以从 `this.ctx` 获取到
// if error throw on scope, will auto rollback

表达式(Literal)
如果需要调用 MySQL 内置的函数(或表达式),可以使用 Literal。

内置表达式

NOW():数据库当前系统时间,通过 app.mysql.literals.now 获取。

1
2
3
await this.app.mysql.insert(table, {
create_time: this.app.mysql.literals.now,
});

=> INSERT INTO $table(create_time) VALUES(NOW())

自定义表达式

下例展示了如何调用 MySQL 内置的 CONCAT(s1, …sn) 函数,做字符串拼接。

1
2
3
4
5
6
7
8
9
const Literal = this.app.mysql.literals.Literal;
const first = 'James';
const last = 'Bond';
await this.app.mysql.insert(table, {
id: 123,
fullname: new Literal(`CONCAT("${first}", "${last}"`),
});

=> INSERT INTO `$table`(`id`, `fullname`) VALUES(123, CONCAT("James", "Bond"))

安装

1
$ npm i egg-mysql --save

egg 的 MySQL 插件,支持egg 应用访问 MySQL 数据库。

本插件基于ali-rds,具体用法可以参考ali-rds文档。

更改${app_root}/config/plugin.js以启用 MySQL 插件:

1
2
3
4
exports.mysql = {
enable: true,
package: 'egg-mysql',
};

配置数据库信息${app_root}/config/config.default.js

简单的数据库实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
exports.mysql = {
// database configuration
client: {
// host
host: 'localhost',
// port
port: '3306',
// username
user: 'test_user',
// password
password: 'test_password',
// database
database: 'test',
},
// load into app, default is open
app: true,
// load into agent, default is close
agent: false,
};

用法:

1
2
app.mysql.query(sql, values); // you can access to simple database instance by using app.mysql.

多数据库实例

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
26
27
28
exports.mysql = {
clients: {
// clientId, access the client instance by app.mysql.get('clientId')
db1: {
// host
host: 'mysql.com',
// port
port: '3306',
// username
user: 'test_user',
// password
password: 'test_password',
// database
database: 'test',
},
// ...
},
// default configuration for all databases
default: {

},

// load into app, default is open
app: true,
// load into agent, default is close
agent: false,
};

用法:

1
2
3
4
5
const client1 = app.mysql.get('db1');
client1.query(sql, values);

const client2 = app.mysql.get('db2');
client2.query(sql, values);

CRUD 用户指南

插入

1
2
3
// 插入
const result = yield app.mysql.insert('posts', { title: 'Hello World' });
const insertSuccess = result.affectedRows === 1;

1
2
3
4
5
6
7
8
9
// 获取
const post = yield app.mysql.get('posts', { id: 12 });
// 查询
const results = yield app.mysql.select('posts',{
where: { status: 'draft' },
orders: [['created_at','desc'], ['id','desc']],
limit: 10,
offset: 0
});

更新

1
2
3
4
5
6
7
8
9
// 按主键 ID 更新,并刷新
const row = {
id: 123,
name: 'fengmk2',
otherField: 'other field value',
modifiedAt: app.mysql.literals.now, // `now()` 在数据库服务器上
};
const result = yield app.mysql.update('posts', row);
const updateSuccess = result.affectedRows === 1;

删除

1
2
3
const result = yield app.mysql.delete('table-name', {
name: 'fengmk2'
});

交易

手动

  • 优势:beginTransactioncommit或者rollback可以完全由开发者控制
  • 缺点:手写代码较多,忘记捕捉错误或清理会导致严重的bug。
1
2
3
4
5
6
7
8
9
10
11
const conn = yield app.mysql.beginTransaction();

try {
yield conn.insert(table, row1);
yield conn.update(table, row2);
yield conn.commit();
} catch (err) {
// 错误,回滚
yield conn.rollback(); 回滚调用不会抛出错误
throw err;
}

自动控制:有作用域的事务

  • 接口:

    1
    *beginTransactionScope(scope, ctx)
    • scope:一个generatorFunction,它将执行这个事务的所有sqls。
    • ctx: 当前请求的上下文对象,它会确保即使在嵌套事务的情况下,一个请求中同时也只有一个活动事务。
  • 优点:好用,就好像你的代码里没有事务一样。

  • 缺点:所有交易都会成功或失败,无法精确控制

1
2
3
4
5
6
7
const result = yield app.mysql.beginTransactionScope(function* (conn) {
// don't commit or rollback by yourself
yield conn.insert(table, row1);
yield conn.update(table, row2);
return { success: true };
}, ctx); // ctx 是当前请求的上下文,通过`this.ctx` 访问。
// 如果在作用域上抛出错误,将自动回滚

进步

自定义SQL拼接

1
const results = yield app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);

文字

如果要在 mysql 中调用文字或函数,可以使用Literal.

内部文字

  • NOW():数据库系统时间,可以通过app.mysql.literals.now.
1
2
3
4
5
yield app.mysql.insert(table, {
create_time: app.mysql.literals.now
});

// 插入`$table`(`create_time`) VALUES(NOW())

自定义文字

下面的demo展示了如何CONCAT(s1, ...sn)在mysql中调用函数进行字符串拼接。

1
2
3
4
5
6
7
8
9
const Literal = app.mysql.literals.Literal;
const first = 'James';
const last = 'Bond';
yield app.mysql.insert(table, {
id: 123,
fullname: new Literal(`CONCAT("${first}", "${last}"`),
});

// INSERT INTO `$table`(`id`, `fullname`) VALUES(123, CONCAT("James", "Bond"))

Node.js

特点

  • 属于单线程逻辑处理
  • 不会产生死锁
  • 适合做基于社交网络的大规模WEB应用

对比js

​ js运行在客户浏览器,存在多种解释器,有代码兼容性问题;Node.js运行在服务器,只有V8引擎一种解释器,不存在代码兼容性问题

​ 两者都有相同内置对象和自定义对象,不同的宿主对象

​ js用于开发就浏览器端的交互效果,Node.js用于服务器端功能开发,例如数据库的访问,其他服务器得到调用

运行方式

​ 脚本模式:node 文件的路径 回车

​ 交互模式:node 回车 进入交互模式

​ 退出:两次ctrl+c/ctrl+d/.exit

全局对象

​ global对象

​ (1),检测一个变量或函数是否为全局的

​ (2),在交互模式下属于全局作用域,里面的变量是和函数都是全局的

​ (3),在脚本模式下不属于全局作用域,里面的变量和函数都不是全局的,可以防止全局污染

​ console对象

​ console.log(1);//输出日志 console.info(2);//输出消息

​ console.warn(3);//输出警告 console.error(4);//输出错误

​ console.time()开始计时 console.timeEnd()结束计时

​ 开始计时和结束计时提供的值要保持一致

​ process对象

​ 进程:计算机在运行软件的时候,都会产生相应的进程

1
2
3
4
5
6
7
8
9
process.arch	查看当前CPU的架构

process.platfrom 查看当前的操作系统

process.version 查看当前Node.js版本

process.pid 查看当前进程的编号

process.kill(编号) 结束指定进程

​ Buffer缓冲区

1
2
3
4
5
6
7
缓冲区,是内存中临时存储的区域

let buf = Buffer.alloc(4,'root');

创建Buffer,设置大小为4个字节,并填充数据,每个字节占3个字节

String(buf)/buf.toString();//将Buffer数据转为字符串

​ 定时器函数

1
2
3
4
5
6
7
//一次性定时器:	setTimeout(回调函数,时间/毫秒);
//清除定时器: clearTimeout(定时器的变量);
//周期性定时器: setInterval(回调函数,时间/毫秒) 每隔一段时间,调用一次函数
//清除定时器 clear Interval(定时器的变量);
//立即执行定时器
//开启setImmediate(回调函数) 清除clearImmediate(timer)
//开启process.nextTick(回调函数) 宏任务

模块

​ 每文件是一个模块,每一个模块都是一个独立的功能

​ 一个模块引入其他的模块

​ 一个模块也可以被其他的模块引入

1
2
3
4
5
6
7
require( ) //是一个函数,用于引入其他的模块

module.exports //导出的对象,默认是一个空对象,如果要导出哪些内容需要放入到这个对象

__dirname: //是一个局部变量,当前模块 的绝对路径

__filename: //是一个叫局部变量,当前模块的绝对路径+模块名称
模块分类
1.自定义模块,第三方模块,核心模块
以路径开头 不以路径开头
文件形式 require(‘./文件名.js’)用于引入自定义模块 require(‘querystring’)用于引入官方提供的核心模块
目录形式 require(‘./文件名’)首先会找到目录下寻找package.json文件中main对应的文件,如果找不到则会寻找index.js require(‘tao’)首先会找到同一级目录下的node_modules目录中寻找tao,如果没找到会一直往上一级的node_modules目录中寻找;用于引入第三方模块
2.包和npm

​ CommonJS:是一种规范,制定了Node.js的模块化概念

​ 包:通常指的是目录模块

​ npm:是用于管理包的工具模块

​ 网址:https://www/npmjs.com

​ 使用npm:npm init -y 生成package.json文件,作为项目文件,可以用于记录下载安装的包的信息

​ 下载包:npm install 包的名称 ;下载安装指定的包,将包放入到node_modules目录中,如果目录不存在会先创建,同时生成package-lock.json,用于记录所有包的版本号

​ npm install 自动下载package.json和package-lock.json中记录的包

​ 下载或运行其他版本文件:npx -p node@8 node 拖拽文件 下载指定版本的Node.js运行文件,运行完以后再把下载的Node.js删除

3.查询字符串模块(querystring)

​ 查询字符串:浏览器向WEB服务器发送请求,传递的数据的一种方式,位于浏览器的地址栏中

keyword=笔记本&enc=utf-8

查询字符串模块用于操作查询字符串的工具

​ parse( )将查询字符串解析为对象

4.URL模块

​ URL:统一资源定位,互联网上的任何资源都有对应的URL

​ new URL( ) 将一个URL解析为对象,获取URL的各个部分

5.文件系统模块

​ 用于操作服务器端的文件,例如文件的读取,写入,删除……

​ 文件分为目录形式和文件形式 同步加Sync

​ (1)fs.statSync(文件的路径) / fs.stat(文件的路径,回调函数)

​ 查看是否为文件形式 isFile() 返回true或false

​ 查看是否为目录文件 isDirectory() 返回true或false

​ (2)创建目录 fs.mkdirSync(‘目录的路径’)

​ (3)移除目录 fs.rmdirSync(‘目录的路径’) //只能移除空目录

​ (4)读取目录 fs.readdirSync(‘../上级目录文件名’) / readdir(目录的路径,回调函数)

​ (5)覆盖写入文件 writeFileSync(文件的路径,数据) / writeFile(文件的路径,数据,回调函数)

​ 如果文件不存在,则先创建文件然后写入数据

​ 如果文件已经存在,会清空文件内容然后写入文件

​ (6)追加写入文件 appendFileSync(文件的路径,数据) / appendFile(文件的路径,数据,回调函数)

​ 如果文件不存在,则先创建文件然后写入文件

​ 如果文件已经存在,会在文件的末尾追加写入数据

​ (7)读取文件数据 readFileSync(文件的路径) / readFile(文件的路径,回调函数)

​ 读取的数据结果为buffer数据格式

​ (8)删除文件 unlinkSync(文件的路径) / unlink(文件的路径,回调函数)

​ fs模块

​ (9)判断文件是否存在 existsSync(文件/目录) 存在–>true 不存在–>false

​ (10)拷贝文件 copyFileSync(源文件路径,目标文件路径) / copyFile(源文件路径,目标文件路径,回调函数)

6.*同步和异步

​ 同步:在主程序中执行,会阻止后续代码的执行,通过返回值来获取结果

​ 异步:在一个独立的线程执行,不会阻止主程序后续代码的执行,将结果以会低矮函数的形式放入到事件队列

文件流

1
2
3
4
5
6
7
createReadStream()	创建可读取的文件流

createWriteStream() 创建可写入的文件流

on(事件名称,回调函数) 添加事件,事件名称是固定的字符串,一旦坚挺到事件呼呼自动执行回调函数

pipe()管道,可以将读取的流添加到写入的流

HTTP协议

​ 浏览器和WEB服务器之间的通信协议

​ (1)通信头信息

1
2
3
4
5
Request URL:请求的服务器端的资源

Request Method: 请求的方法,对资源的操作方式 get/post/detele/put

Status Code: 响应的状态码

​ 100:接受到了请求,还没有做出响应

​ 200:成功的响应

​ 300:响应的重定向,跳转到另一个url

​ 400:客户端错误

​ 500:服务器错误

​ (2)响应头信息 response

1
2
Content-Type:响应的内容类型   text/html; charset=UTF-8
Location:设置要跳转的url

​ (3)请求头信息 request

​ (4)请求主题

​ 显示传递的数据,不是每一次都出现

HTTP模块

​ 可以用来创建WEB服务器

(1)创建服务器

1
2
3
4
5
引入HTTP模块const http = require('http');

创建WEB服务器const app = http.createServer();

设置端口app.listen(80,()=>{ });

(2)接收请求作出相应

1
2
3
4
5
6
7
8
9
10
//给服务器添加事件
app.on('request',(req,res)=>{
req 请求对象
req.url 获取请求的url,显示的端口号后的部分,
req.method 获取请求的方法
res 响应对象
*res.writeHead(状态码,头信息) 设置响应的状态码和头信息,第二个参数可以为空
*res.write() 设置响应到浏览器的内容
*res.end() 结束并发送响应
})

框架

​ 框架:是一整套解决方案,简化了已有的功能,添加了之前没有的功能

​ Node.js框架:express,koa,egg

​ 1.express框架

​ express是基于 Node.js 平台,快速、开放、极简的 Web 开发框架

​ 属于是第三方模块,需要先下载安装 npm install express

(1)创建WEB服务器
1
2
3
引入express模块	const express = require('express');
创建WEB服务器 const app = express();
设置端口 app.listen(8080,()=>{ })

(2)路由

​ 根据请求的URL和请求的方法来做出特定的响应

​ 包含三部分:请求的URL,请求的方法,回调函数

1
2
3
4
req请求对象
req.url 获取请求的URL
req.method 获取请求的方法
req.query 获取get传递的数据。格式为对象

1
2
3
4
5
res响应对象
res.send( ) 设置响应的内容并发送
res.redirect( ) 设置响应的重定向并发送
res.sendFile( ) 设置响应的文件并发送
//以上三个方法在路由中只能执行一次

数据传递的方式

名称 格式 获取
get方式 http://127.0.0.1:8080/mysearch?keyword=传递的值 req.query
post方式 URL中不可见,在http协议的请求主体 req.on(‘data’,(chunk)=>{chunk.toString() 需要使用查询字符串解析为对象})
路由传参 http://127.0.0.1:8080/package/mysql app.get(‘/package/:pname’,(req,res)=>{req.params})
(3)路由器

​ 路由器可以统一管理路由,还额可以给一组路由器添加统一的前缀

1
2
3
//路由器
const r = express.Router(); 创建路由器对象
module.exports = r; 导出路由器对象

​ WEB服务器

1
2
//引入
app.use('/produck',perduckRouter) 挂载路由器到WEB服务器

中间件

​ 拦截对服务端的请求,也可以做出响应

​ express下中间件分为应用级中间件,路由级中间件,内置中间件,第三方中间件,错误处理中间件

​ (1)应用级中间件

​ 也成为自定义中间件,本质上就是一个函数

​ app.use(URL,回调函数)

​ app.use(回调函数)

​ (2)路由级中间件

​ app.use(拦截的url,回调函数)

​ (3)内置中间件

​ 托管静态资源

​ 客户端请求静态资源(html,css ,js,图像…)不在创建路由,让客户端自动到指定的目录下查找

​ app.use(express.static(‘要托管的目录’))

​ (4)第三方中间件

​ 第三方中间件以第三方模块的形式出现,需要先下载安装

1
2
3
4
5
6
7
8
//1.引入中间件
const bodyParser = require('body-Parser')
//2.使用中间件,将所有post请求的数据解析为对象
app.use(bodyParser.urlencoded({
extended:false //false 不使用第三方的查询字符串模块,就会使用核心querystring
}))
//3.在路由中获取数据,格式为对象
req.body

MySQL模块

​ Node.js下,专门用于操作MySQL数据库的第三方模块

​ node install mysql 下载安装

​ mysql命令

1
2
3
4
5
6
7
mysql -uroot //登录数据库
mysql.exe -h127.0.0.1 -p3306 -uroot -p
mysql -uroot<拖拽脚本 //
insert into 表名 values(.....);
delete from 表名 where 列名=值;
update 表名 set 列名=修改的值,....where 列名=值;
select * from 表名;

SQL注入:在让用于提供的值中,并拼接了其他的SQL注入。

占位符( ? ):先对数据进行过滤,过滤完以后在替换占位符

1
2
3
4
5
6
7
8
9
10
mysql.createConnection({
host:'127.0.0.1',
port:'3306',
user:'root',
password:'',
database:'xz', //数据库名
connectionLimit:15 //默认15
}) //创建连接对象
connect() //测试连接
query(SQL命令,要过滤的数据,回调函数)

连接池

​ 开始的时候创建一批的链接,可以被反复的使用,用完后会归还

1
2
mysql.createPool( ) 创建连接池对象
query( ) 执行SQL命令

RESTful接口

​ 接口:后端为前端提供的动态资源(对数据的增删改查)

​ RESTful:是一种接口的设计规范

​ ①URL

1
2
3
4
5
6
7
8
9
员工资源 
http://127.0.0.1:8080 /v1 /emps 多个资源
版本 资源名称(复数形式)
http://127.0.0.1:8080 /v1 /emps /5 单个资源

编号
http://127.0.0.1:8080 /v1 /login 对资源特殊操作

登录

​ ②请求的方法

​ 对资源的操作方式

1
2
3
4
get		获取资源
delete 删除资源
put 修改资源
post 新建资源

​ ③过滤数据

1
2
3
//针对于多个资源的操作
http://127.0.0.1:8080/v1/emps?pno=1&count=9
//通过分页过滤的数据 当前页码 每页数据量
1
2
http://127.0.0.1:8080/v1/emps?*salary1=6000&salary2=8000*
//过滤一组区间的数据

​ ④返回结果

​ 格式为json对象:字符串形式的对象,实姓名必须用双引号,属性值是字符串也得是双引号

​ 包含状态码(人为规定),消息,数据

正则表达式

1
2
3
4
5
6
7
test( )	检测字符串是否符合规则	

​ replace(规则,替换的字符串) 查找并替换

​ search(规则) 查找符合规则的第一个,返回下标,找不到返回-1

​ match(规则) 查找符合规则的所有,返回数组

​ 修饰符

1
g - global 全局查找			i - ignore 忽略大小写