今天从一本vue.js书中学习了《汇率计算器》的案例,这个案例的效果如下:
案例可以查询人民币、日元、港元、美元、欧元之间的汇率关系,代码中定义了一个汇率表rate,包含了每种货币对其他5种货币的汇率。其中还有一个功能是点击下方四种货币的任意一种货币,可以和最上面的一种货币实现互换。例如点击了欧元,欧元会到最上面,和人民币位置互换。
这是代码的html部分,下面4种货币使用v-for循环生成列表项<li>。
<div id="app"><h2 class="title">汇率计算器</h2> <ul><li><span>{{from.currency}}</span><input v-model="from.amount"></input></li><li v-for="item in toList":data-currency="item.currency"@click="changeCur"><span>{{item.currency}}</span><span>{{item.amount}}</span></li> </ul> <p class="intro">鼠标点击可以切换货币种类</p></div>
两种货币位置互换的功能,是在methods中定义了一个 changeCur(event) 方法来实现的,如下:
methods: {changeCur(event){const c = event.currentTarget.dataset.currency; //获取点击的项const f = this.from.currency; //获取from项this.from.currency = c; //点击项赋值给fromthis.toList.find(arritem => arritem.currency === c).currency = f; //from项赋值给点击项},exchange(from, amount, to){return (amount * rate[from][to]).toFixed(2)},},
给我造成困扰的是event.currentTarget,我以前在javascript中学习过e.target和this的区别,e.target指触发事件的元素,this指绑定事件的元素,那这里出现的currentTarget又是怎么回事?
资料上说,target指的是触发事件的元素,currentTarget是指监听事件的元素。为了理解这句话的含义,我还写了一段代码来验证。设置了内外两个div,为了便于区分,给内外两个div加了不同的背景色。
测试案例完整代码如下。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>target与currentTarget</title><style>#outer {width: 200px;height: 200px;background-color: #339f63;}#inner {width: 120px;height: 120px;background-color: #23c6d9;}</style>
</head>
<body><div id="outer"><div id="inner"></div></div>
</body>
<script>var a = document.getElementById('outer')var b = document.getElementById('inner')function handler(e) {console.log(e.target); //触发事件的元素console.log(e.currentTarget); //监听事件的元素}a.addEventListener('click', handler);
</script>
</html>
点击内部蓝色区域,e.target和e.currentTarget分别输出如下。
console.log(e.target)输出
<div id="inner"></div>
console.log(e.currentTarget)输出
<div id="outer"><div id="inner"></div>
</div>
很明显,这个例子中监听是加在#outer上,而点击的是#inner,由此也直观地看到了e.target和e.currentTarget两者的区别,验证了e.currentTarget是监听事件的元素,e.target是触发事件的元素。
只是,这个汇率计算器困扰我的地方是,根据我最初的理解,在这个例子中,event.target指的是<li>元素,event.currentTarget也是<li>元素,两者应该是一致的,所以我把event.currentTarget换成了event.target。结果出乎意料,点击下方某种货币的时候,有时候正常,能够互换,有时候不正常,报错。我不明白造成这种现象的原因是什么,理论上说,要么对,要么不对,结果一会儿对,一会儿不对,这是什么状况?被这个问题困扰挺长时间。
后来仔细研究了一下<li>的结构,总算真相大白。
<li v-for="item in toList":data-currency="item.currency"@click="changeCur"><span>{{item.currency}}</span><span>{{item.amount}}</span></li>
<li>标记中有两个<span>标记,类似测试案例中的两个inner,我改成event.target后,在点击的时候,比较具有随意性,有时点击在<li>的空白处,有时点击在文字上,导致event.target有时候是<li>,有时候是<span>,所以就有时候正常,有时候不正常,总算想明白原因了。
总之,一番折腾后,发现这段代码中,必须使用event.currentTarget。
下面是实现汇率计算器的完整代码。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>汇率计算器</title><script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- 人民币:CNY 港元:HKD 美元:USD 欧元:EUR 日元:JPY--><style>h2.title {text-align: center;font-size:18px;margin: 30px 0 10px 0;}p.intro {text-align: center;font-size:14px;}ul {margin:0 auto;width: 200px;list-style-type: none;border:2px solid #999;border-radius: 10px;padding: 0;font-size: 16px;font-weight: bold;font-family: 'Courier New', Courier, monospace;}li {padding:10px;}li:first-child {display: flex;border-bottom: 2px solid #999;}li:not(:first-child):hover {background-color: #ddd;}span:last-child {float:right;}input {text-align: right;border: none;font-size: 16px;width:100px;margin-left:auto;font-family: 'Courier New', Courier, monospace;outline:none;border-bottom: 1px solid #000;}</style>
</head>
<body><div id="app"><h2 class="title">汇率计算器</h2> <ul><li><span>{{from.currency}}</span><input v-model="from.amount"></input></li><!--data-currency 自定义属性,绑定货币名称,便于后期交换使用 --><li v-for="item in toList":data-currency="item.currency"@click="changeCur"><span>{{item.currency}}</span><span>{{item.amount}}</span></li> </ul> <p class="intro">鼠标点击可以切换货币种类</p></div><script>//汇率表let rate={'人民币':{'人民币':1 , '日元':16.876, '港元':1.1870, '美元':0.1526, '欧元':0.1294 },'日元':{'人民币':0.0595, '日元':1 , '港元':0.0702, '美元':0.0090, '欧元':0.0077 },'港元':{'人民币':0.8463, '日元':14.226, '港元':1 , '美元':0.1286, '欧元':0.10952},'美元':{'人民币':6.5813, '日元':110.62, '港元':7.7759, '美元':1 , '欧元':0.85164},'欧元':{'人民币':7.7278, '日元':129.89, '港元':9.1304, '美元':1.1742, '欧元':1 },}const app = Vue.createApp ({data() {return {from: {currency:'人民币', amount:100},toList:[{currency:'日元', amount:0}, {currency:'港元', amount:0}, {currency:'美元', amount:0}, {currency:'欧元', amount:0}]}},methods: {changeCur(event){const c = event.currentTarget.dataset.currency; //获取点击的项的货币名称console.log(event.currentTarget);console.log(event.target);const f = this.from.currency; //获取from项this.from.currency = c; //点击项赋值给fromthis.toList.find(arritem => arritem.currency === c).currency = f; //from项赋值给点击项},exchange(from, amount, to){return (amount * rate[from][to]).toFixed(2)},},//计算兑换后的金额,例如 美元amount=exchange(人民币,1000,美元)watch:{ //监听fromfrom: {handler(value){this.toList.forEach(item => {item.amount = this.exchange(this.from.currency, this.from.amount, item.currency)}); },deep:true, //监听from对象里的currency、amount,deep需设置为trueimmediate:true //页面一打开的时候,就执行一次}}}).mount('#app')</script>
</body>
</html>