一、Vue使用
(一) 基本使用,组件使用-常用,必须会
[1]. 指令、插值
- 插值、表达式
- 指令、动态属性
- v-html:会有XSS风险,会覆盖子组件
[2]. computed和watch
- computed 有缓存,data不变则不会重新计算
- watch如何深度监听?
- watch默认是浅监听,可以使用deep参数
- watch监听引用类型,拿不到oldValue
[3]. class和style
- 使用动态属性
- 使用驼峰写法
[4]. v-if 和 v-show
- v-if v-else的用法,可使用变量,也可以使用===表达式
- v-if 和 v-show 的区别。
- v-if和v-show的使用场景。
如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
[5]. 循环(列表)渲染
- 如何遍历对象?
可以用v-for - key的重要性。Key不能乱写(如random 或者 index)
https://www.zhihu.com/question/61064119
key的作用主要是为了高效的更新虚拟DOM。 - v-for 和 v-if 不能同一级一起使用,v-for 比 v-if的级别高
[6]. 事件
-
event参数,自定义参数
event是原生的, 事件被挂载到当前元素<div id="app"> <button @click="showMessage('这是一个参数',$event)">查看event</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: "#app", methods: { showMessage(msg,event){ console.log(event); } }, }); </script>
-
事件修饰符,按键修饰符
- .stop 阻止事件继续传播
- .prevent 阻止默认事件
- .stop.prevent 可以串联
- <form v-on:submit.prevent> </form>只有修饰符
- 添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处理,然后才交由内部元素处理
.capture - 只当在 event.target 是当前元素自身是触发处理函数,即事件不是从内部元素触发的
.self - .native 监听子组件事件
- .once 只触发一次事件
[7]. 表单
- v-model
- 常见表单项textarea checkbox radio select
- 修饰符 .lazy .number .trim
[8]. Vue组件使用
1. 父子组件通讯:props和$emit
2. 组件间通讯-自定义事件
- 非父子组件:eventBus( $on,$emit,$off)
3. 组件生命周期
- 挂载阶段:beforeCreate、created、beforeMount、mounted
- 更新阶段:beforeUpdate、updated
- 销毁阶段:beforeDestroy、destroyed
- created和mounted的区别
-
咱们说一下created与mounted真正的区别吧:
Created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
Mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。 -
Created与Mounted的区别面试时的说法:
Created:在dom渲染之前调用,通常把初始化数据放在里面调用比如ajax数据等等。
Mounted:在dom渲染之后调用,比如咱们要获取document.getElementById,$(“#id”),ref等等需要dom操作放在这里,比如初始化的轮播图效果swiper需要获取dom就放在这里使用。 -
那么在用户体验解决了什么问题呢?
比如在传统模式下开发,网速慢的时候会先展示默认的静态数据,等ajax请求完成后才显示动态数据,这样对于用户体验不是很好,现在vue的钩子函数created就解决了这个问题,咱们把ajax请求的数据放到created里面,这样页面在加载dom之前就先把数据获取出来然后在渲染到页面上就解决了之前显示默认静态数据的问题,提升用户体验。
-
4. 生命周期(父子组件)
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
父beforeUpdate->子beforeUpdate->子updated->父updated
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
(二) 高级特性-不常用,但体现深度
[1]. 自定义 v-model
https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model
[2]. $nextTick
- Vue是异步渲染
- data改变之后,DOM不会立刻渲染
- $nextTick会在DOM渲染之后被触发,以获取最新DOM节点
[3]. slot细节
- 基本使用
- 作用域插槽
- 具名插槽
[4]. 动态、异步组件
1). 动态组件
- :is=“component-name” 用法
- 需要根据数据,动态渲染的场景。即组件类型不确定。
2). 异步组件
https://cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
[5]. keep-alive
- 缓存组件
- 频繁切换,不需要重复渲染
- Vue常见性能优化
[6]. mixin
- 多个组件有相同的逻辑,抽离出来
- mixin并不是完美的解决方案,会有一些问题
- 变量来源不明确,不利于阅读
- 多mixin可能会造成命名冲突
- mixin和组件可能出现多对多的关系,复杂度较高。
- Vue3 提出的Composition API旨在解决这些问题
[7]. ref
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件
(三) Vuex
[1]. 基本概念、基本使用和API必须掌握
1). 基本概念
- state
- getters
- action
- mutation
2). 用于Vue组件
- dispatch
- commit
- mapState
- mapGetters
- mapActions
- mapMutations
[2].可能会考察state的数据结构设计
vuex里面有module模块,通过这种方式可以把复杂的存储结构按功能拆分成不同文件管理。便于阅读和维护。
(四) Vue-router
1). 路由模式(hash、H5 history)
- hash模式(默认) 如:http://abc.com/#/user/10
- H5 history模式 如:http://abc.com/user/20
- 后者需要server端支持,因此无特殊需求可选择前者
2). 路由配置(动态路由、懒加载)
1.动态路由
二、Vue原理
(一) 组件化
[1]. 组件化基础
1). 如何理解Vue的MVVM模型
-
“很久以前”的组件化
- asp jsp php 已经有组件化了
- nodejs中也有类似的组件化
- 组件和模块
其实组件相当于库,把一些能在项目里或者不同类型项目中可复用的代码进行工具性的封装。
而模块相应于业务逻辑模块,把同一类型项目里的功能逻辑进行进行需求性的封装。
-
数据驱动视图(MVVM,setState)
- 传统组件,只是静态渲染,更新还要依赖于操作DOM
- 数据驱动视图-Vue MVVM
-
Vue MVVM
-
View对应视图层,在vue中对应template内的DOM部分。
-
Model(模型) 对应data里面的数据。
-
ViewModel是一个比较抽象的词语,这一层级应该说的vue提供的一个能力,比如说@click事件、methods里面的方法,让这两者连接的地方(当然包括methods内 涉及到model 修改的方法也属于ViewModel)。
-
这两者通过ViewModel 这一层进行关联通信
- 当Model数据改变可以立即执行到View的渲染
- View通过触发点击事件或DOM事件修改Model的数据
-
通过修改数据、视图层自动渲染,不用我们操作DOM,这些就是数据驱动视图,也是MVVM的核心概念。
(二)Vue 响应式
1). 步骤一:组件data的数据一旦变化,立刻触发视图的更新:
-
如何实现响应式:核心API - Object.defineProperty
基本使用:const data = { }; //var name = '张三'; //Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性 Object.defineProperty(data, 'name', { get: function () { console.log('get'); return name; }, set: function (newValue) { console.log('set'); name = newValue; } }); //测试 console.log(data.name); data.name = '李四'; console.log(data.name);
-
深度监听data变化
//视图更新函数 function updateView() { console.log('触发更新视图'); } //重新定义数组原型,避免污染全局Array原型 const oldArrayProperty = Array.prototype; //创建新对象,原型指向oldArrayProperty, 再扩展新的方法不会影响原型 const arrProto = Object.create(oldArrayProperty); ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { //重写Array的push,pop等方法 arrProto[methodName] = function () { updateView(); //触发视图更新 oldArrayProperty[methodName].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; } if (Array.isArray(target)) { target.__proto__ = arrProto; } //重新定义各个属性(for in 也可以遍历数组) for (let key in target) { defineReactive(target, key, target[key]) } } //准备数据 const data = { name: '张三', age: 20, info: { address: '北京' //需要深度监听 }, nums: [10, 20, 30] } //监听数据 observer(data); //测试 data.name = '李四'; //触发视图更新 data.age = 21; //触发视图更新 /*//设置新值时需要继续深度监听的理由 data.age = { num: 22 } data.age.num = 26 */ data.x = '100'; //新增属性,监听不到- 所以有Vue.set delete data.name //删除属性,监听不到 - 所以有Vue.delete data.info.address = '上海'; //深度监听 触发视图更新 data.nums.push(4) //监听数组 触发视图更新 console.dir(data)
-
Object.defineProperty的一些缺点(Vue3.0启用Proxy,Proxy有兼容性问题,且无法polyfill)
- 深度监听时,需要递归到底,一次性计算量大(数据深度很深时,容易卡死)
- 无法监听新增 / 删除属性(vue中使用Vue.set Vue.delete)
- 无法监听原生数组,需要特殊处理
(三) vdom(Virtual DOM)和diff
[1]. Virtual DOM
- vdom是实现vue和react的重要基石
- vue和react是数据驱动视图,如何有效控制DOM操作?
- 解决方案 - vdom
- vdom - 用js模拟DOM结构,计算出最小的变更,操作DOM
- 用JS模拟DOM结构
- 通过snabbdom学习vdom
- 简洁强大的vdom库,易学易用
- Vue参考它实现的vdom和diff
- https://github.com/snabbdom/snabbdom
- vdom: patch(elem, vnode) 和 patch(vnode, newVnode)
[2]. diff算法
diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。
1). diff算法概述
- 树diff的时间复杂度O(n^3)
- 优化时间复杂度到O(n)
- 只比较同一层级,不跨级比较
- tag 不相同,则直接删掉重建,不再深度比较
- tag和key,两者都相同, 则认为是相同节点,不再深度比较
2). diff算法源码
h函数
- 使用h()函数调用vnode()函数,返回创建 Javascript 对象(VNode) 描述真实DOM
patch函数
-
patch函数接收两个参数oldVnode和Vnode分别代表新的节点和之前的旧节点
-
如果两个节点都是一样的,那么就深入检查他们的子节点。如果两个节点不一样那就说明Vnode完全被改变了,就可以直接替换oldVnode。
-
虽然这两个节点不一样但是他们的子节点一样怎么办?别忘了,diff可是逐层比较的,如果第一层不一样那么就不会继续深入比较第二层了。
function patch (oldVnode, vnode) { // sameVnode 判断新老Vnode是否相同 if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode) } else { const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点 let parentEle = api.parentNode(oEl) // 父元素 createEle(vnode) // 根据Vnode生成新元素 if (parentEle !== null) { api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素 api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点 oldVnode = null } } // some code return vnode }
patchVnode函数
-
当我们确定两个节点值得比较之后我们会对两个节点指定patchVnode方法。那么这个方法做了什么呢?
patchVnode (oldVnode, vnode) { const el = vnode.el = oldVnode.el let i, oldCh = oldVnode.children, ch = vnode.children if (oldVnode === vnode) return if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) { api.setTextContent(el, vnode.text) }else { updateEle(el, vnode, oldVnode) if (oldCh && ch && oldCh !== ch) { updateChildren(el, oldCh, ch) }else if (ch){ createEle(vnode) //create el's children dom }else if (oldCh){ api.removeChildren(el) } } }
这个函数做了以下事情:
- 找到对应的真实dom,称为el
- 判断Vnode和oldVnode是否指向同一个对象,如果是,那么直接return
- 如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点。
- 如果oldVnode有子节点而Vnode没有,则删除el的子节点
- 如果oldVnode没有子节点而Vnode有,则将Vnode的子节点真实化之后添加到el
- 如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要
updateChildren函数
先说一下这个函数做了什么
- 将Vnode的子节点Vch和oldVnode的子节点oldCh提取出来
- oldCh和vCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和vCh至少有一个已经遍历完了,就会结束比较。
总结图
(四) 模板编译
[1]. 前置知识:JS的with语法
var obj = {
a:'张三',b:'李四'}
with(obj){
console.log(a); //张三
console.log(b); //李四
console.log(c); // c is not defined
}
[2]. vue-template-complier将模板编译为render函数
- 模板编译为render函数,执行render函数返回vnode
- 基于vnode在执行patch和diff
- 使用 webpack vue-loader,会在开发环境下编译模板(重要)
const compiler = require('vue-template-compiler');
/*******************************************/
//插值
//const template = `<p>{
{message}}</p>`;
//获得结果
//with(this){return _c('p',[_v(_s(message))])}
//等同于
//with(this){return createElement('p',[createTextVNode(toString(message))])}
//返回 vnode
/*******************************************/
/*******************************************/
//表单式
//const template = `<p>{
{flag ? message :'no message found'}}</p>`
//获得结果
//with(this){return _c('p',[_v(_s(flag ? message :'no message found'))])}
//等同于
//with(this){return createElement('p',[createTextVNode(toString(flag ? message :'no message found'))])
//返回 vnode
/*******************************************/
/*******************************************/
//属性和动态属性
//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}})])}
//等同于
/*with(this){
return createElement(
'div',
{staticClass:"container",attrs:{"id":"div1"}},
[
createElement(
'img',
{
attrs:{"src":imgUrl}
}
)
]
)
}*/
//返回 vnode
/*******************************************/
/*******************************************/
//条件
// 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")])])}
//返回 vnode
/*******************************************/
/*******************************************/
//循环
// 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)
// }
//返回 vnode
/*******************************************/
/*******************************************/
//事件
// const template = `<button @click="clickHandler">submit</button>`
//获得结果
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}
/*******************************************/
/*******************************************/
//v-model
const template = `<input type="text" v-model="name">`
// 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}}})}
/*******************************************/
//编译
const res = compiler.compile(template);
console.log(res.render);
// ---------------分割线--------------
//with 中的this 就是 vue的实例
//template 被编译成render 函数
//返回 vnode
// _c(标签名, 属性, 子元素)
// // 从 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;
// }
[3]. 执行render函数生成vmode
(五) 渲染过程
[1]. 初次渲染过程
- 解析模板为render函数(或在开发环境以完成, vue-loader)
- 触发响应式,监听data属性getter setter
- 执行render函数,生成vnode, patch(elem,vnode)
[2]. 更新过程
- 修改data, 触发setter(此前在getter中已被监听)
- 从新执行render函数,生成newVnode
- patch(vnode, newVnode)
[3]. 异步渲染
- 回顾$nextTick
- 汇总data的修改,一次性更新视图
- 目的:减少DOM操作次数,提高性能
(六) 前端路由
[1]. hash路由
1). hash的特点
-
hash变化会触发网页跳转,即浏览器的前进、后退
-
hash变化不会刷新页面,SPA(单页面应用)必须的特点
-
hash永远不会提交到server端(前端自生自灭)
-
hash变化包括:js修改url,手动修改url的hash,浏览器的前进、后退。
<button id="btn">更改hash</button> <script> window.addEventListener("hashchange",function(){ console.log(location.hash); }) document.addEventListener('DOMContentLoaded', function () { console.log(location.hash); }) document.querySelector("#btn").addEventListener("click", function () { location.hash = '#/user' }) </script>
[2].H5 history
-
用url规范的路由,但跳转时不刷新页面
-
history.pushState 打开一个新路由,浏览器不会刷新页面
-
window.onpopstate 监听浏览器的前进、后退
-
需要后端支持
<p>history API test</p> <button id="btn1">修改 url</button> <script> // 页面初次加载,获取 path document.addEventListener('DOMContentLoaded', () => { console.log('load', location.pathname) }) // 打开一个新的路由 // 【注意】用 pushState 方式,浏览器不会刷新页面 document.getElementById('btn1').addEventListener('click', () => { const state = { name: 'page1' } console.log('切换路由到', 'page1') history.pushState(state, '', 'page1') // 重要!! }) // 监听浏览器前进、后退 window.onpopstate = (event) => { // 重要!! console.log('onpopstate', event.state, location.pathname) } // 需要 server 端配合,可参考 // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90 </script>
[3]. 两者的选择
三、Vue真题
[1]. v-show 和 v-if 的区别
- v-show通过CSS display 控制显示和隐藏
- v-if组件真正的渲染和销毁,而不是显示和隐藏
- 频繁切换显示状态用v-show,否则用v-if
[2]. 为何在v-for中用key
- 必须用key,且不能是index和random
- diff算法中通过tag和key来判断,是否是sameNode
- 减少渲染次数,提升渲染性能
[3]. 描述Vue组件生命周期(父子组件)
- 组件生命周期图
- 父子组件生命周期关系
[4]. Vue组件如何通讯(常见)
- 父子组件props和this.$emit
- 自定义事件eventBus.$no eventBus.$off eventBus.$emit
- vuex
[5].描述组件渲染和更新的过程
-
初次渲染:
解析模板为render函数(或再开发环境已完成)
触发响应式,监听data属性的getter的依赖收集,也即是往dep里面添加watcher的过程
执行render函数,生成vnode,patch -
更新过程:
修改data,setter(必需是初始渲染已经依赖过的)调用Dep.notify(),将通知它内部的所有的Watcher对象进行视图更新
重新执行rendern函数,生成newVnode
然后就是patch的过程(diff算法)
[6]. 双向数据绑定v-model的实现原理
- input元素的value = this.name
- 绑定input事件this.name = $event.target.value
- data更新触发re-render
[7]. 对MVVM的理解
[8]. computed有何特点
- 缓存,data不变不会重新计算
- 提高性能
[9]. 为何组件data必须是一个函数
.vue文件编译后是一个class(类), 当去使用这个组件时,就是对类的实例化,如果data不是一个函数则在不同实例中会进行数据共享,造成数据的黏连。
[10]. ajax请求应该放在哪个生命周期
- mounted
- js是单线程,ajax异步获取数据
- 放在mounted之前没有用,只会让逻辑更加混乱
[11]. 如何将所有props传递给子组件
- $props
- <User v-bind="$props" />
- 细节知识点,优先级不高
[12]. 如何自己实现v-model
[13]. 多个组件有相同的逻辑,如何进行抽离
- mixin
- 以及mixin的一些缺点
[14]. 何时使用异步组件
- 加载大组件
- 路由异步加载
[15]. 何时需要使用keep-alive
- 缓存组件,不需要重复渲染
- 如多个静态tab页的切换
- 优化性能
[16]. 何时需要使用beforeDestory
- 解绑自定义事件 event.$off
- 清除定时器
- 解绑自定义的DOM事件,如window scroll等
[17]. 什么是作用域插槽
[18]. Vuex中active和mutation有何区别
- action中处理异步,mutation不可以
- mutation做原子操作
- action 可以整合多个mutation
[19]. Vue-router常用的路由模式
- hash默认
- H5 history (需要服务端支持)
- 两者的比较
[20]. 如何配置 Vue-router异步加载
import异步组件的方式进行配置。
[21]. 请用vnode描述一个DOM结构
[22]. 监听data变化的核心API是什么
- Object.defineProperty
- 以及深度监听,监听数组
- 有何缺点
[23]. Vue如何监听数组变化
- Object.defineProperty不能监听数组变化
- 重新定义原型,重写push pop等方法,实现监听
- proxy可以原生支持监听数组的变化
[24]. 请描述响应式原理
- 监听data变化
- 组件监听和更新的过程
[25]. diff算法的时间复杂度
- O(n)
- 在O(n^3)基础上做了一些调整
[26]. 简述diff算法过程
- diff算法主要描述了当数据发生改变时dom都经历了什么
- 当开发者更改了一处代码进行保存之后,会调用Object.defineProperty中的set方法,set方法会调用Dep.notify方法通知所有订阅者Watcher,订阅者就会调用patch方法去进行dom的判断
- ① 在patch方法中会调用sameVnode方法,通过sameVnode方法判断newNode与oldNode是否相同
- ② 如果不相同,直接销毁oldNode,将newNode添加到dom中
- ③ 如果相同,则调用patchVnode方法比较下属节点
- 如果oldVnode有子节点而newNode没有,则删除dom的子节点
- 如果他们都有文本节点并且不相等,那么将dom的文本节点设置为newNode的文本节点。
- 如果oldVnode没有子节点而newNode有,则将newNode的子节点真实化之后添加到dom
- 如果两者都有子节点,则执行updateChildren函数比较子节点
- 在updateChildren函数中,使用while循环遍历下面的子节点,并通过回调sameVnode方法继续进行判断,直到子节点对比完成
[27]. Vue为何是异步渲染,$nextTick何用
- 异步渲染(以及合并data修改),以提高渲染性能
- $nextTick在DOM更新完之后,触发回调
[28]. Vue常见性能优化方式
- 合理使用v-show 和v-if
- 合理使用conputed
- v-for时加key,以及避免和v-if同时使用
- 自定义事件、DOM事件及时销毁
- 合理使用异步组件
- 合理使用keep-alive
- data层级不要太深,因为深度监听时递归过多
- 使用vue-loader在开发环境做模板编译(预编译)
- webpack层面的优化
- 前端通用的性能优化,如图片懒加载
- 使用SSR
四、Vue3
[1]. Vue 3升级内容
- 全部用ts重写(响应式、vdom、模板编译等)
- 性能提升,代码量减少
- 会调整部分API
[2]. Proxy实现响应式
-
基本使用
操作数据是对proxyData操作
const data = { name: '张三', age: 20, } const proxyData = new Proxy(data, { get(target, key, receiver) { const result = Reflect.get(target, key, receiver); console.log('get', key); return result; //返回结果 }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); console.log('set', key, value); return result; }, deleteProperty(target, key) { const result = Reflect.deleteProperty(target, key); console.log('delete property', key); return result; } }) proxyData.age = 21;
五、项目设计
(一) data数据设计
- 用数据描述所有的内容
- 数据要结构化,易于程序操作(遍历、查找)
- 数据要可扩展,以便增加新的功能
(二) 组件设计
- 从功能上才分层次
- 尽量让组件原子化
- 容器组件(只管理数据)&UI组件(只显示视图)
五、项目流程
(一) 项目角色
- PM产品经理
- UE视觉设计师
- FE前端开发
- RD后端开发
- CRD移动端开发
- QA测试人员
(二) 项目流程
[1].需求分析
- 了解背景
- 质疑需求是否合理
- 需求是否闭环
- 开发难度如何
- 是否需要其他支持
- 不要急于给排期
[2]. 技术方案设计
- 求简,不过渡设计
- 产出文档
- 找准设计重点
- 组内评审
- 和RD CRD沟通
- 发出会议结论
[3]. 开发
- 如何反馈排期
- 预留一个排期空间,一般为真实时间+ 真实时间/4
- 符合开发规范
- 写出开发文档
- 及时单元测试
- Mock API
- Code Review
[4]. 联调
- 和 RD CRD技术联调
- 让UE确定视觉效果
- 让PM确定产品功能
[5]. 测试
- 提测发邮件,抄送项目组
- 测试问题要详细记录
- 有问题及时沟通,QA和FE天生信息不对称
[6]. 上线
- 上线之后及时通知QA回归测试
- 上线之后及时同步给PM和项目组
- 如有问题,及时回滚,先止损,再排查问题
(三)面试题
- 1). PM(产品经理)想在项目开发过程中增加需求,该怎么办?
- 不能拒绝,走需求变更流程即可
- 如果公司有规定,则按规定走
- 否则,发起项目组和leader的评审,重新评估排期
- 2). 项目即将延期了,该怎么办
- 及时沟通
- 3). 你将如何保证项目质量
- 符合开发规范
- 写出开发文档
- 及时单元测试
- Code Review