js parsefloat保留两位小数_《JS类型转换、内存图、GC、深/浅拷贝》

本文针对阮一峰JS教程的部分补充,介绍了JavaScript的如下信息:

1、JS类型转换(部分API)

2、内存图

3、GC(垃圾回收)

4、深拷贝(复制)/浅拷贝(复制)初识


1、JS类型转换(部分API)

符号——symbol暂时不讨论。

这里只介绍其他六种类型间的转换。

(1)其他类型转换为字符串

(1-1)x.toString()方法(x是被转换的类型,这里的S是大写,小写会报错。部分API隐藏的方法。也是最传统的方法)

toString()方法可以将布尔、数字转换为字符串;

cc9f3e571e3024eb9a5d8f70e1ce8cb3.png

toString()方法将null、undefined转成成字符串会报错;

404f4f0afaa9d8c54eb23fa7d2503bdf.png

toString()方法可以将对象转成成字符串"[object Object]",这个前面的o是小写,后面的O是大写,但是这个结果并不是我们想要的字符串。

d5d4acb9bddd7946b95054a75e94e3f2.png

(1-1-1)console.log()的括号中在非undefined和非null的情况下隐含了toString()

console.log('fang'),输入的是字符串'fang',但是打印出来的是fang,不是字符串,说明这里的打印是随便打印的。

console.log(),这个括号里面输入数字或者字符串都会转换为字符串,就算不是字符串也会自动转换成字符串,所以括号这里隐含了toString()这个API。

0b65a4212f60a36fe5bdb193345f51b4.png

如果输入非字符串,并且不能自动转换成字符的类型,比如变量,如果变量没有赋值也就是undefined类型,会报错,因为undefined使用toString()不能转换成字符串。

160d8293ed9abba182767f280c5611ed.png

但是输入对象这里并不会转换为字符串。

d8df857d4a391e617369c982c11cf291.png

(1-1-2)对象用中括号[]取值,在非undefined和非null的情况下隐含了toString()

如果一个变量未赋值,它是不能转换成字符串的,会报错。

用中括号设置对象key-value,中括号里面的类型,不管是数字或者字符串都隐含了toString()这个API,会自动转换成字符串。

eded07dc7d9d9dfbceb1dcd97339ed98.png

就算是对象也是会转成字符串

8d2e7412236e38e04dd6c1c39da8b347.png

这样隐含了toString()的情况还有挺多的,这里就不一一例举了。

注意一个问题:

如果一个变量未声明,用console.log(n)会报错,console.log((n).toString())同样报错

如果一个变量声明了,用console.log(n)不会报错,console.log((n).toString())会报错,报错的原因就是toString()不可以转换undefined为字符串。

9c5547c7c2cd3a1760773af475d61515.png

对象用中括号[]取值也一样。

如果一个变量未声明,用a[n]=1会报错,a[(n).toString()]=1同样报错

如果一个变量声明了,用a[n]=1不会报错,a[(n).toString()]=1会报错,报错的原因就是toString()不可以转换undefined为字符串。

7ee943893ccc3389f0b1dff17b62a6c3.png

(1-2)''+任意类型,或者,任意类型+''(可以把null和undefined都转成字符串的装逼方法)

左边引号''+数字,可以得到字符串,数字+''右边引号,也可以得到字符串;

引号''+布尔值也可以得到字符串;

引号''+未声明的变量,会报错;

引号''+声明后的变量,会得到字符串'undefined';

引号''+undefined,会得到字符串'undefined';

引号''+null,会得到字符串'null';

引号''+空数组,会得到空字符串;

引号''+对象,会得到字符串"[object Object]",前面的o小写,后面的O大写;

引号''+函数,会得到字符串;

双引号""也是一样的效果;

391a95808b32602b8c10e3964b925688.png

(1-2-1)不常用的使用方法

字符串和数字相加,会优先把非字符串转换为字符串然后连接。

第一行其实就是经过了第二行转换后,最后和第三行一样。

aa8757a7320a9133f43803d9ada03b35.png

(1-2-1)console.log()的括号中隐含了方法''+,就算undefine或者null也不会报错

fd1a8dd1f2aa5df0ff3d894a06244115.png

这里打印出来的仍然不是字符串'undefined'或者'null',所以console.log()还是随便打的。

(1-2-2)对象用中括号[]取值隐含了方法''+,就算undefined或者null的情况下也不会报错

3e3c3ca7075fa113df65a0601694a575.png

(1-3)全局对象(函数)——String()

window.String(),括号里面布尔转换成字符串;

window.String(),括号里面null转换成字符串'null';

window.String(),括号里面undefined转换成字符串'undefined';

window.String(),括号里面空数组转成空字符串;

window.String(),括号里面对象转成"[object Object]",前面的o小写,后面的O大写;

window.String(),括号里面函数转成字符串;

直接用String(),也可以实现转换为字符串;

window.String(),括号里面是未声明的变量,会报错;

window.String(),括号里面是声明后的变量,转换成'undefined';

5631c307bdc736b27c156a425b70ef15.png

数组类的对象三种方法都可以正常转换

bbba67342d8cac823983fb38f504263a.png

(1-3-1)console.log()的括号中隐含了String(),就算undefine或者null也不会报错

e4e1cba2a07533e911079b1308abd801.png

这里打印出来的仍然不是字符串'undefined'或者'null',所以console.log()还是随便打的。

(1-3-2)对象用中括号[]取值隐含了String(),就算undefined或者null的情况下也不会报错

5c0fde47d769cf3a0a917be4f1ab4fcf.png

小结:

console.log()或者中括号[]对于null和undefined不会报错。

所以console.log()或者中括号[]隐含的API更像String()方法或者''+方法,因为对于null和undefined这种方法不会报错。

而toString()方法对于null和undefined会报错,就跟console.log()不同了。

console.log()这里打印出来的不是字符串,因为没有引号,所以console.log()还是随便打的。

f1b0827972932f20ff433e375153490c.png

53d22376d877a0b31e5005985fc8ad93.png

81f40ca4173425339eba5dd388799c55.png

(2)其他类型转换为布尔

(2-1)Boolean()方法

其余五种类型都可以使用该方法转为布尔

257a56c0e7779aa0a908e2ed7a7b4624.png

(2-2)两个逻辑运算符非(!!)方法

其余五种类型都可以使用该方法转为布尔

51d908c954abe416b2caee37eb94ea8c.png

(2-3)五个falsy值

Falsy​developer.mozilla.org
aa92d90121d0093a11f2819e9c14cf34.png

五个falsy值:

1、数字0;

2、数字NaN;

3、空字符串;

4、null;

5、undefined。

这五个falsy值转换为布尔都是false,其余的都是true

a2fd6b6e76e6dd0d08cb9cec715223b3.png

小结:

bf98407b20b4be8a0088081eee3a312d.png

(3)其他类型转换为数字

(3-1)Number()方法

未声明的变量名通过Number()转换会报错;

声明的变量名通过Number()转换为NaN;

字符串'1'通过Number()转换为1;

字符串'1a'通过Number()转换为NaN;

空字符串通过Number()转换为0;

null通过Number()转换为0;

undefined通过Number()转换为NaN;

数组[1,2,3,4]通过Number()转换为NaN;

空数组[]通过Number()转换为0;

空对象{}通过Number()转换为NaN;

对象{name:'bomber'}通过Number()转换为NaN;

布尔true通过Number()转换为1;

布尔false通过Number()转换为0;

c8f30c880215daeba4fdc7ff1b009d51.png

字符串'011'通过Number()转换为11,说明默认是按照十进制转换,不会按照前面的0转换为八进制

78bf23fa617dd3a608b7790f2d6ca7e5.png

如果是小数,例如:字符串'23.3434'通过Number()转换会23.3434

87d879adcbb5e1772356db6e0c391135.png

(3-2)全局函数(对象)parseInt()

parse是解析的意思。

布尔true通过parseInt()转换为NaN;

布尔false通过parseInt()转换为NaN;

未声明的变量名通过parseInt()转换会报错;

声明后的变量名通过parseInt()转换会NaN;

null通过parseInt()转换会NaN;

undefined通过parseInt()转换会NaN;

空字符串通过parseInt()转换会NaN;

空数组通过parseInt()转换会NaN;

空对象通过parseInt()转换会NaN;

字符串'011'通过parseInt()转换会11,因为默认是十进制,不会按照前面的0转换为八进制;

数组[1,2,3]通过parseInt()转换会1,因为只转换能够识别为数字的类型,碰到不能识别为数字的类型终止转换;

对象{name:'bomber'}通过parseInt()转换会NaN;

字符串'011',10通过parseInt()转换会11,因为后面的10是按照十进制转换的意思,默认也是按照十进制转换;

字符串'011',8通过parseInt()转换会9,因为后面的8是按照八进制转换的意思,不写这个8会按照默认也是按照十进制转换;

字符串'12a'通过parseInt()转换会12,因为只转换能够识别为数字的类型,碰到不能识别为数字的类型终止转换;

68d57e1b7edf4e550c46ad50df0bb6c9.png

如果是小数,会省略掉小数点后面的数字,因为小数点不能识别为数字。

字符串'23.3434'通过parseInt()转换会23,因为只转换能够识别为数字的类型,碰到不能识别为数字的类型终止转换,比如碰到这里的小数点就会终止返回。

28214b7b0f75a4dd472172b27718541f.png

(3-3)全局函数(对象)parseFloat()

Float是浮动的意思,在这里也就是可以转换浮点数(小数)。

未声明的变量名通过parseFloat()转换会报错;

声明后的变量名通过parseFloat()转换为NaN;

字符串'1'通过parseFloat()转换为1;

字符串'1.1123'通过parseFloat()转换为1.1123;

布尔true通过parseFloat()转换为NaN;

布尔false通过parseFloat()转换为NaN;

null通过parseFloat()转换为NaN;

undefined通过parseFloat()转换为NaN;

对象{name:'bomber'}通过parseFloat()转换为NaN;

数组[1.3,2.3,3.3]通过parseFloat()转换为1.3,因为只转换能够识别为数字(包括小数)的类型,碰到不能识别为数字(包括小数)的类型终止转换;

空数组通过parseFloat()转换为NaN;

空对象通过parseFloat()转换为NaN;

空字符串通过parseFloat()转换为NaN;

字符串'011'通过parseFloat()转换为11,因为默认是十进制,不会按照前面的0转换为八进制;

字符串'011',10通过parseFloat()转换为11,因为后面的10是按照十进制转换的意思,默认也是按照十进制转换;

字符串'011',8通过parseFloat()转换为11,虽然后面的数是8,但是因为Float针对的是小数,只有十进制,所以这里无法改变为其他进制;

字符串'12a',通过parseFloat()转换为12,因为只转换能够识别为数字(包括小数)的类型,碰到不能识别为数字(包括小数)的类型终止转换;

56ad9a24b5260ea199bdf10b6cadb508.png

(3-4)x-0(x是被转换的类型)

这是应该算老司机比较骚的方法啦

未声明的变量名通过-0方法会报错;

声明后的变量名通过-0方法会转换为NaN;

空字符串通过-0方法会转换为0;

字符串'5'通过-0方法会转换为5;

字符串'5.15'通过-0方法会转换为5.15,说明小数也可以转换;

空数组通过-0方法会转换为0;

数组[1.4,2.2,3]通过-0方法会转换为NaN;

空对象通过-0方法会转换为-0;

对象{name:'bomber'}通过-0方法会转换为-0;

布尔true通过-0方法会转换为1;

布尔false通过-0方法会转换为0;

null通过-0方法会转换为0;

undefined通过-0方法会转换为NaN;

字符串'011'通过-0方法会转换为11,说明默认是按照十进制转换,不会按照前面的0转换为八进制;

aaa97be002d9207dc10d76684a4e6c1e.png

(3-5)+x或者-(-x)(x是被转换的类型)

这也是同样骚的方法,但是更简洁。

未声明的变量名通过+会报错;

声明后的变量名通过+会转换为NaN;

空字符串通过+会转换为0;

字符串'5'通过+会转换为5;

字符串'5.15'通过+会转换为5.15,说明小数也可以转换;

空数组通过+会转换为0;

数组[1.4,2.2,3]通过+会转换为NaN;

空对象通过+会转换为NaN;

对象{name:'bomber'}通过+会转换为NaN;

布尔true通过+会转换为1;

布尔false通过+会转换为0;

null通过+会转换为0;

undefined通过+会转换为NaN;

字符串'011'通过+会转换为11,说明默认是按照十进制转换,不会按照前面的0转换为八进制;

f0cc2370fd0246a301ee4d5ac0cc0ee1.png

小结:

e1c751797345421c34b903f35a0992f2.png

571fce6bfbc2b92c72ebc2d048826096.png

(4)其他类型转换为undefined和null

因为这两种类型里面分别只有一个值,而且就是本身,所以不需要通过转换来得到。


2、内存图

该节的内容如果是初学者或者是对象理解不够的最好按照顺序看。

(2-1)前置小知识

内存外观和特点,截图来自饥人谷

f09fa61a3bb49e605f3d93b7a93dbe1a.png

开机后浏览器占用内存后,分给JS的内存100M左右,截图来自饥人谷。

1ff4fe4a15c9f1927775e1eb41217b64.png

100M内存分配给JS的代码区和数据区,截图来自饥人谷。

09d02a02cb16ab9491889712011d1ce0.png

100M内存分配给JS数据区继续分两个区(stack栈内存和Heap堆内存),截图来自饥人谷。

ff466b8cc3a5e329c2e193b001e8a054.png

(2-2)简单类型(例如数字)和复杂类型(对象)在内存中的分配形式

(2-2-1)类型为数字一般都是存储在stack栈内存中

45afb5e050c2d172e6d653c7bbe536b4.png

(2-2-2)复杂类型(对象)在内存中的存储

(2-2-2-1)类型为对象,如果实际内容存储在stack栈内存中,会花费大量的时间来移动,效率会非常慢,来看看为什么

16536007286872e70c0d9487f7d15fbb.png

(2-2-2-2)类型为对象,实际内容不存储在stack栈内存中,存储在stack栈内存中的只是Heap堆内存的一个地址,而实际内容是存储在Heap堆内存中

通过c.gendar='male'和var c2={name:'jack'}操作后,代码区,数据区的栈stack内存区以及数据区的Heap堆内存区的变化情况

01b2d3ee171d9f6ec8fa1ce63c17952e.png

通过c2=c赋值后,代码区,数据区的栈stack内存区以及数据区的Heap堆内存区的变化情况

2151c4f9da464224afaa6df009e17434.png

总结的部分知识:

1、以上对象里面画的箭头(由stack栈内存区地址指向heap堆内存区的箭头)并不是指针的意思,指针是C语言的概念,跟JS无关。这里只是一种虚拟的示意,在JS里面简称引用。JS这里只是stack栈内存的地址刚好在Heap堆内存里面也有一个这个地址,并且Heap堆内存地址里面还储存了具体内容;

2、ES3里面数字是64位,字符是16位;

3、简单的类型:数字(number),符号(Symbol),null,undefined,布尔(boolean)都是存在stack栈内存里面;

4、复杂类型:对象(object)存Heap堆内存的地址,这个地址储存在stack栈内存里面;地址代表的具体内容存在Heap堆内存里面;

5、简单的类型:字符串(string)可以存在stack栈内存里面,也可以存在heap堆内存里面;

上面一堆的分析过程是人看不到的过程,下面是人能看到的结果

67410b3c680cba032aca0ecda3c2d13a.png

(2-3)分析若干个用到内存图的题目来加深印象

题目1:

e657b5c5ccfee66c21cdefee78c7a3d2.png

使用内存图分析过程

5e12c5f6d336bc638b6b7cca8b917d4b.png

所以a===1

e98570f09f8d9f62cec8da37856c260a.png

题目2:

fc37691791655905de429eaba76ef8c1.png

使用内存图分析过程

44d8ef027c66a6d8e3288c1c5dc3ae3d.png

所以a.name==='a'

af9efa4a91f92765c68e9120ac2867b2.png

题目3:

5aa8bb395199889287ffeae8a3cae587.png

使用内存图分析过程

aabb77ef10eaa4ea8b3f65431d9f2c29.png

所以a.name==='b'

05bf7c50fe7ced4a8e2122400ca7dbc4.png

题目4:

2539129d0939ae4f3383d4f62e8f3041.png

使用内存图分析过程

f1e9bcaddf37c71621c43983613b0bac.png

所以a==={name:'a'}

f006d8a84dd5abdd8427c69869399a69.png

题目5:

var 

person.self是什么?

person.self.name是什么?

person.self.self.self.self是什么?

person.self.self.self.self.name是什么?

使用内存图分析过程

5b089081e748064eb0ae145f11a7d6ac.png

所以person.self是person

person.self.name是'bomber'

person.self.self.self.self是person

person.self.self.self.self.name是'bomber'

39e2b4b90895ea0026f5e4e4561ca22c.png

题目6:

var 

使用内存图分析过程

58db08d7da506866ddaf7b29bc6dad37.png

所以此时,a.x===undefined,b.x==={n:2}

67493dec0b5b4336dda5fdcac0832c1a.png

alert(a.x)会显示undefined

5b88fa5398f27d50bf1e04cc0a9e076d.png

alert(b.x)会显示[object Object]

fc415c522d2fbbe53711d13fcbdadfac.png

alert(b.x)会显示[object Object]是因为alert里面隐含了String(),其实也就是alert(String(b.x))

具体见前面章节(1)其他类型转换为字符串中的说明

题目7:

var 

使用内存图分析过程

97ccfa39e4b90c4dce7454cb98d91106.png

所以此时,a.x==={n:2},b.x===undefined

8434e2173a7acb7de68b3615c679e919.png

所以此时alert(a.x)会显示[object Object],alert(b.x)会显示undefined。

2019.5.28更新

经过查询发现.运算的优先级仅次于圆括号(),所以这么记忆也是不错的选择,就是点运算是最先计算的,

所以a.x=a={n:2},先运行a.x,此时a.x里面的a已经变成了{n:1,x:undefined},然后从右向左赋值,a={n:2},

此时b也是{n:1,x:undefined},最后执行a.x=a(这里左边的a.x的a的地址跟右边的a的地址是不同的,左边的a.x其实可以看成b.x)。所以a.x已经初始化过了,它是{n:1,x:undefined}.x=a。

简单来说也就是a先经过成员运算符的操作,然后再经过赋值操作,那么按照最后的赋值操作的值为最终的值,也就是a={n:2}为最终值。a的地址也改变了。但是b的地址赋值之后就没有再次赋值,所以它还存在之前地址存的值。

优先级链接

运算符优先级​developer.mozilla.org
aa92d90121d0093a11f2819e9c14cf34.png

2020.2.14增加测试结果

7be16b4f2f2602dcf66919adc450bf50.png

3、GC(垃圾回收)

内存管理​developer.mozilla.org
aa92d90121d0093a11f2819e9c14cf34.png

浏览器每个页面都会用很多内存,而某个页面用完了之后没用了,就可以回收给浏览器继续给别的页面用,或者回收给系统等等。

这里换做对象来解释就是:如果一个对象没有被引用,它就是垃圾,将会被回收。

https://baike.baidu.com/item/GC/66426?fr=aladdin​baike.baidu.com
48e10b08aca38fae4343b22aecdf1c66.png
https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8)​zh.wikipedia.org

