什么是mvvm?
MVVM是Model-View-ViewModel的缩写。mvvm是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
mvvm和mvc区别?
mvc和mvvm其实区别并不大。都是一种设计思想。主要就是mvc中Controller演变成mvvm中的viewModel。mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到View 。
计算属性和watch的区别及使用场景
computed计算属性是用来声明式的描述一个值依赖了其它的值。当你在模板里把数据绑定到一个计算属性上时,Vue 会在其依赖的任何值导致该计算属性改变时更新 DOM。这个功能非常强大,它可以让你的代码更加声明式、数据驱动并且易于维护。 // 计算属性computed,计算的是Name依赖的值,它不能计算在data中已经定义过的变量。
watch监听的是你定义的变量,当你定义的变量的值发生变化时,调用对应的方法。// 当num的值发生变化时,就会调用num的方法,方法里面的形参对应的是num的新值和旧值
- 一个数据属性在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性。
- watch函数适用于,当数据发生变化时,执行异步操作或较大开销操作的情况。
生命周期
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
-
创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
-
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
-
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
-
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
作用
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
第一次页面加载会触发哪几个钩子?
会触发下面这几个beforeCreate、created、beforeMount、mounted 。
DOM 渲染在哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了
vue的指令
- v-if指令
- v-show指令
- v-else指令
- v-for指令
- v-bind指令
- v-on指令
- v-model指令
- v-text读取文本不能读取html标签
- v-html 能读取html标签
双向绑定 – Object.defineProperty()
vue实现数据双向绑定主要是:
采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue追踪依赖,在属性被访问和修改时通知变化。
Proxy 相比于 defineProperty 的优势
- 数组变化也能监听到
- 不需要深度遍历监听
let data = { a: 1 }
let reactiveData = new Proxy(data, {
get: function(target, name){
// ...
},
// ...
})
vue路由hash模式与history模式的区别
- hash模式
- 路由的哈希模式其实是利用了window可以监听onhashchange事件,也就是说你的url中的哈希值(#后面的值)如果有变化,前端是可以做到监听并做一些响应(搞点事情),这么一来,即使前端并没有发起http请求他也能够找到对应页面的代码块进行按需加载。
后来人们给他起了一个霸气的名字叫前端路由,成为了单页应用标配。
- 路由的哈希模式其实是利用了window可以监听onhashchange事件,也就是说你的url中的哈希值(#后面的值)如果有变化,前端是可以做到监听并做一些响应(搞点事情),这么一来,即使前端并没有发起http请求他也能够找到对应页面的代码块进行按需加载。
- history模式
- 我们先介绍一下H5新推出的两个神器:pushState与replaceState
具体自行百度,简而言之,这两个神器的作用就是可以将url替换并且不刷新页面,好比挂羊头卖狗肉,http并没有去请求服务器该路径下的资源,一旦刷新就会暴露这个实际不存在的“羊头”,显示404。
- 我们先介绍一下H5新推出的两个神器:pushState与replaceState
那么如何去解决history模式下刷新报404的弊端呢,这就需要服务器端做点手脚,将不存在的路径请求重定向到入口文件(index.html),前后端联手,齐心协力做好“挂羊头卖狗肉”的完美特效。
总之,pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。
history模式下,build之后本地 index.html 打开是无效的。
hash模式下,build之后本地 index.html 打开正常!
大牛回答:hash模式url里面永远带着#号,我们在开发当中默认使用这个模式。那么什么时候要用history模式呢?如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url适合推广宣传。当然其功能也有区别,比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人员配合让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok啦。
$route 和 $router的区别
- $route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
- 而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等
路由跳转
- 编程式( js跳转)this.$router.push()
- 声明式(标签跳转)
<router-link to=""></router-link>
路由的懒加载
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const routes = [
{
path:'/',
component:resolve => require(['@/components/First'],resolve)
},
{
path:'/first',
component:resolve => require(['@/components/First'],resolve)
},
{
path:'/second',
component: resolve => require(['@/components/Second'],resolve)
}
]
//最后创建router 对路由进行管理,它是由构造函数 new vueRouter() 创建,接受routes 参数。
const router = new VueRouter({
routes
})
export default router;
按需加载会在页面第一次请求的时候,把相关路由组件块的js添加上;非按需加载则会把所有的路由组件块的js包打在一起。当业务包很大的时候建议用路由的按需加载(懒加载)。
路由的传参
<template>
<div>
<el-button type="primary" @click="pushTo1">按钮1</el-button>
<el-button type="primary" @click="pushTo2">按钮2</el-button>
<el-button type="primary" @click="pushTo3">按钮3</el-button>
</div>
</template>
// query 传参
<script>
export default {
name: 'officeHome',
data () {
return {};
},
methods: {
pushTo1 () {
this.$router.push({path: '/view/home', query: { username: 'liu', userId: 12 }});
},
pushTo2 () {
this.$router.push({name: 'home', query: { username: 'liu', userId: 12 }});
},
pushTo3 () {
// name 和 path 都有的情况下,以name为准
this.$router.push({name: 'home', path: '/view/home', query: { username: 'liu', userId: 12 }});
}
}
};
</script>
// params 传参
<script>
export default {
name: 'officeHome',
data () {
return {};
},
methods: {
pushTo1 () {
// 这种指定了路由的path,没有指定路由的name,会导致params失效
this.$router.push({path: '/view/home', params: { username: 'liu', userId: 12 }});
},
// 使用params传递参数,必须指定Vue组件路由的name属性
pushTo2 () {
this.$router.push({name: 'home', params: { username: 'liu', userId: 12 }});
},
pushTo3 () {
// name 和 path 都有的情况下,以name为准
this.$router.push({name: 'home', path: '/view/home', params: { username: 'liu', userId: 12 }});
}
}
};
</script>
vue-router解决的问题
- 监听URL的变化,并在变化前后执行相应的逻辑
- 不同的URL对应不同的组件
- 提供多种方式改变URL的API(URL的改变不能导致浏览器刷新)
v-if 和 v-show 的区别
- v-if按照条件是否渲染,v-show是display的block或none;
- v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
在Vue中使用插件的步骤
- 采用ES6的import … from …语法或CommonJS的require()方法引入插件
- 使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })
vue-cli 工程技术集合介绍
使用技术
- vue.js:vue-cli工程的核心,主要特点是 双向数据绑定 和 组件系统。
- vue-router:vue官方推荐使用的路由框架。
- vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护vue组件间共用的一些 变量 和 方法。
- axios( 或者 fetch 、ajax ):用于发起 GET 、或 POST 等 http请求,基于 Promise 设计。
- vuex等:一个专为vue设计的移动端UI组件库。
- 创建一个emit.js文件,用于vue事件机制的管理。
- webpack:模块加载和vue-cli工程打包器。
vuex
- state: 状态中心
- mutations: 更改状态
- actions: 异步更改状态
- getters: 获取状态
- modules: 将state分成多个modules,便于管理
vue-cli配置反向代理
vue-cli提供了配置反向代理的接口,即设置config/index.js中的proxyTable。配置如下:
dev: {
......
proxyTable: {
'/api':{ //将www.exaple.com印射为/apis
target:'https://www.exaple.com', //跨域地址
changeOrigin:true, //是否跨域
secure:false, //是否使用https
pathRewrite: {
'^/api': '/api' //匹配以/api为开头的请求地址,并使用/api替换
}
}
}
}
在组件中调用接口示例(axios):
import axios from 'axios'
axios({
method:'post',
url:'/api/login',
data:{
username:'qwe',
password:123456
}
}).then();
上述示例请求的地址会被解析为https://www.exaple.com/api/login
。如果proxyTable中pathRewrite配置为空,则请求的地址被解析为https://www.exaple.com/login
。
以上配置只是在开发环境(dev)中解决跨域。要解决生产环境的跨域问题,则在config/dev.env.js和config/prod.env.js里也就是开发/生产环境下分别配置一下请求的地址API_HOST,开发环境中我们用上面配置的代理地址api,生产环境下用正常的接口地址。配置代码如下:
module.exports = merge(prodEnv, {
NODE_ENV: '"development"', //开发环境
API_HOST:"/api/"
})
module.exports = {
NODE_ENV: '"production"', //生产环境
API_HOST:'"http://40.00.100.100:3002/"'
}
对反向代理解决跨域过程的理解
通过伪造请求使得http请求为同源的,然后将同源的请求发送到反向代理服务器上,由反向代理服务器去请求真正的url,这样就绕过直接请求真正的url导致跨域问题。
Vue 组件 data 为什么必须是函数
- 每个组件都是 Vue 的实例。
- 组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他
axios
get
this.$axios.get("http://www.wwtliu.com/sxtstu/blueberrypai/getIndexBanner.php",{
params:{
type:'参数'
}
})
.then(res=>{
consoe.log(res)
})
.catch(error=>{
console.log(error)
})
post
this.$axios.post("http://www.wwtliu.com/sxtstu/blueberrypai/getIndexBanner.php",{
typs:'参数'
}
)
.then(res=>{
console.log(res)
})
.catch(error=>{
console.log(error)
})
post请求注意:如果后台接收的数据格式是 form-data格式 : ?name=a&age=20 你需要进行转换
默认是 x-www-form-urlencoded格式: {name:‘a’,age:‘20’} 这种格式的
怎样转换 :node给我们提供了一个库 qs 我们先将qs引进来
import Qs from 'qs'
然后:Qs.stringify({转换参数})
this.$axios.post("http://www.wwtliu.com/sxtstu/blueberrypai/getIndexBanner.php",Qs.stringify(
{
typs:'参数'
}
))
.then(res=>{
this.msg=res.data.banner
})
.catch(error=>{
console.log(error)
});
axios封装
import axios from "axios";
export default {
ajaxGet (api, cb) {
axios.get(api)
.then(cb)
.catch(err => {
console.log(err);
})
},
ajaxPost (api, post, cb) {
axios.post(api, post)
.then(cb)
.catch(err => {
console.log(err);
})
},
}
// get请求
http.ajaxGet(url, res => {
console.log(res)
});
// post请求
http.ajaxPost(url, obj, res => {
console.log(res)
});
vue的SEO优化问题
常用解决方案有3种
- 页面预渲染
- 服务端渲染
- 路由采用h5 history模式
keep-alive
vue的内置组件。通过
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
使用场景
一般用于缓存路由组件
// 方式一:vue文件中
<keep-alive>
<router-view></router-view>
</keep-alive>
// 方式二:router.js中
{
path: '/as',
icon: 'earth',
title: '赠品管理',
name: 'as',
component: Main,
children: [{
path: 'a',
title: '赠品管理',
name: 'a',
component: () =>
import ('@/views/a/index.vue'),
meta:{
keepAlive:true
}
}]
}
// 可以使用v-if通过路由元信息判断缓存哪些路由
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!--这里是会被缓存的路由-->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!--因为用的是v-if 所以下面还要创建一个未缓存的路由视图出口-->
</router-view>
//router配置
new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: {
keepAlive: true // 需要被缓存
}
},
{
path: '/edit',
name: 'edit',
component: Edit,
meta: {
keepAlive: false // 不需要被缓存
}
}
]
});
属性
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
生命周期
- activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用
- deactivated:组件被停用(离开路由)时调用
注意:使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。