写点什么

重学 JavaScript02——类型

发布于: 2020 年 08 月 08 日
重学JavaScript02——类型



编程语言就是用一定的词法和语法,表达一定的语义,从而操作运行时。



因此一门编程语言的学习要抓住词法语法、语义和运行时三个方面。



类型



运行时类型就是代码的实际执行过程中我们用到的类型,而所有的类型都属于7种类型之一,无论是变量、参数、返回值、中间结果,JS运行过程中产生的所有数据都具有运行时类型。



七种类型包括:



常见的:



  • Number

  • String

  • Boolean



空的:



  • Null

  • Undefined



对象:



  • Object



以及ES新增的:



  • Symbol



Undefined 和 Null



为什么有些编程规范中,要求void 0 代替 undefined?



Undefined



void 运算任意表达式void(anything),结果都为undefined。



Undefined , 字面含义,表示未定义。Undefined 类型只有一个值,就是undefined。



也就是说,任何变量,在赋值以前,类型是Undefined, 值是undefined,我们常用(全局的)变量名undefined来表示这个值。



而问题就出在了undefined是个全局变量,而非关键字,也就是说undefined可以被篡改。(当然,可篡改这件事在ECMAScript5 被修复了,现代浏览器中,undefined的值是不能被重写的。)



15.1.1.3 undefined

The value of undefined is undefined (see 8.1). This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.



那IE8或者更古老的浏览器,就需要使用void(0) 了,那如何使用呢?



在代码中应当避免直接使用void(0)或者 typeof x === "undefined",因为这些表达式不是自解释的,应该包装在isUndefined function函数中,像这样:



function isUndefined(value){
//获得undefined,保证它没有被重新赋值
var undefined = void(0);
return value === undefined;
}



许多的工具库已经部署了这个方法,例如: _.isUndefinedunderscore中的isUndefined方法



Null



Null 表示的是空,定义了,但是为空,黑洞。



也因此我们很少将变量赋值为undefined,来让undefined就表示从未赋值的状态。



Null 的类型也只有一个值,就是null。但是null是Javascript的关键字,可以放心使用。



Boolean 和 String



Boolean 类型有两个值,true 和 false,同样有关键字true 和false,可以放心使用, 不做讨论。



String的表示



前置知识——字符集标准



现行的国际标准,字符以Unicode的方式表示,每个Unicode的码点表示一个字符。



UTF是Unicode的编码方式,规定的是码点在计算机中的表示方法,常见UTF8、UTF16。



0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)。



Javascript 中的字符串



JS为了“性能优化和实现简单”,其字符串把每个UTF16单元当作一个字符处理。因此如果遇到BMP之外的字符要小心。



JS中的字符串是永远无法变更的,一旦构造无法改变,因此具有值类型的特征。



Number



有多少个Number



JS的Number类型,有(2^64 - 2^53+3) 个值。同时规定了:



  • Nan

  • Infinity,无穷大

  • -Infinity, 负无穷大



+0 与 -0



加法类运算没有区别,注意除法运算,除以-0,会得到-Infinity。



著名的0.1 + 0.2 == 0.3 问题



浮点运算无法保证精度。



应当更换比较方法



console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);



Object



JS中,对象的定义是“属性的集合”。



而属性可以分为数据属性和访问器属性,二者都是k-v的结构,key 可以是字符串或者Symbol类型。



JS中的类



Java(以及C++)中,每个类都是一个类型。



但JS中,类仅仅是运行时对象的一个私有属性,JS无法自定义类型。



基本类型与对象类型



注意一下几个基本类型:



  • Number

  • String

  • Boolean

  • Symbol



他们在对象类型中都有一个亲戚,3 与new Number(3) 是完全不同的值,前者是Number类型,后者是对象类型。



而且,Number、String、Boolean 这三个构造器,有两种用法



  • 与new 搭配时,产生对象

  • 直接调用时,表示强制类型转换



Symbol 直接调用new 会报错,它仍然是Symbol对象的构造器。



JS 设计上试图模糊对象与基本类型之间的关系,因此我们甚至可以对基本类型,使用对象的方法,如:



console.log("abc".charAt(0)); //a
// 甚至在原型上添加方法,也能应用于基本类型
Symbol.prototype.hello = () => console.log("hello");
var a = Symbol("a");
console.log(typeof a); //symbol,a并非对象
a.hello(); //hello,有效



也就是说,.运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。



Symbol



ES 6 规范中,整个对象系统被Symbol重塑了,表示一切非字符串对象key的集合。



类型转换



JS是弱类型语言,所以类型转换发生十分频繁,大部分我们熟悉的运算依赖于类型转换。我们只需要关心一下反人性的结果。



==



== 的规则复杂到非人类可记,属于设计失误,因此明确不推荐,改用===进行比较。



其他类型转换



大部分类型转换规则简单,符合人性。





较为复杂的是Number 和String之间的转换,以及对象跟基本类型之间的转换。



StringToNumber



字符串转成字符,支持进制转换和科学计数法:



  • 30

  • 0b111

  • 0o13

  • 0XFF

  • 1e3

  • -1e-2



多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。



parseInt 建议传入第二个参数,否则只支持16进制的“0X”,而且会忽略非数字字符,也不支持科学计数法。



parseFloat 将原字符串作为10进制解析,不引入任何其他进制。



NumberToString



我们用得上的情况下,数字到字符串的转换符合直觉。



只需要注意Number 绝对值较大或者较小,以及科学计数法表示,原则是保证产生的字符串不会过长,算法过多,用到再说。



装箱



由于前面讲到基本类型Number、String、Boolean、Symbol在对象中都有对应类。



基本类型转换成对应的对象,就是装箱。



Symbol 函数无法用new,我们可以利用装箱来得到SymbolObject。



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函数,显式调用装箱能力。



var symbolObject = Object(Symbol("a"));
console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true



每一类装箱对象,都有私有的Class属性,可以如下调取:



var symbolObject = Object(Symbol("a"));
console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]



由于JS中,没有方法可以更改私有的Class属性,因此Object.prototype.toString 可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。但是call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。



拆箱



对象类型到基本类型的转换,就是拆箱,JS中的ToPrimitive函数。



对象到String 和 Number 的转换规则为,先拆箱再转换,会尝试调用valueOf 和toString, 来获得拆箱后的基本类型。如果valueOf 和toString 不存在(或者没有返回基本类型),会产生类型错误



var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o * 2
// valueOf
// toString
// TypeError



String 的拆箱操作会优先调用toString:



var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
String(o)
// toString
// valueOf
// TypeError



ES6中,允许对象通过显示指定toPrimitive Symbol 来覆盖原有的行为



var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
console.log(o + "")
// toPrimitive
// hello



注意typeOf



typeof 的运算结果,与运行时类型的规定有很多不一致的地方。多数项是对应的,但是请注意 object——Null 和 function——Object 是特例,我们理解类型的时候需要特别注意这个区别。 JavaScript 之父本人也在多个场合表示过,typeof 的设计是有缺陷的,只是现在已经错过了修正它的时机。









用户头像

还未添加个人签名 2017.10.17 加入

还未添加个人简介

评论

发布
暂无评论
重学JavaScript02——类型