浅谈如何使用 webpack 优化资源


Posted in Javascript onOctober 20, 2017

前言

在前端应用的优化中,对加载资源的大小控制极其的重要,大多数时候我们能做的是在打包编译的过程对资源进行大小控制、拆分与复用。

本片文章中主要是基于 webpack 打包,以 React、vue 等生态开发的单页面应用来举例说明如何从 webpack 打包的层面去处理资源以及缓存,其中主要我们需要做的是对 webpack 进行配置的优化,同时涉及少量的业务代码的更改。

同时对打包资源的分析可以使用 (webpack-contrib/webpack-bundle-analyzer) 插件,当然可选的分析插件还是很多的,在本文中主要以该插件来举例分析。

TIP: webpack 版本 @3.6.0

一、打包环境与代码压缩

首先我们有一个最基本的 webpack 配置:

// webpack.config.js
const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const PROJECT_ROOT = path.resolve(__dirname, './');

module.exports = {
 entry: {
  index: './src/index.js'
 },
 output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[chunkhash:4].js'
 },
 module: {
  rules: [
   {
    test: /\.js[x]?$/,
    use: 'babel-loader',
    include: PROJECT_ROOT,
    exclude: /node_modules/
   }
  ]
 },
 plugins: [
  new BundleAnalyzerPlugin()
 ],
 resolve: {
  extensions: ['.js', '.jsx']
 },
};

执行打包可以看到一个项目的 js 有 1M 以上:

Hash: e51afc2635f08322670b
Version: webpack 3.6.0
Time: 2769ms
    Asset  Size Chunks          Chunk Names
index.caa7.js 1.3 MB    0 [emitted] [big] index

这时候只需要增加插件 `DefinePlugin` 与 `UglifyJSPlugin` 即可减少不少的体积,在 plugins 中添加:

// webpack.config.js
...
{
 ...
 plugins: [
  new BundleAnalyzerPlugin(),
  new webpack.DefinePlugin({
   'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
  }),
  new UglifyJSPlugin({
   uglifyOptions: {
    ie8: false,
    output: {
     comments: false,
     beautify: false,
    },
    mangle: {
     keep_fnames: true
    },
    compress: {
     warnings: false,
     drop_console: true
    },
   }
  })
 ]
 ...
}

可以看到这时候的打包输出:

Hash: 84338998472a6d3c5c25
Version: webpack 3.6.0
Time: 9940ms
    Asset  Size Chunks          Chunk Names
index.89c2.js 346 kB    0 [emitted] [big] index

代码的大小从 1.3M 降到了 346K。

(1)DefinePlugin

DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和发布模式的构建允许不同的行为非常有用。如果在开发构建中,而不在发布构建中执行日志记录,则可以使用全局常量来决定是否记录日志。这就是 DefinePlugin 的用处,设置它,就可以忘记开发和发布构建的规则。

在我们的业务代码和第三方包的代码中很多时候需要判断 `process.env.NODE_ENV` 来做不同处理,而在生产环境中我们显然不需要非 `production` 的处理部分。

在这里我们设置 `process.env.NODE_ENV` 为 `JSON.stringify('production')`,也就是表示讲打包环境设置为生产环境。之后配合 `UglifyJSPlugin` 插件就可以在给生产环境打包的时候去除部分的冗余代码。

(2)UglifyJSPlugin

UglifyJSPlugin 主要是用于解析、压缩 js 代码,它基于 `uglify-es` 来对 js 代码进行处理,它有多种配置选项:https://github.com/webpack-contrib/uglifyjs-webpack-plugin。

通过对代码的压缩处理以及去除冗余,大大减小了打包资源的体积大小。

二、代码拆分/按需加载

在如 React 或者 vue 构建的单页面应用中,对页面路由与视图的控制是由前端来实现的,其对应的业务逻辑都在 js 代码中。

当一个应用设计的页面和逻辑很多的时候,最终生成的 js 文件资源也会相当大。

然而当我们打开一个 url 对应的页面时,实际上需要的并非全部的 js 代码,所需要的仅是一个主的运行时代码与该视图对应的业务逻辑的代码,在加载下一个视图的时候再去加载那部分的代码。

因此,对这方面可做的优化就是对 js 代码进行按需加载。

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

在 webpack 中提供了动态导入的技术来实现代码拆分,首先在 webpack 的配置中需要去配置拆分出来的每个子模块的配置:

// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const PROJECT_ROOT = path.resolve(__dirname, './');

module.exports = {
 entry: {
  index: './src/index.js'
 },
 output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[chunkhash:4].js',
  chunkFilename: '[name].[chunkhash:4].child.js',
 },
 module: {
  rules: [
   {
    test: /\.js[x]?$/,
    use: 'babel-loader',
    include: PROJECT_ROOT,
    exclude: /node_modules/
   }
  ]
 },
 plugins: [
  new BundleAnalyzerPlugin(),
  new webpack.DefinePlugin({
   'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
  }),
  new UglifyJSPlugin({
   uglifyOptions: {
    ie8: false,
    output: {
     comments: false,
     beautify: false,
    },
    mangle: {
     keep_fnames: true
    },
    compress: {
     warnings: false,
     drop_console: true
    },
   }
  }),
 ],
 resolve: {
  extensions: ['.js', '.jsx']
 },
};

其中主要需要定义的则是 `output` 中的 `chunkFilename`,它是导出的拆分代码的文件名,这里给它设置为 `[name].[chunkhash:4].child.js`,其中的 `name` 对应模块名称或者 id,`chunkhash` 是模块内容的 hash。

之后在业务代码中 webpack 提供了两种方式来动态导入:

  • `import('path/to/module') -> Promise`,
  • `require.ensure(dependencies: String[], callback: function(require), errorCallback: function(error), chunkName: String)`

对于最新版本的 webpack 主要推荐使用 `import()` 的方式(注意:import 使用到了 Promise,因此需要确保代码中支持了 Promise 的 polyfill)。

// src/index.js
function getComponent() {
 return import(
  /* webpackChunkName: "lodash" */
  'lodash'
 ).then(_ => {
  var element = document.createElement('div');

  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;

 }).catch(error => 'An error occurred while loading the component');
}

getComponent().then(component => {
 document.body.appendChild(component);
})

可以看到打包的信息:

Hash: d6ba79fe5995bcf9fa4d
Version: webpack 3.6.0
Time: 7022ms
        Asset   Size Chunks       Chunk Names
lodash.89f0.child.js 85.4 kB    0 [emitted] lodash
    index.316e.js 1.96 kB    1 [emitted] index
  [0] ./src/index.js 441 bytes {1} [built]
  [2] (webpack)/buildin/global.js 509 bytes {0} [built]
  [3] (webpack)/buildin/module.js 517 bytes {0} [built]
  + 1 hidden module

可以看到打包出来的代码生成了 `index.316e.js` 和 `lodash.89f0.child.js` 两个文件,后者则是通过 `import` 来实现拆分的。

`import` 它接收一个 `path` 参数,指的是该子模块对于的路径,同时还注意到其中可以添加一行注释 `/* webpackChunkName: "lodash" */`,该注释并非是无用的,它定义了该子模块的 name,其对应与 `output.chunkFilename` 中的 `[name]`。

`import` 函数返回一个 Promise,当异步加载到子模块代码是会执行后续操作,比如更新视图等。

(1)React 中的按需加载

在 React 配合 React-Router 开发中,往往就需要代码根据路由按需加载的能力,下面是一个基于 webpack 代码动态导入技术实现的 React 动态载入的组件:

import React, { Component } from 'react';

export default function lazyLoader (importComponent) {
 class AsyncComponent extends Component {
  state = { Component: null }

  async componentDidMount () {
   const { default: Component } = await importComponent();

   this.setState({
    Component: Component
   });
  }

  render () {
   const Component = this.state.Component;

   return Component
    ? <Component {...this.props} />
    : null;
  }
 }

 return AsyncComponent;
};

在 `Route` 中:

<Switch>
 <Route exact path="/"
  component={lazyLoader(() => import('./Home'))}
 />
 <Route path="/about"
  component={lazyLoader(() => import('./About'))}
 />
 <Route
  component={lazyLoader(() => import('./NotFound'))}
 />
</Switch>

在 `Route` 中渲染的是 `lazyLoader` 函数返回的组件,该组件在 mount 之后会去执行 `importComponent` 函数(既:`() => import('./About')`)动态加载其对于的组件模块(被拆分出来的代码),待加载成功之后渲染该组件。

使用该方式打包出来的代码:

Hash: 02a053d135a5653de985
Version: webpack 3.6.0
Time: 9399ms
     Asset   Size Chunks          Chunk Names
0.db22.child.js 5.82 kB    0 [emitted]
1.fcf5.child.js  4.4 kB    1 [emitted]
2.442d.child.js   3 kB    2 [emitted]
 index.1bbc.js  339 kB    3 [emitted] [big] index

三、抽离 Common 资源

(1)第三方库的长缓存

首先对于一些比较大的第三方库,比如在 React 中用到的 react、react-dom、react-router 等等,我们不希望它们被重复打包,并且在每次版本更新的时候也不希望去改变这部分的资源导致在用户端重新加载。

在这里可以使用 webpack 的 CommonsChunkPlugin 来抽离这些公共资源;

CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。
首先需要在 entry 中新增一个入口用来打包需要抽离出来的库,这里将 `'react', 'react-dom', 'react-router-dom', 'immutable'` 都给单独打包进 `vendor` 中;

之后在 plugins 中定义一个 `CommonsChunkPlugin` 插件,同时将其 `name` 设置为 `vendor` 是它们相关联,再将 `minChunks` 设置为 `Infinity` 防止其他代码被打包进来。

// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const PROJECT_ROOT = path.resolve(__dirname, './');

module.exports = {
 entry: {
  index: './src0/index.js',
  vendor: ['react', 'react-dom', 'react-router-dom', 'immutable']
 },
 output: {
  path: path.resolve(__dirname, 'dist'),
  filename: '[name].[chunkhash:4].js',
  chunkFilename: '[name].[chunkhash:4].child.js',
 },
 module: {
  rules: [
   {
    test: /\.js[x]?$/,
    use: 'babel-loader',
    include: PROJECT_ROOT,
    exclude: /node_modules/
   }
  ]
 },
 plugins: [
  new BundleAnalyzerPlugin(),
  new webpack.DefinePlugin({
   'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
  }),
  new UglifyJSPlugin({
   uglifyOptions: {
    ie8: false,
    output: {
     comments: false,
     beautify: false,
    },
    mangle: {
     keep_fnames: true
    },
    compress: {
     warnings: false,
     drop_console: true
    },
   }
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'vendor',
   minChunks: Infinity,
  }),
 ],
 resolve: {
  extensions: ['.js', '.jsx']
 },
};

运行打包可以看到:

Hash: 34a71fcfd9a24e810c21
Version: webpack 3.6.0
Time: 9618ms
     Asset   Size Chunks          Chunk Names
0.2c65.child.js 5.82 kB    0 [emitted]
1.6e26.child.js  4.4 kB    1 [emitted]
2.e4bc.child.js   3 kB    2 [emitted]
 index.4e2f.js 64.2 kB    3 [emitted]     index
 vendor.5fd1.js  276 kB    4 [emitted] [big] vendor

可以看到 `vendor` 被单独打包出来了。

当我们改变业务代码时再次打包:

Hash: cd3f1bc16b28ac97e20a
Version: webpack 3.6.0
Time: 9750ms
     Asset   Size Chunks          Chunk Names
0.2c65.child.js 5.82 kB    0 [emitted]
1.6e26.child.js  4.4 kB    1 [emitted]
2.e4bc.child.js   3 kB    2 [emitted]
 index.4d45.js 64.2 kB    3 [emitted]     index
 vendor.bc85.js  276 kB    4 [emitted] [big] vendor

vendor 包同样被打包出来的,然而它的文件 hash 却发生了变化,这显然不符合我们长缓存的需求。

这是因为 webpack 在使用 CommoChunkPlugin 的时候会生成一段 runtime 代码(它主要用来处理代码模块的映射关系),而哪怕没有改变 vendor 里的代码,这个 runtime 仍然是会跟随着打包变化的并且打入 verdor 中,所以 hash 就会开始变化了。解决方案则是把这部分的 runtime 代码也单独抽离出来,修改之前的 `CommonsChunkPlugin` 为:

// webpack.config.js
...
new webpack.optimize.CommonsChunkPlugin({
 name: ['vendor', 'runtime'],
 minChunks: Infinity,
}),
...

执行打包可以看到生成的代码中多了 `runtime` 文件,同时即使改变业务代码,vendor 的 hash 值也保持不变了。

当然这段 `runtime` 实际上非常短,我们可以直接 inline 在 html 中,如果使用的是 `html-webpack-plugin` 插件处理 html,则可以结合 [`html-webpack-inline-source-plugin`](DustinJackson/html-webpack-inline-source-plugin) 插件自动处理其 inline。

(2)公共资源抽离

在我们打包出来的 js 资源包括不同入口以及子模块的 js 资源包,然而它们之间也会重复载入相同的依赖模块或者代码,因此可以通过 CommonsChunkPlugin 插件将它们共同依赖的一些资源打包成一个公共的 js 资源。

// webpack.config.js
plugins: [
 new BundleAnalyzerPlugin(),
 new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
 }),
 new UglifyJSPlugin({
  uglifyOptions: {
   ie8: false,
   output: {
    comments: false,
    beautify: false,
   },
   mangle: {
    keep_fnames: true
   },
   compress: {
    warnings: false,
    drop_console: true
   },
  }
 }),
 new webpack.optimize.CommonsChunkPlugin({
  name: ['vendor', 'runtime'],
  minChunks: Infinity,
 }),
 new webpack.optimize.CommonsChunkPlugin({
  // ( 公共chunk(commnons chunk) 的名称)
  name: "commons",
  // ( 公共chunk 的文件名)
  filename: "commons.[chunkhash:4].js",
  // (模块必须被 3个 入口chunk 共享)
  minChunks: 3
 })
],

可以看到这里增加了 `commons` 的一个打包,当一个资源被三个以及以上 chunk 依赖时,这些资源会被单独抽离打包到 `commons.[chunkhash:4].js` 文件。

执行打包,看到结果如下:

Hash: 2577e42dc5d8b94114c8
Version: webpack 3.6.0
Time: 24009ms
     Asset   Size Chunks          Chunk Names
0.2eee.child.js 90.8 kB    0 [emitted]
1.cfbc.child.js 89.4 kB    1 [emitted]
2.557a.child.js  88 kB    2 [emitted]
 vendor.66fd.js  275 kB    3 [emitted] [big] vendor
 index.688b.js 64.2 kB    4 [emitted]     index
commons.a61e.js 1.78 kB    5 [emitted]     commons

却发现这里的 `commons.[chunkhash].js` 基本没有实际内容,然而明明在每个子模块中也都依赖了一些相同的依赖。

借助 webpack-bundle-analyzer 来分析一波:

浅谈如何使用 webpack 优化资源

可以看到三个模块都依赖了 `lodash`,然而它并没有被抽离出来。

这是因为 CommonsChunkPlugin 中的 chunk 指的是 entry 中的每个入口,因此对于一个入口拆分出来的子模块(children chunk)是不生效的。

可以通过在 CommonsChunkPlugin 插件中配置 `children` 参数将拆分出来的子模块的公共依赖也打包进 `commons` 中:

// webpack.config.js
plugins: [
 new BundleAnalyzerPlugin(),
 new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
 }),
 new UglifyJSPlugin({
  uglifyOptions: {
   ie8: false,
   output: {
    comments: false,
    beautify: false,
   },
   mangle: {
    keep_fnames: true
   },
   compress: {
    warnings: false,
    drop_console: true
   },
  }
 }),
 new webpack.optimize.CommonsChunkPlugin({
  name: ['vendor', 'runtime'],
  minChunks: Infinity,
 }),
 new webpack.optimize.CommonsChunkPlugin({
  // ( 公共chunk(commnons chunk) 的名称)
  name: "commons",
  // ( 公共chunk 的文件名)
  filename: "commons.[chunkhash:4].js",
  // (模块必须被 3个 入口chunk 共享)
  minChunks: 3
 }),
 new webpack.optimize.CommonsChunkPlugin({
  // (选择所有被选 chunks 的子 chunks)
  children: true,
  // (在提取之前需要至少三个子 chunk 共享这个模块)
  minChunks: 3,
 })
],

查看打包效果:

浅谈如何使用 webpack 优化资源

其子模块的公共资源都被打包到 `index` 之中了,并没有理想地打包进 `commons` 之中,还是因为 `commons` 对于的是 entry 中的入口模块,而这里并未有 3 个 entry 模块共用资源;

在单入口的应用中可以选择去除 `commons`,而在子模块的 `CommonsChunkPlugin` 的配置中配置 `async` 为 `true`:

// webpack.config.js
plugins: [
 new BundleAnalyzerPlugin(),
 new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
 }),
 new UglifyJSPlugin({
  uglifyOptions: {
   ie8: false,
   output: {
    comments: false,
    beautify: false,
   },
   mangle: {
    keep_fnames: true
   },
   compress: {
    warnings: false,
    drop_console: true
   },
  }
 }),
 new webpack.optimize.CommonsChunkPlugin({
  name: ['vendor', 'runtime'],
  minChunks: Infinity,
 }),
 new webpack.optimize.CommonsChunkPlugin({
  // (选择所有被选 chunks 的子 chunks)
  children: true,
  // (异步加载)
  async: true,
  // (在提取之前需要至少三个子 chunk 共享这个模块)
  minChunks: 3,
 })
],

查看效果:

浅谈如何使用 webpack 优化资源

子模块的公共资源都被打包到 `0.9c90.child.js` 中了,该模块则是子模块的 commons。

四、tree shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
在我们引入一个依赖的某个输出的时候,我们可能需要的仅仅是该依赖的某一部分代码,而另一部分代码则是 `unused` 的,如果能够去除这部分代码,那么最终打包出来的资源体积也是可以有可观的减小。

首先,webpack 中实现 tree shaking 是基于 webpack 内部支持的 es2015 的模块机制,在大部分时候我们使用 babel 来编译 js 代码,而 babel 会通过自己的模块加载机制处理一遍,这导致 webpack 中的 tree shaking 处理将会失效。因此在 babel 的配置中需要关闭对模块加载的处理:

// .babelrc
{
 "presets": [
  [
   "env", {
    "modules": false,
   }
  ],
  "stage-0"
 ],
 ...
}

然后我们来看下 webpack 是如何处理打包的代码,举例有一个入口文件 `index.js` 和一个 `utils.js` 文件:

// utils.js
export function square(x) {
 return x * x;
}

export function cube(x) {
 return x * x * x;
}
```

```js
// index.js
import { cube } from './utils.js';

console.log(cube(10));
```

打包出来的代码:

```
// index.bundle.js
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
function square(x) {
 return x * x;
}

function cube(x) {
 return x * x * x;
}

可以看到仅有 `cube` 函数被 `__webpack_exports__` 导出来,而 `square` 函数被标记为 `unused harmony export square`,然而在打包代码中既是 `square` 没有被导出但是它仍然存在与代码中,而如何去除其代码则可以通过添加 `UglifyjsWebpackPlugin` 插件来处理。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js 数组操作代码集锦
Apr 28 Javascript
jQuery中parents()方法用法实例
Jan 07 Javascript
js实现右下角提示框的方法
Feb 03 Javascript
jQuery插件ajaxFileUpload异步上传文件
Oct 19 Javascript
JS中传递参数的几种不同方法比较
Jan 20 Javascript
详解redux异步操作实践
Aug 15 Javascript
vue中动态添加class类名的方法
Sep 05 Javascript
微信小程序使用gitee进行版本管理
Sep 20 Javascript
浅谈Javascript常用正则表达式应用
Mar 08 Javascript
js如何获取访问IP、地区、当前操作浏览器
Jul 23 Javascript
ES5新增数组的实现方法
May 12 Javascript
javascript实现随机抽奖功能
Dec 30 Javascript
利用jQuery实现简单的拖曳效果实例代码
Oct 20 #jQuery
Js利用prototype自定义数组方法示例
Oct 20 #Javascript
js 中rewrap-ajax.js插件实例代码
Oct 20 #Javascript
jQuery访问浏览器本地存储cookie、localStorage和sessionStorage的基本用法
Oct 20 #jQuery
JS 中使用Promise 实现红绿灯实例代码(demo)
Oct 20 #Javascript
用JavaScript做简易的购物车的代码示例
Oct 20 #Javascript
浅谈JS 数字和字符串之间相互转化的纠纷
Oct 20 #Javascript
You might like
PHP 和 MySQL 基础教程(一)
2006/10/09 PHP
php将fileterms函数返回的结果变成可读的形式
2011/04/21 PHP
php上传文件中文文件名乱码的解决方法
2013/11/01 PHP
thinkphp5.1 框架导入/导出excel文件操作示例
2020/05/25 PHP
jQuery textarea的长度进行验证
2009/05/06 Javascript
jQuery AJAX回调函数this指向问题
2010/02/08 Javascript
jQueryUI写一个调整分类的拖放效果实现代码
2012/05/10 Javascript
javascript jscroll模拟html元素滚动条
2012/12/18 Javascript
JavaScript在XHTML中的用法详解
2013/04/11 Javascript
javascript firefox 自动加载iframe 自动调整高宽示例
2013/08/27 Javascript
NodeJS学习笔记之FS文件模块
2015/01/13 NodeJs
nodeJS实现路由功能实例代码
2017/06/08 NodeJs
vue2.0中vue-cli实现全选、单选计算总价格的实例代码
2017/07/18 Javascript
微信小程序 本地图片按照屏幕尺寸处理
2017/08/04 Javascript
fetch 使用及如何接收JS传值
2017/11/11 Javascript
利用node实现一个批量重命名文件的函数
2017/12/21 Javascript
JavaScript封装的常用工具类库bee.js用法详解【经典类库】
2018/09/03 Javascript
微信公众平台 客服接口发消息的实现代码(Java接口开发)
2019/04/17 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
2019/06/18 Javascript
详解package.json版本号规则
2019/08/01 Javascript
python使用arcpy.mapping模块批量出图
2017/03/06 Python
python增加矩阵维度的实例讲解
2018/04/04 Python
对Python3中的print函数以及与python2的对比分析
2018/05/02 Python
python模块之subprocess模块级方法的使用
2019/03/26 Python
使用Python检测文章抄袭及去重算法原理解析
2019/06/14 Python
django 实现手动存储文件到model的FileField
2020/03/30 Python
python编写一个会算账的脚本的示例代码
2020/06/02 Python
python程序如何进行保存
2020/07/03 Python
建筑行业的大学生自我评价
2013/12/08 职场文书
小学防溺水制度
2014/01/29 职场文书
电大奖学金获奖感言
2014/08/14 职场文书
2015年安全工作总结范文
2015/04/02 职场文书
婚育证明格式
2015/06/17 职场文书
2016年寒假学习心得体会
2015/10/09 职场文书
2016党员学习《反对自由主义》心得体会
2016/01/22 职场文书
go语言使用Casbin实现角色的权限控制
2021/06/26 Golang