JavaScript-学习日记
JavaScript 简介
什么是JavaScript ?
JavaScript 最初被创建的目的是“使网页更生动”。这种编程语言写出来的程序被称为 脚本 。它们可以被直接写在网页的HTML中,在页面加载的时候自动执行。
脚本被以纯文本的形式提供和执行。它们不需要特殊的准备或编译即可运行
JavaScript有自己的语言规范ECMAScript。
JavaScript 不仅可以在浏览器中执行,也可以在服务端执行,甚至可以在任意搭载了 JavaScript 引擎 的设备中执行。
浏览器中嵌入了 JavaScript 引擎,有时也称作“JavaScript 虚拟机”。
引擎是如何工作的?
引擎很复杂,但是基本原理很简单。
- 引擎(如果是浏览器,则引擎被嵌入在其中)读取(“解析”)脚
本。 - 然后,引擎将脚本转化(“编译”)为机器语言。
- 然后,机器代码快速地执行。
引擎会对流程中的每个阶段都进行优化。它甚至可以在编译的脚本运行时监视它,分析流经该脚本的数据,并根据获得的信息进一步优化机器代码。
浏览器中的JavaScript 能做什么?
现代的 JavaScript 是一种“安全的”编程语言。它不提供对内存或 CPU 的底层访问,因为它最初是为浏览器创建的,不需要这些功能。
JavaScript 的能力很大程度上取决于它运行的环境。例如, Node.js 支持允许 JavaScript 读取/写入任意文件,执行网络请求等的函数。
浏览器中的 JavaScript 可以做与网页操作、用户交互和 Web 服务器相关的所有事情。
- 在网页中添加新的 HTML,修改网页已有内容和网页的样式。
- 响应用户的行为,响应鼠标的点击,指针的移动,按键的按动。
- 向远程服务器发送网络请求,下载和上传文件(所谓的 AJAX 和 COMET 技术)。
- 获取或设置 cookie,向访问者提出问题或发送消息。
- 记住客户端的数据(“本地存储”)。
浏览器中的 JavaScript 不能做什么?
- 网页中的 JavaScript 不能读、写、复制和执行硬盘上的任意文件。它没有直接访问操作系统的功能。
- 现代浏览器允许JavaScript做一些文件相关的操作,但是这个操作是被严格限制的。和许多相机、麦克风等IO设备的交互方式类似,都需要获得用户的明确许可。
- 不同的标签页/窗口之间不能互相通信。
- 即便是一个标签页通过JavaScript打开的另外一个标签页。如果这两个标签页不是同一个网站。也不能通信
- 这是“同源策略”。JavaScript中有一些特定的代码专门用于解决它。
- JavaScript与非当前标签页对应的网络服务器的通信能力被削弱了。
- 与其他网站/域的服务器通信需要来着远程服务器的明确协议(存储在HTTP header)
是什么使得 JavaScript 与众不同?
- 与 HTML/CSS 完全集成。
- 简单的事,简单地完成。
- 被所有的主流浏览器支持,并且默认开启。
JavaScript “上层”语言
许多新语言,这些语言在浏览器中执行之前,都会被
编译 (转化)成 JavaScript。现代化的工具使得编译速度非常快且透明。
- CoffeeScript 是 JavaScript 的一种语法糖。它引入了更加简短的语法,使我们可以编写更清晰简洁的代码。通常,Ruby 开发者喜欢它。
- TypeScript 专注于添加“严格的数据类型”以简化开发,以更好地支持复杂系统的开发。由微软开发。
- Flow 也添加了数据类型,但是以一种不同的方式。由 Facebook 开发。
- Dart 是一门独立的语言。它拥有自己的引擎,该引擎可以在非浏览器环境中运行(例如手机应用),它也可以被编译成 JavaScript。由 Google 开发。
- Brython 是一个 Python 到 JavaScript 的转译器,让我们可以在不使用 JavaScript 的情况下,以纯 Python 编写应用程序
- Kotlin 是一个现代、简洁且安全的编程语言,编写出的应用程序可以在浏览器和 Node 环境中运行
总结
- JavaScript 最开始是专门为浏览器设计的一门语言,但是现在也被用于很多其他的环境。
- JavaScript 作为被应用最广泛的浏览器语言,且与 HTML/CSS 完全集成,具有独特的地位。
- 有很多其他的语言可以被“编译”成 JavaScript,这些语言还提供了更多的功能。
手册与规范
规范
ECMA-262 规范 包含了大部分深入的、详细的、规范化的关于JavaScript 的信息。这份规范明确地定义了这门语言。
手册
MDN(Mozilla)JavaScript 索引 是一个带有用例和其他信息的主要的手册。它是一个获取关于个别语言函数、方法等深入信息的很好的信息来源。
兼容性表
JavaScript 是一门还在发展中的语言,定期会添加一些新的功能。
要查看它们在基于浏览器的引擎及其他引擎中的支持情况,请看:
- http://caniuse.com —— 每个功能的支持表
- https://kangax.github.io/compat-table —— 一份列有语言功能以及引擎是否支持这些功能的表格。
现代模式,”use strict”
ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的指令——“use strict” 来明确地激活这些特性。
这个指令看上去像一个字符串 “use strict” 或者 ‘use strict’。当它处于脚本文件的顶部时,则整个脚本文件都将以“现代”模式进行工作。
只有注释可以出现在 “use strict” 的上面。
没有办法取消 use strict
没有类似于 “no use strict” 这样的指令可以使程序返回默认模式。一旦进入了严格模式,就没有回头路了。
浏览器控制台
当你使用 开发者控制台 运行代码时,请注意它默认是不启动 use strict 的。
怎么在控制台中启用 use strict 呢?
搭配使用 Shift + Enter 按键去输入多行代码,然后将 use strict 放在代码最顶部
一种很丑但可靠的启用 use strict 的方法。将你的代码放在这样的包装器中:
(function() {
;
// ...你的代码...
})()
我们应该使用 “use strict” 吗?
现代 JavaScript 支持 “class” 和 “module” —— 高级语言结构,它们会自动启用 use strict 。因此,如果我们使用它们,则无需添加 “use strict” 指令。
JavaScript基础
变量
使用 let or var 来定义变量,目前推荐使用Let来定义。变量的定义和赋值与Java类似,区别是JavaScript貌似不需要明确的数据结构声明。
变量命名
JavaScript 的变量命名有两个限制:
- 变量名称必须仅包含字母、数字、符号 $ 和 _ 。
- 首字符必须非数字。
保留字
有一张 保留字列表 ,这张表中的保留字无法用作变量命名,因为它们被
用于编程语言本身了。
常量
声明一个常数(不变)变量,可以使用 const 而非 let :
类似 Java 的 final,不能被修改
总结
我们可以使用 var 、 let 或 const 声明变量来存储数据。
- let — 现代的变量声明方式。
- var — 老旧的变量声明方式。一般情况下,我们不会再使用它。
- const — 类似于 let ,但是变量的值无法被修改。
变量应当以一种容易理解变量内部是什么的方式进行命名。
数据类型
JavaScript中有八种基本的数据类型(和Java一样是八种呀)分别是 Number、BigInt、String、Boolean、null、undefined、Object和Symbol(感觉怪怪的,看起来Number应该包括了整数和浮点数,BigInt 可以类比为 BigInteger,String在Java中是引用类似了,在JavaScript中是基本数据类型,null 和 undefined也可以算作类型吗,在Java中 通常null就可以表示未定义了吧,Object是对象,Symbol是什么呢?)
Number
Number类型可以表示 整数和浮点数 还可以表示一些 特殊数值(Infinity、-Infinity、NaN)无穷大、无穷小、非数。
JavaScript中的数学运算是安全的,我们可以做任何事:除以 0,将非数字字符串视为数字,等等
Number可以表示的数据范围是
$$
-(2^{53} - 1)
$$
到
$$
(2^{53} - 1)
$$
BigInt
用来表示Number表示不了的数值,创建BigInt的时候需要在数字末尾加上n
Symbol类型
用于创建对象的唯一标识符
typeof 运算符
typeof 可以返回参数的类型。
typeof undefined // "undefined" |
总结
JavaScript 中有八种基本的数据类型(前七种为基本数据类型,也称为原始类型,而 object 为复杂数据类型)。
- number 用于任何类型的数字:整数或浮点数,在 ±(2 53 -1)
范围内的整数。 - bigint 用于任意长度的整数。
- string 用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。
- boolean 用于 true 和 false 。
- null 用于未知的值 —— 只有一个 null 值的独立类型。
- undefined 用于未定义的值 —— 只有一个undefined 值的独
立类型。 - symbol 用于唯一的标识符。
- object 用于更复杂的数据结构。
我们可以通过 typeof 运算符查看存储在变量中的数据类型。
- 通常用作 typeof x ,但 typeof(x) 也可行。
- 以字符串的形式返回类型名称,例如 “string” 。
- typeof null 会返回 “object” —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 object 。
交互
alert
alert()会弹出一个带有信息的小窗口(modal,模态窗)。modal意味着用户不能与页面的其他部分进行交互,知道处理完窗口
prompt
result = prompt(title, [default]); |
语法中的方括号 […],表示该参数是可选的,不是必需的。
prompt 将返回用户在 input 框内输入的文本,如果用户取消了输入,则返回 null 。
confirm
result = confirm(question); |
总结
alert:显示信息。
prompt:显示信息要求用户输入文本。点击确定返回文本,点击取消或按下 Esc 键返回 null 。
confirm:显示信息等待用户点击确定或取消。点击确定返回 true ,点击取消或按下 Esc 键返回 false 。
这些方法都是模态的:它们暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除。
上述所有方法共有两个限制:
- 模态窗口的确切位置由浏览器决定。通常在页面中心。
- 窗口的确切外观也取决于浏览器。我们不能修改它。
类型转换
大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型。
- alert 会自动将任何值都转换为字符串以进行显示。
- 算术运算符会将值转换为数字。
字符串转换
当我们需要一个字符串形式的值时,就会进行字符串转换。
- alert(value) 将 value 转换为字符串类型,然后显示这个值。
- 显式地调用 String(value) 来将 value 转换为字符串类型。
字符串转换最明显。 false 变成 “false” , null 变成 “null” 等。
数字型转换
在算术函数和表达式中,会自动进行 number 类型转换。
- 当把除法 / 用于非 number 类型
使用 Number(value) 显式地将这个 value 转换为number 类型。
如果该字符串不是一个有效的数字,转换的结果会是 NaN 。
number 类型转换规则:
值 | 转换结果 |
---|---|
undefined | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0 。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN 。 |
布尔型转换
它发生在逻辑运算中(稍后我们将进行条件判断和其他类似的东西),但是也可以通过调用 Boolean(value) 显式地进行转换。
转换规则如下:
- 直观上为“空”的值(如 0 、空字符串、 null 、 undefined 和 NaN )将变为 false 。
- 其他值变成 true 。
总结
有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。
- 字符串转换 —— 转换发生在输出内容的时候,也可以通过String(value) 进行显式转换。原始类型值的 string 类型转换通常是很明显的。
- 数字型转换 —— 转换发生在进行算术操作时,也可以通过Number(value) 进行显式转换。
- 布尔型转换 —— 转换发生在进行逻辑操作时,也可以通过Boolean(value) 进行显式转换。
基础运算符
数学
支持以下数学运算:
- 加法 + ,
- 减法 - ,
- 乘法 * ,
- 除法 / ,
- 取余 % ,
- 求幂 ** .
求幂 *
求幂运算 a ** b 将 a 提升至 a 的 b 次幂。
+ 的特性
- 被应用于字符串,它将合并(连接)各个字符串
- 应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 + 则会将其转化为数字。
运算符优先级
如果一个表达式拥有超过一个运算符,执行的顺序则由 优先级 决定。
每个运算符都有对应的优先级数字。数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行。
只需要记住,一元运算符的优先级高于二元运算符
链式赋值(Chaining assignments)
let a, b, c; |
链式赋值从右到左进行计算。首先,对最右边的表达式 2 + 2 求值,然后将其赋给左边的变量: c 、 b 和 a 。最后,所有的变量共享一个值。
位运算符
- 按位与 ( & )
- 按位或 ( | )
- 按位异或 ( ^ )
- 按位非 ( ~ )
- 左移 ( << )
- 右移 ( >> )
- 无符号右移 ( >>> )
值的比较
字符串比较
在比较字符串的大小时,JavaScript 会使用“字典(dictionary)”或“词典(lexicographical)”顺序进行判定。
字符串的比较算法非常简单:
- 首先比较两个字符串的首位字符大小。
- 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。
- 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。
- 重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。
- 如果两个字符串的字符同时用完,那么则判定它们相等,否则未结束(还有未比较的字符)的字符串更大。
比较参考的是非真正的字典顺序,而是 Unicode 编码顺序,小写的 ‘a’ > ‘A’
不同类型间的比较
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
严格相等
严格相等运算符 === 在进行比较时不会做任何的类型转换。
如果 a 和 b 属于不同的数据类型,那么 a === b 不会做任何的类型转换而立刻返回 false 。
对 null 和 undefined 进行比较
- 严格相等 === 比较二者时它们不相等,因为它们属于不同的类型。
- 使用非严格相等 == 比较二者时。JavaScript 存在一个特殊的规则,会判定它们相等。它们俩就
像“一对恋人”,仅仅等于对方而不等于其他任何的值(只在非严格相等下成立)。 - 使用数学式或其他比较方法 < > <= >= 时:null/undefined 会被转化为数字: null 被转化为 0 ,undefined 被转化为 NaN 。
undefined 和 null 在相等性检查 == 中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。
总结
- 比较运算符始终返回布尔值。
- 字符串的比较,会按照“词典”顺序逐字符地比较大小。
- 当对不同类型的值进行比较时,它们会先被转化为数字(不包括严格相等检查)再进行比较。
- 在非严格相等 == 下, null 和 undefined 相等且各自不等于任何其他的值。
- 在使用 > 或 < 进行比较时,需要注意变量可能为 null/undefined 的情况。比较好的方法是单独检查变量是否等于null/undefined 。
条件分支:if 和 ‘?’
条件运算符 ‘?’
有时我们需要根据一个条件去赋值一个变量。
let accessAllowed; |
语法:
let result = condition ? value1 : value2; |
多个 ‘?’
使用一系列问号 ? 运算符可以返回一个取决于多个条件的值。
let age = prompt('age?', 18); |
‘?’ 的非常规使用
有时可以使用问号 ? 来代替 if 语句:
let company = prompt('Which company created JavaScript?', ''); |
逻辑运算符
JavaScript 中有四个逻辑运算符: || (或), && (与), !(非), ?? (空值合并运算符)。
- 或运算寻找第一个真值
- 与运算寻找第一个假值
空值合并运算符 ‘??’
由于它对待 null 和 undefined 的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null 也不是 undefined 时,我们将其称为“已定义的(defined)”。
a ?? b 的结果是:
- 如果 a 是已定义的,则结果为 a ,
- 如果 a 不是已定义的,则结果为 b 。
如果第一个参数不是 null/undefined ,则 ?? 返回第一个参数。否则,返回第二个参数。
?? 的常见使用场景是提供默认值。
let user; |
我们还可以使用 ?? 序列从一系列的值中选择出第一个非 null/undefined 的值。
let firstName = null; |
与 || 比较
或运算符 || 可以以与 ?? 运算符相同的方式使用。
它们之间重要的区别是:
- || 返回第一个 真值。
- ?? 返回第一个 已定义的值。
|| 无法区分 false 、 0 、空字符串 “” 和 null/undefined 。它们都一样 —— 假值(falsy values)。如果其中任何一个是 || 的第一个参数,那么我们将得到第二个参数作为结果。
在实际中,我们可能只想在变量的值为 null/undefined 时使用默认值。也就是说,当该值确实未知或未被设置时。
let height = 0; |
高度 0 通常是一个有效值,它不应该被替换为默认值。所以?? 运算得到的是正确的结果。
优先级
?? 运算符的优先级与 || 相同,它们的的优先级都为 4
出于安全原因,JavaScript 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级。
总结
- 空值合并运算符 ?? 提供了一种从列表中选择第一个“已定义的”值的简便方式。
- ?? 运算符的优先级非常低,仅略高于 ? 和 = ,因此在表达式中使用它时请考虑添加括号。
- 如果没有明确添加括号,不能将其与 || 或 && 一起使用。
函数
函数声明
function showMessage() { |
JavaScript的函数没有设置返回值类型
局部变量
在函数中声明的变量只在该函数内部可见。
外部变量
函数也可以访问外部变量,函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。
只有在没有局部变量的情况下才会使用外部变量。如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量。
全局变量
任何函数之外声明的变量都被称为 全局变量。
全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。
减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。
参数
我们可以通过参数将任意数据传递给函数。
function showMessage(from, text) { // 参数:from 和 text |
默认值
如果一个函数被调用,但有参数(argument)未被提供,那么相应的值就会变成 undefined 。
我们可以使用 = 为函数声明中的参数指定所谓的“默认”(如果对应参数的值未被传递则使用)值:
function showMessage(from, text = "no text given") { |
返回值
函数可以将一个值返回到调用代码中作为结果。
function sum(a, b) { |
不要在 return 与返回值之间添加新行,JavaScript 默认会在 return 之后加上分号。
// 下面两段代码运行流程相同: |
总结
函数声明方式如下所示:
function name(parameters, delimited, by, comma) { |
- 作为参数传递给函数的值,会被复制到函数的局部变量。
- 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
- 函数可以返回值。如果没有返回值,则其返回的结果是undefined 。
为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。
与不获取参数但将修改外部变量作为副作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。
函数命名:
- 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
- 一个函数是一个行为,所以函数名通常是动词
函数表达式
在 JavaScript 中,函数不是“神奇的语言结构”,而是一种特殊的值。
没有赋值给变量的函数叫做函数声明
function sayHi() { |
另一种创建函数的语法称为 函数表达式 。
它允许我们在任何表达式的中间创建一个新函数。
let sayHi = function() { |
函数创建发生在赋值表达式的上下文中(在 = 的右侧),因此这是一个函数表达式 。函数表达式允许省略函数名。
在 JavaScript 中,函数是一个值,我们可以把它当成值对待。
回调函数
function ask(question, yes, no) { |
ask 的两个参数值 showOk 和 showCancel 可以被称为 回调函数 或简称 回调 。主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。
用函数表达式对同样的函数进行大幅简写
function ask(question, yes, no) { |
函数可以被视为一个 行为(action) 。我们可以在变量之间传递它们,并在需要时运行。
函数表达式 vs 函数声明
最根本的差别是,JavaScript 引擎会在什么时候创建函数。
- 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
- 函数声明则不同,在函数声明被定义之前,它就可以被调用。一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。
这是内部算法的原故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。
函数声明的另外一个特殊的功能是它们的块级作用域。
严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
let age = prompt("What is your age?", 18); |
{}内的空间是一个独立的代码块,一个函数声明的范围仅在对应的代码块内生效,如果希望在代码块外生效,可以使用函数表达式对代码块外声明的变量进行修改,这样就可以在代码块外调用代码块内声明的函数了。
什么时候选择函数声明与函数表达式?
我们需要声明一个函数时,首先考虑函数声明语法。它能够为组织代码提供更多的灵活性。因为我们可以在声明这些函数之前调用这些函数。
总结
- 函数是值。它们可以在代码的任何地方被分配,复制或声明。
- 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。
- 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。
- 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。
- 函数表达式在执行流程到达时创建。
在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通
常也会使得代码可读性更高。所以,仅当函数声明不适合对应的任务时,才应使用函数表达式。
箭头函数
类似Java的基于函数式接口的Lambda表达式简化过的匿名方法。
对象
对象用来存储键值对和更复杂的实体。
我们可以用下面两种语法中的任一种来创建一个空的对象
let user = new Object(); // “构造函数” 的语法 |
通常,我们用花括号。这种方式我们叫做 字面量 。
文本和属性
我们可以在创建对象的时候,立即将一些属性以键值对的形式放到{…} 中。
let user = { // 一个对象 |
属性有键(或者也可以叫做“名字”或“标识符”),位于冒号 “:” 的前面,值在冒号的右边。
我们可以用 delete 操作符移除属性:
delete user.age; |
我们也可以用多字词语来作为属性名,但必须给它们加上引号:
let user = { |
列表中的最后一个属性应以逗号结尾:
let user = { |
这叫做尾随(trailing)或悬挂(hanging)逗号。这样便于我们添加、删除和移动属性,因为所有的行都是相似的。
方括号
对于多词属性,点操作就不能用了,但可以用[]来获取对象内的属性,这是一种通用的获取对象中属性的方式。
let user = {}; |