前言 今天注意到17年百度前端学院 活动开放注册了, 作为一只入行一年的小前端, 希望能参与这次活动锻炼一下自己. 按照要求报名后回到首页, 看到有热身任务, 于是点开来玩玩.
但是… 看到第一题后傻眼, 原来是智力题而不是技术题, 😂 然而耗尽250的智商我也没能把题目解出来.
分析页面 于是, 智商不够技术来凑, 打开 dev tool, 注意到console有输出 You are running Vue in development mode.
, 说明该页面是用 vue.js 写的单页应用, 观察 url, url末尾包含 #, 所以路由控制是用 vue-router 的 hash 模式.
打开 sources 页查看源文件, 项目应该是使用了 vue-cli 的 webpack 模板, build 生成了三个js文件, 分别为 app-<hash>.js
, manifest-<hash>.js
, vendor-<hash>.js
, vendor 和 manifest 是项目基础框架和库文件, 我们只需要关注 app.js 即可.
分析源码 首先格式化代码, 得到1641行的代码. 搜索第一题的文案内容: 一笔画出折线
, 找到位于1612行的位置, 向上看找到该代码块开头的地方, 观察1555 ~ 1618行, 可以推断出这里是一个 vue componet, 并且它就是我们看到的第一关, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function (t, e ) {
t.exports = {
render : function ( ) {
var t = this
, e = t.$createElement
, s = t._self._c || e;
return s('div' , {
staticClass : 'index-view'
}, [t._m(0 ), t._v(' ' ), s('div' , {
staticClass : 'points-container' ,
attrs : {
id : 'points-container'
},
on : {
mousemove : t.mousemove,
click : t.click,
contextmenu : t.contextmenu
}
}, [s('svg' , [s('polyline' , {
attrs : {
points : t.linePath
}
})]), t._v(' ' ), t._l(t.points, function (t ) {
return s('i' , {
style : {
left : t.x + 'px' ,
top : t.y + 'px'
}
});
})], 2 ), t._v(' ' ), s('p' , {
staticClass : 'note'
}, [t._v('请使用pc浏览器打开,chrome / firefox / safari / ie9+' )]), t._v(' ' ), s('alert' , {
attrs : {
title : t.alertTitle,
show : t.showAlert
},
on : {
click : function (e ) {
t.showAlert = !1 ;
}
}
}), t._v(' ' ), s('alert' , {
attrs : {
title : 'well done!' ,
show : t.showSuccess
},
on : {
click : t.doNext
}
})], 1 );
},
staticRenderFns : [function ( ) {
var t = this
, e = t.$createElement
, s = t._self._c || e;
return s('h3' , [s('b' , {
staticClass : 'title-tip'
}, [t._v('Unlock' )]), t._v('一笔画出折线,穿过图中的9个点,' ), s('b' , {
staticStyle : {}
}, [t._v('折线个数尽量少' )]), t._v('。(按`enter`确定,按`右键`重新画线,按`esc`取消上一条线段)' )]);
}
]
};
}
但是, 该部分代码仅仅是UI渲染部分, 并不包含任务成功失败的逻辑判断, 我们再尝试搜索下答错题目后显示的文案内容: 还可以用更少的笔画
, 位于328行, 216 ~ 363就是第一关的逻辑判断部分, 其中我们重点关注 computeThrough 这个方法, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
computeThrough: function ( ) {
var t = this
, e = this ;
setTimeout(function ( ) {
if (t.lines.length > 5 )
return void e.showMessage('还可以用更少的笔画,再接再厉哦!' );
for (var s = 0 , n = t.lines.length - 1 , a = t.points.slice(), r = t.lines; n > 0 ; ) {
for (var o = r[n], c = r[n - 1 ], l = a.length - 1 ; l >= 0 ; l--) {
var u = a[l]
, f = Math .pow((c.y - o.y) * u.x + (o.x - c.x) * u.y + (c.x * o.y - o.x * c.y), 2 ) / (Math .pow(c.y - o.y, 2 ) + Math .pow(o.x - c.x, 2 ));
f <= 64 && i(u, o, c) && (a.splice(l, 1 ),
s++);
}
n--;
}
s >= 9 ? (astorage.setItem('startTime-0' , Date .now() - t.startTime),
t.showSuccess = !0 ) : s ? e.showMessage('不错呦,穿过了' + s + '个点!' ) : e.showMessage('瞎了。。。' );
}, 100 );
},
观察代码逻辑后, 我们分别在 if (t.lines.length > 5)
和 s >= 9 ? (astorage.setItem('startTime-0', Date.now() - t.startTime),
的位置下断点, 然后回到题目再次提交答案. 果然代码执行到了我们刚才设置的第一个断点位置, 如下图所示:
lines
是一个 vue 组件实例的 data, 因为其是一个数组类型, 我们修改它的 length 为0, 继续执行代码
由于不满足条件, 代码和预料的一样, 停留在第二个断点位置, 这次我们修改 s 的值为233. 然后继续向后执行, 跳出 well done! 弹窗, 证明我们做的事情是对的!
后续题目 第二题 找出下一关的地址, 这种题目一定是把地址隐藏在了页面里, 直接查看dom即可找出. 不过, 由于页面是 vue 写的, 刚好我又安装了 vue debug 插件, 打开插件可以看到如下内容:
地址拿到了 Iy9CQzY3QTA4MzM1NkM4MzU5NEJDQzU3RUQxMzNEN0EwMjM2N0U0Qg==
, 但是我们直接拿该地址替换url中的hash部分的话, 页面显示 当前URL已失效,请返回首页重新进行闯关。
, 沃特? 什么鬼? 我想百度的童鞋应该不会搞错题目的, 我们再来观察下拿到的地址, 它比第二关的hash长度多了一部分, 并且末尾以==结尾, 这似乎很像是常用的 bash64 编码的格式啊! 打开 base64 解码工具, 反解得到真正的地址 BC67A083356C83594BCC57ED133D7A02367E4B
, 拼接地址并访问.
第三题 观察代码, 每个题目的代码是从上往下排列的, 我们查看第一题后面的第二个function, 得出该题判断条件位于434行, 我们修改 a/c/i/l/r/u 六个变量均为0即可.
1
2
3
4
5
6
7
8
9
10
11
12
13
this .timer = setInterval(function ( ) {
var n = $(window ).height() + ''
, i = -n.slice(0 , 1 )
, a = -n.slice(1 , 2 )
, r = -n.slice(2 )
, o = $('.lock-panel' )
, c = parseInt (o.find('.n1' ).css('top' )) - e
, l = parseInt (o.find('.n2' ).css('top' )) - e
, u = parseInt (o.find('.n3' ).css('top' )) - e;
i * s > c - 10 && i * s < c + 10 && a * s > l - 10 && a * s < l + 10 && r * s > u - 10 && r * s < u + 10 && (astorage.setItem('startTime-2' , Date .now() - t.startTime),
t.showSuccess = !0 );
}, 1e3 );
第四题 该题判断条件在487行, 直接修改 s.length 值为0即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fgarefa: function ( ) {
var t = $('.svg-panel' )
, e = t.offset()
, s = '109 580 280 660 185 664 390.4 807 95 872 294 824' .split(' ' ).map(function (t ) {
return +t;
})
, i = [];
t.find('i' ).each(function (t, s ) {
var n = $(s).offset();
i.push(n.top - e.top),
i.push(n.left - e.left);
});
for (var a = 0 ; a < s.length; a++)
if (Math .abs(i[a] - s[a]) > 2 )
return void (this .balabal = '' );
this .balabal = '#/' + n.maps.d;
}
第五题 观察569 ~ 590行, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
this .ball.on('start' , function (t ) {
s.css({
left : t.x,
top : t.y
});
}).on('move' , function (t ) {
s.css({
left : t.x,
top : t.y
});
}).on('impact' , function ( ) {
n.showMessage('撞车了。。。。' );
}).on('outbound' , function ( ) {
n.showMessage('出界了。。。。' );
}).on('star' , function (t ) {
n.stars.splice(t.starIndex, 1 );
}).on('success' , function (t ) {
return t.starCount < 3 ? void n.showMessage('至少要`3`个星星呦。。。' ) : t.time > 6e4 ? void n.showMessage('跑的比蜗牛还慢,该减减肥了。。。' ) : (astorage.setItem('startTime-4' , Date .now() - this .startTime),
astorage.setItem('step-4-stars' , t.starCount),
n.successTitle = '很牛逼啊,吃了' + t.starCount + '个星星,耗时' + Math .round(t.time / 1e3 ) + '秒' ,
void (n.showSuccess = !0 ));
}),
该题成功失败是基于事件的, 所以我们搜索 success 事件, 看看是在哪里触发的. 得到代码如下:
1
2
3
4
5
6
7
8
9
if (Math .abs(this .x - this .endX) < 10 && Math .abs(this .y - this .endY) < 10 )
return this .stop(),
this .emit('success' , {
starCount: this .starCount,
time : this .endTime - this .startTime
}),
!0 ;
判断条件位于1037行和1040行, 我们修改 this.x / this.endX / this.y / this.endY 的值均为0, 修改 this.starCount 的值为233即可.
胜利 顺利通关 2333, 心满意足. 对百度前端学院 有兴趣的小朋友们, 一起来玩耍吧~~ (免费广告, 去百度面试过两次前端不要俺~~)