Vue
什么是MVVM
全称: Model-View-ViewModel
, Model
表示数据模型层。 view
表示视图层, ViewModel
是 View
和 Model
层的桥梁,数据绑定到 viewModel
层并自动渲染到页面中,视图变化通知 viewModel 层更新数据。
MVVM模式的优点以及与MVC模式的区别
MVC
中Controller
演变成MVVM
中的ViewModel
MVVM
通过数据来显示视图层而不是节点操作MVVM
主要解决了MVC
中大量的dom
操作使页面渲染性能降低,加载速度变慢,影响用户体验
数据双向绑定
采用数据劫持和发布者-订阅者模式的方式,通过Object.defineProperty()
劫持各个属性的getter
、setter
方法,在数据变动时发布消息给订阅者,触发相应监听回调
vue
实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调
v-model中的实现原理
通过 input
元素的 value = this.name
,绑定 input
事件 this.name = $event.target.value
,data
更新触发 re-render
vue-loader是什么?使用它的用途有哪些?
vue
文件加载器, 将style/template/js
转换成js
模块。js
可以写es6
、style
样式可以scss
或less
、template
可以加jade
等
组件之间传值方式有哪些
- 父组件向子组件传递数据 通过
props
- 子组件像父组件传递事件
$emit
方法(子通过$on
绑父的事件,再通过 $emit 触发自己的事件(发布订阅)) eventbus
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 的区别 使用场景
computed
:- 有缓存机制、依赖项发生改变重新计算
- 不支持异步,当computed 内部有异步操作时、无法监听数据变化
- 不需要在data里声明
- 一个属性受多个属性影响时使用
- 使用场景:购物车结算
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
内置组件标签中include
和exclude
属性中使用(项目使用 keep-alive 时,可搭配组件 name 进行缓存过滤)vue-devtools
调试工具里显示的组见名称是由vue
中组件name
决定的
组件的插槽功能
- 插槽的写法
<myComponent>
通过插槽传进去的内容
</myComponent>
<template>
<div>
<h1>我的组件</h1>
<p>
<slot></slot>
</p>
</div>
</template>
页面渲染出:
我的组件
通过插槽传进去的内容
如果
<myComponent>
没有包含一个<slot></slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃
- 设置插槽的默认内容
<slot>默认内容</slot>
- 多个插槽使用(具名插槽)
<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.ensure
是webpack
所独有的,可以被es6
的import
取代
const Foo = () => import(/* webpackChunkName: "foo" */ './Foo.vue') //foo[hash].js
Vue性能优化
编码优化:
- 拆分组件
key
保证唯一性- 路由懒加载、异步组件
- 防抖节流
Vue加载性能优化:
- 第三方模块按需导入( babel-plugin-component )
- 图片懒加载
keep-alive的作用
keep-alive 可以实现组件的缓存,在 Vue
中,每次切换组件时,都会重新渲染。如果有多个组件切换(如tab
切换),又想让它们保持原来的状态,避免重新渲染,这个时候就可以使用 keep-alive
。 keep-alive
可以使被包含的组件保留状态,或避免重新渲染。常用的2个属性 include/exclude
,2个生命周期 activated
, deactivated
vue怎么实现页面的权限控制
利用 vue-router
的 beforeEach
事件,可以在跳转页面前判断用户的权限(利用 cookie
或 token
),是否能够进入此页面,如果不能则提示错误或重定向到其他页面
假设定义了一个数组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怎么定义全局方法
- 挂载在
Vue
的prototype
上
// 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.assetsPublicPath
和build.assetsSubDirectory
链接来确定的
注意:任何放在static
中的文件需要以绝对路径的形式引用:/static/[filename]
assets
:
assets
中的文件会经过webpack
打包,重新编译
static
中建议放一些外部第三方,自己的文件放在assets
,别人的放在static
中
若把图片放在assets
和static
中,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
放自己写的
← Array Vue-Router →