跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

Vue.js 核心语法与原理详解

综述由AI生成Vue.js 的核心概念与语法,涵盖 MVVM 模式、数据绑定、指令系统(v-bind, v-model, v-if 等)、事件处理、计算属性与侦听器、列表渲染、虚拟 DOM 及生命周期钩子。通过实例演示了响应式原理、数据代理机制及常见开发场景下的最佳实践,适合初学者快速掌握 Vue 框架基础。

CloudNative发布于 2026/4/6更新于 2026/5/2226 浏览
Vue.js 核心语法与原理详解

1. Vue2 简介

// 数据驱动视图 // 双向数据绑定

1.1 数据驱动视图

1.1.1 单向数据绑定

单向的数据绑定,当页面数据发生变化时,页面会自动重新渲染。

文章配图

1.1.2 双向数据绑定

在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。

文章配图

1.1.3 MVVM 分层思想

文章配图

// M:Model(模型/数据)
// V:View(视图)
// VM:ViewModel(视图模型):VM 是 MVVM 中的核心部分。

问题:MVVM 模型当中倡导了 Model 和 View 进行了分离,为什么要分离?

将 Model 和 View 分离之后,出现了一个 VM 核心,这个 VM 把所有的脏活累活给做了。也就是说,当 Model 发生改变之后,VM 自动去更新 View;当 View 发生改动之后,VM 自动去更新 Model。再也不需要编写操作 DOM 的 JS 代码了,开发效率提高了很多。

文章配图

<!-- 准备容器 --> <!-- View V-->
<div id="app">姓名:<input type="text" v-model="name"></div>
<!-- vue 程序 -->
<script>
// ViewModel VM
const vm = new Vue({
  el: '#app',
  data: {
    name: 'zhangsan'
  }
})
</script>

2. 第一个 Vue 程序

首先,使用 script 标签引入 vue.js 文件:

<script src="../js/vue.js"></script>
<body>
  <!-- 指定 VUE 实例的挂载位置 -->
  <div id="app"></div>
  <script>
    const myVue = new Vue({
      template: '<h1>Hello World!</h1>'
    })
    myVue.$mount('#app') // Vue 实例的挂载代码
  </script>
</body>

当使用 script 引入 vue.js 之后,Vue 会被注册为一个全局变量。首先必须要 new 一个 Vue 实例。

2.1 Vue 构造函数的参数:options

options 翻译为多个选项,Vue 框架要求 options 参数必须是一个纯粹的 JS 对象 {}。在当前对象中编写大量的键值对 key:value;每个键值对都是配置项。

2.2 Vue 实例挂载

将 Vue 实例挂载到 id=app 的元素位置。

  1. Vue 实例都有一个 $mount() 方法,这个方法的作用是什么? 将 Vue 实例挂载到指定位置,将 Vue 实例编译后的 HTML 代码渲染到页面的指定位置。注意:指定位置的元素被替换。
  2. #app 显然是 ID 选择器。

2.3 Template 语句的数据来源 data

<body>
  <div id="app"></div>
  <script>
    /* 模板语句的数据来源:
     * 1. 谁可以给模板语句提供数据支持呢?data 选项。
     * 2. data 选项的类型是什么?Object|Function(对象或者函数)
     * 3. data 配置项的专业叫法:Vue 实例的数据对象。(data 实际上是给整个 Vue 实例提供数据来源的。)
     * 4. 如果 data 是对象的话,对象必须是纯粹的对象 (含有零个或多个的 key/value 对)
     * 5. data 数据如何插入到模板语句当中?{{}} 这是 Vue 框架自己搞的一套语法,别的框架看不懂的,浏览器也是不能够识别的。vue 框架自己是能够看懂的。这种语法在 vue 框架中被称为:模板语法中的插值语法。(有的人把他叫做胡子语法。) 怎么用?{{data 的 key}} 插值语法:{}不能有其他字符包括空格
     */
    new Vue({
      template: `<h1>{{ name }}{{releaseTime}}开始学 Vue!! {{lead.age}}的{{lead.name}}也在学习!! 班里还有{{classmates[0].age}}岁的{{classmates[0].name}}和{{classmates[1].age}}岁的{{classmates[1].name}}。</h1>`,
      data: {
        name: '张三',
        releaseTime: '2025 年 8 月 2 日',
        lead: { name: '高齐强', age: 40 },
        classmates: [
          { name: '李四', age: 18 },
          { name: '王五', age: 80 }
        ]
      }
    }).$mount('#app')
  </script>
</body>

2.4 Template 配置项

template 只能有一个根元素;template 编译后进行渲染时会将挂载位置的元素替换。只要 data 中的数据发生变化,模板语句一定会重新编译。template 后面的代码如果需要换行的话,建议将代码写到 ```` 符号当中,不建议使用 + 进行字符串的拼接。将 Vue 实例挂载时,也可以不用 $mount 方法,可以使用 Vue 的 el 配置项。el 配置项主要是用来指定 Vue 实例关联的容器。也就是说 Vue 所管理的容器是哪个。

<body>
  <div id="app">
    <div>
      <h1>{{msg}}</h1>
      <h2>{{name}}</h2>
    </div>
  </div>
  <!-- vue 程序 -->
  <script>
    /* 关于$mount('#app')? VUE 中有一个配置项:el
     * el 配置项和$mount()可以达到同样的效果
     * el 配置项的作用:告诉 VUE 实例去接管哪个容器.
     */
    new Vue({
      // template: '<h1>{{msg}}</h1><h2></h2>', //错误
      template: `
        <div>
          <h1>{{msg}}</h1>
          <h2>{{name}}</h2>
        </div>
      `,
      data: {
        msg: "hello world!",
        name: "明天不学了!"
      },
      el: '#app' // el: document.getElementById('app')
    })
  </script>
</body>

2.5 Vue 实例和容器

Vue 实例和容器的关系是:一夫一妻制。

<body>
  <!-- 准备容器 -->
  <div id="app"><h1>{{msg}}</h1></div>
  <div id="app2"><h1>{{msg}}</h1></div>
  <div><h1>{{name}}</h1></div>
  <script>
    /* 验证:一个 Vue 实例可以接管多个容器吗?
     * 不能,一个 Vue 实例只能接管一个容器;不能重复接管,因此下面 app2 显示'张三'
     */
    new Vue({
      el: '.app',
      data: { msg: 'hell0' }
    })
    new Vue({
      el: '#app2',
      data: { name: '张三' }
    })
    new Vue({
      el: '#app2',
      data: { name: '李四' }
    })
  </script>
</body>

3. Vue 核心语法

3.1 插值 {{ }} 语法

<!-- 主要研究:{{这里可以写什么}}
 * 1. 在 data 中声明的变量、函数等都可以。
 * 2. 常量都可以。
 * 3. 只要是合法的 javascript 表达式,都可以。
 * 4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 等。
 * 'Infinity,undefined,NaN,isFinite,isNaN,'
 * 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,'
 * 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,'
 * 'require'
-->
<script>
// 用户自定义的一个全局变量
var i = 100
// 用户自定义的一个全局函数
function sum() {
  console.log('sum.....');
}
new Vue({
  el: '#app',
  data: {
    number: 1,
    gender: true,
    msg: 'abcdef',
    sayHello: function () {
      console.log('hello vue!');
    }
  }
})
</script>
<!-- 准备容器 -->
<div>
  <!-- 在 data 中声明的 -->
  <h1>{{msg}}</h1>
  <h1>{{sayHello()}}</h1>
  <!-- 不在 data 中定义不可用 -->
  <!-- <h1>{{i}}</h1> -->
  <!-- <h1>{{sum()}}</h1> -->
  <!-- 常量 -->
  <h1>{{100}}</h1>
  <h1>{{'hello vue!'}}</h1>
  <h1>{{3.14}}</h1>
  <!-- javascript 表达式 -->
  <h1>{{1 + 1}}</h1>
  <h1>{{'hello' + 'vue'}}</h1>
  <h1>{{msg + 1}}</h1>
  <h1>{{'msg' + 1}}</h1>
  <h1>{{gender ? '男' : '女'}}</h1>
  <h1>{{number + 1}}</h1>
  <h1>{{'number' + 1}}</h1>
  <!-- .split(''):将字符串分割成字符数组 -->
  <!-- .join('')指的是将数组中的所有元素组合成一个字符串,使用空字符串连接 -->
  <h1>{{msg.split('').reverse().join('')}}</h1>
  <!-- 错误的:不是表达式,这是语句。 -->
  <!-- <h1>{{var i = 100}}</h1> -->
  <!-- 在白名单里面的 -->
  <h1>{{Date}}</h1>
  <h1>{{Date.now()}}</h1>
  <h1>{{Math}}</h1>
  <h1>{{Math.ceil(3.14)}}</h1>
</div>

3.2 Vue 指令

  1. 什么是指令?作用是什么? 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
  2. Vue 框架中的所有指令的名字都以'v-'开始。
  3. Vue 框架中所有的指令都是以 HTML 标签的 属性形式 存在的。
  4. 指令的语法规则:
<HTML v-指令名:参数="javascript 表达式"></HTML>

不是所有的指令都有参数和表达式: 有的指令,不需要参数,也不需要表达式,例如:v-once 有的指令,不需要参数,但是需要表达式,例如:v-if="表达式" 有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"

3.2.1 v-once 指令

作用:只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。

3.2.2 条件渲染指令
3.2.2.1 v-if 指令

作用:表达式的执行结果需要是一个布尔类型的数据:true 或者 false。

  • true:这个指令所在的标签,会被渲染到浏览器当中。
  • false:这个指令所在的标签,不会被渲染到浏览器当中。
<!-- 准备一个容器 -->
<div>
  <h1>{{msg}}</h1>
  <h1 v-once>{{msg}}</h1>
  <h1 v-if="a <= b">v-if 测试:{{msg}}</h1>
