Vue

什么是MVVM

全称: Model-View-ViewModelModel 表示数据模型层。 view 表示视图层, ViewModelViewModel 层的桥梁,数据绑定到 viewModel 层并自动渲染到页面中,视图变化通知 viewModel 层更新数据。

MVVM模式的优点以及与MVC模式的区别

  • MVCController演变成MVVM中的ViewModel
  • MVVM通过数据来显示视图层而不是节点操作
  • MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

数据双向绑定

采用数据劫持和发布者-订阅者模式的方式,通过Object.defineProperty()劫持各个属性的gettersetter 方法,在数据变动时发布消息给订阅者,触发相应监听回调

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应监听回调

v-model中的实现原理

通过 input 元素的 value = this.name,绑定 input 事件 this.name = $event.target.value,data 更新触发 re-render

vue-loader是什么?使用它的用途有哪些?

vue 文件加载器, 将style/template/js 转换成js模块。js可以写es6style样式可以scsslesstemplate可以加jade

组件之间传值方式有哪些

  1. 父组件向子组件传递数据 通过props
  2. 子组件像父组件传递事件$emit方法(子通过 $on 绑父的事件,再通过 $emit 触发自己的事件(发布订阅))
  3. eventbus
  4. vuex

EventBus注册在全局上时,路由切换时会重复触发事件,如何解决呢

在有使用$on的组件中要在beforeDestroy钩子函数中用$off销毁

组件中的data为什么是函数

避免组件中的数据互相影响。同一个组件被复用多次会创建多个实例,如果 data 是一个对象的话,这些实例用的是同一个构造函数。为了保证组件的数据独立,要求每个组件都必须通过 data 函数返回一个对象作为组件的状态。

vue 中怎么重置 data

使用Object.assign()vm.$data可以获取当前状态下的data
vm.$options.data可以获取到组件初始化状态下的data
Object.assign(this.$data, this.$options.data())

vue computed 和 watch 的区别 使用场景

  1. computed:
    • 有缓存机制、依赖项发生改变重新计算
    • 不支持异步,当computed 内部有异步操作时、无法监听数据变化
    • 不需要在data里声明
    • 一个属性受多个属性影响时使用
    • 使用场景:购物车结算
  2. watch
    • 没有缓存机制,数据发生变化直接触发
    • 支持异步操作
    • 必须在data里声明
    • 使用场景:搜索、滚动锚点定位

watch属性

  • handle是你watch中需要具体执行的方法
  • immediate:立即执行
  • deep:开启深度监听

v-for和v-if为什么不能连用

v-for 会比 v-if 的优先级更高,连用的话会把 v-if 的每个元素都添加一下,造成性能问题。

使用v-for遍历对象时,是按什么顺序遍历的?如何保证顺序

Object.keys()的顺序的遍历,转成数组保证顺序

v-if 和 v-show 区别

  • v-if 如果条件不成立不会渲染当前指令所在节点的DOM元素(组件真正的渲染和销毁)
  • v-show 只是切换当前DOM的显示与隐藏
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用v-if 较好。2

组件的命名规范

给组件命名有两种方式,一种是使用链式命名my-component,一种是使用大驼峰命名MyComponent

  • 在字符串模板中<my-component></my-component><MyComponent></MyComponent>都可以使用
  • 在非字符串模板中最好使用<MyComponent></MyComponent>,因为要遵循W3C规范中的自定义组件名

组件的name选项有什么作用

  • DOM 做递归组件时需要调用自身name
  • is特殊特性和component内置组件标签时使用
  • keep-alive内置组件标签中includeexclude属性中使用(项目使用 keep-alive 时,可搭配组件 name 进行缓存过滤)
  • vue-devtools 调试工具里显示的组见名称是由vue中组件name决定的

组件的插槽功能

  1. 插槽的写法
<myComponent>
    通过插槽传进去的内容
</myComponent>
<template>
    <div>
        <h1>我的组件</h1>
        <p>
            <slot></slot>
        </p>
    </div>
</template>

页面渲染出:

我的组件
通过插槽传进去的内容

如果 <myComponent> 没有包含一个 <slot></slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃

  1. 设置插槽的默认内容
<slot>默认内容</slot>
  1. 多个插槽使用(具名插槽)
