导航

[[深入01] 执行上下文](https://juejin.im/post/684490...
[[深入02] 原型链](https://juejin.im/post/684490...
[[深入03] 继承](https://juejin.im/post/684490...
[[深入04] 事件循环](https://juejin.im/post/684490...
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...
[[深入08] 前端安全](https://juejin.im/post/684490...
[[深入09] 深浅拷贝](https://juejin.im/post/684490...
[[深入10] Debounce Throttle](https://juejin.im/post/684490...
[[深入11] 前端路由](https://juejin.im/post/684490...
[[深入12] 前端模块化](https://juejin.im/post/684490...
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...
[[深入14] canvas](https://juejin.im/post/684490...
[[深入15] webSocket](https://juejin.im/post/684490...
[[深入16] webpack](https://juejin.im/post/684490...
[[深入17] http 和 https](https://juejin.im/post/684490...
[[深入18] CSS-interview](https://juejin.im/post/684490...
[[深入19] 手写Promise](https://juejin.im/post/684490...
[[深入20] 手写函数](https://juejin.im/post/684490...

[[react] Hooks](https://juejin.im/post/684490...

[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...

前置知识

js中省略每行结尾的 ; 分号时,需要注意的问题

  • <font color=red>() 小括号开头的前一条语句,小括号前必须加分号,或者在前一条语句结束时加分号</font>
  • <font color=red>[] 中括号开头的前一条语句,中括号前必须加分号,或者在前一条语句结束时加分号</font>
js中省略每行结尾的 ; 分号,需要注意的问题:

- () 小括号开头的前一条语句,小括号前必须加分号,或者在前一条语句结束时加分号
- [] 中括号开头的前一条语句,中括号前必须加分号,或者在前一条语句结束时加分号


例子:

(1) () 小括号开头的前一条语句,小括号前要加 ';' 分号,或者前一条语句结尾加分号
var a = 1
(function() { // 报错:Uncaught TypeError: 1 is not a function
  console.log(a)
})()
解决方法:
var a = 1
;(function() {   <---------
  // 在()前加分号
  // 或者在 var a = 1; 结尾加分号 
  console.log(a)
})()

(2) [] 中括号开头的前一条语句,需要在[]前面加上 ';' 分号,或者前一条语句结尾加分号
var a = 1
[1,2,3].forEach(item => console.log(item)) // Uncaught TypeError: Cannot read property 'forEach' of undefined
解决方法:
var a = 1
;[1,2,3].forEach(item => console.log(item))    <---------

作用域

  • 作用域:指变量存在的范围
  • 作用域分为:<font color=red>全局作用域,函数作用域,eval</font>

    <script>
    var a = 1
    var c = 2
    function x() {
      var a = 10 // 全局中也有a,但是函数作用域声明的a不会影响全局
      var b = 100
      c = 20 // 但是函数内部能修改全局的变量,(作用域链内部能修改外部的变量)
    }
    x()
    console.log(a) // 1
    console.log(c) // 20
    ;(function() { // 注意分号是必须的,因为()[]开头的前一条语句末尾,或者()[]开头加 ;
      console.log(c) // 20
    })()
    console.log(b) // 报错,b is not defined 函数外部不能访问函数内部的变量
    </script>

浏览器解析js文件的流程

浏览器加载 javascript 脚本,主要通过script标签来完成

  • (1) 浏览器一边下载html文件,一边开始解析 就是说:不等到html下载完成,就开始解析
  • (2) 解析过程中,遇到script标签就暂定解析,把网页的渲染控制权交给javascript引擎
  • (3) 如果script标签引用了外部脚本,则下载脚本并执行;如果没有直接执行script标签内的代码
  • (4) javascript引擎执行完毕,控制权交还渲染引擎,恢復解析html网页

    浏览器解析js文件流程
    
  • html是一边下载,一边解析的
  • script标签会阻塞html解析,如果耗时较长,就会出现浏览器假死现象
  • script标签之所以会阻止html解析,阻止渲染页面,是因为js可能会修改DOM树和CSSOM树,造成复杂的线程竞赛和控制权竞争的问题

js同步加载,js异步加载

  • <font color=red>同步加载:阻塞页面</font>
  • <font color=red>异步加载:不会阻塞页面</font>

script标签 异步加载的方式 async defer

defer:是延迟的意思

  • (1) script标签放置在body底部

    • 严格说不算异步加载,但是这也是常见的通过改变js加载方式来提升页面性能的一种方式
  • (2) <font color=red>defer 属性</font>

    • 异步加载,不阻塞页面,在DOM解析完成后才执行js文件
    • 顺序执行,不影响依赖关系
  • (3) <font color=red>async 属性</font>

    • 异步加载,加载不阻塞页面,但是async会在异步加载完成后,立即执行,如果此时html未加载完,就会阻塞页面
    • 注意:异步加载,加载不会阻塞页面,执行会阻塞页面
    • 不能保证各js文件的执行顺序
  • defer (1)加载:是异步加载,加载不阻塞页面;(2)执行:要DOM渲染完才执行,能保证各js的执行顺序
  • async (1)加载:是异步加载,加载不阻塞页面;(2)执行:加载完立即执行,不能保证各js的执行顺序
    bgcolor=orange>
  • <font color=red>defer,async,放在body底部,三种方法哪种好?</font>

    • 最稳妥的办法还是把<script>写在<body>底部,没有兼容性问题,没有白屏问题,没有执行顺序问题

tree命令 - 生成目录结构

tree [<Drive>:][<Path>] [/f] [/a]

tree命令生成目录结构


tree [<Drive>:][<Path>] [/f] [/a]
/f:显示每个目录中文件的名称
/a:使用文本字符而不是图形字符连接

例子:
C:.
│  index.html
│
├─a
│  └─b
│          ccc.js
│
└─requirejs
        a.js
        b.js
        c.js

立即调用的函数表达式 IIFE

IIFE Immediately-invoked Function Expressions 立即调用的函数表达式
(function(){...})()(function(){...}())

  • 需求:在函数定义后,立即调用该函数
  • 出现问题:<font color=red>如果直接在函数后面加上括号去调用,就会报错 </font>

    • 报错:function(){}();
  • 报错原因:<font color=red>function关键词出现在行首,会被js解析成语句( 即函数的定义 ),不应该以圆括号结尾,所以报错</font>
  • 如何解决:IIFE 利用立即调用的函数表达式去解决

    • 即让其成为一个表达式,而不是语句
    • <font color=red>语句不能以圆括号结尾,但是表达式可以 </font>
  • 需要掌握的知识点:

    • (1) IIFE如何传参
    • (2) 多个IIFE一起时,分号不能省略
    • (3) IIFE不会污染全局变量,因为不用为函数命名
    • (4) IIFE可以形成一个单独的作用域名,则可以封装一些外部无法读取的变量
    • (5) IIFE的两种写法
    IIFE 立即调用的函数表达式
    
    需要理解的几个方面:
    (1) IIFE如何传参 - (作为模块时,依赖项就是通过参数传递实现的)
    (2) 多个IIFE一起时,分号不能省略
    (3) IIFE不会污染全局变量,因为不用为函数命名
    (4) IIFE可以形成一个单独的作用域名,则可以封装一些外部无法读取的变量
    (5) IIFE的两种写法
    
    
    ---------------
    案例:
    
    const obj = {
    name: 'woow_wu7'
    };
    
    (function(params) { // params形参
    console.log(obj) // 这里访问的obj,不是函数参数传入的,而是访问的父级作用域的obj
    console.log(params)
    })(obj); // obj是实参
    // (1)
    // 注意:这里末尾的分号是必须的,因为是两个IIFE连续调用
    // 打印:都是 { name: 'woow_wu7' }
    // (2)
    // IIFE的两种写法:
    // 1. (function(){...})()
    // 2. (function(){...}())
    // 上面的(1)(2)都会被js理解为表达式,而不是语句
    // 表达式可以以圆括号结尾,函数定义语句不能以圆括号结尾
    // (3)
    // 因为function没有函数名,避免了变量名污染全局变量
    
    (function(params2){
    console.log(obj)
    console.log(params2)
    }(obj))

前端模块化

模块的概念

  • 将一个复杂程序的各个部分,按照一定的规则(规范)封装不同的块(不同的文件),并组合在一起
  • <font color=red>块 内部的变量和方法是私有的,只会向外暴露一些接口,通过接口与外部进行通信</font>

非模块化存在的问题

  • 对全局变量的污染
  • 各个js文件内部变量互相修改,即只存在全局作用域,没有函数作用域
  • 各个模块如果存在依赖关系,依赖关系模糊,很难分清谁依赖谁,而依赖又必须前置
  • 难以维护

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      var me = 'changeMe';
      console.log(window.me, '全局变量未被修改前的window.me') // changeMe
    </script>
    <script src="./home.js"></script> <!-- var home = 'chongqing' -->
    <script src="./me.js"></script> <!-- var me = 'woow_wu7' -->
    <script src="./map.js"></script> <!-- var map = 'baidu' -->
    </head>
    <body>
    <script>
      console.log(window.me, '全局变量被修改后的window.me') // 'woow_wu7' 说明污染了全局变量
      console.log(map, '模块内变量map被修改前') // baidu
      var map = 'Amap'
      console.log(map, '别的模块内mao居然被修改了') // Amap 说明模块内的变量被修改了,因为只有全局作用域
    </script>
    </body>
    </html>

模块化的好处

可以先记忆一下,有个概念

  • 更好的分离:避免一个html放置多个script,只需引入一个总的script
  • 避免命名冲突:模块内的变量不会影响到模块外,即各个模块可以有相同命令的变量
  • 更好的处理依赖:每个模块只用担心自己所依赖的模块,不用考虑其他模块的依赖问题,<font color=red>如果在一个html中引入很多script,各个模块(script)的依赖关系是很难分清楚的</font>
  • 更利于维护

模块化需要解决的问题

  • 模块的安全安装,即不能污染任何模块外的代码
  • 唯一的标识每个模块
  • 优雅的暴露api,不能增加任何全局变量
  • 可以引用其他依赖

<font color=red>模块化不同方案对比</font>

IIFE,CommonJS规范,AMD,CMD,ES6的模块化方案
commonjs -------------------------------- node.js使用的规范
AMD:Asynchronous Module Definition ----- 异步模块定义
CMD:Common Module Definition ----------- 通用模块定义

  • commonjs用于服务端,同步加载 ------------------------------ node.js使用的标准
  • AMD和CMD主要用于浏览器端,异步加载
  • ES6的模块方案:用于浏览器和服务器,通用方案,静态化
  • AMD依赖前置,依赖必须一开始写好,提前加载依赖 ( 依赖前置,提前执行 ) -------- RequireJS
  • CMD依赖就近,需要使用的时候,才去加载依赖 ( 依赖就近,延时执行 ) ------------- seajs
  • ES6模块是静态化的,在 ( 编译时) 就能确定模块得依赖关系,输入,输出;而AMD和CMD只能在运行时才能确定
  • 2021/3/23补充

    • ES6的模块化方案 => 动态更新
    • CommonJS => 模块输入的是 ( 值的缓存 ),不存在动态更新
    • ES6的模块中

      • export { a, b }; export后面的花括号并不是对象,而是一个接口,这个接口中有 a 和 b 两个变量
    • CommonJS中

      • module.export = {a: 1}; 导出的就是一个对象

        es6模块
        -------
        
        export var foo = 'bar';
        setTimeout(() => foo = 'baz', 500);
        
        上面代码输出变量foo,值为bar,500 毫秒之后变成baz

        微信图片_20210323232128.jpg

模块化的发展历程

(1)原始阶段 - 只用函数作用域

var a1 = 'a1'
function a1() {}
function a2() {}

缺点: 函数名会污染全局变量

(2)对象封装

var a3 = 'a3'
var a1 = {
    a2: function() {}
    a3: function() {}
}

优点:
1. a1对象的a3属性不会污染全局变量a3
2. 减少了全局作用域内的变量数量: 
    - 这里只有a3,a1 ------- 2个
    - 而全用函数:---------- 3个
缺点:
1. 还是会污染全局变量
2. 外部可以修改a1的属性,即会暴露所有属性并且可以被外部修改

(3)用 IIFE(立即调用的函数表达式) 实现模块化

IIFE Immediately-invoked Function Expressions

  • IIFE实现的模块化能解决的问题:

    • <font color=red>在其他地方都要不能修改模块内部的变量</font>

      • 不能在其他地方修改模块内的变量,说明每个模块都有自己的( <font color=red>单独的作用域</font> ),<font color=red>外部无法访问</font>
      • 函数就具有( <font color=red>函数作用域</font> ),函数外部无法访问函数内部的变量
    • <font color=red>模块内的变量不能污染全局变量</font>

      • 即模块内的变量的作用域不能是( 全局作用域 ),则可以用函数来解决( 函数作用域 )
      • 什么叫不能污染全局变量:

        • 即不能变量覆盖,模块内的变量不能覆盖全局的变量,从而影响全局变量
    • <font color=red>避免直接使用函数,函数名污染全局变量 </font>
    • <font color=red>IIFE实现的模块化,依赖其他模块,可用传参来解决</font>
    • <font color=red>模块需要暴露的方法和变量,都可以挂载都window上</font> => 权衡全局变量污染问题,可以使用特殊符号避免
    用 IIFE(立即调用的函数表达式) 实现模块化
    
    
    需要解决的问题:
    (1) 各个模块中定义的变量不能在模块外被修改,只能在该模块内修改,则每个模块需要形成单独的作用域
    (2) 模块内的变量不能污染全局变量 => 即不能在同一个作用域,用函数可以解决
    
    
    ------
    未解决以上问题前的模块:
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      var me = 'changeMe';
      console.log(window.me, '全局变量未被修改前的window.me') // changeMe
    </script>
    <script src="./home.js"></script> <!-- var home = 'chongqing' -->
    <script src="./me.js"></script> <!-- var me = 'woow_wu7' -->
    <script src="./map.js"></script> <!-- var map = 'baidu' -->
    </head>
    <body>
    <script>
      console.log(window.me, '全局变量被修改后的window.me') // 'woow_wu7' 说明污染了全局变量
      console.log(map, '模块内变量map被修改前') // baidu
      var map = 'Amap'
      console.log(map, '别的模块内mao居然被修改了') // Amap 说明模块内的变量被修改了
    </script>
    </body>
    </html>
    
    
    ------
    IIFE实现的模块化:
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script>
      (function(window,$) {
        var me = 'changeMe';
        console.log(me) // changeMe
        window.me = me
      })(window, jquery) 
      // 该模块依赖jquery
      // 需要暴露的变量,可以挂载到window对象上
    </script>
    </head>
    <body>
    <script>
     console.log(me, '外部无法访问,报错') 
     // me is not defined
    </script>
    </body>
    </html>

(4)CommonJS规范

  • <font color=red>Nodejs采用CommonJS规范,主要用于服务端,同步加载</font>
  • 同步加载

    • nodejs主要用服务端,加载的模块文件一般都存在硬盘上,加载起来比加快,不用考虑异步加载的方式
    • 但如果是浏览器环境,要从服务器加载模块,就必须采用异步方式,所以就有了AMD CMD 方案
  • <font color=red>module表示当前模块,module.exports是对外的接口,require一个模块其实就是加载module.exports属性</font>

    • 注意:在node.js中 moudle.exports 和 exports 的区别?
    
    nodejs中 moudle.exports 和 exports 的区别
    
    
    案例:
    --- modu.js ---
    const a = 11;
    module.exports = a ---------------------------------- module.exports 暴露模块
    
    --- modu2.js ---
    const b = 22;
    exports.bVar = b ------------------------------------ exports 暴露模块
    
    --- index.js ---
    const a = require('./modu.js') ---------------------- require 引入模块
    const b = require('./modu2.js')
    console.log(a, 'a') // 11
    console.log(b, 'b') // { bVar: 22 }
    console.log(b.bVar, 'b.bVar') // 22

(5) AMD - Asynchronous Module Definition异步模块定义 <font color=red>// RequireJS</font>

  • AMD用于浏览器端,异步加载,依赖前置
  • <font color=red>浏览器端不能使用commonjs同步加载方案</font>

    • <font color=red>是因为浏览器端加载js的文件在服务器上,需要的时间较长,同步加载会阻塞页面的加载和渲染</font>
    • <font color=red>而对于服务器端,文件则在硬盘中,加载和读取都十分快,所以可以同步加载,不用考虑加载方式</font>
  • RequireJS

    RequireJS
    
    (1) 目录结构:
    C:.
    │  index.html
    │
    └─requirejs
          b.js
          c.js
    
    (2) 例子
    b.js
    define(function () { // ----------------- define(function(){...}) 定义一个模块
    return 'string b'
    })
    
    c.js
    define(['./b.js'], function(res) { // --- define(['a'], function(res){...}) 定义一个有依赖的模块,c 依赖模块 b
    return res + 'c'
    });
    
    index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
    </head>
    <body>
    <script>
      require(['./requirejs/c.js'], function(res) { // 引入模块,并使用模块暴露的值,res 就是模块 c 暴露的值
        console.log(res, 'res')
      })
    </script>
    </body>
    </html>

(6) CMD - Common Module Definition通用模块定义 <font color=red>// seajs</font>

  • CMD用于浏览器端,异步加载,依赖就近,即使用时才会去加载
  • CMD是SeaJS 在推广过程中对模块定义的规范化产出

    //定义没有依赖的模块
    define(function(require, exports, module){
    var value = 1
    exports.xxx = value
    module.exports = value
    })
    
    //定义有依赖的模块
    define(function(require, exports, module){
    var module2 = require('./module1') //引入依赖模块(同步)
    require.async('./module2', function (m3) { //引入依赖模块(异步)
    })
    exports.xxx = value // 暴露模块,也可以用module.exports
    })
    
    // 引入使用模块
    define(function (require) {
    var a = require('./module1')
    var b = require('./module2')
    })

(7) ES6中的模块方案

  • <font color=red>ES6的模块化方案作为通用方案,可以用于浏览器端和服务器端</font>
  • <font color=red>ES6中的模块化方案,设计思想是静态化,即在编译时就能确定模块的依赖关系,输入变量,输出变量;而CommonJS和AMD和CMD都只有在运行时才能确定依赖关系,输入和输出</font>

    • CommonJS模块就是对象,输入时(引入模块)必须查找对象属性
  • CommonJS是运行时加载,因为只有运行时才能生成对象,从而得到对象,才可以访问对象的值
  • ES6模块不是对象,而是通过exports显示输出的代码,通过import输入
  • ES6的模块,默认采用严格模式

    // CommonJS模块
    let { stat, exists, readFile } = require('fs');
    (1) 实质上是整体加载模块fs,在fs对象上再去读取stat,exists等属性
    (2) 像CommonJS这种加载方式成为运行时加载,因为只有运行时才能得到这个对象
    
    
    
    // ES6模块
    import { stat, exists, readFile } from 'fs';
    (1) 实质是从fs模块加载3个方法,其他方法不加载 - 称为编译时加载或者静态加载
    (2) ES6在编译时完成加载,而不需要像CommonJS,AMD,CMD那样运行时加载,所以效率较高
      - 这会导致没法引用 ES6 模块本身,因为它不是对象
    
    
    
    // ES6模块的好处
    (1) 静态加载,编译时加载 ----- 效率较高,可以实现( 类型检查 )等只能靠( 静态分析 )实现的功能
    (2) 不再需要( 对象 )作为( 命名空间 ),未来这些功能可以通过模块提供
  • export 命令

    • 模块的功能主要由两个命令构成:import 和 export
    • <font color=red>export 可以输出变量,函数,类</font>
    • export 输出的变量,就是变量本来的名字,但是可以通过 <font color=red>as</font> 关键字来重命名
    // 报错
    export 1;
    
    // 报错
    var m = 1;
    export m;
    
    // 写法一
    export var m = 1;
    
    // 写法二
    var m = 1;
    export {m};
    
    // 写法三
    var n = 1;
    export {n as m};
    
    // 报错
    function f() {}
    export f;
    
    // 正确
    export function f() {};
    
    // 正确
    function f() {}
    export {f};
  • 模块的整体加载

    • 除了指定加载某个输出值,还可以使用整体加载。
    • 即用 <font color=red>*</font> 指定一个对象,所有输出值都加载到这个对象上面
  • export default

    • import 需要直到函数名或变量命,否则无法加载, - export default指定模块的默认输出
    • <font color=red>export default 其实时输出 default 的变量,所以他后面不能跟变量的声明语句</font>
    // 正确
    export var a = 1;
    
    // 正确
    var a = 1;
    export default a; ---------------->  export default a : 意思是把变量a赋值给default变量
    
    // 错误
    export default var a = 1
    
    // 正确
    export default 42; --------------->  注意:可以将值赋值给default变量,对外的接口是 default
    
    // 报错
    export 42; ----------------------->  没有指定对外的接口
  • <font color=red>export 与 import 的复合写法</font>

    export { foo, bar } from 'my_module';
    
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };

import()函数 - 支持动态加载模块

  • 因为ES6模块化方案是静态加载,即编译时就能确定依赖关系,输入,输出;不用等到运行时
  • <font color=red>那如何做到 动态加载 ?</font>
  • <font color=red>import(specifier) ---------- specifier:说明符</font>
  • <font color=red>import()返回一个promise</font>

    • import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。
    • 它是<font color=red>运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。</font>
    import() 语法
    
    
    (1) 按需加载:在需要的时候,再加载某个模块
    
    (2) 条件加载:可以根据不同的条件加载不同的模块,比如 if 语句中
    
    (3) 动态的模块路径:允许模块路径动态生成
    import(f()).then(...); // 据函数f()的返回值,加载不同的模块。
    
    (4) import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。
    // 因此,可以使用对象解构赋值的语法,获取输出接口。
    // import('./myModule.js').then(({export1, export2}) => {...});
    
    (5) 如果模块有default输出接口,可以用参数直接获得。
    // import('./myModule.js').then(myModule => {console.log(myModule.default)});
    // 上面 myModule 模块具有defalut接口,所以可以用 ( 参数.default ) 获取
    
    
    (6) 同时加载多个模块
    Promise.all([
    import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
     ···
    });

资料

详细 (模块化) 真的写得好:https://juejin.im/post/684490...
超完整(模块化):https://juejin.im/post/684490...
js中哪些情况不能省略分号:https://blog.csdn.net/BigDrea...
ES6模块化方案:http://es6.ruanyifeng.com/#do...
我的语雀:https://www.yuque.com/woowwu/...
模块化参考资料 https://juejin.cn/post/684490...


woow_wu7
10 声望2 粉丝