今天从一本vue.js书中学习了《汇率计算器》的案例,这个案例的效果如下:
案例可以查询人民币、日元、港元、美元、欧元之间的汇率关系,代码中定义了一个汇率表rate,包含了每种货币对其他5种货币的汇率。其中还有一个功能是点击下方四种货币的任意一种货币,可以和最上面的一种货币实现互换。例如点击了欧元,欧元会到最上面,和人民币位置互换。
这是代码的html部分,下面4种货币使用v-for循环生成列表项<li>。
html
<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) 方法来实现的,如下:
javascript
methods: {
changeCur(event){
const c = event.currentTarget.dataset.currency; //获取点击的项
const f = this.from.currency; //获取from项
this.from.currency = c; //点击项赋值给from
this.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加了不同的背景色。
测试案例完整代码如下。
html
<!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)输出
html
<div id="inner"></div>
console.log(e.currentTarget)输出
html
<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>的结构,总算真相大白。
html
<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。
下面是实现汇率计算器的完整代码。
html
<!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; //点击项赋值给from
this.toList.find(arritem => arritem.currency === c).currency = f; //from项赋值给点击项
},
exchange(from, amount, to){
return (amount * rate[from][to]).toFixed(2)
},
},
//计算兑换后的金额,例如 美元amount=exchange(人民币,1000,美元)
watch:{ //监听from
from: {
handler(value){
this.toList.forEach(item => {
item.amount = this.exchange(this.from.currency,
this.from.amount, item.currency)});
},
deep:true, //监听from对象里的currency、amount,deep需设置为true
immediate:true //页面一打开的时候,就执行一次
}
}
}).mount('#app')
</script>
</body>
</html>