Vue响应式数据原理

Vue 前端工程师必须技能之一

vue基础使用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../js/vue.js"></script>
</head>

<body>

  <div id="app">
    <!-- 这里的变量就是vm 实例的属性 -->
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}</p>
    <p>性别:{{gender}}</p>
  </div>

  <script>
    // vm 是 vue 的实例
    const vm = new Vue({
      el: '#app', // el 挂载
      data: {
        name: 'fqniu',
        age: 18,
        gender: '男'
      }
    })

    console.log(vm)

    /**
      架构分层
        * MVC
            * Model         数据层
            * View          视图层
            * Controller    控制层  业务逻辑处理(M和V之间的连接器)
            * 缺点:依赖复杂
        * MVP
            * Model         数据层
            * View          视图层
            * Presenter     可以理解为松散的控制器,其中包含了视图的 UI 业务逻辑,所有从视图发出的事件,
                            都会通过代理给 Presenter 进行处理;同时,Presenter 也通过视图暴露的接口
                            与其进行通信。
        * MVVM
            * Model         数据层
            * View          视图层
            * ViewModel     类似与MVP中的Presenter,唯一的区别是,它采用双向绑定:View的变动,
                            自动反映在 ViewModel,反之亦然
    */
  </script>
</body>

</html>

架构模式

复杂的软件必须有清晰合理的架构,更容易开发、维护和测试。

MVC

MVC模式的意思是,软件可以分成三个部分。

  • 模型(Model):数据处理
  • 视图(View):数据展示
  • 控制器(Controller):业务逻辑处理(M和V之间的连接器)

在这里插入图片描述

  1. View 传送指令到 Controller(用户发送指令)
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈
  • 缺点:依赖复杂
    • View 依赖Controller和Model
    • Controller依赖View和Model

MVP

MVP 架构模式是 MVC的改良模式(改进Controller, 把Model和View完全隔离开)

  • Model
  • View
  • Presenter 可以理解为松散的控制器,其中包含了视图的 UI 业务逻辑,所有从视图发出的事件,都会通过代理给 Presenter 进行处理;同时,Presenter 也通过视图暴露的接口与其进行通信。

在这里插入图片描述

MVVM

由MVP模式演变而来

  • Model
  • View
  • ViewModel 类似与MVP中的Presenter,唯一的区别是,它采用双向绑定:View的变动,自动反映在 ViewModel,反之亦然

在这里插入图片描述

  • 核心思想:关注Model的变化,让MVVM框架利用自己的机制去自动更新DOM,从而把开发者从操作DOM的繁琐中解脱出来!

响应式数据的原理

   /**
      响应式数据的原理:
        * 响应式属性:能监听到修改操作,并更新视图 ==== 数据更新,页面跟着更新
    
        * 如何设置一个属性为响应式属性(属性特性)
          * 值属性:拥有值的属性
            * configurable  可配置性(其他属性特性的总开关)
            * enumerable    可枚举性
            * writable      可写性
            * value         属性的值

          * 存储器属性:本身没有值,一般用于代理其他数据
              * configurable 
              * enumerable
              * get
              * set
              
        * 获取属性特性:
          *  Object.getOwnPropertyDescriptor(target,pro)
          * Object.getOwnPropertyDescriptors(target)
         
        * 设置属性特性:
          * Object.defineProperty(target,prop,descriptor)
          * Object.defineProperties(target,descriptor)


      特别说明:
          1. 传统方式添加的属性,所有属性特性默认为true
          2. 通过Object.defineProperty()添加的属性,所有属性特性默认为false
          
    */

    // 1、值属性
    const data = {
      name: 'fqniu',
      age: 18
    }
    // 传统添加属性的方式
    data.gender = '男'

    // 2、通过Object.definePropery(target,prop,descriptor)
    //    target:      目标对象
    //    prop:        目标属性
    //    descriptor:  属性特性

    // descriptor: 属性特性的方法:
    // 1、writable 目的:控制data 里面某个属性不可更改 比如data.age
    Object.defineProperty(data, 'age', {
      writable: false
    })
    // 2、enumerable 目的:控制data某个属性不可枚举 比如data.password
    Object.defineProperty(data, 'password', {
      enumerable: false
    })

    // 循环遍历对象
    for (let key in data) {
      console.log('key=', key)
      console.log('data=', data[key])
    }
    // key= name
    // key= age
    // key= gender
    // 发现并没有打印出 password 属性

    // 传统的添加属性方式
    // data.job="front end"  //job的所有属性特性为true
    // console.log(data)  
    // 注意 password 属性的颜色,明显是淡色 而且__proto__也是淡色
    // 而且淡色的属性是不会遍历出来的

    // Object.defineProperty() 添加的属性
    Object.defineProperty(data, 'job', {
      value:'front end',  //等效于data.job = "front end"
      configurable:true
    })
    console.log(data)

    // 特别说明:
    //  1. 传统方式添加的属性,所有属性特性默认为true
    //  2. 通过Object.defineProperty()添加的属性,所有属性特性默认为false