</div>
<!-- vue 程序 -->
<script>
new Vue({
  el: '#app',
  data: {
    msg: 'Hello Vue!',
    a: 10,
    b: 11
  }
})
</script>
3.2.2.2 v-show 指令
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- v-if 指令的值:true/false
     * true: 表示该元素会被渲染到页面上。
     * false: 表示该元素不会被渲染到页面上。(注意:不是修改了 CSS 样式,是这个元素压根没有加载)
     -->
    <div v-if="false">{{msg}}</div>
    <div v-if="2 === 1">{{msg}}</div>
    <button @click="counter++">点我加 1</button>
    <h3>{{counter}}</h3>
    <img :src="imgPath1" v-if="counter % 2 === 1">
    <!-- 提醒:v-if 和 v-else 之间不能断开。 -->
    <!-- <div></div> -->
    <!-- <img :src="imgPath2" v-if="counter % 2 === 0"> -->
    <!-- 为了提高效率,可以使用 v-else 指令 -->
    <img :src="imgPath2" v-else>
    <br><br>
    温度:<input type="number" v-model="temprature"><br><br>
    <!-- 天气:<span v-if="temprature <= 10">寒冷</span>
         <span v-if="temprature > 10 && temprature <= 25">凉爽</span>
         <span v-if="temprature > 25">炎热</span> -->
    天气:<span v-if="temprature <= 10">寒冷</span>
    <!-- v-if v-else-if v-else三者在使用的时候,中间不能断开。 -->
    <!-- <br> -->
    <span v-else-if="temprature <= 25">凉爽</span>
    <span v-else>炎热</span>
    <br><br><br>
    <div v-show="false">你可以看到我吗?</div>
    <!-- template 标签/元素只是起到占位的作用,不会真正的出现在页面上,也不会影响页面的结构。 -->
    <template v-if="counter === 10">
      <input type="text">
      <input type="checkbox">
      <input type="radio">
    </template>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '条件渲染',
        counter: 1,
        imgPath1: '../img/1.jpg',
        imgPath2: '../img/2.jpg',
        temprature: 0
      }
    })
  </script>
</body>
</html>
/* v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
 * v-show 指令会动态为元素添加或移除 样式,从而控制元素的显示与隐藏;
 * v-if 和 v-show 应该如何选择?
 * 1. 如果一个元素在页面上被频繁的隐藏和显示,建议使用 v-show,因为此时使用 v-if 开销比较大。
 * 2. v-if 的优点:页面加载速度快,提高了页面的渲染效率。
 */
3.2.3 v-bind 属性绑定指令

可以让 HTML 标签的某个属性的值产生动态的效果。

<HTML v-bind:参数="表达式"></HTML>
<!--简写方式-->
<HTML :参数="表达式"></HTML>

编译后:

<HTML 参数="表达式的执行结果"></HTML>

第一:在编译的时候 v-bind 后面的'参数名'会被编译为 HTML 标签的**'属性名'** 第二:表达式会关联 data ,当 data 发生改变之后,表达式的执行结果就会发生变化。所以,连带的就会产生动态效果。

什么时候使用插值语法?什么时候使用指令?

  1. 凡是标签体当中的内容要想动态,需要使用插值语法。
  2. 只要想让 HTML 标签的属性动态,需要使用指令语法。
<!-- 准备一个容器 -->
<div>
  <!-- 注意:以下代码中 msg 是变量名。 -->
  <!-- 注意:原则上 v-bind 指令后面的这个参数名可以随便写。 -->
  <!-- 虽然可以随便写,但大部分情况下,这个参数名还是需要写成该 HTML 标签支持的属性名。这样才会有意义。 -->
  <span v-bind:xyz="msg"></span>
  <!-- 这个表达式带有单引号,这个'msg'就不是变量了,是常量。 -->
  <span v-bind:xyz="'msg'"></span>
  <!-- v-bind 实战 -->
  <img src="../img/1.jpg">
  <br>
  <img v-bind:src="imgPath">
  <br>
  <!-- v-bind 简写形式 -->
  <img :src="imgPath">
  <br>
  <!-- 这是一个普通的文本框 -->
  <input type="text" name="username" value="zhangsan">
  <br>
  <!-- 以下文本框可以让 value 这个数据变成动态的:这个就是典型的动态数据绑定。 -->
  <input type="text" name="username" :value="username">
  <br>
  <!-- 使用 v-bind 也可以让超链接的地址动态 -->
  <a href="https://www.baidu.com">走起</a>
  <br>
  <a :href="url">走起 2</a>
  <br>
  <!-- 不能采用以下写法吗? -->
  <!-- 不能这样,报错了,信息如下:Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div>, use <div :id="val"> 属性内部插值这种语法已经被移除了。(可能 Vue 在以前的版本中是支持这种写法的,但是现在不允许了。)请使用 v-bind 或冒号速记来代替。请使用 <div :id="val"> 来代替 <div> -->
  <!-- <a href="{{url}}">走起 3</a> -->
  <h1>{{msg}}</h1>
</div>
<!-- vue 程序 -->
<script>
// 赋值的过程就可以看做是一种绑定的过程。
//let i = 100
new Vue({
  el: '#app',
  data: {
    msg: 'Hello Vue!',
    imgPath: '../img/1.jpg',
    username: 'jackson',
    url: 'https://www.baidu.com'
  }
})
</script>
3.2.4 v-model 指令
<!-- v-bind 和 v-model 的区别和联系:
 * 1. v-bind 和 v-model 这两个指令都可以完成数据绑定。
 * 2. v-bind 是单向数据绑定。
 *    data ===> 视图
 * 3. v-model 是双向数据绑定。
 *    data <===> 视图
 * 4. v-bind 可以使用在任何 HTML 标签当中。v-model 只能使用在表单类元素上,例如:
 *    input 标签、select 标签、textarea 标签。
 *    为什么 v-model 的使用会有这个限制呢?
 *       因为表单类的元素才能给用户提供交互输入的界面。
 *    v-model 指令通常也是用在 value 属性上面的。
-->
<!-- v-bind 和 v-model 都有简写方式:
 * v-bind 简写方式:v-bind:参数="表达式" 简写为 :参数="表达式"
 * v-model 简写方式:v-model:value="表达式" 简写为 v-model="表达式"
-->
<!-- 准备一个容器 -->
<div>
  v-bind 指令:<input type="text" :value="name1"><br>
  v-model 指令:<input type="text" v-model:value="name2"><br>
  <!-- 以下报错了,因为 v-model 不能使用在这种元素上。 -->
  <!-- <a v-model:href="url">百度</a> -->
  v-bind 指令:<input type="text" :value="name1"><br>
  v-model 指令:<input type="text" v-model="name2"><br>
  消息 1:<input type="text" :value="msg"><br>
  消息 2:<input type="text" v-model="msg"><br>
</div>
<!-- vue 程序 -->
<script>
new Vue({
  el: '#app',
  data: {
    name1: 'zhangsan',
    name2: 'wangwu',
    url: 'https://www.baidu.com',
    msg: 'Hello Vue!'
  }
})
</script>
3.2.4.1 v-model 指令修饰符

文章配图

3.2.5 事件绑定指令 v-on
<!-- Vue 事件处理:
 * 1. 指令的语法格式:
 *    <标签 v-指令名:参数名="表达式">{{插值语法}}</标签>
 *    '表达式'位置都可以写什么?
 *    常量、JS 表达式、Vue 实例所管理的 XXX
 * 2. 在 Vue 当中完成事件绑定需要哪个指令呢?v-on 指令。
 *    语法格式:v-on:事件名="表达式"
 *    例如:v-on:click="表达式" 表示当发生鼠标单击事件之后,执行表达式。
 *    v-on:keydown="表达式" 表示当发生键盘按下事件之后,执行表达式。
 * 3. 在 Vue 当中,所有事件所关联的回调函数,需要在 Vue 实例的配置项 methods 中进行定义。
 *    methods 是一个对象:{}
 *    在这个 methods 对象中可以定义多个回调函数。
 * 4. v-on 指令也有简写形式
 *    v-on:click 简写为 @click
 *    v-on:keydown 简写为 @keydown
 *    v-on:mouseover 简写为 @mouseover ....
 * 5. 绑定的回调函数,如果函数调用时不需要传递任何参数,小括号 () 可以省略。
 * 6. Vue 在调用回调函数的时候,会自动给回调函数传递一个对象,
 *    这个对象是:当前发生的事件对象。
 * 7. 在绑定回调函数的时候,可以在回调函数的参数上使用 $event 占位符,
 *    Vue 框架看到这个 $event 占位符之后,会自动将当前事件以对象的形式传过去。
-->
<div>
  <h1>{{msg}}</h1>
  <!-- 使用 javascript 原生代码如何完成事件绑定。 -->
  <button onclick="alert('hello')">hello</button>
  <!-- 使用 Vue 来完成事件绑定 -->
  <!-- 以下是错误的,因为 alert() 并没有被 Vue 实例管理。 -->
  <!-- <button v-on:click="alert('hello')">hello</button> -->
  <!-- 以下是错误的,因为 sayHello() 并没有被 Vue 实例管理。
       此时 sayHello() 未被定义在 Vue 实例中 -->
  <!-- <button v-on:click="sayHello()">hello</button> -->
  <!-- 正确的写法 -->
  <button v-on:click="sayHello()">hello</button>
  <!-- v-on 指令的简写形式 -->
  <button @click="sayHi()">hi button</button>
  <button @click="sayHi($event, 'jack')">hi button2</button>
  <!-- 绑定的回调函数,如果不需要传任何参数,小括号 () 可以省略 -->
  <button @click="sayWhat">what button</button>
</div>
<!-- vue 代码 -->
<script>
const vm = new Vue({
  el: '#app',
  data: {
    msg: 'Vue 的事件绑定'
  },
  methods: {
    // 回调函数
    sayHello() {
      alert('hello2')
    },
    sayHi(event, name) {
      console.log(name, event)
      //alert("hi " + name)
    },
    // Vue 在调用回调函数的时候,会自动给回调函数传递一个对象
    // 这个对象是:当前发生的事件对象
    // 传入的内容是 事件参数对象 event
    sayWhat(event) {
      console.log(event)
      // console.log(event.target) // <button>what button</button>
      console.log(event.target.innerText) // what button
      //alert('what...')
    }
  }
})
</script>
3.2.5.1 事件修饰符
<!-- Vue 当中提供的事件修饰符:
 * .stop :停止事件冒泡,等同于 event.stopPropagation()。
 * .prevent :等同于 event.preventDefault() 阻止事件的默认行为。
 * .capture :添加事件监听器时使用事件捕获模式
 *    添加事件监听器包括两种不同的方式:
 *    一种是从内到外添加。(事件冒泡模式)
 *    一种是从外到内添加。(事件捕获模式)
 * .self:这个事件如果是'我自己元素'上发生的事件,这个事件不是别人给我传递过来的事件,
 *    则执行对应的程序。
 * .once:事件只发生一次
 * .passive:passive 翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为。
 * .passive 和 .prevent 修饰符是对立的。不可以共存。(如果一起用,就会报错。)
 * .prevent:阻止事件的默认行为。
 * .passive:解除阻止。
