LJKのBlog

学无止境

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

  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
    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
26
27
28
29
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
4
5
6
7
8
9
10
11
12
13
14
var a1 = [2, 3, 4, 6, 7, 1000]
var a2 = [1, 2, 3, 2000]

var i = a1.length
while (i--) {
var j = a2.length - 1
if (j === -1) {
break
}
var a2v = a2[j]
a2v >= a1[i] && a1.splice(i + 1, 0, a2.pop()) && i++
}
a1 = a2.concat(a1)
console.log(a1)

实际工作部分

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

  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;
    }

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
// 模拟网络请求 start
const request = params =>
new Promise(resolve => {
setTimeout(() => {
resolve("Hello World " + params)
}, 1000)
})
// 模拟网络请求 end

const get = async s => {
const res = await request(s)
console.log(res)
}

const hangings = []

;[1, 2, 3].forEach((_, index) => {
new Promise(resolve => {
hangings.push(() => {
resolve(get("lujinkai " + index))
})
})
})

hangings.forEach(h => h())

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.thenfunction(value){
//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
4
5
6
7
8
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
13
14
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
8
9
10
11
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
22
23
24
25
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
17
18
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
18
19
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()

补充:

then 和 catch 要么不写,写了就一定要定义 return,否则默认 return undefined

promise 是链式的,对于链中间的一个 promise,以下两种写法是等价的:

1
2
3
4
// 1
promise.then(res => {...})
// 2
promise.then(res => {...}).catch(err => {return Promise.reject(err)})

export 和 export default

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

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

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

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

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

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 返回数组的值并建立数字索引.

JavaScript 中的正则

第一步: 定义正则表达式

1
let regex = /\d+/g

第二步: 使用正则表达式

1
2
3
4
5
6
7
let str = "a13adddda3afsdfsfa99aa000aa999a34567"
str.match(regex) // 返回数组
str.matchAll(regex) // 返回迭代器
str.test(regex)
str.search(regex)
str.replace(regex)
str.split(regex)

正则标志:

/g 全局搜索

/i 不区分大小写

/m 多行匹配

\r 代表回车,也就是打印头归位

\n 代表换行,就是走纸

Linux 只用\n 换行

win 下用\r\n 表示换行

简单数组或者对象:元素都是简单数据的数组或者对象

拷贝数组 使用slice方法 或者 数组展开语法

1
2
3
4
5
6
7
let arr = Array(10).fill(null);
let brr = arr.slice();

brr[0] = 1;

console.log(brr);
console.log(arr);

拷贝对象 使用 assign 或者对象展开语法

1

这个方法了不起啊, vue.js 和 avalon.js 都是通过他实现双向绑定.

几行代码简单了解一下

1
2
3
4
5
var a = {};
Object.defineProperty(a, "b", {
value: 123,
});
consloe.log(a.b); //123
阅读全文 »

  1. for in 这个方法适合遍历对象,如果用来遍历数组,遍历出来的索引是字符串
  2. forEach( (value, i) => { } ),遍历数组,性能不佳
  3. map,遍历数组,返回新数组
  4. for of 当不需要使用数组的索引的时候, 使用 for of 遍

快捷键

ctrl+p 查找文件

ctrl+r 查找方法

ctrl + shift + PgDn 窗口分屏

ctrl + PgDn 取消窗口分屏

修改快捷键

  1. Preferences - Browse Packages(首选项-浏览插件) 打开包管理文件夹。
  2. 打开你要修改的插件文件夹。比如你怀疑冲突的 「ConvertToUTF8」。
  3. 会看到 Default (XXX).sublime-keymap 之类的文件。注:XXX 是你当前的操作系统名称。
  4. 然后用文本编辑器打开。把里面的东西复制到 Key Bindings - User 里面。
  5. 然后修改为你要的快捷键即可。

修改快捷键.png

必备插件

Alignment : ctrl + shift + a 对其代码(就是把=上下对其)

AutoFileName : 自动补全路径

Ctags : 跳转到函数定义的位置

DocBlockr : 补全注释的格式

FindKeyConflicts : 找冲突的快捷键

Tag :

ConvertToUTF8 :

Emmet : html 智能提示

HTML/CSS/JS Prettify : 格式化 html, css, js 代码

sftp : 自动上传代码到服务器

ColorPicker : ctrl + shift + c 调出颜色

Ctags 插件

  1. 官网下载 windows 的版本 ctags58.zip,下载地址http://ctags.sourceforge.net/
  2. 将解压后的文件夹中的 ctags.exe 拷贝到 c:\windows\system32,如果拷贝到其他目录,需要配置环境变量。
  3. 在 sublime text 中的 package control install 下安装 ctags
  4. 配置 ctags 路径:将 ctags 插件下的 Settings-Default 文件打开,ctrl+a 全选复制到 Settings-User 文件
  5. 在 Setting-User 文件中修改 31 行,加入你的 ctags 路径Ctags插件_1.png
  6. 在使用函数调转功能前,需要先生成.tags 文件,只需在项目文件管理器的项目文件上右键点击 Ctags:Rebuild Tags 即可(注意,在改动文件之后也许重新成.tags)
  7. 最后,在函数名上右键点击 navigate 头 definition 跳转到指定函数了,返回用 jump back;Ctags插件_1.png

FindKeyConflicts 插件

用于找出键值映射(Key Mapping)冲突的插件

ctrl+shift+p 打开命令面板 输入

FindKeyConflicts: All Key Maps to Quick Panel 查询所有快捷键

FindKeyConflicts: (Direct) Conflicts to Quick Panel 查询所有冲突按键

sftp 自动上传

https://packagecontrol.io/installation#Simple

1
import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)

将上面的一段黏贴到控制台

然后在下载 sftp 插件

下载完成后,打开 Settings-User,填写注册码

1
2
3
4
{
"email": "xiaosong@xiaosong.me",
"product_key": "d419f6-de89e9-0aae59-2acea1-07f92a"
}

手动安装 Installed Packages

  1. 进入 sublime 的配置目录下的 Installed Packages 目录

    1
    cd ~/.config/sublime-text-3/'Installed Packages'
  2. 下载 [Package Control.sublime-package](https://packagecontrol.io/Package Control.sublime-package),需要科学上网

    1
    wget https://packagecontrol.io/Package%20Control.sublime-package
  3. 重启 sublime

  4. 配置 Packages Control.sublime-settings

    Preferences –> Packages Settings –> Packages Control –> Settings

    1
    2
    3
    4
    5
     "channels":
    [
    "http://packagecontrol.cn/channel_v3.json"
    ],
    # 修改 channels 为国内源,或者直接copy到本地
  5. 重启 sublime

添加到右键菜单

将路径替换为 sublime text3 的安装路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\SubLime]
@="edit with Sublime Text"
"Icon"="D:\\sublime_text_build_4113_x64\\sublime_text.exe,0"

[HKEY_CLASSES_ROOT\*\shell\SubLime\Command]
@="D:\\sublime_text_build_4113_x64\\sublime_text.exe \"%1\""

[HKEY_CLASSES_ROOT\Directory\shell\SubLime]
@="edit with Sublime Text"
"Icon"="D:\\sublime_text_build_4113_x64\\sublime_text.exe,0"

[HKEY_CLASSES_ROOT\Directory\shell\SubLime\Command]
@="D:\\sublime_text_build_4113_x64\\sublime_text.exe -a \"%1\""

1. 超时断线

secureCRT 无操作过一段时间会自动断线, 很麻烦

解决办法:

Option -> Global Option -> Edit Default Setting … -> Terminal -> 勾选 Send protocol NO-OP every 60s

2. 菜单消失

解决办法: alt + enter

ctrl + d

复制选中块, 或者复制当前行

ctrl + p

调出函数的参数列表

ctrl + shift + backspace

跳回最后一次修改的地方

ctrl + alt + l

格式化代码

ctrl + e

显示最近打开的文件列表

ctrl + j

提示可用的快捷方式

alt + up / down

光标定位上一个或者下一个方法

ctrl + shift +j

将下一行代码和当前行合并(去除空格)

ctrl + shift + v

显示粘贴板

ctrl + h

查看当前类的继承层次

ctrl + shift + up / down

上移 / 下移 当前 行 或者是 片段

vscode

html, js, php, python 等等, 不同文件的格式化规则是不同的, 需要单独配置, 幸运的是, 都是按快捷键 shift + alt + f(Ubuntu 上 Ctrl+ Shift+I)格式化代码。
设置 -> 文本编辑器 -> 正在格式化 -> 勾选Format On Save

sublime text 3

sublime 比较麻烦,还没有研究明白

phpstorm && pychamy

录制宏,参考:https://blog.csdn.net/koobee_1/article/details/85198009

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