JavaScript语言精粹
优先考虑使用.表示法,因为它更紧凑且可读性更好,但是如果你尝试检索一个不一定存在的值,则需使用[‘string’]写法,它将返回一个
undefined
,若尝试从undefined
的成员属性中取值,则会导致 TypeError 异常,这时可以通过&&
运算符避免错误1
2
3
4
5
6
7
8const obj = {
name: 'zhangsan'
}
obj.name // 'zhangsan'
obj['age'] // undefined
obj.hubby.name // throw 'TypeError'
obj.hubby && obj.hubby.name // undefiendfor in
语句可以遍历一个对象中的所有属性,该枚举过程将会列出所有的属性——包括函数和原型中的属性。所以通常可以使用hasOwnProperty
方法过滤。属性名的出现顺序是不确定的,如果你想要确保属性以特定顺序出现,最高的办法就是避免使用for in
语句,而是创建一个数组,在其中以正确的顺序保函属性名。通过 for 而不是 for in,可以得到我们想要的属性,而不用担心可能发觉出原型链中的属性,并且我们按正确顺序取得了它们的值。1
2
3
4
5
6
7
8
9
10
11const obj = {.........}
const arr = [
'name',
'age',
'height',
'weight',
'hubby'
]
arr.forEach(k => {
console.log(item + ': ' + obj[k])
})减少全局变量污染
闭包的好处是内部函数可以访问定义他们的外部函数的参数和变量(除了 this 和 arguments),且内部函数拥有比外部函数更长的生命周期。
函数柯里化:1.参数复用;2.提前返回;3延迟计算/运行(bind就是个延迟执行的例子)
对象说明符:
1
2
3
4
5
6
7
8
9
10
11
12// bad
const myObj = maker(a, b, c, d, e)
// good
const myObj = maker({
first: a,
middle: b,
last: c,
state: d,
city: e
})
// 现在多个参数可以按照任意顺序排列,如果maker函数接受参数使用默认值,那么一些参数也可以忽略调,并且代码也更容易阅读伪类:当一个函数被创建时,新函数对象会被赋予一个
prototype
属性,他的值是一个包含constructor
属性且属性值为该新函数的对象。prototype
是存放继承特征的地方。当采用构造器调用模式,即用new
前缀调用一个函数时,函数执行方式会被修改。构造器函数存在一个严重的危害,如果你在调用构造器函数时忘记加上new
前缀,那么this将会绑定到全局对象上,造成不但没有扩充新对象,反而破坏了全局变量环境,所以构造器函数约定明明成首字母大写的形式(es6的class解决了这个问题,如果不用new调用,则会报错。es6的class可以看做是一个语法糖,他的绝大部分功能es5都可以做到,但是es6的class写法更加清晰、更像面对对象编程的语法)。原型:一个新的对象可以继承一个旧对象的属性(
Object.creat(parentObj)
)。你可以通过构造一个有用的对象开始,接着构造更多的和那个对象类似的对象。这样可以完全避免把一个应用拆解成一系列嵌套抽象类的分类过程。函数化:上面几种继承模式的一个弱点就是没法保护隐私(私有变量、私有函数)。如果对象的所有状态都是私有的,那么该对象就成为一个『防伪(tamper-proof)』对象。该对象的属性可以被替换或删除,但该对象的完整性不会受到损害。
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
34const mammal = function (spec, my = {}) {
// my对象是一个为继承链中的构造器提供秘密共享的容器,my对象可以选择性使用
let _this
_this = {}
// _this = new Object()
// _this = Object.creat({})
// name和saying现在就是完全私有的,只能通过下面两个方法去访问它,无法从其他方式访问
_this.getName = function () {
return spec.name
}
_this.say = function () {
return spec.saying || ''
}
return _this
}
const myMammal = mammal({ name: 'zhangsan' })
// 创建cat继承他,我们的cat只需要关心自身差异
const cat = function (spec = 'meow') {
const _this = mammal(spec)
_this.xyz = function () {
return 'Hello World'
}
_this.getName = function () {
return 'aaaaaa==' + spec.name
}
return _this
}
const myCat = cat({name: 'lisi'})正则尾部符号意义: g(global)/i(ignoreCase)/m(multiline)
用正则表达式字面量创建RegExp对象共享同一个单利:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 字面量
function test () {
return /a/gi
}
const x = test()
const y = test()
x.lastIndex = 10
console.log(y.lastIndex) // 10
// new
function reg () {
return new RegExp(a, 'gi')
}
const x = reg()
const y = reg()
x.lastIndex = 8
console.log(y.lastIndex) // 0除了控制字符和特殊字符外,所有的字符都会被按照字面处理,下列字符如果需要按照字面去匹配,那么必须要用一个
\
前缀来进行转义。1
\ / [ ] ( ) { } ? + * | . ^ $
正则表达式分组:
- 捕获型(/([a-z])/):任何匹配这个分组的字符都会被捕获。每个捕获型分组都被指定了一个数字。在正则表达式中第一个捕获(的分组是1,第二个捕获(的分组是2。($1….$x)
- 非捕获型(/(?:a)/):非捕获型分组仅做简单的匹配,并不会捕获所匹配的文本。这会带来微弱的性能优势。非捕获型分组不会干扰捕获型分组的编号。
- 向前正向匹配(/(?=a)/):它类似于非捕获型分组,但在这个组匹配后,文本会倒回到它开始的地方,实际上并不会匹配到任何东西。这不是一个好特性。
- 向前负向匹配(/(?!a)/):它类似于向前正向匹配分组,但只有当它匹配失败时它才回继续向前进行匹配。这不是一个好特性
1
2
3
4
5
6
7
8"abcabc".match(/(a)(b)(c)/) // ["abc", "a", "b", "c"]
"abcabc".match(/(?:a)(b)(c)/) // ["abc", "b", "c"]
'<div>'.match(/<(?=br>)/) // null
'<br>'.match(/<(?=br>)/) // ["<"]
'<div>'.match(/<(?!br>)/) // ["<"]
'<br>'.match(/<(?!br>)/) // nullregexp.exec()
是正则表达式最强大(也是最慢)的方法。如果它成功匹配regexp
和字符串string
,它会返回一个数组。数组下标0的元素将包含正则表达式regexp匹配的子字符串。下标1的元素是分组1捕获的文本,2的元素是分组2捕获的文本,以此类推。如果匹配失败,它会放回null
。如果regexp带有一个g标识,regexp.lastIndex
会被设置为该匹配后第一个字符的位置,不成功则会重置为0。regexp.test()
是正则表达式最简单(也是最快)的方法。如果它匹配成功则会返回true
,否则返回false
。不要对这个方法使用g标识。统一的代码风格….
毒瘤:
- 全局变量
- 自动插入分号
糟粕:
- ==,隐式转换:
- 对象和布尔值比较时,会先转换为字符串,在转换为数字
- 对象和字符串比较时,对象转换为字符串,然后两者进行比较
- 对象和数字比较时,对象转化为字符串,然后转换为数字,再和数字进行比较
- 字符串和数字比较时,字符串转换为数字
- 字符串和布尔值进行比较时,二者全部转换成数值再比较
- 布尔值和数字进行比较时,布尔转换为数字
1
2
3
4
5'' == 0 // true
[1,2,3] == '1,2,3' // true
'0' == false // true
[] == false // true
true == 1 // true- with: js提供了with语句,本意是想用它来快捷的访问对象属性,不幸的是,他的结果有时不可预料
- eval: eval传递一个字符串给js编译器,并执行其结果。使用eval会导致代码更加难以阅读,且导致性能显著降低,因为它需要运行编译器。eval函数减弱了程序的安全性,因为他给求值的文本太多权利,而且就像with语句执行方式一样,他降低了语言的性能
- continue: 重构移除continue后,性能会得到改善
- 缺少块的语句:貌似在做一件事,实际确实另一件事的程序是非常难理解清楚的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15if (a)
t = 1
fn()
// 它看起来像是要这样:
if (a) {
t = 1
fn()
}
// 实际上本意是
if (a) {
t = 1
}
fn()- 位运算符:在java里,位运算符处理的是整数,ja没有整型,只有双精度浮点数。因此,位操作符把它们的数字运算数先转换成整数,接着执行运算,最后再换回去。再大多数语言中,这些位运算符接近于硬件处理,所以非常快,而js执行环境一般接触不到硬件,所以非常慢。
1
2
3
4
5
6
7& and 按位与
| or 按位或
^ xor 按位异或
~ not 按位非
>> 带符号右位移
>>> 无符号又位移(用0补足)
<< 左位移- ==,隐式转换: