Vue:数据响应式

91 阅读3分钟

components 三种写法

image.png

image.png

image.png 阴影中的写法和options的写法是一样的,只是阴影中的 data 必须用函数

message前面不加 冒号,双引号里面的 n 就是字符串; 加了冒号后,后面的双引号里面的内容就是 JS 代码 image.png

getter 和 setter

// setter 和 getter

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.姓名)
//总结:getter 就是这样用的,不加括号的函数,仅此而已。

//需求三:姓名可以被写
 let obj3 = {
   姓:'高',
   名:'圆圆',
   get 姓名(){
     return this.姓 + this.名
   },
   set 姓名(xx){
     this.姓 = xx[0]
     this.名 = xx.slice(1)
   },
     age:18
 }
 obj3.姓名 = '高媛媛'
 console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
 
 // 总结: setter 就是这样用的,用 =xx 触发 set 函数。

Object.defineProperty、代理、监听

let data0 = {
  n:0
}

//需求一:用 Object.defineProperty 定义 n
 let data1 = {}
 
 Object.defineProperty(data1,'n',{
   value:0
 })
 console.log(`需求一:${data1.n}`)
 
 // 需求二:n 不能小于 0
 // 即 data2.n = -1 应该无效,但 data.n = 1 有效
 
 let data2 = {}
 
 data2._n = 0  // _n 用来偷偷存储 n 的值
 
 Object.defineProperty(data2,'n',{
   get(){
     return this._n
   },
   set(value){
     if(value < 0) return
     this._n = value
   }
 })
 
 console.log(`需求二:${data2.n}`)
 
 data2.n = -1
 console.log(`需求二:${data2.n} 设置为 -1 失败`)
 data2.n = 1
 console.log(`需求二:${data2.n} 设置为 1 成功`)
 
 // 那如果对方直接用 data2._n 呢?
 
 // 需求三:使用代理
 let data3 = proxy({
   data:{
     n:0
   }
 })
 // proxy 圆括号里是匿名对象,无法访问

 function proxy({data}){ 
   // {data}解构赋值,相当于
   // function proxy(options){const {data} = options},意思是从 options 中拿到 data ,但是 options 只用了一遍,所以就省略了。
   const obj = {}
   //这里的 'n' 写死了,理论上应该遍历 data 的所有的 Key ,这里做了简化
   Object.defineProperty(obj,'n',{
     get(){
       return data.n
     },
     set(value){
       if(value < 0) return
       data.n = value
     }
   })
   return obj  //obj 就是代理
 }

// data3 就是 obj

console.log(`需求三:${data3.n}`)

