首页 > 代码库 > JavaScript筑基篇(二)->JavaScript数据类型
JavaScript筑基篇(二)->JavaScript数据类型
说明
介绍JavaScript数据类型
目录
- 前言
- 参考来源
- 前置技术要求
- JavaScript的6种数据类型
- 哪6种数据类型
- undefined 类型
- null 类型
- boolean 类型
- number 类型
- string 类型
- 复杂 类型
- 基本型和引用型的不同
- 关于数据类型的一些常见疑问
- 为什么typeof null === ‘object‘
- string,String,object,Object,function,Function的关系
- 关于String类型与string类型的疑问
- function类型与object类型的区别
- ==和===的区别
- typeof的作用
- instanceof的作用
- Object.prototype.toString的作用
- 基本数据类型为什么能够使用toString等操作
前言
参考来源
前人栽树,后台乘凉,本文参考了以下来源
- JavaScript之数据类型
- 知乎:为什么JavaScript里面typeof(null)的值是"object"?
- SF:JS中为什么不能给基本类型的变量添加属性和方法?
前置技术要求
阅读本文前,建议先阅读以下文章
- JavaScript变量、值与对象
JavaScript的6种数据类型
var 变量 = 值; //其中只有两种类型,一种是基本类型(类似于常量数据),一种是引用类型(对象)
首先,我们要明确一点JavaScript的数据类型即为值的数据类型。JavaScript中有6种数据类型(5种基本数据类型,1种引用类型)
哪6种数据类型
- 五种基本数据类型(其实也统称为基本型或原始型)
undefined,null,number,boolean,string
- 一种复杂数据类型(引用型)
Object
undefined 类型
undefined型只有一个值,即特殊的undefined。使用var声明变量但未对其加以初始化时,这个变量的值就就是undefined。例如
var a;console.log(a===undefined);//true
null 类型
null型也只有一个值,即null,从逻辑角度来看,null值表示一个空指针(这也是 使用typeof操作符检测返回object的原因)。如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其它值。这样只要直接检测null值就可以知道相应的变量是否已经保存了一个对象的引用了。
var a = null;console.log(typeof a);//"object"
实际上,ECMAScript中,可以认为undefined值是派生自null值的,所以undefined==null但是undefined!==null
console.log(undefined==null);//trueconsole.log(undefined===null);//false
注意,一定要区分undefined和null的使用。一般来说,变量的值初始设置为undefined(记得别显示设置,由解释器默认设置即可)。而引用型对象的初始值一般可以显式的设置为null。或者可以这样理解undefined作为基本数据类型的变量的初始值(默认设置),null作为引用类型的变量的初始值(显式设置)
boolean 类型
boolean型只有两个字面值true,false。但是这两个值和数字值不是一回事,因此true不一定等于1,而false也不一定等于0。
要将一个值转为boolean型等价的值,有两种方案:
- 一种是显式转换-调用类型转换函数Boolean()
- 一种是自动转换-如if(value)中的value就会自动转化成为boolean值
各类型与boolean型之间值得转换关系如下表
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
boolean | true | false |
string | 任何非空字符串 | "" (空字符串) |
bumber | 任何非零数字值(包括无穷大) | 0和NaN |
undefined | 无 | undefined |
null | 无 | null |
Object | 任何对象 | 无 |
number 类型
number类型用来表示整型和浮点数字,还有一种特殊的数值(NaN-not a number,这个数值用于表示一个本来要返回数值的操作数未返回数值得情况-防止抛出错误)。
比如在其它语言中数值÷0都会导致错误,停止运行,但是在JS中。0/0、NaN/0会返回NaN,其它数字/0会返回Infinity,不会报错。
任何涉及与NaN的操作都会返回NaN,JS有一个isNaN()函数,可以判断接收的参数是否为NaN,或者参数转化为数字后是否为NaN
console.log(NaN + 1); //NaN,任何涉及到NaN的操作都会返回NaNconsole.log(NaN === NaN); //false,NaN与任何值都不相等,包括NaN本身console.log(isNaN(NaN)); //true,是NaNconsole.log(isNaN(‘10‘)); //false,被转为数字10console.log(isNaN(true)); //false,被转为数字1console.log(isNaN(null)); //false,被转为数字0console.log(isNaN(undefined)); //true,返回NaNconsole.log(isNaN(‘hello‘)); //true,无法转换为数字console.log(0/0);//NaN,0/0返回NaNconsole.log(NaN/0);//NaN,NaN/0返回NaNconsole.log(1/0);//Infinity,其它数字/0返回Infinityconsole.log(‘1‘/0);//Infinity,‘1‘成功转为数字console.log(‘1a‘/0);//NaN,‘1a‘转为数字失败,变为NaNconsole.log(Infinity/0);//Infinity,其它数字/0返回Infinity
注意:Infinity的类型是Number(不是基础数据类型)
有两种方法可以将非number类型的值转换为number类型
- 一种是隐式转换,如进行(*、/)操作时,会自动其余类型的值转为number类型
console.log("1"*2);//12console.log("1"/2);//0.5console.log("1a"/2);//NaN
- 一种是显示转换-调用Number()、parseInt()、parseFloat()方法转换
Number()函数的转换规则如下:(引自参考来源)
- 如果是boolean值,true和false将分别被替换为1和0
- 如果是数字值,只是简单的传入和返回
- 如果是null值,返回0
- 如果是undefined,返回NaN
- 如果是字符串,遵循下列规则:
- 如果字符串中只包含数字,则将其转换为十进制数值,即”1“会变成1,”123“会变成123,而”011“会变成11(前导的0被忽略)
- 如果字符串中包含有效的浮点格式,如”1.1“,则将其转换为对应的浮点数(同样,也会忽略前导0)
- 如果字符串中包含有效的十六进制格式,例如”0xf“,则将其转换为相同大小的十进制整数值
- 如果字符串是空的,则将其转换为0
- 如果字符串中包含除了上述格式之外的字符,则将其转换为NaN
- 如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再依次按照前面的规则转换返回的字符串值。
console.log(Number(‘‘));//0console.log(Number(‘a‘));//NaNconsole.log(Number(true));//1console.log(Number(‘001‘));//1console.log(Number(‘001.1‘));//1.1console.log(Number(‘0xf‘));//15console.log(Number(‘000xf‘));//NaNvar a = {};console.log(Number(a));//NaNa.toString = function(){return 2};console.log(Number(a));//2a.valueOf = function(){return 1};console.log(Number(a));//1
parseInt()常常用于将其它类型值转化为整形。parseInt转换与Number()有区别,具体规则如下
- parseInt(value,radius)有两个参数,第一个参数是需要转换的值,第二个参数是转换进制(该值介于 2 ~ 36 之间。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。),如果不传(或值为0),默认以10为基数(如果value以 “0x” 或 “0X” 开头,将以 16 为基数)
- 注意在第二个参数默认的情况下,如果需要转换的string值以0开头,如‘070‘,有一些环境中,会自动转化为8进制56,有一些环境中会自动转化为10进制70。所以为了统一效果,我们在转换为10进制时,会将第二个参数传10
- parseInt转换示例
console.log(parseInt(‘‘));//NaNconsole.log(parseInt(‘a‘));//NaNconsole.log(parseInt(‘1234blue‘));//1234console.log(parseInt(true));//NaNconsole.log(parseInt(‘070‘));//70,但是有一些环境中会自动转换为8进制56console.log(parseInt(‘070‘,8));//56console.log(parseInt(‘001.1‘));//1console.log(parseInt(‘0xf‘));//15,16进制console.log(parseInt(‘AF‘,16));//175,16进制console.log(parseInt(‘AF‘));//NaNconsole.log(parseInt(‘000xf‘));//0var a = {};console.log(parseInt(a));//NaNa.toString = function(){return 2};console.log(parseInt(a));//2a.valueOf = function(){return 1};console.log(parseInt(a));//2
parseFloat()转换规则基本与parseInt()一致,只有如下不同点
- parseFloat()遇到浮动数据时,浮点有效(但是只有第一个.有效),如"10.1"会被转为10.1;‘10.1.1‘会被转为10.1
- parseFloat()只会默认处理为10进制,而且会忽略字符串前面的0,所以不会有在默认情况下转为8进制的情况
- 示例
console.log(parseFloat(‘1234blue‘));//1234console.log(parseFloat(‘1234blue‘,2));//1234console.log(parseFloat(‘0xA‘));//0console.log(parseFloat(‘10.1‘));//10.1console.log(parseFloat(‘10.1.1‘));//10.1console.log(parseFloat(‘010‘));//10
由于Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是parseInt()函数-需注意最好第二个参数传10,处理浮点数时更常用parseFloat()
另外注意,浮点数直接的计算存在误差,所以两个浮点数无法用"="进行判断
var a=10.2;var b= 10.1;console.log(a - b === 0.1);//falseconsole.log(a - 10.1 === 0.1);//false,实际是0.09999999999999964console.log(a - 0.1 === 10.1);//true
string 类型
string类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由单引号(‘)或双引号(")表示。任何字符串的长度都可以通过访问其length属性取得。
要把一个值转换为一个字符串有三种方式。
- 第一种是使用几乎每个值都有的toString()方法(除去null和undefined没有)
- toString(radius)有一个参数-基数,当需要toString的值为number时,参数可以生效(可以转换为对应进制输出,如10.toString(8)输出为12)
- 第二种是隐式转换,比如字符串+ number(null,undefined,object等),会默认转为字符串(如果后面相加的是object对象,会返回object对象的toString或valueOf值)
- 第三种是通过转换函数String(),String()转换规则如下
- 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果(注意,valueOf()方法没用)
- 如果值是null,则返回"null"
- 如果值是undefined,则返回”undefined“
- 示例
var a = 10;var b = ‘10‘var c = {};console.log(a.toString());//10console.log(a.toString(8));//12console.log(b.toString(8));//10,字符串基数没用console.log(String(c));//[object Object]console.log(c);//[object Object]console.log(c + ‘1‘);//[object Object]1 c.valueOf = function(){return 2};console.log(String(c));//[object Object]console.log(c);//[object Object],valueOf没用console.log(c + ‘1‘);//21,隐式转换时,valueOf起作用了 c.toString = function(){return 2};console.log(String(c));//2console.log(c);//2,toString起作用了 console.log(String(null));//"null",null和undefined可以String()输出console.log(null.toString());//报错,null和undefined不能toString
复杂 类型
复杂 类型即引用型,也就是我们常说的JS对象(包括普通的object型对象和function型对象)
对象其实就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象类型的名称来创建。而创建Object的实例并为其添加属性和(或)方法,就可以创建自定义对象。如
var o = new Object();//创建一个新的自定义对象{}
也就是说,除去基本类型,剩余的就是引用型(包括内置对象,自定义对象等)都是基于Object进行拓展的
Object的每个实例都具有下列属性和方法:
- constructor——保存着用于创建当前对象的函数
- hasOwnProperty(propertyName)——用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty("name"))
- isPrototypeOf(object)——用于检查传入的对象是否是另一个对象的原型
- propertyIsEnumerable(propertyName)——用于检查给定的属性是否能够使用for-in语句来枚举
- toString()——返回对象的字符串表示
- valueOf()——返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。
参考 JS原型和原型链的理解
基本型和引用型的不同
基本型和引用型最大的不同就是两者的存储方式不同,如图:
也就是说,上图中,如果变量1的值变为102,实际中栈内存中的101是不会变的,只是在栈内存中新开辟出一处,用来存放102这个常量。然后将变量1指向102。
而变量2由于栈内存中存放的是指针,实际执行的是堆内存中的数据,所以变量2的值是可以随便改的(堆内存中的数据可以更改)
关于数据类型的一些常见疑问
为什么typeof null === ‘object‘
这个问题有很多人提出过,因为按理说,null作为JS的五大基本数据类型之一,那么typeof null 为和会===object呢?这与ECMAScript的历史原因有关。原因如下:
- JS中的五大基本数据类型,除了null外,其余的类型存放在栈区的都是常量值(如undefined存放的是undefined值,number类型可以存放0,1,2...等等)
- 与其余四种类型相同,null的值也是存放在栈区的,而且也只有一个值null。而恰巧从逻辑上认为这是一个空对象的指针(机器码NULL空指针),所以typeof时会返回object。 (具体原因如下,引自知乎同名回答)
- JS类型值是存在32 BIT 单元里,32位有1-3位表示TYPE TAG,其它位表示真实值
- 而表示object的标记位正好是低三位都是0 (000: object. The data is a reference to an object.)
- 而js 里的null 是机器码NULL空指针, (0x00 is most platforms).所以空指针引用 加上 对象标记还是0,最终体现的类型还是object..
- 这也就是为什么Number(null)===0吧...
- 曾有提案尝试修复typeof === ‘null‘,但是被拒绝了(如在V8引擎中会导致大量问题)
string,String,object,Object,function,Function的关系
请区分Object,Function,String与object,function,string。
- Object,Fucntion,String是JS内置对象(都是引用型),object,function,string是typeof检查类型后的返回值。
- 一般情况下,我们把后面的object,undefined,function等称之为对应值的类型(null用typeof 无法识别的,另外函数对象返回function)。
- 所以到了这一步,应该是所有的引用类型typeof都返回object的。但是在引用类型中,有一个比较特殊的类型"fucntion"。它的出现造成了引用类型中函数对象typeof返回‘function‘。
具体参考: function类型与object类型的区别
- 现在又回到了最初的数据类型划分的时候了
- JS中的基本数据类型有五种:undefined,null,number,boolean,string
- JS中的引用类型中包含两种:object、function(fucntion类型是Function对象typeof检测后的返回值,Function对象是基于Object拓展的)
- JS中有一些内置对象:Object,Function,String。这些对象用typeof返回值都是function。但是new Object()出来的新对象的typeof返回值就是object(除了new Function()返回function以外)
- 所以其实这些类型名称都是不同人自己定义出来的,不要被他们限制。
比如有的人会说JS中有7中类型:5中基本数据类型和object与function(但其实我们这这里就将后面两种以前算成引用型了)
或者用一句话总结更为合适:"JS中有对象,每一个对象都有一个自己的类型"。就好比每一个动物都有属于自己的类型一样(人类,猴子...)。另外基本类型可以认为是一个不会改变的对象(便于理解)
至于为什么基本类型明明不是引用型,却能像引用型一样去使用一些基本数据操作(如toFixed,toString等)。请参考 基本数据类型为什么能够使用toString等操作
关于String类型与string类型的疑问
JavaScript中,基本类型有string,number等等,复杂类型中也拓展有String,Number等等。那么这两者的区别是什么呢?如下图,简单描述了基本类型中的string与复杂类型中的String的区别。
也就是说,string类型都是放在栈内存中的(类似于常量),如果string类型的变量的值改为另一个string,那么栈内存中原有的string并没有变化,只不过是在栈内存中新开辟出一个string,然后改变变量的引用而已
而Strign的型的栈内存中只有指针,指向堆内存的数据。所以如果值进行了改变,是会直接修改堆内存中的数据内容,而栈内存中的指针并不会变。
function类型与object类型的区别
这个一个知识点也是很多人疑惑的地方,明明只有一种复杂对象Object,但为什么一些函数类型的typeof 返回function,其它对象返回 object呢?
- 简单点可以这样理解:Object,Function,String等都是JavaScript内置的函数对象,typeof是获取函数对象的类型,所以返回fucntion。而new Object()、new String()等是构造出一个新的对象,所以typeof返回object。而new Fucntion()构造处理的仍然是一个函数对象,所以返回fucntion
Function是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。
- 关于Object和Function可以这样理解
null是天地之始,然后null生了Object,Object是万物之母。然后Object有一个属性constructor,恰巧可以返回function的值,所以typeof Object为function。然后Function是基于Object派生的。Function.prototype._proto_指向Object.prototype。(Function.constructor也返回function值)
- 如果要深入理解,需要对JS中的原型和原型链有一定了解
参考 JS原型和原型链的理解
- 示例
function a(){}; var b = function(){}; var c = new Function(); var d = new Object(); var e = new String(); var f = new Date(); console.log(typeof a);//function console.log(typeof b);//function console.log(typeof c);//function console.log(typeof Function);//function console.log(typeof Object);//function console.log(typeof d);//object console.log(typeof String);//function console.log(typeof e);//object console.log(typeof Date);//function console.log(typeof f);//object console.log(Object instanceof Function);//true console.log(Function instanceof Object);//true console.log(new Object() instanceof Object);//true console.log(new Object() instanceof Function);//false console.log(new Function() instanceof Function);//true console.log(new Function() instanceof Object);//true function Foo(){}; var foo = new Foo(); console.log(foo instanceof Foo);//true console.log(foo instanceof Function);//false console.log(foo instanceof Object);//true console.log(Foo instanceof Function);//true console.log(Foo instanceof Object);//true
==和===的区别
==和===在JS中都有比较的意思,但是两者有着很大的不同,两者区别如下:
- 对于string,number,boolean等基础简单类型而言,==和===是有区别的
因为不同类型的值比较,==会将比较值转换为同一类型的值后 在看值是否相等。===的话会先判断类型,如果类型不同,结果就是不等。
- 对于引用类型而言,==和===是没有区别的
因为这类值得比较都是“指针地址”比较,不同的值,肯定为false
- 对于基础类型和引用类型比较而言,==和===是有区别的
对于==会将复杂类型转换为基础类型,进行值比较,对于===,由于类型不同,直接为false
- 示例
var a = 1;var b = true;console.log(a == b); //true,转换为同一类型后值相等console.log(a === b); //false,先比较类型不能,直接为falsevar a = { ‘test‘: ‘1‘};var b = { ‘test‘: ‘1‘};console.log(a == b); //false,比较指针地址,不等console.log(a === b); //false,比较指针地址,不等var a = ‘11‘;var b = new String(‘11‘);console.log(a == b); //true,将高级类型String转化为基础类型,值相等console.log(a === b); //false,因为类型不同,直接为false
typeof的作用
JS的变量的值是松散类型的(弱类型),可以保存任何类型的数据,JS内置的typeof可以检查给定变量的数据类型,可能的返回值如下:
- undefined:undefined类型
- boolean:boolean类型
- string:string类型
- number:number类型
- object:null类型或者其它的引用型(object,去除function)
- function:这个值是函数对象,引用类型中的特殊类型(可以认为引用型中除去了function就是object)
console.log(typeof ‘test‘); //‘string‘console.log(typeof 101); //‘number‘console.log(typeof true); //‘boolean‘console.log(typeof undefined); //‘undefined‘console.log(typeof null); //‘object‘console.log(typeof function() {}); //‘function‘console.log(typeof {}); //objectconsole.log(typeof new Date()); //object
instanceof的作用
instanceof用于判断一个变量是否是某个对象的实例,主要是判断某个构造函数的prototype属性是否存在另一个要检查对象的原型链上。
- Instanceof可以判断内置的对象类型(基于Obejct对象拓展的,如Array,Date等)。
- 可以判断自定义对象类型,如下述中的Child和Parent
- 但是不能判断简单类型(因为本质是通过原型来判断,但是简单类型只是一个常量,并不是引用对象)
//识别内置对象 - Array, Date等 console.log([] instanceof Array); //true console.log(new String(‘11‘) instanceof String); //true console.log(‘11‘ instanceof String); //false,因为11是简单类型 //识别自定义对象类型以及父子类型 function Parent(x) { this.x = x; } function Child(x, y) { Parent.call(this, x); this.y = y; } //将Child的原型指向Parent,表明继承关系,此时Child的构造变为了Parent的构造 Child.prototype = new Parent(); //然后将构造函数换为Child自己的 Child.prototype.constructor = Child; console.log(Child.prototype.constructor); //输出构造函数是Child自己的 var person = new Child(1, 2); console.log(person.x + ‘,‘ + person.y); //1,2 console.log(person instanceof Child); //true console.log(person instanceof Parent); //true //不能识别简单类型,因为instanceof后面只能是基于Object对象拓展的类型 console.log(101 instanceof number); //报错,number is not defined
Object.prototype.toString的作用
Object.prototype.toString的存在主要是为了解决typeof和instanceof的不足,比如typeof无法识别内置类型(Array Date等),而instanceof无法识别简单类型。所以才有了这个。
Object.prototype.toString可以识别5种简单类型,以及全部内置类型(Array.Date等一些内置类型),但是无法识别自定义对象类型
/** * @description 通过Object.prototype.toString来判断传入对象类别 * @param {Object} obj */ function type(obj) { //slice的作用,例如本来返回[object number],slice筛选出number return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); } console.log(type(1)); //number console.log(type(‘1‘)); //string console.log(type(true)); //boolean console.log(type(undefined)); //undefined console.log(type(null)); //null console.log(type(new Date())); //date console.log(type([])); //array console.log(type({})); //object function Test(a) { this.a = a; } console.log(type(new Test(‘1‘))); //object,自定义类别只能识别为object
基本数据类型为什么能够使用toString等操作
前面说到JS中有基本类型和引用类型(对象类型、复杂类型各种说法都行)。请注意两者是有本质区别的。
- 基本类型的值进行toString操作时,并不是用自身进行操作的,而是有类似与“装箱”、“拆箱”的操作
var a = 10.1;console.log(a.toFixed(2));//10.10,临时构建了个Number对象进行操作,操作完后销毁了 a.foo = ‘test‘;console.log(a.foo); // undefined,因为a的值并不是一个对象,无法绑定属性
上述代码中,对基本类型的
a
进行a.xx
操作时,会在内部临时创建一个对应的包装类型(比如number类型对应Number类型)的临时对象。并把对基本类型的操作代理到对这个临时对象身上,使得对基本类型的属性访问看起来像对象一样。但是在操作完成后,临时对象就扔掉了,下次再访问时,会重新建立临时对象,当然对之前的临时对象的修改都不会有效了。
JavaScript筑基篇(二)->JavaScript数据类型