Vue入门到精通:从零开始学Vue

Vue入门到精通:从零开始学Vue

目录

一、第一个Vue程序

第一步

Vue构造函数的参数:options

template配置项

第二步

模板语句的数据来源

Template配置项

Vue实例和容器

二、Vue模板语法

Vue 插值

Vue 指令

v-bind指令

v-model指令

三、MVVM分层思想

四、VM

defineProperty

五、数据代理机制

Vue数据代理机制对属性名的要求

手写Vue框架数据代理的实现

六、解读Vue框架源代码

data(函数)

七、Vue事件处理

事件绑定

Vue事件绑定

事件回调函数中的this

methods实现原理

八、事件修饰符

按键修饰符

九、计算属性

反转字符串methods实现

反转字符串计算属性实现

计算属性用法

十、侦听属性

比较大小的案例watch实现

***puted实现

十一、数据绑定

Class绑定

字符串形式

数组形式

对象形式

style绑定

十二、条件渲染

十三、列表渲染

 十四、虚拟DOM与diff算法

列表过滤

列表过滤(计算属性实现)

列表排序

十五、表单数据的收集

十六、过滤器

十七、Vue的其他指令

v-cloak

v-once

自定义指令

十八、响应式与数据劫持

数组的响应式处理

十九、Vue的生命周期


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',

        // Model  M
        data: {
            name: 'zhangsan'
        }
    })

</script>
</body>

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 实例挂载到指定位置。也就是说将 Vue 编译后的 HTML

代码渲染到页面的指定位置。注意:指定位置的元素被替换

2. #app显然是ID选择器

2.3 Template语句的数据来源data