-->
<head>
  <style>
    .divList {
      width: 300px;
      height: 200px;
      background-color: aquamarine;
      overflow: auto; // 自动在对应方向出现滚动条
    }
    .item {
      width: 300px;
      height: 200px;
    }
  </style>
</head>
<body>
  <!-- 容器 -->
  <div>
    <h1>{{msg}}</h1>
    <!-- 阻止事件的默认行为 -->
    <a href="https://www.baidu.com" @click.prevent="yi">百度</a>
    <br><br>
    <!-- 停止事件冒泡 -->
    <div @click="san">
      <div @click.stop="er">
        <button @click="yi">事件冒泡</button>
      </div>
    </div>
    <br><br>
    <!-- 添加事件监听器时使用事件捕获模式 -->
    <div @click.capture="san">
      <!-- 这里没有添加.capture 修饰符,以下这个元素,以及这个元素的子元素,都会默认采用冒泡模式。
           如果希望依次是 3,2,1 那下面这两个子元素都需要是.capture -->
      <div @click="er">
        <button @click="yi">添加事件监听器的时候采用事件捕获模式</button>
      </div>
    </div>
    <br>
    <!-- .self 修饰符:只执行 1 和 3 -->
    <div @click="san">
      <div @click.self="er">
        <button @click="yi">self 修饰符</button>
      </div>
    </div>
    <br>
    <!-- 在 Vue 当中,事件修饰符是可以多个联合使用的。
         但是需要注意:@click.self.stop:先.self,再.stop
                        @click.stop.self:先.stop,再.self -->
    <div @click="san">
      <div @click="er">
        <button @click.self.stop="yi">self 修饰符</button>
      </div>
    </div>
    <br>
    <!-- .once 修饰符:事件只发生一次 -->
    <button @click.once="yi">事件只发生一次</button>
    <!-- .passive 修饰符 -->
    <!-- @wheel 是数据滚动事件,会触发 testPassive 方法-->
    <div @wheel.passive="testPassive">
      <div>div1</div>
      <div>div2</div>
      <div>div3</div>
    </div>
  </div>
  <!-- vue 代码 -->
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '事件修饰符'
      },
      methods: {
        yi(event) {
          //alert('去百度!!!!!!')
          // 手动调用事件对象的 preventDefault() 方法,可以阻止事件的默认行为。
          // 在 Vue 当中,这种事件的默认行为可以不采用手动调用 DOM 的方式来完成,可以使用事件修饰符:prevent。
          //event.preventDefault();
          alert(1)
        },
        er() {
          alert(2)
        },
        san() {
          alert(3)
        },
        testPassive(event) {
          for (let i = 0; i < 100000; i++) {
            console.log('test passive')
          }
          // 阻止事件的默认行为
          //event.preventDefault()
        }
      }
    })
  </script>
</body>
3.2.5.2 按键修饰符
<body>
  <!-- 9 个比较常用的按键修饰符:.enter .tab (必须配合 keydown 事件使用。)
       .delete (捕获'删除'和'退格'键).esc .space .up .down .left .right
       怎么获取某个键的按键修饰符?
       第一步:通过 event.key 获取这个键的真实名字。
       第二步:将这个真实名字以 kebab-case 风格进行命名。
       PageDown 是真实名字。经过命名之后:page-down
       按键修饰符是可以自定义的?
       通过 Vue 的全局配置对象 config 来进行按键修饰符的自定义。
       语法规则:Vue.config.keyCodes.按键修饰符的名字 = 键值
       系统修饰键:4 个比较特殊的键 ctrl、alt、shift、meta
       对于 keydown 事件来说:只要按下 ctrl 键,keydown 事件就会触发。
       对于 keyup 事件来说:需要按下 ctrl 键,并且加上按下组合键,然后松开组合键之后,keyup 事件才能触发。
  -->
  <div>
    <h1>{{msg}}</h1>
    回车键:<input type="text" @keyup.enter="getInfo"><br>
    回车键(键值):<input type="text" @keyup.13="getInfo"><br>
    delete 键:<input type="text" @keyup.delete="getInfo"><br>
    esc 键:<input type="text" @keyup.esc="getInfo"><br>
    space 键:<input type="text" @keyup.space="getInfo"><br>
    up 键:<input type="text" @keyup.up="getInfo"><br>
    down 键:<input type="text" @keyup.down="getInfo"><br>
    left 键:<input type="text" @keyup.left="getInfo"><br>
    right 键:<input type="text" @keyup.right="getInfo"><br>
    <!-- tab 键无法触发 keyup 事件。只能触发 keydown 事件。 -->
    tab 键: <input type="text" @keyup.tab="getInfo"><br>
    tab 键(keydown): <input type="text" @keydown.tab="getInfo"><br>
    PageDown 键: <input type="text" @keyup.page-down="getInfo"><br>
    huiche 键: <input type="text" @keyup.huiche="getInfo"><br>
    ctrl 键 (keydown): <input type="text" @keydown.ctrl="getInfo"><br>
    ctrl 键 (keyup): <input type="text" @keyup.ctrl="getInfo"><br>
    ctrl 键 (keyup+i 触发): <input type="text" @keyup.ctrl.i="getInfo"><br>
  </div>
  <script>
    // 自定义了一个按键修饰符:.huiche 。代表回车键。
    Vue.config.keyCodes.huiche = 13
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '按键修饰符'
      },
      methods: {
        getInfo(event) {
          // 当用户键入回车键的时候,获取用户输入的信息。
          //if(event.keyCode === 13){
          // event.target 是触发事件的 DOM 元素本身。例子中,触发 keyup 事件的是<input>元素
          console.log(event.target.value)
          //}
          console.log(event.key)
        }
      }
    })
  </script>
</body>
3.2.6 v-for 列表渲染指令

v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。

<body>
  <div>
    <h1>{{msg}}</h1>
    <h2>遍历对象的属性</h2>
    <ul>
      <li v-for="(value, propertyName) of user"> {{propertyName}},{{value}} </li>
    </ul>
    <h2>遍历字符串</h2>
    <ul>
      <li v-for="(c,index) of str"> {{index}},{{c}} </li>
    </ul>
    <h2>遍历指定的次数</h2>
    <ul>
      <li v-for="(num,index) of counter"> {{index}}, {{num}} </li>
    </ul>
    <h2>遍历数组</h2>
    <!-- 静态列表 -->
    <ul>
      <li>张三</li>
      <li>李四</li>
      <li>王五</li>
    </ul>
    <!-- 动态列表 -->
    <ul>
      <!-- 1. v-for 要写在循环项上。
           2. v-for 的语法规则:v-for="(变量名,index) in/of 数组"
           变量名 代表了 数组中的每一个元素 -->
      <li v-for="fdsafds in names"> {{fdsafds}} </li>
    </ul>
    <ul>
      <li v-for="name of names"> {{name}} </li>
    </ul>
    <ul>
      <li v-for="(name,index) of names"> {{name}}-{{index}} </li>
    </ul>
    <ul>
      <!-- 对象形式 -->
      <li v-for="(vip,index) of vips"> 会员名:{{vip.name}},年龄:{{vip.age}}岁 </li>
    </ul>
    <table>
      <tr>
        <th>序号</th>
        <th>会员名</th>
        <th>年龄</th>
        <th>选择</th>
      </tr>
      <tr v-for="(vip,index) in vips">
        <td>{{index+1}}</td>
        <td>{{vip.name}}</td>
        <td>{{vip.age}}</td>
        <td><input type="checkbox"></td>
      </tr>
    </table>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '列表渲染',
        names: ['jack', 'lucy', 'james'],
        vips: [
          { id: '111', name: 'jack', age: 20 },
          { id: '222', name: 'lucy', age: 30 },
          { id: '333', name: 'james', age: 40 }
        ],
        user: { id: '111', name: '张三', gender: '男' },
        str: '动力节点',
        counter: 10
      }
    })
  </script>
</body>
3.2.7 虚拟 DOM 与 diff 算法(:key 属性)
<!-- v-for 指令所在的标签中,还有一个非常重要的属性::key
 * 如果没有指定 :key 属性,会自动拿 index 作为 key。
 * 这个 key 是这个 dom 元素的身份证号/唯一标识。
 * 分析以下:采用 index 作为 key 存在什么问题?
 * 第一个问题:效率低。
 * 第二个问题:非常严重了。产生了错乱。尤其是对数组当中的某些元素进行操作。(非末尾元素。)
 * 怎么解决这个问题?建议使用对象的 id 作为 key -->
<body>
  <div>
    <h1>{{msg}}</h1>
    <table>
      <tr>
        <th>序号</th>
        <th>英雄</th>
        <th>能量值</th>
        <th>选择</th>
      </tr>
      <tr v-for="(hero,index) in heros" :key="hero.id">
        <td>{{index+1}}</td>
        <td>{{hero.name}}</td>
        <td>{{hero.power}}</td>
        <td><input type="checkbox"></td>
      </tr>
    </table>
    <button @click="add">添加英雄麦文</button>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '虚拟 dom 与 diff 算法',
        heros: [
          { id: '101', name: '艾格文', power: 10000 },
          { id: '102', name: '麦迪文', power: 9000 },
          { id: '103', name: '古尔丹', power: 8000 },
          { id: '104', name: '萨尔', power: 6000 }
        ]
      },
      methods: {
        add() {
          this.heros.unshift({ id: '105', name: '麦文', power: 9100 })
        }
      }
    })
  </script>
