diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..441ac71b
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index eba1110b..00000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-# Auto detect text files and perform LF normalization
-* text=auto
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..f296fe54
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,82 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+*.DS_Store
\ No newline at end of file
diff --git a/.idea/Leo-JavaScript.iml b/.idea/Leo-JavaScript.iml
new file mode 100644
index 00000000..24643cc3
--- /dev/null
+++ b/.idea/Leo-JavaScript.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..9c694110
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..28a804d8
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..af6a5e30
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 00000000..e0d8afc5
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1566777377778
+
+
+ 1566777377778
+
+
+
+
+
+
+
+
+ 1566777411452
+
+
+
+ 1566777411452
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.obsidian/app.json b/.obsidian/app.json
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/.obsidian/app.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json
new file mode 100644
index 00000000..990f3376
--- /dev/null
+++ b/.obsidian/appearance.json
@@ -0,0 +1,3 @@
+{
+ "baseFontSize": 16
+}
\ No newline at end of file
diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json
new file mode 100644
index 00000000..ab1d5111
--- /dev/null
+++ b/.obsidian/core-plugins.json
@@ -0,0 +1,15 @@
+[
+ "file-explorer",
+ "global-search",
+ "switcher",
+ "graph",
+ "backlink",
+ "page-preview",
+ "note-composer",
+ "command-palette",
+ "editor-status",
+ "markdown-importer",
+ "word-count",
+ "open-with-default-app",
+ "file-recovery"
+]
\ No newline at end of file
diff --git a/.obsidian/hotkeys.json b/.obsidian/hotkeys.json
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/.obsidian/hotkeys.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/.obsidian/workspace b/.obsidian/workspace
new file mode 100644
index 00000000..e8879d91
--- /dev/null
+++ b/.obsidian/workspace
@@ -0,0 +1,87 @@
+{
+ "main": {
+ "id": "682aa650850c8ac3",
+ "type": "split",
+ "children": [
+ {
+ "id": "c0c599ff6b44a27a",
+ "type": "leaf",
+ "state": {
+ "type": "empty",
+ "state": {}
+ }
+ }
+ ],
+ "direction": "vertical"
+ },
+ "left": {
+ "id": "bb69674bbe1c8018",
+ "type": "split",
+ "children": [
+ {
+ "id": "facb5bc48416748a",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "ad9d62dd2632f11c",
+ "type": "leaf",
+ "state": {
+ "type": "file-explorer",
+ "state": {}
+ }
+ },
+ {
+ "id": "ccaf0812caeeaf46",
+ "type": "leaf",
+ "state": {
+ "type": "search",
+ "state": {
+ "query": "",
+ "matchingCase": false,
+ "explainSearch": false,
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300
+ },
+ "right": {
+ "id": "adc5a726a4c80488",
+ "type": "split",
+ "children": [
+ {
+ "id": "4139bb7d06693d9e",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "46abbe14b4079d38",
+ "type": "leaf",
+ "state": {
+ "type": "backlink",
+ "state": {
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical",
+ "showSearch": false,
+ "searchQuery": "",
+ "backlinkCollapsed": false,
+ "unlinkedCollapsed": true
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300,
+ "collapsed": true
+ },
+ "active": "c0c599ff6b44a27a",
+ "lastOpenFiles": []
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..0d22b583
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..28a51d58
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "compile-hero.disable-compile-files-on-did-save-code": false
+}
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-Dictionary&&HashTable.md b/Cute-Algorithms/Cute-Algorithms-Dictionary&&HashTable.md
new file mode 100644
index 00000000..5c7546e9
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-Dictionary&&HashTable.md
@@ -0,0 +1,549 @@
+
+
+这是第五周的练习题,上周忘记发啦,这周是复习 **Dictionary 和 HashTable**。
+
+下面是之前分享的链接:
+* 1.[每周一练 之 数据结构与算法(Stack)](https://juejin.im/post/5cb2df0c5188251aca7340a0)
+* 2.[每周一练 之 数据结构与算法(LinkedList)](https://juejin.im/post/5cbdbb1af265da036d79bb35)
+* 3.[每周一练 之 数据结构与算法(Queue)](https://juejin.im/post/5cc3cbaaf265da03a85ac7f8)
+* 4.[每周一练 之 数据结构与算法(Set)](https://juejin.im/post/5cceee526fb9a0323a01c72e)
+
+> 欢迎关注我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+
+**本周练习内容:数据结构与算法 —— Dictionary 和 HashTable**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+
+## 一、字典和散列表的概念
+
+1. 字典是什么?
+
+2. 字典和集合有什么异同?
+
+3. 什么是散列表和散列函数?
+
+4. 散列表的特点是什么?
+
+---
+解析:
+
+1. 字典是什么?
+
+字典是一种以 **键-值对** 形式存储数据的数据格式,其中键名用来查询特定元素。
+
+2. 字典和集合有什么异同?
+
+相同:都是用来存储不同元素的数据格式;
+区别:集合是以 **值-值** 的数据格式存储,而字典是以 **键-值** 的数据格式存储。
+
+3. 什么是散列表和散列函数?
+
+哈希表(`Hash table`,也叫散列表),是根据关键码值(·Key value·)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做**散列函数**,存放记录的数组叫做**散列表**。
+
+4. 散列表的特点是什么?
+
+特点:数组和链接优点的结合,查询速度非常的快,几乎是O(1)的时间复杂度,并且插入和删除也容易。
+
+
+## 二、请实现一个字典
+
+`set(key,value)`:向字典中添加新元素。
+`delete(key)`:通过使用键值从字典中移除键值对应的值。
+`has(key)`:如果某个键值存在于这个字典中,则返回 true,否则返回 false。
+`get(key)`:使用键值查找对应的值并返回。
+`clear()`:删除字典中的所有元素。
+`size()`:返回字典包含的元素数量,与数组的 length 属性类似。
+`keys()`:将字典的所有键名以数组的形式返回。
+`values()`:将字典包含的所有数值以数组形式返回。
+
+
+使用示例:
+
+```js
+let dictionary = new Dictionary();
+
+dictionary.set("Gandalf", "gandalf@email.com");
+dictionary.set("John", "johnsnow@email.com");
+dictionary.set("Tyrion", "tyrion@email.com");
+
+console.log(dictionary.has("Gandalf"));
+console.log(dictionary.size());
+
+console.log(dictionary.keys());
+console.log(dictionary.values());
+console.log(dictionary.get("Tyrion"));
+
+dictionary.delete("John");
+
+console.log(dictionary.keys());
+console.log(dictionary.values());
+
+```
+
+**提示:Web 端优先使用 ES6 以上的语法实现。**
+
+---
+解析:
+```js
+// 二、请实现一个字典
+class Dictionary {
+ constructor(){
+ this.items = []
+ }
+ /**
+ * 向字典中添加新元素
+ * @param {*} key 添加的键名
+ * @param {*} value 添加的值
+ */
+ set (key, value) {
+ if ( !key ) return new Error('请指定插入的key')
+ this.items[key] = value
+ }
+
+ /**
+ * 查询某个键值存在于这个字典
+ * @param {*} key 查询的键名
+ * @return {Boolean} 是否存在
+ */
+ has (key) {
+ return key in this.items
+ }
+
+ /**
+ * 通过使用键值从字典中移除键值对应的值
+ * @param {*} key 移除的键名
+ * @return {Boolean} 是否移除成功
+ */
+ delete (key) {
+ if(!key || !this.has(key)) return false
+ delete this.items[key]
+ return true
+ }
+
+ /**
+ * 使用键值查找对应的值并返回
+ * @param {*} key 查找的键名
+ * @return {*} 查找的结果
+ */
+ get (key) {
+ return this.has(key) ? this.items[key] : undefined
+ }
+
+ /**
+ * 删除字典中的所有元素
+ */
+ clear () {
+ this.items = {}
+ }
+
+ /**
+ * 将字典的所有键名以数组的形式返回
+ * @return {Array} 所有键名的数组
+ */
+ keys () {
+ return Object.keys(this.items)
+ }
+
+ /**
+ * 将字典的所有键值以数组的形式返回
+ * @return {Array} 所有键值的数组
+ */
+ values () {
+ let result = []
+ for(let k in this.items){
+ if(this.has[k]){
+ result.push(this.items[k])
+ }
+ }
+ return result
+ }
+
+ /**
+ * 返回字典包含的元素数量
+ * @return {Number} 元素数量
+ */
+ size () {
+ const values = this.values()
+ return values.length
+ }
+}
+```
+
+## 三、请实现一个散列表
+
+`put(key,value)`:向散列表增加/更新一个新的项。
+`remove(key)`:根据键值从散列表中移除值。
+`get(key)`:根据键值检索到特定的值。
+`print()`:打印散列表中已保存的值。
+
+
+散列表内部的散列算法:
+
+```js
+function hashCode(key) {
+ let hash = 0;
+ for (let i = 0; i < key.length; i++) {
+ hash += key.charCodeAt(i);
+ }
+ return hash % 37;
+}
+```
+
+使用示例:
+
+```js
+const hashTable = new HashTable();
+
+hashTable.put("Gandalf", "gandalf@email.com");
+hashTable.put("John", "johnsnow@email.com");
+hashTable.put("Tyrion", "tyrion@email.com");
+hashTable.print();
+```
+
+---
+解析:
+```js
+// 三、请实现一个散列表
+class HashTable {
+ constructor(){
+ this.table = []
+ }
+ /**
+ * 散列函数
+ * @param {*} key 键名
+ */
+ hashCode(key) {
+ let hash = 0;
+ for (let i = 0; i < key.length; i++) {
+ hash += key.charCodeAt(i);
+ }
+ return hash % 37;
+ }
+ /**
+ * 向散列表增加/更新一个新的项
+ * @param {*} key 添加的键名
+ * @param {*} value 添加的值
+ */
+ put (key, value) {
+ let position = this.hashCode(key)
+ this.table[position] = value
+ }
+
+ /**
+ * 根据键值从散列表中移除值
+ * @param {*} key 移除的键名
+ * @return {Boolean} 是否成功移除
+ */
+ remove (key) {
+ if ( !key ) return false
+ let position = this.hashCode(key)
+ this.table[position] = undefined
+ return true
+ }
+
+ /**
+ * 根据键值检索到特定的值
+ * @param {*} key 查找的键名
+ * @return {*} 查找的值
+ */
+ get (key) {
+ let position = this.hashCode(key)
+ return this.table[position]
+ }
+
+ /**
+ * 打印散列表中已保存的值
+ * @return {*} 散列表的值
+ */
+ print () {
+ return this.table
+ }
+}
+```
+
+## 四、请利用之前已实现的链表,实现一个分离链接的散列表
+
+分离链接是为散列表的每一个位置创建一个链表储存元素的方式来处理散列表中的冲突:
+
+
+
+
+请实现新的散列表方法:
+
+`put(key,value)`:将 `key 和 `value` 存在一个 `ValuePair` 对象中(即可定义一个包含 `key` 和 `value` 属性的 `ValuePair` 类),并将其加入对应位置的链表中。
+
+`get(key)`:返回键值对应的值,没有则返回 `undefined`。
+
+`remove(key)`:从散列表中移除键值对应的元素。
+
+`print()`:打印散列表中已保存的值。
+
+
+**提示:先找到元素储存位置所对应的链表,再找到对应的值。**
+
+```js
+const hashTable = new HashTable();
+
+hashTable.put("Gandalf", "gandalf@email.com");
+hashTable.put("Tyrion", "tyrion@email.com");
+hashTable.put("Aaron", "aaron@email.com");
+hashTable.put("Ana", "ana@email.com");
+hashTable.put("Mindy", "mindy@email.com");
+hashTable.put("Paul", "paul@email.com");
+
+hashTable.print();
+
+console.log(hashTable.get("Tyrion"));
+console.log(hashTable.get("Aaron"));
+
+hashTable.remove("Tyrion");
+
+hashTable.print();
+```
+
+---
+解析:
+```js
+// 链表的实现代码省略 可以查看之前的代码
+let ValuePair = function (key, value){
+ this.key = key
+ this.value = value
+ this.toString = function(){
+ return `[${this.key} - ${this.value}]`
+ }
+}
+class HashTable {
+ constructor(){
+ this.table = []
+ }
+ /**
+ * 散列函数
+ * @param {*} key 键名
+ */
+ hashCode(key) {
+ let hash = 0;
+ for (let i = 0; i < key.length; i++) {
+ hash += key.charCodeAt(i);
+ }
+ return hash % 37;
+ }
+ /**
+ * 向散列表增加/更新一个新的项
+ * @param {*} key 添加的键名
+ * @param {*} value 添加的值
+ */
+ put (key, value) {
+ let position = this.hashCode(key)
+ if(this.table[position] == undefined){
+ this.table[position] = new LinkedList()
+ }
+ this.table[position].append(new ValuePair(key, value))
+ }
+
+ /**
+ * 根据键值从散列表中移除值
+ * @param {*} key 移除的键名
+ * @return {Boolean} 是否成功移除
+ */
+ remove (key) {
+ let position = this.hashCode(key)
+ if ( !key || this.table[position] === undefined ) return false
+ let current = this.table[position].getHead()
+ while(current.next){
+ if(current.element.key === key){
+ this.table[position].remove(current.element)
+ if(this.table[position].isEmpty){
+ this.table[position] = undefined
+ }
+ return true
+ }
+ current = current.next
+ }
+ }
+
+ /**
+ * 根据键值检索到特定的值
+ * @param {*} key 查找的键名
+ * @return {*} 查找的值
+ */
+ get (key) {
+ let position = this.hashCode(key)
+ if(!key || this.table[position] === undefined) return undefined
+ let current = this.table[position].getHead()
+ while(current.next()){
+ if(current.element.key === key){
+ return current.element.value
+ }
+ current = current.next
+ }
+ }
+
+ /**
+ * 打印散列表中已保存的值
+ * @return {*} 散列表的值
+ */
+ print () {
+ return this.table
+ }
+}
+
+```
+
+
+## 五、实现一个线性探查的散列表
+
+
+线性探查是解决散列表中冲突的另一种方法,当向表中某一个位置加入新元素的时候,如果索引为 `index` 的位置已经被占据了,就尝试 `index+1` 的位置。如果 `index+1` 的位置也被占据,就尝试 `index+2`,以此类推。
+
+
+
+
+
+请实现散列表:
+
+`put(key,value)`:将 `key` 和 `value` 存在一个 `ValuePair` 对象中(即可定义一个包含 `key` 和 `value` 属性的 `ValuePair` 类)并分配到散列表。
+
+`get(key)`:返回键值对应的值,没有则返回 `undefined`。
+
+`remove(key)`:从散列表中移除键值对应的元素。
+
+
+**提示:移除一个元素,只需要将其赋值为 undefined。**
+
+
+使用示例:
+```js
+const hashTable = new HashTable();
+
+hashTable.put("Gandalf", "gandalf@email.com");
+hashTable.put("Tyrion", "tyrion@email.com");
+hashTable.put("Aaron", "aaron@email.com");
+hashTable.put("Ana", "ana@email.com");
+hashTable.put("Mindy", "mindy@email.com");
+hashTable.put("Paul", "paul@email.com");
+
+hashTable.print();
+
+console.log(hashTable.get("Tyrion"));
+console.log(hashTable.get("Aaron"));
+
+hashTable.remove("Tyrion");
+
+hashTable.print();
+```
+---
+解析:
+```js
+let ValuePair = function (key, value){
+ this.key = key
+ this.value = value
+ this.toString = function(){
+ return `[${this.key} - ${this.value}]`
+ }
+}
+class HashTable {
+ constructor(){
+ this.table = []
+ }
+ /**
+ * 散列函数
+ * @param {*} key 键名
+ */
+ hashCode(key) {
+ let hash = 0;
+ for (let i = 0; i < key.length; i++) {
+ hash += key.charCodeAt(i);
+ }
+ return hash % 37;
+ }
+ /**
+ * 向散列表增加/更新一个新的项
+ * @param {*} key 添加的键名
+ * @param {*} value 添加的值
+ */
+ put (key, value) {
+ let position = this.hashCode(key)
+ if(this.table[position] == undefined){
+ this.table[position] = new ValuePair(key, value)
+ }else{
+ let index = ++position
+ while(this.table[index] !== undefined){
+ index ++
+ }
+ this.table[index] = new ValuePair(key, value)
+ }
+ }
+
+ /**
+ * 根据键值从散列表中移除值
+ * @param {*} key 移除的键名
+ * @return {Boolean} 是否成功移除
+ */
+ remove (key) {
+ let position = this.hashCode(key)
+ if( !key || this.table[position] === undefined ) return undefined
+ if(this.table[position].key === key){
+ this.table[index] = undefined
+ }else{
+ let index = ++position
+ while(
+ this.table[index] === undefined ||
+ this.table[index].key !== key
+ ){
+ index ++
+ }
+ if(this.table[index].key === key){
+ this.table[index] = undefined
+ }
+ }
+ }
+
+ /**
+ * 根据键值检索到特定的值
+ * @param {*} key 查找的键名
+ * @return {*} 查找的值
+ */
+ get (key) {
+ let position = this.hashCode(key)
+ if( !key || this.table[position] === undefined ) return undefined
+ if(this.table[position].key === key){
+ return this.table[position].value
+ }else{
+ let index = ++position
+ while(
+ this.table[index] === undefined ||
+ this.table[index].key !== key
+ ){
+ index ++
+ }
+ if(this.table[index].key === key){
+ return this.table[index].value
+ }
+ }
+ }
+
+ /**
+ * 打印散列表中已保存的值
+ * @return {*} 散列表的值
+ */
+ print () {
+ return this.table
+ }
+}
+```
+
+## 下周预告
+下周将练习 **Tree** 的题目。
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-LinkedList.md b/Cute-Algorithms/Cute-Algorithms-LinkedList.md
new file mode 100644
index 00000000..f9e99c2b
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-LinkedList.md
@@ -0,0 +1,550 @@
+这是第三周的练习题,原本应该先发第二周的,因为周末的时候,我的母亲大人来看望她的宝贝儿子,哈哈,我得带她看看厦门这座美丽的城市呀。
+
+这两天我抓紧整理下第二周的题目和答案,下面我把之前的也列出来:
+* 1.[每周一练 之 数据结构与算法(Stack)](https://juejin.im/post/5cb2df0c5188251aca7340a0)
+
+> 欢迎关注我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+
+
+**本周练习内容:数据结构与算法 —— LinkedList**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+
+## 一、链表是什么?与数组有什么区别?生活中有什么案例?
+----
+解析:
+概念参考阅读 [链表 —— 维基百科](https://zh.wikipedia.org/wiki/%E9%93%BE%E8%A1%A8)
+
+**1.概念:**
+
+链表(Linked list)是一种上一个元素的引用指向下一个元素的存储结构,链表通过指针来连接元素与元素;
+
+链表是线性表的一种,所谓的线性表包含顺序线性表和链表,顺序线性表是用数组实现的,在内存中有顺序排列,通过改变数组大小实现。而链表不是用顺序实现的,用指针实现,在内存中不连续。意思就是说,链表就是**将一系列不连续的内存联系起来**,将那种碎片内存进行合理的利用,解决空间的问题。
+
+所以,链表允许插入和删除表上任意位置上的节点,但是不允许随即存取。链表有很多种不同的类型:**单向链表**、**双向链表**及**循环链表**。
+
+**2.与数组的区别:**
+* 相同:
+两种结构均**可实现数据的顺序存储**,构造出来的模型呈**线性结构**。
+
+* 不同:
+链表是**链式的存储结构**;数组是**顺序的存储结构**。
+链表通过**指针**来连接元素与元素,数组则是把所有元素按**次序**依次存储。
+
+链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难。
+
+数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。
+
+
+**数组和链表一些操作的时间复杂度对比:**
+数组:
+* 查找复杂度:O(1)
+* 添加/删除复杂度:O(n)
+
+链表:
+* 查找复杂度:O(n)
+* 添加/删除复杂度:O(1)
+
+**3.生活中的案例:**
+火车,是由一些列车厢连接起来;
+寻宝游戏,每个线索都是下一个线索地点的指针。
+
+## 二、请实现一个链表,并实现以下方法
+* `append(element)`:向列表尾部添加一个新的元素。
+* `insert(position, element)`:向列表指定位置插入一个新的元素。
+* `remove(element)`:从列表中移除并返回特定元素(若有多个相同元素则取第一次出现的情况)。
+* `indexOf(element)`:返回元素在列表的索引(若有多个相同元素则取第一次出现的情况),如果列表中没有该元素则返回 `-1`。
+* `removeAt(position)`:从列表中,移除并返回特定位置的一项。
+* `isEmpty()`:如果列表不含任何元素,返回 `true`,否则返回 `false`。
+* `size()`:返回列表中元素个数,与数组的 `length` 属性类似。
+* `toString()`:由于列表项使用 `Node` 类,需要重写继承自 JavaScript 对象默认的 `toString()` 方法,让其只输出元素的值。
+**提示:Web 端优先使用 ES6 以上的语法实现。**
+
+----
+解析:
+
+```js
+class Node {
+ constructor(element){
+ this.element = element
+ this.next = null
+ }
+}
+class LinkedList {
+ constructor(){
+ this.length = 0
+ this.head = null
+ }
+ /**
+ * 添加元素(末尾添加)
+ * @param {*} element 添加的元素
+ */
+ append(element){
+ let node = new Node(element)
+ if(!this.head){
+ this.head = node
+ }else{
+ let current = this.head
+ // 查找最后一项
+ while(current.next){
+ current = current.next
+ }
+ // 将最后一下的next赋值为node,实现追加元素
+ current.next = node
+ }
+ this.length ++
+ }
+ /**
+ * 添加元素(指定位置)
+ * @param {Number} position 添加的位置
+ * @param {*} element 添加的元素
+ */
+ insert(position, element){
+ if(position >= 0 && position <= this.length){
+ let node = new Node(element),
+ index = 0,
+ previous = null
+ if(position === 0){
+ node.next = this.head
+ this.head = node
+ }else{
+ let current = this.head
+ while(index++ < position){
+ previous = current
+ current = current.next
+ }
+ previous.next = node
+ node.next = current
+ }
+ this.length ++
+ }
+ }
+ /**
+ * 删除元素
+ * @param {*} element 删除的元素
+ * @return {*} 被删除的元素
+ */
+ remove(element){
+ let current = this.head,
+ previous = null
+ if(element === this.head.element){
+ this.head = current.next
+ }else{
+ while(current.next && current.element !== element){
+ previous = current
+ current = current.next
+ }
+ previous.next = current.next
+ this.length --
+ return current.element
+ }
+ }
+ /**
+ * 删除元素(指定位置)
+ * @param {Number} position 删除元素的位置
+ * @return {*} 被删除的元素
+ */
+ removeAt(position){
+ if(position >= 0 && position <= this.length){
+ let current = this.head,
+ index = 0,
+ previous = null
+ if(position === 0){ // 删除第一项
+ this.head = current.next
+ }else{
+ while(index++ < position){
+ previous = current
+ current = current.next
+ }
+ previous.next = current.next
+ }
+ this.length --
+ return current.element
+ }
+ }
+ /**
+ * 查找指定元素的位置
+ * @param {*} element 查找的元素
+ * @return {Number} 查找的元素的下标
+ */
+ indexOf(element){
+ let current = this.head,
+ index = 0
+ while(current.next && current.element !== element){
+ current = current.next
+ index ++
+ }
+ return index === 0 ? -1 : index
+ }
+ /**
+ * 链表是否为空
+ * @return {Boolean}
+ */
+ isEmpty(){
+ return this.length === 0
+ }
+ /**
+ * 链表的长度
+ * @return {Number}
+ */
+ size(){
+ return this.length
+ }
+ /**
+ * 将链表转成字符串
+ * @return {String}
+ */
+ toString(){
+ let current = this.head,
+ arr = new Array()
+ while(current.next){
+ arr.push(current.element)
+ current = current.next
+ }
+ arr.push(current.element)
+ return arr.toString()
+ }
+}
+
+let leo = new LinkedList()
+leo.append(3)
+leo.append(6)
+leo.append(9)
+console.log(leo.length)
+console.log(leo.head)
+leo.remove(6)
+console.log(leo.length)
+console.log(leo.head)
+console.log(leo.toString())
+```
+
+## 三、实现反转链表
+用链表的方式,输出一个反转后的单链表。
+
+示例:
+```js
+输入: 1->2->3->4->5->NULL
+输出: 5->4->3->2->1->NULL
+// input
+let head = {
+ 'val': 1,'next': {
+ 'val': 2,'next': {
+ 'val': 3,'next': {
+ 'val': 4,'next': {
+ 'val': 5,
+ 'next': null
+ }
+ }
+ }
+ }
+};
+reverseList(head)
+
+// output
+head = {
+ 'val': 5,'next': {
+ 'val': 4,'next': {
+ 'val': 3,'next': {
+ 'val': 2,'next': {
+ 'val': 1,
+ 'next': null
+ }
+ }
+ }
+ }
+};
+```
+
+**解题思路1.使用迭代:**
+在遍历列表时,将当前节点的 `next` 指针改为**指向前一个元素**。由于节点没有引用其上一个节点,因此必须**先存储其前一个元素**。在更改引用之前,还需要另一个指针来存储下一个节点。**不要忘记在最后返回新的头引用**!
+
+**解题思路2.使用递归:**
+通过递归修改 `head.next.next` 和 `head.next` 指针来实现。
+
+
+----
+解析:
+题目出自:[Leetcode 206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)
+
+介绍两种常用方法:
+
+**1.使用迭代:**
+在遍历列表时,将当前节点的 `next` 指针改为**指向前一个元素**。由于节点没有引用其上一个节点,因此必须**先存储其前一个元素**。在更改引用之前,还需要另一个指针来存储下一个节点。**不要忘记在最后返回新的头引用**!
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+let reverseList = function(head) {
+ let pre = null, curr = head
+ while (curr) {
+ next = curr.next
+ curr.next = pre
+ pre = curr
+ curr = next
+ }
+ return pre
+};
+```
+**复杂度分析**
+
+**时间复杂度**:`O(n)`。 假设 `n` 是列表的长度,时间复杂度是 `O(n)`。
+**空间复杂度**:`O(1)`。
+
+
+**2.使用递归:**
+
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+let reverseList = function(head) {
+ if(head == null || head.next == null) return head
+ let pre = reverseList(head.next)
+ head.next.next = head
+ head.next = null
+ return pre
+};
+```
+**复杂度分析**
+
+**时间复杂度**:`O(n)`。 假设 `n` 是列表的长度,那么时间复杂度为 `O(n)`。
+**空间复杂度**:`O(n)`。 由于使用递归,将会使用隐式栈空间。递归深度可能会达到 `n` 层。
+
+
+## 四、判断链表是否有环
+设计一个函数 `hasCycle`,接收一个链表作为参数,判断链表中是否有环。
+为了表示给定链表中的环,我们使用整数 `pos` 来表示**链表尾**连接到**链表中的位置**(索引从 `0` 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。
+
+
+
+需要注意的是,不可能存在多个环,最多只有一个。
+
+**示例 1:**
+```
+输入:head = [3,2,0,-4], pos = 1
+输出:true
+解释:链表中有一个环,其尾部连接到第二个节点。
+```
+
+
+**示例 2:**
+```
+输入:head = [1,2], pos = 0
+输出:true
+解释:链表中有一个环,其尾部连接到第一个节点。
+```
+
+
+**示例 3:**
+```
+输入:head = [1], pos = -1
+输出:false
+解释:链表中没有环。
+```
+
+
+
+
+**解题思路1.判断是否有 null:**
+一直遍历下去,如果遍历到 `null` 则表示没有环,否则有环,但是考虑到性能问题,最好给定一段时间作为限制,超过时间就不要继续遍历。
+
+**解题思路2.标记法:**
+也是要遍历每个节点,并在遍历的节点添加标记,如果后面遍历过程中,遇到有这个标记的节点,即表示有环,反之没有环。
+
+**解题思路3.使用双指针(龟兔赛跑式):**
+设置2个指针,一个 `快指针` 每次走 2 步,`慢指针` 每次走 1 步,如果没有环的情况,最后这两个指针不会相遇,如果有环,会相遇。
+
+----
+解析:
+题目出自:[Leetcode 141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/)
+
+**1.断是否有 null**
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+let hasCycle = function(head) {
+ while(head){
+ if(head.value == null) return true
+ head.value = null
+ head = head.next
+ }
+ return false
+}
+```
+
+**2.标记法**
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+let hasCycle = function(head) {
+ let node = head
+ while(node){
+ if(node.isVisit){
+ return true
+ }else{
+ node.isVisit = true
+ }
+ node = node.next
+ }
+ return false
+};
+```
+
+**3.使用双指针**
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+
+/**
+ * @param {ListNode} head
+ * @return {boolean}
+ */
+let hasCycle = function(head) {
+ if(head == null || head.next == null) return false
+ let slow = head, fast = head.next
+ while(slow != fast){
+ if(fast == null || fast.next == null) return false
+ slow = slow.next // 慢指针每次走1步
+ fast = fast.next.next // 快指针每次走1补
+ }
+ return true
+};
+```
+
+
+## 五、实现两两交换链表中的节点
+给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
+
+**你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。
+
+**示例:**
+```
+给定 1->2->3->4, 你应该返回 2->1->4->3.
+给定 1->2->3->4->5, 你应该返回 2->1->4->3->5.
+```
+
+**解题思路1.使用迭代:**
+和**反转链表**类似,关键在于有三个指针,分别指向前后和当前节点,而不同在于两两交换后,移动节点的步长为2,需要注意。
+
+**解题思路2.使用递归:**
+这里也可以使用递归,也可以参考**反转链表**的问题,终止条件是递归到链表为空,或者只剩下一个元素没得交换了,才终止。
+
+----
+解析:
+题目出自:[Leetcode 24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
+
+介绍两种常用方法:
+
+**1.使用迭代:**
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+let swapPairs = function (head){
+ if(!head) return null
+ let arr = []
+ while(head){
+ let next = head.next
+ head.next = null
+ arr.push(head)
+ head = next
+ }
+
+ for(let i = 0; i < arr.length; i += 2){
+ let [a, b] = [arr[i], arr[i + 1]]
+ if(!b) continue
+ [arr[i], arr[i + 1]] = [b, a]
+ }
+
+ for(let i = 0; i < arr.length - 1; i ++){
+ arr[i].next = arr[i + 1]
+ }
+ return arr[0]
+}
+```
+
+**2.使用递归:**
+```js
+/**
+ * Definition for singly-linked list.
+ * function ListNode(val) {
+ * this.val = val;
+ * this.next = null;
+ * }
+ */
+/**
+ * @param {ListNode} head
+ * @return {ListNode}
+ */
+
+let swapPairs = function (head){
+ if(head == null || head.next ==null) return head
+ let next = head.next
+ head.next = swapPairs(next.next)
+ next.next = head
+ return next
+}
+```
+
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-Queue.md b/Cute-Algorithms/Cute-Algorithms-Queue.md
new file mode 100644
index 00000000..1a6ce1f4
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-Queue.md
@@ -0,0 +1,363 @@
+
+
+这是第二周的练习题,这里补充下咯,五一节马上就要到了,自己的计划先安排上了,开发一个有趣的玩意儿。
+
+下面是之前分享的链接:
+* 1.[每周一练 之 数据结构与算法(Stack)](https://juejin.im/post/5cb2df0c5188251aca7340a0)
+* 2.[每周一练 之 数据结构与算法(LinkedList)](https://juejin.im/post/5cbdbb1af265da036d79bb35)
+
+> 欢迎关注我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+
+**本周练习内容:数据结构与算法 —— Queue**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+
+## 一、队列有什么特点,生活中有什么例子?
+
+----
+解题:
+**1.概念介绍**
+> 队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。 ——《维基百科》
+
+队列特点:**先进先出**操作。
+生活中的案例:常见的排队,在电影院也好,排队结账也是,排在第一位的人会先接受服务。
+
+**2.与堆栈区别**
+队列的操作方式和堆栈类似,唯一的区别在于**队列只允许新数据在后端进行添加。**
+
+
+## 二、请实现一个队列,并实现以下方法:
+
+* `enqueue(element)`:向队列尾部添加一个新的项。
+* `dequeue()`:移除队列的第一项,并返回被移除的元素。
+* `front()`:返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与 `Stack` 类的 `peek` 方法类似)。
+* `tail()`:返回队列中的最后一个元素,队列不做任何变动。
+* `isEmpty()`:如果栈没有任何元素就返回 `true`,否则返回 `false`。
+* `size()`:返回队列包含的的元素个数,与数组的 `length` 属性类似。
+* `print()`:打印队列中的元素。
+
+
+**提示:Web 端优先使用 ES6 以上的语法实现。**
+
+----
+解题:
+
+```js
+ /**
+ * 2. 实现一个队列
+ */
+class Queue {
+ constructor (){
+ this.items = []
+ }
+ // enqueue(element):向队列尾部添加一个新的项。
+ enqueue( element ){
+ this.items.push(element)
+ }
+ // dequeue():移除队列的第一项,并返回被移除的元素。
+ dequeue (){
+ return this.items.shift()
+ }
+ // front():返回队列中第一个元素 —— 最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息 —— 与 Stack 类的 peek 方法类似)。
+ front (){
+ return this.items[0]
+ }
+ // tail():返回队列中的最后一个元素,队列不做任何变动。
+ tail (){
+ return this.items[this.items.length-1]
+ }
+ // isEmpty():如果栈没有任何元素就返回 true,否则返回 false。
+ isEmpty (){
+ return this.items.length === 0
+ }
+ // size():返回队列包含的的元素个数,与数组的 length 属性类似。
+ size (){
+ return this.items.length
+ }
+ // print():打印队列中的元素。
+ print (){
+ console.log(this.items.toString())
+ }
+}
+```
+
+
+## 三、使用队列计算斐波那契数列的第 n 项。
+
+斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:
+```
+1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610...
+```
+
+在数学上,斐波那契数列以如下被以递推的方法定义:**F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)**,即**前两项固定为 1**,**后面的项为前两项之和**,依次向后。在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。
+
+使用示例如下:
+```js
+fibonacci(5); --> 5
+fibonacci(9); --> 34
+fibonacci(14); --> 377
+```
+
+---
+解题:
+
+**解题方法1:**
+```js
+/**
+ * 3. 使用队列计算斐波那契数列的第 n 项。
+ * 前两项固定为 1,后面的项为前两项之和,依次向后。
+ * @param {Number} num
+ */
+
+function fibonacci (num){
+ if(isNaN(num) || num < 0 || num === 0) return 0
+ // // 1. 直接
+ // let n1 = 1, n2 = 1, sum
+ // for(let i = 3; i <= num; i++){
+ // sum = n1 + n2
+ // n1 = n2
+ // n2 = sum
+ // }
+ // // 2. 队列 考虑小于等于2
+ // let arr = [], sum
+ // num === 1 && (arr = [1])
+ // num >= 2 && (arr = [1, 1])
+ // for(let i = 3; i <= num; i ++){
+ // sum = arr[i-2] + arr[i-3]
+ // arr.push(sum)
+ // }
+ // // 3.队列 进出队列
+ let queue = [], sum;
+ for(let i = 1; i <= num; i ++){
+ if(i <=2 ){
+ queue.push(1)
+ }else{
+ sum = queue[0] + queue[1]
+ queue.push(sum)
+ queue.shift()
+ }
+ }
+ return sum
+}
+```
+
+**解题方法2:**
+```js
+function fibonacci(n) {
+ const queue = new Queue();
+ queue.enqueue(1);
+ queue.enqueue(1);
+
+ let index = 0;
+ while(index < n - 2) {
+ index += 1;
+ // 出队列一个元素
+ const delItem = queue.dequeue();
+ // 获取头部值
+ const headItem = queue.front();
+ const nextItem = delItem + headItem;
+ queue.enqueue(nextItem);
+ }
+ return queue.tail();
+}
+console.log(fibonacci(9)); // 34
+```
+
+
+## 四、实现优先队列 PriorityQueue。
+
+现实中优先队列的例子很多,比如机场登机的顺序,头等舱和商务舱乘客优先级高于经济舱乘客。又如在银行中办理业务时,VIP 客户的优先级高于普通客户。要实现一个优先队列,有两种方式:
+
+1. 设置优先级,然后在正确的位置添加元素。
+2. 用入列操作添加元素,然后按照优先级移除它们。
+
+
+**本题要求使用第一种方式来实现优先队列,数值越小优先级越高,若优先级相同时,先入队的元素,排在前面。**
+
+使用示例如下:
+```js
+let priorityQueue = new PriorityQueue();
+priorityQueue.enqueue("leo", 2);
+priorityQueue.enqueue("pingan", 1);
+priorityQueue.enqueue("robin", 1);
+priorityQueue.print();
+// pingan - 1
+// robin - 1
+// leo - 2
+```
+
+---
+解题:
+
+**解题方法1:**
+```js
+class PriorityQueue {
+ constructor() {
+ this._items = [];
+ }
+
+ enqueue(element, priority) {
+ let queueElement = {
+ element
+ priority
+ };
+
+ if (this.isEmpty()) {
+ this._items.push(queueElement);
+ } else {
+ let added = false;
+ for (var i = 0; i < this.size(); i++) {
+ if (queueElement.priority < this._items[i].priority) {
+ this.items.splice(i, 0, queueElement);
+ added = true;
+ break ;
+ }
+ }
+
+ if (!added) {
+ this._items.push(queueElement);
+ }
+ }
+ }
+
+ print() {
+ var strArr = [];
+ strArr = this._items.map(function (item) {
+ return `${item.element}->${item.priority}`;
+ });
+
+ console.log(strArr.toString());
+ }
+}
+```
+
+**解题方法2:**
+```js
+/**
+ * 4. 实现优先队列
+ */
+
+class PriorityQueue {
+ constructor (){
+ this.items = []
+ }
+ enqueue (element, priority){
+ let ele = {element, priority}
+ let isAdded = false
+ for(let i = 0; i < this.items.length; i++){
+ if(ele.priority < this.items[i].priority){
+ this.items.splice(i, 0, ele)
+ isAdded = true
+ break
+ }
+ }
+ !isAdded && this.items.push(ele)
+ }
+ print (){
+ for(let i = 0; i < this.items.length; i++){
+ let {element, priority} = this.items[i]
+ console.log(`${element} - ${priority}`)
+ }
+ }
+}
+let leo = new PriorityQueue()
+leo.enqueue("leo", 2);
+leo.enqueue("leo1", 1);
+leo.enqueue("leo2", 1);
+console.log(leo)
+```
+
+## 五、用队列实现栈。
+
+利用两个队列实现栈,栈的特点是后进先出,可以让元素入队 `q1`,留下队尾元素让其他元素出队,暂存到 `q2` 中,再让 `q1` 中剩下的元素出队,即最后进的最先出来。
+
+
+**提示:入栈和出栈都在 q1 中完成,q2 只作为临时中转空间。**
+
+---
+解题:
+```js
+/**
+ * 5. 队列实现栈
+ */
+class Myqueue {
+ constructor (){
+ this.items = []
+ }
+ enqueue (element){
+ this.items.push(element)
+ }
+ dequeue (){
+ return this.items.shift()
+ }
+}
+class Mystack {
+ constructor (){
+ this.q1 = new myQueue()
+ this.q2 = new myQueue()
+ }
+ push (element){
+ this.q1.enqueue(element)
+ this.q2.items = []
+ let len = this.q1.items.length
+ while(len > 0){
+ this.q2.enqueue(this.q1.items[len-1])
+ len --
+ }
+ }
+ pop (){
+ let result = this.q2.dequeue()
+ let len = this.q2.items.length
+ this.q1.items = []
+ while(len > 0){
+ this.q1.enqueue(this.q2.items[len-1])
+ len --
+ }
+ return result
+ }
+ print (){
+ console.log(this.q1.items.toString())
+ }
+}
+```
+
+**这里也可以直接使用第二题定义的Queue来实现:**
+```js
+class QueueStack {
+ constructor() {
+ this.queue = new Queue();
+ }
+
+ push(item) {
+ this.queue.enqueue(item);
+ }
+
+ pop() {
+ // 向队列末尾追加 队列长度-1 次,后弹出队列头部
+ for(let i = 1; i < this.queue.size(); i += 1) {
+ this.queue.enqueue(this.queue.dequeue());
+ }
+ return this.queue.dequeue();
+ }
+
+ peek() {
+ return this.queue.tail();
+ }
+}
+```
+
+## 下周预告
+下周将练习**集合(Set)** 的题目,五一要到咯,也要好好做自己一个项目了。
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-Set.md b/Cute-Algorithms/Cute-Algorithms-Set.md
new file mode 100644
index 00000000..4fedf7a0
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-Set.md
@@ -0,0 +1,268 @@
+这是第四周的练习题,五一放假结束,该收拾好状态啦。
+
+下面是之前分享的链接:
+* 1.[每周一练 之 数据结构与算法(Stack)](https://juejin.im/post/5cb2df0c5188251aca7340a0)
+* 2.[每周一练 之 数据结构与算法(LinkedList)](https://juejin.im/post/5cbdbb1af265da036d79bb35)
+* 3.[每周一练 之 数据结构与算法(Queue)](https://juejin.im/post/5cc3cbaaf265da03a85ac7f8)
+
+> 欢迎关注我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+
+**本周练习内容:数据结构与算法 —— Set**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+## 一、集合是什么?与它相关数学概念有哪些
+
+---
+解题:
+**1.集合定义:**
+**集合(Set)**是一种包含不同元素的数据结构。集合中的元素称为**成员**,集合最重要的两个特点:
+* 集合中的成员是无序;
+* 集合中不存在相同成员;
+
+即:无序且唯一。
+
+**2.集合相关的数学概念:**
+集合的概念,如数学中一个由大于或等于0的整数组成的自然数集合, `N = { 0, 1, 2, ...}`。
+还有如**空集**,表示不包含任何元素的集合。
+并且也有并集,交集,差集等操作。
+
+
+## 二、请实现一个集合,并实现以下方法
+
+`add(value)`:向集合添加一个新的项。
+`delete(value)`:从集合移除一个值。
+`has(value)`:如果值在集合中,返回 true,否则返回 false。
+`clear()`:移除集合中的所有项。
+`size()`:返回集合所包含元素的数量。与数组的 length 属性类似。
+`values()`:返回一个包含集合中所有值的数组。
+
+---
+解题:
+```js
+class Sets {
+ constructor(){
+ this.items = {}
+ }
+ has(value){
+ // return value in this.items
+ return this.items.hasOwnProperty(value)
+ }
+ add(value){
+ if(!this.has(value)) {
+ this.items[value] = value
+ return true
+ }
+ return false
+ }
+ delete(value){
+ if(!this.has(value)){
+ delete this.items[value]
+ return true
+ }
+ return false
+ }
+ clear(){
+ this.items = {}
+ }
+ size(){
+ const values = this.values()
+ return values.length
+ }
+ values(){
+ return Object.keys(this.items)
+ }
+}
+```
+
+## 三、请实现集合的并集、交集、差集、子集操作
+* **并集(union)**:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。
+* **交集(intersection)**:对于给定的两个集合,返回一个包含两个集合中共用元素的新集合。
+* **差集(difference)**:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。
+* **子集(subset)**:验证一个给定集合是否是另一个集合的子集。
+
+---
+解题:
+```js
+/**
+ * union 并集
+ * @param {Object} otherSet 其他集合
+ */
+Sets.prototype.union = function(otherSet){
+ let result = new Sets(),
+ current = this.values(),
+ other = otherSet.values()
+ for(let i = 0; i < current.length; i++){
+ result.add(current[i])
+ }
+ for(let i = 0; i < other.length; i++){
+ result.add(other[i])
+ }
+ return result
+}
+
+
+/**
+ * intersection 交集
+ * @param {Object} otherSet 其他集合
+ */
+Sets.prototype.intersection = function(otherSet){
+ let result = new Sets(),
+ current = this.values()
+ for(let i = 0; i < current.length; i++){
+ if(otherSet.has(current[i])){
+ result.add(current[i])
+ }
+ }
+ return result
+}
+
+
+/**
+ * difference 差集
+ * @param {Object} otherSet 其他集合
+ */
+Sets.prototype.difference = function(otherSet){
+ let result = new Sets(),
+ current = this.values()
+ for(let i = 0; i < current.length; i++){
+ if(!otherSet.has(current[i])){
+ result.add(current[i])
+ }
+ }
+ return result
+}
+
+
+
+/**
+ * subset 子集
+ * @param {Object} otherSet 其他集合
+ */
+Sets.prototype.subset = function(otherSet){
+ let result = new Sets(),
+ current = this.values()
+
+ if(this.size() > otherSet.size()) return false
+ for(let i = 0; i < current.length; i++){
+ if(!otherSet.has(current[i])){
+ return false
+ }
+ }
+ return true
+}
+```
+
+## 四、给定两个数组,编写一个 intersection() 函数来计算它们的交集
+
+
+使用示例如下:
+```js
+const nums1 = [1, 2, 2, 1];
+const nums2 = [2, 2];
+const nums3 = [4, 9, 5];
+const nums4 = [9, 4, 9, 8, 4];
+
+intersection(nums1, nums2); // [2]
+intersection(nums3, nums4); // [9, 4]
+```
+
+**提示:输出结果中的每个元素是唯一的,可以不考虑输出结果的顺序。**
+
+---
+解题:
+```js
+function intersection(arr1, arr2){
+ if(!Array.isArray(arr1) || !Array.isArray(arr2)) return []
+ let create = function(arr){
+ let sets = new Sets()
+ arr.map(item => sets.add(item))
+ return sets
+ }
+ let Sets1 = create(arr1)
+ let Sets2 = create(arr2)
+ let result = Sets1.intersection(Sets2)
+ return result.values()
+}
+```
+
+## 五、给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集
+
+使用示例如下:
+```js
+const nums = [1, 2, 3];
+subsets(nums);
+// 输出以下结果:
+[
+ [3],
+ [1],
+ [2],
+ [1, 2, 3],
+ [1, 3],
+ [2, 3],
+ [1, 2],
+ []
+]
+```
+来源:[leetcode 78.集合](https://leetcode-cn.com/problems/subsets/)
+
+---
+解题:
+
+**目前网络上的最优解:**
+```js
+function subsets(nums){
+ if(!nums || !Array.isArray(nums)) return []
+
+ function diff (num, vec) {
+ let tmp = vec.slice(0)
+ result.push(tmp)
+ for (let i = num; i < len; i++) {
+ vec.push(nums[i])
+ diff(i + 1, vec)
+ vec.splice(-1)
+ }
+ }
+
+ const len = nums.length
+ let arr = [], result = []
+ diff(0, arr)
+ return result
+}
+```
+
+**穷举法:**
+```js
+function subsets(nums){
+ if(!nums || !Array.isArray(nums)) return []
+
+ let result = [[]],
+ len = nums.length
+ if(len === 0) return result
+ for(let i = 0; i < len; i++){
+ let l = result.length
+ let num = nums[i]
+ let array = [num]
+ for(let j = 0; j < l; j++){
+ let tmparray = result[j].concat(array)
+ result.push(tmparray)
+ }
+ }
+ return result
+}
+```
+
+## 下周预告
+下周将练习**Dictionary 和 HashTable** 的题目。
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-Stack.md b/Cute-Algorithms/Cute-Algorithms-Stack.md
new file mode 100644
index 00000000..a847572a
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-Stack.md
@@ -0,0 +1,313 @@
+最近公司内部在开始做前端技术的技术分享,每周一个主题的 **每周一练**,以**基础知识**为主,感觉挺棒的,跟着团队的大佬们学习和复习一些知识,新人也可以多学习一些知识,也把团队内部学习氛围营造起来。
+
+我接下来会开始把每周一练的题目和知识整理一下,便于思考和巩固,就像今天这篇开始。
+
+学习的道路,很漫长,要坚持,希望大家都能掌握自己喜欢的技术,和自己需要的技术。
+
+> 欢迎查看我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+**本周练习内容:数据结构与算法 —— Stack**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+## 一、栈有什么特点,生活中有什么例子?
+* 栈( stack )又称**堆栈**,是一种后进先出的**有序集合**,其中一端为栈顶,另一端为栈底,添加元素(称为压栈/入栈或进栈)时,将新元素压入栈顶,删除元素(称为出栈或退栈)时,将栈底元素删除并返回被删除元素。
+* 特点:**先进后出,后进先出**。
+* 例子:一叠书、一叠盘子。
+
+
+
+## 二、实现一个栈,并实现下面方法
+* `push(element)`:添加一个新元素到栈顶。
+* `pop()`:移除栈顶的元素,同时返回被移除的元素。
+* `peek()`:返回栈顶的元素,不对栈做任何修改 (这个方法不会移除栈顶的元素,仅仅返回它)。
+* `isEmpty()`:如果栈没有任何元素就返回 `true`,否则返回 `false`。
+* `clear()`:移除栈里面的所有元素。
+* `size()`:返回栈里的元素个数。这个方法与数组的 `length` 属性类似。
+
+**方法1:ES6实现**
+```js
+class Stack {
+ constructor (){
+ this.items = []
+ }
+ push( element ){
+ this.items.push(element)
+ }
+ pop(){
+ return this.items.pop()
+ }
+ peek(){
+ return this.items[this.items.length - 1]
+ }
+ isEmpty(){
+ return this.items.length === 0
+ }
+ clear(){
+ this.items = []
+ }
+ size(){
+ return this.items.length
+ }
+}
+```
+上面实现的方式虽然简单,但是内部 `items` 属性是公共的,为了满足面向对象变成私有性的原则,我们应该让 `items` 作为私有属性,因此我们可以使用 ES6 中 `Symbol` 或 `WeakMap` 来实现:
+
+**方法2:使用 ES6 的 Symbol 基本数据类型实现**
+知识点复习:[ES6 中的 Symbol 介绍](http://es6.ruanyifeng.com/#docs/symbol)
+```js
+const _items = Symbol()
+class Stack {
+ constructor (){
+ this[_items] = []
+ }
+ push (element){
+ this[_items].push(element)
+ }
+ // 剩下方法和第一种实现的差不多,这里省略
+ // 只要把前面方法中的 this.items 更改为 this[_items]
+}
+```
+
+**方法3:使用 ES6 的 WeakMap 实现**
+知识点复习:[ES6 中的 WeakMap 介绍](http://es6.ruanyifeng.com/#docs/set-map#WeakMap)
+```js
+const items = new WeakMap()
+class Stack {
+ constructor (){
+ items.set(this, [])
+ }
+ push (element){
+ let item = items.get(this)
+ item.push(element)
+ }
+ // 剩下方法和第一种实现的差不多,这里省略
+ // 只要把前面方法中的获取 this.items 的方式,更改为 items.get(this) 获取
+}
+```
+
+## 三、编写一个函数,实现十进制转二进制
+题目意思很简单,就是十进制转二进制,但是在实际工作开发中,我们更愿意实现的是任意进制转任意进制,不过呢,我们还是以解决问题为首要目标呀。
+
+当然,业务需求可以直接使用 `toString(2)` 方法,但是为了练习,咱还是不这么用咯。
+
+**方法1:使用前面定义的 Stack 类**
+这里使用前面题目中定义的 `Stack` 类。
+```js
+/**
+ * 十进制转换为二进制
+ * @param {Number} bit
+ */
+function bitset (bit){
+ if(bit == 0) return '0'
+ if(!/^[0-9]+.?[0-9]*$/.test(bit)){
+ return new Error('请输入正确的数值!')
+ }
+
+ let stack = new Stack(), result = ''
+ while (bit > 0){
+ stack.push(bit % 2)
+ bit = Math.floor(bit / 2)
+ }
+ while (!stack.isEmpty()){
+ result += stack.pop().toString()
+ }
+ return result
+
+}
+```
+
+**方法2:简单实现**
+下面这个方法,其实不太好,因为没有怎么用到这次要练习的**栈**方法,哈哈。
+```js
+/**
+ * 十进制转换为二进制
+ * @param {Number} bit
+ */
+function bitset (bit){
+ if(bit == 0) return '0'
+ if(!/^[0-9]+.?[0-9]*$/.test(bit)){
+ return new Error('请输入正确的数值!')
+ }
+
+ let arr = []
+ while(bit > 0){
+ arr.push(bit % 2)
+ bit = Math.floor(bit / 2)
+ }
+ return arr.reverse().join('')
+}
+```
+另外可以参考:[wikiHow - 从十进制转换为二进制](https://zh.wikihow.com/%E4%BB%8E%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E8%BF%9B%E5%88%B6)。
+
+
+## 四、编写一个函数,实现检验圆括号顺序的有效性
+主要目的就是:该函数接收一个**圆括号字符串**,判断里面的括号顺序是否有效,如果有效则返回 `true` 反之 `false`。
+如:
+* `(` -> `false`
+* `()` -> `true`
+* `(()` -> `false`
+* `())` -> `false`
+* `())` -> `false`
+* `(((()()))())` -> `true`
+
+这个题目实现的主要方法是:遍历字符串,先排除错误情况,然后将 `(` 入栈保存,将 `)` 入栈匹配前一个元素是否是 `(` ,如果是,则 `pop()` 前一个元素 `(`,如果不是,则 `push()` 这个 `)` 入栈,最终查看栈是否为空,若是则检验成功,否则失败。
+
+**方法1:使用前面定义的 Stack 类**
+这里使用前面题目中定义的 `Stack` 类。
+```js
+/**
+ * 检验圆括号顺序的有效性
+ * @param {String} str
+ */
+function validParentheses (str){
+ if(!str || str.length === 0 || str[0] === ')') return false
+
+ let stack = new Stack()
+ str.split('').forEach(char => {
+ let status = stack.peek() === '(' && char === ')'
+ status ? stack.pop() : stack.push(char)
+ })
+ return stack.isEmpty()
+}
+```
+
+
+**方法2:出入栈操作**
+```js
+/**
+ * 检验圆括号顺序的有效性
+ * @param {String} str
+ */
+function validParentheses (str){
+ if(!str || str.length === 0 || str[0] === ')') return false
+
+ let arr = []
+ for(let i = 0; i < str.length ; i++){
+ str[i] === '(' ? arr.push(str[i]) : arr.pop()
+ }
+ return arr.length === 0
+}
+```
+
+## 五、改造题二,添加一个 min 函数来获得栈中最小元素
+
+|步骤|数据栈|辅助栈|最小值|
+|---|---|---|---|
+|1.push 3|3|0|3|
+|2.push 4|3, 4|0, 0|3|
+|3.push 2|3, 4, 2|0, 0, 2|2|
+|4.push 1|3, 4, 2 ,1|0, 0, 2, 3|1|
+|5.pop |3, 4, 2|0, 0, 2|2|
+|6.pop |3, 4|0, 0|3|
+|7.push |3, 4 ,0|0, 0, 2|0|
+
+使用示例如下:
+```js
+let stack = new Stack();
+stack.push(3);
+console.log('After push 3, Min item is', stack.min());
+stack.push(4);
+console.log('After push 4, Min item is', stack.min());
+stack.push(2);
+console.log('After push 2, Min item is', stack.min());
+stack.push(1);
+console.log('After push 1, Min item is', stack.min());
+stack.pop();
+console.log('After pop, Min item is', stack.min());
+stack.pop();
+console.log('After pop, Min item is', stack.min());
+stack.push(0);
+console.log('After push 0, Min item is', stack.min());
+```
+
+**提示:利用辅助栈(Web 端可利用数组),每次对栈 push/pop 元素时,也同时更新辅助栈(存储最小元素的位置)**
+
+**方法1:小操作**
+```js
+class Stack {
+ constructor() {
+ this.items = [];
+ this.minIndexStack = [];
+ }
+
+ push(element) {
+ this.items.push(element);
+ let minLen = this.minIndexStack.length;
+ let minItemIndex = this.minIndexStack[minLen - 1];
+ if(minLen === 0 || this.items[minItemIndex] > item) {
+ this.minIndexStack.push(this.items.length - 1);
+ } else {
+ this.minIndexStack.push(minItemIndex);
+ }
+ }
+
+ pop() {
+ this.minIndexStack.pop();
+ return this.items.pop();
+ }
+
+ min() {
+ let len = this.minIndexStack.length;
+ return (len > 0 && this.items[this.minIndexStack[len - 1]]) || 0;
+ }
+
+ peek() {
+ return this.items[this.items.length - 1];
+ }
+
+ // 省略其它方法
+}
+```
+
+
+**方法2:与方法1中push实现的差异**
+```js
+class Stack {
+ constructor (){
+ this.items = [] // 数据栈
+ this.arr = [] // 辅助栈
+ }
+ push( element ){
+ this.items.push(element)
+ let min = Math.min(...this.items)
+ this.arr.push( min === element ? this.size() - 1 : 0)
+ }
+ pop(){
+ this.arr.pop()
+ return this.items.pop()
+ }
+ peek(){
+ return this.items[this.items.length - 1]
+ }
+ isEmpty(){
+ return this.items.length === 1
+ }
+ clear(){
+ this.items = []
+ }
+ size(){
+ return this.items.length
+ }
+ min (){
+ let last = this.arr[this.arr.length - 1]
+ return this.items[last]
+ }
+}
+```
+
+## 下周预告
+下周将练习**队列(Queue)** 的题目,开始翻起算法书籍学习咯。
+
+
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/Cute-Algorithms-Tree.md b/Cute-Algorithms/Cute-Algorithms-Tree.md
new file mode 100644
index 00000000..24a18190
--- /dev/null
+++ b/Cute-Algorithms/Cute-Algorithms-Tree.md
@@ -0,0 +1,387 @@
+
+
+这是第六周的练习题,最近加班比较多,上周主要完成一篇 [GraphQL入门教程](https://juejin.im/post/5cd56b1f6fb9a0321e16bde3) ,有兴趣的小伙伴可以看下哈。
+
+下面是之前分享的链接:
+* 1.[每周一练 之 数据结构与算法(Stack)](https://juejin.im/post/5cb2df0c5188251aca7340a0)
+* 2.[每周一练 之 数据结构与算法(LinkedList)](https://juejin.im/post/5cbdbb1af265da036d79bb35)
+* 3.[每周一练 之 数据结构与算法(Queue)](https://juejin.im/post/5cc3cbaaf265da03a85ac7f8)
+* 4.[每周一练 之 数据结构与算法(Set)](https://juejin.im/post/5cceee526fb9a0323a01c72e)
+* 5.[每周一练 之 数据结构与算法(Dictionary 和 HashTable)](https://juejin.im/post/5ce2a196f265da1b7638738b)
+
+> 欢迎关注我的 [个人主页](https://github.com/pingan8787) && [个人博客](http://www.pingan8787.com/) && [个人知识库](http://js.pingan8787.com/) && 微信公众号“前端自习课”
+
+
+**本周练习内容:数据结构与算法 —— Tree**
+
+这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
+
+
+## 一、什么是树?
+1.树有什么特点,什么是二叉树和二叉搜索树(BST: Binary Search Tree)?
+2.生活中常见的例子有哪些?
+
+---
+解析:
+1. 树有什么特点,什么是二叉树和二叉搜索树:
+
+* **树**是一种**非线性的数据结构**,以**分层方式存储数据**,用来表示**有层级关系的数据**。
+
+* 每棵树至多只有一个**根结点**,**根结点**会有很多**子节点**,每个**子节点只有一个父结点**。
+
+* **父结点**和**子节点**是相对的。
+
+2. 生活中的例子:
+如:家谱、公司组织架构图。
+
+## 二、请实现二叉搜索树(BST),并实现以下方法:
+* `insert(key)`:向树中插入一个新的键;
+* `search(key)`:树中查找一个键,如果节点存在返回true,不存在返回false;
+* `min()`:返回树中最小的值/键;
+* `max()`:返回树中最大的值/键;
+* `remove(key)`:移除某个键;
+
+
+> 提示:所谓的键对应于之前章节所学的节点(Node)
+
+```js
+class Node {
+ constructor(key){
+ this.key = key
+ this.left = null
+ this.right = null
+ }
+}
+class BST {
+ constructor(){
+ this.root = null
+ }
+ /**
+ * 插入一个节点
+ * @param {*} node 插入的位置节点
+ * @param {*} newNode 插入的节点
+ */
+ insertNode (node, newNode){
+ if(newNode.key < node.key){
+ if(node.left === null && node.right === null){
+ node.left = newNode
+ }else if(node.left !== null && node.right === null){
+ node.right = newNode
+ }else{
+ this.insertNode(node.left, newNode)
+ }
+ }else{
+ if(node.left === null && node.right === null){
+ node.left = newNode
+ }else if(node.left !== null && node.right === null){
+ node.right = newNode
+ }else{
+ this.insertNode(node.right, newNode)
+ }
+ }
+ }
+ /**
+ * 插入操作
+ * @param {*} key
+ */
+ insert (key){
+ let newNode = new Node(key)
+ if(this.root === null){
+ this.root = newNode
+ }else{
+ this.insertNode(this.root, newNode)
+ }
+ }
+ searchNode (node, key){
+ if(node === null) return false
+ if(key < node.key){
+ return this.searchNode(node.left, key)
+ }else if(key > node.key){
+ return this.searchNode(node.right, key)
+ }else{
+ return true
+ }
+ }
+ /**
+ * 搜索操作
+ * @param {*} key
+ */
+ search (key){
+ return this.searchNode(this.root, key)
+ }
+ /**
+ * 最小值的节点
+ */
+ min (){
+ let node = this.root
+ if(node === null) return null
+ while(node && node.left !== null){
+ node = node.left
+ }
+ return node.key
+ }
+ /**
+ * 最大值的节点
+ */
+ max (){
+ let node = this.root
+ if(node === null) return null
+ while(node && node.right !== null){
+ node = node.right
+ }
+ return node.key
+ }
+ /**
+ * 找到最小节点
+ * @param {*} node
+ */
+ findMinNode (node){
+ if(node === null) return null
+ while(node && node.left !== null){
+ node = node.left
+ }
+ return node
+ }
+ /**
+ * 删除一个节点
+ * @param {*} node
+ * @param {*} key
+ */
+ removeNode (node, key){
+ if(node === null) return null
+ if(key < node.key){
+ node.left = this.removeNode(node.left, key)
+ return node
+ }else if(key > node.key){
+ node.right = this.removeNode(node.right, key)
+ return node
+ }else{
+ // 1.叶节点
+ if(node.left === null && node.right === null){
+ node = null
+ return node
+ }
+ // 2.只有一个子节点
+ if(node.left === null){
+ node = node.right
+ return node
+ }else if(node.right === null){
+ node = node.left
+ }
+ // 3.有两个子节点
+ let curNode = this.findMinNode(node.right)
+ node.key = curNode.key
+ node.right = this.removeNode(node.right, curNode.key)
+ return node
+ }
+ }
+ /**
+ * 删除一个节点
+ * @param {*} key
+ */
+ remove (key){
+ if(this.root === null) return null
+ this.root = this.removeNode(this.root, key)
+ }
+}
+```
+
+
+## 三、基于题二实现二叉搜索树扩展以下方法:
+* `preOrderTraverse()`: 通过先序遍历方式遍历所有节点;
+* `inOrderTraverse()`: 通过中序遍历方式遍历所有节点;
+* `postOrderTraverse()`: 通过后序遍历方式遍历所有节点;
+
+
+提示:
+
+* 先序:先访问根节点,然后以同样方式访问左子树和右子树;(根==>左==>右)
+
+输出 =》 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
+
+
+* 中序:先访问左子树,再访问根节点,最后访问右字数;以升序访问所有节点;(左==>根==>右)
+
+输出 =》 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
+
+
+
+* 后序:先访问叶子节点,从左子树到右子树,再到根节点。(左==>右==>根)
+
+输出 =》 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
+
+
+
+
+---
+解析:
+```js
+// 1. 先序
+BST.prototype.preOrderTraverseNode = function(node, callback){
+ if(node !== null){
+ callback(node.key)
+ this.preOrderTraverseNode(node.left, callback)
+ this.preOrderTraverseNode(node.right, callback)
+ }
+}
+BST.prototype.preOrderTraverse = function(callback){
+ this.preOrderTraverseNode(this.root, callback)
+}
+
+// 2. 中序
+BST.prototype.inOrderTraverseNode = function(node, callback){
+ if(node !== null){
+ this.inOrderTraverseNode(node.left, callback)
+ callback(node.key)
+ this.inOrderTraverseNode(node.right, callback)
+ }
+}
+BST.prototype.inOrderTraverse = function(callback){
+ this.inOrderTraverseNode(this.root, callback)
+}
+
+// 3. 后序
+BST.prototype.postOrderTraverseNode = function(node, callback){
+ if(node !== null){
+ this.postOrderTraverseNode(node.left, callback)
+ this.postOrderTraverseNode(node.right, callback)
+ callback(node.key)
+ }
+}
+BST.prototype.postOrderTraverse = function(callback){
+ this.postOrderTraverseNode(this.root, callback)
+}
+```
+
+## 四、请实现从上往下打印二叉树
+给定的二叉树为:[3, 9 , 20, null, null, 15, 7]
+```
+ 3
+ / \
+ 9 20
+ / \
+ 15 7
+```
+
+请实现一个 `printLevelOrder` 方法,输出以下结果:
+```
+[
+ [3],
+ [9, 20],
+ [15, 7]
+]
+```
+---
+来源:[102.二叉树的层次遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/)
+解析:
+* 方法一:
+```js
+BST.prototype.printLevelOrder = function (root, arr = [], i = 0){
+ if (root && (root.key || root.key === 0)) {
+ !arr[i] && (arr[i] = [])
+ arr[i].push(root.key)
+ i++
+ root.left && this.printLevelOrder(root.left, arr, i)
+ root.right && this.printLevelOrder(root.right, arr, i)
+ }
+ return arr
+}
+```
+
+* 方法二:
+```js
+BST.prototype.printLevelOrder = function (){
+ if(this.root === null) return []
+ let result = [], queue = [this.root]
+ while(true){
+ let len = queue.length, arr = []
+ while(len > 0){
+ console.log(queue)
+ let node = queue.shift()
+ len -= 1
+ arr.push(node.key)
+ if(node.left !== null) queue.push(node.left)
+ if(node.right !== null) queue.push(node.right)
+ }
+ if(arr.length === 0) return result
+ result.push([...arr])
+ }
+}
+```
+
+## 五、给定一个二叉树,判断其是否是一个有效的二叉搜索树。
+假设一个二叉搜索树具有如下特征:
+
+* 节点的左子树只包含**小于**当前节点的数。
+* 节点的右子树只包含**大于**当前节点的数。
+* 所有左子树和右子树自身必须也是二叉搜索树。
+
+示例 1:
+```
+输入:
+ 2
+ / \
+ 1 3
+输出: true
+```
+
+示例 2:
+```
+输入:
+ 5
+ / \
+ 1 4
+ / \
+ 3 6
+输出: false
+解释: 输入为: [5,1,4,null,null,3,6]。
+根节点的值为 5 ,但是其右子节点值为 4 。
+```
+
+代码实现:
+```js
+/**
+ * 二叉树节点定义
+ */
+function TreeNode(val) {
+ this.val = val;
+ this.left = this.right = null;
+}
+
+/**
+- @param {TreeNode} root
+- @return {boolean}
+*/
+function isValidBST(root) {};
+```
+
+---
+来源:[99.验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)
+解析:
+```js
+function isValidBST(root) {
+ let arr = []
+ function inOrderTraverse(node){
+ if(node === null) return;
+ node.left && inOrderTraverse(node.left);
+ arr.push(node.val);
+ node.right && inOrderTraverse(node.right);
+ }
+ inOrderTraverse(root)
+ for(let i = 0; i < arr.length - 1; i++){
+ if(arr[i] >= arr[i+1]) return false
+ }
+ return true
+};
+```
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|ES小册|js.pingan8787.com|
+
+## 微信公众号
+
\ No newline at end of file
diff --git a/Cute-Algorithms/README.md b/Cute-Algorithms/README.md
new file mode 100644
index 00000000..106875be
--- /dev/null
+++ b/Cute-Algorithms/README.md
@@ -0,0 +1,30 @@
+## 💌仓库介绍
+**Cute-Algorithms 系列**主要分享我学习**数据结构与算法**的相关笔记,每种算法都会有 5 道题目来练习,喜欢的朋友欢迎 👉star。
+
+
+## 关于作者
+[](http://www.pingan8787.com)
+[](https://www.yuque.com/wangpingan/cute-frontend)
+[](https://zhuanlan.zhihu.com/cute-javascript)
+[](https://juejin.im/user/586fc337a22b9d0058807d53/posts)
+[](https://segmentfault.com/blog/pingan8787)
+[](https://blog.csdn.net/qq_36380426)
+[](https://www.jianshu.com/u/2ec5d94afd60)
+
+
+完整知识库,请查看我的【**语雀知识库**】,阅读体验更好。[💌跳转~](https://www.yuque.com/wangpingan/cute-frontend)
+
+
+## 💌文章目录
+
+1. [《数据结构与算法 - Tree》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Tree.md)
+
+2. [《数据结构与算法 - Stack》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Stack.md)
+
+3. [《数据结构与算法 - Queue》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Queue.md)
+
+4. [《数据结构与算法 - Set》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Set.md)
+
+5. [《数据结构与算法 - LinkedList》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Tree.md)
+
+6. [《数据结构与算法 - Dictionary&&HashTable》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Algorithms/Cute-Algorithms-Dictionary%26%26HashTable.md)
diff --git a/Cute-Angular/README.md b/Cute-Angular/README.md
new file mode 100644
index 00000000..b3f3b1d0
--- /dev/null
+++ b/Cute-Angular/README.md
@@ -0,0 +1,22 @@
+## 💌仓库介绍
+**Cute-GraphQL 系列**主要分享我学习 `Angular` 的一些学习笔记和资料,后面也有实战的案例等,喜欢的朋友欢迎 👉star。
+
+## 关于作者
+[](http://www.pingan8787.com)
+[](https://www.yuque.com/wangpingan/cute-frontend)
+[](https://zhuanlan.zhihu.com/cute-javascript)
+[](https://juejin.im/user/586fc337a22b9d0058807d53/posts)
+[](https://segmentfault.com/blog/pingan8787)
+[](https://blog.csdn.net/qq_36380426)
+[](https://www.jianshu.com/u/2ec5d94afd60)
+
+
+完整知识库,请查看我的【**语雀知识库**】,阅读体验更好。[💌跳转~](https://www.yuque.com/wangpingan/cute-frontend)
+
+## 💌文章目录
+
+1. [《Angular 入门教程》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Angular/books%E9%A1%B9%E7%9B%AEdemo/README.md)
+2. [《Angular 官网 demo》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Angular/angualr%E5%AE%98%E7%BD%91demo/README.md)
+3. [《Angular 知识点整理》](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Angular/%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86/README.md)
+
+
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/.editorconfig" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/.editorconfig"
new file mode 100644
index 00000000..6e87a003
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/.editorconfig"
@@ -0,0 +1,13 @@
+# Editor configuration, see http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/.gitignore" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/.gitignore"
new file mode 100644
index 00000000..ee5c9d83
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/.gitignore"
@@ -0,0 +1,39 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/README.md" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/README.md"
new file mode 100644
index 00000000..e41787b6
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/README.md"
@@ -0,0 +1,27 @@
+# AngularDemo
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.2.4.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/angular.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/angular.json"
new file mode 100644
index 00000000..1bb00277
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/angular.json"
@@ -0,0 +1,127 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "angular-demo": {
+ "root": "",
+ "sourceRoot": "src",
+ "projectType": "application",
+ "prefix": "app",
+ "schematics": {},
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/angular-demo",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.app.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "angular-demo:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "angular-demo:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "angular-demo:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "main": "src/test.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.spec.json",
+ "karmaConfig": "src/karma.conf.js",
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": [],
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ]
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "src/tsconfig.app.json",
+ "src/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ },
+ "angular-demo-e2e": {
+ "root": "e2e/",
+ "projectType": "application",
+ "architect": {
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "angular-demo:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "angular-demo:serve:production"
+ }
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": "e2e/tsconfig.e2e.json",
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "angular-demo"
+}
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/protractor.conf.js" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/protractor.conf.js"
new file mode 100644
index 00000000..86776a39
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/protractor.conf.js"
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './src/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: require('path').join(__dirname, './tsconfig.e2e.json')
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.e2e-spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.e2e-spec.ts"
new file mode 100644
index 00000000..820fb057
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.e2e-spec.ts"
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';
+
+describe('workspace-project App', () => {
+ let page: AppPage;
+
+ beforeEach(() => {
+ page = new AppPage();
+ });
+
+ it('should display welcome message', () => {
+ page.navigateTo();
+ expect(page.getParagraphText()).toEqual('Welcome to angular-demo!');
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.po.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.po.ts"
new file mode 100644
index 00000000..82ea75ba
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/src/app.po.ts"
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get('/');
+ }
+
+ getParagraphText() {
+ return element(by.css('app-root h1')).getText();
+ }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/tsconfig.e2e.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/tsconfig.e2e.json"
new file mode 100644
index 00000000..a6dd6220
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/e2e/tsconfig.e2e.json"
@@ -0,0 +1,13 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "jasminewd2",
+ "node"
+ ]
+ }
+}
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/package.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/package.json"
new file mode 100644
index 00000000..9680352b
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/package.json"
@@ -0,0 +1,49 @@
+{
+ "name": "angular-demo",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "test": "ng test",
+ "lint": "ng lint",
+ "e2e": "ng e2e"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^6.1.0",
+ "@angular/common": "^6.1.0",
+ "@angular/compiler": "^6.1.0",
+ "@angular/core": "^6.1.0",
+ "@angular/forms": "^6.1.0",
+ "@angular/http": "^6.1.0",
+ "@angular/platform-browser": "^6.1.0",
+ "@angular/platform-browser-dynamic": "^6.1.0",
+ "@angular/router": "^6.1.0",
+ "angular-in-memory-web-api": "^0.8.0",
+ "core-js": "^2.5.4",
+ "rxjs": "~6.2.0",
+ "zone.js": "~0.8.26"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.8.0",
+ "@angular/cli": "~6.2.4",
+ "@angular/compiler-cli": "^6.1.0",
+ "@angular/language-service": "^6.1.0",
+ "@types/jasmine": "~2.8.8",
+ "@types/jasminewd2": "~2.0.3",
+ "@types/node": "~8.9.4",
+ "codelyzer": "~4.3.0",
+ "jasmine-core": "~2.99.1",
+ "jasmine-spec-reporter": "~4.2.1",
+ "karma": "~3.0.0",
+ "karma-chrome-launcher": "~2.2.0",
+ "karma-coverage-istanbul-reporter": "~2.0.1",
+ "karma-jasmine": "~1.1.2",
+ "karma-jasmine-html-reporter": "^0.2.2",
+ "protractor": "~5.4.0",
+ "ts-node": "~7.0.0",
+ "tslint": "~5.11.0",
+ "typescript": "~2.9.2"
+ }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.spec.ts"
new file mode 100644
index 00000000..d68ef067
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.spec.ts"
@@ -0,0 +1,13 @@
+import { AppRoutingModule } from './app-routing.module';
+
+describe('AppRoutingModule', () => {
+ let appRoutingModule: AppRoutingModule;
+
+ beforeEach(() => {
+ appRoutingModule = new AppRoutingModule();
+ });
+
+ it('should create an instance', () => {
+ expect(appRoutingModule).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.ts"
new file mode 100644
index 00000000..2bbdb5fd
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app-routing.module.ts"
@@ -0,0 +1,26 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule, Routes } from '@angular/router';
+import { HeroesComponent } from './heroes/heroes.component';
+import { DashboardComponent } from './dashboard/dashboard.component';
+import { HeroDetailComponent } from './hero-detail/hero-detail.component';
+
+const routes: Routes = [
+ //把一个与空路径“完全匹配”的 URL 重定向到路径为 '/dashboard' 的路由
+ { path: '', redirectTo:'/dashboard', pathMatch:'full' },
+ { path: 'heroes', component: HeroesComponent },
+ { path: 'dashboard', component: DashboardComponent },
+ { path: 'detail/:id', component: HeroDetailComponent }
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ RouterModule.forRoot(routes), // 监听浏览器地址变化
+ ],
+ declarations: [],
+ exports: [
+ RouterModule
+ ]
+})
+export class AppRoutingModule { }
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.css"
new file mode 100644
index 00000000..b8ab4642
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.css"
@@ -0,0 +1,22 @@
+/* Application-wide Styles */
+h1 {
+ color: #369;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 250%;
+ }
+ h2, h3 {
+ color: #444;
+ font-family: Arial, Helvetica, sans-serif;
+ font-weight: lighter;
+ }
+ body {
+ margin: 2em;
+ }
+ body, input[type="text"], button {
+ color: #888;
+ font-family: Cambria, Georgia;
+ }
+ /* everywhere else */
+ * {
+ font-family: Arial, Helvetica, sans-serif;
+ }
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.html"
new file mode 100644
index 00000000..757ab484
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.html"
@@ -0,0 +1,14 @@
+
+
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.spec.ts"
new file mode 100644
index 00000000..5148b0a2
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.spec.ts"
@@ -0,0 +1,31 @@
+import { TestBed, async } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ AppComponent
+ ],
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'angular-demo'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('angular-demo');
+ });
+
+ it('should render title in a h1 tag', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-demo!');
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.ts"
new file mode 100644
index 00000000..eba78e65
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.component.ts"
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+ title = 'leo-angular-demo';
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.module.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.module.ts"
new file mode 100644
index 00000000..556af361
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/app.module.ts"
@@ -0,0 +1,35 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { HttpClientModule } from '@angular/common/http';
+import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
+import { InMemoryDataService } from './in-memory-data.service';
+
+import { AppComponent } from './app.component';
+import { HeroesComponent } from './heroes/heroes.component';
+import { HeroDetailComponent } from './hero-detail/hero-detail.component';
+import { MessagesComponent } from './messages/messages.component';
+import { AppRoutingModule } from './app-routing.module';
+import { DashboardComponent } from './dashboard/dashboard.component';
+import { HeroSearchComponent } from './hero-search/hero-search.component';
+
+// 存放关键性的元数据在@NgModule
+@NgModule({
+ declarations: [
+ AppComponent,
+ HeroesComponent,
+ HeroDetailComponent,
+ MessagesComponent,
+ DashboardComponent,
+ HeroSearchComponent
+ ],
+ imports: [
+ BrowserModule,FormsModule, AppRoutingModule,HttpClientModule,
+ HttpClientInMemoryWebApiModule.forRoot(
+ InMemoryDataService, {dataEncapsulation:false}
+ )
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.css"
new file mode 100644
index 00000000..122d4e37
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.css"
@@ -0,0 +1,63 @@
+/* DashboardComponent's private CSS styles */
+[class*='col-'] {
+ float: left;
+ padding-right: 20px;
+ padding-bottom: 20px;
+ }
+ [class*='col-']:last-of-type {
+ padding-right: 0;
+ }
+ a {
+ text-decoration: none;
+ }
+ *, *:after, *:before {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ h3 {
+ text-align: center;
+ margin-bottom: 0;
+ }
+ h4 {
+ position: relative;
+ }
+ .grid {
+ margin: 0;
+ }
+ .col-1-4 {
+ width: 25%;
+ }
+ .module {
+ padding: 20px;
+ text-align: center;
+ color: #eee;
+ max-height: 120px;
+ min-width: 120px;
+ background-color: #607d8b;
+ border-radius: 2px;
+ }
+ .module:hover {
+ background-color: #eee;
+ cursor: pointer;
+ color: #607d8b;
+ }
+ .grid-pad {
+ padding: 10px 0;
+ }
+ .grid-pad > [class*='col-']:last-of-type {
+ padding-right: 20px;
+ }
+ @media (max-width: 600px) {
+ .module {
+ font-size: 10px;
+ max-height: 75px; }
+ }
+ @media (max-width: 1024px) {
+ .grid {
+ margin: 0;
+ }
+ .module {
+ min-width: 60px;
+ }
+ }
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.html"
new file mode 100644
index 00000000..54711c92
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.html"
@@ -0,0 +1,12 @@
+Top Heroes
+
+
+
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.spec.ts"
new file mode 100644
index 00000000..9c996c37
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DashboardComponent } from './dashboard.component';
+
+describe('DashboardComponent', () => {
+ let component: DashboardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DashboardComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DashboardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.ts"
new file mode 100644
index 00000000..f9f93f6c
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/dashboard/dashboard.component.ts"
@@ -0,0 +1,24 @@
+import { Component, OnInit } from '@angular/core';
+import { Hero } from '../hero';
+import { HeroService } from '../hero.service';
+
+@Component({
+ selector: 'app-dashboard',
+ templateUrl: './dashboard.component.html',
+ styleUrls: ['./dashboard.component.css']
+})
+export class DashboardComponent implements OnInit {
+ heroes: Hero[] = [];
+ constructor(private heroService: HeroService) { }
+
+ ngOnInit() {
+ this.getHeroes();
+ }
+
+ getHeroes(): void{
+ this.heroService.getHeroes().subscribe(
+ heroes => this.heroes = heroes.slice(1, 5)
+ );
+ }
+
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.css"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.html"
new file mode 100644
index 00000000..84051a78
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.html"
@@ -0,0 +1,12 @@
+
+
{{hero.name | uppercase}} Details
+
id: {{hero.id}}
+
name: {{hero.name}}
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.spec.ts"
new file mode 100644
index 00000000..5e34497b
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HeroDetailComponent } from './hero-detail.component';
+
+describe('HeroDetailComponent', () => {
+ let component: HeroDetailComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HeroDetailComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HeroDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.ts"
new file mode 100644
index 00000000..4d6b9733
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-detail/hero-detail.component.ts"
@@ -0,0 +1,40 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { Hero } from '../hero';
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+import { HeroService } from '../hero.service';
+
+@Component({
+ selector: 'app-hero-detail',
+ templateUrl: './hero-detail.component.html',
+ styleUrls: ['./hero-detail.component.css']
+})
+export class HeroDetailComponent implements OnInit {
+ @Input() hero: Hero; // 外部数据通过@Input绑定数据
+ constructor(
+ private route: ActivatedRoute,
+ private heroService: HeroService,
+ private location: Location
+ ) { }
+
+ ngOnInit(): void {
+ this.getHero();
+ }
+ goBack(): void {
+ this.location.back();
+ }
+ getHero(): void{
+ const id = + this.route.snapshot.paramMap.get('id');
+ this.heroService.getHero(id).subscribe(
+ hero => this.hero = hero
+ );
+ // route.snapshot 是一个路由信息的静态快照,抓取自组件刚刚创建完毕之后。
+ // paramMap 是一个从 URL 中提取的路由参数值的字典。 "id" 对应的值就是要获取的英雄的 id。
+ };
+ save(): void{
+ this.heroService.updateHero(this.hero).subscribe(
+ () => this.goBack()
+ )
+ };
+
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.css"
new file mode 100644
index 00000000..3faebe3a
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.css"
@@ -0,0 +1,40 @@
+/* HeroSearch private styles */
+.search-result li {
+ border-bottom: 1px solid gray;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ width: 195px;
+ height: 16px;
+ padding: 5px;
+ background-color: white;
+ cursor: pointer;
+ list-style-type: none;
+ margin: 0 auto;
+ }
+
+ .search-result li:hover {
+ background-color: #607D8B;
+ }
+
+ .search-result li a {
+ color: #888;
+ display: block;
+ text-decoration: none;
+ }
+
+ .search-result li a:hover {
+ color: white;
+ }
+ .search-result li a:active {
+ color: white;
+ }
+ #search-box {
+ width: 200px;
+ height: 20px;
+ }
+
+
+ ul.search-result {
+ margin-top: 0;
+ padding-left: 0;
+ }
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.html"
new file mode 100644
index 00000000..c111268e
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.html"
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.spec.ts"
new file mode 100644
index 00000000..901bb7f2
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HeroSearchComponent } from './hero-search.component';
+
+describe('HeroSearchComponent', () => {
+ let component: HeroSearchComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HeroSearchComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HeroSearchComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.ts"
new file mode 100644
index 00000000..a7d8a06d
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero-search/hero-search.component.ts"
@@ -0,0 +1,39 @@
+import { Component, OnInit } from '@angular/core';
+import { Observable, Subject } from 'rxjs';
+import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
+
+import { Hero } from '../hero';
+import { HeroService } from '../hero.service';
+
+@Component({
+ selector: 'app-hero-search',
+ templateUrl: './hero-search.component.html',
+ styleUrls: ['./hero-search.component.css']
+})
+export class HeroSearchComponent implements OnInit {
+ heroes$: Observable;
+ private searchTerms = new Subject();
+
+ constructor(
+ private heroSerive: HeroService
+ ) { }
+
+ search(term: string): void{
+ // Push a search term into the observable stream.
+ this.searchTerms.next(term);
+ // 通过调用next(value) 方法往 Observable 中推送一些值,
+ }
+
+ ngOnInit() : void{
+ this.heroes$ = this.searchTerms.pipe(
+ // 在传出最终字符串之前,debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。
+ debounceTime(300),
+
+ // distinctUntilChanged() 会确保只在过滤条件变化时才发送请求。
+ distinctUntilChanged(),
+
+ // switchMap() 会为每个从 debounce 和 distinctUntilChanged 中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。
+ switchMap((term: string) => this.heroSerive.searchHeroes(term))
+ );
+ }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.spec.ts"
new file mode 100644
index 00000000..082791a7
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { HeroService } from './hero.service';
+
+describe('HeroService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: HeroService = TestBed.get(HeroService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.ts"
new file mode 100644
index 00000000..bbe21fda
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.service.ts"
@@ -0,0 +1,99 @@
+import { Injectable } from '@angular/core';
+import { Hero } from './hero';
+import { HEROES } from './mock-heroes';
+import { Observable, of } from 'rxjs';
+import { MessageService } from './message.service';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { catchError, map, tap } from 'rxjs/operators';
+
+const httpOptions = {
+ headers: new HttpHeaders({
+ 'Content-Type': 'application/json'
+ })
+};
+
+
+// @Injectable把这个类标记为依赖注入系统的参与者之一 注册服务的提供商
+@Injectable({
+ providedIn: 'root'//单一的、共享的,可以把它注入到任何想要它的类上
+})
+
+export class HeroService {
+
+ constructor(
+ private http: HttpClient,
+ private messageService: MessageService// 注入 MessageService
+
+ ) {
+
+ }
+ private heroesUrl = 'api/heroes';
+ private log(message: string){
+ this.messageService.add(`HeroService: ${message}`);
+ };
+ private handleError (operation = 'opration', result?:T){
+ return (error: any): Observable => {
+ console.error(error);
+ this.log(`${operation} failed:${error.message}`);
+ return of(result as T);// 返回一个空结果 让程序继续运行
+ }
+ };
+ getHeroes(): Observable{
+ // this.messageService.add('HeroService: fetched heroes!');
+ // return of(HEROES);
+ return this.http.get(this.heroesUrl).pipe(
+ tap(_ => this.log('fetched heroes')),
+ catchError(this.handleError('getHeroes', []))
+ );
+ }
+
+ getHero(id: number): Observable{
+ // this.messageService.add(`HeroService: fetched heroes!id = ${id}`);
+ // return of(HEROES.find(hero => hero.id === id));
+ const url = `${this.heroesUrl}/${id}`;
+ return this.http.get(url).pipe(
+ tap(_ => this.log(`fetched hero id=${id}`)),
+ catchError(this.handleError(`getHero id=${id}`))
+ );
+ }
+
+ updateHero (hero: Hero): Observable{
+ return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
+ tap(_ => this.log(`updated hero id = ${hero.id}`)),
+ catchError(this.handleError('updateHero'))
+ )
+ };
+
+ addHero (hero: Hero): Observable{
+ return this.http.post(this.heroesUrl, hero, httpOptions).pipe(
+ tap((hero:Hero) => this.log(`added hero w/ id=${hero.id}`)),
+ catchError(this.handleError('addHero'))
+ );
+ }
+
+ deleteHero(hero: Hero): Observable{
+ const id = typeof hero === 'number' ? hero : hero.id;
+ const url = `${this.heroesUrl}/${id}`;
+ return this.http.delete(url, httpOptions).pipe(
+ tap( _ => this.log(`deleted hero id=${id}`)),
+ catchError(this.handleError('deleteHero'))
+ );
+ };
+
+ searchHeroes(term: string): Observable{
+ //如果没有搜索词,该方法立即返回一个空数组
+ if(!term.trim()){
+ return of([])
+ }
+ return this.http.get(`${this.heroesUrl}/?name=${term}`).pipe(
+ tap(_ => this.log(`found heroes matching "${term}"`)),
+ catchError(this.handleError('serchHeroes', []))
+ )
+ }
+}
+
+
+// tap操作符会查看 Observable 中的值,使用那些值做一些事情,并且把它们传出来。这种 tap 回调不会改变这些值本身。
+
+// HttpClient.put() 方法接受三个参数
+// URL 地址, 要修改的数据(这里就是修改后的英雄),选项
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.ts"
new file mode 100644
index 00000000..08a76ef9
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/hero.ts"
@@ -0,0 +1,4 @@
+export class Hero {
+ id: number;
+ name: string;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.css"
new file mode 100644
index 00000000..0f74618f
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.css"
@@ -0,0 +1,73 @@
+/* HeroesComponent's private CSS styles */
+.heroes {
+ margin: 0 0 2em 0;
+ list-style-type: none;
+ padding: 0;
+ width: 15em;
+}
+.heroes li {
+ position: relative;
+ cursor: pointer;
+ background-color: #EEE;
+ margin: .5em;
+ padding: .3em 0;
+ height: 1.6em;
+ border-radius: 4px;
+}
+
+.heroes li:hover {
+ color: #607D8B;
+ background-color: #DDD;
+ left: .1em;
+}
+
+.heroes a {
+ color: #888;
+ text-decoration: none;
+ position: relative;
+ display: block;
+ width: 250px;
+}
+
+.heroes a:hover {
+ color:#607D8B;
+}
+
+.heroes .badge {
+ display: inline-block;
+ font-size: small;
+ color: white;
+ padding: 0.8em 0.7em 0 0.7em;
+ background-color: #607D8B;
+ line-height: 1em;
+ position: relative;
+ left: -1px;
+ top: -4px;
+ height: 1.8em;
+ min-width: 16px;
+ text-align: right;
+ margin-right: .8em;
+ border-radius: 4px 0 0 4px;
+}
+
+button {
+ background-color: #eee;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ cursor: hand;
+ font-family: Arial;
+}
+
+button:hover {
+ background-color: #cfd8dc;
+}
+
+button.delete {
+ position: relative;
+ left: 194px;
+ top: -32px;
+ background-color: gray !important;
+ color: white;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.html"
new file mode 100644
index 00000000..1a510250
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.html"
@@ -0,0 +1,30 @@
+My Heroes
+
+
+
+
+
+
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.spec.ts"
new file mode 100644
index 00000000..66518e44
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HeroesComponent } from './heroes.component';
+
+describe('HeroesComponent', () => {
+ let component: HeroesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HeroesComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HeroesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.ts"
new file mode 100644
index 00000000..82bcc39f
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/heroes/heroes.component.ts"
@@ -0,0 +1,55 @@
+import { Component, OnInit } from '@angular/core';
+import { Hero } from '../hero';
+import { HeroService } from '../hero.service';
+
+@Component({
+ selector: 'app-heroes',
+ templateUrl: './heroes.component.html',
+ styleUrls: ['./heroes.component.css']
+ // selector — 组件的选择器(CSS 元素选择器)
+ // templateUrl — 组件模板文件的位置。
+ // styleUrls — 组件私有 CSS 样式表文件的位置。
+})
+export class HeroesComponent implements OnInit {
+ hero: Hero = {
+ id: 1,
+ name:'Windstorm'
+ };
+ heroes: Hero[];
+
+ // selectedHero : Hero;
+ // onSelect(hero : Hero): void{
+ // this.selectedHero = hero;
+ // };
+
+ constructor(private heroService: HeroService) { //标记为一个 HeroService 的注入点
+
+ }
+
+ getHeroes(): void{
+ this.heroService.getHeroes().subscribe( // 订阅服务
+ heroes => this.heroes = heroes
+ );
+
+ };
+
+ ngOnInit() {
+ this.getHeroes();
+ }
+
+ add(name: string): void{
+ name = name.trim();
+ if(!name)return;
+ this.heroService.addHero({name} as Hero).subscribe(
+ hero => {
+ this.heroes.push(hero);
+ }
+ )
+ }
+
+ delete(hero: Hero):void{
+ this.heroes = this.heroes.filter(h => h !== hero);
+ this.heroService.deleteHero(hero).subscribe();
+ }
+
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.spec.ts"
new file mode 100644
index 00000000..a75ef029
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { InMemoryDataService } from './in-memory-data.service';
+
+describe('InMemoryDataService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: InMemoryDataService = TestBed.get(InMemoryDataService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.ts"
new file mode 100644
index 00000000..91250c2a
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/in-memory-data.service.ts"
@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core';
+import { Hero } from './hero';
+import { InMemoryDbService } from 'angular-in-memory-web-api';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class InMemoryDataService implements InMemoryDbService {
+ createDb() {
+ const heroes = [
+ { id: 11, name: 'Mr. Nice' },
+ { id: 12, name: 'Narco' },
+ { id: 13, name: 'Bombasto' },
+ { id: 14, name: 'Celeritas' },
+ { id: 15, name: 'Magneta' },
+ { id: 16, name: 'RubberMan' },
+ { id: 17, name: 'Dynama' },
+ { id: 18, name: 'Dr IQ' },
+ { id: 19, name: 'Magma' },
+ { id: 20, name: 'Tornado' }
+ ]
+ return {heroes};
+ }
+ genId(heroes: Hero[]): number {
+ return heroes.length > 0? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
+ }
+
+ constructor() { }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.spec.ts"
new file mode 100644
index 00000000..24d2d1d3
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { MessageService } from './message.service';
+
+describe('MessageService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: MessageService = TestBed.get(MessageService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.ts"
new file mode 100644
index 00000000..bae6d133
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/message.service.ts"
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MessageService {
+ messages: string[] = [];
+
+ add(message: string) {
+ this.messages.push(message);
+ };
+
+ clear(){
+ this.messages = [];
+ };
+
+ constructor() { }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.css"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.html"
new file mode 100644
index 00000000..586527c2
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.html"
@@ -0,0 +1,12 @@
+
+
Message
+
+
+ {{message}}
+
+
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.spec.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.spec.ts"
new file mode 100644
index 00000000..66109cc1
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MessagesComponent } from './messages.component';
+
+describe('MessagesComponent', () => {
+ let component: MessagesComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ MessagesComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MessagesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.ts"
new file mode 100644
index 00000000..95165a52
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/messages/messages.component.ts"
@@ -0,0 +1,16 @@
+import { Component, OnInit } from '@angular/core';
+import { MessageService } from '../message.service';
+
+@Component({
+ selector: 'app-messages',
+ templateUrl: './messages.component.html',
+ styleUrls: ['./messages.component.css']
+})
+export class MessagesComponent implements OnInit {
+
+ constructor(public messageService: MessageService) { }
+
+ ngOnInit() {
+ }
+
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/mock-heroes.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/mock-heroes.ts"
new file mode 100644
index 00000000..317e0224
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/app/mock-heroes.ts"
@@ -0,0 +1,14 @@
+import { Hero } from './hero';
+
+export const HEROES: Hero[] = [
+ { id: 11, name: 'Mr. Nice' },
+ { id: 12, name: 'Narco' },
+ { id: 13, name: 'Bombasto' },
+ { id: 14, name: 'Celeritas' },
+ { id: 15, name: 'Magneta' },
+ { id: 16, name: 'RubberMan' },
+ { id: 17, name: 'Dynama' },
+ { id: 18, name: 'Dr IQ' },
+ { id: 19, name: 'Magma' },
+ { id: 20, name: 'Tornado' }
+];
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/assets/.gitkeep" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/assets/.gitkeep"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/browserslist" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/browserslist"
new file mode 100644
index 00000000..37371cb0
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/browserslist"
@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.prod.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.prod.ts"
new file mode 100644
index 00000000..3612073b
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.prod.ts"
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.ts"
new file mode 100644
index 00000000..7b4f817a
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/environments/environment.ts"
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/favicon.ico" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/favicon.ico"
new file mode 100644
index 00000000..8081c7ce
Binary files /dev/null and "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/favicon.ico" differ
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/index.html" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/index.html"
new file mode 100644
index 00000000..565f4a57
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/index.html"
@@ -0,0 +1,14 @@
+
+
+
+
+ AngularDemo
+
+
+
+
+
+
+
+
+
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/karma.conf.js" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/karma.conf.js"
new file mode 100644
index 00000000..b6e00421
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/karma.conf.js"
@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
\ No newline at end of file
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/main.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/main.ts"
new file mode 100644
index 00000000..28bfa9e1
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/main.ts"
@@ -0,0 +1,13 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
+
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/polyfills.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/polyfills.ts"
new file mode 100644
index 00000000..d310405a
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/polyfills.ts"
@@ -0,0 +1,80 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ **/
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ */
+
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+
+ /*
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ */
+// (window as any).__Zone_enable_cross_context_check = true;
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/styles.css" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/styles.css"
new file mode 100644
index 00000000..90d4ee00
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/styles.css"
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/test.ts" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/test.ts"
new file mode 100644
index 00000000..16317897
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/test.ts"
@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.app.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.app.json"
new file mode 100644
index 00000000..190fd300
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.app.json"
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.spec.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.spec.json"
new file mode 100644
index 00000000..de773363
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tsconfig.spec.json"
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tslint.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tslint.json"
new file mode 100644
index 00000000..52e2c1a5
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/src/tslint.json"
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/tsconfig.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/tsconfig.json"
new file mode 100644
index 00000000..916247e4
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/tsconfig.json"
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "es2015",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2017",
+ "dom"
+ ]
+ }
+}
diff --git "a/Cute-Angular/angualr\345\256\230\347\275\221demo/tslint.json" "b/Cute-Angular/angualr\345\256\230\347\275\221demo/tslint.json"
new file mode 100644
index 00000000..6ddb6b29
--- /dev/null
+++ "b/Cute-Angular/angualr\345\256\230\347\275\221demo/tslint.json"
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/README.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/README.md"
new file mode 100644
index 00000000..0eb45f08
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/README.md"
@@ -0,0 +1,46 @@
+## 本文目录
+* 一、[项目起步](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_1.md)
+* 二、[编写路由组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_1.md)
+* 三、[编写页面组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_2.md)
+ * 1.[编写单一组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_2.md)
+ * 2.[模拟数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_2.md)
+ * 3.[编写主从组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_2.md)
+* 四、[编写服务](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+ * 1.[为什么需要服务](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+ * 2.[编写服务](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+* 五、[引入RxJS](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+ * 1.[关于RxJS](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+ * 2.[引入RxJS](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+ * 3.[改造数据获取方式](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_3.md)
+* 六、[改造组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 1.[添加历史记录组件](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 2.[添加和删除历史记录](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+* 七、[HTTP改造](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 1.[引入HTTP](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 2.[通过HTTP请求数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 3.[通过HTTP修改数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 4.[通过HTTP增加数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 5.[通过HTTP删除数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+ * 6.[通过HTTP查找数据](https://github.com/pingan8787/Leo-JavaScript/blob/master/Angular/books%E9%A1%B9%E7%9B%AEdemo/angular_books_4.md)
+* 八、结语
+
+## 项目介绍
+
+这个入门项目是我学习完[Angular 英雄指南教程](https://angular.cn/tutorial)后,自己手写的一个练习项目,一步一步来,最终的[项目源码可以这里查看](https://github.com/pingan8787/Leo-JavaScript/tree/master/Cute-Angular/books%E9%A1%B9%E7%9B%AEdemo),大佬们请指点啦。
+
+推荐两个Angular学习网站:
+1. [Angular 中文网](https://angular.cn/)
+2. [Angular 修仙之路](http://www.semlinker.com/)
+
+还有呢,我没怎么关注到样式,所以样式会有点丑,主要都放在核心逻辑中了。
+**最终实现:**
+* 首页书本列表数据展示
+* 各个页面静态/动态路由跳转
+* 本地模拟数据服务
+* 书本数据的增删改查
+* 父子组件通信
+* 常用指令使用和介绍
+
+
+
+后面我将把这个系列的文章,收录到我的[【CuteJavaScript】](http://js.pingan8787.com)中,里面有整理了**ES6/7/8/9知识点**和**重温JS基础系列**文章。
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_1.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_1.md"
new file mode 100644
index 00000000..1e904465
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_1.md"
@@ -0,0 +1,275 @@
+## 本文目录
+* **一、项目起步**
+* **二、编写路由组件**
+* 三、编写页面组件
+ * 1.编写单一组件
+ * 2.模拟数据
+ * 3.编写主从组件
+* 四、编写服务
+ * 1.为什么需要服务
+ * 2.编写服务
+* 五、引入RxJS
+ * 1.关于RxJS
+ * 2.引入RxJS
+ * 3.改造数据获取方式
+* 六、改造组件
+ * 1.添加历史记录组件
+ * 2.添加和删除历史记录
+* 七、HTTP改造
+ * 1.引入HTTP
+ * 2.通过HTTP请求数据
+ * 3.通过HTTP修改数据
+ * 4.通过HTTP增加数据
+ * 5.通过HTTP删除数据
+ * 6.通过HTTP查找数据
+* 八、结语
+
+
+
+这个入门项目是我学习完[Angular 英雄指南教程](https://angular.cn/tutorial)后,自己手写的一个练习项目,一步一步来,最终的[项目源码可以这里查看](https://github.com/pingan8787/Leo-JavaScript/tree/master/Cute-Angular/books%E9%A1%B9%E7%9B%AEdemo),大佬们请指点啦。
+
+推荐两个Angular学习网站:
+1. [Angular 中文网](https://angular.cn/)
+2. [Angular 修仙之路](http://www.semlinker.com/)
+
+还有呢,我没怎么关注到样式,所以样式会有点丑,主要都放在核心逻辑中了。
+**最终实现:**
+* 首页书本列表数据展示
+* 各个页面静态/动态路由跳转
+* 本地模拟数据服务
+* 书本数据的增删改查
+* 父子组件通信
+* 常用指令使用和介绍
+
+
+
+后面我将把这个系列的文章,收录到我的[【CuteJavaScript】](http://js.pingan8787.com)中,里面有整理了**ES6/7/8/9知识点**和**重温JS基础系列**文章。
+
+那么,快跟我一步步来完成这个入门项目吧。
+
+## 零、Angular安装
+Angular 需要 `Node.js` 的 `8.x` 或 `10.x` 版本。
+检查你的`Node.js`版本,请在终端/控制台窗口中运行 `node -v` 命令。
+要想安装` Node.js`,请访问 nodejs.org。
+
+1. 安装Angular CLI
+
+```sh
+npm install -g @angular/cli
+```
+
+2. 常用命令
+
+后续用到会详细介绍这些命令。
+
+* 启动服务,并打开新窗口
+```sh
+ng serve --open
+# --open 可简写 -o
+```
+
+* 创建新组件
+```sh
+ng generate component books
+# generate 可简写 g
+```
+
+* 创建新服务
+```sh
+ng generate service books
+```
+
+* 创建路由模块
+```sh
+ng generate module app-routing --flat --module=app
+```
+
+* 其他
+另外Angular CLI还有很多的命令提供,详细可以查阅官方文档 [Angular CLI 命令](https://angular.cn/cli)。
+
+最后搭建完是这样:
+
+
+
+## 一、项目起步
+1. 创建项目
+```sh
+ng new books
+cd books
+```
+
+2. 创建所需的两个页面组件
+```sh
+ng g component index
+ng g component detail
+```
+`g`是`generate`的简写。
+
+然后运行项目:
+
+```sh
+ng serve --open
+```
+
+## 二、编写路由组件
+这里为了项目结构先起来,所以先简单配置一下路由,后面路由会调整,如果遇到什么不懂,可以查看[Angular 路由与导航](https://angular.cn/guide/router)。
+
+1. 安装**路由模块**
+```sh
+ng g module app-routing --flat --module=app
+```
+**知识点:**
+`--flat` 把这个文件放进了 `src/app` 中,而不是单独的目录中。
+`--module=app` 告诉 CLI 把它注册到 `AppModule` 的 `imports` 数组中。
+
+2. 引入**路由模块**
+```js
+// app-routing.module.ts
+import { RouterModule, Routes } from '@angular/router';
+```
+3. 导出**路由模块**的指令
+
+这里需要添加一个 `@NgModule.exports` 数组,并传入`RouterModule`,导出 `RouterModule` 让路由器的相关指令可以在 `AppModule` 中的组件中使用。
+```js
+// app-routing.module.ts
+@NgModule({
+ imports: [CommonModule],
+ declarations: [],
+ exports: [RouterModule]
+})
+```
+
+4. 添加定义路由
+
+这里添加路由的时候,记得将所需要指向的组件也引入进来,这里我们需要引入两个页面的组件:
+```js
+// app-routing.module.ts
+import { IndexComponent } from './index/index.component';
+import { DetailComponent } from './detail/detail.component';
+```
+然后将我们所需要的路由定义在`routes`变量中,类型是我们引入的`Routes`:
+```js
+// app-routing.module.ts
+const routes: Routes = [
+ { path: '', redirectTo:'/index', pathMatch:'full' }, // 1
+ { path: 'index', component: IndexComponent}, // 2
+ { path: 'detail/:id', component: DetailComponent}, // 3
+]
+```
+**知识点**:
+`angular`的路由接收两个参数:
+* `path`:用于匹配浏览器地址栏中 `URL` 的字符串。
+* `component`:当导航到此路由时,路由器展示的组件名称。
+
+**第1行代码**:
+作为路由系统的默认路由,当所有路由都不匹配的话,就会重定向到这个路由,并展示对应的组件。
+**第2行代码**:
+正常情况下的路由配置。
+**第3行代码**:
+配置的是携带参数的路由,在路由`/`后,用 `:` 拼接参数名来实现,**获取这个参数的值的方法后面会介绍**。
+
+另外,我们还可以这么传递参数,直接将数据通过路由传入,后面还会介绍:
+```js
+{ path: 'pathname', component: DemoComponent, data: { title: 'pingan8787' } },
+```
+
+5. 添加路由监视
+
+配置好路由还不能使用,需要一个监视路由变化的工具,这时候需要把`RouterModule`添加到 `@NgModule.imports` 数组中,并用 `routes` 来配置它。
+这里只需要调用` imports `数组中的 `RouterModule.forRoot()` 函数就行了,就像这样:
+```js
+// app-routing.module.ts
+imports: [ RouterModule.forRoot(routes) ],
+```
+
+6. 添加路由出口
+
+所谓的路由出口,就是路由所对应的组件展示的地方,接下来我们在`app.component.html`内容中,添加``:
+```html
+
+
+
欢迎来到我的个人书屋!
+
+
+```
+这里的``就是我们路由输出的地方,也是组件展示的地方,简单理解就是,它会告诉路由器要在哪里显示路由的视图。
+
+7. 添加路由链接
+
+所谓的路由链接,就是出发路由跳转事件的地方,比如一个按钮,一张图片等,我们还是在`app.component.html`中,使用``添加3个按钮:
+```html
+
+
+```
+这边3个按钮的路由,我们将上面定义的3种路由,传入到`routerLink`参数中,现在就项目就可以实现页面跳转了。
+
+另外,这里还可以传入一个可选参数`routerLinkActive="className"`,表示当这个``标签激活的时候显示的样式,值是一个字符串,为样式的类名:
+```html
+打开首页 |
+```
+
+8. 获取带参数路由的参数
+
+在第7步中,我们点击 **打开书本详情** 按钮中,在路由中带了参数,这时候我们需要这么来获取这个参数:
+* 先导出模块`ActivatedRoute`和`Location`:
+```js
+// detail.component.ts
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+```
+* 再注入到构造函数中,并将值作为私有变量:
+```js
+// detail.component.ts
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location
+ ) { }
+ ngOnInit() {}
+}
+```
+**知识点:**
+`ActivatedRoute` 保存该 `DetailComponent` 实例的路由信息。可以从这个组件获取URL中的路由参数和其他数据。
+`Location` 是一个 `Angular` 的服务,用来与浏览器打交道。后续会使用它来导航回上一个视图。
+
+* 提取路由参数:
+
+这里声明`getDetail`方法,提取路由参数,并`ngOnInit`**生命周期钩子方法**在中执行。
+```js
+// detail.component.ts
+ngOnInit() {
+ this.getDetail()
+}
+getDetail(): void{
+ const id = +this.route.snapshot.paramMap.get('id');
+ console.log(`此课本的id是${id}`)
+}
+```
+**知识点**:
+`route.snapshot` 是一个路由信息的**静态快照**,抓取自组件刚刚创建完毕之后。
+`paramMap` 是一个URL中路由所携带的参数值的对象。"id"对应的值就是要获取的书本的 id。
+**注意**:
+路由参数总会是字符串。这里我们使用 (+) 操作符,将字符串转换成数字。
+
+现在在浏览器上刷新下页面,再点击 **打开书本详情** 按钮,可以看到控制台输出了` 此课本的id是1 `的结果。
+到这一步,我们算是把路由配置完成了,接下来可以开始做页面的逻辑了。
+
+**本部分内容到这结束**
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+|微信公众号|前端自习课|
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_2.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_2.md"
new file mode 100644
index 00000000..1ab75c51
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_2.md"
@@ -0,0 +1,227 @@
+## 本文目录
+* 一、[项目起步](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 二、[编写路由组件](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 三、**编写页面组件**
+ * 1.**编写单一组件**
+ * 2.**模拟数据**
+ * 3.**编写主从组件**
+* 四、编写服务
+ * 1.为什么需要服务
+ * 2.编写服务
+* 五、引入RxJS
+ * 1.关于RxJS
+ * 2.引入RxJS
+ * 3.改造数据获取方式
+* 六、改造组件
+ * 1.添加历史记录组件
+ * 2.添加和删除历史记录
+* 七、HTTP改造
+ * 1.引入HTTP
+ * 2.通过HTTP请求数据
+ * 3.通过HTTP修改数据
+ * 4.通过HTTP增加数据
+ * 5.通过HTTP删除数据
+ * 6.通过HTTP查找数据
+
+
+## 三、编写页面组件
+接下来开始编写页面组件,这里我们挑重点来写,一些布局的样式,后面可以看源码。
+
+### 1.编写单一组件
+我们首先写一个书本信息的组件,代码如下:
+```html
+
+
+
+
+
+

+
+
程姬
+
+
+
+```
+**知识点**:
+`*ngFor` 是一个 Angular 的复写器(repeater)指令,就像**angular1**中的`ng-for`和**vuejs**中的`v-for`。 它会为列表中的每项数据复写它的宿主元素。
+这时候可以看到页面变成下面这个样子:
+
+
+
+接下来我们要把写死在HTML上面的数据,抽到JS中:
+
+现在先新建一个`books.ts`文件来定义一个`Book`类,并添加`id`,`url`,`title`和`author`四个属性:
+```js
+// src/app/books.ts
+export class Book {
+ id: number;
+ url: string;
+ title: string;
+ author: string;
+}
+```
+然后回到`index.component.ts`文件去引入它,并定义一个`books`属性,使用导入进来的`Book`类作为类型:
+```js
+// index.component.ts
+import { Book } from '../books';
+export class IndexComponent implements OnInit {
+ books: Book = {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ }
+}
+```
+然后再改造前面的组件文件`index.component.html`:
+```html
+
+
+

+
+
{{books.author}}
+
+```
+
+接着,我们再为每个课本添加一个点击事件,来实现点击封面图能查看大图的效果,现在`index.component.ts`中定义一个`getDetailImage`方法,并在`index.component.html`中绑定该方法:
+```js
+// index.component.ts
+export class IndexComponent implements OnInit {
+ getDetailImage(books){
+ alert(`正在查看id为${books.id}的大图!`);
+ }
+}
+```
+这边方法的具体实现,不写,不是本文重点。下面是增加点击事件的绑定:
+```html
+
+
+```
+**知识点**:
+`(click)`是Angular用来绑定事件,它会让 Angular 监听这个`
` 元素的 `click` 事件。 当用户点击 `
` 时,Angular 就会执行表达式 `getDetailImage(books)`。
+
+再来,我们引入前面学到的**路由链接**指令来改造HTML:
+```html
+
+{{books.title}}
+```
+这时候,我们在点击书本的标题,发现页面跳转到URL地址为`http://localhost:4200/detail/1`的页面,这就说明,我们页面的路由跳转也成功了~
+
+改造完成后,可以看到,页面显示的还是一样,接下来我们先这样放着,因为我们后面会进行数据模拟,和模拟服务器请求。
+
+我们就这样写好第一个单一组件,并且数据是从JS中读取的。
+
+### 2.模拟数据
+这时候为了方便后面数据渲染,我们这里需要模拟一些本地数据,我们创建一个本地` mock-books.ts`文件来存放模拟的数据:
+```js
+// app/mock-books.ts
+import { Books } from './books';
+export const BookList: Books[] = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ // 省略其他9条
+]
+```
+然后在`index.component.ts`中导入模拟的数据,并将原有的`books`值修改成导入的模拟数据`BookList`:
+```js
+// index.component.ts
+import { BookList } from '../mock-books';
+books = BookList;
+```
+并将原本的`*ngFor`中修改成这样,绑定真正的数据:
+```html
+
+
+

+
+
{{item.author}}
+
+```
+
+### 3.编写主从组件
+当我们写完一个单一组件后,我们会发现,如果我们把每个组件都写到同一个HTML文件中,这是很糟糕的事情,这样做有缺点:
+* 代码复用性差;(导致每次相同功能要重新写)
+* 代码难维护;(因为一个文件会非常长)
+* 影响性能;(打开每个页面都要重复加载很多)
+
+为了解决这个问题,我们这里就要开始使用真正的**组件化思维**,将通用常用组件抽离出来,通过参数传递来控制组件的不同业务形态。
+这便是我们接下来要写的主从组件。
+
+思考一下,我们这里现在能抽成组件作为公共代码的,就是这个单个书本的内容,因为每个书本的内容都一致,只是里面数据的差异,于是我们再新建一个组件:
+```sh
+ng g component books
+```
+并将前面`index.component.html`中关于课本的代码剪切到`books.component.html`中来,然后删除掉`*ngFor`的内容,并将原本本地的变量`books`替换成`list`,这个变量我们等会会取到:
+```html
+
+
+

+
+
{{list.author}}
+
+```
+再将这个组件,引用到它的父组件中,这里是要引用到`index.component.html`的组件中,并将前面的`*ngFor`再次传入``:
+```html
+
+```
+
+接下来要做的就是获取到`list`变量的值,显然这个值是要从外面组件传进来的,我们需要在`books.component.ts`引入前面定义的 `Books`类 和 `@Input() 装饰器`,还要添加一个带有 `@Input() 装饰器`的 `list` 属性,另外还要记得将`getDetailImage`方法也剪切过来:
+```js
+// books.component.ts
+import { Component, OnInit, Input } from '@angular/core';
+import { Books } from '../books';
+
+export class BooksComponent implements OnInit {
+ @Input() list: Books;
+ constructor() { }
+ ngOnInit() {}
+ getDetailImage(books){
+ alert(`正在查看id为${books.id}的大图!`);
+ }
+}
+```
+`@Input() 装饰器`介绍具体可以查看 [手册](https://angular.cn/guide/template-syntax#inputs-outputs)
+
+我们要获取的 `list` 属性必须是一个带有` @Input() `装饰器的输入属性,因为外部的 `IndexComponent` 组件将会绑定到它。就像这样:
+```html
+
+```
+
+**知识点**:
+`[list]="item"` 是 `Angular` 的**属性绑定**语法。这是一种**单向数据绑定**。从 `IndexComponent` 的 `item` 属性绑定到目标元素的 `list` 属性,并映射到了 `BooksComponent` 的 `list` 属性。
+
+做到这里,我们已经将`BooksComponent`作为`IndexComponent`的子组件来引用了,在实际开发过程中,这样的父子组件关系,会用的非常多。
+
+写到这里,看看我们项目,还是一样正常在运行,只是现在项目中组件分工更加明确了。
+
+现在的效果图:
+
+
+
+**本部分内容到这结束**
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+|微信公众号|前端自习课|
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_3.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_3.md"
new file mode 100644
index 00000000..b98ff92e
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_3.md"
@@ -0,0 +1,251 @@
+## 本文目录
+* 一、[项目起步](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 二、[编写路由组件](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 三、[编写页面组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 1.[编写单一组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 2.[模拟数据](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 3.[编写主从组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+* 四、**编写服务**
+ * 1.**为什么需要服务**
+ * 2.**编写服务**
+* 五、**引入RxJS**
+ * 1.**关于RxJS**
+ * 2.**引入RxJS**
+ * 3.**改造数据获取方式**
+* 六、改造组件
+ * 1.添加历史记录组件
+ * 2.添加和删除历史记录
+* 七、HTTP改造
+ * 1.引入HTTP
+ * 2.通过HTTP请求数据
+ * 3.通过HTTP修改数据
+ * 4.通过HTTP增加数据
+ * 5.通过HTTP删除数据
+ * 6.通过HTTP查找数据
+
+## 四、编写服务
+截止到这部分,我们的`BooksComponent`组件获取和显示的都是本地模拟的数据。
+接下来我们要开始对这些进行重构,让聚焦于为它的视图提供支持,这也让它更容易使用模拟服务进行单元测试。
+
+### 1.为什么需要服务
+我们不应该让组件来直接获取或保存数据,它们应该聚焦于展示数据,而数据访问的工作交给其他服务来做。
+这里我们需要创建一个名为`BooksService`的服务,让我们应用中所有的类都使用它来获取书本列表的数据,使用的时候,只需要将它通过Angular的**依赖注入机制**注入到需要用的组件的构造函数中。
+
+**知识点:**
+服务可以实现多个不同组件之间信息共享,后面我们还会将它注入到两个地方:
+`BooksService`中,使用该服务发送消息。
+`IndexService`中,使用该服务来展示消息。
+
+接下来我们使用命令行,创建`BooksService `:
+```sh
+ng g service books
+```
+在生成的`books.service.ts`文件中:
+```js
+// books.service.ts
+import { Injectable } from '@angular/core';
+@Injectable({
+ providedIn: 'root'
+})
+```
+新导入了`@Injectable`装饰器,是为了让`BooksService`提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖,简单理解就是**如果你的服务需要依赖,那么你就需要导入它**。
+并且它接收该服务的元数据对象。
+
+### 2.编写服务
+接下来我们开始编写`books.service.ts`服务。
+
+* 导入服务所需组件
+
+这里我们导入`Books`和`BookList`,并添加一个`getBooks`方法来返回所有书本的数据,并且还需要添加一个`getBooks`方法来返回指定id的书本信息:
+```js
+// index.component.ts
+import { Books } from './books';
+import { BookList } from './mock-books';
+@Injectable({
+ providedIn: 'root'
+})
+export class BooksService {
+ constructor() { }
+ getBookList(): Books[] {
+ return BookList;
+ }
+ getBook(id: number): Books{
+ return BookList.find(book => book.id === id)
+ }
+}
+```
+在我们使用这个服务之前,需要先注册该服务,因为我们在使用`ng g service books`命令创建服务时,CLI已经默认为我们添加了注册了,这是方法就是上面代码中的:
+```js
+providedIn: 'root'
+```
+表示将我们的服务注册在**根注入器**上,这样我们就可以把这个服务注入到任何享用的类上了。
+
+* 修改`IndexComponent`
+
+先删除`BookList`的引入,并修改`books`属性的定义:
+
+```js
+// index.component.ts
+import { BooksService } from '../books.service';
+export class IndexComponent implements OnInit {
+ books : Books[];
+ ngOnInit() {}
+}
+```
+然后注入我们的`BooksService`服务,需要先往构造函数中添加一个私有的`booksservice`,使用注入的`BooksService`作为类型,理解成一个注入点:
+```js
+// index.component.ts
+constructor(private booksservice: BooksService) { }
+```
+
+之后我们需要添加一个`getBooks`方法来获取这些书本数据,并在生命周期函数`ngOnInit`中调用:
+```js
+export class IndexComponent implements OnInit {
+ ngOnInit() {
+ this.getBooks();
+ }
+ getBooks(): void{
+ this.books = this.booksservice.getBookList();
+ }
+}
+```
+
+* 修改`DetailComponent`
+我们先改造书本详情页的HTML结构:
+```html
+
+
+
《{{books.title}}》介绍
+
+

+
+
书本标题: {{books.title}}
+
书本作者: {{books.author}}
+
书本id: {{books.id}}
+
+
+
暂无信息
+
+```
+**知识点**:
+这里使用了`*ngIf`指令,当条件为`true`则显示其HTML内容。
+
+```js
+// detail.component.ts
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location,
+ private booksservice: BooksService // 引入BooksService服务
+ ) { }
+
+ books: Books; // 定义books类型
+ ngOnInit() {
+ this.getDetail()
+ }
+ getDetail(): void{
+ const id = +this.route.snapshot.paramMap.get('id');
+ this.getBooks(id);
+ }
+ getBooks(id: number): void {
+ this.books = this.booksservice.getBook(id);
+ }
+}
+```
+这段代码,主要定义了`getBooks`方法,当刚进入页面时,将书本`id`传入`getBooks`方法,去`BooksService`去获取对应id的书本信息,并复制给变量`books`,然后展示到页面。
+
+改造之后,我们的页面显示依旧正常。
+
+
+
+但是我们要知道,这背后的逻辑已经改变了。
+
+## 五、引入RxJS改造项目
+### 1.关于RxJS
+这里简单介绍关键概念,具体可以查看 [RxJS 官网](https://RxJS.dev/),也可以参考 [浅析Angular之RxJS](https://www.jianshu.com/p/36d85f8cafdd)。
+
+#### 什么是RxJS
+
+RxJS全称`Reactive Extensions for JavaScript`,中文意思: JavaScript的响应式扩展。
+RxJS主要是提供一种更加强大和优雅的方式,来利用响应式编程的模式,实现JavaScript的异步编程。
+
+#### RxJS优点
+
+* 纯净性;
+* 流动性;
+
+#### RxJS核心概念
+
+RxJS 是基于观察者模式和迭代器模式以函数式编程思维来实现的。RxJS 中含有两个基本概念:`Observables` 与 `Observer`。
+`Observables` 作为被观察者,是一个值或事件的流集合;而 `Observer` 则作为观察者,根据 `Observables` 进行处理。它们之间的订阅发布关系(观察者模式) 如下:
+**订阅**:`Observer` 通过 `Observable` 提供的 `subscribe()` 方法订阅 `Observable`。
+**发布**:`Observable` 通过回调 `next` 方法向 `Observer` 发布事件。
+
+———— 来源[Angular修仙之路 RxJS Observable](http://www.semlinker.com/rxjs-observable/)
+
+另外这里列出来一些核心,具体还是看官网咯,并且下面使用到的时候会具体介绍。
+* `Observable` (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
+* `Observer`(观察者): 一个回调函数的集合,它知道如何去监听由 `Observable` 提供的值。
+* `Subscription` (订阅): 表示 `Observable` 的执行,主要用于取消 `Observable` 的执行。
+* `Operators` (操作符): 采用函数式编程风格的纯函数 (`pure function`),使用像 `map`、`filter`、`concat`、`flatMap` 等这样的操作符来处理集合。
+* `Subject` (主体): 相当于 `EventEmitter`,并且是将值或事件多路推送给多个 `Observer` 的唯一方式。
+* `Schedulers` (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 `setTimeout` 或` requestAnimationFrame `或其他。
+
+### 2.引入RxJS
+在我们的真实应用中,我们必须要等到服务器响应后,我们才能获取到数据,因此这天生就需要用异步思维来操作。
+
+由于Angular中已经自带RxJS,所以我们只要在需要使用的时候,引入即可使用:
+
+### 3.改造数据获取方式
+了解完RxJS的一些概念后,我们开始改造下这些书本的数据获取方式。
+
+* 改造`BooksService`
+
+首先我们从 RxJS 中导入 `Observable` 和 `of` 符号:
+```js
+// books.service.ts
+import { Observable, of } from 'rxjs';
+```
+**知识点**:
+`Observable`: 观察者模式中的观察者,具体可以参考 [Angular修仙之路 RxJS Observable](http://www.semlinker.com/rxjs-observable/)
+`of`: 用来获取观察者拿到的数据,通常是一个`Observable`。
+
+然后修改`getBookList`方法
+```js
+// books.service.ts
+getBookList(): Observable {
+ return of(BookList);
+}
+```
+这里 `of(BookList)` 返回一个` Observable`,它会发出单个值,这个值就是这些模拟书本的数组。
+
+* 改造`IndexComponent`
+
+这里也要修改`getBooks`方法,使用`subscribe`去订阅服务返回回来的值:
+```js
+// index.component.ts
+getBooks(): void{
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+由于原本直接赋值数据,在实际场景中是不可能这样同步的,所以这里`subscribe`函数,会在`Observable`发出数据以后,再把书本列表传到里面的回调函数,再复制给`books`属性。
+使用这种异步方式,当 `BooksService` 从远端服务器获取英雄数据时,不用担心还没拿到数据就执行后面。
+
+下一步,我们就要改造一下项目了。
+
+**本部分内容到这结束**
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+|微信公众号|前端自习课|
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_4.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_4.md"
new file mode 100644
index 00000000..f99493dd
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_4.md"
@@ -0,0 +1,562 @@
+## 本文目录
+* 一、[项目起步](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 二、[编写路由组件](https://juejin.im/post/5c70ae586fb9a049c64476a0)
+* 三、[编写页面组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 1.[编写单一组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 2.[模拟数据](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+ * 3.[编写主从组件](https://juejin.im/post/5c70b48d6fb9a04a0b22cbce)
+* 四、[编写服务](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+ * 1.[为什么需要服务](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+ * 2.[编写服务](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+* 五、[引入RxJS](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+ * 1.[关于RxJS](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+ * 2.[引入RxJS](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+ * 3.[改造数据获取方式](https://juejin.im/post/5c70b5486fb9a049d7484fdf)
+* 六、**改造组件**
+ * 1.**添加历史记录组件**
+ * 2.**添加和删除历史记录**
+* 七、**HTTP改造**
+ * 1.**引入HTTP**
+ * 2.**通过HTTP请求数据**
+ * 3.**通过HTTP修改数据**
+ * 4.**通过HTTP增加数据**
+ * 5.**通过HTTP删除数据**
+ * 6.**通过HTTP查找数据**
+
+
+## 六、改造组件
+从这里开始,我们要使用RxJS来改造组件和添加新功能了,让整个项目更加完善。
+
+### 1.添加历史记录组件
+
+* 创建`HistoryComponent`组件
+```sh
+ng g component hostory
+```
+然后在`app.component.html`文件夹中添加组件:
+```html
+
+
+```
+
+### 2.添加增删改查功能
+
+这里我们要开始做书本的增删改查功能,需要先创建一个`HistoryService`服务,方便我们实现这几个功能:
+
+* 创建`HistoryService`服务
+```sh
+ng g service history
+```
+然后在生成的ts文件中,增加`add`和`clear`方法,`add`方法用来添加历史记录到`history`数组中,`clear`方法则是清空`history`数组:
+```js
+// history.service.ts
+export class HistoryService {
+ history: string[] = [];
+ add(history: string){
+ this.history.push(history);
+ }
+ clear(){
+ this.history = [];
+ }
+}
+```
+
+* 使用`HistoryService`服务
+
+在将这个服务,注入到`BooksService`中,并改造`getBooks`方法:
+```js
+// books.service.ts
+import { HistoryService } from './history.service';
+constructor(
+ private historyservice: HistoryService
+) { }
+getBooks(): void{
+ this.historyservice.add('请求书本数据')
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+也可以用相同方法,在`IndexComponent`中添加`访问首页书本列表`的记录。
+```js
+// index.component.ts
+import { HistoryService } from '../history.service';
+constructor(
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+) { }
+getBooks(): void{
+ this.historyservice.add('访问首页书本列表');
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+
+接下来,将我们的`HistoryService`注入到`HistoryComponent`中,然后才能将历史数据显示到页面上:
+```js
+// history.component.ts
+import { HistoryService } from '../history.service';
+export class HistoryComponent implements OnInit {
+ constructor(private historyservice: HistoryService) { }
+ ngOnInit() {}
+}
+```
+```html
+
+
+```
+**代码解释**:
+`*ngIf="historyservice.history.length"`,是为了防止还没有拿到历史数据,导致后面的报错。
+`(click)="historyservice.clear()"`, 绑定我们服务中的`clear`事件,实现清除缓存。
+`*ngFor="let item of historyservice.history"`,将我们的历史数据渲染到页面上。
+
+
+到了这一步,就能看到历史数据了,每次也换到首页,都会增加一条。
+
+
+
+接下来,我们要在书本详情页也加上历史记录的统计,导入文件,注入服务,然后改造`getBooks`方法,实现历史记录的统计:
+```js
+// detail.component.ts
+import { HistoryService } from '../history.service';
+
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+ //...
+ getBooks(id: number): void {
+ this.books = this.booksservice.getBook(id);
+ this.historyservice.add(`查看书本${this.books.title},id为${this.books.id}`);
+ console.log(this.books)
+ }
+}
+```
+
+
+这时候就可以在历史记录中,看到这些操作的记录了,并且**清除**按钮也正常使用。
+
+## 七、HTTP改造
+原本我只想写到上一章,但是想到,我们实际开发中,哪有什么本地数据,基本上数据都是要从服务端去请求,所以这边也有必要引入这一张,模拟实际的HTTP请求。
+
+### 1.引入HTTP
+在这一章,我们使用Angular提供的 `HttpClient` 来添加一些数据持久化特性。
+然后实现对书本数据进行**获取,增加,修改,删除和查找**功能。
+
+`HttpClient`是Angular通过 HTTP 与远程服务器通讯的机制。
+
+这里我们为了让`HttpClient`在整个应用全局使用,所以将`HttpClient`导入到根模块`app.module.ts`中,然后把它加入 `@NgModule.imports` 数组:
+```js
+import { HttpClientModule } from '@angular/common/http';
+@NgModule({
+ //...
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ HttpClientModule
+ ],
+ //...
+})
+```
+
+这边我们使用 [内存 Web API(In-memory Web API) ](https://github.com/angular/in-memory-web-api)模拟出的远程数据服务器通讯。
+**注意:** 这个内存 Web API 模块与 Angular 中的 HTTP 模块无关。
+
+通过下面命令来安装:
+```sh
+npm install angular-in-memory-web-api --save
+```
+然后在`app.module.ts`中导入 `HttpClientInMemoryWebApiModule` 和 `InMemoryDataService` 类(后面创建):
+```js
+// app.module.ts
+import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
+import { InMemoryDataService } from './in-memory-data.service';
+@NgModule({
+ // ...
+ imports: [
+ // ...
+ HttpClientInMemoryWebApiModule.forRoot(
+ InMemoryDataService, {dataEncapsulation:false}
+ )
+ ],
+ // ...
+})
+export class AppModule { }
+```
+**知识点:**
+`forRoot()` 配置方法接受一个 InMemoryDataService 类(初期的内存数据库)作为参数。
+
+然后我们要创建`InMemoryDataService`类:
+```sh
+ng g service InMemoryData
+```
+并将生成的`in-memory-data.service.ts`修改为:
+```js
+// in-memory-data.service.ts
+import { Injectable } from '@angular/core';
+import { InMemoryDbService } from 'angular-in-memory-web-api';
+import { Books } from './books';
+@Injectable({
+ providedIn: 'root'
+})
+export class InMemoryDataService implements InMemoryDbService {
+ createDb(){
+ const books = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ // 省略其他9条数据
+ ];
+ return {books};
+ }
+ constructor() { }
+}
+```
+
+这里先总结`InMemoryDbService`所提供的RESTful API,后面都要用到:
+例如如果`url`是`api/books`,那么
+* 查询所有成员:以**GET**方法访问`api/books`
+* 查询某个成员:以**GET**方法访问`api/books/id`,比如`id`是`1`,那么访问`api/books/1`
+* 更新某个成员:以**PUT**方法访问`api/books/id`
+* 删除某个成员:以**DELETE**方法访问`api/books/id`
+* 增加一个成员:以**POST**方法访问`api/books`
+
+
+### 2.通过HTTP请求数据
+
+现在要为接下来的网络请求做一些准备,先在`books.service.ts`中引入HTTP符号,然后注入`HttpClient`并改造:
+```js
+// books.service.ts
+import { HttpClient, HttpHeaders} from '@angular/common/http';
+// ...
+export class BooksService {
+ constructor(
+ private historyservice: HistoryService,
+ private http: HttpClient
+ ) { }
+ private log(histories: string){
+ this.historyservice.add(`正在执行:${histories}`)
+ }
+ private booksUrl = 'api/books'; // 提供一个API供调用
+ // ...
+}
+```
+这里我们还新增一个私有方法`log`和一个私有变量`booksUrl`。
+
+接下来我们要开始发起http请求数据,开始改造`getBookList`方法:
+```js
+// books.service.ts
+// ...
+getBookList(): Observable {
+ this.historyservice.add('请求书本数据')
+ return this.http.get(this.booksUrl);
+}
+// ...
+```
+这里我们使用 `http.get` 替换了 `of`,其它没修改,但是应用仍然在正常工作,这是因为这两个函数都返回了 `Observable`。
+
+实际开发中,我们还需要考虑到**请求的错误处理**,要捕获错误,我们就要使用 RxJS 的 `catchError()` 操作符来建立对 Observable 结果的处理管道(pipe)。
+
+我们引入`catchError `并改造原本`getBookList`方法:
+
+```js
+// books.service.ts
+getBookList(): Observable {
+ this.historyservice.add('请求书本数据')
+ return this.http.get(this.booksUrl).pipe(
+ catchError(this.handleError('getHeroes', []))
+ );
+}
+private handleError (operation = 'operation', result?: T) {
+ return (error: any): Observable => {
+ this.log(`${operation} 失败: ${error.message}`); // 发出错误通知
+ return of(result as T); // 返回空结果避免程序出错
+ };
+}
+```
+**知识点**:
+`.pipe()` 方法用来扩展 `Observable` 的结果。
+`catchError()` 操作符会拦截失败的 Observable。并把错误对象传给错误处理器,错误处理器会处理这个错误。
+`handleError()` 错误处理函数做了两件事,发出错误通知和返回空结果避免程序出错。
+
+这里还需要使用`tap`操作符改造`getBookList`方法,来窥探`Observable`数据流,它会查看`Observable`的值,然后我们使用`log`方法,记录一条历史记录。
+`tap` 回调不会改变这些值本身。
+```js
+// books.service.ts
+getBookList(): Observable {
+ return this.http.get(this.booksUrl)
+ .pipe(
+ tap( _ => this.log('请求书本数据')),
+ catchError(this.handleError('getHeroes', []))
+ );
+}
+```
+
+### 3.通过HTTP修改数据
+这里我们需要在原来`DetailComponent`上面,添加一个输入框、保存按钮和返回按钮,就像这样:
+```html
+
+
+
+
修改信息:
+
+
+
+
+```
+这边切记一点,一定要在`app.module.ts`中引入 `FormsModule`模块,并在`@NgModule`的`imports`中引入,不然要报错了。
+```js
+// app.module.ts
+// ...
+import { FormsModule } from '@angular/forms';
+@NgModule({
+ // ...
+ imports: [
+ // ...
+ FormsModule
+ ],
+ // ...
+})
+```
+`input`框绑定书本的标题`books.title`,而保存按钮绑定一个`save()`方法,这里还要实现这个方法:
+```js
+// detail.component.ts
+save(): void {
+ this.historyservice.updateBooks(this.books)
+ .subscribe(() => this.goBack());
+}
+goBack(): void {
+ this.location.back();
+}
+```
+这里通过调用`BooksService`的`updateBooks`方法,将当前修改后的书本信息修改到源数据中,这里我们需要去`books.service.ts`中添加`updateBooks`方法:
+```js
+// books.service.ts
+// ...
+updateBooks(books: Books): Observable{
+ return this.http.put(this.booksUrl, books, httpOptions).pipe(
+ tap(_ => this.log(`修改书本的id是${books.id}`)),
+ catchError(this.handleError(`getBooks请求是id为${books.id}`))
+ )
+}
+// ...
+```
+**知识点**:
+`HttpClient.put()` 方法接受三个参数:`URL 地址`、`要修改的数据`和`其他选项`。
+`httpOptions` 常量需要定义在`@Injectable`修饰器之前。
+
+现在,我们点击首页,选择一本书进入详情,修改标题然后保存,会发现,首页上这本书的名称也会跟着改变呢。这算是好了。
+
+
+### 4.通过HTTP增加数据
+我们可以新增一个页面,并添加上路由和按钮:
+```sh
+ng g component add
+```
+添加路由:
+```js
+// app-routing.module.ts
+// ...
+import { AddComponent } from './add/add.component';
+
+const routes: Routes = [
+ { path: '', redirectTo:'/index', pathMatch:'full' },
+ { path: 'index', component: IndexComponent},
+ { path: 'detail/:id', component: DetailComponent},
+ { path: 'add', component: AddComponent},
+]
+```
+添加路由入口:
+```html
+
+
+添加书本
+```
+编辑添加书本的页面:
+```html
+
+
+```
+初始化添加书本的数据:
+```js
+// add.component.ts
+// ...
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+import { HistoryService } from '../history.service';
+import { Location } from '@angular/common';
+export class AddComponent implements OnInit {
+ books: Books = {
+ id: 0,
+ url: '',
+ title: '',
+ author: ''
+ }
+ constructor(
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+ ngOnInit() {}
+ add(books: Books): void{
+ books.title = books.title.trim();
+ books.author = books.author.trim();
+ this.booksservice.addBooks(books)
+ .subscribe( book => {
+ this.historyservice.add(`新增书本${books.title},id为${books.id}`);
+ this.location.back();
+ });
+ }
+}
+```
+然后在`books.service.ts`中添加`addBooks`方法,来添加一本书本的数据:
+```js
+// books.service.ts
+addBooks(books: Books): Observable{
+ return this.http.post(this.booksUrl, books, httpOptions).pipe(
+ tap((newBook: Books) => this.log(`新增书本的id为${newBook.id}`)),
+ catchError(this.handleError('添加新书'))
+ );
+}
+```
+
+
+现在就可以正常添加书本啦。
+
+
+
+
+### 5.通过HTTP删除数据
+这里我们先为每个书本后面添加一个删除按钮,并绑定删除事件`delete`:
+```html
+
+
+X
+```
+```js
+// books.component.ts
+import { BooksService } from '../books.service';
+export class BooksComponent implements OnInit {
+ @Input() list: Books;
+ constructor(
+ private booksservice: BooksService
+ ) { }
+ // ...
+ delete(books: Books): void {
+ this.booksservice.deleteBooks(books)
+ .subscribe();
+ }
+}
+```
+然后还要再`books.service.ts`中添加`deleteBooks`方法来删除:
+```js
+// books.service.ts
+deleteBooks(books: Books): Observable{
+ const id = books.id;
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.delete(url, httpOptions).pipe(
+ tap(_ => this.log(`删除书本${books.title},id为${books.id}`)),
+ catchError(this.handleError('删除书本'))
+ );
+}
+```
+这里需要在删除书本结束后,通知`IndexComponent`将数据列表中的这条数据删除,这里还需要再了解一下[Angular 父子组件数据通信](https://blog.csdn.net/u010730126/article/details/68080139)。
+然后我们在父组件`IndexComponent`上添加`change`事件监听,并传入本地的`funChange`:
+```html
+
+
+```
+在对应的`index.component.ts`中添加`funChange`方法:
+```js
+// index.component.ts
+funChange(books, $event){
+ this.books = this.books.filter(h => h.id !== books.id);
+}
+```
+
+再来,我们在子组件`BooksComponent`上多导入`Output`和`EventEmitter`,并添加`@Output()`修饰器和调用`emit`:
+```js
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+export class BooksComponent implements OnInit {
+ // ...
+ @Output()
+ change = new EventEmitter()
+ // ...
+ delete(books: Books): void {
+ this.booksservice.deleteBooks(books)
+ .subscribe(()=>{
+ this.change.emit(books);
+ });
+ }
+}
+```
+这样就实现了我们父子组件之间的事件传递啦,现在我们的页面还是正常运行,并且删除一条数据后,页面数据会更新。
+
+
+### 6.通过HTTP查找数据
+还是在`books.service.ts`,我们添加一个方法`getBooks`,来实现通过ID来查找指定书本,因为我们是通过ID查找,所以返回的是单个数据,这里就是`Observable`类型:
+```js
+// books.service.ts
+getBooks(id: number): Observable{
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.get(url).pipe(
+ tap( _ => this.log(`请求书本的id为${id}`)),
+ catchError(this.handleError(`getBooks请求是id为${id}`))
+ )
+}
+```
+注意,这里 `getBooks` 会返回 `Observable`,是一个可观察的单个对象,而不是一个可观察的对象数组。
+
+
+## 八、结语
+这个项目其实很简单,但是我还是一步一步的写下来,一方面让自己更熟悉Angular,另一方面也是希望能帮助到更多朋友哈~
+最终效果:
+
+
+
+
+**本部分内容到这结束**
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+|微信公众号|前端自习课|
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_all.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_all.md"
new file mode 100644
index 00000000..d9de7974
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/angular_books_all.md"
@@ -0,0 +1,1197 @@
+* 零、Angular安装
+* 一、项目起步
+* 二、编写路由组件
+* 三、编写页面组件
+ * 1.编写单一组件
+ * 2.模拟数据
+ * 3.编写主从组件
+* 四、编写服务
+ * 1.为什么需要服务
+ * 2.编写服务
+* 五、引入RxJS改造项目
+ * 1.关于RxJS
+ * 2.引入RxJS
+ * 3.改造数据获取方式
+* 六、改造组件
+ * 1.添加历史记录组件
+ * 2.添加增删改查功能
+* 七、HTTP改造
+ * 1.引入HTTP
+ * 2.通过HTTP请求数据
+ * 3.通过HTTP修改数据
+ * 4.通过HTTP增加数据
+ * 5.通过HTTP删除数据
+ * 6.通过HTTP查找数据
+* 八、结语
+
+
+这个入门项目是我学习完[Angular 英雄指南教程](https://angular.cn/tutorial)后,自己手写的一个练习项目,一步一步来,最终的[项目源码可以这里查看](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Angular/books%E9%A1%B9%E7%9B%AEdemo/books_angular),大佬们请指点啦。
+
+推荐两个Angular学习网站:
+1. [Angular 中文网](https://angular.cn/)
+2. [Angular 修仙之路](http://www.semlinker.com/)
+
+还有呢,我没怎么关注到样式,所以样式会有点丑,主要都放在核心逻辑中了。
+**最终实现:**
+* 首页书本列表数据展示
+* 各个页面静态/动态路由跳转
+* 本地模拟数据服务
+* 书本数据的增删改查
+* 父子组件通信
+* 常用指令使用和介绍
+
+
+
+后面我将把这个系列的文章,收录到我的[【CuteJavaScript】](http://js.pingan8787.com)中,里面有整理了**ES6/7/8/9知识点**和**重温JS基础系列**文章。
+
+那么,快跟我一步步来完成这个入门项目吧。
+
+## 零、Angular安装
+Angular 需要 `Node.js` 的 `8.x` 或 `10.x` 版本。
+检查你的`Node.js`版本,请在终端/控制台窗口中运行 `node -v` 命令。
+要想安装` Node.js`,请访问 nodejs.org。
+
+1. 安装Angular CLI
+
+```sh
+npm install -g @angular/cli
+```
+
+2. 常用命令
+
+后续用到会详细介绍这些命令。
+
+* 启动服务,并打开新窗口
+```sh
+ng serve --open
+# --open 可简写 -o
+```
+
+* 创建新组件
+```sh
+ng generate component books
+# generate 可简写 g
+```
+
+* 创建新服务
+```sh
+ng generate service books
+```
+
+* 创建路由模块
+```sh
+ng generate module app-routing --flat --module=app
+```
+
+* 其他
+另外Angular CLI还有很多的命令提供,详细可以查阅官方文档 [Angular CLI 命令](https://angular.cn/cli)。
+
+最后搭建完是这样:
+
+
+
+## 一、项目起步
+1. 创建项目
+```sh
+ng new books
+cd books
+```
+
+2. 创建所需的两个页面组件
+```sh
+ng g component index
+ng g component detail
+```
+`g`是`generate`的简写。
+
+
+## 二、编写路由组件
+这里为了项目结构先起来,所以先简单配置一下路由,后面路由会调整,如果遇到什么不懂,可以查看[Angular 路由与导航](https://angular.cn/guide/router)。
+
+1. 安装**路由模块**
+```sh
+ng g module app-routing --flat --module=app
+```
+**知识点:**
+`--flat` 把这个文件放进了 `src/app` 中,而不是单独的目录中。
+`--module=app` 告诉 CLI 把它注册到 `AppModule` 的 `imports` 数组中。
+
+2. 引入**路由模块**
+```js
+// app-routing.module.ts
+import { RouterModule, Routes } from '@angular/router';
+```
+3. 导出**路由模块**的指令
+
+这里需要添加一个 `@NgModule.exports` 数组,并传入`RouterModule`,导出 `RouterModule` 让路由器的相关指令可以在 `AppModule` 中的组件中使用。
+```js
+// app-routing.module.ts
+@NgModule({
+ imports: [CommonModule],
+ declarations: [],
+ exports: [RouterModule]
+})
+```
+
+4. 添加定义路由
+
+这里添加路由的时候,记得将所需要指向的组件也引入进来,这里我们需要引入两个页面的组件:
+```js
+// app-routing.module.ts
+import { IndexComponent } from './index/index.component';
+import { DetailComponent } from './detail/detail.component';
+```
+然后将我们所需要的路由定义在`routes`变量中,类型是我们引入的`Routes`:
+```js
+// app-routing.module.ts
+const routes: Routes = [
+ { path: '', redirectTo:'/index', pathMatch:'full' }, // 1
+ { path: 'index', component: IndexComponent}, // 2
+ { path: 'detail/:id', component: DetailComponent}, // 3
+]
+```
+**知识点**:
+`angular`的路由接收两个参数:
+* `path`:用于匹配浏览器地址栏中 `URL` 的字符串。
+* `component`:当导航到此路由时,路由器展示的组件名称。
+
+**第1行代码**:
+作为路由系统的默认路由,当所有路由都不匹配的话,就会重定向到这个路由,并展示对应的组件。
+**第2行代码**:
+正常情况下的路由配置。
+**第3行代码**:
+配置的是携带参数的路由,在路由`/`后,用 `:` 拼接参数名来实现,**获取这个参数的值的方法后面会介绍**。
+
+另外,我们还可以这么传递参数,直接将数据通过路由传入,后面还会介绍:
+```js
+{ path: 'pathname', component: DemoComponent, data: { title: 'pingan8787' } },
+```
+
+5. 添加路由监视
+
+配置好路由还不能使用,需要一个监视路由变化的工具,这时候需要把`RouterModule`添加到 `@NgModule.imports` 数组中,并用 `routes` 来配置它。
+这里只需要调用` imports `数组中的 `RouterModule.forRoot()` 函数就行了,就像这样:
+```js
+// app-routing.module.ts
+imports: [ RouterModule.forRoot(routes) ],
+```
+
+6. 添加路由出口
+
+所谓的路由出口,就是路由所对应的组件展示的地方,接下来我们在`app.component.html`内容中,添加``:
+```html
+
+
+
欢迎来到我的个人书屋!
+
+
+```
+这里的``就是我们路由输出的地方,也是组件展示的地方,简单理解就是,它会告诉路由器要在哪里显示路由的视图。
+
+7. 添加路由链接
+
+所谓的路由链接,就是出发路由跳转事件的地方,比如一个按钮,一张图片等,我们还是在`app.component.html`中,使用``添加3个按钮:
+```html
+
+
+```
+这边3个按钮的路由,我们将上面定义的3种路由,传入到`routerLink`参数中,现在就项目就可以实现页面跳转了。
+
+另外,这里还可以传入一个可选参数`routerLinkActive="className"`,表示当这个``标签激活的时候显示的样式,值是一个字符串,为样式的类名:
+```html
+打开首页 |
+```
+
+8. 获取带参数路由的参数
+
+在第7步中,我们点击 **打开书本详情** 按钮中,在路由中带了参数,这时候我们需要这么来获取这个参数:
+* 先导出模块`ActivatedRoute`和`Location`:
+```js
+// detail.component.ts
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+```
+* 再注入到构造函数中,并将值作为私有变量:
+```js
+// detail.component.ts
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location
+ ) { }
+ ngOnInit() {}
+}
+```
+**知识点:**
+`ActivatedRoute` 保存该 `DetailComponent` 实例的路由信息。可以从这个组件获取URL中的路由参数和其他数据。
+`Location` 是一个 `Angular` 的服务,用来与浏览器打交道。后续会使用它来导航回上一个视图。
+
+* 提取路由参数:
+
+这里声明`getDetail`方法,提取路由参数,并`ngOnInit`**生命周期钩子方法**在中执行。
+```js
+// detail.component.ts
+ngOnInit() {
+ this.getDetail()
+}
+getDetail(): void{
+ const id = +this.route.snapshot.paramMap.get('id');
+ console.log(`此课本的id是${id}`)
+}
+```
+**知识点**:
+`route.snapshot` 是一个路由信息的**静态快照**,抓取自组件刚刚创建完毕之后。
+`paramMap` 是一个URL中路由所携带的参数值的对象。"id"对应的值就是要获取的书本的 id。
+**注意**:
+路由参数总会是字符串。这里我们使用 (+) 操作符,将字符串转换成数字。
+
+现在在浏览器上刷新下页面,再点击 **打开书本详情** 按钮,可以看到控制台输出了` 此课本的id是1 `的结果。
+到这一步,我们算是把路由配置完成了,接下来可以开始做页面的逻辑了。
+
+
+
+## 三、编写页面组件
+接下来开始编写页面组件,这里我们挑重点来写,一些布局的样式,后面可以看源码。
+
+### 1.编写单一组件
+我们首先写一个书本信息的组件,代码如下:
+```html
+
+
+
+
+
+

+
+
程姬
+
+
+
+```
+**知识点**:
+`*ngFor` 是一个 Angular 的复写器(repeater)指令,就像**angular1**中的`ng-for`和**vuejs**中的`v-for`。 它会为列表中的每项数据复写它的宿主元素。
+这时候可以看到页面变成下面这个样子:
+
+
+
+接下来我们要把写死在HTML上面的数据,抽到JS中:
+
+现在先新建一个`books.ts`文件来定义一个`Book`类,并添加`id`,`url`,`title`和`author`四个属性:
+```js
+// src/app/books.ts
+export class Book {
+ id: number;
+ url: string;
+ title: string;
+ author: string;
+}
+```
+然后回到`index.component.ts`文件去引入它,并定义一个`books`属性,使用导入进来的`Book`类作为类型:
+```js
+// index.component.ts
+import { Book } from '../books';
+export class IndexComponent implements OnInit {
+ books: Book = {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ }
+}
+```
+然后再改造前面的组件文件`index.component.html`:
+```html
+
+
+

+
+
{{books.author}}
+
+```
+
+接着,我们再为每个课本添加一个点击事件,来实现点击封面图能查看大图的效果,现在`index.component.ts`中定义一个`getDetailImage`方法,并在`index.component.html`中绑定该方法:
+```js
+// index.component.ts
+export class IndexComponent implements OnInit {
+ getDetailImage(books){
+ alert(`正在查看id为${books.id}的大图!`);
+ }
+}
+```
+这边方法的具体实现,不写,不是本文重点。下面是增加点击事件的绑定:
+```html
+
+
+```
+**知识点**:
+`(click)`是Angular用来绑定事件,它会让 Angular 监听这个`
` 元素的 `click` 事件。 当用户点击 `
` 时,Angular 就会执行表达式 `getDetailImage(books)`。
+
+再来,我们引入前面学到的**路由链接**指令来改造HTML:
+```html
+
+{{books.title}}
+```
+这时候,我们在点击书本的标题,发现页面跳转到URL地址为`http://localhost:4200/detail/1`的页面,这就说明,我们页面的路由跳转也成功了~
+
+改造完成后,可以看到,页面显示的还是一样,接下来我们先这样放着,因为我们后面会进行数据模拟,和模拟服务器请求。
+
+我们就这样写好第一个单一组件,并且数据是从JS中读取的。
+
+### 2.模拟数据
+这时候为了方便后面数据渲染,我们这里需要模拟一些本地数据,我们创建一个本地` mock-books.ts`文件来存放模拟的数据:
+```js
+// app/mock-books.ts
+import { Books } from './books';
+export const BookList: Books[] = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ // 省略其他9条
+]
+```
+然后在`index.component.ts`中导入模拟的数据,并将原有的`books`值修改成导入的模拟数据`BookList`:
+```js
+// index.component.ts
+import { BookList } from '../mock-books';
+books = BookList;
+```
+并将原本的`*ngFor`中修改成这样,绑定真正的数据:
+```html
+
+
+

+
+
{{item.author}}
+
+```
+
+### 3.编写主从组件
+当我们写完一个单一组件后,我们会发现,如果我们把每个组件都写到同一个HTML文件中,这是很糟糕的事情,这样做有缺点:
+* 代码复用性差;(导致每次相同功能要重新写)
+* 代码难维护;(因为一个文件会非常长)
+* 影响性能;(打开每个页面都要重复加载很多)
+
+为了解决这个问题,我们这里就要开始使用真正的**组件化思维**,将通用常用组件抽离出来,通过参数传递来控制组件的不同业务形态。
+这便是我们接下来要写的主从组件。
+
+思考一下,我们这里现在能抽成组件作为公共代码的,就是这个单个书本的内容,因为每个书本的内容都一致,只是里面数据的差异,于是我们再新建一个组件:
+```sh
+ng g component books
+```
+并将前面`index.component.html`中关于课本的代码剪切到`books.component.html`中来,然后删除掉`*ngFor`的内容,并将原本本地的变量`books`替换成`list`,这个变量我们等会会取到:
+```html
+
+
+

+
+
{{list.author}}
+
+```
+再将这个组件,引用到它的父组件中,这里是要引用到`index.component.html`的组件中,并将前面的`*ngFor`再次传入``:
+```html
+
+```
+
+接下来要做的就是获取到`list`变量的值,显然这个值是要从外面组件传进来的,我们需要在`books.component.ts`引入前面定义的 `Books`类 和 `@Input() 装饰器`,还要添加一个带有 `@Input() 装饰器`的 `list` 属性,另外还要记得将`getDetailImage`方法也剪切过来:
+```js
+// books.component.ts
+import { Component, OnInit, Input } from '@angular/core';
+import { Books } from '../books';
+
+export class BooksComponent implements OnInit {
+ @Input() list: Books;
+ constructor() { }
+ ngOnInit() {}
+ getDetailImage(books){
+ alert(`正在查看id为${books.id}的大图!`);
+ }
+}
+```
+`@Input() 装饰器`介绍具体可以查看 [手册](https://angular.cn/guide/template-syntax#inputs-outputs)
+
+我们要获取的 `list` 属性必须是一个带有` @Input() `装饰器的输入属性,因为外部的 `IndexComponent` 组件将会绑定到它。就像这样:
+```html
+
+```
+
+**知识点**:
+`[list]="item"` 是 `Angular` 的**属性绑定**语法。这是一种**单向数据绑定**。从 `IndexComponent` 的 `item` 属性绑定到目标元素的 `list` 属性,并映射到了 `BooksComponent` 的 `list` 属性。
+
+做到这里,我们已经将`BooksComponent`作为`IndexComponent`的子组件来引用了,在实际开发过程中,这样的父子组件关系,会用的非常多。
+
+写到这里,看看我们项目,还是一样正常在运行,只是现在项目中组件分工更加明确了。
+
+现在的效果图:
+
+
+
+
+## 四、编写服务
+截止到这部分,我们的`BooksComponent`组件获取和显示的都是本地模拟的数据。
+接下来我们要开始对这些进行重构,让聚焦于为它的视图提供支持,这也让它更容易使用模拟服务进行单元测试。
+
+### 1.为什么需要服务
+我们不应该让组件来直接获取或保存数据,它们应该聚焦于展示数据,而数据访问的工作交给其他服务来做。
+这里我们需要创建一个名为`BooksService`的服务,让我们应用中所有的类都使用它来获取书本列表的数据,使用的时候,只需要将它通过Angular的**依赖注入机制**注入到需要用的组件的构造函数中。
+
+**知识点:**
+服务可以实现多个不同组件之间信息共享,后面我们还会将它注入到两个地方:
+`BooksService`中,使用该服务发送消息。
+`IndexService`中,使用该服务来展示消息。
+
+接下来我们使用命令行,创建`BooksService `:
+```sh
+ng g service books
+```
+在生成的`books.service.ts`文件中:
+```js
+// books.service.ts
+import { Injectable } from '@angular/core';
+@Injectable({
+ providedIn: 'root'
+})
+```
+新导入了`@Injectable`装饰器,是为了让`BooksService`提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖,简单理解就是**如果你的服务需要依赖,那么你就需要导入它**。
+并且它接收该服务的元数据对象。
+
+### 2.编写服务
+接下来我们开始编写`books.service.ts`服务。
+
+* 导入服务所需组件
+
+这里我们导入`Books`和`BookList`,并添加一个`getBooks`方法来返回所有书本的数据,并且还需要添加一个`getBooks`方法来返回指定id的书本信息:
+```js
+// index.component.ts
+import { Books } from './books';
+import { BookList } from './mock-books';
+@Injectable({
+ providedIn: 'root'
+})
+export class BooksService {
+ constructor() { }
+ getBookList(): Books[] {
+ return BookList;
+ }
+ getBook(id: number): Books{
+ return BookList.find(book => book.id === id)
+ }
+}
+```
+在我们使用这个服务之前,需要先注册该服务,因为我们在使用`ng g service books`命令创建服务时,CLI已经默认为我们添加了注册了,这是方法就是上面代码中的:
+```js
+providedIn: 'root'
+```
+表示将我们的服务注册在**根注入器**上,这样我们就可以把这个服务注入到任何享用的类上了。
+
+* 修改`IndexComponent`
+
+先删除`BookList`的引入,并修改`books`属性的定义:
+
+```js
+// index.component.ts
+import { BooksService } from '../books.service';
+export class IndexComponent implements OnInit {
+ books : Books[];
+ ngOnInit() {}
+}
+```
+然后注入我们的`BooksService`服务,需要先往构造函数中添加一个私有的`booksservice`,使用注入的`BooksService`作为类型,理解成一个注入点:
+```js
+// index.component.ts
+constructor(private booksservice: BooksService) { }
+```
+
+之后我们需要添加一个`getBooks`方法来获取这些书本数据,并在生命周期函数`ngOnInit`中调用:
+```js
+export class IndexComponent implements OnInit {
+ ngOnInit() {
+ this.getBooks();
+ }
+ getBooks(): void{
+ this.books = this.booksservice.getBookList();
+ }
+}
+```
+
+* 修改`DetailComponent`
+我们先改造书本详情页的HTML结构:
+```html
+
+
+
《{{books.title}}》介绍
+
+

+
+
书本标题: {{books.title}}
+
书本作者: {{books.author}}
+
书本id: {{books.id}}
+
+
+
暂无信息
+
+```
+**知识点**:
+这里使用了`*ngIf`指令,当条件为`true`则显示其HTML内容。
+
+```js
+// detail.component.ts
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location,
+ private booksservice: BooksService // 引入BooksService服务
+ ) { }
+
+ books: Books; // 定义books类型
+ ngOnInit() {
+ this.getDetail()
+ }
+ getDetail(): void{
+ const id = +this.route.snapshot.paramMap.get('id');
+ this.getBooks(id);
+ }
+ getBooks(id: number): void {
+ this.books = this.booksservice.getBook(id);
+ }
+}
+```
+这段代码,主要定义了`getBooks`方法,当刚进入页面时,将书本`id`传入`getBooks`方法,去`BooksService`去获取对应id的书本信息,并复制给变量`books`,然后展示到页面。
+
+改造之后,我们的页面显示依旧正常。
+
+
+
+但是我们要知道,这背后的逻辑已经改变了。
+
+## 五、引入RxJS改造项目
+### 1.关于RxJS
+这里简单介绍关键概念,具体可以查看 [RxJS 官网](https://RxJS.dev/),也可以参考 [浅析Angular之RxJS](https://www.jianshu.com/p/36d85f8cafdd)。
+
+#### 什么是RxJS
+
+RxJS全称`Reactive Extensions for JavaScript`,中文意思: JavaScript的响应式扩展。
+RxJS主要是提供一种更加强大和优雅的方式,来利用响应式编程的模式,实现JavaScript的异步编程。
+
+#### RxJS优点
+
+* 纯净性;
+* 流动性;
+
+#### RxJS核心概念
+
+RxJS 是基于观察者模式和迭代器模式以函数式编程思维来实现的。RxJS 中含有两个基本概念:`Observables` 与 `Observer`。
+`Observables` 作为被观察者,是一个值或事件的流集合;而 `Observer` 则作为观察者,根据 `Observables` 进行处理。它们之间的订阅发布关系(观察者模式) 如下:
+**订阅**:`Observer` 通过 `Observable` 提供的 `subscribe()` 方法订阅 `Observable`。
+**发布**:`Observable` 通过回调 `next` 方法向 `Observer` 发布事件。
+
+———— 来源[Angular修仙之路 RxJS Observable](http://www.semlinker.com/rxjs-observable/)
+
+另外这里列出来一些核心,具体还是看官网咯,并且下面使用到的时候会具体介绍。
+* `Observable` (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
+* `Observer`(观察者): 一个回调函数的集合,它知道如何去监听由 `Observable` 提供的值。
+* `Subscription` (订阅): 表示 `Observable` 的执行,主要用于取消 `Observable` 的执行。
+* `Operators` (操作符): 采用函数式编程风格的纯函数 (`pure function`),使用像 `map`、`filter`、`concat`、`flatMap` 等这样的操作符来处理集合。
+* `Subject` (主体): 相当于 `EventEmitter`,并且是将值或事件多路推送给多个 `Observer` 的唯一方式。
+* `Schedulers` (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 `setTimeout` 或` requestAnimationFrame `或其他。
+
+### 2.引入RxJS
+在我们的真实应用中,我们必须要等到服务器响应后,我们才能获取到数据,因此这天生就需要用异步思维来操作。
+
+由于Angular中已经自带RxJS,所以我们只要在需要使用的时候,引入即可使用:
+
+### 3.改造数据获取方式
+了解完RxJS的一些概念后,我们开始改造下这些书本的数据获取方式。
+
+* 改造`BooksService`
+
+首先我们从 RxJS 中导入 `Observable` 和 `of` 符号:
+```js
+// books.service.ts
+import { Observable, of } from 'rxjs';
+```
+**知识点**:
+`Observable`: 观察者模式中的观察者,具体可以参考 [Angular修仙之路 RxJS Observable](http://www.semlinker.com/rxjs-observable/)
+`of`: 用来获取观察者拿到的数据,通常是一个`Observable`。
+
+然后修改`getBookList`方法
+```js
+// books.service.ts
+getBookList(): Observable {
+ return of(BookList);
+}
+```
+这里 `of(BookList)` 返回一个` Observable`,它会发出单个值,这个值就是这些模拟书本的数组。
+
+* 改造`IndexComponent`
+
+这里也要修改`getBooks`方法,使用`subscribe`去订阅服务返回回来的值:
+```js
+// index.component.ts
+getBooks(): void{
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+由于原本直接赋值数据,在实际场景中是不可能这样同步的,所以这里`subscribe`函数,会在`Observable`发出数据以后,再把书本列表传到里面的回调函数,再复制给`books`属性。
+使用这种异步方式,当 `BooksService` 从远端服务器获取英雄数据时,不用担心还没拿到数据就执行后面。
+
+下一步,我们就要改造一下项目了。
+
+
+## 六、改造组件
+从这里开始,我们要使用RxJS来改造组件和添加新功能了,让整个项目更加完善。
+
+### 1.添加历史记录组件
+
+* 创建`HistoryComponent`组件
+```sh
+ng g component hostory
+```
+然后在`app.component.html`文件夹中添加组件:
+```html
+
+
+```
+
+### 2.添加增删改查功能
+
+这里我们要开始做书本的增删改查功能,需要先创建一个`HistoryService`服务,方便我们实现这几个功能:
+
+* 创建`HistoryService`服务
+```sh
+ng g service history
+```
+然后在生成的ts文件中,增加`add`和`clear`方法,`add`方法用来添加历史记录到`history`数组中,`clear`方法则是清空`history`数组:
+```js
+// history.service.ts
+export class HistoryService {
+ history: string[] = [];
+ add(history: string){
+ this.history.push(history);
+ }
+ clear(){
+ this.history = [];
+ }
+}
+```
+
+* 使用`HistoryService`服务
+
+在将这个服务,注入到`BooksService`中,并改造`getBooks`方法:
+```js
+// books.service.ts
+import { HistoryService } from './history.service';
+constructor(
+ private historyservice: HistoryService
+) { }
+getBooks(): void{
+ this.historyservice.add('请求书本数据')
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+也可以用相同方法,在`IndexComponent`中添加`访问首页书本列表`的记录。
+```js
+// index.component.ts
+import { HistoryService } from '../history.service';
+constructor(
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+) { }
+getBooks(): void{
+ this.historyservice.add('访问首页书本列表');
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+}
+```
+
+接下来,将我们的`HistoryService`注入到`HistoryComponent`中,然后才能将历史数据显示到页面上:
+```js
+// history.component.ts
+import { HistoryService } from '../history.service';
+export class HistoryComponent implements OnInit {
+ constructor(private historyservice: HistoryService) { }
+ ngOnInit() {}
+}
+```
+```html
+
+
+```
+**代码解释**:
+`*ngIf="historyservice.history.length"`,是为了防止还没有拿到历史数据,导致后面的报错。
+`(click)="historyservice.clear()"`, 绑定我们服务中的`clear`事件,实现清除缓存。
+`*ngFor="let item of historyservice.history"`,将我们的历史数据渲染到页面上。
+
+
+到了这一步,就能看到历史数据了,每次也换到首页,都会增加一条。
+
+
+
+接下来,我们要在书本详情页也加上历史记录的统计,导入文件,注入服务,然后改造`getBooks`方法,实现历史记录的统计:
+```js
+// detail.component.ts
+import { HistoryService } from '../history.service';
+
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+ //...
+ getBooks(id: number): void {
+ this.books = this.booksservice.getBook(id);
+ this.historyservice.add(`查看书本${this.books.title},id为${this.books.id}`);
+ console.log(this.books)
+ }
+}
+```
+
+
+这时候就可以在历史记录中,看到这些操作的记录了,并且**清除**按钮也正常使用。
+
+## 七、HTTP改造
+原本我只想写到上一章,但是想到,我们实际开发中,哪有什么本地数据,基本上数据都是要从服务端去请求,所以这边也有必要引入这一张,模拟实际的HTTP请求。
+
+### 1.引入HTTP
+在这一章,我们使用Angular提供的 `HttpClient` 来添加一些数据持久化特性。
+然后实现对书本数据进行**获取,增加,修改,删除和查找**功能。
+
+`HttpClient`是Angular通过 HTTP 与远程服务器通讯的机制。
+
+这里我们为了让`HttpClient`在整个应用全局使用,所以将`HttpClient`导入到根模块`app.module.ts`中,然后把它加入 `@NgModule.imports` 数组:
+```js
+import { HttpClientModule } from '@angular/common/http';
+@NgModule({
+ //...
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ HttpClientModule
+ ],
+ //...
+})
+```
+
+这边我们使用 [内存 Web API(In-memory Web API) ](https://github.com/angular/in-memory-web-api)模拟出的远程数据服务器通讯。
+**注意:** 这个内存 Web API 模块与 Angular 中的 HTTP 模块无关。
+
+通过下面命令来安装:
+```sh
+npm install angular-in-memory-web-api --save
+```
+然后在`app.module.ts`中导入 `HttpClientInMemoryWebApiModule` 和 `InMemoryDataService` 类(后面创建):
+```js
+// app.module.ts
+import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
+import { InMemoryDataService } from './in-memory-data.service';
+@NgModule({
+ // ...
+ imports: [
+ // ...
+ HttpClientInMemoryWebApiModule.forRoot(
+ InMemoryDataService, {dataEncapsulation:false}
+ )
+ ],
+ // ...
+})
+export class AppModule { }
+```
+**知识点:**
+`forRoot()` 配置方法接受一个 InMemoryDataService 类(初期的内存数据库)作为参数。
+
+然后我们要创建`InMemoryDataService`类:
+```sh
+ng g service InMemoryData
+```
+并将生成的`in-memory-data.service.ts`修改为:
+```js
+// in-memory-data.service.ts
+import { Injectable } from '@angular/core';
+import { InMemoryDbService } from 'angular-in-memory-web-api';
+import { Books } from './books';
+@Injectable({
+ providedIn: 'root'
+})
+export class InMemoryDataService implements InMemoryDbService {
+ createDb(){
+ const books = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ // 省略其他9条数据
+ ];
+ return {books};
+ }
+ constructor() { }
+}
+```
+
+这里先总结`InMemoryDbService`所提供的RESTful API,后面都要用到:
+例如如果`url`是`api/books`,那么
+* 查询所有成员:以**GET**方法访问`api/books`
+* 查询某个成员:以**GET**方法访问`api/books/id`,比如`id`是`1`,那么访问`api/books/1`
+* 更新某个成员:以**PUT**方法访问`api/books/id`
+* 删除某个成员:以**DELETE**方法访问`api/books/id`
+* 增加一个成员:以**POST**方法访问`api/books`
+
+
+### 2.通过HTTP请求数据
+
+现在要为接下来的网络请求做一些准备,先在`books.service.ts`中引入HTTP符号,然后注入`HttpClient`并改造:
+```js
+// books.service.ts
+import { HttpClient, HttpHeaders} from '@angular/common/http';
+// ...
+export class BooksService {
+ constructor(
+ private historyservice: HistoryService,
+ private http: HttpClient
+ ) { }
+ private log(histories: string){
+ this.historyservice.add(`正在执行:${histories}`)
+ }
+ private booksUrl = 'api/books'; // 提供一个API供调用
+ // ...
+}
+```
+这里我们还新增一个私有方法`log`和一个私有变量`booksUrl`。
+
+接下来我们要开始发起http请求数据,开始改造`getBookList`方法:
+```js
+// books.service.ts
+// ...
+getBookList(): Observable {
+ this.historyservice.add('请求书本数据')
+ return this.http.get(this.booksUrl);
+}
+// ...
+```
+这里我们使用 `http.get` 替换了 `of`,其它没修改,但是应用仍然在正常工作,这是因为这两个函数都返回了 `Observable`。
+
+实际开发中,我们还需要考虑到**请求的错误处理**,要捕获错误,我们就要使用 RxJS 的 `catchError()` 操作符来建立对 Observable 结果的处理管道(pipe)。
+
+我们引入`catchError `并改造原本`getBookList`方法:
+
+```js
+// books.service.ts
+getBookList(): Observable {
+ this.historyservice.add('请求书本数据')
+ return this.http.get(this.booksUrl).pipe(
+ catchError(this.handleError('getHeroes', []))
+ );
+}
+private handleError (operation = 'operation', result?: T) {
+ return (error: any): Observable => {
+ this.log(`${operation} 失败: ${error.message}`); // 发出错误通知
+ return of(result as T); // 返回空结果避免程序出错
+ };
+}
+```
+**知识点**:
+`.pipe()` 方法用来扩展 `Observable` 的结果。
+`catchError()` 操作符会拦截失败的 Observable。并把错误对象传给错误处理器,错误处理器会处理这个错误。
+`handleError()` 错误处理函数做了两件事,发出错误通知和返回空结果避免程序出错。
+
+这里还需要使用`tap`操作符改造`getBookList`方法,来窥探`Observable`数据流,它会查看`Observable`的值,然后我们使用`log`方法,记录一条历史记录。
+`tap` 回调不会改变这些值本身。
+```js
+// books.service.ts
+getBookList(): Observable {
+ return this.http.get(this.booksUrl)
+ .pipe(
+ tap( _ => this.log('请求书本数据')),
+ catchError(this.handleError('getHeroes', []))
+ );
+}
+```
+
+### 3.通过HTTP修改数据
+这里我们需要在原来`DetailComponent`上面,添加一个输入框、保存按钮和返回按钮,就像这样:
+```html
+
+
+
+
修改信息:
+
+
+
+
+```
+这边切记一点,一定要在`app.module.ts`中引入 `FormsModule`模块,并在`@NgModule`的`imports`中引入,不然要报错了。
+```js
+// app.module.ts
+// ...
+import { FormsModule } from '@angular/forms';
+@NgModule({
+ // ...
+ imports: [
+ // ...
+ FormsModule
+ ],
+ // ...
+})
+```
+`input`框绑定书本的标题`books.title`,而保存按钮绑定一个`save()`方法,这里还要实现这个方法:
+```js
+// detail.component.ts
+save(): void {
+ this.historyservice.updateBooks(this.books)
+ .subscribe(() => this.goBack());
+}
+goBack(): void {
+ this.location.back();
+}
+```
+这里通过调用`BooksService`的`updateBooks`方法,将当前修改后的书本信息修改到源数据中,这里我们需要去`books.service.ts`中添加`updateBooks`方法:
+```js
+// books.service.ts
+// ...
+updateBooks(books: Books): Observable{
+ return this.http.put(this.booksUrl, books, httpOptions).pipe(
+ tap(_ => this.log(`修改书本的id是${books.id}`)),
+ catchError(this.handleError(`getBooks请求是id为${books.id}`))
+ )
+}
+// ...
+```
+**知识点**:
+`HttpClient.put()` 方法接受三个参数:`URL 地址`、`要修改的数据`和`其他选项`。
+`httpOptions` 常量需要定义在`@Injectable`修饰器之前。
+
+现在,我们点击首页,选择一本书进入详情,修改标题然后保存,会发现,首页上这本书的名称也会跟着改变呢。这算是好了。
+
+
+### 4.通过HTTP增加数据
+我们可以新增一个页面,并添加上路由和按钮:
+```sh
+ng g component add
+```
+添加路由:
+```js
+// app-routing.module.ts
+// ...
+import { AddComponent } from './add/add.component';
+
+const routes: Routes = [
+ { path: '', redirectTo:'/index', pathMatch:'full' },
+ { path: 'index', component: IndexComponent},
+ { path: 'detail/:id', component: DetailComponent},
+ { path: 'add', component: AddComponent},
+]
+```
+添加路由入口:
+```html
+
+
+添加书本
+```
+编辑添加书本的页面:
+```html
+
+
+```
+初始化添加书本的数据:
+```js
+// add.component.ts
+// ...
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+import { HistoryService } from '../history.service';
+import { Location } from '@angular/common';
+export class AddComponent implements OnInit {
+ books: Books = {
+ id: 0,
+ url: '',
+ title: '',
+ author: ''
+ }
+ constructor(
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+ ngOnInit() {}
+ add(books: Books): void{
+ books.title = books.title.trim();
+ books.author = books.author.trim();
+ this.booksservice.addBooks(books)
+ .subscribe( book => {
+ this.historyservice.add(`新增书本${books.title},id为${books.id}`);
+ this.location.back();
+ });
+ }
+}
+```
+然后在`books.service.ts`中添加`addBooks`方法,来添加一本书本的数据:
+```js
+// books.service.ts
+addBooks(books: Books): Observable{
+ return this.http.post(this.booksUrl, books, httpOptions).pipe(
+ tap((newBook: Books) => this.log(`新增书本的id为${newBook.id}`)),
+ catchError(this.handleError('添加新书'))
+ );
+}
+```
+
+
+现在就可以正常添加书本啦。
+
+
+
+
+### 5.通过HTTP删除数据
+这里我们先为每个书本后面添加一个删除按钮,并绑定删除事件`delete`:
+```html
+
+
+X
+```
+```js
+// books.component.ts
+import { BooksService } from '../books.service';
+export class BooksComponent implements OnInit {
+ @Input() list: Books;
+ constructor(
+ private booksservice: BooksService
+ ) { }
+ // ...
+ delete(books: Books): void {
+ this.booksservice.deleteBooks(books)
+ .subscribe();
+ }
+}
+```
+然后还要再`books.service.ts`中添加`deleteBooks`方法来删除:
+```js
+// books.service.ts
+deleteBooks(books: Books): Observable{
+ const id = books.id;
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.delete(url, httpOptions).pipe(
+ tap(_ => this.log(`删除书本${books.title},id为${books.id}`)),
+ catchError(this.handleError('删除书本'))
+ );
+}
+```
+这里需要在删除书本结束后,通知`IndexComponent`将数据列表中的这条数据删除,这里还需要再了解一下[Angular 父子组件数据通信](https://blog.csdn.net/u010730126/article/details/68080139)。
+然后我们在父组件`IndexComponent`上添加`change`事件监听,并传入本地的`funChange`:
+```html
+
+
+```
+在对应的`index.component.ts`中添加`funChange`方法:
+```js
+// index.component.ts
+funChange(books, $event){
+ this.books = this.books.filter(h => h.id !== books.id);
+}
+```
+
+再来,我们在子组件`BooksComponent`上多导入`Output`和`EventEmitter`,并添加`@Output()`修饰器和调用`emit`:
+```js
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+export class BooksComponent implements OnInit {
+ // ...
+ @Output()
+ change = new EventEmitter()
+ // ...
+ delete(books: Books): void {
+ this.booksservice.deleteBooks(books)
+ .subscribe(()=>{
+ this.change.emit(books);
+ });
+ }
+}
+```
+这样就实现了我们父子组件之间的事件传递啦,现在我们的页面还是正常运行,并且删除一条数据后,页面数据会更新。
+
+
+### 6.通过HTTP查找数据
+还是在`books.service.ts`,我们添加一个方法`getBooks`,来实现通过ID来查找指定书本,因为我们是通过ID查找,所以返回的是单个数据,这里就是`Observable`类型:
+```js
+// books.service.ts
+getBooks(id: number): Observable{
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.get(url).pipe(
+ tap( _ => this.log(`请求书本的id为${id}`)),
+ catchError(this.handleError(`getBooks请求是id为${id}`))
+ )
+}
+```
+注意,这里 `getBooks` 会返回 `Observable`,是一个可观察的单个对象,而不是一个可观察的对象数组。
+
+
+## 八、结语
+这个项目其实很简单,但是我还是一步一步的写下来,一方面让自己更熟悉Angular,另一方面也是希望能帮助到更多朋友哈~
+最终效果:
+
+
+
+
+**本部分内容到这结束**
+
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+|微信公众号|前端自习课|
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.editorconfig" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.editorconfig"
new file mode 100644
index 00000000..6e87a003
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.editorconfig"
@@ -0,0 +1,13 @@
+# Editor configuration, see http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.gitignore" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.gitignore"
new file mode 100644
index 00000000..ee5c9d83
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/.gitignore"
@@ -0,0 +1,39 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/README.md" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/README.md"
new file mode 100644
index 00000000..55356bf0
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/README.md"
@@ -0,0 +1,27 @@
+# Books
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.2.4.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/angular.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/angular.json"
new file mode 100644
index 00000000..1f14688a
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/angular.json"
@@ -0,0 +1,127 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "books": {
+ "root": "",
+ "sourceRoot": "src",
+ "projectType": "application",
+ "prefix": "app",
+ "schematics": {},
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/books",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.app.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "books:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "books:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "books:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "main": "src/test.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.spec.json",
+ "karmaConfig": "src/karma.conf.js",
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": [],
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ]
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "src/tsconfig.app.json",
+ "src/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ },
+ "books-e2e": {
+ "root": "e2e/",
+ "projectType": "application",
+ "architect": {
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "books:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "books:serve:production"
+ }
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": "e2e/tsconfig.e2e.json",
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "books"
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/protractor.conf.js" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/protractor.conf.js"
new file mode 100644
index 00000000..86776a39
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/protractor.conf.js"
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './src/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: require('path').join(__dirname, './tsconfig.e2e.json')
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.e2e-spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.e2e-spec.ts"
new file mode 100644
index 00000000..21fad901
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.e2e-spec.ts"
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';
+
+describe('workspace-project App', () => {
+ let page: AppPage;
+
+ beforeEach(() => {
+ page = new AppPage();
+ });
+
+ it('should display welcome message', () => {
+ page.navigateTo();
+ expect(page.getParagraphText()).toEqual('Welcome to books!');
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.po.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.po.ts"
new file mode 100644
index 00000000..82ea75ba
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/src/app.po.ts"
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get('/');
+ }
+
+ getParagraphText() {
+ return element(by.css('app-root h1')).getText();
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/tsconfig.e2e.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/tsconfig.e2e.json"
new file mode 100644
index 00000000..a6dd6220
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/e2e/tsconfig.e2e.json"
@@ -0,0 +1,13 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "jasminewd2",
+ "node"
+ ]
+ }
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/package.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/package.json"
new file mode 100644
index 00000000..552ba76a
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/package.json"
@@ -0,0 +1,49 @@
+{
+ "name": "books",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "test": "ng test",
+ "lint": "ng lint",
+ "e2e": "ng e2e"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^6.1.0",
+ "@angular/common": "^6.1.0",
+ "@angular/compiler": "^6.1.0",
+ "@angular/core": "^6.1.0",
+ "@angular/forms": "^6.1.0",
+ "@angular/http": "^6.1.0",
+ "@angular/platform-browser": "^6.1.0",
+ "@angular/platform-browser-dynamic": "^6.1.0",
+ "@angular/router": "^6.1.0",
+ "angular-in-memory-web-api": "^0.8.0",
+ "core-js": "^2.5.4",
+ "rxjs": "~6.2.0",
+ "zone.js": "~0.8.26"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.8.0",
+ "@angular/cli": "~6.2.4",
+ "@angular/compiler-cli": "^6.1.0",
+ "@angular/language-service": "^6.1.0",
+ "@types/jasmine": "~2.8.8",
+ "@types/jasminewd2": "~2.0.3",
+ "@types/node": "~8.9.4",
+ "codelyzer": "~4.3.0",
+ "jasmine-core": "~2.99.1",
+ "jasmine-spec-reporter": "~4.2.1",
+ "karma": "~3.0.0",
+ "karma-chrome-launcher": "~2.2.0",
+ "karma-coverage-istanbul-reporter": "~2.0.1",
+ "karma-jasmine": "~1.1.2",
+ "karma-jasmine-html-reporter": "^0.2.2",
+ "protractor": "~5.4.0",
+ "ts-node": "~7.0.0",
+ "tslint": "~5.11.0",
+ "typescript": "~2.9.2"
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.css"
new file mode 100644
index 00000000..ba0564ca
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.css"
@@ -0,0 +1,14 @@
+.add{
+ width: 260px;
+ margin: 0 auto;
+}
+.add label{
+ display: block;
+ margin: 5px 0;
+}
+.add label input{
+ float: right;
+}
+.add div{
+ text-align: center;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.html"
new file mode 100644
index 00000000..945c2e08
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.html"
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.spec.ts"
new file mode 100644
index 00000000..fdcddf4c
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AddComponent } from './add.component';
+
+describe('AddComponent', () => {
+ let component: AddComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AddComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AddComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.ts"
new file mode 100644
index 00000000..5d5fb48b
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/add/add.component.ts"
@@ -0,0 +1,36 @@
+import { Component, OnInit } from '@angular/core';
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+import { HistoryService } from '../history.service';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-add',
+ templateUrl: './add.component.html',
+ styleUrls: ['./add.component.css']
+})
+export class AddComponent implements OnInit {
+ books: Books = {
+ id: 0,
+ url: '',
+ title: '',
+ author: ''
+ }
+ constructor(
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+
+ ngOnInit() {
+ }
+ add(books: Books): void{
+ books.title = books.title.trim();
+ books.author = books.author.trim();
+ this.booksservice.addBooks(books)
+ .subscribe( book => {
+ this.historyservice.add(`新增书本${books.title},id为${books.id}`);
+ this.location.back();
+ });
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.spec.ts"
new file mode 100644
index 00000000..d68ef067
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.spec.ts"
@@ -0,0 +1,13 @@
+import { AppRoutingModule } from './app-routing.module';
+
+describe('AppRoutingModule', () => {
+ let appRoutingModule: AppRoutingModule;
+
+ beforeEach(() => {
+ appRoutingModule = new AppRoutingModule();
+ });
+
+ it('should create an instance', () => {
+ expect(appRoutingModule).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.ts"
new file mode 100644
index 00000000..5521ccc5
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app-routing.module.ts"
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { IndexComponent } from './index/index.component';
+import { DetailComponent } from './detail/detail.component';
+import { AddComponent } from './add/add.component';
+
+const routes: Routes = [
+ { path: '', redirectTo:'/index', pathMatch:'full' },
+ { path: 'index', component: IndexComponent},
+ { path: 'detail/:id', component: DetailComponent},
+ { path: 'add', component: AddComponent},
+]
+
+@NgModule({
+ imports: [ RouterModule.forRoot(routes) ],
+ declarations: [],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.css"
new file mode 100644
index 00000000..395f433b
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.css"
@@ -0,0 +1,14 @@
+.my_books{
+ width: 770px;
+ margin: 0 auto;
+ border: 1px solid rebeccapurple;
+ border-radius: 10px;
+ box-sizing: border-box;
+}
+.my_books_title{
+ text-align: center;
+}
+
+.my_books .router{
+ text-align: center;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.html"
new file mode 100644
index 00000000..9154d026
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.html"
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.spec.ts"
new file mode 100644
index 00000000..64497cb0
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.spec.ts"
@@ -0,0 +1,31 @@
+import { TestBed, async } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ AppComponent
+ ],
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'books'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('books');
+ });
+
+ it('should render title in a h1 tag', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain('Welcome to books!');
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.ts"
new file mode 100644
index 00000000..6d034386
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.component.ts"
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+ title = 'books';
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.module.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.module.ts"
new file mode 100644
index 00000000..ba73ce1c
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/app.module.ts"
@@ -0,0 +1,37 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { HttpClientModule } from '@angular/common/http';
+import { FormsModule } from '@angular/forms';
+
+import { AppComponent } from './app.component';
+import { IndexComponent } from './index/index.component';
+import { DetailComponent } from './detail/detail.component';
+import { AppRoutingModule } from './app-routing.module';
+import { BooksComponent } from './books/books.component';
+import { HistoryComponent } from './history/history.component';
+
+import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
+import { InMemoryDataService } from './in-memory-data.service';
+import { AddComponent } from './add/add.component';
+@NgModule({
+ declarations: [
+ AppComponent,
+ IndexComponent,
+ DetailComponent,
+ BooksComponent,
+ HistoryComponent,
+ AddComponent
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ HttpClientModule,
+ HttpClientInMemoryWebApiModule.forRoot(
+ InMemoryDataService, {dataEncapsulation:false}
+ ),
+ FormsModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.spec.ts"
new file mode 100644
index 00000000..40950f51
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { BooksService } from './books.service';
+
+describe('BooksService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: BooksService = TestBed.get(BooksService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.ts"
new file mode 100644
index 00000000..64366e6b
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.service.ts"
@@ -0,0 +1,75 @@
+import { Injectable } from '@angular/core';
+import { Books } from './books';
+import { BookList } from './mock-books';
+import { Observable, of } from 'rxjs';
+import { HistoryService } from './history.service';
+import { HttpClient, HttpHeaders} from '@angular/common/http';
+import { catchError, map, tap } from 'rxjs/operators';
+import { Options } from 'selenium-webdriver/chrome';
+
+const httpOptions = {
+ headers: new HttpHeaders({ 'Content-Type': 'application/json' })
+}
+@Injectable({
+ providedIn: 'root'
+})
+export class BooksService {
+ constructor(
+ private historyservice: HistoryService,
+ private http: HttpClient
+ ) { }
+ private log(histories: string){
+ this.historyservice.add(`正在执行:${histories}`)
+ }
+ private booksUrl = 'api/books'; // 提供一个API供调用
+ // 获取书本列表
+ getBookList(): Observable {
+ return this.http.get(this.booksUrl)
+ .pipe(
+ tap( _ => this.log('请求书本数据')),
+ catchError(this.handleError('getHeroes', []))
+ );
+ }
+ // 获取指定id的书本
+ getBook(id: number): Books{
+ return BookList.find(book => book.id === id)
+ }
+ // 获取指定id的书本
+ getBooks(id: number): Observable{
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.get(url).pipe(
+ tap( _ => this.log(`请求书本的id为${id}`)),
+ catchError(this.handleError(`getBooks请求是id为${id}`))
+ )
+ }
+ // 更新书本数据
+ updateBooks(books: Books): Observable{
+ return this.http.put(this.booksUrl, books, httpOptions).pipe(
+ tap(_ => this.log(`修改书本的id是${books.id}`)),
+ catchError(this.handleError(`getBooks请求是id为${books.id}`))
+ )
+ }
+ // 添加书本
+ addBooks(books: Books): Observable{
+ return this.http.post(this.booksUrl, books, httpOptions).pipe(
+ tap((newBook: Books) => this.log(`新增书本的id为${newBook.id}`)),
+ catchError(this.handleError('添加新书'))
+ );
+ }
+ // 删除书本
+ deleteBooks(books: Books): Observable{
+ const id = books.id;
+ const url = `${this.booksUrl}/${id}`;
+ return this.http.delete(url, httpOptions).pipe(
+ tap(_ => this.log(`删除书本${books.title},id为${books.id}`)),
+ catchError(this.handleError('删除书本'))
+ );
+ }
+
+ private handleError (operation = 'operation', result?: T) {
+ return (error: any): Observable => {
+ this.log(`${operation} 失败: ${error.message}`); // 发出错误通知
+ return of(result as T); // 返回空结果避免程序出错
+ };
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.ts"
new file mode 100644
index 00000000..fe4b7954
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books.ts"
@@ -0,0 +1,6 @@
+export class Books {
+ id: number;
+ url: string;
+ title: string;
+ author: string;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.css"
new file mode 100644
index 00000000..b9c4248f
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.css"
@@ -0,0 +1,33 @@
+.books_item{
+ width: 116px;
+ margin: 6px;
+ padding: 10px;
+ border: 1px solid green;
+ display: inline-block;
+ position: relative;
+}
+.books_item img{
+ width: 100%;
+ height: 160px;
+}
+.books_item div{
+ margin: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.books_item .title a{
+ font-size: 14px;
+}
+.books_item .author{
+ font-size: 13px;
+}
+.books_item .delete{
+ position: absolute;
+ right: 0;
+ top: 0;
+ background: red;
+ color: #fff;
+ padding: 0 5px;
+ cursor: pointer;
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.html"
new file mode 100644
index 00000000..2a0bcd67
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.html"
@@ -0,0 +1,8 @@
+
+

+
+
{{list.author}}
+
X
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.spec.ts"
new file mode 100644
index 00000000..13067bab
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BooksComponent } from './books.component';
+
+describe('BooksComponent', () => {
+ let component: BooksComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ BooksComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BooksComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.ts"
new file mode 100644
index 00000000..6d91b66b
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/books/books.component.ts"
@@ -0,0 +1,32 @@
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+
+@Component({
+ selector: 'app-books',
+ templateUrl: './books.component.html',
+ styleUrls: ['./books.component.css']
+})
+export class BooksComponent implements OnInit {
+ @Input() list: Books;
+
+ @Output()
+ change = new EventEmitter()
+
+ constructor(
+ private booksservice: BooksService
+ ) { }
+ ngOnInit() {}
+
+ getDetailImage(books){
+ alert(`正在查看id为${books.id}的大图!`);
+ }
+
+ delete(books: Books): void {
+ this.booksservice.deleteBooks(books)
+ .subscribe(()=>{
+ this.change.emit(books);
+ });
+ }
+
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.css"
new file mode 100644
index 00000000..8efcf1e1
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.css"
@@ -0,0 +1,8 @@
+.detail{
+ width: 360px;
+ margin: 0 auto;
+}
+.detail img{
+ width:200px;
+ height: 300px;
+}
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.html"
new file mode 100644
index 00000000..abe744cc
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.html"
@@ -0,0 +1,20 @@
+
+
《{{books.title}}》介绍
+
+

+
+
书本标题: {{books.title}}
+
书本作者: {{books.author}}
+
书本id: {{books.id}}
+
+
+
暂无信息
+
+
+
+
修改信息:
+
+
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.spec.ts"
new file mode 100644
index 00000000..149b9be7
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DetailComponent } from './detail.component';
+
+describe('DetailComponent', () => {
+ let component: DetailComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DetailComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.ts"
new file mode 100644
index 00000000..1280cd15
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/detail/detail.component.ts"
@@ -0,0 +1,41 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+import { Books } from '../books';
+import { BooksService } from '../books.service';
+import { HistoryService } from '../history.service';
+
+@Component({
+ selector: 'app-detail',
+ templateUrl: './detail.component.html',
+ styleUrls: ['./detail.component.css']
+})
+export class DetailComponent implements OnInit {
+ constructor(
+ private route: ActivatedRoute,
+ private location: Location,
+ private booksservice: BooksService,
+ private historyservice: HistoryService
+ ) { }
+
+ books: Books;
+ ngOnInit() {
+ this.getDetail()
+ }
+ getDetail(): void{
+ const id = +this.route.snapshot.paramMap.get('id');
+ this.getBooks(id);
+ }
+ getBooks(id: number): void {
+ this.books = this.booksservice.getBook(id);
+ this.historyservice.add(`查看书本${this.books.title},id为${this.books.id}`);
+ console.log(this.books)
+ }
+ save(): void {
+ this.booksservice.updateBooks(this.books)
+ .subscribe(() => this.goBack());
+ }
+ goBack(): void {
+ this.location.back();
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.spec.ts"
new file mode 100644
index 00000000..594fa69e
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { HistoryService } from './history.service';
+
+describe('HistoryService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: HistoryService = TestBed.get(HistoryService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.ts"
new file mode 100644
index 00000000..6c05ce63
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history.service.ts"
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class HistoryService {
+ history: string[] = [];
+
+ add(histories: string){
+ this.history.push(histories);
+ }
+ clear(){
+ this.history = [];
+ }
+ constructor() { }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.css"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.html"
new file mode 100644
index 00000000..8d836c75
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.html"
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.spec.ts"
new file mode 100644
index 00000000..f68be4f2
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HistoryComponent } from './history.component';
+
+describe('HistoryComponent', () => {
+ let component: HistoryComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HistoryComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HistoryComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.ts"
new file mode 100644
index 00000000..50606ddf
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/history/history.component.ts"
@@ -0,0 +1,16 @@
+import { Component, OnInit } from '@angular/core';
+import { HistoryService } from '../history.service';
+
+@Component({
+ selector: 'app-history',
+ templateUrl: './history.component.html',
+ styleUrls: ['./history.component.css']
+})
+export class HistoryComponent implements OnInit {
+
+ constructor(private historyservice: HistoryService) { }
+
+ ngOnInit() {
+ }
+
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.spec.ts"
new file mode 100644
index 00000000..a75ef029
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.spec.ts"
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { InMemoryDataService } from './in-memory-data.service';
+
+describe('InMemoryDataService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: InMemoryDataService = TestBed.get(InMemoryDataService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.ts"
new file mode 100644
index 00000000..2c84ad77
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/in-memory-data.service.ts"
@@ -0,0 +1,76 @@
+import { Injectable } from '@angular/core';
+import { InMemoryDbService } from 'angular-in-memory-web-api';
+import { Books } from './books';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class InMemoryDataService implements InMemoryDbService {
+ createDb(){
+ const books = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ {
+ id: 2,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30002856.jpg',
+ title: '拜占庭帝国史',
+ author: '[美] A.A.瓦西列夫',
+ },
+ {
+ id: 3,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30005383.jpg',
+ title: '吴承恩捉妖记 上',
+ author: '有时右逝',
+ },
+ {
+ id: 4,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29952612.jpg',
+ title: '生命是什么',
+ author: '[以色列]埃迪·普罗斯',
+ },
+ {
+ id: 5,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29965934.jpg',
+ title: '圆屋',
+ author: '[美]厄德里克(Louise Erdrich)',
+ },
+ {
+ id: 6,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29960204.jpg',
+ title: '通识',
+ author: '日本实业出版社 / [日] 茂木健一郎 主编',
+ },
+ {
+ id: 7,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30002353.jpg',
+ title: '读心师',
+ author: '向林',
+ },
+ {
+ id: 8,
+ url: 'https://img1.doubanio.com/view/subject/m/public/s29951649.jpg',
+ title: '微精通',
+ author: '[英] 罗伯特·特威格尔',
+ },
+ {
+ id: 9,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29958456.jpg',
+ title: '人生最焦虑的就是吃些什么',
+ author: '刘汀',
+ },
+ {
+ id: 10,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29906241.jpg',
+ title: '过剩之地',
+ author: '[美]莫妮卡·普拉萨德',
+ },
+ ];
+ return {books};
+ }
+ constructor() { }
+
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.css"
new file mode 100644
index 00000000..56a3a8a5
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.css"
@@ -0,0 +1,6 @@
+.content{
+ width: 100%;
+ padding: 10px;
+}
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.html"
new file mode 100644
index 00000000..b83baab5
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.html"
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.spec.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.spec.ts"
new file mode 100644
index 00000000..03122420
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.spec.ts"
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { IndexComponent } from './index.component';
+
+describe('IndexComponent', () => {
+ let component: IndexComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ IndexComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(IndexComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.ts"
new file mode 100644
index 00000000..684eabcd
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/index/index.component.ts"
@@ -0,0 +1,40 @@
+import { Component, OnInit } from '@angular/core';
+import { Books } from '../books';
+// import { BookList } from '../mock-books';
+import { BooksService } from '../books.service';
+import { HistoryService } from '../history.service';
+import { HttpClient, HttpHeaders} from '@angular/common/http';
+
+
+@Component({
+ selector: 'app-index',
+ templateUrl: './index.component.html',
+ styleUrls: ['./index.component.css']
+})
+export class IndexComponent implements OnInit {
+ // books: Books = {
+ // id: 1,
+ // url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ // title: '像火焰像灰烬',
+ // author: '程姬',
+ // }
+ books : Books[];
+ constructor(
+ private booksservice: BooksService,
+ private historyservice: HistoryService,
+ private http: HttpClient
+ ) { }
+
+ ngOnInit() {
+ this.getBooks();
+ }
+ getBooks(): void{
+ this.historyservice.add('访问首页书本列表');
+ this.booksservice.getBookList()
+ .subscribe(books => this.books = books);
+ }
+ funChange(books, $event){
+ this.books = this.books.filter(h => h.id !== books.id);
+ console.log('ssssssss')
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/mock-books.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/mock-books.ts"
new file mode 100644
index 00000000..4a7863a9
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/app/mock-books.ts"
@@ -0,0 +1,64 @@
+import { Books } from './books';
+
+export const BookList: Books[] = [
+ {
+ id: 1,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
+ title: '像火焰像灰烬',
+ author: '程姬',
+ },
+ {
+ id: 2,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30002856.jpg',
+ title: '拜占庭帝国史',
+ author: '[美] A.A.瓦西列夫',
+ },
+ {
+ id: 3,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30005383.jpg',
+ title: '吴承恩捉妖记 上',
+ author: '有时右逝',
+ },
+ {
+ id: 4,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29952612.jpg',
+ title: '生命是什么',
+ author: '[以色列]埃迪·普罗斯',
+ },
+ {
+ id: 5,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29965934.jpg',
+ title: '圆屋',
+ author: '[美]厄德里克(Louise Erdrich)',
+ },
+ {
+ id: 6,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29960204.jpg',
+ title: '通识',
+ author: '日本实业出版社 / [日] 茂木健一郎 主编',
+ },
+ {
+ id: 7,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s30002353.jpg',
+ title: '读心师',
+ author: '向林',
+ },
+ {
+ id: 8,
+ url: 'https://img1.doubanio.com/view/subject/m/public/s29951649.jpg',
+ title: '微精通',
+ author: '[英] 罗伯特·特威格尔',
+ },
+ {
+ id: 9,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29958456.jpg',
+ title: '人生最焦虑的就是吃些什么',
+ author: '刘汀',
+ },
+ {
+ id: 10,
+ url: 'https://img3.doubanio.com/view/subject/m/public/s29906241.jpg',
+ title: '过剩之地',
+ author: '[美]莫妮卡·普拉萨德',
+ },
+]
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/assets/.gitkeep" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/assets/.gitkeep"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/browserslist" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/browserslist"
new file mode 100644
index 00000000..37371cb0
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/browserslist"
@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.prod.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.prod.ts"
new file mode 100644
index 00000000..3612073b
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.prod.ts"
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.ts"
new file mode 100644
index 00000000..7b4f817a
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/environments/environment.ts"
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/favicon.ico" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/favicon.ico"
new file mode 100644
index 00000000..8081c7ce
Binary files /dev/null and "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/favicon.ico" differ
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/index.html" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/index.html"
new file mode 100644
index 00000000..d2020127
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/index.html"
@@ -0,0 +1,14 @@
+
+
+
+
+ Books
+
+
+
+
+
+
+
+
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/karma.conf.js" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/karma.conf.js"
new file mode 100644
index 00000000..b6e00421
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/karma.conf.js"
@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
\ No newline at end of file
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/main.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/main.ts"
new file mode 100644
index 00000000..28bfa9e1
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/main.ts"
@@ -0,0 +1,13 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
+
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/polyfills.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/polyfills.ts"
new file mode 100644
index 00000000..d310405a
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/polyfills.ts"
@@ -0,0 +1,80 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ **/
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ */
+
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+
+ /*
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ */
+// (window as any).__Zone_enable_cross_context_check = true;
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/styles.css" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/styles.css"
new file mode 100644
index 00000000..90d4ee00
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/styles.css"
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/test.ts" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/test.ts"
new file mode 100644
index 00000000..16317897
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/test.ts"
@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.app.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.app.json"
new file mode 100644
index 00000000..190fd300
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.app.json"
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.spec.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.spec.json"
new file mode 100644
index 00000000..de773363
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tsconfig.spec.json"
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tslint.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tslint.json"
new file mode 100644
index 00000000..52e2c1a5
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/src/tslint.json"
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ]
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tsconfig.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tsconfig.json"
new file mode 100644
index 00000000..916247e4
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tsconfig.json"
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "es2015",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2017",
+ "dom"
+ ]
+ }
+}
diff --git "a/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tslint.json" "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tslint.json"
new file mode 100644
index 00000000..6ddb6b29
--- /dev/null
+++ "b/Cute-Angular/books\351\241\271\347\233\256demo/books_angular/tslint.json"
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}
diff --git "a/Cute-Angular/\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206/README.md" "b/Cute-Angular/\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206/README.md"
new file mode 100644
index 00000000..02f4e27c
--- /dev/null
+++ "b/Cute-Angular/\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206/README.md"
@@ -0,0 +1,33 @@
+Angular知识点整理,内容以Angular4实战课程中整理:
+
+1. 程序架构
+* 组件:Angular基本构建块,是一段含业务逻辑和数据的html
+* 服务:封装可重用的业务逻辑
+* 指令:允许向html元素添加指定行为
+* 模块:将不同部分组成一个单元
+
+
+2. 组件相关概念
+Component 必备:
+* 装饰器 @Component() 告知Angular如何处理类,它包含的值叫**元数据**,根据元数据来渲染和展示组件。
+@叫装饰器,@Component()叫组件元数据装饰器
+
+* 模版 Template
+* 控制器 Controller 包含绝大多数页面逻辑
+
+可选的可注入对象:
+* 输入属性 @Imports() 组件之间传递数据
+* 提供器 providers 依赖注入
+* 生命周期钩子 Lifecycle Hooks
+
+可选的输出对象:
+* 输出属性 @Outputs
+* 样式表 styles
+* 动画 Animations
+* 生命周期钩子 Lifecycle Hooks
+
+@NgModule:
+* declatations 模块包含的内容,只能组件指令和管道
+* imports 组件依赖的模块
+* providers 模块提供的服务
+* bootstrap 模块的主组件
\ No newline at end of file
diff --git a/Cute-Article/README.md b/Cute-Article/README.md
new file mode 100644
index 00000000..65c41316
--- /dev/null
+++ b/Cute-Article/README.md
@@ -0,0 +1,116 @@
+
+## 关于作者
+[](http://www.pingan8787.com)
+[](https://www.yuque.com/wangpingan/cute-frontend)
+[](https://zhuanlan.zhihu.com/cute-javascript)
+[](https://juejin.im/user/586fc337a22b9d0058807d53/posts)
+[](https://segmentfault.com/blog/pingan8787)
+[](https://blog.csdn.net/qq_36380426)
+[](https://www.jianshu.com/u/2ec5d94afd60)
+
+
+完整知识库,请查看我的【**语雀知识库**】,阅读体验更好。[💌跳转~](https://www.yuque.com/wangpingan/cute-frontend)
+
+## 目录
+
+### 0、前端工程化
+
+* [74-《大前端工程化的时实践和思考》狼叔分享](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Article/article/74-%E3%80%8A%E5%A4%A7%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96%E7%9A%84%E6%97%B6%E5%AE%9E%E8%B7%B5%E5%92%8C%E6%80%9D%E8%80%83%E3%80%8B%E7%8B%BC%E5%8F%94%E5%88%86%E4%BA%AB.md)
+
+### 1、JS业务逻辑实现
+* [1-JavaScript实现页面防抖](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/1-JavaScript%E5%AE%9E%E7%8E%B0%E9%A1%B5%E9%9D%A2%E9%98%B2%E6%8A%96.md)
+* [2-同步返回ajax请求结果方法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/2-%E5%90%8C%E6%AD%A5%E8%BF%94%E5%9B%9Eajax%E8%AF%B7%E6%B1%82%E7%BB%93%E6%9E%9C%E6%96%B9%E6%B3%95.md)
+* [★ 9-常用业务模块代码整理](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/9-%E5%B8%B8%E7%94%A8%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9D%97%E4%BB%A3%E7%A0%81%E6%95%B4%E7%90%86.md)
+* [12-javascript开发的一些简写技巧](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/12-javascript%E5%BC%80%E5%8F%91%E7%9A%84%E4%B8%80%E4%BA%9B%E7%AE%80%E5%86%99%E6%8A%80%E5%B7%A7.md)
+* [15-精心收集的48个JavaScript片段,简单理解](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/15-%E7%B2%BE%E5%BF%83%E6%94%B6%E9%9B%86%E7%9A%8448%E4%B8%AAJavaScript%E7%89%87%E6%AE%B5%EF%BC%8C%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3.md)
+* [29-关于随机数的一些总结](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/29-%E5%85%B3%E4%BA%8E%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93.md)
+
+### 2、VueJS
+* [4-基于Vue配置axios](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/4-%E5%9F%BA%E4%BA%8EVue%E9%85%8D%E7%BD%AEaxios.md)
+* [★ 5-[原创]VUE中实现通用js函数库封装](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/5-%5B%E5%8E%9F%E5%88%9B%5DVUE%E4%B8%AD%E5%AE%9E%E7%8E%B0%E9%80%9A%E7%94%A8js%E5%87%BD%E6%95%B0%E5%BA%93%E5%B0%81%E8%A3%85.md)
+* [★ 7-[原创]缩小Vuejs打包体积方法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/7-%5B%E5%8E%9F%E5%88%9B%5D%E7%BC%A9%E5%B0%8FVuejs%E6%89%93%E5%8C%85%E4%BD%93%E7%A7%AF%E6%96%B9%E6%B3%95.md)
+* [14-Vue的一些小注意点](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/14-Vue%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E6%B3%A8%E6%84%8F%E7%82%B9.md)
+* [★ 24-Vue折腾记-给Axios做个挺靠谱的封装(报错,鉴权,跳转,拦截,提示)](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/24-Vue%E6%8A%98%E8%85%BE%E8%AE%B0-%E7%BB%99Axios%E5%81%9A%E4%B8%AA%E6%8C%BA%E9%9D%A0%E8%B0%B1%E7%9A%84%E5%B0%81%E8%A3%85%EF%BC%88%E6%8A%A5%E9%94%99%2C%E9%89%B4%E6%9D%83%2C%E8%B7%B3%E8%BD%AC%2C%E6%8B%A6%E6%88%AA%2C%E6%8F%90%E7%A4%BA%EF%BC%89.md)
+* [40-解密Vue SSR](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/40-%E8%A7%A3%E5%AF%86Vue%20SSR.md)
+* [49-Vue 面试中常问知识点整理](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/49-Vue%20%E9%9D%A2%E8%AF%95%E4%B8%AD%E5%B8%B8%E9%97%AE%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86.md)
+
+### 3、ES6/ES7/ES8...
+* [3-Promise简单用法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/3-Promise%E7%AE%80%E5%8D%95%E7%94%A8%E6%B3%95.md)
+* [13-ES7和ES8的一点新东西](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/13-ES7%E5%92%8CES8%E7%9A%84%E4%B8%80%E7%82%B9%E6%96%B0%E4%B8%9C%E8%A5%BF.md)
+* [19-ES6的7个实用技巧](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/19-ES6%E7%9A%847%E4%B8%AA%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7.md)
+* [★ 31-ES6这些就够了](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/31-ES6%E8%BF%99%E4%BA%9B%E5%B0%B1%E5%A4%9F%E4%BA%86.md)
+* [★ 34-我眼中的async&await](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/34-%E6%88%91%E7%9C%BC%E4%B8%AD%E7%9A%84async%26await.md)
+* [35-ES6中的模块导入导出整理](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/35-ES6%E4%B8%AD%E7%9A%84%E6%A8%A1%E5%9D%97%E5%AF%BC%E5%85%A5%E5%AF%BC%E5%87%BA%E6%95%B4%E7%90%86.md)
+* [41-ES2018(ES9)的新特性](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/41-ES2018%EF%BC%88ES9%EF%BC%89%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7.md)
+* [63-ES6汇总.md](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/63-ES6%E6%B1%87%E6%80%BB.md)
+* [64-ES7汇总.md](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/64-ES7%E6%B1%87%E6%80%BB.md)
+* [65-ES8汇总.md](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/65-ES8%E6%B1%87%E6%80%BB.md)
+* [66-ES9汇总.md](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/66-ES9%E6%B1%87%E6%80%BB.md)
+
+### 4、Webpack
+* [8-vue-cli2的webpack配置分析](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/8-vue-cli2%E7%9A%84webpack%E9%85%8D%E7%BD%AE%E5%88%86%E6%9E%90.md)
+* [25-Webpack入门教程整理(整理中)](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/25-Webpack%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B%E6%95%B4%E7%90%86%EF%BC%88%E6%95%B4%E7%90%86%E4%B8%AD%EF%BC%89.md)
+* [26-Webpack常用配置整理(整理中)](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/26-Webpack%E5%B8%B8%E7%94%A8%E9%85%8D%E7%BD%AE%E6%95%B4%E7%90%86%EF%BC%88%E6%95%B4%E7%90%86%E4%B8%AD%EF%BC%89.md)
+* [75-Webpack怎么运行?](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Article/article/75-Webpack%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%EF%BC%9F.md)
+* [77-一看就懂之webpack高级配置与优化](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Article/article/77-%E4%B8%80%E7%9C%8B%E5%B0%B1%E6%87%82%E4%B9%8Bwebpack%E9%AB%98%E7%BA%A7%E9%85%8D%E7%BD%AE%E4%B8%8E%E4%BC%98%E5%8C%96.md)
+
+### 5、WebSocket
+* [18-websocket常用demo](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/18-websocket%E5%B8%B8%E7%94%A8demo.md)
+* [★ 20-WebSocket重新学习](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/20-WebSocket%E9%87%8D%E6%96%B0%E5%AD%A6%E4%B9%A0.md)
+
+### 6、细节知识点
+* [6-关于js的作用域和声明提前](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/6-%E5%85%B3%E4%BA%8Ejs%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%92%8C%E5%A3%B0%E6%98%8E%E6%8F%90%E5%89%8D.md)
+* [★ 10-知识点整理1](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/10-%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%861.md)
+* [16-带你理解 JS 容易出错的坑和细节](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/16-%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%20JS%20%E5%AE%B9%E6%98%93%E5%87%BA%E9%94%99%E7%9A%84%E5%9D%91%E5%92%8C%E7%BB%86%E8%8A%82.md)
+* [17-TypeScript和 JavaScript 深度对比](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/17-TypeScript%E5%92%8C%20JavaScript%20%E6%B7%B1%E5%BA%A6%E5%AF%B9%E6%AF%94.md)
+* [★ 21-JavaScript异步机制详解](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/21-JavaScript%E5%BC%82%E6%AD%A5%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3.md)
+* [22-JavaScript中有趣的区分同步和异步Ajax](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/22-JavaScript%E4%B8%AD%E6%9C%89%E8%B6%A3%E7%9A%84%E5%8C%BA%E5%88%86%E5%90%8C%E6%AD%A5%E5%92%8C%E5%BC%82%E6%AD%A5Ajax.md)
+* [23-JavaScript八张思维导图](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/23-JavaScript%E5%85%AB%E5%BC%A0%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE.md)
+* [28-JavaScript中的void运算符](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/28-JavaScript%E4%B8%AD%E7%9A%84void%E8%BF%90%E7%AE%97%E7%AC%A6.md)
+* [31-聊一聊JavaScript的IIFE](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/32-%E8%81%8A%E4%B8%80%E8%81%8AJavaScript%E7%9A%84IIFE.md)
+* [33-javascript的纯函数](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/33-javascript%E7%9A%84%E7%BA%AF%E5%87%BD%E6%95%B0.md)
+* [36-好好学习toLocaleString方法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/36-%E5%A5%BD%E5%A5%BD%E5%AD%A6%E4%B9%A0toLocaleString%E6%96%B9%E6%B3%95.md)
+* [37-JavaScript事件委托详解](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/37-JavaScript%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98%E8%AF%A6%E8%A7%A3.md)
+* [38-JavaScript中常见设计模式](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/38-JavaScript%E4%B8%AD%E5%B8%B8%E8%A7%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md)
+* [42-JS高程中的垃圾回收机制与常见内存泄露的解决方法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/42-JS%E9%AB%98%E7%A8%8B%E4%B8%AD%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.md)
+* [43-手机端页面开发常见问题和解决](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/43-%E6%89%8B%E6%9C%BA%E7%AB%AF%E9%A1%B5%E9%9D%A2%E5%BC%80%E5%8F%91%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E5%92%8C%E8%A7%A3%E5%86%B3.md)
+* [44-前端本地文件操作和上传](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/44-%E5%89%8D%E7%AB%AF%E6%9C%AC%E5%9C%B0%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%92%8C%E4%B8%8A%E4%BC%A0.md)
+* [45-js中reduce的神奇用法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/45-js%E4%B8%ADreduce%E7%9A%84%E7%A5%9E%E5%A5%87%E7%94%A8%E6%B3%95.md)
+* [46-在JavaScript中更好的使用数组](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/46-%E5%9C%A8JavaScript%E4%B8%AD%E6%9B%B4%E5%A5%BD%E7%9A%84%E4%BD%BF%E7%94%A8%E6%95%B0%E7%BB%84.md)
+* [48-js获取元素高度和浏览器各种高度方法汇总](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/48-js获取元素高度和浏览器各种高度方法汇总.md)
+* [50-js中get和post的区别](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/50-js%E4%B8%ADget%E5%92%8Cpost%E7%9A%84%E5%8C%BA%E5%88%AB.md)
+* [52-前端模块化(CommonJs,AMD和CMD)](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/52-%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96%EF%BC%88CommonJs%2CAMD%E5%92%8CCMD%EF%BC%89.md)
+* [53-js中call和apply和bind方法介绍](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/53-js%E4%B8%ADcall%E5%92%8Capply%E5%92%8Cbind%E6%96%B9%E6%B3%95%E4%BB%8B%E7%BB%8D.md)
+* [54-ajax详解](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/54-ajax%E8%AF%A6%E8%A7%A3.md)
+* [55-JS中attribute和property区别](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/55-JS%E4%B8%ADattribute%E5%92%8Cproperty%E5%8C%BA%E5%88%AB.md)
+* [56-js中原型继承原理](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/56-js%E4%B8%AD%E5%8E%9F%E5%9E%8B%E7%BB%A7%E6%89%BF%E5%8E%9F%E7%90%86.md)
+* [58-详解HTML5data-自定义属性](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/58-%E8%AF%A6%E8%A7%A3HTML5data-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7.md)
+* [59-前端HTML5几种存储方式的总结](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/59-%E5%89%8D%E7%AB%AFHTML5%E5%87%A0%E7%A7%8D%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F%E7%9A%84%E6%80%BB%E7%BB%93.md)
+* [60-懒加载和预加载](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/60-%E6%87%92%E5%8A%A0%E8%BD%BD%E5%92%8C%E9%A2%84%E5%8A%A0%E8%BD%BD.md)
+* [61-JS中this的4种绑定规则](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/61-JS%E4%B8%ADthis%E7%9A%844%E7%A7%8D%E7%BB%91%E5%AE%9A%E8%A7%84%E5%88%99.md)
+* [67-JS箭头函数的适用和不适用的场景.md](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/67-JS%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E7%9A%84%E9%80%82%E7%94%A8%E5%92%8C%E4%B8%8D%E9%80%82%E7%94%A8%E7%9A%84%E5%9C%BA%E6%99%AF.md)
+* [68-创建对象的七种方式](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/68-%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%B8%83%E7%A7%8D%E6%96%B9%E5%BC%8F.md)
+* [69-秒懂this](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/69-秒懂this.md)
+* [70-JS复杂判断的更优雅写法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/70-JS复杂判断的更优雅写法.md)
+* [71-复习instanceof运算符](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/71-%E5%A4%8D%E4%B9%A0instanceof%E8%BF%90%E7%AE%97%E7%AC%A6.md)
+* [72-【重温基础】JS中的高阶函数](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/72-%E3%80%90%E9%87%8D%E6%B8%A9%E5%9F%BA%E7%A1%80%E3%80%91JS%E4%B8%AD%E7%9A%84%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0.md)
+* [78-【JavaScript】模块化方案总结](https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Article/article/78-JavaScript%20%E6%A8%A1%E5%9D%97%E5%8C%96%E6%96%B9%E6%A1%88%E6%80%BB%E7%BB%93.md)
+
+### 7、HTTP
+* [47-http请求头与响应头的应用](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/47-http%E8%AF%B7%E6%B1%82%E5%A4%B4%E4%B8%8E%E5%93%8D%E5%BA%94%E5%A4%B4%E7%9A%84%E5%BA%94%E7%94%A8.md)
+* [51-Apache之HTTP协议](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/51-Apache%E4%B9%8BHTTP%E5%8D%8F%E8%AE%AE.md)
+
+### 8、正则表达式
+* [30-一次记住js的6个正则方法](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/30-%E4%B8%80%E6%AC%A1%E8%AE%B0%E4%BD%8Fjs%E7%9A%846%E4%B8%AA%E6%AD%A3%E5%88%99%E6%96%B9%E6%B3%95.md)
+* [11-20个超级常用的正则表达式](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/11-20%E4%B8%AA%E8%B6%85%E7%BA%A7%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md)
+* [57-ES5ES6正则表达式总结](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/57-ES5ES6%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%80%BB%E7%BB%93.md)
+
+### 9、面试题
+* [62-2018各大公司近期面试题](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/62-2018%E5%90%84%E5%A4%A7%E5%85%AC%E5%8F%B8%E8%BF%91%E6%9C%9F%E9%9D%A2%E8%AF%95%E9%A2%98.md)
+
+### 10、React
+* [79-在React中使用ShadowDOM](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/79-在React中使用ShadowDOM.md)
+
+### 11、其他
+* [★ 27-Markdowm语法整理](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/27-Markdowm%E8%AF%AD%E6%B3%95%E6%95%B4%E7%90%86.md)
+* [39-作为前端需要了解的开源协议知识](https://github.com/pingan8787/Leo-JavaScript/blob/master/article/39-%E4%BD%9C%E4%B8%BA%E5%89%8D%E7%AB%AF%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E7%9A%84%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE%E7%9F%A5%E8%AF%86.md)
diff --git "a/1-JavaScript\345\256\236\347\216\260\351\241\265\351\235\242\351\230\262\346\212\226.md" "b/Cute-Article/article/1-JavaScript\345\256\236\347\216\260\351\241\265\351\235\242\351\230\262\346\212\226.md"
similarity index 100%
rename from "1-JavaScript\345\256\236\347\216\260\351\241\265\351\235\242\351\230\262\346\212\226.md"
rename to "Cute-Article/article/1-JavaScript\345\256\236\347\216\260\351\241\265\351\235\242\351\230\262\346\212\226.md"
diff --git "a/10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md" "b/Cute-Article/article/10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md"
similarity index 96%
rename from "10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md"
rename to "Cute-Article/article/10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md"
index fe117fcf..44ed17a6 100644
--- "a/10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md"
+++ "b/Cute-Article/article/10-\347\237\245\350\257\206\347\202\271\346\225\264\347\220\2061.md"
@@ -400,7 +400,27 @@ console.log(samesums[0]); // 1
> * `defer`并行加载js文件,会按照页面上`script`标签的顺序执行
> * `async`并行加载js文件,下载完成立即执行,不会按照页面上`script`标签的顺序执行
+### 13、检测属性
+用于判断对象是否存在某个属性:
+```js
+let a = {
+ x : 11,
+ y : 22
+}
+```
+> 1.通过 in 运算符判断:
+```js
+'x' in a ; // true
+'z' in a ; // false
+```
+> 2.通过hasOwnProperty()方法判断:
+```js
+a.hasOwnProperty("x") ; //true : a有一个自有属性x,若是继承属性,返回false
+```
+> 3.更便捷 !== undefined:
+```js
+a.x !== undefined ;//true: a 中有属性 x
+```
-
-### 最近更新 2018.04.09
+### 最近更新 2018.05.17
回到顶部 [介绍](#介绍)
diff --git "a/11-20\344\270\252\350\266\205\347\272\247\345\270\270\347\224\250\347\232\204\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/Cute-Article/article/11-20\344\270\252\350\266\205\347\272\247\345\270\270\347\224\250\347\232\204\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md"
similarity index 100%
rename from "11-20\344\270\252\350\266\205\347\272\247\345\270\270\347\224\250\347\232\204\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md"
rename to "Cute-Article/article/11-20\344\270\252\350\266\205\347\272\247\345\270\270\347\224\250\347\232\204\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md"
diff --git "a/12-javascript\345\274\200\345\217\221\347\232\204\344\270\200\344\272\233\347\256\200\345\206\231\346\212\200\345\267\247.md" "b/Cute-Article/article/12-javascript\345\274\200\345\217\221\347\232\204\344\270\200\344\272\233\347\256\200\345\206\231\346\212\200\345\267\247.md"
similarity index 100%
rename from "12-javascript\345\274\200\345\217\221\347\232\204\344\270\200\344\272\233\347\256\200\345\206\231\346\212\200\345\267\247.md"
rename to "Cute-Article/article/12-javascript\345\274\200\345\217\221\347\232\204\344\270\200\344\272\233\347\256\200\345\206\231\346\212\200\345\267\247.md"
diff --git "a/13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md" "b/Cute-Article/article/13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md"
similarity index 92%
rename from "13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md"
rename to "Cute-Article/article/13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md"
index 67c3854a..e5431109 100644
--- "a/13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md"
+++ "b/Cute-Article/article/13-ES7\345\222\214ES8\347\232\204\344\270\200\347\202\271\346\226\260\344\270\234\350\245\277.md"
@@ -55,8 +55,8 @@ Object.keys(obj).forEach((key) => // Object.keys() 对象=>数组
});
```
-2-不使用ES8
-使用Object.values()遍历对象的属性值,无需使用使用属性名:
+2-使用ES8
+使用Object.values()遍历对象的属性值,无需使用属性名:
```js
let obj = {a: 1, b: 2, c: 3}
Object.values(obj).forEach(value =>
@@ -77,8 +77,8 @@ Object.keys(obj).forEach((key) =>
})
```
-2-不使用ES8
-使用Object.values()遍历对象的属性值,无需使用使用属性名:
+2-使用ES8
+使用Object.entries()遍历对象的属性值,无需使用属性名:
```js
let obj = {a: 1, b: 2, c: 3};
Object.entries(obj).forEach(([key, value]) =>
@@ -90,14 +90,13 @@ Object.entries(obj).forEach(([key, value]) =>
### 5、ES8 - padStart()
在字符串前面填充指定的字符串。
1-不使用ES8
-使用Object.keys()遍历对象的属性名和属性值:
```js
console.log('0.00') // 0.00
console.log('10,000.00') // 10,000.00
console.log('250,000.00') // 250,000.00
```
-2-不使用ES8
+2-使用ES8
使用padStart()可以在字符串前面填充指定的字符串:
```js
console.log('0.00'.padStart(20)) // 0.00
@@ -108,14 +107,13 @@ console.log('250,000.00'.padStart(20)) // 250,000.00
### 6、ES8 - padEnd()
在字符串后面填充指定的字符串。
1-不使用ES8
-使用Object.keys()遍历对象的属性名和属性值:
```js
console.log('0.00 ' + '0.00' ) // 0.00 0.00
console.log('10,000.00 ' + '10,000.00' ) // 10,000.00 10,000.00
console.log('250,000.00 ' + '250,000.00') // 250,000.00 250,000.00
```
-2-不使用ES8
+2-使用ES8
使用padEnd()可以在字符串后面填充指定的字符串:
```js
console.log('0.00'.padEnd(20) + '0.00' ) // 0.00 0.00
@@ -181,4 +179,4 @@ fetchData(query).then(data =>
this.props.processfetchedData(data)
})
```
-`Async/Await` 是写异步代码的新方式,以前的方法有 `回调函数` 和 `Promise` 。相比于 `Promise` ,它更加简洁,并且处理错误、条件语句、中间值都更加方便,因此有望替代 `Promise` ,成为新一代的一步代码编写方式。
\ No newline at end of file
+`Async/Await` 是写异步代码的新方式,以前的方法有 `回调函数` 和 `Promise` 。相比于 `Promise` ,它更加简洁,并且处理错误、条件语句、中间值都更加方便,因此有望替代 `Promise` ,成为新一代的一步代码编写方式。
diff --git "a/14-Vue\347\232\204\344\270\200\344\272\233\345\260\217\346\263\250\346\204\217\347\202\271.md" "b/Cute-Article/article/14-Vue\347\232\204\344\270\200\344\272\233\345\260\217\346\263\250\346\204\217\347\202\271.md"
similarity index 100%
rename from "14-Vue\347\232\204\344\270\200\344\272\233\345\260\217\346\263\250\346\204\217\347\202\271.md"
rename to "Cute-Article/article/14-Vue\347\232\204\344\270\200\344\272\233\345\260\217\346\263\250\346\204\217\347\202\271.md"
diff --git "a/15-\347\262\276\345\277\203\346\224\266\351\233\206\347\232\20448\344\270\252JavaScript\347\211\207\346\256\265\357\274\214\347\256\200\345\215\225\347\220\206\350\247\243.md" "b/Cute-Article/article/15-\347\262\276\345\277\203\346\224\266\351\233\206\347\232\20448\344\270\252JavaScript\347\211\207\346\256\265\357\274\214\347\256\200\345\215\225\347\220\206\350\247\243.md"
similarity index 100%
rename from "15-\347\262\276\345\277\203\346\224\266\351\233\206\347\232\20448\344\270\252JavaScript\347\211\207\346\256\265\357\274\214\347\256\200\345\215\225\347\220\206\350\247\243.md"
rename to "Cute-Article/article/15-\347\262\276\345\277\203\346\224\266\351\233\206\347\232\20448\344\270\252JavaScript\347\211\207\346\256\265\357\274\214\347\256\200\345\215\225\347\220\206\350\247\243.md"
diff --git "a/16-\345\270\246\344\275\240\347\220\206\350\247\243 JS \345\256\271\346\230\223\345\207\272\351\224\231\347\232\204\345\235\221\345\222\214\347\273\206\350\212\202.md" "b/Cute-Article/article/16-\345\270\246\344\275\240\347\220\206\350\247\243 JS \345\256\271\346\230\223\345\207\272\351\224\231\347\232\204\345\235\221\345\222\214\347\273\206\350\212\202.md"
similarity index 100%
rename from "16-\345\270\246\344\275\240\347\220\206\350\247\243 JS \345\256\271\346\230\223\345\207\272\351\224\231\347\232\204\345\235\221\345\222\214\347\273\206\350\212\202.md"
rename to "Cute-Article/article/16-\345\270\246\344\275\240\347\220\206\350\247\243 JS \345\256\271\346\230\223\345\207\272\351\224\231\347\232\204\345\235\221\345\222\214\347\273\206\350\212\202.md"
diff --git "a/17-TypeScript\345\222\214 JavaScript \346\267\261\345\272\246\345\257\271\346\257\224.md" "b/Cute-Article/article/17-TypeScript\345\222\214 JavaScript \346\267\261\345\272\246\345\257\271\346\257\224.md"
similarity index 100%
rename from "17-TypeScript\345\222\214 JavaScript \346\267\261\345\272\246\345\257\271\346\257\224.md"
rename to "Cute-Article/article/17-TypeScript\345\222\214 JavaScript \346\267\261\345\272\246\345\257\271\346\257\224.md"
diff --git "a/18-websocket\345\270\270\347\224\250demo.md" "b/Cute-Article/article/18-websocket\345\270\270\347\224\250demo.md"
similarity index 100%
rename from "18-websocket\345\270\270\347\224\250demo.md"
rename to "Cute-Article/article/18-websocket\345\270\270\347\224\250demo.md"
diff --git "a/19-ES6\347\232\2047\344\270\252\345\256\236\347\224\250\346\212\200\345\267\247.md" "b/Cute-Article/article/19-ES6\347\232\2047\344\270\252\345\256\236\347\224\250\346\212\200\345\267\247.md"
similarity index 100%
rename from "19-ES6\347\232\2047\344\270\252\345\256\236\347\224\250\346\212\200\345\267\247.md"
rename to "Cute-Article/article/19-ES6\347\232\2047\344\270\252\345\256\236\347\224\250\346\212\200\345\267\247.md"
diff --git "a/2-\345\220\214\346\255\245\350\277\224\345\233\236ajax\350\257\267\346\261\202\347\273\223\346\236\234\346\226\271\346\263\225.md" "b/Cute-Article/article/2-\345\220\214\346\255\245\350\277\224\345\233\236ajax\350\257\267\346\261\202\347\273\223\346\236\234\346\226\271\346\263\225.md"
similarity index 100%
rename from "2-\345\220\214\346\255\245\350\277\224\345\233\236ajax\350\257\267\346\261\202\347\273\223\346\236\234\346\226\271\346\263\225.md"
rename to "Cute-Article/article/2-\345\220\214\346\255\245\350\277\224\345\233\236ajax\350\257\267\346\261\202\347\273\223\346\236\234\346\226\271\346\263\225.md"
diff --git "a/20-WebSocket\351\207\215\346\226\260\345\255\246\344\271\240.md" "b/Cute-Article/article/20-WebSocket\351\207\215\346\226\260\345\255\246\344\271\240.md"
similarity index 100%
rename from "20-WebSocket\351\207\215\346\226\260\345\255\246\344\271\240.md"
rename to "Cute-Article/article/20-WebSocket\351\207\215\346\226\260\345\255\246\344\271\240.md"
diff --git "a/2017-\345\205\250\345\271\264\346\200\273\347\273\223.md" "b/Cute-Article/article/2017-\345\205\250\345\271\264\346\200\273\347\273\223.md"
similarity index 100%
rename from "2017-\345\205\250\345\271\264\346\200\273\347\273\223.md"
rename to "Cute-Article/article/2017-\345\205\250\345\271\264\346\200\273\347\273\223.md"
diff --git "a/Cute-Article/article/2018-\345\205\250\345\271\264\346\200\273\347\273\223.md" "b/Cute-Article/article/2018-\345\205\250\345\271\264\346\200\273\347\273\223.md"
new file mode 100644
index 00000000..3867c47b
--- /dev/null
+++ "b/Cute-Article/article/2018-\345\205\250\345\271\264\346\200\273\347\273\223.md"
@@ -0,0 +1,202 @@
+****
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+****
+
+今年一整年,有这么几个感悟:
+> 我相信每个人都是幸运,而且聪明。
+> 厚积才能薄发,基础牢固才能做好。
+> 做出改变,做好调整,接受挑战,对自己有好处。
+> 自律很重要。
+
+# 零、瞎聊瞎感慨
+2018年,我很高兴,体重比和一个朋友打赌说的那个数字多了15斤,哇。
+2018年,我也很难过,离开了一群很棒的小伙伴,哎。
+
+这一年,我买了好几本新书,但都没有看完。
+这一年,我独自去了很多地方,锻炼自己的内心。
+
+程序员第二年,我边放边学了深度学习,有点时间不够。
+程序员第二年,我可以总结文章,和同事朋友聊代码聊网络聊计算机原理算法(也是皮毛)。
+
+当了第26年的儿子和弟弟,我多往母亲的卡里打了很多钱(我的能力范围内),让家人省心但也让家人操心了。
+当了第26年的儿子和弟弟,我更多的替哥哥姐姐着想,也更清楚家的重要。
+
+离开第三年,思想总在围城,但总有一件事能让人彻底释放。
+离开第三年,清空以后,发现我应该我可以做更多事情。
+
+这一年的其他角色,有好有坏,愿随时间长河,消逝,冲散,堆积,沉淀。
+
+# 一、2018计划完成情况
+开始写这篇总结的时候,我翻了一下 [49-【总结】2017全年总结](http://pingan8787.com/2018/02/21/49-%E3%80%90%E6%80%BB%E7%BB%93%E3%80%912017%E5%85%A8%E5%B9%B4%E6%80%BB%E7%BB%93/) 也深深回忆了一下,感叹,感谢你们。
+
+我忐忑的打开 [49-【总结】2018全年计划](pingan8787.com/2018/02/21/49-【总结】2018全年计划/),总觉得,这一年,这么快,也这么慢。
+
+## 1.1 2018计划概述
+大概整理一些:
+**学习上**
+* 前端:
+前端知识的基础打牢固,学习React框架,深入研究Vuejs源码,和其他NPM包。
+
+* 后端:
+学习Nodejs,从Express/Koa去实践,Mongodb也要研究和运用。
+
+* 客户端开发:
+移动端APP从RN和Weex入手,桌面端放弃掉。
+
+* 博客:
+改版和模块进来,Github和掘金,开始发原创和维护自己的项目。
+
+* 机器学习:
+深入去学习这块,可以从图像识别入手,另外可以考虑物联网方面。
+
+* 计算机研究:
+从一本书开始研究计算机原理,《算法导论》去学习算法,适当学习计算机通信知识。
+
+**生活上**
+* 日常生活:
+换个新宿舍,自己学着做健康早餐,考完驾照,和女票游玩一次。
+
+* 健康生活:
+多跑步,研究健康饮食。
+
+* 家庭生活:
+替母亲分担,争取过年给更大礼物,帮家里重新装修,多和哥哥姐姐分享交流,找个女票。
+
+
+## 1.2 2018计划统计
+检查了下 **2018全年计划** 的完成情况,有些高兴,有些失望,也有些欣慰。
+先来一张github的contributions图:
+
+
+
+**学习上**
+* 前端:
+前端基础知识已经复习起来了,在掘金整理了一个系列的文章,还在更新中。
+[pingan8787 掘金](https://juejin.im/user/586fc337a22b9d0058807d53/posts) 和
+[【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)](https://juejin.im/post/5c02b106f265da61764aa0c1)
+这两块大的文章。
+React有学习一些,Vuejs源码也有研究一些,但是现在的工作,用的都是Angular,所以React和Vuejs对于我而言更多的意义是参考和学习。
+NPM包,今年就研究了lodash的源码。
+
+* 后端:
+`Nodejs`和`Mongodb`开始学习,有用了`Express`和`Mongodb`做了个自己用的小记事本,源码放在 [小小日记本](https://github.com/pingan8787/Leo_Nodejs/tree/master/express/express%2Bmongoose%20%E6%97%A5%E8%AE%B0%E6%9C%AC) 里面,功能比较简单,以后还会开发新的项目。
+
+* 客户端开发:
+移动端APP:RN研究过一点皮毛,但是如今主攻`flutter`这个非常棒,也要学习`dart`,桌面端放弃掉。
+
+* 博客:
+博客:添加了【微博】【评论】【样式】等一些东西。
+模块:添加了【ES规范小册】,后面还会有【ES基础】【ES面试】等。
+Github掘金简书思否:开始在同步自己的原创文章,目前主要两块[pingan8787 掘金](https://juejin.im/user/586fc337a22b9d0058807d53/posts) 和
+[【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)](https://juejin.im/post/5c02b106f265da61764aa0c1) ,也有一些其他的。
+另外我的[每日文章](https://github.com/pingan8787/Leo_Reading/issues)系列也坚持了2018年完整一年了。
+
+* 机器学习:
+深度学习这块,今年算是从入门到入门吧,后面真的是没有时间去研究,也没有机会去实战,导致`python`的也不熟练,如果接下来还继续,会考虑`JavaScript`实现。但是原理和高数这些,还是得实打实。
+
+* 计算机研究:
+今年通过网易云课堂学了一些大学计算机基础的知识,了解了很多原理层面的知识,不过还要再深入点。
+
+**生活上**
+* 日常生活:
+宿舍还是没搬,在这里离公司比较近,而且房东一家都很好,觉得可以再住一年。
+健康早餐,一直在坚持煮粥或者燕麦或者核桃粉,搭配面包或包子,感觉还是很棒的。
+女票木有,驾照科三挂了就没去了,因为烦也因为不好请假那么多天事情也多。
+
+* 健康生活:
+好像今年,跑的真的很少,主要是骑行,30多公里是最多的。
+
+* 家庭生活:
+借着让母亲帮忙存钱的理由,往家里多寄了更多钱,也存好给母亲买礼物的钱。
+家里重新装修好了,家电设备也换了新的了。
+哥哥姐姐交流更多,也更多的互相理解。
+
+# 二、2018总结和分析
+总结和分析,按照以前的框架,总结了下这一年:
+
+## 2.1 最正确的一件事
+在这一年,做正确的事情,很多,但不可否认,在我内心认为,做得最重要的事情,是来到现在的公司。
+写下这句,心里有点惭愧,但确实是如此,我追求技能提高,技术进步,价值提升,我追求事业追求理想。
+我在WLHD很开心,也很自豪,一群小伙伴,每个人都这么的友善和友好,感谢每个人也很感谢镇智的亦师亦友。
+
+
+之所以作为最正确的一件事情,是因为进入到EXE的三个月,我真的学习到很多,跟我自己理想的,计划的是一致的,总结有这几方面:
+* 公司主项目,是我所喜欢的,是用户使用频率高,贴近生活的,有前景和价值的,是正导向的。
+* 公司福利,比较符合我的想法。
+* 项目技术栈,是`Angular`这个我爱恨交加的框架,虽然现在我负责的还是用1的版本,但是`Angular`确实是个好家伙,国外大厂都喜欢,并且项目也混合这其他技术进来,好像还有一些我不知道的,也有一些未来的尝试(具体涉及到的名称我删除掉了)。
+* 团队协作,是`git流`,这真的是超级棒,代码管理起来非常方便(虽然刚开始用的时候踩了很多坑),公司用的`JIRA`也特别棒。
+* 团队氛围,开发职责分明,有大牛,还有宝哥这个超神的人,每周二周四晚上的技术分享会,技术氛围特别好。
+
+在公司每次和同事,特别是宝哥,都会给我们讲很多很棒的知识,大到前沿技术小到源码阅读等,受到宝哥的影响,我开始尝试学习和分享,坚持下来。感觉很棒。
+家里没有矿也没有油,我只能激励自己,做得更好,希望路过我的世界的你们,都能好,能更好。
+
+## 2.2 最错误的一件事
+可能,没有最,因为我回忆了好久,没有想到最错误。
+但有一件事,我心里比较清楚,只是有点愧疚,不想写,愿这样做,会是做好的结果。
+
+## 2.3 最疯狂的一件事
+这必须是**世界杯**,哈哈,四年一届,2014年的世界杯,我还在实习,不得不说时间过得真的快。
+这届世界杯,我差不多看了35场左右,基本都是半夜12点 2点 4点开始的球赛,有时候一个晚上2场,直接看到早上,并且第二天要上班,然而我还能精神,也是不容易。
+这届世界杯,喜怒哀乐都有,花了点小球买了球,德国的早早淘汰,中国的迟迟观望,法国克罗地亚的超常发挥,日本韩国的眼前一亮,西班牙葡萄牙的惊险刺激,还有一些熟悉的名字:莫德里奇 C罗 梅西 姆巴佩 凯恩 库尔图瓦 萨拉赫 久巴 苏亚雷斯 小豌豆 佩佩 德赫亚 伊涅斯塔 吉鲁 格里兹曼 阿圭罗 伊瓜因 等等等等,太多了太棒了。
+我也在想,下届世界杯,那时候的我,会是神马样?
+
+## 2.4 最开心的一件事
+还是和去年一样,一整年下来,大大小小的开心事,贼多呢,but,最开心,应该是和我哥一起做了一个小项目,一个很有意思的项目。
+这个项目我们两个人,一个出想法,一个敲代码,我感觉很棒,和哥哥齐心协力,赚钱,兄弟应该是这样的。
+虽然呢,赚不多,但是,是好的开始,兄弟应该一起加油的。
+现在也是经常跟我哥一起聊一些赚钱的项目,聊一些我们的人生观价值观等,对一些事情的看法,更加互相了解,我哥是我的榜样。
+
+## 2.5 2018工作学习上
+现在是北京时间(2019.01.06 02:14),可能写完会更晚。
+工作上分为两块:
+* WLHD期间:
+**第一阶段:**
+人工智能研究,还在为人工智能深度学习挥洒汗水,虽然工作上没有实际用到,但是更重要的意义在于,让我拓展了视野,知道更广阔的计算机世界,还有知识与实践结合,也清楚了人工智能对于未来的意义所在。
+**第二阶段:**
+公司旧项目维护和新项目开展,大约有半年左右,这段时间,对于后台管理系统的完善,我有个更清晰的认识,就是对于开发后台管理系统,要多考虑数据默认值,搜索性能,路由安全权限,组件模块化。对于公司人才的招聘,我也是有些自己的感想,由于团队小,招聘进来的人,需要性格跟团队合得来,技术上需要看得到热情。
+**第三阶段:**
+项目转型,转型往往会使人一下子难以适应,产生迷茫,自己该坚持的事情依然坚持,但环境却在变化,变化着,有时候转过身来才发现,原来自己再原地走。
+
+* EXE期间:
+**第一阶段:**
+适应期,这段时间有各种不适应,也担心出错,也积极去融入团队和公司,这段时间经历了尴尬的团队成员的认识,经历了公司的羽毛球比赛,经历了合并掉其他同事的代码,经历了发版之前的代码大修改,经历了好多次的通宵,经历了一次严重的感冒,经历...
+**第二阶段:**
+稳定成长期,这段时间算开始稳定和熟悉了,日常工作也开始正常,也跟着公司的脚步开始学习一些新技术,特别是`flutter`,真香。
+现在也更懂得项目的团队协作方式,并且这是非常重要的,好比前面写到的。
+
+学习上:
+* 今年也参加了好几个前端技术分享会,确实,大公司大团队新技术,这些都是让我大开眼界,更加坚信前端的未来前景。
+* 这一年学习的新知识可以说是内容多范围广,大到人工智能机器学习,小到冒泡算法等,学的越多,越需要总结和记录。
+
+## 2.6 2018感情上
+这一年的感情生活,相比去年,有进步,被困扰的感情,已经都放开了,放过彼此,我也过得更好,对于自己内心的想法,还是早点告知免得浪费对方时间。出现在我的世界的人,依然在。
+这一年,似乎对于找女朋友的看法,也在转变,有很多的应该,也有很多的至少,这算是一些条件。
+反正,对我来说,能聊的开心,这是重要的。
+
+## 2.7 2018养成新习惯
+这一年新养成的一些习惯,相比去年,增加了几个,也时常告诉着自己,自律自律自律:
+* 第一个,学习上,开始去按照一个系列的内容,去系统的学习和回顾知识,这包括我整理下来的[pingan8787 掘金](https://juejin.im/user/586fc337a22b9d0058807d53/posts) 和
+[【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)](https://juejin.im/post/5c02b106f265da61764aa0c1) 这两个系列,按照系列的课程系统学习,是一种循序渐进的过程,才能深入去学习。现在的系统学习,我更偏向于,学习+记录,将复习和学习完的知识点,按照自己的逻辑写一遍,补充完善,成为自己的东西,也方便以后查阅。
+* 第二个,迁移自己的开发环境,慢慢把自己开发环境往Mac迁移,因为公司给的电脑是Win的额,只能用自己的Mac系统,不过确实Mac很适合我们前端开发呢。
+* 第三个,更注重健康生活,每天必须坚持做的几个:早上不会着急马上起床,必须伸懒腰全身舒展后休息一会才起床,毕竟急急忙忙起床会影响一天的工作情绪;早起必须做早操,活动下全身,很棒的感觉;晚上睡觉前必须做几组俯卧撑,已经是习惯了。
+* 第四个,坚持公众号推送,不为给别人看,只为自己做积累。
+## 2.8 2018小小欣慰的事
+我更愿意,花时间去看一些书,虽然是后半年才开始,新加入两本书《南方高速》和《地球上最后的夜晚》,似乎境界还不足理解作者真正或深入的内涵,但看书,确实能让人静心,养性,抛开外面的纷繁世界,沉浸自我。
+
+
+# 完、再来几句
+原来,总结就是个回忆的过程,各种感受都会有,相信我们现在走的每一步,都是我们的成长,以后成为自己喜欢的那个人。
+这一年,也更加相信“你在成就公司,公司也在成就你”这句话了。
+
+Tips:我用Markdown也算是熟练一些了,比起去年总结的排版好看多了呢。
+
+
+
+
+完:2019.01.06 12:39
\ No newline at end of file
diff --git "a/2018-\345\205\250\345\271\264\350\256\241\345\210\222.md" "b/Cute-Article/article/2018-\345\205\250\345\271\264\350\256\241\345\210\222.md"
similarity index 59%
rename from "2018-\345\205\250\345\271\264\350\256\241\345\210\222.md"
rename to "Cute-Article/article/2018-\345\205\250\345\271\264\350\256\241\345\210\222.md"
index fbccd84b..07403f46 100644
--- "a/2018-\345\205\250\345\271\264\350\256\241\345\210\222.md"
+++ "b/Cute-Article/article/2018-\345\205\250\345\271\264\350\256\241\345\210\222.md"
@@ -22,35 +22,43 @@
# 1、工作
今年我会换一份工作,公司偏向正常化,项目正常化,也许我还是比较实在,很多现在公司在做的项目和方向我并不喜欢,有各种担心。
我想说现在的工作,公司的方向不明确项目不靠谱,画饼太多真实性差,同事积极性差,唯我难以带起所有人,我已经很努力做好我本职,包括不是本职我也努力帮忙,努力维护军心,但是依旧难以调动公司氛围,于自身能力也好,于职位有碍也好,我认为公司的Leader必须要有几点,非常重要(个人想法):
-* 1.决策力,在该做决定时能有决策能力;
-* 2.影响力,能让自己(积极工作等)影响到其他人;
-* 3.执行力,在确定的项目上不轻易放弃一个项目,一个idea,一句title;
+
+1. 决策力,在该做决定时能有决策能力;
+2. 影响力,能让自己(积极工作等)影响到其他人;
+3. 执行力,在确定的项目上不轻易放弃一个项目,一个idea,一句title;
+
其他还有一个,大概意思就是Leader需要对员工负责,关心员工自身发展。
这一年,我想继续试试心目中比较向往的那家公司,一年多的沉淀也许技术上不是非常牛逼,但是我想继续学习更多知识,学习更多项目开发的经验,项目管理等,若能成我将在此潜心学习修行很长时间,我不喜欢现在的虚虚实实现在公司的迷茫,性格问题,我想接触更多有意思的人和事。
# 2、学习
通过去年一年的各种踩坑试错,慢慢总结出自己的一套学习方法,不能说高效,但适合现在的自己,2018年继续按照这套学习方法,学习更多知识,踩更多坑,并总结起来,优化调整学习方法。
经过去年的尝试和踩坑,2018年,是时候该好好学习下面几个内容:
-* 前端知识:
-* 1. 前端基础:HTML/CSS/JavaScript更多基础,将原生知识基础打牢固,还有ES6/7/8/9研究;
-* 2. 前端框架:React需要开始学习,Vuejs深入源码学习更多原理知识,还有很多不错的NPM包;
-* 后端知识:
-* 1. 服务端开发:Nodejs(包含Express/koa等框架)是时候开始研究并运用了;
-* 2. 数据库开发:Mongodb(包含Mongoose)也需要开始研究和运用了;
-* 客户端开发:
-* 1. APP开发:React Native或者Weex,今年应该从这两个方向入手APP开发,开发环境也将开始转向Mac开发;
-* 2. 桌面应用程序开发:Electorn可以用,但是我放弃桌面应用程序开发,no why 就是不喜欢;
-* 博客:
-* 1. 个人博客:需要做改版和调整,添加新标签进来;
-* 2. github:几个仓库和issue继续维护,也许会再添加一个自己项目的仓库;
-* 3. 掘金:开始尝试发表自己的原创文章;
-* 机器学习:
-* 1. 深度学习:找个方向深入学习,可能会是图像识别;
-* 2. 物联网:我有兴趣;
-* 计算机研究:
-* 1. 计算机原理:这是我一直想好好研究的内容,去年碍于各种问题未能开展,今年从一本书入手研究;
-* 2. 计算机算法:算法真的很有意思,《算法导论》很棒但很难,尝试静心研究下去;
-* 3. 计算机通信:反正很棒,暂未想好如何研究;
+
+**前端知识**:
+1. 前端基础:HTML/CSS/JavaScript更多基础,将原生知识基础打牢固,还有ES6/7/8/9研究;
+2. 前端框架:React需要开始学习,Vuejs深入源码学习更多原理知识,还有很多不错的NPM包;
+
+**后端知识**:
+1. 服务端开发:Nodejs(包含Express/koa等框架)是时候开始研究并运用了;
+2. 数据库开发:Mongodb(包含Mongoose)也需要开始研究和运用了;
+
+**客户端开发**:
+1. APP开发:React Native或者Weex,今年应该从这两个方向入手APP开发,开发环境也将开始转向Mac开发;
+2. 桌面应用程序开发:Electorn可以用,但是我放弃桌面应用程序开发,no why 就是不喜欢;
+
+**博客**:
+1. 个人博客:需要做改版和调整,添加新标签进来;
+2. github:几个仓库和issue继续维护,也许会再添加一个自己项目的仓库;
+3. 掘金:开始尝试发表自己的原创文章;
+
+**机器学习**:
+1. 深度学习:找个方向深入学习,可能会是图像识别;
+2. 物联网:我有兴趣;
+
+**计算机研究**:
+1. 计算机原理:这是我一直想好好研究的内容,去年碍于各种问题未能开展,今年从一本书入手研究;
+2. 计算机算法:算法真的很有意思,《算法导论》很棒但很难,尝试静心研究下去;
+3. 计算机通信:反正很棒,暂未想好如何研究;
差不多这样,满满一整年的任务,还是很重的,毕竟今年是工作上开始转向成熟稳定的一年。
> 这里插入一件小事,一个朋友再向我讨教一个框架的问题,这个框架我花了很多时间研究,ta说我怎么这么厉害懂这么多,我说多花时间研究就懂啦,ta的回答有点出乎我的意料,说真的,我的下巴都快掉了,ta说(大概意思):“我除了上班没事做才研究,不然都没时间研究这些,下班了谁还敲代码!”,这句话当时真心吓到我了,我总结的是:“出门在外学习知识是为了提升自身价值,并非被工作所左右,想要学习知识你就会想办法主动学习,主动自觉的学习!”。说完,便不想回复ta微信,因为发现跟ta认识到现在,基本能了解这个人,抱怨太多而不脚踏实地研究学习。
@@ -59,18 +67,21 @@
# 3.生活
从前年至今,我经历了最苦逼的日子(真正是快吃不起饭的日子),经历了最迷茫混沌的日子(离职和学习的迷茫),经历了最黑暗的加班赶项目的日子(第一次每天加班到晚上十点多周末无休),经历了感情上最纠缠的日子(难以割舍的真正初恋的她),还经历了最奋斗的日子(人工智能深度学习让我真的着迷)等等,这一年的神奇竟然我是都撑了下来,自己的一次成长,看待这些事情更多了一份成熟。
2018年生活规划:
-* 日常生活:
-* 1. 住宿条件:换一个新的安静点的房间,为以后女朋友一起住做准备;
-* 2. 住宿生活:尽量自己煮饭,外卖真心难吃且油腻不健康,早餐一定自己煮,健康且营养;
-* 3. 驾驶证:6月份前考完;
-* 4. 关于游玩:跟女朋友游玩一次,任性,哈哈;
-* 健康生活:
-* 1. 跑步健身:6月份左右或者提前,担心自己程序猿坐久了身体出问题,而且我也挺喜欢跑步,放空自己;
-* 2. 饮食健康:早餐食谱研究一套,或者在现在的基础丰富,午餐公司,晚餐尽量更好点,夜宵感觉不需要,怕变胖就成中年大叔了;
-* 家庭生活:
-* 1. 母亲:替母亲分担重任,每个月打更多钱回家,过年买一条比今年大一倍的金项链什么的给母亲,过年红包也会更大,今年还要帮母亲把家里重新装修;
-* 2. 女朋友:感情需要更加深入,也许来我这边一起工作生活,彼此磨合,更加了解对方,然后带她去看更大的世界;
-* 3. 哥哥姐姐:帮他们分担更多烦恼,多跟他们交流,帮他们做更多我办得到的事情还要我的小外甥,常回家看看;
+
+**日常生活**:
+1. 住宿条件:换一个新的安静点的房间,为以后女朋友一起住做准备;
+2. 住宿生活:尽量自己煮饭,外卖真心难吃且油腻不健康,早餐一定自己煮,健康且营养;
+3. 驾驶证:6月份前考完;
+4. 关于游玩:跟女朋友游玩一次,任性,哈哈;
+
+**健康生活**:
+1. 跑步健身:6月份左右或者提前,担心自己程序猿坐久了身体出问题,而且我也挺喜欢跑步,放空自己;
+2. 饮食健康:早餐食谱研究一套,或者在现在的基础丰富,午餐公司,晚餐尽量更好点,夜宵感觉不需要,怕变胖就成中年大叔了;
+
+**家庭生活**:
+1. 母亲:替母亲分担重任,每个月打更多钱回家,过年买一条比今年大一倍的金项链什么的给母亲,过年红包也会更大,今年还要帮母亲把家里重新装修;
+2. 女朋友:感情需要更加深入,也许来我这边一起工作生活,彼此磨合,更加了解对方,然后带她去看更大的世界;
+3. 哥哥姐姐:帮他们分担更多烦恼,多跟他们交流,帮他们做更多我办得到的事情还要我的小外甥,常回家看看;
2018全年规划基本如此,需要让自己将24小时过成26小时。
去年一年的沉淀,让我看来很多事情更有自己的考虑和考量,2018继续加油,脚踏实地,不忘初心,为自己为家庭也为公司,继续加油!Love coding , Love life .
diff --git "a/Cute-Article/article/2019-\345\205\250\345\271\264\346\200\273\347\273\223.md" "b/Cute-Article/article/2019-\345\205\250\345\271\264\346\200\273\347\273\223.md"
new file mode 100644
index 00000000..a21c91f4
--- /dev/null
+++ "b/Cute-Article/article/2019-\345\205\250\345\271\264\346\200\273\347\273\223.md"
@@ -0,0 +1,136 @@
+
+
+
+回顾这一年,几句总结:
+> 再忙也要留点学习进步的时间;
+> 再小的事,都是未来的基石;
+
+## 零、与人为善
+
+
+## 一、2019 计划完成情况
+
+每年总结,写到回顾去年计划完成情况,各种事情便仿佛在眼前一闪而过。翻起去年[《130-【总结】2019全年计划》](http://pingan8787.com/2019/01/12/130-%E3%80%90%E6%80%BB%E7%BB%93%E3%80%912019%E5%85%A8%E5%B9%B4%E8%AE%A1%E5%88%92/),隐约闻到时间的味道,还有太阳的味道,不知你们会不会这样觉得。
+
+先放几张这一年的代码 push 记录,累加起来接近一整年啦:
+
+* **Github**:
+
+
+
+* **Gitlab**:
+
+
+
+* **Gitee**:
+
+
+
+
+### 1. 回顾学习规划
+
+回顾今年学习规划,学习的知识范围更集中了,也做到知识沉淀,将沉淀的知识转化为自己的文章,并同步到各个前端技术社区、[博客](htt://www.pingan8787.com)和[语雀](https://www.yuque.com/wangpingan/cute-frontend)上。
+
+下面将几部分内容分别总结下:
+
+* **前端知识**
+
+前端基础知识,整理了包括 JavaScript / ECAMScript / TypeScript / HTTP / 数据结构与算法 / 正则表达式 / Hybrid / 设计模式 等一系列基础入门知识的文章。
+
+前端框架知识,整理了包括 Webpack / Angular / GraphQL / React / MobX 几个框架的基础入门知识的文章。
+
+今年也学习很多其他知识,包括 JSBridge / 前端原生端交互 / ionic-native 原理 / Auto2 / CSRF和CORS / Restful等等等等。
+
+很多已经汇总在[《Cute-Frontend》](https://www.yuque.com/wangpingan/cute-frontend)中。
+
+* **后端知识**
+
+学习 Eggjs + Mongodb + Redis ,并开发团队内部项目网盘项目,说真的也是学习了很多后端开发的思维。
+另外今年也有使用 Nodejs 写了几个简单工具,如:Fiddler 请求接口时间统计工具 / Excel 公众号数据清洗工具。
+
+* **个人品牌**
+
+公众号【前端自习课】:今年满勤,截止今天已经连续推送 461 篇(含删除/未获得白名单的转载/发重复),粉丝数涨了约 11 倍(从2019/7/24开始),阅读量稳定 160+(自然)。
+
+个人博客:将自己在其他社区发的文章都同步到博客,原创文章 57 篇,未达标。
+
+其他社区:今年推送的文章分享的平台包括:[掘金](https://juejin.im/user/586fc337a22b9d0058807d53/posts)、[思否](https://segmentfault.com/blog/pingan8787)、[知乎](https://zhuanlan.zhihu.com/cute-javascript)、[CSDN](https://blog.csdn.net/qq_36380426)、[语雀](https://www.yuque.com/wangpingan/cute-frontend)、[简书](https://www.jianshu.com/u/2ec5d94afd60)、[慕课手记](https://www.imooc.com/u/3591250/articles)还有自己[博客](http://www.pingan8787.com)和 [github](https://github.com/pingan8787)。掘金粉丝 4429 未达标,点赞数 8862 也未达标。
+
+[Github Reading](https://github.com/pingan8787/Leo_Reading/issues):今年满勤,截止这个月已经连续打卡 30 个月了。
+
+### 2. 回顾工作规划
+
+工作上,对于迭代需求任务,已经养成开发前核对需求,提测前核对UI稿和原型稿这样的习惯,几次迭代任务实现 0 BUG。
+
+这一年也向团队前端学习了很多,为人处世,工作方式,工作技能等等。对于公司产品,今年积极参与各种运营活动(学习打卡、点赞活动、学习任务等),也发表一些自己对产品的看法,两个专栏号持续输出中。
+
+### 3. 回顾生活规划
+
+生活的规划中,2019年,熬夜少了很多,基本12点准时关电脑睡觉,除了自己喜欢的球赛啦~
+
+锻炼这方面也是陆陆续续,唯一做得好的可能就是坚持自己做早饭,牛奶燕麦,白米粥,绿豆粥,燕麦粥,烤面包,煎蛋,水煮蛋,培根鸡蛋堡,今年的早餐都是这些,也比较清淡。
+
+宿舍没换,不过住得舒适呀,另外独自去了很多地方,骑行 1100+ 公里:
+
+
+
+达成厦门岛换到骑行🚴的个人记录:
+
+
+
+
+## 二、2019 总结和分析
+
+老套路,继续按照每个类型选一件事情来说:
+
+### 2019 最正确的一件事
+
+回顾这一整年,发生的事情真的很多,做得正确的事情也很多,从对于职业生涯和自身性格出发,我觉得2019我做得最正确的一件事是**12月份去杭州参加语雀知识大会**,我也总结一篇[《北上 寻“雀”》](https://www.yuque.com/wangpingan/cute-frontend/uf68aa)。
+
+这趟三天旅行,是我独自一人制定所有行程和计划,我逛了阿里巴巴、支付宝、蚂蚁金服、湖畔花园这些互联网传奇的地方,也去了西湖、雷峰塔、钱塘江大桥、京杭大运河、浙江大学、黄龙洞等等。
+
+也面基玉伯大佬,并拿到签名与合影,😄哈哈。
+
+
+
+这次行程,很大程度拓展我的视野,一些只在互联网上存在的庞然大物,原来也是离我这么近的,并且今年有一次蚂蚁金服的面试,虽然挂了,还是自身本领不够。
+
+很多时候,人的眼界格局一下子产生变换,很多失败,我都将之视为经验,未来,再来。
+
+### 2019 最错误的一件事
+
+这应该算是那次顶撞母亲的事情,被催相亲催到发脾气,顶撞母亲,实在惭愧惭愧,虽然已经和母亲交谈,但心里还是挺惭愧。
+
+### 2019 最开心的一件事
+
+当然属于公众号【前端自习课】,全年满勤,截止今天已经连续推送 461 篇(含删除/未获得白名单的转载/发重复),粉丝数涨了约 11 倍(从2019/7/24开始),阅读量稳定 160+(自然)。
+
+公众号的初衷,是想**看看自己能坚持做这件事情做多久**,后面发现,做这个事情特别有意义,对我对他人都是。
+
+对我而已,是自己学习的素材库,坚持的动力,也是和圈子交流的话题。
+
+对他人而言,这也许是他们发现宝藏的时刻。
+
+* **[【前端自习课】的2019](https://cu2019.newrank.cn/h5.html?n=5903fb7)**
+
+2019.07.24 开始正式向外部宣传,至今收获 2300+ 粉丝,一个 100+ 人微信群。
+
+累计 3W 阅读量,693 个在看,362 天发布, 404 篇文章, 89 篇原创 ,267W 字...
+
+
+
+另外今年也将为【前端自习课】开发了[文章分类大全,点击链接即可体验](https://blog.pingan8787.com/weixin/index.html),另外也有小程序,只是小程序有现在无法打开公众号文章。
+
+## 三、思考自省
+
+21世纪20年代已经开始。
+
+总结过往 10 年,人生两大事情改变了我,人的生老病死,爱情的现实。
+
+既然遇到了,那我就积极、勇敢面对,去改变。
+
+这 10 年,从一个娇生惯养的小孩,到一心只有这个家的年轻人,双手不再稚嫩,双肩不再轻荡。
+
+努力做好自己,强大自己,去学习,去进步。
+
+这 10 年,我尽力了。
\ No newline at end of file
diff --git "a/Cute-Article/article/2019-\345\205\250\345\271\264\350\256\241\345\210\222.md" "b/Cute-Article/article/2019-\345\205\250\345\271\264\350\256\241\345\210\222.md"
new file mode 100644
index 00000000..e4716209
--- /dev/null
+++ "b/Cute-Article/article/2019-\345\205\250\345\271\264\350\256\241\345\210\222.md"
@@ -0,0 +1,100 @@
+****
+|Author|王平安|
+|---|---|
+|E-mail|pingan8787@qq.com|
+|博 客|www.pingan8787.com|
+|微 信|pingan8787|
+|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
+|JS小册|js.pingan8787.com|
+****
+
+> 我相信每个人都是幸运,而且聪明。
+> 厚积才能薄发,基础牢固才能做好。
+> 做出改变,做好调整,接受挑战,对自己有好处。
+> 自律很重要。
+
+以上是摘自我的《2018-全年计划》(这里就不放地址了)
+
+也许还有一句,我比较喜欢的:你在成就公司,公司也在成就你
+
+2019年对我来说,应该会变得更加理性,为人处世,日常生活,今年剩下的12个月(到2020年春节前),计划依旧分三方面:`工作`、`学习`和`生活`。
+
+# 1、工作
+今年我会坚持在现在公司沉淀自己,完善自己,也多为公司出力。对于现在公司,我挺满意,产品也好,技术氛围也好,学习氛围还有同事间的氛围,我觉得都很符合我的理想工作环境。而且公司给我们提供很多很棒的资源,如**技术分享会**,**书籍借阅**,**设备支持**,**实践机会**等等,所以今年要利用好这些呢。
+今年呢,工作上有这样的安排:
+1. 跟着公司项目的脚步,学习和开发,另外在需求提交之前,一定要做好UI稿**核对**,原型逻辑**核对**等,减少BUG。
+2. 与小伙伴们分享自己的**知识总结**和**技术分享**,锻炼自己,思路和思维。
+3. 向公司老大前辈们学习,对于一些**问题的处理方式**,和**管理团队的方式**等。
+4. 努力尝试能为公司写一些工具,**简化工作流程**,或者**提高工作效率**等。
+5. 多**使用公司产品**,**参与产品运营**,发现问题,提出改善意见等。
+6. 研究一套项目基础架构(如公司现在这一套`flutter`的架构)。
+
+这一年工作上,会有更多的挑战,已经能感受到了,还是必须要相信自己,踩坑填坑踩坑填坑...,增长经验沉淀技术咯。
+
+# 2.学习
+经过这两年的程序员学习和工作生涯,也算是找到一套自己的学习方法,也是挺适合自己,相信今年的学习之路,效率和质量会高一些。
+今年的话,学习的内容会更加集中,而不会想去年那样涉及那么多方面:
+
+## 2.1学习内容
+
+**前端知识(核心)**:
+1. 前端基础:基础依然非常重要,今年需要更深入研究`JavaScript原理层面`(如引擎原理,工作原理等),还有CSS的一些不太常用的知识,另外ES规范还需复习,特别是如`Proxy`/`Set`和`Map`这类用的比较少的,但是用起来效率又特别高的这类知识。
+2. 前端框架:`Angular6`(也有可能升级到7)这个必须学会,而且能用在工作中使用,也需要针对一些知识点去研究原理(这方面可以多跟宝哥还有其他人学习),其他框架,就不需要太去研究,现在觉得学好一个框架,研究清楚原理,其他都会是一样的。
+3. 前端趋势:把握和跟进前端的发展趋势,新技术,新工具等,学着了解未来的变化。
+
+**后端知识**:
+1. 服务端开发:今年需要把`Nodejs`用于工作中了,开发一些工具,提高公司的开发效率,还是使用`Express`和`Koa`,重点朝`Koa`方向。
+2. 数据库开发:搭配服务端开发,使用`Mongodb`和它延伸出来的`Mongoose`进行实际应用。
+
+**混合开发**:
+1. APP开发:熟练mac环境开发,学习`flutter`,并且开始使用到公司的一些模块上面(看老大安排),毕竟这个技术很棒,今年自己也要自己开发一个App(demo会是现在在开始的TIMI,正式的后面再规划),`RN`和`Weex`今年不会花时间去研究,除非公司业务需要。
+
+**博客**:
+1. 个人博客:内容改版,新增栏目(待定),“作品”这个栏目内容要充实上去,同步自己在其他平台推送的文章。今年希望将【原创文章】突破到60篇(目前17篇)。
+2. Cute-JavaScrip:需要添加功能,访问量统计(博客也需要,使用百度流量统计),另外【JS基础系列】【面试题系列】必须完成,有空也需要改一改之前的文章。
+3. github:开源新项目,目前暂定2个(Flutter的两个),将学习过程中生产的一些笔记和代码,整理后也放到github上。另外【每日文章】继续维护,坚持第三年。
+4. 掘金/思否/CSDN/简书:各个平台同步自己的原创文章,首发掘金,另外掘金今年加油**粉丝突破5000**,**点赞突破1W**(虽然不太看重这些)。
+
+5. 公众号:继续维护,每日一篇,不做宣传,不去吸粉,只想积累,也许某一天,一个幸运的粉丝发现了它,希望能让他有种如获至宝的感觉。截至今天已经发了第106期
+
+**机器学习**:
+1. 深度学习:`tensorflow.js`有必要尝试下,毕竟对图像识别还是挺有兴趣的呢。
+
+**技术书籍**:
+1. CSS:《CSS世界》(喜欢)。
+2. JS:《你不知道的JavaScript》上中下,也是加强基础和原理理解。
+3. 算法:《算法图解》和《学习JavaScript数据结构与算法》(这本之前看过,但没看完),主要还是要多刷题,好的算法能提高代码质量和效率。
+书太多,要取舍,研究好几本就好。
+4. 架构:《前端技术架构》这本需要结合自己工作再去看。
+
+**非技术书籍**:
+1. 《地球上最后的夜晚》和《南方高速》,尝试着去理解作者想表达的思想和道理。
+
+## 2.2小结
+这学习计划,看上去真的多,但是在我看来,这些都是必须学的。
+所以,这当做是一整年的计划,我还需要拆分到具体的时间上面,做安排。
+坚持!
+自律!
+
+# 3.生活
+生活,我向往简单,人和事。
+新公司加班的现象比较常见,但是也是给我提供很多的学习机会。
+这一年的生活,应该是健康,且有更多幸福感的。
+
+**日常生活**:
+1. 业余生活:少熬夜(必须少熬夜),多锻炼(早操和睡前锻炼要坚持),坚持能自己做饭尽量自己做饭(早餐不吃外面),学习做饭做菜(找老妈讨教,换宿舍以后),毕竟我有做饭神器——小米电饭煲和小米电磁炉,研究下健康食谱。
+2. 住宿条件:换个宿舍,不用多么高档,安静舒适,适合自己或者和未来女票。
+3. 旅游:出去旅游一次,国内外都行(现在护照港澳通行证都有),需要旅游一次放松也开阔下视野。
+
+**家庭生活**:
+1. 未来的她:还需要好好加油,一起加油,一起做一件事的感觉会非常好。
+2. 母亲:为母亲分担更多的压力,每个月多汇钱给母亲,帮忙开个店铺给母亲做,照顾好母亲。
+3. 哥哥姐姐:常回家看看,多跟他们交流,今年找个项目再和哥哥一起做,亲兄弟团结。另外哥哥今年结婚,我要给他包个很大的红包。
+
+2019全年规划基本如此,不论多与少,需要分配好,偶尔迷茫的时候,会给我带来动力。和去年一样,加油让自己24小时过程26小时。
+
+沉淀自己,自律生活,为人和善,为公司,为家庭,也为自己。
+
+
+
+leo 2019.01.12
\ No newline at end of file
diff --git "a/Cute-Article/article/2020-\345\205\250\345\271\264\350\256\241\345\210\222.md" "b/Cute-Article/article/2020-\345\205\250\345\271\264\350\256\241\345\210\222.md"
new file mode 100644
index 00000000..e69de29b
diff --git "a/Cute-Article/article/2020\345\205\250\345\271\264\346\200\273\347\273\223.md" "b/Cute-Article/article/2020\345\205\250\345\271\264\346\200\273\347\273\223.md"
new file mode 100644
index 00000000..5cfc97f2
--- /dev/null
+++ "b/Cute-Article/article/2020\345\205\250\345\271\264\346\200\273\347\273\223.md"
@@ -0,0 +1,175 @@
+
+> 生活不可能像你想象的那么好,但也不会像你想象的那么糟。我觉得人的脆弱和坚强都超乎自己的想像,有时我脆弱得一句话就泪流满面,有时又发现自己咬着牙走了很长的路。
+
+回看 2020,我更加喜爱这句话了,每个小句子都有了不同味道。
+
+## 一、再见 2020 👋
+2020疫情复工后,我便开始进入“战斗模式”,深受公众号“全栈修仙之路”作者“[阿宝哥](https://juejin.cn/user/764915822103079)”影响🌟,开始把更多时间和精力用来修炼自身,努力成长和进阶,成为一位“靠谱的人”和一名“T 型人才”。
+
+- 靠谱的人:让自己靠谱,让别人放心;
+- T 型人才:深挖知识深度,拓展知识广度。
+
+(以下数据统计的时间,全部以 2020-12-19 日截止)
+
+🤗🤗🤗
+
+### 1. 走过的路
+
+疫情期间为了不给国家添麻烦,咱就天天家里窝着,闲来无事就给“貔貅”拍拍照啥的😎。
+
+
+
+
+复工后,骑上我的“绿豆”去了好多地方(快把厦门岛逛透透了),算是把疫情期间没去玩的地方都补上了🤠。
+
+
+
+还有这杯意义不同的咖啡,和一句“心之所在即为家”😜。
+
+
+
+当然,除了玩,这一年也做了很多重要的事情😊。
+
+2020 年,写了很多文章(包含未发布),基本都放在语雀上。数一数,将近 **150** 篇文档是在 2020 年完成的🤔。
+
+
+
+除了文章,我也画了很多图,慢慢形成自己的画图风格。
+
+
+
+当然,代码还是少不了的😎。
+
+程序员嘛,当然要看看代码提交次数,这一年提交了这些代码,感觉 [Github](https://github.com/pingan8787) + Gitlab + [Gitee](https://gitee.com/pingan8787) 三个提交记录合并一下,都快铺满了。
+
+1. Github 提交记录
+
+
+
+2. Gitlab 提交记录
+
+
+
+3. Gitee(码云) 提交记录
+
+
+
+
+另外自己的微信公众号“前端自习课”也完成“**连续推送 810+ 天**”的成绩,这一年,我也玩起短视频,视频号了,也开始自己做动画,将一些知识点通过动画和大家分享,视频可以查看[《1分钟了解 Axios 拦截器实现原理》](https://mp.weixin.qq.com/s/1s4_WXCZscB6MXsVP3lbzQ)。
+
+
+
+2020 这一年还有很多事情想和大家分享,考虑到本文主旨和内容篇幅,就不再多介绍咯~有兴趣的朋友欢迎私聊我(微信:pingan8787)💘。
+
+### 2. 感谢的人
+今年最需要感谢的,是**阿宝哥**和我们“**前端突击队**”**学习小组**的每位小伙伴啦💐~
+
+🌰最大感受是:原来前端还能这样玩!
+🌰最开心的是:团队学习更有动力,你不是一个人在战斗!
+
+在阿宝哥指导下,整理了一份自己的前端技能树,才知道自己的前端技能有几斤几两重,也才有更多动力和更清晰的方向。
+
+
+
+在我们学习小组中,采用“**专题学习 + 总结输出**”的方式一起学习,目前已经沉淀 **200+ **篇文章啦!
+小组目前 7 人(不含班主任),平均下来每人将近写了 30+ 篇!为小伙伴们点赞👍~
+
+
+
+2020 年 11 月的某一天,思考了最近学习的知识和接下来的需要做的事情,于是有了下面的这篇字数少,内容多的笔记(用手机敲的,就是有点手酸🙁):
+
+
+
+慢慢的,越来越发现,学得越多,发现自己要学的越多。🤣
+
+这里再次感谢阿宝哥,感谢“前端突击队”的小伙伴们。未来继续冲🦆!
+
+### 3. 遗憾的事
+这一年,比较遗憾的事,是自己与阿里插肩而过呀🥺~倒也让我发现更多不足。
+
+
+
+这里也非常感谢内推的小伙伴,还有几位面试官,人都挺不错。😃
+
+我们闽南人嘛,喜欢“爱拼才会赢”,所以,趁年轻多拼多创。
+
+### 4. 点赞的事
+这一年为自己坚持的几件事情点赞~
+①自己微信公众号“前端自习课”连续推送 810+ 天文章,为此我把所有文章分类做了一张词云图,如下:
+
+
+
+可以看出,我主要分享的内容包括:“JS”、“CSS”、“拓展”和“Web技术”。🔔
+
+②自己坚持的每月学习文章整理,也超过 40+ 个月了,截图如下:
+
+详细请看 github 地址:[https://github.com/pingan8787/Leo_Reading](https://github.com/pingan8787/Leo_Reading)
+
+
+
+## 二、你好 2021 👏
+2021 年即将到来,希望新的一年,每一个“下次一定”都能实现完成承诺。
+
+⚽️⚽️⚽️
+
+### 1. 加油,前端工程师
+在这前端生涯的第五年伊始,回想自己踩过的坑,走过的弯路,才慢慢领悟自己的前端生涯应该如何去走。
+
+曾经和多数人一样,时常迷失学习什么知识,看到什么火,就去学什么,到头来,效果并不好。
+
+未来自己的前端生涯,更应该站在巨人肩膀上,看向更远的地方。定个小目标呗,早日晋升技术专家。
+
+接下来的时间里,做好自己在工作中的身份,做一个优秀的前端工程师。
+
+
+
+### 2. 加油,小儿子
+作为家中最小的孩子,被催婚已经成为这一年的常事,哈哈。
+
+也许性格如此,加上独自在外工作,每天只想把事情做得更好,学更多知识,提升自己的价值。
+
+很幸运这一年遇到了女孩 C。
+
+接下来的时间里,做好自己在家里的身份,做一个让父母放心的好儿子。
+
+
+
+### 3. 加油,骑行侠
+我这人,兴趣爱好不太多,比如:骑行🚴、足球⚽️、敲代码💻。
+
+骑行让我如此着迷。
+
+换上衣服,12 月的寒风,也依然无法阻挡我的脚步。
+
+接下来的时间里,坚持自己的热爱,做一个勇往直前大胆创的闽南人。
+
+
+
+### 4. 加油,前端自习课
+
+运营公众号“前端自习课”以后,认识了许多小伙伴,看见了许多从前的自己。
+
+后来也慢慢和大家分享一些自己的经验和经历。
+
+深刻记得,我简历中最后一句话:“希望自己的成⻓之路能帮助更多人,也希望在这个世界留下自己的一些足迹”。
+
+接下来的时间里,坚持自己的初心,做一个对这个社区、这个社会有帮助的人。
+
+
+## 三、总结
+每一年的总结,都是五味杂陈,才发现这一年来,自己又进步和成长了。
+
+回顾篇头的一句话:“有时又发现自己咬着牙走了很长的路”。有时候一瞬间,一个偶然,发现自己原来咬着牙前进这么久,改变这么多。
+
+最后,再思考一句话,希望对大家能有不同感受:
+
+> 除去睡眠,人的一生有一万多天。但是人与人之间的区别就在于,你究竟是活了一万多天,还是仅仅活了一天,却重复了一万多次。
+
+希望未来的我们,会感到自己的每一天都是崭新的。
+
+像武磊一样努力,加油!
+
+最后欢迎关注我呀~
+[](http://www.pingan8787.com)[](https://www.yuque.com/wangpingan/cute-frontend)[](https://zhuanlan.zhihu.com/cute-javascript)[](https://juejin.im/user/586fc337a22b9d0058807d53/posts)[](https://segmentfault.com/blog/pingan8787)[](https://blog.csdn.net/qq_36380426)[](https://www.jianshu.com/u/2ec5d94afd60)
+
+ [掘金年度征文 | 2020 与我的技术之路 征文活动正在进行中......](https://juejin.cn/post/6901125532729999374)
\ No newline at end of file
diff --git "a/Cute-Article/article/21 \345\274\240\345\233\276\346\200\273\347\273\223\346\210\221\347\232\204 2020 \345\271\264.md" "b/Cute-Article/article/21 \345\274\240\345\233\276\346\200\273\347\273\223\346\210\221\347\232\204 2020 \345\271\264.md"
new file mode 100644
index 00000000..5cfc97f2
--- /dev/null
+++ "b/Cute-Article/article/21 \345\274\240\345\233\276\346\200\273\347\273\223\346\210\221\347\232\204 2020 \345\271\264.md"
@@ -0,0 +1,175 @@
+
+> 生活不可能像你想象的那么好,但也不会像你想象的那么糟。我觉得人的脆弱和坚强都超乎自己的想像,有时我脆弱得一句话就泪流满面,有时又发现自己咬着牙走了很长的路。
+
+回看 2020,我更加喜爱这句话了,每个小句子都有了不同味道。
+
+## 一、再见 2020 👋
+2020疫情复工后,我便开始进入“战斗模式”,深受公众号“全栈修仙之路”作者“[阿宝哥](https://juejin.cn/user/764915822103079)”影响🌟,开始把更多时间和精力用来修炼自身,努力成长和进阶,成为一位“靠谱的人”和一名“T 型人才”。
+
+- 靠谱的人:让自己靠谱,让别人放心;
+- T 型人才:深挖知识深度,拓展知识广度。
+
+(以下数据统计的时间,全部以 2020-12-19 日截止)
+
+🤗🤗🤗
+
+### 1. 走过的路
+
+疫情期间为了不给国家添麻烦,咱就天天家里窝着,闲来无事就给“貔貅”拍拍照啥的😎。
+
+
+
+
+复工后,骑上我的“绿豆”去了好多地方(快把厦门岛逛透透了),算是把疫情期间没去玩的地方都补上了🤠。
+
+
+
+还有这杯意义不同的咖啡,和一句“心之所在即为家”😜。
+
+
+
+当然,除了玩,这一年也做了很多重要的事情😊。
+
+2020 年,写了很多文章(包含未发布),基本都放在语雀上。数一数,将近 **150** 篇文档是在 2020 年完成的🤔。
+
+
+
+除了文章,我也画了很多图,慢慢形成自己的画图风格。
+
+
+
+当然,代码还是少不了的😎。
+
+程序员嘛,当然要看看代码提交次数,这一年提交了这些代码,感觉 [Github](https://github.com/pingan8787) + Gitlab + [Gitee](https://gitee.com/pingan8787) 三个提交记录合并一下,都快铺满了。
+
+1. Github 提交记录
+
+
+
+2. Gitlab 提交记录
+
+
+
+3. Gitee(码云) 提交记录
+
+
+
+
+另外自己的微信公众号“前端自习课”也完成“**连续推送 810+ 天**”的成绩,这一年,我也玩起短视频,视频号了,也开始自己做动画,将一些知识点通过动画和大家分享,视频可以查看[《1分钟了解 Axios 拦截器实现原理》](https://mp.weixin.qq.com/s/1s4_WXCZscB6MXsVP3lbzQ)。
+
+
+
+2020 这一年还有很多事情想和大家分享,考虑到本文主旨和内容篇幅,就不再多介绍咯~有兴趣的朋友欢迎私聊我(微信:pingan8787)💘。
+
+### 2. 感谢的人
+今年最需要感谢的,是**阿宝哥**和我们“**前端突击队**”**学习小组**的每位小伙伴啦💐~
+
+🌰最大感受是:原来前端还能这样玩!
+🌰最开心的是:团队学习更有动力,你不是一个人在战斗!
+
+在阿宝哥指导下,整理了一份自己的前端技能树,才知道自己的前端技能有几斤几两重,也才有更多动力和更清晰的方向。
+
+
+
+在我们学习小组中,采用“**专题学习 + 总结输出**”的方式一起学习,目前已经沉淀 **200+ **篇文章啦!
+小组目前 7 人(不含班主任),平均下来每人将近写了 30+ 篇!为小伙伴们点赞👍~
+
+
+
+2020 年 11 月的某一天,思考了最近学习的知识和接下来的需要做的事情,于是有了下面的这篇字数少,内容多的笔记(用手机敲的,就是有点手酸🙁):
+
+
+
+慢慢的,越来越发现,学得越多,发现自己要学的越多。🤣
+
+这里再次感谢阿宝哥,感谢“前端突击队”的小伙伴们。未来继续冲🦆!
+
+### 3. 遗憾的事
+这一年,比较遗憾的事,是自己与阿里插肩而过呀🥺~倒也让我发现更多不足。
+
+
+
+这里也非常感谢内推的小伙伴,还有几位面试官,人都挺不错。😃
+
+我们闽南人嘛,喜欢“爱拼才会赢”,所以,趁年轻多拼多创。
+
+### 4. 点赞的事
+这一年为自己坚持的几件事情点赞~
+①自己微信公众号“前端自习课”连续推送 810+ 天文章,为此我把所有文章分类做了一张词云图,如下:
+
+
+
+可以看出,我主要分享的内容包括:“JS”、“CSS”、“拓展”和“Web技术”。🔔
+
+②自己坚持的每月学习文章整理,也超过 40+ 个月了,截图如下:
+
+详细请看 github 地址:[https://github.com/pingan8787/Leo_Reading](https://github.com/pingan8787/Leo_Reading)
+
+
+
+## 二、你好 2021 👏
+2021 年即将到来,希望新的一年,每一个“下次一定”都能实现完成承诺。
+
+⚽️⚽️⚽️
+
+### 1. 加油,前端工程师
+在这前端生涯的第五年伊始,回想自己踩过的坑,走过的弯路,才慢慢领悟自己的前端生涯应该如何去走。
+
+曾经和多数人一样,时常迷失学习什么知识,看到什么火,就去学什么,到头来,效果并不好。
+
+未来自己的前端生涯,更应该站在巨人肩膀上,看向更远的地方。定个小目标呗,早日晋升技术专家。
+
+接下来的时间里,做好自己在工作中的身份,做一个优秀的前端工程师。
+
+
+
+### 2. 加油,小儿子
+作为家中最小的孩子,被催婚已经成为这一年的常事,哈哈。
+
+也许性格如此,加上独自在外工作,每天只想把事情做得更好,学更多知识,提升自己的价值。
+
+很幸运这一年遇到了女孩 C。
+
+接下来的时间里,做好自己在家里的身份,做一个让父母放心的好儿子。
+
+
+
+### 3. 加油,骑行侠
+我这人,兴趣爱好不太多,比如:骑行🚴、足球⚽️、敲代码💻。
+
+骑行让我如此着迷。
+
+换上衣服,12 月的寒风,也依然无法阻挡我的脚步。
+
+接下来的时间里,坚持自己的热爱,做一个勇往直前大胆创的闽南人。
+
+
+
+### 4. 加油,前端自习课
+
+运营公众号“前端自习课”以后,认识了许多小伙伴,看见了许多从前的自己。
+
+后来也慢慢和大家分享一些自己的经验和经历。
+
+深刻记得,我简历中最后一句话:“希望自己的成⻓之路能帮助更多人,也希望在这个世界留下自己的一些足迹”。
+
+接下来的时间里,坚持自己的初心,做一个对这个社区、这个社会有帮助的人。
+
+
+## 三、总结
+每一年的总结,都是五味杂陈,才发现这一年来,自己又进步和成长了。
+
+回顾篇头的一句话:“有时又发现自己咬着牙走了很长的路”。有时候一瞬间,一个偶然,发现自己原来咬着牙前进这么久,改变这么多。
+
+最后,再思考一句话,希望对大家能有不同感受:
+
+> 除去睡眠,人的一生有一万多天。但是人与人之间的区别就在于,你究竟是活了一万多天,还是仅仅活了一天,却重复了一万多次。
+
+希望未来的我们,会感到自己的每一天都是崭新的。
+
+像武磊一样努力,加油!
+
+最后欢迎关注我呀~
+[](http://www.pingan8787.com)[](https://www.yuque.com/wangpingan/cute-frontend)[](https://zhuanlan.zhihu.com/cute-javascript)[](https://juejin.im/user/586fc337a22b9d0058807d53/posts)[](https://segmentfault.com/blog/pingan8787)[](https://blog.csdn.net/qq_36380426)[](https://www.jianshu.com/u/2ec5d94afd60)
+
+ [掘金年度征文 | 2020 与我的技术之路 征文活动正在进行中......](https://juejin.cn/post/6901125532729999374)
\ No newline at end of file
diff --git "a/21-JavaScript\345\274\202\346\255\245\346\234\272\345\210\266\350\257\246\350\247\243.md" "b/Cute-Article/article/21-JavaScript\345\274\202\346\255\245\346\234\272\345\210\266\350\257\246\350\247\243.md"
similarity index 100%
rename from "21-JavaScript\345\274\202\346\255\245\346\234\272\345\210\266\350\257\246\350\247\243.md"
rename to "Cute-Article/article/21-JavaScript\345\274\202\346\255\245\346\234\272\345\210\266\350\257\246\350\247\243.md"
diff --git "a/22-JavaScript\344\270\255\346\234\211\350\266\243\347\232\204\345\214\272\345\210\206\345\220\214\346\255\245\345\222\214\345\274\202\346\255\245Ajax.md" "b/Cute-Article/article/22-JavaScript\344\270\255\346\234\211\350\266\243\347\232\204\345\214\272\345\210\206\345\220\214\346\255\245\345\222\214\345\274\202\346\255\245Ajax.md"
similarity index 100%
rename from "22-JavaScript\344\270\255\346\234\211\350\266\243\347\232\204\345\214\272\345\210\206\345\220\214\346\255\245\345\222\214\345\274\202\346\255\245Ajax.md"
rename to "Cute-Article/article/22-JavaScript\344\270\255\346\234\211\350\266\243\347\232\204\345\214\272\345\210\206\345\220\214\346\255\245\345\222\214\345\274\202\346\255\245Ajax.md"
diff --git "a/23-JavaScript\345\205\253\345\274\240\346\200\235\347\273\264\345\257\274\345\233\276.md" "b/Cute-Article/article/23-JavaScript\345\205\253\345\274\240\346\200\235\347\273\264\345\257\274\345\233\276.md"
similarity index 100%
rename from "23-JavaScript\345\205\253\345\274\240\346\200\235\347\273\264\345\257\274\345\233\276.md"
rename to "Cute-Article/article/23-JavaScript\345\205\253\345\274\240\346\200\235\347\273\264\345\257\274\345\233\276.md"
diff --git "a/24-Vue\346\212\230\350\205\276\350\256\260-\347\273\231Axios\345\201\232\344\270\252\346\214\272\351\235\240\350\260\261\347\232\204\345\260\201\350\243\205\357\274\210\346\212\245\351\224\231,\351\211\264\346\235\203,\350\267\263\350\275\254,\346\213\246\346\210\252,\346\217\220\347\244\272\357\274\211.md" "b/Cute-Article/article/24-Vue\346\212\230\350\205\276\350\256\260-\347\273\231Axios\345\201\232\344\270\252\346\214\272\351\235\240\350\260\261\347\232\204\345\260\201\350\243\205\357\274\210\346\212\245\351\224\231,\351\211\264\346\235\203,\350\267\263\350\275\254,\346\213\246\346\210\252,\346\217\220\347\244\272\357\274\211.md"
similarity index 100%
rename from "24-Vue\346\212\230\350\205\276\350\256\260-\347\273\231Axios\345\201\232\344\270\252\346\214\272\351\235\240\350\260\261\347\232\204\345\260\201\350\243\205\357\274\210\346\212\245\351\224\231,\351\211\264\346\235\203,\350\267\263\350\275\254,\346\213\246\346\210\252,\346\217\220\347\244\272\357\274\211.md"
rename to "Cute-Article/article/24-Vue\346\212\230\350\205\276\350\256\260-\347\273\231Axios\345\201\232\344\270\252\346\214\272\351\235\240\350\260\261\347\232\204\345\260\201\350\243\205\357\274\210\346\212\245\351\224\231,\351\211\264\346\235\203,\350\267\263\350\275\254,\346\213\246\346\210\252,\346\217\220\347\244\272\357\274\211.md"
diff --git "a/25-Webpack\345\205\245\351\227\250\346\225\231\347\250\213\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md" "b/Cute-Article/article/25-Webpack\345\205\245\351\227\250\346\225\231\347\250\213\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
similarity index 100%
rename from "25-Webpack\345\205\245\351\227\250\346\225\231\347\250\213\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
rename to "Cute-Article/article/25-Webpack\345\205\245\351\227\250\346\225\231\347\250\213\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
diff --git "a/26-Webpack\345\270\270\347\224\250\351\205\215\347\275\256\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md" "b/Cute-Article/article/26-Webpack\345\270\270\347\224\250\351\205\215\347\275\256\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
similarity index 100%
rename from "26-Webpack\345\270\270\347\224\250\351\205\215\347\275\256\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
rename to "Cute-Article/article/26-Webpack\345\270\270\347\224\250\351\205\215\347\275\256\346\225\264\347\220\206\357\274\210\346\225\264\347\220\206\344\270\255\357\274\211.md"
diff --git "a/27-Markdowm\350\257\255\346\263\225\346\225\264\347\220\206.md" "b/Cute-Article/article/27-Markdowm\350\257\255\346\263\225\346\225\264\347\220\206.md"
similarity index 100%
rename from "27-Markdowm\350\257\255\346\263\225\346\225\264\347\220\206.md"
rename to "Cute-Article/article/27-Markdowm\350\257\255\346\263\225\346\225\264\347\220\206.md"
diff --git "a/28-JavaScript\344\270\255\347\232\204void\350\277\220\347\256\227\347\254\246.md" "b/Cute-Article/article/28-JavaScript\344\270\255\347\232\204void\350\277\220\347\256\227\347\254\246.md"
similarity index 100%
rename from "28-JavaScript\344\270\255\347\232\204void\350\277\220\347\256\227\347\254\246.md"
rename to "Cute-Article/article/28-JavaScript\344\270\255\347\232\204void\350\277\220\347\256\227\347\254\246.md"
diff --git "a/29-\345\205\263\344\272\216\351\232\217\346\234\272\346\225\260\347\232\204\344\270\200\344\272\233\346\200\273\347\273\223.md" "b/Cute-Article/article/29-\345\205\263\344\272\216\351\232\217\346\234\272\346\225\260\347\232\204\344\270\200\344\272\233\346\200\273\347\273\223.md"
similarity index 100%
rename from "29-\345\205\263\344\272\216\351\232\217\346\234\272\346\225\260\347\232\204\344\270\200\344\272\233\346\200\273\347\273\223.md"
rename to "Cute-Article/article/29-\345\205\263\344\272\216\351\232\217\346\234\272\346\225\260\347\232\204\344\270\200\344\272\233\346\200\273\347\273\223.md"
diff --git "a/3-Promise\347\256\200\345\215\225\347\224\250\346\263\225.md" "b/Cute-Article/article/3-Promise\347\256\200\345\215\225\347\224\250\346\263\225.md"
similarity index 100%
rename from "3-Promise\347\256\200\345\215\225\347\224\250\346\263\225.md"
rename to "Cute-Article/article/3-Promise\347\256\200\345\215\225\347\224\250\346\263\225.md"
diff --git "a/30-\344\270\200\346\254\241\350\256\260\344\275\217js\347\232\2046\344\270\252\346\255\243\345\210\231\346\226\271\346\263\225.md" "b/Cute-Article/article/30-\344\270\200\346\254\241\350\256\260\344\275\217js\347\232\2046\344\270\252\346\255\243\345\210\231\346\226\271\346\263\225.md"
similarity index 100%
rename from "30-\344\270\200\346\254\241\350\256\260\344\275\217js\347\232\2046\344\270\252\346\255\243\345\210\231\346\226\271\346\263\225.md"
rename to "Cute-Article/article/30-\344\270\200\346\254\241\350\256\260\344\275\217js\347\232\2046\344\270\252\346\255\243\345\210\231\346\226\271\346\263\225.md"
diff --git "a/31-ES6\350\277\231\344\272\233\345\260\261\345\244\237\344\272\206.md" "b/Cute-Article/article/31-ES6\350\277\231\344\272\233\345\260\261\345\244\237\344\272\206.md"
similarity index 100%
rename from "31-ES6\350\277\231\344\272\233\345\260\261\345\244\237\344\272\206.md"
rename to "Cute-Article/article/31-ES6\350\277\231\344\272\233\345\260\261\345\244\237\344\272\206.md"
diff --git "a/32-\350\201\212\344\270\200\350\201\212JavaScript\347\232\204IIFE.md" "b/Cute-Article/article/32-\350\201\212\344\270\200\350\201\212JavaScript\347\232\204IIFE.md"
similarity index 100%
rename from "32-\350\201\212\344\270\200\350\201\212JavaScript\347\232\204IIFE.md"
rename to "Cute-Article/article/32-\350\201\212\344\270\200\350\201\212JavaScript\347\232\204IIFE.md"
diff --git "a/33-javascript\347\232\204\347\272\257\345\207\275\346\225\260.md" "b/Cute-Article/article/33-javascript\347\232\204\347\272\257\345\207\275\346\225\260.md"
similarity index 100%
rename from "33-javascript\347\232\204\347\272\257\345\207\275\346\225\260.md"
rename to "Cute-Article/article/33-javascript\347\232\204\347\272\257\345\207\275\346\225\260.md"
diff --git "a/34-\346\210\221\347\234\274\344\270\255\347\232\204async&await.md" "b/Cute-Article/article/34-\346\210\221\347\234\274\344\270\255\347\232\204async&await.md"
similarity index 100%
rename from "34-\346\210\221\347\234\274\344\270\255\347\232\204async&await.md"
rename to "Cute-Article/article/34-\346\210\221\347\234\274\344\270\255\347\232\204async&await.md"
diff --git "a/35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md" "b/Cute-Article/article/35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md"
similarity index 97%
rename from "35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md"
rename to "Cute-Article/article/35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md"
index 7908ac22..73ae0428 100644
--- "a/35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md"
+++ "b/Cute-Article/article/35-ES6\344\270\255\347\232\204\346\250\241\345\235\227\345\257\274\345\205\245\345\257\274\345\207\272\346\225\264\347\220\206.md"
@@ -8,7 +8,7 @@
****
## 1、导出export命令
-如果需要外部获取文件内部变量,需要用`expor`t将变量输出,有三种方式:
+如果需要外部获取文件内部变量,需要用`export`将变量输出,有三种方式:
```js
//*方式一 单独输出
//main.js
@@ -102,4 +102,4 @@ function foo() {
export default foo;
```
-> 参考阮一峰[ES6 Module语法](http://es6.ruanyifeng.com/#docs/module)
\ No newline at end of file
+> 参考阮一峰[ES6 Module语法](http://es6.ruanyifeng.com/#docs/module)
diff --git "a/36-\345\245\275\345\245\275\345\255\246\344\271\240toLocaleString\346\226\271\346\263\225.md" "b/Cute-Article/article/36-\345\245\275\345\245\275\345\255\246\344\271\240toLocaleString\346\226\271\346\263\225.md"
similarity index 100%
rename from "36-\345\245\275\345\245\275\345\255\246\344\271\240toLocaleString\346\226\271\346\263\225.md"
rename to "Cute-Article/article/36-\345\245\275\345\245\275\345\255\246\344\271\240toLocaleString\346\226\271\346\263\225.md"
diff --git "a/37-JavaScript\344\272\213\344\273\266\345\247\224\346\211\230\350\257\246\350\247\243.md" "b/Cute-Article/article/37-JavaScript\344\272\213\344\273\266\345\247\224\346\211\230\350\257\246\350\247\243.md"
similarity index 100%
rename from "37-JavaScript\344\272\213\344\273\266\345\247\224\346\211\230\350\257\246\350\247\243.md"
rename to "Cute-Article/article/37-JavaScript\344\272\213\344\273\266\345\247\224\346\211\230\350\257\246\350\247\243.md"
diff --git "a/Cute-Article/article/38-JavaScript\344\270\255\345\270\270\350\247\201\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/Cute-Article/article/38-JavaScript\344\270\255\345\270\270\350\247\201\350\256\276\350\256\241\346\250\241\345\274\217.md"
new file mode 100644
index 00000000..b1578d37
--- /dev/null
+++ "b/Cute-Article/article/38-JavaScript\344\270\255\345\270\270\350\247\201\350\256\276\350\256\241\346\250\241\345\274\217.md"
@@ -0,0 +1,1247 @@
+开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。
+
+### JavaScript 中常见设计模式
+* 单例模式
+* 策略模式
+* 代理模式
+* 迭代器模式
+* 发布-订阅模式
+* 命令模式
+* 组合模式
+* 模板方法模式
+* 享元模式
+* 职责链模式
+* 中介者模式
+* 装饰者模式
+* 状态模式
+* 适配者模式
+
+### 各设计模式关键词
+看完了上述设计模式后,把它们的关键词特点罗列出来,以后提到某种设计模式,进而联想相应的关键词和例子,从而心中有数。
+
+|设计模式|特点|案例|
+|-|-|-|
+|单例模式|一个类只能构造出唯一实例|创建菜单对象|
+|策略模式|根据不同参数可以命中不同的策略|动画库里的算法函数|
+|代理模式|代理对象和本体对象具有一致的接口|图片预加载|
+|迭代器模式|能获取聚合对象的顺序和元素|each([1, 2, 3], cb)|
+|发布-订阅模式|PubSub|瀑布流库|
+|命令模式|不同对象间约定好相应的接口|按钮和命令的分离|
+|组合模式|组合模式在对象间形成一致对待的树形结构|扫描文件夹|
+|模板方法模式|父类中定好执行顺序|咖啡和茶|
+|享元模式|减少创建实例的个数|男女模具试装|
+|职责链模式|通过请求第一个条件,会持续执行后续的条件,直到返回结果为止|if else 优化|
+|中介者模式|对象和对象之间借助第三方中介者进行通信|测试结束告知结果|
+|装饰者模式|动态地给函数赋能|天冷了穿衣服,热了脱衣服|
+|状态模式|每个状态建立一个类,状态改变会产生不同行为|电灯换挡|
+|适配者模式|一种数据结构改成另一种数据结构|枚举值接口变更|
+
+
+## 1.单例模式
+### 两个条件
+* 确保只有一个实例
+* 可以全局访问
+
+### 适用
+适用于弹框的实现,全局缓存
+### 实现单例模式
+```js
+const singleton = function(name) {
+ this.name = name
+ this.instance = null
+}
+
+singleton.prototype.getName = function() {
+ console.log(this.name)
+}
+
+singleton.getInstance = function(name) {
+ if (!this.instance) { // 关键语句
+ this.instance = new singleton(name)
+ }
+ return this.instance
+}
+
+// test
+const a = singleton.getInstance('a') // 通过 getInstance 来获取实例
+const b = singleton.getInstance('b')
+console.log(a === b)
+```
+### JavaScript 中的单例模式
+因为 JavaScript 是无类的语言,而且 JS 中的全局对象符合单例模式两个条件。很多时候我们把全局对象当成单例模式来使用,
+```js
+var obj = {}
+```
+### 弹框层的实践
+实现弹框的一种做法是先创建好弹框,然后使之隐藏,这样子的话会浪费部分不必要的 DOM 开销,我们可以在需要弹框的时候再进行创建,同时结合单例模式实现只有一个实例,从而节省部分 DOM 开销。下列为登入框部分代码:
+```js
+const createLoginLayer = function() {
+ const div = document.createElement('div')
+ div.innerHTML = '登入浮框'
+ div.style.display = 'none'
+ document.appendChild(div)
+ return div
+}
+```
+使单例模式和创建弹框代码解耦
+```js
+const getSingle = function(fn) {
+ const result
+ return function() {
+ return result || result = fn.apply(this, arguments)
+ }
+}
+```
+```js
+const createSingleLoginLayer = getSingle(createLoginLayer)
+
+document.getElementById('loginBtn').onclick = function() {
+ createSingleLoginLayer()
+}
+```
+
+***
+
+## 2.策略模式
+> 定义:根据不同参数可以命中不同的策略
+
+### JavaScript 中的策略模式
+观察如下获取年终奖的 demo,根据不同的参数(level)获得不同策略方法(规则),这是策略模式在 JS 比较经典的运用之一。
+```js
+const strategy = {
+ 'S': function(salary) {
+ return salary * 4
+ },
+ 'A': function(salary) {
+ return salary * 3
+ },
+ 'B': function(salary) {
+ return salary * 2
+ }
+}
+
+const calculateBonus = function(level, salary) {
+ return strategy[level](salary)
+}
+
+calculateBonus('A', 10000) // 30000
+```
+在函数是一等公民的 JS 中,策略模式的使用常常隐藏在高阶函数中,稍微变换下上述 demo 的形式如下,可以发现我们平时已经在使用它了,恭喜我们又掌握了一种设计模式。
+```js
+const S = function(salary) {
+ return salary * 4
+}
+
+const A = function(salary) {
+ return salary * 3
+}
+
+const B = function(salary) {
+ return salary * 2
+}
+
+const calculateBonus = function(func, salary) {
+ return func(salary)
+}
+
+calculateBonus(A, 10000) // 30000
+```
+### 优点
+* 能减少大量的 if 语句
+* 复用性好
+
+***
+
+## 3.代理模式
+情景:小明追女生 A
+* 非代理模式:小明 =花=> 女生A
+* 代理模式:小明 =花=> 让女生A的好友B帮忙 =花=> 女生A
+
+### 代理模式的特点
+* 代理对象和本体对象具有一致的接口,对使用者友好
+
+代理模式的种类有很多,在 JS 中最常用的为虚拟代理和缓存代理。
+#### 虚拟代理实现图片预加载
+下面这段代码运用代理模式来实现图片预加载,可以看到通过代理模式巧妙地将创建图片与预加载逻辑分离,并且在未来如果不需要预加载,只要改成请求本体代替请求代理对象就行。
+```js
+const myImage = (function() {
+ const imgNode = document.createElement('img')
+ document.body.appendChild(imgNode)
+ return {
+ setSrc: function(src) {
+ imgNode.src = src
+ }
+ }
+})()
+
+const proxyImage = (function() {
+ const img = new Image()
+ img.onload = function() { // http 图片加载完毕后才会执行
+ myImage.setSrc(this.src)
+ }
+ return {
+ setSrc: function(src) {
+ myImage.setSrc('loading.jpg') // 本地 loading 图片
+ img.src = src
+ }
+ }
+})()
+
+proxyImage.setSrc('http://loaded.jpg')
+```
+
+缓存代理实现乘积计算
+```js
+const mult = function() {
+ let a = 1
+ for (let i = 0, l; l = arguments[i++];) {
+ a = a * l
+ }
+ return a
+}
+
+const proxyMult = (function() {
+ const cache = {}
+ return function() {
+ const tag = Array.prototype.join.call(arguments, ',')
+ if (cache[tag]) {
+ return cache[tag]
+ }
+ cache[tag] = mult.apply(this, arguments)
+ return cache[tag]
+ }
+})()
+
+proxyMult(1, 2, 3, 4) // 24
+```
+
+### 小 tip
+在开发时候不要先去猜测是否需要使用代理模式,如果发现直接使用某个对象不方便时,再来优化不迟。
+
+***
+
+## 4.迭代器模式
+> 定义:能访问到聚合对象的顺序与元素
+### 实现一个内部迭代器
+```js
+function each(arr, fn) {
+ for (let i = 0; i < arr.length; i++) {
+ fn(i, arr[i])
+ }
+}
+
+each([1, 2, 3], function(i, n) {
+ console.log(i) // 0 1 2
+ console.log(n) // 1 2 3
+})
+```
+可以看出内部迭代器在调用的时候非常简单,使用者不用关心迭代器内部实现的细节,但这也是内部迭代器的缺点。比如要比较两数组是否相等,只能在其回调函数中作文章了,代码如下:
+```js
+const compare = function(arr1, arr2) {
+ each(arr1, function(i, n) {
+ if (arr2[i] !== n) {
+ console.log('两数组不等')
+ return
+ }
+ })
+ console.log('两数组相等')
+}
+
+const arr1 = [1, 2, 3]
+const arr2 = [1, 2, 3]
+compare(arr1, arr2) // 两数组相等
+```
+### 实现一个外部迭代器
+
+相较于内部迭代器,外部迭代器将遍历的权利转移到外部,因此在调用的时候拥有了更多的自由性,不过缺点是调用方式较复杂。
+```js
+const iterator = function(arr) {
+ let current = 0
+ const next = function() {
+ current = current + 1
+ }
+ const done = function() {
+ return current >= arr.length
+ }
+ const value = function() {
+ return arr[current]
+ }
+ return {
+ next,
+ done,
+ value,
+ }
+}
+
+const arr1 = [1, 2 ,3]
+const arr2 = [1, 2, 3]
+const iterator1 = iterator(arr1)
+const iterator2 = iterator(arr2)
+
+const compare = function(iterator1, iterator2) {
+ while (!iterator1.done() && !iterator2.done()) {
+ if (iterator1.value() !== iterator2.value()) {
+ console.log('两数组不等')
+ return
+ }
+ iterator1.next() // 外部迭代器将遍历的权利转移到外部
+ iterator2.next()
+ }
+ console.log('两数组相等')
+}
+
+compare(iterator1, iterator2)
+```
+
+***
+
+## 5.发布订阅模式
+事件发布/订阅模式 (PubSub) 在异步编程中帮助我们完成更松的解耦,甚至在 MVC、MVVC 的架构中以及设计模式中也少不了发布-订阅模式的参与。
+优点:在异步编程中实现更深的解耦
+缺点:如果过多的使用发布订阅模式,会增加维护的难度
+
+### 实现一个发布订阅模式
+```js
+var Event = function() {
+ this.obj = {}
+}
+
+Event.prototype.on = function(eventType, fn) {
+ if (!this.obj[eventType]) {
+ this.obj[eventType] = []
+ }
+ this.obj[eventType].push(fn)
+}
+
+Event.prototype.emit = function() {
+ var eventType = Array.prototype.shift.call(arguments)
+ var arr = this.obj[eventType]
+ for (let i = 0; i < arr.length; i++) {
+ arr[i].apply(arr[i], arguments)
+ }
+}
+
+var ev = new Event()
+
+ev.on('click', function(a) { // 订阅函数
+ console.log(a) // 1
+})
+
+ev.emit('click', 1) // 发布函数
+```
+
+### 订阅函数逻辑一定要优先于发布函数吗
+考虑以下场景:
+```js
+$.ajax('', () => {
+ // 异步订阅函数逻辑
+})
+
+// 在其他地方执行发布函数,此时并不能保证执行发布函数的时候,订阅函数已经执行
+```
+我们需要实现这样的逻辑:
+```js
+var ev = new Event()
+ev.emit('click', 1)
+
+ev.on('click', function(a) {
+ console.log(a) // 1
+})
+```
+目标明确后,来着手实现它:
+```js
+var Event = function() {
+ this.obj = {}
+ this.cacheList = []
+}
+
+Event.prototype.on = function(eventType, fn) {
+ if (!this.obj[eventType]) {
+ this.obj[eventType] = []
+ }
+ this.obj[eventType].push(fn)
+
+ for (let i = 0; i < this.cacheList.length; i++) {
+ this.cacheList[i]()
+ }
+}
+
+Event.prototype.emit = function() {
+ const arg = arguments
+ const that = this
+ function cache() {
+ var eventType = Array.prototype.shift.call(arg)
+ var arr = that.obj[eventType]
+ for (let i = 0; i < arr.length; i++) {
+ arr[i].apply(arr[i], arg)
+ }
+ }
+ this.cacheList.push(cache)
+}
+```
+以上代码实现思路就是把原本在 `emit` 里触发的函数存到 `cacheList`,再转交到 `on` 中触发。从而实现了发布函数先于订阅函数执行。
+
+***
+
+## 6.命令模式
+命令模式与策略模式有些类似,在 JavaScript 中它们都是隐式的。
+重要性:较低
+### JavaScript 中的命令模式
+命令模式在 JavaScript 中也比较简单,下面代码中对按钮和命令进行了抽离,因此可以复杂项目中可以使用命令模式将界面的代码和功能的代码交付给不同的人去写。
+```js
+const setCommand = function(button, command) {
+ button.onClick = function() {
+ command.excute()
+ }
+}
+
+// -------------------- 上面的界面逻辑由A完成,下面的由B完成
+
+const menu = {
+ updateMenu: function() {
+ console.log('更新菜单')
+ },
+}
+
+const UpdateCommand = function(receive) {
+ return {
+ excute: receive.updateMenu,
+ }
+}
+
+const updateCommand = UpdateCommand(menu) // 创建命令
+
+const button1 = document.getElementById('button1')
+setCommand(button1, updateCommand)
+```
+
+***
+
+## 7.组合模式
+* 组合模式在对象间形成树形结构;
+* 组合模式中基本对象和组合对象被一致对待;
+* 无须关心对象有多少层,调用时只需在根部进行调用;
+
+### demo1 —— 宏命令
+
+想象我们现在手上有个万能遥控器,当我们回家,按一下开关,下列事情将被执行:
+1. 煮咖啡
+2. 打开电视、打开音响
+3. 打开空调、打开电脑
+
+我们把任务划分为 3 类,效果图如下:
+
+接着看看结合了命令模式和组合模式的具体实现:
+```js
+const MacroCommand = function() {
+ return {
+ lists: [],
+ add: function(task) {
+ this.lists.push(task)
+ },
+ excute: function() { // ①:组合对象调用这里的 excute,
+ for (let i = 0; i < this.lists.length; i++) {
+ this.lists[i].excute()
+ }
+ },
+ }
+}
+
+const command1 = MacroCommand() // 基本对象
+
+command1.add({
+ excute: () => console.log('煮咖啡') // ②:基本对象调用这里的 excute,
+})
+
+const command2 = MacroCommand() // 组合对象
+
+command2.add({
+ excute: () => console.log('打开电视')
+})
+
+command2.add({
+ excute: () => console.log('打开音响')
+})
+
+const command3 = MacroCommand()
+
+command3.add({
+ excute: () => console.log('打开空调')
+})
+
+command3.add({
+ excute: () => console.log('打开电脑')
+})
+
+const macroCommand = MacroCommand()
+macroCommand.add(command1)
+macroCommand.add(command2)
+macroCommand.add(command3)
+
+macroCommand.excute()
+
+// 煮咖啡
+// 打开电视
+// 打开音响
+// 打开空调
+// 打开电脑
+```
+
+可以看出在组合模式中基本对象和组合对象被一致对待,所以要保证基本对象(叶对象)和组合对象具有一致方法。
+
+### demo2 —— 扫描文件夹
+扫描文件夹时,文件夹下面可以为另一个文件夹也可以为文件,我们希望统一对待这些文件夹和文件,这种情形适合使用组合模式。
+```js
+const Folder = function(folder) {
+ this.folder = folder
+ this.lists = []
+}
+
+Folder.prototype.add = function(resource) {
+ this.lists.push(resource)
+}
+
+Folder.prototype.scan = function() {
+ console.log('开始扫描文件夹:', this.folder)
+ for (let i = 0, folder; folder = this.lists[i++];) {
+ folder.scan()
+ }
+}
+
+const File = function(file) {
+ this.file = file
+}
+
+File.prototype.add = function() {
+ throw Error('文件下不能添加其它文件夹或文件')
+}
+
+File.prototype.scan = function() {
+ console.log('开始扫描文件:', this.file)
+}
+
+const folder = new Folder('根文件夹')
+const folder1 = new Folder('JS')
+const folder2 = new Folder('life')
+
+const file1 = new File('深入React技术栈.pdf')
+const file2 = new File('JavaScript权威指南.pdf')
+const file3 = new File('小王子.pdf')
+
+folder1.add(file1)
+folder1.add(file2)
+
+folder2.add(file3)
+
+folder.add(folder1)
+folder.add(folder2)
+
+folder.scan()
+
+// 开始扫描文件夹: 根文件夹
+// 开始扫描文件夹: JS
+// 开始扫描文件: 深入React技术栈.pdf
+// 开始扫描文件: JavaScript权威指南.pdf
+// 开始扫描文件夹: life
+// 开始扫描文件: 小王子.pdf
+```
+
+***
+
+## 8.模板方法模式
+> 定义:在继承的基础上,在父类中定义好执行的算法。
+>
+### 泡茶和泡咖啡
+来对比下泡茶和泡咖啡过程中的异同
+
+|步骤|泡茶|泡咖啡|
+|-|-|-|
+|1|烧开水|烧开水|
+|2|浸泡茶叶|冲泡咖啡|
+|3|倒入杯子|倒入杯子|
+|4|加柠檬|加糖|
+
+可以清晰地看出仅仅在步骤 2 和 4 上有细微的差别,下面着手实现:
+```js
+const Drinks = function() {}
+
+Drinks.prototype.firstStep = function() {
+ console.log('烧开水')
+}
+
+Drinks.prototype.secondStep = function() {}
+
+Drinks.prototype.thirdStep = function() {
+ console.log('倒入杯子')
+}
+
+Drinks.prototype.fourthStep = function() {}
+
+Drinks.prototype.init = function() { // 模板方法模式核心:在父类上定义好执行算法
+ this.firstStep()
+ this.secondStep()
+ this.thirdStep()
+ this.fourthStep()
+}
+
+const Tea = function() {}
+
+Tea.prototype = new Drinks
+
+Tea.prototype.secondStep = function() {
+ console.log('浸泡茶叶')
+}
+
+Tea.prototype.fourthStep = function() {
+ console.log('加柠檬')
+}
+
+const Coffee = function() {}
+
+Coffee.prototype = new Drinks
+
+Coffee.prototype.secondStep = function() {
+ console.log('冲泡咖啡')
+}
+
+Coffee.prototype.fourthStep = function() {
+ console.log('加糖')
+}
+
+const tea = new Tea()
+tea.init()
+
+// 烧开水
+// 浸泡茶叶
+// 倒入杯子
+// 加柠檬
+
+const coffee = new Coffee()
+coffee.init()
+
+// 烧开水
+// 冲泡咖啡
+// 倒入杯子
+// 加糖
+```
+
+### 钩子
+假如客人不想加佐料(糖、柠檬)怎么办,这时可以引人钩子来实现之,实现逻辑如下:
+```js
+// ...
+
+Drinks.prototype.ifNeedFlavour = function() { // 加上钩子
+ return true
+}
+
+Drinks.prototype.init = function() { // 模板方法模式核心:在父类上定义好执行算法
+ this.firstStep()
+ this.secondStep()
+ this.thirdStep()
+ if (this.ifNeedFlavour()) { // 默认是 true,也就是要加调料
+ this.fourthStep()
+ }
+}
+
+// ...
+const Coffee = function() {}
+
+Coffee.prototype = new Drinks()
+// ...
+
+Coffee.prototype.ifNeedFlavour = function() {
+ return window.confirm('是否需要佐料吗?') // 弹框选择是否佐料
+}
+```
+
+***
+
+## 9.享元模式
+享元模式是一种优化程序性能的模式,本质为减少对象创建的个数。
+
+以下情况可以使用享元模式:
+* 有大量相似的对象,占用了大量内存
+* 对象中大部分状态可以抽离为外部状态
+
+### demo
+某商家有 50 种男款内衣和 50 种款女款内衣,要展示它们
+
+方案一:造 50 个塑料男模和 50 个塑料女模,让他们穿上展示,代码如下:
+```js
+const Model = function(gender, underwear) {
+ this.gender = gender
+ this.underwear = underwear
+}
+
+Model.prototype.takephoto = function() {
+ console.log(`${this.gender}穿着${this.underwear}`)
+}
+
+for (let i = 1; i < 51; i++) {
+ const maleModel = new Model('male', `第${i}款衣服`)
+ maleModel.takephoto()
+}
+
+for (let i = 1; i < 51; i++) {
+ const female = new Model('female', `第${i}款衣服`)
+ female.takephoto()
+}
+```
+
+方案二:造 1 个塑料男模特 1 个塑料女模特,分别试穿 50 款内衣
+```js
+const Model = function(gender) {
+ this.gender = gender
+}
+
+Model.prototype.takephoto = function() {
+ console.log(`${this.sex}穿着${this.underwear}`)
+}
+
+const maleModel = new Model('male')
+const femaleModel = new Model('female')
+
+for (let i = 1; i < 51; i++) {
+ maleModel.underwear = `第${i}款衣服`
+ maleModel.takephoto()
+}
+
+for (let i = 1; i < 51; i++) {
+ femaleModel.underwear = `第${i}款衣服`
+ femaleModel.takephoto()
+}
+```
+对比发现:方案一创建了 100 个对象,方案二只创建了 2 个对象,在该 demo 中,gender(性别) 是内部对象,underwear(穿着) 是外部对象。
+
+当然在方案二的 demo 中,还可以进一步改善:
+
+* 一开始就通过构造函数显示地创建实例,可用工场模式将其升级成可控生成
+* 在实例上手动添加 underwear 不是很优雅,可以在外部单独在写个 manager 函数
+```js
+const Model = function(gender) {
+ this.gender = gender
+}
+
+Model.prototype.takephoto = function() {
+ console.log(`${this.gender}穿着${this.underwear}`)
+}
+
+const modelFactory = (function() { // 优化第一点
+ const modelGender = {}
+ return {
+ createModel: function(gender) {
+ if (modelGender[gender]) {
+ return modelGender[gender]
+ }
+ return modelGender[gender] = new Model(gender)
+ }
+ }
+}())
+
+const modelManager = (function() {
+ const modelObj = {}
+ return {
+ add: function(gender, i) {
+ modelObj[i] = {
+ underwear: `第${i}款衣服`
+ }
+ return modelFactory.createModel(gender)
+ },
+ copy: function(model, i) { // 优化第二点
+ model.underwear = modelObj[i].underwear
+ }
+ }
+}())
+
+for (let i = 1; i < 51; i++) {
+ const maleModel = modelManager.add('male', i)
+ modelManager.copy(maleModel, i)
+ maleModel.takephoto()
+}
+
+for (let i = 1; i < 51; i++) {
+ const femaleModel = modelManager.add('female', i)
+ modelManager.copy(femaleModel, i)
+ femaleModel.takephoto()
+}
+```
+
+***
+
+## 10.职责链模式
+职责链模式:类似多米诺骨牌,通过请求第一个条件,会持续执行后续的条件,直到返回结果为止。
+
+重要性:4 星,在项目中能对 if-else 语句进行优化
+### 场景 demo
+场景:某电商针对已付过定金的用户有优惠政策,在正式购买后,已经支付过 500 元定金的用户会收到 100 元的优惠券,200 元定金的用户可以收到 50 元优惠券,没有支付过定金的用户只能正常购买。
+```js
+// orderType: 表示订单类型,1:500 元定金用户;2:200 元定金用户;3:普通购买用户
+// pay:表示用户是否已经支付定金,true: 已支付;false:未支付
+// stock: 表示当前用于普通购买的手机库存数量,已支付过定金的用户不受此限制
+
+const order = function( orderType, pay, stock ) {
+ if ( orderType === 1 ) {
+ if ( pay === true ) {
+ console.log('500 元定金预购,得到 100 元优惠券')
+ } else {
+ if (stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+ }
+ } else if ( orderType === 2 ) {
+ if ( pay === true ) {
+ console.log('200 元定金预购,得到 50 元优惠券')
+ } else {
+ if (stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+ }
+ } else if ( orderType === 3 ) {
+ if (stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+ }
+}
+
+order( 3, true, 500 ) // 普通购买,无优惠券
+```
+
+下面用职责链模式改造代码:
+```js
+const order500 = function(orderType, pay, stock) {
+ if ( orderType === 1 && pay === true ) {
+ console.log('500 元定金预购,得到 100 元优惠券')
+ } else {
+ order200(orderType, pay, stock)
+ }
+}
+
+const order200 = function(orderType, pay, stock) {
+ if ( orderType === 2 && pay === true ) {
+ console.log('200 元定金预购,得到 50 元优惠券')
+ } else {
+ orderCommon(orderType, pay, stock)
+ }
+}
+
+const orderCommon = function(orderType, pay, stock) {
+ if (orderType === 3 && stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+}
+
+order500( 3, true, 500 ) // 普通购买,无优惠券
+```
+
+改造后可以发现代码相对清晰了,但是链路代码和业务代码依然耦合在一起,进一步优化:
+```js
+// 业务代码
+const order500 = function(orderType, pay, stock) {
+ if ( orderType === 1 && pay === true ) {
+ console.log('500 元定金预购,得到 100 元优惠券')
+ } else {
+ return 'nextSuccess'
+ }
+}
+
+const order200 = function(orderType, pay, stock) {
+ if ( orderType === 2 && pay === true ) {
+ console.log('200 元定金预购,得到 50 元优惠券')
+ } else {
+ return 'nextSuccess'
+ }
+}
+
+const orderCommon = function(orderType, pay, stock) {
+ if (orderType === 3 && stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+}
+
+// 链路代码
+const chain = function(fn) {
+ this.fn = fn
+ this.sucessor = null
+}
+
+chain.prototype.setNext = function(sucessor) {
+ this.sucessor = sucessor
+}
+
+chain.prototype.init = function() {
+ const result = this.fn.apply(this, arguments)
+ if (result === 'nextSuccess') {
+ this.sucessor.init.apply(this.sucessor, arguments)
+ }
+}
+
+const order500New = new chain(order500)
+const order200New = new chain(order200)
+const orderCommonNew = new chain(orderCommon)
+
+order500New.setNext(order200New)
+order200New.setNext(orderCommonNew)
+
+order500New.init( 3, true, 500 ) // 普通购买,无优惠券
+```
+
+重构后,链路代码和业务代码彻底地分离。假如未来需要新增 order300,那只需新增与其相关的函数而不必改动原有业务代码。
+另外结合 AOP 还能简化上述链路代码:
+```js
+// 业务代码
+const order500 = function(orderType, pay, stock) {
+ if ( orderType === 1 && pay === true ) {
+ console.log('500 元定金预购,得到 100 元优惠券')
+ } else {
+ return 'nextSuccess'
+ }
+}
+
+const order200 = function(orderType, pay, stock) {
+ if ( orderType === 2 && pay === true ) {
+ console.log('200 元定金预购,得到 50 元优惠券')
+ } else {
+ return 'nextSuccess'
+ }
+}
+
+const orderCommon = function(orderType, pay, stock) {
+ if (orderType === 3 && stock > 0) {
+ console.log('普通购买,无优惠券')
+ } else {
+ console.log('库存不够,无法购买')
+ }
+}
+
+// 链路代码
+Function.prototype.after = function(fn) {
+ const self = this
+ return function() {
+ const result = self.apply(self, arguments)
+ if (result === 'nextSuccess') {
+ return fn.apply(self, arguments) // 这里 return 别忘记了~
+ }
+ }
+}
+
+const order = order500.after(order200).after(orderCommon)
+
+order( 3, true, 500 ) // 普通购买,无优惠券
+```
+职责链模式比较重要,项目中能用到它的地方会有很多,用上它能解耦 1 个请求对象和 n 个目标对象的关系。
+
+***
+
+## 11.中介者模式
+中介者模式:对象和对象之间借助第三方中介者进行通信。
+
+### 场景 demo
+一场测试结束后,公布结果:告知解答出题目的人挑战成功,否则挑战失败。
+```js
+const player = function(name) {
+ this.name = name
+ playerMiddle.add(name)
+}
+
+player.prototype.win = function() {
+ playerMiddle.win(this.name)
+}
+
+player.prototype.lose = function() {
+ playerMiddle.lose(this.name)
+}
+
+const playerMiddle = (function() { // 将就用下这个 demo,这个函数当成中介者
+ const players = []
+ const winArr = []
+ const loseArr = []
+ return {
+ add: function(name) {
+ players.push(name)
+ },
+ win: function(name) {
+ winArr.push(name)
+ if (winArr.length + loseArr.length === players.length) {
+ this.show()
+ }
+ },
+ lose: function(name) {
+ loseArr.push(name)
+ if (winArr.length + loseArr.length === players.length) {
+ this.show()
+ }
+ },
+ show: function() {
+ for (let winner of winArr) {
+ console.log(winner + '挑战成功;')
+ }
+ for (let loser of loseArr) {
+ console.log(loser + '挑战失败;')
+ }
+ },
+ }
+}())
+
+const a = new player('A 选手')
+const b = new player('B 选手')
+const c = new player('C 选手')
+
+a.win()
+b.win()
+c.lose()
+
+// A 选手挑战成功;
+// B 选手挑战成功;
+// C 选手挑战失败;
+```
+在这段代码中 A、B、C 之间没有直接发生关系,而是通过另外的 playerMiddle 对象建立链接,姑且将之当成是中介者模式了。
+
+***
+
+## 12.装饰者模式
+装饰器模式:动态地给函数赋能。
+
+### JavaScript 的装饰者模式
+生活中的例子:天气冷了,就添加衣服来保暖;天气热了,就将外套脱下;这个例子很形象地含盖了装饰器的神韵,随着天气的冷暖变化,衣服可以动态的穿上脱下。
+```js
+let wear = function() {
+ console.log('穿上第一件衣服')
+}
+
+const _wear1 = wear
+
+wear = function() {
+ _wear1()
+ console.log('穿上第二件衣服')
+}
+
+const _wear2 = wear
+
+wear = function() {
+ _wear2()
+ console.log('穿上第三件衣服')
+}
+
+wear()
+
+// 穿上第一件衣服
+// 穿上第二件衣服
+// 穿上第三件衣服
+```
+
+这种方式有以下缺点:1:临时变量会变得越来越多;2:this 指向有时会出错
+### AOP 装饰函数
+```js
+// 前置代码
+Function.prototype.before = function(fn) {
+ const self = this
+ return function() {
+ fn.apply(this, arguments)
+ return self.apply(this, arguments)
+ }
+}
+
+// 后置代码
+Function.prototype.after = function(fn) {
+ const self = this
+ return function() {
+ self.apply(this, arguments)
+ return fn.apply(this, arguments)
+ }
+}
+```
+用后置代码来实验下上面穿衣服的 demo,
+```js
+const wear1 = function() {
+ console.log('穿上第一件衣服')
+}
+
+const wear2 = function() {
+ console.log('穿上第二件衣服')
+}
+
+const wear3 = function() {
+ console.log('穿上第三件衣服')
+}
+
+const wear = wear1.after(wear2).after(wear3)
+wear()
+
+// 穿上第一件衣服
+// 穿上第二件衣服
+// 穿上第三件衣服
+```
+
+但这样子有时会污染原生函数,可以做点通变
+```js
+const after = function(fn, afterFn) {
+ return function() {
+ fn.apply(this, arguments)
+ afterFn.apply(this, arguments)
+ }
+}
+
+const wear = after(after(wear1, wear2), wear3)
+wear()
+```
+
+***
+
+## 13.状态模式
+状态模式:将事物内部的每个状态分别封装成类,内部状态改变会产生不同行为。
+
+优点:用对象代替字符串记录当前状态,状态易维护
+缺点:需编写大量状态类对象
+
+### 场景 demo
+某某牌电灯,按一下按钮打开弱光,按两下按钮打开强光,按三下按钮关闭灯光。
+```js
+// 将状态封装成不同类
+const weakLight = function(light) {
+ this.light = light
+}
+
+weakLight.prototype.press = function() {
+ console.log('打开强光')
+ this.light.setState(this.light.strongLight)
+}
+
+const strongLight = function(light) {
+ this.light = light
+}
+
+strongLight.prototype.press = function() {
+ console.log('关灯')
+ this.light.setState(this.light.offLight)
+}
+
+const offLight = function(light) {
+ this.light = light
+}
+
+offLight.prototype.press = function() {
+ console.log('打开弱光')
+ this.light.setState(this.light.weakLight)
+}
+
+const Light = function() {
+ this.weakLight = new weakLight(this)
+ this.strongLight = new strongLight(this)
+ this.offLight = new offLight(this)
+ this.currentState = this.offLight // 初始状态
+}
+
+Light.prototype.init = function() {
+ const btn = document.createElement('button')
+ btn.innerHTML = '按钮'
+ document.body.append(btn)
+ const self = this
+ btn.addEventListener('click', function() {
+ self.currentState.press()
+ })
+}
+
+Light.prototype.setState = function(state) { // 改变当前状态
+ this.currentState = state
+}
+
+const light = new Light()
+light.init()
+
+// 打开弱光
+// 打开强光
+// 关灯
+```
+
+### 非面向对象实现的状态模式
+借助于 JavaScript 的委托机制,可以像如下实现状态模式:
+```js
+const obj = {
+ 'weakLight': {
+ press: function() {
+ console.log('打开强光')
+ this.currentState = obj.strongLight
+ }
+ },
+ 'strongLight': {
+ press: function() {
+ console.log('关灯')
+ this.currentState = obj.offLight
+ }
+ },
+ 'offLight': {
+ press: function() {
+ console.log('打开弱光')
+ this.currentState = obj.weakLight
+ }
+ },
+}
+
+const Light = function() {
+ this.currentState = obj.offLight
+}
+
+Light.prototype.init = function() {
+ const btn = document.createElement('button')
+ btn.innerHTML = '按钮'
+ document.body.append(btn)
+ const self = this
+ btn.addEventListener('click', function() {
+ self.currentState.press.call(self) // 通过 call 完成委托
+ })
+}
+
+const light = new Light()
+light.init()
+```
+
+***
+
+## 14.适配者模式
+适配者模式:主要用于解决两个接口之间不匹配的问题。
+### demo
+```js
+// 老接口
+const zhejiangCityOld = (function() {
+ return [
+ {
+ name: 'hangzhou',
+ id: 11,
+ },
+ {
+ name: 'jinhua',
+ id: 12
+ }
+ ]
+}())
+
+console.log(getZhejiangCityOld())
+
+// 新接口希望是下面形式
+{
+ hangzhou: 11,
+ jinhua: 12,
+}
+
+// 这时候就可采用适配者模式
+const const adaptor = (function(oldCity) {
+ const obj = {}
+ for (let city of zhejiangCityOld) {
+ obj[city.name] = city.id
+ }
+ return obj
+}())
+```
+
+
+> 原文地址 [JavaScript 中常见设计模式整理](https://juejin.im/post/5afe6430518825428630bc4d)
\ No newline at end of file
diff --git "a/Cute-Article/article/39-\344\275\234\344\270\272\345\211\215\347\253\257\351\234\200\350\246\201\344\272\206\350\247\243\347\232\204\345\274\200\346\272\220\345\215\217\350\256\256\347\237\245\350\257\206.md" "b/Cute-Article/article/39-\344\275\234\344\270\272\345\211\215\347\253\257\351\234\200\350\246\201\344\272\206\350\247\243\347\232\204\345\274\200\346\272\220\345\215\217\350\256\256\347\237\245\350\257\206.md"
new file mode 100644
index 00000000..8345ff42
--- /dev/null
+++ "b/Cute-Article/article/39-\344\275\234\344\270\272\345\211\215\347\253\257\351\234\200\350\246\201\344\272\206\350\247\243\347\232\204\345\274\200\346\272\220\345\215\217\350\256\256\347\237\245\350\257\206.md"
@@ -0,0 +1,112 @@
+* 作为前端工程师,开发中在所难免会用到一些开源框架,而每个框架都有自己的开源协议,每个开源协议之间有什么差别呢? 如果你要开源一个项目,又应该选择哪种开源协议呢?
+* 许多开发者,对于开源协议的认知很少,本文从这些常用的前端框架入手,介绍开源常用开源协议的基础知识。
+
+### 什么是开源协议?
+根据 [开源协议](https://en.wikipedia.org/wiki/Open-source_license) 在维基百科的定义:
+> 开源许可是一种计算机软件和其他产品的许可类型,允许使用、修改或在定义的条款和条件下使用、修改或共享的源代码、蓝图和设计。
+> 这允许终端用户和商业公司对源代码、图纸或设计进行审查和修改,以满足自己的定制、好奇心或故障排除的需要。
+> 开源许可的软件大多是免费的,尽管这并不一定是必须的。许可证只允许非商业的重新分配或修改个人使用的源代码,通常不被认为是开源许可。
+> 然而,开源许可可能会有一些限制,尤其是对软件的起源的表达,比如要求保留作者的名字和代码中的版权声明,或要求重新分配授权软件只有在相同的许可(如copyleft许可证)。
+> 一组流行的开源软件许可证是由开源计划(OSI)根据其开源定义(OSD)批准的。
+
+
+### 为什么要选用开源协议?
+在 [GcsSloop](http://www.gcssloop.com/) 写的文章 [程序员不可不知的版权协议](http://www.gcssloop.com/tips/choose-license) 中给出了很好的概括。
+* 首先是对作者的保护,防止知识成果被恶意利用。开源协议中一般都包含免责声明(禁止代码的作者承担代码被使用后产生的风险及后果),比如你开源了一个破解智能锁的代码,如果有人利用这个去盗窃导致他人损失,你是无需承担责任的。
+* 其次是对使用者的保护,方便使用者。使用者一看就知道自己允许进行哪些操作,不允许进行哪些操作。未添加协议的代码默认是作者保留所有权利的(对此不同国家的法律可能稍微存在区别),这就像一颗定时炸弹,如果你在项目中使用了这一份没有协议的代码,原作者只要能证明你未经许可使用了他的代码,是能够起诉你的。
+
+### 当前主流开源许可证(GPL、BSD、MIT、Mozilla、Apache、LGPL)和它们的异同?
+
+相关概念解析:
+> * 协议和版权信息(License and copyright notice):在代码中保留作者提供的协议和版权信息
+> * 声明变更(State Changes):在代码中声明对原来代码的重大修改及变更
+> * 公开源码(Disclose Source):代码必需公开。如果是基于LGPL协议 下,则只需使用的开源代码公开,不必将整个软件源码公开
+> * 库引用(Library usage):该库可以用于商业软件中
+> * 责任承担(Hold Liable):代码的作者承担代码使用后的风险及产生的后果
+> * 商标使用(Use Trademark):可以使用作者的姓名,作品的Logo,或商标
+> * 附加协议(Sublicensing):允许在软件分发传播过程中附加上原来没有的协议条款等
+
+
+### 当前前端主流框架选取的开源协议
+纵观比较常用的前端框架,用的最广泛的便是 MIT 开源协议。
+> * Vue: MIT [点击阅读](https://github.com/vuejs/vue/blob/dev/LICENSE )
+> * React:MIT [点击阅读]( https://github.com/facebook/react/blob/master/LICENSE)
+> * Element: MIT [点击阅读](https://github.com/ElemeFE/element/blob/master/LICENSE )
+> * Ant Design:MIT [点击阅读](https://github.com/ant-design/ant-design/blob/master/LICENSE )
+
+列举的框架开源协议都是 MIT 。那么为什么选择 MIT 呢?
+MIT是一种简短而简单的许可,只需要保留版权和许可通知。许可的作品、修改和更大的作品可以在不同的条件下分发,并且没有源代码。MIT允许别人用作者的代码做任何事情,但必须保证作者的所有权,并且作者无须承担代码使用产生的风险。
+其中,要重点说一下 React 的开源协议,从 github 的提交历史来看,React 的开源协议经历了一个动荡的过程。从 LICENCE 的提交历史看,Facebook 对专利的重视程度可见一斑。
+
+去年知乎上一个《如何看待百度要求内部全面停止使用 React / React Native?》的文章引起了前端界的热议,事情的起因是大家发现了 Facebook 专利许可证上的描述暗藏玄机。在技术开源的世界,对于开发者而言,许可证就是他们使用开源软件的 “用户协议”。而 Facebook 的开源方式跟其他家都不太一样,别家一般用的都是开源社区公认通用的许可证,而 Facebook 使用的是两个许可证,第一个是通用的 BSD 许可证,第二个是自己写的专利许可证 (patent grant)。
+而在 React 的开源协议中这么写到:
+
+意思就是:
+当发生下列情况时,facebook 有权益吊销你的 React 使用权:
+> * 与 facebook 及其附属机构发生利益冲突
+> * 同任何一个和 facebook 有关的组织发生了法律纠纷
+> * 同任何与 React 有关的组织发生利益冲突
+
+
+翻译成大白话就是:如果你觉得 Facebook 侵犯了你的知识产权,同时你的核心产品是基于 React 实现,如果你想起诉 Facebook,就要权衡一下了,因为根据条款它有权利吊销你的 React 使用权。或者说你用 React 做了一个产品并且在某些领域对 Facebook 构成了利益冲突,那么它就可以强制你的产品下线。
+
+可以说,一旦你开始使用 React 去构建你的核心产品,你的公司就被 facebook 埋下了一颗定时炸弹,并且,炸弹的引爆按钮就握在 facebook 手中。
+
+其实这种事情,从去年就在前端技术圈开吵,后来愈演愈烈,形势每况日下:开源社区在更多 Facebook 开源的热门项目中发现了相同的许可证模式和条款。开发者认为 Facebook 的这种许可证模式正在毒害社区,污染开源精神。而且 Apache 软件基金会宣布所有使用 Apache 开源协议的软件都不得使用带有 Facebook BSD + 专利许可证模式的组件。
+
+不过 Facebook 最后还是意识到了这些问题,修改了开源协议。
+
+
+### 如何为我们的项目选择一个开源协议?
+首先,我们要清楚我们选择开源的目的是什么?
+作为个人,在开源的情况下,我们可以帮助他人,也可以获得他人的帮助,还是一个提升个人代码质量的好方法,同样,也是一个展示自己能力的好方法。世界上开源软件协议的种类非常之多,并且同一款协议有很多变种,协议太宽松会导致作者丧失对作品的很多权利,太严格又不便于使用者使用及作品的传播,所以开源作者要考虑自己对作品想保留哪些权利,放开哪些限制。
+作为公司,代码开源后,会提升公司的地位,树立一个良好的品牌形象,也可以帮助公司发掘潜在员工。
+那么,我们如何选择适合我们的开源许可证呢?
+
+由一张图直观了解如何选取所需要的开源许可证。(原著:乌克兰程序员Paul Bagwell,翻译:阮一峰)
+
+
+举例来说:
+如果我只是想专心的写代码,那么可以选择 MIT ,MIT在保证了作者的所有权的前提下允许别人使用作者的代码,且作者不需要承担使用时的风险。如果我想保护我的代码、专利,那么可以选择 Apache ,Apache 与 MIT 的区别就是提供了专利贡献者的授权,使用者需要明确这一点。
+
+Github 专门发布了一个网站 [Choosing an OSS license doesn’t need to be scary](https://choosealicense.com/) 来帮助开源项目开发者。
+
+> * 我想要一个简单宽松的许可证建议: MIT 许可证。这是一个宽松的、简明扼要的许可证,只要用户在项目副本中包含了版权声明和许可声明,他们就可以拿你的代码做任何想做的事情,你也无需承担任何责任。
+> 使用该许可证的项目:jQuery、Rails
+> * 我比较关心专利
+> 建议: Apache许可证。这类似于 MIT 许可证,但它同时还包含了贡献者向用户提供专利授权相关的条款。
+> 使用该许可证的项目:Apache、SVN和NuGet
+> * 我关心项目的共享改进
+> 建议:GPL( V2或 V3)许可证。这是一种 copyleft 许可证,要求修改项目代码的用户再次分发源码或二进制代码时,必须公布他的相关修改。V3版本与V2类>似,但其进一步约束了在某些限制软件更改的硬件上的使用范围。
+> 使用该许可证的项目:Linux、Git
+> * 我的开源项目不是代码
+> 建议: Creative Commons。这是一个相对宽松的版权协议。它只保留几种了权利(some rights reserved)。使用者可以明确知道所有者的权利,不容易侵犯对>方的版权,作品可以得到有效传播。作为作者,你可以选择以下1~4种权利组合:
+> 1) 署名(Attribution,简写为BY):必须提到原作者。
+> 2) 非商业用途(Noncommercial,简写为NC):不得用于盈利性目的。
+> 3) 禁止演绎(No Derivative Works,简写为ND):不得修改原作品, 不得再创作。
+> 4) 相同方式共享(Share Alike,简写为SA):允许修改原作品,但必须使用相同的许可证发布。
+> * 更多选择
+> Licenses - [http://ChooseALicense.com](https://choosealicense.com/),这里提供了Apache/ GPL/ MIT/ Artistic/ Eclipse/ BSD/ LGPL/ Mozilla/ No License/ Public Domain >Dedication 协议的适用情形、许可内容、禁止内容,及协议全文。
+
+### 如何为代码添加开源协议?
+#### GitHub
+1. 首先需要注册一个 GitHub 账号,并登录
+2. 在 GitHub 上选择创建一个新的 repository
+
+3. 进入创建 repository 页面后,输入基本信息后,点击右下角的 Add a license 选择开源协议,默认是 none。对应的 license 可以直接选择,也可以输入自己想要的 license
+
+
+
+4. 点击最下方 Create repository,就创建成功了。
+5. 创建成功后,代码库中就可以看到自动生成了一个 LICENSE 文件。
+
+### 参考文献
+> [选择一个开源软件协议](http://choosealicense.online/)
+> [程序员不可不知的版权协议](http://www.gcssloop.com/tips/choose-license)
+> [开源许可证都有什么区别,一般开源项目用什么许可证?](https://www.zhihu.com/question/28292322)
+> [都在封杀 React/React Native ,那我到底还该不该继续学呢?](https://zhuanlan.zhihu.com/p/29492362)
+> [React开源协议之争知多少?](https://cauu.github.io/2017/09/React-Opensource-license/)
+> [how to choose a license](https://www.cnblogs.com/Wayou/p/how_to_choose_a_license.html)
+
+
+> [原文地址](https://zhuanlan.zhihu.com/p/35876146)
\ No newline at end of file
diff --git "a/4-\345\237\272\344\272\216Vue\351\205\215\347\275\256axios.md" "b/Cute-Article/article/4-\345\237\272\344\272\216Vue\351\205\215\347\275\256axios.md"
similarity index 100%
rename from "4-\345\237\272\344\272\216Vue\351\205\215\347\275\256axios.md"
rename to "Cute-Article/article/4-\345\237\272\344\272\216Vue\351\205\215\347\275\256axios.md"
diff --git "a/Cute-Article/article/40-\350\247\243\345\257\206Vue SSR.md" "b/Cute-Article/article/40-\350\247\243\345\257\206Vue SSR.md"
new file mode 100644
index 00000000..ab2faccf
--- /dev/null
+++ "b/Cute-Article/article/40-\350\247\243\345\257\206Vue SSR.md"
@@ -0,0 +1,251 @@
+## 1.引言
+最近笔者和小伙伴在研究Vue SSR,但是市面上充斥了太多的从0到1的文章,对大家理解这其中的原理帮助并不是很大,因此,本文将从 *Vue SSR的构建流程、运行流程、SSR的特点和利弊* 这几方面对Vue SSR有一个较为详细的介绍。最后还将附上一个笔者实现的 *去除Vue全家桶的Demo案例* 。
+
+## 2.剖析构建流程
+首先我们镇上一张官网给出的构建图:
+
+
+### app.js入口文件
+`app.js` 是我们的通用`entry`,它的作用就是构建一个Vue的实例以供服务端和客户端使用,注意一下,在纯客户端的程序中我们的`app.js`将会挂载实例到`dom`中,而在`ssr`中这一部分的功能放到了`Client entry`中去做了。
+
+### 两个entry
+接下里我们来看`Client entry`和`Server entry`,这两者分别是客户端的入口和服务端的入口。*Client entry的功能很简单,就是挂载我们的Vue实例到指定的dom元素上*;`Server entry`是一个使用`export`导出的函数。主要负责调用组件内定义的获取数据的方法,获取到SSR渲染所需数据,并存储到上下文环境中。*这个函数会在每一次的渲染中重复的调用*。
+
+### webpack打包构建
+然后我们的服务端代码和客户端代码通过`webpack`分别打包,生成`Server Bundle`和`Client Bundle`,前者会运行在服务器上通过node生成预渲染的`HTML字符串`,发送到我们的客户端以便完成初始化渲染;而客户端bundle就自由了,初始化渲染完全不依赖它了。客户端拿到服务端返回的HTML字符串后,会去“激活”这些静态HTML,是其变成由`Vue动态管理`的DOM,以便响应后续数据的变化。
+
+## 3.剖析运行流程
+到这里我们该谈谈`ssr`的程序是怎么跑起来的了。首先我们得去构建一个vue的实例,也就是我们前面构建流程中说到的`app.js`做的事情,但是这里不同于传统的客户端渲染的程序,我们*需要用一个工厂函数去封装它,以便每一个用户的请求都能够返回一个新的实例,也就是官网说到的避免交叉污染了*。
+
+然后我们可以暂时移步到服务端的`entry`中了,这里要做的就是拿到当前路由匹配的组件,调用组件里定义的一个方法(官网取名叫`asyncData`)拿到初始化渲染的数据,而这个方法要做的也很简单,就是去调用我们`vuex store`中的方法去异步获取数据。
+
+接下来`node服务器`如期启动了,跑的是我们刚写好的服务端`entry`里的函数。在这里还要做的就是将我们刚刚构建好的Vue实例渲染成`HTML字符串`,然后将拿到的数据混入我们的`HTML字符串`中,最后发送到我们客户端。
+
+打开浏览器的network,我们看到了初始化渲染的HTML,并且是我们想要初始化的结构,且完全不依赖于客户端的js文件了。再仔细研究研究,里面有初始化的dom结构,有css,还有一个script标签。script标签里把我们在服务端`entry`拿到的数据挂载了`window`上。原来只是一个纯静态的HTML页面啊,没有任何的交互逻辑,所以啊,现在知道为啥子需要服务端跑一个`vue客户端`再跑一个`vue`了,服务端的`vue`只是混入了个数据渲染了个静态页面,客户端的`vue`才是去实现交互的!
+
+
+顺着前面的思路,我们该看客户端的`entry`了。在这里客户端拿到存在`window`中的数据混入我们客户端的`vuex`中,然后分析数据去执行我们熟悉的其余客户端操作了。
+
+## 4.SSR独特之处
+在SSR中,创建`Vue实例`、创建`store`和创建`router`都是套了一层`工厂函数`的,目的就是`避免数据的交叉污染`。
+
+在服务端只能执行生命周期中的`created`和`beforeCreate`,原因是在服务端是无法操纵dom的,所以可想而知其他的周期也就是不能执行的了。
+
+服务端渲染和客户端渲染不同,需要创建两个`entry`分别跑在`服务端`和`客户端`,并且需要*webpack对其分别打包*;
+
+SSR服务端请求不带`cookie`,需要手动拿到浏览器的`cookie`传给服务端的请求。[实现方式戳这里](https://www.mmxiaowu.com/article/596cbb2d436eb550a5423c30)。
+
+SSR要求dom结构规范,因为浏览器会自动给HTML添加一些结构比如tbody,但是客户端进行混淆服务端放回的HTML时,不会添加这些标签,导致混淆后的HTML和浏览器渲染的HTML不匹配。
+
+*性能问题需要多加关注*。
+* vue.mixin、axios拦截请求使用不当,会内存泄漏。[原因戳这里](https://github.com/vuejs/vue/issues/5089)
+* lru-cache向内存中缓存数据,需要合理缓存改动不频繁的资源。
+
+## 5.可能是把双刃剑
+### SSR的优点
+
+* 更利于SEO。
+
+不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。
+使用了`Vue`或者其它`MVVM框架`之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。
+另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。
+
+* 更利于首屏渲染
+首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
+
+
+## 6.SSR的局限
+
+* 服务端压力较大
+本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;
+
+* 开发条件受限
+在服务端渲染中,`created`和`beforeCreate`之外的生命周期钩子不可用,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;
+
+* 学习成本相对较高
+除了对`webpack`、`Vue`要熟悉,还需要掌握`node`、`Express`相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。
+
+## 6.去除VUEX的SSR实践
+先附上demo地址,[戳这里](https://github.com/LNoe-lzy/vue-ssr-demo/tree/vue-ssr-without-vuex)!
+
+说在前面:
+
+* vue-router不是必须的,不用router其实做个vue的[preRender](https://github.com/chrisvfritz/prerender-spa-plugin)就可以了,完全没必要做ssr;
+* vuex不是必须的,vuex是实现我们客户端和服务端的状态共享的关键,我们可以不使用vuex,但是我们得去实现一套数据预取的逻辑;
+
+官网的demo大而全,集成了`vue-router`和`vuex`,想想我们的项目如果没有使用到这两者,光引入就又需要改造成本,这并不是我们想搞的“丝滑般”过渡,接下来笔者将带领大家一步一步的做个“啥都没有的”demo。
+
+在此笔者的思路是:*构造一个Vue的实例,那么我们可以用这个实例的data来存储我们的预取数据,而用methods中的方法去做数据的异步获取,这样我们只在需要预取数据的组件中去调用这个方法就可以了*。
+
+首先我们需要让我们的组件“共享”这个EventBus,为此笔者简单的封装了一个plugin:
+```js
+export default {
+ install (Vue) {
+ const EventBus = new Vue({
+ data () {
+ return {
+ list: [],
+ nav: []
+ }
+ },
+ methods: {
+ getList () {
+ // get list
+ },
+ getNav () {
+ // get nav
+ }
+ }
+ })
+
+ Vue.prototype.$events = EventBus
+ Vue.$events = EventBus
+ }
+}
+```
+
+然后我们需要在`main.js`中`export`出我们的`EventBus`以便两个`entry`使用。这样我们的`main.js`就像下面这样:
+```js
+import Vue from 'vue'
+import App from './App'
+import EventBus from './event'
+
+Vue.use(EventBus)
+Vue.config.devtools = true
+
+export function createApp () {
+ const app = new Vue({
+ // 注入 router 到根 Vue 实例
+ router,
+ render: h => h(App)
+ })
+
+ return { app, router, eventBus: app.$events }
+}
+```
+
+接下来是我们的两个`entry`了。`server`用来匹配我们的组件并调用组件的`asyncData`方法去获取数据,`client`用来将预渲染的数据存储到我们`eventBus`中的`data`中。
+```js
+// server
+import { createApp } from './main'
+
+export default context => {
+ return new Promise((resolve, reject) => {
+ const { app, eventBus, App } = createApp()
+ // 这里笔者的demo比较简单,仅app组件需要预取数据,复杂业务可以递归遍历哈;
+ const matchedComponents = [App]
+
+ Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
+ eventBus
+ }))).then(() => {
+ context.state = eventBus._data
+ resolve(app)
+ }).catch(reject)
+ })
+}
+
+
+// client
+import Vue from 'vue'
+import { createApp } from './main'
+const { app, eventBus } = createApp()
+
+if (window.__INITIAL_STATE__) {
+ eventBus._data = window.__INITIAL_STATE__
+}
+
+app.$mount('#app')
+```
+
+然后我们需要改造我们的组件了,只需要定义一个`async`方法去调用`EventBus`中的方法获取,考虑到服务端只会执行`beforeCreate`和`created`两个生命周期而`beforeCreate`不能拿到`data`,所以我们需要在`created`中去做数据的获取。
+```js
+// 服务端渲染数据预取;
+asyncData ({ store, eventBus }) {
+ return eventBus.getNav()
+}
+// 将服务端拿到的数据混入vue组件中;
+created () {
+ this.nav = this.$events.nav
+}
+```
+
+然后是`webpack`的改造了,`webpack`的配置其实和纯客户端应用类似,为了区分客户端和服务端两个环境我们将配置分为`base`、`client`和`server`三部分,`base`就是我们的通用基础配置,而`client`和`server`分别用来打包我们的客户端和服务端代码。
+
+首先是`webpack.server.conf.js`,用于生成`server bundle`来传递给`createBundleRenderer函数`在node服务器上调用,入口文件是我们的`entry-server`:
+```js
+const webpack = require('webpack')
+const merge = require('webpack-merge')
+const nodeExternals = require('webpack-node-externals')
+const baseConfig = require('./webpack.base.conf.js')
+const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
+// 去除打包css的配置
+baseConfig.module.rules[1].options = ''
+
+module.exports = merge(baseConfig, {
+ entry: './src/entry-server.js',
+ // 以 Node 适用方式导入
+ target: 'node',
+ // 对 bundle renderer 提供 source map 支持
+ devtool: '#source-map',
+ output: {
+ filename: 'server-bundle.js',
+ libraryTarget: 'commonjs2'
+ },
+ externals: nodeExternals({
+ whitelist: /\.css$/
+ }),
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
+ 'process.env.VUE_ENV': '"server"'
+ }),
+ // 这是将服务器的整个输出
+ // 构建为单个 JSON 文件的插件。
+ // 默认文件名为 `vue-ssr-server-bundle.json`
+ new VueSSRServerPlugin()
+ ]
+})
+```
+其次是`webpack.client.conf.js`,这里我们可以根据官方的配置生成`clientManifest`,自动推断和注入资源预加载,以及 css 链接 / script 标签到所渲染的 HTML。入口是我们的`client-server`:
+```js
+const webpack = require('webpack')
+const merge = require('webpack-merge')
+const base = require('./webpack.base.conf')
+const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
+
+const config = merge(base, {
+ entry: {
+ app: './src/entry-client.js'
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
+ 'process.env.VUE_ENV': '"client"'
+ }),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ minChunks: function (module) {
+ return (
+ /node_modules/.test(module.context) &&
+ !/\.css$/.test(module.request)
+ )
+ }
+ }),
+ // 这将 webpack 运行时分离到一个引导 chunk 中,
+ // 以便可以在之后正确注入异步 chunk。
+ // 这也为你的 应用程序/vendor 代码提供了更好的缓存。
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'manifest'
+ }),
+ new VueSSRClientPlugin()
+ ]
+})
+```
+
+从`localhost`中我们看到`ssr`预取的数据已经成功出来了,大功告成!
+
+
+## 7.结语
+本文介绍了Vue的SSR的构建和运行流程,也分析了SSR的特点和利弊,希望对大家了解SSR有一定的帮助。最后针对不使用vuex的SSR实现方案进行了介绍,如果感兴趣或者有疑问,欢迎大家留言交流。
+
+[阅读原文](https://zhuanlan.zhihu.com/p/35871344)
\ No newline at end of file
diff --git "a/Cute-Article/article/41-ES2018\357\274\210ES9\357\274\211\347\232\204\346\226\260\347\211\271\346\200\247.md" "b/Cute-Article/article/41-ES2018\357\274\210ES9\357\274\211\347\232\204\346\226\260\347\211\271\346\200\247.md"
new file mode 100644
index 00000000..a10e3cdc
--- /dev/null
+++ "b/Cute-Article/article/41-ES2018\357\274\210ES9\357\274\211\347\232\204\346\226\260\347\211\271\346\200\247.md"
@@ -0,0 +1,226 @@
+在这篇文章中,我将介绍ES2018(ES9)的新特性,并介绍如何使用它们。
+
+JavaScript(ECMAScript)是跨多个平台的许多厂商实施的不断发展的标准。ES6(ECMAScript 2015)花费六年的时间敲定,是一个很大的发行版。新的年度发布流程被制定,以简化流程并更快地添加功能。 ES9(ES2018)是撰写本文时的最新版本。
+
+TC39由包括浏览器厂商在内的各方组成,他们开会推动JavaScript提案沿着一条严格的发展道路前进:
+
+* Stage 0: strawman——最初想法的提交。
+* Stage 1: proposal(提案)——由TC39至少一名成员倡导的正式提案文件,该文件包括API事例。
+* Stage 2: draft(草案)——功能规范的初始版本,该版本包含功能规范的两个实验实现。
+* Stage 3: candidate(候选)——提案规范通过审查并从厂商那里收集反馈
+* Stage 4: finished(完成)——提案准备加入ECMAScript,但是到浏览器或者Nodejs中可能需要更长的时间
+
+## ES2016
+ES2016添加了两个小的特性来说明标准化过程:
+
+1. 数组`includes()`方法,用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回`true`,否则返回`false`。
+
+2. `a ** b`指数运算符,它与 `Math.pow(a, b)`相同。
+
+## ES2017
+ES2017提供了更多的新特性:
+
+1. `Async` 函数呈现更清晰的 `Promise` 语法
+
+2. `Object.values` 方法返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用`for...in`循环的顺序相同(区别在于`for...in`循环枚举原型链中的属性)
+
+3. `Object.entries()`方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用`for...in`循环遍历改对象时返回的顺序一致(区别在于`for...in`循环也枚举原型链中的属性)
+
+4. `Object.getOwnPropertyDescriptors()`返回一个对象的所有自身属性的描述符(`.value`,`.writable`,`.get`,`.set`,`.configurable`,`enumerable`)
+
+5. `padStart()`和`padEnd()`,填充字符串达到当前长度
+
+6. 结尾逗号,数组定义和函数参数列表
+
+7. `ShareArrayBuffer`和`Atomics`用于从共享内存位置读取和写入
+
+关于ES2017的更多信息请[参阅](https://www.sitepoint.com/es2017-whats-new/)
+
+## ES2018
+ECMAScript 2018(或者叫ES9)现在已经可用了。以下功能已经到达 stage 4,但是在撰写本文时在各个浏览器的实现还不完整。
+
+### 异步迭代
+在`async/await`的某些时刻,你可能尝试在同步循环中调用异步函数。例如:
+```js
+async function process(array) {
+ for (let i of array) {
+ await doSomething(i);
+ }
+}
+```
+这段代码不会正常运行,下面这段同样也不会:
+```js
+async function process(array) {
+ array.forEach(async i => {
+ await doSomething(i);
+ });
+}
+```
+这段代码中,循环本身依旧保持同步,并在在内部异步函数之前全部调用完成。
+ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了`next()`方法返回一个`Promise`。因此`await`可以和`for...of`循环一起使用,以串行的方式运行异步操作。例如:
+```js
+async function process(array) {
+ for await (let i of array) {
+ doSomething(i);
+ }
+}
+```
+
+### Promise.finally()
+一个`Promise`调用链要么成功到达最后一个`.then()`,要么失败触发`.catch()`。在某些情况下,你想要在无论`Promise`运行成功还是失败,运行相同的代码,例如清除,删除对话,关闭数据库连接等。
+`.finally()`允许你指定最终的逻辑:
+```js
+function doSomething() {
+ doSomething1()
+ .then(doSomething2)
+ .then(doSomething3)
+ .catch(err => {
+ console.log(err);
+ })
+ .finally(() => {
+ // finish here!
+ });
+}
+```
+
+## Rest/Spread 属性
+ES2015引入了[Rest参数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters)和[扩展运算符](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax)。三个点(...)仅用于数组。Rest参数语法允许我们将一个布丁数量的参数表示为一个数组。
+```js
+restParam(1, 2, 3, 4, 5);
+
+function restParam(p1, p2, ...p3) {
+ // p1 = 1
+ // p2 = 2
+ // p3 = [3, 4, 5]
+}
+```
+
+展开操作符以相反的方式工作,将数组转换成可传递给函数的单独参数。例如`Math.max()`返回给定数字中的最大值:
+```js
+const values = [99, 100, -1, 48, 16];
+console.log( Math.max(...values) ); // 100
+```
+
+ES2018为对象解构提供了和数组一样的Rest参数()和展开操作符,一个简单的例子:
+```js
+const myObject = {
+ a: 1,
+ b: 2,
+ c: 3
+};
+
+const { a, ...x } = myObject;
+// a = 1
+// x = { b: 2, c: 3 }
+```
+
+或者你可以使用它给函数传递参数:
+```js
+restParam({
+ a: 1,
+ b: 2,
+ c: 3
+});
+
+function restParam({ a, ...x }) {
+ // a = 1
+ // x = { b: 2, c: 3 }
+}
+```
+跟数组一样,Rest参数只能在声明的结尾处使用。此外,它只适用于每个对象的顶层,如果对象中嵌套对象则无法适用。
+扩展运算符可以在其他对象内使用,例如:
+```js
+const obj1 = { a: 1, b: 2, c: 3 };
+const obj2 = { ...obj1, z: 26 };
+// obj2 is { a: 1, b: 2, c: 3, z: 26 }
+```
+
+可以使用扩展运算符拷贝一个对象,像是这样`obj2 = {...obj1}`,但是 这只是一个对象的浅拷贝。另外,如果一个对象A的属性是对象B,那么在克隆后的对象cloneB中,该属性指向对象B。
+
+### 正则表达式命名捕获组(Regular Expression Named Capture Groups)
+JavaScript正则表达式可以返回一个匹配的对象——一个包含匹配字符串的类数组,例如:以YYYY-MM-DD的格式解析日期:
+```js
+const
+ reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,
+ match = reDate.exec('2018-04-30'),
+ year = match[1], // 2018
+ month = match[2], // 04
+ day = match[3]; // 30
+```
+
+这样的代码很难读懂,并且改变正则表达式的结构有可能改变匹配对象的索引。
+ES2018允许命名捕获组使用符号`?`,在打开捕获括号(后立即命名,示例如下:
+```js
+const
+ reDate = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/,
+ match = reDate.exec('2018-04-30'),
+ year = match.groups.year, // 2018
+ month = match.groups.month, // 04
+ day = match.groups.day; // 30
+```
+任何匹配失败的命名组都将返回`undefined`。
+命名捕获也可以使用在`replace()`方法中。例如将日期转换为美国的 MM-DD-YYYY 格式:
+```js
+const
+ reDate = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/,
+ d = '2018-04-30',
+ usDate = d.replace(reDate, '$-$-$');
+```
+
+### 正则表达式反向断言(lookbehind)
+目前JavaScript在正则表达式中支持先行断言(lookahead)。这意味着匹配会发生,但不会有任何捕获,并且断言没有包含在整个匹配字段中。例如从价格中捕获货币符号:
+```js
+const
+ reLookahead = /\D(?=\d+)/,
+ match = reLookahead.exec('$123.89');
+
+console.log( match[0] ); // $
+```
+
+ES2018引入以相同方式工作但是匹配前面的反向断言(lookbehind),这样我就可以忽略货币符号,单纯的捕获价格的数字:
+```js
+const
+ reLookbehind = /(?<=\D)\d+/,
+ match = reLookbehind.exec('$123.89');
+
+console.log( match[0] ); // 123.89
+```
+
+以上是 肯定反向断言,非数字`\D`必须存在。同样的,还存在 否定反向断言,表示一个值必须不存在,例如:
+```js
+const
+ reLookbehindNeg = /(? [阅读原文 obkoro1.com](http://obkoro1.com/2018/07/08/JS%E9%AB%98%E7%A8%8B%E4%B8%AD%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/)
+
+### 内存的生命周期:
+1. 分配你所需要的内存:
+
+由于字符串、对象等没有固定的大小,js程序在每次创建字符串、对象的时候,程序都会**分配内存来存储那个实体**。
+
+2. 使用分配到的内存做点什么。
+
+3. 不需要时将其释放回归:
+
+在不需要字符串、对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是**垃圾回收机制所存在的意义**。
+
+**所谓的内存泄漏**指的是:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。
+***
+### 垃圾回收机制:
+在C和C++之类的语言中,需要手动来管理内存的,这也是造成许多不必要问题的根源。幸运的是,在编写js的过程中,内存的分配以及内存的回收完全实现了自动管理,我们不用操心这种事情。
+
+### 垃圾收集机制的原理:
+垃圾收集器会按照固定的时间间隔,**周期性的找出不再继续使用的变量,然后释放其占用的内存**。
+
+#### 什么叫不再继续使用的变量?
+
+不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。
+
+全局变量的生命周期直至浏览器卸载页面才会结束,也就是说**全局变量不会被当成垃圾回收**。
+
+### 标记清除:当前采用的垃圾收集策略
+工作原理:
+
+当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
+
+工作流程:
+
+1. 垃圾收集器会在运行的时候会给存储在内存中的**所有变量都加上标记**。
+2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
+3. 那些还存在标记的变量被视为准备删除的变量。
+4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。
+到2008年为止,IE、Chorme、Fireofx、Safari、Opera **都使用标记清除式的垃圾收集策略**,只不过垃圾收集的时间间隔互有不同。
+
+### 引用计数略:被废弃的垃圾收集策
+循环引用:跟踪记录每个值被引用的技术
+
+在老版本的浏览器中(对,又是IE),IE9以下BOM和DOM对象就是使用C++以COM对象的形式实现的。
+
+COM的垃圾收集机制采用的就是引用计数策略,这种机制在出现循环引用的时候永远都释放不掉内存。
+```js
+var element = document.getElementById('something');
+var myObject = new Object();
+myObject.element = element; // element属性指向dom
+element.someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。
+```
+解决方式是,当我们不使用它们的时候,手动切断链接:
+```js
+myObject.element = null;
+element.someThing = null;
+```
+#### 淘汰:
+
+IE9把BOM和DOM对象转为了真正的js对象,避免了使用这种垃圾收集策略,消除了IE9以下常见的内存泄漏的主要原因。
+
+IE7以下有一个声明狼藉的性能问题,大家了解一下:
+
+1. 256个变量,4096个对象(或数组)字面或者64KB的字符串,达到任何一个临界值会触发垃圾收集器运行。
+2. 如果一个js脚本的生命周期一直保有那么多变量,垃圾收集器会一直频繁的运行,引发严重的性能问题。
+IE7已修复这个问题。
+***
+
+### 哪些情况会引起内存泄漏?
+虽然有垃圾回收机制,但我们在编写代码的时候,有些情况还是会造成内存泄漏,了解这些情况,并在编写程序的时候,注意避免,我们的程序会更具健壮性。
+
+#### 意外的全局变量:
+上文我们提到了**全局变量不会被当成垃圾回收**,我们在编码中有时会出现下面这种情况:
+```js
+function foo() {
+ this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2
+ bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar
+}
+foo();
+```
+当我们使用[默认绑定](https://juejin.im/post/5b3715def265da59af40a630#heading-3),this会指向全局,`this.something`也会创建一个全局变量,这一点可能很多人没有注意到。
+
+**解决方法:在函数内使用严格模式or细心一点**
+```js
+function foo() {
+ "use strict";
+ this.bar2 = "严格模式下this指向undefined";
+ bar = "报错";
+}
+foo();
+```
+当然我们也可以手动释放全局变量的内存:
+```js
+window.bar = undefined
+delete window.bar2
+```
+#### 被遗忘的定时器和回调函数
+当不需要`setInterval`或者`setTimeout`时,**定时器没有被clear**,定时器的**回调函数以及内部依赖的变量都不能被回收**,造成内存泄漏。
+```js
+var someResource = getData();
+setInterval(function() {
+ var node = document.getElementById('Node');
+ if(node) {
+ node.innerHTML = JSON.stringify(someResource));
+ // 定时器也没有清除
+ }
+ // node、someResource 存储了大量数据 无法回收
+}, 1000);
+```
+**解决方法**: 在定时器完成工作的时候,手动清除定时器。
+
+### 闭包:
+**闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。**
+```js
+function bindEvent() {
+ var obj = document.createElement("XXX");
+ var unused = function () {
+ console.log(obj,'闭包内引用obj obj不会被释放');
+ };
+ // obj = null;
+}
+```
+**解决方法**:手动解除引用,`obj = null`。
+
+#### 循环引用问题
+就是IE9以下的循环引用问题,上文讲过了。
+
+#### 没有清理DOM元素引用:
+```js
+var refA = document.getElementById('refA');
+document.body.removeChild(refA); // dom删除了
+console.log(refA, "refA"); // 但是还存在引用 能console出整个div 没有被回收
+```
+不信的话,可以看下这个[dom](https://codepen.io/OBKoro1/pen/vroKbg)。
+
+**解决办法**:`refA = null`;
+
+#### console保存大量数据在内存中。
+过多的console,比如定时器的console会导致浏览器卡死。
+
+**解决**:合理利用console,线上项目尽量少的使用console,当然如果你要发招聘,除外。
+***
+### 如何避免内存泄漏:
+**记住一个原则:不用的东西,及时归还,毕竟你是’借的’嘛。**
+
+1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
+2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
+3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
+
+#### 关于内存泄漏:
+1. 即使是1byte的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
+2. 一般是堆区内存泄漏,栈区不会泄漏。
+基本类型的值存在内存中,被保存在栈内存中,引用类型的值是**对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏**。
+
+3. 使用chorme监控内存泄漏,可以看一下[这篇文章](https://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/)
diff --git "a/Cute-Article/article/43-\346\211\213\346\234\272\347\253\257\351\241\265\351\235\242\345\274\200\345\217\221\345\270\270\350\247\201\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263.md" "b/Cute-Article/article/43-\346\211\213\346\234\272\347\253\257\351\241\265\351\235\242\345\274\200\345\217\221\345\270\270\350\247\201\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263.md"
new file mode 100644
index 00000000..ffb5253f
--- /dev/null
+++ "b/Cute-Article/article/43-\346\211\213\346\234\272\347\253\257\351\241\265\351\235\242\345\274\200\345\217\221\345\270\270\350\247\201\351\227\256\351\242\230\345\222\214\350\247\243\345\206\263.md"
@@ -0,0 +1,356 @@
+## 1.解决页面使用 overflow: scroll 在 iOS 上滑动卡顿的问题?
+首先你可能会给页面的 html 和 body 增加了 height: 100%, 然后就可能造成 iOS 上页面滑动的卡顿问题。解决方案是:
+(1) 看是否能把 body 和 html 的 height: 100% 去除掉。
+(2) 在滚动的容器中增加:`-webkit-overflow-scrolling: touch` 或者给 body 增加:`body {overflow-x: hidden}`。
+
+## 2.iOS 页面橡皮弹回效果遮挡页面选项卡?
+(1) 有时 body 和 html 的 height: 100% 去除掉问题可能就没有了。
+(2) 到达临界值的时候在阻止事件默认行为
+```js
+var startY,endY;
+//记录手指触摸的起点坐标
+$('body').on('touchstart',function (e) {
+ startY = e.touches[0].pageY;
+});
+$('body').on('touchmove',function (e) {
+ endY = e.touches[0].pageY; //记录手指触摸的移动中的坐标
+ //手指下滑,页面到达顶端不能继续下滑
+ if(endY>startY&& $(window).scrollTop()<=0){
+ e.preventDefault();
+ }
+ //手指上滑,页面到达底部能继续上滑
+ if(endY=$('body')[0].scrollHeight){
+ e.preventDefault();
+ }
+})
+```
+有时也会碰见弹窗出来后两个层的橡皮筋效果出现问题,我们可以在弹出弹出时给底层页面加上一个类名,类名禁止页面滑动这样下层的橡皮筋效果就会被禁止,就不会影响弹窗层。
+
+## 3.iOS 机型 margin 属性无效问题?
+(1) 设置 html body 的高度为百分比时,margin-bottom 在 safari 里失效
+(2) 直接 padding 代替 margin
+
+## 4.iOS 绑定点击事件不执行?
+(1) 添加样式 `cursor :pointer`。点击后消除背景闪一下的 css:`-webkit-tap-highlight-color:transparent`;
+
+## 5.iOS 键盘换行变为搜索?
+首先,input 要放在 form 里面。
+这时 "换行" 已经变成 “前往”。
+如果想变成 “搜索”,input 设置 `type="search"`。
+
+## 6.jQuery对 a 标签点击事件不生效?
+出现这种情况的原因不明,有的朋友解释:我们平时都是点击的 A 标签中的文字了。 所以要想用 JS 模拟点击 A 标签事件,就得先往 A 标签中的文字添加能被 JS 捕获的元素,然后再用 JS 模拟点击该元素即可。但是我觉得不合理,虽然找不到原因但是解决办法还是有的。
+```js
+// 方法1
+document.getElementById("abc").click();
+// 方法2
+$("#abc")[0].click();
+```
+## 7.有时因为服务器或者别的原因导致页面上的图片没有找到?
+这是我们想需要用一个本地的图片代替没有找的的图片
+```html
+
+
+```
+
+## 8.transform 属性影响 position:fixed?
+(1) 规范中有规定:如果元素的 transform 值不为 none,则该元素会生成包含块和层叠上下文。CSS Transforms Module Level 1 不只在手机上,电脑上也一样。除了 fixed 元素会受影响之外,z-index(层叠上下文)值也会受影响。绝对定位元素等和包含块有关的属性都会受到影响。当然如果 transform 元素的 display 值为 inline 时又会有所不同。最简单的解决方法就是 transform 元素内部不能有 absolute、fixed 元素.
+
+## 9.iOS 对 position: fixed 不太友好,有时我们需要加点处理?
+在安卓上面,点击页面底部的输入框,软键盘弹出,页面移动上移。 而 iOS 上面,点击页面底部输入框,软键盘弹出,输入框看不到了。。。查资料说什么的都有,iscroll,jquery-moblie,absolute,fixe,static 都非常复杂,要改很多。。。 让他弹出时让滚动条在最低部
+```js
+var u = navigator.userAgent, app = navigator.appVersion;
+var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //iOS终端
+if (isiOS) {
+ $('textarea').focus(function () {
+ window.setTimeout('scrollBottom()', 500);
+ });
+}
+function scrollBottom() {
+ window.scrollTo(0, $('body').height());
+}
+```
+
+## 10.jQuery validate 插件验证问题?
+所有的 input 必须有 name 不然会出错
+
+## 11.有时手机会出现断网的情况,我没可能会对断网的情况做一些处理?
+`navigator.onLine` 可判断是否是脱机状态.
+
+## 12.判断对象的长度?
+(1) 用 `Object.keys`,`Object.keys` 方法返回的是一个数组,数组里面装的是对象的属性。
+```js
+var person = {
+ "name" : "zhangshan",
+ "sex" : "man",
+ "age" : "50",
+ "height" : "180",
+ "phone" : "1xxxxxxxxxx",
+ "email" : "xxxxxxxxx@xxx.com"
+};
+var arr = Object.keys(person);
+console.log(arr.length);
+```
+(2)Object.getOwnPropertyNames(obj).length
+
+## 13.上一题我们用到了 Object.keys 与 Object.getOwnPropertyNames 他们的区别?
+`Object.keys` 定义:返回一个对象可枚举属性的字符串数组;
+`Object.getOwnPropertyNames` 定义:返回一个对象可枚举、不可枚举属性的名称;
+属性的可枚举性、不可枚举性:定义:可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性,对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true,对于通过 `Object.defineProperty` 等定义的属性,该标识值默认为 false。
+```js
+var obj = { "prop1": "v1" };
+Object.defineProperty(obj, "prop2", { value: "v2", enumerable: false });
+console.log(Object.keys(obj).length); //output:1
+console.log(Object.getOwnPropertyNames(obj).length); //output:2
+console.log(Object.keys(obj)); //output:Array[1] => [0: "prop1"]
+console.log(Object.getOwnPropertyNames(obj)); //output:Array[2] => [0: "prop1", 1: "prop2"]
+```
+
+综合实例:
+```js
+var obj = { "prop1": "v1" };
+Object.defineProperty(obj, "prop2", { value: "v2", enumerable: false});
+console.log(obj.hasOwnProperty("prop1")); //output: true
+console.log(obj.hasOwnProperty("prop2")); //output: true
+console.log(obj.propertyIsEnumerable("prop1")); //output: true
+console.log(obj.propertyIsEnumerable("prop2")); //output: false
+console.log('prop1' in obj); //output: true
+console.log('prop2' in obj); //output: true
+for (var item in obj) {
+ console.log(item);
+}
+//output:prop1
+for (var item in Object.getOwnPropertyNames(obj)) {
+ console.log(Object.getOwnPropertyNames(obj)[item]);
+}
+//ouput:[prop1,prop2]
+```
+
+## 14.移动开发不同手机弹出数字键盘问题?
+#### 1. type="tel"
+iOS 和 Android 的键盘表现都差不多
+
+#### 2. type="number"
+**优点**:Android 下实现的一个真正的数字键盘
+**缺点一**:iOS 下不是九宫格键盘,输入不方便
+**缺点二**:旧版 Android(包括微信所用的 X5 内核)在输入框后面会有超级鸡肋的小尾巴,好在 Android 4.4.4 以后给去掉了。 不过对于缺点二,我们可以用 webkit 私有的伪元素给 fix 掉:
+```js
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ appearance: none;
+ margin: 0;
+}
+```
+#### 3. pattern 属性
+pattern 用于验证表单输入的内容,通常 HTML5 的 type 属性,比如 email、tel、number、data 类、url 等,已经自带了简单的数据格式验证功能了,加上 pattern 后,前端部分的验证更加简单高效了。
+显而易见,pattern 的属性值要用正则表达式。
+实例 简单的数字验证
+数字的验证有两个:
+```html
+
+
+```
+
+## 15.input[number] 类型输入非数字字符
+js 获取的值是空;比如 - 12,+123 等
+
+## 16.Javascript:history.go() 和 history.back() 的用法与区别?
+`go(-1)`: 返回上一页,原页面表单中的内容会丢失;
+`back()`: 返回上一页,原页表表单中的内容会保留;
+`history.go(-1)`: 后退 + 刷新;
+`history.back()`: 后退;
+
+之所以注意到这个区别,是因为不同的浏览器后退行为也是有区别的,而区别就跟 `javascript:history.go()` 和 `history.back()` 的区别类似。
+Chrome 和 ff 浏览器后退页面,会刷新后退的页面,若有数据请求也会提交数据申请。类似于 `history.go(-1)`;
+而 safari(包括桌面版和 ipad 版)的后退按钮则不会刷新页面,也不会提交数据申请。类似于 `javascript:history.back()`;
+
+## 17.Meta 基础知识:
+```html
+
+// width 设置viewport宽度,为一个正整数,或字符串‘device-width’
+// height 设置viewport高度,一般设置了宽度,会自动解析出高度,可以不用设置
+// initial-scale 默认缩放比例,为一个数字,可以带小数
+// minimum-scale 允许用户最小缩放比例,为一个数字,可以带小数
+// maximum-scale 允许用户最大缩放比例,为一个数字,可以带小数
+// user-scalable 是否允许手动缩放
+空白页基本meta标签
+
+
+
+
+
+
+
+
+其他meta标签
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+## 18.移动端如何定义字体 font-family?
+@ ------------ 中文字体的英文名称
+@ 宋体 SimSun
+@ 黑体 SimHei
+@ 微信雅黑 Microsoft Yahei
+@ 微软正黑体 Microsoft JhengHei
+@ 新宋体 NSimSun
+@ 新细明体 MingLiU
+@ 细明体 MingLiU
+@ 标楷体 DFKai-SB
+@ 仿宋 FangSong
+@ 楷体 KaiTi
+@ 仿宋GB2312 FangSongGB2312
+@ 楷体GB2312 KaiTiGB2312
+**说明**:中文字体多数使用宋体、雅黑,英文用 Helvetica
+```css
+body {font-family: Microsoft Yahei,SimSun,Helvetica;}
+```
+
+## 19.打电话发短信写邮件怎么实现?
+```html
+// 一、打电话
+打电话给:0755-10086
+// 二、发短信,winphone系统无效
+发短信给: 10086
+// 三、写邮件
+点击我发邮件
+//2.收件地址后添加?cc=开头,可添加抄送地址(Android存在兼容问题)
+点击我发邮件
+//3.跟着抄送地址后,写上&bcc=,可添加密件抄送地址(Android存在兼容问题)
+点击我发邮件
+//4.包含多个收件人、抄送、密件抄送人,用分号(;)隔开多个邮件人的地址
+点击我发邮件
+//5.包含主题,用?subject=
+点击我发邮件
+//6.包含内容,用?body=;如内容包含文本,使用%0A给文本换行
+点击我发邮件
+//7.内容包含链接,含http(s)://等的文本自动转化为链接
+点击我发邮件
+//8.内容包含图片(PC不支持)
+点击我发邮件
+//9.完整示例
+点击我发邮件
+```
+
+## 20.移动端 touch 事件(区分 webkit 和 winphone)?
+#### 1. 以下支持 webkit
+**touchstart**——当手指触碰屏幕时候发生。不管当前有多少只手指
+**touchmove**——当手指在屏幕上滑动时连续触发。通常我们再滑屏页面,会调用 event 的 **preventDefault() 可以阻止默认情况的发生:阻止页面滚动
+**touchend**——当手指离开屏幕时触发
+**touchcancel**——系统停止跟踪触摸时候会触发。例如在触摸过程中突然页面 alert() 一个提示框,此时会触发该事件,这个事件比较少用
+
+#### 2. TouchEvent 说明:
+**touches**:屏幕上所有手指的信息
+**targetTouches**:手指在目标区域的手指信息
+**changedTouches**:最近一次触发该事件的手指信息
+touchend 时,touches 与 targetTouches 信息会被删除,changedTouches 保存的最后一次的信息,最好用于计算手指信息
+#### 3.参数信息 (changedTouches[0])
+**clientX**、**clientY** 在显示区的坐标
+**target**:当前元素
+#### 4.事件响应顺序
+ontouchstart > ontouchmove > ontouchend > onclick
+
+## 21.点击元素产生背景或边框怎么去掉
+* **iOS用户** 点击一个链接,会出现一个半透明灰色遮罩, 如果想要禁用,可设置`-webkit-tap-highlight-color`的alpha值为`0`去除灰色半透明遮罩;
+* **android用户** 点击一个链接,会出现一个边框或者半透明灰色遮罩, 不同生产商定义出来额效果不一样,可设置`-webkit-tap-highlight-color`的alpha值为`0`去除部分机器自带的效果;
+* **winphone系统** 点击标签产生的灰色半透明背景,能通过设置``去掉;
+* 特殊说明:有些机型去除不了,如小米2。对于按钮类还有个办法,不使用a或者input标签,直接用div标签
+```css
+a,button,input,textarea {
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
+ -webkit-user-modify:read-write-plaintext-only;
+ //-webkit-user-modify 副作用 输入法不再能够输入多个字符
+}
+```
+也可以
+```css
+* { -webkit-tap-highlight-color: rgba(0,0,0,0); }
+```
+winphone下
+```html
+
+```
+## 22.美化表单元素
+#### 1. 使用 appearance 改变 webkit 浏览器的默认外观
+```css
+input,select {-webkit-appearance:none; appearance: none;}
+```
+#### 2.winphone 下,使用伪元素改变表单元素默认外观
+* 1) 禁用 select 默认箭头,`::-ms-expand` 修改表单控件下拉箭头,设置隐藏并使用背景图片来修饰
+```css
+select::-ms-expand {display:none;}
+```
+* 2) 禁用 radio 和 checkbox 默认样式,`::-ms-check` 修改表单复选框或单选框默认图标,设置隐藏并使用背景图片来修饰
+```css
+input[type=radio]::-ms-check,
+input[type=checkbox]::-ms-check { display:none; }
+```
+* 3) 禁用 pc 端表单输入框默认清除按钮,`::-ms-clear` 修改清除按钮,设置隐藏并使用背景图片来修饰
+```css
+input[type=text]::-ms-clear,
+input[type=tel]::-ms-clear,
+input[type=number]::-ms-clear { display:none; }
+```
+
+## 23.移动端字体单位 font-size 选择 px 还是 rem?
+如需适配多种移动设备,建议使用 rem。以下为参考值:
+```css
+html {font-size: 62.5%;} //10*16 = 62.5%
+```
+设置 12px 字体 这里注意在 rem 前要加上对应的 px 值,解决不支持 rem 的浏览器的兼容问题,做到优雅降级
+```css
+body {font-size:12px; font-size:1.2rem;}
+```
+
+## 24.input 标签添加上 disable 属性在 iOS 端字体颜色不兼容的问题?
+```css
+input[disabled],input:disabled,input.disabled{
+ color: #3e3e3e;
+ -webkit-text-fill-color: #3e3e3e;
+ -webkit-opacity:1;
+ opacity: 1;
+}
+```
+
+## 25.iOS 的光标大小问题
+#### IE:
+不管该行有没有文字,光标高度与 font-size 一致。
+#### FF:
+该行有文字时,光标高度与 font-size 一致。该行无文字时,光标高度与 input 的 height 一致。
+#### Chrome:
+该行无文字时,光标高度与 line-height 一致;该行有文字时,光标高度从 input 顶部到文字底部 (这两种情况都是在有设定 line-height 的时候),如果没有 line-height,则是与 font-size 一致。
+
+iOS 中情况和 Chrome 相似。
+设置字体大小和行高一致,然后通过 padding 撑开大小,只给 IE 浏览器设置
+```css
+line-height:-ms-line-height:40px;
+```
+
+原文:https://segmentfault.com/a/1190000015178877 作者:键盘上的眼泪
\ No newline at end of file
diff --git "a/Cute-Article/article/44-\345\211\215\347\253\257\346\234\254\345\234\260\346\226\207\344\273\266\346\223\215\344\275\234\345\222\214\344\270\212\344\274\240.md" "b/Cute-Article/article/44-\345\211\215\347\253\257\346\234\254\345\234\260\346\226\207\344\273\266\346\223\215\344\275\234\345\222\214\344\270\212\344\274\240.md"
new file mode 100644
index 00000000..195d40fc
--- /dev/null
+++ "b/Cute-Article/article/44-\345\211\215\347\253\257\346\234\254\345\234\260\346\226\207\344\273\266\346\223\215\344\275\234\345\222\214\344\270\212\344\274\240.md"
@@ -0,0 +1,352 @@
+[原文地址](https://juejin.im/post/5a193b4bf265da43052e528a)
+
+前端无法像原生APP一样直接操作本地文件,否则的话打开个网页就能把用户电脑上的文件偷光了,所以需要通过用户触发,用户可通过以下三种方式操作触发:
+
+1. 通过input type="file" 选择本地文件
+2. 通过拖拽的方式把文件拖过来
+3. 在编辑框里面复制粘贴
+
+### 第一种
+第一种是最常用的手段,通常还会自定义一个按钮,然后盖在它上面,因为`type="file"`的input不好改变样式。如下代码写一个选择控件,并放在form里面:
+```html
+
+```
+然后就可以用FormData获取整个表单的内容:
+```js
+$("#file-input").on("change", function() {
+ console.log(`file name is ${this.value}`);
+ let formData = new FormData(this.form);
+ formData.append("fileName", this.value);
+ console.log(formData);
+});
+```
+把input的value和formData打印出来是这样的:
+
+
+可以看到文件的路径是一个假的路径,也就是说在浏览器无法获取到文件的真实存放位置。同时FormData打印出来是一个空的Objet,但并不是说它的内容是空的,只是它对前端开发人员是透明的,无法查看、修改、删除里面的内容,只能`append`添加字段。
+
+`FormData`无法得到文件的内容,而使用`FileReader`可以读取整个文件的内容。用户选择文件之后,`input.files`就可以得到用户选中的文件,如下代码:
+```js
+$("#file-input").on("change", function() {
+ let fileReader = new FileReader(),
+ fileType = this.files[0].type;
+ fileReader.onload = function() {
+ if (/^image/.test(fileType)) {
+ // 读取结果在fileReader.result里面
+ $(`
`).appendTo("body");
+ }
+ }
+ // 打印原始File对象
+ console.log(this.files[0]);
+ // base64方式读取
+ fileReader.readAsDataURL(this.files[0]);
+});
+```
+把原始的File对象打印出来是这样的:
+
+
+
+它是一个window.File的实例,包含了文件的修改时间、文件名、文件的大小、文件的mime类型等。
+如果需要`限制上传文件的大小`就可以通过判断`size`属性有没有超,单位是字节,而要判断是否为图片文件就可以通过type类型是否以image开头。通过判断文件名的后缀可能会不准,而通过这种判断会比较准。上面的代码使用了一个正则判断,如果是一张图片的话就把它赋值给img的src,并添加到dom里面,但其实这段代码有点问题,就是web不是所有的图片都能通过img标签展示出来,通常是jpg/png/gif这三种,所以你应该需要再判断一下图片格式,如可以把判断改成:
+```js
+/^image\/[jpeg|png|gif]/.test(this.type)
+```
+然后实例化一个`FileReader`,调它的`readAsDataURL`并把`File`对象传给它,监听它的`onload`事件,load完读取的结果就在它的`result`属性里了。它是一个`base64`格式的,可直接赋值给一个img的src。
+
+使用`FileReader`除了可读取为`base64`之外,还能读取为以下格式:
+```js
+// 按base64的方式读取,结果是base64,任何文件都可转成base64的形式
+fileReader.readAsDataURL(this.files[0]);
+
+// 以二进制字符串方式读取,结果是二进制内容的utf-8形式,已被废弃了
+fileReader.readAsBinaryString(this.files[0]);
+
+// 以原始二进制方式读取,读取结果可直接转成整数数组
+fileReader.readAsArrayBuffer(this.files[0]);
+```
+其它的主要是能读取为`ArrayBuffer`,它是一个原始二进制格式的结果。把`ArrayBuffer`打印出来是这样的:
+
+可以看到,它对前端开发人员也是透明的,不能够直接读取里面的内容,但可以通过`ArrayBuffer.length`得到长度,还能转成整型数组,就能知道文件的原始二进制内容了:
+```js
+let buffer = this.result;
+// 依次每字节8位读取,放到一个整数数组
+let view = new Uint8Array(buffer);
+console.log(view);
+```
+
+### 第二种
+如果是通过第二种拖拽的方式,应该怎么读取文件呢?如下html(样式略):
+```html
+
+ drop your image here
+
+```
+这将在页面显示一个框:
+
+
+然后监听它的拖拽事件:
+```js
+$(".img-container").on("dragover", function (event) {
+ event.preventDefault();
+})
+
+.on("drop", function(event) {
+ event.preventDefault();
+ // 数据在event的dataTransfer对象里
+ let file = event.originalEvent.dataTransfer.files[0];
+
+ // 然后就可以使用FileReader进行操作
+ fileReader.readAsDataURL(file);
+
+ // 或者是添加到一个FormData
+ let formData = new FormData();
+ formData.append("fileContent", file);
+})
+```
+数据在`drop`事件的`event.dataTransfer.files`里面,拿到这个`File`对象之后就可以和输入框进行一样的操作了,即使用`FileReader`读取,或者是新建一个空的`formData`,然后把它`append`到`formData`里面。
+
+
+### 第三种
+第三种粘贴的方式,通常是在一个编辑框里操作,如把`div`的`contenteditable`设置为true:
+```html
+
+ hello, paste your image here
+
+```
+粘贴的数据是在`event.clipboardData.files`里面:
+```js
+$("#editor").on("paste", function(event) {
+ let file = event.originalEvent.clipboardData.files[0];
+});
+```
+但是Safari的粘贴不是通过`event`传递的,它是直接在输入框里面添加一张图片,如下图所示:
+
+
+它新建了一个`img`标签,并把`img`的`src`指向一个`blob`的本地数据。什么是`blob`呢,如何读取`blob`的内容呢?
+blob是一种类文件的存储格式,它可以存储几乎任何格式的内容,如json:
+```js
+let data = {hello: "world"};
+let blob = new Blob([JSON.stringify(data)],
+ {type : 'application/json'});
+```
+为了获取本地的blob数据,我们可以用ajax发个本地的请求:
+```js
+$("#editor").on("paste", function(event) {
+ // 需要setTimeout 0等图片出来了再处理
+ setTimeout(() => {
+ let img = $(this).find("img[src^='blob']")[0];
+ console.log(img.src);
+ // 用一个xhr获取blob数据
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", img.src);
+ // 改变mime类型
+ xhr.responseType = "blob";
+ xhr.onload = function () {
+ // response就是一个Blob对象
+ console.log(this.response);
+ };
+ xhr.send();
+ }, 0);
+});
+```
+上面代码把blob打印出来是这样的:
+
+
+能得到它的大小和类型,但是具体内容也是不可见的,它有一个`slice`的方法,可用于切割大文件。和`File`一样,可以使用`FileReader`读取它的内容:
+```js
+function readBlob(blobImg) {
+ let fileReader = new FileReader();
+ fileReader.onload = function() {
+ console.log(this.result);
+ }
+ fileReader.onerror = function(err) {
+ console.log(err);
+ }
+ fileReader.readAsDataURL(blobImg);
+}
+readBlob(this.response);
+```
+除此,还能使用`window.URL`读取,这是一个新的API,经常和`Service Worker`配套使用,因为SW里面常常要解析url。如下代码:
+```js
+function readBlob(blobImg) {
+ let urlCreator = window.URL || window.webkitURL;
+ // 得到base64结果
+ let imageUrl = urlCreator.createObjectURL(this.response);
+ return imageUrl;
+}
+
+readBlob(this.response);
+```
+关于src使用的是blob链接的,除了上面提到的img之外,另外一个很常见的是video标签,如youtobe的视频就是使用的blob:
+
+
+这种数据不是直接在本地的,而是通过持续请求视频数据,然后再通过`blob`这个容器媒介添加到`video`里面,它也是通过URL的API创建的:
+```js
+let mediaSource = new MediaSource();
+video.src = URL.createObjectURL(mediaSource);
+let sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
+sourceBuffer.appendBuffer(buf);
+```
+具体我也没实践过,不再展开讨论。
+
+上面,我们使用了三种方式获取文件内容,最后得到:
+
+1. `FormData`格式
+2. `FileReader`读取得到的`base64`或者`ArrayBuffer`二进制格式
+
+如果直接就是一个`FormData`了,那么直接用`ajax`发出去就行了,不用做任何处理:
+```js
+let form = document.querySelector("form"),
+ formData = new FormData(form),
+formData.append("fileName", "photo.png");
+
+let xhr = new XMLHttpRequest();
+// 假设上传文件的接口叫upload
+xhr.open("POST", "/upload");
+xhr.send(formData);
+```
+如果用jQuery的话,要设置两个属性为false:
+```js
+$.ajax({
+ url: "/upload",
+ type: "POST",
+ data: formData,
+ processData: false, // 不处理数据
+ contentType: false // 不设置内容类型
+});
+```
+因为jQuery会自动把内容做一些转义,并且根据`data`自动设置请求`mime`类型,这里告诉jQuery直接用`xhr.send`发出去就行了。
+
+观察控制台发请求的数据:
+
+
+可以看到这是一种区别于用`&`连接参数的方式,它的编码格式是`multipart/form-data`,就是上传文件`form`表单写的`enctype`:
+```html
+
+```
+如果`xhr.send`的是`FormData`类型话,它会自动设置`enctype`,如果你用默认表单提交上传文件的话就得在`form`上面设置这个属性,因为上传文件只能使用`POST`的这种编码。常用的`POST`编码是`application/x-www-form-urlencoded`,它和`GET`一样,发送的数据里面,参数和参数之间使用`&`连接,如:
+```js
+key1=value1&key2=value2
+```
+特殊字符做转义,这个数据`POST`是放在请求`body`里的,而`GET`是拼在`url`上面的,如果用jq的话,jq会帮你拼并做转义。
+
+而上传文件用的这种`multipart/form-data`,参数和参数之间是且一个相同的字符串隔开的,上面的是使用:
+```
+------WebKitFormBoundary72yvM25iSPYZ4a3F
+```
+这个字符通常会取得比较长、比较随机,因为要保证正常的内容里面不会出现这个字符串,这样内容的特殊字符就不用做转义了。
+
+请求的contentType被浏览器设置成:
+```
+Content-Type:
+multipart/form-data; boundary=----WebKitFormBoundary72yvM25iSPYZ4a3F
+```
+后端服务通过这个就知道怎么解析这么一段数据了。(通常是使用的框架处理了,而具体的接口不需要关心应该怎么解析)
+
+如果读取结果是`ArrayBuffer`的话,也是可以直接用`xhr.send`发送出去的,但是一般我们不会直接把一个文件的内容发出去,而是用某个字段名等于文件内容的方式。如果你读取为`ArrayBuffer`的话再上传的话其实作用不是很大,还不如直接用`formData`添加一个`File`对象的内容,因为上面三种方式都可以拿到`File`对象。如果一开始就是一个`ArrayBuffer`了,那么可以转成`blob`然后再`append`到`FormData`里面。
+
+使用比较多的应该是`base64`,因为前端经常要处理图片,读取为`base64`之后就可以把它画到一个`canvas`里面,然后就可以做一些处理,如压缩、裁剪、旋转等。最后再用`canvas`导出一个`base64`格式的图片,那怎么上传`base64`格式的呢?
+
+### 怎么上传`base64`格式
+第一种是拼一个表单上传的`multipart/form-data`的格式,再用`xhr.sendAsBinary`发出去,如下代码:
+```js
+let base64Data = base64Data.replace(/^data:image\/[^;]+;base64,/, "");
+let boundary = "----------boundaryasoifvlkasldvavoadv";
+xhr.sendAsBinary([
+ // name=data
+ boundary,
+ 'Content-Disposition: form-data; name="data"; filename="' + fileName + '"',
+ 'Content-Type: ' + "image/" + fileType, '',
+ atob(base64Data), boundary,
+ //name=imageType
+ boundary,
+ 'Content-Disposition: form-data; name="imageType"', '',
+ fileType,
+ boundary + '--'
+].join('\r\n'));
+```
+上面代码使用了`window.atob`的api,它可以把`base64`还原成原始内容的字符串表示,如下图所示:
+
+
+`btoa`是把内容转化成`base64`编码,而`atob`是把`base64`还原。在调`atob`之前,需要把表示内容格式的不属于`base64`内容的字符串去掉,即上面代码第一行的`replace`处理。
+
+这样就和使用`formData`类似了,但是由于`sendAsBinary`已经被`deprecated`了,所以新代码不建议再使用这种方式。那怎么办呢?
+
+可以把`base64`转化成`blob`,然后再`append`到一个`formData`里面,下面的函数(来自b64-to-blob)可以把`base64`转成`blob`:
+```js
+function b64toBlob(b64Data, contentType, sliceSize) {
+ contentType = contentType || '';
+ sliceSize = sliceSize || 512;
+
+ var byteCharacters = atob(b64Data);
+ var byteArrays = [];
+
+ for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
+ var slice = byteCharacters.slice(offset, offset + sliceSize);
+
+ var byteNumbers = new Array(slice.length);
+ for (var i = 0; i < slice.length; i++) {
+ byteNumbers[i] = slice.charCodeAt(i);
+ }
+
+ var byteArray = new Uint8Array(byteNumbers);
+
+ byteArrays.push(byteArray);
+ }
+
+ var blob = new Blob(byteArrays, {type: contentType});
+ return blob;
+}
+```
+然后就可以`append`到`formData`里面:
+```js
+let blob = b64toBlob(b64Data, "image/png"),
+ formData = new FormData();
+formData.append("fileContent", blob);
+```
+这样就不用自己去拼一个`multipart/form-data`的格式数据了。
+
+上面处理和上传文件的API可以兼容到IE10+,如果要兼容老的浏览器应该怎么办呢?
+
+可以借助一个`iframe`,原理是默认的`form`表单提交会刷新页面,或者跳到`target`指定的那个url,但是如果把`ifrmae`的`target`指向一个`iframe`,那么刷新的就是`iframe`,返回结果也会显示在`ifame`,然后获取这个`ifrmae`的内容就可得到上传接口返回的结果。
+
+如下代码:
+```js
+let iframe = document.createElement("iframe");
+iframe.display = "none";
+iframe.name = "form-iframe";
+document.body.appendChild(iframe);
+// 改变form的target
+form.target = "form-iframe";
+
+iframe.onload = function() {
+ //获取iframe的内容,即服务返回的数据
+ let responseText = this.contentDocument.body.textContent
+ || this.contentWindow.document.body.textContent;
+};
+
+form.submit();
+```
+`form.submit`会触发表单提交,当请求完成(成功或者失败)之后就会触发iframe的onload事件,然后在onload事件获取返回的数据,如果请求失败了的话,iframe里的内容就为空,可以用这个判断请求有没有成功。
+
+
+
+使用iframe没有办法获取上传进度,使用xhr可以获取当前上传的进度,这个是在XMLHttpRequest 2.0引入的:
+```js
+xhr.upload.onprogress = function (event) {
+ if (event.lengthComputable) {
+ // 当前上传进度的百分比
+ duringCallback ((event.loaded / event.total)*100);
+ }
+};
+```
+这样就可以做一个真实的loading进度条。
+
+
+本文讨论了3种交互方式的读取方式,通过`input`控件在`input.files`可以得到File文件对象,通过拖拽的是在`drop`事件的`event.dataTransfer.files`里面,而通过粘贴的`paste`事件在`event.clipboardData.files`里面,Safari这个怪胎是在编辑器里面插入一个src指向本地的img标签,可以通过发送一个请求加载本地的`blob`数据,然后再通过`FileReader`读取,或者直接`append`到`formData`里面。得到的File对象就可以直接添加到`FormData`里面,如果需要先读取`base64`格式做处理的,那么可以把处理后的`base64`转化为`blob`数据再`append`到`formData`里面。对于老浏览器,可以使用一个iframe解决表单提交刷新页面或者跳页的问题。
+
+总之,前端处理和上传本地文件应该差不多就是这些内容了,但是应该还有好多细节没有提及到,读者可通过本文列的方向自行实践。如果有其它的上传方式还请告知。
\ No newline at end of file
diff --git "a/Cute-Article/article/45-js\344\270\255reduce\347\232\204\347\245\236\345\245\207\347\224\250\346\263\225.md" "b/Cute-Article/article/45-js\344\270\255reduce\347\232\204\347\245\236\345\245\207\347\224\250\346\263\225.md"
new file mode 100644
index 00000000..01b61708
--- /dev/null
+++ "b/Cute-Article/article/45-js\344\270\255reduce\347\232\204\347\245\236\345\245\207\347\224\250\346\263\225.md"
@@ -0,0 +1,135 @@
+最近经常在项目中经常看到别人用reduce处理数据,很是牛掰,很梦幻, 不如自己琢磨琢磨。先看w3c语法。
+## w3c语法
+```js
+array.reduce(function(total, currentValue, currentIndex, arr), initialValue);
+/*
+total: 必需。初始值, 或者计算结束后的返回值。
+currentValue: 必需。当前元素。
+currentIndex: 可选。当前元素的索引;
+arr: 可选。当前元素所属的数组对象。
+initialValue: 可选。传递给函数的初始值,相当于total的初始值。
+*/
+```
+
+## 常见用法
+
+### 1.数组求和
+```js
+const arr = [12, 34, 23];
+const sum = arr.reduce((total, num) => total + num);
+// 设定初始值求和
+const arr = [12, 34, 23];
+const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和
+// 对象数组求和
+var result = [
+{ subject: 'math', score: 88 },
+{ subject: 'chinese', score: 95 },
+{ subject: 'english', score: 80 }
+];
+const sum = result.reduce((prev, cur) => prev + cur.score, 0);
+const sum = result.reduce((prev, cur) => prev + cur.score, -10); // 总分扣除10分
+```
+
+### 2.数组最大值
+```js
+const a = [23,123,342,12];
+const max = a.reduce(function(pre,cur,inde,arr){return pre>cur?pre:cur;}); // 342
+```
+
+## 进阶用法
+
+### 1.数组对象中的用法
+```js
+// 比如生成“老大、老二和老三”
+const objArr = [{name: '老大'}, {name: '老二'}, {name: '老三'}];
+const res = objArr.reduce((pre, cur, index, arr) => {
+if (index === 0) {
+return cur.name;
+}
+else if (index === (arr.length - 1)) {
+return pre + '和' + cur.name;
+}
+else {
+return pre + '、' + cur.name;
+}
+}, '');
+```
+### 2.求字符串中字母出现的次数
+```js
+const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
+const res = str.split('').reduce((prev, cur) => {prev[cur] ? prev[cur]++ : prev[cur] = 1; return prev;}, {});
+```
+
+### 3.数组转数组
+```js
+// 按照一定的规则转成数组
+var arr1 = [2, 3, 4, 5, 6]; // 每个值的平方
+var newarr = arr1.reduce((prev, cur) => {prev.push(cur * cur); return prev;}, []);
+```
+
+### 4.数组转对象
+```js
+// 按照id 取出stream
+var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];
+var obj = streams.reduce((prev, cur) => {prev[cur.id] = cur; return prev;}, {});
+```
+
+## 高级用法
+
+### 1.多维的叠加执行操作
+```js
+// 各科成绩占比重不一样, 求结果
+var result = [
+{ subject: 'math', score: 88 },
+{ subject: 'chinese', score: 95 },
+{ subject: 'english', score: 80 }
+];
+var dis = {
+math: 0.5,
+chinese: 0.3,
+english: 0.2
+};
+var res = result.reduce((prev, cur) => dis[cur.subject] * cur.score + prev, 0);
+
+// 加大难度, 商品对应不同国家汇率不同,求总价格
+var prices = [{price: 23}, {price: 45}, {price: 56}];
+var rates = {
+us: '6.5',
+eu: '7.5',
+};
+var initialState = {usTotal:0, euTotal: 0};
+var res = prices.reduce((prev1, cur1) => Object.keys(rates).reduce((prev2, cur2) => {
+console.log(prev1, cur1, prev2, cur2);
+prev1[`${cur2}Total`] += cur1.price * rates[cur2];
+return prev1;
+}, {}), initialState);
+
+var manageReducers = function() {
+return function(state, item) {
+return Object.keys(rates).reduce((nextState, key) => {
+state[`${key}Total`] += item.price * rates[key];
+return state;
+}, {});
+}
+};
+var res1= prices.reduce(manageReducers(), initialState);
+```
+
+### 2.扁平一个多维数组
+```js
+var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
+var res = arr.reduce((x, y) => x.concat(y), []);
+```
+
+### 3.对象数组去重
+```js
+const hash = {};
+chatlists = chatlists.reduce((obj, next: Object) => {
+const hashId = `${next.topic}_${next.stream_id}`;
+if (!hash[hashId]) {
+hash[`${next.topic}_${next.stream_id}`] = true;
+obj.push(next);
+}
+return obj;
+}, []);
+```
diff --git "a/Cute-Article/article/46-\345\234\250JavaScript\344\270\255\346\233\264\345\245\275\347\232\204\344\275\277\347\224\250\346\225\260\347\273\204.md" "b/Cute-Article/article/46-\345\234\250JavaScript\344\270\255\346\233\264\345\245\275\347\232\204\344\275\277\347\224\250\346\225\260\347\273\204.md"
new file mode 100644
index 00000000..f6ed44b0
--- /dev/null
+++ "b/Cute-Article/article/46-\345\234\250JavaScript\344\270\255\346\233\264\345\245\275\347\232\204\344\275\277\347\224\250\346\225\260\347\273\204.md"
@@ -0,0 +1,142 @@
+[阅读原文](https://juejin.im/post/5b8d0a74f265da431d0e7ec0)
+[MDN Array 介绍](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)
+
+本文短小精悍,我保证。在过去的数个月里,我注意到在我审阅的 pull request 中有四个(关于数组使用的)错误经常出现。同时,我自己也会犯这些错误,因此有了这篇文章。让我们一起学习,以确保以后能正确地使用数组方法!
+
+## 1.使用 `Array.includes` 替代 `Array.indexOf`
+
+> "如果需要在数组中查找某个元素,请使用 `Array.indexOf`。"
+
+我记得在我学习 JavaScript 的课程中有类似的这么一句话。毫无疑问,这完全正确!
+
+在 MDN 文档中,对 `Array.indexOf` 的描述是:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回`-1`。因此,如果在之后的代码中需要用到(给给定元素的)索引,那么` Array.indexOf` 是不二之选。
+
+然而,如果我们仅需要知道数组中是否包含给定元素呢?这意味着只是是与否的区别,这是一个布尔问题(boolean question)。针对这种情况,我建议使用直接返回布尔值的 `Array.includes`。
+```js
+'use strict';
+
+const characters = [
+ 'ironman',
+ 'black_widow',
+ 'hulk',
+ 'captain_america',
+ 'hulk',
+ 'thor',
+];
+
+console.log(characters.indexOf('hulk'));
+// 2
+console.log(characters.indexOf('batman'));
+// -1
+
+console.log(characters.includes('hulk'));
+// true
+console.log(characters.includes('batman'));
+// false
+```
+
+## 2.使用 `Array.find` 替代 `Array.filter`
+`Array.filter` 是一个十分有用的方法。它通过回调函数过滤原数组,并将过滤后的项作为新数组返回。正如它的名字所示,我们将这个方法用于过滤,(一般而言)会获得一个长度更短的新数组。
+
+然而,如果知道经回调函数过滤后,只会剩余唯一的一项,那么我不建议使用 `Array.filter`。比如:使用等于某个唯一 ID 为过滤条件去过滤一个数组。在这个例子中,`Array.filter` 返回一个仅有一项的新数组。然而,我们仅仅是为了获取 ID 为特定 ID 的那一项,这个新数组显得毫无用处。
+
+让我们讨论一下性能。为了获取所有符合回调函数过滤条件的项,`Array.filter` 必须遍历整个数组。如果原数组中有成千上万项,回调函数需要执行的次数是相当多的。
+
+为避免这些情况,我建议使用 `Array.find`。它与 `Array.filter` 一样需要一个回调函数,(但只是返回)符合条件的第一项。当找到符合回调函数过滤条件的第一个元素时,它会立即停止往下的搜寻。不再遍历整个数组。
+```js
+'use strict';
+
+const characters = [
+ { id: 1, name: 'ironman' },
+ { id: 2, name: 'black_widow' },
+ { id: 3, name: 'captain_america' },
+ { id: 4, name: 'captain_america' },
+];
+
+function getCharacter(name) {
+ return character => character.name === name;
+}
+
+console.log(characters.filter(getCharacter('captain_america')));
+// [
+// { id: 3, name: 'captain_america' },
+// { id: 4, name: 'captain_america' },
+// ]
+
+console.log(characters.find(getCharacter('captain_america')));
+// { id: 3, name: 'captain_america' }
+```
+
+## 3.使用 `Array.some` 替代 `Array.find`
+我承认我经常犯这个错误。之后,一位朋友建议我去查看 [MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/some) 以寻找更好的方法。事实上(这错误)与上面 `Array.indexOf`/`Array.includes` 的例子十分相像。
+
+在上面的例子中,我们知道 `Array.find` 需要一个回调函数作为参数,并返回(符合条件的)第一个元素。然而,当我们需要知道数组中是否存在一个元素时,`Array.find` 是最好的选择吗?不一定是,因为它返回一个元素,而不是一个布尔值。
+
+在下面的例子中,我建议使用 `Array.some`,它返回你需要的布尔值。
+```js
+'use strict';
+
+const characters = [
+ { id: 1, name: 'ironman', env: 'marvel' },
+ { id: 2, name: 'black_widow', env: 'marvel' },
+ { id: 3, name: 'wonder_woman', env: 'dc_comics' },
+];
+
+function hasCharacterFrom(env) {
+ return character => character.env === env;
+}
+
+console.log(characters.find(hasCharacterFrom('marvel')));
+// { id: 1, name: 'ironman', env: 'marvel' }
+
+console.log(characters.some(hasCharacterFrom('marvel')));
+// true
+```
+译者注:补充一下 `Array.some` 与 `Array.includes` 使用上的区别。两者都返回一个布尔值,表示某项是否存在于数组之中,一旦找到对应的项,立即停止遍历数组。不同的是 `Array.some` 的参数是回调函数,而 `Array.includes` 的参数是一个值(均不考虑第二个可选参数)。
+假设希望知道值为 value 的项是否存在于数组中,既可以编写代码:`[].includes(value)`, 也可以给 `Array.some` 传入 `item => item === value` 作为回调函数。`Array.includes` 使用更简单,`Array.some` 可操控性更强。
+
+## 4.使用 `Array.reduce` 替代 `Array.filter` 与 `Array.map` 的组合
+事实上说,`Array.reduce` 不太容易理解。然而,如果我们先使用 `Array.filter` 过滤原数组,之后(对结果)再调用 `Array.map` (以获取一个新数组)。这看起似乎有点问题,是我们忽略了什么吗?
+
+这样做的问题是:我们遍历了两次数组。第一次是过滤原数组以获取一个长度稍短的新数组,第二次遍历(译者注:指 `Array.map`)是对 `Array.filter` 的返回的新数组进行加工,再次创造了一个新数组!为得到最终的结果,我们结合使用了两个数组方法。每个方法都有它自己的回调函数,而且供 `Array.map` 使用的临时数组是由 `Array.filter` 提供的,(一般而言)该数组无法复用。
+
+为避免如此低效场景的出现,我的建议是使用 `Array.reduce` 。一样的结果,更好的代码!`Array.reduce` 允许你将过滤后切加工过的项放进累加器中。累加器可以是需要待递增的数字、待填充的对象、 待拼接的字符串或数组等。
+
+在上面的例子中,我们使用了 `Array.map`,(但更)建议使用累加器为待拼接数组的 `Array.reduce` 。在下面的例子中,根据变量 `env` 的值,我们会将它加进累加器中或保持累加器不变(即不作任何处理)。
+```js
+'use strict';
+
+const characters = [
+ { name: 'ironman', env: 'marvel' },
+ { name: 'black_widow', env: 'marvel' },
+ { name: 'wonder_woman', env: 'dc_comics' },
+];
+
+console.log(
+ characters
+ .filter(character => character.env === 'marvel')
+ .map(character => Object.assign({}, character, { alsoSeenIn: ['Avengers'] }))
+);
+// [
+// { name: 'ironman', env: 'marvel', alsoSeenIn: ['Avengers'] },
+// { name: 'black_widow', env: 'marvel', alsoSeenIn: ['Avengers'] }
+// ]
+
+console.log(
+ characters
+ .reduce((acc, character) => {
+ return character.env === 'marvel'
+ ? acc.concat(Object.assign({}, character, { alsoSeenIn: ['Avengers'] }))
+ : acc;
+ }, [])
+)
+// [
+// { name: 'ironman', env: 'marvel', alsoSeenIn: ['Avengers'] },
+// { name: 'black_widow', env: 'marvel', alsoSeenIn: ['Avengers'] }
+// ]
+```
+
+### 这就是本文的全部内容!
+希望这对你有帮助。如果你对本文有任何意见或(关于数组方法使用的)例子需要讨论,请在评论中告诉我。如果你觉得本文不错,请给我点赞 👏 (译者注:对灯发誓,这是原文,不是译者骗赞!)并分享给更多的小伙伴。感谢你的阅读!
+
+注意:请在使用 `Array.find` 和 `Array.includes` 前检查浏览器是否支持相关方法,上述两个方法在 Internet Explorer 上并不支持(译者注:可以使用` Polyfill`)。
diff --git "a/Cute-Article/article/47-http\350\257\267\346\261\202\345\244\264\344\270\216\345\223\215\345\272\224\345\244\264\347\232\204\345\272\224\347\224\250.md" "b/Cute-Article/article/47-http\350\257\267\346\261\202\345\244\264\344\270\216\345\223\215\345\272\224\345\244\264\347\232\204\345\272\224\347\224\250.md"
new file mode 100644
index 00000000..5d93ba9c
--- /dev/null
+++ "b/Cute-Article/article/47-http\350\257\267\346\261\202\345\244\264\344\270\216\345\223\215\345\272\224\345\244\264\347\232\204\345\272\224\347\224\250.md"
@@ -0,0 +1,547 @@
+> [阅读原文](https://juejin.im/post/5b854ddef265da43635d9302)
+
+## Chap1 发现headers
+当我们随便打开一个网址(比如大家经常拿来测试网络的百度)时,打开Network,会看到如下请求头,响应头:
+
+究竟这些headers都有什么用呢? 咱们挨个探个究竟。
+
+## Chap2 headers用途
+
+### 2.1 Content-Type
+`Content-Type`表示请求头或响应头的内容类型。作为请求头时,利用它可以进行`body-parser`。
+Sooo~ What is body-parser?
+body-parser是node常用的中间件,其作用是:
+
+> Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
+
+即在处理数据之前用中间件对post请求体进行解析。
+[body-parser](https://www.npmjs.com/package/body-parser)的例子为:
+
+下面的例子展示了如何给路由添加`body parser`。通常,这是在`express`中最为推荐的使用`body-parser`的方法。
+```js
+var express = require('express')
+var bodyParser = require('body-parser')
+var app = express()
+// create application/json parser
+var jsonParser = bodyParser.json()
+// create application/x-www-form-urlencoded parser
+var urlencodedParser = bodyParser.urlencoded({ extended: false })
+// POST /login gets urlencoded bodies
+app.post('/login', urlencodedParser, function (req, res) {
+ if (!req.body) return res.sendStatus(400)
+ res.send('welcome, ' + req.body.username)
+})
+// POST /api/users gets JSON bodies
+app.post('/api/users', jsonParser, function (req, res) {
+ if (!req.body) return res.sendStatus(400)
+ // create user in req.body
+})
+```
+`body-parser`核心源码为:
+```js
+ // this uses a switch for static require analysis
+ switch (parserName) {
+ case 'json':
+ parser = require('./lib/types/json')
+ break
+ case 'raw':
+ parser = require('./lib/types/raw')
+ break
+ case 'text':
+ parser = require('./lib/types/text')
+ break
+ case 'urlencoded':
+ parser = require('./lib/types/urlencoded')
+ break
+ }
+```
+以`json`为例:
+```js
+var contentType = require('content-type')
+//...
+/**
+ * Get the charset of a request.
+ *
+ * @param {object} req
+ * @api private
+ */
+function getCharset (req) {
+ try {
+ return (contentType.parse(req).parameters.charset || '').toLowerCase()
+ } catch (e) {
+ return undefined
+ }
+}
+//...
+// assert charset per RFC 7159 sec 8.1
+var charset = getCharset(req) || 'utf-8'
+if (charset.substr(0, 4) !== 'utf-') {
+ debug('invalid charset')
+ next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
+ charset: charset,
+ type: 'charset.unsupported'
+ }))
+ return
+}
+```
+可以看出:其背后工作原理就是通过分析请求头中的`Content-Type`的类型,根据不同的类型进行相应数据处理,我们自己模拟一下:
+
+step1: 先建立`server.js`:
+```js
+ req.on('end',function (params) {
+ let r = Buffer.concat(arr).toString();
+ // body-parser 解析请求,根据不同的格式进行不同的解析
+ if (req.headers['content-type'] === www.js){
+ let querystring = require('querystring');
+ r = querystring.parse(r); // a=1&b=2
+ console.log(r,1);
+ } else if (req.headers['content-type'] === 'application/json'){
+ console.log(JSON.parse(r),2);
+ } else{
+ console.log(r,3);
+ }
+ res.end('end');
+ })
+```
+step2: 客户端模拟请求:
+```js
+let opts = {
+ host:'localhost',
+ port:3000,
+ path:'/hello',
+ headers:{
+ 'a':1,
+ 'Content-Type':'application/json',
+ "Content-Length":7 //模拟的时候需要带上长度,不然客户端会当成没有传递数据
+ }
+}
+let http = require('http');
+let client = http.request(opts,function (res) {
+ res.on('data',function (data) {
+ console.log(data.toString());
+ })
+});
+client.end("{\"a\":1}"); // 表示把请求发出去
+```
+step3: 测试。
+先启动server,再启动client,服务端收到按照`application/json`格式解析的数据: `{ a: 1 } 2`.`Content-Type`与`body-parser`之间的关系就先分析到这里了。后面我们接着看请求头。
+
+### 2.2 Range:bytes
+请求头通过`Range:bytes`可以请求资源的某一部分。利用这个字段可模拟部分读取。如下:
+```js
+ http.createServer(function (req, res) {
+ let range = req.headers['range'];
+ })
+```
+server:
+```js
+let http = require('http');
+let fs = require('fs');
+let path = require('path');
+// 当前要下载的文件的大小
+let size = fs.statSync(path.join(__dirname, 'my.txt')).size;
+let server = http.createServer(function (req, res) {
+ let range = req.headers['range']; // 0-3
+ if (range) {
+ // 模拟请求 curl -v --header "Range:bytes=0-3" http://localhost:3000
+ let [, start, end] = range.match(/(\d*)-(\d*)/);
+ start = start ? Number(start) : 0;
+ end = end ? Number(end) : size - 1; // 10个字节 size 10 (0-9)
+ res.setHeader('Content-Range', `bytes ${start}-${end}/${size - 1}`);
+ fs.createReadStream(path.join(__dirname, 'my.txt'), { start, end }).pipe(res);
+ } else {
+ // 会把文件的内容写给客户端
+ fs.createReadStream(path.join(__dirname, 'my.txt')).pipe(res);
+ //可读流可以通过pipe导到可写流
+ }
+});
+server.listen(3000);
+```
+client:
+```js
+let opts = {
+ host:'localhost',
+ port:3000,
+ headers:{}
+}
+let http = require('http');
+let start = 0;
+let fs = require('fs');
+function download() {
+ opts.headers.Range = `bytes=${start}-${start+3}`;
+ start+=4;
+ console.log(`start is ${start}`)
+ let client = http.request(opts,function (res) {
+ let total = res.headers['content-range'].split('/')[1];
+ // console.log(half)
+ res.on('data',function (data) {
+ fs.appendFileSync('./download1.txt',data);
+ });
+ res.on('end',function () {
+ setTimeout(() => {
+ if ((!pause)&&(start < total))
+ download();
+ }, 1000);
+ })
+ });
+ client.end();
+}
+download()
+```
+分段读取添加暂停功能,监听用户输入
+```js
+let pause = false;
+process.stdin.on('data',function (data) {
+ if (data.toString().includes('p')){
+ pause = true
+ }else{
+ pause = false;
+ download()
+ }
+})
+```
+测试结果:
+
+
+分段读取有以下好处:
+
+> 提高读取速度,多线程并行,分块读取
+> 断点续传
+
+模拟并行下载:
+```js
+let halfFlag = 20
+function download() {
+ opts.headers.Range = `bytes=${start}-${start+3}`;
+ start+=4;
+ console.log(`start is ${start}`)
+ let client = http.request(opts,function (res) {
+ let total = res.headers['content-range'].split('/')[1];
+ let halfFlag = Math.floor(total/2)
+ // console.log(half)
+ res.on('data',function (data) {
+ fs.appendFileSync('./download1.txt',data);
+ });
+ res.on('end',function () {
+ setTimeout(() => {
+ if ((!pause)&&(start < halfFlag))
+ download();
+ }, 1000);
+ })
+ });
+ client.end();
+}
+let half = halfFlag
+
+function downloadTwo() {
+ opts.headers.Range = `bytes=${half}-${half+3}`;
+ half+=4;
+ console.log(`half is ${half}`)
+ let client = http.request(opts,function (res) {
+ let total = res.headers['content-range'].split('/')[1];
+ res.on('data',function (data) {
+ fs.appendFileSync('./download2.txt',data);
+ });
+ res.on('end',function () {
+ setTimeout(() => {
+ if (!pause&&half < total)
+ downloadTwo();
+ }, 1000);
+ })
+ });
+ client.end();
+}
+download();
+downloadTwo();
+```
+运行结果,会把原文件分成两部分下载到download1.txt和download2.txt。
+测试:
+
+
+理论上,这样的下载方式会比第一种方法节约一半的时间。但是实际中的文件下载怎样实现加速以及并行下载的,还有待考究。
+
+### 2.3 Cache-Control与Expires之强制缓存
+Response Header响应头中`Cache-Control: max-age=1233`可以设置相对当前的时间的强制缓存,与它相关的`Expires`可以设置某个绝对时间点限定读取缓存的时间。
+模拟实现:
+```js
+let url = require('url'); // 专门用来处理url路径的核心模块
+// http://username:password@hostname:port/pathname?query
+let server = http.createServer(async function (req,res) {
+ console.log(req.url)
+ let { pathname,query} = url.parse(req.url,true);
+ // true就是将query转化成对象
+ let readPath = path.join(__dirname, 'public', pathname);
+ try {
+ let statObj = await stat(readPath);
+ // 根客户端说 10s 内走缓存
+ res.setHeader('Cache-Control','max-age=10');
+ res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString());
+ // 10s之内的请求都会走cache 返回200, (from disk cache)不发生请求
+ if (statObj.isDirectory()) {
+ let p = path.join(readPath, 'index.html');
+ await stat(p);
+ // 如果当前目录下有html那么就返回这个文件
+ fs.createReadStream(p).pipe(res);
+ } else {
+ fs.createReadStream(readPath).pipe(res);
+ }
+ }catch(e){
+ res.statusCode = 404;
+ res.end(`Not found`);
+ }
+}).listen(3000);
+```
+测试:
+
+10s内刷新:
+
+
+### 2.4 对比缓存之Last-Modified和If-Modified-Since
+对比响应头Last-Modified and 与请求头If-Modified-Since,可以通过文件修改时间看文件是否修改,从而决定是重新请求还是走缓存。
+模拟如下:
+step1 不设置强制缓存
+```js
+res.setHeader('Cache-Control','no-cache');
+```
+step2 应用文件修改时间比对是否修改,
+```js
+res.setHeader('Last-Modified', statObj.ctime.toGMTString());
+if (req.headers['if-modified-since'] === statObj.ctime.toGMTString()) {
+ res.statusCode = 304;
+ res.end();
+ return; // 走缓存
+}
+fs.createReadStream(readPath).pipe(res);
+```
+测试:
+
+
+### 2.5 对比缓存之Etag和 If-None-Match
+对比响应头:Etag 与请求头:If-None-Match,Etag和If-None-Match如果相等,即返回304。
+etag如何添加?
+
+> 根据文件内容,生成一个md5的摘要,给实体加一个标签。
+
+这种方法虽然比较耗性能,但是能够更加精确的对比出文件是否进行了修改。依靠文件修改时间进行对比并不够准确。因为有时文件有改动Last-Modified发生了变化,但是文件的内容可能根本没有变化。所以这种方案要优于2.4.
+
+实现方法:
+```js
+let rs = fs.createReadStream(p);
+let md5 = crypto.createHash('md5'); // 不能写完响应体再写头
+let arr = [];
+rs.on('data',function (data) {
+ md5.update(data);
+ arr.push(data);
+});
+```
+设置Etag
+```js
+rs.on('end',function () {
+let r = md5.digest('base64');
+res.setHeader('Etag', r);
+if (req.headers['if-none-match'] === r ){
+ res.statusCode = 304;
+ res.end();
+ return;
+}
+res.end(Buffer.concat(arr));
+})
+```
+测试:
+
+
+### 2.6 Accept-Encoding
+依靠请求头: `Accept-Encoding: gzip, deflate`, br告诉服务端可接受的数据格式。服务端返回后会把数据格式通过响应格式通过Content-Encoding来标记。
+在客户端接受gzip的格式下,后端可通过文件压缩处理传递,提高性能。
+node api中提供了[zlib](http://nodejs.cn/api/zlib.html#zlib_class_zlib_gzip)模块:
+> zlib模块提供通过 Gzip 和` Deflate/Inflate` 实现的压缩功能
+
+下面我们来应用zlib与请求头`Accept-Encoding`来实现压缩功能。
+```js
+let zlib = require('zlib');
+let fs = require('fs');
+let path = require('path');
+function gzip(filePath) {
+ let transform = zlib.createGzip();//转化流通过transform压缩,然后再写
+ fs.createReadStream(filePath).pipe(transform).pipe(fs.createWriteStream(filePath+'.gz'));
+}
+gzip('2.txt')
+```
+解压:
+```js
+function gunzip(filePath) {
+ let transform = zlib.createGunzip();
+ fs.createReadStream(filePath).pipe(transform).pipe(fs.createWriteStream(path.basename(filePath,'.gz')));
+}
+```
+`path.basename(filePath,'.gz')`用来去掉filePath文件名的后缀`.gz`。
+根据请求头接受的类型后端的具体操作 :
+```js
+if(req.url === '/download'){
+ res.setHeader('Content-Disposition', 'attachment' )
+ return fs.createReadStream(path.join(__dirname, '1.html')).pipe(res);
+}
+```
+
+```js
+let http = require('http');
+let fs = require('fs');
+let path = require('path');
+let zlib = require('zlib');
+http.createServer(function (req,res) {
+ if(req.url === '/download'){
+ res.setHeader('Content-Disposition', 'attachment' )
+ return fs.createReadStream(path.join(__dirname, '1.html')).pipe(res);
+ }
+ let rule = req.headers['accept-encoding'];
+ if(rule){
+ if(rule.match(/\bgzip\b/)){
+ res.setHeader('Content-Encoding','gzip');
+ fs.createReadStream(path.join(__dirname, '1.html'))
+ .pipe(zlib.createGzip())
+ .pipe(res);
+ } else if (rule.match(/\bdeflate\b/)){
+ res.setHeader('Content-Encoding', 'deflate');
+ fs.createReadStream(path.join(__dirname, '1.html'))
+ .pipe(zlib.createDeflate())
+ .pipe(res);
+ }else{
+ fs.createReadStream(path.join(__dirname, '1.html')).pipe(res);
+ }
+ }else{
+ fs.createReadStream(path.join(__dirname, '1.html')).pipe(res);
+ }
+}).listen(3000);
+```
+
+test deflate:
+```docker
+curl -v --header "Accept-Encoding:deflate" http://localhost:3000
+* Rebuilt URL to: http://localhost:3000/
+* Trying 127.0.0.1...
+* TCP_NODELAY set
+* Connected to localhost (127.0.0.1) port 3000 (#0)
+> GET / HTTP/1.1
+> Host: localhost:3000
+> User-Agent: curl/7.54.0
+> Accept: */*
+> Accept-Encoding:deflate
+>
+< HTTP/1.1 200 OK
+< Content-Encoding: deflate
+< Date: Thu, 23 Aug 2018 03:01:13 GMT
+< Connection: keep-alive
+< Transfer-Encoding: chunked
+```
+test others:
+```docker
+curl -v --header "Accept-Encoding:nn" http://localhost:3000
+* Rebuilt URL to: http://localhost:3000/
+* Trying 127.0.0.1...
+* TCP_NODELAY set
+* Connected to localhost (127.0.0.1) port 3000 (#0)
+> GET / HTTP/1.1
+> Host: localhost:3000
+> User-Agent: curl/7.54.0
+> Accept: */*
+> Accept-Encoding:nn
+>
+< HTTP/1.1 200 OK
+< Date: Thu, 23 Aug 2018 03:02:51 GMT
+< Connection: keep-alive
+< Transfer-Encoding: chunked
+<
+
+
+
+
+
+
+ Document
+
+
+ 你好
+
+
+* Connection #0 to host localhost left intact
+%
+```
+### 2.7 referer
+
+referer表示请求文件的网址,请求时会携带。为了防止自己网站的文件被外网直接引用,可以通过比较referer,即请求的地址,与本地地址比较,设置防盗链。
+```js
+let http = require('http');
+let fs = require('fs');
+let url = require('url');
+let path = require('path');
+// 这是百度的服务器
+let server = http.createServer(function (req,res) {
+ let { pathname } = url.parse(req.url);
+ let realPath = path.join(__dirname,pathname);
+ fs.stat(realPath,function(err,statObj) {
+ if(err){
+ res.statusCode = 404;
+ res.end();
+ }else{
+ let referer = req.headers['referer'] || req.headers['referred'];
+ if(referer){
+ let current = req.headers['host'] // 代表的是当前图片的地址
+ referer = url.parse(referer).host // 引用图片的网址
+ if (current === referer){
+ fs.createReadStream(realPath).pipe(res);
+ }else{
+ fs.createReadStream(path.join(__dirname,'images/2.jpg')).pipe(res);
+ }
+ }else{
+ fs.createReadStream(realPath).pipe(res);
+ }
+ }
+ })
+}).listen(3000);
+```
+### 2.8 Accept-Language
+请求头:`Accept-Language: zh-CN,zh;q=0.9`
+多个语言用 ',' 分隔,权重用 '=' 表示',没有默认权重为1
+
+后端根据请求接受语言的权重一次查找,查找到就返回,找不到就用默认语言
+```js
+let langs = {
+ en: 'hello world',
+ 'zh-CN':'你好世界',
+ zh:'你好',
+ ja: 'こんにちは、世界'
+}
+let defualtLanguage = 'en'
+// 多语言之服务端方案:来做 (浏览器会发一个头) 前端来做
+// 通过url实现多语言
+let http = require('http');
+http.createServer(function (req,res) {
+ let lan = req.headers['accept-language'];
+ //[[zh,q=0.9],[zh-CN]] =>[{name:'zh-CN',q=1},{name:'zh',q:0.9}]
+ if(lan){
+ lan = lan.split(',');
+ lan = lan.map(l=>{
+ let [name,q] = l.split(';');
+ q = q?Number(q.split('=')[1]):1
+ return {name,q}
+ }).sort((a,b)=>b.q-a.q); // 排出 权重数组
+
+ for(let i = 0 ;i `标签,所以也可表示为:
+```js
+// 表示HTML文档所在窗口的当前高度
+let height = document.body.clientHeight;
+// 表示HTML文档所在窗口的当前宽度
+let width = document.body.clientWidth;
+```
+结论:
+`document.body.clientWidth/Height` 的宽高偏小,高甚至默认200;
+`document.documentElement.clientWidth/Height` 和 `window.innerWidth/Height` 的宽高始终相等。
+所以在不同浏览器都实用的的Javascripit方案:
+```js
+let height = document.documentElement.clientWidth || document.body.clientWidth;
+let width = document.documentElement.clientHeight || document.body.clientHeight;
+```
+
+## 二、网页正文全文宽高
+`scrollWidth` 和 `scrollHeight` 获取网页内容高度和宽度:
+
+* 1.针对IE.Opera:
+`scrollHeight`是网页内容实际高度,可以小于`clientHeight`;
+
+* 2.针对NS.firefox:
+`scrollHeight`是网页内容高度,不过最小值是`clientHeight`;也就是说网页内容实际高度小于`clientHeight`的时候,`scrollHeight`返回`clientHeight`;
+
+* 3.浏览器兼容代码:
+
+```js
+let height = document.documentElement.scrollHeight || document.body.scrollHeight;
+let width = document.documentElement.scrollWidth || document.body.scrollWidth;
+```
+
+## 三、网页可见区域宽高,包括滚动条等边线(会随窗口的显示大小改变)
+* 1.值:
+offsetWidth = scrollWidth + 左右滚动条 + 左右边框;
+offsetHeight = scrollHeight + 上下滚动条 + 上下边框;
+
+* 2.浏览器兼容代码:
+
+```js
+let width = document.documentElement.offsetWidth || document.body.offsetWidth ;
+let height = document.documentElement.offsetHeight || document.body.offsetHeight ;
+```
+
+## 四、网页卷去的距离与偏移量
+1.`scrollLeft`:设置或获取位于给定**对象左边界**与窗口中**目前可见内容的最左端**之间的距离;
+2.`scrollTop`:设置或获取位于给定**对象最顶端**与窗口中**目前可见内容的最左端**之间的距离;
+3.`offsetLeft`:设置或获取位于给定对象相对于版面或由offsetParent属性指定的父坐标的计算左侧位置;
+4.`offsetTop`:设置或获取位于给定对象相对于版面或由offsetParent属性指定的父坐标的计算顶端位置;
+
+## 常用高度/宽度获取的整理
+* 1.获取屏幕的高度和宽度(屏幕分辨率):
+
+```js
+window.screen.height
+window.screen.width
+```
+
+* 2.获取屏幕工作区域的高度和宽度(去掉状态栏):
+
+```js
+window.screen.availHeight
+window.screen.availWidth
+```
+
+* 3.网页全文的高度和宽度:
+
+```js
+document.body.scrollHeight
+document.body.scrollWidth
+```
+
+* 4.滚动条卷上去的高度和向右卷的宽度:
+
+```js
+document.body.scrollTop
+document.body.scrollLeft
+```
+* 5.网页可见区域的高度和宽度(不加边线):
+
+```js
+document.body.clientHeight
+document.body.clientWidth
+```
+* 6.网页可见区域的高度和宽度(加边线):
+
+```js
+document.body.offsetHeight
+document.body.offsetWidth
+```
\ No newline at end of file
diff --git "a/Cute-Article/article/49-Vue \351\235\242\350\257\225\344\270\255\345\270\270\351\227\256\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206.md" "b/Cute-Article/article/49-Vue \351\235\242\350\257\225\344\270\255\345\270\270\351\227\256\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206.md"
new file mode 100644
index 00000000..b1166a77
--- /dev/null
+++ "b/Cute-Article/article/49-Vue \351\235\242\350\257\225\344\270\255\345\270\270\351\227\256\347\237\245\350\257\206\347\202\271\346\225\264\347\220\206.md"
@@ -0,0 +1,208 @@
+[原文](https://mp.weixin.qq.com/s/5tiAmJCLlPTQObMDY2ZgkA)
+
+看看面试题,只是为了查漏补缺,看看自己那些方面还不懂。切记不要以为背了面试题,就万事大吉了,最好是理解背后的原理,这样面试的时候才能侃侃而谈。不然,稍微有水平的面试官一看就能看出,是否有真才实学还是刚好背中了这道面试题(有空再把例子中代码补上)。
+
+## 一、对于MVVM的理解?
+MVVM 是 Model-View-ViewModel 的缩写。
+
+* **Model** 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
+* **View** 代表UI 组件,它负责将数据模型转化成UI 展现出来。
+* **ViewModel** 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
+
+在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
+
+ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
+
+## 二、Vue的生命周期
+
+`beforeCreate`(创建前),在数据观测和初始化事件还未开始
+
+`created`(创建后),完成数据观测,属性和方法的运算,初始化事件, $el 属性还没有显示出来
+
+`beforeMount`(载入前),在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
+
+`mounted`(载入后),在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
+
+`beforeUpdate`(更新前),在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
+
+`updated`(更新后),在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
+
+`beforeDestroy`(销毁前),在实例销毁之前调用。实例仍然完全可用。
+
+`destroyed`(销毁后),在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
+
+### 1、什么是vue生命周期?
+
+答: Vue 实例从创建到销毁的过程,就是生命周期。从`开始创建`、`初始化数据`、`编译模板`、`挂载Dom→渲染`、`更新→渲染`、`销毁`等一系列过程,称之为 Vue 的**生命周期**。
+
+### 2、vue生命周期的作用是什么?
+
+答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
+
+### 3、vue生命周期总共有几个阶段?
+
+答:它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。
+
+### 4、第一次页面加载会触发哪几个钩子?
+
+答:会触发下面这几个`beforeCreate`、`created`、`beforeMount`、`mounted` 。
+
+### 5、DOM 渲染在哪个周期中就已经完成?
+
+答:DOM 渲染在 `mounted` 中就已经完成了。
+
+## 三、 Vue实现数据双向绑定的原理:Object.defineProperty()
+
+vue实现数据双向绑定主要是:
+采用**数据劫持**结合**发布者-订阅者模式**的方式,通过 `Object.defineProperty()` 来劫持各个属性的`setter`,`getter`,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 `Object.defineProperty()` 将它们转为 `getter/setter`。用户看不到 `getter/sette`r,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
+
+vue的数据双向绑定:
+将MVVM作为数据绑定的入口,整合`Observer`,`Compile`和`Watcher`三者,通过`Observer`来监听自己的`model`的数据变化,通过`Compile`来解析编译模板指令(vue中是用来解析` {{}}`),最终利用`watcher`搭起`observer`和`Compile`之间的通信桥梁,达到**数据变化 —>视图更新**;视图交互变化(input)—>数据model变更双向绑定效果。
+
+js实现简单的双向绑定:
+```html
+
+
+
+
+
+```
+
+
+## 四、Vue组件间的参数传递
+
+### 1、父组件与子组件传值
+父组件传给子组件:子组件通过`props`方法接受数据;
+
+子组件传给父组件: `$emit` 方法传递参数
+
+### 2、非父子组件间的数据传递,兄弟组件传值
+`eventBus`,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道)。
+
+## 五、Vue的路由实现:hash模式 和 history模式
+**hash模式**:在浏览器中符号 `#`,#以及#后面的字符称之为`hash`,用 `window.location.hash` 读取。
+特点:`hash`虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,`hash`不会重加载页面。
+
+**history模式**:`history`采用HTML5的新特性;且提供了两个新方法: `pushState()`, `replaceState()`可以对浏览器历史记录栈进行修改,以及`popState`事件的监听到状态变更。
+
+## 六、Vue与Angular以及React的区别?
+版本在不断更新,以下的区别有可能不是很正确。我工作中只用到vue,对angular和react不怎么熟。
+
+### 1、与AngularJS的区别
+#### 相同点:
+* 都支持指令:内置指令和自定义指令;
+* 都支持过滤器:内置过滤器和自定义过滤器;
+* 都支持双向数据绑定;
+* 都不支持低端浏览器。
+
+#### 不同点:
+* AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;
+* 在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;
+* Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
+
+### 2、与React的区别
+#### 相同点:
+* React采用特殊的`JSX`语法,Vue.js在组件开发中也推崇编写`.vue`特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;
+* 中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;
+* 都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;
+* 在组件开发中都支持`mixins`的特性。
+
+#### 不同点:
+* React采用的Virtual DOM会对渲染出来的结果做脏检查;
+* Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。
+
+## 七、vue路由的钩子函数
+首页可以控制导航跳转,`beforeEach`,`afterEach`等,一般用于页面`title`的修改。一些需要登录才能调整页面的重定向功能。
+
+**beforeEach**主要有3个参数`to`,`from`,`next`。
+**`to`**:route即将进入的目标路由对象。
+**`from`**:route当前导航正要离开的路由。
+**`next`**:function一定要调用该方法`resolve`这个钩子。执行效果依赖`next`方法的调用参数。可以控制网页的跳转。
+
+## 八、vuex是什么?怎么使用?哪种功能场景使用它?
+只用来读取的状态集中放在`store`中;
+改变状态的方式是提交`mutations`,这是个同步的事物;
+异步逻辑应该封装在`action`中。
+
+在`main.js`引入`store`,注入。新建了一个目录`store`,`… export` 。
+
+场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车。
+
+
+**state**:Vuex 使用单一状态树,即每个应用将仅仅包含一个`store` 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
+
+**mutations**:`mutations`定义的方法动态修改`Vuex `的 `store` 中的状态或数据。
+
+**getters**:类似`vue`的计算属性,主要用来过滤一些数据。
+
+**action**:`actions`可以理解为通过将`mutations`里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。`view` 层通过` store.dispath` 来分发 `action`。
+```js
+onst store = new Vuex.Store({ //store实例
+ state: {
+ count: 0
+ },
+ mutations: {
+ increment (state) {
+ state.count++
+ }
+ },
+ actions: {
+ increment (context) {
+ context.commit('increment')
+ }
+ }
+})
+
+```
+**modules**:项目特别复杂的时候,可以让每一个模块拥有自己的`state`、`mutation`、`action`、`getters`,使得结构非常清晰,方便管理。
+```js
+const moduleA = {
+ state: { ... },
+ mutations: { ... },
+ actions: { ... },
+ getters: { ... }
+}
+const moduleB = {
+ state: { ... },
+ mutations: { ... },
+ actions: { ... }
+}
+const store = new Vuex.Store({
+ modules: {
+ a: moduleA,
+ b: moduleB
+})
+
+```
+
+
+## 九、其它小知识点
+
+### 1、css只在当前组件起作用
+答:在`style`标签中写入`scoped`即可 例如: ``
+
+### 2、`v-if` 和 `v-show` 区别
+答:`v-if`按照条件是否渲染,`v-show`是`display`的`block`或`none`;
+
+### 3、`$route`和`$router` 区别
+
+答:`$route`是 `路由信息对象`,包括`path`,`params`,`hash`,`query`,`fullPath`,`matched`,`name`等路由信息参数。而`$router`是 `路由实例` 对象包括了路由的跳转方法,钩子函数等。
+
+PS:缺少的案例代码,这几天再补上去。有些地方可能描述的不够清楚,如果有歧义,可能是我理解错了。
\ No newline at end of file
diff --git "a/5-[\345\216\237\345\210\233]VUE\344\270\255\345\256\236\347\216\260\351\200\232\347\224\250js\345\207\275\346\225\260\345\272\223\345\260\201\350\243\205.md" "b/Cute-Article/article/5-[\345\216\237\345\210\233]VUE\344\270\255\345\256\236\347\216\260\351\200\232\347\224\250js\345\207\275\346\225\260\345\272\223\345\260\201\350\243\205.md"
similarity index 100%
rename from "5-[\345\216\237\345\210\233]VUE\344\270\255\345\256\236\347\216\260\351\200\232\347\224\250js\345\207\275\346\225\260\345\272\223\345\260\201\350\243\205.md"
rename to "Cute-Article/article/5-[\345\216\237\345\210\233]VUE\344\270\255\345\256\236\347\216\260\351\200\232\347\224\250js\345\207\275\346\225\260\345\272\223\345\260\201\350\243\205.md"
diff --git "a/Cute-Article/article/50-js\344\270\255get\345\222\214post\347\232\204\345\214\272\345\210\253.md" "b/Cute-Article/article/50-js\344\270\255get\345\222\214post\347\232\204\345\214\272\345\210\253.md"
new file mode 100644
index 00000000..f6027312
--- /dev/null
+++ "b/Cute-Article/article/50-js\344\270\255get\345\222\214post\347\232\204\345\214\272\345\210\253.md"
@@ -0,0 +1,33 @@
+在常见的客户端传递参数的方式有`GET`和`POST`两种:
+* **浏览器地址栏直接输入**:一定是`GET`请求;
+* **超链接**:一定是`GET`请求;
+* **表单**:可以是`GET`,也可以是`POST`,这取决与`