<body>
    <div id="app"></div>
    <script>
        /* 模板语句的数据来源:
            1.谁可以给模板语句提供数据支持呢?data选项。
            2.data选项的类型是什么?0bject|Function(对象或者函数)

            3.data配置项的专业叫法:Vue 实例的数据对象.
                (data实际上是给整个Vue实例提供数据来源的。)

            4.如果data是对象的话,对象必须是纯粹的对象(含有零个或多个的 key/value 对)
            5.data数据如何插入到模板语句当中?
                {{}} 这是Vue框架自己搞的一套语法,别的框架看不懂的,
                浏览器也是不能够识别的。
                vue框架自己是能够看懂的。
                这种语法在vue框架中被称为;模板语法中的插值语法。(有的人把他叫做胡子语法。)
                怎么用?
                    {{data的key}}
                
                插值语法:
                {不能有其他字符包括空格{}不能有其他字符包括空格}
        */
        new Vue({
            template: `<h1>{{  name  }}{{releasename}}开始学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中的数据发生变化,模板语句一定会重新编译。      (只要data变,template就会重新编译,重新渲染)
  • 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: "明天不tm学了!"
            },

            el: '#app'
            // el: document.getElementById('app')
        })
    </script>

</body>

2.5 Vue实例和容器

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

<body>
    <!-- 准备容器 -->
    <div class="app">
        <h1>{{msg}}</h1>
    </div>
    <div class="app">
        <h1>{{msg}}</h1>    // {{msg}}
    </div>

    <div id="app2">
        <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>

3. Vue核心语法

3.1 插值{{ }}语法 

<!-- 
    主要研究:{{这里可以写什么}}
    1. 在data中声明的变量、函数等都可以。
    2. 常量都可以。
    3. 只要是合法的javascript表达式,都可以。
    4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 等。
        'Infinity,undefined,NaN,isFinite,isNaN,' 
        'parseFloat,parseInt,decodeURI,decodeURI***ponent,encodeURI,encodeURI***ponent,' 
        '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',  // 为了方便沟通,以后我们把msg叫做变量。(这行代码就可以看做是变量的声明。)
            sayHello: function () {
                console.log('hello vue!');
            }
        }
    })
</script>
<!-- 准备容器 -->
<div id="app">
    <!-- 在data中声明的 -->
    <!-- 这里就可以看做在使用msg变量。 -->
    <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>
    <!-- 没有加''是变量,所以取得是abcdef,之后字符串拼接 -->
    <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 id="app">
    <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 id="app">
        <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 指令会动态为元素添加或移除 style="display: none;" 样式,从而控制元素的显示与隐藏;

  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 id="app">
    <!-- 注意:以下代码中 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.***">走起</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 id="{{ val }}">, use <div :id="val">
        
        属性内部插值这种语法已经被移除了。(可能Vue在以前的版本中是支持这种写法的,但是现在不允许了。)
        请使用v-bind或冒号速记来代替。
        请使用 <div :id="val"> 来代替 <div id="{{ val }}">

        -->
    <!-- <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.***'
        }
    })
</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 id="app">
    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.***',
            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 id="app">
    <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 id="app">
        <h1>{{msg}}</h1>

        <!-- 阻止事件的默认行为 -->
        <a href="https://www.baidu.***" @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 class="divList" @wheel.passive="testPassive">
            <div class="item">div1</div>
            <div class="item">div2</div>
            <div class="item">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 id="app">
        <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 id="app">
        <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 id="app">
        <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 id="app">
        <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 id="app">
        <!-- 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 id="app">
        <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 id="app">
    <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 id="app2">
    <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 id="app">
        <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 id="app2">
        <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
                }
            },
            ***puted: {
                formatPrice() {
                    if (this.price === '' || this.price === undefined || this.price === null) {
                        return '-'
                    }
                    return this.price
                }
            },
        })
    </script>
</body>

5. 计算属性***puted

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


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

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

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


5.1 反转字符串methods实现

<body>
    <div id="app">
        <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 反转字符串计算属性实现

<!--语法格式:需要一个新的配置项 ***puted
    ***puted : {
        // 这是一个计算属性
        计算属性1 : {
            // setter 和 getter方法。
            // 当读取计算属性1的值的时候,getter方法被自动调用。
            get(){

            },
            // 当修改计算属性1的值的时候,setter方法被自动调用。
            set(val){

            }
        },
    }-->
<div id="app">
    <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'
            }
        },

        ***puted: {
            // 可以定义多个计算属性
            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 id="app">
        <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
                        }
                    }
                }
            },
            ***puted: {
                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 id="app">
        <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 id="app">
        <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 id="app">
        <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 id="app">
    <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 id="app">
        <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 id="app">
        <h1>{{msg}}</h1>

        <!-- 静态写法 -->
        <div class="static small">{{msg}}</div>
        <br><br>

        <button @click="changeBig">变大</button>
        <button @click="changeSmall">变小</button>

        <!-- 动态写法:动静都有 -->
        <!-- 适用场景:如果确定动态绑定的样式个数只有1个,但是名字不确定。 -->
        <div class="static" :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 id="app">
        <h1>{{msg}}</h1>
        <!-- 静态写法 -->
        <div class="static active text-danger">{{msg}}</div>
        <br>

        <!-- 动态写法:动静结合 -->
        <div class="static" :class="['active','text-danger']">{{msg}}</div>
        <br>

        <div class="static" :class="[c1, c2]">{{msg}}</div>
        <br>

        <!-- 适用场景:当样式的个数不确定,并且样式的名字也不确定的时候,可以采用数组形式。 -->
        <div class="static" :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 id="app">
        <h1>{{msg}}</h1>

        <!-- 动态写法:动静结合 -->
        <!-- 对象形式的适用场景:样式的个数是固定的,样式的名字也是固定的,但是需要动态的决定样式用还是不用。 -->
        <div class="static" :class="classObj">{{msg}}</div>
        <br>

        <div class="static" :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 id="app">
        <h1>{{msg}}</h1>

        <!-- 静态写法 -->
        <div class="static" style="background-color: green;">{{msg}}</div>
        <br>

        <!-- 动态写法:字符串形式 -->
        <div class="static" :style="myStyle">{{msg}}</div>
        <br>

        <!-- 动态写法:对象形式 -->
        <!-- 这个属性名需要为驼峰形式 -->
        <div class="static" :style="{backgroundColor: 'gray'}">{{msg}}</div>
        <br>
        <div class="static" :style="styleObj1">{{msg}}</div>
        <br>

        <!-- 动态写法:数组形式 -->
        <div class="static" :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 id="app">
        <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>

列表排序

<body>
    <div id="app">
        <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 }
                ]
            },
            ***puted: {
                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 id="app">
        <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 value="">请选择学历</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.a***ept">阅读并接受协议<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: '',
                    a***ept: ''
                },
                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 id="app">
        <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 = 'jack@126.***'

        // 如果你想给后期追加的属性添加响应式处理的话,调用以下两个方法都可以:
        // Vue.set() 、 vm.$set()
        //Vue.set(目标对象, 属性名, 属性值)
        //Vue.set(vm.$data.a, 'email', 'jack@126.***')
        //Vue.set(vm.a, 'email', 'jack@123.***')
        vm.$set(vm.a, 'email', 'jack@456.***')

        // 避免在运行时向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 id="app">
        <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 id="app">
        <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:实例刚被创建,数据观测和事件配置之前调用.

  • 使用场景:插件初始化、全局事件总线设置

  • 特点:无法访问 datamethods 和 ***puted 等属性

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: 销毁完成');
    }
    转载请说明出处内容投诉
    CSS教程网 » Vue入门到精通:从零开始学Vue

    发表评论

    欢迎 访客 发表评论

    一个令你着迷的主题!

    查看演示 官网购买