cainiaoblog 23.2.171

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/CNAME +1 -0
  2. package/MP_verify_o6dHCK5mIkxNpzvZ.txt +1 -0
  3. package/README.md +8 -0
  4. package/about/index.html +54 -0
  5. package/ads.txt +1 -0
  6. package/app/manual.html +104 -0
  7. package/appConfigs/config +1 -0
  8. package/archives/2018/index.html +48 -0
  9. package/archives/2019/index.html +48 -0
  10. package/archives/2019/page/2/index.html +48 -0
  11. package/archives/2020/index.html +48 -0
  12. package/archives/2021/index.html +48 -0
  13. package/archives/2022/index.html +48 -0
  14. package/archives/2023/index.html +48 -0
  15. package/archives/index.html +48 -0
  16. package/archives/page/2/index.html +48 -0
  17. package/archives/page/3/index.html +48 -0
  18. package/article/10047.html +148 -0
  19. package/article/10164.html +64 -0
  20. package/article/10624.html +436 -0
  21. package/article/10745.html +586 -0
  22. package/article/11674.html +71 -0
  23. package/article/11939.html +72 -0
  24. package/article/12238.html +86 -0
  25. package/article/1230.html +533 -0
  26. package/article/13265.html +69 -0
  27. package/article/13823.html +78 -0
  28. package/article/14125.html +72 -0
  29. package/article/14192.html +561 -0
  30. package/article/14375.html +85 -0
  31. package/article/14744.html +83 -0
  32. package/article/14936.html +451 -0
  33. package/article/1518.html +250 -0
  34. package/article/15428.html +71 -0
  35. package/article/15518.html +288 -0
  36. package/article/15569.html +418 -0
  37. package/article/1566.html +122 -0
  38. package/article/16017.html +156 -0
  39. package/article/16947.html +440 -0
  40. package/article/17096.html +82 -0
  41. package/article/17246.html +89 -0
  42. package/article/17575.html +350 -0
  43. package/article/179.html +757 -0
  44. package/article/18039.html +233 -0
  45. package/article/18071.html +375 -0
  46. package/article/18142.html +71 -0
  47. package/article/19909.html +160 -0
  48. package/article/20014.html +89 -0
  49. package/article/20021.html +389 -0
  50. package/article/20037.html +280 -0
  51. package/article/20056.html +95 -0
  52. package/article/20093.html +404 -0
  53. package/article/2021.html +607 -0
  54. package/article/20459.html +115 -0
  55. package/article/20613.html +263 -0
  56. package/article/20635.html +71 -0
  57. package/article/21317.html +71 -0
  58. package/article/21320.html +90 -0
  59. package/article/21782.html +548 -0
  60. package/article/22230.html +435 -0
  61. package/article/22237.html +347 -0
  62. package/article/22398.html +109 -0
  63. package/article/22422.html +270 -0
  64. package/article/22565.html +300 -0
  65. package/article/23046.html +250 -0
  66. package/article/23083.html +215 -0
  67. package/article/23195.html +297 -0
  68. package/article/23301.html +835 -0
  69. package/article/23589.html +285 -0
  70. package/article/23655.html +338 -0
  71. package/article/23864.html +76 -0
  72. package/article/23914.html +96 -0
  73. package/article/24169.html +135 -0
  74. package/article/24581.html +349 -0
  75. package/article/2475.html +500 -0
  76. package/article/24826.html +77 -0
  77. package/article/24924.html +247 -0
  78. package/article/25164.html +1495 -0
  79. package/article/25318.html +394 -0
  80. package/article/25710.html +1129 -0
  81. package/article/25734.html +83 -0
  82. package/article/26083.html +174 -0
  83. package/article/26147.html +283 -0
  84. package/article/26291.html +110 -0
  85. package/article/26354.html +71 -0
  86. package/article/26624.html +145 -0
  87. package/article/26639.html +298 -0
  88. package/article/26850.html +138 -0
  89. package/article/26940.html +258 -0
  90. package/article/27796.html +190 -0
  91. package/article/27800.html +161 -0
  92. package/article/28963.html +159 -0
  93. package/article/28976.html +247 -0
  94. package/article/29042.html +72 -0
  95. package/article/29596.html +81 -0
  96. package/article/29619.html +183 -0
  97. package/article/29623.html +329 -0
  98. package/article/29829.html +205 -0
  99. package/article/29abbd1c.html +112 -0
  100. package/article/30215.html +178 -0
  101. package/article/30552.html +78 -0
  102. package/article/30818.html +201 -0
  103. package/article/31330.html +364 -0
  104. package/article/3154.html +577 -0
  105. package/article/31695.html +348 -0
  106. package/article/31826.html +71 -0
  107. package/article/31843.html +132 -0
  108. package/article/32280.html +212 -0
  109. package/article/32431.html +74 -0
  110. package/article/32443.html +72 -0
  111. package/article/32732.html +122 -0
  112. package/article/3286.html +251 -0
  113. package/article/32960.html +198 -0
  114. package/article/33713.html +72 -0
  115. package/article/33899.html +615 -0
  116. package/article/34351.html +74 -0
  117. package/article/34372.html +89 -0
  118. package/article/34410.html +74 -0
  119. package/article/34431.html +118 -0
  120. package/article/3447.html +75 -0
  121. package/article/34474.html +142 -0
  122. package/article/34871.html +147 -0
  123. package/article/35397.html +175 -0
  124. package/article/3544.html +199 -0
  125. package/article/35503.html +115 -0
  126. package/article/35624.html +84 -0
  127. package/article/35697.html +384 -0
  128. package/article/35863.html +73 -0
  129. package/article/36037.html +253 -0
  130. package/article/36118.html +71 -0
  131. package/article/3621.html +119 -0
  132. package/article/36251.html +296 -0
  133. package/article/36642.html +352 -0
  134. package/article/37102.html +763 -0
  135. package/article/37336.html +351 -0
  136. package/article/37351.html +89 -0
  137. package/article/3760.html +294 -0
  138. package/article/37749.html +75 -0
  139. package/article/37789.html +83 -0
  140. package/article/38005.html +765 -0
  141. package/article/38009.html +284 -0
  142. package/article/38269.html +194 -0
  143. package/article/38344.html +88 -0
  144. package/article/38359.html +334 -0
  145. package/article/38423.html +379 -0
  146. package/article/38530.html +213 -0
  147. package/article/38743.html +268 -0
  148. package/article/38753.html +204 -0
  149. package/article/38881.html +71 -0
  150. package/article/39087.html +414 -0
  151. package/article/39309.html +84 -0
  152. package/article/39486.html +74 -0
  153. package/article/39637.html +306 -0
  154. package/article/39839.html +72 -0
  155. package/article/39937.html +152 -0
  156. package/article/39970.html +71 -0
  157. package/article/402.html +559 -0
  158. package/article/40698.html +71 -0
  159. package/article/40811.html +71 -0
  160. package/article/40964.html +462 -0
  161. package/article/41011.html +322 -0
  162. package/article/41152.html +78 -0
  163. package/article/41696.html +71 -0
  164. package/article/41717.html +281 -0
  165. package/article/41777.html +149 -0
  166. package/article/419.html +985 -0
  167. package/article/4233.html +136 -0
  168. package/article/4292.html +86 -0
  169. package/article/43302.html +96 -0
  170. package/article/43397.html +106 -0
  171. package/article/43724.html +281 -0
  172. package/article/43999.html +224 -0
  173. package/article/44426.html +77 -0
  174. package/article/44964.html +137 -0
  175. package/article/45004.html +123 -0
  176. package/article/4510.html +83 -0
  177. package/article/45156.html +320 -0
  178. package/article/45157.html +52 -0
  179. package/article/45267.html +87 -0
  180. package/article/45641.html +207 -0
  181. package/article/45960.html +75 -0
  182. package/article/46100.html +1262 -0
  183. package/article/46366.html +74 -0
  184. package/article/46771.html +96 -0
  185. package/article/46911.html +517 -0
  186. package/article/46948.html +137 -0
  187. package/article/46ee4850.html +349 -0
  188. package/article/47019.html +84 -0
  189. package/article/47532.html +94 -0
  190. package/article/4783.html +429 -0
  191. package/article/47972.html +72 -0
  192. package/article/48216.html +127 -0
  193. package/article/48252.html +185 -0
  194. package/article/48520.html +74 -0
  195. package/article/48722.html +75 -0
  196. package/article/48807.html +99 -0
  197. package/article/49126.html +323 -0
  198. package/article/50001.html +88 -0
  199. package/article/50058.html +151 -0
  200. package/article/50343.html +410 -0
  201. package/article/50510.html +82 -0
  202. package/article/51082.html +98 -0
  203. package/article/51677.html +111 -0
  204. package/article/5204.html +1085 -0
  205. package/article/52500.html +538 -0
  206. package/article/52689.html +259 -0
  207. package/article/52877.html +569 -0
  208. package/article/53125.html +191 -0
  209. package/article/53428.html +251 -0
  210. package/article/53524.html +74 -0
  211. package/article/53698.html +322 -0
  212. package/article/53803e6.html +93 -0
  213. package/article/53924.html +291 -0
  214. package/article/53962.html +253 -0
  215. package/article/54006.html +71 -0
  216. package/article/54018.html +88 -0
  217. package/article/54161.html +83 -0
  218. package/article/54178.html +69 -0
  219. package/article/54258.html +327 -0
  220. package/article/54295.html +214 -0
  221. package/article/54313.html +1976 -0
  222. package/article/54351.html +90 -0
  223. package/article/54604.html +80 -0
  224. package/article/54632.html +71 -0
  225. package/article/54912.html +756 -0
  226. package/article/54918.html +129 -0
  227. package/article/55119.html +136 -0
  228. package/article/55756.html +118 -0
  229. package/article/55769.html +136 -0
  230. package/article/55870.html +161 -0
  231. package/article/56139.html +92 -0
  232. package/article/56557.html +69 -0
  233. package/article/56604.html +69 -0
  234. package/article/56816.html +266 -0
  235. package/article/56926.html +153 -0
  236. package/article/57747.html +409 -0
  237. package/article/57965.html +475 -0
  238. package/article/58816.html +78 -0
  239. package/article/59059.html +435 -0
  240. package/article/59095.html +350 -0
  241. package/article/59349.html +244 -0
  242. package/article/59402.html +216 -0
  243. package/article/59448.html +55 -0
  244. package/article/59870.html +94 -0
  245. package/article/59877.html +208 -0
  246. package/article/5dbb6f41.html +86 -0
  247. package/article/6008.html +75 -0
  248. package/article/60327.html +126 -0
  249. package/article/6035.html +76 -0
  250. package/article/60428.html +71 -0
  251. package/article/60544.html +128 -0
  252. package/article/61319.html +95 -0
  253. package/article/61367.html +93 -0
  254. package/article/624d1918.html +292 -0
  255. package/article/62543.html +71 -0
  256. package/article/62887.html +169 -0
  257. package/article/63074.html +120 -0
  258. package/article/63266.html +311 -0
  259. package/article/63351.html +74 -0
  260. package/article/63542.html +104 -0
  261. package/article/63824.html +275 -0
  262. package/article/64051.html +653 -0
  263. package/article/64189b69.html +72 -0
  264. package/article/64354.html +75 -0
  265. package/article/64869.html +79 -0
  266. package/article/64923.html +596 -0
  267. package/article/65128.html +95 -0
  268. package/article/65295.html +79 -0
  269. package/article/6888.html +75 -0
  270. package/article/7065.html +84 -0
  271. package/article/7455.html +85 -0
  272. package/article/7a3ef8f.html +315 -0
  273. package/article/8140.html +188 -0
  274. package/article/8190.html +85 -0
  275. package/article/8225.html +78 -0
  276. package/article/84c9ccd6.html +159 -0
  277. package/article/8f9a9ae3.html +82 -0
  278. package/article/9174.html +73 -0
  279. package/article/9738.html +294 -0
  280. package/article/a4145266.html +71 -0
  281. package/article/bedea419.html +72 -0
  282. package/article/bf7e7421.html +78 -0
  283. package/article/c1867fbf.html +103 -0
  284. package/article/c5576490.html +70 -0
  285. package/article/dcb0db28.html +76 -0
  286. package/article/fc3b727a.html +79 -0
  287. package/atom.xml +445 -0
  288. package/baidusitemap.xml +1079 -0
  289. package/blocks/dns.txt +264 -0
  290. package/blocks/filter.txt +1070 -0
  291. package/blocks/script.js +42 -0
  292. package/categories/CDN/index.html +48 -0
  293. package/categories/IDE/index.html +48 -0
  294. package/categories/git/index.html +48 -0
  295. package/categories/hexo/index.html +48 -0
  296. package/categories//345/205/254/345/205/261/350/212/202/346/227/245/index.html +48 -0
  297. package/categories//345/210/206/344/272/253/index.html +48 -0
  298. package/categories//345/211/215/347/253/257/index.html +48 -0
  299. package/categories//345/211/215/347/253/257/page/2/index.html +48 -0
  300. package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250/index.html +48 -0
  301. package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250//347/275/221/347/273/234/345/215/217/350/256/256/index.html +48 -0
  302. package/categories//345/215/232/345/256/242/index.html +48 -0
  303. package/categories//345/267/245/345/205/267/index.html +48 -0
  304. package/categories//345/271/277/345/221/212/350/277/207/346/273/244/index.html +48 -0
  305. package/categories//345/271/277/345/221/212/350/277/207/346/273/244//350/247/206/351/242/221/347/224/265/350/247/206/345/260/217/350/257/264/346/274/253/347/224/273/350/265/204/350/256/257/351/237/263/344/271/220/index.html +48 -0
  306. package/categories//346/255/243/345/210/231/index.html +48 -0
  307. package/categories//346/265/217/350/247/210/345/231/250/index.html +48 -0
  308. package/categories//347/256/227/346/263/225/index.html +48 -0
  309. package/categories//350/265/204/350/256/257/index.html +48 -0
  310. package/categories//350/275/273/346/235/276/344/270/200/345/210/273/index.html +48 -0
  311. package/cdn/css/font-awesome.min.css +4 -0
  312. package/cdn/css/grids-responsive-min.css +7 -0
  313. package/cdn/css/normalize.css +349 -0
  314. package/cdn/css/pure-min.css +11 -0
  315. package/cdn/fonts/fontawesome-webfont.eot +0 -0
  316. package/cdn/fonts/fontawesome-webfont.svg +2671 -0
  317. package/cdn/fonts/fontawesome-webfont.ttf +0 -0
  318. package/cdn/fonts/fontawesome-webfont.woff +0 -0
  319. package/cdn/fonts/fontawesome-webfont.woff2 +0 -0
  320. package/cdn/js/Valine.min.js +17 -0
  321. package/cdn/js/canvas-nest.js +1 -0
  322. package/cdn/js/clipboard.min.js +7 -0
  323. package/cdn/js/crypto-js.js +25 -0
  324. package/cdn/js/jquery.min.js +2 -0
  325. package/css/default.css +1144 -0
  326. package/css/donate.css +338 -0
  327. package/css/hbe.style.css +749 -0
  328. package/css/style.css +2095 -0
  329. package/donate/index.html +39 -0
  330. package/google917dcf72f6e5c7f5.html +1 -0
  331. package/guestbook/index.html +68 -0
  332. package/hosts/ads.txt +149 -0
  333. package/hosts/flash.txt +9 -0
  334. package/hosts/github.txt +6 -0
  335. package/hosts/google.txt +16936 -0
  336. package/hosts/winrar.txt +2 -0
  337. package/img/alipay.svg +46 -0
  338. package/img/bitcoin.svg +135 -0
  339. package/img/blog.ico +0 -0
  340. package/img/blog.png +0 -0
  341. package/img/gaba.png +0 -0
  342. package/img/github.svg +1 -0
  343. package/img/like.svg +1 -0
  344. package/img/mp-mini.jpg +0 -0
  345. package/img/mp.png +0 -0
  346. package/img/paypal.svg +63 -0
  347. package/img/upy_logo.svg +59 -0
  348. package/img/wechat.svg +49 -0
  349. package/index.html +48 -0
  350. package/jd_root.txt +1 -0
  351. package/js/codeblock-resizer.js +51 -0
  352. package/js/dark.js +12 -0
  353. package/js/donate.js +87 -0
  354. package/js/fixedPage.js +106 -0
  355. package/js/gitment.browser.js +3751 -0
  356. package/js/search.js +86 -0
  357. package/js/share.js +60 -0
  358. package/js/smartresize.js +32 -0
  359. package/js/totop.js +12 -0
  360. package/kankan/index.html +77 -0
  361. package/lib/hbe.js +293 -0
  362. package/package.json +25 -0
  363. package/page/10/index.html +49 -0
  364. package/page/11/index.html +59 -0
  365. package/page/12/index.html +67 -0
  366. package/page/13/index.html +63 -0
  367. package/page/14/index.html +66 -0
  368. package/page/15/index.html +60 -0
  369. package/page/16/index.html +60 -0
  370. package/page/17/index.html +62 -0
  371. package/page/18/index.html +61 -0
  372. package/page/19/index.html +58 -0
  373. package/page/2/index.html +48 -0
  374. package/page/20/index.html +57 -0
  375. package/page/21/index.html +61 -0
  376. package/page/22/index.html +55 -0
  377. package/page/23/index.html +64 -0
  378. package/page/24/index.html +60 -0
  379. package/page/25/index.html +58 -0
  380. package/page/26/index.html +48 -0
  381. package/page/27/index.html +58 -0
  382. package/page/3/index.html +48 -0
  383. package/page/4/index.html +48 -0
  384. package/page/5/index.html +51 -0
  385. package/page/6/index.html +51 -0
  386. package/page/7/index.html +51 -0
  387. package/page/8/index.html +53 -0
  388. package/page/9/index.html +48 -0
  389. package/randomcall/README.html +1 -0
  390. package/randomcall/lucker.html +290 -0
  391. package/randomcall/lucky.png +0 -0
  392. package/robots.txt +22 -0
  393. package/search.xml +6740 -0
  394. package/sitemap.xml +2669 -0
  395. package/tags//345/216/237/345/210/233/index.html +48 -0
  396. package/tags//345/250/261/344/271/220/index.html +48 -0
  397. package/tags//346/211/213/345/206/231/index.html +48 -0
  398. package/tags//347/226/253/346/203/205/index.html +48 -0
  399. package/tags//350/275/254/350/275/275/index.html +48 -0
  400. package/tags//350/275/254/350/275/275/page/2/index.html +48 -0
  401. package/v.html +90 -0
