你不懂js系列学习笔记-类型与文法- 01

第一章:类型

原文:You-Dont-Know-JS

大多数开发者会说,动态语言(就像 JS)没有 类型。让我们看看 ES5.1 语言规范(www.ecma-international.org/ecma-262/5.…

在本语言规范中的算法所操作的每一个值都有一种关联的类型。可能的值的类型就是那些在本条款中定义的类型。类型还进一步被分为 ECMAScript 语言类型和语言规范类型

一个 ECMAScript 语言类型对应于 ECMAScript 程序员使用 ECMAScript 语言直接操作的值。ECMAScript 语言类型有 Undefined,Null,Boolean,String,Number,和 Object。

1.内建类型

JavaScript 定义了七种内建类型:

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol -- 在 ES6 中被加入的!

注意: 除了 object 所有这些类型都被称为“基本类型(primitives)”。

typeof 操作符可以检测给定值的类型,而且总是返回七种字符串值中的一种 -- 令人吃惊的是,对于我们刚刚列出的七中内建类型,它没有一个恰好的一对一匹配。

typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true

// 在 ES6 中被加入的!
typeof Symbol() === "symbol"; // true
复制代码

如上所示,这六种列出来的类型拥有相应类型的值,并返回一个与类型名称相同的字符串值。Symbol 是 ES6 的新数据类型,我们将在第三章中讨论它。

正如你可能已经注意到的,我在上面的列表中剔除了 null。它是 特殊的 -- 特殊在它与 typeof 操作符组合时是有 bug 的。

typeof null === "object"; // true
复制代码

要是它返回 "null" 就好了(而且是正确的!),但是这个原有的 bug 已经存在了近二十年,而且好像永远也不会被修复了,因为有太多已经存在的 web 的内容依存着这个 bug 的行为,“修复”这个 bug 将会 制造 更多的“bug”并毁掉许多 web 软件。

如果你想要使用 null 类型来测试 null 值,你需要一个复合条件:

var a = null;

!a && typeof a === "object"; // true
复制代码

null 是唯一一个“falsy”(也叫类 false;见第四章),但是在 typeof 检查中返回 "object" 的基本类型。

那么 typeof 可以返回的第七种字符串值是什么?

typeof function a() {
  /* .. */
} === "function"; // true
复制代码

函数实际上是对象这一事实十分有用。最重要的是,它们可以拥有属性。

数组:

typeof [1, 2, 3] === "object"; // true
复制代码

不,它们仅仅是对象。考虑它们的最恰当的方法是,也将它们认为是对象的“子类型”(见第三章),带有被数字索引的附加性质(与仅仅使用字符串键的普通对象相反),并维护着一个自动更新的 .length 属性。

2. 值作为类型

在 JavaScript 中,变量没有类型 -- 值才有类型。变量可以在任何时候,持有任何值。

如果你对一个变量使用 typeof,它不会像表面上看起来那样询问“这个变量的类型是什么?”,因为 JS 变量是没有类型的。取而代之的是,它会询问“在这个变量里的值的类型是什么?”

var a = 42;
typeof a; // "number"

a = true;
typeof a; // "boolean"
复制代码

typeof 操作符总是返回字符串。所以:

typeof typeof 42; // "string"
复制代码

第一个 typeof 42 返回 "number",而 typeof "number""string"

2.1 undefined vs "undeclared"

当前 还不拥有值的变量,实际上拥有 undefined 值。对这样的变量调用 typeof 将会返回 "undefined"

var a;

typeof a; // "undefined"

var b = 42;
var c;

// 稍后
b = c;

typeof b; // "undefined"
typeof c; // "undefined"
复制代码

大多数开发者考虑“undefined”这个词的方式会诱使他们认为它是“undeclared(未声明)”的同义词。然而在 JS 中,这两个概念十分不同。

一个“undefined”变量是在可访问的作用域中已经被声明过的,但是在 这个时刻 它里面没有任何值。相比之下,一个“undeclared”变量是在可访问的作用域中还没有被正式声明的。

考虑这段代码:

var a;

a; // undefined
b; // ReferenceError: b is not defined
复制代码

一个恼人的困惑是浏览器给这种情形分配的错误消息。正如你所看到的,这个消息是“b is not defined”,这当然很容易而且很合理地使人将它与“b is undefined.”搞混。需要重申的是,“undefined”和“is not defined”是非常不同的东西。要是浏览器能告诉我们类似于“b is not found”或者“b is not declared”之类的东西就好了,那会减少这种困惑!

还有一种 typeof 与未声明变量关联的特殊行为,进一步增强了这种困惑。考虑这段代码:

var a;

typeof a; // "undefined"

typeof b; // "undefined"
复制代码

typeof 操作符甚至为“undeclared”(或“not defined”)变量返回 "undefined"。要注意的是,当我们执行 typeof b 时,即使 b 是一个未声明变量,也不会有错误被抛出。这是 typeof 的一种特殊的安全防卫行为。

2.2 typeof Undeclared

不管怎样,当在浏览器中处理 JavaScript 时这种安全防卫是一种有用的特性,因为浏览器中多个脚本文件会将变量加载到共享的全局名称空间。

注意: 许多开发者相信,在全局名称空间中绝不应该有任何变量,而且所有东西应当被包含在模块和私有/隔离的名称空间中。这在理论上很伟大但在实践中几乎是不可能的;但它仍然是一个值得的努力方向!幸运的是,ES6 为模块加入了头等支持,这终于使这一理论变得可行的多了。

作为一个简单的例子,想象在你的程序中有一个“调试模式”,它是通过一个称为 DEBUG 的全局变量(标志)来控制的。在实施类似于在控制台上输出一条日志消息这样的调试任务之前,你想要检查这个变量是否被声明了。一个顶层的全局 var DEBUG = true 声明只包含在一个“debug.js”文件中,这个文件仅在你开发/测试时才被加载到浏览器中,而在生产环境中则不会。

然而,在你其他的程序代码中,你不得不小心你是如何检查这个全局的 DEBUG 变量的,这样你才不会抛出一个 ReferenceError。这种情况下 typeof 上的安全防卫就是我们的朋友。

// 噢,这将抛出一个错误!
if (DEBUG) {
  console.log("Debugging is starting");
}

// 这是一个安全的存在性检查
if (typeof DEBUG !== "undefined") {
  console.log("Debugging is starting");
}
复制代码

即便你不是在对付用户定义的变量(比如 DEBUG),这种检查也是很有用的。如果你为一个内建的 API 做特性检查,你也会发现不抛出错误的检查很有帮助:

if (typeof atob === "undefined") {
  atob = function() {
    /*..*/
  };
}
复制代码

注意: 如果你在为一个还不存在的特性定义一个“填补”,你可能想要避免使用 var 来声明 atob。如果你在 if 语句内部声明 var atob,即使这个 if 条件没有通过(因为全局的 atob 已经存在),这个声明也会被提升(参见本系列的 作用域与闭包)到作用域的顶端。在某些浏览器中,对一些特殊类型的内建全局变量(常被称为“宿主对象”),这种重复声明也许会抛出错误。忽略 var 可以防止这种提升声明。

另一种不带有 typeof 的安全防卫特性,而对全局变量进行这些检查的方法是,将所有的全局变量作为全局对象的属性来观察,在浏览器中这个全局对象基本上是 window 对象。所以,上面的检查可以(十分安全地)这样做:

if (window.DEBUG) {
  // ..
}

if (!window.atob) {
  // ..
}
复制代码

和引用未声明变量不同的是,在你试着访问一个不存在的对象属性时(即便是在全局的 window 对象上),不会有 ReferenceError 被抛出。

另一方面,一些开发者偏好避免手动使用 window 引用全局变量,特别是当你的代码需要运行在多种 JS 环境中时(例如不仅是在浏览器中,还在服务器端的 node.js 中),全局变量可能不总是称为 window

技术上讲,这种 typeof 上的安全防卫即使在你不使用全局变量时也很有用,虽然这些情况不那么常见,而且一些开发者也许发现这种设计方式不那么理想。想象一个你想要其他人复制-粘贴到他们程序中或模块中的工具函数,在它里面你想要检查包含它的程序是否已经定义了一个特定的变量(以便于你可以使用它):

function doSomethingCool() {
  var helper =
    typeof FeatureXYZ !== "undefined"
      ? FeatureXYZ
      : function() {
          /*.. 默认的特性 ..*/
        };

  var val = helper();
  // ..
}
复制代码

doSomethingCool() 对称为 FeatureXYZ 变量进行检查,如果找到,就使用它,如果没找到,使用它自己的。现在,如果某个人在他的模块/程序中引入了这个工具,它会安全地检查我们是否已经定义了 FeatureXYZ

// 一个 IIFE(参见本系列的 *作用域与闭包* 中的“立即被调用的函数表达式”)
(function() {
  function FeatureXYZ() {
    /*.. my XYZ feature ..*/
  }

  // 引入 `doSomethingCool(..)`
  function doSomethingCool() {
    var helper =
      typeof FeatureXYZ !== "undefined"
        ? FeatureXYZ
        : function() {
            /*.. 默认的特性 ..*/
          };

    var val = helper();
    // ..
  }

  doSomethingCool();
})();
复制代码

这里,FeatureXYZ 根本不是一个全局变量,但我们仍然使用 typeof 的安全防卫来使检查变得安全。而且重要的是,我们在这里 没有 可以用于检查的对象(就像我们使用 window.___ 对全局变量做的那样),所以 typeof 十分有帮助。

另一些开发者偏好一种称为“依赖注入”的设计模式,与 doSomethingCool() 隐含地检查 FeatureXYZ 是否在它外部/周围被定义过不同的是,它需要依赖明确地传递进来,就像这样:

function doSomethingCool(FeatureXYZ) {
  var helper =
    FeatureXYZ ||
    function() {
      /*.. 默认的特性 ..*/
    };

  var val = helper();
  // ..
}
复制代码

在设计这样的功能时有许多选择。这些模式里没有“正确”或“错误” -- 每种方式都有各种权衡。但总的来说,typeof 的未声明安全防卫给了我们更多选项,这还是很不错的。

复习

JavaScript 有七种内建 类型nullundefinedbooleannumberstringobjectsymbol。它们可以被 typeof 操作符识别。

变量没有类型,但是值有类型。这些类型定义了值的固有行为。

许多开发者会认为“undefined”和“undeclared”大体上是同一个东西,但是在 JavaScript 中,它们是十分不同的。undefined 是一个可以由被声明的变量持有的值。“未声明”意味着一个变量从来没有被声明过。

JavaScript 很不幸地将这两个词在某种程度上混为了一谈,不仅体现在它的错误消息上(“ReferenceError: a is not defined”),也体现在 typeof 的返回值上:对于两者它都返回 "undefined"

然而,当对一个未声明的变量使用 typeof 时,typeof 上的安全防卫机制(防止一个错误)可以在特定的情况下非常有用。

转载于:https://juejin.im/post/5ae9503f518825673446d78d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值