使用webpack打包一个自己的基础库

这篇文章我以一个大数相加的简单实现,带大家了解一下怎么使用webpack打包自己的js库,这个包有几个基本的要求

  • 我们需要满足各个模块导入的规范
  • 在生产环境下,我们需要让使用者使用一个压缩后的版本,但是在开发环境下,使用没有被压缩过的版本
  • 使用者可以直接用 import largeNumber from 'large-number' 这个代码进行引用
  • 需要发布到 npm
  1. 首先我们新建一个 large-number 的文件夹
  2. 执行 npm init -y,生成package.json文件
  3. 新建 src 文件夹,在文件件夹下新建 index.js 文件,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    export default function add(a, b){
    let i = a.length -1;
    let j = b.length -1;

    let carry = 0;
    let ret = "";
    while(i >= 0 || j>=0){
    let x = 0;
    let y = 0;
    let sum;
    if(i >= 0){
    x = a[i] - '0'
    i--
    }
    if(j >= 0){
    y = b[j] - '0'
    j--
    }

    sum = x + y + carry;
    if(sum >=10){
    carry = 1;
    sum -= 10;
    }else{
    carry = 0
    }

    ret = sum + ret
    }

    if(carry){
    ret = carry + ret
    }
    return ret
    }
  4. 根目录新建 webpack.config.js 文件,配置如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const TerserPlugin = require('terser-webpack-plugin')
    module.exports = {
    mode: "none",
    entry: {
    "large-number": "./src/index.js",
    "large-number.min": "./src/index.js"
    },
    output: {
    filename: "[name].js",
    library: "largeNumber",
    libraryTarget: "umd",
    libraryExport: "default"
    },
    optimization: {
    minimize: true,
    minimizer: [
    new TerserPlugin({
    include: /\.min\.js$/
    })
    ]
    }
    }

    根据这个包的需求,我们需要将代码打包为js和min.js两份代码,为了自己控制是否需要打包,所以引入了terser-webpack-plugin 这个插件,并且配置文件输出 libraryTarget为”umd”、libraryExport 为 “default”

  5. 为了区分开发环境与生产魂晶,我们需要在根目录新建index.js文件,代码如下

    1
    2
    3
    4
    5
    if(process.env.NODE_ENV === 'production'){
    module.exports = require('./dist/large-number.min.js')
    }else{
    module.exports = require('./dist/large-number.js')
    }
  6. 我们需要配置package.json的main属性为 ‘index.js’,

  7. 最后我们执行 npm publish 命令即可将包发布到npm仓库,该操作需要登录自己的npm账号,这部分内容不做阐述,如果包的名称重复的话,修改即可

webpack实现SSR

SSR的一些概念,本文不做阐述,直接开始配置

  • ssr的文件打包配置,与普通的webpack打包没有什么区别。 以下是一份多页面打包的逻辑,注意output的libraryTarget需要设置为umd,并且为了解决服务端不会处理css样式的问题,我们需要对打包使用的html模板进行一些特殊处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    let path = require('path');
    let HtmlWebpackPlugin = require('html-webpack-plugin')
    let {CleanWebpackPlugin} = require('clean-webpack-plugin')
    let MiniCssExtractPlugin = require('mini-css-extract-plugin')
    let OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    let CopyWebpackPlugin = require('copy-webpack-plugin')
    let HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
    let glob = require('glob')

    const setMPA = () => {
    const entry = {};
    const htmlWebpackPlugins = [];
    const entryFiles = glob.sync(path.join(__dirname, './src/*/index-server.js'))
    Object.keys(entryFiles)
    .map((index)=>{
    const entryFile = entryFiles[index];
    const match = entryFile.match(/src\/(.*)\/index-server.js/)
    const pageName = match && match[1];
    if(pageName){
    entry[pageName] = entryFile;
    htmlWebpackPlugins.push(
    new HtmlWebpackPlugin({
    template: `./src/${pageName}/index-server.html`,
    filename: `${pageName}-server.html`,
    chunks: ['commons', pageName],
    title: 'webpack-learn',
    inject: true,
    minify: {
    html5: true,
    removeAttributeQuotes: true,
    collapseWhitespace: true,
    },
    hash: true
    }),
    )
    }
    })

    return {
    entry,
    htmlWebpackPlugins
    }
    }
    let {entry, htmlWebpackPlugins} = setMPA()

    module.exports = {
    entry,
    output: {
    filename: '[name]-server.js',
    path: path.resolve('./dist'),
    libraryTarget: 'umd'
    },
    module: {
    rules: [{
    test: /\.js$/,
    use: ['babel-loader']
    }, {
    test: /\.less$/,
    use: [MiniCssExtractPlugin.loader, {
    loader: 'css-loader'
    }, {
    loader: 'less-loader'
    }, {
    loader: 'postcss-loader',
    options: {
    plugins: ()=>[
    require('autoprefixer')({
    overrideBrowserslist: [
    'last 2 version',
    '>1%',
    'IE 10',
    'ios 7'
    ]
    })
    ]
    }
    }, {
    loader: 'px2rem-loader',
    options: {
    remUnit: 75,
    remPrecision: 8
    }
    }]
    }, {
    test: /\.(png|jpg|gif|jpeg|svg)$/,
    use: [{
    loader: 'url-loader',
    options: {
    limit: 1024 * 3
    }
    }]
    }, {
    test: /\.(otf|woff|woff2|eot|ttf)$/,
    use: [{
    loader: 'file-loader',
    options: {
    name: '[name]_[hash:8].[ext]'
    }
    }]
    }]
    },
    plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin([{
    from: './src/doc',
    to: 'public'
    }]),
    new MiniCssExtractPlugin({
    filename: '[name]_[contenthash:8].css'
    }),
    new OptimizeCSSAssetsPlugin({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require('cssnano')
    }),
    new HtmlWebpackExternalsPlugin({
    externals: [{
    module: 'react',
    entry: 'https://unpkg.com/react@16/umd/react.production.min.js',
    global: 'React'
    }, {
    module: 'react-dom',
    entry: 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js',
    global: 'ReactDOM'
    }]
    }),
    ...htmlWebpackPlugins
    ],
    mode: 'production',
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html>
    <html lang="en">
    <head>
    ${require('raw-loader!./meta.html')}
    <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><%=htmlWebpackPlugin.options.title%></title>
    <script type="text/javascript" src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

    <script type="text/javascript">
    ${require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}
    </script>
    </head>
    <body>
    <div id="root"><!--HTML_PLACEHOLDER--></div>
    </body>
    </html>

    HTML模板中添加了<!--HTML_PLACEHOLDER-->这段注释,等到服务端渲染时,会替换打包生成的html文件中的这部分的内容。

  • 最主要的区别,在于入口文件的代码编写上。入口文件的代码实例如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    // import React from 'react';
    // import logo from './images/logo.svg'
    // import {a} from './tree-shaking';
    // import './style.less'
    // import './search.less'
    // import largeNumber from 'large-number-fx'
    const React = require('react');
    const logo = require('./images/logo.svg')
    const {a} = require('./tree-shaking') ;
    require('./style.less')
    require('./search.less')
    const largeNumber = require('large-number-fx')

    //模块热更新
    if(module.hot){
    module.hot.accept()
    }

    console.log(a())


    class Search extends React.Component{

    constructor(props){
    super(props);
    this.state = {
    Text: null
    }
    }

    loadComponent = () =>{
    import('./text.js').then((Text)=>{
    this.setState({
    Text: Text.default
    })
    })
    }

    render(){
    let { Text} = this.state
    let number = largeNumber('1111111111111111111', '2222222222222')
    return (
    <div className="search-text">
    Search Text 搜索组件
    <button onClick={this.loadComponent}>load text</button>
    <h1>{number}</h1>
    <img src={logo} />
    {Text? <Text />: null}
    </div>
    )
    }
    }
    module.exports = <Search/>;
    // ReactDOM.render(<Search/>, document.getElementById('root'))

    注意以上代码注释的部分:

    1. import 的语法需要全部更改为 cjs 的 require()语法
    2. ReactDOM.render()的方法需要更改为直接暴露出 <Search />组件即可
  • 我们需要新建一个server.js的文件,启动nodejs的server,来处理路由逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //避免报错
    if(typeof window === 'undefined'){
    global.window = {};
    }
    const fs = require('fs')
    const path = require('path')

    const express = require('express')
    const { renderToString } = require('react-dom/server')
    const SSR = require('../dist/app-server')
    const template = fs.readFileSync(path.join(__dirname, '../dist/app-server.html'), 'UTF-8')

    const server = (port) => {
    const app = express();
    app.use(express.static('dist'));
    app.get('/search', (req, res)=>{
    const html = renderMarkup(renderToString(SSR));
    res.status(200).send(html)
    })

    app.listen(port, ()=>{
    console.log('Server is running on port: ' + port)
    })
    }

    const renderMarkup = (str)=>{
    return template.replace('<!--HTML_PLACEHOLDER-->', str)
    }

    server(process.env.PORT || 3000)

    server.js的基本逻辑是:首先我们需要引用react-dom/server下的renderToString方法,用这个方法去解析我们打包后的输出文件,得到组件的一些html片段,在将打包后的html文件中的<!--HTML_PLACEHOLDER-->替换成该代码片段即可,这里使用fs去读取打包后的html文件是为了,保留之前css以及js的一些引用,使页面的样式可以正常显示。

react的Component和PureComponent的区别

结论

PureComponent是在Component的基础上对shouldComponentUpdate这部分进行了优化,通过对新旧state和新旧props各进行一次浅比较,通过判断是否发生变化来确认是否需要进行更新,以此来避免大量的重复渲染,优化性能。我们通过一段代码来看下两者的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

class ChildComponent extends React.Component {
render() {
console.log('ChildComponent render')
return(
<div>
{this.props.numbers}
</div>
)
}
}

class ChildComponent extends React.PureComponent {
render() {
console.log('ChildComponent render')
return(
<div>
{this.props.numbers}
</div>
)
}
}
class MainComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
numbers: [0]
}
}
handleClick() {
const arr = this.state.numbers;
arr.push(1);
this.setState({
numbers: arr
})
}
render() {
console.log('MainComponent render')
return(
<div>
<button onClick={this.handleClick} >click me</button>
<ChildComponent numbers={this.state.numbers}/>
</div>
)

}
}

export default MainComponent

在点击按钮时,MainComponent每次都会执行render函数,但ChildComponent只有继承PureComponent时,才会执行render,在react的源码中,PureComponent在判断是否需要更新组件时,进行了以下的比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
shouldUpdate = !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);


//shallowEqual函数实现

function is(x: any, y: any) {
return (
//这边是为了处理 0 === -0 和 NaN !== NaN的情况
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}

const hasOwnProperty = Object.prototype.hasOwnProperty;

function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}

if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}

return true;
}

注意:这个代码示例中, handleClick 方法中给state对象push元素时,用的是引用的方式,所以,当使用PureComponent时,因为我们操作的时同一个数据对象,所以就算修改了state,组件也不会进行更新,但是因为Component不进行这个处理,所以页面的数据可以正常发生变化,这也是使用PureComponent必须留意的地方。我们必须通过浅拷贝或者immutable的一些库,来避免这种错误的发生。
用浅拷贝的修改方式如下

1
2
3
4
5
6
7
8
 handleClick() {
const arr = this.state.numbers;
let newArr = [...arr]
newArr.push(1)
this.setState({
numbers: newArr
})
}

用immutableJS的修改方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
//使用immutablejs的List来初始化state
this.state = {
numbers: List([0])
}

//使用List的push函数重新生成arr
handleClick() {
const arr = this.state.numbers;
let newArr = arr.push(1)
this.setState({
numbers: newArr
})
}

js判断数据类型的方法

typeof

返回的结果用该类型的字符串(全小写字母)形式表示

1
2
3
4
5
6
7
8
9
10
typeof ''; // string 有效
typeof 1; // number 有效
typeof Symbol(); // symbol 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Function(); // function 有效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效

  • 对于基本类型,除null以外,均可以返回正确的结果
  • 对于引用类型,除function以外,一律返回object
  • 对于null,返回object
  • 对于function,返回function类型

    instanceof

    A instance B 用来判断A是否为B的实例,instanceof 检测的是原型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var O = R.prototype;// 取 R 的显示原型
    L = L.__proto__;// 取 L 的隐式原型
    while (true) {
    if (L === null)
    return false;
    if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
    return true;
    L = L.__proto__;
    }

    [] instanceof Array; // true
    {} instanceof Object;// true
    new Date() instanceof Date;// true

    function Person(){};
    new Person() instanceof Person;

    [] instanceof Object; // true
    new Date() instanceof Object;// true
    new Person instanceof Object;// true
  • instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

  • 当一个页面不止一个全局执行环境时(存在多个iframe),不同环境之前的数据类型使用instanceof判断有问题

    1
    2
    3
    4
    5
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    xArray = window.frames[0].Array;
    var arr = new xArray(1,2,3); // [1,2,3]
    arr instanceof Array; // false

    针对数组的这个问题,可以根据Array.isArray()判断

    constructor

    1
    2
    3
    function F(){}
    var f = new F()
    F.prototype.constructor === f.constructor
  • 原则上实例的constuctor属性就代表了数据类型,但是如果重写prototypez之后,就会有问题

    toString

    toString是Object原型的方法,调用该方法会返回当前对象的[[Class]],这是一个内部属性,格式为[object Xxx],Xxx就是数据的类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Object.prototype.toString.call('') ;   // [object String]
    Object.prototype.toString.call(1) ; // [object Number]
    Object.prototype.toString.call(true) ; // [object Boolean]
    Object.prototype.toString.call(Symbol()); //[object Symbol]
    Object.prototype.toString.call(undefined) ; // [object Undefined]
    Object.prototype.toString.call(null) ; // [object Null]
    Object.prototype.toString.call(new Function()) ; // [object Function]
    Object.prototype.toString.call(new Date()) ; // [object Date]
    Object.prototype.toString.call([]) ; // [object Array]
    Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
    Object.prototype.toString.call(new Error()) ; // [object Error]
    Object.prototype.toString.call(document) ; // [object HTMLDocument]
    Object.prototype.toString.call(window) ; //[object Window] window 是全局对象 global 的引用

js创建对象方式与集成方式

创建对象

  1. 工厂模式
    没有解决对象识别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name)
    }
    return o
    }
    var person1 = createPerson('tom', 29, 'web开发')
  2. 构造函数模式
    方法重复定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function CreatePerson(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
    alert(this.name)
    }
    }
    var person1 = new CreatePerson('tom', 29, 'web开发')
  3. 原型模式
    所有的实例有了相同的属性和方法

    1
    2
    3
    4
    5
    6
    7
    8
    function Person(){}
    Person.prototype.name = 'tom';
    Person.prototype.age = 29;
    Person.prototype.job = 'web开发';
    Person.prototype.sayName = function(){
    alert(this.name)
    }
    var person1 = new Person()
  4. 原型模式 + 构造函数模式
    所有的实例有了相同的属性和方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    }

    Person.prototype.sayName = function(){
    alert(this.name)
    }
    var person1 = new Person()
  5. 寄生构造函数模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function SpecialArray(){
    var values = new Array();
    values.push.apply(values, arguments);
    values.toPipedString = function(){
    return this.join("|");
    }
    return values
    }
    var colors = new SpecialArray('red', 'blue', 'green');
    alert(colors.toPipedString())
  6. 稳妥构造函数模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person(name, age, job){
    var o = new Object();
    o.sayName = function(){
    alert(name)
    };
    return o
    }
    var friend = Person('tom', 29, 'web开发');
    friend.sayName();

继承

  1. 原型链
    子类构造函数原型指向父类的实例,缺陷在于创建子类的时候,不能像父类的构造函数传递参数,而且原型中包含引用类型的值,修改后会影响所有的实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function SuperType(){
    this.property = true
    }
    SuperType.prototype.getSuperValue = function(){
    return this.property;
    }
    function SubType(){
    this.subpreoperty = false;
    }
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function(){
    return this.subproperty;
    }
    var instance = new SubType();
    alert(instance.getSuperValue())
  2. 借用构造函数继承
    在子类的构造函数中,使用call方法调用父类函数,生成实例,缺陷:只能继承到父类构造函数中的属性方法,原型上无法获取,并且,方法不能服用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function SuperType(){
    this.colors = ['red', 'blue', 'greem']
    }
    function SubType(){
    SuperType.call(this)
    }
    var instance1 = new SubType();
    instance1.colors.push('black');
    alert(instance1.colors);
    var instance2 = new SubType();
    alert(instance2.colors)
  3. 组合继承
    使用call继承父类的属性(可以传参,自定义)和方法,再修改子类原型指向父类实例继承父类原型的方法,缺陷:父类的构造函数执行了两次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function SuperType(name){
    this.name = name
    this.colors = ['red', 'blue', 'greem']
    }
    SuperType.prototype.sayName = function(){
    alert(this.name)
    }
    function SubType(name, age){
    SuperType.call(this, name);
    this.age = age
    }
    SubType.prototype = new SuperType();
    SubType.prototype.sayAge = function(){
    alert(this.name)
    }
  4. 原型式继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function object(o){
    function F(){}
    F.prototype = o;
    return new F();
    }
    var person = {
    name: 'tom',
    friends: ['shelby', 'jerry']
    }
    var anotherPerson = object(person);
    anotherPerson.name = 'Linda';
    anotherPerson.friends.push('bob');
    var anotherPerson2 = object(person);
    anotherPerson2.name = 'Grey';
    anotherPerson2.friends.push('barbie')
    alert(person.friends)
  5. 寄生式继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function object(o){
    function F(){}
    F.prototype = o;
    return new F();
    }
    function createAnother(original){
    var clone = object(original);
    clone.sayHi = function(){
    alert('hi')
    }
    return clone
    }
    var person = {
    name: 'tom',
    age: 29
    }
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();
  6. 寄生组合式继承
    通过构造函数继承属性,原型链方式继承方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function object(o){
    function F(){}
    F.prototype = o;
    return new F();
    }
    function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);
    prorotype.constructor = subType;
    subType.prototype = prototype
    }
    function Super() {}

    借助Object.create方法实现如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Super.prototype.getNumber = function() {
    return 1
    };
    function Sub() {}
    let s = new Sub();
    Sub.prototype = Object.create(Super.prototype, {
    constructor: {
    value: Sub,
    enumerable: false,
    writable: true,
    configurable: true
    }
    })

重学webpack

webpack安装

  • 安装webpack
  • 安装webpack-cli

webpack可以进行0配置

  • 新建src文件下,创建index.js文件
  • 执行npx webpack
  • 指定配置文件 webpack –config webpack.config.js