通过一些题目来理解垃圾回收

题目1

var 

使用内存图分析过程

92b4231dd9ee4fbce0f61582718aa6b9.png

题目2

var 

使用内存图分析过程

582fc335b25e8c645a0db7233766eff4.png

document.body.οnclick=fn,其中document是一个对象,该对象里面还有一个对象body,body里面有一个onclick属性,也就是key,那么最后的=fn,这里的fn就是value,所以最后的就是在body里面使onclick:null变为onclick:fn。

fn=null之前被两个地方引用,fn=null之后,它还被一个地方引用,所以这里fn不是垃圾。

如果上面在加一行代码document.body.οnclick=null,这样对象fn的地址ADDR700在Heap堆内存区才成为了没有被引用的地址,就会被当做垃圾。

var 

或者题目2代码对应的页面被关掉,这样的document就不存在了,那么对应的ADDR700、ADDR701、ADDR702在Heap堆内存区都没有被引用,这三个都成为了垃圾,将会被回收。

关闭页面使得页面中的document不存在不适用于IE6.0浏览器,因为该浏览器中存在BUG。因为IE6.0的垃圾回收的算法存在一些问题导致关闭页面认为ADDR700、ADDR701、ADDR702在Heap堆内存区都没有被引用,但并不是垃圾,不会被回收。IE6.0需要把整个浏览器关闭掉,才会认为这三个没有引用的地址ADDR700、ADDR701、ADDR702是垃圾。这就是所谓的内存泄露。

所以IE6.0浏览器在关闭页面的时候需要清除掉没有被引用的对象的垃圾需要增加下面的代码

window
window.onunload​developer.mozilla.org
aa92d90121d0093a11f2819e9c14cf34.png

4、深拷贝(复制)/浅拷贝(复制)初识

通过前面的内存图分析,现在已经不用画内存图也可以知道对象的中stack栈内存区和Heap堆内存区的变化了。

对于基本类型,也就是没有用到对象的情况下。例如

var 

这个特点叫做b变化不影响a,这就叫做深拷贝。所以基本类型的简单赋值都是深拷贝,因为没有用到引用。

对于复杂类型(对象),例如

var 

这个特点叫做b变化导致a变化,这就叫做浅拷贝,所以复杂类型(对象)的简单赋值都是浅拷贝,因为a和b都是引用同一个Heap堆内存的地址。

怎么样在对象中实现深拷贝呢?

需要把这句话(var b=a)换能够创建和a不同的stack栈内存地址,但是引用在Heap堆内存地址对应的具体内容一致的代码,如果a里面还有对象,b也要跟a完全一样,但是stack栈内存的地址不能一样。

也就是要实现一个b使得同a中所有的stack栈内存地址完全不一样,但是Heap所引用的地址的内容完全一样的代码,这样才能实现为深拷贝。

因为一旦stack中有一样的地址,那么在修改b的时候会影响到a。

var a={name:'a'}
var b=a// 需要把这句话换能够创建和a不同的stack栈内存地址,但是引用在Heap堆内存地址对应的具体内容一致的代码,如果a里面还有对象,b也要跟a完全一样,但是stack栈内存的地址不能一样。
//也就是要实现一个b使得同a中所有的stack栈内存地址完全不一样,但是Heap所引用的地址的内容完全一样的代码,这样才能实现为深拷贝。
b.name='b'
//这时候a.name也是'b',因为a和b都是引用同一个Heap堆内存的地址

部分总结:

1、JS类型转换的部分前面有小结,也有总结的表格;

2、内存图就看前面的7个题目;

3、GC(垃圾回收),理解了内存图这个就迎刃而解;

4、深拷贝(复制)/浅拷贝(复制)——在理解内存图的基础上,b变化导致a变化,叫浅拷贝,b变化不影响a,叫深拷贝。

本文为本人的原创文章,著作权归本人和饥人谷所有,转载务必注明来源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值