《JavaScript权威指南》笔记(3)

站长的个人作品

第5章 语句

1. 表达式语句

具有副作用的表达式是JavaScript中最简单的语句。比如:

//赋值语句
greting= "Hello "+ name;
i*= 3; 

//delete运算语句
delete o.x; 

//函数调用
alert(greeting);
window.close();
2. 复合语句和空语句

复合语句:在JavaScript中将多条语句联合在一起的语句,并花括号将多条语句括起来,就形成一条单独的语句块,就叫复合语句。

//复合语句
{
    pi= Math.PI;
    cx= Math.cos(pi);
    console.log("cos(π) = "+ cx);
}//这里不需要分号

注意点:1、复合语句结尾不需要分号(也就是花括号结束处);2、语句块的缩进不是必须的,这是为了让代码可读性更强,更易于理解;3、JavaScript中没有块级作用域,在复合语句中声明的变量并不是语句快私有的。

空语句: 不包含一条语句的语句。

空语句啥都不处理,但有时候很作用。

 ;  //空语句,啥都没

//空语句的作用
for( var i= 0; i< a.lenght; a[i++]= 10) ; //空语句,因为数组a的初始化在for语句中处理了。

//为了增强阅读性,把上面代码改为
for( var i= 0; i< a.lenght; a[i++]= 0)  /* 空语句 */ ; //空语句
3.声明语句var和function

var 和 function 都是声明语句它们声明或定义变量或函数。

这些语句定义标识符(变量和函数名)并赋值后,可以在程序中任意地方使用。

1.var的使用,其之后可以跟随的是申明的变量列表,列表中的每个变量可以带有初始化表达式,由于指定它的初始值。

var i;  
var j;
var p, q;
var greeting= "hello"+ name;
var x= 2.3, y= x*x;
var x= 2, f= function(x){ return x*x; }, y= f(x);

我们前面知道,JavaScript的变量的声明会提前,但真正初始化却没有,如下:

    console.log(i); //声明提前了,但没有初始化,undefined
    for( var i= 0; i< 10; i++){
        //这里我们暂不处理
    }
    console.log(i); // 输出是10

PS:JavaScript中多次声明同一个变量名是无所谓的。

2.function使用。function是用来定义函数的。

var f = function(x) {return x+1; } //将表达式赋值给一个变量
function f(x){ return x+1; } //含有变量名的语句 

函数声明语句通常出现在JavaScript代码的最顶层,可以嵌套在其他函数体内。但在嵌套时,函数声明只能出现在所嵌套函数的顶部。也就是说,函数定义不能出现在if语句、while循环或其他任何语句中。

尽管函数声明语句和函数定义表达式包含相同的函数名,但二者仍然不同。两种方式都创建额新的函数对象,但函数声明语句中的函数名是一个变量名,变量指向函数对象。和通过var函数变量一样,函数定义语句中的函数被显示地”提前“但脚本或函数顶部。因此它们在整个脚本和函数内部是可见的。

使用var的话,是有变量声明提前了---变量的初始化代码仍然在原来的位置。

然而使用函数声明语句的话,函数名称和函数体均提前:脚本中的所有函数和函数中的所有嵌套的函数都会在当前上下问中其他代码之前声明。也就是说,可以在声明一个JavaScript函数之前调用它。

PS:和var语句一样,函数申明语句创建的变量也是无法删除的,但这写变量不是只读的,变量值可以重写的。

4. 条件语句

条件语句是通过判断指定表达式的值来取决执行还是跳过某些语句。

if/else和switch语句就是条件语句。

//if语句
if(num >= 10){//当express为真时,就执行花括号中的语句块
    console.log("num >= 10"); //花括号中可以是0条、1条或者多条语句
}

//if else语句
if(num >= 10){
    console.log("num >=10 ");
}else{
    console.log("num < 10 ");
}

