搜索 K
Appearance
Appearance
16
分钟4.2k
字2022-02-11
Invalid Date
优点:
缺点:
Vue 的全家桶,你可以选择不用,或者只选几样去用,比如不用 vuex
相同点:
不同点:
MVC
MVVM
注意:Vue 不严格符合 MVVM,因为 MVVM 规定 Model 和 View 不能直接通信,而 Vue 可以使用
ref
进行通信
防止组件被多个页面使用时,造成的变量互相污染
.lazy
:输入框失焦时才会更新 v-model 的值.trim
:讲 v-model 绑定的值首位空格给去掉.number
:将 v-medol 绑定的值转数字.stop
:阻止事件冒泡.capture
:事件的捕获.self
:点击事件绑定本身才触发.once
:事件只触发一次.prevent
:阻止默认事件.native
:绑定事件在自定义组件上时,确保能执行.left. .middle. .right
:鼠标左中右键的触发passive
:相当于给移动端滚动事件加一个.lazy
camel
:确保变量名会被识别成驼峰命名.sync
:简化子修改父值的步骤全局钩子
beforeEach:跳转路由前
afterEach:路由跳转后
路由独享钩子
routes: [
{
path: '/xxx',
component: xxx,
beforeEnter: (to, from, next) => {
}
}
]
组件内路由钩子
v-text
:元素的 textContentv-html
:元素的 innerHTMLv-show
:通过样式 display 改变显隐v-if
:通过操作 DOM 改变显隐v-else
:配合 v-ifv-else-id
:配合 v-elsev-for
:循环渲染v-on
:绑定事件,缩写@v-bind
:绑定变量,缩写:v-model
:双向绑定v-slot
:插槽v-once
:只渲染一次v-pre
:跳过元素编译v-cloak
:隐藏双括号,有值再显示props
接收$emit
对父组件进行传值$parent
和$chidren
获取实例进而通信vuex
进行状态管理eventBus
进行跨组件值传递provide
和inject
,官方不建议使用$ref
获取实例,进而传值<div :class="{ 'is-active': true, 'red': isRed }"></div>
<div :class="['is-active', isRed ? 'red' : '' ]"></div>
<div :style="{ color: textColor, fontSize: '18px' }"></div>
v-if
:通过操作 DOM 来控制显隐,适用于偶尔显隐的情况v-show
:通过改变样式 display 属性控制显隐,适用于频繁显隐的情况computed
:依赖多个变量计算出一个变量,且具有缓存机制,依赖值不变的情况下,会复用计算值。computed
中不能进行异步操作watch
:通常监听一个变量的变化,而去做一些事,可异步操作computed
的多对一,watch
一对多beforeCreate
:实例 Vue,未初始化和响应式数据created
:已初始化和响应式数据,可访问数据beforeMount
:render 调用,虚拟 DOM 生成,未转真实 DOMmounted
:真实 DOM 挂载完成beforeUpdate
:数据更新,新虚拟 DOM 生成updated
:新旧虚拟 DOM 进行对比,打补丁,然后进行真实 DOM 更新beforeDestroy
:实例销毁前,仍可访问数据destroy
:实例销毁,子实例销毁,指令解绑,解绑本实例的事件activated
:keep-alive 所缓存组件激活时调用deactivated
:keep-alive 所缓存组件停用时调用errorCaptured
:子孙组件的错误捕获,此函数可返回 false 阻止继续向上传播v-for
优先级高于v-if
,每项都通过v-for
渲染出来后再去通过v-if
判断显隐,做了很多无用功
state
:定义初始状态getter
:从 store 从取数据mutation
:更改 store 中状态,只能同步操作action
:用于提交 mutation,而不直接更改状态,可异步操作module
:store 的模块拆分父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
原因:Object.defineProperty 没有对对象的新属性进行劫持
解决:
Vue.set(target, key, value)
Vue.delete(target, key)
原因:Vue 出于性能考虑没有对数组下标进行劫持,而是通过改写数组原型方法
解决:
splice:arr.splice(index, 1, value)
Vue.set(target, index, value)
普通插槽 普通插槽slot
会被当做子元素进行解析,最终会被解析成一个_t
函数,他接收的第一个参数为插槽的名称,默认是default
,也就是_t('default')
,执行此函数进行最终元素的渲染,如果是具名插槽,则传对应的插槽名 作用域插槽 插槽会被封装成一个函数放置在scopeSlotes
对象中,解析时_t
第二个参数接收子组件的数据,并进行渲染
举个例子:
<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
{ name: '小明', id: '123' },
{ name: '小红', id: '124' },
{ name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
新旧对比
<div key="0">小明</div> <div key="0">小林</div>
<div key="1">小红</div> <div key="1">小明</div>
<div key="2">小花</div> <div key="2">小红</div>
<div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
现在我们使用id来做key,渲染为
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' }),渲染为
<div key="122">小林</div>
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
新旧对比
<div key="122">小林</div>
<div key="123">小明</div> <div key="123">小明</div>
<div key="124">小红</div> <div key="124">小红</div>
<div key="125">小花</div> <div key="125">小花</div>
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果
用index
和用随机数
都是同理,随机数
每次都在变,做不到专一性,很渣男
,也很消耗性能,所以,拒绝渣男
,选择老实人
这里只说 Vue2 的
bind
:指令绑定到指定元素时调用,只调用一次inserted
:指定元素插入父节点时调用update
:所在组件的 VNode 更新时调用componnetUpdated
:所在组件以及其子组件 VNode 全部更新后调用unbind
:只调用一次,指令与元素解绑时调用修改数据时不能马上得到最新的 DOM 信息,所以需要使用 nextTick,在 nectTick 回调中可以获取最新 DOM 信息
优先是Promise.then
方法,是个微任务,这样可以避免多一次队列,进而少一次 UI 渲染,节省性能
SSR 全称Server Side Render
Object.defineProperty
对对象进行递归劫持属性的get. set
watcher
进行观察数据使用的地方dep
收集watcher
,数据更改时,通过notify
方法通知dep
里的watcher
去进行相应的更新数组的元素大概率是成百上千的,所以对数组下标进行劫持的话会非常消耗性能。Vue 通过对数组原型上方法的重写,实现数组的响应式
Vue.set(target, key, value)
第一步:判断 target 是数组的话,则调用 target.splice(key, 1, value)
第二步:判断 target 是对象的话,再判断传入的 key 是否已存在 target 中
export default function set(target, key, val) {
if (Array.isArray(target)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
const ob = target.__ob__
if (key in target && !(key in target.prototype) || !ob) {
target[key] = val
return val
}
defineReactive(target, key, val)
return val
}
Vue.delete(target, key)
export default function del (target, key) {
if (Array.isArray(target)) {
target.splice(key, 1)
return
}
const ob = target.__ob__
if (!(key in target)) return
delete target[key]
if (!ob) return
ob.dep.notify()
}
维护一个数组,每次调用时讲回调函数压入这个数组,然后优先选择微任务,在微任务回调中去执行数组中的所有回调,同时维护一个布尔值,确保每一次队列进行一次执行数组所有回调
let callbacks = []; //回调函数
let pending = false;
function flushCallbacks() {
pending = false; //把标志还原为false
// 依次执行回调
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
}
let timerFunc; //先采用微任务并按照优先级优雅降级的方式实现异步刷新
if (typeof Promise !== "undefined") {
// 如果支持promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== "undefined") {
// MutationObserver 主要是监听dom变化 也是一个异步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== "undefined") {
// 如果前面都不支持 判断setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后降级采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
timerFunc();
}
}
虚拟 dom
虚拟 dom 是一个对象,一个描述真实 DOM 的对象,每次数据更新时,新旧虚拟 DOM 都会互相进行同层对比,而 diff 算法优化就是在此时做优化的。
diff 算法
第一步:调用patch
方法,传入新旧虚拟 DOM,开始同层对比
第二步:调用isSameNode
方法,对比新旧节点是否同类型节点
第三步:如果不同,新节点直接代替旧节点
第四步:如果相同,调用patchNode
进行对比节点
updateChildren
方法进行新旧子节点的对比props: {
num: {
default: 1,
validator: function (value) {
// 返回值为false则验证不通过,报错
return [
1, 2, 3, 4, 5
].indexOf(value) !== -1
}
}
}
}
Vue 实例初始化的时候立即调用 watch 的监听回调函数
mounted() {
Object.keys(this.params)
.filter((_) => !["c", "d"].includes(_)) // 排除对c,d属性的监听
.forEach((_) => {
this.$watch((vm) => vm.params[_], handler, {
deep: true,
});
});
},
data() {
return {
params: {
a: 1,
b: 2,
c: 3,
d: 4
},
};
},
watch: {
params: {
deep: true,
handler() {
this.getList;
},
},
}
样式模块化 scoped 的效果,在本组件的标签都会带上 data-v-xxx 的属性,然后通过属性选择器实现样式模块化的效果
这是我们常用的使用定时器的方式
export default{
data(){
timer:null
},
mounted(){
this.timer = setInterval(()=>{
//具体执行内容
console.log('1');
},1000);
}
beforeDestory(){
clearInterval(this.timer);
this.timer = null;
}
}
上面做法不好的地方在于:得全局多定义一个 timer 变量,可以使用 hook 这么做:
export default{
methods:{
fn(){
let timer = setInterval(()=>{
//具体执行代码
console.log('1');
},1000);
this.$once('hook:beforeDestroy',()=>{
clearInterval(timer);
timer = null;
})
}
}
}
如果子组件需要在 mounted 时触发父组件的某一个函数,平时都会这么写:
//父组件
<rl-child @childMounted="childMountedHandle"
/>
method () {
childMountedHandle() {
// do something...
}
},
// 子组件
mounted () {
this.$emit('childMounted')
},
使用 hook 的话可以更方便:
//父组件
<rl-child @hook:mounted="childMountedHandle"
/>
method () {
childMountedHandle() {
// do something...
}
},
引用数据类型响应式,基础数据类型不响应式
同时存在时,el > $mount
<aButton @[someEvent]="handleSomeEvent()" :[someProps]="1000" />
更改如有组件上的 key 即可
export default: {
model: {
event: 'change',
prop: 'checked'
}
}
使用this.$options.data().xxx
获取初始值
computed 比较好,computed 有缓存机制,可以节省性能。而 method 则每次更新都会重新计算,不考虑缓存
router.push
:跳转,并向 history 栈中加一个记录,可以后退到上一个页面router.replace
:跳转,不会向 history 栈中加一个记录,不可以后退到上一个页面router.go
:传正数向前跳转,传负数向后跳转