ts 声明文件基本写法

全局示例

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
// global-lib.js
function globalLib(option) {
console.log(option);
}
globalLib.version = '1.0.0';
globalLib.doSomething = function () {
console.log('global do something');
};
// global.d.ts
declare function globalLib(option: globalLib.Option): void;

declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Option {
[key: string]: any;
}
}

// global.d.ts
declare function globalLib(option: globalLib.Option): void;

declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Option {
[key: string]: any;
}
}

模块示例

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
// module-lib.js
const version = '1.0.0'
const doSomething = function() {
console.log('module do something')
}
function moduleLib(option) {
console.log(option)
}

moduleLib.version = version
moduleLib.doSomething = doSomething

module.exports = moduleLib


// module-lib.d.ts
declare function moduleLib(option: Option): void

interface Option {
[key: string]: any
}

declare namespace moduleLib {
const version: string
function doSomething(): void
}

export = moduleLib



umd 模块示例

// umd-lib.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory)
} else if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else {
root.umdLib = factory()
}
}(this, function() {
return {
version: '1.0.0',
doSomething() {
console.log('umd do something')
}
}
}))


// umd-lib.d.ts
declare namespace umdLib {
const version: string
function doSomething(): void
}

// umd 模块必须写此行
export as namespace umdLib

export = umdLib

前置知识:

declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
export 导出变量
export namespace 导出(含有子属性的)对象
export default ES6 默认导出
export = commonjs 导出模块
export as namespace UMD 库声明全局变量
declare global 扩展全局变量
declare module 扩展模块
/// 三斜线指令

react hooks 初步

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码”钩”进来。 React Hooks 就是那些钩子。

react 约定, hook 一律使用 use 前缀命名,便于识别
常用 hook

useState()
useContext()
useEffect()
useReducer()
useState

useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

1
2
3
4
function Button() {
const [count, setCount] = useState(0);
return <button onClick={setCount(count + 1)}>+1</button>;
}

useState()接受初始值,返回一个数组,数组第一个是当前状态,第二个是更新状态的函数。

useContext

useContext()用于组件间共享状态。

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
// 创建一个 context 对象
const ctxDep = {name: '王花花'}
const Ctx = React.creatContext(ctxDep)
// 在组件中使用
function App() {
return (
// 下级组件使用 useContext 获取的值是 ctxDep.name
<Ctx.Provider value={ctxDep.name}>
<Sub />
<Sub2 />
</Ctx.Provider>
)
}
function Sub() {
// useContext 获取的是距离当前组件最近的 <MyContext.Provider> 的 value prop 。
// 此处的就是 ctxDep.name
const name = useContext(Ctx)
return (
<div>name: {name}</div>
)
}
function Sub2() {
const name = useContext(Ctx)
...
}

useEffect

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。

1
2
3
useEffect(() => {
// Async Action
}, [dependencies]);

useEffect()接受两个参数。第一个是一个函数,操作的代码放在里面(常见的是异步操作)。第二个是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行 useEffect()。 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect

useEffect 清除操作

在 useEffect 中返回一个函数去执行清除操作, React 会在组件卸载的时候执行清除操作。React 会在执行当前 effect 之前对上一个 effect 进行清除。

1
2
3
4
5
6
useEffect(() => {
...
return function cleanFun() {
...
}
}, [])

useReducer

useReducers()钩子用来引入 Reducer 功能。升级版 useState

1
const [state, dispatch] = useReducer(reducer, initialState);

useReducer()接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是与其配套的发送 action 的 dispatch 函数。

使用 useReducer()接受的 reducer 形式如(state, action) => newState

惰性初始化

使用 useReducer 的第三个参数来设置初始值

1
2
3
const initialState = 1;
// init 是函数,返回 {count: 1}
const [state, dispatch] = useReducer(reducerCount, initialState, init);

如果 useReducer 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。

useMemo

用于性能优化,useMemo 返回一个值。

1
const memoizedValue = useMemo(() => dosomething(a, b), [a, b]);

useMemo 会在渲染期执行,但除了第一次外,只会在依赖项发生改变后才会重新执行。

useCallback

useCallback()返回一个函数, 在依赖值没有发生改变时不触发重新渲染。

