学习JavaScript之——第3章 类型、值和变量(下)
学习内容:
8、类型转换
9、变量声明
10、变量作用域
3.8 类型转换
- JavaScript根据自行需要转换类型
10 + "objects";//"10objects":数字10转换成字符串
"7" * "4";//28:两个字符串均转换成数字
var n = 1 - "x";
n;//NaN:字符串x无法转换成数字
n + "objects";//"NaNobjects":NaN转换成字符串"NaN"
- 原始值通过调用String()、Number()或Boolean()构造函数,转换为它们各自的包装对象
3.8.1 转换和相等性
-由于JavaScript可以做灵活的类型转换,因此其"=="相等运算符也随相等的含义灵活多变
null == undefined;//true
"0" == 0;//true
0 == false;//true
"0" == false;//true
3.8.2 显式类型转换
- 做显式类型换最简单的方法就是使用Boolean()、Number()、String()或Object()函数
Number("3");//3
String(false);//"false"
false.toString();//"false"
[] == false;//true
Boolean([]);//true:
**所有的对象(包括数组和函数)到布尔值的转换都转化为true!!!!**
Object(3);//Number { 3 }
- 除了null和undefined之外的任何值都具有toString()方法
x + "";//"undefined":等价于String(x)
+x;//NaN:等价于Number(x),也可以写成 x - 0
!!x;//false:等价于Boolean(x)
- Number类定义的toString()方法可以接收表示转换基数的可选参数,如果不指定此参数,转换规则将是基于十进制。同样亦可将数字转换为其它进制数(基数范围2~36之间)
var n = 17;
binary_string = n.toString(2);//"10001":转换为二进制
octal_string = n.toString(8);//"21":转换为八进制
hex_string = "0x" + n.toString(16);//"0x11":转换为十六进制
- Number类为数字到字符串类型转换场景定义了三个方法:这三个方法都会适当的进行四舍五入或填充0
- toFixed()根据小数点后的指定位数将数字转换为字符串,它从不使用科学计数法
- toExponential()使用指数计数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数由参数指定(有效数字位数比指定位数要多一位)
- toPrecision()根据指定的有效数字位数将数字转换成字符串;如果有效数字的位数少于数字整数部分的位数,则转换成指数形式
var n = 123456.789;
n.toFixed(0);//"123457":四舍五入后得到小数点后0位
n.toFixed(2);//"123456.79":四舍五入后得到小数点后2位
n.toFixed(5);//"123456.78900":补充0得到小数点后5位
n.toExponential(1);//"1.2e+5":转为指数计数法并保留1位小数
n.toExponential(3);//"1.235e+5":转为指数计数法并保留3位小数
n.toPrecision(4);//"1.235e+5":有效数字的位数少于数字整数部分的位数,转换成指数形式并保留4位有效数字
n.toPrecision(7);//"123456.8":四舍五入保留7位有效数字
n.toPrecision(10);//"123456.7890"//补充0保留10位有效数字
- 如果通过Number()转换函数传入一个字符串,它会试图将其转换为一个整数或浮点数直接量,这个方法只能基于十进制数进行转换,并且不能出现非法的尾随字符。
- 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");//NaN:整数不能以"."开始
parseFloat("$72.47");//NaN:整数不能以"$"开始
- parseInt()可以接收第二个可选参数,这个参数指定数字转换的基数,合法的取值范围是2~36
parseInt("11",2);//3:二进制数11转换为十进制是3
parseInt("ff",16);//255:十六进制数ff转换为十进制是255
parseInt("zz",36);//1295:三十六进制数ff转换为十进制是1295
parseInt("077",8);//63:八进制数077转换为十进制是63
parseInt("077",10);//77:十进制数077是77
3.8.3 对象转换为原始值
- 对象到布尔值的转换:所有对象(包括数组和函数)都转换为true。对于包装对象亦是如此:
new Boolean(false);//是一个对象而不是原始值,它将转换为true
- 对象到字符串和对象到数字的转换是通过调用待转换对象的一个方法来完成的
- 这里提到的字符串和数字的转换规则只适用于本地对象;宿主对象(例如,由Web浏览器定义的对象)根据各自的算法可以转换成字符串和数字
- 所有的对象继承了两个转换方法:toString()、valueOf()
- 第一个是toString(),它的作用是返回一个反映这个对象的字符串
({x:1 , y:2}).toString();//"[object Object]"
- 数组类(Array class)的toString()方法将每个数组元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串。
[1,2,3].toString();//"1,2,3"
- 函数类(Function class)的toString()方法返回这个函数的实现定义的表示方式。实际上,这里的实现方式通常是将用户定义的函数转换为JavaScript源代码字符串
(function(x){ f(x);}).toString();//"function(x){ f(x);}"
- 日期类(Date class)定义的toString()方法返回了一个可读的日期和时间字符串
new Date(2010,0,1).toString();//“Fri Jan 01 2010 00:00:00 GMT+0800 (China Standard Time)” - RegExp类(RegExp class)定义的toString()方法将RegExp对象转换为表示正则表达式直接量的字符串
/\d+/g.toString();//"/\\d+/g"
- 另一个转换对象的函数时valueOf():如果存在任意原始值,它就默认将对象转换为表示它的原始值;如果是复合值,就返回对象本身,而不是返回原始值。
- 数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回对象本身。
- 日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数。
var d = new Date(2020,0,1);
d.valueOf();//1577808000000
- JavaScript原始值:
原始值即一些代表原始数据类型的值,也叫基本数据类型,首先说一下js中有哪些原始值,Number,String,Boolean,Null,Undefined这些基本数据类型都是原始值。原始值存储在栈中。意思就是说,当一个原始变量把值赋给另一个原始变量时,只是把栈中的内容复制给另一个原始变量,此时这两个变量互不影响,即当一个变量值改变时,另一个变量不会因此而发生任何变化。 - JavaScript引用值:
引用值是指一些复合类型数据的值,包括Object,function,Array,RegExp,Data,引用值于原始值不同,引用值把引用变量存储在栈中,而实际的对象存储在堆中。每一个引用变量都有一根指针指向其堆中的实际对象。 - 总结:
JavaScript中对象到字符串的转换经过了如下步骤: - 如果对象具有toString()方法,则调用这个方法;如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
- 如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JavaScript会调用valueOf()方法。如果存在这个方法JavaScript调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
- 否则,JavaScript无法从toString()或valueOf()获得一个原始值,因此这是它将抛出一个类型异常错误。
———————————————————————————————————— - 在对象到数字的转换过程中,JavaScript会优先尝试使用valueOf()方法:
- 如果对象具有valueOf()方法,后者返回一个原始值,则JavaScript将这个原始值转换为数字(如果需要的话)并返回这个数字。
- 否则,如果对象具有toString()方法,后者返回一个原始值,则JavaScript将其转换并返回。
- 否则,JavaScript抛出一个类型异常的错误。
- 空数组会被转换为数字0
————————————————————————————————————— - JavaScript中的 " + " 运算符可以进行数学加法和字符串连接操作。如果它的其中一个操作数是对象,则JavaScript将使用特殊的方法将对象转换为原始值。"=="相等运算符与此类似。如果将对象和一个原始值比较,则转换将会遵照对象到原始值的转换方式进行。
- 日期类是JavaScript语言核心中的唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。对于所有非日期的对象来说,对象到原始值的转换基本上是对象到数字的转换(首先调用valueOf()),日期对象则使用对象到字符串的转换模式,然而这里的转换和上文讲述的并不完全一致:通过valueOf()或toString()返回的原始值将被直接使用,不会被强制转换为数字或字符串。
- 和"==“一样,”<"运算符以及其他关系运算符也会做对象到原始值的转换,但要除去日期对象的特殊情形:任何对象都会首先尝试调用valueOf(),然后调用toString()。不管得到的原始值是否直接使用,它都不会被强制转换为数字或字符串。
- "-"会把它的两个操作数都转换为数字
var now = new Date();
typeof(now + 1);//"string":"+"将日期转换为字符串
typeof(now - 1);//"number":"-"使用对象到数字的转换
now == now.toString();//true:隐式和显示的字符串转换
now > (now -1);//true:">"将日期转换为数字
3.9 变量声明
在JavaScript中使用一个变量应当先声明。变量是使用关键字var来声明的
var i;
var sum;
也可以使用一个关键字来声明多个变量:
var i , sum;
还可以将变量的初赋值和变量的声明合写在一起:
var message = "hello";
var i = 0, j = 0, k = 0;
如果未在var声明语句中给变量指定初始值,那么虽然声明了这个变量,但在它存入一个值之前,它的初始值就是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);
JavaScript变量可以是任意数字类型
var i =10;
i = "ten";
3.10 变量作用域
全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的;
在函数内声明的变量只有在函数体内有定义,它们是局部的,作用域是局部性的,函数参数也是局部变量,它们只在函数体内有定义。
在函数体内:局部变量的优先级高于同名的全局变量,如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就会被局部变量所遮盖。
var scope = "global";
function checkscope(){
var scope = "local";
return scope;
}
checkscope(); //"local"
尽管在全局作用域声明变量时可以不写var,但是声明局部变量时必须使用var语句
scope = "global";
function checkscope2(){
scope = "local";
myscope = "local";
return [scope,myscope];
}
checkscope2();//Array [ "local", "local" ]:产生了副作用
scope;//"local":全局变量修改了
myscope;//"local":全局变量名空间搞乱了
函数定义是可以嵌套的。由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function nested(){
var scope = "nested scope";
return scope;
}
return nested();
}
checkscope();//"nested scope"
3.10.1 函数作用域和声明提前
变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
function test(o){
var i = 0; //i是在整个函数体内是有有定义的
if(typeof o == "object"){
var j = 0; //j在函数体内是有定义的,不仅仅是在这个代码段内
for(var k =0; k< 10; k++){//k在函数体内是有定义的,不仅仅在循环内
console.log(k);//输出数字0~9
}
console.log(k);//k已经定义了,输出10
}
console.log(j);//j已经定义了,但可能没有初始化
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。这意味着变量在声明之前甚至已经可用。JavaScript这个特性被非正式地称为声明提前,即JavaScript函数声明里的所有变量(不涉及赋值)都被提前至函数体顶部。
var scope ="global";
function f(){
console.log(scope);//undefined
var scope = "local";
console.log(scope);//"local"
}
f();
将函数内的变量声明提前至函数体顶部
function f(){
var scope;//这里存在变量,其实是undefined
scope = "local";
console.log(scope);
}
f();//"local"
3.10.2 作为属性的变量
当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性,当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。如果没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的全局变量是全局对象的正常的可配置属性,并可以删除它们:
var truevar = 1;//声明一个不可删除的全局变量
fakevar = 2;//创建全局对象的一个可删除属性
this.fakevar2 = 3;//创建全局对象的一个可删除属性
delete truevar;//false:变量没有删除
delete fakevar;//true:变量被删除
delete fakevar2;//true:变量被删除
JavaScript全局变量是全局对象的属性,JavaScript可以允许使用this关键字来引用全局对象,却没有方法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不可见的内部实现。
3.10.3 作用域链
- 全局变量在程序中始终都是有定义的,局部变量在声明它的函数体内以及其所有嵌套的函数内始终是有定义的。
- 每一段JavaScript代码都有一个与之关联的作用域链(scope chain)。
- 这个作用域链是一个对象列表或者链表,这组对象定义了这段代码"作用域中"的变量。当JavaScript需要查找变量x的值的时候(这个过程称作"变量解析"),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,JavaScript会继续查找链上的下一个对象…以此类推,如果作用域上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。
- 在JavaScript的最顶层代码中(不包含任何函数定义内的代码),作用域链由一个全局对象组成。
- 在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。
- 在一个嵌套的函数体内,作用域链上至少有三个对象。
- 当定义一个函数时,它实际上保一个作用域链,当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的"链"。
- 对于嵌套函数,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。