-
vue常见面试题
2019-11-27 22:29:05VUE常见面试题 vue的双向数据绑定原理 vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现 数据劫持的实现方式Object.defineProperty(),该方法会直接在一个对象上定义一个新属性,或者修改一个对象的...VUE常见面试题
vue的双向数据绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现
数据劫持的实现方式Object.defineProperty(),该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象,语法Object.defineProperty(obj,prop,descriptor)
当你把一个普通的js对象传入Vue实例作为data选项,Vue将遍历此对象的所有属性,并使用Object.defineProperty()把这些属性全部转换为getter/setter
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图
3.实现一个解析器Complie,可以扫描并解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器解决跨域问题
jsonp
api/user,js
router.get(’/’, function(req, res ,next) {
let _callback = req.query.callback;
sql.find(User, {}, { _id: 0 }).then(data => {
if (_callback) {
res.type(‘text/javascript’);
res.send(_callback + ‘(’ + JSON.stringify(data) + ‘)’);
} else {
res.json(data)
}
})
})
_ 后端代码 _
调用的地方这样写:$.ajax({ url: 'http://localhost:3000/api/user', dataType: 'jsonp', success: function(data) { console.log(data) } }) _前端代码_
cors
app.js里如下设置
var allowCrossDomain = function (req, res ,next) {
res.header(‘Access-Control-Allow-Origin’, ‘*’); //自定义中间件,设置跨域需要的响应头
next();
};
app.use(allowCrossDomain) //运用跨域的中间件父子组件的传值
父给子传值
父组件在调用子组件的地方
添加一个自定义的属性
属性的值就是要传递给子组件的值在子组件定义的地方 const Content = {}
添加一个选项 props const Content = { props: }
props的值可以为数组以及对象
如果是数组,元素则为父组件中自定义的属性名、
如果是number/boolean/变量,需要使用到绑定属性
如果是对象有两种写法:
1.验证传递数据的有效性
props: {
test: String,
count: Number,
flag: Boolean,
tip: String
}
2.可以设定属性的默认值
props: {
test: {
type:String,
default: '测试数据了"
}
}
在子组件中就可以通过 {{ test }} 得到父组件传递过来的数据子给父传值
父组件调用子组件的地方绑定一个自定义的事件,事件不要加()
<my-content @myevent=“getData”/>
在父组件选项methods中实现此事件,默认参数为你将从子组件得到的值
methods: {
getData(val) {
console.log(val)
}
}
在子组件中,可以是生命周期钩子函数,也可以是组件自己的事件触发父组件中的自定义事件
this.$emit('myevent",10000)兄弟组件之间的传值
const bus = new Vue()
const List = {
template: ‘#list’,
methods: {
add () {
bus.KaTeX parse error: Expected 'EOF', got '}' at position 26: …t-event', 1) }̲ } } const Cou…on(‘count-event’,(val)=> {
console.log(val)
this.num += val
})
}
} -
Vue常见面试题
2021-01-03 11:22:37下面来看常见的面试题: 1、v-for和v-if哪个优先级更高?如果两者同时出现,应该怎么优化得到更好的性能? 源码位置:src/compiler/codegen/index.js 官方说法: 永远不要把 v-if 和 v-for 同时用在同一个元素上。...首先,对于vue中的一些书写规范以及常见问题,建议大家优先阅读 vue风格指南;这里面有很多面试中的常见点。
下面来看常见的面试题:
1、v-for和v-if哪个优先级更高?如果两者同时出现,应该怎么优化得到更好的性能?
源码位置:src/compiler/codegen/index.js
官方说法:
永远不要把 v-if 和 v-for 同时用在同一个元素上。
一般我们在两种常见的情况下会倾向于这样做:- 为了过滤一个列表中的项目 (比如 v-for=“user in users” v-if=“user.isActive”)。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
- 为了避免渲染本应该被隐藏的列表 (比如 v-for=“user in users” v-if=“shouldShowUsers”)。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。
// 1-1代码: // 当v-if和v-for处于同一元素时, <ul> <li v-for="user in users" v-if="user.isActive" :key="user.id" > {{ user.name }} </li> </ul> // 以上代码运行结果如下js一样,每次循环进行if判断: this.users.map(function (user) { if (user.isActive) { return user.name } })
总结:
- 首先,源码中,代码执行优先判断了v-for,其次判断的是v-if,所以v-for的优先级比v-if高;官方更是明确说明v-for优先级高于v-if。
- 其次,v-for和v-if可以一起使用,并不会出现什么错误,一样可以渲染列表;只是因为v-for的优先级高,所以当这两个指令在同一个标签元素上面时,每次渲染都会先执行循环再判断条件,浪费了性能,如
1-1代码
。 - 为避免出现性能浪费,所以官方给出了以上两种解决方式;一种通过计算属性,先进行筛选,再渲染;另一种就是分开写在不同的元素上面。
详情参阅 vue风格指南:避免-v-if-和-v-for-用在一起必要
2、Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?
源码位置:src\core\instance\state.js - initData() 函数每次执行都会返回全新data对象实例
总结:
- 一个Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这样做不合理;
- 采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,是因为根实例只有一个,不需要担心这种情况。
3、你知道vue中key的作用和工作原理吗?说说你对它的理解。
源码位置:src\core\vdom\patch.js - updateChildren()
总结:
- key的作用主要是为了提高更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得这个patch过程更加高效,减少DOM操作量,提升性能。
- 另外,若不设置key还可能在列表更新时引发一些隐蔽的bug。
- vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
4、怎么理解vue中的diff算法?
源码分析1:必要性,lifecycle.js - mountComponent() 组件中可能存在很多个data中的key使用。 源码分析2:执行方式,patch.js - patchVnode() patchVnode是diff发生的地方。 源码分析3:高效性,patch.js - updateChildren() diff算法整体策略:深度优先、同层比较。
总结:- diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比,将变化的地方更新在真实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。
- vue2中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方。
- vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch。
- diff过程整体遵循深度优先,同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同,做4次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非常精准找到相同节点,因此整个patch过程非常高效。
5、谈一谈对vue组件化的理解
回答总体思路: 组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调vue中组件化的一些特点。 组件定义源码:src\core\global-api\assets.js vue-loader会编译template为render函数,最终导出的依然是组件配置对象。 组件化优点 :lifecycle.js - mountComponent() 组件、Watcher、渲染函数和更新函数之间的关系 组件化实现: 构造函数:src\core\global-api\extend.js 实例化及挂载:src\core\vdom\patch.js - createElm()
总结:
- 组件是独立和可复用的代码组织单元。组件系统是vue核心特性之一,它让开发者能使用小型、独立和通用可复用的组件构建大型应用。
- 组件化开发能大幅度提高应用开发效率、测试性、复用性等。
- 组件使用按分类有:页面组件、业务组件、通用组件。
- vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于VueComponent,扩展于Vue。
- vue中常见组件化技术有:属性prop、自定义事件、插槽等,它们主要用于组件通信、扩展等。
- 合理的划分组件,有助于提升应用性能。
- 组件应该是高内聚、低耦合的。
- 遵循单向数据流原则,父=>子。
6、谈一谈对vue设计原则的理解?
vue官网定义:
- 渐进式JavaScript框架
- 易用、灵活、高效
- 渐进式JavaScript框架:与其他大型框架不同的是,Vue被设计为自底层向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用程序提供驱动。
- 易用性:vue提供数据响应式、声明模板式语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心业务即可,只要会写js、html、css就能轻松编写vue应用。
- 灵活性:渐进式框架的最大优点就是灵活,如果应用足够小,我们可能仅需要vue核心特性即可完成功能;随着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管应用体积;而学习难度是一个逐渐增加的平和曲线。
- 高效性:超快的虚拟DOM和diff算法使我们的应用拥有最佳的性能表现。追求高效的过程还在继续,vue3中引入Proxy对数据响应式改进以及编译器中对于静态内容编译的改进都会让vue更加高效。
7、你了解哪些vue性能优化方法?
-
路由懒加载。
-
keep-alive缓存页面。
-
使用v-show复用DOM。
-
v-for遍历同时避免使用v-if。
-
长列表性能优化:
- 如果列表是纯粹的数据显示,不会有任何改变,就不需要做响应化;
- 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域内容;参考:vue-virtual-scroller、vue-virtual-scroll-list 。
-
事件的销毁:vue组件销毁时。会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件;使用beforeDestroy生命周期函数。
-
图片懒加载:图片过多,为了页面的加载速度,未出现在可视区域的图片先不做加载,滚动到可视区后再加载;参考:vue-lazyload。
-
第三方插件按需引入;像element-ui这样的第三方库可以按需引入避免体积过大。
-
无状态组件标记为函数式组件,给template标签添加属性functional。
-
子组件分割。
-
变量本地化。
8、vue中组件之间的通信方式
通信方式使用场景可以分为三类: 1. 父子组件通信 2. 兄弟组件通信 3. 跨层组件通讯
通信方式:
props
父子$emit/$on(事件总线$bus)
全局vuex
全局$parent/$children
父子$attrs/$listeners
跨层provide/inject
跨层
总结:
- 父组件通过props向子组件传递值,子组件通过
$emit
向父组件发送数据,父组件通过v-on/@
来触发接收数据方法。 - vue实例作为事件总线用来触发事件和监听事件,可以通过此种方式进行组件间通信,包括:父子组件通信、兄弟组件通信、跨层组件通信。
// 创建bus文件并引入 import bus from './bus' // 监听sendData事件 mounted(){ bus.$on('sendData',(val)=>{this.msg = val}) } // 触发sendData事件 methods:{ sendMsg(){ bus.$emit('sendData','传递的值') } }
- vuex:全局状态管理
核心概念:
state:vuex中的唯一数据源,获取多个state可以使用...mapState
。
getter:可以理解为计算属性,getter的返回值根据他的依赖缓存起来,依赖发生变化才重新计算,获取多个getter可以使用...mapGetters
mutation:mutation对象里面存储的都是同步函数,是进行state状态修改的地方;同步函数有两个参数,第一个是state,第二个是修改后的值。
action:action和mutation都是修改状态,不同之处在于:
(1) action不能直接修改数据,需要通过mutation间接修改数据;
(2) action可以包含异步操作,mutation只能进行同步操作;
(3) action中的回调函数第一个参数是context,一个与store实例具有相同属性方法的对象;
(4) action通过store.dispatch触发,mutation通过store.commit提交;
module:由于使用单一状态树,应用的所有状态集中到比较大的对象,当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决以上问题,vuex允许我们将store分割成模块,每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块从上至下进行同样方式分割。
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
$parent / $children与 ref
(1)$parent / $children
:访问父/子实例
(2)ref
:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果在子组件上使用,引用就指向组件实例
以上两种方式都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
<component-a ref="comA"></component-a> // 获取组件component-a的实例 const comA = this.$refs.comA;
-
$attrs/$listeners
只做数据传递,不做中间处理
(1)$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定(class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过v-bind="$attrs"
传入内部组件。通常配合 interitAttrs 选项一起使用。
(2)$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"
传入内部组件。简单来说,
$attrs
与$listeners
是两个对象,$attrs
里存放的是父组件中绑定的非 Props 属性,$listeners
里存放的是父组件中绑定的非原生事件。 -
provide/inject 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
注:provide和inject主要为高阶插件/组件库提供用例,能在业务中熟练运用,可以达到事半功 倍的效果!
9、vue为什么要求组件模版只能有一个根元素?
- new Vue({el:'#app'}) - 单文件组件中,template下的元素div,其实就是"树"状数据结构中的"根"。 - diff算法要求的,源码中,patch.js里patchVnode()。
总结:
(1) 实例化Vue时,我们会寻找一个带有id的元素当做挂载对象,这个元素就相当于“根节点”,如果有多个带id的元素,Vue如何确定应当挂载到哪个el上面?如下代码:<body> <div id='app'></div> </body> <script> var vm = new Vue({ el:'#app' }) </script>
(2) 组件模板中会要求template标签下有一个div元素,div元素就相当于“根”元素;
<template> <div> </div> </template>
而template这个标签有三个特性:
- 隐藏性:该标签不会显示在页面的任何地方,即便里面有多少内容,它永远都是隐藏的状态,设置了
dispaly:none
; - 任意性:该标签可以写在任意地方,甚至是head、body、script标签内;
- 无效性:该标签里的任何HTML内容都是无效的,不会起任何作用;只能使用innerHTML来获取到里面的内容;
一个单文件组件就是一个vue实例,如果template下有多个div,那么如何指定vue实例的根入口?为了让组件可以正常生成一个vue实例,这个div会自然处理成程序的入口,通过这个根节点,来递归遍历整个vue树下的节点,并处理为vdom,最后再渲染成真正的HTML,插入到正确的位置。
(3) 源码中diff算法要求每个单文件组件需要有一个唯一的根元素。
10、你知道nextTick的原理吗?
nextTick官方文档的解释,它可以在DOM更新完毕之后执行一个回调
总结:
- vue用异步队列的方式来控制DOM更新和nextTick回调先后执行;
- microtask因为其高优先级特性,能确保队列任务中的微任务在一次事件循环前被执行完;
- 因为兼容性问题,vue不得不做了microtask向macrotask(setTimeout)的降级方案;
-
vue 常见面试题
2020-12-29 16:42:48Vue 自动挡,一套工具链 单页面网站优缺点 不利于SEO 初次加载耗时相对较多 Vue 是渐进式框架的理解 Vue只提供两个最核心的组件系统和数据绑定,其他周边都是围绕vue.js 去开发的 什么是MVVM MVVM 是一种设计思想 M...React 手动挡,更加灵活,更偏向于心智模型
Vue 自动挡,一套工具链单页面网站优缺点
不利于
SEO
初次加载耗时相对较多Vue 是渐进式框架的理解
Vue只提供两个最核心的组件系统和数据绑定,其他周边都是围绕vue.js 去开发的
什么是MVVM
MVVM 是一种设计思想
- M-Model 代表数据模型
- V-View 代表UI组件
- VM-ViewModel 通过双向数据绑定把View和Model连接起来,View和Model之间的同步工作完全自动的,无需认为干涉,开发者只需要关注业务逻辑
Vue 的生命周期有哪些
- beforeCreate
- created 可以获取data值,并进行操作
- beforeMoundt
- mounted 发起ajax请求
- beforeUpdate
- updated
- beforeDestory
- destoryed 卸载所有监听事件
keep-alive 用于对组件进行缓存,节省性能
场景:滚动条位置,当前页面状态
生命周期:activated、deactivated
包括哪些组件和不包括哪些组件<keep-alive include="bookLists,bookLists"> <router-view></router-view> </keep-alive> <keep-alive exclude="indexLists"> <router-view></router-view> </keep-alive>
利用meta属性
export default[ { path:'/', name:'home', components:Home, meta:{ keepAlive:true //需要被缓存的组件 }, { path:'/book', name:'book', components:Book, meta:{ keepAlive:false //不需要被缓存的组件 } ] // ==================================================== <keep-alive> <router-view v-if="this.$route.meat.keepAlive"></router-view> <!--这里是会被缓存的组件--> </keep-alive> <keep-alive v-if="!this.$router.meta.keepAlive"></keep-alive> <!--这里是不会被缓存的组件-->
v-if 和 v-show 的区别
v-if 按条件渲染,v-show 相当于 display:none 和 display:block
常用修饰符
v-model.lazy 在@change 事件中,失去焦点,才会更新最新值
v-model.trim 去处前后空格
v-model.number 搭配type=‘number’ 使用事件修饰符:
@click.stop
@submit.prevent
@click.stop.prevent 串联修饰符
tips:v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
按键修饰符:
@keyup.enter
@keyup.alt.67 // alt+cv-on 监听多个方法
- @change=“vchange(),vchange2()”
- @mouseenter=“onEnter” @mouseleave=“onLeave” 或者 v-on="{mouseenter: onEnter,mouseleave: onLeave}"
vue key值的作用
以key来给每个节点做一个唯一标识,有key的话就可以走diff算法,高效的复用旧的节点,减少创建dom的开销
如何使用 $event 对象
@click=“getRvent($event)”
在函数中传递参数 e 即可$nextick 的使用
nextick
Vue 的data为什么必须是函数
因为每个实例都要有自己的域,跟vue本身设计无关,更贴切应该叫 setData
v-for 和 v-if
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,
最佳实践:data: { numbers: [ 1, 2, 3, 4, 5 ] }, // 先过滤好if computed: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } }
子组件触发父组件的方法
- 子组件中调用 this.parent 看到的是一个 Proxy 对象
- 一般采用这种方法,React中一般以props传递
// 父组件中 <HelloWorld msg="Hello" @a="a"/> // 子组件中 this.$emit('a')
- 直接将方法当做props传递,子组件调用
// 父组件中 <HelloWorld msg="Hello" :a="a"/> // 子组件中 props: { a: { type: Function, default: null } }, methods: { childMethod() { if (this.a) { this.a(); } } }
父组件调用子组件的方法
this.$refs.change.hide();
vue 如何编写可复用的组件
定义 options,event,slot
单个插槽
/// App.vue <SlotComponent> <div>123</div> </SlotComponent> /// SlotComponent.vue <template> <div class="frame"> <slot></slot> </div> </template>
多个插槽
/// App.vue <SlotComponent> <div>123</div> <template #body> <p>大家加油!</p> </template> </SlotComponent> /// SlotComponent.vue <template> <div class="frame"> <slot></slot> <slot name="body"></slot> </div> </template>
Vue 改变对象 BUFFIX
vue2
1:this.$set(this.arr,1,2)
2:自己实现浅拷贝
vue3 不需要做特殊处理
解决加载中花括号的问题
//css样式 [v-cloak] { display: none; } //html代码 <div id="app" v-cloak> <ul> <li v-for="item in tabs">{{item.text}}</li> </ul> </div>
v-for 产生的列表实现active的切换
<li v-for="(info,index) in list" :key="info.id" @click="select(index)" v-bind:class="{'active':info.active}">{{info.name}}</li>
vue3已废弃 filter,用 computed代替
vue计算属性的缓存和方法调用的有什么区别?
如果一个开销较大的计算希望得到依赖缓存,则用computed,如果不是则使用methods
vue router
监听路由参数的变化
watch: { '$route' (to, from) { // 对路由变化作出响应... } }
动态加载路由
const Index = () => import(/* webpackChunkName: "group-home" */ '@/views/index') const routers = [ { path: '/', name: 'index', component: Index } ]
this.router
$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。常用 router 导航钩子
全局:
afterEach 和 beforeEach
独享:cont router = new VueRouter({ routes: [ { path: '/file', component: File, beforeEnter: (to, from ,next) => { // do someting } } ] });
-
VUE常见面试题
2020-09-05 23:22:16vue的两个核心 组件系统、数据驱动 什么是双向数据绑定? v-model,数据发生变化,同步视图,视图发生变化,同步数据 什么是单向数据流? 在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许...vue的两个核心
组件系统、数据驱动
什么是双向数据绑定?
v-model,数据发生变化,同步视图,视图发生变化,同步数据
什么是单向数据流?
在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
MVVM的设计思想的优势?
- 双向绑定技术,当Model变化时,View也会自动变化,view发生更新,model也跟着同步
- 我们减少了dom的操作,因为我们只需要关注数据就可以
- mvvm的设计思想大大提高了代码的耦合性
事件传参
- 没有传递参数,事件函数的默认第一个参数是事件对象
- 如果传递了参数,事件函数就没有了默认参数,全部变为对应位置的实参的形参,就没有了事件对象
- 既有自己传的的参数,也有事件对象,通过$event传递事件对象,在事件函数内部通过通过对应位置的形参来接收事件对象,传递刀了event没有强制性的位置,但是建议放在最后
自定义指令:directive
为什么自定义指令?
vue提供的系统指令满足不了我们的需求,那么我们就需要自定义指令
通过Vue.directive进行自定义指令的定义
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
计算属性:computed
定义的时候是一个方法,使用的时候当作属性使用
只要return后面的数据发生变化,该计算属性就会重新计算
计算属性具有缓存功能
监听器:watch
watch侦听器如果监听的是一个对象,需要开启深度监听
watch:{ num:{ // 监听数据发生变化的处理函数 handler(newNum) { console.log(newNum) }, // 是否开启深度监听 deep: true } }
过滤器
<div id="app"> <div> {{ date | formatDate('-')}} </div> <div> {{ date | formatDate('/')}} </div> </div> <script src="vue.js"></script> <script> Vue.filter('formatDate',(data,line)=>{ console.log(data,line) const y = data.getFullYear() const m = (data.getMonth()+1).toString().padStart(2,0) const d = data.getDate().toString().padStart(2,0) return y+line+m+line+d }) new Vue({ el: '#app', data:{ date: new Date } }) </script>
生命周期函数
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
生命周函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
// 创建阶段 beforeCreate() { // 这个生命周函数,代表开始创建实例了 console.log('beforeCreate',this.num) }, created () { // 代表数据和方法已经初始化成功了,此处dom还没有挂载到页面上 console.log('created',this.num,this.$el) }, beforeMount () { // 挂在之前 console.log('beforeMount',this.$el) }, mounted () { // dom已经挂载了 console.log('mounted',this.$el) }, // 运行更新阶段 beforeUpdate () { // 数据更新,页面还没有同步 console.log('beforeUpdated',this.num,document.getElementById('app').innerHTML) }, updated () { // 数据更新,页面已经同步 console.log('updated',this.num,document.getElementById('app').innerHTML) }, // 销毁阶段 beforeDestroy () { // 销毁之前 console.log('beforeDestroy') }, destroyed () { // 已经销毁了 console.log('destroy') }
在vue中通过索引直接在修改数组中的某一项数据,页面是否更新?
在vue中对 对象新添加属性,页面是否更新?
不更新,如果想解决这个问题,vm.$set(vm.list, 1, ‘or’)或者Vue.set
但是vm.list[3].a = 456,通过索引修改某一项的对象内部的属性是没问题的
vue组件中的data为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
组件
如何创建一个全局组件
通过Vue.component来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过template配置组件的结构,data定义数据等等
如何创建一个局部组件
在组件内部通过components来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
如何定义局部自定义指令
在组件内部通过directives来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
如何定义局部过滤器
在组件内部通过filters来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
组件传值
父向子传值
- 父亲怎么传:通过属性绑定形式传
- 儿子怎么接收:通过props来接收
子向父传值
- 子怎么传:通过this.$emit触发一个自定义事件,并且发送一个值
- 父怎么接收:通过定义自定义事件的事件函数的形参来接收
兄弟组件传值
定义一个事件中心,或者是第三方
接收值的组件:通过该事件中心的$on来定义自定义事件的事件函数来接收值
eventBus.$on('getTab1',(data)=>{ console.log('接收tab1传递的值',data) })
另一个兄弟组件怎么传:通过事件中心的$emit触发对应的 刀了 on的事件,并且把值传递过去
eventBus.$emit('getTab1',this.num)
跨组件传值
Vue.component('my-sub1',{ template:'#my-sub1', data() { return { money: 10000000 } }, provide:{ money:1000 }, components:{ 'sub-a':{ template:'<div>子组件<sub-b></sub-b></div>', components:{ 'sub-b':{ template:'<div>子组件{{money}}</div>', inject:['money'] } } } } }) new Vue({ el: '#app' })
组件插槽
-
默认插槽:
- 在组件标签中间可以传递一些子节点
- 组件内部利用slot标签进行接收
-
具名插槽
-
在组件标签中间通过定义slot的名字传递子节点
<my-banner> <div slot="header"> 头部 </div> <div slot="footer"> 底部 </div> </my-banner>
-
组件内部利用slot的name进行对应接收
<template id="banner"> <div> <slot name="header"></slot> <slot name="footer"></slot> </div> </template>
-
-
作用域插槽
-
在组件内部定义数据,将数据传递给插槽的结构
-
通过给slot动态绑定属性
<template id="my-li"> <ul> <li v-for="item in arr"> <slot :row="item"></slot> </li> </ul> </template>
-
插槽内部:通过slot-scope=“scope”来接收
<my-li> <template slot-scope="scope"> <p>{{scope.row}}</p> </template> </my-li> <my-li> <template slot-scope="scope"> <a href="04-侦听器.html">{{scope.row}}</a> </template> </my-li>
-
Promise的使用
利用Promise处理异步解决回调地狱的问题
Promise的all的方法
Promise的race的方法
面试题:
现在有三个接口地址,需要三个接口地址请求完事之后进行下一步的逻辑处理(不一定按顺序请求完成)
// .then回调 axios.get('http://xxx').then(res=>{ console.log(res) axios.get('http://xxx').then(res=>{ console.log(res) axios.get('http://xxx').then(res=>{ console.log(res) }) }) }) // .then返回新的Promise继续调用.then axios.get('http://xxx').then(res=>{ return axios.get('http://xxx') }).then(res=>{ return axios.get('http://xxx') }).then(res=>{ console.log('三个请求完事') }) // async await const asyncHandle = async function(){ const res1 = await axios.get('http://xxx1') const res2 = await axios.get('http://xxx') const res3 = await axios.get('http://xxx') console.log(res1,res2,res3) } asyncHandle() // Promise all方法 const getComments = new Promise(()=>{ axios.get('http://xxx') }) Promise.all([ axios.get('http://xxx'), axios.get('http://xxx'), axios.get('http://xxx') ]).then(res=>{ console.log(res) })
axios拦截器
请求拦截
axios.interceptors.request.use
响应拦截
axios.interceptors.response.use
路由
什么是路由?
路由就是对应关系,组件和url地址,根据不同的地址显示不同的组件,路由也是实现spa(单页面应用程序)的主要核心,因为单页面应用程序,就是只有一个html,在这个html里面切换组件,根据url,例如地址为/home,在这个页面中就显示home组件
前端路由:url和组件
后端路由:根据不同的地址请求不同的接口
在vue中路由传参
-
params传参
-
在跳转的时候可以通过/home/10
-
路由规则:
new VueRouter({ routes: [ { path: '/home/:id', component: Home } ] })
-
组件内部怎么来接收参数
this.$route.params.id
-
-
query传参
-
在跳转的时候可以通过/home?id=10
-
组件内部怎么来接收参数
this.$route.query.id
-
路由history模式注意的问题
嵌套路由
编程式导航
路由钩子
全局钩子:都会对所有的路由进行拦截
beforeEach:进入之前
afterEach:已经进入了
路由独享钩子:可以针对某一个路由进行拦截,这个需要写在路由规则里
{ path: '/', name: 'home', beforeEnter: (to,from,next)=>{ console.log('即将进入home') }, component: Home }
组件内的守卫:
针对组件进行拦截
beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 next() }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` console.log('即将离开about') if(confirm('当前表单没有提交?确定要离开首页?')){ next() } }
vue脚手架的安装和使用
命令行方式
利用vue.config.js关闭esLint
ui界面方式
安装element-ui
- 安装vue-cli-plugin-element插件
使用element中select组件
-
注册组件
import Vue from 'vue' import { Button, Select } from 'element-ui' Vue.use(Button) Vue.use(Select)
-
使用组件
<el-select v-model="value" placeholder="请选择"> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select>
-
复制数据
options: [{ value: '选项1', label: '黄金糕' }, { value: '选项2', label: '双皮奶' }, { value: '选项3', label: '蚵仔煎' }, { value: '选项4', label: '龙须面' }, { value: '选项5', label: '北京烤鸭' }], value: ''
安装axios
- 安装依赖》运行依赖》axios
- 使用axios发送请求
- 利用Vue.prorotype挂载到原型上
vue动画
利用类名添加动画
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
-
首先给需要添加动画的元素用transition包裹起来
-
利用类名实现动画
.v-enter,.v-leave-to{ transform: translate(200px,0) } .v-enter-active,.v-leave-active{ transition: transform 1s; } .v-enter-to,.v-leave{ transform: translate(0,0) }
-
自定义动画类名
<transition name="box2"> <div class="box" v-show="flag"> </div> </transition>
.box2-enter,.box2-leave-to{ opacity: 0; } .box2-enter-active,.box2-leave-active{ transition: opacity 1s; } .box2-enter-to,.box2-leave{ opacity: 1; }
结合css动画库设置动画
-
引入css动画库
-
通过enter-active-class、leave-active-class设置动画
<transition enter-active-class="animated bounceIn" leave-active-class="animated bounceOut" > <div class="box" v-show="flag"> </div> </transition>
加深面试题
虚拟dom
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1p9mus2-1599319228627)(images/虚拟DOM.png)]
Diff算法
虚拟dom高效更新执行过程
- 初次渲染时,会根据model数据创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面
- 当数据变化后,会重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
- 什么是虚拟dom:用js对象来表示页面上dom元素的的样式体现
- 虚拟dom的作用:高效更新页面,还有就是让react脱离了浏览器的概念
- 怎么来高效更新页面的:就是在第一次渲染页面的时候生成一份初始的虚拟dom树,然后当数据改变之后,再生成一份虚拟dom树,然后根据新旧dom树进行对比,对比完成之后,把有区别的进行更新
- diff算法的作用就是:新旧虚拟dom树在对比的时候就是通过diff算法来对比的
- diff算法又是怎么去对比的:tree diff、component diff、element diff
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zy4YhINU-1599319228662)(images/diff算法.png)]
面试语术:
你知道虚拟dom吗?简单又谈一下?
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容,虚拟dom可以实现高效更新,(后面如果自己能说一气说出来)
如何实现高效更新的?
利用新旧虚拟dom树进行对比,从而进行局部进行更新
如何进行新旧dom树进行对比?
利用diff算法,主要是tree diff树对比,component diff 组件对比,element diff 元素对比
加上一些其他话术
所以虚拟dom在前端中不管是vue、react等等都采用
axios配置代理服务器
什么是跨域?
在浏览器里面域名、端口、ip地址、协议,有任何一项不同,则跨域
A网站:http://localhost:8080/#/
B网站:http://localhost:3000/#/
处理跨域的方式?
JsonP(只能处理get请求)、cors(后端开启)、代理服务器
module.exports = { devServer: { host: 'localhost', port: 8080, proxy: { '/api': { target: 'http://localhost:3000',// 要跨域的域名 changeOrigin: true, // 是否开启跨域 }, '/get': { target: 'http://localhost:3000',// 要跨域的域名 changeOrigin: true, // 是否开启跨域 } } } }
小程序分包加载
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 12M
- 单个分包/主包大小不能超过 2M
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
代码演示,分包加载之前
pages: [ 'pages/tabs/home', 'pages/tabs/cates', 'pages/tabs/search', 'pages/tabs/cart', 'pages/tabs/my', 'pages/goods_list', 'pages/goods_detail/main', 'pages/order', 'pages/orderList' ]
抽离之后
pages: [ 'pages/tabs/home', 'pages/tabs/cates', 'pages/tabs/search', 'pages/tabs/cart', 'pages/tabs/my', 'pages/order', 'pages/orderList' ], subpackages: [ { name: 'goods', root: 'goods', pages: [ 'goods_list', 'goods_detail/main' ] } ]
图片或者图标等资源文件使用线上资源
v-if和v-for避免结合使用
因为v-for的优先级大于v-if,所以会先根据所有数据生成结构,然后在根据,v-if进行按需渲染
<div id="app"> <ul> <li v-for="item in arr" v-if="item%2===0">{{ item }}</li> </ul> </div> <script src="./vue.js"></script> <script> new Vue({ el: '#app', data: { arr: [ 1,2,3,4 ] } }) </script>
我们可以采用计算属性来避免此问题,这样的话,先通过计算属性,计算出要渲染的数据,然后直接循环计算属性即可
<div id="app"> <ul> <li v-for="item in newArr">{{ item }}</li> </ul> </div> <script src="./vue.js"></script> <script> new Vue({ el: '#app', data: { arr: [ 1,2,3,4 ] }, computed: { newArr() { return this.arr.filter(item=>{ return item%2===0 }) } } }) </script>
vue中route和router的区别
- route是当前路由信息,可以获取到当前路由地址参数等等
- router是全局路由(VueRouter)实例对象,可以通过router进行路由的跳转后退等等
如何封装一个插件
-
定义一个button组件,在index.js中引入并注册
-
在components中创建一个index.js
import sgButton from './button.vue' function install (Vue) { Vue.component(sgButton.name, sgButton) } export default { install }
-
在main.js中导入该js文件,当调用Vue.use方法,会默认调用内部的install方法
面试题
你有封装过插件吗? Vue.use的原理是什么?
当调用Vue.use方法,会默认调用内部的install方法,install这个方法默认的第一个形参就是Vue,这样的话我们就可以通过Vue注册一些全局组件,给Vue扩展一下方法。
vueI18n的使用
vue中实现语言切换的方式如何实现的
1.NPM 项目安装
cnpm i vue-i18n
2.使用方法
/* 国际化使用规则 */ import Vue from 'vue' import VueI18n from 'vue-i18n' Vue.use(VueI18n) <!-- 需要国际化的数据定义在此处 --> const messages = { en: { message: { hello: 'world hello' } }, zh: { message: { hello: '世界' } } } <!-- 使用i18n --> const i18n = new VueI18n({ locale: 'zh', messages }) export default { data () { return { hello: this.$t('message.hello') } }, i18n }
3.页面数据使用
<div id="#app"> <p>{{ $t("message.hello") }}</p> </div>
4.案例练习
定义基本结构
<div id="app"> <button>切换语言</button> <ul> <li>首页</li> <li>新闻</li> <li>关于</li> <li>概况</li> </ul> </div>
ul{ list-style: none; li{ width: 20%; height: 70px; line-height: 70px; background: green; color: #fff; float: left; margin-left: 2%; text-align: center; line-height: 70px; } }
定义语言包
zh.js
export default { nav: ['首页', '新闻', '概况', '关于'] }
en.js
export default { nav: ['home', 'news', 'gk', 'about'] }
在main.js中引入语言包
import zh from './i18n/zh.js' import en from './i18n/en.js' Vue.use(VueI18n) const i18n = new VueI18n({ locale: 'en', messages: { zh, en } })
数据渲染和切换
<template> <div id="app"> <button @click="changeLang">切换语言</button> <ul> <li v-for="(item, index) in $t('nav')" :key="index">{{ item }}</li> </ul> </div> </template> <script> export default { methods: { changeLang () { this.$i18n.locale = this.$i18n.locale === 'en' ? 'zh' : 'en' } } } </script>
keep-alive
vue-router中hash和history的区别
- history没有#/,会比hash好看
- history模式是采用的h5
history.pushState
API 来完成 URL 跳转而无须重新加载页面 - hash是利用location.hash进行跳转的
- hash是不需要后台配置支持的
- history不过这种模式要玩好,还需要后台配置支持
this.$nextTick()的作用
这个函数是可以等dom重新更新完成会调用
数据渲染完成,页面完成更新即调用this.$nextTik
当修改了数据,dom是异步同步的,所以,如果更改了数据,在修改数据下面重新操作dom会出问题,需要保证dom也更新完成才能操作。