你不知道的javascript (丛书系列&电子博客版本)

github start:https://github.com/paiDaXing-web/You-Dont-Know-JS-books

在线地址:https://youjia.sx.cn/you-dont-know-ts/

在前一章中,我介绍了编程的基本构建块儿,比如变量,循环,条件,和函数。当然,所有被展示的代码都是 JavaScript。但是在这一章中,为了作为一个 JS 开发者入门和进阶,我们想要特别集中于那些你需要知道的关于 JavaScript 的事情。

我们将在本章中介绍好几个概念,它们将会在后续的 YDKJS 丛书中全面地探索。你可以将这一章看作是这个系列的其他书目中将要详细讲解的话题的一个概览。

特别是如果你刚刚接触 JavaScript,那么你应当希望花相当一段时间来多次复习这里的概念和代码示例。任何好的基础都是一砖一瓦积累起来的,所以不要指望你会在第一遍通读后就立即理解了全部内容。

你深入学习 JavaScript 的旅途从这里开始。

注意: 正如我在第一章中说过的,在你通读这一章的同时,你绝对应该亲自尝试这里所有的代码。要注意的是,这里的有些代码假定最新版本的 JavaScript(通常称为“ES6”,ECMAScript 的第六个版本 —— ECMAScript 是 JS 语言规范的官方名称)中引入的功能是存在的。如果你碰巧在使用一个老版本的,前 ES6 时代的浏览器,这些代码可能不好用。应当使用一个更新版本的现代浏览器(比如 Chrome,Firefox,或者 IE)。

#值与类型

正如我们在第一章中宣称的,JavaScript 拥有带类型的值,没有带类型的变量。下面是可用的内建类型:

JavaScript 提供了一个typeof操作符,它可以检查一个值并告诉你它的类型是什么:


string
number
boolean
null 和 undefined
object
symbol (ES6 新增类型)

var a;
typeof a; // "undefined"

a = "hello world";
typeof a; // "string"

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

a = true;
typeof a; // "boolean"

a = null;
typeof a; // "object" -- 奇怪的bug

a = undefined;
typeof a; // "undefined"

a = { b: "c" };
typeof a; // "object"

来自typeof的返回值总是六个(ES6 中是七个! —— “symbol”类型)字符串值之一。也就是,typeof "abc"返回"string",不是string。

注意在这个代码段中变量a是如何持有每种不同类型的值的,而且尽管表面上看起来很像,但是typeof a并不是在询问“a的类型”,而是“当前a中的值的类型”。在 JavaScript 中只有值拥有类型;变量只是这些值的简单容器。

typeof null是一个有趣的例子,因为当你期望它返回"null"时,它错误地返回了"object"。

警告: 这是 JS 中一直存在的一个 bug,但是看起来它永远都不会被修复了。在网络上有太多的代码依存于这个 bug,因此修复它将会导致更多的 bug!

另外,注意a = undefined。我们明确地将a设置为值undefined,但是在行为上这与一个还没有被设定值的变量没有区别,比如在这个代码段顶部的var a;。一个变量可以用好几种不同的方式得到这样的“undefined”值状态,包括没有返回值的函数和使用void操作符。

#对象

object类型指的是一种复合值,你可以在它上面设定属性(带名称的位置),每个属性持有各自的任意类型的值。它也许是 JavaScript 中最有用的类型之一。

var obj = {
  a: "hello world",
  b: 42,
  c: true,
};

obj.a; // "hello world"
obj.b; // 42
obj.c; // true

obj["a"]; // "hello world"
obj["b"]; // 42
obj["c"]; // true

可视化地考虑这个obj值可能会有所帮助:

属性既可以使用 点号标记法(例如,obj.a) 访问,也可以使用 方括号标记法(例如,obj["a"]) 访问。点号标记法更短而且一般来说更易于阅读,因此在可能的情况下它都是首选。

如果你有一个名称中含有特殊字符的属性名称,方括号标记法就很有用,比如obj["hello world!"] —— 当通过方括号标记法访问时,这样的属性经常被称为 键。[ ]标记法要求一个变量(下一节讲解)或者一个string 字面量(它需要包装进" .. "或' .. ')。

当然,如果你想访问一个属性/键,但是它的名称被存储在另一个变量中时,方括号标记法也很有用。例如:

var obj = {
  a: "hello world",
  b: 42,
};

var b = "a";

obj[b]; // "hello world"
obj["b"]; // 42

注意: 更多关于 JavaScript 的object的信息,请参见本系列的 this 与对象原型,特别是第三章。

