开始学习JavaScript
1、
- 我们可以使用一个
<script>
标签将 JavaScript 代码添加到页面中。 type
和language
属性不是必需的。- 外部的脚本可以通过
<script src="path/to/script.js"></script>
的方式插入。
2、在大多数的编辑器中,一行代码可以使用 Ctrl+/ 热键进行单行注释,诸如 Ctrl+Shift+/ 的热键可以进行多行注释(选择代码,然后按下热键)。对于 Mac 电脑,应使用 Cmd 而不是 Ctrl,使用 Option 而不是 Shift。
3、
"use strict"
指令将浏览器引擎转换为“现代”模式,改变一些内建特性的行为。我们会在之后的学习中了解这些细节。- 严格模式通过将
"use strict"
放置在整个脚本或函数的顶部来启用。一些新语言特性诸如 “classes” 和 “modules” 也会自动开启严格模式。 - 所有的现代浏览器都支持严格模式。
- 我们建议始终使用
"use strict"
启动脚本。本教程的所有例子都默认采用严格模式,除非特别指定(非常少)
4、
我们可以使用 var
、let
或 const
声明变量来存储数据。
let
— 现代的变量声明方式。var
— 老旧的变量声明方式。一般情况下,我们不会再使用它。但是,我们会在 旧时的 "var" 章节介绍var
和let
的微妙差别,以防你需要它们。const
— 类似于let
,但是变量的值无法被修改。
变量应当以一种容易理解变量内部是什么的方式进行命名。
5、
JavaScript 中有八种基本的数据类型(译注:前七种为基本数据类型,也称为原始类型,而 object
为复杂数据类型)。
number
用于任何类型的数字:整数或浮点数,在 ±253 范围内的整数。bigint
用于任意长度的整数。string
用于字符串:一个字符串可以包含一个或多个字符,所以没有单独的单字符类型。boolean
用于true
和false
。null
用于未知的值 —— 只有一个null
值的独立类型。undefined
用于未定义的值 —— 只有一个undefined
值的独立类型。symbol
用于唯一的标识符。object
用于更复杂的数据结构。
我们可以通过 typeof
运算符查看存储在变量中的数据类型。
- 两种形式:
typeof x
或者typeof(x)
。 - 以字符串的形式返回类型名称,例如
"string"
。 typeof null
会返回"object"
—— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个object
。
ps:专门试了一下 typeof null
会返回 "object"
6、
有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。
字符串转换 —— 转换发生在输出内容的时候,也可以通过 String(value)
进行显式转换。原始类型值的 string 类型转换通常是很明显的。
数字型转换 —— 转换发生在进行算术操作时,也可以通过 Number(value)
进行显式转换。
数字型转换遵循以下规则:
值 | 变成…… |
---|---|
undefined | NaN |
null | 0 |
true / false | 1 / 0 |
string | “按原样读取”字符串,两端的空白会被忽略。空字符串变成 0 。转换出错则输出 NaN 。 |
布尔型转换 —— 转换发生在进行逻辑操作时,也可以通过 Boolean(value)
进行显式转换。
布尔型转换遵循以下规则:
值 | 变成…… |
---|---|
0 , null , undefined , NaN , "" | false |
其他值 | true |
上述的大多数规则都容易理解和记忆。人们通常会犯错误的值得注意的例子有以下几个:
- 对
undefined
进行数字型转换时,输出结果为NaN
,而非0
。 - 对
"0"
和只有空格的字符串(比如:" "
)进行布尔型转换时,输出结果为true
。
7、
- 比较运算符始终返回布尔值。
- 字符串的比较,会按照“词典”顺序逐字符地比较大小。
- 当对不同类型的值进行比较时,它们会先被转化为数字(不包括严格相等检测)再进行比较。
- 在非严格相等
==
下,null
和undefined
相等且各自不等于任何其他的值。 - 在使用
>
或<
进行比较时,需要注意变量可能为null/undefined
的情况。比较好的方法是单独检查变量是否等于null/undefined
。
8、
我们学习了与用户交互的 3 个浏览器的特定函数:
alert
显示信息。
prompt
显示信息要求用户输入文本。点击确定返回文本,点击取消或按下 Esc 键返回 null
。
confirm
显示信息等待用户点击确定或取消。点击确定返回 true
,点击取消或按下 Esc 键返回 false
。
这些方法都是模态的:它们暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除。
上述所有方法共有两个限制:
- 模态窗口的确切位置由浏览器决定。通常在页面中心。
- 窗口的确切外观也取决于浏览器。我们不能修改它。
这就是简单的代价。还有其他一些方法可以显示更漂亮的窗口,并与用户进行更丰富的交互,但如果“花里胡哨”不是非常重要,那使用本节讲的这些方法也挺好。
9、
我们学习了三种循环:
while
—— 每次迭代之前都要检查条件。do..while
—— 每次迭代后都要检查条件。for (;;)
—— 每次迭代之前都要检查条件,可以使用其他设置。
通常使用 while(true)
来构造“无限”循环。这样的循环和其他循环一样,都可以通过 break
指令来终止。
如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 continue
指令。
break/continue
支持循环前的标签。标签是 break/continue
跳出嵌套循环以转到外部的唯一方法。
10、
函数声明方式如下所示:
function
name
(
parameters,
delimited,
by,
comma
)
{
/* code */
}
- 作为参数传递给函数的值,会被复制到函数的局部变量。
- 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
- 函数可以返回值。如果没有返回值,则其返回的结果是
undefined
。
为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。
与不获取参数但将修改外部变量作为副作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。
函数命名:
- 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
- 一个函数是一个行为,所以函数名通常是动词。
- 目前有许多优秀的函数名前缀,如
create…
、show…
、get…
、check…
等等。使用它们来提示函数的作用吧。
函数是脚本的主要构建块。现在我们已经介绍了基本知识,现在我们就可以开始创建和使用函数了。但这只是学习和使用函数的开始。我们将继续学习更多函数的相关知识,更深入地研究它们的先进特征。
11、
- 函数是值。它们可以在代码的任何地方被分配,复制或声明。
- 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。
- 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。
- 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。
- 函数表达式在执行流程到达时创建。
在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通常也会使得代码可读性更高。
12、
对于一行代码的函数来说,箭头函数是相当方便的。它具体有两种:
- 不带花括号:
(...args) => expression
— 右侧是一个表达式:函数计算表达式并返回其结果。 - 带花括号:
(...args) => { body }
— 花括号允许我们在函数中编写多个语句,但是我们需要显式地return
来返回一些内容。
13、
对象是具有一些特殊特性的关联数组。
它们存储属性(键值对),其中:
- 属性的键必须是字符串或者 symbol(通常是字符串)。
- 值可以是任何类型。
我们可以用下面的方法访问属性:
- 点符号:
obj.property
。 - 方括号
obj["property"]
,方括号允许从变量中获取键,例如obj[varWithKey]
。
其他操作:
- 删除属性:
delete obj.prop
。 - 检查是否存在给定键的属性:
"key" in obj
。 - 遍历对象:
for(let key in obj)
循环。
对象是通过引用被赋值或复制的。换句话说,变量存储的不是“对象的值”,而是值的“引用”(内存地址)。所以复制这样的变量或者将其作为函数参数进行传递时,复制的是引用,而不是对象。基于复制的引用(例如添加/删除属性)执行的所有的操作,都是在同一个对象上执行的。
我们可以使用 Object.assign
或者 _.cloneDeep(obj) 进行“真正的复制”(一个克隆)。
我们在这一章学习的叫做“基本对象”,或者就叫对象。
JavaScript 中还有很多其他类型的对象:
Array
用于存储有序数据集合,Date
用于存储时间日期,Error
用于存储错误信息。- ……等等。
它们有着各自特别的特性,我们将在后面学习到。有时候大家会说“数组类型”或“日期类型”,但其实它们并不是自身所属的类型,而是属于一个对象类型即 “object”。它们以不同的方式对 “object” 做了一些扩展。
JavaScript 中的对象非常强大。这里我们只接触了冰山一角。在后面的章节中,我们将频繁使用对象进行编程,并学习更多关于对象的知识。
14、
Symbol
是唯一标识符的基本类型
Symbol 是使用带有可选描述(name)的 Symbol()
调用创建的。
Symbol 总是不同的值,即使它们有相同的名字。如果我们希望同名的 Symbol 相等,那么我们应该使用全局注册表:Symbol.for(key)
返回(如果需要的话则创建)一个以 key
作为名字的全局 Symbol。使用 Symbol.for
多次调用 key
相同的 Symbol 时,返回的就是同一个 Symbol。
Symbol 有两个主要的使用场景:
-
“隐藏” 对象属性。 如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个 Symbol 并使用它作为属性的键。Symbol 属性不会出现在
for..in
中,因此它不会意外地被与其他属性一起处理。并且,它不会被直接访问,因为另一个脚本没有我们的 symbol。因此,该属性将受到保护,防止被意外使用或重写。因此我们可以使用 Symbol 属性“秘密地”将一些东西隐藏到我们需要的对象中,但其他地方看不到它。
-
JavaScript 使用了许多系统 Symbol,这些 Symbol 可以作为
Symbol.*
访问。我们可以使用它们来改变一些内置行为。例如,在本教程的后面部分,我们将使用Symbol.iterator
来进行 迭代 操作,使用Symbol.toPrimitive
来设置 对象原始值的转换 等等。
从技术上说,Symbol 不是 100% 隐藏的。有一个内置方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 Symbol。还有一个名为 Reflect.ownKeys(obj) 的方法可以返回一个对象的 所有 键,包括 Symbol。所以它们并不是真正的隐藏。但是大多数库、内置方法和语法结构都没有使用这些方法。
15、
- 存储在对象属性中的函数被称为“方法”。
- 方法允许对象进行像
object.doSomething()
这样的“操作”。 - 方法可以将对象引用为
this
。
this
的值是在程序运行时得到的。
- 一个函数在声明时,可能就使用了
this
,但是这个this
只有在函数被调用时才会有值。 - 可以在对象之间复制函数。
- 以“方法”的语法调用函数时:
object.method()
,调用过程中的this
值是object
。
请注意箭头函数有些特别:它们没有 this
。在箭头函数内部访问到的 this
都是从外部获取的。
16、
对象到原始值的转换,是由许多期望以原始值作为值的内建函数和操作符自动调用的。
这里有三种类型(hint):
"string"
(对于alert
和其他需要字符串的操作)"number"
(对于数学运算)"default"
(少数操作符)
规范明确描述了哪个操作符使用哪个 hint。很少有操作符“不知道期望什么”并使用 "default"
hint。通常对于内建对象,"default"
hint 的处理方式与 "number"
相同,因此在实践中,最后两个 hint 常常合并在一起。
转换算法是:
- 调用
obj[Symbol.toPrimitive](hint)
如果这个方法存在, - 否则,如果 hint 是
"string"
- 尝试
obj.toString()
和obj.valueOf()
,无论哪个存在。
- 尝试
- 否则,如果 hint 是
"number"
或者"default"
- 尝试
obj.valueOf()
和obj.toString()
,无论哪个存在。
- 尝试
在实践中,为了便于进行日志记录或调试,对于所有能够返回一种“可读性好”的对象的表达形式的转换,只实现以 obj.toString()
作为全能转换的方法就够了。
17、
- 构造函数,或简称构造器,就是常规函数,但大家对于构造函数有个共同的约定,就是其命名首字母要大写。
- 构造函数只能使用
new
来调用。这样的调用意味着在开始时创建了空的this
,并在最后返回填充了值的this
18、
- 除
null
和undefined
以外的原始类型都提供了许多有用的方法。我们将在即将到来的章节中研究这些内容。 - 从形式上讲,这些方法通过临时对象工作,但 JavaScript 引擎可以很好地调整以在内部对齐进行优化,因此调用它们并不需要太高的成本。
19、
写非常大的数字:
- 附加
"e"
来省略 0,e 后面的数字就是零的个数。就像:123e6
是123
后面接 6 个零。 "e"
后面的负数将导致数字除以 1 后面接着给定数量的零。e-6 那是一百万分之一。
对于不同的进制:
- 可以在十六进制(
0x
),八进制(0o
)和二进制(0b
)系统中直接写入数字。 parseInt(str,base)
解析来自任何数字系统的整数,其基数为:2≤base≤36
。num.toString(base)
将数字转换为数字系统中具有给定base
的字符串。
将 12pt
和 100px
等值转换为数字:
- 使用
parseInt / parseFloat
进行软
转换,它从字符串中读取一个数字,然后返回错误发生前可以读取的值。
分数:
- 使用
Math.floor
、Math.ceil
、Math.trunc
、Math.round
或num.toFixed(precision)
截取。 - 请记住,使用分数时会损失精度。
更多的数学函数:
- 需要时请参阅 Math 对象,虽然这个文档非常小,但是它可以满足基础的要求。
20、
数组是一种特殊的对象,适用于存储和管理有序的数据项。
-
声明:
// 方括号 (常见用法)
let
arr=
[
item1,
item2...
]
;
// new Array (极其少见)
let
arr=
new
Array
(
item1,
item2...
)
;
调用
new Array(number)
会创建一个给定长度的数组,但不含有任何项。 -
length
属性是数组的长度,准确地说,它是数组最后一个数字索引值加一。它由数组方法自动调整。 -
如果我们手动缩短
length
,那么数组就会被截断。
我们可以通过下列操作以双端队列的方式使用数组:
push(...items)
在末端添加items
项。pop()
从末端移除并返回该元素。shift()
从前端移除并返回该元素。unshift(...items)
从前端添加items
项。
遍历数组的元素:
for (let i=0; i<arr.length; i++)
— 运行得最快,可兼容旧版本浏览器。for (let item of arr)
— 现代语法,只能访问 items。for (let i in arr)
— 永远不要用这个。
21、
组方法备忘单:
-
添加/删除元素:
push(...items)
— 向尾端添加元素,pop()
— 从尾端提取一个元素,shift()
— 从首端提取一个元素,unshift(...items)
— 向首端添加元素,splice(pos, deleteCount, ...items)
— 从index
开始删除deleteCount
个元素,并在当前位置插入items
。slice(start, end)
— 创建一个新数组,将从位置start
到位置end
(但不包括end
)的元素复制进去。concat(...items)
— 返回一个新数组:复制当前数组的所有元素,并向其中添加items
。如果items
中的任意一项是一个数组,那么就取其元素。
-
搜索元素:
indexOf/lastIndexOf(item, pos)
— 从位置pos
开始搜索item
,搜索到则返回该项的索引,否则返回-1
。includes(value)
— 如果数组有value
,则返回true
,否则返回false
。find/filter(func)
— 通过func
过滤元素,返回使func
返回true
的第一个值/所有值。findIndex
和find
类似,但返回索引而不是值。
-
遍历元素:
forEach(func)
— 对每个元素都调用func
,不返回任何内容。
-
转换数组:
map(func)
— 根据对每个元素调用func
的结果创建一个新数组。sort(func)
— 对数组进行原位(in-place)排序,然后返回它。reverse()
— 原位(in-place)反转数组,然后返回它。split/join
— 将字符串转换为数组并返回。reduce(func, initial)
— 通过对每个元素调用func
计算数组上的单个值,并在调用之间传递中间结果。
-
其他: –
Array.isArray(arr)
检查arr
是否是一个数组。
请注意,sort
,reverse
和 splice
方法修改的是数组本身。
这些是最常用的方法,它们覆盖 99% 的用例。但是还有其他几个:
-
arr.some(fn)/arr.every(fn) 检查数组。
与
map
类似,对数组的每个元素调用函数fn
。如果任何/所有结果为true
,则返回true
,否则返回false
。 -
arr.fill(value, start, end) — 从索引
start
到end
,用重复的value
填充数组。 -
arr.copyWithin(target, start, end) — 将从位置
start
到end
的所有元素复制到 自身 的target
位置(覆盖现有元素)。
有关完整列表,请参阅 手册。
乍看起来,似乎有很多方法,很难记住。但实际上这比看起来要容易得多。
浏览这个备忘单,以了解这些方法。然后解决本章中的习题来进行练习,以便让你有数组方法的使用经验。
然后,每当你需要对数组进行某些操作,而又不知道怎么做的时候,请回到这儿,查看这个备忘单,然后找到正确的方法。示例将帮助你正确编写它。用不了多久,你就自然而然地记住这些方法了,根本不需要你死记硬背。
22、可迭代对象没看懂