一、第一部分


1、Vue的特点

<script>
  // 1、Vue的特点
  // 1.1、采用组件化模式,提高代码的复用率、而且DOM、CSS、JS都是分离的,代码更好维护

  // 1.2、声明式编码,让编码人员无需直接操作DOM,提高开发效率,数据控制DOM,不用像之前写一堆模板字符串

  // 1.3、使用虚拟DOM+优秀的Diff算法,尽量减少复用DOM节点(这个很关键)

</script>

2、指令和修饰符

<script>
  // 常用的指令


  // 1、v-bind:动态绑定数据
  // 简写: <div :name="name"></div>

  // 2、v-model:数据的双向绑定
  // 注意:v-model只能应用在表单类元素,其他元素用会报错比如H标签用不了v-model
  // 还有一个需要注意的是,v-model自动绑定到表单元素的value属性上
  // 简写:<input v-model="searchValue"></input>

  // 3、v-on:事件处理
  // 简写: <div :name="name" @click="sayHello"></div>
  // 可以传事件本身,但是需要用$占位符,$event放前放后都是一样的
  // <div :name="name" @click="sayHello($event, '你好')"></div>

  // 4、v-once: 只会执行一次渲染,当数据发生改变时,不会再变化
  // <p v-once>{{ 绑定的字段 }}</p>
  // 下面看下区别:什么叫只渲染一次(初始渲染)
  // <div id="root">
  //   <h2>初始值是:{{n}}</h2> // 你会发现这个n永远是初始值
  //   <h2>当前值是:{{n}}</h2> // 而这里的值会随着点击button不断增加
  //   <button @click="n++">点我n+1</button>
  // </div>
  // 总结:v-once所在节点在初次动态渲染后就视为静态内容了,以后数据的改变不会引起更新,优化性能用的

  // 5、v-show: 你懂的

  // 以下这三个必须紧紧挨着使用
  // 6、v-if:
  // 7、v-else:
  // 8、v-else-if:
  // <div>
  //   <span v-if="score >= 90">优秀</span>
  //   <span v-else-if="score >= 80">良好</span>
  //   <span v-else-if="score >= 60">及格</span>
  //   <span v-else>不及格</span>
  // </div>

  // 9、v-for: 你懂的

  // 10、v-html:
  // 11、v-text: 向所在标签插入文本内容,覆盖所有的内容
  // v-text是渲染字符串,会覆盖原先的字符串;v-html是渲染为html。{{}}双大括号和v-text都是输出为文本
  // 那如果想输出为html。使用v-html,如下例子
  // const innerHtml = '<button>一个按钮</button>';
  // <div v-text="innerHtml"></div> //文本
  // <div v-html="innerHtml"></div> // 会生成一个按钮

  // 12、v-cloak: 在加载很慢的时候,vue.js文件没有加载完成时,在页面上上会出现 ‘{{message}}’的字样,
  // 等到vue创建实例、编译模板时,DOM就会被替换掉,在这整个过程中屏幕上会出现闪动一下。
  // <div id="app" v-cloak>{{message}}</div>,并且需要搭配CSS使用
  // [v-cloak]{
  //   display:none;
  // }
  // 在Vue解析模版前,模版是不显示的display: none嘛,当模版解析完成后,Vue自己会把v-cloak指令清除
  // v-cloak指令可以解决初始化慢而导致页面闪动的一个方案,如果是在有工程化的项目里面,项目的HTML结构中就
  // 只有一个空的DIV元素,其他的都是让路由去挂载不同组件来完成的,就不需要v-cloak指令的了

  // 13、v-pre: 加了这个v-pre就是程序员写什么样页面显示就是什么样,它不会再进行解析,不解析插值语法
  // 所以不要在,有插值语句,或者点击事件或者添加了标签属性的元素上加pre等等
  // 要在像第一行只有文本文字的情况下去加
  // <h2 v-pre>Vue其实很简单</h2> //  显示:Vue其实很简单
  // <h2 v-pre>{{n}}}</h2>       //  显示:{{n}}}





  // 常用的修饰符
  // 先看事件修饰符
  // 1、@click.prevent:阻止默认事件,比如阻止a标签的跳转
  // 2、@click.stop:阻止事件冒泡
  // 3、@click.once:事件只触发一次(常用),以后再点击就不行了
  // 4、@click.capture:阻止事件捕获
  // 5、@click.self:只有event.target是当前操作的元素时才触发事件
  // 这个要有意思很多,event.target就是触发事件的元素(标签),在事件冒泡和事件捕获的过程中,event.target是不会变的
  // 所以.self其实也是一种阻止事件冒泡和捕获的方式,因为事件触发前会判别,当前这个标签是不是event.target
  // 不过这个用的不多,因为他是用在你不想触发事件的标签的,比如下面的就会放在div上,而不是放在button上,当我们点击button时
  // 就变相的不会事件冒泡了
  // <div @click.self="sayHello">
  //   <button @click="sayHello">我是button</button>
  // </div>
  // 6、@click.passive:事件的默认行为立即执行,无需等待事件回调执行完毕
  // 这里加一个解释:@scroll:滚动条滚动事件 和 @wheel:滚轮滚动事件,当我们滚到底部的时候,发现滚动条
  // 不动了,而滚轮还是可以动的,这个时候滚轮事件继续触发,而滚动条事件不会再触发了,这就是区别
  // 好了接下来解释这个修饰符是干嘛的:给一个div加一个滚动事件(@scroll="sayHello"),我们滚滚轮的是时候
  // 滚动条是一起在动的,为什么呢,因为滚动条的滚动是滚轮滚动的默认事件,如果我们给sayHello写一个回调执行体
  // 是100万次的循环,滚动条滚动会等循环执行完才会滚动
  // 但是如果我们开启了passive,滚动条不会等执行体执行完,而是立即执行默认事件,也就是滚动条滚动
  // 当然如果你直接绑定@scroll滚动条滚动事件,就没这个情况了
  // 7、@click.native:我们知道在自定义组件上,只能监听自定义事件,一些原生事件(比如click)是没有办法直接触发的,
  // 但是使用.native修饰符可以帮我们办到这点,比如自定义组件没有click事件,加了native就好了
  // <NativeCustom @click.native="onClick" />
  


  // 键盘按键修饰符
  // 7、enter
  // 用法:@keyup.enter="sayHello";当光标在表单元素内,我们按下回车键,回车键上升的时候触发,下面的全都一样
  // 8、delete
  // 9、esc
  // 10、space
  // 11、tab:这个按键比较特殊,因为它本身有一个功能就是把光标焦点切换走,不等tab键抬起来,光标已经切走了
  // 所以tab键不适合keyup,只能用keydown使用
  // 12、up
  // 13、down



  // 鼠标修饰符
  // 14、left:鼠标左键
  // 15、right:鼠标右键
  // 15、middle:鼠标中键



  // 系统修饰符
  // 16、这几个也特殊:ctrl、alt、shift、win;他们是系统修饰键,在配合keyup使用时
  // 要按下修饰键同时按下其他键,随后释放其他键,事件才会被触发;在配合keydown使用时
  // 正常触发事件不用其他键
  // 如果只想实现ctrl + y触发事件这样写
  // <input type="text" @keyup.crtl.y="sayHello">



  // v-bind的修饰符:
  // 17、.sync       <hello :data.sync="data":></hello>
  // 当我们想要在父组件和子组件之间对某个属性值进行双向绑定时,有什么便捷的方式?
  // 是的只要.sync修饰符即可办到;
  // 18、.camel      <svg :view-box.camel="viewBox"></svg>  
  // .camel修饰符允许在使用 DOM 模板时将 v-bind property 名称驼峰化
  // 19、.prop :帮助我们用来处理自定义属性的
  // <div :my-name="myName"></div>  最终变成了 <div my-name="myName的实际值"></div>
  // <div :my-name.prop="myName"></div>  最终变成了<div></div>,不会把自定义属性显示出来,这里用getAttribute也访问不到my-name属性
  // 关于.prop修饰符官网只有这句话 .prop 作为一个 DOM property 绑定而不是作为 attribute 绑定
  // 通过自定义属性存储变量,避免暴露数据;防止污染 HTML 结构


  // 表单修饰符
  // 20、.trim :对于输入的内容,希望可以过滤首尾空格应该怎么做呢
  // <input type="text" v-model.trim="name">
  // 21、.lazy v-model大家都很熟悉,默认情况下,每次input事件触发的时候都会将输入框的值与其绑定
  // 的数据进行实时同步。但是如果想要实现光标离开的时候再更新数据如何实现呢
  // <input type="text" v-model.lazy="text2">
  // 22、.number: 我们知道input输入框的type哪怕是number得到的值的类型也是string,如果我们想直接
  // 拿到number类型的数据,又不想麻烦的手动转换应该怎么办呢
  // 需要注意的是.number经常都是和表单元素type=number一起使用的
  // <input type="number" v-model.number="number2">
  // 还有需要注意的是,这里type=number的作用是你输入不了字符串(字母和其他符号),只能输入数字



  // 注意!!!!!!!!!!!!
  // 总结一下:修饰符可以连着写
  // @click.stop.prevent
