JS的数据类型
一、思考:浏览器/计算机是如何管理各种数据类型值的?
所有的数据类型值,在计算机底层,都是以2进制进行存储的
常用的进制
- 2进制 : 0 1
- 8进制 : 0~7
- 10进制 : 0~9 {我们平时写代码,写出来的值都是10进制的}
- 16进制 : 0~9 A-F
如果我们写值是以“0x”开头的,浏览器认为其是16进制,默认帮我们转换为10进制进行处理,如果写的值是以“0”开始的,浏览器认为其实8进制,也帮助我们默认转换为10进制,剩余写的值,都是按照10进制算的;但是不论咋样,计算机最后都是按照2进制进行存储!!
JS使用number类型表示数字(整数和浮点数)
- 遵循 IEEE-754 标准 通过64位二进制值来表示一个数字
- 第0位:符号位,0表示正数,1表示负数 S
- 第1位到第11位「11位指数」:储存指数部分 E
- 第12位到第63位「52位尾数」:储存小数部分(即有效数字)F 注:尾数部分在规约形式下第一位默认为1(省略不写)
把其它进制转换为10进制
'10101' 2机制 -> 10进制 : 1 * 2^0 + 0 * 2^1 + 1 * 2^2 + 0 * 2^3 + 1 * 2^4
parseInt('12px',10)
在’12px‘字符串中从左查找,找到所有符合10进制的内容「0~9」 => '12'
把找到的'12'当做10进制,转换为10进制 => 12
parseInt('12px',2)
在`12px`中查找,找到所有符合2进制的「0~1」 => '1'
把`1`看做2进制转化为10进制 =>
其它进制转换为10进制练习题
101 这是一个二进制的
1*2^2 + 0*2^1 + 1*2^0 -> 4+0+1 => 5
241 这是一个八进制
2*8^2 + 4*8^1 + 1*8^0 = 128+32+1 = 161
十进制 (decimal) 转 二进制 (binary)
[number].toString([radix]) : 把一个十进制数字转换为[radix]进制的字符串,如果不写[radix],默认是10
整数
用十进制的值一直除以2,直到商为0结束,把每一次取到的余数,从末尾到开始串起来即可
var n = 42;
console.log(n.toString(2)); //->'101010' 二进制字符串
console.log(n.toString(8)); //->'52' 八进制字符串
浮点数
用十进制浮点数乘以2,每一次去整数部分,把剩下的小数部分继续乘以2...直到乘积是1,没有小数为止... {很多时候会出现无限循环下去} -> 但是计算机存储最长64位,超出的部分干掉了,也就是 计算机底层存储的浮点数的二进制值,不一定是准确的,有可能是省略后的结果...
计算精准度问题:0.1+0.2=?
// var n = 0.1;
// console.log(n.toString(2)); //'0.0001100110011001100110011001100110011001100110011001101'
// console.log(0.1 + 0.2 == 0.3); //false
// console.log(0.1 + 0.2); //0.30000000000000004
// console.log(((0.1 * 10) + (0.2 * 10)) / 10); //0.3 乘以相关系数
二、原始值数据类型
number
- 分类:正数 负数 0 小数 NaN(Not a Number) Infinity(无穷大)负无穷大(-Infinity) JS中最大安全数字「16位」:9007199254740991
- 获取最大值的两种方式
- Number.MAX_SAFE_INTEGER
- Math.pow(2,53)-1
string 字符串类型
在JS中但凡用单引号或者双引号 反引号 包起来的都是字符串
- 反引号是ES6新提供的模板字符串,好处是方便字符串拼接 ${变量/运算程序} 可以换行
- 每一种引号里面不能包自己
- 单双引号里可以加\作为转义字符''这样就会让带有特殊含义的字符变成普通字符,\n把普通字符转化为特殊含义的字符,单引号要加转义字符才能随意换行
- \:换行 编写字符串的时候看起来换行,实际上字符串并没有换行
- \n:换行符 字符串真的会换行
- \t:制表符
- +:字符串拼接
代表字符串的1 :var num="1";
代表数字1:var num2=1;
- 获取字符串的长度:变量.length
var a="hello";
console.log(a.length)==>5
索引
每个字符都对应一个下标,叫做索引,从0开始逐渐递增:0,1,2,3...
- 第一个字符索引:0
- 最后一个字符索引:【变量】.length-1
- 获取当前索引位置对应的字符:【变量】【索引值】
var a="hello"
a[0]===>"h" 获取到第一个字符
a.length-1 获取最后一个字符的索引值
a[a.length-1] 获取最后一个字符
布尔
- 有两个值:一般用于判断
- true:真
- false:假
null空对象指针{没有}
null已知的没有,一般都是先手动赋值为null,后期再给赋予其他值
1.声明变量的时候,不知道这个数据类型是什么,可以先赋值为null,后面可以再给具体的值
var dom=document.getElementById("box");
console.log(dom);
null
2.获取页面中不存在的元素
typeof null;
"object"
3.如果想要清除对象的空间地址时候,我们可以赋值为null
var obj={"name":"li"};
obj=null;
undefined未定义{没有}
undefined未知的没有,一般都是浏览器默认赋予的空
1.只声明,没定义 undefined
var name;
console.log(name);
undefined
2.想要获取对象中的属性值,里面没有对应的属性, undefined
var obj={"name":"lili"};
console.log(obj.name)
lili
console.log(obj.age)
undefined
3.函数里面设置了形参,在调用的时候,没有传实参,在函数中打印形参,undefined
function fn(x,y){console.log(x,y)}
fn();
undefined
4.函数没有return返回值,函数的返回结果就是undefined
function fn(x,y){var total=x+y}
var res=fn(1,2);
console.log(res)
undefined
symbol(唯一值)
console.log(Symbol() == Symbol()); //false
console.log(Symbol('AA') == Symbol('AA')); //false
let n = Symbol('AA');
let m = n;
console.log(n == m); //true
BigInt
- 最大安全数字Number.MAX_SAFE_INTEGER =9007199254740991
- 必须是整数,不能是小数
超过最大安全数字,不能进行准确的计算,所以出现了BigInt这个新的类型
var obj=456n;此时obj就是最大数数据类型,而不是数字数据类型了
console.log(Symbol() == Symbol()); //false
console.log(Symbol('AA') == Symbol('AA')); //false
let n = Symbol('AA');
let m = n;
console.log(n == m); //true
三、对象数据类型
- 对象:把描述同一件事物特征的属性和方法放在一起,实现分组和分类的作用,避免相互冲突
- 零到多组 键值对(属性名和属性值)组成的
- 注意:属性名是不能冲突的,而且属性名的类型是字符串和Symbol,属性值可以是任何类型的
//如果想要获取唯一值类型
var seb=Symbol(123);
var obj = {
// 'name'
name: '东方淼淼',
// 'age'
age: 18,
// 0会默认转换为字符串 '0' 作为属性名
0: 100,
1: true,
// Uncaught SyntaxError: Unexpected token ':' 语法错误
// Symbol(): 100
// 想要设置Symbol类型的属性名,直接写会报语法错误,需要拿[]包起来
[Symbol()]: 100,
[seb]:101//变量承接
};
var name = '1';
console.log(obj['name']); //获取成员是"name"的值 '东方淼淼'
console.log(obj[name]); //当name没有加引号时,就在全局变量里面查找,此时在var name = '1';中,把'1'这个属性值,作为属性名,给obj里面的name,相当于obj['1'] 获取name变量存储的那个值,所对应的成员属性值 true */
// 'name'
// name 变量 代表的是 '1'
console.log(obj.name);//获取成员是"name"的值 '东方淼淼'
console.log(obj['name']);//获取成员是"name"的值 '东方淼淼'
// console.log(obj.0); //Uncaught SyntaxError: missing ) after argument list
console.log(obj['0']);//100
console.log(obj[0]); //浏览器会默认把0转换为'0' 100
console.log(obj[Symbol()]); //undefined 此处是创建了一个新的唯一值,不是获取之前那个唯一值的属性
如果对象中没有这个成员,而我们去获取了,结果不会报错,但是值是undefined */
console.log(obj[seb]);//会找参数seb 这个参数存放的是属性名 101
标准普通对象
标准特殊对象
数组:[1,23]
正则:var reg = /^[+-]?(\d|([0-9]\d+))(\.\d+)?$/;
日期对象:var time = new Date();
错误对象
// var err = new SyntaxError();
// SyntaxError 语法错误
// ReferenceError 引用错误
// TypeError 类型错误
// Error 错误
Math()//数学方法
非标准特殊对象
可执行/调用对象 function 实现了call或者能调用call方法」函数 function
function sum(x, y) {
return x + y;
}
sum(10, 20);
四、数据间的相互转换
把其它类型转换为数字
Number([value]) 按照计算机底层存储的二进制值进行转换的
- 一般用于隐式转换「数学运算、isNaN、==比较...」
- 字符串->数字 空字符串变为0 & 字符串中只要出现非有效数字字符结果就是NaN
- 布尔->数字 true变为1 false变为0
- null->0
- undefined->NaN
- Symbol->报错
- BigInt->正常转换
- 对象遵循 Symbol.toPrimitive/valueOf/toString/Number
Number("123")=>123
Number("")=>0
Number("12px")=>NaN
Number(true)=>1
Number(false)=>0
Number(null)=>0
Number(undefined)=>NaN
Number(99999n)=>99999
Number({"name":"lili"})=>NaN
Number([1,2])=>NaN
Number([1])=>1
Number([])=>0
//唯一值类型会报错
Number(Symbol("你是最帅的"))
parseInt/parseFloat([value])
- 首先会把[value]变为字符串,从字符串左侧第一个字符开始查找,直到找到一个非有效数字字符为止,把找到的结果转换为数字,一个都没找到,结果就是NaN「parseFloat多识别一个小数点」
parseInt("123")=>123
parseInt("")=>NaN
parseInt("12px")=>12
parseInt(true)=>1
parseInt(false)=>0
parseInt(null)=>NaN
parseInt(undefined)=>NaN
parseInt(99999n)=>99999
parseInt({"name":"lili"})=>NaN
parseInt([1,2])=>1
parseInt([1])=>1
parseInt([])=>NaN
//唯一值类型会报错
parseInt(Symbol("你是最帅的"))
parseFloat("12.5")=>12.5
parseInt("12.5px")=>12 遇到非有效数字停止
parseInt("wid12.5px")=>NaN
parsetInt([val],[radix])处理机制
- [val] 必须是一个字符串,如果不是,则也要默认转换为字符串
- [radix]不设置(或者写的是零):正常都是按照10处理的,如果字符串是以”0x“开始的,默认值是16...
- 先在[val]中,找到所有符合[radix]进制的内容(从左到右查找,直到遇到不符合的为止「不论后面是否还有符合进制的,都不在查找了」),然后再把找到的内容看做[radix]进制,转换为十进制
- [radix]范围 2~36,除了0以外(0->10/16),不在这个范围内,结果都是NaN
isNaN()
console.log(NaN == NaN); //false
let n=?;
if(n==NaN){ //这个条件永远不会成立,所以不要这样判断是否为有效数字
console.log('n不是有效数字');
}
// isNaN([value]):验证这个值不是有效数字的命题是否成立,如果真的不是有效数字,则返回true,反之是false
// + isNaN('12px') 如果检测的值不是number类型的,首先需要转换成数字类型「隐式转换:Number」
if(isNaN(n)){
console.log('n不是有效数字');
}
其他数据类型转字符串
规则:原始值转换是直接用引号包起来「bigint会去除n」;除对象转换为字符串是比较特殊的;
toString「排除Object.prototype.toString{检测数据类型}」
字符串/模板字符串拼接「“+”在JS中除了数学运算还有字符串拼接{但是其它运算符一般都是数学运算}」
- “+”左右两边,有一边出现了 字符串或者部分对象 则都是按照字符串拼接处理的
- Symbol.toPrimitive/valueOf/toString
- “+”有一边出现对象:{}+10 / 10+{}
- “+”只有一边:+n / ++n / n++
把原始值其它类型转换为字符串:[value].toString / String([value])
[value].toString 浏览器隐式转换都按照这个来
对象类型值转换为字符串/转换为数字 {V8「webkit」底层渲染机制}
- 第一步:先检测是否存在 Symbol.toPrimitive 这个属性,存在则执行这个方法获取
- 第二步:如果不存在,则继续基于 valueOf 方法获取原始值
- 第三步:如果valueOf获取的不是原始值,则再调用toString转换为字符串
- 第四步:如果是转换为数字,则在Number一下即可
var arr = [10, 20, 30];
console.log(arr + 10); //"10,20,3010"
先看 arr[Symbol.toPrimitive] ->undefined
再看 arr.valueOf() ->[10,20,30] 不是原始值
再看 arr.toString() -> "10,20,30"
字符串拼接处理了
// 需求:我就想变为20数字
arr[Symbol.toPrimitive] = function () {
return 10;
};
console.log(arr + 10); */
var n = new Number(10);
// n[Symbol.toPrimitive] undefined
// n.valueOf() -> 10
console.log(n + 10); //20
把其它类型转换为布尔
- 规则:只有“0、NaN、null、undefined、空字符串”会变为false,其余都是转换为true
- 场景:Boolean([value]) \ !![value] \ ![value] 转换为布尔类型取反
- 条件判断 例如:if(1){} \ A||B A&&B ...
五、补充
“+”:在JS中除了数学运算,还有字符串拼接
“+”有左右两边,其中有一边是一个字符串「或者是个对象」,这样是字符串拼接
- 排除:{}+1 认为左边{}是个代码块,和“+1”是分开的,其实我们运算的“+1”
- ({}+1) 这就是字符串拼接了,因为这是一个整体,或者 var n={}+1,这样也是字符串拼接...
- 1+{} 这一定是字符串拼接了
- 如果出现的是对象,按照 Symbol.toPrimitiv/valueOf/toString/Number 这个顺序来处理,能走到哪一步,就按照哪一步的规则处理
“+”只有一边
- +[value] 把[value]变为数字
- [value]+ 报错
- ++i 先把i累加1「数学运算」,然后再输出或者运算
- i++ 先拿i原来的值进行输出或者运算,完了之后再自己累加1
var i = 3;
console.log(5 + (++i)); // ++i i=4 5+4 9
i = 3;
console.log(5 + (i++)); // 5+3 8 i++ i=4
console.log(i); //4
var i = 2;
console.log(2 + (++i) - (i++) + (i--) - (--i)); //4
// 2+(++i) ++i i=3 2+3 5
// 5-(i++) 5-i 2 i++ i=4
// 2+(i--) 2+i 6 i-- i=3
// 6-(--i) --i i=2 6-2 4
console.log(i); //2
/* // 阿里
// i++ i+=1 i=i+1 是否一样?
var i = '2';
i++; //数学运算中的累加1
console.log(i); //3
i = '2';
i += 1; //=> i=i+1 '2'+1
console.log(i); //'21' */
// console.log(+'10'); //10
// console.log(1 + 1); //2
// console.log(1 + '2'); //'12'
!
- ![value] 先把[value]转换为布尔类型,然后取反
- !![value] 转换为布尔类型 等价于 Boolean([value])
console.log(!1); //1->true 取反 false
console.log(![]); //false
console.log(!0); //true
console.log(!![]);//true
if (1) {
// 隐式转换:先把1转换为布尔类型,判断真假,决定条件是否成立
}
// 大厂面试题
let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr);
// ---
parseInt(27.2, 0)//27
parseInt(0, 1)//NaN
parseInt('0013', 2)//1
parseInt('14px', 3)//1
parseInt(123, 4)//27
*/
比较
- = 赋值
- ==相等 !=不相等
- ===绝对相等 !==绝对不相等
1.A===B 绝对相等 「推荐」
- A/B类型一样,则直接比较值即可
- A/B类型不一样,结果是false,不会进行数据类型转换
null===undefined =>false
null==undefined =>true
2. A==B 值比较
- A/B类型一样,则直接比较值即可
console.log(1 == 1); //true
console.log('zhufeng' == 'peixun'); //false
3. A/B类型不一样,则需要先转换为相同的数据类型,然后再比较
规则:
- 对象==字符串 对象转换为字符串
- null==undefined 结果是true,但是null或者undefined和其它任何值都不相等
- NaN!=NaN NaN和自己本身也不相等,和谁都不相等
- 剩下所有两边类型不一致的情况下,都是转换为数字,然后再比较
console.log(null == undefined); //true
console.log(null == 0); //false
console.log(undefined == ""); //false
console.log([12] == "12"); //[12]->'12' => true
console.log("10" == 10); //Number("10") -> 10 10==10 => true
console.log(false == ""); //0 == 0 => true
console.log(true == 0); //1==0 => false
console.log(true == 2); //1==2 => false
原始值类型和对象类型的区别
原始值类型,结构比较简单,直接存储到栈内存中即可,后续变量都是直接关联和操作这个值的“按值操作”「所以它也叫:值类型/基本数据类型」
var a = 12;
var b = a;
b = 13;
console.log(a); //12
对象类型,结构比较复杂,不能直接存储到栈内存中,需要在堆内存中单独开辟一个空间,来存储对象的键值对,而变量关联和操作的都是:堆内存空间的引用地址{16进制}“按引用地址操作”「所以它也叫:引用数据类型」
var a = {
n: 12
};
var b = a;
b['n'] = 13;
console.log(a.n);