Vue篇
39.Vue最⼤的特点(核⼼)
1.组件化:就是可以将页面和页面中可复用的元素都看做成组件,写页面的过程,就是写组件,然后页面是由这些组件“拼接“起来的组件树
2.数据驱动:就是让我们只关注数据层,只要数据变化,页面(即视图层)会自动更新,至于如何操作 dom,完全交由 vue 去完成,咱们只关注数据,数据变了,页面自动同步变化了,很方便
40.Vue和JQuery对⽐
JQuery:玩 dom 操作的神器,强大的选择器,分装了好多好用的 dom 操作方法 和 ajax方法
Vue:主要是数据驱动和组件化,很少操作dom(可以用ref)
41.Vue常⻅指令
v-if
:根据表达式的值的真假条件渲染元素。在切换时元素及它的数据绑定/组件被销毁并重建。
v-show
:根据表达式之真假值,切换元素的 display CSS 属性。
v-for
:循环指令,基于一个数组或者对象渲染一个列表,vue 2.0 以上必须需配合 key 值使用。
v-bind
:动态地绑定一个或多个特性/属性,或一个组件 prop 到表达式。
v-on
:用于监听指定元素的 DOM 事件,比如点击事件。绑定事件监听器。
v-model
:实现表单输入和应用状态之间的双向绑定
v-pre
:跳过这个元素和它子元素的编译过程。用来显示原始Mustache标签。跳过大量没有指令的节点会加快编译。
v-once
:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
42.v-if和v-for优先级问题
- 如果同时出现,每次渲染都会先执行循环在判断条件,无法避免循环,浪费了性能
- 在外层嵌套
`,在这一层进行
v-if然后在内部进行
v-for`循环 - 如果条件出现在循环内部,可通过计算属性提前过滤不需要显示的项
43.Vue⾃定义指令实现
1.全局自定义指令:Vue.directive(‘指令名’, { inserted(el) { } })
2.局部自定义指令:directives:{ }
44.Vue修饰符的⽤途
修饰符用途:
- 通过自定义属性存储变量,避免暴露数据
- 防治污染 HTML 结构
45.Vue常⽤修饰符(v-onv-bindv-model)
v-on 指令常用修饰符:<@>
.stop
- 调用 event.stopPropagation(),禁止事件冒泡。
.prevent
- 调用 event.preventDefault(),阻止事件默认行为。
.capture
- 添加事件侦听器时使用 capture 模式。
.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调。
.native
- 监听组件根元素的原生事件。
.once
- 只触发一次回调。
v-bind 指令常用修饰符:<:>
.prop
- 被用于绑定 DOM 属性 (property)。(差别在哪里?看46)
.camel
- (2.1.0+) 将 kebab-case 特性名转换为 camelCase. (从 2.1.0 开始支持)
.sync (2.3.0+)
语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器。
v-model 指令常用修饰符:
.lazy
- 取代 input 监听 change 事件
.number
- 输入字符串转为数字
.trim
- 输入首尾空格过滤
46.v-bind有⽆.prop的差别
v-bind
默认绑定到 DOM 节点的 attribute 上。使用 .prop
修饰后,会绑定到 proterty
使用 property 获取最新的值 attribute 设置的自定义属性会在渲染后的 HTML 标签显示,property 不会
无 .prop
,``
标签结构:``
input.data === undefined
input.attributes.data === this.inputData
有 .prop
,``
标签结构:``
input.data === this.inputData
input.attributes.data === undefined
47.Vue中methods、computed和watch的区别
Methods
: 中都是封装好的函数,无论是否有变化只要触发就会执行Computed
: 是vue独有的特性计算属性,可以对data中的依赖项再重新计算, 得到一个新值,应用到视图中,和 methods 本质区别是 computed 是可缓存的, 也就是说 computed 中的依赖项没有变化,则 computed 中的值就不会重新计算, 而 methods 中的函数是没有缓存的。Watch
: 是监听 data 和计算属性中的新旧变化。 computed 和 watch 都是观察页面的数据变化的。数据变化时执行异步操作或开销较大的操作,这个时候使用 watch 是合适的
48.Vue单项数据流
单向数据流主要是 vue 组件间传递数据是单向的,数据总是从父组件传递给子组件,子组件在其内部维护自己的数据,但它没有权限修改父组件传递给它的数据,当尝试这样做的时候vue 将会报错。这样做是为了组件间更好的维护。
在开发中可能有多个子组件依赖于父组件的某个数据,假如子组件可以修改父组件数据的话,一个子组件变化会引发所有依赖这个数据的子组件发生变化,所以vue 不推荐子组件修改父组件的数据
49.Vue路由(前端路由的实现原理)
第一种:利用 H5 的 history API 实现:主要通过 history.pushState
和 history.replaceState
来实现,不同之处在于,pushState 会增加一条新的历史记录,而 replaceState则会替换当前的历史记录[发布项目时,需要配置下 apache]
this.$router.push(location, onComplete?, onAbort?)` 点击 `` 等同于调用 `router.push(...)
this.$router.replace(...)` 点击 `` 等同于调用 `router.replace(...)
第二种:利用 url 的 hash(#)实现:主要利用监听哈希值的变化来触发事件hashchange 事件来做页面局部更新
50.Vue路由守卫(钩⼦)和路由中间件
Vue 路由守卫(钩子):跳转过程中拦截当前路由和要跳转路由的信息,处理页面访问权限
全局:beforeEach (to, from, next)
组件内:beforeRouteEnter(to, from, next)
在同构渲染下,页面的拦截从服务端角度是在进入页面前就需要处理页面访问的,可以使用 路由中间件 处理,它允许定义一个自定义函数运行在一个页面或一组页面渲染前。既处理服务端渲染的路由拦截又处理客户端渲染的拦截
/**
* 验证是否登录的中间件
*/
// 每一个中间件应放置在 middleware 目录,文件名的名称即为中间件名
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.user) {
return redirect('/login')
}
}
// 使用
export default {
...
// 在路由匹配组件渲染之前会先执行中间件处理
middleware: ['authenticated']
}
51.Vue过滤器(0,1->男⼥)
跟创建自定义指令类似,也有全局和局部过滤器的形式
全局过滤器:Vue.filter(‘过滤器名’,function(参数 1,参数 2,…) { ... return 要返回的数据格式 })
局部过滤器:在组件内部添加 filters 属性来定义过滤器
fitlers:{ 过滤器名(参数 1,参数 2,,…参数 n) { ... return 要返回的数据格式 } }
52.Vue组件通讯(传值)的四种形式及实现
第一种:父传子:主要通过 props 来实现
父组件通过 import 引入子组件,并注册( components:{} ),在子组件标签上添加要传递的属性,子组件通过 props 接收,接收有两种形式: 一是通过数组形式[‘要接收的属性’ ],二是通过对象形式{ }来接收,对象形式可以设置要传递的数据类型和默认值,而数组只是简单的接收
第二种:子传父:主要通过 $emit 来实现
子组件通过通过绑定事件触发函数,在其中设置this.$emit(‘要派发的自定义事件’,要传递的值),然后父组件中,在这个子组件身上@(v-on)派发的自定义事件,绑定事件触发的methods 中的方法接受的默认值,就是传递过来的参数,或者直接使用 @event 接收参数
第三种:兄弟之间传值有两种方法:
方法一:通过 event bus 实现
创建一个空的 vue 作为事件总线/事件中心 并暴露出去,这个作为公共的 bus,即当作两个组件的桥梁,在两个兄弟组件中分别引入刚才创建的 bus,在组件 A 中通过bus.$emit(’自定义事件名’,要发送的值)发送数据,在组件 B 中通过 bus.$on(‘自定义事件名‘,function(v) { //v 即为要接收的值 })接收数据
方法二:通过 vuex 实现
vuex 是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括 state,actions,mutations,getters 和 modules 5 个要素,主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态.而 getters 相当于组件的计算属性对,组件中获取到的数据做提前处理的.再说到辅助函数的作用.
第四种:通过 ref 获取子组件
把它作用到普通 HTML 标签上,则获取到的是 DOM 对象
把它作用到组件标签上,则获取到的是组件实例对象
在使用子组件的时候,添加 ref 属性,然后在父组件等渲染完毕后使用 $refs 访问($refs 只会在组件渲染完成之后生效,并且它们不是响应式的)
53.如何解决Vue⾸屏加载慢或⽩屏(性能优化)
- 合理使用
v-if
和v-show
。v-for
遍历为 item 添加key
,避免同时使用v-if
- 区分 computed 和 watch 的使用
addEventListner
添加的事件在组件销毁时用removeEventListner
移除监听- 路由懒加载
const test = () => import(‘@/...’))
- 开启 Gzip 压缩
config index.js module.exports = {...:{productionGzip: false}}
- 使用 webpack 的 externals 属性把不需要打包的库文件分离出去,减少打包后文件的大小,优化 Source Map
- 使用 vue 的服务端渲染(SSR),首屏快,SEO好
- 网络加载方面的优化:减少http请求(合并文件、CSS雪碧图、图片路由 懒加载),减少dom操作(dom 缓存),代码封装
- 第三方库用 cdn 加载(OSS优化),按需引入
54.聊聊SSR服务端渲染
一份代码,服务端先通过 server-side-rendering 生成 html 以及初始化数据(客户端SPA),客户端拿到代码后,通过对 html 的 dom 进行 path 和事件绑定对 dom 进行客户端激活。接管服务端渲染的内容把它激活为一个动态页面
- 客户端发起请求
- 服务端渲染首屏内容 + 生成客户端 SPA 相关资源
- 服务端将生成的首屏资源发送给客户端
- 客户端直接展示服务端渲染好的首屏内容
- 首屏中的 SPA 相关资源执行之后会激活客户端 Vue
- 之后客户端所有的交互都由客户端 SPA 处理
55.Vue响应式(双向数据绑定)原理(发布订阅模式)
Vue.js 2.x 中响应式系统的核心 defineProperty
初始化时遍历 data 中的所有成员,通过 defineProperty 把对象的属性转换成 getter 和 setter,如果 data 中的属性又是对象的话,需要递归处理每一个子对象的属性。这些都是初始化时进行的,如果你、未使用这些属性也会进行响应式的处理
Vue.js 3.x 中使用 Proxy 对象重写响应式系统
1.Proxy 的性能本身就比 defineProperty 好,且代理对象可以拦截属性的访问、赋值、删除等操作,不需要初始化时遍历所有的属性,如果有多层属性嵌套只有访问某个属性时才会递归处理下一级属性
2.使用 Proxy 对象默认可以监听动态新增的属性,而 Vue.js 2.x 想要动态添加响应式属性需要调用 Vue.set 方法来处理
3.Vue.js 2.x 监听不到属性的删除
4.Vue.js 2.x 对数组的索引和 length 属性也监听不到
56.观察者模式和发布订阅模式
Vue 响应式原理通过数据劫持结合 发布订阅者模式来实现
观察者模式:
由具体目标调度:比如当事件触发,Dep就会调用观察者的方法(订阅者和发布者之间是存在依赖的)
发布订阅模式:
由统一调度中心调用,发布者和订阅者不需要知道对方的存在
在 new Vue的时候:在Observer中通过Object.defineProperty()
达到数据劫持,代理所有数据的getter
和setter
属性,在每次触发setter的时候会通过Dep来通知Watcher,Watcher作为Observer数据监听器与Compiler模板解析器之间的桥梁。当Observer监听到数据发生改变时,通过Updater来通知Compiler更新视图,而Compiler通过Watcher订阅对应数据,绑定更新函数,通过Dep来添加订阅者
57.Vue如何动态添加属性,实现数据相应
当创建好 Vue 实例后,新增一个成员,此时 data 并没有定义该成员,data 中的成员是在创建 Vue 对象的时候 new Observer 来将其设置成响应式数据,当 Vue 实例化完成之后,再添加一个成员,此时仅仅是给 vm 上增加了一个js属性而已,因此并不是响应式的
可以使用 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式属性。还可以使用vm.$set实例方法,这也是全局Vue.set方法的别名。
原理:defineReactive(ob.value, key, val) 给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染。ob.dep.notify() 触发当前的依赖(这里的依赖依然可以理解成渲染函数),所以页面就会进行重新渲染
58.虚拟DOM中dif算法
Vue 2.x 内部使用的 Virtual DOM 就是改造的 Snabbdom
虚拟DOM是如何实现的:虚拟DOM是通过js语法来在内存中维护一个通过数据结构描述出来的一个模拟DOM树,当数据发生改变的时候,会先对虚拟DOM进行模拟修改,然后再通过新的虚拟DOM树与旧的虚拟DOM树来对比,而这个对比就是通过diff算法来进行的.虚拟DOM最大的意义不在于性能的提升(JavaScript对象比DOM对象性能高),而在于抽象了DOM的具体实现(对DOM进行了一层抽象)
- 首先对根元素进行对比,如果根元素发生改变就直接对根元素替换
- 如果根元素没有发生改变的话,再对下一层元素进行对比,如果对比发现元素发生删除,就执行删除,发现元素被替换就执行替换,发现添加了新的元素就执行添加
- 对比的同时,会通过key值来判断元素是否发生改变,判断元素是仅仅位置发生改变还是需要整个替换或删除
- 如果不是元素发生改变的话,再对内容进行对比,如果是内容发生改变的话,就直接修改内容
- 其实就是进行逐层对比,再通过不同的对比来判断执行不同的操作
虚拟DOM最大的意义不在于性能的提升(JavaScript对象比DOM对象性能高),而在于抽象了DOM的具体实现(对DOM进行了一层抽象)
59.虚拟DOM中key的作⽤和好处
作用:追踪列表中哪些元素被添加、被修改、被移除的辅助标志。可以快速对比两个虚拟DOM对象,找到虚拟DOM对象被修改的元素,然后仅仅替换掉被修改的元素,然后再生成新的真实DOM
好处:可以优化 DOM 的操作,减少Diff算法和渲染所需要的时间,提升性能
Vue Vuex篇
60.异步更新队列nextTick的作⽤是使⽤场景
Vue 更新 DOM 是异步执行的、批量的,nextTick 就是在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
例如:有一个 div,默认用 v-if 将它隐藏,点击一个按钮后,改变 v-if 的值,让它显示出来,同时拿到这个 div 的文本内容。如果 v-if 的值是 false,直接去获取 div 内容是获取不到的,因为此时 div 还没有被创建出来,那么应该在点击按钮后,改变 v-if 的值为 true,div 才会被创建,此时再去获取,
Vue 在观察到数据变化时并不是直接更新 DOM,而是开启一个队列,并缓冲在同一个事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和 DOM 操作。然后,在下一个事件循环 tick 中,Vue 刷新队列并执行实际(已去重的)工作。Vue 会根据当前浏览器环境优先使用原生的 Promise.then
和 MutationObserver
,如果都不支持,就会采用 setImmediate
(宏任务队列) setTimeout
代替
61.Vue中keep-alive组件的作⽤(避免重新渲染)及使用场景
主要用于保留组件状态或避免重新渲染。属性:
include
:字符串或正则表达式。只有匹配的组件会被缓存。
exclude
:字符串或正则表达式。任何匹配的组件都不会被缓存。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。当组件在 ` 内被切换,在 2.2.0 及其更高版本中,
activated和
deactivated`生命周期 将会在 树内的所有嵌套组件中触发
场景:
- Vue中前进刷新,后退缓存用户浏览数据
- 列表页面 =>点击进入详情页=> 后退到列表页 要缓存列表原来数据
- 重新进入列表页面 => 获取最新的数据
62.Vuex如何实现数据持久化
因为 vuex 中的 state 是存储在内存中的,一刷新就没了,例如登录状态,解决方案有:
第一种:利用 H5 的本地存储:localStorage
, sessionStorage
,不会自动把数据发送给服务器
第二种:利用第三方封装好的插件,例如:vuex-persistedstate
第三种:使用 js-cookie
cookieparse
(将Cookie字符串解析为一个对象) 插件来做存储
如果是现代化服务端渲染,不能放到本地存储,因为本地存储只有客户端可以获取到,我们希望数据既能被客户端获取又能被服务端获取。因此我们将数据放到cookie,这样前后端都能获取。
客户端加载 js-cookie 包
// process.client 是Nuxt中特殊提供的数据,运行在客户端为 true; 运行在服务端为 false
const Cookie = process.client ? require('js-cookie') : undefined
// 为了防止刷新页面数据丢失,需要将数据持久化
Cookie.set('user', data.user)
// 服务端渲染期间 nuxtServerInit 是一个特殊的 action 方法
// commit:用来提交 mutation 的 commit 方法; req:服务端渲染期间的 request 请求对象
nuxtServerInit ({ commit }, { req }){
const parsed = cookieparser.parse(req.headers.cookie)
user = JSON.parse(parsed.user) // 转为对象
commit('setUser', user)
}
第四种:可以把数据传递到后台,存储到数据库中,比较耗费资源
63.Cookie、localStorage、sessionStorage
Cookie | localStorage | sessionStorage | |
---|---|---|---|
数据生命周期 | 一般由服务器生成,可设置失效时间。如果在浏览器生成,默认关闭后失效 | 除非被消除,永久保存 | 仅在当前会话下有小,关闭页面失效 |
大小 | 4KB | 5MB | 5MB |
cookie 最开始被设计出来并不是来做本地存储的。而是为了弥补HHTP请求在状态管理上的不足。HTTP为无状态协议,客户端向服务端发请求,服务器返回响应,下次发请求让服务端知道客户端是谁
cookie 是网站为了标识用户身份而存储在用户本地终端上的数据(通常加密),cookie始终在同源的http请求中携带(即使不需要)
64.Vue中的http请求如何管理
vue 中的 http 请求如果散落在 vue 各种组件中,不便于后期维护与管理,所以项目中通常将业务需求统一存放在一个目录下管理,这里面放入组件中用到的所有封装好的 http 请求并导出,再其他用到的组件中导入调用
export function xxx() {
return request({
url: '/api',
method: 'GET'
})
}
65.Axios拦截器(封装请求模块、对公共数据做操作)
axios 拦截器可以让我们在项目中对后端 http 请求和响应自动拦截处理,减少请求和响应的代码量,提升开发效率同时也方便项目后期维护
/**
* 基于 axios 封装的请求模块
*/
const request = axios.create({
baseURL: 'https://conduit.productionready.io/'
})
export default request
66.Vue 跨域相关问题
不同协议、不同域名、不同端口都会造成跨域。由于开发服务器的缘故,我们将应用运行在 loaclhost
的一个端口上面,而最终上线过后,一般又和 API 部署到同源地址下面
这就会产生一个非常见问题:实际生产当中可以直接访问API,但是回到开发环境就会产生跨域请求问题
我最推荐的⽅式就是: CORS 全称为 Cross Origin Resource Sharing(跨域资源共享)。这种⽅案对于前端来说没有什么⼯作量,和正常发送请求写法上没有任何区别,⼯作量基本都在后端这⾥。每⼀次请求,浏览器必须先以 options 请求⽅式发送⼀个预请求(也不是所有请求都会发送 options),通过预检请求从⽽获知服务器端对跨源请求⽀持的 HTTP ⽅法。在确认服务器允许该跨源请求的情况下,再以实际的 HTTP 请求⽅法发送那个真正的请求。推荐的原因是:只要第⼀次配好了,之后不管有多少接⼝和项⽬复⽤就可以了,⼀劳永逸的解决了跨域问题,⽽且不管是开发环境还是正式环境都能⽅便的使⽤
但总有后端觉得麻烦不想这么搞,那纯前端也是有解决⽅案的:
在 dev
开发模式下可以下使⽤ webpack 的 proxy
,但这种⽅法在⽣产环境是不能使⽤的,在⽣产环境中需要使⽤ nginx
进⾏反向代理。不管是 proxy
还是 nginx
的原理都是⼀样的,通过搭建⼀个中转服务器来转发请求规避跨域的问题
如果只针对vue本身可以通过代理的方式可以实现:在 config中的index.js中配置prox
module.exports = {
...
devServer: {
proxy: {
'/boss': {
target: 'http://eduboss.lagou.com',
// changeOrigin: true 以实际代理请求的主机名请求
// 设置请求头中的 host 为 target,防⽌后端反向代理服务器⽆法识别
changeOrigin: true
}
}
}
}