Vue(二):事件处理,计算属性、监视属性及二者的区别

一、事件相关内容

1、事件处理

1、使用v-on:xxx或@xxx绑定事件,其中xxx是事件名。

2、事件的回调需要配置在methods对象中,最终会在vm上。

3、methods中配置的函数,不要用箭头函数!否则this就不是vm了。

4、methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象。

5、@click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参。

6、如果不传参,默认第一个形参是事件对象;如果传参,按照参数顺序来接,如果想接事件对象,要传入$event关键字

javascript 复制代码
<body>
    <!-- 准备好一个容器 -->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- <button v-on:click="showInfo">点我提示信息</button> -->
        <button @click="showInfo1">点我提示信息1(不传参)</button>
        <button @click="showInfo2(66,$event)">点我提示信息2(传参)</button>
    </div>
    <script>
        Vue.config.productionTip = false //阻止Vue在启动时生成提示

        const vm = new Vue({
            el:'#root',
            data:{
                name:'椰果'
            },
            methods:{
                showInfo1(event){
                    //当使用箭头函数时,this是在编译前绑定的,指向上一级window
                    //console.log(this);//此处的this是指vm
                    alert('同学你好!')
                },
                showInfo2(number,a){
                    console.log(number,a);
                    //当使用箭头函数时,this是在编译前绑定的,指向上一级window
                    // console.log(event.target.innerText);
                    //console.log(this);//此处的this是指vm
                    alert('同学你好!!')
                }
            }
        })
    </script>
</body>

2、事件修饰符

Vue.js 通过由点 . 表示的指令后缀来调用修饰符。

  • .stop - 阻止冒泡
  • .prevent - 阻止默认事件
  • .capture - 阻止捕获
  • .self - 只监听触发该元素的事件
  • .once - 只触发一次
javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件修饰符</title>
    <script src="../js/vue.js"></script>
    <style>
        *{
            margin-top: 20px;
        }
        .box1{
            padding: 5px;
            background-color: green;
        }
        .box2{
            padding: 5px;
            background-color: red;
        }
        .list{
            height: 200px;
            width: 200px;
            background-color: aqua;
            overflow: auto;
        }
        li{
            height: 100px;
        }
    </style>
</head>
<body>
    <!-- 
        1.prevent:阻止默认事件(常用);
        2.stop:阻止事件冒泡(常用);
        3.once:事件只触发一次(常用);
        4.capture:使用事件的捕获模式;(捕获由外向里,冒泡由里向外)
        5.self:只有event.target是当前操作的元素是才触发事件;
        6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
     -->
    <!-- 准备好一个容器 -->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- 1.prevent: 阻止默认事件(常用) -->
        <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
        <!-- 2.stop:阻止事件冒泡(常用); -->
        <div class="demo1" @click="showInfo">
            <button @click.stop="showInfo">点我提示信息</button>
        </div>
        <!-- 3.once:事件只触发一次(常用); -->
        <button @click.once="showInfo">点我提示信息</button>
        <!-- 4.capture:使用事件的捕获模式 -->
        <div class="box1" @click.capture="showMsg(1)">
            box1
            <div class="box2" @click="showMsg(2)">
                box2
            </div>
        </div>
        <!-- 5.self:只有event.target是当前操作的元素是才触发事件,也可以阻止冒泡? -->
        <div class="demo1" @click.self="showInfo">
            <button @click="showInfo">点我提示信息</button>
        </div>
        <!-- 6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕 -->
        <!-- <ul @scroll="demo" class="list"> 滚动条 -->
        <ul @wheel.passive="demo" class="list"> 
            <!-- 鼠标的滚轮 -->
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
        </ul>
    </div>
    <script>
        new Vue({
            el:"#root",
            data:{
                name:'椰果'
            },
            methods:{
                showInfo(e){
                    alert('同学你好')
                },
                showMsg(msg) {
                    console.log(msg);
                },
                demo() {
                    for (let i = 0; i < 10000; i++) {
                        console.log('#');
                    }
                    console.log('累坏了');
                }
            }
        })
    </script>
</body>
</html>

3、键盘事件

javascript 复制代码
<!-- 准备好一个容器 -->
<div id="root">
    <h2>我的名字叫{{name}}</h2>
    <input type="text" placeholder="按下回车输出" @keyup.enter="showInfo">
    <input type="text" placeholder="按下回车输出" @keyup.13="showInfo">
    <input type="text" placeholder="按下回车输出" @keyup.huiche="showInfo">
    <input type="text" placeholder="按下删除或退格输出" @keyup.delete="showInfo">
    <input type="text" placeholder="按下切换大小写输出" @keyup.caps-lock="showInfo">
