谈一下你对 MVVM 的认识
mvvm :Model-View-ViewModel的缩写,
model数据层
,数据模型,仅仅关注数据本身
View视图层
是用户操作界面 也可称为,当ViewModel对Model进行更新的时候,会通过数据绑定更新到view
viewmodel 业务逻辑层 view需要什么数据 ViewModel要提供这个数据,view有哪些操作,ViewModel就要响应哪些操作,所以也可以说 它是Model for View
为什么会出现MVVM
前端开发中暴露出了三个痛点问题:
- 开发者在代码中大量调用相同的 DOM API,处理繁琐 ,操作冗余,使得代码难以维护。
- 大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
- 当 Model 频繁发生变化,开发者需要主动更新到 View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到 Model 中,这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。其实,早期 jquery 的出现就是为了前端能更简洁的操作 DOM 而设计的,但它只解决了第一个问题,另外两个问题始终伴随着前端一直存在。MVVM 的出现,完美解决了以上三个问题。
总结:MVVM模式简化了界面与业务的一览,解决了数据频繁更新,MVVM在使用当中,利用双向绑定技术,使得Model变化时,ViewModel会自动更新,而,ViewModel变化时,View也会自动变化。在MVVM的框架下视图和模型是不能直接通信的,并没有直接的联系,它们通过ViewModel来通信。Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到View 上。ViewModel 通过双向数据绑定把 View 和 Model 连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
MVC
- MVC 是 Model-View-Controller 的缩写,即 模型—视图—控制器。
Model:后端传递的数据。
View:所看到的页面。
Controller:页面业务逻辑。 - MVC是单向通信。即View和Model,必须通过Controller来承上启下。
- 使用MVC的目的就是将M和V的代码分离。
- MVC 和 MVVM 的区别 并不是VM完全取代了C,ViewModel 存在目的 在于抽离 Controller 中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在 Controller 中实现。也就是说MVVM实现的是业务逻辑组件的重用。
vue 生命周期有哪些?
一共8个阶段
1、beforeCreate(创建前)
2、created(创建后)
3、beforeMount(载入前)
4、mounted(载入后)
5、beforeUpdate(更新前)
6、updated(更新后)
7、beforeDestroy(销毁前)
8、destroyed(销毁后)
vue生命周期及对应的行为
image.png
beforeCreate(创建前) 在数据观测和初始化事件还未开始
created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el
属性还没有显示出来
beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用
。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html
。注意此时还没有挂载html到页面上。
mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前
。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
vue父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父beforeUpdate->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
介绍JSX
jsx可以像我们写HTML文件一样写业务代码,借助于babel,会将jsx语法转换成render语法,没有什么副作用。
vue template语法简单明了,数据操作与视图分离,开发体验友好。但是在某些特定场合中,会限制一些功能的扩展,如动态使用过滤器、解析字符串类型的模板文件等。以上功能的实现可以借助vue的render语法,render语法比template更偏底层,允许在HTML中使用js语法,可以极大的扩展HTML的能力。render函数注入了一个参数createElement,用来创建我们所需要的标签内容,有三个参数:HTML标签(elementTag),标签属性(option),子元素(children);从createElement的参数列表里面可以看出,如果组件内部结构嵌套比较深,render的语法写起来会比较繁琐,需要不断的调用createElement,对于想偷懒的我,还是想想有没有什么比较简易的写法,jsx无疑是一种很好的选择,区别在于jsx可以像我们写HTML文件一样写业务代码,借助于babel,会将jsx语法转换成render语法,没有什么副作用。
如何实现一个指令
什么是虚拟 DOM
虚拟DOM概念随着react的诞生而诞生,由facebook提出,其卓越的性能很快得到广大开发者的认可;继react之后vue2.0也在其核心引入了虚拟DOM的概念
什么是虚拟DOM?
vdom可以看作是一个使用javascript模拟了DOM结构的树形结构,这个树结构包含整个DOM结构的信息.。dom 树对应的虚拟 dom 对象( js 对象),如下图:
image.png
可见左边的DOM结构,不论是标签名称还是标签的属性或标签的子集,都会对应在右边的树结构里。
为什么要使用虚拟DOM?
虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的,在react,vue等技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom 然后修改样式行为或者结构,来达到更新 ui 的目的。这种方式相当消耗计算资源
,因为每次查询 dom 几乎都需要遍历整颗 dom 树
,如果建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树,那么每次 dom 的更改
就变成了 js 对象的属性的更改
,这样一来就能查找 js 对象的属性变化要比查询 dom 树的 性能开销小
。
其实并不是查询 dom 树性能开销大而是 dom 树的实现模块和 js 模块是分开的这些跨模块的通讯增加了成本,以及 dom 操作引起的浏览器的回流和重绘,使得性能开销巨大,原本在 pc 端是没有性能问题的,因为 pc 的计算能力强,但是随着移动端的发展,越来越多的网页在智能手机上运行,而手机的性能参差不齐,会有性能问题。
他们的思想是每次更新 dom 都尽量避免刷新整个页面,而是有针对性的 去刷新那被更改的一部分
,来释放掉被无效渲染占用的 gpu,cup 性能。
angular
angular 采用的机制是 脏值检测查机制
所有使用了 ng 指令的 scope data 和 {{}} 语法的 scope data 都会被加入脏检测的队列
vue
vue 采用的是虚拟dom通过重写 setter , getter
实现观察者
监听 data 属性的变化生成新的虚拟 dom 通过 h 函数创建真实 dom 替换掉dom树上对应的旧 dom。
react
react 也是通过虚拟 dom 和 setState 更改 data 生成新的虚拟 dom 以及 diff 算法来计算和生成需要替换的 dom 做到局部更新的。
虚拟dom的原理
diff算法 深度优先遍历
diff的过程就是调用patch函数,就像打补丁一样修改真实dom
virtual dom有哪些好处
- 虚拟 DOM 不会立马进行排版与重绘操作
- 虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗
- 虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部
虚拟DOM主要做了什么
虚拟DOM本身是什么(JS对象)
为什么虚拟 DOM 的操作比 DOM 更快;
总之,一切为了减弱频繁的大面积重绘引发的性能问题,不同框架不一定需要虚拟DOM,关键看框架是否频繁会引发大面积的DOM操作
你的知道浏览器的虚拟DOM与真实DOM的区别(注意:需不需要虚拟DOM,其实与框架的DOM操作机制有关):
虚拟DOM不会进行排版与重绘操作
虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分(注意!),最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
真实DOM频繁排版与重绘的效率是相当低的
虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部(同2)
使用虚拟DOM的损耗计算:
总损耗=虚拟DOM增删改+(与Diff算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘
直接使用真实DOM的损耗计算:
总损耗=真实DOM完全增删改+(可能较多的节点)排版与重绘
总之,一切为了减弱频繁的大面积重绘引发的性能问题,不同框架不一定需要虚拟DOM,关键看框架是否频繁会引发大面积的DOM操作
说一下virtual Dom中key的作用
实际的标签中可能存在两个一模一样的两个节点,但是在virtual Dom中无法体现这个区别,另一方面为了加快diff算法的速度,一个区别节点的变量的需求变得非常必要。virtual Dom中需要给每个节点一个标识,作为判断是同一个节点的依据。所以这也是 Vue 和 React 中官方推荐列表里的节点使用唯一的 key 来保证性能。其中在diff算法中,大量使用了利用tagName和key组合判断节点之间差异的逻辑代码
vue有了响应式,为啥需要虚拟dom
vue的虚拟dom和react虚拟dom有啥区别嘞
vue.nextTick实现原理
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
1.在Vue生命周期的created()钩子函数进行DOM操作一定要放到Vue.nextTick()的回调函数中。
在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题。
2.在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的Promise.then和MessageChannel,如果执行环境不支持,会采用setTimeout(fn, 0)代替。
例如,当你设置vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数在 DOM 更新完成后就会调用。
Emit事件怎么发,需要引入什么
子组件: 通过emit(“listenToChildEvent”, deliver)
父组件进行绑定事件的,调用方法!
v-on:listenToChildEvent=”showMsgFromChild”
渲染出全部的name,在哪个生命周期里写,其中有几个name不存在,通过异步接口获取,如何做
谈谈eleme框架源码
vue和react谈谈区别和选型考虑
1) React和Vue有许多相似之处,它们都有:
使用 Virtual DOM
提供了响应式(Reactive)和组件化(Composable)的视图组件。
将注意力集中保持在核心库,伴随于此,有配套的路由和负责处理全局状态管理的库。
2) 性能:
到目前为止,针对现实情况的测试中,Vue的性能是优于React的
3) 生态圈
Vue.js: ES6+Webpack+unit/e2e+Vue+vue一router+单文件组件+vuex+iVew
React: ES6+Webpack+Enzyme+React+React一router+Redux
4) 什么时候选择Vue.js
如果你喜欢用(或希望能够用)模板搭建应用,请使用Vue
如果你喜欢简单和”能用就行”的东西,请使用Vue
如果你的应用需要尽可能的小和快,请使用Vue
如果你计划构建一个大型应用程序,请使用React
如果你想要一个同时适用于Web端和原生App的框架,请选择React
如果你想要最大的生态圈,请使用React
vue项目中如何约束rxjs数据的类型(根据项目问)
vue组件间通信x3
组件间通讯方法
import { Button } from ‘antd’,打包的时候只打包button,分模块加载,是怎么做到的
image.png
使用import时,webpack对node_modules里的依赖会做什么
配置相关路径
前端怎么控制管理路由
可以通过vue-router实例来配置路由规则列表,指定路径path与组件component的对应关系。可以通过mode这一参数控制路由的实现模式,默认值是hash
,基于hash的实现方式,如果显示设置为history
,则会设为基于history API的实现方式,如果浏览器不支持,可以设置fallback来控制是否需要回滚为’hash’模式。另外,如果是非浏览器端运行(如nodejs中),会将mode强制设为’abstract’模式。
vue-router支持路由嵌套、动态路由的配置、重定向及别名等,可参看官方文档。
前端路由的实现方式
在HTML5的 history API出现之前,前端路由主要是通过 hash 来实现的,hash能兼容低版本的浏览器。下面分别来介绍这2种方式。
方法一:基于hash(location.hash+hashchange事件)
我们知道location.hash的值是url中#后面的内容,如http://www.163.com#netease此网址中,location.hash=’#netease’。hash满足以下几个特性,才使得其可以实现前端路由:
url中hash值的变化并不会重新加载页面,因为hash是用来指导浏览器行为的,对服务端是无用的,所以不会包括在http请求中。
hash值的改变,都会在浏览器的访问历史中增加一个记录,也就是能通过浏览器的回退、前进按钮控制hash的切换
我们可以通过hashchange事件,监听到hash值的变化,从而响应不同路径的逻辑处理。
window.addEventListener(“hashchange”, funcRef, false)
如此一来,我们就可以在hashchange事件里,根据hash值来更新对应的视图,但不会去重新请求页面,同时呢,也在history里增加了一条访问记录,用户也仍然可以通过前进后退键实现UI的切换。
触发hash值的变化有2种方法:
一种是通过a标签,设置href属性,当标签点击之后,地址栏会改变,同时会触发hashchange事件
<a href=”#kaola”>to KAOLA</a>
另一种是通过js直接赋值给location.hash,也会改变url,触发hashchange事件。
location.hash=”#kaola”
route是一条路由,是将一个URL路径和一个处理函数相关联,是一条url和函数的映射规则,如上面代码中通过原型上的route可以设置一条路由规则,将一个path和其callback关联起来。
而router则更像是一个容器,或者说一种机制,它管理了一组route。简单来说,route只是进行了URL和函数的映射,而在当接收到一个URL之后,去路由映射表中查找相应的函数,这个过程是由router来处理的,如上面代码,Router管理传入的route,并且在hash改变的时候,根据当前的url响应其对应的函数。
方法二:基于History新API(history.pushState()+popState事件)
HTML5中history对象上新的API,同样能实现前端的路由。通过pushState()方法或replaceState()方法可以修改url的地址,并在popstate事件中能监听地址的改变,不同的是,手动的进行pushState()并不会触发popstate事件。
先认识下两个新增的API:history.pushState和 history.replaceState,这两个API都接收三个参数:
window.history.pushState(null, null, “http://www.163.com“);
状态对象(state object),一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,会触发popstate事件,并能在事件中使用该对象。
标题(title) :传入一个短标题给当前state。现在大多数浏览器不支持或者会忽略此参数,最好传入null代替;
地址(URL):新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
这两个API的相同之处是都会操作浏览器的历史记录,而不会引起页面的刷新。不同之处在于,pushState会增加一条新的历史记录,而replaceState则会替换当前的历史记录。这两个api,加上state改变触发的popstate事件,提供了单页应该的另一种路由方式。
当我们在历史记录中切换时就会触发 popstate 事件,可以在事件中还原当前state对应的UI。对于触发popstate 事件的方式,各浏览器实现也有差异,我们可以根据不同浏览器做兼容处理。
两种方式对比,基于Hash的路由,兼容性更好;基于History API的路由,则更正式,可以设置与当前URL同源的任意URL,路径更直观。另外,基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造,配置不同的路由都返回相同的页面。
使用路由时出现问题如何解决
路由的匹配规则是按照书写的顺序执行的,第一条匹配成功则不去匹配下一条,利用这一特性,可以在所有匹配路由的下面拦截匹配所有路由:
//创建路由对象并配置路由规则
let router = new VueRouter({
routes:[
{path:'/',redirect:{name:"home"}}, // 重定向到主页
{name:'home',path:'/home',component:Home},
{name:'login',path:'/login',component:Login},
{path:'*',component:NotFound},//全不匹配的情况下,匹配NotFound组件,路由按顺序从上到下,依次匹配。最后一个*能匹配全部,
]
});
原理同方法2,只不过在匹配到*时,重定向到根路径:
//创建路由对象并配置路由规则
let router = new VueRouter({
routes:[
{path:'/',redirect:{name:"home"}}, // 重定向到主页
{name:'home',path:'/home',component:Home},
{name:'login',path:'/login',component:Login},
{path:'*',redirect:'/'},//路由按顺序从上到下,依次匹配。最后一个*能匹配全部,然后重定向到主页面
]
});
history 路由和 hash 路由的区别, 在浏览器有什么影响;
hash前端路由,无刷新
history 会去请求接口
vue-router提供两种模式的原因:
vue 是渐进式前端开发框架,为了实现 SPA ,需要引入前端路由系统(vue-router)。前端路由的核心是:改变视图的同时不会向后端发出请求。
为了达到这一目的,浏览器提供了 hash 和 history 两种模式。
- hash :hash 虽然出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
- history :history 利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。
因此可以说, hash 模式和 history 模式都属于浏览器自身的属性,vue-router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现路由。
实现的原理
- hash 模式的原理是 onhashchange 事件,可以在 window 对象上监听这个事件。
- history :hashchange 只能改变 # 后面的代码片段,history api (pushState、replaceState、go、back、forward) 则给了前端完全的自由,通过在window对象上监听popState()事件。
pushState()、replaceState() 方法接收三个参数:stateObj、title、url。 // 设置状态
history.pushState({color: "red"}, "red", "red");
// 监听状态window.onpopstate = function(event){
console.log(event.state);
if(event.state && event.state.color === "red"){
document.body.style.color = "red";
}} // 改变状态history.back();history.forward();复制代码
应用场景
通过 pushState 把页面的状态保存在 state 对象中,当页面的 url 再变回到这个 url 时,可以通过 event.state 取到这个 state 对象,从而可以对页面状态进行还原,如页面滚动条的位置、阅读进度、组件的开关等。
调用 history.pushState() 比使用 hash 存在的优势:
- pushState 设置的 url 可以是同源下的任意 url ;而 hash 只能修改 # 后面的部分,因此只能设置当前 url 同文档的 url
- pushState 设置的新的 url 可以与当前 url 一样,这样也会把记录添加到栈中;hash 设置的新值不能与原来的一样,一样的值不会触发动作将记录添加到栈中
- pushState 通过 stateObject 参数可以将任何数据类型添加到记录中;hash 只能添加短字符串
- pushState 可以设置额外的 title 属性供后续使用
劣势:
- history 在刷新页面时,如果服务器中没有相应的响应或资源,就会出现404。因此,如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面
- hash 模式下,仅 # 之前的内容包含在 http 请求中,对后端来说,即使没有对路由做到全面覆盖,也不会报 404
方案题:不同前端技术栈的项目,如何实现一套通用组件方案?
vuex, mobx, redux各自的特点和区别
vue-router(hash, HTML5 新增的 pushState)
单页应用,如何实现其路由功能—路由原理
写在前面:通常 SPA 中前端路由有2种实现方式:
- window.history
- location.hash
下面就来介绍下这两种方式具体怎么实现的
一.history
1.history基本介绍
window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:
- history.back() – 与在浏览器点击后退按钮相同
- history.forward() – 与在浏览器中点击按钮向前相同
- history.go(n) – 接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面
- 如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败
在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法可以用来向历史栈中添加数据,就好像 url 变化了一样(过去只有 url 变化历史栈才会变化),这样就可以很好的模拟浏览历史和前进后退了,现在的前端路由也是基于这个原理实现的。
2.history.pushState
pushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。
- stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
- title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
- url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
关于pushState,有几个值得注意的地方:
pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新
这里的 url 是受到同源策略限制的,防止恶意脚本模仿其他网站 url 用来欺骗用户,所以当违背同源策略时将会报错
3.history.replaceState
replaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其余和 pushState一模一样
4.popstate事件
定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
注意:仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。
二.Hash
1.Hash基本介绍
url 中可以带有一个 hash http://localhost:9000/#/a.html
window 对象中有一个事件是 onhashchange,以下几种情况都会触发这个事件:
- 直接更改浏览器地址,在最后面增加或改变#hash;
- 通过改变location.href或location.hash的值;
- 通过触发点击带锚点的链接;
- 浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同。
vue-router如何做用户登录权限等
router官网中进阶部分提供了导航守卫的功能
,可以通过设置全局前置守卫
或者路由独享守卫
来做用户登陆权限的判别。
你在项目中怎么实现路由的嵌套
需要在 VueRouter 的参数中使用 children 配置,这样就可以很好的实现路由嵌套。
vue怎么监听数组
在将数组处理成响应式数据后,如果使用数组原始方法改变数组时,数组值会发生变化,但是并不会触发数组的setter来通知所有依赖该数组的地方进行更新,为此,vue通过重写数组的某些方法来监听数组变化,重写后的方法中会手动触发通知
该数组的所有依赖进行更新
。
我们知道通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter
,这就会造成使用上述方法改变数组后,页面上并不能及时体现这些变化,也就是数组数据变化不是响应式的(对上述不了解的可以参考这篇文章)。但实际用vue开发时,对于响应式数组,使用push、splice、pop等方法改变数组时,页面会及时体现这种变化,那么vue中是如何实现的呢?通过vue源码可以看出,vue重写了数组的push、splice、pop等方法
因为监听的数组带来的代价和一些问题,Vue使用了重写原型的方案
代替。拦截了数组的一些方法,在这个过程中再去做通知变化等操作。
双向绑定的原理?数据劫持?
如何做到的双向绑定
原理
vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现
数据劫持
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果
典型的有
1.Object.defineProperty()
2.es6中Proxy对象
vue2.x使用Object.defineProperty();
vue3.x使用Proxy;
订阅发布模式
定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
订阅发布模式中事件统一由处理中心处理,订阅者发布者互不干扰。
优点:实现更多的控制,做权限处理,节流控制之类,例如:发布了很多消息,但是不是所有订阅者都要接收
// 实现一个处理中心
let event = {
clientList: {}, // 订阅事件列表
// 订阅
on(key, fn){
// 如果这个事件没有被订阅,那么创建一个列表用来存放事件
if(!this.clientList[key]) {
this.clientList[key] = []
}
// 将事件放入已有的事件列表中
this.clientList[key].push(fn);
},
// 发布
trigger(type, args){
let fns = this.clientList[type] // 拿到这个事件的所有监听
if(!fns || fns.length === 0){ // 如果没有这条消息的订阅者
return false
}
// 如果存在这个事件的订阅,那么遍历事件列表,触发对应监听
fns.forEach(fn => {
// 可以在此处添加过滤等处理
fn(args)
})
}
}
vue中如何实现
利用Object.defineProperty();把内部解耦为三部分
Observer: 递归的监听对象上的所有属性,当属性改变时触发对应的watcher
watcher(观察者):当蒋婷的数据值修改时,执行相应的回调函数,更新模板内容
dep:链接observer和watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher
proxy实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
代码中。对象person是观察目标,函数print是观察者。一旦数据发生变化,print就会自动执行
使用proxy实现一个最简单观察者模式,即实现observable和observe这两个函数。
思路是observable函数返回一个原始对象的proxy代理,拦截复制操作。触发充当观察者的各个函数
const queue = new Set();
const observe = fn => queue.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queue.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合,然后,observable函数返回原始对象的代理,拦截赋值操作。
拦截函数set中,自动执行所有观察者
谈一下你对 Vue 的认识,以及 Vue 底层实现的机制;
Vue 3.0有没有过了解
关于Vue 3.0有幸看过尤大的关于3.0版本的RFC Vue Function-based API RFC。 大致说了三个点,第一个是关于提出的新API setup()
函数,第二个说了对于Typescript的支持,最后说了关于替换Object.defineProperty为Proxy的支持。
详细说了下关于Proxy代替带来的性能上的提升,因为传统的原型链拦截的方法,无法检测对象及数组的一些更新操作,但使用Proxy又带来了浏览器兼容问题。
1、速度方面:
通过更新 runtime-core 与 runtime-dom ,支持了包括 Fragments、Portals与Suspense w/ async setup() 等(似乎有点越来越像 react),支持 Composition API 、Options API 和 typings,配合 Proxy 的引入,极大程度上提高了响应式的能力。
Composition API 与 Proxy 的运用使得组件化更加灵活,逻辑业务组件的编写与UI组件的多样化能够更好的实现。
2、体积方面:
runtime-core 体积压缩成了 约10kb
3、维护性:
代码迁移成 TypeScript (还没学的抓紧啦)
编译器(Compiler)优化,以下借用尤大大的特性更新图翻译一下:
使用模块化架构
优化 “Block tree”
更激进的 static tree hoisting 功能
支持 Source map
内置标识符前缀(又名 “stripWith”)
内置整齐打印功能
移除 source map 和标识符前缀功能后,使用 Brotli 压缩的浏览器版本精简了大约 10KB
可见,编译器更多从数据结构入手,优化架构层级,降低使用成本。
4、更接近原生开发
5、让你更轻松开发
其次是 GitHub 上的源码目录:
reactivity 目录:数据响应式系统,主要使用 Proxy。
runtime-core 目录:支持 v-dom 与 Vue 原生的各种 API 在浏览器上的调用,调用 Vue 特性的相关代码。
runtime-dom 目录: 调用各类浏览器原生 DOM 事件的相关代码。
runtime-test 目录: 测试用的runtime的相关代码。
server-renderer 目录: SSR 相关代码。
compiler-core 目录: 支持 Vue 原生编写的 编译器特性,以及开发者编写的编译器特性的相关代码。
compiler-dom 目录: 调用浏览器的编译器的相关代码。
shared 目录: 无内置API。
vue 目录: 实现 vue 构建与编译的相关代码。
Vue3 proxy解决了哪些问题?
Vue3 proxy的优劣
Vue响应式原理
在官方文档中,有简单介绍一点,是基于 Object.defineProperty
去实现的;但是有一个要求,就是在 实例化 Vue
构造函数前,所有熬进行双向绑定的数据,都是要在 data
里面去做初始化的,哪怕是一个空值;因为在每次实例化的时候,Vue
会去检测 data
,查看并把存在属性用 Object.defineProperty
进行监听;在每一个需要判断或者展示当前响应式监听的数据时,例如:
<div>{{name}}</div>
复制代码
new Vue({
data(){
return {
name:'zhangsan'
}
}
})
复制代码
初始化的时候,我绑定了一个 name
属性,它在一个 div
里面做了展示;当我在 div
里面添加 name
展示的时候,其实在模板编译的时候,获取了一下 name
属性;因为前面有提到,我给当前的属性绑定了 Object.defineProperty
,所以在获取的时候,我会调用到 get
方法;
在这之前,我有实例化一个 dep
队列,把每次获取 name
属性的地方,做一个 push
;
当我接下来要做数据修改的时候,比如把 zhangsan
变成了 lisi
,那么在 set
方法里,把我当前属性的队列有监听当前属性的位置,全部更新一遍;最后把 data
对象里面的属性值做修改;
注:这是一个面试的回答,但这个不够详细,如果想很详细的去了解,具体都做了什么,请跳转:Vue 源码解析(实例化前) – 响应式数据的实现原理
Proxy 相比于 defineProperty 的优势
Proxy的优势:
可以直接监听对象而非属性
- 可以直接监听数组的变化
- 拦截方式较多
- Proxy返回一个新对象,可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
- Proxy作为新标准将受到浏览器厂商重点持续的性能优化
Object.defineProperty的优势如下:
- 兼容性好,支持IE9
key的作用
vuex数据流动过程
1、vuex是什么
公共状态管理
解决多个非父子组件传值问题。
多个页面都能用Vuex中store公共的数据
a、并不是所有的数据都要放在Vuex中,只有各个组件公用的一些数据会放在Vuex当中
b、Vuex是一个公共状态管理模式 并不是数据库 所以不可能持久保存一些数据,当用户刷新浏览器的时候那么数据就有可能消失
c、Vuex主要应用在大型的单页面开发中
2、vuex的特点
1、遵循单向数据流
2、Vuex中的数据是响应式的
3、vuex的流程图:
image.png
总结下vuex数据传输流程
1、通过new Vuex.Store()创建一个仓库 state是公共的状态,state—>components渲染页面
2、在组件内部通过this.$store.state.属性 来调用公共状态中的state,进行页面的渲染。
3、当组件需要修改数据的时候,必须遵循单向数据流。组件里在methods中扩展mapActions,调用store里的actions里的方法。
4、actions中的每个方法都会接受一个对象 这个对象里面有一个commit方法,用来触发mutations里面的方法
5、mutations里面的方法用来修改state中的数据 mutations里面的方法都会接收到2个参数 一个是store中的state, 另外一个是需要传递到参数
6、当mutations中的方法执行完毕后state会发生改变,因为vuex的数据是响应式的 所以组件的状态也会发生改变
vuex的理解
vuex vue-router原理等
一、vue-router——路由管理
经过自己的学习加项目总结,针对路由这块内容说下自己的理解:
路由配置主要有三个关键字:route routes router
route(单数)表示一条路由,它是一个对象由两部分构成(path——路径;component——组件)
例如:homeBtn——homeContent(点击homeBtn按钮页面切换到homeContent组件);sideBtn——sideContent;这是两条路由(route)
如果路由多,这样一条一条表示显然不合理。
由此出现routes(复数)表示一组路由(路由的数组),上面的两条路由就可以这样表示:
image
具体写法:const routes = [{path:’/home’,component:homeContent},{path:’/sidebar’,component:sidebarContent}]
router表示路由机制(即路由的管理者),实现原理:点击homeBtn按钮——到routes里去查找到‘/home’对应得组件homeContent,并显示页面。
写法:const router = new VueRouter({routes:routes}),简写:const router = new VueRouter({routes})
最后将router实例注入到vue根实例中,便可以使用路由
配置好路由再定义好路由的出口router-view就可以使用路由实现页面跳转了。
ps:router-link相当于a标签。
二、vuex——数据状态管理
简单说下vuex的作用:主要用于组件直接通讯。比如A和B和C为兄弟组件,组件中都用到name这个字段,A组件中如果对name字段进行更改,那该如何通知B和C组件name字段已经发生变化了呢?这个时候就可以使用Vuex来进行通讯了。先将name存起来(state管理变量初始状态的),A组件中通过触发mutation来通知state中的name发生改变。那么B和C组件就可以从vuex获取state中的name。大概就是这么个意思。
另外说一下:vuex中的几个关键要素:state、mutation、action以及vuex中自带的函数:mapState、mapMutation、mapAction、mapGetter(都是个人的理解,如有不当请指出。)
state主要定义一些初始化的变量;
mutation定义修改State的方法,有两个参数(state、value)
action定义触发mutation行为的方法;语法:方法名(context)、通过context.commit来触发mutation的方法;
getter定义获取state的方法,参数state;
上面内容主要说如何定义这些变量,下面说下如果在组件中使用这些方法:
第一种情况:设置/修改变量的两种方式:
a、在计算属性中或方法中调用mutation方法:this.$store.commit(‘方法名’,参数值)
b、使用action触发mutation方法:this.$store.dispatch(mutaion的方法)
官方不建议使用a,建议使用b。
第二种情况:获取参数
两种方式:1、this.store.getter.方法名
使用this.$store.state等这些书写方式太长。所以可以通过vuex的辅助函数:mapState等来简化书写
辅助函数的使用方法:
1、mapGetters/apState放在computed属性中。
书写方式:a、…mapGetters([‘Getter定义的方法名’])
b、参数如果是count:…mapState({state=>state.count})
2、mapMutation和mapAction,一般放在method中写:…mapMutation([‘mutation定义的方法名’]
如何实现一个自定义组件,不同组件之间如何通信的?
https://blog.csdn.net/brave_haha/article/details/81000633
如何设计一个组件
http://www.fly63.com/article/detial/996
对无状态组件的理解,使用过程中遇到的问题,如何解决的
watch computed区别
1、methods,computed的区别
例子:
var vm = new Vue({
el: ‘#app’,
data: {
msg: ‘nihao’,
text: ‘byebye’
},
computed: {
getmsg: function () {
return this.msg
}
},
methods:{
gettext:function () {
return this.msg
}
}
})
<p>msg:{{getmsg}}</p>
<p>text:{{gettext()}}</p>
在这个例子中,只要msg值发生变化,getmsg方法就会触发,而text的值发生变化,只要没有调用gettext方法,显示的值不会动态改变。就是说,methods中就是普通的方法,通过调用执行,computed中的方法会监听自己的依赖,依赖发生变化方法会自动执行。
- computed 和methods 达到的效果是相同的
- 计算属性computed和methods都不应该使用箭头函数来定义计算属性 因为箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向Vue
不同点是: - 计算属性compute是基于他们的依赖(如果是实例范畴之外的依赖,比如非响应式的not reactive是不会触发属性更新的)进行缓存(计算属性的结果会被缓存),只有相关依赖会发生改变时才会重新求值,未改变只会返回只之前的结果,不在执行函数 .原值与新值一样不会触发,函数的返回值就是最终获取的值.
- computed是响应式的,methods并非响应式。
- 调用方式不一样,computed定义的成员像属性一样访问,methods定义的成员必须以函数形式调用。
- computed是带缓存的,只有其引用的响应式属性(属性绑定)发生改变时才会重新计算(如果引用的属性没有改变,则调用上一次缓存值),而methods里的函数在每次调用时都要执行
- computed中的成员可以只定义一个函数作为只读属性,也可以定义get/set变成可读写属性,这点是methods中的成员做不到的。 computed 计算属性的方法在用属性时不用加(),而methods 方法在使用时要像方法一样去用,必须必须要加(){{ mes() }}
默认加载的时候先computed再watch,不执行methods;等触发某一事件后,则是:先methods再watch。
watch适合处理的场景是,侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化(该数据影响别的多个数据)==当数据发生变化时,执行异步操作或较大开销操作的情况。如图所示:
image.png
computed适合处理的场景是,一个数据属性在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性。如图所示:
image.png
vue和react的区别
react fiber架构的理解
https://juejin.im/post/5c92f499f265da612647b754#heading-3
restful接口架构的优缺点?
RESTful架构优点:
- 前后端分离,减少流量
- 安全问题集中在接口上,由于接受json格式,防止了注入型等安全问题
- 前端无关化,后端只负责数据处理,前端表现方式可以是任何前端语言(android,ios,html5)
- 前端和后端人员更加专注于各自开发,只需接口文档便可完成前后端交互,无需过多相互了解
- 服务器性能优化:由于前端是静态页面,通过nginx便可获取,服务器主要压力放在了接口上
RESTful架构设计原则(不同公司具体细节可能不同):
- 在接口命名时应该用名词,不应该用动词,因为通过接口操作到是资源。
- 在url中加入版本号,利于版本迭代管理更加直观<pre style=”margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: “Courier New” !important; font-size: 12px !important;”>https://www.rgc.com/v1/</pre>
- 对于资源的操作类型应该是通过http动词表示。
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
- 排序规则:默认时升序,‘-’为降序;多个排序规则时以逗号间隔组合。使用sort查询参数限制<pre style=”margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: “Courier New” !important; font-size: 12px !important;”>GET /tickets?sort=-time,created_at
优先以time倒序显示,其次以created_at正序显示</pre> - 限制返回值的字段域:明确指定输出字段列表,用于控制网络带宽和速度。使用fields查询参数来限制。<pre style=”margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: “Courier New” !important; font-size: 12px !important;”>GET /tickets?fileds=id,subject,customer_name,time&sort=-time
返回参数列表为id,subject,customer_name,time,并且以time字段倒序显</pre> - HTTP Method分别对于资源的CURD操作
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。</pre>保证 POST,PUT,DELETE,PATCH,GET 操作幂等性。 - 使用SSL(Secure Sockets Layer 安全套接层)
- 参数和url采用蛇行命名方式。如:updated_time
- 服务器请求和返回的数据格式,应该尽量使用JSON,避免使用XML。在 request中的Accept和Response中的Content-Type:application/json
image.png
总结:优秀的RESTful接口设计,能够根据请求的路径及请求方法就能看出这个接口主要是对具体某个资源进行什么方法的操作以及返回数据的规则等等。
restful接口架构会导致什么安全问题,具体怎么解决?
https://www.jianshu.com/p/b5966899c435
重新渲染render会做些什么
JS是什么范式语言(面向对象还是函数式编程)
js 是更倾向于函数式编程,js是弱类型语言,也可以叫解释型语言,开始创建js时倾向于函数式编程。随着js的发展,加入了面向对象的东西,但它是函数式编程。
https://www.cnblogs.com/Darlietoothpaste/p/10633550.html
资深前端
会vue基础使用不值钱
组件化 + element-ui源码
做一些更高端和通用的事情,组件化,框架,框架源码里,有大量的最佳实践。vue源码里,大量的工程化,设计模式,代码规范的最佳实践。除了经验丰富外,视野更高,解决诡异的bug,架构的设计