目录
Vue
1、MVC与MVVM的区别
什么是MVC?
什么是MVVM?
2、谈谈你对MVVM开发模式的理解?
3、谈谈对Vue渐进式框架的理解?
为什么说vue是一个渐进式的javascript框架,渐进式是什么意思?
4、vue 的优点是什么?
5、vue脚手架自己能搭建项目吗
6、vue 常用的修饰符?
7、v-if 和 v-show 的区别
v-if与v-show的区别
8、为什么避免v-for和v-if同时使用?
9、vue三大核心:
10、vue双向数据绑定原理
11、vue2.0响应式底层原理?
12、vue3响应式原理--proxy
13、vue2和vue3的区别
14、说说你对 SPA 单页面的理解,它的优缺点分别是什么?
15、生命周期是什么?
16、Vue的生命周期方法有哪些?一般在哪一步发送请求?
异步请求在哪一步发起?
17、直接给一个数组项赋值,Vue 能检测到变化吗?
18、watch 和 computed 区别
19、Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
20、vue中为什么data是一个函数
21、filter中的this是什么?
22、vue的key的作用是什么?
23、为什么不建议用 index 作为 key 呢?
24、写一个produce带参数路由配置
25、Vue中路由相关的组件是?
26、异步请求适合在哪个生命周期调用?
27、vue、小程序、uni-app中的本地数据存储和接收
28、vue , 微信小程序 , uni-app的页面生命周期函数
29、vue , 微信小程序 , uni-app属性的绑定
30、怎么重定向页面?
31、vue的性能优化?
32、说一下vue3.0你了解多少?
33、Vue 的父组件和子组件生命周期钩子函数执行顺序?
34、为什么 Vuex 的 mutation 中不能做异步操作?
35、Hash和history有什么区别
36、Vue的patch diff 算法
37、query和params 有何区别?
38、虚拟 DOM 的优缺点?
虚拟DOM实现原理?
什么是虚拟DOM?
39、在vue-for循环中为什么使用key?
40、vue2 有没有遇到数据更新视图不更新?
41、this指向问题
42、vue中.sync的使用
43、vue中$nextTick的作用及使用场景?
44、Vue组件通讯有哪些方式?
45、 axios封装哪些常用功能
46、 哪些数据你都存储在vuex?
47、vuex和全局变量区别
48、VueRouter 是什么?你平常是怎么用的?
49、你使用过 Vuex 吗?
50、vuex的 mutations和action区别
51、 你封装过哪些组件(哪些插件)?
52、vue-router 有哪几种导航守卫?
53、谈谈你对 keep-alive 的了解?
54、 父组件可以监听到子组件的生命周期吗?
55、router和route的区别
56、路由传参,子路由
React
1、为什么虚拟 dom 会提高性能?
2、回调渲染模式
3、调用 setState 之后发生了什么?
4、概述下 React 中的事件处理逻辑
5、React 中 keys 的作用是什么?
6、React 中 Element 与 Component 的区别是?
什么是 React?
React性能优化方案
1、Code Splitting 代码分割
2、shouldComponentUpdate 避免重复渲染
3、使用不可突变数据结构
4、组件尽可能的进行拆分、解耦
5、列表类组件优化
6、bind 函数优化
7、不要滥用 props
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
MVC中Controller演变成MVVM中的ViewModel
MVVM通过数据来显示视图层而不是节点操作
MVVM主要解决了MVC中大量的dom操作,使页面渲染性能降低,加载速度变慢,影响用户体验
MVC表示“模型-视图-控制器”,有利于SEO
MVVM表示“模型-视图-视图模型”,有利于开发
MVVM是由MVC衍生出来的。MVC中,View会直接从Model中读取数据;
MVVM各部分的通信是双向的,而MVC各部分通信是单向的; MVVM是真正将页面与数据逻辑分离放到js里去实现,而MVC里面未分离。
MVVM中的View 和 ViewModel可以互相通信。
MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开.View一般用过Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本都是单向联系。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。
优点:
低耦合
重用性高
生命周期成本低
部署快
可维护性高
有利软件工程化管理
在MVVM的框架下,视图和模型是不能直接通信的,它们通过ViewModal来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。
渐进式的含义:主张最少,没有多做职责之外的事;它只是轻量视图而已,只做自己该做的事,没有做不该做的事,仅此而已。
1、Vue有些方面不如React、不如Angular,但它是渐进的,没有强主张;
2、可以在原有的系统上面,把一两个组件改用它实现,当jQuery用;
3、也可以整个当全家桶开发,当Angular用;
4、还可以用它的视图,搭配自己设计的整个下层用。
5、也可以只使用它的视图层,底层的数据你用OO方式(设计模式)进行实现;
6、所以VUE的适用面很广,可以用它代替老项目中的JQuery。也可以在新项目启动初期,有限的使用VUE的功能特性,从而降低上手的成本。
vue允许你将一个页面分割成可复用的组件,每个组件都包含属于自己的html、css、js用来渲染网页中相应的地方。 对于vue的使用可大可小,它都会有相应的方式来整合到你的项目中。所以说它是一个渐进式的框架。 vue是响应式的(reactive)这是vue最独特的特性,也就是说当我们的数据变更时,vue会帮你更新所有网页中用到它的地方。
1、低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的"View"上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
2、可重用性:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑。
3、独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用 Expression Blend 可以很容易设计界面并生成 xml 代码。
4、可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。
能,原理是用webpack 搭建,写配置文件
vue-loader 处理.vue
vue-template-compiler 编译模板
.prevent
: 提交事件不再重载页面;
.stop
: 阻止单击事件冒泡;
.self
: 当事件发生在该元素本身而不是子元素的时候会触发;
.capture
: 事件侦听,事件发生的时候会调用
都可以隐藏节点
v-show通过css方式隐藏,适用于非常频繁地切换。(不能用于权限操作)
v-if 直接移除dom节点,适用于运行时条件很少改变。
频繁切换显示与隐藏用v-show反之用v-if
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。元素销毁和重建控制显示隐藏
v-show 会被编译成指令,条件不满足时控制样式将此节点隐藏(display:none) css样式控制
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景。
v-show 适用于需要非常频繁切换条件的场景。
三者公共点都是 隐藏。
不同点:
1、是否占据空间。
display:none,隐藏之后不占位置;
visibility:hidden、opacity:0,隐藏后任然占据位置。
2、子元素是否继承。
display:none --- 不会被子元素继承,父元素都不存在了,子元素也不会显示出来。
visibility:hidden --- 会被子元素继承,通过设置子元素 visibility:visible 来显示子元素。
opacity:0 --- 会被子元素继承,但是不能设置子元素 opacity:0 来先重新显示。
3、事件绑定。
display:none 的元素都已经不存在了,因此无法触发他绑定的事件。
visibility:hidden 不会触发他上面绑定的事件。
opacity:0 元素上面绑定的事件时可以触发的。
4、过度动画。
transition对于display是无效的。
transition对于visibility是无效的。
transition对于opacity是有效的。是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且V和VM可以进行通信。
v-for比v-if优先级高,使用的话每次v-for都会v-if判断,影响性能;
解决方法很简单,就是在v-for的外面再加一层标签进行v-if的判断
Vue.js 的组件化
机制
Vue.js 的响应式系统
原理
Vue.js 中的 Virtual DOM
及 Diff 原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体实现流程:
1、实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者
2、实现一个订阅者Watcher,每个Watcher都绑定一个更新函数,Watcher可以收到属性的变化通知并执行相应的函数,从而更新视图 iii:实现一个消息订阅器 Dep ,主要收集订阅者,当 Observe监听到发生变化,就通知Dep 再去通知Watcher去触发更新。
3、实现一个解析器Compile,可以扫描和解析每个节点的相关指令,若节点存在指令,则Compile初始化这类节点的模板数据(使其显示在视图上),以及初始化相应的订阅者。
通过Object.defineProperty劫持对象的getter与setter 结合订阅者与发布者模式 观察者来连接视图与数据 当数据发生变化时候通知要订阅该数据的订阅者更新
追问1:那如果我要监听一个对象属性的删除或添加呢?
受 defineProperty 限制,Vue 无法检测对象属性的删除和添加。所以我们可以利用 Vue 提供的 Vue.set
来解决此问题。
追问2:为什么对象属性的删除或添加无法触发页面更新?
因为 vue 在实例化过程中,深度遍历了 data 下所有属性, 把属性全转为 getter/setter 。这样才能监听属性变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
当你在对象上新加了一个属性 newProperty ,当前新加的这个属性并没有加入 vue 检测数据更新的机制(因为是在初始化之后添加的), vue.$set 是能让 vue 知道你添加了属性, 它会给你做处理。
在Vue3中,数据响应式主要借助proxy和Reffect配合实现,可以做到实现数据的增删改查。
通过Proxy(代理): 拦截对象中任意属性的变化,包括:属性值的读写,属性的增加,属性的删除等。通过Reffect(反射):对源对象的属性进行操作
1.数据进行劫持的方法不同,vue2是利用ES5的一个API Object.definePropert()而vue3是利用proxy代替。
2、vue3增加(组合式)compositionAPI的选项 setup()
3、启动方式不同
4、全局挂载方法
5、生命周期函数不同,vue2中的生命周期:beforeCreate,created;beforeMount,mounted;beforeUpdate,updated、卸载前后。 vue3的生命周期:setup;onBeforeMount,onMounted;onBeforeUpdate,onUpdated、卸载前后 除了这些钩子函数外,vue3还增加了onRenderTracked 和onRenderTriggered函数。
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页 面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
1、用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
2、基于上面一点,SPA 相对对服务器压力小;
3、前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
1、初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS 统一加载,部分页面按需加载;
2、前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
3、SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
创建期间的生命周期函数:
1、beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
2、created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
3、beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中。 换句话说,此时页面中的类似 {{msg}} 这样的语法还没有被替换成真正的数据。
4、mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示【可以获取 DOM 节点,获取组件结构 | 发起异步请求】用户已经可以看到渲染好的页面了
运行期间的生命周期函数:
5、beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
6、updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
销毁期间的生命周期函数:
7、beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用。
使用场景:解除绑定,销毁子组件以及事件监听器。
8、destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
keep-alive 缓存操作
9、activated: keep-alive 专属,组件被激活时调用
10、deactivated: keep-alive 专属,组件被销毁时调用
ssr:该钩子在服务器端渲染期间不被调用
11、onErrorCaptured: –当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播
网络请求:created、mounted
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data已经创建,可以将服务器端返回的数据进行赋值。
如果异步请求不需要依赖 DOM 推荐加载 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:1、能更快获取到服务端数据,减少页面loading时间;2、如果依赖DOM元素,需要在mounted里面进行请求
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
watch 是监听动作,computed 是计算属性
watch 没缓存,只要数据变化就执行。computed 有缓存,只在属性变化的时候才去计算。
watch 可以执行异步操作,而 computed 不能
watch 常用于一个数据影响多个数据,computed 则常用于多个数据影响一个数据
运用场景:
computed
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;使用:当一个属性受多个属性影响时,例如:购物车结算
watch
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。使用:当一个数据影响多条数据时,例如:搜索框
1、Vue 中使用 Object.defineProperty 进行双向数据绑定时,告知使用者是可以监听数组的,但是只是监听了数组的 push()、pop()、shift()、unshift()、splice()、sort()、reverse() 这八种方法,其他数组的属性检测不到。
2、Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
3、Object.defineProperty 只能劫持对象的属性,因此对每个对象的属性进行遍历时,如果属性值也是对象需要深度遍历,那么就比较麻烦了,所以在比较 Proxy 能完整劫持对象的对比下,选择 Proxy。
4、Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
1、和js本身有关,涉及到原型。
2、每一个组件都有自己的私有作用域,确保各组件数据不会被干扰。
3、组件的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一分新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
this是undefined,在filter中拿不到vue实例。filter应该是个纯函数,不应该依赖外界或者对外界有所影响。如果需要用到this,可以用 computed 或者 method 代替。
过滤器的作用?如何实现一个过滤器?使用场景?
过滤器是用来过滤数据的,在vue中使用filters来过滤数据;
使用场景:例如(时间/日期 格式化)
key的作用主要是为了高效的更新虚拟DOM
更新DOM时会出现性能问题
因为index在同一个页面会有重复的情况,违背了高效渲染的初衷
{ name:"produce",path:"/produce/:id",component:Produce }
在页面中获取参数$route.params.id
存放路由页面 切换路由
路由配置
{ path:"/about" ,
name:"about",
component:About
}
路由传参
{path:"/produce/:id",name:"produce",component:Produce}
{$route.params.id}
路由方法(5个)
$router.go 跳转
back() 返回
forward 前进
push() 添加历史记录
replace()替换
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端返回的数据进行赋值。
我一般在 created 钩子函数中调用异步请求,能更快获取到服务端数据,减少页面 loading 时间;
vue:
存储:localstorage.setItem(‘key’,‘value’)
接收:localstorage.getItem(‘key’)
微信小程序:
存储:通过wx.setStorage/wx.setStorageSync写数据到缓存
接收:通过wx.getStorage/wx.getStorageSync读取本地缓存,
uni-app:
存储:uni.setStorage({key:“属性名”,data:“值”}) //异步
uni.setStorageSync(KEY,DATA) //同步
接收:uni.getStorage({key:“属性名”,success(res){res.data}}) //异步
uni.getStorageSync(KEY) //同步
vue:
beforeCreate(创建前) created(创建后)
beforeMount(载入前) mounted(载入后)
beforeUpdate(更新前) updated(更新后)
beforeDestroy(销毁前) destroyed(销毁后)
小程序/uni-app:
1、onLoad:首次进入页面加载时触发,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
2、onShow:加载完成后、后台切到前台或重新进入页面时触发
3、onReady:页面首次渲染完成时触发
4、onHide:从前台切到后台或进入其他页面触发
5、onUnload:页面卸载时触发
6、onPullDownRefresh:监听用户下拉动作
7、onReachBottom:页面上拉触底事件的处理函数
8、onShareAppMessage:用户点击右上角转发
vue和uni-app动态绑定一个变量的值为元素的某个属性的时候,会在属性前面加上冒号":";
小程序绑定某个变量的值为元素属性时,会用两个大括号{{}}括起来,如果不加括号,为被认为是字符串。
const router = new VueRouter({
routes:[
{ path: '/a', redirect: '/b' }
]
})
// 怎么配置404页面?
const router = new VueRouter({
routes:[
{
path: '*', redirect: {path:'/'}
}
]
})
路由懒加载、图片懒加载、第三方组件库按需引入、keep-alive缓存页面、使用v-show复用DOM、避免v-if与v-for同时使用
vue2中,我们一般会采用mixin来复用逻辑代码,挺好用的,但是存在一些问题:例如代码来源不清晰、方法属性等冲突。
vue3中引入了Composition API(组合API),使用纯函数分隔复用代码。
Fragment:在vue2中,组件必须只有一个根节点,很多时候会添加一些没有意义的节点用于包裹。Fragment组件就是用于解决这个问题的
(1) Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过Action来提交mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。
(2) 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的跟踪,给调试带来困难
1、vue项目默认是hash,hash就是指url后面的#号以及后面的字符,history没有带#;
2、原理:
(1) hash通过监听浏览器的onhaschange()事件变化,查找对应的路由规则;
(2) history原理:H5的history中新增的两个API push State()和replace State() 和一个事件onpopstate监听url变化;
3.hash能兼容到IE8,history只能兼容到IE10;
4.由于hash值变化不会导致浏览器向服务器发出请求,而且hash改变会触发hashchange事件(hashchange只能改变#后面的url片段);虽然hash路径出现在url中,但是不会出现在HTTP请求中,对后端完全没有影响,因此改变hash值不会重新加载页面,基本都是使用hash来实现前端路由的。
patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较virtual DOM的变更,得出变化以修改视图。
diff算法核心是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,是一种相当高效的算法。
同层级比较(只比较同一层级,不跨级比较) tag 不相同,则直接删除重建,不在深度比较 tag 和 key,两个都相同,则认为是相同节点,会进行深度比较
用法:
query用path来引入,params只能用name来传递,不能使用path。 如果params中把name写成了path,接收参数页面将会是undefined。 query更像是get请求(会在地址栏显示参数);params更像post方法传递(不会在地址栏显示)。
优点:
1、保证性能下限: 虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限
2、无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高开发效率
3、跨平台: 虚拟DOM本质上是JavaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等等
缺点:
1.首次显示要慢些:首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢
2.无法进行极致优化:虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中 无法进行针对性的极致优化。
虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
状态变更时,记录新树和旧树的差异
最后把差异更新到真正的dom中
什么是虚拟DOM?
简单的说:虚拟DOM就是用来模拟DOM结构的一个js对象。
(1) 虚拟dom是什么?
vue2.x才有的虚拟dom;本质是js对象;
(2) 虚拟dom在vue中做了什么?
将真实dom转化虚拟dom(js对象); 更新的时候做对比;
(3) 虚拟dom是如何提升vue的渲染效率的?
局部更新(节点更新); 将直接操作dom的地方拿到两个js对象之中去做比较;
在虚拟DOM出现之前我们是怎么操作DOM的? 2.1.使用原生JS/jQuery 这时通过直接操作DOM元素来达到视图更新的效果 流程:直接操作DOM元素 -> 视图更新。
在vue2.0推出后我们是怎么操作DOM的?
更新视图的核心是:改变数据 -> 操作DOM -> 视图更新
在vue中:改变数据 -> 操作DOM 这个过程是在vue中进行的
改变一次数据就要操作一次DOM,那么当许多数据同时改变,就要多次作DOM,这样的方式是不行的,最直接的缺点就是浪费时间。所以,虚拟DOM派上了用场!!!
虚拟DOM的工作流程
首先:用JS以对象的形式模拟DOM结构,表示页面上的某个节点
其次:通过patch函数,将虚拟DOM节点塞入到空的容器中
最后:通过核心计算渲染时指挥修改改变过的节点,没有变化的节点不需要再次渲染
使用虚拟DOM的好处在于,新虚拟DOM和旧虚拟DOM的比较,计算出来需要更新的视图,再操作DOM,也就是使用虚拟DOM来表示真实的DOM,目的计算出最小的变化,根据最小变化来更新真实的DOM结构
虚拟DOM核心diff算法
diff算法的概念:diff 算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历
流程:
1-先遍历一次老的虚拟DOM 2-再遍历一次新的虚拟DOM 3-根据改变/新增,对真实DOM重新进行排序
弊端:如果有100个DOM节点的话就要计算100^3次,如果节点更多的话会造成大量的计算,这样的方式是不可取的
所以,vue中对diff算法进行了优化
1-只会比较同一层级,不会跨级比较 2-比较标签名:标签名不同时,直接删除,不继续深入比较 3-标签名相同时:判断key是否相同,这就是为什么在vue中v-for循环 一定要绑定key的原因,当key相同时,就会默认是相同节点,不进行深入比较
key为了让vue虚拟dom节点查找与更新更加优化(优化dom的渲染)
vdom 优化diff算法:
1、如果key一致就不向下查找
2、如果tag不一致,子节点不查找
3、只在同级做比较
n3次方优化到n1次方
vue2 通过数组下标更新数据,视图不会更新
Vue.set(数据名,下标,数值)强制视图更新
this.$set(数组名,key,value)
vue2 数组的双向绑定监听是通过重写数组的原型方法 pop,push ..
函数的this在函数执行确定的
1、构造函数 new 函数名() this指向 new出来对象的实例
2、箭头函数的this指向上一层作用域的this
3、对象中的this指向对象
4、事件响应函数的this指向 调用者 5、setTimout setInterval 里面this指向window 6、call,apply,bind 响应的函数this指向第一个参数
.sync 是自定义事件: v-on事件的新的语法糖
vue中的nextTick主要用于处理数据动态变化后,DOM未及时更新的问题,用nextTick就可以获取数据更新后最新DOM的变化,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
nextTick的回调函数会等到同步任务执行完毕,DOM更新后才触发。
使用场景:
当你通过网络请求获取数据,由于网络请求的延迟导致ui没有被加载,然后获取结构发生报错的情况下,你可以把赋值操作放到nextTick里面,它会保证到DOM渲染完毕后去执行。
1、props 和 $emit。父组件向子组件传递数据是通过props传递的,子组件传递给父组件是通过$emit触发事件来做到的。
$emit v-on 事件处理(自定义事件) -> (.sync:2.3+)简化
2、$parent 和 $children 获取单签组件的父组件和当前组件的子组件。
3、$attrs 和 $listeners A -> B -> C。Vue2.4开始提供了$attrs和$listeners来解决这个问题。
4、父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中适用,但是写组件库时很常用。)
5、$refs 获取组件实例。
6、envetBus 兄弟组件数据传递,这种情况下可以使用事件总线的方式。
7、vuex 状态管理,token、购物车
基础配置:baseURL,timeout,headers
拦截响应与请求:加载提示,token和其他请求头,错误响应操作,添加或移除权限
当多个组件都需要使用的数据通常都存储在vuex里面
app信息: 菜单栏信息,设备,运行模式
用户相关:token ,用户信息,权限信息,第三方登录信息 用户访问历史,操作日志
设置信息:主题,自定义菜单,标签 权限相关:路由,固定路由,菜单路由, 同时通过actions执行请求,mutations更新
都可以实现多个组件全局共享数据
vuex的数据是响应式,会自动更新视图
vuex的修改必须是mutations,更加利于调试
vuex还支持异步操作,方便vuex插件便于调试
是什么:Vue-Router 是 Vue 官方的路由管理器
作用:为了页面跳转
原理:监听锚点值改变,渲染指定页面
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state ),通过vuex可以更好集中管理数据,多个组件共享数据
(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:
1、State => 存放公共数据的地方,可以在这里设置默认的初始状态。
2、Getter => 获取根据业务场景返回的数据,从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
3、Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。
4、Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
5、Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中,减少代码臃肿;
mutations 修改状态的方案 state(唯一方式)
actions 提交mutations,而不是直接修改状态
mutations 特性,直接更改状态,只可以更改同步操作
actions 特性,提交mutations,而不是直接修改状态,执行异步操作
日期格式,防抖节流,瀑布流,弹框,提示,翻译。 设计好组件的data(尽量不使用data) 定义好props(多使用) 设计好方法 设计插槽和查找作用域 注意组件的传参
哪些工具
解析时间,格式时间,获取查询参数 清空数组,url转换(qs)html转文本 对象 合并,常见dom操作,防抖,节流 深拷贝
全局守卫、路由独享守卫、路由组件内的守卫
//路由的钩子函数总结有6个
全局的路由钩子函数:beforeEach、afterEach
单个的路由钩子函数:beforeEnter
组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate
实现权限管理:
在路由配置定义meta权限
通过全局beforeEach实现路由守卫
beforeEach 3个参数 to要去的页面,from从哪个页面来,next下一操作
一般结合路由和动态组件一起使用,用于缓存组件;
< keep-alive >是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
< keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
生命周期钩子:actived:激活调用; deactived:停用时调用
属性:include:名称(正则 or 字符串)匹配时调用; exclude:名称不匹配时调用; max:最多缓存数量
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
// Child.vue
mounted() {
this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
1、route是一个跳转的路由对象(路由信息对象),每一个路由都会有一个$route对象,是一个局部的对象。可以从route里面获取hash,name,path,query,mathsr等属性方法(接收参数时使用)
2、router 跳转连接就可以使用,$router是VueRouter的一个实例,他包含了所有的路由,包括路由的跳转方法,钩子函数等,也包含一些子对象(例如history)
params传参 query查询传参 hash传参 meta传参
子路由:在路由配置添加children
动态添加路由 ,router有个AddRoutes方法
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
具体实现步骤如下:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异,把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
这种模式中,组件会接收某个函数作为其子组件,然后在渲染函数中以 props.render进行调用,这种模式的优势在于将父组件与子组件解耦和,父组件可以直接把组件的内部状态传递给子组件而不需要再通过 Props 传递,这样父组件能够更为方便地把数据共享给子级组件。
在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。
React 内部实现上,并不是对每个 DOM 分别绑定事件的,它的事件统一绑定在document 上,这个可以在控制台里运行 getEventListeners(document)来查看事件绑定 getEventListeners($0)可以查看当前 Elements 页面选中元素的事件绑定的事件。
React里各个 DOM 元素的事件都统一绑定在 document 上,document 上触发了一个事件,React 从事件的数据里找到是谁触发的,然后在监听的映射表里找到应该触发谁的绑定事件,这样性能很好,可以让 React 更快。
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。
简单而言,React Element 是描述屏幕上所见内容的数据结构,是对于 UI 的对象表述。典型的 React Element 就是利用 JSX 构建的声明式代码片然后被转化为createElement 的调用组合。
而 React Component 则是可以接收参数输入并且返回某个React Element 的函数或者类。
它是用于构建用户界面的库。无论 React 或 React 生态系统看起来多么复杂,这都是 React 的核心-构建 UI。考虑到这一点,我们得出了第一个定义,即 Element。
简而言之,React 元素描述了您想要在屏幕上看到的内容。简而言之,React 元素是 DOM节点的对象表示。注意,我使用了“ 描述 ”一词。重要的是要注意,React 元素实际上并不是您将在屏幕上看到的东西。相反,它只是它的对象表示。
有几个原因。首先是 JavaScript对象是轻量级的— React 可以创建和销毁这些元素而不会产生太多开销。第二个原因是React 可以分析对象,将其与先前的对象表示形式进行比较以查看发生了什么变化。然后,React 只能在发生这些更改的地方更新实际的 DOM。这具有一些性能优势。
为了创建 DOM 节点(又称 React 元素)的对象表示,我们可以使用 React 的createElement 方法。
学习 React 的有趣之处在于,通常,首先教会您的是组件。“组件是 React 的基石”。但是请注意,我们是从元素开始的。这样做的原因是,一旦您理解了元素,对组件的理解就可以顺利过渡。组件是可以选择接受输入并返回 React 元素的函数或类。
1、Code Splitting 代码分割
2、shouldComponentUpdate 避免重复渲染
3、使用不可突变数据结构
4、组件尽可能的进行拆分、解耦
5、列表类组件优化
6、bind函数优化
7、不要滥用props
可以帮你”懒加载“代码,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个bundle拆分成多个bundle + 多份动态代码的形式。
webpack提供三种代码分离的方法: 入口起点:使用entry配置手动底分离代码 防止重复:使用SplitChunks去重和分离chunk 动态导入:通过模块的内联函数调用来分离代码
当一个组件的 props 或者 state 改变时,React 通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际Code Splitting的 DOM。当他们不相等时,React 会更新 DOM。
在一些情况下,你的组件可以通过重写这个生命周期函数 shouldComponentUpdate 来提升速度, 它是在重新渲染过程开始前触发的。 这个函数默认返回 true,可使 React 执行更新。
引用官网中的例子解释一下突变数据产生的问题。例如,假设你想要一个 ListOfWords 组件来渲染一个逗号分隔的单词列表,并使用一个带了点击按钮名字叫 WordAdder 的父组件来给子列表添加一个单词。以下代码并不正确:
class ListOfWords extends React.PureComponent {
render() {
return {this.props.words.join(',')};
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 这段内容将会导致代码不会按照你预期的结果运行
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
render() {
return (
);
}
}
导致代码无法正常工作的原因是 PureComponent 仅仅对 this.props.words 的新旧值进行“浅比较”。在 words 值在 handleClick 中被修改之后,即使有新的单词被添加到数组中,但是 this.props.words 的新旧值在进行比较时是一样的(引用对象比较),因此ListOfWords 一直不会发生渲染。避免此类问题最简单的方式是避免使用值可能会突变的属性或状态,如:
1)数组使用 concat,对象使用 Object.assign()
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
// 假设我们有一个叫 colormap 的对象,下面方法不污染原始对象 function
updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
2)ES6 支持数组或对象的 spread 语法
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'], }));
};
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
3)使用不可突变数据 immutable.js
immutable.js 使得变化跟踪很方便。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。
const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false
在这个例子中,x 突变后返回了一个新的索引,因此我们可以安全的确认 x 被改变了。不可突变的数据结构帮助我们轻松的追踪对象变化,从而可以快速的实现shouldComponentUpdate。
组件尽可能的细分,比如一个 input+list 组件,可以将 list 分成一个 PureComponent,只在 list 数据变化时更新。否则在 input 值变化页面重新渲染的时候,list 也需要进行不必要的 DOM diff。
key 属性在组件类之外提供了另一种方式的组件标识。通过 key 标识,在组件发生增删改、排序等操作时,可以根据 key 值的位置直接调整 DOM 顺序,告诉 React 避免不必要的渲染而避免性能的费。
例,对于一个基于排序的组件渲染:
var items = sortBy(**this**.state.sortingAlgorithm, **this**.props.items);
return items.map(**function**(item){
return
});
当顺序发生改变时,React 会对元素进行 diff 操作,并改 img 的 src 属性。显示,这样的操作效率是非常低的。这时,我们可以为组件添加一个 key 属性以唯一的标识组件:
return
增加 key 后,React 就不是 diff,而是直接使用 insertBefore 操作移动组件位置,而这个操作是移动 DOM 节点最高效的办法
绑定 this 的方式:一般有下面 3 种方式:
1)constructor 绑定
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this); //构造函数中绑定
}//然后可以
2)使用时绑定
3)使用箭头函数
{ this.handleClick() }}/>
以上三种方法,第一种最优。
因为第一种构造函数只在组件初始化的时候执行一次,
第二种组件每次 render 都会执行
第三种在每一次 render 时候都会生成新的箭头函数。例:Test 组件的 click 属性是个箭头函数,组件重新渲染的时候 Test 组件就会因为这个新生成的箭头函数而进行更新,从而产生 Test 组件的不必要渲染。
props 尽量只传需要的数据,避免多余的更新,尽量避免使用{...props}