话不多说,码上开始。
建立项目
- 首先下载 安装 Node.js 环境,它会自动安装 npm 包管理工具。在使用之前我们修改一下 npm 包的下载路径,不然放在 C 盘怎么得了?在命令行中输入
npm config ls
可以看到 prefix 的值就是下载路径,输入npm config set prefix "你的路径"
就可以修改。 - 安装 vue-cli 脚手架工具,
npm install -g vue-cli
,参数 -g 表示全局安装(路径在之前设置的 prefix 中),否则就是本地安装(路径在当前目录)。 - 建立项目,在某目录下,输入
vue init webpack-simple 项目名称
,参数 webpack-simple 是一个项目模板;按提示输入相关信息后,将自动新建项目文件夹,输入cd 文件夹名称
进入目录,输入npm install
,在当前项目路径下载所需的包;输入npm run dev
就可运行了。 dev 是 package.json里面定义的脚本命令,作用是启动服务器。
打开 Vue.app 文件,我们就可以在里面愉快的写组件了。
这里不再介绍 Vue 的基本知识,若要学习请移步 Vue2.x 教程。
需求分析
其实文章开头说的 话不多说,码上开始 是一种十分错误的做法。我就是在想当然之后——按照脑海中最初浮现的逻辑——马上开始写界面,吭哧吭哧地开始写事件处理程序。随后,在测试中痛苦地发现,功能太简陋,简单的单次计算并不能满足我们印象中对于计算器的期待。
好吧,推倒重来,如果上天再给我一次机会,我会好好地做需求分析……
首先,我们打开一个计算器看看它有什么功能:
也就是说,计算器应该支持单次运算、连续计算、继续计算,遵循运算符的优先级顺序,并具有一定的容错机制。
几经修改,最后整理流程图如下:
组件结构
- 首先,为了进行四则运算,我们需要一个操作符栈
oprStack
和一个数值栈numStack
- 为了显示算式的功能,我们需要一个数组
cacheArr
顺序记录输入和操作符和数值。 - 由于显示部分应该绑定一个字符串变量
cacheStr
,此变量又于上述数组相关,那么为了保持数据结构的简单,对于这个字符串变量,我们使用计算属性。 - 一些缓存变量,
inputData
preType
- 一些标志变量,
calculated
inputShow
errorOccurred
那么自然而然地,计算器组件的结构设计如下:
<template>
<div id="calculator">
...
<div id="panel">
<hr>
<div>{{ cacheStr }}</div>
<hr>
<div :class="{'text-danger' : errorOccurred}">{{ inputData }}</div>
<hr>
</div>
<div id="board" @click="changePanel($event);inputHandler($event)">
<table>
...
</table>
</div>
</div>
</template>
<script>
export default{
data () {
return{
cacheArr:[], //显示算式
inputData:'', //缓存输入数值
preType:'num', //上一个输入类型
oprStack:[], //操作符栈
numStack:[], //操作数栈
calculated:0, //连续计算和重新计算等逻辑需要的标志
inputShow: 0, //inputData 有时作为输入有时作为输出,需要一个标志
errorOccurred: 0 //错误标志
}
},
computed:{
cacheStr() {
...
return this.cacheArr.join("");
}
},
methods: {
/** 初始化 */
init(){
...
},
/** 显示处理 */
changePanel(e){
...
},
/** 输入处理 */
inputHandler (e) {
...
//重新计算判断:1. 上次计算后输入 num 或 opr0; 2. 上次计算结果是 NaN;
if(...){
this.init();
}
//继续计算判断:1. 上次计算后输入 opr1 或 opr2
if(...){
//主要处理显示的部分
...
}
// inputData 从显示模式调整为输入模式
...
//处理数字-----------------------------------------
if(type === 'num'){
...
}
//处理管理级操作符---------------------------------
if(type==='opr0'){
...
}
//处理其他操作符----------------------------------
if(type==='opr1' || type==='opr2'){
//查看寄存器是否有数据
...
//容错机制
//1. 如果输入二元操作符-等号,则删掉二元操作符
//2. 如果连续输入两次二元操作符,则删掉前一个
...
//处理
if(value==='equals')
{
this.oprStack.push({type:type, value:value, level:level});
this.preCalculate();
this.calculated = 1;
}
else{
this.oprStack.push({type:type, value:value, level:level});
this.calculated = 0;
}
}
//保存等号前的最后一个输入类型--------------------
...
},
/** 计算预处理 */
preCalculate () {
//计算
this.calculate(this.oprStack, this.numStack);
//显示结果数据
...
},
/** 计算过程 */
calculate(oprStack, numStack){
//对操作符栈和数值栈进行计算,考虑四则运算的优先级
...
},
/** 具体计算函数 */
...
}
}
</script>
<style>
...
</style>
大体思路就是这样,一个计算器应该保持简单、直观的使用方式,以免造成用户的困惑。
比如说,自带的计算器里面,上一次计算结果既在显示屏上,也在数值栈中,当用户进行“继续计算”时,输入一个二元运算符,紧接着输入一个等号,计算器会把当前显示屏的数值当做第二个操作数,但这可能不是用户想要的,因为他并没有输入第二个操作数。
在 po主的计算器中,把最后一个输入是二元运算符这种情况当作误操作,自动去掉,以免造成混淆。
##踩坑小记 这是一点题外话,主要是记录一下自己犯的错误,大佬们请忽略~ 1. Unicode 字符值的使用。可以到 [Unicode 官网](http://www.unicode.org/charts/) 上查询字符值(16进制),比如说 ÷ 号:00f7。
在 JavaScript 中使用:`\u00f7`,在 CSS 中使用:`\00f7`,在 html 中使用:`&井号247;`。注意 html 中是10进制噢!这里 po主为了避免 markdown 语法自动转换成除号把 ‘#’ 改成了 ‘井号’,各位童鞋自行转换哈… 2. Vue 文件的 debug 方法。我们知道,使用了 webpack 的项目中,脚本最后要经过编译、压缩、打包,在浏览器的调试工具中是无法调试的。
一开始我采用的方法是,在源代码要断点的地方输入 `debugger`,在运行时就可以进入调试模式了。
后来经老大介绍,有一个叫 Vue DevTools 的工具,各位童鞋可以网上自取。 3. 跟常识相悖的是:空对象 `{}` 竟然是真值。所以判断空对象时 `if(!nullObj){...}` 并不会得到你想要的结果。可以使用 jQuery 中的判断空对象方法: ```javascript function isEmptyObject(e) { var t; for (t in e) return !1; return !0 } ``` 4. 由于存在 NaN 这个神奇的毒瘤,以及 Infinity 这样的值,所以判断一个变量是否数值的方法: ```javascript function isNumber(value){ return typeof value === 'number' && isFinite(value); } ``` 5. 在 Vue 中,可以在你的方法中访问计算属性,但不要试图去更改它,因为没用。在下次使用该计算属性时,它依然会调用自身的方法来计算。