js 中的闭包

什么是闭包?

闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。举例:

1
2
3
4
5
6
7
8
9
10
const a = function () {
let n = 1;
return function () {
console.log(n);
n++;
};
};
const b = a();
b(); // result: 1
b(); // result: 2

在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。

循环中使用闭包解决 var 定义函数的问题

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
//result;全是 6

因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i就是 6 了,所以会输出一堆 6。

解决办法(三种)

一、使用闭包

1
2
3
4
5
6
7
8
for (var i = 1; i <= 5; i++) {
(function timer(j) {
setTimeout(function () {
console.log(j);
}, j * 1000);
})(i);
}
// result: 1 2 3 4 5

在上面的代码中,我们使用立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。(其实相当于创建了 5 个函数作用域, timer 执行的时候就在自己的作用域里去访问 j 的值,而不是统一访问 i)

第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。

1
2
3
4
5
6
7
8
9
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j);
},
i * 1000,
i
);
}

第三种就是使用 let 定义 i 了来解决问题了,这个也是 最为推荐 的方式

1
2
3
4
5
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}

CSS 常用命名惯例参考

贴士:以下常用名称可灵活组合使用

方位、大小及范围

命名 意义 原型
top
bottom
left
right
front
back
side
row
col column
bar 条;如search-bar - 搜索条
box 框、盒子;如login-box - 登录框
block 块;跟盒子一样
size 尺寸
sm small
md medium
lg large
bold
light
horizontal 水平的
vertical 垂直的
normal 正常的;常规的
align 对齐
parent 父级

页面主结构

命名 意义 原型
header 页头,头
nav 导航栏 navigation
main 主要
content 内容
main-content 主要内容
footer 页脚
container 容器
wrapper 同上

页面辅助结构

命名 意义 原型
logo 商标,标志
headline 头条,头版头
sidebar 侧栏
sidebar-left 左侧栏(其他方向以此类推)
list 列表
title 标题
cat 分类 category
icon 图标
menu 菜单
tab 标签页
banner 条幅,标语
slider, carousel 轮播
btn 按钮 button
msg 消息 message
sub-... 子… 如:sub-menu - 子菜单
...-item 单条… 如:nav-item - 单条导航(导航链接)

电商站

命名 意义 原型
ad 广告 advertising
promotion 促销
hot 热卖
new 新品
product 产品
price 价格
summary 摘要
publisher 厂商
buy 购买
pay 支付

常用功能

命名 意义 原型
login 登录
signup, reg 注册 regsiter, signup
logout 登出
search 搜索
keyword 关键词
input 输入
output 输出
scroll 滚动
ranking 排行
anno 公告 announcement

Promise 快速入门

Promise 的含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// resolve, reject 由 js 引擎部署
const p = new Promise((resolve, reject) => {
console.log('yo.')
// some code

// 是当前的 Promise 对象的状态从 'pending' 变为 'resolved' 成功
// 并且不再改变
resolve('hello resovle'
// reject 让状态从 'pending' 变为 'reject' 失败
// reject(err)
})
// Promise 内部的代码,会在 new 的时候立即执行

// Promise 状态为 resolve 时,执行 then()
p.then((value) => {
// resolve 会把他的参数传给 then()
console.log(value) // 'hello resolve'
})
.catch((err) => {
// 在 Promise 状态为 reject 时执行
// 或 前面的 then() 发生错误时执行
console.log(err)
})

方法精讲

Promise 的常用 API 如下:

  • Promise.resolve(value)

类方法,该方法返回一个以 value 值解析后的 Promise 对象 1、如果这个值是个 thenable(即带有 then 方法),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)
2、如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
3、其他情况以该值为成功状态返回一个 Promise 对象。

上面是 resolve 方法的解释,传入不同类型的 value 值,返回结果也有区别。这个 API 比较重要,建议大家通过练习一些小例子,并且配合上面的解释来熟悉它。如下几个小例子:

1
2
3
4
5
6
7
8
9
10
11
//如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
function fn(resolve){
setTimeout(function(){
resolve(123);
},3000);
}
let p0 = new Promise(fn);
let p1 = Promise.resolve(p0);
// 返回为true,返回的 Promise 即是 入参的 Promise 对象。
console.log(p0 === p1);
复制代码

传入 thenable 对象,返回 Promise 对象跟随 thenable 对象的最终状态。

ES6 Promises 里提到了 Thenable 这个概念,简单来说它就是一个非常类似 Promise 的东西。最简单的例子就是 jQuery.ajax,它的返回值就是 thenable 对象。但是要谨记,并不是只要实现了 then 方法就一定能作为 Promise 对象来使用。

1
2
3
4
5
6
//如果传入的 value 本身就是 thenable 对象,返回的 promise 对象会跟随 thenable 对象的状态。
let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象
promise.then(function(value){
console.log(value);
});
复制代码

返回一个状态已变成 resolved 的 Promise 对象。

1
2
3
4
let p1 = Promise.resolve(123);
//打印p1 可以看到p1是一个状态置为resolved的Promise对象
console.log(p1)
复制代码
  • Promise.reject

类方法,且与 resolve 唯一的不同是,返回的 promise 对象的状态为 rejected。

  • Promise.prototype.then

实例方法,为 Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的 then 回调接收。

  • Promise.prototype.catch

实例方法,捕获异常,函数形式:fn(err){}, err 是 catch 注册 之前的回调抛出的异常信息。

  • Promise.race

类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。 。

  • Promise.all

类方法,多个 Promise 任务同时执行。
如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。


方法详解

Promise.prototype.then()

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

1
2
3
4
5
6
p.then(val => {
return val
// return val 会让 val 作为 下一个 then() 的参数
}).then(val => {
console.log(val)
}).then(...)

Promise.prototype.catch()

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

1
2
3
4
5
6
7
8
p(val)
.then(() => {
// ...
})
.catch(err => {
// 处理 p(val) 和 前一个回调函数运行时发生的错误
console.log('发生错误!', err);
});

Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代码中,不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race()

类似 Promise.all(), 区别在于 下面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

1
const p = Promise.race([p1, p2, p3]);

参考 阮一峰 ES6/promise

CSS 基本

在 HTML 中使用 CSS(引入 CSS)

1
2
3
内联(inline style attribute)
<head> 标签内的 <style> 标签
<link> 标签中的外联

样式优先级(从高到低)

1
2
3
4
!important
内联样式
<style> 中的样式
link 中的样式

选择器

没有选择器就没法确定样式的作用范围,毫无重点的加样式,最后只会是一锅粥。所以熟练使用选择器是必备技能。

核心选择器

  • . (英文句点)
    • 类选择器,使用类名选择。可能是使用最多,也是最灵活的选择器了。
    • 如:
      • HTML: <div class="red">Yo</div>
      • CSS:.red { ... }
        • .red 就是类选择器,由于类可以在页面中重复出现,它天生就拥有批量选择的能力。
  • #(井号)
    • ID 选择器,使用 ID 选择。
    • 如:
      • HTML:<div id="logo">Yo</div>
      • CSS logo { ... }
        • #logo 就是 ID 选择器,由于 ID 不可以在页面中重复出现,它天生就拥有极高权重,也就是说如果类选择下定义的样式和 ID 选择器下定义的样式发生冲突后一定是 ID 选择器获胜。
  • div(元素名称)
    • 元素选择器,直接使用元素名称选择。
    • 如:
      • HTML:<div>Yo</div>
      • CSS:div { ... }
        • 一般与.#结合使用。

核心选择方式

  • 目标1 目标2 ...(注意中间的空格)
    • 后代选择器,可以将选择范围缩小至前一个元素。
    • 目标之间可以是任何层级的包含关系,如父子关系,爷孙关系,不适用于兄弟关系。
  • 目标1 > 目标 (大于号)
    • 父子选择器,可以将范围缩小至儿子辈,但选不中孙子辈。

选择器优先级(从高到低)

1
2
3
4
5
!important
内联样式
id 选择器
class 选择器
元素选择器

核心属性

display (显示方式)

  • block
    • 块,默认占整宽。
    • 通常作为容器分割页面,一些元素默认显示为block,如<div><p>
  • inline
    • 行内,通常存在于块级元素中作为内容填充,如文章的文字,图标,链接。一些元素默认显示为inline,如<span><a>
  • inline-block
    • 行内块。可设置宽高。

float (浮动)

  • 注意:
    • 浮动元素撞到父级元素的“内壁”或和其他浮动元素摞到一起时才会停止“移动”,如果父级元素的空间不够,它们就另起一行。
    • fixedabsolute元素不浮动。
    • inline-blockinline元素在浮动后会变成block元素。
    • 未指定宽度的block元素的宽度会根据其内容长度而动态变化。
    • 一组“正常”的元素中如果有一个元素浮动会发生什么事情?

盒模型

1
2
3
4
content
padding
border
margin

position (元素定位)

static relative absolute fixed

1
2
3
4
非 static 元素可以用 top left bottom right 属性来设置坐标
relative 是相对定位,原位置仍然留有空间
absolute 完全绝对定位, 忽略其他所有东西, 往上浮动到非 static 的元素
fixed 基于 window 的绝对定位, 不随页面滚动改变