本书指出了JavaScript这门语言的精华与糟粕,作者也是JSON的创立者,更是莎士比亚迷,每一章前面都会引用莎士比亚的一句名言~
Chap-1 精华
好的部分
函数,弱类型,动态对象,对象字面量表示法。
弱类型
强类型允许编译器在编译时检测错误。虽然强类型为主流要求,但是弱类型更自由,无须建立复杂的类层次,不用疲于应付系统以得到想要的行为。
字面量表示法
简单直观,列出对象的组成部分就是创建过程,JSON的灵感来源。
原型继承
(class-free)无类别对象系统,对象直接从其他对象继承属性。
坏的部分
基于全局变量的编程模型
Chap-2 语法
数字
- 单一数字类型,表示为64位的浮点数,没有分离出单独的整数类型。1和1.0是相同的值。避免了短整数的溢出问题。
数字字面量可以有指数部分:
12var a = new Number(1e2);console.log(a); //[Number: 100]数字有方法。
Math
对象拥有作用于数字的一套方法。如Math.floor
字符串
- JavaScript被创建时,Unicode是16位的字符集,所以JavaScript所有字符都是16位的。
\u
只懂用数字表示的字符码位。"A" === "\u0041";
- 字符串有
length
属性:'seven'.length === 5;
- 字符串不可变,
'+'
运算符连接后的字符串是新字符串 'c' + 'a' + 't' === 'cat';
- 字符串有方法。如
toUpperCase()
语句
- 花括号创建的代码块,并没有创建新的作用域。
if
为假的值:false, null, undefined, ' ', 0, NaN
12345for(myvar in obj){if(obj.hasOwnProperty(myvar)){ //是该对象成员还是原型链里找到的...}}try...catch
:在try
语句中throw
一个异常对象,有name
和message
属性。return
语句:若没有指定返回表达式则返回值为undefined
。return
关键字和表达式之间不能换行,break
关键字和标签之间也不能换行。
表达式
- 运算符优先级
typeof
返回值:number, string, boolean, undefined, function, object
。对于数组和null
也返回object
。这样不好。/
运算符可能产生一个非整数结果,即使两个运算数都是整数。
Chap-3 对象
- 简单类型:数字,字符串,布尔值,
null
值和undefined
值。其他值(包括正则表达式)均为对象。 - 对象是键控集合(keyed collections),数字、字符串、布尔值貌似对象,因为它们拥有方法,但它们是不可变的,故而不是对象。
- 属性名:包括空字符串在内的任意字符串;属性值:除
undefined
值以外的任何值。、 - JS对象是无类别(class-free)的。对于新属性的名字和值没有约束。
- 原型链特性:对象继承,减少对象初始化的时间和内存消耗。
对象字面量
对象字面量即“名/值”对,如果属性名合法且不是保留字,并不强制要求用引号括住属性名,如:"first-name"
必须括住,而first_name
可以不用引号括住。
- 读取属性和给属性赋值:可以用方括号表示法和点表示法,优先后者。方括号方式的优点是:可以使用变量和表达式作为属性。赋值时,若属性已存在则更新属性值,不存在则创建属性值。
- 检索属性值:用
&&
避免该属性不存在而报错 - 用
||
填充属性默认值 - 对象通过引用传递,而不是拷贝。
原型
原型连接在属性值更新时不起作用,只有检索值的时候才用到。委托:层层向上寻找某属性,直到找到Object.prototype
为止,若没有则返回undefined
。
创建一个使用原对象作为其原型的新对象:
反射
检查对象有什么属性:typeof
确定属性的类型,可以用来排除函数;hasOwnProperty
确定是否是对象独有的属性,不检查原型链。
枚举
for...in
语句:
- 会列出包含原型链属性在内的所有属性:结合反射,过滤掉你不想要的值。
- 出现的顺序不确定。
删除
delete
运算符。移除对象中属性,不触及原型链中的任何对象。删除对象的属性可能会让来自原型链中的属性暴露出来。
减少全局变量污染
最小化使用全局变量:在应用中只创建唯一一个全局变量(命名空间):
把多个全局变量整理在一个命名空间下,避免与其他应用程序、组件或类库之间相互影响。
或:使用闭包。
Chap-4 函数
代码复用、信息隐藏、组合调用。
函数对象
每个对象都拥有一个连到原型对象的隐藏连接:对象字面量产生的对象连接到Object.prototype
,函数对象连接到Function.prototype
(该原型对象本身连接到Object.prototype
)。
每个函数在创建时附有两个附加隐藏属性:函数的上下文,实现函数行为的代码。
每个函数创建时,该函数对象有一个prototype
属性,该属性值也是一个对象,该对象的constructor
属性值为该函数,即:
函数字面量
通过函数字面量定义函数,如果没有给函数命名,会被认为是匿名函数:
函数字面量可以出现在任何允许出现表达式的地方。
闭包:通过函数字面量创建的函数对象包含一个连到外部上下文的连接。闭包中能访问到它被嵌套在其中的那个函数的参数与变量。
函数调用
函数调用时,接收的参数除了声明时定义的形式参数之外(如果实际参数过多了,超出的参数值将被忽略;如果实际参数值过少,缺失的参数值为undefined
),还会接收两个附加参数:this
和arguments
。this
的值取决于函数调用的模式:
方法调用模式
函数被保存为对象的一个属性:方法。
超级迟绑定:方法被调用时,该方法所属对象的this
绑定到方法上。高度复用this
。
公共方法:通过this
可以取得其所属对象的上下文的方法函数调用模式
函数并非对象属性时,为函数调用。this
指向全局对象。这是设计上的错误,没法通过内部函数的this
访问到外部变量。解决方法:var that = this;
12345678910111213var myObj = {value: 0};myObj.double = function(){ ////该函数是对象myObj的属性,其内部的this就指向myObj对象的this,故可通过this.value访问到myObj对象的value属性值var that = this;var helper = function(){ //该函数不是对象myObj的属性,其内部的this不指向myObj对象的this,故无法通过this.value访问到myObj对象的value属性值that.value = that.value + that.value;};helper();}构造器调用模式
new Constructor()
:123456789101112//constructorvar Quo = function(str){this.status = str;};//公共方法,所有实例共有Quo.prototype.getStatus = function(){return this.status; //this指向该构造函数构造的对象}var myQuo = new Quo("confused");console.log(myQuo.getStatus()); //confusedapply
调用模式func.apply(将被绑定给this的值, arguments array);
若第一个参数为null
,则直接调用函数本身。
参数
arguments
类数组对象。不推荐的模式。
返回
如果函数以new
调用,且返回值不是一个对象,则返回this
(该新对象)
异常
throw
语句中断函数执行,抛出一个exception
对象,该对象包含可识别异常类型的name
属性和message
属性,也可添加其他属性。
产生的exception
对象会被传递到catch
从句。
给类型增加方法
本质:原型继承的动态性,新添加的方法立刻赋予到所有对象实例上,哪怕对象实例在方法被创建前就建立好了。
注意:
确定没有该方法时才添加它:
123if(!this.prototype[name]){this.prototype[name] = func;}因为是在原型上添加,而
for...in
在原型上的表现很糟糕,使用注意判断筛选。
递归
汉诺塔问题
|
|
递归处理给定DOM树
|
|
尾递归优化
尾递归:如果函数返回自身递归调用的结果(如Fibonacci序列),调用过程会被替换为一个循环,提高速度。
尾递归优化:JavaScript没有提供尾递归优化。所以深度递归可能导致堆栈溢出。
作用域
控制变量和参数的可见性及生命周期。
JavaScript没有块级作用域:最好在函数体的顶部声明函数中可能用到的所有变量。
闭包
闭包:函数可以访问它被创建时所处的上下文环境。
example1
|
|
返回的对象中的两个闭包仍能访问value
example2
关于前述的构造器调用模式:用了一个getter
函数去访问本可以直接访问的属性status
。修改:将status
属性设为私有属性,然后使用闭包去访问它:
即使quo
已经返回了,但是闭包get_status
仍然享有访问quo
对象的status
属性的特权。
example3
|
|
example4
预期结果:单击每个节点时弹出节点序号
实际结果:单击每个节点都弹出节点总数,即循环结束时i
最后的值。
修改:使用闭包
回调
发起异步请求,当服务器的响应到达时调用回调函数,避免客户端假死。
模块
模块是一个提供接口却隐藏状态与实现的函数或对象。
examle1
给String
添加方法,寻找字符串中的HTML字符实体并替换为它们对应的字符。
只有deentityify
方法有权访问entity
对象
模块模式:一个定义了私有变量和函数(可访问私有变量的闭包)的函数。避免了全局变量的使用。
example2
用来产生安全的对象
如果把unique
作为一个值传给第三方函数,那个函数能使用它,但是不能通过它改变prefix
或seq
的值。
级联
没有返回值的函数,如果让其返回this
而不是undefined
,就可以启用级联(链式调用)。在一条语句中依次调用同一个对象的很多方法。
套用:curry
套用:将函数与传递给它的参数结合产生一个新的函数。给函数添加curry
方法:
记忆
函数用对象记住先前的操作结果,避免无谓的运算。比如Fibonacci数列:
=> 带记忆功能的函数
Chap-5 继承
对象可以直接从其他对象继承。
伪类 pseudoclassical
创建一个类,其原型为父类对象
对象说明符
传参时,若有多个参数,考虑将其写在一个参数对象里,将参数对象传入函数。
原型
用前面给对象添加的beget
方法继承:差异化继承
函数化:模块模式
函数化构造器的伪代码模板:
相比较与伪类模式,函数话模式具有更好的封装和信息隐藏,以及访问父类方法的能力。
函数化模式提供了处理父类方法的方法:
持久性:使用函数化模式创建一个对象,且对象的所有方法都不使用this
或that
。该对象所有状态都是私有的。
部件
构造一个能添加事件处理函数到任何对象上的函数:
Chap-6 数组
数组字面量
数组字面量:var arr = [];
继承自Array.rpototype
array-like 类数组:var arr = { '0': 'a', '1': 'b' }
继承自Object.prototype
JavaScript允许数组包含任意混合类型的值。
长度
length
属性值是这个数组的最大整数属性名+1,它不一定等于数组之际属性个数。[]
后缀下标运算符将它的表达式转换成字符串(一般调用该表达式的toString
方法)。
设置更大的length
无须给数组分配更多空间,设置更小的length
导致下标小于新length
的属性被删除。
删除
delete
运算符:删除的元素下标会保留,对应值变为undefined
- 更好的方法:
splice
:arr.splice(index, delLength, replace elements);
但是每删除一次后面的元素就要移动位置,对大型数组可能效率不高。
枚举
虽然可以用for...in
,但是最好不要,因为:
- 可能遍历到原型链中的属性
- 无法保证属性顺序
最好用for(var ... )
循环
混淆的地方
typeof
对数组、对象和null
的判断结果均为'object'
。下面方法对不同
'window'
或'frame'
里构造的数组会失效:123a &&typeof a === 'object' &&a.constructor === Array;最可靠的写法:
12345a &&typeof a === 'object' &&a.length === 'number' &&typeof a.splice === 'function' &&!(a.propertyIsEnumerable('length')); //length属性是否会被for...in是否会枚举出来
方法
可以通过Array.prototype
扩充对所有数组有效的方法,或通过arr.func
给某个特定数组对象添加方法。
维度
JavaScript没有多维数组,但是它支持元素为数组的数组。
Chap-7 正则表达式 Regular Expression
相关方法:regexp.exex
regexp.test
string.match
string.replace
string.search
string.split
正则表达式必须写在一行中
例子
example1
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]&*))?(?:\?([^#]*))?(?:#(.*))?$/;
匹配结果:
解释:(?:([A-Za-z]+):)?
匹配协议名?:
表示一个非捕获型分组(noncapturing group),后缀?
表示该分组重复0次或1次。括号中的内容表示一个捕获型分组,有编号,对应result
数组的下标。[]
表示字符类。+
表示一次或多次。
example2
|
|
大小写问题: /i
表示忽略大小写。也可以不写/i
,把e
写成[Ee]
或(?:[E|e])
结构
优先使用正则表达式字面量构造正则表达式。
使用字面量创建的正则表达式对象共享一个实例。
如果使用RegExp
构造器,需要双写反斜杠并对引号进行转义,且第二个参数为标志g,i,m
。RegExp
构造器适用于运行时动态生成的情形。
元素
/in|int/
如果匹配了in
就不会匹配int
。
当指定了/m
标志时,$
也能匹配行结束符。
Col1 | Col2 |
---|---|
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | tab |
\d | [0-9] |
\D | [^0-9] |
\s | 空白字符:空格,tab,回车,换行,换页 |
\S | 非空白字符 |
\w | 单词字符 [0-9A-Z_a-z] |
\W | 非单词字符 |
\1 | 指向分组1所捕获到的文本的引用 |
分组
捕获型
是一个被包在圆括号中的正则表达式。
非捕获型
有前缀(?:
。仅做简单匹配,并不捕获所匹配文本。即它只进行匹配,并不保存结果供以后引用。“微弱的性能优势”。veda文章
向前正向匹配
有前缀(?=)
。类似于非捕获型分组,匹配后,文本将倒回到它开始的地方,实际上并不匹配任何东西。是个好特性。
veda文章
向前负向匹配
有前缀(?!)
。类似于向前正向匹配分组。只有当它匹配失败时它才进行匹配。
量词
?
等同于 {0, 1}
*
等同于 {0, }
+
等同于 {1, }
如果只有一个量词,则趋向于进行贪婪性匹配:匹配尽可能多的重复直至到达上限。
Chap-8 方法
Array
array.concat(item...)
与array.push()
都可以接收多个参数,不同之处在于:前者返回新数组,后者改变原数组。array.join(seperator)
返回字符串array.pop()
和array.shift()
:分别移除并返回最后一个和第一个array.slice(start, end)
浅复制array.sort(comparefn)
:
不带参数默认视作字符串排序。自定义比较函数,将数字按照从小到大排序:
构造一个给对象数组排序的比较函数(面试有被问到过):
修改参数,让其可接收第二个参数:当主要键值相同时,使用次要参数比较。
array.splice(start, deleteCount, item)
返回被移除元素的数组array.unshift(item...)
在数组首部插入元素,返回新数组的长度。
Function
Function.apply(thisArg, thisArray);
Number
number.toExponential(fragtionDigits)
将数字转换成指数形式的字符串,参数为小数点后的数字位数(0~20)。number.toFixed(fractionDigits)
将数字转换成十进制形式的字符串,参数为小数点后的数字位数(0~20)。number.toPrecision(precision)
将数字转换成十进制形式的字符串,参数为有效位数(0~21)。number.toString(radix)
将数字转换成字符串,参数为基数。不指定基数也可以写成String(number)
。
Object
object.hasOwnProperty()
RegExp
regexp.exec(string)
使用正则表达式最强大和最慢的方法。
匹配成功返回数组:[子字符串, 分组1捕获的文本, 分组2捕获的文本, ...]
匹配失败返回null
如果设置了全局标志/g
,查找会从regexp.lastIndex
开始。如果匹配成功,lastIndex
为匹配字符的末字符,如果匹配失败,regexp.lastIndex
为0。
如果在一个循环中调用exec
去查询一个匹配模式在一个字符串中发生几次,注意如果提前退出了循环,再次进入该循环前必须把regexp.lastIndex
置0.
regexp.test(string)
使用正则表达式最简单和最快的方法。不要对这个方法使用g
标志。返回true/false
String
string.charAt(pos)
返回类型为字符串string.charCodeAt(pos)
返回字符的ASCII码string.concat(string...)
可接收多个参数string.indexOf(searchString, pos)
从searchString
开始找string.lastIndexOf(searchString, pos)
string.match(regexp)
若有g
标志,则返回一个数组,包含除捕获分组之外的所有匹配分组。(?)string.replace(searchValue, replaceValue)
:如果searchValue
是个字符串或是个没有g
标志的正则表达式,则只会在第一次出现的地方被替换。
- 如果
replaceValue
字符串中若含$
,表示特殊含义:
特殊符号 | 替换对象 |
---|---|
$$ | $ |
$& | 整个匹配的文本 |
$number | 分组捕获的文本 |
$` | 匹配之前的文本 |
$’ | 匹配之后的文本 |
|
|
replaceValue
也可以是个函数:123456789101112131415161718192021String.method = function(name, func){this.prototype[name] = func;return this;};console.log(String.method); //[Function]String.method('entityify', function(){var character = {'<': '<','>': '>','&': '&','"': 'quot;'};return function(){return this.replace(/[<>&"]/g, function(c){return character[c];});};}());console.log("<&>".entityify()); //<&>
string.search(regexp)
:只接收正则表达式作为参数,不接受字符串。会忽略g
标志。返回第一个匹配的首字符位置,没找到返回-1。string.slice(start, end)
若参数为负,自动加上字符串长度。最好不用sutstring
string.split(seperator, limit)
:将字符串分割为字符串数组。seperator
是字符串或正则表达式,若为空字符串,则返回单字符数组。忽略g
标志。limit
可选。
如果正则表达式中有捕获分组,则捕获分组文本会被包含在分割后的数组中:
IE会省略输出数组中的空字符串。
string.toLowerCase()
, string.toUpperCase()
string.fromCharCode(char...)
从一串数字字符码中返回一个字符串