JavaScript——数据类型
JavaScript共有6种数据类型,其中有5种基础数据类型。
-
数值型(Number)
-
字符串型(String)
-
布尔型 (Boolean)
-
null
-
Undefined
-
Object
除Object其他5种均为基础数据类型(原始类型),对于Object,又称复杂类型。
数值型
JavaScript中,数值的内部结构为64位的浮点小数,不过实际编程中,使用整数情况更多。不管内部结构如何,从JavaScript的代码上来看,只要是写为整数就能够作为整数使用,而不必考虑是否是浮点数的问题。因为所有的数值都是浮点小数,所以其运行效率多少会有些下降。因此对于比较注重运行效率的程序来说,JavaScript可能并不是合适的选择。
var n1 = 1; //将数值1赋值给变量n
var n2 = 2; //将数值2赋值给变量n2
n1+n2; //将变量n1的值与变量n2的值相加
console.log(typeof n); //result:number
console.log(typeof 1); //result:number
通过typeof运算符来判断数值的类型。对于数值来说,typeof运算符返回的结果是字符串值“number”。
数值运算
对于数值可以进行+、-、*、/、四则运算。通过%符号则可以进行求模运算。
需要注意的是,尽管从代码上来看进行的是整数运算,但是其实在内部进行的仍然是浮点数运算,例如,对0做除法并不会得到错误的结果,而是会得到一个特殊的数值。
在JavaScript标准对象中有一个Math对象。该对象定义了圆周率PI、自然对数的底数E等数学常量,以及一些相关的数学函数。例如,可以通过Math.pow函数计算2的10次方。
这里例举一些有关浮点数的常见注意事项。在使用其他程序设计语言中的double型或是float型时也同样注意这些问题。在其他的程序设计语言中由于需要开发者显式地使用浮点数值,因此在使用时会更加注意,而在JavaScript中,由于总是会使用浮点小数,因此这些问题常常会被忽视。
首先,要说一些可能让不了解浮点数的人感到惊讶的事实。对于浮点数来说,有时候并不能正确地表达小数点以后的部分。实际上,能够正确表达一个数的值反而是一种例外,大部分情况下浮点数只能表达数值的近似值。
console.log(0.1+0.2); //结果不是0.3
console.log((0.1+0.2)==0.3); //两者并不相等
// result:false
console.log((0.1+0.2)===0.3); //两者并不相等
// result:false
var i = console.log(1/3); //1除以3的近似结果
// result:0.3333333333333333
对于浮点数来说,要执行正确的实数运算是不可能的。这并不是个应该如何避免的问题,而是从原理就是无法回避的。对于整数的情况,则可以保证在53位的范围内有正确的结果。因此,如果只是使用53位以内的整数的话,就不会有任何问题。
数值类
正如存在字符串类(String类),JavaScript中也存在数值类(Number类)。字符串值和字符串类在经过隐式数据类型转之后,就基本能够以相同的方式使用,与此类似,经过数据类型转换之后,数值和数值对象也能够被视为等价的。
例如,可以对一个数值调用方法。和字符串对象的情况类似,在内部其实是隐式生成了数值对象,不过也不需要对此特别注意。
console.log((1).toString()); //为了区分小数点和点运算符使用了括号
console.log(typeof (1).toString()); // result:string
可以new运算符来显式的生成数值对象。和字符串对象的情况类似,这将引起等值运算的混乱,所以如果没有特殊用途,不建议使用显式的数值对象。
var nobj = new Number(1);
var nobj1 = new Number(1);
console.log(nobj==nobj1);
console.log(nobj===nobj1); //虽然值相同,但是引用对象不同,因而结果为false
console.log(nobj==1); // true
// 在进行数据类型转换下的等值结果为true(不严格相等会隐式进行数据类型转换)
console.log(nobj1===1); // false
// 在不进行数据类型转换下的等值结果为false (严格相等下不会进行任何变动)
如上,由于会进行隐式数据类型转换,因此数值和数值对象在外表上是没有什么区别的。如有必要,可以通过typeof运算对其进行判断。对数值对象执行typeof运算的结果:object
var n = new Number(1);
console.log(typeof n); //object
调用Number函数
和String函数类似,以通常的方式调用Number函数的话,将返回相应的数值。在需要进行显式数据类型转换的时候,可以使用Number函数。
var n = Number(1);
console.log(typeof n); //result:number
console.log(n==1); //result:true
console.log(n===1); //result:true
var n = Number("1"); //NaN
console.log(typeof n); //result:number
如果参数无法被转换为数值类型,Number函数返回的结果将是NaN。使用new运算符(构造函数调用)的情况也是如此。
var n = Number("x");
console.log(n); //result:NaN
console.log(typeof n); //result:number
// 这里很特别,即JS判定n不是一个数值,但是n是一个数值类型
var nobj = new Number("x");
console.log(nobj); //result:NaN
console.log(typeof nobj); //result:object
Number“类”的函数以及构造函数调用
// Number([value])————将参数value转换为数值类型
// new Number([value])————生成Number的实例(Number对象)
console.log(Number(1)); //result:1
console.log(typeof Number(1)) //resutl:number
var n = new Number(1);
console.log(typeof n); //result:object
Number类的属性——————属性名说明
prototype————用于原型链
length————值为1
MAX_VALUE————64位浮点小数所支持的正数最大值
MIN_VALUE64————为浮点小数所支持的正数最小值/NaN————含义为Not a Number 的值
NEGATIVE_INFINITY————表示负无穷大的值
POSITIVE_INFINITY————表示正无穷大的值
Number.prototype.x = 1;
var n = new Number();
console.log(n.x); //1
console.log(Number.length); //1
console.log(Number.MAX_VALUE); //1.7976931348623157e+308
console.log(Number.MIN_VALUE); //5e-324
console.log(Number.NaN); //NaN
console.log(Number.POSITIVE_INFINITY); //"Infinity"
console.log(Number.NEGATIVE_INFINITY); //"-Infinity"
Number.prototype对象的属性
边界值与特殊数值
可以通过Number对象的属性值来获知64位浮点数所支持的最大正值和最小正值。如果在其之前添加负号(运算符),就能够获得相应的最大负值和最小负值。
还可以通过Number.MAX_VALUE.toString(16)
来获得相应的16进制数值
浮点小树的特殊数值
从内部结构上看,这3个特殊数值(正无穷大、负无穷大、NaN)都是基于IEEE754标准的比特位数值。虽然它们在形式上属于数值(typeof运算执行结果为number),但是并不能作为数值进行计算。例如,将最大正数值乘2之后能够得到正无穷大,但反之则不成立,正无穷大除以2无法得到最大正数值。
事实上,这3个特殊数值对于任何运算都无法得到通常的数值结果;
console.log(Number.MAX_INTEGER); //result:1.7976931348623157e+308
var inf = Number.MAX_VALUE*2;
console.log(inf); //result:Infinity
console.log(inf/2); //无法得到原值 result:Infinity
console.log(inf*0); //即使乘以0,结果也不是0。 result:NaN
NaN
对NaN进行任何计算,其结果都是NaN,因此,如果在计算过程中出现了一次NaN,最终结果一定是NaN。
console.log(NaN*1);
console.log(NaN*0);
console.log(NaN-NaN);
// 结果均为NaN
NaN的运算还具有更为特别的性质。NaN不但不与其他任何数值相等,就算是两个NaN的等值判断,其结果也为false
console.log(NaN==1); //false
console.log(NaN===1); //false
console.log(NaN==NaN); //false
console.log(NaN===NaN); //false
NaN对于各种比较运算也总是
console.log(NaN>1); //false
console.log(NaN>=1); //false
console.log(NaN>NaN); //false
console.log(NaN>=NaN); //false
由此可见,对于值为NaN的数值是无法进行判断的。不过在JavaScript中预定义了一个全局函数isNaN()。
isNaN函数返回值为true的条件是其参数的值为NaN,或是在经过数据类型转换后值为NaN;
console.log(isNaN(NaN)); //true
var nobj = new Number(NaN); //值为NaN的Number对象
console.log(typeof nobj); //result:object
console.log(isNaN(nobj)); //result:true
预定义全局函数isFinite可以对3个特殊数值(即NaN与正负无穷大)之外的数值进行判断。
console.log(isFinite(1)); //result:true
console.log(isFinite(NaN)); //result:false
console.log(isFinite(Number.MAX_VALUE*2)); //result:false
console.log(isFinite(Infinity)); //result:false
可以通过特定的运算来得到特殊数值结果。不过实际编程过程中,故意产生Infinity或是NaN的情况十分少见。事实上,不小心在运算过程中生成的特殊数值往往是发生错误的根源。
Infinity和NaN都是预定义的全局变量,所以从语法规则上看,其值是可以改变的(当然不推荐这么做)。不过即使改变了他们的值,只要进行如下运算,就能够很容易的恢复初始值。
//ECMA5
NaN = 7;
console.log(NaN); //result:NaN
Infinity-8;
console.log(Infinity); //result:Infinity
在ECMAScript第5版中,已将NaN和Infinity改为了只读变量,因此不能在对它们进行数值更改,但是,对它们进行赋值的过程中并不会报错。
数值完
字符串型
字符串字面量
var a = "abc"; //将字符串赋值给变量a
alert(a);
var s = "abc"; //将字符串赋值给变量s
alert(s);
特殊字符通过转义字符来表示。转义符是反斜杠(\)。例如,n\是换行符的表达方式。
把字符串值作为右值出现在=运算符右侧,就能够将其赋值给=号左侧的变量;如果在右侧书写的值作为字符串值的变量,也一样能够将其赋值给等号左侧的变量。
var a = "abc";
var a2 = a;
alert(a2); //abc
var s1 = "012";
var s2 = "345";
var s3 = s1+s2; //字符串连接
alert(s3); //"012345"
var x = "012";
x+="345"; //+=运算符可以在连接字符串时同时进行赋值
alert(x); //"012345"
var num = "012";
var num2 = num;
num+="345"; //变量num的值为"012345"
alert(num2); //变量num2的值依旧是"012"
字符串型的比较
JavaScript有两种等值运算符,即==和===。与之对应也有两种不等运算符!==和!=
区别在于,比较的时候是否会进行数据类型的转换。===比较时候不会对数据类型进行转换。在ECMAScript标准中,将其称为严格相等。如果考虑字符串之间的比较,===和==是没有区别的。两种方式都会判断字符串的内容是否一致。
var s1 = "012";
var s2 = "0";
var s3 = s2+"12";
console.log(s1==s3); //true
console.log(s1===s3); //true
console.log(s1!=s3); //false
console.log(s1!==s3); //false
此外,还可以对字符串值进行大小比较运算。
var s1 = "abc";
var s2 = "def";
console.log(s1<s2); //true
console.log(s1<=s2); //true
console.log(s1>s2); //false
console.log(s1>=s2); //false
//依照字母权重,具体规则不展开
字符串"类"
JavaScript中字符串型是一种内建类型。不过JavaScript的字符串也存在字符串类
字符串类的名称成为String。字符串型和string类型之间也同样支持隐式类型转换。
var s = "012"; //形式上类似于读取字符串值的属性
console.log(s.length); //3
"012".length; //3 形式上类似于读取字符串字面量的属性
上述代码中,内部发生了字符串值到String对象的隐式数据类型转换。——《JavaScript编程全解》
表面上,代码行为和Java相类似,但其实两者在语法意义上不同。Java的字符串字面量生成的是string类的对象,所以这里的点运算符含义其实是一般意义上的对类方法的调用。
另一方面,在字符串值会先被隐式地转换为字符串对象,然后在读取字符串对象的length属性。
字符串对象
使用new运算符,显式生成一个字符串对象。
var sobj = new String("abc"); //生成字符串对象
var s = sobj+"def"; //将字符串对象隐式转换为字符串值
console.log(s); //"abcdef"
由于字符串值能够被隐式转换为字符串对象,所以在实际中几乎不使用new来生成字符串对象。
隐式类型转换也能反向进行。如上,代码中sobj所引用的字符串对象被转换为了字符串值之后,通过+运算符对其进行了字符串的链接。
字符串值和字符串对象之间可以进行隐式类型转换,因此,一般来说不需要在意对象之间的区别。不过正因看起来非常相似,所以会存在一些陷阱,例如在判定两者是否相等上是存在差异的。
对象的相等运算,取决于两者是否引用同一个对象(而非两者内容是否相同)。
var sobj1 = new String("abc");
var sobj2 = new String("abc");
console.log(sobj1==sobj2); //false
console.log(sobj1===sobj2); //false
var sobj3 = new String("sobj3");
var sobj4 = sobj3;
console.log(sobj3==sobj4); //true
console.log(sobj3===sobj4); //true
alert(typeof (sobj1+"")); //string
console.log(sobj1+""==sobj2+""); //这里的"+"会将sobj1对象隐式转换为字符串值
console.log(sobj1+""===sobj2+"");
上面的两个字符串对象sobj1与sobj2在通过+与字符串值连接后,就会进行隐式数据类型转换而变为字符串值,从而结果发生变化。
对于字符串值和字符串对象的等值判断,如果使用的是会进行隐式数据类型转换的==运算,则只会判定其内容是否相同。内容相同为真。
对于比较大小运算,字符串对象和字符串值一样,都是比较其字符串内容。这时候值与对象之间没有区别。
var sobj = new String("abc");
var s = "abc";
console.log(s==sobj); //true 进行隐式类型转换
console.log(s===sobj); //false 不进行隐式类型转换
字符串型over
布尔型
布尔型也被称为逻辑值类型或者真假值类型。布尔型只能够取真(true)和假(false)两种数值。除此之外,其他的值都不被支持。
var flag = 1+1;
console.log(1+1==2); //true
console.log(!1+1==2); //1+1==2不为真 false
console.log(!!1+1==2); //1+1==2不可能不为真 true
true和false定义成了字面量。字面量的使用方法与在代码使用数值的方法类似,将它们理解为等价的行为即可。也就是说,var flag = true与var n = 0,true与0的地位相同。
理解:都是字面量,且都是作为右值使用。
对布尔值进行typeof运算的话,得到结果为“Boolean”。
console.log(typeof true); //boolean
console.log(typeof false); //boolean
布尔类(Boolean类)
布尔类(Boolean类)是一种布尔值的包装类型,(理解为String类,Number类)。
true.toString(); //隐式数据转换为了Boolean对象
值得一提的是,布尔类中并没有什么实用的方法,所以基本上也很少去使用。
和String以及Number一样,他也可以通过new运算符显式的生成布尔对象。不过也与String和Number的情况相似,一般来说没有必要显式的生成布尔对象。
var t = new Boolean(true); //声明布尔对象
console.log(t); //true
console.log(typeof t); //object
console.log(t==true); //此处相等比较运算中,首先将t进行隐式数据类型转换为boolean型,之后进行比较
// true
console.log(t===true); //严格相等比较是不进行隐式数据类型转换的,false
通过调用Boolean函数可以将任意值显式地转换为布尔值。不过根据具体的代码语境,必要时,一个值将会被隐式地转换为布尔值类型,所以一般来说,并不需要显式的进行数据类型转换。
var tval = Boolean(true); //通过函数调用来进行显示的数据类型转换
console.log(typeof tval); //boolean
console.log(tval==true); //true
console.log(tval===true); //true
布尔篇完
Null
null值的意义存在于对象的引用之中。null值最初的含义为"没有引用任何对象"。null型只能取null这一个值。null值是一个字面量。由于只支持null这个值,所以将null型称为一种类型未免有些奇怪。不过从语法规则上看,null型确实是一种数据类型。
console.log(typeof null); //object
null型没有与之相对应的Null类。因此,如果对null值进行点运算,就会产生TypeError异常。
null.toString();
//TypeError:null has no properties
和其他程序设计语言一样,null值可能引发各种各样的错误,其中大部分和数据类型转换以及一些运算有关。
注:对变量赋值null可以解除内存引用;
Undefined
undefined型只能够取undefined这一个值。对undefined值进行typeof运算,结果为:undefined。
console.log(typeof undefined); //undefined
从代码上看,undefined值似乎和null值一样都是一种字面量。但实际上,它并非字面量,而是一个预定义的全局变量。
由于有这样的设定,也就不难理解,可以对变量undefined进行赋值。当然,本身并不应该进行这样的操作
undefined = "abc"; //对名称为undefined的全局变量进行赋值
console.log(undefined); //abc
console.log(typeof undefined); //string
由于undefined在ECMAScript5中变为了只读变量,所以上面的赋值是无效的。但是无需要注意的是,这样的赋值并不会引起错误。
在本书中,将undefined型的值称为undefined值。这与将字符串类型的值称为字符串值是一样的理解。
应当认识到,全局变量undefined与undefined值的关系其实就是:首先有了undefined型的值,之后才将该值赋值到了全局变量undefined。
null是一种字面量,而undefined是一种变量名,这并不是一种偶然。要使一个变量的值为null,就必须将null以字面量的形式赋值给该变量。因此从语言规则角度老说,null必须是一种字面量。另一方面,undefined值最多只能算是某个没有经过显式赋值的变量的初始值。所以根据字面含义,将其称为未定义值或是未初始化值都没有问题。
var u; //单纯只进行声明的变量
console.log(typeof u); //undefined
也就是说,从语法规则上看,undefined这一标识符并不是必需的。这是因为只需将全局变量undefined赋值给没有被复制的变量就可以了。null值指的是没有引用任何对象的状态,尽管从含义上来看是否定的,但仍然是有其含义的。而undefined值则不同,像它的字面意思那样,仅仅指的是一个尚未定义的值。
对于undefined值来说,并不存在与之相对应的Undefined类。因此如果对undefined值进行点运算,会产生TypeError异常。
总结一下出现undefined值的情况。
-
未初始化的变量的值(未赋值);
-
不存在的属性的值;
-
在没有传入实参而调用函数时,该函数内相应参数的值;
-
没有return语句或是return语句中不含表达式的函数的返回值;
-
对void运算符求值的结果;
null值可能会因其程序出错,而undefined值比它更容易引发错误。另情况更为混乱的是,如果对null值和under值做进行数据类型转换的等值运算==,结果为true。此外,对于不进行数据类型转换的等值运算===,其结果为false。
console.log(null==undefined); //true
console.log(null===undefined); //false
undefined值是一种有着非常高的潜在出错风险的语言特性,搜易在使用under值时请多加留心。
对象
对象字面量表达式与对象的使用
可以通过对象字面量表达式来生成一个对象。对象字面量表达式由大括号{}括起,内部有属性名和属性值。
//对象字面量表达式的语法
{键名:键值,键名:键值,.......}
属性名可以是标识符、字符串或是数值。
属性值可以是任意的值或对象(函数)。
{x:2,y:1}; //属性名是标识符
{"x":2,"y":2}; //属性名是字符串
{'x':2,'y':2}; //属性名是字符串
{1:2,2:1}; //属性名是数值
{
x:2,
y:1,
enable:true,
color:{
r:255,
g:255,
b:255
}
}; //各种类型的属性值
ECMAScript第5版中,允许以逗号结尾的对象字面量,而这在ECMAScript3中是被禁止的。避免出现在对象字面量的最后以逗号结尾。
对对象字面量表达式求值所得到的结果,是所生成对象的一个引用。
var obj = {x:3,y:4}; //所生成的对象(对象字面量)的引用赋值给变量obj
console.log(typeof obj); //object
把对象字面量作为右值处理,在赋值表达式左侧书写变量名,就能够将对象的引用赋值给变量
属性访问
可以通过点运算符(.)访问对象引用中的属性。只要在点运算符之后书写属性名,就能够读取相应的属性值。
var obj = {x:3,y:4}; //对象字面量生成对象
console.log(obj.x); //访问对象属性x
var obj2 = {pos:{x:3,y:4}};
console.log(obj2.pos.x); //如果属性值是一个对象,可以通过多次点运算符来读取属性
obj.x = 3; //在赋值表达式左侧书写属性访问表达式(作为左值),就可以将相应的值赋值给该属性
console.log(obj.x);
obj.z = 5; //赋值给不存在的属性名,则将新建该属性并对其赋值
console.log(obj.z);
obj.fun = function test(a,b) { //将函数赋值给属性(方法)
return a+b;
};
console.log(obj.fun(10,20));
属性访问(括号方式)
除了点运算符之外,还可以使用中括号运算符([])来访问属性。[]内是需要访问的属性名的字符串值。
var obj3 = {
time:"2018年1月22日 10:49:39",
name:"dh",
age:20,
addr:"LZ"
};
console.log(obj3["time"]+obj3["name"]); //注意,[]内是键名的字符串
var name_me = "name"; //[]内也可以是字符字面量,也可以是值为字符串的变量
console.log(obj3[name_me]); //等同于:obj3["name"] or obj3.name
obj3["5"] = 5;
console.log(obj3["5"]); //也能用于赋值表达式的左侧(若不存在该属性则新建属性)
什么情况下必须使用方括号表访问对象的属性?
- 使用了不能作为标识符的属性名的情况
- 将变量的值作为属性名使用的情况
- 将表达式的求值结果作为属性名使用的情况
var test = {
1:"one",
name:"name",
var:"var",
'2 333':2333,
"foo-bar":5,
x:"x"
};
test["1"]; // "one"
test.1; // Unexpected number错误
test[1]; // Unexpected number错误
test["name"]; // "name"
test.name; // "name"
test.var; // "var"
test["var"]; // "var"
test["2 333"]; // 属性名含有空格必须用方括号
// 属性名不能使用数字!
test.foo-bar; // 含有横杠的属性名点运算会引起错误,会解析为:obj.foo减去bar,引发错误
test["foo-bar"]; // 5
// 下面展示将变量的值作为属性名使用的情况
var key = "name";
test[key]; // "name" 方括号内的变量存储的值依旧是引号包裹的字符串,方括号正常访问
方法
可以把任意类型的值、对象或者函数赋值给对象的属性。对匿名函数表达式求值所得到结果是对函数对象的引用。
function sum(a,b) {
return a+b;
}
var obj = {};
obj.fun1 = sum;
obj.fun2 = function time(a,b) { //实际上,将time函数表达式求值后,得到一个关于time函数的引用并将其赋值给fun2属性
return a*b; //fun2得到的并非是这个函数,而是这个time函数的引用
};
console.log(obj.fun1(2,3));
console.log(obj.fun2(10,20));
//两者都是生成一个没有名称的函数体(function对象)之后,在赋值给属性。
new表达式
JavaScript中new表达式的作用是生成一个对象
var obj = new Object();
typeof obj;
和通过对象字面量表达式生成的对象一样,通过new表达式生成的对象,其属性能够被读取
以直观方式来理解,关键字new之后所写的是类名。不过JavaScript中没有类的概念,根据JavaScript语法规则,new之后所写的是函数名。在new之后写函数名的话,就会把该函数作为构造函数来进行调用。
JavaScript的语言特性没有"类"的概念。但是未来便于理解,将用类这个词称呼那些可以被视作类的概念。在本书中,将用"类"称呼那些实际上将会调用构造函数的Funcion对象。此外,在强调对象是通过构造函数而生成的时候,会将这些被生成的对象称作对象实例以示区别。
综上所述,完全可以认为,new Object()的作用是生成一个Object类的实例。
数组
数组是一种用于表达有顺序关系的值的集合的语言结构。在JavaScript中,数组并非一种内建类型。相对地,JavaScript支持Array类,所以数组能够以Array类的实例的形式实现。不过,由于有数组字面量的表达方式,所以一般情况下,只需将其作为内建类型使用即可。
var arr = [1,100,83];
console.log(arr[1]); //读取索引值为1的元素
arr[1] = 200;
console.log(arr[1]); //为索引值为1的元素赋值
var n = 1;
console.log(arr[n]); //与arr[1]含义相同
console.log(arr[n+1]); //等价于:arr[2]
* 理解对象引用
对象表达式的值并非对象本身,而是一个指向对象的引用。
如图示,基本类型值直接存储在变量中,而对象不是。对象的值中存储的是指向对象的引用(变量b)。
由于对象的值其实是引用,所以把一个对象赋值给一个变量,实际上会产生该对象引用的一个副本,而不会赋值对象本身。换句话说,对象赋值不会产生新对象。想想看,对象的赋值与基本类型值赋值过程并没有不同。变量间的赋值就是把保存一个盒子里的东西赋值一份再保存到另一个盒子中,而该盒子中存储的可能是数值,也可能是一个引用。下图表示基本类型赋值和对象赋值,可以想一想。
对象与其他类型值的第二个重要的区别是:
对同一个对象字面量的每次求值,都会产生一个新对象。
- 如上:脚本声明了三个变量,创建了两个对象。虽然两个对象拥有相同的属性,每个属性的值也相同,但它们却是两个不同的对象,因此这个脚本会创建两个对象。
关于变量只保存对象的引用而非对象这一点,不仅在赋值的时候有所体现,在等同性测试的时候也会有所体现。这两种情况下,我们都必须搞明白。对于测试表达式x===y,我们想知道x和y中等值是否相同。
var a = {x:1,y:2}; // 声明对象字面量1
var b = a; // 将变量a引用对象的引用赋值给变量b
var c = {x:1,y:2}; // 声明对象字面量2
alert(a===b); // true
alert(a===c); // false
// 1. 即使两个对象的内部结构相同,但是比较结果:false
// 2. 相等运算,比较的是引用的对象是否来自同一个对象,a、b变量引用同一个对象,全等运算为:true
// 3. 值得一提的是,a,b变量操作过程中,并没有产生新对象
// 再次强调!对象相等运算比较的是,引用对象来源是否是同一个对象
如下,一个对象可以同时被多个变量引用,因此通过其中任何一个变量都可以修改对象的属性,也都可以查看修改后的结果。
var a = {x:1,y:2};
var b = a;
var c = {x:1,y:2};
b.y = 3;
console.log(a.y); // b的引用指向a引用的对象,因为它们引用同一个对象
Question:对表达式{x:1,y:2}=== {x:1,y:2}求值,解释结果。
Answer:false:使用{}大括号就已经声明了对象字面量,本质上和上面的变量a,c引用的对象是相同的,所过结果为假。
可见个人Segmentfault
JS数据类型
typeof和instanceof的作用和区别?
typeof 运算符
鉴于ECMAScript是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof就是负责提供这方面信息的运算符。对一个值使用typeof操作符可能返回下列某个字符串:
- undefined ——如果这个值未定义;
- boolean——如果这个值是布尔值;
- string——如果这个值是字符串;
- number——如果这个值是数值;
- object——如果这个值是对象或null;
- function——如果这个值是函数。
例:
var message = "some string";
console.log(typeof message); //string
console.log((typeof message)); //string
console.log(typeof 95); //number
这几个例子表明,typeof运算符的操作数可以是变量,也可以是数值字面量。注意,typeof是一个操作符而不是函数,因此例子中的圆括号尽管可以使用,但不是必须的。
有些时候,typeof操作符会返回一些令人迷惑但技术上却正确的值。比如,调用typeof null
会返回object,因为特殊值null被认为是一个空的对象引用。Safari5和Chrome7之前的版本在对正则表达式调用typeof操作符都会返回function,而其他浏览器在这种情况下会返回object。
从技术角度讲,函数在ECMAScript中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof操作符来区分函数和其他对象是有必要的。
instanceof
虽然在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道他是什么类型的对象,为此,ECMAScript提供了instanceof操作符。
语法:result = variable instanceof constructor
如果变量是给定引用类型的实例,那么instanceof操作符就会返回true。
console.log(person instanceof Object); //变量person是Object吗?
console.log(colors instanceof Array); //变量colors是Array吗?
console.log(pattrern instanceof RegExp); //变量pattern时RegExp吗?
根据规定,所有引用类型的值都是Object的实例,因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。当然,如果使用instanceof操作符检测基本类型的值,则该操作符始终会返回false,因为基本类型不是对象。
NaN是什么? 有什么特别之处?
Null类型是第二个只有一个值的数据类型,这个特殊的值是null。从逻辑角度看,null表示一个空对象指针,而这也正是使用typeof操作符检测null值时会返回“object”的原因。
var car = null;
console.log(typeof car); //object
如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其他值。这样一来,只要检查null值就可以知道相应的变量是否已经保存了一个对象的引用。
if (car != null) {
// 对car对象执行某些操作
}
实际上,undefined值是派生自null的,因此ECMA-262规定它们的相等性测试要返回true。
console.log(null == undefined); //true
null和undefined之间的相等操作符总是返回true,不过要注意的是,这个操作符出于比较的目的会转换其操作数,即不严格相等的隐式数据类型转换。
尽管null和undefined之间有这样的关系,但它们的用途完全不同。无论什么情况下,都没有必要把一个变量显式的设置为undefined,但对null却可以。
如何把非数值转化为数值?
有3个函数可以把非数值转换为数值:
- number() —— 可用于任何数据类型
- parseInt() —— 用于将字符串转换为数值
- parseFloat() —— 用于将字符串转换为数值
number();
number的转换规则:
-
如果是boolean值,true和false将分别转换为1和0;
-
如果是数字,只是简单的传入和返回;
-
如果是null值,返回0;
-
如果是undefined,返回NaN;
-
如果是字符串,则:
- 如果字符串中包含数字(包括前面带正号或负号情况),则将其转换为十进制数值
console.log(Number("1")); // 1 console.log(Number("123")); // 123 console.log(Number("011")); // 11 前导的0被忽略了
- 如果字符串包含有效的浮点格式,如1.1,则将其转换为对应的浮点数值
console.log(Number("1.1")); // 1.1 console.log(Number("0.12")); // 0.12 console.log(Number("10.00000")); // 10
- 如果字符串是空的(不包含任何字符,包括空格符),转换为0
- 如果字符串中包含除上述格式之外的字符,则将其转换为NaN。
-
如果是对象,则会先调用对象的
valueOf()
方法,然后按照上面规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()
方法,然后再次按照规则转换返回的字符。
ParseInt();
由于Number()
函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()
函数。parseInt()
函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()
就会返回NaN;也就是说,用parseInt()
转换空字符串会返回NaN(Number()
对空字符串返回0)。如果第一个字符是数字字符,parseInt()
会解析第二个字符,直到解析完所有后续字符后者遇到了一个非数字字符。例如,"12345blue"会被转换为1234,因为blue会被完全忽略。类似的,“22.5”会被转换为22,因为小数点不是有效的数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别各种整数格式。也就是说,如果字符串以"0x"开头且后跟数字字符,就会将其做一个十六进制整数;如果字符串以"0"开头且后跟数字字符,则会将其当做一个八进制数来解析。
var num1 = parseInt("1234blue"); //1234
var num2 = parseInt(""); //NaN
var num3 = parseInt("0xA"); //16 十六进制
var num4 = parseInt(22.5); //22
var num5 = parseInt("070"); //56 八进制
var num6 = parseInt("0xf"); //15 十六进制
在ECMAScript5中,parseInt()已经不具有解析八进制值的能力,因此前导的零会被认为是无效,从而将这个值当成"70",结果就得到十进制的70.在ECMAScript5中,即使是在非严格模式下也会如此。
为了消除在使用parseInt()函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(多少进制)。
var num = parseInt("0xAF",16) //175
实际上,如果指定了16作为第二个参数,字符串可以不带前面的"0x",如下所示:
var num1 = parseInt("AF",16); //175
var num2 = parseInt("AF"); //NaN
第一个转换成功,第二个则失败。因为第一个传入了基数,明确告诉parseInt()要解析一个十六进制格式的字符串;而第二个转换发现第一个字符不是数字字符,因此就自动终止了。
指定基数会影响到转换的输出结果。例如:
var num1 = parseInt("10",2); //2 按二进制解析
var num2 = parseInt("10",8); //8 八进制
var num3 = parseInt("10",10); //10 十进制
var num4 = parseInt("10",16); //16 十六进制
parseFloat()
parseFloat()也是从第一个字符(位置0)开始解析每个字符。而且也是一直解析到字符末尾,或者解析到遇见一个无效的浮点数字字符位置。也就是说,字符串中第一个小数点是有效的,而第二个小数点就是无效的了。"22.34.5"会被转换为:"22.34"。
除了第一个小数点有效之外,parseFloat()始终会忽略前导的0。parseFloat()可以识别前面所有的浮点数格式,包括十进制整数格式。但十六进制格式的字符串则始终会被转换为0。
由于parseFloat()只解析十进制值,因此它没有用第二个参数指定基数的用法。最后一点:如果字符串包含一个可解析为整数的数(没有小数点,或者小数点后都是0),parseFloat()会返回整数。
var num1 = parseFloat("1234blue"); //1234
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000
其他实例:
数值:
7 * false // 0 false隐式转换为0
7 * true // 7 true隐式转换为1
7 * "5" // 35 字符串转换为数值
7 * " " // 0 空字符串转换为0
7 * "dog" // NaN 无法转换的字符串转换为NaN
7 * null // 0 隐式转换为0
7 * undefined // NaN 同上
7 * {x:1} // NaN 对象会先调用valueOf(),转换为NaN
布尔值:
!5 // false 5转换为true
!0 // true 0转换为false
!"dog" // false 字符串转换为true
!"" // true 空字符串转换为false
!" " // false 字符串转换为true
!null // true null转换为false
!undefined // true undefined转换为0
!{x:1} // false 等于执行了:Boolean({x:1}),转换为true
这里是字符串:
"xyz"+false // "xyzfalse" +号前有字符串,则+号为字符串连接符,后续部分:false.toString()
"xyz"+true // "xyztrue" 同上
"xyz"+7 // "xyz7"
"xyz"+null // "xyznull"
"xyz"+undefined // "xyzundefined"
"xyz"+{x:1} // xyz[object Object] 等价于:"xyz"+({x:1}.toString())
"xyz"+[1,2,3] // xyz1,2,3 [1,2,3].toString(),转换为字符形式的:"1,2,3"
通过以上实验,可以看出操作符搭配错误的类型,不仅不会报错,还会正常显示。通过类型转换以有意义的方式处理了错误类型的值。
由于存在隐式数据类型转换,JavaScript被称为弱类型编程语言。在强类型编程语言中,由错误类型值构成的表达式会导致错误。如果脚本里含有这种"病句",要么不会被允许执行,要么干脆直接停止工作,要么会在问题表达式被求值时抛出异常。
break与continue有什么区别
break和continue语句用于在循环中精确地控制代码的执行。其中,break语句会立即退出循环,强制继续执行循环后面的语句。而continue语句虽然也是立即退出循环,但退出后会从循环的顶部继续执行。
var num = 0;
for (var i=1;i<10;i++) {
if (i % 5 == 0) {
break;
}
num++;
}
console.log(num); //4
这个例子中的for循环会将变量i由1递增至10,在循环体内,有一个if语句检查i的值是否可以被5整除。如果是,则执行break语句循环。另一方面,变量num从0开始,用于纪录循环执行的次数。在执行break语句后,要执行的下一行代码是alert函数,结果显示4。说明i等于5事,num++执行了4次;而break语句的执行导致了循环在num再次递增前就退出了。如果在这里把break替换为continue,则可以看到另一种结果。
var num = 0;
for (var i=1;i<10;i++) {
if (i % 5 == 0) {
continue;
}
num++;
alert(i+" | "+num);
}
console.log(num); //8
结果显示8,num++总共执行了8次,当变量i等于5,循环会在num再次递增之前退出,但接下来执行的是下一轮循环,i的值等于6。循环再次执行,直到i等于10时自然退出。num之所以为8,是因为continue语句导致它少递增了一次。
练习:
var a = 1, b = 2, c = 3;
var val = typeof a + b || c > 0;
console.log(val); // "number2"
var d = 5;
var data = d ==5 && console.log('bb');
console.log(data);
var data2 = d = 0 || console.log('haha');
console.log(data2);
var x = !!"Hello" + (!"world", !!"from here!!");
console.log(x);
猜输出
变量a,b,c初始化后,首先是一个or判断。
JS引擎从左至右解析,按照优先级,typeof运算符最大,先计算(typeof a),则:"number" + b || c > 0;
加法运算符前是一个字符串,此时加法运算符作字符串连接符处理,则:"number2" || c > 0;
由于||运算符存在短路,将不计算右边表达式,短路退出,输出左边表达式的结值。
答案:"number2"
将5赋值给变量d;
d == 5 && console.log('bb')
——对于这段表达式,console.log()优先级最高,先执行console.log,从后面的控制台弹出也验证了判断,因为显示:"bb"。执行完console.log后,这里注意!JS将先从左边(d==5)开始计算,不信可以将表达式改为:false && console.log('bb')
,这样更改,bb则不会弹出,也就是JS判定左边为假后,短路退出,不会对右边表达式做布尔转换和判断。说明JS已经对左边d==5做了计算,为真,才会对右边表达式进行计算,此时已经执行过左边表达式的计算了。
现在问题主要集中在,右边的表达式这里。
我原来的答案是,相当于:Boolean(console.log('bb'))
,结果应该为真,而后整个&&为真,但是答案却是undefined
。
我意识到是自己判断右边表达式出了差错,于是改写了一些。
console.log(true && console.log('bb'))
当然结果是一样的,那么问题就在console.log('bb')这里了。
我们首先对console.log('bb')做typeof,得到答案是undefined。
console.log(
typeof console.log(''bb)
)
然后拿出红宝书查看相关内容,找到如下说明。
逻辑与操作可以应用任何类型的操作数,而不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与操作就不一定返回布尔值;
- 如果第一个操作数是对象,则返回第二个操作数
- 如果第二个操作数是对象,则只有在第一个操作数的求值结果为true的情况下才会返回该对象。
- 如果两个操作数都是对象,则返回第二个操作数。
- 如果第一个操作数是null,则返回null。
- 如果第二个操作数是NaN,则返回NaN。
- 如果第一个操作数是undefined,则返回undefined。
既然typeof判断是undefined,那么对于这个情况,逻辑与只能返回undefined(第一个操作数为真,第二个操作数undefined)。
所以d == 5 && console.log('bb')
答案:"bb",”undefined“
var data2 = d = 0 || console.log('haha');
先弹出console没什么好说的,计算d=0,为假,计算右边表达式,剩下的同上。
答案:"haha","undefined"
var x = !!"Hello" + (!"world", !!"from here!!");
可以看到,每个表达式都有!
符号,那么可以看成最终形式为:布尔值的相加
如:var x = true + true
现在就是判断+号两边的布尔值了,字符串布尔值转换为true,两次取反,不变,左边为true
右边那么一坨,括号内,逗号操作符作用是返回表达式中的最后一项。那么:
(!"world", !!"from here!!")
返回最后一项是!!"from here!"
,布尔转换后值为true
。
则:var x = true + true
那么,两个true相加,true转换为1,结果不言而喻了
答案: 2
网友评论