数据类型
数据类型划分
javascript中定义了6中基本数据类型(原始值类型),和一种复杂数据类型(引用类型),所谓复杂类型,其本质是由无序的名值对(key:value)组成的。
基本数据类型:
- String
- Number
- Boolean
- undefined
- null
- Symbol (ES6 新增)
复杂数据类型
- Object
原始值引用值
上面提到了原始值和引用类型,可能有些人对于引用类型很熟悉,但是原始值却很陌生实际上,在ECMAScript中,变量可以存放两种类型的值,即原始值和引用值。
原始值(primitive value)
原始值是固定而简单的值,是存放在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
引用值(reference value)
引用值则是比较大的对象,存放在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(pointer),指向存储对象的内存处。所有引用类型都集成自Object。
之所以说原始值是固定的,原因是当我们对原始值进行一些操作时结果返回的都是一个新的副本,但是对引用值操作时可能更改原值。
var str = 'asdfghjkl';
var obj = {name:1,age:2};
var str2 = str;
var obj2 = obj;
str2 = 'lkjhgfdsa';
obj2.name= 3;
console.log(str,str2,obj,obj2)
//asdfghjkl lkjhgfdsa {name: 3, age: 2} {name: 3, age: 2}
obj == obj2 //true
通过以上代码可以明确看出字符串是按值传递的,在赋值时会新建存储空间,将str 和 str2 存放在不同的内存空间内,对象是按引用传递的,obj = obj2时没有新建堆内存空间,而是在栈内存中存放标识符和值的引用地址,引用地址与obj的栈值相同,指向堆内存中的存储空间。
同时可以看到obj == obj2 返回true,这是为什么?
var obj3 = {name:3,age:2}
obj == obj3 //false
obj3 与 obj 的属性和属性值是一样的,但是 obj == obj3 却返回false, obj == obj2 返回true, 这说明引用类型在判断相等的时候比较的是指针,即指向对内存的地址。
提到原始值 引用值 内存地址等词,就不得不提数据的存储空间
数据的存储方式
堆栈
之前说到基本类型存储在栈内存中,复杂类型存储在堆内存中,那么什么是栈,什么是堆?
这里说的堆和栈并不是一种数据结构,而是指存储空间,JVM内存划分为:寄存器,本地方法区,方法区,堆内存,栈内存,我们说的堆栈就是这里的堆内存 和 栈内存。
栈区(stack)
由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap)
一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
堆栈区别
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收,前提是没有任何引用。
关于堆和栈的内存空间,这里只是简单提起,强调指内存空间并非数据结构。
不同数据类型的存储区别
var a = undefined;
var b = null;
var c = 'asdfg';
var d = 123;
var e = true;
var f = [1,2];
var g = {name:1,age:2};
var h = g;
下图解释不同每一种类型的存储方式
上图体现出每种数据类型在内存中的存储方式:
- 基本类型undefined null String Number Boolean 直接将标识符和值存储在栈区内;
- 复杂类型对象,数组等,栈区内只存放标识符和指向堆内存中的对象的指针,真真的对象值存储在堆内存中,同一对象的引用指针相同:g == h;
数据类型的判断方式
数据类型的判断有多种方式,下面简单介绍
typeof
typeof 用于检测数据的类型,返回值有
- number
- string
- boolean
- undefined
- object
- function
var u ,a = 'asdf',b = 1,c = true,d = {},e = null,f = function(){}
typeof a; // string
typeof b; //number
typeof c; //boolean
typeof u; //undefined
typeof d; //object
typeof e; //object
typeof f; //function
typeof a == 'string' //true
typeof a == String //false
以上代码可以看出
- 返回值为字符串,并且区分大小写;
- typeof null 返回 object,因为null是Object的一种,表示空引用,即栈内存储的指向堆中对象的指针为空;
- typeof 可以判断function的类型,并且对于非Object类型的数据区分类型比较方便;
- 不能区分Object的 具体类型,如 Array Date
instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
由于js可在多个frame之间交互,但是每个frame有自己的全局环境,所以不同frame和窗口之间Array是不同的,所以无法用 a 环境下的数组去判断是否是 b 环境下的Array对象的实例
var a = 'asdffgg';
var b = 1;
var c = new String('aaaa');
var arr = [1,2];
var date = new Date();
a instanceof String //false 思考:为什么是false?
b instanceof Number //false
c instanceof Number //true 思考:a 和 c 同样是字符串结果却不一样
arr instanceof Array //true
arr instanceof Object //true 思考:为什么 Array 和 Object都是true?
date instanceof Date //true
date instanceof Object //true
typeof a //string 字面量的形式 返回基本类型 string
typeof c //object new 一个String的实例 返回复杂类型 object
通过以上的代码段体现出以下特点和弊端:
- instanceof 用于检测Object类型的对象,基本类型的字面量值永远返回false;
- instanceof 由于对象的继承关系,所以只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型
数组即是Array的实例,又是Object的实例的原因详解:
由于对象之间存在继承关系,所以instanceof 能够判断出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最终 Object.prototype.__proto__ 指向了null,标志着原型链的结束。这里关于原型链的知识点不细说
所以无法判断实例对象具体是Object的哪一种类型。
constructor
constructor 属性返回对创建此对象的构造函数的引用。
当定义一个函数Fn的时候,JS引擎会为其添加一个原型prototype,并为原型添加一个constructor属性,将其指向Fn的引用
实例f的constructor是指向其构造函数Fn的,所以通过constructor我们可以判断其构造函数是谁,从而间接的判断对象的具体类型
但是由于constructor在实现继承的时候可以被更改,实质更改是由于子类继承父类的时候可能重写了子类的原型prototype,因此使子类的prototype上的constructor发生变化,因此类型判断可能不准确
undefined 和 null没有constructor属性
toString
Object.prototype.toString 方法可以返回对象的内部属性[[Class]],格式为[object,Xxxx],其中Xxxx为具体的类型
Object.prototype.toString.call('') //"[object String]"
Object.prototype.toString.call([1]) //"[object Array]"
Object.prototype.toString.call(new Date) //"[object Date]"
为什么不调用对象的toString方法,而是通过call调用Object.prototype.toString?
'11'.toString() //"11"
[1,2].toString() //"1,2"
new Date().toString() //"Fri Jun 22 2018 14:44:52 GMT+0800 (CST)"
可以看到字符串输出的是本身,数组输出的是“,”链接的数组字符串,时间类型输出的时间字符串,都不是内部属性[[Class]],原因是实例的构造函数重写了toString方法,所以要吊用Object对象的原型伤的toString方法。
类型转换
转数值类型
显示转换:Number() parseInt() parseFloat()
向Number 转换规则 ToNumber
- Boolean类型的true false,转成1 和 0
- 数值直接转成原始值,即本身
- null 转为0,undefined 转为NaN
- 字符串类型,数值字符串转为数值型数字;空字符串''转为0;非数字字符串转为NaN
- 对象,则调用对象的valueOf()方法,按前面规则转为原始值,如果返回NaN,调用对象的toString()方法,返回值再依照前面规则转数值
var a = {a:1}
Number(11) //11
Number('123') //123
Number(true) //1
Number(null) //0
Number(undefined) //NaN
Number('') //0
Number(a) //NaN a.valueOf返回 {a:1},a.toString 返回 '[object,Object]',字符串转数值 返回NaN
parseInt parseFloat 这里不细说
隐式转换:操作符
- +做为一元操作符,操作数转化为数值类型
- +作为二元操作符,两个操作数中只要有一个是字符串类型,那么另一个也转化成字符串类型
- +作为二元操作符,两个操作数均不是字符串类型,那么两个操作数均各自隐式向数值型转化,然后在计算
- 如果数值计算的操作符不是+操作符,那么操作数向数值转化
转String类型
显示转换 toString() String()
toString:返回相应的字符串表现,
向字符串转换规则 ToString
- 数字类型,转为数字字符串
- 字符串,返回原值
- undefined null,没有toString方法,可以用String()方法,返回"null"和"undefined"字符串
- Boolean 转为"true" "false"
- 对象,先调用toString得到原始值,如果是原始值类型则返回;不是则调用valueOf,返回结果如果是原始值类型则返回,不是在做toString调用,返回结果
var a = {a:1};var b = 11;var c = '11'; var e = '[1,3]';var d = true
a.toString() //'[object,Object]'
b.toString() //'11'
c.toString() //'11'
d.toString() //'true'
e.toString() //'1,3'
隐式转换同+操作符数值转化
转Boolean类型
显示转换:Boolean()
向Boolean转换规则:
数字0,''空字符串,null,undefined,NaN转为false,其余转为true
隐式转换操作符 if
什么是隐式转换?
隐式转换与执行环境和操作符相关,当前操作期望某种值的时候,就会发生隐式转换,实际上面提到的具体规则点就是隐式转换的过程。
内置对象(本地对象)、单体内置对象,宿主对象
本地对象:不依赖于宿主环境的对象
Object Array Date Function RegExp String Boolean Number
单体内置对象:由ECMAScript实现提供的,不依赖于宿主环境的对象,这些对象在ECMAScript程序执行前就已经存在了
Global(所有不属于其他任何对象的属性和方法都属于Global,全局变量,方法),Math,一些数学公式和计算方法
宿主对象:由ECMAScript实现的宿主环境提供的对象,可以理解为:浏览器提供的对象。所有的BOM和DOM都是宿主对象 , Window
参考
javascript 高级程序设计
堆和栈的概念和区别
全面解析js中的数据类型与类型转换