在前端技术飞速发展的今天,彷佛全部人都沉浸在前端框架的学习上。在这些框架和新技术推出以后,好像一晚上之间就会有《深刻解读xx框架》、《手把手教你写一个xx》这样的文章,看得人是眼花缭乱,同时我也在想,为何有些人不是开发团队的人,可是能够这么快就写出对框架解读的文章?是做弊了吗?显然技术的道路上没有捷径,借用以前在TFC(腾讯前端大会)上听到的React-China的站长说的一句话:“其实我就是学东西快。”然而这句话也不能光从字面意思去理解,大佬之因此学东西快,除了智商碾压的部分,实际上是由于大佬的基础知识很牢固。javascript
因此从这篇文章开始,我会根据本身的理解和学习状况,总结JavaScript中一系列常见的重点的知识点(但不会是简单地罗列概念),但愿以此创建一个系统的前端知识体系。html
而今天就从JavaScript的类型系统开始。前端
咱们已经知道,在JavaScript中有七种类型:java
前六种是原始类型(primitive),而Object(对象)是引用类型。除此以外,其它全部的类型都是从Object继承的。若是想验证一下的话,能够在控制台声明一个引用类型的变量,而后输出这个变量,能够看到,原型链的最末端都是Objectsegmentfault
能够看到,虽然arr是一个数组类型的变量,可是__proto__仍然指向了Object。数组
typeof是咱们很熟悉的一种简单检测变量数据类型的操做符。用typeof操做简单类型时,除了Null类型,其它类型都能正确返回对应的类型;对Object和Null类型,都会返回object;而对于函数,则会返回function。浏览器
对于typeof不能正确识别Null、引用类型和自定义类型,在我看过的文章里,无不说是设计错误,是JavaScript的历史包袱。没错,不能正确识别Null是由于:安全
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。因为 null表明的是空指针(大多数平台下值为 0x00),所以,null 的类型标签是 0,typeof null 也所以返回 "object"。-- From MDN前端框架
可是其不能正确识别引用类型咱们是否是能够这样理解:typeof操做符返回的是变量原型链最顶端原型的类型。框架
固然,在JavaScript的世界里,万物皆对象,若是按照上面的理解,对函数使用typeof操做符应该也返回object,然鹅函数也确实有一些特殊的属性,所以经过typeof来区分函数和其余对象是有必要的 -- From 《JavaScript高级程序设计》
Undefined类型只有一个值 -- undefined。在对变量声明(var或let)但未初始化时,这个变量的值是undefined:
var name // undefined let age // undefined const gender // Uncaught SyntaxError: Missing initializer in const declaration
若是咱们不显式地初始化变量,即初始化时就给变量赋值,那么这个时候就会出现一个矛盾:
var name console.log(typeof name) // undefined // age没有声明 // var age console.log(typeof age) // undefined
此时就很难区分变量究竟是声明了未赋值仍是根本没有声明。所以,从代码规范的角度来看,显式地初始化变量仍是颇有必要的。这样当typeof返回undefined时,就能清楚地知道是变量尚未被声明,而不是声明了未赋值。
关于undefined还有一个小问题须要注意 -- 由于undefined不是关键字,因此undefined是能够被重写的。咱们能够经过如下的代码测试
var undefined = 'overwrite undefined' console.log(undefined)
好吧,我认可把你带偏了,若是直接在控制台输入这段代码,输出的还是undefined,缘由是ECMAScript 5中修复了在window下重写undefined的问题,咱们能够经过如下代码查看window下的undefined的属性
Object.getOwnPropertyDescriptor(window, 'undefined') // { value: undefined, configurable: false, writable: false, enumerable: false }
可是不能高兴得太早,咱们来看这个例子
(function () { var undefined = 'overwrite undefined!' console.log(undefined) console.log(undefined === window.undefined) })()
这段代码会分别输出overwrite undefined和false,因此对于以前说的结论应该作一点修改 -- 因为undefined不是关键字,在函数做用域下是能够被重写的,可是window下是不可被重写的
那当咱们使用到undefined的时候,若是避免相似的状况发生呢?其实很简单,使用void 0代替便可。
void是JavaScript中的一个操做符,语法是void 表达式,做用是计算后面的表达式并返回undefined,这是一个比直接用undefined更安全的作法。
Null类型只有一个值,就是null,表示定义了但为空。最开始说到,null表示的是空指针,因此若是定义了变量是准备用来保存对象的话,最好将这个变量初始化为null。这样作不只能够体现null做为空对象指针的习惯,方便检查变量是否已经有对象引用,同时也更方便地区分null和undefined。
另外,null在JavaScript中是一个关键字,因此不用担忧其会被重写。
Boolean类型表示的是逻辑真假值,这个类型有两个值 -- true和false,且为关键字。
对于这两个值其实没有太多须要注意的,不过其它任意的类型的值均可以转换成对应的逻辑值
这些转换规则对于各类流程控制语句中的判断条件是很是重要的。
在JavaScript中,有2^64 - 2^53 + 3个值,由于NaN占用了9007199254740990,还有-Infinity和Infinity。
同时,JavaScript中能够保存+0和-0,它们俩在值的比较上是相等的,在作加法类的运算时也是相等的,可是在除法的场合就不同了。当它们分别作分母时,获得的值分别是Infinity和-Infinity。所以,若是想区分+0和-0,最好的方式是检查1/x是Infinity仍是-Infinity。
在JavaScript中有效的整数范围是-0xffffffffffffffff 到 0xffffffffffffffff,在这个范围以外的数字没法精确表示。
而最经典的0.1 + 0.2 == 0.3会返回false 的问题,根据浮点数的定义,0.1和0.2的二进制表示分别为
Number(0.1).toString(2) // "0.0001100110011001100110011001100110011001100110011001101" Number(0.2).toString(2) // "0.001100110011001100110011001100110011001100110011001101"
所以将二进制下的0.1和0.2相加以后,显然就不等于0.3了。而解决精度丢失的方案有不少,最简洁的实际上是比较等号左右的差值的绝对值是否是小于最小精度,即Number.EPSILON
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON)
字符串也是平时开发用得不少的一种类型,在JavaScript中,字符串是由16位Unicode字符组成的字符序列,它有最大长度2^53-1,正好是JavaScript中能表示的最大安全整数
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true
这个值大约有9PB,这个数字是很是大的了,不可能有一个变量要存这么大的数据量,更况且,普通计算机也没有这么大的内存,有也不可能分配这么大的内存给浏览器。
JavaScript字符串把每一个UTF16单元当作一个字符来处理,因此处理非BMP的字符(超出U+0000 - U+FFFF)时应格外当心。
Symbol是ES6新增的基本类型,是一切非字符串的对象key的集合,并且ES6规范中的对象系统彻底用Symbol重塑了。
Symbol具备字符串描述,可是即便描述相同,Symbol也不相等
let s1 = Symbol('symbol') let s2 = Symbol('symbol') console.log(s1 == s2) // false
几个须要注意的点:
var a = {} var k = Symbol('a') a[k] = 1 console.log(a[k]) // 1 console.log(a[Object(k)]) // 1
在JavaScript的世界中,全部内容都是对象。关于对象的基础知识就再也不赘述,这里解答一个问题:为何给对象添加的方法能用在基本类型上?
Number.prototype.test = () => console.log('test') var num = 1 num.test() // test
首先,几个基本类型在对象类型中都有对应的“映射”:
前三种可使用new操做符,返回一个对应类型的实例。虽然对Symbol函数使用new操做符会报错,但Symbol函数仍然是一个构造器。这四种基本类型在被转换成对应的对象类型后,就能够经过原型链访问到对应的类型的原型。而上述代码的最后一行中,. 操做符其实就是会临时对num进行一次装箱操做,将其转换成Number对象类型,这样就使得咱们看上去能在基础类型上调用对应对象的方法。
众所周知,JavaScript做为动态语言,最大的特色之一就是变量的类型是能够任意转换的,并且咱们熟悉的运算几乎都会先进行类型转换。
上图中大部分的转换是很符合咱们的直觉的,比较复杂的Number和String之间的转换,以及对象和基本类型之间的转换。
将字符串转换成数字有三个函数:
Number接受一个参数,而且按照必定的规则判断参数是否能够被转换成数字。
从图中就能看出Number函数在内部作了不少判断和操做。
parseInt接受两个参数,第一个参数是要转换的内容,第二个参数是转换的进制。在不传入第二个参数的状况下,parseInt只支持16进制前缀‘0x’,并且会忽略第一个非16进制字符及其后的内容;若是是0开头的数字,会由于不一样的浏览器而使用不一样的进制,所以全部状况下使用parseInt都应该指定进制。
这两个函数共同的问题都是不支持科学计数法。
有趣的是,Nicholas Zakas在《JavaScript高级程序设计》中说由于Number的判断条件的复杂性,所以在处理整数时更经常使用的是parseInt。可是winter在重学前端中给出的建议是,多数状况下,Number是比parseInt和parseFloat更好的选择。
那对于普通开发者而言,应该使用Number仍是parseInt&parseFloat呢?我仍是以为一切不基于业务场景和开发者自身状况的技术选型都是耍流氓
BTW,若是平时只处理
可使用加号操做符进行类型的隐式转换
var a = '99' // 字符串数字 var b = '0b111' // 二进制 var c = '0o123' // 八进制 var d = '0xFF' // 十六进制 var e = '1e3' // 科学计数法 console.log(+a) // 99 console.log(+b) // 7 console.log(+c) // 83 console.log(+d) // 255 console.log(+e) // 1000
装箱转换,即,将每一中基本类型转换为对应的对象。
虽然Symbol函数没法用new来调用,可是能够用Object函数显式调用装箱转换
var symbolObject = Object(Symbol("a")); console.log(typeof symbolObject); //object console.log(symbolObject instanceof Symbol); //true console.log(symbolObject.constructor == Symbol); //true
全部被装箱的对象都有私有的Class属性,这个属性的值能够经过Object.prototype.toString获取
var a = Object(Symbol('a')) console.log(Object.prototype.toString.call(a)) // [object Symbol]
在JavaScript中,开发者是不能更改这个私有的Class属性的,所以Object.prototype.toString能够更加准确识别对象对应的基本类型,它比instanceof更准确。
在JavaScript中,规定了ToPrimitive函数,它是对象类型到基本类型的转换,即,拆箱转换。
拆箱转换会尝试调用valueOf和toString来获取拆箱后的基本类型。若是二者都不存在,或者都没有返回基本类型,则会产生类型错误TypeError。
对象到数字的转换优先调用valueOf,再调用toString
var o = { valueOf : () => {console.log("valueOf"); return {}}, toString : () => {console.log("toString"); return {}} } o * 2 // valueOf // toString // TypeError
而对象到字符串的转换优先调用toString,再调用valueOf
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
文章里讲到的一些内容并无更加深刻地探讨,主要是由于对应的主题会在以后的文章中再展开,并且一个主题也会花费大量的时间从新读官方文档和其它文章,因此今天的这个主题也算是一次试水吧。
本文的参考资料主要是:
在通过从wordpress,到Express+Mongo,再到Koa+MySQL+Vue的博客搭建,我花费了大量的时间在简单的CRUD和网站的样式上,从而忽略了文章的细节。在年初把博客服务关闭以后,一直在想到底应该再以哪一种形式对本身的学习作好输出呢?最终仍是回归了公众号(半年前手残把以前能够留言的公众号注销了、Github和知乎。
以前的几篇"文章"内容比较水,没什么技术含量,主要是为了先发几篇凑个数,从这一篇开始,我会尽可能作到周更,同时保证内容再也不太水,毕竟看文章的都是大佬,而能看到这里可能也是为了支持一下文章的完整阅读率吧。
欢迎关注公众号:Debugger