JS是如何计算 1+1=2 的?
身为程序员多年,作者今天突然对这件事感到十分好奇了。我问计算机芸芸部件,1+1究竟是如何计算的,他们都茫然的看着我。
打开谷歌浏览器->Console面板,大脑向双手不停发送生物电信号,肌肉细胞大量钙分子游离到细胞外肌肉拉紧,钙分子回到细胞内肌肉又松开,钙分子来来回回进进出出多次,在大脑总枢纽指挥下,在多指配合下,最终完成了1、Shift+、1的键入,然后回车,输出如下:
谷歌浏览器返回了2。
作者问浏览器:“你小子是怎么知道1+1等于2的?纵观人类进化史,从学会使用石头,到学会结绳记数,用了100万年。你年纪轻轻28岁,是怎么知道1+1等于2的?”
浏览器说:“我不知道啊,是v8告诉我的。”
“v8是谁?是男是女?”
“非男非女,亦男亦女。v8是谷歌研发的JavaScript引擎,你发给我的JS代码,都是由他执行的。”
“把v8叫来,我有事问他。”
不一会儿,v8来到我面前。我问他:“你是怎么知道1+1等于2的?人类世界上最聪明的孩子降生时,都不知道1+1是等于2的。你是怎么知道的?”
“我并不知道1+1等于几,我所有结果都是基于您的输入给出的。”
作者问v8:“当浏览器把1+1发给你以后,你干了啥?”
“我就是进行了词法分析、语法分析,建立了语法树、符号表等...”
浏览器说:“这些都不要说了,这都是身为编译器/解释器的份内之事,大家都是这么干的,我解析Html标签也是这么干的。直接说你解析完了干了什么?”
v8道:“解析完了,我就使用了MacroAssembler库...”
浏览器说:"MacroAssembler库就是缩写为masm的汇编库吧,我去年在Strongtalk VM那里见过他,要知道Strongtalk VM可是大大鼎鼎的Java虚拟机HotSpot JVM的前辈呢!Java可是比JS快多了!"浏览器显得是一个见多识广的人。要知道全世界的www网页都展示都在他上面显示,他真的是见多识广。
但作者不喜欢浏览器自作聪明,“浏览器别打岔,v8你继续讲,使用masm干了什么?”
v8道:“masm提供了很多方法,基本和js是一一对应的,js语句是什么,就调用对应的masm方法。例如1+1这名js代码,对应调用masm的C++代码是这样的:
#define __ masm.
__ mov(eax, 1) //在这里 __ 是一个宏,在预处理之后将被统一替换为“masm.”。这一句是将寄存器eax设置为1
__ add(eax, 1) //这一句将寄存器的值加1
__ ret(eax) //这里返回寄存值的值
(以上只是示例,伪代码不要当真)
上面是C++代码,在内存里生成机器码大概长这个样子:”
B8 01 00 00 00 ;mov eax,1
83 C0 01 ;add eax,1
浏览器道:“胡说!机器码都是二进制格式010110010这种的,你这种B8、C0是什么机器码?”
v8道:“我没有胡说啊!'B8 01 00 00 00'这是二进制机器码的16进制展示,人类使用16进制更方便阅读,但我和CPU交流都是以010110010这种二进制方式。”
这时CPU听到有人叫他的名字,按耐不住了。CPU问:“谁在说俺的大名!v8,那后面的'mov eax,1'是什么?我怎么从来没见你提过?”
v8道:“'mov eax,1'是机器码注释,是给人类大哥看的,我给你看的都是二进制字节码,是010110010这种。像mov它只是诸如1010这种汇编指令的代名词,人类写的是mov,汇编编译器译完就是1010了。
eax是寄存器地址,'mov eax,1'这句指令就是将寄存器的值设为1。同时,它下面那句'add eax,1'是将寄存器的数值加1。add与mov不就是你的两个指令吗,CPU大哥?如果我发错了指令,大哥从来都不曾理会我。”
CPU点点头。
浏览器继续问:“好啊v8,用户每天都骂我慢得像蜗牛,罪魁祸首原来在你这。码农都说你快,我每天看你却很慢。原来你是将js代码先转成了汇编代码,再将汇编代码转成为机器器,一件事转二道手续,这样能不慢吗?为什么不直接将js代码转为二进制机器码交给CPU大哥执行?”
“哈哈哈”,v8大笑道:“浏览器,你只知表面,不知就理。js是解析型语言,如何直接编译成机器码?如果是这样,它不就和Java一样,是编译型语言了吗?”
浏览器反驳道:“虽然是解释型语言,为什么不能先编译再执行?在Java版JS解释器rhino中,js脚本不是被编译为Java字节码执行的吗?”
作者觉得讨论有点跑偏了,道:“言归正传。v8,浏览器给你的js代码,你是读一行调用masm转化一行,还是读完了一起调用masm再转化的?”
v8说:“是一起转化的,但这一切都是在内存那里折腾的。我有两个助手,一个叫初级全码编译器(官名叫Full Code Generator),他将所有js代码依次调用masm全部在内存中走了一遍;另一个叫优化能手编译器(官名叫Crankshaft),他针对运行多次的代码,以全码编译器的编译结果为基础,再作一次优化编译,目的是使代码执行更快。”
这时内存说话了,“是啊,每次都把我折腾的晕头转向。别的exe文件,是先于我这里加载、后交给CPU运行,一次搞定,很干脆。唯有v8交给我的执行文件,连个名字都没有,忽长忽短,变化莫测。”
CPU说:“但是我感觉v8交给我的机器码和普通的exe文件机器码没有什么区别,在我这里他们都是合法公民。只要机器指令全部正确,我就能返回正确的运行结果。”
看来v8并不知道1+1为什么等于2,v8为了执行js快一点,大量占用了内存空间,是用”空间换时间”的方法,博得了“v8引擎执行快”的美名。具体为什么1+1等于2,还需要问问CPU。
作者对CPU道:“CPU,你说只要指令正确,你就能返回正确结果。那么v8将1+1的机器码传给你,你都做了什么?”
CPU道:“报告主人,我什么都没有做。我做的一切,都是让按照您的指令完成的。这一切都是您的智慧啊!”CPU态度很诚恳。
浏览器小声道:“嘘,马屁精!”
CPU不理会,继续说道:“首先,当我看到'mov eax 1',就知道这是叫我将值1移动到寄存器eax处。我有一个助理,叫指令指挥官,他负责指令的分类与调度。例如他看到指令是010100010010,首先从前4位0101判断,这是一个寄存器设置命令,于是就打电话通知寄存器老头来领取数据包裹;如果看到前4位是1010,就知道这是一个加法指令,就打电话通知算术运算单位的加法器来领取数据和任务,待加法器计算完了,他会将运算结果发给寄存器老头保存。”
这时浏览器对CPU如何计算的也起了好奇,问道:“不要说人话,讲机器语言,说人话我们听不懂。指令指挥官是如何给你的单位职员分派任务的?他看到0101,是怎么知道应该分派给寄存器老头的?”
“这么简单你都不明白吗?比如0101这4个bit,依次代表4个路口,每个路口有两个岔,0向左转,1向右转,这样0101一路走下来不就知道是哪个职员负责了。”
指令分派确实简单,关键还在加法器上。1+1等于几是他算出来的,于是作者问道:“CPU,那加法器是如何计算1+1的呢?”
CPU道:“这就不那么简单了。加法器并不知道1+1等于几。加法器是由半加器组成的,而半加器又是由一个异或门加一个与门组成的,如下所示是一个半加器:
(在上图中,A、B是输入,S是结果,C是进位结果。)
学过数学很容易理解,异或门的逻辑是这样的:
负负得负、正负得正、负正得正、负负得正,这就是异或门逻辑。如果说异或门电路有点复杂,那么异或门又可以由与非门表示:
(读者可以将1、0不同值分别代入A、B,验证异或门结果Q)
与非门的逻辑是这样的:
负负得正、负正得正、正负得正、正正得负。与非门简单电路可以是这样的:
x、y是两个开关。x、y的开状态为1,关状态为0。x、y相当于与非门中的A、B。x、y状态全开,以及任何一个状态为开,电路都是不通的。只有当x、y状态全为关,电路才是通的。
与非门可以由开关设计组成,异或门也可以由开关组成。异或门加一个与门组成了半加器,多个半加器串到一起,就组成了全加器,如下所示:
低位半加器的进位结果恰是高位半加器的输入,合在一起就组成了一个多位全加器。所以,我的加法运算能力也不是无限的,能算多大数字是由硬件决定的。”
这下明白了,CPU并不知道1+1等于2。之所以1+1能算出等于2,是人类在设计CPU的时候赋能给它的。而CPU内所有的运算,归根结底又都是开关的开合。从这点来看,计算机的鼻祖竟然是小小的开关。
浏览器问:“CPU,这样说来你的加法器都是由众多开关实现的。那减法运算、乘法运算、除法运算又是怎么实现的?”
CPU道:“减法在我这里也是加法,乘法是换算为多位加法累加的,除法又可以换算为乘法。所以,所有四则运算都是由加法实现的。包括文字与音频、视频信息处理,在我这里都是二进制的加减乘法与逻辑与非。”
浏览器又问:“那这样说,在你内部肯家有很多很多的开关喽?”
CPU说:“人类发明了一种双极型三极管,简称晶体管。在我内部,晶体管不多,也就有几十亿个吧。每个晶体管就相当于一个电路中的开关。”
原来作者在浏览器里简单敲一个1+1,CPU那里就要噼里啪啦开关个不停。
计算机并没有智能。我们从宏观上看,仿佛计算机拥有了智能一般,能处理很多复杂的问题,其实都是通过数以亿计的晶体管开关电路实现的,并且这种能力也都是人类赋予它的。
在人的大脑中,也有几十亿个神经元,像一个计算机一样。人为什么拥有智能?或者人根本也并不拥有智能,在上帝那里,我们的大脑也只是按照他老人家的设计表现开头状态而已?
2018年12月21日于北京
艺览无余
首发于“艺述思维”微信公众号:JS是如何计算 1+1=2 的?
JS是如何计算 1+1=2 的?的更多相关文章
- js如何计算浮点数
js中浮点型是如何运算的呢? 例如:var a=0.69; 我想得到6.9 直接这样写 var c=a*10; alert(c); 得到结果是:6.8999999999999995 到网上一搜,有 ...
- JS: 如何计算一个月有多少天
转自:https://www.2cto.com/kf/201806/755776.html 1 function getCountDays() { var curDate = new Date(); ...
- js如何计算当前日期的前一个月和后一个月?
<div class="query_title_div"><img src="../../images/task/before.png"/&g ...
- 记一次js中和php中的字符串长度计算截取的终极问题和完美解决方案
1.js是用unicode算长度的,比如单字节的算1,中文也算1,但是正常我们想让两个单字节算1,如何计算这个长度 第一种解决方案,用正则,如下 /[\u0x00-\u0xff]/,天真的想着,这样就 ...
- flexible.js字体大小诡异现象解析及解决方案
最近在做一个手机端页面时,遇到了一个奇怪的问题:字体的显示大小,与在CSS中指定的大小不一致.大家可以查看这个Demo(记得打开Chrome DevTools). 就如上图所示,你可以发现,原本指定的 ...
- 在线压缩JS的工具
给大家介绍款在线压缩JS的工具 首先说下该工具的域名:http://javascriptcompressor.com/ 进入后界面如下: 具体要讲下它的功能点:在线压缩 Javascript 源码可以 ...
- 从Chrome源码看JS Array的实现
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
- vue.js移动端app实战1:初始配置
本系列将会用vue.js2制作一个移动端的webapp单页面,页面不多,大概在7,8个左右,不过麻雀虽小,五脏俱全,常用的效果如轮播图,下拉刷新,上拉加载,图片懒加载都会用到.css方面也会有一些描述 ...
- js 将一大段时间均分为很多个小时间段
最近写项目,遇到一个将选中时间段平均分割为若干小段,然后根据小段时间在数据库查询求均值的问题,后台大哥犯懒,非说后台做不了,让我分好传给他ヾ(. ̄□ ̄)ツ゜゜゜好气呦,但还要保持微笑,我就是这么懂礼貌 ...
随机推荐
- Java—继承
继承 继承是类与类的一种关系,是一种“is a”的关系.注意:java中的继承是单继承,一个类只有一个父类. 继承的好处:子类拥有父类的所有属性和方法(private修饰的无效),实现代码的复用 语法 ...
- CKEditor插件开发
以前做过一个教育项目,是有关在线考试的.其中对编辑器CKEditor做了扩充,增加了插入客观题.主观题.选择题和判断题的功能.这里记述下CKEditor插件开发的过程. CKEditor以前叫FCKE ...
- python实现各种排序
1.冒泡排序: # -*- coding: utf-8 -*- def BubbleSort(a): n=len(a) for i in range(0,n-1): swapped=False for ...
- System Center Configuration Manager 2016 必要条件准备篇(Part3)
步骤3.安装SQL Server 2017 注意:在Configuration Manager服务器(CM16)上以本地管理员身份执行以下操作 按照https://go.microsoft.com ...
- eclipse中Tomcat启动了 但看不到tomcat首页
症状: tomcat在eclipse里面能正常启动,而在浏览器中访问http://localhost:8080/不能访问,且报404错误.同时其他项目页面也不能访问. 关闭eclipse里面的tomc ...
- 拼接sql语句时拼接空字符串报sql错误
先上代码(php): $id_card=""; $sql = "select * from people where id_card=".$id_card; 看 ...
- 命令行输入Jmeter提示不是内部或外部命令,处理方式:添加环境变量
命令行输入Jmeter提示不是内部或外部命令,需要在环境变量path中添加jmeter的bin目录绝对路径 我的电脑 > 右击 >属性 > 高级系统变量 > 环境变量 > ...
- mysql的慢查询实战+sql优化
背景:使用A电脑安装mysql,B电脑通过xshell方式连接,数据内容我都已经创建好,现在我已正常的进入到mysql中 步骤1:设置慢查询日志的超时时间,先查看日志存放路径查询慢日志的地址,因为有慢 ...
- BFS变换素数,POJ(3126)
题目链接:http://poj.org/problem?id=3126 解题报告: #include <iostream> #include <queue> #include ...
- while counter<10:
[root@chenbj test]# python Python 2.7.5 (default, Nov 6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat ...