# 知食记

给岁月以文明，而不是给文明以岁月

## 目录

[思维导图](/mind-maps) / [归档 ](/archive)/ [博客](/blog)&#x20;

### 🎃CSS

[CSS 基础 ](/css/css-basis)/ [CSS3](/css/css3) / [SCSS](/css/scss)

#### &#x20;🎉JavaScript&#x20;

[JS 概念](/javascript/js-concept) / [JS 陷阱](/javascript/js-trap) / [JS 开发知识点](/javascript/js-kai-fa-zhi-shi-dian) / [实现 JS 常见函数](/javascript/chang-jian-han-shu) / [JS Worker](/javascript/js-worker) / [ES6](/javascript/es6-1) / [ES6 函数](/javascript/es6-han-shu) / [TypeScript](/javascript/typescript) / [V8](/javascript/v8)

### 🕹️框架

[Vue](/framework/vue-1) / [Vue3](/framework/vue3) / [React](/framework/react) / [Nuxt](/framework/nuxt) / [Koa2](/framework/koa2)

### 🎯算法

[算法与数据结构](/algorithm/algorithm-and-data-structure)

### 🎁HTML

[DOM](/html/dom) / [SVG](/html/svg)

### 🏈计算机网络

[浏览器](/network/liu-lan-qi) / [计算机网络](/network/ji-suan-ji-wang-luo)

### 🥊前端生态

[Webpack](/frontend-ecosystem/webpack) / [Babel](/frontend-ecosystem/babel) / [Fetch](/frontend-ecosystem/fetch) / [Axios](/frontend-ecosystem/axios-1) / [Npm](/frontend-ecosystem/npm) / [Yarn](/frontend-ecosystem/yarn) / [开源项目](/open-source/open-source-project) / [业务开发](/frontend-ecosystem/ye-wu-kai-fa) / [微前端](/frontend-ecosystem/wei-qian-duan) / [Hexo](/frontend-ecosystem/hexo-1)&#x20;

### 🏀后端

[Node](/backend/node) / [Java](/backend/java) / [Python](/backend/python)

### 🕹️面试

[面试真经](/interview/inverview-record)

### 🤖开源

[开源项目](/open-source/open-source-project)

### 🧸其他

[Linux](/other/linux) / [Git](/other/git) / [正则](/other/zheng-ze) / [设计模式](/other/she-ji-mo-shi-1) / [计算机理论](/other/ji-suan-ji-li-lun)

## 关于我

英文名 Ricky，工程师，虽然是搞技术的，但是点子挺多的。

## 关于本站

