便宜网站建设模板网站,聚名网域名怎么过户给公司,遵义公司做网站,找工作58同城最新招聘附近Vue3.0 为什么要用 proxy#xff1f;在 Vue2 中#xff0c; 0bject.defineProperty 会改变原始数据#xff0c;而 Proxy 是创建对象的虚拟表示#xff0c;并提供 set 、get 和 deleteProperty 等处理器#xff0c;这些处理器可在访问或修改原始对象上的属性时进行拦截在 Vue2 中 0bject.defineProperty 会改变原始数据而 Proxy 是创建对象的虚拟表示并提供 set 、get 和 deleteProperty 等处理器这些处理器可在访问或修改原始对象上的属性时进行拦截有以下特点∶不需用使用 Vue.$set 或 Vue.$delete 触发响应式。全方位的数组变化检测消除了Vue2 无效的边界情况。支持 MapSetWeakMap 和 WeakSet。Proxy 实现的响应式原理与 Vue2的实现原理相同实现方式大同小异∶get 收集依赖Set、delete 等触发依赖对于集合类型就是对集合对象的方法做一层包装原方法执行后执行依赖相关的收集或触发逻辑。大厂面试题分享 面试题库后端面试题库 面试必备 推荐★★★★★地址前端面试题库说说你对slot的理解slot使用场景有哪些一、slot是什么在HTML中 slot 元素 作为 Web Components 技术套件的一部分是Web组件内的一个占位符该占位符可以在后期使用自己的标记语言填充举个栗子templateidelement-details-templateslotnameelement-nameSlot template/slot/templateelement-detailsspanslotelement-name1/span/element-detailselement-detailsspanslotelement-name2/span/element-details复制代码template不会展示到页面中需要用先获取它的引用然后添加到DOM中customElements.define(element-details,classextendsHTMLElement {constructor() {super();const template document.getElementById(element-details-template).content;const shadowRoot this.attachShadow({mode: open}).appendChild(template.cloneNode(true));}
})
复制代码在Vue中的概念也是如此Slot 艺名插槽花名“占坑”我们可以理解为solt在组件模板中占好了位置当使用该组件标签时候组件标签里面的内容就会自动填坑替换组件模板中slot位置作为承载分发内容的出口二、使用场景通过插槽可以让用户可以拓展组件去更好地复用组件和对其做定制化处理如果父组件在使用到一个复用组件的时候获取这个组件在不同的地方有少量的更改如果去重写组件是一件不明智的事情通过slot插槽向组件内部指定位置传递内容完成这个复用组件在不同场景的应用比如布局组件、表格列、下拉选、弹框显示内容等使用vue渲染大量数据时应该怎么优化说下你的思路分析企业级项目中渲染大量数据的情况比较常见因此这是一道非常好的综合实践题目。回答在大型企业级项目中经常需要渲染大量数据此时很容易出现卡顿的情况。比如大数据量的表格、树处理时要根据情况做不同处理可以采取分页的方式获取避免渲染大量数据vue-virtual-scroller (opens new window)等虚拟滚动方案只渲染视口范围内的数据如果不需要更新可以使用v-once方式只渲染一次通过v-memo (opens new window)可以缓存结果结合v-for使用避免数据变化时不必要的VNode创建可以采用懒加载方式在用户需要的时候再加载数据比如tree组件子树的懒加载还是要看具体需求首先从设计上避免大数据获取和渲染实在需要这样做可以采用虚表的方式优化渲染最后优化更新如果不需要更新可以v-once处理需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化无线滚动、懒加载等方案scoped样式穿透scoped虽然避免了组件间样式污染但是很多时候我们需要修改组件中的某个样式但是又不想去除scoped属性使用/deep/!-- Parent --templatedivclasswrapChild //div/templatestylelangscssscoped.wrap /deep/ .box{background: red;
}
/style!-- Child --templatedivclassbox/div/template复制代码使用两个style标签!-- Parent --templatedivclasswrapChild //div/templatestylelangscssscoped/* 其他样式 *//stylestylelangscss.wrap.box{background: red;
}
/style!-- Child --templatedivclassbox/div/template复制代码Vue中v-html会导致哪些问题可能会导致 xss 攻击v-html 会替换掉标签内部的子元素let template require(vue-template-compiler);
let r template.compile(div v-htmlspanhello/span/div) // with(this){return _c(div,{domProps: {innerHTML:_s(spanhello/span)}})} console.log(r.render);// _c 定义在core/instance/render.js // _s 定义在core/instance/render-helpers/index,jsif (key textContent || key innerHTML) { if (vnode.children) vnode.children.length 0if (cur oldProps[key]) continue// #6601 work around Chrome version 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property if (elm.childNodes.length 1) { elm.removeChild(elm.childNodes[0]) }
}
复制代码如果让你从零开始写一个vuex说说你的思路思路分析这个题目很有难度首先思考vuex解决的问题存储用户全局状态并提供管理状态API。vuex需求分析如何实现这些需求回答范例官方说vuex是一个状态管理模式和库并确保这些状态以可预期的方式变更。可见要实现一个vuex要实现一个Store存储全局状态要提供修改状态所需APIcommit(type, payload), dispatch(type, payload)实现Store时可以定义Store类构造函数接收选项options设置属性state对外暴露状态提供commit和dispatch修改属性state。这里需要设置state为响应式对象同时将Store定义为一个Vue插件commit(type, payload)方法中可以获取用户传入mutations并执行它这样可以按用户提供的方法修改状态。 dispatch(type, payload)类似但需要注意它可能是异步的需要返回一个Promise给用户以处理异步结果实践Store的实现classStore {constructor(options) {this.state reactive(options.state)this.options options}commit(type, payload) {this.options.mutations[type].call(this, this.state, payload)}
}
复制代码vuex简易版/*** 1 实现插件挂载$store* 2 实现store*/letVue;classStore {constructor(options) {// state响应式处理// 外部访问 this.$store.state.***// 第一种写法// this.state new Vue({// data: options.state// })// 第二种写法防止外界直接接触内部vue实例防止外部强行变更this._vm newVue({data: {$$state: options.state}})this._mutations options.mutationsthis._actions options.actionsthis.getters {}options.getters this.handleGetters(options.getters)this.commit this.commit.bind(this)this.dispatch this.dispatch.bind(this)}get state () {returnthis._vm._data.$$state}set state (val) {returnnewError(Please use replaceState to reset state)}handleGetters (getters) {Object.keys(getters).map(key {Object.defineProperty(this.getters, key, {get: () getters[key](this.state)})})}commit (type, payload) {let entry this._mutations[type]if (!entry) {returnnewError(${type} is not defined)}entry(this.state, payload)}dispatch (type, payload) {let entry this._actions[type]if (!entry) {returnnewError(${type} is not defined)}entry(this, payload)}
}constinstall (_Vue) {Vue _VueVue.mixin({beforeCreate () {if (this.$options.store) {Vue.prototype.$store this.$options.store}},})
}exportdefault { Store, install }
复制代码验证方式importVuefromvueimportVuexfrom./vuex// this.$storeVue.use(Vuex)exportdefaultnewVuex.Store({state: {counter: 0},mutations: {// state从哪里来的add (state) {state.counter}},getters: {doubleCounter (state) {return state.counter * 2}},actions: {add ({ commit }) {setTimeout(() {commit(add)}, 1000)}},modules: {}
})
复制代码参考 前端进阶面试题详细解答Vue与Angular以及React的区别Vue与AngularJS的区别Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScriptAngularJS依赖对数据做脏检查所以Watcher越多越慢Vue.js使用基于依赖追踪的观察并且使用异步队列更新所有的数据都是独立触发的。AngularJS社区完善, Vue的学习成本较小Vue与React的区别相同点Virtual DOM。其中最大的一个相似之处就是都使用了Virtual DOM。(当然Vue是在Vue2.x才引用的)也就是能让我们通过操作数据的方式来改变真实的DOM状态。因为其实Virtual DOM的本质就是一个JS对象它保存了对真实DOM的所有描述是真实DOM的一个映射所以当我们在进行频繁更新元素的时候改变这个JS对象的开销远比直接改变真实DOM要小得多。组件化的开发思想。第二点来说就是它们都提倡这种组件化的开发思想也就是建议将应用分拆成一个个功能明确的模块再将这些模块整合在一起以满足我们的业务需求。Props。Vue和React中都有props的概念允许父组件向子组件传递数据。构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具React中可以使用CRAVue中可以使用对应的脚手架vue-cli。对于配套框架Vue中有vuex、vue-routerReact中有react-router、redux。不同点模版的编写。最大的不同就是模版的编写Vue鼓励你去写近似常规HTML的模板React推荐你使用JSX去书写。状态管理与对象属性。在React中应用的状态是比较关键的概念也就是state对象它允许你使用setState去更新状态。但是在Vue中state对象并不是必须的数据是由data属性在Vue对象中进行管理。虚拟DOM的处理方式不同。Vue中的虚拟DOM控制了颗粒度组件层面走watcher通知而组件内部走vdom做diff这样既不会有太多watcher也不会让vdom的规模过大。而React走了类似于CPU调度的逻辑把vdom这棵树微观上变成了链表然后利用浏览器的空闲时间来做diffVue项目中你是如何解决跨域的呢一、跨域是什么跨域本质是浏览器基于同源策略的一种安全手段同源策略Sameoriginpolicy是一种约定它是浏览器最核心也最基本的安全功能所谓同源即指在同一个域具有以下三个相同点协议相同protocol主机相同host端口相同port反之非同源请求也就是协议、端口、主机其中一项不相同的时候这时候就会产生跨域一定要注意跨域是浏览器的限制你用抓包工具抓取接口数据是可以看到接口已经把数据返回回来了只是浏览器的限制你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。Class 与 Style 如何动态绑定Class 可以通过对象语法和数组语法进行动态绑定对象语法div v-bind:class{ active: isActive, text-danger: hasError }/divdata: {isActive: true,hasError: false
}
复制代码数组语法div v-bind:class[isActive ? activeClass : , errorClass]/divdata: {activeClass: active,errorClass: text-danger
}
复制代码Style 也可以通过对象语法和数组语法进行动态绑定对象语法div v-bind:style{ color: activeColor, fontSize: fontSize px }/divdata: {activeColor: red,fontSize: 30
}
复制代码数组语法div v-bind:style[styleColor, styleSize]/divdata: {styleColor: {color: red},styleSize:{fontSize:23px}
}
复制代码了解history有哪些方法吗说下它们的区别history 这个对象在html5的时候新加入两个api history.pushState() 和 history.repalceState() 这两个API可以在不进行刷新的情况下操作浏览器的历史纪录。唯一不同的是前者是新增一个历史记录后者是直接替换当前的历史记录。从参数上来说window.history.pushState(state,title,url)
//state需要保存的数据这个数据在触发popstate事件时可以在event.state里获取//title标题基本没用一般传null//url设定新的历史纪录的url。新的url与当前url的origin必须是一样的否则会抛出错误。url可以时绝对路径也可以是相对路径。//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, ./qq/)则变成 https://www.baidu.com/a/qq///执行history.pushState(null, null, /qq/)则变成 https://www.baidu.com/qq/window.history.replaceState(state,title,url)
//与pushState 基本相同但她是修改当前历史纪录而 pushState 是创建新的历史纪录复制代码另外还有window.history.back() 后退window.history.forward()前进window.history.go(1) 前进或者后退几步从触发事件的监听上来说pushState()和replaceState()不能被popstate事件所监听而后面三者可以且用户点击浏览器前进后退键时也可以在Vue中使用插件的步骤采用ES6的import ... from ...语法或CommonJS的require()方法引入插件使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })$route和$router的区别$route是“路由信息对象”包括pathparamshashqueryfullPathmatchedname等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法钩子函数等为什么要使用异步组件节省打包出的结果异步组件分开打包采用jsonp的方式进行加载有效解决文件过大的问题。核心就是包组件定义变成一个函数依赖import() 语法可以实现文件的分割加载。components:{ AddCustomerSchedule:(resolve)import(../components/AddCustomer) // require([])
}
复制代码原理exportfunction ( Ctor: ClassComponent | Function | Object | void, data: ?VNodeData, context: Component, children: ?ArrayVNode, tag?: string ): VNode | ArrayVNode | void { // async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory CtorCtor resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend // 第二次渲染时Ctor不为undefined if (Ctor undefined) { returncreateAsyncPlaceholder( // 渲染占位符 空虚拟节点 asyncFactory, data, context, children, tag ) } }
}
functionresolveAsyncComponent ( factory: Function, baseCtor: ClassComponent ): ClassComponent | void { if (isDef(factory.resolved)) { // 3.在次渲染时可以拿到获取的最新组件 return factory.resolved }const resolve once((res: Object | ClassComponent) { factory.resolved ensureCtor(res, baseCtor) if (!sync) { forceRender(true) //2. 强制更新视图重新渲染 } else { owners.length 0 } })const reject once(reason { if (isDef(factory.errorComp)) { factory.error trueforceRender(true) } })const res factory(resolve, reject)// 1.将resolve方法和reject方法传入用户调用 resolve方法后 sync falsereturn factory.resolved
}
复制代码函数式组件优势和原理函数组件的特点函数式组件需要在声明组件是指定 functional:true不需要实例化所以没有this,this通过render函数的第二个参数context来代替没有生命周期钩子函数不能使用计算属性watch不能通过$emit 对外暴露事件调用事件只能通过context.listeners.click的方式调用外部传入的事件因为函数式组件是没有实例化的所以在外部通过ref去引用组件时实际引用的是HTMLElement函数式组件的props可以不用显示声明所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)优点由于函数式组件不需要实例化无状态没有生命周期所以渲染性能要好于普通组件函数式组件结构比较简单代码结构更清晰使用场景一个简单的展示组件作为容器组件使用 比如 router-view 就是一个函数式组件“高阶组件”——用于接收一个组件作为参数返回一个被包装过的组件例子Vue.component(functional,{ // 构造函数产生虚拟节点的functional:true, // 函数式组件 // data{attrs:{}}render(h){returnh(div,test)}
})
const vm newVue({el: #app
})
复制代码源码相关// functional componentif (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件returncreateFunctionalComponent(Ctor, propsData, data, context, children)
}// extract listeners, since these needs to be treated as// child component listeners instead of DOM listenersconst listeners data.on// 处理事件// replace with listeners with .native modifier// so it gets processed during parent component patch.
data.on data.nativeOn// 处理原生事件// install component management hooks onto the placeholder nodeinstallComponentHooks(data) // 安装组件相关钩子 函数式组件没有调用此方法从而性能高于普通组件复制代码Vue.set的实现原理给对应和数组本身都增加了dep属性当给对象新增不存在的属性则触发对象依赖的watcher去更新当修改数组索引时我们调用数组本身的splice去更新数组数组的响应式原理就是重新了splice等方法调用splice就会触发视图更新基本使用以下方法调用会改变原始数组push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )调用方法Vue.set(target, key, value )target要更改的数据源(可以是对象或者数组)key要更改的具体数据value 重新赋的值dividapp{{user.name}} {{user.age}}/divdividapp/divscript// 1. 依赖收集的特点给每个属性都增加一个dep属性dep属性会进行收集收集的是watcher// 2. vue会给每个对象也增加一个dep属性const vm newVue({el: #app,data: { // vm._data user: {name:poetry}}});// 对象的话调用defineReactive在user对象上定义一个age属性增加到响应式数据中触发对象本身的watcherob.dep.notify()更新 // 如果是数组 通过调用 splice方法触发视图更新vm.$set(vm.user, age, 20); // 不能给根属性添加因为给根添加属性 性能消耗太大需要做很多处理// 修改肯定是同步的 - 更新都是一步的 queuewatcher/script复制代码相关源码// src/core/observer/index.js 44exportclassObserver { // new Observer(value)value: any;dep: Dep;vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {this.value valuethis.dep newDep() // 给所有对象类型增加dep属性}
}
复制代码// src/core/observer/index.js 201exportfunctionset (target: Arrayany | Object, key: any, val: any): any {// 1.是开发环境 target 没定义或者是基础类型则报错if (process.env.NODE_ENV ! production (isUndef(target) || isPrimitive(target))) {warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)})}// 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)if (Array.isArray(target) isValidArrayIndex(key)) {target.length Math.max(target.length, key)// 利用数组的splice变异方法触发响应式 target.splice(key, 1, val)return val}// 3.如果是对象本身的属性则直接添加即可if (key in target !(key inObject.prototype)) {target[key] val // 直接修改属性值 return val}// 4.如果是Vue实例 或 根数据data时 报错,更新_data 无意义const ob (target: any).__ob__if (target._isVue || (ob ob.vmCount)) {process.env.NODE_ENV ! production warn(Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.)return val}// 5.如果不是响应式的也不需要将其定义成响应式属性if (!ob) {target[key] valreturn val}// 6.将属性定义成响应式的defineReactive(ob.value, key, val)// 通知视图更新ob.dep.notify()return val
}
复制代码大厂面试题分享 面试题库后端面试题库 面试必备 推荐★★★★★地址前端面试题库我们阅读以上源码可知vm.$set 的实现原理是如果目标是数组 直接使用数组的 splice 方法触发相应式如果目标是对象 会先判读属性是否存在、对象是否是响应式最终如果要对属性进行响应式处理则是通过调用 defineReactive 方法进行响应式处理 defineReactive 方法就是 Vue 在初始化对象时给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法Vue为什么没有类似于React中shouldComponentUpdate的生命周期考点: Vue的变化侦测原理前置知识: 依赖收集、虚拟DOM、响应式系统根本原因是Vue与React的变化侦测方式有所不同当React知道发生变化后会使用Virtual Dom Diff进行差异检测但是很多组件实际上是肯定不会发生变化的这个时候需要 shouldComponentUpdate 进行手动操作来减少diff从而提高程序整体的性能Vue在一开始就知道那个组件发生了变化不需要手动控制diff而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期的但是通常合理大小的组件不会有过量的diff手动优化的价值有限因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期vue-router中如何保护路由分析路由保护在应用开发过程中非常重要几乎每个应用都要做各种路由权限管理因此相当考察使用者基本功。体验全局守卫const router createRouter({ ... })
router.beforeEach((to, from) {// ...// 返回 false 以取消导航returnfalse
})
复制代码路由独享守卫const routes [{path: /users/:id,component: UserDetails,beforeEnter: (to, from) {// reject the navigationreturnfalse},},
]
复制代码组件内的守卫constUserDetails {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 beforeEachguards []for (const guard of beforeGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))}guards.push(canceledNavigationCheck)returnrunGuardQueue(guards)}).then(() {// check in components beforeRouteUpdateguards 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 guardsreturnrunGuardQueue(guards)}).then(() {// check the route beforeEnterguards []for (const record of to.matched) {// do not trigger beforeEnter on reused viewsif (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 guardsreturnrunGuardQueue(guards)}).then(() {// NOTE: at this point to.matched is normalized and does not contain any () PromiseComponent// clear existing enterCallbacks, these are added by extractComponentsGuardsto.matched.forEach(record (record.enterCallbacks {}))// check in-component beforeRouteEnterguards extractComponentsGuards(enteringRecords,beforeRouteEnter,to,from)guards.push(canceledNavigationCheck)// run the queue of per route beforeEnter guardsreturnrunGuardQueue(guards)}).then(() {// check global guards beforeResolveguards []for (const guard of beforeResolveGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))}guards.push(canceledNavigationCheck)returnrunGuardQueue(guards)})// catch any navigation canceled.catch(err isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)? err: Promise.reject(err))
复制代码源码位置(opens new window)Vue-router 路由钩子在生命周期的体现一、Vue-Router导航守卫有的时候需要通过路由来进行一些操作比如最常见的登录权限验证当用户满足条件时才让其进入导航否则就取消跳转并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程全局的单个路由独享的或者组件级的全局路由钩子vue-router全局有三个路由钩子;router.beforeEach 全局前置守卫 进入路由之前router.beforeResolve 全局解析守卫2.5.0在 beforeRouteEnter 调用之后调用router.afterEach 全局后置钩子 进入路由之后具体使用∶beforeEach判断是否登录了没登录就跳转到登录页router.beforeEach((to, from, next) { let ifInfo Vue.prototype.$common.getSession(userData); // 判断是否登录的存储信息if (!ifInfo) { // sessionStorage里没有储存user信息 if (to.path /) { //如果是登录页面路径就直接next() next(); } else { //不然就跳转到登录 Message.warning(请重新登录); window.location.href Vue.prototype.$loginUrl; } } else { returnnext(); }
})复制代码afterEach 跳转之后滚动条回到顶部router.afterEach((to, from) { // 跳转之后滚动条回到顶部 window.scrollTo(0,0);
});复制代码单个路由独享钩子beforeEnter 如果不想全局配置守卫的话可以为某些路由单独配置守卫有三个参数∶ to、from、nextexportdefault [ { path: /, name: login, component: login, beforeEnter: (to, from, next) { console.log(即将进入登录页面) next() } }
]复制代码组件内钩子beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave这三个钩子都有三个参数∶to、from、nextbeforeRouteEnter∶ 进入组件前触发beforeRouteUpdate∶ 当前地址改变并且改组件被复用时触发举例来说带有动态参数的路径foo/∶id在 /foo/1 和 /foo/2 之间跳转的时候由于会渲染同样的foa组件这个钩子在这种情况下就会被调用beforeRouteLeave∶ 离开组件被调用注意点beforeRouteEnter组件内还访问不到this因为该守卫执行前组件实例还没有被创建需要传一个回调给 next来访问例如beforeRouteEnter(to, from, next) { next(target { if (from.path /classProcess) { target.isFromProcess true } })
}复制代码二、Vue路由钩子在生命周期函数的体现完整的路由导航解析流程不包括其他生命周期触发进入其他路由。调用要离开路由的组件守卫beforeRouteLeave调用局前置守卫∶ beforeEach在重用的组件里调用 beforeRouteUpdate调用路由独享守卫 beforeEnter。解析异步路由组件。在将要进入的路由组件中调用 beforeRouteEnter调用全局解析守卫 beforeResolve导航被确认。调用全局后置钩子的 afterEach 钩子。触发DOM更新mounted。执行beforeRouteEnter 守卫中传给 next 的回调函数触发钩子的完整顺序路由导航、keep-alive、和组件生命周期钩子结合起来的触发顺序假设是从a组件离开第一次进入b组件∶beforeRouteLeave路由组件的组件离开路由前钩子可取消路由离开。beforeEach路由全局前置守卫可用于登录验证、全局路由loading等。beforeEnter路由独享守卫beforeRouteEnter路由组件的组件进入路由前钩子。beforeResolve路由全局解析守卫afterEach路由全局后置钩子beforeCreate组件生命周期不能访问tAis。created;组件生命周期可以访问tAis不能访问dom。beforeMount组件生命周期deactivated离开缓存组件a或者触发a的beforeDestroy和destroyed组件销毁钩子。mounted访问/操作dom。activated进入缓存组件进入a的嵌套子组件如果有的话。执行beforeRouteEnter回调函数next。导航行为被触发到导航完成的整个过程导航行为被触发此时导航未被确认。在失活的组件里调用离开守卫 beforeRouteLeave。调用全局的 beforeEach守卫。在重用的组件里调用 beforeRouteUpdate 守卫(2.2)。在路由配置里调用 beforeEnteY。解析异步路由组件如果有。在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫2.5标示解析阶段完成。导航被确认。调用全局的 afterEach 钩子。非重用组件开始组件实例的生命周期beforeCreatecreated、beforeMountmounted触发 DOM 更新。用创建好的实例调用 beforeRouteEnter守卫中传给 next 的回调函数。导航完成Vue-router 导航守卫有哪些全局前置/钩子beforeEach、beforeResolve、afterEach路由独享的守卫beforeEnter组件内的守卫beforeRouteEnter、beforeRouteUpdate、beforeRouteLeaveVue的diff算法详细分析1. 是什么diff 算法是一种通过同层的树节点进行比较的高效算法其有两个特点比较只会在同层级进行, 不会跨层级比较在diff比较的过程中循环从两边向中间比较diff 算法在很多场景下都有应用在 vue 中作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较2. 比较方式diff整体策略为深度优先同层比较比较只会在同层级进行, 不会跨层级比较比较的过程中循环从两边向中间收拢下面举个vue通过diff算法更新的例子新旧VNode节点如下图所示第一次循环后发现旧节点D与新节点D相同直接复用旧节点D作为diff后的第一个真实节点同时旧节点endIndex移动到C新节点的 startIndex 移动到了 C第二次循环后同样是旧节点的末尾和新节点的开头(都是 C)相同同理diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 endIndex 移动到了 B新节点的 startIndex 移动到了 E第三次循环中发现E没有找到这时候只能直接创建新的真实节点 E插入到第二次创建的 C 节点之后。同时新节点的 startIndex 移动到了 A。旧节点的 startIndex 和 endIndex 都保持不动第四次循环中发现了新旧节点的开头(都是 A)相同于是 diff 后创建了 A 的真实节点插入到前一次创建的 E 节点后面。同时旧节点的 startIndex 移动到了 B新节点的startIndex 移动到了 B第五次循环中情形同第四次循环一样因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 startIndex移动到了 C新节点的 startIndex 移动到了 F新节点的 startIndex 已经大于 endIndex 了需要创建 newStartIdx 和 newEndIdx 之间的所有节点也就是节点F直接创建 F 节点对应的真实节点放到 B 节点后面3. 原理分析当数据发生改变时set方法会调用Dep.notify通知所有订阅者Watcher订阅者就会调用patch给真实的DOM打补丁更新相应的视图源码位置src/core/vdom/patch.jsfunctionpatch(oldVnode, vnode, hydrating, removeOnly) {if (isUndef(vnode)) { // 没有新节点直接执行destory钩子函数if (isDef(oldVnode)) invokeDestroyHook(oldVnode)return}let isInitialPatch falseconst insertedVnodeQueue []if (isUndef(oldVnode)) {isInitialPatch truecreateElm(vnode, insertedVnodeQueue) // 没有旧节点直接用新节点生成dom元素} else {const isRealElement isDef(oldVnode.nodeType)if (!isRealElement sameVnode(oldVnode, vnode)) {// 判断旧节点和新节点自身一样一致执行patchVnodepatchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)} else {// 否则直接销毁及旧节点根据新节点生成dom元素if (isRealElement) {if (oldVnode.nodeType 1 oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR)hydrating true}if (isTrue(hydrating)) {if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {invokeInsertHook(vnode, insertedVnodeQueue, true)return oldVnode}}oldVnode emptyNodeAt(oldVnode)}return vnode.elm}}
}
复制代码patch函数前两个参数位为oldVnode 和 Vnode 分别代表新的节点和之前的旧节点主要做了四个判断没有新节点直接触发旧节点的destory钩子没有旧节点说明是页面刚开始初始化的时候此时根本不需要比较了直接全是新建所以只调用 createElm旧节点和新节点自身一样通过 sameVnode 判断节点是否一样一样时直接调用 patchVnode去处理这两个节点旧节点和新节点自身不一样当两个节点不一样的时候直接创建新节点删除旧节点下面主要讲的是patchVnode部分functionpatchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {// 如果新旧节点一致什么都不做if (oldVnode vnode) {return}// 让vnode.el引用到现在的真实dom当el修改时vnode.el会同步变化const elm vnode.elm oldVnode.elm// 异步占位符if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue)} else {vnode.isAsyncPlaceholder true}return}// 如果新旧都是静态节点并且具有相同的key// 当vnode是克隆节点或是v-once指令控制的节点时只需要把oldVnode.elm和oldVnode.child都复制到vnode上// 也不用再有其他操作if (isTrue(vnode.isStatic) isTrue(oldVnode.isStatic) vnode.key oldVnode.key (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance oldVnode.componentInstancereturn}let iconst data vnode.dataif (isDef(data) isDef(i data.hook) isDef(i i.prepatch)) {i(oldVnode, vnode)}const oldCh oldVnode.childrenconst ch vnode.childrenif (isDef(data) isPatchable(vnode)) {for (i 0; i cbs.update.length; i) cbs.update[i](oldVnode, vnode)if (isDef(i data.hook) isDef(i i.update)) i(oldVnode, vnode)}// 如果vnode不是文本节点或者注释节点if (isUndef(vnode.text)) {// 并且都有子节点if (isDef(oldCh) isDef(ch)) {// 并且子节点不完全一致则调用updateChildrenif (oldCh ! ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)// 如果只有新的vnode有子节点} elseif (isDef(ch)) {if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, )// elm已经引用了老的dom节点在老的dom节点上添加子节点addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)// 如果新vnode没有子节点而vnode有子节点直接删除老的oldCh} elseif (isDef(oldCh)) {removeVnodes(elm, oldCh, 0, oldCh.length - 1)// 如果老节点是文本节点} elseif (isDef(oldVnode.text)) {nodeOps.setTextContent(elm, )}// 如果新vnode和老vnode是文本节点或注释节点// 但是vnode.text ! oldVnode.text时只需要更新vnode.elm的文本内容就可以} elseif (oldVnode.text ! vnode.text) {nodeOps.setTextContent(elm, vnode.text)}if (isDef(data)) {if (isDef(i data.hook) isDef(i i.postpatch)) i(oldVnode, vnode)}}
复制代码patchVnode主要做了几个判断新节点是否是文本节点如果是则直接更新dom的文本内容为新节点的文本内容新节点和旧节点如果都有子节点则处理比较更新子节点只有新节点有子节点旧节点没有那么不用比较了所有节点都是全新的所以直接全部新建就好了新建是指创建出所有新DOM并且添加进父节点只有旧节点有子节点而新节点没有说明更新后的页面旧节点全部都不见了那么要做的就是把所有的旧节点删除也就是直接把DOM 删除子节点不完全一致则调用updateChildrenfunctionupdateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {let oldStartIdx 0// 旧头索引let newStartIdx 0// 新头索引let oldEndIdx oldCh.length - 1// 旧尾索引let newEndIdx newCh.length - 1// 新尾索引let oldStartVnode oldCh[0] // oldVnode的第一个childlet oldEndVnode oldCh[oldEndIdx] // oldVnode的最后一个childlet newStartVnode newCh[0] // newVnode的第一个childlet newEndVnode newCh[newEndIdx] // newVnode的最后一个childlet oldKeyToIdx, idxInOld, vnodeToMove, refElm// removeOnly is a special flag used only by transition-group// to ensure removed elements stay in correct relative positions// during leaving transitionsconst canMove !removeOnly// 如果oldStartVnode和oldEndVnode重合并且新的也都重合了证明diff完了循环结束while (oldStartIdx oldEndIdx newStartIdx newEndIdx) {// 如果oldVnode的第一个child不存在if (isUndef(oldStartVnode)) {// oldStart索引右移oldStartVnode oldCh[oldStartIdx] // Vnode has been moved left// 如果oldVnode的最后一个child不存在} elseif (isUndef(oldEndVnode)) {// oldEnd索引左移oldEndVnode oldCh[--oldEndIdx]// oldStartVnode和newStartVnode是同一个节点} elseif (sameVnode(oldStartVnode, newStartVnode)) {// patch oldStartVnode和newStartVnode 索引左移继续循环patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)oldStartVnode oldCh[oldStartIdx]newStartVnode newCh[newStartIdx]// oldEndVnode和newEndVnode是同一个节点} elseif (sameVnode(oldEndVnode, newEndVnode)) {// patch oldEndVnode和newEndVnode索引右移继续循环patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)oldEndVnode oldCh[--oldEndIdx]newEndVnode newCh[--newEndIdx]// oldStartVnode和newEndVnode是同一个节点} elseif (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// patch oldStartVnode和newEndVnodepatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)// 如果removeOnly是false则将oldStartVnode.eml移动到oldEndVnode.elm之后canMove nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))// oldStart索引右移newEnd索引左移oldStartVnode oldCh[oldStartIdx]newEndVnode newCh[--newEndIdx]// 如果oldEndVnode和newStartVnode是同一个节点} elseif (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left// patch oldEndVnode和newStartVnodepatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)// 如果removeOnly是false则将oldEndVnode.elm移动到oldStartVnode.elm之前canMove nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)// oldEnd索引左移newStart索引右移oldEndVnode oldCh[--oldEndIdx]newStartVnode newCh[newStartIdx]// 如果都不匹配} else {if (isUndef(oldKeyToIdx)) oldKeyToIdx createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)// 尝试在oldChildren中寻找和newStartVnode的具有相同的key的VnodeidxInOld isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key]: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)// 如果未找到说明newStartVnode是一个新的节点if (isUndef(idxInOld)) { // New element// 创建一个新VnodecreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)// 如果找到了和newStartVnodej具有相同的key的Vnode叫vnodeToMove} else {vnodeToMove oldCh[idxInOld]/* istanbul ignore if */if (process.env.NODE_ENV ! production !vnodeToMove) {warn(It seems there are duplicate keys that is causing an update error. Make sure each v-for item has a unique key.)}// 比较两个具有相同的key的新节点是否是同一个节点//不设keynewCh和oldCh只会进行头尾两端的相互比较设key后除了头尾两端的比较外还会从用key生成的对象oldKeyToIdx中查找匹配的节点所以为节点设置key可以更高效的利用dom。if (sameVnode(vnodeToMove, newStartVnode)) {// patch vnodeToMove和newStartVnodepatchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)// 清除oldCh[idxInOld] undefined// 如果removeOnly是false则将找到的和newStartVnodej具有相同的key的Vnode叫vnodeToMove.elm// 移动到oldStartVnode.elm之前canMove nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)// 如果key相同但是节点不相同则创建一个新的节点} else {// same key but different element. treat as new elementcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)}}// 右移newStartVnode newCh[newStartIdx]}}
复制代码while循环主要处理了以下五种情景当新老 VNode 节点的 start 相同时直接 patchVnode 同时新老 VNode 节点的开始索引都加 1当新老 VNode 节点的 end相同时同样直接 patchVnode 同时新老 VNode 节点的结束索引都减 1当老 VNode 节点的 start 和新 VNode 节点的 end 相同时这时候在 patchVnode 后还需要将当前真实 dom 节点移动到 oldEndVnode 的后面同时老 VNode 节点开始索引加 1新 VNode 节点的结束索引减 1当老 VNode 节点的 end 和新 VNode 节点的 start 相同时这时候在 patchVnode 后还需要将当前真实 dom 节点移动到 oldStartVnode 的前面同时老 VNode 节点结束索引减 1新 VNode 节点的开始索引加 1如果都不满足以上四种情形那说明没有相同的节点可以复用则会分为以下两种情况从旧的 VNode 为 key 值对应 index 序列为 value 值的哈希表中找到与 newStartVnode 一致 key 的旧的 VNode 节点再进行patchVnode同时将这个真实 dom移动到 oldStartVnode 对应的真实 dom 的前面调用 createElm 创建一个新的 dom 节点放到当前 newStartIdx 的位置小结当数据发生改变时订阅者watcher就会调用patch给真实的DOM打补丁通过isSameVnode进行判断相同则调用patchVnode方法patchVnode做了以下操作找到对应的真实dom称为el如果都有都有文本节点且不相等将el文本节点设置为Vnode的文本节点如果oldVnode有子节点而VNode没有则删除el子节点如果oldVnode没有子节点而VNode有则将VNode的子节点真实化后添加到el如果两者都有子节点则执行updateChildren函数比较子节点updateChildren主要做了以下操作设置新旧VNode的头尾指针新旧头尾指针进行比较循环向中间靠拢根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点从哈希表寻找 key一致的VNode 节点再分情况操作大厂面试题分享 面试题库后端面试题库 面试必备 推荐★★★★★地址前端面试题库
文章转载自: http://www.morning.zfqr.cn.gov.cn.zfqr.cn http://www.morning.qdxkn.cn.gov.cn.qdxkn.cn http://www.morning.ltksw.cn.gov.cn.ltksw.cn http://www.morning.tnnfy.cn.gov.cn.tnnfy.cn http://www.morning.rbcw.cn.gov.cn.rbcw.cn http://www.morning.zcwzl.cn.gov.cn.zcwzl.cn http://www.morning.wqbfd.cn.gov.cn.wqbfd.cn http://www.morning.psgbk.cn.gov.cn.psgbk.cn http://www.morning.iterlog.com.gov.cn.iterlog.com http://www.morning.jfch.cn.gov.cn.jfch.cn http://www.morning.fmjzl.cn.gov.cn.fmjzl.cn http://www.morning.cokcb.cn.gov.cn.cokcb.cn http://www.morning.tfzjl.cn.gov.cn.tfzjl.cn http://www.morning.qphgp.cn.gov.cn.qphgp.cn http://www.morning.mwqbp.cn.gov.cn.mwqbp.cn http://www.morning.cwwbm.cn.gov.cn.cwwbm.cn http://www.morning.pcqdf.cn.gov.cn.pcqdf.cn http://www.morning.qnbgh.cn.gov.cn.qnbgh.cn http://www.morning.gzttoyp.com.gov.cn.gzttoyp.com http://www.morning.nkyc.cn.gov.cn.nkyc.cn http://www.morning.yrjkz.cn.gov.cn.yrjkz.cn http://www.morning.kgslc.cn.gov.cn.kgslc.cn http://www.morning.gcxfh.cn.gov.cn.gcxfh.cn http://www.morning.bgqr.cn.gov.cn.bgqr.cn http://www.morning.bhwll.cn.gov.cn.bhwll.cn http://www.morning.trlhc.cn.gov.cn.trlhc.cn http://www.morning.dywgl.cn.gov.cn.dywgl.cn http://www.morning.trbxt.cn.gov.cn.trbxt.cn http://www.morning.dgxrz.cn.gov.cn.dgxrz.cn http://www.morning.rydbs.cn.gov.cn.rydbs.cn http://www.morning.ljjmr.cn.gov.cn.ljjmr.cn http://www.morning.zrks.cn.gov.cn.zrks.cn http://www.morning.fxwkl.cn.gov.cn.fxwkl.cn http://www.morning.bbmx.cn.gov.cn.bbmx.cn http://www.morning.trrhj.cn.gov.cn.trrhj.cn http://www.morning.xhqwm.cn.gov.cn.xhqwm.cn http://www.morning.fplqh.cn.gov.cn.fplqh.cn http://www.morning.cznsq.cn.gov.cn.cznsq.cn http://www.morning.rkdhh.cn.gov.cn.rkdhh.cn http://www.morning.xhhqd.cn.gov.cn.xhhqd.cn http://www.morning.rpkl.cn.gov.cn.rpkl.cn http://www.morning.snbrs.cn.gov.cn.snbrs.cn http://www.morning.kltmt.cn.gov.cn.kltmt.cn http://www.morning.grynb.cn.gov.cn.grynb.cn http://www.morning.rfrxt.cn.gov.cn.rfrxt.cn http://www.morning.rqrh.cn.gov.cn.rqrh.cn http://www.morning.bfmq.cn.gov.cn.bfmq.cn http://www.morning.hxpsp.cn.gov.cn.hxpsp.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.rrxgx.cn.gov.cn.rrxgx.cn http://www.morning.qrsm.cn.gov.cn.qrsm.cn http://www.morning.lfpdc.cn.gov.cn.lfpdc.cn http://www.morning.rqsr.cn.gov.cn.rqsr.cn http://www.morning.njdtq.cn.gov.cn.njdtq.cn http://www.morning.dshkp.cn.gov.cn.dshkp.cn http://www.morning.qrgfw.cn.gov.cn.qrgfw.cn http://www.morning.lgwpm.cn.gov.cn.lgwpm.cn http://www.morning.nmrtb.cn.gov.cn.nmrtb.cn http://www.morning.lgsqy.cn.gov.cn.lgsqy.cn http://www.morning.pbsqr.cn.gov.cn.pbsqr.cn http://www.morning.ltffk.cn.gov.cn.ltffk.cn http://www.morning.qkxnw.cn.gov.cn.qkxnw.cn http://www.morning.zpstm.cn.gov.cn.zpstm.cn http://www.morning.bfnbn.cn.gov.cn.bfnbn.cn http://www.morning.gcspr.cn.gov.cn.gcspr.cn http://www.morning.nmqdk.cn.gov.cn.nmqdk.cn http://www.morning.lctrz.cn.gov.cn.lctrz.cn http://www.morning.wklhn.cn.gov.cn.wklhn.cn http://www.morning.gqtxz.cn.gov.cn.gqtxz.cn http://www.morning.rfhmb.cn.gov.cn.rfhmb.cn http://www.morning.wnhsw.cn.gov.cn.wnhsw.cn http://www.morning.nbgfk.cn.gov.cn.nbgfk.cn http://www.morning.rfxg.cn.gov.cn.rfxg.cn http://www.morning.qcdhg.cn.gov.cn.qcdhg.cn http://www.morning.fhtmp.cn.gov.cn.fhtmp.cn http://www.morning.cfpq.cn.gov.cn.cfpq.cn http://www.morning.wnjbn.cn.gov.cn.wnjbn.cn http://www.morning.dqgbx.cn.gov.cn.dqgbx.cn http://www.morning.tnktt.cn.gov.cn.tnktt.cn http://www.morning.mkyny.cn.gov.cn.mkyny.cn