目录
理解MVVM
MVVM模型
MVVM简介
MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。
Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel负责连接 View 和 Model,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷。
MVVM模型
MVVM拆开来即为Model-View-ViewModel,有View,ViewModel,Model三部分组成。View层代表的是视图、模版,负责将数据模型转化为UI展现出来。Model层代表的是模型、数据,可以在Model层中定义数据修改和操作的业务逻辑。ViewModel层连接Model和View。
在MVVM的架构下,View层和Model层并没有直接联系,而是通过ViewModel层进行交互。ViewModel层通过双向数据绑定将View层和Model层连接了起来,使得View层和Model层的同步工作完全是自动的。因此开发者只需关注业务逻辑,无需手动操作DOM,复杂的数据状态维护交给MVVM统一来管理。
vue.js中MVVM的体现
Vue.js的实现方式,对数据(Model)进行劫持,当数据变动时,数据会出发劫持时绑定的方法,对视图进行更新。
Vue.js关于双向数据绑定的一些实现细节
下面我又简单的了解了一下Vue.js关于双向数据绑定的一些实现细节;主要参考了下面这篇文章,很有研究价值,有意向者可以去看一下:
深入MVVM模型带你理解Vue.js的双向绑定
vue是采用Object.defineProperty的getter和setter,并结合观察者模式来实现数据绑定的。当把一个普通的javascript对象传给Vue实例来作为它的data选项时,Vue将遍历它的属性,用Object.defineProperty将它们转为getter/setter。用户看不到getter/setter,但是在内部它们让Vue追踪依赖。在属性被访问和修改时通知变化。
Observer数据监听器,能够对数据对象的所有属性进行监听,如有变动可拿到最新的值并通知订阅者,内部采用的Obiect.defineProperty的getter和setter来实现。
complie指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定Observer和Complie的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应的回调函数
Watcher订阅者,作为连接Observer和Complie的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数。
Dep消息订阅器,内部维护了一个数组,用来收集订阅者(watcher),数据变动触发notify函数,再调用订阅者的update方法。
_Object.defineProperty
Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
需要定义属性的对象。prop
需被定义或修改的属性名。descripter
需被定义或修改的属性的描述符。
实例
var o={};//创建一个新对象
Object.defineProperty(o,"a",{
//该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
value:37,
// 仅当仅当该属性的writable为 true 时,该属性才能被赋值运算符改变。默认为 false
writable:true,
//仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false
enumerable:true,
// 仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除。默认为 false
configurable:true
});
// 对象o拥有了属性a,值为37
var bValue;
Object.defineProperty(o,"b",{
get:function () { return bValue; },
set:function (v) { bValue=v; },
enumerable:true,
configurable:true
});
o.b=45;
数据和视图联动
给对象o定义新的属性b,并且定义属性b的get和set方法,当o.b的时候会调用b属性的get方法,给b属性赋值的时候,会调用set方法,这就是修改数据的时候,视图会自动更新的关键
前端获取数据后,需要根据数据操作dom,视图变化后,需要修改不少代码,有没有方法将数据和dom操作隔离,看一个例子,显示用户信息的html模版。
<div>
<p>你好,<span id='nickName'></span></p>
<div id="introduce"></div>
</div>
//视图控制器
var userInfo = {};
Object.defineProperty(userInfo, "nickName", {
get: function(){
return document.getElementById('nickName').innerHTML;
},
set: function(nick){
document.getElementById('nickName').innerHTML = nick;
}
});
Object.defineProperty(userInfo, "introduce", {
get: function(){
return document.getElementById('introduce').innerHTML;
},
set: function(introduce){
document.getElementById('introduce').innerHTML = introduce;
}
});
//数据
//todo 获取用户信息的代码
//....
userInfo.nickName = "xxx";
userInfo.introduce = "我是xxx,我来自云南,..."
理解数据代理
代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id='root'>
<!-- <h1>hello {{name}} {{$mount}}</h1> -->
</div>
<script type="text/javascript">
Vue.config.productionTip = false
let num = 20
let person = {
name:'钢弹',sex:'males'
}
Object.defineProperty(
person,
'age',
{
// value:18,
// enumerable:true, // 支持枚举,枚举就是可遍历啥的,不然新增属性,遍历不出来!
// writable:true, // 控制属性是否能被 修改
// configurable:true,// 控制属性是否能删除
get(){
console.log('有人访问了person就会触发这个方法,从而让 = num')
return num;
},
set(value){
console.log('有人修改person中的age的值,从而把num等于这个值')
num = value
}
}
)
console.log(person)
</script>
</body>
</html>
第二个例子:person1中的age跟person2中一致
let person1 = {
name:'zhangsan'
}
let person2 = {
name:'lisi',age:4
}
Object.defineProperty(person1,'age',{
get(){
return person2.age
},
set(value){
person2.age = value
}
})
vue中的数据代理
<script type="text/javascript">
let num = 16
let person = {
name:"张三",
//age:18,
sex:'男'
}
Object.defineProperty(person,'age',{
/*value:16,
enumerable:true,//控制属性是否能被遍历
writable:true,//控制属性是否可以被修改
configurable:true,//控制属性是否可以被删除*/
//有人读取age属性时,get函数(getter)就会被调用。且返回值就是return的值
get(){
console.log('读取值了')
return num
},
//有人修改age属性时,set函数(setter)就会被调用。且返回值就是return的值
set(val){
console.log('修改值了,且值为',val)
num = val;
}
})
console.log(person)
1.Vue中的数据代理
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的 好处
更加方便的操作data中的数据
3.基本原理:
通过object.defineProperty()把data对象中的所有属性添加到vm上
为每一个添加到vm上的属性,都指定一个getter和setter
在getter/setter内部去操作data中对应的属性
事件处理
监听事件
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
示例
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'
事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击。
新增
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
不像其它只能对原生的 DOM 事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。
新增
Vue 还对应 addEventListener 中的 passive 选项提供了 .passive
修饰符
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
这个 .passive
修饰符尤其能够提升移动端的性能。
不要把 .passive
和 .prevent
一起使用,因为 .prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为
键盘事件
在 JavaScript 中,当用户操作键盘时,会触发键盘事件,键盘事件主要包括下面 3 种类型:
- keydown:在键盘上按下某个键时触发。如果按住某个键,会不断触发该事件,但是 Opera 浏览器不支持这种连续操作。该事件处理函数返回 false 时,会取消默认的动作(如输入的键盘字符,在 IE 和 Safari 浏览器下还会禁止keypress 事件响应)。
- keypress:按下某个键盘键并释放时触发。如果按住某个键,会不断触发该事件。该事件处理函数返回 false 时,会取消默认的动作(如输入的键盘字符)。
- keyup:释放某个键盘键时触发。该事件仅在松开键盘时触发一次,不是一个持续的响应状态。
当获取用户正按下键码时,可以使用 keydown、keypress 和 keyup 事件获取这些信息。其中 keydown 和 keypress 事件基本上是同义事件,它们的表现也完全一致,不过一些浏览器不允许使用 keypress 事件获取按键信息。所有元素都支持键盘事件,但键盘事件多被应用在表单输入中。
<textarea id="key"></textarea>
<script>
var key = document.getElementById("key");
key.onkeydown =f; //注册keydown事件处理函数
key.onkeyup = f; //注册keyup事件处理函数
key.onkeypress = f; //注册keypress事件处理函数
function f (e) {
var e = e || window.event; //标准化事件处理
var s = e.type + " " + e.keyCode; //获取键盘事件类型和按下的值
key.value = s;
}
</script>
属性
键盘定义了很多属性,如下表所示。利用这些属性可以精确控制键盘操作。键盘事件属性一般只在键盘相关事件发生时才会存在于事件对象中,但是 ctrlKey 和 shiftKey 属性除外,因为它们可以在水保事件中存在。例如,当按下 Ctrl 或Shift 键时单击鼠标操作。
属性 | 说明 |
---|---|
keyCode | 该属性包含键盘中对应键位的键值 |
charCode | 该属性包含键盘中对应键位的 Unicode 编码,仅 DOM 支持 |
target | 发生事件的节点(包含元素),仅 DOM 支持 |
srcElement | 发生事件的元素,仅 IE 支持 |
shiftKey | 是否按下 Shift 键,如果按下返回 true,否则为false |
ctrlKey | 是否按下 Ctrl 键,如果按下返回 true,否则为false |
altKey | 是否按下 Alt 键,如果按下返回 true,否则为false |
metaKey | 是否按下 Mtea 键,如果按下返回 true,否则为false,仅 DOM 支持 |
键盘相应顺序
当按下键盘时,会连续触发多个事件,它们将按如下顺序发生。
对于字符键来说,键盘事件的响应顺序:keydown → keypress → keyup。
对于非字符键(如功能键或特殊键)来说,键盘事件的相应顺序:keydown → keyup。
如果按下字符键不放,则 keydown 和 keypress 事件将逐个持续发生,直至松开按键。
如果按下非字符键不放,则只有 keydown 事件持续发生,直至松开按键。
事件总结
资源事件
事件名称 | 何时触发 |
---|---|
error | 资源加载失败时。 |
abort | 正在加载资源已经被中止时。 |
load | 资源及其相关资源已完成加载。 |
beforeunload | window,document 及其资源即将被卸载。 |
unload | 文档或一个依赖资源正在被卸载。 |
网络事件
事件名称 | 何时触发 |
---|---|
online | 浏览器已获得网络访问。 |
offline | 浏览器已失去网络访问。 |
焦点事件
事件名称 | 何时触发 |
---|---|
focus | 元素获得焦点(不会冒泡)。 |
blur | 元素失去焦点(不会冒泡)。 |
WebSocket 事件
事件名称 | 何时触发 |
---|---|
open | WebSocket 连接已建立。 |
message | 通过 WebSocket 接收到一条消息。 |
error | WebSocket 连接异常被关闭(比如有些数据无法发送)。 |
close | WebSocket 连接已关闭。 |
会话历史事件
事件名称 | 何时触发 |
---|---|
pagehide | A session history entry is being traversed from. |
pageshow | A session history entry is being traversed to. |
popstate | A session history entry is being navigated to (in certain cases). |
CSS 动画事件
事件名称 | 何时触发 |
---|---|
animationstart | 某个 CSS 动画开始时触发。 |
animationend | 某个 CSS 动画完成时触发。 |
animationiteration | 某个 CSS 动画完成后重新开始时触发。 |
CSS 过渡事件
事件名称 | 何时触发 |
---|---|
transitionstart | A CSS transition has actually started (fired after any delay). |
transitioncancel | A CSS transition has been cancelled. |
transitionend | A CSS transition has completed. |
transitionrun | A CSS transition has begun running (fired before any delay starts). |
表单事件
事件名称 | 何时触发 |
---|---|
reset | 点击重置按钮时 |
submit | 点击提交按钮 |
打印事件
时间名称 | 何时触发 |
---|---|
beforeprint | 打印机已经就绪时触发 |
afterprint | 打印机关闭时触发 |