BX.luckyDraw()
}, false)
// other codes…
btn.addEventListener(‘click’, function () {
BX.track(‘lucky-draw’)
}, false)
所有功能终于又正常了,我很开心地准备上线。
第二关
事实证明我还是太天真了,PM 是不会一次性把所有需求都告诉你的。原来,这个抽奖功能还需要做 A/B 测试,也就是说,只有一半的用户会看到这个抽奖功能。
这意味着用户的页面上可能根本没有 btn
这个元素,那么 btn.addEventListener(...)
这一句直接就抛错了。因此,在为按钮绑定事件处理函数之前,我不得不先判断一下:
if (btn) {
btn.addEventListener(‘click’, function () {
BX.luckyDraw()
}, false)
}
// other codes…
if (btn) {
btn.addEventListener(‘click’, function () {
BX.track(‘lucky-draw’)
}, false)
}
虽然这样的代码在所有用户的页面上都可以正常工作,但这些预先判断看起来很蛋疼啊。我再次带着疑惑向前辈请教。前辈慈祥地看着我,说出了一句经典名言:
傻瓜,为什么不用万能的 jQuery 呢?
原来,神奇的 jQuery 允许我们忽略很多细节,比如这种没有取到元素的情况会被它默默地消化掉。而且 jQuery 的事件绑定方法也不存在兼容性问题,API 也比较好看。不错不错,不管网上的大神们怎么喷 jQuery,但它简直是我的救星啊!
于是,我的代码变成了以下这样:
var $btn = $(‘#lucky-draw’)
$btn.on(‘click’, function () {
BX.luckyDraw()
})
// other codes…
$btn.on(‘click’, function () {
BX.track(‘lucky-draw’)
})
我的代码看起来像那么回事了,我很开心地准备上线。
第三关
当然,我的故事不会这么快结束。要知道,对一个有追求的前端团队来说,不断提升用户体验是永恒的目标。比如,我们网站使用了一些方法来提升页面加载性能,部分页面内容并不是原本存在于页面中的,而是在用户需要时,由 JavaScript 动态生成的。
拿这个抽奖功能来说,抽奖按钮存在于一个名为 “惊喜” 的 tab 中,而这个 tab 在初始状态下是没有内容的,只有当用户切换到这个 tab 时,才会由 JS 填充其内容。示意代码是这样的:
$(‘.tabs > .surprise’).on(‘click’, function () {
var htmlSurpriseTab = [
‘
‘Lucky Draw’,
‘’
].join(‘’)
$(‘.tab-panels > .surprise’).html(htmlSurpriseTab)
// BTN READY
})
这意味着,我写的事件绑定代码需要写在 // BTN READY
处。这种深层的耦合看起来很不理想,我需要想办法解决它。
我想起来,我在阅读 jQuery 文档时看到有一种叫作 “事件委托” 的方法,可以在元素还未添加到页面之前就为它绑定事件。于是,我尝试这样来写:
$(document.body).on(‘click’, ‘#lucky-draw’, function () {
BX.luckyDraw()
})
果然,我成功了!好事多磨啊,这个需求终于开心地上线了。
经过进一步的研究,我了解到 “事件委托” 的本质是利用了事件冒泡的特性。把事件处理函数绑定到容器元素上,当容器内的元素触发事件时,就会冒泡到容器上。此时可以判断事件的源头是谁,再执行对应的事件处理函数。由于事件处理函数是绑定在容器元素上的,即使容器为空也没有关系;只要容器的内容添加进来,整个功能就是准备就绪的。
虽然事件委托的原理听起来稍有些复杂,但由于 jQuery 对事件委托提供了完善的支持,我的代码并没有因此变得很复杂。
多想一步
经过这一番磨炼,我收获了很多经验值;同时,我也学会了更进一步去发现问题和思考问题。比如,在我们的网页,通常会有多个按钮,那为它们绑定事件的脚本代码可能就是这样的:
$body = $(document.body)
$body.on(‘click’, ‘#lucky-draw’, function () {
BX.luckyDraw()
})
$body.on(‘click’, ‘#some-btn’, function () {
// do something…
})
$body.on(‘click’, ‘#another-btn’, function () {
// do something else…
})
我隐隐觉得这样不对劲啊!虽然这些代码可以正常工作,但每多一个按钮就要为 body
元素多绑定一个事件处理函数;而且根据直觉,这样一段段长得差不多的代码是需要优化的。因此,如果我可以把这些类似的代码整合起来,那不论是在资源消耗方面,还是在代码组织方面,都是有益的。
于是,我尝试把所有这些事件委托的代码合并为一次绑定。首先,为了实现合并,我需要为这些按钮找到共同点。很自然地,我让它们具有相同的 class:
Lucky Draw
Button
真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲
详情关注公中号【编程进阶路】
Link
然后,我试图通过一次事件委托来处理所有这些按钮:
$body.on(‘click’, ‘.action’, function () {
// WHEN CLICK ANY ‘.action’, WE COME HERE.
})
很显然,所有具有 action
类名的元素被点击后都会触发上面这个事件处理函数。那么,接下来,我们在这里区分一下事件源头,并执行对应的任务:
$body.on(‘click’, ‘.action’, function () {
switch (this.id) {
case ‘lucky-draw’:
BX.luckyDraw()
break
case ‘some-btn’:
// do something…
break
// …
}
})
这样一来,所有分散的事件委托代码就被合并为一处了。在这个统一的事件处理函数中,我们使用 ID 来区分各个按钮。
但 ID 有一些问题,由于同一页面上不能存在同名的元素,相信前端工程师们都对 ID 比较敏感,在日常开发中都尽量避免滥用。此外,如果多个按钮需要执行的任务相同,但它的 ID 又必须不同,则这些 ID 和它们对应的任务之间的对应关系就显得不够明确了。
于是,我改用 HTML5 的自定义属性来标记各个按钮:
Lucky Draw
Button
我在这里使用了 data-action
这个属性来标记各个按钮元素被点击时所要执行的动作。回过头看,由于各个按钮都使用了这个属性,它们已经具备了新的共同点,而 class 这个共同点就不必要了,于是我们的 HTML 代码可以简化一些:
Lucky Draw
Button
同时 JS 代码也需要做相应调整:
$body.on(‘click’, ‘[data-action]’, function () {
var actionName = $(this).data(‘action’)
switch (actionName) {
case ‘lucky-draw’:
BX.luckyDraw()
break
case ‘some-btn’:
// do something…
break
// …
}
})
我们的代码看起来已经挺不错了,但我已经停不下来了,还要继续改进。那个长长的 switch
语句看起来有点臃肿。通常优化 switch
的方法就是使用对象的键名和键值来组织这种对应关系。于是我继续改:
var actionList = {
‘lucky-draw’: function () {
BX.luckyDraw()
},
‘some-btn’: function () {
// do something…
}
// …
}
$body.on(‘click’, ‘[data-action]’, function () {
var actionName = $(this).data(‘action’)
var action = actionList[actionName]
if ($.isFunction(action)) action()
})
经过这样的调整,我发现代码的嵌套变浅了,而且按钮们的标记和它们要做的事情也被组织成了actionList
这个对象,看起来更清爽了。
在这样的组织方式下,如果页面需要新增一个按钮,也很容易做扩展:
// HTML
$body.append(‘Link’)
ES6
-
列举常用的ES6特性:
-
箭头函数需要注意哪些地方?
-
let、const、var
-
拓展:var方式定义的变量有什么样的bug?
-
Set数据结构
-
拓展:数组去重的方法
-
箭头函数this的指向。
-
手写ES6 class继承。
微信小程序
-
简单描述一下微信小程序的相关文件类型?
-
你是怎么封装微信小程序的数据请求?
-
有哪些参数传值的方法?
-
你使用过哪些方法,来提高微信小程序的应用速度?
-
小程序和原生App哪个好?
-
简述微信小程序原理?
-
分析微信小程序的优劣势
-
怎么解决小程序的异步请求问题?