义
树是一种数据结构,由一组表示分层树结构的链接节点组成。每个节点都通过父子关系链接到其他节点。树中的第一个节点是根,而没有任何子节点的节点是叶子。
JavaScript 树可视化
树数据结构中的每个节点都必须具有以下属性:
key: 节点的键
value: 节点的值
parent: 节点的父节点(null如果没有)
children: 指向节点子节点的指针数组
树形数据结构的主要操作有:
insert: 插入一个节点作为给定父节点的子节点
remove: 从树中删除一个节点及其子节点
find: 检索给定节点
preOrderTraversal: 通过递归遍历每个节点及其子节点来遍历树
postOrderTraversal: 通过递归遍历每个节点的子节点来遍历树
class TreeNode {
constructor(key, value=key, parent=null) {
this.key=key;
this.value=value;
this.parent=parent;
this.children=[];
}
get isLeaf() {
return this.children.length===0;
}
get hasChildren() {
return !this.isLeaf;
}
}
class Tree {
constructor(key, value=key) {
this.root=new TreeNode(key, value);
}
*preOrderTraversal(node=this.root) {
yield node;
if (node.children.length) {
for (let child of node.children) {
yield* this.preOrderTraversal(child);
}
}
}
*postOrderTraversal(node=this.root) {
if (node.children.length) {
for (let child of node.children) {
yield* this.postOrderTraversal(child);
}
}
yield node;
}
insert(parentNodeKey, key, value=key) {
for (let node of this.preOrderTraversal()) {
if (node.key===parentNodeKey) {
node.children.push(new TreeNode(key, value, node));
return true;
}
}
return false;
}
remove(key) {
for (let node of this.preOrderTraversal()) {
const filtered=node.children.filter(c=> c.key !==key);
if (filtered.length !==node.children.length) {
node.children=filtered;
return true;
}
}
return false;
}
find(key) {
for (let node of this.preOrderTraversal()) {
if (node.key===key) return node;
}
return undefined;
}
}
用class创建一个类TreeNode,使用构造函数初始化 key, value, parent 和 children。
定义一个isLeaf getter方法,用Array.prototype.length检查children是否为空。
定义一个hasChildren getter方法,用来检查是否有子节点。
用 class 创建一个Tree,使用构造函数初始化树的根节点。
定义一个preOrderTraversal() 方法,按顺序遍历树的生成器方法,使用 yield* 语法将遍历委托给自身。
定义一个postOrderTraversal()方法,以后序遍历树的生成器方法,使用yield*语法将遍历委托给自身。
定义一个insert()方法,使用preOrderTraversal()和Array.prototype.push()方法向树中添加一个TreeNode 节点。
定义一个remove()方法,使用preOrderTraversal()和Array.prototype.filter()方法从树删除一个TreeNode节点。
定义一个find()方法,使用preOrderTraversal()方法检索树中的给定节点。
const tree=new Tree(1, 'AB');
tree.insert(1, 11, 'AC');
tree.insert(1, 12, 'BC');
tree.insert(12, 121, 'BG');
[...tree.preOrderTraversal()].map(x=> x.value);
// ['AB', 'AC', 'BC', 'BCG']
tree.root.value; // 'AB'
tree.root.hasChildren; // true
tree.find(12).isLeaf; // false
tree.find(121).isLeaf; // true
tree.find(121).parent.value; // 'BC'
tree.remove(12);
[...tree.postOrderTraversal()].map(x=> x.value);
// ['AC', 'AB']
eb浏览器创建Document对象,并且开始解析Web页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中.在这个阶段document.readystate属性的值是”loading”.
当HTML解析器遇到没有async和defer属性的<script>元素时,它把这些元素添加到文档中,然后执行行内或外部脚本.这些脚本会同步执行,并且在脚本下载(如果需要)和执行时解析器会暂停.这样脚本就可以用document.write()来把文本插入到输入流中.解析器恢复时这些文本会成为文档的一部分.同步脚本经常简单定义函数和注册后面使用的注册事件处理程序,但它们可以遍历和操作文档树,因为在它们执行时已经存在了.这样,同步脚本可以看到它自己的<script>元素和它们之前的文档内容.
当解析器遇到设置了async属性的<script>元素时,它开始下载脚本文本,并继续解析文档.脚本会在它下载完成后尽快执行,但是解析器没有停下来等它下载.异步脚本禁止使用document.write()方法.它们可以看到自己的<script>元素和它之前的所有文档元素,并且可能或干脆不可能访问其他的文档内容.
当文档完成解析,document.readyState属性变成“interactive”.
所有有defer属性的脚本,会按它们在文档的里的出现顺序执行.异步脚本可能也会在这个时间执行.延迟脚本能访问完整的文档树,禁止使用document.write()方法.
浏览器在Document对象上触发DOMContentLoaded事件.这标志着程序执行从同步脚本执行阶段转换到了异步事件驱动阶段.但要注意,这时可能还有异步脚本没有执行完成.
这时,文档已经完全解析完成,但是浏览器可能还在等待其他内容载入,如图片.当所有这些内容完成载入时,并且所有异步脚本完成载入和执行,document.readyState属性改变为“complete”,Web浏览器触发Window对象上的load事件.
从此刻起,会调用异步事件,以异步响应用户输入事件、网络事件、计时器过期等.
David Flanagan. JavaScript权威指南
JavaScript时间线
原文:https://os-note.com/articles/client-side-javascript-timeline.html
产品说要让前端用 JavaScript 画一棵树出来,但是这难道不能直接让 UI 给一张图片吗?
后来一问才知道,产品要的是一颗 随机树,也就是树的茂盛程度、长度、枝干粗细都是随机的,那这确实没办法叫 UI 给图,毕竟 UI 不可能给我 10000 张树的图片吧?
所以第一时间想到的就是 Canvas,用它来画这棵随机树(文末有完整代码)
接下来使用 Canvas 去画这棵随机树
基础页面
我们需要在页面上写一个 canvas 标签,并设置好宽高,同时需要获取它的 Dom 节点、绘制上下文,以便后续的绘制
坐标调整
默认的 Canvas 坐标系是这样的
但是我们现在需要从中间去向上去画一棵树,所以坐标得调整成这样:
这些操作可以使用 Canvas 的方法
绘制一棵树的要素
绘制一棵树的要素是什么呢?其实就是树枝和果实,但是其实树枝才是第一要素,那么树枝又有哪些要素呢?无非就这几个点
开始绘制
所以我们可以写一个 drawBranch 来进行绘制,并且初始调用肯定是绘制树干,树干的参数如下:
这个终点应该怎么算呢?其实很简单,根据树枝长度、生长角度就可以算出来了,这是初高中的知识
于是我们可以使用 Canvas 的绘制方法,去绘制线段,其实树枝就是一个一个的线段
到现在我绘制出了一个树干 出来
但是我们是想让这棵树开枝散叶,所以需要继续递归继续去绘制更多的树枝出来~
递归绘制
其实往哪开枝散叶呢?无非就是往左或者往右
所以需要递归画左边和右边的树枝,并且子树枝肯定要比父树枝更短、比父树枝更细,比如我们可以定义一个比例
而子树枝的生长角度,其实可以随机,我们可以在 0° - 30° 之间随机选一个角度,于是增加了递归调用的代码
但是这个时候会发现,报错了,爆栈了,因为我们只递归开始,但却没有在某个时刻递归停止
我们可以自己定义一个停止规则(规则可以自己定义,这会决定你这棵树的茂盛程度):
现在可以看到我们已经大致绘制出一棵树了
不过还少了树的果实
绘制果实
绘制果实很简单,只需要在绘制树枝结束的时候,去把果实绘制出来就行,其实果实就是一个个的白色实心圆
至此这棵树完整绘制完毕
绘制部分的代码如下
*请认真填写需求信息,我们会在24小时内与您取得联系。