你不知道的JavaScript(上)--知识点汇总

前言:写这个博客是为了检验自己的阅读成果,同时把自己的一些理解分享给同样喜欢这书的人,我会持续把自己的一些新的理解更新进来,不足的地方希望大家不吝指教!

  1. 作用域是什么
    1. 编译原理:常规语言编译有下面三个步骤,区别在于JS是运行时编译,并且比下面步骤复杂得多
      1. 分词/词法分析、分词顾名思义就是把一整句代码拆成一个个词法单元, var a = 2; 进行分解,会得到以下结果:var、a、=、2、;,这个过程叫分词。而词法分析是指在分解过程中进行有状态的解析,比如分解a的过程。 
      2. 解析/语法分析、这里我的理解不是很深入,简单来说可以理解成把上个步骤分解出来的词法单元拼装成一个抽象语法树(AST),方便记录状态和解析操作。
      3. 代码生成、将AST转换为可执行代码
    2. 理解作用域:
      1. 编译器、负责上面编译的三个步骤,把编程出来的可执行代码交给引擎,把编译过程中的变量声明交给作用域处理。
      2. 引擎、执行代码,其过程中会跟作用域进行交流,交流方式按变量出现的位置可分为两种,出现在赋值操作符左边的叫LHS查询,查找赋值操作的容器。出现在赋值操作符右侧叫RHS查询,查询赋值操作数据源
      3. 作用域、复杂收集所有已声明的变量,并控制当前执行代码对变量的访问权限
    3. 作用域嵌套、当一个代码块嵌套在另一个代码块里,就会形成作用域嵌套。引擎在跟作用域进行交流时,如果在当前作用域无法找到对应变量,会逐级往上查找。
    4. 异常、区分lSH跟RHS的原因就在于进行赋值跟获取的时候,对应变量找不到报的异常是有区别的,比如声明的时候找不到该变量,作用域会自动生成一个(非严格模式下)
  2. 词法作用域
    1. 概念:词法作用域就是你在写代码的时候书写的那个块作用域
    2. 欺骗词法、可以通过以下方式修改词法作用域(非常非常非常影响性能,会导致引擎无法优化这些代码)
      1. eval(str); 通过eval函数可以传入一个代码片段的字符串,它会相当于你本身就书写在那里去执行。
      2. with(obj); 通过with函数传入一个对象,它会生成一个作用域,该作用域就是obj的作用域。注意:因为作用域的逐级查找原则,如果你在该作用域内做的操作可能会影响到外部作用域
  3. 函数作用域和块作用域
    1. 函数中的作用域、函数会在其内部生成一个独立的作用域
      1. 隐藏内部实现、这是个实现原则,程序应尽量隐藏内部逻辑,最小程度暴露必要内容
      2. 规避冲突、这里说的是规避变量命名上的冲突
        1. 模块管理、通过模块机制,把一些库或自定义的标识符导入到特定的作用域下,可以做到隔离和依赖
        2. 命名空间、给某些变量统一放到某个作用域下,如挂到一个对象下
    2. 通过函数包装代码已经可以做到隐藏了,但是这样比较麻烦,需要调用该函数代码才会执行,并且函数本身的标识符已经污染了所在作用域,可以通过以下方式解决
      1. 自执行函数(IIFE)、( function fn(){...} )(),函数名是可省略的,后面的括号也可省略
      2. 区分函数声明跟表达式,可以简单理解成第一个字符不是function的就是函数表达式,表达式可以匿名
      3. IIFE有个很别致的用法,本末倒置导致,可以增强代码阅读性。就是把需要执行的代码作为参数传给只执行函数,然后在内部调用。
    3. 块作用域,表面上JS是不具有块级作用域的,但是实际上它是存在的,比如try{}catch{}的catch中就是一个独立的作用域。所幸的是ES6中有了解决方案
      1. let、用let声明的变量会隐式的绑定在该代码块作用域中
      2. const、跟let一样,只是其值不可修改
  4. 提升
    1. 提升是因为编译在编译过程中,会先把变量的声明交给作用域处理,然后才生成可执行代码交给引擎
    2. 函数提升优先,且函数提升是连着内容。函数表达式不是
  5. 闭包(重头戏来了)
    1. 一句话定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
    2. 理解:在高程中对闭包的定义是一个有权访问另一个函数作用域的函数。哪要怎样才叫有权呢,这里有个概念叫词法作用域,即你书写代码的时候它所在的作用域。一个函数记住了它所在的词法作用域,并且跑到这个词法作用域外执行,就形成了闭包。

      函数是默认就会记住其所在的词法作用域的,因为函数在声明的时候会在其内部形成一个函数作用域,因为作用域链的规则,这个函数作用域能访问其父级作用域,也就是这个函数的词法作用域。所以第一个条件天生就满足了,第二个条件是函数需在其词法作用域外执行,js中函数是可以作为参数任意传播的,所以只需要把它传递给另一个作用域即可。一个函数在执行完之后它的作用域应该被垃圾回收机制回收的,但是此时如果它还存在引用,那么就不会被回收,这个引用就是闭包的特性

    3. 引用书中例子,bar记住了foo的语法作用域,外部调用bar的时候访问到了foo的私有属性
      function foo() { 
          var a = 2;
          function bar() { 
              console.log( a );
          }
          return bar; 
      }
      var baz = foo();
      baz(); // 2 —— 朋友,这就是闭包的效果。

       

    4. 由闭包理解模块、这个点书中说得比较少,只是简单的讲解利用闭包解决全局变量污染的问题,我打算后续抽时间专门写一篇文章说这个,现在现按书中的例子简单理解下

      var MyModules = (function Manager() {
          var modules = {};
          // name: 模块名称、deps 依赖的模块名、impl 模块内容
          function define(name, deps, impl) {
              // 通过模块名找到对应模块
              for (var i=0; i < deps.length; i++) {
                 deps[i] = modules[deps[i]];
              }
              // 把依赖注入到对应模块中
              modules[name] = impl.apply( impl, deps );
          }
      
          function get(name) {
              return modules[name];
          }
          return {
              define: define,
              get: get
          };
      })();
      
      MyModules.define( "bar", [], function() {
          function hello(who) {
            return "Let me introduce: " + who;
          }
          return {
          hello: hello
          };
      });
      MyModules.define( "foo", ["bar"], function(bar) {
          var hungry = "hippo";
          function awesome() {
              console.log( bar.hello( hungry ).toUpperCase() );
          }
          return {
              awesome: awesome
          };
      });
      
      var bar = MyModules.get( "bar" );
      var foo = MyModules.get( "foo" );
      console.log(
         bar.hello( "hippo" )
      ); // Let me introduce: hippo
      foo.awesome(); // LET ME INTRODUCE: HIPPO

       

你可能感兴趣的:(阅读)