对MVVM的理解?
MVVM是Model-View-ViewModel的缩写,Model代表数据模型负责业务逻辑和数据封装,View代表UI组件负责界面和显示,ViewModel监听模型数据的改变和控制视图行为,处理用户交互,简单来说就是通过双向数据绑定把View层和Model层连接起来。在MVVM架构下,View和Model没有直接联系,而是通过ViewModel进行交互,我们只关注业务逻辑,不需要手动操作DOM,不需要关注View和Model的同步工作。
vue等单页面应用及优缺点
vue核心是一个响应的数据绑定系统,mvvm,数据驱动,组件化,轻量,简洁,高效,快速,模块友好。
缺点:不支持低版本浏览器,最低到IE9,不利于SEO的优化,首页加载时间较长,不可以使用浏览器的导航按钮需要自行实现前进后退。
什么是RESTful API,然后怎么使用?
RESTful是一个api的标准,无状态请求。请求的路由地址是固定的。 restful:给用户一个url,根据method不同在后端做不同处理:比如post 创建数据,get 获取数据,put和patch修改数据,delete删除数据
vue-cli中src目录下每个文件的用途?
1.vue-cli名字改为@vue/cli,所以全局安装了旧版的要通过npm install vue-cli -g
卸载。
安装新版vue-cli
npm install -g @vue/cli
2.创建一个项目 vue create hello-world
3.assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置;view视图;app.vue是一个应用主组件;main.js是入口文件
v-model的原理
复制代码
相当于
复制代码
v-if和v-show的区别
v-show只是在display: none和display: block之间切换,只需要切换CSS,DOM还是一直保留着,v-show在初始渲染时有更高的开销,但是切换开销很小,更适合频繁切换的场景
v-if涉及到vue底层的编译,当属性初始为false时组件不会被渲染,直到条件为true,并且切换条件时会触发销毁/挂载组件,切换时开销更高,更适合不经常切换的场景
route和router的区别
route是路由信息对象,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
router是路由实例对象,包括了路由的跳转方法,钩子函数。
怎么定义vue-router的动态路由?怎么获取传过来的值
在router目录下的index.js文件,对path属性加上:id,
使用router对象的params.id获取
active-class是哪个组件的属性?嵌套路由怎么定义
active-class是vue-router模块中router-link组件的属性
使用children定义嵌套路由
对keep-alive的了解
keep-alive是一个内置组件,可使被包含的组件保留状态或避免重新渲染,有include(包含的组件缓存)和exclude(排除的组件不缓存)两个属性。
vue常用修饰符
.prevent: 提交时间不再重载页面
.stop:阻止单击事件冒泡
.self:当事件发生在该元素本身而不是子元素的时候触发
.capture:事件侦听,事件发生的时候会调用
组件中data什么时候可以适用对象
组件复用时所有组件实例都会共享data,如果data是对象就会造成一个组件修改data以后会影响到其他所有组件,所以需要将data写成函数,每次用到就调用一次函数获得新的数据
当我们使用new Vue()的方式的时候,无论我们将data设置为对象还是函数都是可以的,因为new Vue()的方式是生成一个根组件,该组件不会复用,也就不存在共享data的情况
vue-cli如何新增自定义指令?
1.创建局部指令
directives:{ // 指令名称 dir1: { inserted(el){ // 第一个参数是当前使用指令的DOM el.style.width = '200px'; el.style.height = '200px'; el.style.background = '#000' } }}复制代码
2.全局指令
Vue.directive('dir2', { inserted(el){ console.log(el) }})复制代码
3.指令的使用
复制代码
vue如何自定义一个过滤器?
{ {msg | capitalize}}data(){ return{ msg: '' }},filters: { capitalize: function(value){ if(!value) return ""; value = value.toString(); return value.charAt(0).toUpperCase()+value.slice(1) }}复制代码
computed和watch区别
computed是计算属性,依赖其他属性计算值,并且computed的值有缓存,只有当计算值变化才会返回内容
watch监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用watch
另外computed和watch还支持对象的写法
data: { firstName: 'Chen', lastName: 'Miao', fullName: 'Chen Miao'},watch: { firstName: function(val){ this.fullName = val+ ' '+this.lastName }, lastName: function(val){ this.fullName = this.firstName+ ' '+val }},computed: { anoFullName: function(){ return this.firstName+' '+this.lastName }}复制代码
extend能做什么?
作用是扩展组件生成一个构造器,通常与$mount一起使用。
// 创建组件构造器let Component = Vue.extend({ template: 'test'})// 挂载到#app上new Component().$mount('#app')// 扩展已有组件let SuperComponent = Vue.extend(Component)new SuperComponent({ created(){ console.log(1) }})new SuperComponent().$mount('#app')复制代码
mixin和mixins区别
mixin用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
Vue.mixin({ beforeCreate(){ // 会影响到每个组件的beforeCreate钩子函数 }})复制代码
mixins最常用的扩展组件的方式。如果多个组件有相同的业务逻辑,就可将这些逻辑剥离出来,通过mixins混入代码。需要注意:mixins混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。
如何使用vue.nextTick()?
nextTick可以使我们在下次DOM更新循环结束之后执行延迟回调,用于获得更新后的DOM
data:function(){ return { message: '没有更新' }},methods: { updateMessage: function(){ this.message='更新完成' console.log(this.$el.textContent) // '没有更新' this.$nextTick(function(){ console.log(this.$el.textContent)// '更新完成' }) }}复制代码
transition 过渡的实现原理
复制代码
类名介绍:
- v-enter:定义进入过渡的开始状态
- v-enter-active:定义进入过渡生效时的状态
- v-enter:定义进入过渡的结束状态
- v-leave:定义离开过渡的开始状态
- v-leave-active:定义离开过渡生效时的状态
- v-leave-to:定义离开过渡的结束状态
简单说一下组件通信
父子通信
1.props和emit
父组件通过props传递数据给子组件,子组件通过emit发送事件传递给父组件。
// 父组件data(){ return{ toChild: '大儿子', fromChild: '' }},methods: { getFromChild(val){ this.fromChild=val }}// 子组件 { {data}}props:[data],methods: { toParent(){ this.$emit('send', '给父亲') }}复制代码
2.v-model
v-model其实是props,emit的语法糖,v-model默认会解析成名为value的prop和名为input的事件。
// 父组件{
{msg}}data(){ return{ msg:'model' }}// 子组件props: ['value'],methods: { toInput(e){ this.$emit('input', e.target.value) }}复制代码
3.在父组件使用$children
访问子组件,在子组件中使用$parent
访问父组件
// 父组件data(){ return { msg: '父组件数据' }},methods: { test(){ console.log('我是父组件的方法,被执行') }},mounted(){ console.log(this.$children[0].child_msg); // 执行子组件方法}// 子组件 { { $parent.msg}}data(){ return{ child_msg: '子组件数据' }},mounted(){ // 子组件执行父组件方法 this.$parent.test(); }复制代码
$listeners
和$attrs
$attrs
--继承所有父组件属性(除了prop传递的属性)
inheritAttrs--默认值true,继承所有父组件属性(除props),为true会将attrs中的属性当做html的data属性渲染到dom根节点上
$listeners
--属性,包含了作用在这个组件上所有监听器,v-on="$listeners"将所有事件监听器指向这个组件的某个特定子元素
// 父组件data(){ return { child1: 'childOne', child2: 'childTwo' }},methods: { onTest1(){ console.log('test1 running') }, onTest2(){ console.log('test2 running') }}// 子组件 {
{child1}}props: ['child1'],mounted(){ this.$emit('test1')}// 孙组件 {
{child2}{
{ $attrs}}props: ['child2'],mounted(){ this.$emit('test2')}复制代码
.sync方式
在vue1.x中是对prop进行双向绑定,在vue2只允许单向数据流,也是一个语法糖
// 父组件data(){ return { num: 0 }}// 子组件 adddata(){ return { counter: this.count }},props: ["count"],methods: { handleAdd(){ this.$emit('update:count', ++this.counter) }}复制代码
兄弟组件通信
可以通过查找父组件中的子组件实现, this.$parent.$children
在$children
中可以通过组件name查询到需要的组件实例,然后进行通信
跨多层次组件通信
可以使用provide/inject,虽然文档中不推荐直接使用在业务中。
假设有父组件A,然后有一个跨多层次的子组件B
// 父组件Aexport default{ provide: { data: 1 }}// 子组件Bexport default{ inject: ['data'], mounted(){ // 无论跨几层都能获取父组件的data属性 console.log(this.data); // 1 }}复制代码
任意组件
可以用Vuex或Event Bus解决
eventBus的使用
1.新建一个bus.js文件
import Vue from 'vue';export default new Vue();复制代码
2.使用它
添加import Bus from 'bus.js';export default{ methods: { addCart(event){ Bus.$emit('getTarget', event.target) } }}// 另一组件export default{ created(){ Bus.$on('getTarget', target =>{ console.log(target) }) }}复制代码
vue-router路由
单页面应用SPA的核心之一是:更新视图而不重新请求页面
普通路由
router.push('home')
router.push({path: 'home')
命名路由
const router=new VueRouter({ routes: [{ path: '/user', name: 'user', component: User }]})复制代码
复制代码
router.push({ name: 'user'})复制代码
动态路由匹配
{ { $route.params.id}}复制代码
const router = new VueRouter({ routes: [{ path: '/user/:id', component: User }]})复制代码
router.push({name:'user',params: {id: 123})
嵌套路由
{ { $route.params.id}}
复制代码
const router = new VueRouter({ routes: [{ path: '/user/:id', children: [{ // 当/user/:id path: '', component: UserHome },{ // 当/user/:id/profile path: 'profile', component: UserProfile }] }]})复制代码
参数的路由
router.push({path:'register',query:{plan:'private'})
编程式导航
router.push()
参数:
1.字符串
router.push('home')
2.对象
router.push({path: 'home'})
3.命名的路由
router.push({ name: 'user', params: { userId: 123 } })
4.带查询参数,变成/register?plan=private
router.push({ path: 'register', query: { plan: 'private' } })
router.replace()
不会向history添加新纪录,替换当前的history记录
点击<router-link :to="..." replace>
等同于调用router.replace(...)
router.go(n)
在历史记录中向前或向后退多少步
// 前进一步,等同history.forward()router.go(1)// 后退一步router.go(-1)// 前进3步记录router.go(3)复制代码
命名视图
复制代码
const router = new VueRouter({ routes: [{ path: '/', components: { default: Foo, a: Bar, b: Baz } }]})复制代码
vue路由的钩子函数
导航钩子主要用来拦截导航,让它完成跳转或取消。
router.beforEach((to,from,next) => {})
钩子是异步执行解析的,每个钩子方法接收三个参数:
to: Route即将进入的目标路由对象
from: Route当前导航正要离开的路由
next: Function,调用该方法来resolve这个钩子,执行效果看参数
-
next():进行下一个钩子
-
next(false):中断当前的导航
-
next('/')或next({path: '/'}):跳转到另一地址
请详细说下你对vue生命周期的理解
生命周期共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
创建前/后:在beforeCreated阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。created阶段,vue实例的数据对象data有了,el还没有。
载入前后:在beforeMount阶段,vue实例的el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染
更新前/后:当data变化时,会触发beforeUpdated和updated方法
销毁前/后:beforeDestroy在实例销毁前调用,实例仍然完全可用。destroy在实例销毁之后调用,调用后所有事件监听器会被移除,所有子实例也会被销毁。
生命周期的作用?
生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程中更容易形成好的逻辑。
vuex是什么?怎么使用?哪种功能场景使用它?
1.vuex是vue生态系统中的状态管理,用来管理vue中的所有组件状态。
2.使用
import Vue from vue;import Vuex from vuex;Vue.use(Vuex);const store = new Vuex.store({ state: { count: 0 }, getters: { addTen: state => { return state.count+10; } }, mutations: { increment(state){ state.count++ } }})复制代码
store.commit('increment');console.log(this.$store.state.count);复制代码
vuex中有
①state状态,还有mapState映射状态
computed: mapState({ count: state => state.count})复制代码
getter相当于store的计算属性,主要用来过滤一些数据
getters: { addTen: state => { return state.count+10; }}store.getters.addTen复制代码
mapGetters是将store中的getter映射到局部计算属性中
computed: { ...mapGetters([ 'addTen' ])}复制代码
③Mutation 改变vuex中store状态唯一方法就是提交mutation,可传入额外参数,是一个同步函数。 在组件中提交Mutation
import {mapMutations} from 'vuex'export default{ methods: { ...mapMutations([ 'increment' ]), ...mapMutaions({ add: 'increment' }) }}复制代码
④Action 类似mutation,但是是异步的,view层通过store.dispath分发action
actions:{ increment(context){ context.commit('increment') }}复制代码
⑤module 当应用比较复杂,可以将store分割成模块
3.常用的场景有:单页应用中,组件之间的状态,音乐播放、登录状态、加入购物车等等
谈谈你对vue的双向数据绑定原理的理解
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.definePorperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指定(解析{
{}}),最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新;视图交互变化input->数据model变更的双向绑定效果实现简单的双向绑定
复制代码
另一种方式实现vue的响应式原理
Proxy在目标对象之前架设一层“拦截”,外界对该对象的访问都必须先通过这层拦截,因此提供一种机制,可以对外界的访问进行过滤和改写。
Object.defineProperty的缺点:
1.不能检测到增加或删除的属性
2.数组方面的变动,如根据索引改变元素,以及直接改变数组长度时的变化,不能被检测到。
聊聊你对Vue.js的template编译的理解
先转化成AST树,再得到render函数返回VNode(Vue的虚拟DOM节点)
详细步骤:首先通过compile编译器把template编译出AST语法树,然后AST会经过generate(将AST语法树转化成render function字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有标签名,子节点,文本等