0%

仅用于开发环境,不要在生产环境中使用!

source-map –映射关系

当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。这并通常没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。

为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。

原理

面试常常会问原理,需要掌握。

配置

在mode:'development'时,source map默认是打开,

也可以devtool : 'source-map',此时会生成一个.map文件,存放代码打包前后的映射关系。如果用inline-cheap-source-map时,就不会生成.map文件,直接写在打包后的js文件中用base64写入。

  • source-map 产生source map文件。

  • inline:不会生成map文件,直接将.map作为DataURI嵌入打包后的js文件中(包含inline关键字的配置项也会产生.map文件,但是这个map文件是经过base64编码作为DataURI嵌入)

  • cheap:只显示第几行,不显示第几行的第几列。

  • module:显示loader、第三方模块的source map。

eval:用eval函数直接把代码注入。没有.map 也没有base64写入。速度相对较快。是使用eval将webpack中每个模块包裹,然后在模块末尾添加模块来源//# souceURL, 依靠souceURL找到原始代码的位置。(eval模式有点特殊, 它和其他模式不一样的地方是它依靠sourceURL来定位原始代码, 而其他所有选项都使用.map文件的方式来定位)

最佳实践

1
2
3
4
5
6
7
8
[webpack.config.js]
...
mode : 'production' ,
devtool : 'eval-cheap-module-source-map'
...

mode : 'production' ,
devtool : 'cheap-module-source-map' 或者注释掉,不做映射
  1. https://segmentfault.com/a/1190000008315937
  2. https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
  3. http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html
  4. https://www.youtube.com/watch?v=NkVes0UMe9Y

Dev Server

每次要编译代码时,手动运行 npm run bundle 就会变得很麻烦。我们需要:

  1. 修改源代码后自动打包。
  2. 打包后自动打开浏览器。
  3. 刷新浏览器。

webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:

  1. webpack’s Watch Mode 观察模式
  2. webpack-dev-server
  3. webpack-dev-middleware

多数场景中需要使用 webpack-dev-server

配置

1
2
3
4
5
6
7
8
[package.json]
{
'scripts' : {
'watch' : 'webpack --watch' ,
'start' : 'webpack-dev-server',
'server' : 'node server.js'
}
}

观察模式

源代码改变就自动重新打包。但不能重新打开浏览器。静态页面也无法发出ajax请求等服务器操作。

在命令行中运行 npm run watch,就会看到 webpack 编译代码,然而却不会退出命令行。这是因为 script 脚本还在观察文件。

devServer

webpack-dev-server 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

需要安装webpack-dev-server,并修改配置文件告诉服务器在哪里查找文件。

以下配置告知 webpack-dev-server,在 localhost:8080 (默认值)下建立服务,将 dist 目录下的文件,作为可访问文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
npm install -D webpack-dev-server
[webpack.config.js]
...
//修改配置文件告诉服务器在哪里查找文件。
devSever : {
contentBase : './dist',
open : true //自动打开浏览器
port : 8080 //端口
proxy : {
'/api' : 'http://xxx' //跨域代理配置,将请求转发
}
}
...


[输出]
> webpack-dev-server

i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wds」: Content not from webpack is served from ./dist
i 「wdm」: Hash: 146d630f7310e8e6347c

middleware

可以自己实现一个server并配置使用它,但要实现与devServer样的功能需要很多配置。了解即可,不需要自己手动配置。

webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。

https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-middleware

Hot Module Replacement HMR 模块热更新

模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。

配置样式文件的热更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[webpack.config.js]
const webpack = require("webpack"); //模块热更新插件在webpack包里
...
devServer: {
contentBase: "./dist",
open: true,
+ hot: true, //打开模块热更新
+ hotOnly: true,//热更新失败也不刷新页面
},
plugins: [
new HtmlWebpackPlugin({
template: "src/index.html",
}),
new CleanWebpackPlugin(),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NamedModulesPlugin(), //以便更容易查看要修补(patch)的依赖。
],

配置之后,修改样式文件即可热更新。

自己配置的js文件直接使用配置不会生效,原因是需要配置module.hot.accept()

1
2
3
4
module.hot.accept(
dependencies, // 可以是一个字符串或字符串数组,可以是修改的
callback // 用于在模块更新后触发的函数
)

样式文件的css-loader、vue的vue-loader会帮你做这部分工作,所以只要打开热更新,不需要其他的配置即可生效。

打包静态文件

Loader

前序问题:

  1. webpack 是什么?
  2. 模块是什么
  3. webpack 配置文件的作用是什么?

回答:1.webpack 是模块打包工具。

  1. 模块是项目中的 js 文件、css、或者是一张图片等。
  2. 配置文件是配置如何去打包模块

打包图片文件

webpack 可以打包 js 文件,但是非 js 文件需要手动在模块中定义如何打包。以 jpg 文件为例,看代码中如何配置.其他类型的文件同理,需要用对应的 loader 去配置

1
2
3
4
5
6
7
8
9
10
11
12
13
[webpack.config.js];
module.exports = {
module: {
rules: [
{
test: /\.jpg$/,
use: {
loader: "file-loader",
},
},
],
},
};

module 是个对象,包含的 rules 是数组,数组中定义了以 jpg 结尾的文件名(正则表达式),这样的文件用file-loader这个 loader 来打包。

当然,需要用 npm 安装这个 file-loader。

file-loader 做了什么?

它发现有.jpg 结尾的文件后,就把它打包移动到 output 目录(这里是 dist)并重命名(名字也可以在配置文件中自定义)。得到图片名称后,会把名字作为返回值,返回给引入模块的变量中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[index.js];
var Header = require("./header.js");
var Sidebar = require("./sidebar.js");
var Content = require("./content.js");
var avatar = require("./avatar.jpg");

console.log(avatar);
//打包后在dist中的图片名称:\dist\e49823921af3dad34a48ed93700b95ac.jpg

new Header();
new Sidebar();
new Content();

var img = new Image();
img.src = avatar;
var root = document.getElementById("root");
root.appendChild(img);

vue 文件同理要用 vue-loader

自定义静态文件打包后的名字/路径等信息

通过配置 loader 的 option 参数。

1
2
3
4
5
6
7
8
9
10
11
12
{
test: /\.jpg$/,
use: {
loader: "file-loader",
options: {
//name就是图片文件本身的name
//ext是图片本身的后缀
name: "[name].[ext]",
outputPath: "images/", //打包到dist下的images文件夹下
},
},
},

name: "[name].[ext]"这种语法叫 placeholder 占位符。在官网可以查到各种占位符如[hash]等等。

url-loader

这个 loader 同样可以打包 jpg|png|gif 等图片文件,但不是把图片打包到 dist 文件夹的方式,而是把图片转换为 base64 编码,直接放入 bundle.js 文件中。节省一次 http 请求,但会浪费加载时间。

所以 url-loader 有一个配置 limit,如果图片大于 limit,就打包成图片文件,如果小于 limit,就打包到 bundle.js 中,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//rules
{
test: /\.jpg$/,
use: {
loader: "file-loader",
options: {
//name就是图片文件本身的name
//ext是图片本身的后缀
name: "[name].[ext]",
outputPath: "images/", //打包到dist下的images文件夹下
limit : 10240 //10kb
},
},
},

打包样式文件

需要用到 [“style-loader”, “css-loader”]

1
2
3
4
5
//module
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},

css-loader:分析出几个 css 文件之间的关系,最终把这几个 css 文件合并成一段 css。

style-loader:把 css-loader 生成的 css 代码片段挂载到页面的 header 部分。

scss less stylus

.scss 结尾的需要额外的 sass-loader 去把.scss 文件翻译成 css 文件

1
2
3
4
5
//module
{
test: /\.css$/,
use: ["style-loader", "css-loader" , "sass-loader"],
},

loader 的执行是有顺序的, 顺序是从下到上,从右到左

自动添加厂商前缀

1
2
3
4
5
//module
{
test: /\.css$/,
use: ["style-loader", "css-loader", "sass-loader", "postcss-loader"],
},

通过查看 webpack 官网的 loader,发现 postcss-loader 需要配置文件。

1
2
3
4
5
[postcss.config.js];
module.exports = {
//使用了autoprefixer来自动添加厂商前缀
plugins: [require("autoprefixer")],
};

并安装这个 autoprefixer 插件,就可以实现自动添加厂商前缀的效果了,可以在打包后的 html 文件用浏览器打开查看。

css-loader 中的 importLoaders

  • 官网解释:

importLoaders

查询参数 importLoaders,用于配置「css-loader 作用于 @import 的资源之前」有多少个 loader。

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2 // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
modules : true //开启模块化打包,下小结详细讲解
}
},
'postcss-loader',
'sass-loader'
]
}

在模块系统(即 webpack)支持原始 loader 匹配后,此功能可能在将来会发生变化。

  • 适用情况:

当一个 scss 文件中@import 了新的 scss 文件,此时对原文件的打包已经按顺序加载到了 css-loader 的功能,引入的 scss 文件用 css-loader 加载就会出错。这个importLoaders用来使引入的 scss 文件依然从 sass-loader 开始加载。

css 打包的模块化

在入口文件中引入的样式文件会作用在全局,可能会出现意外的样式覆盖问题,所以引入 css 模块化概念。

只需要加一句modules:true即可开启。

对应的,在入口文件中也需做修改,注意:如果依然按全局引入的话样式会不生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[index.js]入口文件
import avatar from "./avatar.jpg";
import Avatar from "./avatar";

//import "./avatar"; //在这里引入的样式作用在全局,容易出现样式冲突
import style from "./index.scss"; //使用模块化的css,配合css-loader的option:{modules:true}

Avatar();

var root = document.getElementById("root");
var img = new Image();
img.classList.add(style.avatar); //模块引入的使用方法
//img.classList.add("avatar"); //全局引入的使用方法
img.src = avatar;
root.append(img);

打包后:
<img class="_17cnVz87yzSOO5TpFdnLsk" src="images/avatar.jpg">

在其他 js 文件中使用同理。

打包字体文件

如 iconfont 的eot .ttf .svg .woff等,需要额外的 loader 去进行打包。这里使用的是 file-loader。配置与 url-loader 类似。

最后附上完整文件,github 地址

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
[webpack.config.js];

const path = require("path");
module.exports = {
mode: "development", //添加mode,否则在打包时会有警告,默认值是 production
entry: { main: "./src/index.js" },
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"), //这里必须使用绝对路径,所以引入node的核心模块path,生成一个绝对路径,__diename就是当前文件所在目录
},
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: "url-loader",
options: {
name: "[name].[ext]",
outputPath: "images/", //打包到dist下的images文件夹下
limit: 10240, //limit 图片小于10k则变成base64写入bundle.js,大于10k则单独打包到image文件夹中
},
},
},
{
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "font/", //打包到dist下的font文件夹下
},
},
},
{
test: /\.scss$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
//配置「`css-loader 作用于 `@import` 的资源之前」有多少个 loader。
importLoaders: 2, // 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, sass-loader
//modules: true,
},
},
"postcss-loader",
"sass-loader",
],
},
],
},
};

来嗑打包工具

基础三连

  1. 是什么?
  2. 干什么?
  3. 怎么干?
  1. 是模块打包工具
  2. 打包模块,包括js、css静态文件等。
  3. 通过配置文件去配置

安装和基本操作

npm init

使这个项目符合node规范,可以用webpack来管理这个项目。它在项目中生成一个package.json文件。

1
2
3
4
5
6
[package.json]
{
...
+ 'private':true//表示是私人仓库,不会被发布到npm仓库中去
- 'main':'index.js' //向外暴露一个js文件以便被引用
}

安装webpack

npm install webpack webpack-cli -D -D=–save-dev

不推荐全局安装。不便于使用不同版本

npx:node提供的在项目内使用webpack命令。直接使用webpack命令时会从全局查找而报错。

3种运行webpack的方式

  1. global webpack index.js 不推荐,可能不同项目用的webpack版本不同。
  2. local npx webpack index.js 配置文件在webpack.config.js中配置。
  3. npm scripts 在package.json文件中添加配置,再用npm run bundle 即是2的效果
1
2
3
4
5
6
7
[package.json]
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"bundle": "wabpack"
//配置后可用npm run bubdle 代替npx wabpack,优先查找node_modules里安装的webpack

},

实际都是运行webpack命令,webpack-cli包就是能在命令行运行webpack命令。

When installing a package that will be bundled into your production bundle, you should use npm install --save. If you’re installing a package for development purposes (e.g. a linter, testing libraries, etc.) then you should use npm install --save-dev. More information can be found in the npm documentation.

打包输出的内容

image-20200629152904382

Asset:打包生成的js文件

size:文件大小

Chunks:入口文件的chunk id

Chunk Names:入口文件的Chunk name:

1
2
3
4
5
6
7
8
9
[webpack.config.js]
mudule.exports={
mode:'production'
enrty:'./src/index.js'
//是以下的简写
entry:{
main:'./src/index.js'
}
}

这个main就是默认的chunk name

缺少mode字段的wanning

image-20200629152758065

默认的mode是’production’。此时打包出的文件会被压缩(变成了1行)

这个提示会告诉你在配置文件中没有定义mode,但其实缺省时,webpack也会使用默认值。

mode:development时,代码就不会被压缩

之前踩到的一个坑。

js 中,不区分 int float 等等,只有一个 Number 类型。占 64 位(8 字节)。

数字类型采用 64 位浮点格式表示,我们可以利用 Number 对象的属性 Number.MAX_VALUE , Number.MIN_VALUE 来查看;JavaScript 中 Number 范围为正负 2 的 53 次方,也即从最小值-9007199254740992 到最大值+9007199254740992 之间的范围。−9007199254740992 and 9007199254740992 (即正负 2 的 53 次方)
image-20200302172949558

JavaScript 里的数字是采用 IEEE 754 标准的 64 位双精度浮点数。该规范定义了浮点数的格式,对于 64 位的浮点数在内存中的表示,最高的 1 位是符号位,接着的 11 位是指数,剩下的 52 位为有效数字,具体:

第 0 位:符号位, s 表示 ,0 表示正数,1 表示负数;
第 1 位到第 11 位:储存指数部分, e 表示 ;
第 12 位到第 63 位:储存小数部分(即有效数字),f 表示,
符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。 IEEE 754 规定,有效数字第一位默认总是 1,不保存在 64 位浮点数之中。也就是说,有效数字总是 1.xx…xx 的形式,其中 xx..xx 的部分保存在 64 位浮点数之中,最长可能为 52 位。因此,JavaScript 提供的有效数字最长为 53 个二进制位(64 位浮点的后 52 位+有效数字第一位的 1)。

即:由于有 11 位的指数位,可以表示很大的数值,但是超过 53 位大数的精度无法保证。

这样的规则会带来一些问题,比如浮点数的精度问题和大数问题。

浮点数精度问题

1
2
3
const float1 = 7.9;
const float2 = 0.8;
console.log(float1 - float2); //7.1000000000000005

发生的原因:
首先,十进制的 7.9 和 0.8 都会被转换成二进制,但由于浮点数用二进制表达时是无穷的。标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以浮点数小数位的限制而截断的二进制数字,进行运算后,再转换为十进制,就会产生误差。
这种问题根本原因是在于:计算机都是 0 或 1 标识,对于某浮点准确值,是通过不断增加位数去逼近该值。浮点数实现这种方式就是移位运算,所以不管是在表示还是运算时,都会出现误差。
如何解决:
1 简单的方法使用 toFixed
parseFloat((数学表达式).toFixed(digits)); // toFixed() 精度参数须在 0 与 20 之间
// 运行

大数问题

1
aa=68406452651714150433  //显示出aa=68406452651714150000

js 的 number 类型有个最大值(安全值)。即 2 的 53 次方,为 9007199254740992。如果超过这个值,那么 js 会出现不精确的问题。这个值为 16 位。

