JavaScript基础知识点回顾
date
Jun 7, 2023
slug
JavaScript基础知识点回顾
status
Published
tags
IT
summary
JavaScript基础知识点回顾
type
Post
箭头函数
模块
每个模块都是一个文件。例如,考虑以下两个包含模块的文件:
file-tools.js
中的模块导出其功能isTextFilePath()
:main.js
中的模块导入整个模块path
和函数isTextFilePath()
:法定变量和属性名称
变量名称和属性名称的语法类别称为标识符。
标识符允许具有以下字符:
- Unicode 字母:
A
-Z
,a
-z
(等)
$
,_
- Unicode 数字:
0
-9
(等)
- 变量名不能以数字开头
有些单词在 JavaScript 中有特殊含义,称为保留字。例如:
if
,true
,const
。保留字不能用作变量名:
连接类型
- 驼峰:
threeConcatenatedWords
- 下划线(也称蛇形):
three_concatenated_words
- 分隔线(也称串形):
three-concatenated-words
命名大写
通常,JavaScript 使用驼峰大小写,但常量除外。
小写:
- 函数,变量:
myFunction
- 方法:
obj.myMethod
- CSS:
- CSS 实体:
special-class
- 对应的 JavaScript 变量:
specialClass
大写:
- 类名:
MyClass
- 常数:
MY_CONSTANT
- 常量也常用骆驼写成:
myConstant
分号
6.6.1. 分号的经验法则
每个语句都以分号结束。
例外:以块结尾的语句。
以下情况有点棘手:
整个
const
声明(一个语句)以分号结尾,但在其中,有一个箭头函数表达式。那就是:声明本身并不是以花括号结尾;它是嵌入式箭头函数表达式。这就是为什么最后会有一个分号的原因。6.6.2. 分号:控制语句
控制语句的主体本身就是一个声明。例如,这是
while
循环的语法:正文可以是一行语句:
但代码块也是声明,因此也是控制声明的合法主体:
如果你想让一个循环有一个空的主体,那么你的首选便是一个空语句(它只是一个分号):
你的第二个选择是一个空语句块:
js 中 右大括号后何时添加分号:
1、普通语句,变量赋值 要加
2、函数声明 ,块,不用加
ASI
ASI 规则:
Javascript将不对上一行句尾添加分号:"("、"["、"/"、"+"和"-"。
自动添加:
虽然我建议总是写分号,但大多数都是 JavaScript 中的可选项。使这成为可能的机制称为自动分号插入(ASI)。在某种程度上,它可以纠正语法错误。
ASI 的工作原理如下。语句解析会直到出现以下情况:
- 分号
- 行终止符后跟非法标记
换句话说,ASI 可以看作在换行符处插入分号。接下来的小节将介绍 ASI 的陷阱。
6.7.1. ASI 意外触发
关于 ASI 的好消息是——如果你不依赖它并且总是写分号——你只需要注意一个陷阱。这是 JavaScript 禁止在一些标记之后的换行符。如果插入换行符,也会插入分号。
最实际相关的标记是
return
。例如,考虑以下代码:此代码解析为:
也就是说,一个空的 return 语句,后跟一个代码块,后跟一个空语句。
为什么 JavaScript 会这样做?它可以防止在
return
之后意外返回一行中的值。6.7.2. ASI 意外没有触发
在某些情况下,当您认为 ASI 应该触发时,ASI 并没有触发。对于那些不喜欢分号的人来说,这会使生活更加复杂,因为他们需要意识到这些情况。以下是三个例子。还有更多。
例 1:非预期的函数调用。
解析为:
例 2:意外分裂。
解析为:
例 3:非预期的属性访问。
被执行为:
保留字
从技术上讲,这些单词不是保留的,但你也应该避免使用它们,因为它们实际上是关键字:
Infinity NaN undefined async
您也不应将全局变量的名称(
String
,Math
等)用于您自己的变量和参数。保留字不能是变量名,但它们可以是属性名。
所有 JavaScript 关键字都是保留字:
await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return static super switch this throw try typeof var void while with yield
以下标记也是关键字,但目前未在该语言中使用:
enum implements package protected interface private public
以下值是保留字:
true false null
语句与表达式
语句
语句 是一段可以执行并执行某种操作的代码。例如,
if
是一段语句:语句的另一个例子:函数声明。
表达式
表达式是可以评估以产生值的一段代码。例如,括号之间的代码是一个表达式:
括号之间使用的运算符
_?_:_
称为三元运算符。它是if
语句的表达式版本。让我们看一下表达式的更多例子。我们输入表达式,REPL 为我们评估它们:
什么是允许的
JavaScript 源代码中的当前位置决定了您可以使用哪种语法结构:
- 函数体必须是一系列语句:
- 函数调用或方法调用的参数必须是表达式:
但是,表达式可以用作语句。然后将它们称为表达式语句。相反的情况并非如此:当上下文需要表达式时,你便不能使用语句。
以下代码演示了某个表达式
bar()
可以是表达式还是语句——它取决于上下文:语法模糊
JavaScript 有几种语法歧义的编程结构:相同的语法被不同地解释,这取决于它是在语句上下文中还是在表达式上下文中使用。本节探讨了这一现象及其引发的陷阱。
6.5.1. 相同的语法:函数声明和函数表达式
函数声明是一个声明:
函数表达式是一个表达式(
=
右侧的):6.5.2. 相同的语法:object literal 和 block
在下面的代码中,
{}
是 对象字面值:一个创建空对象的表达式。这是一个空代码块(声明):
6.5.3. 消除歧义
歧义只是语句上下文中的一个问题:如果 JavaScript 解析器遇到模糊语法,它不知道它是简单语句还是表达式语句。例如:
- 如果语句以
function
开头:它是函数声明还是函数表达式?
- 如果语句以
{
开头:它是对象字面值还是代码块?
为了解决歧义,以
function
或{
开头的语句永远不会被解释为表达式。如果希望表达式语句以这些标记中的任何一个开头,则必须将其包装在括号中:在这段代码中:
- 我们首先通过函数表达式创建一个函数:
- 然后我们调用该函数:
('abc')
#1 只被解释为表达式,因为我们将它包装在括号中。如果我们没有,我们会得到一个语法错误,因为 JavaScript 需要一个函数声明,之后还会警告缺少的函数名称。此外,你不能在函数声明后立即进行函数调用。
console
打印多个值
第一中形式在控制台上打印(文本)值:
打印替换的字符串
你可以用于替换的一些指令:
%s
将相应的值转换为字符串输出。
%o
将被替换的对象转为字符串形式输出。
%j
将被替换的值转换为 JSON 字符串输出。
%%
插入一个%
。
JSON.stringify()
偶尔用于打印嵌套对象:输出:
const和循环
你可以将
const
与for-of
循环一起使用,每次迭代都会有一个新的绑定被创建:在普通的
for
循环中,必须使用let
,however:静态的与动态的
"暂时性死区”:避免访问未声明的变量
一些可能的方法是:
- 该变量被解析在当前作用域的作用域范围内。
- 如果读取变量,你会得到
undefined
。你也可以给变量赋值。 (这就是var
的工作原理。)
- 抛出错误。
选择 3
在进入变量的作用域到变量的声明这段时间“暂时性死区”(TDZ):
- 在此期间,变量被认为是未初始化的(就好像它是一个特殊值)。
- 如果访问未声明的变量,则会得到
ReferenceError
。
- 到变量声明后,变量获得初始值(通过赋值符号指定)或
undefined
- 如果没有赋初值。
以下代码表明了了“暂时性死区”(TDZ):
接下来这个例子说明了“暂时性死区”是“暂时的”(与调用时间有关):
即使
func()
位于myVar
声明之前并使用该变量,我们也可以调用func()
。但是我们必须等到myVar
的暂时死区结束。多个全局作用域
全局变量
如果一个变量声明在顶级作用域,那么它就是全局变量。每个内层作用域都可以访问全局变量。在JavaScript中,会有多个全局作用域(图 5 ):
- 最外层的全局作用域是特殊的:它的变量可以通过对象(全局对象)的属性所被访问。全局对象在浏览器环境中被称为window和self。此作用域的变量通过以下方式创建的:
- 作为全局对象的属性
var
和function
在脚本的最上面声明的变量(script被浏览器支持。它们是简单的代码片段以及模块的前身。有关详细信息,请参阅模块的章节。)
- 嵌套在这个作用域内是脚本的全局作用域。此作用域中的变量由
let
,const
和class
在脚本的最上面声明。
- 嵌套在该作用域的是模块的作用域。每个模块都有自己的全局作用域。变量在该作用域被最上面的模块声明。
Figure 5: JavaScript有多个全局作用域。
闭包
绑定变量与自由变量:
- 绑定变量: 在作用域内声明。它们是参数或局部变量。
- 自由变量: 在作用域外声明。它们也被称为非局部变量。
闭包是一个函数加上与“出生地”存在的变量的连接。
维持这种连接有什么意义?它让函数自由变量的值可被访问。例如:
funcFactory
返回一个闭包赋值给func
。当func在A行被调用时,它仍然能访问自由变量的值,因为func跟变量有关联,(即使不在它的作用域)。闭包的内在矛盾是运行时的环境和定义时的作用域之间的矛盾。那么把内部环境中需要的变量,打包交给闭包函数,它就可以随时访问这些变量了。(变量注册在堆上,在返回之前,把连接关系打包交给函数)
图片来源于网络
接下来我们用通俗的语言来描述下给 fun2 赋值时的执行过程:
- 先执行 fun1() 函数,内部的 inner() 函数作为返回值返回给调用者。这时,程序能访问两层作用域,最近一层是 fun1(),里面有变量 b;外层还有一层,里面有全局变量 a。这时是把环境变量打包的最后的机会,否则退出 fun1() 函数以后,变量 b 就消失了。
- 然后把内部函数连同打包好的环境变量的值,创建一个 FunctionObject 对象,作为 fun1() 的返回值,给到调用者。
- 给 fun2 这个变量赋值。
- 调用 fun2() 函数。函数执行时,有一个私有的闭包环境可以访问 b 的值,这个环境就是第二步所创建的 FunctionObject 对象。
这样,就实现了闭包的功能。
在这个过程中,我们要提前记录下 inner() 函数都引用了哪些外部变量,以便对这些变量打包。这是在对程序做语义分析时完成的,实现代码可以参考:
下面是代码是把环境变量打包进闭包中的代码片段,它是在当前的栈里获取数据的:
我们经常提到函数是一等公民,也就是要能把函数像普通数值一样赋值给变量,可以作为参数传递给其他函数,可以作为函数的返回值。上面的例子中我们正是把函数作为了返回值,正是体现了这一点。
声明变量的方法
Table 1: 下面是所有声明变量的方法在JavaScript中。
eval 延迟解析
值的类型
JavaScript 的类型层次结构
图 6: JavaScript 类型的部分层次结构。缺少的是错误类,与基元类型相关的类等等。此图暗示了并非所有的对象都是
Object
的实例。图 6 显示了 JavaScript 的类型层次结构。我们从该图中学到了什么?
- JavaScript 区分两种值:原始值和对象。我们很快就会看到有什么区别。
- 该图区分了类
Object
的对象和实例。Object
的每个实例也是一个对象,但反之亦然。但是,实际上你在实践中遇到的所有对象都是Object
的实例。例如,通过对象字面值创建的对象。关于该主题的更多细节在26.4.3.4 对象并非Object
的实例中进行解释。
undefined
:唯一元素undefined
。
null
:唯一元素null
。
boolean
:包含false
和true
元素。
number
:所有数字的类型(例如123
,3.141
)。
string
:所有字符串的类型(例如'abc'
)。
symbol
:所有符号的类型(例如Symbol('My Symbol')
)。
object
:所有对象的类型(与Object
不同,类Object
及其子类的所有实例的类型)。
原始值与对象
规范对值进行了重要区分:
- 原始值是
undefined
,null
,boolean
,number
,string
,symbol
类型的元素。
- 所有其他值都是对象。
与 Java 相比(启发了 JavaScript 语言),原始值不是二等公民。它们和对象之间的区别更加微妙。简而言之,它是:
- 原始值:是 JavaScript 中的原子数据块。
- 它们是值传递的:当原始值分配给变量或传递给函数时,它们的内容被复制。
- 它们按值进行比较:比较两个原始值时,比较它们的内容。
- 对象:是复合数据。
- 它们是通过标识(我的术语)传递:当对象被分配给变量或传递给函数时,它们的标识(想一下指针)被复制。
- 它们是通过标识(我的术语)进行比较的:当比较两个对象时,他们的标识进行比较。
除此之外,原始值和对象非常相似:它们都具有属性(键值条目),并且可以在相同的位置使用。
接下来,我们将更深入地研究原始值和对象。
原始值(基本数据类型)
1、原始值不可改变,不能被添加或者删除属性
2、按值传递
基元是值传递的:变量(包括参数)存储基元的内容。将原始值分配给变量或将其作为参数传递给函数时,会复制其内容。
3、按值比较
基元按值进行比较:当比较两个原始值时,我们比较它们的内容。
typeof 和 instanceof
经验法则:typeof用于原始值,instanceof用于对象
他们有什么不同?
typeof
区分规范的 7 种类型(减去一个遗漏,加上一个补充)。
instanceof
测试哪个类创建了给定值。
表 2:
typeof
操作符的结果集表 2 列出
typeof
的所有结果。它们大致对应于语言规范的 7 种类型。唉,有两个不同之处,它们是语言怪癖:typeof null
返回'object'
而不是'null'
。那是一个错误。不幸的是,它无法修复。 TC39 尝试这样做,但它在网络上打破了太多代码。
- 函数的
typeof
应该是'object'
(函数是对象)。为功能引入单独的类别令人困惑。
instanceof
该运算符回答了问题:是否有一个类
C
创建了值x
?例如:
原始值不是任何实例:
类和构造函数
JavaScript 的对象原始工厂是构造函数:如果通过
new
操作符调用它们,则返回自身的“实例”的普通函数。ES6 引入了构造函数最好的语法,类。
在本书中,我可以互换地使用术语构造函数和类。
类可以看作是将规范的单一类型
object
划分为子类型——它们给出了比规范中有限的 7 种类型更多的类型。每个类都是由它创建的对象的类型。与基本类型关联的构造函数
每个基本类型(
undefined
和null
的规范内部类型除外)都有一个关联的构造函数(想一下类):- 构造函数
Boolean
与布尔值相关联。
- 构造函数
Number
与数字相关联。
- 构造函数
String
与字符串相关联。
- 构造函数
Symbol
与符号相关联。
每个函数都扮演着几个角色。例如,
Number
:- 可以将其用作函数并将值转换为数字:
Number.prototype
提供数字的属性。例如,方法.toString()
:
Number
是数字工具函数的命名空间/容器对象。例如:
- 最后,你还可以将
Number
用作类并创建数字对象。这些对象与实数不同,应该避免。
包装原始值
与基本类型相关的构造函数也称为包装类型,因为它们提供了将原始值转换为对象的规范方法。在此过程中,原始值被“包装”在对象中。
包装在实践中很少有用,但它在语言规范内部使用,以提供原语属性。
在类型之间转换
- 显式转换:通过
String()
等功能。
- 强制(自动转换):当操作接收到无法使用的操作数/参数时发生。
1、显式转换
2、强制类型转换
对于许多操作,如果操作数/参数的类型不匹配,JavaScript 会自动转换它们。这种自动转换称为强制。
例如,乘法运算符将其操作数强制转换为数字:
许多内置函数也强制执行。例如,
parseInt()
将其参数强制转换为字符串(解析在第一个不是数字的字符处停止):运算符
- 运算符将其操作数强制转换为适当的类型
- 大多数运算符只处理原始值
加法运算符(+)
加法运算符在 JavaScript 中如下工作:
- 首先,它将两个操作数转换为原始值。然后它切换到以下两种模式之一:
- 字符串模式:如果两个原始值中的一个是字符串,则它将另一个转换为字符串,连接两个字符串并返回结果。
- 数字模式:否则,它将两个操作数转换为数字,将它们相加并返回结果。
字符串模式让我们使用
+
来组合字符串:数字模式意味着如果操作数都不是字符串(或字符串对象),那么所有内容都被强制转换为数字:
Number(true)
是1
。赋值运算符
1、普通赋值运算符
普通赋值运算符用于更改存储位置:
变量声明中的初始值设定也可以视为赋值形式:
2、 复合赋值运算符
+= -= *= /= %= **=
<<= >>= >>>= &= ^= |=
逗号运算符
对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
最常用的是for 循环中提供多个参数:
void 运算符
对给定的表达式进行求值,然后返回 undefined。
常用1: 使用 void 0 获得undefined
常用2: 在使用立即执行的函数表达式时,可以利用 void 运算符让 JavaScript 引擎把一个function关键字识别成函数表达式而不是函数声明(语句)。
常用3:在箭头函数中避免泄漏
相等(==、===、Object.is())
1、== 当比较中有操作数是原始的,对象会被强制转换为原始值
==认为undefined和null相等:
2、===
类型相同才会相等
===运算符不认为undefined和null相等:
比较字符串和数字时
3、使用 Object.is() 更加严谨 ,主要在于 NaN 和 NaN 之间的比较,会被认为相等
LHS 和 RHS
Left-hand-side Expressions:左手表达式 在 复制表达式左侧 ,找目标
Right-hand-side Expressions:右手表达式 在 复制表达式右侧 ,找源头
null 和 undefined
undefined
表示“未初始化”(例如变量)或“不存在”(例如对象的属性)。
null
表示“故意没有任何对象值”
程序员可以做出以下区分:
undefined
是语言使用的非值(当某些内容未初始化时)。
undefined和null没有属性
- 具有对象类型的变量用
null
初始化。
- 每个原始类型都有自己的初始化值。例如,
int
变量用0
初始化。
在 JavaScript 中,每个变量都可以包含对象值和原始值。因此,如果
null
表示“不是对象”,JavaScript 还需要一个初始化值,这意味着“既不是对象也不是原始值”。初始化值为undefined
。undefined
undefined的位置
未初始化的变量
myVar
:未提供的参数
x
:缺失属性
.unknownProp
:如果没有通过
return
运算符显式指定函数的结果,JavaScript 会为您返回undefined
:变量提升后,还没运行到原来定义的表达式之前,是undefined
null
对象的原型是一个对象,或者在原型链的末尾,是
null
。 Object.prototype
没有原型:如果将正则表达式(例如
/a/
)与字符串(例如'x'
)匹配,则可以获得具有匹配数据的对象(如果匹配成功)或null
(如果匹配失败):