//if,else if,else语句
if(num > 10){
   console.log("num > 10 );
}else if( num ==10 ){
   console.log("num == 10 ");
}else{
   console.log("num < 10 ");
}


//switch 语句
switch(num){
   case 10:
      console.log("num is 10"); //语句块,也可以多条
      break;//break是退出switch语句
   case 15:
      console.log("num is 15");
      break;
   case 5:
      console.log("num is 5");
      break;
    default://这是最后其他条件会执行的语句
       console.log("other number.");
      break;
}
5. 循环

循环语句就是程序路径的一个回路,让代码片段重复执行。循环语句主要有while、do/while、for和for/in语句。

//while 语句,如果条件不允许,一次也不会执行花括号中的代码片段
var num= 10;
while(num > 0){ //条件符合就继续执行花括号中的代码片段,如果一直符合,会一直执行
     num--; 
     console.log("num:"+ num);
}


//do/while循环
var num= 10 ;
do{
    console.log("num:"+ num);
    num--;
}while( num> 0); //不论while条件是否成立,至少执行一次花括号中的代码片段。


//for循环语句
for( var num= 0; num> 0; num--){
   console.log("num:"+ num);
}

//for/语句用于变量对象属性成员,,这个和for循环有些不同
var a= [1, 2, 3, 4, 5];
for( var i in a){
   console.log(a[i]);
}

//把对象属性复制到数组中
var o = { x: 1, y: 2, x: 3 };
var a= [], i= 0;
for( a[i++] in o )/*empty*/;   //每次都会计算a[i++]中的值

下面我们重点讲解一下for/in循环。其语法:

for( variable in object){
        statement
}

variable是一个变量名,也可是一个可以产生左值的表达式或者一个通过var语句声明的变量,总之必须是一个使用于赋值表达式左侧的值。object是一个表达是,这个表达式的计算结果是一个对象。同样,statement是一个语句或语句块,它构成了循环主体。

在for/in语句中,如果JavaScript解释器首先计算object表达式。乳沟表达式位null或undefined,JavaScript解释器将会跳过循环并执行后续的代码。如果表达式等于一个原始值,这个原始值将会转换为与之对应的包装对象。JavaScript会依次美剧对象的属性来执行循环。然而在每次循环之前,JavaScript都会先计算variable表达式的值,并降属性名(一个字符串)赋值给它。

PS:只要for/in循环中variable的值可以当做赋值表达式的左值,它可以是任意表达式。每次循环都会计算这个表达式,也就是每次循环它计算的值有可能不同。

JavaScript中的数组是一种特殊的对象,因此,for/in循环可以向枚举对象属性意向枚举数组索引。

值得注意的是:for/in循环并不会变量对象所有的属性,只有”可枚举“的属性才会遍历到。比如toString()属性就不会遍历。

如果在for/in的循环体删除了还未枚举的属性,那么这个属性将不会再枚举到。如果循环体定义了对象的新属性,这些属性通常也不会枚举到。(然而,JavaScript的有些实现是可以枚举到在循环体中新增家的集成属性)

6. 跳转

JavaScript中的另一类语句是跳转语句。也就是使它执行的位置跳到另一个位置。

比如:

break语句就是跳出循环或者其他语句的结束。

continue语句是终止此次循环,开始下一次循环执行。

return语句是让解释器跳出函数体的执行,并提供本次调用的返回值。

throw语句触发或者抛出一个异常。它是与try/catch/finally语句一同使用。

//break语句
var num= 0;
switch(num){
    case 1:
               console.log("number is 1");
               break; //跳出switch循环,如果没有break,会执行到case 2代码段
     case 2:
               console.log("number is 2");
               break;
     defalt:
               console.log("other number");
               break;
}


//continue语句:计算5到9的值,,以下只是测试,是一种很笨的方式。
var sum= 0;
for( var i= 0; i< 10; i++){
  if( i< 5 ){
     continue;//如果i小于5就跳过
  }
  sum+= i;
}

//return语句
function f(x){
   return x*x; //x的平方
}


//throw语句
function division(a,b){
   //除数不能为0
   if(b==0){
      throw() throw new Error("除数不能为0");;
   }
   return a/b; 
}
7. 标签语句

语句是可以添加标签的,标签是有语句前的标识符和冒号组成:

identifier : statement

通过给语句定义标签,就可以在程序中任何地方通过标签名引用这条语句。break和continue是JavaScript中唯一可以使用语句标签的语句。

//continum语句使用了标签
mainloop: while(token != null){
    //忽略这里代码
    continue mainloop; //跳转到下一次循环
    //忽略这里的代码
}
//break中添加标签也类似,我就不写例子了
8. try/catch/finally语句

try/catch/finally语句是JavaScript的异常处理机制。其中try语句中定义了需要处理的异常所在代码块。catch语句是当try代码块中出异常时才会执行的代码块。finally语句块不论有没有异常都会执行的。

try{
//通常来讲,这里的代码会从头执行到尾而不会产生任何问题
//但有时候会抛出一个异常,要么是由throw语句抛出异常
//要么是通过调用的方法抛出异常
}catch(e){
//当try语句中有异常抛出时才会执行这里。
//这里可以通过e来获取Error对象或者抛出的其他异常对象
//这里可以根据某种原因处理这个异常,也可以忽略这个异常
//还可以通过throw语句重新抛出异常
}finally{
//不论try是否抛出异常,这里的代码都会执行。
}
9 .with语句

在严格模式中禁止with语句使用,在非严格模式中也不推荐使用with语句,尽可能避免使用with语句。

所以这里不过多的解释。

10. debugger语句

debugger语句通常什么也不做。但在调试程序可以用并运行时候,JavaScript解释器将会(非必需)以调试摩尔是运行。实际上,这条语句用来产生一个断点,JavaScript代码会执行到这个断点的位置,这时候可以调用调试器输出变量的值、检查调用栈。

function f(0){
   if( undefined === 0){
        debugger; //这一行代码只是用于临时调试
   }
   //省略其他部分代码
}
11. ”use strict“

”use strict“ 是ECMAScript 5引入的一条指令。指令不是语句,但非常接近语句。

其和普通语句之间的区别:

1. 它不包含任何语言的关键字,指令仅仅是一个包含一个特殊字符串直接量的表达式。对于那些没有实现ECMAScript 5的JavaScript解释器来说,它是一条没有副作用的表达是语句,它什么也没做。

2. 它只能出现在脚本代码的开始或函数体的开始、任何实体语句之前。但它不必一定出现在脚本的首行或者函数体内的首行,因为”use strict “指令之后或之前都是可能有其他字符串直接量表达式语句,并且JavaScript的具体实现可能将它们解析位解释器自有的命令。在脚本或者函数体内第一条常规语句之后字符串直接量表达式语句只能当多普通的表达式语句对待,它们不会当做指定解析,它们没有任何副作用。

使用”use strict“指令的目的是说明(脚本或函数体)后续的代码将会解析位严格代码。严格代码也严格模式执行。

在ECMAScript 5 中的严格模式就该语言的一个受限制的子集,它修正了语言的严重缺陷,并提供健壮的差错功能和增强的安全机制。

严格模式和非严格模式的重要区别:

1. 在严格模式中,禁止使用with语句

2. 严格模式中,所有的变量都要先声明,如果给一个位声明的变量、函数、函数参数、catch从句参数或者全局对象的属性赋值,将会抛出一个运用错误异常。

3. 在严格模式中,调用的函数(不是方法)中的一个this值是undefined。(在非用哪个模式中,调用的函数中的this值重视全局对象)。可以使用这种特性判断JavaScript实现是否支持严格模式:

var hasStrictMode = ( 
function() {
     "use strict";
 return this===undefined;
}());

第6章 对象

对象是JavaScript的基本数据类型。对象是一中复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。

1.对象的创建

对象的创建有对象直接量、关键字new和Object.create()(ECMAScript 5)函数来创建对象。

//对象直接量
var emput= {}; //没有任何属性的对象
var point= {x: 1, y: 2}; //两个属性
var book = {
    "main titile": "Javascript", //属性名中有空格必须使用字符串表示
    "sub-title": "The Definitive Gude",
    "for": "all audiences",  //for是保关键字,必须使用引号
    "author": {   //这个属性是对象
        firstname: "David",
        surname: "Flanagan"
     }
};


//使用new创建对象
var o= new Object();  //创建一个空对象
var a = new Array(); //创建一个空数组
var d= new Date(); //创建一个表示当前时间的Date对象

由于Object.create()有点复杂,我这里专门介绍。

ECMAScript 5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,可以用对象的属性进行进一步描述。

Objectl.create()是一个静态函数,而不是提供给某个对象调用的方法。使用它的方法很简单,只需要传入所需的原型对象即可。

可以通过传入参数null来创建一个没有原型的新对象,但通过这种方式创建的对象不会继承任何东西,设置不包括基础防范,比如toString(),也就是说它将不能和”+“运算符一起正常工作。

如果想创建一个普通的空对象(比如通过{}或New Object()创建的对象),需要传入Object.prototype。

//使用Object.create()
var o = Object.create({x:1, y:2 }); //o继承了属性x和y
var o1 = Object.create(null); //o1不继承任何属性和方法
var 02 = Object.create(Object.prototype); //和new Object一样
2. 原型

每个JavaScript对象(null除外)都和一个对象相关联。”另一个“对象就是我们熟悉的原型,每个对象都从原型继承属性。

所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对原型对象的引用。

通过关键字new和构造函数调用创建的对象原型就是构造函数的prototype属性的值。

因此,通过new Object()创建的对象同使用{}创建对象一样,也继承Object.prototype。

同样,通过new Array()创建的对象的原型就是Array.prototype,通过new Date()创建的对象的原型就是Date.prototype。

没有原型的对象为数不多,Object.prototype就是其中之一。它不继承任何属性。

3. 属性的查询和设置

我们可以通过点(.)或方括号([])运算符来获取属性值。

对于点(.)来说,右侧必须是一个以属性名称命名的简单标识符。对于方括号([])来说,方括号内必须是一个计算结果位字符串的表达式(也就是属性的名字)。

//通过点或方括号获取属性
var author = book.author;  //得到book的”author“属性
var name = author.name; //获取author的”surname“
var title = book["main title"]; //获取book的属性”main title“

//通过点或方括号进行赋值属性
book.edition = 5; //给book创建一个名为”edition“的属性
book["main title"] = "ECMAScript"; //给”main title“属性赋值

在ECMAScript 3中,点运算符后的标识符不能是保留字,比如 book.for 或 student.class都是非法的。但如果对象中存在属性位保留字时候,必须使用方括号形式访问它们。比如book["for"]和student[”class“]。在ECMAScript 5中对此放宽了限制,可以在点运算符后使用保留字。

4. 关联数组的对象

从上面我们知道,下面两个表达式获取的属性值是相同的:

object.property; //点运算符
object["property"]; //方括号运算符

上面第二种方式中使用方括号和字符串看起来更像数组,这个数组的索引是字符串而不是数字,我们称这种数组叫关联数组,也称为散列、映射或字典。

JavaScript的对象都是关联数组。

5. 继承

JavaScript对象具有”自有属性“,也就是一些属性是从原型对象继承而来的。

举个例子:假设要查询对象o的属性x,如果o中不存在x属性,那么会继续在o的原型对象(父亲)中查询属性x,如果原型对象(o的父亲)也没有x属性,但原型对象也有原型(也就是o的祖父),那么继续查询这个原型对象的原型(祖父)上查询,直到找到属性x或者找到一个原型位null的对象为止。(类似于找祖宗的属性一样)。

var o = {}; //o从Object.prototype继承对象的方法
o.x = 1; //给o定义一个属性x

var p = inherit(o); //p继承o对象和Object.prototype
p.y = 2; //给p定义一个属性y

var q = inherit(p);// q继承o、p和Object.prototype
q.z = 3; //给q定义一个属性z

var s= q.toString(); //toString继承域Object.prototype
q.x + q.y ;  // 输出3, x和y分别继承o和p

如果给对象q赋值属性z,且q中已经存在此属性且不是继承来的,那么这个赋值操作只改变这个已有的属性z。如果q中不存在这个属性z,那么赋值操作给q新增一个属性z。如果之前的q已经继承了属性z,那么这个继承的属性就会被新创建的同名属性覆盖。

6.删除属性

delete运算符可以删除对象的属性。它的操作数赢点个是一个属性访问表达式。

delete book.author; //book不再有属性author
delete book["main title"]; //book不再有属性”main title“

不过需要注意的是:delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。

a = {p: {     x: 1;   }    };
b = a.p;
delete a.p; 
b.x;  // 1

//由于已经删除的属性的引用依然存在,因此在JavaScript的某些实现中,可能因为这种不严谨的代码而造成内存泄露。索引在销毁对象的时候,要遍历属性中的属性,依次删除

delete运算符只能删除自有属性,不能删除继承属性

当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete删除不是一个属性访问表达式,同样返回true。

var o = { x: 1 }; //对象o有一个属性x,并继承toString
delete o.x ;  //删除属性x,返回true
delete o.x;  //什么也没做,属性x不存在,返回true
delete o.toString();// 什么也没做,属性toString是继承来的,返回true
delete 1; //无意义 返回true

delete不能删除那些可配置属性位false的属性。某些 内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。在严格模式中,删除一个不可配置的属性会报一个类型错误。在非严格模式中(以及ECMAScript 3中),这些情况下的delete操作返回false。

delete Object.prototype ; //不能删除,属性是不可配置的
var x = 1;//声明一个全局变量
delete this.x; //不能删除这个属性
function f() {}; //声明一个全局函数
delete this.f; //也不能删除全局函数

//在非严格模式中删除全局对象的可配置属性时,可以省略全局对象的引用,直接使用delete操作符后跟随要删除的属性名
this.a = 1 ; // 创建一个可配置的全局属性(没有用var)
delete a; //将a删除

//然而在严格模式中,delete后跟随一个非法的操作数,则会报一个语法错误,因此必须显示指定对象以及属性
delete x; //在严格模式中报语法错误
delete this.x; //正常工作
7. 检测属性

JavaScript对象可以看做属性的集合,我们可以检测集合中成员的所属关系---判断某个属性是否存在某个对象中。

我们可以通过in运算、hasOwnProperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。

 1.  in运算符

in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true:

var o = { x: 1}; 
“x” in o;  //true, x是o对象的属性
"y" in o; //false ,y不是o对象的属性
“toString” in o; //true,o继承有toString的属性
 2. hasOwnProperty()

对象中有hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性,就返回false:

var o = { x: 1 };
o.hasOwnProperty("x");  // true ,o有一个自有属性x
o.hasOwnProperty("y");  //false, o中不存在属性y
o.hasOwnProperty("toString"); //false,toString是继承属性,不是自有属性

3. propertyIsEnumerable()

propertyIsEnumerable()是hasOwnProperty()的增强版,只检测到是自有属性且这个属性的可枚举性位true是它才返回true。

某些内置属性是不可以枚举的。通常由JavaScript代码创建的属性都是可枚举的,除非子啊ECMAScript 5中使用一个特殊的方法改变属性的可枚举性。

var o = inherit( { y: 2} ); //在测试中inherit提示找不到,,,后续看,,这里只是举个例子
o.x = 1;
o.propertyIsEnumerable("x"); //true ,x属性是o中可枚举的自有属性
o.propertyIsEnumerable("y"); //false,y是继承来的
Object.prototype.propertyIsEnumerable("toString"); //false,不可枚举
4. !==运算符

除了使用in运算符之外,另一种更简单的方法是使用“!==”运算符,这个可以判断一个属性是否是undefined:

var o = { x: 1};

o.x !== undefined; //true, o中有属性x
o.y !== undefined; //false, o中没有属性y
o.toString !== undefined; //true,o继承了toString属性

PS:有一种场景只能使用in运算符而不能使用上述属性访问的方式。因为in运算符可以区分不存在的属性存在值为undefined的属性

var o = { x: undefined }; //属性被显示的赋值位undefined
o.x !== undefined; // false 属性存在,但值位undefined
o.y !== undefined; //false 属性不存在
“x” in o; //true  属性存在
“y” in o; //false 属性不存在
delete o.x ;  //删除属性x
“x” in o;  //false 属性已经被删除,不存在了
8. 枚举属性

除了检测对象的属性是否存在,我们还会经常遍历对象的属性。

1.for/in循环

通常使用for/in循环遍历。for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承属性),把属性名称赋值和循环变量。

