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最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

Promise.all()

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

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

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

p的状态由p1、p2、p3决定,分成两种情况。

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

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

Promise.race()

类似 Promise.all(), 区别在于 下面代码中,只要p1、p2、p3之中有一个实例率先改变状态,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 (浮动)

  • 注意:
    • 浮动元素撞到父级元素的“内壁”或和其他浮动元素摞到一起时才会停止“移动”,如果父级元素的空间不够,它们就另起一行。
    • fixed或absolute元素不浮动。
    • inline-block或inline元素在浮动后会变成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 的绝对定位, 不随页面滚动改变