├── .gitignore ├── README.md ├── commit ├── README.md ├── README_gitee.md └── pics │ ├── create-private-repo-gitee.png │ ├── create-private-repo.png │ ├── invite-collab-2.png │ ├── invite-collab-gitee-2.png │ ├── invite-collab-gitee.png │ ├── invite-collab.png │ ├── issue-example-gitee.png │ └── issue-example.png ├── crazy-jump ├── README.md ├── demo.gif └── meme.jpg ├── is-it-a-prime └── README.md ├── qr-code-playground ├── QRCodePlayground.pdf ├── README.md └── pics │ ├── Alphanumeric_Mode_Table.png │ ├── Code.png │ └── QR_Code_Structure_Example.png └── to-frontend-newbie ├── README.md ├── demo ├── css.css ├── html.html └── js.js ├── image-20210918113322477.png ├── image-20210918121242153.png ├── image-20210918133011325.png ├── image-20210918185655361.png ├── image-20210918211357404.png ├── image-20210919024240910.png └── image-20210921150424436.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Contributor:@巨硬 2 | # 2021 Fall Round Two 3 | 4 | 恭喜您,从众多zjuer中脱颖而出,通过了我们的第一轮考验。现在,您还需要跨过最后一个关卡————这道小小的二面题。 5 | 6 | 我们为您精心准备了四道试题,它们难度不一、类型各异。我们希望您**至少挑选一道**完成,来向我们展示您解决问题的智慧和热情。 7 | 8 | 两道前端入门题,适合没有太多基础的同学完成: 9 | - `to-front-end-newbies` from @喵刀 10 | - `crazy-jump` from @Switch 11 | 12 | 两道需要一定理解能力和编程基础的试题: 13 | - `qr-code-playground` from @保安 14 | - `is-it-a-prime` from @Switch @巧克力猫猫 15 | 16 | 请注意: 17 | 18 | ``` 19 | 1. 每一个题目前都标注了它的难度和目标人群建议,请选择您认为合适的题目进行解答。我们鼓励您去尝试能力范围内难度较大的试题,这会在最终评价中有所体现。 20 | 2. 如果您有余力,可以选择完成多于一个问题并提交。但我们不建议您在低完成度的情况下做多道试题。 21 | 3. 无论完成度如何,都请提交您的成果。我们关注的更多是您解决问题的智慧、对新知识的习得能力。无论您是否有能力完成,我们都建议您在这个过程中尽量多地展现自己的学习和思考过程。 22 | ``` 23 | 之后,我们会为您安排一次(也许并不简短的)交流,请您到场简单谈谈解答体会。具体时间请关注短信和群公告。 24 | 25 | ---- 26 | 我们非常乐意解答您的疑惑。每道题目的开始部分都标注了本题的出题者,在遇到技术或非技术性问题时,您可以直接在二面群中非匿名地提问。**原则上,您提出的问题将不会影响您的二面评价。** 27 | 但在您提问之前,我们强烈建议您仔细阅读下面的建议。 28 | 29 | ## 提问的智慧 30 | 31 | 请您明白,没有人有义务花费时间为您解答问题。我们乐意为您解答问题,是因为我们曾经因他人的解答而受益,也希望您能从我们的二面题中收获更多。 32 | 我们希望我们的每一个回答都能给最需要的人以帮助。在您提问之前,不妨做如下尝试: 33 | ``` 34 | 1. 善用搜索引擎。您遇到的问题极有可能已被他人解决,只不过您没有找到这些解答。google、bing 会是您最有力而近便的助手。 35 | 2. 善用报错。错误信息结合搜索引擎应当能解决大部分的问题。 36 | 3. 尝试自己检查或试验以找到答案。请勿因为粗心浪费掉了一次宝贵的向他人提问的机会。 37 | ``` 38 | **概述原文参考 github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way* 。 39 | 40 | 在您做了以上尝试之后,feel free to ask us。 41 | 42 | ## 其他建议 43 | 44 | 二面题不仅仅是一次考察。它一定程度上是我们日常工作的一种模拟。我们希望在这个过程中清晰地考察您的学习能力和耐心、时间规划能力,也希望通过这些二面题,来让您身临其境地体验 Qsc Tech 的真正工作。 45 | 46 | 我们会看重这些素质: 47 | ``` 48 | 认真程度。但不要卷。 49 | 学习能力。相比“会做”,我们更重视您是“如何学会”的。相比“做了多少”,我们更重视您“如何去做”。 50 | 耐心和时间投入。这些题目都有不小的学习量(如果您选择了水平匹配的题目的话),或是有一些开放性的部分可以学习更多。 51 | 查找资料和精准提问的能力。 52 | ``` 53 | 54 | ## 关于验收和题目解答 55 | 56 | 所有人都必须查看本教程中的 `commit/` 文件夹,阅读其中的 `README.md` ,在 **9月29日中午12:00之前** 提交成品。 57 | 58 | 另外,在开始做题之前,请确保您阅读完成上面的提交方式,并**留出足够的时间用于实践这些提交步骤**。如何`提交` 也是二面题中**难度不小**的一环。 59 | 60 | 那么,请您开始吧!我们都在期待您的精彩解答! 61 | 62 | ### 注意: 63 | 64 | **任何取自网络的代码请用人类可以发现的方式注明出处,严禁面试者之间互相交流以及参考代码。如果发现,将直接取消资格。有任何技术或非技术性问题,请直接在群里提问。** 65 | -------------------------------------------------------------------------------- /commit/README.md: -------------------------------------------------------------------------------- 1 | > Contributor:@异特龙 2 | # 二面试题提交 3 | > 如果您打算使用 gitee 提交,请看另一份文档:`README_gitee.md` 。 4 | > 如果您没有某些众所周知的工具的话,对 `github` 的访问可能会不稳定。此时您需要使用 gitee 代替。 5 | 6 | ## 面试官名单 7 | #### crazy-jump 8 | * @Enzymii 9 | #### is-it-a-prime 10 | * @RalXYZ 11 | * @Enzymii 12 | #### qr-code-playground 13 | * @Deluxurous 14 | #### to-frontend-newbie 15 | * @palemoons 16 | #### 仓库维护者 17 | * @dinoallo 18 | 19 | ## Tl;dr 20 | 1. 复制 / 镜像一份二面试题到自己 Github 的*私有*仓库 21 | 2. 提交代码 22 | 3. 将选做题目的**出题面试官**和**仓库维护者**(名单参见上方“面试官名单”),设为协作者 23 | 4. 到*原仓库*发一个 issue (模板参见置顶 issue ) 24 | 25 | 如果你已经很清楚以上几个步骤该如何操作,那么可以跳过 Step-by-Step Guide 。如果你有至少一个步骤不熟悉,那么请你仔细阅读引导。 26 | 27 | ## 注意事项 28 | 29 | ### 面试期间请积极查看 Github 通知 30 | 31 | 如果你的提交格式有误,或者面试官没有权限查看仓库,面试官会在你的 issue 下提示,若已提醒而错误未修正,面试者将自行承担后果。 32 | 33 | ### 请不要向仓库上传任何文件压缩包(包含 zip, rar 等) 34 | 35 | 如成品以压缩包方式打包,面试官有权不受理,请面试者自行承担后果。如有特殊需求,请咨询面试官。 36 | 37 | ### 请不要向仓库上传音频及视频 38 | 39 | gif 文件在容许范围内。 40 | 41 | ### 请不要在仓库中透露任何私密信息(如私钥及密码) 42 | 43 | 如果你的工作目录下包含任何私密信息文件,请参考 `gitignore` [相关教学文章](https://linuxize.com/post/gitignore-ignoring-files-in-git/) 来回避上传这些文件。 44 | 45 | # Step-by-Step Guide 46 | ## `git` 47 | `git` 是一个版本控制系统,在本次面试过程中,我们使用 `git` 来提交成品。 48 | 关于 `git` 的具体用法,请搜索相关文章,或参考这本书:[Pro Git 中文版](https://git-scm.com/book/zh/v2) 。 49 | ## 提交方式 50 | 在掌握了基本的 `git` 操作之后,就可以提交你的代码了! 51 | > 请注意,在进行每一步的同时,请思考每一步命令甚至每一个参数的作用。 52 | ### 在 Github 上配置 ssh key 53 | > [什么是 ssh ?](https://docs.github.com/cn/github/authenticating-to-github/connecting-to-github-with-ssh/about-ssh) 54 | #### 生成 ssh 密钥 55 | 如果你已有 ssh 密钥,可以跳过这个步骤。如果你没有,请参考:[生成 ssh 密钥](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) 。 56 | #### 添加密钥到 Github 57 | 关于如何配置 Github ssh key ,参考:[添加 ssh 密钥到 Github 账户](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) 。 58 | ### 新建 Private Repository 59 | 在 Github 上,新建一个 **Private** Repository ,并*命名为 `2021-fall-round-two`* 。 60 | ![Create Private Repo](./pics/create-private-repo.png) 61 | ### `git clone` 原本的 Repository 62 | 命令如下: 63 | 64 | ``` shell 65 | git clone --bare git@github.com:QSCTech/2021-fall-round-two.git 66 | ``` 67 | ### `git push` 一份题目到自己的 Private Repository 68 | 进入 `2021-fall-round-two` 文件夹,执行如下命令: 69 | 70 | ``` shell 71 | git push --mirror git@github.com:/2021-fall-round-two.git # 把 替换为你自己的 Github 用户名 72 | ``` 73 | 74 | ### `git clone` 自己的 Private Repository 75 | 提交题目的仓库是自己的 Private Repository ,之前克隆的原 Repository 就没用了,可以删除,并请不要提交到原 Repository 。 76 | 77 | ``` shell 78 | git clone git@github.com:/2021-fall-round-two.git # 克隆自己的私有仓库 79 | ``` 80 | 81 | ### Go hacking! 82 | 克隆完自己的仓库后,就可以开始在仓库文件夹下写程序了! 83 | 84 | ### `git push` 同步当前 commits 至 Github 85 | 当你想同步目前的工作进度,或者想提交代码时,运行以下命令: 86 | 87 | ``` shell 88 | git push -u origin 89 | ``` 90 | 91 | ### 提交 issue 到原仓库 92 | 如果你已经准备好让面试官 review 自己的成品了,请到原题目仓库新增一个 issue,格式请参考: 93 | ![Issue Example](./pics/issue-example.png) 94 | 95 | ### 给予面试官协作者权限 96 | 根据你选的题目,务必让*出题的面试官* 和*仓库维护者*成为协作者: 97 | 98 | ![Invite collaborator](./pics/invite-collab.png "将面试官加到协作者中") 99 | ![Invite collaborator 2](./pics/invite-collab-2.png "输入面试官 ID") 100 | 101 | 面试官能正确查看你的仓库(见下方注意事项)后,会在 issue 下回复,提交就算完成了。 102 | 103 | ## 更新二面试题仓库 104 | 有时你会接到面试官让你更新仓库的通知,或者你已经很久都没有操作仓库了,你手边的仓库可能已经不是最新的了,为了避免你与面试官和其他面试者的信息不对等,我们必须得到最新版本的仓库。 105 | > 强烈建议大家要时常更新仓库,频率大概是半天到一天,如果面试官通知你,**一定要更新** 。 106 | 107 | ### 将原仓库设为你私有仓库的 remote 108 | 109 | ``` shell 110 | git remote add upstream git@github.com:QSCTech/2021-fall-round-two.git # upstream 为原仓库 111 | ``` 112 | 这个步骤只需要做一遍即可,建议大家在仓库刚克隆下来时,执行这行命令,之后更新仓库便不需要重新执行一次了。 113 | 114 | ### 从原仓库获取更新并合并 115 | 116 | ``` shell 117 | git pull upstream master # 如果你的工作分支是 master 118 | ``` 119 | 如果你的 git 的暂存区没有任何东西,那么只需执行以上步骤就可以完成更新。 120 | 121 | 122 | #### 如果你已经用 `git add` 命令暂存了一些代码 123 | 我们可以利用 `git stash` 命令: 124 | ``` shell 125 | git stash 126 | ``` 127 | 然后按照上面所说的获取更新,再执行: 128 | 129 | ``` shell 130 | git stash pop 131 | ``` 132 | 之前的改动就会回来了。 133 | -------------------------------------------------------------------------------- /commit/README_gitee.md: -------------------------------------------------------------------------------- 1 | # 二面试题提交 2 | 3 | ## 面试官名单 4 | #### crazy-jump 5 | * @Enzymii 6 | #### is-it-a-prime 7 | * @RalXYZ 8 | * @Enzymii 9 | #### qr-code-playground 10 | * @Deluxurous 11 | #### to-frontend-newbie 12 | * @palemoons 13 | #### 仓库维护者 14 | * @dinoallo 15 | 16 | ## Tl;dr 17 | 1. 复制 / 镜像一份二面试题到自己 Gitee 的*私有*仓库 18 | 2. 提交代码 19 | 3. 由于不是所有面试官都有 Gitee 帐号,请直接将**仓库维护者**(名单参见上方“面试官名单”)设为协作者 20 | 4. 到*原仓库*发一个 issue (模板参见置顶 issue ) 21 | 22 | 如果你已经很清楚以上几个步骤该如何操作,那么可以跳过 Step-by-Step Guide 。如果你有至少一个步骤不熟悉,那么请你仔细阅读引导。 23 | 24 | ## 注意事项 25 | 26 | ### 面试期间请积极查看 Gitee 通知 27 | 28 | 如果你的提交格式有误,或者面试官没有权限查看仓库,面试官会在你的 issue 下提示,若已提醒而错误未修正,面试者将自行承担后果。 29 | 30 | ### 请不要向仓库上传任何文件压缩包(包含 zip, rar 等) 31 | 32 | 如成品以压缩包方式打包,面试官有权不受理,请面试者自行承担后果。如有特殊需求,请咨询面试官。 33 | 34 | ### 请不要向仓库上传音频及视频 35 | 36 | gif 文件在容许范围内。 37 | 38 | ### 请不要在仓库中透露任何私密信息(如私钥及密码) 39 | 40 | 如果你的工作目录下包含任何私密信息文件,请参考 `gitignore` [相关教学文章](https://linuxize.com/post/gitignore-ignoring-files-in-git/) 来回避上传这些文件。 41 | 42 | # Step-by-Step Guide 43 | ## `git` 44 | `git` 是一个版本控制系统,在本次面试过程中,我们使用 `git` 来提交成品。 45 | 关于 `git` 的具体用法,请搜索相关文章,或参考这本书:[Pro Git 中文版](https://git-scm.com/book/zh/v2) 。 46 | ## 提交方式 47 | 在掌握了基本的 `git` 操作之后,就可以提交你的代码了! 48 | > 请注意,在进行每一步的同时,请思考每一步命令甚至每一个参数的作用。 49 | ### 在 Gitee 上配置 ssh key 50 | > [什么是 ssh ?](http://cn.linux.vbird.org/linux_server/linux_redhat9/0310telnetssh.php#ssh) 51 | #### 生成 ssh 密钥 52 | 如果你已有 ssh 密钥,可以跳过这个步骤。如果你没有,请参考:[生成 ssh 密钥](https://gitee.com/help/articles/4181#article-header0) 。 53 | #### 添加密钥到 Gitee 54 | 关于如何配置 Gitee ssh key ,参考:[添加 ssh 密钥到 Gitee 账户](https://gitee.com/help/articles/4191#article-header0) 。 55 | ### 新建 Private Repository 56 | 在 Gitee 上,新建一个 **Private** Repository ,并*命名为 `fall-2021-round-two`* 。 57 | ![Create Private Repo](./pics/create-private-repo-gitee.png) 58 | ### `git clone` 原本的 Repository 59 | 命令如下: 60 | 61 | ``` shell 62 | git clone --bare git@gitee.com:dinoallo/qsctech-2021-fall-round-two.git 63 | ``` 64 | ### `git push` 一份题目到自己的 Private Repository 65 | 进入 `qsctech-2021-fall-round-two` 文件夹,执行如下命令: 66 | 67 | ``` shell 68 | git push --mirror git@gitee.com:/fall-2021-round-two.git # 把 替换为你自己的 Gitee 用户名 69 | ``` 70 | 71 | ### `git clone` 自己的 Private Repository 72 | 提交题目的仓库是自己的 Private Repository ,之前克隆的原 Repository 就没用了,可以删除,并请不要提交到原 Repository 。 73 | 74 | ``` shell 75 | git clone git@gitee.com:/fall-2021-round-two.git # 克隆自己的私有仓库 76 | ``` 77 | 78 | ### Go hacking! 79 | 克隆完自己的仓库后,就可以开始在仓库文件夹下写程序了! 80 | 81 | ### `git push` 同步当前 commits 至 Gitee 82 | 当你想同步目前的工作进度,或者想提交代码时,运行以下命令: 83 | 84 | ``` shell 85 | git push -u origin 86 | ``` 87 | 88 | ### 提交 issue 到原仓库 89 | 如果你已经准备好让面试官 review 自己的成品了,请到原题目仓库新增一个 issue,格式请参考: 90 | ![Issue Example](./pics/issue-example-gitee.png) 91 | 92 | ### 给予面试官协作者权限 93 | 由于并非所有面试官都有 gitee 帐号,请直接将*仓库维护者*设为协作者: 94 | #### 仓库维护者 95 | * @dinoallo 96 | 97 | ![Invite collaborator](./pics/invite-collab-gitee.png "将面试官加到协作者中") 98 | ![Invite collaborator 2](./pics/invite-collab-gitee-2.png "输入面试官 ID") 99 | 100 | 面试官能正确查看你的仓库(见下方注意事项)后,会在 issue 下回复,提交就算完成了。 101 | 102 | ## 更新二面试题仓库 103 | 有时你会接到面试官让你更新仓库的通知,或者你已经很久都没有操作仓库了,你手边的仓库可能已经不是最新的了,为了避免你与面试官和其他面试者的信息不对等,我们必须得到最新版本的仓库。 104 | > 强烈建议大家要时常更新仓库,频率大概是半天到一天,如果面试官通知你,**一定要更新** 。 105 | 106 | ### 将原仓库设为你私有仓库的 remote 107 | 108 | ``` shell 109 | git remote add upstream git@gitee.com:dinoallo/qsctech-2021-fall-round-two.git # upstream 为原仓库 110 | ``` 111 | 这个步骤只需要做一遍即可,建议大家在仓库刚克隆下来时,执行这行命令,之后更新仓库便不需要重新执行一次了。 112 | 113 | ### 从原仓库获取更新并合并 114 | 115 | ``` shell 116 | git pull upstream master # 如果你的工作分支是 master 117 | ``` 118 | 如果你的 git 的暂存区没有任何东西,那么只需执行以上步骤就可以完成更新。 119 | 120 | 121 | #### 如果你已经用 `git add` 命令暂存了一些代码 122 | 我们可以利用 `git stash` 命令: 123 | ``` shell 124 | git stash 125 | ``` 126 | 然后按照上面所说的获取更新,再执行: 127 | 128 | ``` shell 129 | git stash pop 130 | ``` 131 | 之前的改动就会回来了。 132 | -------------------------------------------------------------------------------- /commit/pics/create-private-repo-gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/create-private-repo-gitee.png -------------------------------------------------------------------------------- /commit/pics/create-private-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/create-private-repo.png -------------------------------------------------------------------------------- /commit/pics/invite-collab-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/invite-collab-2.png -------------------------------------------------------------------------------- /commit/pics/invite-collab-gitee-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/invite-collab-gitee-2.png -------------------------------------------------------------------------------- /commit/pics/invite-collab-gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/invite-collab-gitee.png -------------------------------------------------------------------------------- /commit/pics/invite-collab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/invite-collab.png -------------------------------------------------------------------------------- /commit/pics/issue-example-gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/issue-example-gitee.png -------------------------------------------------------------------------------- /commit/pics/issue-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/commit/pics/issue-example.png -------------------------------------------------------------------------------- /crazy-jump/README.md: -------------------------------------------------------------------------------- 1 | > Contributor:@Switch 2 | 3 | # 写给幼儿园小朋友的 H5 小游戏入门 4 | 5 | ## 阅前须知 6 | 7 | - **本文主要面向几乎没有开发基础的人**, 如果对 canvas 有一定程度的了解的话, 强烈建议去看看隔壁的几道题目(qrcode 或 isit a prime), 继续下去可能会浪费生命中的几个小时 8 | - 以下会出现一些 Task 和简单的 Question, 所有的 Task 可以在你的代码中体现, 而对于 Question 来说, 希望你可以在目录下另附一个文档来写下你的答案(什么格式的文件都行, 不过希望尽可能不要使用[二进制文件](https://en.wikipedia.org/wiki/Binary_file)) 9 | - **除了文末的 Question 以外**, 所有的 Task 和 Question 都不会强制要求你完成, 你只需要尽自己最大的努力独立来做就好, 我们会乐于在二面的过程中和你交流你的经历和困难哦~ 以及, Question 的答案虽然基本都可以从搜索引擎中查到, 但是希望不要原封不动的复制粘贴, 而是融入自己的理解和思考 10 | - 文章中给出的链接大概都是英文版, 因为就如同小说一样, 再高深的译者也不能保证 100%保留原文的风味, 还希望大家尽可能去阅读英文文档, 培养见到英文文档不发怵的胆魄 11 | 阅读这些文档并不需要太高的英语水平, 大家都能考上浙江大学的话肯定都冰雪聪明没啥问题, 遇到个别不认识的单词就找个翻译软件翻译一下就好 ww 12 | ~~当然事实上, 确实有不少链接是有对应的中文版的, 实在想看的话可以自寻方式 XD~~ 13 | - 单纯完成任务不是目的, 在过程中有所成长才是最大的收获. 请记住: **我们看重的不仅仅是你的能力, 更重要的是你的态度** 14 | - _出题人并不是 Switch_~~_, 有事也请不要去惊扰 Switch_~~ 15 | - Have fun~ :-D 16 | 17 | ## 故事背景 18 | 19 | 三岁半的 Baby Switch 超喜欢玩各种小游戏 20 | 21 | 她最近突然想写游戏了, 我们和她一起做吧 22 | 23 | ![](meme.jpg) 24 | 25 | ~~一看就是懒得编了~~ 26 | 27 | ## 目标演示 28 | 29 | 演示动画 30 | 31 | 这只是一个方便大家理解我们要干什么的图, 相信大家肯定能创作出比这个美观, 比这个好玩的版本 ww 32 | 33 | ## 让我们开始吧 34 | 35 | ### 提前准备 36 | 37 | > 工欲善其事,必先利其器。———《论语·卫灵公》 38 | 39 | 为了获得更好的开发体验, 人们发明了各种有利于开发的工具, 你最好挑选一个用着趁手的工具, 这会大大的改善你的开发体验 40 | 41 | 如果你还不太清楚自己需要什么, 可以看看 Baby Switch 在用什么; 如果你不知道它们是什么, 你最好去求助于搜索引擎了解一下 42 | 43 | - [git](https://git-scm.com/) 44 | - [vscode](https://code.visualstudio.com/) (并请自行选择安装你喜爱的插件, 如果没有头绪或许可以提问 45 | 46 | ~~当然像这次这么简单的任务 完全可以使用记事本解决啦~~ 47 | 48 | ### Let's Code! 49 | 50 | #### 搭架子 51 | 52 | 首先, Baby Switch 打算让她的游戏在浏览器里运行. Baby Switch 在搜索引擎上查了一下, 认为自己应该使用 html + css + js 来完成任务, 所以她创建了一个文件夹, 然后新建了几个文件 53 | 54 | ```plain 55 | . 56 | ├── lmth.html 57 | ├── sj.js 58 | └── ssc.css 59 | ``` 60 | 61 | (我劝你**最好不要**像她这样起文件名..) 62 | 63 | > #### Task 64 | > 65 | > 请参照[MDN](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics)搭建一个 html 文件的框架, 其中应该至少包含: 66 | > 67 | > - 对网页标题的修改 (``) 68 | > - 对`.js`和`.css`文件的引入 69 | > - 一个大标题(`<h1>`或别的什么标签) 70 | > - 一个`<canvas>`, 并且请给它一个`id`, 这在之后应该会有大用 71 | 72 | 然后, 她就可以在浏览器中打开这个 html 文件啦, 她看到自己写的东西被展示出来, 非常开心 73 | 74 | 不过她觉得, 这个布局不太好看... 75 | 76 | > #### Task 77 | > 78 | > 通过编写 css 文件, 对标题和 canvas 实现水平居中 (其实只需要一点点 css, 如果能使用 flex box 来完成就更好了 w) 79 | > 80 | > - [不懂 css 的话可以戳这里](https://developer.mozilla.org/en-US/docs/Learn/CSS) 81 | > - [或许能用到的 flex box 指北](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) 82 | 83 | > #### Question 84 | > 85 | > 通过常识/查阅相关资料可以知道, canvas 标签是有`width`和`height`属性的, 而 css 中, 我们也可以给 canvas 设置`width`和`height`, 那么它们有什么不同呢? 如果把它们设置了不一样的值又会发生甚么呢? 请谈谈你的理解 86 | 87 | #### 画画 88 | 89 | 由于网页里元素就那么些, 那么我们想画一个游戏出来肯定就是要对那个 canvas 下手了. 90 | 91 | 根据 Baby Switch 浅薄的理解, 对 canvas 的控制应该是 js 的活, 因此她打开了 js 文件, 准备往里面填代码. 92 | 93 | 不过 Baby Switch 并不会 JavaScript, 所以她决定去找[一个 JS 教程网站](https://javascript.info/)学习一下 94 | 95 | 她发现, 自己似乎只需要学会[JavaScript Fundamentals 这一章节](https://javascript.info/first-steps)的内容就可以了, 而其中似乎有相当一部分和自己半年前接触到的 C 语言差不多的样子(也就是说大概 4.8.9.10.11.13.14.15 这几个部分草草过一遍就行啦), 所以她用了几个小时就学的差不多了, 相信冰雪聪明的你也一定没有问题哒~ 96 | 97 | 由于 Baby Switch 是纸片人, 所以她认为她不需要一个 3D 的游戏, 因此她只打算整一个 2D 版本的 98 | 99 | 那么要做什么呢? 她决定从先学习学习怎么画, 从画一个小人开始 100 | 101 | > #### Task 102 | > 103 | > 实现一个**在指定位置**绘制小人的函数, 你可以给它起名叫`drawHero`或者别的什么你喜欢的名字 (不过如果你能遵守[小驼峰命名规则](https://wiki.c2.com/?LowerCamelCase), 那会让 Baby Switch 很开心 104 | > 105 | > 这个小人最好有一个圆圆的脑袋, 身体的样子倒不是很重要, 或许你可以画一个三角形, 或者矩形? emm...五角星可能会有些奇怪, 但好像也不是不行 106 | > 107 | > 众所周知, 鲜艳大胆的用色会极大的吸引幼儿的注意, 所以, Baby Switch 会给她的小人涂一个鲜艳的颜色, 希望你也这样做 108 | > 109 | > 在这一步中, 如果你感到毫无头绪, 你可以看看[这里](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D), 它甚至会带你手把手画一个可爱的小房子 w 110 | 111 | > #### Question 112 | > 113 | > _如果你有一个具有良好代码提示的工作环境, 那么可能会更好的帮助你回答和体会这个问题_ 114 | > 115 | > 假如说, 你的 canvas 的 id 属性被赋值为了"my-canvas", 那么 116 | > 117 | > `const myCanvas = document.getElementById('my-canvas')` 118 | > 119 | > 这里的`myCanvas`这个变量的类型是什么? 120 | > 121 | > `getContext`是什么类型的方法? 122 | > 123 | > 为什么我们可以调用`myCanvas.getContext()`? 124 | 125 | 在你学会画小人之后, 那么场景里面有的元素你应该都会画了吧~ 126 | 127 | 比如小人起跳的平台, 比如力度条, 比如显示分数或者别的什么的.. 这里就请你自由发挥啦~ 128 | 129 | #### 画动画 130 | 131 | 那么显然, 我们现在可以渲染出静态的画面了, 但是如果作为一款游戏, 那么我们肯定是要让我们的小人动起来的 132 | 133 | 根据[wikipedia 上动画的定义](https://en.wikipedia.org/wiki/Animation), 我们很容易知道, 动画其实是一帧一帧的静画连续播放营造出的效果 134 | 135 | 就像现在 Baby Switch 喜欢看的各种动画片, 每秒会播放 24 帧, 也就是每秒会渲染 24 张图片(默认 1k 作画前提下) 136 | 137 | 那么我们想要播放动画, 自然也需要进行一个按帧的播放 138 | 139 | Baby Switch 非常幸运的找到了这样一个函数: [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) 140 | 141 | > #### Task 142 | > 143 | > 阅读文档(也可以不阅读), 调用`requestAnimationFrame`函数, 按帧渲染刚才你画的静画 144 | > 145 | > 想体会动感的话, 你可以在每一帧中修改一些参数的值, 让每一帧静画的渲染有一些区别 (比如修改蓄力条, 或者小人的位置什么的) 146 | 147 | > #### Question 148 | > 149 | > 你知道 setInterval 函数吗? 它是做什么的? 为什么做动画的时候常用 requestAnimationFrame 而不是使用 setInterval 每隔多少 ms 触发一次呢? 谈谈你的理解 150 | 151 | 那么我们现在已经会画动画了! 那么我们来添加最重要的动画吧 152 | 153 | > #### Task 154 | > 155 | > 实现一个小人起跳后的抛物线 156 | > 157 | > 你可能需要一点点数学/物理知识, 比如你要知道, 在斜上抛运动中, 横纵坐标与时间, 是具有函数关系的 158 | > 159 | > 这里的初速度啦, 重力加速度啦, 都可以由你来自由设置, 毕竟这是由你创造的世界~ 160 | > 161 | > _(Baby Switch 并没有上过高中, 所以这里可把她为难坏了_ 162 | 163 | #### 添加控制 164 | 165 | 这个游戏已经能动了, 但是还不能玩, 我们现在来让它能玩 166 | 167 | 我们有一些所谓的[GlobalEventHandlers](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers), 通过它们我们可以添加对于事件的相应, 比如按键盘啦, 动鼠标啦以及别的一些什么 168 | 169 | > #### Task 170 | > 171 | > 添加控制系统, 使得按下键盘时蓄力, 松开键盘后让小人起跳 172 | > 173 | > _Hint: 你可能会用到上面参考链接中的`onkeyup`或者`onkeydown`_, 当然或许你还想添加别的, feel free to do so~ 174 | 175 | #### 其他 176 | 177 | 好了, 要学的东西 Baby Switch 已经基本都会了, 相信你也基本都会啦, 现在就把刚刚学到的东西灵活运用吧~ 178 | 179 | > #### Task 180 | > 181 | > 生成位置和宽度随机的平台 182 | > 183 | > 添加小人有没有成功跳到对面平台的判断 184 | > 185 | > - 如果跳到了, 分数+1, 渲染下一个平台 (或许你可以把镜头往右, 或许你也可以把平台往左 186 | > - 如果没跳到, 游戏结束, 或许你需要渲染一个游戏结束的界面 187 | > 188 | > 提供至少一种游戏重新开始的方式 189 | > 190 | > 美化游戏的界面, 或许在画布里添一些飘忽不定的云, 或许在画布外贴个背景图片, 或许... 这是发挥创造力的好时机! 191 | > 192 | > 添加其他你觉得应该有的东西... 这是你的世界, 你想做什么都可以~ 193 | 194 | ### 分享 195 | 196 | 啊 上帝啊 我们就这样完成了一个游戏~ 197 | 198 | 我要把它分享给亲朋好友们耍~ 199 | 200 | 那么, 与其把这几个文件分享出去, 为什么不尝试把它在什么地方部署一下呢? 201 | 202 | 如果你还没有自己的服务器, [Github Pages](https://pages.github.com/)可以是一个选择. 203 | 204 | > #### Task 205 | > 206 | > 在一个什么地方用什么方式部署你的游戏 207 | 208 | ### Summary 209 | 210 | > #### Reflection 211 | > 212 | > 注意: **此题为必答题, 选择本试题的面试者请务必对该问题作出回答** 213 | > 214 | > 在完成整个项目的过程中, 你有什么心得和体会? 215 | > 216 | > 你觉得这篇文章你可以打几分? (满分 10, 保留到整数即可) 如此评分的原因是什么?还有什么意见或建议? 217 | > 218 | > 你还有什么想说的? 219 | 220 | 那么二面题就到这里了, 期待与你的见面~ 221 | 222 | 以上。 -------------------------------------------------------------------------------- /crazy-jump/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/crazy-jump/demo.gif -------------------------------------------------------------------------------- /crazy-jump/meme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/crazy-jump/meme.jpg -------------------------------------------------------------------------------- /is-it-a-prime/README.md: -------------------------------------------------------------------------------- 1 | > Contributor: @Switch @巧克力猫猫 2 | 3 | # 写给小学生的 TypeScript 类型元编程 4 | 5 | ## 阅前须知 6 | 7 | - 本文思维难度较大, 涉及到的概念会比较抽象, 可能更适合具有一定编程思维和编程基础的玩家, 如果不太能理解题目的话建议去看隔壁的几道题目。 8 | - 虽然题目中使用了 TypeScript, 但是涉及到的语法并不是很多, 而且文中应该会有所讲解, 希望有 TypeScript 基础的人都来看看本题, **不会 TypeScript 也是可以做的** 9 | - **所有需要你来做的部分, 都有`Not implemented yet`这样的字眼**. 如果你不想看文档, 请你善用搜索 10 | - 你可以把要补全后的代码写在任何地方, 比如一个`.ts`文件, 或许一个`.md`文件, 甚至一张纸的照片也可以, 然后按规定进行提交 11 | - **文末的总结问题必答** 12 | - Have fun~ :-D 13 | 14 | ## 故事背景 15 | 16 | 有一天, Pupil Switch 在做体操, 他在搜索引擎中遨游的时候, 发现 TypeScript 的类型系统好像很好玩的样子... 所以你也来玩吧... 17 | 18 | ## 任务 19 | 20 | 对 TypeScript 比较熟悉的同学都知道, 如果我们想判断一个**自然数**是否为素数, 那么我们可以这样写 21 | 22 | ```ts 23 | const isPrime = (x: number): number => { 24 | if (x < 2) return false; 25 | for (let i = 2; i * i < x; ++i) if (x % i === 0) return false; 26 | return true; 27 | }; 28 | ``` 29 | 30 | 当然这个太简单了没什么意思, 我们来尝试使用 TypeScript~~强大的~~类型系统, 来实现一个判断素数的类型 IsPrime. 在做完这道题后,你的代码能达到如下效果 31 | 32 | ```ts 33 | type check = IsPrime<18>; // type check = "F", 意思是 check 这个类型严格等于 "F" 这个字面量 34 | type check = IsPrime<13>; // type check = "T" 35 | ``` 36 | 37 | 也就意味着, 以下代码不会出现编译错误 38 | 39 | ```ts 40 | let check0: IsPrime<18> = "F"; 41 | let check1: IsPrime<13> = "T"; 42 | ``` 43 | 44 | 既然在预期中, `check0` 的类型, 即 `IsPrime<18>` 严格等于 `"F"`, 那么 `check0` 只能取 `"F"` 这个值, 那么赋值语句 `... = "F"` 一定不会出现编译错误. 45 | 46 | ## 让我们开始吧 47 | 48 | ### 提前准备 49 | 50 | 为了更好的完成这个任务, 你应该有一个趁手的开发环境, 并可以顺利的使用较新版本的 Node.js 和 TypeScript 51 | 52 | 以及, 如果你的编辑器具有良好的类型提示和报错, 那会让你事半功倍 53 | 54 | (当然, 逻辑思维足够强的人可以只用纸笔完成这个任务) 55 | 56 | ### 讲一点类型语法 57 | 58 | 这一部分我可能只会讲一点浅薄的理解, 如果大家对 TypeScript 感兴趣, 想更加认真的学习类型系统, 欢迎大家去查阅[The TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) 59 | 60 | #### 类型基础 61 | 62 | Emmmm, 虽然这个理解可能不是很正确, 但你可以暂时把 TypeScript 中的类型理解为集合. 63 | 64 | TypeScript 有数种基础类型, 比如 number, string, boolean, 以及 null, undefined 什么的, 如果你会 JavaScript, 那你应该也认识它们 65 | 66 | 当一个变量被标记为一个类型的时候, 表示它属于这个集合. 67 | 68 | ```ts 69 | const p: number = 2; // p属于number这个集合, 它只能是一个数字 70 | ``` 71 | 72 | 可以把类型赋值给一个类型, 相当于起了一个别名 73 | 74 | ```ts 75 | type MyType1 = number; // MyType以后就和number是一样的 76 | ``` 77 | 78 | 我们可以对这个集合做运算, 比如取并集: 79 | 80 | ```ts 81 | type MyType2 = number | string; // MyType 是数字或者字符串 82 | ``` 83 | 84 | 在 TypeScript 中, 字面量也可以作为类型, 此时, 这个值就**只能是**这个**字面量** 85 | 86 | ```ts 87 | type Four = 4; // 类型为Four就只能取4这个数字 88 | ``` 89 | 90 | 看起来没什么用? 但是可以结合上面的并操作: 91 | 92 | ```ts 93 | type ReqMethods = "GET" | "POST" | "PUT" | "DELETE"; // 类型为ReqMethods的变量的值只能是这四个字符串之一 94 | ``` 95 | 96 | 此外, objects 也可以作为类型, 你甚至可以指定每个元素的类型 97 | 98 | ```ts 99 | type Employee = { 100 | name: string; 101 | gender: "Male" | "Female"; 102 | salary: number; 103 | }; 104 | ``` 105 | 106 | 此外还有一种写法, 是定义 interface 107 | 108 | ```ts 109 | interface Employee { 110 | name: string; 111 | gender: "Male" | "Female"; 112 | salary: number; 113 | } // 和上面的基本等价 114 | ``` 115 | 116 | #### 奇怪的类型 117 | 118 | TypeScript 中, 有一些 JavaScript 中没有的类型, 比如 119 | 120 | - void 121 | 这个一般是用在函数的返回值上, 表示这个函数没有返回值 122 | - **never** 123 | 这个一般是用于不会返回的函数上(比如死循环), 表示这个函数永远不会返回 124 | - **unknown** 125 | 这个表示这个函数可能是任何类型, 在需要确定类型的时候需要做类型推断 126 | - any 127 | 这个表示让 TypeScript 不再对这个变量做类型检查, 在一些 TypeScript 代码规范中, 会有不允许使用 any 的情况出现 128 | ~~JavaScript = AnyScript~~ 129 | 130 | #### 泛型 131 | 132 | 举一个例子[(来自 Handbook)](https://www.typescriptlang.org/docs/handbook/2/generics.html): 133 | 134 | 我们想要一个复读机函数, 它会返回传入的参数: 135 | 136 | ```ts 137 | function identity(x: any): any { 138 | return x; 139 | } 140 | ``` 141 | 142 | 然后如果我们要做类型标注, 假如我们希望获得的返回值的类型跟传入的一样, 而不会给我们一个 any, 但我们又不想为每一个类型单独写一个 identity 函数, 我们可以使用泛型, 表示这个地方可以传入任何类型 (似乎和 C++里的 template 差不多?) 143 | 144 | ```ts 145 | function identity<T>(x: T): T { 146 | return x; 147 | } 148 | ``` 149 | 150 | #### extends 151 | 152 | TypeScript 中的 extends 是一个似乎被过度使用的关键词, 它有一些截然不同的用法 153 | 154 | - 首先, 它可以表示包含关系, 从集合的观点来看, `A extends B`表示 A 是 B 的子集(注意子集和真子集概念的区别). 在这种情况下, 类型为 A 的变量可以直接赋值给类型为 B 的变量 155 | - 其次, 在泛型中, 我们可以定义 156 | ```ts 157 | type Generated = Generator<T extends BasicType>; 158 | ``` 159 | 在这个例子中, 表示传入这个泛型处的类型必须是 BasicType 的子集 160 | - 然后, extends 可以用于判断句中 161 | ```ts 162 | type Flag = Checked extends BasicType ? true : false; 163 | ``` 164 | 这个表示, 如果 Checked 是 BasicType 的子集, 那么 Flag 表示 true, 否则表示 false.. 注意, 这里`A extends B`本身不具备 boolean 属性, 后面的`?xxx:xxx`这些都是不可以省略的. (另外, 或许值得注意的是, 如果 B 是一个字面量, 那么这是一个判断字面量是否相等的好方法~) 165 | 166 | 此外, 有一些特殊的规定. 167 | 168 | 在类型运算中, `never`是最小集(可以算是空集), 无论`Type`是什么, `never extends Type`都成立. 169 | 170 | 而`unknown`是最大集, 无论`Type`是什么, `Type extends unknown`也都成立. 171 | 172 | (由于个人习惯和喜好问题, 我们在这里不讨论`any`) 173 | 174 | #### 递归 175 | 176 | 类型定义中可以递归, 例如我们定义一个链表的节点, 我们可以 177 | 178 | ```ts 179 | type Node = { 180 | next?: Node; // ?:表示该属性可选 181 | value: any; 182 | }; 183 | ``` 184 | 185 | ## 开始做操 186 | 187 | 那么有了上面这些概念就差不多了, 我们可以来开始玩了. 188 | 189 | 我们发现, 我们可以拥有字面量表示类型, 我们有判断, 有递归, 这就意味着我们可以做很多很多事了. 190 | 191 | 那么 我们现在先来定义自然数. 192 | 193 | ### 自然数 194 | 195 | 我们应该都在小学数学课上听老师讲过[皮亚诺公理](https://en.wikipedia.org/wiki/Peano_axioms), 因此我们可以这样定义自然数 196 | 197 | 我们把自然数当作一个无限长的链表; 对于每一个自然数, 它的结构应该是这样的 198 | 199 | ```ts 200 | type Num = { 201 | prev: Num; // 前一个自然数 202 | zero: "T" | "F"; //这个数是否是0, 可以作为我们递归的结束条件 203 | // 你可以把 "T" 和 "F" 换成其他你喜欢的字面量, 只要它能表示一个二元的状态, 且在你接下来的编程中沿用你制定的规定 204 | }; 205 | ``` 206 | 207 | 我们于是可以这样定义 `0` 208 | 209 | ```ts 210 | type Zero = { 211 | prev: never; // 0没有前驱 212 | zero: "T"; 213 | }; 214 | ``` 215 | 216 | 既然我们已经有了对 `0` 的定义, 那么我们可以很轻松地定义出**得到一个数的后继的类型**和**得到一个数的前驱的类型**. 217 | 218 | ```ts 219 | type NextNum<T extends Num> = // Not implemented yet 220 | type PrevNum<T extends Num> = // Not implemented yet 221 | ``` 222 | 223 | *Hint: 我们知道, 在自然数中, 0 的后继是 1. 那么, `NextNum<Zero>` 就应当返回一个能正确表示 1 的类型. 更具体地, 1 的前驱是 0, 且 1 非 0.* 224 | 225 | ### 四则运算 226 | 227 | 有了自然数的定义, 我们就可以进行四则运算了. 很不幸, TypeScript 中字面量类型之间是没有关系的, 不能直接进行运算, 所以我们需要利用递归 228 | 229 | 在这里, 我们给出一下加法的样例, 观察到 $a+b=(a+1)+(b-1)$: 230 | 231 | ```ts 232 | type Add<T1 extends Num, T2 extends Num> = T2["zero"] extends "T" 233 | ? T1 234 | : Add<NextNum<T1>, PrevNum<T2>>; 235 | ``` 236 | 237 | 接下来请大家理解上面干了啥, 然后参照着实现减法 乘法 和整除. 请留意, 整除运算有可能依赖比较运算, 比较运算可能依赖减法运算. 238 | 239 | ```ts 240 | type Sub<// Not implemented yet 241 | type Mul<// Not implemented yet 242 | type Div<// Not implemented yet 243 | ``` 244 | 245 | _Hint: 乘法和除法过程中或许可以添加一个泛型值作为辅助, 并且你应该为所有辅助提供初值, 因为我们不会显式调用它_ 246 | 247 | _Hint: 做运算的时候请注意边界条件 (例如自然数的减法中没有负数, 除数不能为 0 等)_ 248 | 249 | ### 比较运算和逻辑运算 250 | 251 | 啊 这一部分相信大家冰雪聪明, 自己完成肯定没问题啦 252 | 253 | 在这一部分中, 你至少需要实现: `IsLessThan` `IsEqual` `And` `Or` `Not` 254 | 255 | ```ts 256 | // Not implemented yet 257 | ``` 258 | 259 | #### 判断素数! 260 | 261 | 啊 这应该是最简单的一步了吧 我们已经完成了所有工具 现在只需要把它们拼起来就可以啦 262 | 263 | ```ts 264 | type IsPrimeNum<// Not implemented yet 265 | ``` 266 | 267 | ### 更阳间的写法 268 | 269 | 根据我们上面的写法, 当我们传参的时候, 我们需要传大量的`NextNum<`然后套娃才能搞出这个数字 270 | 271 | 我们能不能整个方便的写法呢? 272 | 273 | 在这个部分, 我们来实现这样一个类型吧, 它接受一个数字, 然后会生成一个相等的, 我们定义的自然数 274 | 275 | ```ts 276 | type SetNum<T extends number// Not implemented yet 277 | // e.g. SetNum<5> = NextNum<NextNum<NextNum<NextNum<NextNum<Zero>>>>> 278 | ``` 279 | 280 | _Hint: 可以试试用一个不断变长的数组来做递归判断~_ 281 | 282 | 把这一步代入上一步, 这样我们就完成了我们的终极形态~ 283 | 284 | ```ts 285 | type IsPrime<T extends number> = IsPrimeNum<SetNum<T>>; 286 | ``` 287 | 288 | 这样我们就完成啦! 找几个数来测试一下对不对呢? 289 | 290 | ```ts 291 | //e.g. 292 | let check0: IsPrime<1> = "F"; 293 | let check1: IsPrime<2> = "T"; 294 | let check2: IsPrime<9> = "F"; 295 | let check3: IsPrime<23> = "T"; 296 | //... 297 | ``` 298 | 299 | 有编译错误吗? 300 | 301 | ## 写在最后 302 | 303 | ### 说明 304 | 305 | - 以上内容纯属奇技淫巧, 在正常开发中应该用不到, 就是来锻炼大脑的 306 | - 关于 TypeScript 类型系统, 本文只介绍了非常小的一部分, 想做体操的话可以试试[这个](https://typescript-exercises.github.io/) 307 | - 出题比较仓促, 可能 review 不够, 还请谅解 308 | 309 | > #### 总结 310 | > 311 | > 注意: **此题为必答题, 选择本试题的面试者请务必对该问题作出回答** 312 | > 313 | > 在完成整个项目的过程中, 你有什么心得和体会? 314 | > 315 | > 你觉得这篇文章你可以打几分? (满分 10, 保留到整数即可) 如此评分的原因是什么?还有什么意见或建议? 316 | > 317 | > 你还有什么想说的? 318 | 319 | 那么二面题就到这里了, 期待与你的见面~ 320 | 321 | 以上。 -------------------------------------------------------------------------------- /qr-code-playground/QRCodePlayground.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/qr-code-playground/QRCodePlayground.pdf -------------------------------------------------------------------------------- /qr-code-playground/README.md: -------------------------------------------------------------------------------- 1 | > Contributor:@保安 (0924 updated) 2 | 3 | # QR Code Playground 4 | 5 | **QR码**(英语:Quick Response Code;全称为**快速响应矩阵图码**)是二维码的一种,于1994年由[日本](https://zh.wikipedia.org/wiki/日本)[DENSO WAVE](https://zh.wikipedia.org/wiki/電裝)公司发明。QR来自英文 **Q**uick **R**esponse 的缩写,即快速反应,因为发明者希望QR码可以快速解码其内容。 6 | 7 | 我们本次使用的 QR 码遵循 [ISO/IEC 18004:2006](https://www.iso.org/standard/43655.html) 标准。事实上在 2015 年该标准又有了一次更新,但是生成 QR 码的基本步骤算法没有变化。 8 | 9 | 本文存在许多 $\mathrm{\LaTeX}$ 数学公式。请在本地渲染此 markdown 文档以获取最佳阅读体验。或者直接阅读同一目录下的 PDF 文件。 10 | 11 | 12 | 13 | > 本题工作量稍大,建议有一定编程基础的同学选择这题。当然,这样的难度差距在评价时也会被考虑在内。 14 | > 15 | > 本题需要各位自己动手实现网络上随处可见的二维码生成器,所以不希望大家直接参考网络上其他人的代码。 16 | 17 | 18 | 19 | ## 0. 前言 20 | 21 | 本题希望大家了解 QR 码的编码原理,学习 QR 码的生成过程,并使用一门编程语言自己动手从零开始实现一个基本的二维码生成器。 22 | 23 | 需要大家做到的: 24 | 25 | - 分阶段实现基本的二维码生成功能。具体要求会在下文中提出。 26 | - 有充分的、有条理的、尽可能简明规范的注释。 27 | - 学会自己在网络上查找合适的文档资料并自主学习完成。**这份教程并没有给出很多实现细节,这旨在驱使大家自己动手充分利用互联网获取自己想要的信息。** 28 | - 编写一份简明的文档,列出主要函数的调用方式、参数信息、重要注释等等。 29 | - 禁止调用他人或网络上已有的与二维码生成相关的库(可以使用 numpy 等工具库;最后的绘图步骤可以使用绘图库)。 30 | - 如果实现全部功能比较困难,可以只实现部分。比如不实现 version 2 的 QR 码生成功能,或者不实现掩码的选择等等。我们最终考察的并非只是题目完成度,更多的是考察学习能力与态度。 31 | 32 | 最后需要大家提交的文件包括源代码和文档。 33 | 34 | 35 | 36 | 下面是一些额外的要求,希望大家尽量去做: 37 | 38 | - 面向对象编程。具体的实现方式可以自行发挥(如数据类型、函数定义等),但我们希望你可以尝试一下面向对象的思路。 39 | - 使用 Python 语言。当然,如果你没有任何 Python 基础,可能会比较困难,那么你可以使用其他你较为熟悉的语言。如果你对 Python 非常熟悉,那么可以试试其他你没那么熟悉的语言,比如 Java 等。这条是希望大家在能力范围内勇于使用自己舒适区外的语言,同时学会查阅语言文档。 40 | - 规范化编程。规范化的编程不仅可以增加代码的可读性,还可以避免一些意料之外的 bug。 41 | - 模块化编程。模块化的编程便于调试,也可以使代码更易读。 42 | - 使自己的代码文件可以供他人作为库导入。为此,你撰写的文档应当足够明晰(不需要卷!)。 43 | 44 | 45 | 46 | 在开始之前,还需要简单介绍一下 QR 码。我们平常见到的很多正方形二维码都是 QR 码或者其变体。它们有个很明显的标志,就是三个角上的“回”字形标志,称为**定位标志**(Positioning detection markers)。扫描器在检测二维码时需要有着三个标志的存在才能定位二维码。当 QR 码尺寸较大时,其中还需要一些小的“回”字来辅助定位,称为**校正标志**(Alignment markings)。其中还有一些**定时标志**(Timing pattern)、版本信息与格式信息。剩余的部分就用来存储数据与纠错码。 47 | 48 | ![QR码的结构指示图](pics/QR_Code_Structure_Example.png) 49 | 50 | QR 码也有很多分类。有一种分类是静态 QR 码与动态 QR 码。我们本次只讨论静态 QR 码(Static QR Code)。静态码中可以存储的信息类别也有很多,比如 URL、plain text、邮箱、WiFi 连接信息等等。我们本次只考虑对 plain text 编码的实现。 51 | 52 | QR 码也有不同的尺寸,从最小的 version 1 (21 × 21 pixels) 到最大的 version 40 (177 × 177 pixels)。还有一种更小的 Micro QR Code。同时,QR 码需要支持纠错(Error correction),按照标准有 L, M, Q, H 从低到高四种不同的纠错能力等级,它们会分别需要不同的纠错码数量。**只需要大家实现 version 1~2 尺寸的 QR 码即可。**实现更大尺寸 QR 码的方法与实现小尺寸的并无太大区别,所以这里不要求实现较大尺寸 QR 码的生成。 53 | 54 | 在具体编码之前,需要先确定所生成的二维码的尺寸与纠错等级。每种尺寸与纠错等级对应一个能够表示的数据的最大容量。所以还需要根据要编码的信息灵活选择。具体的对应关系在 QR Code 的标准中都有规定。这里给出一部分: 55 | 56 | | Version / 版本 | Error correction level / 纠错等级 | Number of data bits / 数据位数 | 57 | | -------------- | --------------------------------- | ------------------------------ | 58 | | 1 | L | 152 | 59 | | 1 | M | 128 | 60 | | 1 | Q | 104 | 61 | | 1 | H | 72 | 62 | | 2 | L | 272 | 63 | | 2 | M | 224 | 64 | | 2 | Q | 176 | 65 | | 2 | H | 128 | 66 | 67 | 68 | 69 | ## 1. 信息编码 70 | 71 | 众所周知,二维码中的信息需要用黑白色块,也就是 0 和 1 来表示。这意味着我们需要把要传达的信息编码成 01 串。 72 | 73 | 按照标准,QR 码有这样几种编码方式: 74 | 75 | - Numeric mode: 适用于编码全数字的串。 76 | - Alphanumeric mode: 适用于编码仅含有阿拉伯数字、大写字母与一些常用符号的字符串。**本次我们只考虑这种编码方式。** 77 | - Byte mode: 适用于编码仅含有 ISO-8859-1 字符集字符的串。 78 | - Kanji mode: 适用于编码含有 Shift JIS 字符集的串。对于有中文、日文等字符的信息,需要使用这个编码模式。 79 | - 其他还有一些拓展的编码模式以及混合编码模式,这里不多赘述。 80 | 81 | 一般来说,编码信息时需要先合理选择使用的编码模式。但是本题只需要大家考虑 Alphanumeric 的编码方式,即只含有下表中 Char. 列所示的字符。 82 | 83 | ![image-20210920171831896](pics/Alphanumeric_Mode_Table.png) 84 | 85 | 其中 SP 表示空格。 86 | 87 | 具体的编码方式是: 88 | 89 | 1. 将需要编码的字符串中的每个字符转换为对应的数字值(见上表) 90 | 2. 两两分组。每一组的数按照一定方式计算出一个值。 91 | 3. 将每一组的值转换为一个一定长度的二进制串。位数不足需要补前导 0。 92 | 4. 将得到的二进制串首尾依次拼接得到我们需要的编码后的数据串(Encoded Data)。 93 | 94 | 之后,我们还要在这个串的头部加入一些必要的信息。 95 | 96 | 1. 加入编码方式的信息。每种编码方式都有一个对应的 4 位 01 串(Mode Indicator)。 97 | 2. 加入原数据字符串的字符个数。对于 version 1~9 尺寸的 QR 码,需要将位数转换为一个 9 位的 01 二进制串(Character Count Indicator)。 98 | 99 | 将编码方式、字符个数、数据串和一个 4 位终止符 0000 (Terminator)依次首尾连接,得到一个较长的 01 数据流(Bit stream)。 100 | 101 | 下一步是将这个数据流转化为许多**码字**(Codewords)。一个码字包含 8 位(8 bits),所以需要先将这个数据流的长度补成 8 的倍数。具体方法是在终止符前加入尽可能少数量的 0。之后,我们还需要把它补足到足够的位数(由选择的 QR 码尺寸与纠错等级决定)。补足时只需在末尾重复加入 11101100 和 00010001 两个串即可。这一步结束之后,就可以将这个数据流每 8 位视作一个码字,并进入之后的步骤。 102 | 103 | 至此,数据编码部分就完成了。 104 | 105 | 106 | 107 | > 对于这一部分做一些补充说明: 108 | > 109 | > - 实现可以调用某函数将字符串编码为一个 01 数据流(包括全部的头部信息、数据部分、补位部分)的功能。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 110 | > - 只需实现 Alphanumeric 的编码方式,其余的不做要求。 111 | > 112 | 113 | 114 | 115 | 116 | 117 | ## 2. 计算纠错码 118 | 119 | 这一部分看似需要一些数学与算法基础,但事实上如果没有基础也可以理解它的内容,因为它对于数学知识的运用并不深。 120 | 121 | 第一步是要将这若干个码字分组。当数据规模较大时,需要对每一组分别计算纠错码。但在本题中,由于需要实现的 QR 码尺寸不大,所以只需要分成一组(也就是不分组)。 122 | 123 | 接下去是对每一组计算纠错码。QR 码采用的是在[伽罗华域](https://en.wikipedia.org/wiki/Galois_field) GF(256) 上的[里德-所罗门码](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)。 124 | 125 | #### 伽罗华域 126 | 127 | 在伽罗华域(有限域)中数字的计算与我们平常的数字计算方式有所不同。 128 | 129 | QR 码纠错码的计算需要用到的是信息领域常用到的伽罗华域 GF(256)。这个域有有限个元素(准确来说是 256 个元素),并对加法和乘法满足封闭性、结合律、交换律、分配律,具有单位元和可逆性。 130 | 131 | 对于 GF(p) (p 是一个质数),我们可以定义其中的元素为 0 ~ p-1,加法 $\oplus$ 定义为 $a \oplus b = a + b \bmod p$,乘法 $\otimes$ 定义为 $a \otimes b = a \times b \bmod p$。 132 | 133 | 而对于里德-所罗门编码需要用到的 GF(2^8) 来说,其中的元素有很多表示的方式。其中一种,是把其中的元素都看作一个 7 次的多项式,每一项的系数只能是 0 或 1。具体地,例如一个元素在十进制下表示为 105,105 的二进制表示为 01101001,将二进制下的每一位都作为多项式表示下的系数,就可以得到 105 的多项式表示:$x^6 + x^5 + x^3 + 1$。 134 | 135 | **系数**的加法与减法等价,都是 $a + b \bmod 2$ ,也就是等同于 $a \text{ xor } b$。**系数**的乘法也就是 $a \times b \bmod 2$ 。 136 | 137 | 那么 GF(256) 中的元素加减乘法也就可以得到定义: 138 | 139 | **加法**即为将两元素的多项式表示进行一次多项式加法。其中系数相加/减时满足上面所说的运算法则。 140 | 141 | 例如十进制表示下的 105 与 27 相加:它们的多项式表示为 $x^6 + x^5 + x^3 + 1$ 与 $x^4 + x^3 + x + 1$ 。相加结果为 $x^6 + x^5 + x^4 + x$。 142 | 143 | **乘法**即为将两元素的多项式表示进行一次多项式乘法。这里的多项式乘法满足分配律与交换律,所以可以拆开分项相乘再相加。结果多项式的次数可能大于 7 次,所以要对一个“素数”取模。我们选用这个域下的一个“素数” 285,它的多项式是 $x^8 + x^4 + x^3 + x^2 + 1$。这个多项式被称为本原多项式,它在 GF(256) 上不可约,所以它就像是一个“素数”。当乘法的结果大于 7 次时,我们要把结果多项式对本原多项式取模。取模的过程使用**长除法**,这与平常用的长除法类似,只是用异或操作代替了减法。 144 | 145 | 下面就是一个长除法求余的例子。 146 | 147 | ``` 148 | 1010001111010 149 | ^ 100011101 150 | ------------- 151 | 0010110101010 152 | ^ 100011101 153 | ----------- 154 | 00111011110 155 | ^ 100011101 156 | --------- 157 | 011000011 158 | ``` 159 | 160 | 这样计算乘法有些麻烦。 161 | 162 | 所以需要引入域中元素的另一种表示:对数表示。每个元素都可以表示为 $\alpha^x$ ,这里 $\alpha = 2$。利用 $\alpha$ 可以生成出 GF(256) 中的所有元素。具体方式见下: 163 | 164 | | $\alpha^x$ | 二进制表示 | 十进制表示 | 多项式表示 | 165 | | -------------- | ---------- | ---------- | --------------- | 166 | | $\alpha^0 = 1$ | 0000 0001 | 1 | $1$ | 167 | | $\alpha^1$ | 0000 0010 | 2 | $x$ | 168 | | $\alpha^2$ | 0000 0100 | 4 | $x^2$ | 169 | | $\alpha^3$ | 0000 1000 | 8 | $x^3$ | 170 | | $\alpha^4$ | 0001 0000 | 16 | $x^4$ | 171 | | $\alpha^5$ | 0010 0000 | 32 | $x^5$ | 172 | | $\alpha^6$ | 0100 0000 | 64 | $x^6$ | 173 | | $\alpha^7$ | 1000 0000 | 128 | $x^7$ | 174 | | $\alpha^8$ | 0001 1101 | 29 | $x^4+x^3+x^2+1$ | 175 | | $\alpha^9$ | 0011 1010 | 58 | $x^5+x^4+x^3+x$ | 176 | | $\alpha^{10}$ | 0111 0100 | 116 | $x^6+x^5+x^4+x^2$ | 177 | | $\alpha^{11}$ | 1110 1000 | 232 | $x^7+x^6+x^5+x^3$ | 178 | | $\alpha^{12}$ | 1100 1101 | 205 | $x^7+x^6+x^3+x^2+1$ | 179 | | $\alpha^{255}$ | 0000 0001 | 1 | $1$ | 180 | 181 | 简单来说,对于二进制表示,每次将前一项乘以 2 (也就是左移一位)。如果得到的结果大于 255,那么异或上 285(之前提到的“素数”);对于多项式表示,每次将前一个多项式乘上 $x$ ,如果得到的次数大于 7 次,那么减去(注意系数的加减法等同于异或)本原多项式 $x^8 +x^4+x^3+x^2+1$。 182 | 183 | 以此类推,最后得到 $\alpha^{255} = 1$ 时为止。这样以来,我们就可以利用对数表示来更快的计算乘法。 184 | 185 | 众所周知,$x^a \cdot x^b = x^{(a+b)}$ 。这里也一样,$\alpha ^x \times \alpha^y = \alpha^{(x+y)}$。当 $x+y$ 的值大于 255 时,由于 $\alpha^{255} = 1$,所以可以将指数对 255 取模。 186 | 187 | 举个例子,将十进制表示下的 105 与 27 相乘。105 的对数表示是 $\alpha^{58}$,27 的对数表示是 $\alpha^{248}$,$105 \otimes 27 = \alpha^{58 + 248} = \alpha^{306} = \alpha^{306 \bmod 255} = \alpha^{51}$ 。$\alpha^{51}$ 的十进制表示是 10。这一运算结果可以用之前讲的多项式乘法运算的方法验证。 188 | 189 | 使用对数表示计算乘法非常简便,唯一的问题是离散对数的求解没有优秀的方法。但因为 GF(256) 中元素个数不多,可以通过预先处理后查表的方式解决。 190 | 191 | #### 里德-所罗门码 192 | 193 | 了解了在伽罗华域中的计算之后,就可以来计算需要用到的纠错码了。 194 | 195 | 首先,需要计算出一个信息多项式(Message Polynomial)$M(x)$。这个多项式并非是伽罗华域中某一元素的多项式表示,而是一个**系数均为伽罗华域中元素**的多项式。$M(x) = p_{n-1}x^{n-1} + p_{n-2}x^{n-2} + p_{n-3}x^{n-3} + \cdots p_1x + p_0$。这里的系数就是在**信息编码**步骤中最后计算出的**码字**。将码字依次转换为十进制,按顺序当作是 $M(x)$ 的系数。而式中的 $n$ 就是这些码字的数量。 196 | 197 | 我们还需要一个生成多项式(Generator Polynomial)$G(x)$。计算方法是 $\displaystyle G(x) = \prod_{i=0}^{m-1} \left(x-\alpha^i\right)$。其中 $m$ 等于由纠错等级和尺寸决定的纠错码码字的数量。比如,version 为 1,纠错等级为 M 的 QR 码所需的纠错码码字数量为 10 个。所以上式中 $m = 10$ 。注意这个多项式的系数也是伽罗华域中的元素,所以加减乘法均应按照上文讲到的方法进行。 198 | 199 | 里德-所罗门码的计算公式其实很简单,就是将 $M(x) \cdot x^m$ 对 $G(x)$ 求余。具体来说,求一个次数小于 $m$ 的多项式 $R(x)$ ,使得 $M(x)\cdot x^m = D(x)G(x) + R(x)$。$R(x)$ 就是所求的余数多项式。而 $R(x)$ 每一项系数的十进制表示就是所求的纠错码的码字。 200 | 201 | 202 | 203 | 为了帮助大家 debug,这里给出 `HELLO WORLD` 在 version 1, 纠错等级为 M 时的纠错码:196 35 39 119 235 215 231 226 93 23。 204 | 205 | 206 | 207 | > 对于这一部分进行一些补充说明: 208 | > 209 | > - 实现可以调用某函数根据尺寸和纠错能力等级计算一串码字的纠错码的功能。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 210 | > - 不要求在性能上进行优化,但至少应在能接受的时间长度内计算出答案。 211 | > 212 | 213 | 214 | 215 | 216 | 217 | ## 3. 填充矩阵 218 | 219 | 在进行填充之前还需要将之前算出的全部数据按一定的顺序排列。但是此次采用的小尺寸 QR 码只需把第一部算出的数据简单地放在第二步算出的纠错码前面就行。之后把全部的码字转换回二进制串,将其首尾相接,按照标准补足剩余的位数。 220 | 221 | 得到数据串后,按步骤填充矩阵。 222 | 223 | 1. 先绘制定位标志、校正标志、定时标志 224 | 2. 预留出版本信息、格式信息的区域 225 | 3. 从右下角开始按规定顺序填充数据串。1 为黑色,0 为白色。注意避开已填充的位置和预留出的区域。 226 | 227 | 228 | 229 | 230 | 231 | > 对于这一部分进行一些补充说明: 232 | > 233 | > - 实现可以调用某函数将数据流码字和纠错码码字转换为最终的 01 数据串的功能。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 234 | > - 实现可以调用某函数按照尺寸填充 QR 码定位标志、校正标志、定时标志等非数据位置的功能。这个函数仅需填充定位标志等,不需要填充数据位置。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 235 | > - 实现可以调用某函数将 01 数字串填充进 QR 码矩阵的功能。这个函数仅需填充数据串,不需要填充定位标志等位置。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 236 | > 237 | 238 | 239 | 240 | 241 | 242 | ## 4. 选择掩码 243 | 244 | 刚填充好的 QR 码矩阵很容易出现大面积连续的同色色块。为了便于扫描器的检测,我们需要对这个二维码选择一个合适的掩码图案。掩码只会在数据和纠错码部分进行操作,不会影响其他定位用标志以及版本信息等重要信息。而掩码其实就是有规律地对某些位置进行异或操作(更简单地来说就是翻转黑白色)。 245 | 246 | 可选的掩码图案有 8 种。需要依次评估每个掩码的罚分(Penalty)。具体来说,有 4 个评估测试。分别是: 247 | 248 | 1. 连续同色色条检测。 249 | 2. 同色色块检测。 250 | 3. 特殊模式图案检测。 251 | 4. 黑白比例检测。 252 | 253 | 每条检测都规定的检测方式与罚分。最后应在 8 种掩码图案中选取罚分最少的那一种。 254 | 255 | 256 | 257 | 258 | 259 | > 对于这一部分进行一些补充说明: 260 | > 261 | > - 实现可以调用某函数将已填充好定位等标志、数据部分以及纠错码部分的 QR 码矩阵选择并应用掩码的功能。需要记录选择的掩码图案编号(可以通过函数返回值返回,也可以记录在成员变量中)。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 262 | > 263 | 264 | 265 | 266 | 267 | 268 | ## 5. 填充格式信息 269 | 270 | 格式信息串包括纠错等级、掩码图案编号信息,是一个 5 位的 01 串。 271 | 272 | 由于格式信息串十分重要,所以还需要为计算格式信息串单独计算纠错码。这里的纠错码与之前的稍有不同。QR 码标准中规定了这一步骤所使用的生成多项式(Generator Polynomial)为 $x^{10} + x^8 + x^5 + x^4 + x^2 + x + 1$。 273 | 274 | 这次直接将格式信息串的每一位作为信息多项式的系数。然后将 $M(x) \cdot x^{10}$ 对 $G(x)$ 求余。余数多项式的系数依次相接就可以得到一个 10 位的 01 纠错码串。 275 | 276 | 将两串拼接,得到一个 15 位的 01 串。再把该串异或上另一个规定的掩码串,得到最终能够填入 QR 码中的格式信息串。 277 | 278 | 不难发现,这个格式信息串只与纠错等级和掩码图案编号有关。所以可以根据这两个信息直接查表得到。 279 | 280 | 版本信息串只会在 version 7 及以上尺寸的 QR 码中用到。所以这里不做介绍。 281 | 282 | 计算得到格式信息串之后将其按一定顺序填充进预留出的位置中即可。 283 | 284 | 至此,QR 码已完全生成。 285 | 286 | 287 | 288 | 289 | 290 | > 对于这一部分进行一些补充说明: 291 | > 292 | > - 实现可以调用某函数根据纠错等级和掩码图案编号计算格式信息串的功能。计算出的格式信息串应包含纠错码,并已经过掩码。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 293 | > - 实现可以调用某函数根据纠错等级和二维码尺寸生成最终二维码的功能。这个函数的具体调用方式可以自行定义,但请把这个函数列入文档中。 294 | > 295 | 296 | 297 | 298 | 299 | 300 | ## 6. 绘制图片 301 | 302 | 如果你还有余力,那么可以尝试将生成的二维码 01 矩阵转化为正式的图片输出。这一步可以调用你喜欢的绘图库。 303 | 304 | 如果你实现了这一功能,请将绘制图片函数的调用方式写入文档。 305 | 306 | 307 | 308 | 为了帮助大家调试,这里给出 `HELLO WORLD` 的 version 1,纠错能力等级为 M 的 QR 码图片: 309 | 310 | ![Code](pics/Code.png) 311 | 312 | 313 | 314 | ## 7. 总结与反思 315 | 316 | 如果对本题有什么疑问或者何处表述有错误,请联系 @保安 。 317 | 318 | 在文档中,需要你在开头或者结尾部分回答下列问题: 319 | 320 | 1. 你觉得解决这个任务的过程有意思吗? 321 | 2. 你在网上找到了哪些资料供你学习?你觉得去哪里/用什么方式搜索可以比较有效的获得自己想要的资料? 322 | 3. 在过程中,你遇到最大的困难是什么?你是怎么解决的? 323 | 4. 完成任务之后,再回去阅读你写下的代码和文档,有没有看不懂的地方?如果再过一年,你觉得那时你还可以看懂你的代码吗? 324 | 5. 在完成任务之后,如果要求你在已写好的代码基础上再加入生成 version 3~40 的二维码的功能,你是否会选择重新规划代码结构并重写大部分代码? 325 | 6. 其他想说的想法或者建议。 326 | 327 | 那么二面题就到这里了, 期待与你的见面~ 328 | 329 | 以上。 -------------------------------------------------------------------------------- /qr-code-playground/pics/Alphanumeric_Mode_Table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/qr-code-playground/pics/Alphanumeric_Mode_Table.png -------------------------------------------------------------------------------- /qr-code-playground/pics/Code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/qr-code-playground/pics/Code.png -------------------------------------------------------------------------------- /qr-code-playground/pics/QR_Code_Structure_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/qr-code-playground/pics/QR_Code_Structure_Example.png -------------------------------------------------------------------------------- /to-frontend-newbie/README.md: -------------------------------------------------------------------------------- 1 | > Contributor: 喵刀🐱 2 | 3 | # 0. 开始之前 4 | 5 | ## 一些前言 6 | 7 | 你好啊👋欢迎阅读这本《前端入门指北》。 8 | 9 | 在报名和一面的过程中,我看到许多表示对前端有强烈兴趣的同学,我很高兴有这么多志同道合的朋友,也祝大家能顺利加入求是潮,与前端小组一起参与Web开发。 10 | 11 | 这本指南主要面向没有学习过任何编程语言、不了解或了解一点点网络开发的朋友。**如果你认为你已经对开发有一定的理解,或者觉得Tasks中⭐️标注的问题你能1h内完成,可以移步Baby Switch的crazy-jump项目**。我会先扩展一些可能需要的“程序员素养”,再向你介绍所谓的前端三件套,最后带你入门React框架(由于题目体量的限制,此处不会是重点)。如果你并不是完全没有基础,可以选择性阅读。《指北》的最终目标是希望你入门现代前端,对未来的学习路径有一定的认识。在二面时,我们会让你谈谈你对这段时间学习的理解。 12 | 13 | 《指北》会解决这些问题: 14 | 15 | 1. 什么是文档?我为什么要看文档? 16 | 2. 当我遇到开发问题时(例如报错),我该如何解决? 17 | 3. 为什么我要使用命令行?我该如何装包? 18 | 4. 如何快速了解前端三件套?我应该学到什么程度? 19 | 5. React是什么东西?我该如何搭建一个前端项目? 20 | 21 | 请注意,这本《指北》是一篇指导性教程,希望能达到授人以渔的目的。文档编写者能力有限,不可能事无巨细,欢迎捉虫。 22 | 23 | ## 需要完成的Tasks 24 | 25 | (部分)阅读正文之后,你需要完成这些任务,其中`E`表示是附加题。其中,前7题的截图以markdown格式提交,最后一题需提交你的代码。 26 | 27 | - [ ] 下载Windows Terminal,并截图展示。 28 | 29 | - [ ] E:尝试对Terminal进行美化,并将你美化后的终端界面截图。 30 | 31 | - [ ] 安装Nodejs LTS/Current (Latest)版本,并截图展示你所安装的版本。 32 | 33 | - [ ] E:如果使用了`nvm`管理nodejs版本,那么使用`nvm list`指令展示你的环境中安装的全部版本。 34 | 35 | - [ ] E:把npm的源更换为淘宝源,并截图展示。 36 | 37 | - [ ] E:安装yarn,并展示你所安装的yarn版本。 38 | 39 | - [ ] 使用`create-react-app`创建你的React项目,并截图展示本地运行的首页。 40 | 41 | - [ ] ⭐️现在你有一大批结构类似的数据,例如这样: 42 | 43 | ```json 44 | [ 45 | { 46 | "sentence": "一个细胞里,却分裂出了两种截然不同的命运。", 47 | "from": "秦明「法医秦明」" 48 | }, 49 | { 50 | "sentence": "人间忽晚,山河已秋。", 51 | "from": "亦沫不吃鱼「人间忽晚」" 52 | }, 53 | { 54 | "sentence": "失去故土的花朵,回不去,却也离不开。", 55 | "from": "夏达「长歌行」" 56 | } 57 | ] 58 | ``` 59 | 60 | 现在,你需要将这里面的内容,批量显示在网页中。 61 | 62 | 由于篇幅限制,我不会提供更多的数据,但你需要自己添加更多的“小卡片”数据(当然,将这3句话复读多次也是可以的)。你需要使用flexbox来对每一个“小卡片”(也就是一句话以及它的出处)进行排版,需要注意一条语句的最小宽度,并在合适的时候换行。 63 | 64 | ⚠️本题需要你使用JS将文本插入到html中,而**不是在html文件中重复添加文本(硬编码)**。如果🐱发现你没有使用任何DOM树操作方法,后果很严重。 65 | 66 | > Hint:你可能会使用的技术 67 | > 68 | > - flexbox 69 | > - 数组方法 70 | > - DOM树操作 71 | 72 | 效果如下: 73 | 74 | <img src="image-20210921150424436.png" alt="image-20210921150424436" style="zoom:45%;" /> 75 | 76 | # 1. 文档 77 | 78 | > 当我要学习一门新语言、一个新框架或者一个新工具时,我该怎么学? 79 | 80 | 当我从高中毕业的时候,我对这个问题也一头雾水,我在初学前端时也走了很多弯路,看过b站的视频、翻过5年前的博客...不过,视频和其他开发者的博客并**不是第一手知识**,有些教程甚至是**过时**的。因此,我强烈不推荐参与二面的同学通过看b站、查CSDN博客的方式学习接下来要介绍的前端内容。如果你非常想知道我为什么这样建议,欢迎和我讨论。 81 | 82 | 对于开发者而言,查阅官方文档是最好的选择—— 83 | 84 | <img src="image-20210918113322477.png" alt="React文档首页" style="zoom:50%;" /> 85 | 86 | 文档的内容通常有快速上手(Tutorial)、文档(Docs)、API文档(API)以及可能会出现的常见问题模块(Q&A),文档一般都能帮助你完成~~从入门到入土~~的全过程,堪称一条龙服务。 87 | 88 | **不要因为文档是英文就跑路!**我推荐你尽量阅读英文文档,因为它们大多数时候都是最新版本。当然,如果你认为阅读起来存在一些困难,也可以尝试将语言切换为中文(目前已经有不少文档有中文翻译),但是需要注意的是,有些中文文档不一定是最新的。同时,不是所有的文档都有中文翻译。 89 | 90 | 希望你从一开始就养成翻阅文档的好习惯👍 91 | 92 | # 2. 开发问题 93 | 94 | > 如何使用网络资源解决大部分问题? 95 | 96 | 在过去的面试中,我们也强调过如何向二面群中的负责解答问题的同学提问。[提问的智慧(此处是中文翻译版)](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)是每一位开发者应该了解的文档。 97 | 98 | 不过在提问之前,我先简单向你分享我自己解决问题的过程,相信你能通过这个方式自己解决大部分编程问题。 99 | 100 | 1. 找到报错提示,例如这样: 101 | 102 | <img src="image-20210918121242153.png" alt="Failed to compile!" style="zoom:50%;" /> 103 | 104 | 这是一个常见的React编译报错,第一行是出错的源文件路径,第二行则是出错原因。 105 | 106 | 2. 直接从报错描述中解决 107 | 108 | 有一些报错会直接给出可能的解决方案。这个例子就是如此`Did you want a JSX fragment <>...</>?`。 109 | 110 | 3. Search engine! 111 | 112 | 如果你无法用自己的能力解决问题,我推荐你通过搜索引擎查询解决方案。 113 | 114 | - 我**强烈不推荐百度👎**如果你有过百度搜索开发问题的经历,你一定不会对那些垃圾信息感到陌生。随着你深入学习前端开发,百度搜索结果对你的帮助会越来越少。并不是在说百度一无是处,只是希望你能有意识地使用下面两款搜索引擎。 115 | 116 | - 如果你没有科学上网的工具,**Bing海外版**是一个可选的搜索引擎。 117 | - 如果你能够访问不存在的网站,那么**Google**当然是很好的选择。 118 | 119 | 一般来说,你只需要把报错信息贴进搜索框,就能够搜索到对应问题。 120 | 121 | <img src="image-20210918133011325.png" alt="Google一下" style="zoom:40%;" /> 122 | 123 | 4. Stackoverflow! 124 | 125 | 现在向你隆重推荐[stackoverflow](https://stackoverflow.com),开发的大部分问题都能在这个网站上找到答案,通常,这个网站会出现在搜索引擎结果的第一条。当然,是全英文。(虽然访问速度很慢,但它的确**没有被墙掉**) 126 | 127 | <img src="image-20210919024240910.png" alt="Stackoverflow" style="zoom:50%;" /> 128 | 129 | 5. 向面试官求助 130 | 131 | 如果你仍然无法解决问题,向负责解答问题的同学提出问题是最后的选择。但请注意,不要总是提出一些`Stupid question` 132 | 133 | 怎样的问题才是好问题?欢迎阅读二面题综述中《提问的智慧》😸 134 | 135 | # 3. 命令行 136 | 137 | 对于大部分过去没有接触过编程的同学,在使用电脑的过程中都几乎不会见到命令行界面(CLI)。但从现在开始,你要做的很多事情都不得不用命令行解决。 138 | 139 | ## Terminal! 140 | 141 | 在这里,我首先向你推荐一款比较美观的命令行界面应用,[Microsoft Terminal](https://www.microsoft.com/zh-cn/p/windows-terminal/9n0dx20hk701?activetab=pivot:overviewtab)。 142 | 143 | <img src="image-20210918211357404.png" alt="应用商店页面" style="zoom:45%;" /> 144 | 145 | Terminal在2019年被微软公布,集成了PowerShell、CMD(就是你通常在百度经验中遇到的那个cmd)以及WSL,你可以暂且不必了解这三样是什么,直接下载Terminal并启动即可。 146 | 147 | 出厂设置的Terminal并没有想象中的那么美观,至少不像应用商店展示的那样花里胡哨。但好在你可以对它进行美化,至少在后续的开发中,它会显得赏心悦目一些。 148 | 149 | 祝贺🎉你从此开始了和命令行界面斗智斗勇的生活~ 150 | 151 | ## Node JS! 152 | 153 | 对于前端开发者来说,JS的运行环境和包管理器是跑不了的,也就是`nodejs`和`npm`(Node Package Manager)。 154 | 155 | <img src="./image-20210918185655361.png" alt="Nodejs官网" style="zoom:50%;" /> 156 | 157 | 由于`nodejs`版本会不断更新,必要的时候你可能会在同一台电脑上安装好几个版本的nodejs,**推荐**你现在就使用nodejs的版本管理器`nvm`(Node Version Manager)管理你的node版本。 158 | 159 | 如果你是Windows选手并且不使用WSL,那么需要使用[nvm-windows](https://github.com/coreybutler/nvm-windows),在releases页面中下载最新版本。当然,你也可以直接安装Nodejs。 160 | 161 | ## 包&&包管理器 162 | 163 | > ~~既然要做胎教,自然要贯彻到底。~~ 164 | 165 | 你可能已经听说过这个名词`package`,也可能是在《指北》里第一次听说。这里我们用常用的一个比喻来形容,搭建一个前端项目好比造一台汽车🚗。现代的前端项目不会要求你从造出一个个零部件开始,相反,开源社区中已经有了很多可以直接使用的代码,它们可以直接用在你的项目中。 166 | 167 | 在这里,包的概念出现了,它们作为车的零部件,被直接用来组装你的成品车。也就是说,通过引入若干已有的包,你可以更快地搭出更复杂的网页,而不再需要重复造轮子。 168 | 169 | 好了,看起来装包能帮我们解决很多麻烦,那我们从哪里去找这样的包呢?包管理器其实帮你解决了这样的问题,你只要在电脑里装好包管理器,并知道你想要的包的名字,那么就可以通过包管理器快速下载。在MacOS上,我们常用`Homebrew`;在前端开发里,我们则使用`npm`或者`yarn`。 170 | 171 | (你可能想问Windows难道不配吗?事实上在Windows上也有类似的包管理器,例如`Chocolatey`,如果你喜欢,也可以尝试使用它) 172 | 173 | 上一个小节里安装Node JS之后,`npm`会自动出现在你的电脑里,它和`yarn`都是用来管理JavaScript开发中所需要的包。在后续的开发中,你会经常和它们打交道。在这里,我们又说到了一个新的包管理器`yarn`。`yarn`和`npm`之间几乎可以无缝切换,不过`yarn`的速度更快(至于为什么快,感兴趣的同学可以自己查询),我非常推荐你使用,因为它会让你的装包过程没那么痛苦。 174 | 175 | 在你要装包之前,我推荐你先给`npm`换一个淘宝源,换源之后,装包的速度也会有很大的提升(当然,还是没有yarn快)。至于怎么做,你可以尝试使用搜索引擎🔍来完成。 176 | 177 | # 4. 前端三件 178 | 179 | 终于,在使用了三个部分的内容长度介绍了一些你可能需要了解的知识之后,我们可以正式进入前端的学习了。 180 | 181 | 你可能迫不及待地想知道我会费多少笔墨来帮助你入门前端。不幸的是,至少在这场二面,我几乎不会教你任何内容。 182 | 183 | 不过,我会向你提供比较基础向的文档、教程。毕竟,它们才是专业的。我显然不会让你把所有的文档全部读完,这不现实也很枯燥,更多的内容你可以在今后遇到问题的时候回过头来查询。 184 | 185 | 这里是一些`HTML`, `CSS`和`JS`的参考资料: 186 | 187 | - [HTML in MDN (zh-CN)](https://developer.mozilla.org/zh-CN/docs/Learn/HTML) 你至少需要阅读以下几篇文档: 188 | 189 | 1. [开始学习 HTML](https://developer.mozilla.org/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started) 190 | 191 | - [CSS in MDN (zh-CN) ](https://developer.mozilla.org/zh-CN/docs/Learn/CSS) 你至少需要阅读以下几篇文档: 192 | 193 | 1. [什么是CSS](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS) 194 | 2. [让我们开始CSS学习之旅](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps/Getting_started) 195 | 3. [flex 布局的基本概念](https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) 196 | 197 | - [JavaScript in The Modern JavaScript Tutorial](https://zh.javascript.info) 你至少需要阅读以下几篇文档: 198 | 199 | 1. [变量](https://zh.javascript.info/variables) 200 | 2. [值的比较](https://zh.javascript.info/comparison) 201 | 3. [数据类型](https://zh.javascript.info/types) 202 | 4. [函数](https://zh.javascript.info/function-basics), [函数表达式](https://zh.javascript.info/function-expressions), [箭头函数,基础知识](https://zh.javascript.info/arrow-functions-basics) 203 | 5. [字符串](https://zh.javascript.info/string) 204 | 6. [数组](https://zh.javascript.info/array), [数组方法](https://zh.javascript.info/array-methods) 205 | 206 | 如果你已经修过C小程,那么以下几篇你可以略过: 207 | 208 | 1. [条件分支:if 和 '?'](https://zh.javascript.info/ifelse) 209 | 2. [逻辑运算符](https://zh.javascript.info/logical-operators) 210 | 3. [循环:while 和 for](https://zh.javascript.info/while-for) 211 | 4. ["switch" 语句](https://zh.javascript.info/switch) 212 | 213 | 阅读并实践之后,你应该有了完成Tasks中有关前端综合内容的能力。 214 | 215 | 如果你不了解应该使用什么编辑器来进行开发,那么我建议你看看[VS Code](https://code.visualstudio.com),并根据你自己的喜好(或一些推荐)下载合适的插件。 216 | 217 | # 5. React JS 218 | 219 | 恭喜🎉如果你能顺利实现第4部分的练习,你已经能够尝试使用React JS框架了。在我看来,在基础上浪费太多时间也会太快地消磨初学者的意志。我更希望的是在今后的实践中你能在遇到问题时,还记得回过头来翻阅MDN,或者通过第2部分中介绍的方式解决问题。 220 | 221 | 总之,接下来我将会简单介绍React以及如何搭建一个React单页应用,这也是《指北》的最后一个内容。 222 | 223 | 随着前端的发展,现代前端已经进入了框架的时代。也许你之前没有听说过`React`, `Vue`或者`AngularJS`,这几个框架是目前比较流行的前端框架。不过,由于潮内目前的技术栈以`React`为主,我在这里也只介绍它。框架的学习一通百通,当你对一种框架比较熟悉之后,就能很容易上手其他框架。 224 | 225 | 如果你问我React最大的特点是什么,我可能会说是它的**组件化**。整个前端的页面被抽象为一个一个组件,组件内部有着独立的代码片段。你可能现在不能理解框架的强大之处,不过当你逐步接触大型页面的开发时,你会慢慢认识到它的优势。 226 | 227 | 如果你想现在就知道一个React组件可能长什么样,可以先阅读这篇文章:[React是什么](https://zh-hans.reactjs.org/tutorial/tutorial.html#overview)。当然,这不是你在二面中一定要了解的内容。 228 | 229 | 你也许还记得我在第1部分向你展示过React的文档首页。现在,你可以尝试阅读文档中的[这篇教程](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app),并根据内容的指示创建你的第一个React App。 230 | 231 | 由于二面时间的限制,我们无法再继续向前推进了。当然,如果你学有余力,当然可以提前阅读React的相关文档,在后续的实际开发中,这是必要的前提。 232 | 233 | 234 | > #### Reflection 235 | > 236 | > 注意: **此题为必答题, 选择本试题的面试者请务必对该问题作出回答** 237 | > 238 | > 在完成整个项目的过程中, 你有什么心得和体会? 239 | > 240 | > 你觉得这篇文章你可以打几分? (满分 10, 保留到整数即可) 如此评分的原因是什么?还有什么意见或建议? 241 | > 242 | > 你还有什么想对说的? 243 | 244 | 那么二面题就到这里了, 期待与你的见面~ 245 | 246 | 以上。 247 | -------------------------------------------------------------------------------- /to-frontend-newbie/demo/css.css: -------------------------------------------------------------------------------- 1 | #app { 2 | display: flex; 3 | flex-wrap: wrap; 4 | } 5 | 6 | .card { 7 | display: inline; 8 | margin: 16px; 9 | padding: 16px; 10 | max-width: 400px; 11 | min-width: 240px; 12 | flex: 1; 13 | background-color: #e2e2e2; 14 | box-shadow: 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14), 15 | 0 1px 8px 0 rgba(0, 0, 0, 0.12); 16 | } 17 | 18 | .card > span { 19 | margin: 1em 0; 20 | float: right; 21 | } 22 | -------------------------------------------------------------------------------- /to-frontend-newbie/demo/html.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="zh-CN"> 3 | <head> 4 | <meta charset="UTF-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 | <script src="./js.js"></script> 7 | <link href="./css.css" rel="stylesheet"> 8 | </head> 9 | <body> 10 | <div id="app"></div> 11 | </body> 12 | </html> 13 | -------------------------------------------------------------------------------- /to-frontend-newbie/demo/js.js: -------------------------------------------------------------------------------- 1 | window.onload = () => { 2 | const data = [ 3 | { 4 | sentence: "一个细胞里,却分裂出了两种截然不同的命运。", 5 | from: "秦明「法医秦明」", 6 | }, 7 | { 8 | sentence: "人间忽晚,山河已秋。", 9 | from: "亦沫不吃鱼「人间忽晚」", 10 | }, 11 | { 12 | sentence: "失去故土的花朵,回不去,却也离不开。", 13 | from: "夏达「长歌行」", 14 | }, 15 | ]; 16 | data.forEach((value) => { 17 | const divElement = document.createElement("div"); 18 | const sentence = document.createElement("p"); 19 | const author = document.createElement("span"); 20 | 21 | divElement.className = "card"; 22 | sentence.innerText = value.sentence; 23 | author.innerText = `——${value.from}`; 24 | 25 | divElement.appendChild(sentence); 26 | divElement.appendChild(author); 27 | document.getElementById("app").appendChild(divElement); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210918113322477.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210918113322477.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210918121242153.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210918121242153.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210918133011325.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210918133011325.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210918185655361.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210918185655361.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210918211357404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210918211357404.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210919024240910.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210919024240910.png -------------------------------------------------------------------------------- /to-frontend-newbie/image-20210921150424436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QSCTech/2021-fall-round-two/66415ded6dfb375724ff289577f1b3a624cae661/to-frontend-newbie/image-20210921150424436.png --------------------------------------------------------------------------------