├── 0 ├── 0.1.4.md ├── 0.1.5.md ├── 0.1.6.md ├── 0.1.1.md ├── 0.1.3.md └── 0.1.2.md ├── 1 ├── 1.2.7.md ├── 1.5.4.md ├── 1.2.14.md ├── 1.1.2.md ├── 1.4.5.md ├── 1.2.11.md ├── 1.5.3.md ├── 1.2.6.md ├── 1.3.3.md ├── 1.2.12.md ├── 1.2.5.md ├── 1.4.2.md ├── 1.4.3.md ├── 1.4.4.md └── 1.3.4.md ├── 2 ├── 2.1.2.md ├── 2.0.md ├── 2.2.2.md ├── 2.1.6.md ├── 2.1.4.md ├── 2.1.3.md └── 2.1.7.md ├── 3 ├── 3.2.1.md ├── 3.1.2.md └── 3.1.1.md ├── 4 ├── 4.3.2.md ├── transition │ ├── ease.png │ ├── chrome.png │ ├── ease-in.png │ ├── linear.png │ ├── ease-out.png │ ├── steps-end.png │ ├── ease-in-out.png │ └── steps-start.png ├── si-.1.1-shen-ru-li-jie-webpack-da-bao.md └── 4.2.1.md ├── 5 ├── 5.3.2.md ├── 5.2.3.md ├── 5.3.1.md ├── 5.1.6.md ├── 5.2.2.md ├── 5.1.1.md └── 5.1.5.md ├── 6 ├── 6.4.1.md ├── 6.1.0.md ├── 6.3.3.md ├── 6.1.3.md ├── 6.1.4.md ├── 6.1.2.md ├── 6.3.2.md ├── 6.2.5.md └── 6.2.3.md ├── 7 ├── 7.1.6.md ├── 7.1.7.md ├── 7.1.9.md ├── 7.1.10.md ├── 7.1.11.md ├── 7.1.12.md ├── 7.1.13.md ├── 7.1.4.md ├── 7.1.8.md ├── 7.2.1.md ├── 7.1.14.md ├── 7.1.3.md ├── 7.1.5.md ├── 7.1.1.md └── 7.1.2.md ├── 9 └── 9.1.md ├── 10 ├── 10.2.md ├── 10.1-1.md └── 10.1.md ├── _config.yml ├── .gitbook └── assets │ ├── 1.3.1.6.png │ ├── 1.5.2.1.jpg │ ├── 2.1.1.0.png │ ├── 2.1.1.4.jpg │ ├── 6.1.0.jpeg │ ├── 6.2.3.6.png │ ├── 9.1.1.jpg │ ├── 9.1.2.1.jpg │ ├── 9.1.2.2.jpg │ ├── 9.1.3.1.jpg │ ├── 9.1.3.2.jpg │ ├── 9.1.4.1.jpg │ ├── 9.1.5.1.jpg │ ├── 9.1.7.1.jpg │ ├── 9.1.8.1.jpg │ ├── baidu.jpeg │ ├── bobma.jpg │ ├── image.png │ ├── 1.2.6.4.1.png │ ├── 1.5.2.3.1.jpg │ ├── 1.5.2.3.2.jpg │ ├── 1.5.2.4.1.jpg │ ├── 2.1.1.0.1.png │ ├── 3.1.1.3.1.jpg │ ├── 5.3.1.1.1.png │ ├── 6.3.1.0.1.png │ ├── 6.3.1.2.1.png │ ├── 6.3.1.2.2.png │ ├── 6.3.1.2.3.png │ ├── 6.3.1.2.4.png │ ├── 6.3.1.2.5.png │ ├── 6.3.1.3.1.png │ ├── 7.1.14.1.png │ ├── copyright.png │ ├── image (1).png │ ├── image (5).png │ ├── image (6).png │ ├── image (7).png │ ├── image (8).png │ ├── image (9).png │ ├── 2.1.1.4-Heap.gif │ ├── edan-qrcode.png │ ├── image (11).png │ ├── image (12).png │ ├── image (13).png │ ├── image (14).png │ ├── image (15).png │ ├── image (16).png │ ├── image (17).png │ ├── image (18).png │ ├── image (19).png │ ├── image (20).png │ ├── image (21).png │ ├── image (22).png │ ├── image (24).png │ ├── image (25).png │ ├── image (26).png │ ├── image (27).png │ ├── image (28).png │ ├── image (29).png │ ├── image (30).png │ ├── image (31).png │ ├── image (32).png │ ├── image (33).png │ ├── image (35).png │ ├── image (37).png │ ├── image (38).png │ ├── image (39).png │ ├── image (42).png │ ├── john-resig.jpg │ ├── wechatimg21.png │ ├── 2.1.1.1-Bubble.gif │ ├── 2.1.1.10-Radix.gif │ ├── 2.1.1.5-Merge.gif │ ├── 2.1.1.6-Quick.gif │ ├── 2.1.1.7-Shell.gif │ ├── 2.1.1.9-Bucket.gif │ ├── cover20200504.jpg │ ├── cover20200619.jpg │ ├── image (1) (1).png │ ├── image (14) (1).png │ ├── image (19) (1).png │ ├── image (33) (1).png │ ├── image (35) (1).png │ ├── image (37) (1).png │ ├── image (5) (1).png │ ├── image (6) (1).png │ ├── image (7) (1).png │ ├── monotone-stack.gif │ ├── 2.1.1.2-Selection.gif │ ├── 2.1.1.3-Insertion.gif │ ├── 2.1.1.8-Counting.gif │ ├── image (1) (1) (1).png │ ├── image (6) (1) (1).png │ ├── image (7) (1) (1).png │ ├── makai_greatwall.png │ ├── image (14) (1) (1).png │ └── image (35) (1) (1).png ├── code ├── 1.2.1-2.js ├── 1.2.1-3.js ├── 1.2.1-4.js └── 1.2.1-1.js ├── LICENSE.md ├── 0.0.1.md └── SUMMARY.md /7/7.1.6.md: -------------------------------------------------------------------------------- 1 | # 柒.1.6 命令模式 -------------------------------------------------------------------------------- /7/7.1.7.md: -------------------------------------------------------------------------------- 1 | # 柒.1.7 组合模式 -------------------------------------------------------------------------------- /7/7.1.9.md: -------------------------------------------------------------------------------- 1 | # 柒.1.9 享元模式 -------------------------------------------------------------------------------- /7/7.1.10.md: -------------------------------------------------------------------------------- 1 | # 柒.1.10 职责链模式 -------------------------------------------------------------------------------- /7/7.1.11.md: -------------------------------------------------------------------------------- 1 | # 柒.1.11 中介者模式 -------------------------------------------------------------------------------- /7/7.1.12.md: -------------------------------------------------------------------------------- 1 | # 柒.1.12 装饰者模式 -------------------------------------------------------------------------------- /7/7.1.13.md: -------------------------------------------------------------------------------- 1 | # 柒.1.13 状态模式 -------------------------------------------------------------------------------- /7/7.1.4.md: -------------------------------------------------------------------------------- 1 | # 柒.1.4 迭代器模式 -------------------------------------------------------------------------------- /7/7.1.8.md: -------------------------------------------------------------------------------- 1 | # 柒.1.8 模板方法模式 -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /4/4.3.2.md: -------------------------------------------------------------------------------- 1 | # 肆.3.2 适用于前端开发者的20个VSCode插件 2 | 3 | -------------------------------------------------------------------------------- /4/transition/ease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/ease.png -------------------------------------------------------------------------------- /4/transition/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/chrome.png -------------------------------------------------------------------------------- /4/transition/ease-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/ease-in.png -------------------------------------------------------------------------------- /4/transition/linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/linear.png -------------------------------------------------------------------------------- /.gitbook/assets/1.3.1.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.3.1.6.png -------------------------------------------------------------------------------- /.gitbook/assets/1.5.2.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.5.2.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.0.png -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.4.jpg -------------------------------------------------------------------------------- /.gitbook/assets/6.1.0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.1.0.jpeg -------------------------------------------------------------------------------- /.gitbook/assets/6.2.3.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.2.3.6.png -------------------------------------------------------------------------------- /.gitbook/assets/9.1.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.2.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.2.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.2.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.2.2.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.3.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.3.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.3.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.3.2.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.4.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.4.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.5.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.5.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.7.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.7.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/9.1.8.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/9.1.8.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/baidu.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/baidu.jpeg -------------------------------------------------------------------------------- /.gitbook/assets/bobma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/bobma.jpg -------------------------------------------------------------------------------- /.gitbook/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image.png -------------------------------------------------------------------------------- /4/transition/ease-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/ease-out.png -------------------------------------------------------------------------------- /4/transition/steps-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/steps-end.png -------------------------------------------------------------------------------- /.gitbook/assets/1.2.6.4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.2.6.4.1.png -------------------------------------------------------------------------------- /.gitbook/assets/1.5.2.3.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.5.2.3.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/1.5.2.3.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.5.2.3.2.jpg -------------------------------------------------------------------------------- /.gitbook/assets/1.5.2.4.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/1.5.2.4.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.0.1.png -------------------------------------------------------------------------------- /.gitbook/assets/3.1.1.3.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/3.1.1.3.1.jpg -------------------------------------------------------------------------------- /.gitbook/assets/5.3.1.1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/5.3.1.1.1.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.0.1.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.2.1.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.2.2.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.2.3.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.2.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.2.4.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.2.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.2.5.png -------------------------------------------------------------------------------- /.gitbook/assets/6.3.1.3.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/6.3.1.3.1.png -------------------------------------------------------------------------------- /.gitbook/assets/7.1.14.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/7.1.14.1.png -------------------------------------------------------------------------------- /.gitbook/assets/copyright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/copyright.png -------------------------------------------------------------------------------- /.gitbook/assets/image (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (5).png -------------------------------------------------------------------------------- /.gitbook/assets/image (6).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (6).png -------------------------------------------------------------------------------- /.gitbook/assets/image (7).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (7).png -------------------------------------------------------------------------------- /.gitbook/assets/image (8).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (8).png -------------------------------------------------------------------------------- /.gitbook/assets/image (9).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (9).png -------------------------------------------------------------------------------- /4/transition/ease-in-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/ease-in-out.png -------------------------------------------------------------------------------- /4/transition/steps-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/4/transition/steps-start.png -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.4-Heap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.4-Heap.gif -------------------------------------------------------------------------------- /.gitbook/assets/edan-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/edan-qrcode.png -------------------------------------------------------------------------------- /.gitbook/assets/image (11).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (11).png -------------------------------------------------------------------------------- /.gitbook/assets/image (12).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (12).png -------------------------------------------------------------------------------- /.gitbook/assets/image (13).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (13).png -------------------------------------------------------------------------------- /.gitbook/assets/image (14).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (14).png -------------------------------------------------------------------------------- /.gitbook/assets/image (15).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (15).png -------------------------------------------------------------------------------- /.gitbook/assets/image (16).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (16).png -------------------------------------------------------------------------------- /.gitbook/assets/image (17).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (17).png -------------------------------------------------------------------------------- /.gitbook/assets/image (18).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (18).png -------------------------------------------------------------------------------- /.gitbook/assets/image (19).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (19).png -------------------------------------------------------------------------------- /.gitbook/assets/image (20).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (20).png -------------------------------------------------------------------------------- /.gitbook/assets/image (21).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (21).png -------------------------------------------------------------------------------- /.gitbook/assets/image (22).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (22).png -------------------------------------------------------------------------------- /.gitbook/assets/image (24).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (24).png -------------------------------------------------------------------------------- /.gitbook/assets/image (25).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (25).png -------------------------------------------------------------------------------- /.gitbook/assets/image (26).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (26).png -------------------------------------------------------------------------------- /.gitbook/assets/image (27).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (27).png -------------------------------------------------------------------------------- /.gitbook/assets/image (28).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (28).png -------------------------------------------------------------------------------- /.gitbook/assets/image (29).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (29).png -------------------------------------------------------------------------------- /.gitbook/assets/image (30).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (30).png -------------------------------------------------------------------------------- /.gitbook/assets/image (31).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (31).png -------------------------------------------------------------------------------- /.gitbook/assets/image (32).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (32).png -------------------------------------------------------------------------------- /.gitbook/assets/image (33).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (33).png -------------------------------------------------------------------------------- /.gitbook/assets/image (35).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (35).png -------------------------------------------------------------------------------- /.gitbook/assets/image (37).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (37).png -------------------------------------------------------------------------------- /.gitbook/assets/image (38).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (38).png -------------------------------------------------------------------------------- /.gitbook/assets/image (39).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (39).png -------------------------------------------------------------------------------- /.gitbook/assets/image (42).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (42).png -------------------------------------------------------------------------------- /.gitbook/assets/john-resig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/john-resig.jpg -------------------------------------------------------------------------------- /.gitbook/assets/wechatimg21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/wechatimg21.png -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.1-Bubble.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.1-Bubble.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.10-Radix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.10-Radix.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.5-Merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.5-Merge.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.6-Quick.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.6-Quick.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.7-Shell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.7-Shell.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.9-Bucket.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.9-Bucket.gif -------------------------------------------------------------------------------- /.gitbook/assets/cover20200504.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/cover20200504.jpg -------------------------------------------------------------------------------- /.gitbook/assets/cover20200619.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/cover20200619.jpg -------------------------------------------------------------------------------- /.gitbook/assets/image (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (1) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (14) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (14) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (19) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (19) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (33) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (33) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (35) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (35) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (37) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (37) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (5) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (5) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (6) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (6) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (7) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (7) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/monotone-stack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/monotone-stack.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.2-Selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.2-Selection.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.3-Insertion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.3-Insertion.gif -------------------------------------------------------------------------------- /.gitbook/assets/2.1.1.8-Counting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/2.1.1.8-Counting.gif -------------------------------------------------------------------------------- /.gitbook/assets/image (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (1) (1) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (6) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (6) (1) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (7) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (7) (1) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/makai_greatwall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/makai_greatwall.png -------------------------------------------------------------------------------- /.gitbook/assets/image (14) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (14) (1) (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (35) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coffe1891/frontend-hard-mode-interview/HEAD/.gitbook/assets/image (35) (1) (1).png -------------------------------------------------------------------------------- /code/1.2.1-2.js: -------------------------------------------------------------------------------- 1 | //IIFE 2 | (function(){ 3 | console.log("我是立即运行的匿名函数"); 4 | })(); 5 | 6 | (function(){ 7 | console.log("我也是立即运行的匿名函数"); 8 | }()); -------------------------------------------------------------------------------- /6/6.4.1.md: -------------------------------------------------------------------------------- 1 | # 陆.4.1 什么是响应式编程? 2 | 3 | **响应式编程简称RP(Reactive Programming)**,是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 4 | 5 | //todo 6 | 7 | -------------------------------------------------------------------------------- /3/3.2.1.md: -------------------------------------------------------------------------------- 1 | # 叁.2.1 React Hooks究竟是什么? 2 | 3 | ## 参考文献 4 | 5 | {% hint style="info" %} 6 | [React Hooks 新手筆記](https://medium.com/@z3388638/react-hooks-%E6%96%B0%E6%89%8B%E7%AD%86%E8%A8%98-8c9f1cccd142) 7 | {% endhint %} 8 | 9 | -------------------------------------------------------------------------------- /5/5.3.2.md: -------------------------------------------------------------------------------- 1 | # 伍.3.2 RxJS 2 | 3 | [RxJS](https://rxjs-cn.github.io/)是使用Observables 的响应式编程的库,它使编写异步或基于回调的代码更容易。随着深入你会发现它采用了订阅者模式,其中也带有纯函数的思想。 4 | 5 | ## 01.统一同步与异步 6 | 7 | ## 02.统一获取与订阅 8 | 9 | ## 03.统一现在与未来 10 | 11 | -------------------------------------------------------------------------------- /code/1.2.1-3.js: -------------------------------------------------------------------------------- 1 | //函数调用自身称为递归,函数名为“func” 2 | (function func(i){ 3 | console.log("函数名为"+func.name+",第"+i+"次调用") 4 | if(i<3){//递归出口 5 | func(++i); 6 | } 7 | })(1); 8 | //函数名为func,第1次调用 9 | //函数名为func,第3次调用 10 | //函数名为func,第3次调用 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。 2 | -------------------------------------------------------------------------------- /10/10.2.md: -------------------------------------------------------------------------------- 1 | # 拾.2 2020年前端技术展望 2 | 3 | ## 01.Serverless 4 | 5 | 阿里云针对ServerLess推出了一个新产品叫**函数计算**。 6 | 7 | 函数计算的免运维特性与前端工程师天然互补,工程师只需编写业务代码即可快速搭建云原生的 Web 应用,有效提高上线迭代效率,降低运维成本。 8 | 9 | 可以使用函数计算构建 Serverless Web 应用: 10 | 11 | * **搭建 Web 网站:** 比如搭建基于 Wordpress、Express 等框架的站点; 12 | * **提供 Web API:**为前后端分离的架构提供后端数据支撑,比如提供获取天气 API 查看实时天气; 13 | * **Web 后端:**作为 IOT、移动后端等验证和处理 API 请求。 14 | 15 | ## 02.lowCode & noCode 16 | 17 | -------------------------------------------------------------------------------- /code/1.2.1-4.js: -------------------------------------------------------------------------------- 1 | //函数调用自身称为递归,函数名为“func” 2 | (function (i){ 3 | //"use strict" //取消注释则为严格模式,会报错 4 | console.log("函数名为"+func.name+",第"+i+"次调用") 5 | if(i<3){//递归出口 6 | arguments.callee(++i); 7 | } 8 | })(1); 9 | //函数名为func,第1次调用 10 | //函数名为func,第3次调用 11 | //函数名为func,第3次调用 12 | 13 | 14 | //该函数与后面的箭头函数等效 15 | (function(i){ 16 | console.log(i); 17 | })(1); 18 | //箭头函数 19 | ((i)=>{ 20 | console.log(i); 21 | })(1); -------------------------------------------------------------------------------- /4/si-.1.1-shen-ru-li-jie-webpack-da-bao.md: -------------------------------------------------------------------------------- 1 | # 肆.1.1 深入理解Webpack打包 2 | 3 | ## 参考文献 4 | 5 | {% hint style="info" %} 6 | [手写webpack核心原理,再也不怕面试官问我webpack原理](https://mp.weixin.qq.com/s/TTIRDG15T3l5VDm8SrUZWg) 7 | {% endhint %} 8 | 9 | {% hint style="info" %} 10 | [Let's Write a JavaScript Library in ES6 using Webpack and Babel](https://www.loginradius.com/engineering/blog/write-a-javascript-library-using-webpack-and-babel/) 11 | {% endhint %} 12 | 13 | {% hint style="info" %} 14 | [Webpack构建library时的踩坑经历](https://developer.aliyun.com/article/465323) 15 | {% endhint %} 16 | 17 | -------------------------------------------------------------------------------- /code/1.2.1-1.js: -------------------------------------------------------------------------------- 1 | //函数的声明形态 2 | function func(){ 3 | console.log("函数的声明形态") 4 | } 5 | 6 | //函数的表达式形态 7 | let func0 =function(){ 8 | console.log("函数的表达式形态"); 9 | } 10 | 11 | //函数的嵌套形态 12 | let func1 = function(){ 13 | console.log("函数的嵌套形态"); 14 | let func2 = function(){ 15 | console.log("func2嵌套在func1里") 16 | } 17 | func2(); 18 | } 19 | 20 | // 函数的闭包形态 21 | let func3 = function(){ 22 | return function(){ 23 | console.log("我是以闭包形态存在的函数"); 24 | } 25 | } 26 | //所有的函数都通一对括号“()”调用执行 27 | func(); 28 | func0(); 29 | func1(); 30 | func3()(); -------------------------------------------------------------------------------- /7/7.2.1.md: -------------------------------------------------------------------------------- 1 | # 柒.2.1 MVC的前世今生 2 | 3 | 如果要理解MVC,得先知道MVC是解决软件工程范畴内的问题。那么什么是软件工程?援引百度百科的解释: 4 | 5 | > 软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。它涉及程序设计语言、数据库、软件开发工具、系统平台、标准、设计模式等方面。 6 | 7 | 因此,前面讲的十四种设计模式,也是属于软件工程范畴内的;更进一步,本书似乎也是在尝试尽可能多地讲软件工程。 8 | 9 | 严格来讲,MVC不属于设计模式,它比设计模式的粒度更粗,层级更高一些,更专注于“解决构建、维护”等软件工程领域的问题。 10 | 11 | 至于MVC的起源,由谁发明的,由谁率先应用的,由谁发扬光大……记住这些没有卵用,并不会让你在实践中把MVC用的更好。那本篇为什么要用“前世今生”来命题呢?这里的“前世今生”,不是简单的基于历史时间线做记录,而是想从软件的构建与维护的角度,来剖析MVC出现之前的和之后带来的改变,方便读者更深入的理解MVC。 12 | 13 | ## 柒.2.1.1 MVC出现之前 14 | 15 | //todo 16 | 17 | ## 柒.2.1.2 MVC出现之后 18 | 19 | //todo 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /6/6.1.0.md: -------------------------------------------------------------------------------- 1 | # 陆.1.0 导读:SOLID 2 | 3 | ![](../.gitbook/assets/6.1.0.jpeg) 4 | 5 | ## 01. 认识SOLID 6 | 7 | SOLID原则是我们心爱的[Bob叔\(Uncle Bob\)](https://sites.google.com/site/unclebobconsultingllc/)提出的,是面向对象编程的基本设计原则。 8 | 9 | “SOLID”是五条设计原则英文首字母的组合,如下: 10 | 11 | > * **S – Single Responsibility Principle 单一职责原则** 12 | > * **O – Open-Closed Principle 开放封闭原则** 13 | > * **L – Liskov Substitution Principle 里氏替换原则** 14 | > * **I – Interface Segregation Principle 接口隔离原则** 15 | > * **D – Dependency Inversion Principle 依赖倒置原则** 16 | 17 | JavaScript程序员掌握SOLID要相对其他语言更难一些。为什么呢?因为JavaScript是一门非常“松散类型”的语言,有些人认为它是函数式语言,有些人认为它是面向对象的语言,还有很多人认为两者都是。 18 | 19 | ## 02. 掌握SOLID的必要性 20 | 21 | 根据我的经验,你会很少想在JavaScript中使用类以及多层次的继承等OOP有关的特性,但是掌握SOLID的原理,对我们开发基于JavaScript的软件仍有十分重要的指导意义。 22 | 23 | 尽管起初SOLID是为了指导类和接口的创建,但我还是将SOLID定义扩展到JavaScript函数和模块上,以方便前端工程师朋友们掌握并应用到实际工作中去。 24 | 25 | -------------------------------------------------------------------------------- /6/6.3.3.md: -------------------------------------------------------------------------------- 1 | # 陆.3.3 Pointfree无参数风格编程 2 | 3 | [上一篇](6.3.2.md#quan-bu-ke-li-hua-de-mu-de-shi-shen-me-ne)讲到了一个Pointfree的例子。 4 | 5 | ## 什么是Pointfree 6 | 7 | Pointfree仍是一种新事物,仍还在快速演绎变化中,因此笔者不会刻意为这种编程风格下定义,而是通过列举更多的例子,可以让读者了解什么是Pointfree。 8 | 9 | ```javascript 10 | //todo 11 | ``` 12 | 13 | ## Pointfree的用处到底是什么? 14 | 15 | Pointfree 风格需要一定的时间才能习惯。可能并不需要所有的地方都没有参数。有时候知道某些 Ramda 函数需要多少参数,也是很重要的。 16 | 17 | 但是,一旦习惯了这种方式,它将变得非常强大:可以以非常有趣的方式将很多小的 pointfree 函数组合起来。 18 | 19 | Pointfree 风格的优点是什么呢?人们可能会认为,这只不过是为了让函数式编程变得“好看一点”的学术活动而已。然而,我认为还是有一些优点的,即使需要花一些时间来习惯这种方式也是值得的: 20 | 21 | * 它让编程更简单、精练。虽然这并不总是一件好事,但大部分情况下是这样的。 22 | * 它让算法更清晰。通过只关注正在组合的函数,我们可以在没有参数的干扰下,更好地了解发生了什么。促使我们更专注于正在做的转换的本身,而不是正被转换的数据。 23 | * 它可以帮助我们将函数视为可以作用于不同数据的通用构建模块,而非对特定类型数据的操作。如果给数据一个名字,我们的思想便会被禁锢在:"需要在哪里使用我们的函数";如果去掉参数,便会使我们更有创造力。 24 | 25 | ## 参考文献 26 | 27 | {% hint style="info" %} 28 | [Thinking in Ramda: Pointfree Style](https://randycoulman.com/blog/2016/06/21/thinking-in-ramda-pointfree-style/) 29 | {% endhint %} 30 | 31 | -------------------------------------------------------------------------------- /5/5.2.3.md: -------------------------------------------------------------------------------- 1 | # 伍.2.3 Pointfree无参数风格编程 2 | 3 | [上一篇](5.2.2.md#quan-bu-ke-li-hua-de-mu-de-shi-shen-me-ne)讲到了一个Pointfree的例子。 4 | 5 | ## 01.什么是Pointfree 6 | 7 | Pointfree仍是一种新事物,仍还在快速演绎变化中,因此笔者不会刻意为这种编程风格下定义,而是通过列举更多的例子,可以让读者了解什么是Pointfree。 8 | 9 | ```javascript 10 | //todo 11 | ``` 12 | 13 | ## 02.Pointfree的用处到底是什么? 14 | 15 | Pointfree 风格需要一定的时间才能习惯。可能并不需要所有的地方都没有参数。有时候知道某些 Ramda 函数需要多少参数,也是很重要的。 16 | 17 | 但是,一旦习惯了这种方式,它将变得非常强大:可以以非常有趣的方式将很多小的 pointfree 函数组合起来。 18 | 19 | Pointfree 风格的优点是什么呢?人们可能会认为,这只不过是为了让函数式编程变得“好看一点”的学术活动而已。然而,我认为还是有一些优点的,即使需要花一些时间来习惯这种方式也是值得的: 20 | 21 | * 它让编程更简单、精练。虽然这并不总是一件好事,但大部分情况下是这样的。 22 | * 它让算法更清晰。通过只关注正在组合的函数,我们可以在没有参数的干扰下,更好地了解发生了什么。促使我们更专注于正在做的转换的本身,而不是正被转换的数据。 23 | * 它可以帮助我们将函数视为可以作用于不同数据的通用构建模块,而非对特定类型数据的操作。如果给数据一个名字,我们的思想便会被禁锢在:"需要在哪里使用我们的函数";如果去掉参数,便会使我们更有创造力。 24 | 25 | ## 参考文献 26 | 27 | {% hint style="info" %} 28 | [Thinking in Ramda: Pointfree Style](https://randycoulman.com/blog/2016/06/21/thinking-in-ramda-pointfree-style/) 29 | {% endhint %} 30 | 31 | -------------------------------------------------------------------------------- /0/0.1.4.md: -------------------------------------------------------------------------------- 1 | # 零.1.4 准备一份好的简历 2 | 3 | 一线互联网企业从来不缺简历,尤其现在普通前端工程师可批量生产的时代。一份工作经常能收到几百上千份简历,HR每天要筛选大量的简历,是相当费时费力的。因此我们要准备一份好的简历,这样才能从简历海洋里面脱颖而出,并争取到宝贵的面试机会。 4 | 5 | 那么什么样的简历才算好的简历呢?笔者从多年的面试(被面试和面试别人都有)经验中总结如下: 6 | 7 | ## 01.简历大纲结构清晰 8 | 9 | 花几分钟了解一下Word/WPS的大纲模式,"章、节、条、款、例",在大纲模式下,先把简历文档按这样的层级编排一下。 10 | 11 | ## 02.具体内容少而精确 12 | 13 | 发挥你上学时写作文的能力,言简意赅甚至咬文嚼字地把重点描述清楚。 14 | 15 | ## 03.精通的技术写成熟悉,熟悉的写成了解 16 | 17 | 在中国,往往是谦受益、满招损。 18 | 19 | 精通某类技术,别表现得太得意自满。因为总有比我们技术更好的考官,当我们表现出自信满满的时候,绝大多数考官往往会想要挫一下我们的锐气,试探一下我们的技术上限,所以往往会挑我们最自信的领域出题,而且题目难度和我们表现出的自信成正比。如果一个难题回答得不好,往往会前功尽弃,给很大的负面评分。所以,对自己精通的技术,不妨谦虚一点写成熟悉,这样回答考官的技术问题,反而能给对方意外惊喜。 20 | 21 | 同理,只是熟悉的技术,写成了解,少一些节外生枝的麻烦。 22 | 23 | 如果只是了解的技术,干脆就别写了。不然随便问几个稍微有点点深度的问题却答不上来,会让考官认为我们的简历写得太浮夸,从而对我们的印象大打折扣。 24 | 25 | ## 04.尽可能放上自己的博客、个人主页、开源项目链接 26 | 27 | 当我们学会了总结、归纳之后,学一样东西往往是很快的。写博客、搭建个人主页,参加GitHub开源项目,就是归纳、总结、分享的过程。 28 | 29 | 在个人简历里面放上自己的博客、个人主页、开源项目链接,能让用人方更深刻地了解我们的学习方法论,让用人单位知道我们除了自己有良好的学习能力之外,还具有强大的分享能力,能分享给同事们很多新技能,推动整个团队的整体职业素养变得更好! 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /10/10.1-1.md: -------------------------------------------------------------------------------- 1 | # 拾.1 成为一个好的程序员远比找份好工作重要 2 | 3 | ## 拾.1.1 为什么很多程序员写到一定阶段,就失去了对coding的兴趣? 4 | 5 | 我觉得是因为没有追求优雅地去coding,写的东西缺乏美感。 6 | 7 | 而我看过了一些高龄的老外coder的博客,读他们的代码,看他们的文字,发现居然是在欣赏艺术品,整个过程几乎没有枯燥的感觉。 8 | 9 | 这里面深层次的原因,是因为什么? 10 | 11 | 我觉得,是因为很长一段时间,我们在面向工作编程。 12 | 13 | 明白这点之后,我们要做的正确的事,应该是面向“成为一个更好的程序员”去编程。 14 | 15 | [www.recurse.com](http://www.recurse.com) 不知道是否有朋友了解,纽约一个程序员互助组织。它的创立初衷和我的想法一致:程序员应该让自己成为一个好的程序员。 16 | 17 | 至于好的工作,那是成为好程序员之后的附属品。 18 | 19 | ## 拾.1.2 要不要重复造轮子? 20 | 21 | 经常听到“不要重复造轮子”的话。 22 | 23 | 的确,这可以让程序员成为一个效率高的员工,但不会有助于成为一个更好技术水平的程序员。 24 | 25 | 长期停留在调用api的阶段,时间久了,会失去对coding的热爱。 26 | 27 | 这某种程度上解释了为什么那么多程序员会在40岁之前转行,因为不够爱。而不够爱,是因为一开始认知就错了。 28 | 29 | 想办法争取机会造轮子吧(比如去做中台),如果你真的爱并且想coding到八十岁的话。 30 | 31 | ## 拾.1.3 作为前浪的老程序员真的缺少舞台吗? 32 | 33 | 有一些互联网软件工程师朋友和我倾诉,说到一定年纪明显感到来自后浪的竞争压力。 34 | 35 | 是的,后浪推前浪,一代接一代迭代演化前行,前浪们一定会感受后浪明显的推背力。然而,并不意味着前浪就没有舞台了。其实,这次新冠疫情加速全行业的数字化,体力旺盛的后浪源源不断进入行业,而资深工程师、技术专家又是现在企业急需的,大量的有深度的问题需要这些资深专家解决。所以,只要前浪们不要陷入体力竞争的死胡同,在多年的职业生涯中积累了技术的广度之后,再努力一步把自己的技能往纵深发展,成为垂直领域资深中的资深,到那时,coding到八十岁是一个美好又可实现的理想。 36 | 37 | 岂曰无衣?加油,前浪们。 38 | 39 | -------------------------------------------------------------------------------- /0.0.1.md: -------------------------------------------------------------------------------- 1 | # 零.0.1 前言·写给有缘人 2 | 3 | ## 01. coding 到 80 岁 4 | 5 | 作为一个老程序员,coding已经成为生命的一部分,coding到八十岁是我的理想,而JavaScript是我挚爱的语言! 6 | 7 | 自从2010年出版个人第一本互联网技术书籍后,便再也没有时间可以写系统性论述技术的著作。因为后来加入奇虎360和百度这两家一线互联网公司,从事前端与手机客户端技术性工作,并主持研发了亿级用户的手机APP,这期间相当繁忙。 8 | 9 | 在接受了一线互联网企业的再锻造与重塑之后,编程工作就像打游戏一样,让我觉得快乐与充实。每天都能“通关”,解决掉一个又一个问题,与优秀的聪明的睿智的热心的同事们一起协同合作,创造一个又一个生动有趣的产品,成就感是满满的……而且最重要的,在这个过程中居然还能赚到不少钱,让我作为一个程序员能够体面地生活并做自己喜欢的事,这实在是太美妙了! 10 | 11 | ## 02. 求知之路,道阻且长 12 | 13 | 而在进入360和百度之前,记得那年是2011年,创业公司彻底倒闭,我失业了。实在没办法,为了谋生存必须找一份有稳定收入的工作。当时是计划入职一线互联网公司,做自己感兴趣的前端\(Front-End\)coding。因为从大学出来后和师兄们折腾创业,虽有一些技术积累但是不够系统化,而且缺乏职业化的工作经历,求职时是有点尴尬的,一线互联网公司似乎不太乐意接受这种情况,面试之旅已然进入Hard模式。面对逆境,我希望通过用扎实的技术功底和丰富的实战经验来打动雇主。 14 | 15 | 在复习/学习期间,我查阅了大量国内外文档资料,发现原始资料相对少,原始的资料论文式偏多的,对英语阅读能力有非常大的挑战。而国内的资料大多数有错漏、不完整:首先是翻译问题非常大,术语翻译错误、译文表意模棱两可、原文内容翻译不完整,让人看了更加迷惑不解;国内网上的文章,绝大多数缺乏系统化论述,知识点分散零碎,而且很多有代码错误……各种原因,对自己的学习过程造成了不少困扰。 16 | 17 | ## 03. 感同身受,写给有缘人 18 | 19 | 综上,感同身受求知之不易,因此多年后\(2019年\)有了闲暇便立即着手整理并分享本书,把当前前端核心知识要点梳理一遍,供前端工程师朋友们复习与进阶参考。 20 | 21 | 如果我能通过自身努力最终能达成入职一线互联网公司的目标,那么更年轻、更健康和更聪明的程序员朋友们只会做得更好。加油吧,奥力给!本书还正在持续更新中,许多章节会陆续“点亮”的 🧡 。 22 | 23 | -------------------------------------------------------------------------------- /10/10.1.md: -------------------------------------------------------------------------------- 1 | # 拾.1 成为一个好的程序员远比找份好工作重要 2 | 3 | ## 拾.1.1 为什么很多程序员写到一定阶段,就失去了对coding的兴趣? 4 | 5 | 我觉得是因为没有追求优雅地去coding,写的东西缺乏美感。 6 | 7 | 而我看过了一些高龄的老外coder的博客,读他们的代码,看他们的文字,发现居然是在欣赏艺术品,整个过程几乎没有枯燥的感觉。 8 | 9 | 这里面深层次的原因,是因为什么? 10 | 11 | 我觉得,是因为很长一段时间,我们在面向工作编程。 12 | 13 | 明白这点之后,我们要做的正确的事,应该是面向“成为一个更好的程序员”去编程。 14 | 15 | [www.recurse.com](http://www.recurse.com) 不知道是否有朋友了解,纽约一个程序员互助组织。它的创立初衷和我的想法一致:程序员应该让自己成为一个好的程序员。 16 | 17 | 至于好的工作,那是成为好程序员之后的附属品。 18 | 19 | ## 拾.1.2 要不要重复造轮子? 20 | 21 | 经常听到“不要重复造轮子”的话,尤其团队迫切需要提升ROI\(投入产出比\)的情况下。 22 | 23 | 的确,这可以让程序员成为一个做事足够快ROI足够高的员工,但不会太多地有助于其成为一个更好技术水平的程序员。 24 | 25 | 长期停留在调用api的阶段,时间久了,会失去对coding的热爱。 26 | 27 | 这某种程度上解释了为什么那么多程序员会在40岁之前转行,因为不够爱。而不够爱,是因为一开始认知就错了。 28 | 29 | 想办法争取机会造轮子吧,比如去做中台,如果你真的爱并且想coding到八十岁的话。 30 | 31 | ## 拾.1.3 作为前浪的老程序员真的缺少舞台吗? 32 | 33 | 有一些互联网软件工程师朋友和我倾诉,说到一定年纪明显感到来自后浪的竞争压力。 34 | 35 | 是的,后浪推前浪,一代接一代迭代演化前行,前浪们一定会感受后浪明显的推背力。然而,并不意味着前浪就没有舞台了。其实,这次新冠疫情加速全行业的数字化,体力旺盛的后浪源源不断进入行业,而资深工程师、技术专家又是现在企业急需的,大量的有深度的问题需要这些资深专家解决。所以,只要前浪们不要陷入体力竞争的死胡同,在多年的职业生涯中积累了技术的广度之后,再努力一步把自己的技能往纵深发展,成为垂直领域资深中的资深,到那时,coding到八十岁是一个美好又可实现的理想。 36 | 37 | 岂曰无衣?加油,前浪们。 38 | 39 | -------------------------------------------------------------------------------- /2/2.1.2.md: -------------------------------------------------------------------------------- 1 | # 贰.1.2 链表 2 | 3 | \(单\)链表的JavaScript数据结构如下: 4 | 5 | ```javascript 6 | let Node = {//链表中的一个结点 7 | val : 1891, //结点的值 8 | next: node //一个指针指向下一个结点。若结点为链表的尾端,则next的值是null 9 | } 10 | ``` 11 | 12 | ## 01.反转链表 13 | 14 | ### 迭代法 15 | 16 | ```javascript 17 | var reverseList = function(head/*Node*/) { 18 | if(!head || !head.next) return head; 19 | var prev = null, curr = head; 20 | while(curr) { 21 | // 用于临时存储 curr 后继节点 22 | var next = curr.next; 23 | // 反转 curr 的后继指针 24 | curr.next = prev; 25 | // 变更prev、curr 26 | // 待反转节点指向下一个节点 27 | prev = curr; 28 | curr = next; 29 | } 30 | head = prev; 31 | return head; 32 | } 33 | ``` 34 | 35 | ### 尾递归法 36 | 37 | ```javascript 38 | var reverseList = function(head/*Node*/) { 39 | if(!head || !head.next) return head; 40 | return reverse(null, head); 41 | }; 42 | 43 | var reverse = function(prev, curr) { 44 | if(!curr) return prev; 45 | var next = curr.next; 46 | curr.next = prev; 47 | return reverse(curr, next); 48 | }; 49 | ``` 50 | 51 | ## 02.回形链表 52 | 53 | -------------------------------------------------------------------------------- /0/0.1.5.md: -------------------------------------------------------------------------------- 1 | # 零.1.5 不卑不亢,不疾不徐地说话 2 | 3 | 如果没有丰富的工作经验和阅历,新手前端工程师在面对一线互联网企业的面试官时,多少都是有一点自卑心理的,容易把自己摆在弱势,以表现出诚恳态度、或减少对方的麻烦,来争取对方的好感、获取信息。这不能说完全错误,因为确实有些好虚荣、地位不高的面试官喜欢听人这样说。但说老实话,他们所负责招聘的职位,往往不会重要,他们所在的公司,多半不会大有前途。 4 | 5 | 但是你面试的是一线互联网企业,这是真正的大企业,公司的领导层是聪明睿智的一批人。**睿智的人让别人最舒服的一点,就是让你无需顾及他的自尊心;而让别人觉得最可怕,就是你那点小套路人家早看穿了。** 所以,你用弱势的态度自居,过分谨慎小心地说话,是较难获得对方好感的。 6 | 7 | ## 零.1.5.1 保持自信但不傲慢 8 | 9 | ### 自信的表现 10 | - **眼神交流**:与面试官保持适度的眼神接触,不要躲闪 11 | - **语速适中**:说话不疾不徐,给面试官思考的时间 12 | - **姿态自然**:坐姿端正但不僵硬,手势自然得体 13 | - **声音清晰**:音量适中,语速均匀,发音清晰 14 | 15 | ### 避免傲慢 16 | - **不要打断**:等面试官说完再回答 17 | - **承认不足**:遇到不会的问题,诚实承认并表达学习意愿 18 | - **尊重对方**:即使不认同面试官的观点,也要礼貌表达 19 | 20 | ## 零.1.5.2 语言表达技巧 21 | 22 | ### 回答问题的结构 23 | 1. **直接回答**:先给出明确的答案 24 | 2. **解释原因**:说明为什么这样认为 25 | 3. **举例说明**:用具体例子支撑观点 26 | 4. **总结归纳**:简要总结要点 27 | 28 | ### 避免的语言陷阱 29 | - **过度谦虚**:"我可能不太行"、"我试试看吧" 30 | - **推卸责任**:"这不是我的错"、"别人没告诉我" 31 | - **模糊表达**:"大概"、"可能"、"差不多" 32 | 33 | ## 零.1.5.3 情绪管理 34 | 35 | ### 保持冷静 36 | - **深呼吸**:面试前做几次深呼吸,稳定情绪 37 | - **积极暗示**:告诉自己"我能行"、"我准备充分" 38 | - **转移注意力**:如果紧张,可以观察周围环境 39 | 40 | ### 应对压力 41 | - **承认紧张**:如果面试官看出你紧张,可以诚实承认 42 | - **寻求支持**:面试前可以和朋友模拟练习 43 | - **调整期望**:把面试当作学习机会,而不是生死考验 44 | 45 | ## 零.1.5.4 结语 46 | 47 | 面试中的语言表达不仅仅是技巧问题,更是心态和自信的体现。保持不卑不亢的态度,既能展现你的专业素养,也能让面试官感受到你的自信和能力。记住,你是在和未来的同事交流,而不是在乞求工作机会。 48 | 49 | -------------------------------------------------------------------------------- /0/0.1.6.md: -------------------------------------------------------------------------------- 1 | # 零.1.6 "有什么问题要问我吗",如何回答? 2 | 3 | 这个问题几乎是标准问题,一般公司都会问到,一线互联网公司尤其会以该问题结束这一轮面试。 4 | 5 | 有数据显示,面试中面试官讲话时间的占比越高,面试的成功率就越高。因为他愿意把公司展现给你,表现出来的自然是一种积极、愿意配合的态度。反之,如果他只是一味的听你说做自我介绍,听你讲你之前的工作经验,那你成功的几率将会大大降低。 6 | 7 | 所以,当面试官提出这个终极问题:"你还有什么问题要问我吗?",一定要记住:不是问什么问题都是OK的,更重要的是不能盲目的去提问一些死记硬背的问题,因为这不但不会有加分项,更有可能把面试上半部分的好印象也都抵消了。 8 | 9 | ## 零.1.6.1 一定要提问 10 | 11 | 综合而言,一定要提问,显得你准备充分,应变能力强,千万不可放弃这个机会。可以往以下几个方向提问(提的问题不要和本书写的一样,应稍作变化): 12 | 13 | ### **1.为什么要招人?**_**`(对方是HR、leader)`**_ 14 | 15 | 对工作岗位的真实性做深入了解,显得你是在认真的关注这个工作机会。 16 | 17 | ### 2.这个岗位具体会做哪些事情,会与哪些人合作? 18 | 19 | 进一步对工作岗位进行深入了解,以及对将来的合作伙伴有更多提前认知,同时给面试官一些尽情表达的机会,显得你是非常关心这个工作机会。 20 | 21 | ### **3.咱们公司的企业文化是什么样的?**_`(对方是HR、leader)`_** 22 | 23 | 一线互联网公司最亮眼的就是企业文化了,很多人都是因为了解到该公司文化,内心第一时间有了共鸣,然后才被感召而去应聘、入职的,面试官当初也很可能会有这个共鸣在里面,所以一般会巴拉巴拉非常自豪地和你讲解一番。面试官得到了充分的表达机会,充分表示出了积极意愿,充分的被倾听,心理上会对你有更多的好感。然后你正好也听听更专业的表述,看这种企业文化是否真正让你内心共鸣了呢? 24 | 25 | ### **4.咱们团队目前面对的最大挑战/困难是什么?** 26 | 27 | 抓住机会深入了解团队目前的困难、痛点,利用入职前的空档期,提前做好预习与准备,以便顺利入职之后快速产出。 28 | 29 | ### **5.如果我能加入这个团队,你对我有什么期望?** 30 | 31 | ### **6.期望我的加入带来哪些改变?**_`(对方是leader)`_** 32 | 33 | 如果你应聘的岗位要带人的话,比如架构师、技术负责人、技术经理等带有管理性质的岗位,则可以更进一步提出这个问题。 34 | 35 | ## 零.1.6.2 结语 36 | 37 | 总之,**问太肤浅的问题,会让你显得格局小。** 太细节、太琐碎的,比如作息制度、报销制度,是否管午饭,是否经常加班,团队有几个女生,有没有健身房……等等类似太具体的问题,还是留在该公司将offer发给你之后、入职已经胜券在握的时候再去问HR吧。 38 | 39 | ### 40 | 41 | -------------------------------------------------------------------------------- /9/9.1.md: -------------------------------------------------------------------------------- 1 | # 玖.1 一线互联网公司前端团队官方公众号 2 | 3 | ## 玖.1.1 奇虎360 4 | 5 | ### 奇舞周刊 6 | 7 | ![](../.gitbook/assets/9.1.1.jpg) 8 | 9 | 这是360的官方前端团队“奇舞团”的微信公众号,几乎都是精品文章。直接搜公众号名(本段标题加粗黑字部分)就可找到(以下同)。 10 | 11 | ## 玖.1.2 阿里巴巴 12 | 13 | ### 淘系前端团队 14 | 15 | ![](../.gitbook/assets/9.1.2.1.jpg) 16 | 17 | 这个是淘宝的前端团队,有很多开源产品介绍,介绍的技术比较前卫。 18 | 19 | ### 钉钉大前端团队 20 | 21 | ![](../.gitbook/assets/9.1.2.2.jpg) 22 | 23 | 这个公众号比较新,目前文章不多,招聘启事蛮多的。钉钉属于比较有潜力的产品,因此看好这个前端团队的发展前景。 24 | 25 | ## 玖.1.3 腾讯 26 | 27 | ### 腾讯IMWeb前端团队 28 | 29 | ![](../.gitbook/assets/9.1.3.1.jpg) 30 | 31 | 这个是腾讯官方支持的前端公众号,IMWeb前端社区,汇聚众多前端开发爱好者。关注可听精致大咖直播课,看前沿干货推文,带你修炼前端开发内功! 32 | 33 | ### QQ音乐前端团队 34 | 35 | ![](../.gitbook/assets/9.1.3.2.jpg) 36 | 37 | 有一些Flutter和React介绍,可以看出这个团队是web和native端技术都有使用的,跨平台研发的前端团队。 38 | 39 | ## 玖.1.4 字节跳动 40 | 41 | ### 字节跳动技术团队 42 | 43 | ![](../.gitbook/assets/9.1.4.1.jpg) 44 | 45 | 技术范围比较广,有前端技术,内容讲解都比较深入。 46 | 47 | ## 玖.1.5 百度 48 | 49 | ### FEX团队 50 | 51 | ![](../.gitbook/assets/9.1.5.1.jpg) 52 | 53 | 这是我老东家的前端团队的官方公众号,FEX技术周刊做得很有特色,强烈推荐。 54 | 55 | ## 玖.1.6 快手 56 | 57 | //暂时还没找到,有人知道的话麻烦评论分享一下 58 | 59 | ## 玖.1.7 美团 60 | 61 | ### 美团技术团队 62 | 63 | ![](../.gitbook/assets/9.1.7.1.jpg) 64 | 65 | 9000+工程师,如何支撑中国领先的生活服务电子商务平台?3.2亿消费者、500万商户、2000多个行业、几千亿交易额背后是哪些技术?这里是美团、大众点评、美团外卖、美团大零售等技术团队的对外窗口,每周推送技术文章、活动及招聘信息等。 66 | 67 | ## 玖.1.8 网易 68 | 69 | ### 严选技术团队 70 | 71 | ![](../.gitbook/assets/9.1.8.1.jpg) 72 | 73 | 网易严选技术团队致力于分享电商业态下的技术干货,严选好物,用心生活;这里是网易严选技术、产品的对外窗口,每周推送技术文章、团队活动、招聘信息等内容。 74 | 75 | -------------------------------------------------------------------------------- /2/2.0.md: -------------------------------------------------------------------------------- 1 | # 贰.0 本章导读 2 | 3 | ## 贰.0.1 为什么要学习数据结构和算法? 4 | 5 | 数据结构和算法是编程的内功,对于编程能力提高和职场之路进阶至关重要。 6 | 7 | 深厚的内功可以有效保障写出的代码性能良好,可以提前预估代码运行达到预期目的,提高工作产出,也能让学习其他编程语言和框架变得事半功倍。 8 | 9 | 而且现状是,数据结构和算法如今已经是国内外一线互联网公司面试的必考知识点。 10 | 11 | ## 贰.0.2 会调框架API和CRUD还不够 12 | 13 | 如果仅仅满足实现日常开发中调调框架API和做做CRUD(Creat,Read,Update,Delete,也即数据库增删改查),那确实大多数时候不太用要数据结构和算法知识,但长期这样下去,就会沦落为吃青春饭的coder(俗称“码农”),到一定年龄之后体力速度跟不上,便会遭受就业排挤。 14 | 15 | 并不是说调框架API和CRUD就代表低水平,实际上复杂的软件都是可以分解成若干个简单的子模块来组成,每一个子模块里边做的事情可能也就是调用API然后CRUD,但是这些子模块组合在一起是可以实现很复杂业务逻辑或者说流程。 16 | 17 | 互联网前端领域这几年发展非常快,很多非科班的学生也都进入了这个行业。在转行的时候,大家考虑的都是经济效益。那么放在最重要的就是熟悉一门编程语言、会调一些框架的API、然后会一些数据库CRUD操作,以遍快速找到工作,因此不会有太多时间去专注学习数据结构和算法。的确,一些中小型企业也无需求简单的话,可以应付。 18 | 19 | 但是,当你进入一线互联网企业,项目越来越大越来越复杂,就会发现不是所有的项目都是调API和CRUD就可以搞定了。比如说一些广告后台、搜索、排序推荐的系统,还是有一些数据结构的;再比如说搜索引擎里边,最后对召回的结果要进行合并,很典型的合并k个有序的数组算法;再比如说热搜词的实时统计,你怎么实现?直接调前端框架API?直接拿数据库进行增删改查?那不行的。 20 | 21 | ## 贰.0.3 为什么面试官都喜欢问数据结构和算法呢? 22 | 23 | 因为数据结构和算法最能体现前端工程师的编程基本功。 24 | 25 | 基本功扎实的人,无论是做业务实现还是去做算法,都不太会差到哪里去。我们以前在百度招人的时候都有一个标准,就说招进来的这个人至少要排到team里面前50%。只有这样招进来的人才能够让我们的team更加强大,不能招一个很差的人来拉低平均水平。怎么评判这个人能够在team里面排到前50%呢?其实是有很多标准的,比如说算法数据结构就是里边很重要的一部分;其次,他的逻辑思维能力,系统设计能力,他的职业素养等等。但是算法和数据结构占的比重还是最大的。 26 | 27 | 如果连数据结构和算法都不会,有没有什么影响?有的,要知道程序员这个群体也是有金字塔结构的。如果连基本的算法和数据结构都不会,基本上属于比较底层的程序员。比较底层的程序员就意味着比较低的薪酬待遇。同样是出售脑力劳动和时间,你比别人少赚。 28 | 29 | 所以就算是从薪资待遇的角度来讲,也要重视数据结构与算法。 30 | 31 | ## 贰.0.4 结语 32 | 33 | 如果想在前端领域有长足的发展,就扎实学好数据结构和算法,需要成长为领域专家,从coder升级到架构师甚至更高阶的技术专家,成为有思想、有策略、有创新能力的前端精英。由于数据结构与算法内容庞大,一本书根本写不下,所以本章只挑选一些我认为相对更有用的内容放入。 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /7/7.1.14.md: -------------------------------------------------------------------------------- 1 | # 柒.1.14 适配器模式 2 | 3 | 适配器模式(Adapter Pattern)又称包装器模式,将一个类(对象)的接口(方法、属性)转化为用户需要的另一个接口,解决类(对象)之间接口不兼容的问题。 4 | 5 | 主要功能是进行转换匹配,目的是复用已有的功能,而不是来实现新的接口。也就是说,访问者需要的功能应该是已经实现好了的,不需要适配器模式来实现,适配器模式主要是负责把不兼容的接口转换成访问者期望的格式而已。 6 | 7 | ## 1. 适配器模式生活实例 8 | 9 | 现实生活中我们会遇到形形色色的适配器,最常见的就是转接头了,比如不同规格电源接口的转接头、iPhone 手机的 3.5 毫米耳机插口转接头、DP/miniDP/HDMI/DVI/VGA 等视频转接头、电脑、手机、ipad 的电源适配器,都是属于适配器的范畴。 在类似场景中,这些例子有以下特点: 10 | 11 | * 旧有接口格式已经不满足现在的需要; 12 | * 通过增加适配器来更好地使用旧有接口。 13 | 14 | ## 2. 适配器模式的实现 15 | 16 | 电源适配器的例子,一开始我们使用的中国插头标准: 17 | 18 | 但是我们出国旅游了,到了日本,需要增加一个日本插头到中国插头的电源适配器,来将我们原来的电源线用起来: 19 | 20 | ![](../.gitbook/assets/7.1.14.1.png) 21 | 22 | 访问者需要目标对象的某个功能,但是这个对象的接口不是自己期望的,那么通过适配器模式对现有对象的接口进行包装,来获得自己需要的接口格式。 23 | 24 | ```javascript 25 | var chinaPlug = { 26 | name: 'china plug', 27 | chinaPlug() { 28 | console.log(this.name); 29 | } 30 | } 31 | 32 | var japanPlug = { 33 | name: 'japan plug', 34 | japanPlug() { 35 | console.log(this.name); 36 | } 37 | } 38 | 39 | function japanPlugAdapter(plug) { 40 | return { 41 | // 改变chinaPlug调用接口,不影响之前的调用接口 42 | chinaPlug() { 43 | return plug.japanPlug(); 44 | } 45 | } 46 | } 47 | chinaPlug.chinaPlug(); 48 | japanPlugAdapter(japanPlug).chinaPlug(); 49 | ``` 50 | 51 | ## 3. 适配器模式的优缺点 52 | 53 | ### 适配器模式的优点: 54 | 55 | * 已有的功能如果只是接口不兼容,使用适配器适配已有功能,可以使原有逻辑得到更好的复用,有助于避免大规模改写现有代码; 56 | * 可扩展性良好,在实现适配器功能的时候,可以调用自己开发的功能,从而方便地扩展系统的功能; 57 | * 灵活性好,因为适配器并没有对原有对象的功能有所影响,如果不想使用适配器了,那么直接删掉即可,不会对使用原有对象的代码有影响。 58 | 59 | ### 适配器模式的缺点: 60 | 61 | * 会让系统变得零乱,明明调用 A,却被适配到了 B,如果系统中这样的情况很多,那么对可阅读性不太友好。如果没必要使用适配器模式的话,可以考虑重构,如果使用的话,可以考虑尽量把文档完善。 62 | 63 | -------------------------------------------------------------------------------- /0/0.1.1.md: -------------------------------------------------------------------------------- 1 | # 零.1.1 一线互联网公司有什么不同? 2 | 3 | ## 零.1.1.1 **首先,是"规模大,设施全,极具包容性"** 4 | 5 | 一线互联网公司的规模少则数千人,多则几万十来万人。办公室场地也超级大,成片的工位一望无际,办公场所内部设施齐全。以百度大厦为例,办公、吃饭、洗澡、睡眠、洗衣、健身、购物、娱乐……一应俱全,我在百度工作的时候,就曾经一个月在百度大厦里开心工作生活,不需要回家。 6 | 7 | 包容性在自己身上就得到了体现。我听力不好,加入360和百度这2个大集体后,丝毫不影响日常工作的合作和沟通。大家协作性、包容性意识都很强,会尽全力互相配合,也比较有爱。这些大集体内还有很多奇奇怪怪的人,绝大多数都能自由地发挥出自己的特长,就像夜空中的点点繁星,各自自在地闪耀着光芒。 8 | 9 | ![身后是百度大厦与我的小伙伴们,摄于2013年](../.gitbook/assets/baidu.jpeg) 10 | 11 | ## 零.1.1.2 **其次,是"分工细,技术要求广度,但更重要的是深度"** 12 | 13 | 这里的工种划分十分细致。从产品的创意讨论、原型设计,到交互设计,然后到Photoshop图片、Fireworks切图制作HTML/CSS静态页面,然后到写JavaScript前端业务逻辑,再到配置Linux服务器,安装数据库,建库,写后端API,Web服务器架设,安全防卫,日常业务测试,打包上线……都有专业的人员把关。在这里,你可以很快地成长为某个领域的专家,而且随着时间增长,随着参与项目数量增加、规模增大,你会不断得到锻炼,逐渐成为专家中的专家。 14 | 15 | 而我在创业的时候,就没有太明确的分工概念。因为初创公司钱不多,所以能招到的人少,事情多的时候,恨不得一个人搞定所有,遂被成功锤(zhe)炼(mo)成多面手,美其名曰"全栈工程师"😭。 16 | 17 | 18 | ## 零.1.1.3 **有鲜明的文化特征** 19 | 20 | 比如百度的同学文化,字节跳动的极客文化,阿里巴巴的侠客文化,都是很有意思的文化。因此,衡量一家互联网公司是否是一线公司的标准之一,就是看它有没有鲜明的文化特征。 21 | 22 | 23 | ## 零.1.1.4 **钟爱名校和高学历人才** 24 | 25 | 985和211毕业生是很受一线互联网公司欢迎的。虽然为了政治正确,这些公司口头上也说"学历不限,能力第一",但是如果你是985或者211毕业的,会加分的。当然,即使你不是985、211毕业生,也不是硕士博士,也没关系的,仍有非常非常多的不知名大学毕业、甚至辍学的朋友们在这些一线互联网企业做出了卓越的成绩。我在百度期间,就曾经为自己的团队不限学历地招聘了一个尚未毕业的专科生加入,而且他的表现确实惊艳。由此可见,能力才是第一位的。 26 | 27 | 28 | ## 零.1.1.5 **嗯嗯,开的薪资待遇高出一大截** 29 | 30 | 为了争夺人才实现公司正向循环发展,在薪酬竞赛中长期由这些一线互联网公司领跑,中小型互联网公司长期被压制,只能望其项背。目前同样的职业技能水平要求,字节跳动、快手、腾讯、饿了么、阿里巴巴等一线互联网大公司所开的年薪,几乎是其他中小型互联网公司的2倍甚至更高。 31 | 32 | ## 零.1.1.6 本篇结语 33 | 34 | 综上,在非一线互联网公司苦练3~5年之后,进入一线公司深造,把技术深度锤炼出来,是每一个有理想、有追求的前端工程师的必由之路。强烈推荐每个前端工程师,如果有条件、有机会的话,一定要入职一线互联网公司工作,体验那种多人大规模协作打造惊世产品的乐趣。这样的职业生涯,才是基本美满的。 35 | 36 | -------------------------------------------------------------------------------- /1/1.2.7.md: -------------------------------------------------------------------------------- 1 | # 壹.2.7 同步和异步,阻塞和非阻塞 2 | 3 | 本篇文章其实是讲编程全领域通行的概念,之所以单独拎出来写在本书,是因为笔者发现绝大多数前端工程师对这块儿的概念理解得不太严谨。 4 | 5 | 在实际的开发中,我们经常会听到同步、异步,阻塞、非阻塞这些编程概念,可能都会比较懵,然后就各种查网上似是而非的资料,结果越查越迷糊,大部分文章都千篇一律,没有说到本质上的区别。所以下次再碰到这些概念,印象还是比较模糊,尤其是在一些场景下同步与阻塞,异步与非阻塞感觉没什么区别,但其实这四个术语描述的事物还真不是一回事。 6 | 7 | 下面我们来慢慢探讨他们之间的区别与联系,在这之前,我们还会经常看到下面的组合术语: 8 | 9 | * 同步+阻塞 10 | * 同步+非阻塞 11 | * 异步+阻塞 12 | * 异步+非阻塞 13 | 14 | 在当什么是同步和异步,阻塞与非阻塞的概念还没弄清楚之前,更别提上面这些组合术语了,只会让你更加困惑。 15 | 16 | ## 壹.2.7.1 同步和异步 17 | 18 | **同步和异步其实指的是,请求发起方对消息结果的获取是主动发起的,还是等被动通知的。** 19 | 20 | 如果是请求方主动发起的,一直在等待应答结果(同步阻塞),或者可以先去处理其他的事情,但要不断轮询查看发起的请求是否有应答结果(同步非阻塞 )因为不管如何都要发起方主动获取消息结果,所以形式上还是同步操作。 21 | 22 | 如果是由服务方通知的,也就是请求方发出请求后,要么在一直等待通知(异步阻塞),要么就先去干自己的事了(异步非阻塞),当事情处理完成之后,服务方会主动通知请求方,它的请求已经完成,这就是异步。异步通知的方式一般是通过状态改变,消息通知,或者回调函数来完成,大多数时候采用的都是回调函数。 23 | 24 | ![](../.gitbook/assets/image%20%2835%29%20%281%29.png) 25 | 26 | ## 壹.2.7.2 阻塞和非阻塞 27 | 28 | 阻塞和非阻塞在计算机的世界里面,通常指的是针对IO的操作,如网络IO和磁盘IO等。 29 | 30 | 那么什么是阻塞和非阻塞呢?简单的说就是我们调用了一个函数之后,在等待这个函数返回结果之前,当前的线程是处于挂起状态,还是运行状态,如果是挂起状态,就意味着当前线程什么都不能干,就等着获取结果,这就叫同步阻塞,如果仍然是运行状态,就意味当前线程是可以的继续处理其他任务,但要时不时的去看下是否有结果了,这就是同步非阻塞。 31 | 32 | ![](../.gitbook/assets/image%20%2828%29.png) 33 | 34 | ## 壹.2.7.3 场景举例 35 | 36 | 同步,异步,阻塞和非阻塞,会组合成上面提到过的四种结果: 37 | 38 | 举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片。 39 | 40 | ### 1. 同步+阻塞 41 | 42 | 这个时候如果我们一直在店里面啥都不干,一直等待商家面前等待它洗完照片,这个过程就叫同步阻塞。 43 | 44 | ### 2. 同步+非阻塞 45 | 46 | 当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问,直到照片洗完,这个过程就叫同步非阻塞。 47 | 48 | ### 3. 异步+阻塞 49 | 50 | 因为店里生意太好了,越来越多的人过来拍,店里面快没地方坐了,老板说你把你手机号留下,我一会洗好了就打电话告诉你过来取,然后你去外面找了一个长凳开始躺着睡觉等待老板打电话,啥不都干,这个过程就叫异步阻塞。 51 | 52 | ### 4.异步+非阻塞 53 | 54 | 当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,店老板来了电话就去取照片,这样一来两不耽误,这个过程就叫异步非阻塞。 55 | 56 | ## 壹.2.7.4 小结 57 | 58 | 只有严谨地理解了同步、异步,阻塞、非阻塞的概念,才能更好地理解JavaScript的事件处理机制以及Eventloop,我们下一篇见。 59 | 60 | -------------------------------------------------------------------------------- /0/0.1.3.md: -------------------------------------------------------------------------------- 1 | # 零.1.3 该岗位负责做什么的,岗位所属部门在什么位置,上升空间多大? 2 | 3 | 在面试一线互联网公司之前,除了了解公司背景,还需要深入了解目标岗位的具体情况。这包括岗位职责、部门架构和职业发展路径。 4 | 5 | ## 零.1.3.1 深入了解岗位职责 6 | 7 | ### 前端工程师岗位职责 8 | - **基础开发工作**:HTML/CSS/JavaScript开发,页面布局和交互实现 9 | - **框架应用**:Vue、React、Angular等主流框架的使用和优化 10 | - **工程化**:Webpack、Vite等构建工具的使用,代码规范和质量控制 11 | - **性能优化**:页面加载速度优化,用户体验提升 12 | - **跨端开发**:移动端适配,小程序开发等 13 | - **团队协作**:与产品、设计、后端等角色的沟通配合 14 | 15 | ### 高级前端工程师额外职责 16 | - **技术方案设计**:复杂业务场景的技术选型和架构设计 17 | - **团队管理**:指导初级工程师,技术分享和培训 18 | - **技术预研**:新技术调研和团队技术栈升级 19 | 20 | ## 零.1.3.2 部门架构和位置 21 | 22 | ### 常见的前端部门组织架构 23 | ``` 24 | 技术部 25 | ├── 前端组 26 | │ ├── 基础架构组(负责公共组件、工具库) 27 | │ ├── 业务开发组(负责具体产品线) 28 | │ └── 移动端组(负责移动端开发) 29 | ├── 后端组 30 | ├── 测试组 31 | └── 运维组 32 | ``` 33 | 34 | ### 部门在公司中的位置 35 | - **产品导向型**:前端组直接向产品负责人汇报 36 | - **技术导向型**:前端组向技术总监汇报 37 | - **混合模式**:前端组在技术部门下,但参与产品决策 38 | 39 | ## 零.1.3.3 职业发展上升空间 40 | 41 | ### 技术发展路径 42 | 1. **初级前端工程师**(0-2年) 43 | - 掌握基础技能,能独立完成简单页面开发 44 | - 薪资范围:8K-15K 45 | 46 | 2. **中级前端工程师**(2-5年) 47 | - 熟练使用主流框架,能解决复杂技术问题 48 | - 薪资范围:15K-30K 49 | 50 | 3. **高级前端工程师**(5-8年) 51 | - 技术专家,能设计技术方案,指导团队 52 | - 薪资范围:30K-50K 53 | 54 | 4. **前端架构师**(8年以上) 55 | - 负责技术架构设计,技术决策 56 | - 薪资范围:50K-80K+ 57 | 58 | 5. **技术总监/CTO**(10年以上) 59 | - 负责整个技术团队的管理和公司技术战略 60 | - 薪资范围:80K-200K+ 61 | 62 | ### 管理发展路径 63 | - **技术组长**:负责小团队的技术指导 64 | - **前端经理**:负责前端团队的管理和业务对接 65 | - **技术总监**:负责多个技术团队的管理 66 | 67 | ## 零.1.3.4 如何了解这些信息 68 | 69 | ### 面试前准备 70 | 1. **查看招聘JD**:仔细阅读岗位描述和任职要求 71 | 2. **搜索公司信息**:了解公司的组织架构和技术栈 72 | 3. **咨询内部员工**:通过人脉了解真实情况 73 | 4. **查看技术博客**:了解公司的技术文化和项目 74 | 75 | ### 面试中询问 76 | - "这个岗位具体负责哪些业务线?" 77 | - "团队规模有多大,如何分工协作?" 78 | - "公司的技术栈是什么,有什么技术规划?" 79 | - "这个岗位的晋升机制是怎样的?" 80 | 81 | ## 零.1.3.5 结语 82 | 83 | 了解岗位职责、部门位置和上升空间,不仅能帮助你在面试中表现更好,也能让你对未来的职业发展有更清晰的规划。选择适合自己的岗位和公司,比盲目追求大公司更重要。 84 | 85 | -------------------------------------------------------------------------------- /1/1.5.4.md: -------------------------------------------------------------------------------- 1 | # 壹.5.4 HTTP和HTTPS的区别在哪里 2 | 3 | ## HTTP和HTTPS的基本概念 4 | 5 | **HTTP**:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。 6 | 7 | **HTTPS**:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入[SSL](https://link.juejin.cn?target=https%3A%2F%2Fso.csdn.net%2Fso%2Fsearch%3Fq%3DSSL%26spm%3D1001.2101.3001.7020)层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 8 | 9 | HTTPS协议的主要作用可以分为两种: 10 | 1. 一种是建立一个信息安全通道,来保证数据传输的安全 11 | 2. 另一种就是确认网站的真实性 12 | 13 | ## 二者的区别 14 | 15 | HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。 16 | 17 | 简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全。 18 | 19 | HTTPS和HTTP的区别主要如下: 20 | 21 | 1. **HTTPS协议需要到CA申请证书**,一般免费证书较少,因而需要一定费用 22 | 2. **HTTP是超文本传输协议**,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议 23 | 3. **HTTP和HTTPS使用的是完全不同的连接方式**,用的端口也不一样,前者是80,后者是443 24 | 4. **HTTP的连接很简单,是无状态的**;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全 25 | 26 | ## HTTPS的特点 27 | 28 | ### 优点 29 | 30 | 1. **使用HTTPS协议可认证用户和服务器**,确保数据发送到正确的客户机和服务器 31 | 2. **HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议**,要比HTTP协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性 32 | 3. **HTTPS是现行架构下最安全的解决方案**,虽然不是绝对安全,但它大幅增加了中间人攻击的成本 33 | 4. **谷歌曾在2014年8月份调整搜索引擎算法**,并称"比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高" 34 | 35 | ### 缺点 36 | 37 | 1. **HTTPS协议握手阶段比较费时**,会使页面的加载时间延长近50%,增加10%到20%的耗电 38 | 2. **HTTPS连接缓存不如HTTP高效**,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响 39 | 3. **SSL证书需要钱**,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用 40 | 4. **SSL证书通常需要绑定IP**,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗 41 | 5. **HTTPS协议的加密范围也比较有限**,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行 42 | 43 | ## 实际应用建议 44 | 45 | ### 何时使用HTTP 46 | - 内部网络环境 47 | - 不涉及敏感信息的网站 48 | - 对性能要求极高的场景 49 | 50 | ### 何时使用HTTPS 51 | - 涉及用户隐私的网站(如电商、银行) 52 | - 需要用户登录的网站 53 | - 对安全性要求较高的应用 54 | - 希望获得更好的搜索引擎排名 55 | 56 | ## 结语 57 | 58 | 虽然HTTPS相比HTTP有一些性能上的损失,但在当今网络安全日益重要的环境下,使用HTTPS已经成为最佳实践。对于大多数网站来说,安全性的提升远大于性能的轻微损失。随着技术的不断发展,HTTPS的性能开销也在逐渐减小。 59 | 60 | -------------------------------------------------------------------------------- /2/2.2.2.md: -------------------------------------------------------------------------------- 1 | # 贰.2.2 分治法、动态规划与贪心算法的区别 2 | 3 | 分治法,动态规划、贪心算法三者之间有类似之处,比如都需要将问题划分为一个个子问题,然后通过解决这些子问题来解决最终问题,但其实这三者之间的区别还是很大的。 4 | 5 | ## **分治法** 6 | 7 | 分治法\(Divide-and-Conquer\) : 将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。 8 | 9 | 分治模式在每一层递归上都有三个步骤: 10 | 11 | * 分解\(Divide\):将原问题分解成一系列子问题; 12 | * 解决\(Conquer\):递归地解决各个子问题。若子问题足够小,则直接求解。 13 | * 合并\(Combine\):将子问题的结果合并成原问题的解。 14 | 15 | 合并排序\(Merge Sort\)是一个典型分治法的例子。其对应的直观的操作如下: 16 | 17 | 分解: 将n个元素分成各含n/2个元素的子序列; 18 | 19 | 解决:用合并排序法对两个子序列递归地排序; 20 | 21 | 合并:合并两个已排序的子序列以得到排序结果。 22 | 23 | ## **动态规划** 24 | 25 | 动态规划算法的设计可以分为如下4个步骤: 26 | 27 | * 描述最优解的结构 28 | * 递归定义最优解的值 29 | * 按自底向上的方式计算最优解的值 30 | * 由计算出的结果构造一个最优解 31 | 32 | 分治法是指将问题划分成一些独立的子问题,递归的求解各子问题,然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。 33 | 34 | 适合采用动态规划方法的最优化问题中的两个要素:最优子机构和重叠子问题。 35 | 36 | 最优子机构:如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子机构。 37 | 38 | 重叠子问题:适用于动态规划求解的最优化问题必须具有的第二个要素是子问题的空间要很小,也就是用来求解原问题的递归算法反复地解同样的子问题,而不是总是在产生新的子问题。对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,则它们是重叠的。 39 | 40 | In a word, 分治法 —— 各子问题独立;动态规划 —— 各子问题重叠。 41 | 42 | 算法导论: **动态规划要求其子问题既要独立又要重叠,这看上去似乎有些奇怪。虽然这两点要求听起来可能矛盾的,但它们描述了两种不同的概念,而不是同一个问题的两个方面。如果同一个问题的两个子问题不共享资源,则它们就是独立的。对两个子问题俩说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,是重叠的,则它们是重叠** 43 | 44 | ## **贪心算法** 45 | 46 | 对许多最优化问题来说,采用动态规划方法来决定最佳选择有点“杀鸡用牛刀”了,只要采用另一些更简单有效的算法就行了。贪心算法是使所做的选择看起来都是当前最佳的,期望通过所做的局部最优选择来产生出一个全局最优解。贪心算法对大多数优化问题来说能产生最优解,但也不一定总是这样的。 47 | 48 | 贪心算法只需考虑一个选择(亦即,贪心的选择);在做贪心选择时,子问题之一必须是空的,因此只留下一个非空子问题。 49 | 50 | 贪心算法与动态规划与很多相似之处。特别地,贪心算法适用的问题也是最优子结构。贪心算法与动态规划有一个显著的区别,就是贪心算法中,是以自顶向下的方式使用最优子结构的。贪心算法会先做选择,在当时看起来是最优的选择,然后再求解一个结果子问题,而不是先寻找子问题的最优解,然后再做选择。 51 | 52 | 贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时看起来是最佳的选择。这一点是贪心算法不同于动态规划之处。在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。因此,解动态规划问题一般是自底向上,从小子问题处理至大子问题。贪心算法所做的当前选择可能要依赖于已经做出的所有选择,但不依赖于有待于做出的选择或子问题的解。 53 | 54 | **因此,贪心算法通常是自顶向下地做出贪心选择,不断地将给定的问题实例归约为更小的问题。贪心算法划分子问题的结果,通常是仅存在一个非空的子问题。** 55 | 56 | -------------------------------------------------------------------------------- /6/6.1.3.md: -------------------------------------------------------------------------------- 1 | # 陆.1.3 开放封闭原则 2 | 3 | ## 01.官方定义 4 | 5 | 开闭原则,英文缩写**OCP**,全称 **Open Closed Principle**。 6 | 7 | 原始定义: 8 | 9 | > Software entities \(classes, modules, functions\) should be open for extension but closed for modification。 10 | 11 | 严谨的翻译: 12 | 13 | > 软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭。 14 | 15 | ## **02.原理解释** 16 | 17 | ### 对扩展开放 18 | 19 | 模块对扩展开放,就意味着需求变化时,可以对模块扩展,使其具有满足那些改变的新行为。换句话说,模块通过扩展的方式去应对需求的变化。 20 | 21 | ### 对修改关闭 22 | 23 | 模块对修改关闭,表示当需求变化时,关闭对模块源代码的修改,当然这里的“关闭”应该是尽可能不修改的意思,也就是说,应该尽量在不修改源代码的基础上面扩展组件。 24 | 25 | ## **03.为什么要“开”和“闭”** 26 | 27 | 一般情况,我们接到需求变更的通知,通常方式可能就是修改模块的源代码,然而修改已经存在的源代码是存在很大风险的,尤其是项目上线运行一段时间后,开发人员发生变化,这种风险可能就更大。所以,为了避免这种风险,在面对需求变更时,我们一般不修改源代码,即所谓的对修改关闭。不允许修改源代码,我们如何应对需求变更呢?答案就是我们下面要说的对扩展开放。 28 | 29 | 通过扩展去应对需求变化,就要求我们必须要面向接口编程,或者说面向抽象编程。所有参数类型、引用传递的对象必须使用抽象(接口或者抽象类)的方式定义,不能使用实现类的方式定义;通过抽象去界定扩展,比如我们定义了一个接口A的参数,那么我们的扩展只能是接口A的实现类。 30 | 31 | ## **04.总结** 32 | 33 | 有一个经验可以快速检验代码是否遵守了该原则:如果我必须打开你某个模块的js文件并进行修改具体代码才能增强该模块的功能,则你写的这个模块无法通过“开放封闭原则”的检验。 34 | 35 | ```javascript 36 | //iceCreamMaker.js 37 | let iceCreamFlavors = ['chocolate', 'vanilla'];//口味 38 | let iceCreamMaker = { 39 | makeIceCream(flavor) { 40 | if (iceCreamFlavors.indexOf(flavor) > -1) { 41 | console.log('您选的口味有货,马上给您做冰激凌。'); 42 | } else { 43 | console.log('哎呀,您选的口味我们没有。'); 44 | } 45 | }, 46 | }; 47 | export default iceCreamMaker; 48 | ``` 49 | 50 | 如你所见,上面这段代码,不编辑`iceCreamFlavor`数组就无法添加冰淇淋口味!我们来改进一下: 51 | 52 | ```javascript 53 | //iceCreamMaker.js 54 | let iceCreamFlavors = ['chocolate', 'vanilla'];//口味 55 | let iceCreamMaker = { 56 | makeIceCream(flavor) { 57 | if (iceCreamFlavors.indexOf(flavor) > -1) { 58 | console.log('您选的口味有货,马上给您做冰激凌。'); 59 | } else { 60 | console.log('哎呀,您选的口味我们没有。'); 61 | } 62 | }, 63 | //增加口味 64 | addFlavor(flavor) { 65 | iceCreamFlavors.push(flavor); 66 | }, 67 | }; 68 | export default iceCreamMaker; 69 | ``` 70 | 71 | 现在,我们可以在代码中的任何位置调用addFlavor函数,以便添加美味的冰淇淋口味,而无需打开编辑iceCreamMaker.js文件。 确实是可靠的\(solid\)JavaScript,😋 ! 72 | 73 | **总之,开放封闭原则可提高软件的可维护性与代码的重用性。** 74 | 75 | -------------------------------------------------------------------------------- /5/5.3.1.md: -------------------------------------------------------------------------------- 1 | # 伍.3.1 什么是响应式编程? 2 | 3 | **响应式编程简称RP(Reactive Programming)**,是一种面向**异步**(asynchronous)**数据流**(Data Stream)的编程范式。其实,这并不是什么新东西,而是新瓶装旧酒,把编程中常用的[发布-订阅模式](../7/7.1.5.md)发扬光大,然后形成一整套可供我们高效解决问题的编程范式。 4 | 5 | ## 01.什么是数据流(Data Stream\) 6 | 7 | 接上面说的,其实数据流也不是新东西。举个例子,我们在浏览器中单击一个按钮,这个点击事件\(event\)就是一个真正的异步数据流(asynchronous data stream),以下简称流。在这个流里面,你可以观察一些对象,也可以做一些响应处理,总之随你喜好。 8 | 9 | 而且,我们可以把任意东西包装成流,而不限于点击事件、或者鼠标滑过事件。流无处不在,信手拈来,任意东西都可以成为流:变量、用户的操作、属性、缓存、数据结构等等。譬如:假想你的微博首页是一个流,那么你就可以监听它的变化,并且根据需要采取阅读与否的决定(做出响应)。 10 | 11 | 最重要的是,我们将获得令人惊叹的功能工具箱,这个工具箱里面有一系列函数,可以帮助我们组合、创建、过滤这些流。是的,这时候函数式编程就派上用场了。如果不了解函数式编程,可以看本书之前的相关章节。 12 | 13 | 流可以用作另一流的输入, 甚至多个流也可以用作另一个流的输入。 您可以合并两个流。 您可以过滤流以获得另一个只包含您感兴趣的事件的流。您可以将数据值从一个流映射到另一个新流。 14 | 15 | 既然流对于响应式(Reactive)如此重要,那么,让我们从熟悉的“单击按钮”这个事件流开始细致地研究它们。接下来我们看一个图,因为看起来像一颗颗弹珠,通常这类型的图叫弹珠图。 16 | 17 | ![弹珠图](../.gitbook/assets/5.3.1.1.1.png) 18 | 19 | 如上图,水平从左至右的箭头线表示时间线。流是**按时间顺序排列的一系列进行中的事件**, 它可以含有三种不同的东西: 20 | 21 | * 一个值(是某种类型的)。也就是上图的各色弹珠。 22 | * 一个错误(也可能没有)。也就是上图的红叉。 23 | * 一个“完成”信号(也可能没有)。也就是上图中时间线上的竖线。 例如,当包含该按钮的当前窗口或视图关闭时,可以认为流发出了“完成”信号。 24 | 25 | 接下来,让我们做点有趣的事情:创建从原始点击事件流转换而来的新点击事件流。 26 | 27 | ## 02.流的转换 28 | 29 | 在[函数式编程](5.2.1.md#07-han-zi-functor)我们介绍过带有map方法的函子(比如Array就是函子,实现了map方法)。map方法接受一个函数,让该函数处理函子里面的数据,输出另一个函子。这段说得比较学术化,但其实说白了,就是将原数据通过map转换一下,得到一个新的数据,如此而已。 30 | 31 | 针对流,我们也可以类似的这么做。输入一个流,通过map(等)函数处理一下,变成一个新的流。 32 | 33 | //todo 34 | 35 | ## 03.“我为什么要考虑通过响应式编程解决问题?” 36 | 37 | 上面介绍了一些响应式编程的技术特征,那么响应式编程的必要性是什么呢?可以先简单讲一下。后面再以实例展开叙述。 38 | 39 | 响应式编程提升了代码的抽象层次,让我们可以专注于处理业务逻辑,而不必关注太多细枝末节的事情:比如服务器端数据变化后,前端变量值的实时获取、及时更新其他有关的变量、以及界面的再次渲染。使用响应式编程会让这些细碎的事情变得更简单。 40 | 41 | 在与大量数据事件相关的、UI事件高度交互的现代Web应用程序和移动应用程序中,响应式编程的好处更加明显。10年前,与网页的交互基本上是向后端提交一个form表单,后端处理好之后,再向前端执行“粗暴的”渲染逻辑以呈现变化。现代的应用程序已经变得更加实时:修改单个表单字段可以自动触发到后端的保存,对某些内容的“点赞”可以实时反映给其他已连接的用户……等等。 42 | 43 | 如今,现代应用程序具有各种实时事件,可以为用户带来高度交互的体验。我们需要适当处理这些问题的工具,而响应式编程就是一个不错的选项。 44 | 45 | ## 参考文献 46 | 47 | {% hint style="info" %} 48 | [https://gist.github.com/staltz/868e7e9bc2a7b8c1f754](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) 49 | {% endhint %} 50 | 51 | -------------------------------------------------------------------------------- /6/6.1.4.md: -------------------------------------------------------------------------------- 1 | # 陆.1.4 里氏替换原则 2 | 3 | ## 01.什么是里氏替换原则 4 | 5 | 里氏替换原则\(Liskov Substitution Principle LSP\)面向对象设计的基本原则之一。 6 | 7 | > 里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 8 | 9 | LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 10 | 11 | 我们一般对里氏替换原则 LSP 的解释为: 12 | 13 | > **子类对象能够替换父类对象,而程序逻辑不变。** 14 | 15 | ## 02.里氏替换原则的两种含义: 16 | 17 | ### 1\).含义一 18 | 19 | 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。 20 | 21 | ### 2\).含义二 22 | 23 | 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。 24 | 25 | 不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。 26 | 27 | 在面向对象编程领域,根据我的经验可以总结一句话 :**“** **尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承”。** 28 | 29 | ## **03.JavaScript代码示例** 30 | 31 | 在JavaScript领域,因为JavaScript实在太“宽松”了,几乎想怎么写就怎么写,所以并不是特别好演示里氏替换原则。这里用一个简单的例子来粗浅地演示一下: 32 | 33 | ```javascript 34 | //父类 35 | class Shape { 36 | get area() { 37 | return 0; 38 | } 39 | } 40 | 41 | //子类 42 | class Rectangle extends Shape { 43 | constructor(length, width) { 44 | super(); 45 | this.length = length; 46 | this.width = width; 47 | } get area() { 48 | return this.length * this.width; 49 | } 50 | } 51 | //子类 52 | class Square extends Shape { 53 | constructor(length) { 54 | super(); 55 | this.length = length; 56 | } get area() { 57 | return this.length ** 2; 58 | } 59 | } 60 | //子类 61 | class Circle extends Shape { 62 | constructor(radius) { 63 | super(); 64 | this.radius = radius; 65 | } get area() { 66 | return Math.PI * (this.radius ** 2); 67 | } 68 | } 69 | 70 | //这里是父类hape的数组,但是数组具体的项存放的是子类的实例 71 | const shapes = [ 72 | new Rectangle(1, 2), 73 | new Square(1, 2), 74 | new Circle(2), 75 | ] 76 | 77 | for (let s of shapes) { 78 | //本来要调用父类的area,但是现在调用子类的area。 79 | //即使调用子类的area,也不会影响软件的整体功能。 80 | //也即“子类对象能够替换父类对象,而程序逻辑不变”。 81 | console.log(s.area); 82 | } 83 | ``` 84 | 85 | \*\*\*\* 86 | 87 | -------------------------------------------------------------------------------- /1/1.2.14.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 一线互联网公司几乎都在推行模块化开发 3 | --- 4 | 5 | # 壹.2.14 模块化开发 6 | 7 | ## 壹.2.14.1 老一辈前端工程师如何实现模块化 8 | 9 | 在 ECMAScript 6 出现之前,JavaScript语言没有内建支持模块化的语法,这导致前端开发复杂Web应用的时候,引用.js、组织文件、扩展功能、维护工程都显得效率低下,而且流程繁琐。然而对于一个复杂的Web应用,模块化编程可以良好的工作分工、功能扩展、工程维护,是软件开发方法论里面最基本的需求。那时候老一辈前端工程师们可以使用 IIFE 函数来实现模块化,很多库比如jQuery是这样实现的。 10 | 11 | ```javascript 12 | let module = (function() { 13 | var _version = "1.0"; 14 | var _name = "a-nice-module"; 15 | 16 | function _log(x) { 17 | console.log(x); 18 | } 19 | 20 | function say(s) { 21 | _log(s); 22 | } 23 | 24 | return { 25 | name: _name, 26 | say: say 27 | }; 28 | })(); 29 | 30 | console.log(module._version);//>> undefined 31 | console.log(module._log);//>> undefined 32 | console.log(module.name); //>> a-nice-module 33 | module.say("hello"); //>> hello 34 | ``` 35 | 36 | **模块本质上是封装,把模块内可访问的和不可以访问的区分得清清楚楚**。如上示例,模块之外是无法访问到模块内部的私有属性`_version`和私有方法`_log`的;而以一个对象的属性和方法的形式暴露内部属性`_name`和方法`say(s)`之后,然后将该对象返回给模块外部的调用者,那么在模块外部就可以访问它们了。 37 | 38 | ECMAScript 6 吸收了老一辈前端工程师的各种优秀思想,最终采用了`import`和`export`内建语法支持模块化开发,基于浏览器底层的实现,使前端的模块化开发变得空前地规范和有效率起来,模块化开发终于迎来了应用热潮。 39 | 40 | ## 壹.2.14.2 AMD 和 CommonJS 41 | 42 | //todo 43 | 44 | ## 壹.2.14.3 ECMAScript 6 的模块化 45 | 46 | ### 1.模块里的变量是以引用形式传递的 47 | 48 | 模块内变量的改变会同步到外面,说明是模块是以引用类型传递的,也即引用模块时,模块外层再包了一层对象,变量以该对象的属性传递过去。举例说明如下: 49 | 50 | ```javascript 51 | ///////////////// module.js ////////////////////// 52 | 53 | let m = 0; 54 | setTimeout(() => { 55 | m = 1; 56 | }, 1000); 57 | export function getM() { 58 | return m; 59 | } 60 | ``` 61 | 62 | 上面代码相当于传递下面的对象,其中\_m表示私有属性m 63 | 64 | ```javascript 65 | { 66 | _m:0, 67 | getM:function(){ 68 | return this._m; 69 | } 70 | } 71 | ``` 72 | 73 | 下面为入口文件 index.js : 74 | 75 | ```javascript 76 | /////////////// index.js ////////////////////////// 77 | 78 | import {getM} from "./module.js"; 79 | setTimeout(()=>{ 80 | console.log(getM()); 81 | }); 82 | setTimeout(()=>{ 83 | console.log(getM()); 84 | },2000); 85 | //>> 0 86 | //>> 1 87 | ``` 88 | 89 | ### 2.模块嵌套 90 | 91 | 有些书籍称之为模块的继承,继承是面向对象编程的概念,本书更愿意称之为**嵌套**。 92 | 93 | //todo 94 | 95 | -------------------------------------------------------------------------------- /3/3.1.2.md: -------------------------------------------------------------------------------- 1 | # 叁.1.2 React、Vue和Angular对比 2 | 3 | ## 1. React VS Vue 4 | 5 | > React 的哲学是:**如无必要,勿增实体**。 6 | > 7 | > Vue 的哲学是:什么好用,给你什么。 8 | > Vue 会自动帮你绑定 this,React 不会,因为 JS 能做; 9 | > Vue 会自动帮你合并 class 和 style,React 不会,因为 JS 能做; 10 | > Vue 会给你的 data 创建 getter/setter,React 不会,因为 JS 能做; 11 | > Vue 会给你提供 v-if / v-for / v-show,React 不会,因为 JS 能做; 12 | > Vue 会给你提供 computed / watch / methods,React 不会,因为 JS 能做。 13 | > 14 | > 那么 React 到底提供了什么? 15 | > **只提供了一条让你用原生 JS 解决所有 UI 问题的途径。** 16 | > 17 | > 很多新人反馈: 18 | > Vue 用久了,好像不会写高级的 JS 了,只会一些简单的,居然就能完成工作。 19 | > 而 React 用久了,就发现 JS 的语法特性真多啊,学都学不过来。 20 | 21 | by [方应杭](https://www.zhihu.com/question/314428335/answer/644922545) 22 | 23 | ## 2. React VS Angluar 24 | 25 | > 你会发现 Angular 在逐渐的弥补自身框架的不足,其他框架也在弥补他们自身的不足,他们都在互相吸取,互相学习,大家最终会越来越趋同。 26 | > 27 | > 喜欢装饰器,Class Component,Service Class 风格的,可以选择 Angular。 28 | > 29 | > 喜欢纯函数,Function Component,Hooks 的,可以选择 React。 30 | > 31 | > 没有啥偏好的使用 Vue,想用啥用啥。 32 | 33 | by [徐海峰](%20https://www.zhihu.com/question/355760849/answer/938934606) 34 | 35 | > Angular 的初始曲线对于搞 java 的人来说确实不高,因为那一套概念都是 java 里面有的,而且搞 java 的都已经习惯繁琐了。但是对于不搞 java 的人来说,就很烦了,更关键的是在我看来前端根本就不应该学 java 那一套。 36 | > 37 | > Angular 的后续曲线也不低,因为它的可优化性比较糟糕,以至于你不理解它的内部运作原理就很难避免各种性能坑。 38 | 39 | > React 上手的曲线也看人。对于习惯了服务端模板的人来说,要接受 JSX 是一个挑战;但是对于搞函数式的人来说,简直就是太自然了,因为 JSX 能让你把模板看做一个纯函数。过了这个坎后上手写两个例子,React 还是相对简单的。但是这也是个假象。接下来要搞『正经』的 React 应用了,突然出现了 Node,CommonJS, Webpack 一堆东西。写过 Node 的人会觉得哦哦,太自然了。没接触过 Node 的人就像没接触过 java 的人初学 Angular 一样:这些都是什么鬼。再往下,终于把构建环境搭好了,这时候你会发现 React 文档里的东西你已经也基本看完了,你以为你这就叫『学会 React』了?其实这时候你还是不知道怎么用 React 写一个应用,因为 React 本身不管这些事情,所以你要开始跳入 React 社区的大坑,开始学习 react-router, react-hot-reload, Flux 及其 N 种实现 \([voronianski/flux-comparison · GitHub](https://link.zhihu.com/?target=https%3A//github.com/voronianski/flux-comparison)\) , CSS in JS 及其 N 种实现 \([https://github.com/MicheleBertoli/css-in-js](https://link.zhihu.com/?target=https%3A//github.com/MicheleBertoli/css-in-js)\),Immutable-js, GraphQL, Relay... 还有 context 这种大家都在用但是没有官方文档的功能...(现在终于有文档了)用 Redux 又开始一堆概念了,actions, action creators, reducers, containers, higher order components... 然后现在又开始配着 generator 搞 redux-saga,搞着搞着你可能还会被 Clojure/Elm 吸引过去,跳入函数式的大坑... 这个曲线比起 Angular,只能说有过之而无不及... 当然,因为学习过程中多了很多可以用来装逼的资本,还是有一定的激励效果的,不像 Angular 学了半天也没什么能吹的。 40 | 41 | by [尤雨溪](%20https://www.zhihu.com/question/35767399/answer/64496760) 42 | 43 | -------------------------------------------------------------------------------- /2/2.1.6.md: -------------------------------------------------------------------------------- 1 | # 贰.1.6 分治法、动态规划与贪心算法的区别 2 | 3 | 分治法,动态规划、贪心算法三者之间有类似之处,比如都需要将问题划分为一个个子问题,然后通过求解子问题来得到最终解,但其实这三者之间的区别还是很大的。 4 | 5 | ## **01.分治法** 6 | 7 | 分治法\(Divide-and-Conquer\) : 将原问题划分成n个规模较小而结构、且与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。 8 | 9 | 分治模式在每一层递归上都有三个步骤: 10 | 11 | * 分解\(Divide\):将原问题分解成一系列子问题; 12 | * 解决\(Conquer\):递归地解决各个子问题。若子问题足够小,则直接求解。 13 | * 合并\(Combine\):将子问题的结果合并成原问题的解。 14 | 15 | [快速排序](2.1.1.md#kuai-su-pai-xu)是一个典型分治法的例子。 16 | 17 | ## **02.动态规划** 18 | 19 | 动态规划算法的设计可以分为如下4个步骤: 20 | 21 | * 描述最优解的结构 22 | * 递归定义最优解的值 23 | * 按自底向上的方式计算最优解的值 24 | * 由计算出的结果构造一个最优解 25 | 26 | 分治法是指将问题划分成一些独立的子问题,递归的求解各子问题,然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题独立且**重叠**的情况,也就是各子问题包含公共的子子问题。在这种情况下,若用分治法则会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。 27 | 28 | 适合采用动态规划方法的最优化问题中的两个要素:最优子机构和重叠子问题。 29 | 30 | **最优子机构:**如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子机构。 31 | 32 | **重叠子问题:**子问题的空间要很小,也就是用来求解原问题的递归算法反复地解同样的子问题,而不是总是在产生新的子问题。对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,则它们是重叠的。 33 | 34 | **一句话总结分治法与动态规划的区别: 分治法 —— 各子问题独立;动态规划 —— 各子问题重叠。** 35 | 36 | 引自《算法导论》: 37 | 38 | > 动态规划要求其子问题既要独立又要重叠,这看上去似乎有些奇怪。虽然这两点要求听起来可能矛盾的,但它们描述了两种不同的概念,而不是同一个问题的两个方面。如果同一个问题的两个子问题不共享资源,则它们就是独立的。对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,是重叠的,则它们是重叠。 39 | 40 | ### 延伸阅读(建议必读) 41 | 42 | {% hint style="info" %} 43 | 1\) [司徒正美讲动态规划:Javascript背包问题详解](https://segmentfault.com/a/1190000012829866) 44 | 45 | 2\)[【动态规划】三种背包问题(01背包、完全背包、多重背包)](https://blog.csdn.net/sinat_30973431/article/details/85119871) 46 | {% endhint %} 47 | 48 | ### **华为实战题** 49 | 50 | > **有一座独木桥,长度为L,青蛙每跳一次的最大距离为M,最小距离为S,桥上有N个石墩,石墩不会落在两端只在桥中间,桥墩位置数组为Postions,青蛙很不喜欢站在石墩上。问:青蛙最少要几步能过桥,而且满足落在石墩上的次数最少。** 51 | > 52 | > 举例输入:L=10,S=1,M=3,N=5,Positions=\[2,4,5,7,9\] 53 | > 应该输出:2 54 | 55 | //todo 56 | 57 | ## **03.贪心算法** 58 | 59 | 对许多最优化问题来说,采用动态规划方法来决定最佳选择有点“杀鸡用牛刀”了,只要采用另一些更简单有效的算法就行了。贪心算法是使所做的选择**看起来都是当前最佳的**,期望通过所做的局部最优选择来产生出一个全局最优解。贪心算法对大多数优化问题来说能产生最优解,但也不一定总是这样的。 60 | 61 | 贪心算法只需考虑一个选择(亦即,贪心的选择);在做贪心选择时,子问题之一必须是空的,因此只留下一个非空子问题。 62 | 63 | 贪心算法与动态规划与很多相似之处。特别地,贪心算法适用的问题也是最优子结构。贪心算法与动态规划有一个显著的区别,就是贪心算法中,是以自顶向下的方式使用最优子结构的。贪心算法会先做选择,这个选择在当时看起来是最优的,然后再求解一个结果子问题;而不是先寻找子问题的最优解,然后再做选择。 64 | 65 | 贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时看起来是最佳的选择。这一点是贪心算法不同于动态规划之处。在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。因此,解动态规划问题一般是自底向上,从小子问题处理至大子问题。贪心算法所做的当前选择可能要依赖于已经做出的所有选择,但不依赖于有待于做出的选择或子问题的解。 66 | 67 | **因此,贪心算法通常是自顶向下地做出贪心选择,不断地将给定的问题实例归约为更小的问题。贪心算法划分子问题的结果,通常是仅存在一个非空的子问题。** 68 | 69 | -------------------------------------------------------------------------------- /0/0.1.2.md: -------------------------------------------------------------------------------- 1 | # 零.1.2 该公司是做什么的,实力怎么样,前景如何,口碑怎样? 2 | 3 | 知己知彼,百战不殆。在打算寻求入职某家一线互联网公司之前,多打听一下该公司背景情况,充分做好准备,以便面试时不慌。 4 | 5 | ## 零.1.2.1 用词条筛选一遍 6 | 7 | 比如我想要面试“字节跳动\(bytedance\)”,就会可以进百度APP搜索“字节跳动”,一般一线互联网企业都有专人维护自己的百度百科词条,而该词条内容是否丰富,体现这个企业是否有足够的人力去维护企业形象。词条内容越丰富越具体,说明该公司很重视企业形象,属于实力强大的特征之一。 8 | 9 | 10 | 11 | 12 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 |
13 |

14 |

15 | 字节跳动-百度百科词条 17 |

18 |
20 |

21 |

22 | 字节跳动详细的词条信息 24 |

25 |
字节跳动-百度百科词条信息有专人维护
36 | 37 | 百度搜索一遍之后,大概就知道这家公司是做什么业务的,以及对公司实力有了一个初步印象了。 38 | 39 | ## 零.1.2.2 做好前景调查 40 | 41 | 然后进入知乎([www.zhihu.com](https://www.zhihu.com/))搜索该公司的业务关键字,看看高赞的话题评论。比如字节跳动主要产品《今日头条》和《抖音》,一个是做个性化信息流、一个是做小视频。那你就可以搜这些关键词,看看网上其他大牛在知乎上的评价。 42 | 43 | ## 零.1.2.3 打听口碑 44 | 45 | 口碑可以去“职声”这个APP查,也可以去“百度”、“知乎”搜索“xxx公司怎么样”以获得答案。如果平时人脉不错,可以去“微信前端群”、“QQ前端群”之类的前端专业群里询问群友,或者私下问问同行的朋友……总之多角度的打听清楚,从而对该公司口碑有一个立体清晰的认知。 46 | 47 | ## 零.1.2.4 调研该公司创始人 48 | 49 | 然后要提醒一下,一个公司的文化特征和这个公司最大的boss息息相关,所以也要记得调研一下这个公司的创始人,要认真思考看看是否是你欣赏和喜欢的,否则的话可能入职了一段时间会觉得不适应。 50 | 51 | ## 零.1.2.5 用好求职APP 52 | 53 | 如果对该公司的业务方向、产品形态、实力、前景都看好,而且口碑也不错的话,就可以针对这个公司搜索工作机会了。 54 | 55 | 搜索工作机会,推荐“Boss直聘”和“拉勾”。就前端而言,求职效果最好的就是这两个APP,Boss直聘的活跃程度要比拉勾高一些,Boss直聘是聊天勾搭且必须等对方回复了之后才能投简历,而拉勾是不用聊天就能直接投简历,各有优缺点,都可以试试,提高约面率。 56 | 57 | ## 零.1.2.6 用好猎头,事半功倍 58 | 59 | **Boss直聘上有猎头,拉勾上也有。** 60 | 61 | 猎头是做什么的呢?通俗点说就是工作机会信息中介。如何识别猎头呢?可以在APP里点开招聘方人员的头像,再点“查看ta的更多内容”,看看他发布了多少个职位。如果发布的职位包含很多个公司,同时面试地点遍布中国各地,那很大几率该“HR”实际上是搜集简历的猎头。 62 | 63 | 中介服务良莠不齐,猎头也一样。猎头里面也有少数认真负责的,提供的是**顾问式**服务。如果碰到这样的猎头,值得职业生涯里携手共度。 64 | 65 | 而目前很多猎头为了业绩而力求多推荐,并没有用心帮求职者做沟通和面试建议,以及很少做跟踪服务,企业的反馈结果也没有及时传递回来……这样的猎头就是在浪费求职者的时间。所以从概率上来说,碰到一个认真负责的好猎头概率真的很低,建议不要把简历递给猎头,而应该**努力地递给真正的企业HR**,他们才会更多认真的去推荐给相关部门,并且一般都有跟踪服务和及时的反馈。 66 | 67 | -------------------------------------------------------------------------------- /1/1.1.2.md: -------------------------------------------------------------------------------- 1 | # 壹.1.2 JavaScript未来的方向 2 | 3 | JavaScript作为一门动态编程语言,自1995年诞生以来,经历了翻天覆地的变化。从最初的简单脚本语言,发展到今天支持多种编程范式的强大语言。那么,JavaScript的未来发展方向是什么呢? 4 | 5 | ## 壹.1.2.1 语言特性的演进 6 | 7 | ### TC39委员会的推动作用 8 | TC39(Technical Committee 39)是负责ECMAScript标准制定的委员会,由各大浏览器厂商、技术公司代表组成。他们定期开会讨论和推进JavaScript新特性的提案,确保语言能够与时俱进。 9 | 10 | ### 年度发布模式 11 | 自ES6(ES2015)之后,JavaScript采用了年度发布模式,每年6月发布一个新版本。这种模式让JavaScript能够: 12 | - 更快地响应开发者需求 13 | - 保持向后兼容性 14 | - 逐步引入新特性,避免大规模破坏性更新 15 | 16 | ## 壹.1.2.2 主要发展方向 17 | 18 | ### 1. 性能优化 19 | - **V8引擎优化**:Google的V8引擎持续优化,提升JavaScript执行性能 20 | - **WebAssembly集成**:与WebAssembly的深度集成,让JavaScript能够调用高性能的底层代码 21 | - **JIT编译优化**:即时编译技术的不断改进 22 | 23 | ### 2. 类型系统增强 24 | - **TypeScript的普及**:微软的TypeScript为JavaScript添加了静态类型检查 25 | - **原生类型注解提案**:TC39正在讨论为JavaScript添加原生类型注解的可能性 26 | - **类型推断优化**:更好的类型推断和错误提示 27 | 28 | ### 3. 异步编程改进 29 | - **Async/Await的完善**:ES2017引入的async/await语法让异步编程更加直观 30 | - **Promise的增强**:Promise.allSettled、Promise.any等新方法的加入 31 | - **Top-level await**:模块级别的await支持 32 | 33 | ### 4. 模块系统发展 34 | - **ES Modules**:原生模块系统的普及和完善 35 | - **动态导入**:import()函数的支持,实现按需加载 36 | - **模块联邦**:微前端架构下的模块共享方案 37 | 38 | ## 壹.1.2.3 应用场景扩展 39 | 40 | ### 1. 全栈开发 41 | - **Node.js生态**:服务器端JavaScript的广泛应用 42 | - **Deno**:基于V8和Rust的新一代JavaScript运行时 43 | - **Bun**:高性能的JavaScript运行时和工具链 44 | 45 | ### 2. 移动端开发 46 | - **React Native**:使用JavaScript开发原生移动应用 47 | - **Flutter**:Google的跨平台框架,支持JavaScript 48 | - **小程序开发**:微信、支付宝等平台的小程序生态 49 | 50 | ### 3. 桌面应用 51 | - **Electron**:使用Web技术构建跨平台桌面应用 52 | - **Tauri**:基于Rust的轻量级桌面应用框架 53 | 54 | ### 4. 物联网和嵌入式 55 | - **IoT.js**:为物联网设备优化的JavaScript运行时 56 | - **Espruino**:专为微控制器设计的JavaScript解释器 57 | 58 | ## 壹.1.2.4 工具链和生态 59 | 60 | ### 1. 构建工具 61 | - **Vite**:基于ESM的快速构建工具 62 | - **Webpack 5**:模块打包工具的持续改进 63 | - **Rollup**:专注于库打包的工具 64 | 65 | ### 2. 包管理 66 | - **npm**:Node.js的包管理器 67 | - **yarn**:Facebook开发的包管理器 68 | - **pnpm**:快速、节省磁盘空间的包管理器 69 | 70 | ### 3. 开发体验 71 | - **ESLint**:代码质量检查工具 72 | - **Prettier**:代码格式化工具 73 | - **Husky**:Git钩子管理工具 74 | 75 | ## 壹.1.2.5 挑战和机遇 76 | 77 | ### 1. 面临的挑战 78 | - **浏览器兼容性**:不同浏览器的JavaScript引擎实现差异 79 | - **性能瓶颈**:在复杂应用中的性能限制 80 | - **生态系统碎片化**:过多的框架和工具选择 81 | 82 | ### 2. 发展机遇 83 | - **Web平台的强大**:Web API的不断丰富 84 | - **跨平台开发**:一套代码多端运行的需求 85 | - **AI和机器学习**:TensorFlow.js等AI库的兴起 86 | 87 | ## 壹.1.2.6 结语 88 | 89 | JavaScript的未来是光明的。作为Web的核心语言,它将继续在Web开发、移动开发、桌面应用、服务器端开发等多个领域发挥重要作用。随着新特性的不断加入和生态系统的完善,JavaScript将变得更加强大和易用。 90 | 91 | 对于前端工程师来说,持续关注JavaScript的发展动态,学习新特性,掌握相关工具,是保持竞争力的重要途径。同时,也要理性看待新技术的出现,选择适合项目需求的技术栈,而不是盲目追求最新最酷的特性。 92 | 93 | -------------------------------------------------------------------------------- /2/2.1.4.md: -------------------------------------------------------------------------------- 1 | # 贰.1.4 二叉树的遍历 2 | 3 | 对于给定的二叉树如图: 4 | 5 | 6 | 7 | ![](http://img.blog.csdn.net/20150204101904649?%3C/p%3E%3Cp%3Ewatermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvTXlfSm9icw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 8 | 9 | ## 贰.1.4.1 前序遍历 10 | 11 | ### 定义 12 | 13 | 前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。 14 | 15 | 文首的图,前序遍历的结果是:12457836 16 | 17 | ### 题目 18 | 19 | 给定一个二叉树,返回它的 前序 遍历。示例: 20 | 21 | 输入: \[1,null,2,3\]的树 22 | `1 23 | \ 24 | 2 25 | / 26 | 3` 27 | 输出: \[1,2,3\] 28 | 29 | ### 代码 30 | 31 | 递归解法: 32 | 33 | ```javascript 34 | /** 35 | * @param {TreeNode} root 36 | * @return {number[]} 37 | */ 38 | var preorderTraversal = function(root) { 39 | let result=[]; 40 | (function func(root){ 41 | if(root) 42 | result.push(root.val); 43 | if(root&&root.left){ 44 | func(root.left);//递归 45 | } 46 | if(root&&root.right){ 47 | func(root.right);//递归 48 | } 49 | })(root); 50 | return result; 51 | }; 52 | ``` 53 | 54 | 迭代解法: 55 | 56 | ```javascript 57 | /** 58 | * @param {TreeNode} root 59 | * @return {arr[]} 60 | */ 61 | var preorderTraversal = function (root) { 62 | let stack = [root]; 63 | let arr = []; 64 | while (stack.length > 0) {//循环迭代 65 | let node = stack.pop(); 66 | node && arr.push(node.val); // node不为空时,向arr中推入节点值 67 | node && node.right && stack.push(node.right); //关键点:模拟栈,后入先出,故先压右节点 68 | node && node.left && stack.push(node.left); // 关键点:后入先出,后压左节点 69 | } 70 | return arr 71 | }; 72 | ``` 73 | 74 | ## 贰.1.4.2 中序遍历 75 | 76 | ### 定义 77 | 78 | 中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。 79 | 80 | 文首的图,中序遍历的结果是:42758136 81 | 82 | ### 题目 83 | 84 | //略 85 | 86 | ## 贰.1.4.3 后序遍历 87 | 88 | ### 定义 89 | 90 | 后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。 91 | 92 | 文首的图,后序遍历的结果是:47852631 93 | 94 | ### 题目 95 | 96 | //略 97 | 98 | ## 贰.1.4.4 层序遍历 99 | 100 | 文首的图,层序遍历的结果是:12345678 101 | 102 | ### 题目 103 | 104 | 文首的二叉树,请你返回其按 **层序遍历** 得到的节点值(即逐层地,从左到右访问所有节点)。 105 | 返回其层次遍历结果数组: 106 | `[[1],[2,3],[4,5,6],[7,8]]` 107 | 108 | ### 思路 109 | 110 | 利用队列这个数据结构,将每层的节点放进队列,遍历完当前层的节点之后,再将子节点放到队列进行遍历,直到遍历完所有叶节点。 111 | 112 | ### 代码 113 | 114 | ```javascript 115 | //用一个多维数组表示树 116 | let tree=[[1,]]; 117 | 118 | ((T)=>{ 119 | let queue=[]; 120 | //todo 121 | 122 | })(tree); 123 | ``` 124 | 125 | ## 贰.1.4.5 深度优先与广度优先遍历 126 | 127 | 其实深度优先遍历也就是前序遍历,广度优先遍历就是层序遍历。 128 | 129 | -------------------------------------------------------------------------------- /5/5.1.6.md: -------------------------------------------------------------------------------- 1 | # 伍.1.6 用JavaScript实现接口 2 | 3 | JavaScript并不支持接口(interface),然而在构建大型框架/库的时候,我们很需要想办法实现接口的特性: 4 | 5 | * 实现接口的类必须实现了接口中的方法; 6 | * 有办法检测到该类的实例的类型,并且这个类型和接口(约定的)类型值相等。 7 | 8 | ## 01.TypeScript的实现 9 | 10 | 由于JavaScript没有接口,因此,我将向你展示TypeScript如何实现此目的,因为TypeScript补全了JavaScript面向对象编程的能力,以和JavaScript面向原型的一个分支领域区别开来。 11 | 12 | ```javascript 13 | interface ShapeInterface { 14 | area(): number 15 | } 16 | class Circle implements ShapeInterface { 17 | let radius: number = 0 18 | constructor (r: number) { 19 | this.radius = r 20 | } 21 | 22 | public area(): number { 23 | return MATH.PI * MATH.pow(this.radius, 2) 24 | } 25 | } 26 | ``` 27 | 28 | 在上面的示例中,展示了如何在TypeScript中实现此目标,但是TypeScript在后台将代码编译为纯JavaScript,而在已编译的代码中,由于JavaScript没有接口,因此也竟然没有给出接口有关的表现形式。真是扫兴啊! 29 | 30 | ## 02.JavaScript的实现 31 | 32 | 那么在JavaScript缺乏接口的情况下,我们如何实现这一目标呢? 33 | 34 | 通过**函数组合**可以实现。 35 | 36 | 首先,我们创建`shapeInterface`工厂函数,因为我们在谈论接口,所以我们的`shapeInterface`将使用函数组合出像接口一样抽象体。 37 | 38 | **什么是工厂函数?** 39 | 40 | > 在JavaScript中,函数若返回一个对象,且被返回的对象既不是构造函数、也不是某一个类,那么这该函数被称作工厂函数。 41 | 42 | ```javascript 43 | const shapeInterface = (state) => ({ 44 | type: 'shapeInterface', 45 | area(){ 46 | if(state.area && typeof state.area === "function") 47 | return state.area(state); 48 | else 49 | throw new Error("必须实现接口shapeInterface的方法area"); 50 | } 51 | }) 52 | ``` 53 | 54 | 然后我们让正方形(square)工厂函数实现上面的接口。 55 | 56 | ```javascript 57 | const square = (length) => { 58 | const proto = { 59 | length, 60 | type : 'Square', 61 | area : (args) => Math.pow(args.length, 2) 62 | } 63 | const basics = shapeInterface(proto) 64 | const composite = Object.assign({}, basics) 65 | return Object.assign(Object.create(composite), {length}) 66 | } 67 | ``` 68 | 69 | 接着调用工厂函数`square`后,结果应该是下面这样的: 70 | 71 | ```javascript 72 | const s = square(5) 73 | console.log('OBJ\n', s) 74 | console.log('PROTO\n', Object.getPrototypeOf(s)) 75 | s.area() 76 | // 输出 77 | //>> OBJ 78 | //>> { length: 5 } 79 | //>> PROTO 80 | //>> { type: 'shapeInterface', area: [Function: area] } 81 | //>> 25 82 | ``` 83 | 84 | 如此一来,我们可试着做一个产品经理要求的功能:计算面积之和的。其间可以检测具体的对象否有没有实现接口: 85 | 86 | ```javascript 87 | //计算面积之和 88 | sum() { 89 | const area = [] 90 | for (shape of this.shapes) { 91 | //用Object.getPrototypeOf来检测类型 92 | if (Object.getPrototypeOf(shape).type === 'shapeInterface') { 93 | area.push(shape.area()) 94 | } else { 95 | throw new Error('该对象不是接口shapeInterface的实例') 96 | } 97 | } 98 | return area.reduce((v, c) => c += v, 0) 99 | } 100 | ``` 101 | 102 | ## 03.结语 103 | 104 | 最后再次说一下,由于JavaScript不支持强类型语言之类的接口,上面的示例演示了我们如何模拟它,并不能十分完美的实现接口的各种特性。这也许是语言本身的局限,也有待你我探索。 105 | 106 | -------------------------------------------------------------------------------- /7/7.1.3.md: -------------------------------------------------------------------------------- 1 | # 柒.1.3 代理模式 2 | 3 | ## 柒.1.3.1 什么是代理模式 4 | 5 | ​ 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。 6 | 7 | ​ 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。 8 | 9 | ## 柒.1.3.2 使用实例 10 | 11 | ### 柒.1.3.2.1 简单实例 12 | 13 | ​ 我们来举一个简单的例子,假如小明要送喜欢的小姐姐玫瑰花,却脸皮薄不好意思,想委托老王去送这些玫瑰,那老王就是个代理,那我们如何来做呢? 14 | 15 | ```js 16 | // 先声明小姐姐对象 17 | var girl = function (name) { 18 | this.name = name; 19 | }; 20 | 21 | // 这是小明 22 | var xiaoMing = function (girl) { 23 | this.girl = girl; 24 | this.sendGift = function (gift) { 25 | alert("Hi " + girl.name + ", 小明送你一个礼物:" + gift); 26 | } 27 | }; 28 | 29 | // 老王是代理 30 | var proxyTom = function (girl) { 31 | this.girl = girl; 32 | this.sendGift = function (gift) { 33 | (new xiaoMing(girl)).sendGift(gift); // 替小明送花咯 34 | } 35 | }; 36 | ``` 37 | 38 | 调用方式: 39 | 40 | ```js 41 | var proxy = new proxyTom(new girl("小姐姐")); 42 | proxy.sendGift("999朵玫瑰"); 43 | ``` 44 | 45 | ### 柒.1.3.2.2 虚拟代理实现图片预加载 46 | 47 | ​ 在 Web 开发中,图片预加载是一种常用的技术,如果直接给某个 img 标签节点设置 src 属性, 由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这种 场景就很适合使用虚拟代理。 48 | 49 | ​ 下面我们来实现这个虚拟代理,首先创建一个普通的本体对象,这个对象负责往页面中创建 一个 img 标签,并且提供一个对外的 setSrc 接口,外界调用这个接口,便可以给该 img 标签设置 50 | src 属性: 51 | 52 | ```js 53 | var myImage = (function(){ 54 | var imgNode = document.createElement( 'img' ); 55 | document.body.appendChild( imgNode ); 56 | return { 57 | setSrc: function( src ) { 58 | imgNode.src = src; 59 | } 60 | } 61 | })(); 62 | ``` 63 | 64 | 我们把网速调至 5KB/s,然后通过 MyImage.setSrc 给该 img 节点设置 src,可以看到,在图片 65 | 66 | 被加载好之前,页面中有一段长长的空白时间。 67 | 68 | 现在开始引入代理对象 proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中 69 | 将出现一张占位的菊花图 loading.gif, 来提示用户图片正在加载。代码如下: 70 | 71 | ```js 72 | var myImage = (function(){ 73 | var imgNode = document.createElement( 'img' ); 74 | document.body.appendChild( imgNode ); 75 | return { 76 | setSrc: function( src ) { 77 | imgNode.src = src; 78 | } 79 | } 80 | })(); 81 | 82 | var proxyImage = (function() { 83 | var img = new Image; 84 | img.onload = function() { 85 | myImage.setSrc( this.src ); 86 | } 87 | return { 88 | setSrc: function( src ) { 89 | myImage.setSrc( './pic.jpg' ); 90 | img.src = src; 91 | } 92 | } 93 | })(); 94 | 95 | proxyImage.setSrc( './loading.gif' ); 96 | ``` 97 | 98 | ## 柒.1.3.3 总结 99 | 100 | ​ 在 JavaScript 开发中最常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。 当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。 101 | 102 | 103 | 104 | > 参考引用资料 105 | > 106 | > 《JavaScript设计模式与开发实践》 107 | > 108 | > [汤姆大叔的博客——深入理解JavaScript系列](https://link.jianshu.com/?t=http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html) -------------------------------------------------------------------------------- /1/1.4.5.md: -------------------------------------------------------------------------------- 1 | # 壹.4.5 V8引擎内存管理和垃圾回收机制 2 | 3 | ## v8引擎内存结构 4 | 5 | - 内存分配:栈空间,堆空间 6 | - 栈空间:代码运行的环境(逻辑运行的环境) 7 | - 堆空间:所有函数和引用类型数据存放的具体地址 8 | 9 | ## 栈 10 | 11 | 栈是临时存储空间,主要存储局部变量和函数调用。 12 | 13 | 基本类型数据(Number, Boolean, String, Null, Undefined, Symbol, BigInt)保存在在栈内存中。 引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。 14 | 15 | > 基本类型赋值,系统会为新的变量在栈内存中分配一个新值,这个很好理解。引用类型赋值,系统会为新的变量在栈内存中分配一个值,这个值仅仅是指向同一个对象的引用,和原对象指向的都是堆内存中的同一个对象。 16 | 17 | 对于函数,解释器创建了”调用栈“来记录函数的调用过程。每调用一个函数,解释器就可以把该函数添加进调用栈,解释器会为被添加进来的函数创建一个栈帧(用来保存函数的局部变量以及执行语句)并立即执行。如果正在执行的函数还调用了其他函数,新函数会继续被添加进入调用栈。函数执行完成,对应的栈帧立即被销毁。 18 | 19 | 两种查看调用栈的方法 20 | 21 | 1. 使用 [console.trace()](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FConsole%2Ftrace) 向Web控制台输出一个堆栈跟踪. 22 | 2. 浏览器开发者工具进行断点调试 23 | 24 | 栈虽然很轻量,在使用时创建,使用结束后销毁,但是不是可以无限增长的,被分配的调用栈空间被占满时,就会引起”栈溢出“的错误。 25 | 26 | ```js 27 | (function foo() { 28 | foo() 29 | })() 30 | ``` 31 | 32 | ![](https://img.wenhairu.com/images/2022/06/12/7JPLh.png) 33 | 34 | 为什么基本数据类型存储在栈中,引用数据类型存储在堆中? 35 | 36 | JavaScript引擎需要用栈来维护程序执行期间的上下文的状态,如果栈空间大了的话,所有数据都存放在栈空间里面,会影响到上下文切换的效率,进而影响整个程序的执行效率。 37 | 38 | ## 堆 39 | 40 | 堆空间存储的数据比较复杂,大致可以划分为下面 5 个区域:代码区(Code Space)、Map 区(Map Space)、大对象区(Large Object Space)、新生代(New Space)、老生代(Old Space)。本篇文章主要讨论新生代和老生代的内存回收算法。 41 | 42 | 新生代内存是临时分配的内存,存活时间段,老生代内存是常驻内存,存活时间长。 43 | 44 | ![](https://img.wenhairu.com/images/2022/06/12/7J0DP.png) 45 | 46 | ### 新生代内存回收 47 | 48 | 新生代中用 **Scavenge 算法**来处理。所谓 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是对象区域(from),一半是空闲区域 (to)。 49 | 50 | 新的对象会首先被分配到 from 空间,当进行垃圾回收的时候,会先将 from 空间中的 存活的对象复制到 to 空间进行保存,对未存活的对象的空间进行回收。 复制完成后, from 空间和 to 空间进行调换,to 空间会变成新的 from 空间,原来的 from 空间则变成 to 空间。这种算法称之为 ”Scavenge“。 51 | 52 | ![](https://img.wenhairu.com/images/2022/06/12/7J4cD.png) 53 | 54 | 新生代内存回收频率很高,速度也很快,但是空间利用率很低,因为有一半的内存空间处于"闲置"状态。 55 | 56 | ### 老生代内存回收 57 | 58 | 新生代中多次进行回收仍然存活的对象会被转移到空间较大的老生代内存中,这种现象称为**晋升**。以下两种情况 59 | 60 | 1. 在垃圾回收过程中,发现某个对象之前被清理过,那么将会晋升到老生代的内存空间中 61 | 2. 在 from 空间和 to 空间进行反转的过程中,如果 to 空间中的使用量已经超过了 25% ,那么就讲 from 中的对象直接晋升到老生代内存空间中。 62 | 63 | 因为老生代空间较大,如果仍然用 Scavenge 算法来频繁复制对象,那么性能开销就太大了。 64 | 65 | #### 标记-清除(Mark-Sweep) 66 | 67 | 老生代采用的是”标记清除“来回收未存活的对象。 68 | 69 | 分为标记和清除两个阶段。标记阶段会遍历堆中所有的对象,并对存活的对象进行标记,清除阶段则是对未标记的对象进行清除。 70 | 71 | ![](https://img.wenhairu.com/images/2022/06/12/7JI2t.png) 72 | 73 | #### 标记-整理(Mark-Compact) 74 | 75 | 标记清除不会对内存一分为二,所以不会浪费空间。但是经过标记清除之后的内存空间会生产很多不连续的碎片空间,这种不连续的碎片空间中,在遇到较大的对象时可能会由于空间不足而导致无法存储。 为了解决内存碎片的问题,需要使用另外一种算法 - **标记-整理(Mark-Compact)**。标记整理对待未存活对象不是立即回收,而是将存活对象移动到一边,然后直接清掉端边界以外的内存。 76 | 77 | ![](https://img.wenhairu.com/images/2022/06/12/7JTfS.png) 78 | 79 | #### 增量标记 80 | 81 | 为了避免出现JavaScript应用程序与垃圾回收器看到的不一致的情况,进行垃圾回收的时候,都需要将正在运行的程序停下来,等待垃圾回收执行完成之后再回复程序的执行,这种现象称为“全停顿”。如果需要回收的数据过多,那么全停顿的时候就会比较长,会影响其他程序的正常执行。 82 | 83 | ![](https://img.wenhairu.com/images/2022/06/12/7JVQC.png) 84 | 85 | 为了避免垃圾回收时间过长影响其他程序的执行,V8将标记过程分成一个个小的子标记过程,同时让垃圾回收和JavaScript应用逻辑代码交替执行,直到标记阶段完成。我们称这个过程为**增量标记**算法。 86 | 87 | ![](https://img.wenhairu.com/images/2022/06/12/7JXes.png) 88 | 89 | 通俗理解,就是把垃圾回收这个大的任务分成一个个小任务,穿插在 JavaScript任务中间执行,这个过程其实跟 React Fiber 的设计思路类似。 90 | 91 | 92 | 93 | ## 参考 94 | 95 | - [V8 引擎垃圾内存回收原理解析](https://juejin.im/post/6844903993420840967) 96 | - [JavaScript中V8引擎内存问题](https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2Fcangqinglang%2Fp%2F12668374.html) 97 | - [浅谈V8引擎中的垃圾回收机制](https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000000440270) 98 | 99 | -------------------------------------------------------------------------------- /6/6.1.2.md: -------------------------------------------------------------------------------- 1 | # 陆.1.2 接口隔离原则 2 | 3 | ## 01.什么是接口隔离原则 4 | 5 | 接口隔离原则,英文缩写**ISP**,全称 **Interface Segregation Principle**。 6 | 7 | 定义一: 8 | 9 | > Clients should not be forced to depend upon interfaces that they don't use. 10 | > 不应该强行要求客户端依赖于它们不用的接口。 11 | 12 | 定义二: 13 | 14 | > The dependency of one class to another one should depend on the smallest possible interface. 15 | > 类之间的依赖应该建立在最小的接口上面。 16 | 17 | 简单点说,客户端需要什么功能,就提供什么接口,对于客户端不需要的接口不应该强行要求其实现;类之间的依赖应该建立在最小的接口上面,这里最小的粒度取决于单一职责原则的划分。 18 | 19 | ## 02.举例说明 20 | 21 | 假设我们有一堆几何体(shape),除了计算它们的面积,我们还想计算出它们的体积。我们可以这样用JavaScript模拟做一个interface(接口): 22 | 23 | ```javascript 24 | //state为几何体的状态参数集合,是一个json对象 25 | const shapeInterface = (state) => ({ 26 | type: 'shapeInterface',//接口名 27 | area: () => state.area(state),//计算面积 28 | volume: () => state.volume(state)//计算体积 29 | }) 30 | ``` 31 | 32 | by the way,用JavaScript简单模拟面向对象语言中的interface特性,可以参阅本书 [用JavaScript实现接口](../5/5.1.6.md)。 33 | 34 | 如果按上面的代码实现接口,那么,我们创建的任何shape都必须实现计算体积的方法。但是我们知道正方形是平面的,并且平面的shape没有体积,因此此接口将强制正方形的构造函数实现计算体积的方法。 35 | 36 | 接口隔离原则对此表示反对。怎么办呢?你可以创建另一个称为`solidShapeInterface`的接口,该接口具有计算体积的方法,诸如立方体等立体shape都可以实现此接口: 37 | 38 | ```javascript 39 | //定义计算面积的接口 40 | const shapeInterface = (state) => ({ 41 | type: 'shapeInterface', 42 | area: () => state.area(state) 43 | }) 44 | //定义计算体积的接口 45 | const solidShapeInterface = (state) => ({ 46 | type: 'solidShapeInterface', 47 | volume: () => state.volume(state) 48 | }) 49 | 50 | //定义一个立方体 51 | const cubo = (length) => { 52 | //state 53 | const proto = { 54 | //属性 55 | type : 'Cubo', 56 | length, 57 | //方法 58 | area : (args) => Math.pow(args.length, 2) * 6, 59 | volume : (args) => Math.pow(args.length, 3) 60 | } 61 | 62 | const basics = shapeInterface(proto) //实现计算面积的接口 63 | const complex = solidShapeInterface(proto)//实现计算体积的接口 64 | //利用Object.assign组合出一个新的类,这个类实现了上面2种接口 65 | const composite = Object.assign({}, basics, complex) 66 | //最后传入参数,返回新的类的实例 67 | return Object.assign(Object.create(composite), {length}) 68 | } 69 | ``` 70 | 71 | 上面代码是一种相对较好的方法,能够计算面积,也能计算体积。但是要注意的是,如果想计算shape的面积与体积之和,而不是依靠改动已有`shapeInterface`或`solidShapeInterface`这两个接口,那又该怎么实现呢? 72 | 73 | 你可以创建另一个接口,名字可以是`manageShapeInterface`,并在平面和立体的shape上都实现它,这种方式的代码如下: 74 | 75 | ```javascript 76 | const manageShapeInterface = (fn) => ({ 77 | type: 'manageShapeInterface', 78 | calculate: () => fn() 79 | }) 80 | //平面圆 81 | const circle = (radius) => { 82 | const proto = { 83 | radius, 84 | type: 'Circle', 85 | area: (args) => Math.PI * Math.pow(args.radius, 2) 86 | } 87 | const basics = shapeInterface(proto) 88 | const abstraccion = manageShapeInterface(() => basics.area()) 89 | const composite = Object.assign({}, basics, abstraccion) 90 | return Object.assign(Object.create(composite), {radius}) 91 | } 92 | //立方体 93 | const cubo = (length) => { 94 | const proto = { 95 | length, 96 | type : 'Cubo', 97 | area : (args) => Math.pow(args.length, 2) * 6, 98 | volume : (args) => Math.pow(args.length, 3) 99 | } 100 | const basics = shapeInterface(proto) 101 | const complex = solidShapeInterface(proto) 102 | //关键语句,传入一个函数,让该函数实现具体的逻辑 103 | const abstraccion = manageShapeInterface( 104 | () => basics.area() + complex.volume() 105 | ) 106 | const composite = Object.assign({}, basics, abstraccion) 107 | return Object.assign(Object.create(composite), {length}) 108 | } 109 | ``` 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /1/1.2.11.md: -------------------------------------------------------------------------------- 1 | # 壹.2.11 位操作符与进制转换 2 | 3 | 为什么单独讲“位操作符”呢?因为我发现日常工作中,前端工程师朋友们经常使用**算术操作符、一元操作符、关系操作符、相等操作符**,就是很少有用到**位操作符**(\|,&,~,^,<<,>>)。但其实位操作符用好了又特别省事,一般的老手都会用它来实现一些特定功能,因此也会被经常问到来侧面考察一个前端工程师是否老到。所以,这里单独用一篇文章来系统性讲解位操作符。 4 | 5 | ## 基础讲解 6 | 7 | ### 按位与(&) 8 | 9 | 两个操作数中相对应的位都是1时,这位为1,否则都为零 10 | 11 | ```javascript 12 | var a=3//011 13 | var b=7//111 14 | console.log(a&b)// 输出3 => 011 15 | ``` 16 | 17 | ### 按位或(\|) 18 | 19 | 如果两个数对应的有一个为1时,这位就为1 20 | 21 | ```javascript 22 | var a=3//011 23 | var b=7//111 24 | console.log(a|b)// 输出7 => 111 25 | ``` 26 | 27 | ### 按位异或(^) 28 | 29 | 两个数对应位不同,这位才为1,否则为0 30 | 31 | ```javascript 32 | var a=3//011 33 | var b=7//111 34 | console.log(a^b)// 输出4 => 100 35 | ``` 36 | 37 | ### 按位非\(~\) 38 | 39 | 一元操作符。将操作数所有位取反。而根据js中带符号的整数的表示方法,对一个值使用~运算符相当于它的相反数减1。 40 | 41 | ```javascript 42 | var b=-7 43 | console.log(~b)//输出6 44 | ``` 45 | 46 | ### 左移(<<) 47 | 48 | 将第一个操作数进行左移,移动的位数为第二个操作数\(0~31之间的一个整数\),新的位由0补齐。 49 | 50 | ```javascript 51 | var a=3 52 | var b=7//000111 53 | console.log(b< 输出56 54 | ``` 55 | 56 | ### 有符号右移(>>) 57 | 58 | 将第一个操作数进行右移,移动的位数为第二个操作数\(0~31之间的一个整数\),右边的溢出位被忽略,填补在左边的位由原操作数符号决定,以便保持结果的符号与原操作数一致。如果第一个操作数是正数,移位后用0填补最高位;如果为负,用1填补最高位。 59 | 60 | ```javascript 61 | var a=2 62 | var b=-7//1 111111001 63 | console.log(b>>a)//1 11111110 => -2 64 | ``` 65 | 66 | ### 无符号右移 67 | 68 | 同样右移,最高位补0,忽略符号位 69 | 70 | ```javascript 71 | var a=4 72 | var b=-1// 1 11111111 73 | console.log(b>>>a)//0 0000 1111 1111 1111 1111 1111 1111 1111 => 输出268435455 74 | ``` 75 | 76 | ## 进制转换 77 | 78 | 位操作涉及与二进制的转换,这里顺带把JavaScript的进制转换也讲一下。 79 | 80 | ### **1.十进制转其他** 81 | 82 | 十进制转其他相对简单,直接使用 toString\(\) 方法。 83 | 84 | ```javascript 85 | //十进制转其他 86 | var x=110; 87 | alert(x); 88 | alert(x.toString(2));//转二进制 89 | alert(x.toString(8));//转八进制 90 | alert(x.toString(16));//转十六进制 91 | ``` 92 | 93 | ### **2.其他转十进制** 94 | 95 | 其他进制转十进制也比较简单,使用parseInt,传递第二个参数代表当前数字的进制。 96 | 97 | ```javascript 98 | //其他转十进制 99 | var x='110'; 100 | alert(parseInt(x,2)); //二进制转十进制 101 | alert(parseInt(x,8)); //八进制转十进制 102 | alert(parseInt(x,16));//十六进制转十进制 103 | ``` 104 | 105 | ### **3.其他进制转其他进制** 106 | 107 | 这里需要转换两次,首先使用parseInt转换到十进制,然后使用toString转换到目标进制。 108 | 109 | ```javascript 110 | //其他转其他 111 | //先用parseInt转成十进制再用toString转到目标进制 112 | alert(String.fromCharCode(parseInt(141,8))) 113 | alert(parseInt('ff',16).toString(2)); 114 | ``` 115 | 116 | 另外,可以参考这篇文章,[用笔和纸手算二进制与十进制转换](https://www.cnblogs.com/web-record/p/11132861.html)。 117 | 118 | ## 面试题 119 | 120 | ### 华为面试题:判断两个IP是否在同一子网 121 | 122 | > 子网掩码是用来判断任意两台计算机的IP地址是否属于同一子网络的根据。 123 | > 子网掩码与IP地址结构相同,是32位二进制数,其中网络号部分全为“1”和主机号部分全为“0”。利用子网掩码可以判断两台主机是否中同一子网中。若两台主机的IP地址分别与它们的子网掩码相“与”后的结果相同,则说明这两台主机在同一子网中。 124 | > 125 | > #### 示例: 126 | > 127 | > **I P 地址 192.168.0.1 128 | > 子网掩码 255.255.255.0** 129 | > 130 | > 转化为二进制进行运算: 131 | > 132 | > I P 地址 11010000.10101000.00000000.00000001 133 | > 子网掩码 11111111.11111111.11111111.00000000 134 | > 135 | > AND运算 11000000.10101000.00000000.00000000 136 | > 137 | > 转化为十进制后为: 192.168.0.0 138 | > 139 | > **I P 地址 192.168.0.254 140 | > 子网掩码 255.255.255.0** 141 | > 142 | > 转化为二进制进行运算: 143 | > 144 | > I P 地址 11010000.10101000.00000000.11111110 145 | > 子网掩码 11111111.11111111.11111111.00000000 146 | > 147 | > AND运算 _\*\*_11000000.10101000.00000000.00000000 148 | > 149 | > 转化为十进制后为: 192.168.0.0 150 | > 151 | > 通过以上对两台计算机IP地址与子网掩码的AND运算后,我们可以看到它运算结果是一样的。均为192.168.0.0,所以这二台计算机可视为是同一子网络。 152 | 153 | ```javascript 154 | //todo 155 | ``` 156 | 157 | -------------------------------------------------------------------------------- /5/5.2.2.md: -------------------------------------------------------------------------------- 1 | # 伍.2.2 JavaScript的函数式编程探索 2 | 3 | [上一篇](5.2.1.md)讲了函数式编程的理论知识,本篇探索一下JavaScript如何进行函数式编程。 4 | 5 | ## 01.Ramda,一款实用的 JavaScript 函数式编程库 6 | 7 | 工欲善其事必先利其器,JavaScript函数式编程怎能没有库这种利器! 8 | 9 | [Ramda](https://ramda.cn/)是一个非常适合函数式编程的函数库。函数库类似的有[lodash](https://www.lodashjs.com/)。 10 | 11 | lodash已经很成熟了,提供了足够好用的函数,那为什么还要Ramda呢?笔者认为:Ramda的理念更适合函数式编程。 12 | 13 | Ramda的理念是: 14 | 15 | > Function first,data last. 16 | 17 | 也即函数优先,数据靠后。体现在具体的用法区别上,就是lodash会把第一个参数当成要处理的数据,函数等放在第一个参数之后传进来。而Ramda会把要出来的数据放在最后一个参数,最后一个参数之前的参数,都是传入的函数。 18 | 19 | ```javascript 20 | const list = [{a: 1}, {a: 2}, {a: 3}]; 21 | 22 | //lodash:数据在前 23 | _.findIndex(list, (o)=> o.a== 2); //>> 0 24 | 25 | 26 | //Ramda:数据在最末尾 27 | R.findIndex(R.propEq('a', 2),list); //>> 1 28 | ``` 29 | 30 | ### 将数据放在后面有什么好处呢? 31 | 32 | 好处是写的代码更精简,更方便阅读理解。这点结合Ramda的柯里化会感受更加明显。 33 | 34 | ## 02.Ramda的函数都是柯里化的 35 | 36 | 上面的代码第8行,可以写成: 37 | 38 | ```javascript 39 | R.findIndex(R.propEq('a', 2))(list); //>> 1 40 | ``` 41 | 42 | 也是可以的,这不就是柯里化形态吗。没错,而Ramda提供的所有函数都是已经柯里化的,这可以从源代码找到证据。看看最简单的[add函数](https://github.com/ramda/ramda/blob/master/source/add.js)的源代码: 43 | 44 | ```javascript 45 | import _curry2 from './internal/_curry2'; 46 | 47 | 48 | /** 49 | * Adds two values. 50 | * 51 | * @func 52 | * @memberOf R 53 | * @since v0.1.0 54 | * @category Math 55 | * @sig Number -> Number -> Number 56 | * @param {Number} a 57 | * @param {Number} b 58 | * @return {Number} 59 | * @see R.subtract 60 | * @example 61 | * 62 | * R.add(2, 3); //=> 5 63 | * R.add(7)(10); //=> 17 64 | */ 65 | var add = _curry2(function add(a, b) { 66 | return Number(a) + Number(b); 67 | }); 68 | export default add; 69 | ``` 70 | 71 | 上面代码第1行就引入了`_curry2`这个内部函数,然后第21行调用`_curry2`将`add`函数柯里化。Ramda每个对外的函数,都这样处理过。因此Ramda对外提供的每个函数都是柯里化过的。 72 | 73 | ### 全部柯里化的目的是什么呢? 74 | 75 | 仍然是为了与前面讲述的“将数据放在末尾”结合使用,以便体现代码的简洁、可读性。 76 | 77 | 现在举例说明:求列表中a的值大于1的项,然后取各项值之和。 78 | 79 | ```javascript 80 | const list = [{ a: 1 }, { a: 2 }, { a: 3 }]; 81 | 82 | //lodash 83 | { 84 | //根据给定的列表求和 85 | let sum = data => _.reduce(data, (a, b) => a + b.a,0); 86 | //得到值>1的项组成的列表 87 | let getList = data => _.filter(data, (o) => o.a > 1); 88 | let total = _.flow(getList, sum)(list); 89 | console.log(total);//>> 5 90 | } 91 | 92 | 93 | //Ramda 94 | { 95 | let sum = data => R.reduce((a,b)=>a+b.a,0,data); 96 | let getList = data => R.filter((o) => o.a > 1,data); 97 | let total = R.compose(sum,getList)(list); 98 | console.log(total);//>> 5 99 | } 100 | ``` 101 | 102 | 这样可能还看不出明显差别,别急,因为Ramda提供的函数都是柯里化过的,柯里化可以将多参数函数拆分成单参数函数,于是可以改成如下: 103 | 104 | ```javascript 105 | let sum = data => R.reduce((a,b)=>a+b.a,0)(data); 106 | let getList = data => R.filter((o) => o.a > 1)(data); 107 | let total = R.compose(sum,getList)(list); 108 | console.log(total);//>> 5 109 | ``` 110 | 111 | 基本可以发现一个Ramda代码的特征:第1、2行代码,作为数据的参数`data`固定地出现在参数和最末尾位置。既然第1,2行代码都有传数据参数`data`,而第3行代码传数据参数`list`,重复这么多次没有必要,同样是将数据以参数传递,笔者直觉这种情况出现一次就够了(理论上,**代码里有重复的地方都可以优化,出现一次就够**)。 112 | 113 | 那第1、2行多余的参数传递可以优化掉!因为末尾都是数据,那可以十分方便地优化掉(拿走就行了,比将数据放在最开头的参数位置而言,要方便得多)。再因为Ramda提供的函数都是柯里化过的,柯里化可以[延迟运行](../1/1.3.2.md#2-yan-chi-yun-hang),也即可以暂时不用传数据进来,在真正需要运行函数的时候传数据进来就可以了。所以,代码优化如下: 114 | 115 | ```javascript 116 | let sum = R.reduce((a,b)=>a+b.a,0);//优化掉data 117 | let getList = R.filter((o) => o.a > 1);//优化掉data 118 | let total = R.compose(sum,getList)(list);//最后再统一传参data 119 | console.log(total);//>> 5 120 | ``` 121 | 122 | 哇!上面代码中第1、2行原本需要传递的参数`data`不见了,效果却一样!居然可以没`data`什么事?!的确可以!很清爽的代码。至此,就引出了一种无参数的编程风格:**Pointfree**,放在[下一篇](5.2.3.md)单独介绍。 123 | 124 | 可以发现正是因为Ramda把数据放在参数的最后一个位置,同时每个函数都柯里化过,因此能够省略一些参数,代码才变得更简洁,更易读。 125 | 126 | ## 参考文献 127 | 128 | {% hint style="info" %} 129 | [The Philosophy of Ramda](https://fr.umio.us/the-philosophy-of-ramda/#header) 130 | {% endhint %} 131 | 132 | -------------------------------------------------------------------------------- /6/6.3.2.md: -------------------------------------------------------------------------------- 1 | # 陆.3.2 JavaScript的函数式编程探索 2 | 3 | [上一篇](6.3.1.md)讲了函数式编程的理论知识,本篇探索一下JavaScript如何进行函数式编程。 4 | 5 | ## Ramda,一款实用的 JavaScript 函数式编程库 6 | 7 | 工欲善其事必先利其器,JavaScript函数式编程怎能没有库这种利器! 8 | 9 | [Ramda](https://ramda.cn/)是一个非常适合函数式编程的函数库。函数库类似的有[lodash](https://www.lodashjs.com/)。 10 | 11 | lodash已经很成熟了,提供了足够好用的函数,那为什么还要Ramda呢?笔者认为:Ramda的理念更适合函数式编程。 12 | 13 | Ramda的理念是: 14 | 15 | > Function first,data last. 16 | 17 | 也即函数优先,数据靠后。体现在具体的用法区别上,就是lodash会把第一个参数当成要处理的数据,函数等放在第一个参数之后传进来。而Ramda会把要出来的数据放在最后一个参数,最后一个参数之前的参数,都是传入的函数。 18 | 19 | ```javascript 20 | const list = [{a: 1}, {a: 2}, {a: 3}]; 21 | 22 | //lodash:数据在前 23 | _.findIndex(list, (o)=> o.a== 2); //>> 0 24 | 25 | 26 | //Ramda:数据在最末尾 27 | R.findIndex(R.propEq('a', 2),list); //>> 1 28 | ``` 29 | 30 | ### 将数据放在后面有什么好处呢? 31 | 32 | 好处是写的代码更精简,更方便阅读理解。这点结合Ramda的柯里化会感受更加明显。 33 | 34 | ## Ramda的函数都是柯里化的 35 | 36 | 上面的代码第8行,可以写成: 37 | 38 | ```javascript 39 | R.findIndex(R.propEq('a', 2))(list); //>> 1 40 | ``` 41 | 42 | 也是可以的,这不就是柯里化形态吗。没错,而Ramda提供的所有函数都是已经柯里化的,这可以从源代码找到证据。看看最简单的[add函数](https://github.com/ramda/ramda/blob/master/source/add.js)的源代码: 43 | 44 | ```javascript 45 | import _curry2 from './internal/_curry2'; 46 | 47 | 48 | /** 49 | * Adds two values. 50 | * 51 | * @func 52 | * @memberOf R 53 | * @since v0.1.0 54 | * @category Math 55 | * @sig Number -> Number -> Number 56 | * @param {Number} a 57 | * @param {Number} b 58 | * @return {Number} 59 | * @see R.subtract 60 | * @example 61 | * 62 | * R.add(2, 3); //=> 5 63 | * R.add(7)(10); //=> 17 64 | */ 65 | var add = _curry2(function add(a, b) { 66 | return Number(a) + Number(b); 67 | }); 68 | export default add; 69 | ``` 70 | 71 | 上面代码第1行就引入了`_curry2`这个内部函数,然后第21行调用`_curry2`将`add`函数柯里化。Ramda每个对外的函数,都这样处理过。因此Ramda对外提供的每个函数都是柯里化过的。 72 | 73 | ### 全部柯里化的目的是什么呢? 74 | 75 | 仍然是为了与前面讲述的“将数据放在末尾”结合使用,以便体现代码的简洁、可读性。 76 | 77 | 现在举例说明:求列表中a的值大于1的项,然后取各项值之和。 78 | 79 | ```javascript 80 | const list = [{ a: 1 }, { a: 2 }, { a: 3 }]; 81 | 82 | //lodash 83 | { 84 | //根据给定的列表求和 85 | let sum = data => _.reduce(data, (a, b) => a + b.a,0); 86 | } 87 | //得到值>1的项组成的列表 88 | let getList = data => _.filter(data, (o) => o.a > 1); 89 | } 90 | let total = _.flow(getList, sum)(list); 91 | console.log(total);//>> 5 92 | } 93 | 94 | 95 | //Ramda 96 | { 97 | let sum = data => R.reduce((a,b)=>a+b.a,0,data); 98 | let getList = data => R.filter((o) => o.a > 1,data); 99 | let total = R.compose(sum,getList)(list); 100 | console.log(total);//>> 5 101 | } 102 | ``` 103 | 104 | 这样可能还看不出明显差别,别急,因为Ramda提供的函数都是柯里化过的,柯里化可以将多参数函数拆分成单参数函数,于是可以改成如下: 105 | 106 | ```javascript 107 | let sum = data => R.reduce((a,b)=>a+b.a,0)(data); 108 | let getList = data => R.filter((o) => o.a > 1)(data); 109 | let total = R.compose(sum,getList)(list); 110 | console.log(total);//>> 5 111 | ``` 112 | 113 | 基本可以发现一个Ramda代码的特征:第1、2行代码,作为数据的参数`data`固定地出现在参数和最末尾位置。既然第1,2行代码都有一个传数据参数`data`的函数,而第3行代码也有一个传数据参数`list`的函数,重复这么多相同的函数没必要,笔者直觉这种情况出现一次就够了(理论上,**代码里有重复的地方都可以优化,出现一次就够**)。 114 | 115 | 那第1、2行代码多余的函数可以优化掉!因为末尾都是数据,那可以十分方便地优化掉(拿走就行了,比将数据放在最开头的参数位置而言,要方便得多)。再因为Ramda提供的函数都是柯里化过的,柯里化可以[延迟运行](../1/1.3.2.md#2-yan-chi-yun-hang),也即可以暂时不用传数据进来,在真正需要运行的时候传数据进来就可以了。所以,代码优化如下: 116 | 117 | ```javascript 118 | let sum = R.reduce((a,b)=>a+b.a,0);//优化掉data 119 | let getList = R.filter((o) => o.a > 1);//优化掉data 120 | let total = R.compose(sum,getList)(list);//最后再统一传参data 121 | console.log(total);//>> 5 122 | ``` 123 | 124 | 哇!上面代码中第1、2行原本需要传递的参数`data`不见了,效果却一样!居然可以没`data`什么事?!的确可以!很清爽的代码。至此,就引出了一种无参数的编程风格:**Pointfree**,放在[下一篇](6.3.3.md)单独介绍。 125 | 126 | 可以发现正是因为Ramda把数据放在参数的最后一个位置,同时每个函数都柯里化过,因此能够省略一些参数,代码才变得更简洁,更易读。 127 | 128 | ## 参考文献 129 | 130 | {% hint style="info" %} 131 | [The Philosophy of Ramda](https://fr.umio.us/the-philosophy-of-ramda/#header) 132 | {% endhint %} 133 | 134 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [首页·前端内参](README.md) 4 | 5 | * [前言·写给有缘人](0.0.1.md) 6 | 7 | ## 零、准备Hard模式下的面试 8 | 9 | * 1.一线互联网公司面试前的准备 10 | * [零.1.1 一线互联网公司有什么不同?](0/0.1.1.md) 11 | * [零.1.2 该公司是做什么的,实力怎么样,前景如何,口碑怎样?](0/0.1.2.md) 12 | * [零.1.3 该岗位负责做什么的,岗位所属部门在什么位置,上升空间多大?](0/0.1.3.md) 13 | * [零.1.4 准备一份好的简历](0/0.1.4.md) 14 | * [零.1.5 不卑不亢,不疾不徐地说话](0/0.1.5.md) 15 | * [零.1.6 "有什么问题要问我吗",如何回答?](0/0.1.6.md) 16 | 17 | ## 壹、前端之灵:JavaScript/ECMAScript 18 | 19 | * 1.你需要知道的新东西 20 | * [壹.1.1 新版 ECMAScript 特性分析](1/1.1.1.md) 21 | * [壹.1.2 JavaScript未来的方向](1/1.1.2.md) 22 | 23 | * 2.核心概念 24 | * [壹.2.1 函数](1/1.2.1.md) 25 | * [壹.2.2 作用域、执行上下文、作用域链](1/1.2.2.md) 26 | * [壹.2.3 彻底搞懂 this](1/1.2.3.md) 27 | * [壹.2.4 深入理解 call、apply、bind](1/1.2.4.md) 28 | * [壹.2.5 面试时高频问到的"闭包"](1/1.2.5.md) 29 | * [壹.2.6 原型和原型链](1/1.2.6.md) 30 | * [壹.2.7 同步和异步,阻塞和非阻塞](1/1.2.7.md) 31 | * [壹.2.8 Event Loop](1/1.2.8.md) 32 | * [壹.2.9 强大的数组](1/1.2.9.md) 33 | * [壹.2.10 正则表达式](1/1.2.10.md) 34 | * [壹.2.11 位操作符与进制转换](1/1.2.11.md) 35 | * [壹.2.12 spread和rest操作符](1/1.2.12.md) 36 | * [壹.2.13 实现异步非阻塞的任务](1/1.2.13.md) 37 | * [壹.2.14 模块化开发](1/1.2.14.md) 38 | 39 | * 3.其他知识点 40 | * [壹.3.1 深拷贝与浅拷贝](1/1.3.1.md) 41 | * [壹.3.2 JavaScript函数柯里化](1/1.3.2.md) 42 | * [壹.3.3 JavaScript元编程:Proxy与Reflect](1/1.3.3.md) 43 | * [壹.3.4 JavaScript中的进程、线程、协程](1/1.3.4.md) 44 | 45 | * 4.浏览器、V8引擎 46 | * [壹.4.2 页面重排(Reflow)与重绘(Repaint\)](1/1.4.2.md) 47 | * [壹.4.3 DOM、Shadow DOM、Virtual DOM](1/1.4.3.md) 48 | 49 | * 5.相关后端知识、通信协议、安全 50 | * [壹.5.2 了解TCP、UDP、TLS](1/1.5.2.md) 51 | * [壹.5.3 HTTP几个版本的区别](1/1.5.3.md) 52 | * [壹.5.4 HTTP和HTTPS的区别在哪里](1/1.5.4.md) 53 | * [壹.5.5 XSS与CSRF攻击](1/1.5.5.md) 54 | 55 | ## 贰、数据结构与算法 56 | 57 | * 1.面试时高频率出现的算法 58 | * [贰.0 本章导读](2/2.0.md) 59 | * [贰.1.1 十大排序算法](2/2.1.1.md) 60 | * [贰.1.2 链表](2/2.1.2.md) 61 | * [贰.1.3 单调栈](2/2.1.3.md) 62 | * [贰.1.4 二叉树的遍历](2/2.1.4.md) 63 | * [贰.1.5 实战:字节跳动前端面试2道算法题](2/2.1.5.md) 64 | * [贰.1.6 分治法、动态规划与贪心算法的区别](2/2.1.6.md) 65 | * [贰.1.7 实战:阿里巴巴前端面试题](2/2.1.7.md) 66 | 67 | ## 叁、主流框架/库 68 | 69 | * 1.综合比较 70 | * [叁.1.1 jQuery过时了吗?](3/3.1.1.md) 71 | * [叁.1.2 React、Vue和Angular对比](3/3.1.2.md) 72 | 73 | * 2.React 74 | * [叁.2.1 React Hooks究竟是什么?](3/3.2.1.md) 75 | 76 | ## 肆、必会的工具 77 | 78 | * 1.Webpack——自动打包模块工具 79 | * [肆.1.1 深入理解Webpack打包](4/si-.1.1-shen-ru-li-jie-webpack-da-bao.md) 80 | 81 | * 2.IDE——工欲善其事必先利其器 82 | * [肆.3.2 适用于前端开发者的20个VSCode插件](4/4.3.2.md) 83 | 84 | ## 伍、编程范式 85 | 86 | * 1.面向对象编程(OOP) 87 | * [伍.1.1 面向对象与面向过程有什么区别?](5/5.1.1.md) 88 | * [伍.1.3 重新认识JavaScript面向对象: 继承](5/5.1.3.md) 89 | * [伍.1.4 彻底搞懂泛型](5/5.1.4.md) 90 | * [伍.1.5 用JavaScript实现抽象类](5/5.1.5.md) 91 | * [伍.1.6 用JavaScript实现接口](5/5.1.6.md) 92 | 93 | * 2.函数式编程 94 | * [伍.2.1 什么是函数式编程?](5/5.2.1.md) 95 | * [伍.2.2 JavaScript的函数式编程探索](5/5.2.2.md) 96 | * [伍.2.3 Pointfree无参数风格编程](5/5.2.3.md) 97 | 98 | * 3.响应式编程 99 | * [伍.3.1 什么是响应式编程?](5/5.3.1.md) 100 | * [伍.3.2 RxJS](5/5.3.2.md) 101 | 102 | ## 陆、设计原则与编程范式 103 | 104 | * 1.SOLID原则 105 | * [陆.1.0 导读:SOLID](6/6.1.0.md) 106 | * [陆.1.1 单一职责原则](6/6.1.1.md) 107 | * [陆.1.2 接口隔离原则](6/6.1.2.md) 108 | * [陆.1.3 开放封闭原则](6/6.1.3.md) 109 | * [陆.1.4 里氏替换原则](6/6.1.4.md) 110 | * [陆.1.5 依赖倒置原则](6/6.1.5.md) 111 | 112 | ## 柒、设计模式与软件工程 113 | 114 | * 1.经典设计模式 115 | * [柒.1.5 发布-订阅模式](7/7.1.5.md) 116 | * [柒.1.14 适配器模式](7/7.1.14.md) 117 | 118 | * 2.软件工程架构 119 | * [柒.2.1 MVC的前世今生](7/7.2.1.md) 120 | 121 | ## 玖、公众号&博客推荐 122 | 123 | * [玖.1 一线互联网公司前端团队官方公众号](9/9.1.md) 124 | * [玖.2 知名前端人物](9/9.2.md) 125 | 126 | ## 拾、后记 127 | 128 | * [拾.1 成为一个好的程序员远比找份好工作重要](10/10.1.md) 129 | * [拾.2 2020年前端技术展望](10/10.2.md) 130 | -------------------------------------------------------------------------------- /2/2.1.3.md: -------------------------------------------------------------------------------- 1 | # 贰.1.3 单调栈 2 | 3 | ## **贰.1.3.1 定义** 4 | 5 | **单调栈,顾名思义就是栈内元素单调按照递增\(递减\)顺序排列的栈**。这里的单调递增或递减是指的从栈顶到栈底单调递增或递减。 6 | 7 | 示例:用数组`[7,4,9,5,3,2]`构建一个单调递增栈。 8 | 9 | ![](../.gitbook/assets/monotone-stack.gif) 10 | 11 | 代码示例: 12 | 13 | ```javascript 14 | (()=>{ 15 | let A=[7,4,9,5,3,2],stk=[]; 16 | let l=A.length; 17 | for (let i = 0; i < l; i++) { 18 | while (stk.lenght && A[i] > A[stk[stk.length-1]]) { // 单调递增栈 19 | // 单调递减栈的情况为 A[i] < A[stk[stk.length-1]]] 20 | stk.pop(); 21 | } 22 | stk.push(i); 23 | } 24 | })(); 25 | ``` 26 | 27 | ## 贰.1.3.2 应用场景 28 | 29 | * 找出数组中每项左/右边第一个大于/小于它的解; 30 | * 题目中隐含查找第一个(或离此项元素最近的)大于/小于此项元素的值,这类问题都可以考虑用单调栈求解。 31 | 32 | ## 贰.1.3.3 应用示例 33 | 34 | ### 1.气温问题([LeetCode.739](https://leetcode-cn.com/problems/daily-temperatures/)) 35 | 36 | 题目如下: 37 | 38 | > 根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。 39 | > 40 | > 例如,给定一个数组`T= [73, 74, 75, 71, 69, 72, 76, 73]`,你的输出应该是`[1, 1, 4, 2, 1, 1, 0, 0]`。 41 | 42 | #### 解法一 43 | 44 | 这道题用若2个暴力循环的办法解决,时间复杂度是 $$O(N^2)$$ ; 45 | 46 | ```javascript 47 | /** 48 | * @param {number[]} T 49 | * @return {number[]} 50 | */ 51 | var dailyTemperatures = function (T) { 52 | let arr = []; 53 | let l = T.length; 54 | 55 | for (let i = 0; i < l; i++) { 56 | if (i == l - 1) {//末尾一定是0 57 | arr.push(0); 58 | } else { 59 | for (let j = i + 1; j < l; j++) { 60 | if (T[j] > T[i]) { 61 | arr.push(j - i); 62 | break; 63 | }else if(j==l-1){ 64 | arr.push(0); 65 | } 66 | } 67 | } 68 | } 69 | 70 | return arr; 71 | }; 72 | ``` 73 | 74 | #### 解法二 75 | 76 | 而若使用单调栈,时间复杂度能将为 $$O(N)$$ 。来分析下这道题,思路如下: 77 | 78 | * 维护一个单调递增栈,栈内存储气温数组 T 的 index ; 79 | * 查看当前元素是否大于栈顶元素所对应的 T 的值,也就是 T\[stack\[stack.length - 1\]\] ; 80 | * 如果大于,那说明找到需要等待的天数。如果不大于那说明还没到找到比这天高的温度,同时继续维护这个单调栈; 81 | * 如果大于,需要等待的天数就是当前数组 T 的index减去单调栈顶对应的index; 82 | * 若循环完毕还没有找到,则需要等待的天数为0。 83 | 84 | ```javascript 85 | /** 86 | * @param {number[]} T 87 | * @return {number[]} 88 | */ 89 | var dailyTemperatures = function(T) { 90 | let { length } = T; 91 | let res = new Array(length).fill(0); 92 | let stack = []; 93 | for(let i = 0; i < length; i++) { 94 | while(stack.length && T[i] > T[stack[stack.length - 1]]) { 95 | let index = stack.pop();//条件满足得到一个结果之后,取出栈顶存放的index 96 | //并执行出栈,可以接着计算下一个了。 97 | res[index] = i - index;//res[index]是巧妙之处,存放天数 98 | } 99 | stack.push(i);//栈内存放T的index 100 | } 101 | return res; 102 | }; 103 | ``` 104 | 105 | 说白了就是空间换时间,新增一个单调栈存放index,巧妙地来换取时间复杂度的降低。 106 | 107 | ### 2.求数组项左右两侧比该项小的第一个数 108 | 109 | 原题如下: 110 | 111 | > 给定一个长度为N的正整数数组,输出每个数组项左右两边第一个比它小的数,如果不存在则输出 - 1。 112 | > 113 | > 输入: `[3, 4, 2, 7, 5]` 114 | > 115 | > 输出: 116 | > 左边 `[-1, 3, -1, 2, 2]` 117 | > 右边 `[2, 2, -1, 5, -1]` 118 | 119 | 解题思路: 120 | 121 | * 用一个单调栈存放数组项的index; 122 | * 用两个数组存放两侧输出结果; 123 | * 栈顶(对应的数组项)右边第一个更小的元素是即将入栈的当前元素; 124 | * 数组当前项左边的第一个更小的元素,是栈顶元素(对应的数组项)。 125 | 126 | ```javascript 127 | ((arr) => { 128 | let l = arr.length; 129 | let stack = []; 130 | let resLeft = new Array(l).fill(-1); 131 | let resRight = new Array(l).fill(-1); 132 | 133 | for (let i = 0; i < l; i++) { 134 | while (stack.length && arr[i] < arr[stack[stack.length - 1]]) { 135 | let index = stack.pop(); 136 | resRight[index] = arr[i]; 137 | if (stack.length) 138 | resLeft[index] = arr[stack[stack.length - 1]]; 139 | } 140 | stack.push(i); 141 | } 142 | 143 | console.log(resLeft, resRight); 144 | })([3, 4, 2, 7, 5]); 145 | ``` 146 | 147 | ## 贰.1.3.4 总结要点 148 | 149 | 单调栈的核心并不是单调,单调递增或者递减只不过是副产品。其实际意义在于: 150 | 151 | * 实际上单调栈中并不保存数组项的值,而是保存数组项在原数组中的索引位置(index),它把空间复杂度从O\(1\)增大为O\(N\); 152 | * 因为针对单调栈又做了一遍历子循环(while),结合出栈操作,这个循环内可以巧妙地做很多事情;由此,单调栈成为一个临时用来协助降低时间复杂度的数据结构,; 153 | * 若从1到N遍历数组一遍,把每个将要放入的数组项其对应的索引位置(index)记录到栈里,就可以在O\(N\)的时间复杂度内得到数组所有项的左/右边、距离该项元素最近、且比该项元素大/小的其他元素的索引位置(index)。 154 | 155 | -------------------------------------------------------------------------------- /5/5.1.1.md: -------------------------------------------------------------------------------- 1 | # 伍.1.1 面向对象与面向过程有什么区别? 2 | 3 | emmm...这个话题其实很复杂,我觉得应该从编程的发展史来谈。 4 | 5 | ## 01. 面向过程编程 6 | 7 | 当开发软件这门科学还处于非常简单的早期时,我们这样编程: 8 | 9 | ```javascript 10 | 定义函数 11 | 函数a 12 | 函数b 13 | …… 14 | 15 | 定义数据 16 | 数据a 17 | 数据b 18 | …… 19 | 20 | 然后 21 | 将数据传递给函数 22 | 按指定的步骤执行函数 23 | 输出结果 24 | ``` 25 | 26 | 你基本上可以认为上面就是面向过程编程。 27 | 28 | > 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,然后一个一个依次调用函数就可以了。 29 | 30 | 当计算机软件应用越来越多后,软件的功能变得越来越强大,代码行数越来越多,复杂度远超Hello World的时候,我们如果按上面的形式编写程序/软件就有麻烦了。 31 | 32 | 函数和数据会定义得非常多,这会导致两个问题: 33 | 34 | * **首先是命名冲突。**英文单词也就那么几个,可能写着写着取名时就没合适的短词用了,为了避免冲突,只能把函数名取得越来越长。 35 | * **然后是代码重复。**比如你做一个计算器程序,你的函数就要确保处理的是合理的数据,这样最起码加减乘除四个函数里,你就都要写对参数进行检测的代码,写四遍或者复制粘贴四遍不会很烦,但多了你就痛苦了,而且因为这些检测代码是跟你的加减乘除函数的本意是无关的,却一定要写在那里,使代码变得臃肿不堪、意图模糊,不能直观地看出其用意。 36 | 37 | 就算一个网络小说的作者,他每次写新章节时也不大可能直接打开包含着前面几百章文字的文档接着写。更正常的做法是新建一个文档,写新的一章,为的是减小复杂性,减少干扰。更何况代码那么复杂那么容易出错的东西。 38 | 39 | ## 02. 面向对象编程 40 | 41 | ### 1\).初步解决办法 42 | 43 | 随着软件业的发展,解决办法就要出来了: 44 | 45 | * 代码重复,我们可以用函数里面调用函数的方法,比如把检测代码抽出来成一个独立函数,然后加减乘除四个函数运行时只要调用一下检测函数对参数进行检查就可以了。分布在四个函数里的重复代码变成了一个函数,是不是好维护多了。 46 | * 命名冲突,我们就把这一堆函数们进行分类吧。比如没有分类时候,我们取名只能取名: 47 | 48 | ```text 49 | 检测 50 | 整数加 51 | 整数减 52 | 整数乘 53 | 整数除 54 | 复数加 55 | 复数减 56 | 复数乘 57 | 复数除 58 | 小数加 59 | ... 60 | ``` 61 | 62 | 进行归类后: 63 | 64 | ```text 65 | 整数{ 66 | 检测 67 | 加 68 | 减 69 | 乘 70 | 除 71 | } 72 | 复数 { 73 | 检测 74 | 加 75 | 减 76 | 乘 77 | 除 78 | } 79 | 小数 { 80 | 检测 81 | 加 82 | 减 83 | 乘 84 | 除 85 | } 86 | 分数 { 87 | 检测 88 | 加 89 | 减 90 | 乘 91 | 除 92 | } 93 | ``` 94 | 95 | 是不是一种叫做类(class)的概念就呼之欲出了,这样我们打开一个整数类代码文件,里面就是简简单单的加减乘除四个函数(也叫方法,method),简单清晰,而不会跟外面的其他加减乘除函数产生命名冲突(因为有了命名空间,namespace)。 96 | 97 | 当然,这样进行归类后,又会有新的问题和解决办法出现。比如四个类中的检测也是应该提取出来的,所以简单的起因最终发展出比如**封装**、**继承**、**多态**之类等挺复杂的一套编程范式。然后编程界的一些学术大神起了各种高大上的名字,即所谓**面向对象编程(OOP)**,然后去”毒害“你我这样当初还年轻的孩子们,从此上了贼船乐呵呵下不来 😭 。 98 | 99 | > 面向对象是把构成问题的要素分解成各个对象,建立对象的目的不是为了完成一个个步骤,而是为了描叙某个要素在整个解决问题的步骤中的行为。 100 | 101 | ### 2\).更进一步解决问题 102 | 103 | 上面进行归类后,代码其实还是不好维护的,然后我们就继续提取为: 104 | 105 | ```text 106 | 数 { 107 | 检测 108 | 加 109 | 减 110 | 乘 111 | 除 112 | } 113 | 整数 { 114 | 沿用上面数的设计 115 | } 116 | 小数 { 117 | 沿用上面数的设计 118 | } 119 | ``` 120 | 121 | 所谓**继承**,就是数这个类的整体设计,沿用给整数,分数小数这些类,作为他们的编写大纲去编写加减乘除这些函数的具体代码。根据整数,分数,小数各自的性质,做出各自的调整。 122 | 123 | 这时数这个类,如果你给它里面的加减乘除函数的写了一些很粗糙简单的代码,就叫做**父类**,基\(础\)类。子类们“继承”了父类(把代码进行了复杂化)。 124 | 125 | 如果类里没写具体的函数实现逻辑,那这个类其实就只是个空壳一样的设计图而已,叫做**抽象类**。子类们**“实现”**了抽象类(把空空的设计变成了具体代码)。 126 | 127 | 模版是什么?像C++语言是强类型的,就是给变量进行了类型区分的,比如整数类型,双整数类型。很明显这两种变量所能容纳的数据体积是不一样的,单个函数不能通吃多种类型的参数,我们就可能会面临下面两套代码并存的局面。 128 | 129 | ```text 130 | 单整数类 { 131 | 单整数加 132 | 单整数减 133 | 单整数乘 134 | 单整数除 135 | } 136 | 双整数类 { 137 | 双整数加 138 | 双整数减 139 | 双整数乘 140 | 双整数除 141 | } 142 | ``` 143 | 144 | 所以C++跟其他强类型语言比如JAVA等,为我们提供了一个所谓**模版功能**,也叫[**泛型**](5.1.4.md): 145 | 146 | ```text 147 | <变量类型>整数 { 148 | <变量类型>加 149 | <变量类型>减 150 | <变量类型>乘 151 | <变量类型>除 152 | } 153 | ``` 154 | 155 | 整数类等于把变量类型设置为整数,套上模版 双整数类等于把变量类型设置为双整数,套上模版 156 | 157 | 这样就写了一份代码,得到了两份类的代码。 158 | 159 | 当然,弱类型的编程语言比如我最爱的JavaScript,因为变量没有严格的类型分别,是没有这种烦恼的。但变量类型有时候还是很重要的,弱类型语言里就会出现类似数加字符串这种运算,可能并不是程序员的预期和本意,所以比起强类型性语言而言,初学者在使用弱类型语言时,经常会出现很多”枯燥无聊“的bug。 160 | 161 | ### 3\).再深入啰嗦几句 162 | 163 | 上面发展出了父类之后,我们发现编程还是有问题的,小数类: 164 | 165 | ```text 166 | 小数类 { 167 | 加 168 | 减 169 | 乘 170 | 除 171 | } 172 | ``` 173 | 174 | 如果我们需要一个”能自动实现结果四舍五入功能”的小数计算类,同时又需要一个不带该功能的,怎么办呢,难道要写两个类吗?大可不必。 175 | 176 | 所以编程界的天才大神们捣鼓出了“**实例”**或者说是“**对象**”这个概念,首先把类改成: 177 | 178 | ```text 179 | 小数类 { 180 | 标识变量:是否四舍五入 181 | 标识变量:是否限定小数点后位数 182 | 构造函数(设置上面的标识) 183 | 加(会根据上面两个标识变量输出不同结果) 184 | 减(会根据上面两个标识变量输出不同结果) 185 | 乘(会根据上面两个标识变量输出不同结果) 186 | 除(会根据上面两个标识变量输出不同结果) 187 | } 188 | ``` 189 | 190 | 这样,我们就写一个类,但是通过构造函数,把一份代码,通过关键字`new`构造出行为稍微有点不同的两个实例供我们使用,这个过程叫**实例化**。 191 | 192 | 不能进行实例化的类,叫做**静态类**,函数们的行为是固定的。不能实例化的类,其实只是函数们的一个集合归纳,只是对函数进行了整理,功能的强大和编码的自由灵活度是不够的。 193 | 194 | 能够进行实例化,变化出各种行为各自不大一样的实例的类,我们一般就把它们叫做类了,因为最常见。程序员们也就能保持代码简单的同时而又可以很方便进行代码行为微调。 195 | 196 | ## 总结 197 | 198 | 文尾以五子棋来举例说明一下。 199 | 200 | 面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。 201 | 202 | 而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。 203 | 204 | 啰啰嗦嗦说了一大堆,也不知道说明白没有。夜已深,我要洗澡然后休息了。最后一句:就算有一天新出来一个什么”面向XX编程“的概念,我们也不用困惑怎么又出新东西了,肯定是为了解决现存在的问题而出现的;多多思考其背后的原因,有助于我们更深入的理解当前所学所用的编程范式。 205 | 206 | ## 207 | 208 | -------------------------------------------------------------------------------- /1/1.5.3.md: -------------------------------------------------------------------------------- 1 | # 壹.5.3 Http几个版本的区别 2 | 3 | 《[HTTP/2: the Future of the Internet](https://link.zhihu.com/?target=https://http2.akamai.com/demo)》 是 Akamai 公司建立的一个官方的演示,用以说明 HTTP/2 相比于之前的 HTTP/1.1 在性能上的大幅度提升。 同时请求 379 张图片,从Load time 的对比可以看出 HTTP/2 在速度上的优势。 4 | 5 | ## HTTP2.0和HTTP1.X相比的新特性 6 | 7 | ### 二进制分帧层 8 | 9 | HTTP2.0性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务器之间传输。这里所谓的“层”,指的是位于套接字接口与应用可见高层HTTP API之间的一个新机制:HTTP语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。HTTP1.x以换行符作为纯文本的分隔符,而HTTP2.0将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。 10 | 11 | 这样一来,客户端和服务器为了相互理解,必须都使用新的二进制编码机制:HTTP1.x客户端无法理解只支持HTTP2.0的服务器,反之亦然。不过不要紧,现有的应用不必担心这些变化,因为客户端和服务器会替它们完成必要的分帧工作。 12 | 13 | HTTPS是二进制分帧的另一个典型示例:所有HTTP消息都以透明的方式为我们编码和解码,从而实现客户端与服务器安全通信,但不必对应用进行任何修改。HTTP2.0的工作原理差不多也是这样。 14 | 15 | ### 流、消息和帧 16 | 17 | 新的二进制分帧机制改变了客户端与服务器之间交互数据的方式。为了说明这个过程,我们需要了解HTTP2.0的几个新概念: 18 | 19 | > **流** 已建立的连接上的双向字节流。 20 | > **消息** 与逻辑消息对应的完整的一系列数据帧。 21 | > **帧** HTTP2.0通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。 22 | 23 | HTTP2.0通信都在一个连接上完成,这个连接可以承载任意数据量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。HTTP2.0的所有帧都采用二进制编码,所有首部数据都会被压缩。 24 | 25 | 这简简单单的几句话里浓缩了大量的信息: 26 | 27 | * 所有通信都在一个TCP连接上完成; 28 | * 流是连接中的一个虚拟信道,可以承载双向的消息。每个流都有一个唯一的整数标识符; 29 | * 消息是指逻辑上的HTTP消息,比如请求、相应等,由一或多个帧组成; 30 | * 帧是最小的通信单位,承载这特定类型的数据,如HTTP首部、负荷等; 31 | 32 | 简言之,HTTP2.0把HTTP协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应地,很多流可以并行的在同一个TCP连接上交换消息。 33 | 34 | ### 多路复用(MultiPlexing) 35 | 36 | 在HTTP1.x中,如果客户端想发送多个并行的请求以及改进性能,那么必须使用多个TCP连接。这是HTTP1.x交付模型的直接结果,该模型会保证每个连接每次只交付一个响应(多个响应必须排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层TCP连接的效率低下。 37 | 38 | HTTP2.0中新的**二进制分帧层**突破了这些限制,实现了多向请求和响应:客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。 39 | 40 | 把HTTP消息分解为独立的帧,交错发送,然后在另一端重新组装是HTTP2.0最重要的一项增强。事实上,这个机制会在整个Web技术栈中引发一系列连锁反应,从而带来巨大的性能提升,因为: 41 | 42 | * 可以并行交错的发送请求,请求之间互不影响; 43 | * 可以并行交错的发送响应,响应之间互不干扰; 44 | * 只使用一个连接即可并行发送多个请求和响应; 45 | * 消除不必要的延迟,从而减少页面加载的时间; 46 | * 不必再为绕过HTTP1.x限制而多做很多工作。 47 | 48 | 总之,HTTP2.0的二进制分帧机制解决了HTTP1.x中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果就是应用速度更快、开发更简单、部署成本更低。 49 | 50 | 支持多向请求和响应,可以省掉对HTTP1.x限制所费的那些工作,比如拼接文件、图片精灵、域名分区。类似地,通过减少TCP连接的数量,HTTP2.0也会减少客户端和服务器的CPU及内存占用。 51 | 52 | ### 请求优先级 53 | 54 | 把HTTP消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,进一步提升性能。为了做到这一点,每个流都可以带有一个31比特的优先值: 55 | 56 | > 0表示最高优先级; \(2^31\)-1表示最低优先级。 57 | 58 | 有了这个优先值,客户端和服务器就可以在处理不同的流时采用不同的策略,以最优的方式发送流、消息和帧。具体来讲,服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将高优先级的帧发送给客户端。 59 | 60 | 浏览器在渲染页面时,并非所有资源都具有相同的优先级:HTML文档本身对构建DOM不可或缺,CSS对构建CSSOM不可或缺,而DOM和CSSOM的构建都可能会受到JavaScript资源的阻塞,其他资源(如图片)的优先级都可以降低。为加快页面加载的速度,所有现代浏览器都会基于资源的类型以及它在页面中的位置排定请求的优先次序,甚至通过之前的访问来学习优先级模式–比如,之前的渲染如果被某些资源阻塞了,那么同样的资源在下一次访问时可能就会被赋予更高的优先级。 61 | 62 | ### 每个来源一个连接 63 | 64 | 有了新的分帧机制后,HTTP2.0不再依赖多个TCP连接去实现多流并行了。现在,每个数据流都拆分成很多帧,而这些帧可以交错,还可以分别优先级。于是,所有HTTP2.0连接都是持久化的,而且客户端与服务器之间也只需要一个连接即可。 65 | 66 | 每个来源一个连接显著减少了相关资源的占用:连接路径上的套接字管理工作量少了,内存占用少了,连接的吞吐量大了。此外,从上到下所有层面上也都获得了相应的好处: 67 | 68 | * 所有话剧流的优先次序始终如一; 69 | * 压缩上下文单一使得压缩效果更好; 70 | * 由于TCP连接减少而使网络拥塞状况得以改观; 71 | * 慢启动时间减少,拥塞和丢包回复速度更快。 72 | 73 | ### 流量控制 74 | 75 | 在同一个TCP上传输多个数据流,就意味着要共享带宽。标定数据流的优先级有助于按序交付,但只有优先级还不足以确定多个数据流或多个连接间的资源分配。为解决这个问题,HTTP2.0为数据流和连接的流量控制提供了一个简单的机制: 76 | 77 | * 流量控制基于每一跳进行,而非端到端的控制; 78 | * 流量控制基于窗口更新帧进行,即接收方广播自己准备接收某个数据流的多少字节,以及整个连接要接收多少字节; 79 | * 流量控制窗口大小通过WINDOW\_UPDATE帧更新,这个字段指定了流ID和窗口大小递增值; 80 | * 流量控制有方向性,即接收放可能根据自己的情况为每个流乃至整个连接设置任意窗口大小; 81 | * 流量控制可以由接收方禁用,包括针对个别的流和针对整个连接。 82 | 83 | HTTP2.0建立连接之后,客户端与服务器交换SETTINGS帧,目的是设置双向的流量控制窗口大小。除此之外,任何一端都可以选择禁用个别流或整个连接的流量控制。 84 | 85 | ### 服务端推送(server push) 86 | 87 | HTTP2.0新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外想客户端推送资源,而无需客户端明确的请求。 88 | 89 | 建立HTTP2.0连接后,客户端与服务器交换SETTINGS帧,借此可以限定双向并发的流的最大数量。因此,客户端可以限定推送流的数量,或者通过设置为0而完全禁用服务器推送。 90 | 91 | 所有推送的资源都遵守同源策略。换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方确认才行。 92 | 93 | ### 首部(header)压缩 94 | 95 | HTTP的每次通信都会携带一组首部,用于描述传输的资源及其属性。在HTTP1.x中这些元数据都是以纯文本形式发送的,通常会给每个请求增加500-800字节的负担。如果算上Cookie,增加的负担更重。为减少这些,HTTP2.0会压缩首部元数据。 HTTP2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送; 首部表在HTTP2.0的连接存续期内始终存在,有客户端和服务器共同更新; 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 96 | 97 | 于是,HTTP2.0连接的两端都知道已经发送了哪些首部。请求与响应首部的定义在HTTP2.0中基本没有改变,只是所有的首部健必须全部小写。 98 | 99 | ## HTTP2.0的升级改造 100 | 101 | HTTP2.0其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先升级HTTPS为好。 102 | 103 | 当你的网站已经升级HTTPS之后,那么升级HTTP2.0就简单很多,如果你使用NGINX,只要在配置文件中启动相应的协议就可以了,可以参考NGINX白皮书,NGINX配置HTTP2.0官方指南 [https://www.nginx.com/blog/nginx-1-9-5/。](https://www.nginx.com/blog/nginx-1-9-5/。) 104 | 105 | 使用了HTTP2.0那么,原本的HTTP1.x怎么办,这个问题其实不用担心,HTTP2.0完全兼容HTTP1.x的语义,对于不支持HTTP2.0的浏览器,NGINX会自动向下兼容的。 106 | 107 | ## HTTP2.0的多路复用和HTTP1.X中的长连接复用有什么区别? 108 | 109 | HTTP/1.\* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接; 110 | 111 | HTTP/1.1 Pipeling解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞; 112 | 113 | HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行。 114 | 115 | -------------------------------------------------------------------------------- /6/6.2.5.md: -------------------------------------------------------------------------------- 1 | # 陆.2.5 用JavaScript认识抽象类和虚方法 2 | 3 | ## 01.抽象类与虚方法 4 | 5 | 虚方法是类成员中的概念,是只做了一个声明而未实现的方法,具有虚方法的类就称之为抽象类,这些虚方法在派生类中才被实现。抽象类是不能实例化的,因为其中的虚方法并不是一个完整的函数,不能被调用。所以抽象类一般只作为基类被派生以后再使用。 和类的继承一样,JavaScript并没有任何机制用于支持抽象类。但利用JavaScript语言本身的性质,可以实现自己的抽象类。 6 | 7 | ## 02.在JavaScript实现抽象类 8 | 9 | 在传统面向对象语言中,抽象类中的虚方法必须先被声明,但可以在其他方法中被调用。而在JavaScript中,虚方法就可以看作该类中没有定义的方法,但已经通过this指针使用了。和传统面向对象不同的是,这里虚方法不需经过声明,而直接使用了。这些方法将在派生类中实现,例如: 10 | 11 | ```javascript 12 | //定义extend方法 13 | Object.extend = function(destination, source) { 14 | for (property in source) { 15 | destination[property] = source[property]; 16 | } 17 | return destination; 18 | } 19 | Object.prototype.extend = function(object) { 20 | return Object.extend.apply(this, [this, object]); 21 | } 22 | //定义一个抽象基类base,无构造函数 23 | function base(){} 24 | base.prototype={ 25 | initialize:function(){ 26 | this.oninit(); //调用了一个虚方法 27 | } 28 | } 29 | //定义class1 30 | function class1(){ 31 | //构造函数 32 | } 33 | //让class1继承于base并实现其中的oninit方法 34 | class1.prototype=(new base()).extend({ 35 | oninit:function(){ //实现抽象基类中的oninit虚方法 36 | //oninit函数的实现 37 | } 38 | }); 39 | ``` 40 | 41 | 这样,当在class1的实例中调用继承得到的initialize方法时,就会自动执行派生类中的oninit\(\)方法。从这里也可以看到解释型语言执行的特点,它们只有在运行到某一个方法调用时,才会检查该方法是否存在,而不会向编译型语言一样在编译阶段就检查方法存在与否。JavaScript中则避免了这个问题。当然,如果希望在基类中添加虚方法的一个定义,也是可以的,只要在派生类中覆盖此方法即可。例如: 42 | 43 | ```javascript 44 | //定义一个抽象基类base,无构造函数 45 | function base(){} 46 | base.prototype={ 47 | initialize:function(){ 48 | this.oninit(); //调用了一个虚方法 49 | }, 50 | oninit:function(){} //虚方法是一个空方法,由派生类实现 51 | } 52 | ``` 53 | 54 | ## 03.示例 55 | 56 | 仍然以prototype-1.6.1为例,其中定义了一个类的创建模型: 57 | 58 | ```javascript 59 | //Class是一个全局对象,有一个方法create,用于返回一个类 60 | var Class = { 61 | create: function() { 62 | return function() { 63 | this.initialize.apply(this, arguments); 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | 这里Class是一个全局对象,具有一个方法create,用于返回一个函数(类),从而声明一个类,可以用如下语法: 70 | 71 | ```javascript 72 | var class1=Class.create(); 73 | ``` 74 | 75 | 这样和函数的定义方式区分开来,使JavaScript语言能够更具备面向对象语言的特点。现在来看这个返回的函数(类): 76 | 77 | ```javascript 78 | function(){ 79 | this.initialize.apply(this, arguments); 80 | } 81 | ``` 82 | 83 | 这个函数也是一个类的构造函数,当new这个类时便会得到执行。它调用了一个initialize方法,从名字来看,是类的构造函数。而从类的角度来看,它是一个虚方法,是未定义的。但这个虚方法的实现并不是在派生类中实现的,而是创建完一个类后,在prototype中定义的,例如prototype可以这样写: 84 | 85 | ```javascript 86 | var class1=Class.create(); 87 | class1.prototype={ 88 | initialize:function(userName){ 89 | alert(“hello,”+userName); 90 | } 91 | } 92 | ``` 93 | 94 | 这样,每次创建类的实例时,initialize方法都会得到执行,从而实现了将类的构造函数和类成员一起定义的功能。其中,为了能够给构造函数传递参数,使用了这样的语句: 95 | 96 | ```javascript 97 | function(){ 98 | this.initialize.apply(this, arguments); 99 | } 100 | ``` 101 | 102 | 实际上,这里的arguments是function\(\)中所传进来的参数,也就是new class1\(args\)中传递进来的args,现在要把args传递给initialize,巧妙的使用了函数的apply方法,注意不能写成: 103 | 104 | ```javascript 105 | this.initialize(arguments); 106 | ``` 107 | 108 | 这是将arguments数组作为一个参数传递给initialize方法,而apply方法则可以把arguments数组对象的元素作为一组参数传递过去,这是一种很巧妙的实现。 尽管这个例子在prototype-1.3.1中不是一个抽象类的概念,而是类的一种设计模式。但实际上可以把Class.create\(\)返回的类看作所有类的共同基类,它在构造函数中调用了一个虚方法initialize,所有继承于它的类都必须实现这个方法,完成构造函数的功能。它们得以实现的本质就是对prototype的操作。 109 | 110 | **具体代码:** 111 | 112 | ```markup 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 130 | 131 | 132 | 133 | 134 | 135 | ``` 136 | 137 | **父类js:** 138 | 139 | ```javascript 140 | var Person = Class.create(); 141 | Person.prototype={ 142 | //必须给初始化值 143 | initialize: function(name) { 144 | this.personName=name; 145 | }, 146 | showInfo:function(){ 147 | alert(this.personName); 148 | } 149 | } 150 | ``` 151 | 152 | **子类的js:** 153 | 154 | ```javascript 155 | var Employee = Class.create(); 156 | 157 | Employee.prototype = Object.extend(new Person(), { 158 | //定义一个抽象类 159 | initialize: function(name,corp) { 160 | this.personName=name; 161 | this.corpName=corp; 162 | }, 163 | // corpName:"Micosoft", 164 | showInfo:function(){ 165 | return this.personName+","+this.corpName; 166 | } 167 | }); 168 | ``` 169 | 170 | -------------------------------------------------------------------------------- /7/7.1.5.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 最高频使用和被问到的设计模式,没有之一 3 | --- 4 | 5 | # 柒.1.5 发布-订阅模式 6 | 7 | ## 柒.1.5.1 什么是发布-订阅模式 8 | 9 | ### 举个例子 10 | 11 | 作为前端工程师的你很关心前端技术,会经常去刷笔者的公众号学习。这样有了新文章你不会第一时间知道;要是忘了刷,过段时间新文章被挤下去之后,你再回来刷,就会可能错过了这篇文章;你要一直保持刷的姿势,很累的。换个轻松点的姿势,你选择了关注笔者的公众号,一有新的文章发布,公众号就会发消息到你的手机通知到你,从此你再也不会错过新文章,新松获取新知识。 12 | 13 | ### 定义 14 | 15 | **发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。** 16 | 17 | 订阅者(Subscriber)把自己想订阅的事件注册(subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(publish event)到调度中心,也就是该事件触发时,由调度中心统一调用(fire event)订阅者注册到调度中心的处理逻辑代码。 18 | 19 | ## 柒.1.5.2 优缺点 20 | 21 | ### 优点 22 | 23 | * 对象之间解耦 24 | * 异步编程中,可以更松耦合的代码编写 25 | 26 | ### 缺点 27 | 28 | * 创建订阅者本身要消耗一定的时间和内存 29 | * 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护 30 | 31 | ## 柒.1.5.3 如何实现 32 | 33 | ### 发布-订阅模式的实现思路 34 | 35 | * 创建一个对象; 36 | * 在该对象上创建一个调度中心,实际上是一个缓存列表; 37 | * on 方法用来把函数 fn 都加到缓存列表中,也即订阅者注册事件到调度中心; 38 | * emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码); 39 | * off 方法可以根据 event 的值取消订阅; 40 | * once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)。 41 | 42 | ### JavaScript代码实现 43 | 44 | ```javascript 45 | let eventEmitter = { 46 | // 缓存列表 47 | list: {}, 48 | // 订阅 49 | on (event, fn) { 50 | let _this = this; 51 | // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表 52 | // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里 53 | (_this.list[event] || (_this.list[event] = [])).push(fn); 54 | return _this; 55 | }, 56 | // 监听一次 57 | once (event, fn) { 58 | // 先绑定,调用后删除 59 | let _this = this; 60 | function on () { 61 | _this.off(event, on); 62 | fn.apply(_this, arguments); 63 | } 64 | on.fn = fn; 65 | _this.on(event, on); 66 | return _this; 67 | }, 68 | // 取消订阅 69 | off (event, fn) { 70 | let _this = this; 71 | let fns = _this.list[event]; 72 | // 如果缓存列表中没有相应的 fn,返回false 73 | if (!fns) return false; 74 | if (!fn) { 75 | // 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空 76 | fns && (fns.length = 0); 77 | } else { 78 | // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可 79 | let cb; 80 | for (let i = 0, cbLen = fns.length; i < cbLen; i++) { 81 | cb = fns[i]; 82 | if (cb === fn || cb.fn === fn) { 83 | fns.splice(i, 1); 84 | break 85 | } 86 | } 87 | } 88 | return _this; 89 | }, 90 | // 发布 91 | emit () { 92 | let _this = this; 93 | // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出 94 | let event = [].shift.call(arguments), 95 | fns = _this.list[event]; 96 | // 如果缓存列表里没有 fn 就返回 false 97 | if (!fns || fns.length === 0) { 98 | return false; 99 | } 100 | // 遍历 event 值对应的缓存列表,依次执行 fn 101 | fns.forEach(fn => { 102 | fn.apply(_this, arguments); 103 | }); 104 | return _this; 105 | } 106 | }; 107 | 108 | function user1 (content) { 109 | console.log('用户1订阅了:', content); 110 | } 111 | 112 | function user2 (content) { 113 | console.log('用户2订阅了:', content); 114 | } 115 | 116 | function user3 (content) { 117 | console.log('用户3订阅了:', content); 118 | } 119 | 120 | function user4 (content) { 121 | console.log('用户4订阅了:', content); 122 | } 123 | 124 | // 订阅 125 | eventEmitter.on('article1', user1); 126 | eventEmitter.on('article1', user2); 127 | eventEmitter.on('article1', user3); 128 | 129 | // 取消user2方法的订阅 130 | eventEmitter.off('article1', user2); 131 | 132 | eventEmitter.once('article2', user4) 133 | 134 | // 发布 135 | eventEmitter.emit('article1', 'Javascript 发布-订阅模式'); 136 | eventEmitter.emit('article1', 'Javascript 发布-订阅模式'); 137 | eventEmitter.emit('article2', 'Javascript 观察者模式'); 138 | eventEmitter.emit('article2', 'Javascript 观察者模式'); 139 | 140 | // eventEmitter.on('article1', user3).emit('article1', 'test111'); 141 | 142 | /*>> 143 | 用户1订阅了: Javascript 发布-订阅模式 144 | 用户3订阅了: Javascript 发布-订阅模式 145 | 用户1订阅了: Javascript 发布-订阅模式 146 | 用户3订阅了: Javascript 发布-订阅模式 147 | 用户4订阅了: Javascript 观察者模式 148 | */ 149 | 150 | ``` 151 | 152 | ## 柒.1.5.4 发布-订阅模式与观察者模式的区别 153 | 154 | 先看个图以便有个直观认识。 155 | 156 | ![](../.gitbook/assets/image%20%2821%29.png) 157 | 158 | **`观察者模式`**:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 159 | 160 | **`发布订阅模式`**:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 161 | 162 | ### **差异** 163 | 164 | * 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。 165 | * 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 166 | * 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。 167 | * 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。 168 | 169 | -------------------------------------------------------------------------------- /7/7.1.1.md: -------------------------------------------------------------------------------- 1 | # 柒.1.1 单例模式 2 | 3 | ## 柒.1.1.1 什么是单例模式 4 | 5 | ​ 单例模式其实在JS中是一个最简单、也是最常见的设计模式,它的含义也比较简单:保证一个类仅仅有一个实例,并且只提供一个访问它的全局访问点。这句话不好理解的话,给大家再举个例子:我们使用CDN的方式在项目代码里引入的JS库文件,其实它的入口就是一个单例,比如我们的jQuery。当我们在页面中通过 123 | 124 | 125 | 133 | 134 | 135 | 136 | 137 | 138 | ``` 139 | 140 | **父类js:** 141 | 142 | ```javascript 143 | var Person = Class.create(); 144 | Person.prototype={ 145 | //必须给初始化值 146 | initialize: function(name) { 147 | this.personName=name; 148 | }, 149 | showInfo:function(){ 150 | alert(this.personName); 151 | } 152 | } 153 | ``` 154 | 155 | **子类的js:** 156 | 157 | ```javascript 158 | var Employee = Class.create(); 159 | 160 | Employee.prototype = Object.extend(new Person(), { 161 | //定义一个抽象类 162 | initialize: function(name,corp) { 163 | this.personName=name; 164 | this.corpName=corp; 165 | }, 166 | // corpName:"Micosoft", 167 | showInfo:function(){ 168 | return this.personName+","+this.corpName; 169 | } 170 | }); 171 | ``` 172 | 173 | -------------------------------------------------------------------------------- /4/4.2.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: transition 过渡动画 3 | date: 2019-09-12 17:05:51 4 | tags: 5 | --- 6 | # 过渡 7 | css3时代,增加了一个transition属性,用处是让变化增加过程 8 | ```css 9 | .example { 10 | transition: [transition-property] [transition-duration] [transition-timing-function] [transition-delay]; 11 | } 12 | ``` 13 | 我们来个简单的例子 14 | ```css 15 | .box { 16 | transition: background-color 0.5s ease; 17 | background-color: red; 18 | width:100px; 19 | height:100px; 20 | } 21 | .box:hover { 22 | background-color: green; 23 | } 24 | ``` 25 | 可以用鼠标经过一下下面红色方块,会渐渐变成绿色;移开又会渐渐变回红色。 26 | ```html 27 |
 
