2022-04-24 JavaScript基础学习 - 1
前情提要
此类文集是根据《JavaScript高级程序设计(第四版)》为参考进行JavaScript
的基础补足。
昨天和朋友聊了聊,发现自身的JavaScript
基础很薄弱,对于很多JavaScript
的方法只是知道怎么用而不知道其中原理,比如最简单数组中的push
等。
第一章 什么是 JavaScript
前言
《JavaScript高级程序设计》适用于:
- 有经验的开发者,熟悉面向对象,比如
java
后端想学习前端 - web应用程序开发者,希望增强自身网站或web应用程序的易用性
- 初级
JavaScript
开发者,希望能更好地理解这门语言
而我则属于第三者
起源
1995年,JavaScript
问世,一开始它的期望是用作客户端验证,如表单必填项的确认,在那个年代,这样的新功能是新奇而又让人期待的。
而后,JavaScript
成为了主流,它的功能也不只局限于表单验证,而多了很多如闭包,class
等新功能
1995年,网景公司在 Netscape Navigator 2 发布了JavaScript
(原叫做LiveScript
),初发布很成功,也让网景公司占据了市场上的主导地位,后来微软也决心向 IE 投入更多资源,但为了避免于网景公司发生许可纠纷,微软将自己的JavaScript
叫做JScript
,这时候,市面上有了两种JavaScript
,一个是网景公司的,一个是微软的。
所以最后,JavaScript
也走向标准化的征程——
1997年,JavaScript 1.1
版本作为提案提交给欧洲计算机制造商协会(ECMA),由第39技术委员会承担这项任务,最后打造出的ECMAScript
,直到1998年以后,各家浏览器都将ECMAScript
作为自身JavaScript
实现的依据。
JavaScript 的实现
完整的JavaScript
包括ECMAScript
、DOM
、BOM
。
而ECMAScript
则定义的是:
- 语法
- 类型
- 语句
- 关键字
- 保留字
- 操作符
- 全局对象
es版本
ES5,于2009年12月3日正式 发布。第5版致力于厘清第3版存在的歧义,也增加了新功能。新功能包括原生的解析和序列化JSON数据的JSON对象、方便继承和高 级属性定义的方法,以及新的增强ECMAScript引擎解释和执行代 码能力的严格模式。第5版在2011年6月发布了一个维护性修订版, 这个修订版只更正了规范中的错误,并未增加任何新的语言或库特性。
现在最常说的 ES6 于2015年6月发布。这一版包含了大概这个规范有史以来最重要的 一批增强特性。ES6正式支持了类、模块、迭代器、生成器、箭头 函数、期约、反射、代理和众多新的数据类型。
ES7,于2016年6月发布。这次 修订只包含少量语法层面的增强,如Array.prototype.includes 和 指数操作符。
ES8,完成于2017年6月。这一 版主要增加了异步函数(async/await)、SharedArrayBuffer 及 Atomics API,以及 Object.values()/Object.entries()/Object.getOwnPropertyDescriptors() 和字符串填充方法,另外明确支持对象字面量最后的逗号。
ES9,发布于2018年6月。这次修订包括异步迭代、剩余和扩展属性、一组新的正则表达式特性、Promise finally(),以及模板字面量修订。
ES10,发布于2019年6月。这 次修订增加了 Array.prototype.flat()/flatMap()、String.prototype.trimStart() 方法,以及 Symbol.prototype.description 属性,明确定义了 Function.prototype.toString() 的返回值并固定了 Array.prototype.sort() 的顺序。另外,这次修订解决了与 JSON 字符串兼容的问题,并定义了 catch 子句的可选绑定。
DOM
dom,文档对象模型(document object module)是一个应用编程接口(API),用于在 HTML 中使用扩展的 XML。DOM 将整个页面抽象为一组分层节点。HTML 或 XML 页面的每个组成部分都是一种节点,包含不同的数据。
DOM 通过创建表示文档的数,让开发者可以随心所欲地控制网页中的内容和结构,使用 DOM API,可以轻松地删除、添加、替换、修改节点。
为什么 DOM 是必需的?
使用 DOM 可以在不刷新网页的情况下修改网页的内容(由于一开始网景公司和微软使用了不同的设计思路开发 DHTML,导致可能会变成面向浏览器变成,因此 W3C 制定了 DOM 标准)
BOM
IE3 和 Netscape Navigator 3 提供的浏览器对象模型(Browser object module),而在 HTMl5 中,对于 BOM 有了比较统一的规定。
第二章 HTML 中的 JavaScript
script 元素
最早 script 元素是由网景公司创造出来的,是为了在 HTML 中插入 JavaScript。后来此标签正式归入 HTML 规范,并且此元素具有以下 8 大属性:
- async(可选):表示应该立即下载脚本,但不能阻止其他页面动作(此概念就是异步),只对外部脚本文件有效。
属性值: async - charset(可选):规定在外部脚本文件中使用的字符编码。
属性值:character_encoding(字符编码,如 UTF-8) - crossorigin(可选):配置相关请求的 CORS(跨域资源共享)设置。默认不使用 CORS。
属性值:anonymous(配置文件请求不 必设置凭据标志)
属性值:use-credentials(设置凭据标志, 意味着出站请求会包含凭据) - defer(可选):表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。
属性值:defer - integrity(可选):允许比对接收到的资源和指定的加密签名以验 证子资源完整性(SRI,Subresource Integrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。
- language(废弃):最初用于表示代码块中的脚本语言,现在使用
type
- src:(可选):表示包含要执行的代码的外部文件。
- type:(可选):代替language,表示代码块中脚本语言的内容类型 (也称MIME类型)。按照惯例,这个值始终都是
"text/javascript"
。
属性值:module(则代码会被当成ES6模块,而且只有这时候代码中才能出现import
和export
)
注意事项
使用 script
元素的注意事项,在该元素中,不能直接使用</script>
字符串,这样 html 会将其识别为<script>
的结束标签,而导致报错,若需要使用,则应该使用\
转义字符,如<\/script>
使用外部资源文件时,如<script src="../xxx.js"></script>
,不要在其中写 JavaScript 代码,浏览器不会解析元素内部的 JavaScript 代码,并且<script src="../xxx.js" />
的写法只在 XHTML 文件中生效。
<script>
元素的一个最为强大、同时也备受争议的特性是,它可以包含来自外部域的JavaScript文件。跟<img>
元素很像,<script>
元素的src
属性可以是一个完整的 URL,而且这个 URL 指向的资源可以跟包含它的 HTML 页面不在同一个域中。因此,也就是我们常说的 jsonp
跨域(缺陷在于,只能发送get
请求)。
不管包含的是什么代码,浏览器都会按照<script>
在页面中出现的顺序依次解释它们,前提是它们没有使用defer
和async
属性。(也就是延后和异步)后一个<script>
必须在前一个解析完后才能开始解析。
在页面中,如果将<script>
放在<head>
中,那么浏览器会解析完<script>
后才会解析<body>
中的内容。而将<script>
放到<body>
尾部,那么会先加载页面内容,再加载交互效果。且<head>
中执行的<script>
若有使用到页面dom
的,将会获取为undefined
或null
。当然,在<script>
中加入defer
属性,也是可以的,但defer
只对外部 js 文件生效,且兼容性需求较高,因此还是推荐在<body>
底部使用<script>
也可以使用async
属性达到defer
的效果,但是<script>
的执行顺序将不再变得可控。
第三章 语言基础
- ECMAScript 很大程度上借鉴了C语言和其他类C语言,在 ECMAScript 中区分大小写,如
typeof
是一个关键名,不能被用作变量名定义。 - 标识符(变量)的首字母必须是字母、下划线(
_
)、美元符号($
),其他可以是字母、数字、下划线、美元符号;多意义标识符采用小驼峰命名法,如helloWorld
- 在 ECMAScript 中,使用
//
为单行注释,/**/
可以多行注释 - 在代码开头或函数开头,可以使用
use strict
标注为严格模式,在严格模式中,会严格检测语法 - ECMAScript 中语句以
;
结尾,类似于if
、switch
等应以{ }
包裹起来
关键字
在 ECMAScript 中,es6定义的关键字如下:
break
do
in
typeof
case
instanceof
else
var
catch
export
new
void
extends
class
return
while
const
finally
super
with
continue
for
switch
yield
debugger
function
this
default
throw
if
delete
try
import
以及未来保留的词汇:
enum
implements
packge
public
interface
protected
static
let
private
await
在这里面,我熟知且常用的有:
switch
break
case
多条件判断语句 终止循环/终止 Switch Switch 中的选择分支
in
for in 循环,in 指向属性名;单独使用,判断属性是否存在于对象中,返回 Boolean
typeof
获取变量类型
instanceof
判断该值是否存在于对象的原型链上
if
else
判断
var
声明变量
try
catch
捕获错误
export
import
default
导入导出,默认导出
new
操作符
class
extends
static
super
类,类的继承,静态,类继承方法的引入
return
函数返回
while
for
continue
循环,跳过循环
const
let
声明变量,不同于 var
function
定义函数
this
对象指向,根据使用场景,this
的指向不同
throw
抛出异常
delete
对象删除属性
await
配合异步函数使用的关键字
变量
定义变量,使用var
let
const
关键字,注意let
和const
只能在 es6 以及往后的版本使用。在 ts 出现之前,ECMAScript 中定义的变量可以储存任何类型的值,即混用。
var message
第一次定义变量时,未赋值,不初始化,此时message
默认会保存一个特殊值undefined
var message = 'hi'
第一次定义变量时,初始化变量,赋值'hi'
,但并不会改变该变量的类型,可以在后续的使用中将message
的值改为true
,这在 ECMAScript 中是可以实现的,但不推荐使用。
var
- var 声明作用域
var 声明的变量生效于函数作用域,在函数内部使用 var,会将此变量声明为该函数的私有变量,而外部是访问不了的;如果在函数中声明变量,且省略 var 关键字,那么此变量为全局变量,调用该函数时,进行了一个为其赋值的操作。
在 ECMAScript 中,此行为是可以实现的,但是不推荐使用,这样会导致全局变量变得难以维护,且,在严格模式下,会报错。 - var 声明提升
使用 var 声明变量,会默认在代码段的开头先声明一次不初始化的变量名,再在之后的代码中进行赋值,所以你在var message = 'hi'
前调用变量message
不会报错,但同样的,你也拿不到想要赋值给message
的值'hi'
。
使用 var 声明变量,即便你同一个变量名声明多次,也不会报错。
let
- let 声明作用域
let 是块级作用域声明,即{}
为一个作用域,let 不允许{}
外部的方法使用{}
内部的变量;let 不允许声明冗余;let 不允许在初始化之前访问该变量,即俗称的”暂时性死区“,原因在于 let 没有声明提升。(tips:即便是两个<script>
元素,也算作是一个作用域哦) - let 全局声明
let 在全局下声明的变量不会成为 window 的属性
const
- const 声明
const 的使用规则和 let 大体一样,不同点在于 const 定义变量时必须同时初始化变量值
根据堆和栈的设定,const 定义的变量不能被修改,但这些不能被修改的变量一版指向栈或者,如对象的地址,而对象中的内容是可以被修改的。
使用
- 不使用 var
- 优先使用 const,let 次之
数据类型
ECMAScript 目前(截止到2022年,而不是红宝书第四版)有 7 种基本数据类型:string
number
boolean
null
undefined
symbol
bigint
;1种引用数据类型:object
typeof 操作符
使用 typeof 操作符确认任意变量的数据类型
严格来讲,function 在 ECMAScript 中被认为是对象,并不代表一种数据类型,但函数也有自身特殊的属性,因此需要通过 typeof 操作符来区分函数和其他对象。
undefined 类型
undefined 类型只有一个值,当使用 let 或 var 声明却没有初始化时,变量的值默认就是 undefined
不要为了刻意去定义 undefined 为初始化 undefined
null 类型
从严格意义上来说,null 表示一个空对象指针,可以初始化时定义 null 来表示该变量即将要接收一个对象的值;
undefined 由 null 派生而来,因此在表面上是相等的;
Boolean 类型
boolean 类型只有两个字面量:true
和 false
(字面量区分大小写,因此只有纯小写的才能被识别为 boolean 变量)
虽然布尔值只有两个,但所有其他ECMAScript类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的 Boolean() 转型函数
number 类型
Number类型使用 IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式;
在严格模式下,请使用0o
表示8进制,0x
表示16进制;
在 ECMAScript 中,Number.MIN_VALUE 表示最小的数字,这个值通常是5e-324
,Number.MAX_VALUE 表示最大的数字,这个值通常是1.797 693 134 862 315 7e+308
。如果某个值超出了这两个阈值,将会转化为Infinity
(无穷),而Infinity
将无法参与计算;
NaN
NaN 是一个特殊的数值,用于表示本来要返回数值的操作失败了(而不是抛出错误);
ECMAScript 提供了 isNaN()
来判断该数值是否是非数字。
数字转型
-
Number()
Number()
是转型函数,基于true => 1
false => 2
(boolean)null => 0
undefined => NaN
(特殊)number => number
(数字本身)"" => 0
"12" => 12 / "012" => 12 / "-12" => -12
"0xf" => 16进制数字 / "0o7" => 8进制数字
"ab12" => NaN
(字符串) 的规则来转化内容;
除此之外,对象转数字,首先调用valueOf()
的结果看是否能转化为数字,如果结果是NaN
,再调用toString()
,按照字符串的转化规则来转化。 -
parseInt()
想要得到整数时优先使用parseInt()
;
parseInt()
会自动忽略字符串前面的空白字符,如果第一个字符不是数字、加减号,parseInt()
将立即返回NaN
;
parseInt()
会自动处理如0x
0o
开头的十六进制和八进制数字,在非严格模式下07
或许也会被处理为八进制;
浮点数也会被处理为整数;
parseInt()
第二个参数将告诉这个函数将以什么进制来解析这个数字,支持范围为0-32
-
parseFloat()
parseFloat()
除了空白字符外,还会默认处理数字前面的0
;
parseFloat()
只处理到第一个有效小数点;
parseFloat()
只处理十进制;
string类型
String (字符串)数据类型表示零或多个16位 Unicode 字符序列。字符串可以使用双引号(")、单引号(')或反引号(``)标示,但是标示符号要首位对应。
String 中的字面量
\n 换行
\t 制表
\b 退格
\r 回车
\f 换页
\\ 反斜杠(\)
\' 单引号(')
\" 双引号(")
` 反引号(`) \xnn 以十六进制编码nn表示的字符
\unnnn 以十六进制编码nnnn表示的Unicode字符
String 字符串的特性
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。
转换 String 字符串
toString()
String()
+ ""
这3种方式都可以将目标转换为字符串,注意undefined
和null
转化后仍为该值的字面量。
原始字符串
使用 String.raw()
可以得到未转义的字符串字面量
模板字符串拼接函数
let a = 6
let b = 9
function stringView(strings, num1, num2, num3) {
console.log(strings)
console.log(num1)
console.log(num2)
console.log(num3)
}
stringView`${a} + ${b} = ${ a + b }`
// ["", " + ", " = ", ""] => strings
// 6 => num1 => a
// 9 => num2 => b
// 15 => num3 => a+b
symbol
Symbol(符号)是ECMAScript 6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
最重要的是,Symbol()函数不能用作构造函数,与new关键字一起使用。这样做是为了避免创建符号包装对象。
使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
为此,需要使用Symbol.for()
方法
全局符号和普通符号不同,即便内容相同,需要使用Symbol.keyFor()
来查询对应的全局符号,如果查询未定义的全局符号,则也会显示Symbol(undefined)
;
并且全局符号是可以重复调用的,当你注册一个已有的全局符号时,该Symbol.for()
会自动寻找已有的全局符号,并赋值。所以重复调用的两个变量,是相等的。
object类型
ECMAScript中的对象其实就是一组数据和功能的集合。
每个 object 都有如下属性:
constructor
:用于创建当前对象的函数。
hasOwnProperty
:用于判断当前对象实例(不是原型)上是否存在给定的属性。
isPrototypeOf
:于判断当前对象是否为另一个对象的原型。
propertyIsEnumerable
:用于判断给定的属性是否 可以使用for-in语句枚举。
toLocaleString
:返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
toString
:返回对象的字符串表示。
valueOf
:返回对象对应的字符串、数值或布尔值表示。通常和toString
返回值相同。