JavaScript中有很多值得学习和记住的小技巧和经典的代码片段,在这里做个小总结。

1. 链式方法

jQuery中 $dom.html().css().addClass() 这类的就叫链式方法,在对象方法中返回对象本身(this)就可以实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Chain(val){
this.val = val

this.print = function(){
console.log(this.val)
return this
}

this.setVal = function(val){
this.val = val
return this
}
}

let chain = new Chain('foo')
chain.print().setVal('bar').print()
//foo
//bar

2. 注意字符串连接

JavaScript中经常会有一些意想不到的类型转换,’+’是其中最常见的一种运算,它既可以做数字的加法,也可以做字符串连接,不注意使用可能会出现不想看到的结果。看下面的例子

1
2
3
4
5
6
let c1 = 12, c2 = 34, c3 = '56'

let result1 = c1 + c2 + c3 //'4656'
let result2 = ''.concat(c1, c2, c3) //'123456'
/*当然也可以这样,总是在前面加个''*/
let result3 = '' + c1 + c2 + c3 //'123456'

结果1中先做了加法,而非我们期望的字符串连接,使用后两种方法更安全一些。
如果是ES6,使用template string显然更方便。

1
2
3
let c1 = 12, c2 = 34, c3 = '56'

let result = `${c1}${c2}${c3}` //'123456'

3. 检查某个数是否为-1

要知道if(n)只有当n(数字类型)为0时才会判定为false,而对于~n只有当n为-1时会得到0,这在使用Array.prototype.indexOf()判断某一项是否在数组中时可以起到简化代码的作用,如下

1
2
3
4
const a = [1, 2, 3]
if (a.indexOf(4) !== -1) { // 或者a.indexOf(4) > -1
// 数组a中含有4
}

使用~我们可以像下面这样,代码会更简洁,只是可读性会稍稍降低

1
2
3
4
const a = [1, 2, 3]
if (~a.indexOf(4)) {
// 数组a中含有4
}

4. 双波浪运算(~~)

‘~~’表示执行两次’~’(按位非)运算,对于正数,它相当于Math.floor(),对于负数则相当于Math.ceil(),只是性能更高,写起来也更快,不过要注意它只能用于32位及以内的数值。

1
2
3
4
~12       //-13
~~12.34 //12
~~12.89 //12
~~-12.88 //-12

5. 使用!!总是返回Boolean类型

‘!!’表示执行两次非运算,经过类型转换之后总是会返回布尔值,这在有些场景下会很有用。

1
2
3
function isLogin (req) {
return !!req.session.user
}

6. Debouncing VS Throttling

Debouncing 和 Throttling 都是防止某个函数执行过于频繁,以提高性能。不同的是,Debouncing 表示把某一段时间内重复触发的事件归结到一次回调中执行,比如谷歌或百度的动态搜索框的文本输入事件,可以设置在键盘输入停止500ms才发起ajax请求;而 Throttling 表示的是在某一时间段内,限制某一函数只执行一次,比如一个动态加载的列表,它会在滚动到底部时加载新的列表项,然而用户的滚动事件的触发太频繁,我们可以限制它每500ms最多执行一次加载操作。更详细的说明可以参考这篇文章
underscorelodash都有对这两个高阶函数的实现和拓展,不考虑拓展的基本实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var debounce = function (fn, delay) {
var timer
return function () {
var args = arguments,
self = this
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
clearTimeout(timer)
fn.apply(self, args)
timer = null
}, delay)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var throttle = function (fn, interval) {
var timer,
firstTime = true //是否首次调用
return function () {
var 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)
}
}

window.onresize = throttle(function () {
console.log(document.body.clientWidth)
}, 250)

7. 惰性载入函数

由于浏览器之间的差异,在开发前端的时候,一些特性嗅探操作总是不可避免的,比如一个比较通用的添加事件的函数

1
2
3
4
5
6
7
var addListener = function (el, type, handler) {
if (window.addEventListener) {
el.addEventListener(type, handler, false)
} else {
el.attachEvent(type, handler)
}
}

上面代码的缺点是每次调用addListener都用执行里面的if分支判断,这种情况是完全可以避免的

1
2
3
4
5
6
7
8
9
10
11
12
var 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判断,往后的调用都不需要再做判断。

8. html文本编码

对于插入dom中的文本,我们需要对’<>&”‘等特殊字符进行转义,通常可以像下面这样子做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function htmlEscape (test) {
return text.replace(/[<>&"]/g, function (match) {
switch (match) {
case '<':
return '&lt;'
case '>':
return '&gt;'
case '&':
return '&amp;'
case '"':
return '&quot;'
}
})
}

其实在浏览器环境中,还可以写成下面那样:

1
2
3
function htmlEscape (text) {
return document.createElement('p').appendChild(document.createTextNode(text)).parentNode.innerHTML
}

9. 简单的类型判断

例如判断一个变量是否为数组?只是使用instanceof的话在多个iframe的页面,由于不同iframe全局执行环境不同,存在多个不同的Array构造函数,不一定可行;在es5中可以直接使用静态方法Array.isArray(),而比较通用的方法则如下所示:

1
2
3
function isArray (value) {
return Object.prototype.toString.call(value) === '[object Array]'
}

当然除了数组,JavaScript中的基本类型和其他内置对象也可以用这种方法。

1
2
3
function isNumber (value) {
return Object.prototype.toString.call(value) === '[object Number]'
}

10. 检测是否为本地代码

1
2
3
function isNative (constructor) {
return /native code/.test(constructor.toString())
}

比如检测当前作用域下的Promise构造器是否为浏览器自带的Promise实现。

1
isNative(Promise)