</div>

<script>
    Vue.config.keyCodes.huiche = 13; //定义一个别名为huiche的enter按键

    const vm = new Vue({
        el: '#root',
        data: {
            name: 'zzy',
            age: 18
        },
        methods: {
            showInfo(e) {
                // if (e.keyCode !== 13) return  //原生js实现按下回车输出值
                console.log(e.target.value);
            }
        }
    })
</script>

1、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

比如我想按下切换大小写的按键就触发showInfo:

html 复制代码
<input type="text" placeholder="按下切换大小写输出" @keyup.caps-lock="showInfo">

2、系统修饰键(用法特殊):ctrl、alt、shift、meta

  • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
  • 配合keydown使用:正常触发事件。

3、也可以使用keyCode去指定具体的按键(不推荐)

html 复制代码
<input type="text" placeholder="按下回车输出" @keyup.13="showInfo">

4、Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

html 复制代码
Vue.config.keyCodes.huiche = 13; //定义一个别名为huiche的enter按键

4.补充两个细节

1、修饰符可以连续写

<button @click.stop.prevent="showInfo">你好</button>

2、如果先让两个键同时按下才触发,键名也可以连续绑定

比如按下ctrl+y触发showInfo方法

<input type="text" @keyup.ctrl.y="showInfo">

二、计算属性

顾名思义,计算属性就是计算出来的属性,英文名儿computed

这里要和data和methods里的东西区分,data里的属性,methods里的函数,你写的都会原封不动放到vm里,但是computed里面的东西写的是对象,最后放到vm里的是这个对象的名,值是get里的返回值。

1. 使用插值语法和methods拼接姓名

使用插值语法,如果简单点还行,如果复杂了,里面的逻辑写一大堆,很难看

使用methods不错,但是有一个问题,就是每次用到fullName都会调用一次,缓存太多

javascript 复制代码
<!-- 准备好一个容器 -->
<div id="root">
    姓: <input type="text" v-model="firstName"><br><br>
    名: <input type="text" v-model="lastName"><br><br>
    <!-- 拼接姓名实现"张-三"联动 -->
    <!-- 第一种实现:使用插值语法(字符串的slice方法,左闭右开截取字符串) ,麻烦-->
    姓名: <span>{{firstName.slice(0,3)}}-{{lastName}}</span><br><br>


    <!-- 第二种实现:使用methods,每次用到都会调用一次,而计算属性只调用第一次就欧了-->
    姓名: <span>{{fullName()}}</span><br><br>
    姓名: <span>{{fullName()}}</span><br><br>
    姓名: <span>{{fullName()}}</span><br><br>
    姓名: <span>{{fullName()}}</span><br><br>
    姓名: <span>{{fullName()}}</span>
</div>

<script>
    const vm = new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三'
        },
        methods: {
            fullName() {
                console.log('调用')
                return this.firstName + '-' + this.lastName;
            }
        }
    })
</script>

2. 使用计算属性拼接姓名

1、没有缓存问题,第一次调用之后就不会再调用,节省内存

2、计算属性里面的属性要写成对象的形式,每个对象里面都有getter和setter

3、fullName实际上就是firstName和lastName经过一番计算得到的玩意儿,直接到vm身上成为vm的属性

4、当有人读取fullName时,get调用,返回值作为fullName的值

5、set什么时候调用? fullName被修改时调用,这里边呢有个连锁反应,我手动修改vm.fullName导致firstName和lastName被修改,Vue模板重新解析,页面刷新,firstName和lastName的修改又导致get被重新执行(依赖的数据变了),返回新的fullName,计算属性计算属性,就是一直在计算,所以要想改fullName,必须改它依赖的值才行。

html 复制代码
    <!-- 准备好一个容器 -->
    <div id="root">
        姓:<input type="text" v-model="firstnmae"> <br><br>
        名:<input type="text" v-model="lastnmae"> <br><br>
        全名:<span>{{fullname}}</span>
    </div>
    <script>
        const vm = new Vue({
            el: "#root",
            data: {
                firstname:'你',
                lastname:'鹏'
            },
            computed:{
                fullname:{
                    // get有什么作用?当有人读取ful1Name时,get就会被调用,且返回值就作为fullName的值
                    // get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
                    get(){
                        console.log('被调用了');
                        return this.firstname + '-' + this.lastname;
                    },
                //set什么时候调用? fullName被手动修改时调用
                //这里边呢有个连锁反应,我手动修改vm.fullName导致firstName和lastName被修改,Vue模板重新解析
                //页面刷新,firstName和lastName的修改又导致get被重新执行(依赖的数据变了),返回fullName
                //计算属性计算属性,就是一直在计算,所以要想改fullName,必须改它依赖的值
                set(val){
                    let arr = val.split('-');
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                }
                } 
            }
        })
    </script>
