JS 模塊化、組件化、工程化相關的 15 道面試題
- 1.什么是模塊化?
- 2.簡述模塊化的發展歷程?
- 3.AMD、CMD、CommonJS 與 ES6 模塊化的區別?
- 4.它們是如何使用的?
- 5.export 是什么?
- 6.module.export、export 與 export defalut 有什么區別?
- 7.什么是組件化?
- 8.組件化的原則是什么?
- 9.全局組件與局部組件的區別?
- 10.如何注冊一個全局組件,並使用它?
- 11.局部組件又是如何注冊並使用的?
- 12.如何封裝一個高復用的 Vue 前端組件?
- 13.什么是前端工程化思想?
- 14.工程化可以解決什么問題?
- 15.是如何處理這些問題的?
問:1.什么是模塊化?
答:
將 JS 分割成不同職責的 JS,解耦功能,來用於解決全局變量污染、 變量沖突、代碼冗余、依賴關系難以維護等問題的一種 JS 管理思想,這就是模塊化的過程。
問:2.簡述模塊化的發展歷程?
答:
模塊化的發展主要從最初的無模塊化,發展到閉包式的 IIFE 立即執行解決模塊化,到后來的 CommonJS、 AMD、CMD,直到 ES6 模塊化規范的出現。
// jQuery風格的匿名自執行
(function(window) {
//代碼
window.jQuery = window.$ = jQuery; //通過給window添加屬性而暴漏到全局
})(window);
問:3.AMD、CMD、CommonJS 與 ES6 模塊化的區別?
答:
CommonJS 是 NodeJs 的一種模塊同步加載規范,一個文件即是一個模塊,使用時直接 require(),即可,但是不適用於客戶端,因為加載模塊的時候有可能出現‘假死’狀況,必須等模塊請求成功,加載完畢才可以執行調用的模塊。但是在服務期不存在這種狀況。
AMD (Asynchronous Module Definition):異步模塊加載機制。requireJS 就是 AMD 規范,使用時,先定義所有依賴,然后在加載完成后的回調函數中執行,屬於依賴前置,使用:define()來定義模塊,require([module], callback)來使用模塊。
AMD 同時也保留 CommonJS 中的 require、exprots、module,可以不把依賴羅列在 dependencies 數組中,而是在代碼中用 require 來引入。
// AMD規范
require(['modA'], function(modA) {
modA.start();
});
// AMD使用require加載模塊
define(function() {
console.log('main2.js執行');
require(['a'], function(a) {
a.hello();
});
$('#b').click(function() {
require(['b'], function(b) {
b.hello();
});
});
});
缺點:屬於依賴前置,需要加載所有的依賴, 不可以像 CommonJS 在用的時候再 require,異步加載后再執行。
CMD(Common Module Definition):定義模塊時無需羅列依賴數組,在 factory 函數中需傳入形參 require,exports,module,然后它會調用 factory 函數的 toString 方法,對函數的內容進行正則匹配,通過匹配到的 require 語句來分析依賴,這樣就真正實現了 CommonJS 風格的代碼。是 seajs 推崇的規范,是依賴就近原則。
// CMD規范
// a.js
define(function(require, exports, module) {
console.log('a.js執行');
return {
hello: function() {
console.log('hello, a.js');
}
};
});
// b.js
define(function(require, exports, module) {
console.log('b.js執行');
return {
hello: function() {
console.log('hello, b.js');
}
};
});
// main.js
define(function(require, exports, module) {
var modA = require('a');
modA.start();
var modA = require('b');
modB.start();
});
ES6 模塊化是通過 export 命令顯式的指定輸出的代碼,輸入也是用靜態命令的形式。屬於編譯時加載。比 CommonJS 效率高,可以按需加載指定的方法。適合服務端與瀏覽器端。
// a.js
export var a = 'a';
export var b = function() {
console.log(b);
};
export var c = 'c';
// main.js
import { a, b } from 'a.js';
console.log(a);
console.log(b);
區別:
AMD 和 CMD 同樣都是異步加載模塊,兩者加載的機制不同,前者為依賴前置、后者為依賴就近。
CommonJS 為同步加載模塊,NodeJS 內部的模塊管理規范,不適合瀏覽器端。
ES6 模塊化編譯時加載,通過 export,import 靜態輸出輸入代碼,效率高,同時適用於服務端與瀏覽器端。
問:4.它們是如何使用的?
答:
CommonJS 使用 module.exports,向外暴露模塊,使用 require()引入模塊,然后直接調用其中的數據或方法。
// m1.js 模塊定義
module.exports={
'date1':123,
'date2':{a:1,b:'hello'},
'function1':function(){...},
};
// main.js 模塊使用
var module=require(m1.js);
module.data1;
module.data2;
module.function1();
AMD 使用 define(['m1','m2','m3',...],function(m1,m2,m3,...){})來定義模塊內部的輸出,使用 require(['m1','m2',...],function(m1,m2,...){})來調用模塊並使用它。
// 定義:
define(['module1','module2','module3',...],function(module1,module2,module3,...){})
// 引入並調用:
require(['modA'], function(modA) {
modA.start();
});
CMD 用 define(factory)來定義模塊或使用它,factory 可以是數據也可以是方法,而后 define 內部通過 module.exports 向外部暴露 。在使用時,,通過工廠函數 function(require, exports, module)中的 require 來引入其他模塊並使用該模塊。
// 定義 m1.js
define(function (require, exports, module) {
//內部變量數據
var data = 'atguigu.com'
//內部函數
function show() {
console.log('module1 show() ' + data)
}
//向外暴露
exports.show = show
});
// m2.js
define(function(require, exports, module) {
module.exports = {
msg: 'I Will Back'
};
});
// m3.js
define(function (require, exports, module) {
const API_KEY = 'abc123'
exports.API_KEY = API_KEY
});
// m4.js
define(function (require, exports, module) {
//引入依賴模塊(同步)
var module2 = require('./module2')
function show() {
console.log('module4 show() ' + module2.msg)
}
exports.show = show
//引入依賴模塊(異步),最后執行,因為是異步的,主線的先執行完才會執行這
require.async('./module3', function (m3) {
console.log('異步引入依賴模塊3 ' + m3.API_KEY)
})
});
// main.js調用模塊並使用
define(function (require) {
var m1 = require('./m1')
var m4 = require('./m3')
m1.show()
m4.show()
});
5.export 是什么?
答:
export 是 ES6 中用於向外暴露數據或方法的一個命令。
通常使用 export 關鍵字來輸出一個變量,該變量可以是數據也可以是方法。
而后,使用 import 來引入,並使用它。
// a.js
export var a = 'hello';
export function sayHello(name) {
console.log('Hello,' + name);
}
// main.js
import { a, sayHello } from './a.js';
console.log(a);
console.log(sayHello('LiMing'));
問:6.export defalut、export 與 module.exports 有什么區別?
答:
都是用於向外部暴露數據的命令。
export defalut 與 export 是 ES6 Module 中對外暴露數據的。
export defalut 是向外部默認暴露數據,使用時 import 引入時需要為該模塊指定一個任意名稱,import 后不可以使用{};
export 是單個暴露數據或方法,利用 import{}來引入,並且{}內名稱與 export 一一對應,{}也可以使用 as 為某個數據或方法指定新的名稱;
module.exports 是 CommonJS 的一個 exports 變量,提供對外的接口。
// export defalut示例
// a.js
var a='Hello World!';
export defalut=a;
// main.js
import A from 'a.js';
console.log(A);
// export 示例
// b.js
export var b='b';
export function sayHello(name){
console.log(name+'Hello World!');
}
// main.js
import {b,sayHello} from 'b.js'
sayHello('LiMing');
console.log(b);
// module.exports示例
// c.js
var c='123';
function getValue(){
return c;
}
function updateValue(value){
c=value;
}
module.exports={
getValue,
updateValue
}
// main.js
import handleEvent from 'c.js';
handleEvent.getValue();
handleEvent.updateValue('456');
問:7.什么是組件化?
答:
組件化主要是從 Html 角度把頁面解耦成一個一個組件的過程,便於代碼的高復用、單一職責、方便維護、避免代碼冗余的一種解決方案。
問:8.組件化的原則是什么?
答:
組件的主要原則就是單一職責,高復用性。在前端的開發中,我們通常會對一個頁面解耦拆分成許多組件,確保一個組件只負責一個事情,同時盡可能減少外部的關聯,組件的相關邏輯只在組件內部處理,利用傳遞參數,事件通信來保持組件對外的通信。
對於 Vue 項目來說,我們一般把頁面中頻繁使用的組件,注冊為全局可用;其他的按需在頁面中局部使用。
問:9.全局組件與局部組件的區別?
答:
全局組件經過注冊后,全局可用,可以在任何地方使用,局部組件我們一般定義好后,在頁面需要的地方按需引入。
在 Vue 中,全局組件一般是全局使用的 Toast、Loading、Confirm 等,而局部組件是頁面中的某個功能的 vue 組件;
另外全局組件與局部組件注冊方式不同。
問:10.Vue 如何注冊一個全局組件,並使用它?
答:
一般我們定義好.vue 的組件后,通過 import 引入,使用 Vue.Component()來注冊全局注冊組件。這樣我們就可以在其他地方使用它了。
問:11.Vue 局部組件又是如何注冊並使用的?
答:
局部組件也是通過 import 引入,不同的是在 Vue 實例中 components 對象中注冊,我們就可以在 templete 中使用了。
問:12.如何封裝一個高復用的 Vue 前端組件?
答: 1.我們可以把頁面上的每個獨立的可視/可交互區域/相同頁面功能視為一個組件來解耦頁面; 2.當我們確定了一個 vue 組件后,再從 html、css 和 js 三個方面把組件的自身邏輯放入組件內部,然后通過 Props,$emit 來保持與父組件進行傳輸的傳遞與事件的通信,對於跨父子關系的組件間,使用 eventBus 來做通信; 3.最后我們就可以在使用的地方使用 import 引入,Vue.Component(),或實例的 components 來注冊它,最后在頁面模板中使用; 4.組件內部我們可能用到動態 class、動態 style、組件的 Props 雙向綁定、組件的生命周期等,具體邏輯需要根據組件本身功能來動態調優。
問:13.什么是前端工程化思想?
答:
前端工程化是把前端項目當成一個工程,制定合理的開發流程、工具集的使用以及合理的開發規范,來貫穿開發、優化、測試、代碼管理,到發布部署的一種管理思想。
問:14.工程化可以解決什么問題?
答:
前端工程化可以解決業務代碼維護難,開發流程不統一,代碼格式風格多樣性,測試覆蓋率低成效不顯著, 發布部署流程繁瑣復雜等問題。
問:15.是如何處理這些問題的?
答:
對於一個好的前端工程我們一般都是從以下方面來着手:
制定開發規范:包含高效率的開發流程、代碼命名/注釋/文檔規范,合理的目錄結構,數據請求規范,路由管理方案,靜態資源處理等等;
模塊化規范:統籌模塊化方案,合理設計全局模塊以及按需引入的局部模塊的使用,使用可靠的三方工具庫,盡可能減少代碼冗余,保證模塊的單一職責,高聚低耦等;
組件化開發:盡可能拆分為組件,封裝各個組件的功能,保證組件的高復用性、靈活性以及與外部的通信暢通;
性能優化:對工程作必要的性能測試,對於性能瓶頸項制定解決方案,並做持續優化。優化數據請求,合並請求,Node 中間件優化請求數據,對靜態資源壓縮/CDN 部署,盡可能使用字體圖標代替圖片資源,合理的使用數據緩存等等;
項目測試:編寫必要的單元測試,保證功能的可靠性,處理好邏輯的邊界問題以及合理的容錯機制;
發布部署:使用持續集成,持續交付的管理模式來簡化發布部署流程,提高發布部署的效率,將更多的時間轉移至功能開發測試上;
開發流程:優化開發流程,保證需求評審、技術評估、業務開發、測試、debugger 以及發布上線的高效率溝通,輸出留存必要的協作文檔資料,盡可能地減少無效溝通,采用小組式敏捷開發思想;
開發工具集:使用必要的工具集,提升開發管理效率,比如使用編輯器 VSCode 與其插件、代碼管理工具與平台 Svn/Git/SourceTree/Gitee/GitLab、Webapack 打包工具 + npm script 開發工作流、Tapd 協作平台、產品設計平台 Axure/ 藍湖、思維導圖 XMind 等等。