本站基于 [Gitbook](https://gitbook.com) 编辑生成 Markdown 文件，并托管在 GitHub 上。

Gitbook 基于树形结构组织文档，方便系统性的知识梳理。

## 关于知食&#x20;

知“识”应该是知“食”，从“识”到“食”即是一个看到到咀嚼和品尝的过程。

借以知食的名义，梳理我对各种知识点的理解。

## 关于积累

为什么会有这个站？&#x20;

涉猎的东西越来越多后，发现过去的记忆变得模糊。

借用《三体》里面有一句我非常喜欢的话：

> 给岁月以文明，而不是给文明以岁月

这里在岁月中一点一滴地篆刻知识的文明吧。


# 思维导图

gitbook是抽屉状的思维结构，比较适合日常的知识结构化整理。

xmind是树状的思维结构，比较适合知识的体系化构建。

![](/files/-M0FsPyLwecBg8IlVZmN)

我用 xmind 画了一波前端的知识体系图，包括

1. 浏览器与JS线程
2. 前端优化
3. 正则速查表
4. vue 性能优化总结
5. vue-reactive
6. css知识点
7. JavaScript常见概念
8. TypeScript知识点
9. 垃圾回收
10. webpack
11. 前端网络知识点
12. 前端存储
13. flex布局
14. vue 知识点

具体可见notion: <https://www.notion.so/8dedb12b567a4170a34a75053f4c2c8d?v=c0e1143c74204efaa2274a5fad0ce9bc>


# 归档

{% content-ref url="/pages/lm1yvmSGW0vYXuQEcMMd" %}
[理解 CAP 理论，以开一家餐厅为例](/blog/cap-understanding)
{% endcontent-ref %}

{% content-ref url="/pages/-MR01\_xa1g9u1McUb\_bL" %}
[TypeScript tsconfig.json 整理](/blog/tsonfig-json)
{% endcontent-ref %}

{% content-ref url="/pages/-MNR3zfvNTc7SNh6Ys7y" %}
[this 陷阱与原理](/blog/trap-and-principle-of-this)
{% endcontent-ref %}

{% content-ref url="/pages/-MKmdgVqt7QpKnHdvpyD" %}
[时间戳与时区](/blog/timestamp-timezone)
{% endcontent-ref %}

{% content-ref url="/pages/-MKd0ZTeF0xSEd9JUTB5" %}
[vue-cli 项目添加 tailwind.css](/blog/vue-cli-tailwind)
{% endcontent-ref %}

{% content-ref url="/pages/-MF9Pxy8sSZAEeJf4YEP" %}
[实现 JavaScript 数组的逆序索引](/blog/shi-xian-javascript-shu-zu-de-ni-xu-suo-yin)
{% endcontent-ref %}

{% content-ref url="/pages/-M4qDqVUmHnj0WZRk\_Oe" %}
[工程师段位记](/blog/engineer-level)
{% endcontent-ref %}

{% content-ref url="/pages/-M3tZ\_-VYmFfh4JsZbFs" %}
[Jest 内部原理剖析](/blog/how-does-jest-work-inside)
{% endcontent-ref %}

{% content-ref url="/pages/-M26JIwNcPPcB7ZPxfZs" %}
[如何成为技术高手](/blog/become-a-tech-master)
{% endcontent-ref %}

{% content-ref url="/pages/-M1x3SW5P6sqpXof8dtP" %}
[Vue lifecycle hook little trick](/blog/vue-lifecycle-hook)
{% endcontent-ref %}

{% content-ref url="/pages/-M1spBSPApiGWIqBoBAG" %}
[利用 Cloudflare Worker 定制 Gitbook](/blog/modify-gitbook)
{% endcontent-ref %}

{% content-ref url="/pages/-M1pdOdbuMVjv0z4sDbm" %}
[如何解除端口占用？](/blog/how-to-free-port)
{% endcontent-ref %}


# 博客


# 理解 CAP 理论，以开一家餐厅为例

在分布式系统的设计和运维中，CAP理论是一个基本的理论框架，帮助我们理解在设计和实现这些系统时需要面对的挑战和取舍。

## 定义

CAP理论由加州大学伯克利分校的计算机科学家Eric Brewer在2000年提出，它描述了分布式系统在面临网络分区（网络不稳定和不可靠情况）时，只能同时满足以下三个中的两个要求：

#### C：一致性（Consistency）

一致性指的是在分布式系统中，所有数据副本在同一时刻是否具有相同的值。简单来说，如果操作数据的系统保证一致性，那么每次读操作都会返回最新写入的值。

#### A：可用性（Availability）

可用性是指系统提供的服务必须始终处于可用状态，对于系统的每个请求都必须能在有限的时间内返回结果。无论系统内部发生什么问题，对外服务不能中断。

#### P：分区容忍性（Partition Tolerance）

分区容忍性指的是系统中若部分节点之间因网络故障失去联系，整个系统依然能继续运行。在现实世界中，网络问题是不可避免的，因此分区容忍性在分布式系统中是必须保证的。

## 一个更容易理解的定义

**一致性（Consistency）**&#x20;

要求无论客户在哪个分店，体验和服务都必须完全一样。要达到这种一致性，所有分店必须严格遵守相同的标准和流程。这包括食材、烹饪方法、服务标准等。

**可用性（Availability）**&#x20;

意味着餐厅必须始终开放，随时准备服务每一个顾客。这要求餐厅能够应对各种意外情况，如员工短缺、设备故障或供应链问题，而不影响开放状态。

**分区容忍性（Partition Tolerance）**&#x20;

涉及到系统在面对网络中断或其他分区问题（比如交通延迟影响食材供应）时，仍能继续运行。这需要餐厅有应急计划和备份方案，以保持操作。

## 取舍

根据CAP理论，一个分布式系统不可能同时满足这三个条件。为什么会这样呢？让我们用上面餐厅的比喻来进一步解释：

1. **一致性与可用性**：如果坚持一致性，当遇到供应问题（比如特定的食材短缺）时，为了保证菜品在所有分店的标准一致，可能需要停止提供某些菜品，这样就牺牲了可用性。相反，为了保持高可用性，分店可能需要使用替代食材，这就可能破坏了一致性。
2. **一致性与分区容忍性**：当网络或通信出现问题时，分店之间可能无法及时更新信息以保持一致。如果仍强调一致性，则可能需要暂停服务，直到网络恢复，影响了分区容忍性。
3. **可用性与分区容忍性**：通常较容易同时达成，但如果要在网络问题持续期间保持一致性，可能需要牺牲一些服务的可用性，例如，延迟订单处理以等待网络恢复正常。

因此，这三个目标的完美平衡是理论上的理想，而在现实操作中，通常需要根据具体情况权衡和优先考虑。这就是为什么CAP理论是设计分布式系统和服务时的一个重要考虑因素，它帮助决策者明白在特定环境和条件下需要做出哪些妥协。

####


# TypeScript tsconfig.json 整理

## 引入配置

文件相关的配置确保 TypeScript 能包含正确的文件

### include

设置一个包含文件名的数组或匹配规则，指明那些文件是引入到程序中的。文件名基于包含`tsconfig.json`文件的路径解析

```
// settings
{
	"include": ["src/**/*", "tests/**/*"]
}

// results
.
├── scripts                ⨯
│   ├── lint.ts            ⨯
│   ├── update_deps.ts     ⨯
│   └── utils.ts           ⨯
├── src                    ✓
│   ├── client             ✓
│   │    ├── index.ts      ✓
│   │    └── utils.ts      ✓
│   ├── server             ✓
│   │    └── index.ts      ✓
├── tests                  ✓
│   ├── app.test.ts        ✓
│   ├── utils.ts           ✓
│   └── tests.d.ts         ✓
├── package.json
├── tsconfig.json
└── yarn.lock
```

**默认值**

如果定义了`files`配置，则为 `[ ]` ；否则为 `[" ** / * "]`

### exclude

设置一个包含文件名的数组或匹配规则,指明忽略哪些`include`中的配置

`exclude` 仅对 `include` 配置中的文件生效。一个被 `exclude` 设置的文件依然可以被代码引入依赖，因此，这并不是一个避免文件被使用的设置，这仅仅是用来改变 `include` 的设置

**默认值**

`["node_modules", "bower_components", "jspm_packages"]` 和 outdir

### files

设置一个包含文件名的数组，指明包含到程序中的文件。这通常应用于你仅需少量文件时，否则使用`include`是更好的建议

```
{
  "compilerOptions": {},
  "files": [
    "core.ts",
    "sys.ts",
    "types.ts",
    "scanner.ts",
    "parser.ts",
    "utilities.ts",
    "binder.ts",
    "checker.ts",
    "tsc.ts"
  ]
}
```

**默认**

false

### extends

继承通用的设置。如果有重名的设置，则新定义的设置会覆盖被继承的配置。extends 配置对于大型项目的 `tcsonfig.json` 的复用帮助很大

```
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
```

```
{
  "extends": "./configs/base",
  "files": ["main.ts", "supplemental.ts"]
}
```

## 项目配置

项目配置决定了你希望项目是如何运行的

### allowJS

是否允许引入 JavaScript 文件。默认仅允许引入 `.ts` 和 `.tsx` 文件

**默认**

`false`

### checkJS

是否报告 JavaScript 文件中的错误。此选项依赖 `allowJS` 为 `true`

例如，根据 TypeScript 自带的 `parseFloat` 类型定义，这是不正确的 JavaScript：

```
// parseFloat 仅接受一个字符串作为参数
module.exports.pi = parseFloat(3.124);
```

当引入到一个 TypeScript 模块：

```
// @filename: constants.js
module.exports.pi = parseFloat(3.124);

// @filename: index.ts
import { pi } from "./constants";
console.log(pi);Try
```

你将不会得到任何错误。但是如果你开启了 `checkJs` 选项，那么你可以从 JavaScript 文件中得到错误信息。

**默认**

false

### declaration

是否要在编译时输出`.d.ts`文件。`.d.ts` 文件用于向外部模块描述API的类型定义文件。

当 `declaration` 设置为 `true` 时，用编译器执行下面的 TypeScript 代码：

```
export let helloWorld = "hi";Try
```

将会生成如下这样的 `index.js` 文件：

```
export let helloWorld = "hi";Try
```

以及一个相应的 `helloWorld.d.ts`：

```
export declare let helloWorld: string;
```

**默认值**

false

### lib

TypeScript 包括一组默认的内建 JS 接口（例如 Math）的类型定义，以及在浏览器环境中存在的对象的类型定义（例如 document）

TypeScript 还包括与你指定的 target 选项相匹配的较新的 JS 特性的 API。例如如果target 为 ES6 或更新的环境，那么 Map 的类型定义是可用的。

最小化的依赖引入能避免下游包被污染的可能性。我们需要谨慎的选择`lib`需要包含的库。

[高阶库](https://www.notion.so/dce02bcf3bf14a7a9d20a3b1f18dd371)

### jsx

控制 JSX 在 JavaScript 文件中的输出方式。 这只影响 .tsx 文件的 JS 文件输出。

* `react`: 将 JSX 改为等价的对 `React.createElement` 的调用并生成 `.js` 文件。
* `react-jsx`: 改为 `__jsx` 调用并生成 `.js` 文件。
* `react-jsxdev`: 改为 `__jsx` 调用并生成 `.js` 文件。
* `preserve`: 不对 JSX 进行改变并生成 `.jsx` 文件。
* `react-native`: 不对 JSX 进行改变并生成 `.js` 文件。

示例代码：

```
export const helloWorld = () => <h1>Hello world</h1>;
```

默认为： `"react"`

```
export const helloWorld = () => React.createElement("h1", null, "Hello world");Try

```

保留: `"preserve"`

```
export const helloWorld = () => <h1>Hello world</h1>;Try

```

React Native: `"react-native"`

```
export const helloWorld = () => <h1>Hello world</h1>;Try

```

React 17 转换: `"react-jsx"`[\[1\]](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)

```
import { jsx as _jsx } from "react/jsx-runtime";
export const helloWorld = () => _jsx("h1", { children: "Hello world" }, void 0);Try

```

React 17 开发模式转换: `"react-jsxdev"`[\[1\]](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)

```
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
const _jsxFileName = "/home/runner/work/TypeScript-Website/TypeScript-Website/packages/typescriptlang-org/index.tsx";
export const helloWorld = () => _jsxDEV("h1", { children: "Hello world" },

```

### module

这设置了`tsc`编译打包的模块系统。改变`module`同时会影响`moduleResolution`

示例

```
// @filename: index.ts
import { valueOfPi } from "./constants";

export const twoPi = valueOfPi * 2;
```

#### CommonJS

```
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;
```

#### UMD

```
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./constants"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.twoPi = void 0;
    const constants_1 = require("./constants");
    exports.twoPi = constants_1.valueOfPi * 2;
});
```

#### AMD

```
define(["require", "exports", "./constants"], function (require, exports, constants_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.twoPi = void 0;
    exports.twoPi = constants_1.valueOfPi * 2;
});
```

#### System

```
System.register(["./constants"], function (exports_1, context_1) {
    "use strict";
    var constants_1, twoPi;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [
            function (constants_1_1) {
                constants_1 = constants_1_1;
            }
        ],
        execute: function () {
            exports_1("twoPi", twoPi = constants_1.valueOfPi * 2);
        }
    };
});
```

#### ESNext

```
import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;Try
```

#### ES2020

```
import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;Try
```

#### None

```
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_1 = require("./constants");
exports.twoPi = constants_1.valueOfPi * 2;
```

**默认值**

如果`target` 为 ES3 或者 ES5，默认值为 `CommonJS`

如果`target` 为 ES6 或者更高，则默认值为 `ES6`/`ES2015`

### moduleResolution

指定模块解析策略：'node' （Node.js） 或 'classic' （在 TypeScript 1.6 版本之前使用）。 你可能不需要在新代码中使用 classic。

**默认值**

如果`module`为`AMD`/`UMD`/`System`/`ES6`, 则为 `Classic`

其他情况下为 `Node`

### noEmit

是否禁止编译生成文件。如果你想使用`babel`来编译`TypeScript`，可以设置这个选项为`true`

**默认值**

false

### outDir

指定编译输出的目录。若没有指定，`.js` 将被生成至于生成它们的 `.ts` 文件相同的目录中：

```
$ tsc

example
├── index.js
└── index.ts
```

使用类似这样的 `tsconfig.json`：

```
{
  "compilerOptions": {
    "outDir": "dist"
  }
}
```

使用这些配置运行 `tsc` 时，会将文件移动到指定的 `dist` 文件夹中：

```
$ tsc

example
├── dist
│   └── index.js
├── index.ts
└── tsconfig.json
```

**默认值**

n/a

### outFile

设置打包输出至单个文件中。

除非`module`为`None`, `System`或`AMD`，否则不能使用 `outFile`

**默认值**

n/a

### plugins

设置编辑器内运行的语言服务插件列表

语言服务插件是一种基于现有 TypeScript 文件向用户提供额外信息的方法。它们可以改进 TypeScript 和编辑器之间的现有信息，或提供自己的错误信息。

* [ts-sql-plugin](https://github.com/xialvjun/ts-sql-plugin#readme) — 增加了用模板字符串做 SQL 构建器时的风格检查。
* [typescript-styled-plugin](https://github.com/Microsoft/typescript-styled-plugin) — 在目标字符串中提供 CSS 风格检查。
* [typescript-eslint-language-service](https://github.com/Quramy/typescript-eslint-language-service) — 在编译器的输出中提供 eslint 的错误信息和修复信息。
* [ts-graphql-plugin](https://github.com/Quramy/ts-graphql-plugin) — 在 GraphQL 查询目标字符串中提供验证和补全。

### removeComments

设置当打包 JavaScript 时，忽略所有 TypeScript 文件中的注释。

例如，这是一个有 JSDoc 注释的 TypeScript 文件：

```
/** 'Hello world' 的葡萄牙语翻译 */
export const helloWorldPTBR = "Olá Mundo";
```

当然 `removeComments` 被设置为 `true`：

```
export const helloWorldPTBR = "Olá Mundo";Try
```

未设置 `removeComments` 或被设置为 `false`：

```
/** 'Hello world' 的葡萄牙语翻译 */
export const helloWorldPTBR = "Olá Mundo";Try
```

这意味着你的注释将呈现在 JavaScript 中。

**默认值**

false

### rootDir

设定 TypeScript 打包输出的根文件目录。

这么说有点抽象。举例来说，假设你有一些输入文件：

```
MyProj
├── tsconfig.json
├── core
│   ├── a.ts
│   ├── b.ts
│   ├── sub
│   │   ├── c.ts
├── types.d.ts
```

`rootDir` 推断的结构是所有非声明输入文件的最长公共路径，在例子中为 `core/`。

如果你的 `outDir` 是 `dist`，TypeScript 将会生成这样的文件树：

```
MyProj
├── dist
│   ├── a.ts
│   ├── b.ts
│   ├── sub
│   │   ├── c.ts
```

但你可能希望让 `core` 成为输出目录结构的一部分。 通过在 `tsconfig.json` 中指定 `rootDir: "."`，TypeScript 将会生成这样的文件树：

```
MyProj
├── dist
│   ├── core
│   │   ├── a.js
│   │   ├── b.js
│   │   ├── sub
│   │   │   ├── c.js
```

**注意**

rootDir 不会影响哪些文件被包含在编译中。如果设置了`rootDir`，但是程序又依赖了`rootDir` 之外的文件，这个文件会被输出至`outDir`外。因此，`rootDir`务必要保证依赖的文件都在`rootDir`下

### sourceMap

是否生成sourcemap

**默认值**

false

## 严格检查


# this 陷阱与原理

## 前言

this 在很多地方都有解释。听得最多的说法就是 this 是由**调用的位置**来决定的。话虽然没有错，但是什么是调用的位置，还是非常含糊不清。

另外，this 绑定中有

* 显式绑定
* 隐式绑定
* 默认绑定

又分别是什么？看定义的名称都非常的抽象。如果我们仅仅是面试前记住对应的概念，而不知道其中的原理，这种知识会非常容易遗忘。

## 陷阱

```javascript
const obj = {
	foo: function () {
		console.log(this)
	}
}

const foo = obj.foo

// 写法一
obj.foo()

// 写法二
foo()
```

答案:

写法一的 this 指向 obj 对象，但是写法二的obj指向 window 对象 / undefined

```javascript
{foo: ƒ}
Window ... // 严格模式下指向 undefined
```

很多教科书会说，`this`指的是函数运行时所在的环境。对于`obj.foo()`来说，`foo`运行在`obj`环境，所以`this`指向`obj`；对于`foo()`来说，`foo`运行在全局环境，所以`this`指向全局环境。所以，两者的运行结果不一样。

这种解释没错，但是教科书往往不告诉你，为什么会这样？也就是说，函数的运行环境到底是怎么决定的？举例来说，为什么`obj.foo()`就是在`obj`环境执行，而一旦`var foo = obj.foo`，`foo()`就变成在全局环境执行？

## 原理

定义的对象的 JS 过程

```javascript
const obj = { foo: 5 }
```

![](/files/-MNR4Rq2rXhmCwOhWhUP)

JavaScript 引擎会先在内存里面，生成一个对象{ foo: 5 }，然后把这个对象的内存地址赋值给变量obj

变量obj是一个地址（reference）。后面如果要读取obj.foo，引擎先从obj拿到内存地址，然后再从该地址读出原始的对象，返回它的foo属性。

原始的对象以字典结构保存，每一个属性名都对应一个属性描述对象。举例来说，上面例子的foo属性，实际上是以下面的形式保存的。

![](/files/-MNR4i4G4F39YiBWnP30)

{% hint style="info" %}
在 ES5 中模拟实现 const，也即通过 Object.defineProperty 来改变 writable 属性
{% endhint %}

上面的例子中，value 一个数值，但是，value也可以是一个函数。

```javascript
const obj = { foo: function () {} }
```

![](/files/-MNR50fYPyUekJcP3c43)

### 实际示例

```javascript
var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2
```

#### 单独运行

![](/files/-MNR5EzPBA606Ux2vWsN)

#### 对象运行

![](/files/-MNR5JzLWTI-Zuerm0mw)

## 总结

回到本文开头提出的问题，`obj.foo()`是通过obj找到foo，所以就是在obj环境执行。一旦`var foo = obj.foo`，变量foo就直接指向函数的地址，所以foo()就变成在全局环境执行。

## 延伸1 - this 的优先级

{% hint style="info" %}
new > call / apply / bind (显示绑定) > obj call (隐式绑定)> function call (默认绑定)
{% endhint %}

下面将阐述为什么有这样的优先级。

### call / apply

apply和call所做的事情都相同，那就是改变函数内部的this指向并调用它。

```javascript
const obj = {
  foo: function () {
    console.log(this)
  }
}

obj.foo() // obj

obj.foo.call(this) // window 

```

模拟实现 call

````javascript
Function.prototype.call = function (context, ...args) {
  // 检查调用```call```的对象是否为函数
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }

  // 将函数作为传入的```context```对象的一个属性，调用该函数
  const fn = Symbol()
  context[fn] = this
  context[fn](...args)

  // 不要忘了调用之后删除该属性
  delete context[fn]
}
````

### new

new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作：

1. 创建一个空的简单JavaScript对象（即{}
2. 链接该对象（即设置该对象的构造函数）到另一个对象
3. 将步骤1新创建的对象作为this的上下文
4. 如果该函数没有返回对象，则返回this。

```javascript
function myNew (fn, ...args) {
  // 第一步，创建一个空的简单JavaScript对象（即{}）；
  let obj = {}

  // 第二步，原型链绑定
  fn.prototype !== null && (obj.__proto__ = fn.prototype)

  // 第三步，改变this指向并运行该函数
  let ret = fn.call(obj, ...args)

  // 第四步，如果该函数没有返回对象，则返回this
  // 别忘了 typeof null 也返回 'object' 的bug
  if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
    return ret 
  }
  return obj
}
```

## 延伸2 - class this 调用陷阱

😒 问题：对象解构后丢失 this 作用域

```javascript
class Demo {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    sum() {
        const sumVal = this.x + this.y
				console.log(sumVal)
        return sumVal
    }
}

const myDemo = new Demo(2, 3)

// 用法1 
myDemo.sum() // 5

// 用法2
const sum = myDemo.sum //将sum的引用赋值给新变量sum （写法等同于 const { sum } = myDemo;）
sum() // Uncaught TypeError: Cannot read property 'x' of undefined ||  window doesnt has property 'x'
```

😁  因此有这样的改写

```javascript
class Demo {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    this.sum = this.sum.bind(this); // sum中this显式绑定
    }
    sum() {
        let sumVal = this.x + this.y;
        return sumVal;
    }
}

const myDemo = new Demo(2, 3);
const sum = myDemo.sum; 
sum() // 5
```

## 延伸3 - 回调函数的 this 陷阱

```javascript
function foo() {
  console.log( this.a )
}

function doFoo(task, callback) {
	console.log(task)
  callback()
}

const obj = {
  a: 2,
  foo: foo
}

var a = "oops, global";

// 写法1
doFoo('show a', obj.foo ) // "oops, global"

// 写法2
const { foo: myFoo } = obj
doFoo('show a again', myFoo ) // "oops, global"
```

## 箭头函数

{% hint style="info" %}
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this，并且形成绑定关系
{% endhint %}

{% hint style="info" %}
箭头函数中的this是在定义函数的时候绑定(词法作用域)，而不是在执行函数的时候绑定
{% endhint %}

```javascript
var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  },
	d: () => () => console.log(this.i, this),
  e: () => ({
    i:100,
    f: () => console.log(this.i, this)
  }),
  g: function() {
    const h = () => console.log(this.i, this)
    return h
  }
}

obj.b(); 
// undefined, Window{...}
obj.c(); 
// 10, Object {...}
obj.d()()
// undefined, Window{...}
obj.e().f()
// undefined, Window{...}
obj.g()()
// 10, Object {...}

obj.g().call(this)
// 10, Object {...}
obj.g.call(this)()
// undefined, Window{...}

```

于是 class 中的 this 调用改写

```javascript
class Demo {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  sum = () => {
    const sumVal = this.x + this.y;
    console.log(sumVal)
    return sumVal
  }
}

let myDemo = new Demo(2, 3)

// 用法1 
myDemo.sum()  // 😎 5

// 用法2
const sum = myDemo.sum //将sum的引用赋值给新变量sum （写法等同于 const { sum } = myDemo;）
sum() // 😎 5 
```

对象方法中的 this 改写

```javascript
function foo() {
  console.log( this.a )
}

function doFoo(task, callback) {
	console.log(task)
  callback()
}

const obj = {
  a: 2,
  foo: () => foo.call(obj)
}

var a = "oops, global";

// 写法1
doFoo('show a', obj.foo ) // "oops, global"

// 写法2
const { foo: myFoo } = obj
doFoo('show a again', myFoo ) // "oops, global"
```

## 再次总结

1. this 指向当前运行环境下调用的对象
2. this 的执行可以被 call/apply/bind 改写
3. 箭头函数从句法上定义 this 指向，不能被 call/apply/bind 修改


# 时间戳与时区

### 什么是时间戳

`时间戳` 指的就是Unix时间戳(Unix timestamp)。它也被称为Unix时间(Unix time)、POSIX时间(POSIX time)，是一种时间表示方式，定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数

`时间戳` 在地球的每一个角落都是相同的, 可以尝试修改不同时区查看下面代码结果

```javascript
console.log(Date.now());
```

结果都是一样的

### 什么是时区

`时区` 是指相同的时间在不同的地区有不同的含义, 我们所处的时区为 `东八区`, 可以在设备的时间设置中手动调整时区

同一时间戳在不同时区所表示的日期是不同的, 比如下面的代码

```javascript
const time = new Date(1556074515196);
console.log(time.getFullYear());
console.log(time.getMonth());
console.log(time.getDate());
```

在北京时间执行的结果为 `2019 3 24`, 而在美国时间执行的结果却是 `2019 3 23`

### 产生的问题

有这样一个场景, 接口返回了某演出的开演时间string和相应的时间戳, string是按服务端按东八区计算的结果, 然后端上会用时间戳展示一个日历组件, 结果导致用户选择了15号的日期, 实际演出却是16号的, 造成客诉

### 如何解决问题

定义了如下方法用于转换时区, 使其得到的结果与服务端一致

```javascript
/**
* 把指定时区的时间换算到当地时区
* @param {number} timestamp 指定的时间戳
* @param {number} timeZone 指定时间戳的时区，
* @returns {number} 系统时区的时间
*/
function transformToLocalTime(timestamp, timeZone = -8) {
    const date = new Date();
    const zone = date.getTimezoneOffset() / 60;
    const zoneGap = zone - timeZone;
    timestamp += (zoneGap * 60 * 60 * 1000);
    return timestamp;
}
```


# vue-cli 项目添加 tailwind.css

最近在尝试工具类思想设计的 CSS 库 tailwind.css，简单记录一下怎么在 vue-cli 工程中引入 tailwind.css。

## 1. 安装 tailwind

```javascript
# Using npm
npm install tailwindcss

# Using Yarn
yarn add tailwindcss
```

## 2. 创建 tailwind 配置文件（可选）

如果你希望定制化tailwind.css， 可以试着引入 tailwind 的配置文件

```
npx tailwindcss init 
```

这会在你的工程根目录创建一个最小的`tailwind.config.js`

```javascript
// tailwind.config.js
module.exports = {
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}
```

## 3. 配置 PostCSS

你需要添加`PostCSS` 选项来让`tailwind.css` 跑起来

```
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  }
}
```

请在你的工程根目录引入如上所示配置，并命名为`postcss.config.js`

## 4. 把 tailwind 引入工程

现在可以创建一个 CSS 文件在 `src/assets/css/styles.css` 并添加

```javascript
@tailwind base;
@tailwind components;
@tailwind utilities;
```

接着在 main.js 中导入到 vue 工程中

```javascript
import '@/assets/css/style.css'
```

一切顺利的话，你的Vue功能就能使用 tailwind.css 了。以上。


# 实现 JavaScript 数组的逆序索引

在 Python 和 Matlab 里面，我们可以通过 arr\[-1] 的方式访问数组的最后一位，如何赋予 JavaScript 这种能力呢？

需求： 实现一种新的数组特性

```javascript
// 实现一种新的数组特性，完成数组的逆序访问
const arr = [1,2,3]
arr[-1] = 3
arr[-2] = 2
arr[-3] = 1
```

思路

首先我们不谈这道题是什么解决的，单纯了解一下 数组如何存储的。

数组( Array )在内存中用一串连续的区域来存放一些值。注意「连续」一词，它至关重要。

![](/files/-MF9Q7EVChtrK1Nratf2)

上图表示存储在内存中的一个数组。存储4个4 bit 的元素,一共需要16 bit 存储区域，每个元素顺序保持一致。

假设，我声明了tinyInt arr\[4]，它占用从1201位置开始的一段存储区域。当某个时刻我尝试读取a\[2]，那么只要做简单的计算，找到a\[2]的位置就可以了。例如1201+(2X4)然后直接从1209位置读取数据。

However，JavaScript 的内存数据结构并不是连续的，而是链表结构。如果在 JavaScript 中声明var arr = new Array(4)；它会产生类似下图的结构。

![](/files/-MF9QC4xg5ncUWqyoMCY)

因此，如果你想在程序中某一处读取a\[2]，它必须从1201位置开始溯寻a\[2]的位置。

这就是 JavaScript 数组和真正的数组不同的地方

HowHowever,  以上只是简单科普一下 JavaScript Array 的数据结构。如果你准备构思如何改变 JavaScript 的链表结构来实现这道题。我只能祝你好运了。

真实的做法是利用 Proxy 或者 Object.defineProperty 来劫持 数组访问。直接上解决方案

```javascript
// proxy 写法
const NewArrProxy = {
  get (target, key, receiver) {
    let idx = key < 0 ? target.length + Number(key): key
    return Reflect.get(target, idx, receiver)
  }
}

const arr = new Proxy([1,2,3], NewArrProxy)
console.log(arr[-1])   // 3
console.log(arr[-2])   // 2
console.log(arr[-3])   // 1
```

```javascript
// object.definePropery 写法
const arr = [1,2,3]

function newArrObserver(arr){
  for (let idx = 1; idx <= arr.length; idx++) {
    Object.defineProperty(arr, `-${idx}`, {
      get() {
          return arr[arr.length - idx]
      }
    })
  }
}
newArrObserver(arr)

console.log(arr[-1])   // 3
console.log(arr[-2])   // 2
console.log(arr[-3])   // 1
```

惊不惊喜意不意外&#x20;

以上。


# 工程师段位记

工程师已经逐渐脱离了只需要埋头干好技术活就OK的职业要求了，在职业竞争发展的当下，对工程师这个岗位更深的定位和思考，能帮助自己更精进的成长。

## Good 与 Great

在工作中，如何定义好什么是做好，什么是做得出色呢？

### Good

做完 + 代码质量 + 迭代成本 + 独立思考 + 主动协调 + 独当一面

### Great

Good + 深入专业领域 + 有能力指导其他人 + 前瞻性 + 解决不止自身领域的问题 + 潜移默化影响他人积极向上

高段位要求我们不仅仅把事情做好，更要把事情做得出色。

## 段位、瓶颈与突破

### 初级工程师

* **要求**：初级工程师的能力 >= 独立承担，独立执行
* **核心关注点**：打牢技术基础，坐拥技术广度，挖掘技术深度
* **瓶颈**：被动执行，做完就好
* **提升方面自省**：
  1. **人效**：上班效率行不行？什么是无效加班？时间被浪费在哪里？如何解决？
  2. **业务接口人**：如何跟进需求？如何排期？如何分工？
  3. **质量**：编码质量能更好吗？提测为什么出问题？线上稳定性如何？
  4. **业务理解**：用户是谁？目标是啥？要做什么？业务数据如何解读？
  5. **技术方案**：基于现有业务，如何设计解决方案？技术文档怎么写？可维护性可扩展性高吗？
  6. **沟通&反馈**：80%的问题都是沟通问题，关键是，你知道问题出在哪里吗？
* **思维突破**：

  不管是项目中小修小补的螺丝角色，还是工程里重复劳动的机器人角色，要学会想办法突破资源的限制，想办法把小修小补变得自动化，智能化，从中开拓自己解决问题的角度，从而真切上手实践更多的技术项，提高自己的技术水平；

  自己理解的“业务”还处于一个不成熟的阶段，不要因为解决了一些产品需求就沾沾自喜，时刻戒骄戒躁。

  由点到线，解决问题，把手头的事情做到极致。摒弃浮躁，看长远。

### 高级工程师

* **要求**：高级工程师的能力 >= 初级工程师 + 能思考并实践如何做的更好
* **核心关注点**：积累项目经验，注重架构协同
* **瓶颈**：做自己的事，潜意识设界。
* **提升方面自省**：
  1. **技术能力**：我可以把技术做的更精进吗？我有更多去了解其他领域的技术演变吗？
  2. **对外开放**：我有把我所学所悟跟别人分享吗？我可以做哪些分享给别人？
  3. **主动性**：我遇到问题都是自己解决好的吗？为什么我不可以在问题之前预判问题？
  4. **业务理解**：未来发展会如何？产品这个方案还可以更好吗？
* **思维突破**：

  要想更上一层楼，首先要认识到，在资深以前，主要做的是“汲取”，那么在资深以后，就要学会“反哺”。

  在资深以前，是“叫你去做”，那么在资深以后，是“你要去做”。

  由线到面，提升专项能力。聚焦技术，提升核心竞争力。提前准备，抓住机会。

### 资深工程师

* **要求**：资深工程师的能力 >=高级工程师 + 通过影响和带动他人，帮助业务和团队拿到更多结果
* **核心关注点**：方案体系化建设，Leadership培养
* **瓶颈**：一个人解决问题，解决眼前问题。
* **提升方面自省**：
  1. **开发规范**：我是否很好解决了编码规范、安全规范等层面的问题和缺陷？
  2. **研发流程**：协作模式可以更好吗？我们为什么要强依赖？如何做到解耦分层？
  3. **基础资产**：好像调试工具还不太好用，我能创造一个更好的吗？物料系统要不要搞一个？Web工具就现有的真的够吗？
  4. **性能体验**：加载效率和渲染效率还能不能再提升？人机交互体验可以更好吗？用户感知和引导还能做什么？
  5. **统计监控**：流程控制是否完善？应用报错是否监测到位？异常数据采集分析还能做些什么？
  6. **安全防控**：代码是否合规？包安全性已经达标了吗？
  7. **质量保障**：UI测试可以自动化吗？单元测试覆盖率是否达标？
* **思维突破**：

  关注面更广：提效、用户体验、稳定性、数据能力、前端安全……

  业务支撑力：业务理解、方案评估、时间管理、风险控制、代码质量、复盘沉淀……

  技术创新力：规范标准、方案选项、物料库、平台支撑、工程套件……

  组织成长力：技术分享、分层培训、组织建设、梯队培养、文化建设、管理工具……

  内外影响力：对内赋能、横向影响、对外输出……

## 总结

从初高级工程师到资深工程师，除了要脚踏实地努力之外，最重要的还是要多去了解下在更高层级的人，具备的能力、对自己的要求以及思考方式的角度都是怎么样的。

做事能力和思维方式的螺旋式提升是个人成长的基准线。


# Jest 内部原理剖析

![](/files/-M3tZmK1eYBsncHGvzP8)

Jest 是 Facebook 开源的一个 JavaScript 测试框架，已经应用在  [Babel](https://babeljs.io/) / [TypeScript](https://www.typescriptlang.org/) / [Node](https://nodejs.org/en/) /  [React](https://reactjs.org/) /  [Angular](https://angular.io/) /  [Vue](https://vuejs.org/) 等项目中。

在愉悦地编写 Jest 测试代码后，你是否会好奇 `test` / `expect` / `mock` / `spyOn` 这些内置方法是如何实现的呢？下面我们一起来看一下。

## Test

Jest 中的 `test` 函数标准实例是：

```javascript
test('Passing test', () => {})
// ✓ Passing test
```

```javascript
test('Failing test', () => new Error('Error message'))
// ✕ Failing test
// Error message
```

这里可以注意到，如果第二个参数的函数执行没有抛出异常的话，会打印第一个参数，因此我们的模拟实现可以是：

```javascript
function test(title, callback) {
  try {
    callback()
    console.log(`✓ ${title}`)
  } catch (error) {
    console.error(`✕ ${title}`)
    console.error(error)
  }
}
```

## Expect

`expect` 函数的典型使用实例是：

```javascript
// 需要测试的函数
function multiply(a, b) {
   return a * b
}

// 测试用例
test('Multipling 3 by 4 is 12', () => {
   expect(multiply(3, 4)).toBe(12)
} // ✓ Multipling 3 by 4 is 12

test('Multipling 3 by 4 is 12', () => {
   expect(multiply(3, 4)).toBe(13)
}) // ✕ Multipling 3 by 4 is 12
   // Expected: 13
   // Received: 12
```

注意到`expect` 的返回值具备一个可以调用的方法`toBe` ，那么我们可以这样模拟`expect`&#x20;

```javascript
function expect(current) {
  return {
    toBe(expected) {
      if (current !== expected) {
        throw new Error(`
          Expected: ${expected}
          Received: ${current}
        `)
      }
    }
  }
}
```

## Mock

当我们要避免测试一个功能时又依赖于不稳定的另外一个功能，我们创建一个 Mock 来更好的管理。Jest 使用 `jest.fn` 是实现 Mock

```javascript
// random.js
function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min) + min)
}
export { getRandom }

// cards.js
import { getRandom } from './random.js'
const getRandomCard = (cards) => {
   const randomCardIndex = getRandom(0, array.length)
   return cards[randomCardIndex]
}
export { getRandomCard }

// cards.test.js
import * as randomGenerator from './random.js'
import { getRandomCard } from './cards.js'
test('Returns 7♥', () => {
    const originalImplementation = randomGenerator.getRandom
    randomGenerator.getRandom = jest.fn(() => 2)
    const result = getRandomCard(['2♣', 'K♦️', '7♥', '3♠'])
    expect(result).toBe('7♥')
    expect(randomGenerator.getRandom).toHaveBeenCalledTimes(1)
    expect(randomGenerator.getRandom).toHaveBeenCalledWith(0, 4)
    // we keep the test idempotent
    randomGenerator.getRandom = originalImplementation
})
```

为了实现上面的效果，我们可以这样模拟定义`jest.fn` 和 `expect`&#x20;

```javascript
// jest.fn
function fn(impl) {
  const mockFn = (...args) => {
    mockFn.mock.calls.push(args)
    return impl(...args)
  }
  mockFn.mock = {calls: []}
  return mockFn
}

// expect
import assert from 'assert'
function expect(current) {
  return {
    toHaveBeenCalledTimes(nrTimesExpected) {
      if (current.mock.calls.length !== nrTimesExpected) {
        throw new Error(`
          Expected: ${expected}
          Called: ${func.mock.calls.length}
        `)
      }
    },
    toHaveBeenCalledWith(...params) {
        // this is a simplified version         
        if (!assert.deepStrictEqual(current.mock.calls[0], ...params)) {
            throw new Error(`
               Expected: ${expected}
               Called: ${func.mock.calls.length}
            `)
        }
    }
  }
}
```


# 如何成为技术高手

转载

{% hint style="info" %}
这是一篇转载文，文中注重体系化的知识构建和我建立知食记的初衷是一致的。
{% endhint %}

## **第一阶段 认真构建完整的知识体系**

十几年前我投身软件行业的时候，光是讲解数据库原理、操作系统、TCP/IP、组网、算法等等基础知识的英文原版书摞起来就等身，认认真真看完，各种上手实践，入行后，读遍 C++ 各种经典著作，读遍各种协议原文，认认真真打基础。\
很多工程师都说自己平常就是在某些 IT 门户上看看推荐的博文或新闻，我说这属于典型的零敲碎打，不够刺激。\
聊到这时，我会举一个例子，**为什么要阅读长篇小说，因为中短篇小说就像用针扎你，而长篇小说就像把你装进一个沙袋里吊起来，从四面八方用狼牙棒打你，酣畅淋漓**。构建可用的知识体系，就得读书，书是有体系结构的，你关心不关心，现阶段你用到用不到，它都讲到了，从头到尾看几遍，针扎得透透的。\
\
何谓知识体系？\
几年前，前支付宝架构师姚建东曾经在我们公司做过技术人员如何规划自己的分享讲座，他是这么论述的：\
技术与技巧包括：

* 计算机基础理论
  * 计算机模型：内存/IO/时钟/CPU……
  * 算法
  * 专项技术领域：
    * 数据挖掘
    * 数据管理
    * 智能推荐
    * 搜索
    * ……
* 语言与工具
  * 语言与相关体系
  * 开发工具，分析工具，代码管理工具
  * HTML/CSS/JS/Ajax
  * 常用框架与第三方类库
* 调试与测试
  * 调试方法和哲学
  * 定位问题
  * BUG管理工具
  * 单元测试
  * 集成测试
  * 性能测试
  * 安全测试
  * 兼容性测试与方法
  * JS/Ajax测试与方法
  * 服务层测试
  * Web层测试
* 网络与系统
  * TCP/IP协议与模型，HTTP/SMTP等协议
  * Linux系统，网络分析工具，系统分析工具
  * 容量，流量与负载均衡
  * 应用部署、规范、规划
  * 安全
  * 监控与故障分析
  * 磁盘与存储
  * Shell
  * DNS与域名
  * 缓存，反向代理
  * 图片服务器（海量小文件）
* 需求挖掘与分析
  * 需求文档格式
  * 需求访谈
  * 需求分析方法，需求分析工具
  * 领域知识与经验
* 系统分析与设计
  * UML语言与模型
  * 分析模式
  * 设计模式，领域驱动
  * 系统分析文档格式
  * 系统设计文档格式
  * 功能性需求与非功能性需求
* 数据与系统
  * 数据库
  * 可伸缩策略，扩展策略，备份，容灾，性能，安全，高可用……
  * 数据设计与范式，SQL/NoSQL，Cache，分布式文件
* 架构设计
  * 架构模式，典型互联网公司架构演进历史
  * 架构原则，常用策略
  * 架构设计方法
  * 非功能性理解
    * 扩展性
    * 伸缩性
    * 稳定性
    * 一致性
    * 性能
    * 吞吐量
  * 容量预测与规划
  * 架构体系与相关技术
* 过程与管理
  * 分析过程
  * 研发过程
  * 评审过程
  * 测试过程
  * 发布过程
  * 回滚过程
  * 文档管理
  * 知识管理
  * 项目管理

以上其实就是一份从业基础知识清单，你可以按图索骥，阅读相关书籍。

## **第二阶段 顺着一个Topic钻进去，锻炼自己的预研能力**

无论公司业务还是自己喜欢做的事，都可以抽象出通用性课题，然后以做论文的方式杀进去。**这个事情得反复操练，有意识操练**。\
做事方式为：

1. 抽象出 Topic——如分布式锁，分布式并行计算引擎，防CSRF的FormToken自动生成框架，定时任务管理与调度平台，分布式跟踪，等等
2. 向功课好的学生学习——有针对性地深入了解业界其他公司是如何分析问题和解决问题的，汇总各种方案，站在巨人的肩膀上
3. 分析特定应用场景，技术选型
4. 兼顾高可用性和可伸缩，做设计评审
5. 做测试自证靠谱，梳理知识点，开技术分享会
6. 上线商用，总结经验教训，开经验分享会

其中一个重点是汇总和分享。05年时，应电信级统一消息业务需要，我去研究了 SIP 协议，做了各种试验，分析报文，写了一系列的幻灯片，做了公开分享，一时间还颇受欢迎：

1. SIP\_to\_Freshman\_by\_zhengyun.ppt
2. SIP之穿越NAT\_by\_zhengyun.ppt
3. SIP体系架构讲义及消息交互演示\_by\_zhengyun.ppt
4. SIP多方会话消息之实例讲解\_by\_zhengyun.ppt
5. SIP安全框架之认证\[NTLM和Kerberos]\_by\_zhengyun.ppt
6. SIP消息之逐项讲解\_by\_zhengyun.ppt

为什么要写出来、讲出来呢？\
因为有一个学习金字塔理论，如下图所示：\
![](https://pic1.zhimg.com/80/90a98144c7ba399f19f5aece2838327c_1440w.png)\
我们读过的事情能够记住学习内容的10%，\
我们听过的事情能够记住20%，\
我们看过的事情能够记住30%，\
我们听过和看过的事情能够记住50%——如看影像/看展览/看演示/现场观摩，\
我们说过的事情能够记住70%——如参与讨论/发言，\
**我们说过和做过的事情能够记住90%——如做报告，给别人讲，亲身体验，动手做。**\
这也就是我在《[窝窝研发过去几年做对了哪些事](https://link.zhihu.com/?target=http%3A//www.cnblogs.com/zhengyun_ustc/p/rightThings.html)》中阐述的管理方法：我们从入职之后就有意识地训练大家，让大家能够公开陈述、清晰表达。所以，试用期内，新人必须做一次技术分享和一次技术评审，面对各方的 challenge；预研的中间和结尾都要有分享会；平时也要定期组织技术讲座。

## **第三阶段 疯狂回答技术问题**

知识体系慢慢构建，与业务相关的抽象 Topic 也在探索中。\
但这还不够。\
因为你亲身接触到的世界太小，可能不足以构成挑战，你可能意识不到自己缺多少知识和技能，不利于你分析问题、提出问题和解决问题的能力培养。\
所以，要主动出击：\
**疯狂回答问题**。\
\
我曾经在入行的头几年里几乎把我关注的垂直领域（包括语言领域和业务领域）里的所有问题都回答了一遍。我对外宣扬知无不言言无不尽，放出邮件地址和 MSN（那时候 MSN 很高大上），很多网友都会发邮件或者加我好友，问各种开发疑难问题，平均每天都有几个，然后我把解决问题的过程写成微软 KB（KnowledgeBase） 文体发表在我的博客上。\
你想想看，工作中的问题你平均每隔几天才能遇到一个，而这么做，每天你都会遇到几个乃至于十几个，第一让你脑力激荡，第二接触到更多新知。\
\
05年到06年期间，我因工作需要学习了 JavaME（或古老的称呼 J2ME），早年间 Symbian 手机上的客户端开发。那段时间我天天扫中文论坛的帖子，力求回答所有问题，尤其是那些 BUG 或故障。对于那些暂时没有人解决的，如流媒体实时播放，如仿 OperaMini 二级菜单界面，都上下求索，最后放出思路以及源码。\
同时，我经常整理常见问题，梳理成册并发布。譬如我整理过的 J2ME 疑难问题：

1. \[J2ME Q\&A]真机报告MontyThread -n的错误之解释
2. \[J2MEQ\&A]WTK初始化WMAClient报错XXX has no IP address的解释
3. \[J2ME Q\&A]untrusted domain is not configured问题回应
4. \[J2ME]“Cannot open socket for LIME events”错误解决

几个月后，我成为 J2ME 中文论坛超级版主。通过这个历程，我想告诉大家，回答网友问题，技巧得当的话，比如别老是重复回答新手问题，试着攻克那些疑难问题，或者离奇故障，绝对不会浪费你的时间。\
为什么？\
因为你要信奉：<br>

> **你学过的每一样东西，你遭受的每一次苦难，都会在你一生中的某个时候派上用场**。\
> ——佩内洛普·菲兹杰拉德 《离岸》\
> \
> Everything that you've learnt and all the hardships you've suffered will all come in handy at some point in your life.

## **第四阶段 RCA/总结**

现在是你把经验教训变为财富的时刻了。\
什么是好的技术 Leader？\
随便一个业务需求或业务场景讲出来，你立刻把它抽象为几个模块/系统/Topic，然后侃侃而谈，业界都是怎么解决的，我们以前又是怎么分析怎么解决的，现在咱们这种情况下应该如何设计，可能会遇到什么问题，我们应该做哪些预防设计，blabla。\
\
怎么做到这一点？

### 第一，写 RCA 报告。

我以前说过，『窝窝从 2011 年开始，一直坚持每错必查、错了又错就整改、每错必写，用身体力行告诉每一个新员工直面错误、公开技术细节、分享给所有人，长此以往，每一次事故和线上漏测都会变为我们的财富。这就是我们的 RCA（Root Cause Analysis）制度，截止到目前已经收集整理了近两百个详尽的 RCA 报告。』\
RCA 报告格式为：

1. 背景知识（Optional）
2. 问题现象
3. 影响范围
4. 问题原因
5. 问题分析过程（Optional）
6. 解决办法
7. 后续处理措施：如线上脏数据如何修复，如对用户造成的影响如何弥补等（Optional）
8. 经验教训
9. RCA类型：如代码问题、实施问题、配置问题、设计问题、测试问题

这样，作为一名合格的老兵，你见过了足够多的血，并且把它们变成了你的人生财富。

### 第二，写总结。

话说，要经常拉清单。\
侃侃而谈得有资料，这些都得是你自己写才能印象深刻，关键时刻想得起来。\
\
好了，这就是我告诉面试者的高手炼成四个阶段。


# Vue lifecycle hook little trick

我们都知道 React Hook 刮起了函数式编程的旋风，与之对应的，Vue 也在 3.0 借鉴了这种思想。不过Vue 3.0 hook 不在本文的讨论范畴，本文将重点总结一下Vue 2.0的生命周期 Hook。

## 组件上的 hook 小 trick

比如有父组件 Parent 和子组件 Child，如果父组件监听到子组件挂载 mounted 就做一些逻辑处理，常规的写法可能如下：

{% code title="Parent.vue" %}

```markup
<Child @mounted="doSomething"/>
```

{% endcode %}

{% code title="Child.vue" %}

```javascript
mounted() {
  this.$emit("mounted");
}
```

{% endcode %}

此外，还有一种特别简单的方式，子组件不需要任何处理，只需要在父组件引用的时候通过@hook 来监听即可，代码如下：

```markup
<Child @hook:mounted="doSomething" />
<Child @hook:updated="doSomething" />
```

## 方法中的 hook 小trick

可以通过一个程序化的侦听器来注册生命周期的绑定

```javascript
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })

  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}
```


# 利用 Cloudflare Worker 定制 Gitbook

![](/files/-M1spV0ukNd9u2lfi6On)

支撑本站背后的是 Gitbook v2 版本，曾经开源产品的闭源商业化分支。Gitbook 暂时不提供定制 CSS 的特性。因此我们对于左栏一定会出现的 " Powered by Gitbook "无法做出隐藏的修改。

难道只能等官方提供这个功能后才能实现吗？

非也，这里我来分享一下利用 Cloudflare Worker 来完成这个神奇的功能。

## Cloudflare Worker

Cloudflare Workers的名称来自Web Workers，更具体地说是Service Workers，一个用于在web浏览器后台运行并拦截HTTP请求的脚本的W3C标准API。Cloudflare Workers是针对相同的标准API编写的，但是是在Cloudflare的服务器上运行，而不是在浏览器中运行。

以下是可以使用的工具：

* 使用最新的标准语言功能执行任意JavaScript代码。
* 拦截和修改HTTP请求和响应URL，状态，标头和正文内容。
* 直接从您的Worker响应请求，或将其转发到其他地方。
* 将HTTP请求发送到第三方服务器。
* 以串行或并行方式发送多个请求，并使用响应组成对原始请求的最终响应。
* 在响应已经返回到客户端之后发送异步请求（例如，用于记录或分析）。
* 控制其他Cloudflare功能，例如缓存行为。

我们可以在 [Worker Template](https://developers.cloudflare.com/workers/templates/) 看到各种各样的参考示例。

## mind-ricky-css-inject

我的目标很简单，就是把” Powered by Gitbook " 的 HTML 块隐藏即可。这里所涉及到的步骤如下：

1. 导入域名至 Cloudflare
2. 配置域名的DNS。根域名指向 `hosting.gitbook.com`
3. 创建新的 worker
4. 配置 worker routes。把域名规则和对应worker关联起来
5. 生效

其中最核心的是，创建什么样的 worker 可以完成我们的需求。这个 worker 需要在返回的 HTML 中添加一段 CSS。

```javascript
addEventListener('fetch', event => {
	event.passThroughOnException()
	event.respondWith(handleRequest(event.request))
})

/**
 * Fetch and log a given request object
 * @param {Request} request
 */
async function handleRequest(request) {
	const response = await fetch(request)
	var html = await response.text()

	// Inject scripts
	const customScripts = `<style type="text/css">
    a[role='presentation'] {
      display: none;
    }
  </style></body>`
	html = html.replace( /<\/body>/ , customScripts)

	// return modified response
	return new Response(html, {
		headers: response.headers
	}) 
}
```

如此一来，我们再通过 cloudflare 代理 gitbook 的链接访问，并配置 worker 加载，那么会在 html 中的 body 前注入

```markup
<style type="text/css">
  a[role='presentation'] {
    display: none;
  }
</style>
```

从而实现设置隐藏 gitbook power by 的目的。


# 如何解除端口占用？

![](/files/-M1sonYauhEkf2ciAR-A)

这是我个人在开发过程中经常碰到的问题，在通过IDE调试的时候，用`webpack`起一个server后挂掉了，也不知道哪里可以找到对应端口的占用进程，于是乎每次匆匆忙忙的检索一番。

这里算是总结一下，非常简单：

#### 1.查看端口占用程序

```
lsof -i:[端口号]
```

如：

```
lsof -i:8080
```

#### 2.结束占用进程

```
kill -9 [pid]
```

如

```
kill -9 9635
```

哇，世界清净了。


# CSS基础


# 盒模型

盒模型是刚学前端的时候就会接触到的问题。

元素都是按照盒模型的规则布局在页面中的。盒模型由 `margin + border + padding + content` 四个属性组成，分为两种：W3C的标准盒模型和IE盒模型。

## **W3C的标准盒模型**

`width = content`，不包含 `border + padding`

![](/files/-LviQbZ-gW0O8Gl6-0C2)

## **IE盒模型**

`width = border + padding + content`

![](/files/-LviQfLjpJ0E3A0d_N5a)

## **相互转换**

二者之间可以通过CSS3的 `box-sizing` 属性来转换。

`box-sizing: content-box` 是W3C盒模型

`box-sizing: border-box` 是IE盒模型

## IFC(inline formatting context)布局规则

`Inline level`的box会参与形成IFC，比如`display`值为`inline，inline-table，inline-block`的元素。

在行内格式化上下文中，框(boxes)一个接一个地水平排列，起点是包含块的顶部。水平方向上的 `margin`，`border` 和 `padding`在框之间得到保留。框在垂直方向上可以以不同的方式对齐：它们的顶部或底部对齐，或根据其中文字的基线对齐。包含那些框的长方形区域，会形成一行，叫做行框。

## BFC(block formatting context)布局规则

`Block level`的box会参与形成BFC，比如`display`值为`block，list-item，table`的元素。

1. 内部的Box会在垂直方向，一个接一个地放置。
2. Box垂直方向的距离由`margin`决定。属于同一个BFC的两个相邻Box的`margin`会发生重叠
3. 每个元素的左外边缘（`margin-left`)， 与包含块的左边（`contain box left`）相接触(对于从左往右的格式化，否则相反)。即使存在浮动也是如此。除非这个元素自己形成了一个新的BFC。
4. BFC的区域不会与`float box`重叠。
5. BFC就是页面上的一个隔离的独立容器，容器里面的子元素不会影响到外面的元素。反之也如此。
6. 计算BFC的高度时，浮动元素也参与计算

## 如何创建BFC

1. 根元素或其它包含它的元素
2. 浮动 (元素的 `float` 不是 `none`)
3. 绝对定位的元素 (元素具有 `position` 为 `absolute` 或 `fixed`)
4. 非块级元素具有 `display: inline-block，table-cell, table-caption, flex, inline-flex`
5. 块级元素具有`overflow` ，且值不是 `visible`

## 关于inline-block

inline-block的元素的内部是一个BFC，但是它本身可以和其它inline元素一起形成IFC

## 关于浮动与BFC

![](/files/-LztVCY33fxpSbxzNnIF)

> <https://segmentfault.com/a/1190000009545742>
>
> <https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context>

<br>


# 清除浮动

### 1.使用空标签&#x20;

给所有浮动标签后面添加一个空标签，定义CSS clear:both. 但是这种方法会增加一个无意义的标签。

### 2.设置父级元素为BFC元素&#x20;

### 3.使用after伪元素清除浮动&#x20;

这个方法只适用于非IE浏览器。该方法必须为需要清除浮动元素的伪对象中设置height:0,不然该元素会比实际元素高出若干像素。

```css
#parent:after{
    content:".";
    height:0;
    visibility:hidden;
    display:block;
    clear:both;
}
```


# html5的语义化标签

\<header>顶部标签\</header>\
\<nav>导航栏标签\</nav>\
\<section>中心标签\</section>\
\<article>文章标签\</article>\
\<footer>底部标签\</footer>


# 水平居中

1\) 若是行内元素, 给其父元素设置 text-align:center,即可实现行内元素水平居中.

2\) 若是块级元素, 该元素设置 margin:0 auto即可.

3\) 若子元素包含 float:left 属性, 为了让子元素水平居中, 则可让父元素宽度设置为fit-content,并且配合margin, 作如下设置:

```css
.parent{
    width: -moz-fit-content;
    width: -webkit-fit-content;
    width:fit-content;
    margin:0 auto;
}
```

fit-content是CSS3中给width属性新加的一个属性值,它配合margin可以轻松实现水平居中, 目前只支持Chrome 和 Firefox浏览器.

4\) 使用flex 2012年版本布局, 可以轻松的实现水平居中, 父元素设置如下:

```css
.parent{
    display: flex;
    justify-content: center;
}
```

5\) 使用CSS3中新增的transform属性, 子元素设置如下:

```css
.son{
    position:absolute;
    left:50%;
    transform:translate(-50%,0);
}
```

6\) 使用绝对定位方式, 以及负值的margin-left, 子元素设置如下:

```css
.son{
    position:absolute;
    width:固定;
    left:50%;
    margin-left:-0.5宽度;
}
```

7\) 使用绝对定位方式, 以及left:0;right:0;margin:0 auto; 子元素设置如下:

```css
.son{
    position:absolute;
    width:固定;
    left:0;
    right:0;
    margin:0 auto;
}
```


# 垂直局中

### **单行文本**

1\) 若元素是单行文本, 则可设置 line-height 等于父元素高度

