浅谈如何使用 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 相关文章推荐
如何实现iframe(嵌入式帧)的自适应高度
Jul 26 Javascript
Firefox下无法正常显示年份的解决方法
Sep 04 Javascript
Bootstrap每天必学之缩略图与警示窗
Nov 29 Javascript
JS弹出对话框实现方法(三种方式)
Dec 18 Javascript
Ionic如何创建APP项目
Jun 03 Javascript
详谈JS中实现种子随机数及作用
Jul 19 Javascript
js 获取图像缩放后的实际宽高,位置等信息
Mar 07 Javascript
element vue validate验证名称重复 输入框与后台重复验证 特殊字符 字符长度 及注意事项小结【实例代码】
Nov 20 Javascript
详解小程序不同页面之间通讯的解决方案
Nov 23 Javascript
jQuery实现的点击图片居中放大缩小功能示例
Jan 16 jQuery
webpack打包html里面img后src为“[object Module]”问题
Dec 22 Javascript
Vue+axios封装请求实现前后端分离
Oct 23 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
DISCUZ 论坛管理员密码忘记的解决方法
2009/05/14 PHP
PHP CURL中传递cookie的方法步骤
2019/05/09 PHP
laravel异步监控定时调度器实例详解
2019/06/21 PHP
PHP单元测试配置与使用方法详解
2019/12/27 PHP
用javascript getComputedStyle获取和设置style的原理
2008/10/10 Javascript
javascript一些实用技巧小结
2011/03/18 Javascript
jQuery效果 slideToggle() 方法(在隐藏和显示之间切换)
2011/06/28 Javascript
JS多物体 任意值 链式 缓冲运动
2012/08/10 Javascript
JS事件Event元素(兼容IE,Firefox,Chorme)
2012/11/01 Javascript
jQuery实现表头固定效果的实例代码
2013/05/24 Javascript
在JavaScript里嵌入大量字符串常量的实现方法
2013/07/07 Javascript
JS实现复制功能
2017/03/01 Javascript
webpack 2.x配置reactjs基本开发环境详解
2017/08/08 Javascript
原生JS+Canvas实现五子棋游戏
2020/05/28 Javascript
详解nodejs中express搭建权限管理系统
2017/09/15 NodeJs
JS实现的哈夫曼编码示例【原始版与修改版】
2018/04/22 Javascript
AngularJS日期格式化常见操作实例分析
2018/05/17 Javascript
JavaScript实现正则去除a标签并保留内容的方法【测试可用】
2018/07/18 Javascript
Vue项目history模式下微信分享爬坑总结
2019/03/29 Javascript
详解Vue.js和layui日期控件冲突问题解决办法
2019/07/25 Javascript
axios如何利用promise无痛刷新token的实现方法
2019/08/27 Javascript
JavaScript使用localStorage存储数据
2019/09/25 Javascript
vue配置多代理服务接口地址操作
2020/09/08 Javascript
Python 旋转打印各种矩形的方法
2019/07/09 Python
Python closure闭包解释及其注意点详解
2019/08/28 Python
LivingSocial英国:英国本地优惠
2019/02/22 全球购物
Glamest意大利:女性在线奢侈品零售店
2019/04/28 全球购物
便利店投资创业计划书
2014/02/08 职场文书
应届毕业生如何写求职信
2014/02/16 职场文书
妇联主席先进事迹
2014/05/18 职场文书
中层干部培训方案
2014/06/16 职场文书
暑假社会实践心得体会
2014/09/02 职场文书
班主任高考寄语
2015/02/26 职场文书
2019年汽车租赁合同范本!
2019/08/12 职场文书
Python基础学习之奇异的GUI对话框
2021/05/27 Python
html中显示特殊符号(附带特殊字符对应表)
2021/06/21 HTML / CSS