HTML 中的 JavaScript
1.script 元素
- crossorigin:可选。配置相关请求的 CORS(跨源资源共享)设置。默认不使用 CORS。crossorigin= “anonymous”配置文件请求不必设置凭据标志。crossorigin=”use-credentials”设置凭据 标志,意味着出站请求会包含凭据
- defer:可选 表示脚本可以延迟到文档完全被解析和显示之后再执行 只对外部脚本文件有效
- integrity:可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性 果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错, 脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提 供恶意内容
使用了 src 属性的<script>
元素不应该再在<script>
标签之间再包含其他 js 代码 如果两者都提供的话 浏览器只会下载并执行标本文件 从而忽略行内代码
浏览器会根据特定的设置缓存所有外部连接的 JavaScript 文件 这意味着如果两个页面都用到同个文件 则该文件只需要下载一次 这最终意味着页面加载更快
语言基础
const
优先 let
次之
使用const
声明可以让浏览器运行时强制保持变量不变 也可以让静态代码分析工具提前法相不合法的赋值操作。
数据类型
typeof 操作符
typeof null == object
null 被认为是一个对空对象的引用
tips:我们建议在声明变量的同时进行初始化 这样当
typeof
返回undefined
时我们知道是因为给定的变量尚未声明而不是声明了但没有初始化tips:当我们定义一个未来将会赋值对象的变量时 应该初始化为 null(可以保持 null 是空对象指针的语义 并与 undefined 区分开
.)
isNaN()
可以用来测试对象 此时会先调用对象的valueOf()
方法 然后再确定返回的值是否可以转换为数值 如果不能 再调用toString()
方法 再测试其返回值 这通常是 ES 内置函数和操作符的工作方式
数值转换
1 | Number() |
字符串是不可变的 要修改某个变量中的字符串的值 必须先销毁原始的字符串 然后将包括新值的另一个字符串保存到该变量中
用加号操作符给一个值加上一个“”
也可以将其转换为字符串
Symbol 类型
符号是原始值 且符号实例唯一 不可变 用于确保对象属性使用唯一标识符 不会发生属性冲突的危险 可以用来创建唯一记号 进而用作非字符串形式的对象属性
符号没有字面量语法 即只要创建Symbol()
实例并将其用作对象的新属性 就可以保证它不会覆盖以有的对象属性 无论是符号属性还是字符串属性
Symbol()
函数不能与 new 关键字一起作为构造函数使用 这样做是为了避免创建符号包装对象 如果想使用符号包装对象 可以使用Object()
函数
1 | let mySymbol = Symbol(); |
使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例 可以使用一个字符串作为键 再全局符号注册表中创建并重用符号 需要使用Symbol.for()
1 | let fooGlobalSymbol = Symbol.for('foo'); |
Object 类型
- hasOwnProperty(propertyName):判断当前对象实例(不是原型)上是否存在特定的属性
- isPrototypeOf(object):判断当前对象是否为另一个对象的原型
- peopertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 循环
由于相等和不相等操作符存在类型转换问题 因此推荐使用全等和不全等操作符 这样有助于在代码中保持数据类型的完整性
语句
for-in
为了确保局部变量不被修改 推荐使用 const for-in 不能保证返回对象属性的顺序 如果迭代的变量是 null 或者 undefined 则不执行循环体
1 | for (const propName in window) { |
for-of
for-of 循环会按照可迭代对象的 next()方法产生值的顺序迭代元素。
for-await-of 循环 支持生成 promise 的异步可迭代对象
switch
switch 语句在比较每个条件的值时会使用全等操作符 因此不会强制转换数据类型
变量、作用域与内存
原始值:按值访问
引用值:对该对象的引用而不是实际的对象本身
复制值
- 1.原始值
在通过变量把原始值赋值给另一个变量时 原始值会被复制到新变量的位置 这两个位置是完全独立的 互不干扰
- 2.引用值
在把引用值从一个变量赋给另一个变量时 储存在变量中的值也会被复制到新变量所在的位置 区别在于这里复制的值实际上时一个指针 它指向储存在堆内存中的对象那个 操作完成后 两个变量实际上指向同一个对象 因此一个对象上面的变化也会从另一个对象上反映出来
1 | let obj1 = new Object(); |
传递参数
ECMAScript 中所有函数的参数都是值传递(ECMAScipt 不可能引用传递) 这就意味着函数外的值会被复制到函数内部的参数中 就像一个变量复制到另一个变量一样 (如果是原始值 就和原始值变量的复制一样 如果是引用值 就和引用值的复制一样)
按值传递参数时 值会被复制到一个局部变量(一个命名参数 即 arguments 对象中的一个槽位)
1 | function addTen(num) { |
1 | function setName(obj) { //obj指向的对象保存在全局作用域的堆内存上 所以也会使外部的对象放映这个变化 |
函数中的参数就是局部变量
类型判断
使用 typeof 判断原始值 使用 instance of 判断引用类型(由原型链决定)
如果想让整个对象都不能被更改 可以使用 freeze()
垃圾回收
标记清理
当变量进入上下文的时候 将变量加上存在于上下文中的标记 当变量离开上下文时 加上离开上下文的标记
来及回收程序运行的时候 会标记内存中存储的所有变量 然后将所有上下文中的变量以及被在上下文中引用的变量的标记去掉 再次之后再被加上标记的变量就是待删除的 原因是任何在上下文中的变量都访问不到它们了,随后垃圾回收程序做一次内存清理
引用计数
对每个值都记录它被引用的次数 当一个值的引用数为 0 时 就说明没法再访问这个值了 可以安全地回收其内存
内存管理
优化内存占用的最佳手段就是保证再执行代码时只保存必要的数据 如果数据不再必要 就设置为 null 从而释放 这个叫做解除引用(适合全局变量和全局变量的属性 局部变量在超出作用域之后会被自动解除引用)
解除对一个值的引用并不会自动导致相关内存被回收 解除引用的关键在于确保相关的值已经不在上下文中了 因此下一次垃圾回收的时候会被回收
内存泄漏
闭包很容易造成内存泄漏
1 | let outer = function () { |
基本引用类型
引用类型和原始值包装类型(String、Number、Boolean
)的主要区别在于对象的生命周期 在通过new
实例化引用类型后 得到的实例会在离开作用域时被销毁 而自动创建的原始值包装对象则指存在于访问它的那行代码执行期间 这意味着不能再运行时给原始值添加属性和方法
1 | let S1 - "some text"; |
在原始值包装类类型的实例上调用 typeof 会返回 object 所有原始值包装对象都会转换为 true
1 | let obj = new Object("some text"); |
使用new
调用原始值包含在那个类型的构造函数和调用同名的转型函数不一样
1 | let value = "25"; |
toFixed()
可以表示有 0-20 个小数的数值
字符串操作方法
concat()
: 将一个或多 i 个字符串拼接成一个新字符串1
2
3
4
5let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
//concat()方法可以接收任意多个参数,因此可以一次性拼接多个字符串,与
concat()
方法一样,slice()、substr() 和 substring()
也不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3, 7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"
//slice将负值参数当成字符串长度加上负参数值 substr将第一个负参数当成字符串长度加上该值 substring将所有负参数值都当作0
let stringValue = "hello world";
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)1
2
3
4
5
6let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7
let stringValue = "hello world";
console.log(stringValue.indexOf("o", 6)); // 7
console.log(stringValue.lastIndexOf("o", 6)); // 4判断是否包含另一个字符串的方法:startsWith() endsWith() inclueds()startsWith()检查开始于索引 0 的匹配项,endsWith()检查开始于索 引(string.length - substring.length)的匹配项,而 includes()检查整个字符串
1
2
3
4
5
6
7let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // falsetrim()
:创建字符串的一个副本 删除亲啊后的所有空格符再返回结果 原字符串不受影响trimLeft()
和trimRight()
分别从开始和末尾清理空格repeat()
接收一个参数 表示将字符串复制多少次后返回拼接所有副本后的结果padStart()
和padEnd()
方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至 满足长度条件。
字符串大小写转换:toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()
单例内置对象
URL 编码方式
encodeURI()
和 encodeURIComponent()
方法用于编码统一资源标识符(URI),以便传给浏览器
使用 encodeURIComponent()应该比使用 encodeURI()的频率更高, 这是因为编码查询字符串参数比编码基准 URI 的次数更多。
encodeURI()
和 encodeURIComponent()
相对的是 decodeURI()
和 decodeURIComponent()
。
eval()
解释器 接收一个参数 即耀执行的 js 字符串
1 | eval("console.log('h1')"); |
使用 eval 的时候必须慎重 因为这个方法会对 CSS 利用暴露出很大的攻击面 用户可能插入导致你网站或引用奔溃的代码
集合引用类型
在使用对象字面量表示定义对象时 并不会按实际调用 Object 构造函数
from()
可用于将类数组结构转换为数组实例 of()
用于将一组参数转换为数组实例
Array.from()
对数组进行浅复刻
1 | const a1 = [1, 2, 3, 4]; |
Array.from()
还可以接收第二个可选的参数 表示直接增强新数组的值 而无需像调用Array.from().map()
那样先创建一个中间数组
1 | const a1 = [1, 2, 3, 4]; |
Array.of()
可以把一组参数转换为数组
数组的迭代器方法
keys()
返回数组索引的迭代器 values()
返回数组元素的迭代器 entries()
返回索引/值对的迭代器
使用结构可以非常容易地在循环中拆分键值对
1 | const a = ["foo", "bar", "baz", "qux"]; |
fill()
静默忽略超出数组边界、零长度以及方向相反的索引范围
copyWithin()
会按照指定范围浅复制数组中的部分内容,然后将它们插入到指 定索引开始的位置。
1 | let ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
如果数组中某一项是 null 或 undefined,则在 join()、toLocaleString()、 toString()和 valueOf()返回的结果中会以空字符串表示
reverse()和 sort()都会返回调用它们的数组的引用
1 | const arr = [1, 3, 2, 5, 4, 6]; |
concat()
方法可以在现有数组全部元素基础上 创建一个新数组。默认打平 可以使用Symbol.isConcatSpreadable
阻止打平
1 | let colors = ["red", "green", "blue"]; |
slice()
用于创建一个包含原有数组中一个或多个元素的新数组。
splice()
的主要目的是 在数组中间插入元素
splice()
方法始终返回这样一个数组,它包含从数组中被删除的元素(如果没有删除元素,则返 回空数组)
1 | let colors = ["red", "green", "blue"]; |
搜索和位置方法
ECMAScript 提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索。
1.严格相等
indexOf()、lastIndexOf()和 includes()。
1 | let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; |
2.断言函数
find()
返回 第一个匹配的元素,findIndex()
返回第一个匹配元素的索引 找到第一个匹配后就不再进行
1 | const people = [ |
迭代方法
every()
:对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。filter()
:对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。forEach()
:对数组每一项都运行传入的函数,没有返回值。map()
:对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。some()
:对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。 这些方法都不改变调用它们的数组
归并方法
reduce()
和 reduceRight()
都会迭代数 组的所有项,并在此基础上构建一个最终返回值
1 | let values = [1, 2, 3, 4, 5]; |
Map
与Object
类型的一个主要差异是Map
实例会维护键值对的插入顺序 因此可以根据插入顺序执行迭代操作
映射实例可以提供一个迭代器 能以插入顺序生成[key, value]形式的数组 可以通过 entries()方法取得迭代器
1 | const m = new Map([ |
键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。当然,这并不妨碍修改作为 键或值的对象内部的属性,因为这样并不影响它们在映射实例中的身份
1 | const m1 = new Map([["key1", "val1"]]); |
选择 Object 还是 Map
- 占用内存:储存当个键值对所占用的内存挥着键的数量线性增加
Map
大约可以比Object
多储存 50%的键值对 - 插入性能:涉及到大量插入操作
Map
更佳 - 查找速度:设计大量查找操作
Object
更佳 - 删除性能:设计大量删除操作 选择
Map
weak Map
键只能是Objec
t 或者继承自Object
的类型 使用非对象设置键会抛出 TypeError 值没有限制类型
1 | const key1 = { id: 1 }, |
WeakMap
的键不属于正式的引用 不会组织垃圾回收 但是只要键存在 键值对就会存在于映射中 并被当作对值的引用 因此不会被垃圾回收
1 | const wm = new WeakMap(); |
**WeakMap
实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值**
WeakMap 的用处
1.私有变量
2.DOM 节点数据
因为WeakMap
实例不会妨碍垃圾回收 所以非常适合保存关联元数据
1 | const m = new Map(); |
Set
Set
会维护值插入时的顺序 因此支持按顺序迭代
修改集合中的值的属性不会影响到其作为集合值的身份
1 | const s1 = new Set(["vall"]); |
WeakSet
可用于给对象打标签
迭代和扩展操作
扩展运算符在对可迭代对象执行浅复刻时特别有用 只需要简单的语法就可以复制整个对象
1 | let arr1 = [1, 2, 3]; |
浅复制意味着只会复制对象的引用
1 | let arr1 = [{}]; |
迭代器和生成器
每个迭代器都表示对可迭代对象的一次性有序遍历 不同的迭代器实例直接拿没有联系 只会独立地遍历可迭代对象
迭代器并不与可迭代对象某个时刻的快照绑定 而仅仅是使用游标来记录遍历可迭代的对象的历程 如果可迭代对象在迭代期间被修改了 那么迭代器也会发生相应的变化
1 | let arr = ["foo", "baz"]; |
迭代器维护着一个指向可迭代对象的引用 因此迭代器会阻止垃圾回收程序回收可迭代对象
自定义一个迭代器(需要将计数器变量放到闭包里 然后通过闭包返回迭代器)
1 | class Counter { |
如果迭代器没有关闭 则还可以继续从上次离开的地方继续迭代 比如 数组的迭代器就是不能关闭的
1 | let a = [1, 2, 3, 4, 5]; |
生成器
生成器对象一开始处于暂停执行的状态 具有next()
方法 调用这个方法会让生成器开始或恢复执行
1 | function* generatorFn() {} |
函数体为 kon 的生成器函数中间不会停留 调用一次next()
就会让生成器达到done:true
状态
生成器函数只会在初次调用next()
方法后开始执行
1 | function* generatorFn() { |
yield 可以让生成器停止和开始执行 遇到关键字后 执行停止 作用域的状态会被保留
1 | function* generatorFn() { |
生成器函数内部的执行流程会针对每个生成器对象区分作用域。在一个生成器对象上调用 next()
不会影响其他生成器
对象、类与面向对象编程
Object.assign()
接收一个目标对象和一个或多个元对象作为参数 然后将每个原对象中可枚举(Object.propertyIsEnumerable()
返回 true)和自有(Object.hasOwnProperty()
返回 true)属性复制到目标对象以字符串和符号为键的属性 会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]
取得属性的值,然后使用目标 对象上的[[Set]]
设置属性的值。
1 | /** |
1 | dest = { |
assign()
是浅复制 意味着只会复制对象的引用
如果赋值期间出错 操作会中止并退出 同时抛出错误 因此可能只完成部分复制
1 | let dest, src, result; |
可计算属性表达式中抛出任何错误都会中断对象创建 如果计算属性的表达式有副作用就要小心 因为如果表达式抛出错误 那么之前完成的计算是不能回滚的
解构并不要求变量必须在解构表达式中说明 不过 如果是事先声明的变量 则赋值表达式必须包含在一对括号中
1 | let personName, personAge; |
嵌套解构
可以通过解构来复制对象属性
1 | let person = { |
涉及到多个属性的解构赋值是一个无关输出的顺序化操作 如果一个解构表达式涉及多个赋值 如果开始的赋值成功而后面的赋值出错 则整个赋值表达式只会完成一部分