<template>
    <div>
        <h2>默认插槽</h2>
        <slot>默认内容</slot>
        <h2>头部插槽</h2>
        <slot name="header"></slot>
        <h2>侧边栏插槽</h2>
        <slot name="sider"></slot>
    </div>
</template>
<template>
    <div>
        <myComponent>
            我是默认插槽
            <template v-slot:header>我是头部插槽</template>
            <template #sider>
                我是侧边栏插槽
            </template>
        </myComponent>
    </div>
</template>

v-slot 指令的缩写是 #v-slot只能添加在<template></template><slot></slot>上面没有name属性,会自动渲染成<slot name="default"></slot>

也可以这么调用<template v-slot:default></template>

Vue中的key到底有什么用

key是为Vue中的vnode(虚拟节点)标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速,(需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点所以简单的说,key 的作用主要是为了高效的更新虚拟 DOM可以减少渲染次数,提高渲染性能)

Vue生命周期

  • beforeCreate:实例初始化之后,数据观测之前调用(可以进行一些数据、资源的请求)
  • created:实例创建完之后调用。实例完成:数据观测、属性和方法的运算、 watch/event 事件回调。无$el(无法获取到DOM)
  • beforeMount:在挂载之前调用,相关 render 函数首次被调用
  • mounted:实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate:数据更新前调用,发生在虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
  • beforeDestroy:实例销毁前调用,实例仍然可用
  • destroyed:实例销毁之后调用,调用后,Vue实例指示的所有东西都会解绑,所有事件监听器和所有子实例都会被移除(可以执行一些优化操作,清空计时器,解除绑定事件)

父子组件生命周期执行顺序

// 渲染
parent beforeCreate
parent created
parent beforeMount
sub beforeCreate
sub created
sub beforeMount
sub mounted
parent mounted

// 数据更新
parent beforeUpdate
sub beforeUpdate
sub updated
parent updated

// 销毁组件
parent beforeDestroy
sub beforeDestroy
sub destroyed
parent destroyed

注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

ajax请求一般放在mounted中,保证逻辑统一性,因为生命周期是同步执行的, ajax是异步执行的。服务端渲染 ssr 统一放在 created 中,因为服务端渲染不支持 mounted 方法。

$nextTick使用场景

获取更新之后的DOM,如:输入框显示隐藏,显示后聚焦(不使用$nextTick无法获取更新后的dom,导致无法聚焦)

<template>
  <div>
    <input type="button" value="改变文本" @click="change" />
    <p ref="myP">{{ str }}</p>
    <p>1111</p>
    <input ref="enter_input" type="text" v-show="showInput" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      str: "我之前很瘦的。",
      showInput: false,
    };
  },
  methods: {
    change() {
      this.str = "现在超胖了!";
      this.showInput = true;
      //示例2:input显示后聚焦--此时聚焦无效
      this.$refs.enter_input.focus();
      // 修改了数据直接获取dom -- 我之前很瘦的。
      console.log(this.$refs.myP.innerText);
      this.$nextTick(() => {
        // 获取更新之后的DOM
        console.log(this.$el.textContent);//获取的是所有的文本 现在超胖了! 1111
        console.log(this.$refs.myP.innerText, "获取更新之后的DOM");//现在超胖了!
        this.$refs.enter_input.focus(); //dom更新后聚焦有效
      });
    },
  },
};
</script>

<style lang="less" scoped>
</style>

Vue在created和mounted这两个生命周期中请求数据有什么区别呢

created中,页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态,DOM节点没出来,无法操作DOM节点。在mounted不会这样,比较好

Vue中能监听到数组变化的方法有哪些?为什么这些方法能监听到呢

  • push()pop()shift()unshift()
    splice()sort()reverse(),这些方法在Vue中被重新定义了,故可以监听到数组变化
  • filter()concat()slice(),这些方法会返回一个新数组,也可以监听到数组的变化