如果再大一些,达到指数位也无法显示这么大的数,就会变成 infinity(由于符号标志位是单独的,所以区分正负)


网上找的说法:

JavaScript 能表示并进行精确算术运算的整数范围为:正负 2 的 53 次方,也即从最小值-9007199254740992 到最大值+9007199254740992 之间的范围;对于超过这个范围的整数,JavaScript 依旧可以进行运算,但却不保证运算结果的精度。值得注意的是,对于整数的位运算(比如移位等操作),JavaScript 仅支持 32 位整型数,也即从-2147483648 到+2147483647 之间的整数。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

本想挖个小坑结果挖出一个黑洞

问题

​ 最近码代码遇到一点问题,之前请求到后端数据直接做处理,现在想要拿到数据后的返回值再分别做处理,正常调用的话返回值总是空;另外有需求要在对列表数据做操作后重新请求接口刷新列表,效果来看却总是没有刷新,打断点才发现,早在操作接口数据返回之前就已经偷偷做了刷新操作,然而太早的刷新根本达不到想要的效果…暂且用 async/await 解决了问题却留下了一个大坑,先填一部分吧。

异步调用

Ajax:“Asynchronous Javascript And XML”(异步 JavaScript 和 XML)

通过 AJAX,JavaScript 无需等待服务器的响应,而是:

  • 在等待服务器响应时执行其他脚本
  • 当响应就绪后对响应进行处理

由于向外部设备获取资源时,比如从网络获取文件、访问数据库等等,如果使用同步代码,将会在请求过程中一直处于阻塞状态——因为 JavaScript 是单线程,任何时候只能做一件事情, 只有一个主线程,其他的事情都阻塞了,直到前面的操作完成。

好在 web 浏览器定义了函数和 api,允许我们在某些事件发生时异步的调用函数(时间推移、用户通过鼠标的交互、获取网络数据等等)而不需要阻塞主线程。

Instead of immediately returning some result like most functions, functions that use callbacks take some time to produce a result. The word ‘asynchronous’, aka ‘async’ just means ‘takes some time’ or ‘happens in the future, not right now’. Usually callbacks are only used when doing I/O, e.g. downloading things, reading files, talking to databases, etc. –Callback Hell

使用同步的办法来处理异步会遇到一些问题,比如:

1
2
3
var response = fetch("myImage.png");
var blob = response.blob();
// display your image blob in the UI somehow

由于第一句的异步请求(可能)会耗费时间,会导致第二行(可能)执行报错。此时需要保证第二行的调用等到 response 返回才能继续进行。

异步编程的目标,就是让它变得更像同步编程。即是,在异步调用的过程中,可以去执行其他与本次调用函数的 response 无关的事情,但是与 response 有关的任务(或者说整个异步调用任务的第二执行阶段),需要保证在拿到 response 返回值后进行。整个任务看上去就像是同步调用一样。

其中有两个比较重要的概念:

  1. 部分代码需要在异步请求之后调用。
  2. 由于异步请求的出现,代码并不总是从上往下执行的,异步执行的代码块没有阻塞,而是继续执行接下来的代码,所以会有一些跳着执行的情况出现。

在 JavaScript 代码中,你经常会遇到两种异步编程风格:老派 callbacks(回调函数),新派 promise。

callbacks (e.g. ‘call you back later’)

callbacks 其实就是回调函数,它作为参数传递给后台执行的函数,当后台执行的函数结束后,会调用 callbacks 函数,通知你这个函数已经执行完成,可以用 callbacks 执行第二阶段了。

常见形式:

1
2
3
4
5
6
7
8
downloadPhoto("http://xxx.com/xxx", handlePhoto); //下载图片
function handlePhoto(error, response) {
if (error) {
console.log("an error!", error);
} else {
console.log("finished", response);
}
}

这段代码做了三件事:先声名handlePhoto函数;然后调用downloadPhoto函数,并将handlePhoto作为它的回调函数;最后调用handlePhoto函数。

