Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

模块化 以及 webpack如何处理模块化 #7

Open
QiandaoLakeYY opened this issue May 5, 2019 · 1 comment
Open

模块化 以及 webpack如何处理模块化 #7

QiandaoLakeYY opened this issue May 5, 2019 · 1 comment

Comments

@QiandaoLakeYY
Copy link
Owner

No description provided.

@QiandaoLakeYY
Copy link
Owner Author

QiandaoLakeYY commented May 5, 2019

最近遇到一个问题。假如我需要用到 lodash 里的 clonedeep 方法,下面两种引入有何区别?
1:import * as _ from 'lodash' or const lodash = require('lodash')
2:import {cloneDeep} from 'lodash' or const cloneDeep = require('lodash').cloneDeep

tip:
这里所说的区别主要是实际项目中,利用webpack打包好文件的大小。也就是说第一种写法是不是把所
有的方法都引入了,第二种是不是只是引入了需要的,还是说也是全部都引入了。

这个问题涉及到以下的知识点:
1:前端模块化有哪些方案,区别是什么;
2:为什么既可以用import 又 可以用require;
3:webpack是如何处理模块化的。接下来我们一一分析。首先分析第一个问题。

前端模块化有哪些方案,区别是什么

我们熟知的模块化方案有:AMD,CMD,Commonjs,ES6。其中AMD,CMD 分别是requirejs和seajs在推广中延伸出来的规范。现在大家比较常用的是Commonjs,ES6这两种方案。所以我们着重分析这两种

Commonjs

我们来看第一个例子

// main.js
console.log('加载了main 模块')
var add = require('./add.js')
console.log('add 模块加载完毕')
console.log(add(1,1))
var square = require('./square.js')
console.log(square.square(3))
// add.js
console.log('加载了add模块')
var add = function(x,y) {
    return x + y
}
module.exports = add
// square.js
console.log('加载了square模块')
var multiply = require('./multiply')
var square = function(num) {
  return multiply.multiply(num, num)
}
module.exports.square = square
// multiply.js
console.log('加载了 multiply 模块')
var multiply = function(x, y) {
    return x * y
}

module.exports.multiply = multiply

我们运行main.js 打印如下:

 加载了main模块 -> 加载了 add 模块 -> add 模块加载完毕 -> 2 -> 加载了 square 模块->加载了multiply 模块 -> 9

从运行结果可以看出,commonjs在加载模块时的基本准则是
遇到需要加载的模块就去加载该模块,并且执行被加载模块,加载完以后再回来继续执行

接下来我们来看看另外的一个例子

// count.js
var count = 3
function incCount() {
  count++
  console.log('in count.js count = ', count)
}
module.exports = {
  count: count,
  incCount: incCount
}
// main.js  
var mod = require('./count')
console.log(mod.count)
mod.incCount()
console.log(mod.count)

运行main.js 打印结果如下

3 -> in count.js count = 4 -> 3

这里的count是基本类型,如果是对象呢?我们来看下

// count.js
var counter = { value: 3 }

function incCount() {
  counter.value++
}

module.exports = {
    counter: counter,
    incCount: incCount
}
// main.js
var mod = require('./count')

console.log(mod.counter)
mod.incCount()
console.log(mod.counter)

运行main.js 结果如下

{value: 3} -> {value: 4}

从以上两个例子可以看出commonjs的另外一个特性
commonjs下的引入是值的拷贝,简单来说类似于 = 赋值

接下来再看考虑一种比较特殊的情况,循环引入

// main.js
const a = require('./a.js')
// a.js
console.log('a is start')
let b = require('./b.js')
console.log('in a, bvalue is %j', b.bValue)
exports.aValue = 'this is a value'
console.log('a is end')
//b.js
console.log('b is start')
let a = require('./a.js')
console.log('in b, a value is %j' , a.aValue )
exports.bValue = 'this is b value'
console.log('b is end')

运行main.js 打印结果如下

开始加载 main 模块 -> a is start -> b is start -> in b, a value is undefined -> b is end
-> in a, bvalue is 'this is b value' -> a is end -> main 模块执行完毕

上面的循环逻辑如下所示
3
我们知道commonjs是遇到require就去加载模块,当在b.js里遇到require(a)的时候,因为a之前已经在mainjs里面require过了。所以为了防止死循环,就会默认此时a已经加载完毕。先执行b里面的代码。等b执行完后再去执行a剩余的

我们再来看一个循环引用的例子

// a.js 
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')
console.log('b starting')

exports.done = false

const a = require('./a.js')

console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')

运行a.js 结果如下

a starting -> b starting -> in b, a.done = false -> b done -> in a,b.done = ture -> a.done

从上述例子可以得出以下结论

commonjs在引入的时候是遇到require就会去执行被引入的模块,等执行完再返回来执行

commonjs 是值的赋值,等于 = 操作

ES6

推荐一个文档链接,关于ES6模块化的实现
http://exploringjs.com/es6/ch_modules.html#sec_cyclic-dependencies-commonjs
我们继续通过demo来分析ES6模块中比较重要的一些特性

