第一部分 JavaScript语言核心 第3章 类型、值和变量
1.JS数据类型:原始类型、对象类型
原始类型:数字、字符串、布尔值 (特殊原始值:null和undefined)
2.对象是属性的集合,每个属性都是有”键值对“构成。
特殊的对象:全局对象
3.普通JS对象时无序集合。特殊对象数组array是有序集合。
4.特殊对象:函数
5.JS有内存管理机制,当对象没有引用的时候,会自动回收。
6.JS是面向对象语言。
数据类型本身可以定义方法来使用值。
a.sort(); //sort(a)的面向对象的版本
7.可变类型:对象、数组
不可变类型:数字、布尔值、null、undefined
JS中字符串是不可变的。
JS可以自由的进行数据类型转换。
JS变量是无类型的,用var关键字来声明。
变量作用域:方法内的只能内部访问。在方法外声明的是全局变量。
3.1 数字
3.1.3 JS中的算术运算
复杂算术运算:
3.1.5 日期和时间
3.2 文本
3.2.2 转义字符
反斜线(\)
3.2.3 字符串的使用
3.2.4 模式匹配
3.3 布尔值
会被转换为false的:
false和以上值被称作”假值“。
所有其他值,包括对象(数组)都会转换成true。也被称作“真值”。
3.4 null和undefined
null可以认为是特殊的对象值,含义是“非对象”。
通常认为null是它自有类型的唯一一个成员,可以表示数字、字符串、对象时“无值”的。
undefined:
1.对象属性或数组元素不存在则返回undefined。
2.函数没有返回任何值,返回undefined。
3.函数参数没有给实参,也会得到undefined。
3.5 全局对象
1.全局属性:undefined、Infinity、NaN
2.全局函数:isNaN()、parseInt()、eval()
3.构造函数:Date()、RegExp()、String()、Object()、Array()
全局对象:Math、JSON
在全局作用域中可以用this来引用全局对象。
var globle = this;//定义一个引用全局对象的全局变量
3.6 包装对象
JS对象时复合值:它是属性或已命名值的集合。通过"."来引用属性值。
当属性是一个函数的时候,称为方法。通过o.m()调用对象o中的方法。
字符串也具有属性和方法:
var s="hello world"; //一个字符串
var word = s.substring(s.indexOf(" ")+1,s.length); //使用字符串的属性
但字符串不是对象。只要引用了字符串s的属性,js就会将字符串值通过调用new String(s)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用(一旦属性引用结束,这个新创建的对象就会被销毁)。
数字和布尔值也有各自的方法:
通过Number()和Boolean()构造函数创建一个临时对象,这些方法的调用均是来自于这个临时的对象。
null和undefined没有包装对象,访问它们的属性会造成类型错误。
可通过String()、Number()、Boolean()构造函数来显式创建包装对象:
var s ="test",n=1,b=true; //一个字符串、数字和布尔值
var S = new String(s); //一个字符串对象
var N = new Number(n); //一个数值对象
var B = new Bollean(b); //一个布尔对象
3.7 不可变的原始值和可变的对象引用
JS的原始值(undefined、null、布尔值、数字和字符串)与对象(数组和函数)有着根本的区别。
原始值是不可更改的。
对象是可变的,它们的值可以修改。
var o ={x:1}; //定义一个对象
o.x = 2; //通过修改对象属性值来更改对象
o.y=3; //再次更改这个对象,给它增加一个新属性。
var a = [1,2,3]; //数组也是可以修改的
a[0] =0;//更改数组的一个元素
a[3] =4 //给数组增加一个新元素
对象的比较并非值的比较:
即使两个对象包含同样的属性和值,也不相等。
各个索引元素完全相等的数组也不相等。
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引用同一个数组,因此相等
复制对象:
var a =['a','b','c']; //待复制的数组
var b = []; //复制到的目标空数组
for(var i=0;i<a.length;i++){ //遍历a[]中的每个元素
b[i]=a[i]; //将元素值复制到b中
}
比较两个数组是否一样:
function equalArrays(a,b){
if(a.length!=b.length) return false;//两个长度不同的数组不相等
for(var i=0;i<a.length;i++) //循环遍历所有元素
if(a[i]!=b[i]) return false; //如果任意元素不同,则不相等
return true; //否则它们相等
}
3.8 类型转换
JS根据需要自行转换类型。如果转换结果无意义的话返回NaN。
10+“ objects” //=> "10 objects" 数字10转换成字符串
"7" * "4" //=>28:两个字符串均转换为数字
var n = 1- "x"; //=>NaN: 字符串"x"无法转换为数字
NaN + " objects" //=>"NaN objects": NaN转化为字符串“NaN”
3.8.1 转换和想等性
“==”想等运算符灵活多变:
null == nudefined //这两值被认为想等
"0" == 0 //在比较之前字符串转换为数字
0 == false //在比较之前布尔值转换成数字
"0" == false //在比较之前字符串和布尔值都转换成数字
注意:
一个值转换为另一个值并不一意味着两个值想等。
比如,在期望使用布尔值的地方用了undefined,它将转换为false,不表示undefined==false。
3.8.2 显示类型转换
显示类型转换:
Number("3") //=>3
String(false) //=>"false"或使用false.toString()
Boolen([]) //=>true
Object(3) //=>new Number(3)
注意:
除了null和undefined之外的任何值都有toString()方法。
如果把null或undefined转换为对象,则会爆出类型错误。
隐式类型转换:
JS中某些运算符会做隐式的类型转换,有时用户类型转换。
x + "" //等价于String(x)
+x //等价于Number(x) 也可以写成x-0
!!x //等价于Boolend(x)
数字转换为其他进制数(范围在2~36之间):
var n = 17;
binary_string = n.toString(2); //转换为"10001"
octal_string = "0" + n.toString(8) //转换为"021"
hex_string = "0x" + n.toString(16); //转换为"0x11"
数字转换为字符串,控制有效小数:
toFiexed() 根据小数点后的指定位数将数字转换为字符串,它从不适用指数计数法。
var n = 123456.789;
n.toFiexd(0); //"123457"保留0位小数,四舍五入
n.toFiexd(2); //"123456.79"
n.toFiexd(5); //"123456.78900"
toExponential()使用指数计数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数则由参数指定(也就是说有效数字位数比指定的位数要多一位)
n.toExponential(1); //"1.2e+5"
n.toExponential(3); //“1.235e+5”
toPrecision()根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字整数部分的位数,则转换成指数形式。
n.toPrecision(4); //"1.235e+5"
n.toPrecision(7); //“123456.8”
n.toPrecision(10); //“123456.7890”
上面三个方法都是当地进行四舍五入或填充0.
如果通过Number()转换函数传入一个字符串,它会试图将其转化为一个整数或浮点数直接量,这个方法只能基于十进制数进行转换,并且不能出现非法的尾随字符。
parseInt()函数和parseFloat()函数(她们是全局函数,不从属于任何类的方法)更加灵活。
parseInt()只解析整数。
parseFloat()可以解析整数和浮点数。
如果字符串前缀是"0x"或者“0X”,parseInt()将其解释为十六进制数。
parseInt()和parseFloat()都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回NaN:
parseInt("3 blind mice") //=> 3
parseFloat(" 3.14 meters") //=> 3.14
parseInt("-12.34") //=> -12
parseInt("0xFF") //=> 255
parseInt("0xff") //=> 255
parseInt("-0XFF") //=> -255
parseFloat(".1") //=> 0.1
parseInt("0.1") //=> 0
parseInt(".1") //=> 数字不能以"."开始
parseFloat("$72.47") //=> NaN:数字不能以"$"开始
parseInt()可以接收第二个可选参数,这个参数指定数字转换的基数,合法的范围2~36,如:
parseInt("11",2); //=> 3(1*2+1)
parseInt("ff",16); //=> 255(15*16+15)
parseInt("zz",36); //=> 1295(35*36+15)
parseInt("077",8); //=> 63(7*8+7)
parseInt("077",10) //=> 77(7*10+7)
3.8.3 对象转换为原始值
对象转布尔值:
所有对象(包括数组和函数)都转换为true。
对于包装对象亦如此:
new Boolean(false)是一个对象而不是原始值,他将转换为true。
对象转字符串:
[1,2,3].toString //=> “1,2,3”
(function(x){ f(x); }).toString() //=>"function(x){\n f(x);\n}"
/\d+/g.toString //=> "/\\d+/g"
new Date(2010,0,1).toString() //=> "Fri Jan 01 2010 00:00:00 GMT-0800(PST)"
3.8.3 对象转换为原始值
对象转布尔值:
所有对象(包括数组和函数)都转换为true。
对于包装对象亦如此:
new Boolean(false)是一个对象而不是原始值,他将转换为true。
对象转字符串:
[1,2,3].toString //=> “1,2,3”
(function(x){ f(x); }).toString() //=>"function(x){\n f(x);\n}"
/\d+/g.toString //=> "/\\d+/g"
new Date(2010,0,1).toString() //=> "Fri Jan 01 2010 00:00:00 GMT-0800(PST)"
另一个转换对象函数ValueOf():
如果存在任意原始值,它就默认将对象转换为表示它的原始值。
对象时复合值,而且大多数对象无法真正表示一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。
数组、函数、正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf(0方法只是简单返回对象本身。
日期类型定义的valueOf(0方法会返回它的一个内部表示:
1970年1月1日以来的毫秒数。
var d = new Date(2010,0,1); //2010年1月1日(太平洋时间)
d.valueOf() //=> 1262332800000
3.9 变量声明
变量通过var关键字来声明。
var i;
var sum;
申明多个变量:var i,sum;
变量声明和赋初值合写:
var message = "hello";
var i=0,j=0,k=0;
如果声明了变量但未赋初值,则初值为undefined。
在for和for/in循环中使用var:
for(var i=0;i<10;i++) console.log(i);
for(var i=0,j=10;i<10;i++,j--) console.log(i*j);
for(var p in o) console.log(p);
JS变量可以是任意数据类型。以下是合法的:
var i=10;
i="ten";
重复的声明和遗漏的声明:
使用var语句重复声明变量是合法且无害的。如果重复声明带有初始化器,那么这就相当于一条简单的赋值语句。
3.10 变量作用域
全局变量拥有全局作用域,在JS代码中的任何地方都是有定义的。
函数内部和函数参数都是局部变量。
在函数内,同名的局部变量优先级高于全局变量。
var scope = "global"; //声明一个全局变量
function checkscope(){
var scope ="local"; //声明一个同名的局部变量
return scope; //返回局部变量的值,而不是全局变量的值
}
checkscope(); // => "local"
全局作用域中可以不用var来声明变量,但声明局部变量时必须使用var语句。
scope ="global"; //声明一个全局变量,甚至不用var来声明
function checkspoe2(){
scope = "local"; //糟糕,我们刚修改了全局变量
myscope = "local";//这里显式地声明了一个新的全局变量
retunr [scope,myscope]; //返回两个值
}
checkscope2() // => ["local","local"]:产生了副作用
scope // => "local":全局变量修改了
myscope // => "local":全局命名空间搞乱了
函数定义可以嵌套:
var scope ="global scope"; //全局变量
funciont checkscope(){
var scope ="local scope"; //局部变量
function neste(){
var scope ="nested scope"; //嵌套作用域内的局部变量
return scope; //返回当前作用域内的值
}
return nested();
}
checkscope(); // => "嵌套作用域"
3.10.1 函数作用域和声明提前
函数作用域:
变量在声明它们的函数体以及整个函数体嵌套的任意函数体内都是有定义的。
在不同位置定义了变量i、j和k,它们都在同一个作用域内,这3个变量在函数体内均是有定义的:
function test(o){
var i =0; //i在整个函数体内都是有定义的
if(typeof 0 =="object"){
var j =0; //j在函数体内都是有定义的,不仅仅是在这个代码段内
for(var k=0;i<10;i++){
console.log(k); //输出数字0-9
}
}
console.log(j); //j已经定义了,但可能没有初始化
}
声明提前:
JS的函数作用域指在函数内声明的所有变量在函数体内始终是可见的。
这意味着变量在声明之前甚至已经可用,JS这个特性被非正式地称为声明提前。即JS函数里声明的所有变量(但不涉及赋值)都被“提前”至函数体的顶部。
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); //这里它具有了我们所期望的值
}
在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和使用变量的代码尽可能彼此靠近,这是个好的习惯。
由于JS没有块级作用域,因此将变量放在函数体顶部,这样比较清晰。
3.10.2 作为熟悉的变量
声明一个JS全局变量时,实际上是定义了全局对象的一个属性。
当使用var声明一个变量时,创建的这个属性是不可配置的,变量无法通过delete运算符删除。
var truevar = 1; //声明一个不可删除的全局变量
fakevar = 2; //创建全局对象的一个可删除属性
this.fakevar2 = 3; //同上
delete truevar // => false:变量并没有被删除
delete fakevar // => true:变量被删除
delete this.fakevar // =>true:变量被删除
JS全局变量是全局对象的属性。
JS允许用this关键字来引用全局对象,却没有方法可以引用局部变量中存放的对象。
3.10.3 作用域链
全局变量在程序中始终都有定义。局部变量在声明它的函数体内以及其所嵌套的函数内始终有定义的。
如果将将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读作用域。
每一段JS代码(全局代码或函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,这组对象对已了这段代码“作用域中”的变量。
当JS需要查找变量x的值的时候(这个过程称作“变量解析”),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象不存在名为x的属性,则JS会继续查找链上的下一个对象。如果都没有,会抛出一个引用错误异常。
在JS的最顶层代码中(也就是不包含任何函数定义内的代码),作用域链由一个全局对象组成。
在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。
理解对象链的创建规则是非常重要的。当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建了一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。
对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链是不同的。内部函数在每次定义的时候都有微妙的差别--每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。
作用域链的概念对于理解with语句和闭包很重要。

浙公网安备 33010602011771号