2023-11-06 16:05:27 +08:00
|
|
|
|
---
|
|
|
|
|
title: 码场悟道
|
|
|
|
|
categories:
|
2023-12-26 10:59:02 +08:00
|
|
|
|
- CS
|
2023-11-06 16:05:27 +08:00
|
|
|
|
status: doing
|
2023-12-26 12:54:52 +08:00
|
|
|
|
abbrlink: 47478
|
2023-11-06 16:05:27 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 模板引擎
|
|
|
|
|
|
|
|
|
|
严格的模板引擎的定义,输入模板字符串 + 数据,得到渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各种各样,但从定义上来说都是差不多的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。倒是前端的后续更新是字符串模板引擎的软肋,因为用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。所以这样的模板引擎如今在纯前端情境下已经不再是好的选择,意义更多是在于方便前后端共用模板。
|
|
|
|
|
|
|
|
|
|
# 古老数据渲染 vm 的方式
|
|
|
|
|
|
|
|
|
|
这种写法,弊端太多了,玩具车
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
|
<title>Document</title>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<ul id="app"></ul>
|
|
|
|
|
</body>
|
|
|
|
|
<script>
|
|
|
|
|
var arr = [
|
|
|
|
|
{ name: "小明", age: 11, sex: "男" },
|
|
|
|
|
{ name: "小红", age: 22, sex: "女" },
|
|
|
|
|
];
|
|
|
|
|
var listDOM = document.getElementById("app");
|
|
|
|
|
arr.forEach(function (item) {
|
|
|
|
|
let _li = document.createElement("li");
|
|
|
|
|
_li.innerText = item.name;
|
|
|
|
|
listDOM.appendChild(_li);
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# mustache 原理
|
|
|
|
|
|
|
|
|
|
- 1、先把模板字符串编译成 tokens(代号)
|
|
|
|
|
- 2、根据 tokens,结合数据渲染成 dom
|
|
|
|
|
|
|
|
|
|
> 本质上,tokens 是一个 js 嵌套数组没事模板字符串 js 的表示,他是`抽象语法树`,`虚拟节点`的开山鼻祖
|
|
|
|
|
|
|
|
|
|
假设有这么一个模板字符串
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<h1>我买了一个{{thing}},好{{mood}}啊</h1>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
会编译成 tokens,如下:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// 这里面每一个数组行都是一个 token,组起来就是 tokens
|
|
|
|
|
// html 标签也会被看成纯文本
|
|
|
|
|
[
|
|
|
|
|
["text", "<h1>我买了一个"],
|
|
|
|
|
["name", "thing"],
|
|
|
|
|
["text", "好"],
|
|
|
|
|
["name", "mood"],
|
|
|
|
|
["text", "啊</h1>"],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
当模板存在循环式,带层级嵌套,如下:
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<div>
|
|
|
|
|
<ul>
|
|
|
|
|
{{#arr}}
|
|
|
|
|
<li>{{.}}</li>
|
|
|
|
|
{{/arr}}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
会被编译成
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
[
|
|
|
|
|
["text", "<div><ul>"],
|
|
|
|
|
[
|
|
|
|
|
"#",
|
|
|
|
|
"arr",
|
|
|
|
|
[
|
|
|
|
|
["text", "li"],
|
|
|
|
|
["name", "."],
|
|
|
|
|
["text", "</li>"],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
["text", "</ul></div>"],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
如果是双重循环,带层级嵌套继续加一层,例如:
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<div>
|
|
|
|
|
<ol>
|
|
|
|
|
{{#students}}
|
|
|
|
|
<li>
|
|
|
|
|
学生{{item.name}}的爱好是
|
|
|
|
|
<ol>
|
|
|
|
|
{{#item.hobbies}}
|
|
|
|
|
<li>{{.}}</li>
|
|
|
|
|
{{/#item.hobbies}}
|
|
|
|
|
</ol>
|
|
|
|
|
</li>
|
|
|
|
|
{{/#students}}
|
|
|
|
|
</ol>
|
|
|
|
|
</div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
会被编译成
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
[
|
|
|
|
|
["text", "<div><ol>"],
|
|
|
|
|
[
|
|
|
|
|
"#",
|
|
|
|
|
"students",
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
[["text", "<li>学生"], ["name", "name"], ["text", "的爱好是<ol>"], ["#", "hobbies", null, null], [
|
|
|
|
|
['text','<li>'],
|
|
|
|
|
['name','.'],
|
|
|
|
|
['text','</li>']
|
|
|
|
|
]],
|
|
|
|
|
['text','<ol></li>'],
|
|
|
|
|
]],
|
|
|
|
|
['text','</ol></div>']
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> 在`mustache.js`中完成上述这一过程的函数`parseTemplate`,可以去找源代码看
|
|
|
|
|
|
|
|
|
|
## tokens 生成算法
|
|
|
|
|
|
|
|
|
|
用简单的模板字符串举例:
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
有一个指针往右遍历,从`我`开始,遍历到`啊`结束,如下:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* Step1:指针位置 = 1
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* Step2:指针位置 = 2
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* Step3:指针位置 = 4
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* Step4:指针位置 = 11
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* Step5:指针位置 = 15
|
|
|
|
|
*
|
|
|
|
|
* 我买了一个{{thing}},好{{mood}}啊
|
|
|
|
|
* ↑
|
|
|
|
|
*
|
|
|
|
|
* ... while(指针位置 >= 模板字符串.length)
|
|
|
|
|
*
|
|
|
|
|
* /
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- Step1:
|
|
|
|
|
|
|
|
|
|
指针右移 1 个长度,以指针位置切割,字符串被分成`我`+`买了一个{{thing}},好{{mood}}啊`
|
|
|
|
|
|
|
|
|
|
- Step2:
|
|
|
|
|
|
|
|
|
|
指针右移 1 个长度,以指针位置切割,字符串被分成`我买`+`了一个{{thing}},好{{mood}}啊`
|
|
|
|
|
|
|
|
|
|
- Step3:
|
|
|
|
|
|
|
|
|
|
第一次遇到,通过 `indexOf("{{") == 0` 判断
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// 标记为 text 放到 tokens 中
|
|
|
|
|
token.push(["text", "我买了一个"]); // tokens: [['text', '我买了一个']]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
结束,此时指针位置 = 4
|
|
|
|
|
|
|
|
|
|
- Step4:
|
|
|
|
|
|
|
|
|
|
指针右移 2 个长度,跳过`{{`,暂存此时`pos_last = 6`
|
|
|
|
|
|
|
|
|
|
此时,右边字符串(尾字符串)`thing}},好{{mood}}啊`
|
|
|
|
|
|
|
|
|
|
右移 5 个长度,识别`模板内部数据对象`:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
substring(post_last, 6 + 5); // thing 5个长度
|
|
|
|
|
// 标记为 name 放到 tokens 中
|
|
|
|
|
token.push(["name", "thing"]); // tokens: [['text', '我买了一个']], ['name', 'thing']]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
结束,此时指针位置 = 11
|
|
|
|
|
|
|
|
|
|
- Step5:
|
|
|
|
|
|
|
|
|
|
> 遇到 `}}`,通过 `indexOf("}}") == 0`判断
|
|
|
|
|
|
|
|
|
|
指针右移 2 个长度,跳过`}}`,暂存此时`post_last = 13`,继续右移 2 个长度
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
substring(post_last, 13 + 2); // ,好 2个长度
|
|
|
|
|
// 标记为 text 放到 tokens 中
|
|
|
|
|
token.push(["text", ",好"]); // tokens: [['text', '我买了一个']], ['name', 'thing'],['text', ',好' ]]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> 第二次,遇到 `{{`
|
|
|
|
|
|
|
|
|
|
剩下循环执行就行了,这个过程,我们可以称作`扫描 Scan`
|
|
|
|
|
|
|
|
|
|
## 扫描器 Scanner
|
|
|
|
|
|
|
|
|
|
新建一个 `Scanner.js`,用来扫描模板字符串,实现上面的原理
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
* 模板字符串扫描器
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
class Scanner {
|
|
|
|
|
constructor(templ) {
|
|
|
|
|
this.templ = templ; // 模板字符串
|
|
|
|
|
this.tail = templ; // 尾字符串
|
|
|
|
|
this.pPos = 0; // 指针位置
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 指针跳过模板标签
|
|
|
|
|
* @param {模板语法包围标签} tag
|
|
|
|
|
*/
|
|
|
|
|
jumpTag(tag) {
|
|
|
|
|
if (this.tail.indexOf(tag) === 0) {
|
|
|
|
|
this.pPos += tag.length; // 指针右移 tag.length 个长度
|
|
|
|
|
this.tail = this.templ.substring(this.pPos); // 尾字符串更新
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 指针遇见模板标签 {{
|
|
|
|
|
* @param {模板语法包围标签} tag
|
|
|
|
|
*/
|
|
|
|
|
missTag(tag) {
|
|
|
|
|
let pPos_last = this.pPos;
|
|
|
|
|
while (!this.eof() && this.tail.indexOf(tag) !== 0) {
|
|
|
|
|
this.pPos++;
|
|
|
|
|
this.tail = this.templ.substring(this.pPos);
|
|
|
|
|
}
|
|
|
|
|
return this.templ.substring(pPos_last, this.pPos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eof() {
|
|
|
|
|
return this.pPos >= this.templ.length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 分析器 Parser
|
|
|
|
|
|
|
|
|
|
调用`Scanner.js`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let tmpl = `我买了一个{{thing}},好{{mood}}啊`;
|
|
|
|
|
// 编译 模板字符串 => tokens
|
|
|
|
|
const Parser = {
|
|
|
|
|
createTokens: (tmpl) => {
|
|
|
|
|
let scanner = new Scanner(tmpl);
|
|
|
|
|
let tokens = [];
|
|
|
|
|
// scanner 循环执行
|
|
|
|
|
while (!scanner.eof()) {
|
|
|
|
|
ctx = scanner.missTag("{{"); // 返回 头字符串
|
|
|
|
|
if (ctx != "") {
|
|
|
|
|
tokens.push(["text", ctx]);
|
|
|
|
|
}
|
|
|
|
|
scanner.jumpTag("{{"); // 跳过 模板字符
|
|
|
|
|
ctx = scanner.missTag("}}"); // 返回 {{ x }}
|
|
|
|
|
if (ctx != "") {
|
|
|
|
|
tokens.push(["name", ctx]);
|
|
|
|
|
}
|
|
|
|
|
scanner.jumpTag("}}");
|
|
|
|
|
}
|
|
|
|
|
return tokens;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
console.log(Parser.createTokens(tmpl));
|
|
|
|
|
// 输出,非常的 奈一丝
|
|
|
|
|
// ["text", "我买了一个"]
|
|
|
|
|
// ["name", "thing"]
|
|
|
|
|
// ["text", ",好"]
|
|
|
|
|
// ["name", "mood"]
|
|
|
|
|
// ["text", "啊"]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 扫描器 Scanner 增强
|
|
|
|
|
|
|
|
|
|
上面的`Parser`只能识别`{{`和`}}`,如果模板语法复杂一点,比如加入 `{{#list}}...{{/list}}`,需要增强`Parser`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const template = `
|
|
|
|
|
哈哈哈
|
|
|
|
|
{{#students}}
|
|
|
|
|
我买了一个 {{ thing }},好{{mood}}啊{{a}}
|
|
|
|
|
{{item.name}}
|
|
|
|
|
{{/students}}
|
|
|
|
|
`;
|
|
|
|
|
const Parser = {
|
|
|
|
|
createTokens: (tmpl) => {
|
|
|
|
|
let scanner = new Scanner(tmpl);
|
|
|
|
|
let tokens = [];
|
|
|
|
|
let ctx = "";
|
|
|
|
|
// scanner 循环执行
|
|
|
|
|
while (!scanner.eof()) {
|
|
|
|
|
ctx = scanner.missTag("{{"); // 返回 头字符串
|
|
|
|
|
if (ctx != "") {
|
|
|
|
|
tokens.push(["text", ctx]);
|
|
|
|
|
}
|
|
|
|
|
scanner.jumpTag("{{"); // 跳过 模板字符
|
|
|
|
|
ctx = scanner.missTag("}}"); // 返回 {{ x }}
|
|
|
|
|
if (ctx != "") {
|
|
|
|
|
switch (ctx[0]) {
|
|
|
|
|
case "#":
|
|
|
|
|
tokens.push(["#", ctx.substr(1)]); // {{# x }}
|
|
|
|
|
break;
|
|
|
|
|
case "/":
|
|
|
|
|
tokens.push(["/", ctx.substr(1)]);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
tokens.push(["name", ctx]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
scanner.jumpTag("}}");
|
|
|
|
|
}
|
|
|
|
|
return tokens;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
console.log(Parser.createTokens(template));
|
|
|
|
|
|
|
|
|
|
// 输出
|
|
|
|
|
// ["text", "↵ 哈哈哈↵ "]
|
|
|
|
|
// ["#", "students"]
|
|
|
|
|
// ["text", "↵ 我买了一个 "]
|
|
|
|
|
// ["name", " thing "]
|
|
|
|
|
// ["text", ",好"]
|
|
|
|
|
// ["name", "mood"]
|
|
|
|
|
// ["text", "啊"]
|
|
|
|
|
// ["name", "a"]
|
|
|
|
|
// ["text", "↵ "]
|
|
|
|
|
// ["name", "item.name"]
|
|
|
|
|
// ["text", "↵ "]
|
|
|
|
|
// ["/", "students"]
|
|
|
|
|
// ["text", "↵ "]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 栈队列算法
|
|
|
|
|
|
|
|
|
|
上一步最后的输出,只有单层嵌套,如果是两层嵌套怎么办?
|
|
|
|
|
|
|
|
|
|
例如模板语法如下:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
var template = `
|
|
|
|
|
哈哈哈
|
|
|
|
|
{{#students}}
|
|
|
|
|
{{#stu}}
|
|
|
|
|
{{stu.name}}买了一个 {{ thing }},好{{mood}}啊{{a}}
|
|
|
|
|
{{/stu}}
|
|
|
|
|
{{item.name}}
|
|
|
|
|
{{/students}}
|
|
|
|
|
`;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
经过`Parser`处理得到:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* ["text", "↵ 哈哈哈↵ "]
|
|
|
|
|
* ["#", "students"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["#", "stu"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["name", "stu.name"]
|
|
|
|
|
* ["text", "买了一个 "]
|
|
|
|
|
* ["name", " thing "]
|
|
|
|
|
* ["text", ",好"]
|
|
|
|
|
* ["name", "mood"]
|
|
|
|
|
* ["text", "啊"]
|
|
|
|
|
* ["name", "a"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["/", "stu"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["name", "item.name"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["/", "students"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
*
|
|
|
|
|
* /
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
此时`students`和`stu`都是`#`标记,我们需要利用算法处理他们的嵌套结构,处理成大约如下这样的结构:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* ["text", "↵ 哈哈哈↵ "]
|
|
|
|
|
* Array(3)
|
|
|
|
|
* "#"
|
|
|
|
|
* "students"
|
|
|
|
|
* Array(5)
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["#", "stu", Array(9)]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["name", "item.name"]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
* ["text", "↵ "]
|
|
|
|
|
*
|
|
|
|
|
* /
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 常用工具类
|
|
|
|
|
|
|
|
|
|
## 递归
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
* {string} dir 递归根目录
|
|
|
|
|
* {object} list 暂存参数
|
|
|
|
|
*/
|
|
|
|
|
const deep = async (dir, list = []) => {
|
|
|
|
|
const dirs = await fs.promises.readdir(dir)
|
|
|
|
|
for (let i = 0; i < dirs.length; i++) {
|
|
|
|
|
const item = dirs[i]
|
|
|
|
|
const itemPath = path.join(dir, item)
|
|
|
|
|
const isDir = fs.statSync(itemPath).isDirectory()
|
|
|
|
|
isDir ? await deep(itemPath, list) : list.push(itemPath)
|
|
|
|
|
}
|
|
|
|
|
return list
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 自增id短码
|
|
|
|
|
|
|
|
|
|
用于连接分享
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
const createAscString = (id) => {
|
|
|
|
|
const dictionary = [
|
|
|
|
|
"0123456789",
|
|
|
|
|
"abcdefghigklmnopqrstuvwxyz",
|
|
|
|
|
"ABCDEFGHIGKLMNOPQRSTUVWXYZ",
|
|
|
|
|
];
|
|
|
|
|
let chars = dictionary.join("").split(""),
|
|
|
|
|
radix = chars.length,
|
|
|
|
|
qutient = 1000 * 1000 * 9999 + +id,
|
|
|
|
|
arr = [];
|
|
|
|
|
while (qutient) {
|
|
|
|
|
mod = qutient % radix;
|
|
|
|
|
qutient = (qutient - mod) / radix;
|
|
|
|
|
arr.unshift(chars[mod]);
|
|
|
|
|
}
|
|
|
|
|
return arr.join("");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
console.log(createAscString(100000000));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 手动实现 eventBus
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
export default class EventBus {
|
|
|
|
|
constructor() {
|
|
|
|
|
// key-value : eventName-date
|
|
|
|
|
this.callbacks = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 监听事件
|
|
|
|
|
* @param {事件名} eventName
|
|
|
|
|
* @param {回调函数} callback
|
|
|
|
|
*/
|
|
|
|
|
on(eventName, callback) {
|
|
|
|
|
this.checkType(eventName).callbacks[eventName]
|
|
|
|
|
? callback(this.callbacks[eventName])
|
|
|
|
|
: this.error(`The event has not been declared`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 注册一个事件
|
|
|
|
|
* @param {事件名} eventName
|
|
|
|
|
* @param {传递的对象} data
|
|
|
|
|
*/
|
|
|
|
|
emit(eventName, data) {
|
|
|
|
|
this.checkType(eventName).callbacks[eventName] = data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 注销事件,不传参数默认注销全部事件
|
|
|
|
|
* @param {事件名} eventName
|
|
|
|
|
*/
|
|
|
|
|
off(eventName) {
|
|
|
|
|
eventName
|
|
|
|
|
? this.checkType(eventName).removeEvent(eventName)
|
|
|
|
|
: this.emptyEvent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 移出事件
|
|
|
|
|
* @param {事件名} eventName
|
|
|
|
|
*/
|
|
|
|
|
removeEvent(eventName) {
|
|
|
|
|
Reflect.deleteProperty(this.callbacks, eventName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 清空全部事件
|
|
|
|
|
*/
|
|
|
|
|
emptyEvent() {
|
|
|
|
|
this.callbacks = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 参数类型校验
|
|
|
|
|
* @param {参数} param
|
|
|
|
|
* @param {合法的类型} validType
|
|
|
|
|
*/
|
|
|
|
|
checkType(param, validType = "string") {
|
|
|
|
|
if (typeof param !== validType)
|
|
|
|
|
this.error(`(param, ${param}) should be of ${validType} type`);
|
|
|
|
|
return this; // 缅怀jQ链式调用
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 错误提示
|
|
|
|
|
* @param {提示文字} text
|
|
|
|
|
*/
|
|
|
|
|
error(text) {
|
|
|
|
|
throw new Error(text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
调用
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// 省略import
|
|
|
|
|
const eventBus = new EventBus();
|
|
|
|
|
eventBus.emit("login", [{ a: 1, d: 2 }]);
|
|
|
|
|
eventBus.on(123, (d) => console.log(d)); // [{...}]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 判断对象是否有某个 key
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
let obj = { alias: "es6" };
|
|
|
|
|
"alias" in obj; // true
|
|
|
|
|
Reflect.has(obj, "alias"); // true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 浏览器
|
|
|
|
|
|
|
|
|
|
## 版本信息
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
window.navigator.userAgent;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 兼容事件绑定
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
/*
|
|
|
|
|
兼容低版本IE,ele为需要绑定事件的元素,
|
|
|
|
|
eventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
function addEvent(ele, eventName, fun) {
|
|
|
|
|
ele.addEventListener
|
|
|
|
|
? ele.addEventListener(eventName, fun, false)
|
|
|
|
|
: ele.attachEvent("on" + eventNme, fun);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 数组对象
|
|
|
|
|
|
|
|
|
|
## reduce
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
var arr = [1, 2, 3, 4];
|
|
|
|
|
var sum = arr.reduce(function(prev, cur, index, arr) {
|
|
|
|
|
console.log(prev, cur, index);
|
|
|
|
|
return prev + cur;
|
|
|
|
|
},0) //注意这里设置了初始值
|
|
|
|
|
console.log(arr, sum);
|
|
|
|
|
|
|
|
|
|
// 求和
|
|
|
|
|
const sum = arr.reduce((p,c) => p+c)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 对象内部根据 key 对 value 进行排序,取前 3
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
let datasource = [
|
|
|
|
|
{ price: 1, alias: "watermelon" },
|
|
|
|
|
{ price: 3, alias: "orange" },
|
|
|
|
|
{ price: 2, alias: "banana" },
|
|
|
|
|
{ price: 4, alias: "apple" },
|
|
|
|
|
];
|
|
|
|
|
// 降序排列
|
|
|
|
|
let compare = (key) => (a, b) => b[key] - a[key];
|
|
|
|
|
let sorted = datasource
|
|
|
|
|
.sort(compare("price"))
|
|
|
|
|
.slice(0, 3)
|
|
|
|
|
.map((i) => i["alias"]);
|
|
|
|
|
// 返回 ["apple", "orange", "banana"]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 随机字符串
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
const getRandomRangeNum = (len = 32) => {
|
|
|
|
|
// 略去不宜辨识字符
|
|
|
|
|
let dictionary = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz";
|
|
|
|
|
let maxPos = dictionary.length;
|
|
|
|
|
let res = "";
|
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
|
res += dictionary.charAt(Math.floor(Math.random() * maxPos));
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 类型检测
|
|
|
|
|
|
|
|
|
|
Q:使用`typeof foo === "object"`检测`foo`是否为对象有什么缺点?如何避免?
|
|
|
|
|
|
|
|
|
|
A:用 `typeof` 是否能准确判断一个对象变量,答案是否定的,`null` 的结果也是 `object`,`Array` 的结果也是 `object`,有时候我们需要的是 "纯粹" 的 `object` 对象
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Object.prototype.toString.call(obj) === "[object Object]";
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 倒计时
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
class Countdown {
|
|
|
|
|
constructor(startNum, endNum, interval) {
|
|
|
|
|
[this.startNum, this.endNum, this.interval] = [startNum, endNum, interval];
|
|
|
|
|
}
|
|
|
|
|
execute() {
|
|
|
|
|
var timer = setTimeout(() => {
|
|
|
|
|
if (this.startNum >= this.endNum) {
|
|
|
|
|
console.log(this.startNum);
|
|
|
|
|
this.startNum -= 1;
|
|
|
|
|
this.execute();
|
|
|
|
|
} else {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
}
|
|
|
|
|
}, this.interval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 实例化调用
|
|
|
|
|
var countdown = new Countdown(5, 0, 1000).execute();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 范围随机数
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 能取到 min,取不到 max
|
|
|
|
|
function getRandomRangeNum(min, max) {
|
|
|
|
|
return min + Math.floor(Math.random() * (max - min));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 获取当前月的天数
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
const getCurMonthDays = new Date(
|
|
|
|
|
new Date().getFullYear(),
|
|
|
|
|
new Date().getMonth() + 1,
|
|
|
|
|
0
|
|
|
|
|
).getDate();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 对象小操作
|
|
|
|
|
|
|
|
|
|
## 去虚假值
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let arr4 = ["小明", "小蓝", "", false, " ", undefined, null, 0, NaN, true];
|
|
|
|
|
console.log(arr4.filter(Boolean)); // => ['小明', '小蓝', ' ', true]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 头尾插入
|
|
|
|
|
|
|
|
|
|
效率比 `unshift()` 高
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let arr = [1, 2, 3];
|
|
|
|
|
// 头插入
|
|
|
|
|
["haha"].concat(arr);
|
|
|
|
|
// 尾插入
|
|
|
|
|
arr.concat(["haha"]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 删除属性
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
function deleteA(obj) {
|
|
|
|
|
delete obj.A;
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用解构赋值
|
|
|
|
|
const deleteA = ({ A, ...rest } = {}) => rest;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 生产、加工、消费分离
|
|
|
|
|
|
|
|
|
|
- 从接口拿数据到视图 fetch api
|
|
|
|
|
- 加工 computed
|
|
|
|
|
- 消费 v-for
|
|
|
|
|
|
|
|
|
|
# 元数据
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
import "reflect-metadata"; // npm install reflect-metadata
|
|
|
|
|
|
|
|
|
|
function Role(name: string): ClassDecorator {
|
|
|
|
|
return (target) => {
|
|
|
|
|
Reflect.defineMetadata("role", name, target);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Role("admin")
|
|
|
|
|
class Post {}
|
|
|
|
|
|
|
|
|
|
const metadata = Reflect.getMetadata("role", Post);
|
|
|
|
|
|
|
|
|
|
Reflect.set(Post, "role2", metadata);
|
|
|
|
|
|
|
|
|
|
console.log(Reflect.get(Post, "role2")); // admin
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 防抖与节流
|
|
|
|
|
|
|
|
|
|
在页面上监听诸如`scroll`(页面滚动),`mousemove`(鼠标移动) ,`keydown`, `keyup`, `keypress`(按下键盘)等等一系列事件的时候,我们并不希望频繁的触发这类监听,尤其当请求非常消耗资源时,这种操作会导致服务器性能急剧下降。
|
|
|
|
|
|
|
|
|
|
## Debounce
|
|
|
|
|
|
|
|
|
|
把触发非常频繁的事件合并成一次延迟执行,如果对监听函数使用 100ms 的容忍时间,那么时间在第 3.1s 的时候执行
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 默认延时100ms
|
|
|
|
|
function debounce(func, dealy = 100) {
|
|
|
|
|
let timer;
|
|
|
|
|
return function () {
|
|
|
|
|
// 暂存this和参数
|
|
|
|
|
let _this = this;
|
|
|
|
|
let args = arguments;
|
|
|
|
|
// 清除定时器,确保不执行func
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
timer = setTimeout(function () {
|
|
|
|
|
func.apply(_this, args);
|
|
|
|
|
}, dealy);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// 执行函数
|
|
|
|
|
function handler() {
|
|
|
|
|
console.log(`delay 100ms ,then handle`);
|
|
|
|
|
}
|
|
|
|
|
// dom添加监听
|
|
|
|
|
document
|
|
|
|
|
.querySelector("#someNode")
|
|
|
|
|
.addEventListener("scroll", debounce(handler));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Throttle
|
|
|
|
|
|
|
|
|
|
固定函数执行的速率,即所谓的“节流”。设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件。
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function throttle(func, delay) {
|
|
|
|
|
let statTime = 0;
|
|
|
|
|
return function () {
|
|
|
|
|
let currentTime = +new Date();
|
|
|
|
|
if (currentTime - statTime > delay) {
|
|
|
|
|
func.apply(this, arguments);
|
|
|
|
|
statTime = currentTime;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// 执行函数
|
|
|
|
|
function resizeHandler() {
|
|
|
|
|
console.log(`resize`);
|
|
|
|
|
}
|
|
|
|
|
// window添加监听
|
|
|
|
|
window.onresize = throttle(resizeHandler, 300);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# this 指向
|
|
|
|
|
|
|
|
|
|
## 全局环境
|
|
|
|
|
|
|
|
|
|
全局环境下,this 始终指向全局对象(window),无论是否严格模式
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
console.log(this === window); // true
|
|
|
|
|
this.a = 37;
|
|
|
|
|
console.log(window.a); // 37
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 函数上下文调用
|
|
|
|
|
|
|
|
|
|
- 非严格模式
|
|
|
|
|
|
|
|
|
|
没有被上一级的对象所调用, this 默认指向全局对象 window
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function f1() {
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
f1() === window; // true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 严格模式
|
|
|
|
|
|
|
|
|
|
this 指向 undefined
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function f2() {
|
|
|
|
|
"use strict"; // 这里是严格模式
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
f2() === undefined; // true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 箭头函数
|
|
|
|
|
|
|
|
|
|
> 箭头函数中,call()、apply()、bind()方法无效
|
|
|
|
|
|
|
|
|
|
在全局代码中,箭头函数被设置为全局对象,总之箭头函数不改变 this 指向
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
var globalObject = this;
|
|
|
|
|
var foo = () => this;
|
|
|
|
|
console.log(foo() === globalObject); // true
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
箭头函数作为对象的方法使用,指向全局 window 对象
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
var obj = {
|
|
|
|
|
i: 10,
|
|
|
|
|
b: () => console.log(this.i, this),
|
|
|
|
|
c: function () {
|
|
|
|
|
console.log(this.i, this);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
obj.b(); // undefined window{...}
|
|
|
|
|
obj.c(); // 10 Object {...}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
箭头函数可以让 this 指向固化,这种特性很有利于封装回调函数
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象
|
|
|
|
|
var handler = {
|
|
|
|
|
id: "123456",
|
|
|
|
|
|
|
|
|
|
init: function () {
|
|
|
|
|
document.addEventListener(
|
|
|
|
|
"click",
|
|
|
|
|
(event) => this.doSomething(event.type),
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
doSomething: function (type) {
|
|
|
|
|
console.log("Handling " + type + " for " + this.id);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# call, apply, bind 与 es6
|
|
|
|
|
|
|
|
|
|
js 的函数继承于`Function.prototype`对象,因此每个函数都会有 apply、call、bind 方法
|
|
|
|
|
|
|
|
|
|
> call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。
|
|
|
|
|
|
|
|
|
|
`call, apply, bind`改变函数中 `this 指向` 的三兄弟,把`this`绑定到第一个参数对象上
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function displayHobbies(...hobbies) {
|
|
|
|
|
console.log(`${this.name} likes ${hobbies.join(", ")}.`);
|
|
|
|
|
}
|
|
|
|
|
// 下面两个等价
|
|
|
|
|
displayHobbies.call({ name: "Bob" }, "swimming", "basketball", "anime"); // Bob likes swimming, basketball, anime.
|
|
|
|
|
displayHobbies.apply({ name: "Bob" }, ["swimming", "basketball", "anime"]); // Bob likes swimming, basketball, anime.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`bind`返回的是一个函数,需要手动执行
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
var p1 = {
|
|
|
|
|
name: "张三",
|
|
|
|
|
age: 12,
|
|
|
|
|
func: function () {
|
|
|
|
|
console.log(`姓名:${this.name},年龄:${this.age}`);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var p2 = {
|
|
|
|
|
name: "李四",
|
|
|
|
|
age: 15,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
p1.func.bind(p2)(); //姓名:李四,年龄:15
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# for 循环优化
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// 每次都要计算array.length
|
|
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
|
|
|
console.log(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用leng缓存array长度
|
|
|
|
|
for (let i = 0, length = array.length; i < length; i++) {
|
|
|
|
|
console.log(i);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 数组
|
|
|
|
|
|
|
|
|
|
## 扁平化去重升序排列
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
|
|
|
|
|
arr.flat(Infinity); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
|
|
|
|
|
|
|
|
|
|
let result = Array.from(new Set(arr.flat(Infinity)).sort((a, b) => a - b));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 前端页面埋点 - 1x1.gif
|
|
|
|
|
|
|
|
|
|
通常用在,统计页面点击,曝光,停留时间,签发……等场景
|
|
|
|
|
|
|
|
|
|
- 比 PNG/JPG 体积小
|
|
|
|
|
- 天然跨域
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<button onClick="countClick()">haorooms</button>
|
|
|
|
|
<script>
|
|
|
|
|
function countClick() {
|
|
|
|
|
new Image().src = `./haorooms.gif?${key}=${value}&${Math.random()} `;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 事件委托
|
|
|
|
|
|
|
|
|
|
利用冒泡原理,委托父元素执行
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<ul>
|
|
|
|
|
<li>苹果</li>
|
|
|
|
|
<li>香蕉</li>
|
|
|
|
|
<li>凤梨</li>
|
|
|
|
|
</ul>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
document.querySelector("ul").onclick = (event) => {
|
|
|
|
|
let target = event.target;
|
|
|
|
|
if (target.nodeName === "LI") {
|
|
|
|
|
console.log(target.innerHTML);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 构造函数 + 原型模式
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function Person(name, age, job) {
|
|
|
|
|
this.name = name;
|
|
|
|
|
this.age = age;
|
|
|
|
|
this.job = job;
|
|
|
|
|
}
|
|
|
|
|
Person.prototype.say = function (text) {
|
|
|
|
|
console.log(`${this.name}say:${text}`);
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 剩余参数...args
|
|
|
|
|
|
|
|
|
|
剩余参数`args`数个数组,`...`解构符
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function fun1(param, ...args) {
|
|
|
|
|
alert(args.length);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 跨页面通信
|
|
|
|
|
|
|
|
|
|
- cookie
|
|
|
|
|
- web worker
|
|
|
|
|
- localstorage
|
|
|
|
|
|
|
|
|
|
# iframe 跨域通信和不跨域通信
|
|
|
|
|
|
|
|
|
|
## 不跨域
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
// fatherSay是父页面全局方法
|
|
|
|
|
window.parent.fatherSay();
|
|
|
|
|
// 父页面Dom
|
|
|
|
|
window.parent.document.getElementById("元素id");
|
|
|
|
|
// 副业页面获取frameID为`iframe_ID`的子页面的Dom
|
|
|
|
|
window.frames["iframe_ID"].document.getElementById("元素id");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 跨域 postMessage
|
|
|
|
|
|
|
|
|
|
子页面
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
window.parent.postMessage("hello", "http://127.0.0.1:8089");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
父页面接受
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
window.addEventListener("message", function (event) {
|
|
|
|
|
alert(123);
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 对象类型判断
|
|
|
|
|
|
|
|
|
|
## 数组
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
let arr = [];
|
|
|
|
|
arr instanceof Array; // true
|
|
|
|
|
Array.isArray(arr); // true
|
|
|
|
|
Object.prototype.toString.call(arr); // "[object Array]"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# js 单线程,如何异步
|
|
|
|
|
|
|
|
|
|
- 主线程 执行 js 中所有的代码。
|
|
|
|
|
|
|
|
|
|
- 主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行,顺便创造一个`回调队列`。
|
|
|
|
|
|
|
|
|
|
- 主线程 已经执行完毕所有同步代码。监听`回调队列`一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该`回调队列`中对应的回调函数。
|
|
|
|
|
|
|
|
|
|
# 移动端最小触控区域
|
|
|
|
|
|
|
|
|
|
苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,通过`padding`、`margin`、`height`等方式进行点击区域扩展
|
|
|
|
|
|
|
|
|
|
# js 精度问题
|
|
|
|
|
|
|
|
|
|
常用类库:`Math.js`、`Big.js`、`decimal.js`
|
|
|
|
|
|
|
|
|
|
# 冻结 Object. freeze()
|
|
|
|
|
|
|
|
|
|
`const`生命的简单变量不可修改,但是复杂对象可以被修改,`Object. freeze()`:可以冻结对象
|
|
|
|
|
|
|
|
|
|
- 不能添加新属性
|
|
|
|
|
- 不能删除已有属性
|
|
|
|
|
- 不能修改已有属性的可枚举性、可配置性、可写性
|
|
|
|
|
- 不能修改已有属性的值
|
|
|
|
|
- 不能修改原型
|
|
|
|
|
|
|
|
|
|
浅冻结
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
const obj1 = {
|
|
|
|
|
internal: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Object.freeze(obj1);
|
|
|
|
|
obj1.internal.a = "aValue";
|
|
|
|
|
console.log(obj1.internal.a); // aValue
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
递归冻结
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
function deepFreeze(obj) {
|
|
|
|
|
// 获取定义在obj上的属性名
|
|
|
|
|
var propNames = Object.getOwnPropertyNames(obj);
|
|
|
|
|
// 在冻结自身之前冻结属性
|
|
|
|
|
propNames.forEach(function (name) {
|
|
|
|
|
var prop = obj[name];
|
|
|
|
|
// 如果prop是个对象,冻结它
|
|
|
|
|
if (typeof prop == "object" && prop !== null) deepFreeze(prop);
|
|
|
|
|
});
|
|
|
|
|
return Object.freeze(obj);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# Reflect
|
|
|
|
|
|
|
|
|
|
## Reflect.get(target, propertyKey, value[receiver])
|
|
|
|
|
|
|
|
|
|
获取对象身上某个属性的值,类似于 target[name]。
|
|
|
|
|
|
|
|
|
|
## Reflect.set(target, propertyKey, value[receiver])
|
|
|
|
|
|
|
|
|
|
将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true。
|
|
|
|
|
|
|
|
|
|
## Reflect.has(target, propertyKey)
|
|
|
|
|
|
|
|
|
|
判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# if else 优化
|
|
|
|
|
|
|
|
|
|
## 表驱动编程
|
|
|
|
|
|
|
|
|
|
空间换时间,设置`obj={key:value}`,通过`obj[key]`取值
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
calculateGrade(score){
|
|
|
|
|
const table = {
|
|
|
|
|
100: 'A',
|
|
|
|
|
90: 'A',
|
|
|
|
|
80: 'B',
|
|
|
|
|
70: 'C',
|
|
|
|
|
60: 'D',
|
|
|
|
|
others: 'E'
|
|
|
|
|
}
|
|
|
|
|
return table[Math.floor(score/10)*10] || table['others']
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 短路运算
|
|
|
|
|
|
|
|
|
|
react 没有`v-if`,运用比较频繁
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
// 函数组件
|
|
|
|
|
const Home = () => {
|
|
|
|
|
return <div>home</div>;
|
|
|
|
|
};
|
|
|
|
|
{
|
|
|
|
|
true && <Home />;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 使用有意义且易读的变量名
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎 const yyyymmdstr = moment().format("YYYY/MM/DD");
|
|
|
|
|
|
|
|
|
|
👍 const currentDate = moment().format("YYYY/MM/DD");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 使用有意义的变量代替数组下标
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
const address = "One Infinite Loop, Cupertino 95014";
|
|
|
|
|
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
|
|
|
|
|
saveCityZipCode(
|
|
|
|
|
address.match(cityZipCodeRegex)[1],
|
|
|
|
|
address.match(cityZipCodeRegex)[2]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
const address = "One Infinite Loop, Cupertino 95014";
|
|
|
|
|
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
|
|
|
|
|
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
|
|
|
|
|
saveCityZipCode(city, zipCode);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 变量名要简洁
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
const Car = {
|
|
|
|
|
carMake: "Honda",
|
|
|
|
|
carModel: "Accord",
|
|
|
|
|
carColor: "Blue"
|
|
|
|
|
};
|
|
|
|
|
function paintCar(car, color) {
|
|
|
|
|
car.carColor = color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
const Car = {
|
|
|
|
|
make: "Honda",
|
|
|
|
|
model: "Accord",
|
|
|
|
|
color: "Blue"
|
|
|
|
|
};
|
|
|
|
|
function paintCar(car, color) {
|
|
|
|
|
car.color = color;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 消除魔术字符串
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎 setTimeout(blastOff, 86400000);
|
|
|
|
|
|
|
|
|
|
👍 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
|
|
|
|
|
setTimeout(blastOff, MILLISECONDS_PER_DAY);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 使用默认参数替代短路运算符
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
function createMicrobrewery(name) {
|
|
|
|
|
const breweryName = name || "Hipster Brew Co.";
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
function createMicrobrewery(name = "Hipster Brew Co.") {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 一个函数
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
function emailClients(clients) {
|
|
|
|
|
clients.forEach(client => {
|
|
|
|
|
const clientRecord = database.lookup(client);
|
|
|
|
|
if (clientRecord.isActive()) {
|
|
|
|
|
email(client);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
function emailActiveClients(clients) {
|
|
|
|
|
clients.filter(isActiveClient).forEach(email);
|
|
|
|
|
}
|
|
|
|
|
function isActiveClient(client) {
|
|
|
|
|
const clientRecord = database.lookup(client);
|
|
|
|
|
return clientRecord.isActive();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
---------------------分割线-----------------------
|
|
|
|
|
|
|
|
|
|
👎
|
|
|
|
|
function createFile(name, temp) {
|
|
|
|
|
if (temp) {
|
|
|
|
|
fs.create(`./temp/${name}`);
|
|
|
|
|
} else {
|
|
|
|
|
fs.create(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
function createFile(name) {
|
|
|
|
|
fs.create(name);
|
|
|
|
|
}
|
|
|
|
|
function createTempFile(name) {
|
|
|
|
|
createFile(`./temp/${name}`);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 函数参数不多于 2 个,如果有很多参数就利用 object 传递,并使用解构
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
function createMenu(title, body, buttonText, cancellable) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
createMenu("Foo", "Bar", "Baz", true);
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
function createMenu({ title, body, buttonText, cancellable }) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
createMenu({
|
|
|
|
|
title: "Foo",
|
|
|
|
|
body: "Bar",
|
|
|
|
|
buttonText: "Baz",
|
|
|
|
|
cancellable: true
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 函数名应该直接反映函数的作用
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
function addToDate(date, month) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
const date = new Date();
|
|
|
|
|
// It's hard to tell from the function name what is added
|
|
|
|
|
addToDate(date, 1);
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
function addMonthToDate(month, date) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
const date = new Date();
|
|
|
|
|
addMonthToDate(1, date);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 尽量使用纯函数
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
const programmerOutput = [
|
|
|
|
|
{
|
|
|
|
|
name: "Uncle Bobby",
|
|
|
|
|
linesOfCode: 500
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Suzie Q",
|
|
|
|
|
linesOfCode: 1500
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Jimmy Gosling",
|
|
|
|
|
linesOfCode: 150
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Gracie Hopper",
|
|
|
|
|
linesOfCode: 1000
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
let totalOutput = 0;
|
|
|
|
|
for (let i = 0; i < programmerOutput.length; i++) {
|
|
|
|
|
totalOutput += programmerOutput[i].linesOfCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
const programmerOutput = [
|
|
|
|
|
{
|
|
|
|
|
name: "Uncle Bobby",
|
|
|
|
|
linesOfCode: 500
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Suzie Q",
|
|
|
|
|
linesOfCode: 1500
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Jimmy Gosling",
|
|
|
|
|
linesOfCode: 150
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Gracie Hopper",
|
|
|
|
|
linesOfCode: 1000
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
const totalOutput = programmerOutput.reduce(
|
|
|
|
|
(totalLines, output) => totalLines + output.linesOfCode,
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 不要过度优化
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
👎
|
|
|
|
|
// 现代浏览器对于迭代器做了内部优化
|
|
|
|
|
for (let i = 0, len = list.length; i < len; i++) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
👍
|
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 关于模块化
|
|
|
|
|
|
|
|
|
|
## 无模块化
|
|
|
|
|
|
|
|
|
|
> 污染全局作用域、维护成本高、依赖关系不明显
|
|
|
|
|
|
|
|
|
|
script 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
<script src="jquery.js"></script>
|
|
|
|
|
<script src="main.js"></script>
|
|
|
|
|
<script src="other1.js"></script>
|
|
|
|
|
<script src="other2.js"></script>
|
|
|
|
|
<script src="other3.js"></script>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## CommonJS 规范(同步)
|
|
|
|
|
|
|
|
|
|
该规范最初是用在服务器端的 node 的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块(同步)
|
|
|
|
|
|
|
|
|
|
> module.exports 本身就是一个对象
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
module.exports = { foo: "bar" }; //true
|
|
|
|
|
module.exports.foo = "bar"; //true。
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
CommonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,比如下边 AMD 规范。
|
|
|
|
|
|
|
|
|
|
## AMD 规范(RequireJS)
|
|
|
|
|
|
|
|
|
|
承接上文,AMD 规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
|
|
|
|
|
|
|
|
|
|
- `require([module], callback)`:加载模块
|
|
|
|
|
- `define(id, [depends], callback)`:定义模块
|
|
|
|
|
- `require.config()`:配置路径、依赖关系
|
|
|
|
|
|
|
|
|
|
## CMD 规范(SeaJS)
|
|
|
|
|
|
|
|
|
|
CMD 是 在推广过程中对模块定义的规范化产出
|
|
|
|
|
|
|
|
|
|
## ES6 import/export
|
|
|
|
|
|
|
|
|
|
通过 babel 将不被支持的 import 编译为当前受到广泛支持的 AMD 规范
|
|
|
|
|
|
|
|
|
|
# AMD模块化实现
|
|
|
|
|
|
|
|
|
|
## define 函数
|
|
|
|
|
|
|
|
|
|
用来声明模块名`module`,依赖数组`deps`,以及模块的作用`callback`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
var module = (function () {
|
|
|
|
|
var stack = {}; //模块存储栈,存的是模块执行后的结果
|
|
|
|
|
|
|
|
|
|
function define(module, deps, callback) {
|
|
|
|
|
stack[module] = callback.apply(null, deps); // 压栈
|
|
|
|
|
console.log(stack); // 查看栈存储的模块
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { define: define };
|
|
|
|
|
})();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
调用`define`试试,发现 `calc`模块被压入了`stack 模块栈中`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
module.define("calc", [], function () {
|
|
|
|
|
return {
|
|
|
|
|
first: function (arr) {
|
|
|
|
|
return arr[0];
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
// {calc: { first: ƒ }}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## deps 依赖(导入)
|
|
|
|
|
|
|
|
|
|
对上面的 `define`函数稍加改造,这一步过程的本质,就是对 deps
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
var module = (function (window) {
|
|
|
|
|
var stack = {}; //模块存储栈,
|
|
|
|
|
|
|
|
|
|
function define(module, deps, callback) {
|
|
|
|
|
// 把 define 函数依赖数组中的模块从 stack 模块中拿出
|
|
|
|
|
deps.map(function (mod, index) {
|
|
|
|
|
deps[index] = stack[mod]; // 赋值给当前模块的 deps
|
|
|
|
|
});
|
|
|
|
|
stack[module] = callback.apply(null, deps);
|
|
|
|
|
console.log(stack); // 查看栈存储的模块
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { define: define };
|
|
|
|
|
})(window);
|
|
|
|
|
|
|
|
|
|
module.define("calc", [], function () {
|
|
|
|
|
return {
|
|
|
|
|
first: function (arr) {
|
|
|
|
|
return arr[0];
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module.define("number", ["calc"], function (calc) {
|
|
|
|
|
return {
|
|
|
|
|
res: calc.first([4, 3, 2, 1]), //4
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 此时 stack 打印的结果为
|
|
|
|
|
//> calc: {first: ƒ}
|
|
|
|
|
//> number: {res: 4}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> 从本质上看,define 函数的第二个参数 deps 数组,相当于 import 导入,并且如果第三个参数 callback 采用 `return { }`,也就相当于 export 导出
|
|
|
|
|
|
|
|
|
|
## 老生常谈的指针
|
|
|
|
|
|
|
|
|
|
说到底,`stack`中 `a模块` export 是一个指针,`{a:value}`(内存地址),所以,`b 模块`会改变`a.a`的值,这点和 `cmd`不同
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
module.define("a", [], function () {
|
|
|
|
|
return {
|
|
|
|
|
a: 1,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
module.define("b", ["a"], function (a) {
|
|
|
|
|
a.a = 2;
|
|
|
|
|
});
|
|
|
|
|
module.define("c", ["a"], function (a) {
|
|
|
|
|
console.log(a.a); // 2
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 缘起 Object.defineProperty()
|
|
|
|
|
|
|
|
|
|
给`目标对象`上定义一个新属性,或者修改`目标对象`属性,并且返回新对象
|
|
|
|
|
|
|
|
|
|
# 上帝的钥匙 get & set
|
|
|
|
|
|
|
|
|
|
属性的`getter`函数,如果没有 `getter`则尾 `undefined`。当访问该属性时,会调用此函数.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let obj = {};
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(obj, "a", {
|
|
|
|
|
get() {
|
|
|
|
|
return 7;
|
|
|
|
|
},
|
|
|
|
|
set(val) {
|
|
|
|
|
console.log(`FAILED!改变 a 属性,新值为:${val},但是被重写 set 劫持了`);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(obj.a);
|
|
|
|
|
obj.a = 4;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> 上帝的钥匙被找到了
|
|
|
|
|
|
|
|
|
|
劫持!劫持!还是 TMD 劫持!
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let obj = {};
|
|
|
|
|
let tempValue = 0;
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(obj, "a", {
|
|
|
|
|
get() {
|
|
|
|
|
return tempValue;
|
|
|
|
|
},
|
|
|
|
|
set(newValue) {
|
|
|
|
|
tempValue = newValue;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(obj.a); // 0
|
|
|
|
|
obj.a = 4;
|
|
|
|
|
console.log(obj.a); // 4
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 封装 defineReactive(obj, prop, val)
|
|
|
|
|
|
|
|
|
|
> 这里 defineReactive 第三个参数 val 替代了上一步中全局变量`tempValue`,对于 get()、set()来说,访问到了其他函数内部的变量,所以形成了闭包
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
function defineReactive(obj, prop, val) {
|
|
|
|
|
Object.defineProperty(obj, prop, {
|
|
|
|
|
get() {
|
|
|
|
|
console.log(`劫持,你访问了${prop}属性`);
|
|
|
|
|
return val;
|
|
|
|
|
},
|
|
|
|
|
set(newValue) {
|
|
|
|
|
if (val === newValue) return;
|
|
|
|
|
console.log(`劫持,你改变了${prop}属性`);
|
|
|
|
|
val = newValue;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let obj = {};
|
|
|
|
|
defineReactive(obj, "a", 4);
|
|
|
|
|
|
|
|
|
|
console.log(obj.a); // 劫持,你访问了a属性 4
|
|
|
|
|
obj.a = 7; // 劫持,你改变了a属性
|
|
|
|
|
console.log(obj.a); // 劫持,你访问了a属性 7
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# 递归侦测
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let obj = { a: { b: { c: {} } } };
|
|
|
|
|
defineReactive(obj, "a", 4);
|
|
|
|
|
```
|
|
|
|
|
|
2023-11-07 16:12:40 +08:00
|
|
|
|
> 如何自动让`obj`对象的全部属性都`reactive`呢?
|
2023-11-06 16:05:27 +08:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
let obj = {
|
|
|
|
|
a: {
|
|
|
|
|
b: {
|
|
|
|
|
c: 5,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
d: 4,
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
定义个方法`observe`,递归 `obj` 的每一层的每个 `prop`,检测是否有`__ob__`,如果没有,`defineReactive`,并且挂一个`Observer`实例在这个`props`上,例如:
|
|
|
|
|
|
|
|
|
|
`Observer对象`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
class Observer {
|
|
|
|
|
constructor(value) {
|
|
|
|
|
def(value, "__ob__", this, false); // 不可枚举,不能给__ob__添加__ob__
|
|
|
|
|
this.walk(value);
|
|
|
|
|
}
|
|
|
|
|
// 遍历每一个 prop的 value
|
|
|
|
|
walk(value) {
|
|
|
|
|
for (let prop in value) {
|
|
|
|
|
defineReactive(value, prop);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`defineReactive.js`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
export default function defineReactive(obj, prop, val) {
|
|
|
|
|
if (arguments.length === 2) val = obj[prop]; // 如果2个参数
|
|
|
|
|
let childNode = observe(val);
|
|
|
|
|
Object.defineProperty(obj, prop, {
|
|
|
|
|
enumerable: true,
|
|
|
|
|
configurable: true,
|
|
|
|
|
get() {
|
|
|
|
|
console.log(`你访问了${prop}属性`);
|
|
|
|
|
return val;
|
|
|
|
|
},
|
|
|
|
|
set(newValue) {
|
|
|
|
|
if (val === newValue) return;
|
|
|
|
|
console.log(`你改变了${prop}属性`);
|
|
|
|
|
val = newValue;
|
|
|
|
|
childNode = observe(newValue);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`observe.js`
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
/**
|
|
|
|
|
* 检测 obj 身上有没有 __ob__(Observer 实例)
|
|
|
|
|
* @param {*} value
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
export default function observe(value) {
|
|
|
|
|
if (typeof value != "object") return;
|
|
|
|
|
let ob;
|
|
|
|
|
//! 用__ob__是为了属性不重名,被覆盖
|
|
|
|
|
if (typeof value.__ob__ != "undefined") {
|
|
|
|
|
ob = value.__ob__;
|
|
|
|
|
} else {
|
|
|
|
|
ob = new Observer(value);
|
|
|
|
|
}
|
|
|
|
|
return ob;
|
|
|
|
|
}
|
|
|
|
|
```
|