Vue原理
响应式原理
Object.definProperty,如何监听对象(深度监听),监听数组,几个缺点
-
深度监听需要递归到底,一次性计算量大
-
无法监听新增属性/删除属性(vue.set vue.delete)
-
无法原生监听数组,需要特殊处理重写数组原型
// 触发更新视图function updateView() { console.log('视图更新')} // 重新定义数组原型const oldArrayProperty = Array.prototype // 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型 const arrProto = Object.create(oldArrayProperty); ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { arrProto[methodName] = function () { updateView() // 触发视图更新 oldArrayProperty[methodName].call(this, ...arguments) // Array.prototype.push.call(this, ...arguments) }}) // 重新定义属性,监听起来 function defineReactive(target, key, value) { // 深度监听 observer(value) // 核心 API Object.defineProperty(target, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 深度监听 observer(newValue) // 设置新值 // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值 value = newValue // 触发更新视图 updateView() } } })} // 监听对象属性function observer(target) { if (typeof target !== 'object' || target === null) { // 不是对象或数组 return target } // 污染全局的 Array 原型 // Array.prototype.push = function () { // updateView() // ... // } if (Array.isArray(target)) { target.__proto__ = arrProto } // 重新定义各个属性(for in 也可以遍历数组) for (let key in target) { defineReactive(target, key, target[key]) }} // 准备数据const data = { name: 'zhangsan', age: 20, info: { address: '北京' // 需要深度监听 }, nums: [10, 20, 30]} // 监听数据observer(data) // 测试/ / data.name = 'lisi' // data.age = 21// // console.log('age', data.age) // data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set // delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete // data.info.address = '上海' // 深度监听data.nums.push(4) // 监听数组
vdom和diff算法
用js模拟DOM结构,计算出最小的变更,操作DOM
- 只比较同一层级,不跨级比较
- tag不相同,则直接删掉重建,不在深度比较
- tag和key两者都相同,则认为是相同节点不在深度比较
patchVnode,addVnodes,removeVnodes,updateChildren(key的重要性)
模板编译
-
js的with语法,vue template complier将模板编译为render函数,执行rende函数生产vnode
-
模板编译为render函数,执行render函数返回vnode
-
基于vnode再执行patch和diff
-
使用webpack vue-loader,会在开发环境下编译模板
const compiler = require('vue-template-compiler') // 插值 // const template = `<p>{{message}}</p>` // with(this){return createElement('p',[createTextVNode(toString(message))])} // h -> vnode // createElement -> vnode // // 表达式 // const template = `<p>{{flag ? message : 'no message found'}}</p>` // // with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])} // // 属性和动态属性 // const template = `// <div id="div1" class="container">// <img :src="imgUrl"/> // </div> // ` // with(this){return _c('div', // {staticClass:"container",attrs:{"id":"div1"}}, // [ // _c('img',{attrs:{"src":imgUrl}})])} // // 条件 // const template = ` // <div> // <p v-if="flag === 'a'">A</p> // <p v-else>B</p> // </div> // ` // with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])} // 循环 // const template = ` // <ul> // <li v-for="item in list" :key="item.id">{{item.title}}</li> // </ul> // ` // with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)} // 事件// const template = `// <button @click="clickHandler">submit</button> // `// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])} // v-modelconst template = `<input type="text" v-model="name">` // 主要看 input 事件 // with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})} // render 函数 // 返回 vnode// patch// 编译const res = compiler.compile(template)console.log(res.render) // ---------------分割线-------------- // // 从 vue 源码中找到缩写函数的含义 // function installRenderHelpers (target) { // target._o = markOnce; // target._n = toNumber; // target._s = toString; // target._l = renderList; // target._t = renderSlot; // target._q = looseEqual; // target._i = looseIndexOf; // target._m = renderStatic; // target._f = resolveFilter; // target._k = checkKeyCodes; // target._b = bindObjectProps; // target._v = createTextVNode; // target._e = createEmptyVNode; // target._u = resolveScopedSlots; // target._g = bindObjectListeners; // target._d = bindDynamicKeys; // target._p = prependModifier; // }
组件渲染过程
-响应式:监听data属性getter setter(包括数组)
-模板编译:模板到render函数,再到vnode
-vdom:patch(elem,vnode)和patch(vnode, newVnode)
初次渲染过程
- 解析模板为render函数(或在开发环境已完成,vue-loader)
- 触发响应式,监听data属性getter setter
- 执行render函数,生成vnode,patch(elem, vnode)
更新过程
- 修改data,触发setter(此前在getter中已经被监听到)
- 重新执行render函数,生成newVnode
- patch(vnode,newVnode)
异步渲染
前端路由
- hash - window.onhashchange
- H5 history - history.pushState 和 window.onpopstate
Vue
1.v-show和v-for的区别
- v-show通过css display控制显示和隐藏
- v-if组件真正的渲染和销毁,而不是显示和隐藏
- 频繁切换显示状态使用v-show,否则用v-if
2.为何v-for中要用key
- 必须使用key,而且不用试index和random
- diff算法中通过key和tag来判断,是否是sameNode
- 减少渲染次数,提升渲染性能
3.描述vue组件生命周期(有父子组件的情况)
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
-
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
-
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
-
父组件更新过程
父 beforeUpdate -> 父 updated
-
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
4.vue组件如何通讯
- 父子组件props和this.$emit
- 之定义事件evet.off event.$emit
- vuex
5.描述组件渲染和更新的过程
6.双向数据绑定v-model的实现原理
- input元素的value=this.name
- 绑定input事件this.name = $event.target.value
- data更新触发re-render
7.computed和watch
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
-
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
-
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
8.为何组件data必须是一个函数
9.vue常见性能优化方案
(1)代码层面的优化
- 合理使用v-if 和 v-show
- 合理使用computed 和 watch
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- keep-alive、异步组件
- 自定义事件、DOM事件的及时销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化无限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 减少 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建结果输出分析
- Vue 项目的编译优化
(3)基础的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶颈