本章主要涉及的是在开发中最经常涉及的三种值:
- 数组 Array
- 字符串 String
- 数字 Number
这三种值也是在其他语言中经常用到的,所以需要重点的了解一下这些值在JavaScript中的表现
一 数组
对比Java这种强类型语言(本人是做Java开发出身的,除了JS外比较熟的语言也就是Java了。),JavaScript在使用数组时候并不需要设定任何类型,其数组可以容纳任何JavaScript中合法类型的值
// 甚至是在一个数组内容纳多个不同类型值,真正的"有容乃大"var youRongNaiDa = ["a",1,undefined,null,[1],{}]youRongNaiDa.length; // 6youRongNaiDa[1]; // 1youRongNaiDa[4][0]; // 1
且数组在声明的时候无需设定大小,添加新元素时也无需创建新数组进行扩容。
var a = [];a[0] = 1;a[1] = 2;a.length; // 2
但注意的是,在创建”稀疏”数组时候(即含有空白或空缺单元的数组),使用时候要小心
var a = [];a[3] = 2;a[1]; // undefineda.length; // 4
上面代码中,a[1]明显是空缺单元但程序显示的值为undefined。这和将undefined赋给a[1]还是有区别的。
其次,在 类型 一章说到,Array其实也是 object 下的一个子类型.
所以也可以包含字符串键值属性(但并不影响Array计算其数组长度)
var a = [];a[0] = 1;a["test"] = "test";a[0]; // 1a["test"]; // testa.length; // 1
但注意的是,如果使用的字符串能够强制转化为十进制的数字,则会被当做数字索引来用
var a = [];a["12"] = 1;a.length; // 13
所以通常不要在数组对象内去声明字符串的键值,要用就用真正的对象来。
二 字符串
很多时候字符串都会被当作字符数组来看待,它们确实很相似,都是类数组,都有length属性以及indexOf(…)(es5数组开始支持)和concat(…)方法;
var a = "foo";var b = ["f","o","o"];a.length; // 3b.length; // 3a.indexOf("0"); // 1b.indexOf("0"); // 1a.concat("bar"); // "foobar"b.concat(["b","a","r"]); // ["f","o","o","b","a","r"]
这并不能意味这两者都是”字符数组”
a[1] = "O";b[1] = "O";a; // "foo"b; // ["f","O","o"]
上述例子中,字符串在JavaScript中是不可变的而数组是可以改变的,而且字符串a[1]并不属于合法语法。
字符串在JavaScript中是不可变的,当字符串创建后其字符串的成员函数不会改变其原始值,而是会创建一个新的字符串并返回。而数组的成员函数都是在其原始值上进行操作。
但我们可以借用数组的一些方法来处理字符串,非常好用~~~
var a = "foo";var b = Array.prototype.join.call(a, "-");var c = Array.prototype.map.call(a, function(value){return value.toUpperCase() + ',';}).join("");b; // f-o-oc; // F,O,O
但其中有一个数组反转函数 reverse() 是字符串无法借用的,因为字符串是不可变的
但我们可以换个思路,这也是很经典的一道 JavaScript 面试题 —- 反转字符串
var a = "abcd";var b = a.split("").reverse().join("");b; // "dcba"
注意!!!这仅仅针对简单的字符串数组,如果是比较复杂的Unicode字符可能不适用。
但大部分场景下,这种方式还是简单管用的!(至于复杂的情况再说,这里也不做多于表述)
三 数字
JavaScript中只有一种的数值类型:number,其又包含了”整数”和带小数的十进制数。
这里之所以”整数”加上引号,因为在JavaScript中并没有真正意义上的整数,其整数可以认为是没有显现小数的十进制数,即42.0等同于42”整数”。
var a = 42;var b = 42.3;var c = 0.42;var d = .423; // 可以省略前面的0var e = 43.0;var f = 43.; // 后面的0也可以省略,但这种纯粹的没必要。为了可读性,做个人吧。
赋值特别大或特别小的数字时候可以使用指数格式显示,与toExponential()函数的输出结果一致
var a = 1e10;a; // 10000000000;a.toExponential(); // "1e+10"var b = 1 / a;b; // 1e-10
数字值可以直接使用内置对象Number的方法
var a = 42;// toFixed(): 保留几位小数,返回字符串a.toFixed(1); // "42.0"// toPrecision(): 保留几位有效数字,返回字符串a.toPrecision(1); // "4e+1"
上面方法不仅可以用于数字变量,也可以直接用在数字常量上。
但注意,. 运算符在识别优先级上,”被认为是数字常量部分”的优先级高于”认为是对象属性访问符”的优先级
// 这里可以理解为:JavaScript认为,这边的.是包含在数字常量里// 即在识别时候,该语句被拆分为(42.)toFixed(1);// 明显toFixed方法前没有.运算符来调用,所以这边会被识别为语法错误42.toFixed(1); // SyntaxError// 但加上一些语法处理即可(42).toFixed(1); // "42.0"42..toFixed(1); // "42.0"
这样方式其实实际开发中并不多见,但了解一下也不是啥坏事,起码能装逼。
其次,JavaScript中也支持声明其他进制的数
// es50xf3; // 243的十六进制0Xf3; // 同上0363; // 243的八进制,但严格模式下并不支持,建议也别用太容易混淆了。// es6下的新格式0o363; // 243的八进制0O363; // 同上0b11110011; // 243的二进制0B11110011; // 同上
为了避免混淆,建议使用时候,字母都小写。(毕竟0和大写O太像了,还是小写比较有友好性)。
经典浮点问题 - 0.1 + 0.2 === 0.3
在JavaScript开发或面试中肯定有碰到下面这种情况
0.1 + 0.2 === 0.3; // false
得出了神奇的结果,初学者很多都是摸不着头脑。
其实是因为JavaScript的数字值遵守的是IEEE754规范(采用了这个规范的语言都有这个毛病)
其转换原理大致是:小数部分乘以2取整数,若结果小数部分不为0,则继续乘2取整数直到小数部分为0
// 过程大致如下0.1 * 2 = 0.2 --------------- 取整数 0,小数 0.20.2 * 2 = 0.4 --------------- 取整数 0,小数 0.40.4 * 2 = 0.8 --------------- 取整数 0,小数 0.80.8 * 2 = 1.6 --------------- 取整数 1,小数 0.60.6 * 2 = 1.2 --------------- 取整数 1,小数 0.20.2 * 2 = 0.4 --------------- 取整数 0,小数 0.40.4 * 2 = 0.8 --------------- 取整数 0,小数 0.80.8 * 2 = 1.6 --------------- 取整数 1,小数 0.60.6 * 2 = 1.2 --------------- 取整数 1,小数 0.2...
随便上网找个转换器算一下,就会发现后面是 0011 的无限循环,因此二进制无法精确保留这种0.1的小数。
在 IEEE 754 规范中,在双精确度 64 位下最多存储的有效整数位数为 52 位,会采用 就近舍入(round to nearest)模式(进一舍零) 进行存储。
这必然造成了精度丢失,导致计算结果的精度丢失
0.1 + 0.2; // 0.30000000000000004
这才是产生这个奇怪算式的原因。(只要是无法在52内算出来的小数,都会有这个问题,这是没办法的。)
ES6中提供了精度的常量值:Number.EPSILON,用来做数字计算中的精度丢失比对。
Number.EPSILON的值通常是 2-52。
(0.1 + 0.2) - 0.3 < Number.EPSILON; // true
整数的安全范围
JavaScript中,Number类型能够安全表示的范围:
-9007199254740991 (-(2^53-1)) - 9007199254740991(2^53-1)
ES6中,上限值定义为:Number.MAX_SAFE_INTEGER,下限值定义为:Number.MIN_SAFE_INTEGER。
ES10中,新定义的基本数据类型:bigint,并不受到该范围限制。(爱写多少写多少,越多表现越好!)
整数检测
列举一些常用的整数检测方法
// 检测是否为整数,后面小数都为0也是认为是整数Number.isInteger(42); // trueNumber.isInteger(42.0000000000000); // trueNumber.isInteger(42.1); // false// 检测是否是安全范围内的数Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // trueNumber.isSafeInteger(Math.pow(2,53)); // falseNumber.isSafeInteger(Math.pow(2,53) - 1); // true
不是数字的数字 - NaN
这是个神奇的数字 - NaN!
其字面意思是:“Not a Number”,但在typeof下却识别它是个数字
typeof NaN; // "number"
其NaN是一个内置的境界词,用于指出在数字类型中的错误情况,比如:使用了不是数字类型的值进行计算等。
这时候,肯定有聪明的小伙伴觉得可以直接判断计算的结果是否为NaN来判断计算结果的合法性。
var a = 1 / "foo"; // NaNa === NaN; // false
然后就获得了上面这种神奇的结果!
NaN是一个特殊的值,是JavaScript中唯一一个自反的值,即自己不等于自己。
NaN !== NaN; // true
这时候肯定有经验丰富的小伙伴说用 isNaN(…)来判断
var a = 1 / "foo"; // NaNwindow.isNaN(a); // true
但isNaN(…)方法过于死板 —— 检查参数是否不是NaN,也不是数字!
var a = 1 / "foo"; // NaNvar b = "NaN";window.isNaN(a); // truewindow.isNaN(b); // true - 啊,这......
明显b是个字符串,它也认为这是个NaN。
所以最简单的方法,就是让他自己和自己进行对比即可
var a = 1 / "foo"; // NaNif(a !== a) {console.log("这是一个NaN.");}
无穷数
Java中有个很神奇的地方
1 / 0; // /by zero1 / 0.0; // Infinity1.0 / 0.0; // Infinity
明显1/0在java中是不允许的会报错,而1.0/0.0在程序中是运行的返回值为Infinity。
而在 JavaScript 中,因为只有number一种类型,所以1/0得出结果会是什么呢?
1 / 0; // Infinity
答案还是Infinity。java的原理我就不多概述了,毕竟这里是学JavaScript的文章。
在JavaScript中使用有限数字表示法(即IEEE754规范),当结果超出该安全范围时候,就会用Infinity/-Infinity表示
1 / 0; // Infinity-1 / 0; // -Infinity
零值
在JavaScript中其实有两个0:一个是0(也作+0),另一个是-0.
0 / -3; // -00 * -3; // -0
加减法是永远不会得到-0结果。
老旧版本的浏览器输出时候然而会显示为0。
根据规范,转换成字符串时候,其显示是为0.但”-0”转会数字却会是正常结果
(-0).toString(); // 0Number("-0"); // -0
在实际开发中,其实这些细节都不会有影响。那为啥要有-0呢?
因为在有些程序中的数据需要以级数形式来表示(比如动画帧的移动速度),数字符号位用来表示其他信息(比如运动的方向),所以当0失去了它的符号位,导致它的方向信息也就会丢失。故这里保留了0的符号位防止此类情况。
说白了,保留0的符号位是保证了数字值的方向完整性,连续性。
赋值/传递
首先我们将JavaScript中数据类型进行区分:
- 简单值,即基本类型值,包括:null,undefined,字符串,数字,boolean,ES6中的Symbol以及ES10中的bigint
- 复合值,包括对象和函数(对象下的子类:数组,封装对象等等)。
其简单值的赋值/传递都是通过 复制 的方式,将新的值传给下一个变量。
而复合值的赋值/传递都是通过 引用复制 的方式,传递给下一个变量。
var a = 1;var b = a;a = 2;a; // 2b // 1var c = [1];var d = c;c[0] = 2;c; // [2]d; // [2]
