# 红宝书第四版笔记
# 语言基础
# 操作符
一元操作符
一元加和减
可用于类型转换,同Number()
console.log(+"123") // 123 console.log(+true) // 1 console.log(-"123") // -123 console.log(-true) // -1
布尔操作符
逻辑非
使用
!!可以实现类型转换,同Boolean()console.log(!!"hello") // true逻辑与
- 根据情况返回第一个值或者第二个值:
如果前者为真,则返回第二个值
如果前者为假,则返回第一个值
console.log(null && 123) // null console.log(123 && 456) // 456- 逻辑与是短路运算符,如果前者为假,则不再对后者求值,直接返回第一个值
console.log(null && undeclaredVariable) // null,不会对undeclaredVariable求值 console.log(true && undeclaredVariable) // 报错,undeclaredVariable未定义,无法求值误区:“逻辑与和逻辑非一样返回布尔值”,逻辑与不一定返回布尔值,本质上是返回第一个值或第二个值
逻辑或
逻辑或和逻辑与是类似的,区别是:
如果前者为假,则返回第二个值
如果前者为真,则返回第一个值
console.log(123 || 'hello') // 123 console.log(null || 'hello') // hello利用这种特性,经常可以用于赋值时添加默认值的操作
const a = null || 123 console.log(a) // 123逻辑或也是短路运算符,如果前者为真,则不再对后者求值,直接返回第一个值
console.log(123 || undeclaredVariable) // 123 console.log(false || undeclaredVariable) // 报错,undeclaredVariable未定义,无法求值
# 语句
标签语句
标签语句用于给语句加标签,通常用于嵌套循环,可以通过break或continue语句引用
let sum = 0 outer: for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { if (i === 5 && j === 5) { break outer // 直接跳出外层循环 } sum++ } } console.log(sum)with语句
with语句用于将代码作用域设置为特定的对象,使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利
const o = { a: 1, b: 2, } with (o) { console.log(a) // 1 如果没有找到a变量,则搜索o对象,如果存在同名属性则使用该属性的值 console.log(b) // 2 }with语句影响性能且难于调试,不推荐使用
# 变量、作用域与内存
# 原始值与引用值
按值传递和按引用传递的区别:
在按值传递参数时,值会被复制到一个局部变量
在按引用传递参数时,值在内存中的位置会被保存在一个局部变量(即两个变量的内存地址相同)
ES中所有函数的参数都是按值传递的
function demo(o) { o.name = 'zhuli' o = new Object() // 由于按值传递,局部变量o和全局变量a相互独立 o.name = 'zhangsan' } const a = new Object() demo(a) console.log(a.name) // zhuli
# 基本引用类型
# Date
- ISO8601 日期时间表示国际标准:
2020-10-22T10:22:00Z, 规定日期时间之间用T分隔,Z表示UTC时间 - ISO8601 没有Z表示本地时间,可以自行指定时区
2020-11-01T17:20+07:00 Date.parse()传入ISO8601时间,返回时间戳。Date.parse('2020-11-01T18:20')Date.UTC()传入UTC时间,返回时间戳。Date.UTC(2020, 10, 1, 10, 20)`new Date()返回ISO8601时间对象,可以传参new Date(2020, 10, 1, 10, 20)后台调用Date.UTC,不过参数被认为是本地时间new Date('2020-11-01T18:20')后台调用Date.parse()Date.now()返回时间戳
个人觉得统一按照ISO8601的方式传参可以明确表示本地时间或者UTC时间,代码更可读
# RegExp
/.at/g如果设置了全局标记g,则每次调用exec()/test()都会在字符串中向前搜索下一个匹配项
# 原始值包装类型
引用类型与原始值包装类型的主要区别在于对象的声明周期。在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。
let s1 = 'some text' s1.color = 'red' // 创建了new String('some text'),执行完毕后销毁 console.log(s1.color) // undefined 重新创建了new String('some text'),执行完毕后销毁Number
toFixed()返回指定小数位数的数值字符串const num = 12.146 console.log(num.toFixed(2)) // 12.15toExponential()返回科学记数法表示的数值字符串,也可以指定小数位数const num = 120.146 console.log(num.toExponential(2)) // 1.20e+2toPrecision()根据情况返回合理的输出结果,可能是固定长度,也可能是科学记数法。这个方法接收一个参数用于指定数字的总位数(不包含指数)const num = 120.146 console.log(num.toPrecision(2)) // 1.2e+2 console.log(num.toPrecision(3)) // 120 console.log(num.toPrecision(5)) // 120.15本质上,toPrecision方法会根据数值和精度来决定调用
toFixed()还是toExponential()为了以正确的小数位精确表示数值,这3个方法都会向上或向下舍入
# 集合引用类型
# Array
队列方法
unshift()方法将传入的值作为整体加到数组前面,不会改变传入参数的顺序const arr = new Array() arr.unshift(1, 2) // [ 1, 2 ] arr.unshift(3, 4) // [ 3, 4, 1, 2 ]
排序方法
sort()默认将元素通过String()转成字符串后进行升序排序。console.log([1, 2, 5, 10].sort()) // [ 1, 10, 2, 5 ]
操作方法
concat()用于合并多个数组,默认会将传入的数组打平const a = [1, 2, 3] const b = a.concat([4, 5], 6) console.log(b) // [ 1, 2, 3, 4, 5, 6 ]可以通过设置
[Symbol.isConcatSpreadable]属性为false使得不打平该数组const a = [1, 2, 3] const b = [4, 5] b[Symbol.isConcatSpreadable] = false const c = a.concat(b) console.log(c) // [ 1, 2, 3, [ 4, 5, [Symbol(Symbol.isConcatSpreadable)]: false ] ]slice()用于获取数组切片(一个新数组)console.log([1, 2, 3, 4, 5].slice(1, 3)) // [ 2, 3 ]
# Map
顺序与迭代
Map实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。
const map = new Map([ [1, 2], [3, 4], [5, 6], ]) for (const [key, value] of map.entries()) { console.log(`${key} => ${value}`) } // 1 => 2 // 3 => 4 // 5 => 6
# WeakMap
WeakMap中的"weak"(弱),描述的是JavaScript垃圾回收程序对待“弱映射”中键的方式。
基本API
弱映射中的键只能是Object或者继承自Object的类型。
WeakMap之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。
"如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。" 这句话存疑,我估计是原始值无法设置弱引用,只有对象才可以有弱引用。
看ES标准:https://www.ecma-international.org/ecma-262/#sec-weakmap-objects
弱键
- WeakMap中“weak”表示弱映射的键是“弱弱地拿着”的。意思是,这些键不属于正式的引用,不会阻止垃圾回收。
# 函数
# 箭头函数
单个参数可以不加括号
不加大括号时只能写入一行表达式,函数默认返回该表达式的值
let sum = (x, y) => x + y
# 函数名
name属性
所有函数内置一个只读的
name属性,包含关于函数的信息,通常是函数名字符串或者其他标识符
# 理解参数
参数传递
- 没有函数签名校验机制,即定义的参数和实际传入的参数可以不同,ES函数既不关心传入参数的个数,也不关心参数的数据类型
- ES中函数参数定义只是为了方便使用,实际不校验
arguments
arguments对象是一个类数组对象(不是Array实例),可以从中获取实际传入的参数值
function sum(x, y) { return arguments[0] + arguments[1] }箭头函数没有arguments参数
arguments.length属性检查传入参数的个数arguments和命名参数始终保持同步,虽然在内存中分开存储function getArg(arg) { arguments[0] = 10 return arg } console.log(getArg(20)) // 10
箭头函数中的参数
- 箭头函数没有
arguments对象,只能通过命名参数访问
- 箭头函数没有
# 没有重载
- ES函数没有签名,因为参数是由包含零个或多个值的数组表示的。
- 没有函数签名,自然也就没有重载。
# 默认参数值
定义
function getName(name = 'default') { return name } console.log(getName('zhuli')) // zhuli console.log(getName()) // default默认参数不影响arguments
function getName(name = 'default') { return arguments[0] } console.log(getName('zhuli')) // zhuli console.log(getName()) // undefined可以引用先定义的参数
function getName(name = 'default', another = name) { return another } console.log(getName()) // default
# 参数扩展与收集
# 期约与异步函数
# 期约
期约基础
- 由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。
- 执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是通过调用它的两个函数参数实现的,这两个函数参数通常都命名为
resolve()和reject()。调用resolve()会把状态切换为兑现,调用reject()会把状态切换为拒绝。 - 执行器函数是同步执行的,因为执行器函数是期约的初始化程序。
Promise.resolve()实例化一个解决的期约,可以传递一个参数作为期约返回的值。- 如果传递的参数本身也是一个Promise,则直接返回该Promise。
Promise.reject()实例化一个拒绝的期约并抛出一个异步错误,可以传递一个参数作为错误对应的值。- 这个错误不能通过
try/catch捕获,只能通过拒绝处理程序捕获。 - 如果传递的参数本身也是一个Promise,这里跟
Promise.resolve()不一样,会将Promise作为错误值返回。
- 这个错误不能通过
期约的实例方法
当Promise进入落地状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。这个特性叫“非重入”特性。
“非重入”我理解是当Promise状态变化后,then或catch等方法不会插入当前同步线程来执行。
当然这里并不是then或catch本身而是then里面注册的
onResolved、onRejected处理函数会进入排队(微任务)在Promise的初始化函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。
建议统一使用错误对象,因为创建错误对象可以让浏览器捕获错误对象中的栈追踪信息。
# 异步函数
异步函数
由于所有需要访问Promise产生值的代码都必须放到处理程序里,这其实很不方便,ES8为此提供了
async/await关键字。async关键字用于声明异步函数,异步函数仍然具有普通函数的正常行为,不过有几点不同:
- 异步函数始终返回Promise,异步函数的返回值会经过
Promise.resolve()的包装后返回。 - 异步函数中抛出错误会返回拒绝的Promise。
- 异步函数始终返回Promise,异步函数的返回值会经过
使用await关键字可以暂停异步函数代码的执行,等待Promise解决。
await关键字会尝试“解包”对象的值,然后将这个值传给表达式,再恢复异步函数的执行。
await关键字期待一个实现thenable接口的对象(解包该对象),但常规的值也可以(直接返回)。
await关键字必须在异步函数中使用。
await关键字会释放拒绝的Promise,抛出错误。
async function test() { try { await Promise.reject('test error') } catch (error) { console.log(error) } } test()
停止和恢复执行
- JS运行时在遇到
await关键字时会记录在哪里暂停执行,等到await右边的值可用了,JS运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。因此,即使await后面跟着一个立即可用的值,也会被异步求值。
- JS运行时在遇到
# BOM
# window对象
Global作用域
- window对象被复用为ES的Global对象,所以通过
var声明的所有全局变量和函数都会变成window对象的属性和方法。let或const定义的全局变量和函数不会添加给window。
- window对象被复用为ES的Global对象,所以通过
导航与打开新窗口
window.open('http://www.baidu.com', 'topFrame', 'height=400,width=400,top=10,left=10,resizable=yes') // arg1: 链接 // arg2: 窗口名称,可以是特殊值: _self, _parent, _top, _blank // arg3: 窗口配置setInterval
setInterval(() => alert("Hello world!"), 10000)第二个参数:间隔时间,指的是向队列中添加新任务前等待的时间。
浏览器不关心任务什么时候执行或者执行的时长,只负责按照固定的时间间隔向队列添加任务。
执行时间短、非阻塞的回调函数比较适合setInterval。
生产环境下通常不使用,因为任务结束和下一个任务开始的时间间隔是无法保证的。
setTimeout
setTimeout(() => alert("Hello world!"), 10000)- setTimeout告诉JS引擎在指定的毫秒数之后将任务添加到队列中
系统对话框
alert() confirm() prompt()
这些对话框都是同步的模态对话框,即在它们显示的时候,代码会停止执行
# location对象
功能:
- 提供了当前窗口加载文档的信息
- 提供了URL信息
- 导航功能
查询字符串
API: URLSearchParams构造函数,可以解析查询字符串为对象属性
let qs = "?q=javascript&num=10"; let searchParams = new URLSearchParams(qs); searchParams.get("num"); // 10
导航
location.assign("http://www.baidu.com")
window.location = "http://www.baidu.com"
location.href = "http://www.baidu.com"
这三个操作等价,后面两个会自动调用第一个方法,启动导航到新的URL,同时添加历史记录
其他属性:location.hash/search/hostname/pathname/port
location.replace() 启动导航,但不添加到历史记录
location.reload() 重新加载页面,可能是从缓存加载 reload(true) 强制从服务器重新加载
# navigator对象
# history对象
导航
- history.go(1) go(-1) 前进或后退页数
- history.back() forward() go方法的简写
- history.length 历史记录的条数
历史状态管理
对于单页应用而言,需要做到改变URL但不刷新页面,H5提供了新的状态管理特性。
状态改变事件:
- haschange: URL中HASH变化时触发
- popstate: 点击后退按钮时触发
状态改变方法:
- pushState({foo:'bar‘}, 'title', 'new/url.html'):创建新的历史记录但浏览器不会发送请求
- 第一个参数为新的状态信息,会保存在history中
- 该信息会在后退事件(popstate)发生时传给event.state
- 第二个参数为状态标题,通常用不到
- 第一个参数为新的状态信息,会保存在history中
- pushState({foo:'bar‘}, 'title', 'new/url.html'):创建新的历史记录但浏览器不会发送请求
第三个参数可选,为要改变的URL相对路径
replaceState({foo:'bar‘}, 'title', 'new/url.html): 覆盖当前的状态,不会创建新的历史记录
# 表单脚本
# 富文本编辑
浏览器支持富文本的编写
# 两种方式
iframe + designMode
<body> <iframe name="richedit" style="height: 500px; width: 500px"></iframe> <script> window.addEventListener('load', () => { frames['richedit'].document.designMode = 'on'; }); </script> </body>contenteditable
适用于任何普通元素,不限于iframe
<body> <div class="editable" id="richedit" contenteditable></div> </body>