1.概念
1.1 对SPA单页面的理解,优缺点是什么?
signal page application
单页面应用,仅在页面初始化的时候加载相应的html
,css
和js
.一旦页面的加载完成,SPA
不会因为用户操作而进行页面的重新加载或跳转,取而代之的是利用路由机制实现html
内容的变换,UI
与用户交互,避免页面的重新加载.
优点:
- 用户体验好,快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重新渲染
- 相对服务器的压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
缺点:
- 初次加载耗时多:为实现单页面web应用功能及显示效果,需要在加载页面时候将js、css统一加载,部分页面按需加载
- SEO难度大:所有内容都在一个页面中动态切换显示
1.2 什么是MVVM?
Model-view-ViewModel (MVVM) 是一个软甲架构的设计模式.又微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动变成方式.
Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。
MVC:Model–View–Controller(MVC)模式.
MVVM:model view viewModel 视图模式。MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。
(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建。
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
var app = new Vue({
el: '#app',
data: { // 用于描述视图状态
message: 'Hello Vue!',
},
methods: { // 用于描述视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: '/your/server/data/api',
success(res){
vm.message = res;
}
});
}
})
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
1.3 Vue的优点
- 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;
- 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
- 双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
- 组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;
- 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
- 虚拟DOM:dom操作是非常耗费性能的, 不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;
- 运行速度更快:相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。
2. 基础知识
2.1 v-show 和 v-if 的区别?
v-show 通过display设置为none来控制元素的隐藏与现实,导尿管v-show为fasle时,将页面中已经渲染的 DOM 元素的 display 设置为 none,且是行内样式.用于频繁切换的场景
v-if 是真正的重新渲染,确保在每次切换的过程中条件块内的事件监听器和子组件都适当的销毁和创建,当v-if 为false 时,根本不会有元素渲染在页面中.用于不频繁切换的场景
2.2 computed ,methods, watch 的区别和运用场景?
computed : 是计算属性,依赖其他的属性值,用来计算复杂的逻辑运算,并且 computed 的值会有缓存,只有它依赖的属性值发生改变,下一次重新获取 computed的时候才会重新计算,
methods : methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例。
watch : 监听,用于监听数据的变化,其中可以监听的数据来源有三部分data,prop,computed
,两个参数,一个是新值.一个是旧值.
注意,不应该使用箭头函数来定义 watcher 函数 (例如 searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.updateAutocomplete 将是 undefined。
运用场景:
当我们需要数值计算,并且依赖于其他数据是,应该使用 computed ,因为可以利用 computed 的缓存特性,避免每次获取值时,都需要重新计算.
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
methods存储方法,computed用来存储需要处理的数据,methods每一次都会调用,computed只有依赖的属性值发生变化的时候才会重新计算.
2.3 请说出vue.cli项目中src目录每个文件夹和文件的用法?
- assets文件夹是放静态资源;
- components是放组件;
- router是定义路由相关的配置;
- app.vue是一个应用主组件;
- main.js是入口文件。
2.4 v-on可以监听多个方法吗?
答:可以,栗子:
<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">。
2.5 v-if和v-for的优先级
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。
2.6 assets和static的区别**
相同点:assets和static两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点
不相同点:
- assets中存放的静态资源文件在项目打包时,也就是运行npm run build时会将assets中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在static文件中跟着index.html一同上传至服务器。
- static中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是static中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于assets中打包后的文件提交较大点。在服务器中就会占据更大的空间。
建议:将项目中template需要的样式文件js文件等都可以放置在assets中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css等文件可以放置在static中,因为这些引入的第三方文件已经经过处理,我们不再需要处理,直接上传。
2.7 vue常用的修饰符
- .stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;
- .prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
- .capture:与事件冒泡的方向相反,事件捕获由外到内;
- .self:只会触发自己范围内的事件,不包含子元素;
- .once:只会触发一次。
3.组件
3.1 组价中的data为什么是一个函数?
-
首先,如果不是一个函数,Vue直接就会报错。
-
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
使用后会相互影响。
3.2 Vue组件间通信的方式?
- 父子组件通信
- props 和 $emit
父组件通过props向子组件传递数据
子组件通过事件向父组件发送消息
- ref和 $children/$parent
子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children或$refs
子组件访问父组件:使用$parent
this.$children是一个数组类型,它包含所有子组件对象。
- 非父子组件通信
- 事件总线
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
定义
Vue.prototype.$bus = new Vue()
发出事件$bus.$emit(‘事件名’,参数)
触发事件$bus.$on(‘事件名’)
- vuex
全局变量, 状态管理模式
3.3 对keep-alive的理解?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
- include - 字符串或正则表达,只有匹配的组件会被缓存
- exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
通过create生命周期函数来验证
4.生命周期
4.1谈谈你对 Vue 生命周期的理解?
- beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
- create:data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
- beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
- mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
- beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
- updated:页面显示的数据和data中的数据已经保持同步了,都是最新的
- beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
- destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。
4.2 Vue 的父组件和子组件生命周期钩子函数执行顺序?
渲染
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁
父beforeDestroy => 子beforeDestroy => 子destroy => 父destroy
4.3 父组件可以监听到子组件的生命周期吗?
- 钩子函数
<chile @hook:mounted = 'doSomethind'></child>
- $emit
4.4 在哪个生命周期内调用异步请求?
created/beforeMounted/mounted
4.5 请详细说下你对vue生命周期的理解?
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
- 创建前/后: 在beforeCreated阶段,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结构依然存在。
5.全家桶
5.1 vuex介绍
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
核心概念和方法
- State 单一状态树,包含了全部应用级状态,是唯一的数据源。 一般computed中计算,mapState
- Getter 从state中派生一些状态,根据依赖进行缓存,只有依赖变化才重新计算,一般computed,mapGetters
- Mutation 修改数据的方法 commit,methods, MapMutations
- Action 异步方法 dispatch,methods,mapActions
- Module 模块化
5.2 dispatch和commit的区别?
- commit => mutations 用来触发同步操作的方法
- dispatch => commit 用来触发异步操作的方法
6. vue路由
6.1 $route 和 $router的区别?
- $route获取的是当前处于活跃状态的路由
- $router是 router 实例通过 this.$router 访问路由器,相当于获取了整个路由文件,在$router.option.routes中,或查看到当前项目的整个路由结构 具有实例方法
6.2 vue-router 中常用的 hash 和 history 路由模式实现原理吗?(1)hash 模式的实现原理
(1)hash 模式的实现原理
- 早期的前端路由的实现就是基于 location.hash 来实现的。
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:
-
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
-
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
-
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
-
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
- HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:
history.pushState()
和history.repalceState()
。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
-
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
-
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
-
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
6.3 vue-router有哪几种导航钩子?
vue-router 的导航钩子,主要用来作用是拦截导航,让他完成跳转或取消。
有三种方式可以植入路由导航过程中:
-
全局的(
beforeEach,afterEach
) -
单个路由独享的(
beforeEnter
) -
组件级的(
beforeRouterEnter,beforeRouterLeave,beforeUpdate
)
1. 全局导航钩子
全局导航钩子主要有两种钩子:前置守卫、后置钩子,
注册一个全局前置守卫:
const router = new VueRouter({ ... });
router.beforeEach((to, from, next) => {
// do someting
});
这三个参数 to 、from 、next 分别的作用:
-
to: Route,代表要进入的目标,它是一个路由对象
-
from: Route,代表当前正要离开的路由,同样也是一个路由对象
-
next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
- next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的)
- next(false):这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址
- next(‘/’) 和 next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址
- next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调
对于全局后置钩子:
router.afterEach((to, from) => {
// do someting
});
不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身
- 路由独享的钩子
顾名思义,即单个路由独享的导航钩子,它是在路由配置上直接进行定义的:
cont router = new VueRouter({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
至于他的参数的使用,和全局前置守卫是一样的
- 组建内的导航钩子
组件内的导航钩子主要有这三种:-
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave。
他们是直接在路由组件内部直接进行定义的
我们看一下他的具体用法:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
需要注意是:
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this
但是并不意味着在 beforeRouteEnter 中无法访问组件实例,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了,如:
beforeRouteEnter(to, from, next) {
next (vm => {
// 这里通过 vm 来访问组件实例解决了没有 this 的问题
})
}
注意,仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调
最后是完整的导航解析流程:
- 导航被触发
- 在失活的组件里调用离开守卫
- 调用全局的 beforeEach 守卫
- 在重用的组件里调用 beforeRouteUpdate 守卫
- 在路由配置里调用 beforEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve 守卫
- 导航被确认
- 调用全局的 afterEach 钩子
- 触发 DOM 更新
- 在创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数
7. 原理
7.1 怎样理解Vue单向数据流?
基本概念 : 子组件更改 props 中的数据不会触发父组件数据的改变, 但是由于响应式原理,父组件数据的改变会导致子组件 props 中值的改变
目的:防止子组件意外的改变父组件中的状态,从而导致应用的数据流向难以理解.
结果 : 子组件的prop数据自动刷新,但是子组件内部不能改变prop的值
解决方案 : 只能通过自定义事件,父组件接受到事件后,有父组件修改数据,
有两种常见的视图改变props值的场景:
- 这个props用来传递一个初始值,这个子组件接下来希望将其作为一个本地的prop数据来使用.在这种情况下,最好定义一个本地的data属性将这个prop用作初始值.
props: ['num'],
data: function () {
return {
counter: this.num
}
}
- 这个props以一种原始的值传入且需要转换.在这种情况下,最后使用这个prop的值来定义一个计算属性.
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
7.2 直接给一个数组赋值,Vue能检测到数据变化吗?
由于JavaScript的限制,vue不能检测如下数据变化:
- 直接通过下标修改数组的值arr[index] = num;
- 修改数组的长度
所以修改数组的值可以可以通过同种方式实现
//方案一
//使用数组的splice方法修改
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
//方案二
//通过vue提供的set方法设置
Vue.set(this.arr,1,2)
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
第二个问题的解决方法
// Array.prototype.splice
vm.items.splice(newLength)
7.3 v-model的原理?
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
- v-bind绑定一个value属性
- v-on指令给当前元素绑定input事件
也就是说下面的代码:等同于下面的代码:
//以input表单元素为例
input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
上面的说法仅仅是针对与输入框,那么其他的表单元素呢,如单选,多选,下拉框等
v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
7.4 runtime-only 和 compiler+runtime的区别?
如果我们需要在客户端编译模板(例如,想template选项传入一个字符串,或者需要将模板中的非DIN的HTML挂载到一个元素),这是就需要带有编译器的版本,因为需要完整构建版本.
//这种情况需要编译器(compiler)
new Vue({
template : `<div>{{hello}}</div>`
})
//这种情况不需要
new Vue({
render(h) {
return h('div',this.hello);
}
})
具体二者之间的差别我们先来看看vue的运行过程
template->ast(abstract syntax tree)->render->virtual dom->UI
1、1. runtime-compiler:template->ast(abstract syntax tree)->render->virtual dom->UI
runtime-only:性能更高、代码量更少
runtime-only :render->virtual dom->UI
- Runtime-Compiler和Runtime-only的选择使用情况?
如果在之后的开发中,你依然使用template,就需要选择Runtime-Compiler
如果你之后的开发中,使用的是.vue文件夹开发,那么可以选择Runtime-only
6.5 render函数解析
render函数的基本用法:
function(createElement) {
//用法1
return createElement('标签',{标签的属性},[标签中的内容]);
return createElement('h2',{class:box},['Hello world]);
//用法2
//传入一个组件对象
return createElemen(cpn)
}
6.6 Vue中key的作用?
key让每个item元素有一个唯一标识的身份,可以使用下标值或者唯一的id值,主要是为了vue能够精准的追踪到每一个元素,高效的更新虚拟DOM,使用v-for遍历数据的时候需要加上key关键字.
6.7 Vue的响应式原理?
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
6.8 Vue检测数组或对象的变化的注意事项?
1. 数组
Vue不能检测以下数组的变动:
- 当你利用索引值直接设置一个数组项时,例如:vm.items[index] = newValue
- 当你修改数组的长度时,例如:vm.items.length = ewLength
一个例子:
let vm = new Vue({
data : {
items :[1,2,3,4],
}
})
//利用下标修改数组值
vm.items[0] = 5; //不是响应式的
//直接修改数组长度
vm.items.length = 6; //不是响应式的
第一个问题(不能通过下标值修改数组元素)的解决方案:
- 利用vue.set()
Vue.set(vm.items, indexOfItem, newValue)
- 利用数组的splice方法
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
第二个问题(不能直接改变数组长度)的解决方案
使用splice方法
vm.items.splice(newLength)
2. 对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:
Vue.set(vm.someObject, 'b', 2)
还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
6.9 Vue中双向数据绑定是如何实现的?
vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。