第三章 基本概念
3.1 语法
- ECMAScript标识符一般采用驼峰大小写格式,也就是第一个字母小写,剩下的每个单词首字母大写
3.3 变量
- 在严格模式下,不能定义名为eval或arguments的变量,否则会导致语法错误。
3.4 数据结构
- ECMAScript中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number、String。还有一个种复杂数据类型–Object
- typeof 操作符的返回值
- “function” —— 如果这个值是函数。
从技术角度讲,函数在 ECMAScript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过 typeof 操作符来区分函数和其他对象是有必要的。 - “object” ——如果这个值是对象或 null,
因为null被认为是一个空对象的引用 - Chrome 7 及之前版本对正则表达式应用 typeof 会返回 “function” 。在IE 和 Firefox中,对正则表达式应用 typeof 会返回 “object”
- “function” —— 如果这个值是函数。
- typeof 是一个操作符而不是函数,所以后面不必加括号如typeof()
3.4.2 Undefine类型
- 对未初始化和未声明的变量执行 typeof 操作符都返回了 undefined 值
3.4.3 Null类型
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值。这样一来,只要直接检查 null 值就可以知道相应的变量是否已经保存了一个对象的引用
|
|
3.4.5 Number类型
- 要将一个值转换为其对应的 Boolean 值,可以调用转型函数 Boolean()
- 永远不要测试某个特定的浮点数值。
- 任何竖直除以非数值会返回NaN,因此不会影响其他代码的执行。
- 数值转换
- Number()
- 如果是 undefined ,返回 NaN
- 如果是 null 值,返回 0
- 如果字符串中包含有效的十六进制格式,例如 “0xf” ,则将其转换为相同大小的十进制整数
- 如果字符串是空的(不包含任何字符),则将其转换为 0,而parseInt()则返回NaN
|
|
- ParseInt()
parseInt() 函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号, parseInt()就会返回 NaN ;也就是说,用 parseInt() 转换空字符串会返回 NaN ( Number() 对空字符返回 0)。如果第一个字符是数字字符, parseInt() 会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符
|
|
为了消除在使用 parseInt() 函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(即多少进制)
|
|
实际上,如果指定了 16 作为第二个参数,字符串可以不带前面的 “0x” 。
- parseFloat()
|
|
3.4.6 String类型
- toString()和String()方法的不同
null和undefined没有toString()方法,在不知道要转换的值是不是 null 或 undefined 的情况下,可以使用转型函数 String()。
在调用数值的 toString() 方法时,可以传递一个参数:输出数值的基数。默认情况下, toString() 方法以十进制格式返回数值的字符串表示。而通过传递基数, toString() 可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值
|
|
|
|
3.4.7 Object类型
Object 的每个实例都具有下列属性和方法
constructor :保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是 Object() 。
hasOwnProperty(propertyName) :用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名( propertyName )必须以字符串形式指定(例如: o.hasOwnProperty(“name”) )。
isPrototypeOf(object) :用于检查传入的对象是否是传入对象的原型。
propertyIsEnumerable(propertyName) :用于检查给定的属性是否能够使用 for-in 语句,与 hasOwnProperty() 方法一样,作为参数的属性名必须以字符串形式指定。
toString() :返回对象的字符串表示
valueOf() :返回对象的字符串、数值或布尔值表示。通常与 toString() 方法的返回值相同
3.5 操作符
一元加减操作符
一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响,不过,在对非数值应用一元加操作符时,该操作符会像 Number() 转型函数一样对这个值执行转换。按位异或 ^
- 左移 <<
- 有符号右移 >>
- 无符号右移 >>>
- 逻辑非!
逻辑非操作符首先会将它的操作数转换为一个布尔值,然后再
对其求反 - 逻辑与&&(短路操作)
- 如果第一个操作数是对象,则返回第二个操作数
- 如果第二个操作数是对象,则只有在第一个操作数的求值结果为 true 的情况下才会返回该对象
- 如果有一个操作数是 null ,则返回 null
- 如果有一个操作数是 NaN ,则返回 NaN
- 如果有一个操作数是 undefined ,则返回 undefined
- 即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。对于逻辑与操作而言,如果第一个操作数是 false ,则无论第二个操作数是什么值,结果都不再可能是true
- 逻辑或||
- 逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为true ,就不会对第二个操作数求值了
- 我们可以利用逻辑或的这一行为来避免为变量赋 null 或 undefined 值
|
|
- 减法
如果有一个操作数是字符串、布尔值、 null 或 undefined ,则先在后台调用 Number() 函数将其转换为数值,然后再根据前面的规则执行减法计算。如果转换的结果是 NaN ,则减法的结果就是 NaN
|
|
第四章 变量、作用域和内存问题
4.1 基本类型和引用类型的值
- ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象
- 当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量
|
|
- 参数只能按值传递,不管是基本类型的值还是引用类型的值12345678function setName(obj) {obj.name = "Nicholas";obj = new Object();obj.name = "Greg";}var person = new Object();setName(person);alert(person.name); //"Nicholas"
实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
- 检测类型 instanceof
虽然在检测基本数据类型时 typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为ECMAScript
提供了 instanceof 操作符
|
|
第5章 引用类型
5.1 Object类型
- 创建Object类型的两种方法
- 使用new操作符后跟Object构造函数
- 对象字面量
对象字面量也是向函数传递大量可选参数的首选方式
|
|
- 访问对象属性的两种方法
- 点表示法,最通用
- 方括号表示法
属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法
|
|
5.2 Array类型
- 数组的length属性可读可写的。
|
|
- 检测数组
- instanceof
- Array.isArray()
|
|
- 转换方法
- toString()
|
|
由于alert() 要接收字符串参数,所以它会在后台调用 toString() 方法,由此会得到与直接调用 toString() 方法相同的结果
- join()
|
|
- 栈方法:后进先出
- push()
返回的是数组的新长度
|
|
- pop()
返回的是移除的项
|
|
- 队列方法:先进先出
- shift()
移除数组中的第一个项并返回该项 - unshift()
在数组前端添加任意个项并返回新数组的长度
- 重排序方法
reverse()
123var values = [1, 2, 3, 4, 5];values.reverse();alert(values); //5,4,3,2,1sort()
12345678910111213//正确的排序方法function compare(value1, value2) {if (value1 < value2) {return -1;} else if (value1 > value2) {return 1;} else {return 0;}}var values = [0, 1, 5, 10, 15];values.sort(compare);alert(values); //0,1,5,10,15
- 操作方法
- concat()
拼接数组 - slice()
slice() 方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下, slice() 方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。
|
|
- splice()
最强大的数组方法,可删除,插入,替换
删除
可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2) 会删除数组中的前两项插入
可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,”red”,”green”) 会从当前数组的位置 2 开始插入字符串 “red” 和 “green”替换
可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,”red”,”green”) 会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串”red” 和 “green”
|
|
- 位置方法
- indexOf()和lastIndexOf()
这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, indexOf() 方法从数组的开头(位置 0)开始向后查找, lastIndexOf() 方法则从数组的末尾开始向前查找,这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。
|
|
- reduce()和reduceRight()
reduce() 和 reduceRight() 的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象
|
|
5.4 Date类型
5.5 RegExp类型
- 可以使用RegExp()构造函数来创建RegExp对象,但更多时候正则表达式直接量定义为包含在一队斜杠(/)之间的字符,例如
|
|
- RegExp实例方法
- test()
- 基本语法:RegExpObject.test(str)
- param(参数) str是需要检测的字符串
- return (返回值) 如果字符串str中含有与RegExpObject匹配的文本的话,返回true,否则返回false
|
|
- exec()
- 基本语法:RegExpObject.exec(string)
- param(参数):string【必填项】要检索的字符串
- return(返回值):返回一个数组,存放匹配的结果,如果未找到匹配,则返回值为null
- 该返回的数组的第一个元素是与正则表达式相匹配的文本,该方法还返回2个属性,index属性声明的是匹配文本的第一个字符的位置;input属性则存放的是被检索的字符串string;该方法如果不是全局的话,返回的数组与match()方法返回的数组是相同的
|
|
|
|
上面一段代码,为什么matches[1]会是”and dad and baby”,后来参考了使用js中的exec()方法构造正则表达式验证这一篇博客,才知道,“在返回的数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)”这句话中的“捕获组匹配的字符串”是指模式汇总每一个()所定义的分组所匹配的字符,这也就不难解释为什么matches[1]是”and dad and baby”,matches[2]是“and baby”了。
关于index和lastINdex,可参考这篇segmentfault上的问答:js中,exec()方法,为何这里的index是5?,摘抄如下:
|
|
咱们按逗号之后不加空格来说:你看,第一次检索出来的是cat,第一个字符的索引是0,所以result.index是0,而此时一个检查字符串中每个字符的指针ptr已经走到t后面了,所以lastIndex是3,就是相当于告诉正则表达式,我们已经检查到索引为3的地方了,下一次直接从这里开始就好了。
第二次检索从lastIndex也就是3开始,找到后面的bat之后,匹配成功,bat里第一个字符b是4,所以result.index是4,而我们匹配完整个bat的时候,指针ptr已经走到bat后面了,也就是索引为7的位置,所以结果是4和7。这样你再开始下一次检索的时候,就会从text[7]开始,也就是第二个逗号的位置开始往后找,所以下一次匹配是8, sat, 11,再下次12, fat, 15。现在到头了,再匹配一次的话就是null了,这时候你看看pattern1.lastIndex,已经回到0了。
5.5 Function类型
- 函数是对象,每个函数都是Function类型的实例,函数名是一个指向函数对象的指针。
- 使用不带圆括号的函数名是访问函数指针,而非调用函数
- 函数声明与函数表达式的区别
|
|
- apply()方法和call()
call()和apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用。
要想以对象o的方法来调用函数f(),可以这样使用call()和apply()。
|
|
apply() 方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象,call() 方法与 apply() 方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call() 方法时,传递给函数的参数必须逐个列举出来
一般用来扩充函数赖以运行的作用域
|
|
- bind()方法
这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值1234567window.color = "red";var o = { color: "blue" };function sayColor(){alert(this.color);}var objectSayColor = sayColor.bind(o);objectSayColor(); //blue
5.6 基本包装类型
- 为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型: Boolean 、 Number 和String
- 每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据
|
|
处理过程如下:
- 创建 String 类型的一个实例
- 在实例上调用指定的方法
- 销毁这个实例
可以将以上三个步骤想象成是执行了下列 ECMAScript 代码
|
|
引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。来看下面的例子:
|
|
5.6.1 Boolean类型
- 由于 Boolean 对象是 Boolean 类型的实例,所以使用 instanceof操作符测试 Boolean 对象会返回 true ,而测试基本类型的布尔值则返回 false
- 理解基本类型的布尔值与 Boolean 对象之间的区别非常重要——当然,我们的建议是永远不要使用 Boolean 对象
5.6.2 Number类型
|
|
- Number 类型还提供了一些用于将数值格式化为字符串的方法
toFixed()
会按照指定的小数位返回数值的字符串表示12var num = 10;alert(num.toFixed(2)); //"10.00"toExponential()
方法返回以指数表示法(也称 e 表示法)表示的数值的字符串形式
|
|
3.toPrecision()
对于一个数值来说, toPrecision() 方法可能会返回固定大小(fixed)格式,也可能返回指数(exponential)格式;具体规则是看哪种格式最合适。这个方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)
|
|
5.6.3 String类型
String 类型的每个实例都有一个 length 属性,表示字符串中包含多个字符
|
|
- 字符方法
charAt()
charAt() 方法以单字符字符串的形式返回给定位置的那个字符12var stringValue = "hello world";alert(stringValue.charAt(1)); //"e"charCodeAt()
想得到的不是字符而是字符编码
|
|
- 字符串操作方法
这些方法都不会修改字符串本身的值,它们只是返回一个基本类型的字符串值
- concat()
用于将一或多个字符串拼接起来,返回拼接得到的新字符串,在实践中一般使用加号操作符来代替 - slice(),substring(),substr()的区别
首先,他们都接收两个参数,slice和substring接收的是起始位置和结束位置(不包括结束位置),而substr接收的则是起始位置和所要返回的字符串长度
|
|
substring是以两个参数中较小一个作为起始位置,较大的参数作为结束位置
|
|
接着,当接收的参数是负数时,slice会将它字符串的长度与对应的负数相加,结果作为参数;substr则仅仅是将负的第一个参数与字符串长度相加后的结果作为第一个参数,而将负的第二个参数转换为0;substring则干脆将负参数都直接转换为0
|
|
- 字符串位置方法
indexOf()和lastIndexOf()
参考数组的indexOf()和lastIndexOf()123var stringValue = "hello world";alert(stringValue.indexOf("o", 6)); //7alert(stringValue.lastIndexOf("o", 6)); //4trim()方法
这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果1234var stringValue = " hello world ";var trimmedStringValue = stringValue.trim();alert(stringValue); //" hello world "alert(trimmedStringValue); //"hello world"字符串大小写的转换写法
12345var stringValue = "hello world";alert(stringValue.toLocaleUpperCase()); //"HELLO WORLD"alert(stringValue.toUpperCase()); //"HELLO WORLD"alert(stringValue.toLocaleLowerCase()); //"hello world"alert(stringValue.toLowerCase()); //"hello world"
- 字符串的匹配模式
- match()
在字符串上调用match()方法,本质上与调用RegExp的exec()方法相同,match()只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。
|
|
- search()
这个方法的唯一参数与 match() 方法的参数相同:由字符串或 RegExp 对象指定的一个正则表达式。 search() 方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回 -1 。而且, search() 方法始终是从字符串开头向后查找模式。
|
|
replace()
这个方法接受两个参数:第一个参数可以是一个 RegExp 对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或者一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局( g )标志12345var text = "cat, bat, sat, fat";var result = text.replace("at", "ond");alert(result); //"cond, bat, sat, fat"result = text.replace(/at/g, "ond");alert(result); //"cond, bond, sond, fond"split()
这个方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象(这个方法不会将字符串看成正则表达式)。 split() 方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小
|
|
5.7 单体内置对象
5.7.1 Global对象
本书前面介绍过的那些函数,诸如 isNaN() 、 isFinite() 、parseInt() 以及 parseFloat() ,实际上全都是 Global对象的方法。除此之外, Global 对象还包含其他一些方法
- URI 编码方法
encodeURI() 和 encodeURIComponent() 方法,可以对 URI(Uniform ResourceIdentifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的 URI 中不能包含某些字符,例如空格。而这两个 URI 编码方法就可以对 URI 进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。
|
|
使用 encodeURI() 编码后的结果是除了空格之外的其他字符都原封不动,只有空格被替换成了%20 。而 encodeURIComponent() 方法则会使用对应的编码替换所有非字母数字字符。这也正是可以对整个 URI使用 encodeURI() ,而只能对附加在现有 URI后面的字符串使用 encodeURIComponent()的原因所在。
一 般 来 说 , 我 们 使 用 encodeURIComponent() 方 法 的 时 候 要 比 使 用encodeURI() 更多,因为在实践中更常见的是对查询字符串参数而不是对基础 URI进行编码。
与 encodeURI() 和 encodeURIComponent() 方法对应的两个方法分别是 decodeURI() 和decodeURIComponent() 。其中, decodeURI() 只能对使用 encodeURI() 替换的字符进行解码。
|
|
在第一次调用 decodeURI()输出的结果中,只有 %20 被替换成了空格。而在第二次调用 decodeURIComponent() 输出的结果中,所有特殊字符的编码都被替换成了原来的字符,得到了一个未经转义的字符串。
- eval()
没什么好说的
5.7.2 Math对象
|
|
- Math.ceil() 执行向上舍入,即它总是将数值向上舍入为最接近的整数;
- Math.floor() 执行向下舍入,即它总是将数值向下舍入为最接近的整数;
- Math.round() 执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。
- random() 方法
Math.random() 方法返回大于等于 0 小于 1 的一个随机数。如果你想选择一个 1到 10 之间的数值,可以像下面这样编写代码
|
|
面向对象的程序设计
6.2 创建对象
6.2.1 工厂模式
用函数来封装以特定接口创建对象的细节
|
|
- 优点
解决了创建多个相似对象的问题 - 缺点
没有解决对象识别的问题,1alert(person1 instanceof createPerson) //false
6.2.2 构造函数模式
|
|
- 构造函数名以大写开头
- 要创建Person实例,必须使用new 操作符,person1和person2分别保存着Person的一个不同的实例,
- 这两个对象都有一个 constructor (构造函数)属性,该属性指向 Person12alert(person1.constructor == Person); //truealert(person2.constructor == Person); //true
|
|
将构造函数当作函数调用
12345678910// 当作构造函数使用var person = new Person("Nicholas", 29, "Software Engineer");person.sayName(); //"Nicholas"// 作为普通函数调用Person("Greg", 27, "Doctor"); // 添加到 windowwindow.sayName(); //"Greg"// 在另一个对象的作用域中调用var o = new Object();Person.call(o, "Kristen", 25, "Nurse");o.sayName(); //"Kristen"存在的问题
不同实例上的同名函数是不相等的1alert(person1.sayName == person2.sayName); //false
可将sayName()函数转移到构造函数的外部
但这又使得全局作用域有点名不其实,只能被某个对象调用,而且如果对象需要定义很多方法,那么就要定义很多个全局函数。
6.2.3 原型模式
isPrototypeOf()
如果 [[Prototype]] 指向调用 isPrototypeOf() 方法的对象
( Person.prototype ),那么这个方法就返回 true12alert(Object.getPrototypeOf(person1) == Person.prototype); //truealert(Object.getPrototypeOf(person1).name); //"Nicholas"Object.getPrototypeOf()
返回[[Prototype]] 的值12alert(Object.getPrototypeOf(person1) == Person.prototype); //truealert(Object.getPrototypeOf(person1).name); //"Nicholas"hasOwnProperty()
可以检测一个属性是存在于实例中,还是存在于原型中- Object.keys()
可取得对象上所有课枚举的实例属性,这个方法接受一个对象作为参数
6.2.4 组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。
6.2.5 动态原型模式
|
|
6.2.6 寄生构造函数模式
形式上可看作是工厂模式+构造函数模式
关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式
6.2.7 稳妥构造函数模式
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this ;二是不使用 new 操作符调用构造函数
|
|
6.3 继承
构造函数,原型和实例的关系
每个构造函数都有一个原型对象prototype,原型对象都包含一个指向构造函数的指针constructor,而实例都包含一个指向原型对象的内容指针proto。
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString(),valueOf()等默认方法的根本原因。确定原型与实例的关系
instanceof操作符
只要用这个操作符来测试实例与原型链中出现过的构造函数,结果都会返回true。1altert(instance instanceof Object); //trueisPrototypeOf方法
只要是原型链中出现过的原型,都可以说是该原型链所派的实例的原型,因此isPrototypeOf()方法也会返回true1altert(Object.isPrototypeOf(instance)); //true