一、类型
- 强制类型转换是JavaScript开发人员最头疼的问题之一,它常被诟病为语言设计的一个缺陷。
- 全面掌握JavaScript的类型之后,我们旨在改变对强制类型转换的成见,看到它的好处并意识到它的缺点被过分夸大。
1. 内置类型
JavaScript
有七种内置类型
- 空值(
null
) - 未定义(
undefined
) - 布尔值(
boolean
) - 数字(
number
) - 字符串(
string
) - 对象(
object
) - 符号(
symbol
)
(还有新增的BigInt
,支持任意长度的整数,所以现在是八种基本类型。)
typeof
查看类型
- 我们可以通过
typeof
运算符来查看值的类型。 - typeof查看
null
比较特殊
typeof null === "object"; // true
- 正确的返回结果应该是“null”,但这个bug由来已久。
- 因为这牵扯太多的Web系统,“修复”它会产生更过bug,令许多系统无法正常工作。
- 我们需要使用复合条件来检测null值的类型
var a = null;
(! a && typeof a === "object"); // true
2. 值和类型
- JavaScript 中的变量是没有类型的,只有值才有。变量可以随时劫持任何类型的值。
- 也就是说,一个变量可以现在被赋值为字符串类型的值,随后又被赋值为数字类型的值。
var a = 42;
typeof a; // "number"
a = true;
typeof a; // "boolean"
undefined
和 undeclared
- 已在作用域中声明但没有赋值的变量,是为
undefined
- 还没有在作用域中声明过的变量,是
undeclared
- 在浏览器中的报错信息不一样。
- 但是用
typeof
判断是一样的,这是因为typeof
的安全防范机制。
二、值
1. 数组
- 与其他强类型的语言不同,在JavaScript中,数组可以容纳任何类型的值,可以是字符串、数字、对象(object),甚至其他数组。
- 数组通过数字进行索引,但它们也是对象,所以也可以包含字符串键值和属性。
2. 类数组
- 类数组是一个普通对象,而数组是Array类型。
- 有时候需要通过数组工具函数(如
ndexOf()
、concant()
、forEach()
)转换成数组。 - 常见类数组:
arguments
3. 字符串
- JavaScript中字符串是不可变的。
- 就是字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。
4. 数字
JavaScript
中的数字类型是基于IEEE 754
标准来决定的,该标准通常也被称为“浮点数”。JavaScript使用的“双精度”格式。toFixed()
可以指定小数部分的显示位数。
0.1 + 0.2 === 0.3; // false
- 二进制浮点数中的0.1和0.2并不是十分精确,它们相加的结果是一个比较接近0.3的数字。
5. 特殊数值
null
指空值undefined
指没有值
不是数字的数字
NaN
指"不是一个数字",是一个警戒值,用于数字类型中的错误情况,即“执行运算没有成功”。
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
可以通过isNaN()
判断一个值是否是NaN
。
无穷数
计算结果一旦溢出为无穷数(Infinity)
var a = 1 / 0; // Infinity
零值
- JavaScript有一个常规的0(也叫做+0),和一个-0。
- 在有些程序中的数据需要以级数形式来表示(比如动画帧的移动速度),数字的符号位用来代表其他信息(比如移动的方向)。
6. 特殊等式
Object.is()
判断两个值是否绝对相等,可以用来处理所有的特殊情况。(比如:NaN
)- 能使用
==
和===
时就尽量不要使用Object.is()
,因为前者效率更高、更为通用。 Object.is()
用来处理那些特殊的相等比较。
7. 值和引用
- JavaScript对值和引用的赋值/传递在语法上没有区别,结果完全根据值的类型来决定的。
- 简单值(即标量基本类型值)总是通过值复制的方式来赋值/传递,包括
null
、undefined
、字符串、数字、布尔值和符号。 - 复合值,总是通过引用赋值的方式来传递的。
- 由于引用指向的是值本身,并未变量,所以一个引用无法更改另一个引用的指向。
三、原生函数
-
常用原生函数(也称内置函数)有:
-
String()
、Number()
、Boolean()
、Array()
、Object()
、Function()
、RegExp()
、Date()
、Error()
、Symbol()
-
原生函数可以当作构造函数来使用。
var a = new String("abc");
typeof a; // 是"object",不是“String”
a instanceof String; // true
Object.prototype.toString.call(a); // "[object String]"
-
通过构造函数创建出来的是封装了基本类型的值的封装对象。
-
例如:
new String("abc")
,创建的是字符串“abc”的封装对象,而非基本类型值“abc”。
1. 内部属性
- 所有
typeof
返回值为“object”
的对象都包含一个内部属性。这个属性无法直接访问,可以通过Object.prototype.toString(...)
来查看。
Object.prototype.toString.call([1,2,3]); // "[object Array]"
- 基本类型值被各自的封装对象自动包装,所以它们也能获取到内部属性。
Object.prototype.toString.call("abc"); // "[object String]"
封装对象包装
- 由于基本类型没有
length
和toString()
这样的属性和方法,需要通过封装对象才能访问。 - 此时,
JavaScript
会自动为基本类型值包装一个封装对象。
var a = "abc";
a.length; // 3
a.toUpperCase() // "ABC"
拆封
如果想要得到分装对象中的基本类型值,可以使用valueOf()
函数。
var a = new String("abc");
a.valueOf(); // "abc"
2. 原生函数作为构造函数
- 应该尽量避免使用构造函数来创建对象,因为可能产生意想不到的结果。
- 通常使用字面量的形式。
Array()
var a = new Array(1,2,3); // 构造函数
var b = Array(1,2,3); // 不带new
var c = [1, 2, 3]; // 字面量
- 构造函数
Array()
可以不带new
,会自动补上。 - 问题:当构造函数
Array()
只有一个参数的时候,该参数会作为数组的预设长度,而不是元素。
Object()/Function()和RegExp()
- 这几个尽量不要使用
- 通过
new Object()
创建对象,无法像字面量形式那样一次设定多个属性,必须逐一设定属性。
Date()和Error()
- 这两个用处很大,并没有对应的字面量形式替代。
- 创建日期对象,必须使用
new Date()
。- 可以带参数,用来指定日期和时间
- 不带参数,则使用当前的日期和时间
- 构造函数
Error()
带不带new
关键字都可。 - 创建错误对象主要是为了获取当前运行栈的上下文。栈上下文信息包含函数调用栈信息和产生的错误代码,以便调试。
throw new Error("x wasn't provided");
Symbol()
- 符号是具有唯一性的特殊值,用他来命名对象属性不容易导致重名。
- 这个比较特殊,不带
new
var a = Symbol("my own symbol");
3. 原生原型
- 原生构造函数都有自己的原型对象。
- 这些对象包含其对应子类型所特有的行为特征。
- 而且这些原生原型是能够进行修改的。