D27 Vue2 + Vue3 K2-K43

D27.Vue

 F1.Vue泛简介(K3-K9)

  1.Vue简介
   1)官网:https://cn.vuejs.org
   2)简介:
     Vue 是一套用来动态构建用户界面的渐进式JavaScript框架 ○构建用户界面:把数据通过某种办法变成用户界面 ○渐进式:Vue可以自底向上逐层的应用,简单应用只需要一个轻量小巧的核心库,复杂应用可以引入各式各样的Vue插件
   3)Vue 的特点
    a. 遵循MVVM模式
    b. 编码简洁,体积小,运行效率高,适合移动/PC端开发
    c. 它本身只关注 UI,可以引入其它第三方库开发项目
    d. 采用组件化模式,提高代码复用率、且让代码更好维护
    e. 声明式编码,让编码人员无需直接操作DOM,提高开发效率
在这里插入图片描述

    f. 使用虚拟DOM 和 Diff算法,尽量复用DOM节点
在这里插入图片描述

   4)与其他 JS 框架的关联

  • 借鉴 angular 的 模板 和 数据绑定 技术
  • 借鉴 react 的 组件化 和 虚拟DOM 技术
  •    5)Vue 周边库

  • vue-cli:vue 脚手架
  • vue-router:路由
  • vuex:状态管理(它是 vue 的插件但是没有用 vue-xxx 的命名规则)
  • vue-lazyload:图片懒加载
  • vue-lazyload:图片懒加载
  • mint-ui:基于 vue 的 UI 组件库(移动端)
  • element-ui:基于 vue 的 UI 组件库(PC 端)
  •   2.初识Vue
      A.前置工作
       1)给浏览器安装 Vue Devtools 插件
       2)标签引入Vue包
       3)(可选)阻止vue在启动时生成生产提示Vue.config.productionTip = false
       4)favicon 需要将页签图标放在项目根路径,重新打开就有了(shfit+F5 强制刷新)
      B.初识Vue
       1)想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
       2)root 容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
       3)root 容器里的代码被称为Vue模板
       4)Vue 实例与容器是一一对应的
       5)真实开发中只有一个Vue实例,并且会配合着组件一起使用
       6){{xxx}}中的 xxx 要写 js 表达式,且 xxx 可以自动读取到data中的所有属性 注意区分:js 表达式 和 js代码(语句)
        a. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方 a a+b demo(1) x === y ? ‘a’ : ‘b’
        b. js代码(语句) if(){} for(){}
       7)一旦data中的数据发生变化,那么模板中用到该数据的地方也会自动更新(Vue实现的响应式)
       8)代码(或直接下载文件创建单元js)

        <!-- 引入Vue -->
        <script type="text/javascript" src="../js/vue.js"></script>
    <!-- 准备好一个容器 -->
    <div id="demo">
    	<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
    </div>
    
    <script type="text/javascript" >
    	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
    	//创建Vue实例
    	new Vue({
    		el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
    		data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
    			name:'hello,world',
    			address:'北京'
    		}
    	});
    
    </script>
    

      3.模板语法和数据绑定
       1)模板语法
         Vue模板语法有2大类:

  • 插值语法:
  •     a. 功能:用于解析标签体内容

        b. 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

  • 指令语法:
  •     a. 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)

        b. 举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可直接读取到data中的所有属性

        c. 备注:Vue中有很多的指令,且形式都是 v-xxx,此处只是拿v-bind举例

        d. 代码

        <div id="root">
          <h2>插值语法</h2>
          <h4>你好,{{ name }}</h4>
          <hr />
          <h2>指令语法</h2>
          <a v-bind:href="tencent.url.toUpperCase()" x="hello">点我去看{{ tencent.name }}1</a>
          <a :href="tencent.url" x="hello">点我去看{{ tencent.name }}2</a>
        </div>
    
      <script type="text/javascript">
        new Vue({
          el: '#root',
          data: {
            name: 'jack',
            tencent: {
              name: '开端',
              url: 'https://v.qq.com/x/cover/mzc00200mp8vo9b/n0041aa087e.html',
            }
          }
        })
      </script>
    

    在这里插入图片描述
       2)数据绑定
         Vue中有2种数据绑定的方式:

  • 单向绑定(v-bind):数据只能从data流向页面
  • 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
  •      双向绑定一般都应用在表单类元素上(如:input、select等)

         v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值
         代码

    <div id="root">
    	<!-- 普通写法 单向数据绑定 -->
        单向数据绑定:<input type="text" v-bind:value="name"><br/>
        双向数据绑定:<input type="text" v-model:value="name"><br/>
        
        <!-- 简写 v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值-->
        单向数据绑定:<input type="text" :value="name"><br/>
        双向数据绑定:<input type="text" v-model="name"><br/>
        
        <!-- 如下代码是错误的,因为 v-model 只能应用在表单类元素(输入类元素)上 -->
    	<!-- <h2 v-model:x="name">你好啊</h2> -->
    </div>
    
    <script>
        new Vue({
    		el:'#root',
    		data:{
    			name:'jack',
            }
    	})
    </script>
    

     F2.el和data的两种写法 + MVVM模型(K10-K16)

      1.el和data的两种写法
      A.el有2种写法
       1)创建Vue实例对象的时候配置el属性
       2)先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
      B.data有2种写法
       1)对象式:data: { }
       2)函数式:data() { return { } }
       3)如何选择:目前哪种写法都可以,以后到组件时,data必须使用函数,否则会报错 一个重要的原则
       4)由Vue管理的函数,一定不要写箭头函数,否则 this 就不再是Vue实例了

      <body>
        <div id="root">
          <h1>你好,{{name}}</h1>
        </div>
      </body>
      <script type="text/javascript">
        // el的两种写法
        // const v = new Vue({
        // 	//el:'#root', // 第一种写法
        // 	data: {
        // 		name:'dselegent'
        // 	}
        // })
        // console.log(v)
        // v.$mount('#root') // 第二种写法
    
        // data的两种写法
        new Vue({
          el: '#root',
          // data的第一种写法:对象式
          // data:{
          // 	name:'dselegent'
          // }
    
          //data的第二种写法:函数式
          data() {
            console.log('@@@', this) // 此处的this是Vue实例对象
            return {
              name: 'dselegent'
            }
          }
        })
      </script>
    

      2.MVVM模型
    在这里插入图片描述

       1)MVVM模型
         M:模型 Model,data中的数据
         V:视图 View,模板代码
         VM:视图模型 ViewModel,Vue实例(相当于数据和页面的连接桥梁)
       2)观察发现
         data中所有的属性,最后都出现在了vm身上
         vm身上所有的属性 及Vue原型身上所有的属性,在 Vue模板中都可以直接使用

        <div id="root">
            <h2>名称:{{ name }}</h2>
            <h2>战队:{{ team }}</h2>
            <h2>测试:{{ $options }}</h2>
        </div>
    
        <script>
            Vue.config.productionTip = false
            new Vue({
                el: '#root',
                data: { 
                    name: 'uzi',
                    team: 'RNG'
                }
            })
        </script>
    

     F3.数据代理 事件处理(K17-K23)

      1.数据代理
      A.数据代理
       1)属性标志:
         对象属性(properties),除 value 外,还有三个特殊的特性(attributes),也就是所谓的“标志”
        a. writable — 如果为 true,则值可以被修改,否则它是只可读的
        b. enumerable — 如果为 true,则表示是可以遍历的,可以在for… .in Object.keys()中遍历出来
        c. configurable — 如果为 true,则此控制属性可以被删除,默认值是false
         Object.defineProperty(obj, prop, descriptor)
        a. obj:要定义属性的对象
        b. prop:要定义或修改的属性的名称
        c. descriptor:要定义或修改的属性描述符

    let number = 18
    let person = {
      name: '张三',
      sex: '男',
    }
    
    Object.defineProperty(person, 'age', {
      // value:18,
      // enumerable:true,		// 控制属性是否可以枚举,默认值是false
      // writable:true,			// 控制属性是否可以被修改,默认值是false
      // configurable:true	// 控制属性是否可以被删除,默认值是false
    
      // 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
      get() {
        console.log('有人读取age属性了')
        return number
      },
    
      // 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
      set(value) {
        console.log('有人修改了age属性,且值是', value)
        number = value
      }
    
    })
    // console.log(Object.keys(person))
    console.log(person)
    

      B.vue中的数据代理
       1)数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

          let vm = {};
          let data = {
            name: 'ds',
            age: 18,
          };
          Object.defineProperty(vm, 'age', {
            get() {
              return data.age;
            },
            set(value) {
              data.age = value;
            },
          });
    

    在这里插入图片描述

         使用{{}}插值语法获取vm的x时,触发get方法,将data的x赋值给vm的x
         修改data中的age时并没有改变vm里的age的值,当{{}}获取vm中age的值时,就会将data的age赋值给vm的age
         修改vm中的age,触发set方法,将修改的值赋值给data中的age
        a. Vue中的数据代理通过vm对象来代理data对象中属性的操作(读/写)
        b. Vue中数据代理的好处:更加方便的操作data中的数据
        c. 基本原理
    在这里插入图片描述

          通过object.defineProperty()把data对象中所有属性添加到vm上
          为每一个添加到vm上的属性,都指定一个 getter,setter
          在getter,setter内部去操作(读/写)data中对应的属性
          Vue将data中的数据拷贝了一份到_data属性中,又将_data里面的属性提到Vue实例中(如name),通过defineProperty实现数据代理,这样通过geter/setter操作 name,进而操作_data中的 name。而_data又对data进行数据劫持,实现响应式
         name被修改–>调用setter–>重新解析模板–>生成新的虚拟DOM–>新旧DOM对比(diff)–>更新页面
      2.事件处理
      A.事件的基本用法
       1)使用v-on:xxx或@xxx绑定事件,其中 xxx 是事件名
       2)事件的回调需要配置在methods对象中,最终会在vm上
       3)methods中配置的函数,不要用箭头函数,否则 this 就不是vm了
       4)methods中配置的函数,不要用箭头函数,否则 this 就不是vm了
       5)@click="demo"和@click="demo($event)"效果一致,但后者可以传参

    <!-- 准备好一个容器-->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- <button v-on:click="showInfo">点我提示信息</button> -->
        <button @click="showInfo1">点我提示信息1(不传参)</button>
        <!-- 主动传事件本身 -->
        <button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                name:'vue',
            },
            methods:{
                // 如果vue模板没有写event,会自动传 event 给函数
                showInfo1(event){
                    // console.log(event.target.innerText)
                    // console.log(this) //此处的this是vm
                    alert('同学你好!')
                },
                showInfo2(event,number){
                    console.log(event,number)
                    // console.log(event.target.innerText)
                    // console.log(this) //此处的this是vm
                    alert('同学你好!!')
                }
            }
    	});
    </script>
    

      B.事件修饰符
         Vue中的事件修饰符 1.prevent 阻止默认事件(常用) 2.stop 阻止事件冒泡(常用) 3.once 事件只触发一次(常用) 4.capture 使用事件的捕获模式 5.self 只有event.target是当前操作的元素时才触发事件 1.passive 事件的默认行为立即执行,无需等待事件回调执行完毕 修饰符可以连续写,比如可以这么用:@click.prevent.stop=“showInfo”

        <style>
          * {margin-top: 20px;}
          .demo1 {height: 50px;background-color: skyblue;}
          .box1 {padding: 5px;background-color: skyblue;}
          .box2 {padding: 5px;background-color: white;}
          .list {width: 200px;height: 200px;background-color: skyblue;overflow: auto;}
          li {height: 100px;}
        </style>
    
        <div id="root">
          <h2>欢迎来到{{ name }}学习</h2>
          <!-- 阻止默认事件(常用) -->
          <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
    
          <!-- 阻止事件冒泡(常用) -->
          <div class="demo1" @click="showInfo">
            <button @click.stop="showInfo">点我提示信息</button>
            <!-- 修饰符可以连续写 -->
            <!-- <a href="http://www.qq.com" @click.prevent.stop="showInfo">点我提示</a> -->
          </div>
    
          <!-- 事件只触发一次(常用) -->
          <button @click.once="showInfo">点我提示信息</button>
    
          <!-- 使用事件的捕获模式 -->
          <div class="box1" @click.capture="showMsg(1)">捕获到的时候就直接触发
            div1
            <div class="box2" @click="showMsg(2)">
              div2
            </div>
          </div>
    
          <!-- 只有event.target是当前操作的元素时才触发事件; -->
          <div class="demo1" @click.self="showInfo">
            <button @click="showInfo">点我提示信息</button>
          </div>
    
          <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
          <!-- scroll是滚动条滚动,passsive没有影响 -->
          <!-- wheel是鼠标滚轮滚动,passive有影响 -->
          <ul @wheel.passive="demo" class="list">
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
          </ul>
        </div>
    
        <script type="text/javascript">
          Vue.config.productionTip = false
    
          new Vue({
            el: '#root',
            data: {
              name: '尚硅谷'
            },
            methods: {
              showInfo(e) {
                alert('同学你好!')
                // console.log(e.target)
              },
              showMsg(msg) {
                console.log(msg)
              },
              demo() {
                for (let i = 0; i < 100000; i++) {
                  console.log('#')
                }
                console.log('累坏了')
              }
            }
          })
        </script>
    

      C.键盘事件
         键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用
       1)Vue中常用的按键别名 回车enter 删除delete捕获“删除”和“退格”键 退出esc 空格space 换行tab特殊,必须配合keydown去使用 上up 下down 左left 右right
       2)Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(多单词小写短横线写法 NumLock(num-lock) CapsLock(caps-lock))
       3)系统修饰键(用法特殊)ctrl、alt、shift、meta(meta就是win键) 3.1配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发 指定 ctrl+y 使用 @keyup.ctrl.y 3.2配合keydown使用:正常触发事件
       4)也可以使用keyCode去指定具体的按键**(不推荐)** @keyup.13=xxx(@keyup.enter=xxx)
       5)Vue.config.keyCodes 自定义键名 = 键码,可以去定制按键别名

        <div id="root">
          <h2>欢迎打开{{name}}笔记</h2>
          <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"><br/>
          <input type="text" placeholder="按下tab提示输入" @keydown.tab="showInfo"><br/>
          <input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo"><br/>
        </div>
    	
        <script type="text/javascript">
          Vue.config.productionTip = false	// 阻止 vue 在启动时生成生产提示。
          Vue.config.keyCodes.huiche = 13		// 定义了一个别名按键
    
          new Vue({
            el: '#root',
            data: {
              name: 'vue'
            },
            methods: {
              showInfo(e) {
                // console.log(e.key,e.keyCode)
                console.log(e.target.value)
              }
            },
          })
        </script>
    

     F4.计算属性 + 侦听属性(K24-K32)

      1.计算属性
       1)插值语法实现

    <title>姓名案例_插值语法实现</title>
    
    <div id="root">
      姓:<input type="text" v-model="firstName"> <br/>
      名:<input type="text" v-model="lastName"> <br/>
      全名:<span>{{ firstName }}-{{ lastName }}</span>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el:'#root',
        data:{
          firstName:'张',
          lastName:'三'
        }
      })
    </script>
    

    在这里插入图片描述
       2)method实现
         数据发生变化,模板就会被重新解析

    <title>姓名案例_methods实现</title>
    
    <div id="root">
      姓:<input type="text" v-model="firstName"><br/>
      名:<input type="text" v-model="lastName"><br/>
      全名:<span>{{ fullName() }}</span>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el: '#root',
        data: {
          firstName: '张',
          lastName: '三'
        },
        methods: {
          fullName() {
            return this.firstName + '-' + this.lastName
          }
        },
      })
    </script>
    

       3)computed实现
        a. 定义:要用的属性不存在,要通过已有属性计算得来
        b.原理:底层借助了Objcet.defineProperty方法提供的getter和setter
        c.get函数什么时候执行?
          初次读取时会执行一次
          当依赖的数据发生改变时会被再次调用
        d.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
        e.备注:
          计算属性最终会出现在vm上,直接读取使用即可
          如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
          如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
         完整写法

    <!-- 准备好一个容器-->
    <div id="root">
        姓:<input type="text" v-model="firstName">
        名:<input type="text" v-model="lastName"> 
        全名:<span>{{fullName}}</span>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                firstName:'张',
                lastName:'三',
            }
            computed:{
                fullName:{
                    //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
                    //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
                    get(){
                        console.log('get被调用了')
                        return this.firstName + '-' + this.lastName
                    },
                    //set什么时候调用? 当fullName被修改时。
                    // 可以主动在控制台修改fullName来查看情况
                    set(value){
                        console.log('set',value)
                        const arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            }
        })
    </script>
    

    在这里插入图片描述
         简写

    <!-- 准备好一个容器-->
    <div id="root">
        姓:<input type="text" v-model="firstName">
        名:<input type="text" v-model="lastName"> 
        全名:<span>{{fullName}}</span>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                firstName:'张',
                lastName:'三',
            }
            computed:{
                fullName() {
            		console.log('get被调用了')
    				return this.firstName + '-' + this.lastName
        		}
            }
        })
    </script>
    

         读取fullName时会自动调用get方法
       4)method和computed区别

    <title>姓名案例_methods实现</title>
    
    <div id="root">
      姓:<input type="text" v-model="firstName"><br/>
      名:<input type="text" v-model="lastName"><br/>
      全名:<span>{{ fullName() }}</span>
      全名:<span>{{ fullName() }}</span>
      全名:<span>{{ fullName() }}</span>
    
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el: '#root',
        data: {
          firstName: '张',
          lastName: '三'
        },
        methods: {
          fullName() {
            return this.firstName + '-' + this.lastName
          }
        },
      })
    </script>
    

         如果只使用一次,其实没什么区别
         使用多次的时候,method每次都会重新调用,而computed会从缓存中读取
      2.侦听属性
       1)method实现

    <div id="root">
      <h3>今天天气很{{ info }}</h3>
      <!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
      <!-- <button @click="isHot = !isHot">切换天气</button> -->
      <button @click="changeWeather">切换天气</button>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      const vm = new Vue({
        el:'#root',
        data:{
          isHot:true,
        },
        computed:{
          info(){
            return this.isHot ? '炎热' : '凉爽'
          }
        },
        methods: {
          changeWeather(){
            this.isHot = !this.isHot
          }
        }
      })
    </script>
    

    在这里插入图片描述

       2)watch实现
         监视属性watch:
        a. 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
        b. 监视的属性必须存在,才能进行监视,既可以监视data,也可以监视计算属性
        c. 配置项属性immediate:false,改为 true,则初始化时调用一次 handler(newValue,oldValue)
        d. 监视的两种写法:
          new Vue时传入watch配置
          通过vm.$watch监视
         第一种写法

    <!-- 准备好一个容器-->
    <div id="root">
        <h2>今天天气很{{ info }}</h2>
        <button @click="changeWeather">切换天气</button>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                isHot:true,
            },
            computed:{
                info(){
                    return this.isHot ? '炎热' : '凉爽'
                }
            },
            methods: {
                changeWeather(){
                    this.isHot = !this.isHot
                }
            },
            watch:{
                isHot:{
                    immediate: true, // 初始化时让handler调用一下
                    // handler什么时候调用?当isHot发生改变时。
                    handler(newValue, oldValue){
                        console.log('isHot被修改了',newValue,oldValue)
                    }
                }
            } 
        })
    </script>
    

         第二种写法

    <!-- 准备好一个容器-->
    <div id="root">
        <h2>今天天气很{{ info }}</h2>
        <button @click="changeWeather">切换天气</button>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                isHot:true,
            },
            computed:{
                info(){
                    return this.isHot ? '炎热' : '凉爽'
                }
            },
            methods: {
                changeWeather(){
                    this.isHot = !this.isHot
                }
            }
        })
        
        vm.$watch('isHot',{
            immediate:true, //初始化时让handler调用一下
            //handler什么时候调用?当isHot发生改变时。
            handler(newValue,oldValue){
                console.log('isHot被修改了',newValue,oldValue)
            }
        })
    </script>
    

       3)深度侦听
         Vue中的watch默认不监测对象内部值的改变(一层) obj:{name:’ds’,age:18} 这里的一层指的后面整个对象字面量,而不是里面的值是第一层
         配置deep:true可以监测对象内部值改变(多层)
        a. Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
        b. 使用watch时根据数据的具体结构,决定是否采用深度监视

    <title>天气案例_深度监视</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    
    <div id="root">
      <h3>a的值是:{{ numbers.a }}</h3>
      <button @click="numbers.a++">点我让a+1</button>
      <h3>b的值是:{{ numbers.b }}</h3>
      <button @click="numbers.b++">点我让b+1</button>
      <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
      {{numbers.c.d.e}}
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      const vm = new Vue({
        el: '#root',
        data: {
          isHot: true,
          numbers: {
            a: 1,
            b: 1,
            c: {
              d: {
                e: 100
              }
            }
          }
        },
        watch: {
          // 监视多级结构中某个属性的变化
          /* 'numbers.a':{
    				handler(){
    					console.log('a被改变了')
    				}
    			} */
          // 监视多级结构中所有属性的变化
          numbers: {
            deep: true,
            handler() {
              console.log('numbers改变了')
            }
          }
        }
      })
    </script>
    

       4)简写
         如果监视属性除了handler没有其他配置项的话,可以进行简写

    <div id="root">
      <h3>今天天气很{{ info }}</h3>
      <button @click="changeWeather">切换天气</button>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      const vm = new Vue({
        el: '#root',
        data: {isHot: true,},
        computed: {info() {return this.isHot ? '炎热' : '凉爽'}},
        methods: {changeWeather() {this.isHot = !this.isHot}},
        watch: {
          //简写
          isHot(newValue, oldValue) {
            console.log('isHot被修改了', newValue, oldValue)
          }
        }
      })
    
      //简写
      // vm.$watch('isHot', (newValue, oldValue) => {
      // 	console.log('isHot被修改了', newValue, oldValue, this)
      // })
    </script>
    

         监视到isHot改变会自动调用handler方法
       5)computed和watch之间的区别
         computed能完成的功能,watch都可以完成
         watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
         两个重要的小原则:
        a. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
        b. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
         比如想延迟一秒显示fullName,只能用watch实现

    new Vue({
      el:'#root',
      data:{
        firstName:'张',
        lastName:'三',
        fullName:'张-三'
      },
      watch:{
        firstName(val){
          setTimeout(()=>{
            this.fullName = val + '-' + this.lastName
          },1000);
        },
        lastName(val){
          this.fullName = this.firstName + '-' + val
        }
      }
    })
    

     F5.绑定样式 + 条件渲染 + 列表渲染(K33-K38)

      1.绑定样式
      A.class样式
         写法::class=“xxx” xxx可以是字符串、对象、数
         所以分为三种写法,字符串写法,数组写法,对象写法
       1)字符串写法
         字符串写法适用于:类名不确定,要动态获取

    <style>
    	.normal{
            background-color: skyblue;
        }
    </style>
    
    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="mood" @click="changeMood">{{name}}</div>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                mood:'normal'
            }
        })
    </script>
    

       2)数组写法
         数组写法适用于:要绑定多个样式,个数不确定,名字也不确定

    <style>
        .atguigu1{
            background-color: yellowgreen;
        }
        .atguigu2{
            font-size: 30px;
            text-shadow:2px 2px 10px red;
        }
        .atguigu3{
            border-radius: 20px;
        }
    </style>
    
    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
    	<div class="basic" :class="classArr">{{name}}</div>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                classArr: ['atguigu1','atguigu2','atguigu3']
           }
        })
    </script>
    

       3)对象写法
         对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用

    <style>
        .atguigu1{
            background-color: yellowgreen;
        }
        .atguigu2{
            font-size: 30px;
            text-shadow:2px 2px 10px red;
        }
    </style>
    
    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
    	<div class="basic" :class="classObj">{{name}}</div>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                classObj:{
                    atguigu1:false,
                    atguigu2:false,
    			}
            }
        })
    </script>
    

      B.style样式
         有两种写法,对象写法,数组写法
       1)对象写法

    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 绑定style样式--对象写法 -->
    	<div class="basic" :style="styleObj">{{name}}</div>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                styleObj:{
                    fontSize: '40px',
                    color:'red',
    			}
            }
        })
    </script>
    

       2)数组写法

    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 绑定style样式--数组写法 -->
    	<div class="basic" :style="styleArr">{{name}}</div>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                styleArr:[
                    {
                        fontSize: '40px',
                        color:'blue',
                    },
                    {
                        backgroundColor:'gray'
                    }
                ]
            }
        })
    </script>
    

      2.条件渲染
      A.v-if
       1)写法:
         v-if=“表达式”
         v-else-if=“表达式”
         v-else=“表达式”
       2)适用于:切换频率较低的场景,因为不展示的DOM元素直接被移除
       3)特点:不展示的DOM元素直接被移除
       4)特点:不展示的DOM元素直接被移除
       5)特点:不展示的DOM元素直接被移除

    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 使用v-if做条件渲染 -->
        <h2 v-if="false">欢迎来到{{name}}</h2>
        <h2 v-if="1 === 1">欢迎来到{{name}}</h2>
        
        
        <!-- v-else和v-else-if -->
        <div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div>
        <div v-else>哈哈</div>
        
        
        <!-- v-if与template的配合使用 -->
        <!-- 就不需要写好多个判断,写一个就行 -->
        <!-- 这里的思想就像事件代理的使用 -->
        <template v-if="n === 1">
            <h2>你好</h2>
            <h2>尚硅谷</h2>
            <h2>北京</h2>
        </template>
    </div>
    
    <script>
    	const vm = new Vue({
            el:'#root',
            data:{
                styleArr:[
                    {
                        fontSize: '40px',
                        color:'blue',
                    },
                    {
                        backgroundColor:'gray'
                    }
                ]
            }
        })
    </script>
    

      B.v-show
       1)写法:v-show=“表达式”
       2)适用于:切换频率较高的场景
       3)特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)
       4)备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到 v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素

    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 使用v-show做条件渲染 -->
        <h2 v-show="false">欢迎来到{{name}}</h2>
        <h2 v-show="1 === 1">欢迎来到{{name}}</h2>
    </div>
    

      3.列表渲染
      A.v-for指令
       1)用于展示列表数据
       2)语法:v-for=“(item, index) in xxx” :key=“yyy”,这里key可以是index,更好的是遍历对象的唯一标识 如果key写的是index,前面可以不写index也能用
       3)可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

    <title>基本列表</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    
    <div id="root">
      <!-- 遍历数组 -->
      <h3>人员列表(遍历数组)</h3>
      <ul>
        <li v-for="(p,index) of persons" :key="index">{{ p.name }}-{{ p.age }}</li>
      </ul>
    
      <!-- 遍历对象 -->
      <h3>汽车信息(遍历对象)</h3>
      <ul>
        <li v-for="(value,k) of car" :key="k">{{ value }}--{{ k }}</li>
      </ul>
    
      <!-- 遍历字符串 -->
      <h3>测试遍历字符串(用得少)</h3>
      <ul>
        <li v-for="(char,index) of str" :key="index">{{ char }}-{{ index }}</li>
      </ul>
    
      <!-- 遍历指定次数 -->
      <h3>测试遍历指定次数(用得少)</h3>
      <ul>
        <li v-for="(number,index) of 5" :key="index">{{ number }}--{{ index }}</li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el: '#root',
        data: {
          persons: [
            { id: '001', name: '张三', age: 18 },
            { id: '002', name: '李四', age: 19 },
            { id: '003', name: '王五', age: 20 }
          ],
          car: {
            name: '奥迪A8',
            price: '70万',
            color: '黑色'
          },
          str: 'hello'
        }
      })
    </script>
    

    在这里插入图片描述
      B.key 的作用与原理
       1)vue中的key有什么作用(key的内部原理)
         ​ 就是vue的虚拟dom,vue会根据 data中的数据生成虚拟dom,如果是第一次生成页面,就将虚拟dom转成真实dom,在页面展示出来
       2)虚拟dom的作用
         ​ 每次vm._data 中的数据更改,都会触发生成新的虚拟dom,新的虚拟dom会跟旧的虚拟dom进行比较,如果有相同的,在生成真实dom时,这部分相同的就不需要重新生成,只需要将两者之间不同的dom转换成真实dom,再与原来的真实dom进行拼接。我的理解是虚拟dom就是起到了一个dom复用的作用,还有避免重复多余的操作,后续有详细解释
       3)key的作用
         ​ key是虚拟dom的标识
         ​ 先来点预备的知识:啥是真实 DOM?真实 DOM 和 虚拟 DOM 有啥区别?如何用代码展现真实 DOM 和 虚拟 DOM
       4)真实DOM和其解析流程
         ​ 所有的浏览器渲染引擎工作流程大致分为5步:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting
        a. 第一步,构建 DOM 树:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到
        b. 第一步,构建 DOM 树:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到
        c. 渲染阻塞:当浏览器遇到一个script标签时,DOM构建将暂停,直到脚本加载执行,然后继续构建DOM树。每次去执行Javascript脚本都会严重阻塞DOM树构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM没有下载和构建,那么浏览器甚至会延迟脚本执行和构建DOM,直到这个CSSOM的下载和构建。所以,script标签引入很重要,实际使用时可以遵循下面两个原则:
          css优先:引入顺序上,css资源先于js资源
          js后置:js代码放在底部,且js应尽量少影响DOM构建
          注:还有一个小知识:当解析html时,会把新来的元素插入dom树里,同时去查找css,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序匹配的例如:div p {…},会先寻找所有p标签并判断它的父标签是否为div之后才决定要不要采用这个样式渲染。所以平时写css尽量用class或者id,不要过度层叠
        d. 第三步,构建渲染树:通过DOM树和CSS规则我们可以构建渲染树。浏览器会从DOM树根节点开始遍历每个可见节点(注意是可见节点)对每个可见节点,找到其适配的CSS规则并应用。渲染树构建完后,每个节点都是可见节点并且都含有其内容和对应的规则的样式。这也是渲染树和DOM树最大的区别所在。渲染是用于显示,那些不可见的元素就不会在这棵树出现了。除此以外,display none的元素也不会被显示在这棵树里。visibility hidden的元素会出现在这棵树里
        e. 第四步,渲染布局:布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小
        f. 第五步,渲染树绘制:在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的
       4)注意点:
        a. DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局
        b. Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染
        c. CSS 的解析注意点: CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢
        d. JS 操作真实 DOM 的代价:传统DOM结构操作方式对性能的影响很大,原因是频繁操作DOM结构操作会引起页面的重排(reflow)和重绘(repaint),浏览器不得不频繁地计算布局,重新排列和绘制页面元素,导致浏览器产生巨大的性能开销。直接操作真实DOM的性能特别差,我们可以来演示一遍

    <div id="app"></div>
    <script>
        // 获取 DIV 元素
        let box = document.querySelector('#app');
        console.log(box);
    
        // 真实 DOM 操作
        console.time('a');
        for (let i = 0; i <= 10000; i++) {
            box.innerHTML = i;
        }
        console.timeEnd('a');
    
        // 虚拟 DOM 操作
        let num = 0;
        console.time('b');
        for (let i = 0; i <= 10000; i++) {
            num = i;
        }
        box.innerHTML = num;
        console.timeEnd('b');
    
    </script>
    

    在这里插入图片描述
         从结果中可以看出,操作真实 DOM 的性能是非常差的,所以我们要尽可能的复用,减少 DOM 操作
       5)虚拟 DOM 的好处
         虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制
         ​ 虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI
         ​ 回到最开始的问题,虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tag、props、children 三个属性
       6)虚拟DOM中key的作用
         key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
        a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
          若虚拟DOM中内容没变, 直接使用之前的真实DOM!
          若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
        b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
          创建新的真实DOM,随后渲染到到页面
       7)若对数据进行:逆序添加、逆序删除等破坏顺序操作:
         会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低

    <!-- 准备好一个容器-->
    <div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表(遍历数组)</h2>
        <button @click.once="add">添加一个老刘</button>
        <ul>
            <li v-for="(p,index) of persons" :key="index">
                {{p.name}}-{{p.age}}
                <input type="text">
            </li>
        </ul>
    </div>
    
    <script type="text/javascript">
    	Vue.config.productionTip = false
    
    	new Vue({
    		el: '#root',
    		data: {
    			persons: [
    				{ id: '001', name: '张三', age: 18 },
    				{ id: '002', name: '李四', age: 19 },
    				{ id: '003', name: '王五', age: 20 }
    			]
    		},
    		methods: {
    			add() {
    				const p = { id: '004', name: '老刘', age: 40 }
    				this.persons.unshift(p)
    			}
    		},
    	});
    </script>
    

       8)上述代码解释:
         初始数据:

    persons: [ { id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ]
    

        a. vue根据数据生成虚拟 DOM
          vue根据数据生成虚拟 DOM

    <li key='0'>张三-18<input type="text"></li>
    <li key='1'>李四-19<input type="text"></li>
    <li key='2'>王五-20<input type="text"></li>
    

        b. 将虚拟 DOM 转为 真实 DOM
    在这里插入图片描述

    this.persons.unshift({ id: '004', name: '老刘', age: 40 })
    在 persons 数组最前面添加上 { id: '004', name: '老刘', age: 40 }
    

          新数据:

    persons: [
    		{ id: '004', name: '老刘', age: 40 },
    		{ id: '001', name: '张三', age: 18 },
    		{ id: '002', name: '李四', age: 19 },
    		{ id: '003', name: '王五', age: 20 }
    ]
    

        c. vue根据数据生成虚拟 DOM
          新虚拟 DOM

    <li key='0'>老刘-30<input type="text"></li>
    <li key='1'>张三-18<input type="text"></li>
    <li key='3'>李四-19<input type="text"></li>
    <li key='4'>王五-20<input type="text"></li>
    

        d. 将虚拟 DOM 转为 真实 DOM
    在这里插入图片描述

          因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法 根据 key 的值 判断 虚拟DOM 全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费
        e. 如果结构中还包含输入类的DOM:
          会产生错误DOM更新 ==> 界面有问题
          这回造成的就不是性能浪费了,会直接导致页面的错误
    在这里插入图片描述

        f. 总结
          最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
          如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
    在这里插入图片描述

      C.列表过滤
       1)watch实现

    <div id="root">
      <h2>人员列表</h2>
      <input type="text" placeholder="请输入名字" v-model="keyWord">
      <ul>
        <li v-for="(p,index) of filPersons" :key="p.id">
          {{ p.name }}-{{ p.age }}-{{ p.sex }}
        </li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      // 用 watch 实现 
       new Vue({
    			el: '#root',
    			data: {
    				keyWord: '',
    				persons: [
    					{ id: '001', name: '马冬梅', age: 19, sex: '女' },
    					{ id: '002', name: '周冬雨', age: 20, sex: '女' },
    					{ id: '003', name: '周杰伦', age: 21, sex: '男' },
    					{ id: '004', name: '温兆伦', age: 22, sex: '男' }
    				],
    				filPersons: []
    			},
    			watch: {
    				keyWord: {
    					immediate: true,
    					handler(val) {
    						this.filPersons = this.persons.filter((p) => {
    							return p.name.indexOf(val) !== -1
    						})
    					}
    				}
    			}
    		}) 
    </script>
    

         注意:indexOf对于空串的结果是0,这样上面代码初始过滤就是全部数据
       2)computed实现

    <div id="root">
      <h2>人员列表</h2>
      <input type="text" placeholder="请输入名字" v-model="keyWord">
      <ul>
        <li v-for="(p,index) of filPersons" :key="p.id">
          {{ p.name }}-{{ p.age }}-{{ p.sex }}
        </li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      // 用 computed 实现
      new Vue({
        el: '#root',
        data: {
          keyWord: '',
          persons: [
            { id: '001', name: '马冬梅', age: 19, sex: '女' },
            { id: '002', name: '周冬雨', age: 20, sex: '女' },
            { id: '003', name: '周杰伦', age: 21, sex: '男' },
            { id: '004', name: '温兆伦', age: 22, sex: '男' }
          ]
        },
        computed: {
          filPersons() {
            return this.persons.filter((p) => {
              return p.name.indexOf(this.keyWord) !== -1
            })
          }
        }
      }) 
    </script>
    

      D.列表排序
         有时为了减少服务器压力,能在前端做的尽量在前端做

    <div id="root">
      <h2>人员列表</h2>
      <input type="text" placeholder="请输入名字" v-model="keyWord">
      <button @click="sortType = 2">年龄升序</button>
      <button @click="sortType = 1">年龄降序</button>
      <button @click="sortType = 0">原顺序</button>
      <ul>
        <li v-for="(p,index) of filPersons" :key="p.id">
          {{p.name}}-{{p.age}}-{{p.sex}}
          <input type="text">
        </li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
      new Vue({
        el: '#root',
        data: {
          keyWord: '',
          sortType: 0, // 0原顺序 1降序 2升序
          persons: [
            { id: '001', name: '马冬梅', age: 30, sex: '女' },
            { id: '002', name: '周冬雨', age: 31, sex: '女' },
            { id: '003', name: '周杰伦', age: 18, sex: '男' },
            { id: '004', name: '温兆伦', age: 19, sex: '男' }
          ]
        },
        computed: {
          filPersons() {
            const arr = this.persons.filter((p) => {
              return p.name.indexOf(this.keyWord) !== -1
            })
            //判断一下是否需要排序
            if (this.sortType) {
              arr.sort((p1, p2) => {
                return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
              })
            }
            return arr
          }
        }
      })
    </script>
    

    在这里插入图片描述

     F6.Vue数据监视 + v-model双向绑定(K39-K43)

      1.Vue数据监视
       1)问题演示
         先来个案例引入一下:

    <!-- 准备好一个容器-->
    <div id="root">
        <h2>人员列表</h2>
        <button @click="updateMei">更新马冬梅的信息</button>
        <ul>
            <li v-for="(p,index) of persons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
            </li>
        </ul> 
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false
    
        const vm = new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'001',name:'马冬梅',age:30,sex:'女'},
                    {id:'002',name:'周冬雨',age:31,sex:'女'},
                    {id:'003',name:'周杰伦',age:18,sex:'男'},
                    {id:'004',name:'温兆伦',age:19,sex:'男'}
                ]
            },
            methods: {
                updateMei(){
                    // this.persons[0].name = '马老师' //奏效
                    // this.persons[0].age = 50 //奏效
                    // this.persons[0].sex = '男' //奏效
                    this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效
                    // this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})
                }
            }
        }) 
    
    </script>
    

         点击更新马冬梅的信息,马冬梅的数据并没有发生改变
    在这里插入图片描述
         我们来看看控制台:
    在这里插入图片描述
         控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到
       2) 模拟一个数据监测
         代码

    let data = {
      name: '尚硅谷',
      address: '北京',
    }
    
    function Observer(obj) {
      // 汇总对象中所有的属性形成一个数组
      const keys = Object.keys(obj)
      // 遍历
      keys.forEach((k) => {
        Object.defineProperty(this, k, {
          get() {
            return obj[k]
          },
          set(val) {
            console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
            obj[k] = val
          }
        })
      })
    }
    
    // 创建一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)
    console.log(obs)
    
    // 准备一个vm实例对象
    let vm = {}
    vm._data = data = obs
    

       3)Vue.set 的使用
         Vue.set(target,propertyName/index,value) vm.$set(target,propertyName/index,value)
         用法:向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')

    <!-- 准备好一个容器-->
    <div id="root">
        <h1>学生信息</h1>
        <button @click="addSex">添加性别属性,默认值:男</button> <br/>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
        const vm = new Vue({
            el:'#root',
            data:{
                student:{
                    name:'tom',
                    age:18,
                    hobby:['抽烟','喝酒','烫头'],
                    friends:[
                        {name:'jerry',age:35},
                        {name:'tony',age:36}
                    ]
                }
            },
            methods: {
                addSex(){
                    // Vue.set(this.student,'sex','男')
                    this.$set(this.student,'sex','男')
                }
            }
        })
    </script>
    

         Vue.set() 或 vm.$set 有缺陷:
    在这里插入图片描述

       4)监测数组
         看完了 vue 监测对象中的数据,再来看看 vue 如何监测 数组里的数据

    <!-- 准备好一个容器-->
    <div id="root">
        <h2>爱好</h2>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>
        <h2>朋友们</h2>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}}--{{f.age}}
            </li>
        </ul>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
        const vm = new Vue({
            el:'#root',
            data:
                student:{
                    name:'tom',
                    age:{
                        rAge:40,
                        sAge:29,
                    },
                    hobby:['抽烟','喝酒','烫头'],
                    friends:[
                        {name:'jerry',age:35},
                        {name:'tony',age:36}
                    ]
                }
            },
            methods: {
                
            }
        })
    </script>
    

    在这里插入图片描述
         所以我们通过 vm._data.student.hobby[0] = ‘aaa’ // 不奏效
         vue 监测在数组那没有 getter 和 setter,所以监测不到数据的更改,也不会引起页面的更新
    在这里插入图片描述
         既然 vue 在对数组无法通过 getter 和 setter 进行数据监视,那 vue 到底如何监视数组数据的变化呢?
            vue对数组的监测是通过 包装数组上常用的用于修改数组的方法来实现的
            vue官网的解释:
    在这里插入图片描述

       5)练习
         代码

    <style>button {margin-top: 10px;}</style>
    
    <div id="root">
      <h1>学生信息</h1>
      <button @click="student.age++">年龄+1岁</button> <br />
      <button @click="addSex">添加性别属性,默认值:男</button> <br />
      <button @click="student.sex = '未知' ">修改性别</button> <br />
      <button @click="addFriend">在列表首位添加一个朋友</button> <br />
      <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br />
      <button @click="addHobby">添加一个爱好</button> <br />
      <button @click="updateHobby">修改第一个爱好为:开车</button> <br />
      <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br />
      <h3>姓名:{{ student.name }}</h3>
      <h3>年龄:{{ student.age }}</h3>
      <h3 v-if="student.sex">性别:{{student.sex}}</h3>
      <h3>爱好:</h3>
      <ul>
        <li v-for="(h,index) in student.hobby" :key="index">{{ h }} </li>
      </ul>
      <h3>朋友们:</h3>
      <ul>
        <li v-for="(f,index) in student.friends" :key="index">{{ f.name }}--{{ f.age }}</li>
      </ul>
    </div>
    
    <script type="text/javascript">
      Vue.config.productionTip = false
    
      const vm = new Vue({
        el: '#root',
        data: {
          student: {
            name: 'tom',
            age: 18,
            hobby: ['抽烟', '喝酒', '烫头'],
            friends: [
              { name: 'jerry', age: 35 },
              { name: 'tony', age: 36 }
            ]
          }
        },
        methods: {
          addSex() {
            // Vue.set(this.student,'sex','男')
            this.$set(this.student, 'sex', '男')
          },
          addFriend() {
            this.student.friends.unshift({ name: 'jack', age: 70 })
          },
          updateFirstFriendName() {
            this.student.friends[0].name = '张三'
          },
          addHobby() {
            this.student.hobby.push('学习')
          },
          updateHobby() {
            // this.student.hobby.splice(0,1,'开车')
            // Vue.set(this.student.hobby,0,'开车')
            this.$set(this.student.hobby, 0, '开车')
          },
          removeSmoke() {
            this.student.hobby = this.student.hobby.filter((h) => {
              return h !== '抽烟'
            })
          }
        }
      })
    

    在这里插入图片描述

       6)总结
         Vue监视数据的原理:
        a. vue会监视data中所有层次的数据
        b. 如何监测对象中的数据?
          通过setter实现监视,且要在new Vue时就传入要监测的数据
          对象中后追加的属性,Vue默认不做响应式处理
          如需给后添加的属性做响应式,请使用如下API:
          Vue.set(target,propertyName/index,value)vm.$set(target,propertyName/index,value)
        c. 如何监测数组中的数据?
         通过包裹数组更新元素的方法实现,本质就是做了两件事:
         调用原生对应的方法对数组进行更新
         调用原生对应的方法对数组进行更新
        d. 在Vue修改数组中的某个元素一定要用如下方法:
         在Vue修改数组中的某个元素一定要用如下方法:
         Vue.set()vm.$set()
         特别注意:Vue.set() vm. $ set()不能给vm 或 vm的根数据对象 添加属性!!
         例:Vue.set(this,…,…)this.$set(this,…,…)
      2.v-model双向绑定
       1)收集表单数据
         若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值

    <!-- 准备好一个容器-->
    <div id="root">
        <form @submit.prevent="demo">
            账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            密码:<input type="password" v-model="userInfo.password"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            <button>提交</button>
        </form>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false
    
        new Vue({
            el:'#root',
            data:{
                userInfo:{
                    account:'',
                    password:'',
                    age:18,
                }
            },
            methods: {
                demo(){
                    console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
    

         若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值

    <!-- 准备好一个容器-->
    <div id="root">
        <form @submit.prevent="demo">
            性别:
            男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female">
        </form>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false
    
        new Vue({
            el:'#root',
            data:{
                userInfo:{
                    sex:'female'
                }
            },
            methods: {
                demo(){
                    console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
    

         若:
        a. 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
        b. 配置input的value属性:
         v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
         v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

    <!-- 准备好一个容器-->
    <div id="root">
        <form @submit.prevent="demo">
            爱好:
            学习<input type="checkbox" v-model="userInfo.hobby" value="study">
            打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
            吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
            <br/><br/>
            所属校区
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">武汉</option>
            </select>
            <br/><br/>
            其他信息:
            <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
            <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
            <button>提交</button>
        </form>
    </div>
    
    <script type="text/javascript">
        Vue.config.productionTip = false
    
        new Vue({
            el:'#root',
            data:{
                userInfo:{
                    hobby:[],
                    city:'beijing',
                    other:'',
                    agree:''
                }
            },
            methods: {
                demo(){
                    console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
    

         备注:v-model的三个修饰符:
         lazy:失去焦点再收集数据
         number:输入字符串转为有效的数字
         trim:输入首尾空格过滤
       2)模拟实现v-model

        <h2>v-model实现原理(vue2)</h2>
        <!-- 
          原生DOM当中是有oninput事件,它经常结合表单元素一起使用,当表单元素文本内容发生变化的时候就会发出一次回调
          Vue2:可以通过value与input事件实现v-model功能
          原生DOM标签身上的 :value是v-bind单项数据绑定
        -->
        <input type="text" :value="msg" 
        @input="msg = $event.target.value">
        <span>{{msg}}</span>
    
These dependencies were not found: * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-c1084962","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage.vue in ./src/views/girder/kanban/bim/ComponentPage.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-418c245e","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage.vue in ./src/views/girder/kanban/bim/EnginePage.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-284d27df","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage.vue in ./src/views/girder/kanban/csbim/ComponentPage.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-3d21053a","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage2.vue in ./src/views/girder/kanban/csbim/ComponentPage2.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-406216fc","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage_llqjz_new.vue in ./src/views/girder/kanban/csbim/ComponentPage_llqjz_new.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-550c1576","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage1_llqjz_new.vue in ./src/views/girder/kanban/csbim/EnginePage1_llqjz_new.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-357ebc64","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage.vue in ./src/views/girder/kanban/csbim/EnginePage.vue * !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-7a45ac5a","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage1.vue in ./src/views/girder/kanban/csbim/EnginePage1.vue To install them, you can run: npm install --save !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-c1084962","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-418c245e","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-284d27df","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-3d21053a","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage2.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-406216fc","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./ComponentPage_llqjz_new.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-550c1576","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage1_llqjz_new.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-357ebc64","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage.vue !!vue-style-loader!css-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-7a45ac5a","scoped":false,"hasInlineConfig":false}!less-loader?{"sourceMap":false}!../../../../../node_modules/vue-loader/lib/selector?type=styles&index=0!./EnginePage1.vue
06-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值