在 JavaScript 程序中有另外两种你将会经常打交道的值类型:数组 和 函数。但与其说它们是内建类型,这些类型应当被认为更像是子类型 —— object类型的特化版本。

#数组

一个数组是一个object,它不使用特殊的带名称的属性/键持有(任意类型的)值,而是使用数字索引的位置。例如:

var arr = ["hello world", 42, true];

arr[0]; // "hello world"
arr[1]; // 42
arr[2]; // true
arr.length; // 3

typeof arr; // "object"

注意: 从零开始计数的语言,比如 JS,在数组中使用0作为第一个元素的索引。

可视化地考虑arr很能会有所帮助:

因为数组是一种特殊的对象(正如typeof所暗示的),所以它们可以拥有属性,包括一个可以自动被更新的length属性。

理论上你可以使用你自己的命名属性将一个数组用作一个普通对象,或者你可以使用一个object但是给它类似于数组的数字属性(0,1,等等)。然而,这么做一般被认为是分别误用了这两种类型。

最好且最自然的方法是为数字定位的值使用数组,而为命名属性使用object。

#函数

另一个你将在 JS 程序中到处使用的object子类型是函数:

function foo() {
  return 42;
}

foo.bar = "hello world";

typeof foo; // "function"
typeof foo(); // "number"
typeof foo.bar; // "string"

同样地,函数也是object的子类型 —— typeof返回"function",这暗示着"function"是一种主要类型 —— 因此也可以拥有属性,但是你一般仅会在有限情况下才使用函数对象属性(比如foo.bar)。

注意: 更多关于 JS 的值和它们的类型的信息,参见本系列的 类型与文法 的前两章。

#内建类型的方法

我们刚刚讨论的内建类型和子类型拥有十分强大和有用的行为,它们作为属性和方法暴露出来。

例如:

var a = "hello world";
var b = 3.14159;

a.length; // 11
a.toUpperCase(); // "HELLO WORLD"
b.toFixed(4); // "3.1416"

使调用a.toUpperCase()成为可能的原因,要比这个值上存在这个方法的说法复杂一些。

简而言之,有一个String(S大写)对象包装器形式,通常被称为“原生类型”,与string基本类型配成一对儿;正是这个对象包装器的原型上定义了toUpperCase()方法。

当你通过引用一个属性或方法(例如,前一个代码段中的a.toUpperCase())将一个像"hello world"这样的基本类型值当做一个object来使用时,JS 自动地将这个值“封箱”为它对应的对象包装器(这个操作是隐藏在幕后的)。

一个string值可以被包装为一个String对象,一个number可以被包装为一个Number对象,而一个boolean可以被包装为一个Boolean对象。在大多数情况下,你不担心或者直接使用这些值的对象包装器形式 —— 在所有实际情况中首选基本类型值形式,而 JavaScript 会帮你搞定剩下的一切。

注意: 关于 JS 原生类型和“封箱”的更多信息,参见本系列的 类型与文法 的第三章。要更好地理解对象原型,参见本系列的 this 与对象原型 的第五章。

#值的比较

在你的 JS 程序中你将需要进行两种主要的值的比较:等价 和 不等价。任何比较的结果都是严格的boolean值(true或false),无论被比较的值的类型是什么。

#强制转换

在第一章中我们简单地谈了一下强制转换,我们在此回顾它。

在 JavaScript 中强制转换有两种形式:明确的 和 隐含的。明确的强制转换比较简单,因为你可以在代码中明显地看到一个类型转换到另一个类型将会发生,而隐含的强制转换更像是另外一些操作的不明显的副作用引发的类型转换。

你可能听到过像“强制转换是邪恶的”这样情绪化的观点,这是因为一个清楚的事实 —— 强制转换在某些地方会产生一些令人吃惊的结果。也许没有什么能比当一个语言吓到开发者时更能唤起他们的沮丧心情了。

强制转换并不邪恶,它也不一定是令人吃惊的。事实上,你使用类型强制转换构建的绝大部分情况是十分合理和可理解的,而且它甚至可以用来 增强 你代码的可读性。但我们不会在这个话题上过度深入 —— 本系列的 类型与文法 的第四章将会进行全面讲解。

这是一个 明确 强制转换的例子:

var a = "42";

var b = Number(a);

a; // "42"
b; // 42 -- 数字!
而这是一个 隐含 强制转换的例子:

var a = "42";

var b = a * 1; // 这里 "42" 被隐含地强制转换为 42

a; // "42"
b; // 42 -- 数字!
#Truthy 与 Falsy

