0%

接口

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

什么是接口

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom',
age: 25
};
//我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。

定义的变量比接口少了一些属性是不允许的。多一些属性也是不允许的。可见,赋值的时候,变量的形状必须和接口的形状保持一致

可选属性

有时我们希望不要完全匹配一个形状,那么可以用可选属性:

1
2
3
4
5
6
7
8
interface Person {
name: string;
age?: number;
}

let tom: Person = {
name: 'Tom'
};

可选属性的含义是该属性可以不存在。这时仍然不允许添加未定义的属性

任意属性

有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
name: 'Tom',
gender: 'male'
};

使用 [propName: string] 定义了任意属性取 string 类型的值。

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.

上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。

另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },这是联合类型和接口的结合。

只读属性

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly定义只读属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。

注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
name: 'Tom',
gender: 'male'
};

tom.id = 89757;

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,报错信息有两处,第一处是在对 tom 进行赋值的时候,没有给 id 赋值。

第二处是在给 tom.id 赋值的时候,由于它是只读属性,所以报错了。

数组的类型

在 TypeScript 中,数组类型有多种定义方式,比较灵活。

「类型 + 方括号」表示法

最简单的方法是使用「类型 + 方括号」来表示数组:

1
let fibonacci: number[] = [1, 1, 2, 3, 5];

数组的项中不允许出现其他的类型:

1
2
3
4
5
let fibonacci: number[] = [1, '1', 2, 3, 5];

// index.ts(1,5): error TS2322: Type '(number | string)[]' is not assignable to type 'number[]'.
// Type 'number | string' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.

上例中,[1, '1', 2, 3, 5] 的类型被推断为 (number | string)[],这是联合类型和数组的结合。

数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:

1
2
3
4
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');

// index.ts(2,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

上例中,push 方法只允许传入 number 类型的参数,但是却传了一个 string 类型的参数,所以报错了。

数组泛型

也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:

1
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

用接口表示数组

接口也可以用来描述数组:

1
2
3
4
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number

any 在数组中的应用

一个比较常见的做法是,用 any 表示数组中允许出现任意类型:

1
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
1
2
3
function sum() {
let args: IArguments = arguments;
}

React

React 介绍

  • React 是一个用于构建用户界面的渐进式 JavaScript 库
    • 本身只处理 UI
    • 不关系路由
    • 不处理 ajax
  • React主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。
    • 数据驱动视图
  • React 由 Facebook 开发
  • 第一个真生意义上把组件化思想待到前端开发领域

React 特点

  • 组件化
  • 高效
    • 虚拟 DOM
    • Vue 2 也是虚拟 DOM
    • 虚拟 DOM 更高效
  • 灵活
    • 渐进式,本身只处理 UI ,可以和你的其它技术栈组合到一起来使用
  • 声明(配置)式设计
    • data 响应式数据
    • mathods 处理函数
    • 这样做的好处就是按照我们约定好的方式来开发,所有人写出来的代码就像一个人写的
    • state
    • 方法就是类成员
    • 也有特定的组件生命钩子
  • JSX
    • 一种预编译 JavaScript 语言,允许让你的 JavaScript 和 HTML 混搭
    • 模板中就是 JavaScript 逻辑
  • 单向数据流
    • 组件传值
    • 所有数据都是单向的,组件传递的数据都是单向
    • Vue 也是单向数据流
    • 没有双向数据绑定

React 与 Vue 的对比

技术层面

  • Vue 生产力更高(更少的代码实现更强劲的功能)
  • React 更 hack 技术占比比较重
  • 两个框架的效率都采用了虚拟 DOM
    • 性能都差不多
  • 组件化
    • Vue 支持
    • React 支持
  • 数据绑定
    • 都支持数据驱动视图
    • Vue 支持表单控件双向数据绑定
    • React 不支持双向数据绑定
  • 它们的核心库都很小,都是渐进式 JavaScript 库
  • React 采用 JSX 语法来编写组件
  • Vue 采用单文件组件
    • template
    • script
    • style

开发团队

  • React 由 Facebook 前端维护开发
  • Vue
    • 早期只有尤雨溪一个人
    • 由于后来使用者越来越多,后来离职专职开发维护
    • 目前也有一个小团队在开发维护

社区

  • React 社区比 Vue 更强大
  • Vue 社区也很强大

Native APP 开发

  • React Native
    • 可以原生应用
    • React 结束之后会学习
  • Weex
    • 阿里巴巴内部搞出来的一个东西,基于 Vue

相关资源链接

起步

https://reactjs.org/docs/hello-world.html

初始化及安装依赖

1
2
3
4
$ mkdir react-demos
$ cd react-demos
$ npm init --yes
$ npm install --save babel-standalone react react-dom

Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>demo - Hello World</title>
<script src="../node_modules/babel-standalone/babel.min.js"></script>
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
</head>

<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, react!</h1>,
document.getElementById('root')
)
</script>
</body>

</html>

JSX

HTML 语言直接写在 JavaScript 语言中,不加任何引号,这就是 JSX 语法。它允许 HTML 与 JavaScript 的混写。

基本语法规则

  • 必须只能有一个根节点

  • 遇到 HTML 标签 (以 < 开头) 就用 HTML 规则解析

    • 单标签不能省略结束标签。
  • 遇到代码块(以 { 开头),就用 JavaScript 规则解析

  • JSX 允许直接在模板中插入一个 JavaScript 变量

    • 如果这个变量是一个数组,则会展开这个数组的所有成员添加到模板中
  • 单标签必须结束 />

在 JSX 中嵌入 JavaScript 表达式

  • 语法
  • 如果 JSX 写到了多行中,则建议包装括号避免自动分号的陷阱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}

const user = {
firstName: 'Harper',
lastName: 'Perez'
};

const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

ReactDOM.render(
element,
document.getElementById('root')
);

在 JavaScript 表达式中嵌入 JSX

1
2
3
4
5
6
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}

JSX 中的节点属性

  • 动态绑定属性值
  • class 使用 className
  • tabindex 使用 tabIndex
  • for 使用 htmlFor

普通的属性:

1
const element = <div tabIndex="0"></div>;

在属性中使用表达式:

1
const element = <img src={user.avatarUrl}></img>;

声明子节点

如果标签是空的,可以使用 /> 立即关闭它。

1
const element = <img src={user.avatarUrl} />;

JSX 子节点可以包含子节点:

1
2
3
4
5
6
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

JSX 自动阻止注入攻击

1
const element = <div>{'<h1>this is safe</h1>'}</div>

在 JSX 中使用注释

写法一:

1
2
3
4
{
// 注释
// ...
}

写法二(单行推荐):

1
{/* 单行注释 */}

写法三(多行推荐):

1
2
3
4
5
{
/*
* 多行注释
*/
}

JSX 原理

Babel 会把 JSX 编译为 React.createElement() 函数。

实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。如下 JSX 代码:

1
2
3
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>

会编译为:

1
2
3
4
5
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
  • 每个 React 元素都是一个真实的 JavaScript 对象

下面两种方式是等价的:

1
2
3
4
5
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
1
2
3
4
5
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
1
2
3
4
5
6
7
8
// Note: this structure is simplified
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};

列表循环

JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员。

1
2
3
4
5
6
7
8
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);

综上所述,我们可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
var names = ['Alice', 'Emily', 'Kate'];

ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);

条件渲染

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
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true}
this.handleToggleClick = this.handleToggleClick.bind(this);
}

handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}

render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}

ReactDOM.render(
<Page />,
document.getElementById('root')
);

事件处理

1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
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
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

​```
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
​```

}

handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

ReactDOM.render(
<Toggle />,
document.getElementById('root')
);

组件

React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。

组件规则注意事项

  • 组件类的第一个首字母必须大写
  • 组件类必须有 render 方法
  • 组件类必须有且只有一个根节点
  • 组件属性可以在组件的 props 获取
    • 函数需要声明参数:props
    • 类直接通过 this.props

函数式组件(无状态)

  • 名字不能用小写
    • React 在解析的时候,是以标签的首字母来区分的
    • 如果首字母是小写则当作 HTML 来解析
    • 如果首字母是大小则当作组件来解析
    • 结论:组件首字母必须大写

类方式组件(有状态)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}

// Example usage: <ShoppingList name="Mark" />

组件传值 Props

EcmaScript 5 构造函数:

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

EcmaScript 6 Class:

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

this.props.children

参考文档:https://reactjs.org/docs/react-api.html#reactchildren

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。

它表示组件的所有子节点。

this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object

用户定义的组件必须以大写字母开头

