先记住这个答案
false 是原始值,new Boolean(false) 是用构造函数创建的包装对象,它的 typeof 是 'object'。JavaScript 判断真假时,所有对象都是真值,不会去看对象里存了什么,所以 if (new Boolean(false)) 会执行分支,!new Boolean(false) 是 false。想拿到里面的值要调用 valueOf()。实际开发中不要用 new Boolean,要转布尔值就用不带 new 的 Boolean(x) 或 !!x。
new Boolean(false)仍是对象- 对象永远是真值,包装的内容不影响判断
valueOf()才能取出里面的falseBoolean(x)不加new返回原始布尔值
看它在各种场景下的表现
同一个 new Boolean(false),在不同运算里表现不一样,这正是它容易出 bug 的原因。
const prim = false
const obj = new Boolean(false)
console.log(typeof prim, typeof obj)
console.log(obj ? '进入 if' : '没进 if') // 条件判断
console.log(obj.valueOf(), obj === false) // 取值和严格相等
console.log(obj == false) // 宽松相等
console.log(new Boolean(false) === new Boolean(false))
console.log(typeof Boolean(0), Boolean(0)) // 不加 new查看输出与解释
boolean object
进入 if
false false
true
false
boolean false条件判断只看它是不是对象,所以进了分支。=== 要求类型相同,对象和原始值永远不严格相等。== 会把对象转成原始值,调用 valueOf() 拿到 false 再比较,于是得到 true。两个 new Boolean(false) 是两个不同的对象,比较的是引用,所以不相等。同一个值,在 if 里是真,在 == false 里也是真,这种不一致就是包装对象最大的坑。
包装对象到底有什么用
原始值本身没有属性和方法,但我们天天写 'abc'.length、true.toString()。能这么写,是因为访问属性时引擎会临时把原始值包装成对应的对象,调用完方法就丢掉。这个过程叫自动装箱,开发者感知不到。
所以包装对象的存在是为了给原始值提供方法,不是为了让开发者手动创建。Boolean、Number、String 这三个构造函数带 new 调用会得到对象,不带 new 就是类型转换函数。Symbol 和 BigInt 这两个较新的类型干脆不允许 new,就是吸取了这个教训。
容易答错的地方
- 以为
new Boolean(false)是假值 - 它是对象,而所有对象都是真值。唯一的例外是浏览器的
document.all,和这里无关。 - 以为
Boolean(x)和new Boolean(x)一样 - 差一个
new,一个返回原始布尔值,一个返回对象。代码审查时看到new Boolean、new Number、new String基本都可以直接当 bug 处理,ESLint 的no-new-wrappers规则就是专门拦这个的。 - 以为
obj == false为真就说明它是假值 ==走的是转原始值再比较的规则,和条件判断的 ToBoolean 是两回事。[] == false也是true,但[]同样是真值。
面试官还会怎么问?
给原始值加属性为什么不生效?
比如 const s = 'a'; s.foo = 1; console.log(s.foo) 输出 undefined。赋值时引擎创建了一个临时包装对象,属性加在了它身上,语句结束这个对象就被丢了。下一次访问又是一个新的包装对象。严格模式下这种赋值会直接抛 TypeError。
Object(false) 和 new Boolean(false) 一样吗?
结果一样,都是 Boolean 包装对象。Object(x) 对原始值会返回对应的包装对象,对对象则原样返回,所以有时用来统一把值转成对象再操作。
怎么判断一个值是 Boolean 包装对象?
typeof 只能得到 'object'。可以用 Object.prototype.toString.call(v),包装对象和原始值都会返回 '[object Boolean]',再配合 typeof v === 'object' 就能区分出包装对象。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。