美文网首页
大厂面试JS模块化

大厂面试JS模块化

作者: Famous | 来源:发表于2022-05-06 16:54 被阅读0次

一、不得不说的历史

JS本身是为了满足简单的页面设计 ==> 页面动画 + 表单提交
无模块化 or 命名空间的概念

JS的模块化需求日益增长

幼年期: 无模块化

  • 1.开始需要在页面中加载不同的JS: 动画、组件、格式化
  • 2.多钟js 文件会被分在不同的文件中
  • 3.不同文件又被同一个模板所引用
<script src="jquery.js">
<script src="main.js">
<script src="lodash.js">

1. 知识点 - async & defer 的区别

普通js 是按照书写顺序从上往下执行,defer和async 标签可以使异步加载js,defer是“DOM渲染完再执行”,async是“下载完就执行”。
总结: 一般不依赖dom的js 可以用async,而有依赖或被依赖的,用defer
具体执行如下图


async&defer.jpg
问题可以被引导到
  • 1.浏览器渲染原理
  • 2.同步异步原理
  • 3.模块化加载原理
  • 4.污染全局作用域 => 不利于大型项目开发以及多人团队的共建

二、成长期: 模块化前夜 - ITFE(语法侧优化)

作用域的把控

为什么会污染

// 同一文件别的js中也有定义a   此时就会相互影响,看谁先执行了
<script> const a = 0; </script>
<script> let a = 1; </script >

利用函数的块级作用域 - 隔离区

例子

(() => {
   let a = 0;
   // ...
})();

尝试定义一个最简单的模块

const lifeModule = (()=>{
     let a = 0;
     // ...
})()
  • 面试追问: 独立模块本身的额外依赖,如何优化 ===> 作为参数传入

优化1: 依赖其他模块的传参型

const lifeModule = ((module1, module2)=>{
    let a = 0;
    // ...
})(module1, module2)
  • 面试追问: jquery 或者其它很多开源的模块加载方案 ==> 传参形式拿依赖,把自身透传出去一个$ 让外面的去调用

揭示模式 revealing => 上层无需了解底层实现,仅关注抽象 => 框架

  • 面试追问:
  • 1.继续模块化横向展开
  • 2.转向框架 jquery | vue | react 模块化细节
    比如vue 问题: computed,methods 等都是对象,而data是一个函数呢??
    答: 每个模块每个组件都是vue的实例,相互独立,而 data 是要做合并的,合并过程中要防止相互引用,变量污染,串扰~

三、成熟期:

CJS - Commonjs

node.js

特征:

  • 通过module + exports 去对外暴露接口
  • 通过require去引入外部模块
const module1 = require('./module1.js')
const module2 = require('./module2.js')
let a = 0;
const add=()=>{a++};
// 一种暴露出去
exports. add = add
// 第二种暴露出去
module.exports ={
   add
}
// exe
const { add } = require('./main.js')   
  • 追问 require 和 import 的区别?
    答: 简单讲,require 是运行时动态加载,而import 是静态编译,require 得到的是一个值的拷贝,而import 得到的是一个值的只读引用,所以引用模块值改变,require 方式不会改变,而 import 方式值会改变
    具体参考 require 和 import 的区别

  • 为什么一些开源项目为何要把全局,指针以及框架本身作为参数
    举例:

(funtion(window,$, undefined ){
     const  _show = funtion(){
      $('app').val('你好')
     }
     window.webShow = _show
})(window, jQuery)

答: 阻断思路

    1. 比如 window 全局作用域转化为局部作用域
    1. 编译时优化 (funtion(c){})(window) // window 被优化成c,便于压缩,及时处理释放
    1. 对于jquery 独立定制复写和挂载 防止全局串扰
    1. 上述的undefined - 防止重写 比如jquery 为了确保undefined是真的undefined,比如在外头 undefined = 1 会影响

commonjs 优点

  • 率先在服务端实现了,框架层面解决了依赖、全局变量污染的问题
  • 缺点
    针对了服务端的解决方案,异步依赖处理不是很完美

新的问题 ---- 异步依赖

