1. 类型
- JavaScript的每一个值都属于某一种数据类型。JavaScript规定了7种语言类型。语言类型广泛用于变量、函数参数、表达式、函数返回值等场合。这7种语言类型是:
- Undefined;
- Null;
- Boolean;
- String;
- Number;
- Symbol;
- Object.
2. Undefined、Null
- Undefined表时未定义,这种类型只有一个值:undefined。
- 任何变量在赋值前是Undefined类型、值为undefined。
- 一般可以用全局变量undefined来表达这个值,或者void运算来吧任意表达式变成undefined值。注:void 0 === undefined true
- undefined是一个变量,但不是关键字,所以一般建议用void 0 来获取undefined值
- null表示“定义了但是为空”。Null类型只有一个值,null。null是关键字,所以在任何代码中,可以放心用null关键字获取null值。
3. Boolean
true和false
4. String
- String并非“字符串”,而是字符串的UTF16编码,字符串的操作charAt,charCodeAt、length等都是针对UTF16编码。所以字符串的最大长度受字符串的编码长度影响(2^53-1)
Note: 现行的字符集国际标准,字符用Unicode方式表示,每个Unicode的码点表示一个字符,理论上其范围是无限的。UTF是Unicode的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8.
- JavaScript中的字符串永远无法变更,一旦构造完成,无法用任何方式改变内容,具有值类型的特征。
5. Number
- JavaScript 中的 Number 类型有
18437736874454810627(即253+3)个值。 - 基本符合双精度浮点数规则,但为了表达额外的语言场景(比如不让除以0报错,而引入无穷大的概念),规定了几个例外情况:
- NaN,占用了9007100254740990,这是符合IEEE规则的数字
- Infinity,无穷大
- -Infinity,负无穷大
ps: JavaScript中有+0和-0,在加法类运算中没有区别,但是除法中需要特别留意:“忘记检测除以-0,而得到负无穷大”经常会导致错误,而区分+0和-0的方式,就是检测1/x是infinity还是-infinity。0 === +0 === -0,
- Number有效的证书范围:-0x1fffffffffffff到0x1fffffffffffff,无法精确表示此范围外的整数。
- 非整数的Number类型无法用==、=== 来比较,比如 0.1 + 0.2 === 0.3。浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了微小的值。错在比较的方法上,正确的比较方法是JavaScript提供的最小精度值:
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
检查等式左右两边差的绝对值是否小于最小精度,才是比较浮点数的方法。
6. Symbol
- Symbol是ES6引入的新类型:它是一切非字符串的对象key的集合,在ES6中,整个对象系统被用Symbol重塑。
- Symbol可以具有字符串类型的描述,但即使描述相同,Symbol也不想等。
创建Symbol的方式是使用全局的Symbol函数。例如:
var mySymbol = Symbol("my symbol");
我们可以用Symbol.iterator来自定义for…of在对象上的行为: ```javascript var o = new Object o[Symbol.iterator] = function() { var v = 0 return {
next: function() {
return { value: v++, done: v > 10}
}
} };
for(var v of o) console.log(v); // 0 1 2 3 … 9 // 这里面涉及到的:Symbol.iterator、迭代器
**7. Object**
1. Object是最复杂的类型,也是JavaScript的核心机制之一,是一切有形和无形物体的总称。
2. 对象(Object):属性的集合。
3. 属性:数据属性和访问器属性,二者都是key-value结构,key可以是字符串或者Symbol类型。
4. 事实上,JavaScript的”类“仅仅是运行时对象的一个私有属性,而JavaScript中是无法自定义类型的。
5. JavaScript中的几个基本类型,都在对象类型中有一个”亲戚“。他们是:
- Number;
- String;
- Boolean;
- Symbol;
<br />6. Number、String和Boolean,三个构造器是两用的,当和new搭配时,产生对象,当直接调用时,表示强制类型转换。
7. Symbol比较特殊,直接用new调用会抛出错误,但它仍然是Symbol对象的构造器。
8. JavaScript语言设计上试图模糊对象和基本类型之间的关系,我们日常代码可以把对象的方法在基本类型上使用,比如:
```javascript
console.log("abc".charAt(0)); // a
- 甚至我们在原型上添加方法,都可以应用于基本类型,比如一下代码,在Symbol原型上添加了hello方法,在任何Symbol类型变量都可以调用。 ```javascript Symbol.prototype.hello = () => console.log(“hello”);
var a = Symbol(“a”); console.log(typeof a);// symbol, a 并不是对象 a.hello(); // hello, 有效
10. 问题“为什么给对象添加的方法能用在基本类型上”:“."运算符提供了装箱操作,他会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
**8. 类型转换**
1. 因为JavaScript是弱类型语言,所以类型转换非常频繁,大部分熟悉的运算都会先进性类型转换。
2. JS中" == "因为试图实现跨类型的比较,所以规则复杂到基本没人可以记住。因此很多实践中推荐禁止使用" == ",而要求程序员进行显式的类型转换后,用“ === ”比较。
3. 大部分的类型转换规则简单,如下表所示:
<br />
<br />接下来看一下几种转换规则。
**[转换规则]9. StringToNumber**
1. 字符串到数字的类型转换,存在一个语法结构,类型转换支持十进制、二进制、八进制和十六进制,比如:
- 30;
- 0b111;
- 0o13;
- 0xFF。
2. 此外,JavaScript支持的字符串语法还包括正负号科学记数法,可以使用大写或者小写e来表时:
- 1e3;
- -1e-2.
3. 需要注意的是:parseInt和parseFloat并不使用这个转换,所以支持的语法跟这里不尽相同(有相同的地方也有不同的地方)
- 再不传入第二个参数的情况下,parseInt只支持16进制前缀“0x”,而且会忽略非数字字符,也不支持科学记数法。
4. 在一些古老浏览器中,parseInt还支持0开头数字作为八进制前缀,这是很多错误的源头。所以在任何环境下,都建议传入parseInt的第二个参数,而parseFloat则直接把源字符串作为十进制来解析,它不会引入任何的其他进制。
5. 多数情况下,Number 是比parseInt和parseFloat更好的选择(这是什么意思。。)
**[转换规则]10. NumberToString**
1. 具体开发中很少用到,可以去JavaScript的语言标准查阅。
**[转换规则]11. 装箱转换**
1. 每一种基本类型Number、String、Boolean、Symbol在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,他是类型转换中一种相当重要的种类。
2. 我们可以用一个函数的call方法强迫产生装箱,定义一个函数,里面只有return this,然后call到一个Symbol类型的值上,就会产生一个symbolObject。
```javascript
var symbolObject = (function(){ return this; }).call(Symbol("a"));
console.log(typeof symbolObject);// object
console.log(symbolObject instanceof Symbol); // true
console.log(symbolObject.constructor ==Symbol); // true
- 装箱机制会频繁产生临时对象,在对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。
使用内置的Object函数,可以在JavaScript代码中显式调用装箱能力: ```javascript var symbolObject = Object(Symbol(“a”));
console.log(typeof symbolObject); //object console.log(symbolObject instanceof Symbol); // true console.log(symbolObject.constructor == Symbol); // true
<br />每一类装箱对象皆有私有的Class属性,这些属性可以用`Object.prototype.toString`获取:
```javascript
var symbolObject = Object(Symbol("a"));
console.log(Object.prototype.toString.call(symbolObject));//[object Symbol]
在JavaScript中,没有任何方法可以更改私有的Class属性,因此Object.prototype.toString
是可以准确识别对象对应的基本类型的方法,它比instanceof
更加准确。
但需要注意的是,call本身会产生装箱操作,所以需要配合typeof来区分基本类型还是对象类型。
[转换规则]12. 拆箱转换
- 在JavaScript标准中,规定了ToPrimitive函数,他是对象类型到基本类型的转换(即拆箱转换)。
- 对象到String和Number的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象编程基本类型,再从基本类型转换为对应的String或者Number。
- 拆箱转换会尝试调用
valueOf
和toString
来获得拆箱后的基本类型。如果都不存在或者没有返回基本类型,则会产生类型错误TypeError
。var o = {
valueof: () => { console.log("vauleof"); return {}},
toString: () => { console.log("toString"); return {}}
}
o * 2
// valueof
// toString
// TypeError
我们定义了一个对象o,有valueOf
和toString
两个方法,都返回了一个对象,然后进行o2运算时候,限制性了valueOf
,接下来是toString
,最后抛出了一个TypeError
,说明这个拆箱转换失败了。
到String的拆箱转换会优先调用toString
。把刚才的运算从o * 2 变成o + “”,调用顺序变为:
// toString
// valueOf
// TypeError
- ES6之后,还允许对象显式指定@toPrimitive Symbol来覆盖原有的行为。 ```javascript var o = { valurOf: () => {console.log(“valueOf”); return {}}, toString: () => {console.log(“toString”); return {}} }
o[Symbol.toPrimitive] = () => {console.log(“toPrimitive”); return “hello”}
console.log(o + “”) // toPrimitive // hello ```
13. 结语
- 除了七种语言类型,还有一些语言的实现者更关心的规范类型。
- List 和 Recort:用于描述函数传参过程。
- Set:主要用于姐是字符集等。
- Completion Record:用于描述异常、跳出等语句执行过程。
- Reference: 用于描述对象属性访问、delete等。
- Property Descriptor: 用于描述对象的属性。
- Lexical Environment 和 Environment Record:用于描述变量和作用域。
- Data Block:用于描述二进制数据。
2. 补充:
- typeof和运行时类型的规定不一致的地方:
多数是对的,但是object——Null和function——Object是特例。需要特别注意