28 | ``` 29 | 30 | # 参数 31 | 跟其他css语言类似,transition是四个属性的简写 32 | *如果不懂css的简写可以从基础看起,比如margin: [margin-top] [margin-right] [margin-bottom] [margin-left]* 33 | ```css 34 | .example { 35 | transition: background-color 0.5s ease 200ms; 36 | } 37 | ``` 38 | 上面的属性详细的写法是下面的 39 | ```css 40 | .example { 41 | transition-property: background-color; /* 过渡属性为背景颜色 */ 42 | transition-duration: 0.5s; /* 过渡动画持续0.5秒 */ 43 | transition-timing-function: ease; /* 过渡时间方法为ease缓动 */ 44 | transition-delay: 200ms; /* 动画延迟200毫秒执行 */ 45 | } 46 | ``` 47 | ## 过渡属性 48 | **transition-property** 49 | 50 | 1. 单一属性 51 | ```css 52 | .example { 53 | transition-property: background-color; /* 单独写一种css的属性 */ 54 | } 55 | ``` 56 | 57 | 58 | 2. 属性列表,逗号分隔 59 | ```css 60 | .example { 61 | transition-property: background-color, width, padding; /* 逗号分隔的多种css的属性 */ 62 | } 63 | ``` 64 | 65 | 66 | 3. 关键字,none 67 | ```css 68 | .example { 69 | transition-property: none; /* 设置为none的时候,没有缓动,可以在需要清除缓动时候做这种赋值处理 */ 70 | } 71 | ``` 72 | 73 | 74 | 4. 关键字,all 75 | ```css 76 | .example { 77 | transition-property: all; /* 设置为all的时候,所有属性都有缓动,这是默认值 */ 78 | } 79 | ``` 80 | 81 | 82 | ## 持续时间 83 | **transition-duration** 84 | 85 | 86 | 动画执行的时间,默认值为0s,单位可以用s(秒)和ms(毫秒) 87 | ```css 88 | .example { 89 | transition-duration: 0.5s; /* 缓动动画间隔为0.5秒 */ 90 | } 91 | .other { 92 | transition-duration: 500ms; /* 缓动动画间隔为500毫秒(其实也就是0.5秒) */ 93 | } 94 | ``` 95 | 96 | 97 | ## 延迟时间 98 | **transition-delay** 99 | 100 | 101 | 动画延迟执行的时间,默认值为0s,单位可以用s(秒)和ms(毫秒) 102 | ```css 103 | .example { 104 | transition-delay: 0.5s; /* 缓动动画延迟0.5秒执行 */ 105 | } 106 | .other { 107 | transition-delay: 500ms; /* 缓动动画延迟500毫秒(其实也就是0.5秒)执行 */ 108 | } 109 | ``` 110 | 111 | 112 | ## 过渡方式 113 | **transition-timing-function** 114 | ### 缓动系数 115 | * ease 116 | * linear 117 | * ease-in 118 | * ease-out 119 | * ease-in-out 120 | 121 | 122 | 缓动系数 | 图片 | 贝塞尔 | 简介 123 | ---------| ------------- | ------------- | ------------- 124 | ease | ![ease](./transition/ease.png) | 0.25, 0.1, 0.25, 1.0 | 开始加速运动,结束减速运动直至停止 125 | linear | ![linear](./transition/linear.png) | 0.0, 0.0, 1.0, 1.0 | 速度不变 126 | ease-in | ![ease-in](./transition/ease-in.png) | 0.42, 0.0, 1.0, 1.0 | 加速运动 127 | ease-out | ![ease-out](./transition/ease-out.png) | 0.0, 0.0, 0.58, 1.0 | 减速运动 128 | ease-in-out | ![ease-in-out](./transition/ease-in-out.png) | 0.42, 0.0, 0.58, 1.0 | 开始加速运动,结束减速运动直至停止。相当于ease的升级版 129 | 130 | ------------- 131 | 132 | 可以实际写一下对比一下 133 | ```css 134 | .spread { 135 | transition-property: width; 136 | transition-duration: 2s; 137 | background-color: red; 138 | width: 100px; 139 | height: 20px; 140 | color: white; 141 | margin-top: 2px; 142 | } 143 | .spread:hover { 144 | width: 300px; 145 | } 146 | .ease { 147 | transition-timing-function: ease; 148 | } 149 | .linear { 150 | transition-timing-function: linear; 151 | } 152 | .ease-in{ 153 | transition-timing-function: ease-in; 154 | } 155 | .ease-out { 156 | transition-timing-function: ease-out; 157 | } 158 | .ease-in-out { 159 | transition-timing-function: ease-in-out; 160 | } 161 | ``` 162 | ```html 163 |
ease
164 |
linear
165 |
ease-in
166 |
ease-out
167 |
ease-in-out
168 | ``` 169 | 170 | 171 | 172 | ### 贝塞尔曲线 173 | 每一个缓动系数都可以用一个贝塞尔曲线来表示 174 | ```css 175 | .ease-in-out { 176 | transition-timing-function: ease-in-out; 177 | transition-timing-function: cubic-bezier(0.42, 0.0, 0.58, 1.0); /*这个写法等同于ease-in-out*/ 178 | } 179 | ``` 180 | 在chrome等浏览器,在编辑css属性的地方,可以如果是transition-timing-function,点击可以直接进入编辑 181 | 182 | 183 | ![chrome](./transition/chrome.png) 184 | 185 | 186 | 你拖动控制杆,就可以在最下面得到你想要的贝塞尔曲线值 187 | 188 | 189 | ## 步骤 190 | * step-start 191 | * step-end 192 | 193 | 194 | 设定动画分几步完成,一般在雪碧图时候要用 195 | 196 | 197 | 198 | 参数 | 图片 | 简介 199 | ---------| ------------- | ------------- 200 | steps(2, start) | ![steps-start](./transition/steps-start.png) | 动画分2步完成,初始状态在开始 201 | steps(4, end) | ![steps-end](./transition/steps-end.png) | 动画分4步完成,初始状态在结束 202 | 203 | 204 | ------------- 205 | 这一项其实是单独的,但是也在transition-timing-function里 206 | ```css 207 | .ease-in-out { 208 | transition-timing-function: linear steps(2, start); 209 | } 210 | ``` 211 | 212 | -------------------------------------------------------------------------------- /1/1.2.6.md: -------------------------------------------------------------------------------- 1 | # 壹.2.6 原型和原型链 2 | 3 | JavaScript在解决复用性方面做过很多尝试,最终确定了利用原型和原型链来解决。这和Java等高级语言有很大的不同,Java可以通过`extend`关键字继承某个类(class)以轻松实现复用。 4 | 5 | 而在ES6之前,JavaScript 中除了基础类型外的数据类型都是对象(引用类型),没有类(class),为了实现类似继承以便复用代码的能力,JavaScript选择了原型和原型链。甚至在ES6之后,JavaScript也没有真正的类(class)。ES6虽然提供了`class`关键字让我么可以伪造一个“类”,但其实只是语法糖而已,本质上仍然是一个对象。ES6实现的继承,本质仍是基于原型和原型链。 6 | 7 | ## 壹.2.6.1 原型、prototype、\_\_**proto\_\_** 8 | 9 | 1. **原型**是一个对象。 10 | 2. **`prototype`是函数的一个属性而已,也是一个对象,它和原型没有绝对的关系**(很多书、很多网络文章都模糊地将prototype表述为原型,这是严重不对的)。JavaScript里函数也是一种对象,每个对象都有一个原型,但不是所有对象都有`prototype`属性,实际上只有函数才有这个属性。 11 | 12 | ```javascript 13 | var a = function(){}; 14 | var b=[1,2,3]; 15 | 16 | //函数才有prototype属性 17 | console.log(a.prototype);//>> function(){} 18 | //非函数,没有prototype属性 19 | console.log(b.prototype);//>> undefined 20 | 21 | ``` 22 | 23 | 3. 每个对象\(实例\)都有一个属性`__proto__`,指向他的构造函数(constructor)的`prototype`属性。 24 | 4. **一个对象的原型就是它的构造函数的`prototype`属性的值**,因此**`__proto__`也即原型的代名词**。 25 | 5. 对象的`__proto__`也有自己的`__proto__`,层层向上,直到`__proto__`为null。换句话说,原型本身也有自己的原型。这种由原型层层链接起来的数据结构成为 **原型链**。因为null不再有原型,所以原型链的末端是null。 26 | 27 | 让我们用更多代码来验证一下以上结论: 28 | 29 | ```javascript 30 | var a = function(){}; 31 | var b=[1,2,3]; 32 | 33 | //a的构造函数是「Function函数」 34 | console.log(a.__proto__ == Function.prototype);//>> true 35 | //b的构造函数是「Array函数」 36 | console.log(b.__proto__ == Array.prototype);//>> true 37 | 38 | //因为「Function函数」和「Array函数」又都是对象,其构造函数 39 | //是「Object函数」,所以,a和b的原型的原型都是Object.prototype 40 | console.log(a.__proto__.__proto__ === Object.prototype);//>> true 41 | console.log(b.__proto__.__proto__ === Object.prototype);//>> true 42 | 43 | //Object作为顶级对象的构造函数,它实例的原型本身就不再有原型了,因此它原型 44 | //的__proto__属性为null 45 | console.log(new Object().__proto__.__proto__);//>> null 46 | //也即Object类型对象,其原型(Object.prototype)的__proto__为null 47 | console.log(Object.prototype.__proto__);//>> null 48 | ``` 49 | 50 | 三者关系图如下: 51 | 52 | ![](../.gitbook/assets/image%20%288%29.png) 53 | 54 | > 使用`__proto__`是有争议的,也不鼓励使用它。因为它从来没有被包括在EcmaScript语言规范中,但是现代浏览器都实现了它。`__proto__`属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。但是,它已被**不推荐使用**,现在更推荐使用`Object.getPrototypeOf`/`Reflect.getPrototypeOf` 和`Object.setPrototypeOf`/`Reflect.setPrototypeOf`(尽管如此,设置对象的原型是一个缓慢的操作,如果性能要求很高,应该避免设置对象的原型)。 55 | 56 | ## 壹.2.6.3 原型继承 57 | 58 | 使用最新的方法`Object.setPrototypeOf`(类似`Reflect.setPrototypeOf`)可以很方便地给对象设置原型,这个对象会继承该原型所有属性和方法。 59 | 60 | 但是,`setPrototypeOf`的性能很差,我们应该尽量使用 `Object.create()`来为某个对象设置原型。 61 | 62 | ```javascript 63 | //obj的原型是Object.prototype 64 | var obj={ 65 | methodA(){ 66 | console.log("coffe"); 67 | } 68 | } 69 | 70 | var newObj = Object.create(obj);//以obj为原型创建一个新的对象 71 | 72 | //methodA实际上是newObj原型对象obj上的方法。也即newObj继承了它的原型对象obj的属性和方法。 73 | newObj.methodA();//>> coffe 74 | ``` 75 | 76 | ## 壹.2.6.3 原型链的查找机制 77 | 78 | 当我们访问某个对象的方法或者属性,如果该对象上没有该属性或者方法,JS引擎就会遍历原型链上的每一个原型对象,在这些原型对象里面查找该属性或方法,直到找到为止,若遍历了整个原型链仍然找不到,则报错。代码示例如下: 79 | 80 | ```javascript 81 | var obj={ 82 | methodA(){ 83 | console.log("coffe"); 84 | } 85 | } 86 | 87 | var newObj = Object.create(obj);//以obj为原型创建一个新的对象 88 | 89 | newObj.hasOwnProperty("methodA");//>> false 90 | ``` 91 | 92 | 上面的代码,`hasOwnProperty`方法并未在`newObj`上定义,也没有在它的原型`obj`上定义,是它原型链上原型`Object.prototype`的方法。其原型链查找顺序如下图所示: 93 | 94 | ![](../.gitbook/assets/image%20%287%29%20%281%29.png) 95 | 96 | 是不是感觉和作用域链上“变量的查找机制”比较相似?没错,这就是JavaScript所遵循的设计美学之一:归一化。你也可以理解成一致性、相似性。 97 | 98 | ## 壹.2.6.4 再来说说类\(class)的prototype和\_\_proto\_\_ 99 | 100 | ES6之后,类\(class\)也有了prototype属性,为什么呢,因为class本质上是构造函数的语法糖。不信可以看如下代码: 101 | 102 | ```javascript 103 | class A { 104 | } 105 | 106 | typeof A;//>> "function" 107 | ``` 108 | 109 | 说明class本质上也是函数,所以它带有prototype属性是十分正常的事。 110 | 111 | 然后,在Chrome浏览器里调试如下代码: 112 | 113 | ```javascript 114 | class A { 115 | } 116 | 117 | A.prototype; 118 | ``` 119 | 120 | 得到的结果如下图: 121 | 122 | ![](../.gitbook/assets/1.2.6.4.1.png) 123 | 124 | 上面代码说明**类的prototype是一个对象**,它包含有`constructor`属性。这和函数的prototype属性表现具有一致性。 125 | 126 | ```javascript 127 | class A { 128 | } 129 | 130 | A===A.prototype.constructor;//>> true 131 | ``` 132 | 133 | 上面代码说明一个重要结论:**类本身指向的构造函数**。 134 | 135 | 而且,事实上,**类的所有方法都定义在类的`prototype`属性上面**。同样可以通过Chrome调试验证,请自行验证。 136 | 137 | ```javascript 138 | class A{ 139 | constructor() { 140 | // ... 141 | } 142 | 143 | toString() { 144 | // ... 145 | } 146 | 147 | toValue() { 148 | // ... 149 | } 150 | } 151 | 152 | // 等同于 153 | A.prototype = { 154 | constructor() {}, 155 | toString() {}, 156 | toValue() {}, 157 | }; 158 | ``` 159 | 160 | ### 定理 161 | 162 | class 作为构造函数的语法糖,同时有`prototype`属性和`__proto__`属性,因此同时存在两条继承链。 163 | 164 | (1)子类的`__proto__`属性,表示构造函数的继承,总是指向父类。 165 | 166 | (2)子类`prototype`属性的`__proto__`属性,表示方法的继承,总是指向父类的`prototype`属性。 167 | 168 | ```javascript 169 | class A { 170 | } 171 | 172 | class B extends A { 173 | } 174 | 175 | B.__proto__ === A //>> true 176 | B.prototype.__proto__ === A.prototype //>> true 177 | ``` 178 | 179 | ### 继承的实现 180 | 181 | 关于继承的内容可以阅读本书 [陆.2.3 重新认识JavaScript面向对象: 继承 ](../5/5.1.3.md)。 182 | 183 | -------------------------------------------------------------------------------- /2/2.1.7.md: -------------------------------------------------------------------------------- 1 | # 贰.1.7 实战:阿里巴巴前端面试题 2 | 3 | ## 贰.1.7.1 求数组子数组之和的最大值 4 | 5 | 这道题源自微软面试题,后来被阿里巴巴用上了,据说只有20%的人能给出时间复杂度为 $$O(N^3)$$ 的答案,给出 $$O(N)$$ 解的人极少。题目如下: 6 | 7 | > 一个有N个整数项的一维数组,这个数组有很连续多子数组,那么子数组之和的最大值是多少? 8 | 9 | 举例: 10 | 11 | * `[ 1,-3, 3, 4, 5,-3]`返回12; 12 | * `[-1,-2,-3,-4,-9,-8]`返回-1; 13 | 14 | ### 解法一 15 | 16 | 问题其实是求从下标为`i`和`j`之间的连续数组项之和的最大值。 17 | 18 | ```javascript 19 | function fn(arr) { 20 | let maxSum=-Infinity, len = arr.length; 21 | for (let i = 0; i < len; i++) { 22 | for (let j = i; j < len; j++) { 23 | let sum=0; 24 | for (let k = i; k <= j; k++) { 25 | sum += arr[k]; 26 | } 27 | if (sum > maxSum){ 28 | maxSum = sum; 29 | } 30 | } 31 | } 32 | console.log(maxSum); 33 | } 34 | fn([ 1, -3, 3, 4, 5, -3]);//>> 12 35 | fn([-1, -2, -3, -4, -9, -8]);//>> -1 36 | ``` 37 | 38 | 上面代码有3个for循环,因此时间复杂度是 $$O(N^3)$$ 。 39 | 40 | ### 解法二 41 | 42 | 如果注意到 $$A_i+...+A_j=A_i+...A_j-_1+A_j$$ ,那么可以将解法一精简如下: 43 | 44 | ```javascript 45 | function fn(arr) { 46 | let maxSum=-Infinity, len = arr.length; 47 | for (let i = 0; i < len; i++) { 48 | let sum = 0; 49 | for (let j = i; j < len; j++) { 50 | sum += arr[j]; 51 | if (sum > maxSum){ 52 | maxSum = sum; 53 | } 54 | } 55 | } 56 | console.log(maxSum); 57 | } 58 | fn([ 1, -3, 3, 4, 5, -3]);//>> 12 59 | fn([-1, -2, -3, -4, -9, -8]);//>> -1 60 | ``` 61 | 62 | 上面代码第6行就体现了 $$A_i+...+A_j$$ ,与解法一的第三个for循环其实是等效的。因此可以精简掉一个for循环,精简之后的算法时间复杂度为 $$O(N^2)$$ 。能做到这样,其实已经不错的,但是还不够,有没有更低时间复杂度的解法呢?有! 63 | 64 | ### 解法三 65 | 66 | 若用**动态规划法**,应该先有一个“看起来更加毫无规律”的样本数据,这里选\[ 1, -3, 3, 4, 5, -3\],然后做下面表格分析规律得出**状态转移方程**: 67 | 68 | | 步骤 | 操作 | 累加的子数组之和 | 最大子数组和 | 69 | | :--- | :--- | :--- | :--- | 70 | | 1 | 加1 | 1 | 1 | 71 | | 2 | -3 | -2 | -2 | 72 | | 3 | 之前的和<0,舍弃,直接从3开始,+3 | 3 | 3 | 73 | | 4 | +4 | 7 | 7 | 74 | | 5 | +5 | 12 | 12 | 75 | | 6 | -3 | 9 | 12 | 76 | 77 | 用数学归纳法得出状态转移方程如下: 78 | 79 | //todo 80 | 81 | ```javascript 82 | function fn(array) { 83 | if (array == null && array.length <= 0) { 84 | return 0; 85 | } 86 | let Maxsum = Integer.MIN_VALUE; 87 | let currentSum = 0; 88 | for (let i = 0; i < array.length; i++) { 89 | currentSum += array[i]; 90 | if (currentSum < array[i]) { 91 | currentSum = array[i]; 92 | } 93 | if (currentSum > Maxsum) { 94 | Maxsum = currentSum; 95 | } 96 | } 97 | return Maxsum; 98 | } 99 | ``` 100 | 101 | 时间复杂度为 $$O(N)$$ 。 102 | 103 | ## 贰.1.7.2 硬币找零 104 | 105 | 题目为: 106 | 107 | > 从面值为 1,2,5,10 的硬币中找零 36 块钱,最少要 几 枚硬币? 108 | 109 | ### 解法一:动态规划 110 | 111 | 这是典型的动态规划问题。所谓动态规划,也即问题分成多个步骤,每一步的选择是可以动态调整的,所有步骤完成之后,最后求最优解。比如求两点之间的最优路径,多种组合方案的最优方案。**这类问题,可以分解成对每一步求最优解,然后将每步的最优解组合,即为最终最优解。** 112 | 113 | 本题求最少硬币数,也可以用动态规划法求解。首先要归纳出一个**状态转移方程。**给定的金额`i`,最少硬币数为`f(i)`,硬币面值为`j`,它们之间的关系可以用方程表达如下(该方程即为**状态转移方程**): 114 | 115 | $$ 116 | f(i)=f(i-j)+1 117 | $$ 118 | 119 | 有了上面这个状态转移方程,可以看出是一个递归求解过程,所以,此类问题就很好解了,代码如下: 120 | 121 | ```javascript 122 | /** 123 | * 硬币找零 之 动态规划法。 124 | * 状态转移方程 DP(i)=DP(i-j)+1,其中i是金额,j是硬币面值。 125 | * @param {array} coins 硬币面额的数组 126 | * @param {number} amount 给定的金额数 127 | */ 128 | 129 | (function (coins, amount) { 130 | //优化,缓存递归过程中已经计算过的金额数 131 | let cache = new Map(); 132 | 133 | //如果金额和面值恰好相等,1枚硬币就能完成 134 | coins.forEach(coin => { 135 | cache.set(coin, 1); 136 | }); 137 | 138 | console.time("coffe1891"); 139 | 140 | let DP = function (amount) { 141 | if (amount <= 0) 142 | return 0; 143 | else if (cache.has(amount)) 144 | return cache.get(amount); 145 | else { 146 | //缓存方案 147 | let tmpArr = []; 148 | for (let i = 0; i < coins.length; i++) { 149 | let coin = coins[i]; 150 | if (amount - coin > 0) {//非法钱数不计算 151 | //递归 152 | let v = DP(amount - coin) + 1; 153 | tmpArr.push(v); 154 | } 155 | } 156 | 157 | //从方案中选最优的 158 | tmpArr.sort((a, b) => a - b); 159 | cache.set(amount, tmpArr[0]); 160 | return tmpArr[0]; 161 | } 162 | } 163 | console.log("从面值为 " + coins + " 的硬币中找零 " + amount + " 块钱,最少要 " + DP(amount) + " 枚硬币"); 164 | console.timeEnd('coffe1891'); 165 | })([1, 2, 5, 10], 36); 166 | ``` 167 | 168 | 因此我们发现一个规律:**可用动态规划法解题的关键,在于找到状态转移方程,通过观察该方程,可以更快知道解题的具体算法思路**。 169 | 170 | ### 解法二:贪心算法 171 | 172 | 每次我们都优先取最大的硬币面额,直到剩余金额不足最大面额时,我们会取第二大的面额,以此类推,从而实现总硬币数最小的目的。其思想非常简单,我们直接上代码: 173 | 174 | ```javascript 175 | /** 176 | * 硬币找零 之 贪心算法 177 | */ 178 | (function (coins, amount) { 179 | let s = new Date().getTime(); 180 | let greedy = function (amount) { 181 | let total = 0;//已找金额 182 | let change = [];//放硬币的罐子 183 | for (let i = coins.length-1; i >= 0; i--) { 184 | let coin = coins[i]; 185 | while (total + coin <= amount) { 186 | change.push(coin); 187 | total += coin; 188 | } 189 | } 190 | return change.length; 191 | } 192 | console.log("从面值为 " + coins + " 的硬币中找零 " + amount + " 块钱,最少要 " + greedy(amount) + " 枚硬币"); 193 | let e = new Date().getTime(); 194 | console.log("耗时 " + (e - s) + " ms"); 195 | })([1, 2, 5, 10], 36); 196 | ``` 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /7/7.1.2.md: -------------------------------------------------------------------------------- 1 | # 柒.1.2 策略模式 2 | 3 | ## 柒.1.2.1 什么是策略模式 4 | 5 | ​ 策略模式是一种简单却常用的设计模式,它的应用场景非常广泛。该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。 6 | ​ 该模式**主要解决**在有多种算法相似的情况下,使用 `if...else` 所带来的复杂和难以维护。它的**优点**是算法可以自由切换,同时可以避免多重`if...else`判断,且具有良好的扩展性。 7 | 8 | ## 柒.1.2.2 具体实现和思想 9 | 10 | ​ 假如需要实现一个表单验校,这个在我们开发中几乎都会遇到,对于表单中的各种值,在提交时会根据用户输入的值进行规则验校,如果不符合规则就提示用户,增加产品体验。下面就写一个基础的表单来示范: 11 | 12 | ```javascript 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 60 | ``` 61 | 62 | - 可以看到,我们需要很多`if...else`来进行各种情况的判断,而且我们每增加一种,都需要动表单提交的内部代码,而且复用性几乎为0。下面我们可以使用策略模式改变一下: 63 | 64 | 1.我们可以吧一些规则先抽离到一个公共js中,方便我们在全局使用,这里我就把它放在全局的`checkForm.js`中: 65 | 66 | ```javascript 67 | /* 单独抽离规则 */ 68 | const rules = { 69 | isEmpty: (val, message)=>{ 70 | if(val === '' || val == null) return message 71 | }, 72 | checkLength: (val, message, minLength, maxLength)=>{ 73 | if(String(val).length < minLength || String(val).length > maxLength) return message 74 | }, 75 | checkMobile: (val, message)=>{ 76 | const mobileReg = /^[1][3,4,5,7,8,9][0-9]{9}$/ 77 | if(!mobileReg.test(val)) return message 78 | }, 79 | isEqual: (val, message, valAgain)=>{ 80 | if(val != valAgain) return message 81 | } 82 | } 83 | ``` 84 | 85 | 2.一般情况下,比如手机号、验证码、密码等这些提示在整个项目中基本相同,所以在规则封装完以后,接着我们可以把需要验校的字段再次封装一下,方便全局使用,这就需要结合业务以及自身的情况灵活调整了。 86 | 87 | ```javascript 88 | /* 表单验校封装 */ 89 | const checkForm = { 90 | mobile: (val)=>{ 91 | const mobileIsEmpty = rules.isEmpty(val, '请输入手机号') 92 | const mobileIsLegitimate = rules.checkMobile(val, '请输入正确的手机号') 93 | const message = mobileIsEmpty || mobileIsLegitimate 94 | if(message) return message 95 | }, 96 | code: (val)=>{ 97 | const codeIsEmpty = rules.isEmpty(val, '请输入验证码') 98 | const codeLength = rules.checkLength(val, '请输入6位数验证码', 6, 6) 99 | const message = codeIsEmpty || codeLength 100 | if(message) return message 101 | }, 102 | password: (val, valAgain)=>{ 103 | const passwordIsEmpty = rules.isEmpty(val, '请输入密码') 104 | const passwordCheckLength = rules.checkLength(val, '请输入8-10位密码', 8, 10) 105 | const passwordAgainIsEmpty = rules.isEmpty(valAgain, '请再次输入密码') 106 | const passwordAgainCheckLength = rules.checkLength(valAgain, '再次确认密码请输入8-10位', 8, 10) 107 | const passwirdIsEqual = rules.isEqual(val, '两次输入密码不一致', valAgain) 108 | const message = passwordIsEmpty || passwordCheckLength || passwordAgainIsEmpty || passwordAgainCheckLength || passwirdIsEqual 109 | if(message) return message 110 | } 111 | } 112 | 113 | /*导出*/ 114 | export default checkForm 115 | ``` 116 | 117 | 3.封装完以后我们就可以愉快地使用了: 118 | 119 | ```javascript 120 | import checkForm from '@/utils/checkForm' 121 | 122 | forgotPasswordForm.onsubmit = (e)=>{ 123 | e.preventDefault() 124 | const mobile = document.getElementById('mobile').value 125 | const code = document.getElementById('code').value 126 | const password = document.getElementById('password').value 127 | const passwordAgain = document.getElementById('passwordAgain').value 128 | 129 | const checkMobile = checkForm.mobile(mobile) 130 | const checkCode = checkForm.code(code) 131 | const checkPassword = checkForm.password(password, passwordAgain) 132 | const message = checkMobile || checkCode || checkPassword 133 | if(message){ 134 | alert(message) 135 | }else{ 136 | alert('提交成功') 137 | } 138 | } 139 | ``` 140 | 141 | 从代码数量上来看,可能感觉比之前还要多,但是极大的提高了代码的复用性,我们可以把`rules`和`checkFrom`单独封装,在使用的地方直接引用即可,再也不用写一堆`if...else`了,同时也提高了代码的可读性。 142 | 143 | ## 柒.1.2.3 总结 144 | 145 | 在实际开发中,如果`各种判断条件下的策略相互独立且可复用`、`内部逻辑相对复杂`、`需要灵活组合`的情况下,我们就需要考虑使用策略模式了。 -------------------------------------------------------------------------------- /1/1.3.3.md: -------------------------------------------------------------------------------- 1 | # 壹.3.3 JavaScript元编程:Proxy与Reflect 2 | 3 | ## 01.什么是元编程 4 | 5 | > 元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比,元编程的工作效率更高。 6 | > 7 | > 编写元程序的语言称之为**元语言**,被操作的语言称之为目标语言。 8 | > 9 | > 一门语言同时也是自身的元语言的能力称之为**反射**。 10 | 11 | 以上是百度百科的定义。 12 | 13 | 这么说的话,常见的`eval`是实实在在的元编程了。 14 | 15 | ```javascript 16 | let str = `(function hello(){ 17 | console.log('hello'); 18 | })()`; 19 | eval(str);//>> hello 20 | ``` 21 | 22 | 如上代码,`eval`可以编写计算机程序从而动态生成一段程序,实现**用程序造程序**,这就是**元编程**。 23 | 24 | 而如果是**程序具备自己造自己**的能力,那么这门程序语言就具备**反射**的能力。上面的JavaScript就是自己造自己:通过输入一段JavaScript字符串,造出一段新的JavaScript函数,因此JavaScript具备反射的能力。 25 | 26 | ## 02.Proxy 27 | 28 | Proxy是ES6引入的一个强大的元编程特性,它允许你拦截并自定义JavaScript对象的基本操作,比如属性查找、赋值、枚举、函数调用等。 29 | 30 | ### 基本语法 31 | 32 | ```javascript 33 | const proxy = new Proxy(target, handler); 34 | ``` 35 | 36 | - `target`:要代理的目标对象 37 | - `handler`:处理器对象,包含"陷阱"(trap)方法 38 | 39 | ### 常用的陷阱方法 40 | 41 | ```javascript 42 | const handler = { 43 | // 拦截属性读取 44 | get(target, prop, receiver) { 45 | console.log(`正在读取属性: ${prop}`); 46 | return target[prop]; 47 | }, 48 | 49 | // 拦截属性设置 50 | set(target, prop, value, receiver) { 51 | console.log(`正在设置属性: ${prop} = ${value}`); 52 | target[prop] = value; 53 | return true; // 必须返回true表示设置成功 54 | }, 55 | 56 | // 拦截属性删除 57 | deleteProperty(target, prop) { 58 | console.log(`正在删除属性: ${prop}`); 59 | return delete target[prop]; 60 | }, 61 | 62 | // 拦截函数调用 63 | apply(target, thisArg, argumentsList) { 64 | console.log('正在调用函数'); 65 | return target.apply(thisArg, argumentsList); 66 | } 67 | }; 68 | ``` 69 | 70 | ### 实际应用示例 71 | 72 | #### 1. 数据验证 73 | 74 | ```javascript 75 | const user = { 76 | name: '张三', 77 | age: 25 78 | }; 79 | 80 | const userProxy = new Proxy(user, { 81 | set(target, prop, value) { 82 | if (prop === 'age' && (typeof value !== 'number' || value < 0)) { 83 | throw new Error('年龄必须是正数'); 84 | } 85 | target[prop] = value; 86 | return true; 87 | } 88 | }); 89 | 90 | userProxy.age = 30; // 正常 91 | // userProxy.age = -5; // 抛出错误 92 | ``` 93 | 94 | #### 2. 默认值处理 95 | 96 | ```javascript 97 | const handler = { 98 | get(target, prop) { 99 | if (prop in target) { 100 | return target[prop]; 101 | } 102 | return `属性 ${prop} 不存在`; 103 | } 104 | }; 105 | 106 | const obj = { name: '李四' }; 107 | const proxy = new Proxy(obj, handler); 108 | 109 | console.log(proxy.name); // 李四 110 | console.log(proxy.age); // 属性 age 不存在 111 | ``` 112 | 113 | ## 03.Reflect 114 | 115 | Reflect是ES6引入的一个新的全局对象,它提供了拦截JavaScript操作的方法。这些方法与Proxy的陷阱方法一一对应,让Proxy能够更方便地调用默认行为。 116 | 117 | ### Reflect的主要方法 118 | 119 | ```javascript 120 | // 获取属性值 121 | Reflect.get(target, propertyKey[, receiver]) 122 | 123 | // 设置属性值 124 | Reflect.set(target, propertyKey, value[, receiver]) 125 | 126 | // 删除属性 127 | Reflect.deleteProperty(target, propertyKey) 128 | 129 | // 检查属性是否存在 130 | Reflect.has(target, propertyKey) 131 | 132 | // 获取对象的所有属性 133 | Reflect.ownKeys(target) 134 | 135 | // 创建对象实例 136 | Reflect.construct(target, argumentsList[, newTarget]) 137 | 138 | // 调用函数 139 | Reflect.apply(target, thisArgument, argumentsList) 140 | ``` 141 | 142 | ### 与Proxy结合使用 143 | 144 | ```javascript 145 | const user = { 146 | name: '王五', 147 | age: 28 148 | }; 149 | 150 | const userProxy = new Proxy(user, { 151 | get(target, prop, receiver) { 152 | console.log(`读取属性: ${prop}`); 153 | // 使用Reflect调用默认行为 154 | return Reflect.get(target, prop, receiver); 155 | }, 156 | 157 | set(target, prop, value, receiver) { 158 | console.log(`设置属性: ${prop} = ${value}`); 159 | // 使用Reflect调用默认行为 160 | return Reflect.set(target, prop, value, receiver); 161 | } 162 | }); 163 | 164 | userProxy.name; // 读取属性: name 165 | userProxy.age = 30; // 设置属性: age = 30 166 | ``` 167 | 168 | ### Reflect的优势 169 | 170 | 1. **函数式编程**:所有操作都是函数调用,而不是操作符 171 | 2. **更好的错误处理**:操作失败时返回false而不是抛出异常 172 | 3. **与Proxy配合**:可以轻松调用默认行为 173 | 4. **更可靠的apply**:比Function.prototype.apply更可靠 174 | 175 | ## 04.实际应用场景 176 | 177 | ### 1. 数据绑定和响应式 178 | 179 | ```javascript 180 | function createReactive(obj) { 181 | return new Proxy(obj, { 182 | get(target, prop) { 183 | console.log(`读取了 ${prop}`); 184 | return target[prop]; 185 | }, 186 | set(target, prop, value) { 187 | console.log(`${prop} 从 ${target[prop]} 变为 ${value}`); 188 | target[prop] = value; 189 | // 这里可以触发视图更新 190 | return true; 191 | } 192 | }); 193 | } 194 | 195 | const data = createReactive({ count: 0 }); 196 | data.count; // 读取了 count 197 | data.count = 1; // count 从 0 变为 1 198 | ``` 199 | 200 | ### 2. 日志记录 201 | 202 | ```javascript 203 | function createLoggingProxy(obj) { 204 | return new Proxy(obj, { 205 | get(target, prop) { 206 | console.log(`[GET] ${prop}`); 207 | return target[prop]; 208 | }, 209 | set(target, prop, value) { 210 | console.log(`[SET] ${prop} = ${value}`); 211 | return Reflect.set(target, prop, value); 212 | } 213 | }); 214 | } 215 | ``` 216 | 217 | ### 3. 权限控制 218 | 219 | ```javascript 220 | function createSecureProxy(obj, allowedProps) { 221 | return new Proxy(obj, { 222 | get(target, prop) { 223 | if (allowedProps.includes(prop)) { 224 | return target[prop]; 225 | } 226 | throw new Error(`无权访问属性: ${prop}`); 227 | } 228 | }); 229 | } 230 | ``` 231 | 232 | ## 05.注意事项 233 | 234 | 1. **性能影响**:Proxy会带来一定的性能开销,在性能敏感的场景下要谨慎使用 235 | 2. **浏览器兼容性**:Proxy是ES6特性,需要现代浏览器支持 236 | 3. **调试困难**:Proxy的拦截行为可能让调试变得复杂 237 | 4. **不可撤销**:Proxy一旦创建就无法撤销,只能创建新的代理对象 238 | 239 | ## 06.结语 240 | 241 | Proxy和Reflect为JavaScript提供了强大的元编程能力,让开发者能够拦截和自定义对象的基本操作。它们在数据绑定、验证、日志记录、权限控制等场景下非常有用。但也要注意合理使用,避免过度复杂化代码。 242 | 243 | -------------------------------------------------------------------------------- /1/1.2.12.md: -------------------------------------------------------------------------------- 1 | # 壹.2.12 spread和rest操作符 2 | 3 | ## 壹.2.12.1 spread和rest的区别 4 | 5 | spread和rest运算符都是`...`+`变量/参数`的形式。是spread还是rest,要根据上下文情境来判断。 6 | 7 | ### 1.spread 8 | 9 | 当被用于迭代器中时,它是 spread 操作符: 10 | 11 | ```javascript 12 | console.log(1, ...[2, 3, 4], 5) 13 | // 1 2 3 4 5 14 | console.log([1, ...[2, 3, 4], 5]) 15 | //[1,2,3,4,5] 16 | ``` 17 | 18 | ```javascript 19 | function add(x, y) { 20 | return x + y; 21 | } 22 | 23 | const numbers = [4, 38]; 24 | add(...numbers) // 42 25 | ``` 26 | 27 | spread主要形式是`...[Array]`,表示对数组的展开。 28 | 29 | ### 2.rest 30 | 31 | 当被用于定义函数的参数时,是 rest 操作符: 32 | 33 | ```javascript 34 | function push(...items) { 35 | console.log(items); 36 | } 37 | let a = 4; 38 | push(a, 1, 2, 3) 39 | //[4,1,2,3] 40 | ``` 41 | 42 | rest主要是将函数的多个参数转化成数组,**而且只能放在函数参数的最后一个位置**,否则,比如`(array,...items,other)`会报错。 43 | 44 | 而rest的出现,让已经不被推荐使用的`arguments`彻底寿终正寝了。 45 | 46 | ```javascript 47 | (function fn(...args) { 48 | console.log(args.join()); 49 | console.log([...arguments].join());//spread形式的用法 50 | })([1,2,3]); 51 | //>> 1,2,3 52 | //>> 1,2,3 53 | ``` 54 | 55 | ## 壹.2.12.2 用法示例 56 | 57 | ### 1.添加属性 58 | 59 | 克隆一个对象,同时向\(浅\)拷贝对象添加附加属性。 60 | 在这个示例中,user 被\(浅\)拷贝,password 属性被添加到 userWithPass 中。 61 | 62 | ```javascript 63 | const user = { id: 100, name: 'Howard Moon'} 64 | const userWithPass = { ...user, password: 'Password!' } 65 | 66 | user //>> { id: 100, name: 'Howard Moon' } 67 | userWithPass //>> { id: 100, name: 'Howard Moon', password: 'Password!' } 68 | ``` 69 | 70 | ### 2.对象合并 71 | 72 | 将两个对象合并到一个新对象中。 73 | 74 | ```javascript 75 | const part1 = { id: 100, name: 'Howard Moon' } 76 | const part2 = { id: 100, password: 'Password!' } 77 | 78 | const user1 = { ...part1, ...part2 } 79 | //>> { id: 100, name: 'Howard Moon', password: 'Password!' } 80 | ``` 81 | 82 | 对象也可以使用以下语法合并: 83 | 84 | ```javascript 85 | const partial = { id: 100, name: 'Howard Moon' } 86 | const user = { ...partial, id: 100, password: 'Password!' } 87 | 88 | user //>> { id: 100, name: 'Howard Moon', password: 'Password!' } 89 | ``` 90 | 91 | ### 3.排除对象属性 92 | 93 | 可以结合使用 rest 运算符删除属性。 在下面这个例子里,password 被删除 ,其余的属性作为 rest 返回。 94 | 95 | ```javascript 96 | const noPassword = ({ password, ...rest }) => rest 97 | const user = { 98 | id: 100, 99 | name: 'coffe1891', 100 | password: 'Password!' 101 | } 102 | 103 | noPassword(user) //>> { id: 100, name: 'coffe1891' } 104 | ``` 105 | 106 | ### 4.动态排除属性 107 | 108 | 函数接受一个 prop 作为参数。使用计算对象属性名称,可以从克隆中动态地删除属性。 109 | 110 | ```javascript 111 | const user1 = { 112 | id: 100, 113 | name: 'coffe1891', 114 | password: 'Password!' 115 | } 116 | const removeProperty = prop => ({ [prop]: _, ...rest }) => rest 117 | // ---- ------ 118 | // \ / 119 | // dynamic destructuring 120 | 121 | const removePassword = removeProperty('password') 122 | const removeId = removeProperty('id') 123 | 124 | removePassword(user1) //>> { id: 100, name: 'coffe1891' } 125 | removeId(user1) //>> { name: 'coffe1891', password: 'Password!' } 126 | ``` 127 | 128 | ### 5.对属性进行排序 129 | 130 | 有时性质并不按照我们需要的顺序排列。 使用一些技巧,我们可以将属性推到列表的顶部,或者将它们移到底部。若要将 id 移动到第一个位置,在扩展对象之前将 `id: undefined` 添加到新的 Object 最前面。 131 | 132 | ```javascript 133 | const user3 = { 134 | password: 'Password!', 135 | name: 'Naboo', 136 | id: 300 137 | } 138 | 139 | const organize = object => ({ id: undefined, ...object }) 140 | // ------------- 141 | // / 142 | // move id to the first property 143 | 144 | organize(user3) 145 | //>> { id: 300, password: 'Password!', name: 'Naboo' } 146 | ``` 147 | 148 | 若要将 password 移到最后一个属性,请从对象中解构 password。然后在使用 Rest 操作符后重新设置 password 属性。 149 | 150 | ```javascript 151 | const user3 = { 152 | password: 'Password!', 153 | name: 'Naboo', 154 | id: 300 155 | } 156 | 157 | const organize = ({ password, ...object }) => 158 | ({ ...object, password }) 159 | // -------- 160 | // / 161 | // move password to last property 162 | 163 | organize(user3) 164 | //>> { name: 'Naboo', id: 300, password: 'Password!' } 165 | ``` 166 | 167 | ### 6.默认属性 168 | 169 | 默认属性是仅当它们不包含在原始对象中时才设置的值。 170 | 在本例中,user2 不包含 quotes 属性。 setdefaults 函数确保所有对象都设置了 quotes 属性,否则它将被设置为`[]`。 171 | 当调用 setDefaults \(user2\)时,返回值将包含 quotes 属性: `[]`。 172 | 在调用 setDefaults \(user4\)时,因为 user4 已经有了 quotes 属性,所以不会修改该属性。 173 | 174 | ```javascript 175 | const user2 = { 176 | id: 200, 177 | name: 'Vince Noir' 178 | } 179 | 180 | const user4 = { 181 | id: 400, 182 | name: 'Bollo', 183 | quotes: ["I've got a bad feeling about this..."] 184 | } 185 | 186 | const setDefaults = ({ quotes = [], ...object}) => 187 | ({ ...object, quotes }) 188 | 189 | setDefaults(user2) 190 | //>> { id: 200, name: 'Vince Noir', quotes: [] } 191 | 192 | setDefaults(user4) 193 | //>> { 194 | //>> id: 400, 195 | //>> name: 'Bollo', 196 | //>> quotes: ["I've got a bad feeling about this..."] 197 | //>> } 198 | ``` 199 | 200 | 如果你希望默认值先出现而不是后出现,也可以这样写: 201 | 202 | ```javascript 203 | const setDefaults = ({ ...object}) => ({ quotes: [], ...object }) 204 | ``` 205 | 206 | ### 7.属性重命名 207 | 208 | 通过结合上面的技术,可以创建一个函数来重命名属性。假设有一些大写 ID 的对象属性名应该是小写的 id。 首先从对象解构 ID 然后在对象 Spread 时将其作为 id 添加回去。 209 | 210 | ```javascript 211 | const renamed = ({ ID, ...object }) => ({ id: ID, ...object }) 212 | 213 | const user = { 214 | ID: 500, 215 | name: "Bob Fossil" 216 | } 217 | 218 | renamed(user) //>> { id: 500, name: 'Bob Fossil' } 219 | ``` 220 | 221 | ### 8.添加条件属性 222 | 223 | 在这个例子中,只有当 password 是真实的时候才会添加 password。 224 | 225 | ```javascript 226 | const user = { id: 100, name: 'Howard Moon' } 227 | const password = 'Password!' 228 | const userWithPassword = { 229 | ...user, 230 | id: 100, 231 | ...(password && { password }) 232 | } 233 | 234 | userWithPassword //>> { id: 100, name: 'Howard Moon', password: 'Password!' } 235 | ``` 236 | 237 | -------------------------------------------------------------------------------- /1/1.2.5.md: -------------------------------------------------------------------------------- 1 | # 壹.2.5 面试时高频问到的“闭包” 2 | 3 | 千呼万唤始出来,犹抱琵琶半遮面,这么形容JavaScript的闭包(closure),一点也不过分。闭包是JavaScript最强大的特性,没有之一。很多强大JavaScript库比如jQuery、Vue.js都使用了闭包的特性来实现的。闭包几乎是一线互联网企业面试必问的题。 4 | 5 | ## 壹.2.5.1 看个小例子:循环中的闭包 6 | 7 | 在上一篇文章讲述bind的时候有个小例子,是一个经典的**循环中的闭包**的问题。代码如下: 8 | 9 | ```javascript 10 | for (var i = 1; i <= 5; i++) { 11 | setTimeout(function test() { 12 | console.log(i) //>> 6 6 6 6 6 13 | }, i * 1000); 14 | } 15 | ``` 16 | 17 | 本意是想每隔一秒依次输出“1 2 3 4 5”,结果变成输出“6 6 6 6 6 ”。为什么会这样呢,根据作用域链上变量查找机制,`setTimeout`第一个参数的函数体内的`i`引用了去全局作用域里面的`i`,当for循环完毕后,`i`的值为6,所以输出了“6 6 6 6 6 ”。 18 | 19 | 上一篇文章说过,用bind可以达到正确输出的目的,并提到过本质上使用bind其实也是用到了闭包。但是什么是闭包呢,用代码来感受一下。下面这段代码,寥寥几行,用闭包就轻松达成了目的。 20 | 21 | ```javascript 22 | for (var i = 1; i <= 5; i++) { 23 | (function(j) {//包了一层IIFE形式的函数,这个函数是闭包 24 | setTimeout(function test() {//函数体内的j引用了外层匿名函数的参数j 25 | console.log(j); //>> 1 2 3 4 5 26 | }, j * 1000); 27 | })(i); 28 | } 29 | ``` 30 | 31 | “噢,在循环体内的最外层包了一层IIFE形式的函数,这个函数就是闭包了吗?” 32 | 33 | 你猜对了一半! 34 | 35 | 首先,每个闭包肯定是一个函数。 36 | 37 | 其次,每个内层的函数,需要引用它上层作用域里的参数/变量。比如上面的代码,IIFE函数体内的变量`j`,引用了上层作用域的参数`j`,也即for循环里面的`i`。 38 | 39 | ## 壹.2.5.2 什么是闭包 40 | 41 | ### 1. 闭包的定义 42 | 43 | 关于JavaScript闭包的定义有很多种,每本书、每个作者都有不完全相同的描述,虽然笔者认为**函数就是闭包**这个定义才是最简单最直白的,但其实笔者看到过不下十种定义,到现在一种都记不住。鉴于此,我们干脆不要记住这些五花八门的定义了,只要记住了产生闭包的时机会更实际一些,面试时,把闭包产生的时机告诉面试官就可以了: 44 | 45 | #### 内层的作用域访问它外层函数作用域里的参数/变量/函数时,闭包就产生了。 46 | 47 | 让我们用代码来说事儿吧: 48 | 49 | ```javascript 50 | function func(){//func1引用了它外层的变量a,因此func成为了闭包 51 | let a="coffe"; 52 | function func1(){ 53 | console.log(a);//访问了外层函数func体内的变量a 54 | debugger;//断点 55 | } 56 | 57 | func1(); 58 | } 59 | 60 | func(); 61 | ``` 62 | 63 | 我们在chrome浏览器的“开发者工具”里面的控制台,运行上面的代码,可以很方便看到闭包。 64 | 65 | ![](../.gitbook/assets/image%20%2812%29.png) 66 | 67 | 看上面这个图,Closure出现在Scope一栏里面,所以可以认为**闭包也是一种作用域**。既然闭包也是一种作用域,闭包能够解决经典的“循环中的闭包”的问题,那是不是利用作用域就能解决问题?让笔者想到了关键字`let`,试试看吧,把本文开头的代码改造一下: 68 | 69 | ```javascript 70 | for (var i = 1; i <= 5; i++) { 71 | { 72 | let j = i; 73 | setTimeout(function test() { 74 | console.log(j) //>> 1 2 3 4 5 75 | }, j * 1000); 76 | } 77 | } 78 | ``` 79 | 80 | 果然,用`let`关键字包上一个作用域,也能和闭包一样解决问题达成目的。因此可以说,**闭包是一种作用域,它拷贝了一套外层函数作用域中被访问的参数、变量/函数**,这个拷贝都是浅拷贝(关于浅拷贝详见[《壹.3.1 深拷贝与浅拷贝》](1.3.1.md)\)。 81 | 82 | ### 2. 写成闭包形式有什么好处呢? 83 | 84 | 当然有好处!还是以之前的代码为例,变量`a`类似于高级语言的私有属性,无法被`func`外部作用域访问和修改,只有`func`内部的作用域(含嵌套作用域)可以访问。这样可以实现软件设计上的**封装**,设计出很强大的类库、框架,比如我们常用的jQuery、AngularJS、Vue.js。 85 | 86 | 看一个ES6出现之前最常见的模块化封装的例子: 87 | 88 | ```javascript 89 | //定义一个模块 90 | function module(n) { 91 | //私有属性 92 | let name = n; 93 | //私有方法 94 | function getModuleName() { 95 | return name; 96 | } 97 | //私有方法 98 | function someMethod() { 99 | console.log("coffe1891"); 100 | } 101 | //以一个对象的形式返回 102 | return { 103 | getModuleName: getModuleName, 104 | getXXX: someMethod 105 | }; 106 | } 107 | 108 | let myapp = module("myModule");//定义一个模块 109 | console.log(myapp.getModuleName()); //>> myModule 110 | console.log(myapp.getXXX()); //>> coffe1891 111 | ``` 112 | 113 | ### 3. 闭包有什么缺点吗? 114 | 115 | javascript中的垃圾回收(GC)规则是这样的:如果对象不再被引用,或者对象互相引用形成数据孤岛后且没有被孤岛之外的其他对象引用,那么这些对象将会被JS引擎的垃圾回收器回收;反之,这些对象一直会保存在内存中。 116 | 117 | 由于闭包会引用包含它的外层函数作用域里的变量/函数,因此会比其他非闭包形式的函数占用更多内存。当外层函数执行完毕退出函数调用栈(call stack)的时候,外层函数作用域里变量因为被引用着,可能并不会被JS引擎的垃圾回收器回收,因而会引起内存泄漏。过度使用闭包,会导致内存占用过多,甚至内存泄漏。 118 | 119 | ```javascript 120 | function A(){ 121 | var count = 0; 122 | function B(){ 123 | count ++; 124 | console.log(count); 125 | } 126 | return B;//函数B保持了对count的引用 127 | } 128 | var b = A(); 129 | b();//>> 1 130 | b();//>> 2 131 | b();//>> 3 132 | ``` 133 | 134 | `count`是函数A中的一个变量,它的值在函数B中被改变,B每执行一次,`count`的值就在原来的基础上累加1。因此,函数A中的`count`一直保存在内存中,并没有因为函数A执行完毕退出函数调用栈后被JS引擎的垃圾回收器回收掉。 135 | 136 | 避免闭包导致内存泄漏的解决方法是,在函数A执行完毕退出函数调用栈之前,将不再使用的局部变量全部删除或者赋值为null。 137 | 138 | ## 壹.2.5.3 其他使用场景介绍 139 | 140 | 除了上面介绍过的循环中的闭包、模块化封装之外,闭包还有一些其他写法。 141 | 142 | ### 1. 返回一个新函数 143 | 144 | ```javascript 145 | function sayHello2(name) { 146 | var text = "Hello " + name; // 局部变量 147 | 148 | var sayAlert = function() { 149 | console.log(text); 150 | }; 151 | 152 | return sayAlert; 153 | } 154 | 155 | var say2 = sayHello2("coffe1891"); 156 | say2(); //>> Hello coffe1891 157 | ``` 158 | 159 | 调用`sayHello2()`函数返回了`sayAlert`,赋值给`say2`。注意`say2`是一个引用变量,指向一个函数本身,而不是指向一个变量。 160 | 161 | ### 2. 扩展全局对象的方法 162 | 163 | 下面这种利用闭包扩展全局对象,可以有效地保护私有变量,形成一定的封装、持久性。 164 | 165 | ```javascript 166 | function setupSomeGlobals() { 167 | //私有变量 168 | var num = 666; 169 | 170 | gAlertNumber = function() {//没有用var和let关键字声明,会成为全局对象的方法 171 | console.log(num); 172 | }; 173 | 174 | gIncreaseNumber = function() { 175 | num++; 176 | }; 177 | 178 | gSetNumber = function(x) { 179 | num = x; 180 | }; 181 | } 182 | 183 | setupSomeGlobals(); 184 | gAlertNumber(); //>> 666 185 | 186 | gIncreaseNumber(); 187 | gAlertNumber(); //>> 667 188 | 189 | gSetNumber(1891); 190 | gAlertNumber(); //>> 1891 191 | ``` 192 | 193 | 三个全局函数`gAlertNumber`,`gIncreaseNumber`,`gSetNumber`指向了同一个闭包,因为它们是在同一次`setupSomeGlobals()`调用中声明的。它们所指向的闭包是与`setupSomeGlobals()`函数关联一个作用域,该作用域包括了`num`变量的拷贝。也就是说,这三个函数操作的是同一个`num`变量。 194 | 195 | ### 3. 延长局部变量的生命 196 | 197 | 日常开发时,Image对象经常被用于数据统计的上报,示例代码如下: 198 | 199 | ```javascript 200 | var report = function(src) { 201 | var img = new Image(); 202 | img.src = src; 203 | } 204 | report('http://www.xxx.com/getClientInfo');//把客户端信息上报数据 205 | ``` 206 | 207 | 这段代码在运行时,发现在一些低版本浏览器上存在bug,会丢失部分数据上报。原因是Image对象是`report`函数中的局部变量,当`report`函数调用结束后,Image对象随即被JS引擎垃圾回收器回收,而此时可能还没来得及发出http请求,所以可能导致此次上报数据的请求失败。 208 | 209 | 怎么办呢?我们可以使用闭包把Image对象封闭起来,就可以解决数据丢失的问题,代码如下: 210 | 211 | ```javascript 212 | var report = (function() { 213 | var imgs = [];//在内存里持久化 214 | return function(src) { 215 | var img = new Image(); 216 | imgs.push(img);//引用局部变量imgs 217 | img.src = src; 218 | } 219 | }()); 220 | report('http://www.xxx.com/getClientInfo');//把客户端信息上报数据 221 | ``` 222 | 223 | -------------------------------------------------------------------------------- /1/1.4.2.md: -------------------------------------------------------------------------------- 1 | # 壹.4.2 页面重排(Reflow)与重绘(Repaint\) 2 | 3 | ## 重排(reflow): 4 | 5 | ### 概念: 6 | 7 | ​ 当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 8 | 9 | 重排也叫回流,简单的说就是重新生成布局,重新排列元素。 10 | 11 | ### 哪些情况会发生重排: 12 | 13 | ​ 1. 页面初始渲染,这是开销最大的一次重排 14 | 15 | ​ 2. 添加/删除可见的DOM元素 16 | 17 | ​ 3. 改变元素位置 18 | 19 | ​ 4. 改变元素尺寸,比如边距、填充、边框、宽度和高度等 20 | 21 | ​ 5. 改变元素内容,比如文字数量,图片大小等 22 | 23 | ​ 6. 改变元素字体大小 24 | 25 | ​ 7. 改变浏览器窗口尺寸,比如resize事件发生时 26 | 27 | ​ 8. 激活CSS伪类(例如:`:hover`) 28 | 29 | ​ 9. 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow 30 | 31 | 10. 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 `getComputedStyle`方法,或者IE里的 `currentStyle` 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。 32 | 33 | 34 | 35 | | 常见引起重排属性和方法 | -- | -- | -- | -- | -- | 36 | | :--------------------: | :----------: | :----------------: | :---------------------: | :----------------------: | :----------: | 37 | | width | height | margin | overflow | padding | display | 38 | | border | border-width | position | font-size | vertical-align | min-height | 39 | | clientWidth | clientHeight | clientTop | clientLeft | offsetWidth | offsetHeight | 40 | | offsetTop | offsetLeft | scrollWidth | scrollHeight | scrollTop | scrollLeft | 41 | | scrollIntoView() | scrollTo() | getComputedStyle() | getBoundingClientRect() | scrollIntoViewIfNeeded() | | 42 | 43 | ### 重排影响的范围: 44 | 45 | 由于浏览器渲染界面是基于流式布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种: 46 | 47 | - 全局范围:从根节点html开始对整个渲染树进行重新布局。 48 | - 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局 49 | 50 | #### 全局范围重排: 51 | 52 | ```html 53 | 54 |
55 |

