Giter VIP home page Giter VIP logo

blog's Introduction

Lee Chen 的博客

进度

JavaScript探秘系列 更新中,已发布 10 篇。

Vue3 探秘系列 更新中,已发布 1 篇。

LeetCode 题解系列 更新中,已发布 286 篇。

Vue 入门系列 25 篇,已完结。

微信小程序入门系列 4 篇,已完结。

React Native 入门系列 2 篇,已完结。

Node.js 入门系列 30 篇,已完结。

Express 入门系列 6 篇,已完结。

rc-form 源码解读 1 篇,已完结。

怎样成为快速阅读的高手 3 篇,已完结。

如何成为有效学习的高手 8 篇,已完结。

如何设计一个优秀的组件 1 篇,已完结。

零碎的记录系列 更新中,已发布 1 篇。

所思所想系列 更新中,已发布 8 篇。

专栏

JavaScript探秘

  1. JavaScript 对象遍历为什么要使用 hasOwnProperty 检查属性
  2. 深入理解shims-vue.d.ts和declare module
  3. 深入理解 TypeScript 的 type 以及 type 与 interface 和 class 的区别
  4. 虚拟模块在前端开发中的应用与示例
  5. 理解并实现自动导入(Auto Import)功能的原理
  6. .bind()、.call()、.apply()的区别
  7. 在HTML里,attribute和property有什么区别?
  8. HTMLCollection 和 NodeList: 了解 DOM 的集合
  9. JavaScript中的浮点数之谜:为什么0.1 + 0.2 不等于 0.3?
  10. 标签的区别

Vue3探秘

  1. 为什么从 props 中解构变量之后再watch它,无法检测到它的变化?

LeetCode 题解系列

  1. LeetCode 题解:15. 三数之和,JavaScript 双循环+HashMap,详细注释
  2. LeetCode 题解:15. 三数之和,JavaScript 双循环+双指针,详细注释
  3. LeetCode 题解:141. 环形链表,JavaScript HashMap,详细注释
  4. LeetCode 题解:141. 环形链表,JavaScript,快慢指针,详细注释
  5. LeetCode 题解:1051. 高度检查器,JavaScript,桶排序,详细注释
  6. LeetCode 题解:1051. 高度检查器,JavaScript,先排序再比较,详细注释
  7. LeetCode 题解:122. 买卖股票的最佳时机 II,JavaScript,一遍循环,详细注释
  8. LeetCode 题解:283. 移动零,JavaScript,一次遍历,详细注释
  9. LeetCode 题解:1. 两数之和,JavaScript,HashMap,详细注释
  10. LeetCode 题解:1. 两数之和,JavaScript,双循环暴力解法,详细注释
  11. LeetCode 题解:206. 反转链表,JavaScript,While 循环迭代,详细注释
  12. LeetCode 题解: 206. 反转链表,JavaScript,容易理解的递归解释,附详细注释
  13. LeetCode 题解:142. 环形链表 II,JavaScript,HashMap,详细注释
  14. LeetCode 题解:142. 环形链表 II,JavaScript,快慢指针,详细注释
  15. LeetCode 题解:189. 旋转数组,JavaScript,暴力法,详细注释
  16. LeetCode 题解:189. 旋转数组,pop+unshift 一行,JavaScript,详细注释
  17. LeetCode 题解:189. 旋转数组,使用新数组 Copy,JavaScript,详细注释
  18. LeetCode 题解:70. 爬楼梯,递归+哈希表,JavaScript,详细注释
  19. LeetCode 题解:70. 爬楼梯,DP 遍历数组,JavaScript,详细注释
  20. LeetCode 题解:70. 爬楼梯,DP 遍历,变量缓存结果,JavaScript,详细注释
  21. LeetCode 题解:189. 旋转数组,3 次翻转,JavaScript,详细注释
  22. LeetCode 题解:189. 旋转数组,环状替换,JavaScript,详细注释
  23. LeetCode 题解:24. 两两交换链表中的节点,迭代,JavaScript,详细注释
  24. LeetCode 题解:24. 两两交换链表中的节点,递归,JavaScript,详细注释
  25. LeetCode 题解:21. 合并两个有序链表,利用数组排序,JavaScript,详细注释
  26. LeetCode 题解:21. 合并两个有序链表,迭代,JavaScript,详细注释
  27. LeetCode 题解:21. 合并两个有序链表,递归,JavaScript,详细注释
  28. LeetCode 题解:21. 合并两个有序链表,迭代(优化),JavaScript,详细注释
  29. LeetCode 题解:88. 合并两个有序数组,双指针+从前往后+使用新数组 Copy,JavaScript,详细注释
  30. LeetCode 题解:88. 合并两个有序数组,for 循环合并数组+sort 排序,JavaScript,详细注释
  31. LeetCode 题解:88. 合并两个有序数组,splice 合并数组+sort 排序,JavaScript,详细注释
  32. LeetCode 题解:88. 合并两个有序数组,双指针遍历+从前往后,JavaScript,详细注释
  33. LeetCode 题解:88. 合并两个有序数组,双指针+从后往前,JavaScript,详细注释
  34. LeetCode 题解:66. 加一,新数组求和再翻转,JavaScript,详细注释
  35. LeetCode 题解:66. 加一,倒序遍历+可中途退出,JavaScript,详细注释
  36. LeetCode 题解:11. 盛最多水的容器,双循环暴力法,JavaScript,详细注释
  37. LeetCode 题解:11. 盛最多水的容器,while 循环双指针,JavaScript,详细注释
  38. LeetCode 题解:11. 盛最多水的容器,for 循环双指针,JavaScript,详细注释
  39. LeetCode 题解:25. K 个一组翻转链表,迭代,JavaScript,详细注释
  40. LeetCode 题解:26. 删除排序数组中的重复项,双指针,JavaScript,详细注释
  41. LeetCode 题解:20. 有效的括号,for 循环 replace,JavaScript,详细注释
  42. LeetCode 题解:20. 有效的括号,while 循环 replace,JavaScript,详细注释
  43. LeetCode 题解:20. 有效的括号,栈,JavaScript,详细注释
  44. LeetCode 题解:155.最小栈,使用两个栈,详细注释
  45. LeetCode 题解:155. 最小栈,单个栈+对象存储,JavaScript,详细注释
  46. LeetCode 题解:155. 最小栈,单个栈同时存储最小值,JavaScript,详细注释
  47. LeetCode 题解:155. 最小栈,单个栈存储入栈元素与最小值之差,JavaScript,详细注释
  48. LeetCode 题解:155. 最小栈,使用链表代替栈,JavaScript,详细注释
  49. LeetCode 题解:84. 柱状图中最大的矩形,双循环暴力,JavaScript,详细注释
  50. LeetCode 题解:84. 柱状图中最大的矩形,循环+双指针暴力,JavaScript,详细注释
  51. LeetCode 题解:239. 滑动窗口最大值,双循环暴力,JavaScript,详细注释
  52. LeetCode 题解:239. 滑动窗口最大值,单调队列,JavaScript,详细注释
  53. LeetCode 题解:225. 用队列实现栈,两个队列, 压入 - O(n), 弹出 - O(1),JavaScript,详细注释
  54. LeetCode 题解:225. 用队列实现栈,两个队列,压入 -O(1), 弹出 -O(n),JavaScript,详细注释
  55. LeetCode 题解:225. 用队列实现栈,一个队列, 压入 - O(n), 弹出 - O(1),JavaScript,详细注释
  56. LeetCode 题解:232. 用栈实现队列,使用两个栈 入队 - O(1), 出队 - O(n),JavaScript,详细注释
  57. LeetCode 题解:232. 用栈实现队列,使用两个栈 入队 - O(1),出队 - 摊还复杂度 O(1),JavaScript,详细注释
  58. LeetCode 题解:232. 用栈实现队列,使用两个栈 入队 - O(n), 出队 - O(1),JavaScript,详细注释
  59. LeetCode 题解:1. 两数之和,Map+队列+双指针,JavaScript,详细注释
  60. LeetCode 题解:206. 反转链表,双指针,JavaScript,详细注释
  61. LeetCode 题解:622. 设计循环队列,使用数组,JavaScript,详细注释
  62. LeetCode 题解:622. 设计循环队列,使用双向链表,JavaScript,详细注释
  63. LeetCode 题解:84. 柱状图中最大的矩形,使用栈,JavaScript,详细注释
  64. LeetCode 题解:66. 加一,BigInt,JavaScript,详细注释
  65. LeetCode 题解:94. 二叉树的中序遍历,递归,JavaScript,详细注释
  66. LeetCode 题解:144. 二叉树的前序遍历,递归,JavaScript,详细注释
  67. LeetCode 题解:145. 二叉树的后序遍历,递归,JavaScript,详细注释
  68. LeetCode 题解:83. 删除排序链表中的重复元素,迭代,JavaScript,详细注释
  69. LeetCode 题解:83. 删除排序链表中的重复元素,递归,JavaScript,详细注释
  70. LeetCode 题解:590. N 叉树的后序遍历,递归,JavaScript,详细注释
  71. LeetCode 题解:589. N 叉树的前序遍历,递归,JavaScript,详细注释
  72. LeetCode 题解:641. 设计循环双端队列,使用队列,JavaScript,详细注释
  73. LeetCode 题解:641. 设计循环双端队列,使用双向链表,JavaScript,详细注释
  74. LeetCode 题解:242. 有效的字母异位词,数组排序,JavaScript,详细注释
  75. LeetCode 题解:242. 有效的字母异位词,哈希表一次循环,JavaScript,详细注释
  76. LeetCode 题解:242. 有效的字母异位词,哈希表两次循环,JavaScript,详细注释
  77. LeetCode 题解:242. 有效的字母异位词,数组计数,JavaScript,详细注释
  78. LeetCode 题解:49. 字母异位词分组,数组排序,JavaScript,详细注释
  79. LeetCode 题解:49. 字母异位词分组,数组计数+哈希表,JavaScript,详细注释
  80. LeetCode 题解:102. 二叉树的层序遍历,递归,JavaScript,详细注释
  81. LeetCode 题解:429. N 叉树的层序遍历,递归,JavaScript,详细注释
  82. LeetCode 题解:226. 翻转二叉树,递归,JavaScript,详细注释
  83. LeetCode 题解:111. 二叉树的最小深度,递归,JavaScript,详细注释
  84. LeetCode 题解:104. 二叉树的最大深度,递归,JavaScript,详细注释
  85. LeetCode 题解:144. 二叉树的前序遍历,使用栈,JavaScript,详细注释
  86. LeetCode 题解:94. 二叉树的中序遍历,使用栈,JavaScript,详细注释
  87. LeetCode 题解:83. 删除排序链表中的重复元素,HashMap,JavaScript,详细注释
  88. LeetCode 题解:589. N 叉树的前序遍历,栈,JavaScript,详细注释
  89. LeetCode 题解:590. N 叉树的后序遍历,栈,JavaScript,详细注释
  90. LeetCode 题解:22. 括号生成,递归先生成再过滤,JavaScript,详细注释
  91. LeetCode 题解:22. 括号生成,递归生成同时过滤,JavaScript,详细注释
  92. LeetCode 题解:98. 验证二叉搜索树,递归,JavaScript,详细注释
  93. LeetCode 题解:98. 验证二叉搜索树,使用栈中序遍历,JavaScript,详细注释
  94. LeetCode 题解:98. 验证二叉搜索树,递归中序遍历完成后再判断,JavaScript,详细注释
  95. LeetCode 题解:98. 验证二叉搜索树,递归中序遍历过程中判断,JavaScript,详细注释
  96. LeetCode 题解:50. Pow(x, n),暴力法,JavaScript,详细注释
  97. LeetCode 题解:50. Pow(x, n),递归分治,JavaScript,详细注释
  98. LeetCode 题解:50. Pow(x, n),迭代分治,JavaScript,详细注释
  99. LeetCode 题解:46. 全排列,回溯,JavaScript,详细注释
  100. LeetCode 题解:47. 全排列 II,回溯,JavaScript,详细注释
  101. LeetCode 题解:78. 子集,递归回溯,JavaScript,详细注释
  102. LeetCode 题解:78. 子集,迭代+位运算,JavaScript,详细注释
  103. LeetCode 题解:78. 子集,递归+for 循环+回溯,JavaScript,详细注释
  104. LeetCode 题解:78. 子集,迭代,JavaScript,详细注释
  105. LeetCode 题解:90. 子集 II,回溯+哈希表去重,JavaScript,详细注释
  106. LeetCode 题解:90. 子集 II,递归+for 循环+回溯,JavaScript,详细注释
  107. LeetCode 题解:90. 子集 II,迭代+位运算,JavaScript,详细注释
  108. LeetCode 题解:90. 子集 II,迭代,JavaScript,详细注释
  109. LeetCode 题解:231. 2 的幂,迭代,JavaScript,详细注释
  110. LeetCode 题解:231. 2 的幂,递归,JavaScript,详细注释
  111. LeetCode 题解:231. 2 的幂,位运算取二进制中最右边的 1,JavaScript,详细注释
  112. LeetCode 题解:77. 组合,回溯+for 循环,JavaScript,详细注释
  113. LeetCode 题解:77. 组合,递归回溯,JavaScript,详细注释
  114. LeetCode 题解:剑指 Offer 22. 链表中倒数第 k 个节点,使用数组,JavaScript,详细注释
  115. LeetCode 题解:剑指 Offer 22. 链表中倒数第 k 个节点,双指针,JavaScript,详细注释
  116. LeetCode 题解:剑指 Offer 22. 链表中倒数第 k 个节点,使用栈,JavaScript,详细注释
  117. LeetCode 题解:剑指 Offer 22. 链表中倒数第 k 个节点,递归,JavaScript,详细注释
  118. LeetCode 题解:169. 多数元素,哈希表,JavaScript,详细注释
  119. LeetCode 题解:169. 多数元素,排序,JavaScript,详细注释
  120. LeetCode 题解:169. 多数元素,分治,JavaScript,详细注释
  121. LeetCode 题解:17. 电话号码的字母组合,队列,JavaScript,详细注释
  122. LeetCode 题解:860. 柠檬水找零,模拟情境,JavaScript,详细注释
  123. LeetCode 题解:121. 买卖股票的最佳时机,一次遍历,JavaScript,详细注释
  124. LeetCode 题解:121. 买卖股票的最佳时机,暴力法,JavaScript,详细注释
  125. LeetCode 题解:455. 分发饼干,贪心 for 循环,JavaScript,详细注释
  126. LeetCode 题解:455. 分发饼干,贪心 while 循环,JavaScript,详细注释
  127. LeetCode 题解:51. N 皇后,回溯+哈希表,JavaScript,详细注释
  128. LeetCode 题解:52. N 皇后 II,回溯+哈希表,JavaScript,详细注释
  129. LeetCode 题解:874. 模拟行走机器人,模拟情境,JavaScript,详细注释
  130. LeetCode 题解:55. 跳跃游戏,贪心,JavaScript,详细注释
  131. LeetCode 题解:45. 跳跃游戏 II,贪心正向查找,JavaScript,详细注释
  132. LeetCode 题解:45. 跳跃游戏 II,贪心从后向前,JavaScript,详细注释
  133. LeetCode 题解:102. 二叉树的层序遍历,BFS,JavaScript,详细注释
  134. LeetCode 题解:515. 在每个树行中找最大值,BFS,JavaScript,详细注释
  135. LeetCode 题解:515. 在每个树行中找最大值,DFS,JavaScript,详细注释
  136. LeetCode 题解:22. 括号生成,BFS,JavaScript,详细注释
  137. LeetCode 题解:433. 最小基因变化,DFS,JavaScript,详细注释
  138. LeetCode 题解:433. 最小基因变化,BFS,JavaScript,详细注释
  139. LeetCode 题解:429. N 叉树的层序遍历,BFS,JavaScript,详细注释
  140. LeetCode 题解:127. 单词接龙,BFS+统计单词变化次数,JavaScript,详细注释
  141. LeetCode 题解:127. 单词接龙,BFS+生成所有可能新单词再匹配,JavaScript,详细注释
  142. LeetCode 题解:127. 单词接龙,双向 BFS,JavaScript,详细注释
  143. LeetCode 题解:18. 四数之和,哈希表,JavaScript,详细注释
  144. LeetCode 题解:18. 四数之和,双指针,JavaScript,详细注释
  145. LeetCode 题解:92. 反转链表 II,递归,JavaScript,详细注释
  146. LeetCode 题解:55. 跳跃游戏,贪心,JavaScript,详细注释
  147. LeetCode 题解:42. 接雨水,暴力法,JavaScript,详细注释
  148. LeetCode 题解:42. 接雨水,动态规划,JavaScript,详细注释
  149. LeetCode 题解:42. 接雨水,双指针,JavaScript,详细注释
  150. LeetCode 题解:42. 接雨水,栈,JavaScript,详细注释
  151. LeetCode 题解:剑指 Offer 40. 最小的 k 个数,sort,JavaScript,详细注释
  152. LeetCode 题解:剑指 Offer 40. 最小的 k 个数,快速排序,JavaScript,详细注释
  153. LeetCode 题解:剑指 Offer 40. 最小的 k 个数,二叉堆,JavaScript,详细注释
  154. LeetCode 题解:239. 滑动窗口最大值,二叉堆,JavaScript,详细注释
  155. LeetCode 题解:347. 前 K 个高频元素,二叉堆,JavaScript,详细注释
  156. LeetCode 题解:264. 丑数 II,暴力法,JavaScript,详细注释
  157. LeetCode 题解:264. 丑数 II,二叉堆,JavaScript,详细注释
  158. LeetCode 题解:264. 丑数 II,三指针,JavaScript,详细注释
  159. LeetCode 题解:347. 前 K 个高频元素,快速排序,JavaScript,详细注释
  160. LeetCode 题解:104. 二叉树的最大深度,BFS,JavaScript,详细注释
  161. LeetCode 题解:111. 二叉树的最小深度,BFS,JavaScript,详细注释
  162. LeetCode 题解:17. 电话号码的字母组合,BFS,JavaScript,详细注释
  163. LeetCode 题解:236. 二叉树的最近公共祖先,递归,JavaScript,详细注释
  164. LeetCode 题解:236. 二叉树的最近公共祖先,存储父节点,JavaScript,详细注释
  165. LeetCode 题解:105. 从前序与中序遍历序列构造二叉树,递归+数组切割,JavaScript,详细注释
  166. LeetCode 题解:105. 从前序与中序遍历序列构造二叉树,递归+使用索引,JavaScript,详细注释
  167. LeetCode 题解:105. 从前序与中序遍历序列构造二叉树,递归+哈希表,JavaScript,详细注释
  168. LeetCode 题解:105. 从前序与中序遍历序列构造二叉树,Simple O(n) without map,JavaScript,详细注释
  169. LeetCode 题解:389. 找不同,ASCII 码求和,JavaScript,详细注释
  170. LeetCode 题解:389. 找不同,位运算,JavaScript,详细注释
  171. LeetCode 题解:200. 岛屿数量,DFS,JavaScript,详细注释
  172. LeetCode 题解:433. 最小基因变化,BFS+生成所有可能新基因再匹配,JavaScript,详细注释
  173. LeetCode 题解:433. 最小基因变化,双向 BFS(beats 99%),JavaScript,详细注释
  174. LeetCode 题解:69. x 的平方根,二分查找,JavaScript,详细注释
  175. LeetCode 题解:69. x 的平方根,牛顿迭代法+迭代,JavaScript,详细注释
  176. LeetCode 题解:69. x 的平方根,牛顿迭代法+递归,JavaScript,详细注释
  177. LeetCode 题解:529. 扫雷游戏,DFS,JavaScript,详细注释
  178. LeetCode 题解:529. 扫雷游戏,BFS,JavaScript,详细注释
  179. LeetCode 题解:33. 搜索旋转排序数组,二分查找,JavaScript,详细注释
  180. LeetCode 题解:74. 搜索二维矩阵,二分查找,JavaScript,详细注释
  181. LeetCode 题解:153. 寻找旋转排序数组中的最小值,二分查找,JavaScript,详细注释
  182. LeetCode 题解:297. 二叉树的序列化与反序列化,DFS,JavaScript,详细注释
  183. LeetCode 题解:297. 二叉树的序列化与反序列化,BFS,JavaScript,详细注释
  184. LeetCode 题解:62. 不同路径,动态规划,JavaScript,详细注释
  185. LeetCode 题解:1091. 二进制矩阵中的最短路径,BFS,JavaScript,详细注释
  186. LeetCode 题解:63. 不同路径 II,动态规划,JavaScript,详细注释
  187. LeetCode 题解:1143. 最长公共子序列,动态规划,JavaScript,详细注释
  188. LeetCode 题解:53. 最大子序和,动态规划,JavaScript,详细注释
  189. LeetCode题解:198. 打家劫舍,动态规划(缓存偷盗状态),JavaScript,详细注释
  190. LeetCode题解:213. 打家劫舍 II,动态规划(缓存偷盗状态),JavaScript,详细注释
  191. LeetCode题解:322. 零钱兑换,动态规划,JavaScript,详细注释
  192. LeetCode题解:152. 乘积最大子数组,动态规划,JavaScript,详细注释
  193. LeetCode题解:718. 最长重复子数组,动态规划,JavaScript,详细注释
  194. LeetCode题解:120. 三角形最小路径和,动态规划(从上到下),JavaScript,详细注释
  195. LeetCode题解:121. 买卖股票的最佳时机,JavaScript,动态规划,详细注释
  196. LeetCode题解:122. 买卖股票的最佳时机 II,动态规划,JavaScript,详细注释
  197. LeetCode题解:123. 买卖股票的最佳时机 III,动态规划,JavaScript,详细注释
  198. LeetCode题解:188. 买卖股票的最佳时机 IV,动态规划,JavaScript,详细注释
  199. LeetCode题解:190. 颠倒二进制位,使用遮罩,JavaScript,详细注释
  200. LeetCode题解:190. 颠倒二进制位,移动n,JavaScript,详细注释
  201. LeetCode题解:309. 最佳买卖股票时机含冷冻期,动态规划,JavaScript,详细注释
  202. LeetCode题解:714. 买卖股票的最佳时机含手续费,动态规划,JavaScript,详细注释
  203. LeetCode题解:279. 完全平方数,动态规划,JavaScript,详细注释
  204. LeetCode题解:518. 零钱兑换 II,动态规划,JavaScript,详细注释
  205. LeetCode题解:125. 验证回文串,翻转数组,JavaScript,详细注释
  206. LeetCode题解:125. 验证回文串,双指针,JavaScript,详细注释
  207. LeetCode题解:64. 最小路径和,动态规划,JavaScript,详细注释
  208. LeetCode题解:91. 解码方法,动态规划,JavaScript,详细注释
  209. LeetCode题解:91. 解码方法,动态规划(优化),JavaScript,详细注释
  210. LeetCode题解:221. 最大正方形,动态规划,JavaScript,详细注释
  211. LeetCode题解:647. 回文子串,动态规划,JavaScript,详细注释
  212. LeetCode题解:213. 打家劫舍 II,动态规划(不缓存偷盗状态),JavaScript,详细注释
  213. LeetCode题解:198. 打家劫舍,动态规划(不缓存偷盗状态),JavaScript,详细注释
  214. LeetCode题解:126. 单词接龙 II,BFS,JavaScript,详细注释
  215. LeetCode题解:92. 反转链表 II,迭代,JavaScript,详细注释
  216. LeetCode题解:剑指 Offer 49. 丑数,暴力法,JavaScript,详细注释
  217. LeetCode题解:剑指 Offer 49. 丑数,二叉堆,JavaScript,详细注释
  218. LeetCode题解:剑指 Offer 49. 丑数,三指针,JavaScript,详细注释
  219. LeetCode题解:17. 电话号码的字母组合,回溯,JavaScript,详细注释
  220. LeetCode题解:145. 二叉树的后序遍历,栈,JavaScript,详细注释
  221. LeetCode题解:641. 设计循环双端队列,使用数组,JavaScript,详细注释
  222. LeetCode题解:73. 矩阵置零,栈,JavaScript,详细注释
  223. LeetCode题解:151. 翻转字符串里的单词,数组,JavaScript,详细注释
  224. LeetCode题解:151. 翻转字符串里的单词,栈,JavaScript,详细注释
  225. LeetCode题解:191. 位1的个数,位运算,JavaScript,详细注释
  226. LeetCode题解:150. 逆波兰表达式求值,栈,JavaScript,详细注释
  227. LeetCode题解:341. 扁平化嵌套列表迭代器,DFS,JavaScript,详细注释
  228. LeetCode题解:456. 132 模式,n平方暴力,JavaScript,详细注释
  229. LeetCode题解:61. 旋转链表,闭合为环,JavaScript,详细注释
  230. LeetCode题解:61. 旋转链表,双指针,JavaScript,详细注释
  231. LeetCode题解:173. 二叉搜索树迭代器,递归,JavaScript,详细注释
  232. LeetCode题解:173. 二叉搜索树迭代器,栈,JavaScript,详细注释
  233. LeetCode题解:80. 删除有序数组中的重复项 II,JavaScript,详细注释
  234. LeetCode题解:781. 森林中的兔子,贪心,JavaScript,详细注释
  235. LeetCode题解:783. 二叉搜索树节点最小距离,递归,JavaScript,详细注释
  236. LeetCode题解:208. 实现 Trie (前缀树),对象,JavaScript,详细注释
  237. LeetCode题解:220. 存在重复元素 III,暴力法,JavaScript,详细注释
  238. LeetCode题解:217. 存在重复元素,哈希表,JavaScript,详细注释
  239. LeetCode题解:219. 存在重复元素 II,哈希表,JavaScript,详细注释
  240. LeetCode题解:27. 移除元素,JavaScript,详细注释
  241. LeetCode题解:2. 两数相加,迭代,JavaScript,详细注释
  242. LeetCode题解:28. 实现 strStr(),暴力法,JavaScript,详细注释
  243. LeetCode题解:143. 重排链表,数组,JavaScript,详细注释
  244. LeetCode题解:897. 递增顺序搜索树,递归,JavaScript,详细注释
  245. LeetCode题解:897. 递增顺序搜索树,栈,JavaScript,详细注释
  246. LeetCode题解:783. 二叉搜索树节点最小距离,栈,JavaScript,详细注释
  247. LeetCode题解:938. 二叉搜索树的范围和,递归,JavaScript,详细注释
  248. LeetCode题解:938. 二叉搜索树的范围和,栈,JavaScript,详细注释
  249. LeetCode题解:938. 二叉搜索树的范围和,DFS,详细注释
  250. LeetCode题解:1237. 找出给定方程的正整数解,枚举,详细注释
  251. LeetCode题解:1237. 找出给定方程的正整数解,二分查找,详细注释
  252. LeetCode题解:1237. 找出给定方程的正整数解,双指针,详细注释
  253. LeetCode:240. 搜索二维矩阵 II,直接查找,详细注释
  254. LeetCode:240. 搜索二维矩阵 II,二分查找,详细注释
  255. LeetCode题解:2347. 最好的扑克手牌,哈希表,详细注释
  256. LeetCode题解:938. 二叉搜索树的范围和,BFS,JavaScript,详细注释
  257. LeetCode题解:633. 平方数之和,枚举,JavaScript,详细注释
  258. LeetCode题解:2357. 使数组中所有元素都等于零,排序,详细注释
  259. LeetCode题解:2357. 使数组中所有元素都等于零,哈希表,详细注释
  260. LeetCode题解:1238. 循环码排列,归纳法,详细注释
  261. LeetCode题解:2363. 合并相似的物品,双指针,详细注释
  262. LeetCode题解:2363. 合并相似的物品,哈希表,详细注释
  263. LeetCode题解:2373. 矩阵中的局部最大值,遍历,详细注释
  264. LeetCode题解:137. 只出现一次的数字 II,哈希表,JavaScript,详细注释
  265. LeetCode题解:137. 只出现一次的数字 II,排序后搜索,JavaScript,详细注释
  266. LeetCode题解:136. 只出现一次的数字,哈希表,JavaScript,详细注释
  267. LeetCode题解:136. 只出现一次的数字,排序后搜索,JavaScript,详细注释
  268. LeetCode 2648. 生成斐波那契数列,迭代+递归,超详细解析
  269. LeetCode:2665. 计数器 II,闭包详解
  270. LeetCode:2695. 包装数组,详细解释
  271. LeetCode题解:2618. 检查是否是类的对象实例,使用instanceof
  272. LeetCode题解:2618. 检查是否是类的对象实例,迭代和递归
  273. LeetCode题解:剑指 Offer 56 - I. 数组中数字出现的次数,哈希表,JavaScript,详细注释
  274. LeetCode题解:617. 合并二叉树,JavaScript,详细注释
  275. LeetCode题解:2625. 扁平化嵌套数组,递归
  276. LeetCode题解:2631. 分组
  277. LeetCode题解:7. 整数反转,数组反转,JavaScript,详细注释
  278. LeetCode题解:7. 整数反转,迭代,JavaScript,详细注释
  279. LeetCode题解:1720. 解码异或后的数组,异或,JavaScript,详细注释
  280. LeetCode题解:1486. 数组异或操作,模拟,JavaScript,详细注释
  281. LeetCode题解:剑指 Offer 03. 数组中重复的数字,原地置换,JavaScript,详细注释
  282. LeetCode题解:剑指 Offer 39. 数组中出现次数超过一半的数字,摩尔投票,JavaScript,详细注释
  283. LeetCode题解:993. 二叉树的堂兄弟节点,BFS,JavaScript,详细注释
  284. LeetCode题解:171. Excel 表列序号,哈希表,TypeScript,详细注释
  285. LeetCode题解:2. 两数相加,递归,JavaScript,详细注释
  286. LeetCode题解:13. 罗马数字转整数,哈希表,JavaScript,详细注释

每日一个小知识

  1. 【立哥】【每日一个小知识】写遗嘱把部分财产赠给第三者,遗嘱有效力吗?
  2. 【立哥】【每日一个小知识】企业家和管理者有什么区别?

Vue 入门系列目录

  1. Vue 教程 00:MVC、MVP、MVVM 模式的区别,服务端渲染与客户端渲染的区别
  2. Vue 教程 01:Vue 表达式与 v-bind 指令
  3. Vue 教程 02:v-model、v-text、v-html
  4. Vue 教程 03:Vue 事件、v-show、v-if 指令
  5. Vue 教程 04:v-for 指令
  6. Vue 教程 05:v-pre、v-cloak 指令
  7. Vue 教程 06:数据同步、双向绑定原理
  8. Vue 教程 07:事件修饰符
  9. Vue 教程 08:Computed 计算属性、Watch 监听属性
  10. Vue 教程 09:双向绑定对象中属性原理
  11. Vue 教程 10:使用 vue-router 实现路由和传参
  12. Vue 教程 11:利用 JS 实现路由跳转,路由监听和导航守卫
  13. Vue 教程 12:多视图
  14. Vue 教程 13:基于 Webpack 构建项目
  15. Vue 教程 14:配置子路由
  16. Vue 教程 15:Vue 组件
  17. Vue 教程 16:Vue 实例生命周期详解
  18. Vue 教程 17:组件间通信之一:通过组件实例通信
  19. Vue 教程 18:组件间通信之二:通过事件通信
  20. Vue 教程 19:Vue 2.0 组件开发模式
  21. Vue 教程 20:Vuex 入门
  22. Vue 教程 21:Vuex Getter
  23. Vue 教程 22:mapState、mapActions、mapGetters
  24. Vue 教程 23:Vuex 异步 Action
  25. Vue 教程 24:Vuex Modules(完结)

React Native 入门系列目录

  1. React Native 教程 01:简介及环境准备
  2. React Native 教程 02:基础组件和样式介绍(完结)

微信小程序入门系列目录

  1. 微信小程序教程 01:小程序简介
  2. 微信小程序教程 02:App(Object)和 Page(Object) 构造器介绍
  3. 微信小程序教程 03:WXML 语法
  4. 微信小程序教程 04:API(完结)

Node.js 入门系列目录

  1. Node.js 教程 01:Node.js 简介
  2. Node.js 教程 02:response.write
  3. Node.js 教程 03:File System
  4. Node.js 教程 04:使用 http 和 fs 模块实现一个简单的服务器
  5. Node.js 教程 05:HTTP 协议
  6. Node.js 教程 06:处理接收到的 GET 数据
  7. Node.js 教程 07:处理接收到的 POST 数据
  8. Node.js 教程 08:同时处理 GET、POST 请求
  9. Node.js 教程 09:实现一个带接口请求的简单服务器
  10. Node.js 教程 10:Node.js 的模块化
  11. Node.js 教程 11:assert(断言)模块
  12. Node.js 教程 12:path(路径)模块
  13. Node.js 教程 13:URL 模块
  14. Node.js 教程 14:querystring 模块
  15. Node.js 教程 15:net 模块初探
  16. Node.js 教程 16:POST 文件上传
  17. Node.js 教程 17:multiparty
  18. Node.js 教程 18:Ajax 跨域
  19. Node.js 教程 19:WebSocket 之一:使用 Socket.io 建立 WebSocket 应用
  20. Node.js 教程 20:WebSocket 之二:用原生实现 WebSocket 应用
  21. Node.js 教程 21:数据库入门
  22. Node.js 教程 22:使用 Node.js 操作数据库
  23. Node.js 教程 23:使用 async await 异步操作数据库
  24. Node.js 教程 24:Stream 流
  25. Node.js 教程 25:启动器
  26. Node.js 教程 26:Node.js 项目之一:创建目录,环境配置,连接数据库
  27. Node.js 教程 27:Node.js 项目之二:实现路由
  28. Nodejs 教程 28:Node.js 项目之三:实现服务器
  29. Nodejs 教程 29:Node.js 项目之四:添加路由,完成项目
  30. Nodejs 教程 30(完结):PM2 入门

Express 入门系列目录

  1. Express 教程 01:创建服务器、配置路由
  2. Express 教程 02:使用中间件处理静态文件和数据请求
  3. Express 教程 03:自己实现一个 body-parser 中间件
  4. Express 教程 04:处理文件上传
  5. Express 教程 05:Cookie
  6. Express 教程 06:Session(完结)

rc-form 源码解读

  1. rc-form 源码解读

怎样成为快速阅读的高手

  1. 怎样成为快速阅读的高手(上)
  2. 怎样成为快速阅读的高手(中)
  3. 怎样成为快速阅读的高手(下)

如何成为有效学习的高手

  1. 如何成为有效学习的高手:1. 找到适合你的学习方法
  2. 如何成为有效学习的高手:2. 不谈兴趣,用任务驱动学习
  3. 如何成为有效学习的高手:3. 拖延症的“确诊与治疗”
  4. 如何成为有效学习的高手:4. 在衣食住行上训练专注力
  5. 如何成为有效学习的高手:5. 直奔大师,不必从基础开始
  6. 如何成为有效学习的高手:6. 给自己制造反馈
  7. 如何成为有效学习的高手:7. 怎样突破学习瓶颈
  8. 如何成为有效学习的高手:8. 思维导图

如何设计一个优秀的组件

  1. 如何设计一个优秀的组件

零碎的记录系列目录

  1. 解决火狐新窗口打开网页被拦截问题

所思所想系列目录

  1. 如何清除团队中的“害群之马”?(上篇)
  2. 如何清除团队中的“害群之马”?(下篇)
  3. 补硒真的能防癌吗?
  4. 如何保护孩子的牙齿健康
  5. 生活是多维的
  6. 中年危机也许只是个幻觉
  7. 如何识别刷屏文章中的伪科学
  8. 癌症筛查清单

勘误及提问

如果有疑问或者发现错误,可以在相应的 issues 进行提问或勘误。

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

blog's People

Contributors

chencl1986 avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

React Native教程02:基础组件和样式介绍(完结)

本教程适合已有React开发经验的朋友阅读

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

可以通过npm run android或npm run ios启动并在模拟器查看效果。

React Native基础组件

示例代码:/lesson01/App.js

  1. View:View在React Native中的作用类似于Web中的div标签,是最基础的组件。
  2. Text:Text在React Native中用来显示文本,其作用类似于Web中的span标签,但它并不是inline元素。
    值得注意的是,在React Native中所有的文本都必须放在Text中,而不能单独显示。
  3. Image:Image类似于Web端的img标签,可在source属性中设置引用图片的路径。在React Native中,Image组件不支持onPress(点击)事件。
<View>
  <Image
    source={require('/react-native/img/favicon.png')}
  />
  <Image
    source={{ uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png' }}
  />
  <Image
    source={{ uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==' }}
  />
</View>
  1. ScrollView:在React Native中,内容如果超出一定高度,是不会像Web端一样自动触发滚动条,而是需要将要滚动的内容放在ScrollView组件中,内容高度超出ScrollView时就会触发滚动。需要注意的是,ScrollView必须通过style设定一个高度。
    若是需要渲染长列表,如一个商品列表,则可以使用FlatList组件,因为ScrollView会将列表组件一次性渲染出,这样容易造成过多内存占用,而FlatList则只会渲染将要出现在屏幕中的元素。
    虽然FlatList在使用上更加复杂,但为了提升性能,我们应该优先使用ScrollView。
  2. Button:在React Native中,Button调用的是原生的按钮,它的缺点是只支持少量的定制。
    如color,在Android中,它修改的是背景色,在iOS中,它修改的是字体颜色。
    在开发中,若需要用到Button,在GitHub上搜索是一个好选择:https://github.com/search?utf8=%E2%9C%93&q=react+native+button&ref=simplesearch

React Native样式介绍

示例代码:/lesson01/App.js

  1. React Native中的样式写法与CSS类似,支持直接用style属性写在组件上,而且因为是运行在原生环境中,所以仅支持部分CSS样式。
  2. React Native也支持类似于类名的写法,但React Native不支持.css文件,因此样式需要写在const styles = StyleSheet.create({})中,其属性名就作为类名使用。使用时也写在组件的style属性中,<View style={styles.container}></View>。
  3. 布局方式:标签没有inline类型,全部都相当于block类型。支持flex布局和position(enum('absolute', 'relative')),但不支持浮动。布局方式:标签没有inline类型,全部都相当于block类型。支持flex布局和position(enum('absolute', 'relative')),但不支持浮动。
  4. 支持的单位:数字(与设备像素密度无关的逻辑像素点,相当于PX)和百分比。
    为了适配各尺寸屏幕,可以通过设定基础屏幕宽度,如750,与当前屏幕宽度换算出当前实际需要的尺寸。
// 引入Dimensions,用于获取设备的尺寸
import { Dimensions } from 'react-native';

// 设置基础宽度为750
const BASE_WIDTH=750;

export function calc(size){
  // 获取当前窗口宽度,支持参数为window和screen
  let { width } = Dimensions.get('window');

  // 换算出当前需要显示的尺寸
  return size * width / BASE_WIDTH;
}

Vue教程16:Vue实例生命周期详解

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

钩子Hook的概念

钩子的概念并不止存在于浏览器或者Vue实例,原意是用于拦截或监听系统的操作,类似于键盘输入等。
Vue实例生命周期中的钩子,主要用来监听Vue实例的运行阶段,并提供给使用者在不同阶段运行代码的机会。
VueRouter的路由守卫也是一种钩子,它除了监听还提供了拦截的功能。

挂载的概念

document.getElementById('id').innerHTML = '<div>挂载</div>'
这个语句其实就是一个挂载操作,它的意思就是把内容输出到页面中。

Vue实例生命周期图示详解

Vue实例生命周期

Vue教程12:多视图

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

多视图

代码示例:/lesson12/01. vue-router 多视图.html

在之前的例子中,路由的组件配置都是用component,如果改为components,就可以支持在一个页面中显示多视图。

在router-view中添加name属性,该视图会显示对应components中同名属性的组件。

JavaScript:

const headerCmp={ // 组件必须有父级标签,不可以直接写入文本
  template: '<div>头部</div>'
}

const footerCmp={
  template: '<div>底部</div>'
}

const footerCmp2={
  template: '<div>底部</div>'
}

const newsCmp={
  template: '<div>新闻</div>'
}

const userCmp={
  template: '<div>用户</div>'
}

const indexCmp={
  template: '<div>首页</div>'
}

// 路由表
const router = new VueRouter({
  routes: [
    {
      path: '/', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      components: { // 通过components属性显示多个组件
        default: indexCmp,  // 默认视图,对应<router-view></router-view>
        header: headerCmp,  // 命名视图,对应<router-view name="header"></router-view>
        footer: footerCmp
      },
    },
    {
      path: '/news',
      name: 'news',
      components: {
        default: newsCmp,
        header: headerCmp,
        footer: footerCmp2
      }
    }
  ]
})

let vm = new Vue({
  el: '#app',
  data: {

  },
  // 将路由添加到Vue中
  router,
  methods: {
    fn1() {
      // 通过路由名称跳转,配置params参数。
      this.$router.replace({ name: 'index', params: { id: Math.random() } });
    },
    fn2() {
      // 直接跳转路由地址,参数直接带在路径中。
      this.$router.push(`/news/${Math.random()}`);
    },
    fn3() {
      // 通过路由地址进行跳转,配置query参数。
      this.$router.push({ path: '/user', query: { userId: 321 } });
    },
    fn4() {
      console.log(this.$router)
      this.$router.go(1)
    },
    fn5() {
      this.$router.forward()
    },
    fn6() {
      this.$router.go(-1)
    },
    fn7() {
      this.$router.back()
    },
  }
})

HTML:

<div id="app">
  <router-link class="nav" to="/">首页</router-link>
  <router-link class="nav" to="/news">新闻</router-link>
  <!-- 多个路由容器 -->
  <!-- name属性的值对应路由配置中components中的属性名 -->
  <router-view name="header"></router-view>
  <router-view></router-view>
  <router-view name="footer"></router-view>
</div>

Vue教程01:Vue表达式与v-bind指令

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

创建Vue项目

代码参考/lesson01/01. Vue表达式.html

  1. 创建一个HTML文件,将Vue.js引入。
  2. HTML中需要有一个id为app的标签。
  3. 在script标签中实例化一个Vue对象。
let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: 'lee',
    age: 18
  }
})
  1. el属性称作根元素或挂载点,Vuejs项目的所有内容都会渲染到这个标签内部。
  2. data属性中的值为项目的数据。

这样我们就创建好了一个最基本的Vue项目。

使用Vue表达式向页面中插入值

代码参考/lesson01/01. Vue表达式.html

使用Vue表达式{{}},就可以将数据插入到页面中的相应位置。

<div id="app">
  <!-- 使用Vue表达式插入值 -->
  姓名:{{name}}<br/>
  年龄:{{age}}
</div>

此时可以尝试在控制台修改数据,如vm.age++,就可以看到页面现实的值跟着改变。

在Vue表达式中,支持书写简单的表达式,如

出生日期:{{new Date().getFullYear() - age}}

对于一些复杂的表达式,可以使用methods实现:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: 'lee',
    age: 18
  },
  methods: {
    getBirth() {
      return new Date().getFullYear() - this.age
    },
  }
})

再将方法用Vue表达式插入到页面中:

出生日期:{{this.getBirth()}}

v-bind指令(directive)

代码参考/lesson01/02. v-bind指令.html

通过v-bind指令向标签中插入属性。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: 'lee',
    age: 18
  }
})

HTML:

<div id="app">
  <!-- 通过v-bind指令插入属性,可以简写为:title="age + '岁'" -->
  <span v-bind:title="age + '岁'">{{name}}</span>
</div>
  1. class与style属性的特殊写法
    class和style属性的值除了支持字符串,还支持对象、数组形式。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: 'lee',
    age: 18,
    classStr: 'class1 class2 class3',
    classArr: ['class1', 'class2', 'class3'],
    classObj: { // 为false的属性将不会被渲染到标签中
      class1: true,
      class2: true,
      class3: false
    },
    styleStr: 'display: block; height: 20px; background-color: red;',
    styleObj: {
      display: 'block',
      height: '20px',
      backgroundColor: 'blue'
    },
    styleObj2: {
      width: '200px',
      margin: '0 auto'
    }
  }
})

HTML:

<div :class="classStr"></div>
<div :class="classArr"></div>
<div :class="classObj"></div>
<div :style="styleStr"></div>
<div :style="styleObj"></div>
<div :style="[styleObj, styleObj2]"></div>

Nodejs教程08:同时处理GET、POST请求

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

同时处理GET/POST请求

通常在开发过程中,同一台服务器需要接收多种类型的请求,并区分不同接口,向客户端返回数据。

最常用的方式,就是对请求的方法、url进行区分判断,获取到每个请求的数据后,统一由一个回调函数进行处理。

如下示例代码,可以在/lesson08/form_get.html和/lesson08/form_post.html中,通过提交表单查看效果。

示例代码:/lesson08/server.js

const http = require('http')
const url = require('url')
const querystring = require('querystring')

const server = http.createServer((req, res) => {
  // 定义公共变量,存储请求方法、路径、数据
  const method = req.method
  let path = ''
  let get = {}
  let post = {}

  // 判断请求方法为GET还是POST,区分处理数据
  if (method === 'GET') {
    // 使用url.parse解析get数据
    const { pathname, query } = url.parse(req.url, true)

    path = pathname
    get = query

    complete()
  } else if (method === 'POST') {
    path = req.url
    let arr = []

    req.on('data', (buffer) => {
      // 获取POST请求的Buffer数据
      arr.push(buffer)
    })

    req.on('end', () => {
      // 将Buffer数据合并
      let buffer = Buffer.concat(arr)

      // 处理接收到的POST数据
      post = querystring.parse(buffer.toString())

      complete()
    })
  }

  // 在回调函数中统一处理解析后的数据
  function complete() {
    console.log(method, path, get, post)
  }
})

server.listen(8080)

Vue教程17:组件间通信之一:通过组件实例通信

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/查看效果

父级获取子级实例实现通信

代码示例:/lesson17/src/components/parent.js,/lesson17/src/components/child.js

在父组件引用子组件时,给子组件设置ref="child"属性,父组件就可以通过this.$refs.child获取到子组件实例。
此时父组件就可以通过直接修改子组件的属性this.$refs.child.num1++,或者调用子组件方法this.$refs.child.add(),实现子组件属性变化。

子级获取父级实例实现通信

代码示例:/lesson17/src/components/parent.js,/lesson17/src/components/child.js

父组件将实例this,通过props属性parent传递给子组件。
此时子组件就可以通过直接修改父组件的属性this.parent.num1++,或者调用子组件方法this.parent.add(),实现子组件属性变化。

parent组件代码:

export default Vue.component('parent', {
  data() {
    return {
      num1: 0,
      num2: 0,
    };
  },
  components: {
    Child
  },
  methods: {
    add() {
      this.num2++
    },
    addChild1() {
      this.$refs.child.num1++
    },
    addChild2() {
      this.$refs.child.add()
    },
  },
  template: `
    <div>
      <div>父级
      num1:{{num1}}
      num2:{{num2}}
      <br/><input type="button" value="子级num1 +1" @click="addChild1" /><br/><input type="button" value="子级num2 +1" @click="addChild2" /></div>
      <child ref="child" :parent="this"></child>
    </div>
  `
});

child组件代码:

export default Vue.component('parent', {
  props: ['parent'],
  data() {
    return {
      num1: 0,
      num2: 0,
    };
  },
  methods: {
    add() {
      this.num2++
    },
    addParent1() {
      this.parent.num1++
    },
    addParent2() {
      this.parent.add()
    },
  },
  template: `
    <div>
      子级
      num1:{{num1}}
      num2:{{num2}}
      <br/><input type="button" value="父级num1 +1" @click="addParent1" />
      <br/><input type="button" value="父级num2 +1" @click="addParent2" />
    </div>
  `
});

Vue教程20:Vuex入门

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/查看效果

Vuex简介

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex具有以下特点:

  1. 全局
    Vuex类似于一个全局变量,它在整个Vue应用中生效。
  2. 统一
    只要状态修改,都会自动通知到所有相关组件,不需要使用者主动推送。
  3. 单一
    在全局中只需要保存一份,即可在全局中使用。

Vuex解决了哪些问题:

  1. 数据跨组件共享,数据的传输不再受到父子级限制,可以在各级组件中任意获取。
  2. 防止数据被意外篡改,Vuex会记录数据被修改的详细信息,如修改状态的组件、时间、代码位置等,便于定位和追踪错误,同时也方便了调试和测试。

Vuex的状态管理示意图详解

Vuex的使用

代码示例:/lesson20/src/main.js

先使用vue-cli创建一个项目,之后使用npm install vuex --save安装Vuex,就可以在/src/main.js中配置Vuex:

// 1. vuex-引入
import Vuex from 'vuex'

// vue-cli自带的编译配置
Vue.config.productionTip = false

// 1. vuex-在Vue中使用Vuex,让Vuex中的操作挂载到Vue中。
Vue.use(Vuex)

// 3. vuex-声明store对象
const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production', // 严格模式:防止直接修改state,只能用Mutations操作,由于strict模式是通过对象深度匹配进行,生产模式打开会严重影响性能。
  state: {a: 12, b: 5}, // 核心:数据
  mutations: { // 定义Mutations
    
  },
  actions: { // 定义actions
    
  },
  getters: {}, // 类似于computed
  modules: {} // 将store拆分成多个命名空间,分开使用。
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store, // 将store挂载到Vue实例中。
  components: { App },
  template: '<App/>'
})

读取state的值

代码示例:/src/components/Index.vue

在Index.vue中可以通过$store.state.a读取到已定义的a的值。

<template>
  <div>
    <!-- 读取Vuex的state -->
    a: {{$store.state.a}}
    <Cmp1/>
    <Table :fields="fields" :datas="datas" :parent="this"/>
  </div>
</template>

修改state的值

接下来实现在Cmp1.vue组件中,点击按钮后修改state中的a的值。

代码示例:/src/components/Cmp1.vue

<template lang="html">
  <div class="">
    <input type="button" value="+5" @click="fn()">
  </div>
</template>

<script>
export default {
  name: 'cmp1',
  methods: {
    fn(){
      // this.$store.state.a+=5; // 在严格模式下,直接修改state可以成功,但会报错
      // this.$store.commit('add', 5);  // 直接触发一个Mutation其实也可行,且不会报错,但这其实违背了Vuex设计的初衷。
      this.$store.dispatch('add', 5);  // 触发一个action,实现数据修改。
    }
  }
}
</script>

<style lang="css" scoped>
</style>

在main.js中定义Actions和Mutations。

代码示例:/lesson20/src/main.js

const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production', // 严格模式:防止直接修改state,只能用Mutations操作,由于strict模式是通过对象深度匹配进行,生产模式打开会严重影响性能。
  state: {a: 12, b: 5}, // 核心:数据
  mutations: { // 定义Mutations,通过action触发并更新state,Vue Devtool可以监听到数据的修改情况。
    add (state, n) { // 第一个参数为旧state,第二个参数为action中commit传入的参数。
      state.a += n
    }
  },
  actions: { // 定义actions,actions被触发后,将数据提交给Mutations进行处理并更新state。
    add ({ commit }, n) { // 第一个参数为context对象,它不是store本身,可以通过context.commit提交一个Mutation。第二个参数为用于更新state的参数。
      commit('add', n)
    }
  },
  getters: {}, // 类似于computed
  modules: {} // 将store拆分成多个命名空间,分开使用。
})

Vue教程02:v-model、v-text、v-html

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

v-model指令

示例代码:/lesson2/01. v-model指令.html

v-model指令的作用是双向绑定数据,它只能用于输入组件,如input、textarea、select、radio等。
需要注意的是,通过v-model绑定的数据都为字符串。

实现一个简单的双向绑定。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: 'lee',
  }
})

HTML:

<div id="app">
  <input type="text" v-model="name">
  <p>{{name}}</p>
</div>

此时通过input修改数据,p标签中的数据也会跟着改变,如果通过vm.name直接修改数据,input和p标签中的数据也会改变,如此就实现了双向绑定。

v-model改变了通信方式

v-model让View层与Model层之间的通信不再需要通过Controller进行,这里的Controller指的是用户的代码,而不是Vue的底层代码。

v-text、v-html指令

示例代码:lesson02/02. v-text、v-html指令.html

v-text与{{}}效果相同,如果传入的是标签会直接转换为字符串输出。
v-html相当于innerHtml,会将传入的字符串转换成HTML输出。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    name: '<strong>lee</strong>',
  }
})

HTML:

<div id="app">
  <div v-text="name"></div>
  <div v-html="name"></div>
</div>

Nodejs教程02:response.write

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

response.write

示例代码:/lesson02/server.js

在教程01的Demo中,我们使用了http.createServer创建一个服务器,在它的回调函数中,会传入2个参数,分别为request(请求对象)和response(响应对象)。

通常使用response.write方法向前端返回数据,该方法可调用多次,返回的数据会被拼接到一起。

需要注意的是,必须调用response.end方法结束请求,否则前端会一直处于等待状态,response.end方法也可以用来向前端返回数据。

const server = http.createServer((request, response) => {
  response.write('a')
  response.write('b')
  response.write('c')
  response.end('d')
})

Nodejs教程05:HTTP协议

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

HTTP协议简介

协议名称 RFC编号 特点
HTTP 1.0 RFC-1945 非持久连接,每个请求完全独立,速度较慢
HTTP 1.1 RFC-2616 持久连接,连接成功之后保持一段时间(一般为30秒),期间若再次请求则不用重新连接
HTTPS RFC-2818 它的主要作用可以分为两种:一种是建立一个信息安全通道,保证数据传输的安全;另一种就是确认网站的真实性
HTTP 2.0 RFC-7540 异步连接多路复用;头部压缩;请求/响应管线化;多路复用请求;对请求划分优先级;压缩HTTP头;服务器推送流(即Server Push技术);

HTTP报文结构

报文(message)是网络中交换与传输的数据单元,即站点一次性要发送的数据块。报文包含了将要发送的完整的数据信息,其长短很不一致,长度不限且可变。

HTTP请求报文由三部分组成:请求行、请求头、请求体。


① 请求方法,最常用的是GET和POST,此外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。

② 请求的URL地址。

③ 协议名称及版本号。

④ HTTP的报文头,包含请求的属性,格式为“属性名:属性值”,最大不超过32K。

⑤ HTTP的报文体,用于传输请求参数、文件等,最大不超过2G。

以下是一幅更详细的HTTP报文结构图:

HTTP状态码分类

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

HTTP状态码列表

状态码 状态码英文名称 中文描述
100 Continue 继续。客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non - Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
400 Bad Request 客户端请求的语法错误,服务
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置 您所请求的资源无法
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time - out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前
411 Length Required 服务器无法处理客户端发送的不带Content - Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry - After的响应信息
414 Request - URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry - After头信息中
504 Gateway Time - out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理

Vue教程04:v-for指令

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

v-for循环数组、对象

代码参考:/lesson04/01. v-for指令.html

使用v-for="(item, index) in items"就可以将数组或对象中的数据循环输出。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    users: [
      { name: 'lee', password: '123456' },
      { name: 'zhangsan', password: '654321' },
      { name: 'lisi', password: '111111' },
    ],
    usersObj: {
      lee: '123456',
      zhangsan: '654321',
      lisi: '111111'
    }
  }
})

HTML:

<div id="app">
  <ul>
    <li v-for="(item, index) in users" :key="index">
      {{index}}.
      用户名:{{item.name}}
      密码:{{item.password}}
    </li>
  </ul>
  <ol>
    <li v-for="(value, key) in usersObj" :key="index">
      用户名:{{key}}
      密码:{{value}}
    </li>
  </ol>
</div>

v-for指令循环字符串、数字

代码参考:/lesson04/02. v-for指令循环字符串、数字.html

v-for指令除了常规循环数组、对象外,还可以循环字符串和数字,需要注意的是,循环数字时默认从1开始。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    str: 'Vuejs Tutorial'
  }
})

HTML:

<div id="app">
  <ul>
    <li v-for="(item, index) in str" :key="index">
      {{item}}
    </li>
  </ul>
  <ul>
    <li v-for="index in 10" :key="index">
      {{index}}
    </li>
  </ul>
</div>

Key属性

在v-for循环中,为循环的标签添加key属性是必须的。
添加key属性的原因要从虚拟DOM说起。通俗地说,虚拟DOM就是一个JSON,假设我们有一段这样的HTML:

<ul>
  <li>
    <span></span>
    <strong></strong>
  </li>
  <li>
    <span></span>
    <strong></strong>
  </li>
</ul>

Vue在执行时,会将这个结构解析成类似于这样的形式:

{
  tag: 'ul',
  children: [
    {
      tag: 'li',
      children: [
        {
          tag: 'span',
          tag: 'strong'
        }
      ]
    },
    {
      tag: 'li',
      children: [
        {
          tag: 'span',
          tag: 'strong'
        }
      ]
    }
  ]
}

在数据更新时,Vue会先在虚拟DOM中判断将要修改的内容,也可以在积累一定更新之后,再一次性渲染到页面中,这样可以减少页面渲染的次数,增加性能。

假设我们有一个数组的数据,在我们进行增删改查操作时,我们不希望进行每次操作的时候,将整个列表全部重新渲染,而Vue很难判断我们操作的是哪个item。

例如我们将items[1]替换成其他值,Vue很难判断它是否被替换。当然Vue内部会生成一个key值,但这个值与我们传入的数据无关,因此需要我们提供一个id进行判断。

同时为了保证数据的唯一性,key必须是唯一的,且不可改变。

Vue教程10:使用vue-router实现路由和传参

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

实现一个简单路由

代码示例:/lesson10 vue-router/01. vue-router 最基本的路由.html

在实例化VueRouter时,配置一个路由表,每个路由中配置相应的组件模板,就可以通过vue-router.html#/index地址访问页面时,在HTML的标签中,渲染出index的模板。

同时可以在HTML中用index标签插入一个跳转按钮,实际渲染结果是一个a标签,to属性是点击后跳转的地址。

router-link的to属性也可以绑定路由名称实现跳转。

router-link也是一个Vue组件,可以给他添加class等其他属性。

JavaScript:

// 路由表
const router = new VueRouter({
  routes: [
    {
      path: '/index', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      component: { // 路由显示的组件
        template: '<div>index</div>'  // 组件模板
      }
    },
    {
      path: '/news',
      name: 'news',
      component: {
        template: '<div>news</div>'
      }
    },
    {
      path: '/user',
      name: 'user',
      component: {
        template: '<div>user</div>'
      }
    },
  ]
})

let vm = new Vue({
  el: '#app',
  data: {
    
  },
  // 将路由添加到Vue中
  router
})

HTML:

<div id="app">
  跳转按钮,通过path跳转<br/>
  <router-link class="nav" to="/index">index</router-link>
  <router-link class="nav" to="/news">news</router-link>
  <router-link class="nav" to="/user">user</router-link><br />
  跳转按钮,通过name跳转<br />
  <router-link class="nav" :to="{ name: 'index' }">index</router-link>
  <router-link class="nav" :to="{ name: 'news' }">news</router-link>
  <router-link class="nav" :to="{ name: 'user' }">user</router-link><br />
  下面是页面内容<br />
  <!-- 路由的内容显示在router-view标签中 -->
  <router-view></router-view>
</div>

路由传参

代码示例:/lesson10/02. vue-router 路由传参.html

vue-router支持用params和query方式向普通路由和命名路由传参。在模板中可以通过$route.params和$route.query获取参数

JavaScript:

// 路由表
const router = new VueRouter({
  routes: [
    {
      path: '/index', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      component: { // 路由显示的组件
        template: '<div>index</div>'  // 组件模板
      }
    },
    {
      path: '/news/:id/', // 通过路由传参
      name: 'news',
      component: {
        template: '<div>新闻:{{$route.params.id}}</div>'
      }
    },
    {
      path: '/user',
      name: 'user',
      component: {
        template: '<div>用户:{{$route.query.userId}}</div>'
      }
    },
  ]
})

let vm = new Vue({
  el: '#app',
  data: {
    
  },
  // 将路由添加到Vue中
  router
})

HTML:

<div id="app">
  跳转按钮,通过path跳转<br/>
  <router-link class="nav" to="/index">index</router-link>
  <router-link class="nav" to="/news/123">news</router-link>
  <router-link class="nav" to="/user?userId=666">user</router-link><br />
  跳转按钮,通过name跳转<br />
  <router-link class="nav" :to="{ name: 'index' }">index</router-link>
  <router-link class="nav" :to="{ name: 'news', params: { id: 321 } }">news</router-link>
  <router-link class="nav" :to="{ name: 'user', query: { userId: 888 } }">user</router-link><br />
  下面是页面内容<br />
  <!-- 路由的内容显示在router-view标签中 -->
  <router-view></router-view>
</div>

Vue教程19:Vue 2.0组件开发模式

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/查看效果

Vue 2.0组件开发说明

之前的教程中使用的一直是Vue 1.0的组件写法,接下来的课程都会使用Vue 2.0的语法。
Vue 2.0项目开发都需要通过Webpack进行打包,此时就要通过相关loader进行处理,具体可以查看webpack.config.js中的配置。
所有的组件都是以vue作为后缀。

入口js文件说明

代码示例:/lesson17/src/index.js

index.js主要用来引入住入口组件App.vue,以及路由配置,其余组件都通过App组件引入。

import Vue from 'vue';
// 主入口组件
import App from './App.vue';
// 引入路由配置
import router from './routers';

let vm=new Vue({
  el: '#div1',
  data: {},
  components: {App},
  router, // 将路由表挂载到Vue实例,在组件中可以直接使用路由组件和功能
  template: `
    <App/>
  `
})

路由配置说明

代码示例:/lesson17/src/routers/index.js

使用vue-router完成路由配置,并将实例导出,提供给入口index.js中的Vue实例引用,这样在组件中就可以直接使用路由组件和方法。

import Vue from 'vue';
import Router from 'vue-router';

// 引入页面组件 
import Index from '@/index.vue';
import News from '@/news.vue';

// 全局使用Router
Vue.use(Router);

// 配置路由
export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: Index
    },
    {
      path: '/news',
      name: 'news',
      component: News
    }
  ]
})

App.vue组件说明

代码示例:/lesson17/src/App.vue

App.vue为项目的入口组件,可以通过它跳转到其他组件,或者通过它引入其他组件。

// 此处书写template模板,支持html等多种语言,等同于Vue.component中的template属性
<template lang="html">
  <div>
    <!-- 将组件写入模板,两种写法都兼容 -->
    <CmpTest />
    <cmp-test />
    <!-- 路由跳转链接,由于路由已经在入口index.js中挂载,组件中可以直接使用 -->
    <router-link :to="{ name: 'index', params: {} }">首页</router-link>
    <router-link :to="{ name: 'news', params: {} }">新闻</router-link>
    <router-view/>
  </div>
</template>

// 此处书写JavaScript代码,等同于Vue.component代码
<script>
// 引入其他组件
import CmpTest from './components/cmp.vue'

// 默认导出Vue组件
export default {
  name: 'app',  // name属性为组件提供了一个ID,调试时的报错信息将显示该名称
  data(){
    return {a: 12}
  },
  components: {
    CmpTest
  }
}
</script>

// 此处书写样式,支持css、less等其他语言
<style lang="css" scoped>
</style>

Vue教程22:mapState、mapActions、mapGetters

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/#/index查看效果

map映射函数

map映射函数 映射结果
mapState 将state映射到computed
mapActions 将actions映射到methods
mapGetters 将getters映射到computed

mapState的使用

代码示例:/lesson21/src/components/Index.vue

首先需要引入map函数:

import { mapState, mapActions, mapGetters } from 'vuex'

在computed中使用mapState:

computed: {
  ...mapState(['a', 'b']),
}

就可以代替这段代码:

computed: {
  a() {
    return this.$store.state.a
  },
  b() {
    return this.$store.state.b
  },
}

mapActions的使用

代码示例:/lesson21/src/components/Index.vue

在methods中添加addA和addB的映射

methods: {
  ...mapActions(['addA', 'addB']),
},

等价于:

methods: {
  addA(n) {
    this.$store.dispatch('addA', n)
  },
  addB(n) {
    this.$store.dispatch('addA', n)
  },
}

mapGetters的使用

代码示例:/lesson21/src/components/Index.vue

在computed中添加count的映射:

computed: {
  ...mapGetters(['count'])
}

等价于:

computed: {
  count() {
    return this.$store.getters.count
  }
}

Vue教程11:利用JS实现路由跳转,路由监听和导航守卫

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

利用JS实现路由跳转

代码示例:/lesson10/01. vue-router 最基本的路由.html

使用this.$router.push方法可以实现路由跳转,this.$router.replace实现替换当前路由。
两个方法的第一个参数可为string类型的路径,或者可以通过对象将相应参数传入。

通过this.$router.go(n)方法可以实现路由的前进后退,n表示跳转的个数,正数表示前进,负数表示后退。

如果只想实现前进后退可以使用this.$router.forward()(前进一页),以及this.$router.back()(后退一页)。

JavaScript:

// 路由表
const router = new VueRouter({
  routes: [
    {
      path: '/index/:id', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      component: { // 路由显示的组件
        template: '<div>首页:{{$route.params.id}}</div>'  // 组件模板
      }
    },
    {
      path: '/news/:id/', // 通过路由传参
      name: 'news',
      component: {
        template: '<div>新闻:{{$route.params.id}}</div>'
      }
    },
    {
      path: '/user',
      name: 'user',
      component: {
        template: '<div>用户:{{$route.query.userId}}</div>'
      }
    },
  ]
})

let vm = new Vue({
  el: '#app',
  data: {

  },
  // 将路由添加到Vue中
  router,
  methods: {
    fn1() {
      // 通过路由名称跳转,配置params参数。
      this.$router.replace({ name: 'index', params: { id: Math.random() } });
    },
    fn2() {
      // 直接跳转路由地址,参数直接带在路径中。
      this.$router.push(`/news/${Math.random()}`);
    },
    fn3() {
      // 通过路由地址进行跳转,配置query参数。
      this.$router.push({ path: '/user', query: { userId: 321 } });
    },
    fn4() {
      console.log(this.$router)
      this.$router.go(1)
    },
    fn5() {
      this.$router.forward()
    },
    fn6() {
      this.$router.go(-1)
    },
    fn7() {
      this.$router.back()
    },
  }
})

HTML:

<div id="app">
  跳转按钮,通过JS跳转<br />
  <div class="links">
    <input type="button" value="跳转到首页" @click="fn1()">
    <input type="button" value="跳转到新闻" @click="fn2()">
    <input type="button" value="跳转到用户" @click="fn3()"><br />
    <input type="button" value="前进一页" @click="fn4()">
    <input type="button" value="前进一页" @click="fn5()">
    <input type="button" value="后退一页" @click="fn6()">
    <input type="button" value="后退一页" @click="fn7()">
  </div>
  下面是页面内容<br />
  <!-- 路由的内容显示在router-view标签中 -->
  <router-view></router-view>
</div>

通过watch实现路由监听

代码示例:/lesson10/02. vue-router 路由监听和守卫.html

通过watch属性设置监听$route变化,达到监听路由跳转的目的。

watch: {
  // 监听路由跳转。
  $route(newRoute, oldRoute) {
    console.log('watch', newRoute, oldRoute)
  },
},

导航守卫

代码示例:/lesson10/02. vue-router 路由监听和守卫.html

vue-router支持3种路由守卫,每个守卫参数中的next方法都必须被调用,否则无法进入下一步操作,会阻止路由的跳转,也可以在next方法中传入路由跳转参数string | object,将路由跳转到不同地址。

  1. 全局守卫
    router.beforeEach((to, from, next) => {})
    router.beforeResolve((to, from, next) => {})
    router.afterEach((to, from) => {})

  2. 路由守卫
    beforeEnter(to, from, next) {}

  3. 组件内守卫
    beforeRouteEnter(to, from, next) {}
    beforeRouteUpdate(to, from, next) {}
    beforeRouteLeave(to, from, next) {}

路由跳转时守卫的运行顺序如下:

  1. 进入一个新路由
    beforeEach => beforeEnter => beforeRouteEnter => beforeResolve => afterEach

  2. 当前路由下跳转,如替换路由参数
    beforeEach => beforeRouteUpdate => beforeResolve => afterEach

  3. 离开当前路由
    beforeRouteLeave => beforeEach => beforeResolve => afterEach

开发中可以通过不同的守卫处理逻辑。

Vue教程03:Vue事件、v-show、v-if指令

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

v-on指令

代码参考:lesson03/01. v-on指令.html

通过v-on指令添加事件,如v-on:click="onClick(1)",表示添加的是click事件,同时传入1作为参数。
v-on:click="onClick(1)"也可以简写为@click="onClick(1)"。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    num: 1
  },
  methods: {
    onClick(add) {
      this.num = this.num + add
    },
  }
})

HTML:

<div id="app">
  {{num}}
  <button v-on:click="onClick(1)">+1</button>
</div>

v-show、v-if指令

代码参考:/lesson03/02. v-show、v-if指令.html

v-show通过控制样式的display: none;和display: block;,实现显示隐藏。
v-if是直接添加和删除该元素,如果有的元素即使display: none;后还是会对页面效果有影响,建议使用v-if。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {
    show: true
  },
  methods: {
    onClick(add) {
      this.show = !this.show
    },
  }
})

HTML:

<div id="app">
  <button v-on:click="onClick()">显示隐藏</button>
  <div class="box" v-show="show"></div>
  <div class="box2" v-if="show"></div>
</div>

Vue教程00:MVC、MVP、MVVM模式的区别,服务端渲染与客户端渲染的区别

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

现代与传统开发模式的区别

现代开发模式 传统开发模式
Vue、React、Angular jQuery
只需花费20%时间在视图层 需花费80%时间在视图层
数据层与视图层自动绑定 数据与视图不分离

MVC模式介绍

MVC为Model(模型,同时也是数据)、View(视图)、Controller(控制)的缩写,它代表程序分为三层:

  • 最上层是View(视图层),即提供给用户的操作界面。
  • 中间层是Controller(控制层),它会根据用户从View(视图层)输入的指令,对Model(数据层)中相关的数据进行操作,产生最终结果。
  • 最底层是Model(数据层),它存储了程序运行所需的数据或信息。

MVC三层相互独立,每一层内部的工作并不影响其它层,各自提供对外接口,供上层调用。这样程序就实现模块化,各层进行修改都不会影响其它层的功能。

MVC、MVP、MVVM模式的区别

摘自阮一峰:MVC,MVP 和 MVVM 的图示

MVC MVP MVVM
分为3个部分:视图(View)用户界面、控制器(Controller)业务逻辑、模型(Model)数据保存。 MVP将Controller改名为Presenter,同时改变了通信方向。 MVVM模式将Presenter改名为ViewModel,基本上与MVP模式完全一致。
通信方式MVC模式通信方式 MVP模式通信方向 MVVM模式通信方向
MVC模式的通信是单向的:
1. View传送只领到Controller。
2. Controller完成业务逻辑后,要求Model改变状态。
3. Model将新的数据发送到View,用户得到反馈。
MVP模式的通信方式:
1. 各部分之间的通信是双向的。
2. View与Model不发生联系,都通过Presenter传递。
3. View非常薄,不部署任何业务逻辑,称为“被动式图”(Passive View),即没有任何主动性,而Presenter非常厚,所有逻辑都部署在这里。
MVVM模式与MVP模式的区别是,它采用双向绑定(data-binding):View的变动,自动反应在ViewModel,反之亦然。View、Angular和Ember都采用这种模式。

服务端渲染与客户端渲染的区别

服务端渲染 客户端渲染
由服务端将数据组合成html标签后,由前端展示,如普通HTML页面 服务端向前端传输数据,如JSON,由前端组装成html页面展示
如Pug、EJS等模板引擎 如Vue、React等框架
优点:1. 安全,因为服务端的内容对前端都不可见 2. 对SEO有利,由于搜索引擎只会读取html,不会执行JavaScript,因此客户端渲染的页面在搜索引擎看来只是个空白页面。 优点:1. 节省流量,数据量少 2. 用户体验好,可以不用刷新页面
一般需要安全性高的页面,比如注册、登录,会使用服务端渲染 安全性要求不高的页面,如商品页等,会使用客户端渲染

Nodejs教程14:querystring模块

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

querystring

querystring用来对url中的query字符串进行解析,常用的方法有querystring.parse和querystring.stringify。

querystring.parse

querystring.parse方法用于解析URL 查询字符串。

示例代码:/lesson14/querystring.js

代码如下:

const querystring = require('querystring')

console.log(querystring.parse('foo=bar&abc=xyz&abc=123'))

解析结果为:

{ foo: 'bar', abc: [ 'xyz', '123' ] }

querystring.stringify

querystring.stringify用于将对象转换为URL查询字符串。

示例代码:/lesson14/querystring.js

代码如下:

console.log(querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }))

解析结果为:

foo=bar&baz=qux&baz=quux&corge=

微信小程序教程04:API(完结)

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该教程示例代码:/lesson04/pages/index/index.js、/lesson04/pages/index/index.wxml

wx.showLoading

wx.showLoading用来显示一个loading提示框,但它不提供duration设置,需主动调用 wx.hideLoading 才能关闭提示框,可以通过title属性显示loading文字。

wx.showLoading({
  title: 'loading'
})
setTimeout(function () {
  wx.hideLoading()
}, 2000)

wx.showToast

wx.showToast方法用来显示一个提示框,title属性可指定显示内容,icon属性指定显示的图标类型,为none时即不显示图标,duration属性用来设置提示框显示的时长。

  wx.showToast({
    title: 'toast',
    icon: 'none',
    duration: 1000
  })

wx.showActionSheet

wx.showActionSheet方法用来显示操作菜单,在itemList属性中指定按钮文案,最大长度为6。

wx.showActionSheet方法有3种回调方式,分别为success(选中菜单时)、fail(点击取消按钮和背景色)、complete(弹窗收起时),都会传入结果参数,如{errMsg: "showActionSheet:ok", tapIndex: 0}或{errMsg: "showActionSheet:ok", tapIndex: 0}。

wx.showShareMenu

wx.showShareMenu方法用于显示当前页的转发按钮,它接受一个withShareTicket参数,为true时会生成一个分享id,可以在小程序后台追踪分享结果。

  wx.showShareMenu({
    withShareTicket: true
  })

wx.getUserInfo

wx.getUserInfo用于获取用户信息,可以在success回调中获取用户信息。

wx.getUserInfo({
  success(res) {
    console.log(res)
  }
})

wx.login

wx.login方法与wx.getUserInfo方法不同,它的success回调数据中的code属性是用户的token,如{errMsg: "login:ok", code: "081qfzoj2LluFC0feOlj2gQboj2qfzo5"}。

token每次请求返回都不同,需要将token传到服务端,由服务端通过code2Session接口获取到用户的session_key、openid和unionid,其中是否返回unionid需要看用户的设置。

code2Session请求地址为:

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

其中需要的APPID和SECRET,可以在小程序的开发设置页面中找到。

code2Session的返回值中,session_key主要用来验证用户身份,请求用户的信息,用户每次登录时,session_key的值都不同。

openid是仅在当前小程序中唯一的,当前用户的id,也就是说openid在其他小程序中是不同的。

unionid指的是开发者的多个公众号和小程序等应用中,对同一个用户的唯一标识。https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

wx.scanCode

wx.scanCode方法是用来调用微信的扫码功能,可用scanType属性指定扫码类型,在success回调中接收扫码结果。

wx.scanCode({
  scanType: ['barCode', 'qrCode'],
  success(res) {
    console.log(res)  // {errMsg: "scanCode:ok", result: "google", scanType: "QR_CODE", charSet: "UTF-8"}
  }
})

wx.setKeepScreenOn

wx.setKeepScreenOn方法用来使屏幕保持常量状态,可将keepScreenOn参数设置为true,开启保持常量。

wx.setKeepScreenOn({
  setKeepScreenOn: true
})

wx.setScreenBrightness

wx.setScreenBrightness可以设置屏幕亮度,参数value属性由0~1表示亮度,
0最暗,1最亮。

wx.setScreenBrightness({
  value: 1
})

wx.getBatteryInfo

wx.getBatteryInfo可用来获取手机点亮,它还有一个同步版本wx.getBatteryInfoSync,在success回调中可以获取数据。

wx.getBatteryInfo({
  success(res) {
    console.log(res)  // {isCharging: false, level: 38, errMsg: "getBatteryInfo:ok"}
  }
})

wx.connectWifi

wx.connectWifi用来链接WIFI,需要传入WIFI的SSID和password。

wx.connectWifi({
  SSID: '',
  password: '',
  success(res) {
    console.log(res.errMsg)
  }
})

wx.startSoterAuthentication

wx.startSoterAuthentication可用来调用微信的生物认证。可在requestAuthModes中传入认证方式,目前仅支持fingerPrint指纹识别。challenge挑战因子属性用于校验调用者身份,将作为 resultJSON 的一部分返回给调用者。

wx.startSoterAuthentication({
  requestAuthModes: ['fingerPrint'],
  challenge: '123456',
  authContent: '请用指纹解锁',
  success(res) {
    wx.showToast({
      title: JSON.stringify(res),
      icon: 'none'
    })
  },
  fail(res) {
    wx.showToast({
      title: JSON.stringify(res),
      icon: 'none',
      duration: 100000
    })
  },
})

wx.downloadFile

wx.downloadFile将会由客户端直接发起一个 HTTPS GET 请求,可通过url属性指定一个下载地址,通过filePath指定下载后的存储路径。

wx.downloadFile({
  url: 'https://cn.bing.com/sa/simg/hpc26i_2x.png',
  filePath: 'hpc26i_2x.png',
  success(res) {
    toast(JSON.stringify(res))
  },
  fail(res) {
    toast(JSON.stringify(res))
  },
})

wx.startBluetoothDevicesDiscovery

wx.startBluetoothDevicesDiscovery方法用来搜索附近的蓝牙设备,在services属性中可以设置要搜索的设备类型。

由于该方法比较消耗系统资源,搜索并链接成功后可以调用wx.stopBluetoothDevicesDiscovery关闭搜索。

wx.startBluetoothDevicesDiscovery({
  success(res) {
    toast(JSON.stringify(res))
    wx.stopBluetoothDevicesDiscovery()
  },
  fail(res) {
    toast(JSON.stringify(res))
  },
})

wx.request

wx.request用于网络请求,注意需要将请求的域名设置为合法域名,否则请求会被拦截,在测试时可以在开发工具中选择不校验合法域名。

wx.request({
  url: 'https://cn.bing.com/search',
  data: { q: 'abc', safe: 'off' },
  method: 'get',
  dataType: 'text',
  success(res) {
    toast(JSON.stringify(res))
  },
  fail(res) {
    toast(JSON.stringify(res))
  }
})

结束语

至此,微信小程序教程完结,在我看来,小程序更像是Vue+React+React Native的混合体,有Vue或React相关开发经验的话入门很简单。

但小程序基于微信平台,能够实现传统Web开发不具备的很多功能,而且能够在微信生态圈内发展,作为前端是不能不掌握的重要技能。

Nodejs教程06:处理接收到的GET数据

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

常用请求方式

GET和POST是最常用的HTTP请求方法,除此之外还有DELETE、HEAD、OPTIONS、PUT、TRACE等,但都很少用到。

GET POST
主要用途是获取数据 主要用途是发送数据
数据放在HTTP请求Header中,通过URL进行传输,容量≤32K 数据放在HTTP请求Body中,容量大,通常上限是2G

处理GET数据

我们可以使用Nodejs自带的url和querystring模块处理接收到的GET数据。

首先新建一个带form表单的HTML文件,讲输入的数据提交到服务器地址:

示例代码:/lesson06/form_get.html

<form action="http://localhost:8080/login" method="get">
  用户:<input type="text" name="username"><br/>
  密码:<input type="text" name="password"><br/>
  <input type="submit" value="提交">
</form>

服务端在接收到请求数据时,可以有3种方式处理数据:

示例代码:/lesson06/server.js

  1. 将请求数据中的req.url进行字符串切割,再用querystring模块获取数据。
const [ pathname, queryStr ] = req.url.split('?')
const query = querystring.parse(queryStr)
console.log(pathname, query)
  1. 用URL构造函数实例化一个url对象,从中获取到pathname和search值,再用querystring模块解析search数据。
const url = new URL(`http://localhost:8080${req.url}`)
const { pathname, search } = url
const query = querystring.parse(search.substring(1, url.search.length))
console.log(pathname, query)
  1. 使用url模块的parse方法,直接解析出数据。
// parse方法第二个参数若传true,则会直接将解析出的query值转为对象形式,否则它只是字符串形式
const { pathname, query } = url.parse(req.url, true)
console.log(pathname, query)

Nodejs教程17:multiparty

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

multiparty

上一节虽然完成了完整的文件上传流程,但实际工作中不可能自己从头开发所有功能,这样效率很低。

我们可以尝试使用第三方库来完成POST请求的处理,如multiparty

multiparty demo

通过如下例子,可以测试一下multiparty的功能。

它会在field事件中,将数据信息的字段名和值返回。在file事件中,将文件的字段名和信息返回。

上传成功后,会在指定的文件夹创建一个上传的文件,并会将文件重命名(如:IqUHkFe0u2h2TsiBztjKxoBR.jpg),以防止重名。

若上传出现失败,已保存的文件会自动删除。

close事件表示表单数据全部解析完成,用户可以在其中处理已经接收到的信息。

示例代码:/lesson17/server.js

const http = require('http')
const multiparty = require('multiparty')

const server = http.createServer((req, res) => {
  const form = new multiparty.Form({
    uploadDir: './upload' // 指定文件存储目录
  })

  form.parse(req) // 将请求参数传入,multiparty会进行相应处理

  form.on('field', (name, value) => { // 接收到数据参数时,触发field事件
    console.log(name, value)
  })

  form.on('file', (name, file, ...rest) => { // 接收到文件参数时,触发file事件
    console.log(name, file)
  })

  form.on('close', () => {  // 表单数据解析完成,触发close事件
    console.log('表单数据解析完成')
  })
})

server.listen(8080)

Vue教程07:事件修饰符

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

事件修饰符

代码示例:/lesson07/01. 事件修饰符.html

通过事件修饰符可以实现为事件添加event.preventDefault()、event.stopPropagation()等操作。
事件修饰符介绍如下:

事件修饰符 作用
.stop 阻止冒泡
.prevent 阻止默认事件
.capture 使用事件捕获
.self 只监听直接触发该元素的事件
.once 事件只触发一次
.passive 该修饰符会忽略event.preventDefault()
.native 监听组件内部根元素的原生事件,而不是自定义事件
.left 鼠标左键点击事件
.right 鼠标右键点击事件
.middle 鼠标中键点击事件
.{keyCode keyAlias}

普通修饰符的例子:
JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {},
  methods: {
    fn1() {
      alert('fn1')
    },
    fn2() {
      alert('fn2')
    },
    submit() {
      alert('submit')
    },
  }
})

HTML:

<div id="app">
  <div v-on:click="fn1()">
    <form action="" v-on:submit.prevent="submit()">
      <input type="submit" value="按钮" v-on:click.prevent.stop.once="fn2()">
    </form>
  </div>

按键修饰符

代码示例:/lesson07/02. 按键修饰符.html

JavaScript:

let vm = new Vue({
  el: '#app',
  data: {},
  methods: {
    fn1() {
      alert('回车')
    },
    fn2() {
      alert('ctrl+回车')
    },
  }
})
<div>
  <!-- 按下回车键时弹窗,也可以写成v-on:keydown.13 -->
  <input type="text" v-on:keydown.enter="fn1()">
  <!-- 按键修饰符也支持组合 -->
  <input type="text" v-on:keydown.ctrl.enter="fn2()">
</div>

Nodejs教程09:实现一个带接口请求的简单服务器

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

带接口请求的简单服务器需求

虽然当前还未涉及到数据库的知识,但已经可以通过文件读写,实现一个简单的服务器,需求如下:

  1. 用户通过GET方法请求/reg接口,实现注册流程。
  2. 用户通过POST方法请求/login接口,实现登录流程。
  3. 非接口请求则直接返回相应文件。

实现思路

  1. 新建users.json文件,用于存放用户列表数据。
  2. 新建index.html文件,实现表单及注册、登录的前端请求功能。
  3. 服务端判断请求路径,决定是前端是通过接口校验用户数据,还是请求HTML文件。
  4. 若是接口请求,则通过用户列表判断用户状态,实现注册和登录流程。

代码及示例

进入/lesson09文件夹,运行node server.js命令,在浏览器访问http://localhost:8080/index.html即可查看效果。

index.html代码如下:

示例代码:/lesson09/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  用户:<input type="text" name="username" id="username"><br/>
  密码:<input type="text" name="password" id="password"><br/>
  <input type="button" value="注册" id="reg">
  <input type="button" value="登录" id="login">
  <script>
    // 注册
    document.querySelector('#reg').addEventListener('click', async function () {
      const response = await fetch(`/reg?username=${document.querySelector('#username').value}&password=${document.querySelector('#password').value}`)
      const result = await response.json()
      console.log(result)
      alert(result.msg)
    })

    // 登录
    document.querySelector('#login').addEventListener('click', async function () {
      const response = await fetch(`/login`, {
        method: 'POST',
        body: JSON.stringify({
          username: document.querySelector('#username').value,
          password: document.querySelector('#password').value
        })
      })
      const result = await response.json()
      console.log(result)
      alert(result.msg)
    })
  </script>
</body>
</html>

server.js代码如下:

示例代码:/lesson09/server.js

const http = require('http')
const url = require('url')
const fs = require('fs')
const querystring = require('querystring')

const server = http.createServer((req, res) => {
  // 定义公共变量,存储请求方法、路径、数据
  const method = req.method
  let path = ''
  let get = {}
  let post = {}

  // 判断请求方法为GET还是POST,区分处理数据
  if (method === 'GET') {
    // 使用url.parse解析get数据
    const { pathname, query } = url.parse(req.url, true)

    path = pathname
    get = query

    complete()
  } else if (method === 'POST') {
    path = req.url
    let arr = []

    req.on('data', (buffer) => {
      // 获取POST请求的Buffer数据
      arr.push(buffer)
    })

    req.on('end', () => {
      // 将Buffer数据合并
      let buffer = Buffer.concat(arr)

      // 处理接收到的POST数据
      post = JSON.parse(buffer.toString())

      complete()
    })
  }

  // 在回调函数中统一处理解析后的数据
  function complete() {
    try {
      if (path === '/reg') {
        // 获取get请求数据
        const {
          username,
          password
        } = get

        // 读取user.json文件
        fs.readFile('./users.json', (error, data) => {
          if (error) {
            res.writeHead(404)
          } else {
            // 读取用户数据
            const users = JSON.parse(data.toString())
            const usernameIndex = users.findIndex((item) => {
              return username === item.username
            })

            // 判断用户名是否存在
            if (usernameIndex >= 0) {
              res.write(JSON.stringify({
                error: 1,
                msg: '此用户名已存在'
              }))
              res.end()
            } else {
              // 用户名不存在则在用户列表中增加一个用户
              users.push({
                username,
                password
              })

              // 将新的用户列表保存到user.json文件中
              fs.writeFile('./users.json', JSON.stringify(users), (error) => {
                if (error) {
                  res.writeHead(404)
                } else {
                  res.write(JSON.stringify({
                    error: 0,
                    msg: '注册成功'
                  }))
                }
                res.end()
              })
            }
          }
        })
      } else if (path === '/login') {
        const {
          username,
          password
        } = post

        // 读取users.json
        fs.readFile('./users.json', (error, data) => {
          if (error) {
            res.writeHead(404)
          } else {
            // 获取user列表数据
            const users = JSON.parse(data.toString())
            const usernameIndex = users.findIndex((item) => {
              return username === item.username
            })

            if (usernameIndex >= 0) {
              // 用户名存在,则校验密码是否正确
              if (users[usernameIndex].password === password) {
                res.write(JSON.stringify({
                  error: 0,
                  msg: '登录成功'
                }))
              } else {
                res.write(JSON.stringify({
                  error: 1,
                  msg: '密码错误'
                }))
              }
            } else {
              res.write(JSON.stringify({
                error: 1,
                msg: '该用户不存在'
              }))
            }
          }
          res.end()
        })
      } else {
        // 若不是注册或登录接口,则直接返回相应文件
        fs.readFile(`.${path}`, (error, data) => {
          if (error) {
            res.writeHead(404)
          } else {
            res.write(data)
          }
          res.end()
        })
      }
    } catch (error) {
      console.error(error);
    }
  }
})

server.listen(8080)

Vue教程13:基于Webpack构建项目

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

代码示例:/lesson13/

创建Vue项目的文件夹结构

├── package.json
├── webpack.config.js # Webpack配置文件
├── index.html # 项目HTML文件
├── css # 项目样式文件夹
├── dest # 项目打包后输出文件夹
├── src # 项目开发文件夹
│ ├── vm.js # 项目入口文件
│ ├── router.js # 路由配置文件
│ ├── components # 组件文件夹

Webpack基本配置

const path=require('path');

module.exports={
  mode: 'development',  // 开发模式
  entry: './src/vm.js', // 入口文件配置
  output: {
    path: path.resolve(__dirname, 'dest'),  // 输出文件夹
    filename: 'bundle.min.js' // 打包输出的文件名
  },
  module: {
    rules: [
      { // 处理CSS
        test: /\.css$/i, 
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

HTML文件

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="app">
      <!-- name属性的值对应路由配置中components中的属性名 -->
      <router-view name="header"></router-view>
      <!-- 渲染默认视图 -->
      <router-view></router-view>
    </div>
  </body>
  <!-- 引用打包后的js文件 -->
  <script src="dest/bundle.min.js" charset="utf-8"></script>
</html>

入口js文件vm.js

// 引入Vue的es module模块,使用vue-cli时,因为配置了alias,所以可以直接引用vue
import Vue from 'vue/dist/vue.esm';
// 引入VueRouter模块
import VueRouter from 'vue-router';

// 引入路由配置
import router from './router';

// 引入项目样式表
import '../css/main.css';

// 安装VueRouter插件
Vue.use(VueRouter);

// 配置Vue应用
const vm=new Vue({
  el: '#app',
  data: {},
  router
});

路由配置router.js

// 引入VueRouter模块
import VueRouter from 'vue-router';

// 引入组件
import Header from './components/header';
import Home from './components/home';
import News from './components/news1';

export default new VueRouter({
  routes: [
    {
      path: '/index', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      components: { // 通过components属性显示多个组件
        header: Header,  // 命名视图,对应<router-view name="header"></router-view>
        default: Home  // 默认视图,对应<router-view></router-view>
      }
    },
    {
      path: '/news',
      name: 'news',
      components: {
        header: Header,
        default: News
      }
    }
  ]
})

编译项目

在命令行使用webpack命令就可以进行编译,在浏览器中打开index.html就可以查看效果。

Nodejs教程12:path(路径)模块

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

path(路径)

path模块主要用来对文件路径进行处理,比如提取路径、后缀,拼接路径等。

path的使用

接下来通过一些例子熟悉一下path的使用:

代码示例:/lesson12/path.js

const path = require('path')

const str = '/root/a/b/1.txt'

console.log(path.dirname(str))  // 获取文件目录:/root/a/b
console.log(path.basename(str)) // 获取文件名:1.txt
console.log(path.extname(str)) // 获取文件后缀:.txt
console.log(path.resolve(str, '../c', 'build', 'strict')) // 将路径解析为绝对路径:C:\root\a\b\c\build\strict
console.log(path.resolve(str, '../c', 'build', 'strict', '../..', 'assets')) // 将路径解析为绝对路径:C:\root\a\b\c\assets
console.log(path.resolve(__dirname, 'build')) // 将路径解析为绝对路径:C:\projects\nodejs-tutorial\lesson12\build

值得一提的是path.resolve方法,它可以接收任意个参数,然后根据每个路径参数之间的关系,将路径最终解析为一个绝对路径。

__dirname指的是当前模块所在的绝对路径名称,它的值会自动根据当前的绝对路径变化,等同于path.dirname(__filename)的结果。

Vue教程06:数据同步、双向绑定原理

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

利用Proxy实现数据同步

代码示例:/lesson06/01. 数据同步

利用Proxy拦截可以对数据的修改,得知数据被修改时,将模板中相应属性的部分替换,就完成了简单的数据同步功能。

const el = document.querySelector('#app')

// 获取标签内容作为页面模板
let template = el.innerHTML

// _data为初始化
let _data = {
  name: 'lee',
  age: 18
}

// 为_data设置拦截,通过修改data中属性的值,来修改
let data = new Proxy(_data, {
  // 当数据修改时,会被set方法拦截,从而得知数据被修改的值value,之后可以将value渲染到页面中,obj为_data
  set(obj, key, value) {
    console.log(`设置${key}属性为${value}`)
    obj[key] = value

    // 将数据渲染到页面中
    render()
  }
})

// 初始化时渲染页面
render()

function render() {
  // 将模板中{{}}内部的内容,用数据替换
  el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
    str = str.substring(2, str.length - 2);

    return _data[str];
  })
}

HTML:

<div id="app">
  姓名:{{name}}<br/>
  年龄:{{age}}
</div>

数据双向绑定

在data改变时,查找页面中含有相应v-model属性的input标签,将input的value值改为data的值。
同时在input标签的值变化时,将input的值设置为data的值,就实现了双向绑定。

JavaScript:

const el = document.querySelector('#app')

// 获取标签内容作为页面模板
let template = el.innerHTML

// _data为初始化
let _data = {
  name: 'lee',
  age: 18
}

// 为_data设置拦截,通过修改data中属性的值,来修改
let data = new Proxy(_data, {
  // 当数据修改时,会被set方法拦截,从而得知数据被修改的值value,之后可以将value渲染到页面中,obj为_data
  set(obj, key, value) {
    console.log(`设置${key}属性为${value}`)
    obj[key] = value

    // 将数据渲染到页面中
    render()
  }
})

// 初始化时渲染页面
render()

function render() {
  // 将模板中{{}}内部的内容,用数据替换
  el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
    str = str.substring(2, str.length - 2);

    return _data[str];
  })

  // 但检测到数据改变时,将input的值同步
  Array.from(document.getElementsByTagName('input'))
    // 查找含有v-model属性,即设置了双向绑定的input
    .filter((ele) => ele.getAttribute('v-model'))
    .forEach((input, index) => {
      const name = input.getAttribute('v-model')
      input.value = _data.name

      // 输入框的值变化时,将data中相应属性的值改变
      input.oninput = function () {
        data[name] = input.value
      }
    })
}

HTML:

<div id="app">
  <input type="text" v-model="name"><br />
  姓名:{{name}}<br/>
  年龄:{{age}}
</div>

Nodejs教程16:POST文件上传

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

简单的文件上传例子

处理文件上传数据,也是前后端交互中重要的功能,它的处理方式与数据不同。

接下来,通过一个例子查看服务端接收到的文件上传数据。

首先,在post_file.html中,新建一个用与上传文件的表单:

form的属性enctype="multipart/form-data"代表表单上传的是文件。

enctype的默认值为enctype="application/x-www-form-urlencoded"表示上传的是数据类型,此时服务端接收到的数据为“username=lee&password=123456&file=upload.txt”。

代码示例:/lesson16/post_file.html

<form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data">
  用户:<input type="text" name="username" value="lee"><br/>
  密码:<input type="password" name="password" value="123456"><br/>
  <input type="file" name="file" id=""><br/>
  <input type="submit" value="提交">
</form>

其次,在server.js中,查看接收到的表单提交数据:

代码示例:/lesson16/server.js

const http = require('http')

const server = http.createServer((req, res) => {
  let arr = []

  req.on('data', (buffer) => {
    arr.push(buffer)
  })

  req.on('end', () => {
    let buffer = Buffer.concat(arr)

    console.log(buffer.toString())
  })
})

server.listen(8080)

最后,在表单中上传/lesson16/upload.txt文件,并查看打印出的结果:

------WebKitFormBoundaryL5AGcit70yhKB92Y
Content-Disposition: form-data; name="username"

lee
------WebKitFormBoundaryL5AGcit70yhKB92Y
Content-Disposition: form-data; name="password"

123456
Content-Disposition: form-data; name="file"; filename="upload.txt"
Content-Type: text/plain

upload
------WebKitFormBoundaryL5AGcit70yhKB92Y--

文件上传数据分析

通过分析上面这个例子中,服务端接收到的数据,可以得到以下信息:

  1. 表单上传的数据,被分隔符“------WebKitFormBoundaryL5AGcit70yhKB92Y”隔开,分隔符在每次上传时都不同。分隔符数据可以从req.headers['content-type']中获取,如:const boundary = '--' + req.headers['content-type'].split('; ')[1].split('=')[1]
  2. 前两段数据中,分别可以获取到表单上传的字段名name="username",以及数据“lee”。
  3. 第三段数据中,多了一个字段filename="upload.txt",它表示的是文件的原始名称。以及可以获取到文件类型“Content-Type: text/plain”,表示这是一个文本文件。最后是文件的内容“upload”。

由此可以看出,文件上传数据虽然有些乱,但还是有规律的,那么处理思路就是按照规律,将数据切割之后,取出其中有用的部分。

文件上传数据简化

先回顾一下上面的数据,并将回车符标记出来:

------WebKitFormBoundaryL5AGcit70yhKB92Y\r\n
Content-Disposition: form-data; name="username"\r\n
\r\n
lee\r\n
------WebKitFormBoundaryL5AGcit70yhKB92Y\r\n
Content-Disposition: form-data; name="password"\r\n
\r\n
123456\r\n
Content-Disposition: form-data; name="file"; filename="upload.txt"\r\n
Content-Type: text/plain\r\n
\r\n
upload\r\n
------WebKitFormBoundaryL5AGcit70yhKB92Y--

可以看出,每段数据的结构其实是这样的:

------WebKitFormBoundaryL5AGcit70yhKB92Y\r\nContent-Disposition: form-data; name="username"\r\n\r\nlee\r\n

将每段上传数据简化如下:

<分隔符>\r\n字段头\r\n\r\n内容\r\n

也就是说,整个表单的数据,就是按照这样的数据格式组装而成。

需要注意的是,在表单数据的结尾不再是\r\n,而是“--”。

文件上传数据处理步骤

  1. 用<分隔符>切分数据:
[
  ‘’,
  "\r\n字段信息\r\n\r\n内容\r\n",
  "\r\n字段信息\r\n\r\n内容\r\n",
  "\r\n字段信息\r\n\r\n内容\r\n",
  '--'
]
  1. 删除数组头尾数据:
[
  "\r\n字段信息\r\n\r\n内容\r\n",
  "\r\n字段信息\r\n\r\n内容\r\n",
  "\r\n字段信息\r\n\r\n内容\r\n",
]
  1. 将每一项数据头尾的的\r\n删除:
[
  "字段信息\r\n\r\n内容",
  "字段信息\r\n\r\n内容",
  "字段信息\r\n\r\n内容",
]
  1. 将每一项数据中间的\r\n\r\n删除,得到最终结果:
[
	"字段信息", "内容",
	"字段信息", "内容",
	"字段信息", "内容",
]

Buffer的数据处理

由于文件都是二进制数据,不能直接将其转换为字符串后再进行处理,否则数据会出错,因此要通过Buffer模块进行数据处理操作。

Buffer模块提供了indexOf方法获取Buffer数据中,其参数所在位置的index值。

Buffer模块提供了slice方法,可通过index值切分Buffer数据。

先测试一下这两个方法:

示例代码:/lesson16/buffer.js

let buffer = Buffer.from('lee\r\nchen\r\ntest')

const index = buffer.indexOf('\r\n')

console.log(index)
console.log(buffer.slice(0, index).toString())

可以看到打印结果分别为3和"lee",也就是说,我们先找到了"\r\n"所在的index为3,之后从Buffer数据的index为0的位置,切割到index为3的位置,得到了正确的结果。

由此,可以封装一个专门用于切割Buffer数据的方法:

示例代码:/lesson16/bufferSplit.js

module.exports = function bufferSplit(buffer, separator) {
  let result = [];
  let index = 0;

  while ((index = buffer.indexOf(separator)) != -1) {
    result.push(buffer.slice(0, index));
    buffer = buffer.slice(index + separator.length);
  }
  result.push(buffer);

  return result;
}

有了bufferSplit方法,就可以正式开始处理数据了。

文件上传数据处理

根据上面的思路,就可以实现一个完整的文件上传流程。

代码示例:/lesson16/server.js

const http = require('http')
const fs = require('fs')
const bufferSplit = require('./bufferSplit')

const server = http.createServer((req, res) => {
  const boundary = `--${req.headers['content-type'].split('; ')[1].split('=')[1]}`  // 获取分隔符
  let arr = []

  req.on('data', (buffer) => {
    arr.push(buffer)
  })

  req.on('end', () => {
    const buffer = Buffer.concat(arr)
    console.log(buffer.toString())

    // 1. 用<分隔符>切分数据
    let result = bufferSplit(buffer, boundary)
    console.log(result.map(item => item.toString()))

    // 2. 删除数组头尾数据
    result.pop()
    result.shift()
    console.log(result.map(item => item.toString()))

    // 3. 将每一项数据头尾的的\r\n删除
    result = result.map(item => item.slice(2, item.length - 2))
    console.log(result.map(item => item.toString()))

    // 4. 将每一项数据中间的\r\n\r\n删除,得到最终结果
    result.forEach(item => {
      console.log(bufferSplit(item, '\r\n\r\n').map(item => item.toString()))

      let [info, data] = bufferSplit(item, '\r\n\r\n')  // 数据中含有文件信息,保持为Buffer类型

      info = info.toString()  // info为字段信息,这是字符串类型数据,直接转换成字符串,若为文件信息,则数据中含有一个回车符\r\n,可以据此判断数据为文件还是为普通数据。

      if (info.indexOf('\r\n') >= 0) {  // 若为文件信息,则将Buffer转为文件保存
        // 获取字段名
        let infoResult = info.split('\r\n')[0].split('; ')
        let name = infoResult[1].split('=')[1]
        name = name.substring(1, name.length - 1)

        // 获取文件名
        let filename = infoResult[2].split('=')[1]
        filename = filename.substring(1, filename.length - 1)
        console.log(name)
        console.log(filename)

        // 将文件存储到服务器
        fs.writeFile(`./upload/${filename}`, data, err => {
          if (err) {
            console.log(err)
          } else {
            console.log('文件上传成功')
          }
        })
      } else {  // 若为数据,则直接获取字段名称和值
        let name = info.split('; ')[1].split('=')[1]
        name = name.substring(1, name.length - 1)
        const value = data.toString()
        console.log(name, value)
      }
    })
  })
})

server.listen(8080)

微信小程序教程02:App(Object)和Page(Object) 构造器介绍

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

在/app.js中,有方法App,它的作用是注册整个小程序的应用,其中可以传入一些配置,或者存储全局状态。

App(Object) 构造器生命周期

属性 类型 描述
onLaunch Function 在小程序初始化时触发,全局仅触发一次
onShow Function 小程序显示时触发,如小程序从悬浮窗显示到前台
onHide Function 小程序隐藏时触发,如小程序收起到悬浮窗
onError Function 出现错误时触发,
onPageNotFound Function 打开不存在页面时触发
其他 Any 可以为全局添加任意参数,在页面中通过const app = getApp()获取应用实例后,即可通过app[propName]访问,也可以通过app[propName]修改它的值,修改后也在全局有效

Page(Object) 构造器生命周期

属性 类型 描述
data Object 页面数据,类似于Vue组件中的data,可以通过this.data[propName]访问
onLoad Function 页面已加载,类似于Vue的created
onShow Function 页面显示的触发
onReady Function 页面初次渲染完成,类似于Vue的mounted
onHide Function 页面隐藏的触发
onUnload Function 页面销毁时触发
onPullDownRefresh Function 下拉刷新时触发
onReachBottom Function 页面上拉触底触发
onShareAppMessage Function 点击右上角分享按钮触发,但只能监听到用户点击,无法获取用户是否成功分享,通常的做法是点击之后,延迟一段时间就判断为已分享
onPageScroll Function 页面滚动触发,类似于window.onscroll
onResize Function 页面尺寸变化时触发,例如手机从竖屏变味横屏
onTabItemTap Function 当前页为tab页时,点击tab时触发
其他 Any 可以为该页面添加任意参数,通过this[propName]可以访问

Nodejs教程15:net模块初探

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

net模块

net 模块用于创建基于流的 TCP 或 IPC 的服务器(net.createServer())与客户端(net.createConnection())。

net模块是专门用于网络通信的模块,若当前的数据交互不通过HTTP协议,就可以使用net模块,如WebSocket。

HTTP协议本质上是以文本形式传输数据,它的传输数据量较大,而且它的传输需要二进制和文本之间进行转换和解析。

在nodejs中,HTTP模块是继承自net模块的。

OSI参考模型

OSI(Open System Interconnect),即开放式系统互联。 它是ISO(国际标准化组织)组织在1985年研究的网络互联模型。该体系结构标准定义了网络互联的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层和应用层),即OSI开放系统互连参考模型。在这一框架下进一步详细规定了每一层的功能,以实现开放系统环境中的互连性、互操作性和应用的可移植性。

OSI参考模型分层

OSI参考模型可以分为以下七层:

物理层 → 数据链路层 → 网络层(IP协议) → 传输层(TCP层) → 会话层 → 表现层 → 应用层(HTTP协议等)

OSI参考模型详细介绍如下:

第1层物理层:处于OSI参考模型的最底层。物理层的主要功能是利用物理传输介质为数据链路层提供物理连接,以便透明的传送比特流。常用设备有(各种物理设备)网卡、集线器、中继器、调制解调器、网线、双绞线、同轴电缆。

第2层数据链路层:在此层将数据分帧,并处理流控制。屏蔽物理层,为网络层提供一个数据链路的连接,在一条有可能出差错的物理连接上,进行几乎无差错的数据传输(差错控制)。本层指定拓扑结构并提供硬件寻址。常用设备有网桥、交换机;

第3层网络层:本层通过寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。它包括通过互连网络来路由和中继数据 ;除了选择路由之外,网络层还负责建立和维护连接,控制网络上的拥塞以及在必要的时候生成计费信息。

第4层传输层:—常规数据递送-面向连接或无连接。为会话层用户提供一个端到端的可靠、透明和优化的数据传输服务机制。包括全双工或半双工、流控制和错误恢复服务;传输层把消息分成若干个分组,并在接收端对它们进行重组。不同的分组可以通过不同的连接传送到主机。这样既能获得较高的带宽,又不影响会话层。在建立连接时传输层可以请求服务质量,该服务质量指定可接受的误码率、延迟量、安全性等参数,还可以实现基于端到端的流量控制功能。

第5层会话层:在两个节点之间建立端连接。为端系统的应用程序之间提供了对话控制机制。此服务包括建立连接是以全双工还是以半双工的方式进行设置,尽管可以在层4中处理双工方式 ;会话层管理登入和注销过程。它具体管理两个用户和进程之间的对话。如果在某一时刻只允许一个用户执行一项特定的操作,会话层协议就会管理这些操作,如阻止两个用户同时更新数据库中的同一组数据。

第6层表示层:主要用于处理两个通信系统中交换信息的表示方式。为上层用户解决用户信息的语法问题。它包括数据格式交换、数据加密与解密、数据压缩与终端类型的转换。

第7层应用层:OSI中的最高层。为特定类型的网络应用提供了访问OSI环境的手段。应用层确定进程之间通信的性质,以满足用户的需要。应用层不仅要提供应用进程所需要的信息交换和远程操作,而且还要作为应用进程的用户代理,来完成一些为进行信息交换所必需的功能。它包括:文件传送访问和管理FTAM、虚拟终端VT、事务处理TP、远程数据库访问RDA、制造报文规范MMS、目录服务DS等协议;应用层能与应用程序界面沟通,以达到展示给用户的目的。 在此常见的协议有:HTTP,HTTPS,FTP,TELNET,SSH,SMTP,POP3等。

OSI参考模型各层功能

(1)物理层(Physical Layer)

物理层是OSI参考模型的最低层,它利用传输介质为数据链路层提供物理连接。

它主要关心的是通过物理链路从一个节点向另一个节点传送比特流,物理链路可能是铜线、卫星、微波或其他的通讯媒介。

它关心的问题有:多少伏电压代表1?多少伏电压代表0?时钟速率是多少?采用全双工还是半双工传输?总的来说物理层关心的是链路的机械、电气、功能和规程特性。

(2)数据链路层(Data Link Layer)

数据链路层是为网络层提供服务的,解决两个相邻结点之间的通信问题,传送的协议数据单元称为数据帧。

数据帧中包含物理地址(又称MAC地址)、控制码、数据及校验码等信息。该层的主要作用是通过校验、确认和反馈重发等手段,将不可靠的物理链路转换成对网络层来说无差错的数据链路。

此外,数据链路层还要协调收发双方的数据传输速率,即进行流量控制,以防止接收方因来不及处理发送方来的高速数据而导致缓冲器溢出及线路阻塞。

(3)网络层(Network Layer)

网络层是为传输层提供服务的,传送的协议数据单元称为数据包或分组。

该层的主要作用是解决如何使数据包通过各结点传送的问题,即通过路径选择算法(路由)将数据包送到目的地。

另外,为避免通信子网中出现过多的数据包而造成网络阻塞,需要对流入的数据包数量进行控制(拥塞控制)。

当数据包要跨越多个通信子网才能到达目的地时,还要解决网际互连的问题。

(4)传输层(Transport Layer)

传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。

该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。

传输层传送的协议数据单元称为段或报文。

(5)会话层(Session Layer)

会话层主要功能是管理和协调不同主机上各种进程之间的通信(对话),即负责建立、管理和终止应用程序之间的会话。会话层得名的原因是它很类似于两个实体间的会话概念。例如,一个交互的用户会话以登录到计算机开始,以注销结束。

(6)表示层(Presentation Layer)

表示层处理流经结点的数据编码的表示方式问题,以保证一个系统应用层发出的信息可被另一系统的应用层读出。

如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据表示格式转换成网络通信中采用的标准表示形式。

数据压缩和加密也是表示层可提供的转换功能之一。

(7)应用层(Application Layer)

应用层是OSI参考模型的最高层,是用户与网络的接口。

该层通过应用程序来完成网络用户的应用需求,如文件传输、收发电子邮件等。

TCP

TCP完成了OSI参考模型中的第四层传输层的功能,net模块简单来说就是TCP协议的node实现。

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

当应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,TCP则把数据流分割成适当长度的报文段,最大传输段大小(MSS)通常受该计算机连接的网络的数据链路层的最大传送单元(MTU)限制。之后TCP把数据包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。

TCP为了保证报文传输的可靠 [1] ,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。

TCP的可靠性虽然很好,但它也因此牺牲了效率,它比较适合于传输文件等场景。如果在对可靠性要求不高,但对效率要求很高的场景,如视频直播等,就可以使用UDP。

UDP

UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。

UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,如音频、视频和普通数据在传送时使用UDP较多,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。

Nodejs也提供了UDP/Datagram模块,可以在需要使用UDP时调用。

参考资料

Net模块:https://nodejs.org/dist/latest-v11.x/docs/api/net.html

UDP/Datagram模块:https://nodejs.org/dist/latest-v11.x/docs/api/dgram.html

OSI参考模型:https://baike.baidu.com/item/OSI%E5%8F%82%E8%80%83%E6%A8%A1%E5%9E%8B

TCP:https://baike.baidu.com/item/TCP/33012

UDP:https://baike.baidu.com/item/UDP

Vue教程24:Vuex Modules(完结)

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/#/index查看效果

运行服务端请cd server,node server.js。

创建Vuex模块

我们可以新建两个Vuex模块,名为src/store/mod_a.js和src/store/mod_b.js。
每个Vuex模块都可以看做一个独立的store对象,里面可以有属于它自己的State、Actions、Mutations等等。

代码示例:/lesson24/src/store/mod_a.js

新建模块代码如下:

export default {
  state: {
    str: 'store_a'
  },
  mutations: {
    'mod_a.setStr': function (state, s){
      alert('a的setStr');
      state.str=s;
    }
  },
  actions: {
    'mod_a.setStr': function ({commit}, s){
      commit('mod_a.setStr', s);
    }
  }
}

在实例化Store对象时,可以引入模块。

import ModA from './mod_a'
import ModB from './mod_b'

同时将模块配置到Store中:

export default new Vuex.Store({
  modules: {
    mod_a: ModA,
    mod_b: ModB
  }
})

读取模块数据

代码示例:/lesson24/src/components/Index.vue

在组件中,就可以通过$store.state.mod_a.str读取到模块内的state。

a_str: {{$store.state.mod_a.str}}<br>
b_str: {{$store.state.mod_b.str}}<br>

当然更推荐的是使用mapState的方式读取,但是和直接读取Store下的值(...mapState(['a', 'b']))不同,读取模块中的State需要通过方法获取:

computed: {
  ...mapState({
    str_a: state=>state.mod_a.str,
    str_b: state=>state.mod_b.str,
  }),
}

这样就可以在template中通过str_a和str_b获取到模块的state。

a_str: {{str_a}}<br>
b_str: {{str_b}}<br>

触发一个Action

假设每个模块中都有一个名为setStr的Action,我们在运行this.$store.dispatch('setStr', 'test')时,所有模块中的同名Action都会被执行。

Mutation也具有同样的特点。但这不是Vuex的Bug,它的用意是让使用者能够通过一个Action同时更新多个模块的数据。

若需要回避这个问题,则可以给每个模块中的Action单独命名,通常我们会加上模块名作为前缀:

代码示例:/lesson24/src/store/mod_a.js

export default {
  state: {
    str: 'store_a'
  },
  mutations: {
    'mod_a.setStr': function (state, s){
      alert('a的setStr');
      state.str=s;
    }
  },
  actions: {
    'mod_a.setStr': function ({commit}, s){
      commit('mod_a.setStr', s);
    }
  }
}

在使用时,只需要分别mapActions:

代码示例:/lesson24/src/components/Index.vue

此时有两种方法可以mapActions:

  1. ...mapActions(['mod_a.setStr', 'mod_b.setStr'])。
    此时需要通过methods运行\this['mod_a.setStr'](str)来触发Action。此时需要通过methods运行\this['mod_a.setStr'](str)来触发Action。
    但在template中直接调用,如<input type="button" value="设置A" @click="'mod_b.setStr'">会报错。
  2. ...mapActions({ set_a: 'mod_a.setStr', set_b: 'mod_b.setStr' })。
    这种方法的好处是,由于已经替换了方法名,在template中可以直接调用,如<input type="button" value="设置A" @click="set_a('aaa')">

完整示例代码如下:

<template>
  <div>
    str: {{$store.state.str}}<br>
    a_str: {{$store.state.mod_a.str}}<br>
    b_str: {{$store.state.mod_b.str}}<br>
    a_str: {{str_a}}<br>
    b_str: {{str_b}}<br>
    <input type="button" value="设置A" @click="setA('aa')">
    <input type="button" value="设置B" @click="setB('bb')"><br/>
    <input type="button" value="设置A" @click="set_a('aaa')">
    <input type="button" value="设置B" @click="set_b('bbb')">
  </div>
</template>

<script>
import {mapState, mapActions, mapGetters} from 'vuex';

export default {
  name: 'Index',
  data () {
    return {
      
    }
  },
  async created(){
    await this.readUsers();
  },
  methods: {
    ...mapActions(['addA', 'addB', 'setOnline', 'readUsers', 'mod_a.setStr', 'mod_b.setStr']),
    ...mapActions({
      set_a: 'mod_a.setStr',
      set_b: 'mod_b.setStr'
    }),
    setA(str) {
      this['mod_a.setStr'](str)
    },
    setB(str) {
      this['mod_b.setStr'](str)
    },
  },
}
</script>

微信小程序教程03:WXML语法

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该教程示例代码:/lesson03/pages/index/index.js、/lesson03/pages/index/index.wxml

WXML介绍

WXML(WeiXin Markup Language)是属于微信视图层的一套标签语言,它与Vue的模板语法很相似。

数据绑定

与Vue语法一致,可以通过{{name}}将数据绑定到页面中,text标签默认为行内元素。

<!--index.wxml-->
<view>
  <text>{{name}}</text>
  <text>{{age}}</text>
  <view>{{name}}</view>
  <view>{{age}}</view>
</view>

列表渲染

列表渲染的属性,也需要写在{{}}中,如wx:for="{{arr}}",若写成wx:for="arr"会被当做字符串处理。

遍历出的item和index都是默认的,不需要像Vue一样写成item, index in arr。
值得注意的是,遍历的项目也需要提供一个key。

<view>
  <view wx:for="{{arr}}" wx:key="{{index}}">{{item}}</view>
</view>

条件渲染

通过wx:if、wx:elif、wx:else,分别可以实现if、else...if、else的条件渲染。

<view wx:if="{{judge}}">
  <text>judge为true时显示</text>
</view>
<view wx:else>
  <text >judge为false时显示</text>
</view>

模板

小程序也同样支持template,可以给template标签设置name属性,表示该模板的名称。

在使用时通过is属性选择相应名称的template进行调用,并可以通过data属性传入相应参数。

<!--wxml-->
<template name="staffName">
  <view>FirstName: {{firstName}}, LastName: {{lastName}}</view>
</template>

<template is="staffName" data="{{...staffA}}"></template>
<template is="staffName" data="{{...staffB}}"></template>
<template is="staffName" data="{{...staffC}}"></template>
// index.js
Page({
  data: {
    staffA: {firstName: 'Hulk', lastName: 'Hu'},
    staffB: {firstName: 'Shang', lastName: 'You'},
    staffC: {firstName: 'Gideon', lastName: 'Lin'}
  }
})

事件

为标签绑定事件,可以用bind + 事件名作为属性,方法名称不需要用{{}}绑定,与Vue不同,绑定的方法不支持传参。

由于小程序运行在移动端,绑定点击事件不能用bindclick,而需要用bindtap。

而在方法中获取数据需要用this.data[propName],修改数据需要用this.setData方法,这一点类似于React。

在Page(Object) 构造器中,绑定的方法不用像Vue一样写在methods属性里,与data属性平级就可以。

<!--wxml-->
<button type="primary" bindtap="onTap">
  修改条件渲染
</button>
onTap() {
  this.setData({
    judge: !this.data.judge
  })
}

scroll-view

scroll-view用来创建一个滚动视图,但需要设置scroll-y="{{true}}才可开启纵轴方向的滚动,同时需要通过wxss设置高度,超出高度才会触发滚动。

<scroll-view scroll-y="{{true}}" style="height: 100rpx;">
  <view wx:for="{{arr}}" wx:key="{{index}}">{{item}}</view>
</scroll-view>

icon

icon用来创建一个图标,它可以通过type属性使用微信的内置图标类型,如success, success_no_circle, info, warn, waiting, cancel, download, search, clear。

还可以通过size设置大小,color设置颜色,所有图标都是矢量图,不需要担心缩放问题。

若需要为图标插入文本,使用text标签即可。

<icon type="success" size="23" color="green" />
<text >成功</text>

navigator

navigator类似于HTML中的a标签,可以通过url属性设置跳转地址,仅支持当前小程序内的跳转。

<navigator url="/pages/login/login">跳转到login</navigator>

radio和checkbox

radio和checkbox都需要放在radio-group和checkbox-group标签中使用,前者用于实现单选功能,后者用于数据分组。

<form action="">
  <radio-group>
    <label wx:for="{{options}}" wx:for-item="item" wx:key="{{item.id}}">
      <radio value="{{item.value}}" /> {{item.name}}
    </label>
  </radio-group>
  <checkbox-group>
    <label wx:for="{{options}}" wx:for-item="item" wx:key="{{item.id}}">
      <checkbox value="{{item.value}}" /> {{item.name}}
    </label>
  </checkbox-group>
</form>

picker

picker可以创建一个选择器,默认的mode为普通选择器selector,它还支持多列选择器,时间选择器,日期选择器,省市区选择器。

picker的children可以是一个标签,点击children时会弹出选择器。

picker的range属性为必须,它可以绑定Array / Object Array,其值为选择器的选项,当其类型为Object Array时,需要用range-key属性指定选择器显示的内容。

picker的value属性为必须,绑定的是选中项目的index。

当选择器发生选择时,需要通过bindchange事件改变绑定的value值,bindchange的参数为event,其中event.detail = {value: value}。

<form >
  <picker mode="selector" range="{{options}}" value="{{pickerSelected}}" bindchange="onPickerChange">
    <view >{{pickerSelected}}</view>
  </picker>
</form>
onPickerChange(event) {
  this.setData({
    pickerSelected: event.detail.value
  })
}

Nodejs教程07:处理接收到的POST数据

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

处理POST数据

示例代码:/lesson07/server.js

POST数据量通常较大,通常不会一次性从客户端发送到服务端,具体每次发送的大小由协议,以及客户端与服务端之间的协商决定。

因此,Nodejs在处理POST数据时,需要通过request对象的data事件,获取每次传输的数据,并在end事件调用时,处理所有获取的数据。

request对象是一个http.IncomingMessage 类,而它实现了可读流接口,因此具有了可读流的data、end等事件。

需要注意的是,data事件中传入的参数是Buffer,Buffer只是一个二进制的数据,它有可能只是一段字符串数据,也有可能是文件的一部分,所以处理Buffer数据的时候要注意这一点。

const http = require('http')
const querystring = require('querystring')

const server = http.createServer((req, res) => {
  let bufferArray = []  // 用于存储data事件获取的Buffer数据。

  req.on('data', (buffer) => {
    bufferArray.push(buffer)  // 将Buffer数据存储在数组中。
  })

  req.on('end', () => {
    // Buffer 类是一个全局变量,使用时无需 require('buffer').Buffer。
    // Buffer.concat方法用于合并Buffer数组。
    const buffer = Buffer.concat(bufferArray)
    // 已知Buffer数据只是字符串,则可以直接用toString将其转换成字符串。
    const post = querystring.parse(buffer.toString())
    console.log(post)
  })
})

server.listen(8080)

Vue教程14:配置子路由

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

添加子路由

在/src/components/news.js文件中配置子路由,之后将子路由配置赋值给'/news'路由的children属性中。

编译后访问路径'/news/1'可以看到效果。

子路由配置:

代码示例:/lesson14/src/components/news.js

export const router = [
  {
    path: '1',
    name: 'news1',
    components: {
      news_header: NewsHeader,
      default: News1
    }
  },
  {
    path: '2',
    name: 'news2',
    components: {
      news_header: NewsHeader,
      default: News2
    }
  }
]

将配置赋值给children属性:

代码示例:/lesson14/src/router.js

import News, { router as news_router } from './components/news';

export default new VueRouter({
  routes: [
    {
      path: '/news',
      name: 'news',
      components: {
        header: Header,
        default: News
      },
      children: news_router
    }
  ]
})

Nodejs教程10:Nodejs的模块化

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

Nodejs定义模块

Nodejs的模块化由于出现的较早,因此它遵循的是CommonJS规范,而非ES6的模块化。

在Nodejs的模块化中,最常用到的有module对象、exports对象、require方法。

其中module和exports用于输出模块,require用于引用模块。

一个简单的模块例子

示例代码:/lesson10/module1.js、/lesson10/require.js

先新建一个module1.js文件,代码如下:

module.exports.a = 1
module.exports.b = 2

let c = 3

在require.js中,引入模块并打印:

const module1 = require('./module1')

console.log(module1)

可以看到打印结果:{ a: 1, b: 2 }。

这段代码的含义如下:

  1. module1.js对外输出了module.exports,module.exports为一个对象,它含有a和b属性。
  2. module1.js中虽然定义了变量c,但它只在module1.js这个模块中存在,从外部无法访问。
  3. 在require.js中引用module1.js,必须使用相对路径或绝对路径。
  4. 若引用时不带路径,而是直接使用模块名称,则会默认引用项目目录下的node_modules文件夹下的模块,如:
const module2 = require('module2')

console.log(module2)  // { a: 1, b: 2 }

若此时项目目录下的node_modules文件夹下存在module2.js文件,则会引用该文件。

若不存在,则会查找系统的node_modules文件夹下,即全局安装的模块,是否存在module2。

若还不存在该模块,则会报错。

通过require导入的模块,可以被任意命名,因此写成const a = require('module2')也是可以的。

module.exports

上面这个例子中的模块导出,还可以省略module,直接写成exports.a = 1; exports.b = 1; ...。

但直接使用exports导出,也仅支持这种写法,若写成:

exports = {
  a: 1,
  b: 2
}

在引用模块时,只能接收到{},也就是说exports只支持exports.a = 1;这样的语法。

如果要将整个模块直接定义为一个对象、函数、变量、类,则需要使用module.exports = 123。

如下:

示例代码:/lesson10/module3.js

module.exports = {
  a: 1,
  b: 2
}

module.exports = 123

module.exports = {
  a: 1,
  b: 2,
  c: 3
}

module.exports = function () {
  console.log('test')
}

module.exports = class {
  constructor(name) {
    this.name = name
  }

  show() {
    console.log(`Show ${this.name}`)
  }
}

module.exports可以让模块被赋值成任意类型,但需要注意的是此时module.exports类似于一个模块内的全局变量。

对它的重复赋值,只有最后的值有效,之前的值会直接被覆盖。

在这个例子中,module3.js模块最终导出为一个类。

因此,通常推荐使用module.exports,可以避免出错。

Vue教程18:组件间通信之二:通过事件通信

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/查看效果

通过$emit触发事件,通过$on接收事件,实现通信

代码示例:/lesson17/src/components/parent.js,/lesson17/src/components/child.js

通过调用组件实例的$emit(事件名, 参数),向组件发送一个事件。
在组件的生命周期created中,使用\this.$on(事件名, 回调函数),在回调函数中可以接收到参数,以此实现组件间通信。

父组件代码:

export default Vue.component('parent', {
  data() {
    return {
      num: 0,
    };
  },
  components: {
    Child
  },
  created() {
    this.$on('add', function (n) {
      this.num = this.num + n
    })
  },
  methods: {
    addChild() {
      this.$refs.child.$emit('add', 5)
    },
  },
  template: `
    <div>
      <div>父级
      num:{{num}}
      <br/><input type="button" value="子级num1 + 5" @click="addChild" />
      <child ref="child" :parent="this"></child>
    </div>
  `
});

子组件代码:

export default Vue.component('parent', {
  props: ['parent'],
  data() {
    return {
      num: 0,
    };
  },
  methods: {
    addParent() {
      this.parent.$emit('add', 5)
    },
  },
  created() {
    this.$on('add', function (n) {
      this.num = this.num + n
    })
  },
  template: `
    <div>
      子级
      num:{{num}}
      <br/><input type="button" value="父级num1 + 5" @click="addParent" />
    </div>
  `
});

Vue教程09:双向绑定对象中属性原理

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

以06课双向绑定为基础实现双向绑定对象属性

代码参考/lesson09/01. watch监听对象属性.html

在06课中,实现了对数据的监听,当然Proxy对象同时也可以监听对象类型的数据,我们需要做的只是将相应的变化渲染到页面中。

首先,我们先将_data中的值修改为

// 用_data保存数据
let _data = {
  userInfo: {
    name: 'lee',
    age: 18
  }
}

HTML修改为:

<div id="app">
  姓名:{{userInfo.name}}<br/>
  年龄:{{userInfo.age}}
</div>

因此需要将render方法中查找模板中要写入值的正则从

/\{\{\w+\}\}/g

替换为:

/\{\{[\w\.]+\}\}/g

这样就可以匹配到HTML模板中的{{userInfo.name}},但我们从对象中获取数据必须使用data["userInfo"]["name"],而不能直接用data[userInfo.name],因此接下来需要拼接出相应的格式查找到数据,就可以将数据渲染到页面中。

完整代码如下:

// 将模板中{{}}内部的内容,用数据替换
el.innerHTML = template.replace(/\{\{[\w\.]+\}\}/g, str => {
  str = str.substring(2, str.length - 2);

  // 将userInfo.name拼接为["userInfo"]["name"],以便查找对象中的属性。
  return eval('_data["' + str.split('.').join('"]["') + '"]')
})

这样一来我们就实现了将对象中的属性数据渲染到页面中。
当然同理,我们就可以实现对象中属性的双向绑定,完整代码如下:

JavaScript:

const el = document.querySelector('#app')

// 获取标签内容作为页面模板
let template = el.innerHTML

// 用_data保存数据
let _data = {
  userInfo: {
    name: 'lee',
    age: 18
  }
}

// 为_data设置拦截,通过修改data中属性的值,来修改
let data = new Proxy(_data, {
  // 当数据修改时,会被set方法拦截,从而得知数据被修改的值value,之后可以将value渲染到页面中,obj为_data
  set(obj, key, value) {
    console.log(`设置${key}属性为${value}`)

    eval('_data["' + key.split('.').join('"]["') + '"] = value')

    // 将数据渲染到页面中
    render()
  }
})

// 初始化时渲染页面
render()

function render() {
  // 将模板中{{}}内部的内容,用数据替换
  el.innerHTML = template.replace(/\{\{[\w\.]+\}\}/g, str => {
    str = str.substring(2, str.length - 2);

    // 将userInfo.name拼接为["userInfo"]["name"],以便查找对象中的属性。
    return eval('_data["' + str.split('.').join('"]["') + '"]')
  })

  // 但检测到数据改变时,将input的值同步
  Array.from(document.getElementsByTagName('input'))
    // 查找含有v-model属性,即设置了双向绑定的input
    .filter((ele) => ele.getAttribute('v-model'))
    .forEach((input, index) => {
      const name = input.getAttribute('v-model')
      eval('input.value = data["' + name.split('.').join('"]["') + '"]')

      // 输入框的值变化时,将data中相应属性的值改变
      input.oninput = function () {
        data[name] = input.value
        eval('data["' + name.split('.').join('"]["') + '"] = input.value')
      }
    })
}

HTML:

<div id="app">
  <input type="text" v-model="userInfo.name"><br />
  姓名:{{userInfo.name}}<br/>
  年龄:{{userInfo.age}}
</div>

Vue教程08:Computed计算属性、Watch监听属性

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

Computed计算属性

代码示例:/lesson08/01. Computed计算属性.html

计算属性类似于方法,用于输出数据的计算结果,在数据变化时,它会同步更新,计算属性不可与data中的属性重名。
相对于方法,它的优势是当它的依赖变化时,才会重新进行计算,也就是说它拥有缓存,而方法在每次render的时候都会计算,因此computed的性能较高。

计算属性除了设置为方法外,还可以用作对象,通过get、set方法进行读写操作。

计算属性还可以当做普通属性使用,通过v-model绑定在input上,而方法无法做到。

JavaScript:

let vm = new Vue({
  el: '#app',
  data: {
    a: 12,
    b: 33,
    familyName: '张',
    name: '三'
  },
  computed: {
    sum() {
      return this.a + this.b
    },
    fullName: {
      get() {
        return this.familyName + this.name
      },
      set(value) {
        this.familyName = value[0]
        this.name = value.substring(1)
      },
    }
  },
})

HTML:

<div id="app">
  <div>
    {{a}} + {{b}} = {{sum}}
    姓:<input type="text" v-model="familyName">
    名:<input type="text" v-model="name">
    姓名:<input type="text" v-model="fullName">
  </div>
</div>

Watch监听属性

代码示例:/lesson08/02. Watch监听属性.html

Watch监听属性可以监听数据的变化,不止可以监听某个变量,还可以监听对象中的属性,数组中的item。

let vm = new Vue({
  el: '#app',
  data: {
    name: 'lee',
    userInfo: {
      name: 'lee',
      age: 18
    },
    users: [
      'lee',
      'chen',
      'john'
    ]
  },
  watch: {
    name(value) {
      console.log(`name改变为${value}`)
    },
    // userInfo的属性修改不会触发该监听
    userInfo(value) {
      console.log('userInfo已改变', value)
    },
    // 可以监听对象的属性变化
    'userInfo.name': function(value) {
      console.log(`userInfo.name改变为${value}`)
    },
    // 可以监听数组中的某一项
    'users.1': function (value) {
      console.log(`users[1]改变为${value}`)
    },
    // 修改users[1]的值同时也会触发对数组的监听
    users(value) {
      console.log(`users改变为${value}`)
    },
  }
})

HTML:

<div id="app">
  <div>
    <input type="text" v-model="name">
    <input type="text" v-model="userInfo.name">
    <input type="text" v-model="users[1]">
  </div>
</div>

Nodejs教程03:File System

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

File System

File System是Nodejs中用来操作文件的库,可以通过const fs = require('fs')引用。

常用的方法有异步文件读取fs.readFile、异步文件写入fs.writeFile、同步文件读取fs.readFileSync、同步文件写入fs.writeFileSync。由于同步操作可能会造成阻塞,通常建议使用异步操作避免该问题。

fs.writeFile

示例代码:/lesson03/server.js

https://nodejs.org/dist/latest-v11.x/docs/api/fs.html#fs_fs_writefile_file_data_options_callback

fs.writeFile可向文件写入信息,若文件不存在会自动创建。

fs.writeFile('./test.txt', 'test', (error) => {
  if (error) {
    console.log('文件写入失败', error)
  } else {
    console.log('文件写入成功')
  }
})

fs.writeFile的主要参数:

  1. 第一个参数为写入的文件路径

  2. 第二个参数为写入内容(可为<string> | <Buffer> | <TypedArray> | <DataView>)

  3. 第三个参数为回调函数,传入数据为error对象,其为null时表示成功。

fs.readFile

示例代码:/lesson03/server.js

https://nodejs.org/dist/latest-v11.x/docs/api/fs.html#fs_fs_readfile_path_options_callback

fs.readFile用来读取文件。

fs.readFile('./test.txt', (error, data) => {
  if (error) {
    console.log('文件读取失败', error)
  } else {
    // 此处因确定读取到的数据是字符串,可以直接用toString方法将Buffer转为字符串。
    // 若是需要传输给浏览器可以直接用Buffer,机器之间通信是直接用Buffer数据。
    console.log('文件读取成功', data.toString())
  }
})

fs.readFile主要参数:

  1. 第一个参数为读取的文件路径

  2. 第二个参数为回调函数。回调函数传入第一个参数为error对象,其为null时表示成功,第二个为数据,可为<string> | <Buffer>。

Nodejs教程19:WebSocket之一:使用Socket.io建立WebSocket应用

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

WebSocket的优势

  1. 性能高。

    根据测试环境数据的不同,大约会比普通Ajax请求高2-10倍。
    HTTP是文本协议,数据量比较大。

    而WebSocket是基于二进制的协议,在建立连接时用的虽然是文本数据,但之后传输的都是二进制数据,因此性能比Ajax请求高。

  2. 双向通信。

    如果是普通Ajax请求,需要实时获取数据,只能用计时器定时发送请求,这样会浪费服务器资源和流量。

    而通过WebSocket,服务器可以主动向前端发送信息。

  3. 安全性高

    由于WebSocket出现较晚,相比HTTP协议,在安全性上考虑的更加充分。

接下来,尝试用Socket.io建立一个基于WebSocket的双向通信。

Socket.io

Socket.io是在使用WebSocket时的一个常用库,它会自动判断在支持WebSocket的浏览器中使用WebSocket,在其他浏览器中,会使用如flash等方式完成通信。

  1. 操作简单
  2. 兼容低端浏览器,如IE6
  3. 自动进行数据解析
  4. 自动重连 若出现连接断开的情况,WebSocket会进行自动重连。

使用Socket.io建立一个WebSocket应用

服务端示例代码:/lesson19/server.js

const http = require('http')
const io = require('socket.io')

// 1. 建立HTTP服务器。
const server = http.createServer((req, res) => {

})

server.listen(8080)

// 2. 建立WebSocket,让socket.io监听HTTP服务器,一旦发现是WebSocket请求,则会自动进行处理。
const ws = io.listen(server)

// 建立连接完成后,触发connection事件。
// 该事件会返回一个socket对象(https://socket.io/docs/server-api/#Socket),可以利用socket对象进行发送、接收数据操作。
ws.on('connection', (socket) => {
  // 根据事件名,向客户端发送数据,数据数量不限。
  socket.emit('msg', '服务端向客户端发送数据第一条', '服务端向客户端发送数据第二条')

  // 根据事件名接收客户端返回的数据
  socket.on('msg', (...msgs) => {
    console.log(msgs)
  })

  // 使用计时器向客户端发送数据
  setInterval(() => {
    socket.emit('timer', new Date().getTime())
  }, 500);
})

客户端示例代码:/lesson19/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <!-- 引用Socket.io的客户端js文件,由于Socket.io已在服务端监听了HTTP服务器的请求,一旦收到对该文件的请求,则会自动返回该文件,不需要开发人员配置。 -->
  <!-- 该文件在服务端的位置为/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js -->
  <script src="http://localhost:8080/socket.io/socket.io.js"></script>
  <script>
    // 与服务器建立WebSocket连接,该连接为ws协议,socket.io不需要担心跨域问题。
    const socket = io.connect('ws://localhost:8080/')

    // 根据事件名,向服务端发送数据,数据数量不限。
    socket.emit('msg', '客户端向服务端发送数据第一条', '客户端向服务端发送数据第二条')

    // 根据事件名接收服务端返回的数据
    socket.on('msg', (...msgs) => {
      console.log(msgs)
    })

    // 接收服务端通过计时器发送来的数据
    socket.on('timer', (time) => {
      console.log(time)
    })
  </script>
</body>

</html>

用浏览器打开index.html,即可在控制台看到打印的消息。

Nodejs教程11:assert(断言)模块

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

assert的用途

assert(断言)通常用来对代码进行校验,若出错则阻止程序运行,并抛出一个错误。

assert的用法

示例代码:/lesson11/assert.js

尝试运行如下代码:

const assert = require('assert')

assert(2 > 1, '2 > 1')

assert(1 > 2, '1 > 2')

代码在运行到assert(2 > 1, '2 > 1')时,由于2 > 1为true,此时不会抛出错误。

而运行到assert(1 > 2, '1 > 2')时,由于1 > 2为false,此时会抛出错误如下:

AssertionError [ERR_ASSERTION]: 1 > 2
    at Object.<anonymous> (C:\projects\nodejs-tutorial\lesson11\assert.js:5:1)
    at Module._compile (internal/modules/cjs/loader.js:734:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:745:10)
    at Module.load (internal/modules/cjs/loader.js:626:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:566:12)
    at Function.Module._load (internal/modules/cjs/loader.js:558:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:797:12)
    at executeUserCode (internal/bootstrap/node.js:526:15)
    at startMainThreadExecution (internal/bootstrap/node.js:439:3)

提示了在\lesson11\assert.js的第5行有一个错误,同时将错误信息1 > 2抛出,并终止代码的运行。

assert的使用方式

通常可以在一个模块或函数的每个阶段使用assert,或者在对函数传参进行assert校验,以保证代码运行的正确性。

assert.deepStrictEqual

assert.deepStrictEqual(actual, expected[, message])用于对actual 参数和 expected的深度比较,即不仅校验它们是否相等,同时也要校验它们的成员之间是否相等。

assert.deepStrictEqual在校验对象或数组时比较有用。

assert.deepStrictEqual的比较相当于===,也就是不仅是值相等,值的类型也要相等。

assert.deepStrictEqual的用法

  1. 使用assert.deepStrictEqual比较对象:

示例代码:/lesson11/assert.deepStrictEqual.object.js

const assert = require('assert')

const obj1 = {
  a: 1,
  b: 2,
  children: {
    c: 3
  }
}

const obj2 = {
  a: 1,
  b: 2,
  children: {
    c: 3
  }
}

const obj3 = {
  a: 1,
  b: 2,
  children: {
    c: '3'
  }
}

assert.deepStrictEqual(obj1, obj2, 'obj1 !== obj2')

assert.deepStrictEqual(obj1, obj3, 'obj1 !== obj3')

代码会抛出错误:obj1 !== obj3。

  1. 使用assert.deepStrictEqual比较数组:

示例代码:/lesson11/assert.deepStrictEqual.array.js

const assert = require('assert')

const arr1 = [{
  a: 1,
  b: 2,
  children: [{
    c: 3
  }]
}]

const arr2 = [{
  a: 1,
  b: 2,
  children: [{
    c: 3
  }]
}]

const arr3 = [{
  a: 1,
  b: 2,
  children: [{
    c: '3'
  }]
}]

assert.deepStrictEqual(arr1, arr2, 'arr1 !== arr2')

assert.deepStrictEqual(arr1, arr3, 'arr1 !== arr3')

代码会抛出错误:arr1 !== arr3。

Nodejs教程04:使用http和fs模块实现一个简单的服务器

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

使用http和fs模块实现一个简单的服务器

  1. 创建一个www目录,存储静态文件1.html、1.jpg。
  2. 1.html文件内容如下:

示例代码:/lesson04/www/1.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  网页内容
  <img src="/1.jpg" alt="">
</body>
</html>
  1. 预期实现的结果为:
    a. 在浏览器访问http://localhost:8080/1.html
    b. 读取到www/1.html,由HTML文件发起对www/1.jpg的请求。
    c. 网页中显示HTML内容和图片。

  2. 使用Nodejs实现服务端代码:

示例代码:/lesson04/server.js

const http = require('http')
const fs = require('fs')

const server = http.createServer((request, response) => {
  console.log(request.url)  // 在request对象中,可以获取请求的URL,通过URL判断请求的资源。
  fs.readFile(`./www${request.url}`, (error, buffer) => { // 根据URL查找读取相应的文件。
    if (error) {  // 若读取错误,则向前端返回404状态码,以及内容Not Found。
      response.writeHead(404)
      response.write('Not Found')
    } else {  // 若读取成功,则向前端返回读取到的文件。
      response.write(buffer)
    }
    response.end()  // 关闭连接。
  })
})

server.listen(8080)

服务器需要具备的基本功能

  1. 响应请求
    如上面的例子,可以根据客户端的请求做出回应,如返回静态文件。
  2. 数据交互
    定义接口,客户端根据接口,与服务端进行数据交互。
    例如在一个购物流程中,客户端向服务端请求商品数据,展现给客户,客户在购买时,客户端将购买的商品信息发送给服务端处理。
  3. 数据库
    对数据库中存储的数据进行读写操作。

接下来的文章中,将进入前后段的数据交互内容。

Vue教程15:Vue组件

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/查看效果

注册Vue组件

示例代码:/lesson15/src/cmp1.js

通过Vue.component可以注册一个组件,再将其导出到入口vm.js即可。

import Vue from 'vue/dist/vue.esm';

// 通过Vue.component注册一个组件
export default Vue.component('cmp1', {
  props: ['name', 'age', 'list'],  // 定义要传入的props,在Vue中只有已定义的props才可以被组件接收到
  data(){ // 组件的data必须是函数,为了保证组件的data作用域独立
    return {a: 77, b: 55};
  },
  // 组件模板
  template: `
<div>
  姓名:{{name}}<br/>
  年龄:{{age}}<br/>
  <ul>
    <li v-for="item in list">{{item}}</li>
  </ul>
</div>
`
});

使用组件

示例代码:/lesson15/src/vm.js

可以直接通过<cmp1 name="Lee Chen" age="18" :list="[1, 2, 3]" />使用组件,这样无论何种情况,组件都会直接被渲染在页面中。

也可以通过通用组件<component :is="type" name="Lee Chen" age="18" :list="[1, 2, 3]" />使用,is属性为需要使用的组件名,通过改变该属性的值,可以控制渲染的组件。

import Vue from 'vue/dist/vue.esm';
import Cmp1 from './cmp1';

let vm=new Vue({
  el: '#div1',
  data: {
    type: 'cmp1'
  },
  // 局部组件
  // 组件可以直接引入,也可以通过通用component组件引入,当is属性为特定组件名时,渲染相应组件。
  template: `
<div>
  可以尝试输入cmp1或my-dialog
  <input type="text" v-model="type" />
  <cmp1 name="Lee Chen" age="18" :list="[1, 2, 3]" />
  <component :is="type" name="Lee Chen" age="18" :list="[1, 2, 3]" />
</div>
`
})

实例化组件

组件还可以通过new关键字进行实例化,实例化后的组件主要用于测试。

// 实例化组件,主要用于测试
let cmp=new Cmp1({
  propsData: {
    name: '张三',
    list: [88, 99, 27]
  }
});

// 生成虚拟vm对象
let vm=cmp.$mount();

// vm.$el中存储了DOM,但不渲染在页面中
console.log(vm.$el);

// 测试代码
if(vm.$el.querySelector('li').innerHTML=='88'){
  console.log('正确');
}else{
  console.log('失败');
}

组件插槽

我们可以在模板中使用<slot />标记一个默认插槽位置,使用<slot name="title"/>标记一个具名插槽位置。

在使用组件时,组件标签<my-dialog></my-dialog>内部的内容都为插槽内容。其中带有相应name属性的内容会被插入到<slot name="title"/>插槽的位置,其余内容会插入到默认插槽。

示例代码:/lesson15/src/my-dialog.js

import Vue from 'vue/dist/vue.esm';
import 'bootstrap/dist/css/bootstrap.css';
import './css/my-dialog.css';

export default Vue.component('my-dialog', {
  data(){
    return {};
  },
  template: `
<div class="panel panel-default my-dialog">
  <div class="panel-heading">
    <slot name="title"/>
  </div>
  <div class="panel-body">
    <slot />
  </div>
</div>
`
});

示例代码:/lesson15/src/vm.js

import Vue from 'vue/dist/vue.esm';
import Cmp1 from './cmp1';
import MyDialog from './my-dialog';

let vm=new Vue({
  el: '#div1',
  data: {
    type: 'cmp1'
  },
  // 局部组件
  // 组件可以直接引入,也可以通过通用component组件引入,当is属性为特定组件名时,渲染相应组件。
  template: `
<div>
  可以尝试输入cmp1或my-dialog
  <input type="text" v-model="type" />
  <cmp1 name="Lee Chen" age="18" :list="[1, 2, 3]" />
  <component :is="type" name="Lee Chen" age="18" :list="[1, 2, 3]" />
  <my-dialog>
    <!-- 名为title的插槽内容 -->
    <template slot="title">标题</template>
    一些文字文字文字
    <!-- 默认插槽内容 -->
    <ul>
      <li>asdfas</li>
      <li>asdfas</li>
    </ul>
  </my-dialog>
</div>
`
})

Vue教程05:v-pre、v-cloak指令

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

v-pre指令

代码示例:/lesson05/01. v-pre指令.html

v-pre可以用来阻止预编译,有v-pre指令的标签内部的内容不会被编译,会原样输出。

如果已知页面内部有大段内容无需编译,使用v-pre指令阻止编译可以提高性能,同时可以防止页面内有可能导致Vue编译出错的代码存在。

JavaScript:

let vm = new Vue({
  el: '#app', // 根元素或挂载点。
  data: {}
})

HTML:

<div id="app">
  <!-- 若不加v-pre指令,直接编译会报错,因为data中没有a和b属性 -->
  <div v-pre>
    {{a}} + {{b}}
  </div>
</div>

v-cloak指令

代码示例:/lesson05/02. v-cloak指令.html

v-cloak指令只是在标签中加入一个v-cloak自定义属性,在HTML还编译完成之后该属性会被删除,可以CSS对标签设置样式,表示HTML还未被编译,比如可以设置display: none;

JavaScript:

// 延迟3秒实例化Vue,若不加v-cloak指令,在页面上会显示{{a}} + {{b}},1秒之后才渲染出10 + 20。
setTimeout(() => {
  let vm = new Vue({
    el: '#app', // 根元素或挂载点。
    data: {
      a: 10,
      b: 20
    }
  })
}, 3000);

HTML:

<div id="app">
  <!-- v-cloak指令只是在标签中加入一个v-cloak自定义属性,在HTML还编译完成之后该属性会被删除,可以CSS对标签设置样式,表示HTML还未被编译,比如可以设置display: none; -->
  <div v-cloak>
    {{a}} + {{b}}
  </div>
</div>

CSS:

<style>
  /* 有v-cloak属性的标签都不显示 */
  [v-cloak] {
    display: none;
  }
</style>

Nodejs教程18:Ajax跨域

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

Ajax跨域问题的产生原因

Ajax请求无法跨域,在前端开发中是个常见问题,它的产生原因是,在浏览器接收服务端返回数据时,会检查该数据是否和当前发起请求的网址在同一域名下。若不是,则会丢弃该数据,并返回一个跨域错误。

在Ajax请求中,遵循如下流程:

网页提交一个Ajax请求到浏览器,浏览器将请求发至服务器,服务器接收到请求后,返回响应数据给浏览器(服务器通常不会对域名进行区分),浏览器接收到响应数据时,检测返回数据的域名是否和当前页面域名相同,若相同则将数据返回给网页,不同则丢弃数据。

Ajax跨域的处理方法

既然跨域问题的产生原因在于浏览器的限制,那么网页端在请求时无法主动规避,此时就需要服务端进行处理。

服务端只需要在响应Ajax请求时,在请求头中加入一个Access-Control-Allow-Origin属性,并设置为*(表示全部域名)或者当前域名就可以让浏览器不再进行限制。

以下示例可以通过/lesson18/ajax.html文件发起Ajax请求测试。

示例代码:/lesson18/server.js

const http = require('http')

const server = http.createServer((req, res) => {
  console.log(req.headers.origin)
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.write(`{"resultCode": "0000", "msg": "success"}`)
  res.end()
})

server.listen(8080)

当然在实际项目中,不可以简单地设置res.setHeader('Access-Control-Allow-Origin', '*'),而是要通过req.headers.origin判断发起请求的域名是否合法,再设置Access-Control-Allow-Origin属性,以免出现安全问题。/

Vue教程21:Vuex Getter

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/#/index查看效果

Getter的基本使用

代码示例:/lesson21/src/index.js

Vuex中的Getter的作用类似于Vue中的Computed,可以实现在state变化时自动计算出一个新的结果。
在store中配置一个Getter:

getters: {
  count (state) {
    return state.a + state.b
  }
}

代码示例:/lesson21/src/components/Index.vue

在组件Index中,可以通过$store.getters.count就可以读取到相应的值。

<div>count from getters: {{$store.getters.count}}</div>

为了方便使用,通常可以把Getter配置到Computed中:

computed: {
  countFromComputed() {
    return this.$store.getters.count
  }
}

在Template中引用:

<div>count from computed: {{countFromComputed}}</div>

利用Computed更新State

因为Computed属性支持get和set方法,所以能够使用set方法更新State。

首先在Store中设置actions和Mutations。

代码示例:/lesson21/src/store/index.js

mutations: {
  addA (state, n) {
    state.a += n
  },
  addB (state, n) {
    state.b += n
  },
},
actions: {
  addA ({commit}, n) {
    commit('addA', n)
  },
  addB ({commit}, n) {
    commit('addB', n)
  },
},

Index.vue模板中添加<input type="button" value="count+5" @click="addCount(5)" />,调用一个addCount方法,在addCount方法中让countFromComputedSet属性加5。
在computed属性中接收到结果后,将增加的5分配给a属性。

countFromComputedSet: {
  get() {
    return this.$store.getters.count
  },
  set(value) {
    this.$store.dispatch('addA', 5)
  },
},

React Native教程01:简介及环境准备

本教程适合已有React开发经验的朋友阅读

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

可以通过npm run android或npm run ios启动并在模拟器查看效果。

移动端开发方式

目前移动端开发方式主要分为4种,分别为Web、原生、混合、编译开发。React Native就是属于编译型开发,即将JavaScript编译成原生代码,实际生成的还是原生APP。

对比一下几种开发方式的优缺点:

Web 原生 混合 编译
能否跨平台 不能
调用系统权限 不能
开发便利性
性能
开发框架、插件等 丰富 丰富 丰富 较少

从以上对比可以看出,编译型开发方式相比其他开发方式有很大优势,但缺点就是还不够成熟。

React Native简介

  1. React Native不是网页开发,HTML标签和CSS样式都无法使用,但提供了React Native组件和替代样式,让开发者可以用类似于网页开发的方式,进行原生应用开发。
  2. React和JavaScript(ES6)几乎完全支持,当然JavaScript最终会被编译成原生代码。

React Native环境准备

  1. 安装Android Studio(需要科学上网,安装流程可以参考:https://www.cnblogs.com/xiadewang/p/7820377.html),Mac系统还需要安装xcode。
  2. 安装包管理工具:yarn,可以设置淘宝镜像:yarn config set registry 'https://registry.npm.taobao.org',以提升安装速度。
  3. 安装JDK1.8(参考:https://blog.csdn.net/u014166319/article/details/71791287)。
  4. 安装Android SDK:Android 8.1(API Level27)。
  5. 安装Android SDK Tools:Android Emulator。
  6. 添加环境变量:变量名ANDROID_HOME,变量值为Android SDK地址。
  7. 安装react-native-cli工具:npm i -g react-native-cli。

新建React Native项目

  1. 使用react-native init project-name就可以创建一个新的React Native项目,会自动通过yarn安装依赖。

  2. 通过Android Studio中的Android Virtual Device Manager创建一个设备并启动。

  3. 依赖安装完成后,通过react-native run-android就可以启动React Native项目。
    a. 在Mac中若遇到报错:Error: fsevents unavailable (this watcher can only be used on Darwin),可以参考expo/expo#854
    需要先安装xcode并且同意协议,然后安装brew:https://brew.sh/,最后运行npm r -g watchman和brew install watchman命令即可。
    b. 在Mac中若出现报错:SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable。则可以在工程的根目录下的android文件下新建一个local.properties的文件,在文件中写入sdk.dir=你的sdk目录。参考:https://www.jianshu.com/p/c04ac43f5021
    c. 在Mac中若出现报错:adb: command not found。则需要配置配置Android环境变量,参考:https://www.jianshu.com/p/db3298ca0a32

  4. 启动过程中会弹出一个Android 调试桥 (adb) 的命令行工具,它是用于连接Android设备并进行通信。

  5. 编译完成之后,React Native项目会自动在Android设备中弹出,这时可以看到一个欢迎页面。

  6. 在Mac中运行一个iOS项目,需要先安装xcode,之后运行react-native run-ios就可以在模拟器中查看到React Native项目。

接下来就可以愉快地开始React Native开发了。

Nodejs教程13:URL模块

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

url.parse

URL模块用于对URL的解析,常用的是url.parse方法。

假设有一个url为https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4,可以用url.parse方法进行解析。

示例代码:/lesson13/url.js

const url = require('url')

const str = 'https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4'

console.log(url.parse(str))

打印结果如下:

Url {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'www.google.com:8080',
  port: '8080',
  hostname: 'www.google.com',
  hash: null,
  search: '?x=1&y=2&y=3&y=4',
  query: 'x=1&y=2&y=3&y=4',
  pathname: '/a/b',
  path: '/a/b?x=1&y=2&y=3&y=4',
  href: 'https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4' }

可以看到url的信息如端口号、域名、query参数等都被解析出来了。

如果需要将query参数转为对象,则可以为url.parse函数的第二个参数传true,如console.log(url.parse(str, true)),打印结果如下:

Url {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'www.google.com:8080',
  port: '8080',
  hostname: 'www.google.com',
  hash: null,
  search: '?x=1&y=2&y=3&y=4',
  query: [Object: null prototype] { x: '1', y: [ '2', '3', '4' ] },
  pathname: '/a/b',
  path: '/a/b?x=1&y=2&y=3&y=4',
  href: 'https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4' }

同时可以看到y=2&y=3&y=4参数被解析为了y: [ '2', '3', '4' ]。

new URL()

除了用url.parse方法解析url,还可以通过构造函数URL,创建一个实例,其中带有解析后的数据。

实例有一个toString方法,可以将实例解析为字符串url。

示例代码:/lesson13/url.js

代码如下:

const { URL } = require('url')
const urlObj = new URL(str)

console.log(urlObj)
console.log(urlObj.toString())

打印结果为:

URL {
  href: 'https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4',
  origin: 'https://www.google.com:8080',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'www.google.com:8080',
  hostname: 'www.google.com',
  port: '8080',
  pathname: '/a/b',
  search: '?x=1&y=2&y=3&y=4',
  searchParams:
   URLSearchParams { 'x' => '1', 'y' => '2', 'y' => '3', 'y' => '4' },
  hash: '' }
  
https://www.google.com:8080/a/b?x=1&y=2&y=3&y=4	// toString方法解析出的url

微信小程序教程01:小程序简介

本教程适合已有React开发经验的朋友阅读

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

可以通过npm run android或npm run ios启动并在模拟器查看效果。

React Native基础组件

示例代码:/lesson01/App.js

  1. View:View在React Native中的作用类似于Web中的div标签,是最基础的组件。
  2. Text:Text在React Native中用来显示文本,其作用类似于Web中的span标签,但它并不是inline元素。
    值得注意的是,在React Native中所有的文本都必须放在Text中,而不能单独显示。
  3. Image:Image类似于Web端的img标签,可在source属性中设置引用图片的路径。在React Native中,Image组件不支持onPress(点击)事件。
<View>
  <Image
    source={require('/react-native/img/favicon.png')}
  />
  <Image
    source={{ uri: 'https://facebook.github.io/react-native/docs/assets/favicon.png' }}
  />
  <Image
    source={{ uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==' }}
  />
</View>
  1. ScrollView:在React Native中,内容如果超出一定高度,是不会像Web端一样自动触发滚动条,而是需要将要滚动的内容放在ScrollView组件中,内容高度超出ScrollView时就会触发滚动。需要注意的是,ScrollView必须通过style设定一个高度。
    若是需要渲染长列表,如一个商品列表,则可以使用FlatList组件,因为ScrollView会将列表组件一次性渲染出,这样容易造成过多内存占用,而FlatList则只会渲染将要出现在屏幕中的元素。
    虽然FlatList在使用上更加复杂,但为了提升性能,我们应该优先使用ScrollView。
  2. Button:在React Native中,Button调用的是原生的按钮,它的缺点是只支持少量的定制。
    如color,在Android中,它修改的是背景色,在iOS中,它修改的是字体颜色。
    在开发中,若需要用到Button,在GitHub上搜索是一个好选择:https://github.com/search?utf8=%E2%9C%93&q=react+native+button&ref=simplesearch

React Native样式介绍

示例代码:/lesson01/App.js

  1. React Native中的样式写法与CSS类似,支持直接用style属性写在组件上,而且因为是运行在原生环境中,所以仅支持部分CSS样式。
  2. React Native也支持类似于类名的写法,但React Native不支持.css文件,因此样式需要写在const styles = StyleSheet.create({})中,其属性名就作为类名使用。使用时也写在组件的style属性中,<View style={styles.container}></View>。
  3. 布局方式:标签没有inline类型,全部都相当于block类型。支持flex布局和position(enum('absolute', 'relative')),但不支持浮动。布局方式:标签没有inline类型,全部都相当于block类型。支持flex布局和position(enum('absolute', 'relative')),但不支持浮动。
  4. 支持的单位:数字(与设备像素密度无关的逻辑像素点,相当于PX)和百分比。
    为了适配各尺寸屏幕,可以通过设定基础屏幕宽度,如750,与当前屏幕宽度换算出当前实际需要的尺寸。
// 引入Dimensions,用于获取设备的尺寸
import { Dimensions } from 'react-native';

// 设置基础宽度为750
const BASE_WIDTH=750;

export function calc(size){
  // 获取当前窗口宽度,支持参数为window和screen
  let { width } = Dimensions.get('window');

  // 换算出当前需要显示的尺寸
  return size * width / BASE_WIDTH;
}

Nodejs教程01:Nodejs简介

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

Nodejs简介

  1. 简单的说 Node.js 就是运行在服务端的 JavaScript。
  2. Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
  3. Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

Nodejs的应用场景

由于Nodejs目前还不够成熟,因此一般不会用作独立开发,它的主要用途如下

  1. 中间层

通常在开发应用时,出于安全考虑,后端的主服务器都不会直接暴露给客户端,两端之间通常需要有一个中间层进行通信。

这样做的好处是,如果中间层出现问题,不会影响后端的主服务器。另外,中间层可以做缓存,或者实现一些业务逻辑,起到降低主服务器复杂度,提高性能的作用。

中间层也可以像CDN一样在各处部署,以提高用户的访问效率。

  1. 小型服务

可以实现一些小型应用,或某个功能模块。

  1. 工具类
    Nodejs可以用来开发一些实用工具,如Webpack、Gulp等等。

Nodejs的优势

  1. Nodejs的语法与前台JavaScript相同,因此便于前端开发入手

  2. 性能高

  3. 利于与前端代码结合,例如在做同样一个数据校验时,前后台代码可以共用,不需要单独开发。

Nodejs的安装

Nodejs的安装与普通软件一样,上官网https://nodejs.org/en/下载最新版本,建议安装Current版本,LTS版本有些新API无法使用,一直下一步安装即可。

Nodejs的卸载

当需要升级Nodejs时,建议先完全卸载旧版本,特别是全局已下载的依赖,否则有小概率会出现更新版本后,新安装依赖时报错。

完整卸载步骤:

  1. 通过系统自带卸载工具,卸载Nodejs,之后最好将Nodejs安装目录整个删除。

  2. 手动删除安装目录,如C:\Program Files\nodejs目录下的node_modules文件夹。

  3. 找到用户目录,如C:\Users\你的用户名,其中如果有node_modules文件夹,则一起删除。

启动一个Nodejs服务器

示例代码:/lesson01/server.js

我们可以新建一个server.js文件,在命令行通过node server.js命令,就可以运行一个服务器,在浏览器访问中访问http://127.0.0.1:3000/,就可以看到Hello World。

// 引入Nodejs自带的http模块
const http = require('http');
// 引入Nodejs自带的child_process模块
const childProcess = require('child_process');

const hostname = '127.0.0.1'; // 本机地址
const port = 3000; // 端口

// 创建一个服务器
const server = http.createServer((req, res) => {
  res.statusCode = 200; // 设置响应状态码
  res.setHeader('Content-Type', 'text/plain'); // 设置响应头
  res.end('Hello World\n'); // 向前台输出内容
});

// 开启监听
server.listen(port, hostname, () => {
  // 在命令行打印运行结果
  console.log(`Server running at http://${hostname}:${port}/`);
  // 使用默认浏览器打开地址
  childProcess.exec(`start http://${hostname}:${port}/`);
});

Vue教程23:Vuex异步Action

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

该节教程代码可通过npm start运行devServer,在http://localhost:8080/#/index查看效果

运行服务端请cd server,node server.js。

创建一个测试数据文件user.txt

文件内容为[{"id":3,"name":"lee","age":18,"online":true},{"id":5,"name":"zhangsan","age":22,"online":false},{"id":11,"name":"lisi","age":25,"online":true}]。
用于输出一个user列表。
通过cd server,node server.js启动服务器,就可以在http://localhost:8081/user.txt访问到它。

添加异步Action

代码示例:/lesson23/src/store/index.js

由于Mutations不接受异步函数,因此只能通过Actions来实现异步请求。
在Actions中添加一个异步Action:

actions: {
  async readUsers ({commit}) {
    let res = await fetch('http://localhost:8081/user.txt')
    let users = await res.json()

    commit('setUsers', users)
  }
},

在Mutations中通过setUsers接收并更新users数据:

setUsers (state, users) {
  state.users = users
}

发起异步Action

代码示例:/lesson23/src/components/Index.vue

在生命周期created中,发起一个异步Action获取数据:

async created(){
  await this.readUsers();
},

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.