Vue入门--第二天

本文详细介绍了Vue的数据响应式机制,包括代理和监听、对象及数组的响应式处理。通过实例展示了如何在对象创建后添加属性并保持响应式,以及如何处理数组的变化。Vue在初始化时对data进行响应式处理,通过setter和getter实现数据的监听和更新。对于新增的属性,可以使用`Vue.set`或`this.$set`进行监听。对于数组,Vue通过修改原型链实现了`push`等方法的响应式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue的数据响应式

代理和监听

今天深入理解options.data
使用的是完整版的Vue做演示

先看一段代码

const myDate = {
  n: 0
}
console.log(myDate)

const vm = new Vue({
  data: myDate,
  template: `
    <div>
      {{n}}
      <button @click="add">+10</button>
    </div>
  `,
  methods: {
    add() {
      this.n += 10
    }
  }
}).$mount("#app");

setTimeout(() => {
  myDate.n += 10
  console.log(myDate) 
}, 3000)

打印出来的两个东西。
在这里插入图片描述

get的计算属性
get怎么用,就是在方法面前加get,然后调用的时候就可以不加括号。

有get那就有set

let obj3 = {: "高",
  名: "圆圆"get 姓名(){
    return this.+ this.}set 姓名(xxx) {//xxx是名字
    this.= xxx[0]
    this.= xxx.substring(1)
  },
  age: 18
}
console.log(obj3.姓名)//就可以打印出高圆圆,不加括号,用了get
obj3.姓名 = '刘诗诗'//就可以改变姓和名,触发了set函数
console.log(obj3);//把obj3打印出来

在这里插入图片描述

最后我们把obj3打印出来可以看到,这个姓名和之前的n差不多,(...),都长这样

说明之前得到的n也是一个get和一个set,姓名和这个n一样,并不是真实的属性

就是不存在真实的姓名,但是你可以对姓名读和写 通过get和set来完成

上面的get和set都是在对象创建的时候就写好的,如果我们想在对象创建后再去添加get和set

这个时候就用

Object.defineProperty(obj3, 'xxx',{
  get(){},
  set(){}
})

意思是在obj3上面创建了一个xxx是属性,里面有get和set,在上面的get和set上不能使用xxx

再看下面代码

let data0 = {
  n: 0
}

let data1 = {}

Object.defineProperty(data1, 'n', {value: 0})
//给data1定义一个属性n,n的值是0
//上面的两种操作看似效果一样,不过当我们有其他需求时,就不一样了。

//需求:n不能小于0
//即data2.n = -1因该无效,但data2.n = 1有效

let data2 = {}
data2._n = 0//因为defineProperty里面定义的属性,在get和set还不能使用该属性,所以定义一个存储值的东西_n
Object.defineProperty(data2, 'n', {
  get(){
    return this._n
  },
  set(value){
    if(value < 0) 
      return
      this._n = value
  }
})
console.log(`需求二:${data2.n}`)//0
data2.n = -1
console.log(`需求二:${data2.n}`)//打印出0,设置成-1失败,还是原来的值
data2.n = 1
console.log(`需求二:${data2.n}`)//打印出1,设置成1成功

//上面的也有一个问题,虽然data2.n不能变成负值,但是data2._n可以直接改

//需求:使用代理
let data3 = proxy({ data: {n:0} })//括号里是匿名对象,无法访问。

function proxy({data}){ //解构赋值,这个data是上面的data的引用
  const obj = {}

  Object.defineProperty(obj. 'n', {
    get(){
      return data.n
    },
    set(value) {
      if(value<0)return
      data.n = value
    }
  })//对obj的n做什么,就对data.n做什么
  return obj//obj就是代理
}

console.log(`需求三:${data3.n}`)//0
data3.n = -1
console.log(`需求三:${data3.n}`)//打印出0,设置成-1失败,还是原来的值
data3.n = 1
console.log(`需求三:${data3.n}`)//打印出1,设置成1成功
//这样就没有暴露出任何可以让你直接修改的属性

//上面是匿名对象{n: 0}
//下面改成不是匿名对象
let myData = {n: 0}
let data4 = proxy({ data: myData })//
//就可以直接使用myData.n = -1,像这样直接设置

//需求: 就算直接修改myData.n,也要拦截他
let myData5 = {n: 0}
let data5 = proxy2({ data: myData5 })