在Vue中那些数组变化无法监听,为什么,怎么解决

  • 利用索引直接设置一个数组项时
  • 修改数组的长度时
    • 第一个情况,利用已有索引直接设置一个数组项时Object.defineProperty()是可以监听到,利用不存在的索引直接设置一个数组项时Object.defineProperty()是不可以监听到,但是官方给出的解释是由于JavaScript的限制,Vue不能检测以上数组的变动,其实根本原因是性能问题,性能代价和获得的用户体验收益不成正比
    • 第二个情况,原因是Object.defineProperty()不能监听到数组的length属性
  • this.$set(this.items, indexOfItem, newValue)this.items.splice(indexOfItem, 1, newValue)来解决第一种情况
  • this.items.splice(newLength)来解决第二种情况

更新数据视图未更新

值变了但是视图没有更新,赋值语句加上延迟或可解决问题



 
 
 



watch:{
    num(v){
        setTimeout(() => {
            this.$set(this, 'num', 5)
        }, 1)
    }
}

在Vue中那些对象变化无法监听,为什么,怎么解决

  • 对象属性的添加
  • 对象属性的删除

因为Vue是通过Object.defineProperty来将对象的key转成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性,所以才会导致上面对象变化无法监听

  • this.$set(this.obj,"key","newValue")来解决第一种情况
  • Object.assign来解决第二种情况

删除对象用delete和Vue.delete有什么区别

  • delete:只是被删除对象成员变为' 'undefined,其他元素键值不变
  • Vue.delete:直接删了对象成员,如果对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制

vue 首屏加载优化方案

  • 把不常改变的库放到index.html 中,通过cdn引入
  • vue路由的懒加载
  • 不生成map文件(找到config/index.js,修改为productionSourceMap: false
  • vue组件尽量不要全局引入
  • 使用更轻量级的工具库
  • 开启gzip压缩
  • 首页单独做服务端渲染

vue如何实现按需加载配合webpack设置

webpack中提供了require.ensure()来实现按需加载。以前引入路由是通过import这样的方式引入,改为const定义的方式进行引入

// 'foo' chunkName
const Foo = r => require.ensure([], () => r(require('@/components/Foo.vue')), 'foo')

require.ensurewebpack所独有的,可以被es6import取代

const Foo = () => import(/* webpackChunkName: "foo" */ './Foo.vue') //foo[hash].js

Vue性能优化

编码优化:

  • 拆分组件
  • key 保证唯一性
  • 路由懒加载、异步组件
  • 防抖节流

Vue加载性能优化:

  • 第三方模块按需导入( babel-plugin-component )
  • 图片懒加载

keep-alive的作用

keep-alive 可以实现组件的缓存,在 Vue 中,每次切换组件时,都会重新渲染。如果有多个组件切换(如tab切换),又想让它们保持原来的状态,避免重新渲染,这个时候就可以使用 keep-alivekeep-alive 可以使被包含的组件保留状态,或避免重新渲染。常用的2个属性 include/exclude ,2个生命周期 activateddeactivated

vue怎么实现页面的权限控制

利用 vue-routerbeforeEach 事件,可以在跳转页面前判断用户的权限(利用 cookietoken),是否能够进入此页面,如果不能则提示错误或重定向到其他页面

假设定义了一个数组a=[1,2,3],相应的,页面上显示的值为1,2,3,现设a[0]=5,页面上的值会变成5,2,3吗?为什么?

不会

因为 Vue 是使用 Object.defineProperty 来监听数值变化的,而直接修改数组的值的这种操作无法监听。

如果需要直接修改数组元素的值,可以使用 Vue.set

Vue.set(vm.items, indexOfItem, newValue)

如何将组件所有 props 传递给子组件

$props  <user v-bind="$props">

监听路由参数的变化

  • watch监听
// 监听当前路由发生变化的时候执行
watch: {
  $route(to, from){
    console.log(to.path)
    // 对路由变化做出响应
  }
}
  • 组件内导航钩子函数
beforeRouteUpdate(to, from, next){
  // to do somethings
}

vuex 中 action 和 mutation有何区别

  • mutation 是同步更新, $watch 严格模式下会报错
  • action 是异步操作,可以获取数据后调用 mutation 提交最终数据

如何让CSS只在当前组件中起作用

将当前组件的<style>修改为<style scoped>

vue中动态设置(创建)javascript的script 标签&js脚本内容&外部js详解

  • 动态通过src引入
// 创建script标签,引入外部文件
let script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'http://xxx.xxx.js'
document.getElementsByTagName('head')[0].appendChild(script)
//动态插入script,并在加载完成后执行callback
script.onload=()=>{
          //do something(methods || callback)
      }
  • 创建script标签并写入脚本
let script = document.createElement('script')
script.type = 'text/javascript'
script.text = `
	console.log(111)
`
document.getElementsByTagName('head')[0].appendChild(script)

Vue怎么定义全局方法

  • 挂载在Vueprototype
// base.js
const install = function (Vue, opts) {
    Vue.prototype.demo = function () {
        console.log('我已经在Vue原型链上')
    }
}
export default {
    install
}
//main.js
//注册全局函数
import base from 'service/base';
Vue.use(base);
  • 利用全局混入mixin
  • this.$root.$on绑定方法,用this.$root.$off解绑方法,用this.$root.$emit全局调用
this.$root.$on('demo',function(){
    console.log('test');
})
this.$root.$emit('demo')this.$root.$off('demo')

混入(mixin)

  • 全局混入在项目中怎么用
    import Vue from 'vue';
    import mixins from './mixins';
    Vue.mixin(mixins);

之后,全局混入可以写在mixins文件夹中index.js中,全局混入会影响到每一个之后创建的 Vue 实例(组件)

  • 局部混入在项目中怎么用

局部混入的注册,在mixins文件中创建一个a_mixin.js文件,然后在a.vue文件中写入

<script>
    import aMixin from 'mixins/a_mixin'
    export default{
        mixins:[aMixin],
    }
</script>

局部混入只会影响a.vue文件中创建的Vue实例,不会影响到其子组件创建的Vue实例

  • 组件的选项和混入的选项是怎么合并的
    • 数据对象【data选项】,在内部进行递归合并,并在发生冲突时以组件数据优先
    • 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
    • watch对象合并时,相同的key合成一个对象,且混入监听在组件监听之前调用
    • 值为对象的选项【filters选项、computed选项、methods选项、components选项、directives选项】将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对

过滤器

  • 用一个局部过滤器过滤器实现人民币汇率转换功能,支持美元(0.15汇率)、英镑(0.12汇率)
<template>
    <div>
        <input v-model="money" type="number" />人民币
        <div><span>{{money | moneyFilter(0.15)}}</span>美元</div>
        <div><span>{{money | moneyFilter(0.12)}}</span>英镑</div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                money: 1
            };
        },
        filters: {
            moneyFilter: function(val, ratio) {
                return Number(val * ratio).toFixed(2);
            }
        }
    };
