1. 基本数据
1.1 判断a是否为NaN
typeof a === 'number' && isNaN(a)
1.2 判断为false的值
- false
- null
- undefined
- 空字符串 ''
- 数字
NaN
注意:字符串"false"
和所有Object
均为true
2. 对象
2.1 对象的定义
对象是属性地容器,其中每个属性都拥有名字和值。
属性的名字可以是包括空字符串在内的任意字符,属性值可以是除了undefined
值之外的任何值。
JavaScript包含一种原型链的特性,允许对象继承另一个对象的属性。
2.2 原型
JS中每个对象都连接到一个原型对象,并且它可以从该原型对象上继承属性。所有通过对象字面量创建的对象都连接到Object.prototype。
当创建一个新对象时,你可以选择某个对象作为它的原型。使用Object.create( )
方法,这叫做原型继承。
举个栗子:
创建一个对象a,其原型对象为对象b,则
var a = Object.create(b);
- 原型连接只有在检索的时候才会被用到。如果我们尝试获取对象的某个属性值,但该对象没有此属性值,那么javascript会尝试着从原型对象中获取该属性值。
- 如果那个原型对象上也没有该属性值,那么再从它的原型对象中寻找,以此类推,直到该过程最后到达终点
Object.prototype
。- 如果想要的属性完全不在原型链中,那么结果就是undefined值。这个过程叫做委托。
注意:原型关系是一种动态关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。
2.3 减少全局变量污染
JavaScript可以很随意地定义全局变量来容纳你的应用的所有资源。遗憾的是,全局变量削弱了程序的灵活性,应该避免使用。
减少全局变量污染的方法有: 命名空间 和 模块化编程,后者更好。
3. 函数
3.1 函数对象
JS中的函数就是对象。JS中,对象是“名/值”对的集合并拥有一个连接到原型对象的隐藏连。函数对象连接到Function.prototype
,该对象又连接到Object.prototype
。
每个函数对象在创建时,包含一个prototype
属性。它的值是一个拥有constructor
属性且值即为该函数的对象。
函数是对象,所以他们可以像任何其他值一样被使用。函数可以保存在变量
,对象
和数组
中。函数可以被当做参数传递给其他函数,函数也可以再返回函数。并且,因为函数是对象,所以函数可以拥有属性和方法。
3.2 函数的调用
调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数。除了声明时定义的形式参数,每个函数还接收两个额外的参数,this
和 arguments
。
参数this
在面向对象编程中非常重要,它的值取决于调用模式。换句话说,this
直接指向该函数的拥有者,与函数的 执行上下文 密切相关。
JS中函数函数的调用形式一共有四种:
- 方法调用模式
当一个函数被保存为一个对象的属性时,我们称它为一个方法。this
指向被绑定到该对象。
var myObject = {
...
method: function() {
// 函数体
};
};
myObject.method( );
- 函数调用模式
当一个函数并非一个对象属性时,那么它就是被当做一个函数来调用的。this
绑定到全局对象上的。这种this
的绑定规则是JS中的一个缺陷,需要特别注意
var sum = add(3,4);
- 构造器调用模式
JS是一门基于原型继承的语言,这意味着对象可以直接从其他对象继承属性
。
当今大部分语言都是基于类的语言。尽管原型继承极富表现力,但它并未被广泛理解。因此,JS提供了一套和基于类的语言类似的对象构建语法,而借鉴类型化语言的语法模糊了这门语言真实的原型本质。
如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this
会被绑定到那个新对象上。
new前缀也会改变return语句的行为。 - apply调用模式
JS是一门函数式的面向对象编程的语言,所以函数可以拥有方法。
apply方法接收两个参数,第一个是要绑定给this
的值,第二个就是一个参数数组。
3.3 参数
arguments
是一个类数组
数据,它不是一个真正的数组,除了拥有一个length
属性,他没有任何数组方法。
3.4 return
return
语句可以用来让函数提前返回。当return
被执行时,函数立即返回而不再执行余下的语句。
一个函数总会返回一个值。如果没有指定返回值,则返回undefined
如果函数调用时在前面加上一个 new
前缀,且返回值不是一个对象,则返回this
,也就是该新对象。
3.4 递归
递归函数就是直接或间接调用自身的函数。递归是一种强大的编程技术,它把一个问题分解为了一组相似的子问题,每一个都用一个寻常解去解决。一个递归函数调用自身去解决它的子问题。
递归函数可以非常高效地操作树形结构,比如DOM
。每次递归调用时处理指定的树的一小段。
var walk_the_DOM = function walk(node, func) {
func(node);
node = node.firstChild;
while (node) {
walk(node, func);
node = node.nextSibling;
}
};
注意:
一些语言提供了 "尾递归" 优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,它可以显著提高速度。
遗憾的是,JS当前并没有提供尾递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。
JS中优化递归的方法,有使用循环替代 和 使用制表法
3.5 作用域链
一个函数的作用域链( Scope chain
)是在函数运行时,被创建的,但是,该作用域链其实在该函数被声明时,就已经确定了。
一个函数被声明时,函数会产生一个内部属性 [[Scope]]
,该内部属性存放了一个指针链表,依次指向了该函数的外部函数的变量对象,直到全局变量对象 Global Object
。
当函数被调用时,会创建一个执行上下文( execution context
),执行上下文定义了函数执行时的环境。每个执行上下文都有自己的作用域链( Scope Chain
),用于标识符解析。
当执行上下文创建时,而它的作用域链( Scope Chain
)初始化为当前运行函数的内部属性 [[Scope]]
所包含的变量对象。 此时,该函数的变量对象会被创建,叫做 活动对象( Active Object
),活动对象会被放到初始化后的作用域链的最前端,生成最终的作用域链。
- 自己总结:
Scope Chain = [[Scope]] + Active Object
3.6 闭包
作用域的好处是内部函数可以访问定义他们的外部函数的参数和变量( 除了this
和 arguments
)。由于外部存在对内部函数的引用,所以内部函数拥有比它的外部函数更长的生命周期,内部函数的作用域链引用着外部函数的变量对象,这就形成了闭包。换句话说,内部函数可以访问它被创建时所处的上下文环境。
需要注意:
内部函数能访问外部函数的实际变量而无需复制。
介于此,有时需要在闭包函数外部创建一个辅助函数
3.7 模块
可以使用 函数 和 闭包 构造模块。
模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数产生模块,我们几乎可以摒弃全局变量的使用,从而缓解这个JS的最为糟糕的特性之一所带来的影响。
模块模式的一般形式:
- 一个定义了私有变量和函数的函数
- 利用闭包创建可以访问私有变量和函数的特权函数
- 最后返回这个特权函数,或者把它们保存到一个可访问到的地方
3.8 级联
有一些方法没有返回值。例如,一些设置或修改对象的某个状态却不返回任何值的方法就是典型的例子。如果我们让这些方法返回 this
而不是 undefined
,就可以启动级联。在一个级联中,我们可以在单独一条语句中依次调用同一个对象的很多方法。
级联技术可以产生出极富表现力的接口。他也能给那波构造 "全能" 接口的热潮降温,一个接口没有必要一次做太多的事。
3.9 函数柯里化
柯里化允许我们把函数与传递给它的参数相结合,产生出一个新的函数。
Function.prototype.curry = function( ) {
var slice = Array.prototype.slice,
args = slice.call(arguments, 0),
that = this;
return function( ) {
var finalArgs = args.concat(slice.
call(arguments, 0));
return that.apply(null, finalArgs);
};
};
3.10 记忆(制表)
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称为记忆( memoization
)。JS的对象和数组要实现这种优化是非常方便的。
举个栗子:
4.继承
JS是一门弱类型语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。
JS是一门基于原型的语言,这意味着对象直接从其他对象继承。
4.1 伪类
它不直接让对象从其他对象继承,反而插入了一个多余的间接层:通过构造器函数产生对象。
分解new操作:
注意:
调用构造函数时,如果没有使用new操作符,那么this将会被绑定到全局对象上,所以你不但没有扩充新对象,反而破坏了全局变量环境,那就糟透了。
引入 安全构造函数模式来克服上面出现的问题
在基于类的语言中,类继承是代码重用的唯一方式。而JS有着更多且更好的选择。
4.2 对象说明符
有时候,构造器接受一大串参数。这可能令人烦恼,因为记住参数的顺序非常困难。这种情况下,如果我们在编写构造器时让它接受一个简单的对象说明符,可能会更加友好。
多个参数可以按任何顺序排列,如果构造器聪明的使用默认值,一些参数可以忽略掉,并且代码也更加容易阅读。
4.3 原型
在一个纯粹的原型模式中,我们会摒弃类,转而专注于对象。基于原型的继承相比于基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。
你通过构造一个有用的对象开始,接着可以构造更多和那个对象类似的对象。
使用 Object.create( )
实现原型继承,一旦有了一个想要的对象,就可以使用这个方法,构造出更多的实例出来。
上面是一种 差异化继承,通过定义了一个新对象
myCat
,我们指明了它与所基于的基本对象
myMammal
的区别。
4.4 函数化
迄今为止,我们所看到的继承模式有一个弱点,就是无法保护隐私。对象的所有属性都是可见的。我们无法得到私有变量和私有函数。
使用模块模式来避免这个问题。我们从构造一个生成对象的函数开始。我们以小写字母开头来命名它,因为它不再需要使用new前缀。构造该函数可以分为以下4部:
- 创建一个新对象。你可以构造一个对象字面量,或者它可以和new前缀连用去调用一个构造器函数,或者它可以使用
Object.create( )
方法去构造一个已经存在的对象的新实例,再或者可以调用一个会返回一个对象的函数。- 有选择地定义私有实例变量和方法。这些就是函数中通过
var
语句定义的普通变量。- 给步骤1中产生的新对象扩充方法。这些方法拥有特权去访问参数,以及在第2步中通过
var
语句定义的变量。- 返回那个新对象
代码模板:
扩充that,为其添加特权方法时,建议采用以下两步:
- 使用
var
定义一个私有函数。 - 再将该私有函数分配给
that
。
这么做的好处: - 如果有其他方法想去直接调用
methodical
,它们可以之间调用methodical( )
而不是that.methodical( )
。 - 如果该实例被破坏或篡改,甚至
that.methodical
被替换掉,调用methodical
的方法同样会继续工作。
定义一个处理父类方法的方法:
使用举例:
函数化模式的好处:
- 相比于伪类模式不仅带来的工作更少,还让我们得到了更好的封装和信息隐藏,以及访问父类方法的能力。
- 用函数化的样式创建一个对象,并且该对象的所有方法都不使用
this
或that
,那么该对象就是持久性(durable
)的。一个持久性对象就是一个简单功能函数的集合。- 一个持久性对象不会被入侵。访问一个持久性对象时,除非有方法授权,否则攻击者不能访问对象的内部状态。
4.5 部件
我们可以从一套组件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件出力特性的函数。它会给对象添加一个on
方法,一个 fire
方法和一个私有的事件注册表对象 registery
:
我们可以在任何单独的对象上调用eventuality,授予它事件处理方法。我们 也可以赶在
that
被返回之前在一个构造函数中调用它。
使用部件方式,一个构造函数可以从一套部件中把对象组装出来。JS的弱类型在此是一个巨大的优势,因为我们无需花费精力去了解对象在类型系统中的继承关系。相反,我们只需要关注它们的个性特征。
如果我们想要eventuality访问该对象的私有状态,可以把**私有成员集my
**传递给它。
5. 数组
JS提供了一种拥有类数组特性的对象。把数组下标转变成字符串,用其作为属性。它的属性检索和更新方式与对象一模一样,只不过多一个可以用整数作为属性名的特性。数组有自己的字面量格式。数组也有一套非常有用的内置方法。
数组是对象,所以我们可以直接给一个单独的数组添加方法。如:
需要注意了:
由于total是字符串,不是整数,所以给数组增加一个total属性不会改变它的length。当属性名是整数时,数组才是最有用的,但是它们依旧是对象,并且对象可以接受任何字符串作为属性名。
由于数组索引和length属性之间错综复杂的关系,继承自Array是不能正常工作的。也就是说,Object.create( )方法用在数组是没有意义的,因为它产生了一个对象,而不是一个数组。产生的对象继承这个数组的值和方法,但是它没有那个特殊的length属性。
6. 正则表达式
正则表达式是一门简单语言的语法规范。它应用在一些方法中,对字符串中的信息实现查找,替换和提取操作。
以下的字符称为正则表达式元字符,在正则表达式中需要使用反斜杠( \ )使其字面化:
/ [ ] { } ( ) ^ $ . ? + * | \
如果拿不准的话,可以给任何特殊字符都添加一个\前缀来使其字面化。
一个未被转义的 . 会匹配除了结束符以外的任何字符。
以下是常用的正则表达式符号:
\d -> [0-9]
\s -> 空白符,是Unicode空白(whitespace
)符的一个不完全子集。
\w -> [0-9a-zA-Z_]
\1 -> 指向分组1所捕获的文本的一个引用,所以它能被再次匹配;依次类推,\2,\3,...
正则表达式分组:
( ):捕获型分组
(?:):非捕获型分组
(?=):向前正向匹配,类似非捕获型分组,但在这个组匹配后,文本会倒回到它开始的地方,实际上,并不匹配任何东西。
(?!):向前负向匹配,它类似于向前正向匹配分组,但只有当它匹配失败时,它才继续向前匹配。
正则表达式的字符转义:
字符内部(
[ ]
)的转义规则和正则表达式因子(元字符)/ /
的相比稍有不同。
/ - [ ] \ ^
是在字符类中需要被转义的特殊字符
正则表达式量词:
- 正则表达式因子可以用一个正则表达式量词后缀来决定这个因子应该被匹配的次数。
- ?等同于{0,1},*等同于{0,},+则等同于{1,}
- 如果只有一个量词,表示趋向于进行贪婪性匹配,即匹配尽可能多的副本直至到达上限。如果这个量词附加一个后缀
?
,则表示趋向于进行非贪婪匹配,即只匹配必要的副本就好。- 最好坚持贪婪匹配
常用的正则表达式:
- email:/^(?:[\w-.]+)@(?:[\w-]+)(?:.\w{2,})$/
7. 方法
7.1 Array
- array.slice(start,end)
[start, end)。如果两个参数中任何一个是负数,array.length会和它们相加,试图让它们成为非负数。如果start大于等于array.length,得到的结果将会是一个新的空数组。 - array.splice(start, deleteCount, item...)
是数组中最强大的方法之一,可以对数组进行删除,添加和替换操作。
Array的迭代方法:
- array.forEach(function(
item
,index
,array
) {...})
对数组中每一项运行给定函数,这个方法没有返回值 - array.filter(... )
对数组中的每一项运行给定函数,返回true的项组成的数组 - array.map(... )
对数组中每一项运行给定函数,返回每次函数调用的结果组成的数组 - array.every(... )
- array.some(... )
Array的归并方法
ES5新增了两个归并数组的方法: reduce( )
和 reduceRight( )
。
reduce( )
是从数组的第一项开始,reduceRight( )
是从数组的最后一项开始。
这两个方法都接受两个参数: 一个在每一项上都调用的函数和(可选的)作为归并基础的初始值。调用的函数接收4个参数,前一个值,当前值,项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传递给下一项,第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数是数组的第二项。
7.2 Number
- number.toExponential(
fractionDigits
)
把number转换成指数形式的字符串。fractionDigits
控制小数点后面的数字位数,值必须在0-20。 - number.toFixed(
fractionDigits
)
把number转换成十进制形式的字符串。fractionDigits
控制小数点后面的数字位数,值必须在0-20,默认值为0。 - number.toPrecision(
precison
)
把number转换成十进制形式的字符串。precison
控制数字的精度。值必须在0-21 - number.toString(
radix
)
7.3 RegExp
- regexp.exec(
string
)
成功匹配regexp和string,它会返回一个数组。数组中下标为0的元素将包含正则表达式regexp匹配的子字符串。下标为1的元素是分组1捕获的文本,下标为2的元素是分组2捕获的文本,依次类推。如果匹配失败,会返回null。
如果regexp带有一个g标识符,查找不是从这个字符串的起始位置开始,而是从regexp.lastIndex(初始值为0)位置开始。 - regexp.test(
string
)
如果regexp匹配string,返回true;否则,返回false。
方法string.search(regexp)
匹配返回第一个匹配的首字符的位置,匹配失败,返回-1。 - RegExp.$1表示第一个捕获分组的文本,依次类推
7.4 String
- string.charAt(
pos
) - string.charCodeAt(
pos
) - string.indexOf(
searchString
,position
)
如果它被找到,返回第一个匹配字符的位置,否则返回-1。 - string.lastIndexOf(
searchString
,position
) - string.match(
regexp
)
如果没有g
标识符,那么string.match(regexp)
的结果和regexp.exec(string)
的结果相同。
如果regexp带有g
标识符,那么它生成一个包含所有匹配(除捕获分组之外)的数组 - sring.replace(
searchValue
,replaceValue
)!!!
replace方法对string进行查找和替换操作,并返回一个新的字符串。参数searchValue
可以是一个字符串或一个正则表达式。如果searchValue
是一个字符串,那么只会在第一次出现的地方被替换;如果searchValue
是一个正则表达式并带有g
,会替换所有匹配。
replaceValue
可以是一个字符串或一个函数,如果replaceValue
是字符串,那么字符$
就拥有特别的含义,其中常用的有$1
表示第一个捕获分组的文本,依次类推。如果replaceValue
是函数,每遇到一次匹配,该函数就会被调用一次,而该函数返回的字符串会被用做替换文本。传递给这个函数的第一个参数是整个被匹配的文本,第二个参数是分组1的文本,下一个参数是分组2的文本,以此类推。 - string.search(
regexp
)
匹配成功返回第一个匹配的首字符的位置,匹配失败,返回-1。 - string.slice(
start
,end
)
slice( )方法复制string的一部分来构造一个新的字符串。如果start参数为负值,它将与string.length相加。常用,string.slice(-2)取string的后两个字符。 - string.split(
separator
,limit
);
可选参数limit
可以限制被分割的片段数量。separator
参数可以是一个字符串或一个正则表达式。 - string.toLowerCase( ) 和 string.toUpperCase( )
- string.toLocalUpperCase( ) 和 string.toLocalLowerCase( )
该规则主要用于土耳其语 - String.fromCharCode(
char...
)
String.fromCharCode(char...
)函数根据一串数字编码返回一个字符串
var a = String.fromCharCode(67, 97, 116);
// a 是 'Cat'