</script>

3、数据代理:Object.defineProperty

<script>
  // 1、数据代理:Object.defineProperty
  // 这个方法就是给对象添加(定义)属性用的

  const person = {
    name: 'jjwu',
  }
  //person.age = 18; 这种写法,age属性可以枚举、修改、删除(delete)

  // 很关键的一个地方Object.defineProperty添加的属性是可以被实时监听到的
  // 而直接添加的属性不行,比如
  let number = 10;
  person.ha = number;
  number = 20;
  console.log(person);// 你发现ha没有变成20,但是用Object.defineProperty添加的属性就可以
  

  Object.defineProperty(person, 'age', {
    /* value: number,
    enumerable: true, //开启之后可以枚举,默认是false
    writable: true, // 控制属性值是否可以被修改,默认是false
    configurable: true, // 控制属性是否可以被删除,默认是false */

    get: function() {
      return number;
    },
    set: function(val) {
      number = val;
    }
  });
  number = 30;
  console.log(person);

  // 这种添加属性的方法,在浏览器开发者工具里看,属性是不可枚举的(颜色比name属性要淡一点)
  // 颜色淡一点就是不可枚举,说白了就是不可遍历,比如for in 遍历不到
  // get函数也叫getter属性,它将添加的属性变成了动态的,Vue能监听到的
  // 我们修改了number之后再访问age属性,你发现值也变了,因为它是现用现取,靠get取
  // 这样的话我们每次读取age的时候,返回的都是最新的正确的值了
	// 并且当我们去改age的值的时候,会同时把number也改掉

	// 总结:!!!!!!!!!!!!
	// 你一定要有一个体会,直接添加的属性,number和person没有产生任何关联
	// 但是用Object.defineProperty就能让number和person产生关联
