框架类
vue的虚拟dom了解过吗
答案解析:
vue将DOM抽象成一个以JavaScript对象构成的抽象树,以VNode节点模拟真实DOM,可以对这个抽象树进行节点的增删改查,这个过程都不需要去操作真实DOM,只需要在修改完使用diff算法算出一些需要修改的最小单位,在将这些小单位视图进行更新,就可以减少很多不必要的DOM操作,大大提高性能。
优点
- 具有跨平台的作用:因为虚拟dom 是以js对象为基础而不依赖真实平台环境,所以有跨平台的优点,可以是浏览器、nodeJs、weex,为前后端同构提高了可能。
- 提高了渲染的性能:在我们大量、频繁的数据更新下,能够对视图进行合理、高效的更新。
- 提高浏览器性能:我们将dom的对比操作放在了JS层,因为dom操作的执行速度远不如js的速度快,因此把大量dom操作搬运至js中,运用patching算法计算出真正更新的节点,最大限度的去减少dom操作,从而显著的提高性能。
vue的patch的算法
核心算法是diff算法,diff算法是通过同层树节点进行比较而非对树进行逐层搜索遍历的方式,所有时间复杂度只有o(n),是一种十分高效的算法。
替换规则:
- 当新节点没有子节点而老节点有子节点时,则移除该DOM节点的所有子节点
- 如果老节点没有子节点而新节点存在子节点,先清空老节点的DOM文本内容,然后为当前DOM节点加上子节点
- 如果新旧节点均匀children子节点,则对子节点做diff操作,调用updateChildren
vue的mvvm框架是什么
mvvm框架是整个视图层view的概念,属于视图层的概念, 全称意思是:
模型-视图-视图模型
- 模型:是指后端传递的数据
- 视图:指的是所看到的页面
- 视图模型:是mvvm模式的核心,它是连接view和model的桥梁,涉及到两个s数据传递方向:
1)将模型转换为视图,即将后端传递的数据转化成所看到页面。实现方法是数据绑定
2)将视图转换为模型,即将所看到的页面转化为后端的数据,实现方式是事件监听
在MVVM框架下视图和模型是不能直接通信的,需要通过view model来通信,通过实现一个观察者,当数据发生变化,view model监听变化然后通知到对应的视图做自动更新,而当用户操作视图,viewmodel也能够监听到视图变化,然后通知数据做改动,实现数据的双向绑定
vue的computer和watch的区别以及应用场景
答案解析:
computer属性
- 计算属性
- 基于缓存的,如果页面重新渲染但是要计算所依赖的值没有发生变化的时候则不会重新计算
- 简化模板{{}}的表达式,用来处理props的传值
watch属性
- 是一个侦听动作
- 不会缓存,侦听的属性值一旦发生变化则会执行handle函数
- 在子组件中监听prop变化执行相应的动作
vue的父子组件的生命周期执行顺序
- 加载渲染过程
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子 mounted -> 父 mounted
- 子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
- 销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
Vue生命周期
-
beforeCreate:
在数据检测和初始化事件还未开始,data、watcher、methods都不存在。 -
created:
在实例创建之后被调用,该阶段可以访问data,使用watcher、events、methods,数据观测和数据监听事件配置已完成,但是dom没有挂载。 -
beforeMount:
将html解析成AST节点,再根据AST节点动态生成渲染函数,相关render函数首次被调用 -
mounted:
在挂载完成之后被调用,执行render函数生成虚拟dom,创建真实dom替换虚拟dom,并挂载在实例上。在这个钩子函数可以操作dom。 -
beforeUpdate:
vm.data被更新以后,虚拟dom重新渲染之前被调用,在这个钩子可以修改vm.data,不会触发附加的重渲染过程。 -
updated:
虚拟dom重新渲染后调用,若在这里修改data,会再次触发beforeUpdate,updated,进入这样一个死循环。 -
beforeDestory:
实例被销毁前调用,在这个钩子还是可以调用实例 -
destory:
实例被销毁后调用,所有的事件监听器被移除,子实例被销毁
vue在那个生命周期阶段调用异步请求最佳
异步请求在什么阶段调用都是可以,因为会先执行完全生命周期的钩子函数才会执行异步函数。但是为了考虑用户体验方面的话,在created中调用异步请求最佳,越早获取数据,在mounted实例挂载的时候越及时。
vue的keep-alive的作用
答案解析:
keep-alive是vue的一个抽象化组件,可以保存其包裹的组件的状态,使其不被销毁,避免组件反复渲染和创建。
<keep-alive :include = 'whiteList' :exclude = 'blackList' :max='amount' >
<router-view></router-view>
</keep-alive>
-
indelude
定义缓存白名单,keep-alive会缓存命中的组件 -
exclude
定义缓存黑名单,被命中的组件将不会被缓存 -
max
定义缓存组件上限,超出上限使用LRU的策略置换缓存数据
属性:
该标签有include和exclude,都可以使用字符串或者是正则表达式表示,前者是只有匹配的组件才会被缓存,后者是匹配成功的组件不会被缓存
生命钩子:
提供两个生命钩子,分别是activated生命周期在组件激活时调用、deactivated生命周期在组件停用时调用。
源码剖析
- 相对于定义组件,组件名设置为
keep-alive
,定义了abstract
属性为true
,props
属性定义了keep-alive
组件支持全部参数。 - 在生命周期定义了钩子函数
-
created
初始化两个对象分别缓存vnode(虚拟dom)和vnode对应键的集合 -
destoryed
删除this.cache中缓存的vnode实例,以及对应组件实例的destory钩子函数 -
mounted
对include和exclude参数进行监听,然后实时地更新(删除)
-
vue的数据双向绑定原理
答案解析:
vue的数据双向绑定是利用数据劫持结合发布-订阅模式实现的,其核心是利用Object.definePrototype()方法,这个方法可以实现对象的属性的可读、可遍历、可枚举的权限,以及监听数据的变化执行get、set操作
实现三大模块
- compile编译器:在项目初始化的时候解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知后,更新视图。
- 监听器:对数据对象以及子属性对象的属性加上getter、setter ,去监听和劫持data的元素,一旦监听到变动,及时向订阅器发送更新信号.
- 订阅器:1)在自身实例化时往属性订阅器(dep)里面添加自己 ; 2)待属性变动触发dep.notice()通知的时候,能够调用自身的update()方法,并且触发compile中绑定的回调,更新视图。
react和vue的区别,为什么学vue
数据流向以及设计思想: vueJS设计思想是响应式的,基于数据可变,数据流是可双向的; react整体设计是函数式思想,把组件设计为纯组件,状态和逻辑通过参数传入,数据流是单向的。
更新性能: react中一个组件的状态的变化,会引起整个组件的子树从根部进行重新渲染;(解决方案:尽量使用不可变的数据结构,随时使用shouldComponentUpdate); vue中可以准确知道那些组件实际上需要重新渲染的,组件的依赖关系在它的渲染期间被自动跟踪,所有不会引发整个组件全部渲染一遍。在更新性能方面,vue优于react
语法: react中html和css都是通过js编写的,所有组件的渲染都需要依靠jsx(jsx是使用xml语法编写js的一种语法糖);vue中提供的是在html中写模板;
vue2.0 和vue3.0的区别
- 项目目录结构
- vue 3.0移除了配置文件目录、config和build文件夹
- 同时移除了static静态文件夹,新增了public文件夹,并且将index.html放在public中
- 项目配置
- 域名配置从在config文件夹中移到了vue.config.js中
- 支持可视化界面,在项目使用vue ui命令即可进行配置、依赖操作
- 数据双向绑定原理核心由
Object.definepropotype
变成 使用proxy对象代理的方式,解决只支持对象的新增的元素可以被监听的问题。
patch的核心算法diff算法
答案解析:
vue的diff算法仅在同级d的vnode间做diff,递归地进行同级vnode的diff,最终实现整个DOM树的更新
diff算法核心函数的实现流程
- 初次渲染时,将VDOM渲染成真正的dom,然后插入到容器当中
function element(vnode){
var tag = vnode.tag;
var attrs = vnode.attrs || {};
var children = vnode.children || [];
var elm = document.createElement(tag);
if(!tag) return;
for(var attrName in attrs){
if(attrs.hasownprototype(attrName){
elem.setAttribute(attrName,attrs[attrName]);
}
children.forEach(function(childNode){
elem.appendChild(createElement())
})
}
}
- 再次渲染时,将新的虚拟结点和旧的虚拟结点相对比
vuejs实现数据驱动视图原理
答案解析:
数据驱动是vuejs的最大特点之一,是通过MVVM框架实现的,包括三个部分:model、view、viemodel. 数据驱动原理是:当数据发生变化的时候,开发者不需要手动去修改dom,而是利用一个观察者来实现数据驱动。
流程如下:
- vueJS在实例化的过程中,会对遍历传给实例化对象中的data选项,遍历其所有属性并用
Object.defineProperty
把这些属性转化成getter/setter - 每一个实例对象都有一个
watcher
对象,该对象会在模板编译的过程中,用getter去访问data属性,并且记为依赖,建立数据与视图之间的联系 - 当我们渲染视图的数据依赖发生变化(其实也就是setter发生变化),观察者会对比前后数据是否发生变化,然后确定是否通知视图进行重新渲染。
babel转换器
是一个js的编辑器,将es6以上的代码转换成向后兼容的js语法,能够运行在当前和旧版本的浏览器中,其实本质也就是一个工具链
babel的执行过程
核心是parse、transform、generator三部分,执行过程如下
- 源代码通过
编译器
转换成抽象语法树AST - 将抽象语法树AST通过
转换器
转换成得到新的AST树 - 将新的AST树通过
生成器
重新生成代码,这样的代码可以在浏览器中安全运行
vue的slot插槽的使用
插槽元素是作为分发内容的一个出口,用于决定所携带的内容,插入到指定的某个位置,从而实现我们想要展示的内容。
优点
- 可以使用父组件的数据,拥有和父组件一样的实例属性,这一点就可以很好去实现父子组件传参
vue2.0怎么实现数据双向绑定、以及vue3.0是怎么实现的?这样做解决了什么问题
vue2.0是通过数据劫持结合发布订阅实现的数据双向绑定,核心是通过object.defineproperty()的getter、setter来监听数据的变化,然后通过订阅器去实现数据更新渲染的。主要涉及这个这么几个模块:监听器、编译器、订阅器之间的相互配合
- 编译器:在项目初始化扫描各个模板的元素并且解析成数据对象然后初始化订阅器
- 监听器:去监听和劫持data元素,一旦监听到变动,及时向订阅器发送送数据更新信号
- 订阅器: 是编译器和监听器的桥梁,接收到信号,执行updated函数,更新视图
vue3.0是通过proxy代理的方式实现数据劫持。相比之下优点是:
- defineProperty只能监听某个属性,不能全对象监听
- 可以检测到数组内部数据变化,不用单独对数组做特异性操作
理解vueJs的 nextTick()
用途场景:
- 在vue的生命周期的created()钩子函数进行dom的操作则需要放在
this.nextTick()
的回调函数中。
在created钩子函数执行的时候dom没有进行任何渲染,所有无法进行dom操作,但是如果借助this.nextTick()
则可以实现在created钩子函数操作dom
- 在下次DOM更新循环结束之后执行延迟回调的情况下,如果需要修改数据可以立即使用这个方法,可以获取到更新后的DOM.
- 在数据变化的时候要执行某个操作,而这个操作需要使用数据改变而改变DOM结构,这个操作可以防止
this.nextTick()
执行
vue的异步更新dom策略
- 当某个响应式数据发生变化时,它的setter函数会通知dep,dep会调用它管理的所有watch对象,触发watch对象的update实现。
- watch对象并不是立即更新视图,而是被push进入到了一个队列,此时状态为waiting状态,在这个期间还可以有很多watch对象被push进入队列中(并且会做去重操作),等到nextTick运行时,这些watch对象才会被遍历出来,更新视图。
nextTick的目的是产生一个回调函数放入vue的任务执行栈中,等到栈执行完以后调用该回调函数,执行队列的watch对象中的run函数,更新视图,能够起到这样一个异步触发的目的。并且在该执行完的回调函数中执行dom就可以确保使用的dom一定是更新完的最新的
异步更新的目的是: 假设在挂载函数中改变test的值很多次,如果是在没有异步更新的情况,则每更新一次都会直接操作一次dom更新视图,十分消耗性能。而vueJS实现了队列,会在下一个tick的时候统一执行队列的watch对象run方法,并且会执行去重操作,如果是同一个id的watch对象不会重复添加到队列中。所以大大优化了性能。
vue获取路由参数的方式
- 通过定义动态路由,获取传过来的动态参数
设置方式:路由配置文件中,path属性加上/:参数名
,然后在实际的路由地址携带参数
获取方式:使用router对象 this.$route.params.id 获取
- 在使用
this.$router.push()
这个函数,可以定义Params
传递参数,以定义动态路由不可同用 -
query
和path
传递参数 -
name
和paramas
v-if和v-for同时使用会出现的问题
原因:v-for的优先级会高于v-if,则意味着v-if会运行在每一个v-for循环中
解决方法:
- 不使用v-if,使用计算属性再利用过滤器去筛选掉不需要显示的信息
vue常用的修辞符
-
.stop
: 阻止冒泡 -
.prevent
:阻止默认事件 -
.once
:事件只触发一次 -
.lazy
:在默认情况,使用v-model
在每次input事件触发的时候将输入框的值与数据同步,但是当我们添加.lazy
修辞符,会转为change
事件之后进行同步(也就是失去焦点或者按下回车键的时候) -
.number
:将输入的值转换成Number类型 -
.trim
: 会自动过滤掉输入的首尾空格
node.js是什么 ?
node.js是一个基于Chrome V8引擎的JavaScript的运行环境。
当前服务器程序存在问题:
- 并发连接数有效,随着客户端的增长,如果希望web应用程序能够支持更多用户,则需要添加更多服务器,增加服务器成本、流量成本和人工成本等。
- 潜在技术问题:用户可能针对不同请求使用不同服务器,因此共享资源需要所有服务器共享。
最大瓶颈:服务器能够处理的并发连接的最大数量
nodeJS提出的解决方案:更改连接到服务器的连接方式。每一个连接发射一个在node引擎的进程中运行的事件,而不是为每一个连接生成一个OS线程。所以就是nodejs通过更改连接到服务器的方式,处理高并发任务。
优点: Node.js使用了一个事件驱动、非阻塞式I/O模型,异步编程,使其轻量又高效。
缺点: 单进程、单进程,只支持单核cpu,不能充分的利用多核cpu服务器,一旦这个进程崩掉了,那么整个服务器就崩了。
vue项目的优化
vue框架通过数据双向绑定和虚拟dom技术,处理了大量最麻烦的dom操作部分,不需要考虑如何去操作dom以及如何高效的去操作dom.
-
代码层面的优化
- v-if和v-show区分好使用场景
- v-if是真正的条件渲染,会确保在切换条件过程中条件块的事件和节点适当被销毁和重建。但是同时也是惰性的,初始渲染条件为假时,则什么都不会做。适用于不需要频繁切换条件
- v-show 无论什么情况元素都是会被渲染,只是简单的基于css的display的切换,适用于频繁切换条件场景。
- computed和watch区分使用场景
- v-for遍历加上key,以及避免和v-if一起使用
- 长列表性能优化
- vue 2.0 是通过object.defineProperty对数据进行劫持,来实现视图响应数据的变化,但是有些组件就是纯粹数据展示,那么就不需要vue去劫持我们的数据,在大量数据展示情况,就能够明显减少组件初始化时间。禁止vue劫持数据方法,通过Object.freeze去冻结对象
- 图片资源懒加载
对于图片过多的页面,为了加速页面加速速度,很多时候可以将页面未出现在可视区域内的图片先不做加载,等滚动到可视区域后再做加载,页面加载性能会有很大提升。
1. 加载静态的loading图片
2. 根据滚动条滚动的高度判断那些图片是需要加载
3. 生成img对象,隐形加载图片
4. 将假图片替换成真图片,需要在data-src放上真正需要加载的图片地址才行 - 路由懒加载
- v-if和v-show区分好使用场景
- webpack层面的优化
- 基本的web技术优化
Vuex原理实现
vuex是一个专门为vue.js应用程序开发的状态管理模式,采用集中式存储管理所有组件的状态,并以相应的规则保证状态,应用场景是:当我们的应用遇到多个组件共享状态时,vue的单项数据流的简洁性很容易受到破坏,可以使用于多个组件共享数据或者跨组件传递数据。主要包括这么几个部分:state、getters、mutations、actions、module
- state:存储的单一状态,是存储的基本数据
- getters:是state的计算属性,对state进行二次加工 ,派生出来的数据,并且getter返回的值会根据它的依赖被缓存起来,只有当它的依赖值发生改变了才会被重新计算
- mutations:提交更改数据,使用
store.commit
方法更改state存储状态(同步更新) - actions:像一个装饰器,提交
mutation
方法,而不是直接变更状态,可以用来执行异步操作 - moudule:是store分割的模块,每个模块都有getters、state、actions等
vuex的数据流
vue组件调用某个vuex的方法过程中需要向后端请求或者说出现异步操作时,需要dispatch
vuex的actions
的方法,在提交mutations
中的方法,以达到对state
成员的操作。最后被修改后的state成员会被渲染到组件的原位置当中。
Vue中mixin的用法详解
vue引入提供的混合机制--mixins,用来更加高效的实现组件内容的复用。
- 组件在引用后,将组件内部的内容如data、method等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了
- 父组件和子组件同时拥有着子组件内的各种属性方法,但是并不意味着他们同时共享、同时处理这些变量,是相互之间不影响的。
Vue-router核心实现原理
router-link 和 router-view 是在vue-router中实现了全局定义的两个组件,分别用来跳转路由和展示路由对应的组件内容。点击router-link时vue-router内部监听路由变化,根据路由规则找到匹配组件,然后再router-view中渲染。
- 单页面: 第一次进入页面会请求一个HTML文件,当我们切换到其他组件,此时路径也发生相对应,页面内容也发生变化,但是没有新的html文件请求。
原理如下: js通过api监听url的变化,利用js将当前页面内容清空,然后将下一个页面内容挂载到当前页面上,这个时候路由不是由前端来做,而是前端控制,判断页面到底显示那个组件,这个过程就是单页应用。
- 多页面: 每一次页面跳转,后台服务器都会返回一个新的HTML文档,这种类型的网站就是多页网站,也叫多页应用。
原理如下: 传统的单页应用,是通过超链接实现页面切换和跳转的。
vue-router的实现原理
原理核心是更新视图但不重新请求页面。vue-router实现页面路由跳转的模式有:hash模式、history模式
路由模式
- hash: 使用url hash值作为路由,为默认模式。hash是浏览器url中
#
+ 后面的内容,URL中的锚点,代表网页中的一个位置,单单改变#后的部分,浏览器只是会加载相应位置的内容,不会重新加载页面,而我们也可以通过window.location.hash属性去读取。-
#
虽然出现在url中,但是不会被包括在http请求中,是用来指导浏览器动作的,对服务端完全无用,因此改变hash不会重载页面。 - 对hash的改变添加监听事件,然后去更新路由视图;每一次改变hash内容,都会在浏览器的访问历史中增加一个记录。
-
所以总结得:hash模式是通过锚点值的改变,根据不同的值,渲染指定dom的不同数据
- history: 依赖HTML5 History API和服务器配置。 history.pushState API能让开发人员在不刷新页面的情况下修改站点URL
history模式如果请求地址在后端没有对应路由处理,则会返回404错误,则为了应对这个的情况,需要增加一个覆盖所有情况的候选资源,当url匹配不到资源时,返回一个统一处理的404页面
vue-router的懒加载
懒加载主要是将页面划分,需要的时间加载页面,有效的承担首页加载压力,减少首页加载用的时间。
懒加载方法、原理
- 使用
require
方式引入路由(与require引入方式相关) - 使用webpack提供
require.ensure()
计数,实现按需加载,将多个路由指定相同的chunkName,然后会打包成一个js文件。
vue的嵌套路由
嵌套路由是指在一个被路由过来的页面下可以继续使用路由,嵌套也就是路由中的路由的意思。多可以使用左右子菜单跳转,实现局部页面内容变化。路由配置中的children属性中配置
vue-router的全局导航钩子
- 全局守卫:
使用routet.beforeEach
注册一个全局前置守卫,当一个导航触发时,全局前置守卫
按照创建顺序调用。
router.beforeEach((to, form , next )=>{
})
接收三个参数:
-
to:Route
代表要进入的目标 -
from:Route
表示正要离开的路由 -
next:Function
守卫执行的效果是依赖next方法的参数
-
next()
进入管道的下一个钩子 -
next(false)
中断当前导航,并且重置到from路由对应的地址 -
next({path:'/'})
调转到一个任意位置地址
-
- 路由独享守卫
在配置的路由对象之间定义beforeEnter
守卫
const router = new VueRouter({
routes:[{
path:'/foo',
component:foo,
beforeEnter:(to, from, next) =>{
}
}]
})
- 组件内的守卫