JavaScript的数据类型
JavaScript的数据类型
简介
JavaScript有六种数据类型:number,string,boolean,object,null,undefined
也可以分为三大类:
- 基本数据类型
- 数值(number):包括整数和小数
- 字符串(string):文本
- 布尔值(boolean):真(true)或假(false)
- 两个特殊值
- null:表示一个空值
- undefined:表示未定义或者不存在
- 引用数据类型(可以看做是一个存放各个值的容器)
- 对象(object):各种值组成的集合。
数组(array)和函数(function)都是对象
判断数据类型之typeof运算符
JavaScript中有三种方法可以确定一个值是什么类型的
- typeof运算符
- instanceof运算符
- Object.prototype.toString方法
typeof运算符返回值类型是一个string类型
1 // typeof运算符判断一个数据的类型 2 // number 3 console.log(typeof 0); 4 // string 5 console.log(typeof '1'); 6 // undefined 7 console.log(typeof undefined); 8 // object 9 console.log(typeof null); 10 // boolean 11 console.log(typeof true); 12 // object 13 console.log(typeof {}); 14 // object,数组是一个特殊的对象,利用instanceof可以区分数组和对象 15 console.log(typeof []); 16 // 创建一个函数,函数名为fn 17 function fn(){} 18 // function 19 console.log(typeof fn);
null和undefined
共同点
- null和undefined都表示'没有',将一个变量设置为null或者undefined,效果是等价的
- null和undefined在if判断语句中都会转换为false,甚至用==号比较时为true
1 // null和undefined在if判断语句中 2 if (!null) { 3 console.log('null转换为boolean为false'); 4 } 5 if (!undefined) { 6 console.log('undefined转换为boolean为false'); 7 } 8 // null == undefined true 9 console.log(null == undefined);
区别
- null表示空值,表示该位置的值现在为空,而undefined表示'未定义'
- null转换为数值时为0,undefined转换为数值时为NaN
1 // null == undefined true 2 console.log(null == undefined); 3 4 // null转换为数值时为0,undefined转换为数值时为NaN 5 // 0 6 console.log(Number(null)); 7 // 2 8 console.log(2 + null); 9 // NaN 10 console.log(Number(undefined)); 11 // NaN 12 console.log(2 + undefined);
以下几种情况的值为undefined
- 只声明未初始化的变量
- 调用对象没赋值的属性
- 调用函数时,该传的参数没有提供
- 没有返回值的函数默认返回undefined
布尔值(boolean)
布尔值只有'真'和'假'这两种状态
==,===,!==,!=,>,<,>=,<=,!(not) 这几种运算符都会返回boolean
如果JavaScript预期某个位置应该是boolean类型,会将该位置上现有的值转换为boolean。以下几种情况会转换为false
- ''(空字符串)
- 0
- null
- undefined
- NaN(Not a number)
- false
1 if (!'') { 2 console.log('空字符串被隐式转换为false'); 3 } 4 if (!0) { 5 console.log('0被隐式转换为false'); 6 } 7 if (!null) { 8 console.log('null被隐式转换为false'); 9 } 10 if (!undefined) { 11 console.log('undefined被隐式转换为false'); 12 } 13 var a = Number('我会被转换为NaN'); 14 // 输出NaN 15 console.log(a); 16 if (!a) { 17 console.log('NaN被隐式转换为false'); 18 }
数值(number)
整数和浮点数
1. JavaScript中所有的数字都是以64位浮点数形式储存的,即使整数也是如此。
如果某些运算涉及到整数时,会把64位浮点数转换为32位整数。
2. JavaScript中的浮点数不是精确地值,所以有时候涉及到浮点数的运算时是不准确的
例如:
1 // 输出0.09999999999999998 2 console.log(0.3 - 0.2); 3 4 // 输出0.30000000000000004 5 console.log(0.1 + 0.2); 6 7 // 输出false 8 console.log((0.3-0.2) === (0.2-0.1));
数值的精度
我们都知道JavaScript的数值是以64位浮点数存储的,所以根据国际标准浮点数的64位二进制从左到右可以分为:
- 第1位:符号位,1表示负数,0表示整数
- 第2到12位(共11位):指数部分,最大的是2的11次方-1(2047),大小范围是0到2047
- 第13位到64位(共52位):小数部分(有效数字),有效数字的第一位默认是1,这个1不保存在64位二进制位里
根据JavaScript的有效数字最长为53个二进制位,得出JavaScript精度最多只能到53个二进制位,也就是说绝对值小于
2的53次方的整数都可以精确表示;绝对值大于2的53次方的整数,精度都无法保持
法则: JavaScript对15位的十进制都可以精确处理
1 // 输出9007199254740992 2 console.log(Math.pow(2,53)); 3 // 输出9007199254740992 4 console.log(Math.pow(2,53))+1; 5 // 输出9007199254740994 6 console.log(Math.pow(2,53))+2; 7 // 输出9007199254740996 8 console.log(Math.pow(2,53))+3;
JavaScript数值的范围
我们都知道了64位浮点数的指数部分的值最大为2047,分一半表示负数,则javascript的表示的数值范围是(21024, 2-1023](不包含前面,包含后面)
- 如果一个数大于等于2的1024次方,javascript无法表示那么大的数,则会发生正向溢出,返回Infinity
- 如果一个数小于等于2的-1075(指数部分1023加上小数部分52),javascript无法表示那么小的数,则会发生负向溢出,这时直接返回0
1 // Infinity 2 console.log(Math.pow(2,1024)); 3 // 0 4 console.log(Math.pow(2,-1075)); 5 // 5e-324 6 console.log(Number.MIN_VALUE); 7 // 1.7976931348623157e+308 8 console.log(Number.MAX_VALUE); 9 // 最小值-9007199254740991 10 console.log(Number.MIN_SAFE_INTEGER); 11 // 最大值9007199254740991 12 console.log(Number.MAX_SAFE_INTEGER);
数值的表示法
以下两种情况javascript会以科学计数法来表示
- 小数点前的数字多于21位
- 小数点后的零多于5位
数值的进制
- 二进制: 0b
- 八进制: 0o
- 十进制
- 十六进制: 0x
特殊的数值
- 正零和负零
- NaN(not a number)
- Infinity和-Infinity
1). javascript中实际上存在两个0,+0和-0,他两是等价的,唯一区别就是在做分母的时候,返回值是不同的
// 输出false console.log((1 / 0) === (1 / -0));
2). NaN(非数字), 主要出现在将字符串解析成数字出错的场合
注意点: NaN不是一个独立的数据类型,它的数据类型仍然属于number,使用typeof可以看得很清楚
// 输出NaN console.log(0 / 0); // 输出NaN console.log(3 - 'x'); // 输出NaN console.log(3 - undefined); // 输出number console.log(typeof NaN);
NaN的运算规则:
- NaN不等于任何值,包括它本身
- 数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立
- NaN在布尔运算时被当做false
- NaN与任何数(包括它自己)的运算,得到的都是NaN
1 // 输出false 2 console.log(NaN === NaN); 3 // 输出false 4 console.log(NaN == NaN); 5 // 输出-1 6 console.log([NaN,2].indexOf(NaN)); 7 // 输出false 8 console.log(Boolean(NaN)); 9 // 输出NaN 10 console.log(NaN + 2); 11 console.log(NaN - 2); 12 console.log(NaN * 2); 13 console.log(NaN / 2);
3). Infinity
Infinity表示'无穷',用来表示两种场景,第一正的值太大或者负的值太小,无法表示,第二任何非0数值除以0,得到Infinity(0/0得到NaN)
Infinity有正负之分,Infinity表示正无穷,-Infinity表示负无穷
Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)
1 // 输出true 2 console.log(Infinity > 10000000000); 3 // 输出true 4 console.log(-Infinity < 10000000000); 5 // 输出false 6 console.log(-Infinity > NaN); 7 // 输出false 8 console.log(Infinity < NaN); 9 // 输出false 10 console.log(-Infinity < NaN); 11 // 输出false 12 console.log(Infinity > NaN);
运算法则:
6种情况
- Infinity的四则运算
- 0乘以Infinity,返回NaN;0除以Infinity,返回0;0加上Infinity,返回Infinity;0减去Infinity,返回Infinity;Infinity除以0,返回Infinity
- Infinity加上Infinity,Infinity乘以Infinity,都返回Infinity
- Infinity减去Infinity,Infinity除以Infinity,都返回NaN
- Infinity与null计算时,null会转成0,等同于与0的计算
- Infinity与undefined计算返回的都是NaN
// 输出 Infinity console.log(Infinity + 5); // 输出 Infinity console.log(Infinity - 5); // 输出 Infinity console.log(Infinity * 5); // 输出 Infinity console.log(Infinity / 5); // 输出 0 console.log(5 / Infinity); // 输出 Infinity console.log(Infinity + 0); // 输出 Infinity console.log(Infinity - 0); // 输出 NaN console.log(Infinity * 0); // 输出 Infinity console.log(Infinity / 0); // 输出 0 console.log(0 / Infinity); // 输出 Infinity console.log(Infinity + Infinity); // 输出 Infinity console.log(Infinity * Infinity); // 输出 NaN console.log(Infinity - Infinity); // 输出 NaN console.log(Infinity / Infinity); // 输出 Infinity console.log(Infinity + null); // 输出 Infinity console.log(Infinity - null); // 输出 NaN console.log(Infinity * null); // 输出 Infinity console.log(Infinity / null); // 输出 0 console.log(null / Infinity); // 输出 Infinity console.log(Infinity + undefined); // 输出 Infinity console.log(Infinity - undefined); // 输出 NaN console.log(Infinity * undefined); // 输出 Infinity console.log(Infinity / undefined); // 输出 0 console.log(undefined / Infinity);
与数值相关的全局方法
- parseInt()
- parseFloat()
- isNaN()
- isInfinity()
1. parseInt(), 用于将字符串中从开始的有效数字部分转换为整数
- 如果字符串头部有空格,空格会被自动去除
- 如果parseInt的参数不是字符串,会先转为字符串再转换
- parseInt转为整数的时候,是一个个字符一次转换的,如果遇到不能转为数字的字符,就不再进行下去,返回转好的部分。
- 如果第一个字符串都不能转换为整数,则parseInt直接返回NaN
- parseInt的返回结果只有两种可能,要么是一个十进制数,要么是NaN
- parseInt还可以接受第二个参数,第二个参数指定第一个参数的进制,范围是(2,36),超出这个范围则parseInt返回NaN;如果第二个参数是0,null,undefined,则parseInt直接忽略
2. parseFloat(),parseFloat和parseInt想似,不同的是parseFloat会转换字符串参数的第一个小数点
- 把字符串从第一个字符开始的有效小数部分转换成浮点数,如遇到不能转换得字符,就不在进行下去,返回转好的部分
- 会自动过滤前后空格
注意: parseInt和parseFloat都会把空字符串转换为NaN
// NaN parseInt(''); parseFloat('');
3. isNaN(); 可以用来判断一个值是否为NaN
- isNaN只对数值有效,如果传入其他值,会先被转换为数值
- 对于对象和数组,isNaN也返回true(特例: 对于空数组和只有一个数值成员的数组,isNaN返回false,原因是这些数组能被Number函数转成数值)
判断NaN更可靠的办法是,利用NaN为唯一不等于自身的特点进行判断
function myIsNaN(value) { return value !== value; }
测试
1 // 输出 true 2 console.log(isNaN('hello')); 3 // 相当于 4 // 输出 true 5 console.log(isNaN(Number('hello'))); 6 7 // true 8 console.log(isNaN({})) 9 // 等同于 10 // true 11 console.log(isNaN(Number({}))) 12 13 // true 14 console.log(isNaN(['xzy'])) 15 // 等同于 16 // true 17 console.log(isNaN(Number(['xzy']))) 18 19 console.log(isNaN([])) // false 20 console.log(isNaN([123])) // false 21 console.log(isNaN(['123'])) // false 22 // 使用isNaN需要注意一点, false 23 console.log(isNaN(['1.']))
4. isFinite() 方法返回一个布尔值,表示某个值是否为正常的数值
除了Infinity,-Infinity,NaN和undefined这几个值返回false,isFinite()对于其他的数值都会返回true
1 // false 2 console.log(isFinite(Infinity)); 3 console.log(isFinite(-Infinity)); 4 console.log(isFinite(NaN)); 5 console.log(isFinite(undefined)); 6 // true 7 console.log(isFinite(null)); 8 console.log(isFinite(0)); 9 console.log(isFinite(-1));
字符串(string)
- 字符串就是零个或多个排在一起的字符
- 转义字符(\n换行,\r回车,\t制表符)
- 反斜杠的三种用法:
- 反斜杠后面紧跟三个八进制数(
000
到377
),代表一个字符('\251'代表版权符号) \x
后面紧跟两个十六进制数(00
到FF
),代表一个字符('\xA9'代表版权符号)\u
后面紧跟四个十六进制数(0000
到FFFF
),代表一个字符('\u00A9'代表版权符号)
- 反斜杠后面紧跟三个八进制数(
- 字符串视为字符数组,可以使用[下标]来访问字符串某个下标的字符
- 字符串的length属性返回字符串的长度,因为字符串的不可变,所以此出行也无法改变
base64转码
base64是一种编码方法,可以将任意值转换成0-9,A-Z,a-z,+和/ 这64个字符组成可打印字符
他的目的不是为了加密,而是为了不出现特殊字符,简化程序的处理
btoa() : 任意值转换为base64编码
atob() : base64编码转换为原来值
但是这两个方法并不适合非ASCII码的字符,要想将非ASCII码转为base64字符,中间必须加一道转码环节
// 非ASCII码转base64 function b64Encode(str) { return btoa(encodeURIComponent(str)); } // base64转非ASCII function b64Decode(str) { return decodeURIComponent(atob(str)); } // "JUU2JTlEJThFJUU0JUI4JTk2JUU4JUIxJUFB" b64Encode('李世豪') // "李世豪" b64Decode('JUU2JTlEJThFJUU0JUI4JTk2JUU4JUIxJUFB');
对象(Object)
对象就是一组键值对集合,一种无序的复合数据集合
对象的创建:
- var obj = { // 键值对集合 }
- var obj = new Object();
- var obj = Object.create([context, args])
读取对象的属性值:
- 使用 . (点)运算符,obj.[键]
- 使用 [] (方括号)运算符,obj[键] (注意点: 如果对象的键不符合标志符,必须加引号;如果键名是数值,会转换为字符串,读取时必须使用[])
对象所有属性的查看: Object.keys()
对象属性的删除: delete obj.属性名; 删除一个不存在的属性delete也会返回true
delete删除属性只有在删除一个不可枚举的属性时才会返回false。delete只能删除本身具有的属性,并不能删除继承过来的
in运算符用于检查对象是否包含某个属性,但是它有个问题,就是不能识别哪些属性是对象自身的,哪些是继承的
所以可以使用obj.hasOwnProperty(key)来判断是否为对象自身的属性
for。。。in。。循环来遍历一个对象的全部属性(包括继承过来的)
- 它遍历的对象是所有可遍历的(enumerable),会跳过不可遍历的属性
- 它不仅遍历自身的属性,还遍历继承过来的属性
- for。。in。。 一般结合hasOwnProperty 来遍历对象自身的属性
with操作符,作用是操作同一个对象的多个属性,语法with(obj){ // 操作对象的属性 } ,弊端: 绑定对象不明确
注意: 如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个
当前作用域的全局变量,正是因为with区块没有改变作用域,它的内部依然是当前作用域
函数
- 函数声明
- function 函数名(参数列表) { // 代码块 }
- var 变量 = function [函数名](参数列表) { // 代码块 }
- 没有函数名的话是将一个匿名函数赋值给变量
- 有函数名的话,此函数名只能在函数内部使用,外部无法使用(作用是函数内部调用自己,方便排错)
- 不会发生函数提升
- new function([参数...], [函数体]); 参数列表中最后一个是函数体,前面的全是函数参数
- 函数重复声明
- 如果函数重复声明的话,由于函数提升,后面的会覆盖掉前面的
- 如果函数名和对象名重复声明的话,对象优先,也就是说对象会覆盖掉同名函数
- 第一等公民
- javascript将函数看作一种值,也就是说它和数值,字符串,布尔值等等都同等地位,它可以赋值给变量和对象的属性,也可以当做参数或者返回值
- 函数名提升
- js引擎将函数视同变量名,所以采用function声明函数时,整个函数会像变量声明一样,被提升到代码头部
-
// 这样子能正常执行 a(); function a(){ console.log('我是a'); }
- 函数属性和方法
- name属性(返回函数的名字)
-
function a(){ } var b = function(){ } var c = function d(){ } // 返回a console.log(a.name); // 返回b console.log(b.name); // 返回d console.log(c.name);
- name的用处就是可以在函数内部获取参数函数的名字
-
- length属性
- 函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数(与实际调用时传入的参数个数无关)
- length属性定义了一种机制,判断定义和调用时的参数个数,来实现面向对象编程之中的方法重载
- toString()方法
- 函数的toString方法会返回一个字符串,内容是函数的源码
函数作用域(作用域指的是变量存在的范围,ES5中只有两种(全局作用域和局部作用域),ES6又增添了块级作用域) - name属性(返回函数的名字)
- 函数内部定义的变量,会在该作用域内覆盖同名全局变量
- 对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明一律都是全局变量
- 与全局作用域一样,函数作用域内部也会产生“变量提升”现象(var声明的变量,function声明的函数),var命令声明的变量,不管在什么位置都会被提升到函数体的头部(赋值部分不会提升)
- 函数执行时所在的作用域是定义时的作用域,而不是调用时所在的作用域;函数体内部声明的变量,作用域绑定函数体内部
- 函数的参数
- 定义:函数运行时有时候需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫做参数
- js函数的参数不是必须的,允许省略参数;
- 函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数
- 传递方式
- 值传递:函数参数是原始值(数值,字符串,布尔值),这意味在函数体内的值是原始值的拷贝,无论怎么修改数值,都不会影响到函数外部
- 引用传递:函数参数是复合类型(数组,对象,其他函数),传递方式是传址,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值
arguments对象(由于js允许函数有不定数目的参数,所以需要一种机制可以在函数体内部读取所有参数,这就是arguments) - arguments对象包含了对象运行时的所有参数,这个对象只有在函数内部才能使用
- 正常模式下,arguments对象可以在运行时修改;严格模式下,修改arguments对象不会影响到实际的函数参数
- 与数组的关系: arguments很像数组,但它是一个对象,可以使用Array.prototype.slice.call(arguments) 来将arguments对象转换为真正数组就可以使用数组的方法了
- callee属性: arguments对象带有一个callee属性,返回它对应的原函数,可以达到调用自身的目的,缺点是在严格模式下是禁用的
- 闭包(闭包就是连接函数内部和函数外部的桥梁,可以在函数外部读取函数内部的变量)
- 闭包可以读取函数内部的变量,可以让这些变量始终保存在内存中即闭包可以使得它的诞生环境一直存在
- 还有一个用处就是闭包可以封装属性和私有方法
-
1 function Person(name){ 2 var _age; 3 function setAge(n) { 4 _age = n; 5 } 6 function getAge(){ 7 return _age; 8 } 9 return { 10 name: name, 11 getAge: getAge, 12 setAge: setAge 13 } 14 } 15 var lsh = new Person('lishihao'); 16 lsh.setAge(12); 17 console.log(lsh.getAge());
-
立即调用函数(IIFE) Immediately-Invoked Function Expression - 注意点: 不要让Function出现在首行,否则以function关键字出现在首行,js引擎会解释为语句;两个IIFE之间需要分号隔开
- 对匿名函数使用IIFE 目的: 一是不必为函数命名,避免污染了全局变量;二是IIFE内部形成单独的作用域,可以封装外部无法读取的变量
- eval没有自己的作用域都在当前作用域内执行,因此有可能会修改当前作用域的值,造成安全问题
- 在严格模式下,虽然eval内部声明的变量不会影响到外部作用域,但是它依然还可以读写当前作用域的值,还是会有安全问题
- eval的本质是在当前作用域之中注入代码。由于安全风险和不利于JavaScript引擎优化执行速度,所以一般不推荐使用,都是用来解析JSON字符串
- eval的别名(只要不是直接调用eval都属于别名调用,eval别名调用的作用域都是全局作用域)
数组
- 定义数组 var arr = [];
- 读取数组元素 arr[下标]
- 下标是从0开始
- 任何数据类型的数据都可以放入数组
数组的本质是一种特殊的对象,typeof运算符会返回数组的类型是Object
Object.keys方法返回数组的所有键名
数组的赋值: 一个值总是先转成字符串,再作为键名进行赋值
- length设置为0
- 重新赋值为[]
in运算符用于检查某个键名是否存在,适用于对象,也适用于数组
for...in.不仅可以遍历对象,也可以遍历数组(还会遍历非数字键,所以不推荐使用for..in..遍历数组)
数组的遍历推荐for循环和while循环
类似数组的对象
定义: 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就是类数组(array-like object);
类数组的length属性不是动态的,并且没有数组的方法;比如arguments对象
数组的slice方法可以将类数组的对象变成真正的数组: var arr = Array.prototype.slice.call(arrayLike);
还可以使用call方法吧数组的方法嫁接到类数组上面,比如: Array.prototype.forEach.call('abc', function(chr){ });
注意:这种方式的效率要不直接使用数组原生的要慢,所以最好还是先将类数组对象转为真正的数组
这是学习js基础知识做的笔记