[深刻12] 前端模塊化

導航

[深刻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
[react] Hooksjavascript

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CIcss

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程html

前置知識

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))    <---------
複製代碼

做用域

  • 做用域:指變量存在的範圍
  • 做用域分爲:全局做用域,函數做用域,eval
<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文件流程

總結:
1. html是一邊下載,一邊解析的
2. script標籤會阻塞html解析,若是耗時較長,就會出現瀏覽器假死現象
3. script標籤之因此會阻止html解析,阻止渲染頁面,是由於js可能會修改DOM樹和CSSOM樹,形成複雜的線程競賽和控制權競爭的問題
複製代碼

js同步加載,js異步加載

  • 同步加載:阻塞頁面
  • 異步加載:不會阻塞頁面

script標籤 異步加載的方式 async defer

defer:是延遲的意思vue

  • (1) script標籤放置在body底部java

    • 嚴格說不算異步加載,可是這也是常見的經過改變js加載方式來提高頁面性能的一種方式
  • (2) defer 屬性node

    • 異步加載,不阻塞頁面,在DOM解析完成後才執行js文件
    • 順序執行,不影響依賴關係
  • (3) async 屬性react

    • 異步加載,加載不阻塞頁面,可是async會在異步加載完成後,當即執行,若是此時html未加載完,就會阻塞頁面
    • 注意:異步加載,加載不會阻塞頁面,執行會阻塞頁面
    • 不能保證各js文件的執行順序
  • defer (1)加載:是異步加載,加載不阻塞頁面;(2)執行:要DOM渲染完才執行,能保證各js的執行順序
  • async (1)加載:是異步加載,加載不阻塞頁面;(2)執行:加載完當即執行,不能保證各js的執行順序
  • defer,async,放在body底部,三種方法哪一種好?jquery

    • 最穩妥的辦法仍是把<script>寫在<body>底部,沒有兼容性問題,沒有白屏問題,沒有執行順序問題

tree命令 - 生成目錄結構

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

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

  • 需求:在函數定義後,當即調用該函數
  • 出現問題:若是直接在函數後面加上括號去調用,就會報錯
    • 報錯:function(){}();
  • 報錯緣由:function關鍵詞出如今行首,會被js解析成語句( 即函數的定義 ),不該該以圓括號結尾,因此報錯
  • 如何解決:IIFE 利用當即調用的函數表達式去解決
    • 即讓其成爲一個表達式,而不是語句
    • 語句不能以圓括號結尾,可是表達式能夠
  • 須要掌握的知識點:
    • (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))
複製代碼

前端模塊化

模塊的概念

  • 將一個複雜程序的各個部分,按照必定的規則(規範)封裝不一樣的塊(不一樣的文件),並組合在一塊兒
  • 塊 內部的變量和方法是私有的,只會向外暴露一些接口,經過接口與外部進行通訊

非模塊化存在的問題

  • 對全局變量的污染
  • 各個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
  • 避免命名衝突:模塊內的變量不會影響到模塊外,即各個模塊能夠有相同命令的變量
  • 更好的處理依賴:每一個模塊只用擔憂本身所依賴的模塊,不用考慮其餘模塊的依賴問題,若是在一個html中引入不少script,各個模塊(script)的依賴關係是很難分清楚的
  • 更利於維護

模塊化須要解決的問題

  • 模塊的安全安裝,即不能污染任何模塊外的代碼
  • 惟一的標識每一個模塊
  • 優雅的暴露api,不能增長任何全局變量
  • 能夠引用其餘依賴

模塊化不一樣方案對比

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只能在運行時才能肯定

模塊化的發展歷程

(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實現的模塊化能解決的問題:
    • 在其餘地方都要不能修改模塊內部的變量
      • 不能在其餘地方修改模塊內的變量,說明每一個模塊都有本身的( 單獨的做用域 ),外部沒法訪問
      • 函數就具備( 函數做用域 ),函數外部沒法訪問函數內部的變量
    • 模塊內的變量不能污染全局變量
      • 即模塊內的變量的做用域不能是( 全局做用域 ),則能夠用函數來解決( 函數做用域 )
      • 什麼叫不能污染全局變量:
        • 即不能變量覆蓋,模塊內的變量不能覆蓋全局的變量,從而影響全局變量
    • 避免直接使用函數,函數名污染全局變量
    • IIFE實現的模塊化,依賴其餘模塊,可用傳參來解決
    • 模塊須要暴露的方法和變量,均可以掛載都window上 => 權衡全局變量污染問題,可使用特殊符號避免
用 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採用CommonJS規範,主要用於服務端,同步加載
  • 同步加載
    • nodejs主要用服務端,加載的模塊文件通常都存在硬盤上,加載起來比加快,不用考慮異步加載的方式
    • 但若是是瀏覽器環境,要從服務器加載模塊,就必須採用異步方式,因此就有了AMD CMD 方案
  • module表示當前模塊,module.exports是對外的接口,require一個模塊其實就是加載module.exports屬性
    • 注意:在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異步模塊定義 // RequireJS

  • AMD用於瀏覽器端,異步加載,依賴前置
  • 瀏覽器端不能使用commonjs同步加載方案
    • 是由於瀏覽器端加載js的文件在服務器上,須要的時間較長,同步加載會阻塞頁面的加載和渲染
    • 而對於服務器端,文件則在硬盤中,加載和讀取都十分快,因此能夠同步加載,不用考慮加載方式
  • 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通用模塊定義 // seajs

  • 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中的模塊方案

  • ES6的模塊化方案做爲通用方案,能夠用於瀏覽器端和服務器端
  • ES6中的模塊化方案,設計思想是靜態化,即在編譯時就能肯定模塊的依賴關係,輸入變量,輸出變量;而CommonJS和AMD和CMD都只有在運行時才能肯定依賴關係,輸入和輸出
    • 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
    • export 能夠輸出變量,函數,類
    • export 輸出的變量,就是變量原本的名字,可是能夠經過 as 關鍵字來重命名
// 報錯
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 default
    • import 須要直到函數名或變量命,不然沒法加載, - export default指定模塊的默認輸出
    • export default 其實時輸出 default 的變量,因此他後面不能跟變量的聲明語句
// 正確
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 與 import 的複合寫法
export { foo, bar } from 'my_module';

// 能夠簡單理解爲
import { foo, bar } from 'my_module';
export { foo, bar };
複製代碼

import()函數 - 支持動態加載模塊

  • 由於ES6模塊化方案是靜態加載,即編譯時就能肯定依賴關係,輸入,輸出;不用等到運行時
  • 那如何作到 動態加載 ?
  • import(specifier) ---------- specifier:說明符
  • import()返回一個promise
    • import()相似於 Node 的require方法,區別主要是前者是異步加載,後者是同步加載。
    • 它是運行時執行,也就是說,何時運行到這一句,就會加載指定的模塊。
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]) => {
   ···
});
複製代碼

資料

詳細 (模塊化) 真的寫得好:juejin.im/post/5cb004…
超完整(模塊化):juejin.im/post/5c17ad…
js中哪些狀況不能省略分號:blog.csdn.net/BigDreamer_…
ES6模塊化方案:es6.ruanyifeng.com/#docs/modul…
個人語雀:www.yuque.com/woowwu/msyq…

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息