</body>
3.2.7.1 列表插入删除方法
// unshift()- 开头插入 arr.unshift('newItem'); // O(1) ~ O(n)
// push()- 结尾插入 arr.push('newItem'); // O(1) 时间复杂度
// splice()- 任意位置插入 arr.splice(index, 0, 'newItem'); // 平均 O(n)
// pop()- 删除最后一项 arr.pop(); // O(1)
// shift()- 删除第一项 arr.shift(); // O(n)
// splice()- 任意位置删除 arr.splice(index, 1); // O(n)
3.2.8 v-text v-html 指令
<body>
  <div>
    <h1>{{msg}},test</h1>
    <!-- v-text 指令:
     * 可以将指令的内容拿出来填充到标签体当中。和 JS 的 innerText 一样。
     * 这种填充是以覆盖的形式进行的。先清空标签体当中原有的内容,填充新的内容。
     * 即使内容是一段 HTML 代码,这种方式也不会将 HTML 代码解析并执行。
     * 只会当做普通文本来处理。
     -->
    <h1 v-text="msg">test</h1>
    <h1 v-text="name">test</h1>
    <h1 v-text="s1"></h1>
    <!-- v-html 指令:
     * 和 v-text 一样,也是填充标签体内容。也是采用覆盖的形式进行。
     * 只不过 v-html 会将内容当做一段 HTML 代码解析并执行。
     -->
    <h1 v-html="s1"></h1>
    <ul>
      <li v-for="m, index of messageList" :key="index" v-html="m"></li>
    </ul>
    <textarea cols="50" rows="30" v-model.lazy="message"></textarea>
    <br><br>
    <button @click="save">保存留言</button>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Vue 的其它指令',
        name: 'jack',
        s1: '<h1>欢迎大家学习 Vue!</h1>',
        message: '',
        messageList: []
      },
      methods: {
        save() {
          this.messageList.push(this.message)
        }
      }
    })
  </script>
</body>
3.2.9 v-cloak 指令
<head>
  <style>
    /* 刚开始不显示,加载之后将 v-cloak 干掉,所以就会显示 */
    [v-cloak] { display: none; }
  </style>
</head>
<body>
  <div>
    <!-- v-cloak 指令使用在标签当中,当 Vue 实例接管之后会删除这个指令。 -->
    <h1 v-cloak>{{msg}}</h1>
  </div>
  <script>
    setTimeout(() => {
      let scriptElt = document.createElement('script')
      scriptElt.src = '../js/vue.js'
      document.head.append(scriptElt)
    }, 3000)
    setTimeout(() => {
      const vm = new Vue({
        el: '#app',
        data: {
          msg: 'Vue 的其它指令'
        }
      })
    }, 4000)
  </script>
</body>
3.2.10 v-pre 指令
<body>
  <div>
    <h1 v-cloak>{{msg}}</h1>
    <!-- 使用该指令可以提高编译速度。带有该指令的标签将不会被编译 -->
    <h1 v-pre>欢迎学习 Vue 框架!</h1>
    <h1 v-pre>{{msg}}</h1>
    <ul>
      <!-- 只渲染一次。之后将被视为静态内容 -->
      <li v-for="user,index of users" :key="index" v-once> {{user}} </li>
    </ul>
    <ul>
      <li v-for="user,index of users" :key="index"> {{user}} </li>
    </ul>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Vue 的其它指令',
        users: ['jack', 'lucy', 'james']
      }
    })
  </script>
</body>
3.2.11 自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。 在使用自定义指令时,需要加上 v- 前缀。 在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值。

<div>
  <h1>自定义指令</h1>
  <div v-text="msg"></div>
  <div v-text-danger="msg"></div>
  用户名:<input type="text" v-bind:value="username">
  <!-- 需要一个指令,可以和 v-bind 指令完成相同的功能,
       同时将该元素的父级元素的背景色设置为蓝色。 -->
  <div>
    用户名:<input type="text" v-bind-blue="username">
  </div>
</div>
<div>
  <div v-text-danger="msg"></div>
  <div>
    用户名:<input type="text" v-bind-blue="username">
  </div>
</div>
私有自定义指令
const vm = new Vue({
  el: '#app',
  data: {
    msg: '自定义指令',
    username: 'jackson'
  },
  directives: {
    // 指令 1
    // 指令 2
    // ...
    // 关于指令的名字:1. v- 不需要写。
    // 2. Vue 官方建议指令的名字要全部小写。如果是多个单词的话,请使用 - 进行衔接。
    // 这个回调函数的执行时机包括两个:第一个:标签和指令第一次绑定的时候。第二个:模板被重新解析的时候。
    // 这个回调函数有两个参数:第一个参数是真实的 dom 元素。第二个参数是标签与指令之间绑定关系的对象。
    // 函数式方式。
    'text-danger': function(element, binding){
      console.log('@')
      element.innerText = binding.value // element 指的就是 div, binding 指的就是 v-text-danger
      element.style.color = 'red'
    },
    'bind-blue': function(element, binding){
      element.value = binding.value
      console.log(element)
      // 为什么是 null,原因是这个函数在执行的时候,指令和元素完成了绑定,
      // 但是只是在内存当中完成了绑定,元素还没有被插入到页面当中。
      console.log(element.parentNode)
      element.parentNode.style.backgroundColor = 'blue'
    },
    // 对象式
    'bind-blue': {
      // 这个对象中三个方法的名字不能随便写。
      // 这三个函数将来都会被自动调用。
      // 元素与指令初次绑定的时候,自动调用 bind
      // 注意:在特定的时间节点调用特定的函数,这种被调用的函数称为钩子函数。
      bind(element, binding){
        element.value = binding.value
      },
      // 元素被插入到页面之后,这个函数自动被调用。
      inserted(element, binding){
        element.parentNode.style.backgroundColor = 'blue'
      },
      // 当模板重新解析的时候,这个函数会被自动调用。
      update(element, binding){
        element.value = binding.value
      }
    }
  }
})
全局自定义指令
// 定义全局的指令
// 函数式
Vue.directive('text-danger', function (element, binding) {
  //对于自定义指令来说,函数体当中的 this 是 window,而不是 vue 实例。
  console.log(this)
  element.innerText = binding.value
  element.style.color = 'red'
})
// 对象式
Vue.directive('bind-blue', {
  bind(element, binding) {
    element.value = binding.value
    console.log(this) // Window
  },
  inserted(element, binding) {
    element.parentNode.style.backgroundColor = 'skyblue'
    console.log(this)
  },
  update(element, binding) {
    element.value = binding.value
    console.log(this)
  }
})

4. 过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。 过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定,过滤器应该被添加在 JavaScript 表达式的尾部。

<!-- 需求:
 * 从服务器端返回了一个商品的价格 price,这个 price 的值可能是这几种情况:''、null、undefined、60.5
 * 要求:如果是''、null、undefined,页面上统一显示为 -
 *       如果不是 ''、null、undefined,则页面上显示真实的数字即可。
 * 在 Vue3 当中,已经将过滤器语法废弃了。
-->

4.1 filters 节点中定义局部过滤器

const vm = new Vue({
  el: '#app',
  data: {
    msg: '过滤器',
    price: 50.6
  },
  filters: {
    // 局部过滤器
    filterA(val){
      if(val === null || val === undefined || val === ''){
        return '-'
      }
      return val
    },
    filterB(val, number){
      // 确保传递过来的数据 val,保留两位小数。
      return val.toFixed(number)
    }
  }
})

4.2 配置全局过滤器

// 配置全局的过滤器。
Vue.filter('filterA', function (val) {
  if (val === null || val === undefined || val === '') {
    return '-'
  }
  return val
})
Vue.filter('filterB', function (val, number) {
  // 保留两位小数
  return val.toFixed(number)
})
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- formatPrice 是计算属性-->
    <h2>商品价格:{{formatPrice}}</h2>
    <!--方法 formatPrice2() 的返回值-->
    <h2>商品价格:{{formatPrice2()}}</h2>
    <!--输入 price,首先经过第一层过滤 filterA,之后经过第二层过滤 filterB(3) 保留 3 位小数-->
    <h2>商品价格:{{price | filterA | filterB(3)}}</h2>
    <input type="text" :value="price | filterA | filterB(3)">
  </div>
  <hr>
  <div>
    <h2>商品价格:{{price | filterA | filterB(3)}}</h2>
  </div>
  <script>
    // 配置全局的过滤器。
    // 第一个参数是全局过滤器的名字,第二个参数是全局过滤器的'处理函数'
    Vue.filter('filterA', function (val) {
      if (val === null || val === undefined || val === '') {
        return '-'
      }
      return val
    })
    Vue.filter('filterB', function (val, number) {
      // 保留两位小数
      return val.toFixed(number)
    })
    const vm2 = new Vue({
      el: '#app2',
      data: {
        price: 20.3
      }
    })
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '过滤器',
        price: 50.6
      },
      methods: {
        formatPrice2() {
          if (this.price === '' || this.price === undefined || this.price === null) {
            return '-'
          }
          return this.price
        }
      },
      computed: {
        formatPrice() {
          if (this.price === '' || this.price === undefined || this.price === null) {
            return '-'
          }
          return this.price
        }
      },
    })
  </script>
</body>

5. 计算属性 computed

虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性。


使用 Vue 的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。

**Vue 的原有属性:**data 对象当中的属性可以叫做 Vue 的原有属性。

**全新的属性:**表示生成了一个新的属性,和 data 中的属性无关了,新的属性也有自己的属性名和属性值。


5.1 反转字符串 methods 实现

<body>
  <div>
    <h1>{{msg}}</h1>
    输入的信息:<input type="text" v-model="info">
    <br>
    <!-- 在插值语法中可以调用方法,小括号不能省略。这个方法需要是 Vue 实例所管理的。 -->
    反转的信息:{{reverseInfo()}}
    <br>
    反转的信息:{{reverseInfo()}}
    <br>
    反转的信息:{{reverseInfo()}}
    <br>
    反转的信息:{{reverseInfo()}}
    <br>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '计算属性 - 反转字符串案例',
        info: ''
      },
      methods: {
        // 反转信息的方法
        reverseInfo() {
          console.log('@')
          return this.info.split('').reverse().join('');
        }
      }
    })
  </script>
</body>

5.2 反转字符串计算属性实现

