什么是响应式?
我上来给你一记军体拳,你说:“草,真疼!”那么你就是响应式的。
即如果一个物体对外界的刺激做出反应,那么它就是响应式的。
被别人锤一拳,完了一句话不说,什么反应都没有,那就不太正常了...
什么是响应式网页?
我们经常会听到响应式网页这个词,不了解的话会感觉很懵。其实就是当窗口大小发生改变时,比如你拖动、缩小或放大浏览器,或者先用电脑浏览器看一个网页,再用手机或平板看这个网页,这个网页会根据你屏幕大小来使用不同的内容分布方式,这就是响应式网页。
比如你用电脑看这个网页,因为电脑屏幕大,图片是横着放的,一排能整三四张图片。但是换到手机上,屏幕变小了,一排只能放一张图片了,多了看不到咋办?那就只能一张一张往下排了,这就是响应式网页。
响应式网页主要用到了CSS里的媒体查询。
什么是Vue数据响应式?
官方文档是这样说的:
Vue最独特的一个特性之一,是其非侵入性的响应式体统。
这样乍得一看会比较懵,直接上代码:
注:下面的所有代码都默认引入了Vue.js
//html:
<div id="app">
{{n}}
</div>
//js:
new Vue({
el:"#app",
data:{
n:0
}
})
我们在data选项里写上n的值为0,页面div里的n的值会自动变成0,我们在页面上就能看到0;
当我们把data选项里的n的值改成1后,页面div里的n的值也会跟着变成1,这就是Vue的数据响应式;
我们在创建一个Vue的实例时,把一个对象传进去作为data选项,那么我们在修改对象里属性值的时候,页面里对应的值就会直接跟着改变。也就是说,我们在修改数据模型时,视图就会进行更新,这样就使得状态管理非常简单直接。
浅析Vue数据响应式
先看代码:
const myData = {a:1}
console.log(myData) //{a:1}
new Vue({data:myData})
console.log(myData) //得到的结果变成了{__ob__: we}
在Chrome控制台里打开得到的结果看看:
里面多了好多东西,除了原型_proto_,我们还可以看到 get 和 set ,后面跟着属性a,这就比较神奇,为什么会传给new Vue之后就会变了呢?
这是因为我们把普通的JS对象传入Vue实例作为data选项后,Vue会遍历这个对象里所有的属性(property),并且使用ES6中的Object.defineProperty方法来把这些属性全部转为getter和setter;
IE8和更低版本的浏览器不支持Object.defineProperty,所以这是Vue不支持这些浏览器的原因。
Object.defineProperty是做什么的?
可以给对象添加属性value
let data1 = {}
Object.defineProperty(data1,'n',{
value:0
})
console.log(data1)
//想象中data1是这样的:
{
n:{
value:0
}
}
//然而实际上data1却是这样的:
{
n:0
}
//所以console.log(data1.n)结果是
0
可以给对象添加getter和setter
let data2 = {}
Object.defineProperty(data2,n,{
get(){},
set(){}
})
这里的get属性里就可以写getter函数了,当我们用data2.n来访问data2的n属性时,就会调用这个函数,该函数的返回值会被用作这个属性的值。如果里面什么都不写,即没有getter函数,就返回undefined。
同样set属性里的就是setter函数,当我们修改属性值时,会调用这个函数。这个函数接受一个参数,这个参数就是被赋予这个属性的新值。如果什么都不写,就没有setter,就是undefined了。
那么根据这些来继续完善上面的代码:
let data2 = {}
data2.x = 0
Object.defineProperty(data2,n,{
get(){
return this.x
},
set(value){
this.x = value
}
})
console.log(data2)
console.log(data2.n)
上面代码的意思是:在data2对象里创建一个n属性,里面有一个get和set:
get负责:如果你访问n,那么就给你返回0
set负责:如果你想修改n,那么就得从我这走一圈~
这样就可以做到对属性的读写进行监控:
let data2 = {}
data2.x = 0
Object.defineProperty(data2,n,{
get(){
return this.x
},
set(value){
if(value<0){
return
}else{
this.x = value
}
}
})
data2.n = -1
console.log(data2.n) //0
data2.n = 1
console.log(data2.n) //1
这样可以监控到属性值的变化,但是有个问题:
我们访问data2对象的属性值的时候,实际上是调用getter函数,返回的是data2的x属性的属性值,然后设置的时候也是修改data2的x属性的属性值来达到修改n属性的目的。那我不用data2.n = -1来修改,我直接修改data.x的值不就能绕过setter了吗?
data2.x = -1
console.log(data2.n) //-1,说明不受setter的控制
因此,我们需要想办法来隐藏对象的属性,不让对象的属性暴露出来,以免被人随意修改。
这时就需要用到代理:
简单理解代理proxy
之前我们都是先声明对象,然后写一个对象的属性值来提前存储数据,比如:
let data2 = {}
data2.x = 0
现在我们使用匿名对象试试:
let data3 = proxy({data:{n:0}})
proxy({data})是一个函数,真正的对象是{n:0},是一个匿名对象,匿名对象就不好访问到了。因为修改一个对象的属性,你至少要知道对象的名字吧,连对象的名字都不知道,还怎么修改里面的属性的值?
然后简单的实现以下proxy函数:
function proxy({data}){
let obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value<0){
return
}else{
data.n = value
}
}
})
return obj
}
当你读取obj的n时,obj就返回data的n,当你修改obj的n的时候,obj就会同步设置data的n;
即你对obj的n属性做什么,就会同时对data的n属性做一样的操作,最后返回obj,这个obj就是一个代理,相当于你对data的操作的所有过程都被obj中转了一次。
这样如果别人还想通过上面例子中,修改data2.x的值来达到绕过setter的目的的话,会发现不知道从哪儿下手,因为proxy里面内容是没有暴露出去的。要用data.n就必须经过setter,不用的话又不知道该用什么对象的什么属性来绕开。
更夸张的情况
上面是使用了匿名对象,然后中间代理了一层,别人不知道里面到底是什么对象在起作用。但是如果我不用匿名函数,我先声明一个函数,然后把这个函数引用到proxy里,不是一样可以达到效果吗:
let myData = {n:0}
let data4 = proxy({data:myData})
myDatafmy.n = -1
console.log(data4.n)//-1
这样就成功了绕开了代理,达到了修改的效果。
那么如何做到,就算用户修改了myData,我们也能拦截呢?
let myData2 = {n:0}
let data5 = proxy2({data:myData2})
function proxy2({data}){
let value = data.n //用value来存储原始的data.n的值
delete data.n //把原始的data.n删除
Object.defineProperty(data,'n',{ //然后创建一个n属性,放在data上
get(){
return value //访问data.n时,返回value
},
set(newValue){
if(newValue<0)return
value = newValue //如果修改data.n的值,就把先看这个值是否符合要求,符合就赋给value
}
})
//这样就能达到监听的效果
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj
}
console.log(data5.n) //0
myData2.n = -1
console.log(data5.n) //还是0,修改失败
总结
当我们这样写时:
let vm = new Vue({data:myData})
会有这样的过程:
- vm会变成myData的代理(proxy),对vm取n,就等于对myData取n,修改vm的n属性的值,就等于修改myData的n属性的值。
- vm会对myData的所有属性进行监控
为什么要进行监控呢?因为防止myData属性变了,vm不知道。vm知道了又怎么样呢?vm感知到myData对象的属性变化后,就可以调用render()去更新视图层。
回头看Vue数据响应式
Vue数据响应式主要就是用了Object.defineProperty方法,对传入的对象属性进行监控,这样对象属性值在变化时Vue就可以更新视图。
摘抄官方的文档:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。