接下來讓我們看一下代碼倉庫的界面,看看界面上有哪些我們需要關注的信息和功能。重點看下我標注的這些模塊,我會一一講解這些模塊的功能:Used by: 展示了這個項目被 github 上其他項目使用的次數,例如圖中的 React 是個知名的前端庫,所以使用者眾多;Watch: 點擊 Watch 后,相當于你就關注了這個項目,那么以后要是這個項目有更新,你就會收到提醒;Star: 類似朋友圈點贊功能,你覺得這個項目不錯,就可以給它點贊;Fork: 拷貝一份項目到你自己的倉庫,不過如果原倉庫后面有更新,你自己的倉庫不會自動更新代碼,需要通過其他方式同步過來才行。Issues: 當你在使用公共庫發現了 bug 或者有疑惑的時候,就可以在 Issues 模塊提出問題,等待倉庫作者或者其他使用了這個倉庫的開發者來解答;Pull requests: Pull request 列表,Pull request 簡稱 “PR”,意思是向這個倉庫提交代碼合并請求;描述: 之前創建倉庫時填寫的描述會展示在這里;commits: 代碼提交記錄;branches: 代碼分支;releases: 代碼發布的歷史版本可以在這里找到;contributors: 倉庫的貢獻者,只要你向這份倉庫貢獻過代碼,就會出現在這個列表里面;顏色條: 倉庫中所用到的各種代碼語言占比;Branch: 點擊這里可以切換不同的分支,圖中可以看到現在是 master 分支;New pull request: 創建一個代碼合并請求;Clone or download: 使用 git clone 項目倉庫,或者直接下載項目壓縮包。接下來,我會介紹上面其中幾個功能的妙用和小技巧。
網頁有一個最優秀的特點就是它的跨平臺性,一個前端程序員寫出的頁面,既可以運行在 Windows 的瀏覽器上、也可以運行在 MacOS 的瀏覽器上、還可以運行在 IOS 和安卓瀏覽器上。正是由于網頁所具備的優異跨平臺性擴展出了套殼網頁的這種形式,比如看起來只是個 apk 的安卓程序,點擊也能安裝到手機中,但實際上里面的內容都是網頁…還有現在紅極一時的小程序,其實在很早以前小程序就已經火起來了,但這次疫情真的是把小程序徹底推向了一個巔峰:去商場要掃小程序二維碼、坐高鐵要掃小程序二維碼、去麥當勞要用小程序點餐、去景點參觀要用小程序預約、去看電影要用小程序訂票…那么小程序其實是和前端技術是分不開的,雖然騰訊覺得自己搞的東西不能叫HTML、CSS,取而代之的是 WX(微信)ML、WX(微信)SS… 但其實還是換湯不換藥,語法什么的都基本一致,好多東西甚至連名稱都沒改。而且我們現在做小程序也有那種多端小程序框架:uni-app、mpvue、taro等…這里用的都是 CSS 而不是 微信SS 。所以學會了移動端布局,不僅僅可以把學到的知識運用到移動端的網頁上、還可以用到 React Native、小程序、快應用、Weex等這些前端演變出來的技術上。
對象的屬性在訪問的時候,務必要關心屬性是否真的存在。特別是服務端返回的數據,如果碰到數據出錯,就可能造成頁面無反應、白屏等問題:const getList = async () => { // 假裝拿了服務端的數據,并返回了 return { code: 1, data: { list: null, page: 1, count: 1111, }, };};getList() .then((res) => { // 取出數據 const { data } = res; const { list, page, count } = data; list.forEach(() => { // 處理一些業務 }); // 拋錯:TypeError: Cannot read property 'forEach' of null // alert 不會執行 alert('獲取數據成功'); });上面這段代碼,執行是會報錯的,因為 list 是 null,并不是期望的數組,這樣就導致了代碼無法正常執行下去。所以在使用的時候,最好可以判斷或者處理一下不可靠的數據。// 使用 if 判斷// ...if (list) { list.forEach(() => { // 處理一些業務 });} else { // ...}// ...// 提供一個默認值const { list = [], page, count } = data;list.forEach(() => { // 處理一些業務});// ...// 提供一個默認值const { list, page, count } = data;(list || []).forEach(() => { // 處理一些業務});// ...方法還有很多,還可以封裝一個函數專門用來取對象屬性的值,目的就是要代碼變得更加可靠,防止一些可能會造成重要后果的異常。如在 react 組件中,如果 render 函數中拋出了錯誤沒有處理,就可能導致組件或者頁面白屏。新的 ECMAScript 標準提供了可選鏈和雙問號操作符來更好的處理這個問題。const object = { a: { b: 2, c: { d: 3, }, },};const f = object.a?.b?.c?.d?.e?.f ?? 10;console.log(f); // 輸出:10關于這個知識點不再展開,可以參考 ES6+ 相關的 Wiki。簡單的說,在訪問對象屬性的時候,如果數據源不可靠,一定要做好處理異常的準備。
在學習 Proxy 之前,我們先來回歸一下 ES5 中的 Object.defineProperty() ,接觸過前端框架的同學應該都知道 Vue 和 React,其中 Vue 中的響應式數據底層就是使用 Object.defineProperty() 這個 API 來實現的。下面是 Object.defineProperty() 的語法。Object.defineProperty(obj, prop, descriptor)Object.defineProperty() 會接收三個參數:obj 需要觀察的對象;prop 是 obj 上的屬性名;descriptor 對 prop 屬性的描述。當我們去觀察一個對象時需要在 descriptor 中去定義屬性的描述參數。在 descriptor 對象中提供了 get 和 set 方法,當我們訪問或設置屬性值時會觸發對應的函數。var obj = {};var value = undefined;Object.defineProperty(obj, "a", { get: function() { console.log('value:', value) return value; }, set: function(newValue) { console.log('newValue:', newValue) value = newValue; }, enumerable: true, configurable: true});obj.a; // value: undefinedobj.a = 20; // newValue: 20上面的代碼中,我們使用一個變量 value 來保存值,這里需要注意的是,不能直接使用 obj 上的值,否則就會出現死循環。Object.defineProperty() 是 Vue2 的核心, Vue2 在初始化時會對數據進行劫持,如果劫持的屬性還是對象的話需要遞歸劫持。下面我們把 Vue2 中數據劫持的核心代碼寫出來。var data = { name: 'imooc', lession: 'ES6 Wiki', obj: { a: 1 }}observer(data);function observer(data) { if (typeof data !== 'object' || data == null) { return; } const keys = Object.keys(data); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let value = obj[key]; defineReactive(obj, key, value); }}function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { return value; }, set(newValue) { if (newValue === value) return; observer(newValue); value = newValue; } })}上面代碼的核心是 defineReactive 方法,它是遞歸的核心函數,用于重新定義對象的讀寫。從上面的代碼中我們發現 Object.defineProperty() 是有缺陷的,當觀察的數據嵌套非常深時,這樣是非常耗費性能的,這也是為什么現在 Vue 的作者極力推廣 Vue3 的原因之一,Vue3 的底層使用了 Proxy 來代替 Object.defineProperty() 那 Proxy 具體有什么好處呢?
當我們需要構建一個大型的前端項目,里面包含幾個并列的子項目時,我們就可以使用yarn得 workspace 。目前國內得許多經典開源項目,如 vue、react 等,都是用得這一思路去構建他們得項目。1.3.1 沒有使用workspace時,我們怎么做的在不使用 workspace 時,我們的項目目錄通常是這樣的projects/| project1/| |--package.json| |--node_modules/| | |--vue/|--project2| |--package.json| |--node_modules/| | |--vue/| | |--project1/其中第一個子項目 project1 的 package.json 配置可以簡化為:{ "name": "project1", "version": "1.0.0", "dependencies": { "vue": "1.0.0" }}第二個子項目 project2 的 package.json 配置可以簡化為:{ "name": "project2", "version": "1.0.0", "dependencies": { "vue": "1.0.0", "project1": "1.0.0" }}這種經典的傳統使用方法,就會暴露出如上文所說的問題,總結本案例的不足點如下:兩個子項目有相同的依賴 vue ,每個子項目都會下載一次 vue 依賴,不僅浪費開發效率,還占用額外空間,當子項目較多時,問題更加明顯。第二個子項目 project2 依賴于第一個子項目 project2 ,而 project1 如果沒有發布到 npm 倉庫,那就得使用yarn link命令來配置依賴,非常不方便。需要使用 yarn build 構建項目時,需要每個子項目分別構建,不能統一構建。1.3.2 使用workspace示例使用 workspace 不用安裝別的依賴,直接新建一個項目根目錄 projects, 初始化項目即可。然后修改初始化的 package.json 文件為:{ "private": true, "workspaces": ["project1", "project2"] // 也可以使用通配符設置為 ["project*"],開源社區則都基本上使用 "workspaces": ["packages/*"] 的目錄結構。}兩個子項目 project1 和 project2 如上文配置不變。在根目錄 projects 目錄下執行 yarn install$ yarn installyarn install v1.22.0info No lockfile found.[1/4] ? Resolving packages...[2/4] ? Fetching packages...[3/4] ? Linking dependencies...[4/4] ? Building fresh packages...success Saved lockfile.? Done in 0.56s.此時查看目錄結構如下:projects/|--package.json|--project1/| |--package.json|--project2| |--package.json|--node_modules/| |--vue/| |--project1/ -> ./project1/備注:如果需要某個特殊的 子項目 不受 Yarn Workspace 管理,只需在此 workspace 目錄下添加 .yarnrc 文件,并添加如下內容禁用即可workspaces-experimental false如果想單獨添加或者移除某個子項目的依賴,可以使用如下命令:$ yarn workspace project1 add vue --dev$ yarn workspace project1 remove vue 以上便是 yarn 的 workspace 簡單用法。
選項類型默認值描述–allowJsbooleanfalse允許編譯 JavaScript 文件–allowSyntheticDefaultImportsbooleanfalse允許從沒有設置默認導出的模塊中默認導入–allowUnreachableCodebooleanfalse不報告執行不到的代碼錯誤–allowUnusedLabelsbooleanfalse不報告未使用的標簽錯誤–alwaysStrictbooleanfalse以嚴格模式解析并為每個源文件生成 "use strict" 語句--baseUrlstring解析非相對模塊名的基準目錄–charsetstring“utf8”輸入文件的字符集–checkJsbooleanfalse在 .js 文件中報告錯誤,與 --allowJs 配合使用–declaration -dbooleanfalse生成相應的 .d.ts 文件–declarationDirstring生成聲明文件的輸出路徑–diagnosticsbooleanfalse顯示診斷信息–disableSizeLimitbooleanfalse禁用 JavaScript 工程體積大小的限制–emitBOMbooleanfalse在輸出文件的開頭加入BOM頭(UTF-8 Byte Order Mark)–emitDecoratorMetadata[1]booleanfalse給源碼里的裝飾器聲明加上設計類型元數據。查看 issue #2577 了解更多信息。–experimentalDecorators[1]booleanfalse啟用實驗性的ES裝飾器–extendedDiagnosticsbooleanfalse顯示詳細的診斷信息–forceConsistentCasingInFileNamesbooleanfalse禁止對同一個文件的不一致的引用–help -h打印幫助信息–importHelpersstring從 tslib 導入輔助工具函數(比如 __extends, __rest等)–inlineSourceMapbooleanfalse生成單個 sourcemaps 文件,而不是將每 sourcemaps 生成不同的文件–inlineSourcesbooleanfalse將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性--init初始化 TypeScript 項目并創建一個 tsconfig.json 文件–isolatedModulesbooleanfalse將每個文件作為單獨的模塊(與 “ts.transpileModule” 類似)–jsxstring“Preserve”在 .tsx 文件里支持 JSX: “React” 或 “Preserve”。–jsxFactorystring“React.createElement”指定生成目標為 react JSX 時,使用的 JSX 工廠函數,比如 React.createElement 或 h–libstring[]編譯過程中需要引入的庫文件的列表。 可能的值為: ? ES5 ? ES6 ? ES2015 ? ES7 ? ES2016 ? ES2017 ? ES2018 ? ESNext ? ES5 ? ES5 ? ES5 ? ES5 ? ES5 ? ES5 ? DOM ? DOM.Iterable ? WebWorker ? ScriptHost ? ES2015.Core ? ES2015.Collection ? ES2015.Generator ? ES2015.Iterable ? ES2015.Promise ? ES2015.Proxy ? ES2015.Reflect ? ES2015.Symbol ? ES2015.Symbol.WellKnown ? ES2016.Array.Include ? ES2017.object ? ES2017.Intl ? ES2017.SharedMemory ? ES2017.String ? ES2017.TypedArrays ? ES2018.Intl ? ES2018.Promise ? ES2018.RegExp ? ESNext.AsyncIterable ? ESNext.Array ? ESNext.Intl ? ESNext.Symbol 注意:如果 --lib 沒有指定默認注入的庫的列表。默認注入的庫為: ? 針對于 --target ES5:DOM,ES5,ScriptHost ? 針對于 --target ES6:DOM,ES6,DOM.Iterable,ScriptHost–listEmittedFilesbooleanfalse打印出編譯后生成文件的名字–listFilesbooleanfalse編譯過程中打印文件名–localestring(platform specific)顯示錯誤信息時使用的語言,比如:en-us–mapRootstring為調試器指定指定 sourcemap 文件的路徑,而不是使用生成時的路徑。當 .map 文件是在運行時指定的,并不同于 js 文件的地址時使用這個標記。指定的路徑會嵌入到 sourceMap 里告訴調試器到哪里去找它們–maxNodeModuleJsDepthnumber0node_modules 依賴的最大搜索深度并加載 JavaScript 文件,僅適用于 --allowJs–module -mstringtarget === “ES6” ? “ES6” : “commonjs”指定生成哪個模塊系統代碼: “None”, “CommonJS”, “AMD”, “System”, “UMD”, "ES6"或 “ES2015”。 ? 只有 "AMD"和 "System"能和 --outFile一起使用。 ? "ES6"和 "ES2015"可使用在目標輸出為 "ES5"或更低的情況下。–moduleResolutionstringmodule === “AMD” or “System” or “ES6” ? “Classic” : “Node”決定如何處理模塊?;蛘呤?“Node” 對于 Node.js/io.js,或者是 “Classic”(默認)–newLinestring(platform specific)當生成文件時指定行結束符: "crlf"(windows)或 "lf"(unix)–noEmitbooleanfalse不生成輸出文件–noEmitHelpersbooleanfalse不在輸出文件中生成用戶自定義的幫助函數代碼,如 __extends–noEmitOnErrorbooleanfalse報錯時不生成輸出文件–noErrorTruncationbooleanfalse不截短錯誤消息–noFallthroughCasesInSwitchbooleanfalse報告 switch 語句的 fallthrough 錯誤。(即,不允許 switch 的 case 語句貫穿)--noImplicitAnybooleanfalse在表達式和聲明上有隱含的 any 類型時報錯–noImplicitReturnsbooleanfalse不是函數的所有返回路徑都有返回值時報錯–noImplicitThisbooleanfalse當 this 表達式的值為 any 類型的時候,生成一個錯誤–noImplicitUseStrictbooleanfalse模塊輸出中不包含 “use strict” 指令–noLibbooleanfalse不包含默認的庫文件( lib.d.ts )–noResolvebooleanfalse不把 /// <reference``> 或模塊導入的文件加到編譯文件列表–noStrictGenericChecksbooleanfalse禁用在函數類型里對泛型簽名進行嚴格檢查–noUnusedLocalsbooleanfalse若有未使用的局部變量則拋錯–noUnusedParametersbooleanfalse若有未使用的參數則拋錯--outDirstring重定向輸出目錄–outFilestring將輸出文件合并為一個文件,合并的順序是根據傳入編譯器的文件順序和 ///<reference``> 和 import 的文件順序決定的–skipDefaultLibCheckbooleanfalse忽略庫的默認聲明文件的類型檢查–skipLibCheckbooleanfalse忽略所有的聲明文件( *.d.ts )的類型檢查--sourceMapbooleanfalse生成相應的 .map 文件–sourceRootstring指定 TypeScript 源文件的路徑,以便調試器定位。當 TypeScript 文件的位置是在運行時指定時使用此標記, 路徑信息會被加到 sourceMap 里--strictbooleanfalse啟用所有嚴格類型檢查選項–strictFunctionTypesbooleanfalse禁用函數參數雙向協變檢查–strictPropertyInitializationbooleanfalse確保類的非 undefined 屬性已經在構造函數里初始化。若要令此選項生效,需要同時啟用 --strictNullChecks--strictNullChecksbooleanfalse在嚴格的 null 檢查模式下,null 和 undefined 值不包含在任何類型里,只允許用它們自己和 any 來賦值(有個例外, undefined 可以賦值到 void)–stripInternal[1]booleanfalse不對具有 /** @internal */ JSDoc注解的代碼生成代碼–suppressExcessPropertyErrors[1]booleanfalse阻止對對象字面量的額外屬性檢查–suppressImplicitAnyIndexErrorsbooleanfalse阻止 --noImplicitAny 對缺少索引簽名的索引對象報錯。查看 issue #1232 了解詳情。--target <br> -tstring“ES3”指定ECMAScript目標版本 "ES3"(默認), "ES5", "ES6"/ "ES2015", "ES2016", "ES2017" 或 "ESNext"。 注意: "ESNext" 最新的生成目標列表為 ES proposed features–traceResolutionbooleanfalse生成模塊解析日志信息–typesstring[]要包含的類型聲明文件名列表–typeRootsstring[]要包含的類型聲明文件路徑列表--version <br> -v打印編譯器版本號–watch -w在監視模式下運行編譯器。會監視輸出文件,在它們改變時重新編譯。監視文件和目錄的具體實現可以通過環境變量進行配置[1] 這些選項是試驗性的