一、HTML5/CSS3
1. 盒子水平垂直居中?
- 父盒子开启flex,并设置主轴居中,侧轴居中
- 父盒子开启flex,子盒子设置margin:auto
- 子绝父相,子盒子设置top和left值为50%,利用transform基于自身回去-50%
- 子绝父相,子盒子设置上下左右均为0,在设置margin:auto也可以
2. 盒模型
- 四部分: 内容+内边距+外边距+边框
- 盒模型可以进行切换: box-sizing: border-box; ie盒模型/c3盒模型/怪异盒模型, 宽高定死,不受边框内间距撑开
- 默认是content-box: 标准盒模型, width+内间距+边框
3. flex:1
flex:1原理实际上是几个三个属性
4. CSS3新属性
- c3盒模型box-sizing
- flex布局
- transition过渡
- transform2D转换
- background-size背景缩放
- border-radius圆角
- 等等,不用都说说一些常用的就ok
5. BFC
概念: 块级格式化上下文,说白了,bfc是一种属性,拥有bfc会拥有独立的渲染区域,从而不会影响外界
触发bfc: position为absolute或fixed display为inline-block, table-cell, table-caption, flex, inline-flex overflow不为visible
bfc的应用:
外边距重叠: 触发bfc
浮动导致高度丢失: 触发bfc
清除浮动: 触发bfc
盒子重叠: 触发bfc
6. Less/Scss的特性
css预处理语言
- less/scss的异同或区别
相同点功能:
- 变量声明
- 混入
- 变量插值
- 嵌套
- 运算
- 导入
- 作用域
- 颜色函数
语法区别:
- 变量声明
- less: @
- scss: $
- 混入
- less: 定义混入通过点(.),使用混入也是通过点,如果没有参数可以省略小括号
- scss:定义通过@mixin,使用通过@include
- 变量插值
- less: 通过@{}
- scss:通过${}
- 颜色函数
- LESS 使用名为 spin() 的函数;
- SCSS 使用名为 adjust_hue() 的函数。
- 条件语句循环语句
- less: 不支持
- scss: 支持
后缀名为.sass的sass3.0之前,比较恶心, 不用花括号,固定的缩进格式3.0之后后缀scss,和less差不多
7. vw/vh/px/em/rem/vmin/vmax区别
- vw和vh: 相对于视口的大小,100vh等于视口的高度,100vw等于视口的宽度
- px: 固定大小
- em: 大小相对于父元素的font-size: 20px 1em = 20px
- rem: 大小相对于html的font-size
- vmin: 大小等于最小的视口: 100vmin等于最小的视口
- vmax: 大小等于最大的视口
8. 让Chrome支持小于12px 的文字方式有哪些?
pc端的浏览器,字体默认最小只能显示12px
- transform: scale(0.5)
- -webkit-text-size-adjust: none; 字体就可以按照font-size去显示
谷歌27版本之后就不能使用了
9. 移动端适配方案
10. 重绘/回流(重排)
- 重绘(repaint)
- 概念:即重新绘制
- 导致重绘:外观、风格发生变化即发生重绘,例如color、background、box-shadow、background-size
- 影响:重绘不一定回流
- 回流(重排):
- 概念:当渲染树中元素的尺寸大小、布局等属性改变时,浏览器的布局需要调整,则需要重新渲染DOM。这个过程就叫回流。回流也叫重排(对整个页面进行重新排版)。
- 影响:回流必然会重绘
- 导致重排:width、height、margin、padding、font-size、position等等
- 避免重绘和回流
- 多个样式修改不要一个一个更改,可以通过操作类名,一次统一修改
// js width
// js height
// js color
.active {
width: 100px;
height: 100px;
color: red;
}
二、JavaScript
slice和splice两者的区别
substr和subString
使用过哪些es6?
2. 聊一聊promise
概念:
promise是一个对象/构造函数,es6新增的,更加灵活的处理异步,可以配合async和await将异步代码变为类似同步的同步,也可以解决我们的回调地狱
promise有三种状态:
pending
fullfield
rejected
不可逆
pending => (resolve)fullfield
pending =>(rejecte) rejected
实例方法:
Promise.then\catch\finnaly
静态方法:
all\allsettled\race\any
解决回调地狱:
利用then,会返回一个新的promise,继续调用then,从而构成链式
promise穿透
reesolve的结果会交给then,但是then的参数如果不是回到函数,继续向下传
终级解决方案:
async await
await 用来修饰promise, async用来修饰await就近的函数,async修饰的函数返回值是promise,所以可以继续使用await修饰
静态方法:
all: 可以获取到多个promise处理异步的结果,all发起的异步是并行的,并且Promise.all的返回值是promise,所以可以调用then,这个then,all的所有promise都resolve成功后才执行,有任意一个reject即进入all的catch, all的then返回的结果就是对应的promise返回的数据
allSettled: 可以获取多个promise处理异步的结果,then,不管resolve还是reject都会执行then,then的返回结果是一个数组对象,对象内部会通过status记录状态,通过value记录值,status记录的状态: fulfilled/rejected
Promise.race 使用和 all 一样,但是只返回第一个结果,不管成功或失败
Promise.any 返回第一个成功的结果
Generator
概念: 也是es6的,可以将函数的控制权交出,也可以利用generator更方便的控制异步,实际async和await就是他的语法糖
区分: 星号
如何交出控制权: 通过yield进行控制权交出,通过next逐步调用
如何处理异步: 可以通过yield配合promise达到类似async和await的效果,通过yiled返回promise,在promise中处理异步,等异步成功调用resolve,这样在外部可以通过next.value获取到promise,通过then等待成功后,执行下一次的next
而且对应的自执行generator函数有co库,可以去自执行generator
区别:
new的过程
创建一个新对象
// 将新对象的__proto__ 指向了 Person.prototype
// 将构造函数内部的this指向新对象
// 执行构造函数给this(实例对象)添加属性或方法
// 默认return this
// 如果写了return 看 数据类型
// 数据类型是简单数据类型: return简单数据类型忽略 最终还是return this
// 如果数据类型是复杂数据类型: 最终得到是该复杂数据类型 return this无效
字符串反转
'hello'.split('').reverse()
forin/forof区别
for in:一般用来遍历对象,并且可以遍历原型对象,不建议循环数组,可以循环
for of:不可以进行遍历普通对象的,可以遍历数字字符串数组和 newSet 对象等等,并且可以进行 break 终止,不可以 return
中断for
统计数组中出现次数最多的元素
// 统计出出现次数最多的元素 返回次数和元素
const arr = [1, 2, 3, 2, 3, 2, 3, 7, 8, 7, 6, 7, 5, 7, 0, 7, 7, 7, 7, 2, 5, 5, 5, 5, 5, 5]
function repeatCount(arr) {
// key 是元素 value: 次数
const obj = {}
let max = 0
let maxItem = null
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (obj[item]) {
obj[item]++ } else {
obj[item] = 1
}
if (obj[item] > max) {
max = obj[item]
maxItem = item
}
}
console.log(maxItem, max)
}
repeatCount(arr)
递归使用场景
export function transListToTree(data, pid) {
const arr = []
data.forEach((item) => {
if (item.pid === pid) {
// 当前: item 就是1级数据 item.id
const children = transListToTree(data, item.id)
if (children.length) {
item.children = children
}
arr.push(item)
}
})
return arr
}
聊一聊异步
为什么有异步?
js是单线程,如果js语言是多线程,在操作dom容易混乱,所以js就是单线程,问就是,如果某一个任务比较耗时,阻塞运行,js把耗时交给浏览器执行,交给浏览器执行的这些耗时任务: 异步任务
js在es5之前无法发起异步,es6的Promise可以发起异步
异步执行流程
- 主线程先判断任务类型
- 如果是同步任务,主线程自己执行
- 如果是异步任务,交给宿主环境(浏览器)执行
- 浏览器进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列,依次放入
- 等主线程任务全部执行完后,发现主线程没有任务可执行了,会取任务队列中的任务,由于任务队列里是依次放入进来的,所以取得时候也会先取先进来的,也就是先进先出原则
- 在任务队列中取出来的任务执行完后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训
es6模块化和commonjs模块化的区别
- es模块化的导入: import import {} import * as
- es6模块化的导出: export export default
- commonJs模块化导入: require
- commonjs模块化导出: module.exports module
继承的实现方式
闭包
垃圾回收
箭头函数和普通函数的区别
- 箭头函数this指向上下文,普通函数this看调用方式
- 剪头函数没有arguments对象,普通函数具备arguments对象
- 箭头函数没有prototype, 普通函数具备prototype
- 箭头函数不能用做构造函数,普通函数可以用做构造函数
三、Vue2
1. v-if/v-show的区别?
- 效果: 控制元素显示和隐藏的
区别:
-
v-if
- 原理: 移除或添加dom元素
- 优点: 懒渲染, 默认首次如果是false,元素不会创建, 如果是组件,可以重新触发生命周期
- 缺点: 频繁的删除重建
- 使用场景: dom首次判断是否显示还是隐藏,或者组件需要重新触发生命周期
-
v-show:
2. $route和 router作用?
router: 和new VueRouter完全是同一个对象,全局路由实例 可以使用路由方法进行前进后退跳转等
路由编程式导航都是通过this.$router实现的
3. 聊聊vuex?
vuex的作用? vuex的5个属性? vuex的优缺点? vuex的流程? vuex中的命名空间 ?如何触发mutation、action?
- vuex是什么?
vuex是基于vue状态管理的库 - vuex的作用和优点
实现组件数据共享,同时vuex的数据也是响应式,vuex数据操作远比组件内数据传递方便,方便维护和管理 - vuex的5个属性
state: 存储或定义共享数据,如果像访问数据,$store\mapState
mutations: 修改vuex数据的唯一来源,为了方便后期的调试和维护,当然也可以直接修改state数据,但是很不建议, commit提交mutaiotn
actions: mutations是用来处理同步的,devtools不准确,actions处理异步,dispatch,但是actions只用来处理异步,如果想修改数据,context中的commit提交mutations,
getters: 基于state进行派生数据
moudles: 将vuex数据进行模块化,方便维护,划分模块,每个模块中都拥有state/actions/mutaions/getters,每个模块可以设置命名空间,如果不设置,实际mutations或actions中的方法和全局的并无区别,开启命名空间后,必须通过模块名去访问
说一下两个流程
同步流程: commit > mutations > state
异步流程: dispatch > actions > mutations > state
开发: 统一异步流程 - vuex中的缺点\解决方案
实际vuex也是有缺陷的,不能持久化,想解决该问题/利用本地存储,或者利用插件实现自动存储vuex-persistedstate - vuex-persistedstate:
- 默认还是localstorage
- cookie
- sesstion
- 应用场景
在开发项目的时候,一般用户信息或者token/或者网页的配置数据存入vuex
4. vue.use作用?
Vue.use({install(){}}, 1,2)
- 参数1: 可以传一个函数或者对象,如果是对象,对象中需要一个install函数,如果是一个函数,相当于是install函数
- 其他参数: 依次传给了install函数
install函数默认第一个参数是Vue,后面的参数来自于Vue.use后续参数
Vue.use本质是无法注册全局组件或者给Vue原型添加方法,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过Vue.component注册了全局组件或者给Vue.prototype手动添加方法
5. $nextTick有什么用?
作用
问题: vue中数据发生变化同步视图是异步的,所以数据发生变化不能立即获取最新的视图, 想获取最新的视图通过this.nextTick函数和vue源码中的nextTick函数,是同一个函数,源码中调用nextTick函数为了异步更新视图,我们在使用this.$nextTick函数的时候回调会在源码的nextTick函数之后执行,所以获取到最新的视图,
源码的nextTick函数异步原理利用的就是向下兼容可宏可微,源码中依次进行判断Promise/mutationobserver/setImmdiate/setTimeout
keep-alive
作用: 缓存不活动的组件
使用: keep-alive组件包裹路由占位
使用场景: 列表进详情需要把列表页缓存,将列表的滚动条的位置记录下来,当重新进入列表页的时候设置滚动条位置
按需缓存:
- kepp-alive上的include或exclude匹配的是组件的name
- 结合路由的meta,添加一个缓存标识,通过在keep-alive缓存的位置获取当前路由信息上的meta中的缓存标识进行控制是否显示keep-alive包裹router-view还是直接显示router-view
生命周期:
被缓存的组件激活: actived
被缓存的组件失活: deactived
6. 生命周期
组件
本质:就是函数,会在特定的阶段自动调用,生命周期函数
作用:可以让我们在某个阶段做一些事情
4个阶段
阶段1: 创建阶段
- beforeCreate: 开始创建实例,此时实例的数据和方法还没有
- created:
- 作用:实例已经创建完成,数据和方法都已存在
- 应用场景: 发送请求获取数据, 页面进入的方法需要立即执行
- 扩展: 如果非要在created中操作dom也可以,利用$nextTick
阶段2: 挂载阶段(dom)
- beforeMount: 开始挂载dom,真正的dom元素还没有挂载完成, 操作dom不可以
- mounted:
- dom已经挂载完成,可以操作真实dom
- 应用场景: 页面已进入操作dom
阶段3: 更新阶段
- beforeUpdate: 数据变了,但是视图还没变
- updated: 数据和视图都变了
阶段4: 销毁阶段
- beforeDestory: 即将销毁
- destoryed: 组件销毁
- 应用场景: 清除挂载在window相关的行为,例如定义器\事件
父子
创建挂载阶段
父beforeCreated > 父created > 父 beforeMounted > 子beforeCreate > 子created > 子beforeMount > 子>mounted > 父mounted
更新阶段
如果更新的数据不涉及到子组件,只会父组件更新 父beforeUpdate > 父updated
如果更新的数据涉及到子组件, 父beforeUpdate > 子beforeUpdate > 子updated > 父updated
销毁阶段
父beforeDestory > 子beforeDestory > 子destoryed> 父destoryed
7. 插槽
默认插槽:
直接在组件标签中间进行传递
组件内部通过vue内置的slot组件进行接收
如果组件想给组件内部不同的区域进行自定义,需要使用具名插槽
多插槽: 具名插槽
直接在组件标签中间进行传递,但是需要通过slot或#指定是哪一个具名插槽
<sg-button>
<span slot="icon">🎨</span>
</sg-button>
组件内部: 通过多slot进行接收,slot通过name进行区分
<div @click="$emit('click')" :class="[type, size]" class="btn">
<slot name="icon" />
<!-- 组件内部通过slot向外传递数据 -->
<slot name="text" />
</div>
不管是普通插槽还是具名插槽都无法在父组件的插槽结构中获取子组件的数据,所以还需要使用作用域插槽<br />**作用域插槽**
直接在组件内部进行传递,但是同时可以通过v-slot="scope" 获取组件内部通过slot组件传递的数据
<sg-button>
<!-- v-slot 可以获取到对应的slot上所有属性 -->
<template v-slot:text="scope">
<span>注册</span>
{{ scope.age }}
{{ scope.data }}
</template>
<span slot="icon">🎨</span>
</sg-button>
组件内部通过slot组件属性向外传递数据,冒号传递的是变量,否则传递的就是字符串
<div @click="$emit('click')" :class="[type, size]" class="btn">
<slot name="icon" />
<!-- 组件内部通过slot向外传递数据 -->
<slot age="19" :data="count" name="text" />
</div>
应用场景: element ui很多组件,例如树形\表格
8. v-model
作用: 数据双向绑定(mode >> view),进行组件通信
原理: v-model就一个语法糖, 动态绑定了value和注册了input事件
使用场景:
在表单中使用
在组件上使用, 既要将数据传给子组件,子组件还要修改数据的时候,此时可以使用v-model进行简化
v-model有一个缺点: 一个组件上只能使用一次
9 .sync修饰符
作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)
原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发
.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件
10. vue组件通信(传值)
- 父传子: 父组件属性方式传递,子组件(props)
- 子传父: 子组件通过$emit,父组件通过自定义事件
- eventbus: // 发布订阅模式
class EventBus {
// 记录事件和回调
clientList = {
send: [() => {}, () => {}],
}
// 订阅事件,参数event事件名,callback 回调
$on = function (event, callback) {
// 将事件和函数记录
// 如果事件记录过,那就将回调push
if (this.clientList[event]) {
this.clientList[event].push(callback)
} else {
this.clientList[event] = [callback]
}
}
$emit = function (event, val) {
if (!this.clientList[event]) {
throw new Error(event + ' is not a event')
}
this.clientList[event].forEach((cb) => {
cb(val)
})
}
}
const eventBus = new EventBus()
// 订阅事件
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$emit('send', 1
-
本质: vue实例对象
-
实现原理: 利用发布订阅模式
- 传递数据的组件,通过eventBus的$emit发布自定义事件,并传递数据
- 获取数据的组件,通过eventBus的$on订阅自定义事件,并通过回调函数接收数据
-
设计模式:发布订阅设计模式: 一对多的依赖关系, 发布方是一,依赖方是多,发布方发布消息,所有的依赖(进行订阅了)方收到通知
-
vuex
-
ref获取子组件
-
v-model
-
.sync
-
$children: 可以获取当前组件的所有子组件,并以数组的格式返回数据
-
$parent: 可以获取到当前组件的父组件, 返回当前组件的父组件
-
provide/inject: 跨组件传值provide,进行给后代组件提供值,inject在后代组件上进行注入值
-
$attrs: 获取到当前组件节点上的所有属性集合
11-虚拟dom
什么是虚拟dom?
虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom
为什么出现?
虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)
如何实现高效更新?
初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法
diff算法?
同级比较.深度优先,而且采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升
diff对比之后的情况?
元素不同: 删除重建
元素相同,属性不同: 元素复用,属性更新
v-for:
无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能
无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响
有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比
12-mixins
mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可
可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中
优先级:
- 生命周期,组件和混入的都会调用(混入的先调用
- data/computed数据: 进行合并,冲突以组件为主,mixins被覆盖
- methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
13-路由模式的区别
- abstract支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
- 是否有#/
hash有
history: 没有
- 是否经过服务器
hash: 不会
history: 会
- 是否需要后端配合
hash: 不需要
history: 需要
- 底层原理
hash: 跳转 window.location.href, 监听 onhashchange
history: 跳转history API, history.pushState和history.repleaceState 监听 onpopState(前进/后退)
封装的方法: pushState(history.pushState/history.repleaceState)
14. 用过哪些修饰符
修饰符: 在指令后面通过.语法使用的
15. vue优缺点
优点:
- 简单易用
- 渐进式
- 响应式
- 双向数据绑定
- 虚拟dom
- 耦合低
- 用户体验好
- 结合vuerouter实现spa
缺点:
16. vue-i18n
**概念: **
实现国际化(多语言),项目可以进行语言的切换
使用:
- 装包
- 在单独的模块中进行封装/引入/注册/配置语言包
- 封装一个组件用来进行语言切换(this.$i18n.locale)
- element-ui: 直接导入element-ui的语言包,同时在use element ui时候并配置i18n
17. computed和watch的区别
computed:
- 计算属性: 数据发生变化,重新进行计算从而得到新的数据
- 逻辑计算的时候一般使用计算属性
- 定义的时候是方法,使用的是属性
- computed内部return依赖的数据发生变化,,逻辑会进行重新计算
- computed实际只会计算1次,只要数据不变,不管使用多少次,后续的使用的是缓存
- computed不能处理异步
watch
- 进行监听,监听某一个数据如果发生变化,我们想进行逻辑的处理
- watch可以监视:props\data\computed$route
- 当监听的数据发生变化,对应的函数会重新执行
- 监视的是一个对象: 开启深度监听 deep:true
- watch内部是可以处理异步的
项目应用:
计算属性:
vuex的getter/state数据: 都映射到计算属性中使用
统计审批次数:入职/离职
收货地址: 后端返回的是多个数据
小程序: 订单金额,订单数量
watch:
父组件异步向子组件传递数据
路由变化但是组件复用了
封装的对话框组件:数据变化,需要将数据通过自定义事件传给父组件
18. watch原理
- vue内部通过initWatch进行初始化,循环watch对象,但是监视的值可以写成数组,所以进行两层循环
- 每一个监视的回调都会创建一个用户watcher,内部通过createWatcher创建
- watcher内部兼容了三种watcher, 渲染watcher/用户watcher/计算属性watcher
- 如果是渲染watcher,this.getter等于expOrFn,如果是用户watcher通过parsePath生成了用户的getter,判断expOrFn是否为函数,还是一个字符串
- 数据发生变化进入更新阶段,执行进入run函数,调用创建watcher传入的回调,这个回调实际就是watch监视的数据的回调
- 旧值在watcher初始化的时候调用的get函数中获取到
- 新值在run里执行的get函数中获取到,将新旧交替,将新旧值传给回调
- watch的使用还可以通过实例.watch创建用于watcher
19. computed原理
- 将计算属性的get和set转换为Object.defineProperty的get和set,并且计算属性如果写为简写的方式,函数直接交给了Object.defineProperty的get
- 循环计算属性给每一个属性创建计算属性 创建计算属性watcher
- 缓存原理:重写计算属性Object.defineProperty的get, 通过createComputedGetter进行生成,返回一个函数,在函数内部主要通过获取到计算属性对应的watcher的dirty,默认为true,计算过一次变为false,根据dirty判断是否重新计算(evaluate)重新计算,
- 依赖的数据发生变化,重新渲染页面,依赖的数据收集渲染watcher:
- 通过stack记录多个watcher
- 在计算属性get中判断是否还有watcher
- 通过渲染watcher的depend方法内部循环deps,获取到每一个dep,计算属性依赖的每一个数据,每一个dep收集watcher,dep内部的depend方法
20. computed和watch执行顺序
初始化阶段computed会比watch先执行,原因就是在initState中先判断的是否有computed,初始化computed,再去判断是否有watch,如果有watch再去初始化watch
21. 单项数据流
数据是单项流动,数据可以从父组件流向子组件,父组件的数据发生变化,会自动同步子组件,反之不允许
22. v-if和v-for为什么避免同时使用
v2中:
v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,利用v-if进行移除
- v-if写到外层
- 先通过计算属性将数据计算好
23. vue 组件中的 data 为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
24. 简易数据响应式原理
- 概念: 数据发生变化,数据使用的地方会进行同步更新
- vue中数据响应式的核心Object.defineProperty可以对对象的进行定义属性,内部有两个方法get,set,get数据读取会触发,set,数据变化会触发
- vue劫持数据的时候必须要通过一个数据进行周转,出发get或者set直接用原数据,就会堆栈溢出
- Object.definePropert一次只能劫持一个数据,所以源码中需要对数据进行循环遍历劫持,类似于递归的方式对所有的数据进行劫持
- 对象新增的数据是无法劫持到的,因为新增的数据,需要劫持的数据已经被劫持了,对象新增的数据也就不是响应式的,vue中通过this.$set解决
- 数组的下标修改数据Object.defineProperty.可以劫持的到,vue因为性能和用户体验的关系,取消了vue中检测数组下标的变化
- 数组的7个方法是响应式的原因是因为vue对7个方法进行重写了,并不是完全重写,利用了切面编程,对方法进行封装和逻辑的处理,最终还是会调用原始的方法
- 观察者设计模式: 一对多的数据依赖关系,和发布订阅的区别,没有调度中心,被观察者和观察者直接通信, 观察者内部会有update方法进行消息更新,被观察者内部会有add收集观察者的方法,通知观察者的方法notify,通过notify通过所有的观察者,观察者通过update更新消息
- vue源码中: get中进行依赖收集,会给每个数据添加一个watcher观察者(每一个watcher对应的就是一个组件)
- set中: a数据发生变化通知观察者(组件)进行更新,vue中更新是以组件为单位
25. vue源码
数据响应式数据劫持
- data数据通过选项对象传递给Vue构造函数,Vue类接收数据,将数据传递给initMixin方法,方法内部通过_init接收数据,最终将数据initData,初始化data数据,
- 首先判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data进行数据劫持
- 将data数据传递到observe函数中,对data类型进行判断,将data传递到Observer类中的walk,walk起始函数
- 在walk函数中循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
- 处理通过this实例访问data选项数据,此时在initData内部通过proxy将vm进行劫持,当访问vm上的数据时,代理到_data数据等同于data,进入walk内部的数据劫持
- Observer中会给需要劫持的数据添加一个__ob__是否被劫持的标识
简易版本: 核心
data选项数据传入到initMixin(初始化所有), 内部将传入到initdata(初始化data数据), 获取data数据, 判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data数据传入到observe函数中,将数据传入,判断数据是否是对象, 如果是对象将数据传递给Observer类中的walk内部循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
数组劫持:
Observer中进行判断数据类型是否为数组,如果是数组,走observeArray方法,对数组中的复杂数据类型进行劫持
7个数组方法重写: ['push', 'pop', 'shift', 'unshift', 'slice', 'reverse', 'sort']
AOP 切面编程实现, vue内部重写了这7个方法,但不是完全重写,自身的push unshift功能还是保留所以还会调用原来的方法,又对unshift push splice方法的增加的数据进行了劫持,其他的只做了通知notify更新通知操作
模板编译:
compileTofunction这个方法生成render函数,通过render函数生成虚拟dom,虚拟dom疏对比.利用patch方法进行dom的更新
通过parseHtml方法解析模板,解析模板的过程就是使用大量正则匹配,生成AST语法树,将AST语法树生成了render函数
数据响应式数据发生变化:模板更新:
创建了一个watcher,内部通过get调用了updateComponent这个方法: 编译更新整个组件内部会进行虚拟dom树的对比
收集watcher:
编译模板模板中用到哪些数据,给哪些数据收集watcher,通过dep进行watcher收集,每一个数据通过闭包的方式都有自己dep,通过deo收集watcher,所以每个数据都收集了自己的watcher, 数据劫持的get中收集watcher,拿不到watcher,通过Dep.target进行中转, watcher中的getter调用前将this存给Dep.target,然后在get中将Dep.target进行
通知:
数据发生变化触发set,获取到数据对应的dep,dep中通过subs存着watcher,dep中有一个notify方法循环收集所有的watcher,调用watcher的update方法进行组件更新 => get >getter > updatecomponent
vue数据响应式:
观察者模式:多对多
dep > watcher
watcher > dep
dep => depend > 调用watcher的addDep方法进行收集dep, 通过dep收集watcher通过addSub
异步更新:
quereWatcher watcher队列收集, 根据watcher的id进行将watcher存入quere对列中,调用watcher的get方法,不能同步调用,只会更新第一个数据的视图,保证所有数据更新完后在统一的更新视图,将watcher的get方法放到异步任务里
next原理:
this.$nextTick就是vue异步更新视图中封装的nextTick方法,利用异步更新视图,异步优先使用微任务,因为同步代码更新完成后进入微任务更快,优先使用Promise.resolve,如果Promise没有,使用MutationObserver也是微任务,如果MutationObserver也没有使用setImmediate是宏任务,他比setTimeout快,如果setImmediate也没有使用setTImeout
总结:
数据劫持 + 模板编译 + 观察者模式 + 异步更新视图nextTick
数据劫持:
数据传入到initMixin,将数据传入到initData,获取data数据,可能是函数/对象, 将数据传给observe函数中,数据判断是否为对象,以及是否被观测过__ob__,继续将数据传入到Observer中的walk函数,循环对象,对每一个对象通过defineReactive函数进行数据劫持
Observer中判断数据是否为数组,如果是数组observeArray对数组的复杂数据进行劫持,数组的7个方法通过AOP切面变成进行重写
模板编译:
通过compileTofunction生成render函数,内部通过大量的正则匹配生成AST语法树,将AST语法树生成render函数,通过render函数创建虚拟dom,将虚拟dom渲染成真实dom
观察者模式:
依赖收集,依赖通知
在数据劫持中get函数中进行watcher收集,因为watcher对应的一个组件的更新,通过dep进行收集,观察者模式多对多,dep收集watcher,watcher也会收集dep,数据发生变化在set中通过dep.notify进行依赖的watcher通知
异步更新视图:
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
26. vue 异步队列
异步更新视图:
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
27. 路由守卫/路由钩子
- 全局守卫
对所有路由全都生效,进行权限的处理,比如是否登录的路由权限控制
- 路由前置守卫
beforeEach
- 路由后置守卫
arterEach
- 路由独享守卫
针对某一条路由进行单向控制
beforeEnter
- 组件内守卫
针对组件
beforeRouteEnter: 只要通过路由,不管是哪一条路由显示该组件就会触发,控制改页面是否展示
beforeRouteUpdate: 路由组件被服用时,例如/a/1 进入a页面,在当前组件重新进入/a/2 显示a页面,a页面就会被复用, 组件没有销毁,组件不会重新走created不会执行,此时可以使用beforeRouteUpdate进行解决
beforeRouteLeave: 应用场景: 表单页面的返回,提示用户当前表单未提交是否离开,离开当前路由组件:
28. 获取数据created和mounted
为什么有时候在created获取数据,为什么数据可视化大屏可能会在mounted中获取数据
如果涉及到dom的操作了,应该在mounted中
如果没有涉及到dom操作,在created中
29. 数据不响应的情况
- 对象新增属性
- 数组通过下标直接更改数据: arr[0] = 1 arr[0].name = '李四'
- 对象删除
原理:
**解决方案: **
更新对象: this.set(数组. 下标, value)
对象删除: this.$fourceupdate()
上述情况在后面进行了可以支持数据响应式的,上面的也会同步更新
30. spa的优缺点
31. 服务端渲染ssr
- 为什么?
spa单页面应用的seo不友好,spa单页面,由js进行渲染页面,不同的路由规则渲染不同的组件,对应html就是一个空页面,百度爬虫在爬,什么都爬不到,导致seo不友好
- ssr
服务端渲染: 先由服务端将页面解析好返回,返回之后,浏览器进行渲染,直接将页面的内容进行渲染,html不在是一个空页面,爬虫可以爬到
- 配置
路由: 在pages下创建文件,自动生成路由
ui组件库: 在nuxt.config.js中通过plugins节点指定配置文件的路径,在对应的路径文件中进行配置
css全局样式: 可以在nuxt.config.js中的css节点中进行配置
seo优化: nuxt.config.js中通过head进行title和meta的配置
在pages中也可以通过head进行页面级别的配置
- 获取数据
生命周期分为
服务端
nuxtServerInit: 服务端初始化
RouteMiddleware: 中间件
validate: ok
asyncData: 获取服务端数据
asyncData中进行获取数据,该生命周期是在服务端执行的,所以不能使用this
客户端
created => fetch(fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。) = mounted
32. vue中使用了哪些设计模式
四、typscript
ts的优势/好处
- 错误前置: ts是属于静态类型语言,先将ts编译为js文件,在编译的过程中就可以发现错误,实际配合vscode插件,写代码的时候就已经可以发现错误
- 对高级语法的兼容
- 项目后期维护会更好
- 代码提示帮助我们减少考虑可以使用哪些数据或方法
- ts自身支持类型推断,所以并不是所有的数据都需要添加类型约束
ts的数据类型
- 原始数据类型
number/string/null/undefined/boolean
Array/Object/function
- ts新增的
元组/联合类型/字面量/枚举/any/never/unknow
any: 可以赋值任意数据类型
unknow: 可以赋值任意数据类型,但是不能任意调用属性或方法
什么是元组?
联合数据类型?
keyof和typeof区别
泛型
作用: 用来约束某个数据配合多个类型使用,保证类型安全,从而达到复用
例如: {a: '', b: '', c: 12}, {a: '', b: '', c: boolean}
泛型使用: 通过尖括号<定义类型变量>,某个数据类型不确定可以使用类型变量,使用泛型的时候<传递具体的类型>
泛型: 泛型函数/泛型接口/泛型type
type和interface的区别
type:
- 给任意数据类型进行命名复用
- 可以进行扩展通过&扩展type/interface
interface:
五、Vue3
1. v2和v3的区别
- api
- v2: options API(选项式API):优点:对于新手容易,按照固定选项实现功能缺点: 对于代码的阅读\维护\复用(逻辑)不是很友好,v2中也可以通过Mixins实现逻辑的复用
- v3: composition API(组合式API)优点: 功能代码组合在一起
- 使用
- v2: 配合vuex,对与ts的支持不是很好,不允许使用多个根节点
- v3: 推荐配合pina,底层就使用ts重构的,对ts支持很好,多个根节点
- 性能
- v3的性能会比v2快1.2_2倍:
- v3采用了静态标记,给静态节点打上标记,diff的过程只对比动态节点,忽略静态节点
- 虚拟dom的创建:v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom,v3中数据发生变化,看该虚拟dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom
- 事件函数的缓存: 事件函数每次在模板编译的时候会进行缓存
- v3的tree shaking:实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的,v3的打包比v2的小
- 数据劫持
2. vite和webpack的区别
- vite: 工程化工具
- vite原理: 启动项目不会对项目进行完全打包,启动服务,将资源通过服务进行托管,项目加载而是通过浏览器利用esm(es module)的方式加载资源,使用到资源才会加载才会处理,这实际上是让浏览器接管了打包程序的部分工作。
- webpack 真正的打包: 先读取配置文件,根据配置文件的规则进行打包,读打包的入口文件,根据入口文件的依赖进行打包,js转低级语法:babel,三方文件:loader,html:html-webpack-plugin,css文件的抽离:mini-css-extract-plugin,启动浏览器,浏览器加载资源,直接加载不会再次处理
3. vue3 如何定义响应式数据
- ref: 将简单数据类型或复杂数据类型处理为响应式
模板中使用自动解套value,在js中使用需要通过value访问到数据
- reactive: 只能将复杂数据类型处理为响应式,直接用数据不需要value
推荐的使用方式:
4. script setup语法
- 语法: 在script标签上定义setup属性
- 特性:
5. computed计算属性
- computed: 计算属性, 就是vue提供的一个函数.所以需要通过引入vue得到
- 语法: 调用computed函数, 传递一个回调函数,在回调函数内部进行计算,将计算好的数据return出
- 特性: 具有缓存, 多次调用多次定义, 除了可以传递函数,还可以传递对象, 包含get和set函数
6. watch监视
- 概念: 监听数据变化, 也是vue提供的一个函数, 所以需要通过引入vue得到
- 语法:
- 参数1: 监视的数据
- 监视单个数据: 直接写数据
- 监视多个数据: 写数组,数组内放数据
- 监视reactive对象中的某一个属性: 函数返回属性
- 参数2: 处理函数, 两个参数
- 参数1: 新值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数2: 旧值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数3: 配置对象
7. 生命周期函数
vue3中的生命周期函数来自vue,所以需要通过vue按需引入,生命周期函数可以多次调用
- 创建阶段
- vue2: beforeCreate/created
- vue3: setup
- 挂载阶段
- vue2: beforeMount/mounted
- vue3: onBeforeMount/onMounted
- 更新阶段
- vue2: beforeUpdate/updated
- vue3: onBeforeUpdate/onUpdated
- 销毁阶段
8. 组件通信
9. 过滤器
10. toRefs的使用
作用: 保证解构的数据还是响应式
原理: 将解构的数据变为ref处理
11. 路由
vue3的路由结合vue-router@4.x版本
- 创建路由实例, 通过createRouter进行创建,createRouter就是一个函数,来自于vue-router的按需引入
- 配置路由模式,通过createWebHashHistory(hash路由模式)、createWebHistory创建history路由模式
import { createRouter, createWebHashHistory as createRouterMode } from 'vue-router'
// 可以创建路由实例
// 通过参数对象进行配置
const router = createRouter({
// createWebHashHistory 创建hash模式
// createWebHistory 创建history模式
history: createRouterMode(),
// 路由规则
routes: [
{
path: '/home',
component: () => import('../views/Home/index.vue')
},
{
path: '/about/:id',
component: () => import('../views/About/index.vue')
}
]
})
export default router
- 获取路由实例, 通过useRouter, useRouter函数来自于vue-router, 调用useRouter获取到router实例
import { ref } from 'vue'
// 引入useRouter
import { useRouter } from 'vue-router'
// 创建router实例
const router = useRouter()
const goAbout = () => {
router.push('/about/1')
}
- 获取当前的路由信息, 通过useRoute, useRoute函数来自于vue-router, 调用useRoute获取到route
import { ref } from 'vue'
// 引入useRoute
import { useRoute } from 'vue-router'
// 创建route
const route = useRoute()
12. pinia
12.1 pinia相比较vuex的优势
12.2 pinia如何使用
- main.js中进行createPinia的引入,通过createPinia创建pinia实例,并进行挂载
// 引入创建pinia实例
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
// 挂载pinia
app.use(pinia)
- 创建pinia模块, 在js文件中通过defineStore(来自于pinia),创建一个模块数据管理
- 参数1: 模块标识
- 参数2:配置对象(state,actions, getters)
- 返回值: 函数
import { defineStore } from 'pinia'
// 参数1: 模块标识
// 参数2: 配置对象
// 返回值: 函数
const cartStore = defineStore('cart', {
state: () => {
return {
total: '111.00'
}
},
actions: {},
getters: {}
})
export default cartStore
- 使用模块的数据或方法, 导入模块, 直接调用模块的属性(state,getters)或方法(actions)
- 如果想对模块的数据进行解构,通过storeToRefs处理后解构的数据变为响应式了
- 模块肯定进行统一整合
12.3 pinia有哪些属性
六、小程序
1. wxss和css的区别?
- wxss是小程序配合wxml渲染结构样式
- css是网页结合html渲染结构样式
- wxss新增了rpx,适配单位, 750rpx等于整屏的宽度
- wxss区分全局样式和局部样式,全局样式app.wxss,局部样式,每个页面内部的wxss文件,权重,先看权重,谁的权重高就会把另一个覆盖,同等权重,局部的覆盖全局的
2. 原生小程序组件使用过哪些?
3. 原生小程序中如何绑定事件?
4. 原生小程序中如何修改数据并同步视图?
5. 事件传参?
6. 小程序中发起网络请求
7. 导航跳转方式?
- 声明式导航(navigator标签进行跳转),跳转到tabbar页面,需要配合open-type="switchTab",open-type="navigateBack" delta="层级"
- 编程式导航: wx.navigatorTo() 普通页面的跳转, wx.switchTab跳转到tabbar页面,wx.navigateBack
- 导航传参: 通过query(?根参数 key=value&key=value), 接收参数,通过onLoad的形参去接收
8. 监听上拉触底
9. 小程序的生命周期函数?
- 应用的生命周期
// 触发一次
onLaunch() {
console.log('小程序开启启动,初始化完成')
},
// 小程序显示,多次触发
onShow() {
console.lg('小程序显示了')
},
onHide() {
console.log('小程序隐藏了')
},
onError() {
console.log('小程序出现异常')
},
- 页面的生命周期
// 1. onLoad 页面开始加载 发送请求获取数据,获取到导航参数
// 2. onShow 页面显示 (多次触发) 提示信息
// 3. onReady 页面初次渲染完成
// 4. onHide 页面隐藏 tabbar页面切换只是隐藏
// 5. onUnload 页面销毁 不是tabbar页面 b页面 返回 A页面对应的页面销毁 // 清理操作
- 组件的生命周期函数
10. 定义全局组件和局部组件
全局组件
- 创建一个组件
- 通过app.json中的useingComponets进行组件注册,注册为全局组件
局部组件
11. 组件通信
父向子传值
子向父传值
- 子组件传值
this.triggerEvent('自定义事件名', 传递的数据)
- 父组件接收, 通过bind:绑定自定义的事件名, 通过事件函数的e.detail获取到子组件传递的数据
- 通过事件函数的e.detail接收数据
getImage(e) {
console.log(e.detail)
},
获取子组件的方式
- 通过selectComponents获取子组件
this.selectComponent('组件对应的选择器')
12. wxs是什么?
wxs weixin script: 在模板中无法调用js中定义的函数,可以调用wxs定义的函数,一般用wxs进行数据处理
缺点:
- 不支持es6
- 不支持小程序的api
- 隔离性, wxs和js不能互相调用
优点:
13. 原生小程序上线流程
14. uni-app小程序的上线流程
- 在hbuilderX中发行-小程序,就会打包
- 在微信开发者工具中点击上传,弹出填写上传信息,填写完毕进行上传
- 登录小程序管理系统(开发人员不会参与)
- 将版本设置为体验版本(产品和ui进行体验)(测试人员进行测试)
- 改bug,重复1\2步骤
- 提交审核
- 审核通过点击发布
15. 登录流程
- 微信授权登录
1.1 获取用户信息(iv, rawData, encrytedData,signature)1.2 调用uni-app的login方法获取code(用户唯一标识)
const {
code
} = await uni.login()
- 根据微信授权的信息进行接口登录
const res = await login({
iv,
rawData,
encryptedData,
signature,
code
})
- 登录成功后
16. 支付流程
- 根据购买的商品信息生成订单,从而得到订单号
- 预支付: 根据订单号得到支付的参数
async payGoods(order_number) {
// 获取支付参数
const {
message: {
pay
}
} = await getPayParams({
order_number
})
//
},
- uni.requestPayMent: 支付,需要很多参数
// 发起微信支付
const [err, res] = await uni.requestPayment(pay)
if (err) {
uni.showToast('取消支付,跳转订单页面')
} else {
uni.showToast('支付成功,跳转订单页面')
}
17. tabbar页面切换如何传递参数
18. uni-app从头开始写项目
18. 1介绍
- 原生小程序和vue的结合
原生(写法相同): 组件\生命周期\api
vue: 数据绑定\数据渲染\事件绑定\逻辑的定义\计算属性\watch\过滤器\自定义指令\vuex 等等逻辑的处理
独立:
- App.vue (app.js和app.wxss)
- pages.json(app.json): 配置页面的路径和窗口的外观,全局组件也在这里进行注册,tabbar也是在该位置进行配置
- manifest.json打包配置: 不同的平台配置不同
18. 2创建项目
18.3 运行项目
18.4 外观配置
在pages.json中通过globalstyle进行配置
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
18.5 tabbar的配置
在pages.json中通过tabbar进行配置
"tabBar": {
"list": [
{
"pagePath": "页面路径",
"iconPath": "图标路径",
"selectedIconPath": "选中的图标路径",
"text": "文本"
}
],
},
18.6 创建全局组件
- 在项目根目录创建components目录
- 右键创建组件
- 直接使用
18.7 新建页面
在pages上右键进行新建页面
填写创建页面的信息
设为首页,可以在pages.json中进行配置
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
,{
"path" : "pages/home/home",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
],
18.8 组件库(vant-weapp)
- npm init -y
- 下包
npm i @vant/weapp -S --production
- 在项目的根目录创建一个wxcomponents目录
- 修改名字
- 定义全局组件
"usingComponents": {
"van-button": "/wxcomponents/weapp/button/index"
}
18.9 封装request
- 使用flyio
npm i flyio
- 在utils目录下新建request.js
- 接口还需要在小程序管理后台中配置服务合法域名,如果目前还没有合法域名,
18.10 vuex状态管理
- 无需下载,直接新建store目录,新建index.js, 借助createLogger进行vuex的数据调试
19. 原生小程序开发项目
19.1 创建项目
- 利用微信开发者工具进行创建
- 填写项目基本信息
19.2 项目清理
- 创建新的首页,app.json中pages选项下新增路径,页面自动生成
- pages下新建目录,在目录下在新建page也会自动生成,同时路径也会自动生成
19.3 封装request
- 对wx.request进行二次封装
const baseURL = ''
export function request({
url,
method = "GET",
data
}) {
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + url,
method,
data,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
19.4 定义数据和渲染数据
- 定义到data,
- 数据渲染
19.5 数据修改和事件绑定
- 数据修改
- this.setData({要修改的数据}), 既可以更新数据也可以更新视图
- 事件绑定
19.6 组件注册
- 全局组件
- 注册组件: 在app.json中的
19.7 页面导航
- 声明式导航
- 普通页面:
- tabbar页面:
- 后退:
- 编程式导航
- 普通页面: wx.navigatorTo({url: ''})
- tabbar页面: wx.switchTab({url: ''})
- 后退: wx.navigatorBack({delta: 1})
- tabbar页面跳转传参
19.8 生命周期
- 应用的生命周期
- onLaunch: 小程序初始化(加载一次)
- onShow: 每次看见小程序
- onHide: 小程序隐藏
- onError: 小程序出错
- 页面的生命周期
- onLoad: 页面加载(加载一次) 获取到导航参数,数据请求
- onShow: 页面显示,
- onReady: 页面加载完成(加载一次)
- onHide: 页面隐藏
- onUnload: 页面销毁
- 组件的生命周期
20. 小程序如何跳转另外一个小程序?
21. 小程序最近更新了什么?
22. 小程序地图
23. 小程序嵌套过深,导致跳转失效?
- 出现问题的原因:
小程序的页面栈最多10层,之前是5层,所以一旦超过10层,跳转则无反应
- 解决思路
合理使用navigateTo、navigateBack、redirectTo、reLaunch
方式一:可以通过获取页面栈信息getCurrentPages,以及即将跳转的页面是否在页面栈中,如果有直接利用navigatorBack进行返回
方式二:可以通过获取页面栈信息getCurrentPages,是否超出限制,如果超出限制利用redirectTo进行跳转或者wx.reLaunch进行跳转
方式三:因为redirectTo/reLaunch跳转,会销毁当前页面栈进入另一个页面,a、b、c,b通过redirectTo到c,再返回会进入a,这个问题解决,可以维护自己的页面栈列表,进行进栈,弹栈维护,从自己维护的列表中取对应的页面,对应的往后退。
24. 小程序打包文件太大如何处理?
- 删除无用代码、组件
- 图片压缩链接
- 分包处理
25. 小程序如何下载文件?
小程序有内置的下载文件api, wx.downloadFile进行文件下载,单次下载允许的最大文件为 200MB
参考链接
26. 小程序项目选择uni-app的原因?
- 跨终端(但是你的项目没有多终端,只有小程序,说这个就不合适了)
- 开发风格贴近vue, 开发舒服
- 开发效率比原生效率快
- 微信小程序具备的uni-app均具备
- 微信开发者工具没有vscode和hbuilderx好用
27. 小程序内嵌h5页面?
七、浏览器相关&HTTP
跨域
跨域的原因是同源策略
同源策略是浏览器提供的一种安全机制,可以防止跨站脚本攻击
也就是A网站请求B网站的资源,是否能够使用的问题
同源策略:协议(http/https)、域名/IP地址、端口号,一致则同源,代表是同一个网站,资源共享
有一项不同既不同源,代表是两个网站,此时资源不共享
跨域的本质: 浏览器
为什么会出现跨域?
当下,最流行的就是**前后分离**项目,也就是**前端项目**和**后端接口**并不在一个域名之下,那么前端项目访问后端接口必然存在**跨域**的行为.
解决:
JSONP: 利用的不是xhr请求, 利用的script标签的src可以跨域, 请求接口资源,同时携带callback回调函数名字, 将数据传给回调函数, 解决get不能解决post
cors: 后端开启
正向代理服务器原理图解: 只能用于开发期间具体配置:
vue.config.js
devServer: {
proxy: {
'/api': {
target: '接口url地址'
}
}
}
上线: nginx\将项目放置统一服务下
nginx: 反向代理
在nginx服务器nginx.conf配置文件
server {
listen 8083;# 监听的端口
server_name localhost; #监听的主机名 也可以是域名或者ip地址
location ~ /api/ {
proxy_pass http://localhost:8084;
}
location ~ /prod/ {
proxy_pass http://localhost:8085;
}
}
地址栏输入url过程
-
url解析:是合法url还是搜索关键词
-
dns解析:本地/hosts/dns服务器
-
TCP链接:通过ip地址/3次握手,1次:确认客户端发送能力,服务端接收能力;2次:确认服务端的发送能力、接受能力;此时服务端不知道客户端的接受能力;3次:确认客户端的接受能力
-
发起http请求
-
响应请求
-
页面渲染: html 生成dom树,style生成css规则树,dom树和css规则树生成render树,render树进行layout(谷歌重排)/reflow(火狐回流),render树上的paint和paint setup事件将render树绘制为像素信息
16.6ms浏览器干啥了?
dom和css 由 GUI引擎线程进行解析的
js由JS引擎线程(v8)解析的
浏览器的刷新频率60hz,16.6ms一次
刷新一次,会执行js,如果js执行时间超过16.6毫秒,css样式重绘重排丢帧,原因就是js引擎线程和GUI引擎线程是互斥的
window.requestIdleCallback():
_requestIdleCallback_利用的是帧的空闲时间
时间切片, 没有执行完放到下一帧,余出时间渲染css -
断开链接: 是否有keep-alive,有:关闭网页断开,网页运行期间,如果有新的请求,不会重新链接断开; 没有keep-alive;请求完毕断开链接,下次请求重新链接
-
4次挥手:1. 客户端发出断开/等待,客户端进程发出连接释放报文,所以FIN=1;2. 服务端同意,响应服务端收到FIN之后,如果同意断开就发回一个ACK确认,ACK=1。根据规定,确认号ack=u+1,然后带上一个随机生成的序列号seq=v。此时处于半链接,有可能有数据没发送完,继续发送未发送完的数据;3. 服务端发送完成/等待状态,4. 客户端确认收到数据,服务器只要收到客户端发出的确认就进入关闭状态。服务器结束TCP连接的时间要比客户端早
如何应对xss攻击
概念: 跨站脚本攻击,原理: 通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序
场景: 评论功能,或者通过v-html渲染
解决: 采用三方库,dompurify,使用比较简单,装包,通过该包中的sanitize方法可以将要渲染的数据提前进行过滤,过滤掉可能会有攻击的代码
csrf攻击
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网站攻击方式
攻击原理:利用请求自动携带cookie
攻击流程:
- 张三登录某银行网站(mouyinhang.com)
- 登录成功将用户信息存储到cookie中
- 在银行网站引导张三进入(攻击网站)
- 再攻击网站中向银行网站发送转账请求(mouyinhang.com/zhuangzhang…
- 攻击成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求 自动携带cookie => 服务端 用户信息 处理成功
A网站 => 登录 => 用户信息存到cookie => 转账 发起请求携带referer/www.a.com => 服务端 获取referer, referer是合法的,相应成功,转账成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求,没有携带referer 自动携带cookie => 服务端一看,没有referer, 不知道是请求是在哪里过来的,相应失败
解决方案:
SQL注入
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令 或访问未被授权的数据。
ddos
DDoS 攻击,全称是 Distributed Denial of Service,翻译成中文就是分布式拒绝服务
攻击者短时间内向目标服务器发起大量请求大规模消耗主机资源,从而导致其他人无法正常访问
解决方案:
后端/运维处理
高防服务器:花钱,有人保护
黑名单:拉黑ip
ddos清洗:会对用户请求数据进行实时监控,及时发现 DOS 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。
cdn加速
HTTP和HTTPS
区别
- 默认端口
- http:80
- https:443
- 传输过程
- http:明文,被截获不安全
- Https:密文,截获的是加密后的
- ssl证书:_SSL 证书_就是遵守 SSL协议(它是在传输通信协议(TCP/IP)上实现的一种安全协议,采用公开密钥技术),由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能
- 是什么: ssl 证书
- 颁发: CA
- 干啥: 数据传输加密
- http:不需要
- https:需要
- http:基于7层协议中的应用层(提供应用程序间的交换和数据交换)
- https:基于7层协议中的传输层(传输层协议提供计算机之间的通信会话,并确保数据在计算机之间可靠地传输。)
对称加密
发送方和接收方使用同一个密钥(一串字符串)进行加密和解密
- 服务端使用密钥进行加密
- 客户端使用密钥进行解密
但是第一次要传输一次密钥,如果密钥被拦截,就被破解了
性能好,速度快,缺点,密钥被拦截被破解
非对称加密
一个公钥,一个私钥
公钥加密,私钥解密
- 服务端(私钥)
- 客户端(公钥)
- 客户端公钥加密传输(被拦截无法解密,需要用私钥)
- 服务端通过私钥解密
https
使用非对称加密进行密钥传输,使用对称加密进行数据传输
如何保证首次传输的公钥是安全的,需要网站机构,进行网站和公钥的登记(CA机构,颁发证书,安全可靠)
状态码
-
2xx: 请求处理成功状态码
- 200: 客户端发的请求被服务端正常处理
- 201:请求成功创建新的资源
- 202: 服务接受请求但还没处理
- 204: 请求成功但没有返回内容
-
3xx:请求重定向
- 301: 永久重定向
- 302: 临时重定向
- 304: 服务端资源没有变化,使用强缓存
-
4xx: 请求失败
- 400: 客户端请求的语法有误,服务端无法处理
- 401: 当前请求需要验证权限,一般token问题
- 403: 服务端拒绝访问资源
- 404: 资源不存在
-
5xx:服务器问题
一、HTML5/CSS3
1. 盒子水平垂直居中?
- 父盒子开启flex,并设置主轴居中,侧轴居中
- 父盒子开启flex,子盒子设置margin:auto
- 子绝父相,子盒子设置top和left值为50%,利用transform基于自身回去-50%
- 子绝父相,子盒子设置上下左右均为0,在设置margin:auto也可以
2. 盒模型
- 四部分: 内容+内边距+外边距+边框
- 盒模型可以进行切换: box-sizing: border-box; ie盒模型/c3盒模型/怪异盒模型, 宽高定死,不受边框内间距撑开
- 默认是content-box: 标准盒模型, width+内间距+边框
3. flex:1
flex:1原理实际上是几个三个属性
4. CSS3新属性
- c3盒模型box-sizing
- flex布局
- transition过渡
- transform2D转换
- background-size背景缩放
- border-radius圆角
- 等等,不用都说说一些常用的就ok
5. BFC
概念: 块级格式化上下文,说白了,bfc是一种属性,拥有bfc会拥有独立的渲染区域,从而不会影响外界
触发bfc: position为absolute或fixed display为inline-block, table-cell, table-caption, flex, inline-flex overflow不为visible
bfc的应用:
外边距重叠: 触发bfc
浮动导致高度丢失: 触发bfc
清除浮动: 触发bfc
盒子重叠: 触发bfc
6. Less/Scss的特性
css预处理语言
- less/scss的异同或区别
相同点功能:
- 变量声明
- 混入
- 变量插值
- 嵌套
- 运算
- 导入
- 作用域
- 颜色函数
语法区别:
- 变量声明
- less: @
- scss: $
- 混入
- less: 定义混入通过点(.),使用混入也是通过点,如果没有参数可以省略小括号
- scss:定义通过@mixin,使用通过@include
- 变量插值
- less: 通过@{}
- scss:通过${}
- 颜色函数
- LESS 使用名为 spin() 的函数;
- SCSS 使用名为 adjust_hue() 的函数。
- 条件语句循环语句
- less: 不支持
- scss: 支持
后缀名为.sass的sass3.0之前,比较恶心, 不用花括号,固定的缩进格式3.0之后后缀scss,和less差不多
7. vw/vh/px/em/rem/vmin/vmax区别
- vw和vh: 相对于视口的大小,100vh等于视口的高度,100vw等于视口的宽度
- px: 固定大小
- em: 大小相对于父元素的font-size: 20px 1em = 20px
- rem: 大小相对于html的font-size
- vmin: 大小等于最小的视口: 100vmin等于最小的视口
- vmax: 大小等于最大的视口
8. 让Chrome支持小于12px 的文字方式有哪些?
pc端的浏览器,字体默认最小只能显示12px
- transform: scale(0.5)
- -webkit-text-size-adjust: none; 字体就可以按照font-size去显示
谷歌27版本之后就不能使用了
9. 移动端适配方案
10. 重绘/回流(重排)
- 重绘(repaint)
- 概念:即重新绘制
- 导致重绘:外观、风格发生变化即发生重绘,例如color、background、box-shadow、background-size
- 影响:重绘不一定回流
- 回流(重排):
- 概念:当渲染树中元素的尺寸大小、布局等属性改变时,浏览器的布局需要调整,则需要重新渲染DOM。这个过程就叫回流。回流也叫重排(对整个页面进行重新排版)。
- 影响:回流必然会重绘
- 导致重排:width、height、margin、padding、font-size、position等等
- 避免重绘和回流
- 多个样式修改不要一个一个更改,可以通过操作类名,一次统一修改
// js width
// js height
// js color
.active {
width: 100px;
height: 100px;
color: red;
}
二、JavaScript
slice和splice两者的区别
substr和subString
使用过哪些es6?
2. 聊一聊promise
概念:
promise是一个对象/构造函数,es6新增的,更加灵活的处理异步,可以配合async和await将异步代码变为类似同步的同步,也可以解决我们的回调地狱
promise有三种状态:
pending
fullfield
rejected
不可逆
pending => (resolve)fullfield
pending =>(rejecte) rejected
实例方法:
Promise.then\catch\finnaly
静态方法:
all\allsettled\race\any
解决回调地狱:
利用then,会返回一个新的promise,继续调用then,从而构成链式
promise穿透
reesolve的结果会交给then,但是then的参数如果不是回到函数,继续向下传
终级解决方案:
async await
await 用来修饰promise, async用来修饰await就近的函数,async修饰的函数返回值是promise,所以可以继续使用await修饰
静态方法:
all: 可以获取到多个promise处理异步的结果,all发起的异步是并行的,并且Promise.all的返回值是promise,所以可以调用then,这个then,all的所有promise都resolve成功后才执行,有任意一个reject即进入all的catch, all的then返回的结果就是对应的promise返回的数据
allSettled: 可以获取多个promise处理异步的结果,then,不管resolve还是reject都会执行then,then的返回结果是一个数组对象,对象内部会通过status记录状态,通过value记录值,status记录的状态: fulfilled/rejected
Promise.race 使用和 all 一样,但是只返回第一个结果,不管成功或失败
Promise.any 返回第一个成功的结果
Generator
概念: 也是es6的,可以将函数的控制权交出,也可以利用generator更方便的控制异步,实际async和await就是他的语法糖
区分: 星号
如何交出控制权: 通过yield进行控制权交出,通过next逐步调用
如何处理异步: 可以通过yield配合promise达到类似async和await的效果,通过yiled返回promise,在promise中处理异步,等异步成功调用resolve,这样在外部可以通过next.value获取到promise,通过then等待成功后,执行下一次的next
而且对应的自执行generator函数有co库,可以去自执行generator
区别:
new的过程
创建一个新对象
// 将新对象的__proto__ 指向了 Person.prototype
// 将构造函数内部的this指向新对象
// 执行构造函数给this(实例对象)添加属性或方法
// 默认return this
// 如果写了return 看 数据类型
// 数据类型是简单数据类型: return简单数据类型忽略 最终还是return this
// 如果数据类型是复杂数据类型: 最终得到是该复杂数据类型 return this无效
字符串反转
'hello'.split('').reverse()
forin/forof区别
for in:一般用来遍历对象,并且可以遍历原型对象,不建议循环数组,可以循环
for of:不可以进行遍历普通对象的,可以遍历数字字符串数组和 newSet 对象等等,并且可以进行 break 终止,不可以 return
中断for
统计数组中出现次数最多的元素
// 统计出出现次数最多的元素 返回次数和元素
const arr = [1, 2, 3, 2, 3, 2, 3, 7, 8, 7, 6, 7, 5, 7, 0, 7, 7, 7, 7, 2, 5, 5, 5, 5, 5, 5]
function repeatCount(arr) {
// key 是元素 value: 次数
const obj = {}
let max = 0
let maxItem = null
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (obj[item]) {
obj[item]++ } else {
obj[item] = 1
}
if (obj[item] > max) {
max = obj[item]
maxItem = item
}
}
console.log(maxItem, max)
}
repeatCount(arr)
递归使用场景
export function transListToTree(data, pid) {
const arr = []
data.forEach((item) => {
if (item.pid === pid) {
// 当前: item 就是1级数据 item.id
const children = transListToTree(data, item.id)
if (children.length) {
item.children = children
}
arr.push(item)
}
})
return arr
}
聊一聊异步
为什么有异步?
js是单线程,如果js语言是多线程,在操作dom容易混乱,所以js就是单线程,问就是,如果某一个任务比较耗时,阻塞运行,js把耗时交给浏览器执行,交给浏览器执行的这些耗时任务: 异步任务
js在es5之前无法发起异步,es6的Promise可以发起异步
异步执行流程
- 主线程先判断任务类型
- 如果是同步任务,主线程自己执行
- 如果是异步任务,交给宿主环境(浏览器)执行
- 浏览器进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列,依次放入
- 等主线程任务全部执行完后,发现主线程没有任务可执行了,会取任务队列中的任务,由于任务队列里是依次放入进来的,所以取得时候也会先取先进来的,也就是先进先出原则
- 在任务队列中取出来的任务执行完后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训
es6模块化和commonjs模块化的区别
- es模块化的导入: import import {} import * as
- es6模块化的导出: export export default
- commonJs模块化导入: require
- commonjs模块化导出: module.exports module
继承的实现方式
闭包
垃圾回收
箭头函数和普通函数的区别
- 箭头函数this指向上下文,普通函数this看调用方式
- 剪头函数没有arguments对象,普通函数具备arguments对象
- 箭头函数没有prototype, 普通函数具备prototype
- 箭头函数不能用做构造函数,普通函数可以用做构造函数
三、Vue2
1. v-if/v-show的区别?
- 效果: 控制元素显示和隐藏的
区别:
-
v-if
- 原理: 移除或添加dom元素
- 优点: 懒渲染, 默认首次如果是false,元素不会创建, 如果是组件,可以重新触发生命周期
- 缺点: 频繁的删除重建
- 使用场景: dom首次判断是否显示还是隐藏,或者组件需要重新触发生命周期
-
v-show:
2. $route和 router作用?
router: 和new VueRouter完全是同一个对象,全局路由实例 可以使用路由方法进行前进后退跳转等
路由编程式导航都是通过this.$router实现的
3. 聊聊vuex?
vuex的作用? vuex的5个属性? vuex的优缺点? vuex的流程? vuex中的命名空间 ?如何触发mutation、action?
- vuex是什么?
vuex是基于vue状态管理的库 - vuex的作用和优点
实现组件数据共享,同时vuex的数据也是响应式,vuex数据操作远比组件内数据传递方便,方便维护和管理 - vuex的5个属性
state: 存储或定义共享数据,如果像访问数据,$store\mapState
mutations: 修改vuex数据的唯一来源,为了方便后期的调试和维护,当然也可以直接修改state数据,但是很不建议, commit提交mutaiotn
actions: mutations是用来处理同步的,devtools不准确,actions处理异步,dispatch,但是actions只用来处理异步,如果想修改数据,context中的commit提交mutations,
getters: 基于state进行派生数据
moudles: 将vuex数据进行模块化,方便维护,划分模块,每个模块中都拥有state/actions/mutaions/getters,每个模块可以设置命名空间,如果不设置,实际mutations或actions中的方法和全局的并无区别,开启命名空间后,必须通过模块名去访问
说一下两个流程
同步流程: commit > mutations > state
异步流程: dispatch > actions > mutations > state
开发: 统一异步流程 - vuex中的缺点\解决方案
实际vuex也是有缺陷的,不能持久化,想解决该问题/利用本地存储,或者利用插件实现自动存储vuex-persistedstate - vuex-persistedstate:
- 默认还是localstorage
- cookie
- sesstion
- 应用场景
在开发项目的时候,一般用户信息或者token/或者网页的配置数据存入vuex
4. vue.use作用?
Vue.use({install(){}}, 1,2)
- 参数1: 可以传一个函数或者对象,如果是对象,对象中需要一个install函数,如果是一个函数,相当于是install函数
- 其他参数: 依次传给了install函数
install函数默认第一个参数是Vue,后面的参数来自于Vue.use后续参数
Vue.use本质是无法注册全局组件或者给Vue原型添加方法,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过Vue.component注册了全局组件或者给Vue.prototype手动添加方法
5. $nextTick有什么用?
作用
问题: vue中数据发生变化同步视图是异步的,所以数据发生变化不能立即获取最新的视图, 想获取最新的视图通过this.nextTick函数和vue源码中的nextTick函数,是同一个函数,源码中调用nextTick函数为了异步更新视图,我们在使用this.$nextTick函数的时候回调会在源码的nextTick函数之后执行,所以获取到最新的视图,
源码的nextTick函数异步原理利用的就是向下兼容可宏可微,源码中依次进行判断Promise/mutationobserver/setImmdiate/setTimeout
keep-alive
作用: 缓存不活动的组件
使用: keep-alive组件包裹路由占位
使用场景: 列表进详情需要把列表页缓存,将列表的滚动条的位置记录下来,当重新进入列表页的时候设置滚动条位置
按需缓存:
- kepp-alive上的include或exclude匹配的是组件的name
- 结合路由的meta,添加一个缓存标识,通过在keep-alive缓存的位置获取当前路由信息上的meta中的缓存标识进行控制是否显示keep-alive包裹router-view还是直接显示router-view
生命周期:
被缓存的组件激活: actived
被缓存的组件失活: deactived
6. 生命周期
组件
本质:就是函数,会在特定的阶段自动调用,生命周期函数
作用:可以让我们在某个阶段做一些事情
4个阶段
阶段1: 创建阶段
- beforeCreate: 开始创建实例,此时实例的数据和方法还没有
- created:
- 作用:实例已经创建完成,数据和方法都已存在
- 应用场景: 发送请求获取数据, 页面进入的方法需要立即执行
- 扩展: 如果非要在created中操作dom也可以,利用$nextTick
阶段2: 挂载阶段(dom)
- beforeMount: 开始挂载dom,真正的dom元素还没有挂载完成, 操作dom不可以
- mounted:
- dom已经挂载完成,可以操作真实dom
- 应用场景: 页面已进入操作dom
阶段3: 更新阶段
- beforeUpdate: 数据变了,但是视图还没变
- updated: 数据和视图都变了
阶段4: 销毁阶段
- beforeDestory: 即将销毁
- destoryed: 组件销毁
- 应用场景: 清除挂载在window相关的行为,例如定义器\事件
父子
创建挂载阶段
父beforeCreated > 父created > 父 beforeMounted > 子beforeCreate > 子created > 子beforeMount > 子>mounted > 父mounted
更新阶段
如果更新的数据不涉及到子组件,只会父组件更新 父beforeUpdate > 父updated
如果更新的数据涉及到子组件, 父beforeUpdate > 子beforeUpdate > 子updated > 父updated
销毁阶段
父beforeDestory > 子beforeDestory > 子destoryed> 父destoryed
7. 插槽
默认插槽:
直接在组件标签中间进行传递
组件内部通过vue内置的slot组件进行接收
如果组件想给组件内部不同的区域进行自定义,需要使用具名插槽
多插槽: 具名插槽
直接在组件标签中间进行传递,但是需要通过slot或#指定是哪一个具名插槽
<sg-button>
<span slot="icon">🎨</span>
</sg-button>
组件内部: 通过多slot进行接收,slot通过name进行区分
<div @click="$emit('click')" :class="[type, size]" class="btn">
<slot name="icon" />
<!-- 组件内部通过slot向外传递数据 -->
<slot name="text" />
</div>
不管是普通插槽还是具名插槽都无法在父组件的插槽结构中获取子组件的数据,所以还需要使用作用域插槽<br />**作用域插槽**
直接在组件内部进行传递,但是同时可以通过v-slot="scope" 获取组件内部通过slot组件传递的数据
<sg-button>
<!-- v-slot 可以获取到对应的slot上所有属性 -->
<template v-slot:text="scope">
<span>注册</span>
{{ scope.age }}
{{ scope.data }}
</template>
<span slot="icon">🎨</span>
</sg-button>
组件内部通过slot组件属性向外传递数据,冒号传递的是变量,否则传递的就是字符串
<div @click="$emit('click')" :class="[type, size]" class="btn">
<slot name="icon" />
<!-- 组件内部通过slot向外传递数据 -->
<slot age="19" :data="count" name="text" />
</div>
应用场景: element ui很多组件,例如树形\表格
8. v-model
作用: 数据双向绑定(mode >> view),进行组件通信
原理: v-model就一个语法糖, 动态绑定了value和注册了input事件
使用场景:
在表单中使用
在组件上使用, 既要将数据传给子组件,子组件还要修改数据的时候,此时可以使用v-model进行简化
v-model有一个缺点: 一个组件上只能使用一次
9 .sync修饰符
作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)
原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发
.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件
10. vue组件通信(传值)
- 父传子: 父组件属性方式传递,子组件(props)
- 子传父: 子组件通过$emit,父组件通过自定义事件
- eventbus: // 发布订阅模式
class EventBus {
// 记录事件和回调
clientList = {
send: [() => {}, () => {}],
}
// 订阅事件,参数event事件名,callback 回调
$on = function (event, callback) {
// 将事件和函数记录
// 如果事件记录过,那就将回调push
if (this.clientList[event]) {
this.clientList[event].push(callback)
} else {
this.clientList[event] = [callback]
}
}
$emit = function (event, val) {
if (!this.clientList[event]) {
throw new Error(event + ' is not a event')
}
this.clientList[event].forEach((cb) => {
cb(val)
})
}
}
const eventBus = new EventBus()
// 订阅事件
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$on('send', (val) => {
console.log('send订阅' + val)
})
eventBus.$emit('send', 1
-
本质: vue实例对象
-
实现原理: 利用发布订阅模式
- 传递数据的组件,通过eventBus的$emit发布自定义事件,并传递数据
- 获取数据的组件,通过eventBus的$on订阅自定义事件,并通过回调函数接收数据
-
设计模式:发布订阅设计模式: 一对多的依赖关系, 发布方是一,依赖方是多,发布方发布消息,所有的依赖(进行订阅了)方收到通知
-
vuex
-
ref获取子组件
-
v-model
-
.sync
-
$children: 可以获取当前组件的所有子组件,并以数组的格式返回数据
-
$parent: 可以获取到当前组件的父组件, 返回当前组件的父组件
-
provide/inject: 跨组件传值provide,进行给后代组件提供值,inject在后代组件上进行注入值
-
$attrs: 获取到当前组件节点上的所有属性集合
11-虚拟dom
什么是虚拟dom?
虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom
为什么出现?
虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)
如何实现高效更新?
初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法
diff算法?
同级比较.深度优先,而且采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升
diff对比之后的情况?
元素不同: 删除重建
元素相同,属性不同: 元素复用,属性更新
v-for:
无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能
无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响
有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比
12-mixins
mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可
可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中
优先级:
- 生命周期,组件和混入的都会调用(混入的先调用
- data/computed数据: 进行合并,冲突以组件为主,mixins被覆盖
- methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
13-路由模式的区别
- abstract支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
- 是否有#/
hash有
history: 没有
- 是否经过服务器
hash: 不会
history: 会
- 是否需要后端配合
hash: 不需要
history: 需要
- 底层原理
hash: 跳转 window.location.href, 监听 onhashchange
history: 跳转history API, history.pushState和history.repleaceState 监听 onpopState(前进/后退)
封装的方法: pushState(history.pushState/history.repleaceState)
14. 用过哪些修饰符
修饰符: 在指令后面通过.语法使用的
15. vue优缺点
优点:
- 简单易用
- 渐进式
- 响应式
- 双向数据绑定
- 虚拟dom
- 耦合低
- 用户体验好
- 结合vuerouter实现spa
缺点:
16. vue-i18n
**概念: **
实现国际化(多语言),项目可以进行语言的切换
使用:
- 装包
- 在单独的模块中进行封装/引入/注册/配置语言包
- 封装一个组件用来进行语言切换(this.$i18n.locale)
- element-ui: 直接导入element-ui的语言包,同时在use element ui时候并配置i18n
17. computed和watch的区别
computed:
- 计算属性: 数据发生变化,重新进行计算从而得到新的数据
- 逻辑计算的时候一般使用计算属性
- 定义的时候是方法,使用的是属性
- computed内部return依赖的数据发生变化,,逻辑会进行重新计算
- computed实际只会计算1次,只要数据不变,不管使用多少次,后续的使用的是缓存
- computed不能处理异步
watch
- 进行监听,监听某一个数据如果发生变化,我们想进行逻辑的处理
- watch可以监视:props\data\computed$route
- 当监听的数据发生变化,对应的函数会重新执行
- 监视的是一个对象: 开启深度监听 deep:true
- watch内部是可以处理异步的
项目应用:
计算属性:
vuex的getter/state数据: 都映射到计算属性中使用
统计审批次数:入职/离职
收货地址: 后端返回的是多个数据
小程序: 订单金额,订单数量
watch:
父组件异步向子组件传递数据
路由变化但是组件复用了
封装的对话框组件:数据变化,需要将数据通过自定义事件传给父组件
18. watch原理
- vue内部通过initWatch进行初始化,循环watch对象,但是监视的值可以写成数组,所以进行两层循环
- 每一个监视的回调都会创建一个用户watcher,内部通过createWatcher创建
- watcher内部兼容了三种watcher, 渲染watcher/用户watcher/计算属性watcher
- 如果是渲染watcher,this.getter等于expOrFn,如果是用户watcher通过parsePath生成了用户的getter,判断expOrFn是否为函数,还是一个字符串
- 数据发生变化进入更新阶段,执行进入run函数,调用创建watcher传入的回调,这个回调实际就是watch监视的数据的回调
- 旧值在watcher初始化的时候调用的get函数中获取到
- 新值在run里执行的get函数中获取到,将新旧交替,将新旧值传给回调
- watch的使用还可以通过实例.watch创建用于watcher
19. computed原理
- 将计算属性的get和set转换为Object.defineProperty的get和set,并且计算属性如果写为简写的方式,函数直接交给了Object.defineProperty的get
- 循环计算属性给每一个属性创建计算属性 创建计算属性watcher
- 缓存原理:重写计算属性Object.defineProperty的get, 通过createComputedGetter进行生成,返回一个函数,在函数内部主要通过获取到计算属性对应的watcher的dirty,默认为true,计算过一次变为false,根据dirty判断是否重新计算(evaluate)重新计算,
- 依赖的数据发生变化,重新渲染页面,依赖的数据收集渲染watcher:
- 通过stack记录多个watcher
- 在计算属性get中判断是否还有watcher
- 通过渲染watcher的depend方法内部循环deps,获取到每一个dep,计算属性依赖的每一个数据,每一个dep收集watcher,dep内部的depend方法
20. computed和watch执行顺序
初始化阶段computed会比watch先执行,原因就是在initState中先判断的是否有computed,初始化computed,再去判断是否有watch,如果有watch再去初始化watch
21. 单项数据流
数据是单项流动,数据可以从父组件流向子组件,父组件的数据发生变化,会自动同步子组件,反之不允许
22. v-if和v-for为什么避免同时使用
v2中:
v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,利用v-if进行移除
- v-if写到外层
- 先通过计算属性将数据计算好
23. vue 组件中的 data 为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
24. 简易数据响应式原理
- 概念: 数据发生变化,数据使用的地方会进行同步更新
- vue中数据响应式的核心Object.defineProperty可以对对象的进行定义属性,内部有两个方法get,set,get数据读取会触发,set,数据变化会触发
- vue劫持数据的时候必须要通过一个数据进行周转,出发get或者set直接用原数据,就会堆栈溢出
- Object.definePropert一次只能劫持一个数据,所以源码中需要对数据进行循环遍历劫持,类似于递归的方式对所有的数据进行劫持
- 对象新增的数据是无法劫持到的,因为新增的数据,需要劫持的数据已经被劫持了,对象新增的数据也就不是响应式的,vue中通过this.$set解决
- 数组的下标修改数据Object.defineProperty.可以劫持的到,vue因为性能和用户体验的关系,取消了vue中检测数组下标的变化
- 数组的7个方法是响应式的原因是因为vue对7个方法进行重写了,并不是完全重写,利用了切面编程,对方法进行封装和逻辑的处理,最终还是会调用原始的方法
- 观察者设计模式: 一对多的数据依赖关系,和发布订阅的区别,没有调度中心,被观察者和观察者直接通信, 观察者内部会有update方法进行消息更新,被观察者内部会有add收集观察者的方法,通知观察者的方法notify,通过notify通过所有的观察者,观察者通过update更新消息
- vue源码中: get中进行依赖收集,会给每个数据添加一个watcher观察者(每一个watcher对应的就是一个组件)
- set中: a数据发生变化通知观察者(组件)进行更新,vue中更新是以组件为单位
25. vue源码
数据响应式数据劫持
- data数据通过选项对象传递给Vue构造函数,Vue类接收数据,将数据传递给initMixin方法,方法内部通过_init接收数据,最终将数据initData,初始化data数据,
- 首先判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data进行数据劫持
- 将data数据传递到observe函数中,对data类型进行判断,将data传递到Observer类中的walk,walk起始函数
- 在walk函数中循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
- 处理通过this实例访问data选项数据,此时在initData内部通过proxy将vm进行劫持,当访问vm上的数据时,代理到_data数据等同于data,进入walk内部的数据劫持
- Observer中会给需要劫持的数据添加一个__ob__是否被劫持的标识
简易版本: 核心
data选项数据传入到initMixin(初始化所有), 内部将传入到initdata(初始化data数据), 获取data数据, 判断data数据类型,如果是函数通过data.call(vm),如果是对象,直接返回对象,接下将data数据传入到observe函数中,将数据传入,判断数据是否是对象, 如果是对象将数据传递给Observer类中的walk内部循环数据,将数据传递给defineReactive,对每一个数据进行劫持,继续调用observe
数组劫持:
Observer中进行判断数据类型是否为数组,如果是数组,走observeArray方法,对数组中的复杂数据类型进行劫持
7个数组方法重写: ['push', 'pop', 'shift', 'unshift', 'slice', 'reverse', 'sort']
AOP 切面编程实现, vue内部重写了这7个方法,但不是完全重写,自身的push unshift功能还是保留所以还会调用原来的方法,又对unshift push splice方法的增加的数据进行了劫持,其他的只做了通知notify更新通知操作
模板编译:
compileTofunction这个方法生成render函数,通过render函数生成虚拟dom,虚拟dom疏对比.利用patch方法进行dom的更新
通过parseHtml方法解析模板,解析模板的过程就是使用大量正则匹配,生成AST语法树,将AST语法树生成了render函数
数据响应式数据发生变化:模板更新:
创建了一个watcher,内部通过get调用了updateComponent这个方法: 编译更新整个组件内部会进行虚拟dom树的对比
收集watcher:
编译模板模板中用到哪些数据,给哪些数据收集watcher,通过dep进行watcher收集,每一个数据通过闭包的方式都有自己dep,通过deo收集watcher,所以每个数据都收集了自己的watcher, 数据劫持的get中收集watcher,拿不到watcher,通过Dep.target进行中转, watcher中的getter调用前将this存给Dep.target,然后在get中将Dep.target进行
通知:
数据发生变化触发set,获取到数据对应的dep,dep中通过subs存着watcher,dep中有一个notify方法循环收集所有的watcher,调用watcher的update方法进行组件更新 => get >getter > updatecomponent
vue数据响应式:
观察者模式:多对多
dep > watcher
watcher > dep
dep => depend > 调用watcher的addDep方法进行收集dep, 通过dep收集watcher通过addSub
异步更新:
quereWatcher watcher队列收集, 根据watcher的id进行将watcher存入quere对列中,调用watcher的get方法,不能同步调用,只会更新第一个数据的视图,保证所有数据更新完后在统一的更新视图,将watcher的get方法放到异步任务里
next原理:
this.$nextTick就是vue异步更新视图中封装的nextTick方法,利用异步更新视图,异步优先使用微任务,因为同步代码更新完成后进入微任务更快,优先使用Promise.resolve,如果Promise没有,使用MutationObserver也是微任务,如果MutationObserver也没有使用setImmediate是宏任务,他比setTimeout快,如果setImmediate也没有使用setTImeout
总结:
数据劫持 + 模板编译 + 观察者模式 + 异步更新视图nextTick
数据劫持:
数据传入到initMixin,将数据传入到initData,获取data数据,可能是函数/对象, 将数据传给observe函数中,数据判断是否为对象,以及是否被观测过__ob__,继续将数据传入到Observer中的walk函数,循环对象,对每一个对象通过defineReactive函数进行数据劫持
Observer中判断数据是否为数组,如果是数组observeArray对数组的复杂数据进行劫持,数组的7个方法通过AOP切面变成进行重写
模板编译:
通过compileTofunction生成render函数,内部通过大量的正则匹配生成AST语法树,将AST语法树生成render函数,通过render函数创建虚拟dom,将虚拟dom渲染成真实dom
观察者模式:
依赖收集,依赖通知
在数据劫持中get函数中进行watcher收集,因为watcher对应的一个组件的更新,通过dep进行收集,观察者模式多对多,dep收集watcher,watcher也会收集dep,数据发生变化在set中通过dep.notify进行依赖的watcher通知
异步更新视图:
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
26. vue 异步队列
异步更新视图:
通过quereWatcher保存所有的watcher队列,通过quere数组进行保存,同一个watcher不会进行重复保存,保证所有的数据都发生变化后再去更新视图调用队列中所有的watcher的update方法,所以应该通过异步去调用,此时封装了nextTick,和用户使用的$nextTick是同一个,本身是可宏可微, 向下兼容promise\mutationObserver\setImmediate\setTimout
27. 路由守卫/路由钩子
- 全局守卫
对所有路由全都生效,进行权限的处理,比如是否登录的路由权限控制
- 路由前置守卫
beforeEach
- 路由后置守卫
arterEach
- 路由独享守卫
针对某一条路由进行单向控制
beforeEnter
- 组件内守卫
针对组件
beforeRouteEnter: 只要通过路由,不管是哪一条路由显示该组件就会触发,控制改页面是否展示
beforeRouteUpdate: 路由组件被服用时,例如/a/1 进入a页面,在当前组件重新进入/a/2 显示a页面,a页面就会被复用, 组件没有销毁,组件不会重新走created不会执行,此时可以使用beforeRouteUpdate进行解决
beforeRouteLeave: 应用场景: 表单页面的返回,提示用户当前表单未提交是否离开,离开当前路由组件:
28. 获取数据created和mounted
为什么有时候在created获取数据,为什么数据可视化大屏可能会在mounted中获取数据
如果涉及到dom的操作了,应该在mounted中
如果没有涉及到dom操作,在created中
29. 数据不响应的情况
- 对象新增属性
- 数组通过下标直接更改数据: arr[0] = 1 arr[0].name = '李四'
- 对象删除
原理:
**解决方案: **
更新对象: this.set(数组. 下标, value)
对象删除: this.$fourceupdate()
上述情况在后面进行了可以支持数据响应式的,上面的也会同步更新
30. spa的优缺点
31. 服务端渲染ssr
- 为什么?
spa单页面应用的seo不友好,spa单页面,由js进行渲染页面,不同的路由规则渲染不同的组件,对应html就是一个空页面,百度爬虫在爬,什么都爬不到,导致seo不友好
- ssr
服务端渲染: 先由服务端将页面解析好返回,返回之后,浏览器进行渲染,直接将页面的内容进行渲染,html不在是一个空页面,爬虫可以爬到
- 配置
路由: 在pages下创建文件,自动生成路由
ui组件库: 在nuxt.config.js中通过plugins节点指定配置文件的路径,在对应的路径文件中进行配置
css全局样式: 可以在nuxt.config.js中的css节点中进行配置
seo优化: nuxt.config.js中通过head进行title和meta的配置
在pages中也可以通过head进行页面级别的配置
- 获取数据
生命周期分为
服务端
nuxtServerInit: 服务端初始化
RouteMiddleware: 中间件
validate: ok
asyncData: 获取服务端数据
asyncData中进行获取数据,该生命周期是在服务端执行的,所以不能使用this
客户端
created => fetch(fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。) = mounted
32. vue中使用了哪些设计模式
四、typscript
ts的优势/好处
- 错误前置: ts是属于静态类型语言,先将ts编译为js文件,在编译的过程中就可以发现错误,实际配合vscode插件,写代码的时候就已经可以发现错误
- 对高级语法的兼容
- 项目后期维护会更好
- 代码提示帮助我们减少考虑可以使用哪些数据或方法
- ts自身支持类型推断,所以并不是所有的数据都需要添加类型约束
ts的数据类型
- 原始数据类型
number/string/null/undefined/boolean
Array/Object/function
- ts新增的
元组/联合类型/字面量/枚举/any/never/unknow
any: 可以赋值任意数据类型
unknow: 可以赋值任意数据类型,但是不能任意调用属性或方法
什么是元组?
联合数据类型?
keyof和typeof区别
泛型
作用: 用来约束某个数据配合多个类型使用,保证类型安全,从而达到复用
例如: {a: '', b: '', c: 12}, {a: '', b: '', c: boolean}
泛型使用: 通过尖括号<定义类型变量>,某个数据类型不确定可以使用类型变量,使用泛型的时候<传递具体的类型>
泛型: 泛型函数/泛型接口/泛型type
type和interface的区别
type:
- 给任意数据类型进行命名复用
- 可以进行扩展通过&扩展type/interface
interface:
五、Vue3
1. v2和v3的区别
- api
- v2: options API(选项式API):优点:对于新手容易,按照固定选项实现功能缺点: 对于代码的阅读\维护\复用(逻辑)不是很友好,v2中也可以通过Mixins实现逻辑的复用
- v3: composition API(组合式API)优点: 功能代码组合在一起
- 使用
- v2: 配合vuex,对与ts的支持不是很好,不允许使用多个根节点
- v3: 推荐配合pina,底层就使用ts重构的,对ts支持很好,多个根节点
- 性能
- v3的性能会比v2快1.2_2倍:
- v3采用了静态标记,给静态节点打上标记,diff的过程只对比动态节点,忽略静态节点
- 虚拟dom的创建:v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom,v3中数据发生变化,看该虚拟dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom
- 事件函数的缓存: 事件函数每次在模板编译的时候会进行缓存
- v3的tree shaking:实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的,v3的打包比v2的小
- 数据劫持
2. vite和webpack的区别
- vite: 工程化工具
- vite原理: 启动项目不会对项目进行完全打包,启动服务,将资源通过服务进行托管,项目加载而是通过浏览器利用esm(es module)的方式加载资源,使用到资源才会加载才会处理,这实际上是让浏览器接管了打包程序的部分工作。
- webpack 真正的打包: 先读取配置文件,根据配置文件的规则进行打包,读打包的入口文件,根据入口文件的依赖进行打包,js转低级语法:babel,三方文件:loader,html:html-webpack-plugin,css文件的抽离:mini-css-extract-plugin,启动浏览器,浏览器加载资源,直接加载不会再次处理
3. vue3 如何定义响应式数据
- ref: 将简单数据类型或复杂数据类型处理为响应式
模板中使用自动解套value,在js中使用需要通过value访问到数据
- reactive: 只能将复杂数据类型处理为响应式,直接用数据不需要value
推荐的使用方式:
4. script setup语法
- 语法: 在script标签上定义setup属性
- 特性:
5. computed计算属性
- computed: 计算属性, 就是vue提供的一个函数.所以需要通过引入vue得到
- 语法: 调用computed函数, 传递一个回调函数,在回调函数内部进行计算,将计算好的数据return出
- 特性: 具有缓存, 多次调用多次定义, 除了可以传递函数,还可以传递对象, 包含get和set函数
6. watch监视
- 概念: 监听数据变化, 也是vue提供的一个函数, 所以需要通过引入vue得到
- 语法:
- 参数1: 监视的数据
- 监视单个数据: 直接写数据
- 监视多个数据: 写数组,数组内放数据
- 监视reactive对象中的某一个属性: 函数返回属性
- 参数2: 处理函数, 两个参数
- 参数1: 新值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数2: 旧值, 如果监视的是数组,参数1还是新值,以数组的方式体现
- 参数3: 配置对象
7. 生命周期函数
vue3中的生命周期函数来自vue,所以需要通过vue按需引入,生命周期函数可以多次调用
- 创建阶段
- vue2: beforeCreate/created
- vue3: setup
- 挂载阶段
- vue2: beforeMount/mounted
- vue3: onBeforeMount/onMounted
- 更新阶段
- vue2: beforeUpdate/updated
- vue3: onBeforeUpdate/onUpdated
- 销毁阶段
8. 组件通信
9. 过滤器
10. toRefs的使用
作用: 保证解构的数据还是响应式
原理: 将解构的数据变为ref处理
11. 路由
vue3的路由结合vue-router@4.x版本
- 创建路由实例, 通过createRouter进行创建,createRouter就是一个函数,来自于vue-router的按需引入
- 配置路由模式,通过createWebHashHistory(hash路由模式)、createWebHistory创建history路由模式
import { createRouter, createWebHashHistory as createRouterMode } from 'vue-router'
// 可以创建路由实例
// 通过参数对象进行配置
const router = createRouter({
// createWebHashHistory 创建hash模式
// createWebHistory 创建history模式
history: createRouterMode(),
// 路由规则
routes: [
{
path: '/home',
component: () => import('../views/Home/index.vue')
},
{
path: '/about/:id',
component: () => import('../views/About/index.vue')
}
]
})
export default router
- 获取路由实例, 通过useRouter, useRouter函数来自于vue-router, 调用useRouter获取到router实例
import { ref } from 'vue'
// 引入useRouter
import { useRouter } from 'vue-router'
// 创建router实例
const router = useRouter()
const goAbout = () => {
router.push('/about/1')
}
- 获取当前的路由信息, 通过useRoute, useRoute函数来自于vue-router, 调用useRoute获取到route
import { ref } from 'vue'
// 引入useRoute
import { useRoute } from 'vue-router'
// 创建route
const route = useRoute()
12. pinia
12.1 pinia相比较vuex的优势
12.2 pinia如何使用
- main.js中进行createPinia的引入,通过createPinia创建pinia实例,并进行挂载
// 引入创建pinia实例
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
// 挂载pinia
app.use(pinia)
- 创建pinia模块, 在js文件中通过defineStore(来自于pinia),创建一个模块数据管理
- 参数1: 模块标识
- 参数2:配置对象(state,actions, getters)
- 返回值: 函数
import { defineStore } from 'pinia'
// 参数1: 模块标识
// 参数2: 配置对象
// 返回值: 函数
const cartStore = defineStore('cart', {
state: () => {
return {
total: '111.00'
}
},
actions: {},
getters: {}
})
export default cartStore
- 使用模块的数据或方法, 导入模块, 直接调用模块的属性(state,getters)或方法(actions)
- 如果想对模块的数据进行解构,通过storeToRefs处理后解构的数据变为响应式了
- 模块肯定进行统一整合
12.3 pinia有哪些属性
六、小程序
1. wxss和css的区别?
- wxss是小程序配合wxml渲染结构样式
- css是网页结合html渲染结构样式
- wxss新增了rpx,适配单位, 750rpx等于整屏的宽度
- wxss区分全局样式和局部样式,全局样式app.wxss,局部样式,每个页面内部的wxss文件,权重,先看权重,谁的权重高就会把另一个覆盖,同等权重,局部的覆盖全局的
2. 原生小程序组件使用过哪些?
3. 原生小程序中如何绑定事件?
4. 原生小程序中如何修改数据并同步视图?
5. 事件传参?
6. 小程序中发起网络请求
7. 导航跳转方式?
- 声明式导航(navigator标签进行跳转),跳转到tabbar页面,需要配合open-type="switchTab",open-type="navigateBack" delta="层级"
- 编程式导航: wx.navigatorTo() 普通页面的跳转, wx.switchTab跳转到tabbar页面,wx.navigateBack
- 导航传参: 通过query(?根参数 key=value&key=value), 接收参数,通过onLoad的形参去接收
8. 监听上拉触底
9. 小程序的生命周期函数?
- 应用的生命周期
// 触发一次
onLaunch() {
console.log('小程序开启启动,初始化完成')
},
// 小程序显示,多次触发
onShow() {
console.lg('小程序显示了')
},
onHide() {
console.log('小程序隐藏了')
},
onError() {
console.log('小程序出现异常')
},
- 页面的生命周期
// 1. onLoad 页面开始加载 发送请求获取数据,获取到导航参数
// 2. onShow 页面显示 (多次触发) 提示信息
// 3. onReady 页面初次渲染完成
// 4. onHide 页面隐藏 tabbar页面切换只是隐藏
// 5. onUnload 页面销毁 不是tabbar页面 b页面 返回 A页面对应的页面销毁 // 清理操作
- 组件的生命周期函数
10. 定义全局组件和局部组件
全局组件
- 创建一个组件
- 通过app.json中的useingComponets进行组件注册,注册为全局组件
局部组件
11. 组件通信
父向子传值
子向父传值
- 子组件传值
this.triggerEvent('自定义事件名', 传递的数据)
- 父组件接收, 通过bind:绑定自定义的事件名, 通过事件函数的e.detail获取到子组件传递的数据
- 通过事件函数的e.detail接收数据
getImage(e) {
console.log(e.detail)
},
获取子组件的方式
- 通过selectComponents获取子组件
this.selectComponent('组件对应的选择器')
12. wxs是什么?
wxs weixin script: 在模板中无法调用js中定义的函数,可以调用wxs定义的函数,一般用wxs进行数据处理
缺点:
- 不支持es6
- 不支持小程序的api
- 隔离性, wxs和js不能互相调用
优点:
13. 原生小程序上线流程
14. uni-app小程序的上线流程
- 在hbuilderX中发行-小程序,就会打包
- 在微信开发者工具中点击上传,弹出填写上传信息,填写完毕进行上传
- 登录小程序管理系统(开发人员不会参与)
- 将版本设置为体验版本(产品和ui进行体验)(测试人员进行测试)
- 改bug,重复1\2步骤
- 提交审核
- 审核通过点击发布
15. 登录流程
- 微信授权登录
1.1 获取用户信息(iv, rawData, encrytedData,signature)1.2 调用uni-app的login方法获取code(用户唯一标识)
const {
code
} = await uni.login()
- 根据微信授权的信息进行接口登录
const res = await login({
iv,
rawData,
encryptedData,
signature,
code
})
- 登录成功后
16. 支付流程
- 根据购买的商品信息生成订单,从而得到订单号
- 预支付: 根据订单号得到支付的参数
async payGoods(order_number) {
// 获取支付参数
const {
message: {
pay
}
} = await getPayParams({
order_number
})
//
},
- uni.requestPayMent: 支付,需要很多参数
// 发起微信支付
const [err, res] = await uni.requestPayment(pay)
if (err) {
uni.showToast('取消支付,跳转订单页面')
} else {
uni.showToast('支付成功,跳转订单页面')
}
17. tabbar页面切换如何传递参数
18. uni-app从头开始写项目
18. 1介绍
- 原生小程序和vue的结合
原生(写法相同): 组件\生命周期\api
vue: 数据绑定\数据渲染\事件绑定\逻辑的定义\计算属性\watch\过滤器\自定义指令\vuex 等等逻辑的处理
独立:
- App.vue (app.js和app.wxss)
- pages.json(app.json): 配置页面的路径和窗口的外观,全局组件也在这里进行注册,tabbar也是在该位置进行配置
- manifest.json打包配置: 不同的平台配置不同
18. 2创建项目
18.3 运行项目
18.4 外观配置
在pages.json中通过globalstyle进行配置
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
18.5 tabbar的配置
在pages.json中通过tabbar进行配置
"tabBar": {
"list": [
{
"pagePath": "页面路径",
"iconPath": "图标路径",
"selectedIconPath": "选中的图标路径",
"text": "文本"
}
],
},
18.6 创建全局组件
- 在项目根目录创建components目录
- 右键创建组件
- 直接使用
18.7 新建页面
在pages上右键进行新建页面
填写创建页面的信息
设为首页,可以在pages.json中进行配置
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
,{
"path" : "pages/home/home",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
],
18.8 组件库(vant-weapp)
- npm init -y
- 下包
npm i @vant/weapp -S --production
- 在项目的根目录创建一个wxcomponents目录
- 修改名字
- 定义全局组件
"usingComponents": {
"van-button": "/wxcomponents/weapp/button/index"
}
18.9 封装request
- 使用flyio
npm i flyio
- 在utils目录下新建request.js
- 接口还需要在小程序管理后台中配置服务合法域名,如果目前还没有合法域名,
18.10 vuex状态管理
- 无需下载,直接新建store目录,新建index.js, 借助createLogger进行vuex的数据调试
19. 原生小程序开发项目
19.1 创建项目
- 利用微信开发者工具进行创建
- 填写项目基本信息
19.2 项目清理
- 创建新的首页,app.json中pages选项下新增路径,页面自动生成
- pages下新建目录,在目录下在新建page也会自动生成,同时路径也会自动生成
19.3 封装request
- 对wx.request进行二次封装
const baseURL = ''
export function request({
url,
method = "GET",
data
}) {
return new Promise((resolve, reject) => {
wx.request({
url: baseURL + url,
method,
data,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
19.4 定义数据和渲染数据
- 定义到data,
- 数据渲染
19.5 数据修改和事件绑定
- 数据修改
- this.setData({要修改的数据}), 既可以更新数据也可以更新视图
- 事件绑定
19.6 组件注册
- 全局组件
- 注册组件: 在app.json中的
19.7 页面导航
- 声明式导航
- 普通页面:
- tabbar页面:
- 后退:
- 编程式导航
- 普通页面: wx.navigatorTo({url: ''})
- tabbar页面: wx.switchTab({url: ''})
- 后退: wx.navigatorBack({delta: 1})
- tabbar页面跳转传参
19.8 生命周期
- 应用的生命周期
- onLaunch: 小程序初始化(加载一次)
- onShow: 每次看见小程序
- onHide: 小程序隐藏
- onError: 小程序出错
- 页面的生命周期
- onLoad: 页面加载(加载一次) 获取到导航参数,数据请求
- onShow: 页面显示,
- onReady: 页面加载完成(加载一次)
- onHide: 页面隐藏
- onUnload: 页面销毁
- 组件的生命周期
20. 小程序如何跳转另外一个小程序?
21. 小程序最近更新了什么?
22. 小程序地图
23. 小程序嵌套过深,导致跳转失效?
- 出现问题的原因:
小程序的页面栈最多10层,之前是5层,所以一旦超过10层,跳转则无反应
- 解决思路
合理使用navigateTo、navigateBack、redirectTo、reLaunch
方式一:可以通过获取页面栈信息getCurrentPages,以及即将跳转的页面是否在页面栈中,如果有直接利用navigatorBack进行返回
方式二:可以通过获取页面栈信息getCurrentPages,是否超出限制,如果超出限制利用redirectTo进行跳转或者wx.reLaunch进行跳转
方式三:因为redirectTo/reLaunch跳转,会销毁当前页面栈进入另一个页面,a、b、c,b通过redirectTo到c,再返回会进入a,这个问题解决,可以维护自己的页面栈列表,进行进栈,弹栈维护,从自己维护的列表中取对应的页面,对应的往后退。
24. 小程序打包文件太大如何处理?
- 删除无用代码、组件
- 图片压缩链接
- 分包处理
25. 小程序如何下载文件?
小程序有内置的下载文件api, wx.downloadFile进行文件下载,单次下载允许的最大文件为 200MB
参考链接
26. 小程序项目选择uni-app的原因?
- 跨终端(但是你的项目没有多终端,只有小程序,说这个就不合适了)
- 开发风格贴近vue, 开发舒服
- 开发效率比原生效率快
- 微信小程序具备的uni-app均具备
- 微信开发者工具没有vscode和hbuilderx好用
27. 小程序内嵌h5页面?
七、浏览器相关&HTTP
跨域
跨域的原因是同源策略
同源策略是浏览器提供的一种安全机制,可以防止跨站脚本攻击
也就是A网站请求B网站的资源,是否能够使用的问题
同源策略:协议(http/https)、域名/IP地址、端口号,一致则同源,代表是同一个网站,资源共享
有一项不同既不同源,代表是两个网站,此时资源不共享
跨域的本质: 浏览器
为什么会出现跨域?
当下,最流行的就是**前后分离**项目,也就是**前端项目**和**后端接口**并不在一个域名之下,那么前端项目访问后端接口必然存在**跨域**的行为.
解决:
JSONP: 利用的不是xhr请求, 利用的script标签的src可以跨域, 请求接口资源,同时携带callback回调函数名字, 将数据传给回调函数, 解决get不能解决post
cors: 后端开启
正向代理服务器原理图解: 只能用于开发期间具体配置:
vue.config.js
devServer: {
proxy: {
'/api': {
target: '接口url地址'
}
}
}
上线: nginx\将项目放置统一服务下
nginx: 反向代理
在nginx服务器nginx.conf配置文件
server {
listen 8083;# 监听的端口
server_name localhost; #监听的主机名 也可以是域名或者ip地址
location ~ /api/ {
proxy_pass http://localhost:8084;
}
location ~ /prod/ {
proxy_pass http://localhost:8085;
}
}
地址栏输入url过程
-
url解析:是合法url还是搜索关键词
-
dns解析:本地/hosts/dns服务器
-
TCP链接:通过ip地址/3次握手,1次:确认客户端发送能力,服务端接收能力;2次:确认服务端的发送能力、接受能力;此时服务端不知道客户端的接受能力;3次:确认客户端的接受能力
-
发起http请求
-
响应请求
-
页面渲染: html 生成dom树,style生成css规则树,dom树和css规则树生成render树,render树进行layout(谷歌重排)/reflow(火狐回流),render树上的paint和paint setup事件将render树绘制为像素信息
16.6ms浏览器干啥了?
dom和css 由 GUI引擎线程进行解析的
js由JS引擎线程(v8)解析的
浏览器的刷新频率60hz,16.6ms一次
刷新一次,会执行js,如果js执行时间超过16.6毫秒,css样式重绘重排丢帧,原因就是js引擎线程和GUI引擎线程是互斥的
window.requestIdleCallback():
_requestIdleCallback_利用的是帧的空闲时间
时间切片, 没有执行完放到下一帧,余出时间渲染css -
断开链接: 是否有keep-alive,有:关闭网页断开,网页运行期间,如果有新的请求,不会重新链接断开; 没有keep-alive;请求完毕断开链接,下次请求重新链接
-
4次挥手:1. 客户端发出断开/等待,客户端进程发出连接释放报文,所以FIN=1;2. 服务端同意,响应服务端收到FIN之后,如果同意断开就发回一个ACK确认,ACK=1。根据规定,确认号ack=u+1,然后带上一个随机生成的序列号seq=v。此时处于半链接,有可能有数据没发送完,继续发送未发送完的数据;3. 服务端发送完成/等待状态,4. 客户端确认收到数据,服务器只要收到客户端发出的确认就进入关闭状态。服务器结束TCP连接的时间要比客户端早
如何应对xss攻击
概念: 跨站脚本攻击,原理: 通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序
场景: 评论功能,或者通过v-html渲染
解决: 采用三方库,dompurify,使用比较简单,装包,通过该包中的sanitize方法可以将要渲染的数据提前进行过滤,过滤掉可能会有攻击的代码
csrf攻击
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网站攻击方式
攻击原理:利用请求自动携带cookie
攻击流程:
- 张三登录某银行网站(mouyinhang.com)
- 登录成功将用户信息存储到cookie中
- 在银行网站引导张三进入(攻击网站)
- 再攻击网站中向银行网站发送转账请求(mouyinhang.com/zhuangzhang…
- 攻击成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求 自动携带cookie => 服务端 用户信息 处理成功
A网站 => 登录 => 用户信息存到cookie => 转账 发起请求携带referer/www.a.com => 服务端 获取referer, referer是合法的,相应成功,转账成功
A网站 => 登录 => 用户信息存到cookie => 张三跳到B网站 => B网站发请求 A网站/转账请求,没有携带referer 自动携带cookie => 服务端一看,没有referer, 不知道是请求是在哪里过来的,相应失败
解决方案:
SQL注入
SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令 或访问未被授权的数据。
ddos
DDoS 攻击,全称是 Distributed Denial of Service,翻译成中文就是分布式拒绝服务
攻击者短时间内向目标服务器发起大量请求大规模消耗主机资源,从而导致其他人无法正常访问
解决方案:
后端/运维处理
高防服务器:花钱,有人保护
黑名单:拉黑ip
ddos清洗:会对用户请求数据进行实时监控,及时发现 DOS 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。
cdn加速
HTTP和HTTPS
区别
- 默认端口
- http:80
- https:443
- 传输过程
- http:明文,被截获不安全
- Https:密文,截获的是加密后的
- ssl证书:_SSL 证书_就是遵守 SSL协议(它是在传输通信协议(TCP/IP)上实现的一种安全协议,采用公开密钥技术),由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能
- 是什么: ssl 证书
- 颁发: CA
- 干啥: 数据传输加密
- http:不需要
- https:需要
- http:基于7层协议中的应用层(提供应用程序间的交换和数据交换)
- https:基于7层协议中的传输层(传输层协议提供计算机之间的通信会话,并确保数据在计算机之间可靠地传输。)
对称加密
发送方和接收方使用同一个密钥(一串字符串)进行加密和解密
- 服务端使用密钥进行加密
- 客户端使用密钥进行解密
但是第一次要传输一次密钥,如果密钥被拦截,就被破解了
性能好,速度快,缺点,密钥被拦截被破解
非对称加密
一个公钥,一个私钥
公钥加密,私钥解密
- 服务端(私钥)
- 客户端(公钥)
- 客户端公钥加密传输(被拦截无法解密,需要用私钥)
- 服务端通过私钥解密
https
使用非对称加密进行密钥传输,使用对称加密进行数据传输
如何保证首次传输的公钥是安全的,需要网站机构,进行网站和公钥的登记(CA机构,颁发证书,安全可靠)
状态码
-
2xx: 请求处理成功状态码
- 200: 客户端发的请求被服务端正常处理
- 201:请求成功创建新的资源
- 202: 服务接受请求但还没处理
- 204: 请求成功但没有返回内容
-
3xx:请求重定向
- 301: 永久重定向
- 302: 临时重定向
- 304: 服务端资源没有变化,使用强缓存
-
4xx: 请求失败
- 400: 客户端请求的语法有误,服务端无法处理
- 401: 当前请求需要验证权限,一般token问题
- 403: 服务端拒绝访问资源
- 404: 资源不存在
-
5xx:服务器问题
- 500: 服务器内部错误
- 503: 服务器停机维护/超负载,无法处理请求
- 505: 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本