<!--语法格式:需要一个新的配置项 computed
 * computed : {
 *   // 这是一个计算属性
 *   计算属性 1 : {
 *     // setter 和 getter 方法。
 *     // 当读取计算属性 1 的值的时候,getter 方法被自动调用。
 *     get(){ },
 *     // 当修改计算属性 1 的值的时候,setter 方法被自动调用。
 *     set(val){ }
 *   },
 * }
-->
<div>
  <h1>{{msg}}</h1>
  输入的信息:<input type="text" v-model="info">
  <br>
  反转的信息:{{reversedInfo}}<br>
  反转的信息:{{reversedInfo}}<br>
  反转的信息:{{reversedInfo}}<br>
  反转的信息:{{reversedInfo}}<br>
  反转的信息:{{reversedInfo}}<br>
  {{hehe}} <br>
  <!-- 计算属性的缓存机制,所以只调用一次 get() 方法 -->
  {{hehe}} <br>
  {{hehe}} <br>
  {{hehe}} <br>
  {{hehe}} <br>
  {{hello()}} <br>
  {{hello()}} <br>
  {{hello()}} <br>
  {{hello()}} <br>
  {{hello()}} <br>
</div>
<script>
const vm = new Vue({
  el: '#app',
  data: {
    msg: '计算属性 - 反转字符串案例',
    info: ''
  },
  methods: {
    hello() {
      console.log('hello 方法执行了')
      return 'hello'
    }
  },
  computed: {
    // 可以定义多个计算属性
    hehe: {
      // get 方法的调用时机包括两个
      // 第一个时机:第一次访问这个属性的时候。
      // 第二个时机:该计算属性所关联的 Vue 原有属性的值发生变化时,getter 方法会被重新调用一次。
      get() {
        console.log('getter 方法调用了')
        //console.log(this === vm) TRUE
        return 'haha' + this.info
      },
      // 不能使用箭头函数,使用箭头函数会导致 this 的指向是:window
      // get:()=>{
      //   console.log('getter 方法调用了')
      //   console.log(this === vm)
      //   return 'haha'
      // },
      set(val) {
        console.log('setter 方法调用了')
        //console.log(this === vm) TRUE
      }
    },
    // 完整写法
    reversedInfo: {
      get() {
        return this.info.split('').reverse().join('')
      },
      // 当修改计算属性的时候,set 方法被自动调用。
      set(val) {
        //console.log('setter 方法被调用了。')
        // 不能这么做,这样做就递归了。
        //this.reversedInfo = val
        // 怎么修改计算属性呢?原理:计算属性的值变还是不变,取决于计算属性关联的 Vue 原始属性的值。
        // 也就是说:reversedInfo 变还是不变,取决于 info 属性的值变不变。
        // 本质上:修改计算属性,实际上就是通过修改 Vue 的原始属性来实现的。
        this.info = val.split('').reverse().join('')
      }
    }
    // 简写形式:set 不需要的时候。
    /* reversedInfo() {
      return this.info.split('').reverse().join('')
    } */
  }
})
</script>

6. 监视属性

  1. 监视属性:监视哪个属性,就把属性放入 watch 中即可。
  2. 可以监视 Vue 的原有属性。
<!-- watch: {
 * 1. 打开页面初始化时,会调用一次 handler 方法。
 * 2. handler 方法的调用时间:
 *    当被监视的属性发生变化的时候,handler 就会自动调用一次。
 * 3. handler 方法上有两个参数:第一个参数 newValue,第二个参数是 oldValue。
 *    newValue 是属性值改变之后的新值,oldValue 是属性值改变之前的旧值。
 * }
-->

6.1 监视属性的变化

<body>
  <div>
    <h1>{{msg}}</h1>
    数字:<input type="text" v-model="number"><br>
    数字:<input type="text" v-model="a.b"><br>
    数字:<input type="text" v-model="a.c"><br>
    数字:<input type="text" v-model="a.d.e.f"><br>
    数字 (后期添加监视): <input type="text" v-model="number2"><br>
    {{hehe}}
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        number2: 0,
        msg: '侦听属性的变化',
        number: 0,
        // a 属性中保存的值是一个对象的内存地址。
        // a = 0x2356
        a: {
          b: 0,
          c: 0,
          d: {
            e: {
              f: 0
            }
          }
        }
      },
      computed: {
        hehe() {
          return 'haha:' + this.number
        }
      },
      watch: {
        // 可以监视多个属性
        // 监视哪个属性,请把这个属性的名字拿过来即可。
        // 可以监视 Vue 的原有属性
        number: {
          // 打开页面初始化的时候,调用一次 handler 方法。
          immediate: true,
          handler(newValue, oldValue) {
            console.log(newValue, oldValue)
            // this 是当前的 Vue 实例。
            // 如果该函数是箭头函数,这个 this 是 window 对象。不建议使用箭头函数。
            console.log(this)
          }
        },
        // 无法监视 b 属性,因为 b 属性压根不存在。
        /* b : {
          handler(newValue, oldValue){
            console.log('@')
          }
        } */
        // 如果监视的属性具有多级结构,一定要添加单引号:'a.b'
        /* 'a.b' : {
          handler(newValue, oldValue){
            console.log('@')
          },
          'a.c' : {
            handler(newValue, oldValue){
              console.log('@')
            },
        */
        a: {
          // 启用深度监视,默认是不开启深度监视的。
          // 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。
          deep: true,
          handler(newValue, oldValue) {
            console.log('@')
          }
        },
        // 注意:监视某个属性的时候,也有简写形式,什么时候启用简写形式?
        // 当只有 handler 回调函数的时候,可以使用简写形式。
        number(newValue, oldValue) {
          console.log(newValue, oldValue)
        },
        // 也可以监视计算属性
        hehe: {
          handler(a, b) {
            console.log(a, b)
          }
        }
      }
    })
  </script>
</body>

6.2 后期添加监视

// 如何后期添加监视?调用 Vue 相关的 API 即可。
// 语法:vm.$watch('被监视的属性名', {})
vm.$watch('number2', {
  immediate: true,
  deep: true,
  handler(newValue, oldValue){
    console.log(newValue, oldValue)
  }
})
// 这是后期添加监视的简写形式 (能实现的功能也就是 handler)。
vm.$watch('number2', function (newValue, oldValue) {
  console.log(newValue, oldValue)
})

6.3 immediate 选项

默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。 如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。

immediate : true 页面首次加载完毕就触发 handler:0, undefined

6.4 deep 选项

如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。 此时需要使用 deep 选项。

7. 数据代理

7.1 VM(View Model)

<body>
  <!-- 1. 通过 Vue 实例都可以访问哪些属性?(通过 vm 都可以 vm. 什么。)
     * Vue 实例中的属性很多,有的以 $ 开始,有的以 _ 开始。
     * 所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。
     * 所有以 _ 开始的属性,可以看做是私有的属性,这些属性是 Vue 框架底层使用的。一般我们程序员很少使用。
     * 通过 vm 也可以访问 Vue 实例对象的原型对象上的属性,例如:vm.$delete...
     -->
  <div>
    <h1>{{msg}}</h1>
  </div>
  <script>
    let dataObj = { msg: 'Hello Vue!' }
    const vm = new Vue({
      el: '#app',
      data: dataObj
    })
    // 按说 msg 是 dataObj 对象的属性。
    console.log('dataObj 的 msg', dataObj.msg);
    // 为什么 msg 属性可以通过 vm 来访问呢?
    // 这是因为 Vue 框架底层使用了数据代理机制。
    // 要想搞明白数据代理机制,必须有一个基础知识点要学会:Object.defineProperty()。
    console.log('vm 的 msg', vm.msg);
  </script>
</body>

7.2 Object.defineProperty() 方法

给对象新增属性,或者设置对象原有的属性

<!-- Object.defineProperty()
 * 1. 怎么用?
 *    Object.defineProperty(给哪个对象新增属性,'新增的这个属性名叫啥', {给新增的属性设置相关的配置项 key:value 对})
 * 2. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
 *    value 配置项:给属性指定值
 *    writable 配置项:设置该属性的值是否可以被修改。true 表示可以修改。false 表示不能修改。
 *    getter 方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter 方法被自动调用。
 *    * getter 方法的返回值非常重要,这个返回值就代表这个属性它的值。
 *    setter 方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter 方法被自动调用。
 *    * setter 方法上是有一个参数的,这个参数可以接收传过来的值。
 *    注意:当配置项当中有 setter 和 getter 的时候,value 和 writable 配置项都不能存在。
-->
<script>
// 这是一个普通的对象
let phone = {}
// 临时变量
let temp
// 给上面的 phone 对象新增一个 color 属性
Object.defineProperty(phone, 'color', {
  //value : '太空灰',
  //writable : true,
  // 默认值是 false
  // getter 方法配置项
  get: function () {
    console.log('getter 方法执行了@@@');
    //return '动态'
    //return this.color 会出现递归情况,不能使用
    return temp
  },
  // setter 方法配置项
  set: function (val) {
    console.log('setter 方法执行了@@@', val);
    //this.color = val // 会出现递归情况,不能使用
    temp = val
  }
})
</script>

7.3 数据代理机制

  1. 什么是数据代理机制? 通过访问 代理对象的属性 来间接访问 目标对象的属性。 数据代理机制的实现需要依靠:Object.defineProperty() 方法。
  2. ES6 新特性: 在对象中的函数/方法 :function 是可以省略的。
<body>
  <div>
    <h1>{{msg}}</h1>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue!'
      }
    })
  </script>
  <script>
    // 目标对象
    let target = { name: 'zhangsan' }
    // 代理对象
    let proxy = {}
    // 如果要实现数据代理机制的话,就需要给 proxy 新增一个 name 属性。
    // 注意:代理对象新增的这个属性的名字 和 目标对象的属性名要一致。
    Object.defineProperty(proxy, 'name', {
      // get : function(){
      //   // 间接访问目标对象的属性
      //   return target.name
      // },
      // set : function(val){
      //   target.name = val
      // }
      get(){
        console.log('getter 方法执行了@@@@');
        return target.name
      },
      set(val){
        target.name = val
      }
    })
  </script>
