探索 Python 来反补 JavaScript,带你 Cross Fire —— JS 数据类型的奥秘

写在最前

数据类型能够说是编程语言的基石,重要性不言而喻。那么如今就从数据类型开始,打破你的思惟认知,作一个充满想象力的FEE。针对上篇的一些偏激评论,我想强调的一点是:我写的文章,并非给那些偏激到说脏话的人看的,请尊重每一位为前端贡献微薄力量的Blogger前端

好像,我这标题起的也太秀了,会不会被打😂。java

多说一句

这篇能够算是 前端猎奇系列中的 探索 Python 来反补 JavaScript 的中篇。 若是没有看过上篇文章的,能够去个人专栏里读读上篇,在知识上没有啥关联的地方,相对独立。基本是我在学习PY的时候,学到某一个地方,忽然会想到JS在这一方面是如何表现的,而后随着思考,真的会有很多收获吧。node

关于数据类型

有句话说的好,掌握数据类型是学习一门语言的基础。咱们从这句话中能够看出,掌握好数据类型是有多么重要。你曾经是否是有想过JS的数据类型为何会是这样,为何要有nullundefined。也许你有过疑问,可是疑问触发后,简简单单的探寻后,就把疑问扔到回调函数里了,这一扔就是到现在。如今我将从PY的角度来反补JS,经过PY去看清JS的数据类型,看清编程语言的一些规律。now go!chrome

JS的数据类型分为值类型和引用类型:编程

  1. 值类型有:数字、字符串、布尔、Null、Undefined、Symbol、
  2. 引用类型有:Array、Function、Set、Map

PY的数据类型分为数值类型、序列类型、Set类型、字典类型:segmentfault

  1. 数值类型有:Integer、Long integer、Boolean、Double-precision floating
  2. 序列类型有:String、Tuple、List(和JS的Array相同)
  3. Set类型(和JS的Set相同)
  4. Dictionary类型(和JS的Map相同)

如今咱们看一下PYJS的数据类型,这里我不阐述具体是什么,我只是总结一下,当我学习到这的时候,我对JS的数据类型有了什么样新的理解。如今,你会发现几个颇有趣的地方,请看以下:数组

关于 Set 和 Map

这和PYSetDictionary不谋而合,可是ES6规范的制定者,没有选择使用Dictionary做为键值对的类名称,而选择了使用Map做为键值对的类名称。而Map正是Java的键值对的类名称。因此给个人感受就是,JS在吸取不少语言的优秀的特性,我我的认为,命名成Map要比Dictionary好,毕竟少写7个字符呢😂。浏览器

关于 Array 和 List

就这样就结束了吗?No,咱们再看上面两种类型,首先注意PYListJSArray是相同的,都是能够动态进行修改的。可是不少FEE,由于掌握的知识不够宽泛,致使了对不少事情不能理解的很透彻。好比,咱们的思惟中就是这样一种固定的模式:数组是能够动态修改的,数组就是数组类型。。我我的建议,FEE必定不能将本身的思惟束缚在某个维度里。这真的会阻碍你 开启那种瞬间顿悟的能力。前端工程师

若是你了解了PY或者其余语言,你会发现其实JS的数组,其在编程语言里面,只能算是List类型,属于序列类型的一种。并且很重要的是,JSArray是动态的,长度不固定,了解过Java的同窗应该知道,在Java中,数组是分为ArrayArrayListAarry是长度固定的,ArrayList是长度能够动态扩展的。因此JSArray其实只是编程语言 的Array中的一种。若是你知道这些,我以为这对去深入理解JS的数据类型将有很大的帮助。虽然JS对一些知识点进行了简化,可是做为一个合格的计算机工程师,咱们不能习惯的接受简化的知识点,必定要去多维度理解和 掌握简化的知识点。了解其背后的世界,也是很是奇光异彩的。编程语言

关于 JS 的 String 和 PY 的 String

你会发现JSString是被归类为数值类型,而PYString是被归类为序列类型。其实我我的更倾向于把JSString归为序列类型,为何这么说呢,由于JS的字符串自己就带有不少属性和方法,既然有方法和属性,也就意味着至少是个对象吧,也就是隐式执行了new String。字符串对象能够经过方法和属性来操做本身的字符序列。因此这被归类为数值类型的话,我我的认为是不科学的,而PY就分的很清楚。

关于 null 和 undefined

