Vue是一个渐进式的框架
Vue特点和Web开发常见高级功能
方式一. 直接CDN引入
可以选择引入开发环境版本 / 生产环境版本
// 开发环境版本, 包含了帮助的命令行警告
// 生产环境版本, 优化了尺寸和速度
方式二. 下载和引入
// 开发环境
https://vuejs.org/js/vue.js
// 生产环境
https://vuejs.org/js/vue.min.js
方式三. NPM安装
通过webpack和CLI的使用
M: Model 数据模型
V: View 视图模板
VM: View-Model 视图模型
语法糖: 简写
事物从诞生到消亡的整个过程
插值操作 Mustache
v-once
后面不需要跟任何表达式
表示元素和组件只渲染一次, 不会随着数据的改变而变化
v-html
后面往往跟一个string类型
会将string的html解析出来并渲染
v-text
与Mustache相似, 一般不用, 不灵活
v-pre
用于跳过这个元素和它子元素的编译过程, 用于显示原本的Mustache语法
v-cloak
在某些情况下, 我们浏览器可能会直接显示出未编译的Mustache标签
v-bind
作用: 动态绑定属性
简写: :
条件判断
v-show
作用: 绑定事件监听
简写: @
写法:
点击次数: {{counter}}
修饰符 | 作用 | 实际调用 |
---|---|---|
.stop | 阻止事件冒泡 | event.stopPropagation() |
.prevent | 阻止默认事件 | event.preventDefault() |
{keyCode I keyAlias} | 监听某个键盘的键帽 | – |
.native | 监听组件根元素的原生事件 | – |
.once | 只触发一次回调 | – |
// 遍历过程中, 没有使用索引值
{{item}}
// 遍历过程中, 获取索引值
{{index + 1}} - {{item}}
// 遍历对象的时候, 只有一个值, 活得的是value
// 获取对象的key和value
{{value}} - {{key}}
// 获取对象的key和value和index
{{value}} - {{key}} - {{index}}
官方推荐, 使用v-for的时候, 加上一个 key属性
图1
key的作用是为了高效的更新虚拟DOM
key要具有唯一性, 不然就没意义
{{index + 1}} - {{item}}
实现表单元素和数据的双向绑定
{{message}}
cosnt app = new Vue({
el: '#app',
data: {
message: '你好'
}
})
// 界面的message数据改了, data里面的message就改变了, 是双向的
可以将v-model用于textarea元素
输入内容: {{message}}
其他方法实现双向绑定
// v-bind绑定一个value属性
// v-on指令给当前元素绑定input事件
// 下面代码 等同于 使用v-model
{{message}}
cosnt app = new Vue({
el: '#app',
data: {
message: '你好'
}
})
您选择的性别是: {{sex}}
cosnt app = new Vue({
el: '#app',
data: {
sex: '男'
}
})
// 需求: 是否同意该协议, 同意后下一步
下一步
cosnt app = new Vue({
el: '#app',
data: {
isAgree: false
}
})
// 点击文字部分也可以选中
您的爱好是: {{hobbies}}
cosnt app = new Vue({
el: '#app',
data: {
isAgree: false,
hobbies: []
}
})
下拉框单选
您最喜欢的水果: {{mySelect}}
下拉框多选
您最喜欢的水果: {{mySelect}}
含义: 动态的给value赋值
1 在前面的value中的值, 都是在定义input的时候直接给定的
2 但真实开发中, input的值可能是从网络获取或定义在data中的
3 可以通过v-bind:value动态的给value绑定值
您的爱好是: {{hobbies}}
cosnt app = new Vue({
el: '#app',
data: {
hobbies: [],
nums: ['篮球', '足球', '羽毛球']
}
})
lazy修饰符
number修饰符
trim修饰符
Vue是响应式, 所以当数据发生变化时, Vue会自动检测数据变化, 视图会发生对应的更新.
Vue中观察数据编译的方法 – 用它们改变数组会触发视觉更新
数组响应式方法 | 作用 |
---|---|
push() | 在数组中最后增加元素 |
pop() | 删除数组中最后一个元素 |
shift() | 删除数组中的第一个元素 |
unshift() | 在数组最前面添加元素 |
splice() | 删除 / 插入 / 替换元素 |
sort() | xx |
reverse() | xx |
数组未响应 | 作用 |
---|---|
filter() | xxxxxxx |
concat() | xxxxxxx |
slice() | xxxxxxx |
使用: 需要将多个数据结合起来进行显示的时候
// 使用拼接的方法 -- 语法太过繁琐
{{firstName + '' + lastName}}
{{firstName}} {{lastName}}
// 使用方法
{{getFullName()}}
// 使用计算属性 -- 看起来最舒服 最好
{{fullName}}
const app = new Vue({
el: '#app',
data:{
firstName: 'li',
lastName: 'er'
},
// 计算属性
computed: {
fullName: function () {
reture this.firstName + '' + this.lastName
}
},
// 方法
methods: {
getFullName () {
reture this.firstName + '' + this.lastName
}
}
})
{{fullName}}
const app = new Vue({
el: '#app',
data:{
firstName: 'li',
lastName: 'er'
},
// 计算属性
computed: {
fullName: {
// 一般没有set方法
set: function (value) {
},
// 只读属性
get: function () {
reture this.firstName + '' + this.lastName
}
}
}
})
上面案例的写法是简写. 完整写法是调用了get();
多次使用的时候
const app = new Vue({
el: '#app',
data:{
aaa: 'li'
},
// 过滤器
fulters: {
showA (value) {
return;
}
})
//使用组件
//创建组件构造器
const cpnC = Vue.extend({
template: '
标题
模板
'
})
//注册组件
Vue.component('my-cpn', cpmC)
const app = new Vue({
el: '#app',
data:{
message: ''
}
})
//
楼上的案例
const cpnC = Vue.extend({
template: '
标题
模板
'
})
const app = new Vue({
el: '#app',
data:{
message: ''
},
components: {
cpn: cpnC
}
})
父子组件的错误用法: 以子标签的形式在Vue实例中使用
Vue为了简化注册组件的过程, 提供了注册的语法塘, 省去了调用Vue.extend()的步骤. 而是可以直接使用一个对象来代替
// 全局组件
Vue.component('xxx', {
template: '
哈哈哈哈
'
})
// 内部会自动调用Vue.extend()
// 局部组件
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
components: {
'cpn': {
template: '
哈哈哈哈
'
}
}
})
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
哈哈哈哈
Vue.component('cpn', {
template: '#cpn'
})
不能访问
组件是一个单独功能模块的封装 - 这个模块有自己的HTML模板, 也应该有属性自己的数据data
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: '哈哈哈'
}
}
})
// data属性必须是一个函数, 返回必须是一个对象
https://www.bilibili.com/video/av59594689?p=57
{{item}}
{{cmessage}}
const cpn = {
template: '#cpn',
// 把数组里的当变量来看了
props: ['cmovies', 'cmessage'],
data () {
return {}
}
}
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈',
movies: ['哈喽', '嗨', '哟哟']
},
components: {
cpn
}
})
// 在使用组件的时候绑定
// 不支持驼峰命名 cMovies要写成c-movies
{{item}}
{{cMessage}}
const cpn = {
template: '#cpn',
// 把数组里的当变量来看了
props: {
// 类型限制
// cMovies: Array,
// cMeesage: String,
// 类型限制 + 提供一些默认值 or required表示必传值,不然报错
cMessage: {
type: String,
default: '哈',
required: true
},
cMovies: {
type: Array, // 对象or数组类型的时候, 默认值必须是个函数
default() {
return {}
}
}
}
data () {
return {}
}
}
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈',
movies: ['哈喽', '嗨', '哟哟']
},
components: {
cpn
}
})
// 父组件模板
// 2.父组件监听一个事件
// 不能写驼峰
// 子组件模板
// 子组件
const cpn = {
template: '#cpn',
data() {
return {
categories: [
{id: 'aaaa', name: '热门推荐'},
{id: 'bbbb', name: '手机数码'},
{id: 'cccc', name: '家用家电'},
{id: 'dddd', name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 要把item传给父组件
// 1. 发送一个事件 (自定义事件)
// 会把item当成默认的传到父组件去
this.$emit('itemclick', item)
]
}
}
// 父组件
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
components: {
cpn
},
methods: {
// 3. 父组件监听的事件
cpnClick(item) {
console.log('成功了', item);
}
}
})
https://www.bilibili.com/video/av59594689?p=63
// this.$children是一个数组类型, 它包含所有子组件对象
// 通过遍历, 取出所有子组件的message状态
// 使用$refs的时候, 在想要访问的子组件上添加ref属性
按钮
我是子组件
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
methods: {
btnClick() {
cosole.log(this.$children);
// $children 的使用方法, 一般用的少, 下标不固定
// this.$children[0].showMessage();
// this.$children[0].name;
//for (let c of this.$children) {
// console.log(c.name); // 我是子组件的name
// c.showMessage(); // showMessage
//}
// $refs 的使用方法 => 对象类型, 默认是一个空的对象
console.log(this.$refs.aaa.name); // 我是子组件的name
}
}
components: {
cpn: {
template: "#cpn",
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
cosole.log('showMessage');
}
}
}
}
})
我是子组件
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
components: {
cpn: {
template: "#cpn",
data() {
return {
name: '我是cpn的name'
}
},
conmponents: {
ccpn: {
template: '#ccpn',
btnClick() {
// 1. 访问父组件$parent
// 不建议这么写, 一层套一层 复用性太差
console.log(this.$parent);
console.log(this.$parent.name); // 我是cpn的name
// 2. 访问根组件 $root
console.log(this.$root.meassage); // 哈哈哈
}
}
}
}
}
})
provide选项允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
fun: this.fun, //方法
name: 'Bob', // 数据
}
}
然后在任何后代组件里,我们都可以使用inject选项来接收指定的我们想要添加在这个实例上的属性:
//接受方法
inject: ['fun']
//接受数据
inject:['name']
作用:
组件的插槽是为了让我们封装的组件更加具有扩展性
让使用者可以决定组件内部的一些内容到底展示什么
// 使用默认值替换
// 插槽替换的元素
这是替换的内容呀
// 多个元素
第一个元素
第二个元素
这个是标题
这个是内容
// 放入插槽
默认值元素
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
components: {
cpn: {
template: '#cpn'
}
}
})
在多个插槽的情况下, 替换制定插槽的内容
中间标题
左边
中间
右边
const app = new Vue({
el: '#app',
data: {
meassage: '哈哈哈'
},
components: {
cpn: {
template: '#cpn'
}
}
})
父组件模板所有的东西都在父级作用域内编译, 子组件模板的所有东西会在子级作用域内编译
我是一 // true
我是二
// false
const app = new Vue({
el: '#app',
data: {
isShow: true // Vue实例中的属性
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false // 子组件中的属性
}
}
}
}
})
父组件替换插槽的标签, 但是内容由子组件来提供
需求:
1. 子组件中包括一组数据, num: ['1', '2', '3', '4', '5']
2. 需要在多个界面展示
某些界面是以水平方向展示
某些界面是以列表形式展示
某些界面直接展示一个数组
3. 内容在子组件, 希望父组件告诉我们如何展示, 怎么办?
利用slot作用域插槽就行了
// 2. 获取子组件中的num
// 根据之前的起名来取 如 slot.aaa
{{item}}
// 1. slot定义 'data'可以随便起名 如 :aaa="num"
- {{item}}
const app = new Vue({
el: '#app',
data: {
message: '哈哈哈'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
num: ['1', '2', '3', '4', '5']
}
}
}
}
})
是一个JS应用的静态模块打包工具
webpack依赖node环境, node环境为了可以正常执行很多代码, 必须包含各种依赖的包,npm工具是为了管理包
// node官网下载就行了
// 查看node版本
npm -v
// 全局安装webpack (指定版本)
nom install [email protected] -g
// 局部安装webpack
// --save-dev 是开发时依赖, 项目打包后不需要继续使用
cd 对应目录
npm install [email protected] --save-dev
// 查看webpack安装版本
webpack -v
// 安装webpack后错误提示 / VUE项目安装同理
webpack : 无法加载文件 C:\Users\球球\AppData\Roaming\npm\webpack.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。
+ webpack -v
解决方案
(1)以管理员身份运行vs code
(2)在终端执行:get-ExecutionPolicy,显示Restricted(表示状态是禁止的)
(3)在终端执行:set-ExecutionPolicy RemoteSigned
(4)在终端执行:get-ExecutionPolicy,显示RemoteSigned
https://www.bilibili.com/video/av59594689?p=77
创建: webpack.config.js 文件
// 为了动态获取打包后的路径, path 在node包里面 -- 看下面一段代码 装包
const path = require('path')
module.exports = {
entry: './src/main.js', // 要打包的文件
output: {
path: path.resolve(__dirnmae, 'dist'), // 打包后的文件 要写绝对路径--动态获取路径
filename: 'bundle.js'
}
}
node装包
// 初始化--生成
npm init
// 生成的文件可能有中文 / 符号 可以改名
meetwebpack
... 后面全部回车, 一路通过. OK就行
... 生成 package.json文件
// package.json文件里面也有依赖的文件
package.json文件
"version" -- 版本号
// 本地安装webpack (之前是全局安装) -- 开发时依赖 运行时依赖
npm install [email protected] --save-dev
// 开发时依赖 (本地安装webpack后, 重新打开package.json文件会出现)
"devDependencies": {
"webpack": "^3.6.0"
}
// 使用vue之后, 会有 (运行时依赖)
"dependencise": {
"webpack": "^3.6.0"
}
---
// 执行脚本的配置
"scripts": {
"test": '',
"build": "webpack"
}
npm run test
npm run build
webpack本身来说无法处理css, 图片, 高级ES6转化ES5的能力, 所以需要webpack扩展对应的loader就好了
loader使用过程
main.js
// 1. 使用commonjs的模块化规范
const {add, mul} = require('./js/mathUtils.js')
// 2. 使用ES6的模块化的规范
import {name, age} from "./js/info"
// 3. 依赖CSS文件
require('./css/normal.css')
具体的直接看视频吧
plugin是什么
loader和plugin区别
plugin的使用过程
名字: BannerPlugin (webpack自带)
// 按照下面方法修改webpack.config.js文件
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归coder所有')
]
}
// 重新打包程序, 查看bundle.js文件的头部, 看到如下信息
/*! 最终版权归coder所有 */
我们知道index.html文件是存在项目的根目录下的, 在真实发布项目是, 发布的却是dist文件夹中的内容, 所以需要将index.html文件打包到dist文件夹中.
名字: htmlWebPlugin
npm install html-webpack-plugin --save-dev
// 修改webpack.config.js文件
const path = require('path')
const webpack = require('webpack')
const htlmWwbPlugin = require('html-webpack-plugin')
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归coder所有'),
new htmlWebPlugin({
template: 'index.html' // 这里的template表示根据什么模板快来生index.html
})
]
}
项目发布之前, 需要对JS等我呢渐进性压缩处理 (代码丑化)
名字: uglifyjs-webpack-plugin
PS: 指定版本1.1.1 和CLI2保持一致
npm install [email protected] --save--dev
// 修改webpack.config.js文件
const path = require('path')
const webpack = require('webpack')
const uglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归coder所有'),
new uglifyJsPlugin()
]
}
// 查看打包的bundle.js文件, 是已经被压缩的文件
webpack提供一个可选的本地开发服务器(基础node.js搭建), 内部使用express框架, 可以实现让浏览器自动刷新显示我们修改后的结果
它是一个单独的模块, 在webpack中使用需要先安装
npm install --save-dev [email protected]
// 修改webpack.config.js文件
module.exports = {
...
devServer: {
contentBase: './dist',
inline: true
}
}
// package.json文件中再配置一个scripts, open参数表示直接打开浏览器
"scripts": {
...
"dev": "webpack-dev-server --open"
}
很多配置开发时需要, 发布时不需要,反之一样, 所以要做分离
npm install webpack-merge --save-dev
CLI 是(Command-Line Interface) 命令行界面, 俗称脚手架
Vue CLI是官方发布的vue.js项目脚手架, 可以快速搭建vue开发环境以及webpack配置
Vue CLI 使用前提 - 安装node 安装webpack
// 安装Vue脚手架
npm install -g @vue/cli
// 上面安装的是Vue CLI3.0版本, 想要按照Vue CLI2的方式初始化项目时需要进行下列的命令
// 拉取2.x模板
// 'vue init' 的运行效果将会跟 '[email protected]' 相同
npm install -g @vue/cli-init
// Vue CLI2初始化项目 project -> 项目名称 英文
vue init webpack project
// Vue CLI3初始化项目 project -> 项目名称 英文
vue create project
// 创建Vue CLI2项目
vue init webpack vuecli2
? Project name vuecli2 --> 项目名字
? Project description test vue cli2 --> 项目描述
? Author huqinxue --> 作者
? Vue build --> 详解看下面一段
? Install vue-router? (Y/n) Y
? Use ESlint to lint your code? (Y/n) Y
? Pick an ESLint preset --> 选择ESlint的规范 Standard
? Set up unit tests (Y/n) --> 单元测试
? Setup e2e tests with Nightwatch? (Y/n) --> 端对端测试
? Yes, use NPM
Yes, use Yarn
https://www.bilibili.com/video/av59594689?p=95
① runtiome+compilter 和 runtiome-only 区别
② render函数的使用
runtiome+compilter 和 runtiome-only 区别只在 main.js文件里面
// runtiome+compilter 流程
template -> ast (抽象语法树) -> render -> Virtual dom (虚拟DOM) -> UI
// runtiome-only 流程 (1. 性能更好 2. 代码量更少)
render -> Virtual dom (虚拟DOM) -> UI
rebder函数的使用 (runtiome-only)
-- 使用方式一:
return createElement('标签', '相关数据对象, 可不传', ['内容数组'])
new Vue({
el: "#app",
render: (createElement) => {
//render函数基本使用
return createElement('div', {class: 'box'}, ['codewhy'])
//嵌套render函数
return createElement('div', {class: 'box'}, ['codewhy', createElement('h2', ['标题啊'])])
}
})
-- 使用方式二: 传入一个组件对象
const cpn = Vue.component('cpn', {
template: '我是cpn组件',
data () {
return {
}
}
})
new Vue({
el: "#app",
render: (createElement) => {
return createElement(cpn)
}
})
https://www.bilibili.com/video/av59594689?p=92
// package.json文件 -> 查看命令的写法
"scripts": {
"dev": --> build / webpack.dev.conf.js
"start":
"lint":
"build": --> build / build.js + webpack.prod.conf.js
}
// config文件夹
都是定义的一些变量
// node_moudules文件夹
依赖的包
// src文件夹
开发的文件夹
// static文件夹
静态资源, 打包后会原封不动的放到build文件夹中
// .bobelrc文件
依赖evn的时候产生的文件
// .editorconfig文件
快速的一些习惯, 比如空格是2个字符还是4个字符
// .eslintignore文件
eslint可以忽略的问题
! 看视频吧, 写的..累
图2
// 安装不成功的时候, 可以清除缓存 (要从管理员权限打开)
npm clean cache --force
3 是基于 webpack 4 打造,2还是 webapck 3
3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
3 提供了 vue ui 命令,提供了可视化配置,更加人性化
移除了static文件夹,新增了public文件夹,并且index.html移动到public中
// 创建Vue CLI3项目
vue create program
? Please pick a preset:
> default (babel, eslint)
> Manually select features //手动选择功能
// 用哪个下载依赖
Pick the package manager to use when installing dependencies:
Use Yarn
> Use NPM
Vue CLI v3.0.0-alpha.5
✨ reating project in E:\git\note\my-project. // 创建项目
� Initializing git repository... // 初始化git库
⚙ Installing CLI plugins. This might take a while... // 安装脚手架插件
其实这个过程中还会判断你对npm/yarn源的连接速度,询问你是否切换至淘宝镜像
Your connection to the the default npm registry seems to be slow.
Use https://registry.npm.taobao.org for faster installation?
完成之后我们可以看到除node_modules之外的目录结构变成了
│ package-lock.json
│ package.json
├─public
│ favicon.ico
│ index.html
└─src
│ App.vue
│ main.js
├─assets
│ logo.png
└─components
HelloWorld.vue
? Check the features needed for your project: (Press to select, to toggle all, to invert selection)
>( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
( ) CSS Pre-processors
( ) Linter / Formatter --> ESLint
( ) Unit Testing
( ) E2E Testing
PS: 空格选中/反选 回车确认
看到可以自由组合现在所需的功能了。
创建的过程中会询问配置文件保存位置是config.js还是package.json,但是其中也是一些简单的配置
// 这些配置文件存放方式
? Where do you prefer placing config for Bable, PostCSS, ESLint, etc,> (Use arrow keys)
> In dedicated config files --> 单独存放到一个文件
In package.json
// 刚刚自定义的配置是否需要保存一个模板 (下次会在创建后出现)
// 可以删除的, 在.vuerc文件里
// 文件后面有rc (run command 运行终端的意思)
? Save this as a preset for future projects? (Y/n)
...
// 用哪个下载依赖
Pick the package manager to use when installing dependencies:
Use Yarn
> Use NPM
// public 文件夹
相当于Vue CLI2中的 static文件夹
存储不压缩的内容
// .babelrc 文件
ES语法转换
// .browserslistrc 文件
浏览器相关支持情况
// .gitigore 文件
git忽略文件
// .postcssrc.js 文件
CSS相关转换
https://www.bilibili.com/video/av59594689?p=97
// 启动本地服务器
vue ui
// 自定义配置
看视频吧~~
路由是一个网络工程里面的术语
路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动 — 维基百科
路由器提供了两种机制: 路由和转送
路由中有一个非常重要的概念叫路由表
https://www.bilibili.com/video/av59594689?p=100
早期的网站开发整个HTML页面是由服务器来渲染
上面的操作, 就是后端路由
后端路由的缺点
前后端分离阶段
前端渲染
浏览器中显示的网页中大部分内容, 都是由前端写的JS代码在浏览器中执行,最终渲染出来
SPA单页面富应用阶段
前端路由的核心
改变URL,但是页面不进行整体的刷新
如何实现
前端渲染:
指的是后端返回JSON数据,前端利用预先写的html模板,循环读取JSON数据,拼接字符串(es6的模板字符串特性大大减少了拼接字符串的的成本),并插入页面。
后端渲染:
前端请求,后端用后台模板引擎直接生成html,前端接受到数据之后,直接插入页面。
前端渲染与后端渲染对比:
页面呈现速度:快,受限于用户的带宽
流量消耗:少一点点(可以省去前端框架部分的代码)
可维护性:差(前后端东西放一起,掐架多年,早就在闹分手啦)
seo友好度:好
编码效率:低(这个跟不同的团队不同,可能不对)
页面呈现速度:主要受限于带宽和客户端机器的好坏,优化的好,可以逐步动态展开内容,感觉上会更快一点
流量消耗:多一点点(一个前端框架大概50KB)当然,有的用后端渲染的项目前端部分也有在用框架
可维护性:好,前后端分离,各施其职,代码一目明了。
SEO友好度:差,大量使用ajax,多数浏览器不能抓取ajax数据。
编码效率:高,前后端各自只做自己擅长的东西,后端最后只输出接口,不用管页面呈现,只要前后端人员能力不错,效率不会低
现在 Web 服务器不再处理任何业务,它接收到请求后,经过转换,发送给各个相关后端服务器,将各个后端服务器返回的,处理过的业务数据填入 HTML 模板,最后发送给浏览器。Web 服务器和后端服务器间,可以选用任何你觉得合适的通信手段,可以是 REST,可以是 RPC,选用什么样的通信手段,这是另一个议题了。
这样,前端人员和后端人员约定好接口后,前端人员彻底不用再关心业务处理是怎么回事,他只需要把界面做好就可以了,后端人员也不用再关系前端界面是什么样的,他只需要做好业务逻辑处理即可。服务的切离,代码管理,服务部署也都独立出来分别管理,系统的灵活性也获得了极大的提升。
注意,这不是个微服务架构,那是另外一个议题了
总结,任何系统架构设计,实际上是对组织结构在系统上进行映射,前后端分离,就是在对前端开发人员和后端开发人员的工作进行解耦,尽量减少他她们之间的交流成本,帮助他她们更能专注于自己擅长的工作。
最后是几个常见误解的说明:
不是,前后端分离里的前端不是浏览器,指的是生成 HTML 的那个服务,它可以是一个仅仅生成 HTML 的 Web 服务器,也可以是在浏览器中通过 JS 动态生成 HTML 的 单页应用。实践中,有实力的团队往往在实现前后端分离里时,前端选用 node 服务器,后端选用 C#、Java 等(排名不分先后)
不是,前后端分离是种架构模式,或者说是最佳实践。所谓模式就是大家这么用了觉得不错,你可以直接抄来用的固定套路。
看你团队和项目的情况,如果是短平快的小项目,真的没必要。如果是面向简历开发,那绝对在任何时候都应该使用前后端分离这种架构。
A. 什么是前端路由?
很重要的一点是页面不刷新,前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,每跳转到不同的URL都是使用前端的锚点路由. 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载
B. 什么是后端路由?
浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示, 返回不同的页面, 意味着浏览器会刷新页面,网速慢的话说不定屏幕全白再有新内容。后端路由的另外一个极大的问题就是 前后端不分离。
优点:分担了前端的压力,html和数据的拼接都是由服务器完成。
缺点:当项目十分庞大时,加大了服务器端的压力,同时在浏览器端不能输入制定的url路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。
C. 什么时候使用前端路由?
在单页面应用,大部分页面结构不变,只改变部分内容的使用
D. 前端路由有什么优点和缺点?
优点:
缺点:
目前前端流行的三大框架, 都有自己的路由实现:
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用
我们可以访问其官方网站对其进行学习: https://router.vuejs.org/zh/
vue-router是基于路由和组件的
路由用于设定访问路径, 将路径和组件映射起来.
在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
https://www.bilibili.com/video/av59594689?p=102
因为我们已经学习了webpack, 后续开发中我们主要是通过工程化的方式进行开发的.
在后续, 我们直接使用npm来安装路由即可
npm install vue-router --save
在src文件夹中创建router文件夹
在router文件夹中创建index.js文件
-- index.js 配置路由相关的信息
// 导入路由对
import Vue from ‘vue’
import VueRouter from ‘vue-router’
// 2. 通过Vue.use(插件)安装插件
Vue.use(VueRouter)
// 3. 创建路由实例--> VueRouter对象
// 3.2 把router抽象出来
const routes = [
]
const app = new VueRouter({
// 3.1 配置路由和组件之间的映射配置
router
})
// 4. 将router对象传入到Vue实例
export defualt router
-- main.js 文件
// 导入router
import router from './router'
// 挂载
new Vue({
el: '#app',
router,
render: h => h(App)
})
通过和
-- router - index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
import Home from '../components/Home'
import About from '../components/About'
Vue.use(VueRouter)
const routes = [
{
path: '/',
// 页面默认的时候, redirect重定向
redirect: './home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
const app = new VueRouter({
router,
// 改变模式 默认是hash模式(有#号)
mode: 'history'
})
export default router
-- App.vue文件
属性 to
用于指定跳转的路径
属性 tag
可以指定之后渲染成什么组件
// 会被渲染成一个元素
...
属性 replace
不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中
属性 active-class
设置active-class可以修改默认的名称
// home被选中状态
// 当对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class
// 修改默认的名称
...
// 每一个都添加太麻烦了, 可以在router - index.js中统一修改
const app = new VueRouter({
router,
mode: 'history',
linkActiveClass: 'active'
})
在路由跳转需要执行对应的js代码的时候, 可以使用
-- App.vue文件
某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
-- router - index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
import Home from '../components/Home'
import About from '../components/About'
import User from '../components/User'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: './home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/user/:userId',
component: User
}
]
const app = new VueRouter({
router,
mode: 'history'
})
export default router
-- App.vue文件
上面的代码照写
-- User.vue文件
{{userId}}
第二种写法
{{$route.params.userId}}
https://www.bilibili.com/video/av59594689?p=108
-- dist - static - js文件夹
app.xxxxxx - 当前应用程序开发的所有代码 (业务代码)
manifest.xxxxx - 为了打包的代码做底层支撑
vendor.xxxx - 提供第三方vue/vue-router/axios/bs
懒加载的方式
const Home = resolve => {
require.ensure(['../components/Home.vue'], () => {
resolve(require('../components/Home.vue'))
})
};
不推荐使用, 最早期的写法
const About = resolve => require(['../components/About.vue'], resolve);
const Home = () => import('../components/Home.vue')
最新的, 推荐
上面的案例增加路由软加载
-- router - index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
const Home = () => import('../components/Home.vue')
const About = () => import('../components/About.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: './home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
const app = new VueRouter({
router,
mode: 'history'
})
export default router
嵌套路由是一个很常见的功能
比如在home页面中, 我们希望通过/home/news和/home/message访问一些内容.
一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件.
-- router - index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
const Home = () => import('../components/Home.vue')
const HomeNews = () => import('../components/HomeNews.vue')
const About = () => import('../components/About.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: './home'
},
{
path: '/home',
component: Home,
children: {
{
path: 'news',
component: HomeNews
}
}
},
{
path: '/about',
component: About
}
]
const app = new VueRouter({
router,
mode: 'history'
})
export default router
-- Home.vue文件
我是首页
// 要写完整的路径
准备工作
为了演示传递参数, 我们这里再创建一个组件, 并且将其配置好
传递参数主要有两种类型: params和query
params的类型
就是动态路由的传递方式 ^^ 往上看
query的类型
-- index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
const Home = () => import('../components/Home.vue')
const User = () => import('../components/User.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: './home'
},
{
path: '/user',
component: User
}
]
const app = new VueRouter({
router,
mode: 'history'
})
export default router
-- User.vue文件
{{$route.query}}
{{$route.query.name}}
参考上面写的动态跳转的第二个方法
-- index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
const Home = () => import('../components/Home.vue')
const User = () => import('../components/User.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: './home'
},
{
path: '/user',
component: User
}
]
const app = new VueRouter({
router,
mode: 'history'
})
export default router
-- User.vue文件
route的由来–看源码
https://www.bilibili.com/video/av59594689?p=113
为什么使用导航守卫?
我们来考虑一个需求: 在一个SPA应用中, 如何改变网页的标题呢?
更好的办法-使用导航守卫
vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
导航守卫使用
我们可以利用beforeEach来完成标题的修改.
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题.
-- index.js文件
import Vue from ‘vue’
import VueRouter from ‘vue-router’
const Home = () => import('../components/Home.vue')
const User = () => import('../components/User.vue')
Vue.use(VueRouter)
const routes = [
{
path: '/home',
redirect: './home',
// 元数据 (描述数据的数据)
meta: {
title: '首页'
}
},
{
path: '/user',
component: User,
meta: {
title: '用户'
}
}
]
const app = new VueRouter({
router,
mode: 'history'
})
// 前置守卫(guard)
router.beforeEach((to, from, next) => {
// 从from跳转到to
window.document.title = to.matched[0].meta.title;
next();
})
export default router
导航钩子的三个参数解析:
导航守卫补充
// 后置守卫(guard)
router.afterEach((to, from) => {
})
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
它们有两个非常重要的属性:
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存
// 所有路径匹配到的视图组件都会被缓存
图片等静态资源引用路径,如果修改太麻烦了. 可以统一做配置.
-- build 文件夹 - webpack.base.config.js文件
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'views': resolve('src/views')
}
}
使用:
import xxx from './components/xxx'
-->
import xxx from 'components/xxx'
-->
在html增加需要加~号
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
状态管理到底是什么
管理什么状态呢
如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
单界面的状态管理
State:状态。(你姑且可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变
官方看图
View->Actions->State->View
多界面的状态管理
官方看图
需求: 一个数字, 二个按钮, 加减
import Vue from 'Vue'
import Vuex from 'Vuex'
Vue.use(Vuex)
const store = new Vuex.store({
state: {
count: 0
},
mutations: {
// state是默认的参数
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
}
})
export default store
import Vue from 'vue'
import App from './App'
impotr store from './store'
new Vue({
e;: '#app',
stroe,
render: h => h(App)
})
{{count}}
Vuex提出使用单一状态树, 即单一数据源
应用开发中
类似于computed
需要从store中获取一些state变异后的状态
案例: 获取学生年龄大于20的个数
const store = new Vuex.store({
state: {
students: [
{id: 100, name: 'aaa', age: 10},
{id: 100, name: 'bbb', age: 12},
{id: 100, name: 'ccc', age: 11},
{id: 100, name: 'ddd', age: 12},
]
},
getters: {
// state是默认参数
getAgesCount (state) {
return state.students.filter(s => s.age > 10)
}
}
})
computed: {
getAgesCount () {
return this.$store.state.students.filter(s => s.age > 10)
}
}
Getters作为参数和传递参数
需求; 如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写
需求2: getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
比如上面的案例中,我们希望根据ID获取用户的信息
const store = new Vuex.store({
state: {
students: [
{id: 100, name: 'aaa', age: 10},
{id: 100, name: 'bbb', age: 12},
{id: 100, name: 'ccc', age: 11},
{id: 100, name: 'ddd', age: 12},
]
},
getters: {
getAgesCount (state) {
return state.students.filter(s => s.age > 10)
},
// 需求1
getAgesCountLength(state, getters) {
return getters.getAgesCount.length
},
// 需求2
// 在调用的时候 传入了参数
getAges(state) {
return function (age) {
renturn stage.students.filter(s => s.id === id)
}
}
}
})
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分
Mutation定义方式
mutations: {
aaa(state) {
state.xx++
}
}
通过Mutation更新
bbb: function () {
this.$store.commit('aaa')
}
aaa(state, n) {
state.count -= n;
}
bbb: function () {
this.$store.commit('aaa', 1);
}
有很多参数需要传递.
通常会以对象的形式传递, 也就是payload是一个对象
aaa(state, payload) {
state.count -= payload.count;
}
bbb: function () {
this.$store.commit('aaa', {count: 0});
}
this.$store.commit({
type: 'aaa',
count: 100
})
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
必须遵守一些Vuex对应的规则:
// 以下的方式, 修改了数据, 但是界面未更新, 不是响应式
const store = new Vuex.store({
state: {
info: {
nameL: 'qiuqiu',
age: 18
}
},
mutations: {
upInfo(state, payload) {
state.info('sex') = payload.sex;
}
}
})
// 需要数据实时更新界面的写法
mutations: {
upInfo(state, payload) {
// 方法一: Vue.set()
Vue.set(state.info, 'sex', payload.sex);
// 方法二: 给info赋值一个新的对象
state.info = {...state.info, 'sex': payload,sex};
}
}
// 属性删除 Vue.delete(obj, 'newProp')
考虑下面的问题
如何避免上述的问题
在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型.
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
具体怎么做
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.
-- mutation-types.js
export const UPDATE_INFO = 'UPDATE_INFO'
-- store - index.js
import * as type from './mutation-types'
Vue.use(Vuex)
const store = new Vuxe.Store({
state: {
info: {
name: 'qiuqiu', age: 18
}
},
mutations: {
[UPDATE_INFO] (state, payload) {
state.info = {...state.info, 'sex': payload.sex}
}
}
})
-- App.vue
import {UPDATE_INFO} from './mutation-types'
export default {
name: 'App',
methods: {
this.$store.commit(UPDATE_INFO, {sex: '女'});
}
}
使用谷歌插件–devtools
不要再Mutation中进行异步操作.
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
Actions基本操作
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
addCount (state) {
state.count++
}
},
actions: {
// 默认参数 context 上下文
// 所用数据修改都要在mutations中, 所以在actions中异步后, 也要去mutations中修改
// 在mutations中修改, 才能被devtools监控到
addCount (context) {
context.commit('addCount');
}
}
})
methods: {
addCount () {
// 调用actions中的方法, 使用的是 dispatch
this.$store.dispatch('addCount');
})
}
Action返回的Promise
actions: {
addCount (context) {
return new Promise((resolve) => {
setTimout (() => {
context.commit('addCount');
resolve();
}, 1000)
})
}
}
methods: {
addCount () {
this.$store.dispatch('addCount').then(res => {
console.log('完成了接口的操作')
})
}
}
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
当应用变得非常复杂时,store对象就有可能变得相当臃肿.
为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutation、action、getters等
const moduleA = {
state: {...},
mutations: {...},
actions: {...},
getters: {...}
}
const moduleB = {
state: {...},
mutations: {...},
actions: {...},
getters: {...}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // mouduleA的state的状态
store.state.b // mouduleB的state的状态
Module局部状态
上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
我们在moduleA中添加state、mutations、getters
mutation和getters接收的第一个参数是局部状态对象
const moduleA = {
state: {
count: 0
},
mutations: {
addCount (state) {
state.count++;
}
},
getters: {
doubleCount (state) {
return state.count * 2;
}
}
}
const moduleB = {
}
const store = new Vuex.Store({
state: {
gCount: 111
},
modules: {
a: moduleA,
b: moduleB
}
})
注意:
虽然, 我们的doubleCount和increment都是定义在对象内部的.
但是在调用的时候, 依然是通过this.$store来直接调用的.
Actions的写法
接收一个context参数对象
局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
const muduleA = {
//...
actions: {
addCountSum ({state, commit, rootState}) {
if ((state.count + rootState.count) / 2 === 1) {
commit('addCount');
}
}
}
}
getters的写法
const moduleA = {
// ...
getters: {
sunRootCount (state, getters, rootState) {
return state.count + rootState.count;
}
}
}
components文件夹
store文件夹
- index.js -> 组装模块并导出 store的文件
- actions.js -> 根级别的 actions
- mutations.js -> 根级别的 mutations
- modules
- aaaa.js -> a模块
- bbbb.js -> b模块
选择一: 传统的Ajax是基于XMLHttpRequest(XHR)
为什么不用它呢?
非常好解释, 配置和调用方式等非常混乱.
编码起来看起来就非常蛋疼.
所以真实开发中很少直接使用, 而是使用jQuery-Ajax
选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax
相对于传统的Ajax非常好用.
为什么不选择它呢?
首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?
jQuery的代码1w+行.
Vue的代码才1w+行.
完全没有必要为了用网络请求就引用这个重量级的框架.
选择三: 官方在Vue1.x的时候, 推出了Vue-resource.不在维护了
Vue-resource的体积相对于jQuery小很多.
另外Vue-resource是官方推出的.
为什么不选择它呢?
在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.
那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.
对以后的项目开发和维护都存在很大的隐患.
选择四: 作者推荐了框架axios
优点:
在前端开发中, 我们一种常见的网络请求方式就是JSONP
使用JSONP最主要的原因往往是为了解决跨域访问的问题.
JSONP的原理是什么呢?
JSONP的核心在于通过script标签的src来帮助我们请求数据.
原因是我们的项目部署在domain1.com服务器上时, 是不能直接访问domain2.com服务器上的资料的.
这个时候, 我们利用script标签的src帮助我们去服务器请求到数据, 将数据当做一个javascript的函数来执行, 并且执行的过程中传入我们需要的json.
所以, 封装jsonp的核心就在于我们监听window上的jsonp进行回调时的名称.
JSONP如何封装呢
我们一起自己来封装一个处理JSONP的代码吧.
let count = 1;
export default function originPJSONP (option) {
// 1. 从传入的option中提取URL
const url = option.url;
// 2. 在body中添加script标签
const body = document.getElementsByTagName('body')[0];
const script = document.createElement('script);
// 3. 内部生成一个不重复的callback
const callback = 'jsonp' + count++
// 4. 监听window上的jsonp调用
return new Promise((resolve, reject) => {
try {
window[callback] = function (result) {
body.removeChild(script);
resolve(result);
}
const params = handleParam(option.data);
script.src = url + '?callback=' + callback + params;
body.appendChild(script);
} catch (e) {
body.removeChild(script);
reject(e);
}
})
}
funciton handleParam (data) {
let url = '';
for (let key in data) {
let value = data[key] !== undefinded ? data[key] :
url += '&${key}=${encodeURIComponent(value)}'
}
return url
}
支持多种请求方式:
使用axios.all, 可以放入多个请求的数组.
axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2
import axios from 'axios'
export default {
name: 'app',
created () {
// 发送并发请求
axios.all([axios.get('tp://000.000/xxxx'),
axios.get('tp://000.000/xxxx', {params: {type: 'sell', page: 1}})])
.then(axios.spread((rea1, res2) => {
console.log(res1);
console.log(res2);
}))
}
}
开发中,很多参数是固定的. 可以进行一些抽取, 也可以利用axiox的全局配置
axios.defaults.baseURL = 'tp://000.000'
axios.defaults.headers.post['Content-Type'] = 'xxxxx'
export default {
name: 'app',
created () {
// 提取全局的配置
axios.defaults.baseURL = 'http://000.000'
// 发送并发请求
axios.all([
axios.get('/xxxx'),
axios.get('/xxxxx', {
params: {
type: 'sell',
page: 1
}}
)
]).then(axios.spread((rea1, res2) => {
console.log(res1);
console.log(res2);
}))
}
}
请求地址 url: ‘/user’,
请求类型 method: ‘get’,
请根路径 baseURL: ‘http://www.mt.com/api’,
请求前的数据处理 transformRequest:[function(data){}],
请求后的数据处理 transformResponse: [function(data){}],
自定义的请求头 headers:{‘x-Requested-With’:‘XMLHttpRequest’},
URL查询对象 params:{ id: 12 },
查询对象序列化函数
paramsSerializer: function(params){ }
request body
data: { key: ‘aa’},
超时设置s timeout: 1000,
跨域是否带Token withCredentials: false,
自定义请求处理 adapter: function(resolve, reject, config){},
身份验证信息 auth: { uname: ‘’, pwd: ‘12’},
响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: ‘json’,
为什么要创建axios的实例
const inatancel = axios.create({
baseURL: 'http: //111.2222',
timeout: 5000
})
instancel({
url: '/home/mutidata'
}).then(res => {
console.log(res);
})
instancel({
url: '/home/index'
}).then(res => {
console.log(res);
})
const other = axios.create({
baseURL: 'http: //111.2222',
timeout: 5000,
// header: {}
})
https://www.bilibili.com/video/av59594689?p=145
简化的比较复杂 看视频吧
export function request (config) {
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://111.222.33.44',
timeout: 5000
})
// 2. 发送真正的网络请求
// 返回的 instancel(config)就是一个promise
return instancel(config)
}
import {request} from './network/request';
request({
url: '/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理
export function request (config) {
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://111.222.33.44',
timeout: 5000
})
// 2. axios拦截器
// config可以随便命名
instance.interceptiors.request.use(config => {
// 不返回, 调用的时候会进入err
return config
}, err => { // 发送都没成功
console.log(err);
})
// 3. 发送真正的网络请求
return instancel(config)
}
拦截器中做什么
instance.interceptors.resques.use(res => {
return config
}, err => {
console.log(err);
})
变量作用域: 交叠在什么范围内是可用
ES5之前因为if和for都没有块级作用域的概念, 在很多时候, 我们必须借助于function的作用域来解决应用外变量的问题
ES6中, 加入了let.let它有if和for的块级作用域
属性的增强写法
// ES5 对象字面量
const name = 'why';
const age = 18;
const obj = {
name: name,
age: age
}
// ES6
const obj = {
name,
age
}
函数的增强写法
// ES5
const obj = {
run: function () {
}
}
// ES6
const obj = {
run() {
}
}
// vue 编程范式: 声明式编程
{{message}}
const app = new Vue({
el: 'app', // 用户挂载要管理的元素
data: { // 定义数据
message: '你好啊'
}
})
// 元素JS的做法: 命令式编程
1. 创建div元素, 设置id属性
2. 定义一个变量叫message
3. 将message变量放在前面的div元素中显示
面向对象编程 (第一公民: 对象) or 函数式编程 (第一公民: 函数)
https://www.bilibili.com/video/av59594689?p=44
高阶函数 filter / map / reduce
案例详细讲解:
const nums = [10, 20, 111, 222, 444, 40, 50];
// 需求一: 取出所有小于的数字
let newNums = [];
for (let n of nums) {
if (n < 100) {
newNums.push(n);
}
}
// 需求二: 将所有小于100的数字进行转化 - 全部 *2
let newNums2 = [];
for (let n of newNums) {
newNums2.push(n * 2);
}
// 需求三: 将所有newNums2的数字相加, 得到最终的结果
let total = 0;
for (let n of newNums2) {
total += n;
}
filter - 回调函数有一个要求: 必须返回一个boolean值
true: 返回true时, 函数内部会自动将这次回调的n加入到新的数组中
false: 返回false时, 函数内部会过滤掉这次的n
const nums = [10, 20, 111, 222, 444, 40, 50];
// 需求一: filter
let newNums = nums.filter(functcion (n) {
return n < 100;
})
// 遍历出数组中的每一个是否<100来判断true / false, 然后为true加入新数组newNums中
map
// 需求二: map
let newNums2 = newNums.map(functcion (n) {
return n * 2;
})
reduce
作用: 对数组中所有的内容进行汇总
// reduce原理解释
let total = newNums2.reduce(function (preValue, n) {
return 100;
}, 0);
// reduce (参数一, 参数二) 参数二是第一个preValue值
// 第一次: preValue 0 n 20
// 第二次: preValue 100 n 40
// 第三次: preValue 100 n 80
// 第四次: preValue 100 n 100
// 需求三: reduce
let total = newNums2.reduce(function (preValue, n) {
return preValue + n;
}, 0);
// 第一次: preValue 0 n 20
// 第二次: preValue 20 n 40
// 第三次: preValue 60 n 80
// 第四次: preValue 140 n 100
const nums = [10, 20, 111, 222, 444, 40, 50];
let total = nums.filter(function (n) {
return n < 100;
}).map(function (n) {
return n * 2;
}).reduce(function (preValue, n) {
return preValue + n;
}, 0);
// 240
ES6中一个非常重要和好用的特性就是Promise
Promise做什么的
Promise是异步编程的一种解决方案
Promise什么时候用来处理异步事件
常见的场景: 一般在有异步操作-比如网络请求时
调接口的时候用Promise封装一遍, 成功的时候走then, 失败走catch函数里的代码
需求: 以定时器为例
setTimeout(() => {
console.log('哈哈哈');
}, 1000)
// 参数-> 函数
// resolve, reject本身也是函数
// 成功的时候
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000)
}).then(() => {
console.log('哈哈哈');
})
// 失败的时候
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
reject('error message');
}, 1000)
}).then(() => {
console.log('哈哈哈');
}).catch(error => {
console.log(error);
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
reject('error message');
}, 1000)
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
定时器异步事件解析
需求:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
// 1. 自己处理10行代码
console.log(res, '第一层的10行处理代码');
// 2. 对结果进行第一次的处理
return new Promise(resolve => {
resolve(res + '111');
})
}).then(res => {
console.log(res, '第二层的10行处理代码');
return new Promise(resolve => {
resolve(res + '222');
})
}).then(res +> {
console.log(res, '第三层的处理代码')
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 简写
return Promise.resolve(res+ '111');
}).then(res => {
console.log(res, '第二层的10行处理代码');
return Promise.resolve(res+ '222');
}).then(res +> {
console.log(res, '第三层的处理代码')
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 简写
return res+ '111';
}).then(res => {
console.log(res, '第二层的10行处理代码');
return res+ '222';
}).then(res +> {
console.log(res, '第三层的处理代码')
})
**需求: **
Promise.all({
new Promise((resolve, reject) => {
$ajax({
url: '',
success: function (data) {
resolve(data);
}
}),
$ajax({
url: '',
success: function (data) {
resolve(data);
}
})
}).then(results => {
console.log(results[0]); // aaa
console.log(results[1]); // bbb
...
})
})
异步操作后会有三种状态
需求: 以定时器为例
setTimeout(() => {
console.log('哈哈哈');
}, 1000)
// 参数-> 函数
// resolve, reject本身也是函数
// 成功的时候
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000)
}).then(() => {
console.log('哈哈哈');
})
// 失败的时候
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
reject('error message');
}, 1000)
}).then(() => {
console.log('哈哈哈');
}).catch(error => {
console.log(error);
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
reject('error message');
}, 1000)
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
定时器异步事件解析
需求:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
// 1. 自己处理10行代码
console.log(res, '第一层的10行处理代码');
// 2. 对结果进行第一次的处理
return new Promise(resolve => {
resolve(res + '111');
})
}).then(res => {
console.log(res, '第二层的10行处理代码');
return new Promise(resolve => {
resolve(res + '222');
})
}).then(res +> {
console.log(res, '第三层的处理代码')
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 简写
return Promise.resolve(res+ '111');
}).then(res => {
console.log(res, '第二层的10行处理代码');
return Promise.resolve(res+ '222');
}).then(res +> {
console.log(res, '第三层的处理代码')
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa');
}, 1000)
}).then(res => {
console.log(res, '第一层的10行处理代码');
// 简写
return res+ '111';
}).then(res => {
console.log(res, '第二层的10行处理代码');
return res+ '222';
}).then(res +> {
console.log(res, '第三层的处理代码')
})
**需求: **
Promise.all({
new Promise((resolve, reject) => {
$ajax({
url: '',
success: function (data) {
resolve(data);
}
}),
$ajax({
url: '',
success: function (data) {
resolve(data);
}
})
}).then(results => {
console.log(results[0]); // aaa
console.log(results[1]); // bbb
...
})
})
异步操作后会有三种状态