</body>
7.3.1 Vue 数据代理机制对属性名的要求
<!-- 1. Vue 实例不会给以_和$开始的属性名做数据代理。
 * 2. 为什么?
 *    如果允许给_或$开始的属性名做数据代理的话。
 *    vm 这个 Vue 实例上可能会出现_xxx 或$xxx 属性,
 *    而这个属性名可能会和 Vue 框架自身的属性名冲突。
 * 3. 在 Vue 当中,给 data 对象的属性名命名的时候,不能以_或$开始。
-->
7.3.2 手写 Vue 框架数据代理的实现
// 定义一个 Vue 类 用于数据代理功能的实现
// 创建一个 Vue 实例的构造函数
class Vue {
  // 定义构造函数
  // options 是一个简单的纯粹的 JS 对象:{}
  // options 对象中有一个 data 配置项
  constructor(options) {
    // 获取所有的属性名
    // 首先遍历 data 对象的属性名列表
    // 之后遍历对 data 对象中的每个属性(如 msg, count 等)进行迭代处理
    // propertyName:当前属性的名称,index:当前属性在属性列表中的索引位置
    Object.keys(options.data).forEach((propertyName, index) => {
      //console.log(typeof propertyName, propertyName, index)
      let firstChar = propertyName.charAt(0)
      if (firstChar != '_' && firstChar != '$') {
        Object.defineProperty(this, propertyName, {
          // 数据代理
          get() {
            return options.data[propertyName]
          },
          // 数据劫持
          set(val) {
            //1. 修改内存中该对象的属性值
            options.data[propertyName] = val
            //2. 重新渲染页面
          }
        })
      }
    })
    // 获取所有的方法名
    /* Object.keys(options.methods).forEach((methodName, index) => {
      // 给当前的 Vue 实例扩展一个方法
      this[methodName] = options.methods[methodName]
    }) */
  }
}
<body>
  <!-- 容器 -->
  <div>
    <h1>{{msg}}</h1>
  </div>
  <!-- Vue 代码 -->
  <script>
    // const vm = new Vue({
    //   el: '#app',
    //   data: {
    //     msg: 'Hello Vue!',
    //     name: 'jackson',
    //     age: 30
    //   }
    // })
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue!',
        _name: 'jackson',
        $age: 30
      }
    })
  </script>
</body>

8. Vue 框架源代码

var data = vm.$options.data;
  • 创建 Vue 实例时,原始数据会被存储在 vm._data 属性中。
  • vm 是一个 Vue 实例,vm.$options.data 就是创建该实例时传入的 data 选项。
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
  • isFunction(data)判断传入的 data 选项是否是函数类型
<!--Vue 允许两种形式的 data:
 * 对象形式:data: { message: 'Hello' }
 * 函数形式:data() { return { message: 'Hello' } }
-->

如果 data 是函数,则调用 getData(data, vm) 来获取真正的数据对象 data。

如果 data 不是函数,则直接使用 data。如果是 undefined或 null,使用空对象 {} 作为默认值。

双重赋值:将处理后的数据对象同时赋值给:局部变量 data 和 Vue 实例的 _data属性。

8.1 为什么要给 vm 扩展_data 属性?

<!-- 程序执行到这里,为什么要给 vm 扩展一个_data 属性呢?
 * _data 属性,以"_"开始,足以说明,这个属性是人家 Vue 框架底层需要访问的。
 * Vue 框架底层它使用 vm._data 这个属性干啥呢?
 * vm._data 是啥?
 * vm._data 是:{ name : 'jackson', age : 35 }
 * vm._data 这个属性直接指向了底层真实的 data 对象。
 * 通过_data 访问的 name 和 age 是不会走数据代理机制的。
 * 通过 vm._data 方式获取 name 和 age 的时候,是不会走 getter 和 setter 方法的。
 * 注意:对于 Vue 实例 vm 来说,不仅有_data 这个属性,还有一个$data 这个属性。
 * _data 是框架内部使用的,可以看做私有的。
 * $data 这是 Vue 框架对外公开的一个属性,是给我们程序员使用。
-->

8.2 重点函数

<!-- function isReserved(str) {
 * var c = (str + '').charCodeAt(0);
 * return c === 0x24 || c === 0x5f;
 * }
 * 这个函数是用来判断字符串是否以 _ 和 $开始的。
 * true 表示以_或$开始的。
 * false 表示不是以_或$开始的。
-->
<!-- proxy(vm, "_data", key); 通过这行代码直接进入代理机制(数据代理)。
 * // target 是目标对象 Vue 实例 (vm)
 * // sourceKey: 源数据属性名(如 '_data')
 * // key: 要代理的属性名(如 'age')
 * function proxy(target, sourceKey, key) {
 *   sharedPropertyDefinition.get = function proxyGetter() {
 *     return this["_data"]["age"];
 *   };
 *   sharedPropertyDefinition.set = function proxySetter(val) {
 *     this["_data"]["age"] = val;
 *   };
 *   // 定义代理属性;在目标对象(Vue 实例)上定义一个新属性
 *   Object.defineProperty(vm, 'age', sharedPropertyDefinition);
 * }
-->
<!-- 容器 -->
<div>
  <h1>姓名:{{name}}</h1>
  <h1>年龄:{{age}}岁</h1>
</div>
<!-- vue 代码 -->
<script>
function isReserved(str) {
  var c = (str + '').charCodeAt(0);
  return c === 0x24 || c === 0x5f;
}
const vm = new Vue({
  el: '#app',
  data: {
    name: 'jackson',
    age: 35
  }
})
// 如果我们程序员不想走代理的方式读取 data,想直接读取 data 当中的数据,可以通过_data 和$data 属性来访问。
// 建议使用$data 这个属性。
console.log('name = ' + vm.$data.name)
console.log('age = ' + vm.$data.age)
</script>

8.3 data(函数形式)

<body>
  <!-- 容器 -->
  <div>
    <h1>{{msg}}</h1>
  </div>
  <!-- vue 代码 -->
  <script>
    const vm = new Vue({
      el: '#app',
      // data : {
      //   msg : 'Hello Vue!'
      // }
      // data functions should return an object:data 函数应该返回一个对象。
      // data 也可以是一个函数。
      // 如果是函数的话,必须使用 return 语句返回{}对象。
      // data 可以是直接的对象,也可以是一个函数,什么时候使用直接的对象?什么时候使用函数呢?
      // (等你学到组件的时候自然就明白了。)
      // data : function(){
      //   return {
      //     msg : 'Hello Vue!'
      //   }
      // }
      // 在对象当中,函数的 :function 可以省略
      data() {
        return {
          msg: 'Hello Zhangsan!'
        }
      }
    })
    // 关于源码 sharedPropertyDefinition 函数中的配置项:enumerable、configurable
    let phone = { name: '苹果 X' }
    // 给 phone 对象新增一个 color 属性
    Object.defineProperty(phone, 'color', {
      value: '奶奶灰',
      // true 表示该属性是可以遍历的。(可枚举的,可迭代的。)
      // false 表示该属性是不可遍历的。
      enumerable: false,
      // true 表示该属性是可以被删除的。
      // false 表示该属性是不可以被删除的。
      configurable: false
    })
  </script>
</body>

9. 数据绑定

9.1 Class 绑定

9.1.1 字符串形式
<head>
  <title>Class 绑定之字符串形式</title>
  <script src="../js/vue.js"></script>
  <style>
    .static { border: 1px solid black; background-color: aquamarine; }
    .big { width: 200px; height: 200px; }
    .small { width: 100px; height: 100px; }
  </style>
</head>
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- 静态写法 -->
    <div>{{msg}}</div>
    <br><br>
    <button @click="changeBig">变大</button>
    <button @click="changeSmall">变小</button>
    <!-- 动态写法:动静都有 -->
    <!-- 适用场景:如果确定动态绑定的样式个数只有 1 个,但是名字不确定。 -->
    <div :class="c1">{{msg}}</div>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Class 绑定之字符串形式',
        c1: 'small'
      },
      methods: {
        changeBig() {
          this.c1 = 'big'
        },
        changeSmall() {
          this.c1 = 'small'
        }
      },
    })
  </script>
</body>
9.1.2 数组形式
<head>
  <title>Class 绑定之数组形式</title>
  <script src="../js/vue.js"></script>
  <style>
    .static { border: 1px solid black; width: 100px; height: 100px; }
    .active { background-color: green; }
    .text-danger { color: red; }
  </style>
</head>
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- 静态写法 -->
    <div>{{msg}}</div>
    <br>
    <!-- 动态写法:动静结合 -->
    <div :class="['active','text-danger']">{{msg}}</div>
    <br>
    <div :class="[c1, c2]">{{msg}}</div>
    <br>
    <!-- 适用场景:当样式的个数不确定,并且样式的名字也不确定的时候,可以采用数组形式。 -->
    <div :class="classArray">{{msg}}</div>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Class 绑定之数组形式',
        c1: 'active',
        c2: 'text-danger',
        classArray: ['active', 'text-danger']
      }
    })
  </script>
</body>
9.1.3 对象形式
<head>
  <title>Class 绑定之对象形式</title>
  <script src="../js/vue.js"></script>
  <style>
    .static { border: 1px solid black; width: 100px; height: 100px; }
    .active { background-color: green; }
    .text-danger { color: red; }
  </style>
</head>
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- 动态写法:动静结合 -->
    <!-- 对象形式的适用场景:样式的个数是固定的,样式的名字也是固定的,但是需要动态的决定样式用还是不用。 -->
    <div :class="classObj">{{msg}}</div>
    <br>
    <div :class="{active:true,'text-danger':false}">{{msg}}</div>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Class 绑定之对象形式',
        classObj: {
          // 该对象中属性的名字必须和样式名一致。
          active: false,
          'text-danger': true
        }
      }
    })
  </script>
</body>

9.2 style 绑定

<head>
  <title>Style 绑定</title>
  <script src="../js/vue.js"></script>
  <style>
    .static { border: 1px solid black; width: 100px; height: 100px; }
  </style>
