我是一名自学敲代码的管理学研究生,喜欢 js/ts 但是菜得不行,平常挺关注国内的前端圈。
拍澄湖大闸蟹 Ingo Schulz/Offset by Shutterstock
除了读雪碧大佬[1]等等大佬的知乎外(蒟蒻还看不太懂),平常也看看大圣老师[2]等等的B站。
有一次看大圣老师直播点评简历,他提到:“如果我来面试你,我就把我面前的笔记本给你,随便给你打开个网页比如淘宝,你给我用浏览器现场统计一下各个标签出现的次数。”
!这道题应该不难?我分析无非就是考察了三点:
- 最最基础的浏览器调试能力
- 算法能力
- 基础的 JavaScript API 应用
刚和爸妈打完球回来,那我就做做这道题。
首先咱捋一下思路:
- 其实早在听到这个题目时,我脑子中就蹦出两个字:『递归』!
- 毕竟,我们的网页就是一棵 DOM 树,从根部有子节点,子节点还有子节点,对于每个节点,我们能够
并且知道这个节点是什么标签
就可以了对其子节点做同样的事
然后我们捋一下需要哪些技术细节:
- 首先我们应该获取根节点,这个好说,我们在浏览器的控制台里试一试就知道:
document.children[0]
- 然后我们应该能够获取每个标签对象的
和字符串名字
,分别是子节点列表
和tagName
children
- 至于如何实现「递归」呢?这里未必要用到递归,我用的是宽度优先搜索 BFS ,简单一个队列就能实现
值得一提的是,我近一个月里写了基于
C++
、
Python
、
JavaScript/TypeScript
、
Scala/Java
的不同项目/小脚本(工作要求...),所以我也记不住 JavaScript 的 API ,我都是在浏览器控制台里试出来的,比如
获取标签的名字是 tagName
、
获取子节点 Array 是 children
。如下图,我试关键词试出来的,要不然谁记得住啊。
输入 tag 会不会得到我想要的 API 呢?果然!
下面动手来做吧
第零步,打开浏览器的
Sources
,新建一个 Snippet 。
Sources
首先我不知道 JavaScript 里有没有现成的队列数据结构,应该是没有,那我就自己实现一个吧。
class Queue {
#array = []
constructor () {
this.#array = []
}
top () {
return this.#array[0]
}
size () {
return this.#array.length
}
pop () {
this.#array.shift()
}
push (ele) {
this.#array.push(ele)
}
}
复制
很简单的封装!我平时做算法题都是用 C++ ,所以这里方法的名称就都尽量接近 C++ 的
std::queue<T>
。
接下来咱们写 BFS 就行了!
我看现在大佬们都把每个逻辑封装在函数里,所以咱也把脚本运行逻辑
main()
里,然后再在外面调用一下
main()
,看着整洁点。
const main = () => {
const dict = {}
const queue = new Queue()
const htmlTag = document.children[0]
dict[htmlTag.tagName] += 1 // !!!
queue.push(htmlTag)
while (queue.size() > 0) {
const t = queue.top()
queue.pop()
for (let i = 0; i < t.children.length; i ++) {
childTag = t.children[i]
dict[htmlTag.tagName] += 1 // !!!
queue.push(childTag)
}
}
for (let item in dict) {
console.log(item, ': ', dict[item])
}
}
main()
复制
上面是最最简单的 BFS 实现了,可见这道题着实不是用算法难为我们,很实在的一道题。
注意我标注的
!!!
两行,这里有一个问题:
-
中,对于未声明过的键值,如果直接调用运算,会报错dict = {}
dict[未声明的键值] +=1 // 报错!
- 而 js 又不是 Python ,没有
给我们用比如setdefault
dict.setdefault(键值, 0); dict[键值] += 1
- js 也不是 C++ ,直接默认未出现过的键值的值为 0
- 因此我们需要再写一个
功能dict[未声明的键值] +=1
咱们把这个逻辑写成一个
Effect
,返回一个函数,以显示咱很注重逻辑复用性(划去)。
const addDictEffect = (dict) => {
return (name) => {
if (dict[name]) {
dict[name] += 1
} else {
dict[name] = 1
}
}
}
复制
OK 那下面在修改一下
main
,一共有三处!
const main = () => {
const dict = {}
const addDict = addDictEffect(dict) // 第一处!
const queue = new Queue()
const htmlTag = document.children[0]
addDict(htmlTag.tagName) // 第二处!
queue.push(htmlTag)
while (queue.size() > 0) {
const t = queue.top()
queue.pop()
for (let i = 0; i < t.children.length; i ++) {
childTag = t.children[i]
addDict(childTag.tagName) // 第三处!
queue.push(childTag)
}
}
for (let item in dict) {
console.log(item, ': ', dict[item])
}
}
main()
复制
啪!很快啊,本题目解决。
www.taobao.com
结果如下。
代码
结果
其他网页均可测试。
完整代码
class Queue {
#array = []
constructor () {
this.#array = []
}
top () {
return this.#array[0]
}
size () {
return this.#array.length
}
pop () {
this.#array.shift()
}
push (ele) {
this.#array.push(ele)
}
}
const addDictEffect = (dict) => {
return (name) => {
if (dict[name]) {
dict[name] += 1
} else {
dict[name] = 1
}
}
}
const main = () => {
const dict = {}
const addDict = addDictEffect(dict)
const queue = new Queue()
const htmlTag = document.children[0]
addDict(htmlTag.tagName)
queue.push(htmlTag)
while (queue.size() > 0) {
const t = queue.top()
queue.pop()
for (let i = 0; i < t.children.length; i ++) {
childTag = t.children[i]
addDict(childTag.tagName)
queue.push(childTag)
}
}
for (let item in dict) {
console.log(item, ': ', dict[item])
}
}
main()
复制
目前不会js/ts+没做过项目,菜到都没法给大圣老师发简历让他点评。期待早日能到发简历的地步。
参考资料
[1]
雪碧大佬: https://www.zhihu.com/people/doodlewind
[2]
大圣老师: https://space.bilibili.com/26995758