基础配置

  • entry

    1
    2
    3
    4
    5
    6
    7
    1. 单页应用
    entry: './src/index.js'
    2. 多页应用
    entry: {
    index: './src/index.js',
    main: './src/main.js'
    }
  • output

    1
    2
    3
    4
    5
    output: {
    filename: '[name].[hash:8].js',
    path: path.resolve(__dirname, 'build'),
    publicPath: 'http://www.baidu.com'
    },
  • mode

    1
    mode: 'development', // development or production
  • module

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    module: {
    /* 不去解析jquery和loadsh文件的依赖 */
    noParse: /jquery|lodash/,
    rules: [{
    test: /\.js$/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ],
    plugins: [
    /* 支持class 以及 修饰器 */
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose" : true }],
    "@babel/plugin-transform-runtime"
    ]
    }
    }
    }]
    }
  • resolve

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    resolve: { 
    /* 第三方包寻找地址 */
    modules: [path.resolve('node_modules')],
    /* 别名 */
    alias: {
    bootstrap: 'bootstrap/dist/css/bootstrap.css'
    },
    /* 第三方包引入的时候优先查找package.json下的哪个文件 */
    mainFields: ['style', 'main'],
    /* 入口文件的名字 */
    mainFiles: [],
    /* 扩展名,依次查找 */
    extensions: ['.js', '.css', '.json'],
    },
  • plugins

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    一些常用的插件
    1. html-webpack-plugin
    2. mini-css-extract-plugin
    3. clean-webpack-plugin
    4. copy-webpack-plugin
    5. webpack.DefinePlugin
    6. webpack.BannerPlugin
    7. webpack.IgnorePlugin

    plugins: [
    new webpack.DefinePlugin({
    DEV: JSON.stringify('dev')
    }),
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html',
    hash: true,
    minify: {
    removeAttributeQuotes: true,
    collapseWhitespace: true,
    },
    chunks: ['index']
    }),
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'main.html',
    hash: true,
    minify: {
    removeAttributeQuotes: true,
    collapseWhitespace: true,
    },
    chunks: ['main']
    }),
    new MiniCssExtractPlugin({
    filename: 'css/main.css'
    }),
    // new webpack.ProvidePlugin({ //在每个模块中注入$
    // $: 'jquery'
    // }),
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin([{
    from: 'doc',
    to: './doc'
    }]),
    new webpack.BannerPlugin('make 2019 by fanxing'),
    new webpack.IgnorePlugin(/\.\/local/, /moment/)
    ],
  • devServer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     devServer: {
    port: 3000,
    progress: true,
    contentBase: './build',
    compress: true,
    // proxy: {
    // '/api': {
    // target: 'http://localhost:3001',
    // pathRewrite: {'/api': ''}
    // }
    // },
    // before(app){
    // app.get('/api/user', (req, res)=>{
    // res.json({name: 'fanxing'})
    // })
    // }
    },
  • watch && watchOption

    1
    2
    3
    4
    5
    6
    7
    8
    9
    watch: false,
    watchOptions: {
    /* 询问频率 一秒询问1000次 */
    poll: 1000,
    /* 防抖 */
    aggregateTimeout: 500,
    /* 忽略监听 */
    ignored: /node_modules/
    },
  • externals

    1
    2
    3
    4
    externals: {
    //使用CDN后 依然像写 import $ from 'jquery' 写法
    jquery: "$"
    },
  • optimization

    1
    2
    3
    4
    5
    6
    7
    8
     optimization: {
    //使用 optimize-css-assets-webpack-plugin 压缩css后,js也必须使用 uglifyjs-webpack-plugin 来压缩
    minimizer: [
    //new uglifyjs(),
    new TerserJSPlugin({}),
    //new OptimizeCSSAssetsPlugin({})
    ]
    }

JS基础巩固系列:【8】手写一个简单的Promise

规范

Promise有一套标准的规范,Promises/A+,初步了解规范后,我们针对一些重要的点,实现一个简单的Promise函数

  1. 一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
  2. 处于等待态时,
    • promise,可以迁移至执行态或拒绝态
  3. 处于执行态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的终值
  4. 处于拒绝态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的据因
  5. 一个promise必须提供then方法来访问其当前值、终值和据因。
  6. then 方法可以被同一个 promise 调用多次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(func){
const _this = this;
_this.state = PENDING;
_this.value = null;
_this.resolveCallbacks = [];
_this.rejectCallbacks = [];

function resolve(value){
if(_this.state === PENDING){
_this.state = RESOLVED;
_this.value = value;
_this.resolveCallbacks.map(cb => cb(_this.value))
}
}

function reject(value){
if(_this.state === PENDING){
_this.state = REJECTED;
_this.value = value;
_this.rejectCallbacks.map(cb => cb(_this.value))
}
}

try{
func(resolve, reject)
}catch(err){
reject(err)
}
}
MyPromise.prototype.then = function(onResolve, onReject){
console.log('then 方法执行')
const _this = this;
onResolve = typeof onResolve === 'function'? onResolve: v=>v;
onReject = typeof onReject === 'function'? onReject: v=>{throw v};
if(_this.state === PENDING){
_this.resolveCallbacks.push(onResolve)
_this.rejectCallbacks.push(onReject)
}

if(_this.state === RESOLVED){
onResolve(_this.value);
}

if(_this.state === REJECTED){
onReject(_this.value);
}
}

let test = new MyPromise(function(resolve, reject){
setTimeout(()=>{resolve('success')}, 2000)
})
test.then(function(result){
console.log('第一次调用promise', result)
})
test.then(function(result){
console.log('第二次调用promise', result)
})

JS基础巩固系列:【7】原型链的难点