function proxy2({data}) {
  let value = data.n//用value来存储原始的值,
  //delete data.n//把原始数据的n删掉
  //上面这句话可以不写,因为下面的添加'n'属性,发现重名了,就会把上面的n直接覆盖掉。
  Object.defineProperty(data. 'n', {
    get(){
      return data.n
    },
    set(newValue) {
      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就是代理
}
//这样的话,外面的n就被删除了(覆盖),就不能直接赋值了
console.log(`需求5:${data5.n}`)//0
data5.n = -1
console.log(`需求5:${data5.n}`)//打印出0,设置成-1失败,还是原来的值
data5.n = 1
console.log(`需求5:${data5.n}`)//打印出1,设置成1成功

把上面的代码细品一下

let data5 = proxy2({ data:myData5 })
const vm = new Vue({data: {n:0} })

发现和Vue实例创建的时候差不多。

所以最开始看到的打印出来的对象,Vue的原理就是这样的。

vm = new Vue({data:meData})

  • 会让vm成为myDate的代理
  • 会让myData的所有属性进行监控
  • 为啥要监控,防止myData的属性变了,vm不知道
  • vm知道了,就可以UI = render(data)
    就是把myData的值全部换了一个地方,换到了vm自身身上。不能通过myData来修改。

返回第一张图,第一段代码,两个console.log的地方很精髓
传入之后myData就发生了变化,Vue把myData篡改了。
原始的data.n没有被删掉,只是在改这个对象里面的东西
一个是代理,一个是被修改过后的原始对象

这里Vue有个问题,算小bug吧
上面说的Vue在初始化的时候使用了Object.defineProperty(obj, 'n',{...})
所以必须给了n才能去监听n,如果没有给n,怎么监听呢

new Vue({
  data: {},
  template: `
    <div>
      {{n}}
    </div>
  `
}).$Mount("#app")

页面不会打印出n,并且抛出一个警告,没有n

new Vue({
  data: {
    obj: {
      a: 0
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      this.obj.b = 1;
    }
  }
}).$Mount("#app")

这样写就不会报错,因为Vue监听了第一层的obj,点击按钮,页面会出现obj.b吗?
不会出现,因为在最开始的时候,Vue只是监听了obj.a,没有监听obj.b,所以没有反应。

解决
  1. 可以事先声明好,赋值undefined,后面再加属性就行了
  2. 使用Vue.set或者this.$set

  methods: {
    setB() {
      //this.obj.b = 1;
      Vue.set(this.obj, 'b', 1);
      this.$set(this.obj, 'b', 1)
    }
  }

Vue.setthis.$set,就相当于调用了Object.defineProperty(obj, 'n',{...}),就对obj.b进行了监听

  • 新增key
  • 自动创建代理和监听(如果没有创建过)
  • 出发UI更新(但不会立刻更新)

其实就是最开始调用Object.defineProperty(obj, 'n',{...}),不过是,最开始没监听上,后面再调用这个去就监听

代理是写在vm上的,监听是改当前的data对象。

data里面有数组怎么办

数组的长度可以一直增加,下标就是key
array: ["a", "b", "c"]
等价于
array: {0: 'a', 1: 'b', 2: 'c'}
所以直接用

methods: {
  setD() {
    this.array[3] = 'd';
  }
}

来向数组里面增加东西是没有用的。

只能通过

methods: {
  setD() {
    this.$set(this.array, 3, 'd');
  }
}

数组提前声明是没用的,不知道有多少项,但是每次都写this.$set(this.array, 3, 'd')又太麻烦。

Vue作者就提供了使用push
this.array.push("d");
就可以直接往数组里加东西了,但是这个push,不是原来数组对象上的push,而是被Vue的作者改了的push

来看看他是怎么改的呢

methods: {
  setD() {
    this.array.push("d");
    console.log(this.array);
  }
}

在这里插入图片描述

从打印出的对象可以看出,和之前的对象的方法不一样了,这就是Vue作者在array对象和array原型中间加了一层原型,这个push是Vue作者自己写的。所以可以直接push进数组里面

改了的push也很简单

  • 调用原来的push
  • 告诉Vue,添加监听这个新push进来的东西
class VueArray extends Array {
  push(...args) {
    super.push(...args)//调用父类的push
    //添加监听的操作,自己的代码
  }
}

中间加的代码就是对push篡改了,中间加了一层自己写的一些代码。

这就是大概思路。

上面是es6的写法,es5的写法要难一点

const vueArrayPrototype = {
  push: function(){
    //自己加的代码
    return Array.prototype.push.apply(this, arguments)//es6的写法简单,es5的写法难一点,调用数组对象本身的push,然后改this的指向
  }
}
vueArrayPrototype.__proto__ = Array.prototype

es5的大概思路是这样

总结

对象中新增的key
  • Vue没有办法事先来监听和代理
  • 可以用set来新增key,创建监听和代理,更新UI
  • 但是数组做不到(不去新增key),this.$set又很麻烦
数组中新增key
  • Vue的作者篡改了原型,来让我们能使用push等方法,直接改变data中的数组

这些api在Vue.js文档中的变异方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值