以小写字母开头的元素代表一个 HTML 内置组件,比如 <div> 或者 <span> 会生成相应的字符串 'div' 或者 'span' 传递给 React.createElement(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 <Foo /> 会编译为 React.createElement(Foo)

我们建议使用大写字母开头命名自定义组件。如果你确实需要一个以小写字母开头的组件,则在 JSX 中使用它之前,必须将它赋值给一个大写字母开头的变量。

例如,以下的代码将无法按照预期运行:

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';

// 错误!组件应该以大写字母开头:
function hello(props) {
// 正确!这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签
return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
// 错误!React 会认为 <hello /> 是一个 HTML 标签,因为它没有以大写字母开头:
return <hello toWhat="World" />;
}

要解决这个问题,我们需要重命名 helloHello,同时在 JSX 中使用 <Hello />

设置Menu

  • 默认只有两个首页和归档。

打开主题的配置文件_config.yml

1
2
3
4
5
6
7
8
9
menu:
home: / || home
#about: /about/ || user
#tags: /tags/ || tags
#categories: /categories/ || th
archives: /archives/ || archive
#schedule: /schedule/ || calendar
#sitemap: /sitemap.xml || sitemap
#commonweal: /404/ || heartbeat

然后当你要设置某项菜单时,将_config.yml对应的#去除。 然后使用hexo new page xxx生成目录即可

生成“分类”页并添加tpye属性

打开命令行,进入博客所在文件夹。执行命令

1
$ hexo new page categories

成功后会提示:

1
INFO  Created: ~/Documents/blog/source/categories/index.md

根据上面的路径,找到index.md这个文件,打开后默认内容是这样的:

1
2
3
4
---
title: 文章分类
date: 2019-07-05 21:04:49
---

添加type: "categories"到内容中,添加后是这样的:

1
2
3
4
5
---
title: 文章分类
date: 2019-07-05 21:04:49
type: "categories"
---

保存并关闭文件。

生成“标签”页并添加tpye属性

打开命令行,进入博客所在文件夹。执行命令

1
$ hexo new page tags

成功后会提示:

1
INFO  Created: ~/Documents/blog/source/tags/index.md

根据上面的路径,找到index.md这个文件,打开后默认内容是这样的:

1
2
3
4
---
title: 标签
date: 2019-07-05 21:04:49
---

添加type: "tags"到内容中,添加后是这样的:

1
2
3
4
5
---
title: 文章分类
date: 2019-07-05 21:04:49
type: "tags"
---

保存并关闭文件。

给文章添加“tags”、“categories”属性

打开需要添加标签的文章,为其添加tags属性。

1
2
3
4
5
6
7
8
9
---
title: jQuery的Dom操作
date: 2019-07-05 21:04:49
categories:
- web前端
tags:
- jQuery
- Dom
---

至此,成功给文章添加分类,点击首页的“标签”可以看到该标签下的所有文章。当然,只有添加了tags: xxx的文章才会被收录到首页的“标签”中。

打开需要添加分类的文章,为其添加categories属性。下方的categories: web前端表示添加这篇文章到“web前端”这个分类。注意:hexo一篇文章只能属于一个分类,也就是说如果在“- web前端”下方添加“-xxx”,hexo不会产生两个分类,而是把分类嵌套(即该文章属于 “- web前端”下的 “-xxx ”分类)。

redux学习笔记

学习动机

随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

使用的情形

  • 用户的使用方式复杂

  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)

  • 多个用户之间可以协作

  • 与服务器大量交互,或者使用了WebSocket

  • View要从多个来源获取数据

  • 从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。

    • 某个组件的状态,需要共享
    • 某个状态需要在任何地方都可以拿到
    • 一个组件需要改变全局状态
    • 一个组件需要改变另一个组件的状态

核心api

createStore 可以帮助我们创建一个store

store.dispatch 帮助我们派发action 这个action会传递给store

store.getState 获取到store里面所有的数据内容

store.subscribe 可以让我们订阅(监听) store的改变 只要store发生改变, 这个方法的回调函数就会执行。

Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

1
2
import { createStore } from 'redux';
const store = createStore(fn);

上面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。

State

Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过store.getState()拿到。

1
2
3
4
import { createStore } from 'redux';
const store = createStore(fn);

const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

Action

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

1
2
3
4
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};

上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux

可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

1
2
3
4
5
6
7
8
9
10
const ADD_TODO = '添加 TODO';

function addTodo(text) {
return {
type: ADD_TODO,
text
}
}

const action = addTodo('Learn Redux');

上面代码中,addTodo函数就是一个 Action Creator。

store.dispatch()

store.dispatch()是 View 发出 Action 的唯一方法。

1
2
3
4
5
6
7
import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});

上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

1
store.dispatch(addTodo('Learn Redux'));

Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

1
2
3
4
const reducer = function (state, action) {
// ...
return new_state;
};

Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。

纯函数是函数式编程的概念,必须遵守以下一些约束。

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

1
2
3
4
5
6
7
8
9
10
11
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}

最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。

store.subscribe()

Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

1
2
3
4
import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。

redux工作流程

首先,用户发出 Action。

1
store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

1
let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

1
2
// 设置监听函数
store.subscribe(listener);

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

1
2
3
4
function listerner() {
let newState = store.getState();
component.setState(newState);
}

react-redux

Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。

安装

React Redux 依赖 React 0.14 或更新版本。

1
npm install --save react-redux
1
yarn add react-redux

API

<Provider store>

<Provider store> 使组件层级中的 connect() 方法都能够获得 Redux store。正常情况下,你的根组件应该嵌套在 <Provider> 中才能使用 connect() 方法。

1
2
3
4
5
6
//自定义App组件  Provider组件将store全部提供给被它包裹的组件。 Provider内部的组件都可以获得store里的数据。
const App = (
<Provider store={store}>
<TodoList/>
</Provider>
)

connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

1
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);  // connect方法与Provider做连接  获取store里的数据

连接 React 组件与 Redux store。

连接操作不会改变原来的组件类。
反而返回一个新的已与 Redux store 连接的组件类。

参数

  • [mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用(例如,当 props 接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算)。

mapStateToProps 函数的第一个参数是整个 Redux store 的 state,它返回一个要作为 props 传递的对象。

1
2
3
4
5
6
7
//把store里的数据映射到组件的props里  
const mapStateToProps = (state) => { //state参数里的数据就是store内的数据
return {
inputValue: state.inputValue,
list: state.list
}
}
  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中dispatch方法会将 action creator 的返回值作为参数执行。这些属性会被合并到组件的 props 中。

如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起

1
2
3
4
5
6
7
8
9
10
11
// store.dispatch, props 把store.dispatch方法映射到props里  通过props调用dispatch里的方法改变store的值
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
dispatch(action)
},
}

简写语法注入 todos 和特定的 action 创建函数(addTodo and deleteTodo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { addTodo, deleteTodo } from './actionCreators'

function mapStateToProps(state) {
return { todos: state.todos }
}

const mapDispatchToProps = {
addTodo,
deleteTodo
}

export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoApp)

附上自己写的react + redux小Demo仓库:https://github.com/fuchengjx/ReduxDemo

定义类

类实质是个”特殊的函数”,就像你能够定义的函数表达式和函数声明一样, 类语法有两个组成部分: 类表达式和类声明。

class的本质是function。它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程语言的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Ponit { // 类声明 按照书写习惯 类名要大写 就像构造函数一样。
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return this.x + this.y
}
}
typeof Ponit // "function"
Point === Point.prototype.constructor // true

let p = new Ponit() //类的实例化 p为实例对象

上面的代码表明,类的数据类型就是函数,类本身就指向构造函数。 使用的时候也是直接对类使用new命令,和构造函数的用法一样。

构造函数的prototype属性在ES6的”类”上继续存在。事实上,类的所有方法都定义在prototype属性上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class B {
constructor() {
//...
}
toString() {
//...
}
toValue() {
//...
}
}

// 等同于
B.prototype = {
constructor() {},
toString() {},
toValue() {}
}

let b = new B()
b.constructor === B.prototype.constructor // true 在类的实例上调用方法,其实就是调用原型上的方法。

由于类的方法(除constructor以外)都定义在prototype对象上,所以类的新方法可以添加在prototype对象上。 另外,类的内部定义的所有方法都是不可枚举的。

提升

函数声明和类声明宅男有一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则代码会抛出一个ReferenceError。 所以在继承中也必须保证之类在父类后定义

constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时自动调用该方法。一个类必须有constructor方法,如果显示定义,JavaScript引擎会自动为它添加一个空的constructor方法。

constructor方法默认返回实例对象(即this),不过完全可以定返回另外一个对象。

1
2
3
4
5
6
7
class Foo {
constructor() {
return Object.create(null)
}
}

new Foo() instanceof Foo // false

一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor的方法,则将抛出 一个SyntaxError

一个构造函数可以使用 super 关键字来调用一个父类的构造函数。

类的实例对象

生成实例对象的写法和ES5完全一样,也是使用new命令。如果忘记加上new, 像函数那样调用Class将会报错。

1
2
3
4
5
6
7
8
9
class Point {
// ...
}

// 报错
let point = Point()

// 正确
let point = new Point()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class P {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
// ...
}
}

let p = new P(2, 3)
p.hasOwnProperty('x') // true
p.hasOwnProperty('toString') // false

上面代码中,x和y都是实例对象p自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true。而toString是原型对象的属性(因为定义在P类上),所以hasOwnProperty方法返回false。

与ES5一样,类的所有实例共享一个原型对象。

1
2
3
4
let p1 = new P(2, 3)
let p2 = new P(4, 5)

p1.__proto__ === p2.__proto__ // true

上面的代码中,p1和p2都是P的实例,它们的原型都是P.prototype,所以 __proto__属性是相等的。这也意味着,可以通过实例的 __proto__属性为类添加方法。

静态方法

类相对于实例的原型,所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类调用,成为”静态方法”。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;

return Math.hypot(dx, dy);
}
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));

静态属性

1
2
3
4
5
class Foo {

}
Foo.prop = 1
console.log(Foo.prop) // 1

上面的写法可读/写Foo类的静态属性prop。 目前,只有这种学法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。

这是我浅读<<css世界>>的一些体会与笔记

css流

: 就是”文档流”, “流”实际上是css世界中的一种基本的定位和布局机制,与现实中的水流有着异曲同工的表现。所谓流,就是css世界中引导元素排列和定位的一条看不见的”水流”。

流体布局

指的是利用元素”流”的特性实现的各类布局效果。

是典型的具有”流”特性的元素。

流、元素与基本尺寸

HTML中虽然标签种类繁多,但通常我们就把它们分为两类:块级元素(block-level element)和内联元素(inline element)。

块级元素

常见的块级元素有div、li和table等,它们都符合块级元素的基本特征—也就是一个水平流上只能单独显示一个元素,多个块级元素则换行显示。

每个元素都有两个盒子,外在盒子和内在盒子。外在盒子负责元素是可以一行显示,还是只能换行显示;内在盒子负责宽高、内容呈现什么的。 按照display的属性值不同,值为block的元素的盒子实际由外在的”块级盒子”和内在的”块级容器盒子”组成,值为inline-block的元素则由外在的”内联盒子”和内在的”块级容器盒子”组成,值为inline的元素则是内外均为”内联盒子”。

这也就是为什么属性为inline-block的元素既能和图文一行显示,又能直接设置width/height的原因了,因为有两个盒子,外面的盒子是inline级别,里面的盒子是block级别。

width/height

width/height也是作用在内在盒子,也就是”容器盒子中”

width的默认值是auto。非常深藏不露的家伙,’外部尺寸’的宽带最好不要设置,因为一旦设置,流动性就会丢失

格式化宽度

仅出现在”绝对定位模型中”,也就是出现在position属性值为absolute或fixed的元素中。在默认情况下,绝对定位元素的宽度表现是”包裹性”,宽度由内部尺寸决定。 但是,有一种情况是由外部尺寸决定的,对于非替换元素,当left/right或top/bottom对立方位的属性值同时存在的时候,元素的宽度表现为”格式化宽度”,其宽度大小相对于最近的定位特性(postion属性值不少 static)的祖先元素计算。

1
2
div { postion: absoulte; left: 20px; right: 20px}
//该div元素相对与最近的具有定位特性的祖先元素的宽度是1000px, 则这个div元素的宽度为 960px 1000-20-20

此外,和上面的普通流一样,”格式化宽度”具有完全的流体性,也就是margin、border、padding和content内容区域同样会自动分配水平(和垂直)空间。

最大宽度

如果内部没有块级元素或者块级元素没有设定宽度值,则”最大宽度”实际上是最大的连续内联盒子的宽度。

css流体布局下的宽度分离原则

所谓宽度分离原则,就是css中的width属性不与影响宽度的padding/border(有时候包括margin)属性共存。也就是分两层嵌套标签写。父元素写width,子元素写padding、margin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="father">
<div class="son"></div>
</div>

//css样式
.father {
width: 180px
}
.son {
margin: 0 20px;
padding: 20px;
border:1px solid;
}
//width独立占用一层标签,而padding、border、margin利用流动性在内部自适应呈现。

改变width/height作用细节的box-sizing

box-sizing改变了width作用的盒子。内在盒子:他们分别是content box、padding box、border box、margin box。默认情况下width作用在content box上的, box-sizing的作用就是可以把width作用的盒子变成其他几个。

1
2
3
4
.box1 {box-sizing: content-box} /*默认值*/
.box2 {box-sizing: padding-box} /*Firefox曾经支持过*/
.box3 {box-sizing: border-box} /*全部浏览器支持 一半修改都是用这个*/
.box4 {box-sizing: margin-box} /*从未支持过*/

当 .box3 {box-sizing: border-box}时, contetn box从宽度值中释放,形成局部流动性,和padding一起自动分配width值。

关于height:100%

height和width还有一个明显的区别就是对百分比定位的支持。对于width属性,就算父元素width为auto,其百分比也是支持的;但是,对于height属性,如果父元素height为auto,只要子元素在文档流中,其百分比值就完全被忽略。

eg:某个小白想要在页面插入一个

,然后满屏显示背景图。

1
2
3
4
5
div {
width: 100%; /* 这是多余的 */
height: 100%; /* 这是多余的 */
background: url(bg.jpg);
}

这个div高度永远是0,哪怕其父级塞满内容也是如此。事实上,他需要如下设置才行:

1
2
3
4
5
6
7
8
9
10
html, body {
height: 100%;
}

使用绝对定位。
div {
height: 100%;
position: absolute;
}
此时的height: 100%就会有计算值,但是绝对定位的宽高百分比是相对padding box的,非绝对定位是相对于content box计算的。

浏览器渲染的基本原理。从上到下、自内而外的顺序渲染DOM内容。

初始值

min-width/min-height的初始值都是auto

max-width/max-height的初始值都是none

max-width会覆盖 width !important , min-width会覆盖max-width。

内联元素

定义 “内联元素”的内联 特指 外在盒子。

内联盒模型

  1. 内容区域。内容区域指一种围绕文字看不见的盒子,其大小仅受字符本身特性控制,本质是一个字符盒子;但是有些元素,如图片这样的替换元素,其内容显然不是文字,不存在字符盒子子类的,因此,对于这些元素,内容区域可以看成元素自身。
  2. 内联盒子。”内联盒子”不会让内容块显示,而是排成一行,这里的”内联盒子”实际指的就是元素的”外在盒子”,用来决定是内联还是块级。该盒子又可以细分为”内联盒子”和”匿名内联盒子”。 如果外部含内联标签()等;如果是个光秃秃的文字,则属于”匿名内联盒子” 需要值得注意的是,并不是所有光秃秃的文字都是”匿名内联盒子”,其还有可能是”匿名块级盒子”,关键看前后的标签是内联还是块级。

幽灵空白节点

1
2
3
4
<div>
<span></span>
</div>
此时div的高度并不是,而是18px

我们认为在span元素前面还有个宽度为0的空白字符。

盒尺寸四大家族

content、 padding、 border、 margin

深入理解content

替换元素

根据是否具有替换内容,我们可以把元素分为替换元素和非替换元素。替换元素,顾名思义,内容可以被替换。

这种通过修改某个属性值呈现的内容就可以被替换的元素称为”替换元素”。因此img、object、video、iframe或者表单元素textarea和input都是典型的替换元素。

特性

内容的外观不受页面上的css的影响。 有自己的尺寸, 可替换元素的baseline为元素的下边缘。

input white-space pre 当文字足够多的时候,按钮不会自动换行

button white-space normal 会自动换行

尺寸

从内到外分为 固有尺寸、html尺寸、css尺寸。 默认全部为px单位。

  1. 固有尺寸 替换内容的原本尺寸,不加修饰的尺寸。 这样就是固有尺寸 img插入图片原来的大小 无法改变这个替换元素的固有尺寸。

  2. html尺寸 只能通过html原生属性改变。

    1
    2
    3
    <img width="300" height="100">
    <input size-"30">
    <textarea cols="3">
  3. css尺寸对应content box。 通过css的width和height设置。

css样式优先级最高

content与替换元素关系剖析

content属性生成的对象称为”匿名替换元素”,content生成的内容都是替换元素。

content生成的文本都是无法选中的、无法复制的。替换的仅仅是视觉层。

所以重要的内容千万不能用content生成,对可访问性和seo不友好

async

async

ES2017标准引入了async函数,使得异步操作变得更加方便。

async的改进之处

  1. async和await比起星号和yield,语义更加清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  2. async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便了许多。可以用then方法指定下一步操作。 进一步说,async函数完全可以看做由多个异步操作包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
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
function getValueA() {
let promise = new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log("i am a getValueA")
resolve(2)
},2000)
})
return promise
}
function getValueB() {
let promise = new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log("i am a getValueB")
resolve(4)
},4000)
})
return promise
}
function getValueC(a, b) {
let promise = new Promise(function (resolve, reject) {
setTimeout(()=>{
console.log("i am a getValueC")
resolve(3)
},3000)
})
return promise
}

async function getABC() {
let A = await getValueA()
let B = await getValueB()
let C = await getValueC(A,B) //将A,B的返回值传递给getABC()
return A*B*C //24

}

async函数返回一个Promise对象

async函数内部return语句返回的值,会成为then方法回调函数的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function f() {
return 'hello world';
}

f().then(v => {
console.log(v) //'hello world'
}
//上面的代码中,函数f内部return命令返回的值会被then方法回调函数接收到。

async function f() {
throw new Error('出错了')
}

f().then(v => {
console.log(v) //'Error: 出错了'
}
//async函数内部抛出错误会导致放回的Promise对象变为reject状态。 抛出的错误对象会被catch方法回调函数接收到

await命令

正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。

1
2
3
4
5
async function f() {
return await 123;
}

f().then(v => console.log(v)) //123

上面的代码中,await命令的参数是数值的123,它被转成Promise对象并立即resolve。

如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject

1
2
3
4
5
6
7
8
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了')
})
}

f().then(v => console.log(v))
.catch( e => console.log(e)) //Error: 出错了

多个await命令后面的异步操作如果不存在继发关系,最好让它们同时触发。

1
2
3
4
5
6
7
8
9
10
11
12
let foo = await getFoo();
let bar = await getBar();
//上面的代码中,getFoo和getBar是两个独立的异步操作(及互不依赖)被写成继发关系。这样比较耗时,因为只有getFoo完成后才会执行getBar,完全可以让它们同时触发。

//写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

//写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法中,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。

async函数的实现原理

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
async function fn(args) {
// ...
}

//等于
function fn(args) {
return spawn(function* () {
// ...
})
}
//所有的async函数都可以写成上面第二种形式,其中的spawn函数就是自动执行器。

//spawn函数的实现
function spawn(genF) {
return new Promise(function (resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function (e) {
step(function () {
return gen.next(v);
}, function (e) {
step(function () {
return gen.throw(e);
})
})
})
}
step(function () {
return gen.next(undefined);
})
})
}

Promise的含义

所谓Promise, 简单来说是一个容器, 里面保存这未来才会结束的事件(通常是一个异步操作)的结果。

Promise对象有以下两个特点

  1. 对象的状态不受外界影响。 Promise对象代表一个异步操作,有3种状态: Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。 只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都不会改变这个状态。
  2. 一旦状态改变就不会改变,任何时候都可以得到这个结果。Promise对象的状态改变只能有两种可能:从Pending变化为Fulfilled和从Pending变为Rejected。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,以避免了层层嵌套的回调函数。

语法

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

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。

resolve函数的作用是将Promise对象从”未完成”变为”成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去;

reject函数的作用也类似, 不过是(从Pending变为Rejected),在异步操作失败时调用,并将异步操作的错误作为参数传递出去。

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

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
let promise = new Promise(function (resolve, reject) {
console.time('st')
console.log('start')
setTimeout(() => {
console.log('Promise')
resolve('par'); //resolve将参数传给回调函数
console.timeEnd('st') //2500ms左右
},2500)
})
promise.then(function (res) {
console.time('then')
setTimeout(() => {
console.log("Resolved")
console.log(res) //par 打印接受到的参数
console.timeEnd('then') //1000ms
},1000)
})
console.log('hi')

以上分别输出
start
hi
Promise //2.5s后输出
st: 2500ms
Resolved //1s之后再输出
par
then: 1000ms

上面代码中执行: Promise新建后就会立即执行,所以首先输出start、 然后setTimeout异步执行,然后then方法指定的回调函数将在当前脚本所有同步任务执行完成后才会执行 ,所以Resolved最后输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var p1 = new Promise(function (resolve, reject) {
setTimeout( () => reject(new Error('fail')), 3000)
})
console.log(p1) //Promise { <pending> } 直接打印输出p1实例可以输出当前Promise所处状态
var p2 = new Promise(function (resolve, reject) {
resolve(p1)
})

p2.then( () => {
console.log("i am p2 callback")
}, (err) => {
console.log(err) //Promise { <rejected> }
console.log(err) //Error: fail 因为p1是Rejected,所以p2直接执行rejected函数
})

上面代码中p1,p2都是Promise的实例,此时p1的状态会传给p2。也就是说,p1的状态决定了p2的状态。

promise.all()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var r1 = new Promise(function (resolve, reject) {
console.time('start')
setTimeout(() => {
console.log('this is a request1')
console.timeEnd('start')
},3000)
})
var r2 = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('this is a request2')
},3000)
})
Promise.all([
r1,
r2
])

// this is a request1
// start: 3000ms
// this is a request2

上面代码中r1,r2都是同时输出。

这个方法返回一个新的promise对象,该promise对象在数组参数里所有的promise对象都成功的时候才会触发成功,一旦数组里面有任何一个的promise对象失败则立即触发该promise对象的失败。

HTTP概述

HTTP (HyperText Transfer Protocol,超文本传输协议)。 Web是建立在HTTP协议通信的。

HTTP 是个应用层协议。HTTP 无需操心网络通信的具体细节;它把联网的细节都
交给了通用、可靠的因特网传输协议 TCP/IP。

TCP 提供了:
• 无差错的数据传输;
• 按序传输(数据总是会按照发送的顺序到达);
• 未分段的数据流(可以在任意时刻以任意尺寸将数据发送出去)。

HTTP资源

Web 服务器是 Web 资源(Web resource)的宿主。Web 资源是 Web 内容的源头。
最简单的 Web 资源就是 Web 服务器文件系统中的静态文件。这些文件可以包含
任意内容:文本文件、HTML 文件、微软的 Word 文件、Adobe 的 Acrobat 文件、
JPEG 图片文件、AVI 电影文件,或所有其他你能够想到的格式。

MIME

(Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同
的电子邮件系统之间搬移报文时存在的问题。MIME 在电子邮件系统中工作得非常
好,因此 HTTP 也采纳了它,用它来描述并标记多媒体内容

• HTML 格式的文本文档由 text/html 类型来标记。
• 普通的 ASCII 文本文档由 text/plain 类型来标记。
• JPEG 格式的图片为 image/jpeg 类型。
• GIF 格式的图片为 image/gif 类型。
• Apple 的 QuickTime 电影为 video/quicktime 类型。
• 微软的 PowerPoint 演示文件为 application/vnd.ms-powerpoint 类型

URL

服务器资源名被称为统一资源标识符(Uniform Resource Identifier,URI)。
URI 就像因特网上的邮政地址一样,在世界范围内唯一标识并定位信息资源。

大部分 URL 都遵循一种标准格式,这种格式包含三个部分。
• URL 的第一部分被称为方案(scheme),说明了访问资源所使用的协议类型。这
部分通常就是 HTTP 协议(http://)。
• 第二部分给出了服务器的因特网地址(比如,www.joes-hardware.com)。
• 其余部分指定了 Web 服务器上的某个资源(比如,/specials/saw-blade.gif)

HTTP 报文

所有的 HTTP 报文都可以分为两类: 请求报文(request message)和响应报文
(response message)。

请求报文的格式

GET /test/hi-there.txt HTTP/1.1
Accept: text/*
Host: www.joes-hardware.com

响应报文的格式

HTTP/1.0 200 OK
Content-type: text/plain
Content-length: 19
Hi! I’m a message!

包括以下三个部分

• 起始行
报文的第一行就是起始行,在请求报文中用来说明要做些什么,在响应报文中说
明出现了什么情况。
• 首部字段
起始行后面有零个或多个首部字段。每个首部字段都包含一个名字和一个值,为
了便于解析,两者之间用冒号(:)来分隔。首部以一个空行结束。添加一个首
部字段和添加新行一样简单。
• 主体
空行之后就是可选的报文主体了,其中包含了所有类型的数据。请求主体中包括
了要发送给 Web 服务器的数据;响应主体中装载了要返回给客户端的数据。起
始行和首部都是文本形式且都是结构化的,而主体则不同,主体中可以包含任意
的二进制数据(比如图片、视频、音轨、软件程序)。当然,主体中也可以包含
文本

HTTP方法

一些常见的HTTP方法

HTTP方法 描  述
GET 从服务器向客户端发送命名资源
PUT 将来自客户端的数据存储到一个命名的服务器资源中去
DELETE 从服务器中删除命名资源
POST 将客户端数据发送到一个服务器网关应用程序
HEAD 仅发送命名资源响应中的 HTTP 首部

HTTP状态码

状态码则用来告诉客户端,发生了什么事情。状态码位于响应的起始行中。比如,在行 HTTP/1.0 200 OK 中,状态码就是 200。

状态码分类:

状态码分类

常见状态码:

100——客户必须继续发出请求

101——客户要求服务器根据请求转换HTTP协议版本

200——交易成功

201——提示知道新文件的URL

202——接受和处理、但处理未完成

203——返回信息不确定或不完整

204——请求收到,但返回信息为空

205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件

206——服务器已经完成了部分用户的GET请求

300——请求的资源可在多处得到

301——删除请求数据

302——在其他地址发现了请求数据

303——建议客户访问其他URL或访问方式

304——客户端已经执行了GET,但文件未变化

305——请求的资源必须从服务器指定的地址得到

306——前一版本HTTP中使用的代码,现行版本中不再使用

307——申明请求的资源临时性删除

400——错误请求,如语法错误

401——请求授权失败

402——保留有效ChargeTo头响应

403——请求不允许

404——没有发现文件、查询或URl

405——用户在Request-Line字段定义的方法不允许

406——根据用户发送的Accept拖,请求资源不可访问

407——类似401,用户必须首先在代理服务器上得到授权

408——客户端没有在用户指定的时间内完成请求

409——对当前资源状态,请求不能完成

410——服务器上不再有此资源且无进一步的参考地址

411——服务器拒绝用户定义的Content-Length属性请求

412——一个或多个请求头字段在当前请求中错误

413——请求的资源大于服务器允许的大小

414——请求的资源URL长于服务器允许的长度

415——请求资源不支持请求项目格式

416——请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段

417——服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求

500——服务器产生内部错误

501——服务器不支持请求的函数

502——服务器暂时不可用,有时是为了防止发生系统过载

503——服务器过载或暂停维修

504——关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长

505——服务器不支持或拒绝支请求头中指定的HTTP版本

HTTP的结构组件

在本章的概述中,我们重点介绍了两个 Web 应用程序(Web 浏览器和 Web 服务器)
是如何相互发送报文来实现基本事务处理的。在因特网上,要与很多 Web 应用程序
进行交互。在本节中,我们将列出其他一些比较重要的应用程序,如下所示。
• 代理
位于客户端和服务器之间的 HTTP 中间实体。
• 缓存
HTTP 的仓库,使常用页面的副本可以保存在离客户端更近的地方。
• 网关
连接其他应用程序的特殊 Web 服务器。
• 隧道
对 HTTP 通信报文进行盲转发的特殊代理。
• Agent 代理
发起自动 HTTP 请求的半智能 Web 客户端。

代理

代理

代理位于客户端和服务器之间,接收所有客户端的 HTTP 请求,并
将这些请求转发给服务器(可能会对请求进行修改之后转发)。对用户来说,这些应
用程序就是一个代理,代表用户访问服务器。

缓存

缓存

Web 缓存(Web cache)或代理缓存(proxy cache)是一种特殊的 HTTP 代理服务
器,可以将经过代理传送的常用文档复制保存起来。下一个请求同一文档的客户端
就可以享受缓存的私有副本所提供的服务了。 客户端从附近的缓存下载文档会比从远程 Web 服务器下载快得多。

网关

隧道

网关(gateway)是一种特殊的服务器,作为其他服务器的中间实体使用。通常用于
将 HTTP 流量转换成其他的协议。网关接受请求时就好像自己是资源的源端服务器
一样。客户端可能并不知道自己正在与一个网关进行通信。

隧道

隧道

隧道(tunnel)是建立起来之后,就会在两条连接之间对原始数据进行盲转发的
HTTP 应用程序。HTTP 隧道通常用来在一条或多条 HTTP 连接上转发非 HTTP 数
据,转发时不会窥探数据。

HTTP 隧道的一种常见用途是通过 HTTP 连接承载加密的安全套接字层(SSL,
Secure Sockets Layer)流量,这样 SSL 流量就可以穿过只允许 Web 流量通过的防
火墙了。如图 所示,HTTP/SSL 隧道收到一条 HTTP 请求,要求建立一条到目
的地址和端口的输出连接,然后在 HTTP 信道上通过隧道传输加密的 SSL 流量,这
样就可以将其盲转发到目的服务器上去了

Agent 代理

用户 Agent 代理(或者简称为 Agent 代理)是代表用户发起 HTTP 请求的客户端程
序。(也被叫做爬虫、网络蜘蛛、web机器人),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫

HTTPS

HTTPS 是最常见的 HTTP 安全版本。它得到了很广泛的应用,所有主要的商业浏览
器和服务器上都提供 HTTPS。HTTPS 将 HTTP 协议与一组强大的对称、非对称和
基于证书的加密技术结合在一起,使得 HTTPS 不仅很安全,而且很灵活,很容易
在处于无序状态的、分散的全球互联网上进行管理。

https分层

HTTPS = HTTP + SSL(或TLS) + 认证(证书) + 加密(加密算法)

HTTP和HTTPS协议的区别

1、HTTPS协议需要到证书颁发机构(Certificate Authority,简称CA)申请证书,一般免费证书很少,需要交费。

2、HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。

3、HTTP和HTTPS使用的是完全不同的连接方式,使用的端口也不一样,前者是80,后者是443。

4、HTTP的连接很简单,是无状态的。

5、HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全。

从上面可看出,HTTPS和HTTP协议相比提供了

· 数据完整性:内容传输经过完整性校验

· 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥

· 身份认证:第三方无法伪造服务端(客户端)身份

其中,数据完整性和隐私性由TLS Record Protocol保证,身份认证由TLS Handshaking Protocols实现。