基础甲骨文2

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流程图.png

// 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 操作(赛跑机制)

sync-await解决异步嵌套终极方案.png

async-await.png

async/await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出 现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。

async-await是解决异步嵌套 终极方案。(让异步代码按特定顺序执行)

① 如果在 function 中使用了 await,则 function 必须被 async 修饰

② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行

async.png

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 / .finally
 process.nextTick
 其它微任务
 
 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 先微后宏 

同步任务异步任务.png

宏任务与微任务.png

事件队列,先微后宏.png

webpack:

概念:

webpack本身是, node的一个第三方模块包, 用于静态模块打包(module bundler)。

webpack默认只能处理js类型文件和 json文件,处理css需要 style-loader + css-loader 两个依赖包。

  1. 减少文件数量,压缩代码体积,提高加载速度
  2. 支持less/sass => css
  3. 支持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代码。

图片处理.png

webpack图片打包.png

js高级语法兼容性降级处理.png

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 – 依赖包列表文件

main.js.png

vue_main_index流程图.png

关闭检查规则:

1,关闭所有规则:vue.config.js–>module.exports:{lintOnSave:false}

2,关闭某一个错误检查规则:package.json–>“eslintConfig”:{“rules”:{“no-unused-vars[错误代码]”:"off}}

插值表达式:

又叫(声明式渲染/文本插值)

{{ obj.age > 18 ? " 成年 " : " 未成年 " }} 里面放三元表达式

MVVM设计模式:

设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。

mvvm.png

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>

动态变量图片路径import.png

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的使用.png

// 特别注意: 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的值为 新值val
      
      this.$set(this.arr[index], "目标对象属性名", 新值val);
      //目标 元素对象 ,目标对象属性名,新值val
      
      this.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,在数据变动时,发布消息给订阅者,触发响应的监听回调。

响应式原理.png

响应式检测变化:

对于对象,Vue 无法检测 property (属性)的新增或移除

对于数组:Vue 不能检测以下数组的变动:

  1. 当你利用索引直接更新一个数组项时,例如:arr[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如: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 来提供一个排序提示。

diff算法对比动图.gif

动态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计算属性—缓存功能:

计算属性根据依赖变量的结果缓存, 依赖变化–>重新计算结果存入缓存, 依赖未发生变化–>直接使用缓存值不再调用函数,比普通方法性能更高

vue计算属性-缓存.png

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

局部注册.png

<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的值不能在子组件重新赋值。

父子之间传值.jpg

EvenBus: 兄弟组件之间传值

兄弟组件之间传值.png

创建一个第三方**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栏目切换方法一:子组件多个点击事件处理函数 触发同一个主组件事件

todu切换栏目显示.png

tab栏目切换方法二:子组件事件代理 多个点击事件,然后触发主组件事件处理函数(全选)

tab栏目切换方法二.png

Todos案例全选:

todo全选-小选框.png

正规做法:todo全选.png

钩子函数:

生命周期.jpg

解读:

编译模板阶段 –开始分析

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 post
  url: '请求地址',
  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

_refs实现组件通信.png

Vue更新数据是一个异步操作(异步微任务),解决异步操作顺序执行的四种方法:

_refs的使用.png

解决异步问题的方法.png

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 标签占位,可以接收到写在组件标签内的内容

  1. 组件内用默认内容占位
  2. 使用组件时夹着的地方, 传入标签替换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模板,这块模板显示不显示、以及怎样显示由父组件来决定。

作用域插槽.png

row ---->自定义变量名
具名插槽和作用域插槽一起使用:

具名插槽和作用域插槽一起使用.png

自定义指令:

指令名 需要加 ” “ 引号

binding —> 一个对象,binding.value就是使用指令时传入的参数。例如:v-color=" ‘red’ "中的 ’red‘

扩展:开发依赖 / 生产(使用)依赖:

开发依赖-生产依赖.png

对象解构:
路由:

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 取值

查询字符串传参-动态参数传参.png

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会自动忽略params
          username: '小传'
        }
      //query: {
      //  username: '小传'
      // }
      })
    }
  }
};
</script>

传递是this.$router 路由实例对象 上面添加跳转新的路由

接收$route. 具体某个路由对象里面接收参数

js编程式导航与传参.png

编程式传参.png

vue路由 - 路由嵌套:

main.js– 配置2级路由

一级路由path从/开始定义

二级路由往后path直接写名字, 无需/开头

嵌套路由在上级路由的children数组里编写路由信息对象

const routes = [
  // ...省略其他
  {
    path: "/find",      //--->注意 /
    name: "Find",
    component: Find,
    children: [         //--->二级路由
      {
        path: "recommend",      //--->注意 直接写路径  无 /
        component: Recommend
      },
      {
        path: "ranking",
        component: Ranking
      }
    ]
  }
  // ...省略其他
]
声明式导航类名区别:

声明式导航类名区别.png

路由跳转传参总结
跳转方法传参位置路由规则接收
/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",
    ],
  ],

vant使用推荐.png

2,手动按需: 3,全局全部引入组件:(,体积稍大,不推荐)

vant使用方法.png

