javaScript 数据类型的陷阱

33 阅读4分钟

判断是否是object

代码:

function isObject(obj){
    if(typeof obj=='object'){
        return true;
    }
    return false;
}

console.log(isObject(null));
  1. 第一个问题: 上面的方法有什么问题?

objnull时,typeof obj=='object'返回true.

  1. 第二个问题:为什么 typeof null 返回的值是object

简单来说,typeof null的结果为Object的原因是一个bug。在 javascript 的最初版本中,使用的 32位系统,js为了性能优化,使用低位来存储变量的类型信息。

image.png

在判断数据类型时,是根据机器码低位标识来判断的,而null的机器码标识为全0,而对象的机器码低位标识为000。所以typeof null的结果被误判为Object

  1. 第三个问题: 为什么不修复这个问题?

为了兼容以前的代码。

一元运算符+转为数字

function toNumber(val) {
    const result = +val;
    return result
}

// 传统数据类型
toNumber(null) // NaN
toNumber(undefined) // NaN
toNumber(1) // 1
toNumber("123cc") // NaN
toNumber("123") // 123
toNumber({}) // NaN
toNumber({a:2}) // NaN
toNumber(true) // 1


// ES6的 bigInt和Symbol

toNumber(10n) //throw error :`Cannot convert a BigInt value to a number`
toNumber(Symbol.for("a")) //throw error:' Cannot convert a Symbol value to a number'

为什么会造成这样的现象?

兼容问题,es6之前的数据类型适用,es6中新添加的数据类型不适用。

位移转为数字

code:

const print = console.log;
function toNumber(val){
    const result = val >> 0; //带符号位位移
    print(result)
    return result
}
function toNumber2(val){
    const result = val >>> 0; //不带符号位位移
    print(result)
    return result
}
  1. 问题: 代码有什么问题?
//数小的时候
toNumber(null)  // 0
toNumber({})    // 0
toNumber("10x") // 0
toNumber("10")  // 10

// 超大的数
toNumber(Number.MAX_SAFE_INTEGER)   // -1
toNumber2(Number.MAX_SAFE_INTEGER)  // 4294967295 

可以看出当数超大时,带符号位位移和不带符号位位移得到的结果不一样。

为什么会造成这样的结果呢?

这是因为带符号位和不带符号位的二进制的处理不一样。

使用>>(带符号位位移)和 >>>(不带符号位位移)时:

  1. 先将十整数转为二进制数
  2. 取二进制的前32位
  3. 无符号位(>>>):将二进制转为十进制; 有符号位(>>):分正负情况。正:直接转为十进制;负:先减一再取反再转为十进制。

所以造成该问题的本质原因是:32位的有符号位移和无符号位移.

字符串批量转换为整数

var results = ["1", "2", "3"].map(parseInt);
console.log(results); //[ 1, NaN, NaN ]
  1. 为什么结果不是[1,2,3]呢? 实际上进行了下面的操作。
["1", "2", "3"].map((val,index)=> parseInt(val,index))
// parseInt("1",0)
// parseInt("2",1)
// parseInt("3",2)
  1. parseInt第二个参数的取值范围?2~36

parseInt(string, radix):其中,string 是要解析的字符串,radix 是一个可选参数,表示解析时使用的基数(即进制)。

如果 radix 参数未指定或为 0,则 parseInt() 会尝试根据 string 的前缀自动判断基数。如果 string 以 "0x" 或 "0X" 开头,则基数为 16(十六进制);如果 string 以 "0" 开头,则基数为 8(八进制);否则基数为 10(十进制)。

如果指定了 radix 参数,则 parseInt() 会将 string 解析为指定进制的整数。radix 的取值范围是 2 到 36。如果 string 不能被解析为指定进制的整数,则 parseInt() 返回 NaN

if条件判断

const result = {};
// name存在
if(obj.name){
    result.name = obj.name;
}
return result;
  1. 本质

是将obj.name转为boolean.

  1. 哪些值转为布尔值为false
  • false
  • 0-0
  • 0n-0n(BigInt 类型的零)
  • ""(空字符串)
  • null
  • undefined
  • NaN

宽松比较

console.log(null == 0)
console.log('0' == false)
console.log(null == null)
  1. 本质

隐式转换

  1. 宽松比较的规律

image.png

typeof 性能比 instanceof 性能高20倍?

var count = 10000000;
var func = function () { };

var startTime = new Date();
console.log(typeof func === "function");
for (var j = 0; j < count; j++) {
    typeof func === "function";
}
console.log('[typeof func === "function"] ' + (new Date().getTime() - startTime.getTime()));
startTime = new Date();


console.log(func instanceof Function);
for (var k = 0; k < count; k++) {
    func instanceof Function;
}
console.log('[func instanceof Function] ' + (new Date().getTime() - startTime.getTime()));

image.png

在百万级时,是高2倍,所以可以放心使用instanceof.

null和undefined实现的机制完全不一样

// null和undefined
var print = console.log;
print(Object.getOwnPropertyDescriptor(global, 'null'))
print(Object.getOwnPropertyDescriptor(global, 'undefined'))

image.png

  1. getOwnPropertyDescriptor方法

Object.getOwnPropertyDescriptor(obj, prop)其中,obj 表示包含属性的对象,prop 表示要获取描述符的属性名。

Object.getOwnPropertyDescriptor() 是 JavaScript 内置函数之一,它用于获取对象上指定属性的描述符。描述符包含属性的值、是否可写、是否可枚举以及是否可配置等信息。

Object.getOwnPropertyDescriptor() 的语法如下:

javascriptCopy code
Object.getOwnPropertyDescriptor(obj, prop)

其中,obj 表示包含属性的对象,prop 表示要获取描述符的属性名。

存在,函数返回一个包含以下属性的对象:

  • value:属性的值,如果属性存在的话。
  • writable:一个布尔值,表示属性是否可写。
  • enumerable:一个布尔值,表示属性是否可枚举。
  • configurable:一个布尔值,表示属性是否可配置。

如果属性不存在,函数返回 undefined