JavaScript中没有被搞清楚的那些野孩子
一、null 与 undefinded那些事儿
[null为没有对象;undefined为缺少值]
1、undefined 表示未定义的变量。null 值表示一个空对象指针。
2、JavaScript中的null是一个表示“无”的对象,转为数值时为0;undefined是一个表示“无”的原始值,转为数值时为NaN。
Number(null)
// 0
5 + null
// 5
Number(undefined)
// NaN
5 + undefined
// NaN(NaN在JavaScript中代表的是特殊非数字值,它本身就是一种数字类型)
3、应用与区别
null表示没有对象,即该处不应该有值。
(1)作为函数的参数,表示该函数的参数不是对象
(2)作为对象原型链的终点
Object.getPrototypeOf(Object.prototype)
// null
undefined表示“缺少值”,就是此处应该有一个值,但是还没定义。
(1)变量被声明了,但没有赋值时,就等于 undefined。
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为 undefined。
(4)函数没有返回值时,默认返回 undefined。
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined
二、“判空”那点事儿
javaScript 五种空值和假值,分别为 undefined,null,false," ",0.这导致了判空时经常会出问题。
let a=0;
console.log(a||'/')
//本意是a为null或者undefined时,就输出‘/’,实际上,只要是上面五种种任何一种都会输出‘/’
当然我们可以写成这样子
let a=0;
if(a===null||a===undefined)console.log('/');
else console.log(a);
但是这个始终觉得有点繁琐,所以ES规范提出了 空值合并操作符(??)
空值合并操作符(??)是一个逻辑操作符,
当左侧的操作数为null或者 undefined时,返回其右侧操作数,
否则返回左侧操作数。
上面的例子可以写成
let a=0;
console.log(a??'/');
// 0
let b;
console.log(b??'&&');
// &&
三、typeof的返回类型有哪些?
typeof的返回值共有七种:
number, boolean, string, undefined, object, function,symbol.
1、number
typeof(10)
// "number"
typeof(NaN)
// "number"
typeof(Infinity)
// "number"
2、boolean
typeof(true);
typeof(false);
3、string
typeof('233');
// "string"
typeof('wqw');
// "string"
4、undefined
typeof(undefined);
// "undefined"(变量没有被赋值)
typeof(undeclared);
// "undefined"(变量没有声明)
//undefined和undeclared原本是两种不同的情况,但是在JavaScript中混为一谈
typeof(s);
// "undefined" 其中s为不存在的变量
5、object
// 对象,数组,null返回object
typeof(null)
// "object"
typeof([1,2,3])
// "object"
typeof({a:'1',b:'2'})
// "object"
6、function
typeof(Array)
// "function"
typeof(Date)
// "function"
7、symbol
typeof(Symbol())
// "symbol"
注意:
1、typeof(null)为什么是返回object?
JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。第一版的 JavaScript 是用 32 位比特来存储值的,且是通过值的低 1 位或 3 位来识别类型的,对象的类型标签是 000。如下:
1:整型(int)
000:引用类型(object)
010:双精度浮点型(double)
100:字符串(string)
110:布尔型(boolean)
但有两个特殊值:
undefined,用整数−2^30(负2的30次方,不在整型的范围内)
null,机器码空指针(C/C++ 宏定义),低三位也是000
由于 null 代表的是空指针(低三位也是 000 ),因此,null 的类型标签是 000,typeof null 也因此返回 “object”。
2、Number——0.1+0.2 !== 0.3
在 JavaScript 会存在类似如下的现象:
0.1 + 0.2
// 0.30000000000000004
原因:
我们在对浮点数进行运算的过程中,需要将十进制转换成二进制。十进制小数转为二进制的规则如下:
对小数点以后的数乘以2,取结果的整数部分(不是1就是0),
然后再用小数部分再乘以2,再取结果的整数部分……
以此类推,直到小数部分为0或者位数已经够了就OK了。
然后把取的整数部分按先后次序排列
根据上面的规则,最后0.1的表示就是:
0.000110011001100110011(0011无限循环)……
所以说,精度缺失不是语言的问题,而是浮点数存储本身固有的缺陷。
JavaScript 是以 64 位双精度浮点数存储所有 Number 类型值,按照 IEEE754 规范,0.1 的二进制数只保留 52 位有效数字,即
1.100110011001100110011001100110011001100110011001101 * 2^(-4)
同理,0.2的进制值数为
1.100110011001100110011001100110011001100110011001101 * 2^(-3)
这样子在进制转换中精度就已经缺失了。运算的时候如下:
0.00011001100110011001100110011001100110011001100110011010
+0.00110011001100110011001100110011001100110011001100110100
------------------------------------------------------------
=0.01001100110011001100110011001100110011001100110011001110
所以导致了最后的计算结果中 0.1 + 0.2 !== 0.3
那么怎么解决呢?
–>方法(1)
把计算数字 提升 10 的N次方 倍 再 除以 10的N次方。N>1.
(0.1*1000+0.2*1000)/1000==0.3
//true
–>方法(2)
ES6提供的Number.EPSILON方法
function numbersequal(a,b){ return Math.abs(a-b)<Number.EPSILON;
}
var a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //true
考虑兼容性的问题了,在chrome中支持这个属性,但是IE并不支持(笔者的版本是IE10不兼容),所以我们还要解决IE的不兼容问题。
Number.EPSILON=(function(){ //解决兼容性问题
return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
})();
//上面是一个自调用函数,当JS文件刚加载到内存中,就会去判断并返回一个结果,相比if(!Number.EPSILON){
// Number.EPSILON=Math.pow(2,-52);
//}这种代码更节约性能,也更美观。
function numbersequal(a,b){
return Math.abs(a-b)<Number.EPSILON;
}
//接下来再判断
var a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //这里就为true了
四、Symbol——我是独一无二最靓的仔
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值
let s = Symbol();
typeof s
// "symbol"
应用场景:
(1)定义一组常量,保证这组数组常量都是不相等的,消除魔法字符串
(2)对象中保证不同的属性名
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
五、数组——对象中一个特殊的存在
数组是一个特殊的存在,是我们平时接触得最多的数据结构之一,它是一个特殊的对象,它的索引就是“普通对象”的 key 值。但它又拥有一些“普通对象”没有的方法,比如 map 等
typeof 是 javascript 原生提供的判断数据类型的运算符,它会返回一个表示参数的数据类型的字符串。但我们不能通过 typeof 判断是否为数组。因为 typeof 数组和普通对象以及 null,都是返回 “object”
怎么判断Array的方法?
(1)Object.prototype.toString.call()
const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"
(2)Array.isArray()
const a = [];
const b = {};
Array.isArray(a);//true
Array.isArray(b);//false
Array.isArray() 是 ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
(3)instanceof。instanceof 运算符可以用来判断某个构造函数的 prototype 属性所指向的對象是否存在于另外一个要检测对象的原型链上。因为数组的构造函数是 Array,所以可以通过以下判断。注意:因为数组也是对象,所以 a instanceof Object 也为 true
const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true,在数组的原型链上也能找到Object构造函数
console.log(b instanceof Array);//false
(4)constructor。通过构造函数实例化的实例,拥有一个 constructor 属性。
function B() {};
let b = new B();
console.log(b.constructor === B) // true
而数组是由一个叫 Array 的函数实例化的。所以可以
let c = [];
console.log(c.constructor === Array) // true
注意:constructor是会被改变的,所以不推荐这样子使用,例如:
let c = [];
c.constructor = Object;
console.log(c.constructor === Array); // false
结论
根据上面的描述,个人推荐的判断方法有如下的优先级
isArray > Object.prototype.toString.call() > instanceof > constructor