面试题 41 . 如何理解Vue中的模板编译原理?
参考回答:
关于Vue编译原理这块的整体逻辑主要分为三步:
第一步将模版字符串转换成element ASTs(解析器)
第二步是对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是使用element ASTs生成render函数代码字符串(代码生成器)
解析器
{{name}}
上面一个简单 的模版转换成element AST树形结构后是这样的:
{
tag: "div"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: undefined,
attrsList: [],
attrsMap: {},
children: [
{
tag: "p"
type: 1,
staticRoot: false,
static: false,
plain: true,
parent: {tag: "div", ...},
attrsList: [],
attrsMap: {},
children: [{
type: 2,
text: "{{name}}",
static: false,
expression: "_s(name)"
}]
}
]
}
我们可以看到上面的dom被解析成了解析器,它的原理主要是两部分内容,一部分是截取字符串,一部分是对截取的字符串做解析。
优化器
优化器的目的就是找出那些静态节点并打上标记,而静态节点指的是DOM不需要发生变化的节点,也就是里面都是静态标签和静态文本。
标记静态节点有两个好处:
一、每次重新渲染的时候不需要为静态节点创建新节点,也就是静态节点的解析器不需要重新创建
二、在Virtual DOM中patching的过程可以被跳过
优化器的实现原理主要分两步:
一、用递归的方式将静态节点添加static属性,用来标识是不是静态节点
二、标记所有静态根节点(子节点全是静态节点就是静态根节点)
代码生成器
代码生成器的作用是使用element ASTs生成render函数代码字符串。
使用本文开头举的例子中的模版生成后的AST来生成render后是这样的:
{
render: with(this){return _c('div',[_c('p',[_v(_s(name))])])}
}
格式化后是这样的:
with(this){
return _c(
'div',
[
_c(
'p',
[
_v(_s(name))
]
)
]
)
}
生成后的代码字符串中看到了有几个函数调用_c、_v、_s。
_c对应的是createElement,它的作用是创建一个元素。
1.第一个参数是一个HTML标签名
2.第二个参数是元素上使用的属性所对应的数据对象,可选项
3.第三个参数是children
_v的意思是创建一个文本节点。
_s是返回参数中的字符串。
代码生成器的总体逻辑其实就是使用element ASTs去递归,然后拼出这样的_c('div',[_c('p',[_v(_s(name))])]) 字符串。
总结
本篇文章我们说了 vue 对模板编译的整体流程分为三个部分:解析器(parser),优化器(optimizer)和代码生成器(code generator)。
解析器(parser)的作用是将 模板字符串 转换成 element ASTs。
优化器(optimizer)的作用是找出那些静态节点和静态根节点并打上标记。
代码生成器(code generator)的作用是使用 element ASTs 生成 render函数代码(generate render function code from element ASTs)。
解析器(parser)的原理是一小段一小段的去截取字符串,然后维护一个 stack 用来保存DOM深度,每截取到一段标签的开始就 push 到 stack 中,当所有字符串都截取完之后也就解析出了一个完整的 AST。
优化器(optimizer)的原理是用递归的方式将所有节点打标记,表示是否是一个 静态节点,然后再次递归一遍把 静态根节点 也标记出来。
代码生成器(code generator)的原理也是通过递归去拼一个函数执行代码的字符串,递归的过程根据不同的节点类型调用不同的生成方法,如果发现是一颗元素节点就拼一个 _c(tagName, data, children) 的函数调用字符串,然后 data 和 children 也是使用 AST 中的属性去拼字符串。
如果 children 中还有 children 则递归去拼。
最后拼出一个完整的 render 函数代码。
面试题 42 . Vue生命周期钩子是如何实现的?
参考回答:
生命周期钩子在内部会被vue维护成一个数组(vue 内部有一个方法mergeOption)和全局的生命周期合并最终转换成数组,当执行到具体流程时会执行钩子(发布订阅模式),callHook来实现调用。
理解vue中模板编译原理?
1 会将模板变成ast语法树(模板编译原理的核心就是 ast-> 生成代码)
2 对ast语法树进行优化,标记静态节点.(vue3中模板编译做了哪些优化patchFlag,blockTree,事件缓存,节点缓存...)
3 代码生成 拼接render函数字符串 + new Function + with;
面试题 43 . 简述v-for中的key的理解?
参考回答:
需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。主要是为了高效的更新虚拟DOM
面试题 44 . Vuex 页面刷新数据丢失怎么解决?
参考回答:
需要做 vuex 数据持久化 ,一般使 用本地储存的方案 来保存数据,可以自己设计存储方
案,也可以使用第三方插件。
推荐使用 vuex-persist ( 脯肉赛斯特 ) 插件,它是为 Vuex 持久化储存而生的一个插件。
不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
面试题 45 . 简述vue-loader是什么?使用它的用途有哪些?
参考回答:
解析.vue文件的一个加载器,跟template/js/style转换成js模块。
用途:js可以写es6、style样式可以scss或less、template可以加jade等
面试题 46 . 请说出vue.cli项目中src目录每个文件夹和文件的用法?
参考回答:
1. assets 文件夹是放静态资源
2. components 文件夹是放全局组件的
3. router 文件夹是定义路由相关的配置
4. store 文件夹是管理 vuex管理数据的位置 模块化开发 全局getters
5. views 视图 所有页面 路由级别的组件
6. App.vue 入口页面 根组件
7. main.js 入口文件 加载组件 初始化等
面试题 47 . 解释Vue.set 改变数组和对象中的属性 ?
参考回答:
在一个组件实例中,只有在data里初始化的数据才是响应的,Vue不能检测到对象属性的添加或删除,没有在data里声明的属性不是响应的,所以数据改变了但是不会在页面渲染;
解决办法:
使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
面试题 48 . Vue.js中的路由导航钩子有哪些?如何使用?
参考回答:
三种,一是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件
beforeRouteEnter、afterEnter、beforeRouterUpdate、beforeRouteLeave
参数:有to(去的那个路由)、from(离开的路由)、next(一定要用这个函数才能去到下一个路由,如果不用就拦截)最常用就这几种
Vue的路由实现: hash模式和history模式(Vue的两种状态)
hash——即地址栏URL的#符号,特点: 通过window.onhashchange的监听, 匹配不同的url路径,进行解析,加载不同的组件,然后动态的渲染出区域内的html内容,不会被包含在HTTP请求中,对后端完全没有影响
HashHistory有两个方法:
HashHistory.push()是将路由添加到浏览器访问历史的栈顶
hashHistory.replace( ) 是替换掉当前栈顶的路由
因为hash发生变化的url都会被浏览器历史访问栈记录下来,这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来的,浏览器还是可以进行前进后退的
history —— 利用了HTML5 History Interface中新增的pushState()和replaceState()方法。这两个方式应用于浏览器的历史记录栈,提供了对历史记录的修改功能。history模式不怕页面的前进和后腿,就怕刷新,当刷新时,如果服务器没有相应的响应或者资源,就会刷出404,而hash模式不会。
面试题 49 . Vue.js中的事件修饰符有哪些?如何使用?
参考回答:
Vue.js中的事件修饰符包括stop、prevent、capture、self、once、passive等。使用方式是在v-on指令后添加相应的修饰符。
解析:这是一个基础问题,主要考察面试者Vue.js中事件修饰符的熟悉程度,并且是否能够简单、清晰地描述出来
面试题 50 . Vue.js中的v-bind指令有何作用?如何使用?
参考回答:
Vue.js中的v-bind指令用于绑定HTML元素的属性或特性。使用方式是在HTML元素上添加v-bind属性,并指定需要绑定的属性或特性。
解析:这是一个基础问题,主要考察面试者Vue.js中v-bind指令的熟悉程度,并且是否能够简单、清晰地描述出来
面试题 51 . 简述vue如何监听键盘事件中的按键?
参考回答:
在我们的项目经常需要监听一些键盘事件来触发程序的执行,而Vue中允许在监听的时候添加关键修饰符:
对于一些常用键,还提供了按键别名:
全部的按键别名:
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
修饰键:
.ctrl
.alt
.shift
.meta
与按键别名不同的是,修饰键和 keyup 事件一起用时,事件引发时必须按下正常的按键。换一种说法:如果要引发 keyup.ctrl,必须按下 ctrl 时释放其他的按键;单单释放 ctrl 不会引发事件
对于elementUI的input,我们需要在后面加上.native, 因为elementUI对input进行了封装,原生的事件不起作用。
面试题 52 . 简述如何在vue-cli生产环境使用全局常量 ?
参考回答:
第一步,在 static 下新建 config.js:
第二步,在 config.js 里面设置全局变量:
第三步,在 index.html 里面引入:
第四步,在其他 .js 文件中即可使用:
第五步,打包后修改:通过 `npm run build` 命令打包后,此 config.js 文件会被打包到 `dist/static`文件夹下,此时如果需要修改 `PUBLIC_IP`,打开`config.js`即可修改,无需重新打包
面试题 53 . 简述Vue中同时发送多个请求怎么操作?
参考回答:
创建两个Promise,在Promise中使用axios
调用Promise.all([p1,p2]).then( res =>{ }).catch( err => { }) 方法
举例说明:
getInfo(){
//创建promise,在promise中调用axios , then里使用resolve回调, catch里使用reject回调
var p1 = new Promie((resolve,reject) => {
this.$axios.get(httpUrl.getUser).then(res => {
resolve(res);
}).catch(err =>{
reject (err);
})
})
var p2 = new Promie((resolve,reject) => {
this.$axios.get(httpUrl.getCompany).then(res => {
resolve(res);
}).catch(err =>{
reject (err);
})
})
//调用Promise.add().then(res => {})
Promise.all([p1,p2]).then(res => {
console.log(res);
})
}
resolve(res);
面试题 54 . Vue 中如何进行组件的使用?Vue 如何实现全局组件的注册?
参考回答:
要使用组件,首先需要使用 import 来引入组件,然后在 components 属性中注册组件,之后就可以在模板中使用组件了。
可以使用 Vue.component 方法来实现全局组件的注册。
面试题 55 . 简述Vue complier 的实现原理是什么样的?
参考回答:
在使用 vue 的时候,我们有两种方式来创建我们的 HTML 页面,第一种情况,也是大多情况下,我们会使用模板 template 的方式,因为这更易读易懂也是官方推荐的方法;第二种情况是使用 render 函数来生成 HTML,它比 template 更接近最终结果。
complier 的主要作用是解析模板,生成渲染模板的 render, 而 render 的作用主要是为了生成 VNode
complier 主要分为 3 大块:
parse:接受 template 原始模板,按着模板的节点和数据生成对应的 ast
optimize:遍历 ast 的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,通过 diff 减少去对比这部分DOM,提升性能
generate 把前两步生成完善的 ast,组成 render 字符串,然后将 render 字符串通过 new Function 的方式转换成渲染函数
面试题 56 . 请简述Vue ref 的作用是什么?
参考回答:
ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
如果用在子组件上,引用就指向组件实例
所以常见的使用场景有:
基本用法,本页面获取 DOM 元素
获取子组件中的 data
调用子组件中的方法
面试题 57 . 简述接口请求一般放在哪个生命周期中?为什么要这样做?
参考回答:
接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
能更快获取到服务端数据,减少页面 loading 时间
SSR 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于代码的一致性
created 是在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。如果在 mounted 钩子函数中请求数据可能导致页面闪屏问题
面试题 58 . 请简述Vue 中相同逻辑如何进行抽离?
参考回答:
可以使用 vue 里面的混入(mixin)技术。混入(mixin)提供了一种非常灵活的方式,来将 vue 中相同的业务逻辑进行抽离。
例如:
在 data 中有很多是公用数据
引用封装好的组件也都是一样的
methods、watch、computed 中也都有大量的重复代码
当然这个时候可以将所有的代码重复去写来实现功能,但是我们并不不推荐使用这种方式,无论是工作量、工作效率和后期维护来说都是不建议的,这个时候 mixin 就可以大展身手了。
一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。说白了就是给每个生命周期,函数等等中间加入一些公共逻辑。
混入技术特点
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
面试题 59 . 请说明给 vue 中的元素设置 key 值时可以使用 Math 的 random 方法么?
参考回答:
random 是生成随机数,有一定概率多个 item 会生成相同的值,不能保证唯一。
如果是根据数据来生成 item,数据具有 id 属性,那么就可以使用 id 来作为 key。
如果不是根据数据生成 item,那么最好的方式就是使用时间戳来作为 key。或者使用诸如 uuid 之类的库来生成唯一的 id
面试题 60 . 简述 v-if 和 v-show 的区别 ?
参考回答:
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。元素销毁和重建控制显示隐藏
v-show 会被编译成指令,条件不满足时控制样式将此节点隐藏(display:none) css样式控制
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景。
v-show 适用于需要非常频繁切换条件的场景。
扩展补充:display:none 、 visibility:hidden 和 opacity:0 之间的区别?
三者公共点都是:隐藏
v-if 和 v-show 不同点:
是否占据空间。
display:none,隐藏之后不占位置;visibility:hidden、opacity:0,隐藏后任然占据位置。
子元素是否继承。
display:none --- 不会被子元素继承,父元素都不存在了,子元素也不会显示出来。
visibility:hidden --- 会被子元素继承,通过设置子元素 visibility:visible 来显示子元素。
opacity:0 --- 会被子元素继承,但是不能设置子元素 opacity:0 来先重新显示。
事件绑定。
display:none 的元素都已经不存在了,因此无法触发他绑定的事件。
visibility:hidden 不会触发他上面绑定的事件。
opacity:0 元素上面绑定的事件时可以触发的。
过度动画。
transition对于display是无效的。
transition对于visibility是无效的。
transition对于opacity是有效的。