</body>

3、计算属性简写

只考虑读取不考虑修改时可使用简写:

javascript 复制代码
    <!-- 准备好一个容器 -->
    <div id="root">
        姓: <input type="text" v-model="firstName"><br><br>
        名: <input type="text" v-model="lastName"><br><br>
        <!-- 拼接姓名实现"张-三"联动 -->
    
        <!-- 使用计算属性,只调用一次,非常奈斯,节省内存 -->
        姓名: <span>{{fullName}}</span>
    </div>
    
    <script>
        const vm = new Vue({
            el: '#root',
            data: {
                firstName: '张',
                lastName: '三'
            },
            computed: {
                fullName() {
                    console.log('fullName被读了');
                    return this.firstName + '-' + this.lastName;
                }

            }
        })
    </script>

这里一定要注意,这个fullName是一个属性,不是一个方法,只不过是调用里面的getter,把返回值直接给了vm罢了,所以直接{{fullName}}就可以搞出来,不用加小括号调用!

4、计算属性总结

  1. 计算属性总结

定义:要用的属性不存在,要通过Vue中已有属性计算得来。

原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

get函数什么时候执行?

(1).初次读取时会执行一次。

(2).当依赖的数据发生改变时会被再次调用。

优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

备注:

1.计算属性最终会出现在vm上,直接读取使用即可,不用加括号,和methods区别一下子。

2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变,这很关键。

三、监视属性

1、天气案例

首先用计算属性写一下天气切换案例

javascript 复制代码
    <!-- 准备好一个容器 -->
    <div id="root">
        <h2>今天天气很{{info}},{{x}}</h2>
        <button @click="changeweather">切换天气</button>
    </div>
    <script>
        new Vue({
            el:'#root',
            data:{
                ishot:true,
                x:1
            },
            computed:{
                info(){
                    return this.ishot ? '炎热' : '寒冷'
                }
            },
            methods: {
                changeweather(){
                    this.ishot = !this.ishot
                    this.x++
                }
            }
        })
    </script>

2、监视属性

(1).new Vue时传入watch配置

javascript 复制代码
<div id="root">
    <!-- 实现点击按钮切换天气 -->
    <!-- 写法1:利用插值语法和三元表达式 -->
    <h2>今天天气很{{isHot ? '炎热': '寒冷'}}</h2>

    <!-- 写法2:利用计算属性 -->
    <h2>今天天气很{{info}}</h2>
    <!--    绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句,但是最好别这么干-->
    <!--    <button @click="isHot = !isHot">切换天气</button>-->
    <button @click='change'>点击切换天气</button>
</div>
<script>
    new Vue({
        el: '#root',
        data: {
            isHot: true
        },
        methods: {
            change() {
                this.isHot = !this.isHot;
            }
        },
        computed: {
            info() {
                //注意这里的isHot要加this
                return this.isHot ? '炎热' : '寒冷';
            }
        },
        watch: {
            isHot: {
                immediate: true,   //初始化时先调用一次handler 
                //当isHot被修改时调用handler函数
                handler(newVal, oldVal) {
                    console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
                }
            },
            //watch不只可以监视data中的属性,还可以监视计算属性
            info: {
                immediate: true,   //初始化时先调用一次handler 
                //当isHot被修改时调用handler函数
                handler(newVal, oldVal) {
                    console.log('info被修改了', '改之前是' + oldVal, '改之后是' + newVal);
                }
            }
        }
    })  
</script>

(2)通过vm.$watch监视

javascript 复制代码
const vm = new Vue({
    el: '#root',
    data: {
        isHot: true
    },
    methods: {
        change() {
            this.isHot = !this.isHot;
        }
    },
    computed: {
        info() {
            //注意这里的isHot要加this
            return this.isHot ? '炎热' : '寒冷';
        }
    }
})

	vm.$watch('isHot', {
	    immediate: true,   //初始化时先调用一次handler 
	    //当isHot被修改时调用handler函数
	    handler(newVal, oldVal) {
	        console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
	    }
	})
	
	vm.$watch('info', {
	    immediate: true,   //初始化时先调用一次handler 
	    //当isHot被修改时调用handler函数
	    handler(newVal, oldVal) {
	        console.log('info被修改了', '改之前是' + oldVal, '改之后是' + newVal);
	    }
	})

