Chapter02 词法结构
1.可省略的分号
如果当前语句无法和下一行语句合并解析,则js解析器会在当前行后填补分号:
var a
a
=
3
console.log(a)
会被解析成:var a; a=3; console.log(a);
var y = x+f
(a+b).toString()
会被解析成: var y = x + f(a+b).toString(); 而不是 var y = x + f; (a+b).toString();
一般情况下,如果一条语句以(,[,/,+,-开始,那么它极有可能和前一条语句合在一起解析。所以一个保守的方法就是在语句前加一个分号,这样哪怕之前的语句被修改了,分号被误删了,当前语句还是可以正确地解析。
但填补分号有两个例外,第一个是return, break和continue,如果这三个关键字后面跟着换行,js引擎会在换行处填补分号,即
return
true
会被解析成 return; true;
第二个是++, --运算符,这些运算符可以作为表达式的前缀,也可以作为后缀,但如果作为后缀,它和表达式应该在同一行,否则,行尾将填补分号,同时++,--
会作为下一行的前缀操作符并与之一起解析,例如:
y
++
x
会被解析成 y; ++x;
Chapter03 类型、值和变量
1.反斜杠“\”
i.ECMAScript3要求字符串直接量必须在一行,而ECMAScript5允许在多行,每行末尾以\结束。
ii.除了上面这种情况,如果\出现在一个非转义的字符之前,则\将被忽略,如\#与#等价。
2.Falsy Value:
undefined null 0 -0 NaN ""
3.Truthy Value:
All values except for the falsy values above. (All objects and arrays will be converted to true when a boolean value is needed.)
4.null & undefined
当对null进行typeof运算时,得到的是“object”,也就是说,可以将null当为一个特殊的对象,含义是“非对象”。但实际上,通常认为null是它自有类型的唯一一个成员,它可以表示数字、字符或对象是“无值”的。
undefined用来表示更深层次的“空值”,它是变量的一种取值,表示未被初始化,如果对象属性或数组元素的值为undefined,则表示此属性或数组元素不存在。如果函数不返回任何值,则返回undefined,.引用没有提供实参的函数形参的值也会得到undefined.
区别:undefined是预定义的全局变量,但不是关键字,而null是关键字。
5.包装对象
如果程序中引用了字符串直接量,数字直接量,布尔直接量(true|false)的属性或方法,则js引擎会隐式创建一个与之对应的包装对象,分别是String,Number,Boolean,然后访问该对象的相应属性或方法,一旦引用过后,该包装对象即被销毁(虽然实现上并不一定创建或销毁这个临时对象,然而整个过程看起来是这样的)。
null和undefined没有包装对象,所以试图引用它们的属性会引发一个error.
看如下代码,思考原因:
var s = "hello kitty";
s.len = 3;
var t = s.len; //undefined
上面的代码,第二行创建了一个临时对象,并为该对象添加一个属性len,随后这个对象被销毁。在第三行试图访问s的len属性时,由于该临时对象已不存在,访问的字符直接量的len属性,所以会是undefined.
可以通过构造方法显示地创建包装对象:
var s = "str",i=1,b=true;
var S = new String(s),
I = new Number(i),
B = new Boolean(b);
js引擎会在必要时将包装对象转换为原始值,所以包装对象常常————但不总是————表示得和原始值一样。==运算符将包装对象和原始值视为相等,但===全等运算符视它们为不等。typeof s返回“string”,但typeof S返回“object”.
6.原始值和对象引用
js中,原始值比较的是值,而对象比较的是引用,两个对象只有引用的是同一个基对象时,其值才相等,例:
var x = {p:1}, y = {p:1};
x == y; // =>false: 两个单独的对象永不相等
var a = [], b = [];
a == b; //=>false: 两个单独的数组永不相等
如果想比较对象的内容是否相等,需要自定义一个比较方法:
function equalArray(a,b) {
if(a.length != b.length) {
return false;
}
for(int i = 0; i < a.length; i++) {
if(a[i] != b[i]) {
return false;
}
}
return true;
}
7.类型转换
7.1转换和相等性
l NaN和任何值都不等,包括它本身,即NaN == NaN //=>false.
l 对象除了和它自身相等外,和其它任何值都不等:
var o = new Object(),o2 = new Object();
o == o2; //=>false
o == o; //=>true
l 一个值转换为另一个值并不意味着这两个值相等。例如,在期望使用布尔值的地方undefined被转换为false,但并不表明undefined == false.js运算符和语句期望使用多样化的数据类型,并可以相互转换,但==运算符从不试图将其操作数转换为布尔值。
Number(),Boolean(),String(),当不通过new运算符调用时,它们会作为类型转换函数执行。如果通过Number()传入一个字符串,
它会试图将其转换为一个整数或浮点数直接量,注意这个方法只能基于10进制进行转换,而不能出现非法的尾随字符。
parseInt()和parseFloat()更灵活:parseInt()只解析整数,parseFloat()解析整数和浮点数。
parseInt()和parseFloat()都会跳过任意数量的前导空格,并忽略非法的尾随字符以尽可能多地解析数字字符,例如parseInt(' 323dksfds')会被解析为整数323.
如果第一个非空格字符是一个非法的数字直接量,将直接返回NaN.
如果字符的前缀是0x或0X,parseInt()将其解析为16进制数;在ECMAScript3中,parseInt()可以对前缀为0(不能是0x或0X)的数字做8进制转换,
但由于细节没有详细说明,所以无法直接使用parseInt()来对前缀为0的值进行解析,除非你明确指出所使用的转换基数!在ECMAScript5中,parseInt()只有在明确传入第二个参数8时才会解析8进制数。(但是经我测试,所有浏览器,包括IE7,IE6我没测,都可以解析parse(023),输出均为19,
所以感觉书中这里说的没什么用,不过当然,明确指出基数是一个好习惯。另外,所有浏览器测试均表明parseInt(023)和parseInt('023')的结果一致,也就是说,参数是不是一个字符串都可以,但是前者应该是直接按数字去处理的,后者是先转换为数字再处理,看下面parseInt(.1)和parseInt('.1')的例子就会明白):
parseInt(.1) //=>0
parseInt('.1') //=> NaN
parseInt("-23.323xxx") //=>-23
parseFloat('$323.22') //=> NaN
parseFloat(' 3.12 meters') //=> 3.14
parseInt('0XFF') //=>255
parseInt('0xff') //=>255
parseInt('0xFf') //=>255
parseInt('11',2) //=>3
parseInt(11,2) //=>3
parseInt('zz',36) //=>1295 (36*35+35)
parseInt('zz',36) //=>error: zz is not defined
parseInt(32,4) //=>14
parseInt(32,5) //=>17
7.2.对象转换为原始值
注:这里说的转换规则只适用于本地对象, 宿主对象(例如由浏览器定义的对象)根据各自的算法可以转换成字符和数字。
所有对象都继承了两个方法:toString()和valueOf():
toString(): Object的toString()方法默认返回"[object object]",Array,RegExp,Function,Date重写了此方法:
Array的oString()将每个元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串;
Function的toString()返回该函数定义的源代码;
RegExp的toString()将RegExp对象转换为正则表达式直接量的字符串;
eg:
[1,2,3].toString() //=> "1,2,3" 注意没有方括号!!
(function(x){x=1;}).toString() //=> "function (x){x=1;}"
/\d+/g.toString() //=> "/\d+/g"
new RegExp('\d+','g').toString() //=> "/\d+/g"
new Date().toString() //=> "Sun Jul 28 2013 12:01:05 GMT+0800 (中国标准时间)"
valueOf(): 这个方法的任务并未详细定义,如果存在任意原始值,它就默认将该对象转换为表示它的原始值. 如果对象是复合值或无法表示为一个原始值,则简
单地返回对象本身.Array,RegExp,Function简单地继承了此方法,因为调用这些类对象的valueOf()方法只会简单地返回对象本身,Date重写了此方法:返回1970年1月1日以来的毫秒数.
eg:
new Number(2).valueOf() //=> 2
new Object().valueOf() //=> Object {}
function Cat(name) {
this.name = name;
}
var c = new Cat();
c.name //=> undefined
c.valueOf() //=>Cat {name: undefined}
c.name = 'mimi';
c.name //=> "mimi"
c.valueOf() //=> Cat {name: "mimi"}
typeof c.valueOf() //=> "object"
l 对象到字符串的转换步骤:
如果对象具有toString()方法,则调用之,如果返回的是一个原始值,则将其转换为字符串(如果本身 不是字符串的话)并返回该字符串作为结果;
如果对象没有toStirng()方法或该方法返回的不是一个原始值,则会尝试调用该对象的valueOf()方法, 如果该方法存在并返回的是一个原始值,则将该原始值转换为字符串(如果本身不是字符串的话)并返回 该字符串作为结果;
否则,如果js无法从toString()或valueOf()获得一个原始值,将抛出一个类型错误异常。
l 对象到数字的转换步骤:
与转换为字符串一样,只不过会先调用valueOf()方法,并试图将得到的原始值转换为一个数字。
这些细节解释了为什么空数组([])转换为数字时是0:数组继承了默认的valueOf()方法(默认的valueOf()方法简单地返回对象本身),
这个方法返回一个对象而不是原始值,因此,转换器调用数组的toString(),得到一个空串,由于空串是一个原始值,因此转换器试图将转换为数字,于是得到了0.
[2]也一样:toString()得到‘2’,再转换为数字就是数字2.
对象的一个特例是Date对象,Date类重写了toString()和valueOf(),因此Date对象像字符串和数字的转换将直接调用toString()或valueOf()
返回的原始值,而不再强制被转换为字符串或数字。
(new Date()).toString(); //=> "Fri Jul 19 2013 13:35:36 GMT+0800"
(new Date()).valueOf(); //=> 1374212166696
typeof (new Date() + 1) //=> 'string' : "+" 将日期转换为字符串
typeof(new Date() - 1) //=> 'number': "-" 将日期转换为数字
var now = new Date();
now == now.toString() // =>true: 隐式和显示的字符串转换
now > now-1 //=> ">" 将日期转换为数字
9.函数作用域和声明提前
js是没有block作用域,只有全局作用域和函数作用域.
特别值得注意的是:js的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,也就是说,变量在声明之前甚至已经可用,js的这个特性被声称为"声明提前",即js函数里声明的所有变量(但不涉及赋值)都被提前至函数体的顶部,看下面的例子:
var scope = "global";
function foo() {
console.log(scope); // =>undefined
var scope = "local";
console.log(scope); //=>local
}
这个例子很好地说明了js的"声明提前"特性.因此为了避免产生迷惑,建议建议将所有函数内变量的声明都放至函数体顶部:
var scope = "global";
function foo() {
var scope;
console.log(scope); // =>undefined
scope = "local";
console.log(scope); //=>local
}
这种做法可以使代码清晰地反映变量的真实作用域.
Chapter04 表达式和运算符
1.delete运算符
delete只能删除自有属性,不能删除继承属性,继承属性必须从定义这个属性的原型对象上删除,一旦删除,会影响到所有继承自这个原型的对象。
当delete表达式删除成功或没有任何副作用(比如试图删除对象中不存在的属性或数组中不存在的元素)时,它返回true.
delelte运算符希望它的操作数是一个左值,如果不是,delelte将不进行任何操作同时返回true。 delete不能删除通过变量声明或函数声明创建的全局对象的属性,也不能删除对象不可配置的属性,执行这
类操作时会返回false。
var o = {x:1,y:2};
delete o.x; //=>true
x in o //=>false
delete o.x; //=>true: 尽管o.x已经不存在
delete o; //=>false: 不能删除通过var声明的变量
delete o.toString; //=>true: 什么也不做,toString是继承来的
delete 1; //=>true: 参数不是左值,do nothing and return true.
this.x = 1; //给全局变量声明一个属性x, 注意这里不是使用var声明的
delete x; //删除它,在非严格模式下将删除成功并返回true,在严格模式会抛出异常,应使用delete this.x;
var x = 22; //通过变量声明创建的全局对象的属性
delete this.x; //=> false,不可删除
function f() {} //通过函数声明创建的全局对象的属性
delete this.f; //=>false
delete Object.prototype; =>false, prototype是不可配置属性
2.”+”运算符
“+”运算符可以对两个数字做加法,也可以做字符串转接操作。当两个操作数都是数字或字符串时,结果是显示易见的。然而对于其它情况,则需要进行一些类型转换,“+”的行为(是做加法还是字符串连接)依赖于最终的转换结果。
“+”转换规则优先考虑字符串连接:如果其中一个操作是字符串或是转换为字符串的对象,则另一个操作数会被转换为字符串,然后进行字符串连接;如果两个操作数都不是字符串的(string-like),那么将进行算术加运算。一些例子:
1 + 1 //=> 2
‘1’ + ‘1’ //=>’11’
‘1’ + 2 //=>’12’
‘1’ + {} //=>’1[object object]’,对象优先转换为字符串
true + true //=> 2, +需要两个数字,true不是对象,所以试图转换为数字,转换成功,为1,所以做加法
2 + null //=> 2: null转换为0后做加法, 原因同true+true一样
2+ undefined //=>NaN: undefined转换为NaN后做加法
3. 对象创建表达式
new Object();
new Cat();
如果一个对象创建表达式不需要传入任何参数的话,那么圆括号是可以省略的,下面的写法与上面的等效:
new Object;
new Cat;
4.eval()
Eval()只有一个参数.如果传入的不是字符串,它直接返回这个参数;如果是字符串,它会把字符串当作js代码进行编译,如果编译失败则抛出一个语法错误异常.如果编译成功则开始执行这段代码,并返回字符串中最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则返回undefined.
Eg:
eval("var a =1;"); //=>undefined
eval("var a =1;a=2;"); //=>2
eval()执行的上下文与调用它的环境相一致,即如果在一个局部函数中调用eval(),则eval()查找变量和方法,以及定义新变量或方法都在局部函数中进行;如果在全局环境下调用,则在全局环境中进行,eg:
var a = 1;
eval("console.log(a);var b=2;"); //=>1
console.log(b); //=>undefined
function test() {
var a = 3;
eval("console.log(a); var c = 4;"); //=>2
console.log(b);
console.log(c);
}
console.log(c); //=>c is not defined
需要注意的是: 传递给eval()的代码片段必须在语法中讲得通,否则eval()将抛出syntax error exception——不能通过eval()往函数中粘贴任意的代码片段:
var foo = function(a) {
eval(a);
}
foo(‘return;’); //SyntaxError: return not in function
function aa() {
var b = 2;
foo("return b;");
}
aa();//SyntaxError: return not in function
这段代码中执行eval(a)的上下文是全局的,在全局上下文中使用return 会抛出syntax error: return not in function. ???
4.1 全局eval()
ECMAScript标准规定不允许对eval()赋别名,即var f = eval; f(“//待执行的代码...”);这种形式是不允许的。如果eval()通过别名调用,将会抛出EvalError.
ES5允许通过别名调用eval(),不过有一些区别: 当直接调用eval()时,它总是在调用它的上下文作用域内执行;间接调用时则总是在全局作用域中执行,并且无法 读、写、定义局部变量和函数:
var geval = eval;
var x = y = "global";
function f() {var x = "local";eval("x += 'changed'");return x;} //直接eval改变局部变量
function g() {var y = "local";geval("y += 'changed'");return y;} //间接eval改变全局变量
console.log(f(),"-----",x)//=> localchanged-----global
console.log(g(),"-----",y); //=>local-----globalchanged
注:IE8及以下版本的IE有个bug,当通过别名调用eval时,它们并不把它当作全局eval来调用,也不报EvalError,而仅仅将它当作局部eval来执行,所以上面这段代码在IE8-中执行的结果是连续打印两个localchanged-----global, 但IE提供了一个execScript()方法,该方法与eval()的作用相同,但总是在全局上下文中执行, 相当于全局eval(), 稍有不同的是,它总是返回null.
所以上面代码中geval的定义可以改为:
var geval = window.navigator.userAgent.indexOf("MSIE") > -1) ? execScript : eval; //这种写法在ff下报错,所以改为下面这种写法:
var geval;
if(typeof execScript != 'undefined') {
geval = execScript;
} else {
geval = eval;
}
var x = y = "global";
function f() {var x = "local";eval("x += 'changed'");return x;} //直接eval改变局部变量
function g() {var y = "local";geval("y += 'changed'");return y;} //间接eval改变全局变量
console.log(f(),"-----",x) //=> localchanged-----global,兼容IE8-
console.log(g(),"-----",y); //=> local-----globalchanged,兼容IE8-
但是这种写法在IE8-下执行到最后一句时会报“不正确的变量类型。”,所以最后改为:
var geval=eval;
var x = y = "global";
function f() {var x = "local";eval("x += 'changed'");return x;} //直接eval改变局部变量
function g() {
var y = "local";
if(typeof execScript != 'undefined') {
execScript("y += 'changed'");
} else {
geval("y += 'changed'");
}
return y;
} //间接eval改变全局变量
console.log(f(),"-----",x) //=> localchanged-----global,兼容IE8-
console.log(g(),"-----",y); //=> local-----globalchanged,兼容IE8-
这种写法可以兼容IE8-,IE9+,FF等。
4.2 严格eval()
ES5规定,在严格模式下执行eval()时,或eval()执行的段以“use strict”开始时,此时的eval()是么有上下文环境中的局部eval,此时的eval可以查询或更新局部变量,但不能在局部环境中定义新的变量或函数;
此外,严格模式下,eval是一个保留关键字,不能为eval()指定别名,且变量名、函数名、参数、异常捕获的参数等都不能叫eval.
eg:
var i = 1;
function foo() {
eval(“’use strict’;i++;var k = 1”);
console.log(i); //=>2
conosole.log(k); //k is not defined,严格模式下,局部变量k没有定义成功
}
var i = 1;
function foo() {
eval(“;i++;var k = 1”);
console.log(i); //=>2
conosole.log(k); //=>1,非严格模式下,局部变量k定义成功
}
Chapter 05 语句
1.语句与表达式的区别
l 表达式是短语,语句是整句或命令;
l 表达式计算出一个值, 语句用来执行以使某事发生;
l 赋值与函数声明等表达式可以作为单独的语句来执行,这种把表达式当语句的用法叫做”表达式语句”(expression statement).
2.var语句
var语句用来声明一个或多个变量.
如果var语句出现在函数体内,则它定义的是一个局部变量,如果在顶层代码中,则它定义的是一个全局变量,该变量是全局对象的一个属性,然而和其它全局属性不同的是,通过var声明的全局属性(包括变量和函数)是不能通过delete删除的. 但是这些属性并不是只读的,属性值可以重写.
3.function
在定义函数时,并不执行函数体内的语句,它和调用函数时待执行的新函数对象(即执行上下文)相关联。也就是说,函数执行的上下文在是执行时才确定的。
函数嵌套时,函数的声明只能出现在所嵌套函数的顶部,也就是说,函数声明不能出现在if,while等语句块中,因为js并没有块级作用域。注意:这只是ECMAScript标准的要求,实际上一些js引擎允许在出现语句块的地方都可以进行函数声明,但是不同的实现在细节处理上有很大差别,因此将函数声明放在其他语句内的做法并不具备可移植性。
3.1 函数定义表达式
var f = function(x) {
return x;
}
或
var f = function g(x) { /*带有变量名的语句,但是不能通过g()来调用该函数,
调用g(23)将在控制台下将打印”local”,不知道为什么*/
return x;
}
这种方法可以通过f(23)来调用,却不可以通过g(23)来调用
3.2函数声明语句
function funcname([arg1[,arg2[...,argn]]]) {
statements
}
3.3二者的区别
二者都创建了新的函数对象,但函数声明中的函数名是一个变量,变量指向函数对象,可以通过函数名来调用该函数,而函数定义表达式中指定的”函数名”却不可以调用方法.
和var声明的变量一样,函数定义语句中的函数被地”提前”到了作用域(局部的或全局的)顶部.因此它们在整个作用域中都是可见的.
l 但函数定义表达式只是将变量声明提前了——变量初始化代码即函数定义并未提交,因此在函数定义表达式之前,该变量的值一直都是undefined,是无法通过该变量来调用方法的。
l 而函数声明则会将函数名和函数体将提前,也就是说,可以在声明一个函数之前调用它。
4.Switch
“default:”标签通常都出现在switch的末尾,位于所有Case之后,当然这是最合理易懂的写法,但实际上,“default:”可以出现在switch语句内的任何地方。
5.for in
for(variable in object) { //只有一条语句时花括号可省略
statements
}
variable通常是一个变量名,也可以是一个可以产生左传的表达式或一个通过var声明的变量,总之必须是一个左值;
Object是一个表达式,这个表达式的计算结果是一个对象。
for/in循环在遍历对象属性时很方便:
for(var p in o) {
Console.log(o[p]);
}
每次循环中p的值都会被重新计算一次。因此可以使用下面的代码将所有对象属性(注意不是属性值,只是属性名!)复制至一个数组中:
var o = {x:1,y:2,z:3}
var a = [], i = 0;
for(a[i++] in o) /*empty*/ ;
此值,a为[x,y,z]
=====================
由于js数组是一种特殊的对象,因此,for/in循环可以像枚举对象属性一样枚举数组索引:
var o = {x:1,y:2,z:3}
var a = [], i = 0;
for(a[i++] in o) /*empty*/ ;
for(i in a) { //注意这里的i跟上面的i的值没有关系,这里的i在每次循环时被重新赋值,依次为0,1,2
console.log(i); //依次打印0,1,2
console.log(a[i]); //依次打印x,y,z
}
For/in 只能枚举到可枚举的属性,如果在for/in循环体内删除了还未枚举的属性,那么这个属性将不会再枚举到。如果在循环体定义了新的属性,这些属性通常也不会枚举到。(然而,有些js实现是可以枚举那些在循环体内增加的继承属性的)
6.try/catch/finally
try不能单独出现,必须和catch或finally至少之一结合使用。
7.with语句
with(object) { //花括号可省略
statements
}
With语句用于临时扩展作用域链,上面的代码将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态。
注意:作用域链只有在查的标识符时才有用,在创建变量的时候不能使用它:
with(o) {
x = 1;
}
如果对象o拥有属性x,则上面这段代码将x的值置为1;如果对象没有属性x,则上面这段代码并不能为o增加一个新的属性x,而是和不使用with的x=1是一样的,即等同于在全局或局部上下文中直接执行x=1。
var a = 1;
var o = {x:1}
with(o) {
x = 2;
a = 3;
}
console.log(o,a); //=>Object { x=2} 3
8.”use strict”指令
”use strict”是ES5新引进的一条指令,该指令的目的是说明后续的代码将会解析为严格代码:
1) 禁止使用with语句;
2) 所有变量都要先声明后使用,否则会抛出引用错误异常;
3) 调用的函数(不是方法,方法是和对象绑定的,而函数不是)中的this值是undefined(在非严格模式中,调用函数中的this值总是全局对象),因此可以用下面这种方法来判断js引擎是否支持严格模式:
4) var hasStrictMode = (function() {"use strict";return this === undefined;}());
5) 通过call()或apply()来调用函数时,其中的this值就是通过call()或apply()传入的第一个参数(在非严格模式,null和undefined值被全局对象和转换为对象的非对象值所代替??)
6) 给只读属性和给不可扩展的对象创建新成员将抛出一个类型错误异常(在非严格模式,只是简单的操作失败而不会报错)
7) 传入eval()的代码不能在调用程序所在的上下文中声明变量或定义函数,变量和函数的定义只能在eval()创建的新作用域中,该作用域在eval()返回时就弃用了:
var a = 1;
eval("'use strict';a =2; var b =2; console.log(b);"); //=>2: b在eval作用域内可见
//在eval作用域外不可见,看下面的例子就知道
console.log(a); //=>2: 可以改变上下文中变量的值
console.log(b);//=>ReferenceError: b is not defined------不能在调用程序所在的上下文中声明变量或定义函数
8) 函数里的arguments对象拥有传入参数的静态副本,而不是直接指向原参数.
9) Delete运算符后跟随非法标识符如变量,函数,函数参数时,将会抛出syntaxError(非严格模式则是什么也不做,并返回false);
10) 试图删除一个不可配置的属性将抛出类型错误异常(非严格模式只是操作失败,并返回false);
11) 同一个对象直接量中定义两个或多个同名属性将产生一个syntaxError((非严格模式中不会报错);
12) 函数声明中存在两个或多个同名参数将产生一个syntaxError((非严格模式中不会报错);
13) 不允许使用八进制整数直接量(以0为前缀的整数).
14) 标识符eval和arguments是关键字, 它们的值不能更改.不能给这些标识符赋值,也不能将它们用做变量名\函数名\函数参数等.
15) 限制了对调用栈的检测能力,arguments.caller和arguments.callee都会抛出一个类型错误异常.
Chapter06 对象
1.三类对象两类属性
l 内置对象: ES规范定义的对象或类,包括数组\函数\日期\正则表达式等.
l 宿主对象: js引擎所嵌入的宿主环境(如web browser)定义的,宿主环境定义的方法可以当成变通的js函数对象,因此宿主对象也可以当成内置对象;
l 自定义对象: 由js代码创建的对象.
Ø 自有属性: 直接在对象中定义的属性;
Ø 继承属性: 在对象原型中定义的属性.
2.Object.create()
ES5定义了一个名为Object.create()的方法,它创建一个对象,其中第一个参数是这个对象的原型,第二个参数可选,用以指定对象的属性.
Object.create()是一个静态函数,而不是提供对某个对象调用的方法。
可以通过传入参数null来创建一个没有原型的对象,但通过这种方法创建的对象不会继承任何东西,基至不包括基础方法,比如toString().
如果想创建一个普通的空对象,可以传入Object.prototype:
Var o = Object.create(Object.prototype); //o和{}、new Object()一样
3.属性检测
l in运算符:所有可枚举的属性,继承的属性也会被枚举出来
l hasOwnProperty():只有自有属性才返回true
l propertyIsEnumerable():hasOwnProperty的增强版,只有检测到是自有属性且这个属性的可枚举性为true时它才返回true.
l Object.keys(obj): 返回一个数组,该数组由对象obj中可枚举的自有属性名组成
l Object.getOwnProperyNames(): 返回对象所有自有属性名,包括不可枚举的属性名.
Var o = {x:1};
“x” in o; //=>true
“y” in o; //=>false
“toString” in o;//=>true
o.hasOwnProperty(“x”); //=>true
o.hasOwnProperty(“y”); //=>false
o.hasOwnProperty(“toString”); //=>false
O.prototype = {y:2};
o.propertyIsEnumerable(“x”); //=>true: o有一个可枚举的自有属性x
o.propertyIsEnumerable(“y”); //=>false: y是继承来的
Object.prototype.propertyIsEnumerable(“toString”); //=>false: toString不可枚举
6.6 属性getter和setter
在ES5(和除了IE外最新主流浏览器的ES3实现)中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter, 当程序查询属性的值时,js将调用它的getter方法,当设置值时,调用它的setter方法。如果一个属性只定义了getter,则它是只读属性;如果只定义了setter,则它是只写属性。这两个方法不使用function关键字,函数名与属性名相同:
var p = { //定义一个对象
x : 1,
y: 1,
//r是可读写的存取器属性
get r() { return Math.sqrt(this.x * this.x + this.y * this.y);}
set r(newvalue) {
var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y);
var ratio = newvalue / oldvalue;
this.x *= ratio;
this.y *= ratio;
}
//theta是只读存取器属性,它只有getter方法
Get theta() { return Math.atan2(this.y, this.x)}
}
6.7属性的特性
数据属性:value, writable, enumerable,configurable
存取器属性:get, set, enumerable, configurable
获取某个对象指定属性的特性:Object.getOwnPropertyDescriptor(obj,attr) //只能获取自有属性的特性
设置属性特性:Object.defineProperty(obj,attr,propertyDescriptorObj)
Egs:
Object.getOwnPropertyDescriptor({x:1},’x’); //=> {value:1,writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(p,’theta’); //p是上面定义的
//=> {get: /*func*/,set: undefined, enumerable: true, configurable: true}
Var o = {};
Object.defineProperty(o,”x”,{value: 2, writable: true, enumerable: false, configurable: true});
//属性是存在的,但不可枚举
O.x //=>2
Object.keys(o); //=>[]
//现在将x变为只读
Object.defineProperty(o,”x”,{writable: false});
O.x = 1; //操作失败但不报错,严格模式中将抛出类型错误异常
O.x; //=>2
//属性依然是可配置的,所以可以通过这种方式进行更改:
Object.defineProperty(o,”x”,{value:1});
O.x; //=>1
//现在将x从数据属性改为存取器属性
Object.defineProperty(o,”x”,{get:function() {return 0;}});
O.x //=>0
规则总结:
- 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能添加新属性;
- 如果属性是不可配置的,则不能修改它的可配置性和可枚举性;
- 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转为数据属性;
- 如果数据属性是不可配置的,则不能转换为存取器属性,也不能将它的可写性从false转为true,但可以从true转为false;
- 如果数据属性是不可写且不可配置的,则不能修改它的值;
- 如果数据属性是不可写但可配置的,可以通过defineProperty的方法修改它的值.(实际是先将它标记为可写,修改值后,再标记为不可写).
6.8对象的三个属性
Prototype, class, extensible
6.8.1 prototype
Object.getPrototypeOf(obj): 查询对象obj的原型; //ES5
o2.isPrototypeOf(o1): o2是否是o1的原型; //ES5
6.8.2 类属性
对象的类属性是一个字符串,用以表示对象的类型信息.Object.prototype默认的toString()返回如下信息:
[object class]
6.8.3对象的可扩展性
对象的可扩展性用以表示是否可以给对象添加属性.
Object.esExtensible(o) :查询对象是否是可扩展的
Object.preventExtensions(o) :将对象设为不可扩展
Object.seal(o) :除了将对象设为不可扩展的,还将对象所有的自有属性设为不可配置
Object.freeze(o) :除了将对象设为不可扩展的,还将对象所有的自有属性设为不可配置和不可写的.(如果对象的存取器属性具有setter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)
Object.isSealed(o): 查询对象是否封闭了
Object.isFrozen(o): 查询对象是否冻结了
6.9 对象序列化
对象序列化是指将对象转换为字符串,也可以将字符串还原为对象.ES5提供内置的JSON.stringify()和JSON.parse()用来序列化和还原js对象.
JSON语法是js语法的子集,它并不能表示js里所有的值.
JSON支持对象,数组,字符串,无穷大数,true,false和null,可以将这些值序列化和还原:
NaN, Infinity, -Infinity序列化的结果是null;
Date对象序列化的结果是JSOn格式的日期字符串,但JSOn.parse()依然保留它的字符串形态,并不能将它们还原成日期对象;
函数,Regexp,error对象和undefined不能序列化和还原;
Json.stringify()只能序列化对象可枚举的自有属性,不能序列化的属性会被省略掉.
Json.stringify()和json.parse()可以接收第二个参数,指定需要序列化或还原的属性列表.