《JavaScript 高级程序设计》
语言:语法、操作符、数据类型、内置功能。
ECMA-262对象的属性和方法等行为,并不适用于JavaScript中的其他对象,如属于宿主对象(浏览器环境)的DOM对象、BOM对象,可能不完全继承。
语法
ECMAScript包含了所有基本的语法、操作符、数据类型以及完成基本的计算任务所必需的对象。
关键字和保留字
关键字
break,case,catch,continue,debugger*,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with
保留字
abstract,boolean,byte,char,class,const,debugger,double,enum,export,extends,final,float,goto,implements,import,int,interface,long,native,package,private,protected,public,short,static,super,synchronized,throws,transient,volatile
ECMA-262第5版,非严格模式保留字
class,const,ennum,export,extends,import,super
ECMA-262第5版,严格模式下对保留字的限制
implements,innterface,let*,package,private,protected,public,static,yield*
ECMA-262第5版,对
eval
和arguments
施加了限制
数据类型
ECMAScript变量是松散类型的,可保存任何类型数据。
var
定义变量是作用域中局部变量,函数结束即销毁;省略var
,字段成了全局变量。
ECMAScript有5种简单(基本)数据类型:Undefined、Null、Boolean、Number和String;还有1种复杂数据类型:Object,由名值对组成。不支持任何创建自定义类型的机制。
数据类型具有动态性!
typeof
操作符(不是函数,语法中圆括号不是必须),检测变量的数据类型:“undefined”值未定义;“boolean”布尔值;“string”字符串值;“number”数值;“object”值是对象或null;“function”值是函数。typeof(变量/字面量)
Undefined类型
只有一个值:undefined
(未初始化的变量);
Null类型
也只有一值:null
(空对象指针);
Boolean类型
两个字面值:true
和false
;
转型函数Boolean()
,可转换任何数据类型为布尔值。
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | “”(空字符串) |
Number | 任何非零数字值(包括无穷大) | 0和NaN |
Object | 任何对象 | null |
Undefined | n/a | undefined |
Number类型
整数、浮点数、NaN;数值字面量格式:十进制、八进制(严格模式无效)、十六进制。
方法:isFinite()
,isNaN()
,Number()
,parseInt()
,parseFloat()
。
Number()
函数转换规则
- Boolean,true和flase将分别转换为
1
和0
- 数字,简单传入和返回
- null,返回
0
- undefined,返回
NaN
- 字符串
- 只包含数字,忽略前导0,转换成十进制
- 包含有效的浮点格式,忽略前导0,转换对应的浮点数
- 包含有效的十六进制,转换为相同大小十进制整数值
- 空字符串,返回
0
- 除上述之外的字符,返回
NaN
- 对象,先调用
valueOf()
,应用前述规则转换返回的值,如果结果是NaN
,调用toString()
方法后再应用前述规则转换返回的字符串值
- Boolean,true和flase将分别转换为
parseInt()
函数转换规则
- 是否符合数值模式
- 忽略字符串前面空格,忽略前导0
- 第一个字符不是数字字符或负号,返回
NaN
- 第一个字符是数字字符,解析后续字符,止于非数字字符
- 空字符串,返回
NaN
- 消除JavaScript引擎中进制导致的问题,传入参数指定进制转换基数
parseFloat()
函数转换规则
- 始终忽略前导0,十六进制格式字符格式始终转换为0
- 只解析十进制,无第二个参数指定转换基数
- 字符串解析为整数,无小数点,或小数点后为0,返回整数
- 类似
parseInt()
的是解析止于无效浮点数值格式,忽略后面字符串
String类型
- 特殊字面量,转义序列,
\n \r \t \\
… - 数值、布尔值、对象和字符串值有
toString()
方法 - null 和 undefined 没有
toString()
方法 - 转型函数
String()
,可转换任何数据类型为字符串,null 和 undefined 返回值的字面量‘null’和‘undefined’
Object类型
其所具有的任何属性和方法也同样存在于更具体的对象中,Object类型是所有它的实例的基础。
constructor
保存着用于创建当前对象的函数hasOwnProperty(propertyName)
检查给定的属性在当前实例对象中(而不是在实例的原型中)是否存在isPrototypeOf(object)
用于检查传入的对象是否是当前对象的原型propertyIsEnumerable(propertyName)
用于检查给定的属性是否能够使用for-in语句来枚举toLocaleString()
返回对象的字符串表示,该字符串与执行环境的地区对应toString()
返回对象的字符串表示valueOf()
返回对象的字符串、数值或布尔值表示。
操作符
ECMAScript操作符与众不同,能够适用很多值,如字符串、数字值、布尔值,甚至对象;应用于对象时,操作符会调用对象的valueOf()
和(或)toString()
方法,以便取得可操作的值。
一元操作符:
++ --
- 前置型递增和递减操作符,变量值在语句被求值之前执行
- 后置型递增和递减操作符,变量值在语句被求值之后执行
- 可用于整数、字符串、布尔值、浮点型数值和对象,将非数值变量变成数值变量
- 有效数字字符的字符串,先转换为数值,再执行加减1
- 不包含有效数字字符的字符串,转换为NaN
- 布尔值 false ,先转换为0,再执行加减1
- 布尔值 true ,先转换为1,再执行加减1
- 浮点值,加减1
- 对象,先调用
valueOf()
以取得一个可供操作的值,应用前述规则,如果结果是NaN,调用toString()
方法后再应用前述规则
一元加和减操作符
+ -
对非数值应用一元加减操作符时,像Number()
转型函数对值执行转换。
位操作符
ECMAScript所有数值以IEEE-754 64位格式存储;
位操作符,先将64位数值转换成32位数值;
前31位用于表示整数的值,第32位(符号位)值表示数值符号,0正,1负;
正数,二进制原码;
负数,二进制补码(先解出负数绝对值的原码 - 0和1对换解出反码 - 反码加1解出补码);
执行位操作,再将32位的数值转换成64位数值。
按位非 NOT
~
返回数值反码按位与 AND
&
将两个数值对齐
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
- 按位或 OR |
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
- 按位异或 XOR ^
第一个数值的位 | 第二个数值的位 | 结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
- 左移 <<
左移不影响操作数的符号位,空位以0
填充
有符号的右移
>>
保留符号位,空位以符号位的值
填充无符号的右移
>>>
所有32位右移,空位以0
填充
布尔操作符
- 逻辑非 NOT
!
可应用于ECMAScript中任何数据类型的值;
先将操作数转换为布尔值,再对其求反;
使用两个逻辑非操作符!!
,结果与Boolean()
转型函数效果一样。
操作数 | 返回值 |
---|---|
!对象 | false |
!空字符串 | true |
!非空字符串 | false |
!数值0 | true |
!非零数值(Infinity) | false |
!null | true |
!NaN | true |
!undefined | true |
- 逻辑与 AND &&
var result = true && false;
真值表
第一个操作数 | 第二个操作数 | 结果 |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
逻辑与&&
可以用于任何类型的操作数,而不仅仅是布尔值;
在有一个操作数不是布尔值的情况下,逻辑与&&
就不一定返回布尔值。
操作数 | 返回值 |
---|---|
第一个是对象 | 第二个 |
第二个是对象 | 仅在第一个求值结果为true 时返回第二个 |
两个都是对象 | 第二个 |
有一个是null | null |
有一个是NaN | NaN |
有一个是undefined | undefined |
逻辑与&&
属于短路操作,如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。
// 第一个操作数为false,不再对第二个操作数求值,即使没有定义也不会报错
// 但把foo设置为true,则会报错“ReferenceError: undefinedVariable is not defined”
// 不能在 逻辑与&& 操作中使用未定义的值;将 foo 设置为 false 则不会发生错误
var foo = false;
var bar = (foo && undefinedVariable);
console.log(bar); // flase
- 逻辑或 OR
||
var result = true || false;
真值表
第一个操作数 | 第二个操作数 | 结果 |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
如果有一个操作数不是布尔值,逻辑或||
也不一定返回布尔值。
操作数 | 返回值 |
---|---|
第一个是对象 | 第一个 |
第一个求值结果为false 时 | 返回第二个 |
两个都是对象 | 第一个 |
两个都是null | null |
两个都是NaN | NaN |
两个都是undefined | undefined |
逻辑或||
也属于短路操作,如果第一个操作数求值结果为true
,就不会再对第二个操作数求值。
// 第一个操作数为true,不再对第二个操作数求值,即使没有定义也不会报错
// 但把foo设置为false,则会报错“ReferenceError: undefinedVariable is not defined”
var foo = true;
var bar = (foo && undefinedVariable);
console.log(bar); //true
利用逻辑或||
这一行为,来避免为变量赋null
或undefined
值。
// 如果 preferredObject 不是 null,则其值将赋给变量
// 如果是 null,则将备用值 backupObject 赋给变量
var myObject = preferredObject || backupObject ;
乘性操作符
- 乘法
*
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 常规(超出ECMAScript数值范围返回Infinity/-Infinity) |
NaN | * | NaN |
Infinity | 0 | NaN |
Infinity | 非0 | Infinity/-Infinity |
Number(非数值) | * | 以上各规则运算 |
- 除法/
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 常规(超出ECMAScript数值范围返回Infinity/-Infinity) |
NaN | * | NaN |
Infinity | Infinity | NaN |
0 | 0 | NaN |
非0 | 0 | Infinity/-Infinity |
Infinity | 非0 | Infinity/-Infinity |
Number(非数值) | * | 以上各规则运算 |
- 求模 %
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 常规 |
Infinity/-Infinity | 有限大数值 | NaN |
有限大数值 | 0 | NaN |
Infinity/-Infinity | Infinity/-Infinity | NaN |
有限大数值 | Infinity/-Infinity | 被除数 |
0 | * | 0 |
Number(非数值) | * | 以上各规则运算 |
加性操作符
- 加法
+
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 常规 |
NaN | * | NaN |
Infinity | Infinity | Infinity |
-Infinity | -Infinity | -Infinity |
Infinity | -Infinity | NaN |
+0 | +0 | +0 |
-0 | -0 | -0 |
+0 | -0 | +0 |
字符串 | 字符串 | 拼接字符串 |
字符串 | 对象/数值/布尔值 toString() | 拼接字符串 |
字符串 | String(null/undefined) | 拼接字符串 |
- 减法 -
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 常规 |
NaN | * | NaN |
Infinity | Infinity | NaN |
-Infinity | -Infinity | NaN |
Infinity | -Infinity | Infinity |
-Infinity | Infinity | -Infinity |
+0 | +0 | +0 |
-0 | -0 | +0 |
+0 | -0 | -0 |
Number(字符串/布尔值/null/undefined) | * | 以上各规则运算 |
Number(对象 valueOf()/toString()) | * | 以上各规则运算 |
关系操作符
小于 <
、大于 >
、小于等于 <=
、大于等于>=
,对两个值进行比较,返回一个布尔值。
操作数 | 操作数 | 结果 |
---|---|---|
Number | Number | 数值比较 |
字符串 | 字符串 | 字符编码值 |
Number(字符串/布尔值/null/undefined) | *(不能同时为字符串) | 数值比较 |
Number(对象 valueOf()/toString()) | * | 数值比较 |
相等操作符
相等==
、不相等!=
先转换再比较
操作数 | 操作数/比较 |
---|---|
Number(布尔值) | * |
Number(字符串) | Number |
对象 valueOf()/toString() | * |
两个对象 | 是否指向同一对象 |
操作 | 结果 |
---|---|
null == undefined | true |
NaN != NaN | true |
NaN == NaN | false |
false == 0 | true |
true == 1 | true |
全等===
、不全等!==
仅比较不转换
条件操作符
variable=boolean_expression ? true_value : false_value;
赋值操作符
等号 =
;复合赋值操作符,*=、/=、%=、+=、-=、<<=、>>=、>>>=
。
逗号操作符
,
,一条语句执行多个操作。
语句
if语句
if (condition1) statement1 else if (condition2) statement2 else statement3
condition
(条件)可以是任意表达式,ECMAScript会自动调用Boolean()
转换函数将这个表达式的结果转换为一个布尔值。
do-while语句
do { statement } while (expression)
do-while
语句是后测试循环语句,在循环体中的代码块执行之后,才会测试出口条件。
while语句
while (expression) { statement }
while
语句是前测试循环语句,在循环体内的代码被执行之前,就会对出口条件求值。
for语句
for (initialization; expression; post-loop-expression) statement
for
语句也是一种前测试循环语句,具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。
ECMAScript不存在块级作用域,循环内部定义的变量,也可以在外部访问。
for-in语句
for (property in expression) statement
for-in语句是一种精准迭代语句,可用来枚举对象的属性。
如果迭代的对象的变量值为null
或undefined
,for-in
语句会抛出错误,ECMAScript5中不再抛出错误,只是不执行循环体,使用for-in
循环之前,先检测确认该对象的值不是null
或undefined
.
for (var propName in window){
// var保证使用局部变量
console.log(propName);
}
label语句
label:statement
label
语句可以在代码中添加标签,可由break
或continue
语句引用,与for
循环语句配合使用。
break和continue语句
break
或continue
用于循环中精确控制代码执行。
break
语句:立即退出循环,强制继续执行循环后面的语句;
continue
语句:立即退出循环,从循环的顶部继续执行。
with语句
with (expression) statement
将代码的作用域设置到特定对象中。
会导致性能降低,也会给代码调试造成困难。
严格模式下禁用with
语句,会视为语法错误。
switch语句
switch (expression){
case value: statement
break;
case value: statement
break;
default: statement
}
可以在switch
语句中使用任何数据类型,每个case
值可以是常量、变量、表达式。
函数
声明
function functionName([arg0, arg1, ... argN]){
statement
}
使用function
关键字来声明,后跟一组参数以及函数体;
return
实现函数的返回值。
参数
ECMAScript函数不限制传入参数个数、参数数据类型;
参数在内部用一组数组表示;
在函数体内可以通过arguments
对象来访问这个参数数组;
没有函数签名,解析器不验证命名参数;
arguments
对象的length
属性可获知参数个数;
arguments
对象的长度由传入的参数个数决定,不是由定义函数时的命名参数个数决定;
没有传递值的命名参数,自动被赋予undefined
值;
ECMAScript中的所有参数传递,都是值传递,不可能通过引用传递参数。
没有重载
在ECMAScript中定义了两个名字相同的函数,后定义的覆盖先定义的;
通过条件语句检查传入函数中的参数数据类型和个数,模仿方法的重载。
变量、作用域和内存问题
基本类型和引用类型值
Undefined、Null、Boolean、Number和String基本数据类型是按值(栈)访问,可操作保存在变量中的实际值。
引用类型的值是保存在内存中的对象(堆),JavaScript不允许直接访问内存中的位置,不能直接操作对象的内存空间。
当复制保存着对象的某个变量时,操作的是对象的引用;在为对象添加属性时,操作的是实际的对象。
包含引用类型值的变量实际上包含的不是对象本身,而是一个指向该对象的指针。
动态的属性
创建一个变量为该变量赋值;
引用类型的值,可以添加、改变和删除其属性和方法;
不能给基本类型的值添加属性;
复制变量值
从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值(副本),然后把该值复制到为新变量分配的位置上;
从一个变量向另一个变量复制引用类型的值,将存储在变量对象中的值复制一份放到为新变量分配的空间中,这个值的副本是一个指针,指针指向存储在堆中的一个对象。
传递参数
ECMAScript中的所有参数传递,都是按值传递。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量,即命名参数,arguments
对象中的一个元素;
在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部;
局部对象会在函数执行完毕之后立即销毁。
检测类型
typeof
操作符,检测基本类型:字符串、数值、布尔值、undefined;如果变量的值是一个对象或null,typeof
返回object
;
instanceof
操作符,检测引用类型;
result = variable instanceof constructor
执行环境及作用域
所有变量(包括基本类型和引用类型)都存在于一个执行环境(execution content)当中,也称作用域;
执行环境,决定了变量的生命周期,以及哪一部分代码可以访问其中的变量;
执行环境有全局执行环境和函数执行环境之分;
每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链(scope chain);
内部环境可以通过作用域链访问所有外部环境;
外部环境不能访问内部环境中的所有变量和函数;
变量的执行环境有助于确定应该何时释放内存;
每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象的内部属性[[Scope]]
中,我们编写的代码无法访问此对象,但解析器在处理数据时会在后台处理它。
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
每个执行环境中所有代码执行完毕,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出—如关闭网页或浏览器—时才会被销毁);
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
环境之间的联系是线性、有次序的,标识符解析向上搜索作用域链,以查询变量和函数名。
关键字:执行环境,变量对象,作用域链(有权有序访问访问变量和函数),标识符解析,执行流:推入、弹出,销毁;
全局执行环境是最外围的一个执行环境;根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window
对象,因此所有全局变量和函数都是作为window
对象的属性和方法创建的。
没有块级作用域(block scope),使用函数作用域(function scope)
类C语言中花括号封闭的代码块都有自己的作用域。
而ECMAScript,if
、for
语句中的变量声明会将变量添加到当前的执行环境,会存在语句外部的执行环境中。
var
声明的变量会自动被添加到最接近的环境中;没有使用var
初始化的变量会被添加到全局环境。
垃圾收集
标记清除(mark-and-sweep),变量进入环境,标记为“进入环境”,变量离开环境,标记为“离开环境”。
引用计数(reference counting)。
旧版本IE中有一部分非原生JavaScript对象,如BOM和DOM的对象是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现,垃圾回收机制采用引用计数策略,存在循环引用问题,内存泄露风险。
管理内存,优化内存占用最佳方式,为执行中的代码只保存必要的数据,一旦数据不再有用,最好通过将其值设置为null
来释放其引用,此谓解除引用(dereferencing),让值脱离执行环境,以便垃圾收集器下次运行时将其回收,适用于大多数全局变量和全局对象的属性。
引用类型
引用类型的值(对象)是引用类型的一个实例。在ECMAScript中引用类型是一种数据结构,用于将数据和功能组织在一起,描述的是一类对象所具有的属性和方法。
对象是某个特定引用类型的实例。新对象使用 new
操作符跟一个构造函数来创建。
Object类型
大多数引用类型值都是 Object 类型的实例。
new
操作符跟 Object 构造函数来创建
var objectVariable = new Object();
objectVariable.itemPropery = "properyContainer";
- 使用对象字面量表示法
var objectVariable = { itemPropery : properyContainer };
除非必须使用变量来访问属性,否则建议使用点表示法。
Array类型
ECMAScript数组与其他语言中的数组都是数据的有序列表;
ECMAScript数组的每一项可以保存任何类型的数据;
ECMAScript数组的大小是可以动态调整的,可以随着数据的添加自动增长以容纳新增数据。
- 使用 Array 构造函数来创建
var arrayVariable = new Object([arguments.length | arguments]);
- 使用对象字面量表示法
var arrayVariable = [arg0, arg1, arg2 , ... , argN];
在读取和设置数组的值时,使用
[]
并提供基于0的数字索引;设置某个值的索引超过了数组现有项数,数组就会自动增加到该索引值加1的长度;
数组的项数保存在其
length
属性中;length
属性可以返回数组长度,也可以设置此属性从数组的末尾移除项,或向数组中添加新项;length
属性可以方便地在数组末尾添加新项。
检测数组
ECMAScript3中检测数组if (value instanceof Array){ //对数组执行某些操作 }
instanceof
操作符的问题在于,它假定只有一个全局执行环境。
如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。
ECMAScript5新增了Array.isArray()
方法,确定某个值是不是数组,不管它是在哪个全局执行环境中创建的。
if (Array.isArray(value)){ //对数组执行某些操作 }
转换方法
所有对象具有 toLocaleString()
、toString()
和valueOf()
方法。
调用
toString()
方法,返回数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串;调用
valueOf()
方法,返回的还是数组;调用
toLocaleString()
方法返回与toString()
和valueOf()
方法相同的值。
数组继承的toLocaleString()
、toString()
和valueOf()
方法在默认操作下都会以逗号分隔的字符串的形式返回数组项;
使用join()
方法,可以使用不同的分隔符来构建这个字符串。
栈方法
ECMAScript数组提供了数组的行为类似于其他数据结构的方法,数组就表现得像栈一样,可以限制插入和删除项的数据结构。
栈,是一种LIFO(Last-In-First-Out)的数据结构,栈中项的插入(推入)和移除(弹出),只发生在一个位置——栈的顶部。
push()
,可以接收任意数量的参数,逐个添加到数组末尾,返回修改后数组的长度;pop()
,从数组末尾移除一项,减少数组的length
值,然后返回移除的项。
队列方法
队列数据结构的访问规则是FIFO(First-In-First-Out),队列在列表的末端添加新项,从列表的前端移除项。
push()
,向数组末端添加新项;shift()
,从数组前端移除一项,返回移除的项;
从相反的方向模拟队列:
unshift()
,从数组前端添加新项;pop()
,从数组末尾移除一项,返回移除的项;
IE7及更早版本,unshift()
方法返回undefined
而不是数组的新长度,IE8在非兼容模式下会返回正确的长度值
重排序方法
reverse()
,反转数组项的顺序;sort()
,按升序排列数组项,最小值于最前,最大值于最后,调用每个数组项的toString()
转型方法,比较字符串。
并不是最佳方案,可以接收一个(降序/升序)比较函数作为参数。
操作方法
concat()
,基于当前数组中的所有项创建一个新数组。先创建当前数组一个副本,然后将接收到的参数添加至此副本的末尾,最后返回新构建的数组;slice()
,基于当前数组中的一个或多个项创建一个新数组,不影响原始数组;方法可以接受一个或两个参数,即要返回项的起始和结束位置;- 一个参数,返回从该指定位置开始到当前数组末尾的所有项;
- 两个参数,返回起始和结束位置之间的项,但不包括结束位置的项;参数为负数,则用数组长度加上该数。
splice()
,操作数组中的项;方法始终都会返回一个包含从原始数组中删除的项的数组(未删除任何项则返回一个空数组);- 删除;删除任意数量的项,指定两个参数:要删除的第一项的位置,要删除项的个数;
- 插入;向指定位置插入任意数量的项,指定三个参数:起始位置,0(要删除项的个数),要插入的项;
- 替换;向指定位置插入任意数量的项,且同时删除任意数量的项,指定三个参数:起始位置,要删除的项数,要插入的任意数量的项;
位置方法
ECMAScript5为数组实例添加了两个位置方法:indexOf()
和lastIndexOf()
;
都接收两个参数:要查找的项和表示查找起点位置的索引(可选);
返回要查找的项在数组中的位置,没找到返回-1
;使用全等操作符===
比较。
indexOf()
,从数组的开头向后查找;lastIndexOf()
,和从数组的末尾向前查找。
IE9+支持
迭代方法
ECMAScript5为数组定义了5个迭代方法;
都接收两个参数:要在数组上的每一项上运行的函数和运行该函数的作用域对象——影响this
值(可选);
传入这些方法中的函数会接收3个参数:数组项的值、该项在数组中的位置、数组对象本身;
这些方法不会修改数组中的包含的值。
every()
,对数组中的每一项运行给定函数,如果该函数对每一项都返回true
,则返回true
;filter()
,对数组中的每一项运行给定函数,返回该函数会返回true
的项组成的数组;forEach()
,对数组中的每一项运行给定函数,这个方法没有返回值;map()
,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组;some()
,对数组中的每一项运行给定函数,如果该函数对任意(某)一项返回true
,则返回true
。
IE9+支持
归并方法
ECMAScript5新增两个归并数组的方法:reduce()
和reduceRight()
;都会迭代数组的所有项,然后构建一个最终返回的值;
都接收两个参数:一个在每一项上调用的函数和作为归并基础的初始值(可选);
传入这些方法中的函数都接收4个参数:前一个值、当前值、项的索引、数组对象;
函数返回的任何值都会作为第一个参数自动传给下一项;
第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。
reduce()
,从数组的第一项开始,逐个遍历到最后;reduceRight()
,从数组的最后一项开始,向前遍历到第一项。
IE9+支持
Date类型
Date类型使用UTC(Coordinated Universal Time);
不传递参数,对象自动获得当前日期和时间;
Date.parse()
,接收一个表示日期的字符串参数,返回相应的毫秒数,因地区而异;
Date.UTC()
,接收参数:年份,基于0的月份,月中的哪一天,基于0的小时数,分钟,秒,毫秒数;
Date.now()
,ECMAScript5,调用这个方法时的日期和时间的毫秒数;IE9+支持;
继承方法
toLocaleString()
、toString()
、valueOf()
;因浏览器而异;日期格式化方法
toDateString()
、toTimeString()
、toLocaleDateString()
、toLocaleTimeString()
、toUTCString()
;因浏览器而异;
日期/时间组件方法,设置或获取日期值中的特定部分的方法;
//日期的毫秒数,与 valueOf() 方法返回相同值
getTime();
setTime();
//四位数年份
getFullYear();
getUTCFullYear();
setFullYear();
setUTCFullYear();
//基于0的月数
getMonth();
getUTCMonth();
setMonth();
setUTCMonth();
//月份中的天数
getDate();
getUTCDate();
setDate();
setUTCDate();
//基于0的星期数
getDay();
getUTCDay();
setDay();
setUTCDay();
//基于0的小时数
getHours();
getUTCHours();
setHours();
setUTCHours();
//基于0的分钟数
getMinutes();
getUTCMinutes();
setMinutes();
setUTCMinutes();
//基于0的秒数
getSeconds();
getUTCSeconds();
setSeconds();
setUTCSeconds();
//毫秒数
getMilliseconds();
getUTCMilliseconds();
setMilliseconds();
setUTCMilliseconds();
//本地时间与UTC相差的分钟数
getTimezoneOffset();
RegExp
var expression = / pattern / flags ;
模式(pattern):字符类、限定类符、分组、向前查找及反向引用;
标志(flags):标明正则表达式的行为,g
(global)全局模式,i
(case-insensitive)大小写模式,m
(multiline)多行模式;
正则表达式中的元字符:( [ { \ ^ $ | ) ? * + . ] }
,元字符须\
转义;
可用字面量形式来定义;也可用 RegExp 构造函数,接收两个参数,一个是要匹配的字符串模式(某些情况下须双重转义),另一个是可选的标志字符串;
ECMAScript3中,正则表达式字面量始终会共享同一个 RegExp 实例,使用构造函数创建的每一个新 RegExp 实例都是一个新实例。
RegExp 实例属性
global
, 布尔值,表示是否设置了g
标志;ignoreCase
,布尔值,表示是否设置了i
标志;lastIndex
,整数,基于0,搜索下一个匹配项的字符位置;multiline
,布尔值,表示是否设置了m
标志;source
,正则表达式的字面量字符串表示。
RegExp 实例方法
exec()
接收一个参数,即应用模式的字符串,
返回包含第一个匹配项信息的数组,
没有匹配项返回null
,返回的数组是Array
的实例,
包含两个额外的属性:index
匹配项在字符串中的位置;input
应用正则表达式的字符串;
在数组中,第一项是整个模式匹配的字符串,其他项是模式中的捕获组匹配的字符串
即使在模式中设置了全局标志g
,它每次调用也只会返回一个匹配项;
在不设置全局标志情况下,同一个字符串上多次调用exec()
将始终返回第一个匹配项的信息;
而在设置全局标志情况下,每次调用exec()
都会在字符串中继续查找新匹配项。
test()
接收一个参数,返回布尔值,模式与该参数是否匹配;实例继承的方法
toLocaleString()
、toString()
,返回正则表达式的字面量;
RegExp 构造函数属性
适用于作用域中所有正则表达式,并且基于所执行的最近一次正则表达式而变化;
可通过长属性名和短属性名方式访问;
长属性名 | 短属性名 | 说明 |
---|---|---|
input | $_ | 最近一次要匹配的字符串 |
lastMatch | $& | 最近一次的匹配项 |
lastParen | $+ | 最近一次匹配的捕获组 |
leftContext | $` | input 字符串中lastMatch 之前的文本 |
multiline | $* | 布尔值,是否所有表达式都使用多行模式 |
rightContext | $’ | input 字符串中lastMatch 之后的文本 |
$1~9 | 捕获组中第1~9个匹配项 |
模式的局限性
不支持特性
- 匹配字符串开始和结尾的
\A
和\Z
锚 [1] - 向后查找(lookbehind) [2]
- 并集和交集类
- 原子组(atomic grouping)
- Unicode支持(单个字符除外,如\uFFFF)
- 命名的捕获组 [3]
- s(single,单行)和x(free-spacing,无间隔)匹配模式
- 条件匹配
- 正则表达式注释
支持特性
- [1] 支持以插入符号(^)和美元符号($)来匹配字符串的开始和结尾
- [2] 但完全支持向前查找(lookahead)
- [3] 但支持编号的捕获组
Function类型
函数是对象,函数名是指针;
每个函数都是Function
类型的实例,
与其它引用类型一样,具有属性和方法,函数名也是一个指向函数对象的指针;
函数声明语法定义:
function functionName([arg0, arg1, ... argN]){
statement
}
函数表达式语法定义:
var functionName = function ([arg0, arg1, ... argN]){
statement
};//这里有个分号哦
函数声明与函数表达式
解析器在向执行环境载数据时,率先读取函数声明;使其在执行任何代码之前可以访问;函数声明提升(function declaration hoisting);
至于表达式,须等到解析器执行到它所在的代码行,才会被解释执行;
作为值的函数
ECMAScript中函数名本身就是变量,函数可以作为值来使用;
可以像传递参数一样把一个函数传递给另一个函数;
可以将一个函数作为另一个函数的结果返回;
可以从一个函数中返回另一个函数;
访问函数的指针,而不执行函数,须去掉函数名后面的那对圆括号。
函数内部属性
函数内部,两个特殊对象:arguments
、this
;
arguments
,保存函数参数,此对象有callee
属性,该属性是一个指针,指向拥有这个arguments
对象的函数,严格模式下会导致错误;
this
,引用的是函数据以执行的环境对象;在全局作用域中调用函数,this
引用的是全局对象window
;把函数赋给对象,this
引用的是对象;
ECMAScript5规范定义了函数对象的属性:caller
,保存调用当前函数的函数的引用;松耦合可以通过arguments.callee.caller
来访问,在全局作用域中调用当前的函数,它的值为null
;
ECMAScript5定义了arguments.caller
属性,为了分清与函数的caller
属性,严格模式下导致错误,非严格模式属性是undefined
;
加强了语言的安全性,第三方代码不能在相同的环境里窥视其他代码;
函数属性和方法
函数包含两个属性
length
函数希望接收参数的个数prototype
保存了引用类型的所有实例方法,prototype
属性可用于创建自定义引用类型以及实现继承;
函数包含非继承而来的方法 apply()
,call()
,在特定的作用域中调用函数,等于设置函数体内this
对象的值;
扩充作用域,对象不需要与方法有任何耦合关系;
apply()
接收两个参数,一个是在其中运行函数的作用域,另一个是参数数组,也可以是Array
的实例,也可以是arguments
对象;call()
第一个参数与apply()
相同,第二个参数需要逐个列举出来;
严格模式下,未指定环境而调用函数,则this
值不会转型为window
,除非明确把函数添加到某个对象或者调用apply()
或call()
,否则this
值将是undefined
。
ECMAScript5定义了bind()
方法,创建一个函数的实例,其this
值会被绑定到传给bind()
函数的值;IE9+支持;
函数继承的toLocaleString()
和toString()
方法返回函数的代码,因浏览器而异;
基本包装类型
便于操作基本类型,ECMAScript提供了3个特殊的引用类型,Boolean
、Number
、String
;
每当读取一个基本类型值的时候,后台创建一个对应的基本包装类型的对象,以操作基本类型的数据;
引用类型与基本包装类型的主要区别就是对象的生存周期;
使用new
操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中;
而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即销毁;不能为基本类型值添加属性和方法;
可以显式的调用Boolean
、Number
、String
来创建基本包装类型的对象,在绝对必要的情况下这样做,因为这种做法分不清是在处理基本类型还是引用类型的值;
对基本包装类型的实例调用typeof
会返回object
;
所有基本包装类型的对象都会被转换为布尔值true
;
Object
构造函数根据传入值的类型返回相应基本包装类型的实例;
使用new
调用基本包装类型的构造函数,typeof
检测是Object
,直接调用同名的转型函数,typeof
是基本类型;
Boolean类型
基本类型的布尔值与Boolean
对象有区别;
Number类型
Number
类型重写了valueOf()
、toLocaleString()
、toString()
方法;
valueOf()
返回对象表示的基本类型的数值,toLocaleString()
和toString()
可传递表示基数的参数,返回字符串形式的值;
toFixed()
,按照指定的小数位返回数值的字符串表示,接收一个参数,数值所有数字的位数;
toExponential()
,返回以指数表示法表示的数值的字符串形式,接收一个参数,数值所有数字的位数;
toPrecision()
,表示某个数值的最合适格式,可能返回固定(fixed)大小格式,也可能返回指数(exponential)格式;接收一个参数,数值所有数字的位数;
String类型
继承的valueOf()
、toLocaleString()
、toString()
方法都返回对象所表示的基本字符串值;
length
属性
String
类型的实例字符串个数- 字符方法
charAt()
、charCodeAt()
ECMAScript5,stringValue[index]
,IE8+支持 字符串操作方法
concat()
、slice()
、substr()
、substring()
concat()
,合并字符串;
slice()
、substr()
、substring()
,接收一个或两个参数,第一个参数指定开始位置,slice()
、substring()
,第二个参数指定结束位置;substr()
,第二个参数指定返回的字符个数;- 没有第二个参数,将字符串长度作为结束位置;
slice()
,参数是负值,将负值与字符串长度相加;substr()
,第一个参数是负值,将负值与字符串长度相加;第二个参数是负值将转换为0;substring()
,所有负值将转换为0;
字符串位置方法
indexOf()
、lastIndexOf()
从字符串开关或末尾搜索- ECMAScript5
trim()
删除前置及后缀所有空格 - 字符串大小写转换方法
toLowerCase()
、toLocaleLowerCase()
、toUpperCase()
、toLocaleUpperCase()
字符串的模式匹配方法
match()
、search()
、replace()
、split()
match()
,捕获匹配模式;
接收一个参数,由字符串或RegExp
对象指定的正则表达式,返回一个数组;search()
,查找模式;
接收一个参数,由字符串或RegExp
对象指定的正则表达式,返回1
或-1
;split()
,分隔字符串
基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中;
接收两个参数,第一参数是是字符串或RegExp
对象,第二个可选参数用于指定数组的大小;
Steven Levithan: Javascript split bugs: Fixedreplace()
,替换字符串;
接收两个参数,第一个参数是字符串(不会转换成正则表达式)或RegExp
对象,第二个参数是字符串或函数;
- 第一个参数是字符串只会替换第一个子字符串,要替换所有子字符串,提供正则表达式指定全局
g
标志; - 第二个参数是函数
在只有一个匹配项,传递“模式匹配项、模式匹配项在字符串中的位置、原始字符串”3个参数;
在正则表达式中定义了多个捕获组情况下,传递函数的参数是“模式匹配项、第1个捕获组匹配项、第n个捕获组匹配项…模式匹配项在字符串中的位置、原始字符串”;
函数返回一个字符串,表示应该被替换的匹配项; - 第二个参数是字符串,使用特殊字符序列,将正则表达式操作得到的值插入到结果字符串;
- 第一个参数是字符串只会替换第一个子字符串,要替换所有子字符串,提供正则表达式指定全局
字符序列 替换文本 $$ $ $& 匹配整个模式的子字符串,与 RegExp.lastMatch
值相同$’ 匹配子字符串之前的子字符串,与 RegExp.leftContext
值相同$` 匹配子字符串之后的子字符串,与 RegExp.rightContext
值相同$n 匹配第n个捕获组的子字符串,如果正则表达式中没有定义捕获组,则使用空字符串 $nn 匹配第nn个捕获组的子字符串,如果正则表达式中没有定义捕获组,则使用空字符串 - 字符串的比较方法
localeCompare()
String
构造函数的静态方法fromCharCode()
单体内置对象
由ECMAScript实现提供的,不依赖于宿主环境的对象;
不需显式地实例化内置对象;
Global对象
不属于其他对象的属性和方法,都是Global
对象的属性和方法;
Global
对象的方法
如isNaN()
、isFinite()
、parseInt()
、parseFloat()
;
URI编码方法
encodeURI()
、encodeURIComponent()
、decodeURI()
、decodeURIComponent()
;
encodeURI()
,用于整个URI编码,如冒号、正斜杠、问号、井字号,encodeURIComponent()
用于对URI中的某一段进行编码,任何非标准字符;eval()
接受一个参数,即要执行的ECMAScript字符串;
被执行的代码具有与该执行环境相同的作用域链;
严格模式下,在外部访问到eval()
中创建的任何变量或函数;
Global
对象的属性
属性 | 说明 | 属性 | 说明 |
---|---|---|---|
undefined | 特殊值undefined | Date | 构造函数Date |
NaN | 特殊值NaN | RegExp | 构造函数RegExp |
Infinity | 特殊值Infinity | Error | 构造函数Error |
Object | 构造函数Object | EvalError | 构造函数EvalError |
Array | 构造函数Array | RangeError | 构造函数RangError |
Function | 构造函数Function | ReferenceError | 构造函数ReferenceError |
Boolean | 构造函数Boolean | SyntaxError | 构造函数SyntaxError |
String | 构造函数String | TypeError | 构造函数TypeError |
Number | 构造函数Number | URIError | 构造函数URIError |
Web浏览器将Global
对象作为window
的对象一部分加以实现;
Math对象
数学公式和信息
Math
对象的属性属性 说明 Math.E 自然对数的底数,即常量 e
的值Math.LN10 10的自然对数 Math.LN2 2的自然对数 Math.LOG2E 以2为底的e的对数 Math.LOG10E 以10为底的e的对数 Math.PI 圆周率 Math.SQRT1_2 1/2的平方根(2的平方根的倒数) Math.SQRT2 2的平方根 min()
、max()
接收任意个数值参数,确定最小值和最大值;
var max = Math.max.apply(Math,[1,2,3,4,5,6]);
舍入方法
Math.ceil()
向上舍入、Math.floor()
向下舍入、Math.round()
标准舍入random()
返回大于等于0小于1的一个随机数;
值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值);
function selectFrome(lowerValue, upperValue) { var choices = upperValue - lowerValue + 1; return Math.floor(Math.random() * choices + lowerValue); }
其它方法
方法 说明 方法 说明 Math.abs(num) num的绝对值 Math.asin(x) x的反正弦值 Math.exp(num) Math.E的num次幂 Math.atan(x) x的反正切值 Math.log(num) num的自然对数 Math.atan2(y,x) y/x的反正切值 Math.pow(num,power) num的power次幂 Math.cos(x) x的余弦值 Math.sqrt(num) num的平方根 Math.sin(x) x的正弦值 Math.acos(x) x的反余弦值 Math.tan(x) x的正切值
面向对象程序的设计
理解对象
ECMAScript支持面向对象编程,但不使用类和接口;
ECMA-262把对象定义为“无序属性的集合,其属性可以包含基本值、对象或者函数”;
对象的每个属性和方法都有一个名字,每个名字映射到一个值;
每个对象基于一个引用类型创建;
对象可以在代码执行过程中创建和增强,具有动态性而非严格定义的实体;
创建自定义对象
创建一个
Object
实例,然后为它添加属性和方法;通过对象字面量创建;
属性类型
JavaScript引擎用的内部才有的特性(attribute),描述了属性(property)的各种特征;
ECMAScript有两种内部属性(用[[内部属性名]]
表示):数据属性和访问器属性
数据属性
[[Configurable]],默认为
true
- 能否通过
delete
删除属性从而重新定义属性; - 能否修改属性的特性;
- 能否把属性修改为访问器属性;
- 能否通过
[[Enumerable]],默认为
true
能否通过for-in
循环返回属性;[[Writable]],默认为
true
能否修改属性的值;[[Value]],默认为
undefined
属性的数据值,从此位置读取/写入属性值;
描述符(descriptor)对象属性:configurable
、enumerable
、writable
,(默认是false
)和value
;
访问器属性
[[Configurable]],默认为
true
- 能否通过
delete
删除属性从而重新定义属性; - 能否修改属性的特性;
- 能否把属性修改为数据属性;
- 能否通过
[[Enumerable]],默认为
true
能否通过for-in
循环返回属性;[[Get]],默认为
undefined
读取属性时调用的函数;[[Set]],默认为
undefined
写入属性时调用的函数;
只指定 getter 属性不能写入;写入会被忽略,严格模式下抛出错误;
只指定 setter 属性不能读取;读取返回undefined
,严格模式下抛出错误;
定义属性
ECMAScript5定义了Object.defineProperty()
,定义属性;
接收“属性所在对象、属性名字和一个描述符对象”3个参数;
Object.defineProperty(对象, 属性名 ,{
数据属性名值 / 访问器属性: 调用函数,
});
定义多个属性
ECMAScript5定义了Object.defineProperties()
,定义多个属性;
Object.defineProperties(对象, {
对象添加或修改的属性: {
数据属性名值,
},
对象添加或修改的属性: {
访问器属性: 调用函数,
}
});
读取属性的特性
ECMAScript5定义了Object.getOwnPropertyDescriptor()
,取得给定属性的描述符;
接收两个参数:属性所在的对象、要读取其描述符的属性名称;
返回一个对象,
如果是访问器属性,这个对象属性有configurable
、enumerable
、get
和 set
;
如果是数据属性,这个对象属性有 configurable
、enumerable
、writable
和value
。
创建对象
Object
构造函数或对象字面量都可以用来创建单个对象;
使用同一接口创建很多对象,会产生大量的重复代码;
采用设计模式;
工厂模式
抽象了创建具体对象的过程;
用函数来封装以特定接口创建对象的细节;
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题;
function createPerson(name,age,job) {
var o =new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function() {
console.log(this.name);
}
return o;
}
var person1=createPerson("hsiang",29,"software engineer");
var person2=createPerson("ge",28,"home wife");
console.log(person1.age)
console.log(person2.age)
person1.sayName();
person2.sayName();
构造函数模式
创建自定义的构造函数,定义自定义对象类型的属性和方法;
- 没有显式地创建对象
- 直接将属性和方法赋给了
this
对象 - 没有
return
语句
function Person(name, age, job) {
this.name=name;
this.age=age;
this.job=job;
this.sayName=function() {
console.log(this.name);
}
}
var person1= new Person("hsiang",29,"software engineer");
var person2=new Person("ge",28,"home wife");
对象有一个constructor
属性指向其构造函数;
对象的constructor
属性用来标识对象类型的,检测对象类型,用instanceof
操作符;
任何函数,通过new
操作符来调用,那它就可以作为构造函数;
任何函数,不通过new
操作符来调用,那它跟普通函数一样;
全局作用域中调用一个函数,this
对象指向Global
对象(在浏览器中就是window
对象);
可以使用函数的call()
或apply()
方法在某个特殊对象的作用域中调用函数;
构造函数的主要问题,完成同样任务的方法都要在每个实例上重新创建一遍,副本;
如果对象需要定义很多方法,那么就要定义很多个全局函数,自定义的引用类型就丝毫没有封闭性可言;
原型模式
创建的每个函数都有一个prototype
原型属性,此属性是一个指向原型对象的指针,原型对象用途是包含可以由待定类型的所有实例共享的属性和方法;
- 原型对象可以让所有对象实例共享它所包含的属性和方法;
- 不必在构造函数中定义对象实例的信息,可以将这些信息直接添加到原型对象中;
理解原型对象
创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype
属性;
这个属性指向函数的原型对象;
默认情况下,所有原型对象都会自动获得一个constructor
构造函数属性,这个属性是一个指向prototype
属性所在函数的指针,通过这个构造函数,可为原型对象添加其他属性和方法;
创建了自定义的构造函数后,其原型对象默认只会取得constructor
属性,至于其他方法则都是从Object
继承而来;
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性)[[Prototype]]
,指向构造函数的原型对象;
这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
通过原型属性的isPrototypeOf()
方法来确定对象之间是否存在[[Prototype]]
连接;
ECMAScript5增加了Object.getPrototype()
方法返回[[Prototype]]
的值,取得一个对象的原型,以利用原型实现继承;IE9+支持;
读取某个对象的某个属性时,根据给定属性名字执行搜索,首先从对象实例本身开始,未找到继续搜索指针指向的原型对象,以返回该属性的值;
可通过对象实例访问保存在原型中的值,不能通过对象实例重写原型中的值,在实例中添加一个重名的属性,将在实例中创建此属性,屏蔽原型中的那个属性;引用类型会受影响;
使用delete
操作符则可以完全删除实例属性,可重新访问原型中的属性;
实例对象的hasOwnProperty()
方法检查给定的属性在当前对象实例中是否存在,而不是在实例的原型中;继承自Object
;
原型与in
操作符
- 单独使用
in
操作符会在通过对象能够访问给定属性时返回true
,无论该属性存在于实例中还是原型中;
//确定属性是否是原型中的属性
function hasPrototypeProperty(object, name){
return !Object.hasOwnProperty(name) && (name in object);
}
for-in
循环
返回所有能够通过对象访问的、可枚举的(enumerable
)属性;
既包括存在于实例中的属性,也包括存在于原型中的属性;
屏蔽了原型中不可枚举属性(即将[[Enumerable]]
标记为false
)的实例属性也会在for-in
循环中返回,因所有开发人员定义的属性都是可枚举的;
hasOwnProperty
、propertyIsEnumerable()
、toLocaleString()
、toString()
、valueOf()
默认不可枚举;
ECMAScript5将constructor
和prototype
属性的[[Enumerable]]
特性设置为false
,未得到所有浏览器支持;
IE8-有bug:屏蔽了不可枚举属性;ECMAScript5的
Object.keys()
,IE9+支持;
取得对象上所有可枚举的实例属性;
接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组;
实例调用,返回数组只包含实例的属性;Object.getOwnPropertyNames()
,IE9+支持;
所有实例属性,无论是否可枚举;
更简单的原型语法
封装原型的功能,用一个包含所有属性和方法的对象字面量来重写整个原型对象,原型对象的constructor
属性指向是新对象Object
构造函数;
可以在对象字面量中特意包含一个constructor
值,并将它的值设置为欲指向的构造函数;重设constructor
属性,会导致它的[[Enumerable]]
特性被设置为true
,原生的constructor
是不可枚举的;
兼容ECMAScript5的JavaScript引擎,可以使用Object.defineProperty()
方法重设构造函数的原型对象的constructor
的属性;
原型的动态性
在原型中查找值的过程是一次搜索,对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型;
实例与原型之间是松散连接关系,实例与原型之间的连接仅是一个指针,而非一个副本;
重写整个原型,把原型修改为另外一个对象,会切断构造函数与最初原型之间的联系,调用构造函数是为实例添加一个指向最初原型的[[Prototype]]
指针,实例中的指针指向原型,而不是指向构造函数;
原生对象的原型
原型模式不仅体现在创建自定义类型,所有原生的引用类型,都是采用这种模式创建,原生引用类型都在其构造函数的原型上定义了各种方法;
通过原生对象的原型,不仅可以取得所有默认方法的引用,也可以添加新方法;可能会导致命名冲突,或重写原生方法;
原型对象的问题
所有实例在默认情况下都将取得相同的属性值;
原型中所有属性是由实例共享的;
函数和基本类型值,添加一个同名属性,则隐藏原型中的对应属性;
对于引用类型的属性值,则会出现问题。
组合使用构造函数模式和原型模式
构造函数定义实例属性,原型模式定义共享的方法和属性;
每个实例都有自己的一份实例属性的副本,同时又共享着对方法的引用;支持向构造函数传递参数;
目前在ECMAScript中使用最广泛、认同度最后的一种创建自定义类型的方法,用来定义引用类型的一种默认模式;
function Person(name, age, job) {
this.name=name;
this.age=age;
this.job=job;
this.colors=["red","black"];
}
Person.prototype={
constructor: Person,
sayName: function () {
console.log(this.name);
}
};
var person1= new Person("hsiang",29,"software engineer");
var person2=new Person("ge",28,"home wife");
person1.colors.push("green");
console.log(person1.colors);
console.log(person2.colors);
console.log(person1.colors===person2.colors);
console.log(person1.sayName===person2.sayName);
动态原型模式
解决独立的构造函数和原型;把所有信息都封闭在构造函数中,在构造函数中初始化原型(仅在必要情况下);
保持了同时使用构造函数和原型的优点,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型;
寄生构造函数模式
仅封装创建对象的代码,返回新创建的对象;
除了使用new
操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的;
构造函数在不返回值的情况下,默认会返回新对象实例;
返回的对象与构造函数或者与构造函数的原型属性之间没有关系;构造函数返回的对象与在构造函数外部创建的对象没有什么不同,不能依赖instanceof
操作符来确定对象类型;
特殊情况下为对象创建构造函数;
稳妥构造函数模式
Douglas Crockford 发明了 JavaScript 中的稳妥对象(durable objects);
没有公共属性,其方法也不引用this
的对象;
适合在一些安全环境中(禁止使用this
和new
),或者在防止数据被其他应用程序改动时使用;
与寄生构造函数类似,有两点不同,
- 新创建对象的实例方法不引用
this
; - 不使用
new
操作符调用构造函数;
继承
OO语言支持接口继承和实现继承;
接口继承只继承方法签名,而实现继承则继承实际的方法;
ECMAScript函数没有签名,只支持实现继承,依靠原型链实现继承;
原型链
原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的:
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针;
让原型对象等于另一个类型的实例,
此时原型对象将包含一个指向另一个原型的指针;
另一个原型中也包含着一个指向另一个构造函数的指针;
另一个原型又是另一个类型的实例,层层递进,构成了实例与原型的链条,就这是原型链的基本概念。
function A(){ this.aProperty = "i'm a"; }//prototype -> A原型对象
A.prototype.getA=function(){ return this.aProperty; }//A.prototype: constructor -> A
function B(){ this.bProperty = "i'm b"; }//prototype -> B原型对象
B.prototype = new A();// 实例,[[Prototype]] -> A原型对象;B.prototype: constructor -> A
B.prototype.getB=function(){ return this.bProperty; }
var C = new B();// 实例,[[Prototype]] -> B原型对象;C.constructor -> B
console.log(C.getA());//原型搜索机制
console.log(C.getB());
console.log(B.constructor === Function);//true;B由Function创建
console.log(A.prototype.constructor === A);//true;原型对象包含一个指向构造函数的指针
console.log(B.prototype.constructor === A);//true;B原型对象是构造函数A创建的实例
console.log(C.constructor === A);//true;
console.log(C.constructor === B);//false;C是B的实例,C内部指针指向B原型对象
默认的原型
默认原型都会包含一个内部指针[[Prototype]]
,指向Object.prototype
;
确定原型和实例的关系
测试实例与原型链中出现过的构造函数
alert(demo instanceof Object);
原型链中出现过的原型
alert(Object.prototype.isPrototypeOf(demo));
谨慎地定义方法
子类型需要覆盖父类中的某个方法,或者需要添加父类中不存在的某个方法;
通过原型链条实现继承,不能使用对象字面量创建原型,否则会重写并切断原型链;
原型链的问题
对象实例共享所有继承的属性和方法;
- 引用类型值的原型属性会被所有实例共享;
- 创建子类型的实例时,不能向父类的构造函数中传递参数;
借用构造函数
借用构造函数(constructor stealing)技术(伪造对象或经典继承)指在子类型构造函数的内部调用父类构造函数,解决原型中包含引用类型带来的问题;
函数是在特定环境中执行代码的对象;
通过使用函数的apply()
和call()
方法可以在新创建的对象上执行构造函数;
借用构造函数问题,函数无法复用;在父类的原型中定义的方法,对子类而言是不可见的;
组合继承
组合继承(combination inheritance)技术(伪经典继承)指将原型链和借用构造函数技术组合;
使用原型链继承原型共享的属性和方法,通过借用构造函数继承实例属性;
既通过原型上定义方法实现函数的复用,又能够保证每个实例都有它自己的属性;
JavaScript中最常用的继承模式;
function A(name){
this.name=name;
this.colors=["red","blue","green"];
}
A.prototype.sayName=function () {
console.log(this.name);
};
function B(name,age) {
A.call(this, name);//继承属性;第二次调用A()
this.age=age;
}
B.prototype=new A();//继承方法;第一次调用A()
B.prototype.constructor=B;//重置指针指向创建当前对象的函数
B.prototype.sayAge=function () {
console.log(this.age);
};
var instance1=new B("hsiang",29);
instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2=new B("ge",28);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
console.log(instance1 instanceof A);
console.log(instance1 instanceof B);
console.log(A.prototype.isPrototypeOf(instance1));
console.log(B.prototype.isPrototypeOf(instance1));
console.log(Object.prototype.isPrototypeOf(instance1));
原型式继承
借助原型基于已有的对象创建新对象;必须有一个对象可以作为另一个对象的基础;
function object(o){
function F(){} //临时构造函数
F.prototype = o; //传入对象作为此构造函数的原型
return new F(); //返回临时类型的一个新实例
}
var person={
name: "hsiang",
friends: ["wu","zhang"]
};
var person1 = object(person);
person1.name="ai";
person1.friends.push("zhi");
var person2 = object(person);
person2.name="si";
person2.friends.push("jing");
alert(person.friends); //"wu","zhang","zhi","jing"
ECMAScript5新增了Object.create()
方法规范化了原型继承;IE9+支持;
接收两个参数,一个用作新对象原型的对象;一个为新对象定义额外属性的对象;
只传入一个参数的情况下,Object.create()
与object()
方法行为相同;
Object.create()
第二个参数与Object.defineProperties()
方法第二个参数格式相同:每个属性都是通过自己的描述符定义的,以这种方式定义的属性会覆盖原型对象上的同名属性。
寄生式继承
寄生式(parasitic)继承是与原型继承紧密相关的一种思路;
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封闭继承过程的函数,该函数在内部以某种方法来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("hi");
};
return clone;
}
var person={
name: "hsiang",
friends: ["wu","zhang"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //注意,函数不能复用
object()
函数不是必须的,任何能够返回新对象的函数都适用于此模式;
寄生组合式继承
组合继承会两次调用父类型构造函数:一次在创建子类型原型的时候,一次在子类型构造函数内部;子类型最终包含父类型对象的全部实例属性,但在调用子类型构造函数时又要重写这些属性;
通过寄生组合式继承解决这个问题,通过借用构造函数来继承属性,通过原型链的混成形式来继承方法;
不必为子类型的原型而调用父类型的构造函数,使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型;
function inheritPrototype(subType, superType) {
//创建父类型原型的一个副本
var prototype = Object(superType.prototype);
//为副本添加 constructor 属性,修正重写原型而失去的默认的 constructor 属性
prototype.constructor = subType;
//将副本赋值给子类型的原型
subType.prototype = prototype;
}
function A(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
A.prototype.sayName = function () {
console.log(this.name);
};
function B(name, age) {
A.call(this, name); //继承属性
this.age = age;
}
inheritPrototype(B, A); //继承方法
B.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new B('hsiang', 29);
instance1.colors.push('black');
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2 = new B('ge', 28);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
只调用一次 SuperType 构造函数,避免了在 SubType.prototype 上面创建不必要的、多余的属性,与此同时,原型链保持不变,能使用instanceof
和isPrototypeOf()
函数表达式
(还在学…)