null 和 undefined 的争论就在此结束吧。

可能一开始会对nullundefined比较陌生,可能会有那么一刻,你怀疑过JSnullundfined为何会被单独做为数据类型,可是过了那一刻,你就默许其是一个语言设计规则了。可是我想说的是,语言设计规则也是人设计的,是人设计的就应该多一份怀疑,没必要把设计语言的人当作神同样。编程语言那么多,哪有那么多神。网上有不少好文章介绍JSundefinednull的,也都说了有多坑。想深刻理解有多坑的能够自行百度谷歌。我也不重复造解释了,好比,undefined竟然不是保留字,也是够神奇的,看了下博客,有篇解释的很不错,能够瞅瞅为何undefined能够被赋值,而null不能够?。写博客的时候,并非一味的写本身的东西,有时候别人总结好的东西,在我写博客过程当中,也能带给我不少灵感和收获。这也是算是和站在巨人的肩膀上是一个道理吧。

不过我仍是有点我的独特的见解的。并且我认为个人见解要比网上绝大多数的看法要更加深入(不要脸)。我不想说undefined有多坑,我只想去探究和理解undefined的本质。掌握了本质后,坑不坑也就不重要了。我我的认为,JSundefined是一种为了处理其余问题而强行作出的一种折中方案,且听我娓娓道来。

既然PYJS都是解释性语言,那么为何PY能够不依赖undefined,只须要使用None就能够了呢? 我写一个简单的例子,能够从我下面的分析中,找到一些更深层的真相,找到设计undefined真正的缘由。代码以下:

let x
console.log(x)
复制代码
# coding=utf-8
print(x)
复制代码

咱们来看运行结果:

image

从图中会发现,JS没有报错,可是PY报错了,到底是什么缘由? 这里中断一下,咱们来看下面这个截图,是java的一段代码的运行结果:

image

图中能够看出,在Java中,能够声明变量,但不赋值,而后不去调用此变量,程序是不报错的,可是在PY中,请看下面截图:

image

咱们发现,咱们声明了,也没有去调用它,程序仍是报错了。是为何呢?

为何在JavaC++C语言中,能够声明变量,而不用赋值,而且不会报错。而在PY中会报错呢,而在JS中是undefined呢?其实仔细一想,会恍然大悟,一个很是关键的一点就是:

JavaC++C是强类型语言,在声明的时候,就已经肯定了数据类型。因此就算不去赋值,JavaC++等也会根据声明的数据类型,设置一个默认的数据类型的值。可是这里注意一点,若是整个程序执行完,在只声明,却没有赋值的状况下,去输出或者调用该变量,程序会报错的。为何会报错呢,是由于此变量的地址是系统随机生成的,并不在此程序内的地址范围内,也就是说此变量的地址多是指向其余程序的地址,在这种状况下,若是去调用该地址,那么可能会出现很大的危险性,好比你调用了其它很重要的东西。这里我以为能够把它理解为游离的指针,虽然这样形容很差,可是很形象,游离的指针是很危险的东西。有多危险,哈哈哈,本身体会✧(≖ ◡ ≖✿)。

中断结束,继续PS,从上面的叙述知道了Java等语言是强类型语言。可是咱们知道而PYJS是脚本语言,属于弱类型语言,而弱类型语言的特色就是:在声明变量的时候,不须要指定数据类型,这样的好处就是一个变量能够指向万物,缺点是性能差一些,须要让编译器在赋值的时候本身去作判断。请紧跟着个人脚步,咱们来看下面这段代码:

let x
console.log(x)
复制代码

能够看到,xJS声明的变量,因为脚本语言是动态的,因此这个变量x能够指向万物,那么若是直接使用x,而不让其报错的话,该怎么作呢。

一个原则必定不能忘,就是不赋值的话,调用必定会报错,OK,那就赋值,给一个默认值,那么这个默认值用什么来表示呢,指向万物的话,那这类型的可能性有好几种,若是使用null来表示的话,因为null表明空对象,这里说一个很关键的点,就是,为何其余语言中好比JavaC++,他们对于空,都是使用null来表明一个空对象的?

