anjia / blog

博客,积累与沉淀
106 stars 4 forks source link

数据类型 | Undefined, Null, Boolean #84

Open anjia opened 2 years ago

anjia commented 2 years ago

在上一篇文章 #83 中我们了解到,学习数据类型需要知道类型的含义、该类型值的存储方式和它能执行的操作,也理解了 JavaScript 原始值是不可变的。

从这篇文章开始,我们将介绍7种原始数据类型。思路大约是:

原始数据类型 = 原始类型,下文简称“类型“
原始值,下文简称“值”
基本类型 = 原始类型 + 对象

本文先介绍前三个基本类型:Undefined, Null, Boolean。

Undefined 类型

Undefined 类型只有一个值,就是 undefined。任何尚未赋值的变量都具有 undefined 值。

未声明的变量,类型是 Undefined,取值时会报错。如下:

typeof foo === 'undefined';  // true
console.log(foo);  // ReferenceError: foo is not defined

已声明但尚未赋值的变量,类型是 Undefined,值是 undefined。如下:

let foo;
typeof foo === 'undefined';  // true
console.log(foo);            // undefined

通用知识

在计算机编程中,undefined 值是表达式没得到正确值的情况,尽管它在语法上是正确的。undefined 值和空字符串、布尔值 false 以及其它已经定义的空值是不一样的。

在有些编程语言中,对 undefined 值的使用可能会导致异常或未定义行为,但在另一些语言中,undefined 值可能发生在正常的可预测的程序执行过程中。

动态类型语言通常会显式处理 undefined 值。在上一篇文章 #83 的“数据类型”小节里我们有提到过动态类型检查和静态类型检查。动态类型检查是在运行时验证程序类型安全的过程,它的实现通常是把每个运行时对象和包含其类型信息的类型标记相关联。此运行时类型信息(RTTI, Runtime Type Information)还可以用来实现动态调度、后期绑定(或动态绑定)、向下转换(或类型细化)以及反射等功能。

JavaScript 就是动态类型语言,所以我们需要知道在什么时候如何处理这个特殊的 undefined 值。

注意事项

全局 undefined 属性

值得一提的是,全局 undefined 属性就表示 undefined 值,也就是说 undefined 是全局范围的一个变量。如下:

typeof undefined === 'undefined';         // true
typeof window.undefined === 'undefined';  // true
console.log(undefined);         // undefined
console.log(window.undefined);  // undefined

它和自动赋的 undefined 值是相等的。如下:

let foo, bar;
undefined === undefined;   // true
foo === bar;               // true
foo === undefined;         // true
foo === window.undefined;  // true

undefined 不是关键字

这是 JavaScript 语言公认的设计失误之一

根据 ES5 规范,现代浏览器里的 undefined 变量是不可配置、不可写的属性,所以在全局作用域里给它赋值是不会生效的(虽然情况并非总是如此,但应该避免覆盖它的值)。

window.undefined = 'hello';
console.log(window.undefined);  // undefined

但是,由于 undefined 不是关键字,这就能让我们在非全局作用域里定义一个名为 undefined 的变量,且能给它随便赋值。如下:

(function() {
  var undefined = 'hello'; // 但是,应避免这样做
  console.log(undefined, typeof undefined);  // hello string
})();

// 但是,应避免这样做
(function(undefined) {
  console.log(undefined, typeof undefined);  // world string
})('world');

判断值是否是 undefined

正是考虑到全局作用域里的 undefined 变量的值有被修改的风险,且在非全局作用域里 undefined 可能就是一个普通的变量,所以在实际开发中,如果要判断一个变量的值是否是 undefined 值,一般不直接使用 undefined 变量。 常见的替代方法有两个。

方法一:用 typeof 判断其类型值。而且当变量没有被声明时,也不会报错。

if (typeof foo === 'undefined') {
}

方法二:用 void 0 来替代 undefined 变量。void 运算符计算给定的表达值(是一个或多个常量/变量/函数和运算符的组合),然后返回 undefined 值。

if (foo === void 0) {
} // 如果变量未声明,会报错 ReferenceError: foo is not defined

Null 类型

Null 类型只有一个值,就是 null。null 值表示对象值的有意缺失。

通用知识

在计算机编程中,null 指针/引用是一个保留值,用来表示指针/引用没有指向一个有效对象。程序通常用它来表示某些特定条件,比如未知长度列表的结尾、某些无法执行的操作。

此外,一些编程语言还有个特性是可空类型(nullable type),它允许将值设置为特殊值 NULL 而不是其它常见的数据类型。在静态类型语言中,可空类型是可选类型(option type 或 maybe type),而动态类型语言(值有类型但变量没有)往往是提供一个单独的 null 值。

NULL 也经常用来表示缺失值或无效值,比如没有成功返回的函数、数据库里的缺失字段(如SQL里的 NULL)。

JavaScript 里的 null 值,可结合上面的这些关键词来理解:引用/对象、可空类型/动态语言.单独值、缺失值。

注意事项

typeof null