​ 回调函数本身没有问题,但是不恰当的使用会导致回调地狱。即回调函数也是异步执行,回调函数的回调函数也是异步执行…(套娃)

​ 另外,错误处理往往非常重要,Node.js 将 error 作为回调函数的第一个参数,也是作为标准提醒码农不要忘记处理错误。(如果没有错误那么第一个参数就是 null)。不过也有另外一种说法:

一个有趣的问题是,为什么 Node.js 约定,回调函数的第一个参数,必须是错误对象 err(如果没有错误,该参数就是 null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。 —-阮一峰的网络日志

Promise

像薛定谔的猫,本次请求成功或者失败的两种结果还没发生,就返回一个 promise 对象,表示一种中间状态像是浏览器对你说:保证尽快答复,所以叫’promise’。这个 promise 对象会在请求响应后被 resolve,并传回 Response 对象。

例如 fetch:

1
2
3
4
5
6
7
8
9
10
11
fetch("products.json")
.then(function(response) {
return response.json();
})
.then(function(json) {
products = json;
initialize();
})
.catch(function(err) {
console.log("Fetch problem: " + err.message);
});

这里 fetch()需要一个参数(请求地址)返回一个 promise,.then()代码块里对 promise 的 response 进行处理,再返回另一个 promise……其中如果任何一个.then()中的代码块执行失败,就会运行.catch(),提供一个 error 对象来报告其中发生的错误。另外同步的 try/catch 不能与 promise 一起工作,但能与async/await一起工作。

事件队列

像 promise 这样的异步操作会被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续 JavaScript 代码的运行。排队操作将尽快完成,然后将结果返回到 JavaScript 环境。这也是前文提到的代码不总是从上往下执行的

promise 对比 callbacks

二者很像,如果用套娃式的写法的话,promise 的代码可能更容易阅读一些,但是本质上都是一个返回的对象,promise 可以将回调函数附加到这个对象上,而不用作为参数传递给异步调用函数。

promise 的优点:

  1. 链式的,严格按照规定的顺序调用(由.then()规定)。
  2. 所有的错误都在.catch()被捕捉到,而不是每个 callback 函数中单独处理。

Promise 对象

它是一个代理对象,代理一个值(通常是一次请求的返回值),这个值在对象创建时是未知的。这种办法可以让异步方法像同步方法一样返回值,只是返回一个代表未来出现的结果的 promise 对象。

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象, 所以它们可以被链式调用。

如果一个 promise 对象处在 fulfilled 或 rejected 状态而不是 pending 状态,那么它也可以被称为settled状态。或者用resolved来表示 promise 对象处于 settled 状态。

异步代码的本质(重点来了)

当不了解代码执行顺序或者将异步代码当做同步代码写时会遇到的问题。用一段示例来展示(源代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log("start");
let image;

fetch("coffee.jpg")
.then(response => {
console.log("It worked :)");
return response.blob();
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
image = document.createElement("img");
image.src = objectURL;
document.body.appendChild(image);
})
.catch(error => {
console.log(
"There has been a problem with your fetch operation: " + error.message
);
});

console.log("All done!");

首先控制台打印start,然后执行异步代码块 fetch(),这里没有阻塞,将相关代码 promise 之后就继续执行了,然后到达最后一行打印All done。

在执行 fetch()完成运行,拿到返回结果给了.then()代码块后,才打印了了it worked然后将返回结果给下一个.then()。所以最后控制台打印顺序是:

  1. start
  2. All done!
  3. It worked :)

到这里,问题出现的原因算是明白了,之后找时间更新目前来讲更好地解决办法。

万万没想到一小时后我竟不需要这个文章了。

  1. 当我修改了_config.yml 后,想在本地看看效果时:

    1
    2
    3
    //Hexo路径下
    hexo g === hexo generate //生成静态文件
    hexo s === hexo server //启动服务器
  2. 当我准备将它发布时:

    1
    2
    3
    hexo clean   //清除缓存文件和静态文件(public)。已生成的有时因为缓存问题,服务器更新不了主题
    hexo g
    hexo d ===hexo deploy //发布