hello

56 |

Name:ZhangSan

57 |
hi
58 |
    59 |
  1. coding
  2. 60 |
  3. loving
  4. 61 |
62 |
63 | 64 | ``` 65 | 66 | 当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响。 67 | 68 | #### 局部范围重排: 69 | 70 | 用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。 71 | 72 | ## 重绘(Repaints): 73 | 74 | ### 概念: 75 | 76 | ​ 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。 77 | 78 | ### 常见的引起重绘的属性: 79 | 80 | | 属性: | -- | -- | -- | 81 | | --------------- | ---------------- | ------------------- | ----------------- | 82 | | color | border-style | visibility | background | 83 | | text-decoration | background-image | background-position | background-repeat | 84 | | outline-color | outline | outline-style | border-radius | 85 | | outline-width | box-shadow | background-size | | 86 | 87 | ### 重排优化建议: 88 | 89 | ​ 重排的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓。通过减少重排的负面影响来提高用户体验的最简单方式就是尽可能的减少重排次数,重排范围。下面是一些行之有效的建议,大家可以用来参考。 90 | 91 | ### 减少重排范围 92 | 93 | 我们应该尽量以局部布局的形式组织html结构,尽可能小的影响重排的范围。 94 | 95 | - 尽可能在低层级的DOM节点上,而不是像上述全局范围的示例代码一样,如果你要改变p的样式,class就不要加在div上,通过父元素去影响子元素不好。 96 | - 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。 97 | 98 | ### 减少重排次数 99 | 100 | #### 1.样式集中改变 101 | 102 | ​ 不要频繁的操作样式,对于一个静态页面来说,明智且可维护的做法是更改类名而不是修改样式,对于动态改变的样式来说,相较每次微小修改都直接触及元素,更好的办法是统一在 `cssText` 变量中编辑。虽然现在大部分现代浏览器都会有 `Flush` 队列进行渲染队列优化,但是有些老版本的浏览器比如IE6的效率依然低下。 103 | 104 | ```js 105 | // bad 106 | var left = 10; 107 | var top = 10; 108 | el.style.left = left + "px"; 109 | el.style.top = top + "px"; 110 | 111 | // 当top和left的值是动态计算而成时... 112 | // better 113 | el.style.cssText += "; left: " + left + "px; top: " + top + "px;"; 114 | 115 | // better 116 | el.className += " className"; 117 | ``` 118 | 119 | #### 2.分离读写操作 120 | 121 | ​ DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。 122 | 123 | ```js 124 | // bad 强制刷新 触发四次重排+重绘 125 | div.style.left = div.offsetLeft + 1 + 'px'; 126 | div.style.top = div.offsetTop + 1 + 'px'; 127 | div.style.right = div.offsetRight + 1 + 'px'; 128 | div.style.bottom = div.offsetBottom + 1 + 'px'; 129 | 130 | 131 | // good 缓存布局信息 相当于读写分离 触发一次重排+重绘 132 | var curLeft = div.offsetLeft; 133 | var curTop = div.offsetTop; 134 | var curRight = div.offsetRight; 135 | var curBottom = div.offsetBottom; 136 | 137 | div.style.left = curLeft + 1 + 'px'; 138 | div.style.top = curTop + 1 + 'px'; 139 | div.style.right = curRight + 1 + 'px'; 140 | div.style.bottom = curBottom + 1 + 'px'; 141 | 142 | ``` 143 | 144 | 原来的操作会导致四次重排,读写分离之后实际上只触发了一次重排,这都得益于浏览器的渲染队列机制: 145 | 146 | > 当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。 147 | 148 | #### 3.将 DOM 离线 149 | 150 | “离线”意味着不在当前的 DOM 树中做修改,我们可以这样做: 151 | 152 | - 使用 display:none 153 | 154 | 一旦我们给元素设置 `display:none` 时(只有一次重排重绘),元素便不会再存在在渲染树中,相当于将其从页面上“拿掉”,我们之后的操作将不会触发重排和重绘,添加足够多的变更后,通过 `display`属性显示(另一次重排重绘)。通过这种方式即使大量变更也只触发两次重排。另外,`visibility : hidden` 的元素只对重绘有影响,不影响重排。 155 | 156 | - 通过 [documentFragment](https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FDocumentFragment) 创建一个 `dom` 碎片,在它上面批量操作 `dom`,操作完成之后,再添加到文档中,这样只会触发一次重排。 157 | 158 | - 复制节点,在副本上工作,然后替换它! 159 | 160 | #### 4.使用 absolute 或 fixed 脱离文档流 161 | 162 | 使用绝对定位会使的该元素单独成为渲染树中 `body` 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排。 163 | 164 | #### 5.优化动画 165 | 166 | - 可以把动画效果应用到 `position`属性为 `absolute` 或 `fixed` 的元素上,这样对其他元素影响较小。 167 | 168 | 动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量: 比如实现一个动画,以1个像素为单位移动这样最平滑,但是Layout就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多 169 | 170 | - 启用GPU加速 `GPU` 硬件加速是指应用 `GPU` 的图形性能对浏览器中的一些图形操作交给 `GPU` 来完成,因为 `GPU` 是专门为处理图形而设计,所以它在速度和能耗上更有效率。 171 | 172 | `GPU` 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。 173 | 174 | ```css 175 | /* 176 | * 根据上面的结论 177 | * 将 2d transform 换成 3d 178 | * 就可以强制开启 GPU 加速 179 | * 提高动画性能 180 | */ 181 | div { 182 | transform: translate3d(10px, 10px, 0); 183 | } 184 | 185 | ``` 186 | 187 | ### 小结: 188 | 189 | - 渲染的三个阶段 Layout,Paint,Composite Layers。 Layout:重排,又叫回流。 Paint:重绘,重排重绘这些步骤都是在 CPU 中发生的。 Compostite Layers:CPU 把生成的 BitMap(位图)传输到 GPU,渲染到屏幕。 190 | - CSS3 就是在 GPU 发生的:Transform Opacity。在 GPU 发生的属性比较高效。所以 CSS3 性能比较高。 191 | 192 | ## 参考文献 193 | 194 | {% hint style="info" %} 195 | https://juejin.cn/post/6844904083212468238#heading-3 196 | https://segmentfault.com/a/1190000017491520 197 | 198 | {% endhint %} 199 | 200 | -------------------------------------------------------------------------------- /1/1.4.3.md: -------------------------------------------------------------------------------- 1 | # 壹.4.3 DOM、Shadow DOM、Virtual DOM 2 | 3 | 在前端开发中,DOM(Document Object Model)是一个核心概念,而Shadow DOM和Virtual DOM则是现代前端框架中的重要技术。本文将详细介绍这三者的概念、区别和应用场景。 4 | 5 | ## 壹.4.3.1 DOM(Document Object Model) 6 | 7 | ### 什么是DOM 8 | 9 | DOM是HTML文档的编程接口,它将HTML文档表示为一个树形结构,每个HTML元素都是树中的一个节点。DOM提供了操作HTML元素的方法和属性,让JavaScript能够动态地修改网页内容。 10 | 11 | ### DOM树结构 12 | 13 | ```html 14 | 15 | 16 | 17 | 页面标题 18 | 19 | 20 |
21 |