在 JavaScript 中,null 是原始值之一,因为它的行为看起来是”原始的“。但是,在某些情况下,null 值并不像它看起来的那样”原始“。所有的对象都继承自 null 值,所以它的 typeof 运算符返回的是 object。如下:

typeof null; // "object"

typeof 是 object 而不是 null,这是一个历史遗留问题。

null 关键字

与 undefined 不同,null 不是全局对象的属性标识符,而且它是 JavaScript 的关键字,所以可以放心地用 null 关键字来获取 null 值。

if (foo === null) {}
let bar = null;

当给 null 赋值的时候,会报错。如下:

null = 'hello';  // SyntaxError: Invalid left-hand side in assignment

此外,null 和 undefined 还有如下不同:

null == undefined;     // true
null === undefined;    // false

1 + null;        // 1
1 + undefined;   // NaN
isNaN(1 + null);       // false
isNaN(1 + undefined);  // true

Boolean 类型

Boolean 类型表示逻辑实体,它有两个值:true 和 false。

通用知识

在计算机科学中,Boolean 数据类型是一种具有两个可能值之一的数据类型,通常表示为 true 和 false,旨在表示逻辑和布尔代数的两个真值。它是以英国数学家/哲学家/逻辑学家 George Boole 的名字命名的,他在19世纪中叶首次定义了逻辑代数系统。

Boolean 数据类型主要和条件语句关联。条件语句根据布尔条件的计算结果的真/假来改变控制流,从而允许不同的操作。Boolean 数据类型是更通用的逻辑数据类型(详见概率逻辑)的特例,逻辑并不总是布尔值。

在大多数编程语言中,即便是那些没有明确 Boolean 类型的语言(可以用其它数据类型来表示真/假值),它们都支持 Boolean 代数运算,比如合取(AND, &, *)、析取(OR, |, +)、等价(EQV, =, ==)、异或/非等价(XOR, NEQV, ^, !=) 和否定(NOT, ~, !)。

可重点关注:各种值/运算/表达式到 Boolean 值的转换,尤其是用在条件语句中的时候

对象包装器

Boolean 对象是 Boolean 值的对象包装器。

  1. 构造器
    • new Boolean() 返回对象。搭配 new 关键字作为构造函数用
    • Boolean() 返回布尔值。直接当函数用,可执行类型转换
  2. 对象的实例方法
    • valueOf() 返回布尔值
    • toString() 返回字符串

eg1. 不同形式对应的类型

let bVal = true;          // 字面量
let bFunc = Boolean();    // 函数
let bObj = new Boolean(); // new

typeof bVal === 'boolean';   // true
typeof bFunc === 'boolean';  // true
typeof bObj === 'object';    // true

eg2. 实例方法

let foo = new Boolean();
foo.valueOf();   // false
foo.toString();  // "false"

注意事项

Boolean 对象的初始值

当参数是以下8种情况之一时,对象的初始值是 false。

  1. 空参数, 空字符串, null, undefined
  2. false
  3. 0, -0, NaN

否则,对象的初始值都是 true。包括:

  1. 字符串 'false'
  2. 任意对象。诸如:
    • 空数组 []
    • 空对象 {}
    • 值是 false 的 Boolean 对象,比如 new Boolean()
let bar = new Boolean('false');
console.log(bar.valueOf());  // true

let foo = new Boolean([]);
console.log(foo.valueOf());  // true

let baz = new Boolean({});
console.log(baz.valueOf());  // true

new Boolean( new Boolean() ).valueOf();   // true

if (对象)

不要将 Boolean 类型的 true/false 值和 Boolean 对象的 true/false 值搞混了。前者是纯纯的原始值,后者是”对象“的值。

任何值不是 undefined 和 null 的对象(包括值为 false 的 Boolean 对象),在传给条件语句的时候,都会被解析成 true。如下:

if([]){
  console.log('[] is true.');  // 会输出 [] is true.
}
if({}){
  console.log('{} is true.');  // 会输出 {} is true.
}
let bObj = new Boolean(false);
if(bObj){
  console.log('new Boolean(false) is true');  // 会输出 new Boolean(false) is true
}

所以,不要使用 Boolean 对象替代 Boolean 原始值

插播:if([]) 是 true,if([]==false) 也是 true 是为什么?

if([] == false) {
  console.log('[]==false is true');   // 会输出 []==false is true
}

当将对象和 Boolean 原始值进行松散比较(==)时,需要了解实际比较的内容。[] == false比较的是[]的值和 false,即[].toString()和 false,即"" == false。(这类知识会在后续”隐式类型转换“主题的文章里讨论,这里就不展开了)。

转成 Boolean 值

如果要把一个非 Boolean 值转成 Boolean 值,不推荐使用 Boolean 对象。可以使用 Boolean 函数或者双感叹号,它们都会返回 Boolean 类型。

var x = (new Boolean(expression)).valueOf();  // 不推荐
var x = Boolean(expression);  // 推荐
var x = !!(expression);       // 推荐

主要参考