本篇仅为个人学习笔记记录,不喜勿喷
JavaScript 模块会从运行时、文法和执行过程三个角度去剖析 JS 的知识体系。
类型
从运行时看类型:
Undefined
表示值未定义。
JavaScript中undefined是一个变量,不是一个关键字。为了避免变量被无意中篡改,建议使用 void 0 来获取undefined的值。
Null
表示值已定义,值为空。
在JavaScript中是关键字,可以放心使用。
String
UTF16 编码?
基本字符域(BMP)?
Number
IEEE 754-2008 规定的双精度浮点规则(什么是双精度浮点数?)
例外情况:
- NaN
- Infinity
- -Infinity
JavaScript 中有 +0 和 -0 的区别。(这样有什么意义呢?)
根据浮点数的定义,非整数的类型无法使用 == (===)比较,这就是为什么 0.1 + 0.2 不等于 0.3。
-
浮点数运算的精度问题导致了等式左右两边不相等,而是相差了微小的值
-
正确的比较方法是使用 JavaScript提供的最小精度值
// Math.abs 返回绝对值 Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
Boolean
true
false
Symbol(?)
ES6 引入的新类型 —— 一切非字符串的对象的key的集合
引入的原因:保证对象每个属性的名字都是独一无二的
Object
Q: 为什么给对象添加的方法能用在基本类型上?
. 运算符提供了装箱操作,会根据基础类型构造一个临时对象,使我们能够在基础类型上调用对应对象的方法。
对象的定义是属性的集合,属性分为:
- 数据属性
- 访问器属性
JavaScript中的类指的是什么?仅仅是运行时对象的一个私有属性,JavaScript中是无法定义类型的。
构造器:Number/String/Boolean,两用的:
- 跟new搭配时,产生对象
- 直接调用,表示强制类型转换
类型转换
- == 试图实现跨类型的比较,规则复杂到几乎没人可以记住(因此使用 ===)
- 加减乘除大于小于也会涉及到类型装换
String to Number
-
十进制、二进制、八进制、十六进制
-
正负号科学计数法
多数情况下,Number 比 parseInt 和 parseFloat 更好。
Number to String
装箱转换
含义:把基本类型装换为对应的对象
装箱机制会频繁产生临时对象,影响性能,要避免。
方法:
- call
- Object 函数
每一类装箱对象都有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取。借此可以准确识别对象对应的基本类型,比 instanceof 准确。
拆箱转换
ToPrimitive 函数:对象类型到基本类型的转换,即拆箱转换。
-
拆箱转换会尝试调用 valueOf 、toString 方法来获取拆箱后的基本类型。如果都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。
-
ES6 还支持显示指定 @@toPrimitive Symbol 来覆盖原有行为
面向对象还是基于对象
什么是面向对象?
自然对象的抽象
编程语言描述对象:
- 类(c++、java)
- 原型(JavaScript)
对象的特点:
- 对象具有唯一标识性(内存地址)
- 对象有状态(属性)
- 对象具有行为(属性)
JavaScript的对象具有高度动态性,可以在运行时动态加减对象属性。
JavaScript中的属性:
- 数据属性
- 四个特征
- value-属性的值
- writable-能否被赋值
- enumerable-for...in 能否枚举
- configurable-能否被删除或者改变特征值
- 四个特征
- 访问器属性
- 四个特征
- getter-函数或者undefined,取值时被调用
- setter-函数或者undefined,设置属性值时被调用
- enumerable
- Configurable
- 可以使用 get set 关键字创建访问器属性
- 四个特征
我们真的需要模拟类吗?
模拟面向对象,实际上做的事情是“模拟基于类的面向对象”。
JavaScript是基于原型的面向对象语言。
什么是原型?
- 基于类的编程提倡使用一个关注分类和类之间关系的模型
- 基于原型的编程更为提倡去关注一系列对象实例的行为,而后才关心如何将这些对象,划分到最近的、使用方式相近的原型对象,而不是将他们分成类。
原型系统的复制操作实现思路:
- 一个是并不是真的复制一个原型对象,而是使得新对象持有一个原型的引用(JavaScript)
- 另一个是切实地复制对象,从此两个对象再无关联
JavaScript的原型
ES6 提供的内置函数,更为直接访问操纵原型,三个方法:
- Object.create:根据指定的原型创建新的对象,原型可以为 null
- Object.getPrototypeOf:获取一个对象的原型
- Object.setPrototypeOf:设置一个对象的原型
早期版本中的类与原型
- ES3及之前版本: 使用
Object.prototype.toString方式访问 [[class]]属性 - ES5:使用
Symbol.toStringTag代替
new 操作:
new 运算接受一个构造器和一组调用参数,实际上做了几件事:
- 以构造器的 prototype 属性为原型,创建新对象(注意与私有字段 [[prototype]] 的区别)
- 将 this 和 调用参数 传给构造器,执行
- 如果构造器返回的是对象,则返回,否则返回第一步创建的对象
new 的行为,视图让函数对象在语法上跟类相似,但是,它客观提供了两种方式:
- 在构造器中添加属性
- 在构造器的prototype属性上添加属性
ES6 中的类
ES6 中加入了新特性 class,推荐使用。
在新的 ES 版本中,我们不需要模拟类,而是可以直接使用 class 新语法,而原型体系作为一种编程范式和运行机制存在。
你知道全部的对象分类吗
对象分类
- 宿主对象
- 内置对象
- 固有对象
- 原生对象
- 普通对象
JavaScript 执行
为什么Promise 里的代码比 setTimeout 先执行
JSC 引擎术语:
- 宏任务:宿主发起的任务叫做宏任务
- 微任务:JavaScript引擎发起的任务称为微任务
微任务和宏任务介绍
- 事件循环(node 术语)
- 宏观任务的队列就相当于事件循环
- 一个宏观任务中还包含着微观任务队列
- 事件循环的原理
- setTimeout 等宿主 API,会增加宏任务;Promise 则会增加微任务
Promise
- 同一个循环内,微任务始终比宏任务先执行
如何确定异步执行的顺序?
- 首先分析有多少个宏任务
- 分析在每个宏任务中有多少个微任务
- 根据调用次序,确定宏任务中的微任务的执行次序
- 根据宏任务的触发规则和调用次序,确定宏任务的执行次序
- 确定整个顺序
新特性:async/await
练习题:红绿灯:按照绿色 3 秒,黄色 1 秒,红色 2 秒循环改变背景色
function sleep(duration) {
return new Promise(function(resolve) {
setTimeout(resolve, duration);
});
}
async function changeColor(duration, color) {
if (document.getElementById('traffic-light')) {
document.getElementById('traffic-light').style.background = color;
await sleep(duration);
}
}
async function main() {
while (true) {
await changeColor(3000, 'green');
await changeColor(1000, 'yellow');
await changeColor(2000, 'red');
}
}
main();
闭包和执行上下文到底是怎么回事?
函数的执行过程。
闭包
闭包其实是一个绑定了运行环境的函数。
- 环境部分
- 环境:函数的词法环境(执行上下文的一部分)
- 标识符列表:函数中用到的未声明的变量
- 表达式部分:函数体
执行上下文:执行的基础设置
JavaScript标准把一段代码(包括函数),执行所需的所有信息定义为:“执行上下文”。
目前有多个版本的执行上下文,我们最好使用最新版,但是也要清楚旧版指的是什么。
var 声明与赋值
let
Realm
9.0 标准的新概念。(比如:通过iframe等方式创建多 window 环境。)
Realm 中包含一组完整的内置对象,而且是复制关系。
现在有多少种函数?
切换执行上下文,函数调用。
函数
- 普通函数:function关键字定义的函数
- 箭头函数
- class 中定义的函数
- 生成器函数:用 function* 定义的函数
- 类:用class定义的类,实际上也是函数
- 异步普通/箭头/生成器函数
this 关键字
类似变量。
this 是执行上下文中很重要的一个组成部分,同一个函数调用方式不同,得到的this值也不同。
普通函数的 this 值由“调用它所使用的引用”决定,奥秘在于:我们获取函数的表达式,它实际返回的并非函数本身,而是一个 Reference 类型。
Reference类型
Reference:用于描述对象属性访问、delete 等。
- 一个对象
- 一个属性值
当做一些算术运算时(或者其他运算),Reference 类型会被解引用(解引用是返回内存地址中保存的值),即获取真正的值(被引用的内容)来参与运算,而类似函数调用,delete等操作,都要用到 Reference 类型中的对象。
调用函数时使用的引用,决定了函数执行时刻的this值。
生成器函数、异步生成器函数和异步普通函数跟普通函数行为是一致的,异步箭头函数和箭头函数行为是一致的。
this 关键字的机制
- 函数定义时
- 为函数规定了用来保存定义时上下文的私有属性 [[Environment]]
- 切换上下文
- 函数执行时,创建一条新的执行环境记录,记录的外层词法环境会被设置为函数的 [[Environment]]
(未完待续...)