@@ -0,0 +1,569 @@
1
+ <!DOCTYPE html><html lang="zh-CN" data-dark><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"><meta content="yes" name="apple-mobile-web-app-capable"><meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"><meta content="telephone=no" name="format-detection"><meta name="description" content="前端壹菜鸟,记录工作难题,关注前端知识,收集精彩博文,做技术的搬运工"><meta name="msvalidate.01" content="0FE4D8B3381D3D87088996B886E1E2BD"><title>MVVM 双向绑定全量版整理 | 前端壹菜鸟</title><link rel="stylesheet" type="text/css" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/css/style.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/normalize.css@latest/normalize.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/pure-min.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/grids-responsive-min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"><link rel="icon" mask="" sizes="any" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="Shortcut Icon" type="image/x-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="apple-touch-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="apple-touch-icon-precomposed" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="alternate" type="application/atom+xml" href="/atom.xml"><script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8385136408348258"></script><script src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script><script>(function(){
2
+ var bp = document.createElement('script');
3
+ var curProtocol = window.location.protocol.split(':')[0];
4
+ if (curProtocol === 'https'){
5
+ bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
6
+ }
7
+ else{
8
+ bp.src = 'http://push.zhanzhang.baidu.com/push.js';
9
+ }
10
+ var s = document.getElementsByTagName("script")[0];
11
+ s.parentNode.insertBefore(bp, s);
12
+ })();
13
+ </script><script>(function(){
14
+ var src = 'https://jspassport.ssl.qhimg.com/11.0.1.js?d182b3f28525f2db83acfaaf6e696dba';
15
+ document.write('<script src="' + src + '" id="sozz"><\/script>');
16
+ })();
17
+ </script><script>var _hmt=_hmt||[];(function(){var hm=document.createElement("script");hm.src='https://hm.baidu.com/hm.js?011f0b44e8452bfa57fbfa23c5fe7683';var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();
18
+ </script><script async src="https://www.googletagmanager.com/gtag/js?id=UA-150860401-1"></script><script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);};gtag('js',new Date());gtag('config','UA-150860401-1');
19
+ </script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/fixedPage.min.js"></script><meta name="generator" content="Hexo 6.3.0"></head><body><div class="body_container"><div id="header"><div class="site-name"><a id="logo" href="/.">前端壹菜鸟</a><p class="description">关注前端知识,收集精彩博文,做技术的搬运工</p></div><div id="nav-menu"><a class="current" href="/." target="_self"><i class="fa fa-home"> 首页</i></a><a href="/archives/" target="_self"><i class="fa fa-archive"> 归档</i></a><a href="/guestbook/" target="_self"><i class="fa fa-comments"> 留言</i></a><a href="/about/" target="_self"><i class="fa fa-user"> 关于</i></a><a href="https://laonongmin.online/" target="_target"><i class="fa fa-group"> 看看</i></a><a href="https://love.xuehuayu.cn/" target="_target"><i class="fa fa-heart"> LOVE</i></a></div></div><div class="pure-g" id="layout"><div class="pure-u-1 pure-u-md-3-4"><div class="content_container"><div class="post"><h1 class="post-title c-post">MVVM 双向绑定全量版整理</h1><div class="post-meta"><span class="date">2019-12-02</span><span> | </span><span class="tag"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载 </a></span><span> | </span><span class="category"><a href="/categories/%E5%89%8D%E7%AB%AF/">前端 </a></span><span id="busuanzi_container_page_pv"> | <span id="busuanzi_value_page_pv"><i class="fa fa-spinner"></i></span><span> 阅读</span></span><span class="post-time"><span class="post-meta-item-text"> | </span><span class="post-meta-item-icon"><i class="fa fa-keyboard-o"></i><span class="post-count"> 4.4k</span><span class="post-meta-item-text"> 字</span></span></span><span class="post-time"> | <span class="post-meta-item-icon"><i class="fa fa-hourglass-half"></i><span class="post-count"> 17</span><span class="post-meta-item-text"> 分钟</span></span></span></div><a class="disqus-comment-count" href="/article/52877.html#vcomment"><span class="waline-comment-count" data-path="/article/52877.html"></span><span> 条评论</span></a><div class="post-content"><p><code>原文地址:https://www.infoq.cn/article/sGeiocEofyryEbrkYH30</code></p>
20
+ <blockquote>
21
+ <p>随着各大前端框架的崛起,前端技术热词不断演进,我们只有知道发展的原因,才能去理解各项技术的优劣,根据应用的实际情况做出最合适的技术栈选择。<br>当前前端领域的前沿特性,双向绑定必占一席,双向绑定是怎么来的?各大框架如何实现双向绑定?我们怎样做出选择?本文对此作了全面整理说明。</p>
22
+ </blockquote>
23
+ <span id="more"></span>
24
+
25
+ <h2 id="发展背景"><a href="#发展背景" class="headerlink" title="发展背景"></a>发展背景</h2><p>早期的 Web 开发主要基于 MVC 模式,MVC 即<code>Model-View-Controller</code>的缩写,表示模型 - 视图 - 控制器,一个标准的 Web 应用组成如下:</p>
26
+ <ul>
27
+ <li>View 用来把数据以某种方式呈现给用户;</li>
28
+ <li>Model 数据;</li>
29
+ <li>Controller 接收并处理来自用户的请求,将 Model 返回给用户。</li>
30
+ </ul>
31
+ <p><img src="https://static001.infoq.cn/resource/image/2d/62/2d7ef5326723b4c661d7b8a0b23c6262.png" alt="MVVM双向绑定全量版整理"></p>
32
+ <p>这种 MVC 架构模式对于简单的应用是合适的,符合软件架构分层的思想。但随着 H5 的发展以及当前各种页面复杂操作行为及数据的出现,MVC 暴露出了痛点问题:</p>
33
+ <ol>
34
+ <li>代码中大量调用 DOM API 时,处理繁琐,使得代码难以维护。</li>
35
+ <li>当 Model 频繁发生变化时,开发者需要主动更新到 View;用户操作导致 Model 发生变化时,同样需要将数据同步到 Model 中,很难维护复杂多变的数据状态。</li>
36
+ </ol>
37
+ <p>MVVM 是<code>Model-View-ViewModel</code>的缩写,VM 代替了 C,改变了通信方向,View 与 Model 不发生联系,通过 ViewModel 传递。可以做到 View 层用户操作时,ViewModel 层数据同步更新,ViewModel 数据变化时,支持同步更新到 View 层,提升了数据频繁变化时的代码可维护性。这里的 View 与 ViewModel 之间的双向同步过程,我们称之为双向绑定。</p>
38
+ <p><img src="https://static001.infoq.cn/resource/image/a2/29/a21198875ea2b0b69da2dfb091729329.png" alt="MVVM双向绑定全量版整理"></p>
39
+ <p>用一张动图来感受一下,表单 change 产生数据变化时自动更新 ViewModel, ViewModel 因外界事件导致数据改变时会同步到 View。</p>
40
+ <p><img src="https://static001.infoq.cn/resource/image/58/5a/583eb02cb586372f2d7345910dff675a.gif" alt="MVVM双向绑定全量版整理"></p>
41
+ <p>当前热门的前端框架 Angular 和 Vue 都主打 MVVM 模式,建立视图层与视图模型层之间的数据连接,可以轻松实现表单变化的数据反馈到模型层。而 React 框架则推荐单向数据流,使用自身 render 机制完成视图渲染,实际上只担任了 View 层。我们来看一看各个不同框架对此都做了怎样的工作。</p>
42
+ <h2 id="Angular-的脏值检测"><a href="#Angular-的脏值检测" class="headerlink" title="Angular 的脏值检测"></a>Angular 的脏值检测</h2><p>脏值检测是 Angular 的数据更新思路,”脏值“意为<code>dirty data</code>,表示当前数据与上一轮 UI 更新的数据不同,Angular 通过监听数据异步更新,采用比较不同 component 组件中方式更新 DOM。总结起来, 主要有如下几种情况可能改变数据:</p>
43
+ <ul>
44
+ <li>用户输入事件,比如 click 事件;</li>
45
+ <li>请求服务端数据 (XHR);</li>
46
+ <li>定时事件,比如 setTimeout,setInterval。</li>
47
+ </ul>
48
+ <p>上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的,如下为 JavaScript 的异步机制:</p>
49
+ <p><img src="https://static001.infoq.cn/resource/image/3a/ba/3aa2a1836119b9e2848fbc8054c9e1ba.png" alt="MVVM双向绑定全量版整理"></p>
50
+ <p>左边表示将要运行的代码,这里的 stack 表示 JavaScript 的运行栈,而 WebApi 则是浏览器中提供的一些 JavaScript 的 API,TaskQueue 表示 JavaScript 中任务队列,因为 JavaScript 是单线程的,异步任务在任务队列中执行。如果这些异步的事件在发生时能够执行 Angular 重写的异步事件,通知到 Angular 框架,那么 Angular 就能及时的检测到变化。Angular 在这里使用了 Zone.js 做异步处理的脏值检测,细节可以查看<a target="_blank" rel="noopener" href="https://blog.strongbrew.io/how-the-hell-do-zones-really-work/">《Zone.js 究竟是如何工作的》</a>以及<a target="_blank" rel="noopener" href="https://github.com/angular/zone.js"> angular&#x2F;zone.js 源码</a>。</p>
51
+ <p>Angular 的每一个 Component 都对应有一个<code>changeDetector</code>,意为变化检测器。由于我们的多个 Component 是一个树状结构的组织,一个 Component 对应一个 changeDetector,所以 changeDetector 之间同样是一个树状结构的组织。</p>
52
+ <p>我们用一个图例来进行说明检测到脏值变化后的更新过程,下图每一个模块都是一个 changeDetector 变化检测器,红色区块表示有 UI 更新的变化检测器,左侧为脏值变化检测前,右侧为有脏值变化时的 UI 更新。根据上述 EventLoop 机制,Angular 框架层捕获到异步事件对一个 component 数据更新后,从根组件开始,通知当前组件链路及以下链路,进行 View 层更新 (OnPush 模式)。因此,无论是从 V 层出发的用户输入事件,还是 VM 处产生的定时、Http 事件,都能保证数据更新、界面更新!</p>
53
+ <p><img src="https://static001.infoq.cn/resource/image/95/1a/9566609341cb845eb5083b742cc8761a.png" alt="MVVM双向绑定全量版整理"></p>
54
+ <p><img src="https://static001.infoq.cn/resource/image/80/ef/80640256e264514f7a51a7f810710def.jpg" alt="MVVM双向绑定全量版整理"></p>
55
+ <p>但是自 Angular1 推出脏值检测机制以来,在性能上一直饱受质疑,原因如下:</p>
56
+ <ol>
57
+ <li>如果有一个 ComponentA 数据修改,影响了下路 ComponentB。</li>
58
+ <li>下路的 componentB 数据在更新时触发了数据调整机制,下路 Component 更新的短时间过程中,又影响了 ComponentA。那么最终是否会形成 ComponentA、ComponentB 的检测循环?</li>
59
+ </ol>
60
+ <p>对于以上问题,Angular1 的解决方式是,如果有 10 次往复循环,就不再进行继续检测。无论是性能还是结果,都不能令人满意。因此 Angular2 以脱胎换骨的方式进行了重构,支持 dev 开发模式与 prod 线上模式,prod 线上模式只进行单向数据流检测,并支持设置<code>ChangeDetectionStrategy</code>变化检测策略,具备局部组件 View 层更新能力,因此性能上有了明显突破。</p>
61
+ <p><img src="https://static001.infoq.cn/resource/image/4a/69/4a371ea97e7bfc2ca163e0332104e969.png" alt="MVVM双向绑定全量版整理"></p>
62
+ <h2 id="Vue-的数据劫持-发布订阅"><a href="#Vue-的数据劫持-发布订阅" class="headerlink" title="Vue 的数据劫持 + 发布订阅"></a>Vue 的数据劫持 + 发布订阅</h2><p>Vue 的双向绑定策略基础是数据劫持,在 Vue2.0 中使用了 ES5 语法 Object.defineProperty,来劫持各个属性的 setter&#x2F;getter,在数据变动时发布消息给订阅者(Wacther), 触发相应的监听回调。先来看一下这个 ES5 特性,我们可以通过 Object.defineProperty 这个方法,直接在一个对象上定义一个新的属性,或者修改已存在的属性,最终这个方法会返回该对象,如下为简单说明,对该特性不了解的同学可以查看《JavaScript 高级程序设计》的第六章,或者在线访问<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"> MDN Web 文档</a>。</p>
63
+ <p>复制代码</p>
64
+ <pre><code>var o = &#123;&#125;;
65
+
66
+ var value = 1;
67
+
68
+ Object.defineProperty(o, &#39;a&#39;, &#123;
69
+
70
+ get: function() &#123; return value; &#125;,
71
+
72
+ set: function(newValue) &#123; value = newValue; &#125;,
73
+
74
+ enumerable: true,
75
+
76
+ configurable: true
77
+
78
+ &#125;);
79
+
80
+ o.a; // 1
81
+
82
+ o.a = 2;
83
+
84
+ o.a; // 2
85
+ </code></pre>
86
+ <p>结合这一特定与发布订阅机制,可以实现完整的双向绑定。如下所示,Observer 数据监听器能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用 Object.defineProperty 的 getter 和 setter 来实现。</p>
87
+ <blockquote>
88
+ <p>Compile 指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。</p>
89
+ </blockquote>
90
+ <blockquote>
91
+ <p>Watcher 订阅者, 作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数。</p>
92
+ </blockquote>
93
+ <blockquote>
94
+ <p>Dep 消息订阅器,内部维护了一个数组,用来收集订阅者(Watcher),数据变动触发 notify 函数,再调用订阅者的 update 方法。</p>
95
+ </blockquote>
96
+ <p>当执行 new Vue() 时,Vue 就进入了初始化阶段,一方面会遍历 data 选项中的属性,用 Object.defineProperty 将它们转为 getter&#x2F;setter,实现数据变化监听功能;另一方面,Vue 的指令编译器 Compile 对元素节点的指令进行扫描和解析,初始化视图,并订阅 Watcher 来更新视图, 此时 Wather 会将自己添加到消息订阅器中 (Dep), 初始化完毕。当数据发生变化时,Observer 中的 setter 方法被触发,setter 会立即调用 Dep.notify(),Dep 开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。</p>
97
+ <p><img src="https://static001.infoq.cn/resource/image/43/9f/43e47f50f1ad9358a2e9ab4e5d10879f.png" alt="MVVM双向绑定全量版整理"></p>
98
+ <p>使用 Object.defineProperty 这个特性存在一些明显的缺点,总结起来大概是下面两个:</p>
99
+ <blockquote>
100
+ <ol>
101
+ <li><blockquote>
102
+ <p>Object.defineProperty 无法监控到数组下标的变化,当监控数组数据对象的时候,实质上就是监控数组的地址,地址不变也就不会被监测到。为了解决这个问题,经过 Vue 内部处理后可以使用 push、pop、shift、unshift、splice、sort、reverse 来监听数组。</p>
103
+ </blockquote>
104
+ </li>
105
+ <li><blockquote>
106
+ <p>Object.defineProperty 只能劫持对象的属性, 因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。</p>
107
+ </blockquote>
108
+ </li>
109
+ </ol>
110
+ </blockquote>
111
+ <p>由于只针对了以上八种方法进行了 hack 处理, 所以其他数组的属性也是检测不到的,还是具有一定的局限性。Vue3.0 中使用了 ES6 语法 Proxy,用于取代 defineProperty,使用 Proxy 有以下两个优点:</p>
112
+ <blockquote>
113
+ <ol>
114
+ <li>可以劫持整个对象,并返回一个新对象;</li>
115
+ <li>有 13 种劫持操作。</li>
116
+ </ol>
117
+ </blockquote>
118
+ <p>既然 Proxy 能解决以上两个问题,而且 Proxy 作为 ES6 的新属性在 Vue2.x 之前就有了,为什么 Vue2.x 不使用 Proxy 呢?一个很重要的原因就是,Proxy 是 ES6 提供的新特性,兼容性不好,并且这个属性无法用 polyfill 来兼容。</p>
119
+ <p>Vue 的双向绑定策略成为当前考察前端人员技术功底的重点,我们以 Object.defineProperty 特性实现一个简单的双向绑定,实现最初的 hello everyone 效果。</p>
120
+ <p>复制代码</p>
121
+ <pre><code>&lt;!DOCTYPE html&gt;
122
+
123
+ &lt;htmllang=&quot;en&quot;&gt;
124
+
125
+ &lt;head&gt;
126
+
127
+ &lt;title&gt; 双向绑定最最最初级 demo&lt;/title&gt;
128
+
129
+ &lt;metacharset=&quot;UTF-8&quot;&gt;
130
+
131
+ &lt;/head&gt;
132
+
133
+ &lt;body&gt;
134
+
135
+ &lt;divid=&quot;app&quot;&gt;
136
+
137
+ &lt;inputtype=&quot;text&quot;id=&quot;txt&quot;&gt;
138
+
139
+ &lt;pid=&quot;show-txt&quot;&gt;&lt;/p&gt;
140
+
141
+ &lt;buttononClick=&quot;changeData()&quot;&gt; 更新数据 &lt;/button&gt;
142
+
143
+ &lt;/div&gt;
144
+
145
+ &lt;/body&gt;
146
+
147
+ &lt;script&gt;
148
+
149
+ var obj=&#123;&#125;
150
+
151
+ Object.defineProperty(obj,&#39;txt&#39;,&#123;
152
+
153
+ get:function()&#123;
154
+
155
+ return obj
156
+
157
+ &#125;,
158
+
159
+ set:function(newValue)&#123;
160
+
161
+ document.getElementById(&#39;txt&#39;).value = newValue
162
+
163
+ document.getElementById(&#39;show-txt&#39;).innerHTML = newValue
164
+
165
+ &#125;
166
+
167
+ &#125;)
168
+
169
+ document.addEventListener(&#39;keyup&#39;,function(e)&#123;
170
+
171
+ obj.txt = e.target.value
172
+
173
+ &#125;)
174
+
175
+ changeData = function() &#123;
176
+
177
+ obj.txt = &#39;hello world&#39;;
178
+
179
+ &#125;
180
+
181
+ &lt;/script&gt;
182
+
183
+ &lt;/html&gt;
184
+ </code></pre>
185
+ <p>由于<code>Object.defineProperty</code>默认只能劫持值类型数据,对引用类型数据的内部修改无法劫持,需要重写覆盖原原型方法,以 Array 为例,如下可以支持到 7 种数组方法:</p>
186
+ <p>复制代码</p>
187
+ <pre><code>let arr = [];
188
+
189
+ let arrayMethod = Object.create(Array.prototype);
190
+
191
+ [&#39;push&#39;, &#39;pop&#39;, &#39;shift&#39;, &#39;unshift&#39;, &#39;splice&#39;, &#39;sort&#39;, &#39;reverse&#39;].forEach(function (method) &#123;
192
+
193
+ Object.defineProperty(arrayMethod, method, &#123;
194
+
195
+ enumerable: true,
196
+
197
+ configurable: true,
198
+
199
+ value: function () &#123;
200
+
201
+ let args = [...arguments]
202
+
203
+ Array.prototype[method].apply(this, args);
204
+
205
+ console.log(`operation: $&#123;method&#125;`);
206
+
207
+ &#125;
208
+
209
+ &#125;)
210
+
211
+ &#125;);
212
+
213
+ arr.__proto__ = arrayMethod;
214
+
215
+ arr.push(1); // 劫持到了 push 方法
216
+ </code></pre>
217
+ <p>相对完整的仿 Vue 双向绑定实现,来自<a target="_blank" rel="noopener" href="https://gitee.com/merico/Blog/tree/master/Object.defineProperty_Array">双向绑定数组源码</a>。</p>
218
+ <p>复制代码</p>
219
+ <pre><code>&lt;!DOCTYPE html&gt;
220
+
221
+ &lt;htmllang=&quot;en&quot;&gt;
222
+
223
+ &lt;head&gt;
224
+
225
+ &lt;title&gt; 双向绑定支持数组监听 &lt;/title&gt;
226
+
227
+ &lt;metacharset=&quot;UTF-8&quot;&gt;
228
+
229
+ &lt;/head&gt;
230
+
231
+ &lt;body&gt;
232
+
233
+ &lt;divid=&quot;app&quot;&gt;
234
+
235
+ &lt;divid=&#39;list&#39;&gt;&lt;/div&gt;
236
+
237
+ &lt;inputtype=&quot;button&quot;value=&quot; 添加 &quot;onclick=&quot;btnAdd()&quot; /&gt;
238
+
239
+ &lt;inputtype=&quot;button&quot;value=&quot; 删除 &quot;onclick=&quot;btnDel()&quot; /&gt;
240
+
241
+ &lt;/div&gt;
242
+
243
+ &lt;/body&gt;
244
+
245
+ &lt;script&gt;
246
+
247
+ // 数据源
248
+
249
+ let vm = &#123;
250
+
251
+ list: [1, 2, 3, 4]
252
+
253
+ &#125;
254
+
255
+ // 用于管理 watcher 的 Dep 对象
256
+
257
+ let Dep = function () &#123;
258
+
259
+ this.list = [];
260
+
261
+ this.add = function (watcher) &#123;
262
+
263
+ this.list.push(watcher)
264
+
265
+ &#125;;
266
+
267
+ this.notify = function (newValue) &#123;
268
+
269
+ this.list.forEach(function (fn) &#123;
270
+
271
+ fn(newValue)
272
+
273
+ &#125;)
274
+
275
+ &#125;
276
+
277
+ &#125;;
278
+
279
+ // 模拟 compile, 通过对 Html 的解析生成一系列订阅者(watcher)
280
+
281
+ functionrenderList() &#123;
282
+
283
+ let listContainer = document.querySelector(&#39;#list&#39;);
284
+
285
+ let contentList = &#39;&#39;;
286
+
287
+ vm.list.forEach(function (item) &#123;
288
+
289
+ contentList = contentList + `&lt;div&gt;&lt;h3&gt;$&#123;item&#125;&lt;/h3&gt;&lt;/div&gt;`
290
+
291
+ &#125;)
292
+
293
+ listContainer.innerHTML = contentList;
294
+
295
+ &#125;
296
+
297
+ // 将解析出来的 watcher 存入 Dep 中待用
298
+
299
+ let dep = new Dep();
300
+
301
+ dep.add(renderList)
302
+
303
+ // 核心方法
304
+
305
+ functioninitMVVM(vm) &#123;
306
+
307
+ Object.keys(vm).forEach(function (key) &#123;
308
+
309
+ let value = vm[key];
310
+
311
+ if (Array.isArray(value)) &#123;
312
+
313
+ observeArray(vm, key)
314
+
315
+ &#125;
316
+
317
+ &#125;)
318
+
319
+ &#125;
320
+
321
+ functionobserveArray(vm, key) &#123;
322
+
323
+ let arrayMethod = bindWatcherToArray();
324
+
325
+ vm[key].__proto__ = arrayMethod;
326
+
327
+ &#125;
328
+
329
+ functionbindWatcherToArray() &#123;
330
+
331
+ let arrayMethod = Object.create(Array.prototype);
332
+
333
+ [&#39;push&#39;, &#39;pop&#39;, &#39;shift&#39;, &#39;unshift&#39;, &#39;splice&#39;, &#39;sort&#39;, &#39;reverse&#39;].forEach(function (method) &#123;
334
+
335
+ Object.defineProperty(arrayMethod, method, &#123;
336
+
337
+ enumerable: true,
338
+
339
+ configurable: true,
340
+
341
+ value: function () &#123;
342
+
343
+ let args = [...arguments]
344
+
345
+ Array.prototype[method].apply(this, args);
346
+
347
+ console.log(`operation: $&#123;method&#125;`)
348
+
349
+ dep.notify();
350
+
351
+ &#125;
352
+
353
+ &#125;)
354
+
355
+ &#125;);
356
+
357
+ return arrayMethod
358
+
359
+ &#125;
360
+
361
+ // 页面引用的方法
362
+
363
+ functionbtnAdd() &#123;
364
+
365
+ vm.list.push(Math.random())
366
+
367
+ &#125;
368
+
369
+ functionbtnDel() &#123;
370
+
371
+ vm.list.pop()
372
+
373
+ &#125;
374
+
375
+ // 初始化数据源
376
+
377
+ initMVVM(vm)
378
+
379
+ // 初始化页面
380
+
381
+ dep.notify();
382
+
383
+ &lt;/script&gt;
384
+
385
+ &lt;/html&gt;
386
+ </code></pre>
387
+ <h2 id="React-的工具辅助"><a href="#React-的工具辅助" class="headerlink" title="React 的工具辅助"></a>React 的工具辅助</h2><p>React 推荐单向数据流,目标从来不是“让开发者写更少的代码”,而是让“代码结构更加清晰易于维护”。加上有如下 Redux 的状态管理方案,可以很清楚地了解应用中状态数据,体现其单向数据流的优势。</p>
388
+ <p><img src="https://static001.infoq.cn/resource/image/ea/73/ea7cc8ee37c9cc3d36b83a8a8415aa73.png" alt="MVVM双向绑定全量版整理"></p>
389
+ <p>由于 React 推荐单向数据流,没有上述 Angular 和 Vue 的双向绑定特性,如果出现似表单类用户视图和存储数据有同步的业务场景,我们需要怎么实现?Redux 的重型状态管理不适合应用于每个场景。一般在 React 里的表单,我们可以监听 “change” 事件来实现数据变更,默认写法是从数据源(通常是 DOM)读取并在我们的某个组件调用 setState() , 如下代码为常规的表单使用方式:</p>
390
+ <p>复制代码</p>
391
+ <pre><code>var NoLink = React.createClass(&#123;
392
+
393
+ getInitialState: function() &#123;
394
+
395
+ return &#123;message: &#39;Hello!&#39;&#125;;
396
+
397
+ &#125;,
398
+
399
+ handleChange: function(event) &#123;
400
+
401
+ this.setState(&#123;message: event.target.value&#125;);
402
+
403
+ &#125;,
404
+
405
+ render: function() &#123;
406
+
407
+ var message = this.state.message;
408
+
409
+ return&lt;inputtype=&quot;text&quot;value=&#123;message&#125;onChange=&#123;this.handleChange&#125; /&gt;;
410
+
411
+ &#125;
412
+
413
+ &#125;);
414
+ </code></pre>
415
+ <p>以上写法每出现一个表单,就需要绑定一个事件,在只有少量表单时还能使用,一旦表单增多,维护大量 value 和 onChange 成了 React 的痛点。React 官方也提供了一种方案 ReactLink:设置如上代码描述的通用数据回流模式的语法糖,或者 “linking” 某些数据结构到 React state,做一层对 onChange 和 setState() 模式的薄包装。它没有根本性地改变你的 React 应用里数据如何流动,下面是 ReactLink 提供的使用方式:</p>
416
+ <p>复制代码</p>
417
+ <pre><code>var LinkedStateMixin = require(&#39;react-addons-linked-state-mixin&#39;);
418
+
419
+ var WithLink = React.createClass(&#123;
420
+
421
+ mixins: [LinkedStateMixin],
422
+
423
+ getInitialState: function() &#123;
424
+
425
+ return &#123;message: &#39;Hello!&#39;&#125;;
426
+
427
+ &#125;,
428
+
429
+ render: function() &#123;
430
+
431
+ return&lt;inputtype=&quot;text&quot;valueLink=&#123;this.linkState(&#39;message&#39;)&#125; /&gt;;
432
+
433
+ &#125;
434
+
435
+ &#125;);
436
+ </code></pre>
437
+ <p>实际项目中,我们几乎不会使用官方提供的 createClass 方案,毕竟写法受限。如何减少 value 与 onChange 的使用,简化 React 下的表单开发,成为了大量轮子制造工程的出发点。下述各团队产出的 form 表单解决方案都给出了一定的方式,以及其他各种平台下的开源轮子数不胜数,可以选择一两种进行了解。</p>
438
+ <ul>
439
+ <li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/56280821">阿里 Fusion:Form 表单组件设计之路 - 高易用性</a></li>
440
+ <li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/51748223">阿里飞冰: Ice FormBinder </a></li>
441
+ <li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/62927004?spm=ata.13261165.0.0.545c6945ca8kKm">阿里供应链平台:面向复杂场景的高性能表单解决方案 - UForm </a></li>
442
+ <li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/44120143">阿里 NoForm:一个更好的表单解决方案</a></li>
443
+ <li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/57820186"> React 实现高度简洁的 Form 组件</a></li>
444
+ <li><a href="https://link.zhihu.com/?target=https://redux-form.com/7.0.3/"> redux-form </a></li>
445
+ <li><a href="https://link.zhihu.com/?target=https://github.com/final-form/final-form"> final-form </a></li>
446
+ </ul>
447
+ <p>追根究底,都是对大量的 value 与 onChange 进行整合,将表单 DOM 的使用方式简化,由辅助函数统一控制。我们可以通过处理函数通用化,来模拟文中提到的动态双向绑定效果。</p>
448
+ <p>复制代码</p>
449
+ <pre><code>import React, &#123;Component&#125; from&#39;react&#39;
450
+
451
+ exportdefaultclassHelloextendsComponent&#123;
452
+
453
+ state = &#123; val: &#39;&#39; &#125;;
454
+
455
+ handleInput = _event =&gt; &#123;
456
+
457
+ let event = _event;
458
+
459
+ let elem = event.target;
460
+
461
+ let value = elem.value;
462
+
463
+ if (elem.attributes.bindField !== null) &#123;
464
+
465
+ let attr = elem.attributes.bindField.value;
466
+
467
+ this.setState(state =&gt; state[attr] = value);
468
+
469
+ &#125;
470
+
471
+ &#125;
472
+
473
+ updateValue = (getFiled, getValue) =&gt; &#123;
474
+
475
+ let fieldNodeList = [...document.querySelectorAll(&#39;[bindField]&#39;)];
476
+
477
+ let fieldNode = fieldNodeList.find(node =&gt; node.attributes[0].nodeValue === getFiled);
478
+
479
+ fieldNode.value = getValue;
480
+
481
+ let attr = fieldNode.attributes.bindField.value;
482
+
483
+ this.setState(state =&gt; state[attr] = getValue);
484
+
485
+ &#125;
486
+
487
+ render() &#123;
488
+
489
+ return (
490
+
491
+ &lt;div&gt;
492
+
493
+ &lt;inputonInput=&#123;this.handleInput&#125;bindField=&#123;&#39;val&#39;&#125; /&gt;
494
+
495
+ &lt;p&gt;&#123;this.state.val&#125;&lt;/p&gt;
496
+
497
+ &lt;buttononClick=&#123;() =&gt; this.updateValue(&#39;val&#39;, &#39;hello world&#39;)&#125;&gt; 设置 &lt;/button&gt;
498
+
499
+ &lt;/div&gt;
500
+
501
+ )
502
+
503
+ &#125;
504
+
505
+ &#125;
506
+ </code></pre>
507
+ <h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>关于 MVVM 双向绑定的策略百花齐放,各有利弊,Angular 与 Vue 通过不同策略直接将双向绑定特性植入框架中,React 推荐单向数据流,但也确实存在不少需要减少编码的双向绑定场景,因此涌现了大量双向绑定辅助库。</p>
508
+ <p>Angular 框架大而全,主要由 Google 团队维护,从特性上看适用于中后台应用,每个关键模块都有官方主导,因此更为稳定。React 实际上仅为一个视图层 Js 库,由 Facebook 推出,但经过社区整合,已经形成了完整的生态。Vue 属于后起之秀,没有大厂背景依靠,但由于使用简单,渲染快,在国内市场增速明显,周边生态也已成熟。</p>
509
+ <p>无论技术如何实现,只有适合自己当前业务场景的策略才是最好的解决方案。</p>
510
+ <p><strong>参考附录:</strong></p>
511
+ <ol>
512
+ <li><a target="_blank" rel="noopener" href="https://www.cnblogs.com/onepixel/p/6034307.html">《Vue.js 和 MVVM 小细节》</a></li>
513
+ <li><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000010928087">《angular 系列之变化检测 (Change Detection)》</a></li>
514
+ <li><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/860418f0785c">《vue3.0 尝鲜 – 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索》</a></li>
515
+ <li><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000011225943?utm_source=tag-newest">《实现一个简单的双向绑定》</a></li>
516
+ <li><a target="_blank" rel="noopener" href="https://reactjs.org/docs/two-way-binding-helpers.html">《react 官网双向绑定辅助工具》</a></li>
517
+ </ol>
518
+ <p><strong>作者介绍:</strong></p>
519
+ <p>飞来,就职于阿里巴巴 CBU 体验技术部,一个以前常写 Angular,刚开始转 React,研究过但没真正写 Vue 的前端人。</p>
520
+ </div><iframe src="/donate/?AliPayQR=https://store.xuehuayu.cn/store/public/alipay_qr.jpg&amp;WeChatQR=https://store.xuehuayu.cn/store/public/wechat_qr.png&amp;UnionPayQR=null&amp;GitHub=null&amp;BTCQR=null&amp;BTCKEY=null&amp;PayPal=https://www.paypal.com/paypalme/OrdinaryPerson" style="overflow-x:hidden; overflow-y:hidden; border:0xp none #fff; min-height:240px; width:100%;" frameborder="0" scrolling="no"></iframe><div><ul class="post-copyright"><li class="post-copyright-author"><strong>本文作者:</strong>雪花雨</li><li class="post-copyright-link"><strong>本文链接:</strong><a href="/article/52877.html">https://xuehuayu.cn/article/52877.html</a></li><li class="post-copyright-license"><strong>版权声明:</strong>① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 转载文章来自网络,已标明出处,<a href="mailto:cainiaoblog@126.com">侵删</a>。</li></ul></div><br><div class="tags"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载</a></div><div class="post-nav"><a class="pre" href="/article/59059.html">20道JS原理题助你面试一臂之力!</a><a class="next" href="/article/34431.html">Node新版本13.2.0正式支持ES Modules特性</a></div><div id="vcomment"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.min.css"><script src="//cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.js"></script><script>const origin = window.location.origin
521
+ const serverURL = origin.includes('cainiaoblog') ? 'https://cmts.cainiaoblog.cn' : 'https://cmts.xuehuayu.cn'
522
+ const locale = {
523
+ placeholder: '请正确填写昵称和邮箱,方便接收回复通知~',
524
+ sofa: '沙发空缺中,还不快抢~',
525
+ };
526
+ Waline.init({
527
+ el: '#vcomment',
528
+ serverURL: serverURL,
529
+ pageSize: '20',
530
+ visitor: true, // 阅读量统计
531
+ requiredMeta: ['nick', 'mail'],
532
+ emoji: [
533
+ 'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/weibo',
534
+ 'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/qq',
535
+ 'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/tw-emoji',
536
+ ],
537
+ locale,
538
+ pageview: true
539
+ })
540
+ </script></div></div></div><div class="pure-u-1 pure-u-md-1-4 fixed-search hidden_mid_and_down"><div id="sidebar"><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/search.min.js"></script><div class="widget"><div class="widget-search"><input class="search" type="radio" name="search" value="baidu" id="baidu" checked="checked"/><label class="label" for="baidu" title="百度全站搜索">百度</label><input class="search" type="radio" name="search" value="google" id="google"/><label class="label" for="google" title="谷歌全站搜索">谷歌</label><input class="search" type="radio" name="search" value="self" id="self"/><label class="label" for="self" title="使用站内搜索">站内</label></div><div class="widget" id="search"><form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"/><input type="hidden" name="si" value="xuehuayu.cn"/><input type="hidden" name="cl" value="3"/><input type="hidden" name="ct" value="2097152"/><input type="hidden" name="s" value="on"/><input class="search-submit" type="submit" value=""/></form></div></div><script>$('input[type=radio][name=search]').change(function() {
541
+ var val = $(this).val()
542
+ var self = '<div class="search-form"><input id="local-search-input" placeholder="站内搜索,首次慢" type="search" name="q" results="0"><input class="search-submit" type="submit" value=""/><div id="local-search-result"></div></div>'
543
+ var google = '<form class="search-form" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="q" maxlength="30" placeholder="谷歌全站搜索"><input type="hidden" name="sitesearch" value="xuehuayu.cn"><input class="search-submit" type="submit" value=""/></form>'
544
+ var baidu = '<form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"><input type="hidden" name="si" value="xuehuayu.cn"><input type="hidden" name="cl" value="3"><input type="hidden" name="ct" value="2097152"><input type="hidden" name="s" value="on"><input class="search-submit" type="submit" value=""/></form>'
545
+
546
+ if (val === 'self') {
547
+ $('#search').html(self)
548
+ var search_path = 'search.xml';
549
+ if (search_path.length == 0) {
550
+ search_path = 'search.xml';
551
+ }
552
+ var path = 'https://cdn.staticaly.com/gh/npljy/npljy.github.io/main/' + search_path;
553
+ searchFunc(path, 'local-search-input', 'local-search-result');
554
+ } else if (val === 'baidu') {
555
+ $('#search').html(baidu)
556
+ } else if (val === 'google') {
557
+ $('#search').html(google)
558
+ }
559
+ })</script><div class="widget widget-wxmp"><img alt="微信公众号" width="100%" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/mp-mini.jpg"/></div><div class="widget widget-categories"><div class="widget-title"><i class="fa fa-folder-o"> 分类</i></div><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/CDN/">CDN</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/IDE/">IDE</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/hexo/">hexo</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%85%AC%E5%85%B1%E8%8A%82%E6%97%A5/">公共节日</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%88%86%E4%BA%AB/">分享</a><span class="category-list-count">29</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/">前端</a><span class="category-list-count">191</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/">网络协议</a><span class="category-list-count">1</span></li></ul></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%8D%9A%E5%AE%A2/">博客</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B7%A5%E5%85%B7/">工具</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/">广告过滤</a><span class="category-list-count">8</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/%E8%A7%86%E9%A2%91%E7%94%B5%E8%A7%86%E5%B0%8F%E8%AF%B4%E6%BC%AB%E7%94%BB%E8%B5%84%E8%AE%AF%E9%9F%B3%E4%B9%90/">视频电视小说漫画资讯音乐</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%AD%A3%E5%88%99/">正则</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E7%AE%97%E6%B3%95/">算法</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%B5%84%E8%AE%AF/">资讯</a><span class="category-list-count">16</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%BD%BB%E6%9D%BE%E4%B8%80%E5%88%BB/">轻松一刻</a><span class="category-list-count">1</span></li></ul></div><div class="widget widget-tags"><div class="widget-title"><i class="fa fa-star-o"> 标签</i></div><div class="tagcloud"><a href="/tags/%E5%8E%9F%E5%88%9B/" style="font-size: 15px;">原创</a> <a href="/tags/%E8%BD%AC%E8%BD%BD/" style="font-size: 15px;">转载</a> <a href="/tags/%E6%89%8B%E5%86%99/" style="font-size: 15px;">手写</a> <a href="/tags/%E7%96%AB%E6%83%85/" style="font-size: 15px;">疫情</a> <a href="/tags/%E5%A8%B1%E4%B9%90/" style="font-size: 15px;">娱乐</a></div></div><div class="widget widget-recent-posts"><div class="widget-title"><i class="fa fa-file-o"> 最近文章</i></div><ul class="post-list"><li class="post-list-item"><a class="post-list-link" href="/article/c5576490.html">面试准备</a></li><li class="post-list-item"><a class="post-list-link" href="/article/84c9ccd6.html">精读《SolidJS》</a></li><li class="post-list-item"><a class="post-list-link" href="/article/53803e6.html">git提交注释规范</a></li><li class="post-list-item"><a class="post-list-link" href="/article/a4145266.html">前端数组拍平flat一行代码</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bedea419.html">一道前端this面试题</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bf7e7421.html">git多账号配置</a></li></ul></div><div class="widget widget-links"><div class="widget-title"><i class="fa fa-external-link"> 友情链接</i></div><ul></ul><a href="https://github.com/npljy?utm_source=xuehuayu.cn" title="GitHub" target="_blank">GitHub</a><ul></ul><a href="https://laonongmin.online?utm_source=xuehuayu.cn" title="看看" target="_blank">看看</a><ul></ul><a href="https://cainiaoblog.cn?utm_source=xuehuayu.cn" title="菜鸟博客" target="_blank">菜鸟博客</a><ul></ul><a href="mailto:cainiaoblog@126.com" title="友链联系" target="_blank">友链联系</a></div></div></div><div class="pure-u-1 pure-u-md-3-4"><div id="footer"><div class="flex-block justify-center align-center flex-wrap"><a class="gxba-link" id="gxba" rel="nofollow" target="_blank" href="http://beian.miit.gov.cn/">京ICP备20007647号-2</a><a class="gaba-link" id="gaba-link" rel="nofollow" target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031264"><img class="nofancybox" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/gaba.png" alt=""/><span id="gaba">京公网安备 11010802031264号</span><span style="padding-right: 10px;"></span></a><span>Copyright © 2023 </span><a href="/." rel="nofollow">前端壹菜鸟. </a><script>(function(){
560
+ var cnb =window.location.origin.indexOf('cainiaoblog') !==-1
561
+ if (cnb) {
562
+ var gxba =document.getElementById('gxba')
563
+ var gaba =document.getElementById('gaba')
564
+ var gabaLink =document.getElementById('gaba-link')
565
+ gxba.innerText ='京ICP备20007647号-1'
566
+ gaba.innerText ='京公网安备 11010802031254号'
567
+ gabaLink.setAttribute('href','http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031254')
568
+ }
569
+ })()</script></div><div class="flex-block justify-center align-center flex-wrap"><a rel="nofollow" target="_blank" href="https://v6.51.la/s/Guz3lwHCsVme9Cu"><div class="busuanzi_container" id="busuanzi_container"><span class="busuanzi_container_site_pv" id="busuanzi_container_site_pv">访问量:<span id="busuanzi_value_site_pv"><i class="fa fa-spinner"></i></span></span><span class="busuanzi_container_site_uv" id="busuanzi_container_site_uv">访客数:<span id="busuanzi_value_site_uv"><i class="fa fa-spinner"></i></span></span><span style="padding-right: 10px;"></span></div></a><a rel="nofollow" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral"><span style="font-weight:bold;letter-spacing: 1px;">本网站由 </span><img class="nofancybox" height="32" style="vertical-align: middle" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/upy_logo.min.svg" alt=""/><span style="font-weight:bold;letter-spacing: 1px;">提供CDN加速/云存储服务</span></a><span style="padding-right: 10px;"></span><a rel="nofollow" style="font-size:18px;font-weight:bold;vertical-align: middle" target="_blank" title="注册就送代金券可直接使用" href="https://console.upyun.com/register/?invite=HyDsjZHIL">注册</a></div></div></div></div><a class="show" id="rocket" href="#top"></a><div class="darkmode-toggle">🌓</div><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/totop.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/dark.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/codeblock-resizer.min.js"></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/smartresize.min.js"></script></div></body></html>