SEO面试要问的问题(SEO面试问题)_黑帽seo手段

黑帽SEO 次浏览

摘要:SEO面试要问的问题(SEO面试问题)_黑帽seo手段1.vue-router用过没SEO面试要问的问题,哪些常用的钩子函数路由钩子的执行流程,钩子函数种类有SEO面试要问的问题:全局守卫、路由守卫、组件守卫。首页可以控制导航跳转

SEO面试要问的问题(SEO面试问题)_黑帽seo手段

1. vue-router用过没SEO面试要问的问题,哪些常用的钩子函数

路由钩子的执行流程,钩子函数种类有SEO面试要问的问题:全局守卫、路由守卫、组件守卫。

首页可以控制导航跳转, beforeEach , afterEach 等,?般?于页? title 的修改。?些需要登录才能调整??的重定向功能。

beforeEach 主要有3个参数 to , from , next 。

to : route 即将进?的?标路由对象。

from : route 当前导航正要离开的路由。

next : function ?定要调?该?法 resolve 这个钩?。执?效果依赖next ?法的调?参数。可以控制网页的跳转

完整的导航解析流程

导航被触发。

在失活的组件里调用离开守卫。

调用全局的 beforeEach 守卫。

在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

在路由配置里调用 beforeEnter。

解析异步路由组件。

在被激活的组件里调用 beforeRouteEnter。

调用全局的 beforeResolve 守卫 (2.5+)。

导航被确认。

调用全局的 afterEach 钩子。

触发 DOM 更新。

用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

2. Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

加载渲染过程父

beforeCreate - 父 created - 父 beforeMount - 子 beforeCreate - 子 created - 子 beforeMount - 子 mounted - 父 mounted

子组件更新过程

父 beforeUpdate - 子 beforeUpdate - 子 updated - 父 updated

父组件更新过程父 beforeUpdate - 父 updated

销毁过程

父 beforeDestroy - 子 beforeDestroy - 子 destroyed - 父 destroyed

3. 谈谈你对 Vue 生命周期的理解?

(1)生命周期是什么?

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom - 渲染、更新 - 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

(2)各个生命周期的作用

生命周期描述beforeCreate组件实例被创建之初,组件的属性生效之前created组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子beforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前update组件数据更新之后activitedkeep-alive 专属,组件被激活时调用deactivatedkeep-alive 专属,组件被销毁时调用beforeDestory组件销毁前调用destoryed组件销毁后调用

4. 直接给一个数组项赋值,Vue 能检测到变化吗?

由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:

当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:

// Vue.setVue.set(vm.items, indexOfItem, newValue)// vm.$set,Vue.set的一个别名vm.$set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:

// Array.prototype.splicevm.items.splice(newLength)5. 怎样理解 Vue 的单向数据流?

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个 prop 的情形 :

这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:

props: ['initialCounter'],data: function () { return { counter: this.initialCounter }}//这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性props: ['size'],computed: { normalizedSize: function () { return this.size.trim().toLowerCase() }}6. 数据请求放哪个生命周期

可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

能更快获取到服务端数据,减少页面 loading 时间;

ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

7. 在什么阶段才能访问操作DOM

在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。

8. 父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vueChild @mounted="doSomething"/ // Child.vuemounted() { this.$emit("mounted");}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

// Parent.vueChild @hook:mounted="doSomething" /ChilddoSomething() { console.log('父组件监听到 mounted 钩子函数 ...');}, // Child.vuemounted(){ console.log('子组件触发 mounted 钩子函数 ...');}, // 以上输出顺序为:// 子组件触发 mounted 钩子函数 ...// 父组件监听到 mounted 钩子函数 ...

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

9. computed 和 watch 的区别和运用的场景?

computed:

是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch:

更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

10. vue-nextTick 如何实现

核心答案:

nextTick的回调是在下次DOM更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。nextTick主要使用了宏任务和微任务。原理就是异步方法(promise, mutationObserver, setImmediate, setTimeout)经常与事件循环一起来问。

补充回答:

vue多次更新数据,最终会进行批处理更新。内部调用的就是nextTick实现了延迟更新,用户自定义的nextTick中的回调会被延迟到更新完成后调用,从而可以获取更新后的DOM。

11. 谈谈你对 keep-alive 的了解?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

一般结合路由和动态组件一起使用,用于缓存组件;

