Promise构造函数:
多层回调函数的相互嵌套,就形成了回调地狱。牵一发而动全身,难以维护,可读性差。
为了解决回调地狱,ES6引入了Promise构造函数概念。
① Promise 是一个构造函数
我们可以创建 Promise 的实例 const p = new Promise()
new 出来的 Promise 实例对象,代表一个异步操作
② Promise.prototype 上包含一个 .then() 方法
每一次 new Promise() 构造函数得到的实例对象,
都可以通过原型链的方式访问到 .then() 方法,例如 p.then()
③ .then() 方法用来预先指定成功和失败的回调函数
p.then(成功的回调函数,失败的回调函数)
p.then(result => { }, error => { }) 两个回调函数作为参数
调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的
// promise读取文件
const fs = require("fs"); //node.js环境下// 封装读取文件方法
function read(fpath) {let p = new Promise(function (resolve, reject) {fs.readFile(fpath, "utf8", (err, dataStr) => {if (err) return reject(err);resolve(dataStr);});});return p;
}read("./files/11.txt").catch(function (err) {console.log(err.message);return 999}).then(function (data1) {console.log(data1);return read("./files/2.txt");}).then(function (data2) {console.log(data2);return read("./files/3.txt");}).then(function (data3) {console.log(data3);}).finally(function () {console.log("我执行了");});
------------------------------------------
ENOENT: no such file or directory, open 'E:\导航文件\资料文件\就业班课程内容\12 node资料\node-biji\07-promise\files\11.txt'
999
222
333
我执行了
.then 链式调用的优点: 解决了回调地狱的问题
.then 链式调用的缺点: 代码冗余、阅读性差、 不易理解
.catch 捕获错误
前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前
Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)注意:数组中 Promise 实例的顺序, 就是最终结果的顺序!
import thenFs from 'then-fs'
const promiseArr = [thenFs.readFile('./files/3.txt', 'utf8'),thenFs.readFile('./files/2.txt', 'utf8'),thenFs.readFile('./files/1.txt', 'utf8'),
]Promise.all(promiseArr).then(result => {console.log(result)
})
--------------------------------------------------
[ '333', '222', '111' ]
Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)
async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出 现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。
async-await是解决异步嵌套 终极方案。(让异步代码按特定顺序执行)
① 如果在 function 中使用了 await,则 function 必须被 async 修饰
② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
EventLoop:
为了防止耗时任务(异步任务)导致程序假死的情况—> eventloop机制
同步/异步 JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这 个过程是循环不断的,所以整个的这种运行机 制又称为 EventLoop(事件循环)
//javascript是一门运行在“客户端”,面向对象的,事件驱动的,单线程的编程语言//代码的执行机制://1.主线程的代码全部执行完毕时才会执行异步代码//2.异步代码:// setTimeout setInterval =>时间到了 // 以on开头的事件 =>事件触发了// ajax的回调函数 onreadystatechange =>数据从服务器返回了//前端异步的代码//1.setTimeout setInterval//2.以on开头的事件 如:btn.onclick onchange(输入框懒加载) onblur失去焦点//3.ajax的回调函数 onreadystatechange 状态值改变
为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:
① 同步任务(synchronous)又叫做非耗时任务,指的是在主线程上排队执行的那些任务,顺序执行只有前一个任务执行完毕,才能执行后一个任务
② 异步任务(asynchronous)又叫做耗时任务,异步任务由 JavaScript 委托给 宿主环境(浏览器或者node等)进行执行当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
① 宏任务(macrotask)异步 Ajax 请求、setTimeout、setInterval、文件操作其它宏任务...
② 微任务(microtask)Promise.then /.catch / .finallyprocess.nextTick其它微任务先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏
webpack:
概念:
webpack本身是, node的一个第三方模块包, 用于静态模块打包(module bundler)。
webpack默认只能处理js类型文件和 json文件,处理css需要 style-loader + css-loader 两个依赖包。
- 减少文件数量,压缩代码体积,提高加载速度
- 支持less/sass => css
- 支持ES6/7/8 => ES5 兼容性优雅降级
模块化开发规范(CommonJS / ES6):
node.js --> commonJS规范:
// nodejs - commonJS规范-规定了导出和导入方式// 导出 module.exports = {}
// 导入 const 变量 = require("模块标识")
ES6规范:
// 导出 export 或者 export default {}
// 导入 import 变量名 from '模块标识'
package.json中的dependencies和 devDependencies区别和作用:
- dependencies 别人使用你的包必须下载的依赖, 比如yarn add jquery
- devDependencies 开发你的包需要依赖的包, 比如yarn add webpack webpack-cli -D (-D 相当于 --save-dev) 开发依赖包
import语法浏览器支持性不好, 需要被webpack转换后, 再使用JS代码。
webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全(必会):
1. 初始化参数:从配置文件读取与合并参数,得出最终的参数
2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,开始执行编译
3. 确定入口:根据配置中的 entry 找出所有的入口文件
4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
npm下载—镜像源
npm config get registry 查看当前的下包镜像源
npm config set registry=https://registry.npmmirror.com/ 下载最新淘宝镜像源
nrm ls 查看所有可用的镜像源
vue:
创建一个新的vue项目:
1,vue create 文件名 //创建一个新的脚手架项目
注意: 项目名不能带大写字母, 中文和特殊符号
2,cd 文件名 //切换到新建的脚手架项目文件夹
3,npm run serve //运行vue项目
4,将vue.config.js文件创建到src文件夹并列位置 //关闭错误检查
module.exports = {devServer:{ //自定义服务器配置port: 3000, //端口open: true},lintOnSave: false //关闭eslint检查
}
什么是对象上的, 属性和方法:
let obj = { // 属性指的是a, b, c, d, e这些名字a: 10,b: [1, 2, 3],c: function(){},d () {},e: () => {} // 值是冒号:右边的值
}
this指向口诀
在function函数中, this默认指向当前函数的调用者 调用者.函数名()
在箭头函数中, this指向外层"函数"作用域this的值
@vue/cli 目录和代码分析
node_modules下都是下载的第三方包
public/index.html – 浏览器运行的网页
src/main.js – webpack打包的入口文件
src/App.vue – vue项目入口页面
package.json – 依赖包列表文件
关闭检查规则:
1,关闭所有规则:vue.config.js–>module.exports:{lintOnSave:false}
2,关闭某一个错误检查规则:package.json–>“eslintConfig”:{“rules”:{“no-unused-vars[错误代码]”:"off}}
插值表达式:
又叫(声明式渲染/文本插值)
{{ obj.age > 18 ? " 成年 " : " 未成年 " }} 里面放三元表达式
MVVM设计模式:
设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。
vue指令:
v-bind 把vue数据变量的值, 赋予给 Dom原生属性 上, 影响标签显示效果
<template><div><!-- vue指令 v-bind属性动态赋值 --><!-- v-bind:属性名="变量名" --><!-- 简写 :属性名="变量名" --><a v-bind:href="url">我是a标签</a><img :src="localImg"></div>
</template><script>
import imgObj from './assets/1.gif'
// 唯独js需要导入本地文件时,不能直接引用地址,必须先 import,因为webpack默认不会对动态路径主动打包。
export default {data(){return{url: 'http://www.baidu.com',localImg:imgObj}}
}
</script>
v-on 给标签绑定事件:
- 语法
- v-on:事件名=“要执行的少量代码”
- v-on:事件名=“methods中的函数”
- v-on:事件名=“methods中的函数(实参)”
- v-on:事件名=“methods中的函数(实参,$event)” —> 获取v-on事件处理函数的对象e
- 简写: v-on ----> @
- 注意:methods中的函数调用data里的变量需要添加 this
- @事件名.修饰符=“methods里函数”
- .stop - 阻止事件冒泡
- .prevent - 阻止默认行为
- .once - 程序运行期间, 只触发一次事件处理函数,事件一直可以触发
- @keyup.enter - 监测回车按键
- @keyup.esc - 监测返回按键…
<a @click="one" .......
<a @click="two(10, $event)" ....... // $event固定写法
<a @click.prevent="fn"
...
one(e){e.preventDefault()},
two(num, e){e.preventDefault()}
字符串转数组再转字符串
this.Str.split("").reverse().join("") === [...this.Str].reverse( ).join("") // 字符串的扩展运算符
🆘重点:v-model 把 <表单标签> 的value属性 和vue数据变量 , 双向绑定到一起
数据双向绑定原理:
<template>
<input v-bind:value="username" @input/change="username = $event.target.value">//input热加载 --- change懒加载(值改变,失去光标)
</template>
<script>
export default {data() {return {username: 'luojiajie'}}
}
</script>
// 特别注意: v-model, 在input[checkbox]的多选框状态
// 变量 为 非数组, 则绑定的是checked的属性(true/false) - 常用于: 单个绑定使用
// 变量 为 数组, 则绑定的是他们的value属性里的值 - 常用于: 收集勾选了哪些值
表单输入的value值是字符串型,所以:
v-model.修饰符=“vue数据变量”
- .number 以parseFloat转成数字类型
- .trim 去除首尾空白字符
- .lazy 在onchange(失去焦点并且内容改变)时 触发而非input时
v-text和v-html:
更新DOM对象的innerText/innerHTML
语法:
- v-text=“数据变量”
- v-html=“数据变量”
注意: 会覆盖插值表达式
v-text把值当成普通字符串显示, v-html把值当做html解析
v-show和v-if
- 语法:
- v-show=“vue变量/boolean”
- v-if=“vue变量/boolean”
- 原理
- v-show 用的 display:none 隐藏 (频繁切换使用,效率更高)
- v-if 直接从DOM树上移除—>v-for一般不与v-if搭配使用
- 高级
- v-else-if使用 v-else
v-for
列表渲染, 所在标签结构, 按照数据数量, 循环生成
-
语法
- v-for=“(值ele, 索引 i ) in 目标结构”
- v-for=“值 in 目标结构”
-
目标结构:
- 可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)
-
注意:
v-for的临时变量名不能用到v-for范围外
-
更新监测:data里数组变更—>页面更新 🆘异步的🆘
-
数组变更方法, 就会导致v-for更新, 页面更新
数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用新数组覆盖旧数组 或 **this.$set()**方法
$set的用法:
this.$set(this.arr数组,索引index,新值val) //更新 数组 索引为index的值为 新值valthis.$set(this.arr[index], "目标对象属性名", 新值val); //目标 元素对象 ,目标对象属性名,新值valthis.arr.splice(1, 0, "新来的") //索引为1的位置删除0个元素。新增‘新来的’元素 let newarr = arr.slice(1,2) //索引为1的位置截取到索引为2的元素,不包含索引为2,返回值为新数组 ,注意第二个参数是索引号拓展: arr.findIndex(obj=>obj.id===变量) //查找需要的对象元素的 索引 arr.find(obj=>obj.id===变量) //也是迭代方法,返回符合条件的 数组元素对象 arr.indexOf(元素值)--->//查找对应值的 索引号,未查到到返回-1
-
回流(重排): 页面元素属性发生变化,导致位置,大小等影响到其他元素时发生回流。当浏览器必须重新处理和绘制部分或全部页面时,回流就会发生
重绘: 不影响布局, 如颜色,字体系列等发生变化,只是标签页面发生变化, 重新绘制
注意: 回流(重排)必引发重绘, 重绘不一定引发回流(重排)
push()在数组末尾添加
pop()在数组末尾删除shift()在数组开头删除
unshift()在数组开头增加splice
reverse
sort((a,b)=>a-b) //接受一个比较函数作为参数,比较函数的参数为数组元素(对象),return三种情况-1,1,0
// function compare(value1,vakue2){if(value1.val<value2.val){return -1;} else if(value1.val>value2.val){return 1;} else {return 0}
}
arr.sort(compare);
构造函数new关键字创建一个实例对象执行4步骤:创建空对象并让this指向该空对象.
深入响应式原理:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property属性,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter,它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。每个组件实例都对应一个 watcher 侦听器/监听者 实例,它会在组件渲染成虚拟DOM的过程中把“接触”过的数据记录为依赖。之后当依赖项(数据层Model发生变化,数据变更时)的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。当 getter 触发(视图层View发生变化时)时,会通知 watcher,从而使视图层变化影响到数据层的变化。
vue.js采用数据劫持<—>结合发布-订阅者模式,通过Object.defineProperty()来劫持data中各个属性的setter、getter,在数据变动时,发布消息给订阅者,触发响应的监听回调。
响应式检测变化:
对于对象,Vue 无法检测 property (属性)的新增或移除。
对于数组:Vue 不能检测以下数组的变动:
- 当你利用索引直接更新一个数组项时,例如:
arr[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
arr.length = newLength
<script>
var person = {age: 0
}
person._age = person.age
Object.defineProperty(person, 'age', {set(val){console.log('触发了set方法', val)if(val>0 && val <=100){this._age = val}},get(){console.log('触发了get方法')return `年龄${this._age}`}
})// person.age //触发了get方法
person.age = 20
</script>
JS对象中的get和set方法:
🧨重点🧨取值器get 赋值器set
var person = {_age: 20,set age(val){console.log('set方法',val)if(val>0 && val <=100){this._age = val}},get age(){console.log('get方法')return `年龄${this._age}`}
}
person.age //get方法
person.age = 20
v-for的就地更新–虚拟Dom–diff算法:🧨重点🧨
目的:提高数据更新的性能。
App.vue文件中的template里写的标签, 都是模板, 都要被vue处理成虚拟DOM对象, 存于内存中,才会渲染显示到真实DOM页面上。
(虚拟DOM本质是个JS对象)因为真实的DOM属性好几百个,虚拟DOM只包含必要的属性。
当数据发生改变的时候,vue机制会在内存中生成新的虚拟DOM结构,和旧的虚拟DOM做对比,利用diff算法,找到不同,只更新变化的部分(重绘/回流)到页面 - 也叫打补丁。
diff算法:
情况1: 根元素变了, 删除旧虚拟DOM重建 ;
情况2: 根元素没变, 属性改变, 旧虚拟DOM元素复用, 就地更新属性;
情况3: 根元素未变, 属性未变, 子元素/内容改变:
3.1 无key – 就地更新;
3.2 有key – 按key比较 :
-
3.2.1 key值为索引 -->还是就地更新,key存在就复用此标签更新内容, 如果不存在就直接建立一个新的
-
3.2.2 key值为ID (key的值只能是唯一不重复的, 字符串或数值) --> 得到一个排序提示 v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 你需要用特殊 attribute
key
来提供一个排序提示。
动态class:
:class=“变量名称”;---->变量为类名
:class=“{类名:布尔值变量}” ---->变量为 true/false
:class=
"{rever类名: arr.includes(item), hide类名:!arr.includes(item)&&arr.length===3}"
// 多类名动态切换 arr.includes(item)判断数组中书否包含指定元素
动态style:
:style=“{ backgroundColor: ‘red’ }” —>'red’字符串
:style=“{ backgroundColor: colorStr }” —>colorStr 变量名
:style=“{ ‘background-color’: colorStr }”
vue过滤器:
过滤器只能用在, 插值表达式和v-bind表达式
- 全局定义 Vue.filter(“过滤器名”, (值) => {return “返回处理后的值”})
- 局部定义 filters: {过滤器名字: (值) => {return “返回处理后的值”}
定义全局过滤器:main.js里面定义,所有 . vue文件都能直接使用
Vue.filter(“reverse”, val => val.split(“”).reverse().join(“”))
定义局部过滤器:xxx.vue组件里面定义,只限当前 . vue文件使用
filters: { // 注意局部过滤器 filter's' 写在data并列位置 必须要有返回值filter11 :(val) {return moment(val).format("YYYY-MM-DD")},filter2 : (val,形参1,形参2...) => moment(val).format("YYYY-MM-DD"), //过滤器名:箭头函数}
------------------------------------------------------
// 全局过滤器接参数
Vue.filter("reverse", (val, s) => {return val.split("").reverse().join(s)
})
使用过滤器: {{ msg | reverse('|') }} / :title="msg | toUp | reverse('|')"
vue计算属性-computed:
必须写 return
computed: {"计算变量" () { // 计算属性: 一个变量的值, 需要用另外变量计算而得来return "值" // 函数内变量变化, 会自动重新计算结果返回}
}
-----------------------------------------------------
computed: { // 完整写法:当需要给计算属性变量赋值的时候"计算变量": {set(计算变量的新值val){ // 修改计算属性触发set方法},get() { // 使用获取计算属性触发get方法return "值"}}
}
vue计算属性—缓存功能:
计算属性根据依赖变量的结果缓存, 依赖变化–>重新计算结果存入缓存, 依赖未发生变化–>直接使用缓存值不再调用函数,比普通方法性能更高。
reduce的使用:
let arr = [{a:1,b:18},{a:2,b:10},{a:3,b:15},{a:4,b:20}]
// reduce接受两个参数
arr.reduce((sum, val) => sum += val.b,0)
vue侦听器-watch:
可以侦听 data / computed 属性值改变,当侦听的属性的属性值改变时,自动触发处理函数。
watch: {"要侦听的属性名": {immediate: true, // 立即执行(网页打开handler执行一次)deep: true, // 深度侦听复杂数据类型 属性值内 的变化handler (newVal, oldVal) { }}
}
vue组件:
一个自定义的标签,用来封装一个特定功能的可复用的 Vue 实例,包含结构样式行为,最大化的复用代码。
全局注册vue.component---------局部注册components
<style scoped> css属性只在当前组件中生效,为**当前组件**中的元素添加一个自定义属性名data-v-xxxxxxxx ,避免样式污染网页其他组件,组件里面的组件样式修改需要/deep/穿透
🆘🆘🆘每个组件的变量和值都是独立的---->组件通信:
1,父子通信传值 :$emit / @自定义事件名,,
2,无关系组件之间通信传值Vuex / eventBus,,
3,ref获取子组件也可以通信传值(this.$refs.子组件ref名.子组件数据变量名),,
4,this.$parent.父组件数据变量名(不推荐,维护困难,难以找到是谁改变了父组件的数据),,
5,this.$childen.子组件数据变量名,,
6,v-model传值(父组件传值给子组件,子组件使用并通知父组件修改),,.sync语法糖也可以
7,provide / inject 祖先组件向后代传值,,
8 , 路由跳转传参
在vue中需要遵循单向数据流原则 -->数据从父组件流向子组件
1. 父组件的数据发生了改变,子组件会**自动**跟着变
2. 子组件不能直接修改父组件传递过来的props数据 props是**只读**的
Vue规定props==里的变量, ==本身是只读的,props的值不能在子组件重新赋值。
EvenBus: 兄弟组件之间传值
创建一个第三方**js文件(**导入vue对象,并导出一个vue实例),相互传值的组件分别引入这个js文件,传出用eventBus. e m i t ,接收用 e v e n t B u s . emit,接收用eventBus. emit,接收用eventBus.on
解读:‘send’ ----->自定义事件名///
给子组件List 传值传的是一个复杂数据类型 ‘arr’ ,所以在子组件内部修改数据会使父组件数据改变,故未再子传父通信(eslint会报错)。
eventBus使用较少,因为子组件接收到需要更新数据过后,在该子组件直接修改数据又是Vue所不建议的,所以有需要传值给父组件。
Todos案例切换栏目显示:
tab栏目切换方法一:子组件多个点击事件处理函数 触发同一个主组件事件
tab栏目切换方法二:子组件事件代理 多个点击事件,然后触发主组件事件处理函数(全选)
Todos案例全选:
正规做法:
钩子函数:
解读:
编译模板阶段 –开始分析
1.Has el option? – 是否有el选项 – 检查要挂到哪里
没有. 调用$mount()手动指定挂载点
有, 继续检查template选项
2.template选项检查
有 - 编译template到render渲染函数中
无 – 编译el选项对应标签作为template(要渲染的模板)
axios:
特点
- 支持客户端发送Ajax请求
- 支持服务端Node.js发送请求
- 支持Promise相关用法
- 支持请求和响应的拦截器功能
- 自动转换JSON数据
- axios 底层还是原生js实现, 内部通过Promise封装的
扩展–对象合并:
let a = {username: 'zs'}let b = {age: 20}//es5当中let o = {username: a.username,age: b.age}// es6当中方法1// 通过扩展元素符let o = {...a,...b}// 方法2 通过Object.assign 浅拷贝let o = {}Object.assign(o, a, b) console.log(o)
axios基本使用:
axios({method: '请求方式', // get posturl: '请求地址',data: { // 拼接到请求体的参数, post请求的参数xxx: xxx,},params: { // 拼接到请求行的参数, get请求的参数 -->get没有请求体xxx: xxx }
}).then(res => { // axios()原地返回一个Promise对象console.log(res.data) // 后台返回的结果
}).catch(err => {console.log(err) // 后台报错返回
})
-----------------------------------------
axios.defaults.baseURL = "http://123.57.109.30:3006" // 添加全局基地址
扩展-Promise:
let p = new Promise((resolve, reject) => {setTimeout(() => {//resolve成功回调函数// resolve('123')//reject失败回调函数reject({ status: 500, message: '失败' })}, 1000)})p.then((data) => {console.log(data) ----> .then里面只写一个回调函数时表示成功的回调函数resolve}).catch(error => { ----> 若无catch捕捉错误信息,且.then里面只写一个回调函数时,无打印结果console.log(error)})
使用$refs(获取Dom/)获取子组件(就是一个自定义标签),实现组件通信的方法:
原生获取(不建议)或者 ref = ‘自定义name’ --> $refs.自定义name
Vue更新数据是一个异步操作(异步微任务),解决异步操作顺序执行的四种方法:
Vue 的 $nextTick 的原理是什么?
为什么需要 nextTick ,Vue 是异步修改 DOM 的(异步微任务)并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。nextTick 的原理正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。
VM.$nextTick 是一个微任务 (先微后宏)
第一次加载页面会触发哪几个钩子函数?
当页面第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数。
props: 定义校验方式
props: {background: String,color: {type: String,default: "#fff", //指定默认值},title: {type: String,required: true, //指定必填项},},-----------------props: ['title','list']
动态组件:
vue内置组件设置挂载点, 配合is属性, 设置要显示的组件名字 效果和v-if / v-else一样。
子路由出口 --> 等同于对动态组件的进一步封装。
<template> // 切换 登录/注册页面<div><button @click="comName = 'UserName'">账号密码填写</button><button @click="comName = 'UserInfo'">个人信息填写</button><p>下面显示注册组件-动态切换:</p> <div style="border: 1px solid red;"><keep-alive><component :is="comName"></component>//挂载点<component> :is="组件变量" 动态创建和销毁//挂载点<component> :is="'组件名'" 直接使用组件名时,里面为字符串🆘🆘🆘</keep-alive> //缓存,激活/非激活</div></div>
</template>
组件切换会导致组件被频繁销毁和重新创建, 性能不高 --> 组件缓存:
Vue内置的 组件 包起来要频繁切换的组件 或者 子路由出口 -->让动态组件缓存,不再创建和销毁,而是激活和非激活。
补充2个钩子方法名:
activated – 激活时触发
deactivated – 失去激活状态触发
组件插槽:
用于实现组件的内容分发,通过 slot 标签占位,可以接收到写在组件标签内的内容
- 组件内用默认内容占位
- 使用组件时夹着的地方, 传入标签替换slot
具名插槽:
//子组件不确定的地方 slot占位
<slot name="title"></slot>
--------------------------------------
//主组件:
<Pannel><template v-slot:title> // v-slot:组件名 可以简化成 #组件名 使用 //具名插槽必须在主组件中添加名字<h4>芙蓉楼送辛渐</h4></template>
</Pannel>
// slot的name属性起插槽名, 使用组件时, <template>配合#插槽名传入具体标签
🆘🆘作用域插槽:
在给插槽赋值时想在父组件环境下使用子组件里的变量
1,子组件, 在slot上绑定属性和子组件内的值 传值给父组件 :变量名1 = '子组件变量名'
2,父组件中,<template>配合v-slot="变量名2"(2.6.0之后) 注意是 '=' 接收参数
3,父组件标签内使用子组件中的变量。 变量名2.变量名1--->'子组件变量名'
插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。
- row ---->自定义变量名
具名插槽和作用域插槽一起使用:
自定义指令:
指令名 需要加 ” “ 引号
binding —> 一个对象,binding.value就是使用指令时传入的参数。例如:v-color=" ‘red’ "中的 ’red‘
扩展:开发依赖 / 生产(使用)依赖:
对象解构:
路由:
vue里面 路径和组件的映射关系—>使用场景:在一个页面中切换业务场景
网易云音乐—>单页面应用(SPA): 所有功能在一个html页面上实现
—>前端路由优缺点:整体不刷新页面,数据传递简单,体验好效率高;首次加载比较慢,不利于SEO搜索
@/ —> src绝对路径
使用步骤:
1,安装:
npm i vue-router
2,在main.js中导入路由:
import VueRouter from 'vue-router'
3,使用路由插件:
Vue.use(VueRouter)
4,创建路由规则数组:
const routes = [{path: "/find",component: Find},{path: "/my",component: My},...
]
5, 创建路由对象 - 传入规则:
const router = new VueRouter({routes
})
6, 关联到vue实例:
new Vue({router
})
7, App.vue中设置挂载点:
<--当url的hash值路径切换,显示规则里对应的组件到这里-->
<router-view></router-view>
vue路由 - 声明式导航:
可用全局组件router-link来替代a标签,to 来代替 href:
<a href="#/find">a链接</a>
<router-link to="/find">发现音乐</router-link>
//声明式导航高亮的功能(自带类名) .router-link-active激活时自动添加类名<span @click="btn('/my' 或者 'My')">我的音乐</span> // js编程式导航,name
声明式导航 - 跳转传参:
传参:跳转路径上拼接 查询字符串 ?key=value 子组件页面用 this.$route.query.key 取值传参:跳转路径上拼接 /值 (提前在路由规则/path/:key) 子组件页面用 this.$route.params.key 取值
vue路由 - 重定向:
例如: 网页默认打开, 匹配路由"/“, 强制切换到”/find"上
const routes = [{path: "/", // 默认hash值路径redirect: "/find" // 重定向到/find 路径上,重新匹配当前规则数组// 浏览器url中#后的路径被改变成/find-重新匹配数组规则},......// 404 找不到路径:// 1.创建NotFound组件页面 2.引入到main.js 3.在规则数组最后(规则是从前往后逐个比较path){path: "*",component: NotFound }
]
模式设置:
目标: 修改路由在地址栏的模式
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
模式文档
main.js
const router = new VueRouter({routes,mode: "history" // 打包上线后需要后台支持, 默认模式是hash
})
vue路由 - 编程式导航:
<template><div><div class="footer_wrap"><span @click="btn('/find'或者'Find')">发现音乐</span> //name在router/index.js定义<span @click="oneBtn">朋友-小传</span> //或者main.js路由中定义</div><div class="top"><router-view></router-view></div></div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名 接收
// 方式2:
// query => $route.query.参数名 接收
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由//当前页面点击跳转当前页面,会触发警告冗余导航的问题
export default {methods: {btn(targetPath 或者 targetName){this.$router.push({// path: targetPath,name: targetName // 虽然用name跳转, 但是url的hash值还是切换path路径值}) // 方便修改: name路由名(在页面上看不见随便定义)}, // path可以在url的hash值看到(尽量符合组内规范)oneBtn(){this.$router.push({name: 'Part',//path: '/Part'params: { // path会自动忽略paramsusername: '小传'}//query: {// username: '小传'// }})}}
};
</script>
传递是this.$router 路由实例对象 上面添加跳转新的路由
接收$route. 具体某个路由对象里面接收参数
vue路由 - 路由嵌套:
main.js– 配置2级路由
一级路由path从/开始定义
二级路由往后path直接写名字, 无需/开头
嵌套路由在上级路由的children数组里编写路由信息对象
const routes = [// ...省略其他{path: "/find", //--->注意 /name: "Find",component: Find,children: [ //--->二级路由{path: "recommend", //--->注意 直接写路径 无 /component: Recommend},{path: "ranking",component: Ranking}]}// ...省略其他
]
声明式导航类名区别:
路由跳转传参总结
跳转方法 | 传参位置 | 路由规则 | 接收 |
---|---|---|---|
/path?key=value | 无特殊 | $route.query.key | |
/path/值 | /path/:key | $route.params.key | |
this.$router.push({path: “/path”, query: {key: value}}) | query的对象 | 无特殊 | $route.query.key |
this.$router.push({name: “com”, params: {key: value}) | params的对象 | 路由规则需要name属性 | $route.params.key(注意,这种在内存中保存) |
1、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面
2、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
3、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数
4、格外注意: 使用path会自动忽略params
全局前置守卫:
// 目标: 路由守卫
// 场景: 当你要对路由权限判断时
// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
// 参数1: 要跳转到的路由 (路由对象信息) 目标
// 参数2: 从哪里跳转的路由 (路由对象信息) 来源
// 参数3: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")
// 注意: 如果不调用next, 页面留在原地// 例子: 判断用户是否登录, 是否决定去"我的音乐"/my
const isLogin = true; // 登录状态(未登录)
router.beforeEach((to, from, next) => {if (to.path === "/my" && isLogin === false) {alert("请登录")next(false) // 阻止路由跳转} else {next() // 正常放行}
})
Vant 组件库:
1,全局自动按需引入vant组件:(推荐方式)
plugins: [ // babel.config.js 配置文件["import",{libraryName: "vant",libraryDirectory: "es",style: true,},"vant",],],
2,手动按需: 3,全局全部引入组件:(,体积稍大,不推荐)
节流和防抖:
// 节流: (减缓事件执行速度,坦克大战发射子弹)let timer = null;document.querySelector(".jl").addEventListener("click", () => {if (!timer) { // 定时器为空,则进入定时器timer = setTimeout(() => {console.log("打印了一次");timer = null; // 时间到了再将定时器timer清空,清空之前无法再次进入定时器// 这儿必须要用timer=null,,,clearTimeout(timer)不能清除自身}, 1000);}});// 防抖:(多次事件触发只执行最后一次动作,英雄回城)document.querySelector(".fd").addEventListener("click", () => {clearTimeout(timer); // 进入定时器之前先清空所有定时器timer = setTimeout(() => {console.log("打印了一次");}, 1000); // 每次触发函数体执行之前都清空timer,保证只有最后一次生效});
Vant 动态设置 REM 基准值:
rem相对于html根字体font-size–>通过媒体查询(参考bootstrap)@media–>lib-flexible 自动设置HTML根标签字体大小(默认屏幕划分10等份)
npm i amfe-flexible / flexible
开发依赖包 -->postcss-pxtorem 是一款 PostCSS 插件,用于将 px 单位转化为 rem 单位
npm i postcss-pxtorem@5.1.1 -D 注意版本,该插件不能转换行内样式中的 px
http和https:
- https 协议:安全,对请求报文和响应报文做加密
网络传输的安全性;对称加密和非对称加密;公钥和私钥
对称加密:
加解密使用 相同 秘钥 高效,适用于大量数据的加密场景 算法公开,安全性取决于秘钥大小,但秘钥越大效率越低,需要权衡在安全和效率中做权衡。缺点:算法本身安全,但使用场景不够安全(秘钥传送安全问题),因为解密和加密都是同一个秘钥。
非对称加密:
使用 匹配的一对密钥 来分别
进行加密和解密。公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
- 注意:公钥加密的数据 只能 用 对应的私钥解密,同理,私钥加密的数据 只能用 对应的公钥解密。公钥其实是根据私钥生成的
- 用法概要:
- 加密:对数据做加密
- 签名:证明数据是谁发的
安全性高,但加解密复杂,效率低,耗时长。
公钥加密:
用来针对互联网上加密数据传递,
补充:如果要在网络上相互发送密文,可以让对方也发对方的公钥过来,用对方的公钥来加密。
私钥签名:
目的是为了将明文公布给别人,同时证明是自己发的;可以防止明文在传送给别人时被篡改。
第一步: James 用 James的私钥 对明文(我同意)的hash值进行加密,把加密后的密文(签名)和明文(我同意)一起发给 Linda。
第二步: Linda 用 James的公钥解密密文(签名),解密后的明文hash值 和 接收到的明文(我同意)的hash值进行对比,如果一样则是 James 发的。
https协议:
非对称加密 -> 公钥传送安全问题–>使用 权威认证机构(CA) 来证明 网站的公钥没被篡改 -->https
使用流程:
- 客户端 操作系统内置 权威认证机构(CA) 的机构证书X
- 服务器A 找认证机构生成认证证书A(服务器的域名,证书有效期,证书颁发机构,服务器自己的公钥A等),并保存在服务器A中
- 客户端浏览器 请求获取 服务器A证书
- 客户端浏览器 用 机构证书X 解密 服务器A证书
- 解密成功:获取 服务器的公钥A(只要解密成功,就说明 是 机构认证的)
- 解密失败:认证失败
- 浏览器 将自己的秘钥 发给服务器
- 使用对称加密算法 生成 会话密钥B
- 使用服务器A公钥对 会话密钥B做加密,并发给 服务器
- 浏览器和服务器 使用会话秘钥B来对 请求报文 和 响应报文 做加密
function fn() {console.log (setTimeout(() => {console.log(456);}, 2000))}fn() // 1 456 定时器id
vuex:
vuex是采用集中式管理组件依赖的共享数据的一个工具,可以解决不同组件数据共享问题。
结论
vuex存储数据时存储在内存中,刷新/F5会清除内存,从而导致vuex数据丢失。常见的保存token最保险的方式时在vuex和本地存储都存一下。
- 修改state状态/数据必须通过**
mutations
** mutations
只能执行同步代码,类似ajax,定时器之类的代码不能在mutations中执行- 执行异步代码/同步代码,要通过actions,然后将数据提交给mutations才可以完成。actions函数返回值为promise对象,一般await修饰。
- state的状态即共享数据可以在组件中引用
- 组件中可以调用action,用dispatch
npm i vuex -S // 运行依赖
在main.js中 import Vuex from 'vuex'Vue.use(Vuex) // 调用了 vuex中的 一个install方法,安装注册const store = new Vuex.Store({...配置项}) /配置state mutations actions..store挂载在Vue实例上
state: // 🆘🆘组件计算属性computed里面使用
定义state:const store = new Vuex.Store({state: {count: 0,list: [1,5,6,8,7,9]}, // ... mutations actions getters
})组件中获取公共数据state: 方法1原始导入:this.$store.state.变量名 直接使用或者把state中数据,定义在组件内的计算属性中 再使用count变量computed: {count () {return this.$store.state.count}}方法2辅助函数:import { mapState } from 'vuex' computed: { ...mapState(['count'])}
mutations: // 🆘🆘组件方法methods里面使用
state数据的修改只能通过mutations:(mutations是一个对象,对象中存放修改state的方法)1,定义: mutations: {// 方法里参数 第一个参数是当前store的state属性// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷addCount (state,payload) {state.变量 += payload}}, 2,组件中调用 原始方法:methods: { // 调用方法addCount () {// 调用store中的mutations 提交给muations// commit('muations名称', 2)this.$store.commit('addCount', 10) // 直接调用mutations里面的方法addCount}}3,辅助函数方法mapMutations:import { mapMutations } from 'vuex'methods: {...mapMutations(['addCount']) === addCount(){this.$store.commit('addCount')}}actions: // 🆘🆘组件方法methods里面使用1,定义actionsactions: {// 获取异步的数据 context表示当前的store的实例 可以通过 context.state 获取状态/数据 也可以通过context.commit 来提交/触发mutations, 也可以 context.diapatch调用其他的actiongetAsyncCount (context,payload) {setTimeout(function(){// 一秒钟之后 要给一个数 去修改statecontext.commit('addCount', payload)}, 1000)}} 2,原始传参调用:addAsyncCount () {this.$store.dispatch('getAsyncCount', 123)}3,辅助函数 mapActions 调用:import { mapActions } from 'vuex'
methods: {...mapActions(['getAsyncCount'])
}
<button @click="getAsyncCount(payload)">+异步</button>getters: // 🆘🆘组件计算属性computed里面使用
1,定义getters: --->相当于 vuex 中的计算属性getters: {// getters函数的 第一个参数 是 state // 必须要有 返回值 returnfilterList: state => state.list.filter(item => item > 5)}
2,原始方法调用:<div>{{ $store.getters.filterList }}</div>
3,辅助函数 mapGetters:import { mapGetters } from 'vuex'computed: { ...mapGetters(['filterList'])}
vuex的模块化:
const store = new Vuex.Store({modules: {user: { //模块一:usernamespaced: true,state: {token: '12345'},mutations: {// 这里的state表示的是user的stateupdateToken (state) {state.token = 678910}}},setting: { //模块二:settingstate: {name: 'Vuex实例'}}})
//请注意: 此时要获取子模块的状态数据 需要通过 $store.state.模块名称.属性名 来获取
模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
user模块还是setting模块,它的 action、mutation 和 getter 其实并没有区分,都可以直接通过全局的方式调用。
但是,如果我们想保证内部模块的高封闭性,我们可以采用namespaced命名空间 来进行设置。
使用带命名空间的模块 actions/mutations
方案1:直接调用-带上模块的属性名路径
test () {this.$store.dispatch('user/updateToken') // 直接调用方法
}
方案2:辅助函数-带上模块的 “属性名/方法名”
methods: {...mapMutations(['user/updateToken']),test () {this['user/updateToken']()}}<button @click="test">修改token</button>
方案3: createNamespacedHelpers 创建基于某个命名空间辅助函数
import { mapGetters, createNamespacedHelpers } from 'vuex'
const { mapMutations } = createNamespacedHelpers('user')
<button @click="updateToken">修改token2</button>
请求拦截器:
// 请求工具:请求拦截器(在请求头添加token)
request.interceptors.request.use(function (config) {// Do something before request is sent// config :本次 网络请求 的配置对象 请求地址/请求头/请求体...相关数据// config 里面有一个属性:headers// console.log(config)// 拦截检查本地vuex数据中是否有token,if (store.state.token) {config.headers.Authorization = `Bearer ${store.state.token.token}`}return config // 返回配置对象
}, function (error) {// Do something with request errorreturn Promise.reject(error)
})
响应拦截器
// 添加响应拦截器
// 处理 transformResponse 处理之后数据
request.interceptors.response.use(function (response) {console.log(response)// 对响应数据做点什么??return response
}, function (error) {// 对响应错误做点什么??return Promise.reject(error)
})
记录滚动位置
给每个列表设置独自的滚动区域,而现在滚动区域是公共的body
.artcile-list {height: 79vh;overflow: auto;
}
今日头条切换页面缓存位置原理:组件缓存 : include=” “
目标:文章列表缓存 所有子组件都缓存下来,(无需再次网络请求,组件不销毁,占内存)<!-- <keep-alive :include="Layout"> --><router-view></router-view> 子路由出口<!-- </keep-alive> -->
// :include="需要缓存的组件名name" --> "name1,name2,name3..."
1,来回切换的两个组件的上一级组件需要用标签包裹子路由出口,让需要缓存的那个组件缓存起来;'上一级组件’被销毁,则子组件缓存丢失。
2,需要缓存的子组件中:
// 变为激活状态activated () {// 给.article-list元素(子组件的根元素标签,template标签下一级)赋值滚动条的位置数据this.$refs.listRoot.scrollTop = localStorage.getItem('scrollTop')},// 变为未激活状态deactivated () {},mounted () {// 监听滚动条的滚动,保存滚动条的位置this.$refs.listRoot.onscroll = function (e) {localStorage.setItem('scrollTop', e.target.scrollTop)}} // 'scrollTop' 用和对应组件的特殊标识变量保存到本地,可以实现该子组件所有页面都独立缓存下来
------------------------------------------------------------------------
<style lang='less' scoped>
.article-list {// <!-- 目标:文章列表滚动条缓存 -->// 🆘🆘🆘给每个列表设置独自的滚动区域,而现在滚动区域是公共的bodyoverflow-y: auto;height: 93vh;
}
</style>
大数字精度失真问题:
后端返回 JSON 字符串格式的数据,经过 axios 转换成数据对象,但是 JavaScript 能够准确表示的整数范围在-2^53
到2^53
之间(不含两个端点),超出安全整数范围的 id 无法精确表示。
npm i json-bigint //第三方包处理大数字的包
// https://www.npmjs.com/package/json-bigintconst jsonStr = '{ "art_id": 1245953273786007552 }'
// JSONBig 可以处理数据中超出 JavaScript 安全整数范围的问题
console.log(JSONBig.parse(jsonStr)) // 把 JSON 格式的字符串转为 JavaScript 对象console.log(JSONBig.stringify(JSONBig.parse(jsonStr))) // 把 JavaScript 对象 转为 JSON 格式的字符串转
通过 Axios 请求得到的数据都是 Axios 处理(JSON.parse)之后的,我们应该在 Axios 执行处理之前手动使用 json-bigint 来解析处理。Axios 提供了自定义处理原始后端返回数据的 API:transformResponse
。
// 请求拦截器--》transformRequest--》transformResponse--》响应拦截器
import axios from 'axios'
import jsonBig from 'json-bigint'const request = axios.create({baseURL: 'http://ttapi.research.itcast.cn/', // 接口基础路径// transformResponse 允许自定义后端返回的原始响应数据(字符串)transformResponse: [function (data) {try {// 如果转换成功则返回转换的数据结果return jsonBig.parse(data)} catch (err) {// 如果转换失败,则包装为统一数据格式并返回return {data}}}]
})export default request
后端返回文章正文样式调整:
github-markdown-css 样式文件下载到项目中
1,导入 import './github-markdown.css'
2,添加类名 文章内容最外层标签 添加 'markdown-body'类名
3,'postcss-pxtorem'配置文件中配置忽略文件 exclude: 'github-markdown'
多层if-else判断:
标签配合v-if v-else
样式引入 css less引入:
数据在子组件,父组件需要获取并修改:
// 方式一:在子组件props里面定义评论列表list,给定默认值,非必须,父组件通过父传子的方式也就获得子组件评论数据listprops: {commentList: { // 评论列表type: Array,default: function () {return []}}}父组件:this.commentList.unshift(val.new_obj)// 方式二:通过ref获取子组件元素,再获取到子组件内的评论数组listthis.$refs.Aclist.list.unshift(val.new_obj)
provide / inject 祖先组件向后代传值:
一般越级传递,父子之间还是建议用
- 类型:
- provide:
Object | () => Object
给所有后代组件提供数据 - inject:
Array<string> | { [key: string]: string | Symbol | Object }
一个字符串数组,或一个对象,对象的 key 是本地的绑定名。
- provide:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。提示:provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
// 父级组件提供 'foo'
var Provider = {provide: {foo: this.commentId},data() {return: {commentId: "25478965"}}// ...
}// 子组件注入 'foo'
var Child = {inject: ['foo'],// inject: { 或者写成一个对象,可以来校验传递的数据// foo: {// type: [Number,String,Object],// defalt: Null 默认值,非必须// }// }
}created () {console.log(this.foo) // => "25478965"}// ...
}
v-model父子组件数据双向绑定:
🆘🆘🆘用v-model传递数据,监听同一数据。使用model属性,prop将数据名由value改为想要的,event将事件名input改为想要的名字。
Vant组件弹出层:
弹出层是懒渲染的,只有在第一次展示的时候才会渲染里面的内容。强制渲染可以采用v-if的方式。
动态路由props传参两种实现方式:
//点击 articleList里面 articleItem 带参数跳转
<van-cell :to="'/article/' + article.art_id">...</van-cell>
<van-cell :to="`/article/${article.art_id}`">...</van-cell>
<van-cell :to="{name:'article',params:{articleId:article.art_id}}">...</van-cell>
//路由配置里面:
const routes = [{其他路径 ...},{// 🆘🆘文章动态路由:path: '/article/:articleId',name: 'article',component: () => import('@/views/article/index.vue'),// 🆘🆘方法一:开启路由传参,将路由动态参数:articleId映射到组件的 props中 推荐这种做法props: true},其他路径 ...
]
//子组件中props正常接收:
export default {props: {// 动态路由传值。articleId: {type: [Number, String, Object],required: true}},// 方法二:data(){return articleId: this.$route.params.articleId }
}
git操作:
1- 创建远端仓库XXXXX
2- 初始化仓库
前提:如果项目当中没有.git
文件夹
命令: git init
vscode: 切换到源代码管理面板,点击初始化,选择项目
3- 提交代码到暂存区
命令:git add .
vscode:在更改
添加+
4- 提交代码到本地仓库
命令: git commit -m '第一次提交'
vscode:在输入框输入注释,点击ctrl+enter
5- 推送到远程仓库
命令: git push origin master
,git push https://gitee.com/huangjinlin/xxxxx.git master
vsocde:点击...
,点击推送
注意第一次需要填写url
,别名origin
vue_cli本地代理解决跨域问题:
vue-cli的配置文件即**vue.config.js
**,这里有我们需要的 代理选项
module.exports = {devServer: {// 代理配置proxy: {// 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制// localhost:8888/api/abc => 代理给另一个服务器// 本地的前端 =》 本地的后端 =》 代理我们向另一个服务器发请求 (行得通)// 本地的前端 =》 另外一个服务器发请求 (跨域 行不通)'/api': {target: 'www.baidu.com', // 我们要代理的地址changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求// 按需 路径重写pathRewrite: {// 重新路由 localhost:8888/api/login => www.baidu.com/api/login'^/api': '' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做 }},}}
}
当加载的图片报错无法找到时,设置默认图片的方法:
方式一/二:利用原生方法 / vue中 this.$refs. ,获取图片的DOM元素后,通过onerror事件监听报错的图片,回调函数配置默认图片的src地址;
方式三:通过创建自定义指令,设置自动监听onerror事件,回调函数配置默认图片的src地址。
vue项目中package.json与package-lock.json作用及区别:
package-lock.json文件内保存了 node_modules中所有包的信息,包含着这些包的名称、版本号、下载地址。这样带来好处是,如果重新 npm install 的时候,就无需逐个分析包的依赖项,因此会大大加快安装速度。lock代表的是“锁定”的意思,用来锁定当前开发使用的版本号,防止npm install的时候自动更新到了更新版本。因为新版本可能替换掉老的api,导致之前的代码报错。
package.json 文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑package-lock.json文件应运而生,所以当你每次安装一个依赖的时候就锁定在你安装的这个版本。当你执行npm install的时候,node从package.json文件读取模块名称,从package-lock.json文件中获取版本号,然后进行下载或者更新。
路由规则里面的hidden:
path: ’ ’ // 当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由
redirect [重定向]:
在父子嵌套结构中,父级的redirect指向子级children里的path
meta [元数据]:
其实就是存储数据的对象 我们可以在这里放置一些信息可以作用判断[用户是否已登陆]
可以通过meta值,展示[面包屑]
hidden 是否需要展示该路由[是否渲染该路由入口] 布尔值
functional为true,表示该组件为一个函数式组件:
函数式组件: 没有data状态,没有响应式数据,只会接收props属性, 没有this, 他就是一个函数
具名插槽的使用:
批量注册全局组件:
箭头函数的坑:
(a)=>{return b} a=>b
// 1,不加{}自带return, 加{}必须要使用return
// 2,当需要箭头函数返回一个对象的时候,必须要()包裹{},否则{}会被当成箭头函数函数体的一部分
(a)=>({a:boo,b:baz})
v-for和v-if :
v-for
与v-if
作用在不同标签时候,v-if包裹v-for时候,是先进行判断,再进行列表的渲染 ;
最终结论:v-for
优先级比v-if
高
- 永远不要把
v-if
和v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断) - 如果避免出现这种情况,则在外层嵌套
template
(页面渲染不生成dom
节点),在这一层进行v-if判断,然后在内部进行v-for循环 - 如果条件出现在循环内部,可提前过滤掉那些不需要显示的项,再循环。
data属性是一个函数而不是一个对象:
- 根实例是单例对象时(其他组件为函数式组件),
data
可以是对象也可以是函数,不会产生数据污染情况 - 组件实例对象
data
必须为函数,目的是为了防止多个组件实例对象之间共用一个data
,产生数据污染。采用函数的形式,initData
时会将其作为工厂函数都会返回全新data
对象
一个项目有多个组件,每个组件都是一个单独的Vue实例对象,当多个实例对象打包合并时,对象式数据变量会相互影响/污染,所以不能是对象。
创建元素的三种方式:
document.write('<div>123</div>');inner.innerHTML += '<a href="#">百度</a>'var a = document.createElement('a');create.appendChild(a);
innerHTML字符串拼接方式(效率低)–> document.body.innerHTML += ‘
createElement方式(效率一般)
innerHTML数组方式(效率高)–>array.push( ) array.join(‘’);
Vue3:
reactive函数:
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
<script>//源数据let person = {name: '张三',age: 18}//模拟Vue3中实现响应式const p = new Proxy(person, {//有人读取p的某个属性时调用get(target, propName) {console.log(target, propName); // {name: '张三', age: 18} 'name'console.log(`有人读取了p身上的${propName}属性`);// return target[propName]return Reflect.get(target, propName)},//有人修改p的某个属性、或给p追加某个属性时调用set(target, propName, value) {console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)// target[propName] = valueReflect.set(target, propName, value)},//有人删除p的某个属性时调用deleteProperty(target, propName) {console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)// delete target[propName]return Reflect.deleteProperty(target, propName)}})</script>
扁平数据转树型结构:
/** ** 将列表型的数据转化成树形数据 => 递归算法 => 自身调用自身 => 一定条件不能一样, 否则就会死循环* 遍历树形 有一个重点 要先找一个头儿* ***/
export function tranListToTreeData(list, rootValue) {var arr = []list.forEach(item => {if (item.pid === rootValue) {// 找到之后 就要去找 item 下面有没有子节点const children = tranListToTreeData(list, item.id)if (children.length) {// 如果children的长度大于0 说明找到了子节点item.children = children}arr.push(item) // 将内容加入到数组中}})return arr
}
.sync语法糖:
父组件给子组件传参,子组件使用并修改
// 父组件:
<AddDept v-if="showDialog" :show-dialog.sync="showDialog" ></AddDept>// 子组件关闭弹层:
this.$emit('update:showDialog', false)// 或者直接使用 this.$parent.showDialog = false 注意父级组件嵌套关系
some和every对空数组进行遍历时:
//对空数组使用some和every结果与预期不符合(也不符合逻辑)
[].some(item=>item===true) //false
[].every(item=>item===true) //true //every() 不会对空数组进行检测 若收到一个空数组,此方法在一切情况下都会返回 true//解决办法:
//在使用some和every之前,先对数组进行判断是否为空
权限管理:
1,登录权限控制:
依靠 Token 存入vueX 存入本地cookies
2,菜单权限管理:
router 路由守卫 里面执行:
- 获取个人角色信息里面的页面标识menus ;
- 过滤动态路由(往vueX里面加入动态路由,控制菜单栏显示隐藏);
- addRoutes往静态路由里面合并动态路由以及 * 重定向到/404 ;
- 再跳转一次 next(to.path)
使用Mixin技术注入全局vue方法:
3,具体权限点points控制显示与隐藏:
常用于权限点的控制,控制页面具体按钮的显示与隐藏
// vueX混入公共函数
import store from "@/store";
// 在所有vue组件中混入钩子函数/方法等,以供调用
export default {created() { // 不会影响单组件的crested钩子函数执行console.log("created-minxin");},methods: {checkPermission(point) { // 某一个具体按钮的权限点为pointtry {return store.state.user.userInfo.roles.points.includes(point);} catch (error) {// 捕获错误,防止退出登录的时候报错return false; // 🆘🆘🆘退出的时候先清除了个人信息,store.state.user.userInfo.roles变成undefined了,无法读取points,所以报错。}}}
};
-----------------------------------------------------------
// main.js里面引入--混入
//引入mixin
import checkPermission from '@/minxin_test/checkPermission'
//混入Vue
Vue.mixin(checkPermission)
-----------------------------------------------------------// 组件中使用:
this.checkPermission() //直接使用(模板中使用无需this)
vueX和localStorage,sessionStorage,cookie的区别:
1.区别:
vueX 随运行程序存储在内存中,当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vueX存储的值会丢失。
localstorage(本地存储)则以文件的方式存储在本地硬盘中, 永久保存(不主动删除,则一直存在);
sessionStorage(会话存储), 临时保存,同源的窗口中始终存在,不怕刷新,但是页面关闭后就清除掉了。
localStorage和sessionStorage 不受页面刷新影响。只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理。
cookie:既可以在客户端设置也可以在服务器端设置。cookie会跟随任意HTTP请求一起发送,一般最多只能存储4KB
的数据(随浏览器不同而变化)。cookie的内容主要包括:名字、值、过期时间、路径和域。路径与域一起构成cookie的作用范围。若不设置时间(存储在内存中),则表示这个cookie的生命期为浏览器会话期间(页面关闭清除), 设置时间(存储在硬盘中)。
2.应用场景:vuex(响应式的)用于组件之间的传值,localstorage,sessionstorage,cookie则主要用于不同页面之间的传值。(非响应式的,页面刷新才会更新)
总结:vuex怕F5刷新,sessionStorage怕关闭页面,cookie不设置过期时间时怕关闭页面,设置有过期时间就和localstorage一样啥都不怕。
————————————————
$store和store的区别:
$store 是挂载在 Vue 实例对象原型上的(即Vue.prototype),而组件也其实是一个Vue实例,在组件中可使用 this 访问原型上的属性,template标签 拥有组件实例的上下文,可直接通过 {{ $store.state.userName }} 访问user模块中 userName变量,等价于 script标签 中的 this.$store.userName。
至于 {{ store.state.userName }},script 中的 data 需声明过 store 才可访问,一般在组件中引用过后使用。
Node-Sass does not yet support your current environment
BUG解决办法:
产生问题的原因
执行npm install命令时,其实是npm按照项目里的package.json文件来下载项目所有的依赖;
由于每个人的电脑环境等不同的问题,有些依赖会不支持当前的环境;
解决方案
先卸载之前的node-sass,然后再安装一遍node-sass就可以完美解决了,npm会自动智能的选择最新的并且支持本地环境的依赖;
特别注意
node-sass被墙了,使用npm会下载失败,所以请用淘宝镜像cnpm下载或者使用翻墙软件下载;
//先卸载node-sass
npm uninstall node-sass//用cnpm重新安装node-sass
cnpm install node-sass -D
登陆login处理函数 登录过程1
validate对整个表单进行验证 登录过程2
发起action登录的异步请求 登录过程3
vuex中actions发登录请求 登录过程4
登录接口 到请求拦截器 登录过程5
axios设置请求拦截器 设置请求头token 判断是否失效 登录过程6
axios设置响应拦截器 结构数据 登录过程7
在vuex中保存token 同时保存到本地 登录过程8 跳转首页
前置守卫 登录过程10
1,获取菜单控制的标识menus
2, 过滤动态路由
3,添加动态路由
4,解决404问题
TypeScript:
//01,安装ts:
npm install -g typescript//02,查看版本:
tsc -V//03,VSCode编译
//手动编译:tsc 文件名.ts
//自动编译:
// 1). 生成配置文件tsconfig.jsontsc --init
// 2). 修改tsconfig.json配置"target": "ES5", //编译目标js版本"outDir": "./js", //指定输出文件夹"strict": false, //是否严格模式
// 3). 启动监视任务:终端 -> 运行任务 -> 监视tsconfig.json//04,类型注解:
// 变量: 变量类型|变量类型... (首字母小写/string/boolean/any任意类型/unknown未知类型) 建议用unknown
// any类型的变量会影响其他变量,unknown类型的变量不会影响其他变量,是一个类型安全的any
// 声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
let c1 : boolean | string;//也可以使用 | 来连接多个类型(联合类型)
let b1: "male" | "female";let c: boolean = false;
// 如果变量的声明和赋值时同时进行的,TS可以自动对变量进行类型检测
let c= false;
c = true; // 可以赋值
c = 123; // 报错 //也可以直接使用字面量进行类型声明 但是后期不可修改有点儿类似常量了
let a1: 10;//默认情况下(非严格模式) null 和 undefined 是所有类型的子类型。就是说你可以把 null 和 undefined 赋值给 number 类型的变量//数组定义方式1
//let 变量名:数据类型[] = [值1,值2,值3...]
let arr1: number[] = [10,20,30,40,50,60]//数组定义方式2 泛型的写法
//let 变量名:Array<数据类型> = [值1,值2,值3...]
let arr2: Array<string> = ['asd','zxc','qwe']//数组定义方式3 元组Tuple类型的写法 限定对应关系
//let 变量名:[数据类型,数据类型,数据类型] = ['小甜甜',100,true] //数组定义方式4 let 变量名:any[] = ['小甜甜',100,true...] 数组元数个数不确定,类型不确定时//枚举类型:enum 元素的编号(数据)省略时,默认从0开始,也可以手动赋值
enum Color {Red = 333,Green = 2,Blue = 4
}
let c: Color = Color.Green // 2
let c2: Color = Color[4] // Blue//定义一个函数参数是object类型,返回值也是object类型:
function fn2(arg: object): object {console.log('fn2()', arg)//return 'abc' //报错return {}
}// void 类型像是与 any 类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
/* 表示没有任何类型, 一般用来说明函数的返回值不能是undefined和null之外的值,意义不大。 */
function fn(): void {console.log('fn()')// return undefined// return null// return 1 // error
}//05,类型断言: 值类型有多种情况的时候,可以用来手动指定一个值的类型
//语法方式1: <类型>值
// 方式2: 值 as 类型 tsx中只能用这种方式
let x: boolean | string; //声明了未赋值
if ((<string>x).length) {return (x as string).length}
//06,类 和 接口://定义一个类class User {firstName: stringlastName: stringfullName: stringconstructor(firstName: string, lastName: string) {this.firstName = firstNamethis.lastName = lastNamethis.fullName = firstName + '_' + lastName}}//由 User这个类 实例化一个对象(约束一个实参)let user = new User('shi', 'hui')console.log(user); //User {firstName: 'shi', lastName: 'hui', fullName: 'shi_hui'}//定义一个接口(约束形参):是一种类型,一种规范,一种规则,一种约束...interface Person {readonly firstName: string //接口的只读属性lastName?: string //接口的可选属性}//定义接口函数function getFullName(person: Person) {return 'hello' + '_' + person.firstName + '_' + person.lastName}console.log(getFullName(user)); //hello_shi_hui
/*
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型
接口: 是对象的状态(属性)和行为(方法)的抽象(描述)
接口类型的对象多了或者少了属性是不允许的可选属性: 属性后面加 ?只读属性: 属性前面加 readonly
*///07,函数类型接口:通过接口的方式作为函数的类型来使用 接口可以描述函数类型(参数的类型与返回的类型)
interface SearchFunc {(source: string, subString: string): boolean
}
const mySearch: SearchFunc = function (source, sub) {return source.search(sub) > -1 //类似于source.indexOf(sub)
}
//等同于:
const mySearch = function(source: string, sub: string): boolean {return source.search(sub) > -1
}console.log(mySearch('abcd', '1')) //false//08,类类型接口-->类实现接口,TS能够用接口来明确的强制一个类去符合某种契约。
interface Alarm {alert(): any
}interface Light {lightOn(): voidlightOff(): void
}class Car implements Alarm, Light {alert() {console.log('类和接口之间叫“实现”!类实现接口用implements关键字,多个接口用,逗号')}lightOn() {console.log('123')}lightOff() {console.log('456')}
}//接口与接口之间也可以相互继承,使用extends关键字,逗号连接多个接口,{ }结尾
interface LightAndAlarm extends Alarm, Light { }
//09,类的继承与多态
class Animal {name: stringconstructor(name: string) {this.name = name}run(distance: number = 0) {console.log(`${this.name} run ${distance}m`)}
}class Snake extends Animal {constructor(name: string) {// 调用父类型构造方法super(name)}// 重写父类型的方法run(distance: number = 5) {console.log('sliding...')super.run(distance)}
}class Horse extends Animal {constructor(name: string) {// 调用父类型构造方法super(name)}// 重写父类型的方法run(distance: number = 50) {console.log('dashing...')// 调用父类型的一般方法super.run(distance)}xxx() {console.log('xxx()')}
}const snake = new Snake('sn')
snake.run()
//sliding...
//sn run 5mconst horse = new Horse('ho')
horse.run()
//dashing...
//ho run 50m// 父类型引用指向子类型的实例 ==> 多态
const tom: Animal = new Horse('ho22')
tom.run()
//dashing...
//ho22 run 50m/* 如果子类型没有扩展的方法, 可以让子类型引用指向父类型的实例 */
const tom3: Snake = new Animal('tom3')
tom3.run()
//tom3 run 0m/* 如果子类型有扩展的方法, 不能让子类型引用指//实例 */
// const tom2: Horse = new Animal('tom2') 报错
// tom2.run()
//修饰符(类中成员的修饰符)