JavaScript
文章目录
JS特性
- 解释型语言(无需编译,可执行脚本)/ 或即时编译性语言(需要学完后深入了解JS引擎工作原理!)
- 动态类型编程语言(声明一个变量可存入任意数据类型)
- 基于原型的面向对象
- 数学运算是安全的(分母可为0、字符串视作数字*(运算符自动实现数据类型转换)*,会得到NaN,脚本不会停止)
- 函数隶属于
object
类型
闭包
-
闭包是指一个函数可以记住其外部变量并可以访问这些变量,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 “new Function” 语法 中讲到)。
-
使用
new Function
创建一个函数,那么该函数的[[Environment]]
并不指向当前的词法环境,而是指向全局环境。因此,此类函数无法访问外部(outer)变量,只能访问全局变量。
-
-
也就是说:JavaScript 中的函数会自动通过隐藏的
[[Environment]]
属性(词法环境)记住创建它们的位置,所以它们都可以访问外部变量。 -
通常,闭包是指使用一个特殊的属性,即词法环境
[[Environment]]
来记录函数自身的创建时的环境的函数(函数引用外部变量)→ 词法环境~函数
变量
使用let声明,无需声明其数据类型,JS为动态语言
变量命名
-
变量名称必须仅包含字母、数字、符号
$
和_
( 与html不同,js不能使用连字符-
) -
首字符非数字
-
大小写敏感
-
不可使用保留字(类似return、let等)
常量
- 使用const直接声明
- 常量声明后不可再赋值
- hard-coded例如颜色值通常使用
大写字母+下划线
的形式命名
数据类型
1 原始类型
直接在栈内存中存储;能使用方法,但不是对象,不能存储额外的数据(不能添加属性)
-
Number(整数、浮点数、无穷大(
Infinity
、-Infinity
)和NaN(not a number))-
Infinity
(和-Infinity
)是一个特殊的数值,比任何数值都大(小)isFinite(value)` 将其参数转换为数字,如果是常规数字而不是 `NaN/Infinity/-Infinity`,则返回 `true
-
NaN
代表一个 error,不等于任何东西,包括它自身1. isNaN(value)` 将其参数转换为数字,然后测试它是否为 `NaN 2. Object.is(NaN,NaN) === true(用于检查两个值是否严格相等的内建函数)
-
num.toString(base)
返回在给定base
进制数字系统中num
的字符串表示形式(num直接写为数字,则需要两个点或括号括起数字来调用方法) -
舍入
| Math.floor | Math.ceil|Math.round|Math.trunc|
| :---------: | :------: |:---------: | :------: |
|向下舍入|向上舍入|四舍五入|移除小数点|
-
- 函数 toFixed(n) 将数字四舍五入到小数点后 n 位,并以字符串形式返回结果。
- 随机生成整数的问题:四舍五入时落到两端边界整数值的概率少一半,要概率相等需要向两侧扩大0.5的范围,或者向右扩展1个单位再向下取整
JavaScript 中最常用的两种数据结构是 Object
和 Array
。
- 对象是一种根据键存储数据的实体。
- 数组是一种直接存储数据的有序列表。
0.1 + 0.2 ≠ 0.3的问题
-
精度损失
使用小数必然会损失精度,求和时精度损失叠加
! 结论:在处理小数时避免相等性检查。
alert( 0.1 + 0.2 ); // 0.30000000000000004
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
- 原理
一个数字以其二进制的形式存储在内存中(64位双精度浮点数),一个 1 和 0 的序列。在十进制数字系统中看起来很简单的 0.1,0.2 这样的小数,实际上在二进制形式中是无限循环小数。
什么是 0.1?0.1 就是 1 除以 10,1/10,即十分之一。在十进制数字系统中,这样的数字表示起来很容易。将其与三分之一进行比较:1/3,三分之一变成了无限循环小数 0.33333(3)。
在十进制数字系统中,可以保证以 10 的整数次幂作为除数能够正常工作,但是以 3 作为除数则不能。也是同样的原因,在二进制数字系统中,可以保证以 2 的整数次幂作为除数时能够正常工作,但 1/10 就变成了一个无限循环的二进制小数。
使用二进制数字系统无法 精确 存储 0.1 或 0.2,就像没有办法将三分之一存储为十进制小数一样。
IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。这些舍入规则通常不允许我们看到“极小的精度损失”,但是它确实存在。
-
例如,6.35的小数部分是一个无限二进制,存储时会造成损失,造成数字变小,四舍五入变成6.3,乘以10扩大至63.5,小数部分为1/2,即以2的整数次幂为分母 的小数,在二进制系统中可以被精确的表示。
-
解决方案
- 舍入法:对求和结果使用toFixed(n)函数舍入
- 乘除法:将数字临时乘以100等变为整数,运算后再除回(除法仍会出现误差,因此此方法只能减少误差,而不能消除)
-
BigInt(较大的整数,即任意长度的整数)
-
String(单个字符或者字符串)
单/双引号’ ’ | 反引号`` |
---|---|
简单引用 | 功能扩展引号 |
-
反引号是 功能扩展 引号。它们允许我们
-
通过将变量和表达式包装在
${…}
中,来将它们嵌入到字符串中。 -
允许字符串跨行,拆分成多行(单双引号通过换行符\n实现)
-
使得字符串中间可以加一个单引号,而无需用转义字符
-
-
str.length:字符串长度,属性,无需加()
-
str[n]访问字符,for(…of ”str") 遍历字符,可访问但不可更改字符串!
-
JS中没有char类型
-
检测匹配
str.includes(substr, pos)
startsWith/endsWith
-
获取子字符串
slice(start, end):不含end,允许负值
substring(start,end)
-
根据语言比较字符串时使用 localeCompare,否则将按字符代码进行比较
-
字符串可以使用正则表达式进行搜索/替换的方法
-
字符串的大/小写转换,使用:
toLowerCase/toUpperCase
-
str.trim()
—— 删除字符串前后的空格 (“trims”) -
str.repeat(n)
—— 重复字符串n
次
-
Boolean(true or false)
-
null(可写入变量,代表“无”、“空”或“值未知”的特殊值)
-
undefine(默认初始值,代表未被赋值)
2 Object 类型和 Symbol 类型
保存到堆内存中
1. object (对象)
存储键值对和更复杂的实体(在if中始终为真)
-
创建方式
- 构造函数(可复用对象创建代码)
-
命名以大写字母开头
-
只能由 “new” 操作符来执行
new意味着开始时创建一个空的this,结束时返回一个带值的this
-
单个复杂对象——创建代码可被封装到一个立即调用的构造函数中(无需重用)
-
本质就是一个常规普通函数罢了
-
字面量(花括号)
-
尾随(trailing)或悬挂(hanging)逗号
-
花括号括起来的就是对象
-
添加属性:obj.newkey=1;
【方法也可以作为属性】
-
-
属性键
只有两种原始类型可以用作对象属性键:
- 字符串类型
- symbol 类型
-
属性名获取
-
点符号
-
方括号(计算属性、多词属性)
-
-
检查属性是否存在
-
直接调用,若不存在会返回undefined
-
“key” in object返回true则存在(不常用)
-
-
删除属性:delete obj.prop
-
遍历对象所有键
for ( (let) key in object) {
// 对此对象属性中的每个键执行的代码
// 如果进到循环里面,说明有属性。
}
- 对象的拷贝
- 引用:拷贝地址,因此所有通过被拷贝得到的对象,其引用的操作(如添加、删除属性)都作用在同一个对象上。
- 克隆:循环访问逐一拷贝
- 合并(浅拷贝):src拷贝到dest,调用返回dest
Object.assign(dest, [src1, src2, src3…])
- spread语法
- 深拷贝:如果对象有值为对象,则复制它的结构;可递归实现或利用深拷贝函数( lodash 库的 _.cloneDeep(obj))
2. symbol(对象唯一标识符)
-
创建:Symbol( name )
-
唯一性
symbol 一个描述(也称为 symbol 名),即使我们创建了许多具有相同描述的 symbol,它们的值也是不同
- 优点:”私有成员“
字符串id可能被外部改写,但symbol无法被访问、改写(for…in也不能访问到,但可以.assign复制),即使不同第三方使用相同名称的symbol,它们也是不同的,几个代码共用对象不能随意添加字段时,添加symbol就很方便
- 优点:”私有成员“
-
全局symbol注册表
可以在其中创建 symbol,确保一致性,即每次访问相同名字的 symbol 时,返回的都是相同的 symbol
- 从注册表中读取:
Symbol.for(key)
若不存在,则自动创建一个全局symbol
- 返回一个名字(全局)
Symbol.keyFor(sym)
- 从注册表中读取:
-
JavaScript 中的大多数值都支持字符串的隐式转换。例如,我们可以 alert 任何值,都可以生效。symbol 比较特殊,它不会被自动转换为字符串(二者有本质上的不同)
- 通过toString()做类型转换
- 通过.description属性访问symbol的标签名
typeof 运算符
- typeof 运算符(非函数)以字符串的形式返回参数的类型(调用方法:
typeof x
ortypeod(x)
,x可以为任何类型的变量,包括对象、保留字、字符串等)
交互函数
打开一个模态窗口
- 模态:暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除。
- 窗口的外观和位置都由浏览器决定
- alert
alert(“Hello”);
- 自动将任何值转换为字符串进行显示
- prompt
result = prompt(title, [default]);
- title为显示文本
- []代表可选参数
- default指定input框的初始默认值
- result获取返回的用户输入文本
- 点击取消返回null,输入空格返回’’
- confirm
result = confirm(question);
- question为显示文本
- 带有两个按钮,点击确定返回 true,点击取消返回 false
- result接收返回值(由用户输入)
类型转换
多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型,在某些情况下,我们需要将值显式地转换为我们期望的类型。
- 隐式转换:alert转换为string、运算符将string转换为number
- 显示转换
- 字符串转换(String(value))
- 数字型转换(*Number(value)*或者一元+号)
如果该字符串不是一个有效的数字,转换的结果会是 NaN
- 布尔型转换(Boolean(value))
- 直观上为“空”的值(如 0、空字符串、null、undefined 和 NaN)将变为 false(空字符串不同于只有空格的字符串)
- 其他值变成 true,非空的字符串总是
true
对象—原始值转换
- hint(类型转换三种变体)
- string
- number
- default
- 对象到原始值的转换由许多期望以原始值作为值的内建函数和运算符自动调用
逻辑运算符
JS中逻辑运算可以被应用于任意类型的值,而不仅仅是布尔值,它们的结果也同样可以是任意类型
下面优先级递增
- “||”或运算符
-
获取变量列表或者表达式中的第一个真值并返回,没有时返回最后一个操作数
-
短路求值(Short-circuit evaluation)
利用这个特性,只在左侧的条件为假时才执行命令
- “&&”与运算符
- 与运算寻找第一个假值并返回,(都为真)没有时返回最后一个操作数
- “ ! ”非运算符
- 两个非运算 !! 有时候用来将某个值转化为布尔类型, ,第一个非运算将该值转化为布尔类型并取反,第二个非运算再次取反,最后我们就得到了一个任意值到布尔值的转化
- “ ?? ”空值合并运算符
- 获得两者中的第一个*“已定义的”值*,通常用于设置默认值
- 与||优先级一致,弥补||的不足,|| 无法区分 false、0、空字符串 “” 和 null/undefined
循环
- break:跳出整个循环,循环中止;
- break 语句跳出嵌套循环至标签处(循环使用标签标记)
- continue:执行下一次循环,跳过本次循环的剩余部分;不可与三元运算符?一起使用
switch语句
- 无break将会不经判断执行后续语句,可用于分组
- case判断时要求严格相等
函数
JS中函数是一个值,可复制(函数名后无括号)/可调用(有括号)
函数是一个值,因此函数参数也可以是函数***(回调函数)***!
函数创建
- 函数声明——末尾无需加分号
- 函数表达式(加分号,相当于赋值语句)
- 在任何表达式中可创建新函数
- 函数名可以省略(匿名函数)
- 可通过全局变量在代码块外部调用
- 箭头函数
- 简单的单行行为(action)
- 多行箭头函数需要加花括号和return返回值
调试
- 浏览器中打断点(or 右键选择条件断点)
- IDE中调试:代码块中加一语句debugger;
-
察看(Watch):显示任意表达式的当前值。可以点击加号 + 然后输入一个表达式。调试器将显示它的值,并在执行过程中自动重新计算该表达式。
-
调用栈(Call Stack):显示嵌套的调用链。如果点击了一个堆栈项,调试器将跳到对应的代码处,并且还可以查看其所有变量。没有函数,显示*“anonymous”*
-
作用域(Scope): 显示当前的变量。
Local
显示当前函数中的变量,你还可以在源代码中看到它们的值高亮显示了出来。
Global
显示全局变量(不在任何函数中)
- console.log()
- 更多开发者工具请参考:
https://developers.google.com/web/tools/chrome-devtools
代码风格
可自定义的代码检查器—ESLint
- 安装 Node.JS。
- 使用
npm install -g eslint
命令(npm 是一个 JavaScript 包安装工具)安装 ESLint。 - 在你的 JavaScript 项目的根目录(包含该项目的所有文件的那个文件夹)创建一个名为
.eslintrc
的配置文件。 - 在集成了 ESLint 的编辑器中安装/启用插件。大多数编辑器都有这个选项。
注释
- 自描述性代码
减少大片解释性注册,用函数代替代码块,函数名清晰易懂,函数就变为注释 - 正确的注释
-
描述架构
对代码进行解释的特殊编程语言 UML,构建代码的高层次架构图 -
记录函数的参数和用法
用于记录函数的语法 JSDoc:用法、参数和返回值
开发流程
-
编写规范
-
功能实现
-
测试通过
-
迭代2,3
使用 Mocha 进行自动化测试
使用以下 JavaScript 库进行测试:
-
Mocha —— 核心框架:提供了包括通用型测试函数 describe 和 it,以及用于运行测试的主函数。
-
Chai —— 提供很多断言(assertion)支持的库。它提供了很多不同的断言,现在我们只需要用 assert.equal。
-
Sinon —— 用于监视函数、模拟内建函数和其他函数的库,我们在后面才会用到它。
这些库都既适用于浏览器端,也适用于服务器端。
- 行为驱动开发(BDD)
代码转译
- 让代码在还不支持最新特性的旧引擎上工作
- 转译器(Transpilers)—新的语法结构和运算符
现代项目构建系统,例如 webpack,提供了在每次代码更改时自动运行转译器的方法,因此很容易将代码转译集成到开发过程中。
-
垫片(Polyfills脚本)—新的函数
新函数,而不是语法更改,因此无需在此处转译任何内容,只需要声明缺失的函数;JavaScript 是一种高度动态的语言,脚本可以添加/修改任何函数,甚至包括内建函数。
- 可以搭建一个基于 webpack 和 babel-loader 插件的代码构建系统
垃圾回收
-
可达性(Reachability)
有被引用到,可以被访问到(只有传入引用才可以使对象可达,被引用不一定可达)
-
根
-
垃圾回收
-
当一个对象没有引用,变为不可达,无法访问到(例如赋新值给变量,失去对原对象的访问),则JavaScipt引擎中的垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存。
-
几个对象互相引用,但没有外部引用,就成为一座孤岛,将被删除。
-
垃圾回收基本算法
“mark-and-sweep”:从根开始遍历标记引用,未被标记的对象会被删除。
-
this关键字
- 可以用于任何函数
-
this
的值是在代码运行时计算出来的,它取决于代码上下文。 -
this 只有在函数被调用时才会有值,this指代作为对象的方法被调用(以点符号调用)的此函数的对象。作为函数被调用(等号赋值的形式),则this的值为undefined(实际上是当前函数的this)
-
确保访问的安全性
-
箭头函数没有自己的 “this”,从外部获取
新增特性
可选链 “?.”
可选链 ?.
是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误(有些属性应该允许为null)
value?.prop
value存在就返回属性,不存在就立即停止运算(短路效应)并返回undefined
- 一些要求value非空的情况下,不要用可选链
- 可选链
?.
不是一个运算符,而是一个特殊的语法结构,它还可以与函数和方括号一起使用
- obj?.[prop]
如果 obj 存在则返回 obj[prop],否则返回 undefined。- obj.method?.()
如果 obj.method 存在则调用 obj.method(),否则返回 undefined
- 可用于安全地读取或删除,但不能写入
delete user?.name;
// 如果 user 存在,则删除 user.name
参考文章
zh.javascript.info