重学前端-学习笔记-javascript篇

244 阅读8分钟

本篇仅为个人学习笔记记录,不喜勿喷

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 函数:对象类型到基本类型的转换,即拆箱转换。

  • 拆箱转换会尝试调用 valueOftoString 方法来获取拆箱后的基本类型。如果都不存在,或者没有返回基本类型,则会产生类型错误 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 新语法,而原型体系作为一种编程范式和运行机制存在。

你知道全部的对象分类吗

对象分类

  • 宿主对象
  • 内置对象
    • 固有对象
    • 原生对象
      • img
    • 普通对象

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]]

(未完待续...)