判断数据类型是否为空_关于JavaScript数据类型,你知道多少?

JavaScript的数据类型是每一个前端开发者必须要掌握的内容,也是最基础最重要的角色之一,关于JavaScript数据类型你知道多少?

ddbd2c365b40e71e63bc880d1aab04b7.png e8df21ac85a5318913e5ffa50c01562a.gif

JavaScript一共有7种数据类型:String、Number、Null、Undefined、Boolean、Object、Symbol(ES6新增)。他们可以分为两大类:值类型引用类型。在文章最后会讲解他们的区别。

eb4579910f9870da3fc917e65bb81241.png

undefined

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.png undefined未找到的意思,它的值只有一个,也就是undefined,我们可以通过以下几种方式得到undefined:
  • 已用一个已声明但未初始化的变量
  • 一个没有返回值的函数
  • 执行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

Null

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.png Null与undefined类似,它也只有一个唯一的值,那就是null,表示为空的意思。如果我们将null与undefined作弱比较,可以发现他们是相等的。
null == undefined     // truenull === undefined    // false

null为JavaScript的保留字关键字,undefined是一个常量,因此null不能作为变量名。

var undefined = 1   // undefinedvar null = 1    //Uncaught SyntaxError: Unexpected token 'null'
eb4579910f9870da3fc917e65bb81241.png

Boolean

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.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

Number

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.png number表示数字,在JavaScript中不会区分一个数字具体是一个整数还是一个小数,又或者是一个浮点数。他们都属于number类型。number有两个特殊的值:NaN 和 Infinity。
  • 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

String

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.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

Symbol

9f1d72160f4087d64c3536d5bfb40cab.png 2fe1e46e010805bd2061cee1fb701aa1.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 723eae802e223e0ed0115a87f50ab4c6.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 723eae802e223e0ed0115a87f50ab4c6.png JavaScript 是一种弱类型的语言,相对于其他高级语言有一个特点,那就是在处理不同数据类型运算或逻辑操作时会强制转换成同一数据类型。如果我们处理不好转换问题,在开发过程中可能会引发很多bug。

(1)强制转换

所谓强制转换,就是我们通过一些方法主动的去转换数据类型。
  • 转为String:.toString()、String()、JSON.stringify
  • 转为Number:Number()、parseInt()
  • 转为Boolean:Boolean()
  • 转为对象:JSON.parse()
(2)隐式转换

所谓隐式转换,是在我们进行一些操作时,针对于不同的情况,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 723eae802e223e0ed0115a87f50ab4c6.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的值。明明赋值在改变值的前面进行的,为什么会出现这种情况呢? 这与不同数据类型的存储方式不同有关,值类型是以栈的方式存储的,而引用类型是存储在堆中的。当值类型赋值给其他变量时,会在栈中生成一个新的空间,其值与原值相同,但是他们之间是相互独立的,没有任何联系。

12f9c69d38e62f8e6f63fb0e4dcf6715.png

保存与复制的是指向对象的一个指针,当堆中的值改变后,指向该堆的变量值都会一起改变。

3862762fc64f79de8ae495beadee65ee.png

接着上面的那个例子,改变d的age属性,可以看到b的age也发生了改变。

d.age = 18;console.log(b);   // { name: 'def', age: 18 }

浅拷贝与深拷贝

1e484164e3b5344c6370ab47e156678f.png 723eae802e223e0ed0115a87f50ab4c6.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 ecf05b5bad00f9c57f2f397522bfa33b.png ac14052966f7072d8477d1e4d8acfe07.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值