说到 JavaScript 的基础知识,那肯定首当其冲的就是 基础类型 和 引用类型 了。
两种类型
JavaScript 中变量有两种不同类型的值:基本类型、引用类型。
基础类型保存在 栈内存 中,在按值访问,操作的是他们实际保存的值。
引用类型保存在 堆内存 中,但js不允许直接访问内存,所以在操作的时候,实际操作的是 对象的引用。
基础类型 主要有:字符串string
、数值number
、布尔值boolean
、null
、undefined
以及ES6中的symbol
(独一无二的值),加上现在最新的 BigInt
引用类型 主要有:对象object
、数组array
、正则 regExp
、函数function
、日期date
以及 特殊的基本包装类型(String、Number、Boolean
)、单体内置对象(Math
)等。
1 | // 基础类型 |
两种类型的复制
基本类型的复制
复制时,会在栈内存中创建一个新的值,然后把值复制到新分配的位置。
1 | // 基本类型的复制 |
引用类型的复制
复制的是存储在栈内存中的 指针,将 指针 复制到栈中新分配的位置,这个 指针副本 与 原指针 都指向存储在堆中的 同一个对象。由于同指向一个对象,所以当其中一个变量改变时,也将会影响另一个变量。
1 | // 引用类型的复制 |
tip
new String('abc')
属于引用类型,不等于'abc'
1
console.log(new String('abc')=== 'abc') // false
这是因为 String
只是一个 string
的对象封装类型,经过 new
操作符得出来的属于对象的实例。
- 使用
const
声明的对象,可以修改其属性1
2const a = {}
a.b = 123 // 不报错
这是因为 const对象对应的堆内存指向是不变的,但是堆内存中的数据本身的大小或者属性是可变的。而对于const定义的基础变量而言,这个值就相当于const对象的指针,是不可变。
如何判断类型
typeof
typeof
操作符是检测基本类型的最佳工具。
1 | console.log('字符串:',typeof 'abc') // 字符串: string |
可以发现,typeof
并不能完全区分出 null、object、array、regexp、date
等,得到的值都是 object
。
在 Javascript 语言中,typeof null === 'object'
。这样就会错误的认为 null
是一个对象。实际上这只是一个无法修复的bug。可以看这里
在《JavaScript高级程序设计》中这样解释:“因为特殊值null被认为是一个空对象的引用”。
而经过 typeof
测试,object、array、regexp、date
等 也都得出了 object
,这是因为所有的引用类型,在堆中都是对象,其实都是基于Object
实例进行的一种扩展。
至于 typeof function (){}
为什么是 function
, 在《JavaScript权威指南》中function
被看做是object
基本数据类型的一种特殊对象,《JavaScript高级程序设计》也把函数视为对象,而不是一种基本数据类型。1
2var fn = function () { };
console.log(fn instanceof Object); // true
instanceof
instanceof
检测值是不是一个构造函数的实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 常规用法
var stringObj = new String("abc")
console.log(stringObj instanceof String) // true
console.log("" instanceof String) // false
console.log("" instanceof Object) // false
function Foo(){}
var foo = new Foo()
console.log(foo instanceof Foo) // true
// 继承中关系中的用法
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo()
var foo = new Foo()
console.log(foo instanceof Foo) // true
console.log(foo instanceof Aoo) // true
...
经过实验,也可以得出,当使用 instanceof
检测基本类型时候,只会返回false
。
但是当使用 instanceof
检测frame的数组时,会出现问题。所以 Array.isArray()
应运而生。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<html>
<head>
<script>
function test(arr) {
var iframeWin = frames[0]
console.log(arr instanceof Array) // false
console.log(arr instanceof iframeWin.Array) // true
console.log(Array.isArray(arr)) // true
}
</script>
</head>
<body>
<iframe></iframe>
<script>
var iframeWin = frames[0]
iframeWin.document.write('<script>window.parent.test([])</'+'script>')
</script>
</body>
</html>
constructor
如下:1
2var a = 123
console.log(a.constructor == Number) //true
(…待更新)
toString
其实是使用Object.prototype.toString()
方法来检测数据类型。这个方法非常通用。
当然,也可以使用Reflect
,更加简洁。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16console.log(Object.prototype.toString.call(new Boolean(1))) // "[object Boolean]"
console.log(Object.prototype.toString.call(true)) // "[object Boolean]"
console.log(Object.prototype.toString.call(new Number(1))) // "[object Number]"
console.log(Object.prototype.toString.call(100)) // "[object Number]"
console.log(Object.prototype.toString.call(BigInt(11))) // "[object BigInt]"
console.log(Object.prototype.toString.call(11n)) // "[object BigInt]"
console.log(Object.prototype.toString.call(new String("sssssss"))) // "[object String]"
console.log(Object.prototype.toString.call("sssssss")) // "[object String]"
console.log(Object.prototype.toString.call(function(){return 1})) // "[object Function]"
console.log(Object.prototype.toString.call(new Array(1,2,3,4,5))) // "[object Array]"
console.log(Object.prototype.toString.call([1,2,3,4,5])) // "[object Array]"
console.log(Object.prototype.toString.call(new Date())) // "[object Date]"
console.log(Object.prototype.toString.call(new RegExp('sssssss'))) // "[object RegExp]"
console.log(Object.prototype.toString.call(/sssssss/)) // "[object RegExp]"
console.log(Reflect.toString === Object.prototype.toString) // true