0%

安装为双系统后,默认为ubuntu启动

现在将其设为,默认启动上次选择启动的系统(让grub记住上次启动时选择的系统):

1
sudo vim /etc/default/grub

GRUB_DEFAULT=0(0为ubuntu启动)改为GRUB_DEFAULT=saved

文件末尾添加 GRUB_SAVEDEFAULT=true(这行命令可以修复只修改“grub_deault=saved”,但是启动时依然不能记住上次的启动的 GRUB2的bug)

保存退出

终端更新配置文件:sudo update-grub

sudo reboot重启

注:

GRUB_TIMEOUT=10 可调整自动进入时间

要固定默认启动系统,只需调整GRUB_DEFAULT=0为windows10所在序号(ubuntu为0以此类推),然后更新配置文件

附/etc/default/grub配置文件详解:

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
# 设定默认启动项,推荐使用数字
GRUB_DEFAULT=0

# 注释掉下面这行将会显示引导菜单
#GRUB_HIDDEN_TIMEOUT=0

# 黑屏,并且不显示GRUB_HIDDEN_TIMEOUT过程中的倒计时
GRUB_HIDDEN_TIMEOUT_QUIET=true

# 设定超时时间,默认为10秒
# 设定为-1取消倒计时
GRUB_TIMEOUT=10

# 获得发行版名称(比如Ubuntu, Debian)
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`

# 将会导入到每个启动项(包括recovery mode启动项)的'linux'命令行
GRUB_CMDLINE_LINUX=""

# 同上,但是只会添加到 normal mode 的启动项
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

# 取消注释以允许图形终端(只适合grub-pc)
#GRUB_TERMINAL=console

# 分辨率设定,否则采用默认值
#GRUB_GFXMODE=640x480

# 取消注释以阻止GRUB将传递参数 "root=UUID=xxx" 传递给 Linux
#GRUB_DISABLE_LINUX_UUID=true

# 取消启动菜单中的“Recovery Mode”选项
#GRUB_DISABLE_LINUX_RECOVERY="true"

# 当GRUB菜单出现时发出鸣音提醒
#GRUB_INIT_TUNE="480 440 1"

参考资料:

CMAKE_BUILD_TYPE = Debug | Release | RelWithDebInfo | MinSizeRel

cmake的构建模式

CMAKE_INSTALL_PREFIX:PATH = /usr/local/mysql

mysql 的安装目录
COMMUNITY_BUILD = ON

是否为社区版本

DOWNLOAD_BOOST = 1

是否从下载开源 boost

DWITH_BOOST =

This CMake script will look for boost in .

DOWNLOAD_BOOST_TIMEOUT = 600

下载 boost 的超时时间, 单位 秒

ENABLED_PROFILING = ON

是否启用查询分析代码 参考

ENABLE_GCOV = OFF

是否包括gcov支持, 不太懂这是什么, 默认关闭.

ENABLE_GPROF = OFF

启用gprof, (仅优化Linux版本), 不太懂, 默认关闭

ENABLE_MEMCACHED_SASL = OFF

不太懂, 默认关闭

ENABLE_MEMCACHED_SASL_PWDB:BOOL=OFF

不太懂, 看起来和上面一天是配套的, 也默认关闭

FEATURE_SET:STRING=community

这也不太懂, 默认就好了, 它的注释说这个选项好像已经被弃用了

INSTALL_LAYOUT = STANDALONE

选择预定义的安装布局, STANDALONE

  • STANDALONE:与用于.tar.gz.zip 包的布局相同 。这是默认值。
  • RPM:布局类似于RPM包。
  • SVR4:Solaris包布局。
  • DEB:DEB封装布局(实验)。

MYSQL_DATADIR = /usr/local/mysql/data

默认的mysql数据目录

MYSQL_KEYRINGDIR:PATH=/usr/local/mysql/keyring

默认的mysql手册目录

OPTIMIZER_TRACE:BOOL=ON

optimizer_trace 是mysql5.6之后加入的新功能, explain是各种执行计划选择的结果, 如果想看整个执行计划以及对于多种索引方案之间是如何选择的, 就使用optimizer_trace这个功能.

REPRODUCIBLE_BUILD = OFF

不太懂, 默认关闭

TMPDIR = P_tmpdir

临时文件的目录, P_tmpdir的值可以在 /usr/include/stdio.h中查看, 是/tmp

  • WITH_ARCHIVE_STORAGE_ENGINE:BOOL=ON
  • WITH_BLACKHOLE_STORAGE_ENGINE:BOOL=ON
  • WITH_FEDERATED_STORAGE_ENGINE:BOOL=ON
  • WITH_INNOBASE_STORAGE_ENGINE:BOOL=ON
  • WITH_PARTITION_STORAGE_ENGINE:BOOL=ON

分别开启 ARCHIVE , BLACKHOLE , FEDERATED, INNOBASE , PARTITION 引擎, 默认都是开启的.

WITH_ASAN = OFF

是否启用AddressSanitizer , 不太懂, 默认是关闭的.

WITH_ASAN_SCOPE = OFF

不太懂, 看起来像是和上面一个选项是配套的, 默认关闭.

WITH_CLIENT_PROTOCOL_TRACING = ON

是否将客户端协议跟踪框架构建到客户端库中。默认情况下,此选项被启用。

WITH_DEBUG:BOOL=OFF

是否包括调试支持, 默认关闭

WITH_DEFAULT_COMPILER_OPTIONS:BOOL=ON

是否使用默认的编译器来编译, 也就是cmake

WITH_DEFAULT_FEATURE_SET:BOOL=ON

是否使用cmake的特性集

WITH_EDITLINE:STRING = bundled

要使用 哪个libedit/ editline库。允许的值为 bundled(默认值)和 systemWITH_EDITLINE被添加到MySQL 5.7.2中。它取而代之WITH_LIBEDIT,已被删除。

WITH_EMBEDDED_SERVER:BOOL=ON

是否构建libmysqld嵌入式服务器库。 注意: 从libmysqldMySQL 5.7.17起,嵌入式服务器库已被弃用,MySQL 8.0中将被删除。

WITH_EXTRA_CHARSETS:STRING=all

哪些额外的字符集包括: all, complex, none.

WITH_INNODB_MEMCACHED:BOOL=OFF

是否生成memcached共享库(libmemcached.soinnodb_engine.so)。

WITH_LZ4:STRING = bundled

  • bundled:使用LZ4 与发行版捆绑在一起的库。这是默认值。
  • system:使用系统 LZ4库。如果 WITH_LZ4设置为此值,则不构建lz4_decompress实用程序。在这种情况下, 可以使用系统lz4命令。

WITH_MSAN:BOOL=OFF

是否启用MemorySanitizer,支持它的编译器。默认是关闭。对于此选项,如果启用该功能,则所有连接到MySQL的库也必须已经通过启用该选项进行编译。此选项已添加到MySQL 5.7.4中。

WITH_RAPID:BOOL=ON

是否构建快速开发周期插件.

WITH_SASL:STRING = system

不太懂

WITH_SSL = ssl_type | path_name

要包含的SSL支持类型或要使用的OpenSSL安装的路径名。

  • ssl_type 可以是以下值之一:
    • yes:使用系统SSL库(如果存在),否则与发行版捆绑在一起的库。
    • bundled:使用与发行版捆绑在一起的SSL库。这是默认值。
    • system:使用系统SSL库。
  • path_name是要使用的OpenSSL安装的路径名。使用这个可能比使用这个ssl_type值 更好 system,因为它可以防止CMake检测并使用系统上安装的较旧或不正确的OpenSSL版本。(另一个允许的方式做同样的事情是设置 CMAKE_PREFIX_PATH选项 path_name。)

WITH_TEST_TRACE_PLUGIN = OFF

是否构建测试协议跟踪客户端插件. 默认情况下,此选项被禁用。启用此选项不起作用,除非该WITH_CLIENT_PROTOCOL_TRACING 选项被启用。如果MySQL配置启用了这两个选项,libmysqlclient客户端库将内置测试协议跟踪插件构建,所有标准的MySQL客户端都会加载该插件。但是,即使启用测试插件,默认情况下也不起作用。使用环境变量来控制插件; 请参见第28.2.4.11.1节“使用测试协议跟踪插件”

不要启用 WITH_TEST_TRACE_PLUGIN,如果你想使用自己的协议跟踪的插件,因为只有一个这样的插件可以在同一时间被加载并出现错误尝试加载第二个选项。如果您已经使用启用了测试协议跟踪插件的MySQL来构建MySQL,以了解它是如何工作的,那么在使用自己的插件之前,您必须重新构建MySQL。

WITH_UBSAN:BOOL=OFF

是否为支持它的编译器启用Undefined Behavior Sanitizer。默认是关闭。此选项已添加到MySQL 5.7.6中。

WITH_UNIT_TESTS:BOOL=ON

如果启用,则使用单元测试编译MySQL。默认值为ON,除非服务器未被编译。

WITH_VALGRIND:BOOL=OFF

是否在Valgrind头文件中编译,这将Valgrind API暴露给MySQL代码。默认是 OFF

要生成一个Valgrind感知的调试构建, -DWITH_VALGRIND=1通常与之结合-DWITH_DEBUG=1。请参阅 构建调试配置

WITH_ZLIB:STRING=bundled

某些功能要求服务器使用压缩库支持(如功能COMPRESS()UNCOMPRESS()功能)以及客户端/服务器协议的压缩来构建 。这 WITH_ZLIB表明zlib支持的来源:

  • bundled:使用zlib与发行版捆绑在一起的 库。这是默认值。
  • system:使用系统 zlib库。

按resful风格设计接口: 按资源来设置接口的请求地址, 通过不同的请求类型来区分要对这个资源进行什么操作

get 语义是获取数据

post 语义是添加信息

delete 语义是删除数据

put 语义是更新数据(是更新这个数据的【所有信息】)

patch 语义是更新数据(是更新数据的【部分信息】)

假如要做注册

请求类型: post

请求地址: ‘/users’ (必须表示的是资源)

假如要做登录

请求类型: post (因为登录之后服务器的变化就添加了一个session)

请求地址: ‘/sessions’

假如要做退出登录

请求类型: delete

请求地址: ‘/sessions/id’ (退出登录就是服务器删除session)

假如要判断用户有没有登录

请求类型: get

请求地址 ‘/sessions’

总结: restful 风格 api

  1. 我们使用一个能够表示资源的地址(资源是数据库里的一条数据, 或是数组里的一个元素, 或者文件夹中的一个文件)

  2. 我们通过不同的请求类型(get, post, delete, put, patch), 对这个地址对应的资源进行CRUL, 让后端代码根据不同的请求类型, 对数据做不同的操作

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
/**
* 路由处理
* 最终这里我们只配置路由, 不实现对应回调函数的具体代码(放到controllers文件夹中)
* 按 restful 风格设计接口:
* 按资源来设计我们的接口的请求地址, 通过不同的请求类型,来区分要对这个资源进行什么操作
* 示例:
* '/gamerooms/998' 对应998这个房间号
* // 获取房间的信息
* router.get('/gamerooms/998', handler001)
* // 删除该房间
* router.delete('/gamerooms/998, handler002)
* // 修改房间信息
* router.put('/gamerooms/998, handler003)
* // 添加新房间
* router.post('/gamerooms', handler004)
*
* // get 语义是获取数据
* // delete 语义是删除数据
* // put 语义是更新数据(是更新这个数据的【所有信息】)
* // patch 语义是更数据(是更【新部分信息】)
*/
const express = require('express')
const gameRoomController = require('../controllers/gameRoomController.js')
const friendController = require('../controllers/friendController.js')
const userController = require('../controllers/userController.js')
const router = module.exports = express.Router()

// 注册
router.post('/users', userController.postSignUp)
// 登陆
router.post('/session', userController.postSignIn)

// 游戏房间
router.post('/gamerooms', gameRoomController.postGameRooms)
router.get('/gamerooms', gameRoomController.getGameRooms)
// 搜索用户, 和获取好友
router.get('/users', userController.getUsers)
router.get('/friends', friendController.getFriends)
router.post('/friends', friendController.postFirends)

/**
* 游戏房间创建,删除,修改,查询
*/
// router.post('/gamerooms', gameRoomController.postGameRooms)
// router.get('/gamerooms', gameRoomController.getGameRooms)

/**
* 游戏记录,查询游戏记录,删除游戏记录,修改记录, 添加记录
*/
// 假设游戏记录地址是: /gamerecords/10002 (包含: 游戏房间号,玩家,游戏时间)

// 获取一条游戏记录详细信息
// router.get('/gamerecords/10002', xxxx001)

// 删除这条游戏记录信息
// router.delete('/gamerecords/10002', xxxx002)

// 修改记录(想修改游戏的所有信息: 房间号,玩家, 游戏时间)
// router.put('/gamerecords/10002', xxxx003)

// 修改记录(只想修改房间号)
// router.patch('/gamerecords/10002', xxxx04)

// 添加一条游戏记录
// router.post('/gamerecords/10002', xxxx05)

// 获取所有游戏记录(也可能包含分页)
// router.get('/gamerecords', xxxx07)

// 对于前端来说 restful api 有没有开发难度:
// $.ajax({
// url: '',
// // type: 'delete',
// type: 'put',
// data: {},
// success: function () {

// }
// })

// users/10002
// 假如要做注册:
// 请求类型: post
// 请求地址: '/users' (必需表示的是资源)

// 假如要做登陆:
// 请求类型: post
// 请求地址: /sessions // 因为登陆其实是添加的 session

// 假如退出登陆
// 请求类型: delete
// 请求地址: /sessions/id // 退出登陆,服务器是要删除session

// 假如要判断用户有没有登陆
// 请求类型: get
// 请求地址: /sessions

// '/deluser'
// '/getuser'

// 总结: restful 风格 api
// 1. 我们使用一个能够表示资源的地址(资源是数据库里的一条数据,或者数组里的一个元素,或者文件夹中的一个文件)
// 2. 我们通过不同的请求类型(get, post, delete, put, patch), 对这个地址对应的资源
// 进行CRUD, 让后端代码根据不同的请求类型,对数据做不同的操作。

// 我遵循的风格: javascrtip standard style
// function xxx () {
// var aa = 11;
// }

WebSocket是HTML5出的东西(协议)

WebSocket协议的目标是在一个独立的持久连接上提供全双工双向通信。客户端和服务器可以向对方主动发送和接受数据。在JS中创建WebSocket后,会有一个HTTP请求发向浏览器以发起请求。在取得服务器响应后,建立的连接会使用HTTP升级将HTTP协议转换为WebSocket协议。也就是说,使用标准的HTTP协议无法实现WebSocket,只有支持那些协议的专门浏览器才能正常工作。

由于WebScoket使用了自定义协议,所以URL与HTTP协议略有不同。未加密的连接为ws://,而不是http://。加密的连接为wss://,而不是https://。

WebSocket是应用层协议,是TCP/IP协议的子集,通过HTTP/1.1协议的101状态码进行握手。也就是说,WebSocket协议的建立需要先借助HTTP协议,在服务器返回101状态码之后,就可以进行websocket全双工双向通信了,就没有HTTP协议什么事情了

这里补充一点: 关于三次握手, 四次挥手。是基于TCP/IP协议的。
WebSocket是基于TCP的,TCP的握手和WebSocket的握手是不同层次的。
TCP的握手用来保证链接的建立,WebSocket的握手是在TCP链接建立后告诉服务器这是个WebSocket链接,服务器你要按WebSocket的协议来处理这个TCP链接。

jetbrains 全家桶

ltsc 2019 自带的kms激活

1
2
slmgr /skms kms.digiboy.ir
slmgr /ato

windows

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,02,00,00,00,01,00,3a,00,00,00,00,00

将以上内容复制到reg后缀的文件中, 双击, 然后重启电脑即可

ubuntu

1
echo '/usr/bin/setxkbmap -option "caps:escape"' >> ~/.profile

externals 方案

将体积大而且不需要按需加载的包通过CDN的方式加载, 不参与打包, 例如: vueaxiosvue-router , 通过CDN加载的包不能使用Vue.use()

引入资源

在index.html中通过link和script方法引入所需的js和css

配置externals

webpack.dev.conf.jswebpack.prod.conf.jsmodule下均添加externals, 这样webpack编译打包时不处理它, 却可以import引用到它。

按需加载

每个包按需加载的方式都不同, 这里以 element-ui 为例, 在main.js主入口文件中给element-ui组件库做按需导入设置,去除没有使用的组件,进一步精简项目的总代码量。
参考: https://element.eleme.cn/#/zh-CN/component/quickstart

安装 babel-plugin-component

1
npm install babel-plugin-component -D

修改 .babelrc

按需引入

完整组件列表和引入方式(完整组件列表以 components.json 为准)

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
import Vue from 'vue'
import {
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
// ...
Loading,
MessageBox,
Message,
Notification
} from 'element-ui'

Vue.prototype.$ELEMENT = { size: 'small' } // element-ui的全局配置

Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Autocomplete)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Radio)
// ...
Vue.use(Loading.directive);

Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;

Vuex是实现组件之间数据共享的一种机制

父子传值或者兄弟传值,不好管理,而且有些需求实现不了

vuex提供了一种全新的数据共享管理机制, 该模式相比较简单的组件传值更加高端、大气、上档次

初始化vuex

1
npm i vuex -S

导入并注册

1
2
import Vuex from 'vuex'
Vue.use(Vuex)

创建store公共状态对象

在main.js文件中添加:

1
2
3
4
5
6
const store = new Vuex.Store({
// state 中存放的,就是全局共享的数据,可以把 state 认为 是组件中的 data
state: {
count: 0
}
})

将创建的 store 挂载到 vm 实例上

1
2
3
4
5
6
new Vue({
el: '#app',
render: c => c(app),
router,
store // 将 创建的共享状态对象,挂载到 Vue 实例中,这样,所有的组件,就可以直接从 store 中获取 全局的数据了
})

访问信息

1
this.$store.state.xxx

修改信息

不要直接修改this.$store.state.xxx,容易造成数据混乱

mutations 方式

同步

1
2
3
4
5
6
7
8
9
mutations:{
// 参数state是固定的,代表vuex本身的state,用以获取共享数据
add(state){
state.count++
}
}
...
// 调用
this.$store.commit('add')

actions 方式

异步

总结:

store中存储的数据,不要放到data中, 而是要放到计算你属性中,因为:

a. 如果将字符串, 数字这些普通值赋值给data中的属性时,当我们使用双向数据绑定来修改data中的数据时, 原来store中的数据是不会被影响的

b. vue官方推荐使用mutation的方式来统一修改store中的数据, mutation是需要我们定义的

全局安装脚手架

1
npm install -g vue-cli

初始化项目

1
vue init webpack my-project

Vue build 选择 Runtime-only 选项
Use ESLint to lint your code? 这个本应该应该yes, 但是作为个人开发者, 选择no比较方便
Should we run npm install for you after the project has been created? 选择 No, I will handle that myself

安装依赖包

1
2
cd my-project	// 切换到项目根目录下
npm install // 安装依赖包

webpack做简单的配置

默认8080端口, 可以选择修改

1568166821268

修改打包后的js和css为相对路径引入

build.js下的webpack.prod.conf.js中得output中添加publicPath。

新建.prettierrc文件

1
2
3
4
5
{
"tabWidth": 2,
"semi": false,
"singleQuote": true
}

运行项目

1
npm run dev

打包项目

1
npm run build

安装项目

开发过程中, 用到的包需要安装

1
2
npm i -D	// 工程构建(开发时、“打包”时)依赖 ;例:xxx-cli , less-loader , babel-loader...
npm i -S // 项目(运行时、发布到生产环境时)依赖;例:vue, element-ui, react...

常用的需要手动安装的扩展包

  • less

    npm i less-loader less -D

  • 使jsx支持v-model

    npm i babel-plugin-jsx-v-model -D

    .babelrc"plugins"下添加"jsx-v-model"

非零开始

以上1-7是从零开始构建项目, 如果是已有项目拷贝复制, 肯定不能把 node_modules 目录一同复制, 所以我们可以复制除了node_modules以外的所有文件, 然后再执行 npm install 即可

开发模式下,图片加载是没有问题的,但是打包后图片就无法加载了,需要更改打包配置。

  1. 修改 build 目录下的utils.js,添加 publicPath: '../../'

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (options.extract) {
    return ExtractTextPlugin.extract({
    use: loaders,
    publicPath: '../../',
    fallback: 'vue-style-loader'
    })
    } else {
    return ['vue-style-loader'].concat(loaders)
    }
  2. 修改config目录下的index.js, 将assetsPublicPath设置为 './'
    这个配置修改,只有在打包的时候才修改,开发的时候需要改回来

    1
    2
    3
    4
    5
    6
    dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: './',
    proxyTable: {},
    ...

注意:index.html中引入的图片是无法加载的,解决办法可以把index.html中引入的图片转成base_64格式的数据再引入。

组件

函数组件 与 ES6 的 class 组件

注意: 组件名称必须以大写字母开头。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome

你可以在深入 JSX中了解更多关于此规范的原因。

组合组件

props

react很灵活, 但是它有一个严格的规定:

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

注: js和php不一样, php会写时复制, 而js对于数组或者对象永远都是地址引用, 理解下面的代码:

1
2
3
4
5
6
let obj = {a: 'this is a',b:'this is b'}
function fn(obj) {
obj.c = 'this is c'
}
fn(obj)
console.log(obj) // {a: "this is a", b: "this is b", c: "this is c"}

如果一定要修改, 就是用组件自己私有的状态: state

state

拥有sate属性的组件就是有状态组件, 没有sate属性的组件就是无状态组件。

尽管 this.propsthis.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。

1
2
3
4
5
6
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

例如,此代码可能会无法更新计数器:因为counter的更新依赖了state和props

1
2
3
4
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});

要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

1
2
3
4
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));

通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:

  1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
  2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
  3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

数据是向下流动的

这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。

如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。

组件的生命周期函数

事件

使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

html中: onClick=“fn()”

jsx中: onClick={fn}

1
2
3
4
5
6
render() {
return (
<button onClick={this.handleClick} />
);
}
// 如果 this.handleClick 需要传递参数的话

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

当事件函数中需要this时, 我们有两种处理方式:

  1. bind绑定

  2. public class fields 语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class LoggingButton extends React.Component {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    // 注意: 这是 *实验性* 语法。
    handleClick = () => {
    console.log('this is:', this);
    }
    // 注意 handleClick的写法, 如果 handleClick() {} 这么写是不对的

    render() {
    return (
    <button onClick={this.handleClick}>
    Click me
    </button>
    );
    }
    }
    // 或者 调用的时候使用箭头函数, 那么事件方法在定义的时候就可以写成 handleClick() {} 这种形式
    // <button onClick={()=>this.handleClick()}>

推荐使用public class fields语法。

状态提升

因为state只能影响其及其”下面”的组件,如果想影响到”上面”的组件怎么办? 这就是状态提升, 实际上就是子组件给父组件通信。

实现方式和vue一样,通俗的来讲都是父组件定义方法, 这个方法一定是要传参的, 然后传递给子组件, 当子组件有数据修改,就通过this.props.event(val)的方式激活父组件来执行方法。vue中是通过this.$emit(event, i)来实现的。

1
2
3
npm install 等价 npm i
--save 等价 -S
--save-dev 等价 -D
1
2
3
npm i -D    // 工程构建(开发时、“打包”时)依赖 ;例:xxx-cli , less-loader , babel-loader...

npm i -S // 项目(运行时、发布到生产环境时)依赖;例:vue, element, react...

什么时候 用 –save 什么时候用 –save-dev, 这是个规范问题

npm install -g -S -D的区别.png

数据类型

五种简单数据类型: undefined, null, boolean, number, string

一种复杂数据类型: object

另外按照存储方式分为值类型和引用类型

有三大引用类型: object, array, function

typeof返回哪些数据类型

array, object, null 都返回 object

function 返回 function

例举 3 种强制类型转换 和 2种隐试类型转换

强制: parseInt(), parseFloat(), Number()

隐式: == 等等

split() 和 join() 的区别

前者是将字符串切割成数组的形式, 后者是将数组转换成字符串

pop() push() unshift() shift()

pop() 尾部删除, push() 尾部添加

shift() 头部添加, unshift() 头部添加

事件绑定 和 普通事件 有什么区别

普通添加事件, 最下面的事件会覆盖上面的, 而事件绑定(addEventListener)就不会

事件冒泡 和 捕获

事件的三个阶段: 捕获, 目标, 冒泡

addEventListener 的第三个参数 默认false 冒泡阶段触发, true 捕获阶段触发

冒泡阶段触发就是先触发内部元素的事件

捕获阶段触发就是先触发外部元素的事件

call 和 apply 以及 bind() 的区别

call() 和 apply() 是在执行函数的时候调用, 临时改变函数内部的this指向. 两者的唯一区别就是call传入参数列表, apply传入参数数组.

bind() 是创建新函数, 在创建函数的时候调用. 例如: f = fn.bind(xx) 把fn函数内部的this指向xx对象, 然后就生成可新函数f, 以后无论怎么调用函数f, 他里面的this都是指向xx

es6 class

严格模式

如果没有指定this, 则 this 值就将为 undefined

构造函数

constructor方法是一个特殊的方法, 这种方法用于创建和初始化一个由class创建的对象, 一个类只能有一个名为 “constructor” 的特殊方法.

原型方法

和PHP类一样, 只是没有function关键字

静态方法

static 关键字用来定义一个类的静态方法. 调用静态方法不需要实例化类, 但不能通过实例化一个实例调用静方法. 静态方法通常用于为一个应用程序创建工具函数.

extends

如果子类中存在构造函数, 则需要在使用 this 之前需要先调用 super()

super 关键字

super 这个关键字, 既可以当作函数调用, 也可以当做对象使用, 这两种情况下, 它的用法完全不同.

  1. super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
  2. super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

es5 和 es6 的继承

es5

  1. 借用构造函数, 缺点: 无法继承原型链上的属性和方法.

    1
    2
    3
    4
    //子构造函数
    function Sub() {
    Super.call(this)
    }
  2. 原型链继承:

    1
    2
    子构造函数.prototype = new 父构造函数()		//实现继承
    子构造函数.prototype.constructor = 子构造函数 //添加 constructor

    缺点: 不能给父构造函数传参,

  3. 组合继承: 借用构造函数 + 原型链继承

    1
    2
    3
    4
    5
    6
    //子构造函数
    function Sub() {
    Super.call(this) //第一次调用
    }
    Sub.prototype = new Super() //第二次调用
    Sub.prototype.constructor = Sub

    缺点: 无论什么情况, 都会调用两次超类型构造函数, 一次是在创建子类型原型的时候, 一次是在子类型构造函数的内部

  4. 寄生式继承 : 创建一个仅仅用于封装过程的函数, 然后在内部以某种方式增强对象, 最后返回对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
    }
    function createSubObj(original) {
    var clone = object(original) //object() 是ES5前Object.create()的非规范化实现
    return clone
    }
  5. 寄生组合式继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
       //Object.create 创建一个新对象, 使用现有的对象来提供创建的对象的 __proto__
    function inheritPrototype(Super,Sub){
    var superProtoClone = Object.create(Super.prototype) //创建一个空的新对象, 指定其 __proto__
    Sub.prototype = superProtoClone //修改子构造函数的prototype
    Sub.prototype.constructor = Sub //修改子构造函数的prototype.constructor
    }
    function Super(){
    this.property = 'Super Property'
    this.xx = 'xxx'
    }
    function Sub(){
    Super.call(this)
    this.property = 'Sub Property'
    }
    inheritPrototype(Super,Sub)
    var sub = new Sub()
    console.dir(sub)

es6

es6 的继承主要是 class 的继承

基本用法: class 通过 extends 关键字实现继承, 这比es5的通过修改原型链实现继承, 要清晰和方便的多

有哪些内置的构造函数

  1. Object()
  2. Function()
  3. Array()
  4. RegExp()
  5. Number()
  6. String()
  7. Boolean()
  8. Date()
  9. Error()

常用的内置对象有哪些, 并列举该对象的常用方法

DOM节点的增删改查

手写一个闭包

希望在访问不同作用域中的变量, 就需要使用闭包

闭包的作用, 就是保存自己私有的变量, 通过提供的接口(方法)给外部使用, 但外部不能直接访问该变量

1
2
3
4
5
6
7
8
9
10
//希望在另一个作用域访问, 访问fn中的name这个局部变量
function fn() {
var name = 'zs'
return () => {
return name
}
}
var f = fn()
var n = f()
console.log(n)

变量提升

仅提升, 不初始化

代码实现数组排序并去重

简单方法 :

1
2
3
4
5
6
function fn(arr) {
arr.sort((a, b) => a - b)
return [...new Set(arr)]
}
var arr = [1,1,2,9,9,6,5,3]
fn(arr)

冒泡 + 双重遍历去重 :

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
function fn(arr) {
let length = arr.length, i, j, k,l
//冒泡排序
for(i = 0; i< length - 1; i++) {
for (j = 0; j < length -1; j++) {
if(arr[j] < arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
}
}
}
//双重遍历去重, 外循环从 0 到 length, 内循环从 k+1 到 length, 将重复的值去掉
for(k = 0; k < arr.length; k++) {
let c = arr[k]
for(l = k + 1; l < arr.length; l++) {
if(arr[l] == c) {
arr.splice(l,1)
l-- // splice后, 数组长度减一, 所以要 l--
}
}
}
return arr
}

var arr = [1,1,2,9,9,6,5,3]
fn(arr)

实际工作部分

出一套适应不同分辨率, 不同终端的前端方案实现方案是什么

  1. 流式布局, 就是百分比布局.
  2. 响应式布局
  3. rem布局, rem只相对 html 根元素的 font-size , 所以将 html 的font-size设置为 100px

如何实现跨域

  1. JSONP 已淘汰

  2. CORS

  3. 服务器跨域

  4. postmessage html5中新增的方法

对象和类

在 js 中没有类, 所以在 js 中所谓的 类 就是构造函数, 对象是由构造函数创建

JS的垃圾回收机制

如果一个对象不再被引用就会被 GC 回收, 如果两个对象互相引用, 而不再被第三者引用, 那么这两个互相引用的对象也会被回收

正则表达式

捕获异常

H5有哪些新特性

  1. 拖拽释放
  2. 语义化更好的内容标签
  3. 音频视频
  4. 画布
  5. 地理
  6. 本地离线缓存
  7. sessionStorage
  8. 表单控件
  9. 新的技术

如何实现浏览器多个标签之间的通信

调用 localstorage, cookies 等本地存储方式

原型分为 隐式原型(__proto__) 和 显式原型(prototype)

隐式原型,所有的对象都有

显示原型,只有函数才有

每个对象都有一个__proto__属性,指向创建该对象的函数的prototype

所以,当找不到自身的属性或方法的时候,就去自己的__proto__里找,如果还是找不到,就再往下一层__proto__去找,一层一层找寻的这个路线就是原型链,就是这么简单

js的原型.png

显式原型( prototype ):所有的显示原型中都有constructor和__proto__这两个属性

constructor是一个函数,指向自身

至于__proto__,因为显示原型prototype是个对象,所以这个__proto__指向Object的prototype,这个Object就是女娲

隐式原型( __proto__ ) : JavaScript不希望开发者用到这个属性,有的低版本浏览器甚至不支持这个属性,所有你在VS2012这么智能的编辑器中,都不会有__proto__的智能提示,不用管他,直接写出来就行了。

总结:

  1. Object的prototype里除了constructor指向自身外,剩下的都是系统自带的对象的最最最基本的几个属性(方法),是所有的”对象”都有的几个属性。奇怪的是没有__proto__,你要是打印Object.prototype.__proto__,得到的是null,也对,这里就没必要有__proto__了,因为到原型链的尽头了

  2. Object的__proto__指向Function的prototype,因为Object是构造函数

  3. Function的prototype里: constructor指向自身,还有一些系统自带的最最基本的几个属性(方法),是所有的函数都有的几个属性。最后,__proto__指向Object的prototype,因为prototype也是对象。

其实所有的prototype中的__proto__都指向Object.prototype

Function是被自身创建的,所以__proto__指向自己的prototype

对象.constructor 就是创建它的构造函数

部署在服务器

参考: https://hexo.bootcss.com/docs/github-pages.html

  1. 安装node和git

    过程略…

  2. 安装hexo

    1
    npm install -g hexo-cli
  3. 手动创建hexo目录,进入hexo目录,初始化

    1
    hexo init	# 这一步比较慢,如果太慢,先打开cmd,ping github.com 
  4. 默认主题不好看,下载使用next主题

    1
    2
    3
    4
    5
    6
    7
    rm -f _config.landscape.yml 
    编辑package.json 删除 hexo-theme-landscape
    npm install

    npm install hexo-theme-next@latest
    vim _config.yml # theme: next
    cp node_modules/hexo-theme-next/_config.yml _config.next.yml
  5. 配置github

    1
    2
    3
    4
    ssh-keygen -t rsa -C "441757636@qq.com"
    ssh -T git@github.com
    cat ~/.ssh/id_rsa.pub
    ssh-rsa AAAAB3NzaC1yc2EA...省略...= 441757636@qq.com

    登录github,添加新ssh key,内容就是id_rsa.pub

  6. 写博客,位于source/_posts目录

  7. 配置nginx,根目录在hexo/pulic

部署在 github

1
2
3
4
5
6
7
8
9
npm install hexo-deployer-git --save
git config --global user.name "ljkk"
git config --global user.email "441757636@qq.com"
vim _config.yml
deploy:
type: git
repo: git@github.com:ljkk/ljkk.github.io.git
branch: main
hexo clean && hexo deploy

后续有博客更新,直接执行hexo clean && hexo deploy即可

优化NEXT

添加algolia

默认情况下,algolia只会搜索 标题、时间、摘要、分类、标签等关键信息,不会进行全文搜索

  1. 安装hexo-algolia,next主题内置了algolia

    1
    npm install hexo-algolia --save
  2. https://www.algolia.com/ 注册账号

  3. 新建index,类似对象存储的空间,建议以网站名命名

  4. 左侧API Keys –> All API Keys –> New API Key

  5. 配置 _config.melody.yml

    1
    2
    3
    4
    5
    6
    7
    8
    algolia_search:
    enable: true
    hits:
    per_page: 12
    labels:
    input_placeholder: Search for Posts
    hits_empty: "We didn't find any results for the search: ${query}" # if there are no result
    hits_stats: "${hits} results found in ${time} ms"
  6. 配置 _config.yml

    1
    2
    3
    4
    algolia:
    applicationID: 'Application ID'
    apiKey: 'Search-Only API Key'
    indexName: 'blog.lujinkai.cn'
  7. 生成索引

    1
    2
    export HEXO_ALGOLIA_INDEXING_KEY='刚生成的API Key'
    hexo algolia
  8. 查看效果

    1
    hexo s

后续更新了文章,也需要更新索引,执行:

1
2
export HEXO_ALGOLIA_INDEXING_KEY='刚生成的API Key'
hexo algolia

推荐相关文章

1
2
npm install hexo-related-popular-posts
hexo clean
1
2
3
# vim _config.yml
related_posts:
enable: true

添加gitalk评论插件

略…

优化分类

每篇笔记都手动编辑分类,这显然不是科学的方法,可以通过插件根据目录实现自动生成分类

https://github.com/xu-song/hexo-auto-category

1
2
npm install hexo-auto-category --save
hexo clean && hexo s

修改首页样式

所以需要改一下,我前端水平一般,没找到在哪里配置,只好修改源码,但是原则上尽量不直接修改,而是先继承再修改

  1. 修改 themes/next/layout/_macro/post.njk,只修改一行,给最外层的div添加一个类

    1
    2
    3
    <div class="post-block {%- if is_index %} ljk-index-post-block{%- endif %}">
    ...
    </div>
  2. 我用的是Gemini样式,进入themes/next/source/css/_schemes/Gemini,将index.styl重命名index.orgi.styl,然后新建index.styl,内容如下:

    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
    @import './index.orgi';
    // Modified by Lu Jinkai

    .main-inner > .sub-menu:not(:first-child):not(.sub-menu), .main-inner > .post-block:not(:first-child):not(.sub-menu), .main-inner > .tabs-comment:not(:first-child):not(.sub-menu), .main-inner > .comments:not(:first-child):not(.sub-menu), .main-inner > .pagination:not(:first-child):not(.sub-menu) {
    margin-top: 0px;
    }
    .ljk-index-post-block {
    padding: 12px 20px; // 这里写死了,不太好,post-block是自适应的
    .post-header {
    margin-bottom: 0;
    text-align: left;
    }
    .post-header .post-title {
    font-size: 1em;
    // white-space:nowrap;
    // overflow: hidden;
    // text-overflow: ellipsis;
    }
    .post-header .post-meta-container {
    font-size: 0.4em;
    margin-top: 0px;
    .post-meta {
    justify-content: flex-start;
    .post-meta-item-icon,.post-meta-item-text{
    display:none;
    }
    .post-meta-item {
    time {
    cursor: default;
    }
    time[itemprop="dateModified"] {
    display: none;
    }
    &::before {
    content: '';
    margin: 0 ;
    }
    &:last-child a {
    color: #999;
    &:hover {
    color: #888;
    }
    }
    &:last-child::before {
    content: '|';
    margin: 0 0.2em 0 0.6em;
    }
    &:first-child::before {
    display:none;
    }
    }
    }
    }
    .post-body {
    display: none;
    }
    }

    .pagination {
    margin: 0;
    }

export 和 export default

两个导出,下面我们讲讲它们的区别

export与export default均可用于导出常量、函数、文件、模块等

在一个文件或模块中,export、import可以有多个,export default仅有一个

通过export方式导出,在导入时要加{ },export default则不需要

export能直接导出变量表达式,export default不行。

Promise:一个承诺, 成功或者失败, 要么成功, 要么失败. 不存在先成功然后失败, 或者先成功然后失败.

语法:

1
new Promise( function(resolve, reject) {...} /* executor */ );

参数:

executor是一个带有reslove和reject两个参数的函数. exceutor函数在Promise构造函数执行时同步执行, 被传递resolve和reject函数(excetor函数在Promise构造函数返回新建对象前被调用). reslove和reject函数被调用时, 分别将promise的状态改为fulfilled(完成)或rejected(失败)。exceutor内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulilled,或者在发生错误的时将他的状态改为rejected。
如果在executor函数中抛出一个错误,那么该promise状态为rejected。executor函数的返回值被忽略。

描述:

Promise对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步的方法可以像同步的方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。
一个Promise有以下几种状态:
pending: 初始状态, 成功或失败状态
fulfilled: 意味着操作成功完成
rejected: 意味着操作失败

pending状态的Promise对象可能出发fulfilled状态并传递一个值给相应处理方法, 也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise对象的then方法绑定的处理方法(handlers)就会被调用(then方法包含两个参数: onfulfilled和onrejected, 他们都是Function类型。当Promise状态为fulfilled时,调用then的onfulfilled方法,当Promise状态为rejected时,调用then的onjected方法,所有在异步操作的完成和绑定处理方法之间不存在竞争)。

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

创建Promise:

1
2
3
4
5
6
const p = new Promise((resolve,reject) => {
//异步操作, 最终会调用:
// resolve(someValue); //fulfilled
//or
// reject("failure reason") //rejected
})

想要某个函数拥有promise功能, 只需让其返回一个promise即可:

1
2
3
4
5
6
7
8
9
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr new XMLHttpRequest();
xhr.open('GET',url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
}

Promise 的含义

Promise是异步编程的一种解决方案, 比传统的解决方案–回调函数和事件–更合理和更强大。原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending、fulfilled、rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能: 从pending为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意: 为了方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一但新建他就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一阶段。

如果某些事件不断的反复发生,一般来说,使用Stream模式是比部署Promise更好的选择。

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例:

1
2
3
4
5
6
7
8
const promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。他们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数:

1
2
3
4
5
promise.then(functionvalue){
//success
}, function(error){
//failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

下面是是一个Promise对象的简单例子:

1
2
3
4
5
6
7
8
9
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}

timeout(100).then((value) => {
console.log(value);
});

上面的代码中,timeout方法返回一个Promise实例, 表示一段时间以后才会发生的结果. 过了指定的时间(ms参数)以后, Promise实例的状态变为resolved, 就会触发then方法绑定的回调函数。

Promise 新建后就会立即执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();}
);

promise.then(function() {
console.log('resolved.');
});

console.log('Hi!');
// Promise
// Hi!
// resolved

下面是异步加载图片的例子:

1
2
3
4
5
6
7
8
9
10
11
12
function loadImageAsync(url) {
return new Promise(function(reslove, reject) {
const image = new Image()
image.onload = function() {
resolve(image);
}
image.onerror = function() {
reject(new Error('Could not load image at' + url))
}
image.src = url
})
}

下面是一个用Promise对象实现的Ajax操作的例子:

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
const getJSON = function(url) {
const promise = new Promise(function(){
const handler = function() {
if(this.readyState !== 4) return
if(this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
const client = new XMLHttpRequest()
client.open("GET",url)
client.onreadystatechange = handler
client.responseType = "json"
client.setRequestHeader("Accept", "application/json")
client.send()
})
return promise
}

getJSON('/post.json').then(function(json) {
console.log('content: ' + json)
}, functioin(error) {
console.error('出错了',error)
})

reject函数的参数通常是Error对象的实例, 表示抛出的错误; resolve函数的参数除了正常的值以外, 还可能是另一个Promise实例, 比如像下面这样:

1
2
3
4
5
6
7
const p1 = new Promise(function (resolve, reject) {
//...
}
const p2 = new Promise(function (resolve, reject) {
//...
return p1
}

上面的代码中, p1和p2都是Promise的实例, 但是p2的resolve方法将p1作为参数, 即一个异步操作的结果是返回另一个异步操作。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立即执行。
(Promise对象(P1)的参数(函数)会立即同步执行, 通过在这个函数内调用resolve或者reject来确定Promise对象的状态, 调用resolve回调函数,将Promise对象的状态转为fulfilled, 调用reject回调函数,将Promise对象的状态转为rejected。如果这个函数内既没有调用resolve, 也没有调用reject, 而是返回另一个Promise对象(P2), 则P1的状态完全失效, 由P2的状态来决定P1的状态。此时P1的then语句也变成针对P2)

一般来说,调用resolve或reject以后,Promise的使命就完成了,后继操作应该放到then方法里面,而不应该写在resolve或reject的后面。所以,最好在它前面加上return语句, 这样就不会有意外。

1
2
3
4
5
new Promise((resolve, reject)=>{
return resolve(1)
//后面的语句不会执行
console.log(2)
})

Promise.prototype.then()

Prototype实例具有then方法, 也就是说, then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法(不是then的参数res和rej)返回的是一个新的Promise实例(注意,不是原来的那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

1
2
3
4
5
getJSON("/post.json").then(function(json) {
return json.post
}).then(function(post) {}
//...
})

上面的代码使用then方法, 依次指定了两个回调函数。第一个回调函数完成之后,会将返回结果作为参数,传入第二个回调函数。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

1
2
3
getJSON('/post/1.json').then(post => {
return getJSON(post.commentURL)
}).then(comments => { }, err => { })

上面的代码中, 第一个then方法指定的回调函数, 返回的是另一个Promise对象( 大公鸡 )。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生改变。

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名, 用于指定发生错误时的回调函数.

Promise对象的错误具有” 冒泡 “性质, 会一直向后传递, 直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。

1
2
3
4
5
6
7
8
//bad
Promise
.then(data => {}, err => {})

//good
Promise
.then(data => {})
.catch(err => {})

上面代码中, 第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也能接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

1
2
3
4
5
6
7
8
9
10
11
12
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

上面代码中,someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise内部的错误不会影响到Promise外部的代码,通俗的说就是“Promise”会吃掉错误。

再看下面的例子:

1
2
3
4
5
6
7
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test 报错

上面的代码中,Promise指定在下一轮“事件循环”再抛出错误。到了这个时候,Promise的运行已经结束了,所以这个错误是在Promise函数体外抛出的, 会冒泡到最外层, 成了未捕获的错误。

一般总是建议,Promise对象后面要跟着catch方法,这样可以处理Promise内部发生的错误。catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。

Promise.all()

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})

let p2 = new Promise((resolve, reject) => {
resolve('success')
})

let p3 = Promse.reject('失败')

Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})

Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

代码模拟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let wake = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time / 1000}秒后醒来`)
}, time)
})
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
console.log(error)
})

需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

Promise.race()

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})

let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})

Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})

原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。

Promise.resolve()

Promise.reject()

两个有用的附加方法

应用

Promise.try()

JS版本数组去重

1
2
3
let arr = [2,22,2,2]
let x = new Set(arr)
console.log([...x])

PHP版本数组去重

1
2
3
4
5
6
7
8
$input = ["a" => "green", "red", "b" => "green", "blue", "red"];
//方法一 使用array_unique()函数
array_unique($input);
print_r($input);
//方法二 键值交换, 然后只取键
$res = array_flip($input);
$res = array_keys($input);
print_r($res);

补充 : 如何重建数组的索引 ?

1
//使用array_values 返回数组的值并建立数字索引.