</script>

<script>
  // Object.defineProperty在vue中的体现比如一个Vue实例是这样的
  const vm = new Vue({
    el: '.p',
    data: function() {
      return {
        name: 'jjwu',
        age: 18
      }
    }
  })
  // 你注意看data中的name和age属性
  // 我们在创建vm实例的时候会给vm传配置项,而vm会将配置项里的data里所有东西放在自己的_data属性上,
  // 之后vm再给自己添加所有_data里的属性,这里就是name和data这两个,
  // 而这些添加的属性(从_data到vm自身)都有getter和setter属性,都是可以被监听的,是实时的,动态的,现取现用
  // 取的就是实例对象里_data中的name和age值,修改的时候会修改_data里的name和age值

  // 总结一下,所谓的Vue的数据代理,代理的就是data中的数据,这个data对象在创建实例的时候会把他放在vm的一个属性上_data
  // 所有代理的数据就在他自己本身的_data属性上,而一旦_data中的数据变化了,页面就会自动更新
  // 为什么_data中的数据发生变化,页面会自动更新呢,这个你就要知道什么是数据劫持了

  // 数据代理加上数据劫持就是我们Vue的响应式操作了

</script>

4、计算属性

<body>
  <div>{{fullName}}</div>
  <div>{{fullName}}</div>
  <div>{{fullName}}</div>
  <div>{{fullName}}</div>

  <div>{{fullName()}}</div>
  <div>{{fullName()}}</div>
  <div>{{fullName()}}</div>
  <div>{{fullName()}}</div>
  <script>
    // 计算属性
    new Vue({
      el: '#root',
      data: {
        firstName: 'jjwu',
        lastName: '27'
      },
      methods: {
        fullName() {
          console.log('我是fullName');
          return this.firstName + '-' + this.lastName;
        }
      },
      computed: {
        fullName: {
          get() {
            console.log('我是fullName')
            return this.firstName + '-' + this.lastName;
          },
          set(val) {
            // 根据实际情况处理,不用太想理解
            // 很多情况下不需要写set
          }
        },

        // 只考虑读取,不考虑修改,当不用set的时候,计算属性是可以简写的
        fullName2() {
          return this.firstName + '-' + this.lastName; 
        }
      }
    })

    // 我的理解计算属性本质上就是typescript的get和set获取值
    // 但是除此之外呢,计算属性还有缓存,我们的四个div调用了四次fullName,但是
    // console.log,只会打印一次说明只访问了一次
    // 所以get只有初始访问以及所依赖的数据(firstName和lastName)发生改变的时候才会被调用
    // methods没有缓存而是调用四次
  </script>