### 通用（不管行级，块级，图片)

2\) 若元素是行内块级元素, 基本思想是使用tabel-cell + vertical-align让内容块处于容器中央.

```css
.parent{
    display: table-cell;
    vertical-align: middle;
}
```

这是一种很流行的方法, 也适应IE7.

{% hint style="info" %}
vertital-align:middle 仅对行内元素有效。因此需要显式重新设置display
{% endhint %}

### 通用（不管行级，块级，图片)

3\) 可用 **Flex 2012版**, 这是CSS布局未来的趋势. Flexbox是CSS3新增属性, 设计初衷是为了解决像垂直居中这样的常见布局问题. 相关的文章如《[弹性盒模型Flex指南](https://link.juejin.im?target=http%3A%2F%2Flouiszhai.github.io%2F2017%2F01%2F13%2Fflex%2F)》

父元素做如下设置即可保证子元素垂直居中:

```css
.parent {
  display: flex;
  align-items: center;
}
```

**优点**

* 内容块的宽高任意, 优雅的溢出.
* 可用于更复杂高级的布局技术中.

**缺点**

* IE8/IE9不支持
* 需要浏览器厂商前缀
* 渲染上可能会有一些问题

### transform 相对方案

4\) 可用 **transform** , 设置父元素相对定位(position:relative), 子元素如下css样式:

```css
.son{
    position:absolute;
    top:50%;
    -webkit-transform: translate(-50%,-50%);  
    -ms-transform: translate(-50%,-50%);
    transform: translate(-50%,-50%);
}
```

**优点**

代码量少

**缺点**

IE8不支持, 属性需要追加浏览器厂商前缀, 可能干扰其他 transform 效果, 某些情形下会出现文本或元素边界渲染模糊的现象.

### **元素高度固定**

5\) 设置父元素相对定位(position:relative), 子元素如下css样式:

```css
.son{
    position:absolute;
    top:50%;
    height:固定;
    margin-top:-0.5高度;
}
```

**优点**

适用于所有浏览器.

**缺点**

父元素空间不够时, 子元素可能不可见(当浏览器窗口缩小时,滚动条不出现时).如果子元素设置了overflow:auto, 则高度不够时, 会出现滚动条.

6\) 设置父元素相对定位(position:relative), 子元素如下css样式:

```css
.son{
    position:absolute;
    height:固定;
    top:0;
    bottom:0;
    margin:auto 0;
}
```

**优点**

简单

**缺点**

没有足够空间时, 子元素会被截断, 但不会有滚动条.<br>


# 水平垂直居中

### vertical-align + text-align 方案

```css
#outer {
    width:400px; // 方便看效果
    height:500px; // 方便看效果
    display:table-cell;
    vertical-align:middle;
    text-align:center;
}
```

### flex 方案

```css
#outer{
    display:flex;
    justify-content:center;
    aligns-item:center;
}
```

### 绝对定位

```css
#outer {
    position: relative;
}
#inner {
    top:50%;
    left:50%;
    transform: translate(-50%, -50%);
}
```


# Sticky Footer

### 全局增加一个负值下边距等于底部高度

```markup
<body>
  <div class="wrapper">

      content

    <div class="push"></div>
  </div>
  <footer class="footer"></footer>
</body>
```

```css
html, body {
  height: 100%;
  margin: 0;
}
.wrapper {
  min-height: 100%;

  /* Equal to height of footer */
  /* But also accounting for potential margin-bottom of last child */
  margin-bottom: -50px;
}
.footer,
.push {
  height: 50px;
}
```

## flex

```markup
<body>
  <div class="content">
    content
  </div>
  <footer class="footer"></footer>
</body>
```

```css
html {
  height: 100%;
}
body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}
.content {
  flex: 1;
}
```


# 三栏布局

三栏布局是很常见的一种页面布局方式。左右固定，中间自适应。实现方式有很多种方法。

**第一种：flex**

```markup
<div class="container">
    <div class="left">left</div>
    <div class="main">main</div>
    <div class="right">right</div>
</div>
.container{
    display: flex;
}
.left{
    flex-basis:200px;
    background: green;
}
.main{
    flex: 1;
    background: red;
}
.right{
    flex-basis:200px;
    background: green;
}
```

{% hint style="info" %}
注意设置左右宽度的时候用的是flex-basis而不是width，这两者是有差别的。

详细参考：<https://www.zhangxinxu.com/wordpress/2020/01/css-initial-unset/>
{% endhint %}

**第二种：position + margin**

```markup
<div class="container">
    <div class="left">left</div>
    <div class="right">right</div>
    <div class="main">main</div>
</div>
body,html{
    padding: 0;
    margin: 0;
}
.left,.right{
    position: absolute;
    top: 0;
    background: red;
}
.left{
    left: 0;
    width: 200px;
}
.right{
    right: 0;
    width: 200px;
}
.main{
    margin: 0 200px ;
    background: green;
}
```

**第三种：float + margin**

```markup
<div class="container">
    <div class="left">left</div>
    <div class="right">right</div>
    <div class="main">main</div>
</div>
body,html{
    padding:0;
    margin: 0;
}
.left{
    float:left;
    width:200px;
    background:red;
}
.main{
    margin:0 200px;
    background: green;
}
.right{
    float:right;
    width:200px;
    background:red;
}
```

**第四种：grid**

```css
<section class="layout grid">
  <style media="screen">
    .layout.grid .left-center-right{
      display:grid;
      width:100%;
      grid-template-rows:100px;
      grid-template-columns:300px auto 300px;
    }
    .layout.grid .left{
      background:red;
    }
    .layout.grid .center{
      background:yellow;
    }
    .layout.grid .right{
      background:blue;
    }
  </style>
  <article class="left-center-right">
    <div class="left"></div>
    <div class="center">
      <h2>网格布局解决方案</h2>
      1.这是三蓝布局中间部分
      2.这是三蓝布局中间部分
    </div>
    <div class="right"></div>
  </article>
</section>
```

## 对比

* 绝对定位：快捷，布局已经脱离了文档流，导致下面的元素也要脱离文档流，使用性较差
* flex布局，解决了绝对定位的缺点，移动端基本已经支持了兼容性
* 网格布局：新出的 需要考虑兼容性


# 文本截断省略号

## 单行文本截断

**核心 CSS 语句**

* overflow: hidden；（文字长度超出限定宽度，则隐藏超出的内容）
* white-space: nowrap；（设置文字在一行显示，不能换行）
* text-overflow: ellipsis；（规定当文本溢出时，显示省略符号来代表被修剪的文本）

**优点**

* 无兼容问题
* 响应式截断
* 文本溢出范围才显示省略号，否则不显示省略号
* 省略号位置显示刚好

**短板**

* 只支持单行文本截断

**适用场景**

* 适用于单行文本溢出显示省略号的情况

## 多行文本截断

**核心 CSS 语句**

* -webkit-line-clamp: 2；（用来限制在一个块元素显示的文本的行数, 2 表示最多显示 2 行。 为了实现该效果，它需要组合其他的WebKit属性）
* display: -webkit-box；（和 1 结合使用，将对象作为弹性伸缩盒子模型显示 ）
* -webkit-box-orient: vertical；（和 1 结合使用 ，设置或检索伸缩盒对象的子元素的排列方式 ）
* overflow: hidden；（文本溢出限定的宽度就隐藏内容）
* text-overflow: ellipsis；（多行文本的情况下，用省略号“…”隐藏溢出范围的文本)

**优点**

* 响应式截断
* 文本溢出范围才显示省略号，否则不显示省略号
* 省略号显示位置刚好

**短板**

* 兼容性一般： -webkit-line-clamp 属性只有 WebKit 内核的浏览器才支持。不过移动端支持比较好

## LESS方案

```css
.wrapline(@line: 1) when(@line <=1) { 
  overflow: hidden; 
  text-overflow: ellipsis; 
  white-space: nowrap; 
} 
 
.wrapline(@line: 2) when(@line > 1) { 
  overflow: hidden; 
  // text-overflow: ellipsis; //不是必须的 
  display: -webkit-box; 
  -webkit-line-clamp: @line; 
  -webkit-box-orient: vertical; 
} 
 
.wrap-line { 
  width: 110px; 
  .wrapline(2); 
}
```

## scss方案

```css
/**
* 溢出省略号
* @param {Number} 行数
*/
@mixin ellipsis($rowCount: 1) {
  @if $rowCount <=1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  } @else {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: $rowCount;
    -webkit-box-orient: vertical;
  }
}
```


# 伪类与伪元素

## 伪类

![](/files/-LviSIQMm1nBcyiLXvHs)

## 伪元素

![](/files/-LviTO9q_n5u8GOGjjxG)

## 区别

1. 伪元素在一个选择器里只能出现一次，并且只能出现在末尾;
2. 伪类则是像真正的类一样发挥着类的作用，没有数量上的限制，只要不是相互排斥的伪类，也可以同时使用在相同的元素上。


# 定位

## static

静态定位，也即默认定位

```markup
<h1>Basic document flow</h1>

<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p>

<p>By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height.</p>

<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p>

<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements will <span>wrap onto a new line if possible (like this one containing text)</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p>
```

![](/files/-LviWRb0mh2j545DS9yj)

`static`定位所导致的元素位置，是浏览器自主决定的，所以这时`top`、`bottom`、`left`、`right`这四个属性无效。

## relative

与静态定位非常相似，但是可以通过`top` `left` `right` `bottom` 更改最终位置

```
top: 30px;
left: 30px;
```

![](/files/-LviWm-GaC6Og2XTPKD7)

## absolute

绝对定位，按照父元素中最近的relative元素进行定位。

绝对定位使元素的位置与文档流无关，因此不占据空间。这一点与相对定位不同，相对定位实际上被看作普通流定位模型的一部分，因为元素的位置相对于它在普通流中的位置。

## fixed

`fixed`表示，相对于视口（viewport，浏览器窗口）进行偏移，即定位基点是浏览器窗口。这会导致元素的位置不随页面滚动而变化，好像固定在网页上一样。

## sticky

`sticky`跟前面四个属性值都不一样，它会产生动态效果，很像`relative`和`fixed`的结合：一些时候是`relative`定位（定位基点是自身默认位置），另一些时候自动变成`fixed`定位（定位基点是视口）。

`sticky`生效的前提是，必须搭配`top`、`bottom`、`left`、`right`这四个属性一起使用，不能省略，否则等同于`relative`定位，不产生"动态固定"的效果。原因是这四个属性用来定义"偏移距离"，浏览器把它当作`sticky`的生效门槛。

它的具体规则是，当页面滚动，父元素开始脱离视口时（即部分不可见），只要与`sticky`元素的距离达到生效门槛，`relative`定位自动切换为`fixed`定位；等到父元素完全脱离视口时（即完全不可见），`fixed`定位自动切换回`relative`定位。

## float

浮动定位，和绝对定位一样，会脱离文档流。

浮动的框可以向左或向右移动，直到它的外边缘碰到包含框或另一个浮动框的边框为止。由于浮动框不在文档的普通流中，所以文档的普通流中的块框表现得就像浮动框不存在一样。


# BEM

BEM = Block + Element + Modify

{% hint style="info" %}
在选择器中，由以下三种符号来表示扩展的关系：

```
-   中划线 ：仅作为连字符使用，表示某个块或者某个子元素的多单词之间的连接记号。
__  双下划线：双下划线用来连接块和块的子元素
_   单下划线：单下划线用来描述一个块或者块的子元素的一种状态

type-block__element_modifier
```

{% endhint %}

## 块（block）

一个块是设计或布局的一部分，它有具体且唯一地意义 ，要么是语义上的要么是视觉上的。

在大多数情况下，任何独立的页面元素（或复杂或简单）都可以被视作一个块。它的HTML容器会有一个唯一的CSS类名，也就是这个块的名字。

针对块的CSS类名会加一些前缀（ `ui-`），这些前缀在CSS中有类似 [命名空间](https://github.com/Tencent/tmt-workflow/wiki/%E2%92%9B-%5B%E8%A7%84%E8%8C%83%5D--CSS-BEM-%E4%B9%A6%E5%86%99%E8%A7%84%E8%8C%83) 的作用。

一个块的正式（实际上是半正式的）定义有下面三个基本原则：

1. CSS中只能使用类名（不能是ID）。
2. 每一个块名应该有一个命名空间（前缀）
3. 每一条CSS规则必须属于一个块。

例如：一个自定义列表 `.list` 是一个块，通常自定义列表是算在 `mod` 类别的，在这种情况下，一个 `list` 列表的block写法应该为:

```css
.list   
```

## 元素（element）

块中的子元素是块的子元素，并且子元素的子元素在 `bem` 里也被认为是块的直接子元素。**一个块中元素的类名必须用父级块的名称作为前缀。**

如上面的例子，`li.item` 是列表的一个子元素

```css
.list{}
.list .item{}


.list{}
.list__item{}
```

## 修饰符（modifier）

一个“修饰符”可以理解为一个块的特定状态，标识着它持有一个特定的属性。

用一个例子来解释最好不过了。一个表示按钮的块默认有三个大小：小，中，大。为了避免创建三个不同的块，最好是在块上加修饰符。这个修饰符应该有个名字（比如：`size` ）和值（ `small`，`normal` 或者 `big` ）。

如上面的例子中，表示一个选中的列表，和一个激活的列表项

```css
.list{}
.list.select{}
.list .item{}
.list .item.active{}

  
.list{}
.list_select{}
.list__item{}
.list__item_active{}
```


# 主题切换

## Props参数下发

提供一个`theme.js`数据文件，包含了`DEFAULT`和`FIRST` 等配置

```javascript
const DEFAULT_THEME = {
  primary: '#2F54EB',
  subPrimary: '#D6E4FF',
  error: '#F5222D',
  success: '#52C41A',
  warning: '#FAAD14',
  background: '#FFFFFF',
  text: '#222222'
}
export default {
    DEFAULT: DEFAULT_THEME,
    FIRST: {
        ...DEFAULT_THEME,
        background: '#2590ff'
    }
}
```

在组件中使用

```javascript
<script>
import theme from 'theme.js'
export default {
    name: "themeChange",
    data() {
        return {
            theme: theme['DEFAULT']
        }
    },
    methods: {
        changeTheme() {
            this.theme = theme['FIRST']
        }
    }
}
</script>
```

## Ajax替换

通过`Ajax`获取`css`，然后替换其中的颜色变量，再通过`style`标签将样式插入`DOM`

> <https://github.com/ALetterSong/Note/issues/15>

## 动态Less

使用可以在浏览器上直接运行的`less`，通过传入变量动态编译。

> <https://pro.ant.design/docs/theme-cn#%E5%9C%A8%E7%BA%BF%E5%88%87%E6%8D%A2%E4%B8%BB%E9%A2%98>

##


# 权重

**!important > 内联样式 > ID选择器 > 类选择器 > 标签选择器 > 通配符选择器**

| 选择器     | 表达式或示例                                                    | 说明                                                                        | 权重  |
| ------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | --- |
| ID选择器   | #aaa                                                      |                                                                           | 100 |
| 类选择器    | .aaa                                                      |                                                                           | 10  |
| 标签选择器   | h1                                                        | 元素的tagName                                                                | 1   |
| 属性选择器   | \[title]                                                  | [详见这里](http://www.cnblogs.com/rubylouvre/archive/2009/10/27/1590102.html) | 10  |
| 相邻选择器   | selecter + selecter                                       | 拆分为两个选择器再计算                                                               |     |
| 兄长选择器   | selecter \~ selecter                                      | 拆分为两个选择器再计算                                                               |     |
| 亲子选择器   | selecter > selecter                                       | 拆分为两个选择器再计算                                                               |     |
| 后代选择器   | selecter selecter                                         | 拆分为两个选择器再计算                                                               |     |
| 通配符选择器  | \*                                                        |                                                                           | 0   |
| 各种伪类选择器 | 如:link， :visited， :hover， :active， :target， :root， :not等  | 10                                                                        |     |
| 各种伪元素   | 如::first-letter,::first-line,::after,::before,::selection | 1                                                                         |     |

#### 计算示例

* important: 无穷大
* 内联样式: 1000
* ID: 100
* 类、伪类、属性: 10
* 元素、伪元素: 1
* 通配符: 0

权重计算时，每遇到一个选择器就加上相应的基数，总和大的认为权重更高。如下:

```
style=""                    => 1000(一个行内样式)
#title{}                    => 100(一个ID选择器)
.root                       => 10(一个类)
[title]                     => 10(一个属性)
div                         => 1(一个元素)
*{}                         => 0(通配符)
div span {}                 => 1+1=2(两个元素)
div h1+span {}              => 1+1+1=2(三个元素)
div:first-child             => 1+10=11(一个元素，一个伪类)
div [title]                 => 1+10=11(一个元素，一个属性选择器)
body #title .root p {}      => 112(1+100+10+1，两个元素，一个Id选择器，一个类)
```

#### 其他总结：

1. !important 优先级最高，但也会被权重高的important所覆盖
2. 行内样式总会覆盖外部样式表的任何样式(除了!important)
3. 单独使用一个选择器的时候，不能跨等级使css规则生效
4. 如果两个权重不同的选择器作用在同一元素上，权重值高的css规则生效
5. 如果两个相同权重的选择器作用在同一元素上：以后面出现的选择器为最后规则.
6. 权重相同时，与元素距离近的选择器生效


# px, em, rem

## PX

px像素（Pixel）。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。

## EM

em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置，则相对于浏览器的默认字体尺寸。

### **EM特点**

* em的值并不是固定的；
* em会继承父级元素的字体大小

{% hint style="info" %}
**注意：**&#x4EFB;意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算，需要在css中的body选择器中声明Font-size=62.5%，这就使em值变为 16px\*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10，然后换上em作为单位就行了。

所以我们在写CSS的时候，需要注意两点：

* 1\. body选择器中声明Font-size=62.5%；
* 2\. 将你的原来的px数值除以10，然后换上em作为单位；
* 3\. 重新计算那些被放大的字体的em数值。避免字体大小的重复声明。

也就是避免1.2 \* 1.2= 1.44的现象。比如说你在#content中声明了字体大小为1.2em，那么在声明p的字体大小时就只能是1em，而不是1.2em, 因为此em非彼em，它因继承#content的字体高而变为了1em=12px。
{% endhint %}

## REM

rem是CSS3新增的一个相对单位（root em，根em），这个单位引起了广泛关注。这个单位与em有什么区别呢？区别在于**使用rem为元素设定字体大小时，仍然是相对大小，但相对的只是HTML根元素**。这个单位可谓集相对大小和绝对大小的优点于一身，通过它既可以做到只修改根元素就成比例地调整所有字体大小，又可以避免字体大小逐层复合的连锁反应。


# flex 布局

任何一个容器都可以指定为 Flex 布局

```css
.box{
    display: flex
}    
```

行内元素也可以使用 Flex 布局

```css
.box{
  display: inline-flex;
}
```

设为 Flex 布局以后，子元素的`float`、`clear`和`vertical-align`属性将失效。

Flex 布局是轴线布局，只能指定"项目"针对轴线的位置，可以看作是**一维布局。**&#x47;rid 布局则是将容器划分成"行"和"列"，产生单元格，然后指定"项目所在"的单元格，可以看作是**二维布局**。

## 定位

容器默认存在两根轴：水平的主轴（main axis）和垂直的交叉轴（cross axis）

![](/files/-M-OGfKP99zHCid0k4VL)

![](/files/-M-ORsTj-nlFG9S4jXWL)


# CSS3


# calc

calc函数是css3新增的功能，可以使用calc()计算border、margin、pading、font-size和width等属性设置动态值

```css
#div1 {
    position: absolute;
    left: 50px;
    width: calc( 100% / (100px * 2) );
    //兼容写法
    width: -moz-calc( 100% / (100px * 2) );
    width: -webkit-calc( 100% / (100px * 2) );
    border: 1px solid black;
}
```

* 需要注意的是，运算符前后都需要保留一个空格，例如：width: calc(100% - 10px);
* calc()函数支持 "+", "-", "\*", "/" 运算;
* 对于不支持 calc() 的浏览器，整个属性值表达式将被忽略。不过我们可以对那些不支持 calc()的浏览器，使用一个固定值作为回退。


# SCSS


# lighten

```css
lighten($color, $amount) //=> color 
```

Makes `$color` lighter.

The `$amount` must be a number between `0%` and `100%` (inclusive). Increases the HSL lightness of `$color` by that amount.

#### ⚠️ Heads up!

The `lighten()` function increases lightness by a fixed amount, which is often not the desired effect. To make a color a certain percentage lighter than it was before, use [`scale()`](https://sass-lang.com/documentation/modules/color#scale) instead.

Because `lighten()` is usually not the best way to make a color lighter, it’s not included directly in the new module system. However, if you have to preserve the existing behavior, `lighten($color, $amount)` can be written [`adjust($color, $lightness: $amount)`](https://sass-lang.com/documentation/modules/color#adjust).

* [SCSS](https://sass-lang.com/documentation/modules/color#example-16-scss)
* [Sass](https://sass-lang.com/documentation/modules/color#example-16-sass)

#### SCSS SYNTAX

```css
// #e1d7d2 has lightness 85%, so when lighten() adds 30% it just returns white.
@debug lighten(#e1d7d2, 30%); // white

// scale() instead makes it 30% lighter than it was originally.
@debug color.scale(#e1d7d2, $lightness: 30%); // #eae3e0
```


# JS 概念


# 类型

对象是JavaScript的基础。在JavaScript中一共有六种主要类型（术语是“语言类型”）：

* string
* number
* boolean
* null
* undefined
* object

注意， 简单基本 类型（ string 、 boolean 、 number 、 null 和 undefined ）本身并不是对象。 null 有时会被当作一种对象类型， 但是这其实只是语言本身的一个 bug， 即对 null 执行 typeof null 时会返回字符串 "object" 。 1 实际上， null 本身是基本类型。

有一种常见的错误说法是“JavaScript 中万物皆是对象”，这显然是错误的。

**更新**

最新的 ECMAScript 标准定义了 8 种数据类型:

* 7 种原始类型:
  * Boolean
  * Null
  * Undefined
  * Number
  * BigInt
  * String
  * Symbol&#x20;
* 和 Object

函数就是对象的一个子类型（从技术角度来说就是“可调用的对象”）。 JavaScript 中的函 数是“一等公民”，因为它们本质上和普通的对象一样（只是可以调用），所以可以像操作 其他对象一样操作函数（比如当作另一个函数的参数）。

数组也是对象的一种类型，具备一些额外的行为。数组中内容的组织方式比一般的对象要 稍微复杂一些。

### 存储方式

基础原始类型存在栈(stack)中，对象类型存在堆(Heap)中

![](/files/-M-UFeujOzNEADPZAePK)

### 数组

在JavaScript中，类型相同的数组是连续内存，类型不同的数组是非连续内存。

> <https://juejin.im/post/5d2832a5f265da1bc07e669e>


# 类型转换

JS中类型转换只有三种情况，分别是：

* 转换为布尔值
* 转换为数字
* 转换为字符串

### 转换为Boolean

在条件判断时，除了 `undefined`， `null`， `false`， `NaN`， `''`， `0`， `-0`，其他所有值都转为 `true`，包括所有对象。

### 转为数字|字符串

见表

{% hint style="info" %}
注意图中有一个错误，Boolean 转字符串这行结果我指的是 true 转字符串的例子，不是说 Boolean、函数、Symblo 转字符串都是 `true`
{% endhint %}

![](/files/-LrnnOlu7FT5ChZdCNeD)

#### 对象转原始类型

1. 如果Symbol.toPrimitive()方法，优先调用再返回
2. 调用valueOf()，如果转换为原始类型，则返回
3. 调用toString()，如果转换为原始类型，则返回
4. 如果都没有返回原始类型，会报错

```javascript
var obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
  [Symbol.toPrimitive]() {
    return 6
  }
}
console.log(obj + 1); // 7
```


# 内存

## shadow size

对象自身占用的内存大小，不包括它引用的对象。\
针对非数组类型的对象，它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。\
针对数组类型的对象，它的大小是数组元素对象的大小总和。

## retained size

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义：A->B->C, C就是间接引用)&#x20;

换句话说，Retained Size就是当前对象被GC后，从Heap上总共能释放掉的内存。 不过，释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被当做Garbage。

## 看图理解

![](/files/-LrrLL-JNrgA-RY96Zhn)

上图中，GC Roots直接引用了A和B两个对象。\
\
A对象的Retained Size=A对象的Shallow Size\
B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size\
\
**这里不包括D对象，因为D对象被GC Roots直接引用。**\
如果GC Roots不引用D对象呢？<br>

![](/files/-LrrLVQ1DLRP5NG_WdRM)

此时, B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size + D对象的Shallow Size


# 原型链

有用的参考：

{% embed url="<https://github.com/mqyqingfeng/Blog/issues/2>" %}

## 上面参考中递进的图片实例

![构造函数和实例原型](/files/-LxepxBLn-Ca0oJklN7-)

![每一个JavaScript对象(除了 null )都具有的\_\_proto\_\_属性 | person.\_\_proto\_\_ === Person.prototype](/files/-Lxeq5VdBaLydbIMXqLs)

![每个原型都有一个 constructor 属性指向关联的构造函数 | Person === Person.prototype.constructor](/files/-LxeqlX4Zi4BKNhOfjqs)

> 一个构造函数可以生成多个实例, 因此没有原型指向实例的箭头

![原型的原型 | Person.prototype.\_\_proto\_\_ === Object.prototype](/files/-LxerTcxyiZwv_smmk0D)

![蓝色线就是原型链 | Object的原型的原型为空 | Object.prototype.\_\_proto\_\_ === null](/files/-LxernlOJFDypWTanCl_)

## 真的是继承吗？

继承意味着复制操作，然而 JavaScript 默认并不会复制对象的属性，相反，JavaScript 只是在两个对象之间创建一个关联，这样，一个对象就可以通过委托访问另一个对象的属性和函数，所以与其叫继承，委托的说法反而更准确些。

## 更全面的原型链图

![](/files/-Lq5S6IiUsxJ7NrFSRDg)

要想理解这两张图，最好的做法是推演一下这几个表达式：

```javascript
Function.prototype instanceof Object // true
Function.prototype.__proto__ === Object.prototype // true

Object instanceof Object;//true 
Function instanceof Function; //true 

Number instanceof Number;//false 

Function instanceof Object;//true 
 
Foo instanceof Object; //true
Foo instanceof Function;//true 
Foo instanceof Foo;//false
```

自己总结

```javascript
function A(){}
const a = new A()

a.__proto__ === A.prototype
A.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
```

## instanceof

**`instanceof`** **运算符**用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上。

```javascript
// 模拟实现 右边变量的原型存在于左边变量的原型链上
function instanceOf(left, right) {
  let leftValue = left.__proto__
  let rightValue = right.prototype
  while (true) {
    if (leftValue === null) {
      returnfalse
    }
    if (leftValue === right) {
      returntrue
    }
    leftValue = leftValue.__proto__
  }
}
```

## 只有函数有原型链

```javascript
let a = {}
let b = function () { }
console.log(a.prototype) // undefined
console.log(b.prototype) // { constructor: function(){...} }
```

## 不要再使用 \_\_proto\_\_&#x20;

`__proto__`属性没有写入 ES6 的正文，而是写入了附录。

原因是它本质上是一个内部属性，而**不是一个正式的对外的 API，只是由于浏览器广泛支持，才被加入了 ES6**。

标准明确规定，只有浏览器必须部署这个属性，**其他运行环境不一定需要部署，而且新的代码最好认为这个属性是不存在的**。

### 推荐使用 Object.getPrototypeOf  Object.setPrototypeOf Object.create

所以无论从语义的角度，还是从兼容性的角度，都不要使用这个属性，应该使用：`Object.getPrototypeOf(target)`（读操作）、`Object.setPrototypeOf(target)`（写操作）、`Object.create(target)`（生成操作）代替


# 继承


# 类式继承+原型继承

## 类式继承

```javascript
function SuperClass() {
  this.superValue = true;
}
SuperClass.prototype.getSuperValue = function() {
  return this.superValue;
}

function SubClass() {
  this.subValue = false;
}
SubClass.prototype = new SuperClass();

SubClass.prototype.getSubValue = function() {
  return this.subValue;
}

var instance = new SubClass();

console.log(instance instanceof SuperClass)//true
console.log(instance instanceof SubClass)//true
console.log(SubClass instanceof SuperClass)//false
console.log(SubClass.prototype instanceof SuperClass)//true
```

虽然实现起来清晰简洁，但是这种继承方式有两个缺点：

* 由于子类通过其原型prototype对父类实例化，继承了父类，所以说父类中如果共有属性是引用类型，就会在子类中被所有的实例所共享，因此一个子类的实例更改子类原型从父类构造函数中继承的共有属性就会直接影响到其他的子类
* 由于子类实现的继承是靠其原型prototype对父类进行实例化实现的，因此在创建父类的时候，是无法向父类传递参数的。因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化

## 原型继承

```javascript
function inheritObject(o) {
    //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例，该对象的原型继承了父对象
  return new F();
}
```

原型式继承大和类式继承非常的相似，它只是对类式继承的一个封装，其中的过渡对象就相当于类式继承的子类，只不过在原型继承中作为一个普通的过渡对象存在，目的是为了创建要返回的新的实例对象。


# 构造函数继承

```javascript
function SuperClass(id) {
  this.books = ['js','css'];
  this.id = id;
}
SuperClass.prototype.showBooks = function() {
  console.log(this.books);
}
function SubClass(id) {
  //继承父类
  SuperClass.call(this,id);
}
//创建第一个子类实例
var instance1 = new SubClass(10);
//创建第二个子类实例
var instance2 = new SubClass(11);

instance1.books.push('html');
console.log(instance1)
console.log(instance2)
instance1.showBooks();//TypeError
```

`SuperClass.call(this,id)`当然就是构造函数继承的核心语句了.由于父类中给this绑定属性，因此子类自然也就继承父类的共有属性。由于这种类型的继承没有涉及到原型`prototype`，所以父类的原型方法自然不会被子类继承。


# 组合式继承

```javascript
function SuperClass(name) {
  this.name = name; 
  this.books = ['Js','CSS'];
}
SuperClass.prototype.getBooks = function() {
    console.log(this.books);
}
function SubClass(name,time) {
  SuperClass.call(this,name); // 拥有 name和 books 就相当于super
  this.time = time;
}
SubClass.prototype = new SuperClass();

SubClass.prototype.getTime = function() {
  console.log(this.time);
}
```

如上，我们就解决了之前说到的一些问题。

组合继承最大的问题是，无论什么情况下，都会调用两次超类型构造函数：一次是在创建子类型原型的时候，另一次是在子类型构造函数内部。

{% hint style="info" %}
SuperClass.call 很重要

在 ES6 class 中，子类必须在 `constructor` 方法中调用 `super` 方法，否则新建实例时会报错。这是因为**子类没有自己的 `this` 对象，而是继承父类的 `this` 对象，然后对其进行加工。如果不调用 `super` 方法，子类就得不到 `this` 对象。**

也正是因为这个原因，在子类的构造函数中，只有调用 `super` 之后，才可以使用 `this` 关键字，否则会报错。
{% endhint %}


# 寄生继承

```javascript
function inheritObject(o) {
    //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例，该对象的原型继承了父对象
  return new F();
}

function createBook(obj) {
    //通过原型方式创建新的对象
  var o = new inheritObject(obj);
  // 拓展新对象
  o.getName = function(name) {
    console.log(name)
  }
  // 返回拓展后的新对象
  return o;
}
```

其实寄生式继承就是对原型继承的拓展，一个二次封装的过程，这样新创建的对象不仅仅有父类的属性和方法，还新增了别的属性和方法。


# 寄生组合

寄生组合式继承是寄生式继承和构造函数继承的组合。但是这里寄生式继承有些特殊，这里他处理不是对象，而是类的原型。

```javascript
// inheriteObject 其实就是Object.create的实现
function inheritObject(o) {
  //声明一个过渡对象
  function F() { }
  //过渡对象的原型继承父对象
  F.prototype = o;
  //返回过渡对象的实例，该对象的原型继承了父对象
  return new F();
}

function inheritPrototype(subClass,superClass) {
    // 复制一份父类的原型副本到变量中
  var p = inheritObject(superClass.prototype);
  // 修正因为重写子类的原型导致子类的constructor属性被修改
  p.constructor = subClass;
  // 设置子类原型
  subClass.prototype = p;
}
```

![寄生组合继承图示](/files/-LxzEz29IuA_oJHqjjLg)

**我们需要继承的仅仅是父类的原型，不用去调用父类的构造函数**。换句话说，在构造函数继承中，我们已经调用了父类的构造函数。因此我们需要的就是父类的原型对象的一个副本，而这个副本我们可以通过原型继承拿到，但是这么直接赋值给子类会有问题，因为对父类原型对象复制得到的复制对象p中的`constructor`属性指向的不是`subClass`子类对象，因此在寄生式继承中要对复制对象p做一次增强，修复起`constructor`属性指向性不正确的问题，最后将得到的复制对象p赋值给子类原型，这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。

综上，寄生组合只调用了一次超类构造函数，效率更高。避免在`SuberType.prototype`上面创建不必要的、多余的属性，与其同时，原型链还能保持不变。

有一点你需要注意，就是子类在想添加原型方法必须通过prototype来添加，否则直接赋予对象就会覆盖从父类原型继承的对象了.

```javascript
function SuperClass(name) {
  this.name = name;
  this.books=['js book','css book'];
}
SuperClass.prototype.getName = function() {
  console.log(this.name);
}
function SubClass(name,time) {
  SuperClass.call(this,name);
  this.time = time;
}
inheritPrototype(SubClass,SuperClass);
SubClass.prototype.getTime = function() {
  console.log(this.time);
}
var instance1 = new SubClass('React','2017/11/11')
var instance2 = new SubClass('Js','2018/22/33');

instance1.books.push('test book');

console.log(instance1.books,instance2.books);
instance1.getName();
instance2.getTime();
```

{% tabs %}
{% tab title="First Tab" %}

{% endtab %}

{% tab title="输出" %}
instance1.books: \["js book", "css book", "test book"]

instance2.books: \["js book", "css book"]

instance1.getName(): 2017/11/11

instance2.getName(): 2018/22/33
{% endtab %}
{% endtabs %}

> 修正constructor是为了保证它能按照constructor被设计的意义一样正确的指向构造函数，如果constructor正确指向构造函数，就意味着能获取到构造函数的prototype，拿到prototype，哈哈那你就可以为所欲为了，但是又因为constructor的指向不是强制的，所以如果你想要用constructor做什么事要小心，因为它一不定都是严格指向该对象的构造函数，所以这边你修改不修改没有什么强制性
>
> constructor属性**不影响**任何JavaScript的内部属性。instanceof检测对象的原型链，通常你是无法修改的（不过某些引擎通过私有的\_\_proto\_\_属性暴露出来）。
>
> constructor其实没有什么用处，只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的，所以未必真的指向对象的构造函数，只是一个提示。不过，从编程习惯上，我们应该尽量让对象的constructor指向其构造函数，以维持这个惯例。\
> \
> 作者：贺师俊\
> 链接：<https://www.zhihu.com/question/19951896/answer/13457869\\>
> 来源：知乎\
> 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。。


# 词法作用域

JavaScript 采用的是词法作用域，函数的作用域在函数定义的时候就决定了。

```javascript
var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 输出 1
```

分析：

**执行 foo 函数，先从 foo 函数内部查找是否有局部变量 value，如果没有，就根据书写的位置，查找上面一层的代码，也就是 value 等于 1，所以结果会打印 1。**

如果是动态作用域，才会输出2


# 事件委托

## 基本概念

事件委托，通俗地来讲，就是把一个元素响应事件（click、keydown......）的函数委托到另一个元素。

一般来讲，会把一个或者一组元素的事件委托到它的父层或者更外层元素上，真正绑定事件的是外层元素，当事件响应到需要绑定的元素上时，会通过事件冒泡机制从而触发它的外层元素的绑定事件上，然后在外层元素上去执行函数。

## 事件冒泡与捕获

DOM 中事件委托的实现是利用事件冒泡的机制。

{% hint style="info" %}
addEventListener有三个参数：

> element.addEventListener(event, function, useCapture)

第一个参数是需要绑定的事件\
第二个参数是触发事件后要执行的函数\
第三个参数默认值是false，表示在**事件冒泡阶段**调用事件处理函数，如果参数为true，则表示在**事件捕获阶段**调用处理函数。
{% endhint %}

![](/files/-LwaUgpDWEJ3D9wap3nU)

## 事件委托的优点

### 内存小

每一个不必都要绑定函数，统一绑定到父元素

### 动态绑定

新增的子元素也可一并监听到

## 原生实现事件委托

```javascript
function delegate(agent,type,selctor,fn) {
  //agent.addEventListener(type,fn)如果是这样fn中的this会指向agent
  agent.addEventListener(type,function(e){
      let target = e.target         //target指向实际点击的最里层的元素
      let ctarget = e.currentTarget //ctarget会永远指向agent
      let bubble = true

      while(bubble && target != ctarget){
          if(target.matches(selctor)){
            //改变this的指向.
            bubble = fn.call(target,e) === false ? false : true
          }
          target = target.parentNode //模拟事件冒泡
          if(!bubble){
            //  如果fn有return false 那么阻止默认行为
            e.preventDefault()
          }
      }
  },false)
}
```

应用

```javascript
var list = document.getElementById('list')
delegate(list,'click','li',function(e){
  console.log(`${e.target.innerText}   currentTarget : ${e.currentTarget.id}`)
})
delegate(list,'click','#l2',function(e){
  console.log(`${e.target.id}   currentTarget : ${e.currentTarget.id} `)
})
```

关于`target` 和`currentTarget` , **currentTarget始终是监听事件者，而target是事件的真正发出者**。


# Falsy

**falsy** 值 (虚值) 是在 [Boolean](https://developer.mozilla.org/en-US/docs/Glossary/Boolean) 上下文中认定为 false 的值。

[JavaScript](https://developer.mozilla.org/en-US/docs/Glossary/JavaScript) 在需要用到布尔类型值的上下文中使用强制类型转换([Type Conversion](https://developer.mozilla.org/en-US/docs/Glossary/Type_Conversion) )将值转换为布尔值，例如[条件语句](https://developer.mozilla.org/zh-CN/docs/learn/JavaScript/Building_blocks/conditionals)和循环语句。

在 JavaScript 中只有 7 **个** **falsy** 值。

这意味着当 JavaScript 期望一个布尔值，并被给与下面值中的一个时，它总是会被当做 false。

| `false`                                                                  | [false](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Future_reserved_keywords_in_older_standards) 关键字                                   |   |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - |
| 0                                                                        | 数值 [zero](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type)                                                                               |   |
| 0n                                                                       | 当 [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) 作为布尔值使用时, 遵从其作为数值的规则. `0n` 是 *falsy* 值.                                  |   |
| "", '', \`\`                                                             | 这是一个空字符串 (字符串的长度为零). JavaScript 中的字符串可用双引号 **`""`**, 单引号 `''`, 或 [模板字面量](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) **` `` `** 定义。 |   |
| [null](https://developer.mozilla.org/en-US/docs/Glossary/null)           | [null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null) - 缺少值                                                                          |   |
| [undefined](https://developer.mozilla.org/en-US/docs/Glossary/undefined) | [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined) - 原始值                                                                |   |
| [NaN](https://developer.mozilla.org/en-US/docs/Glossary/NaN)             | [NaN ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)- 非数值                                                                            |   |


# This

`this`是在运行中绑定的，并不是在编写时绑定。它的上下文取决于函数调用时的各种条件。`this`的绑定和函数声明的位置没有任何关系，只取决于函数的调用方式。

当一个函数被调用时，会创建一个活动记录（执行上下文）。这个记录会包含函数在哪里被调用（调用栈）、函数的调用方式、传入的参数等信息。`this`就是这样一个属性，**它指向什么完全取决于在哪里被调用。**

在javascript中，函数的调用一共有4种方&#x5F0F;**：**

1. Function Invocation Pattern
2. Method Invocation Pattern
3. Constructor Pattern
4. Apply Pattern

下面来分别解释一下四种调用模式。

## Function Invocation Pattern

诸如`foo()`的调用形式被称为Function Invocation Pattern，是函数最直接的使用形式，注意这里的foo是作为单独的变量出现，而不是属性。

```javascript
function foo() {
	console.log(this.a);
}

var a = 2;
foo(); // 2
```

{% hint style="info" %}
参数传递/间接引用也会有Function Invocation Pattern的场景出现
{% endhint %}

```javascript
function foo(){
	console.log(this.a);
}

var obj = {
	a: 2,
	foo: foo
}

var a = 'I am global'; //全局对象的属性

function doFoo(fn){
	// fn实际上引用的是foo 尽管代码显示传进来是obj.foo，但实际上，执行的是foo函数的本身
	// 这是一种参数传递的 Function 调用
	fn(); // <---调用位置
}

doFoo(obj.foo); // I am global

// 内置的函数也有参数传递特性
setTimeout( obj.foo, 100 ); // I am global
```

```javascript
function foo() { console.log(this.a) }

var a = 2; 
var o = { a: 3, foo: foo }; 
var p = { a: 4 };

o.foo(); // 3 
(p.foo = o.foo)(); // 2
```

赋值表达式 p.foo = o.foo 的 返回值 是目标函数的引用， 因此调用位置是 foo() 而不是 p.foo() 或者 o.foo() 。

## Method Invocation Pattern

诸如`foo.bar()`的调用形式被称为Method Invocation Pattern，注意其特点是被调用的函数作为一个对象的属性出现，必然会有“.”或者“\[]”这样的关键符号。在这种模式下，bar函数体中的this永远为“.”或“\[”前的那个对象，如上例中就一定是foo对象。

```javascript
function foo(){
	console.log(this.a);
}

var obj = {
	a: 2,
	foo: foo
};

obj.foo(); // 2
```

{% hint style="info" %}
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用
{% endhint %}

```javascript
function foo() { 
    console.log( this.a ); 
}

var obj2 = { 
    a: 42, 
    foo: foo
};

var obj1 = { 
    a: 2, 
    obj2: obj2
};
​​
obj1.obj2.foo(); // 42
```

## Constructor Pattern

`new foo()`这种形式的调用被称为Constructor Pattern，其关键字`new`就很能说明问题，非常容易识别。 在这种模式下，foo函数内部的this永远是new foo()返回的对象。

```javascript
function foo(a) {
	this.a = a;
}

var bar = new foo(2);

console.log(bar.a); //2
```

## Apply Pattern

`foo.call(thisObject)`和`foo.apply(thisObject)`的形式被称为Apply Pattern，使用了内置的`call`和`apply`函数。在这种模式下，`call`和`apply`的第一个参数就是foo函数体内的this，如果thisObject是`null`或`undefined`，那么会变成Global对象。

```javascript
function foo(){
	console.log(this.a);
}

var obj = {
	a : 2
};

foo.call(obj); // 2
// 通过 foo.call(..)，可以在调用 foo 时强制把它的 this 绑定到 obj 上

function foo(something){
	console.log(this.a, something);
	return this.a + something;
}

var obj = {
	a : 2
};

var bar = foo.bind(obj);
var b = bar(3); // 2 3

console.log(b); // 5
```

## 严格模式

如果使用严格模式（ strict mode ），那么全局对象将无法使用默认绑定，因此 全局this 会绑定 到 undefined&#x20;

## 优先级

1. **Constructor Pattern**
2. **Apply Pattern**
3. **Method Invocation Pattern**
4. **Function Invocation Pattern**

### Apply Pattern > Method Invocation Pattern

```javascript
function foo() { console.log( this .a ); }

var obj1 = { a: 2, foo: foo };

var obj2 = { a: 3, foo: foo };

obj1.foo(); // 2 
obj2.foo(); // 3

obj1.foo.call( obj2 ); // 3 
obj2.foo.call( obj1 ); // 2
```

### Constructor Pattern > Apply Pattern

```javascript
function foo(something) { this .a = something; }

var obj1 = { foo: foo };

var obj2 = {};

obj1.foo( 2 ); 
console.log( obj1.a ); // 2

obj1.foo.call( obj2, 3 ); 
console.log( obj2.a ); // 3

var bar = new obj1.foo( 4 ); 
console.log( obj1.a ); // 2 
console.log( bar.a ); // 4
```

## 结论

现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的 顺序来进行判断：

1.函数是否在 new 中调用（ new 绑定）？如果是的话 this 绑定的是新创建的对象。

var bar = new foo()

2.函数是否通过call, apply指定的对象?如果是的话，this 绑定的是指定的对象

var bar = foo.call(obj2)

3.函数是否在某个上下文对象中调用？如果是的话， this 绑定的是那个上 下文对象。

var bar = obj1.foo()

4.如果都不是的话，使用默认绑定。如果在严格模式下，就绑定到 undefined ，否则绑定到 全局对象。

var bar = foo()

### 记忆

非箭头函数的调用顺序是 NCMD = new > call > method > default

## 箭头函数的this

箭头函数是根据词法作用域来决定`this`

```javascript
function foo(){
//返回一个箭头函数
	return (a) => {
		//this继承自foo()
		console.log(this.a);
	}
}

var obj1 = {
	a: 2
}

var obj2 = {
	a: 3
}

var bar = foo.call(obj1);

bar.call(obj2); // 2，不是3！
```

`foo()`内部创建的箭头函数会捕获调用时`foo()`的`this`。由于`foo()`的`this`绑定到`obj1`，`bar`的`this`也会绑定到`obj1`，箭头函数的绑定无法被修改（`new`也不行）

```javascript
let obj = {
  name: "Nealyang",
  func: (a,b) => {
      console.log(this.name,a,b);
  }
};
obj.func(1,2); // undefined 1 2
let func = obj.func;
func(1,2); // undefined  1 2
let func_ = func.bind(obj);
func_(1,2);// undefined 1 2
func(1,2);//  undefined 1 2
func.call(obj,1,2);// undefined 1 2
func.apply(obj,[1,2]);// undefined 1 2
```

## 题目

```javascript
class Foo {
    print = () => {
        console.log(this.x);
    }

    constructor() {
        this.x = 1;
    }
}

let foo = new Foo();
foo.print.call({x: 2}); // 1
```

```javascript
let Foo = {
    print: function(){
        console.log(this.x)
    },
    
    x: 1
}

Foo.print() // 1
Foo.print.call({x:2}) // 2
Foo.print.call({x:3}) // 3
```


# 闭包

## 定义

闭包是指那些能够访问自由变量的函数。

自由变量是指在函数中使用的，但既不是函数参数也不是函数的局部变量的变量。

```javascript
var a = 1;

function foo() {
    console.log(a);
}

foo();
```

上例函数 foo + foo 函数访问的自由变量 a也算是闭包。所以在《JavaScript权威指南》中就讲到：**从技术的角度讲，所有的JavaScript函数都是闭包**。

但是从实践上意义的闭包定义为：

1. 即使创建它的上下文已经销毁，它仍然存在（比如，内部函数从父函数中返回）
2. 在代码中引用了自由变量

## 闭包的例子

```javascript
function foo() {
    var a = 2; 
    function bar() { 
        console.log( a ); 
    } 
    
    return bar;
} 

var baz = foo(); 
baz(); // 2 —— 朋友，这就是闭包的效果。
```

在 foo() 执行后，通常会期待 foo() 的整个内部作用域都被销毁，因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用，所以很 自然地会考虑对其进行 回收 。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域 没有被回收。谁在使用这个内部作用域？原来是 bar() 本身在使用。

拜 bar() 所声明的位置所赐，它拥有涵盖 foo() 内部作用域的闭包，使得该作用域能够一 直存活，以供 bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用，而这个引用就叫作闭包。

## 陷阱1

```javascript
for ( var i=1; i<=5; i++) { 
    setTimeout( function timer() { 
        console.log( i ); 
    }, i*1000 ); 
}
```

上诉代码，最后只会连续的输出6，而不是1到5。

这背后的原因是根据作用域的工作原理，实际情况是尽管循环中的五个函数是在各个迭代中分别定义的， 但是**它们都 被封闭在一个共享的全局作用域中 ，因此实际上只有一个 i** 。

这样说的话， 当然所有函数共享一个 i 的引用。循环结构让我们误以为背后还有更复杂的机制在起作用，但实际上没有。**如果将延迟函数的回调重复定义五次，完全不使用循环， 那它同这段代码是完全等价的。**

{% hint style="info" %}
个人更多的理解是，参考event-loop机制，setTimeout是宏任务，不会被同步执行，会先注册。又由于在i\*1000所有同步执行完后，真正执行timer时，i值已经被改变了
{% endhint %}

解决问题的方式是，更改成立即执行函数

```javascript
for(var i=1;i<=5;i++){
    (function(j){
        setTimeout( function timer() { 
            console.log( j ); 
        }, j*1000 ); 
    })(i)
}
```

当然，更酷的是es6的写法：

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

## 陷阱2

```javascript
function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    }
}
 
var f1 = Foo(),
    f2 = Foo();
f1();
f1();
f2();
```

道题考察**闭包**和**引用类型对象**的知识点：\
1.一般来说函数执行完后它的局部变量就会随着函数调用结束被销毁，但是此题foo函数返回了一个匿名函数的引用（即一个**闭包**），它可以访问到foo()被调用产生的环境，而局部变量i一直处在这个环境中，只要一个环境有可能被访问到，它就不会被销毁，所以说闭包有延续变量作用域的功能。这就好理解为什么：

```javascript
f1();//0
f1();//1
```

2.我一开始认为f1和f2都=foo()是都指向了同一个function引用类型，所以顺理成章就会答错认为：

```javascript
f2();//2
```

但其实foo()返回的是一个匿名函数，所以f1,f2相当于指向了两个不同的函数对象，所以结果也就顺理成章的变为：

```javascript
f2();//0
```

### 注意

如果上面的代码改成下面，结果会改变

```javascript
const Foo = (function() {
    var i = 0;
    return function() {
        console.log(i++);
    }
})()
 
var f1 = Foo,
    f2 = Foo;
f1();  // 0
f1();  // 1
f2();  // 2
```

其本质区别为， Foo变成立即运行函数，已经生成了确定的匿名函数地址，再分别分配给 f1和 f2

## 闭包的应用场景

vue 源码中 全局Api中的$watch方法中使用闭包

```javascript
// 根据Vue的$watch原理实现的简易版$watch
Vue.prototype.$watch = function(exp, cb, options = {immediate: true, deep: false}) {
    let watcher = new Watcher(this, exp, cb, options);
    return () => {
        watcher.unWatch();
    };
}
```

应用

```javascript
let obj = {
	name: 'kiner',
	age: 20
};
 
//  监听obj.name的改变
let unWatchName = this.$watch("obj.name",function(newVal, oldVal){
	console.log(`新值：${newVal};旧值：${oldVal}；`);
});
 
// 取消监听，取消后，obj.name的改变不再会通知
unWatchName()
```

这样实现的巧妙之处在于，我们无须关心要调用谁去取消监听，你怎么监听的，他就给你返回一个取消监听的方法，直接调用这个方法取消就可以了。

## 闭包应用

### 节流

### 防抖

### 缓存

```javascript
const Id2NameMap = {};
const getNameById = function (id) {

  if (Id2NameMap[id]) return Id2NameMap[id];

  return new Promise(resolve => {
    mockGetNameById(id, function (name) {
      Id2NameMap[id] = name;
      resolve(name);
    })
  });
}
Promise.resolve(getNameById(id)).then(name => {
  console.log(name);
});
```

### Once

```javascript
/**
 * Ensure a function is called only once.
 */
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}
```


# Event-loop

## 执行顺序

![](/files/-LszGVxfUqSnPmUfM1wV)

![](/files/-LszGn-lLt6bPGqIPeLE)

> 一个掘金的老哥（ssssyoki）的文章摘要： 那么如此看来我给的答案还是对的。但是js异步有一个机制，就是遇到宏任务，先执行宏任务，将宏任务放入eventqueue，然后在执行微任务，将微任务放入eventqueue最骚的是，这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数，然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。

* 而宏任务一般是：包括整体代码script，setTimeout，setInterval、setImmediate、requestAnimationFrame。
* 微任务：原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。

setTimeout和setInterval的运行机制是，将指定的代码移出本次执行，等到下一轮Event Loop时，再检查是否到了指定时间。如果到了，就执行对应的代码；如果不到，就等到再下一轮Event Loop时重新判断。这意味着，setTimeout指定的代码，必须等到本次执行的所有代码都执行完，才会执行。

每一轮Event Loop时，都会将“任务队列”中需要执行的任务，一次执行完。setTimeout和setInterval都是把任务添加到“任务队列”的尾部。因此，它们实际上要等到当前脚本的所有同步任务执行完，然后再等到本次Event Loop的“任务队列”的所有任务执行完，才会开始执行。由于前面的任务到底需要多少时间执行完，是不确定的，所以没有办法保证，setTimeout和setInterval指定的任务，一定会按照预定时间执行。

## 宏任务

| #                       | 浏览器 | Node |
| ----------------------- | --- | ---- |
| `I/O`                   | ✅   | ✅    |
| `setTimeout`            | ✅   | ✅    |
| `setInterval`           | ✅   | ✅    |
| `setImmediate`          | ❌   | ✅    |
| `requestAnimationFrame` | ✅   | ❌    |

## 微任务

| #                            | 浏览器 | Node |
| ---------------------------- | --- | ---- |
| `process.nextTick`           | ❌   | ✅    |
| `MutationObserver`           | ✅   | ❌    |
| `Promise.then catch finally` | ✅   | ✅    |

## 题目

```javascript
async function async1() {
  console.log(1);
  const result = await async2();
  console.log(3);
}

async function async2() {
  console.log(2);
}

Promise.resolve().then(() => {
  console.log(4);
});

setTimeout(() => {
  console.log(5);
});

async1();
console.log(6);
```

答案是\[1,2,6,4,3,5]。这道题目主要考对JS**宏任务**和**微任务**的理解程度，JS的事件循环中每个宏任务称为一个**Tick**(标记)，在每个标记的末尾会追加一个微任务队列，一个宏任务执行完后会执行所有的微任务，直到队列清空。上题中我觉得稍微复杂点的在于async1函数，async1函数本身会返回一个Promise，同时await后面紧跟着async2函数返回的Promise，`console.log(3)`其实是在async2函数返回的Promise的then语句中执行的，then语句本身也会返回一个Promise然后追加到微任务队列中，所以在微任务队列中`console.log(3)`在`console.log(4)`后面

## await

遇到await会同步执行await后面的函数，挂起await下面的为微任务

```javascript
setTimeout(function () {
  console.log('6')
}, 0)
console.log('1')
async function async1() {
  console.log('2')
  await async2()
  console.log('5')
}
async function async2() {
  console.log('3')
}
async1()
console.log('4')
```

1. 6是宏任务在下一轮事件循环执行
2. 先同步输出1，然后调用了async1()，输出2。
3. await async2() 会先运行async2()，5进入等待状态。
4. 输出3，这个时候先执行async函数外的同步代码输出4。
5. 最后await拿到等待的结果继续往下执行输出5。
6. 进入第二轮事件循环输出6。

## promise

### Promise新建后会立即执行

```javascript
let promise = new Promise(function(resolve, reject) {
    consoloe.log('Promise')
    resolve()
})

promise.then(function() {
    consoloe.log('Resolved.')
})

console.log('Hi!')

// Promise
// Hi!
// Resolved
```

### 调用`resolve`或`reject`并不会终结 Promise 的参数函数的执行。

```javascript
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1
```

一般来说，调用`resolve`或`reject`以后，Promise 的使命就完成了，后继操作应该放到`then`方法里面，而不应该直接写在`resolve`或`reject`的后面。所以，最好在它们前面加上`return`语句，这样就不会有意外。

```javascript
new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})
```

## 浏览器操作与requestAnimationFrame

假设有这样的一些`DOM`结构：

```markup
<style>
  #outer {
    padding: 20px;
    background: #616161;
  }

  #inner {
    width: 100px;
    height: 100px;
    background: #757575;
  }
</style>
<div id="outer">
  <div id="inner"></div>
</div>
```

```javascript
const $inner = document.querySelector('#inner')
const $outer = document.querySelector('#outer')

function handler () {
  console.log('click') // 直接输出

  Promise.resolve().then(_ => console.log('promise')) // 注册微任务

  setTimeout(_ => console.log('timeout')) // 注册宏任务

  requestAnimationFrame(_ => console.log('animationFrame')) // 注册宏任务

  $outer.setAttribute('data-random', Math.random()) // DOM属性修改，触发微任务
}

new MutationObserver(_ => {
  console.log('observer')
}).observe($outer, {
  attributes: true
})

$inner.addEventListener('click', handler)
$outer.addEventListener('click', handler)
```

如果点击`#inner`，其执行顺序一定是：`click` -> `promise` -> `observer` -> `click` -> `promise` -> `observer` -> `animationFrame` -> `animationFrame` -> `timeout` -> `timeout`。

因为一次`I/O`创建了一个宏任务，也就是说在这次任务中会去触发`handler`。\
按照代码中的注释，在同步的代码已经执行完以后，这时就会去查看是否有微任务可以执行，然后发现了`Promise`和`MutationObserver`两个微任务，遂执行之。\
因为`click`事件会冒泡，所以对应的这次`I/O`会触发两次`handler`函数(\_一次在`inner`、一次在`outer`\_)，所以会优先执行冒泡的事件(\_早于其他的宏任务\_)，也就是说会重复上述的逻辑。\
在执行完同步代码与微任务以后，这时继续向后查找有木有宏任务。\
需要注意的一点是，因为我们触发了`setAttribute`，实际上修改了`DOM`的属性，这会导致页面的重绘，而这个`set`的操作是同步执行的，也就是说`requestAnimationFrame`的回调会早于`setTimeout`所执行。

<br>


# 跨域

## &#x20;域

![](/files/-Lt006ZPFwzyYU-Al7mv)

## 跨域

由于JavaScript的同源策略（是浏览器施加的安全限制），浏览器不能执行其他网站的脚本。

所谓同源是指，协议，域名，端口均相同。

![](/files/-Lt00LM0aW_4UngguVEA)

## JSONP

JSONP 全称为：JSON with padding，可用于解决老版本浏览器的跨域数据访问问题。

由于 web 页面上调用 js 文件不受浏览器同源策略的影响，所以通过 script 标签可以进行跨域请求：

1. 首先前端需要先设置好回调函数，并将其作为 url 的参数。
2. 服务端接收到请求后，通过该参数获取到回调函数名，并将数据放在参数中将其返回
3. 收到结果后因为是 script 标签，所以浏览器会当做是脚本进行运行，从而达到跨域获取数据的目的

jsonp 之所以能够跨域的关键在于页面调用 JS 脚本是不受同源策略的影响，相当于向后端发起一条 http 请求，跟后端约定好函数名，后端拿到函数名，动态计算出返回结果并返回给前端执行 JS 脚本，相当于是一种 "动态 JS 脚本"

接下来我们通过一个实例来尝试：

后端逻辑：

```javascript
// jsonp/server.js
const url = require('url');
	
require('http').createServer((req, res) => {
	const data = {
		x: 10
	};
	// 拿到回调函数名
	const callback = url.parse(req.url, true).query.callback;
	console.log(callback);
	res.writeHead(200);
	res.end(`${callback}(${JSON.stringify(data)})`);

}).listen(3000, '127.0.0.1');

console.log('启动服务，监听 127.0.0.1:3000');
```

前端逻辑：

```javascript
// jsonp/index.html
<script>
    function jsonpCallback(data) {
        alert('获得 X 数据:' + data.x);
    }
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
```

## CORS

CORS 是一个 W3C 标准，全称是"跨域资源共享"（Cross-origin resource sharing）它允许浏览器向跨源服务器，发出 XMLHttpRequest 请求，从而克服了 ajax 只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前，所有浏览器都支持该功能，IE浏览器不能低于IE10。

对于开发者来说，CORS 通信与同源的 ajax 通信没有差别，代码完全一样。浏览器一旦发现 ajax 请求跨源，就会自动添加一些附加的头信息，有时还会多出一次附加的请求，但用户不会有感觉。

因此，实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口，就可以跨域通信。

前端逻辑很简单，只要正常发起 ajax 请求即可:

```javascript
// cors/index.html
<script>
	const xhr = new XMLHttpRequest();
	xhr.open('GET', 'http://127.0.0.1:3000', true);
	xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
			alert(xhr.responseText);
		}
	}
	xhr.send(null);
</script>
```

这似乎跟一次正常的异步 ajax 请求没有什么区别，关键是在服务端收到请求后的处理：

```javascript
// cors/server.js
require('http').createServer((req, res) => {

	res.writeHead(200, {
		'Access-Control-Allow-Origin': 'http://localhost:8080',
		'Content-Type': 'text/html;charset=utf-8',
	});
	res.end('这是你要的数据：1111');

}).listen(3000, '127.0.0.1');

console.log('启动服务，监听 127.0.0.1:3000');
```

关键是在于设置相应头中的 **Access-Control-Allow-Origin**，该值要与请求头中 Origin 一致才能生效，否则将跨域失败。

CORS 的优缺点：

1. 使用简单方便，更为安全
2. 支持 POST 请求方式
3. CORS 是一种新型的跨域问题的解决方案，存在兼容问题，仅支持 IE 10 以上

与JSONP对比

JSONP只支持`GET`请求，CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器，以及可以向不支持CORS的网站请求数据。

## location.hash

在 url 中，`http://www.baidu.com#helloworld` 的 "#helloworld" 就是 location.hash，改变 hash 值不会导致页面刷新，所以可以利用 hash 值来进行数据的传递，当然数据量是有限的。

假设 `localhost:8080` 下有文件 index.html 要和 `localhost:8081` 下的 data.html 传递消息，index.html 首先创建一个隐藏的 iframe，iframe 的 src 指向 `localhost:8081/data.html`，这时的 hash 值就可以做参数传递。

```javascript
// hash/client/index.html 对应 localhost:8080/index.html
<script>
	let ifr = document.createElement('iframe');
	ifr.style.display = 'none';
	ifr.src = "http://localhost:8081/data.html#data";
	document.body.appendChild(ifr);
	
	function checkHash() {
		try {
			let data = location.hash ? location.hash.substring(1) : '';
			console.log('获得到的数据是：', data);
		}catch(e) {

		}
	}
	window.addEventListener('hashchange', function(e) {
		console.log('获得的数据是：', location.hash.substring(1));
	});
</script>
```

data.html 收到消息后通过 parent.location.hash 值来修改 index.html 的 hash 值，从而达到数据传递。

```javascript
// hash/server/data.html 对应 localhost:8081/data.html
<script>
	switch(location.hash) {
		case "#data":
			callback();
			break;
	}
	function callback() {
		const data = "data.html 的数据"
		try {
			parent.location.hash = data;
		}catch(e) {
			// ie, chrome 下的安全机制无法修改 parent.location.hash
			// 所以要利用一个中间的代理 iframe 
			var ifrproxy = document.createElement('iframe');
			ifrproxy.style.display = 'none';
			ifrproxy.src = 'http://localhost:8080/proxy.html#' + data;     // 该文件在 client 域名的域下
			document.body.appendChild(ifrproxy);
		}
	}
</script>
```

由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值，所以要借助于 `localhost:8080` 域名下的一个代理 iframe 的 proxy.html 页面

```javascript
// hash/client/proxy.html 对应 localhost:8080/proxy.html
<script>
    parent.parent.location.hash = self.location.hash.substring(1);
</script>
```

当然这种方法存在着诸多的缺点：

1. 数据直接暴露在了 url 中
2. 数据容量和类型都有限等等

## window\.name

window\.name（一般在 js 代码里出现）的值不是一个普通的全局变量，而是当前窗口的名字，这里要注意的是每个 iframe 都有包裹它的 window，而这个 window 是 top window 的子窗口，而它自然也有 window\.name 的属性，window\.name 属性的神奇之处在于 name 值在不同的页面（甚至不同域名）加载后依旧存在（如果没修改则值不会变化），并且可以支持非常长的 name 值（2MB）。

举个简单的例子：

你在某个页面的控制台输入：

```javascript
window.name = "Hello World"
window.location = "http://www.baidu.com"
```

页面跳转到了百度首页，但是 window\.name 却被保存了下来，还是 Hello World，跨域解决方案似乎可以呼之欲出了：

前端逻辑：

```javascript
// name/client/index.html 对应 localhost:8080/index.html 
<script>
	let data = '';
	const ifr = document.createElement('iframe');
	ifr.src = "http://localhost:8081/data.html";
	ifr.style.display = 'none';
	document.body.appendChild(ifr);
	ifr.onload = function() {
		ifr.onload = function() {
			data = ifr.contentWindow.name;
			console.log('收到数据:', data);
		}
		ifr.src = "http://localhost:8080/proxy.html";
	}
</script>
```

数据页面：

```javascript
// name/server/data.html 对应 localhost:8081/data.html
<script>
	window.name = "data.html 的数据!";
</script>
```

`localhost:8080index.html` 在请求数据端 `localhost:8081/data.html` 时，我们可以在该页面新建一个 iframe，该 iframe 的 src 指向数据端地址(利用 iframe 标签的跨域能力)，数据端文件设置好 window\.name 的值。

但是由于 index.html 页面与该页面 iframe 的 src 如果不同源的话，则无法操作 iframe 里的任何东西，所以就取不到 iframe 的 name 值，所以我们需要在 data.html 加载完后重新换个 src 去指向一个同源的 html 文件，或者设置成 'about:blank;' 都行，这时候我只要在 index.html 相同目录下新建一个 proxy.html 的空页面即可。

## postMessage

postMessage 是 HTML5 新增加的一项功能，跨文档消息传输(Cross Document Messaging)，目前：Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能，使用起来也特别简单。

前端逻辑：

```javascript
// postMessage/client/index.html 对应 localhost:8080/index.html
<iframe src="http://localhost:8081/data.html" style='display: none;'></iframe>
<script>
	window.onload = function() {
		let targetOrigin = 'http://localhost:8081';
		window.frames[0].postMessage('index.html 的 data!', targetOrigin);
	}
	window.addEventListener('message', function(e) {
		console.log('index.html 接收到的消息:', e.data);
	});
</script>
```

创建一个 iframe，使用 iframe 的一个方法 postMessage 可以想 `http://localhost:8081/data.html` 发送消息，然后监听 message，可以获得其文档发来的消息。

数据端逻辑：

```javascript
// postMessage/server/data.html 对应 localhost:8081/data.html
<script>
	window.addEventListener('message', function(e) {
		if(e.source != window.parent) {
			return;
		}
		let data = e.data;
		console.log('data.html 接收到的消息:', data);
		parent.postMessage('data.html 的 data!', e.origin);
	});
</script>
```

## document.domain

对于主域相同而子域不同的情况下，可以通过设置 document.domain 的办法来解决，具体做法是可以在 `http://www.example.com/index.html` 和 `http://sub.example.com/data.html` 两个文件分别加上 `document.domain = "example.com"` 然后通过 index.html 文件创建一个 iframe，去控制 iframe 的 window，从而进行交互，当然这种方法只能解决主域相同而二级域名不同的情况，如果你异想天开的把 script.example.com 的 domain 设为 qq.com 显然是没用的，那么如何测试呢？

测试的方式稍微复杂点，需要安装 nginx 做域名映射，如果你电脑没有安装 nginx，请先去安装一下: [nginx](http://nginx.org/)

前端逻辑：

```
// domain/client/index.html 对应 sub1.example.com/index.html
<script>
	document.domain = 'example.com';
	let ifr = document.createElement('iframe');
	ifr.src = 'http://sub2.example.com/data.html';
	ifr.style.display = 'none';
	document.body.append(ifr);
	ifr.onload = function() {
		let win = ifr.contentWindow;
		alert(win.data);
	}
</script>
```

数据端逻辑：

```
// domain/server/data 对应 sub2.example.com/data.html
<script>
	document.domain = 'example.com';
	window.data = 'data.html 的数据！';
</script>
```

打开操作系统下的 hosts 文件：mac 是位于 /etc/hosts 文件，并添加：

```
127.0.0.1 sub1.example.com
127.0.0.1 sub2.example.com
```

之后打开 nginx 的配置文件：/usr/local/etc/nginx/nginx.conf，并在 http 模块里添加，记得输入 nginx 启动 nginx 服务：

```
/usr/local/etc/nginx/nginx.conf
http {
    // ...
    server {
        listen 80;
        server_name sub1.example.com;
        location / {
            proxy_pass http://127.0.0.1:8080/;
        }
    }
    server {
        listen 80;
        server_name sub2.example.com;
        location / {
            proxy_pass http://127.0.0.1:8081/;
        }
    }
    // ...
}
```

相当于是讲 `sub1.example.com` 和 `sub2.example.com` 这些域名地址指向本地 `127.0.0.1:80`，然后用 nginx 做反向代理分别映射到 8080 和 8081 端口。

这样访问 `sub1(2).example.com` 等于访问 `127.0.0.1:8080(1)`


# function.length

`length` 是函数对象的一个属性值，指该函数有多少个必须要传入的参数，即形参的个数。

形参的数量不包括剩余参数个数，仅包括第一个具有默认值之前的参数个数。

与之对比的是，  [`arguments.length`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments/length) 是函数被调用时实际传参的个数。

```javascript
console.log(Function.length); /* 1 */

console.log((function()        {}).length); /* 0 */
console.log((function(a)       {}).length); /* 1 */
console.log((function(a, b)    {}).length); /* 2 etc. */

console.log((function(...args) {}).length); 
// 0, rest parameter is not counted

console.log((function(a, b = 1, c) {}).length);
// 1, only parameters before the first one with 
// a default value is counted

((a, b, c = 3) => {}).length; // 2
((a, b = 2, c) => {}).length; // 1
((a = 1, b, c) => {}).length; // 0
((...args) => {}).length; // 0
```


# arguments

`arguments`对象是所有（**非箭头**）函数中都可用的**局部变量**。

`arguments`对象不是一个 [`Array`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Array)&#x20;

```javascript
console.log(typeof arguments);    // 'object'
```

它类似于`Array`，但除了length属性和索引元素之外没有任何`Array`属性。例如，它没有 [pop](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) 方法。但是它可以被转换为一个真正的`Array`：

```javascript
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES6
const args = Array.from(arguments);
const args = [...arguments];
```


# !!

有的地方能看到`!!` 这样的操作符，实际上这是`!` 的两次使用。

其真实目的是把一个非`boolean`变量转换为`boolean` 变量, 还有这样写法比较简洁。

```javascript
!!false === false
!!true === true

!!0 === false
!!parseInt("foo") === false // NaN is falsy
!!1 === true
!!-1 === true  // -1 is truthy

!!"" === false // empty string is falsy
!!"foo" === true  // non-empty string is truthy
!!"false" === true  // ...even if it contains a falsy value

!!window.foo === false // undefined is falsy
!!null === false // null is falsy

!!{} === true  // an (empty) object is truthy
!![] === true  // an (empty) array is truthy; PHP programmers beware!
```


# void 0

## undefiend并不是保留词

undefined 并不是保留词（reserved word），它只是全局对象的一个属性，在低版本 IE 中能被重写。

```javascript
var undefined = 10;

// undefined -- chrome
// 10 -- IE 8
alert(undefined);
```

事实上，undefined 在 ES5 中已经是全局对象的一个只读（read-only）属性了，它不能被重写。但是在局部作用域中，还是可以被重写的。

```javascript
(function() {
  var undefined = 10;

  // 10 -- chrome
  alert(undefined);
})();

(function() {
  undefined = 10;

  // undefined -- chrome
  alert(undefined);
})();
```

## 为什么是void 0

void 运算符能对给定的表达式进行求值，然后返回 undefined。也就是说，void 后面你随便跟上一个表达式，返回的都是 undefined，都能完美代替 undefined！那么，这其中最短的是什么呢？毫无疑问就是 void 0 了。


# 柯里化1

**柯里化**, 即 **Currying** 的音译。

**Currying** 为实现多参函数提供了一个递归降解的实现思路——**把接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数而且返回结果的新函数。**

下面尝试对最简单的加法函数进行柯里化改造

```javascript
function add (x, y) {
  return (x + y)
}
```

## **第一层**

```javascript
function curriedAdd (x) {
  return function(y) {
    return x + y
  }
}
```

当然以上实现是有一些问题的：它并不通用，并且我们并不想通过重新编码函数本身的方式来实现 **Currying** 化。

## 第二层

```javascript
function currying (fn, ...args1) {
    return function (...args2) {
        return fn(...args1, ...args2)
    }
}

var increment = currying(add, 1)
```

在此实现中，`currying` 函数的返回值其实是一个接收剩余参数并且立即返回计算值的函数。**即它的返回值并没有自动被 Currying化 。所以我们可以通过递归来将 currying 的返回的函数也自动 Currying** 化。

## 第三层

```javascript
function trueCurrying(fn, ...args) {
    if (args.length >= fn.length) {
        return fn(...args)
    }
    return function (...args2) {
        return trueCurrying(fn, ...args, ...args2)
    }
}
```

以上的思想为

> **比较多次接受的参数总数与函数定义时的入参数量，当接受参数的数量大于或等于被 Currying 函数的传入参数数量时，就返回计算结果，否则返回一个继续接受参数的函数。**

```javascript
const curriedAdd = trueCurring(add)
curriedAdd(1,2) // 3
curriedAdd(1)(2) // 3
```

## 柯里化优势

1. 多参函数复用性
2. 函数式编程而生

## 柯里化劣势

1. 一些特性有其他解决方案

   如果我们只是想提前绑定参数，那么我们有很多好几个现成的选择，bind，箭头函数等，而且性能比Curring更好。


# 柯里化2

重要参考：<https://github.com/mqyqingfeng/Blog/issues/42>

> 用闭包把参数保存起来，当参数的数量足够执行函数了，就开始执行函数，有没有毛病

## 第一版

```javascript
var curry = function (fn) {
    // arguments[0]是fn, 这里是收集更多的参数
    // 注意，arguments是array-like object，所以要用array的方法
    var args = [].slice.call(arguments, 1);
    return function() {
        // 合并所有参数
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};


//应用
function add(a, b) {
    return a + b;
}

var addCurry = curry(add, 1, 2);
addCurry() // 3
//或者
var addCurry = curry(add, 1);
addCurry(2) // 3
//或者
var addCurry = curry(add);
addCurry(1, 2) // 3
```

## 第二版

```javascript
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}

// 应用
var fn = curry(function(a, b, c) {
    return [a, b, c];
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
```

更易懂的实现

```javascript
function curry(fn, args) {
    var length = fn.length;
    args = args || [];
    return function() {
        var _args = args.slice(0), arg, i;
        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            _args.push(arg);
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}
java

var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
```

## 超简洁写法

```javascript
var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)
```

{% hint style="info" %}
参数不满足长度要求时，返回的函数仍需使用rest parameter：`(...arg) => judge(...args, ...arg)`，否则除了第一次可以传递多个参数，后面只能一个一个传到足够为止了，不能直接处理fn(1,2)(3,4,5)的情况。
{% endhint %}

## 超超简洁写法

```javascript
var curry = (fn, ...args) =>
        fn.length <= args.length
            ? fn(...args)
            : curry.bind(null, fn, ...args)
var fn = curry(function(a, b, c){
  console.log([a,b,c])
})

fn(1,2,3)
fn(1,2)(3)
fn(1)(2,3)
fn(1)(2)(3)

// [ 1, 2, 3 ]
// [ 1, 2, 3 ]
// [ 1, 2, 3 ]
// [ 1, 2, 3 ]
```


# 异常

```javascript
// 方式1 
throw 'error'

// 方式2
throw new Error('error')
```

常见的异常还包括 TypeError, RefenceError, RangeError, SyntaxError


# 协程

进程和线程我们都知道，那么协程是什么呢？

**协程是一种比线程更加轻量级的存在**。可以把协程看成是跑在线程上的任务，**一个线程上可以存在多个协程，但是在线程上同时只能执行一个协程**，比如当前执行的是 A 协程，要启动 B 协程，那么 A 协程就需要将主线程的控制权交给 B 协程，这就体现在 A 协程暂停执行，B 协程恢复执行；同样，也可以从 B 协程中启动 A 协程。通常，**如果从 A 协程启动 B 协程，我们就把 A 协程称为 B 协程的父协程**。

正如一个进程可以拥有多个线程一样，一个线程也可以拥有多个协程。最重要的是，协程不是被操作系统内核所管理，而是完全由**程序所控制**（即在用户态执行）。这样带来的好处就是性能得到了很大的提升，不会像线程切换那样消耗资源。

```javascript
function* genDemo() {
  console.log("开始执行第一段")
  yield 'generator 2'

  console.log("开始执行第二段")
  yield 'generator 2'

  console.log("开始执行第三段")
  yield 'generator 2'

  console.log("执行结束")
  return 'generator 2'
}

console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4')
```

执行过程如下图所示，可以重点关注协程之间的切换<br>

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/366ae82a000d4b158397832df8016407~tplv-k3u1fbpfcp-watermark.image)

从图中可以看出来协程的四点规则：

* 通过调用生成器函数 `genDemo` 来创建一个 **协程 `gen`**，创建之后，`gen` 协程并没有立即执行。
* 要让 `gen` 协程执行，**需要通过调用 `gen.next`**。
* 当协程正在执行的时候，可以 **通过 `yield` 关键字来暂停 `gen` 协程的执行**，并返回主要信息给父协程。
* 如果协程在执行期间，遇到了 `return` 关键字，那么 JS 引擎会结束当前协程，并将 `return` 后面的内容返回给父协程。

协程之间的切换：

* `gen` 协程和父协程是在主线程上交互执行的，并不是并发执行的，它们之前的切换是 **通过 `yield` 和 `gen.next` 来配合完成** 的。
* 当在 `gen` 协程中调用了 `yield` 方法时，JS 引擎会保存 `gen` 协程当前的调用栈信息，并恢复父协程的调用栈信息。同样，当在父协程中执行 `gen.next` 时，JS 引擎会保存父协程的调用栈信息，并恢复 `gen` 协程的调用栈信息。

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3f53f90242af45e1a639e0cb19f0b4db~tplv-k3u1fbpfcp-watermark.image)

其实在 JS 中，`Generator` 生成器就是协程的一种实现方式。<br>


# JS陷阱


# 相等

\==和 === 的区别在于， == 检查的是允许类型转换情况下的值的相等性，而 === 检查不允许类型转换 情况下的值的相等性；因此， === 经常被称为“严格相等”。

不等 != 与 == 对应， !== 与 === 对应。

```javascript
var a = "42";
var b = 42;

a == b;  // true
a === b; // false
```

{% hint style="info" %}
如果是比较两个非原生值的话，比如对象（包括函数和数组），那么你需要特殊注意 == 与 === 这些比较规则。因为这些值通常是通过引用访问的，所以 == 和 === 比较只是简单地检 查这些引用是否匹配，而完全不关心其引用的值是什么。

举例来说，通过简单地在元素之间插入逗号（ , ），数组在默认情况下会转换为字符串。你 可能会认为内容相同的两个数组也会 == 相等，但并非如此
{% endhint %}

```javascript
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";

a == c; // true
b == c; // true
a == b; // false
```

## \[ ] == !\[ ]

等价于&#x20;

```javascript
var a = []
var b = []
var c = ![] // => false

a == c // => a == false
[] == false 
```

这里记住不同类型的相等比较，顺序是

1. 首先尝试使用valueOf()
2. 再尝试使用toString()
3. 最后尝试toNumber()

整理就是

1. 隐式调用Boolean转型函数，对空数组转换成Boolean值，再对结果取反。此时比较\[] == false
2. 隐式调用Number转型函数，将false转换为数值0，此时比较\[] == 0
3. 调用valueOf方法和toString方法，此时\[].toString() 为空字符串，比较 '' == 0
4. 隐式调用Number转型函数，将空字符串转换为0，比较 0 == 0
5. 最后返回true

所以 \[ ] == false 为true

## {} == !{} / {} == false

分析与\[]的前两步都相同，但是{}.toString()会转换为'\[object Object]'，因此第4步会比较'\[object Object]' == 0,这必然为 false

## \[ ] == \[ ] / \[ ] == { }

值为 false

这里因为两边对比都是对象时，比较的是两个引用值在内存中是否是同一个对象

## **(a ==1 && a== 2 && a==3) 可能为 true 吗**

这里也在考察隐式变换，== 能把隐式转换 == 两边的类型相等

### 方法1

```javascript
const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
```

### 方法2

```javascript
// 利用数据劫持(proxy/Object.defineProperty)
let i = 1;
let a = new Proxy({}, {
    i: 1,
    get: function() {
        return () => this.i++;
    }
})
```

### 方法3

```javascript
// 数组的toString接口默认调用数组的join方法，重新join方法
let a = [1,2,3];
a.join = a.shift；
```


# 连等赋值

## 引子

```javascript
function foo() {
  let a = b = 0;
  a++;
  return a;
}

foo();
typeof a; // => ???
typeof b; // => ???
```

## 解析

这个代码的重点在第二行:`let a = b = 0`。这个语句声明了一个局部变量 `a`，但是它也声明了一个全局变量`b`。

在 `foo()` 作用域或全局作用域中都没有声明变量 `b`。因此 JS 引荐将`b = 0` 表达式解释为 `window.b = 0`。

如下图所示，函数 `foo` 中的 `i` 都是一个偶然创建的全局变量：

同样，在咱们的问题中，`b` 是一个偶然创建的全局变量。在浏览器中，上面的代码相当于如下：

```javascript
function foo() {
  let a;
  window.b = 0;
  a = window.b;
  a++;
  return a;
}

foo();
typeof a;        // => 'undefined'
typeof window.b; // => 'number'
```

`typeof a` 是 `'undefined'`。变量 `a` 仅在 `foo()` 作用域中声明，在外部作用域内不可用。

`typeof b` 结果是 `'number'`。`b` 是一个值为 `0` 的全局变量


# 改变数组的length

## 引子

```
const clothes = ['jacket', 't-shirt']; 
clothes.length = 0;

clothes[0]; // => ???
```

## 解析

数组对象的 `length` 属性具有一些[特殊的行为](http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-array-instances-length)：

> 减少 length 属性的值的副作用是删除 自己的 数组元素，这些元素的数组索引位于新旧长度值之间。

由于 `length` 属性行为，当 JS 执行 `clothes.length = 0` 时，删除所有的 `clothes` 项。 所以 `clothes[0]` 的值为 `undefined`，因为 `clothes` 数组已被清空。


# 引用传参

## 引子

```javascript
function test(person) {
  person.age = 26
  person = {
    name: 'hzj',
    age: 18
  }
  return person
}
const p1 = {
  name: 'fyq',
  age: 19
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
```

## 解析

```
p1：{name: “fyq”, age: 26}
p2：{name: “hzj”, age: 18}
```

> 原因: 在函数传参的时候传递的是对象在堆中的内存地址值，test函数中的实参person是p1对象的内存地址，通过调用person.age = 26确实改变了p1的值，但随后person变成了另一块内存空间的地址，并且在最后将这另外一份内存空间的地址返回，赋给了p2。


# new Number vs Number

```javascript
new Number( x )
```

返回一个对象

```javascript
Number( x )
```

返回一个基本数字类型


# new Object vs Object vs Object.create(null)

new Object() 和 Object 是一样的，都会创建一个对象实例

但是Object.create(null) 会创建一个空对象的实例

区别在于Object.create(null) 的实例是没有\_\_proto\_\_，而前两者都有


# JS开发知识点

##


# html classlist

## 原生JS增删查html classlist

```javascript
const htmlNode = document.getElementsByTagName('html')[0]
const classList = htmlNode.classList

classList.contains('is-clipped')
classList.add('is-clipped')
classList.remove('is-clipped')
```


# 图片懒加载

## clientHeight、scrollTop 和 offsetTop

首先给图片一个占位资源:

```markup
<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />
```

接着，通过监听 scroll 事件来判断图片是否到达视口:

```javascript
let img = document.getElementsByTagName("img");
let num = img.length;
let count = 0;//计数器，从第一张图片开始计

lazyload();//首次加载别忘了显示图片

window.addEventListener('scroll', lazyload);

function lazyload() {
  let viewHeight = document.documentElement.clientHeight;//视口高度
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;//滚动条卷去的高度
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].offsetTop < scrollHeight + viewHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}
```

当然，最好对 scroll 事件做节流处理，以免频繁触发:

```javascript
// throttle函数我们上节已经实现
window.addEventListener('scroll', throttle(lazyload, 200));
```

## getBoundingClientRect

现在我们用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 `getBoundingClientRect` API。

上述的 lazyload 函数改成下面这样:

```javascript
function lazyload() {
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}
```

## IntersectionObserver

浏览器内置的一个`API`，实现了`监听window的scroll事件`、`判断是否在视口中`以及`节流`三大功能。

我们来具体试一把：

```javascript
let img = document.getElementsByTagName("img");

const observer = new IntersectionObserver(changes => {
  //changes 是被观察的元素集合
  for(let i = 0, len = changes.length; i < len; i++) {
    let change = changes[i];
    // 通过这个属性判断是否在视口中
    if(change.isIntersecting) {
      const imgElement = change.target;
      imgElement.src = imgElement.getAttribute("data-src");
      observer.unobserve(imgElement);
    }
  }
})
Array.from(img).forEach(item => observer.observe(item));
```

这样就很方便地实现了图片懒加载，当然这个`IntersectionObserver`也可以用作其他资源的预加载，功能非常强大。

![](/files/-LwXWMR2XzQ6zJJhROQm)


# 提取对象中所有属性除了某一项

```javascript
const obj  = { A: 1, B: 2, C: 3, D: 4 };
const { C, ...objWithoutC } = obj;
console.log(objWithoutC);
```


# 空值判断

## 空数组判断

通过上面的类型判断是数组类型，然后它的`length = 0`即可

## 空对象判断

### Object.keys() 的长度为 0

```javascript
var obj = {}
Object.keys(obj).length === 0 // true
```

### 对象转字符串为 {}

```javascript
var obj = {}
var a = JSON.stringify(obj)
a === "{}" // true
```

### Object.getOwnPropertyNames() 长度为空

```javascript
var obj = {}
Object.getOwnPropertyNames(obj).length === 0; // true
```


# 实现JS常见函数


# Debounce

防抖函数总结

## 定义

bounce即弹起，debounce的中文意思为“防抖“

## 场景

debounce个人理解的使用场景为，某种经常会触发的事件，等待稳定后最后一次性执行。比较典型的有：

* 键盘输入事件捕捉。等待用户最后输入完成
* scroll事件，等待用户最后滚动结束处理
* resize事件，等待用户最后调整结束处理

## 实现

```javascript
/// 返回一个函数，但是只要它一直持续地被调用，就不会被触发；除非它最后一次被调用停止了n毫秒
// 如果`immediate`参数被传递，那么会直接执行
function debounce(func, wait, immediate){
  var timeout = null;
  
  return excuteFunction(){
    var context = this;
    var args = arguments;
    var later = function(){
      timeout = null
      if(!immediate) func.apply(context, args);
    }
    
    var callNow = immediate && !timeout;
    if(callNow) func.apply(context, args);
    
    // clearTimeout非常关键。如果在timeout完成后later调用前，excuteFunction又被调用，那么timeou会被清除，并接下来被重新赋值开始
    clearTimeout(timeout)
    
    timeout = setTimeout(later, wait);
  }
}
```

## ES6 实现

```javascript
function debounceES6(func, wait, immediate) {
  let timeout
  return (...args) => {
    let callNow = immediate && !timeout
    if (callNow) func.apply(this, args);
    
    clearTimeout(timeout);
    
    timeout = setTimeout(() => {
      timeout = null
      if (!immediate) func.apply(this, args)
    }, wait);
  }
}
```

## 深度

GitHub有用户专门提到利用箭头函数对于this的处理，省略到context的写法

```javascript
function debounce (fn, wait) {
  let t
  return function () {
    clearTimeout(t)
    t = setTimeout(() => fn.apply(this, arguments), wait)
  }
}
```

虽然绝大数情况下箭头函数会调用语义上正确的this，不过考虑如下场景，就会有错误

```javascript
const obj = {
  name: 'foo',
  sayMyName() {
    console.log('My name is', this.name)
  }
}

obj.sayMyName() //-> My name is foo
obj.deb = debounce(obj.sayMyName, 1000)
obj.deb() // Should log -> My name is foo
```

在省略context的写法下，只能输出，因为`My name is`obj的this没有绑定进去。

除非使用

```javascript
obj.deb = debounce(obj.sayMyName.bind(obj), 1000)
obj.deb() // now -> My name is foo 
```


# Throttle

节流函数总结

## 定义

throttle和debounce非常像。唯一的区别就是，throttle会设置固定的时间间隔，即使事件还没有间断大于某个阈值，只要时间间隔到了，就会执行一次。

简单来说，throttle是在debounce最后会执行一次的基础上，穿插在中间固定时间间隔执行。

## ES6实现

```javascript
function throttle(fn, wait) {
    let timeout, lastTime = 0
    return (...args) =>{
        const currentTime = Date.now()
        if(current >= lastTime + wait){
            lastTime = current
            fn(...args)
        }else {
            clearTimeout(timeout)
            timeout = setTimout(()=> fn(...args), wait)
        }
    }
}
```


# Call, Apply, Bind

Call, Apply 和 Bind 都能用来改变this的指向。它们之间的区别是：

* call 用来明确参数个数确定的情况
* apply 用在参数个数不明确的情况
* bind 是传递对象
* call和apply都会立即执行
* bind是创建一个新的函数副本

## Call 和 Apply

举例 获取数组中的最大值和最小值

```javascript
var  numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers),   //458
    maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
```

&#x20;举例 面试题 定义一个 log 方法，让它可以代理 console.log 方法

```javascript
function log(){
  console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2
```

接下来的要求是给每一个 log 消息添加一个"(app)"的前辍

```javascript
function log(){
  var args = Array.prototype.slice.call(arguments);
  args.unshift('(app)');
 
  console.log.apply(console, args);
};
```

## Bind

bind的定义是，bind()方法会创建一个新函数，称为绑定函数，当调用这个绑定函数时，绑定函数会以创建它时传入 bind()方法的第一个参数作为 this，传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

```javascript
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3
```

## 对比

使用举例

```javascript
var obj = {
    x: 81,
};
 
var foo = {
    getX: function() {
        return this.x;
    }
}
 
console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81
```

三个输出的都是81，但是注意看使用 bind() 方法的，他后面多了对括号。

也就是说，区别是，当你希望改变上下文环境之后并非立即执行，而是回调执行的时候，使用 bind() 方法。而 apply/call 则会立即执行函数。

## 实现

### Bind

参考链接&#x20;

* <https://github.com/mqyqingfeng/Blog/issues/12>

#### ES5 实现

```javascript
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
```

#### ES6 实现

```javascript
// formerArgs 为传递给 bind 函数的第二个到之后的参数
Function.prototype.bind = function (ctx, ...formerArgs) {
    let _this = this
    
    // laterArgs 为传递给原函数的参数
    return (...laterArgs) => {
        // bind 函数的不定参数在原函数参数之前
        // formerArgs 本身就是数组，可以直接调用数组的 concat 方法
        // 无需借助 call 或 apply
        return _this.apply(ctx, formerArgs.concat(laterArgs))
    }
}
```

### Call

```javascript
// 第三版
Function.prototype.call2 = function (context) {
    var context = context || window; // 处理null就指向window
    context.fn = this;

    // 处理参数
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}
```

其中

```javascript
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
}
```

最终的数组为：

```javascript
var args = [arguments[1], arguments[2], ...]
```

然后

```javascript
 var result = eval('context.fn(' + args +')');
```

在eval中，args 自动调用 args.toString()方法，eval的效果如 jawil所说，最终的效果相当于：

```javascript
 var result = context.fn(arguments[1], arguments[2], ...);
```

#### call 第二种实现方式

````javascript
Function.prototype.call = function (context, ...args) {
  // 检查调用```call```的对象是否为函数
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }

  // 将函数作为传入的```context```对象的一个属性，调用该函数
  const fn = Symbol()
  context[fn] = this
  context[fn](...args)

  // 不要忘了调用之后删除该属性
  delete context[fn]
}
````

### Apply

```javascript
Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}
```

#### apply 第二种实现方式

````javascript
Function.prototype.apply = function (context, args) {
  // 检查调用```apply```的对象是否为函数
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }

  // 将函数作为传入的```context```对象的一个属性，调用该函数
  const fn = Symbol()
  context[fn] = this
  context[fn](...args)

  // 不要忘了调用之后删除该属性
  delete context[fn]
}
````


# type

## 定义

判断类型

## 背景知识

js中有几种数据类型：

* null(空)
* undefined(未声明)
* number(数字)
* string(字符串)
* object(对象)
* boolean(布尔值)
* symbol(符号，es6新增)

{% hint style="info" %}
js中只有这7种数据类型，其他的都是对象的子类型（数组不是js的数据类型）
{% endhint %}

typeof运算符后可跟着一个操作数，用于判断该操作数的数据类型；**typeof适用于上面7种数据类型中除null、object的其他5种类型的判断**：

```javascript
typeof undefined;  // "undefined";
typeof 1;          // "number";
typeof '1';        // "string";
typeof true;       // "boolean";
typeof Symbol();   // "symbol";
typeof {a : 1};    // "object";
```

以上都是可靠正确而且易于理解的，接下来我们一起看几个正确的，但似乎不是我们期望的结果的例子：

```
typeof null === "object"; // true
typeof function () {} === "function"; // true
```

![](/files/-LqP8Gf9cioKEvQzy4at)

### 为什么 typeof null === 'object'

在 JavaScript 最初的实现中，JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针（大多数平台下值为 0x00），因此，null的类型标签也成为了 0，typeof null就错误的返回了"object"。

### 为什么 typeof function(){} === 'function'

函数是对象的子类型，按道理来说"typeof function () {} === 'object'"才对，但是结果却是"function"。这是因为，**函数是一个“特殊的对象”，它特殊就特殊在它内部有一个属性\[\[call]]，这个属性使它变成一个“可调用的对象”。**&#x800C;根据规范，实现了\[\[call]]属性的对象，typeof的结果是“function”。也因为这个，new出来的function对象也是返回function

```javascript
typeof new Function("console.log(123);") === 'function'; // true
```

## Object.prototype.toString 识别类型

```javascript
// 以下是11种：
var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)
```

![](/files/-LqQ1z6TFMz8NOPjf8tu)

## 实现

{% hint style="info" %}
在 IE6 中，null 和 undefined 会被 Object.prototype.toString 识别成 \[object Object]！
{% endhint %}

```javascript
// 第二版
var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 一箭双雕
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}
```

## 参考

* <https://github.com/mqyqingfeng/Blog/issues/28>
* <https://zhuanlan.zhihu.com/p/38249035>

## 补充

### 判断是否为数组？

&#x20;`Array.isArray` 原生方法


# 深拷贝

## 参考来源

<https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1#heading-0>

## 乞丐版

```javascript
let obj1, obj2
obj2 = JSON.parse(JSON.stringify(obj1))
```

这种写法非常简单，而且可以应对大部分的应用场景，但是它还是有很大缺陷的，比如拷贝其他引用类型、拷贝函数、循环引用等情况。

## 基础版本

```javascript
function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
```

迭代遍历对象的深度，并最终返回结果。不过，这个函数还没有考虑数组的场景。修改为：

```javascript
function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
```

## 循环引用

```javascript
const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target.target = target;
```

会使得之前的函数递归无法推出而溢出。

{% hint style="info" %}
解决循环引用问题，我们可以额外开辟一个存储空间，来存储当前对象和拷贝对象的对应关系，当需要拷贝当前对象时，先去存储空间中找，有没有拷贝过这个对象，如果有的话直接返回，如果没有的话继续拷贝，这样就巧妙化解的循环引用的问题。
{% endhint %}

```javascript
function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
```

### Map -> Weakmap

weakmap是弱引用。Map是强引用。在下面的场景中，强引用的数据并没有被释放

```javascript
let obj = { name : 'ConardLi'}
const target = new Map();
target.set(obj,'code秘密花园');
obj = null;
```

## 克隆函数

实际上克隆函数是没有实际应用场景的，两个对象使用一个在内存中处于同一个地址的函数也是没有任何问题的。

首先，我们可以通过`prototype`来区分下箭头函数和普通函数，箭头函数是没有`prototype`的。

我们可以直接使用`eval`和函数字符串来重新生成一个箭头函数，注意这种方法是不适用于普通函数的。

我们可以使用正则来处理普通函数：

分别使用正则取出函数体和函数参数，然后使用`new Function ([arg1[, arg2[, ...argN]],] functionBody)`构造函数重新构造一个新的函数。

```javascript
function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        console.log('普通函数');
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            console.log('匹配到函数体：', body[0]);
            if (param) {
                const paramArr = param[0].split(',');
                console.log('匹配到参数：', paramArr);
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}
```


# isEuqal

## 参考

* <https://github.com/mqyqingfeng/Blog/issues/41>
* <https://lodash.com/docs/4.17.15#isEqual>


# 数组乱序

## 需求

给定一个数组，返回一个完全乱序过的数组

## 实现

Fisher–Yates Shuffle复杂度 O(n)。

其实它的思想非常的简单，遍历数组元素，将其与之前的任意元素交换。因为遍历有从前向后和从后往前两种方式，所以该算法大致也有两个版本的实现。

### 从后往前版本

```javascript
function shuffle(array) {
  var _array = array.concat();

  for (var i = _array.length; i--; ) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = _array[i];
    _array[i] = _array[j];
    _array[j] = temp;
  }

  return _array;
}
```

### 流传但不准确的版本&#x20;

另一个为人津津乐道的方法是 "巧妙应用" JavaScript 中的 Math.random() 函数。

```javascript
function shuffle(a) {
  return a.concat().sort(function(a, b) {
    return Math.random() - 0.5;
  });
}
```

同样是 \[0, 1, 2 ... 10] 作为初始值，同样跑了 1w 组 case，结果请猛戳 <http://hanzichi.github.io/test-case/shuffle/Math.random/>。

看平均值的图表，很明显可以看到曲线浮动，而且多次刷新，折现的大致走向一致，平均值更是在 5 上下 0.4 的区间浮动。如果我们将 \[0, 1, 2 .. 9] 作为初始数组，可以看到更加明显不符预期的结果（有兴趣的可以自己去试下）。究其原因，要追究 JavaScript 引擎对于**Array.prototype.sort**的实现原理，这里就不展开了（其实是我也不知道）。因为 ECMAScript 并没有规定 JavaScript 引擎对于 Array.prototype.sort 应该实现的方式，所以我猜想不同浏览器经过这样的乱序后，结果也不一样。

什么时候可以用这种方法乱序呢？"非正式" 场合，一些手写 DEMO 需要乱序的场合，这不失为一种 clever solution。

但是这种解法不但不正确，而且 sort 的复杂度，平均下来应该是 O(nlogn)，跟我们接下来要说的正解还是有不少差距的。

## 参考

1. <https://www.h5jun.com/post/array-shuffle.html>
2. <https://github.com/lessfish/underscore-analysis/issues/15>


# 数组去重

```javascript
function removeDup(arr){
  const ret = []
  for(let i = 0; i < arr.length; i ++){
    if(ret.indexOf(arr[i]) < 0){
      ret.push(arr[i])
    }
  }
  return ret
}
```

```javascript
function Uniq(arr = []) {
    return arr.reduce((t, v) => t.includes(v) ? t : [...t, v], []);
}
```

```javascript
Array.from(new Set(arr))
```

```javascript
[...new Set(arr)]
```


# 实现 merge

```javascript
/**
 * Simple is object check.
 * @param item
 * @returns {boolean}
 */
function isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
}


/**
 * Deep merge two objects.
 * @param target
 * @param source
 */
export function mergeDeep(target, source) {
    if (isObject(target) && isObject(source)) {
        Object.keys(source).forEach(key => {
            if (isObject(source[key])) {
                if (!target[key] || !isObject(target[key])) {
                    target[key] = source[key];
                }
                mergeDeep(target[key], source[key]);
            } else {
                Object.assign(target, { [key]: source[key] });
            }
        });
    }
    return target;
}

const A={
    var_a: {
        'loc': 'Earth',
        'title': 'Hello World',
        'type': 'Planet', 
        'deeper':{
            'map':new Map([['a', 'AAA'],['b', 'BBB']]),
            'mapId':15473, 
        }
   }
};

const B={
    var_a: {  
        'type': 'Star',
        'deeper':{ 
            'mapId':9999, 
            'alt_map':new Map([['x', 'XXXX'],['y', 'YYYY']]),
        }
   }
}

function assert(condition, message) {
    if (!condition) {
        message = message || "Assertion failed";
        if (typeof Error !== "undefined") {
            throw new Error(message);
        }
        throw message; // Fallback
    }
}

const C = mergeDeep(A,B);
const D = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}}); 
const E = mergeDeep({b: {c: 'hallo'}}, { b : { c: { d: { e: 12345}}}}); 
const F = mergeDeep({ b : { c: { d: { e: 12345}}, d:'dag', 'f':'one'}}, {b: {c: 'hallo', e:'ok',f:'two'}}); 

assert (C.var_a.type === "Star");
assert (C.var_a.deeper.alt_map.get('x') === 'XXXX');
assert (C.var_a.deeper.map.get('b') === 'BBB');
assert (D.a === 1);
assert (D.b.c.d.e === 12345);
assert (E.b.c.d.e === 12345);
assert (F.b.c === 'hallo');
assert (F.b.d === 'dag');
assert (F.b.e === 'ok')
assert (F.b.f === 'two')
```


# 数组flat

数组flat方法是ES6新增的一个特性，可以将多维数组展平为低维数组。如果不传参默认展平一层，传参可以规定展平的层级。

```javascript
// 展平一级
function flat(arr){
    var result = [];
    for(var i = 0; i < arr.length; i++){
        if(Array.isArray(arr[i])){
            result = result.concat(flat(arr[i]))
        }else{
            result.push(arr[i]);
        }
    }
    return result;
}
```

```javascript
//展平深度层
 function flattenByDeep(array,deep){
      var result = [];
      for(var i = 0 ; i < array.length; i++){
          if(Array.isArray(array[i]) && deep > 1){
                result = result.concat(flattenByDeep(array[i],deep -1))
          }else{
                result.push(array[i])
          }
      }
      return result;
  }
```


# 实现 map

```javascript
 Array.prototype.map = function (fn, context = this) {
      if (typeof fn !== 'function') {
        throw new TypeError(fn + 'is not a function')  
      }
      const arr = this
      const result = new Array(ary.length);
      
      for (let i = 0; i < arr.length; i++) {
        // fix稀疏数组的情况
        if (i in arr) {
            result[i] = fn.call(context, arr[i], i, arr);
        }
      }
      return result
  }
```

使用 reduce 来实现 map

```javascript
Array.prototype.map = function(fn, context = this){
    if (typeof fn !== 'function') {
      throw new TypeError(fn + 'is not a function'); 
    }
    return this.reduce((acc, value, index) => {
      // fix稀疏数组的情况
      if (index in list) {
        acc[index] = fn.call(context, value, index, this);
      }
      return acc;
    }, []); // 注意这里的[]不能省
}
```


# 数组filter

filter方法经常用，实现起来也比较容易。需要注意的就是filter接收的参数依次为数组当前元素、数组index、整个数组，并返回结果为ture的元素。

```javascript
Array.prototype.filter = function(fn,context = this){
    if(typeof fn != 'function'){
        throw new TypeError(`${fn} is not a function`)
    }
    let arr = this;
    let reuslt = []
    for(var i = 0;i < arr.length; i++){
        const temp= fn.call(context,arr[i],i,arr);
        if(temp){
            result.push(arr[i]);
        }
    }
    return result
}
```

使用 reduce 来实现 filter

```javascript
Array.prototype.filter = function(fn, context = this){
    if (typeof fn !== 'function') {
      throw new TypeError(fn + 'is not a function'); 
    }
    return this.reduce((acc, value, index) => {
      // fix稀疏数组的情况
      if (index in list) {
        const ret = fn.call(context, value, index, this);
        if(ret) acc.push(ret)
      }
      return acc;
    }, []); // 注意这里的[]不能省
}
```


# 模拟new

## 定义

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

## 原始功能

```javascript
// Otaku 御宅族，简称宅
function Otaku (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

// 因为缺乏锻炼的缘故，身体强度让人担忧
Otaku.prototype.strength = 60;

Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin
```

从这个例子中，我们可以看到，实例 person 可以：

1. 访问到 Otaku 构造函数里的属性
2. 访问到 Otaku.prototype 中的属性

## 模拟思路

1. new 返回是一个Object
2. 需要设置\_\_proto\_\_指向

## 模拟实现

### 第一版

```javascript
// 第一版代码
function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};
```

在这一版中，我们：

1. 用new Object() 的方式新建了一个对象 obj
2. 取出第一个参数，就是我们要传入的构造函数。此外因为 shift 会修改原数组，所以 arguments 会被去除第一个参数
3. 将 obj 的原型指向构造函数，这样 obj 就可以访问到构造函数原型中的属性
4. 使用 apply，改变构造函数 this 的指向到新建的对象，这样 obj 就可以访问到构造函数中的属性
5. 返回 obj

### 返回值效果实现

接下来我们再来看一种情况，假如构造函数有返回值，举个例子：

```javascript
function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return {
        name: name,
        habit: 'Games'
    }
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
```

在这个例子中，构造函数返回了一个对象，在实例 person 中只能访问返回的对象中的属性。

而且还要注意一点，在这里我们是返回了一个对象，假如我们只是返回一个基本类型的值呢？

再举个例子：

```javascript
function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return 'handsome boy';
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
```

结果完全颠倒过来，这次尽管有返回值，但是相当于没有返回值进行处理。

所以我们还需要判断返回的值是不是一个对象，如果是一个对象，我们就返回这个对象，如果没有，我们该返回什么就返回什么。<br>

### 第二版 解决构造函数返回对象的问题

```javascript
// 第二版的代码
function objectFactory() {
    var obj = new Object(), //从Object.prototype上克隆一个对象
    Constructor = [].shift.call(arguments); //取得外部传入的构造器
    obj.__proto__ = Constructor.prototype; //指向正确的原型
    var ret = Constructor.apply(obj, arguments); //借用外部传入的构造器给obj设置属性
    return typeof ret === 'object' ? ret : obj; //确保构造器总是返回一个对象
};
```


# 模拟实现async

async是基于generator的

真实使用

```javascript
async function fn(args) { ... }

// 可以以下来模拟
const asyncFn = wrap(fn)
asyncFn(args) 
```

简版实现

```javascript
const wrap = (genFn) =>{
	return (...args) => new Promise((resolve, reject) => {
  	const g = genFn(...args)
    function step(data){
    	const res = g.next(data)
      if(!res.done){
      	Promise.resolve(res.value).then(step)
      }else{
      	resolve(res.value)
      }
    }
    step()
  })
}
```

完整实现

```javascript
const wrap = (genFn) =>{
	return (...args) => new Promise((resolve, reject) => {
  	const g = genFn(...args)
    function stepF(nextFn){
    	try{
      	const res = nextFn()
      } catch (e){
      	return reject(e)
      }
      if(res.done){
      	return resolve(res.value)
      }
      
      Promise.resolve(res.value).then( v => {
      	stepF( () => g.next(v))
      }, e => {
      	stepF( () => g.throw(e))
      })
    }
    stepF( () => g.next())
  })
}
```


# 模拟instance of

```javascript
function instance_of(left, right) {
  const RP = right.prototype; // 构造函数的原型
  while(true) {
    if (left === null) {
      return false;
    }
    if (left === RP) { // 一定要严格比较
      return true;
    }
    left = left.__proto__; // 沿着原型链重新赋值
  }
}
```


# Object.create(null)与{}

## Object.create实现

```javascript
Object.create = funtion(o){
  var f = function(){};
  f.prototype = o;
  return new f();
}
```

## Object.create(null)输出

```javascript
let a = Object.create(null)
console.log(a）

------------------
{}
no properties
```

## {}输出

```javascript
let o = {}
console.log(o)

--------------------
{}
__proto__:
constructor
hasOwnProperty
isPrototypreOf
...
```

使用`create`创建的对象，没有任何属性，显示`No properties`，我们可以把它当作一个非常**纯净**的map来使用，我们可以自己定义`hasOwnProperty`、`toString`方法，不管是有意还是不小心，我们完全不必担心会将原型链上的同名方法覆盖掉。

```javascript
//Demo1:
var a= {...省略很多属性和方法...};
//如果想要检查a是否存在一个名为toString的属性，你必须像下面这样进行检查：
if(Object.prototype.hasOwnProperty.call(a,'toString')){
    ...
}
//为什么不能直接用a.hasOwnProperty('toString')?因为你可能给a添加了一个自定义的hasOwnProperty
//你无法使用下面这种方式来进行判断,因为原型上的toString方法是存在的：
if(a.toString){}

//Demo2:
var a=Object.create(null)
//你可以直接使用下面这种方式判断，因为存在的属性，都将定义在a上面，除非手动指定原型：
if(a.toString){}
```


# 实现promisify

有的时候，我们需要将 callback 语法的 API 改造成 Promise 语法，为此我们需要一个 promisify 的方法。

因为 callback 语法传参比较明确，最后一个参数传入回调函数，回调函数的第一个参数是一个错误信息，如果没有错误，就是 null，所以我们可以直接写出一个简单的 promisify 方法：

```javascript
function promisify(original) {
    return function (...args) {
        return new Promise((resolve, reject) => {
            args.push(function callback(err, ...values) {
                if (err) {
                    return reject(err);
                }
                return resolve(...values)
            });
            original.call(this, ...args);
        });
    };
}
```

完整的可以参考 [es6-promisif](https://github.com/digitaldesignlabs/es6-promisify/blob/master/lib/promisify.js)


# 实现Promise.all

## 思路

1. 传入的一定是数组
2. Promise.resolve包裹(元素不是 `Promise` 对象，则使用 `Promise.resolve` 转成 `Promise` 对象
3. 全部成功，返回resolve
4. 只要有一个失败，返回reject

## 代码

```javascript
function promiseAll(promises) {
  return new Promise(function(resolve, reject) {
    if (!isArray(promises)) {
      return reject(new TypeError('arguments must be an array'));
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedValues = new Array(promiseNum);
    for (var i = 0; i < promiseNum; i++) {
      (function(i) {
        Promise.resolve(promises[i]).then(function(value) {
          resolvedCounter++
          resolvedValues[i] = value
          if (resolvedCounter == promiseNum) {
            return resolve(resolvedValues)
          }
        }, function(reason) {
          return reject(reason)
        })
      })(i)
    }
  })
}
```


# 实现Promise.race

```javascript
Promise.race = function (promises) {
    return new Promise(function (resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject)
        }
    })
};
```


# 实现Promise.resolve/reject

```javascript
Promise.resolve = function (value) {
    return new Promise(function (resolve, reject) {
        resolve(value);
    })
};
```

```javascript
Promise.reject = function (reason) {
    return new Promise(function (resolve, reject) {
        reject(reason);
    })
};
```

<br>


# 实现Promise.finnaly

```javascript
Promise.prototype.finally = function (callback) {
  let P = this.constructor
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  )
}
```


# 实现Promise

> 参阅这个，层层递进，写得非常好：<https://zhuanlan.zhihu.com/p/58428287>

## 极简内核

1. Promise可以new, 所以它是一个类
2. new Promise 传入一个函数，所以constructor是fn
3. fn中会有resolve，我们需要自己定义\_resolve传进去作为方法，因此有fn(this.\_resolve.bind(this))
4. new Promise的实例还可以.then，因此then是Promise的方法
5. 内部维护一个callback，作为传入fn的管理，在调用resolve的时候全部遍历执行

```javascript
class Promise {
	callbacks = [];
	constructor(fn) {
		// 内部的_resolve绑定为外部调用
		fn(this._resolve.bind(this))
	}
	then(onFulfilled){
		this.callbacks.push(onFulfilled)
	}
	_resolve(value){
		this.callbacks.forEach(fn => fn(value))
	}
}
```

## 链式调用

1. 由于promise可以组成then的链式调用，then方法必然也返回promise的实例
2. promise.then是微任务，并不是立即执行，需要增加state作为状态进行执行或者收集的判断
3. promise.then可以传递值给下一个then，因此需要内部维护结果，增加value值
4. promise.then 的 onFulfilled 可以为空，直接resolve之前的值，否知先收集，再统一执行

```javascript
class Promise {
	callbacks = []
	state = 'pending'
	value = undefined

	constructor(fn) {
		fn(this._resolve.bind(this)
	}
	then(onFulfilled = null){
		// 下面的resolve就是新的实例的this._resolve
		return new Promise(resolve => {
			this._handle({ onFulfilled, resolve })
		})
	}
	_handle(callback){
		if (this.state === 'pending') {
			this.callbacks.push(callback)
			return 
		}
		if (!callback.onFulfilled){
			// 未设置onFulfilled, resolve 结果给下一个then
			callback.resolve(this.value)
			return
		}
		const ret = callback.onFulfilled(this.value)
		callback.resolve(ret)
	}
	_resolve(value) {
		// 先切换状态，这样handle里面的判断不会影响
		this.state = 'fulfilled' 
		this.value = value
		// 一个实例的所有callbacks执行完
		this.callbacks.foreach(callback => this._handle(callback))
	}
}
```

## 错误处理

1. 增加reject
2. onRejected也可以为空
3. 即使是有rejected，那也只是设置状态，之前的callback依然要遍历执行完

```javascript
class Promise{
	state = 'pending'
	callbacks = []
	value = undefined
	constructor(fn){
		fn(this._resolve.bind(this), this._reject.bind(this)
	}
	_resolve(value){
		this.state = 'fullfiled'
		this.value = value
		this.callbacks.foreach(callback => this._handle(callback))
	}
	_reject(error){
		this.state = 'rejected'
		this.value = error
		this.callbacks.foreach(callback => this._handle(callback))
	}
	_handle(callback){
		if( this.state === 'pending') {
				this.callbacks.push(callback)
				return
		}
		
		let cb = this.state === 'fullfilled' ? callback.onFulfilled : callback.onRejected
		if (!cb) {
			// 没定义 onFulfilled 或者 onRejected
			cb = this.state === 'fullfilled' ? callback.resolve : callback.reject
			cb(this.value)
			return
		}
		const ret = cb()
		cb(ret)
	}
	then(onFulfilled = null, onRejected = null){
		return new Promise((resolve, reject) => {
				this._handle(onFulfilled, onRejected, resolve, reject)	
			}
	}
}
```

## catch

1. catch也是接收一个函数
2. catch会挂载一个onError, 然后注册到then作为下一个微任务执行
3. **为什么class是一个callbacks数组。这里就是callback数组会有两个callback了。一个是then注册的，一个是catch注册的。其他的then都是注册一个新的实例**

```javascript
class Promise{
	state = 'pending'
	callbacks = []
	value = undefined
	constructor(fn){
		fn(this._resolve.bind(this), this._reject.bind(this)
	}
	_resolve(value){
		this.state = 'fullfiled'
		this.value = value
		this.callbacks.foreach(callback => this._handle(callback))
	}
	_reject(error){
		this.state = 'rejected'
		this.value = error
		this.callbacks.foreach(callback => this._handle(callback))
	}
	_handle(callback){
		if( this.state === 'pending') {
				this.callbacks.push(callback)
				return
		}
		
		let cb = this.state === 'fullfilled' ? callback.onFulfilled : callback.onRejected
		if (!cb) {
			// 没定义 onFulfilled 或者 onRejected
			cb = this.state === 'fullfilled' ? callback.resolve : callback.reject
			cb(this.value)
			return
		}
		const ret = cb()
		cb(ret)
	}
	then(onFulfilled = null, onRejected = null){
		// 每次 then 都会创建新的 Promise 实例
		return new Promise((resolve, reject) => {
				this._handle(onFulfilled, onRejected, resolve, reject)	
			}
	}
	catch(onError){
		// onFulfill为null，仅在onReject里面被调用
		return this.then(null, onError)
	}
}
```

## 实现静态方法

1. Promise.resolve对于一个promise实例，会直接返回该实例，因为它自身就有then方法
2. Promise.resolve对于一个对象属性then为函数的，会创建新Promise实例，把这个函数then 传入resolve
3. Promise.resolve对于一个值(即使是函数），会创建新Promise实例，并resolve这个值

{% hint style="info" %}
thenable函数为：A = { then : function(onFulfilled, onRejected){...}}
{% endhint %}

```javascript
class Promise {
	static resolve(value) {
		if(value && value instanceof Promise){
			return value
		} else if( value && typeof value === 'object' && typeof value.then === 'function') {
			const then = value.then
			return new Promise( resolve => then(resolve))
		} else {
			return new Promise(resolve => resolve(value))
		} 
	}
}
```

## 实现finally

1. finally 不管是onFulfilled 还是 onRejected 都会被调用，因为内部注册到then 上要挂载 两个函数

```javascript
class Promise {
	static resolve(value) {
		if(value && value instanceof Promise){
			return value
		} else if( value && typeof value === 'object' && typeof value.then === 'function') {
			const then = value.then
			return new Promise( resolve => then(resolve))
		}  else {
			return new Promise(resolve => resolve())
		}
	}
	static reject(value) {
    if (value && typeof value === 'object' && typeof value.then === 'function') {
      let then = value.then;
      return new Promise((resolve, reject) => {
        then(reject);
      });

    } else {
      return new Promise((resolve, reject) => reject(value));
    }
  }
  finally(onDone) {
    const Promise = this.constructor;
    return this.then(
      value => Promise.resolve(onDone()).then(() => value),
      reason => Promise.resolve(onDone()).then(() => { throw reason })
    );
  }
}
```


# 实现parseInt

当参数 radix 的值为 0，或没有设置该参数时，parseInt() 会根据 string 来判断数字的基数。

举例，如果 string 以 "0x" 开头，parseInt() 会把 string 的其余部分解析为十六进制的整数。如果 string 以 0 开头，那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。如果 string 以 1 \~ 9 的数字开头，parseInt() 将把它解析为十进制的整数。

只有字符串中的第一个数字会被返回。

留意以下测试用例

```javascript
/ 以下例子均返回15:
console.log(_parseInt("F", 16));
console.log(_parseInt("17", 8));
console.log(_parseInt("15", 10));
console.log(_parseInt(15.99, 10));
console.log(_parseInt("FXX123", 16));
console.log(_parseInt("1111", 2));
console.log(_parseInt("15*3", 10));
console.log(_parseInt("12", 13));

// 以下例子均返回 NaN:
console.log(_parseInt("Hello", 8)); // Not a number at all
console.log(_parseInt("546", 2));   // Digits are not valid for binary representations

// 以下例子均返回 -15：
console.log(_parseInt("-F", 16));
console.log(_parseInt("-0F", 16));
console.log(_parseInt("-0XF", 16));
console.log(_parseInt(-15.1, 10));
console.log(_parseInt(" -17", 8));
console.log(_parseInt(" -15", 10));
console.log(_parseInt("-1111", 2));
console.log(_parseInt("-15e1", 10));
console.log(_parseInt("-12", 13));
// 下例中也全部返回 17，因为输入的 string 参数以 "0x" 开头时作为十六进制数字解释，而第二个参数假如经过 Number 函数转换后为 0 或 NaN，则将会忽略。
console.log(_parseInt("0x11", 16));
console.log(_parseInt("0x11", 0));
console.log(_parseInt("0x11"));

// 下面的例子返回 224
console.log(_parseInt("0e0",16));
```

## 完整实现

```javascript
function _parseInt(string, radix) {
  if (typeof string !== "string" && typeof string !== "number") return NaN;
  if (
    radix &&
    (typeof radix !== "number" ||
    /^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$/.test(radix) || // 识别小数
      radix > 36 ||
      radix < 2)
  )
    return NaN;
  string = String(string);
  var rexp =
      radix === 10 ? /(-?)([0]?)([0-9]+)/ : /(-?)([0]?[Xx]?)([0-9a-fA-F]+)/,
    a = string.match(rexp),
    sign = a[1],
    rawRadix = a[2],
    rawNum = a[3],
    result = 0,
    strArr = rawNum.split(""), // 分割每个字符
    len = strArr.length,
    numArr = [];
  if (a && !radix) {
    if (rawRadix.toUpperCase() === "0X") {
      radix = 16;
    } else if (rawRadix === "0") {
      radix = 8;
    } else {
      radix = 10;
    }
  }
  for (var i = 0; i < len; i++) {
    var num;
    var charCode = strArr[i].toUpperCase().charCodeAt(0);
    if (radix <= 36 && radix >= 11) {
      if (charCode >= 65 && charCode <= 90) {
        // a - z => 10 - 35
        num = charCode - 55;
      } else {
        // 只会是数字了 -48 会转换为数字 0-9 => 48-57
        num = charCode - 48;
      }
    } else {
      num = charCode - 48;
    }
    if (num < radix) {
      numArr.push(num);
    } else {
      return NaN;
    }
  }
  if (numArr.length > 0) {
    numArr.forEach(function(item, j) {
      result += item * Math.pow(radix, numArr.length - j - 1);
    });
  }
  if (sign === "-") {
    result = -result;
  }
  return result;
}

```

## 简单实现

```javascript
function _parseInt(str, radix) {
    let str_type = typeof str;
    let res = 0;
    if (str_type !== 'string' && str_type !== 'number') {
        // 如果类型不是 string 或 number 类型返回NaN
        return NaN
    }

    // 字符串处理
    str = String(str).trim().split('.')[0]
    let length = str.length;
    if (!length) {
        // 如果为空则返回 NaN
        return NaN
    }

    if (!radix) {
        // 如果 radix 为0 null undefined
        // 则转化为 10
        radix = 10;
    }
    if (typeof radix !== 'number' || radix < 2 || radix > 36) {
        return NaN
    }

    for (let i = 0; i < length; i++) {
        let arr = str.split('').reverse().join('');
        res += Math.floor(arr[i]) * Math.pow(radix, i)
    }

    return res;
}
```

剔除其他判断的最核心实现

```javascript
function _parseInt(str, radix) {
    const intStr = String(str).trim().split('.')[0]
    // 翻转字符
    const intStrR = intStr.split('').reverse().join('');
    for (let i = 0; i < intStr.length; i++) {
        res += Math.floor(intStrRr[i]) * Math.pow(radix, i)
    }

    return res;
}
```


# 实现foreach

原生的 forEach 函数没有跳出功能，并且没有返回值

**这里新增两个创新功能**

1. 跳出功能
2. 对象也可 forEach




---

[Next Page](/llms-full.txt/1)