</script>
  • 一个插值可以连续使用两个过滤器吗

可以

{{ message | filterA | filterB }}
  • 过滤器除了在插值上使用,还可以用在那个地方

还可以v-bind 表达式 上,如:<div :id="rawId | formatId"></div>

  • 过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数

<template></template>有什么用

当做一个不可见的包裹元素,减少不必要的DOM元素,整个结构会更加清晰

Vue组件里写的原生addEventListeners监听事件,要手动去销毁吗?为什么

要,不然会造成多次绑定和内存泄露

window.addEventListener('resize',this.computeOffset,false);
window.removeEventListener('resize',this.computeOffset,false);

Vue组件里的定时器要怎么销毁

  • 如果页面上有很多定时器,可以在data选项中创建一个对象timer,给每个定时器取个名字一一映射在对象timer中

beforeDestroy构造函数中for(let k in this.timer){clearInterval(k)}

  • 如果页面只有单个定时器,可以这么做
const timer = setInterval(() =>{}, 500);
this.$once('hook:beforeDestroy', () => {
   clearInterval(timer);
})

Vue怎么改变插入模板的分隔符

delimiters选项,其默认是[", "]

// 将分隔符变成ES6模板字符串的风格
new Vue({
  delimiters: ['${', '}']
})

Vue变量名如果以_、$开头的属性会发生什么问题?怎么访问到它们的值

_$ 开头的属性不会被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突,你可以使用例如 vm.$data._property 的方式访问这些属性

怎么修改Vue项目打包后生成文件路径

  • Vue CLI2中修改config/index.js文件中的build.assetsPublicPath的值
  • Vue CLI3中配置publicPath的值

怎么解决Vue项目打包后静态资源图片失效的问题