AMD 规范

通过异步加载 + 允许定制回调函数 ===> 在回调函数里去处理逻辑,可以确保加载完模块后进行,让异步模块也能顺利进行
经典实现框架: require.js

新增定义方式:

// define 来定义模块
define(id, [depends], callback)
// require 来加载
require([module], callback)

模块定义地方:

define('mdaModule', [module1,  module2], (module1, module2)
=>{
// 业务逻辑
})

引入的地方

require(['amdModule', amdModule=>{
amdModule.increase();
}])
  • 追问: 如果想在amdModule中兼容之前的CJS已有代码
define('mdaModule', [], require
=>{
const module1 = require('./module1')
const module2 = require('./module1')
// 业务逻辑
})
  • 面试题 请手写兼容CJS 和 AMD ??
  // 判断关键object 还是function 2. exports? 还是 3. define
  (define('amdModule'), [], require(require, export, module) =>{
      const module1 = require('./module1')
      const module2 = require('./module1')
      // 业务逻辑
     let count = 0
     const add = ()=>{count++}
     export.add = add
})(
    // 目标: 一次性区分CJS 还是AMD 
    typeof module === 'object' && module.exports && typeof define !== 'function' ? 
// 是CJS
factory => module.exports = factory(require, exports, module)
: 
// AMD
define)

优点: 适合浏览器中异步加载模块的方案

缺点: 引入成本

CMD

按需加载: sea.js

    define('module', (require, exports, module) =>{
    let $ = require('jquery');
    // jquery
    let module1 = require('./module1')
    // module1相关逻辑
})

优点 按需加载,依赖就近

缺点是依赖打包,加载逻辑在于每个模块中,扩大了模块体积,同时功能上依赖编译

四、ES6模块化

走向新时代

新增定义:
引入: import
暴露: export
模块在引入和导出,定义的地方:

import module1 from './module1'
import module2 from './module2'
const aa = () => {
// 业务逻辑
}
// 导出
export const a = () => {}
export default {
    aa
}
  • 追问
    1. 性能 - 按需加载 import 分chunk 按需加载打包 require.esure 区别?
    1. 动态模块
      ES11 原生解决方案
import('./esModule.js').then(module1=>{
module1.add()
})

优点: 通过一种最终统一各端的形态,整合了js模块化的通用方案

局限性: 本质上还是一种运行时的依赖分析

五、解决模块化新思路 - 前端工程化

遗留

根本问题: 运行时进行依赖分析
解决方案: 线下执行

  • 面试题: 可否简述,实现一个编译时依赖处理的思路??
  <! doctype html>
  <script src="main.js"> </script>
 <script> 
// 给构建工具一个标识位
require.config(_FRAME_CONFIG_)
</script>
 <script> 
      require(['a', 'b'], ()=>{
     })
    </script>
 <html>

define('a', ()=>{
let b = require('b')
let c = require('c')
})

工程化实现

step1: 扫描依赖关系

   {
     a: ['b', 'c']
 }

step2: 根据依赖关系重置模板

  <! doctype html>
  <script src="main.js"> </script>
 <script> 
// 给构建工具一个标识位
require.config({
'deps':{
   'a': ['b','c'],
   'b': ['d'],
 }
})
</script>
 <script> 
      require(['a', 'b'], ()=>{
     })
    </script>
 <html>

step3: 执行工具,采用模块化解决方案处理

define('a', ['b', 'c'], ()=>{
export.run=()=>{}
})

优点

    1. 构建时生成配置,运行时去运行
    1. 最终转化成为可执行的依赖处理
    1. 可以拓展

简述: 首先是埋点标识位,然后根据我们需要做文件扫描,得到依赖关系,生成一个配置图,去替换埋点,因为在srcipt 中会被执行,执行时用我们预设的工具去执行即可

六、完全体 webpack 为核心的前端工程化 + mvvm框架的组件化 + 设计模式

这个我们都熟,就不再赘述~

相关文章

网友评论

      本文标题:大厂面试JS模块化

      本文链接:https://www.haomeiwen.com/subject/aytzyrtx.html