提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;

对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

12. data为什么返回一个函数

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

13. v-model 的原理?

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

1)text 和 textarea 元素使用 value 属性和 input 事件;

2)checkbox 和 radio 使用 checked 属性和 change 事件;

3)select 字段将 value 作为 prop 并将 change 作为事件。

14. Vue 组件间通信有哪几种方式?

3 类通信:父子组件通信、隔代组件通信、兄弟组件通信

(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref 与 $parent / $children 适用 父子组件通信

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

$parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(4)$attrs/$listeners 适用于 隔代组件通信

$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(5)provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

15. 你使用过 Vuex 吗

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。

Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。

Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

16. mutation/action 哪个处理异步,哪个处理同步

mutation是同步更新数据(内部会进行是否为异步方式更新数据的检测)

action 异步操作,可以获取数据后调佣mutation提交最终数据

17. 使用过 Vue SSR 吗?说说 SSR?

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点:

更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;

更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

18. vue3/vue2区别(vue3.0 特性)

(1)监测机制的改变

3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:

只能监测属性,不能监测对象

检测属性的添加和删除;

检测数组索引和长度的变更;

支持 Map、Set、WeakMap 和 WeakSet。

新的 observer 还提供了以下特性:

用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。

默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。

更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。

不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。

更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。

(2)模板

模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。

同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。

(3)对象式的组件声明方式

vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。

此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。

(4)其它方面的更改

vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:

支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。

支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。

基于 treeshaking 优化,提供了更多的内置功能。

19. vue-router 路由模式有几种

vue-router 有 3 种路由模式:hash、history、abstract;

hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;

history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;

abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

20. 能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

https://www.word.com#search

hash 路由模式的实现主要是基于下面几个特性:

URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;

hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;

可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;

我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);window.history.replaceState(null, null, path);

history 路由模式的实现主要基于存在下面几个特性:

pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

21. vue3 proxy代替vue2的Object.defineProperty()的解决了什么问题(Proxy 与 Object.defineProperty 优劣对比)

Proxy 的优势如下:

Proxy 可以直接监听对象而非属性;

Proxy 可以直接监听数组的变化;

Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;

Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

24. Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?

受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象添加响应式属性,那框架本身是如何实现的呢?

我们查看对应的 Vue 源码:vue/src/core/instance/index.js

