JavaScript的数据类型是每一个前端开发者必须要掌握的内容,也是最基础最重要的角色之一,关于JavaScript数据类型你知道多少?
![ddbd2c365b40e71e63bc880d1aab04b7.png](https://i-blog.csdnimg.cn/blog_migrate/c13f5ac11b86e6e80632cc1c323b55e9.png)
![e8df21ac85a5318913e5ffa50c01562a.gif](https://i-blog.csdnimg.cn/blog_migrate/26cfe5c76c5cf7f2bd4e146939a51cd3.gif)
JavaScript一共有7种数据类型:String、Number、Null、Undefined、Boolean、Object、Symbol(ES6新增)。他们可以分为两大类:值类型和引用类型。在文章最后会讲解他们的区别。
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
undefined
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
- 已用一个已声明但未初始化的变量
- 一个没有返回值的函数
- 执行void表达式
- 引用未定义的对象属性
- 全局常量window.undefined 或 undefined
var a;var obj = { a: 'A',}var fn = function (){var b = 2;}console.log(a); // undefinedconsole.log(void 0); // undefinedconsole.log(obj.b); // undefinedconsole.log(fn()); // undefinedwindow.undefined // undefined
如何判断一个变量的值是否为undefined呢?
前面提到了undefined就是未找到的意思,那现在有下面几种方案看是否可行:
(1)逻辑取非转为布尔值值判断
var a;if (!a) {// 具体操作}
(2)直接与undefined进行比较
var a;if (a === undefined) {// 具体操作}
(3)判断数据类型是否等于undefined字符串
var a;if (typeOf a === "undefined") {// 具体操作}
上面3种方案只有第三种是可行的,第一种,若a的值为null、空字符串、数字0时取非后值都为true。第二种使用三个“=”是可以的,但是如果a未定义时会抛出错误。所以建议使用第三种方案。
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
Null
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
null == undefined // truenull === undefined // false
null为JavaScript的保留字关键字,undefined是一个常量,因此null不能作为变量名。
var undefined = 1 // undefinedvar null = 1 //Uncaught SyntaxError: Unexpected token 'null'
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
Boolean
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
boolean只有两个值:true和false,常用于判断语句中。例如下面这个例子,在一个数组中筛选出偶数:
var arr = [2, 4, 5, 1, 6, 7];var odd = [];arr.map((item) => { if (item % 2 === 0) { odd.push(item); }})console.log(odd); // [2, 4, 6]
他们之间可以通过取非相互转换:
var f = true;console.log(f); // trueconsole.log(!f); // falseconsole.log(!!f); // true
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
Number
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
- NaN(Not a Number)通常在计算失败的时候会得到该值。要判断一个变量是否为 NaN,则可以通过 Number.isNaN 函数进行判断。
- Infinity 是无穷大,加上负号 “-” 会变成无穷小,在某些场景下比较有用,比如通过数值来表示权重或者优先级,Infinity 可以表示最高优先级或最大权重。
在JavaScript中对number进行一些计算时可能会得到一些非期望的结果,如0.1 + 0.3
console.log(0.1 + 0.3); // 0.30000000000000004
这是因为JavaScript在计算时,JavaScript引擎会先将十进制的数转为二进制,计算完成后再转为二进制。在进制转换过程中,如果小数是循环的话,那么就会出现误差。再比如把3开方后在平方后的结果也不等于3。
Math.pow(Math.pow(3, 1/2), 2) // 2.9999999999999996
要想解决精度误差问题,有两种方案:
(1)计算前先把小数转为整数,计算后再转为整数
console.log((0.1 * 10 + 0.3 * 10) / 10); // 0.3
(2)使用precision进行四舍五入,以定点表示法或指数表示法表示的一个数值对象的字符串表示,四舍五入到 precision
参数指定的显示数字位数。
parseFloat((0.1 + 0.2).toPrecision(12)) // 0.3
在涉及到number计算的场景下,我们常会用到一些方法,对这些方法熟记于心才可以在不同的场景下灵活的运用。
// 取绝对值Math.abs(x);// 返回一个数的立方根Math.cbrt(x)// 一个数向上取整后的值Math.ceil(x);// 一个数向下取整后的值Math.floor(x)// 零到多个数值中最大值Math.max([x[, y[, …]]])// 零到多个数值中最小值 Math.min([x[, y[, …]]])// 一个 0 到 1 之间的伪随机数Math.random()// 四舍五入后的整数 Math.round(x)// 一个数的符号,得知一个数是正数、负数还是 Math.sign(x)// ......
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
String
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
字符串类型,最常见的数据类型之一。我们可以通过.length获取字符串的长度,也可以通过substr方法从一段字符串中解决我们所需要的部分,还可以使用split方法把字符串转为一个数组。
var str = 'abcdefg'console.log(str.length) // 8console.log(str.substr(2, 5)) // bcdefconsole.log(str.split('')) // ["a", "", "b", "c", "d", "e", "f", "g"]
![eb4579910f9870da3fc917e65bb81241.png](https://i-blog.csdnimg.cn/blog_migrate/1bfb0bf92f71b90582b604e74feae03e.png)
Symbol
![9f1d72160f4087d64c3536d5bfb40cab.png](https://i-blog.csdnimg.cn/blog_migrate/95ab712bd883dd425b99be43fbf2c77e.png)
![2fe1e46e010805bd2061cee1fb701aa1.png](https://i-blog.csdnimg.cn/blog_migrate/8f62998113ba128b696e696b5116b944.png)
Symbol 是 ES6 中引入的新数据类型,它表示一个唯一的常量,通过 Symbol 函数来创建对应的数据类型,创建时可以添加变量描述,该变量描述在传入时会被强行转换成字符串进行存储。
var a = Symbol('1')var b = Symbol(1)a.description === b.description // truevar c = Symbol({id: 1})c.description // [object Object]var _a = Symbol('1')_a == a // false
Symbol 的最大特点就是它所定义的值永远都是唯一的,常用与的场景有,避免常量值重复、避免对象属性重复等。
看下面这个例子,有一个函数,对所传入的对象要添加一个name属性:
function fn(person) { person.name = 'adc'; console.log(person); // { name: 'abc' }}fn({name: 'aaa',age: 22});
可以看到当传入的对象含有name属性时,会把原有的name覆盖掉,这并不是我们想要的结果。为了避免对象属性的重复覆盖,可以使用symbol很好的解决这个问题:
function fn(person) { const name = Symbol('name'); person[name ]= 'adc';console.log(person); // { name: 'aaa', [Symbol(name)]: 'abc' }}fn({name: 'aaa',age: 22});
数据类型的检测
![1e484164e3b5344c6370ab47e156678f.png](https://i-blog.csdnimg.cn/blog_migrate/5c123e87ad375a5c41947343983694d9.png)
![723eae802e223e0ed0115a87f50ab4c6.png](https://i-blog.csdnimg.cn/blog_migrate/d3322f7f09d49002799e68281a8a6e8d.png)
我们已经知道了JavaScript有这些数据类型,在开发中,往往需要判断某个变量的类型来进行一些后续的操作,有哪些方法可以检测数据的数据类型呢?
(1)typeof
const a = 1;const b = '1';const c = null;const d = undefined;const e = false;const f = [1, 2, 3];const g = { name: 'abc' };const h = new Date();const i = new RegExp();console.log(typeof a); // numberconsole.log(typeof b); // stringconsole.log(typeof c); // objectconsole.log(typeof d); // undefinedconsole.log(typeof e); // booleanconsole.log(typeof f); // objectconsole.log(typeof g); // objectconsole.log(typeof h); // objectconsole.log(typeof i); // symbol
上面的代码中,我们定义了不同类型的常量,打印出了通过typeof方法所检测的值,可以看到对于number、string、undefined、boolean和symbol数据类型是可以检测数来的,但是对于null、Array、对象等类型的结果都是object。也就是说typeof只可以检测出值类型的数据,无法分辨出引用类型的数据。
(2)instanceof
instanceof用来判断A是否是B的实例,如果是则返回true,不是则返回false。console.log([] instanceof Array); //trueconsole.log({} instanceof Object); //trueconsole.log(new Date() instanceof Date); //trueconsole.log(new Date() instanceof Object); //trueconsole.log(new RegExp() instanceof Object); //true
但是这种方式还是比较有局限性,instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型,而且 instanceof 对于引用类型的支持很好,但他是无法对原始类型进行判断,所以一般都是在 typeof 判断为 object 时才使用 instanceof。
(3)constructor
该方法可以打印出目标实例所属的类,函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor引用会丢失,constructor 会默认为 Object。
console.log([].constructor == Array); //trueconsole.log({}.constructor == Object); //trueconsole.log("1".constructor == String); //trueconsole.log((1).constructor == Number); //trueconsole.log(true.constructor == Boolean); //trueconsole.log(Symbol().constructor == Symbol); //trueconsole.log(new Date().constructor == Date); //trueconsole.log(new RegExp().constructor == RegExp); //true
但这种方式无法检测null和undefined,他们是无效的对象,因此不会存在
constructor
。
(4)Object.prototype.toString.call()
toString是Object的一个方法,在Object上调用该方法会返回[object Object],其中第二个Object就是他的数据类型。但是对于其他类型需要使用call / apply才可以返回正确的数据类型。
const a = 1;const b = '1';const c = null;const d = undefined;const e = false;const f = [1, 2, 3];const g = { name: 'abc' };const h = new Date();const i = new RegExp();const j = Symbol('name');const k = function(){};console.log(Object.prototype.toString.call(a)); //[object Number]console.log(Object.prototype.toString.call(b)); //[object String]console.log(Object.prototype.toString.call(c)); //[object Null]console.log(Object.prototype.toString.call(d)); //[object Undefined]console.log(Object.prototype.toString.call(e)); //[object Boolean]console.log(Object.prototype.toString.call(f)); //[object Array]console.log(Object.prototype.toString.call(g)); //[object Object]console.log(Object.prototype.toString.call(h)); //[object Date]console.log(Object.prototype.toString.call(i)); //[object RegExp]console.log(Object.prototype.toString.call(j)); //[object Symbol]console.log(Object.prototype.toString.call(k)); //[object Function]
类型的相互转换
![1e484164e3b5344c6370ab47e156678f.png](https://i-blog.csdnimg.cn/blog_migrate/5c123e87ad375a5c41947343983694d9.png)
![723eae802e223e0ed0115a87f50ab4c6.png](https://i-blog.csdnimg.cn/blog_migrate/d3322f7f09d49002799e68281a8a6e8d.png)
(1)强制转换
所谓强制转换,就是我们通过一些方法主动的去转换数据类型。- 转为String:.toString()、String()、JSON.stringify
- 转为Number:Number()、parseInt()
- 转为Boolean:Boolean()
- 转为对象:JSON.parse()
所谓隐式转换,是在我们进行一些操作时,针对于不同的情况,JavaScript会自动的对一些数据的数据类型进行装换。常发生于一下情况:
- 运算相关的操作符包括 +、-、+=、++、* 、/、%、<
- 数据比较相关的操作符包括 >、=、===。
- 逻辑判断相关的操作符包括 &&、!、||、三目运算符。
下面列举了一下例子,这里就不再一一分讲解了:
console.log(1 + 1 + '1'); //21console.log(1 + '1' + 1); //111console.log(1 + true); //2console.log(1 + false); //1console.log(1 + null); //1console.log(1 + undefined); //NaNconsole.log(null + undefined); //NaNconsole.log('a' + undefined); //aundefinedconsole.log('a' + null); //anullconsole.log(undefined + 'b'); //undefinedbconsole.log(false == 0); //trueconsole.log(false == null); //falseconsole.log(false == undefined); //falseconsole.log(!null); //trueconsole.log(!undefined); //trueconsole.log(!12); //falseconsole.log(!'false'); //false
值类型与引用类型
![1e484164e3b5344c6370ab47e156678f.png](https://i-blog.csdnimg.cn/blog_migrate/5c123e87ad375a5c41947343983694d9.png)
![723eae802e223e0ed0115a87f50ab4c6.png](https://i-blog.csdnimg.cn/blog_migrate/d3322f7f09d49002799e68281a8a6e8d.png)
在上面提到了很多次值类型和引用类型,那么他们究竟有什么区别?
var a = 1;var b = {name: 'abc',age: 22};var c = a;var d = b;console.log(c); // 1console.log(d); // { name: 'abc', age: 22 }a = 2;b.name = 'def';console.log(c); // 1console.log(d); // { name: 'def', age: 22 }
在上面的例子中,声明了两个变量a和b,a为Number类型,b为Object类型。然后把a赋值给变量c,b赋值给变量d。对a和b的值都进行改变,然后打印出c和d,得到的结果是:c的值还是原来的值,而d的值是改变后的b的值。明明赋值在改变值的前面进行的,为什么会出现这种情况呢?
这与不同数据类型的存储方式不同有关,值类型是以栈的方式存储的,而引用类型是存储在堆中的。当值类型赋值给其他变量时,会在栈中生成一个新的空间,其值与原值相同,但是他们之间是相互独立的,没有任何联系。
保存与复制的是指向对象的一个指针,当堆中的值改变后,指向该堆的变量值都会一起改变。
接着上面的那个例子,改变d的age属性,可以看到b的age也发生了改变。
d.age = 18;console.log(b); // { name: 'def', age: 18 }
浅拷贝与深拷贝
![1e484164e3b5344c6370ab47e156678f.png](https://i-blog.csdnimg.cn/blog_migrate/5c123e87ad375a5c41947343983694d9.png)
![723eae802e223e0ed0115a87f50ab4c6.png](https://i-blog.csdnimg.cn/blog_migrate/d3322f7f09d49002799e68281a8a6e8d.png)
- 由于引用类型在赋值时只传递指针,这种拷贝方式称为浅拷贝。
- 而创建一个新的与之相同的引用类型数据的过程称之为深拷贝。
(1)通过把一个对象转为一个字符串后再转为对象进行复制的方式
var obj = {name: 'a',path: 'a',}var copy = JSON.parse(JSON.stringify(obj));obj.path = 'b';console.log(copy); // { name: 'a', path: 'a' }
这种方式不能undefined 、function、RegExp 等类型
(2) 通过Object.assign(target, source)创建一个新的对象
var obj = {a: 1,b: 2,c: 3}var copy = Object.assign({}, obj);copy.b = 5;console.log(obj.b); // 2console.log(copy.b); // 5
这种方式看起来也可以,但是它对只有一层的数据有效,如下面这种大于一层的结构就不行了:
var obj2 = {a: 1,b: 2,c: ['A', 'B', 'C']}var copy2 = Object.assign({}, obj2);copy2.c[0] = 'D';console.log(obj2.c); // [ 'D', 'B', 'C' ]console.log(copy2.c); // [ 'D', 'B', 'C' ]
(3)递归拷贝
对于大于一层的情况,可以使用递归的方法逐层拷贝。这里要注意一点,使用typeof判断null的类型时返回的也是'object'
function deepClone(target) { let result; if (typeof target === 'object') {if (Array.isArray(target)) { result = []; for (let i in target) { result.push(deepClone(target[i])) } } else if(target===null) { result = null; } else if(target.constructor===RegExp){ result = target; }else { result = {};for (let i in target) { result[i] = deepClone(target[i]); } } } else { result = target; }return result;}
长按关注
前端筱园
![5650266ce1cebafcf32ea963bf5e4d2e.png](https://i-blog.csdnimg.cn/blog_migrate/9e1a7db61eaa14ebd335fa84c3e7ef84.jpeg)
![ecf05b5bad00f9c57f2f397522bfa33b.png](https://i-blog.csdnimg.cn/blog_migrate/ba62171eb0d7ad572538930c0d317b0d.png)
![ac14052966f7072d8477d1e4d8acfe07.gif](https://i-blog.csdnimg.cn/blog_migrate/49e7f5425406cfc4ce88b76f7128715c.gif)