算术表达式解析系列之逆波兰表示法

假如我们有这样的一个需求,接受一个用户录入的算术表达式,解析并计算出结果。

其中,这个表达式中,可以存在:

  1. 数,表示参与计算的对象
  2. 运算符(可以是自定义的运算符),表示不同的计算
  3. 括号,表示对计算顺序的干预

那可以怎么做?

本系列文章,将提供几种不同的解决方案。本篇将介绍一种常见的方案:“逆波兰表示法”。

首先我们先了解一些概念。

中缀表示法

中缀表示法,应该是我们最常接触的算术表示法了。该表示法,操作符位于两个操作数中间,因此而得名。

【例 1】:

1
(10 + 15 - 20) * 30 / 40 ^ 2

这就是一个使用中缀表示法的例子。

在中缀表示法中,括号可以改变一个表达式的计算顺序。

(1 - 1) - 11 - (1 - 1) 是完全不同的。所以, 1 - 1 - 1 这样的表达式,对于计算机来说,是存在 解析的歧义的,处理起来相对麻烦。

逆波兰表示法

READ MORE...

如何写一个原生的 Web Component 组件

Web Component

写一个 Web Component 很简单,只需要书写一个继承 HTMLElementclass 即可,比如我们打算创建一个自定义的按钮元素:

1
class MyButton extends HTMLElement {}

然后为了可以在 HTML 中使用,以及可以使用 document.createElement 来创建元素,我们还需要注册下这个组件,分配一个元素名称。

1
2
3
if (!customElements.get('my-button')) {
  customElements.define('my-button', MyButton)
}

然后试试效果:

1
2
3
4
5
6
7
8
<my-button>直接在 HTML 中使用</my-button>

<script>
// 或者使用 JavaScript 创建并插入
const $p = document.createElement('my-button')
$p.innerHTML = '使用 JavaScript 创建并插入'
document.body.appendChild($p)
</script>

页面上如愿显示出内容:


Shadow DOM

如果只是一个长得不像按钮,光秃秃的自定义元素,那根本就派不上用场。 按钮的功能先不论,首先组件内部总该可以封装自己的内容和样式,这是作为一个组件的最基本的需求之一。 于是,这个时候 Shadow DOM 就可以上场了。

让我们改造下代码,使用 Shadow DOM 为我们的自定义 Button 加上内部 DOM 结构:

READ MORE...

鼠标移出窗口外释放的事件触发现象

mouseup 之类的事件,鼠标移出窗口(或 iframe)时,释放点击,不会触发,除非事件绑定在 documentElement、document、window 上面。

例如:

1
2
3
4
5
6
7
8
window.addEventListener('mouseup', () => { console.log('on mouse up(window, capture)') }, true)
document.addEventListener('mouseup', () => { console.log('on mouse up(doc, capture)') }, true)
document.documentElement.addEventListener('mouseup', () => { console.log('on mouse up(html, capture)') }, true)
document.body.addEventListener('mouseup', () => { console.log('on mouse up(body, capture)') }, true)
window.addEventListener('mouseup', () => { console.log('on mouse up(window)') }, false)
document.addEventListener('mouseup', () => { console.log('on mouse up(doc)') }, false)
document.documentElement.addEventListener('mouseup', () => { console.log('on mouse up(html)') }, false)
document.body.addEventListener('mouseup', () => { console.log('on mouse up(body)') }, false)
  1. 当鼠标在页面中点击,然后直接释放点击时,控制台输出
1
2
3
4
5
6
7
on mouse up(window, capture)
on mouse up(doc, capture)
on mouse up(html, capture)
on mouse up(body, capture)
on mouse up(body)
on mouse up(html)
on mouse up(doc)
  1. 当鼠标在页面中点击,然后移动到窗口外释放点击时,控制台输出
1
2
3
4
5
on mouse up(window, capture)
on mouse up(doc, capture)
on mouse up(html, capture)
on mouse up(html)
on mouse up(doc)

该行为可能对自己实现一些 drag & drop 逻辑时有一定的影响。

READ MORE...

使用 Vue 实现一个虚拟列表

大列表问题

因为 DOM 性能瓶颈,大型列表存在难以克服的性能问题。 因此,就有了 “局部渲染” 的优化方案,这就是虚拟列表的核心思想。

虚拟列表的实现,需要重点关注的问题一有以下几点:

  1. 可视区域的计算方法
  2. 可视区域的 DOM 更新方案
  3. 事件的处理方案

下面逐一分解说明。

可视区域计算

可视区域的计算,就是使用当前视口的高度、当前滚动条滚过的距离,得到一个可视区域的坐标区间。 算出可视区域的坐标区间之后,在去过滤出落在该区间内的列表项,这个过程,列表项的坐标也是必须能算出的。

思考以下情况,

  1. 我们的视口高度为 100px
  2. 我们当前已经滚动了 100px
  3. 我们的列表项,每一项高度为 20px

根据这些条件,我们可以计算出,当前可视区域为第 11 项至第 20 项。

1
2
3
4
5
6
7
8
9
10
11
12
13
  01 - 05,可视区域上方
+----+-----------+--------
| 06 | 100 ~ 120 |
+----+-----------+
| 07 | 120 ~ 140 |
+----+-----------+
| 08 | 140 ~ 160 | 可视区域
+----+-----------+
| 09 | 160 ~ 180 |
+----+-----------+
| 10 | 180 ~ 200 |
+----+-----------+--------
  11 - N,可视区域下方

这是因为列表项高度是固定的,我们可以通过简单的四则运算算出已经滚动过去的 100px 中,已经滚动走了 5 个列表项,因此可视区域是从第 6 项开始,而视口高度为 100px,能容纳 100 / 20 即 5 个条目。

上面例子的情况非常简单,也不存在性能问题,因此实际上并没有展开讨论的价值。 而还有另一种复杂很多的情况,那就是,列表项高度不固定,根据内容决定高度。

此时,我们就没办法直接使用四则运算一步到位算出可视区域对应的条目了。

而必须实现一种机制,记录所有列表项的坐标,再通过检查列表项是否落在视口内。

下面重点讨论该问题。

READ MORE...

JavaScript 中的 “相等” 算法

整理笔记本时,看到以往一些对 JavaScript 中 “相等” 关系的记录。 尽管这是一个被说烂了的话题,但写一篇资料整理总结,当作复习下也不错。

JavaScript 的 “相等” 比较算法,按严格程度来排,有以下几种:

  1. == 双等号
  2. === 三等号
  3. SameValueZero 算法
  4. SameValue 算法

下面逐个介绍它们的特点。

READ MORE...