在项目中一般通过配置alias路径别名的方式解决,下面是Vue CLI3的配置

configureWebpack: {
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
            'assets': resolve('src/assets'),
            'css': resolve('src/assets/css'),
            'images': resolve('src/assets/images'),
        }
    },
}

怎么解决Vue中动态设置img的src不生效的问题

因为动态添加src被当做静态资源处理了,没有进行编译,所以要加上require

<template>
    <img class="logo" :src="logo" alt="公司logo">
</template>
<script>
export default {
    data() {
        return {
            logo:require("assets/images/logo.png"),
        };
    }
};
</script>

在Vue项目中如何引入第三方库(比如jQuery)?有哪些方法可以做到

  • 先在主入口页面index.html中用script标签引入<script src="./static/jquery-1.12.4.js"></script>,如果你的项目中有用ESLint检测,会报'$' is not defined,要在文件中加上/* eslint-disable */
  • 先在主入口页面index.html中用script标签引入<script src="./static/jquery-1.12.4.js"></script>,然后在webpack中配置一个externals,即可在项目中使用
externals: {
    'jquery': 'jQuery'
}
  • 先在webpack中配置alias,最后在main.js中用import $ from 'jquery',即可在项目中使用
resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
        '@': resolve('src'),
        'jquery': resolve('static/jquery-1.12.4.js')
    }
}
  • webpack中新增一个plugins,即可在项目中使用
plugins: [
         new webpack.ProvidePlugin({
             $:"jquery",
             jQuery:"jquery",
             "windows.jQuery":"jquery"
         })
     ]

怎么动态绑定Class和Style

<template>
  <div>
  <!--第一种对象语法 -->
  <div class="test" :class="{
  	active: actived ,
  	'active-click': clicked && actived
  }"></div>
  <!-- 第二种数组语法 -->
  <div class="test" :class="[
  	actived? activeClass : '', 
  	clicked && actived ? activeClickClass : ''
  ]"></div>
  <!-- 第三种对象和数组混合 -->
  <div :class="[
  	testClass , 
  	{active: actived} , 
   	{'active-click': clicked && actived}
  ]"></div>
  <!-- 第四种对象和计算属性(推荐) -->
  <div :class="classObject"></div>
  </div>
</template>
<script>
    export default {
        data() {
            return {
                actived: true,
                clicked: true,
                testClass: 'test',
                activeClass: 'active',
                activeClickClass: 'active-click',
            }
        },
        computed: {
            classObject: function() {
                return {
                    test: true,
                    active: this.actived,
                    'active-click': this.actived && this.clicked,
                }
            }
        },
    }
</script>

assets与static的区别

  • 相同点:
    • 文件夹中的资源在html中使用都是可以的
  • 不同点:
    • 使用assets下面的资源,在js中使用的话,路径要经过webpack中的file-loader编译,路径不能直接写
    • static:

    该目录下的文件是不会被wabpack处理的,它们会被直接复制到最终的打包目录下面(默认是 dist/static ),且必须使用绝对路径来引用这些文件
    这是通过在 config.js 文件中的 build.assetsPublicPathbuild.assetsSubDirectory 链接来确定的
    注意:任何放在 static 中的文件需要以绝对路径的形式引用:/static/[filename]

    • assets:

    assets 中的文件会经过 webpack 打包,重新编译
    static中建议放一些外部第三方,自己的文件放在assets,别人的放在static
    若把图片放在assetsstatic中,html页面中都可以使用,但是在动态绑定中,assets路径的图片会加载失败,因为webpack使用的是commenJS规范,必须使用require才可以

//   HTML  结构
<div class="myDemo">
    //直接显示文件内容
    <h5>直接路径</h5>
    <img src="../assets/logo.png" title="assets中的图片">
    <img src="/static/logo.png" title="static中的图片">
    //动态显示文件内容
    <h5>动态路径</h5>
    <img :src="asetUrl" title="assets中的图片">
    <img :src="sticUrl" title="static中的图片">
</div>
//JS  
export default {
    name: 'myDemo',
    data (){
      return {
        asetUrl: require('../assets/logo.png'),
        sticUrl: '/static/logo.png'
    }
}

总结:

assets里的会被打包编译,static不会。static放别人家的,assets放自己写的