节流和防抖:
// 节流: (减缓事件执行速度,坦克大战发射子弹)
      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,保证只有最后一次生效
      });

封装axios导出导入.png

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.png

结论

vuex存储数据时存储在内存中,刷新/F5会清除内存,从而导致vuex数据丢失。常见的保存token最保险的方式时在vuex和本地存储都存一下。

  1. 修改state状态/数据必须通过**mutations**
  2. mutations只能执行同步代码,类似ajax,定时器之类的代码不能在mutations中执行
  3. 执行异步代码/同步代码,要通过actions,然后将数据提交给mutations才可以完成。actions函数返回值为promise对象,一般await修饰。
  4. state的状态即共享数据可以在组件中引用
  5. 组件中可以调用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,定义actions
    actions: {
  //  获取异步的数据 context表示当前的store的实例 可以通过 context.state 获取状态/数据 也可以通过context.commit 来提交/触发mutations, 也可以 context.diapatch调用其他的action
    getAsyncCount (context,payload) {
      setTimeout(function(){
        // 一秒钟之后 要给一个数 去修改state
        context.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 
    // 必须要有 返回值 return
     filterList:  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: {   //模块一:user
       namespaced: true,
       state: {
         token: '12345'
       }mutations: {
        //  这里的state表示的是user的state
          updateToken (state) {
            state.token = 678910
         }
       }
    },
    setting: {   //模块二:setting
        state: {
          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 error
  return 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 {
  // <!-- 目标:文章列表滚动条缓存 -->
  // 🆘🆘🆘给每个列表设置独自的滚动区域,而现在滚动区域是公共的body
  overflow-y: auto;
  height: 93vh;
}
</style>

大数字精度失真问题:

后端返回 JSON 字符串格式的数据,经过 axios 转换成数据对象,但是 JavaScript 能够准确表示的整数范围在-2^532^53之间(不含两个端点),超出安全整数范围的 id 无法精确表示。

npm i json-bigint  //第三方包处理大数字的包
// https://www.npmjs.com/package/json-bigint

const 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,给定默认值,非必须,父组件通过父传子的方式也就获得子组件评论数据list
        props: {
            commentList: {  // 评论列表
              type: Array,
              default: function () {
                return []
              }
            }
         }
		父组件:this.commentList.unshift(val.new_obj)

      // 方式二:通过ref获取子组件元素,再获取到子组件内的评论数组list
      this.$refs.Aclist.list.unshift(val.new_obj)

方式一.png

provide / inject 祖先组件向后代传值:

一般越级传递,父子之间还是建议用

  • 类型
    • provideObject | () => Object 给所有后代组件提供数据
    • injectArray<string> | { [key: string]: string | Symbol | Object } 一个字符串数组,或一个对象,对象的 key 是本地的绑定名。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。提示:provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 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改为想要的名字。

v-model父子传值.png

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

image-20220321215146737

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, 他就是一个函数

具名插槽的使用:
image-20220328140210006
批量注册全局组件:

批量注册全局组件.png

箭头函数的坑:
(a)=>{return b}   a=>b
// 1,不加{}自带return, 加{}必须要使用return
// 2,当需要箭头函数返回一个对象的时候,必须要()包裹{},否则{}会被当成箭头函数函数体的一部分
(a)=>({a:boo,b:baz})
v-for和v-if :

v-forv-if作用在不同标签时候,v-if包裹v-for时候,是先进行判断,再进行列表的渲染 ;

最终结论:v-for优先级比v-if

  1. 永远不要把 v-ifv-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
  2. 如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
  3. 如果条件出现在循环内部,可提前过滤掉那些不需要显示的项,再循环。
data属性是一个函数而不是一个对象:
  • 根实例是单例对象时(其他组件为函数式组件),data可以是对象也可以是函数,不会产生数据污染情况
  • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

一个项目有多个组件,每个组件都是一个单独的Vue实例对象,当多个实例对象打包合并时,对象式数据变量会相互影响/污染,所以不能是对象。

创建元素的三种方式:

创建元素的三种方式.png

document.write('<div>123</div>');

inner.innerHTML += '<a href="#">百度</a>'

var a = document.createElement('a');
            create.appendChild(a);

innerHTML字符串拼接方式(效率低)–> document.body.innerHTML += ‘

123

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] = value
                    Reflect.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)

权限管理.png

使用Mixin技术注入全局vue方法:

3,具体权限点points控制显示与隐藏:

常用于权限点的控制,控制页面具体按钮的显示与隐藏

// vueX混入公共函数
import store from "@/store";
// 在所有vue组件中混入钩子函数/方法等,以供调用
export default {
  created() {  // 不会影响单组件的crested钩子函数执行
     console.log("created-minxin");
   },
  methods: {
    checkPermission(point) {  // 某一个具体按钮的权限点为point
      try {
        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.json
    tsc --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: string
        lastName: string
        fullName: string
        
        constructor(firstName: string, lastName: string) {
            this.firstName = firstName
            this.lastName = lastName
            this.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(): void
  lightOff(): 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: string

    constructor(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 5m

const 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()

//修饰符(类中成员的修饰符)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值