為前端的一枚小菜鳥也憧憬著自己有朝一日能寫出自己的框架,哪怕是流利理解三大框架的源碼也好,當然這沒有關公面試耍大刀的意味╮(‵▽′)╭
一、找到問題,思考解決思路,參考同類或者類比同類框架實現方式,抽象自己的方案,不斷的優化重構去寫就可以了。
框架的目的解決一系列特定的問題,復刻不是水平,創新才是。
二、框架是解決方案的代碼實現。要寫出優秀的框架,首先要善于發現問題(當然也要善于發現機會,總是亦步亦趨解決別人已經解決過的問題是不被認可的),其次才是用精巧的思路進行代碼實現。
三、現在的前端mvvm(react、vue,)框架基本都是由以下幾部分組成
綜上,再復雜的框架都是由各個小的技術點累計而成的,那么將以上每一個技術點都能鉆研透徹,并能夠將其靈活的組合起來的那么你離完成一個框架就不遠了。
當然,能寫框架到寫出好的框架再到能夠投入到生產環境還有相當長的一段路要走,大部分人也就停留到能寫框架的地步吧。
高級前端進階導學
前端高級課程體系梳理
高效源碼學習技巧
設計模式-jQuery源碼分析
整體架構-jQuery核心功能函數揭秘
Sizzle-選擇器
回調對象設計
Callbacks入門
Callbacks原理分析
異步回調解決方案
延時對象-Deferred概念
延時對象-Deferred源碼解析
事件綁定
事件綁定-bind/delegate/on
事件綁定-體系結構/委托設計
事件綁定-自定義設計/模擬事件
DOM操作
DOM操作核心
DOM操作方法-html.text.css
UNDERSCORE.JS源碼分析
設計篇
函數式編程思想概述
underscors整體機構&面向對象
undefned的處理/函數式編程回調iteratee設計
rest參數/underscors創建對象方式
輔助功能篇
數組篇
數組定位&攤平數組
數組運算uniq去重函數 原生對象擴展
函數篇
對象篇
模塊化編程-自研模塊加載器
大家可以關注我,私信回復“前端資源”獲取JavaScript進階資料
這是你可以通過不編寫JS來改善JS的第一件事。對于初學者來說,TypeScript(TS)是JS的“已編譯”的超集(在JS中運行的所有內容都可以在TS中運行)。TS在原生JS體驗的基礎上增加了一個全面的類型靜態檢查系統。長期以來,整個生態系統中對TS的支持都不夠好,所以以前我不建議大家使用。
幸運的是,那些日子已經過去很久了,大多數框架都開箱即用地支持TS。現在我們都在討論了什么是TS,下面我們來談談為什么要使用它。
我自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取。
TypeScript可以保證類型安全
類型安全性可以確保在編譯階段驗證整個代碼段中所有類型是否都以正確方式使用。換句話說,如果您創建一個數字參數的函數foo:
function foo(someNum: number): number { return someNum + 5; }
函數foo只接受number類型的參數
正確的例子 console.log(foo(2)); // 打印 "7" 錯誤的例子 console.log(foo("two")); // 不正確的TS代碼
除了在代碼中添加類型檢查的開銷之外,類型安全性的負面影響幾乎為零。另一方面,它帶來了極大的好處,類型安全性提供了針對常見錯誤的額外保護,這對于像JS這樣有缺陷的語言來說是一種幸運。
TypeScript讓重構大型項目成為可能
重構大型JS應用程序可能是一場噩夢。重構JS的最大苦惱是由于它不強制檢查函數參數簽名。這意味著絕對不能濫用JS函數,假設,如果我有一個函數myAPI提供給1000個不同的調用方使用:
function myAPI(someNum, someString) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } }
當我改變了一下這個函數簽名:
function myAPI(someString, someNum) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } }
我必須100%確定使用此功能的每個地方(成千上萬個地方),我都正確地更新了用法。如果我錯過一個,系統就會崩潰。
如果使用TS的話:
修改前 function myAPITS(someNum: number, someString: string) { ... } 修改后 function myAPITS(someString: string, someNum: number) { ... }
如您所見,myAPITS函數與JavaScript對應函數進行了相同的更改。但是,此代碼不是生成有效的JavaScript,而是導致生成無效的TypeScript,因為它所使用的成千上萬個位置現在出現了錯誤的提示。而且由于我們前面討論的類型安全性,這數千種情況將阻止編譯,這樣你可以從容進行修改重構。
TypeScript使得團隊架構溝通更為順暢
正確配置TS后,如果不先定義接口和類就很難編寫代碼。這提供了一種共享簡潔,可交流的體系結構建議的方法。在TS之前,存在解決此問題的其他解決方案,但是沒有一個解決方案可以在不使您做額外工作的情況下自動解決。例如,如果我想為后端提出新的請求類型,則可以使用TS將以下內容發送給隊友。
interface BasicRequest { body: Buffer; headers: { [header: string]: string | string[] | undefined; }; secret: Shhh; }
我必須已經編寫代碼,但是現在我可以共享我的增量進度并獲得反饋,而無需花費更多時間。我不知道TS本質上是否比JS更不容易出錯。我堅信,強制開發人員首先定義接口和API會產生更好的代碼。總體而言,TS已發展成為比原生JS的更為成熟且更可預測的替代方案。開發人員絕對仍然需要熟練使用原生JS,但是我最近開始的大多數新項目從一開始就是TS。
JavaScript是世界上最流行(即使不是最多)的編程語言之一。您可能希望到現在為止,大多數人已經知道了數以億計的人使用20多年的語言,但實際上相反。最近,對JS進行了許多更改和添加(是的,我知道,從技術上講ECMAScript),從根本上改變了開發人員的體驗。作為最近兩年才開始寫JS的人,我的優勢是沒有偏見或期望。這就導致了關于語言的哪些功能要使用和哪些要避免的更加務實的選擇。
async和await
在長時間內,異步的基于事件驅動的回調方式是JS開發中常規做法:
傳統回調
makeHttpRequest('google.com', function (err, result) { if (err) { console.log('Oh boy, an error'); } else { console.log(result); } });
我不會花時間解釋上述問題的原因,為了解決回調問題,在JS中添加了一個新概念Promises。使用Promise,您可以編寫異步邏輯,同時避免以前困擾基于回調的代碼的嵌套問題。
Promises
makeHttpRequest('google.com').then(function (result) { console.log(result); }).catch(function (err) { console.log('Oh boy, an error'); });
與回調相比,Promises的最大優點是可讀性和可鏈式調用。盡管Promise很棒,但他們仍有一些不足之處。對于許多人來說,Promise的體驗仍然讓人聯想到回調。具體來說,開發人員正在尋求Promise模型的替代方案。為了解決這個問題,ECMAScript委員會決定添加一種新的利用promise,async和await的方法:
try { const result = await makeHttpRequest('google.com'); console.log(result); } catch (err) { console.log('Oh boy, an error'); }
一個警告是,你await的任何內容都必須聲明為async,上一示例中makeHttpRequest的必須定義成
async function makeHttpRequest(url) { // ... }
由于異步功能實際上只是精美的Promise包裝器,因此也可以直接等待Promise。這也意味著async/await代碼和Promise代碼在功能上是等效的。因此,可以在不感到內疚的情況下隨意使用async/await。
let和const
對于JS的大部分存在,只有一個可變范圍限定符:var。var在處理范圍方面有一些非常獨特/有趣的規則。var的作用域范圍不一致且令人困惑,并且導致了意外的行為,因此導致整個JS生命周期中的錯誤。但是從ES6開始,還有var的替代方法:const和let。幾乎不再需要使用var了,任何使用var的邏輯都可以始終轉換為等效的const和let的代碼。
至于何時使用const和let,我總是從聲明所有const開始。const的限制要嚴格得多且“固定不變”,這通常會導致更好的代碼。沒有大量的“真實場景”需要使用let,我會說我用let聲明的1/20變量,其余的都是const。
我說const是“不變的”,因為它不能以與C / C++中的const相同的方式工作。const對JavaScript運行時的含義是,對該const變量的引用永遠不會改變。這并不意味著存儲在該引用上的內容將永遠不會改變。對于原始類型(數字,布爾值等),const確實會轉換為不變性(因為它是單個內存地址)。但是對于所有對象(類,數組,字典),const不能保證不變性。
箭頭=>函數
箭頭函數是在JS中聲明匿名函數的簡潔方式,匿名函數描述未明確命名的函數。通常,匿名函數作為回調或事件掛鉤傳遞。
原生匿名函數
someMethod(1, function () { // has no name console.log('called'); });
在大多數情況下,這種風格沒有任何“錯誤”。原生匿名函數在作用域方面表現出“有趣”,這可能會(或已經)導致許多意外錯誤。借助箭頭功能,我們不必再為此擔心。這是使用箭頭功能實現的相同代碼:
someMethod(1, () => { // has no name console.log('called'); });
除了更加簡潔之外,箭頭功能還具有更多實用的作用域行為。箭頭函數從定義它們的范圍繼承作用域。在某些情況下,箭頭函數功能可能更簡潔:
const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; const merged = Object.assign({}, obj1, obj2); console.log(merged) // prints { dog: 'woof', cat: 'meow' }
我想說清楚,這不是可變的情況;仍然存在原生匿名函數(特別是類方法)的有效用例。話雖這么說,但我發現,如果始終使用默認箭頭功能,則與默認使用原始匿名函數相比,您進行的調試要少得多。
展開操作符 ...
提取一個對象的鍵/值對并將它們添加為另一個對象的子代是一種非常常見的事情。從歷史上看,有幾種方法可以做到這一點,但是所有這些方法都相當笨拙:
const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; const merged = Object.assign({}, obj1, obj2); console.log(merged) // prints { dog: 'woof', cat: 'meow' }
這種模式非常普遍,因此上述方法很快變得乏味。多虧了展開操作符,您無需再使用它:
const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' }
而且更棒的是它也能和數組搭配使用:
const name = 'Ryland'; const helloString = `Hello ${name}`;
這個也許不是最重要的JS新特性,但它是我最喜歡的一個。
模板字符串
字符串是最常見的編程結構之一。這就是為什么它如此尷尬,以至于在許多語言中仍然很少支持原生聲明字符串。長期以來,JS屬于“坡腳的字符串”家族。但是,模板字符串的添加使JS成為了自己的類別。模板字符串本身可以方便地解決編寫字符串的兩個最大問題:添加動態內容和編寫多行的字符串:
const name = 'Ryland'; const helloString = `Hello ${name}`;
對象解構
對象解構是一種從數據集合(對象,數組等)中提取值的方法,而無需遍歷數據或顯式訪問其鍵值:
老方法
[a, b] = [10, 20]; console.log(a); // prints 10
對象解構
function animalParty(dogSound, catSound) {} const myDict = { dog: 'woof', cat: 'meow', }; const { dog, cat } = myDict; animalParty(dog, cat);
你還可以在函數參數簽名中進行解構:
function animalParty({ dog, cat }) {} const myDict = { dog: 'woof', cat: 'meow', }; animalParty(myDict);
解構也能和數組配合使用:
[a, b] = [10, 20]; console.log(a); // prints 10
編寫并行化的應用程序時,您的目標是一次優化您的工作量。如果您有四個可用的核心,而您的代碼只能使用一個核心,那么您的潛力就浪費了75%。這意味著阻塞,同步操作是并行計算的最終敵人。但是考慮到JS是單線程語言,事情就不會在多個內核上運行。那有什么意義呢?
JS是單線程的,但不是單文件的(如學校里的代碼)。即使不是并行的,它仍然是并發的。發送HTTP請求可能需要幾秒鐘甚至幾分鐘,因此,如果JS停止執行代碼,直到請求返回響應,該語言將無法使用。
JavaScript通過事件循環解決了這個問題。事件循環遍歷已注冊的事件,并根據內部調度/優先級邏輯執行它們。這使發送數千個同時的HTTP請求或同時從磁盤讀取多個文件成為可能。要注意的是:只有您使用正確的功能,JavaScript才能利用此功能。
最簡單的例子是for循環:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; async function testCall() { // do async stuff here } for (let i = 0; i < 10; i += 1) { testCall(); }
原始for循環是編程中存在的最少并行構造之一。在我的上一份工作中,我帶領一個團隊花費了幾個月的時間嘗試將傳統的R lang for循環轉換為自動并行代碼。從根本上講,這是一個不可能解決的問題,只有等待深度學習的改善才能解決。并行化for循環的困難源于一些有問題的模式。順序for循環非常少見,但僅憑它們就無法保證for循環可分解性:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; items.map(async (item) => { // do async stuff here });
如果按順序執行該代碼,并且每次迭代執行,則只會產生預期的結果。如果嘗試一次執行多個迭代,則處理器可能會基于不正確的值錯誤的分支,這會使結果無效。如果這是C代碼,我們將有不同的討論,因為用法不同,并且編譯器可以使用循環完成很多技巧。在JavaScript中,僅在絕對必要時才應使用傳統的for循環。否則,請使用以下構造:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; const allResults = await Promise.all(items.map(async (item) => { // do async stuff here }));
我將解釋為什么這些是對傳統for循環的改進。諸如map之類的結構無需按順序執行每個迭代,而是采用所有元素并將它們作為單獨的事件提交給用戶定義的map函數。在大多數情況下,各個迭代之間沒有固有的聯系或依賴性,因此它們可以并行運行。這并不是說您無法使用for循環來完成同一件事。實際上,它看起來像這樣:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; async function testCall() { // do async stuff here } for (let i = 0; i < 10; i += 1) { testCall(); }
如您所見,for循環并不能阻止我以正確的方式進行操作,但是它肯定也不會使其變得更容易。與map版本比較:
var fooVar = 3; // airbnb rules forebid "var"
如您所見,map就可以了。如果要在所有單個異步操作完成之前進行阻塞,則map的優勢將變得更加明顯。使用for循環代碼,您將需要自己管理一個數組。這是map版本:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; const allResults = await Promise.all(items.map(async (item) => { // do async stuff here }));
真的很容易,在許多情況下,與map或forEach相比,for循環的性能相同(或可能更高)。我仍然認為,現在丟失幾個周期值得使用定義良好的API的優點。這樣,將來對該數據訪問模式實現的任何改進都會使您的代碼受益。for循環過于籠統,無法對同一模式進行有意義的優化。
在map和forEach之外還有其他有效的異步選項,例如for-await-of.
沒有一致風格(外觀)的代碼很難閱讀和理解。因此,以任何語言編寫高端代碼的一個關鍵方面是保持一致且明智的風格。由于JS生態系統的廣度,對于lint風格和style細節有很多選擇。我要強調的是,與使用特制的lint/style相比,使用lint并強制使用一種style(其中的任何一種)更為重要。歸根結底,沒有人會完全按照我的意愿來編寫代碼,因此對此進行優化是不切實際的目標。
我看到很多人問他們應該使用eslint還是prettier。對我來說,它們的用途非常不同,因此應結合使用。ESlint大部分時間都是傳統的linter。它將確定與style無關,而與正確性有關的代碼問題。例如,我將eslint與Airbnb規則結合使用。使用該配置,以下代碼將強制linter失敗:
var fooVar = 3; // airbnb rules forebid "var"
很明顯,eslint如何為您的開發周期增加價值。從本質上講,它可以確保您遵循關于什么是好習慣的準則。因此,lint在本質上是強制執行的。與所有意見一樣,將其與鹽一同食用。Linter可能是錯誤的。
Prettier是代碼格式化程序。它較少關注正確性,而更擔心均勻性和一致性。Prettier不會抱怨使用var,但是它將自動對齊代碼中的所有方括號。在我的個人開發過程中,在將代碼推送到Git之前,我總是使用Prettier來美化代碼格式。在許多情況下,讓Prettier在每次對存儲庫的每次提交時自動運行甚至是有意義的。這樣可以確保所有進入源代碼管理的代碼都具有一致的樣式和結構。
編寫測試是改善您編寫的JS代碼的一種間接但非常有效的方法。我建議你需要適應使用各種各樣的測試工具。您的測試需求會有所不同,并且沒有一種工具可以處理所有問題。JS生態系統中有大量完善的測試工具,因此選擇工具主要取決于個人喜好。與往常一樣,請自己考慮。
Test Driver – Ava
Test drivers是一種簡單的框架,它可以提供高級別的結構和實用性。它們通常與其他特定的測試工具結合使用,具體取決于您的測試需求。
Ava是表達力和簡潔性的完美平衡。Ava的并行,隔離的體系結構是我大部分愛的源泉。運行速度更快的測試可節省開發人員時間和公司資金。Ava擁有大量不錯的功能,例如內置斷言,同時設法將其保持在最小限度。
其他選項: Jest, Mocha, Jasmine
Mocks – Nock
HTTP模擬是偽造http請求過程的一部分的過程,因此測試人員可以注入自定義邏輯來模擬服務器行為。
Http模擬可能是一個真正的痛苦,但是Nock減輕了痛苦。Nock直接覆蓋nodejs的內置請求,并攔截傳出的http請求。這又使您可以完全控制響應。
其他選項: 我也不知道其他
測試自動化 – Selenium
我對推薦Selenium感到喜憂參半。由于它是網絡自動化的最受歡迎的選項,因此它擁有龐大的社區和在線資源集。不幸的是,學習曲線相當陡峭,并且它依賴于許多實際使用的外部庫。話雖如此,它是唯一真正的免費選項,因此,除非您要進行企業級的網絡自動化,否則Selenium會是一個好選擇。
其他選項: Cypress, PhantomJS
與大多數事情一樣,編寫更好的JavaScript是一個持續的過程。代碼始終可以變得更加簡潔,一直在添加新功能,并且永遠沒有足夠的測試。看起來似乎勢不可擋,但是由于有很多潛在的方面需要改進,因此您可以按照自己的步調真正進步。一次接著一步,在不知不覺中,您將成為JavaScript的王者。
原文:https://stackoverflow.blog/2019/09/12/practical-ways-to-write-better-javascript/
作者:Ryland Goldstein
翻譯:奶爸碼農
局元素
語義化是html5的一個特色,他不僅能規范代碼,清晰結構,更好地開發和維護,而且還有利于SEO。當然你在實際開發中可以不用,但衡量一個人專不專業就在于一些小細節。
現如今是2021年,距離發布HTML5的2014年已有7年之久!當初對于HTML5標準規范,很多人張口就來說,瀏覽器不兼容、不支持,國情還是用IE的多等等。反正就是拒絕擁抱新技術和新標準。
不管這些快被優化下崗的老鳥愿不愿意,時代還是要進步的。智能手機的快速發展,win10、MAC等硬軟件市場占有率提高,畢業生扎堆涌入IT行業等等,直接、間接推動HTML5的普及率。現如今還有哪個瀏覽器不支持html5,甚至不支持html5都不是這個時代的瀏覽器。
所以如果你是外行剛進入it前端行業,尤其是單單看了一些老到掉牙的教學視頻,當面試官問你關于一些html5的問題,千萬不要在張口就說html5是新技術。。。 畢竟這已是個標準規范,不是加分項,而是必填項,還不能錯的那種。
言歸正傳,本篇介紹HTML5里的一大特色:語義化。相信面試時肯定也被問到語義化理解。
語義學是研究語言中單詞和短語的含義。語義化呢?就是用合理、正確的標簽來展示內容。更具體一點,就是使用新增的語義標簽。在HTML5以前,我們有使用過標題標簽(h1~h6)、表格標簽(table)、表單標簽(form)等。這些標簽都帶有很明顯的意義,無法亂用。
然而最基礎、最重要的html布局元素卻是被人忽視,絕大部分都是使用非語義元素(div和span)來布局,對其內容意義一無所知。代碼一多,實在讓人看得頭疼。
問你眼花沒有,通篇div標簽
倘若是以下的布局,相信不是專業的程序員,有點基礎的小學生都能看得懂!
Html5布局標簽
<header>元素描述了文檔的頭部區域,相當于一篇文章的頭部。
主要是用在網頁的頭部,或者某個部分的頭部。
在一個文檔中,您可以定義多個<header>元素。
header
<main>元素描述了文檔的主體區域,相當于一篇文章的中間主體。
主要是用在網頁的主體部分。
該內容在文檔中應當是獨一無二的,不包含任何在文檔中重復的內容,建議只出現一次。
main
<footer>元素描述了文檔的尾部區域,相當于一篇文章的尾部。
主要是用在網頁的末尾部分,或者某個部分的尾部。常見于版權信息、友情鏈接等等。
在一個文檔中,您可以定義多個<header>元素。
footer
<nav>元素描述了多個超鏈接的區域。
主要是作用于菜單欄、導航欄等多個超鏈接集合。
nav
<article>元素描述了相對比較獨立、完整的的內容區塊
主要是作用于定義獨立模塊,例如一個網站有娛樂、新聞、動漫三個區塊,就用<article>定義3個模塊。
article
<section>元素描述了一個區域或者章節
主要是作用于<article>的子模塊,<article>定義大模塊,<section>則是填充里面的子模塊;亦可以單獨做一個小模塊。
?
section
<aside>元素描述了和頁面內容幾乎無關的部分,可以被單獨的拆分出來而不會影響整體。
主要是作用于側邊欄或嵌入內容(廣告之類)。
aside
*請認真填寫需求信息,我們會在24小時內與您取得聯系。