Vue 里的数据都存储在 data 里,然后对 data 里的数据进行更新,从而使页面的 UI 重新渲染 1.首先先来看一下 Vue 到底对 data 做了什么呢? //引用完整版 Vue import Vue from "vue/dist/vue.js"; Vue.config.productionTip = false;//禁用警告const myData = {n: 0 } console.log(myData) // 精髓new Vue({data: myData,template: `{{n}}
` }).$mount("#app");setTimeout(()=>{myData.n += 10 console.log(myData) // 精髓 },3000)
打出log如图
说明myData 引用到 Vue 实例里,Vue 对 data 里的数据进行了一些处理
ES6的getter和setter 通过虚拟属性xxx来模拟对n的读写操作
需求一,得到姓名
let obj0 = {姓: "高",名: "圆圆",age: 18 };// 需求一,得到姓名let obj1 = {姓: "高",名: "圆圆",姓名() {return this.姓 + this.名;},age: 18 };console.log("需求一:" + obj1.姓名());
// 姓名后面的括号能删掉吗?不能,因为它是函数 // 怎么去掉括号?需求二,姓名不要括号也能得出值
let obj2 = {姓: "高",名: "圆圆",get 姓名() {return this.姓 + this.名;},age: 18 }; console.log("需求二:" + obj2.姓名);
get是一个属性只不过是用函数定义的 总结:这种写法就叫做 getter ,关键词为 get ,用于获取一个值。不加括号的函数,定义时为函数,但是使用时不用加括号。 需求三:姓名可以被写
let obj3 = {姓: "高",名: "圆圆",get 姓名() {return this.姓 + this.名;},set 姓名(xxx){this.姓 = xxx[0]this.名 = xxx.slice(1)//或者this.名 = xxx.substring(1)},age: 18 }; obj3.姓名 = '刘亦菲'console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
// 总结:setter 就是这样用的。接受一个新的值,obj.姓名 = 'xxx'触发 set 函数
确实可以对于'姓名'进行读和写,但是不存在叫'姓名'的属性,通过get和set来模拟对其读写操作
定义完一个对象后,想加在上面再加一个属性,使用Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
obj:要在其上定义属性的对象;prop:要定义或修改的属性的名称;
descriptor:将被定义或修改的属性描述符。
var _xxx = 0 0bject.defineProperty(obj3,'xxx',{ //给obj3添加一个属性,属性为xxx,xxx的值为 get( ){ return _xxx }, set(value){ _xxx=value//由于xxx是虚拟属性不存在,因此用_xxx来储存值 } }) obj3.姓名='刘诗诗'console.log( `需求三:姓${obj3.姓},名${obj3.名}`) console.log(obj3)
例子 需求一:用 Object.defineProperty 定义 n
let data0 = {n: 0 }let data1 = {}Object.defineProperty(data1, 'n', {value: 0 //给data1添加一个属性n,它的值是0 })
需求二:n 不能小于 0
即 data2.n = -1 应该无效,但 data2.n = 1 有效
let data2 &#61; {}data2._n &#61; 0 // _n 用来偷偷存储 n 的值Object.defineProperty(data2, &#39;n&#39;, {get(){return this._n},set(value){if(value <0) returnthis._n &#61; value} })console.log(&#96;需求二&#xff1a;${data2.n}&#96;) data2.n &#61; -1 console.log(&#96;需求二&#xff1a;${data2.n} 设置为 -1 失败&#96;) data2.n &#61; 1 console.log(&#96;需求二&#xff1a;${data2.n} 设置为 1 成功&#96;)
如果对方直接使用 data2._n 呢&#xff1f;
需求三&#xff1a;使用代理
let data3 &#61; proxy({ data:{n:0} }) // 括号里是匿名对象&#xff0c;无法访问function proxy({data})// 解构赋值function proxy(options) {const{data}&#61;options}从options拿到dataconst obj &#61; {}// 这里的 &#39;n&#39; 写死了&#xff0c;理论上应该遍历 data 的所有 key&#xff0c;这里做了简化// 因为我怕你们看不懂Object.defineProperty(obj, &#39;n&#39;, { get(){return data.n //对obj.n做什么同时对data.n做什么},set(value){if(value<0)returndata.n &#61; value}})return obj // obj 就是代理 }// data3 就是 obj console.log(&#96;需求三&#xff1a;${data3.n}&#96;) data3.n &#61; -1 console.log(&#96;需求三&#xff1a;${data3.n}&#xff0c;设置为 -1 失败&#96;) data3.n &#61; 1 console.log(&#96;需求三&#xff1a;${data3.n}&#xff0c;设置为 1 成功&#96;)
如果绕过代理&#xff0c;非要给对象一个名字myData&#xff0c;可以使用myData去修改n
let myData &#61; {n:0} let data4 &#61; proxy({ data:myData }) // 括号里是匿名对象&#xff0c;无法访问// data3 就是 obj console.log(&#96;杠精&#xff1a;${data4.n}&#96;) myData.n &#61; -1 console.log(&#96;杠精&#xff1a;${data4.n}&#xff0c;设置为 -1 失败了吗&#xff01;&#xff1f;&#96;)
需求四、使用监听
let myData5 &#61; {n:0} let data5 &#61; proxy2({ data:myData5 }) // 括号里是匿名对象&#xff0c;无法访问function proxy2({data}){let value &#61; data.n //用value存储原始的值Object.defineProperty(data, &#39;n&#39;, { //此处声明新的虚拟对象n就会删掉原始的nget(){return value},set(newValue){if(newValue<0)returnvalue &#61; newValue}})// 就加了上面几句&#xff0c;这几句话会监听 dataconst obj &#61; {}Object.defineProperty(obj, &#39;n&#39;, {get(){return data.n},set(value){if(value<0)returndata.n &#61; value}})return obj // obj 就是代理 }// data3 就是 obj console.log(&#96;需求五&#xff1a;${data5.n}&#96;) myData5.n &#61; -1 console.log(&#96;需求五&#xff1a;${data5.n}&#xff0c;设置为 -1 失败了&#96;) myData5.n &#61; 1 console.log(&#96;需求五&#xff1a;${data5.n}&#xff0c;设置为 1 成功了&#96;)
可以看出let data5 &#61; proxy2({ data:myData5 })类似于
let vm &#61; new Vue({data: myData})
小结&#xff1a;
vue 对 data 做了什么&#xff1f;
vm &#61; new Vue({data: myData})
1、会让vm成为myData的代理(proxy)&#xff0c;可以通过this来访问vm(this.n来访问myData.n&#xff0c;因为vm是代理&#xff09;
2、会对myData的所有属性进行监控&#xff0c;为了防止myData的属性变了&#xff0c;vm不知道。
vm知道属性变了就可以调用render(data)来更新 UI 和渲染页面&#xff0c;UI &#61; render(data)
3、 const vm &#61; new Vue({data: {n:0}})整个流程&#xff1a;vue会对内部数据data进行改造和监听&#xff0c;let value&#61;0并且对其get和set形成新的被篡改后的对象,然后通过代理对篡改后的对象进行读和写&#xff0c;再用vm接纳新的对象&#xff0c;data 中有多个变量/属性时&#xff0c;可以用闭包和循环来实现这个过程。
数据响应式 若一个物体能对外界的刺激做出反应&#xff0c;它就是响应式的
Vue的data是响应式&#xff0c;const vm &#61; new Vue({data: {n: 0}}}&#xff0c;如果修改vm.n,那么UI中的n就会响应我
Vue 2通过0bject.defineProperty来实现数据响应式
Vue有一个bug
Object.defineProperty的问题
Object.defineProperty(obj, &#39;n&#39; ,{..})必须要有一个&#39;n&#39;,才能监听&代理obj.n
示例1、如果没有给n
import Vue from "vue/dist/vue.js";Vue.config.productionTip &#61; false;new Vue({data: {},template: &#96;{{n}}
&#96; }).$mount("#app");
Vue会给一个警告n没有被定义
示例2、Vue只会检查第一层
import Vue from "vue/dist/vue.js";Vue.config.productionTip &#61; false;new Vue({data: {obj: {a: 0 // obj.a 会被 Vue 监听 & 代理}},template: &#96;{{obj.b}}set b
&#96;,methods: {setB() {this.obj.b &#61; 1; //请问&#xff0c;页面中会显示 1 吗&#xff1f;}} }).$mount("#app");
页面中不会显示1&#xff0c;因为b一开始没有被监听
解决方法有2个
1、一开始声明好所有的key
new Vue({data: {obj: {a: 0 &#xff1b;b:undefined}},
再点击setb就会显示1
2、使用Vue.set 和 this.$set
methods: {setB() {写法1&#xff1a;Vue.set(this.obj,&#39;b&#39;,1) 或者写法2&#xff1a;this.$set(this.obj,&#39;b&#39;,1)}}
作用:
a.新增key
b.自动创建代理和监听(如果没有创建过)
c.触发UI更新(但并不会立刻更新)
当data有数组时 import Vue from "vue/dist/vue.js";Vue.config.productionTip &#61; false;new Vue({data: {array: ["a", "b", "c"]},template: &#96;{{array}}set d
&#96;,methods: {setD() {this.array[3] &#61; "d"; //请问&#xff0c;页面中会显示 &#39;d&#39; 吗&#xff1f;// 等下&#xff0c;你为什么不用 this.array.push(&#39;d&#39;)}} }).$mount("#app");
不会显示d是因为一开始数组的下标只有0,1,2&#xff1b;数组的长度无法预测无法使用 undefined 去为每一项占位&#xff0c;或一直使用 Vue.set( ) 方法。
因此可以用 Array.push 方法来添加
this.array.push(&#39;value&#39;)
Array.push不是原生的API,而是当你将数组对象传递给vue后&#xff0c;它会篡改数组&#xff0c;在中间增加一层原型&#xff0c;该原型有七个方法&#xff1a;
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
这些API会自动处理对数组的监听和代理&#xff0c;并触发视图更新。
大概原理&#xff1a;
c1ass VueArray extends Array {push(...args) {const oldLength &#61; this.length // this 就是当前的数组super.push(...args)//原理会先调用父级的push&#xff0c;但是会执行后面的监听代码console.1og(&#39;你push 了&#39;)for(let i &#61; oldLength; i} const a &#61; new VueArray(1,2,3,4) a. push(5)
总结 对象中新增的key
Vue没有办法事先监听和代理 要使用set 来新增key&#xff0c;创建监听和代理&#xff0c;更新Ul 最好提前把属性都写出来&#xff0c;不要新增key 但数组做不到「不新增key」 数组中新增的key
也可用set来新增key&#xff0c;但set对于数组无法创建监听和代理&#xff0c;更新UI 不过尤玉溪篡改了7个API方便你对数组进行增删 这7个API会自动处理监听和代理&#xff0c;并更新U 结论:数组新增key最好通过7个API