第一节课
课程目的
- 对JavaScript是如何运行的,有一个基本的了解
- 对编译工具有基本的认识
- 最好有能力去动手开发一些基本的编译工具或插件
编程语言和自然语言的区别
既要让人能看懂,又要机器能准确的认识,但这是冲突的。
抽象符号
名可名,非常名。名,只是当时当下表示事物的符号。
计算机不关心,但是人关心。
Uglify
抽象结构
抽象语法树
代码和AST的异同
- 同:同一种信息的两种不同的表达方式
- 不同:前者给人看,后者给机器看。
推荐书籍
提到的一些东西
- haskell
- lisp
- 编译后端
- 词法分析器 Parser
第二节课
一些作业问题
- 编译时和运行时
- 变量名会出现在哪些地方
表达式
- 一种完备形式定义的递归结构
函数是一等公民,如同值,是表达式的一部分
所谓的函数是抽象了一个过程
一旦有变量就会依赖环境
作业提交
第三问 超纲挑战
需要改造一个env,把它变成一个类,并且有自己的父节点(实现原型链),这样才能往上找到变量使用或修改它。也在这里才发现自己以前对于引用类型的理解是错的,JavaScript里的引用类型如同C中的指针,挺好用的,但是之前一直把它当成值,所以一开始还纳闷这样子怎么能修改到它真正的父节点,一是以为访问不到,二是以为空间会疯狂复制然后爆炸,但写了个demo才理解了所谓的引用类型。
class Person {
constructor(name, age, parent) {
this.name = name
this.age = age
this.parent = parent
}
setParent(age) {
this.parent.age = age
// console.log('zi\'fu',this.parent);
}
}
let a = new Person('fu',50)
let b = new Person('zi', 5, a)
console.log(a.age); //50
b.setParent(60);
console.log(a.age) //60
提交代码
创建分支 git branch homework-2
切换分支 git checkout homework-2
提交到本地仓库 add commit
创建远程分支 git push --set-upstream origin homework-2
发现它创建分支时会自动push了
第三节课
作业二讲解
第二个case的功能
用匿名函数实现了阶乘
声明、指令、作用域、控制流
声明不跟代码绑定,而是跟作用域绑定
声明只是在某个作用域里声明了一个名,它只做着一件事
指令的三种类型
- 赋值 => 改变作用域
- 调用 => 作用域的访问者
- 控制流 => if for tryCatch
- 改变代码执行流程
- 通过作用域(环境)
但在JavaScript中,赋值是一个表达式
作用域
作用:把某一个范围封闭起来,进行分治
JavaScript中的三种作用域
- global
- function
- block (在ES5时没有这个概念)
第四节课 字节码与虚拟机
上一节作业讲解
声明提升问题
return/continue/break的问题
字节码与虚拟机
虚拟机 & 指令
问题:不同CPU的指令集无法通用
C语言最初的目的——跨平台(听起来有点神奇但是又觉得合理哈哈哈哈
JavaScript 和 Java 在执行时的区别
- JavaScript多了一层“运行时”,如V8/node,完了之后才是跑在OS
- Java是跑在VM上,VM又跑在操作系统上面
JavaScript是解释性的,Java是编译型的
分类
- 寄存器虚拟机,和物理机类似(CPU与内存的交互过程)
- 栈式虚拟机
指令与环境
字节码
字节码不是机器码
- 一种从代码到执行的中间表示
- 一个二进制文件,里面存着数据和虚拟机执行的指令
设计一个虚拟机
环境:代码执行到某个位置的上下文信息/状态信息
栈的作用:记录入口环境
这部分的内容和《汇编语言》(王爽)讲的物理机的做法是非常类似的,一个道理。
如何实现PC(point current)的跳转,也就是子函数调用导致了PC的变化,子函数执行完后如何跳回父函数?如要在这调用之前记录下从哪里(父函数的某个地方)跳过来的,如果有很多层这个过程,其实就是一个栈的模拟。
虚拟机有哪些指令?
- 转移指令
- 运算指令
- 跳转指令
提到的一些东西
解释/编译 反射
JS / JIT / AOC
第五讲 线程、协程与异步
异步
异步非常依赖callback,就是结果返回的一个通知。
线程
切换线程的时候需要切换环境
JavaScript的单线程异步
单线程异步可以解决内存安全问题
单线程异步框架的好处:不用去处理任何线程之间的关系和问题
缺点:遇到运算密集型的场景,会出现一核出力,多核围观。
内存安全
协程
用户态的任务调度,解决线程之间高频切换带来的高开销。
由用户的程序来调度,是应用层上的一个任务。
对比
遇到的困难
构造函数this的指向
解决方案:当函数定义的时候,就在scope里设置一个变量this的值为当前的this
case 'FunctionDeclaration': {
let childScope = new Scope({}, scope, 'function')
node.body.body.forEach(v => {
hoisting(v, childScope)
})
let f = function (...args) {
// 每次使用函数都要去重新获取一下this
childScope.variables['this'] = this
childScope.isDefine['this'] = 'let'
node.params.map((v, index) => {
childScope.variables[v.name] = args[index]
childScope.isDefine[v.name] = 'let'
})
return evaluate(node.body, childScope).value
}
return f
}
在使用函数的时候,通过 new (func.bind.apply(func, [null].concat(args))) 来重新改变this的指向,当函数执行的时候,所取得的this就会是新的传入的this
大作业
历时25天,终于写完了。完结撒花 ~