export function set (target: Arrayany | Object, key: any, val: any): any { // target 为数组 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改数组的长度, 避免索引数组长度导致splcie()执行有误 target.length = Math.max(target.length, key) // 利用数组的splice变异方法触发响应式 target.splice(key, 1, val) return val } // key 已经存在,直接修改属性值 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // target 本身就不是响应式数据, 直接赋值 if (!ob) { target[key] = val return val } // 对属性进行响应式处理 defineReactive(ob.value, key, val) ob.dep.notify() return val}复制代码

我们阅读以上源码可知,vm.$set 的实现原理是:

如果目标是数组,直接使用数组的 splice 方法触发相应式;

如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

25. 了解vite么26. 虚拟 DOM 的优缺点

优点:

保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;

无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;

跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

27. 虚拟 DOM 实现原理28. Vue 是如何实现数据双向绑定的?

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据,如下图所示:

即:

输入框内容变化时,Data 中的数据同步变化。即 View = Data 的变化。

Data 中的数据变化时,文本节点的内容同步变化。即 Data = View 的变化。

其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

29. 什么是 MVVM?

Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于2005年在他的博客上发表

MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。

(1)View 层

View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。

(2)Model 层

Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层

ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。

MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:

(1)View 层

div id="app" p{{message}}/p button v-on:click="showMessage()"Click me/button/div复制代码

(2)ViewModel 层

var app = new Vue({ el: '#app', data: { // 用于描述视图状态 message: 'Hello Vue!', }, methods: { // 用于描述视图行为 showMessage(){ let vm = this; alert(vm.message); } }, created(){ let vm = this; // Ajax 获取 Model 层的数据 ajax({ url: '/your/server/data/api', success(res){ vm.message = res; } }); }})复制代码

(3) Model 层

{ "url": "/your/server/data/api", "res": { "success": true, "name": "IoveC", "domain": "www.cnblogs.com" }}复制代码30. v-show 与 v-if 有什么区别?

v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

31. Class 与 Style 如何动态绑定?32. Vue 框架怎么实现对象和数组的监听

如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:

/** * Observe a list of Array items. */ observeArray (items: Arrayany) { for (let i = 0, l = items.length; i l; i++) { observe(items[i]) // observe 功能为监测数据的变化 } } /** * 对属性进行递归遍历 */ let childOb = !shallow && observe(val) // observe 功能为监测数据的变化复制代码

通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。

33. 你有对 Vue 项目进行哪些优化?

(1)代码层面的优化

v-if 和 v-show 区分使用场景

computed 和 watch 区分使用场景

v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

SPA页面采用keep-alive缓存组件

长列表性能优化

事件的销毁

图片资源懒加载

合理使用路由懒加载、异步组件

数据持久化的问题 (防抖、节流)

第三方插件的按需引入

优化无限列表性能

服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

Webpack 对图片进行压缩

减少 ES6 转为 ES5 的冗余代码

提取公共代码

模板预编译

提取组件的 CSS

优化 SourceMap

构建结果输出分析

Vue 项目的编译优化

(3)基础的 Web 技术的优化

开启 gzip 压缩

浏览器缓存

CDN 的使用

使用 Chrome Performance 查找性能瓶颈

34. Vue.js 如何让CSS只在当前组件中起作??35. Vue.js 中相同逻辑如何抽离?

Vue.mixin用法 给组件每个生命周期,函数等都混入一些公共逻辑

36. Vue.js 中模板编译原理?

核心答案:

如何将template转换成render函数(这里要注意的是我们在开发时尽量不要使用template,因为将template转化成render方法需要在运行时进行编译操作会有性能损耗,同时引用带有complier包的vue体积也会变大) 默认.vue文件中的 template处理是通过vue-loader 来进行处理的并不是通过运行时的编译。

将 template 模板转换成 ast 语法树 - parserHTML

对静态语法做静态标记 - markUp

重新生成代码 - codeGen

补充回答:

模板引擎的实现原理就是new Function + with来进行实现的。

vue-loader中处理template属性主要靠的是vue-template-compiler

function baseCompile ( template: string, options: CompilerOptions ) { const ast = parse(template.trim(), options) // 1.将模板转化成ast语法树 if (options.optimize !== false) { // 2.优化树 optimize(ast, options) } const code = generate(ast, options) // 3.生成树 return { ast, render: code.render, staticRenderFns: code.staticRenderFns } }) const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; const qnameCapture = `((?:${ncname}\\:)?${ncname})`; const startTagOpen = new RegExp(`^${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名 const endTag = new RegExp(`^\\/${qnameCapture}[^]*`); // 匹配标签结尾的 /div const attribute = /^\s*([^\s"'\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=`]+)))?/; // 匹配属性的 const startTagClose = /^\s*(\/?)/; // 匹配标签结束的 let root; let currentParent; let stack = [] function createASTElement(tagName,attrs){ return { tag:tagName, type:1, children:[], attrs, parent:null } } function start(tagName,attrs){ let element = createASTElement(tagName,attrs); if(!root){ root = element; } currentParent = element; stack.push(element); } function chars(text){ currentParent.children.push({ type:3, text }) } function end(tagName){ const element = stack[stack.length-1]; stack.length --; currentParent = stack[stack.length-1]; if(currentParent){ element.parent = currentParent; currentParent.children.push(element) } } function parseHTML(html){ while(html){ let textEnd = html.indexOf(''); if(textEnd == 0){ const startTagMatch = parseStartTag(); if(startTagMatch){ start(startTagMatch.tagName,startTagMatch.attrs); continue; } const endTagMatch = html.match(endTag); if(endTagMatch){ advance(endTagMatch[0].length); end(endTagMatch[1]) } } let text; if(textEnd =0 ){ text = html.substring(0,textEnd) } if(text){ advance(text.length); chars(text); } } function advance(n) { html = html.substring(n); } function parseStartTag(){ const start = html.match(startTagOpen); if(start){ const match = { tagName:start[1], attrs:[] } advance(start[0].length); let attr,end while(!(end = html.match(startTagClose)) && (attr=html.match(attribute))){ advance(attr[0].length); match.attrs.push({name:attr[1],value:attr[3]}) } if(end){ advance(end[0].length); return match } } } } // 生成语法树 parseHTML(`div id="container"phellospanzf/span/p/div`); function gen(node){ if(node.type == 1){ return generate(node); }else{ return `_v(${JSON.stringify(node.text)})` } } function genChildren(el){ const children = el.children; if(el.children){ return `[${children.map(c=gen(c)).join(',')}]` }else{ return false; } } function genProps(attrs){ let str = '' for(let i = 0; i attrs.length;i++){ let attr = attrs[i]; str+= `${attr.name}:${attr.value},`; } return `{attrs:{${str.slice(0,-1)}}}` } function generate(el){ let children = genChildren(el); let code = `_c('${el.tag}'${ el.attrs.length? `,${genProps(el.attrs)}`:'' }${ children? `,${children}`:'' })`; return code; } // 根据语法树生成新的代码 let code = generate(root); let render = `with(this){return ${code}}`; // 包装成函数 let renderFn = new Function(render); console.log(renderFn.toString());37. 描述组件渲染和更新过程?

渲染组件时,会通过Vue.extend方法构建子组件的构造函数,并进行实例化。最终手动调用$mount()进行挂载。更新组件时会进行patchVnode流程.核心就是diff算法

38. Vue.js 和 react 区别?

相同点

都?持 ssr ,都有 vdom ,组件化开发,实现 webComponents 规范,数据驱 动等

不同点

vue 是双向数据流(当然为了实现单数据流?便管理组件状态, vuex 便出现了), react 是单向数据流。

vue 的 vdom 是追踪每个组件的依赖关系,不会渲染整个组件树, react 每当应该状态被改变时,全部?组件都会 re-render

39. new Vue() 发生了什么?

核心答案:

1)结论:new Vue()是创建Vue实例,它内部执行了根实例的初始化过程。

2)具体包括以下操作:

选项合并

$children,$refs,$slots,$createElement等实例属性的方法初始化

自定义事件处理

数据响应式处理

生命周期钩子调用 (beforecreate created)

可能的挂载

3)总结:new Vue()创建了根实例并准备好数据和方法,未来执行挂载时,此过程还会递归的应用于它的子组件上,最终形成一个有紧密关系的组件实例树。

40. Vue.use是干什么的?原理是什么?

1?检查插件是否注册,若已注册,则直接跳出;

2?处理入参,将第一个参数之后的参数归集,并在首部塞入 this 上下文;

3?执行注册方法,调用定义好的 install 方法,传入处理的参数,若没有 install 方法并且插件本身为 function 则直接进行注册;

插件不能重复的加载

install 方法的第一个参数是vue的构造函数,其他参数是Vue.set中除了第一个参数的其他参数; 代码:args.unshift(this)

调用插件的install 方法 代码:typeof plugin.install === 'function'

插件本身是一个函数,直接让函数执行。 代码:plugin.apply(null, args)

缓存插件。 代码:installedPlugins.push(plugin)

41. Vue中key的作用和工作原理,说说你对它的理解

核心答案:

例如:v-for="(item, itemIndex) in tabs" :key="itemIndex"

key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。

补充回答:

1、若不设置key还可能在列表更新时引发一些隐蔽的bug

2、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

42. Vue 中的diff原理

核心答案:

vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。

补充回答:

先比较是否是相同节点

相同节点比较属性,并复用老节点

比较儿子节点,考虑老节点和新节点儿子的情况

优化比较:头头、尾尾、头尾、尾头

比对查找进行复用

Vue2 与 Vue3.x 的diff算法:

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。

Vue3.x借鉴了ivi算法和 inferno算法,该算法中还运用了动态规划的思想求解最长递归子序列。(实际的实现可以结合Vue3.x源码看。)

43. 如何理解自定义指令?

核心答案:

指令的实现原理,可以从编译原理 =代码生成= 指令钩子实现进行概述

1?在生成 ast 语法树时,遇到指令会给当前元素添加directives属性

2?通过 genDirectives 生成指令代码

3?在patch前将指令的钩子提取到 cbs中,在patch过程中调用对应的钩子。

4?当执行指令对应钩子函数时,调用对应指令定义的方法

SEO面试要问的问题(SEO面试问题)_黑帽seo手段(图1)

 本文内容主要是有关于::SEO面试要问的问题(SEO面试问题)和[黑帽seo手段]

如果您有想法通过seo排名。来获得精准流量。请添加客服咨询我们。专业的团队+AI智能让您没有后顾之忧。

随机内容
// // // //