</head>
<body>
  <div>
    <h1>{{msg}}</h1>
    <!-- 静态写法 -->
    <div>{{msg}}</div>
    <br>
    <!-- 动态写法:字符串形式 -->
    <div :style="myStyle">{{msg}}</div>
    <br>
    <!-- 动态写法:对象形式 -->
    <!-- 这个属性名需要为驼峰形式 -->
    <div :style="{backgroundColor: 'gray'}">{{msg}}</div>
    <br>
    <div :style="styleObj1">{{msg}}</div>
    <br>
    <!-- 动态写法:数组形式 -->
    <div :style="styleArray">{{msg}}</div>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Style 绑定',
        myStyle: 'background-color: gray;',
        styleObj1: {
          backgroundColor: 'green'
        },
        styleArray: [
          { backgroundColor: 'green' },
          { color: 'red' }
        ]
      }
    })
  </script>
</body>

10. 列表渲染

<body>
  <div>
    <h1>{{msg}}</h1>
    <h2>遍历对象的属性</h2>
    <ul>
      <li v-for="(value, propertyName) of user"> {{propertyName}},{{value}} </li>
    </ul>
    <h2>遍历字符串</h2>
    <ul>
      <li v-for="(c,index) of str"> {{index}},{{c}} </li>
    </ul>
    <h2>遍历指定的次数</h2>
    <ul>
      <li v-for="(num,index) of counter"> {{index}}, {{num}} </li>
    </ul>
    <h2>遍历数组</h2>
    <!-- 静态列表 -->
    <ul>
      <li>张三</li>
      <li>李四</li>
      <li>王五</li>
    </ul>
    <!-- 动态列表 -->
    <ul>
      <!-- 1. v-for 要写在循环项上。
           2. v-for 的语法规则:v-for="(变量名,index) in/of 数组"
           变量名 代表了 数组中的每一个元素 -->
      <li v-for="fdsafds in names"> {{fdsafds}} </li>
    </ul>
    <ul>
      <li v-for="name of names"> {{name}} </li>
    </ul>
    <ul>
      <li v-for="(name,index) of names"> {{name}}-{{index}} </li>
    </ul>
    <ul>
      <!-- 对象形式 -->
      <li v-for="(vip,index) of vips"> 会员名:{{vip.name}},年龄:{{vip.age}}岁 </li>
    </ul>
    <table>
      <tr>
        <th>序号</th>
        <th>会员名</th>
        <th>年龄</th>
        <th>选择</th>
      </tr>
      <tr v-for="(vip,index) in vips">
        <td>{{index+1}}</td>
        <td>{{vip.name}}</td>
        <td>{{vip.age}}</td>
        <td><input type="checkbox"></td>
      </tr>
    </table>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '列表渲染',
        names: ['jack', 'lucy', 'james'],
        vips: [
          { id: '111', name: 'jack', age: 20 },
          { id: '222', name: 'lucy', age: 30 },
          { id: '333', name: 'james', age: 40 }
        ],
        user: { id: '111', name: '张三', gender: '男' },
        str: '动力节点',
        counter: 10
      }
    })
  </script>
</body>

11. 列表排序

<body>
  <div>
    <h1>{{msg}}</h1>
    <input type="text" placeholder="请输入搜索关键字" v-model="keyword">
    <br>
    <button @click="type = 1">升序</button>
    <button @click="type = 2">降序</button>
    <button @click="type = 0">原序</button>
    <table>
      <tr>
        <th>序号</th>
        <th>英雄</th>
        <th>能量值</th>
        <th>选择</th>
      </tr>
      <tr v-for="(hero,index) in filteredHeros" :key="hero.id">
        <td>{{index+1}}</td>
        <td>{{hero.name}}</td>
        <td>{{hero.power}}</td>
        <td><input type="checkbox"></td>
      </tr>
    </table>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        type: 0,
        keyword: '',
        msg: '列表排序',
        heros: [
          { id: '101', name: '艾格文', power: 10000 },
          { id: '102', name: '麦迪文', power: 9000 },
          { id: '103', name: '古尔丹', power: 8000 },
          { id: '104', name: '萨尔', power: 11000 }
        ]
      },
      computed: {
        filteredHeros() {
          // 执行过滤
          const arr = this.heros.filter((hero) => {
            return hero.name.indexOf(this.keyword) >= 0
          })
          // 排序
          if (this.type === 1) {
            arr.sort((a, b) => {
              return a.power - b.power
            })
          } else if (this.type === 2) {
            arr.sort((a, b) => {
              return b.power - a.power
            })
          }
          // 返回
          return arr
        }
      }
    })
    // 回顾 sort 方法
    let arr = [8, 9, 5, 4, 1, 2, 3]
    // sort 方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构。
    arr.sort((a, b) => {
      return b - a
    })
    console.log(arr)
  </script>
</body>

12. 表单数据的收集

<body>
  <div>
    <h1>{{msg}}</h1>
    <form @submit.prevent="send">
      <!-- 去掉前后的空格 -->
      用户名:<input type="text" v-model.trim="user.username"><br><br>
      密码:<input type="password" v-model="user.password"><br><br>
      <!-- v-model.number 用于做类型转换,最后收到的数据没有双引号 -->
      年龄:<input type="number" v-model.number="user.age"><br><br>
      性别:
      男<input type="radio" name="gender" value="1" v-model="user.gender">
      女<input type="radio" name="gender" value="0" v-model="user.gender"><br><br>
      爱好:
      <!-- 注意:对于 checkbox 来说,如果没有手动指定 value,那么会拿这个标签的 checked 属性的值作为 value -->
      旅游<input type="checkbox" v-model="user.interest" value="travel">
      运动<input type="checkbox" v-model="user.interest" value="sport">
      唱歌<input type="checkbox" v-model="user.interest" value="sing"><br><br>
      学历:
      <select v-model="user.grade">
        <option>请选择学历</option>
        <option value="zk">专科</option>
        <option value="bk">本科</option>
        <option value="ss">硕士</option>
      </select><br><br>
      简介:
      <!-- v-model.lazy 失去焦点再提交-->
      <textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br>
      <input type="checkbox" v-model="user.accept">阅读并接受协议<br><br>
      <!-- <button @click.prevent="send">注册</button> -->
      <button>注册</button>
    </form>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        user: {
          username: '',
          password: '',
          age: '',
          gender: '1',
          interest: ['travel'],
          grade: 'ss',
          introduce: '',
          accept: ''
        },
        msg: '表单数据的收集'
      },
      methods: {
        send() {
          alert('ajax...!!!!')
          // 将数据收集好,发送给服务器。JSON 格式
          //console.log(JSON.stringify(this.$data))
          console.log(JSON.stringify(this.user))
        }
      }
    })
  </script>
</body>

13. 响应式与数据劫持

修改 data 后,页面自动改变/刷新。这就是响应式。Vue 的响应式是如何实现的?数据劫持:Vue 底层使用了 Object.defineProperty,配置了 setter 方法,当去修改属性值时 setter 方法则被自动调用,setter 方法中不仅修改了属性值,而且还做了其他的事情,例如:重新渲染页面。setter 方法就像半路劫持一样,所以称为数据劫持。

<body>
  <div>
    <h1>{{msg}}</h1>
    <div>姓名:{{name}}</div>
    <div>年龄:{{age}}岁</div>
    <div>数字:{{a.b.c.e}}</div>
    <div>邮箱:{{a.email}}</div>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '响应式与数据劫持',
        name: 'jackson',
        age: 20,
        a: {
          b: {
            c: {
              e: 1
            }
          }
        }
      }
    })
    // 测试:后期给 Vue 实例动态的追加的一些属性,会添加响应式处理吗?
    // 目前来看,通过这种方式后期给 vm 追加的属性并没有添加响应式处理。
    //vm.$data.a.email = '[email protected]'
    // 如果你想给后期追加的属性添加响应式处理的话,调用以下两个方法都可以:
    // Vue.set() 、 vm.$set()
    //Vue.set(目标对象,属性名,属性值)
    //Vue.set(vm.$data.a, 'email', '[email protected]')
    //Vue.set(vm.a, 'email', '[email protected]')
    vm.$set(vm.a, 'email', '[email protected]')
    // 避免在运行时向 Vue 实例或其根$data 添加响应式
    // 不能直接给 vm / vm.$data 追加响应式属性。只能在声明时提前定义好。
    //Vue.set(vm, 'x', '1')
    //Vue.set(vm.$data, 'x', '1')
  </script>
</body>

13.1 数组的响应式处理

控制台修改,页面实时渲染。

<body>
  <!-- 1. 通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的。怎么解决?
     * 数组内每个对象中的属性是有响应式处理的
     * 2. 第一种方案:vm.$set(数组对象,下标,值) Vue.set(数组对象,下标,值)
     * 3. 第二种方案:push() pop() reverse() splice() shift() unshift() sort()
     *    在 Vue 当中,通过以上的 7 个方法来给数组添加响应式处理。
     -->
  <div>
    <h1>{{msg}}</h1>
    <ul>
      <li v-for="user in users"> {{user}} </li>
    </ul>
    <ul>
      <li v-for="vip in vips" :key="vip.id"> {{vip.name}} </li>
    </ul>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '数组的响应式处理',
        users: ['jack', 'lucy', 'james'],
        vips: [
          { id: '111', name: 'zhangsan' },
          { id: '222', name: 'lisi' }
        ]
      }
    })
  </script>
</body>
13.1.1 响应式处理代码
// ✅ 正确方法:使用 Vue 包装的数组方法
vm.users.push('newUser') // 添加元素
vm.users.pop() // 删除最后一个
vm.users.splice(0, 1) // 删除第一个
vm.users.splice(1, 0, 'inserted') // 在索引 1 处插入
// ✅ 使用 Vue.set
Vue.set(vm.users, 0, 'modified') // 修改索引 0 的值
// ❌ 错误方法(不会触发更新)
vm.users[0] = 'newName' // 直接索引赋值
vm.users.length = 2 // 修改长度
// ✅ 修改对象属性(响应式)
Vue.set(vm.vips[0], 'name', '张三') // 修改第一个 VIP 的名字
vm.vips[0].age = 30 // 添加新属性(需用 Vue.set)
// ✅ 添加新对象
vm.vips.push({ id: '333', name: '王五' })
// ✅ 删除对象
vm.vips.splice(1, 1) // 删除索引 1 的对象
// ✅ 新增对象属性
Vue.set(vm.vips[0], 'gender', 'male')
// ❌ 错误方法
vm.vips[0] = { id: '111', name: '张三' } // 直接替换对象
// ✅ 完全替换数组
vm.users = ['new', 'array', 'items']
vm.vips = [
  { id: '999', name: '新用户' },
  { id: '888', name: '测试用户' }
]
// ✅ 过滤,映射后替换;创建新数组替换原数组
vm.users = vm.users.filter(user => user !== 'lucy')
vm.vips = vm.vips.map(vip => ({ ...vip, name: vip.name.toUpperCase() }))