Image text

  1. prototype
    显示原型属性,只有函数才拥有该属性。
  2. __proto__
    隐式原型,每个对象都有,指向了创建该对象的构造函数的原型。
    对于实例对象来说,这个属性是在new的过程中被添加到实例上的,无论是 function Foo() 还是 let a = { b : 1 },内部的实现是使用了 new Function() 和 new Object()
  3. Function.__proto__ === Function.prototype
    对于对象来说,xx.__proto__.contrcutor 是该对象的构造函数,但是在图中我们可以发现 Function.__proto__ === Function.prototype,难道这代表着 Function 自己产生了自己?
    Function.prototype 这个特殊的对象,如果你在浏览器将这个对象打印出来,会发现这个对象其实是一个函数。
    我们知道函数都是通过 new Function() 生成的,难道 Function.prototype 也是通过 new Function() 产生的吗?答案也是否定的,这个函数也是引擎自己创建的。首先引擎创建了 Object.prototype ,然后创建了 Function.prototype ,并且通过 __proto__ 将两者联系了起来。这里也很好的解释了上面的一个问题,为什么 let fun = Function.prototype.bind() 没有 prototype 属性。因为 Function.prototype 是引擎创建出来的对象,引擎认为不需要给这个对象添加 prototype 属性。
    所以我们又可以得出一个结论,不是所有函数都是 new Function() 产生的。
    有了 Function.prototype 以后才有了 function Function() ,然后其他的构造函数都是 function Function() 生成的。
    现在可以来解释 Function.__proto__ === Function.prototype 这个问题了。因为先有的 Function.prototype 以后才有的 function Function() ,所以也就不存在鸡生蛋蛋生鸡的悖论问题了。对于为什么 Function.__proto__ 会等于 Function.prototype ,个人的理解是:其他所有的构造函数都可以通过原型链找到 Function.prototype ,并且 function Function() 本质也是一个函数,为了不产生混乱就将 function Function() 的 __proto__ 联系到了 Function.prototype 上。
  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • Function.prototype 和 Object.prototype 是两个特殊的对象,他们由引擎来创建
  • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的
  • 函数的 prototype 是一个对象,也就是原型
  • 对象的 __proto__ 指向原型, __proto__ 将对象和原型连接起来组成了原型链

JS基础巩固系列:【6】理解执行上下文和作用域

执行上下文

执行上下文可以理解为函数执行的环境,每一个函数执行时,都会给对应的函数创建这样一个执行环境。

1
2
3
4
5
6
7
8
9
10
11
var color = 'blue';
function changeColor(){
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();

以上代码的执行上下文栈的变化过程
Image text

作用域链

  1. 怎么查看作用域链
    每一个 javaScript 函数都表示为一个对象,更确切地说,是 Function 对象的一个实例。
    Function 对象同其他对象一样,拥有可编程访问的属性。和一系列不能通过代码访问的 属性,而这些属性是提供给 JavaScript 引擎存取的内部属性。其中一个属性是 [[Scope]] ,由 ECMA-262标准第三版定义。
    内部属性 [[Scope]] 包含了一个函数被创建的作用域中对象的集合。
    这个集合被称为函数的 作用域链,它能决定哪些数据能被访问到。
    来源于:《 高性能JavaScript 》;
    如果要通过代码查看一个函数的作用域链,我们可以查看其原型constructor属性中的[[Scopes]]属性
  2. 作用域链是在什么时候生成的
    这个问题我们用两段代码比较下:

    • 第一段

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var testValue = 'outer';
      console.log('函数声明前', foo.prototype)
      function foo(){
      console.log(testValue);
      console.log('函数执行时', foo.prototype);
      }
      function bar(){
      var testValue = 'inner';
      console.log(bar.prototype);
      foo();
      }
      bar();
      console.log('函数执行完毕', foo.prototype)
    • 第二段

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var testValue = 'outer';
      function bar(){
      var testValue = 'inner';
      foo();
      console.log(bar.prototype);
      function foo(){
      console.log(testValue);
      console.log(foo.prototype);
      }
      }
      bar();

      根据以上的代码,我们基本可以确认,函数的作用域链,是在js引擎完成初始化上下文执行环境时确认的。