存储器属性

传统方法 添加存储器属性 和 Object.defineProperty() 添加存储器属性

   // 存储器属性
    const user={
      name:'fqniu',
      // gender:'女',

      // 在传统对象中 添加存储器属性 gender
      get gender(){
        return '男'
      },
      set gender(newval){
        console.log('gender.setter=',newval)
      }
    }

    // console.log(user)

    const newUser={
      age:25
    }

    // 通过添加 Object.defineProperty() 添加 age 属性
    Object.defineProperty(user,'age',{
      get(){
        console.log('getter')
        return newUser.age
      },
      
      set(newValue){
        console.log('setter=',newValue)
        newUser.age=newValue
      }
    })
    // console.log(user)
    //  user.age       读取时,获取getter 方法  打印getter
    //  user.age = 18  读取时,获取setter 方法  打印setter

注意:此时newUser里面的属性已被改变。

在这里插入图片描述
在这里插入图片描述

响应式属性的原理

 // 响应式属性的原理:把值属性变为存储器属性 ( getter && setter)
    const obj = {
      name: 'fqniu',
      age: 25
    }
    box.innerHTML = obj.name

    const newObj = {}
    // 把值属性变为存储器属性
    for (let key in obj) {
      Object.defineProperty(newObj, key, {
        get() {
          return obj[key]
        },
        set(newVal) {
          box.innerHTML = newVal
          obj[key] = newVal
        }
      })
    }
    console.log(obj)
    console.log(newObj)
     /*
      通过 改变 newObj 这个对象里面的值 去改变 obj 里面的属性的值
      这就是 vue 里面的实例 vm 内部做的事情 ,也就是viewmodel 来同时 改变 数据层 和 视图层
      MVVM 中的 VM 检测 数据层 和 视图层 的改变
    */

在这里插入图片描述

在这里插入图片描述

 // vue  响应式数据
    const initData = {
      name: 'fqniu',
      age: 25,
      score: {
        eng: 92,
        math: 96
      }
    }

    const vm = new Vue({
      el: '#app',
      data: initData
    })

    console.log(vm)

    // Vue.set(vm.score, 'chinese', 86);

在这里插入图片描述

vue2.x数组中哪些方法是响应式的(vue3.0已经解决)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <ul>
      <li v-for="item in arr">{{item}}</li>
    </ul>
    <button @click="btnclick">按钮</button>
  </div>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好,世界!',
        arr: ['a', 'b', 'c', 'd']
      },
      methods: {
        btnclick() {
          // 数组中响应式方法有:
          // 1、添加元素从最后一个
          this.arr.push('111', '222');
          // 2、添加元素从第一个
          this.arr.unshift('aaa', 'bbb');
          // 3、删除元素从最后一个
          this.arr.pop('ccc', 'ddd');
          // 4、删除元素从第一个
          this.arr.shift('aaa', 'bbb');
          // 5、splice作用:删除元素、插入元素、替换元素
          // 删除元素:
          this.arr.splice(1);//表示从第一个开始,后面的全部删除
          this.arr.splice(1, 2);//表示从第一个开始,删除后面的两个
          // 替换元素:
          this.arr.splice(1, 4, 'e', 'f', 'g', 'h');//表示从第一个开始,替换后面4个值内容
          // 插入元素:
          this.arr.splice(1, 0, 'e', 'f', 'g', 'h');//表示从第一个开始,0代表不删除元素,后面是添加的元素
          //6、sort 排序
          this.arr.sort();
          //7、reverse 反转
          this.arr.reserve();

          //------ 注意:通过索引值修改数组中的元素,不是响应式的,界面没有改变----------------
          this.arr[0] = 'aaaa';

          // 1、响应式可以修改数组中的元素:通过splice
          this.arr.splice(0, 1, 'aaaa');
          //2、 响应式可以修改数组中的元素:set(要修改的元素,索引值,修改后的值)
          Vue.set(this.arr, 0, 'aaaa');


        }
      }
      // 也就是上面说的,通过索引值修改数组中的元素,不是响应式的,界面没有改变
      vm.arr[0] = 'e';  // 这种做不到响应式 数据更新,视图没有更新
      // 或者通过vue的方法 把数组中的第一项改为 e 
      vm.$set(vm.arr, 0 , 'e');
      // 或者 第 0 索引, 删除1 项 ,新增一项
      vm.arr.splice(0, 1, 'e');
      // ----  数组的长度也是不能检测到变化 ------ 可以通过下面方式修改
      vm.$delete(vm.arr, 1);
      // 或者 使用splice
      vm.arr.splice(2);
    })
  </script>
</body>

</html>

对象中的 delete 方法不是响应式的


// 该 方法做不到响应式
delete state.info.age 

// 通过vue内部 删除对象中的某个属性
Vue.delete(state.info, 'age')

vue自定义指令(部分)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="../bootstrap/css/bootstrap.css">
</head>

<body>
  <!-- 
    v-for: 遍历数据          使用 v-for="item,idx in/of xxx"
    v-on:  绑定事件          使用 v-on:事件 = "事件函数" / 简写 @
    v-bind: 动态绑定数据     使用 v-bind:属性="变量" 或者 v-bind:属性="{ 变量:判断条件 }" 简写 :剩下: 即可
    v-show: 是否显示         使用 v-show:"判断条件"  true/false  条件成立显示 否则隐藏
    v-if/v-else/v-else-if   使用 v-if:"判断条件"  true/false  条件成立创建 否则销毁 用于兄弟同级之间

    注意:v-show和v-if的区别???
       v-show是显示和隐藏 而v-if是 创建和销毁 所以v-if指令存在游览器性能消耗。
       建议如果只是显示和隐藏 , 用v-show即可 
       
    注意:v-for为什么不能和v-if连用?(其实不是不可以,会造成性能浪费)
       因为v-for的优先级比v-if的高,会优先执行v-for,这样的话会先循环,再判断对性能浪费
       所以如果使用的话,可以在v-for外面再套用一个标签 里面用v-if判断,先判断,再循环
       如果外层不想多一个标签 可用template ,就不会多一个标签了
       <template v-if="flag">
            <div v-for="(item,idx) in xxxx" key="item.idx">{{ item }}</div>
       </template>
       或者:嵌套使用
       <div v-for="(item,idx) in xxxx" key="item.idx">
             <template v-if="idx % 2">xxx</template>
             <template v-else>xxxx</template>
       </div>
       

    单向数据绑定:
      1{{ }}     只能写在标签里面 等效于v-text
      2、v-text    把html 当文本处理
      3、v-html    可以解析html 
      4、v-bind    给属性绑定数据 比如 v-bind:value =""

    双向数据绑定
    v-model  v-model="数据" 一般用于表单元素 实现双向 视图层到数据层 / 数据层到视图层
    双向数据绑定:能够让我们修改表单之后,不用操作DOM元素就可以获取表单元素的值。
    单向数据绑定:能够让我们不必操作DOM元素,去渲染数据了。
   -->
  <div id="app">
    <ul class="nav nav-tabs">
      <li class="nav-item" v-for="(item,idx) in tablist" v-on:click="changeTab(idx)">
        <a class="nav-link" href="#" v-bind:class="{active:tabflag===idx}">{{ item.name }}-{{ idx }}</a>
      </li>
    </ul>
    <div class="container p-3">
      <div v-for="(item,idx) in tablist" v-show="tabflag===idx">{{item.container}}</div>
    </div>

    <!-- 多种数据绑定的区别 -->
    <p>{{ test }}</p>
    <p v-text="test"></p>
    <p v-html="test"></p>
    <p v-bind:title="test">12</p>

  </div>



  <script src="../js/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        tablist: [{
          name: 'fqniu',
          container: '吃美食'
        }, {
          name: 'niuniu',
          container: '学游泳'
        }, {
          name: '小牛牛',
          container: '宅屋看书'
        }, {
          name: '牛先森',
          container: '敲代码'
        }],
        tabflag: 0,
        test: "吃东西"
      },
      methods: {
        changeTab(idx) {
          this.tabflag = idx
        }
      }
    })
  </script>
</body>

</html>

在这里插入图片描述

todolist

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="../bootstrap/css/bootstrap.css">
  <script src="../js/vue.js"></script>
  <style>
    body,
    html {
      height: 100%;
      background: #ccc;
    }

    #app {
      width: 800px;
      margin: 0 auto;
    }

    table {
      background: #fff;
    }
    th{
      width: 25%;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1>todolist</h1>
    <div class="input-group mb-3" style="width: 800px;">
      <input type="text" class="form-control" placeholder="请输入内容" ref="search" @keyup.enter="addItem"
        v-model="todoTarget">
      <div class="input-group-append">
        <button class="btn btn-success" type="button" v-on:click="addItem">添加</button>
      </div>
    </div>
    <table class="table" style="width: 800px;">
      <thead class="thead-dark">
        <tr>
          <th scope="col">序号</th>
          <th scope="col">待办事项</th>
          <th scope="col">是否完成</th>
          <th scope="col">操作</th>
          <!-- <th scope="col">添加时间</th> -->
        </tr>
      </thead>

      <tbody>
        <tr v-for="(item,idx) in todolistData">
          <th scope="row" v-text="idx + 1"></th>
          <td v-text="item.target"></td>
          <td v-text="item.done ? '是' : '否'"></td>
          <td>
            <button class="btn btn-danger btn-sm" v-on:click="removeItem(item.id)">删除</button>
            <button class="btn btn-success btn-sm" v-on:click="completeItem(item.id)">完成</button>
          </td>
          <!-- <td>{{ formatDate(item.addtime)}}</td> -->
        </tr>
      </tbody>
    </table>
  </div>

  <script>
    const vm = new Vue({
      el: "#app",
      data: {
        todolistData: [
          //   {
          //   id: 1,
          //   target: '按时吃饭',
          //   done: false,
          //   addtime: Date.now()
          // }, {
          //   id: 2,
          //   target: '早睡早起',
          //   done: false,
          //   addtime: Date.now()
          // }
        ],
        // 通过v-model双向绑定 获取v-model中的数据
        todoTarget: ""
      },
      methods: {
        // 添加
        addItem() {
          if (this.todoTarget.trim()) {
            const data = {
              id: this.todolistData.length + 1,
              target: this.todoTarget,
              done: false,
              addtime: Date.now()
            }
            this.todolistData.unshift(data)
            // 清空并自动获取焦点
            this.todoTarget = ''
            this.$refs.search.focus()
          }
        },
        // 删除
        removeItem(id) {
          this.todolistData = this.todolistData.filter(item => item.id !== id)
        },
        // 完成
        completeItem(id) {
          this.todolistData = this.todolistData.map(item => {
            if (item.id === id) {
              item.done = true
            }
            return item
          })
        },
        // // 日期
        // formatDate() {
        //   let d = new Date();
        //   let year = d.getFullYear();
        //   let month = d.getMonth() + 1;
        //   let day = d.getDate();
        //   let hours = d.getHours();
        //   let min = d.getMinutes();
        //   let sec = d.getSeconds();

        //   hours = hours < 10 ? '0' + hours : hours
        //   min = min < 10 ? '0' + min : min
        //   sec = sec < 10 ? '0' + sec : sec

        //   return `${year}-${month}-${day} ${hours}:${min}:${sec}`
        // }
      }
    })
  </script>
</body>

</html>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值