PS:对象继承的内置方法是不可枚举的,但在带中给对象添加的属性都是可枚举的(除非转位不可枚举的)。

var o = { x: 1, y: 2, x:3 };  //三个可枚举的自有属性
o.propertyIsEnumerable("toString"); // false, 不可枚举
for( p in o ){
    console.log(p); //输出可枚举的属性x, y, z
}

//
var o = Object.create({y: 2});
o.x= 1;

for( p in o ){
    console.log(p); //输出x,y
}

许多使工具库给Object.prototype添加新的方法或属性,这些方法和属性可以被所有对象继承并使用。然后在ECMAScript 5标准之前,这些新添加的方法是不能定义为可枚举的,(意思在ECMAScript 5之前这些新增加的方法能是可枚举的,但不算是可枚举属性,但for/in中可以遍历出来)因此在for/in循环中是可以枚举出来的。为了避免这种情况,需要过滤for/in循环返回的属性。如下面:

for( p  in o ){
    if( !o.hasOwnProperty(p) )  continue; //跳过继承的属性
} 

for( p in o ){
   if( typeof o
=== "function" ) continue; //跳过方法 }
2.Object.keys()

Object.keys()返回一个数组,这个数组由对象中可枚举的自有属性的名称组成

3. Object.getOwnPropertyNames()

和上面方法相似,只是返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。

9. 属性getter和setter

JavaScript中的对象是由名字、值和一组特性构成。

在ECMAScript 5中,属性可以用一个或两个方法替代,这两个方法就是getter和setter。由getter和setter定义的属性称做“存取器属性”,它不同于“数据属性”(数据属性只是一个简单的值)。

和数据属性不同,存取器属性不具有可读写性。

如果存取器属性同时具有getter和setter方法,那么它是一个读写属性;如果它只有getter方法,那么它是一个只读属性;如果它只有setter方法,那么它是一个只写属性(有例外。。),读取只写属性总是返回undefined。

var o = {
   //普通的数据属性
   data_prop : value,
   //存取器属性都是成对定义的函数
   get accessor_prop(){ /*这里是函数体*/ },
   set accessor_prop(){ /*这里是函数体*/ }
};

存取器属性定义位一个或两个和属性同名的函数,这个函数定义没有使用function关键字,而是使用get和(或)set,不一定同时存在。

PS:这里并没有使用冒号将属性名和函数体分开,但在函数体的结束和下一个方法或数据属性之间有逗号分隔。

和数据属性一样,存取器属性地可以继承的。

10. 属性的特性

JavaScript的对象中除了包含名字和值之外,属性还可以包含一些标识它们可写、可枚举和可配置的特性。

在ECMAScript 3 中无法设置这些特性,所有通过ECMAScript 3的程序创建的属性都是可写、可枚举的和可配置的,且无法对这些特性做修改。

下面介绍ECMAScript 5中查询和设置这些属性特性的API。这些API对于库的开发者来书是非常重要的,因为:

1. 可以通过这些API给原型对象添加方法,并将它们设置成不可枚举的,这让它们看起来更像内置方法。

2. 可通过这些API给对象定义不能修改或删除的属性,借此“锁定”这个对象。

数据属性的4个特性分别是:值(value)、可写性(writeable)、可枚举性(enumerable)和可配置性(configurable)

存取器属性的4个特性分别是:取(get)、写入(set)、可枚举性和可配置性。

为了实现属性特性的查询和设置操作,ECMAScript 5定义了一个名为“属性描述符”的对象,这个对象代表那4个属性。描述对象的属性和它们所描述的属性特性是同名的。因此数据属性的描述对象的属性有value、writeable

enumerable和configurable。存取器属性的描述符对象则使用get属性和和set属性代替value和writeable,其中writeable、enumerable和configurable都是布尔值,当然get和set属性是函数值。

可以通过使用Object.getOwnPropertyDescriptor()获取某个对象特定属性的属性描述符:

Object.getOwnPropertyDescriptor( { x:1}, "x" ); 
 //返回{ value:1, writeable:true, enumerable:true; configurable: true }


Object.getOwnPropertyDescriptor(random, "octet"); //randon对象是只有get,没有set
//返回{get:/"func"/, set:undefined, enumerable:true, configurable:true}

Object.getOwnPropertyDescriptor( {}, "x" );
// undefined ,没有这个属性

Object.getOwnPropertyDescriptor( {}, "toString");
//undefined , 继承属性
11. 对象的三个属性

每个对象都有与之相关的原型、类和可扩展性。

1. 原型属性

对象的原型属性是用来继承属性的。

原型属性在实例对象创建之处就设置好的。

通过对象直接量创建的对象使用Object.prototype作为它们的原型。通过new创建的对象使用构造函数的prototype属性作为它们的原型。通过object.create()创建的对象使用第一个参数(也可以是null)作为它们的原型。

在ECMAScript 5中,将对象作为参数传入Object.getPrototypeOf()可以查询它的原型。在ECMAScript 3中,则没有与之等价的函数,但经常使用表达式o.constructor.prototype来检测一个对象的原型。通过new表达式来创建的对象,通常继承一个constructor属性,这个属性指代创建这个对象的构造函数。

要想检测一个对象是否是另一个对象的原型(或处于原型链中),可以使用isPrototypeOf()方法。例如:使用p.isPrototypeOf(o)来检测p是否是o的原型

var p = {x: 2};  //定义一个原型对象
var o = Object.create(p);  //使用这个原型创建一个对象
p.isPrototypeOf(o); //true: o继承p
Object.prototype.isPrototypeOf(0); //true  : p继承Object.prototype
2. 类属性

对象的类属性是一个字符串,用来表示对象的类型信息。ECMAScript 3 和ECMAScript 5都没有提供设置这个属性的方法,只有一种间接的方法可以查询它。默认的toString()方法返回字符串:[object class]

因此,想要获得对象的类,可以调用对象的toString()方法,然后提取已返回字符串的第8个到倒数第二个位置之间的字符。不过,由于很多对象继承的toString()方法被重写了,位了能调用正确的toString()版本,必须间接地调用Function.call()方法。如下classof()函数可以返回任意传递给他的任意对象类:

//classOf()函数
function classOf(o){
   if(null === o ) return "null";
   if( undefined === o ) return "Undefined";
   return Object.prototype.toString.call(o).slice(8, -1);
}

classOf()函数可以传入任何类型的参数。数字、字符串和布尔值可以直接调用toString()方法,就和对象调用toString()方法一样,并且这个函数可以包含对null和undefined的特殊处理。

classOf(null);  // "null"
classOf(1);  //"Number"
classOf("");  //"String"
classOf(false);  //"Boolean"
classOf({}); // “Object”
classOf([]); // ”Array“
classOf(/./); //"Regexp"
3. 可扩展性

对象的可扩展性用来表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显示可扩展的,宿主对象的可扩展性有JavaScript引擎定义的。在ECMAScript 5中,所有的内置对象和自定义对象都是可扩展的,除非将它们转义位不可扩展的。同样,宿主对象的可扩展性也是由实现ECMAScript 5的JavaScript引擎定义的。

ECMAScript 5中定义了用来查询和设置对象可扩展性的函数。通过将对象传入Object.esExtensible()来判断该对象是否可扩展的。如果你想将对象转为不可扩展的,需要调用Object.preventExtensions(),将待转换的对象作为参数传入进去。

PS:一旦将对象转换为不可扩展的,将无法再将其转换为可扩展的。同时注意preventExtensions()只影响到对象本身的可扩展性如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会继承这些属性。

可扩展性的目的是将对象”锁定“,以避免外界的干扰。对象的可扩展性通常和属性的可配置性与可写性配合使用。

在ECMAScript 5中定义了一些函数可以方便的设置多种属性。比如:

Object.seal()和Object.preventExtensions()类似,除了能够将对象设置为不可扩展,还可以将对象的所有自有属性都设置位不可配置的。换句话说,就是不能该这个对象添加属性,而且它已有的属性也不能删除或配置,不过它已有的可以写属性依然可以设置。对于那些已经封闭(sealed)起来的对象是不能解封的。可以通过Object.isSealed()来检测对象是否封闭。

Object.freeze()将更严格地锁定对象----”冻结“。除了将对象设置位不可扩展的和将其属性设置位不可配置的之外,还可以将它自有的所有数据属性设置为只读(如果对象读取器属性具有setter方法,读取器属性将不受影响,仍可以通过给属性赋值调用它们)。可以使用Object.isFrozen()来检测对象是否冻结。

12. 序列化对象

对象序列化是指将对象的状态转为字符串,也可以将字符串还原位对象。

ECMAScript 5提供了内置函数JSON.stringify()和JSON.parse()来序列化和还原JavaScript对象。

o = { x: 1, y: { z: [false, null, ""] } }; //定义一个测试对象
s = JSON.stringify(o); // s是 '{"x":1, "y":{"z":[false, null, ""]}}'
p = JSON.parse(s); //p是o的深度拷贝
13. 对象方法
1. toString()方法

toString()方法没有参数,它将返回一个表示调用这个方法的对象值的字符串。在需要将对象转换为字符串的时候,JavaScript都会调用这个方法。

默认的toString()方法返回值带有的信息很少,比如:

var s= { x: 1, y:1 }.toString(); //返回的字符串"[object Object]"

由于默认的toString()方法并不会返回很多有用信息,因此很多类都带有自定义toString()方法。

2. toLocaleString()方法

这个方法返回一个表示这个对象的本地话字符串。Object默认的toLocaleString()方法并不做任何本地化自身的操作,仅仅调用toString()方法并返回值。Date和Number类对toLocaleString()方法做了定制,可以用它对数字、日期和时间做本地化的转换。Array类的toLocaleString()方法和toString()方法很像,唯一的不同是每个数组元素都会调用toLocaleString()方法转为字符串,而不是调用各自的toString()方法。

3. toJSON()方法

Object.prototype实际上并没有定义toJSON()方法,但对于需要执行序列化对象来说,JSON.stringify()方法调用toJSON()方法。如果在待序列化的对象中存在这个方法,则调用它,返回值即是序列化的结果,而不是原始的对象。

4.valueOf()方法

valueOf()方法和toString()方法非常类似,但往往当JavaScript需要将对象转位某种原始值而非字符串的时候才会调用它,尤其是转为数字的时候。如果在需要使用原型值的上下文中使用了对象,JavaScript就会自动调用这个方法。默认的valueOf()方法不足为奇,但有些内置类定义了valueOf()方法。

温馨提示:文章内容系作者个人观点,不代表博客志对观点赞同或支持。
版权声明:本文为投稿文章,感谢 125啦读书导航(125la.com) 的投稿,欢迎分享本文,转载请保留出处!
站长的个人作品
125la导航_独立博客导航平台

发表评论


表情

或者微信联系我