data3.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败`)

data3.n = 1
console.log(`需求三:${data3.n},设置为 1 成功`)

// 需求四

let myData = {n:0}
let data4 = proxy({data:myData}) //proxy 圆括号里是匿名对象,无法访问

// data4 就是 obj 
// 直接改 myData ,绕过代理

console.log(`需求四:${data4.n}`)

myData.n = -1
console.log(`需求四:${data4.n},设置为 -1 成功了`)

// 需求五,就算用户擅自修改 myData ,也要拦截它

let myData5 = {n:0}
let data5 = proxy({data:myData5})

function proxy2({data}){
  let value = data.n
  Object.defineProperty(data,'n',{
    get(){
      return value
    },
    set(value){
      if(newValue < 0 ) return 
      value = newValue
    }
  })
  //就加了上面几句,这几句话会监听 data
  
  const obj = {}
  Object.defineProperty(obj,'n',{
    get(){
      return data.n
    },
    set(value){
      if(value < 0) return
      data.n = value
    }
  })
  
  return obj // obj 就是代理
}

console.log(`需求五:${data5.n}`)

myData5.n = -1
console.log(`需求五:${data5.n},设置为 -1 失败了`)

myData5.n = 1
console.log(`需求五:${data5.n},设置为 1 成功了`)

// let data5 = proxy({data:myData5})
// let vm = new Vue({data: myData})  类似

代理

1、代理是一种设计模式,对 myData 对象的属性读写,全权由另一个对象 vm 负责;

2、那么 vm 就是 myData 的代理(类似于房东租房),比如 myData.n 不用,偏要用 vm.n 来操作 myData.n;

3、vm = new Vue({data:myData}):

  • 会让 vm 成为 myData 的代理;
  • 会对 myData 的所有属性进行监控;
  • 为什么要监控:防止 myData 的属性变了,vm 不知道。一旦 vm 知道属性变了,就可以调用 render(data) 。
  • UI = render(data)

image.png

vue2 的数据响应式

  • const vm = new Vue({data:{n:0}});
  • 如果用户修改 vm.n ,那么 UI 中的 n 就会响应;
  • vue2 通过 Object.defineProperty 来实现数据响应式;

响应式网页

  • 改变窗口大小,网页内容会做出响应,就是响应式网页;
  • Smashing Magazine

Object.defineProperty 的问题

  • Object.defineProperty(obj,'n',{...}),必须要有一个 'n',才能监听和代理 obj.n ;
  • 如果开发者没有给 n ,Vue 会给出一个警告:
new Vue({
  data: {},
  template: `
    <div>{{n}}</div>
  `
}).$mount("#app");

image.png

  • Vue 只会检查第一层属性
new Vue({
  data: {
    obj: {
      a: 0 // obj.a 会被 Vue 监听 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      this.obj.b = 1; //请问,页面中会显示 1 吗?不会,因为 Vue 没办法监听一开始不存在的属性 obj.b
    }
  }
}).$mount("#app");
  • 解决方法 1、把所有的 key 都申明号,后面不在加属性就行;

2、使用 Vue.set 或者 this.$set

Vue.set 和 this.$set

  • 作用 1、新增 Key;

2、自动创建代理和监听(如果没有创建过);

3、触发 UI 更新(但并不会立刻更新) this.$set(this.object,'m',100)

  • data 中有数组 1、问题:没法提前声明所有 key,数组的长度可以一直增加,下标就是 Key ,没办法提前把数组的 Key 都声明出来,Vue 也不能检测新增了下标,每次该数组都用 Vue.set 或者 this.$set 太麻烦;

2、解决方法:Vue 篡改了数组的 API ,变异方法

变异方法

  • ES6 写法
class VueArray extends Array{
  push(...args){
    const oldLength = this.length //this 就是当前数组
    super.push(...args)
    console.log('你 push 了 ')
    for(let i = oldLength; i< this.length; i++){
      Vue.set(this,i,this[i])
      //将每个新增的 key 都告诉 Vue
    }
  }
}
  • ES5 写法-原型
const VueArrayPrototype = {
  push: function(){
    console.log('你 push 了')
    return Array.prototype.push.apply(this,arguments)
  }
}
VueArrayPrototype.__proto__ = Array.prototype
const array = Object.create(VueArrayPrototype)
array.push(1)

总结

对象中新增的 key

1、Vue 没有办法事先监听和代理;

2、要使用 set 来新增 key ,创建监听和代理,更新 UI ;

3、最好提前把属性都写出来,不要新增 key ;

4、但数组做不到‘不新增 key’.

数组中新增的 Key

1、可以用 set 来新增 key ,更新 UI ;

2、7个变异方法会自动处理监听和代理,并更新 UI ;

3、数组新增 key 最好通过7个API ;

小结

我们在声明Vue实例对象的时候,会初始化data,通过哈希表的形式接收数据,在初始化data中的数据后,vue会通过Object.defineProperty()方法来为key添加getter和setter方法实现data中数据的读写操作。以达到监听的目的,data中的每个数据都会进行遍历,然后使用这个方法来实现添加监听,在添加监听后,防止用户直接修改data中的内容,vue又让其实例对象成为data对象的代理,至此,vue实现了对data中数据的监听和代理操作。

但是如果data数据中有对象或者数组类型的数据时,我们无法实现它的增删操作,因为Object.defineProperty()方法只会对已经存在的key进行操作,所以vue为我们提供了vue.set()/this.$set()方法,它接受对象,要增加的key,以及value值,从而使我们可以增加key。但是这样又比较麻烦,因此我们在声明对象数据的时候,尽量提前声明所有key值,但是在对数组操作的时候,我们是无法直接这样添加key值的,所以vue又为我们提供了数组的变异方法,分别有push(),pop(),shift(),unshift(),splice(),sort(),reverse()七种变异方法,他们通过原型链/继承的方式,在实现基本数组增删操作的同时,又实现了vue对其数据的监听和代理,类似于内置了set方法。

测试

《Vue 自测题》