🔍 搜尋結果:rest

🔍 搜尋結果:rest

為初學者到專家提供的 101 個 Bash 指令和提示

> **2019 年 9 月 25 日更新:**感謝[ラナ・kuaru](https://twitter.com/rana_kualu)的辛勤工作,本文現已提供日文版。請點擊下面的連結查看他們的工作。如果您知道本文被翻譯成其他語言,請告訴我,我會將其發佈在這裡。 [🇯🇵 閱讀日語](https://qiita.com/rana_kualu/items/7b62898d373901466f5c) > **2019 年 7 月 8 日更新:**我最近發現大約兩年前發佈在法語留言板上的[這篇非常相似的文章](https://bookmarks.ecyseo.net/?EAWvDw)。如果您有興趣學習一些 shell 命令——並且您*會說 français* ,那麼它是對我下面的文章的一個很好的補充。 直到大約一年前,我幾乎只在 macOS 和 Ubuntu 作業系統中工作。在這兩個作業系統上, `bash`是我的預設 shell。在過去的六、七年裡,我對`bash`工作原理有了大致的了解,並想為那些剛入門的人概述一些更常見/有用的命令。如果您認為您了解有關`bash`所有訊息,請無論如何看看下面的內容 - 我已經提供了一些提示和您可能忘記的標誌的提醒,這可以讓您的工作更輕鬆一些。 下面的命令或多或少以敘述風格排列,因此如果您剛開始使用`bash` ,您可以從頭到尾完成操作。事情到最後通常會變得不那麼常見並且變得更加困難。 <a name="toc"></a> 目錄 -- - [基礎](#the-basics) ``` - [First Commands, Navigating the Filesystem](#first-commands) ``` ``` - [`pwd / ls / cd`](#pwd-ls-cd) ``` ``` - [`; / && / &`](#semicolon-andand-and) ``` ``` - [Getting Help](#getting-help) ``` ``` - [`-h`](#minus-h) ``` ``` - [`man`](#man) ``` ``` - [Viewing and Editing Files](#viewing-and-editing-files) ``` ``` - [`head / tail / cat / less`](#head-tail-cat-less) ``` ``` - [`nano / nedit`](#nano-nedit) ``` ``` - [Creating and Deleting Files and Directories](#creating-and-deleting-files) ``` ``` - [`touch`](#touch) ``` ``` - [`mkdir / rm / rmdir`](#mkdir-rm-rmdir) ``` ``` - [Moving and Copying Files, Making Links, Command History](#moving-and-copying-files) ``` ``` - [`mv / cp / ln`](#mv-cp-ln) ``` ``` - [Command History](#command-history) ``` ``` - [Directory Trees, Disk Usage, and Processes](#directory-trees-disk-usage-processes) ``` ``` - [`mkdir –p / tree`](#mkdir--p-tree) ``` ``` - [`df / du / ps`](#df-du-ps) ``` ``` - [Miscellaneous](#basic-misc) ``` ``` - [`passwd / logout / exit`](#passwd-logout-exit) ``` ``` - [`clear / *`](#clear-glob) ``` - [中間的](#intermediate) ``` - [Disk, Memory, and Processor Usage](#disk-memory-processor) ``` ``` - [`ncdu`](#ncdu) ``` ``` - [`top / htop`](#top-htop) ``` ``` - [REPLs and Software Versions](#REPLs-software-versions) ``` ``` - [REPLs](#REPLs) ``` ``` - [`-version / --version / -v`](#version) ``` ``` - [Environment Variables and Aliases](#env-vars-aliases) ``` ``` - [Environment Variables](#env-vars) ``` ``` - [Aliases](#aliases) ``` ``` - [Basic `bash` Scripting](#basic-bash-scripting) ``` ``` - [`bash` Scripts](#bash-scripts) ``` ``` - [Custom Prompt and `ls`](#custom-prompt-ls) ``` ``` - [Config Files](#config-files) ``` ``` - [Config Files / `.bashrc`](#config-bashrc) ``` ``` - [Types of Shells](#types-of-shells) ``` ``` - [Finding Things](#finding-things) ``` ``` - [`whereis / which / whatis`](#whereis-which-whatis) ``` ``` - [`locate / find`](#locate-find) ``` ``` - [Downloading Things](#downloading-things) ``` ``` - [`ping / wget / curl`](#ping-wget-curl) ``` ``` - [`apt / gunzip / tar / gzip`](#apt-gunzip-tar-gzip) ``` ``` - [Redirecting Input and Output](#redirecting-io) ``` ``` - [`| / > / < / echo / printf`](#pipe-gt-lt-echo-printf) ``` ``` - [`0 / 1 / 2 / tee`](#std-tee) ``` - [先進的](#advanced) ``` - [Superuser](#superuser) ``` ``` - [`sudo / su`](#sudo-su) ``` ``` - [`!!`](#click-click) ``` ``` - [File Permissions](#file-permissions) ``` ``` - [File Permissions](#file-permissions-sub) ``` ``` - [`chmod / chown`](#chmod-chown) ``` ``` - [User and Group Management](#users-groups) ``` ``` - [Users](#users) ``` ``` - [Groups](#groups) ``` ``` - [Text Processing](#text-processing) ``` ``` - [`uniq / sort / diff / cmp`](#uniq-sort-diff-cmp) ``` ``` - [`cut / sed`](#cut-sed) ``` ``` - [Pattern Matching](#pattern-matching) ``` ``` - [`grep`](#grep) ``` ``` - [`awk`](#awk) ``` ``` - [Copying Files Over `ssh`](#ssh) ``` ``` - [`ssh / scp`](#ssh-scp) ``` ``` - [`rsync`](#rsync) ``` ``` - [Long-Running Processes](#long-running-processes) ``` ``` - [`yes / nohup / ps / kill`](#yes-nohup-ps-kill) ``` ``` - [`cron / crontab / >>`](#cron) ``` ``` - [Miscellaneous](#advanced-misc) ``` ``` - [`pushd / popd`](#pushd-popd) ``` ``` - [`xdg-open`](#xdg-open) ``` ``` - [`xargs`](#xargs) ``` - [獎勵:有趣但大多無用的東西](#bonus) ``` - [`w / write / wall / lynx`](#w-write-wall-lynx) ``` ``` - [`nautilus / date / cal / bc`](#nautilus-date-cal-bc) ``` --- <a name="the-basics"></a> 基礎 == <a name="first-commands"></a> 第一個指令,瀏覽檔案系統 ------------ 現代檔案系統具有目錄(資料夾)樹,其中目錄要么是*根目錄*(沒有父目錄),要么是*子目錄*(包含在單一其他目錄中,我們稱之為“父目錄”)。向後遍歷檔案樹(從子目錄到父目錄)將始終到達根目錄。有些檔案系統有多個根目錄(如 Windows 的磁碟機: `C:\` 、 `A:\`等),但 Unix 和類別 Unix 系統只有一個名為`\`的根目錄。 <a name="pwd-ls-cd"></a> ### `pwd / ls / cd` [\[ 返回目錄 \]](#toc) 在檔案系統中工作時,使用者始終*在*某個目錄中工作,我們稱之為當前目錄或*工作目錄*。使用`pwd`列印使用者的工作目錄: ``` [ andrew@pc01 ~ ]$ pwd /home/andrew ``` 使用`ls`列出該目錄的內容(檔案和/或子目錄等): ``` [ andrew@pc01 ~ ]$ ls Git TEST jdoc test test.file ``` > **獎金:** > > 使用`ls -a`顯示隱藏(“點”)文件 > > 使用`ls -l`顯示文件詳細訊息 > > 組合多個標誌,如`ls -l -a` > > 有時您可以連結諸如`ls -la`之類的標誌,而不是`ls -l -a` 使用`cd`更改到不同的目錄(更改目錄): ``` [ andrew@pc01 ~ ]$ cd TEST/ [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST [ andrew@pc01 TEST ]$ cd A [ andrew@pc01 A ]$ pwd /home/andrew/TEST/A ``` `cd ..`是「 `cd`到父目錄」的簡寫: ``` [ andrew@pc01 A ]$ cd .. [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST ``` `cd ~`或只是`cd`是「 `cd`到我的主目錄」的簡寫(通常`/home/username`或類似的東西): ``` [ andrew@pc01 TEST ]$ cd [ andrew@pc01 ~ ]$ pwd /home/andrew ``` > **獎金:** > > `cd ~user`表示「 `cd`到`user`的主目錄 > > 您可以使用`cd ../..`等跳轉多個目錄等級。 > > 使用`cd -`返回到最近的目錄 > > `.`是「此目錄」的簡寫,因此`cd .`不會做太多事情 <a name="semicolon-andand-and"></a> ### `; / && / &` [\[ 返回目錄 \]](#toc) 我們在命令列中輸入的內容稱為*命令*,它們總是執行儲存在電腦上某處的一些機器碼。有時這個機器碼是一個內建的Linux命令,有時它是一個應用程式,有時它是你自己寫的一些程式碼。有時,我們會想依序執行一個指令。為此,我們可以使用`;` (分號): ``` [ andrew@pc01 ~ ]$ ls; pwd Git TEST jdoc test test.file /home/andrew ``` 上面的分號表示我首先 ( `ls` ) 列出工作目錄的內容,然後 ( `pwd` ) 列印其位置。連結命令的另一個有用工具是`&&` 。使用`&&`時,如果左側命令失敗,則右側命令將不會執行。 `;`和`&&`都可以在同一行中多次使用: ``` # whoops! I made a typo here! [ andrew@pc01 ~ ]$ cd /Giit/Parser && pwd && ls && cd -bash: cd: /Giit/Parser: No such file or directory # the first command passes now, so the following commands are run [ andrew@pc01 ~ ]$ cd Git/Parser/ && pwd && ls && cd /home/andrew/Git/Parser README.md doc.sh pom.xml resource run.sh shell.sh source src target ``` ....但是與`;` ,即使第一個命令失敗,第二個命令也會執行: ``` # pwd and ls still run, even though the cd command failed [ andrew@pc01 ~ ]$ cd /Giit/Parser ; pwd ; ls -bash: cd: /Giit/Parser: No such file or directory /home/andrew Git TEST jdoc test test.file ``` `&`看起來與`&&`類似,但實際上實現了完全不同的功能。通常,當您執行長時間執行的命令時,命令列將等待該命令完成,然後才允許您輸入另一個命令。在命令後面加上`&`可以防止這種情況發生,並允許您在舊命令仍在執行時執行新命令: ``` [ andrew@pc01 ~ ]$ cd Git/Parser && mvn package & cd [1] 9263 ``` > **額外的好處:**當我們在命令後使用`&`來「隱藏」它時,我們說該作業(或「進程」;這些術語或多或少可以互換)是「後台的」。若要查看目前正在執行的背景作業,請使用`jobs`指令: > ````bash \[ andrew@pc01 ~ \]$ 職位 \[1\]+ 執行 cd Git/Parser/ && mvn package & ``` <a name="getting-help"></a> ## Getting Help <a name="minus-h"></a> ### `-h` [[ Back to Table of Contents ]](#toc) Type `-h` or `--help` after almost any command to bring up a help menu for that command: ``` \[ andrew@pc01 ~ \]$ du --help 用法:你\[選項\]...\[檔案\]... 或: du \[選項\]... --files0-from=F 對目錄遞歸地總結文件集的磁碟使用情況。 長期權的強制性參數對於短期權也是強制性的。 -0, --null 以 NUL 結束每個輸出行,而不是換行符 -a, --all 計算所有檔案的寫入計數,而不僅僅是目錄 ``` --apparent-size print apparent sizes, rather than disk usage; although ``` ``` the apparent size is usually smaller, it may be ``` ``` larger due to holes in ('sparse') files, internal ``` ``` fragmentation, indirect blocks, and the like ``` -B, --block-size=SIZE 在列印前按 SIZE 縮放大小;例如, ``` '-BM' prints sizes in units of 1,048,576 bytes; ``` ``` see SIZE format below ``` … ``` <a name="man"></a> ### `man` [[ Back to Table of Contents ]](#toc) Type `man` before almost any command to bring up a manual for that command (quit `man` with `q`): ``` LS(1) 使用者指令 LS(1) 姓名 ``` ls - list directory contents ``` 概要 ``` ls [OPTION]... [FILE]... ``` 描述 ``` List information about the FILEs (the current directory by default). ``` ``` Sort entries alphabetically if none of -cftuvSUX nor --sort is speci- ``` ``` fied. ``` ``` Mandatory arguments to long options are mandatory for short options ``` ``` too. ``` … ``` <a name="viewing-and-editing-files"></a> ## Viewing and Editing Files <a name="head-tail-cat-less"></a> ### `head / tail / cat / less` [[ Back to Table of Contents ]](#toc) `head` outputs the first few lines of a file. The `-n` flag specifies the number of lines to show (the default is 10): ``` 列印前三行 ===== \[ andrew@pc01 ~ \]$ 頭 -n 3 c 這 文件 有 ``` `tail` outputs the last few lines of a file. You can get the last `n` lines (like above), or you can get the end of the file beginning from the `N`-th line with `tail -n +N`: ``` 從第 4 行開始列印文件末尾 ============== \[ andrew@pc01 ~ \]$ tail -n +4 c 確切地 六 線 ``` `cat` concatenates a list of files and sends them to the standard output stream (usually the terminal). `cat` can be used with just a single file, or multiple files, and is often used to quickly view them. (**Be warned**: if you use `cat` in this way, you may be accused of a [_Useless Use of Cat_ (UUOC)](http://bit.ly/2SPHE4V), but it's not that big of a deal, so don't worry too much about it.) ``` \[ andrew@pc01 ~ \]$ 貓 a 歸檔一個 \[ andrew@pc01 ~ \]$ 貓 ab 歸檔一個 文件b ``` `less` is another tool for quickly viewing a file -- it opens up a `vim`-like read-only window. (Yes, there is a command called `more`, but `less` -- unintuitively -- offers a superset of the functionality of `more` and is recommended over it.) Learn more (or less?) about [less](http://man7.org/linux/man-pages/man1/less.1.html) and [more](http://man7.org/linux/man-pages/man1/more.1.html) at their `man` pages. <a name="nano-nedit"></a> ### `nano / nedit` [[ Back to Table of Contents ]](#toc) `nano` is a minimalistic command-line text editor. It's a great editor for beginners or people who don't want to learn a million shortcuts. It was more than sufficient for me for the first few years of my coding career (I'm only now starting to look into more powerful editors, mainly because defining your own syntax highlighting in `nano` can be a bit of a pain.) `nedit` is a small graphical editor, it opens up an X Window and allows point-and-click editing, drag-and-drop, syntax highlighting and more. I use `nedit` sometimes when I want to make small changes to a script and re-run it over and over. Other common CLI (command-line interface) / GUI (graphical user interface) editors include `emacs`, `vi`, `vim`, `gedit`, Notepad++, Atom, and lots more. Some cool ones that I've played around with (and can endorse) include Micro, Light Table, and VS Code. All modern editors offer basic conveniences like search and replace, syntax highlighting, and so on. `vi(m)` and `emacs` have more features than `nano` and `nedit`, but they have a much steeper learning curve. Try a few different editors out and find one that works for you! <a name="creating-and-deleting-files"></a> ## Creating and Deleting Files and Directories <a name="touch"></a> ### `touch` [[ Back to Table of Contents ]](#toc) `touch` was created to modify file timestamps, but it can also be used to quickly create an empty file. You can create a new file by opening it with a text editor, like `nano`: ``` \[ andrew@pc01 前 \]$ ls \[ andrew@pc01 ex \]$ 奈米 a ``` _...editing file..._ ``` \[ andrew@pc01 前 \]$ ls A ``` ...or by simply using `touch`: ``` \[ andrew@pc01 ex \]$ touch b && ls ab ``` > **Bonus**: > > Background a process with \^z (Ctrl+z) > > ```bash > [ andrew@pc01 ex ]$ nano a > ``` > > _...editing file, then hit \^z..._ > > ```bash > Use fg to return to nano > > [1]+ Stopped nano a > [ andrew@pc01 ex ]$ fg > ``` > > _...editing file again..._ --- > **Double Bonus:** > > Kill the current (foreground) process by pressing \^c (Ctrl+c) while it’s running > > Kill a background process with `kill %N` where `N` is the job index shown by the `jobs` command <a name="mkdir-rm-rmdir"></a> ### `mkdir / rm / rmdir` [[ Back to Table of Contents ]](#toc) `mkdir` is used to create new, empty directories: ``` \[ andrew@pc01 ex \]$ ls && mkdir c && ls ab ABC ``` You can remove any file with `rm` -- but be careful, this is non-recoverable! ``` \[ andrew@pc01 ex \]$ rm a && ls 西元前 ``` You can add an _"are you sure?"_ prompt with the `-i` flag: ``` \[ andrew@pc01 前 \]$ rm -ib rm:刪除常規空文件“b”? y ``` Remove an empty directory with `rmdir`. If you `ls -a` in an empty directory, you should only see a reference to the directory itself (`.`) and a reference to its parent directory (`..`): ``` \[ andrew@pc01 ex \]$ rmdir c && ls -a 。 .. ``` `rmdir` removes empty directories only: ``` \[ andrew@pc01 ex \]$ cd .. && ls 測試/ \*.txt 0.txt 1.txt a a.txt bc \[ andrew@pc01 ~ \]$ rmdir 測試/ rmdir:無法刪除“test/”:目錄不為空 ``` ...but you can remove a directory -- and all of its contents -- with `rm -rf` (`-r` = recursive, `-f` = force): ``` \[ andrew@pc01 ~ \]$ rm –rf 測試 ``` <a name="moving-and-copying-files"></a> ## Moving and Copying Files, Making Links, Command History <a name="mv-cp-ln"></a> ### `mv / cp / ln` [[ Back to Table of Contents ]](#toc) `mv` moves / renames a file. You can `mv` a file to a new directory and keep the same file name or `mv` a file to a "new file" (rename it): ``` \[ andrew@pc01 ex \]$ ls && mv ae && ls A B C D BCDE ``` `cp` copies a file: ``` \[ andrew@pc01 ex \]$ cp e e2 && ls BCDE E2 ``` `ln` creates a hard link to a file: ``` ln 的第一個參數是 TARGET,第二個參數是 NEW LINK ================================= \[ andrew@pc01 ex \]$ ln bf && ls bcde e2 f ``` `ln -s` creates a soft link to a file: ``` \[ andrew@pc01 ex \]$ ln -sbg && ls BCDE E2 FG ``` Hard links reference the same actual bytes in memory which contain a file, while soft links refer to the original file name, which itself points to those bytes. [You can read more about soft vs. hard links here.](http://bit.ly/2D0W8cN) <a name="command-history"></a> ### Command History [[ Back to Table of Contents ]](#toc) `bash` has two big features to help you complete and re-run commands, the first is _tab completion_. Simply type the first part of a command, hit the \<tab\> key, and let the terminal guess what you're trying to do: ``` \[ andrew@pc01 目錄 \]$ ls 另一個長檔名 這是一個長檔名 一個新檔名 \[ andrew@pc01 目錄 \]$ ls t ``` _...hit the TAB key after typing `ls t` and the command is completed..._ ``` \[ andrew@pc01 dir \]$ ls 這是檔名 這是長檔名 ``` You may have to hit \<TAB\> multiple times if there's an ambiguity: ``` \[ andrew@pc01 目錄 \]$ ls a \[ andrew@pc01 目錄 \]$ ls an 一個新檔名另一個長檔名 ``` `bash` keeps a short history of the commands you've typed previously and lets you search through those commands by typing \^r (Ctrl+r): ``` \[ andrew@pc01 目錄 \] ``` _...hit \^r (Ctrl+r) to search the command history..._ ``` (反向搜尋)``: ``` _...type 'anew' and the last command containing this is found..._ ``` (reverse-i-search)`anew': 觸碰新檔名 ``` <a name="directory-trees-disk-usage-processes"></a> ## Directory Trees, Disk Usage, and Processes <a name="mkdir--p-tree"></a> ### `mkdir –p / tree` [[ Back to Table of Contents ]](#toc) `mkdir`, by default, only makes a single directory. This means that if, for instance, directory `d/e` doesn't exist, then `d/e/f` can't be made with `mkdir` by itself: ``` \[ andrew@pc01 ex \]$ ls && mkdir d/e/f ABC mkdir:無法建立目錄「d/e/f」:沒有這樣的檔案或目錄 ``` But if we pass the `-p` flag to `mkdir`, it will make all directories in the path if they don't already exist: ``` \[ andrew@pc01 ex \]$ mkdir -pd/e/f && ls A B C D ``` `tree` can help you better visualise a directory's structure by printing a nicely-formatted directory tree. By default, it prints the entire tree structure (beginning with the specified directory), but you can restrict it to a certain number of levels with the `-L` flag: ``` \[ andrew@pc01 前 \]$ 樹 -L 2 。 |-- 一個 |-- b |-- c `--d ``` `--e ``` 3個目錄,2個文件 ``` You can hide empty directories in `tree`'s output with `--prune`. Note that this also removes "recursively empty" directories, or directories which aren't empty _per se_, but which contain only other empty directories, or other recursively empty directories: ``` \[ andrew@pc01 ex \]$ 樹 --prune 。 |-- 一個 `--b ``` <a name="df-du-ps"></a> ### `df / du / ps` [[ Back to Table of Contents ]](#toc) `df` is used to show how much space is taken up by files for the disks or your system (hard drives, etc.). ``` \[ andrew@pc01 前 \]$ df -h 已使用的檔案系統大小 可用 使用% 安裝於 udev 126G 0 126G 0% /dev tmpfs 26G 2.0G 24G 8% /執行 /dev/mapper/ubuntu--vg-root 1.6T 1.3T 252G 84% / … ``` In the above command, `-h` doesn't mean "help", but "human-readable". Some commands use this convention to display file / disk sizes with `K` for kilobytes, `G` for gigabytes, and so on, instead of writing out a gigantic integer number of bytes. `du` shows file space usage for a particular directory and its subdirectories. If you want to know how much space is free on a given hard drive, use `df`; if you want to know how much space a directory is taking up, use `du`: ``` \[ andrew@pc01 ex \]$ 你 4 ./d/e/f 8./d/e 12 ./天 4./c 20 . ``` `du` takes a `--max-depth=N` flag, which only shows directories `N` levels down (or fewer) from the specified directory: ``` \[ andrew@pc01 ex \]$ du -h --max-深度=1 12K./天 4.0K./c 20K。 ``` `ps` shows all of the user's currently-running processes (aka. jobs): ``` \[ andrew@pc01 前 \]$ ps PID TTY 時間 CMD 16642 分/15 00:00:00 ps 25409 點/15 00:00:00 重擊 ``` <a name="basic-misc"></a> ## Miscellaneous <a name="passwd-logout-exit"></a> ### `passwd / logout / exit` [[ Back to Table of Contents ]](#toc) Change your account password with `passwd`. It will ask for your current password for verification, then ask you to enter the new password twice, so you don't make any typos: ``` \[ andrew@pc01 目錄 \]$ 密碼 更改安德魯的密碼。 (目前)UNIX 密碼: 輸入新的 UNIX 密碼: 重新輸入新的 UNIX 密碼: passwd:密碼更新成功 ``` `logout` exits a shell you’ve logged in to (where you have a user account): ``` \[ andrew@pc01 目錄 \]$ 註銷 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` `exit` exits any kind of shell: ``` \[ andrew@pc01 ~ \]$ 退出 登出 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` <a name="clear-glob"></a> ### `clear / *` [[ Back to Table of Contents ]](#toc) Run `clear` to move the current terminal line to the top of the screen. This command just adds blank lines below the current prompt line. It's good for clearing your workspace. Use the glob (`*`, aka. Kleene Star, aka. wildcard) when looking for files. Notice the difference between the following two commands: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/ PArrayUtils.java PFile.java PSQLFile.java PWatchman.java PDateTimeUtils.java PFixedWidthFile.java PStringUtils.java PXSVFile.java PDelimitedFile.java PNode.java PTextFile.java Parser.java \[ andrew@pc01 ~ \]$ ls Git/Parser/source/PD\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` The glob can be used multiple times in a command and matches zero or more characers: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/P *D* m\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` <a name="intermediate"></a> # Intermediate <a name="disk-memory-processor"></a> ## Disk, Memory, and Processor Usage <a name="ncdu"></a> ### `ncdu` [[ Back to Table of Contents ]](#toc) `ncdu` (NCurses Disk Usage) provides a navigable overview of file space usage, like an improved `du`. It opens a read-only `vim`-like window (press `q` to quit): ``` \[ andrew@pc01 ~ \]$ ncdu ncdu 1.11 ~ 使用箭頭鍵導航,按 ?求助 \--- /home/安德魯 ------------------------------------------- ------------------ 148.2 MiB \[##########\] /.m2 91.5 MiB \[######\] /.sbt 79.8 MiB \[######\] /.cache 64.9 MiB \[####\] /.ivy2 40.6 MiB \[##\] /.sdkman 30.2 MiB \[##\] /.local 27.4 MiB \[#\] /.mozilla 24.4 MiB \[#\] /.nanobackups 10.2 MiB \[ \] .confout3.txt ``` 8.4 MiB [ ] /.config ``` ``` 5.9 MiB [ ] /.nbi ``` ``` 5.8 MiB [ ] /.oh-my-zsh ``` ``` 4.3 MiB [ ] /Git ``` ``` 3.7 MiB [ ] /.myshell ``` ``` 1.7 MiB [ ] /jdoc ``` ``` 1.5 MiB [ ] .confout2.txt ``` ``` 1.5 MiB [ ] /.netbeans ``` ``` 1.1 MiB [ ] /.jenv ``` 564.0 KiB \[ \] /.rstudio-desktop 磁碟使用總量:552.7 MiB 表觀大小:523.6 MiB 專案:14618 ``` <a name="top-htop"></a> ### `top / htop` [[ Back to Table of Contents ]](#toc) `top` displays all currently-running processes and their owners, memory usage, and more. `htop` is an improved, interactive `top`. (Note: you can pass the `-u username` flag to restrict the displayed processes to only those owner by `username`.) ``` \[ andrew@pc01 ~ \]$ htop 1 \[ 0.0%\] 9 \[ 0.0%\] 17 \[ 0.0%\] 25 \[ 0.0%\] 2 \[ 0.0%\] 10 \[ 0.0%\] 18 \[ 0.0%\] 26 \[ 0.0%\] 3 \[ 0.0%\] 11 \[ 0.0%\] 19 \[ 0.0%\] 27 \[ 0.0%\] 4 \[ 0.0%\] 12 \[ 0.0%\] 20 \[ 0.0%\] 28 \[ 0.0%\] 5 \[ 0.0%\] 13 \[ 0.0%\] 21 \[| 1.3%\] 29 \[ 0.0%\] 6 \[ 0.0%\] 14 \[ 0.0%\] 22 \[ 0.0%\] 30 \[| 0.6%\] 7 \[ 0.0%\] 15 \[ 0.0%\] 23 \[ 0.0%\] 31 \[ 0.0%\] 8 \[ 0.0%\] 16 \[ 0.0%\] 24 \[ 0.0%\] 32 \[ 0.0%\] Mem\[|||||||||||||||||||1.42G/252G\] 任務:188、366 個; 1 執行 交換電壓\[| 2.47G/256G\]平均負載:0.00 0.00 0.00 ``` Uptime: 432 days(!), 00:03:55 ``` PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ 指令 9389 安德魯 20 0 23344 3848 2848 R 1.3 0.0 0:00.10 htop 10103 根 20 0 3216M 17896 2444 S 0.7 0.0 5h48:56 /usr/bin/dockerd ``` 1 root 20 0 181M 4604 2972 S 0.0 0.0 15:29.66 /lib/systemd/syst ``` 533 根 20 0 44676 6908 6716 S 0.0 0.0 11:19.77 /lib/systemd/syst 546 根 20 0 244M 0 0 S 0.0 0.0 0:01.39 /sbin/lvmetad -f 1526 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.00 /usr/sbin/ModemMa 1544 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.06 /usr/sbin/ModemMa F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit ``` <a name="REPLs-software-versions"></a> ## REPLs and Software Versions <a name="REPLs"></a> ### REPLs [[ Back to Table of Contents ]](#toc) A **REPL** is a Read-Evaluate-Print Loop, similar to the command line, but usually used for particular programming languages. You can open the Python REPL with the `python` command (and quit with the `quit()` function): ``` \[ andrew@pc01 ~ \]$ python Python 3.5.2(默認,2018 年 11 月 12 日,13:43:14)... > > > 辭職() ``` Open the R REPL with the `R` command (and quit with the `q()` function): ``` \[ andrew@pc01 ~ \]$ R R版3.5.2(2018-12-20)--「蛋殼冰屋」... > q() 儲存工作區影像? \[是/否/c\]: 否 ``` Open the Scala REPL with the `scala` command (and quit with the `:quit` command): ``` \[ andrew@pc01 ~ \]$ scala 歡迎使用 Scala 2.11.12 ... 斯卡拉>:退出 ``` Open the Java REPL with the `jshell` command (and quit with the `/exit` command): ``` \[ andrew@pc01 ~ \]$ jshell |歡迎使用 JShell——版本 11.0.1 ... jshell> /退出 ``` Alternatively, you can exit any of these REPLs with \^d (Ctrl+d). \^d is the EOF (end of file) marker on Unix and signifies the end of input. <a name="version"></a> ### `-version / --version / -v` [[ Back to Table of Contents ]](#toc) Most commands and programs have a `-version` or `--version` flag which gives the software version of that command or program. Most applications make this information easily available: ``` \[ andrew@pc01 ~ \]$ ls --version ls (GNU coreutils) 8.25 ... \[ andrew@pc01 ~ \]$ ncdu -版本 NCDU 1.11 \[ andrew@pc01 ~ \]$ python --version Python 3.5.2 ``` ...but some are less intuitive: ``` \[ andrew@pc01 ~ \]$ sbt scalaVersion … \[資訊\]2.12.4 ``` Note that some programs use `-v` as a version flag, while others use `-v` to mean "verbose", which will run the application while printing lots of diagnostic or debugging information: ``` SCP(1) BSD 通用指令手冊 SCP(1) 姓名 ``` scp -- secure copy (remote file copy program) ``` … -v 詳細模式。導致 scp 和 ssh(1) 列印偵錯訊息 ``` about their progress. This is helpful in debugging connection, ``` ``` authentication, and configuration problems. ``` … ``` <a name="env-vars-aliases"></a> ## Environment Variables and Aliases <a name="env-vars"></a> ### Environment Variables [[ Back to Table of Contents ]](#toc) **Environment variables** (sometimes shortened to "env vars") are persistent variables that can be created and used within your `bash` shell. They are defined with an equals sign (`=`) and used with a dollar sign (`$`). You can see all currently-defined env vars with `printenv`: ``` \[ andrew@pc01 ~ \]$ printenv SPARK\_HOME=/usr/local/spark 術語=xterm … ``` Set a new environment variable with an `=` sign (don't put any spaces before or after the `=`, though!): ``` \[ andrew@pc01 ~ \]$ myvar=你好 ``` Print a specific env var to the terminal with `echo` and a preceding `$` sign: ``` \[ andrew@pc01 ~ \]$ echo $myvar 你好 ``` Environment variables which contain spaces or other whitespace should be surrounded by quotes (`"..."`). Note that reassigning a value to an env var overwrites it without warning: ``` \[ andrew@pc01 ~ \]$ myvar="你好,世界!" && 回顯 $myvar 你好世界! ``` Env vars can also be defined using the `export` command. When defined this way, they will also be available to sub-processes (commands called from this shell): ``` \[ andrew@pc01 ~ \]$ export myvar="另一" && echo $myvar 另一個 ``` You can unset an environment variable by leaving the right-hand side of the `=` blank or by using the `unset` command: ``` \[ andrew@pc01 ~ \]$ 取消設定 mynewvar \[ andrew@pc01 ~ \]$ echo $mynewvar ``` <a name="aliases"></a> ### Aliases [[ Back to Table of Contents ]](#toc) **Aliases** are similar to environment variables but are usually used in a different way -- to replace long commands with shorter ones: ``` \[ andrew@pc01 apidocs \]$ ls -l -a -h -t 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … \[ andrew@pc01 apidocs \]$ 別名 lc="ls -l -a -h -t" \[ andrew@pc01 apidocs \]$ lc 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … ``` You can remove an alias with `unalias`: ``` \[ andrew@pc01 apidocs \]$ unalias lc \[ andrew@pc01 apidocs \]$ lc 目前未安裝程式“lc”。 … ``` > **Bonus:** > > [Read about the subtle differences between environment variables and aliases here.](http://bit.ly/2TDG8Tx) > > [Some programs, like **git**, allow you to define aliases specifically for that software.](http://bit.ly/2TG8X1A) <a name="basic-bash-scripting"></a> ## Basic `bash` Scripting <a name="bash-scripts"></a> ### `bash` Scripts [[ Back to Table of Contents ]](#toc) `bash` scripts (usually ending in `.sh`) allow you to automate complicated processes, packaging them into reusable functions. A `bash` script can contain any number of normal shell commands: ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file && ls" > ex.sh ``` A shell script can be executed with the `source` command or the `sh` command: ``` \[ andrew@pc01 ~ \]$ 源 ex.sh 桌面 Git TEST c ex.sh 專案測試 桌面 Git TEST c ex.sh 檔案專案測試 ``` Shell scripts can be made executable with the `chmod` command (more on this later): ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file2 && ls" > ex2.sh \[ andrew@pc01 ~ \]$ chmod +x ex2.sh ``` An executable shell script can be run by preceding it with `./`: ``` \[ andrew@pc01 ~ \]$ ./ex2.sh 桌面 Git TEST c ex.sh ex2.sh 檔案專案測試 桌面 Git TEST c ex.sh ex2.sh 檔案 file2 專案測試 ``` Long lines of code can be split by ending a command with `\`: ``` \[ andrew@pc01 ~ \]$ echo "for i in {1..3}; do echo \\ > \\"歡迎\\$i次\\";完成” > ex3.sh ``` Bash scripts can contain loops, functions, and more! ``` \[ andrew@pc01 ~ \]$ 源 ex3.sh 歡迎1次 歡迎2次 歡迎3次 ``` <a name="custom-prompt-ls"></a> ### Custom Prompt and `ls` [[ Back to Table of Contents ]](#toc) Bash scripting can make your life a whole lot easier and more colourful. [Check out this great bash scripting cheat sheet.](https://devhints.io/bash) `$PS1` (Prompt String 1) is the environment variable that defines your main shell prompt ([learn about the other prompts here](http://bit.ly/2SPgsmT)): ``` \[ andrew@pc01 ~ \]$ printf "%q" $PS1 $'\\n\\\[\\E\[1m\\\]\\\[\\E\[30m\\\]\\A'$'\\\[\\E\[37m\\\]|\\\[\\E\[36m\\\]\\u\\\[\\E\[37m \\\]@\\\[\\E\[34m\\\]\\h'$'\\\[\\E\[32m\\\]\\W\\\[\\E\[37m\\\]|'$'\\\[\\E(B\\E\[m\\\] ' ``` You can change your default prompt with the `export` command: ``` \[ andrew@pc01 ~ \]$ export PS1="\\n此處指令> " 此處指令> echo $PS1 \\n此處指令> ``` ...[you can add colours, too!](http://bit.ly/2TMbEit): ``` 此處指令> export PS1="\\e\[1;31m\\n程式碼: \\e\[39m" (這應該是紅色的,但在 Markdown 中可能不會這樣顯示) =============================== 程式碼:回顯$PS1 \\e\[1;31m\\n程式碼: \\e\[39m ``` You can also change the colours shown by `ls` by editing the `$LS_COLORS` environment variable: ``` (同樣,這些顏色可能不會出現在 Markdown 中) =========================== 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 程式碼:匯出 LS\_COLORS='di=31:fi=0:ln=96:or=31:mi=31:ex=92' 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="config-files"></a> ## Config Files <a name="config-bashrc"></a> ### Config Files / `.bashrc` [[ Back to Table of Contents ]](#toc) If you tried the commands in the last section and logged out and back in, you may have noticed that your changes disappeared. _config_ (configuration) files let you maintain settings for your shell or for a particular program every time you log in (or run that program). The main configuration file for a `bash` shell is the `~/.bashrc` file. Aliases, environment variables, and functions added to `~/.bashrc` will be available every time you log in. Commands in `~/.bashrc` will be run every time you log in. If you edit your `~/.bashrc` file, you can reload it without logging out by using the `source` command: ``` \[ andrew@pc01 ~ \]$ nano ~/.bashrc ``` _...add the line `echo “~/.bashrc loaded!”` to the top of the file_... ``` \[ andrew@pc01 ~ \]$ 源 ~/.bashrc ~/.bashrc 已載入! ``` _...log out and log back in..._ ``` 最後登入:2019 年 1 月 11 日星期五 10:29:07 從 111.11.11.111 ~/.bashrc 已加載! \[ 安德魯@pc01 ~ \] ``` <a name="types-of-shells"></a> ### Types of Shells [[ Back to Table of Contents ]](#toc) _Login_ shells are shells you log in to (where you have a username). _Interactive_ shells are shells which accept commands. Shells can be login and interactive, non-login and non-interactive, or any other combination. In addition to `~/.bashrc`, there are a few other scripts which are `sourced` by the shell automatically when you log in or log out. These are: - `/etc/profile` - `~/.bash_profile` - `~/.bash_login` - `~/.profile` - `~/.bash_logout` - `/etc/bash.bash_logout` Which of these scripts are sourced, and the order in which they're sourced, depend on the type of shell opened. See [the bash man page](https://linux.die.net/man/1/bash) and [these](http://bit.ly/2TGCwA8) Stack Overflow [posts](http://bit.ly/2TFHFsf) for more information. Note that `bash` scripts can `source` other scripts. For instance, in your `~/.bashrc`, you could include the line: ``` 來源~/.bashrc\_addl ``` ...which would also `source` that `.bashrc_addl` script. This file can contain its own aliases, functions, environment variables, and so on. It could, in turn, `source` other scripts, as well. (Be careful to avoid infinite loops of script-sourcing!) It may be helpful to split commands into different shell scripts based on functionality or machine type (Ubuntu vs. Red Hat vs. macOS), for example: - `~/.bash_ubuntu` -- configuration specific to Ubuntu-based machines - `~/.bashrc_styles` -- aesthetic settings, like `PS1` and `LS_COLORS` - `~/.bash_java` -- configuration specific to the Java language I try to keep separate `bash` files for aesthetic configurations and OS- or machine-specific code, and then I have one big `bash` file containing shortcuts, etc. that I use on every machine and every OS. Note that there are also _different shells_. `bash` is just one kind of shell (the "Bourne Again Shell"). Other common ones include `zsh`, `csh`, `fish`, and more. Play around with different shells and find one that's right for you, but be aware that this tutorial contains `bash` shell commands only and not everything listed here (maybe none of it) will be applicable to shells other than `bash`. <a name="finding-things"></a> ## Finding Things <a name="whereis-which-whatis"></a> ### `whereis / which / whatis` [[ Back to Table of Contents ]](#toc) `whereis` searches for "possibly useful" files related to a particular command. It will attempt to return the location of the binary (executable machine code), source (code source files), and `man` page for that command: ``` \[ andrew@pc01 ~ \]$ whereis ls ls: /bin/ls /usr/share/man/man1/ls.1.gz ``` `which` will only return the location of the binary (the command itself): ``` \[ andrew@pc01 ~ \]$ 其中 ls /bin/ls ``` `whatis` prints out the one-line description of a command from its `man` page: ``` \[ andrew@pc01 ~ \]$ 什麼是哪裡是哪個什麼是 whereis (1) - 尋找指令的二進位、原始檔和手冊頁文件 which (1) - 定位指令 Whatis (1) - 顯示一行手冊頁描述 ``` `which` is useful for finding the "original version" of a command which may be hidden by an alias: ``` \[ andrew@pc01 ~ \]$ 別名 ls="ls -l" “original” ls 已被上面定義的別名“隱藏” =========================== \[ andrew@pc01 ~ \]$ ls 總計 36 drwxr-xr-x 2 安德魯 andrew 4096 Jan 9 14:47 桌面 drwxr-xr-x 4 安德魯 安德魯 4096 十二月 6 10:43 Git … 但我們仍然可以使用返回的位置來呼叫「原始」ls ======================= \[ andrew@pc01 ~ \]$ /bin/ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="locate-find"></a> ### `locate / find` [[ Back to Table of Contents ]](#toc) `locate` finds a file anywhere on the system by referring to a semi-regularly-updated cached list of files: ``` \[ andrew@pc01 ~ \]$ 找到 README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.jenv/README.md /home/andrew/.myshell/README.md … ``` Because it's just searching a list, `locate` is usually faster than the alternative, `find`. `find` iterates through the file system to find the file you're looking for. Because it's actually looking at the files which _currently_ exist on the system, though, it will always return an up-to-date list of files, which is not necessarily true with `locate`. ``` \[ andrew@pc01 ~ \]$ find ~/ -iname "README.md" /home/andrew/.jenv/README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.oh-my-zsh/plugins/ant/README.md … ``` `find` was written for the very first version of Unix in 1971, and is therefore much more widely available than `locate`, which was added to GNU in 1994. `find` has many more features than `locate`, and can search by file age, size, ownership, type, timestamp, permissions, depth within the file system; `find` can search using regular expressions, execute commands on files it finds, and more. When you need a fast (but possibly outdated) list of files, or you’re not sure what directory a particular file is in, use `locate`. When you need an accurate file list, maybe based on something other than the files’ names, and you need to do something with those files, use `find`. <a name="downloading-things"></a> ## Downloading Things <a name="ping-wget-curl"></a> ### `ping / wget / curl` [[ Back to Table of Contents ]](#toc) `ping` attempts to open a line of communication with a network host. Mainly, it's used to check whether or not your Internet connection is down: ``` \[ andrew@pc01 ~ \]$ ping google.com PING google.com (74.125.193.100) 56(84) 位元組資料。 使用 32 位元組資料 Ping 74.125.193.100: 來自 74.125.193.100 的回覆:位元組=32 時間<1ms TTL=64 … ``` `wget` is used to easily download a file from the Internet: ``` \[ andrew@pc01 ~ \]$ wget \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso ``` `curl` can be used just like `wget` (don’t forget the `--output` flag): ``` \[ andrew@pc01 ~ \]$ 捲曲 \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso \\ > \--輸出ubuntu.iso ``` `curl` and `wget` have their own strengths and weaknesses. `curl` supports many more protocols and is more widely available than `wget`; `curl` can also send data, while `wget` can only receive data. `wget` can download files recursively, while `curl` cannot. In general, I use `wget` when I need to download things from the Internet. I don’t often need to send data using `curl`, but it’s good to be aware of it for the rare occasion that you do. <a name="apt-gunzip-tar-gzip"></a> ### `apt / gunzip / tar / gzip` [[ Back to Table of Contents ]](#toc) Debian-descended Linux distributions have a fantastic package management tool called `apt`. It can be used to install, upgrade, or delete software on your machine. To search `apt` for a particular piece of software, use `apt search`, and install it with `apt install`: ``` \[ andrew@pc01 ~ \]$ apt 搜尋漂白位 ...bleachbit/bionic、bionic 2.0-2 全部 從系統中刪除不需要的文件 您需要“sudo”來安裝軟體 ============== \[ andrew@pc01 ~ \]$ sudo apt installbleachbit ``` Linux software often comes packaged in `.tar.gz` ("tarball") files: ``` \[ andrew@pc01 ~ \]$ wget \\ > https://github.com/atom/atom/releases/download/v1.35.0-beta0/atom-amd64.tar.gz ``` ...these types of files can be unzipped with `gunzip`: ``` \[ andrew@pc01 ~ \]$gunzipatom-amd64.tar.gz && ls 原子 amd64.tar ``` A `.tar.gz` file will be `gunzip`-ped to a `.tar` file, which can be extracted to a directory of files using `tar -xf` (`-x` for "extract", `-f` to specify the file to "untar"): ``` \[ andrew@pc01 ~ \]$ tar -xfatom-amd64.tar && mv \\ 原子-beta-1.35.0-beta0-amd64 原子 && ls 原子atom-amd64.tar ``` To go in the reverse direction, you can create (`-c`) a tar file from a directory and zip it (or unzip it, as appropriate) with `-z`: ``` \[ andrew@pc01 ~ \]$ tar -zcf 壓縮.tar.gz 原子 && ls 原子atom-amd64.tar壓縮.tar.gz ``` `.tar` files can also be zipped with `gzip`: ``` \[ andrew@pc01 ~ \]$ gzipatom-amd64.tar && ls 原子 原子-amd64.tar.gz 壓縮.tar.gz ``` <a name="redirecting-io"></a> ## Redirecting Input and Output <a name="pipe-gt-lt-echo-printf"></a> ### `| / > / < / echo / printf` [[ Back to Table of Contents ]](#toc) By default, shell commands read their input from the standard input stream (aka. stdin or 0) and write to the standard output stream (aka. stdout or 1), unless there’s an error, which is written to the standard error stream (aka. stderr or 2). `echo` writes text to stdout by default, which in most cases will simply print it to the terminal: ``` \[ andrew@pc01 ~ \]$ 回顯“你好” 你好 ``` The pipe operator, `|`, redirects the output of the first command to the input of the second command: ``` 'wc'(字數)傳回檔案中的行數、字數、位元組數 ======================== \[ andrew@pc01 ~ \]$ echo "範例文件" |廁所 ``` 1 2 17 ``` ``` `>` redirects output from stdout to a particular location ``` \[ andrew@pc01 ~ \]$ echo "test" > 文件 && 頭文件 測試 ``` `printf` is an improved `echo`, allowing formatting and escape sequences: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" 1 3 2 ``` `<` gets input from a particular location, rather than stdin: ``` 'sort' 依字母/數字順序對檔案的行進行排序 ======================== \[ andrew@pc01 ~ \]$ sort <(printf "1\\n3\\n2") 1 2 3 ``` Rather than a [UUOC](#viewing-and-editing-files), the recommended way to send the contents of a file to a command is to use `<`. Note that this causes data to "flow" right-to-left on the command line, rather than (the perhaps more natural, for English-speakers) left-to-right: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" > 文件 && 排序 < 文件 1 2 3 ``` <a name="std-tee"></a> ### `0 / 1 / 2 / tee` [[ Back to Table of Contents ]](#toc) 0, 1, and 2 are the standard input, output, and error streams, respectively. Input and output streams can be redirected with the `|`, `>`, and `<` operators mentioned previously, but stdin, stdout, and stderr can also be manipulated directly using their numeric identifiers: Write to stdout or stderr with `>&1` or `>&2`: ``` \[ andrew@pc01 ~ \]$ 貓測試 回顯“標準輸出”>&1 回顯“標準錯誤”>&2 ``` By default, stdout and stderr both print output to the terminal: ``` \[ andrew@pc01 ~ \]$ ./測試 標準錯誤 標準輸出 ``` Redirect stdout to `/dev/null` (only print output sent to stderr): ``` \[ andrew@pc01 ~ \]$ ./test 1>/dev/null 標準錯誤 ``` Redirect stderr to `/dev/null` (only print output sent to stdout): ``` \[ andrew@pc01 ~ \]$ ./test 2>/dev/null 標準輸出 ``` Redirect all output to `/dev/null` (print nothing): ``` \[ andrew@pc01 ~ \]$ ./test &>/dev/null ``` Send output to stdout and any number of additional locations with `tee`: ``` \[ andrew@pc01 ~ \]$ ls && echo "測試" | tee 文件1 文件2 文件3 && ls 文件0 測試 文件0 文件1 文件2 文件3 ``` <a name="advanced"></a> # Advanced <a name="superuser"></a> ## Superuser <a name="sudo-su"></a> ### `sudo / su` [[ Back to Table of Contents ]](#toc) You can check what your username is with `whoami`: ``` \[ andrew@pc01 abc \]$ whoami 安德魯 ``` ...and run a command as another user with `sudo -u username` (you will need that user's password): ``` \[ andrew@pc01 abc \]$ sudo -u 測試觸摸 def && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def ``` If `–u` is not provided, the default user is the superuser (usually called "root"), with unlimited permissions: ``` \[ andrew@pc01 abc \]$ sudo touch ghi && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def -rw-r--r-- 1 root root 0 Jan 11 20:14 ghi ``` Use `su` to become another user temporarily (and `exit` to switch back): ``` \[ andrew@pc01 abc \]$ su 測試 密碼: test@pc01:/home/andrew/abc$ whoami 測試 test@pc01:/home/andrew/abc$ 退出 出口 \[ andrew@pc01 abc \]$ whoami 安德魯 ``` [Learn more about the differences between `sudo` and `su` here.](http://bit.ly/2SKQH77) <a name="click-click"></a> ### `!!` [[ Back to Table of Contents ]](#toc) The superuser (usually "root") is the only person who can install software, create users, and so on. Sometimes it's easy to forget that, and you may get an error: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? ``` You could retype the command and add `sudo` at the front of it (run it as the superuser): ``` \[ andrew@pc01 ~ \]$ sudo apt install ruby 正在閱讀包裝清單... ``` Or, you could use the `!!` shortcut, which retains the previous command: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? \[ andrew@pc01 ~ \]$ sudo !! sudo apt 安裝 ruby 正在閱讀包裝清單... ``` By default, running a command with `sudo` (and correctly entering the password) allows the user to run superuser commands for the next 15 minutes. Once those 15 minutes are up, the user will again be prompted to enter the superuser password if they try to run a restricted command. <a name="file-permissions"></a> ## File Permissions <a name="file-permissions-sub"></a> ### File Permissions [[ Back to Table of Contents ]](#toc) Files may be able to be read (`r`), written to (`w`), and/or executed (`x`) by different users or groups of users, or not at all. File permissions can be seen with the `ls -l` command and are represented by 10 characters: ``` \[ andrew@pc01 ~ \]$ ls -lh 總計 8 drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxr-xr-x 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rw-r--r-- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The first character of each line represents the type of file, (`d` = directory, `l` = link, `-` = regular file, and so on); then there are three groups of three characters which represent the permissions held by the user (u) who owns the file, the permissions held by the group (g) which owns the file, and the permissions held any other (o) users. (The number which follows this string of characters is the number of links in the file system to that file (4 or 1 above).) `r` means that person / those people have read permission, `w` is write permission, `x` is execute permission. If a directory is “executable”, that means it can be opened and its contents can be listed. These three permissions are often represented with a single three-digit number, where, if `x` is enabled, the number is incremented by 1, if `w` is enabled, the number is incremented by 2, and if `r` is enabled, the number is incremented by 4. Note that these are equivalent to binary digits (`r-x` -> `101` -> `5`, for example). So the above three files have permissions of 755, 755, and 644, respectively. The next two strings in each list are the name of the owner (`andrew`, in this case) and the group of the owner (also `andrew`, in this case). Then comes the size of the file, its most recent modification time, and its name. The `–h` flag makes the output human readable (i.e. printing `4.0K` instead of `4096` bytes). <a name="chmod-chown"></a> ### `chmod / chown` [[ Back to Table of Contents ]](#toc) File permissions can be modified with `chmod` by setting the access bits: ``` \[ andrew@pc01 ~ \]$ chmod 777 測試 && chmod 000 tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 \---------- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` ...or by adding (`+`) or removing (`-`) `r`, `w`, and `x` permissions with flags: ``` \[ andrew@pc01 ~ \]$ chmod +rwx Tist && chmod -w 測試 && ls -lh chmod:測試:新權限是 r-xrwxrwx,而不是 r-xr-xr-x 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -r-xrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rwxr-xr-x 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The user who owns a file can be changed with `chown`: ``` \[ andrew@pc01 ~ \]$ sudo chown 碼頭測試 ``` The group which owns a file can be changed with `chgrp`: ``` \[ andrew@pc01 ~ \]$ sudo chgrp hadoop tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 \-----w--w- 1 瑪麗娜·安德魯 2011 年 1 月 40 日 16:16 測試 -rwxr-xr-x 1 安德魯 hadoop 0 一月 11 16:34 tist ``` <a name="users-groups"></a> ## User and Group Management <a name="users"></a> ### Users [[ Back to Table of Contents ]](#toc) `users` shows all users currently logged in. Note that a user can be logged in multiple times if -- for instance -- they're connected via multiple `ssh` sessions. ``` \[ andrew@pc01 ~ \]$ 用戶 安德魯·科林·科林·科林·科林·科林·克里希納·克里希納 ``` To see all users (even those not logged in), check `/etc/passwd`. (**WARNING**: do not modify this file! You can corrupt your user accounts and make it impossible to log in to your system.) ``` \[ andrew@pc01 ~ \]$ alias au="cut -d: -f1 /etc/passwd \\ > |排序| uniq”&& au \_易於 一個廣告 安德魯... ``` Add a user with `useradd`: ``` \[ andrew@pc01 ~ \]$ sudo useradd aardvark && au \_易於 土豚 一個廣告... ``` Delete a user with `userdel`: ``` \[ andrew@pc01 ~ \]$ sudo userdel aardvark && au \_易於 一個廣告 安德魯... ``` [Change a user’s default shell, username, password, or group membership with `usermod`.](http://bit.ly/2D4upIg) <a name="groups"></a> ### Groups [[ Back to Table of Contents ]](#toc) `groups` shows all of the groups of which the current user is a member: ``` \[ andrew@pc01 ~ \]$ 組 andrew adm cdrom sudo dial plugdev lpadmin sambashare hadoop ``` To see all groups on the system, check `/etc/group`. (**DO NOT MODIFY** this file unless you know what you are doing.) ``` \[ andrew@pc01 ~ \]$ alias ag=“cut -d: -f1 /etc/group \\ > |排序”&& ag 管理員 一個廣告 安德魯... ``` Add a group with `groupadd`: ``` \[ andrew@pc01 ~ \]$ sudo groupadd aardvark && ag 土豚 管理員 一個廣告... ``` Delete a group with `groupdel`: ``` \[ andrew@pc01 ~ \]$ sudo groupdel aardvark && ag 管理員 一個廣告 安德魯... ``` [Change a group’s name, ID number, or password with `groupmod`.](https://linux.die.net/man/8/groupmod) <a name="text-processing"></a> ## Text Processing <a name="uniq-sort-diff-cmp"></a> ### `uniq / sort / diff / cmp` [[ Back to Table of Contents ]](#toc) `uniq` can print unique lines (default) or repeated lines: ``` \[ andrew@pc01 man \]$ printf "1\\n2\\n2" > a && \\ > printf "1\\n3\\n2" > b \[ andrew@pc01 人 \]$ uniq a 1 2 ``` `sort` will sort lines alphabetically / numerically: ``` \[ andrew@pc01 man \]$ 排序 b 1 2 3 ``` `diff` will report which lines differ between two files: ``` \[ andrew@pc01 人 \]$ diff ab 2c2 < 2 --- > 3 ``` `cmp` reports which bytes differ between two files: ``` \[andrew@pc01 人\]$ cmp ab ab 不同:字元 3,第 2 行 ``` <a name="cut-sed"></a> ### `cut / sed` [[ Back to Table of Contents ]](#toc) `cut` is usually used to cut a line into sections on some delimiter (good for CSV processing). `-d` specifies the delimiter and `-f` specifies the field index to print (starting with 1 for the first field): ``` \[ andrew@pc01 人 \]$ printf "137.99.234.23" > c \[ andrew@pc01 man \]$ cut -d'.' c-f1 137 ``` `sed` is commonly used to replace a string with another string in a file: ``` \[ andrew@pc01 man \]$ echo "舊" | sed s/舊/新/ 新的 ``` ...but `sed` is an extremely powerful utility, and cannot be properly summarised here. It’s actually Turing-complete, so it can do anything that any other programming language can do. `sed` can find and replace based on regular expressions, selectively print lines of a file which match or contain a certain pattern, edit text files in-place and non-interactively, and much more. A few good tutorials on `sed` include: - [https://www.tutorialspoint.com/sed/](https://www.tutorialspoint.com/sed/) - [http://www.grymoire.com/Unix/Sed.html](http://www.grymoire.com/Unix/Sed.html) - [https://www.computerhope.com/unix/used.htm](https://www.computerhope.com/unix/used.htm) <a name="pattern-matching"></a> ## Pattern Matching <a name="grep"></a> ### `grep` [[ Back to Table of Contents ]](#toc) The name `grep` comes from `g`/`re`/`p` (search `g`lobally for a `r`egular `e`xpression and `p`rint it); it’s used for finding text in files. `grep` is used to find lines of a file which match some pattern: ``` \[ andrew@pc01 ~ \]$ grep -e " *.fi.* " /etc/profile /etc/profile:Bourne shell 的系統範圍 .profile 檔案 (sh(1)) =================================================== ``` # The file bash.bashrc already sets the default PS1. ``` ``` fi ``` ``` fi ``` … ``` ...or contain some word: ``` \[ andrew@pc01 ~ \]$ grep "andrew" /etc/passwd 安德魯:x:1000:1000:安德魯,,,:/home/andrew:/bin/bash ``` `grep` is usually the go-to choice for simply finding matching lines in a file, if you’re planning on allowing some other program to handle those lines (or if you just want to view them). `grep` allows for (`-E`) use of extended regular expressions, (`-F`) matching any one of multiple strings at once, and (`-r`) recursively searching files within a directory. These flags used to be implemented as separate commands (`egrep`, `fgrep`, and `rgrep`, respectively), but those commands are now deprecated. > **Bonus**: [see the origins of the names of a few famous `bash` commands](https://kb.iu.edu/d/abnd) <a name="awk"></a> ### `awk` [[ Back to Table of Contents ]](#toc) `awk` is a pattern-matching language built around reading and manipulating delimited data files, like CSV files. As a rule of thumb, `grep` is good for finding strings and patterns in files, `sed` is good for one-to-one replacement of strings in files, and `awk` is good for extracting strings and patterns from files and analysing them. As an example of what `awk` can do, here’s a file containing two columns of data: ``` \[ andrew@pc01 ~ \]$ printf "A 10\\nB 20\\nC 60" > 文件 ``` Loop over the lines, add the number to sum, increment count, print the average: ``` \[ andrew@pc01 ~ \]$ awk 'BEGIN {sum=0;計數=0; OFS=“”} {sum+=$2; count++} END {print "平均值:", sum/count}' 文件 平均:30 ``` `sed` and `awk` are both Turing-complete languages. There have been multiple books written about each of them. They can be extremely useful with pattern matching and text processing. I really don’t have enough space here to do either of them justice. Go read more about them! > **Bonus**: [learn about some of the differences between `sed`, `grep`, and `awk`](http://bit.ly/2AI3IaN) <a name="ssh"></a> ## Copying Files Over `ssh` <a name="ssh-scp"></a> ### `ssh / scp` [[ Back to Table of Contents ]](#toc) `ssh` is how Unix-based machines connect to each other over a network: ``` \[ andrew@pc01 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 12:30:52,來自 137.xxx.xxx.199 ``` Notice that my prompt has changed as I’m now on a different machine: ``` \[ andrew@pc02 ~ \]$ 退出 登出 與 137.xxx.xxx.89 的連線已關閉。 ``` Create a file on machine 1: ``` \[ andrew@pc01 ~ \]$ echo "你好" > 你好 ``` Copy it to machine 2 using `scp` (secure copy; note that `scp` uses `–P` for a port #, `ssh` uses `–p`) ``` \[ andrew@pc01 ~ \]$ scp –P你好安德魯@137.xxx.xxx.89:~ 你好 100% 0 0.0KB/秒 00:00 ``` `ssh` into machine 2: ``` \[ andrew@pc02 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 22:47:37,來自 137.xxx.xxx.79 ``` The file’s there! ``` \[ andrew@pc02 ~ \]$ ls 你好多xargs \[ andrew@pc02 ~ \]$ 貓你好 你好 ``` <a name="rsync"></a> ### `rsync` [[ Back to Table of Contents ]](#toc) `rsync` is a file-copying tool which minimises the amount of data copied by looking for deltas (changes) between files. Suppose we have two directories: `d`, with one file, and `s`, with two files: ``` \[ andrew@pc01 d \]$ ls && ls ../s f0 f0 f1 ``` Sync the directories (copying only missing data) with `rsync`: ``` \[ andrew@pc01 d \]$ rsync -off ../s/\* . 正在發送增量文件列表... ``` `d` now contains all files that `s` contains: ``` \[ andrew@pc01 d \]$ ls f0 f1 ``` `rsync` can be performed over `ssh` as well: ``` \[ andrew@pc02 r \]$ ls \[ andrew@pc02 r \]$ rsync -avz -e "ssh -p “ [email protected]:~/s/\* 。 接收增量檔案列表 f0 f1 發送 62 位元組 接收 150 位元組 141.33 位元組/秒 總大小為 0 加速率為 0.00 \[ andrew@pc02 r \]$ ls f0 f1 ``` <a name="long-running-processes"></a> ## Long-Running Processes <a name="yes-nohup-ps-kill"></a> ### `yes / nohup / ps / kill` [[ Back to Table of Contents ]](#toc) Sometimes, `ssh` connections can disconnect due to network or hardware problems. Any processes initialized through that connection will be “hung up” and terminate. Running a command with `nohup` insures that the command will not be hung up if the shell is closed or if the network connection fails. Run `yes` (continually outputs "y" until it’s killed) with `nohup`: ``` \[ andrew@pc01 ~ \]$ nohup 是 & \[1\]13173 ``` `ps` shows a list of the current user’s processes (note PID number 13173): ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' 13173 分/10 00:00:12 是 ``` _...log out and log back into this shell..._ The process has disappeared from `ps`! ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' ``` But it still appears in `top` and `htop` output: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' 13173 安德魯 20 0 4372 704 636 D 25.0 0.0 0:35.99 是 ``` Kill this process with `-9` followed by its process ID (PID) number: ``` \[ andrew@pc01 ~ \]$ 殺死 -9 13173 ``` It no longer appears in `top`, because it’s been killed: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' ``` <a name="cron"></a> ### `cron / crontab / >>` [[ Back to Table of Contents ]](#toc) `cron` provides an easy way of automating regular, scheduled tasks. You can edit your `cron` jobs with `crontab –e` (opens a text editor). Append the line: ``` - - - - - 日期 >> ~/datefile.txt ``` This will run the `date` command every minute, appending (with the `>>` operator) the output to a file: ``` \[ andrew@pc02 ~ \]$ head ~/datefile.txt 2019 年 1 月 12 日星期六 14:37:01 GMT 2019 年 1 月 12 日星期六

S.O.L.I.D:提升編碼技能的 5 個黃金法則

在軟體開發領域,這個以其多樣化和強烈持有觀點而聞名的領域,很少有實踐能夠像 SOLID 原則那樣達成共識,作為成為更好的軟體工程師的保證途徑。 Robert C. Martin 在 2000 年代初期正式製定的 5 條黃金法則極大地影響了軟體開發產業,並為更好的程式碼品質和決策過程製定了新標準,至今仍具有相關性。 ![堅實的原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2mdlic5qf7f0ws36owf.jpg) SOLID 原則是專門為支援 OOP(物件導向程式設計)範例而設計的。因此,本文是為希望提高開發技能並編寫更優雅、可維護和可擴展程式碼的 OOP 開發人員而設計的。 這裡使用的語言是 TypeScript,遵循常見的跨語言 OOP 概念。需要基本的 OOP 知識。 --- 1. S = 單一職責原則(SRP) ------------------ 單一職責原則 (SRP) 是五個 SOLID 原則之一,它規定每個類別應該只有一個職責,以保持有意義的關注點分離。 此模式是一種稱為「上帝物件」的常見反模式的解決方案,「上帝物件」只是指承擔太多職責的類別或物件,使其難以理解、測試和維護。 遵循 SRP 規則有助於使程式碼元件可重複使用、鬆散耦合且易於理解。讓我們探討這項原則,展示 SRP 違規情況和解決方案。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } ``` ### 違反 在下面的程式碼中, `ProductManager`類別負責**products 的建立和存儲**,違反了單一職責原則。 ``` class ProductManager { private _products: Product[] = []; createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } const productManager: ProductManager = new ProductManager(); const product: Product = productManager.createProduct('Product 1', Color.BLUE, Size.LARGE); productManager.storeProduct(product); const allProducts: Product[] = productManager.getProducts(); ``` ### 解決 將產品建立和儲存的處理分離到兩個不同的類別可以減少`ProductManager`類別的職責數量。這種方法進一步模組化了程式碼並使其更易於維護。 ``` class ProductManager { createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } } class ProductStorage { private _products: Product[] = []; storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } ``` #### 用法: ``` const productManager: ProductManager = new ProductManager(); const productStorage: ProductStorage = new ProductStorage(); const product: Product = productManager.createProduct("Product 1", Color.BLUE, Size.LARGE); productStorage.storeProduct(product); const allProducts: Product[] = productStorage.getProducts(); ``` --- 2. O = 開閉原理 (OCP) ----------------- > “軟體實體應該對擴展開放,但對修改關閉” 開閉原則 (OCP) 是*「寫一次,寫得夠好以便可擴展,然後就忘記它」。* 這項原則的重要性取決於這樣一個事實:模組可能會根據新的需求不時發生變化。如果在模組編寫、測試並上傳到生產環境後出現新需求,則修改此模組通常是不好的做法,尤其是當其他模組依賴它時。為了防止這種情況,我們可以使用開閉原則。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } class Inventory { private _products: Product[] = []; public add(product: Product): void { this._products.push(product); } addArray(products: Product[]) { for (const product of products) { this.add(product); } } public get products(): Product[] { return this._products; } } ``` ### 違反 讓我們描述一個實作產品過濾類別的場景。讓我們加入按顏色過濾產品的功能。 ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } } ``` 我們已經測試了此程式碼並將其部署到生產中。 幾天后,客戶請求一項新功能 - 也按大小過濾。然後我們修改該類別以支援新的要求。 **現在違反了開閉原則!** ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } bySize(inventory: Inventory, size: Size): Product[] { return inventory.products.filter(p => p.size === size); } } ``` ### 解決 在不違反 OCP 的情況下實現過濾機制的正確方法應該使用「規範」類別。 ``` abstract class Specification { public abstract isValid(product: Product): boolean; } class ColorSpecification extends Specification { private _color: Color; constructor (color) { super(); this._color = color; } public isValid(product: Product): boolean { return product.color === this._color; } } class SizeSpecification extends Specification { private _size: Size; constructor (size) { super(); this._size = size; } public isValid(product: Product): boolean { return product.size === this._size; } } // A robust mechanism to allow different combinations of specifications class AndSpecification extends Specification { private _specifications: Specification[]; // "...rest" operator, groups the arguments into an array constructor ((...specifications): Specification[]) { super(); this._specifications = specifications; } public isValid (product: Product): boolean { return this._specifications.every(specification => specification.isValid(product)); } } class ProductsFilter { public filter (inventory: Inventory, specification: Specification): Product[] { return inventory.products.filter(product => specification.isValid(product)); } } ``` #### 用法: ``` const p1: Product = new Product('Apple', Color.GREEN, Size.LARGE); const p2: Product = new Product('Pear', Color.GREEN, Size.LARGE); const p3: Product = new Product('Grapes', Color.GREEN, Size.SMALL); const p4: Product = new Product('Blueberries', Color.BLUE, Size.LARGE); const p5: Product = new Product('Watermelon', Color.RED, Size.LARGE); const inventory: Inventory = new Inventory(); inventory.addArray([p1, p2, p3, p4, p5]); const greenColorSpec: ColorSpecification = new ColorSpecification(Color.GREEN); const largeSizeSpec: SizeSpecification = new SizeSpecification(Size.LARGE); const andSpec: AndSpecification = new AndSpecification(greenColorSpec, largeSizeSpec); const productsFilter: ProductsFilter = new ProductsFilter(); const filteredProducts: Product[] = productsFilter.filter(inventory, andSpec); // All large green products ``` 過濾機制現在是完全可擴展的。現有的類別不應該再被修改。 如果有新的過濾要求,我們只需建立一個新規範即可。或者如果需要更改規範組合,可以透過使用`AndSpecification`類別輕鬆完成。 --- 3. L=里氏替換原理(LSP) ---------------- 里氏替換原則(LSP)是軟體元件靈活性和穩健性的重要規則。它由 Barbara Liskov 提出,並成為 SOLID 原則的基本要素。 LSP 規定**超類別的物件應該可以用子類別的物件替換,而不影響程式的正確性。**換句話說,子類別應該擴展超類別的行為而不改變其原始功能。採用這種方法可以提高軟體元件的質量,確保可重複使用性並減少意外的副作用。 ### 違反 下面的範例說明了違反里氏替換原則 (LSP) 的場景。當`Rectangle`物件被`Square`物件取代時,透過檢查程序的行為可以觀察到這種違規的跡象。 #### 聲明: ``` class Rectangle { protected _width: number; protected _height: number; constructor (width: number, height: number) { this._width = width; this._height = height; } get width (): number { return this._width; } get height (): number { return this._height; } set width (width: number) { this._width = width; } set height (height: number) { this._height = height; } getArea (): number { return this._width * this._height; } } // A square is also rectangle class Square extends Rectangle { get width (): number { return this._width; } get height (): number { return this._height; } set height (height: number) { this._height = this._width = height; // Changing both width & height } set width (width: number) { this._width = this._height = width; // Changing both width & height } } function increaseRectangleWidth(rectangle: Rectangle, byAmount: number) { rectangle.width += byAmount; } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 5); const square: Square = new Square(5, 5); console.log(rectangle.getArea()); // Expected: 25, Got: 25 (V) console.log(square.getArea()); // Expected: 25, Got: 25 (V) // LSP Violation Indication: Can't replace object 'rectangle' (superclass) with 'square' (subclass) since the results would be different. increaseRectangleWidth(rectangle, 5); increaseRectangleWidth(square, 5); console.log(rectangle.getArea()); // Expected: 50, Got: 50 (V) // LSP Violation, increaseRectangleWidth() changed both width and height of the square, unexpected behavior. console.log(square.getArea()); //Expected: 50, Got: 100 (X) ``` ### 解決 重構的程式碼現在遵循 LSP,確保超類別`Shape`的物件可以替換為子類別`Rectangle`和`Square`的物件,而不會影響計算面積的正確性,也不會引入任何改變程式行為的不必要的副作用。 #### 聲明: ``` abstract class Shape { public abstract getArea(): number; } class Rectangle extends Shape { private _width: number; private _height: number; constructor (width: number, height: number) { super(); this._width = width; this._height = height; } getArea (): number { return this._width * this._height; } } class Square extends Shape { private _side: number; constructor (side: number) { super(); this._side = side; } getArea (): number { return this._side * this._side; } } function displayArea (shape: Shape): void { console.log(shape.getArea()); } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 10); const square: Square = new Square(5); // The rectangle's area is correctly calculated displayArea(rectangle); // Expected: 50, Got: 50 (V) // The square's area is correctly calculated displayArea(square); // Expected: 25, Got: 25 (V) ``` --- 4. I = 介面隔離原則 (ISP) ------------------- 介面隔離原則 (ISP) 強調建立特定於客戶端的介面而不是一刀切的重要性。 這種方法根據客戶的需求集中類,消除了類別必須實現它實際上不使用或不需要的方法的情況。 透過應用介面隔離原則,軟體系統可以以更靈活、易於理解和易於重構的方式建構。讓我們來看一個例子。 ### 違反 這裡違反了 ISP 規則,因為`Robot`必須實現完全沒有必要的`eat()`函數。 ``` interface Worker { work(): void; eat(): void; } class Developer implements Worker { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating..'); } } class Robot implements Worker { public work(): void { console.log('Building a car..'); } // ISP Violation: Robot is forced to implement this function even when unnecessary public eat(): void { throw new Error('Cannot eat!'); } } ``` ### 解決 下面的範例代表了我們之前遇到的問題的解決方案。現在,介面更加簡潔且更加特定於客戶端,允許客戶端類別僅實現與其相關的方法。 ``` interface Workable { work(): void; } interface Eatable { eat(): void; } class Developer implements Workable, Eatable { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating...'); } } class Robot implements Workable { public work(): void { console.log('Building a car..'); } // No need to implement eat(), adhering ISP. } ``` #### ISP 前後: ![重構前後的介面隔離原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szh5wkpkc30z4t91j5oy.png) --- 5. D = 依賴倒置原理(DIP) ------------------ 依賴倒置原則(DIP)是最終的SOLID原則,重點是透過使用抽象來減少低層模組(例如資料讀取/寫入)和高層模組(執行關鍵操作)之間的耦合。 DIP 對於設計能夠適應變化、模組化且易於更新的軟體至關重要。 **DIP 的關鍵準則是:** 1. **高層模組不應該依賴低層模組。兩者都應該依賴抽象。**這意味著應用程式的功能不應該依賴特定的實現,以便使系統更加靈活並且更容易更新或替換低階實現。 2. **抽像不應該依賴細節。細節應該取決於抽象。**這鼓勵設計專注於實際需要什麼操作,而不是如何實現這些操作。 ### 違反 讓我們來看一個展示依賴倒置原則 (DIP) 違規的範例。 `MessageProcessor` (高階模組)緊密耦合並直接依賴`FileLogger` (低階模組),違反了原則,因為它不依賴抽象層,而是依賴特定的類別實作。 **額外獎勵:**這也違反了開閉原則(OCP)。如果我們想要更改日誌記錄機制以寫入資料庫而不是文件,我們將被迫直接修改`MessageProcessor`函數。 ``` import fs from 'fs'; // Low Level Module class FileLogger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // High Level Module class MessageProcessor { // DIP Violation: This high-level module is is tightly coupled with the low-level module (FileLogger), making the system less flexible and harder to maintain or extend. private logger = new FileLogger(); processMessage(message: string): void { this.logger.logMessage(message); } } ``` ### 解決 以下重構的程式碼表示為了遵守依賴倒置原則 (DIP) 所需進行的變更。與前面的範例相反,高階類別`MessageProcessor`持有特定低階類別`FileLogger`的私有屬性,現在它持有`Logger`類型的私有屬性(表示抽象層的介面)。 這種更好的方法減少了類別之間的依賴關係,從而使程式碼更具可擴展性和可維護性。 #### 聲明: ``` import fs from 'fs'; // Abstraction Layer interface Logger { logMessage(message: string): void; } // Low Level Module #1 class FileLogger implements Logger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // Low Level Module #2 class ConsoleLogger implements Logger { logMessage(message: string): void { console.log(message); } } // High Level Module class MessageProcessor { // Resolved: The high level module is now loosely coupled with the low level logger modules. private _logger: Logger; constructor (logger: Logger) { this._logger = logger; } processMessage (message: string): void { this._logger.logMessage(message); } } ``` #### 用法: ``` const fileLogger = new FileLogger(); const consoleLogger = new ConsoleLogger(); // Now the logging mechanism can be easily replaced const messageProcessor = new MessageProcessor(consoleLogger); messageProcessor.processMessage('Hello'); ``` #### DIP 之前和之後: ![重構前後的依賴倒置原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kju8qfpie60b104vuz90.png) 結論 -- 透過遵循 SOLID 原則,開發人員在開發或維護任何規模的軟體系統時,可以避免常見的陷阱,例如緊密耦合、缺乏靈活性、程式碼可重複使用性差以及一般維護困難。掌握這些原則是成為更好的軟體工程師的又一步。 --- 原文出處:https://dev.to/idanref/solid-the-5-golden-rules-to-level-up-your-coding-skills-2p82

70 個 JavaScript 面試問題

嗨大家好,新年快樂:煙火::煙火::煙火:! ---------------------- 這是一篇很長的文章,所以請耐心聽我一秒鐘或一個小時。每個問題的每個答案都有一個向上箭頭**↑**連結,可讓您返回到問題列表,這樣您就不會浪費時間上下滾動。 ### 問題 - [1. `undefined`和`null`有什麼差別?](#1-whats-the-difference-between-undefined-and-null) - [2. &amp;&amp; 運算子的作用是什麼?](#2-what-does-the-ampamp-operator-do) - [3. || 是什麼意思?運營商做什麼?](#3-what-does-the-operator-do) - [4. 使用 + 或一元加運算子是將字串轉換為數字的最快方法嗎?](#4-is-using-the-or-unary-plus-operator-the-fastest-way-in-converting-a-string-to-a-number) - [5.什麼是DOM?](#5-what-is-the-dom) - [6.什麼是事件傳播?](#6-what-is-event-propagation) - [7.什麼是事件冒泡?](#7-whats-event-bubbling) - [8. 什麼是事件擷取?](#8-whats-event-capturing) - [9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別?](#9-whats-the-difference-between-eventpreventdefault-and-eventstoppropagation-methods) - [10. 如何知道元素中是否使用了`event.preventDefault()`方法?](#10-how-to-know-if-the-eventpreventdefault-method-was-used-in-an-element) - [11. 為什麼這段程式碼 obj.someprop.x 會拋出錯誤?](#11-why-does-this-code-objsomepropx-throw-an-error) - \[12.什麼是`event.target` ?\](#12-什麼是 eventtarget- ) - [13.什麼是`event.currentTarget` ?](#13-what-is-eventcurrenttarget) - [14. `==`和`===`有什麼差別?](#14-whats-the-difference-between-and-) - [15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳 false?](#15-why-does-it-return-false-when-comparing-two-similar-objects-in-javascript) - [16. `!!`是什麼意思?運營商做什麼?](#16-what-does-the-operator-do) - [17. 如何計算一行中的多個表達式?](#17-how-to-evaluate-multiple-expressions-in-one-line) - [18.什麼是吊裝?](#18-what-is-hoisting) - [19.什麼是範圍?](#19-what-is-scope) - [20.什麼是閉包?](#20-what-are-closures) - [21. JavaScript 中的假值是什麼?](#21-what-are-the-falsy-values-in-javascript) - [22. 如何檢查一個值是否為假值?](#22-how-to-check-if-a-value-is-falsy) - [23. `"use strict"`有什麼作用?](#23-what-does-use-strict-do) - [24. JavaScript 中`this`的值是什麼?](#24-whats-the-value-of-this-in-javascript) - [25. 物件的`prototype`是什麼?](#25-what-is-the-prototype-of-an-object) - \[26.什麼是 IIFE,它有什麼用?\](#26-what-is-an-iife-what-is-the-use-of-it ) - [27. `Function.prototype.apply`方法有什麼用?](#27-what-is-the-use-functionprototypeapply-method) - [28. `Function.prototype.call`方法有什麼用?](#28-what-is-the-use-functionprototypecall-method) - [29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別?](#29-whats-the-difference-between-functionprototypeapply-and-functionprototypecall) - [30. `Function.prototype.bind`的用法是什麼?](#30-what-is-the-usage-of-functionprototypebind) - \[31.什麼是函數式程式設計以及 JavaScript 的哪些特性使其成為函數式語言的候選者?\](#31-什麼是函數式程式設計和 javascript 的特性是什麼-使其成為函數式語言的候選者 ) - [32.什麼是高階函數?](#32-what-are-higher-order-functions) - [33.為什麼函數被稱為First-class Objects?](#33-why-are-functions-called-firstclass-objects) - \[34.手動實作`Array.prototype.map`方法。\](#34-手動實作 arrayprototypemap-method ) - [35. 手動實作`Array.prototype.filter`方法。](#35-implement-the-arrayprototypefilter-method-by-hand) - [36. 手動實作`Array.prototype.reduce`方法。](#36-implement-the-arrayprototypereduce-method-by-hand) - [37.什麼是`arguments`物件?](#37-what-is-the-arguments-object) - [38. 如何創造沒有**原型的**物件?](#38-how-to-create-an-object-without-a-prototype) - [39. 為什麼當你呼叫這個函數時,這段程式碼中的`b`會變成全域變數?](#39-why-does-b-in-this-code-become-a-global-variable-when-you-call-this-function) - [40.什麼是**ECMAScript** ?](#40-what-is-ecmascript) - [41. **ES6**或**ECMAScript 2015**有哪些新功能?](#41-what-are-the-new-features-in-es6-or-ecmascript-2015) - [42. `var` 、 `let`和`const`關鍵字有什麼差別?](#42-whats-the-difference-between-var-let-and-const-keywords) - [43. 什麼是**箭頭函數**?](#43-what-are-arrow-functions) - [44.什麼是**類別**?](#44-what-are-classes) - [45.什麼是**模板文字**?](#45-what-are-template-literals) - [46.什麼是**物件解構**?](#46-what-is-object-destructuring) - [47.什麼是`ES6 Modules` ?](#47-what-are-es6-modules) - [48.什麼是`Set`物件以及它如何運作?](#48-what-is-the-set-object-and-how-does-it-work) - [49. 什麼是回呼函數?](#49-what-is-a-callback-function) - [50. 什麼是**Promise** ?](#50-what-are-promises) - [51. 什麼是*async/await*以及它是如何運作的?](#51-what-is-asyncawait-and-how-does-it-work) - [52. **Spread 運算子**和**Rest 運算**子有什麼差別?](#52-whats-the-difference-between-spread-operator-and-rest-operator) - [53. 什麼是**預設參數**?](#53-what-are-default-parameters) - [54.什麼是**包裝物件**?](#54-what-are-wrapper-objects) - [55.**隱性強制**和**顯性**強制有什麼差別?](#55-what-is-the-difference-between-implicit-and-explicit-coercion) - [56. 什麼是`NaN` ?以及如何檢查值是否為`NaN` ?](#56-what-is-nan-and-how-to-check-if-a-value-is-nan) - [57. 如何檢查一個值是否為一個**陣列**?](#57-how-to-check-if-a-value-is-an-array) - [58. 如何在不使用`%`或模運算子的情況下檢查數字是否為偶數?](#58-how-to-check-if-a-number-is-even-without-using-the-or-modulo-operator) - [59. 如何檢查物件中是否存在某個屬性?](#59-how-to-check-if-a-certain-property-exists-in-an-object) - [60.什麼是**AJAX** ?](#60-what-is-ajax) - [61. JavaScript 中建立物件的方式有哪些?](#61-what-are-the-ways-of-making-objects-in-javascript) - [62. `Object.seal`和`Object.freeze`方法有什麼不同?](#62-whats-the-difference-between-objectseal-and-objectfreeze-methods) - [63. `in`運算子和物件中的`hasOwnProperty`方法有什麼差別?](#63-whats-the-difference-between-the-in-operator-and-the-hasownproperty-method-in-objects) - [64. JavaScript中處理**非同步程式碼的**方法有哪些?](#64-what-are-the-ways-to-deal-with-asynchronous-code-in-javasscript) - [65.**函數表達式**和**函數宣告**有什麼不同?](#65-whats-the-difference-between-a-function-expression-and-function-declaration) - \[66.一個函數有多少種*呼叫*方式?\]( 66-函數可以有多少種方式被呼叫) ================= - [67. 什麼是*記憶*,它有什麼用?](#67-what-is-memoization-and-whats-the-use-it) - [68. 實現記憶輔助功能。](#68-implement-a-memoization-helper-function) - [69. 為什麼`typeof null`回傳`object` ?如何檢查一個值是否為`null` ?](#69-why-does-typeof-null-return-object-how-to-check-if-a-value-is-null) - [`new`關鍵字有什麼作用?](#70-what-does-the-new-keyword-do) ### 1. `undefined`和`null`有什麼差別? [^](#the-questions "返回問題")在了解`undefined`和`null`之間的差異之前,我們必須先了解它們之間的相似之處。 - 它們屬於**JavaScript 的**7 種基本型別。 ``` let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint']; ``` - 它們是**虛假的**價值觀。使用`Boolean(value)`或`!!value`將其轉換為布林值時計算結果為 false 的值。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(Boolean(null)); //logs false console.log(Boolean(undefined)); //logs false ``` 好吧,我們來談談差異。 - `undefined`是尚未指派特定值的變數的預設值。或一個沒有**明確**回傳值的函數。 `console.log(1)` 。或物件中不存在的屬性。 JavaScript 引擎為我們完成了**指派**`undefined`值的任務。 ``` let _thisIsUndefined; const doNothing = () => {}; const someObj = { a : "ay", b : "bee", c : "si" }; console.log(_thisIsUndefined); //logs undefined console.log(doNothing()); //logs undefined console.log(someObj["d"]); //logs undefined ``` - `null`是**「代表無值的值」** 。 `null`是已**明確**定義給變數的值。在此範例中,當`fs.readFile`方法未引發錯誤時,我們得到`null`值。 ``` fs.readFile('path/to/file', (e,data) => { console.log(e); //it logs null when no error occurred if(e){ console.log(e); } console.log(data); }); ``` 當比較`null`和`undefined`時,使用`==`時我們得到`true` ,使用`===`時得到`false` 。您可以[在此處](#14-whats-the-difference-between-and-)閱讀原因。 ``` console.log(null == undefined); // logs true console.log(null === undefined); // logs false ``` ### 2. `&&`運算子的作用是什麼? [^](#the-questions "返回問題") `&&`或**邏輯 AND**運算子在其運算元中尋找第一個*假*表達式並傳回它,如果沒有找到任何*假*表達式,則傳回最後一個表達式。它採用短路來防止不必要的工作。在我的一個專案中關閉資料庫連線時,我在`catch`區塊中使用了它。 ``` console.log(false && 1 && []); //logs false console.log(" " && true && 5); //logs 5 ``` 使用**if**語句。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { if (conMobile) { conMobile.release(); } } }); ``` 使用**&amp;&amp;**運算子。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { conMobile && conMobile.release() } }); ``` ### 3. `||`是什麼意思?運營商做什麼? [↑](#the-questions "返回問題") `||` or**邏輯 OR**運算子尋找其運算元中的第一個*真值*表達式並傳回它。這也採用短路來防止不必要的工作。在**ES6預設函數參數**被支援之前,它被用來初始化函數中的預設參數值。 ``` console.log(null || 1 || undefined); //logs 1 function logName(name) { var n = name || "Mark"; console.log(n); } logName(); //logs "Mark" ``` ### 4. 使用**+**或一元加運算子是將字串轉換為數字的最快方法嗎? [^](#the-questions "返回問題")根據[MDN 文件,](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus) `+`是將字串轉換為數字的最快方法,因為如果該值已經是數字,它不會對該值執行任何操作。 ### 5.什麼是**DOM** ? [^](#the-questions "返回問題") **DOM**代表**文件物件模型,**是 HTML 和 XML 文件的介面 ( **API** )。當瀏覽器第一次讀取(*解析*)我們的 HTML 文件時,它會建立一個大物件,一個基於 HTML 文件的非常大的物件,這就是**DOM** 。它是根據 HTML 文件建模的樹狀結構。 **DOM**用於互動和修改**DOM 結構**或特定元素或節點。 想像一下,如果我們有這樣的 HTML 結構。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document Object Model</title> </head> <body> <div> <p> <span></span> </p> <label></label> <input> </div> </body> </html> ``` 等效的**DOM**應該是這樣的。 ![DOM 等效項](https://thepracticaldev.s3.amazonaws.com/i/mbqphfbjfie45ynj0teo.png) **JavaScript**中的`document`物件代表**DOM** 。它為我們提供了許多方法,我們可以用來選擇元素來更新元素內容等等。 ### 6.什麼是**事件傳播**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` ,而在**捕獲階段**,事件從`window`開始向下到達觸發的元素事件或`<a href="#12-what-is-eventtarget-">event.target</a>` 。 **事件傳播**分為**三個**階段。 1. [捕獲階段](#8-whats-event-capturing)-事件從`window`開始,然後向下到達每個元素,直到到達目標元素。 2. [目標階段](#12-what-is-eventtarget-)– 事件已到達目標元素。 3. [冒泡階段](#7-whats-event-bubbling)-事件從目標元素冒起,然後向上移動到每個元素,直到到達`window` 。 ![事件傳播](https://thepracticaldev.s3.amazonaws.com/i/hjayqa99iejfhbsujlqd.png) ### 7.什麼是**事件冒泡**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` 。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }); addEvent(parent, 'click', function (e) { console.log('parent'); }); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }); addEvent(document, 'click', function (e) { console.log('document'); }); addEvent('html', 'click', function (e) { console.log('html'); }) addEvent(window, 'click', function (e) { console.log('window'); }) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`child` 、 `parent`元素、 `grandparent` 、 `html` 、 `document`和`window` 。這就是**事件冒泡**。 ### 8. 什麼是**事件擷取**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**捕獲階段**,事件從`window`開始一直到觸發事件的元素。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }, true); addEvent(parent, 'click', function (e) { console.log('parent'); }, true); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }, true); addEvent(document, 'click', function (e) { console.log('document'); }, true); addEvent('html', 'click', function (e) { console.log('html'); }, true) addEvent(window, 'click', function (e) { console.log('window'); }, true) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`window` 、 `document` 、 `html` 、 `grandparent` 、 `parent`和`child` 。這就是**事件捕獲**。 ### 9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別? [↑](#the-questions "返回問題") `event.preventDefault()`方法**阻止**元素的預設行為。如果在`form`元素中使用,它**會阻止**其提交。如果在`anchor`元素中使用,它**會阻止**其導航。如果在`contextmenu`中使用,它**會阻止**其顯示或顯示。而`event.stopPropagation()`方法會停止事件的傳播或停止事件在[冒泡](#7-whats-event-bubbling)或[捕獲](#8-whats-event-capturing)階段發生。 ### 10. 如何知道元素中是否使用了`event.preventDefault()`方法? [↑](#the-questions "返回問題")我們可以使用事件物件中的`event.defaultPrevented`屬性。它傳回一個`boolean` ,指示是否在特定元素中呼叫了`event.preventDefault()` 。 ### 11. 為什麼這段程式碼`obj.someprop.x`會拋出錯誤? ``` const obj = {}; console.log(obj.someprop.x); ``` [^](#the-questions "返回問題")顯然,由於我們嘗試存取 a 的原因,這會引發錯誤 `someprop`屬性中的`x`屬性具有`undefined`值。請記住,物件中的**屬性**本身並不存在,且其**原型**具有預設值`undefined`且`undefined`沒有屬性`x` 。 ### 12.什麼是**event.target** ? [↑](#the-questions "返回問題")最簡單來說, **event.target**是**發生**事件的元素或**觸發**事件的元素。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` JavaScript 範例。 ``` function clickFunc(event) { console.log(event.target); } ``` 如果您單擊按鈕,它會記錄**按鈕**標記,即使我們將事件附加在最外部的`div`上,它也會始終記錄**按鈕**,因此我們可以得出結論, **event.target**是觸發事件的元素。 ### 13.什麼是**event.currentTarget** ? [↑](#the-questions "返回問題") **event.currentTarget**是我們**明確**附加事件處理程序的元素。 複製**問題 12**中的標記。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` 並且稍微改變我們的**JS** 。 ``` function clickFunc(event) { console.log(event.currentTarget); } ``` 如果您按一下該按鈕,即使我們按一下該按鈕,它也會記錄最外層的**div**標記。在此範例中,我們可以得出結論, **event.currentTarget**是我們附加事件處理程序的元素。 ### 14. `==`和`===`有什麼差別? [^](#the-questions "返回問題") `==` \_\_(抽象相等)\_\_ 和`===` \_\_(嚴格相等)\_\_ 之間的區別在於`==`在*強制轉換*後按**值**進行比較,而`===`在不進行*強制轉換的*情況下按**值**和**類型**進行比較。 讓我們更深入地研究`==` 。那麼首先我們來談談*強制*。 *強制轉換*是將一個值轉換為另一種類型的過程。在本例中, `==`進行*隱式強制轉換*。在比較兩個值之前, `==`需要執行一些條件。 假設我們必須比較`x == y`值。 1. 如果`x`和`y`具有相同的類型。 然後將它們與`===`運算子進行比較。 2. 如果`x`為`null`且`y` `undefined` ,則傳回`true` 。 3. 如果`x` `undefined`且`y`為`null`則傳回`true` 。 4. 如果`x`是`number`類型, `y`是`string`類型 然後回傳`x == toNumber(y)` 。 5. 如果`x`是`string`類型, `y`是`number`類型 然後返回`toNumber(x) == y` 。 6. 如果`x`是`boolean`類型 然後返回`toNumber(x) == y` 。 7. 如果`y`是`boolean`類型 然後回傳`x == toNumber(y)` 。 8. 如果`x`是`string` 、 `symbol`或`number`且`y`是 type `object` 然後回傳`x == toPrimitive(y)` 。 9. 如果`x`是`object`且`x`是`string` 、 `symbol` 然後返回`toPrimitive(x) == y` 。 10. 返回`false` 。 **注意:** `toPrimitive`首先使用物件中的`valueOf`方法,然後使用`toString`方法來取得該物件的原始值。 讓我們舉個例子。 | `x` | `y` | `x == y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `true` | | `null` | `undefined` | `true` | | `0` | `false` | `true` | | `'1,2'` | `[1,2]` | `true` | | `'[object Object]'` | `{}` | `true` | 這些範例都傳回`true` 。 **第一個範例**屬於**條件一**,因為`x`和`y`具有相同的類型和值。 **第二個範例**轉到**條件四,**在比較之前將`y`轉換為`number` 。 **第三個例子**涉及**條件二**。 **第四個範例**轉到**條件七,**因為`y`是`boolean` 。 **第五個範例**適用於**條件八**。使用`toString()`方法將陣列轉換為`string` ,該方法傳回`1,2` 。 **最後一個例子**適用於**條件十**。使用傳回`[object Object]`的`toString()`方法將該物件轉換為`string` 。 | `x` | `y` | `x === y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `false` | | `null` | `undefined` | `false` | | `0` | `false` | `false` | | `'1,2'` | `[1,2]` | `false` | | `'[object Object]'` | `{}` | `false` | 如果我們使用`===`運算符,則除第一個範例之外的所有比較都將傳回`false` ,因為它們不具有相同的類型,而第一個範例將傳回`true` ,因為兩者俱有相同的類型和值。 ### 15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳**false** ? [^](#the-questions "返回問題")假設我們有下面的例子。 ``` let a = { a: 1 }; let b = { a: 1 }; let c = a; console.log(a === b); // logs false even though they have the same property console.log(a === c); // logs true hmm ``` **JavaScript**以不同的方式比較*物件*和*基元*。在*基元*中,它透過**值**來比較它們,而在*物件*中,它透過**引用**或**儲存變數的記憶體位址**來比較它們。這就是為什麼第一個`console.log`語句回傳`false`而第二個`console.log`語句回傳`true`的原因。 `a`和`c`有相同的引用,而`a`和`b`則不同。 ### 16. **!!**是什麼意思?運營商做什麼? [↑](#the-questions "返回問題")**雙非**運算子或**!!**將右側的值強制轉換為布林值。基本上,這是一種將值轉換為布林值的奇特方法。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(!!''); //logs false console.log(!!0); //logs false console.log(!!NaN); //logs false console.log(!!' '); //logs true console.log(!!{}); //logs true console.log(!![]); //logs true console.log(!!1); //logs true console.log(!![].length); //logs false ``` ### 17. 如何計算一行中的多個表達式? [↑](#the-questions "返回問題")我們可以使用`,`或逗號運算子來計算一行中的多個表達式。它從左到右計算並傳回右側最後一項或最後一個操作數的值。 ``` let x = 5; x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10); function addFive(num) { return num + 5; } ``` 如果記錄`x`的值,它將是**27** 。首先,我們**增加**x 的值,它將是**6** ,然後我們呼叫函數`addFive(6)`並將 6 作為參數傳遞,並將結果分配給`x` , `x`的新值將是**11** 。之後,我們將`x`的當前值乘以**2**並將其分配給`x` , `x`的更新值將是**22** 。然後,我們將`x`的當前值減去 5 並將結果指派給`x` ,更新後的值將是**17** 。最後,我們將`x`的值增加 10 並將更新後的值指派給`x` ,現在`x`的值將是**27** 。 ### 18.什麼是**吊裝**? [^](#the-questions "返回問題")**提升**是一個術語,用於描述將*變數*和*函數*移動到其*(全域或函數)*作用域的頂部(即我們定義該變數或函數的位置)。 要理解**提升**,我必須解釋*執行上下文*。 **執行上下文**是目前正在執行的「程式碼環境」。**執行上下文**有兩個階段*:編譯*和*執行*。 **編譯**- 在此階段,它獲取所有*函數聲明*並將它們*提升*到作用域的頂部,以便我們稍後可以引用它們並獲取所有*變數聲明***(使用 var 關鍵字聲明)** ,並將它們*提升*並給它們一個默認值*未定義*的 . **執行**- 在此階段,它將值指派給先前*提升的*變數,並*執行*或*呼叫*函數**(物件中的方法)** 。 **注意:**只有使用*var*關鍵字宣告的**函數宣告**和變數才會*被提升*,而不是**函數表達式**或**箭頭函數**、 `let`和`const`關鍵字。 好吧,假設我們在下面的*全域範圍*內有一個範例程式碼。 ``` console.log(y); y = 1; console.log(y); console.log(greet("Mark")); function greet(name){ return 'Hello ' + name + '!'; } var y; ``` 此程式碼記錄`undefined` , `1` , `Hello Mark!`分別。 所以*編譯*階段看起來像這樣。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //implicit "undefined" assignment //waiting for "compilation" phase to finish //then start "execution" phase /* console.log(y); y = 1; console.log(y); console.log(greet("Mark")); */ ``` 出於範例目的,我對變數和*函數呼叫*的*賦值*進行了評論。 *編譯*階段完成後,它開始*執行*階段,呼叫方法並向變數賦值。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //start "execution" phase console.log(y); y = 1; console.log(y); console.log(greet("Mark")); ``` ### 19.什麼是**範圍**? [↑](#the-questions "返回問題") JavaScript 中的**作用域**是我們可以有效存取變數或函數的**區域**。 JavaScript 有三種類型的作用域。**全域作用域**、**函數作用域**和**區塊作用域(ES6)** 。 - **全域作用域**- 在全域命名空間中宣告的變數或函數位於全域作用域中,因此可以在程式碼中的任何位置存取。 ``` //global namespace var g = "global"; function globalFunc(){ function innerFunc(){ console.log(g); // can access "g" because "g" is a global variable } innerFunc(); } ``` - **函數作用域**- 函數內聲明的變數、函數和參數可以在該函數內部存取,但不能在函數外部存取。 ``` function myFavoriteFunc(a) { if (true) { var b = "Hello " + a; } return b; } myFavoriteFunc("World"); console.log(a); // Throws a ReferenceError "a" is not defined console.log(b); // does not continue here ``` - **區塊作用域**- 在區塊`{}`內宣告的變數**( `let` 、 `const` )**只能在區塊內存取。 ``` function testBlock(){ if(true){ let z = 5; } return z; } testBlock(); // Throws a ReferenceError "z" is not defined ``` **範圍**也是一組查找變數的規則。如果一個變數在**當前作用域中**不存在,它會在**外部作用域中查找**並蒐索該變數,如果不存在,它會再次**查找,**直到到達**全域作用域。**如果該變數存在,那麼我們可以使用它,如果不存在,我們可以使用它來拋出錯誤。它搜尋**最近的**變數,一旦找到它就停止**搜尋**或**尋找**。這稱為**作用域鏈**。 ``` /* Scope Chain Inside inner function perspective inner's scope -> outer's scope -> global's scope */ //Global Scope var variable1 = "Comrades"; var variable2 = "Sayonara"; function outer(){ //outer's scope var variable1 = "World"; function inner(){ //inner's scope var variable2 = "Hello"; console.log(variable2 + " " + variable1); } inner(); } outer(); // logs Hello World // because (variable2 = "Hello") and (variable1 = "World") are the nearest // variables inside inner's scope. ``` ![範圍](https://thepracticaldev.s3.amazonaws.com/i/l81b3nmdonimex0qsgyr.png) ### 20.什麼是**閉包**? [^](#the-questions "返回問題")這可能是所有這些問題中最難的問題,因為**閉包**是一個有爭議的話題。那我就從我的理解來解釋。 **閉包**只是函數在宣告時記住其當前作用域、其父函數作用域、其父函數的父函數作用域上的變數和參數的引用的能力,直到在**作用域鏈**的幫助下到達全域作用域。基本上它是聲明函數時建立的**作用域**。 例子是解釋閉包的好方法。 ``` //Global's Scope var globalVar = "abc"; function a(){ //testClosures's Scope console.log(globalVar); } a(); //logs "abc" /* Scope Chain Inside a function perspective a's scope -> global's scope */ ``` 在此範例中,當我們宣告`a`函數時**,全域**作用域是`a's`*閉包*的一部分。 ![a的閉包](https://thepracticaldev.s3.amazonaws.com/i/teatokuw4xvgtlzbzhn8.png) 變數`globalVar`在影像中沒有值的原因是該變數的值可以根據我們呼叫`a`**位置**和**時間**而改變。 但在上面的範例中, `globalVar`變數的值為**abc** 。 好吧,讓我們來看一個複雜的例子。 ``` var globalVar = "global"; var outerVar = "outer" function outerFunc(outerParam) { function innerFunc(innerParam) { console.log(globalVar, outerParam, innerParam); } return innerFunc; } const x = outerFunc(outerVar); outerVar = "outer-2"; globalVar = "guess" x("inner"); ``` ![複雜的](https://thepracticaldev.s3.amazonaws.com/i/e4hxm7zvz8eun2ppenwp.png) 這將列印“猜測外部內部”。對此的解釋是,當我們呼叫`outerFunc`函數並將`innerFunc`函數的回傳值指派給變數`x`時,即使我們將新值**outer-2**指派給`outerVar`變數, `outerParam`也會具有**outer**值,因為 重新分配發生在呼叫`outer`函數之後,當我們呼叫`outerFunc`函數時,它會在**作用域鏈**中尋找`outerVar`的值,而`outerVar`的值為**「outer」** 。現在,當我們呼叫引用了`innerFunc`的`x`變數時, `innerParam`的值為**inner,**因為這是我們在呼叫中傳遞的值,而`globalVar`變數的值為**猜測**,因為在呼叫`x`變數之前,我們為`globalVar`分配了一個新值,並且在呼叫`x`時**作用域鏈**中`globalVar`的值是**猜測**。 我們有一個例子來示範沒有正確理解閉包的問題。 ``` const arrFuncs = []; for(var i = 0; i < 5; i++){ arrFuncs.push(function (){ return i; }); } console.log(i); // i is 5 for (let i = 0; i < arrFuncs.length; i++) { console.log(arrFuncs[i]()); // all logs "5" } ``` 由於**Closures**的原因,此程式碼無法按我們的預期工作。 `var`關鍵字建立一個全域變數,當我們推送一個函數時 我們返回全域變數`i` 。因此,當我們在循環之後呼叫該陣列中的其中一個函數時,它會記錄`5` ,因為我們得到 `i`的目前值為`5` ,我們可以存取它,因為它是全域變數。因為**閉包**保留該變數的**引用,**而不是其建立時的**值**。我們可以使用**IIFES**或將`var`關鍵字變更為`let`來解決此問題,以實現區塊作用域。 ### 21. **JavaScript**中的**假**值是什麼? [↑](#the-questions "返回問題") ``` const falsyValues = ['', 0, null, undefined, NaN, false]; ``` **假**值是轉換為布林值時變成**false 的**值。 ### 22. 如何檢查一個值是否為**假值**? [↑](#the-questions "返回問題")使用**布林**函數或雙非運算符**[!!](#16-what-does-the-operator-do)** ### 23. `"use strict"`有什麼作用? [^](#the-questions "返回問題") `"use strict"`是**JavaScript**中的 ES5 功能,它使我們的程式碼在*函數*或*整個腳本*中處於**嚴格模式**。**嚴格模式**幫助我們避免程式碼早期出現**錯誤**並為其加入限制。 **嚴格模式**給我們的限制。 - 分配或存取未宣告的變數。 ``` function returnY(){ "use strict"; y = 123; return y; } ``` - 為唯讀或不可寫的全域變數賦值; ``` "use strict"; var NaN = NaN; var undefined = undefined; var Infinity = "and beyond"; ``` - 刪除不可刪除的屬性。 ``` "use strict"; const obj = {}; Object.defineProperty(obj, 'x', { value : '1' }); delete obj.x; ``` - 參數名稱重複。 ``` "use strict"; function someFunc(a, b, b, c){ } ``` - 使用**eval**函數建立變數。 ``` "use strict"; eval("var x = 1;"); console.log(x); //Throws a Reference Error x is not defined ``` - **該**值的預設值是`undefined` 。 ``` "use strict"; function showMeThis(){ return this; } showMeThis(); //returns undefined ``` **嚴格模式**的限制遠不止這些。 ### 24. JavaScript 中`this`的值是什麼? [↑](#the-questions "返回問題")基本上, `this`是指目前正在執行或呼叫函數的物件的值。我說**目前**是因為**它**的值會根據我們使用它的上下文和使用它的位置而改變。 ``` const carDetails = { name: "Ford Mustang", yearBought: 2005, getName(){ return this.name; }, isRegistered: true }; console.log(carDetails.getName()); // logs Ford Mustang ``` 這是我們通常所期望的,因為在**getName**方法中我們傳回`this.name` ,在此上下文中`this`指的是`carDetails`物件,該物件目前是正在執行的函數的「擁有者」物件。 好吧,讓我們加入一些程式碼讓它變得奇怪。在`console.log`語句下面加入這三行程式碼 ``` var name = "Ford Ranger"; var getCarName = carDetails.getName; console.log(getCarName()); // logs Ford Ranger ``` 第二個`console.log`語句印製了**「Ford Ranger」**一詞,這很奇怪,因為在我們的第一個`console.log`語句中它印了**「Ford Mustang」** 。原因是`getCarName`方法有一個不同的「擁有者」物件,即`window`物件。在全域作用域中使用`var`關鍵字聲明變數會在`window`物件中附加與變數同名的屬性。請記住,當未使用`"use strict"`時,全域範圍內的`this`指的是`window`物件。 ``` console.log(getCarName === window.getCarName); //logs true console.log(getCarName === this.getCarName); // logs true ``` 本例中的`this`和`window`指的是同一個物件。 解決此問題的一種方法是使用函數中的`<a href="#27-what-is-the-use-functionprototypeapply-method">apply</a>`和`<a href="#28-what-is-the-use-functionprototypecall-method">call</a>`方法。 ``` console.log(getCarName.apply(carDetails)); //logs Ford Mustang console.log(getCarName.call(carDetails)); //logs Ford Mustang ``` `apply`和`call`方法期望第一個參數是一個物件,該物件將是該函數內`this`的值。 **IIFE** (即**立即呼叫函數表達式)** 、在全域作用域中宣告的函數、物件內部方法中的**匿名函數**和內部函數都有一個指向**window**物件的預設**值**。 ``` (function (){ console.log(this); })(); //logs the "window" object function iHateThis(){ console.log(this); } iHateThis(); //logs the "window" object const myFavoriteObj = { guessThis(){ function getThis(){ console.log(this); } getThis(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; myFavoriteObj.guessThis(); //logs the "window" object myFavoriteObj.thisIsAnnoying(function (){ console.log(this); //logs the "window" object }); ``` 如果我們想要取得`myFavoriteObj`物件中的`name`屬性**(Marko Polo)**的值,有兩種方法可以解決這個問題。 首先,我們將`this`的值保存在變數中。 ``` const myFavoriteObj = { guessThis(){ const self = this; //saves the this value to the "self" variable function getName(){ console.log(self.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` 在此圖像中,我們保存`this`的值,該值將是`myFavoriteObj`物件。所以我們可以在`getName`內部函數中存取它。 其次,我們使用**ES6[箭頭函數](#43-what-are-arrow-functions)**。 ``` const myFavoriteObj = { guessThis(){ const getName = () => { //copies the value of "this" outside of this arrow function console.log(this.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` [箭頭函數](#43-what-are-arrow-functions)沒有自己的`this` 。它複製封閉詞法範圍的`this`值,或複製`getName`內部函數外部的`this`值(即`myFavoriteObj`物件)。我們也可以根據[函數的呼叫方式](#66-how-many-ways-can-a-function-be-invoked)來決定`this`的值。 ### 25. 物件的`prototype`是什麼? [↑](#the-questions "返回問題")最簡單的`prototype`是一個物件的*藍圖*。如果目前物件中確實存在它,則將其用作**屬性**和**方法**的後備。這是在物件之間共享屬性和功能的方式。這是 JavaScript**原型繼承**的核心概念。 ``` const o = {}; console.log(o.toString()); // logs [object Object] ``` 即使`o.toString`方法不存在於`o`物件中,它也不會拋出錯誤,而是傳回字串`[object Object]` 。當物件中不存在屬性時,它會尋找其**原型**,如果仍然不存在,則會尋找**原型的原型**,依此類推,直到在**原型鏈**中找到具有相同屬性的屬性。**原型鏈**的末尾在**Object.prototype**之後為`null` 。 ``` console.log(o.toString === Object.prototype.toString); // logs true // which means we we're looking up the Prototype Chain and it reached // the Object.prototype and used the "toString" method. ``` ### 26. 什麼是**IIFE** ,它有什麼用? [^](#the-questions "返回問題") **IIFE**或**立即呼叫函數表達式**是在建立或宣告後將被呼叫或執行的函數。建立**IIFE**的語法是,我們將`function (){}`包裝在括號`()`或**分組運算**子內,以將函數視為表達式,然後用另一個括號`()`呼叫它。所以**IIFE**看起來像這樣`(function(){})()` 。 ``` (function () { }()); (function () { })(); (function named(params) { })(); (() => { })(); (function (global) { })(window); const utility = (function () { return { //utilities }; })(); ``` 這些範例都是有效的**IIFE** 。倒數第二個範例顯示我們可以將參數傳遞給**IIFE**函數。最後一個範例表明我們可以將**IIFE**的結果保存到變數中,以便稍後引用它。 **IIFE**的最佳用途是進行初始化設定功能,並避免與全域範圍內的其他變數**發生命名衝突**或污染全域名稱空間。讓我們舉個例子。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> ``` 假設我們有一個指向庫`somelibrary.js`的連結,該庫公開了我們可以在程式碼中使用的一些全域函數,但該庫有兩個我們不使用`createGraph`和`drawGraph`方法,因為這些方法中有錯誤。我們想要實作我們自己的`createGraph`和`drawGraph`方法。 - 解決這個問題的一種方法是改變腳本的結構。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } </script> ``` 當我們使用這個解決方案時,我們將覆蓋庫為我們提供的這兩種方法。 - 解決這個問題的另一種方法是更改我們自己的輔助函數的名稱。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function myCreateGraph() { // createGraph logic here } function myDrawGraph() { // drawGraph logic here } </script> ``` 當我們使用此解決方案時,我們還將這些函數呼叫更改為新函數名稱。 - 另一種方法是使用**IIFE** 。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> const graphUtility = (function () { function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } return { createGraph, drawGraph } })(); </script> ``` 在此解決方案中,我們建立一個實用程式變數,它是**IIFE**的結果,它傳回一個包含`createGraph`和`drawGraph`兩個方法的物件。 **IIFE**解決的另一個問題就是這個例子。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { li[i].addEventListener('click', function (e) { console.log(i); }) } ``` 假設我們有一個`ul`元素,其類別為**list-group** ,並且它有 5 個`li`子元素。當我們**點擊**單一`li`元素時,我們希望`console.log` `i`的值。 但我們想要的程式碼中的行為不起作用。相反,它會在對`li`元素的任何**點擊**中記錄`5` 。我們遇到的問題是由於**閉包的**工作方式造成的。**閉包**只是函數記住其當前作用域、其父函數作用域和全域作用域中的變數引用的能力。當我們在全域範圍內使用`var`關鍵字聲明變數時,顯然我們正在建立一個全域變數`i` 。因此,當我們單擊`li`元素時,它會記錄**5** ,因為這是我們稍後在回調函數中引用它時的`i`值。 - 解決這個問題的一種方法是**IIFE** 。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { (function (currentIndex) { li[currentIndex].addEventListener('click', function (e) { console.log(currentIndex); }) })(i); } ``` 這個解決方案之所以有效,是因為**IIFE**為每次迭代建立一個新範圍,並且我們捕獲`i`的值並將其傳遞到`currentIndex`參數中,因此當我們呼叫**IIFE**時,每次迭代的`currentIndex`值都是不同的。 ### 27. `Function.prototype.apply`方法有什麼用? [^](#the-questions "返回問題") `apply`呼叫一個函數,在呼叫時指定`this`或該函數的「所有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.apply(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#28-what-is-the-use-functionprototypecall-method">Function.prototype.call</a>`唯一的差異是我們傳遞參數的方式。在`apply`中,我們將參數作為陣列傳遞。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.apply(person, ['Hello']); // returns "Hello Marko Polo!" ``` ### 28. `Function.prototype.call`方法有什麼用? [^](#the-questions "返回問題")此`call`呼叫一個函數,指定呼叫時該函數的`this`或「擁有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.call(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#27-what-is-the-use-functionprototypeapply-method">Function.prototype.apply</a>`唯一的差異是我們傳遞參數的方式。在`call`中,我們直接傳遞參數,對於每個參數`,`用逗號分隔它們。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.call(person, 'Hello'); // returns "Hello Marko Polo!" ``` ### 29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別? [↑](#the-questions "返回問題") `apply`和`call`之間的唯一區別是我們如何在被呼叫的函數中傳遞**參數**。在`apply`中,我們將參數作為**陣列**傳遞,而在`call`中,我們直接在參數列表中傳遞參數。 ``` const obj1 = { result:0 }; const obj2 = { result:0 }; function reduceAdd(){ let result = 0; for(let i = 0, len = arguments.length; i < len; i++){ result += arguments[i]; } this.result = result; } reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // returns 15 reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15 ``` ### 30. `Function.prototype.bind`的用法是什麼? [↑](#the-questions "返回問題") `bind`方法傳回一個新*綁定的*函數 到特定的`this`值或“所有者”物件,因此我們可以稍後在程式碼中使用它。 `call` 、 `apply`方法立即呼叫函數,而不是像`bind`方法那樣傳回一個新函數。 ``` import React from 'react'; class MyComponent extends React.Component { constructor(props){ super(props); this.state = { value : "" } this.handleChange = this.handleChange.bind(this); // Binds the "handleChange" method to the "MyComponent" component } handleChange(e){ //do something amazing here } render(){ return ( <> <input type={this.props.type} value={this.state.value} onChange={this.handleChange} /> </> ) } } ``` ### 31.什麼是**函數式程式設計**? **JavaScript**的哪些特性使其成為**函數式語言**的候選者? [^](#the-questions "返回問題")**函數式程式設計**是一種**聲明式**程式設計範式或模式,它介紹如何**使用表達式來**計算值而不改變傳遞給它的參數的函數來建立應用程式。 JavaScript**陣列**具有**map** 、 **filter** 、 **reduce**方法,這些方法是函數式程式設計世界中最著名的函數,因為它們非常有用,而且它們不會改變或改變陣列,這使得這些函數變得**純粹**,並且JavaScript 支援**閉包**和**高階函數**,它們是**函數式程式語言**的一個特徵。 - **map**方法建立一個新陣列,其中包含對陣列中每個元素呼叫提供的回調函數的結果。 ``` const words = ["Functional", "Procedural", "Object-Oriented"]; const wordsLength = words.map(word => word.length); ``` - **filter**方法會建立一個新陣列,其中包含透過回調函數中測試的所有元素。 ``` const data = [ { name: 'Mark', isRegistered: true }, { name: 'Mary', isRegistered: false }, { name: 'Mae', isRegistered: true } ]; const registeredUsers = data.filter(user => user.isRegistered); ``` - **reduce**方法對累加器和陣列中的每個元素(從左到右)套用函數,將其減少為單一值。 ``` const strs = ["I", " ", "am", " ", "Iron", " ", "Man"]; const result = strs.reduce((acc, currentStr) => acc + currentStr, ""); ``` ### 32.什麼是**高階函數**? [^](#the-questions "返回問題")**高階函數**是可以傳回函數或接收具有函數值的一個或多個參數的函數。 ``` function higherOrderFunction(param,callback){ return callback(param); } ``` ### 33.為什麼函數被稱為**First-class Objects** ? [^](#the-questions "返回問題") JavaScript 中的 \_\_Functions\_\_ 是**一流物件**,因為它們被視為該語言中的任何其他值。它們可以分配給**變數**,可以是稱為**方法的物件的屬性**,可以是**陣列中的專案**,可以**作為參數傳遞給函數**,也可以**作為函數的值返回**。函數與**JavaScript**中任何其他值之間的唯一區別是**函數**可以被呼叫。 ### 34. 手動實作`Array.prototype.map`方法。 [↑](#the-questions "返回問題") ``` function map(arr, mapCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); // push the result of the mapCallback in the 'result' array } return result; // return the result array } } ``` 正如`Array.prototype.map`方法的MDN描述。 **map() 方法建立一個新陣列,其中包含對呼叫陣列中的每個元素呼叫所提供函數的結果。** ### 35. 手動實作`Array.prototype.filter`方法。 [↑](#the-questions "返回問題") ``` function filter(arr, filterCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { // check if the return value of the filterCallback is true or "truthy" if (filterCallback(arr[i], i, arr)) { // push the current item in the 'result' array if the condition is true result.push(arr[i]); } } return result; // return the result array } } ``` 正如`Array.prototype.filter`方法的 MDN 描述。 **filter() 方法建立一個新陣列,其中包含透過所提供函數實現的測試的所有元素。** ### 36. 手動實作`Array.prototype.reduce`方法。 [↑](#the-questions "返回問題") ``js 函數reduce(arr,reduceCallback,initialValue){ // 首先,我們檢查傳遞的參數是否正確。 if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') { ``` return []; ``` } 別的 { ``` // If no initialValue has been passed to the function we're gonna use the ``` ``` let hasInitialValue = initialValue !== undefined; ``` ``` let value = hasInitialValue ? initialValue : arr[0]; ``` ``` // first array item as the initialValue ``` ``` // Then we're gonna start looping at index 1 if there is no ``` ``` // initialValue has been passed to the function else we start at 0 if ``` ``` // there is an initialValue. ``` ``` for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) { ``` ``` // Then for every iteration we assign the result of the ``` ``` // reduceCallback to the variable value. ``` ``` value = reduceCallback(value, arr[i], i, arr); ``` ``` } ``` ``` return value; ``` } } ``` As the MDN description of the <code>Array.prototype.reduce</code> method. __The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.__ ###37. What is the __arguments__ object? [&uarr;](#the-questions "Back To Questions") The __arguments__ object is a collection of parameter values pass in a function. It's an __Array-like__ object because it has a __length__ property and we can access individual values using array indexing notation <code>arguments[1]</code> but it does not have the built-in methods in an array <code>forEach</code>,<code>reduce</code>,<code>filter</code> and <code>map</code>. It helps us know the number of arguments pass in a function. We can convert the <code>arguments</code> object into an array using the <code>Array.prototype.slice</code>. ``` 函數一(){ 返回 Array.prototype.slice.call(參數); } ``` Note: __the <code>arguments</code> object does not work on ES6 arrow functions.__ ``` 函數一(){ 返回參數; } 常數二 = 函數 () { 返回參數; } 常量三 = 函數三() { 返回參數; } const 四 = () =&gt; 參數; 四(); // 拋出錯誤 - 參數未定義 ``` When we invoke the function <code>four</code> it throws a <code>ReferenceError: arguments is not defined</code> error. We can solve this problem if your enviroment supports the __rest syntax__. ``` const 四 = (...args) =&gt; args; ``` This puts all parameter values in an array automatically. ###38. How to create an object without a __prototype__? [&uarr;](#the-questions "Back To Questions") We can create an object without a _prototype_ using the <code>Object.create</code> method. ``` 常數 o1 = {}; console.log(o1.toString()); // Logs \[object Object\] 取得此方法到Object.prototype const o2 = Object.create(null); // 第一個參數是物件「o2」的原型,在此 // case 將為 null 指定我們不需要任何原型 console.log(o2.toString()); // 拋出錯誤 o2.toString 不是函數 ``` ###39. Why does <code>b</code> in this code become a global variable when you call this function? [&uarr;](#the-questions "Back To Questions") ``` 函數 myFunc() { 令a = b = 0; } myFunc(); ``` The reason for this is that __assignment operator__ or __=__ has right-to-left __associativity__ or __evaluation__. What this means is that when multiple assignment operators appear in a single expression they evaluated from right to left. So our code becomes likes this. ``` 函數 myFunc() { 令 a = (b = 0); } myFunc(); ``` First, the expression <code>b = 0</code> evaluated and in this example <code>b</code> is not declared. So, The JS Engine makes a global variable <code>b</code> outside this function after that the return value of the expression <code>b = 0</code> would be 0 and it's assigned to the new local variable <code>a</code> with a <code>let</code> keyword. We can solve this problem by declaring the variables first before assigning them with value. ``` 函數 myFunc() { 令 a,b; a = b = 0; } myFunc(); ``` ###40. <div id="ecmascript">What is __ECMAScript__</div>? [&uarr;](#the-questions "Back To Questions") __ECMAScript__ is a standard for making scripting languages which means that __JavaScript__ follows the specification changes in __ECMAScript__ standard because it is the __blueprint__ of __JavaScript__. ###41. What are the new features in __ES6__ or __ECMAScript 2015__? [&uarr;](#the-questions "Back To Questions") * [Arrow Functions](#43-what-are-arrow-functions) * [Classes](#44-what-are-classes) * [Template Strings](#45-what-are-template-literals) * __Enhanced Object literals__ * [Object Destructuring](#46-what-is-object-destructuring) * [Promises](#50-what-are-promises) * __Generators__ * [Modules](#47-what-are-es6-modules) * Symbol * __Proxies__ * [Sets](#48-what-is-the-set-object-and-how-does-it-work) * [Default Function parameters](#53-what-are-default-parameters) * [Rest and Spread](#52-whats-the-difference-between-spread-operator-and-rest-operator) * [Block Scoping with <code>let</code> and <code>const</code>](#42-whats-the-difference-between-var-let-and-const-keywords) ###42. What's the difference between <code>var</code>, <code>let</code> and <code>const</code> keywords? [&uarr;](#the-questions "Back To Questions") Variables declared with <code>var</code> keyword are _function scoped_. What this means that variables can be accessed across that function even if we declare that variable inside a block. ``` 函數給MeX(showX) { 如果(顯示X){ ``` var x = 5; ``` } 返回x; } console.log(giveMeX(false)); console.log(giveMeX(true)); ``` The first <code>console.log</code> statement logs <code>undefined</code> and the second <code>5</code>. We can access the <code>x</code> variable due to the reason that it gets _hoisted_ at the top of the function scope. So our function code is intepreted like this. ``` 函數給MeX(showX) { 變數 x; // 有一個預設值未定義 如果(顯示X){ ``` x = 5; ``` } 返回x; } ``` If you are wondering why it logs <code>undefined</code> in the first <code>console.log</code> statement remember variables declared without an initial value has a default value of <code>undefined</code>. Variables declared with <code>let</code> and <code>const</code> keyword are _block scoped_. What this means that variable can only be accessed on that block <code>{}</code> on where we declare it. ``` 函數給MeX(showX) { 如果(顯示X){ ``` let x = 5; ``` } 返回x; } 函數給MeY(顯示Y){ 如果(顯示Y){ ``` let y = 5; ``` } 返回y; } ``` If we call this functions with an argument of <code>false</code> it throws a <code>Reference Error</code> because we can't access the <code>x</code> and <code>y</code> variables outside that block and those variables are not _hoisted_. There is also a difference between <code>let</code> and <code>const</code> we can assign new values using <code>let</code> but we can't in <code>const</code> but <code>const</code> are mutable meaning. What this means is if the value that we assign to a <code>const</code> is an object we can change the values of those properties but can't reassign a new value to that variable. ###43. What are __Arrow functions__? [&uarr;](#the-questions "Back To Questions") __Arrow Functions__ are a new way of making functions in JavaScript. __Arrow Functions__ takes a little time in making functions and has a cleaner syntax than a __function expression__ because we omit the <code>function</code> keyword in making them. ``` //ES5版本 var getCurrentDate = 函數 (){ 返回新日期(); } //ES6版本 const getCurrentDate = () =&gt; new Date(); ``` In this example, in the ES5 Version have <code>function(){}</code> declaration and <code>return</code> keyword needed to make a function and return a value respectively. In the __Arrow Function__ version we only need the <code>()</code> parentheses and we don't need a <code>return</code> statement because __Arrow Functions__ have a implicit return if we have only one expression or value to return. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = (name) =&gt; `Hello ${name}` ; constgreet2 = 名稱 =&gt; `Hello ${name}` ; ``` We can also parameters in __Arrow functions__ the same as the __function expressions__ and __function declarations__. If we have one parameter in an __Arrow Function__ we can omit the parentheses it is also valid. ``` const getArgs = () =&gt; 參數 const getArgs2 = (...休息) =&gt; 休息 ``` __Arrow functions__ don't have access to the <code>arguments</code> object. So calling the first <code>getArgs</code> func will throw an Error. Instead we can use the __rest parameters__ to get all the arguments passed in an arrow function. ``` 常量資料 = { 結果:0, 數字:\[1,2,3,4,5\], 計算結果() { ``` // "this" here refers to the "data" object ``` ``` const addAll = () => { ``` ``` // arrow functions "copies" the "this" value of ``` ``` // the lexical enclosing function ``` ``` return this.nums.reduce((total, cur) => total + cur, 0) ``` ``` }; ``` ``` this.result = addAll(); ``` } }; ``` __Arrow functions__ don't have their own <code>this</code> value. It captures or gets the <code>this</code> value of lexically enclosing function or in this example, the <code>addAll</code> function copies the <code>this</code> value of the <code>computeResult</code> method and if we declare an arrow function in the global scope the value of <code>this</code> would be the <code>window</code> object. ###44. What are __Classes__? [&uarr;](#the-questions "Back To Questions") __Classes__ is the new way of writing _constructor functions_ in __JavaScript__. It is _syntactic sugar_ for using _constructor functions_, it still uses __prototypes__ and __Prototype-Based Inheritance__ under the hood. ``` //ES5版本 函數人(名字,姓氏,年齡,地址){ ``` this.firstName = firstName; ``` ``` this.lastName = lastName; ``` ``` this.age = age; ``` ``` this.address = address; ``` } Person.self = 函數(){ ``` return this; ``` } Person.prototype.toString = function(){ ``` return "[object Person]"; ``` } Person.prototype.getFullName = function (){ ``` return this.firstName + " " + this.lastName; ``` } //ES6版本 類人{ ``` constructor(firstName, lastName, age, address){ ``` ``` this.lastName = lastName; ``` ``` this.firstName = firstName; ``` ``` this.age = age; ``` ``` this.address = address; ``` ``` } ``` ``` static self() { ``` ``` return this; ``` ``` } ``` ``` toString(){ ``` ``` return "[object Person]"; ``` ``` } ``` ``` getFullName(){ ``` ``` return `${this.firstName} ${this.lastName}`; ``` ``` } ``` } ``` __Overriding Methods__ and __Inheriting from another class__. ``` //ES5版本 Employee.prototype = Object.create(Person.prototype); 函數 Employee(名字, 姓氏, 年齡, 地址, 職位名稱, 開始年份) { Person.call(this, 名字, 姓氏, 年齡, 地址); this.jobTitle = jobTitle; this.yearStarted = YearStarted; } Employee.prototype.describe = function () { return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}` ; } Employee.prototype.toString = function () { 返回“\[物件員工\]”; } //ES6版本 class Employee extends Person { //繼承自「Person」類 建構函數(名字,姓氏,年齡,地址,工作標題,開始年份){ ``` super(firstName, lastName, age, address); ``` ``` this.jobTitle = jobTitle; ``` ``` this.yearStarted = yearStarted; ``` } 描述() { ``` return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`; ``` } toString() { // 重寫「Person」的「toString」方法 ``` return "[object Employee]"; ``` } } ``` So how do we know that it uses _prototypes_ under the hood? ``` 類別東西{ } 函數 AnotherSomething(){ } const as = new AnotherSomething(); const s = new Something(); console.log(typeof Something); // 記錄“函數” console.log(AnotherSomething 類型); // 記錄“函數” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString === Object.prototype.toString); console.log(s.toString === Object.prototype.toString); // 兩個日誌都回傳 true 表示我們仍在使用 // 底層原型,因為 Object.prototype 是 // 原型鏈的最後一部分和“Something” // 和「AnotherSomething」都繼承自Object.prototype ``` ###45. What are __Template Literals__? [&uarr;](#the-questions "Back To Questions") __Template Literals__ are a new way of making __strings__ in JavaScript. We can make __Template Literal__ by using the backtick or back-quote symbol. ``` //ES5版本 vargreet = '嗨,我是馬克'; //ES6版本 讓問候 = `Hi I'm Mark` ; ``` In the ES5 version, we need to escape the <code>'</code> using the <code>\\</code> to _escape_ the normal functionality of that symbol which in this case is to finish that string value. In Template Literals, we don't need to do that. ``` //ES5版本 var 最後一個字 = '\\n' - ' 在' - '我\\n' - '鋼鐵人\\n'; //ES6版本 讓最後一個單字=` ``` I ``` ``` Am ``` 鋼鐵人 `; ``` In the ES5 version, we need to add this <code>\n</code> to have a new line in our string. In Template Literals, we don't need to do that. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = 名稱 =&gt; { 返回`Hello ${name} !` ; } ``` In the ES5 version, If we need to add an expression or value in a string we need to use the <code>+</code> or string concatenation operator. In Template Literals, we can embed an expression using <code>${expr}</code> which makes it cleaner than the ES5 version. ###46. What is __Object Destructuring__? [&uarr;](#the-questions "Back To Questions") __Object Destructuring__ is a new and cleaner way of __getting__ or __extracting__ values from an object or an array. Suppose we have an object that looks like this. ``` 常量僱員 = { 名字:“馬可”, 姓氏:“波羅”, 職位:“軟體開發人員”, 聘用年份:2017 }; ``` The old way of getting properties from an object is we make a variable t

如何將 Google Gemini 與 Node.js 結合使用

介紹 -- 過去一年,生成式人工智慧一直是科技領域的熱門話題。每個人都在使用它來建造很酷的專案。谷歌有自己的生成人工智慧,稱為 Gemini。 最近,Google 為 Gemini 開發者推出了 API。它附帶了幾個庫和框架,開發人員可以使用它們將其合併到他們的應用程式中。 在本文中,我們將建立一個簡單的 Node.js 應用程式並將 Google Gemini 整合到其中。我們將使用[**Google Gemini SDK**](https://www.npmjs.com/package/@google/generative-ai) 。 那麼,事不宜遲,讓我們開始吧! 什麼是雙子座? ------- ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lky153eb6l4thz5a246n.png) Google Gemini 是由 Google AI 開發的強大且多方面的 AI 模型。 Gemini 不僅處理文字;也處理文字。它可以理解和操作各種格式,如程式碼、音訊、圖像和視訊。這為您的 Node.js 專案帶來了令人興奮的可能性。 專案設定: ----- ### **1.建立Node.js專案:** 要啟動我們的專案,我們需要設定 Node.js 環境。那麼,讓我們建立一個節點專案。在終端機中執行以下命令。 ``` npm init ``` 這將初始化一個新的 Node.js 專案。 ### 2.安裝依賴項: 現在,我們將安裝專案所需的依賴項。 ``` npm install express body-parser @google/generative-ai dotenv ``` 這將安裝以下軟體包: - express:流行的 Node.js Web 框架 - body-parser:用來解析請求體的中介軟體 - @google/generative-ai:用於存取 Gemini 模型的套件 - dotenv:從 .env 檔案載入環境變數 ### 3.**設定環境變數:** 接下來,我們將建立一個`.env`資料夾來安全地儲存 API 憑證等敏感資訊。 ``` //.env API_KEY=YOUR_API_KEY PORT=3000 ``` ### 4.**取得API金鑰:** 在使用 Gemini 之前,我們需要從 Google Developers Console 設定 API 憑證。為此,我們需要註冊 Google 帳戶並建立 API 金鑰。 登入後,前往<https://makersuite.google.com/app/apikey> 。我們會得到這樣的結果: ![Google AI Studio 控制台的圖片](https://cdn.hashnode.com/res/hashnode/image/upload/v1707836987343/d339372d-195e-47f7-80a0-dc33fef00428.png) 然後我們將點擊“建立 API 金鑰”按鈕。這將產生一個唯一的 API 金鑰,我們將使用它來驗證對 Google Generative AI API 的請求。 > 要測試您的 API,您可以執行以下 Curl 命令: > > ```javascript > 捲曲\\ > -H '內容類型:application/json' \\ > -d '{"contents":\[{"parts":\[{"text":"寫一個關於魔法背包的故事"}\]}\]}' \\ > -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR\_API\_KEY > ```` > > 將`YOUR_API_KEY`替換為我們先前獲得的實際 API 金鑰。 取得 API 金鑰後,我們將使用 API 金鑰更新`.env`檔。 ### 5. 建立 Express 伺服器: 現在,我們將在根目錄中建立一個`index.js`檔案並設定一個基本的express 伺服器。請看下面的程式碼: ``` const express = require("express"); const dotenv = require("dotenv"); dotenv.config(); const app = express(); const port = process.env.PORT; app.get("/", (req, res) => { res.send("Hello World"); }); app.listen(port, () => { console.log(`Server running on port ${port}`); }); ``` 在這裡,我們使用“dotenv”套件從`.env`檔案存取連接埠號碼。 在專案的頂部,我們使用`dotenv.config()`載入環境變數,使其可以在整個檔案中存取。 ### 6. 執行專案: 在此步驟中,我們將向`package.json`檔案新增一個啟動腳本,以輕鬆執行我們的專案。 因此,將以下腳本新增至 package.json 檔案中。 ``` "scripts": { "start": "node index.js" } ``` package.json 檔案應如下所示: ![package.json 文件](https://cdn.hashnode.com/res/hashnode/image/upload/v1707982485800/c23cbb23-68c6-4f6b-942d-dad0dfe9c3fb.png) 要檢查一切是否正常,讓我們使用以下命令執行該專案: ``` npm run start ``` 這將啟動 Express 伺服器。現在如果我們造訪這個 URL <http://localhost:3000/>我們會得到: ![http://localhost:3000/ 的圖片](https://cdn.hashnode.com/res/hashnode/image/upload/v1707838639217/c4d08730-7534-4ad5-a0fd-5962d3eb7cc6.png) 驚人的!專案設定已完成並且執行完美。接下來,我們將在下一節中將 Gemini 加入我們的專案中 新增Google雙子座: ------------ ### 1. 設定路由和中介軟體: 要將 Gemini 新增至我們的專案中,我們將建立一個`/generate`路由,以便與 Gemini AI 進行通訊。 為此,將以下程式碼新增至`index.js`檔案。 ``` const bodyParser = require("body-parser"); const { generateResponse } = require("./controllers/index.js"); //middleware to parse the body content to JSON app.use(bodyParser.json()); app.post("/generate", generateResponse); ``` 在這裡,我們使用`body-parser`中間件將內容解析為 JSON 格式。 ### 2.設定Google Generative AI: 現在,我們將建立一個控制器資料夾,並在其中建立一個`index.js`檔案。在這裡,我們將建立一個新的控制器函數來處理上面程式碼中聲明的生成路由。 ``` const { GoogleGenerativeAI } = require("@google/generative-ai"); const dotenv = require("dotenv"); dotenv.config(); // GoogleGenerativeAI required config const configuration = new GoogleGenerativeAI(process.env.API_KEY); // Model initialization const modelId = "gemini-pro"; const model = configuration.getGenerativeModel({ model: modelId }); ``` 在這裡,我們透過傳遞環境變數中的 API 金鑰來為 Google Generative AI API 建立一個配置物件。 然後,我們透過向配置物件的`getGenerativeModel`方法提供模型 ID(“gemini-pro”)來初始化模型。 > #### **型號配置:** > > 我們也可以依照自己的方便配置模型參數 > > 這些參數值控制模型如何產生回應。 > > 例子: > > ```javascript > 常量產生配置 = { > 停止序列:\[“紅色”\], > 最大輸出令牌:200, > 溫度:0.9, > 頂部P:0.1, > 頂級K:16, > }; > > const model = configuration.getGenerativeModel({ model: modelId, GenerationConfig }); > ```` > #### **安全設定:** > > 我們可以使用安全設定來防止有害的反應。預設情況下,安全性設定配置為阻止在各個維度上具有中等到高可能性不安全的內容。 > > 這是一個例子: > > ```javascript > const { HarmBlockThreshold, HarmCategory } = require("@google/generative-ai"); > > 常量安全設定 = \[ > { > ``` > category: HarmCategory.HARM_CATEGORY_HARASSMENT, > > ``` > ``` > threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH, > > ``` > }, > { > ``` > category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, > > ``` > ``` > threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, > > ``` > }, > \]; > > const model = genAI.getGenerativeModel({ model: "MODEL\_NAME", safetySettings }); > ```` > > 透過這些安全設置,我們可以透過最大限度地減少有害內容生成的可能性來增強安全性。 ### 3. 管理對話歷史記錄: 為了追蹤對話歷史記錄,我們建立了一個陣列`history`並將其從控制器檔案中匯出: ``` export const history = []; ``` ### 4.**實現控制器功能:** 現在,我們將編寫一個控制器函數`generateResponse`來處理產生路由(/generate)並產生對使用者請求的回應。 ``` /** * Generates a response based on the given prompt. * @param {Object} req - The request object. * @param {Object} res - The response object. * @returns {Promise} - A promise that resolves when the response is sent. */ export const generateResponse = async (req, res) => { try { const { prompt } = req.body; const result = await model.generateContent(prompt); const response = await result.response; const text = response.text(); console.log(text); history.push(text); console.log(history); res.send({ response: text }); } catch (err) { console.error(err); res.status(500).json({ message: "Internal server error" }); } }; ``` 在這裡,我們從請求正文中獲取提示,並使用`model.generateContent`方法根據提示產生回應。 為了追蹤響應,我們將響應推送到歷史陣列。 ### 5. 查看回覆紀錄: 現在,我們將建立一條路線來檢查我們的回應歷史記錄。該端點傳回`history`陣列。 將簡單程式碼加入`./index.js`資料夾中。 ``` app.get("/generate", (req, res) => { res.send(history); }); ``` 我們就完成了! ### 6.執行專案: 現在,我們必須檢查我們的應用程式是否正常運作! 讓我們使用以下命令來執行我們的專案: ``` npm run start ``` ![端子輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855196139/694e7c44-39c4-4ee7-8080-51e0a429c8ec.png) 沒有錯誤!感謝上帝! :) 它運作正常。 ### 7. 檢查功能 接下來,我們將使用 Postman 發出 Post 請求來驗證我們的控制器功能。 我們將使用以下 JSON 負載向<http://localhost:3000/generate>發送 POST 請求: ``` { "prompt": "Write 3 Javascript Tips for Beginners" } ``` ![郵差控制台輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855502196/bb379294-e966-4fa1-b08d-057f852b8c1a.png) 我們得到了回應: ``` { "response": "1. **Use console.log() for Debugging:**\n - console.log() is a useful tool for debugging your JavaScript code. It allows you to inspect the values of variables and expressions, and to see how your code is executing. This can be especially helpful when you encounter errors or unexpected behavior in your program.\n\n2. **Learn the Basics of Data Types:**\n - JavaScript has several built-in data types, including strings, numbers, booleans, and objects. Understanding the properties and behaviors of each data type is crucial for writing effective code. For instance, strings can be manipulated using string methods, while numbers can be used in mathematical operations.\n\n3. **Use Strict Mode:**\n - Strict mode is a way to opt-in to a restricted and secure subset of JavaScript. It helps you to write more secure and reliable code, as it throws errors for common mistakes that would otherwise go unnoticed in regular JavaScript mode. To enable strict mode, simply add \"use strict;\" at the beginning of your JavaScript file or module." } ``` ![郵差控制台輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855825387/a186b78f-e6d9-4197-8b00-ce55766a2e16.png) 偉大的!我們的 Gemini AI 整合正在按預期工作! 此外,我們可以造訪[http://localhost:3000/generate 的](http://localhost:3000/generate)歷史記錄端點來查看對話歷史記錄。 這樣,我們就將 Gemini AI 整合到了 Node.js 應用程式中。在接下來的文章中,我們將探索 Gemini AI 的更多用例。 到那時,請繼續關注! 結論 -- 如果您發現這篇部落格文章有幫助,請考慮與可能受益的其他人分享。您也可以關注我,以了解更多有關 Javascript、React 和其他 Web 開發主題的內容。 要贊助我的工作,請存取: [Arindam 的贊助頁面](https://arindam1729.hashnode.dev/sponsor)並探索各種贊助選項。 在[Twitter](https://twitter.com/intent/follow?screen_name=Arindam_1729) 、 [LinkedIn](https://www.linkedin.com/in/arindam2004/) 、 [Youtube](https://www.youtube.com/channel/@Arindam_1729)和[GitHub](https://github.com/Arindam200)上與我聯絡。 感謝您的閱讀:) ![謝謝](https://cdn.hashnode.com/res/hashnode/image/upload/v1707859424336/0c24ca09-aebb-4e5a-9a59-065ed5a8a9c8.png) --- 原文出處:https://dev.to/arindam_1729/how-to-use-google-gemini-with-nodejs-2d39

6 個月內成為後端開發人員的技能(路線圖)

讓我給你一個簡單的🚦路線圖,讓你知道你現在在哪裡以及下一步應該去哪裡。 ![Shahan 在 6 個月內成為後端開發人員的技能](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uek3ahjesssiakqs0xe.jpeg) 🔑關鍵概念 ----- 每個網站都有兩個部分。一個**前端**,一個**後端**。 前端是您在瀏覽器中看到它並與之互動的部分。**所有視覺方面**。 後端是為前端提供動力的部分。它*在幕後*,**主要是儲存資料和資料庫**並提供給前端。 🌐工作 --- 因此,網路開發工作分為`three categories` 。 - 👨‍💻前端開發 - 🛟後端開發 - 🚢以及全端開發(涉及前端和後端開發) 👷‍♂️後端開發人員到底是做什麼的? ------------------ 後端開發人員負責建立驅動用戶所使用的應用程式*功能*的**基礎系統**。 這包括設計架構、實施和維護這些關鍵系統等各種任務。 他們的職責通常涉及與: - 💾 資料庫,例如MySQL, - ✂️ 框架,例如 Laravel 或 Ruby on Rails,以及 - 🧨 API(應用程式介面)。 他們的專業知識確保後端基礎設施無縫執行,從而實現使用者介面與底層資料和流程之間的順暢互動。 如果您不知道的話,這是後端開發人員的`core`職責: |任務|描述| |---|---| |了解績效需求與目標 |了解網站的效能要求和目標 | | API的開發與管理|為網站建立和管理 API | |開發資料儲存與處理系統|建置系統以安全地儲存和處理支付處理等流程所需的資料| |編寫、測試和維護程式碼 |編寫程式碼、測試和開發編碼問題的解決方案,包括維護任務 | |設計網站架構|使用敏捷 Scrum 和框架等既定方法設計網站架構 | |組織系統邏輯|有效建構系統邏輯| |提供系統問題的解決方案|提供解決方案來解決與系統相關的問題和挑戰 | --- 🥷 六個月內成為後端開發人員的技能 ----------------- 成為熟練的後端開發人員需要在相對較短的時間內掌握各種技能和技術。 下面,我概述了一個**全面的路線圖**,以幫助您在六個月內實現這一目標: **第 1 個月:🦴後端開發基礎** - **第一週:**了解後端開發人員的角色和職責。熟悉資料庫、框架和 API。 - **第 2-3 週:**參加資料庫綜合課程。了解 SQL 和 MySQL 等關聯式資料庫。練習建立和管理資料庫。 - **第 4 週:**了解伺服器端程式語言。從 PHP 或 Python 開始。學習基本語法、控制結構和資料類型。 **第 2 個月:🍖高階後端概念** - **第 1-2 週:**加深對資料庫的理解。探索 MongoDB 等 NoSQL 資料庫。了解*資料建模和優化*。 - **第 3-4 週:**掌握伺服器端程式語言。深入研究 PHP 或 Python。了解函數、類別和物件導向程式設計。 - **最後 2 天:**使用框架進行實踐專案。選擇流行的框架,例如*適用於 PHP 的 Laravel 或適用於 Python 的 Django* 。建立簡單的應用程式以理解模型-視圖-控制器 (MVC) 架構。 **第 3 個月:🌦️API 開發和集成** - **第 1-2 週:**了解 API 及其在後端開發中的重要性。探索 RESTful API 設計原則。 - **第 3 週:**開始建立您自己的 API。使用`Postman`等工具來測試和偵錯 API 端點。了解身份驗證和安全性。 - **第 4 週:**關注*API 整合*。了解如何在 Web 和行動應用程式中使用 API。練習將第三方 API 整合到您的專案中。 **第 4 個月:🛸故障排除與最佳化** - **第 1-2 週:**掌握故障排除技術。了解如何有效診斷和除錯後端應用程式。 - **第 3 週:**進入效能優化階段。了解快取、負載平衡和資料庫索引。 - **第 4 週:**學習監控和分析工具。使用 New *Relic 或 Datadog*等工具來監控應用程式效能。了解如何產生和**分析**績效指標。 **第 5 個月:🌥️雲端基礎設施和部署** - **第 1 週:**了解雲端運算概念。了解 AWS 或 Azure 等流行的雲端供應商。 - **第 2-4 週:**學習後端開發雲端服務課程。了解無伺服器運算、容器化和微服務。 - **休息日:**練習將應用程式部署到雲端。了解 CI/CD 管道和自動化部署策略。 **第 6 個月:🏗️高級主題和專案** - **第 1-2 週:**在上個月,學習更多高階後端主題。選擇訊息佇列、事件驅動架構和即時通訊等主題。 - **第 3 週:**開展*頂點計畫*。選擇一個具有挑戰性的專案,整合各種後端技術和概念。 - **第 4 週:**最後,反思您的學習歷程。審查您的專案並確定需要改進的領域。透過練習**程式設計挑戰**來準備工作面試。 ⛏️後端開發:工具與軟體 ------------ 以下是後端開發中常用的工具和軟體的細分: **1. 📇資料庫框架:** - **MySQL:**用於儲存和檢索資料的關聯式資料庫管理系統。它廣泛用於 Web 應用程式,並提供可擴展性和可靠性。 - **MongoDB:**一種 NoSQL 資料庫程序,使用具有模式彈性的類似 JSON 的文件。它適用於資料模型快速變化或非結構化資料的應用程式。 **2. 🛝網路伺服器:** - **Apache HTTP Server 2:**一種開源 Web 伺服器軟體,可透過網際網路提供 Web 內容。它以其穩定性、安全性和靈活性而聞名,使其成為託管網站和 Web 應用程式的熱門選擇。 **3. 🔐安全協定:** - **SSL/TLS 憑證:**這些是透過電腦網路提供安全通訊的加密協定。 SSL(安全通訊端層)及其後繼者 TLS(傳輸層安全性)對伺服器和用戶端之間的資料進行加密,確保線上連線的機密性和完整性。 **4. 💫版本控制系統:** - 接下來,請熟悉版本控制系統。這些系統有助於追蹤專案歷史並實現與其他人的協作工作。 - 🔌 吉特: - Git 是使用最廣泛的版本控制系統,超過 70% 的軟體開發團隊都在使用。這是一個必須知道的工具,建議分配大約兩週的時間來學習 Git。 後端開發人員通常利用這些工具和軟體來建立強大、可擴展且安全的 Web 應用程式,用於處理資料儲存、伺服器託管和線上通訊加密。 如果您尋求有關後端開發的更多知識,請考慮查看此[深入的後端開發路線圖](https://roadmap.sh/backend)。 --- 👏結論 --- 透過遵循此時間表並持續努力學習,您可以在 6 個月內獲得寶貴的後端開發技能,並為找到後端開發工作做好充分準備。 如果您想在六個月內成為前端開發人員,您也可以閱讀這篇文章。 👇 [6 個月內成為前端開發人員的路線圖](https://dev.to/codewithshahan/must-have-frontend-development-skills-roadmap-2024-28jc) 最後,如果你想知道[前端開發的未來](https://dev.to/codewithshahan/the-future-of-frontend-development-1amd),你也可以看看這篇文章。 請關注更多有價值的內容,如果您覺得有幫助,您可能也會喜歡我的[YouTube 頻道](https://www.youtube.com/programmingwithshahan)。 感謝您花時間閱讀這篇文章。 🤳我的社交: [X](https://twitter.com/shahancd) --- 原文出處:https://dev.to/codewithshahan/skills-to-become-a-backend-developer-in-6-months-roadmap-4li3

為什麼我從 Visual Studio Code 切換到 Sublime Text

最近,我改用[Sublime Text](https://www.sublimetext.com/)作為我的主要程式碼編輯器。一年多來,我一直使用[Visual Studio Code](https://code.visualstudio.com/)來編寫程式碼。這兩個編輯器非常相似,但也有足夠的差異,我想分享一下是什麼讓我全職使用 Sublime。 *注意:這篇文章並不是要為了一項技術而抨擊另一項技術。我嘗試根據我的個人經驗進行誠實的比較,但選擇程式碼編輯器是一個主觀過程,因此每個人都會對自己的最愛有不同的看法。* 是什麼讓我跳槽 ------- ### 偉大的符號分析 當您在 Sublime Text 中開啟一個專案時,它會自動啟動一個稱為「符號分析」的過程,這是在程式碼中尋找關鍵字的一個奇特術語。符號分析的好處在於,我可以輸入 Cmd + Shift + R 來調出符號搜尋選單,並在整個程式碼中快速找到類別名稱和方法。我主要使用 PHP,因此如果我已經知道我正在處理的類別名稱是`PostController` ,我可以在符號搜尋中搜尋它並立即在編輯器中開啟我的 PHP 類別檔案。 VS Code 也支援符號搜尋,但是,它只支援幾種開箱即用的語言。有一個與 VS Code 一起使用的第三方 PHP 符號分析器,但是,我發現它在處理大型程式碼庫時遇到困難,而 Sublime 則沒有問題。 ### 超快 Sublime Text 是可用於編寫程式碼的最快的文字編輯器。它幾乎立即打開並執行非常快速的搜尋。 Microsoft 在保持 VS Code 效能方面做得很好,但是 VS Code 是基於[Electron](https://electronjs.org/)的。 Electron 是一個用於捆綁 Chromium 實例和用 JavaScript/Node.js 編寫的程式碼的框架。它使編輯器具有很強的可擴展性,但使用 Chromium 的整個實例作為文字編輯器會使應用程式啟動緩慢並使用更多記憶體。 Sublime Text 是一個用 C++ 編寫的本機應用程式,因此其佔用空間要少得多。 ### 更好的 Vim 綁定 我非常喜歡在編寫程式碼時使用 Vim 鍵綁定。儘管我喜歡 Vim 鍵盤快捷鍵,但我仍然喜歡使用標準文字編輯器來利用側邊欄文件清單和文件標籤等現代功能。我發現 Sublime 的 Vim 支援比 VS Code 更準確,這有助於我更快地編寫程式碼。 Sublime 支援開箱即用的 Vim 綁定,但如果您使用[Vintageous](https://github.com/guillermooo/Vintageous)插件,您可以獲得更多功能。 我在 Visual Studio Code 中懷念的事情 ---------------------------- ### 功能豐富的側邊欄 VS Code 有一個非常好的側邊欄,可以更靈活地建立和移動檔案。 Sublime 有一個更好的側邊欄插件,還有其他鍵盤快捷鍵插件(例如[AdvancedNewFile)](https://github.com/skuroda/Sublime-AdvancedNewFile)可以使轉換更容易,但有時我會懷念 VS Code 側邊欄的開箱即用功能。 ### 內建偵錯工具 VS Code 有一個內建的偵錯器,適用於多種程式語言。它使得使用 PHP 的 xdebug 變得非常簡單。儘管 Sublime 有除錯插件,但它們並不像 VS Code 提供的開箱即用的那樣可靠。在這種情況下,如果我正在除錯一些棘手的東西,我仍然會打開 VS Code。 結論 -- 最後,文字編輯器完全取決於個人喜好和工作要求。對於我的用例來說,Sublime 是一次非常愉快的體驗,它幫助我更快地編寫程式碼。如果您想了解有關 Sublime Text 的更多訊息,Jeffrey Way 在 Laracasts 上開設了相關[課程](https://laracasts.com/series/sublime-text-mastery),Wes Bos 也寫了一本相關[書籍](http://wesbos.com/sublime-text-book/)。 請在評論中告訴我您最喜歡的編輯器是什麼! --- 原文出處:https://dev.to/restoreddev/why-i-switched-from-visual-studio-code-to-sublime-text-28k0

下一個專案來玩玩看吧:10 個有趣 API

在程式設計社群中,從事業餘專案的想法被許多人拋棄。坐在空白的程式碼編輯器前思考要建立什麼可能會令人生畏。許多部落格文章都建議建立計算器、待辦事項清單和社交媒體克隆等應用程式。雖然這些肯定對學習技術堆疊有幫助,但讓我們面對現實吧——這世界不需要更多的計算器或待辦事項清單應用程式。相反,我們可以圍繞面向公眾的 REST API 建立新的、有趣的應用程式。 > 這是我經營的部落格[Imago Dev](https://imago.dev)的交叉貼文。 ### 什麼是 REST API? 可表示狀態傳輸 (REST) 應用程式介面 (API) 提供了一組方法,程式設計師可以使用這些方法透過 HTTP 發送和接收資料。由於這些方法是透過 HTTP 實現的,因此任何程式語言都可用於使用 REST API。 幾乎所有可以想像的不同領域都有數以千計的 REST API 可用。天氣或股票市場等常用公共資料有數十個 API 可供使用。許多流行的網路平台,例如 Facebook 和 Twitter,也向開發人員提供 API。有些專有 API 對呼叫次數有限制。許多需要註冊並接收私有 API 金鑰。最安全的 API 需要設定 OAuth 以便使用者安全登入。 您可以[在此 Github 上](https://github.com/public-apis/public-apis)找到大量公共 api 列表,並在[RapidAPI](https://rapidapi.com)上找到更豐富的列表。 ### 10 個有趣的公共 REST API 這個清單當然並沒有詳盡地列出很酷的 REST API,而只是一些我認為特別簡潔且值得進行一些副專案的 API。所有這些都是完全免費的,除了獲取 API 金鑰之外不需要任何其他東西 - 無需弄清楚如何處理 OAuth 或為其使用付費。 1. [PokeAPI](https://pokeapi.co)有史以來最大的媒體特許經營商現在可以輕鬆獲得 800 多個 Pokemon 的資料。 2. [NASA API](https://api.nasa.gov/index.html)空間,最後的前沿。取得有關小行星、星係等的資料。 3. [Open Food Facts](https://world.openfoodfacts.org/data)來自世界各地的大量食品資料。 4. [TransLoc OpenAPI](https://rapidapi.com/transloc/api/openapi-1-2)取得城市和大學校園公共交通的即時資料。 5. [Urban Dictionary API](https://rapidapi.com/community/api/urban-dictionary)人們想出的俚語真是令人驚訝。 6. [Merriam-Webster Dictionary API](https://dictionaryapi.com)適合需要真實單字定義和同義詞的人。 7. [Numbers API](http://numbersapi.com)有關數字的有趣事實和瑣事。 8. [WeatherBit API](https://www.weatherbit.io/api)當前和歷史天氣資料。 9. [美國政府資料 API](https://www.data.gov/developers/apis)一個相當大的包羅萬象的美國農業、健康和公共安全等數十個資料樣本。 10. [Bible API](https://scripture.api.bible)有史以來最暢銷的書。有史以來最偉大的故事。 ### 如何處理它們 所有這些公共 API 都很棒,但是擁有一系列有趣的資料來源並不能本質上幫助解決確定新專案要做什麼的初始問題。 最好的起點是簡單地獲取和顯示資料。也許這正在顯示當天的神奇寶貝或鍵入的單字的定義。對於更具創意的類型,請嘗試獲取資料片段並將其映射到視覺元素,例如溫度到顏色或根據公車移動繪製線條。 最困難的部分就是開始。一旦您克服了獲取和顯示資訊的最初障礙,我相信您會為您的專案想到很多後續步驟! --- 原文出處:https://dev.to/camerenisonfire/10-intriguing-public-rest-apis-for-your-next-project-2gbd

資料庫 101:如何為 100 萬玩家的遊戲建立排行榜模型。

有沒有想過像**《英雄聯盟》** 、 **《要塞英雄**》甚至**《Rockband》**這樣的遊戲是如何建立排行榜模型的?在本文中,我們將了解如何正確建模模式以以極其高效的方式處理它們! 如果您剛開始使用一般資料庫或資料庫,您可能需要閱讀我的第一篇文章[《資料庫 101:初學者的資料一致性](https://dev.to/danielhe4rt/database-101-why-so-interesting-1344)》。那篇文章記錄了我自己對有多少資料庫範例的探索,因為我的眼光遠遠超出了我以前僅使用 SQL 和 MySQL 的經驗。我正在**資料庫 101**系列中追蹤我的研究。 > 距離我發表本系列的第一篇文章已經快一年了!感謝您在我學習主題時與我在一起。您的評論和想法總是非常有幫助! 1. 序言 ----- ![YARG 遊戲玩法截圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvensca2v67ma66vssnh.png) 從我還是個孩子的時候起,就像大多數普通開發者一樣,我就對遊戲及其製作方式著迷。說到這裡,我要跟大家介紹一下我兒時最喜歡的遊戲:《吉他英雄3:搖滾傳奇》。 十多年後,我決定嘗試在開源環境中為一些遊戲做出貢獻,例如[rust-ro(Rust Ragnarok Emulator)](https://github.com/nmeylan/rust-ro)以及本文的主角: [YARG(Yet Another Rhythm Game)](https://github.com/YARC-Official/YARG) 。 YARG 實際上是另一個節奏遊戲,但這個專案的不同之處在於它是完全**開源的**,他們聯合了遊戲開發和設計方面的傳奇貢獻者來讓這個專案能夠運作。 突然之間,這款遊戲被 Twitch 上的 Guitar Hero/Rockband 主播們所採用並玩,我想:好吧,這是一個開源專案,所以也許我可以利用我的資料庫技能來建立一個**速度極快的排行榜**或儲存過去的比賽。 一開始只是在他們的 Discord 上進行了一次簡單的聊天,後來變成了關於如何讓這個專案更快發展的長時間討論。 然後我決定和我的老闆談談,問他我是否可以和 YARG 的人一起工作,條件是建立一些足夠酷的東西來實現[ScyllaDB(NoSQL 寬列資料庫)](https://scylladb.com/) ,因為我在那裡擔任開發倡導者。您不會相信ScyllaDB帶來的簡單性和可擴展性如何完美契合YARG.in的需求! 無論如何,談話是廉價的。讓我向您展示一些程式碼和概念! 2.QDD-查詢驅動的資料建模 --------------- ![NoSQL 與關係型資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivkj9j8ni2fakkctx53n.png) 當我們談論使用**NoSQL**進行開發時,大多數情況下我們應該理解,根據範例(文件、圖形、寬列等),您應該先了解**要執行哪個查詢**。 在 MySQL 中,主要目標是了解一致性,而在 Scylla 中,您應該專注於查詢並基於該查詢建立模式。 在這個專案中,我們將處理兩種類型的範例,它們是: - 核心價值 - 寬列(聚類) 現在讓我們來談談我們建模的查詢/功能。 ### 2.1 功能:儲存匹配 ![提交詳情 YARG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jaw4q7349upgrsa5p5g3.png) 每次完成 YARG 遊戲時,最有趣的事情就是提交您的分數以及許多其他遊戲內指標。 基本上它將是基於主索引的單一查詢,僅此而已。 ``` SELECT score, stars, missed_notes, instrument, ... FROM leaderboard.submisisons WHERE submission_id = 'some-uuid-here-omg' ``` ### 2.2 功能:排行榜 ![排行榜 Figma 文件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69jp0vgxef71titt9ks0.png) 現在我們的主要目標是:一個超酷的**排行榜**,在良好的資料建模之後你不需要關心它。排行榜是按歌曲計算的,因此每次您播放特定歌曲時,您的最佳成績都會被保存並排名。 然而,這個介面有一個重要的點,那就是有過濾器來準確地知道要帶來「哪個」排行榜: - 歌曲 ID:必填 - 儀器:必填 - 修飾符:必需 - 難度:必填 - 玩家 ID:可選 - 分數:可選 想像一下我們的查詢如下所示,它會傳回按分數降序排列的結果: ``` SELECT player_id, score, ... FROM leaderboard.song_leaderboard WHERE instrument = 'guitar' AND difficulty = 'expert' AND modifiers = {'none'} AND track_id = 'dani-california' LIMIT 100; -- player_id | score ----------------+------- -- tzach | 12000 -- danielhe4rt | 10000 -- kadoodle | 9999 ----------------+------- ``` 現在我們知道了將在這裡使用的功能,但是您能想像最終的模式將如何嗎? 不?好的,讓我來幫助你! 3. 資料建模時間! ---------- 是時候深入研究 ScyllaDB 的資料建模並更好地了解如何擴展它了。 ### 3.1 - 匹配建模 ![遊戲結束畫面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6kedk7iu67zg7myj9mp.png) 首先,讓我們先來了解遊戲本身: - 這是一個節奏遊戲; - 您一次播放一首特定的歌曲; - 您可以在遊戲前啟動“修改器”,讓您的生活變得更輕鬆或更困難; - 您必須選擇一種樂器(例如吉他、鼓、貝斯和麥克風)。 - 遊戲玩法的各個方面都會被跟踪,例如: - Score; - Missed notes; - Overdrive count; - Play speed (1.5x ~ 1.0x); - Date/time of gameplay; - And other cool stuff. 考慮到這一點,我們可以輕鬆地開始我們的資料建模,這將變成這樣: ``` CREATE TABLE IF NOT EXISTS leaderboard.submissions ( submission_id uuid, track_id text, player_id text, modifiers frozen<set<text>>, score int, difficulty text, instrument text, stars int, accuracy_percentage float, missed_count int, ghost_notes_count int, max_combo_count int, overdrive_count int, speed int, played_at timestamp, PRIMARY KEY (submission_id, played_at) ); ``` 讓我們跳過所有`int/text`值並跳到`set<text>` 。 **集合**類型可讓您儲存特定類型的專案清單。我決定使用這個清單來儲存修飾符,因為它非常適合。看看查詢是如何執行的: ``` INSERT INTO leaderboard.submissions ( submission_id, track_id, modifiers, played_at ) VALUES ( some-cool-uuid-here, 'starlight-muse' {'all-taps', 'hell-mode', 'no-hopos'}, '2024-01-01 00:00:00' ); ``` 使用這種類型,您可以輕鬆儲存專案清單以供以後檢索。 另一個很酷的資訊是這個查詢是一個鍵值對!這意味著什麼? 由於您始終僅透過`submission_id`來查詢它,因此它可以歸類為鍵值。 ### 3.2 排行榜建模 ![排行榜濾鏡 Figma](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpmzngra3jk5ipf3os3i.png) 在本文的這一部分中,您將學習一些很酷的寬列資料庫概念。 在我們的排行榜查詢中,如前所述,我們總是需要在 WHERE 子句中使用一些動態值,這意味著這些值將屬於**分區鍵**,而**聚類鍵**將具有可以是「可選」的值。 **分區鍵**是基於您新增的用於標識值**的欄位組合的**雜湊。你明白了嗎?不?好吧,我也花了一段時間才明白這一點,但讓我向你展示一些東西: 假設您玩了`Starlight - Muse` 100 次。如果您要查詢此訊息,將透過`score`或`player_id`等聚類鍵區分出100倍不同的結果。 ``` SELECT player_id, score --- FROM leaderboard.song_leaderboard WHERE track_id = 'starlight-muse' LIMIT 100; ``` 如果有 1.000.000 個玩家播放這首歌,你的查詢會變得很慢,並且將來會成為一個問題,因為你的分區鍵只包含一個字段,即`track_id` 。 但是,如果您向**Partition Key**加入更多字段,例如玩遊戲之前的強制性內容,也許我們可以縮小這些可能性以實現更快的查詢。現在你看到大局了嗎?加入諸如**“樂器”** 、 **“難度**”和**“修改器”等**欄位將為您提供一種均勻分割有關特定曲目的資訊的方法。 讓我們想像一些簡單的數字: ``` -- Query Partition ID: '1' SELECT player_id, score, ... FROM leaderboard.song_leaderboard WHERE instrument = 'guitar' AND difficulty = 'expert' AND modifiers = {'none'} AND -- Modifiers Changed track_id = 'starlight-muse' LIMIT 100; -- Query Partition ID: '2' SELECT player_id, score, ... FROM leaderboard.song_leaderboard WHERE instrument = 'guitar' AND difficulty = 'expert' AND modifiers = {'all-hopos'} AND -- Modifiers Changed track_id = 'starlight-muse' LIMIT 100; ``` 因此,如果您以特定形狀建立查詢,它將始終查找特定令牌並根據這些特定分區鍵檢索資料。 我們來看看最終的建模,談談聚類鍵和應用層: ``` CREATE TABLE IF NOT EXISTS leaderboard.song_leaderboard ( submission_id uuid, track_id text, player_id text, modifiers frozen<set<text>>, score int, difficulty text, instrument text, stars int, accuracy_percentage float, missed_count int, ghost_notes_count int, max_combo_count int, overdrive_count int, speed int, played_at timestamp, PRIMARY KEY ((track_id, modifiers, difficulty, instrument), score, player_id) ) WITH CLUSTERING ORDER BY (score DESC, player_id ASC); ``` 分區鍵的定義如上所述,由我們**所需的參數**組成,例如:track\_id、修飾符、難度和樂器。在**聚類鍵**上,我們新增了**Score**和**player\_id** 。 > 請注意,預設情況下,聚類欄位按`score DESC`排序,以防萬一玩家得分相同,選擇獲勝者的標準將按`alphabetical` ¯\\\_(ツ)\_/¯。 首先很容易理解的是,我們**每個玩家只有一個分數**,但透過這種建模,如果玩家以不同的分數兩次經歷同一條賽道,它將產生兩個不同的條目。 ``` INSERT INTO leaderboard.song_leaderboard ( track_id, player_id, modifiers, score, difficulty, instrument, stars, played_at ) VALUES ( 'starlight-muse', 'daniel-reis', {'none'}, 133700, 'expert', 'guitar', '2023-11-23 00:00:00' ); INSERT INTO leaderboard.song_leaderboard ( track_id, player_id, modifiers, score, difficulty, instrument, stars, played_at ) VALUES ( 'starlight-muse', 'daniel-reis', {'none'}, 123700, 'expert', 'guitar', '2023-11-23 00:00:00' ); SELECT player_id, score FROM leaderboard.song_leaderboard WHERE instrument = 'guitar' AND difficulty = 'expert' AND modifiers = {'none'} AND track_id = 'starlight-muse' LIMIT 2; -- player_id | score ----------------+------- -- daniel-reis | 133700 -- daniel-reis | 123700 ----------------+------- ``` 那我們要如何解決這個問題呢?嗯,這本身不是問題。這是一個特點!哈哈 身為開發人員,您必須根據專案需求建立自己的業務規則,這也不例外。我這麼說是什麼意思? 您可以在插入新條目之前執行簡單的**DELETE**查詢,並確保在該特定**分區鍵**組內, **player\_id**中的特定資料不會低於新**分數**。 ``` -- Before Insert the new Gampleplay DELETE FROM leaderboard.song_leaderboard WHERE instrument = 'guitar' AND difficulty = 'expert' AND modifiers = {'none'} AND track_id = 'starlight-muse' AND player_id = 'daniel-reis' AND score <= 'your-new-score-here'; -- Now you can insert the new payload... ``` 這樣我們就完成了簡單的排行榜系統,該系統與 YARG 中執行的系統相同,也可以在每秒數百萬個條目的遊戲中使用:D 4. 如何為 YARG 做出貢獻 ---------------- 這是我邀請您為這個精彩的開源專案做出貢獻的文字部分! 今天,我們正在為所有玩家建立一個全新的平台,使用: - 遊戲:Unity3d [(儲存庫)](https://github.com/YARC-Official/YARG) - 前端:NextJS [(儲存庫)](https://github.com/YARC-Official/yarg.in) - 後端:Laravel 10.x [(儲存庫)](https://github.com/YARC-Official/yarg-api) 我們將需要盡可能多的開發人員和測試人員與主要貢獻者一起討論遊戲的未來實現! ![YARG 不和諧](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5b2pdxvrth2o6jfmada.png) 首先,請確保加入他們的[Discord 社群](https://discord.gg/sqpu4R552r)。在進入開發板之前,所有技術討論都會在社群後台進行。 此外,在 Discord 之外,YARG 社群主要關注[EliteAsian](https://twitter.com/EliteAsian123) (核心貢獻者和專案所有者)Twitter 帳戶的開發展示。一定要跟著他去那裡。 https://twitter.com/EliteAsian123/status/1736149319382671766 僅供參考,遊戲的**首席美術師**(又稱[Kadu)](https://twitter.com/kaduyarg)也是**Elgato**的**廣播專家**和**產品創新**開發人員,曾與以下串流媒體合作: - 忍者 - 納德肖特 - 石山64 - 以及傳奇 DJ Marshmello。 Kadu 也使用他的 Twitter 分享一些見解以及 YARG 新功能和實驗的早期預覽。所以,別忘了在 Twitter 上關注他! https://twitter.com/kaduyarg/status/1689489132060397568 以下是一些有用的連結,可以幫助您了解有關該專案的更多訊息: - [官方網站](https://yarg.in/) - [Github 儲存庫](https://github.com/YARC-Official/YARG) - [任務板](https://yarg.youtrack.cloud/agiles/147-7/current) > 有趣的事實:YARG 受到了 Guitar Hero 專案負責人[Brian Bright](https://twitter.com/BrianBright/status/1744533504531317194)的關注,他喜歡該專案的開源特性。太棒了,對吧? 5. 結論 ----- 資料建模有時具有挑戰性,這項研究花了 3 個月的時間研究了許多新的 ScyllaDB 概念,並與我在 Twitch 的社群一起進行了大量測試。 我還發布了[遊戲排行榜演示](https://github.com/scylladb/gaming-leaderboard-demo),您可以在其中獲得有關如何使用**NextJS**和**ScyllaDB**實現同一專案的一些見解! 另外,如果您喜歡 ScyllaDB 並想了解更多訊息,我強烈建議您觀看我們的免費[大師班課程](https://lp.scylladb.com/masterclass-ondemand-main?siteplacement=navigation)或存取[ScyllaDB 大學](https://university.scylladb.com/)! 不要忘記喜歡這篇文章,在社交上關注我並填滿你的水瓶 xD 下一篇文章見! [在推特上關注我](https://twitter.com/danielhe4rt) [在 Github 上關注我](https://twitter.com/danielhe4rt) [在 Github 上關注我](https://twitter.com/danielhe4rt) [關注並訂閱我的 Twitch 頻道](https://twitch.tv/danielhe4rt) --- 原文出處:https://dev.to/danielhe4rt/database-101-how-to-model-leaderboards-for-1m-players-game-2pfa

GitHub README 文件:響應式? 🤔 動畫? 🤯 淺色和深色模式? 😱

是的,你沒聽錯,我的 GitHub 自述文件有淺色和深色模式,甚至是響應式的。在這篇文章中,我將簡要介紹我用來實現這一目標的技巧(以及使它變得困難的事情!) 但首先,讓我們看看我的個人資料在不同的螢幕尺寸和顏色偏好下是什麼樣子(或者親自嘗試一下 [GrahamTheDevRel 在 GitHub 上的個人資料](https://github.com/GrahamTheDevRel)! ## 深色模式 ![GrahamTheDevRel GitHub 設定檔在桌面上處於深色模式。頂部有 5 個按鈕,就像一個選單,格雷厄姆在一個對話氣泡旁邊豎起大拇指,上面寫著「很好,我看到使用黑暗模式,就像一個真正的開發人員!呵呵!」。以下有 4 個部分討論他所做的專案和工作。![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oo5kj6fueo591w13a4tq.png) ## 移動的 請注意以下部分的不同按鈕設計和佈局。 建立這些按鈕比您想像的要困難得多! ![GrahamTheDevRel GitHub 個人資料在行動裝置上處於深色模式。頂部有5 個按鈕,就像一個顯示社交圖標的選單,格雷厄姆在對話氣泡下方豎起大拇指,上面寫著「很好,我看到使用黑暗模式,就像一個真正的開發人員!呵呵!」。以下有4 個部分討論他所做的專案和工作,這些專案和工作的佈局與桌面不同,以適應較小的螢幕尺寸。![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3zo2c9hay32tcvmoc3ek.png) ## 燈光模式 我忍不住在英雄部分錶達了不同的訊息,當然我只是在開玩笑! 🤣💗 ![GrahamTheDevRel GitHub 設定檔在桌面上處於輕型模式。頂部有5 個按鈕,就像黑底白字的選單,格雷厄姆拇指朝下,旁邊有一個對話氣泡,上面寫著「哦不,不是淺色模式,你不知道開發人員只使用深色模式嗎?」。以下有 4 個部分討論他在淺色方案中所做的專案和工作。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cgipfop82t8499i5fukw.png) ## 喔...我有沒有提到它是動畫的? 請記住,前 5 個按鈕都是單獨的圖像!花了一點功夫才把它弄好! ## 使用了一些技巧! 好吧,所以你來這裡不僅僅是為了看我的個人資料! (如果你這樣做了,也愛你!🤣💗) 不,你是來學一些技巧的,這樣你就可以自己做對吧? 嗯,有一個“技巧”,然後只需一個 HTML 功能,您就可以自己完成此操作。 讓我們從最有趣的開始: ## 製作響應式按鈕和圖像的技巧! 網站上的按鈕和英雄圖像是有趣的部分。 為了讓它們發揮作用,我們使用了許多人沒有聽說過的 SVG 功能「<foreignObject>」。 ## `<foreignObject>` 和 SVG 獲勝! 這是使事物響應的最大技巧。 你看,在 markdown 文件中我們能做的事情非常有限(這就是為什麼我們看到人們使用 `<table>` 等進行佈局......ewww)。 但 SVG 有一個獨特的功能,[`<foreignObject>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)。 這允許您在 SVG 中包含許多內容,**包括 HTML 和 CSS。** 有了它,我們就有了更多的力量! 您可以在 CodePen 的演示中看到(單擊按鈕可以更改外部容器的大小,它代表頁面上圖像的可用空間): ### SVG 中 CSS 的 CodePen 演示 **一定要查看 html 面板**,所有技巧都在那裡! https://codepen.io/GrahamTheDev/pen/mdomxyy 關鍵部分在這裡: ``` <svg xmlns="http://www.w3.org/2000/svg" fill="none"> <foreignObject width="100%" height="100%"> <div xmlns="http://www.w3.org/1999/xhtml"> <!--we can include <style> elements, html elements etc. here now, with a few restrictions! Note it must be in xHTML style (so <img src="" /> not <img src="" > to be valid --> </div> </foreignObject> </svg> ``` 從那裡我們可以使用內聯 `<style>` 元素和標準 HTML 元素來建立響應式映像。 但您可能會注意到該演示中使用的標記的另一件事。 ## 圖片很吸引人! 我將圖像(SVG 氣泡和我的圖像)作為 [`data` URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs )。 這是因為所謂的[內容安全策略 (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 及其在 GitHub 上的實作方式。 現在我不會解釋 CSP,但本質上它們有一條規則:「嘿,除了當前上下文之外,不能從任何地方加載圖像」。 對於普通圖像來說這不是問題,但這是圖像中的圖像,並且該圖像的“上下文”就是其本身。 因此,如果我們嘗試在 SVG 中包含另一個圖像,它將來自不同的位置並破壞我們的 SVG。 幸運的是,「資料」 URI 被視為相同的上下文/來源。 這就是為什麼它們在我們的範例中使用的原因,如果您想自己實現的話,還需要考慮另一件事! ## 最後一個技巧,「<picture>」元素且沒有空格。 我的意思是,這甚至不是一個技巧! 我的自述文件中的最後 4 個框是響應式的(並尊重顏色偏好),但它們使用標準媒體查詢來工作。 這裡唯一的考慮是嘗試找到一個有效的斷點,恰好是 GitHub 上的 768px。 然後我建立了 4 組圖像: - 深色模式和桌面 - 黑暗模式和移動設備 - 燈光模式和桌面 - 燈光模式和移動。 ### 大或小圖像 為了獲得正確的圖像,我們在每個來源上對桌面(大)圖像使用“media =”(min-width:769px)`,對於移動(小)圖像使用“media =“max-width:768px)”放入我們的“<picture>”元素。 ### 淺色和深色模式 若要取得淺色或深色模式,我們使用[`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)媒體查詢。 ### 結合我們的查詢和來源 然後我們只需設定「<picture>」元素來使用「(**寬度查詢**)和(顏色首選項)」的組合來選擇我們想要的來源: ``` <picture> <source media="(min-width: 769px) and (prefers-color-scheme: light)" srcset="readme/[email protected]"> <source media="(max-width: 768px) and (prefers-color-scheme: light)" srcset="readme/[email protected]"> <source media="(max-width: 768px) and (prefers-color-scheme: dark)" srcset="readme/[email protected]"> <img src="readme/[email protected]" alt="You will find me writing about tech, web dev, accessibility, breaking the internet and more over on DEV! Purple and neon pink design with Graham pointing at the next section" width="50%" title="My writing on DEV"> </picture> ``` 本身並不困難,但建立 4 種圖像變體非常耗時。 ### 沒有空格 我遇到了最後一個問題。 底部實際上由 4 個不同的圖像組成(是的,我必須為其建立 16 個不同的圖像...)。 這樣做的原因是每個部分都是一個可點擊的連結。 並不複雜,但有一個小問題要注意。 如果您想要讓兩個影像直接並排接觸(因此兩個影像的寬度均為 50%),則必須刪除錨點、圖片元素甚至這些圖片元素內的來源之間的所有空白。 否則 GitHub 會為你的元素加入一些邊距,並且它們將不會在同一行。 另外,儘管我刪除了所有空白,但我遇到了另一個限制,但第一行和第二行之間仍然有 8px 的間隙,您無法遺憾地刪除它(因此之間的線!)。 ## 包起來! 我可能會在未來對內容安全策略、「<picture>」元素技巧,當然還有「<foreignObject>」做一些更深入的解釋。 這更多的是對概念的介紹,以便您可以自己使用它們,而不是教程。 但現在你已經了解我使用的技巧,我希望看到你建立一個比我的更漂亮的 GitHub 自述文件了! 如果您這樣做,請在評論中分享! 💪🏼🙏🏼💗 大家編碼愉快! 💗 --- 原文出處:https://dev.to/grahamthedev/take-your-github-readme-to-the-next-level-responsive-and-light-and-dark-modes--3kpc

從 Next.js 到 Rails 再到 Elixir:我的 React.js 倦怠之旅

我自 2019 年以來一直是 Web 開發人員。我使用 React.js 和基於 React 的框架,如 Gatsby、Next、Remix、Astro 和 Hydrogen。我從來沒有對這些工具感到完全滿意,但是,作為一個深入 JS 生態系統的初學者,我從同行那裡聽到的都是這樣的話:「這就是方式,任何其他程式語言要么慢,要么老」。 ![就是這樣](https://media.giphy.com/media/stnjSj2vpLcM4rwmEH/giphy.gif) 結果,我習慣了巨大的複雜性:多個獨立的儲存庫、數千個函式庫和框架來實現簡單的事情、GraphQL、微服務、無伺服器、靜態網站產生、增量靜態再生、部分水化、 redux 、redux-thunk、babel、webpack、react 伺服器元件、伺服器操作等。這個清單還可以再持續 10 分鐘。 直到有一天我說**受夠了!** 讓我們來看看我慢慢發瘋的完整時間線。這需要一段時間,在閱讀長篇文章之前,請隨意煮點咖啡! --- ## 倦怠的時間表 ### [Gatsby.js](https://www.gatsbyjs.com/) 我記得完成我的訓練營並想:“我終於能夠建立我的作品集了!”,所以我做到了。只有一個小問題,我想在 Google 上建立索引,但是使用舊的「create-react-app」使這項任務幾乎不可能完成。很快我了解了 SEO 和 React 的水合循環,這讓我找到了這個問題的「解決方案」:Gatsby.js。靜態網站產生的想法對當時的我來說簡直是革命性的,畢竟沒有什麼比預先渲染 HTML 檔案更快了,對吧? 我決定透過閱讀文件來學習這個新框架,讓我告訴你,這**不是**一次有趣的體驗。我以前從未聽說過 GraphQL,顯然,您需要它來產生所有靜態檔案(到底是什麼???)。我問我的一些網友,很難學習這些過度設計的廢話是否正常,他們回答說「技能問題,再努力一點!」。於是我更加努力,終於學會了之後,我把我的個人網站移植到了Gatsby上。 ![再努力一點](https://media.giphy.com/media/gzRiZROEyDCznPofKj/giphy.gif) 我的大部分頁面都成功在 Google 上建立了索引,幾個月來,我對結果非常滿意。然後另一個問題出現了:我的**很多**開發者朋友開始說“Gatsby 死了!建立 Next 是為了簡化靜態站點生成並提供伺服器端渲染”。 ### [Next.js](https://nextjs.org/) 我快速瀏覽了 Next 文件並**立即**愛上了它。我能夠在沒有 GraphQL 的情況下用三分之一的程式碼做與 Gatsby 相同的事情!我再次將我的作品集移植到另一個框架:Next。 這次我確實有一次美好的經驗。部署到 Vercel 輕而易舉,「getStaticProps」和「getServerSideProps」功能很簡單,但功能非常強大,我可以選擇每個頁面的渲染樣式,整體來說非常靈活。 不幸的是,我透過慘痛的教訓學到了一些東西:在 JavaScript 生態系統中,所有美好的事情都會結束。 ### [混音](https://remix.run/) 我清楚記得 Remix 發佈時的情景。多名科技影響者開始發布有關它的內容(一如既往)。然而,當時我在主頁上看到它不支援靜態網站生成,只支援伺服器端渲染,所以我想「等一下,這些年來投資於 [JAMstack](https://jamstack.org/) 都被扔在這裡了嗎?不可能,這個框架不會長久」。然而,令我驚訝的是,Remix 不僅生存了下來,而且還被 Shopify 收購 https://shopify.engineering/remix-joins-shopify ,並成為 Next 的重要競爭對手。 幾個月過去了,我決定嘗試看看。我再一次感到驚訝,Remix 的主要座右銘是使用 Web 基礎知識,而不是像 Next 這樣過於複雜的快取系統。因此,在Remix 中編碼時,我腦中需要的思維模型要簡單10 倍:沒有全域狀態管理器,只需使用URL,更少的客戶端狀態,將所有邏輯移至伺服器,並使用cookie,無需使用完整堆疊中間的 REST API 非常簡單,只需將資料庫查詢移至「loader」函數即可。 ### 離開矩陣 ![離開矩陣](https://media.giphy.com/media/11e0gEWxYoSYTK/giphy.gif) 然後,突然間,真相呈現在我面前,我服下了紅色藥丸。我的腦海中開始浮現出多個問題:Remix 不就像所有其他「古老而無聊」的框架(如 Rails、Laravel 和 Django)一樣嗎?幾十年來,我們一直在使用伺服器端渲染進行全端 Web 開發,但 JavaScript 黑手黨集體認為這種方法是垃圾,將所有內容移至客戶端才是未來。難道同一個黑手黨認為 Rails 一直都是對的嗎?用 JS 框架做所有那些過度設計的怪物不是正確的舉動嗎?我開始質疑一切。這種「新」的 Web 開發方式更加簡單、快速。 ### 我已經完成了 Next 和 Vercel 我透過 [Next.js 應用程式路由器](https://nextjs.org/docs/app) 達到了臨界點。以下是 Vercel 向 Next 推送的所有錯誤的完整清單: - 曾經簡單的:「getStaticProps」和「getServerSideProps」函數現在變得複雜而麻煩。目前,沒有特定的位置來新增 API 呼叫或資料庫查詢,您可以將它們寫入任何您想要的位置!在多年前使用 PHP 犯了同樣的錯誤之後,我們開始再次將業務邏輯與 UI 混合。難道前端開發者不吸取過去的教訓嗎?如果我刪除按鈕會發生什麼事?這是否會破壞我的使用者身份驗證流程,因為資料庫呼叫位於其中?您的前端應該 100% 可廢棄且可更換。你相對於競爭對手的競爭優勢在於業務邏輯,它應該與 UI 層完全隔離。 ![可怕的 Next.js 程式碼](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kp41ds14loo21xgimcza.png) - 接下來是伺服器優先。這聽起來沒那麼糟吧?畢竟,這解決了 SEO 問題並立即向用戶展示新鮮內容。問題在於,大多數現有的 Next 程式碼庫都依賴客戶端程式庫,例如樣式元件和一些全域狀態管理器。這是什麼意思?隨著此類重大變化的不斷發生,您的應用程式將在幾週而不是幾年內變成遺留軟體。更多的時間花在保持所有依賴項最新上,而不是做重要的事情:發布功能。 - Vercel 從 Meta 聘請了多名 React 核心團隊成員。這帶來了嚴重的利益衝突,因為這些工程師現在(據稱)正在發布有利於 Next 的功能,而不是優先考慮那些可以幫助所有基於 React 的框架(如 Remix)的功能。 ![Vercel 正在破壞 React](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ye40ykjgrd3z10t5nx7.png) 我再也受不了了。我對自己說:你知道嗎?我厭倦了一遍又一遍地重新學習相同的框架,我完全不同意這種新的範式。 毫不奇怪,其他內容創作者也經歷了類似的情況: https://youtu.be/zkCBSz353fc?si=z3-FDVgcB3xfp06h https://youtu.be/Zt8mO_Aqzw8?si=10fy1d-ZoB7t3Uc_ --- ## 啟蒙之路 我非常累。在厭倦了所有的 React 工具後,我開始了尋找更簡單的 Web 框架的旅程。以下是我一直在尋找的先決條件: - 含電池 - 約定優於配置 - 良好的開發體驗 - 現代化且高性能的前端 我的第一個反應是查看 [Stack Overflow Survey 2023](https://survey.stackoverflow.co/2023/#section-most-popular-technologies-web-frameworks-and-technologies) 中的頂級框架。我立即從清單中刪除了所有與 JS、C# 和 Java 相關的內容。我從來沒有興趣學習後兩個,它們看起來醜陋且冗長。所以剩下的選項是:Laravel (PHP)、Django (Python)、Rails (Ruby) 和 Phoenix (Elixir)。 Python 是我在網路工程學位期間使用的語言,我獲得了非常愉快的體驗。 Django 似乎遵循約定優於配置的理念,但最終讓我放棄它的是沒有一個好的內建工具來在前端工作。論壇上的大多數人都說他們使用[HTMX](https://htmx.org/) 和[Alpine](https://alpinejs.dev/),但是,兩者都是您需要安裝的外部依賴項。 放棄Laravel 是非常困難的,因為它具有驚人的成本效益,有數百個官方軟體包可以處理新創公司可能需要的幾乎所有內容,例如託管、身份驗證、條紋支付等。對於前端,他們創造了[慣性。Node.js](https://inertiajs.com/),這是一種非常簡單而優雅的方式,可以在前端使用 React 的同時保持 Laravel 的高生產力和強大功能。百分之百誠實地說,我沒有選擇 Laravel 的唯一原因是 PHP 的語法,它看起來很難看,到處都是一堆 `$` 和 `->`。 ### Ruby on Rails Ruby on Rails 無需介紹。它是 Web 開發框架的元年,其革命性的「15 分鐘建立部落格」至今仍令人印象深刻。在我開始抱怨我發現的所有問題之前,讓我們先從好的方面開始。 與 Python 類似,Ruby 是一種可以向非技術人員展示的語言,他們會理解該軟體想要做什麼。它是**迄今為止**我見過的最容易閱讀和最美麗的語言。我很快就意識到,[編寫視覺上令人愉悅的程式碼](https://world.hey.com/dhh/a-writer-s-ruby-2050b634) 是Rails 團隊的首要任務,這對我來說來說是新的。 更不用說 Rails 幾乎發明了「包含電池」和「約定優於配置」的哲學,所以這不會是一個問題。在一份文件中,我提供了任何類型的 Web 應用程式所需的一切。 在前端,有 [Hotwire](https://hotwired.dev/),這是一種非常簡單且輕量級的方法,可以實現 SPA 框架提供的所有 UX 改進。我一直很好奇測試這個庫的極限,它看起來非常有前途。 好吧,Rails 在紙面上滿足了我想要的框架的所有先決條件。我們來試試吧!我在本地測試的第一件事是“railsscaffold”命令。我立即感到震驚。一個指令就能產生 CRUD 所需的一切?決不! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58lbioexmot9412kojr5.png) 在 Node + React 領域,要實現相同的目標,我需要手動編寫所有程式碼(這裡沒有生成器)並安裝一堆程式庫,例如:Vite、prisma、express、react router、redux、redux-thunk 、 vitest、cypress 、react 測試庫、zod、typescript、eslint、prettier、1000 個不同的插件,甚至可能還有GraphQL 或tRPC。基本上就是一個已經有 900 個依賴項的 package.json。 在“railsscaffold”最初的震驚之後,當我從控制器打開程式碼時,我再次震驚了: ``` class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) if @article.update(article_params) redirect_to @article else render :edit, status: :unprocessable_entity end end def destroy @article = Article.find(params[:id]) @article.destroy redirect_to root_path, status: :see_other end private def article_params params.require(:article).permit(:title, :body) end end ``` 這是所有後端程式碼嗎?只需幾行?這不可能!這非常簡單,看起來就像一個「低程式碼」工具。它簡單、優雅、可讀性極強,這是我們在 JS 領域很少見的。 好吧,好吧,你現在一定在想:「這個來自網路的瘋狂 React 開發者說他最終使用了 Elixir,所以 ruby 一定有問題!」。你是對的,我的匿名朋友,有些事情讓我很惱火,讓我們談談。 首先,我們需要解決房間裡的大象:從 React + Typescript 轉向動態類型語言並不容易。從我開始編寫程式碼的那一刻起,我的 VScode 上就沒有出現智慧感知或充滿程式碼建議的下拉式選單,我感到盲目和迷失。這是一種可怕的感覺,我可能會在函數名稱上輸入錯誤,直到網站投入使用時才意識到!我知道我們可以編寫測試,但這是我希望在 IDE 上立即辨識的錯誤類型,而不是在測試或部署期間辨識。 另一件我以為我會喜歡但最終討厭它的事情是:太多的魔法。在 Typescript 程式碼庫中,我可以點擊任何類別或函數的頂部,前往原始程式碼並查看其實作方式。在 Rails 上,我到底在哪裡進行驗證(例如)?我是否在控制器內建立私有函數?有專門的資料夾嗎?不,正確的位置是在模型內部。為什麼?因為這就是它的工作原理,所以您要么採用該約定,要么很難編寫 Ruby 程式碼。我根本無法對一切在幕後如何運作產生“直覺”,我必須盲目地相信維護者在組織一切方面做得很好。 為了解決我的挫折感,我開始寫前端程式碼。如何建立元件? [部分](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)。如何定義該元件的 prop 類型?沒有辦法做到這一點,您需要打開它並直觀地查找其中的所有變數。做一些互動怎麼樣?建立國家?嗯,有帶有 [Stimulus](https://stimulus.hotwired.dev/) 的 Hotwire,但是正如您所看到的,您需要手動建立“重新渲染”功能,它沒有找到一種方法像React 這樣改變狀態後自動重新渲染頁面。 ``` // src/controllers/slideshow_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "slide" ] initialize() { this.index = 0 this.showCurrentSlide() } next() { this.index++ this.showCurrentSlide() } previous() { this.index-- this.showCurrentSlide() } showCurrentSlide() { this.slideTargets.forEach((element, index) => { element.hidden = index !== this.index }) } } ``` 我再一次感到沮喪。我非常接近找到完美的框架!如果 Rails 失敗,我想嘗試的下一個框架是什麼?靈丹妙藥。 ### 長生不老藥和鳳凰 我必須說實話,我已經沒有耐心了。我嘗試了多種不同的生態系統,我幾乎確信要堅持使用 Ruby on Rails,並放棄對完美的追求。直到我的 YouTube 推薦部分出現了一個影片: https://www.youtube.com/live/bfrzGXM-Z88?si=Xsa7yCKeVSY5R3sT 堅持,稍等!在這裡我們可以看到一位 React 開發人員說了很多關於函數式程式設計、Elixir 和 Phoenix Live View 的好話。也許我應該嘗試一下! 我做的第一件事就是打開Elixir 和Phoenix 的文件,我真的很喜歡這樣一個事實:所有包都使用[Hex Docs](https://hexdocs.pm/) 以相同的方式進行記錄,您只需要取得習慣於單一介面以學習新事物。 另一個好處是,您只需閱讀文件即可真正學習 Elixir,無需昂貴的課程!在其他所有生態系統中,我必須透過付費課程學習語言,然後透過閱讀文件來學習框架。 然後是時候開始編寫程式碼了。很快我就明白函數式程式設計與 OOP 有很大不同。我們來做一個小小的比較: ``` // JS const obj = {name: "daniel"} obj.age = 25 // result: obj = {name: "daniel", age: 25} ``` ``` # Elixir obj = %{name: "daniel"} obj = Map.put(obj, :age, 25) # result: obj = %{name: "daniel", age: 25} ``` 或者您可以使用管道運算子透過更簡單的語法實現相同的效果: ``` # Elixir with pipe operator obj = %{name: "daniel"} |> Map.put(:age, 25) # result: obj = %{name: "daniel", age: 25} ``` 最初,您可能會發現它的可讀性較差且更複雜,但我保證隨著時間的推移它會變得有意義!嗯,至少對我來說是這樣。身為 React 開發人員,我已經習慣了到處都可以看到多個函數,甚至前端元件也是函數!更不用說建立類別有時被 JavaScript 黑手黨視為一種程式碼味道。我的大腦已經針對這種新範式進行了“塑造”,這對我來說很自然。自從我在大學獲得網路工程學位以來,我上過幾門關於物件導向程式設計的課程,但它從來沒有「受歡迎」。我無法將複雜的問題建模為類別和物件。隨著時間的推移,使用多個函數來「改變」一個變數是我在腦海中建模的方式。 主要框架怎麼樣?包含鳳凰電池嗎?約定優於配置? **是的!** 老實說,生態系統與 Rails 不在同一水平,但已經達到了 95%。除非您需要非常具體的功能,Phoenix 都能滿足您的需求。 我幾乎被 Elixir 迷住了,我的清單中缺少兩件事:良好的開發人員體驗和現代/高效能的前端程式碼。 José Valim 宣布他正在嘗試為該語言加入類型,但 Elixir 目前還沒有這些類型,所以我很擔心。如何在沒有類型的情況下獲得智能感知和自動完成?很快我發現這些功能不一定相關。在 VScode 上安裝 [ElixirLS 擴充功能](https://marketplace.visualstudio.com/items?itemName=JakeBecker.elixir-ls) 後,我感到很驚訝。可以在隨機資料夾的隨機模組內定義函數,將其導入其他位置,並取得它的智慧感知和文件!我從靜態類型語言中獲得了這些好處,而無需編寫類型的麻煩,簡直太棒了! https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/ 我對前端的最後一個擔憂是由 Phoenix [Live View](https://hexdocs.pm/phoenix_live_view/welcome.html) 解決的。在程式碼方面,這正是文件主頁中讓我信服的部分: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5sjzj90khytebnk523fm.png) 您可以為每個元件定義“props”,如果類型不匹配,您的 IDE 中會出現錯誤,就像 React 一樣!感人的! 使用者體驗怎麼樣?每當使用者點擊連結時是否會載入整個頁面?一定不行!即時視圖與客戶端建立 WebSocket 連接,然後每次頁面轉換只是透過 Websocket 進行內容交換,不會發出新的 HTTP 請求。此外,所有狀態都在伺服器端進行管理,這意味著 Trello 等豐富的用戶體驗過去由於加載過多的 JavaScript 而在客戶端非常卡頓,現在變得非常快! Elixir 處理所有複雜的狀態邏輯並將頁面的更新部分傳送到前端。看看這裡的完整解釋: https://youtu.be/wrmVk2czqMg?si=ZoWAlPjQC-svmV3Y 由於我們使用 WebSocket 來建立 UI,因此建立像 Twitter 這樣的「即時」應用程式只需要幾行程式碼! https://youtu.be/MZvmYaFkNJI?si=gAow6oIjgf8_OTkg ## 結論 可以肯定地說,「完美的技術堆疊」並不存在。解決所有問題的靈丹妙藥是我們在腦中創造的幻覺,以不斷尋找和建構最優化的工具。 然而,在個人層面上,完美的堆疊確實存在。因為每個開發人員都有偏好,您可以輕鬆找到適合您標準的工具。如果你有和我類似的旅程,完美的可能就是長生不老藥和鳳凰!所以試試看吧,也許你會像我現在一樣喜歡它。 如果您讀到了這篇文章的結尾,那您就太棒了!非常感謝您抽出寶貴的時間,希望我能為您的職業生涯帶來一些價值。 ![結束](https://media.giphy.com/media/lD76yTC5zxZPG/giphy.gif) --- 原文出處:https://dev.to/danielbergholz/from-nextjs-to-rails-then-elixir-my-journey-through-reactjs-burnout-h8d

✨ 5 個被低估的開源專案 🫵🤐

## 簡介 本文列出了五個不太受歡迎的優秀專案,您應該嘗試一下。 🔥 這些工具旨在改進**資料處理**、**API 開發**、**後端測試**、**身份驗證**和**安全隧道**。 諸如此類的開源專案依賴社群支持🙏,因此請考慮探索並為這些儲存庫加註星標,以促進它們的發展。 ![擁抱 GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxhja1odmmx414wrts5a.gif) *** ## 1. [集算器](http://scudata.com) **- 資料處理** > 💡 集算器是一種用於資料處理的腳本語言,具有豐富的函式庫函數和強大的語法。 ![集算器資料處理腳本語言](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9dts1lgr1zy96k6zveu.jpg) 集算器是一個針對結構化和半結構化資料的計算和處理引擎。集算器既不是SQL系統,也不是NoSQL技術(如MongoDB),而是採用自創的SPL(結構化處理語言)語法,編碼更簡單,可以利用現有的資料處理技術建立高效的程式。 集算器是**純Java**編寫的,可以輕鬆為您的Java🍵應用程式加入強大的資料處理功能,但非Java應用程式可以透過RESTful API呼叫集算器。 ### 熱門常見問題解答🤔 > **⬇️集算器可以執行在哪些平台上?** 由於它純粹是用 Java 建置,因此可以在任何配備 JVM(Java 虛擬機)、雲端伺服器甚至容器的作業系統中流暢執行。 😎 > **⬇️集算器可以基於現有資料庫運作嗎?** 是的當然!集算器支援數十種資料來源,包括資料庫、文字、excel、json/xml、web服務等。 > **⬇️ 為什麼要放棄 SQL 而選擇集算器?** 簡化的逐步程式碼,易於編寫和除錯。相較於SQL降低N倍的開發、硬體、維運成本。 > 🟢我最近寫了一篇關於這個工具的文章,重點介紹了它的強大功能。看看吧👇。 https://dev.to/shricodev/one-must-have-tool-for-anyone-in-data-field-2jek > 如果你想更深入地了解這個工具的潛力,**[jbx1279](https://dev.to/jbx1279)**分享了一些關於集算器和SPL本身的富有洞察力的文章。請務必也檢查一下它們。 https://github.com/SPLWare/esProc *** ## 2. [Firecamp](https://firecamp.dev/) **- 郵差替代方案** > 💡 API 開發平台,幫助開發人員輕鬆設計、開發、測試和記錄他們的 API。 ![Firecamp 工具 Postman 替代品](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uigr65jz5z6yh731x9gm.jpg) Firecamp 是開放原始碼 Postman 的替代方案,具有 VScode DX,這是一個優先考慮開發人員體驗的 API 開發平台,並為設計、測試和記錄 API 提供無縫環境。 🎯 借助 Firecamp,跨工作區和團隊就 API 集合進行協作,並更快地建立 API,而無需在工具和應用程式之間切換。文件、CLI、CI/CD 一站式提供。 > **⬇️ 從 Postman 切換到 Firecamp 對我來說有挑戰性嗎?** 您可以將 Postman 腳本和資料(例如 **API Collection** 和 **環境變數**)無縫傳輸到 Firecamp,沒有任何問題。 ![Firecamp Postman 替代方案](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q74wl17yc9b7clse6m3h.png) https://github.com/firecamp-dev/firecamp *** ## 3. [Keploy](https://keploy.io/) **- 後端測試** > 💡 為您的應用程式產生實際有效的測試和存根! ![Keploy 產生後端測試](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ry5awt5wtk5qyiqccbwp.jpg) Keploy 是您的開源、以開發人員為中心的後端測試工具。它使工程團隊的後端測試變得簡單且有效率。使用 Keploy,我們不必編寫手動測試用例。 它記錄 API 互動和預期回應,並產生測試案例和資料模擬,使我們的工作變得輕鬆高效,顯著加快發布速度並增強可靠性。 📈 > **⬇️ 它是一個單元測試框架嗎?還是它完全取代了單元測試?** Keploy 與「go-test」、「Pytest」或「Jest」等單元測試框架配合得很好,可簡化測試流程並節省高達 80% 的工作。雖然它涵蓋了大多數情況,但您仍然可以選擇為非 API 可呼叫方法編寫測試。 > **⬇️ 我需要更改程式碼才能將 Keploy 整合到我的應用程式中嗎?** 不需要。Keploy 可以很好地與您現有的程式碼庫配合,無需更改程式碼。 ![Keploy 後端測試示範](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdsnkmq2efgxltzplfq9.gif) https://github.com/keploy/keploy *** ## 4. [Hanko](https://hanko.io) **- 金鑰驗證** > 💡 支援 FIDO2 和 WebAuthn 標準的無密碼身份驗證伺服器。 ![Hanko 金鑰驗證](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqifvf1i536y0afh7nhe.jpg) Hanko 是一款輕量級開源用戶身份驗證解決方案,可帶您踏上超越密碼的旅程。它支援 FIDO2 和 WebAuthn 標準,提供安全、無縫的使用者身份驗證體驗。 > **⬇️ Hanko 如何運作?** Hanko 的工作原理是使用使用者自己的裝置(例如智慧型手機、筆記型電腦或安全金鑰)註冊和驗證使用者。這些裝置可作為加密令牌,無需密碼或其他憑證即可證明使用者的身分。 Hanko 還支援各種身份驗證方法,例如行動應用程式中的生物辨識或 OAuth 登入。 > **⬇️ 我該如何開始使用 Hanko?** 您可以透過註冊免費帳戶並按照文件和教學課程開始使用 Hanko。對於生產用途,請選擇 Hanko Cloud。 > 🟢 我最近使用 Hanko Passkeys 身份驗證建立了一個專案。查看**[此處](https://github.com/shricodev/pdfwhisper-openai)**。 ![Hanko 登陸頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emte4gfglhdft8g8dlhh.png) https://github.com/teamhanko/hanko *** ## 5. [Zrok](https://zrok.io/) **- Ngrok 類固醇** > 💡 Ngrok 的替代品,提供增強的功能和免費的 SaaS 型號。 ![Zrok ngrok 替代方案](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x4n31emowwxoamfqzawm.jpg) Zrok 是一個建立在 **OpenZiti** 之上的工具,有助於共享正在執行的服務,例如 Web 伺服器或網路套接字,或安全地將靜態檔案目錄共享到網際網路。它是 Ngrok 的替代品,但具有一些增強功能和**免費 SaaS** 型號。 借助 Zrok,您可以為應用程式建立安全隧道,從而更輕鬆地共享和協作專案。 > **⬇️ 使用 Zrok 相對於 Ngrok 有什麼好處?** Zrok 擁有內建的身份驗證系統、用於管理隧道的 Web 儀表板以及免費的 SaaS 模型。它也是完全**可自我託管**。 > **⬇️ 我該如何開始使用 Zrok?** 若要開始使用 Zrok,請下載適合您平台的 Zrok 用戶端或使用 Web 介面建立隧道。您也可以使用 Zrok CLI 從命令列建立和管理隧道。 ![Zrok 安全隧道](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bp8bguor0wj3i8h6ail1.png) https://github.com/openziti/zrok *** > 如果您認為您使用的任何其他方便的專案沒有應有的受歡迎,請在下面的評論部分分享。 👇 非常感謝您的閱讀! 🎉🫡 --- 原文出處:https://dev.to/shricodev/top-5-underrated-open-source-projects-that-no-one-talks-about-2gki

如何製作精彩的 GitHub 個人資料

如果您是 GitHub 新手或主要使用私人 GitHub 儲存庫,那麼您可能還沒有 GitHub 個人資料。 GitHub 個人資料有助於為存取您個人資料的人提供基本資訊。擁有良好的個人資料甚至可以幫助您脫穎而出,尤其是當您開始為開源專案做出貢獻並且人們開始注意到您時。 在本文中,我將展示如何建立您自己的 GitHub 設定檔。我還將分享從哪裡獲得個人資料的靈感。最後,我將分享資源和技巧,幫助您建立出色的 GitHub 個人資料! ## 建立您的 GitHub 個人資料 在開始自訂 GitHub 個人資料之前,您首先需要建立一個。 這是一個[簡短指南](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme)來自 GitHub,了解如何設定您的個人資料。 但您需要做的就是: - 建立一個新的儲存庫,**與您的 GitHub 使用者名稱**相同。 - 將 `README.md` 檔案新增至您的新儲存庫。 例如,我的 GitHub 使用者名稱是 [kshyun28](https://github.com/kshyun28)。要建立我的個人資料,我需要建立一個也名為 [kshyun28](https://github.com/kshyun28/kshyun28) 的儲存庫,然後新增一個「README.md」檔案。 ![範例 GitHub 設定檔儲存庫](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616186/GitHub%20Profile/github-profile-repository-example_veplgh.png) 設定「儲存庫」和「README.md」檔案後,透過前往您的 GitHub 個人資料(網址為 https://github.com/YOUR-USERNAME )來驗證您的個人資料是否可見。 就我而言,它將是 https://github.com/kshyun28。 ## 自訂您的 GitHub 個人資料 現在您已經有了 GitHub 個人資料,是時候發揮創意了! 這裡的關鍵是**讓你的個性在你的個人資料上展現**。你的 GitHub 個人資料不必像 LinkedIn 那樣太正式。 我還建議**從簡單開始**。這有助於您的 GitHub 設定檔啟動並執行。當您有新想法時,您可以隨時改進您的個人資料。 ### GitHub 風格的 Markdown、格式和 HTML 為了自訂 GitHub 設定檔的“README.md”,您將使用 **GitHub Flavored Markdown**。如果您以前寫過 Markdown 內容,那麼格式化對您來說應該很容易。 如果你是第一次用Markdown 寫作,你可以去[GitHub 的文件](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) 來熟悉可用的格式化選項。 您也可以使用 **HTML** 作為您的個人資料的其他格式選項。 我發現以下 HTML 標記很有用: - 不間斷空格:`nbsp;` - div 居中對齊:`<divalign="center"></div>` 您可以使用大多數 HTML 標籤,但 GitHub Flavored Markdown 會過濾掉以下 HTML 標籤: - `<標題>` - `<文字區域>` - `<樣式>` - `<xmp>` - `<iframe>` - `<無嵌入>` - `<無框架>` - `<腳本>` - `<明文>` > 💡:要了解更多訊息,請參閱與 HTML 區塊相關的 [GitHub Flavored Markdown Spec](https://github.github.com/gfm/#html-blocks)。 ### 尋找靈感 為了幫助您入門,我建議您查看其他很棒的 GitHub 設定檔以獲取想法。你可以造訪 [awesome-github-profile-readme](https://github.com/abhisheknaiidu/awesome-github-profile-readme),我在製作個人資料時找到了靈感。 由於設定檔是開源的,您可以將一些好主意用於您的精彩設定檔! 您也可以查看[我的個人資料](https://github.com/kshyun28)以獲取一些想法。 😉 ### 新增徽章 要為您的個人資料加入徽章,您可以查看 [markdown-badges](https://github.com/Ileriayo/markdown-badges)。該存儲庫有多種徽章可供選擇,從程式語言到 Netflix 等串流平台。 如果您找不到所需的內容或想要建立自訂徽章,可以前往 [shields.io](https://shields.io/),這就是 [markdown-badges](https://github.com/Ileriayo/markdown-badges) 使用。 這是我在個人資料中使用 [markdown-badges](https://github.com/Ileriayo/markdown-badges) 的範例。 ![Markdown 徽章範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616185/GitHub%20Profile/badges-example_t6jyr6.png) ### 新增圖標 要在您的個人資料中加入“技能”或“技術堆疊”部分,我建議使用 [skill-icons](https://github.com/tandpfun/skill-icons),它提供漂亮的圖標。 如果您的圖示不受支持,您可以造訪 [simpleicons](https://simpleicons.org/),其中包含超過 2900 個流行品牌的 SVG 圖示。 這是一個範例,我在個人資料的技術堆疊部分使用了 [skill-icons](https://github.com/tandpfun/skill-icons)。 ![圖示範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616185/GitHub%20Profile/icons-example_nyo1sn.png) ### 使用表情符號 在 GitHub Flavored Markdown 中,您可以使用表情符號。要查看支援的表情符號的完整列表,您可以存取此 [emoji-cheat-sheet](https://github.com/ikatyang/emoji-cheat-sheet)。 如果您想自己取得支援的表情符號列表,可以使用 [GitHub 的 Emoji API](https://docs.github.com/en/rest/emojis/emojis#get-emojis)。 在瀏覽器上造訪 https://api.github.com/emojis 應該會顯示所有支援的表情符號的 JSON 回應。 ``` { "+1": "https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png?v8", "-1": "https://github.githubassets.com/images/icons/emoji/unicode/1f44e.png?v8", "100": "https://github.githubassets.com/images/icons/emoji/unicode/1f4af.png?v8", "1234": "https://github.githubassets.com/images/icons/emoji/unicode/1f522.png?v8", "1st_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f947.png?v8", "2nd_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f948.png?v8", "3rd_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f949.png?v8", "8ball": "https://github.githubassets.com/images/icons/emoji/unicode/1f3b1.png?v8", ... ``` 這是我在個人資料中使用表情符號的範例。 ![表情符號範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616185/GitHub%20Profile/emojis-example_yfzhef.png) ### 新增 GitHub 統計訊息 要為 GitHub 活動加入卡片和統計訊息,我建議使用 [github-readme-stats](https://github.com/anuraghazra/github-readme-stats)。您可以使用不同的版面配置和主題自訂統計卡。 以下是我將 GitHub 統計資料新增至我的個人資料的範例。 ![GitHub 統計資訊範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616186/GitHub%20Profile/github-stats-example_ndhxk3.png) ### 新增引號 在您的個人資料中加入隨機引用可以為訪客增添一抹亮色。我發現 [github-readme-quotes](https://github.com/PiyushSuthar/github-readme-quotes) 對於這樣做很有用。 這是我的個人資料上的樣子。我個人喜歡加入引號,為我的個人資料訪客提供一些價值。 ![報價範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704616185/GitHub%20Profile/quote-example_dfvjrh.png) ### 提高可存取性 自訂您的個人資料時,請確保它**可供盡可能多的人查看**。並非每個人都可以查看或載入圖像。有些人有殘疾,而有些人的網路連線速度很慢。 提高個人資料的[輔助功能](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility)的一種方法是向圖像加入描述性「替代文字」。 ``` <!-- Markdown Image --> ![Image Alt Text](image-source) <!-- HTML Image Tag --> <img alt="Image Alt Text" src="image-source" /> ``` 然後,要測試您的個人資料的可存取性,您可以嘗試停用網頁瀏覽器上的圖像載入。這是有關如何停用 Google Chrome 映像載入的[指南](https://www.wikihow.com/Disable-Images-in-Google-Chrome)。 這是我的個人資料在 Google Chrome 上停用圖片載入後的樣子。 ![GitHub 設定檔可存取性範例](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704717170/GitHub%20Profile/github-profile-accessibility_vixcg8.png) ### 更多想法 要為您的個人資料加入更多資訊圖表,我建議查看 [metrics](https://github.com/lowlighter/metrics)。這是 GitHub 上最受好評的存儲庫之一,其中包含“github-profile”主題,因此我不能忽略它。 然後我發現了這個漂亮的資源 [beautify-github-profile](https://github.com/rzashakeri/beautify-github-profile),在這裡您可以找到更多自訂個人資料的方法。 如果您也喜歡冒險,可以在[此處](https://github.com/topics/github-profile)探索「github-profile」主題。預設情況下,儲存庫會按星數排序。 請隨意探索有「github-profile」主題的儲存庫。您甚至可能會發現那些使用頻率不高但正是您所需要的。 ### GitHub 簡介 成就 雖然這與自訂 GitHub 設定檔的「README.md」無關,但我覺得有必要包含它。 如果您前往 GitHub 個人資料,您會注意到左側邊欄上有一個「成就」部分。 ![GitHub 個人資料成就](https://res.cloudinary.com/dlieqpdfd/image/upload/v1704632356/GitHub%20Profile/github-profile-achievements_tlqp3p.png) 收集這些成就很有趣,並且可以改善您的整體 GitHub 檔案。 要了解有關可用成就以及如何獲得這些成就的更多訊息,請查看 [GitHub 個人資料成就列表](https://github.com/Schweinepriester/github-profile-achievements)。 ## 結論 回顧一下,我們演練如何建立 GitHub 個人資料。然後我展示瞭如何使用 GitHub Flavored Markdown 和 HTML 格式化您的個人資料。之後,我分享了您可以從哪裡獲得個人資料的靈感。最後,我提供了有關如何自訂個人資料的提示和資源。 我希望這可以幫助您製作精彩的 GitHub 個人資料。我很想看看你能想出什麼辦法! 感謝您的閱讀,請隨時發表評論或與我聯繫[此處](https://linktr.ee/kshyun28)。 --- 原文出處:https://dev.to/kshyun28/how-to-make-your-awesome-github-profile-hog

創作 RawJS 之後,我再也沒有碰過 React。

早在 2012 年 10 月,TypeScript 0.8 就發布了。當時我正在開發一個中型 JavaScript 專案。它發布的那天,我閱讀了最初的規範,在玩了大約 10 分鐘後,我確信這將是未來,因此我開始用 TypeScript 重寫我的整個應用程式。相對於標準無型別 JavaScript 的好處是巨大的。 我對 [RawJS](https://www.squaresapp.org/rawjs/) 也有同樣的感覺。 [RawJS 是一個小型函式庫](https://github.com/squaresapp/rawjs),它使普通 JavaScript 應用程式開發更符合人體工學。它不僅僅是當今最新的 Web 框架,可以與 React、Vue、Svelte 或其他框架競爭。 RawJS 是不同的。 RawJS 直觀地說明了為什麼框架本身的整個前提可能有點誤導。它表明大多數應用程式最好使用普通 JS 並採用某些程式模式。 我知道這是一個非常大膽的聲明。但我懇請您研究一下 RawJS 誕生背後的心態和想法。 ## React 往往會破壞專案的複雜性 我不認為我說 React 太複雜是太過分了。畢竟,Svelte 正是因此而專門建立的。 React 會導致應用程式臃腫。舉個例子——我最近接到了一項任務,負責監督一個 React 應用程式的開發,該應用程式的複雜性已經失控。我最終扔掉了整個應用程式,並使用 RawJS 重建了整個應用程式,使用了一個從未接觸過 RawJS 的團隊,甚至根本沒有做過很多直接的 DOM 操作。幾週之內,團隊就加快了速度,現在該應用程式比以前的 React 應用程式小了約 90%。不,這不是一個錯字。 我們現在已經到了這樣一個階段:React 是「沒有人會因為購買 IBM 而被解僱」的選擇。問題是——人們「應該」因為購買 IBM 而被解僱。這是一個隱喻,指的是那些懶得做充分的需求分析、默認從眾心態、出於恐懼和懶惰而盲目跟隨大多數其他人正在使用的東西的人。 一旦您有幸使用 RawJS 加速的普通 JavaScript 開發應用程式,React 的過度複雜程度就會變得更加清晰。 RawJS 對 props、state、hooks、JSX、從特定基礎強制繼承(React.Component)、虛擬 DOM、自動資料綁定以及 React 所做的一切的必要性提出了質疑。 React 的膨脹及其施加的限制(例如禁止您直接編輯 DOM)都集中在試圖維護其虛擬 DOM 系統的完整性,我認為這是針對特定領域問題的解決方案對於facebook.com,那些精心建置的應用程式根本不具備。 所謂的直接 DOM 操作的效能劣勢被嚴重誇大了。事實上,如果做得正確,直接 DOM 操作通常會提高效能。這是因為您能夠精確控制 DOM 的更新方式。它允許您根據需要使用手術刀更新 DOM 的微小區域。虛擬 DOM 與此相反。它是一個生硬的工具,在大量 DOM 子樹上執行複雜的比較演算法,以便自動計算需要更新的內容。 自動資料綁定和反應性的有用性也被誇大了。假設您的程式碼組織良好,那麼建立您的應用程式以便 DOM 由於資料變更而更新似乎不會比僅建置應用程式以在必要時明確更新 DOM 所需的程式碼少。但與前者的區別在於,它給你強加了一個巨大的難以除錯的黑盒子,並迫使你遵守他們的官僚機構層。除非您組裝了一個精心建置的普通 JS 應用程式(例如使用 RawJS!),否則很難體會到擺脫這種情況的好處。 ## 為什麼沒有人談論匿名控制器類別? 匿名控制器類別(ACC)是一種需要引起更多關注的模式。它們是將普通 JavaScript 應用程式從雜亂無章轉變為連貫且美觀的關鍵想法之一。 ACC 的基本前提是建立一個與 DOM 中單一元素鬆散連接的物件,並且其垃圾收集生命週期等於所連接元素的生命週期。這是對繼承 HTMLElement 的一個進步,HTMLElement 是另一種選擇(但我不喜歡這種選擇,原因我將在另一篇文章中討論)。 考慮以下程式碼: ``` class SomeComponent { readonly head; constructor() { this.head = document.createElement("div"); this.head.addEventListener("click', () => this.click()); // Probably do some other stuff to this.head } private handleClick() { alert("Clicked!") } } ``` ACC 是建立單一 .head 元素(可能還有其他巢狀元素)、連接事件偵聽器、分配樣式等的類別。它們具有通常是事件處理程序或其他輔助方法的方法。然後實例化該元件,並將元件的 .head 元素加入 DOM: ``` const component = new SomeComponent(); document.body.append(component.head); ``` 該類別被視為“匿名”,因為一旦元件實例附加到 DOM,您就可以丟棄該實例。一旦元素從 DOM 中刪除並被垃圾回收,該類別的實例就會被垃圾回收,因為 DOM 是唯一擁有對它的引用的東西。例如: ``` class SomeComponent { readonly head; constructor() { this.head = document.createElement("div"); this.head.addEventListener("click', () => this.remove()); // Probably do some other stuff to this.head } private remove() { // Remove the component's .head element from the DOM, // which will by extension garbage collect this instance of // SomeComponent. this.head.remove(); } } ``` ACC 的優點在於它們基本上不會施加任何限制。他們可以繼承任何東西(或什麼都不繼承)。它們只是一個想法——您可以將它們塑造成您喜歡的樣子。 當然,在許多情況下您可能想要取得與特定元素關聯的 ACC。例如,想像一下迭代 this.head 元素的祖先元素,並取得與其關聯的 ACC 以呼叫某些公共方法。有一個名為 [HatJS](https://github.com/squaresapp/hatjs) 的輕量級程式庫,旨在改善使用 ACC 的人體工學。 **編輯:我是 HatJS 的作者。 「匿名控制器類別」是我發明的一個術語。這是在實驗過程中出現的模式,儘管我懷疑我是第一個發現它的人,因為這個概念非常明顯。就像 JSON 之前有名字一樣。您不需要將 HatJS 與 RawJS 一起使用。許多人正在建立普通的JavaScript 應用程式(或者對於迂腐的人來說是「普通的TypeScript 應用程式」),並且僅僅透過建立繼承自HTMLElement 的自訂元素,有效地將元素和控制器合併到同一個實體中,就取得了巨大的成功。我已經用這種方法建置了一些應用程式,並認為 ACC 更好,原因我會在以後的文章中介紹。** ## 改進 document.createElement() 具有令人驚訝的強大影響 儘管本文試圖為直接使用 DOM API 提供最有力的案例,但這些 API 絕對失敗的一個領域是使用屬性、樣式和事件附件來建立複雜的 DOM 層次結構。 DOM API 的這一部分非常冗長,如果沒有一些外部幫助,您的程式碼將比所需的長度長約 10 倍。這就是 RawJS 的用武之地。 RawJS 的設計正是為了一個目的。它使 document.createElement() 的人體工學性能提高了 10 倍。呼叫函數並取得 HTMLElement 實例的層次結構。它沒有任何其他作用。沒有奇怪的背景魔法。您可能不認為這聽起來很有影響力。但你的評估是錯的。 事實證明,在過去 15 年圍繞框架模式的構思中,我們不需要虛擬 DOM、反應性、資料綁定預編譯器或任何其他野生科學專案。我們需要匿名控制器類別模式和更好的方法來建立 HTMLElement 實例。 使用這兩種技術,我可以肯定地說,我永遠不會再故意使用 React 或任何其他競爭框架。這類框架根本無法提供超出 JavaScript 已經可以完成的功能,無法保證它們所施加的巨大權重和官僚作風。 那麼 RawJS 程式碼是什麼樣的呢?對 RawJS 建立者函數的呼叫遵循以下形式: ``` const htmlElement = raw.div(...parameters); ``` 強大的人體工學來自於可接受的參數的廣度(在 RawJS 中輸入為“Raw.Param”)。 參數可以是字串、數字、布林值、陣列、函數、DOM 節點實例、對 `raw.on("event", ...)` 的呼叫(建立可移植事件附件)以及幾乎任何其他內容。 RawJS 總是做你所期望的。 我不會重申使用 RawJS 來建立層次結構可以做的很棒的事情。快速入門對此進行了詳細介紹。 主要想法是,因為幾乎任何東西都可以是 Raw.Param,所以您可以建立迷你函數庫來產生 Raw.Params 列表並返回它們。由此可實現的程式碼重用水準是前所未有的。再說一次,除非你真正使用過它,否則很難欣賞它。我討厭與 LISP / 閉包進行比較,但還是有相似之處。 ## 我見過的最好的 CSS-in-JS 解決方案 如果不建構對應的 CSS,HTML 元素層次結建置構器有什麼用呢? RawJS 還擁有一流的 CSS-in-JS 解決方案,它可以完成我在任何其他解決方案中未見過的功能。例如,RawJS 完全支援僅限於特定元素的 CSS。 ``` const anchor = raw.a( // This constructs CSS within a global style sheet, // and the rules below will be scoped to the containing // anchor element. raw.css( ":focus", { outline: 0 }, ":visited": { color: "red" } ), raw.text("Hyperlink!") ); ``` 使用 RawJS 的 CSS-in-JS 解決方案還可以做很多其他事情。本文並不是 RawJS 教程,但如果您正在尋找此類內容,這裡有一個快速入門和一個演示應用程式。 ## 使用 DOM 作為狀態管理器實際上很好。 我們收到的一個常見的反對意見是,您將應用程式狀態儲存在哪裡?答案是使用 DOM 作為狀態管理器。 在你對此感到不寒而慄之前,請記住tailwind 如何率先提出在HTML 元素上刪除數百萬個類名的想法,有效地重新建立內聯CSS 的等價物,多年來,內聯CSS 一直被認為是一種反模式,但開發人員堅持它其實是很好,現在大家都在做嗎?同樣的想法也適用於使用 DOM 作為狀態管理器。 如今,每個人決定建立應用程式的方式都是從某種被視為「真相來源」的資料結構開始,然後您需要以某種方式將其笨拙地投影到 UI 中。以另一種方式做這件事被認為是“天真的”,甚至是反模式。但是,我想建議擁有兩個需要保持同步的獨立表示本身就是一種反模式。 嘗試使用某種框架以宣告方式將資料對應到 DOM 會導致複雜度大幅增加。這種技術的問題在於,對同一件事物有兩種不同的表示自然會比假設的只有一種這種表示的替代技術更加臃腫。 事實證明,如果您讓 ACC 接受輸入資料以便自行渲染,效果實際上會好得多。然後,您可以使用某種保存函數來檢查 DOM 的狀態以產生可保存的資料塊。這樣,您的事實來源不必與 DOM 同步,因為您的事實來源就是 DOM。 檢查以下程式碼範例: ``` class FormComponent { readonly head; private readonly firstNameInput; private readonly lastNameInput; constructor(firstName: string, lastName: string) { this.head = raw.form( this.firstNameInput = raw.input({ type: "text", value: firstName }), this.lastNameInput = raw.input({ type: "text", value: lastName }), raw.button( raw.text("Save"), raw.on("submit", () => this.save()) ) ); } private save() { const firstName = this.firstNameInput.value; const lastName = this.lastNameInput.value; SomeDatabaseSomewhere.save({ firstName, lastName }); alert("Saved!"); } } ``` 看?您只需將值儲存在 DOM 中即可。在本例中,我們使用文字輸入的值,但您也可以將資料儲存為 HTML 屬性、類別名稱或任何有意義的內容。 當然,在某些情況下,您需要儲存無法分解為字串、數字和布林值的狀態。我還看到一些狀態儲存在 ACC 內的屬性中的情況。做任何對你有用的事。 ## 元件之間的通信 在某些情況下,您可能需要更新多個元件以回應一項操作,或更簡單地在 ACC 之間發送訊息。 HatJS 可以幫助解決這個問題。 請記住,ACC 建立了一種隱藏的控制器層次結構。您擁有典型的 DOM 元素層次結構,但只有某些元素是 ACC 的頭元素。因此,這將建立自己的 ACC 層次結構,它是整個 DOM 元素層次結構的嚴格子集。 [HatJS](https://github.com/squaresapp/hatjs) 具有遍歷 ACC 層次結構的功能,並快速擷取可能位於或不位於元素後面的 ACC 實例。 ``` class ParentComponent { readonly head; constructor() { this.head = raw.div( new ChildComponent().head ); // Call Hat.wear() to define the object as a "hat" // and make it discoverable by HatJS Hat.wear(this); } callAlert() { alert("Hello!"); } } class ChildComponent { readonly head; constructor() { this.head = raw.div( raw.on("click", () => { // Hat.over finds the "Hat" (or the ACC) that exists // above the specified element in the hierarchy. // And passing ParentComponent gives you type-safe // tells HatJS what kind of component you're looking // for, and also gives you type-safe access to it. Hat.over(this, ParentComponent).callAlert() }) ); } } ``` 除了「Hat.over()」之外,還有「Hat.under()」、「Hat.nearest()」等方法來尋找 DOM 相對中可能存在或不存在的特定類型的其他 ACC到指定的元素。 ## 興奮起來! 那麼,我是否說服您啟動您的 React 應用程式並使用 RawJS 來重建您一生的工作?如果您想開始使用,請造訪 [這裡是 RawJS 網站](https://www.squaresapp.org/rawjs/)。 RawJS 的儲存庫是[此處](https://github.com/squaresapp/rawjs),HatJS 的儲存庫是[此處](https://github.com/squaresapp/hatjs) --- 原文出處:https://dev.to/paulgordon/after-using-rawjs-im-never-touching-react-again-or-any-framework-vanilla-javascript-is-the-future-3ac1

如何建立自己的SAAS業務

我從個人經驗中知道創辦 SAAS(軟體即服務)業務有多麼困難,特別是如果您是自籌資金的獨立創辦人。 在本文中,我將介紹我在如何建立 SAAS 業務方面的一些經驗教訓。 我無論如何都不是SAAS 業務方面的專家,但我在建立網路產品方面擁有十多年的經驗可供借鑒,因此希望這將幫助您避免我所犯的一些錯誤,並幫助您作為新技術創始人蓬勃發展。 ## 建立受眾群體 在開始編寫一行程式碼之前,您應該花一些時間坐下來起草一份 ICP,即「理想客戶檔案」和整體業務計劃。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7oqkhxk1sld88nz5mzp5.png) 大多數新創公司都會失敗——這是身為企業家的殘酷現實;這就是為什麼在實際建造產品之前首先制定可靠的計劃如此重要的原因。 一旦您心中有了理想的客戶檔案(不必是完美的),您就可以弄清楚您的受眾有多大以及如何吸引他們。 要確定您的受眾有多少,您可以: 1)研究你的競爭對手。了解他們服務的客戶數量以及他們的預計收入是多少。有很多工具可以實現這一點。例如:[類似網站](https://www.similarweb.com/)。 2) 瀏覽論壇和 Quora 等地方,了解顧客對競爭對手產品的評價。這將使您深入了解他們的痛點,並了解要重點關注哪些關鍵領域來建立更好的產品。 3)使用[wordstream](https://www.wordstream.com/)等工具進行關鍵字研究。您可以找到搜尋量和其他指標,以幫助您了解受眾群體的規模以及要定位的關鍵字。 現在您已經完成了一些紮實的研究,是時候決定您打算建立的產品是否有足夠的市場需求了。 解決這個問題的最佳方法是找出您的客戶常去的地方,並在該平台上建立某種存在。 專注於一兩個你喜歡的平台很重要,無論是 YouTube、Quora 還是 Facebook - 早期並不那麼重要。 我們的目標是選擇一個平台並堅持下去。您需要長期參與。不要只是為了廣告而參與該社區 - 您需要增加價值。這樣做將幫助您贏得目標社群的尊重和關注。 一旦你獲得了某種關注,你就可以開始與你的受眾對話,並弄清楚他們是否會對你的產品感興趣。 您應該在此階段建立一個登陸頁面或某種郵件清單來捕獲潛在客戶。 ## Django 非常適合 SAAS ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqlayi771vz78zwgidti.png) 在建立技術產品時,作為一名開發人員,我常常很想從頭開始建立所有內容或使用閃亮的新框架。 這是浪費時間。產品的第一次迭代應該是快速 MVP。您應該專注於盡快運送到市場(但不要在品質上妥協)。 Django 是建立 SAAS 產品的可靠選擇。它開箱即用,具有大量功能,可幫助您快速建立 MVP。 此外,除了使用像Django這樣的可靠框架之外,您還應該考慮使用付費或開源的樣板,這樣您就可以專注於核心產品的業務邏輯;而不是無聊的「管道」工作,例如建置:身份驗證、電子郵件工作流程、訂閱工作流程等。 > [SAAS Pegasus](https://www.saaspegasus.com/?via=plexcorp) 是一個非常乾淨且易於使用的 SAAS 樣品板,可以加快您的開發週期。它具有幾乎每個 SAAS 業務都需要的大量功能,可以為您節省數小時甚至數週的開發時間。 [付費促銷] https://www.youtube.com/embed/5mNBYIgLRaU?si=9hXBi1o7eHPHPuPc 您也可以查看我之前寫的一篇文章,其中更深入地解釋了為什麼我認為Django 是Web 後端的最佳選擇[此處](https://dev.to/kwnaidoo/why-django-is-probously-the-best-web-framework-5aan)。 ## 免費套餐 始終有某種免費套餐。這使得進入門檻較低。除非您擁有令人興奮且無需動腦筋的產品,否則很難在客戶第一次登陸您的目標網頁時轉換他們。 讓他們使用免費套餐版本,然後透過電子郵件逐漸教育他們為什麼應該購買你的產品要容易得多。 有多種方法可以做到這一點。我經常發現擁有開源版本或提供免費試用版效果最好。 如果市場相對較大,您也可以考慮產品的永久免費版本,但在使用上有一些限制。 但要小心「免費增值」模式。您需要確保您的定價有足夠的空間,以便您可以支付免費套餐客戶的費用。通常,最好提供試用。 ## 擴大收入 您最終將陷入停滯狀態,並且吸引新客戶將變得越來越困難。 擴大收入為您提供了向現有客戶追加銷售的額外收入潛力,因為說服現有客戶花更多錢比吸引全新客戶要容易得多。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqc160co2w83zpf5auuw.png) **什麼是擴張收入?** 擴展收入只是一項附加服務,例如客戶可以附加到現有訂閱的附加服務。 這通常需要加入額外的用戶席位、購買簡訊/電子郵件積分,或者在 CRM 的範例中,客戶可以支付額外的「HR」模組費用。 ## 隨處列出您的產品 如您所知,內容為王。早期預算有限 - 很難為您的目標網頁或網站帶來流量。 您可以在以下一些免費網站上列出您的產品: 1. [producthunt.com](https://producthunt.com) 2. [替代](https://alternativeto.net/) 3. [殺手新創公司](https://www.killerstartups.com/) 4. [測試版清單](https://betalist.com/) 5. [Reddit](https://www.reddit.com/) - 這裡要小心,Reddit 用戶討厭廣告。 6. [Quora](https://www.quora.com/) 7. [AppSumo](https://appsumo.com/) 8. [Pitchwall](https://pitchwall.co/) 9. [Pinterest](https://pinterest.com/) 10. [Betabound](https://www.betabound.com/) 11. [啟動函式庫](https://startupbase.io/) 12. [獨立駭客](https://www.indiehackers.com/) 13. [設計師新聞](https://www.designernews.co/) 14. [SaasSHub](https://www.saashub.com/) 15. [啟動下一步](https://www.launchingnext.com/) ## 向最好的創辦人學習 當你學習如何程式設計時,特別是如果你像我一樣並且大部分是自學的,最好的學習方法是尋找你正在學習的任何語言的專家並跟隨他們,購買他們的課程等等.... .. 同樣,還有很多成功的創辦人。如果你想變得更好並學習最佳實踐,那麼尋找這些創辦人並向他們學習是作為創辦人成長的好方法。 這是我經常關注的專家創辦人名單(排名不分先後): - [TK 卡德](https://www.youtube.com/@TKKader)。 TK 創辦了許多公司,並且是 SAAS 創辦人的輔導專家。我發現他對概念的三步驟分解非常令人耳目一新且易於理解。 - [尼爾帕特爾](https://www.youtube.com/@neilpatel)。尼爾是一位專業行銷人員,通常會介紹一些有關如何圍繞品牌建立內容的精彩技巧。 - [西蒙霍伊伯格](https://www.youtube.com/@SimonHoiberg)。 Simon 以一種非常酷且有趣的方式向開發人員解釋先進的業務概念。 - [Rob Walling](https://www.youtube.com/@MicroConf/videos)。 Rob 的 YouTube 頻道出色地解釋了作為創辦人需要學習的所有核心概念。他還寫了幾本有關該主題的書,並參與資助許多新的新創公司。 ## 結論 建立 SAAS 業務非常困難,許多人一開始就失敗了——包括我自己。失敗沒關係——它會讓你變得更堅強,並教你重要的教訓,從長遠來看,這將幫助你成長。 我寫這篇文章的目的是分享我的旅程中的一些基本知識,以幫助您作為開發人員成長到這個新領域。 快樂建設!祝您下一個最佳想法一切順利。 --- 原文出處:https://dev.to/kwnaidoo/how-to-build-your-own-saas-business-2op0

Docker 絕對初學者

Docker 是一個工具,允許開發人員將他們的應用程式及其所有依賴項打包到一個容器中。然後,這個容器就可以輕鬆地在任何安裝了 Docker 的機器上傳輸和執行,而不必擔心環境的差異。這就像是打包和執行軟體的標準化方式。 **容器是什麼?** ![Docker 容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imsgbstga86vnxjwgebr.png) 容器就像一個小包,其中包含程式執行所需的一切,可以輕鬆地在不同電腦上移動和執行,而不會造成任何麻煩。 最酷的部分是這個迷你電腦(容器)就像一個披著斗篷的超級英雄。它可以在任何電腦上執行,無論它們有多麼不同,因為它自帶特殊的環境。這是一種保持軟體井然有序的方式,並確保它無論在哪裡都能以相同的方式運作。 ![容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1z5zcued8ya2onerpzwt.png) **為什麼我們需要 Docker?** 1. **一致性:** Docker 確保軟體在您的電腦、您朋友的電腦或任何電腦上以相同的方式運作。它使事情保持一致。 2. **可移植性:** 您可以將您的軟體及其朋友打包到 Docker 容器中,並且它可以移動到任何地方。這就像將您的遊戲及其所有規則放在手提箱中並在朋友家中玩。 3. **隔離:** Docker 容器就像小氣泡。氣泡內發生的事只會留在氣泡內。這意味著容器中的一個程式不會幹擾容器外的另一個程式。 4. **效率:** Docker有助於節省電腦資源。您可以讓許多容器在同一台電腦上執行,而不會相互妨礙,而不是讓一整台電腦只用於一個程式。 5. **速度:** Docker 讓啟動、停止和共享軟體變得快速、輕鬆。這就像打開和關閉遊戲機一樣 - 快速而簡單。 **什麼是 Docker 映像?** ![Docker 映像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/817t1rsad728snnighkj.png) Docker 映像像是程式及其運作所需的所有內容的快照。它是一個打包版本,包括程式碼、工具和設置,就像包含所有成分的餅乾食譜的快照一樣。 **圖像是配方,容器是當您按照該配方實際製作和執行程序時所得到的。** **一些基本的 Docker 命令。** ![基本 Docker 指令](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xq1pwt8896lvster3ppl.png) 1. **`docker執行nginx`** - 此命令告訴 Docker 使用「nginx」映像執行容器。這就像告訴 Docker 啟動一個預製程式的新實例(nginx,它是一個 Web 伺服器)。 2. **`docker ps`** - 顯示正在執行的容器的清單。這就像檢查當前正在執行哪些程式。 3. **`docker ps -a`** - 顯示所有容器的列表,包括已停止的容器。這就像檢查您執行過的所有程式的歷史記錄。 4. **`docker stopsilly_sammet'** - 停止名為「silly_sammet」的正在運作的容器。這就像關閉當前正在執行的程式。 5. **`docker rmsilly_sammet'** - 刪除名為「silly_sammet」的已停止容器。這就像丟掉你不再需要的程式的指令一樣。 6. **`docker 映像`** - 列出您擁有的所有 Docker 映像。這就像查看您可以執行的所有不同程式的選單一樣。 7. **`docker rmi nginx`** - 刪除“nginx”圖像。這就像刪除您不想再使用的程式的配方。 8. **`docker拉nginx`** - 從網路下載「nginx」映像。這就像從食譜中獲取新食譜一樣。 9. **`docker 執行 ubuntu sleep 5`** - 使用「ubuntu」映像檔執行容器並使其休眠 5 秒。這就像啟動一個程序,只是等待一小會兒,然後就停止了。 10. **`docker exectracted_mcclintock cat /etc/hosts`** - 在名為「distracted_mcclintock」的正在執行的容器內執行命令。這就像在食譜書中偷看特定頁面一樣。 11. **`docker run -d kodekloud/simple-webapp`** - 從「kodekloud/simple-webapp」鏡像以分離模式執行容器。這就像啟動一個程式並讓它在背景執行。 12. **`docker Attach a043d`** - 將您的終端附加到 ID 為「a043d」的正在執行的容器。這就像跳入正在執行的程式來查看發生了什麼。 **一些 Docker 概念:** 1. **使用標籤執行:** - 標籤就像程式的版本。它指定您要執行哪個版本。 - 範例程式碼:`docker run nginx:latest` - 這將執行最新版本的 Nginx 程式。 2. **使用標準輸入執行:** - STDIN 就像在鍵盤上打字一樣。有些程式需要您的輸入。 - 範例程式碼:`docker run -i -t ubuntu` - 這會在 Ubuntu 容器內執行互動終端,讓您可以鍵入命令。 3. **使用連接埠映射執行:** - 連接埠就像門。程式使用它們與外界進行通訊。 - 範例程式碼:`docker run -p 8080:80 nginx` - 這將執行 Nginx,並打開電腦連接埠 8080 上的門,將其連接到容器的連接埠 80。 4. **使用磁碟區映射執行:** - 磁碟區就像共用資料夾。它們讓您可以將東西存放在容器之外。 - 範例程式碼:`docker run -v /your/local/folder:/container/folder nginx` - 這將執行 Nginx 並將電腦上的資料夾連接到容器內的資料夾。 5. **檢查容器:** - 檢查就像仔細檢查正在執行的程式。 - 範例程式碼:`docker檢查container_name` - 這為您提供有關正在執行或已停止的容器的詳細資訊。 6. **容器日誌:** - 日誌就像日記。他們記錄程式正在做什麼。 - 範例程式碼:“docker 日誌容器名稱” - 這會向您顯示特定容器的日誌或活動。 ##環境變數 環境變數就像程式用來尋找重要資訊的便利筆記,有點像是程式可以理解和更好工作的秘密訊息! 1. **Python腳本(app.py)中的環境變數:** - 假設您有一個用 Python 寫的程式 (app.py)。您可能想要在不更改程式碼的情況下自訂它。您可以使用環境變數。 - 範例程式碼(app.py): ``` import os app_color = os.getenv("APP_COLOR", "default_color") print(f"The app color is {app_color}") ``` - 正常運作腳本:`python app.py` - 以特定顏色執行:`export APP_COLOR=blue; python 應用程式.py` 2. **在 Docker 中使用 ENV 變數:** - Docker 容器也可以使用環境變數。這就像是向容器內的程式發出指令。 - 範例程式碼: - `docker run -e APP_COLOR=green simple-webapp-color` - 這會執行 Docker 容器(`simple-webapp-color`)並將環境變數 `APP_COLOR` 設為「綠色」。 3. **檢查環境變數:** - 有時,您會想要檢查正在執行的容器正在使用哪些環境變數。 - 範例程式碼:`docker檢查blissful_hopper` - 此命令提供有關名為“blissful_hopper”的容器的詳細訊息,包括其環境變數。 簡單來說,環境變數就像程式(或 Docker 容器)可以讀取以了解如何行為的小註釋。您可以在執行程式之前設定這些註釋,程式將使用它們來自訂自身。第二個範例中的「export」指令就像在執行程式之前寫一條註釋,告訴它如何運作。 “docker Inspect”指令就像是在容器內部查看它有什麼註解。 ## Docker 映像 **Docker 檔案:** Dockerfile 就像是 Docker 建立映像的一組指令。這就像是烤蛋糕的食譜。 ``` # Use the Ubuntu base image FROM Ubuntu # Update apt repository RUN apt-get update # Install dependencies using apt RUN apt-get install -y python # Install Python dependencies using pip RUN pip install flask RUN pip install flask-mysql # Copy source code to /opt folder COPY . /opt/source-code # Set the working directory WORKDIR /opt/source-code # Specify entry point to run the web server ENTRYPOINT ["flask", "run"] ``` **建立自己的圖像的步驟:** 1. 使用上述內容建立一個名為「Dockerfile」的檔案。 2. 將其保存在與原始碼相同的目錄中。 **建置 Docker 映像:** 在終端機中執行以下命令: ``` docker build -t your-image-name . ``` 此命令告訴 Docker 使用目前目錄中的 Dockerfile (`.`) 建置映像,並使用您選擇的名稱對其進行標記 (`-t your-image-name`)。 **分層架構:** ![分層架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i91e79dg61wnbbfp62m.png) - 將 Docker 映像視為一個分層蛋糕。 Dockerfile 中的每個指令都會在映像上新增一層。 - 圖層可重複使用。如果您變更程式碼中的某些內容,Docker 只會重建受影響的層,從而提高效率。 **Docker 建置輸出:** - 當您建置映像檔時,Docker 會顯示流程中的每個步驟。如果發生故障,它會給您錯誤訊息。 **你可以容器化什麼?** - 幾乎所有東西!應用程式、服務、資料庫、網站,基本上任何軟體都可以容器化。 - 這就像將您的軟體放入一個盒子中,以便它可以在任何地方執行而不會造成麻煩。 ## 什麼是 Docker CMD 與 ENTRYPOINT **Docker 中的`CMD`:** - 將 CMD 視為啟動容器時程式執行的預設操作。 - 這就像說,“嘿,當你執行這個容器時,默認執行此操作。” - 範例:`CMD ["flask", "run"]` 表示當容器啟動時,它會自動執行 Flask Web 伺服器。 **CMD 範例:** ``` FROM alpine CMD ["sleep", "5"] ``` 在此範例中,當您使用此映像執行容器時,它會自動休眠 5 秒。 **Docker 中的`ENTRYPOINT`:** - 將 ENTRYPOINT 視為容器所做的主要事情。就好像boss的命令一樣。 - 它設定一個預設應用程式在容器啟動時執行,但您仍然可以根據需要覆蓋它。 - 範例:`ENTRYPOINT ["flask", "run"]` 表示容器主要用於執行 Flask Web 伺服器,但如果需要,您仍可新增更多指令。 **入口點範例:** ``` FROM alpine ENTRYPOINT ["sleep"] CMD ["5"] ``` 在這裡,主要目的是睡眠,如果您願意,您仍然可以覆蓋睡眠持續時間。 在這兩種情況下,容器在啟動時只會休眠幾秒鐘。主要區別在於如何提供參數以及它們是否可以輕鬆覆蓋。 CMD 就像在說,“這是默認要做的事情”,而 ENTRYPOINT 就像在說,“這是主要要做的事情,但如果你願意,你可以稍微調整一下。”它們都有助於定義容器啟動時執行的操作。 ## Docker 中的網路: Docker 網路幫助容器(程式)相互通信,確保它們可以順利地協同工作。 **預設網路:** - Docker 建立預設網路供容器通訊。 - 範例程式碼:`docker run ubuntu --network=host` - 這使用主機網路執行 Ubuntu 容器,這意味著它與主機共享網路命名空間。 **使用者定義的網路:** - 您可以建立自己的網路以更好地組織和控制。 - 範例程式碼: ``` docker network create --driver=bridge --subnet=182.18.0.0/16 custom-isolated-network ``` - 這將建立一個名為「custom-isolated-network」的使用者定義的橋接網絡,具有特定的子網。 **上市網路:** - 您可以查看您擁有的所有網路。 - 範例程式碼:`docker network ls` **檢查網路:** - 您可以檢查特定網路的詳細資訊。 - 範例程式碼:`docker網路檢查blissful_hopper` - 這顯示有關名為「blissful_hopper」的網路的詳細資訊。 **嵌入式 DNS:** - Docker 有一個內建的 DNS 系統,供容器透過名稱相互查找。 - 範例程式碼:`mysql.connect(mysql)` - 這可能是程式碼中的一行,其中名為「mysql」的服務使用 Docker 的 DNS 連接到另一個名為「mysql」的服務。 ## Docker 儲存: ![Docker 儲存](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i54a6m0o1tb0812xbjk.jpg) Docker 儲存就像使用容器時決定將資料保存在哪裡一樣。您可以將它們保留在容器內,使用磁碟區在容器之間共用它們,或將它們儲存在容器外部以妥善保管。 **Docker中的檔案系統:** - Docker 使用分層架構來建立映像。 Dockerfile 中的每個指令都會在檔案系統中新增一個新圖層。 ``` # Dockerfile FROM Ubuntu RUN apt-get update && apt-get install -y python RUN pip install flask flask-mysql COPY . /opt/source-code WORKDIR /opt/source-code ENTRYPOINT ["flask", "run"] ``` - Dockerfile 中的層: - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 - 第 6 層:容器層 **影像圖層:** - 當您建立 Docker 映像時,它由唯讀層組成。每一層代表影像的變化或加入。 - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 ``` # Build the Docker image docker build -t mmumshad/my-custom-app . ``` **容器層:** - 當您執行 Docker 容器時,會在唯讀映像層上方新增一個讀寫層。該層特定於正在執行的容器。 - 第 6 層. 容器層 ``` # Run the Docker container docker run mmumshad/my-custom-app ``` **數量:** - 卷是一種在容器外部保存資料的方法。它們就像外部記憶體。 ``` # Create a Docker volume docker volume create data_volume # Use the volume in a container docker run -v data_volume:/var/mysql mysql ``` - 您也可以使用「-v」將特定目錄從主機掛載到容器: ``` # Mount a host directory to a container directory docker run -v /path/on/host:/var/mysql/mysql -d mysql ``` - docker run --mount 指令用於將主機上的特定目錄或檔案掛載到正在執行的 Docker 容器中。 ``` docker run --mount type=bind,source=/mysql,target=/var/mysql mysql ``` **儲存驅動程式:** - Docker 使用儲存驅動程式來管理資料的儲存和存取方式。一些常見的儲存驅動程式包括 AUFS、ZFS、BTRFS、Device Mapper、Overlay 和 Overlay2。 [在 Docker 管理資料](https://docs.docker.com/storage/) [關於儲存驅動程式](https://docs.docker.com/storage/storagedriver/) [卷](https://docs.docker.com/storage/volumes/) ## Docker 組合 ![Docker Compose](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjyro6o2844s2or1b83c.jpeg) Docker Compose 是一個方便的工具,可幫助您輕鬆執行和連接不同的軟體服務,就好像它們都是同一事件的一部分一樣。 **Docker Compose 基礎:** 1. **執行單一容器:** - 通常,您可以像這樣執行單獨的 Docker 容器: ``` docker run mmumshad/simple-webapp docker run mongodb docker run redis:alpine docker run ansible ``` 2. **Docker 撰寫文件(`docker-compose.yml`):** - Docker Compose 允許您在一個簡單的檔案中定義所有這些服務: ``` # docker-compose.yml version: '3' services: web: image: 'mmumshad/simple-webapp' database: image: 'mongodb' messaging: image: 'redis:alpine' orchestration: image: 'ansible' ``` - 此檔案描述您要執行的服務(「web」、「database」、「messaging」、「orchestration」)、它們各自的映像以及任何其他配置。 3. **使用 Docker Compose 執行:** - 要一起啟動所有這些服務: ``` docker-compose up ``` - Docker Compose 負責啟動「docker-compose.yml」檔案中定義的所有容器。 4. **使用 Docker Compose 建置:** - 您也可以使用 Docker Compose 建置映像: ``` docker-compose build ``` - 此指令建置「docker-compose.yml」檔案中指定的映像。 **執行連結容器:** - 如果您要透過連結執行單一容器: ``` docker run -d --name redis redis docker run --name voting-app -p 5000:80 --link redis:redis voting-app docker run --name result-app -p 5001:80 --link db:db result-app docker run -d --name worker --link db:db --link redis:redis worker ``` - 在 Docker 中撰寫: ``` # docker-compose.yml version: '3' services: vote: image: 'voting-app' ports: - '5000:80' links: - 'redis:redis' result: image: 'result-app' ports: - '5001:80' links: - 'db:db' worker: image: 'worker' links: - 'db:db' - 'redis:redis' db: image: 'db' redis: image: 'redis' ``` Docker Compose 可讓您在單一檔案中描述整個應用程式堆疊,從而輕鬆管理、執行和連接不同的服務。這就像在一份計劃中寫下活動的所有任務,然後 Docker Compose 為您處理設定。 [Docker Compose 概述](https://docs.docker.com/compose/) [Docker 撰寫文件](https://docs.docker.com/engine/reference/commandline/compose/) ## Docker 註冊表 ![Docker 註冊表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzflp82qyg36y8fcf8k8.png) Docker 註冊表是人們儲存和分享 Docker 映像的地方,使其他人可以輕鬆使用和執行他們的軟體。它就像一個大型線上程式庫,可以輕鬆下載並在不同電腦上使用。 **Docker 註冊表基礎知識:** 1. **公共登記處:** - Docker 映像可以在 Docker Hub 等公共註冊表中儲存和共用。 - 例: ``` docker pull nginx ``` 2. **私人登記處:** - 有時,您可能希望將圖像保存在您自己的私人註冊表中。 - 例: - 登入私人註冊表: ``` bash docker login private-registry.io ``` - 從私有註冊表中的映像執行容器: ``` docker run private-registry.io/apps/internal-app ``` 3. **部署您自己的私有註冊表:** - 您可以為您的團隊或公司部署自己的私人註冊表。 - 例: - 在您的電腦上執行私有註冊表: ``` docker run -d -p 5000:5000 --name registry registry:2 ``` - 為私人註冊表標記您的圖像: ``` bash docker image tag my-image localhost:5000/my-image ``` - 將映像推送到您的私人註冊表: ``` bash docker push localhost:5000/my-image ``` - 從您的私人註冊表中提取映像: ``` bash docker pull localhost:5000/my-image ``` 4. **從遠端私有註冊表中提取:** - 您也可以使用 IP 位址或網域從遠端私有註冊表中提取映像。 - 例: ``` docker pull 192.168.56.100:5000/my-image ``` Docker 註冊表就像一個儲存空間,人們在其中保存和共享 Docker 映像。您可以將公用註冊表用於廣泛使用的映像,也可以根據您的特定需求設定自己的私人註冊表。它就像一個用於共享和儲存軟體藍圖(圖像)的特殊庫。 ## Docker 引擎 ![Docker 引擎](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hc6r4dvo9kg9xqgwsrbb.jpg) 想像一下,你有一個魔盒(Docker Engine),可以為你執行和管理各種程式(容器)。 Docker Engine 就像是這個魔盒的大腦。 1. **Docker 守護程式:** - 守護程式就像魔法盒的看門人。它始終在那裡,隨時準備接受指示並確保一切順利進行。 2. **REST API:** - 將 REST API 視為一組允許您與魔盒對話的規則。它就像你和守護程式用來溝通的語言。你告訴守護程式要做什麼,它會理解,因為你們說的是同一種語言。 3. **Docker CLI(命令列介面):** - Docker CLI 就像是用來命令守護程式的魔杖。您輸入指令,守護程式就會按照您的指示進行操作。這就像說「Abracadabra」就能讓事情發生。 **連線到遠端 Docker 引擎:** 連接到遠端 Docker 引擎可讓您控制另一台機器上的容器,且設定約束可確保容器僅使用指定的資源。 1. **Docker主機IP:** - 您可以使用 IP 位址和連接埠連接到不同電腦上的 Docker 引擎。 - 例: ``` docker -H=remote-docker-engine:2375 run nginx ``` - 這告訴您的本機 Docker CLI 與遠端 Docker 引擎進行通訊。 2. **有約束地執行容器:** - Docker 允許您設定容器的資源限制,例如 CPU 和記憶體限制。 - 例: ``` docker run --cpus=0.5 ubuntu docker run --memory=100m ubuntu ``` - 這些指令限制容器僅使用半個 CPU 核心和 100 MB 記憶體。 當然,讓我們簡化一下PID命名空間的概念: **命名空間PID:** PID 命名空間可讓您為容器中的進程(如程式或任務)建立單獨的區域,因此它們有自己的一組「票號」(進程 ID),不會與容器外的進程發生衝突。 **範例程式碼:** 1. **使用主機 PID 命名空間執行容器:** - 這表示容器與主機共用相同的「票號」。 ``` docker run --pid=host ubuntu ``` 2. **執行具有隔離 PID 命名空間的容器:** - 這表示容器有自己的一組獨立於主機的「票號」。 ``` docker run --pid=container ubuntu ``` 在第一個範例中,容器與進程交互,就好像它與主機位於同一空間中一樣。在第二個範例中,容器有自己的進程隔離空間。這就像在大型活動中擁有一個私人區域,您的團隊有自己的一套票號,讓您可以獨立於活動的其餘部分進行操作。 **容器化概念:** 1. **進程 ID 命名空間:** - 容器有自己獨立的流程 ID (PID) 空間,因此容器內的流程與容器外的流程是分開的。 - 例: ``` docker run --pid=host ubuntu ``` - 此指令使用主機的 PID 命名空間來執行容器,因此它共用相同的程序。 2. **網路命名空間:** - 容器也有自己獨立的網路命名空間,這意味著它們可以有自己的網路配置。 - 例: ``` docker run --net=host nginx ``` - 此指令使用主機的網路命名空間來執行容器。 3. **Unix分時命名空間:** - 此命名空間允許容器擁有自己的時間視圖,與主機和其他容器分開。 - 例: ``` docker run --uts=host ubuntu ``` - 此指令使用主機的 Unix 時間共用命名空間來執行容器。 4. **進程間掛載命名空間:** - Mount命名空間隔離檔案系統,讓容器擁有自己的檔案系統視圖。 - 例: ``` docker run --mount=type=bind,source=/host/folder,target=/container/folder ubuntu ``` - 此指令將主機中的資料夾安裝到容器中。 當然!我們來簡化一下cgroup的概念: **C組:** cgroup(控制組的縮寫)可協助在不同進程或容器之間管理和分配系統資源,例如 CPU 和記憶體。它們確保沒有任何一個進程或容器耗盡所有可用資源,從而保持一切平衡。 **範例程式碼:** 1. **使用 Cgroup 設定 CPU 限制:** - 這就像說聚會上的每位客人只能吃一定數量的食物。 ``` docker run --cpus=0.5 ubuntu ``` - 這限制容器僅使用一半的 CPU 核心。 2. **使用 Cgroup 設定記憶體限制:** - 這就像說每位客人只能在舞池上佔據一定的空間。 ``` docker run --memory=100m ubuntu ``` - 這限制容器僅使用 100 MB 記憶體。 [Docker 引擎概述](https://docs.docker.com/engine/) [使用 Docker Engine API 進行開發](https://docs.docker.com/engine/api/) [執行時指標](https://docs.docker.com/config/containers/runmetrics/#control-groups) ## Linux容器與Windows容器的概念: **Linux 容器(預設):** Linux 容器是一種打包和執行軟體及其所需一切的方法,它們最適合執行 Linux 的電腦。 **Windows 容器:** Windows 容器是一種打包和執行軟體的方式,就像 Linux 容器一樣,但它們設計用於執行 Windows 的電腦。 **Windows 容器基礎:** 1. **集裝箱類型:** - Windows 容器有兩種主要類型:Windows Server Core 和 Nano Server。 - **Windows Server Core:** 將其視為功能更齊全的容器,適合各種應用程式。 - **Nano Server:** 將其視為一個輕量級容器,專為特定的、簡約的用例而設計。 2. **基礎鏡像:** - 基礎映像就像是建立容器時開始使用的空白畫布。 - 例: ``` docker pull mcr.microsoft.com/windows/servercore:ltsc2019 ``` - 此指令擷取 Windows Server Core 基礎映像。 - 例: ``` docker pull mcr.microsoft.com/windows/nanoserver:ltsc2019 ``` - 此命令提取 Nano Server 基礎映像。 3. **支援的環境:** - Windows 容器可以在特定版本的 Windows 作業系統上運作。 - 例: - 您可以在 Windows Server 2016 上執行 Windows 容器。 - 例: - 您可以在 Windows 10 專業版和企業版上執行 Windows 容器,並使用 Hyper-V 隔離容器進行額外隔離。 ## 容器編排 ![容器編排](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lyleybojw3xmr2dgfep.png) 容器編排是一種管理和協調多個容器的方法,確保它們無縫協作來執行應用程式,就像一個超級智能的管理器確保所有機器人一起工作來建置完美的塔一樣。 **為什麼要編曲?** 1. **多項任務,一名經理:** - 想像一下您有許多機器人(容器)執行不同的工作。編排就像有一位超級聰明的經理(編排者),他告訴每個機器人該做什麼,並確保一切順利進行。 2. **一致性:** - 編排確保所有任務每次都以相同的方式完成。這就像為您的機器人提供了一套要遵循的指令,以確保其行為的一致性。 3. **效率:** - 編排有助於優化任務,確保資源(如時間和材料)有效利用。這就像經理確保所有機器人一起工作而不浪費能源。 4. **縮放比例:** - 當您需要完成更多工作時,編排可以輕鬆建立額外的機器人(容器)。這就像當有很多事情需要完成時神奇地召喚更多機器人來提供幫助。 5. **可靠性:** - 編排確保任務可靠地完成,即使機器人(容器)出現故障。這就像製定備份計劃來確保無論如何都能完成工作。 6. **協調:** - 編排協調任務,確保機器人無縫協作。這就像經理確保每個機器人都知道自己的角色並協作以實現總體目標。 **容器編排程式碼:** ``` # Create a Docker service with 100 replicas (instances) of a Node.js application docker service create --replicas 100 --name my-nodejs-app nodejs ``` 在這個例子中: - `docker service create`:該指令告訴 Docker 建立一個服務,該服務是一組正在執行的容器。 - `--replicas 100`:此標誌指定您需要 100 個服務實例(副本)。 - `--name my-nodejs-app`:此標誌為您的服務提供名稱,在本例中為「my-nodejs-app」。 - `nodejs`:這是 Node.js 應用程式的圖片或配方。這就像是烘焙紙杯蛋糕的藍圖。 因此,這段簡單的程式碼就像告訴您神奇的廚師助手 (Docker Swarm) 建立 Node.js 應用程式的 100 個副本,確保您有大量容器正在執行並準備好提供服務。 ## Docker 群 ![Docker Swarm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5noonjo2ikyrp90kc38.png) Docker Swarm 是一個工具,可以幫助協調和管理一組電腦(節點)作為一個機器人團隊一起工作,使它們能夠以協調的方式部署和執行多個容器。這就像有一個首席機器人經理,確保所有單一機器人一起建造出偉大而令人驚嘆的東西。 **設定 Docker Swarm:** 1. **群組管理器:** - 想像你有一個首席機器人(Swarm Manager)來領導團隊。主機器人決定需要做什麼,並指導其他機器人(節點)如何協同工作。 ``` # Initiate Docker Swarm on the Swarm Manager docker swarm init ``` 2. **節點工作人員:** - 現在,您的工作機器人(節點工作人員)已準備好加入團隊。 Swarm Manager 共享一個特殊的程式碼(令牌)來邀請他們一起工作。 ``` # Join a Node Worker to the Docker Swarm docker swarm join --token <token> <Swarm Manager IP> ``` **Docker Swarm 服務:** 現在您已經有了一個協調的團隊,您想要建立一項服務,例如與您的機器人團隊一起建造塔: ``` # Create a Docker service (a group of containers) with 3 replicas (instances) docker service create --replicas 3 --network frontend --name my-web-server my-web-image ``` - `--replicas 3`:此標誌告訴 Docker 建立服務的三個實例(副本)。 - `--network frontend`:此標誌指定您的服務屬於名為「frontend」的網路。 - `--name my-web-server`:這會為您的服務命名,在本例中為「my-web-server」。 - `my-web-image`:這是您的網頁伺服器的圖片或藍圖。這就像建造塔樓的配方。 您建立了一個由隊長(Swarm Manager)和工作機器人(Node Workers)組成的機器人團隊。然後,您指示他們建立一個執行您的 Web 伺服器應用程式的服務(容器群組)。主機器人確保建立 Web 伺服器的三個副本並將其連接到「前端」網路。這就像有一個首席機器人經理在工作機器人的幫助下監督多個塔(貨櫃)的建造。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytitz2wt4jsufejox9yx.gif) **好的,這就是本文的內容。** 另外,如果您對此或其他任何問題有任何疑問,請隨時在下面的評論中或在 [Instagram](https://www.instagram.com/_abhixsh/) 、[Facebook](https://www.facebook.com/abhi.haththakage/) 或[Twitter](https://twitter.com/abhixsh)。 感謝您閱讀這篇文章,我們下一篇再見! ❤️ --- 原文出處:https://dev.to/abhixsh/docker-for-the-absolute-beginner-3h1p

🌌 31 個開源庫 + Good First Issues(開始你的旅程)⛰️

為優秀的開源庫做出貢獻是建立您的作品集並加入令人驚嘆的社群的最佳方式。 我編譯了 31 個開源程式庫和一些好的第一期,以幫助推動您的旅程。 不要忘記加星號並支持這些🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/637xnt75fuwgfeaasdke.gif) --- #AI最愛🦾: ### 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 應用內 AI 聊天機器人與 AI 文字區域 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox3mv8nmqzot6m4kvkdh.png) 開源平台,用於使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。 CopilotPortal:應用程式內人工智慧聊天機器人,可以「查看」當前應用程式狀態並採取行動。 CopilotTextarea:AI 驅動的 <textarea /'> 替換。具有自動完成、插入和生成功能。 ###[好第一期:](https://github.com/CopilotKit/CopilotKit/issues/62) ``` Gracefully fail if CopilotProvider is omitted The bug: Virtually every CopilotKit functionality depends on a CopilotContext provided by the CopilotProvider. e.g. CopilotTextarea autocompletions, chatbot, etc. However when a CopilotProvider does not wrap the component, functionality fails silently. To Reproduce 1. Omit <CopilotProvider>...</CopilotProvider> 2. trigger useMakeCopilotReadable, useMakeCopilotActionable, CopilotTextarea, CopilotSidebarUIProvider 3. See how functionality does not work, but no error is emitted Expected behavior An error is emitted, with clear description of the likely core issue and how to resolve it (namely, wrap the app in a CopilotProvider). Point to docs. ``` {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ###2.[PortKeyAI](https://github.com/Portkey-AI/gateway){% embed https://github.com/Portkey-AI/gateway no-readme %} ###3.[Pezzo.ai](https://github.com/pezzolabs/pezzo){% 嵌入 https://github.com/pezzolabs/pezzo no-readme %} ###4.[OpenVoice](https://github.com/myshell-ai/OpenVoice){% 嵌入 https://github.com/myshell-ai/OpenVoice no-readme %} ###5.[LLMCourse](https://github.com/mlabonne/llm-course){% 嵌入 https://github.com/mlabonne/llm-course no-readme %} --- &nbsp; #雲端和資料庫☁️ ### 6. [Winglang](https://github.com/winglang/wing) - 雲端導向的程式語言 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvfykepsj1tszs8260wj.png) Wing 是一種用於雲端應用程式的程式語言。 它結合了雲端基礎設施和應用程式的程式碼,使雲端服務開發變得更加容易。 Wing 獨特的執行模型和測試模擬器有助於高效建置和部署雲端應用程式。 ###[第一期好:](https://github.com/winglang/wing/issues/4998) ``` Support Array.sort() method Feature Spec: let arr: MutArray<num>=[2, 1, 3, 9, 6, 4]; arr.sort(); log("${arr}"); // it should print sorted array in ascending order, eg: [1, 2, 3, 4, 6, 9] Component: Wing SDK Community Notes: If you are interested to work on this issue, please leave a comment. If this issue is labeled needs-discussion, it means the spec has not been finalized yet. Please reach out on the #dev channel in the Wing Slack. ``` {% cta https://github.com/winglang/wing %} 星翼朗 ⭐️ {% endcta %} --- ### 7. [StackQL](https://github.com/stackql/stackql) - 以 SQL 為基礎的雲端資源管理 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdtf51ekap09idn80xnh.png) StackQL 提供了一個獨特的 SQL 為基礎的框架來管理和查詢跨不同提供者(例如 Google、AWS、Azure 等)的雲端資源和 API。 它允許使用類似 SQL 的命令來配置和操作雲端服務,從而簡化了雲端操作。 這使得 StackQL 成為雲端資源管理和互動的多功能工具,特別是對於熟悉 SQL 的人來說。 ###[好第一期:](https://github.com/stackql/stackql/issues/280) ``` Add unit testing to package writer Add unit testing for internal/stackql/writer . Description: add implementation for testing sql_writer.go modify sql_writer.go by adding function for dependency injection add implementation for testing generic.go modify generic.go by adding variable for patching GetDB function modify entryutil.go to adjust sql_writer.go ``` {% cta https://github.com/stackql/stackql/ %} Star StackSQL ⭐️ {% endcta %} --- ###8.[Appwrite](https://github.com/appwrite/appwrite){% 嵌入 https://github.com/appwrite/appwrite no-readme %} ###9.[Supabase](https://github.com/supabase/supabase){% 嵌入 https://github.com/supabase/supabase no-readme %} ###10.[SuperDuperDB](https://github.com/SuperDuperDB/superduperdb){% 嵌入 https://github.com/SuperDuperDB/superduperdb no-readme %} --- &nbsp; #開發實用程式🛠️ ### 11. [Firecamp](https://github.com/firecamp-dev/firecamp) - 多協定 API 協作工具 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adt6n8uv5dseylmemng0.png) Firecamp 是一款多功能 API 開發工具,支援 Rest、GraphQL 和 WebSockets 等協定。 它簡化了 API 的設計、測試和記錄,並增強了 API 專案的團隊協作。 ###[好第一期:](https://github.com/firecamp-dev/firecamp/issues/137) ``` Help out with Manual Testing of Firecamp Responsibilities: Executing test cases and reporting results Logging bugs and issues in the Github issue tracker Providing feedback on usability and the testing process Suggesting improvements to tests and expanding test coverage. Benefits Benefits for your testing profile and career: Experience testing a real-world open source application Each release will include your name with bugs fixes. Exposure to different types of testing such as UI, API, integration, etc Opportunity to have your contributions and feedback incorporated into the product Collaborating with an open source community Having your testing work visible to potential employers Firecamp Swags (T-shirts and stickers) Community shoutout and promotion ``` {% cta https://github.com/firecamp-dev/firecamp %} 星際火營 ⭐️ {% endcta %} --- ###12.[Odigos](https://github.com/keyval-dev/odigos){% 嵌入 https://github.com/keyval-dev/odigos no-readme %} ###13.[Digger](https://github.com/diggerhq/digger){% 嵌入 https://github.com/diggerhq/digger no-readme %} ###14.[鏡像](https://github.com/metalbear-co/mirrord){% 嵌入 https://github.com/metalbear-co/mirrord no-readme %} --- &nbsp; #後端⚙️ ### 15. [Cerbos](https://github.com/cerbos/cerbos) - 可擴充、與語言無關的授權 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cljttnnxua54lyg4w65x.png) Cerbos 提供獨特、可擴展的解決方案,用於在應用程式中實施特定於上下文的使用者權限。 其靈活的、與語言無關的方法可以輕鬆整合和管理複雜的授權結構。 與眾不同的是,Cerbos 簡化了存取控制策略的開發,使其更能適應各種應用需求。 ###[第一期好:](https://github.com/cerbos/cerbos/issues/1920) ``` Produce output when the rule condition is not satisfied Currently the output block is only evaluated if the rule is actually activated (action, roles and conditions are satisfied). In certain situations, it's desirable to produce output when the rule is nearly activated (action and roles match but the condition is not satisfied). In order to maintain backward compatibility, reduce noise, and to keep policy execution as fast as possible (outputs incur a tiny overhead), the proposal is to let users add an optional when section to the output block to opt into this behaviour. - actions: ['view'] effect: EFFECT_ALLOW roles: ['user'] condition: match: expr: timestamp(R.expiry_date) > now() output: expr: > format("%d hours until expiry", (timestamp(R.expiry_date) - now()).getHours()) when: cond_fail: > format("expired on %s", R.expiry_date) When evaluating the above rule, if the action, roles and condition match, output will be the result of evaluating output.expr If the condition is not satisfied, output will be the result of evaluating output. when.cond_fail if it exists. Otherwise no output will be produced. ``` {% cta https://github.com/cerbos/cerbos %} 明星 Cerbos ⭐️ {% endcta %} --- ###16.[Novu](https://github.com/novuhq/novu){% 嵌入 https://github.com/novuhq/novu no-readme %} ###17.[Trigger.dev](https://github.com/triggerdotdev/trigger.dev){% 嵌入 https://github.com/triggerdotdev/trigger.dev no-readme %} ###18.[SuperTokens](https://github.com/supertokens/supertokens-core){% 嵌入 https://github.com/supertokens/supertokens-core no-readme %} ###19.[Wazuh](https://github.com/wazuh/wazuh){% 嵌入 https://github.com/wazuh/wazuh no-readme %} --- &nbsp; #UI/UX🦋: ### 20. [Flowbite](https://github.com/themesberg/flowbite) - 頂級 CSS 元件庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/98dwqyrhf1pbiqkpko8g.png) 最好、最受尊敬的 UI 元件庫之一。 基於實用優先的 CSS 框架。 易於使用,充滿重要的支援和模板。 {% cta https://github.com/themesberg/flowbite %} 明星 Flowbite ⭐️ {% endcta %} &nbsp; ###21.[MaterialUI](https://github.com/mui/material-ui) - 使用 Google 的 Material Design 實現的基礎 React 元件 {% 嵌入 https://github.com/mui/material-ui no-readme %} &nbsp; ###22。 [SwiperUI](https://github.com/nolimits4web/swiper) - 用於實現行動滑動 UI 的受人尊敬的庫 {% 嵌入 https://github.com/nolimits4web/swiper no-readme %} &nbsp; ###23.[ReactSpring](https://github.com/pmndrs/react-spring) - 在 React 中實現具有真實物理效果的動畫 {% 嵌入 https://github.com/pmndrs/react-spring no-readme %} --- &nbsp; #雜項🎨 ### 24. [SwirlSearch](https://github.com/swirlai/swirl-search) - 多源人工智慧資料搜尋器 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thplxod3d4vh1qq5hhpa.jpeg) Swirl 是一款由人工智慧驅動的搜尋工具,可同時查詢多個資料來源,包括資料庫和公用資料服務。 它使用人工智慧對結果進行排名並產生見解,從而可以跨不同的資料儲存庫進行全面搜尋。 Swirl 一次簡化了對各種來源的資料的搜尋和分析,使其成為資料驅動洞察的獨特工具。 ###[第一期好:](https://github.com/swirlai/swirl-search/issues/789) ``` Add a Connector: Yahoo search It would help to search anything with Swirl on Yahoo effectively. Locate and read a bit in their search API first. You might just need to make a new SearchProvider configration vs. a new Connector. Their docs should help guide you a bit in which way you might need to go. ``` {% cta https://github.com/swirlai/swirl-search/ %} Star SwirlSearch ⭐️ {% endcta %} --- ### 25. [Wasp](https://github.com/wasp-lang/wasp) - 使用 React 和 Node.js 開發全端 Web 應用程式 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54jp6j6r8ils6we97i0f.png) 使用 React 和 Node.js 進行快速全端 Web 應用程式開發。 Wasp 提供了一種建立現代 Web 應用程式的簡化方法,將前端的 React 和後端的 Node.js 結合在一個緊密結合的框架中。 ###[好第一期:](https://github.com/wasp-lang/wasp/issues/874) ``` Add images (or link to the example app) of auth UI helpers Wasp provides At this point in docs (also in the tutorial if we're using it), it would be nice to add an image of UI helpers for Auth (login/signup form, Google/GitHub button, ...) so developers can immediately see what they are getting and how nice it looks. ``` {% cta https://github.com/wasp-lang/wasp %} 星黃蜂 ⭐️ {% endcta %} ###26.[Logstash](https://github.com/elastic/logstash) {% 嵌入 https://github.com/elastic/logstash 無自述文件 %} ###27.[Snapify](https://github.com/MarconLP/snapify) {% 嵌入 https://github.com/MarconLP/snapify 無自述文件 %} --- &nbsp; #為了好玩🎭 ###28.[Twitter 的演算法](https://github.com/twitter/the-algorithm){% embed https://github.com/twitter/the-algorithm no-readme %} ###29.[十億行挑戰](https://github.com/gunnarmorling/1brc){% embed https://github.com/gunnarmorling/1brc no-readme %} ###30.【秘密知識之書】(https://github.com/trimstray/the-book-of-secret-knowledge){% embed https://github.com/trimstray/the-book-of -秘密知識無自述文件%} ###31.[GenAI 初學者](https://github.com/microsoft/generative-ai-for-beginners){% 嵌入 https://github.com/microsoft/generative-ai-for-beginners no -自述文件%} --- 原文出處:https://dev.to/copilotkit/31-open-source-libraries-to-kickstart-your-journey-4hhd

您必須在業餘專案中使用的 11 個免費且有趣的 API

開始一個新專案就像一次冒險起航——對開發人員來說充滿了興奮和挑戰。當您集思廣益並為您的下一件大事規劃功能時,有一個遊戲規則改變者可以讓您的旅程更加順利:API。 ![](https://media1.tenor.com/m/31nachzhSKQAAAAd/did-you-say-free.gif) 為了在不減少預算的情況下為您的專案提供所需的推動力,這裡精選了一系列最佳免費 API。它們就像您值得信賴的伙伴,可以無縫地融入您的工作中,讓您的編碼之旅更加愉快和高效。 ------------------- ### 開源API測試平台 [Firecamp](https://firecamp.dev) 是開源 API 測試工具,可協助您比以往更快地測試 API。 如果您能花 5 秒給我們一顆星,我將非常感激💜 ![octacat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kjqmny5y0c4k4gyqcxs.png) ----- 讓我們深入探討一下: 1.[JSONPlaceholder API](https://jsonplaceholder.typicode.com/) - 幫助開發人員在使用真正的程式之前測試他們的程式。它允許您使用 HTTP 請求建立、變更和刪除虛假資料。 - 可以根據您的需求使用它可以是 JSON、CSV 和 YAML ![JSON 佔位](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x660agyk72r429p8kv2p.png) -------- 2.[OpenWeatherMap API](https://openweathermap.org/api) - 全球任何地點的即時天氣資料。 - 存取目前天氣狀況、預報和歷史資料。 - 非常適合涉及天氣預報、氣候分析或旅行計劃的應用。 -------- 3.[新聞API](https://newsapi.org/) - 總結來自世界各地各種來源的新聞文章。 - 提供即時和歷史資料,包括標題、來源和文章摘要。 - 非常適合需要即時新聞更新、內容聚合或情緒分析的應用程式。 -------- 4.[Unsplash API](https://unsplash.com/developers) - 用於應用程式和網站的高品質、免版稅圖像。 - 存取大量專業照片。 - 非常適合涉及視覺內容、設計或使用者參與的專案。 ------------ 5.[REST國家API](https://restcountries.com/) - 檢索有關國家的訊息,包括人口、面積等。 - 對於涉及地理、教育或文化探索的應用程式很有用。 ![國家 API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghsa3b5eev6xnydsgo6s.png) ------------ 6.[Randomuser.me API](https://randomuser.me/) - 產生隨機用戶資料,包括姓名、地址和個人資料圖片。 - 非常適合在不使用真實用戶資料的情況下測試和原型應用程式。 ------------ 7.[JokeAPI](https://jokeapi.dev/) - 透過大量笑話將幽默融入您的專案中。 - 非常適合娛樂應用程式或為您的網站加入輕鬆的感覺。 ------------ 8.[匯率-API](https://www.exchangerate-api.com/) - 存取各種貨幣的即時和歷史匯率。 - 對於涉及電子商務、金融或旅遊的專案至關重要。 ------------ 9.【NASA開放API】(https://api.nasa.gov/) - 從 NASA 的大量收藏中檢索資料,包括圖像、影片和天文學資料。 - 非常適合教育計畫、太空愛好者或任何對宇宙探索感興趣的人。 ------------ 10.[COVID-19資料API](https://covid19api.com/) - 隨時了解全球 COVID-19 即時統計資料。 - 對於專注於健康、資料視覺化或流行病監測的應用程式很有用。 ------------ 11.[圖書API](https://openlibrary.org/developers/api) - 開放式庫提供了一套 API 來幫助開發人員啟動並執行我們的資料。其中包括 RESTful API,它使 Open Library 資料可以採用 JSON、YAML 和 RDF/XML 格式。 ------------ 這些 API 涵蓋了廣泛的功能,可讓您為專案加入不同的功能,而不會產生額外的成本。 請務必瀏覽他們的文件,以便無縫整合到您的開發工作流程中。 您還記得您為有趣的專案嘗試的第一個 API 嗎?在下面評論並與社區分享。 下週見!! 🙏 --- 原文出處:https://dev.to/firecampdev/11-free-and-fun-apis-you-must-use-in-your-side-project-219m

🎉像專業人士一樣監控您的 Javascript 應用程式🧙‍♂️💫

## **簡介** 在本教程中,您將學習如何使用**現代工具**和**最佳實踐**來監控您的Javascript應用程式。 探索分散式追蹤的力量,並了解如何無縫整合和利用 Odigos 和 Jaeger 等工具來增強您的監控能力。 **您將學到什麼:✨** - 如何在 Javascript 中建立微服務🐜。 - 為微服務設定 Docker 容器📦。 - 配置 Kubernetes ☸️ 以管理微服務。 - 整合追蹤後端以可視化追蹤🔍。 您準備好成為監控 JS 應用程式的**專家**了嗎? 😍 說**是的,先生!**。 我聽不到你說話。大聲點說。 🙉 ![大聲點 GIF](https://media.giphy.com/media/8m5dizh7ghyEPIWIx1/giphy.gif) *** ## **讓我們設定一下 🦄** > 🚨 在部落格的這一部分中,我們將建立一個虛擬的 JavaScript 微服務應用程式並將其部署在本地 Kubernetes 上。如果您已經有一個並且正在跟進,請隨意跳過這一部分。 為您的應用程式建立初始資料夾結構,如下所示。 👇🏻 ``` mkdir microservices-demo cd microservices-demo mkdir src cd src ``` ### **設定伺服器** 🖥️ > 👀 出於演示目的,我將建立兩個相互通信的微服務,最終我們可以使用它來視覺化分散式追蹤。 - **建置與 Dockerize 微服務 1** 在「/src」資料夾中,建立一個新資料夾「/microservice-1」。在資料夾內初始化 **NodeJS** 專案並安裝所需的依賴項。 ``` mkdir microservice-1 cd microservice-1 npm init -y npm install --save express node-fetch ``` 建立一個新檔案“index.js”並新增以下程式碼: ``` // 👇🏻/src/microservice-1/index.js const express = require("express"); const fetch = require("node-fetch") const app = express(); const PORT = 3001; app.use(express.json()); app.get("/", async (req, res) => { try { const response = await fetch("http://microservice2:8081/api/data"); const data = await response.json(); res.json({ data: "Microservice 2 data received in Microservice 1", microservice2Data: data, }); } catch (error) { console.error(error.message); res.status(500).json({ error: "Internal Server Error" }); } }); app.listen(PORT, () => { console.log(`Microservice 1 listening on port ${PORT}`); }); ``` 伺服器正在偵聽連接埠“3001”,並且在對“/”發出請求時,我們從“microservice2”請求資料並將回應作為 JSON 物件返回。 📦 現在,是時候對這個微服務進行 docker 化了。在“/microservice-1”資料夾中建立一個新的“Dockerfile”並新增以下程式碼: ``` // 👇🏻/src/microservice-1/Dockerfile FROM node:18 # Use /usr/src/app as the working directory WORKDIR /usr/src/app # Copy package files and install production dependencies COPY --chown=node:node package*.json /usr/src/app RUN npm install --production # Copy the rest of the files COPY --chown=node:node . /usr/src/app/ # Switch to the user node with limited permissions USER node # Expose the application port EXPOSE 3001 # Set the default command to run the application CMD ["node", "index.js"] ``` 將我們不想推送到容器的文件加入到“.dockerignore”總是很好。使用我們不想推送的檔案的名稱來建立一個“.dockerignore”檔案。 ``` // 👇🏻/src/microservice-1/.dockerignore node_modules Dockerfile ``` 最後,透過執行以下命令來建構 🏗️ docker 映像: ``` docker build -t microservice1-image:latest . ``` 現在,這就是我們第一個微服務的完整設定。 ✨ - **建置與 Dockerize 微服務 2** 我們將有一個類似於“microservice1”的設置,只是在這裡和那裡進行了一些更改。 在「/src」資料夾中,建立一個新資料夾「/microservice-2」。在該資料夾內,初始化 **NodeJS** 專案並安裝所需的依賴項。 ``` mkdir microservice-2 cd microservice-2 npm init -y npm install --save express node-fetch ``` 建立一個新檔案“index.js”並新增以下程式碼: ``` // 👇🏻/src/microservice-2/index.js const express = require("express"); const fetch = require("node-fetch") const app = express(); const PORT = 3002; app.use(express.json()); app.get("/api/data", async (req, res) => { const url = "https://jsonplaceholder.typicode.com/users"; try { const response = await fetch(url); const data = await response.json(); res.json(data); } catch (error) { console.error(error.message); res.status(500).json({ error: "Internal Server Error" }); } }); app.listen(PORT, () => { console.log(`Microservice 2 listening on port ${PORT}`); }); ``` 伺服器正在偵聽連接埠 3002,根據對“/api/data”的“GET 請求”,我們從“jsonplaceholder”獲取資料並將回應作為 JSON 物件傳回。 📦 現在,是時候對這個微服務進行 docker 化了。複製並貼上「microservice1」的整個「Dockerfile」內容,然後將連接埠從 3001 變更為 3002。 另外,新增一個「.dockerignore」檔案並包含我們在建立「microservice1」時新增的相同檔案。 最後,透過執行以下命令來建構 🏗️ Docker 映像: ``` docker build -t microservice2-image:latest . ``` 現在,這也是我們第二個微服務的完整設定。 ✨ - **設定 Kubernetes** > 確保已安裝 **[Minikube](https://github.com/kubernetes/minikube)** 透過執行以下命令建立新的本機 Kubernetes 叢集。我們在設定 Odigos 和 Jaeger 時將需要它。 **啟動 Minikube:🚀** ``` minikube start ``` 現在我們已經準備好並 Docker 化了兩個微服務,是時候設定 Kubernetes 來管理這些服務了。 在專案的根目錄下,建立一個新資料夾「/k8s/manifests」。在此資料夾中,我們將為兩個微服務新增部署和服務配置。 - **部署設定📜**:用於在 Kubernetes 叢集上實際部署容器。 - **服務配置📄**:將 Pod 暴露給叢集內部和叢集外部。 首先,我們為「microservice1」建立清單。建立一個新檔案「microservice1-deployment-service.yaml」並新增以下內容: ``` // 👇🏻/k8s/manifests/microservice1-deployment-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: microservice1 spec: selector: matchLabels: app: microservice1 template: metadata: labels: app: microservice1 spec: containers: - name: microservice1 image: microservice1-image # Make sure to set it to Never, or else it will pull from the docker hub and fail. imagePullPolicy: Never resources: limits: memory: "200Mi" cpu: "500m" ports: - containerPort: 3001 --- apiVersion: v1 kind: Service metadata: name: microservice1 labels: app: microservice1 spec: type: NodePort selector: app: microservice1 ports: - port: 8080 targetPort: 3001 nodePort: 30001 ``` 此配置部署了一個名為「microservice1」的微服務,其資源限制為 **200MB 記憶體** 🗃️ 和 **0.5 個 CPU 核心**。它透過部署在連接埠 3001 上公開微服務,並透過服務在 **NodePort** 30001 上公開微服務。 > 🤔 還記得我們用名稱「microservice1-image」建構的「Dockerfile」嗎?我們使用相同的映像來建立容器。 可透過集群內的連接埠 8080 存取它。我們假設「microservice1-image」透過「imagePullPolicy: Never」在本地可用。如果沒有到位,它將嘗試從 Docker Hub 🐋 中提取映像並失敗。 現在,讓我們為「microservice2」建立清單。建立一個名為「microservice2-deployment-service.yaml」的新檔案並新增以下內容: ``` // 👇🏻/k8s/manifests/microservice1-deployment-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: microservice2 spec: selector: matchLabels: app: microservice2 template: metadata: labels: app: microservice2 spec: containers: - name: microservice2 image: microservice2-image # Make sure to set it to Never, or else it will pull from the docker hub and fail. imagePullPolicy: Never resources: limits: memory: "200Mi" cpu: "500m" ports: - containerPort: 3002 --- apiVersion: v1 kind: Service metadata: name: microservice2 labels: app: microservice2 spec: type: NodePort selector: app: microservice2 ports: - port: 8081 targetPort: 3002 nodePort: 30002 ``` 它與“microservice1”的清單類似,只有一些更改。 👀 此配置部署一個名為「microservice2」的微服務,並透過部署在連接埠 3002 上將其內部公開,並透過服務在 **NodePort** 30002 上將其外部公開。 可透過叢集內的連接埠 8081 進行存取,假設「microservice2-image」可透過「imagePullPolicy: Never」在本地使用。 全部完成後,請確保套用這些設定並使用這些服務啟動 Kubernetes 叢集。將目錄更改為`/manifests`並執行以下命令:👇🏻 ``` kubectl apply -f microservice1-deployment-service.yaml kubectl apply -f microservice2-deployment-service.yaml ``` 執行以下命令檢查我們的兩個部署是否正在**執行**:👇🏻 ``` kubectl get pods ``` ![Kubernetes Pod](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ywsvodcqqbx1wv0kede1.png) 最後,我們的應用程式已準備就緒,並使用必要的部署配置部署在 Kubernetes 上。 🎉 *** ## **安裝 Odigos 😍** > 💡 [**Odigos**](https://odigos.io/) 是一個開源可觀察性控制平面,使組織能夠建立和維護其可觀察性管道。 ![Odigos - 監控工具](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7c6i7wth5l3ey9frk0cx.jpg) > ℹ️ 如果您在 Mac 上執行,請執行以下命令在本地安裝 Odigos。 ``` brew install keyval-dev/homebrew-odigos-cli/odigos ``` > ℹ️ 如果您使用的是 Linux 計算機,請考慮透過執行以下命令從 GitHub 版本安裝它。確保根據您的 Linux 發行版更改該檔案。 > ℹ️ 如果 Odigos 二進位檔案不可執行,請在執行安裝指令之前執行此指令 `chmod +x odigos` 使其可執行。 ``` curl -LJO https://github.com/keyval-dev/odigos/releases/download/v1.0.9/cli_1.0.9_linux_amd64.tar.gz tar -xvzf cli_1.0.9_linux_amd64.tar.gz ./odigos install ``` ![Odigos 安裝](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/145z2j9fusgnbp41whcw.png) > 如果您需要有關其安裝的更多簡短說明,請按照此[**連結**](https://docs.odigos.io/installation)操作。 現在,Odigos 已準備好執行 🎉。我們可以執行它的 UI,配置追蹤後端,並相應地發送追蹤。 *** ## **將 Odigos 連接到追蹤後端 💫** > 💡 [**Jaeger**](https://github.com/jaegertracing/jaeger) 是一個開源的端對端分散式追蹤系統。 ![Odigos - 分散式追蹤平台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9bytdpf4wv1ncb0z52p.jpg) ### **設定 Jaeger!** ✨ 在本教程中,我們將使用 **Jaeger** 🕵️‍♂️,這是一個流行的開源平台,用於查看微服務應用程式中的分散式追蹤。我們將用它來查看 Odigos 生成的痕跡。 > 有關 Jaeger 安裝說明,請點選此 [**link**](https://www.jaegertracing.io/download/)。 👀 若要在 Kubernetes 叢集上部署 Jaeger,請執行下列命令:👇🏻 ``` kubectl create ns tracing kubectl apply -f https://raw.githubusercontent.com/keyval-dev/opentelemetry-go-instrumentation/master/docs/getting-started/jaeger.yaml -n tracing ``` 在這裡,我們建立一個「tracing」命名空間,並在該命名空間中為 Jaeger 應用部署配置📃。 此命令設定自託管 Jaeger 實例及其服務。 👀 執行以下命令來取得正在執行的 pod 的狀態:👇🏻 ``` kubectl get pods -A -w ``` 等待所有三個 Pod 都 **正在執行**,然後再繼續。 ![Kubernetes Pod](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n41rxtp8gcbe4cwsl6xx.png) 現在,要在本地查看 Jaeger Interface 💻,我們需要進行連接埠轉送。將流量從本機電腦上的連接埠 16686 轉送至 Kubernetes 叢集中選定 pod 上的連接埠 16686。 ``` kubectl port-forward -n tracing svc/jaeger 16686:16686 ``` 此命令在本機電腦和 Jaeger pod 之間建立一條隧道,公開 Jaeger UI,以便您可以與其互動。 最後,在瀏覽器上開啟「 http://localhost:16686 」並查看 Jaeger 實例正在執行。 ![Jaeger UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gr6bcqph7nyxa7v0u01t.png) ### **設定 Odigos 與 Jaeger 一起工作!** 🌟 > ℹ️ 對於 Linux 用戶,請前往從 GitHub 版本下載 Odigos 二進位檔案的資料夾,然後執行以下命令來啟動 Odigos UI。 ``` ./odigos ui ``` > ℹ️ 對於 Mac 用戶,只需執行: ``` odigos ui ``` 造訪“ http://localhost:3000 ”,您將看到 Odigos 介面,您將在“default”命名空間中看到您的部署。 ![Odigos 登陸頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14yqd2x41i9gqvwxdtsu.png) 選擇這兩個選項並點擊“下一步”。在下一頁上,選擇 Jaeger 作為後端,並在出現提示時加入以下詳細資訊: - **目的地名稱🛣️**:提供您想要的任何名稱,例如說**快速追蹤**。 - **端點🎯**:為端點加上`jaeger.tracing:4317`。 就是這樣 - Odigos 已準備好向我們的 Jaeger 後端發送痕跡。就是這麼簡單。 🤯 ![具有兩個微服務的 Odigos UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqmo7div92zngnkdwwyu.png) *** ## **查看分散式追蹤 🧐** 設定 Odigos 後,在 Jaeger 主頁「 http://localhost:16686 」上,您將已經看到列出的兩個微服務。 ![Jaeger UI 列出了兩個微服務](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwb0qjdmxi4ydcvwjgr1.png) Odigos 已經開始向 Jaeger 發送我們的應用程式痕跡。 😉 請記住,這是我們的微服務應用程式。由於以「microservice1」為起點,因此再向「microservice1」發出一些請求,隨後它將向「microservice2」請求資料並傳回。最終,Jaeger 將開始填滿這些痕跡。 ![Jaeger 分散式追蹤](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4kwzh854bsh5wga1or3.png) 點擊任一請求,您應該能夠觀察請求如何流經您的應用程式以及完成每個請求所需的時間。 這一切都是在沒有更改一行程式碼的情況下完成的。 🤯 一切都感謝 **Odigos**! 🤩 ![令人震驚的 GIF](https://media.giphy.com/media/l0NwHXQy3kUSfFF60/giphy.gif) 想像一下,這只是一個很小的虛擬應用程式,但對於一個執行著大量微服務並相互交互的更大的應用程式來說,分散式追蹤將非常強大! 💪 透過分散式跟踪,您可以輕鬆辨識應用程式中的瓶頸,並確定哪個服務導致問題或花費更長的時間。 🕒 *** ## **讓我們總結一下! 🥱** 到目前為止,您已經學習如何使用 **Odigos** 作為應用程式和追蹤後端 **Jaeger** 之間的 **中間件**,透過分散式追蹤來密切監控 👀 Javascript 應用程式。 👏 如果您已經做到了這一步,請拍拍自己的背。 🥳你值得擁有! 😉 本教學的源程式碼可在此處取得: https://github.com/keyval-dev/blog/tree/main/odigos-monitor-JS-like-a-pro > 如果您對本文有任何疑問或建議,請在下面的評論部分分享。 👇🏻 那麼,這就是本文的內容。感謝您的閱讀! 🎉🫡 --- 原文出處:https://dev.to/odigos/monitor-your-javascript-application-like-a-pro-581p

2024 年您必須嘗試的 25 個 Web 開發專案

毫無疑問,掌握 Web 開發最有效的方法之一就是透過實作。雖然學習理論概念很重要,但將您的知識應用到現實世界的專案中才能真正鞏固您的技能。無論您是想要打下堅實基礎的初學者,還是尋求新挑戰的經驗豐富的開發人員,這裡有 25 個 Web 開發專案可以提高您的能力。 ### 學生成績管理系統 學生成績管理系統旨在為學生和大學提供一種快速且用戶友好的方式來存取和管理考試成績。學生可以登入查看他們的成績,新生可以選擇註冊。該系統旨在以易於理解的方式呈現結果。 **如何做:** 掌握前端、後端和資料庫程式設計的基礎知識後,從建立全端應用程式開始。利用HTML、CSS、JavaScript、PHP和MySQL實現使用者認證、結果顯示和註冊功能。 ![學生成績管理系統](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4y9a2bm1exqbu34lsxph.png) ### 線上程式碼編輯器(React) 該專案涉及使用 React 建立線上程式碼編輯器,允許使用者用各種程式語言編寫和執行程式碼。目標是建立一個用戶可以無縫編輯和測試原始程式碼的平台。 **如何做:** 先使用 HTML、CSS 和 React 進行前端工作。實現程式碼輸入、執行和結果顯示功能。專注於建立用戶友好的介面,以獲得流暢的程式碼編輯體驗。 ![線上程式碼編輯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/06jjz3v5sq7c6apc41d7.png) ### 使用 React 進行 Amazon 克隆 Amazon Clone 專案圍繞著使用 React 建立 Amazon 線上商店的工作副本。該專案將幫助您了解有效的電子商務網站所需的元件並將它們應用到您的應用程式中。 **如何做:** 從 HTML、CSS 和 JavaScript 開始。使用 React 建立電子商務網站的不同部分,例如產品清單、購物車和結帳流程。整合動態資料並增強使用者介面。 ![使用 React 的 Amazon 克隆](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pq55ml71nqntt4j07p1z.png) ### 客戶關係經理 客戶關係管理器專案涉及建立一個後端 Web 應用程式,該應用程式允許建立、讀取、更新和刪除 (CRUD) 客戶資料。這是了解後端 Web 開發的基礎專案。 **如何做:** 利用 Node.js、Express.js 和 MongoDB 等技術來建立後端基礎架構。實施 CRUD 操作來管理客戶資料。開發一個用戶友好的介面,用於與客戶資料庫互動。 ![顧客關係經理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfr25p8b2gzapen1l8ry.png) ### 排序展示台 排序可視化器專案旨在提供各種排序演算法的可視化表示。使用者可以觀察不同的演算法如何執行,並更深入地了解 JavaScript 的基本概念。 **具體操作方法:** 使用 HTML、CSS、Bootstrap 和 JavaScript 建立 Web 應用程式。實現冒泡排序、合併排序和快速排序等排序演算法的視覺化。允許用戶與視覺化進行交互,以增強他們的學習體驗。 ![排序視覺化工具](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/51wpf82samc6k9ggftgc.png) ### 多人遊戲 – Connect4 多人遊戲 – Connect4 專案專注於建立具有多人遊戲功能的著名 Connect4 遊戲。它提供了學習一些重要的網路和遊戲設計基礎知識的機會。 **如何做:** 如果您想知道多人遊戲是如何開發的,或者您曾經想為週末製作一款遊戲,那麼這個專案適合您。使用 PyGame、Sockets 和遊戲編程為您和您的朋友建立多人 Connect4 遊戲。 ![多人遊戲 – Connect4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktsz3ky4pj27b9q7mynm.png) ### YouTube 腳本摘要器 投入時間觀看可能比預期更長的電影變得相當具有挑戰性。有時,如果我們不能從他們那裡收集有用的訊息,我們的努力可能會徒勞無功。透過自動總結影片的文字記錄,我們可以輕鬆地發現這些影片中的關鍵主題,這節省了我們再次觀看整個影片的時間和精力。 **如何做到這一點:** 人們每天都會觀看 YouTube 影片,這些影片可以是指導性的、紀錄片的或任何其他持續時間較長的類型;考慮透過提供摘要資訊可以節省多少時間。該專案將是一個 chrome 擴展,它將向後端的 Rest API 發送請求,該 API 將向您發送 YouTube 腳本的摘要。 ![YouTube 腳本摘要](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k4kw614tpyvyrumjc1g8.png) ### OurApp – NodeJS 中的社交媒體 Web 應用程式 現實應用程式 OurApp 的用戶可以進行交流、相互關注以及發布簡短的推文。掌握 HTML、CSS 和 JS 後,專案最適合想要使用 Nodejs 和 MongoDB 深入研究完整堆疊的人。 **怎麼做:** 你想成為能夠超越 HTML、CSS 和 JS 的全端開發人員嗎?建立這個完整的堆疊應用程式,以了解如何使用 NodeJS、MongoDB 和其他技術來建立現代、快速且可擴展的伺服器端 Web 應用程式。如果您想在磨練 NodeJS 技能的同時開發一些有趣的東西,那麼這個專案就是適合您的。您還可以免費註冊全端 Web 開發課程,這將幫助您成為您所在領域的傑出開發人員。 ![OurApp – NodeJS 中的社交媒體 Web 應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02qifmojncuvx27k6hyu.png) ### Codechef 通知 CodeChef 經常遇到伺服器過載問題,導致評審難以快速提供提交結果。留給編碼人員的唯一選擇是在一段時間後不斷檢查站點,看看結果是否存在。透過這個專案,我們希望消除審查提交頁面以確定提交結果的額外步驟。我們將自動執行檢索結果的過程並在準備好後立即通知使用者。 **如何操作:** Codechef 是一個流行的編碼實踐平台,但伺服器過載可能會導致結果延遲。此附加元件旨在透過自動化獲取結果並及時通知用戶的流程來節省時間。使用網路抓取或 API 來收集結果資訊並實作通知系統。 ![Codechef 通知程序](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4k7ru3ff3h7tgne8qpoi.png) ### 使用 Dash 來視覺化和預測股票 該專案涉及使用 Dash(一種用於建立分析 Web 應用程式的 Python 框架)來視覺化和預測股票資料。它提供了將資料視覺化和機器學習概念應用於金融資料的機會。 **如何操作:** 如果您對股票市場感興趣,該專案將簡化股票資料的可視化。利用Python、Dash和相關函式庫進行資料視覺化。實施基於歷史資料預測股票趨勢的功能。 ![使用 Dash 視覺化和預測股票](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovs08g50etufj84gz6fd.png) ### 線上程式碼編輯器 (JQuery) 線上程式碼編輯器可透過瀏覽器存取,並位於遠端伺服器上。儘管一些線上程式碼編輯器更像是完整的 IDE,但其他編輯器更像是具有語法突出顯示或程式碼完成等基本功能的文字編輯器。 **如何操作:** 使用 HTML、CSS、JavaScript 和 JQuery 建立線上程式碼編輯器。專注於透過語法突出顯示和程式碼完成等功能來增強使用者體驗。確保程式碼輸入和執行順利。 ![線上程式碼編輯器 (JQuery)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1vxgxvbtf1rh578lauc.png) ### 模糊URL FuzzyURLs 涉及使用 Django(Python 的高級 Web 框架)建立 URL 縮短服務。它提供了建立 Web 應用程式的實務經驗,特別關注 URL 操作。 **如何做:** 從頭開始開發一個基於 Django 的 URL 縮短器。了解 URL 縮短服務的工作流程並實現建立、管理和重新導向短 URL 的功能。 ![FuzzyURLs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uouta53v2suc8m27i14.png) ### 使用 React 進行 Slack 克隆 該專案旨在使用 React 建立 Slack 克隆,提供即時訊息和協作的平台。這是一個中高階專案,強調React-Redux和Firebase。 **如何做:** 應用 React-Redux 原理來建立一個類似 Slack 的 Web 訊息服務。利用 Firebase 實現即時資料庫功能。專注於建立響應靈敏且功能豐富的訊息傳遞應用程式。 ![使用 React 的 Slack 克隆](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kugsfjeblq934ceomlhs.png) ### Web 應用程式的 Node.js 驗證 了解使用 Node.js 為 Web 應用程式建立驗證系統。探索各種身份驗證技術,評估其優點和缺點,並實施改進。 **如何做:** 對於那些想要深入研究 Node.js 並了解建立安全身份驗證系統的複雜性的人來說,這個專案非常適合。實施使用者身份驗證、會話管理並探索增強安全性的方法。 ![Web 應用程式的 Node.js 驗證](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0fcet0kd7abf6xu57eh8.png) ### TinyMCE 同義詞插件 為 TinyMCE 富文本編輯器建立一個插件,用於搜尋單字的同義詞並允許使用者將它們插入編輯器中。 **如何做到這一點:** 為 TinyMCE 開發一個自訂插件,整合一項功能,使用戶能夠搜尋同義詞並輕鬆地將它們插入到富文本編輯器中。了解 TinyMCE API 以實現無縫整合。 ![TinyMCE 同義詞外掛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmr8y3x21l9g7vuwz5mi.png) ### 迷宮裡的老鼠 建立一個基本的 React Web 應用程式,顯示老鼠從帶有預設障礙的方形迷宮的左上角到右下角可以採取的所有可能路徑。 **具體操作方法:** 建立一個簡單的 React Web 應用程式來直觀地呈現經典的 Rat in the Maze 謎題。實施功能來展示老鼠穿過迷宮的所有潛在路徑。 ![迷宮中的老鼠](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdcwf4cioua8fogrsp96.png) ### 簡歷產生器 Web 應用程式 使用 ReactJS 和 NodeJS 建立一個用於建立履歷的 Web 應用程式。該專案將指導您完成建立簡歷產生器的步驟,並提供一種實用的方法來支援個人製作簡歷。 **如何做:** 深入研究 ReactJS 和 NodeJS 來開發一個用戶友好的簡歷產生器。實施加入個人詳細資訊、教育背景、工作經驗和技能的功能。專注於建立動態且可自訂的履歷模板。 ![簡歷產生器 Web 應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntwflc67s9kwi00plrti.png) ### Markdown 編輯器 建立一個 Markdown 編輯器,讓使用者編寫 Markdown 並預覽渲染的 HTML。 Markdown 是一種基於網路的文字格式化系統,廣泛用於部落格文章、文件等。 **如何做:** 使用 HTML、CSS 和 JavaScript 開發 Markdown 編輯器。使用戶能夠編寫 Markdown 程式碼並查看渲染的 HTML 的即時預覽。使用粗體文字、圖像和清單等功能增強編輯器。 ![Markdown 編輯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppv6gl6pzq7uetmkax5b.png) ### 450 DSA 追蹤器 450 DSA Tracker 旨在協助使用者追蹤解決 450 道資料結構和演算法問題的進度。它利用 TypeScript、React.js 的 reducer 和 context API 以及即時瀏覽器 IndexedDB 來快取資訊。 **具體操作方法:** 使用 TypeScript 和 React.js 實作 Web 應用程式,以追蹤解決資料結構和演算法問題的進度。利用reducer 和context API 進行狀態管理,並利用IndexedDB 進行即時瀏覽器快取。 ![450 DSA 追蹤器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijhv2y4gx3f1l2ub7xnz.png) ### 待辦事項網頁應用程式 使用 Node.js 框架 Adonis.js 建立待辦事項 Web 應用程式。了解 HTTP、REST API 和 CRUD 操作,同時建立用於管理待辦事項清單的後端 API。 **如何操作:** 使用 Adonis.js 為待辦事項 Web 應用程式建立 CRUD API。使用 Postman 測試 API 並建立用於新增、更新和刪除任務的後端功能。獲得使用後端框架的經驗。 ![待辦事項 Web 應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rajw8titifncb6la3e6.png) ### 兩個真相和一個謊言遊戲 Slack 機器人 開發一個 Slack 機器人,用於在 Slack 工作區中玩「兩個真相和一個謊言」遊戲。該專案利用 JavaScript 和 Node.js 為工作區成員建立一個有趣的互動式遊戲。 **如何做:** 建立一個 Slack 機器人,以促進「兩個真相和一個謊言」遊戲。使用 JavaScript 和 Node.js 處理 Slack 工作區中的互動。實現用戶共享陳述並猜測哪一個是錯誤的功能。 ![兩個真相與一個謊言遊戲 Slack 機器人](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwocawujfx0219s6ho5c.png) ### 使用 Chromakey(綠幕)效果進行即時視訊處理 探索視訊處理中使用的色度(綠幕)效果。使用 HTML、CSS 和 JavaScript 建立 Web 應用程式,以背景影片或圖像取代綠幕。 **具體操作方法:** 開發一個處理即時視訊並應用色鍵效果的 Web 應用程式。使用 HTML、CSS 和 JavaScript 操作影片幀並用背景影片或圖像替換綠幕。 ![使用 Chromakey(綠幕)效果進行即時視訊處理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4yc5aauy13cthc6b2b3.png) ### WhatsApp 網頁克隆 使用 React 和 Firebase 開發具有即時訊息功能的 WhatsApp Web 克隆。 **如何操作:** 使用 React 建立使用者介面並使用 Firebase 實現即時資料庫功能,打造流暢的訊息體驗。 ![WhatsApp 網路克隆](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggf6mpl4bssvzjrr97b3.png) ### WhatsApp 上的電子郵件提醒 設定新電子郵件的 WhatsApp 提醒以簡化電子郵件管理和通知。 **如何操作:** 使用自動化平台 Twilio 建立一個工具,從收件匣中獲取詳細資訊並在 WhatsApp 上發送警報。 ![WhatsApp 上的電子郵件提醒](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zxhacxpysrlx2d78sb5.png) ### 天氣預報應用程式 使用 Streamlit 函式庫和 OpenWeatherMap API 為天氣預報應用程式建立響應式前端。 **如何操作:** 利用 Python 和 Streamlit 可視化天氣資料並與 OpenWeatherMap API 互動以獲取即時天氣資訊。 ![天氣預報應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cwm38a5vw8sj2iu17y57.png) ### 總結 這個 Web 開發專案的彙編提供了各種各樣的挑戰,使您能夠提高跨不同技術和概念的技能。無論您對全端開發、資料視覺化、遊戲設計還是自動化感興趣,這些專案都可以提供有價值的幫助 --- 原文出處:https://dev.to/mukeshkuiry/25-web-development-projects-you-must-work-on-2024-4onl

Flask Rest API -第 1 部分 - 將 MongoDB 與 Flask 結合使用

## 第 1 部分:將 MongoDB 與 Flask 結合使用 你好!在本系列的最後一個[部分](https://dev.to/paurakhsharma/flask-rest-api-part-0-setup-basic-crud-api-4650)中,我們學習瞭如何建立基本的“ CRUD” ` 使用 python `list` 的 REST API 功能。但這不是現實世界應用程式的建構方式,因為如果您的伺服器重新啟動或上帝禁止崩潰,那麼您將丟失伺服器中儲存的所有資訊。為了解決這些問題(以及許多其他問題),使用了資料庫。所以,這就是我們要做的。我們將使用 [MongoDB](https://docs.mongodb.com/manual/) 作為我們的資料庫。 如果您剛從這部分開始,您可以在[此處]找到我們迄今為止編寫的所有程式碼(https://github.com/paurakhsharma/flask-rest-api-blog-series/tree/master/Part% 20 -%200)。 在開始之前,請確保您已在系統中安裝了 MongoDB。如果您還沒有安裝,可以安裝 [Linux](https://docs.mongodb.com/manual/administration/install-on-linux/)、[Windown](https://docs.mongodb.com/手冊/教學/install-mongodb-on-windows/)和[macOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/)。 主要有一些流行的函式庫可以讓 MongoDB 的使用變得更容易: 1) [Pymongo](https://api.mongodb.com/python/current/) 是 MongoDB 的低階 Python 包裝器,使用 `Pymongo` 類似於直接編寫 MongoDB 查詢。 以下是使用“Pymongo”更新“id”與給定“id”相符的電影名稱的簡單範例。 ``` db['movies'].update({'_id': id}, {'$set': {'name': 'My new title'}}) ``` `Pymongo` 不使用任何預先定義的模式,因此它可以充分利用 MongoDB 的無模式特性。 2) [MongoEngine](http://docs.mongoengine.org/) 是物件文件映射器,它使用文件模式,使 MongoDB 的使用變得清晰、更容易。 這是使用“mongoengine”的相同範例。 ``` Movies.objects(id=id).update(name='My new title') ``` 「Mongoengine」對資料庫中的欄位使用預先定義架構,這限制了它使用 MongoDB 的無架構性質。 正如我們所看到的,雙方都有各自的優點和缺點。因此,請選擇最適合您的專案的一種。在本系列中,我們將學習“Mongoengine”,如果您希望我也介紹“Pymongo”,請在下面的評論部分告訴我。 為了在我們的`Flask` 應用程式中更好地使用`Mongoengine`,有一個很棒的`Flask` 擴展,名為[Flask-Mongengine](http://docs.mongoengine.org/projects/flask- mongoengine/en/latest/)。 那麼,讓我們開始安裝「flask-mongoengine」。 ``` pipenv install flask-mongoengine ``` *注意:由於`flask-mongoengine` 是在`mongoengine` 之上建造的,所以在安裝Flask-mongoengine 時會自動安裝,而且`mongoengine` 是在`pymongo` 之上建造的,所以它也會被安裝* 現在,讓我們在「movie-bag」中建立一個新資料夾。我將其稱為“資料庫”。在「database」資料夾中建立一個名為「db.py」的檔案。另外,建立另一個檔案並將其命名為“models.py” 讓我們看看文件/資料夾現在是什麼樣子。 ``` movie-bag │ app.py | Pipfile | Pipfile.lock └───database │ db.py └───models.py ``` 現在,讓我們深入探討有趣的部分。 首先,讓我們透過將以下程式碼新增至「db.py」來初始化我們的資料庫 ``` #~movie-bag/database/db.py from flask_mongoengine import MongoEngine db = MongoEngine() def initialize_db(app): db.init_app(app) ``` 在這裡,我們導入了“MongoEngine”並建立了“db”物件,並定義了一個函數“initialize_db()”,我們將從“app.py”中呼叫該函數來初始化資料庫。 讓我們在“models”目錄中的“movie.py”中編寫以下程式碼 ``` #~movie-bag/database/models.py from .db import db class Movie(db.Document): name = db.StringField(required=True, unique=True) casts = db.ListField(db.StringField(), required=True) genres = db.ListField(db.StringField(), required=True) ``` 我們剛剛建立的是資料庫的文件。因此,使用者無法新增此處定義的其他欄位。 這裡我們可以看到「Movie」文件有三個欄位: 1)`name`:是一個`String`類型的字段,我們在這個字段上也有兩個約束。 - “必需”,這意味著用戶在不提供標題的情況下無法建立新電影。 - “唯一”,這意味著電影名稱必須是唯一的,不能重複。 2) `casts`:是一個`list`類型的字段,其中包含`String`類型的值 3) `genres`: 與`casts`相同 最後,我們可以在「app.py」中初始化資料庫,並更改「view」函數(處理 API 請求的函數)以使用我們先前定義的「Movie」文件。 ``` #~movie-bag/app.py -from flask import Flask, jsonify, request +from flask import Flask, request, Response +from database.db import initialize_db +from database.models import Movie app = Flask(__name__) -movies = [ - { - "name": "The Shawshank Redemption", - "casts": ["Tim Robbins", "Morgan Freeman", "Bob Gunton", "William Sadler"], - "genres": ["Drama"] - }, - { - "name": "The Godfather ", - "casts": ["Marlon Brando", "Al Pacino", "James Caan", "Diane Keaton"], - "genres": ["Crime", "Drama"] - } -] +app.config['MONGODB_SETTINGS'] = { + 'host': 'mongodb://localhost/movie-bag' +} + +initialize_db(app) [email protected]('/movies') -def hello(): - return jsonify(movies) [email protected]('/movies') +def get_movies(): + movies = Movie.objects().to_json() + return Response(movies, mimetype="application/json", status=200) [email protected]('/movies', methods=['POST']) -def add_movie(): - movie = request.get_json() - movies.append(movie) - return {'id': len(movies)}, 200 [email protected]('/movies', methods=['POST']) + body = request.get_json() + movie = Movie(**body).save() + id = movie.id + return {'id': str(id)}, 200 [email protected]('/movies/<int:index>', methods=['PUT']) -def update_movie(index): - movie = request.get_json() - movies[index] = movie - return jsonify(movies[index]), 200 [email protected]('/movies/<id>', methods=['PUT']) +def update_movie(id): + body = request.get_json() + Movie.objects.get(id=id).update(**body) + return '', 200 [email protected]('/movies/<int:index>', methods=['DELETE']) -def delete_movie(index): - movies.pop(index) - return 'None', 200 [email protected]('/movies/<id>', methods=['DELETE']) +def delete_movie(id): + Movie.objects.get(id=id).delete() + return '', 200 app.run() ``` 哇!變化很多,讓我們一步一步地進行變化。 ``` -from flask import Flask, jsonify, request +from flask import Flask, request, Response +from database.db import initialize_db +from database.models.movie import Movie ``` 這裡我們刪除了“jsonify”,因為我們不再需要,並加入了“Response”,我們用它來設定回應的類型。然後我們從之前定義的「db.py」導入「initialize_db」來初始化資料庫。最後,我們從“movie.py”導入“Movie”文件 ``` +app.config['MONGODB_SETTINGS'] = { + 'host': 'mongodb://localhost/movie-bag' +} + +db = initialize_db(app) ``` 這裡我們設定 mongodb 資料庫的配置。這裡主機的格式為「<host-url>/<database-name>」。由於我們已經在本地安裝了 mongodb,因此我們可以從“mongodb://localhost/”存取它,並且我們將資料庫命名為“movie-bag”。 最後,我們初始化資料庫。 ``` [email protected]('/movies') +def get_movies(): + movies = Movie.objects().to_json() + return Response(movies, mimetype="application/json", status=200) + ``` 在這裡,我們使用“Movies.objects()”從“Movie”文件中獲取所有物件,並使用“to_json()”將它們轉換為“JSON”。最後,我們傳回一個「Response」物件,其中我們將回應類型定義為「application/json」。 ``` [email protected]('/movies', methods=['POST']) + body = request.get_json() + movie = Movie(**body).save() + id = movie.id + return {'id': str(id)}, 200 ``` 在「POST」請求中,我們首先取得發送的「JSON」和一個請求。然後我們使用“Movie(**body)”請求中的欄位來載入“Movie”文件。這裡的「**」稱為擴充運算符,在 JavaScript 中寫為「...」(如果您熟悉的話)。顧名思義,它的作用是傳播「dict」物件。 <br/> 所以,`Movie(**body)` 變成了 ``` Movie(name="Name of the movie", casts=["a caste"], genres=["a genre"]) ``` 最後,我們保存文件並獲取其“id”,我們將其作為回應返回。 ``` [email protected]('/movies/<id>', methods=['PUT']) +def update_movie(id): + body = request.get_json() + Movie.objects.get(id=id).update(**body) + return '', 200 ``` 這裡我們先找到與請求中發送的「id」相符的Movie文件,然後更新它。這裡我們也應用了擴充運算子將值傳遞給「update()」函數。 ``` [email protected]('/movies/<id>', methods=['DELETE']) +def delete_movie(id): + Movie.objects.get(id=id).delete() + return '', 200 ``` 與此處的“update_movie()”類似,我們獲取與給定“id”匹配的電影文件並將其從資料庫中刪除。 哦,**我剛剛想起來**,我們還沒有將 API 端點加入到“GET”,僅從我們的伺服器獲取一個文件。 讓我們加入它: 在 `app.run()` 上方加入以下程式碼 ``` @app.route('/movies/<id>') def get_movie(id): movies = Movie.objects.get(id=id).to_json() return Response(movies, mimetype="application/json", status=200) ``` 現在您可以從 API 端點「/movies/<valid_id>」取得單一影片。 要執行伺服器,請確保您位於“movie-bag”目錄。 然後執行 ``` pipenv shell python app.py ``` 在終端機中啟動虛擬環境並啟動伺服器。 哇!恭喜您已經走到這一步了。要測試API,請使用我們在[上一篇]((https://dev.to/paurakhsharma/flask-rest-api-part-0-setup-basic-crud-api-4650)) 中使用的“ Postman」本系列的一部分。 您可能已經注意到,如果我們向端點發送無效資料,例如:沒有名稱或其他字段,我們會收到“HTML”形式的不友善錯誤。如果我們嘗試取得資料庫中不存在的「id」影片文件,那麼我們也會收到「HTML」回應形式的不友善錯誤。這並不是一個精心建構的 API 的例外行為。我們將在本系列的後面部分中了解如何處理此類錯誤。 ### 我們從本系列的這一部分學到了什麼? - `Pymongo` 和 `Mongoengine` 之間的差異。 - 如何使用「Mongoengine」建立文件架構。 - 如何使用「Mongoengine」執行「CRUD」操作。 - Python 擴充運算子。 您可以在[此處]找到這部分的完整程式碼(https://github.com/paurakhsharma/flask-rest-api-blog-series/tree/master/Part%20-%201) 在下一部分中,我們將學習如何使用「Blueprint」來更好地建立 Flask 應用程式。以及如何使用“flask-restful”以最少的設定遵循最佳實踐,更快地建立 REST API 直到那時快樂編碼😊 --- 原文出處:https://dev.to/paurakhsharma/flask-rest-api-part-1-using-mongodb-with-flask-3g7d