JavaScript 中有很多值得学习记录的小技巧和经典的代码片段,在这里做个小总结。
1. 注意字符串连接
JavaScript 中经常会有一些意想不到的类型转换,+
是其中最常见的一种运算,它既可以做数字的加法,也可以做字符串连接,不注意使用可能会出现不想看到的结果。看下面的例子
const c1 = 12, c2 = 34, c3 = '56'
const result1 = c1 + c2 + c3 // '4656'
const result2 = ''.concat(c1, c2, c3) // '123456'
/* 当然也可以这样,总是在前面加个'' */
const result3 = '' + c1 + c2 + c3 // '123456'
结果 1 中先做了加法,而非我们期望的字符串连接,使用后两种方法更安全一些。 如果是 ES6,使用 template literals 显然更方便。
const c1 = 12, c2 = 34, c3 = '56'
const result = `${c1}${c2}${c3}` // '123456'
2. 检查某个数是否为-1
要知道 if(n)
只有当 n (数字类型)为 0 时才会判定为 false
,而对于 ~n
只有当 n 为 -1 时会得到 0,这在使用 Array.prototype.indexOf()
判断某一项是否在数组中时可以起到简化代码的作用,如下
const a = [1, 2, 3]
if (a.indexOf(4) !== -1) { // 或者 a.indexOf(4) > -1
// 数组 a 中含有 4
}
使用 ~
我们可以像下面这样,代码会更简洁,只是可读性会稍稍降低
const a = [1, 2, 3]
if (~a.indexOf(4)) {
// 数组 a 中含有 4
}
3. 双波浪运算(~~)
~~
表示执行两次 ~
(按位非)运算,对于正数,它相当于 Math.floor()
,对于负数则相当于 Math.ceil()
,只是性能更高,写起来也更快,不过要注意它只能用于 32 位及以内的数值。
~12 // -13
~~12.34 // 12
~~12.89 // 12
~~-12.88 // -12
4. 使用 !! 总是返回 Boolean 类型
!!
表示执行两次非运算,经过类型转换之后总是会返回布尔值,这在有些场景下会很有用。
// 判断 value 是否为布尔值
function isBoolean (value) {
return value === !!value
}
5. Debouncing VS Throttling
Debouncing 和 Throttling 的作用都是防止某个函数执行过于频繁,以提高性能。不同的是,Debouncing 表示把某一段时间内重复触发的事件归结到一次回调中执行,比如谷歌或百度的动态搜索框的文本输入事件,可以设置在键盘输入停止 500ms 才发起 ajax 请求;而 Throttling 表示的是在某一时间段内,限制某一函数只执行一次,比如一个动态加载的列表,它会在滚动到底部时加载新的列表项,然而用户的滚动事件的触发太频繁,我们可以限制它每 500ms 最多执行一次加载操作。更详细的说明可以参考这篇文章。 underscore 和 lodash 都有对这两个高阶函数的实现和拓展,不考虑拓展的基本实现如下:
const debounce = function (fn, delay) {
let timer
return function () {
const args = arguments,
self = this
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
clearTimeout(timer)
fn.apply(self, args)
timer = null
}, delay)
}
}
const throttle = function (fn, interval) {
let timer,
firstTime = true // 是否首次调用
return function () {
const args = arguments,
self = this
if (firstTime) { // 如果是首次调用,不用延时
fn.apply(self, args)
return firstTime = false
}
if (timer) {
return false
}
timer = setTimeout(function () {
clearTimeout(timer)
fn.apply(self, args)
timer = null
}, interval)
}
}
6. 惰性载入函数
由于浏览器之间的差异,在开发前端脚本的时候,一些特性嗅探操作总是不可避免的,比如一个比较通用的添加事件的函数
const addListener = function (el, type, handler) {
if (window.addEventListener) {
el.addEventListener(type, handler, false)
} else {
el.attachEvent(type, handler)
}
}
上面代码的缺点是每次调用 addListener
都用执行里面的 if
分支判断,这种情况是完全可以避免的
let addListener = function (el, type, handler) {
if (window.addEventListener) {
addListener = function (el, type, handler) {
el.addEventListener(type, handler, false)
}
} else {
addListener = function (el, type, handler) {
el.attachEvent(type, handler)
}
}
addListener(el, type, handler)
}
上面的代码直接在函数内部重写了 addListener
函数,这样只有首次调用的时候需要做 if
判断,往后的调用都不需要再做判断。
7. html文本编码
对于插入 DOM 中的文本,我们需要对 <>&"
等特殊字符进行转义,通常可以像下面这样子做:
function htmlEscape (test) {
return text.replace(/[<>&"]/g, function (match) {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '&':
return '&'
case '"':
return '"'
}
})
}
其实在浏览器环境中,还可以写成下面那样:
function htmlEscape (text) {
return document.createElement('p').appendChild(document.createTextNode(text)).parentNode.innerHTML
}
8. 简单的类型判断
例如判断一个变量是否为数组?只是使用 instanceof
的话在多个 iframe 的页面,由于不同 iframe 全局执行环境不同,存在多个不同的 Array 构造函数,不一定可行;在 es5 中可以直接使用静态方法 Array.isArray()
,而比较通用的方法则如下所示:
function isArray (value) {
return Object.prototype.toString.call(value) === '[object Array]'
}
当然除了数组,JavaScript 中的基本类型和其他内置对象也可以用这种方法。
function isNumber (value) {
return Object.prototype.toString.call(value) === '[object Number]'
}
9. 检测是否为浏览器内部对象
function isNative (constructor) {
return /native code/.test(constructor.toString())
}
比如检测当前作用域下的 Promise 构造器是否为浏览器自带的 Promise 实现。
isNative(Promise)