JS模块化JStses6gitwebpack面试

[深入12] 前端模块化

2021-08-07  本文已影响0人  woow_wu7
image

导航

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

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

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

前置知识

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

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

作用域

<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标签来完成

浏览器解析js文件流程

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

js同步加载,js异步加载

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

defer:是延迟的意思

image

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(){...}())

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

前端模块化

模块的概念

非模块化存在的问题

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

模块化的好处

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

模块化需要解决的问题

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

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

微信图片_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(立即调用的函数表达式) 实现模块化


需要解决的问题:
(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规范


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>

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>

//定义没有依赖的模块
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中的模块方案

// 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 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};
// 正确
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; ----------------------->  没有指定对外的接口
export { foo, bar } from 'my_module';

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

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

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/6844903817272623117#heading-0
超完整(模块化):https://juejin.im/post/6844903744518389768#heading-0
js中哪些情况不能省略分号:https://blog.csdn.net/BigDreamer_/article/details/90230953
ES6模块化方案:http://es6.ruanyifeng.com/#docs/module
我的语雀:https://www.yuque.com/woowwu/msyqpd/gyys84
模块化参考资料 https://juejin.cn/post/6844903598480965646

上一篇下一篇

猜你喜欢

热点阅读