</body>

5、监视属性

<body>
  <script>
    // 计算属性
    const vm = new Vue({
      el: '#root',
      data: {
        isHot: true,
        isCold: false,
        numbers: {
          a: 1,
          b: 2
        }
      },
      watch: {
        // 正常写法
        isHot: {
          immediate: true, //初始化时调用一下handler
          handler(newValue, oldValue) {
            console.log('我监听到你啦!');
          }
        },

        // 监视多级结构中的单个属性
        'numbers.a': {
          handler(newValue, oldValue) {
            console.log('我监听到你啦!');
          }
        },

        // 深度监视
        // 监视多级结构中所有属性的变化
        numbers: {
          deep: true, // 加了他只要a或者b发生了改变那么numbers就能被监听到,否则a、b改变是监听不到
          handler() {
            console.log('numbers改变了')
          }
        },
        // 当你不写任何配置项的时候可以简写
        numbers2(newValue, oldValue) {
          console.log('numbers2改变了')
        }

      }
    })

    // 监视的另一种写法
    vm.$watch('isCold', {
      immediate: true,
      handler(newValue, oldValue) {
        console.log('我监听到你啦!');
      }
    })
    // 简写,注意回调函数不能写成箭头函数
    vm.$watch('isCold', function(newValue, oldValue){
      console.log('我监听到你啦!');
    })

    // 注意: 计算属性也是可以监视的
  </script>
</body>

6、补充一个this指向的问题

<body>
  <script>
  // 计时器中this指向问题
  const obj = {
  sayHello() {
    // 回调写成箭头函数
    setTimeout(() => {
      console.log(this); //obj
    })
  },
  sayHi() {
    // 回调写成普通函数
    setTimeout(function() {
      console.log(this); // window
    })
  }
}

obj.sayHello();
obj.sayHi();

// 自我理解:箭头函数没有this,只能向上一层级去找,找到了obj
// 而普通函数的this就是谁调用指向谁,setTimeout是谁调用的,是window嘛

// 另一种解释:定时器是在obj的两个方法中开启的,但是定时器的回调不受obj控制
// 它是受浏览器定时器管理模块控制的,定时器到点了,也是JS引擎帮你调的
// 所以JS引擎帮你调用的时候this已经给你指向window了,所以一般我们用计时器都用箭头函数

// 20240816 补充gpt的回答

// 箭头函数不会创建自己的 this,它会继承来自包含它的上下文的 this。在你的代码中,箭头函数的包含上下文是 sayHello 方法,而 sayHello 方法中的 this 是 obj,因此箭头函数内部的 this 就是 obj。
// 普通函数在调用时的 this 值是由调用位置决定的。在 setTimeout 中执行的函数(即普通函数)默认会在全局上下文中执行,因此它的 this 值是 window(在浏览器环境中)或 global(在 Node.js 环境中)。
  </script>
  </body>

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录