用了VUE快2年了,我的工作量还挺大,基本都在复制粘贴,只知道会用,有些点却不知道为什么要这样用,是时候该写个笔记加深下印象啦.
一.Vue相较于JQUERY 原生dom js,多页面间的数据传递, 异步数据监听等,VUE从这些繁琐的数据处理中拯救了我们,它的功劳有:
1.轻量级框架:只关注视图,构建视图的集合
2.简单易学,中文文档没有语言障碍
3.双向数据绑定,操作数据简单
4.组件化,构建单页面应该有优势
5.视图数据分离,使用数据更方便
6.虚拟DOM,原生dom操作很费性能
7.数据异步监听
8.响应式的数据在不同的页面中也能做到
二.Vue数据响应式的原理
创建实例时,遍历 data 用 Object.defineProperty() (vue3 使用 proxy)将他们转为 getter/setter,并且在内部跟踪,每个组件实例都有 watcher 程序, 属性发生变化时,会通知 watcher 重新计算,然后更新组件
三.Vue数据响应式数组的缺陷
通过下标方式修改数组数据,或者给对象增加属性,不可以触发渲染,因为Object.defineProperty 拦截不到这些操作, 大部分的数组都拦截不到, vue 内部通过重写函数的方式 解决了,数组改数据 :push,pop,shift,unshift,splice,sort,reverse
四.Vue 注意点
- 尽量减少data中数据,因为用增加 getter 和 setter, 会收集到 watcher,data的数据越多,会影响性能.
- v-if 和 v-for 不一起使用
原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
// 如下情况,即使100个user中之需要使用一个数据,也会循环整个数组。
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
// 正确用法 放在computed里
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
- v-for 尽量使用 事件代理
原因 请参考如下链接 本人也不知道这点,现在学到了【前端100问】Q94:vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么? - 简书
- SPA(单页面应用)采用 keep-alive 缓存 意思就是打开页面无刷新,这里我在项目中用到的场景是菜单的面包屑,有数据变动的就不要用这个缓存啦。
spa应用中的路由缓存问题与解决方案_weixin_34175509的博客-CSDN博客
- 第三方模块按需导入
- 长列表滚动到可视区再加载
- 图片懒加载 本人做的基本是系统 还未做到商城,疯城一般对图片要求较高,所以需要了解可阅读如下链接 Vue-Lazyload插件的用法
- 防抖节流什么是防抖节流 说实话,我用了2年,如果不是今天来复习,我还是真不知道,下面这个视频比较生动的理解
防抖节流的目的是为了提高VUE性能
我的使用场景
比如父组件在深度监听子组件的对象,子组件实时接收父组件数据变化的emit (对象) 如果我的项目这个场景不使用防抖节流,你们可以想想这可怕的损耗性能,关键是我这个是个类似于做PS图一样的,数据变化非常多且快。
附上函数 及用法
/**
* 函数节流
* @param fn
* @param interval
* @returns {Function}
* @constructor
*/
export function _throttle(fn, time) {
let last
let timer
const interval = time || 200
return function() {
const th = this
const args = arguments
const now = +new Date()
if (last && now - last < interval) {
clearTimeout(timer)
timer = setTimeout(function() {
last = now
fn.apply(th, args)
}, interval)
} else {
last = now
fn.apply(th, args)
}
}
}
// 防抖
export function _debounce(fn, wait) {
const delay = wait || 200
var timer
return function() {
const th = this
const args = arguments
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
timer = null
fn.apply(th, args)
}, delay)
}
}
如何使用
changingData_keypress: _throttle(function(val) {
this.items[this.activeItem.id].x = this.items[this.activeItem.id].x + val.x
this.items[this.activeItem.id].y = this.items[this.activeItem.id].y + val.y
}, 200),
- 使用路由懒加载,异步组件 这个未深入了解 有兴趣可以百度
五.Computed 和 Watch
computed
- 支持缓存,依赖的数据发生变化才会重新计算
- 不支持异步,当有计算属性的时候,无法监听数据变化
- 默认走缓存,基于依赖的响应式进行缓存,或者 父组件传递过来的props 的数据进行计算
- 可以使用 getter 和 setter 方法更灵活
watch
- 不支持缓存,数据变化 会触发响应的操作
- 支持异步监听,两个参数,一个是最新的值,第二个是旧值
- 当一个属性发生变化就做像一个的操作
- immediate: 立即监视, deep : 深度监视 , handle: 监听函数
使用场景:
- 计算 当进行计算,可以依赖缓存特性,避免每次都要计算
- 当需要数据变化时 执行异步,或者 开销较大时,使用 watch , 限制执行该操作的频率,得到最终结果前,设置中间状态(防抖),计算属性不行
watch 监听 tip watch只监听对象上的想监听属性,如何排除其他属性监听
// 可以 直接监听 "params.a":{...}
data() {
return {
params: {
a: 1,
b: 2,
},
};
},
mounted() {
// 遍历所有的对象属性, 筛选出要监听的属性
Object.keys(this.params)
.filter((item) => {
return item === "a";
})
// 遍历要监听的数组
.forEach((item) => {
// 用 vue 实例的 watch 开启监听
// $watch 参数1 要监听的属性, 2 监听的处理函数, 3 配置项, 可以深度和立即监听
this.$watch(
(vm) => {
vm.params[item];
},
this.watchFun,
{
deep: true,
}
);
});
},
methods: {
edit() {
this.params.a = 10;
this.params.b = 10;
},
watchFun() {
console.log("对象单独属性更新");
},
},
watch: {
// params: {
// deep: true,
// handler() {
// console.log("更新了");
// },
// },
},
六.vue 的 hook 使用
1.清除定时器
getServeTimeSetTime() {
// 开启定时器,五分钟获取一次服务器时间
const timer1 = setInterval(this.handleGetServerTime, 5 * 60 * 1000)
this.$once('hook:beforeDestroy', () => {
clearInterval(timer1)
})
},
2.父组件监听子组件的声明周期函数
// 1. 用 $emit 监听
// 父组件
<!-- 监听子组件的 mounted 事件 -->
<DomeChilred @changeMount="changeMount" /> // 监听处理 changeMount
// 子组件
mounted() {
// 子组件声明周期 触发 就像父组件发信号
this.$emit("changeMount", "监听成功");
},
// 2. 使用 hook 监听 --》 缺点是不可以携带子组件的参数到父组件
// 父组件
<!-- 使用 hook -->
<DomeChilred @hook:mounted="changeMount" />
changeMount() {
console.log("子组件触发了 mounted");
},
// 子组件 不用操作
七.v-if、v-show、v-html
- v-if 懒惰的, 第一次是 false 就不加载,切换时 标签创建销毁, 在虚拟 dom 操作,开销比 v-show 大 原理: VNode 不会生成
- v-show 直接创建 显示和隐藏都是用 css display 来控制的,直接生成,在用 display:none隐藏
- v-html : 先移除节点下的所有节点,在 innerHTML 添加 , 可能会导致 xss 攻击,里面的样式还有使用 /deep/ 穿透
- 使用场景,v-show 适合频繁切换
八.$nextTick
- 本质是对 js 执行原理 EventLoop 的一个应用,模拟对应的 宏/微 任务的实现,用 js 的异步 来执行 vue 的异步
- 在下次渲染完成调用,可以获取最新的DOM元素
九.$set $delete
// 给对象或者数组添加一个响应式数据
this.$set(obj , name , 'liu'); // 对象
this.$set(arr , 0 , 'liu') // 数组
// 删除一个响应式数据
this.$delete(arr , 0 ) // 数组
this.$delete( obj , name) // 对象
十.子组件可以修改父组件的数据吗
不可以直接修改,因为 vue 是单项数据流,直接赋值操作,会破坏数据流,可以通过 $emit 派发一个自定义事件,父组件收到后,父组件自己修改
十一.assets 和 static
相同点:资源在html中使用,都是可以的。
不同点:使用assets下面的资源,在js中使用的话,路径要经过webpack中file-loader编译,路径不能直接写。
assets中的文件会经过webpack打包,重新编译,推荐该方式。而static中的文件,不会经过编译。项目在经过打包后,会生成dist文件夹,static中的文件只是复制一遍而已。简单来说,static中建议放一些外部第三方,自己的放到assets,别人的放到static中。
注意:如果把图片放在assets与static中,html页面可以使用;但在动态绑定中,assets路径的图片会加载失败,因为webpack使用的是commenJS规范,必须使用require才可以,具体代码如下:
<div id="hook">
<h3>演示钩子的组件</h3>
<p>直接使用路径</p>
<img src="../../assets/11.png" alt="图片加载失败" title="assets中的图片">
<img src="../../../static/11.png" alt="图片加载失败" title="static中的图片">
<br>
<p>动态绑定路径</p>
<img :src="assetsURL" alt="图片加载失败" title="assets中的图片">
<img :src="staticURL" alt="图片加载失败" title="static中的图片">
</div>
data (){
return {
assetsURL: require('../../assets/11.png'),
staticURL: '../../../static/11.png'
}
十二.路由的 hash 和 history ,abstract-- 抽象模式 服务器的
- hash
- 在url 有 # , http 请求没有,对后端没影响,改变 hash 不会重新加载页面,对浏览器支持友好,成为 SPA(单页面)标配
- 原理 : 监听 onhashchange() 事件
- history
- 没有 # 更好看,需要后端配置支持,不配置好 404
- 分两状态 :
- 修改历史状态: H5新增,pushState 和 replaceState
- 切换历史状态 : forward, back, go
十三.$router 和 $route
$route 是 路由信息对象, path hash fullpath matched name
$router 是 路由实例对象 包含了 跳转方法钩子函数,push back go
十四.vuex 和 localStorage
vuex 存在 内存中, localStorage 以文件的方式存储在本地,只能存储字符串
vuex 刷新页面会丢失 localStorage 不会
十五.处理错误(和警告)的更好方法
// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
alert(err);
};
// Vue 2
Vue.config.errorHandler = (err) => {
alert(err);
};
十六.常见浏览器兼容性问题
*{margin:0;padding:0;}
图片默认有间距 行内块 会有的 --》 float 或者 父元素 font-size:0 , 转成块元素
边距重叠问题,两个相邻的都设置了 margin 边距 取最大值,使用 BFC 来清除
火狐浏览器不能使用 innerText 可以使用 textContent
超链接访问后 hover 不显示 解决是 按顺序, L-V-H-A 来定义
火狐不支持 cursor:hand, 使用pointer
添加浏览器前缀
- 谷歌,苹果 webkit
- ie -ms-
- 火狐 -moz-
- 欧朋 -o-