1
2
3
4
5
6
// 仅当 count 的值发生改变时,memoCallback 才触发组件重新渲染
function Foo() {
const [count, setCount] = useState(0);
const memoizedHandleClick = useCallback(() => console.log(`点击: ${count}`), [count]);
return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}

useRef

1
2
3
4
5
const refContainer = useRef(initialValue);
return (
// 此时 refContainer.current 指向下面这个 button
<button ref={refContainer}></button>
);

useLayoutEffect

效果 useEffect 相同,但会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

react 生命周期

挂载阶段

React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载。

  1. constructor: 所有关于组件自身的状态的初始化工作都会放在这里面去做

  2. static getDerivedStateFromProps(nextProps, prevState): 一个静态方法,所以不能在这个函数里面使用 this,这个函数有两个参数 props 和 state,分别指接收到的新参数和当前的 state 对象,这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null。

    该函数会在挂载时,接收到新的 props,调用了 setState 和 forceUpdate 时被调用

  3. UNSAFE_componentWillMount:组件挂载开始之前,也就是在组件调用  render  方法之前调用。进行组件的启动工作,例如 Ajax 数据拉取、定时器的启动。

  4. render:React 中最核心的方法,一个组件中必须要有这个方法

    返回的类型有以下几种:

    • 原生的 DOM,如 div
    • React 组件
    • Fragment(片段)
    • Portals(插槽)
    • 字符串和数字,被渲染成 text 节点
    • Boolean 和 null,不会渲染任何东西

    render 函数是纯函数,里面只做一件事,就是返回需要渲染的东西,不应该包含其它的业务逻辑,如数据请求,对于这些业务逻辑请移到 componentDidMount 和 componentDid Update 中

  5. componentDidMount:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用。

更新阶段

除了挂载阶段,还有一种“更新阶段”就是  setState  导致 React.js 重新渲染组件并且把组件的变化应用到 DOM 元素上的过程,这是一个组件的变化过程

  1. UNSAFE_componentWillReceiveProps(nextProps):组件从父组件接收到新的  props  之前调用。
  2. static getDerivedStateFromProps
  3. shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回  false  组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
  4. UNSAFE_componentWillUpdate():组件开始重新渲染之前调用。
  5. render()
  6. getSnapshotBeforeUpdate(prevProps, prevState):这个方法在 render 之后,componentDidUpdate 之前调用,有两个参数 prevProps 和 prevState,表示之前的属性和之前的 state,这个函数有一个返回值,会作为第三个参数传给 componentDidUpdate,如果你不想要返回值,请返回 null,不写的话控制台会有警告。
  7. componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

卸载阶段

componentWillUnmount:组件对应的 DOM 元素从页面中删除之前调用。组件从页面上销毁的时候,有时候需要一些数据的清理,例如定时器的清理,就会放在这里面去做。

参考:

React v16.4.0:你可能并不需要派生状态(Derived State)

对 React v16.4 生命周期的理解

react 学习笔记

  • _自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头_。

  • 没有经过特殊处理的话,这些  on\*  的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上

  • React.js 的事件监听方法需要手动  bind  到当前实例,这种模式在 React.js 中非常常用。

  • 当我们要改变组件的状态的时候,不能直接用  this.state = xxx  这种方式来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。所以,一定要使用 React.js 提供的  setState  方法,它接受一个对象或者函数作为参数

  • React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的  setState  都进行合并以后再重新渲染组件。

  • 可以通过给组件添加类属性  defaultProps  来配置默认参数。

  • props  一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的  props,从而达到更新的效果。

  • 没有  state  的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)

  • 对于用表达式套数组罗列到页面上的元素,都要为每个元素加上  key  属性,这个  key  必须是每个元素唯一的标识


受控组件:

React.js 认为所有的状态都应该由 React.js 的 state 控制,只要类似于<input />、<select />、<textarea>这样的输入控件被设置了  value  值,那么它们的值永远以被设置的值为准。值不变,value  就不会变化。

在 React.js 当中必须要用  setState  才能更新组件的内容,所以我们需要做的就是:监听输入框的  onChange  事件,然后获取到用户输入的内容,再通过  setState  的方式更新  state  中的  username,这样  input  的内容才会更新。


子组件传递消息给父组件

父组件只需要通过  props  给子组件传入一个回调函数。子组件调用  props  中的回调函数并且将  state  传入该函数即可。

举例:


状态提升

当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用  props  传递数据或者函数来管理这种依赖或着影响的行为。

对于不会被多个组件依赖和影响的状态(例如某种下拉菜单的展开和收起状态),一般来说只需要保存在组件内部即可,不需要做提升或者特殊的管理。


操作 DOM

1. 使用回调: 在元素上加一个  ref  属性,这个属性值是一个函数,这个节点挂载到页面上后,这个 dom 节点会作为参数传给这个函数

1
2
// 把这个 div 保存到 this 上
<div ref={div => (this.div = div)}></div>

然后我们就可以在  componentDidMount中 或之后使用这个 DOM 元素

2. 使用 React.createRef()

1
2
3
4
5
6
7
8
9
10
11
// 在 constructor 中声明
this.third = React.createRef();
// 在 render 函数中:
<input type="text" defaultValue="Third" ref={this.third} />;
// 获取 ref
this.third.current;

// 在 render 函数里面
<input type="text" defaultValue="First" ref="first" />;
// 获取 ref
this.refs.first;
  • _能不用  ref  就不用_。特别是要避免用  ref  来做 React.js 本来就可以帮助你做到的页面自动更新的操作和事件监听。多余的 DOM 操作其实是代码里面的“噪音”,不利于我们理解和维护。
  • 组件标签也可以加上  ref,此时获取的是组件实例。

props.children

组件标签也能像普通的 HTML 标签那样编写内嵌的结构

1
2
3
4
5
6
7
8
render() {
return (
<Card>
<Test />
</Card>
)
}
// Card 组件中能通过 this.props.children 获取到 <Test />

React.js 默认就支持这种写法,所有嵌套在组件中的 JSX 结构都可以在组件内部通过  props.children  获取到,props.children  是一个数组,React.js 就是把我们嵌套的 JSX 元素一个个都放到数组当中。


PropTypes

引入  prop-types

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class a extends Component {
static propTypes = {
username: PropTypes.string.isRequired,
...
PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
}
}

通过  PropTypes  给组件的参数做类型限制,可以在帮助我们迅速定位错误,这在构建大型应用程序的时候特别有用;另外,给组件加上  propTypes,也让组件的开发、使用更加规范清晰。

git 命令速查

创建仓库

#克隆远程仓库
git clone <url>
#初始化本地仓库
git init

修改和提交

#查看当前目录文件状态(已修改,未暂存...)
git status
#查看还未加入暂存区的文件的变更内容
git diff
#跟踪所有改动过的文件
git add .
#跟踪指定文件
git add "fileName"
#文件改名
git mv "oldName" "newName"
#删除文件
git rm "fileName"
#从仓库和暂存区删除,但工作区保留
git rm --cached "fileName"
#提交所有在暂存区且发生变动的文件
git commit -m "commit message"
#跳过使用 git add,提交所有跟踪过的文件
git commit -a

查看提交历史

#查看提交历史,不带参数时,按时间列出更新,最近更新在最上面
git log
#查看每次提交的内容差异
git log -p
#查看指定文件的提交历史
git log -p "fileName"
#最近n条历史
git log -p -<n>

更多: git log 相关

撤销

#重写上一次的 commit message, 不会产生新的提交
git commit --amend
#取消暂存的文件
git reset HEAD "fileName"
#撤销修改,还原文件为上次提交时的样子
git checkout -- "fileName"

分支

#显示所有分支
git branch
#创建新分支
git branch ”newBranch“
#切换分支
git checkout "branchName"
#合并指定分支到当前分支
git merge "branchName"
#查看已经合并到当前分支的分支们
git branch --merged
#删除指定分支
git branch -d "branchName"

远程

# 下载远程仓库的所有变动
git fetch [remote]
# 显示所有远程仓库
git remote -v
# 显示某个远程仓库的信息
git remote show [remote]
# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]
# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]
# 上传本地指定分支到远程仓库
git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force

git 基本概念

Git 的三个工作区域:

  • git 仓库:保存项目的数据的地方
  • 工作区: 从 git 仓库中提取出来,供自己编辑修改的文件
  • 暂存区: 保存下次将提交的的文件列表信息

Git 有三种状态:

  • 已提交(committed) 数据已保存在本地数据库
  • 已修改(modified) 数据已在编辑器中修改并保存在文件
  • 已暂存(staged) 对已修改的文件做标记,下次提交到 git 仓库

Git 基本工作流程

  1. 在工作目录中修改文件。
  2. 暂存文件,将文件的快照放入暂存区域。
  3. 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录

JS 中关于类型的几个问题

参考: winter 的重学前端


为什么有的编程规范要求用 void 0 代替 undefined?

因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。(void 运算来把任一一个表达式变成 undefined 值)

字符串有最大长度吗?

String 用于表示文本数据。String 有最大长度是 2^53 - 1,这在一般开发中都是够用的,但是这个所谓最大长度,并不完全是你理解中的字符数。因为 String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?

由于 JavaScript 中浮点数的运算精度导致的,要进行此类比较要使用如下方式:

1
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);

ES6 新加入的 Symbol 是个什么东西?

Symbol 是 ES6 中引入的新类型,就像 Number、String、和 Boolean 一样。每个创建的 symbol 都是一个独一无二的值。

为什么给对象添加的方法能用在基本类型上?

. 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

JS 中的类型转换

参考: winter 的重学前端


字符串到数字

使用 Number() 转换

Number() 支持十进制、二进制、八进制和十六进制,和科学计数法

1
2
3
4
5
6
Number('123'); // 123  	十进制
Number('0b111'); // 7 二进制
Number('0o13'); // 11 八进制
Number('0xc'); // 12 十六进制
Number('2e3'); // 2000 科学计数法
Number('123ab'); // NaN 出现非数字字符

parseInt() 和 Number() 的区别

parseIntparseFloat 并不使用上述转换规则,所以支持的语法跟这里不尽相同。在不传入第二个参数的情况下,parseInt只支持 16 进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法。多数情况下 Number 是比 parseIntparseFloat更好的选择。

数字到字符串

  • 在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。
  • Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示。(可能是避免产生的字符串太长)

基本类型装箱转换为对象

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类。所谓装箱转换,正是把基本类型转换为对应的对象。

怎么显式转换呢?

直接使用 new 对应的类。之后就能使用对应 的方法了。

1
2
3
new Number(123); // Number {123}
new String('abc'); // String {"abc"}
new Boolean(true); // Boolean {true}

使用 . 运算符产生临时对象

1
2
// . 运算符 产生一个临时对象,使得 "123" 能够使用对象的方法
'123'.length; // 3

上面的示例中,. 运算符的作用大致相当于 new String()

1
2
var strObj = new String('123');
strObj.length; // 3

特殊的 Symbol

Symbol 对象不能直接 new 出来。但可以使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。

1
2
var symbolObject = Object(Symbol('a'));
typeof symbolObject; // "object"

判别装箱转换后的对象对应的基本类型

每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取

1
2
3
4
var strObj = new String('abc'); // strObj 是一个对象
var symObj = Object(Symbol('sym')); // symObj 是一个对象
Object.prototype.toString.call(strObj); // "[object String]"
Object.prototype.toString.call(symObj); // "[object Symbol]"

在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。

对象拆箱转换为基本类型

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。

对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。

拆箱转换过程

拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。默认先调用 valueOf ,如果valueOf 返回的值不对再调用toString,依然不对就会报错。

1
2
3
4
5
6
7
8
9
10
11
var o = {
valueOf: () => {
console.log('valueOf');
return {};
},
toString: () => {
console.log('toString');
return {};
},
};
o * 2; // valueOf // toString // TypeError

覆盖拆箱转换默认行为

通过显式指定对象的 toPrimitive Symbol 来覆盖原有的行为。

1
2
3
4
5
o[Symbol.toPrimitive] = () => {
console.log('toPrimitive');
return 'hello';
};
o + ''; // 'hello'

typeof 结果与运行时类型对比

typeof结果与运行时类型对比.png

永远不用隐式转换

规则复杂,不要浪费宝贵的精力去记忆这种东西。需要转换的时候,应该用显式转换,不要省这点功夫。
真要用,用的时候再上网查

JS中关于类型的细节

参考: winter 的重学前端


JS 中有哪些类型?

JavaScript 语言的每一个值都属于某一种数据类型。JavaScript 语言规定了 7 种语言类型。语言类型广泛用于变量、函数参数、表达式、函数返回值等场合。根据最新的语言标准,这 7 种语言类型是:

  1. Undefined
  2. Null
  3. Boolean
  4. String
  5. Number
  6. Symbol
  7. Object

Undefined 和 Null

  • Undefined 类型只有一个值,就是 undefined,JS 中任何变量在赋值前,它的类型是 Undefined,值为 undefined。一般可以用 JS 中的全局变量 undefined(就是名为 undefined 的这个变量)来表达这个值,或者 void 运算来把任一一个表达式变成 undefined 值。

  • Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined不同,null 是 JavaScript 关键字,所以在任何代码中,都可以放心用 null 关键字来获取 null 值。

undefined 和 null 的区别

undefined 跟 null 有一定的表意差别,null 表示的是:定义了但是为空。所以,在实际编程时,我们一般不会把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。

Boolean

Boolean 类型有两个值,truefalse,它用于表示逻辑意义上的真和假,同样有关键字 true 和 false 来表示两个值。

String

String 用于表示文本数据。String 有最大长度是 2^53 - 1。

String 是 UTF16 编码

String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

String 可以作为值

JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

Number

JavaScript 中的 Number 类型有 2^64-2^53+3 个值。

Number 里的特殊值

  • NaN,占用了 9007199254740990 个特殊值来表示 NaN,这原本是符合 IEEE 规则的数字;
  • Infinity,无穷大;
  • -Infinity,负无穷大。

+0-0 不同

  • 通过检测 1/x 是 Infinity 还是 -Infinity 去区分 x 是 +0 还是 -0 。

浮点数运算的精度问题

1
console.log(0.1 + 0.2 == 0.3); // false

JS 中浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。

正确的比较方法是使用 JavaScript 提供的最小精度值,检查等式左右两边差的绝对值是否小于最小精度

1
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON); // true

Symbol

Symbol 是个啥?

Symbol 是 ES6 中引入的新类型,就像 Number、String、和 Boolean 一样。

如何创建 Symbol 类型

与别的原始类型不同,Symbols 没有字面量语法(例如,String 有 '')—— 创建 Symbol 的唯一方式是使用全局的 Symbol 函数。记住每个被创建出来的symbol 值都是独一无二的。

1
2
3
4
5
// 可以不用加参数,加了参数以后,就等于为它们加上了描述,输出的时候我们就能够分清,到底是哪一个值。
let s = Symbol('test');
typeof s; // "symbol"
// 每个被 Symbol() 创建的 symbol 都是独一无二的
Symbol('123') === Symbol('123'); // false

Symbol 是可以用来干嘛?

  1. 作为对象的属性名,可以保证这个属性名永远不会冲突(不能用. 运算符)
  2. 给予开发者在 API 中为对象改写默认行为的能力
    • 操作 ES6 中对象内置的 Symbols 属性,例如 Symbol.iterator 等这些内置的 Symbol 可以 JavaScript 内部行为

Object

Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。Object 表示对象的意思,它是一切有形和无形物体的总称。

JS 中的 Object ?

在 JavaScript 中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是 key-value 结构,key 可以是字符串或者 Symbol 类型。

JS 中的类与对象

JavaScript 中的“类”仅仅是运行时对象的一个私有属性,而 JavaScript 中是无法自定义类型的。

基本类型与 Object 的联系

JavaScript 中的几个基本类型,都在对象类型中有一个“亲戚”。它们是:

  • Number
  • String
  • Boolean
  • Symbol

Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。

Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。

对象为基本类型提供的便利

日常代码可以把对象的方法在基本类型上使用,例如:

1
console.log('abc'.length); // 3

原因在于**.** 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。但是 3new Number(3) 是完全不同的值,它们一个是 Number 类型, 一个是对象类型。

图解

JS运行时类型.png

常用语义标签快速了解

aside - 侧栏

aside 表示跟文章主体不那么相关的部分,它可能包含导航、广告等工具性质的内容。


article - 独立主体

页面中具有明确独立性的部分。


header,footer - 头部/底部

  • header,如其名,通常出现在前部,表示导航或者介绍性的内容。

  • footer,通常出现在尾部,包含一些作者信息、相关链接、版权信息等。


section - 语义化 div

section 元素代表文档中的“节”或“段”,“段”可以是指一片文章里按照主题的分段;“节”可以是指一个页面里的分组。section 通常还带标题,虽然 html5 中 section 会自动给标题 h1-h6 降级,但是最好手动给他们降级。


hgroup, h1, h2 - 标题组

hgroup 是标题组,h1 是一级标题,h2 是二级标题,出现有主副标题情况时,用 hgroup 包裹住

1
2
3
4
<hgroup>
<h1>主标题</h1>
<h2>副标题</h2>
</hgroup>

abbr - 缩写

用来包裹缩写的内容。

1
<abbr title="World Wide Web">WWW</abbr>

hr - 转折

样式表现为一根横线,但表示的是故事走向的转变或者话题的转变,如果需要纯视觉效果的横线不应用此标签,而只用 CSS 去实现。


p - 段落

一般用来表示段落,也可以用 class = "note" 的方式表示 HTML 中没有相关语义标签时的替代。


strong, em - 强调

strong 表示包裹的内容很重要, em 表示重音,防止歧义。


blockquote, q, cite - 引用

blockquote 表示段落级引述内容,q 表示行内的引述内容,cite 表示引述的作品名。


time - 时间

1
<time datetime="2019-7-30">30 July 2019</time>

figure, figcaption - 独立插入

figure 表示一段富文本,可以是一个文章插图、一段代码、一个表格,通常搭配 figcaption 来表述这段富文本的描述/标题,当然,一个 figure 下只能有一个 figcaption,也可以没有


dfn - 定义

用来包裹被定义的名词

1
<dfn>苹果</dfn>是一种水果。

nav 表示网站的导航,但不一定所有的导航都需要用 nav 来实现,建议仅用来实现比较重要的导航,例如网页页脚的链接列表,直接 footer 即可。另外,每个页面可以有多个 nav。ul 表示无序列表, ol 表示有序列表。ul,ol 多数出现正在行文中间,它的上文多数在提示:要列举某些项。不要给所有并列关系,递进关系都加上 ul, ol 标签。


pre, samp, code - 预设置

pre 标签,表示这部分内容是预先排版过的,不需要浏览器进行排版。samp 标签表示一段计算机程序的示例输出。code 标签表示一段代码。

1
2
3
4
5
6
<pre>
<samp>
GET /home.html HTTP/1.1
Host: www.example.org
</samp>
</pre>
1
2
3
4
5
6
7
8
9
10
11
12
<pre>
<code>
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Example.org – The World Wide Web&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;The World Wide Web, abbreviated as WWW and commonly known ...&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code>
</pre>

不常用语义标签

不常用语义标签.jpg

快速理解 Web 语义化

参考: winter 的重学前端


语义类标签是什么?

语义是我们说话表达的意思,多数的语义实际上都是由文字来承载的。语义类标签则是纯文字的补充,比如标题、自然段、章节、列表,这些内容都是纯文字无法表达的,我们需要依靠语义标签代为表达。

为什么要用语义?

  • 正确的标签做正确的事情
  • 页面内容结构化
  • 语义类标签增强了可读性,无 CSS 样子时也容易阅读,便于阅读维护和理解,对开发者友好
  • 便于浏览器、搜索引擎解析。 利于爬虫标记、利于 SEO
  • 语义类可以支持读屏软件,根据文章可以自动生成目录

什么情况下不需要使用语义?

在产品里,HTML 用于描述软件界面多过富文本时,因为软件界面里的东西,实际上几乎是没有语义的。比如说,我们做了一个购物车功能,购物车这个按钮,我们一定要用Button吗?实际上我觉得没必要,因为这个场景里面,跟表单中的Button,其实已经相差很远了,所以,在任何软件界面的场景中,可以直接使用 div 和 span。

语义化的三个常见使用场景

自然语言表达能力的补充

作为自然语言和纯文本的补充,用来表达一定的结构或者消除歧义


比如使用 em 标签去消除歧义

1
我今天吃了一个苹果

我们看看这句话,看上去它很清楚,但是实际上,这句话放到不同上下文中,可能表达完全不同的意思。

1
昨天我吃了一个香蕉。 今天我吃了一个苹果。

再比如:

1
昨天我吃了两个苹果。 今天我吃了一个苹果。

在读这两段里面的“今天我吃了一个苹果”,发现读音不自觉地发生了变化。

实际上,不仅仅是读音,这里的意思也发生了变化。前一段中,表示我今天吃的是苹果,而不是别的什么东西,后一段中,则表示我今天只吃了一个苹果,没有多吃。

当没有上下文时,如何消除歧义呢?这就要用到我们的em标签了。em 表示重音:

1
今天我吃了一个<em>苹果</em>。 今天我吃了<em>一个</em>苹果。

通过em标签,我们可以消除这样的歧义。

一些文章常常会拿emstrong做对比,实际上,我们只要理解了em的真正意思,它和strong可谓天差地别,并没有任何混淆的可能。

文章标题摘要

一篇文档会有一个树形的目录结构,它由各个级别的标题组成。这个树形结构可能不会跟 HTML 元素的嵌套关系一致。


h1-h6 是最基本的标题,它们表示了文章中不同层级的标题。有些时候,我们会有副标题,为了避免副标题产生额外的一个层级,我们使用 hgroup 标签。

我们来看下有/无 hgroup 的对比:

1
2
3
4
<h1>JavaScript对象</h1>
<h2>我们需要模拟类吗?</h2>
<p>balah balah</p>
......

此段生成以下标题结构:

  • JavaScript 对象
    • 我们需要模拟类吗?
1
2
3
4
5
6
<hgroup>
<h1>JavaScript对象</h1>
<h2>我们需要模拟类吗?</h2>
</hgroup>
<p>balah balah</p>
......

这一段生成以下标题结构:

  • JavaScript 对象——我们需要模拟类吗?

我们通过两个效果的对比就可以知道,在 hgroup 中的 h1-h6 被视为同一标题的不同组成部分。

从 HTML 5 开始,我们有了 section 标签,这个标签可不仅仅是一个“有语义的 div”,它会改变 h1-h6 的语义。section 的嵌套会使得其中的 h1-h6 下降一级,因此,在 HTML5 以后,我们只需要 section 和 h1 就足以形成文档的树形结构:

适合机器阅读的整体结构

随着越来越多的浏览器推出“阅读模式”,以及各种非浏览器终端的出现,语义化的 HTML 适合机器阅读的特性变得越来越重要。

应用了语义化结构的页面,可以明确地提示出页面信息的主次关系,它能让浏览器很好地支持“阅读视图功能”,还可以让搜索引擎的命中率提升,同时,它也对视障用户的读屏软件更友好。

我们正确使用整体结构类的语义标签,可以让页面对机器更友好。比如,这里一个典型的 body 类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<header>
<nav>...</nav>
</header>
<aside>
<nav>...</nav>
</aside>
<article>
<header>...</header>
<section>...</section>
<section>...</section>
<section>...</section>
<footer>...</footer>
</article>
<article>...</article>
<article>...</article>
<footer>
<address></address>
</footer>
</body>

在 body 下面,有一个 header,header 里面是一个 nav,跟 header 同级的有一个 aside,aside 里面也有一个 nav。接下来是多个独立的文章,也就是一个一个的 article。每一个 article 里面都有自己的 header、section、footer。section 里面可能还有嵌套,但是我们就不管了,最后是一个 footer,这个 footer 里面可能有 address 这样的内容。

在这个结构里,我们看到了一些新标签,我也来逐个介绍一下。

  • header,如其名,通常出现在前部,表示导航或者介绍性的内容。
  • footer,通常出现在尾部,包含一些作者信息、相关链接、版权信息等。

header 和 footer 一般都是放在 article 或者 body 的直接子元素,但是标准中并没有明确规定,footer 也可以和 aside,nav,section 相关联(header 不存在关联问题)。

  • aside 表示跟文章主体不那么相关的部分,它可能包含导航、广告等工具性质的内容。

aside 很容易被理解为侧边栏,实际上二者是包含关系,侧边栏是 aside,aside 不一定是侧边栏。

aside 和 header 中都可能出现导航(nav 标签),二者的区别是,header 中的导航多数是到文章自己的目录,而 aside 中的导航多数是到关联页面或者是整站地图。

最后 footer 中包含 address,这是个非常容易被误用的标签。address 并非像 date 一样,表示一个给机器阅读的地址,而是表示“文章(作者)的联系方式”,address 明确地只关联到 article 和 body。

总结

本篇中我们介绍了一些基本原则和 HTML 文档的整体结构,从整体上了解了 HTML 语义。
分一些场景来看语义,把它用在合适的场景下,可以获得额外的效果。