基础认识
之前就大致说过JS的几种类型。抽象一点讲,所谓的类型是值的内部特征,它定义了值的行为,以此区别其他的不同类型的值。JS中的类型知识,说复杂不复杂,但是其中的类型转换却可以是今人头疼的。
内置类型
JS中有七种内置类型,分别是
- 空值(null)
- 未定义(undefined)
- 布尔值( boolean)
- 数字(number)
- 字符串(string)
- 对象(object)
- 符号(symbol,ES6 中新增) 对象之外的其他类型成为“基本类型”
使用typeof可以用来查看值的类型,但是有一个类型是特殊的
typeof null === 'object' //true
这个原因之前也说过,主要是JS底层二进制表示对象的前三位是000,而null全都都是000,所以判断成对象,正确的做法是
let a = null;
(!a && typeof a === "object"); // true
null本身就是一个“假值”,再加上typeof的判断就可以了。
来看看其他的
typeof function a(){ /* .. */ } === "function"; // true
你会奇怪,function 难道不是内置类型么。但其实,它是Object的子对象,具体讲,函数是可调用的“对象”,这是由其内部的[[Call]]属性决定的。那函数既然是对象,那它就可以拥有属性。
function fn (a,b) {}
console.log(fn.length) //2
没想到吧,函数竟然也有长度,没错,这leng属性就是其声明的参数个数。所以是2。那么还有一种类型--数组是否也是特殊类型么?
typeof [1,2,3] === 'object' //true
显示不是,数组也是对象的一种子类型,数组是按数字顺序来索引的,length表示其元素的个数。
JS的变量是没有类型的, 因为它们可以随时持有任何类型的值,所以值才是有类型的。当变量未赋值时,此时typeof的结果是undefined,但是undefined表不是undeclared(未声明)。
- 变量在作用域中声明了,但是没赋值,是undefined。
- 没有在作用域中声明过的变量,叫做undeclared 有意思的是 typeof对没有定义的变量判断也是 undefined
var a;
typeof a; // "undefined"
typeof b; // "undefined"
这是因为typeof有一个特殊的安全防范机制。避免报错的影响。还有一点,当宿主环境是浏览器的情况下,还可以用window. 来避免报错,未声明的变量默认会挂载在window上。
值
- 数组 数组是JS中常见的使用类型,里面可以收纳各种类型的值,字符串,数字,对象,甚至是数组。数组声明后可以直接赋值,并不需要设定大小,这个大小会跟着变化。但是如果预先设置大小或者在某个位置插入值,就会变成“稀疏”数组,产生很多空白单元,
let c = []
c.length = 10;
c[3] = 3
console.log(c[1]) //undefined
数组通过数组进行索引,当然如果是字符串的键值也行,但是这不算在length里面。
let a =[];
a[0] = 1;
a['fn'] = 2;
a.lenght //1
需要注意的是,如果字符串键值是可以强制转换成10进制的话,它会被当成索引
let a = [];
a['12'] = 12;
a.lenth //13
类数组 例如arguments对象,Dom元素列表等都是类数组,有数组的形状没有数组的方法。正常有以下几种方式
slice();
var arr = Array.prototype.slice.call(arguments) //返回一个数组的副本。
ES6的 Array.from()
var arr = Array.from( arguments );
- 字符串 字符串和数组很像,有很多和数组同名的方法,如indexOf,concat等。但是数组是不可变的,那些函数并不会改变其原始值,而是返回并创建一个新的字符串。而数组的函数大部分是在其原始值上面修改的。
数组和字符串可以相互转换
let a = 'hahahaha'
a.split('') //转为数组
.... //可进行一些数组的方法操作
.join('') //数组再转为字符串
- 数字 JS的数字是包含"整数"和带小数的十进制数。但是JS又没有真正意义上的整数,它无法就是没有带小数的点的十进制数而已,所以40.0等于40。
JS的数字类型是基于IEEE754标准来实现的,这个标准通常被称为”浮点数“,而JS用的是“双精度”格式,即64位二进制。
toFixed() 可指定小数部分返回的显示位数。返回的值是指定数字的字符串形式。数字常量可以调用一些数字的方法
// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42.tofixed(3)是无效语法,因为 . 被视为常量42. 的一部分,所以没有通过 . 来运行toFixed方法 。而最后一个42..没有这个问题是因为第一个 . 是number的一部分,而第二个 .就是属性访问符。
42 .toFixed( 3 ) //中间隔了空格也行
浮点数问题
0.1 + 0.2 === 0.3; // false
这个问题不光JS有,很多使用了IEEE754标准的语言也有这个问题。其本质原因是 二进制浮点数中的0.1和0.2并不精确,相加的结果是 0.30000000000000004这样的接近数字
当然绝大部分程序处理的都是整数的操作,最大也不过百亿万亿,所以其实不用担心JS的精度问题,此时的JS的数字类型是安全的。
那么如何判断 0.1 + 0.2 和 0.3 相等呢
常见的方法是设置一个误差范围值,通常称为“机器精度”,这个值通常是 2^-52 (2.220446049250313e-16)。
ES6之前可以使用Math.pow(2,-52),ES6则可以使用Number.EPSILON
function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b ); //true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false
Number.EPSILON 这个值表示了1 与可表示的大于 1 的最小的浮点数之间的差值,大约也就是2^-52,所以当数字相加之和 和这个值之间的误差小于Number.EPSILON时,就可以判定两数相等了,因为误差太小了。
JS的安全整数范围在 Number.MAX_VALUE和Number.MIN_SAFE_INTEGER之间,就是2^53 - 1 到 -2^53 - 1,也就是9007199254740991~ -9007199254740991
整数的检测方法有:ES6的Number.isInteger()或者polyfill方法
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
特殊值
undefined类型只有一个值,就是undefined,null类型也只有一个值,即null。两者很像,但又有点区别
- null 指空值,undefined 指没有值
- nul不是标识符,不能用来做变量使用和赋值,但是undefined可以做变量和赋值
NaN,无效数值,失败数值。表示不是一个数字。但其本身确是数字类型typeof NaN === "number,一般用指执行数学运算没有成功,是失败的返回结果。它的特殊点不止于此,它不等于任何值,哪怕是自身。
NaN != NaN //true,
ES6的Number.isNaN(..)可以用来判断是否是NaN
简单标量基本类型值(字符串和数字等)通过值复制来赋值/传递,而复合值(对象等) 通过引用复制来赋值 / 传递。JavaScript 中的引用和其他语言中的引用/指针不同,它们不能指向别的变量/引用,只能指向值。
简单来讲就是基本类型复制后,新值的改变不影响旧值,而复杂类型的复制,新值的个数改变或者属性改变会影响旧的对象。
let a = 2;
let b = a;
b++
console.log(a) //2
console.log(b) //3
var c = [1,2,3];
var d = c;
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
var a = [1,2,3];
var b = a;
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
//这就是JS中的引用时指向值的说法,b其实也是指向[1,2,3] 和 a没啥关系,后续 b 变了是它本身引用的值变了,这并不影响a的引用
原生函数
JS 有很多常见的原生函数
- String()
- Number()
- Boolean()
- Array()
- Object()
- Function()
- RegExp()
- Date()
- Error()
- Symbol()——ES6 中新加入的! 原生函数可以被当做构造函数使用,
var a = new String( "abc" );
typeof a; //'object'
这里创建出来的是封装了基本类型值的封装对象。
而这些 typeof 返回值是‘object’的对象内部都有一个属性[[class]],这个属性无法直接访问,一般通过Object.prototype.toString()来查看。
Object.prototype.toString.call([1,2,3]) //[object Array]
Object.prototype.toString.call(/regex-literal/i) //[object RegExp]
Object.prototype.toString.call( null );[object null]
Object.prototype.toString.call( undefined );[object undefined]
Object.prototype.toString.call( "abc" );[object String]
Object.prototype.toString.call( 42 );[object Number]
这个属性基本是和创建这个对象的原生构造函数一致,null和undefined 虽然没有构造函数,但是内部属性任然还是其类型值,而像一些基本类型则会被各自的封装对象自动包装,进而判断出值。
基本类型值一般没有 .length 或者 .toString的方法,这都是JS自动为其做的对象包装。但是如果想要得到封装对象的值的话,可以使用 .valueOf()方法
var a = new String( "abc" );
a.valueOf() //abc
大部分情况下,基本不会用或者少用原生构造函数构造新值,主要是因为这样的操作比较繁琐,而且执行效率不高。
但是像 Date() 和 Error() 这是需要构造函数来做的,Date()不带new的话会返回当前日期的字符串值,而错误类型则只能使用Error来生成。
Symbol:ES6新增的基本类型,意思是 符号,符号是具有唯一性的特殊值(并非绝对),用来命名对象属性不容易导致重名。
symbol同原生构造函数来构造的时候不能带 new 关键字,这样会报错。它一般以静态形式出现,代码和控制台都无法查看和访问它的值。大部分用于私有或特殊属性。