我们先来看最基本的

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ES6</title>
</head>
<body>
    <h1>content</h1>
    <script src="vender/main.js" type="module"></script>
</body>
</html>
// main.js
console.log('加载了main模块')
import { add } from './add.js'
console.log(add(1,1))

import {square} from './square.js'

console.log(square(3))
//add.js
console.log('加载了 add 模块')

var add = function(x, y) {
    return x + y
}

export {add}
// multiply.js
console.log('加载了 multiply 模快')

var multiply = function(x, y){
    return x * y

}

export {multiply}
// square.js
console.log('加载了 square 模块')

import { multiply } from './multiply.js'

var square = function(num) {
    return multiply(num, num)
}

export {square}

运行 index.html 页面,结果如下

加载了 add 模块 -> 加载了 multiply 模快 -> 加载了 square 模块 -> 加载了main模块 -> 2 -> 9

在mainjs里面, import { add } from './add.js' 和 import { square } from './square.js' 调换位置呢? 结果如下

加载了 multiply 模快 -> 加载了 square 模块 -> 加载了 add 模块 -> 加载了main模块 -> 9-> 2

由此,我们可以得出第一条结论
ES6会先进行引入模块,等引入好以后再执行

我们再来看另外一个例子

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>contentd</h1>
    <script src="main.js" type="module"></script>
</body>
</html>
// main.js
import {counter, incCount} from './counter.js'

console.log(counter)
incCount()
console.log(counter)
let counter = 3

function incCount() {
    counter++
}

export {counter, incCount}

我们运行,结果如下

3 -> 4

由此我们可以得出以下结论
ES6 是 值的引用,并不是简单的赋值操作,模块内部变量发生变化会对外部产生影响

我们再来看下ES6中循环引入的demo

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./main.js" type="module"></script>
</head>
<body>
    
</body>
</html>
// main.js
console.log('main.js is start')
import { aValue } from './a.js'

console.log('in main, a value is ', aValue)

console.log('mian.js is end')
// a.js
console.log('a is start')

import { bValue } from './b.js'

var aValue = 'this is a value'

console.log('in a, b value is ', bValue)

export { aValue }
// b.js
console.log('b is start')

import { aValue } from './a.js'

console.log('in b, a value is ', aValue)

let bValue = 'this is bValue'

export { bValue }

运行,结果如下

b is start -> in b, a value is undefined -> a is start -> in, a b value is this is bValue ->
main.js is start -> in main, a value is this is a value -> mian.js is end

上述引入关系如下图所示
2
在进行到第三步的时候,为了防止死循环,假定a已经执行完(其实并没有,只是暂停在 import { bValue } from './b.js' ),先执行b,等b执行完了以后再执行a剩余的。(commonjs也是这个逻辑)

webpack如何处理模块化

我们知道ES6适用于浏览器端,而Commonjs适用于node。那为什么我们在浏览器端也可以用Commonjs呢?
首先我们通过一个简单的例子来看下webpack是如何处理模块的引入的

https://github.com/QiandaoLakeYY/blog_code/blob/master/module/webpack4/simpleWebpack.js

简单来说就是首先确定好模块之前的先后引入关系,然后变成数组传递到匿名函数里进行执行

建议把上面这个函数看懂,有利于之后的理解
接下来我们来看下真正的webpack是如何处理的
https://github.com/QiandaoLakeYY/blog_code/tree/master/module/webpack4

建议在编译好的文件里面进行debug,一步一步来看。会比较清晰

结合上面的例子,假如我们把变量都引入了。但是没有都用呢?
把index.js改成如下

import { es6_name, es6_desc } from './module.js'

import { common_name, common_desc } from './common'

console.log(es6_name)
console.log(common_name)

然后 运行 npm run build-pro 我们看下编译好的文件,你会发现 es6 模块里的没有用到的变量已经被去掉了。但是commonjs里的还在。这是为什么呢?

ES6 是在编译时候就行加载的,所以能进行静态分析,webpack会在这一过程中加入tree-shaking,能去除没用的变量。而Commonjs是在运行时,不能进行进行静态分析。所以不能去掉

TIPS:
在实际中我们可能先用babel对ES6进行转码,转变之后会变成commonjs的语法,如果此时在进行webpack的打包。那么由于已经不是es6语法了。所以也就无法进行tree-shaking。如果要支持bable和tree-shaking,就需要额外的配置

回到最开始的问题。其实第一种和第二种写法效果是一的。如果要做到不引入全部,那么可以用下面的写法

const cloneDeep = require('lodash/cloneDeep') or import cloneDeep from 'lodash/cloneDeep'

参考

https://juejin.im/post/5a2e5f0851882575d42f5609
mqyqingfeng/Blog#108
http://exploringjs.com/es6/ch_modules.html#sec_cyclic-dependencies-commonjs
https://zhuanlan.zhihu.com/p/20731484

@QiandaoLakeYY QiandaoLakeYY reopened this May 5, 2019
@QiandaoLakeYY QiandaoLakeYY changed the title 模块化 模块化 以及 webpack如何处理模块化 May 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant