共计 22804 个字符,预计需要花费 58 分钟才能阅读完成。
组件中写 name 属性的益处
能够标识组件的具体名称不便调试和查找对应属性
// 源码地位 src/core/global-api/extend.js
// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人 -> jsx
}
Vuex 中 actions 和 mutations 有什么区别
题目剖析
mutations
和actions
是vuex
带来的两个独特的概念。老手程序员容易混同,所以面试官喜爱问。- 咱们只需记住批改状态只能是
mutations
,actions
只能通过提交mutation
批改状态即可
答复范例
- 更改
Vuex
的store
中的状态的惟一办法是提交mutation
,mutation
十分相似于事件:每个mutation
都有一个字符串的类型 (type
)和一个 回调函数 (handler
)。Action
相似于mutation
,不同在于:Action
能够蕴含任意异步操作,但它不能批改状态,须要提交mutation
能力变更状态 - 开发时,蕴含异步操作或者简单业务组合时应用
action
;须要间接批改状态则提交mutation
。但因为dispatch
和commit
是两个API
,容易引起混同,实际中也会采纳对立应用dispatch action
的形式。调用dispatch
和commit
两个API
时简直齐全一样,然而定义两者时却不甚雷同,mutation
的回调函数接管参数是state
对象。action
则是与Store
实例具备雷同办法和属性的上下文context
对象,因而个别会解构它为{commit, dispatch, state}
,从而不便编码。另外dispatch
会返回Promise
实例便于解决外部异步后果 - 实现上
commit(type)
办法相当于调用options.mutations[type](state)
;dispatch(type)
办法相当于调用options.actions[type](store)
,这样就很容易了解两者应用上的不同了
实现
咱们能够像上面这样简略实现 commit
和dispatch
,从而分别两者不同
class Store {constructor(options) {this.state = reactive(options.state)
this.options = options
}
commit(type, payload) {
// 传入上下文和参数 1 都是 state 对象
this.options.mutations[type].call(this.state, this.state, payload)
}
dispatch(type, payload) {
// 传入上下文和参数 1 都是 store 自身
this.options.actions[type].call(this, this, payload)
}
}
异步组件是什么?应用场景有哪些?
剖析
因为异步路由的存在,咱们应用异步组件的次数比拟少,因而还是有必要两者的不同。
体验
大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们
import {defineAsyncComponent} from 'vue'
// defineAsyncComponent 定义异步组件,返回一个包装组件。包装组件依据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
// 加载函数返回 Promise
return new Promise((resolve, reject) => {
// ... 能够从服务器加载组件
resolve(/* loaded component */)
})
})
// 借助打包工具实现 ES 模块动静导入
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
答复范例
- 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
- 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
- 应用异步组件最简略的形式是间接给
defineAsyncComponent
指定一个loader
函数,联合 ES 模块动静导入函数import
能够疾速实现。咱们甚至能够指定loadingComponent
和errorComponent
选项从而给用户一个很好的加载反馈。另外Vue3
中还能够联合Suspense
组件应用异步组件。 - 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是
vue
框架,解决路由组件加载的是vue-router
。然而能够在懒加载的路由组件中应用异步组件
Vue 路由的钩子函数
首页能够管制导航跳转,
beforeEach
,afterEach
等,个别用于页面title
的批改。一些须要登录能力调整页面的重定向性能。
beforeEach
次要有 3 个参数to
,from
,next
。to
:route
行将进入的指标路由对象。from
:route
以后导航正要来到的路由。next
:function
肯定要调用该办法resolve
这个钩子。执行成果依赖 next
办法的调用参数。能够管制网页的跳转
参考:前端 vue 面试题具体解答
Vue3 的设计指标是什么?做了哪些优化
1、设计指标
不以解决理论业务痛点的更新都是耍流氓,上面咱们来列举一下 Vue3
之前咱们或者会面临的问题
- 随着性能的增长,简单组件的代码变得越来越难以保护
- 短少一种比拟「洁净」的在多个组件之间提取和复用逻辑的机制
- 类型推断不够敌对
bundle
的工夫太久了
而 Vue3
通过长达两三年工夫的筹备,做了哪些事件?
咱们从后果反推
- 更小
- 更快
- TypeScript 反对
- API 设计一致性
- 进步本身可维护性
- 凋谢更多底层性能
一句话概述,就是更小更快更敌对了
更小
Vue3
移除一些不罕用的API
- 引入
tree-shaking
,能够将无用模块“剪辑”,仅打包须要的,使打包的整体体积变小了
更快
次要体现在编译方面:
diff
算法优化- 动态晋升
- 事件监听缓存
SSR
优化
更敌对
vue3
在兼顾 vue2
的options API
的同时还推出了composition API
,大大增加了代码的逻辑组织和代码复用能力
这里代码简略演示下:
存在一个获取鼠标地位的函数
import {toRefs, reactive} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
const update = e=>{
state.x = e.pageX;
state.y = e.pageY;
}
onMounted(()=>{window.addEventListener('mousemove',update);
})
onUnmounted(()=>{window.removeEventListener('mousemove',update);
})
return toRefs(state);
}
咱们只须要调用这个函数,即可获取 x
、y
的坐标,齐全不必关注实现过程
试想一下,如果很多相似的第三方库,咱们只须要调用即可,不用关注实现过程,开发效率大大提高
同时,VUE3
是基于 typescipt
编写的,能够享受到主动的类型定义提醒
2、优化计划
vue3
从很多层面都做了优化,能够分成三个方面:
- 源码
- 性能
- 语法 API
源码
源码能够从两个层面开展:
- 源码治理
- TypeScript
源码治理
vue3
整个源码是通过 monorepo
的形式保护的,依据性能将不同的模块拆分到 packages
目录上面不同的子目录中
这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易浏览、了解和更改所有模块源码,进步代码的可维护性
另外一些 package
(比方 reactivity
响应式库)是能够独立于 Vue
应用的,这样用户如果只想应用 Vue3
的响应式能力,能够独自依赖这个响应式库而不必去依赖整个 Vue
TypeScript
Vue3
是基于 typeScript
编写的,提供了更好的类型查看,能反对简单的类型推导
性能
vue3
是从什么哪些方面对性能进行进一步优化呢?
- 体积优化
- 编译优化
- 数据劫持优化
这里讲述数据劫持:
在 vue2
中,数据劫持是通过Object.defineProperty
,这个 API 有一些缺点,并不能检测对象属性的增加和删除
Object.defineProperty(data, 'a',{get(){// track},
set(){// trigger}
})
只管 Vue
为了解决这个问题提供了 set
和 delete
实例办法,然而对于用户来说,还是减少了肯定的心智累赘
同时在面对嵌套层级比拟深的状况下,就存在性能问题
default {
data: {
a: {
b: {
c: {d: 1}
}
}
}
}
相比之下,vue3
是通过 proxy
监听整个对象,那么对于删除还是监听当然也能监听到
同时Proxy
并不能监听到外部深层次的对象变动,而 Vue3
的解决形式是在getter
中去递归响应式,这样的益处是真正拜访到的外部对象才会变成响应式,而不是无脑递归
语法 API
这里当然说的就是composition API
,其两大显著的优化:
- 优化逻辑组织
- 优化逻辑复用
逻辑组织
一张图,咱们能够很直观地感触到 Composition API
在逻辑组织方面的劣势
雷同性能的代码编写在一块,而不像 options API
那样,各个性能的代码混成一块
逻辑复用
在 vue2
中,咱们是通过 mixin
实现性能混合,如果多个 mixin
混合,会存在两个非常明显的问题:命名抵触和数据起源不清晰
而通过 composition
这种模式,能够将一些复用的代码抽离进去作为一个函数,只有的应用的中央间接进行调用即可
同样是上文的获取鼠标地位的例子
import {toRefs, reactive, onUnmounted, onMounted} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
const update = e=>{
state.x = e.pageX;
state.y = e.pageY;
}
onMounted(()=>{window.addEventListener('mousemove',update);
})
onUnmounted(()=>{window.removeEventListener('mousemove',update);
})
return toRefs(state);
}
组件应用
import useMousePosition from './mouse'
export default {setup() {const { x, y} = useMousePosition()
return {x, y}
}
}
能够看到,整个数据起源清晰了,即便去编写更多的 hook
函数,也不会呈现命名抵触的问题
v-if 和 v -show 区别
v-show
暗藏则是为该元素增加css--display:none
,dom
元素仍旧还在。v-if
显示暗藏是将dom
元素整个增加或删除- 编译过程:
v-if
切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show
只是简略的基于css
切换 - 编译条件:
v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染 v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
办法- 性能耗费:
v-if
有更高的切换耗费;v-show
有更高的初始渲染耗费
v-show 与 v -if 的应用场景
v-if
与v-show
都能管制dom
元素在页面的显示v-if
相比v-show
开销更大的(间接操作dom 节
点减少与删除)- 如果须要十分频繁地切换,则应用 v-show 较好
- 如果在运行时条件很少扭转,则应用
v-if
较好
v-show 与 v -if 原理剖析
v-show
原理
不论初始条件是什么,元素总是会被渲染
咱们看一下在 vue 中是如何实现的
代码很好了解,有 transition
就执行 transition
,没有就间接设置display
属性
// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {transition.beforeEnter(el)
} else {setDisplay(el, value)
}
},
mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
}
},
updated(el, { value, oldValue}, {transition}) {// ...},
beforeUnmount(el, { value}) {setDisplay(el, value)
}
}
v-if
原理
v-if
在实现上比 v-show
要简单的多,因为还有else
else-if
等条件须要解决,这里咱们也只摘抄源码中解决 v-if
的一小部分
返回一个 node
节点,render
函数通过表达式的值来决定是否生成DOM
// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
(node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// ...
return () => {if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
Vue 的事件绑定原理
原生事件绑定是通过
addEventListener
绑定给实在元素的,组件事件绑定是通过Vue
自定义的$on
实现的。如果要在组件上应用原生事件,须要加.native
修饰符,这样就相当于在父组件中把子组件当做一般html
标签,而后加上原生事件。
$on
、$emit
是基于公布订阅模式的,保护一个事件核心,on
的时候将事件按名称存在事件中心里,称之为订阅者,而后 emit
将对应的事件进行公布,去执行事件中心里的对应的监听器
EventEmitter(公布订阅模式 – 简略版)
// 手写公布订阅模式 EventEmitter
class EventEmitter {constructor() {this.events = {};
}
// 实现订阅
on(type, callBack) {if (!this.events) this.events = Object.create(null);
if (!this.events[type]) {this.events[type] = [callBack];
} else {this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {if (!this.events[type]) return;
this.events[type] = this.events[type].filter(item => {return item !== callBack;});
}
// 只执行一次订阅事件
once(type, callBack) {function fn() {callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
}
}
// 应用如下
const event = new EventEmitter();
const handle = (...rest) => {console.log(rest);
};
event.on("click", handle);
event.emit("click", 1, 2, 3, 4);
event.off("click", handle);
event.emit("click", 1, 2);
event.once("dbClick", () => {console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");
源码剖析
- 原生 dom 的绑定
Vue
在创立真是dom
时会调用createElm
, 默认会调用invokeCreateHooks
- 会遍历以后平台下绝对的属性解决代码, 其中就有
updateDOMListeners
办法, 外部会传入add
办法
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
target = vnode.elm normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
function add (name: string, handler: Function, capture: boolean, passive: boolean) {
target.addEventListener( // 给以后的 dom 增加事件
name,
handler,
supportsPassive ? {capture, passive} : capture
)
}
vue
中绑定事件是间接绑定给实在dom
元素的
- 组件中绑定事件
export function updateComponentListeners (vm: Component, listeners: Object, oldListeners: ?Object) {target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
function add (event, fn) {target.$on(event, fn)
}
组件绑定事件是通过
vue
中自定义的$on
办法来实现的
Vue 中组件和插件有什么区别
1. 组件是什么
组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式,在 Vue 中每一个.vue 文件都能够视为一个组件
组件的劣势
- 升高整个零碎的耦合度,在放弃接口不变的状况下,咱们能够替换不同的组件疾速实现需要,例如输入框,能够替换为日历、工夫、范畴等组件作具体的实现
- 调试不便,因为整个零碎是通过组件组合起来的,在呈现问题的时候,能够用排除法间接移除组件,或者依据报错的组件疾速定位问题,之所以可能疾速定位,是因为每个组件之间低耦合,职责繁多,所以逻辑会比剖析整个零碎要简略
- 进步可维护性,因为每个组件的职责繁多,并且组件在零碎中是被复用的,所以对代码进行优化可取得零碎的整体降级
2. 插件是什么
插件通常用来为 Vue
增加全局性能。插件的性能范畴没有严格的限度——个别有上面几种:
- 增加全局办法或者属性。如:
vue-custom-element
- 增加全局资源:指令 / 过滤器 / 过渡等。如
vue-touch
- 通过全局混入来增加一些组件选项。如
vue-router
- 增加
Vue
实例办法,通过把它们增加到Vue.prototype
上实现。 - 一个库,提供本人的
API
,同时提供下面提到的一个或多个性能。如vue-router
3. 两者的区别
两者的区别次要体现在以下几个方面:
- 编写模式
- 注册模式
- 应用场景
3.1 编写模式
编写组件
编写一个组件,能够有很多形式,咱们最常见的就是 vue 单文件的这种格局,每一个 .vue
文件咱们都能够看成是一个组件
vue 文件规范格局
<template>
</template>
<script>
export default{...}
</script>
<style>
</style>
咱们还能够通过 template
属性来编写一个组件,如果组件内容多,咱们能够在内部定义 template
组件内容,如果组件内容并不多,咱们可间接写在 template
属性上
<template id="testComponent"> // 组件显示的内容
<div>component!</div>
</template>
Vue.component('componentA',{
template: '#testComponent'
template: `<div>component</div>` // 组件内容少能够通过这种模式
})
编写插件
vue
插件的实现应该裸露一个 install
办法。这个办法的第一个参数是 Vue
结构器,第二个参数是一个可选的选项对象
MyPlugin.install = function (Vue, options) {
// 1. 增加全局办法或 property
Vue.myGlobalMethod = function () {// 逻辑...}
// 2. 增加全局资源
Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}
...
})
// 3. 注入组件选项
Vue.mixin({created: function () {// 逻辑...}
...
})
// 4. 增加实例办法
Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...}
}
3.2 注册模式
组件注册
vue 组件注册次要分为 全局注册 与部分注册
全局注册通过 Vue.component
办法,第一个参数为组件的名称,第二个参数为传入的配置项
Vue.component('my-component-name', { /* ... */})
部分注册只需在用到的中央通过 components
属性注册一个组件
const component1 = {...} // 定义一个组件
export default {
components:{component1 // 部分注册}
}
插件注册
插件的注册通过 Vue.use()
的形式进行注册(装置),第一个参数为插件的名字,第二个参数是可抉择的配置项
Vue.use(插件名字,{ /* ... */} )
留神的是:
注册插件的时候,须要在调用 new Vue()
启动利用之前实现
Vue.use
会主动阻止屡次注册雷同插件,只会注册一次
4. 应用场景
- 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是
App.vue
- 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身
简略来说,插件就是指对 Vue
的性能的加强或补充
Watch 中的 deep:true 是如何实现的
当用户指定了
watch
中的 deep 属性为true
时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后watcher
存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新
源码相干
get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上
let value
const vm = this.vm
try {value = this.getter.call(vm, vm)
} catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {throw e}
} finally {if (this.deep) { // 如果须要深度监控
traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法
}popTarget()}
$route
和 $router
的区别
$route
是“路由信息对象”,包含path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息参数。- 而
$router
是“路由实例”对象包含了路由的跳转办法,钩子函数等
Vue-router 跳转和 location.href 有什么区别
- 应用
location.href= /url
来跳转,简略不便,然而刷新了页面; - 应用
history.pushState(/url)
,无刷新页面,动态跳转; - 引进 router,而后应用
router.push(/url)
来跳转,应用了diff
算法,实现了按需加载,缩小了 dom 的耗费。其实应用 router 跳转和应用history.pushState()
没什么差异的,因为 vue-router 就是用了history.pushState()
,尤其是在 history 模式下。
Vue.extend 作用和原理
官网解释:
Vue.extend
应用根底Vue
结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue
组件的外围 api
实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions
把传入组件的 options
和父类的 options
进行了合并
extend
是结构一个组件的语法器。而后这个组件你能够作用到Vue.component
这个全局注册办法里还能够在任意vue
模板里应用组件。也能够作用到vue
实例或者某个组件中的components
属性中并在外部应用apple
组件。Vue.component
你能够创立,也能够取组件。
相干代码如下
export default function initExtend(Vue) {
let cid = 0; // 组件的惟一标识
// 创立子类继承 Vue 父类 便于属性扩大
Vue.extend = function (extendOptions) {
// 创立子类的构造函数 并且调用初始化办法
const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
};
Sub.cid = cid++;
Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
Sub.prototype.constructor = Sub; //constructor 指向本人
Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
return Sub;
};
}
Class 与 Style 如何动静绑定
Class
能够通过对象语法和数组语法进行动静绑定
对象语法:
<div v-bind:class="{active: isActive,'text-danger': hasError}"></div>
data: {
isActive: true,
hasError: false
}
数组语法:
<div v-bind:class="[isActive ? activeClass :'', errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
Style
也能够通过对象语法和数组语法进行动静绑定
对象语法:
<div v-bind:style="{color: activeColor, fontSize: fontSize +'px'}"></div>
data: {
activeColor: 'red',
fontSize: 30
}
数组语法:
<div v-bind:style="[styleColor, styleSize]"></div>
data: {
styleColor: {color: 'red'},
styleSize:{fontSize:'23px'}
}
vue-router 中如何爱护路由
剖析
路由爱护在利用开发过程中十分重要,简直每个利用都要做各种路由权限治理,因而相当考查使用者基本功。
体验
全局守卫:
const router = createRouter({...})
router.beforeEach((to, from) => {
// ...
// 返回 false 以勾销导航
return false
})
路由独享守卫:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
组件内的守卫:
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {// 在渲染该组件的对应路由被验证前调用},
beforeRouteUpdate(to, from) {// 在以后路由扭转,然而该组件被复用时调用},
beforeRouteLeave(to, from) {// 在导航来到渲染该组件的对应路由时调用},
}
答复
vue-router
中爱护路由的办法叫做路由守卫,次要用来通过跳转或勾销的形式守卫导航。- 路由守卫有三个级别:
全局
、路由独享
、组件级
。影响范畴由大到小,例如全局的router.beforeEach()
,能够注册一个全局前置守卫,每次路由导航都会通过这个守卫,因而在其外部能够退出管制逻辑决定用户是否能够导航到指标路由;在路由注册的时候能够退出单路由独享的守卫,例如beforeEnter
,守卫只在进入路由时触发,因而只会影响这个路由,管制更准确;咱们还能够为路由组件增加守卫配置,例如beforeRouteEnter
,会在渲染该组件的对应路由被验证前调用,管制的范畴更准确了。 - 用户的任何导航行为都会走
navigate
办法,外部有个guards
队列按程序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会勾销原有的导航。
原理
runGuardQueue(guards)
链式的执行用户在各级别注册的守卫钩子函数,通过则持续下一个级别的守卫,不通过进入 catch
流程勾销本来导航
// 源码
runGuardQueue(guards)
.then(() => {
// check global guards beforeEach
guards = []
for (const guard of beforeGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
.then(() => {
// check in components beforeRouteUpdate
guards = extractComponentsGuards(
updatingRecords,
'beforeRouteUpdate',
to,
from
)
for (const record of updatingRecords) {
record.updateGuards.forEach(guard => {guards.push(guardToPromiseFn(guard, to, from))
})
}
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {
// check the route beforeEnter
guards = []
for (const record of to.matched) {
// do not trigger beforeEnter on reused views
if (record.beforeEnter && !from.matched.includes(record)) {if (isArray(record.beforeEnter)) {for (const beforeEnter of record.beforeEnter)
guards.push(guardToPromiseFn(beforeEnter, to, from))
} else {guards.push(guardToPromiseFn(record.beforeEnter, to, from))
}
}
}
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {// NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
// clear existing enterCallbacks, these are added by extractComponentsGuards
to.matched.forEach(record => (record.enterCallbacks = {}))
// check in-component beforeRouteEnter
guards = extractComponentsGuards(
enteringRecords,
'beforeRouteEnter',
to,
from
)
guards.push(canceledNavigationCheck)
// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
})
.then(() => {
// check global guards beforeResolve
guards = []
for (const guard of beforeResolveGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)
})
// catch any navigation canceled
.catch(err =>
isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
? err
: Promise.reject(err)
)
源码地位(opens new window)
Vue 我的项目本地开发实现后部署到服务器后报 404 是什么起因呢
如何部署
前后端拆散开发模式下,前后端是独立布署的,前端只须要将最初的构建物上传至指标服务器的 web
容器指定的动态目录下即可
咱们晓得 vue
我的项目在构建后,是生成一系列的动态文件
惯例布署咱们只须要将这个目录上传至指标服务器即可
让 web
容器跑起来,以 nginx
为例
server {
listen 80;
server_name www.xxx.com;
location / {index /data/dist/index.html;}
}
配置实现记得重启nginx
// 查看配置是否正确
nginx -t
// 平滑重启
nginx -s reload
操作完后就能够在浏览器输出域名进行拜访了
当然下面只是提到最简略也是最间接的一种布署形式
什么自动化,镜像,容器,流水线布署,实质也是将这套逻辑形象,隔离,用程序来代替重复性的劳动,本文不开展
404 问题
这是一个经典的问题,置信很多同学都有遇到过,那么你晓得其真正的起因吗?
咱们先还原一下场景:
vue
我的项目在本地时运行失常,但部署到服务器中,刷新页面,呈现了 404 谬误
先定位一下,HTTP 404 谬误意味着链接指向的资源不存在
问题在于为什么不存在?且为什么只有 history
模式下会呈现这个问题?
为什么 history 模式下有问题
Vue
是属于单页利用(single-page application)
而 SPA
是一种网络应用程序或网站的模型,所有用户交互是通过动静重写以后页面,后面咱们也看到了,不论咱们利用有多少页面,构建物都只会产出一个index.html
当初,咱们回头来看一下咱们的 nginx
配置
server {
listen 80;
server_name www.xxx.com;
location / {index /data/dist/index.html;}
}
能够依据 nginx
配置得出,当咱们在地址栏输出 www.xxx.com
时,这时会关上咱们 dist
目录下的 index.html
文件,而后咱们在跳转路由进入到 www.xxx.com/login
要害在这里,当咱们在 website.com/login
页执行刷新操作,nginx location
是没有相干配置的,所以就会呈现 404
的状况
为什么 hash 模式下没有问题
router hash
模式咱们都晓得是用符号 #示意的,如 website.com/#/login
, hash
的值为 #/login
它的特点在于:hash
尽管呈现在 URL
中,但不会被包含在 HTTP
申请中,对服务端齐全没有影响,因而扭转 hash
不会从新加载页面
hash
模式下,仅 hash
符号之前的内容会被蕴含在申请中,如 website.com/#/login
只有 website.com
会被蕴含在申请中,因而对于服务端来说,即便没有配置 location
,也不会返回404
谬误
解决方案
看到这里我置信大部分同学都能想到怎么解决问题了,
产生问题的实质是因为咱们的路由是通过 JS 来执行视图切换的,
当咱们进入到子路由时刷新页面,web
容器没有绝对应的页面此时会呈现404
所以咱们只须要配置将任意页面都重定向到 index.html
,把路由交由前端解决
对 nginx
配置文件 .conf
批改,增加try_files $uri $uri/ /index.html;
server {
listen 80;
server_name www.xxx.com;
location / {
index /data/dist/index.html;
try_files $uri $uri/ /index.html;
}
}
批改完配置文件后记得配置的更新
nginx -s reload
这么做当前,你的服务器就不再返回 404 谬误页面,因为对于所有门路都会返回 index.html
文件
为了防止这种状况,你应该在 Vue
利用外面笼罩所有的路由状况,而后在给出一个 404 页面
const router = new VueRouter({
mode: 'history',
routes: [{ path: '*', component: NotFoundComponent}
]
})
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期
- 考点:
Vue
的变动侦测原理 - 前置常识: 依赖收集、虚构
DOM
、响应式零碎
根本原因是
Vue
与React
的变动侦测形式有所不同
- 当 React 晓得发生变化后,会应用
Virtual Dom Diff
进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要shouldComponentUpdate
进行手动操作来缩小diff
,从而进步程序整体的性能 Vue
在一开始就晓得那个组件产生了变动,不须要手动管制diff
,而组件外部采纳的diff
形式实际上是能够引入相似于shouldComponentUpdate
相干生命周期的,然而通常正当大小的组件不会有适量的 diff,手动优化的价值无限,因而目前Vue
并没有思考引入shouldComponentUpdate
这种手动优化的生命周期
Vue 中的过滤器理解吗?过滤器的利用场景有哪些?
过滤器本质不扭转原始数据,只是对数据进行加工解决后返回过滤后的数据再进行调用解决,咱们也能够了解其为一个纯函数
Vue 容许你自定义过滤器,可被用于一些常见的文本格式化
ps: Vue3
中已废除filter
如何用
vue 中的过滤器能够用在两个中央:双花括号插值和 v-bind
表达式,过滤器应该被增加在 JavaScript 表达式的尾部,由“管道”符号批示:
<!-- 在双花括号中 -->
{message | capitalize}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
定义 filter
在组件的选项中定义本地的过滤器
filters: {capitalize: function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
定义全局过滤器:
Vue.filter('capitalize', function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({// ...})
留神:当全局过滤器和部分过滤器重名时,会采纳部分过滤器
过滤器函数总接管表达式的值 (之前的操作链的后果) 作为第一个参数。在上述例子中,capitalize
过滤器函数将会收到 message
的值作为第一个参数
过滤器能够串联:
{message | filterA | filterB}
在这个例子中,filterA
被定义为接管单个参数的过滤器函数,表达式 message
的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB
,将 filterA
的后果传递到 filterB
中。
过滤器是 JavaScript
函数,因而能够接管参数:
{{message | filterA('arg1', arg2) }}
这里,filterA
被定义为接管三个参数的过滤器函数。
其中 message
的值作为第一个参数,一般字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数
举个例子:
<div id="app">
<p>{{msg | msgFormat('疯狂','--')}}</p>
</div>
<script>
// 定义一个 Vue 全局的过滤器,名字叫做 msgFormat
Vue.filter('msgFormat', function(msg, arg, arg2) {
// 字符串的 replace 办法,第一个参数,除了可写一个 字符串之外,还能够定义一个正则
return msg.replace(/ 单纯 /g, arg+arg2)
})
</script>
小结:
- 部过滤器优先于全局过滤器被调用
- 一个表达式能够应用多个过滤器。过滤器之间须要用管道符“|”隔开。其执行程序从左往右
利用场景
平时开发中,须要用到过滤器的中央有很多,比方 单位转换
、 数字打点
、 文本格式化
、 工夫格式化
之类的等
比方咱们要实现将30000 => 30,000
,这时候咱们就须要应用过滤器
Vue.filter('toThousandFilter', function (value) {if (!value) return ''
value = value.toString()
return .replace(str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g, '$1,')
})
原理剖析
应用过滤器
{{message | capitalize}}
在模板编译阶段过滤器表达式将会被编译为过滤器函数,次要是用过parseFilters
,咱们放到最初讲
_s(_f('filterFormat')(message))
首先剖析一下_f
:
_f
函数全名是:resolveFilter
,这个函数的作用是从 this.$options.filters
中找出注册的过滤器并返回
// 变为
this.$options.filters['filterFormat'](message) // message 为参数
对于resolveFilter
import {indentity,resolveAsset} from 'core/util/index'
export function resolveFilter(id){return resolveAsset(this.$options,'filters',id,true) || identity
}
外部间接调用 resolveAsset
,将option
对象,类型,过滤器id
,以及一个触发正告的标记作为参数传递,如果找到,则返回过滤器;
resolveAsset
的代码如下:
export function resolveAsset(options,type,id,warnMissing){ // 因为咱们找的是过滤器,所以在 resolveFilter 函数中调用时 type 的值间接给的 'filters', 理论这个函数还能够拿到其余很多货色
if(typeof id !== 'string'){ // 判断传递的过滤器 id 是不是字符串,不是则间接返回
return
}
const assets = options[type] // 将咱们注册的所有过滤器保留在变量中
// 接下来的逻辑便是判断 id 是否在 assets 中存在,即进行匹配
if(hasOwn(assets,id)) return assets[id] // 如找到,间接返回过滤器
// 没有找到,代码继续执行
const camelizedId = camelize(id) // 万一你是驼峰的呢
if(hasOwn(assets,camelizedId)) return assets[camelizedId]
// 没找到,继续执行
const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢
if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]
// 如果还是没找到,则查看原型链(即拜访属性)
const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]
// 如果仍然没找到,则在非生产环境的控制台打印正告
if(process.env.NODE_ENV !== 'production' && warnMissing && !result){warn('Failed to resolve' + type.slice(0,-1) + ':' + id, options)
}
// 无论是否找到,都返回查找后果
return result
}
上面再来剖析一下_s
:
_s
函数的全称是 toString
, 过滤器解决后的后果会当作参数传递给 toString
函数,最终 toString
函数执行后的后果会保留到 Vnode
中的 text 属性中,渲染到视图中
function toString(value){
return value == null
? '': typeof value ==='object'
? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来管制字符串外面的间距
: String(value)
}
最初,在剖析下parseFilters
,在模板编译阶段应用该函数阶段将模板过滤器解析为过滤器函数调用表达式
function parseFilters (filter) {let filters = filter.split('|')
let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该办法会更改原数组
let i
if (filters) {for(i = 0;i < filters.length;i++){experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的 expression 实际上是管道符号后面的字符串,即过滤器的第一个参数
}
}
return expression
}
// warpFilter 函数实现
function warpFilter(exp,filter){
// 首先判断过滤器是否有其余参数
const i = filter.indexof('(')
if(i<0){ // 不含其余参数,间接进行过滤器表达式字符串的拼接
return `_f("${filter}")(${exp})`
}else{const name = filter.slice(0,i) // 过滤器名称
const args = filter.slice(i+1) // 参数,但还多了‘)’return `_f('${name}')(${exp},${args}` // 留神这一步少给了一个 ')'
}
}
小结:
- 在编译阶段通过
parseFilters
将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的后果是后一个过滤器函数的参数) - 编译后通过调用
resolveFilter
函数找到对应过滤器并返回后果 - 执行后果作为参数传递给
toString
函数,而toString
执行后,其后果会保留在Vnode
的text
属性中,渲染到视图
SPA、SSR 的区别是什么
咱们当初编写的 Vue
、React
和Angular
利用大多数状况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,因为良好的用户体验逐步成为支流的开发模式。但同时也会有首屏加载工夫长,SEO
不敌对的问题,因而有了SSR
,这也是为什么面试中会问到两者的区别
SPA
(Single Page Application)即单页面利用。个别也称为 客户端渲染(Client Side Render),简称CSR
。SSR
(Server Side Render)即 服务端渲染。个别也称为 多页面利用(Mulpile Page Application),简称MPA
SPA
利用只会首次申请html
文件,后续只须要申请JSON
数据即可,因而用户体验更好,节约流量,服务端压力也较小。然而首屏加载的工夫会变长,而且SEO
不敌对。为了解决以上毛病,就有了SSR
计划,因为HTML
内容在服务器一次性生成进去,首屏加载快,搜索引擎也能够很不便的抓取页面信息。但同时 SSR 计划也会有性能,开发受限等问题- 在抉择上,如果咱们的利用存在首屏加载优化需要,
SEO
需要时,就能够思考SSR
- 但并不是只有这一种代替计划,比方对一些不常变动的动态网站,SSR 反而浪费资源,咱们能够思考预渲染(
prerender
)计划。另外nuxt.js/next.js
中给咱们提供了SSG(Static Site Generate)
动态网站生成计划也是很好的动态站点解决方案,联合一些CI
伎俩,能够起到很好的优化成果,且能节约服务器资源
内容生成上的区别:
SSR
SPA
部署上的区别
说说 vue 内置指令
什么是作用域插槽
插槽
- 创立组件虚构节点时,会将组件儿子的虚构节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类
{a:[vnode],b[vnode]}
- 渲染组件时会拿对应的
slot
属性的节点进行替换操作。(插槽的作用域为父组件)
<app>
<div slot="a">xxxx</div>
<div slot="b">xxxx</div>
</app>
slot name="a"
slot name="b"
作用域插槽
- 作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。(插槽的作用域为子组件)
- 一般插槽渲染的作用域是父组件,作用域插槽的渲染作用域是以后子组件。
// 插槽
const VueTemplateCompiler = require('vue-template-compiler');
let ele = VueTemplateCompiler.compile(`
<my-component>
<div slot="header">node</div>
<div>react</div>
<div slot="footer">vue</div>
</my-component> `
)
// with(this) {
// return _c('my-component', [_c('div', {// attrs: { "slot": "header"},
// slot: "header"
// }, [_v("node")] // _文本及诶点 )
// , _v(" "),
// _c('div', [_v("react")]), _v(""), _c('div', {// attrs: { "slot": "footer"},
// slot: "footer" }, [_v("vue")])])
// }
const VueTemplateCompiler = require('vue-template-compiler');
let ele = VueTemplateCompiler.compile(`
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
<slot></slot>
</div> `
);
with(this) {return _c('div', [_v("node"), _v(""), _t(_v("vue")])]), _v(" "), _t("default")], 2)
}
// _t 定义在 core/instance/render-helpers/index.js
// 作用域插槽:
let ele = VueTemplateCompiler.compile(` <app>
<div slot-scope="msg" slot="footer">{{msg.a}}</div>
</app> `
);
// with(this) {
// return _c('app', { scopedSlots: _u([{
// // 作用域插槽的内容会被渲染成一个函数
// key: "footer",
// fn: function (msg) {// return _c('div', {}, [_v(_s(msg.a))]) } }])
// })
// }
// }
const VueTemplateCompiler = require('vue-template-compiler');
VueTemplateCompiler.compile(` <div><slot name="footer" a="1" b="2"></slot> </div> `);
// with(this) {return _c('div', [_t("footer", null, { "a": "1", "b": "2"})], 2) }