3、深度监视

(1).Vue中的watch默认不监测对象内部值的改变(一层,如果监视那么对象地址不变就不变)。

(2).配置deep:true可以监测对象内部值改变(多层)。

备注:

(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!想要可以就要手动开启deep:true

(2).使用watch时根据数据的具体结构,决定是否采用深度监视

javascript 复制代码
   <!-- 准备一个容器 -->
    <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>
        <h3>我tm就非要强制让number变</h3>
        <button @click="numbers = {a:666, b:999}">点我改变numbers</button>
    </div>
    <script>
        const vm = new Vue({
            el: '#root',
            data: {
                isHot: true,
                numbers: {
                    a: 1,
                    b: 2
                }
            },
            watch: {
                //监视多级结构中某个属性的变化,使用引号包起来(之前不加引号都是简写形式)
                'numbers.a': {
                    handler() {
                        console.log('a被改变了');
                    }
                },
                //如果下面这么写,即使ab变了也不会执行handler,因为这么写意思是监视numbers这个属性
                //而这个属性值是一个对象,只要numbers对象的地址不变就不变,除非像上面div写的暴力方法
                 numbers: {
                     handler() {
                         console.log('numbers改变了');
                     }
                 },

                //但是如果加个deep,就可以监视多级结构中某个属性的变化,ab变了numbers也变
                numbers: {
                    deep: true,
                    handler() {
                        console.log('numbers改变了');
                    }
                },
            }
        })
    </script>

4、监视属性简写

形式1:

javascript 复制代码
	watch: {
                //复杂写法:
                 isHot: {
                    //immediate: true,   //初始化时先调用一次handler 
                    //当isHot被修改时调用handler函数
                    handler(newVal, oldVal) {
                        console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
                     }
                },
                
				//简写
                // 如果handler函数中没有其他的配置项(deep,immediate等),可以简写成以下形式
                isHot(newVal, oldVal) {
                    console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
                }
            }

写法2:

javascript 复制代码
	//复杂形式
	vm.$watch('isHot', {
	    //当isHot被修改时调用handler函数
	    handler(newVal, oldVal) {
	        console.log('isHot被修改了', '改之前是' + oldVal, '改之后是' + newVal);
	    }
	})
	//简写形式
	vm.$watch('isHot',function (newVaule,oldVaule) {
	        console.log(newVaule,oldVaule);
	    })

四、computed对比watch

1.对比拼接姓名案例:

html 复制代码
<!-- 准备好一个容器 -->
<div id="root">
    姓: <input type="text" v-model="firstName"><br><br>
    名: <input type="text" v-model="lastName"><br><br>
    <!-- 拼接姓名实现"张-三"联动 -->
    姓名: <span>{{fullName}}</span>
</div>

计算属性:

javascript 复制代码
const vm = new Vue({
    el: '#root',
    data: {
        firstName: '张',
        lastName: '三'
    },
    computed: {
        fullName() {
            console.log('fullName被读了');
            return this.firstName + '-' + this.lastName;
        }

    }
})

监视属性:

javascript 复制代码
   const vm = new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三',
            fullName: '张-三'
        },
        watch: {
            firstName: {
                handler(newVal, oldVal) {
                    this.fullName = newVal + '-' + this.lastName;
                }
            },
            lastName: {
                handler(newVal, oldVal) {
                    this.fullName = this.firstName + '-' + newVal;
                }
            },
        }
    })

这么看起来好像计算属性更简单,那么计算属性就真的牛逼吗?我觉得watch更牛逼,因为它可以异步。

比如我如果手动改了firstName要等一秒钟再改fullName呢?

用监视属性,我们就可以这么写:

javascript 复制代码
watch: {
    firstName: {
        handler(newVal, oldVal) {
            setTimeout(() => {
                this.fullName = newVal + '-' + this.lastName;
            }, 1000);
        }
    }
}

但是computed就没辙了,因为等一秒再return?

2. 总结:

1、computed能完成的功能,watch都可以完成。

2、watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。

两个重要的小原则!

1、所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。

2、所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

(ps:这一部分老师讲的很细,但是又有点绕,后面还需要再多加练习)

相关推荐
桂月二二24 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
CodeClimb1 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
沈梦研1 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
轻口味2 小时前
Vue.js 组件之间的通信模式
vue.js
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter