js小黄书笔记(一)|作用域与闭包

127 阅读4分钟

编译原理

  • js与传统编译语言不同的是,它并不会被提前编译,也因此被称为脚本语言,动态语言。
  • 传统的编译语言
    • 分词/词法分析
      • 将你的代码分解为词法单元。比如var a=1变成var,a,=和1。
      • 分词与词法分析的主要区别在于,词法分析在判断独立词法单元时调用的是有状态的解析规则。
    • 解析/语法分析
      • 将分词或词法分析的结果转换成“抽象语法树”AST
      • var a=2:var操作符顶级节点-变量名identifier子节点,赋值表达式子节点-值结点
    • 代码生成
      • 将AST转换为可执行代码的过程。
  • JS语言是动态语言,这意味着编译速度是性能的一部分,它没有传统语言在编译时有大量时间做优化的条件。

作用域

作用域是根据名称查找变量的一套规则。

  • 编译运行代码对变量的处理
    • 编译器询问作用域是否有一个该名称变量的同名变量存在。如果存在则会忽略这个声明,没有则要求作用域在当前作用域集合声明出一个名叫a的变量。
    • 编译器为引擎生成用于运行的代码。代码内容为是否存在a,存在则使用,不存在则继续查找作用域链。
    • 进行下文的两种查询方式并进行运算或赋值操作。
  • 查找的两种方式
    • LHS:赋值操作的左侧查询,得到这个变量的地址。后续对这个地址指向内容操作。
    • RHS:赋值操作右侧查询,或者非左侧查询,得到变量地址所指向地址中的。后续只读的使用值。
  • 此书吧作用域链比作盖楼(感觉并不是很形象),依然以我的压栈理解走。
  • 对于LHS引用,当作用域链中没有找到目标会在全局作用域创建一个并调用(非严格模式)。严格模式或RHS引用中未找到会抛出ReferenceError|引用错误。

词法作用域

  • 全局变量会成为全局对象的属性(window),来避免链上的同名遮蔽

欺骗词法

  • eval:接受一个字符串,并再编译时吧这个字符串编译成可执行代码
    • eval("var a = 1;") ---编译---> var a = 1;
    • settimeout,new function也有类似的参数,但是过时且不安全
    • 动态生成代码的场景非常罕见,且造成的性能损耗也远大于它的价值
  • with:需要注意的是,with中的属性调用如果再对象中没有被找到,并不会像直接赋值obj.key一在对象上创建一个属性,而是把这个属性泄露挂载到全局作用域window原因是with实际上是把包含的内容放到了一个其属性下的隔离作用域,然后再这个作用域内LSH寻找对应的变量,如果没有则在作用域链上向上寻找也就是window,在没有找到时就会在window上创建同名变量用于with的隔离作用域内的LSH调用 image.png
  • 他们的性能:编译阶段完全不知道你要做什么,极慢别用

严格模式中这俩都被禁止了~

函数作用域/块作用域

  • 立即执行函数
    • 痛点:将一段代码使用函数包裹并隐藏,需要声明新函数并使用函数名调用。
    • 使用立即执行函数(function (){})(),并不是从function声明操作符开始。编译时函数被当作一个函数表达式处理而不是一个声明函数。
    • 既隔离了函数作用域,也防止声明新函数污染作用域。
  • 其他内容对我来说不新,略

提升

  • function函数声明提升优先级比var变量声明高,且函数声明会把函数内容赋值。
  • 但是同名定义很捞别用

闭包

  • 当函数可以记住并访问所在词法作用域时就产生了闭包,即使函数时在当前词法作用域之外进行。
  • 子函数作用域对父级作用域有调用并被外部引用,这个引用链保护并记录了父作用域中的值而不被回收。
  • this谁调用指向谁(类似动态作用域,寻找调用栈)/变量名引用为定义时引用或理解为一个指向原先函数的地址指针(静态的引用指向,寻找词法作用域链)
    • js不具备动态作用域,它只有定义时确定的词法作用域。
    • 箭头函数没有自己的this,他在定义时继承了父词法作用域中的this。