对 MVC,MVP,MVVM 的理解
MVC
阮一峰老师 MVC 图示
M 表示 Model , 专门用来处理数据模型.
V 表示 View, 专注页面布局和数据显示.
C 表示 Controller 专注于控制, 执行业务逻辑, 操作模型和视图.
MVC 的数据之间通信都是单向的
View(视图层) 传送指令到 Controller(控制层)
Controller(控制层) 完成业务逻辑后, 要求 Model(模型层) 改变状态
Model(模型层) 将新的数据发送到 View(视图层), 用户得到反馈
在 MVC 中, 虽然 View 与 Model 之间的耦合度非常小, 只需要 Model 修改的时候通知 View 发生改变即可, 但是它们之间还有有很重要的联系, 于是, 就有了 MVP
MVP
阮一峰老师 MVP 图示
M 表示 Model, 专门用来处理数据模型
V 表示 View, 专门用来处理用户视图
P 表示 Presenter, 用来处理业务逻辑, 在 MVC 的基础上, 修改了通信方向
MVP 中, View 和 Model 之间没有任何通信关系, 所有的通信和业务逻辑都放在 Presenter 层中
View(视图层) 发送指令到 Presenter 层,
Presenter 层 处理业务逻辑, 要求 Model(模型层) 改变状态
Model(模型层) 修改状态之后, 发送指令到 Presenter 层
Presenter 层通知 View(视图层) 做出改变
在 MVP 中, 所有的通信都是双向的, View 和 Model 不会直接发生通信, 都通过 Presenter 层进行传递
在 MVP 中, 所有的业务逻辑都写在 Presenter 层中, 会导致 Presenter 层过于臃肿
在 MVP 中, View 只负责显示视图, 不包含任何业务逻辑, 导致 View 层过薄, 不具备任何主动性
由于在 MVP 模式中, 所有的业务逻辑都放在 Presenter 层中, 必须通过 Presenter 层来修改 View 层的界面, 所以出现了 MVVM
MVVM
阮一峰老师 MVVM 图示
M 表示 Model, 专门用来处理数据模型
V 表示 View, 专门用来处理用户视图
VM 表示 ViewModel, 用来使的 View 视图层与 Model 层双向绑定, View 的任何变动都会通知 ViewModel, 而 Model 的任何变动, 也都会通知 ViewModel, 而不论哪一项发生改变, 都会使对应的视图 / 数据模型同步发生改变
在 MVVM 中, 与 MVP 一样, 所有的通信都是双向的, 数据与视图不直接发生依赖, 全部通过 VM 层来进行双向绑定
所有的业务逻辑都由 VM 来进行处理, 但是在 View 层和 Model 层修改都会通过 VM 来双向的绑定修改
谈谈对 $nextTick 的理解及使用场景
对 nextTick 的理解
vue 的视图更新是通过数据驱动的, 当数据发生改变的时候, 将当前的数据更改保存在队列中, 然后异步的更新视图
由于 Vue 的视图更新是异步的, 所以我们在修改完成数据之后, 不一定当前的 View 已经发生改变, 于是就有了 nextTick
nextTick 是在下次 DOM 更新循环结束之后执行延迟回调. 在修改数据之后立即使用这个方法, 获取更新后的 DOM.
nextTick 的使用场景
如果要在 created() 钩子函数中进行的 DOM 操作, 由于 created() 钩子函数中还未对 DOM 进行任何渲染, 所以无法直接操作, 需要通过 $nextTick() 来完成. 在 created() 钩子函数中进行的 DOM 操作, 不使用 $nextTick() 会报错
更新数据后, 想要使用 JS 对新的视图进行操作时
在使用某些第三方插件时 , 这些插件需要 dom 动态变化后重新应用该插件, 这 3 时候就需要使用 $nextTick() 来重新应用插件的方法
nextTick 的使用方法
使用 Vue.nextTick 全局方法
- // 修改数据
- vm.msg = 'Hello'
- // DOM 还没有更新
- Vue.nextTick(function () {
- // DOM 更新了执行的回调
- })
- // 作为一个 Promise 使用 (2.1.0 起新增, 详见接下来的提示)
- Vue.nextTick() .then(function () {
- // DOM 更新了
- })
在 Vue 实例对象中, 使用 this.$nextTick 方法
- new Vue({
- // ...
- methods: {
- // ...
- example () {
- // 修改数据
- this.message = 'changed'
- // DOM 还没有更新
- this.$nextTick(function () {
- // DOM 现在更新了
- // `this` 绑定到当前实例
- this.doSomethingElse()
- })
- }
- }
- })
渐进式框架的理解
对于渐进式框架来说, 主要就代表着它的主张性最小, 没有多做职责之外的事.
什么是主张性?
在你使用一个框架的时候, 如果你使用了它的一部分, 你就必须使用它的全部, 或是很大一部分东西, 这个就叫强主张
而说 Vue 的主张性最小, 是因为你可以只使用它的一部分东西, 它可以很容易的集成到你原有的项目中去, 甚至说你只是用它来改造一下一些公共的组件, 你可以在传统的 html 页面中使用它, 也可以使用脚手架工具来搭建一个大型的项目
在学习方面, Vue 的学习代价也不是很大, 你可以非常容易的上手, 我们没有必要一上来就搞明白 Vue 的所有功能, 可以先从核心功能扩展, 然后在逐步学习, 当你需要的时候, 你再去使用它的其他功能, 并且你可以非常容易的使用 Vue 结合其他的框架来进行搭配
组件样式属性 scoped 问题及解决方法
在 Vue 组件中, 为了使样式私有化 (模块化), 不对全局造成污染, 可以在 style 标签上添加 scoped 属性以表示它的只属于当下的模块, 局部有效.
Vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现, 给所有的样式动态的生成一个属性, 然后通过在编译后使用属性选择器加原有选择器的方式来设置样式, 这样就不会对其它组件造成影响, 当前组件的样式只对当前组件生效
然而, 我们在使用 scoped 的时候, 经常会遇到样式问题, 比如说
要在父组件中修改子组件的布局样式等
要修改 v-HTML 渲染出的内容的样式时
使用一些 UI 框架时, 修改样式的问题
对于这些问题的解决方案:
1. 同时使用 scoped 局部样式和全局样式
- <style>
- /* 全局样式 */
- </style>
- <style scoped>
- /* 局部样式 */
- </style>
子组件的根节点元素会同时被设置了 scoped 的父 CSS 样式和设置了 scoped 的子 CSS 样式影响, 这么设计的目的是父组件可以对子组件根元素进行布局.
使用深选择器,>>> 或者 / deep / 进行样式穿透
在 Vue 中, 可以使用 >>> 或者 / deep / 来进行样式穿透, 达到修改样式的功能
在 Sass 或者 Less 这种预编译器中, 不支持 >>>, 可以使用 / deep/
- <style scoped>
- .el-form>>> .el-input {
- // 修改样式
- }
- .el-form /deep/ .el-input {
- // 修改样式
- }
- </style>
建议使用第三种方法, 这样既不会对全局样式造成污染, 也可以达到修改样式的功能
Vue 中的双向数据绑定是如何实现的
Vue 的双向数据绑定是通过数据劫持结合发布者订阅者模式来实现的
要实现这种双向数据绑定, 必要的条件有:
1, 实现一个数据监听器 Observer, 能够对数据对象的所有属性进行监听, 如有变动可拿到最新值并通知订阅者
2, 实现一个指令解析器 Compile, 对每个元素节点的指令进行扫描和解析, 根据指令模板替换数据, 以及绑定相应的更新函数
3, 实现一个 Watcher, 作为连接 Observer 和 Compile 的桥梁, 能够订阅并收到每个属性变动的通知, 执行指令绑定的相应回调函数, 从而更新视图
4,MVVM 入口函数, 整合以上三者
Vue 数据双向绑定实现的原理
个人理解: 在 new Vue 的时候, 在 Observer 中通过 Object.defineProperty() 达到数据劫持, 代理所有数据的 getter 和 setter 属性, 在每次触发 setter 的时候, 都会通过 Dep 来通知 Watcher,Watcher 作为 Observer 数据监听器与 Compile 模板解析器之间的桥梁, 当 Observer 监听到数据发生改变的时候, 通过 Updater 来通知 Compile 更新视图
而 Compile 通过 Watcher 订阅对应数据, 绑定更新函数, 通过 Dep 来添加订阅者, 达到双向绑定
详述虚拟 DOM 中的 diff 算法
首先要先讲一下虚拟 DOM 是如何实现的
虚拟 DOM 是通过 JS 语法来在内存中维护一个通过数据解构描述出来的一个模拟 DOM 树, 当数据发生改变的时候, 会先对虚拟 DOM 进行模拟修改, 然后在通过新的虚拟 DOM 树与旧的虚拟 DOM 树来对比, 而这个对比就是通过 diff 算法来进行的
虚拟 DOM 最大的意义不在于性能的提升 (JavaScript 对象比 DOM 对象性能高), 而在于抽象了 DOM 的具体实现 (对 DOM 进行了一层抽象)
接着, 来看一下 diff 算法是如何进行比对的
正常的 diff 算法, 是通过层层对比, 单单对比来进行的, 对于我们的前端性能来说, 很明显是不可以这样实现的.
前端的虚拟 DOM 对比
所以, 前端的 diff 算法是通过以下步骤来实现的
步骤一: 用 JS 对象模拟 DOM 树
步骤二: 比较两棵虚拟 DOM 树的差异
步骤三: 把差异应用到真正的 DOM 树上
同时维护新旧两棵虚拟 DOM 树, 当数据发生改变的时候, 开始执行对比
首先对根元素进行对比, 如果根元素发生改变就直接对根元素替换
如果根元素没有发生改变的话, 再对下一层元素进行对比, 如果对比发现元素发生删除, 就执行删除, 发现元素被替换就执行替换, 发现添加了新的元素就执行添加
对比的同时, 会通过 key 值来判断元素是否发生改变, 判断元素是仅仅位置发生改变还是需要整个替换或删除
如果不是元素发生改变的话, 再对内容进行对比, 如果是内容发生改变的话, 就直接修改内容
其实就是进行逐层对比, 再通过不同的对比来判断执行不同的操作
Vue 提供了几种脚手架模板
webpack - 一个全面的 webpack+vue-loader 的模板, 功能包括热加载, linting, 检测和 CSS 扩展.
webpack-simple - 一个简单 webpack+vue-loader 的模板, 不包含其他功能, 让你快速的搭建 vue 的开发环境.
browserify - 一个全面的 Browserify+vueify 的模板, 功能包括热加载, linting, 单元检测.
browserify-simple - 一个简单 Browserify+vueify 的模板, 不包含其他功能, 让你快速的搭建 vue 的开发环境.
simple - 一个最简单的单页应用模板.
pwa - 一个集成 pwa 环境的 webapp 的模板
常见的几种 MVVM 的实现方式
实现数据绑定的做法有大致如下几种:
发布者 - 订阅者模式 (Backbone.JS)
脏值检查 (angular.JS)
数据劫持 (vue.js)
发布者 - 订阅者模式
一般通过 sub,pub 的方式实现数据和视图的绑定监听,
更新数据方式通常做法是 vm.set('property', value).
这种方式现在毕竟太 low 了, 我们更希望通过 vm.property = value 这种方式更新数据, 同时自动更新视图, 于是有了下面两种方式.
脏值检查
angular.JS 是通过脏值检测的方式比对数据是否有变更, 来决定是否更新视图,
最简单的方式就是通过 setInterval() 定时轮询检测数据变动,
angular 只有在指定的事件触发时进入脏值检测, 大致如下:
1,DOM 事件, 譬如用户输入文本, 点击按钮等.( ng-click )
2,XHR 响应事件 ( $http )
3, 浏览器 Location 变更事件 ( $location )
4,Timer 事件 ( $timeout , $interval )
5, 执行 $digest() 或 $apply()
数据劫持
vue.JS 则是采用数据劫持结合发布者 - 订阅者模式的方式,
通过 Object.defineProperty() 来劫持各个属性的 setter,getter,
在数据变动时发布消息给订阅者, 触发相应的监听回调.
来源: http://www.jianshu.com/p/d8147230945c