前端系统课程 - 21. JavaScript 里的类型

常见数据类型的转换

  • 除了 Null 和 Undefined 类型,其他数据类型都可以使用 toString() 方法转换为字符串类型,Object 类型虽然有这个方法,但是不一定得到预期的结果(可以使用 JSON.stringify() 方法);比 toString() 方法更简单的就是将要转换的数据拼接一个空字符串,这个方法更简略也更强大,它实际是使用了隐式类型转换,通过全局方法 String() 将数据转换类型。

  • 使用全局方法 Boolean() 可以将所有数据类型转换为布尔值;简便方法可以在要转换的数据前,使用两个取反操作符 !!

  • 转换数字类型的方法有 Number()parseInt()parseFloat() 方法;简便方法可以直接在要转换的数据前加正数符号 +;或者使用数据减 0 的操作,例如 '3.14' - 0,字符串 3.14 就会被转为数字 3.14,这种比较常用。

内存中数据存储方式

  • 编译器会将分配到的内存分为代码区和数据区。

  • 数据区分为 Stack(栈内存) 和 Heap(堆内存)。

  • 堆比栈大,栈比堆快。

  • JavaScript 代码在编译起始,会先将声明的变量提到最前面。

  • 基本数据类型都保存在栈内存,复杂数据类型都保存在堆内存。

  • 当把一个基本数据类型赋值给一个变量时,变量中存储的就是这个数据本身;当把这个变量赋值给另外一个变量时,相当于将它保存的数据本身,复制一个拷贝,再赋值给新变量,之后,两个变量各拥有一份相互独立的数据。

  • 当把一个复杂数据类型赋值给一个变量时,变量中存储的是这个数据在堆内存中的地址,这种关系称为“引用”;当把这个变量赋值给另外一个变量时,相当于把保存的引用地址,复制一个拷贝,再赋值给新变量,之后,两个变量引用的是同一个数据,相当于两个人各拥有一张代表同一个数据的“名片”。

循环引用

对象自身循环引用可以实现,但是不能在声明对象的同时将其自身保存在内,因为编译的顺序是先根据赋值操作符也就是 = 号的右边的数据大小来开辟内存空间,然后再将存好的数据的地址,存入变量;如果要完成自身的循环引用,需要在对象声明并赋值完成后,再将其自身引用通过添加属性的方式保存到自身当中;循环引用可能会造成内存泄露

试题解析

1
2
3
4
5
var a = {n: 1};
var b = a; //b = {n: 1};
a.x = a = {n: 2}; //{n: 1, x: {n: 2}},a = {n: 2};
console.log(a.x); //undefined
console.log(b.x); //[object, Object]
  • 上面的题中,第三行的关键点在于确定当时的 a 变量是哪一个;

  • 编译器是从上到下依次执行,但是各种操作符除了有优先级之分,还有从右往左和从左往右两种关联性,而属性访问操作符优先级比赋值操作符的优先级要高,所以先确定了 a.xa 引用的依然是 {n: 1} 这个数据,并在这个数据中添加了一个属性 x,此时 x 只是声明后未定义等待赋值的状态,就是说源数据成为了 {n: 1, x: undefined}

  • 然后赋值运算符从右往左执行,先将 a 变量中的数据,赋值为了 {n: 2} 的地址,再将这个地址,赋值给了 {n: 1, x: undefined} 这个对象中的 x 属性,此时数据成为了 {n: 1, x: {n: 2}}

  • 第四行访问 a.x,此时的 a 引用的数据已经是 {n: 2} ,里面并没有 x 这个属性,故打印出的结果为 undefined

  • 第五行访问 b.x,此时的 b 引用的数据依然是原来包含 n: 1 键值的那个数据,而此时这个数据已经有了 x 这个属性,并且值为另一个对象引用地址,这个地址对应的数据是 {n: 2},将其隐性地使用 toString() 方法转换为字符串后,打印出的结果便是 [object Object]

垃圾回收

  • 如果一个对象没有被引用,它就是垃圾数据,将被垃圾回收器清理掉。

  • 垃圾回收器会在页面运行时,按照浏览器设定好的机制循环作业。

深拷贝与浅拷贝

  • 拷贝即复制,复制后的数据与原数据,完全独立且互不影响,就称为“深拷贝”;基本数据类型之间的复制,都是深拷贝。

  • 其实基本数据类型之间的复制谈不上什么”深浅“,深拷贝与浅拷贝主要是对于对象数据之间的复制而言的。

  • 如果将一个对象赋值给一个变量,实际是将这个对象的引用地址保存在里变量当中,那么当复制这个变量到另一个变量时,只是把这个引用地址,存到了新的变量中;当通过新的变量改变对象的属性后,访问原先的变量也能发现对象的属性变化;这种程度的复制,就是“浅拷贝”。

课后拾遗

  • JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。

  • 数组的某个元素为空元素(即两个逗号之间没有任何值)时,便称为这个数组存在空位;空位不影响数组的长度;如果读取空位,会返回 undefined,但是把一个元素赋值为 undefined 后,这个元素不属于空位;如果使用 delete 操作符删除某个元素,那么这个元素对应的位置便成为了空位;使用 for...in 语句、数组的 forEach() 方法以及对象的 keys() 方法遍历数组时, 空位都会被跳过。