Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
godkun opened this issue Dec 5, 2018 · 0 comments

Comments

@godkun
Copy link
Owner

godkun commented Dec 5, 2018

写在最前

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

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

多说一句

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

关于数据类型

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

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

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

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

  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. 我说的一些都是我个人出于一个心流状态下的一些思考。可能有点问题,但是都是吾之所感。

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant