解释
View是视图,就是DOM;对应视图也就是HTML部分--代表UI组件,它负责将数据模型转化成UI展现出来。 Model是模型,就是vue组件里的data,或者说是vuex里的数据;--代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。 ViewModel--监听模型数据也就是data的的改变和控制视图行为、处理用户交互,简单理解就是一个同步View和Model的对象,连接Model和View。
总结
在MVVM架构下,View和Model之间并没有直接的联系,而是通过ViewMode进行交互,Model和ViewModel之间的交互是双向的,因此View数据的变化会同步到Model中,而Model数据的变化也会立即反应到View上。
ViewModel通过双向数据绑定把View层和Model层连接了起来,而View和Model之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由MVVM来统一管理。由此,我们可以引出vue是响应式的
说明
Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty中的访问器属性中的 get和 set方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
检测data变化的核心APIObject.defindeProperty
基本使用
const data = {};
let name = "张三";
Object.defineProperty(data,'name',{
get:function(){ console.log('触发get') return name },
set:function(newVal){ console.log('触发set') name=newVal }
})
//测试
console.log(data.name) // 触发get 张三
data.name = '李四' // 触发set
这样就是可以实现数据的获取和赋值的监听
//触发更新视图
function updateView() {
console.log('视图更新')
}
//重新定义数组原型
const oldArrayProperty = Array.prototypo
//创建新对象,原型指向oldArrayProperty,在拓展新的方法(这样不会影响原型)
let arrayProto = Array.prototype
let methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push']
methods.forEach(methodName => {
arrayProto[methodName] = function () {
updateView ()
oldArrayProperty[methodName].call(this,...arguments)
}
})
//监听对象属性
function observer(target){
if(typeof target !=='object' || target === null) {
//不是对象或者数组
return target
}
//重新定义数组原型
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
//重新定义各个属性(for in 对象/数组都可以遍历)
for(let key in target) {
defineReactive(target,key,target[key])
}
}
//重新定义属性,监听起来
function defineReactive (target, key, value){
//递归深度监听
observer(value)
//核心API
Object.defineProperty(target,key,{
get(){
return value
},
set(newValue){
if(newValue !== value) {
// 深度监听
observer(newValue)
//设置新值
value = newvalue
//触发更新视图
updateView()
}
}
})
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
data.name = 'lisi' //视图更新
data.age = 21 //视图更新
console.log('age', data.age) //age 21
data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
delete data.name // 删除属性,监听不到 —— 所以有 Vue.dete
data.info.address = '上海' // 深度监听
data.nums.push(4) // 视图更新
缺点
在解析Vue3的响应式原理之前,首先需要了解两个ES6新增的API:Porxy与Reflect。
Proxy: 代理,顾名思义主要用于为对象创建一个代理,从而实现对对象基本操作的拦截和自定义。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
Reflect: 反射,就是将代理的内容反射出去。Reflect与Proxy一样,也是 ES6 为了操作对象而提供的新 API。它提供拦截JavaScript操作的方法,这些方法与Proxy handlers 提供的的方法是一一对应的,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。且 Reflect 不是一个函数对象,即不能进行实例化,其所有属性和方法都是静态的。
背景
DOM操作是非常耗时的,Vue 和React 是数据驱动视图,就是 通过 虚拟DOM(vdom)来解决的这个问题
vdom就是一段js形式的html代码 用 js 模拟 DOM 结构
<div id ='app' class='box'>
<p>p标签的文本</p>
<ul style='font-size:20px'>
<li>li标签文本</li>
</ul>
</div>
{
tag: 'div',
props: {
id: 'app',
className: 'box'
},
children: [
{
tag: 'p',
children: 'p标签的文本'
},
{
tag: 'ul',
props: {
style: 'font-size:20px'
},
children: [
{
tag: 'li',
children:'li标签文本'
}
]
}
]
}
拓展
?
可以通过学习snabbdom 进一步了解
var snabbdom = require('snabbdom');
var patch = snabbdom.init([ // Init patch function with chosen modules
require('snabbdom/modules/class').default, // makes it easy to toggle classes
require('snabbdom/modules/props').default, // for setting properties on DOM elements
require('snabbdom/modules/style').default, // handles styling on elements with support for animations
require('snabbdom/modules/eventlisteners').default, // attaches event listeners
]);
var h = require('snabbdom/h').default; // helper function for creating vnodes
var container = document.getElementById('container');
var vnode = h('div#container.two.classes', {on: {click: someFn}}, [
h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
' and this is just normal text',
h('a', {props: {href: '/foo'}}, 'I\'ll take you places!')
]);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode);
var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [
h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
' and this is still just normal text',
h('a', {props: {href: '/bar'}}, 'I\'ll take you places!')
]);
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
解析
核心
?
再此推荐两篇文章
vue组件渲染/更新过程(异步渲染)
注意
如果模板中没有用的data数据就不会触发getter,因为和视图没关系(vue里面的优化)
vue分为hash(默认)以及 history 两个路由模式
解析
特点
vue中就是通过hash 的变化触发路由的变化,来触发视图的渲染, js 实现hash
<body>
<p>
hash路由
</p>
<button id='btn'>
修改 hash
</button>
</body>
<script>
//hash 变化 包括;
//a. js 修改URL
//b. 手动修改url的hash
//c.浏览器的前进、后退
//页面初次加载获取hash
window.addEventListener ('DOMContentLoaded',() =>{
console.log('hash',location.hash)
})
//hash变化触发
window.onhashchange = (event) =>{
console.log('hash',location.hash)
}
//js 修改 url
document.getElementById('btn').addEventListener('click',()=>{
location.href = '#/user'
})
</script>
h5 history 主要是通过 history.pushState 跳转 和 window.onpopstate 监听页面的前进和后退
<body>
<p>
historyh路由
</p>
<button id='btn'>
修改 url
</button>
</body>
<script>
//页面初次加载获取hash
window.addEventListener ('DOMCintentLoaded',() =>{
console.log('load',location.pathname)
})
//js 修改 url
document.getElementById('btn').addEventListener('click',()=>{
//pushState 有三个参数
//第一个参数是个js对象,可以放任何的内容,可以在onpostate事件中(后面介绍)获取到便于做相应的、处理。
//第二个参数是页面标题:目前所有浏览器都不支持,填空字符串即可
//第三个参数是个字符串,就是保存到history中的url。
let state= {
title:'新页面'
}
history.pushState(state,'','user')
})
//监听浏览器的前进、后退
window.onpostate = (event) => {
console.log(event.state) // {title:'新页面'}
console.log(location.pathname)
}
</script>
上面的代码如果放在本地html 文件中运行 js代码会报错,需要放在web服务器
注意
history 模式需要后端配合,就是无论用户访问什么路由,所有路由的切换都由前端来做,后端只需要返回index.html的文件,如果后面没有配置兼容,当访问user这个路由,点击刷新,就会报user页面找不到