JS学习篇(一)—— 数据类型篇
JS的有八种数据类型
七种基本类型:
- undefined
- null
- Boolean
- number
- string
- symbol
- bigint
一种引用类型
七种基本类型
1):undefined
定义:通俗的讲就是未定义,当数据被声明但是没有被初始化的时候就会默认为undefined
值:只有一个值,就是undefined
检测: typeof 返回undefined,也就是undefined类型
eg
:
var person;
console.log(person); //undefined
typeof(person) //undefined
但是要注意的是,undefined一般是变量被声明但是没有被初始化的时候默认赋的值。但是如果某个变量没有被声明就直接调用的话就会报错
。
//var age;
console.log(age); //会报错,因为age没有被声明。
但是这个时候用typeof检测还是会返回undefined
typeof(age) //undefined
2):null
定义:表示一个空对象指针
值:只有一个值,就是null
检测: typeof 返回object,但是它是null类型
!!!
返回object的原因:
历史遗留BUG,曾经的计算机是32位的计算机,存储性能各个方面和现在64位的都有差距,处于这个考虑,使用低位存储变量,然后对象的存储是000开头的。而null是全0,所以就误被解析成为object。
作用:经常用于做判断
if ( 变量!==null ){ 如果这个变量为空执行什么操作 } else { 否则执行另一个操作 }
eg:
var box=null
typeof(box) //object
因为undefined是null派生的,所以undefined==null
,但是不全等也就是undefined !== null ,因为类型不一样。
区别:
1:定义不一样:声明一个变量但是没有初始化的时候默认为undefined,但是null一般表示空指针,也就是尚未存在的对象。
2:null转化为数值的时候是0,但是undefined转化为数值的时候是NAN
3):Boolean
定义:布尔值
值:两个字面量值,分别是true和false
检测: typeof 返回Boolean
注意:true和false是区分大小写的
,也就是说这样写True,或者是False是无用的,都不是布尔值
类型转化
数据类型 |
值 |
string |
空字符串转化为false,剩余非空字符串转化为true |
number |
0和NaN转化为false,其余任何非0数字值转化为true |
undefined |
undefined转化为false |
null |
null转化为false |
object |
任何对象都被转化为true(注意null值) |
4):number
定义:number类型,用来表示整数和浮点类型。
值:数字值和NaN
检测: typeof 返回Namber
注意:NaN是一个特殊的值,not a Namber不是一个数值,也就是非数值。任何涉及NaN的操作返回的都是NaN,并且NaN不等于任何值,包括他自己
,一般使用isNaN()
函数判断是否是“不是一个数值”。就是它接收一个数值就会尝试将这个值转化成为数值,如果不能转化的话就会返回true,否则返回false。
浮点类型:
- 数值中必须包含一个小数点,小数点后面至少有一个数字。
- 保存浮点数需要占用的内存空间是保存整数类型的二倍,所以JS运行机制会不失时机的将其转化为整型。比如1.0就会被转化成为1
- 对于极大值以及极小值可以使用e的表示法进行表示。
-
0.1加0.2为什么不等于0.3
???这是一个很经典的问题
然后怀揣着好奇心,我去控制台试验了一波,然后打印出如下效果
参考完https://segmentfault.com/a/1190000012175422才得以解释这个问题。
在JS中浮点数值的最高精度是17位小数,所以当达到最高精度的时候后面的位数都将被直接舍弃
。在进行运算的时候,计算机会先将他们转化成为对应的二进制,然后再进行计算。也就意味着0.1和0.2要先被转化为二进制,所以转化后在计算机内部他们并不是完整的0.1和0.2,而是有舍入误差的数,这个时候已经出现了精度缺失,再进行相加转化为对应的十进制,也就是我们看到的0.30000000000000004
。
看懂了0.1加0.2之后,我又有疑问了:按照这个思路来讲的话,那么0.1加0.3在转化的过程中也被舍弃掉了,为什么0.1+0.3所得的结果还是0.4呢?
是因为0.4和相加之后最后的那个结果是最接近的,他比任何其他的浮点数值都接近,所以在有的语言中直接显示0.4,而非最真实的结果
所以在编码的过程中,我们要注意不能做如下的操作:
if(a+b == 0.3){
//如果a,b是0.1或者是0.2的话,就会导致这个的测试不通过
}
那么如何进行浮点数值的运算呢?
使用bignumber.js,他是用于任意精度十进制和非十进制算术的JavaScript库
安装:npm install bignumber.js
仓库:https://github.com/MikeMcl/bignumber.js
eg:
0.1+0.2
x = BigNumber(0.1) //
y = x.plus(0.2) //这个时候就会输出0.3
数值范围:
|
表示 |
最大值 |
Number.MAX_VALUE (1.7976931348623157e+308),如果超过这个值,就会自动被定义为正无穷infinity
|
最小值 |
Number.MIN_VALUE (5e-324),如果小于这个值,会被转化为负无穷-infinity
|
使用isFinite()
函数检验某个数字值是否在这最大值和最小值之间,如果在之间返回true。否则返回false
数值转换:
JS提供了三个函数,可以将非数值转化为数值:number(),parseInt(),parseFloat()
number():转型函数,可以用于任何数据类型转换为字符类型
类型 |
转换规则 |
Boolean |
true转换为1,false转换为0 |
数字类型 |
简单的传入和传出,NaN返回NaN |
undefined |
返回NaN |
null |
返回0 |
字符串 |
若只是包含数字,准换为相应的十进制数字即可;如果出现十六进制格式,将其转化为对应的十进制;如果里面有有效的浮点格式,将其转化为对应的浮点数值;如果字符串为空,则返回0,;其余全部转化成为0 |
对象 |
会先调用对象自身的valueOf()的方法,然后再调用上面相应的转换规则 |
如果字符串中含有非数字的情况,就不能使用Namber()方法来进行转换,使用下面两种方法
parseInt()
转换规则:
1:会忽略字符串前面的空格,直到找到第一个非空格的字符
2:如果第一个字符不是数字字符或者是负号,就会返回NaN
3:若是数字字符,那么会继续向下解析,直到解析完成或者碰到非数字字符
提供第二个参数,也就是转换时候的进制数
parseInt("12hahaha") //12
parseInt(" ") //NaN
parseInt("22.5") //22 因为小数点不是数字字符,所以遇到解析就停止了
parseInt("0xAF",16) //175
parseInt("0xAF",16) //175 这样写告诉计算机将16进制的0xAF转化为十进制,此时可以省略0x,其他进制也同样可以省略
//省略写法:
parseInt("AF",16) //175
典型题目:
为什么 [“1”, “2”, “3”].map(parseInt) 返回 [1,NaN,NaN]?
对于这段代码实际上执行的是
['1','2','3'].map((item,index)=>{
return parseInt(item,index)
})
所以查分来解释就是
['1','2','3'].map(parseInt)即
parseInt('1',0);index为 0,parseInt() 会根据十进制来解析,所以结果为 1;
parseInt('2',1);index为 1,超出区间范围,所以结果为 NaN;
parseInt('3',2);index 为 2,用2进制来解析,应以 0 和 1 开头,所以结果为 NaN。
parseFloat()
转换规则:
1:从最开始解析,直到解析完成或者是碰到第一个无效的浮点数字字符为止
2:十六进制始终会被解析为0
parseFloat("12hahaha") //12
parseFloat("0xAF") //0
parseFloat("22.5") //22.5
parseFloat("22.5.5") //22.5 只有第一个小数点是有效的
parseFloat("022.5.5") //22.5 忽略前导的0
5):string
定义:字符序列,可以由双引号表示,也可以由单引号表示
检测: typeof 返回string
转换为字符串:
toString()方法
:可以将其他类型的值转换为字符串
注意:1:可以转换数值,布尔值,对象,字符串值,但是无法转换undefined和null
2:转换数值的时候可以提供一个参数,表示对应的进制
10.toString() //"10"
10.toString(2) //"1010"
10.toString(8) //"12"
String()方法
:可以将其他类型的值转换为字符串
转换规则:
1:如果这个值有toString()方法,那么调用该方法并返回相应结果
2:如果是null,返回 “null”
3:如果是undefined,返回“undefined”
除此之外,还有一种方式可以转换为字符串,那就是加法运算
加法规则:
1:如果有一个操作数是NaN,则返回NaN
2:infinity+infinity = infinity ,-infinity+(-infinity)=-infinity,-infinity+infinity = NaN
3:0 + 0 =0; -0 + -0 =-0 ; +0 + -0 =+0(正负0是不相等的,但是 -0 == +0 会返回true
,如果要判断的话可以这样判断1/0 //infinity ;1/-0 //-infinity )
4:如果有一个是字符串:两个都是,则拼接;只有一个,则另一个转换字符串之后再拼接
5:对象,数值,布尔值调用tostring方法转字符串,然后再用字符串加法
undefined和null调用string之后在拼接
6):symbol
ES6引进的原始数据类型
定义:表示独一无二的值,最大的用法就是用来表示对象独一无二的值
检测: typeof 返回symbol
不能使用new命令,会报错,因为symbol是原始类型,不是对象,不能添加属性
let sy = Symbol("KK"); //可以接受一个字符串作为参数,也可以不加
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false
作为对象的属性名,由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名
注意:Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 mySymbol属性,而不是 Symbol 值 mySymbol属性
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = { [mySymbol]: 123 };
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
//通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
Symbol值作为属性名时,该属性还是公开属性,不是私有属性。Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名
。 Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。
var obj = {};
var a = Symbol('a');
var b = Symbol.for('b');
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols // [Symbol(a), Symbol(b)]
方法:
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
7):bigint
定义: BigInt
是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时
。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库
一种引用类型
object
定义:通过执行new操作符后跟要创建的对象类型名称来创建。
检测: typeof 返回object,一般检测对象类型,但是对象他有(包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function
)这么多类型,如果想要正确返回是那种对象类型,一般使用instanceof检测对象
Object 的每个实例都具有下列属性和方法:
-
constructor
:保存着用于创建当前对象的函数
-
hasOwnProperty(propertyName)
:用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例
如:o.hasOwnProperty("name"))
。
-
isPrototypeOf(object)
:用于检查传入的对象是否是传入对象的原型
-
propertyIsEnumerable(propertyName)
:用于检查给定的属性是否能够使用 for-in 语句来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符 串形式指定。
-
toLocaleString()
:返回对象的字符串表示,该字符串与执行环境的地区对应。 toString():返回对象的字符串表示。
-
valueOf()
:返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值 相同。
详细见之后的对象篇
基本类型和引用类型的区别
基本数据类型
:按值访问,可以操作保存在变量中的值。靠传值赋值的,每赋值一次,内存中就新开出一块地方来储存这个数据,保存在栈中。
引用数据类型
:内存中只有唯一的一块地方来储存这个数据,以后的所有赋值都是传址赋值。也就是说,它只是发放一些“廉价的内存指针”,来指向内存中的这个数据。对象是指内存中的可以被 标识符引用的一块区域
区别:
1:基本类型的值没有属性和方法,而引用类型的值有
2:在复制值的时候,当一个变量的值复制给另一个变量,然后改变其中一个变量,基本类型的值没有影响,而引用类型的值则会因为另一个的变化而改变,因为引用类型的值,当一个赋值给另一个的时候,他们的指针指向的是同一个对象,所以改变其中一个另一个也会改变
基本类型的值 引用类型的值
var num1=5; var obj1=new object()
var num2=num1; var obj2=obj1;
num1=3; obj1.name="zhang";
num2//5 obj2//zhang
图解:基本类型的值(保存在栈中):
对于基本类型值,复制变量值,会生成一个新的值,这两个是完全独立的。你可以对它们进行任何操作而不会互相影响
引用类型的值(保存在堆中)
复制引用类型值,复制的其实是引用,变量obj1和obj2引用的都是同一个对象,所以你对任一变量进行操作,另一个变量的值也会变化
3:检测,基本类型的值一般用typeof检测出来,而引用类型的值一般用instanceof判断
4:基本类型的赋值是简单赋值,赋值的时候会在变量对象上面创建一个新的值,然后把该值复制到为新变量分配的位置上去,引用类型的赋值是对象引用
如何判断数据类型
typeof
:在判断除Object类型的对象时比较方便
typeof返回的类型都是字符串形式,能判断的类型有number,string,object,function,undefiend
alert(typeof "helloworld") ------------------>"string"
alert(typeof 123) ------------------>"number"
alert(typeof [1,2,3]) ------------------>"object"
alert(typeof new Function()) ------------------>"function"
alert(typeof new Date()) ------------------>"object"
alert(typeof new RegExp()) ------------------>"object"
alert(typeof Symbol()) ------------------>"symbol"
alert(typeof true) ------------------>"true"
alert(typeof null) ------------------>"object"
alert(typeof undefined) ------------------>"undefined"
alert(typeof 'undefined') ------------------>"string"
2.instanceof
:判断已知对象的引用类型
如果已经知道一个数据是对象类型,那么可以用instanceof来判断它是Array,Date,Function,String,Number,Object。
instanceof不能判断null和undefined.
[1,2,3] instanceof Array -------->true
new Date() instanceof Date -------->true
new Function() instanceof Function -------->true
new Function() instanceof function -------->false
null instanceof Object -------->false
这里还会经常问道能不能手动实现一下instanceof
function instanceof(left, right) {
// 获得类型的原型
let prototype = right.prototype
// 获得对象的原型
left = left.__proto__
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
3.constructor
:
construstor能判断number,string,boolean,array,object,date,function,不能判断null和undefined
console.log(d.constructor === Array) //true
console.log(e.constructor === Date) //true
console.log(f.constructor === Function) //true
注意constructor在类继承时会出错
例如:
function A(){};
function B(){};
var aObj = new A();
console.log(aObj.constructor === A); //true;
console.log(aObj.constructor === B); //false;
function C(){};
function D(){};
C.prototype = new D(); //C继承自D
var cObj = new C();
console.log(cObj.constructor === C); //false;
console.log(cObj.constructor === D); //true;
而instanceof方法不会出现该问题,对象直接继承和间接继承的都会报true:
console.log(cObj instanceof C); //true
console.log(cObj instanceof D); //true
解决construtor的问题通常是让对象的constructor手动指向自己:
cObj.constructor = C;//将自己的类赋值给对象的constructor属性
console.log(cObj.constructor === C); //true;
console.log(cObj.constructor === D); //false; 基类不会报true了;
4.Object.prototype.toString.call
:能判断所有类型
注意:Object.prototype.toString
这种方法来查找,不能直接使用toString,从原型链的角度讲,所有对象的原型链最终都指向了Object,按照JS变量查找规则,其他对象也是可以直接访问到Object的toString方法的,但是事实上,大部分对象都已经实现了自身的toString方法,这样就可能导致Object的toString被终止查找,所以我们使用call方法来强制执行Object的toString方法。
Object.prototype.toString.call(’’) ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object Window] window是全局对象global的引用
类型转换常见题
1. [] == ![]结果是什么?为什么?
在两个==
的操作符中 左右两边都需要转换为数字然后进行比较。[]转换为数字为0。 ![]
首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true, 因此![]
为false,进而在转换成数字,变为0。 0 == 0
,结果为true
2.==
和===
、以及Object.is
的区别?
1:===
叫做严格相等,是指:左右两边不仅值要相等,类型也要相等,例如'1'===1
的结果是false,因为一边是string,另一边是number。
2:==
的判断规则
Object.is
在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是+0和-0,NaN和NaN
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
源码:
function is(x, y) {
if (x === y) {
//运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
//NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
//两个都是NaN的时候返回true
return x !== x && y !== y;
}
3:对象转基本类型的过程
转化流程:
如果Symbol.toPrimitive()
方法,优先调用再返回
调用valueOf()
,如果转换为原始类型,则返回
调用toString()
,如果转换为原始类型,则返回
如果都没有返回原始类型,会报错
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
1 + a // => 3
'1' + a // => '12'
4. null ===
null? NaN ==
null?NaN ===
NaN?
null ===
null true
NaN ==
null false
NaN ===
NaN false
//NaN 和任何类型都不相等