其实最本质的缘由仍是由于他们是强类型语言,必须在变量前面声明数据类型,对于值类型,他们系统能够自动给一个默认值。因此在强类型语言中的null,其做用只是给引用类型用的。而到了弱类型语言中,好比PYJS,咱们看PY,因为PY老哥不想使用undefied,也只想用一个null。那么天然而然的结果就是:直接不容许在未赋值以前,直接调用声明的变量,只要调直接提示报错,那么你可能会有疑问了,为何PY语言中,连只声明变量,不去调用它,程序都会报错呢。其实我我的以为缘由是由于弱类型语言的数据类型不肯定致使的,编译器没法去给一个默认值,也就意味着不肯定因素增长,既然不肯定,那PY的作法就是直接使其报错。经过编译器报错来显式让开发者去遵循编码规则。

而小可爱JS就不同了,因为设计者就是不想使其报错,想容许声明,而且能够在未赋值的时候还能够直接调用而不报错。因此也就意味着他要给声明的变量赋一个默认值,怎么赋值呢?这估计也是困扰了设计者良久,下面我举一个很简单易懂的例子,请看下面代码:

let x;
let y = [1,2,3]
console.log(x, y[3])
复制代码

从代码能够看出,若是想不报错,有几种可能:

第一种: 按照其余语言的规范,只保留一个空值null,ok,继续往下推导,因为JS是弱类型,变量指向万物,因此确定只能给全部声明但未赋值的变量设置null为默认值了。可是这样的话,问题来了。

看第三行代码,其实y[3]也是声明未赋值的变量,是否是有点不相信,以为超出认知了。没事,先继续往下看,既然y[3]也是未赋值的变量,那把y[3]的默认值也设置为null吗?很明显,不合理。

由于y[3]多是各类类型,若是直接都设置为null。那用户直接打印y[3],而后蹦出来一个null,仍是object类型,岂不要炸?因此到这里,我会慢慢发现,其实JS中的nullundefined是彻底不一样的两码事,很容易去区分。

综上,我猜一下JS做者的脑洞应该是这样的,既然我想让调用声明未赋值的变量不报错,那ojbk。不是弱语言么,不是指向万物吗?那要来就来刺激点,我就单独设置一个数据类型,名为undefined。专门用来counter指向万物的声明却未赋值的变量。哈哈哈哈,是否是很刺激😂。

解决最后一千米的疑惑

看下面代码

let x
let y = [1,2,3]
console.log(x, y[3])
复制代码

你会发现xy[3]都是undefined。咱们来透过现象看本质,本质上就是声明了,可是未赋值。为何能够这么说,难道y[3],也是声明了,但未赋值吗?我能够明确告诉你,是的,没毛病。你可能不相信我说的话,下面我在白板上画一个图就顿悟了。。请看图:

image

图中能够看到,其实数组的每个下标也是在栈里进行声明的。和用let x进行声明的操做是同样的。let x的声明以下图:

image

因此是否是发现其实undefined也就那么回事吧。通常来讲,若是某一个知识点越绕人,那咱们就应该从更底层的角度去看清这个知识点。只要你真的是从一个更加深入和底层的角度去看待undefined,其实 just so so 啦。对了,null我也顺带解释了,只不过没有重点关注,可是整篇下来,其实null是什么,也差很少一清二楚了。总之nullundefined就是彻底不一样的两码事。

总结

JSPY的数据类型,咱们能够看出,PY在设计数据类型的时候,明显考虑的不少,或者说,PY语言在被创造的时候,其数据类型的设计是比较规范的。而咱们去看JS,会发现,有不少坑,感受是当初为了简化知识点难度,而留下了不少坑。虽然我没有经历过IE时代的前端,但如今也能深入体会到前端工程师的不容易。之前还有同行说前端很简单啊,如今也有,我都遇到过好几回这种人了:

我:我是前端开发。

人家:噢,我知道了,就是写网页的对吧。。。

我内心os:对你个锤子。。

FEE们都是从坑里一步步爬上来的,真的不容易。总之,如今的前端正在一步步走上规范,走上体面。。。

文末彩蛋一,动态参数

PY中如何处理动态参数的呢,其实PY是经过元组或者字典来处理动态参数的,代码以下,这里只写使用 元组 实现动态参数的代码

# coding=utf-8
def add(x, *tupleName):
    print(x, tupleName)

add('hello', 'godkun', '大王叫我来巡山')
复制代码

执行结果图以下:

image

咱们再看JS是如何实现的

function fun(a, ...tupleName) {
  console.log(a, tupleName)
}
fun('hello', 'godkun', '大王叫我来巡山')
复制代码

执行结果图以下:

image

看上面两种方式,看完你应该就明白了,ES6增长展开符的缘由是什么,以及为何要设计成这个样子。使用...做为标记。同时为何要将全部可变参数放在一个数组里面。

其实语言都是有相同性的,尤为对于JS语言来讲,采纳了不少语言的优势,这对于咱们前端来讲,是一个很大的优点,若是平时善于去这样比较和反补,我我的以为,FEE去承担其余开发岗位,也是彻底能Hold住的。

番外二,深夜写博客时的意外惊喜(意不意外,刺不刺激)

当我写下这段代码:

function a(a, b, c) {
  console.log(arguments);
  console.log({ 0: "1", 1: "2" });
  console.log([1, 2, 3]);
}
a(1, 2, 3);
复制代码

第一种状况:我在node.js环境运行:结果如图所示:

image

第二种状况:我在chrome浏览器下执行这段代码,结果如图所示:

image

第三种状况:我在IE浏览器下执行这段代码,结果如图所示:

ie arguments 1

上面第二种状况,你会发如今chrome浏览器下,输出的结果形式为:

Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  0: 1
  1: 2
  2: 3
  callee: ƒ a(a,b,c)
  length: 3
  Symbol(Symbol.iterator): ƒ values()
  __proto__: Object
复制代码

我靠,什么鬼。竟然把arguments写成了数组的形式:

[1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

可是 __proto__ 仍是 Object。吓的我赶忙试了下面这段代码,代码如图所示:

image

靠,还果然返回了长度。。。可是为何__proto__Object。。。。

不行,我又看了IE浏览器和node.js环境下的结果,都是相同的结果,使用{}表示类对象数组

{0: 1, 1: 2, 2: 3, callee: function a(a,b,c){}, length: 3}
复制代码

我陷入了沉思。。。。

不知道是chrome开发者故意这样设计的,仍是写错了。。小老弟,你怎么回事? chrome会弄错? 本着上帝也不是万能的理念,我打开了个人脑洞。

chrome浏览器既然不按照{}这种写法,直接将arguments写成[],使其直接支持数组操做,同时,其原型又继续是对象原型。仔细看会发现又加了一行

Symbol(Symbol.iterator): ƒ values()

这样作的目的是什么,为何要这样设计?搜了blog,然而没搜到。。。这一连串的疑问,让我再次陷入了沉思。。。

思考了一会,动笔画了画,发现好像能够找到理由解释了。我以为能够这么解释:

chrome想让类数组对象这种不伦不类的东西从谷歌浏览器中消失。因此下面这种输出结果

{0: 1, 1: 2, 2: 3, callee: function a(a,b,c){}, length: 3}
复制代码

就一去不复返了,那么若是不这样写,用什么方法去替代它呢。答案就是写一个原型链继承为对象类型的数组,同时给继承对象类型的数组(其仍是对象,不是数组) 增长Symbol.iterator属性,使其能够for of

为何要这样作呢,由于一些内置类型自带迭代器行为,好比StringArraySetMap,可是Object是不带迭代器的,也就意味着咱们能够推断出,若是从chrome浏览器的那种写法的表面上分析,假定argumentsArray,那么就彻底不必增长Symbol.iterator,因此矛盾,因此能够得出,arguments仍是对象,而对象是不带迭代器的。因此要给形式为 []arguments 增长 Symbol.iterator。使其具备迭代器功能。从而可使用for of。从而完成了 [1,2,3]{'0':1, '1':2, '2':3}的转变

因此:上述答案被证实为正确。

固然,也多是:

有理有据的胡诌。。。

备注:

  1. 我是根据学习PY来去思考JS的数据类型的,对于好比JS的Symbol,Set,Map,没有去说官方用法,我以为没有必要吧。
  2. 我说的一些都是我我的出于一个心流状态下的一些思考。可能有点问题,可是都是吾之所感。

文末的可爱声明: 若是转发或者引用,请贴上原文连接,尊重一下劳动成果😂。文章可能 (确定) 有一些错误,欢迎评论指出,也欢迎一块儿讨论。文章可能写的不够好,还请多多包涵。人生苦短,我学前端,多一点贡献,多一分开心,欢迎关注,后续更加精彩哦~

小伙伴以为我写得还不错的话,就点个赞 以兹鼓励 一下吧😊。

相关文章
相关标签/搜索