14. Vue 的生命周期

文章配图

<body>
  <div>
    <h1>{{msg}}</h1>
    <h3>计数器:{{counter}}</h3>
    <h3 v-text="counter"></h3>
    <button @click="add">点我加 1</button>
    <button @click="destroy">点我销毁</button>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: 'Vue 生命周期',
        counter: 1
      },
      methods: {
        add() {
          console.log('add....')
          this.counter++
        },
        destroy() {
          // 销毁 vm
          this.$destroy()
        },
        /* m(){
          console.log('m....')
        } */
      },
      watch: {
        counter() {
          console.log('counter 被监视一次!')
        }
      },
      /* 1.初始阶段
       * el 有,template 也有,最终编译 template 模板语句。
       * el 有,template 没有,最终编译 el 模板语句。
       * el 没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。
       * 此时如果 template 有,最终编译 template 模板语句。
       * el 没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。
       * 此时如果没有 template,最终编译 el 模板语句。
       * 结论:流程要想继续:el 必须存在。
       * el 和 template 同时存在,优先选择 template。如果没有 template,才会选择 el。
       */
      beforeCreate() {
        // 创建前
        // 创建前指的是:数据代理和数据监测的创建前。
        // 此时还无法访问 data 当中的数据。包括 methods 也是无法访问的。
        console.log('beforeCreate', this.counter)
        // 调用 methods 报错了,不存在。
        //this.m()
      },
      created() {
        // 创建后
        // 创建后表示数据代理和数据监测创建完毕,可以访问 data 中的数据了。
        console.log('created', this.counter)
        // 可以访问 methods 了。
        //this.m()
        debugger // 加断点
      },
      // 2.挂载阶段
      beforeMount() {
        // 挂载前
        console.log('beforeMount')
      },
      mounted() {
        // 挂载后
        console.log('mounted')
        // 创建 vm.$el 并用其代替"el"
        console.log(this.$el)
        console.log(this.$el instanceof HTMLElement)
      },
      // 3.更新阶段
      beforeUpdate() {
        // 更新前
        console.log('beforeUpdate')
      },
      updated() {
        // 更新后
        console.log('updated')
      },
      // 4.销毁阶段
      beforeDestroy() {
        // 销毁前
        console.log('beforeDestroy')
        console.log(this)
        // 虽然仍然绑定监视器,但是不能使用 this.counter = 1000
      },
      destroyed() {
        // 销毁后
        console.log('destroyed')
        console.log(this)
      },
    })
  </script>
</body>

14.1 创建阶段

beforeCreate:实例刚被创建,数据观测和事件配置之前调用.

  • 使用场景:插件初始化、全局事件总线设置
  • 特点:无法访问 data、methods 和 computed 等属性
beforeCreate() {
  console.log('beforeCreate: 实例刚创建');
  console.log('data: ', this.message); // undefined
}

created:实例创建完成,数据观测和计算属性等已配置.

  • 使用场景:API 请求、初始化数据、访问响应式数据
  • 特点:可以访问数据,但 DOM 尚未生成
created() {
  console.log('created: 实例创建完成');
  console.log('data: ', this.message); // 'Hello Vue!'
  this.fetchData(); // 发起 API 请求
}

14.2 挂载阶段

beforeMount:挂载开始之前调用,模板已编译但未渲染到页面.

beforeMount() {
  console.log('beforeMount: 挂载之前');
  console.log('$el: ', this.$el); // undefined
}
  • 使用场景:操作 DOM 前的准备工作
  • 特点:$el 属性尚未生成

mounted:实例挂载到 DOM 后调用.

  • 使用场景:操作 DOM、集成第三方库、启动定时器
  • 特点:可以访问渲染后的 DOM 元素
mounted() {
  console.log('mounted: 挂载完成');
  console.log('$el: ', this.$el); // DOM 元素
  this.initChart(); // 初始化图表库
  this.timer = setInterval(this.updateData, 1000);
}

14.3 更新阶段

beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前.

beforeUpdate() {
  console.log('beforeUpdate: 数据更新前');
  console.log('当前值:', this.count);
  console.log('DOM 值:', this.$refs.counter.textContent); // 更新前的值
}
  • 使用场景:获取更新前的 DOM 状态
  • 特点:可以访问当前数据,但 DOM 尚未更新

updated:数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用.

updated() {
  console.log('updated: 数据更新完成');
  console.log('DOM 值:', this.$refs.counter.textContent); // 更新后的值
}
  • 使用场景:操作更新后的 DOM
  • 注意事项:避免在此钩子中修改状态,可能导致无限循环

14.4 销毁阶段

beforeDestroy:实例销毁之前调用.

  • 使用场景:清理工作(清除定时器、取消事件监听、销毁第三方库实例)
  • 特点:实例仍然完全可用
beforeDestroy() {
  console.log('beforeDestroy: 销毁之前');
  clearInterval(this.timer); // 清除定时器
  this.chart.destroy(); // 销毁图表实例
  window.removeEventListener('resize', this.handleResize);
}

destroyed:实例销毁后调用.

  • 使用场景:最后的清理工作
  • 特点:所有绑定已解除,事件监听器已移除
destroyed() {
  console.log('destroyed: 销毁完成');
}

目录

  1. 1. Vue2 简介
  2. 1.1 数据驱动视图
  3. 1.1.1 单向数据绑定
  4. 1.1.2 双向数据绑定
  5. 1.1.3 MVVM 分层思想
  6. 2. 第一个 Vue 程序
  7. 2.1 Vue 构造函数的参数:options
  8. 2.2 Vue 实例挂载
  9. 2.3 Template 语句的数据来源 data
  10. 2.4 Template 配置项
  11. 2.5 Vue 实例和容器
  12. 3. Vue 核心语法
  13. 3.1 插值 {{ }} 语法
  14. 3.2 Vue 指令
  15. 3.2.1 v-once 指令
  16. 3.2.2 条件渲染指令
  17. 3.2.2.1 v-if 指令
  18. 3.2.2.2 v-show 指令
  19. 3.2.3 v-bind 属性绑定指令
  20. 3.2.4 v-model 指令
  21. 3.2.4.1 v-model 指令修饰符
  22. 3.2.5 事件绑定指令 v-on
  23. 3.2.5.1 事件修饰符
  24. 3.2.5.2 按键修饰符
  25. 3.2.6 v-for 列表渲染指令
  26. 3.2.7 虚拟 DOM 与 diff 算法(:key 属性)
  27. 3.2.7.1 列表插入删除方法
  28. 3.2.8 v-text v-html 指令
  29. 3.2.9 v-cloak 指令
  30. 3.2.10 v-pre 指令
  31. 3.2.11 自定义指令
  32. 私有自定义指令
  33. 全局自定义指令
  34. 4. 过滤器
  35. 4.1 filters 节点中定义局部过滤器
  36. 4.2 配置全局过滤器
  37. 5. 计算属性 computed
  38. 5.1 反转字符串 methods 实现
  39. 5.2 反转字符串计算属性实现
  40. 6. 监视属性
  41. 6.1 监视属性的变化
  42. 6.2 后期添加监视
  43. 6.3 immediate 选项
  44. 6.4 deep 选项
  45. 7. 数据代理
  46. 7.1 VM(View Model)
  47. 7.2 Object.defineProperty() 方法
  48. 7.3 数据代理机制
  49. 7.3.1 Vue 数据代理机制对属性名的要求
  50. 7.3.2 手写 Vue 框架数据代理的实现
  51. 8. Vue 框架源代码
  52. 8.1 为什么要给 vm 扩展_data 属性?
  53. 8.2 重点函数
  54. 8.3 data(函数形式)
  55. 9. 数据绑定
  56. 9.1 Class 绑定
  57. 9.1.1 字符串形式
  58. 9.1.2 数组形式
  59. 9.1.3 对象形式
  60. 9.2 style 绑定
  61. 10. 列表渲染
  62. 11. 列表排序
  63. 12. 表单数据的收集
  64. 13. 响应式与数据劫持
  65. 13.1 数组的响应式处理
  66. 13.1.1 响应式处理代码
  67. 14. Vue 的生命周期
  68. 14.1 创建阶段
  69. 14.2 挂载阶段
  70. 14.3 更新阶段
  71. 14.4 销毁阶段
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Git 安装配置及 IntelliJ IDEA 集成使用指南
  • TikTok 数据抓取教程:Python 工具快速入门
  • 苍穹外卖实战:Spring Task 定时任务与 WebSocket 实时通信
  • MacOS 部署 OpenClaw 并集成飞书实现 AI 自动化
  • OmniInsert:借助扩散变换器模型实现任意参考对象的无掩码视频插入
  • 智能家居笔记:Home Assistant 与小智 AI 搭建指南
  • Transformer 模型核心原理与从零实现详解
  • Windows 本地部署 OpenClaw 对接飞书机器人指南
  • WebToEpub 使用指南:5 步将网页小说转换为 EPUB 电子书
  • DeepSeek-R1 大模型基于 MS-Swift 框架的部署、推理与微调指南
  • Excel 转 CSV 并导入 MySQL 的操作指南
  • Linux 基础 IO(四):用户缓冲区深度解析
  • Python 办公自动化入门:常用文档与数据处理技巧
  • iOS 26 Liquid Glass TabBar 实现与优化实战
  • JVM 对象引用定位机制:句柄池与直接指针
  • 多模态大模型原理与跨模态应用实战
  • Python 基于 Web 的师资管理系统设计与实现
  • 前后端分离机动车号牌管理系统:基于 SpringBoot、Vue 与 MyBatis
  • LeetCode 罗马数字转整数
  • Stable Diffusion 显存优化方案:解决内存不足错误

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online