vuex
源码实现
是vue的一个中全局状态管理的工具,是一个插件,只能在vue中使用,因为依赖于vue的数据绑定。
-
数据持久化
利用插件 vuex-persist:import VuexPersistedstate from 'vuex-persistedstate' const vuexLocal = new VuexPersistedstate({ // storage: window.localStorage,//默认 storage: window.sessionStorage }) const store = new Vuex.Store({ state: { ... }, mutations: { ... }, actions: { ... }, plugins: [vuexLocal.plugin] })
-
严格模式
不使用 mutation 改变状态,会抛出异常。
const store = new Vuex.Store({ strict:true, })
vue-router
-
hash模式
hash表示的是地址栏URL中#符号(也称作为锚点)后的部分, hash虽然会出现在URL中, 但是不会被包含在Http请求中, 因此hash值改变不会重新加载页面,但是会触发 hashchange事件, 浏览器的前进后退也能被触发, 所以在HTML5之前, 基本都是使用hash来实现前端路由。 -
history模式
利用了HTML5新增的**pushState()和replaceState()**两个api, 通过这两个api完成URL跳转不会重新加载页面。 -
hash和history 实现vue-router 区别:
api hash history push window.location.assign window.history.pushState replace window.location.replace window.history.replaceState go window.history.go window.history.go back window.history.go(-1) window.history.go(-1) forward window.history.go(1) window.history.go(1) -
router-link、router-view 这两个组件哪来的?
<router-link to="/home">Home</router-link> <router-link to="/login">Login</router-link> <router-view></router-view>
是在 Vue.use(VueRouter)注册路由 的时候创建的。
Vue.use(VueRouter)
源码做了什么呢?
执行 install 方法,在全局注册了组件。
注意:
在注册两个组件之前,还做了其他事情:
就是定义两个属性 $router、 $route -
$router、 $route
源码中可以看出,定义这两个属性是用mixin方法混入某个组件,执行钩子 beforeCreate 中处理的。
$router的处理逻辑简单, $route 就相对复杂些,在路由切换时响应式的变化。 -
路由懒加载
减少首次加载资源,节约时间,提高效率。
使用 import 导入组件,可定义chunkName,webpack会单独打包为一个js,在进入当前这个路由时才会加载。
动态组件
核心是使用 component 标签和 is 属性。
1、AST解析
标签上 is 属性的存在,会在 抽象语法树上打上 component属性标记,值为 is 属性上绑定的变量。
2、render
根据 ast 树生成render函数,有component属性则会执行动态组件分支。
和普通组件区别是 _c 函数第一参数是一个变量。
style scoped
-
作用
style标签内的样式只在当前模板输出的HTML标签上生效 -
原理
1、每个Vue文件都将对应一个唯一的id,该id可以根据文件路径名和内容hash生成
2、编译template标签时为每个标签添加了当前组件的id
3、编译style标签时,会根据当前组件的id通过属性选择器和组合选择器输出样式
实现vue中双向绑定
- Object.defineproperty ** ,进行数据劫持,有个缺点:**
1、不能对数组进行劫持,vue源码只对 Array的几个属性进行了处理。(push、pop、unshift、shift、splice、sort、reverse)。
2、不能对一个完整的对象进行劫持,只能劫持对象的属性。 - Proxy
可以弥补 Object.defineproperty 的缺点。 - 参考文章
Object.defineproperty、Proxy优劣
实现vue中dom异步更新
computed
初始化所有状态时,也初始化了计算属性,将每个计算属性作为Watcher函数参数实例化,把实例对象保存在一个computedWatchers中,挂在vue实例上。这个watcher构造函数内部定义了获取计算属性的方法和计算结果,并提供一个标识表示是否计算过了。
初始化计算属性时对每个计算属性做了响应式化,定义了一个高阶函数作为get方法,内部就是获取vue实例上的computedWatchers中的每个watcher实例,通过标识判断取值。
keep-alive
本身是一个抽象组件。不会渲染一个dom,也不会出现在父组件链中。
使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不销毁它们。
1、在动态组件中的应用
2、在vue-router中的应用
-
缓存原理
在 created 函数调用时将需要缓存的 VNode 节点保存在 cache 中,在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 cache 中取出之前缓存的 VNode 实例进行渲染
$set
-
解决什么问题?
vue创建实例完成,新添加的属性不能响应式化。也就是修改数据视图不会更新。 -
使用和原理
$set方法是在vue实例化时挂在vue原型上的(stateMixin)。
this.$set(target, key, val)
1、target 是 null 或 undefined 抛错
if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
2、target 是数组,利用 splice方法
if (Array.isArray(target) && isValidArrayIndex(key)) {
//首先需要处理数组length,保证索引>length,避免splice报错
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
3、target 是对象
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
4、给Vue 实例对象添加属性 或 为根数据对象(vm.$data)添加属性
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
5、target 创建全新属性,target本身不是响应式数据, 直接赋值,否则 调用 内部响应式方法
const ob = (target: any).__ob__
if (!ob) {
target[key] = val
return val
}
// 进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
修饰符
-
native
让自定义组件可以响应到自身绑定的事件 -
sync
允许props数据的双向数据绑定。// 子组件 this.$emit('update:val', newVal) //父组件 <v-child :val.sync="val"></v-child>
Vue其实底层是继承了eventBus ,它提供了两个api
this.$emit可以触发事件 $event可以获取$emit的参数
-
stop
阻止事件冒泡,相当于 e.stopPropagation() -
present
阻止事件默认行为,相当于 e.preventDefault() -
self
只有点击元素本身才会触发事,相当于变相的阻止事件冒泡 -
trim
输入框过滤首尾的空格 -
lazy
input框光标离开才更新数据 -
once
只能用一次,无论点击几次,执行一次之后就不会再执行 -
capture
冒泡反转 -
passive
提升移动端的性能, 在每次滚动中都会有一个默认事件会触发,加上这个,则是告诉浏览器, 不需要查询, 不需要触发这个默认事件
$nextick、事件循环在dom更新中的使用
-
为什么需要 $nextick?
因为vue的 dom更新是异步的 ,dom更新完成会触发$nextTick回调函数,通知dom更新完成。
vue的异步dom更新借助事件循环,根据执行环境优先选择Promise.then, 不支持则最终会调用setTimeout(fn, 0)
data为什么是函数?
-
定义为函数,每个组件实例都会创建一个私有数据空间,各个组件维护自己的数据。如果是个对象,所有组件实例都会共用一个数据(data)。
-
这根vue本身的设计无关,是js特性决定的。
我们在面向对象编程时,是基于原型链和构造函数的,原型链上添加的一般都是函数。 -
vue中创建一个组件,相当于创建了一个组件的构造器,使用的时候才实例化。
组件中的data是挂在原型上的,这个组件所有的实例data都指向原型中的data。
所以,data是对象会导致组件的所有实例都共用一个data,这自然不是我们预期的样子。
参考例子 -
那为什么是函数就不会共用一个data了呢?
实例化的时候会调用这个函数,返回新的data,函数也是有作用域的概念的,会有一个私有的作用域。
v-for 中key
主要为了高效的更新虚拟dom,vue的diff算法需要用到key。
vue会基于key重新排列元素顺序,key不存在的元素会被移除
v-if、v-show
- v-if
控制dom元素的创建和销毁,可以结合v-else 、v-else-if
状态不会改变建议使用,如果为false,则不用创建dom,减少消耗。 - v-show
控制元素的css属性display, true: display: block;false: display:none;
状态频繁切换建议使用,减少dom创建和销毁次数,降低消耗。
v-if、v-for为什么不要同时在一个标签
v-for 会优先于 v-if 执行,这样就浪费了资源,我们可以进行嵌套
执行过程源码中都干了些什么?
-
初始化生命周期的状态
-
初始化事件容器
-
初始化创建元素方法
创建了Dep,用于watcher发布订阅模式,依赖收集 -
beforeCreate ,初始化接下来开始。。。
-
初始化vue组件内的属性
- props
- methods
- data
数据代理,响应式化,这时候还没有依赖收集,因为vue正在初始化,模版还没渲染 - computed
- watch
-
created ,现在初始化已经完成,vue内属性全部可以访问
-
判断有无 el,有,继续执行。无,执行结束,等待手动调用 $mount 方法:
new Vue({ ... }).$mount('#app') //这里的$mount 就是手动挂载
-
准备 render 方法,判断有无 render 方法:
- 有:直接进入挂载
- 无:判断有无template,无就用el转换为template,再将其转换为render函数
-
beforeMount ,这时候内存中保存了要渲染的 render 函数
-
将vue的 渲染方法 render 添加到 Watcher 中
-
开始挂载,render函数内部:
- 首先,生成一个虚拟dom,保存
- render函数返回真实dom,保存
- 真实dom替换vm.$el, $el append到页面
-
mounted ,挂载完成
页面dom可见 -
数据发生改变,触发update
-
beforeUpdate
- 生成一个新的虚拟dom,和之前的旧虚拟dom 进行diff,得到一个最小的更新范围,更新 render函数中的数据,返回真实dom
- 旧的虚拟dom替换成新的
-
updated
- 页面重新渲染完成
发布订阅模式
el、template、render属性优先性
当Vue选项对象中有render渲染函数时,Vue构造函数将直接使用渲染函数渲染DOM树,当选项对象中没有render渲染函数时,Vue构造函数首先通过将template模板编译生成渲染函数,然后再渲染DOM树,而当Vue选项对象中既没有render渲染函数,也没有template模板时,会通过el属性获取挂载元素的outerHTML来作为模板,并编译生成渲染函数。
换言之,在进行DOM树的渲染时,render渲染函数的优先级最高,template次之且需编译成渲染函数,而挂载点el属性对应的元素若存在,则在前两者均不存在时,其outerHTML才会用于编译与渲染。
父子组件生命周期执行顺序
p beforeCreate -> p created -> p beforeMount ->
c beforeCreate -> c created -> c beforeMount ->
c mounted -> p mounted
vue 使用步骤