在第一章中,我们简要地提到了值的“truthy”和“falsy”性质:当一个非boolean值被强制转换为一个boolean时,它是变成true还是false。

在 JavaScript 中“falsy”的明确列表如下:

"" (空字符串)
0, -0, NaN (非法的number)
null, undefined
false

任何不在这个“falsy”列表中的值都是“truthy”。这是其中的一些例子:

重要的是要记住,一个非boolean值仅在实际上被强制转换为一个boolean时才遵循这个“truthy”/“falsy”强制转换。把你搞糊涂并不困难 —— 当一个场景看起来像是将一个值强制转换为boolean,可其实它不是。


"hello"
42
true
[ ], [ 1, "2", 3 ] (数组)
{ }, { a: 42 } (对象)
function foo() { .. } (函数)

#等价性

有四种等价性操作符:==,===,!=,和!==。!形式当然是与它们相对应操作符平行的“不等”版本;不等(non-equality) 不应当与 不等价性(inequality) 相混淆。

==和===之间的不同通常被描述为,==检查值的等价性而===检查值和类型两者的等价性。然而,这是不准确的。描述它们的合理方式是,==在允许强制转换的条件下检查值的等价性,而===是在不允许强制转换的条件下检查值的等价性;因此===常被称为“严格等价”。

考虑这个隐含强制转换,它在==宽松等价性比较中允许,而===严格等价性比较中不允许:

var a = "42";
var b = 42;

a == b; // true
a === b; // false

在a == b的比较中,JS 注意到类型不匹配,于是它经过一系列有顺序的步骤将一个值或者它们两者强制转换为一个不同的类型,直到类型匹配为止,然后就可以检查一个简单的值等价性。

如果你仔细想一想,通过强制转换a == b可以有两种方式给出true。这个比较要么最终成为42 == 42,要么成为"42" == "42"。那么是哪一种呢?

答案:"42"变成42,于是比较成为42 == 42。在一个这样简单的例子中,只要最终结果是一样的,处理的过程走哪一条路看起来并不重要。但在一些更复杂的情况下,这不仅对比较的最终结果很重要,而且对你 如何 得到这个结果也很重要。

a === b产生false,因为强制转换是不允许的,所以简单值的比较很明显将会失败。许多开发者感觉===更可靠,所以他们提倡一直使用这种形式而远离==。我认为这种观点是非常短视的。我相信==是一种可以改进程序的强大工具,如果你花时间去学习它的工作方式。

我们不会详细地讲解强制转换在==比较中是如何工作的。它的大部分都是相当合理的,但是有一些重要的极端用例要小心。你可以阅读 ES5 语言规范的 11.9.3 部分(http://www.ecma-international.org/ecma-262/5.1/)来了解确切的规则,而且与围绕这种机制的所有负面炒作比起来,你会对这它是多么的直白而感到吃惊。

为了将这许多细节归纳为一个简单的包装,并帮助你在各种情况下判断是否使用==或===,这是我的简单规则:


var a = [1, 2, 3];
var b = [1, 2, 3];
var c = "1,2,3";

a == c; // true
b == c; // true
a == b; // false

如果一个比较的两个值之一可能是true或false值,避免==而使用===。

如果一个比较的两个值之一可能是这些具体的值(0,"",或[] —— 空数组),避免==而使用===。

在 所有 其他情况下,你使用==是安全的。它不仅安全,而且在许多情况下它可以简化你的代码并改善可读性。

这些规则归纳出来的东西要求你严谨地考虑你的代码:什么样的值可能通过这个被比较等价性的变量。如果你可以确定这些值,那么==就是安全的,使用它!如果你不能确定这些值,就使用===。就这么简单。

!=不等价形式对应于==,而!==形式对应于===。我们刚刚讨论的所有规则和注意点对这些非等价比较都是平行适用的。

如果你在比较两个非基本类型值,比如object(包括function和array),那么你应当特别小心==和===的比较规则。因为这些值实际上是通过引用持有的,==和===比较都将简单地检查这个引用是否相同,而不是它们底层的值。

例如 ,array默认情况下会通过使用逗号(,)连接所有值来被强制转换为string。你可能认为两个内容相同的array将是==相等的,但它们不是:

注意: 更多关于==等价性比较规则的信息,参见 ES5 语言规范(11.9.3 部分),和本系列的 类型与文法 的第四章;更多关于值和引用的信息,参见它的第二章。

作者:马学明

原文:https://youjia.sx.cn/you-dont-know-ts/docs/you-dont-js/up-going/ch2.html#%E5%80%BC%E7%9A%84%E6%AF%94%E8%BE%83

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优价实习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值