标题

22 |

段落内容

23 |
24 | 25 | 26 | ``` 27 | 28 | 对应的DOM树结构: 29 | ``` 30 | document 31 | ├── html 32 | ├── head 33 | │ └── title 34 | └── body 35 | └── div#container 36 | ├── h1 37 | └── p 38 | ``` 39 | 40 | ### DOM操作示例 41 | 42 | ```javascript 43 | // 获取元素 44 | const container = document.getElementById('container'); 45 | const title = document.querySelector('h1'); 46 | 47 | // 修改内容 48 | title.textContent = '新标题'; 49 | 50 | // 创建新元素 51 | const newParagraph = document.createElement('p'); 52 | newParagraph.textContent = '新段落'; 53 | container.appendChild(newParagraph); 54 | 55 | // 删除元素 56 | const oldParagraph = document.querySelector('p'); 57 | container.removeChild(oldParagraph); 58 | ``` 59 | 60 | ### DOM的优缺点 61 | 62 | **优点:** 63 | - 标准化的API,浏览器原生支持 64 | - 直接操作真实DOM,响应及时 65 | - 丰富的操作方法和属性 66 | 67 | **缺点:** 68 | - 操作频繁时性能较差 69 | - 容易产生内存泄漏 70 | - 跨浏览器兼容性问题 71 | 72 | ## 壹.4.3.2 Shadow DOM 73 | 74 | ### 什么是Shadow DOM 75 | 76 | Shadow DOM是Web Components标准的一部分,它允许开发者创建封装的DOM树,这些DOM树与主文档的DOM树分离,具有自己的作用域。Shadow DOM主要用于创建可重用的Web组件。 77 | 78 | ### Shadow DOM的基本概念 79 | 80 | ```javascript 81 | // 创建Shadow DOM 82 | const host = document.getElementById('host'); 83 | const shadow = host.attachShadow({ mode: 'open' }); 84 | 85 | // 在Shadow DOM中添加内容 86 | shadow.innerHTML = ` 87 | 93 |
这是Shadow DOM中的内容
94 | `; 95 | ``` 96 | 97 | ### Shadow DOM的特点 98 | 99 | 1. **封装性**:Shadow DOM中的样式和脚本不会影响外部文档 100 | 2. **作用域隔离**:CSS选择器无法穿透Shadow DOM边界 101 | 3. **可重用性**:可以创建完全独立的组件 102 | 103 | ### 实际应用示例 104 | 105 | ```javascript 106 | class CustomButton extends HTMLElement { 107 | constructor() { 108 | super(); 109 | 110 | // 创建Shadow DOM 111 | const shadow = this.attachShadow({ mode: 'open' }); 112 | 113 | // 添加样式和内容 114 | shadow.innerHTML = ` 115 | 131 | 134 | `; 135 | } 136 | } 137 | 138 | // 注册自定义元素 139 | customElements.define('custom-button', CustomButton); 140 | ``` 141 | 142 | 使用方式: 143 | ```html 144 | 点击我 145 | ``` 146 | 147 | ## 壹.4.3.3 Virtual DOM 148 | 149 | ### 什么是Virtual DOM 150 | 151 | Virtual DOM是一个轻量级的JavaScript对象,它是对真实DOM的抽象表示。Virtual DOM的主要目的是提高DOM操作的性能,通过比较虚拟DOM的差异,批量更新真实DOM。 152 | 153 | ### Virtual DOM的工作原理 154 | 155 | 1. **创建虚拟DOM**:将真实DOM转换为JavaScript对象 156 | 2. **状态变化**:当数据发生变化时,创建新的虚拟DOM 157 | 3. **差异比较**:比较新旧虚拟DOM的差异 158 | 4. **批量更新**:将差异应用到真实DOM 159 | 160 | ### 简单的Virtual DOM实现 161 | 162 | ```javascript 163 | // 创建虚拟DOM元素 164 | function createElement(type, props, ...children) { 165 | return { 166 | type, 167 | props: { 168 | ...props, 169 | children: children.map(child => 170 | typeof child === 'string' ? createTextElement(child) : child 171 | ) 172 | } 173 | }; 174 | } 175 | 176 | function createTextElement(text) { 177 | return { 178 | type: 'TEXT_ELEMENT', 179 | props: { 180 | nodeValue: text, 181 | children: [] 182 | } 183 | }; 184 | } 185 | 186 | // 渲染虚拟DOM到真实DOM 187 | function render(element, container) { 188 | const dom = element.type === 'TEXT_ELEMENT' 189 | ? document.createTextNode('') 190 | : document.createElement(element.type); 191 | 192 | // 设置属性 193 | Object.keys(element.props) 194 | .filter(key => key !== 'children') 195 | .forEach(name => { 196 | dom[name] = element.props[name]; 197 | }); 198 | 199 | // 递归渲染子元素 200 | element.props.children.forEach(child => render(child, dom)); 201 | 202 | container.appendChild(dom); 203 | } 204 | 205 | // 使用示例 206 | const element = createElement( 207 | 'div', 208 | { id: 'app' }, 209 | createElement('h1', null, 'Hello Virtual DOM'), 210 | createElement('p', null, '这是一个简单的实现') 211 | ); 212 | 213 | render(element, document.getElementById('root')); 214 | ``` 215 | 216 | ### Virtual DOM的优势 217 | 218 | 1. **性能提升**:批量更新DOM,减少重排重绘 219 | 2. **跨平台**:虚拟DOM可以渲染到不同平台 220 | 3. **开发体验**:声明式编程,关注数据而非DOM操作 221 | 4. **状态管理**:更容易管理应用状态 222 | 223 | ### Virtual DOM的缺点 224 | 225 | 1. **内存占用**:需要额外的内存存储虚拟DOM 226 | 2. **首次渲染**:需要先创建虚拟DOM,再渲染到真实DOM 227 | 3. **学习成本**:需要理解虚拟DOM的概念和工作原理 228 | 229 | ## 壹.4.3.4 三者的关系与区别 230 | 231 | ### 关系图 232 | 233 | ``` 234 | 真实DOM ←→ Virtual DOM ←→ 应用状态 235 | ↑ 236 | Shadow DOM (封装组件) 237 | ``` 238 | 239 | ### 主要区别 240 | 241 | | 特性 | DOM | Shadow DOM | Virtual DOM | 242 | |------|-----|------------|-------------| 243 | | 作用 | 操作真实DOM | 创建封装组件 | 提高渲染性能 | 244 | | 性能 | 直接操作,性能较低 | 封装隔离,性能较好 | 批量更新,性能最佳 | 245 | | 使用场景 | 原生JavaScript开发 | Web Components | 现代前端框架 | 246 | | 复杂度 | 简单直接 | 中等 | 复杂 | 247 | 248 | ## 壹.4.3.5 实际应用建议 249 | 250 | ### 何时使用DOM 251 | 252 | - 简单的页面交互 253 | - 原生JavaScript项目 254 | - 需要直接控制DOM的场景 255 | 256 | ### 何时使用Shadow DOM 257 | 258 | - 创建可重用组件 259 | - 需要样式封装 260 | - 构建Web Components 261 | 262 | ### 何时使用Virtual DOM 263 | 264 | - 复杂的前端应用 265 | - 需要频繁更新DOM 266 | - 使用现代前端框架 267 | 268 | ## 壹.4.3.6 结语 269 | 270 | DOM、Shadow DOM和Virtual DOM各有其适用场景。理解它们的特点和区别,能够帮助我们在不同的项目中选择合适的技术方案。在实际开发中,这些技术往往是结合使用的,而不是相互排斥的。 271 | 272 | ### 推荐阅读 273 | 274 | {% hint style="info" %} 275 | [What is the Shadow DOM?](https://bitsofco.de/what-is-the-shadow-dom/) 276 | {% endhint %} 277 | 278 | {% hint style="info" %} 279 | [Virtual DOM and Internals](https://reactjs.org/docs/faq-internals.html) 280 | {% endhint %} 281 | 282 | {% hint style="info" %} 283 | [全面理解虚拟DOM,实现虚拟DOM](http://foio.github.io/virtual-dom/) 284 | {% endhint %} 285 | 286 | -------------------------------------------------------------------------------- /1/1.4.4.md: -------------------------------------------------------------------------------- 1 | # 壹.4.4 V8引擎是如何工作的 2 | 3 | ## 01. 什么是 V8引擎 4 | 5 | [V8](https://link.juejin.cn?target=https%3A%2F%2Fv8.dev%2F) 是 Google 发布的开源的高性能 JavaScript 和 WebAssembly 引擎,采用 C++ 编写。它被使用在 Chrome 浏览器,Node.js 等环境中。V8 可以独立运行,也可以嵌入在任何 C++ 应用中运行。 6 | 7 | V8 引擎内部有许多小的模块组成。这里我们只需要了解其中最常用的四个模块即可。 8 | 9 | - **Parser(解析器)** 10 | - **Ignition(解释器)** 11 | - **TurboFan(编译器)** 12 | - **Orinoco(垃圾回收)** 13 | 14 | 下图展示了 V8 引擎工作的基本流程: 15 | 16 | ![](https://img.wenhairu.com/images/2022/06/12/7Jd4B.jpg) 17 | 18 | - 首先 V8 引擎会扫描所有的源代码,进行词法分析,生成 Tokens; 19 | - Parser 解析器根据 Tokens 生成 AST; 20 | - Ignition 解释器将 AST 转换为字节码,并解释执行; 21 | - TurboFan 编译器负责将热点函数优化编译为机器指令执行; 22 | 23 | ## 词法分析 24 | 25 | **什么是词法分析?** 26 | 27 | 词法分析(Tokenizing/Lexing)其作用是将一行行的源码拆解成一个个 token。所谓**词法单元 token**,指的是语法上不可能再分的、最小的单个字符或字符串。 28 | 29 | ECMAScript 中明确定义了 Token 包含的内容。 30 | 31 | ##### CommonToken: 32 | 33 | - IdentifierName 34 | 35 | - Punctuator 36 | 37 | - NunericLiteral 38 | 39 | - StringLiteral 40 | 41 | - Template 42 | 43 | 44 | 45 | 我们来看下`var a = 2;` 这句代码经过词法分析后会被分解出哪些 tokens ? 46 | 47 | ![](https://img.wenhairu.com/images/2022/06/13/7JxIK.jpg) 48 | 49 | 从上图中可以看到,这句代码最终被分解出了五个词法单元: 50 | 51 | - `var` 关键字 52 | - `a` 标识符 53 | - `=` 运算符(符号) 54 | - `2` 数值 55 | - ` ;`分号(符号) 56 | 57 | > Tokens 在线查看网站:[esprima.org/demo/parse.…](https://link.juejin.cn?target=https%3A%2F%2Fesprima.org%2Fdemo%2Fparse.html%23) 58 | 59 | ## 语法分析 60 | 61 | ### Parser 62 | 63 | Parser 是 V8 的解析器,负责根据生成的 Tokens 进行语法分析。Parser 的主要工作包括: 64 | 65 | - **分析语法错误**:遇到错误的语法会抛出异常; 66 | - **输出 AST**:将词法分析输出的词法单元流(数组)转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树——抽象语法树(Abstract Syntax Tree, AST); 67 | - **确定词法作用域**; 68 | 69 | - **生成执行上下文**; 70 | 71 | **什么是抽象语法树(Abstract Syntax Tree, AST)?** 72 | 73 | 还是上面的例子,我们来看下 `var a = 2;` 经过语法分析后生成的 AST 是什么样子的: 74 | 75 | ![](https://img.wenhairu.com/images/2022/06/12/7JBAR.jpg) 76 | 77 | ![](https://img.wenhairu.com/images/2022/06/13/7K6h3.jpg) 78 | 79 | 可以看到这段程序的类型是 VariableDeclaration,也就是说这段代码是用来声明变量的。 80 | 81 | > AST 在线查看网站:[astexplorer.net/](https://link.juejin.cn?target=https%3A%2F%2Fastexplorer.net%2F) 82 | 83 | AST 的结构和代码的结构非常相似,其实你也可以把 AST 看成代码的结构化表示,编译器或者解释器后续的工作都需要依赖于 AST,而不是源代码。 84 | 85 | > AST 是非常重要的一种数据结构,在很多项目中有着广泛的应用。其中最著名的一个项目就是 Babel。Babel 是一个被广泛使用的代码转码器,可以将 ES6 代码转为 ES5 代码,这意味着你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持 ES6。Babel 的工作原理就是先将 ES6 源码转换为 AST,然后再将 ES6 语法的 AST 转换为 ES5 语法的 AST,最后利用 ES5 的 AST 生成 JavaScript 源代码。 除了 Babel 外,还有 ESLint 也使用 AST。ESLint 是一个用来检查 JavaScript 编写规范的插件,其检测流程也是需要将源码转换为 AST,然后再利用 AST 来检查代码规范化的问题。 86 | 87 | ### Pre-Parser 88 | 89 | **什么是预解析 Pre-Parser?** 90 | 91 | 我们先来看看下面这段代码: 92 | 93 | ```js 94 | function foo () { 95 | console.log('function foo') 96 | } 97 | 98 | function bar () { 99 | console.log('function bar') 100 | } 101 | 102 | foo() 103 | ``` 104 | 105 | 上面这段代码中,如果使用 Parser 解析后,会生成 foo 函数 和 bar 函数的 AST。然而 bar 函数并没有被调用,所以生成 bar 函数的 AST 实际上是没有任何意义且浪费时间的。那么有没有办法解决呢?此时就用到了 Pre-Parser 技术。 106 | 107 | 在 V8 中有两个解析器用于解析 JavaScript 代码,分别是 Parser 和 Pre-Parser 。 108 | 109 | - Parser 解析器又称为 full parser(全量解析) 或者 eager parser(饥饿解析)。它会解析所有**立即执行**的代码,包括语法检查,生成 AST,以及确定词法作用域。 110 | - Pre-Parser 又称为惰性解析,它只解析**未被立即执行**的代码(如函数),不生成 AST ,只确定作用域,以此来提高性能。当预解析后的代码开始执行时,才进行 Parser 解析。 111 | 112 | 我们还是以示例来说明: 113 | 114 | ```js 115 | function foo() { 116 | console.log('a'); 117 | function inline() { 118 | console.log('b') 119 | } 120 | } 121 | 122 | (function bar() { 123 | console.log('c') 124 | })(); 125 | 126 | foo(); 127 | ``` 128 | 129 | 1. 当 V8 引擎遇到 foo 函数声明时,发现它未被立即执行,就会采用 Pre-Parser 对其进行解析(inline 函数同)。 130 | 2. 当 V8 遇到`(function bar() {console.log(c)})()`时,它会知道这是一个立即执行表达式(IIFE),会立即被执行,所以会使用 Parser 对其解析。 131 | 3. 当 foo 函数被调用时,会使用 Parser 对 foo 函数进行解析,此时会对 inline 函数再进行一次预解析,也就是说 inline 函数被预解析了两次。如果嵌套层级较深,那么内层的函数会被预解析多次,所以在写代码时,**尽可能避免嵌套多层函数**,会影响性能。 132 | 133 | ## Ignition 134 | 135 | Ignition 是 V8 的解释器,它负责的工作包括: 136 | 137 | - 将 AST 转换为中间代码(字节码 Bytecode) 138 | - 逐行解释执行字节码:在该阶段,就已经可以开始执行 JavaScript 代码了。 139 | 140 | **什么是字节码?** 141 | 142 | 字节码(Bytecode)是介于 AST 和机器码之间的一种中间码,它比机器码更抽象,也更轻量,需要直译器转译后才能成为机器码。 143 | 144 | > 早期版本的 V8 ,并没有生成中间字节码的过程,而是直接将 AST 转换为机器码,由于执行机器码的效率是非常高效的,所以这种方式在发布后的一段时间内运行效果是非常好的。但是随着 Chrome 在手机上的广泛普及,特别是运行在 512M 内存的手机上,内存占用问题也暴露出来了,因为 V8 需要消耗大量的内存来存放转换后的机器码。为了解决内存占用问题,V8 团队大幅重构了引擎架构,引入字节码,并且抛弃了之前的编译器,最终花了将进四年的时间,实现了现在的这套架构。 145 | 146 | ![](https://img.wenhairu.com/images/2022/06/13/7KAEo.jpg) 147 | 148 | ![](https://img.wenhairu.com/images/2022/06/13/7KNwH.jpg) 149 | 150 | 从图中可以看出,机器码所占用的空间远远超过了字节码,所以使用字节码可以减少系统内存的占用。 151 | 152 | ## TurboFan 153 | 154 | TurboFan 是 V8 的优化编译器,负责将字节码和一些分析数据作为输入并生成优化的机器代码。 155 | 156 | 上面我们说到,当 Ignition 将 JavaScript 代码转换为字节码后,程序就可以执行了,那么 TurboFan 还有什么用呢? 157 | 158 | 我们再来看下 V8 的工作流程图: 159 | 160 | ![](https://img.wenhairu.com/images/2022/06/13/7KYRq.jpg) 161 | 162 | 我们主要关注 Ignition 和 TurboFan 的交互: 163 | 164 | ![](https://img.wenhairu.com/images/2022/06/13/7KfBX.jpg) 165 | 166 | 当 Ignition 开始执行 JavaScript 代码后,V8 会一直观察 JavaScript 代码的执行情况,并记录执行信息,如每个函数的执行次数、每次调用函数时,传递的参数类型等。 167 | 168 | 如果一个函数被调用的次数超过了内设的阈值,监视器就会将当前函数标记为热点函数(Hot Function),并将该函数的字节码以及执行的相关信息发送给 TurboFan。TurboFan 会根据执行信息做出一些进一步优化此代码的假设,在假设的基础上将字节码编译为优化的机器代码。如果假设成立,那么当下一次调用该函数时,就会执行优化编译后的机器代码,以提高代码的执行性能。 169 | 170 | > V8 的解释器和编译器的取名也很有意思。解释器 Ignition 是点火器的意思,编译 TurboFan 是涡轮增压的意思,寓意着代码启动时通过点火器慢慢发动,一旦启动,涡轮增压介入,其执行效率随着执行时间越来越高效率,因为热点代码都被编译器 TurboFan 转换了机器码,直接执行机器码就省去了字节码“翻译”为机器码的过程。我们把这种技术称为即时编译(JIT) 171 | 172 | ![](https://img.wenhairu.com/images/2022/06/13/7KCmp.jpg) 173 | 174 | 那如果假设不成立呢?不知道你们有没有注意到上图中有一条由 optimized code 指向 bytecode 的红色指向线。此过程叫做 deoptimize(优化回退),将优化编译后的机器代码还原为字节码。 175 | 176 | 读到这里,你可能有些疑惑:这个假设是什么假设呢?以及为什么要优化回退?我们来看下面的例子。 177 | 178 | ```js 179 | function sum (a, b) { 180 | return a + b; 181 | } 182 | ``` 183 | 184 | 我们都知道 JavaScript 是基于动态类型的,a 和 b 可以是任意类型数据,当执行 sum 函数时,Ignition 解释器会检查 a 和 b 的数据类型,并相应地执行加法或者连接字符串的操作。 185 | 186 | 如果 sum 函数被调用多次,每次执行时都要检查参数的数据类型是很浪费时间的。此时 TurboFan 就出场了。它会分析监视器收集的信息,如果以前每次调用 sum 函数时传递的参数类型都是数字,那么 TurboFan 就预设 sum 的参数类型是数字类型,然后将其编译为机器指令。 187 | 188 | 但是当某一次的调用传入的参数不再是数字时,表示 TurboFan 的假设是错误的,此时优化编译生成的机器代码就不能再使用了,于是就需要进行优化回退。 189 | 190 | ## Orinoco 191 | 192 | Orinoco 是 V8 的垃圾回收模块(garbage collector),负责将程序不再需要的内存空间回收(标记清除法); 193 | 194 | # 参考 195 | 196 | [JavaScript 引擎 V8 执行流程概述](https://mp.weixin.qq.com/s/t__Jqzg1rbTlsCHXKMwh6A ) 197 | 198 | [视野前端(二)V8引擎是如何工作的](https://mp.weixin.qq.com/s?__biz=MzI4NjE3MzQzNg==&mid=2649865842&idx=1&sn=b595dbd13328ce65aa265eb21ba4ea85&chksm=f3e5efe1c49266f7f86bf899cd5f80d4864e21bbedc3f03170762c3f44f573d0c33eb44399de&mpshare=1&scene=24&srcid=&sharer_sharetime=1583838316032&sharer_shareid=3ebcb61ec5c32d188c9235d1a89dcfbf#rd ) 199 | 200 | [What is V8?](https://v8.dev/) 201 | 202 | [JavaScript 引擎(V8)是如何工作的](https://juejin.cn/post/6844904096260947981 ) 203 | -------------------------------------------------------------------------------- /1/1.3.4.md: -------------------------------------------------------------------------------- 1 | # 壹.3.4 JavaScript中的进程、线程、协程 2 | 3 | ## 01.什么是进程 4 | 5 | ​ 我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。 6 | 7 |   进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。 8 | 9 | ### 进程一般由程序、数据集合和进程控制块三部分组成: 10 | 11 | - 程序用于描述进程要完成的功能,是控制进程执行的指令集; 12 | - 数据集合是程序在执行时所需要的数据和工作区; 13 | - 程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。 14 | 15 | ### 进程具有的特征: 16 | 17 | - 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的; 18 | - 并发性:任何进程都可以同其他进程一起并发执行; 19 | - 独立性:进程是系统进行资源分配和调度的一个独立单位; 20 | - 结构性:进程由程序、数据和进程控制块三部分组成。 21 | 22 | ## 02.什么是线程 23 | 24 | ​ 在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。 25 | 26 |   后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程。 27 | 28 |   线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。 29 | 30 | ## 03.什么是协程 31 | 32 | ​ 协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。 33 | 34 | ​ 因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。 35 | 36 | 37 | 38 | ## 04.它们之间的区别 39 | 40 | ​ 相对线程和协程,进程更独立,有自己的内存空间,所以进程间通信比较困难。线程比进程轻量级,属于同一进程的多个线程间可以共享全部资源。协程与线程类似,不同点在于,线程由系统控制切换,协程是由用户控制切换。 41 | 42 | ##### 那么,控制切换,指的是控制什么的切换呢? 43 | 44 | ​ 在一个进程中执行的程序,有时需要同时处理多个工作,这时我们可以创建多个线程,让每个线程处理一个工作。但是,进程只有一个。就好比一个人,你给他分配了多个工作,帮他把每个工作单独拉了一个列表,可还是他一个人干,他只能一会儿干干这一会儿干干那,来模拟多个工作同时进行的状态,这就是所谓的系统控制切换,系统不停的在多个线程间切换来达到并行的效果。你可能会说,那根一件一件干不是一样吗?没错,是一样的,在只有一个cpu的电脑上,用不用多线程程序执行的时间是一样的。但是,如果这个人长了两个脑袋呢?那么他就能同时处理两件工作了。多核cpu就是那个长了好多个脑地的人……而协程的切换是要由用户手动来控制的,所以协程并不适合并行计算,而更多的用来优化程序结构。 45 | 46 | ## 05.js都支持吗? 47 | 48 | ​ 在浏览器中,可以通过webworkers创建进程,可以通过async/await,yield/Generator/GeneratorFunction实现协程,控制程序切换。 49 | 50 | ​ 在node中,除了可以使用上面浏览器中可以使用的方法,还可以通过cluster,child_process创建进程,通过libuv,tagg创建线程 51 | 52 | ### 刚才提到的那些都是啥?怎么用? 53 | 54 | ### webworkers 55 | 56 | ​ 简单点儿说就是使用webworkers你可以在全新的环境中运行一个你指定的js文件。这个全新的环境是独立的,既一个全新的进程,有点儿像一个新iframe还没有window.top,window.parent属性。 57 | 58 | ​ webworkers创建的进程和主进程之间可以通过message事件传递消息,但是消息只能是字符串,所以想要传对象和数组就只能传json了……这也是他不方便的地方。 59 | 60 | 具体使用方法可以看MDN上的文章: [使用 Web Workers](https://www.open-open.com/misc/goto?guid=4959732639773170024) 61 | 62 | ### sync/await 63 | 64 | ​ async/await是es7中新加的两个关键字,async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。 65 | 66 | ​ 其实就是类似汇编的寄存器和跳转指令……呃,通俗的说就是可以根据状态跳转态另一个函数半中间。 67 | 68 | ​ 由于es7还未在各个环境实现,想要使用的话还的用一些babel-polyfill之类的库做兼容…… 69 | 70 | ​ 更详细介绍请看阿阮的文章: [异步操作和Async函数](http://es6.ruanyifeng.com/?search=async&x=0&y=0#docs/async) 71 | 72 | ### yield/Generator/GeneratorFunction 73 | 74 | ​ generator是es6中新增的函数,本质是可以将一个函数执行暂停,并保存上下文,再次调用时恢复当时的状态。但是用来解决协程切换的问题貌似有点儿滥用特性的感觉呢…… 75 | 76 | ​ 更详细介绍请看阿阮的文章: [Generator 函数](http://es6.ruanyifeng.com/?search=async&x=0&y=0#docs/generator) 77 | 78 | ### cluster 79 | 80 | ​ cluster是node官方提供的一个多进程模块,效果和C语言的fork函数类似,当前文件完全重新执行一遍,通过cluster.isMaster判断是不是主进程,在区分不同的操作。进程间通过事件回调来通信,NodeJS 0.6.x 以上的版本开始支持。 81 | 82 | ​ 示例代码就不放了,node官方文档上写的很详细: [cluster](https://www.open-open.com/misc/goto?guid=4959646474498485566) 83 | 84 | ## child_process 85 | 86 | ​ node自带的child_process模块里的fork函数可以实现类似浏览器里webworkers的效果,使用方法和webworker一毛一样,都是通过读取新文件开启新进程,通过message通信。 87 | 88 | ​ 具体介绍请看文档: [child_process.fork(modulePath[, args\][, options])](https://www.open-open.com/misc/goto?guid=4959732640049274960) 89 | 90 | ​ 官方文档没有示例,下面给出一个web服务接收参数计算斐波那契数组的例子: 91 | 92 | #### index.js 93 | 94 | ```js 95 | var express = require('express'); 96 | var fork = require('child_process').fork; 97 | var app = express(); 98 | app.get('/', function(req, res){ 99 | var worker = fork('./work_fibo.js') //创建一个工作进程 100 | worker.on('message', function(m){//接收工作进程计算结果 101 | if('object' === typeof m && m.type === 'fibo'){ 102 | worker.kill();//发送杀死进程的信号 103 | res.send(m.result.toString());//将结果返回客户端 104 | } 105 | }); 106 | worker.send({type:'fibo',num:~~req.query.n || 1});//发送给工作进程计算fibo的数量 107 | }); 108 | app.listen(8124); 109 | 110 | ``` 111 | 112 | #### work_fibo.js 113 | 114 | ```js 115 | var fibo = functionfibo(n){//定义算法 116 | return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; 117 | } 118 | process.on('message', function(m){ 119 | //接收主进程发送过来的消息 120 | if(typeof m === 'object' && m.type === 'fibo'){ 121 | var num = fibo(~~m.num); 122 | //计算jibo 123 | process.send({type: 'fibo',result:num}) 124 | //计算完毕返回结果 125 | } 126 | }); 127 | process.on('SIGHUP', function(){ 128 | process.exit();//收到kill信息,进程退出 129 | }); 130 | 131 | ``` 132 | 133 | ### libuv 134 | 135 | ​ libuv是node底层实现使用的c++库……呃,所以如果你想使用这个库来实现多线程,那么你就得编写c++的代码了,不得不说,要想真正理解程序的本质,不多掌握几门语言真是不行啊…… 136 | 137 | 对c++不了解我就不瞎BB了,推荐两篇文章延伸阅读: 138 | 139 | - [libuv多线程处理的简单示例](https://www.open-open.com/misc/goto?guid=4959732640137785010) 140 | - [利用libuv编写异步多线程的addon实例](https://www.open-open.com/misc/goto?guid=4959732640220648698) 141 | 142 | ### tagg 143 | 144 | ​ tagg(Threads a gogo for Node.js)是Jorge Chamorro Bieling开发的一个node包。使用c语言phread库实现的多线程。 145 | 146 | 还是那刚才的斐波那契数组计算为例: 147 | 148 | ```js 149 | var Threads = require('threads_a_gogo');//加载tagg包 150 | functionfibo(n){//定义斐波那契数组计算函数 151 | return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1; 152 | } 153 | var t = Threads.create().eval(fibo); 154 | t.eval('fibo(35)', function(err, result){//将fibo(35)丢入子线程运行 155 | if (err) throw err; //线程创建失败 156 | console.log('fibo(35)=' + result);//打印fibo执行35次的结果 157 | }); 158 | console.log('not block');//打印信息了,表示没有阻塞 159 | 160 | ``` 161 | 162 | 最后结果: 163 | 164 | ```js 165 | not block 166 | fibo(35)=14930352 167 | 168 | ``` 169 | 170 | 我们可以看到执行效果与webworker类似,不同的是通信使用了异步回调的方式。 171 | 172 | 值得一提的是tagg包目前只能在linux下安装运行,这里再推荐一个tagg2包,是跨平台的。 173 | 174 | 这里需要重点提一下的是,不论tagg还是tagg2包都是利用phtread库和v8的v8::Isolate Class类来实现js多线程功能的。 175 | 176 | Isolate代表着一个独立的v8引擎实例,v8的Isolate拥有完全分开的状态,在一个Isolate实例中的对象不能够在另外一个Isolate实例中使用。嵌入式开发者可以在其他线程创建一些额外的Isolate实例并行运行。在任何时刻,一个Isolate实例只能够被一个线程进行访问,可以利用加锁/解锁进行同步操作。 177 | 178 | 换而言之,我们在进行v8的嵌入式开发时,无法在多线程中访问js变量,这条规则将直接导致我们之前的tagg2里面线程执行的函数无法使用Node.js的核心api,比如fs,crypto等模块。 179 | 180 | 延伸阅读: 181 | 182 | - [tagg](https://www.open-open.com/misc/goto?guid=4959732640301480659) 183 | - [tagg2](https://www.open-open.com/misc/goto?guid=4959732640383245706) 184 | 185 | ## 06.总结 186 | 187 | ​ 经过以上的学习,我们大概应该了解到进程、线程、协程的使用场景了,进程、线程适合用来处理计算密集型操作,协程适合用来优化代码结构,解决回调函数嵌套问题。线程比进程更轻,更节省资源,但是由于上面提到的线程问题,针对一些可以使用js原生的大量计算或循环还可以用用,涉及到使用nodejs核心api的操作,就要用进程解决了。 188 | 189 | 190 | 191 | ## 07.参考 192 | 193 | [JavaScript中的进程、线程和协程](https://www.open-open.com/lib/view/open1483144630465.html) 194 | 195 | [JavaScript的进程和线程](https://www.jianshu.com/p/d1bbd9049bfc) 196 | -------------------------------------------------------------------------------- /3/3.1.1.md: -------------------------------------------------------------------------------- 1 | # 叁.1.1 jQuery过时了吗? 2 | 3 | ## 叁.1.1.1 jQuery带来的好处 4 | 5 | ![jQuery之父 John Resig](../.gitbook/assets/john-resig.jpg) 6 | 7 | John Resig在2006年发布了jQuery第一版,十几年过去了,截止2019年6月的统计,jQuery仍然被全球最活跃的前1000万个网站中的73%所使用,其影响力之大可见一斑。jQuery作为最流行的JavaScript库,其诞生之初便为前端开发带来了以下好处: 8 | 9 | ### **1. 解决了当时浏览器兼容问题** 10 | 11 | jQuery诞生时的2006年,微软IE6在浏览器市场里所占份额最大。这个“傲慢”的浏览器自成一套,很多API没有遵从W3C标准,因此造成各种兼容问题(出于浏览器大战时的竞争策略的考虑,微软没有把遵从W3C标准放在重要的位置)。调用某些相同功能的DOM API,IE6与其他浏览器比如Firefox、Opera、Safari在语法上有差异。这让前端工程师经常要写两份甚至多份代码,不胜其苦。最烦恼的还不止于此,微软自己的浏览器,若版本不一样居然也有兼容性问题,比如IE6的代码居然也不兼容IE7、IE8!此时jQuery出现了,它及时、有效地解决了这些问题,让前端工程师只需要写一份代码,无须担心兼容性。这也是jQuery大受欢迎的最重要的原因。 12 | 13 | ```javascript 14 | // 过去如果不用jQuery,监听事件(兼容IE6)要这么写 15 | if (button.addEventListener) 16 | button.addEventListener('click',fn); 17 | else if (button.attachEvent) { 18 | button.attachEvent('onclick', fn); 19 | }else { 20 | button.onclick = fn; 21 | } 22 | 23 | // 但是如果用jQuery只需要这么写 24 | $(button).on('click', fn) 25 | ``` 26 | 27 | ### **2. 发明了一种便捷的DOM操作方式** 28 | 29 | jQuery 极大地简化了对元素选择: 30 | 31 | ```javascript 32 | // 如果你想获取 .nav > .navItem 对应的所有元素,用 jQuery 是这样写的 33 | $('.nav > .navItem') 34 | 35 | // 在 IE 6 上,你得这么写 36 | var navItems = document.getElementsByClassName('navItem') 37 | var result = [] 38 | for(var i = 0; i < navItems.length; i++){ 39 | if(navItems[i].parentNode.className.match(/\bnav\b/){ 40 | result.push(navItems[i]) 41 | } 42 | } 43 | ``` 44 | 45 | 例如,原来你要修改样式,原生JavaScript是这么写的: 46 | 47 | ```javascript 48 | var dom = document.getElementById('coffe1891'); 49 | dom.style.color = 'blue'; 50 | ``` 51 | 52 | 用上jQuery后,一行搞定: 53 | 54 | ```javascript 55 | $('#coffe1891').css('color', 'blue'); 56 | ``` 57 | 58 | ### **3. 轻松实现动画效果** 59 | 60 | 例如,我们需要把 一个`
`元素移动到左边,直到left属性等于1891像素为止。 使用jQuery,我们可以这么写: 61 | 62 | ```javascript 63 | $("div").animate({left:'1891px'}); 64 | ``` 65 | 66 | ### **4. 封装了极易使用的Ajax** 67 | 68 | 本书[壹.2.13已有一段用原生的JavaScript实现ajax请求的代码](../1/1.2.13.md#ajax),相当的复杂。而 用上了jQuery后,简洁了不少!如下所示: 69 | 70 | ```javascript 71 | $.ajax({ 72 | url:"www.xxx.com/coffe1891", 73 | success:function(result){ 74 | //dosomething 75 | } 76 | }); 77 | ``` 78 | 79 | 总而言之,jQuery作为一个JavaScript库**,**这个库里有很多函数可以简化你的DOM 操作,提供一些特效功能等等。它帮我们封装好了各种不会写、不想写、没时间写的代码,以极其人性化/易懂的API的方式提供出来,让我们通过简单的调用就能直接实现丰富的功能。 80 | 81 | ## 叁.1.1.2 现在已经有了比jQuery更好的方案 82 | 83 | 随着时代的发展,毕竟也有十几年过去了,前端开发有了很大的变化。针对前面提到的jQuery的4点好处,现在也已经有更好的替代方案。 84 | 85 | ### 1. 针对兼容性 86 | 87 | 现在主流浏览器拥抱W3C标准,因此浏览器之间的兼容性做得越来越好。兼容性做得极差的浏览器IE6已经成为历史,甚至在绝大多数生产环境中连IE8都可以不用考虑,况且还有Babel.js这样更轻量级的库存在。 88 | 89 | ### 2. 针对DOM操作 90 | 91 | jQuery能够快速选取DOM结点,这个无疑是jQuery受欢迎的一个重要的原因。但是就目前情况来说,这个优势已经没有了,为什么呢?有两个已经成为W3C标准的API:`document.querySelector`和`document.querySelectorAll`,这两个API可以通过传入css选择器形式的字符串,以匹配到预期的DOM节点,而且基于浏览器的实现其执行效率更好。 92 | 93 | 而MVVM一类的框架比如React/Vue/Angular的流行,其双向绑定的特性已经让DOM的更新变成由框架自行处理,让前端工程师可以不需要操作DOM(改为让框架自行去操作[Virtual DOM](../1/1.4.3.md)),只需要专注数据和业务逻辑。而且React/Vue/Angular这些主流框架针对DOM的操作时做了大量的[Repaint和Reflow](../1/1.4.2.md)有关的优化,比如基于Virtual DOM进行计算,将多次对真实DOM的操作精简为最小变动然后操作真实DOM、在同一个Event Loop循环内将多次DOM操作事件合并……这些优化措施都能有能更好的表现,而这些优化jQuery并没有做。 94 | 95 | ### 3. 动画领域的替代方案 96 | 97 | 现在CSS3动画技术已经非常的成熟,已经完全可以取代jQuery做的动画。css3能比jQuery的animate方法实现更复杂的动画,兼容性好,性能消耗小。举个例子,比方说如果实现背景颜色过度,CSS3可以完美的实现,但是jQuery就不方便。现在已经出现了很多优秀的css3动画库,比如Animate.css、Velocity.js。 98 | 99 | ### 4. Ajax也有了更好的替代 100 | 101 | Fetch已经完美地取代Ajax成为实现异步任务的标准。jQuery的ajax操作,为我们省去了兼容浏览器方面的问题,并且也提供了简明的API去调用get和post,让开发者从繁琐的兼容性与使用原生API上解脱出来。但是现在,这个优势也已经非常微小了。不管是原生JavaScript的Fetch API还是第三方库axios,都为我们提供了强大的ajax使用能力,并且axios还有拦截器这个优势。这时相较而言,jQuery的ajax确实已经无法相比了。 102 | 103 | 当然,Fetch在IE浏览器里需要有Fetch的Polyfill方案:[github/fetch](https://link.zhihu.com/?target=https%3A//github.com/github/fetch),这样只需要引用这一个小小的JS,就可以使用方便的ajax了,相较于jQuery小巧很多。 104 | 105 | ## 叁.1.1.3 jQuery留给我们的财富 106 | 107 | ### 1. 大量功能强大方便使用的插件 108 | 109 | jQuery发展这么多年,它最大的价值之一在于社区积攒的各种插件。无论是做日期选择器还是轮播图,甚至搞个播放器,都可以分分钟找到一个对应的插件。 比如 [CSS transitions and transformations for jQuery](http://ricostacruz.com/jquery.transit/) 这个动画插件,我到现在也还在用,完全没问题。 110 | 111 | 更多宝矿可以自行浏览jQuery插件网站:[https://plugins.jquery.com/](https://plugins.jquery.com/) 这个网站虽然不再接纳新的插件,但可以作为一份地图,可以方便找到jQuery流行插件的Github主页,然后你会发现,很多插件都还非常活跃而且更新非常频繁。 112 | 113 | ![plugins.jquery.com](../.gitbook/assets/3.1.1.3.1.jpg) 114 | 115 | ### 2.简洁易懂的API设计 116 | 117 | jQuery 的 API 风格依然在流行。我们把 jQuery 和 [Axios ](https://github.com/axios/axios)做一下对比: 118 | 119 | ```javascript 120 | $.ajax({url:'/api', method:'get'}) 121 | $.get('/api').then(fn1,fn2) 122 | 123 | axios({ url: '/api', method: 'get'}) 124 | axios.get('/api').then(fn1, fn2) 125 | ``` 126 | 127 | 为什么 2018 年开始流行的 axios 跟 jQuery.ajax 这么相像呢?因为 jQuery 的 API 实在太好用了!搞得新库根本没法超越它,没有办法设计出更简洁的 API 了。 128 | 129 | ### 3.一些鬼斧神工似的编程套路 130 | 131 | 为什么有些人代码水平老是提不高了?就是因为不会造轮子,不会设计优雅的 API,更不会实现优雅的 API,只会调用其他库或框架提供的功能(中枪的举手)。而 jQuery 则提供了简单而又经典的范例供大家学习。不信的话我们就来看看 jQuery 用到了哪些所谓的设计模式(其实就是编程套路)吧。 132 | 133 | #### 函数重载 134 | 135 | 在[壹.2.1 函数](../1/1.2.1.md) 里面最末尾有John Resig的一段有关“用原生JavaScript实现函数重载”的代码,已经见识到jQuery之父的coding能力。jQuery里的函数重载是这样的: 136 | 137 | ```javascript 138 | $(fn) 139 | $('div') 140 | $(div) 141 | $($(div)) 142 | $('span', '#scope1') 143 | ``` 144 | 145 | 你会发现 $ 这个函数的参数可以是函数、字符串、元素和 jQuery 对象,甚至还能接受多个参数,这种重载是怎么做到的?读者可以自己去看看源码一探究竟。 146 | 147 | #### 发布订阅者模式 148 | 149 | ```javascript 150 | var eventHub = $({}); 151 | eventHub.on('xxx', function(){ console.log('收到') }); 152 | eventHub.trigger('xxx'); 153 | ``` 154 | 155 | #### 用原型继承实现插件系统 156 | 157 | ```javascript 158 | $.fn.modal = function(){ ... } 159 | $('#div1').modal(); 160 | ``` 161 | 162 | Vue 2 的插件也是类似的思路。 163 | 164 | #### 事件委托 165 | 166 | ```javascript 167 | $('div').on('click', 'span', function(){...}); 168 | ``` 169 | 170 | 说实话若在 2018 年找前端让他写一个事件委托,我保证 90% 写出来的代码都是有「明显」bug 的。 171 | 172 | #### 链式调用 173 | 174 | ```javascript 175 | $('div').text('hi').addClass('red').animate({left: 100}); 176 | ``` 177 | 178 | #### 命名空间 179 | 180 | ```javascript 181 | // 你的插件在一个 button 上绑定了很多事件 182 | $button.on('click.plugin', function(){...}) 183 | $button.on('mouseenter.plugin', function(){...}) 184 | // 然后你想在某个时刻移除以上所有事件,如果不用 jQuery 就很麻烦 185 | $button.off('.plugin'); 186 | ``` 187 | 188 | #### 高阶函数 189 | 190 | ```javascript 191 | var fn2 = $.proxy(fn1, asThis, param1) 192 | ``` 193 | 194 | $.proxy 接受一个函数,返回一个新的函数。 195 | 196 | 还有许多其他“套路”,限于篇幅就不一一列举了,读者可以自行探索。 197 | 198 | ## 结语 199 | 200 | jQuery虽然完成了它的历史使命,并且在逐渐被新的库/框架所替代,但它给我们留下了很多宝贵的财富,仍然活跃在舞台上。 201 | 202 | 其实,阅读jQuery 的源码和学习它的编程思想,对写代码和封装库很有帮助。现在的前端工程师新人依然可以学习 jQuery 的思想,因为新人直接理解Vue/React/Angular的底层思想难度较大,而jQuery是一个很不错的中间过渡。 203 | 204 | 同时,jQuery 也蕴含了非常多的编程套路。如果你不想学jQuery,直接去学 React/Vue /Angular 会难一点,当然硬要学也能学会,但也仅此而已,反正回头还是要研究更深层次的JavaScript套路,这些在jQuery里都有。 205 | 206 | 最后,jQuery能支持IE8以下版本。由于Vue之类的框架只能支持IE8以上的版本,而实际情况是现在很多那种事业单位里的古董电脑很多都还是IE7,那么这种情况下,用vue之类的MVVM框架显然不适合。 207 | 208 | 如果你读到这里,想必也不会再问jQuery是否过时了。 209 | 210 | ## 参考文献 211 | 212 | {% hint style="info" %} 213 | [https://en.wikipedia.org/wiki/JQuery](https://en.wikipedia.org/wiki/JQuery) 214 | [https://zh.wikipedia.org/wiki/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%A4%A7%E6%88%98](https://zh.wikipedia.org/wiki/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%A4%A7%E6%88%98) 215 | {% endhint %} 216 | 217 | -------------------------------------------------------------------------------- /6/6.2.3.md: -------------------------------------------------------------------------------- 1 | # 陆.2.3 重新认识JavaScript面向对象: 继承 2 | 3 | 抛开JavaScript自带的class语法糖实现类的继承,如果用原生JavaScript实现类的继承,有以下六种方式,其实现代码与优缺点分析如下: 4 | 5 | ## 01.类式继承\(classical inheritance\) 6 | 7 | 实现本质:重写子类的原型,代之以父类的实例。 8 | 9 | ```javascript 10 | //父类 11 | function User(username) { 12 | this.username = username ? username : "Unknown"; 13 | this.books = ["coffe", "1891"]; 14 | User.prototype.read = function () { 15 | console.log(this.username + '喜欢读的书是:' + this.books.join("/")); 16 | } 17 | } 18 | //子类 19 | function CoffeUser(username) { 20 | if (username) 21 | this.username = username; 22 | } 23 | 24 | //关键 25 | CoffeUser.prototype = new User(); 26 | 27 | const user1 = new CoffeUser("bob"); 28 | const user2 = new CoffeUser("steve"); 29 | 30 | //instanceof是检测某个对象是否是某个类的实例 31 | console.log(user1 instanceof User); //>> true 32 | 33 | // 可访问原型链上的属性 34 | console.log(user1.books); //>> ["coffe", "1891"] 35 | 36 | // 可访问原型链上的方法 37 | user1.read();//>> bob喜欢读的书是:coffe/1891 38 | user2.read();//>> steve喜欢读的书是:coffe/1891 39 | 40 | //修改来自原型上的引用类型的属性,则有副作用:会影响到所有实例 41 | user1.books.push("hello"); 42 | console.log(user1.books); //>> ["coffe", "1891", "hello"] 43 | console.log(user2.books); //>> ["coffe", "1891", "hello"] 44 | 45 | //修改来自原型上的值类型的属性,无副作用 46 | user1.username = 'bill'; 47 | console.log(user1.username, user2.username); //>> bill steve 48 | ``` 49 | 50 | 缺陷: 51 | 52 | * **引用类型属性的误修改。**原型属性中的引用类型属性会被所有实例共享,若子类实例更改从父类原型继承来的引用类型的共有属性,会影响其他子类。 53 | 54 | {% hint style="info" %} 55 | 为什么会这样?因为引用类型一般比值类型复杂,引用类型存储在内存的Heap(堆)区,值类型存储在内存的Stack(栈)区。传递参数的时候,针对引用类型仅仅传递的是一个指针而已,而针对值类型会传一个copy副本。所以本例中修改一个引用类型的属性,一定会影响到其他子类。反之可以这样思考:如果一个引用类型也传递的是copy,恰好该引用类型的对象超级复杂、所占内存超级多(比如大型3D游戏的场景对象),那么计算机的内存很快被耗尽,干不了其他事情,这将会是灾难。 56 | {% endhint %} 57 | 58 | * **无法传递参数。**在创建子类型的实例时,不能向父类的构造函数中传递参数。这点如过不好理解的话,接着看下面的“构造函数式继承”。 59 | 60 | 综上,我们在实际开发中很少单独使用类式继承。 61 | 62 | ## 02.构造函数式继承 63 | 64 | 实现本质:在子类的构造函数里通过调用call/apply来实现继承。 65 | 66 | ```javascript 67 | function User(username, password) { 68 | this.password = password; 69 | this.username = username; 70 | User.prototype.login = function () { 71 | console.log(this.username + '要登录Github,密码是' + this.password); 72 | } 73 | } 74 | 75 | function CoffeUser(username, password) { 76 | User.call(this, username, password);//通过call向父类的构造函数传递参数 77 | this.articles = 3; // 文章数量 78 | } 79 | 80 | const user1 = new CoffeUser('coffe1891', '123456'); 81 | const user2 = new CoffeUser('BobMa', '987654'); 82 | 83 | console.log(user1 instanceof User);//>> false 84 | 85 | console.log(user1.username, user1.password); //>> coffe1891 123456 86 | console.log(user2.username, user2.password); //>> BobMa 987654 87 | 88 | console.log(user1.login()); // TypeError: user1.login is not a function 89 | ``` 90 | 91 | 存在明显的缺陷: 92 | 93 | * 无法通过instanceof的测试; 94 | * 并没有继承父类原型上的方法。 95 | 96 | ## 03.组合式继承 97 | 98 | 既然上述两种方法各有缺点,但是又各有所长,那么我们是否可以将其结合起来使用呢?即通过类式继承继承方法,而在构造函数继承属性,这种继承方式就叫做“组合式继承”。 99 | 100 | ```javascript 101 | function User(username, password) { 102 | this.password = password; 103 | this.username = username; 104 | User.prototype.login = function () { 105 | console.log(this.username + '要登录Github,密码是' + this.password); 106 | } 107 | } 108 | 109 | function CoffeUser(username, password) { 110 | User.call(this, username, password); // 第2次执行 User 的构造函数 111 | this.articles = 3; // 文章数量 112 | } 113 | 114 | CoffeUser.prototype = new User(); // 第1次执行 User 的构造函数 115 | const user1 = new CoffeUser("coffe1891", "123456"); 116 | 117 | console.log(user1 instanceof User);//>> true 118 | user1.login();//>> coffe1891要登录Github,密码是123456 119 | ``` 120 | 121 | 虽然这种方式弥补了上述两种方式的一些缺陷,但有些问题仍然存在: 122 | 123 | * 父类的构造函数被调用了两次,显得多余; 124 | * **污染:**若再添加一个子类型,给其原型单独添加一个方法,那么其他子类型也同时拥有了这个方法。 125 | 126 | 综上,组合式继承也不是我们最终想要的。 127 | 128 | ## 04.原型式继承\(prototypal inheritance\) 129 | 130 | 原型式继承是2006年道格拉斯.克罗克福德提出的,他的基本思想是借助原型基于已有的对象创建一个新对象,同时还不必因此创建自定义类型。原型式继承实际上是对类式继承的一种封装,只不过其独特之处在于,定义了一个干净的临时中间类,如下: 131 | 132 | ```javascript 133 | function createObject(o) { 134 | // 创建临时中间类 135 | function F() { 136 | 137 | } 138 | // 修改类的原型为o, 于是f的实例都将继承o上的方法 139 | F.prototype = o; 140 | return new F(); 141 | } 142 | ``` 143 | 144 | 这不就是ES5的 `Object.create` 吗?没错,你可以认为是如此。 145 | 146 | 既然只是类式继承的一种封装,其使用方式自然如下: 147 | 148 | ```text 149 | CoffeUser.prototype = createObject(User) 150 | ``` 151 | 152 | 也就仍然没有解决类式继承的一些问题。从这个角度而言,原型继承和类式继承应该直接归为一种继承。 153 | 154 | ## 05.寄生式继承 155 | 156 | 寄生式继承是与原型继承紧密相关的一种思路,它依托于一个内部对象而生成一个新对象,因此称之为寄生。 157 | 158 | ```javascript 159 | const UserSample = { 160 | username: "coffe1891", 161 | password: "123456" 162 | } 163 | 164 | function CoffeUser(obj) { 165 | var o = Object.create(obj);//o继承obj的原型 166 | o.__proto__.readArticle = function () {//扩展方法 167 | console.log('Read article'); 168 | } 169 | return o; 170 | } 171 | 172 | var user = new CoffeUser(UserSample); 173 | user.readArticle();//>> Read article 174 | console.log(user.username, user.password);//>> coffe1891 123456 175 | ``` 176 | 177 | ## 06.寄生组合式继承 178 | 179 | ```javascript 180 | //寄生组合式继承的核心方法 181 | function inherit(child, parent) { 182 | // 继承父类的原型 183 | const p = Object.create(parent.prototype); 184 | // 重写子类的原型 185 | child.prototype = p; 186 | // 重写被污染的子类的constructor 187 | p.constructor = child; 188 | } 189 | 190 | //User, 父类 191 | function User(username, password) { 192 | let _password = password 193 | this.username = username 194 | } 195 | 196 | User.prototype.login = function () { 197 | console.log(this.username + '要登录Github,密码是' + _password); 198 | //>> ReferenceError: _password is not defined 199 | } 200 | 201 | //CoffeUser, 子类 202 | function CoffeUser(username, password) { 203 | User.call(this, username, password) // 继承属性 204 | this.articles = 3 // 文章数量 205 | } 206 | 207 | //继承 208 | inherit(CoffeUser, User); 209 | 210 | //在原型上添加新方法 211 | CoffeUser.prototype.readArticle = function () { 212 | console.log('Read article'); 213 | } 214 | 215 | const user1 = new CoffeUser("Coffe1891", "123456"); 216 | console.log(user1); 217 | ``` 218 | 219 | 观察chrome浏览器的输出结果: 220 | 221 | ![](../.gitbook/assets/6.2.3.6.png) 222 | 223 | 简单说明一下: 224 | 225 | * 子类继承了父类的属性和方法,同时,属性没有被创建在原型链上,因此多个子类不会共享同一个属性; 226 | * 子类可以传递动态参数给父类; 227 | * 父类的构造函数只执行了一次。 228 | 229 | Nice!这才是我们想要的继承方法。然而,仍然存在一个美中不足的问题: 230 | 231 | * 子类想要在原型上添加方法,必须在继承之后添加,否则将覆盖掉原有原型上的方法。这样的话若是已经存在的两个类,就不好办了。 232 | 233 | 所以,我们可以将其优化一下: 234 | 235 | ```javascript 236 | function inherit(child, parent) { 237 | // 继承父类的原型 238 | const parentPrototype = Object.create(parent.prototype) 239 | // 将父类原型和子类原型合并,并赋值给子类的原型 240 | child.prototype = Object.assign(parentPrototype, child.prototype) 241 | // 重写被污染的子类的constructor 242 | p.constructor = child 243 | } 244 | ``` 245 | 246 | 但实际上,使用`Object.assign` 来进行 copy 仍然不是最好的方法。因为根据本书[ 深拷贝与浅拷贝](../1/1.3.1.md#2object-de-nei-zhi-fang-fa-assign)的描述,上述的继承方法只适用于 copy 原型链上可枚举的方法,而ES6中,类的方法默认都是不可枚举的。此外,如果子类本身已经继承自某个类,以上的继承将不能满足要求。 247 | 248 | ## 参考文献 249 | 250 | {% hint style="info" %} 251 | [Inheritance in JavaScript](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance) 252 | {% endhint %} 253 | 254 | --------------------------------------------------------------------------------