全局对象(global object)在JavaScript中有着重要的用途:全局对象的属性是全局定义的符号, JavaScript程序可以直接使用。当JavaScript解释器启动时(或者任何Web浏览器加载新页面的时候),它将创建一个新的全局对象,并给它一组定义的初始属性:
·全局属性,比如undefined、 Infinity和NaN。
·全局函数,比如isNaN()、 parseInt()和eval()
·构造函数,比如Date()、 RegExp()、 String()、 Object()和Array()
·全局对象,比如Math和JSON
在客户端JavaScript中,在其表示的浏览器窗口中的所有JavaScript代码中, Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,它可以代替this来引用全局对象。 Window对象定义了核心全局属性,但它也针对Web浏览器和客户端JavaScript定义了一少部分其他全局属性。
当初次创建的时候,全局对象定义了JavaScript中所有的预定义全局值。这个特殊对象同样包含了为程序定义的全局值。如果代码声明了一个全局变量,这个全局变量就是全局对象的一个属性
JavaScript对象是一种复合值:它是属性或已命名值的集合。通过“.”符号来引用属性值。当属性值是一个函数的时候,称其为方法。
var s="hello world!";//一个字符串
var word=s.substring(s.indexOf("")+1,s.length);//使用字符串的属性
字符串既然不是对象,为什么它会有属性呢?只要引用了字符串s的属性, JavaScript就会将字符串值通过调用new String(s)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁(其实在实现上并不一定创建或销毁这个临时对象,然而整个过程看起来是这样)。
null和undefined没有包装对象:访问它们的属性会造成一个类型错误。
var s="test";//创建一个字符串
s.len=4;//给它设置一个属性
var t=s.len;//查询这个属性
当运行这段代码时, t的值是undefined。第二行代码创建一个临时字符串对象,并给其len属性赋值为4,随即销毁这个对象。第三行通过原始的(没有被修改过)字符串值创建一个新字符串对象,尝试读取其len属性,这个属性自然不存在,表达式求值结果为undefined。这段代码说明了在读取字符串、数字和布尔值的属性值(或方法)的时候,表现的像对象一样。但如果你试图给其属性赋值,则会忽略这个操作:修改只是发生在临时对象身上,而这个临时对象并未继续保留下来。
存取字符串、数字或布尔值的属性时创建的临时对象称做包装对象,它只是偶尔用来区分字符串值和字符串对象、数字和数值对象以及布尔值和布尔对象。通常,包装对象只是被看做是一种实现细节,而不用特别关注。由于字符串、数字和布尔值的属性都是只读的,并且不能给它们定义新属性。
JavaScript中的原始值(undefined、 null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此——改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来修改字符串中的字符。实际上, JavaScript是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。
原始值的比较是值的比较:只有在它们的值相等时它们才相等。这对数字、布尔值、 null和undefined来说听起来有点儿难懂,并没有其他办法来比较它们。同样,对于字符串来说则并不明显:如果比较两个单独的字符串,当且仅当它们的长度相等且每个索引的字符都相等时, JavaScript才认为它们相等。
var s="hello";//定义一个由小写字母组成的文本
s.toUpperCase();//返回"HELLO",但并没有改变s的值
s//=>"hello":原始字符串的值并未改变
对象和原始值不同,首先,它们是可变的——它们的值是可修改的:
var o={x:1};//定义一个对象
o.x=2;//通过修改对象属性值来更改对象
o.y=3;//再次更改这个对象,给它增加一个新属性
var a=[1,2,3]//数组也是可修改的
a[0]=0;//更改数组的一个元素
对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,它们也是不相等的。各个索引元素完全相等的两个数组也不相等。我们通常将对象称为引用类型(reference type),以此来和JavaScript的基本类型区分开来。依照术语的叫法,对象值都是引用(reference),对象的比较均是引用的比较:当且仅当它们引用同一个基对象时,它们才相等。
var o={x:1},p={x:1};//具有相同属性的两个对象
o===p//=>false:两个单独的对象永不相等
var a=[],b=[];//两个单独的空数组
a===b//=>false:两个单独的数组永不相等
var a=[];//定义一个引用空数组的变量a
var b=a;//变量b引用同一个数组
b[0]=1;//通过变量b来修改引用的数组
a[0]//=>1:变量a也会修改
a===b//=>true:a和b引用同一个数组,因此它们相等
JavaScript中的取值类型非常灵活,我们已经从布尔值看到了这一点:当JavaScript期望使用一个布尔值的时候,你可以提供任意类型值, JavaScript将根据需要自行转换类型。一些值(真值)转换为true,其他值(假值)转换为false。这在其他类型中同样适用:如果JavaScript期望使用一个字符串,它把给定的值将转换为字符串。如果JavaScript期望使用一个数字,它把给定的值将转换为数字(如果转换结果无意义的话将返回NaN)
10+"objects"//=>"10 objects".数字10转换成字符串
"7"*"4"//=>28:两个字符串均转换为数字
var n=1-"x";//=>NaN:字符串"x"无法转换为数字
n+"objects"//=>"NaN objects":NaN转换为字符串"NaN"
null==undefined//这两值被认为相等
"0"==0//在比较之前字符串转换成数字
0==false//在比较之前布尔值转换成数字
"0"==false//在比较之前字符串和布尔值都转换成数字
对象到布尔值的转换非常简单:所有的对象(包括数组和函数)都转换为true。对于包装对象亦是如此: new Boolean(false)是一个对象而不是原始值,它将转换为true。
一个变量的作用域(scope)是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的。然而在函数内声明的变量只在函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。
在函数体内,局部变量的优先级高于同名的全局变量。如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖。声明局部变量时则必须使用var语句。
函数定义是可以嵌套的。由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况。
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思的是,这意味着变量在声明之前甚至已经可用。 JavaScript的这个特性被非正式地称为声明提前(hoisting)【也有叫“提升”】,即JavaScript函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部
var scope="global";
function f(){
console.log(scope);//输出"undefined",而不是"global"
var scope="local";//变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
console.log(scope);//输出"local"
}
function f(){
var scope;//在函数顶部声明了局部变量
console.log(scope);//变量存在,但其值是"undefined"
scope="local";//这里将其初始化并赋值
console.log(scope);//这里它具有了我们所期望的值
}
当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话, JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除它们
var truevar=1;//声明一个不可删除的全局变量
fakevar=2;//创建全局对象的一个可删除的属性
this.fakevar2=3;//同上
delete truevar//=>false:变量并没有被删除
delete fakevar//=>true:变量被删除
delete this.fakevar2//=>true:变量被删除
如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当JavaScript需要查找变量x的值的时候(这个过程称做“变量解析”(variable resolution)),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性, JavaScript会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。
在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。理解对象链的创建规则是非常重要的。当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别——在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。