🔍 搜尋結果:java

🔍 搜尋結果:java

MVC是一個巨大誤會

我是web工程師,從剛開始學MVC就深感困惑: - 怎麼每個地方說的MVC都不太一樣? - 有些文章講的MVC,跟我正在用的MVC,怎麼像完全不同的東西? Model、Controller、View三者到底如何互動?真是一個定義不明、含糊不清的名詞。 這讓我研究了很久。最後,發覺它是一個嚴重的誤會。 這個誤會導致了學習和溝通上的代價,請聽我娓娓道來。 # 哪些是MVC? web領域,不論前端(client-side)、後端(server-side)、不論什麼程式語言,幾乎所有framework都自稱、或被認為是「MVC」。 有哪些呢? 前端:Backbone.js、AngularJS、Ember.js… 後端:Ruby on Rails、CodeIgniter、Laravel、Django… 真的是這樣嗎?它們全都是MVC嗎? # MVC是什麼? 該怎麼定義MVC呢? 我們先來看看維基百科怎麼說: > MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller)。 嗯,跟大家說的一樣。我們繼續往下看: > 模型(Model) 用於封裝與應用程式的業務邏輯相關的資料以及對資料的處理方法。「 Model 」有對資料直接存取的權力,例如對資料庫的存取。「Model」不依賴「View」和「Controller」,也就是說, Model 不關心它會被如何顯示或是如何被操作。但是 Model 中資料的變化一般會通過一種重新整理機制被公布。為了實作這種機制,那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式)) 看起來有些陌生,整段描述跟你的web開發經驗完全不同,對嗎? 最大的疑問來自這句: > 那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式)) 後面直接叫你去看觀察者模式(observer pattern)。 問題來了:你有在view跟model之間實作observer pattern嗎? 也就是說,你的Model在資料改變之後,能主動通知View嗎? 沒有的話,就根本不符合MVC的定義。 # 全都不是MVC? 我們現在發現MVC有observer pattern這個必要條件了。 事情嚴重了起來。 client-side framework或許能夠符合這個條件。 以Backbone.js官網範例來說,我們可以這樣在Model上註冊: ``` book.on({ "change:title": titleView.update, "change:author": authorPane.update, "destroy": bookView.remove }); ``` 它的確實作了observer pattern。 但server-side framework呢? 你的Model如何能在發生改變之後去「主動通知」View? 你平常開發web哪有用到server push的技術? 所有server-side framework,從Ruby的Rails;PHP的CodeIgniter、Laravel;到Python的Django,他們全都不是MVC。 它們實作的,是昇陽電腦在1998年提出的「Model 2」。 # 什麼是Model 2? Model 2名氣不大,在維基百科連中文條目都沒有。我們看看英文條目怎麼講: > Model 2 is a complex design pattern used in the design of Java Web applications which separates the display of content from the logic used to obtain and manipulate the content. > In a Model 2 application, requests from the client browser are passed to the controller. The controller performs any logic necessary to obtain the correct content for display. 它針對web而設計,讓controller進行必要的程序之後,將資料塞進view去呈現。 正是我們server-side框架在做的事情。 也就是說,server-side目前只能實作Model 2;client-side可以實作Model 2,也可以實作MVC。 # 巨大的代價 web工程師最常碰的就是client-side跟server-side框架。結果整個業界把MVC跟Model 2混為一談,都說成MVC。 這帶來了什麼後果呢? MVC變成一個從初學者到業界工程師,永遠說不清楚、下不了定義的名詞。 這件事對於學習和討論,造成了非常巨大的成本。(參考下方的Q1和Q2) # 那該怎麼辦? 下次有初學者詢問「什麼是MVC」的時候,怎麼回答才不會害他回家之後「越查資料越混亂」? [Rails is not MVC](http://andrzejonsoftware.blogspot.tw/2011/09/rails-is-not-mvc.html)的作者提出了三種解決方法: > 第一個方法是聲稱MVC已經從原始意義改變了,Model 2也可以稱為MVC。如此一來,我們可以用「傳統MVC」或「真MVC」來描述原始的MVC。這是現在普遍的作法,但我不認為改變定義是一個好主意。這幾乎是越搞越亂。 > 第二個方法是到處推廣Rails其實是Model 2,MVC就留給…MVC吧。這很困難,但至少能保持定義不變。 > 第三個是直接忽略這些混亂。管它那麼多? 我個人覺得MVC這個詞已經沒救了,不管怎麼解釋都會帶給別人混亂。 當對方同時學習client-side跟server-side時,混亂更強烈。 我選擇這樣回答: 「MVC有分很多種喔!網路上全部沒寫清楚,你一定看不懂。 沒關係,你只要知道View可以抽出來就好。 C跟M先別管,你先隨便瞎搞吧。」 --- # Q&A ## Q1: 怎麼可能各大server-side framework都搞錯? 確實有人腦袋清醒得很,它就是Python的Django。 Django的官方文件內根本沒有「Controller」這個名詞。 看看Django官網的常見問題: > Q: Django似乎是一個MVC框架,但你們把Controller命名為「View」,把View命名為「Template」。你們幹嘛不用標準命名啊? > A: (前略)…如果你真的很想要一個縮寫,你就說Django是一個MTV框架吧。Model、Template、View。這樣分比較有道理些。 Django不想變成搞亂MVC的幫凶,只好委屈地又發明了一個名詞「MTV」。 ## Q2: 那client-side框架有受影響嗎? 有。client-side框架也必須為MVC巨大誤會浪費一堆時間解釋。 看看Backbone.js官網的常見問答: > Backbone跟「傳統MVC」的關聯何在? > …我們來比較一下Backbone跟像是Rails這種server-side MVC框架的差別… Backbone實作了「傳統MVC」,卻被迫用「傳統MVC」來描述server-side的Model 2,然後花一堆篇幅解釋。 ## Q3: 知道Model 2的存在又如何?我現在依然一片混亂! 沒錯,Model 2跟MVC都用到Model、Controller、View三個名詞,所以看起來類似。 但是,我們不應該再把時間花在思考「MVC怎麼如此難懂」。 我們討論的重點,應該是「如何分辨MVC與Model 2」、「在server-side如何實作Model 2才漂亮」、「在client-side實作MVC跟Model 2的優劣分別何在」。 ## Q4: 好,那你現在回答我,「如何分辨MVC與Model 2」? OK,就讓我拋磚引玉一下。 分別談談Model、View、Controller吧: ### View - Model 2: 不具有行為,只是等別人塞資料進去的模板(template)。 - MVC: 具有監視Model的行為,並以此去改變呈現(presentation)。 兩種View有沒有很像?跟張飛、岳飛一樣像。 看看Backbone.js官網的View範例。你server-side的View哪是長這樣? ``` var DocumentRow = Backbone.View.extend({ tagName: "li", className: "document-row", events: { "click .icon": "open", "click .button.edit": "openEditDialog", "click .button.delete": "destroy" }, initialize: function() { this.listenTo(this.model, "change", this.render); }, render: function() { ... } }); ``` ### Controller - Model 2: 接收請求與參數,轉交給Model處理,再把結果(最新的資料)塞進View。 - MVC: 接收請求與參數,轉交給Model處理。沒其他事了。 兩種Controller有沒有很像?跟小狗、熱狗一樣像。 MVC的View跟Model 2的Controller可能還比較像一點。(隨便說說,千萬別這樣類比。) ### Model - Model 2: 接收Controller傳來的參數,回傳結果。 - MVC: 接收Controller傳來的參數,將結果通知View。 Model倒是有些類似。 總之,Model 2跟MVC除了三個部份的名字一樣之外,沒什麼關聯了。 ## Q5: 我決定徹底鑽研MVC跟Model 2的定義了。給我一些延伸閱讀? http://www.ithome.com.tw/node/77330 http://andrzejonsoftware.blogspot.tw/2011/09/rails-is-not-mvc.html https://docs.djangoproject.com/en/1.7/faq/general/#django-appears-to-be-a-mvc-framework-but-you-call-the-controller-the-view-and-the-view-the-template-how-come-you-don-t-use-the-standard-names http://backbonejs.org/#FAQ-mvc https://r.je/views-are-not-templates.html --- ##一些社群的看法(2015-2-28) 附上幾個社群的連結,裡面有許多很棒的討論。 https://www.facebook.com/groups/javascript.tw/permalink/600136880087654/ https://www.facebook.com/groups/199493136812961/permalink/759391387489797/ https://www.facebook.com/groups/pythontw/permalink/10153760201638438/ https://www.facebook.com/mosky.liu/posts/10203964969186383 http://www.ithome.com.tw/voice/94877

給OOP初學者的建議:先搞懂「資料跟行為在一起」就好,其它的慢慢來

初學者接觸OOP,幾乎都會有以下疑惑: 我到底為什麼要學OOP?OOP解決了什麼問題?書上這些範例就算不用OOP也寫得出來吧? 然後覺得「繼承」、「多型」、「介面」、「抽象類別」等等的名詞很難,覺得OOP很難。 其實這些名詞雖然重要,但對新手來說,本來就很難在一開始就搞懂。 建議先搞懂「資料跟行為在一起」是什麼,以及它的好處在哪,就可以了,其它的慢慢來。 # 什麼叫做「資料跟行為在一起」? 假設我們在開發一個「中英文互助學習網」,鼓勵中文人士與英語人士登入討論。 這個系統的貼文、留言功能會顯示「發文日期」。 發文日期要根據使用者的註冊身份(台灣人、英語人士)顯示不同格式(台灣格式、西方格式)。 下面就以這個日期格式的功能舉例說明「資料跟行為在一起」是什麼意思。 ## 作法一:直接硬寫(不OOP、資料跟行為混在一起) 初學者通常會用最簡單、也最直覺的作法,直接硬寫出來,像這樣: ``` <?php $postDate = '2016-06-02'; # 假設資料庫取出來的發文日期長這樣 if (/* 判斷是否顯示台灣格式 */) { # 轉換成這樣 2016.6.2 $arr = explode('-', $postDate); $year = $arr[0]; $month = $arr[1]; $day = $arr[2]; echo "$year.$month.$day"; } else { // 西方格式 # 轉換成這樣 6/2/2016 $arr = explode('-', $postDate); $year = $arr[0]; $month = $arr[1]; $day = $arr[2]; echo "$month/$day/$year"; } ``` 這種寫法的資料(日期)跟行為(轉換成各種格式)混在一起。 它的優點是寫起來很簡單,缺點則有兩個: * 日期格式的邏輯會重複出現在很多地方,整段code會到處重複出現 * 整段code大概會塞在 `div` 或是 `span` 的裡面,導致它跟HTML混在一起,很亂 ## 作法二:自訂函數(不OOP、資料跟行為沒混在一起) 為了解決作法一遇到的問題,聰明的初學者很快就想到可以用「自訂函數」!就像這樣: ``` <?php function localFormat($date) { $arr = explode('-', $date); $year = $arr[0]; $month = $arr[1]; $day = $arr[2]; return "$year.$month.$day"; } function englishFormat($date) { $arr = explode('-', $date); $year = $arr[0]; $month = $arr[1]; $day = $arr[2]; return "$month/$day/$year"; } $postDate = '2016-06-02'; # 假設資料庫取出來的發文日期長這樣 if (/* 判斷是否為台灣人身份 */) { echo localFormat($postDate); } else { // 英語人士身份 echo englishFormat($postDate); } ``` 這種寫法將行為(轉換成各種格式)用自訂函數給獨立出來,也大幅改善了作法一遇到的問題。 對小型的網頁程式來說,這招非常好用,不但開發快速、簡單,還漂亮地將資料跟行為拆開。 但是程式規模變大之後,為了將各種行為拆出來,會寫出很多自訂函數,類似這樣: ``` ?php function localFormat($param) { // blah blah ... } function englishFormat($param) { // blah blah ... } function someTask($param) { // blah blah ... } function anotherTask($param) { // blah blah ... } function otherTask($param) { // blah blah ... } //... ``` 於是又衍生出三個問題: 1. 像localFormat、englishFormat這樣的函數名稱意義模糊,看不出是處理日期、人名,還是什麼東西的格式 2. 這些自訂函數各有不同的行為,全部放在一起顯得很亂,應該要想辦法分類、整理這些函數 3. 像localFormat、englishFormat這樣的函數,只吃特定格式的參數,最好能跟某種資料的形式綁在一起,以後要改程式時,能讓相關的資料跟行為一起被看到 問題1很好解決,只要替函數名稱加前綴字變成dateLocalFormat、dateEnglishFormat就行了。 問題2也很好解決,只要多開幾個檔案,把相關的函數放進同一個檔案就行了。 問題3就很棘手,資料跟行為拆開之後,如何在概念上又找方法整理在一起? ## 作法三:使用class(OOP、資料跟行為在一起) 正是這些處理資料、整理行為的問題,導致了OOP的誕生: ``` <?php class Date { public $year; public $month; public $day; public function __construct($date) { $arr = explode('-', $date); $this->year = $arr[0]; $this->month = $arr[1]; $this->day = $arr[2]; } public function localFormat() { return $this->year . '.' .$this->month . '.' . $this->day; } public function englishFormat() { return $this->month . '/' .$this->day . '/' . $this->year; } } $postDate = '2016-06-02'; # 假設資料庫取出來的發文日期長這樣 $date = new Date($postDate); if (/* 判斷是否為台灣人身份 */) { echo $date->localFormat(); } else { // 英語人士身份 echo $date->englishFormat(); } ``` OOP的寫法,一次解決了前述三個問題: 問題1 => 現在從類別名稱就可以知道底下方法的意義了 問題2 => 現在相關的函數都整理進同一個類別底下成為方法了 問題3 => 現在資料的形式都統一在constructor處理一次,之後不管新增多少方法都不用處理資料了 這就是所謂的「資料跟行為在一起」,也正是OOP的核心概念。 利用這種方式整理程式碼、寫出一個又一個的類別,可以大幅提昇程式碼的品質。 # 結論 上述的作法一跟作法二並沒那麼糟糕,但確實會帶來一些問題。 對於小型的網頁程式來說,可能還算夠用。 但是隨著程式規模變大,如果將概念上相關的資料跟行為整理在一起,會很有幫助。 實務上也可以先從作法二開始寫起,直到發現某些資料跟行為關係密切,再拉出來整理成類別即可。 至於很多OOP教學會提到的「繼承」、「多型」、「介面」、「抽象類別」等等名詞,一時搞不懂沒有關係,你可能實務上也暫時用不到。之後找時間慢慢搞懂它們的用途就好。 光是知道「將資料跟行為放在一起」的技巧,就能夠開始寫OOP程式碼了。 (註:本篇文章的程式碼純屬教學用途。實務上PHP已經有DateTime類別可以使用,或是用更漂亮的Carbon類別。) ## Q&A Q1:我常常設計一些類別,只有資料沒有行為,聽起來OK嗎? 不OK,這很不OOP,而且沒意義。 乾脆直接用關聯式陣列去表示那些資料就好。 Q2:我常常設計一些類別,只有行為沒有資料,聽起來OK嗎? 這個要看情況,不一定。 但唯一可以確定的是,這種作法很不OOP。 因為OOP的核心是「資料跟行為在一起」。 這也是為什麼你會看到有人明明寫了類別、用了物件,別人卻說「這不夠OOP」。 然後你又會看到像JavaScript這樣連「類別」關鍵字都沒有(ES5以前),卻能夠寫出很OOP程式碼的關係。 判定的標準都是一樣的,而且也就只有這麼一個標準:資料跟行為有沒有在一起。 Q3:一個類別包含的概念是越大越好,還是越小越好? 不一定。不過我們從作法一到作法三的過程,有一個明確目的:希望讓程式碼更好懂。 如果聲稱一個類別包含的概念很大(例:設計LanguageHelpWebsite類別,用來代表「中英文互助學習網」需要的所有功能),那會把幾乎整個網站的所有行為跟資料都放進去,成為所謂的God object。它可沒有讓程式碼更好懂。 相反地,如果聲稱一個類別包含的概念很小(例:分別設計LocalDate、EnglishDate類別),雖然意義可能更精準了,但用一整個Date類別的概念去思考,程式碼會更容易理解,也就是所謂的內聚性(Cohesion)更高。 所以要替OOP就是「資料跟行為在一起」加個但書: 要以方便理解程式為前提,將資料和行為放在一起。

JavaScript 系列九:發表作業&心得分享

第1課 ── 學習 Vue 元件基本觀念 [作業](https://tinyurl.com/yst8ytys) 第2課 ── 學習 Vue 的 props 觀念 [作業](https://play.vuejs.org/#eNrtF11vFFX0r1w3MW0JM7MYiWRZQCE+YBSJPLo+zO7c7oydvTPeuftRm000lQCWpESMoCFRowIJKU2MMVWqv6azLE/+Bc/9mrl3dtvSiPHFPmznnu9z7vm6a7U30tQd9HGtUWtmHRql7GyLRL00oQy9s3reD7r4MsWDCA/RMk16qFVzPRvOuVu101WuGfKCrkXwSFAGeNnvxwyttQhCnQTYCSYsa6C1iurjhdDxcU4b+MxfXJJ8CFHM+pToE0JpkjGQoY9cdJzQBtgyDCMGNggZ8o/hEbug0e3Y76xY6Nhv45ijLChN+iTAQQUuTeN/bW7qmyO/l8YYvHn/A4UZg/OarodZmATcV4lL+0xxlI6BeWGUuZY4N+1n4eIacl1XILmzaLwkJEtfSZbE2I2T7uIst6aTNsjfoc86YWmHHbwA47SBGO3jwrvQJ0GMqWkmBCrp+PEVllAfLjrD7CLDvcVWrSevjV4GqRAq9NaVdy+5GaMR6UbLq9JArnGpdECHUf+vBPMFWVagEb93U4V102iuxXZIS+r9nFA3DlnDcFBaV97fGakm9WmGFy2Du/uEcmkJnTtXSlCKZ61DZ7RJh2uohMFQYWGKJObp3PSKpgEHkJPGPsNwQqgZRAPUif0sOwOyk2C1VRPwCiYikPoXEsL8iGBa0ACVKL7iiND00cazT3cmt69N129Ovt5+9v3m9MbPJbopJCG2mmIQK2q+VSvUiPNFTgHAgdNLAhwDmAfP1bReqduzle9vy+Sra/nWnRdnS9GQDrZHyDc6nJIsqJRko8lJM7hoE1zRLHhNPFxlB4dJHGAKNODxdHcnv/qTSIb86gPwvKQ2TK2GanJ3O791f3LvRn7v9vTBFzNB0rarrqrjomMX4s5KOxnNRkrRmzbMhKndZywh6PVOHHVWOGPRZSHR8q27k+u3nm5/tvfnRtOTpDpDPUjRedmayoE0N1/DE2fVvGp68F3AK2O0UZyTVIgUBW1GEJh1sdmSTEuUlLJejZBrlXJ+AfElPHxbXq8chjzcPHR6lBnaD+Yu0vNoEgy9XYoxOYTPbMADZ1nwmT0IJcv2XDDTFqGGVmvSzOY3EJruWMRGGVoM2guLWNW3Rajcq5LOJK1VOmbWFd/6q+kZ7RWOGVuN+ecxOU96Pu1GpIHqoj+nfhDAyJLHcYu4vAFrwpEzjAIGM/9U/WVB3YuIE+KoG8LoP1XPBqGABlEG6lYbaDnGI1PMWaR0coTT9rMIBvOJuhDGieyWLkmrwhRzEFHcYVEChkMU+z0iUB/2MwbT1oFlhsFGCDj4xVTg/DjqEgf2uB4oNeBdH9aBExT3lBHVSv2PzKiU6YFWDCln5r/zRMGoVTdeO66rX2/uLzkOH0n5k9+fPtrQyzJHovzq+t6TX//avbm3c3/y5fZk85e9nY38+p3882+n63+0iMhdBJ/5j99NH27lj7+xpysvBAhAlcgee6K05uBhSuSPf2sRXeuThz/ku5stkm+uTz9Zb4SMpVnD8z7q+5lP3QAPPDAZtOmXgCdqx0OOo1Je7xrzHhAp76fFgqh2/itic1PbmLHsW3C15VuwYsc/n8A67ZNikXuOrUdZoFcf4/513TfEVQKyWFTb8PToCqUiBRNabKHKk8L48pmRUBjN7/lB1Ae/dYzPoYWT9XS0gBpoob6g91CpWPWWtTXpMhpDWh3eYKz1T8Y3hooqOkbdVUkKaQyV4mTRx5hDT2loke4REYxtWEBXBEa6AFmejhA8W6IAiTdYKWuodLxWr/Y191Ut3juGBpiyCJZaR5QlBCtJT6Nj3r51Yzxd/y8fu3zKp7c8j/9B1qswP1/Wm3rlSK2WgEVxWD1YxHOLA/7vVx4W89FrRa96s7Uyr1Je+Xfr5OTR62T8N2L3XWg=) 一開始方向錯誤,沒有懂老師出題的用意,就照先前的想法做了控制面板,後來才想清楚不是單純用vue做東西,而是做出quasar這種開發模板用的componen 第3課 ── 學習 Vue 的 events 觀念 [作業](https://play.vuejs.org/#eNrNV1tvG0UU/iuTVVHsNL7kIUJaArQplwgRhIjEC4vU8e7YXrI3ZmedGMtPIaJtIvJAaWkkbiK0qaqmCCGakhT+C/I6zlP+Amdmdta7rhPaChCWsto555uzc8755ptJR7sYBOVWRDRdmwtNagfsFcOz3cCnDC225yPGfA/Vqe8iQytXlIXPMLSXDM/wyKrAWqSOI4ehjuEhZPoQwCMeC3XUGYbpTnOnhRkuFCUQIUpYRD01QijE7QX7bTtkOvpA2dDQzX8BphD69RY8dMSadlgWk0Rw9XNwjTg6rHnRdw0t5zJ9x6fcRYk14mJklV1S7gYlxBsBhPYnREfV8mzOSv3Is3RUx05I8vCPI1grLJJGWYesw1NltmyH4Xw7HzXN7TU8mkCam2M3mqzm8C6dnmEQ0cAZRagUXxyX4kgiwwxHc3+GFHmdme01xuf4FjaXT0vSwnQZZrfPzNH2RufLDGeeKb9/qYOLtIwuehahoT9KtXwr28Rx/JWzEh3T7P8XW8+flmDNebLHudRWmjYbn9vYJj5XZv9EhqX/KsPn2IYfJm9dEG1ldwlr+hbXaOkTMlrwUm1GCDuEsoKhLdiwSHQeefBnaBMThlYUcYZfSAozdjbY89P//HrnZH/rySBKCsZGeTNxnr0S+VzBzGxCXnLEj6OIEShcYnChjDDmpxA38JrMVdLTDwaMuIGDGYERQnOW3UKmg8PwZWilb4HeCDt41NGm1toq1X0KKAcOMOTXh6eZoSnIBUmqEuGsSqDlDNGGSF0QS0HEIOMU5FJOMcg4OblKOURKtwyKM0z5+XvGJYimfGKAXhW0QwnJsmEE+dJAYnQKuiILWoGKwttcJVNnGIas7fDXKdl8F9OG7YGAiQYH2LKg+7AjKHHB0jW8Mm+GxK7YFoOGz1SrLwh0k3DVFIaw1RQmyw7hW21YkUNWhaWBAyGPMiASjtIK5Vb+FDYM8uuVYHe6sE9ChimT3wa+JKvVprXstYhfpCZKJTS4t9F7/MfR9d1476v+jUdH25/G62u9g18Hd28ffX43fvhjvP7w5HD7fSjS0huX0LuwtIYoNC9Fk7Eg1CuVsG7ymB+FZZ82KtzDqwih44Pfju5tpFcrDkIy/snhZm//dv/6g/7WL739jfjKzfjat4O1x3Kq4ASCYbzz3WD3fry3PVjb7N96cPz91uDqzxIzJE8O2L/xWXz/ZhYoKDkGE6/fifceJZoCvMpDNq8eX/ky3rkT/7SVXAEFvfq7P8SHiSXhUNZ0wXRsczkX6fjgm/4X1+LfN6G2vYODeOMWKpU4rdKdPO5uGlAfSqvkJZHoJUaHN5CMMufsic7nbFKk34ncGqGJKdHoed93CPYULhHorPVppKeWkRclQa4SnWRTydKA4xxxbVaYzIjJ5LRcdVFhdcFawKbyWoOjSRJPl9VIj4ykNmk5UkfNp3BleQ9bdgSVXMSsWca1sFBQOjFbhX1fLaISKqRqMCtMRVDtyWB1Mg3l2B5ZSPbqZRM7ZuFch9e0i6aSnVm8nILrvseWRMFz0CkhCRlcKhV/G1FmMg5YFUgU+o5tIXF+q1ldWUt5BHQ6yS7ogiZwZZMNO0Pcyqp9koLZ8nvwP5MQnWRZ6diM4IJI9cC34diiifipOHrTbxEKwQwWQhpEnynP8HUKiuWEqvsXUnaEYQ==) 嘗試從quasar的原始碼反向工程做出一樣的效果 第4課 ── 學習 Vue 的 v-model 觀念 [作業](https://play.vuejs.org/#eNrNVltv3EQU/iuDVdQN2mtuTc0m4iIh8YAEqtQXzIPXnt012B4zHm8SrRa1TSuVhrA8tAmKkKDqRakCSSsQDckGfgzr3eQpf4EzF3udTSraPvXB9pwz5z7nfOO29n4QFFsR1nStGlrUCdiC4TteQChDnyx/7AcRQ3VKPGRoxZJicHlDe9fw8ZKQs3HdjFyG2oaPkEVA2cc+C3XUTk108nzPNpmZm5ByCFHMIuonFEIOl7xquhHWkR+5rlBBqAOO4C0oD7MmsbllSS+azGqmFHcdMWynDI9EPtDcJWdwS9VSmiUQDHuBazIMFEJV22khyzXDcN7QasReNjTBhx2VRhKp7hEbuyJUEB3FbWiJxHtRAMni04I5Hy+K9QSaX0C5kR6aR+nWyIZr1rALanXHdbE94itaUKU3IkISMdfxszGmnEyU1RIUGFbVUqbsQIZs2eXLd2QreCZtOL6OyuLcA9O2Hb8hyY7hF/nBSMFFx2Zw+JVy+W0h2sROo8kEI2w1Bct2QnC0rKO6i5cEhy8KtkOxxRwCXiziRp4vtmqm9VWDQsvYBeASqiPaqOWm5vJo6hJ/JmQE0EEqYC2vZSaCT9BbhQI62l7tH/4zvLsV7/w4WP9ruHkzvrXSP/jz6Mnj4fdP4ueP4lvPT3qbV6GoVz76EH0K8UmvvBhNxoJQL5XCusVtfhkWCW2U+A6vI5iOD/aH26vJXHEZJM2f9L7r7z0e3N0ddP/o763GtzfiOz8frRxKzVZBnDMCRvzwl6Ot3+Kdzfha799r1+Puevz3BqyloDjSU2KDX2/2D9fiH9aGW89Oerfj7m7/4FF/b41nuNJFn0VmaFIUr94b3ts+vvFgsPXgqLt7vL8BAcVPnw2f3o/Xd47v7w4f7sc/bQ02rh/fuBOv/Q5mTnrfSp+yoRFoxr2uZCX9o5ioUFC9kgzvedATUALFSwBFZKKjK4xC/ygskZ509AEhLjZ9xU2cjfNHo8GhjC0H8FXmEscZuFIQJd/YczgAfm5oZ8bM0L54RSyyiM9MCJCOAElMZgqcdjLjowHUE+V2mrT85jPpplXujBR5mqDG8FLWWksBRDaNFEmEa9i8wLPOXTyT8cU8uoBbcCUUGcw2hq4dwxGKv45gJMcxTfZindBRegvttmrRTqdaEquXwpZiWkPVKiR0JABQDNJOCwsISLBnphxwuIBpN3zpbkzLrIUAHExqubguYEfoQAVJkKWo6ada0CWT5bIXIl52kwqBDNZU5qbyqHLpsngpuIHbWNygWcgDG8p6Co+TwHlppyOzep1YUYi+kUWFvpRcOCDHTrjStcgqsZ+JeW4mjyYrk/CameEhIxRapgszUS5eTlwV1YSrH4QX55sFYSkyC9A7O8sftU+ojUHdh3+MDKNQI4wRD+oAZYCjgejP01YNn6rzC0WNR5O0MIUCJLQsjIj4hS4Wm45qAVBiPKwa5XeQj8MwVymKesgCpJP2GiWYhhtoGpKYHitB5VSq5xg6L9t0+NN8U86ZjMddnD3q1MP/CZ5bn8rZG7XzH5rEyeY=) 同上 第5課 ── 學習 Vue 的 slots 觀念 [作業](https://play.vuejs.org/#eNqdVd1uG0UUfpWDEeJH3o0TCqkWUylBofiiroQbIaS9Ge+O7VHXM6uZsR0TWSoqkVADBFAFFarUIgpEKj8XVCjQwtNk3fQKHoEzM7trO2mo2ht7zpnv/M45325X1tLUHw5oJajUVSRZqs+FnPVTITVcGK8TzqmEjhR9CCv+UqExFmHljZDTLYuMaYcMEg3bIQeIBJpzyrUKYHvmZFI1lzHR5KWXHRBAUj2QHLYn6ApyRJ/qnoiNrZNHREe9UjLOB5rGpaIvBhxl49MojKf6UlkJCpr204RoihJAPWZDiBKi1JthpS3icVixerwpE5XoMaZxrgd4j0JE+IsaOozHMBYDCYoMaYzZRyylCjABlpgLUKzLgXHfBHbGZXgYeioROiCRZoKXzhHRHmiNmrcuNi81mpsbsNaCNTi/udG6VF/K7wpMq3G+CY1mqS+jLC1UaRRFObYJ/19go+OSJyPQPaagbWFVFGh+xkdANWW8C4pSDu2xsXimMt/faJ0oq3nxhOrfWzduPGWZRsTnxdMCDkWlx4k5vuLmrk9kl/EAanbqUhLHWJgTJyH3zVg44IjFGkdvuVZ7wUJ7lHV72irUsGdVMVMYaBxAV7LYarokRYSkfSuRBEfCiwTOKEdLpYnULk7IXXUuVFvImMoAOG6ONWyT6HLXvlSAE9Wjkhk7swCJQBz6jWg7GThwB/17in1AsQr/bBE7GkhlsKlgGF4eq9Z/rcClQjHzWgFONDaNDY1TzNDlFwSkg9bFbueFhBW7//PGpK1Egrtp1YwrijDX4lkt2Amb/qjHcqBIScQ0NtBBtSS8cJhfYaorChLGKTE1zLUu6IkhNm0+wZk7f8WBkQ3y569UK/MEZijvOc+DkB/d3c3u//nw7m7JVuYesp2rh/d//+fBJ4cHP0yv/zrdu3d4sJt9/HV27dbR1b9C/uj250f7Pz/85iMwIw+oy+7cnl7/O9v5Ptv5KfviWrbzY/bLHyF3yzCPym7uo6/DgyvTz76c3ryS7f2WfXjPmT769tPszleFab6rMN3/LnuwB55nxrukt8fRbypFapk3Nw1gXYiEEm759Wn40Tw2wbbLGUma66C4nwuRH6o5XwT4jtjACRrWTdXnMKT7cwta+ipcuQ4VcOCkTxe0x82ftOl+mXvelFNG3M/5LeeFLW+28mo4esxurZZbY/WjnBHO1Nz4min1SprwV0p0QjXOqKfMeLr1qy2vnn193leH9FmCk/uuaAstquCRNE2op8YKa6zC2pByhqy8jjEuXyBRy+rfRssqtGhXUNhs2C8iwDs0GVLNIoJWkpGkitzOlaeQRDqnLOTzq+3lDjErcwrFlMRhqC1nD3jVlWcamc/4sW4vcIIWyIy5CX77XZNKuWTSTkK3XFfw4I2k4VPze4JdLRP4xYbM0agnScwGuARnUuNpkQQm/wFjNTqv) 嘗試模仿quasar拼裝不同呃component 第6課 ── 學習用外部狀態管理 vue 元件 [作業](https://play.vuejs.org/#eNrtVktvFEcQ/ivFhshrsi+bWEo2hgBOohwgQiBx2gO9M727HWamx909axtrTwQFsBUOIRA45KEQHkJAFEUBYkj+jGdtn/gLqX7M7KyxwcohyiF7mO2u/rq6q+qrql4uHY3jWj+hpWZpVnqCxepwK2JhzIWCE0vHEqV4BB3BQ2iVavVMone0Sh8UkB8xEvBuEWklObIV0UWD9WmHJIGC5VYE4HFUENFIySYsw/wxFTXzYyswb3VokdM/qOhdPlGkPGk1AAiqEhHhdhJQoZrQIYGkFVQddZgI3RwGeAdw+0OqetzXR1oNZuPU9MGRTicro3DSbMy22u8CUV4Pt9uZNiJR1M8FIU8inGt1WqCPnq3n3sWJomEcEEVxBjA7X20rlOpDAtKmwaFW6ag+vVWyQo8HXKCwHRDvXCZUdFFVs5WFHlPoZrvSlOw8ReFUJhB4Hd8Oj8REoLertI9fxBgr4RDsMwO74fBs3dwou5xvXd+vhtw3l3NYs44In/XdEOA4FzQEFsskBF9fDiRTQNDhOh6SekoHC4jPYiaZx6Iu0ICpGpyiArcIGtMgoJGfyEyj3t/nQRIr9Fc2kqANYKgVKSV4th0dFPlMMSYhpqJD0VQfx4orEprA6B8lQBTwMMKVPg0gSsL5hIRAF6nwGJ7CkJAhkERVICIqkRAy5Dmp2LN0iNDImjO+PrK+GMdRJOeOc5mHZpdYvi6aO8RzLKJ7i6n+dQJO9Lpg3V4uHg82jm20dyXmnM2qf4GaLn+1IW64R3rm6P8J+t8k6G6R/Qckna0XSilOpVoK9PCALeQhEV2GLaVhanhMfB8DaqeDVtTm/pLFLTBfYT2fajTeNsge1ccbgez3jKiN7ugas6wbmvDWND3oHaRWF9Z3d3SpUio2v7HGulMLjAWPTfcz/D1DgoQ24RjnASWRaTdvah8YYPACIqV1rSIsoqJVwoxgHRSN1I5lRL5F9ojPF3BtjCpFxAKLECE1RAZcIdD9ZfhstFs0avm1nMkckwZZ3ATSlpgwSjsRgGH+odNtsDAvUNNSE7qC+TZ8yEiKzsfWGiHMwy8V1vujA5pt2kH2Z68Lh0Ui6TfI3k5+NdKi2y5PvT9dgemp9/Az494EPCaYj3jFRm3GCDoswCs1oW3YG1Epy7hkweer6EW6mJOv5rzqPJJRcwrL025863Q6Zikki1XH2JlGI160eC58KqoCK1eCdHrXifNjp7Ydexi250htxp79KptHjz7N5n3VKmw+WFl/8dfGtXvpo2+H159t3PoivXhhfe33zft3Nr66nz75Ob345OXzW2cSCqc/mYOTGElrjOZFTylkfL0uO57W+bmscdGt6xVNPVSdrv2x8WAlfwhqEFj9L5+vrj+9M7z2eHj1t/WnK+mlG+mV7zcvvCh0I8BpevuHzXsP00e3Ni+sDm8+3vrx6ublX7c3pzHg8PqX6cMbRaCpjztg0ot300fPLEYXvnHI6uWtS9+kt++mv1wtdDgY3vspfe4kEqs5MrQoOuIFzDs3pmlr7bvh11fSP1fRt+tra+nKTahWdY7tqZgU/NGE00ogt1x/0fbP7SA35m6TafOa8FkStqlwIlOft8GMiXnNyvYaK7dL+3MBdpuTMY+TeHxtL5WubejgbHPFKXQkyXqFdSUu7KchU+UJ23s+1q1nomKtnBw9RzTLEZu/+kdZhwVGeynvys6XufvyBZt4p1zenSCqV8PaUi7bwH+IKQqYXJNQhbKLPMqMaBLegYl4cSJXFWAF+9S1nrMeCbzy/mUdgwHmqk3PybM5uIPF7bQJ0Bj0gCkhBVxeWt6o0VqyE7BhkIAVk/n6EeOdK9zDMsL8OeHAvdTsbHnZ5dIAK4tuFjaMr+sXWVAtkYtBifDtU6h3BYGXCKkjFHPmGgOSXZAoK/gSraIw3WiE+Dgj0vXt/Khmj/ez/mSgWDJrrmgWyuHgb/u4VhA=) 稍微翻了quasar的原始碼,還是搞不懂為何上一個v-close-popup的attribute就可以讓按鈕有關掉modal的功能 很神奇 第7課 ── 學習開發狀態複雜的 vue 元件 [作業](https://play.vuejs.org/#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) 研究了前輩的作業才發向若要讓input的值可以被檢查就不能限定number,要不然輸入非number會直接show出來,卡這個很久... 因為想學quasar的樣式(range條的顏色變化等)做了些研究,後來嘗試把所有想法塞進去,結果做出來是蠻炫的但不是很好看... 心得:覺得老師的出題方向就是幫助學生從業界的需求去學習,然後一點一點從小地方開始做,並且能夠自行研究,而且不強調怎麼寫,而是確實做出來,很務實的取向,覺得獲益良多,上完9堂課默默繳了不貴的學費,有新的課堂會再回來做。 目前正嘗試自行開發一個網頁應用,寫cdoe真的要很有耐心,其實想想現在有很多輔助/查詢工具真的很方便,越寫越佩服那些做框架的人...

新手適用:重構程式碼的幾個簡單方向

## 介紹 程式碼重構,在不改變外部功能的情況下改進現有程式碼。它是寫程式的核心部分之一,不容忽視,否則您將無法獲得更好的程式碼。程式碼重構可以增強程式碼的可讀性、可維護性和可擴展性。它還能提高性能並提高開發人員的工作效率。 今天,我們將研究一些可以幫助您重構程式碼的技術。 原文出處:https://dev.to/documatic/5-code-refactoring-techniques-to-improve-your-code-2lia ## 如何整合Refactoring 在尋找改進重構的技術之前,讓我們看看如何將程式碼重構整合到您的工作流程中。您可以使用以下建議: - 你應該專門分配時間來重構程式碼。 - 將較大的重構問題分解為較小的重構問題進行管理。 - 嘗試讓整個團隊參與重構過程。 - 使用可以幫助您查找常見重構錯誤的自動化工具。 現在,讓我們從用於重構的技術開始。 --- ## 提取方法 此方法將程式碼轉換為單獨的方法/函數。這樣做是為了改善程式碼的結構和可讀性。它將長而復雜的程式碼提取為更小且更易於管理的方法。 要使用這種技術,我們首先需要找到一個執行有點複雜的特定任務的程式碼區塊。找到後,我們提取程式碼並放入新方法中。另外,請確保為該方法提供一個有意義的名稱。 **例子:** 重構前 ``` function calculateInvoiceTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { const item = items[i]; if (!item.quantity || !item.price) { console.error('Invalid item', item); continue; } const itemTotal = item.quantity * item.price; total += itemTotal; } return total; } ``` 重構後: ``` function calculateInvoiceTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { const item = items[i]; const itemTotal = calculateItemTotal(item); total += itemTotal; } return total; } function calculateItemTotal(item) { if (!item.quantity || !item.price) { console.error('Invalid item', item); return 0; } return item.quantity * item.price; } ``` 您可以看到我們如何將在 `for` 迴圈內執行的複雜程式碼轉換為另一種方法,以實現簡單性和可讀性。 --- ## 使用常數 此程式碼重構是為了編寫更清晰、更易讀的程式碼。直接寫數字可能會導致其他人感到困惑,因為他們的目的沒有定義。將硬編碼值轉換為具有有意義名稱的變數可以幫助其他人理解它。此外,您可以為其加入註解以進一步說名。它還可以幫助除錯並降低未來出錯的風險。 **例子:** 前 ``` if (temperature > 32) { // Do something if temperature is above freezing } ``` 後 ``` const int FREEZING_POINT = 32; if (temperature > FREEZING_POINT) { // Do something if temperature is above freezing } ``` ## 合併重複程式碼 重複或相同的程式碼可能出現在來自不同地方的程式碼中。此程式碼不需要完全相同,但它可以執行類似的任務或從原始程式碼擴展一點。重複的程式碼會導致幾個問題,包括增加維護成本、難以更改程式碼庫以及引入錯誤的更高風險。 在重構程式碼時,您必須注意重複程式碼。找到此類程式碼時,一種處理方法是將此類程式碼轉換為單個可重用的函數/方法。 例子: 前 ``` function calculateTotal(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } function calculateAverage(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } const average = total / numbers.length; return average; } ``` 後 ``` function calculateSum(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } function calculateTotal(numbers) { return calculateSum(numbers); } function calculateAverage(numbers) { const total = calculateSum(numbers); const average = total / numbers.length; return average; } ``` 在之前的程式碼範例中,我們進行了求和並再次求和以求平均值。之後,我們將其替換為為兩者提供總和的函數。 --- ## 簡化方法 這與您正在尋找要優化的方法/功能時進行辨識非常相似。此技術可以幫助您減少程式碼行數。 此方法可以分解為更小的程式碼塊,您可以在要優化的函數中找到這些程式碼塊: - 刪除不必要的變數和表達式:可能有一些變數或表達式是您為了除錯而遺漏但忘記刪除的,例如 JavaScript 中的 console.log。 - 使用內建函數:有時使用庫或語言的內建函數會更好。因為可以用更少的程式碼實現相同的功能。 - 簡化條件語句:如果一個方法有複雜的條件語句,可以考慮通過組合條件或使用三元運算符來簡化它們。 ## 使用延遲載入 這是一種僅在需要時才載入物件的技術。這可以通過減少使用的內存量來提高應用程式的性能。這導致更快地載入應用程式。 這種技術在 Web 開發中非常流行。特別是對於像 React 這樣的 JavaScript 框架,您可以通過延遲載入來導入不同的元件。這也可以用於根據需要載入圖像。 **例子:** 前 ``` import React from 'react'; import MyComponent from './MyComponent'; const App = () => { return ( <div> <h1>My App</h1> <MyComponent /> </div> ); } export default App; ``` 後: ``` import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); const App = () => { return ( <div> <h1>My App</h1> <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> </div> ); } export default App; ``` 在更新版本中,我們使用 `lazy` 函數異步導入 `MyComponent` 元件。這意味著該元件僅在實際需要時才載入,從而提高了我們應用程式的整體性能。我們還使用“Suspense”元件在載入元件時顯示後備 UI。 ## 結論 重構是任何想要提高程式碼質量、性能和可維護性的開發人員的基本實踐。通過花時間分析和優化您的程式碼,您可以消除冗餘、降低複雜性並建立更高效和可擴展的應用程式。 通過不斷審查和改進您的程式碼,您可以建立更健壯和更有彈性的應用程式。

# JS油猴系列-遮蔽廣告(D卡未登入)腳本、自定義新增名單

## 前情提要 逛論壇或網站常常有惱人的蓋板廣告,要馬就是要你登入、註冊, 要馬就是搞一堆課程的販售廣告, 對重視使用者體驗的人來說,真的是一場噩夢! 為了解決這個問題,我模擬了AdBlock的其中一個功能,刪除頁面上的元素! 乾乾淨淨才是王道阿。 ## 效果 按下ctrl+Q啟動刪除元素模式: ![](https://i.imgur.com/411nh3Q.png) 被選取的會加上邊框色彩,讓人確認是否要刪除: ![](https://i.imgur.com/zPEKMhC.png) 點選確認後: ![](https://i.imgur.com/nnOY4Ji.png) 煩人的元素就此消失在這個永恆的宇宙裡。 ## 觀念筆記 ### 套件一:toastr ```javascript= //toast.js cdn匯入 const toastJS = ` <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>` $('body').append(toastJS); ``` ![](https://i.imgur.com/lNDch3V.png) [測試效果反應網站](https://codeseven.github.io/toastr/demo.html) ![](https://i.imgur.com/v95GUfO.png) [[十分鐘學習] toastr - 簡易提示訊息](https://ithelp.ithome.com.tw/articles/10197861) ### 套件二:SweetAlert2 ```javascript= //設定cdn使用Sweetalert套件 let Sweetalert2=`<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>` $('html').append(Sweetalert2) ``` ![](https://i.imgur.com/qUeTUlj.png) ![](https://i.imgur.com/FJH8EMX.png) 有提供很多API範例,還可以自己定義可愛的小動物XD [官方網站](https://sweetalert2.github.io/) ### 監聽鍵盤組合鍵 ``` document.addEventListener('keydown', function(e){ if(e.which==81 && e.ctrlKey==true){ //..... } ``` ### Object.assign 動態修改樣式 ![](https://i.imgur.com/AzZMRCc.png) [用JS動態修改CSS的三種寫法,花式JS玩弄樣式,嚇死你的同事!](https://codelove.tw/@JsLover0018/post/Ja64Kx) ## 油猴程式碼 ```javascript= // ==UserScript== // @name 刪除廣告元素 // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/* // @require https://code.jquery.com/jquery-3.6.3.min.js // @icon https://www.hexschool.com/ // @grant none // ==/UserScript== (function() { //--------------------------------------- // 宣告變數 let set let Check = true; let List=[] //設定選取的border色彩效果 let styleNeon = `@keyframes neon-color { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }` $('style:last').append(styleNeon) //設定cdn使用Sweetalert套件 let Sweetalert2=`<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>` $('html').append(Sweetalert2) //使用者點選某個元素 執行刪除元素function function SetDeleteEl(e){ e.preventDefault(); set=e.target.class console.log(set) const border={ border:"4px dotted red" } const Neon={ animation: "neon-color 2.5s linear infinite" } const NoBorder={ border:"none" } const StopNeon={ animation: "" } Object.assign(e.target.style,border) Object.assign(e.target.style,Neon) //e.target.remove() if (Check==true){ Swal.fire({ title: '確定要刪除這個元素?', showDenyButton: true, showCancelButton: false, confirmButtonText: '確認', denyButtonText: `取消`, }).then((result) => { /* Read more about isConfirmed, isDenied below */ if (result.isConfirmed) { e.target.remove() List.push(e.target.className) localStorage.setItem('List',List); Check=false; Swal.fire('成功刪除此元素!', '', 'success') } else if (result.isDenied) { Check=false; Swal.fire('動作已取消', '', 'info') Object.assign(e.target.style,NoBorder) Object.assign(e.target.style,StopNeon) }; }); } //選取流程結束後 解開選取流程的事件綁定 $('body').unbind('click',SetDeleteEl) } //toast.js cdn匯入 const toastJS = ` <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>` $('body').append(toastJS); // 當使用者按下Ctrl+Q 就執行主程序 document.addEventListener('keydown', function(e){ if(e.which==81 && e.ctrlKey==true){ RemoveListItem(); toastr.options = { // 參數設定[註1] "closeButton": false, // 顯示關閉按鈕 "debug": false, // 除錯 "newestOnTop": false, // 最新一筆顯示在最上面 "progressBar": false, // 顯示隱藏時間進度條 "positionClass": "toast-bottom-right", "preventDuplicates": false, // 隱藏重覆訊息 "onclick": null, // 當點選提示訊息時,則執行此函式 "showDuration": "300", // 顯示時間(單位: 毫秒) "hideDuration": "1000", // 隱藏時間(單位: 毫秒) "timeOut": "2000", // 當超過此設定時間時,則隱藏提示訊息(單位: 毫秒) "extendedTimeOut": "1000", // 當使用者觸碰到提示訊息時,離開後超過此設定時間則隱藏提示訊息(單位: 毫秒) "showEasing": "swing", // 顯示動畫時間曲線 "hideEasing": "linear", // 隱藏動畫時間曲線 "showMethod": "fadeIn", // 顯示動畫效果 "hideMethod": "fadeOut" // 隱藏動畫效果 } toastr.success( "已開啟刪除元素模式!" ); $('body').on('click',SetDeleteEl) Check=true }}) function RemoveListItem(){ if (localStorage.getItem('List')!=null){ item=localStorage.getItem('List').split(',') for (i=0;i<item.length;i++){ item[i]='.'+item[i] item[i]=item[i].replaceAll(' ','.') try{ document.querySelector(item[i]).remove()} catch{} } // item='.'+item // item=item.replaceAll(' ','.') // document.querySelector(item).remove() } } //-------------------------------------------------------- })(); ``` ## 心得 短短150行左右的程式碼,就寫得好辛苦!(汗 過去寫過很多次類似的東西,所以基本上沒有卡關的地方, 只是設計邏輯、嘗試的過程還是比想像的還要耗精神( ´•̥̥̥ω•̥̥̥ ) 但寫出來比過去快很多,也比過去好看超級多的。 給自己一個讚讚! 重新把localstorge複習一遍,Object.assign、套件使用等。 算是一個確確實實的實力鞏固過程。 ## 延伸 未來可以新增控制黑名單元素的UI。 可以新增、刪除,看有哪些元素被列入要刪除的黑名單。

[前端學習] 用JS動態修改CSS的三種寫法,花式JS玩弄樣式,嚇死你的同事!

## 前情提要 還在寫行內樣式醜醜的嗎?教你不用寫css檔案,使用javascript來動態修改樣式的招數! 這樣子處理的層次又更不一樣, 前端之路有你有我,馬上來看看這是怎麼完成的。 ## 要修改CSS怎麼做呢?物件儲存 首先樣式寫成物件 ```css= const die={ display:"none" } ``` 為什麼要寫成物件呢?修改一種,可能看不出來,修改很多樣式就能感受到, 物件這個儲存方式是有它特別的美感與好處的。 下面我來舉例: ```css= const wrapperStyle = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', width: '350px', backgroundColor: '#261d4a', borderRadius: '5px', transition: 'box-shadow .5s ease', overflow: 'hidden' }; ``` ## 語法介紹 Object.assign 使用範例 `Object.assign(element.style,Style)` element是你要修改的元素,Style是樣式寫的object 這是修改一個的情況,當像是上面很多屬性要改的話,我們物件與JS的好處就產生了! 直接使用迴圈來全部套入: ```javascript= const wrapper = document.getElementsByClassName('wrapper'); for (let i = 0; i < wrapper.length; i++) { Object.assign(wrapper[i].style, wrapperStyle); } ``` [MDN官網說明](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) [JS 原力覺醒 Day27 - JS 常用 API - Object.assign && Object.defineProperty](https://ithelp.ithome.com.tw/notifications) ## 還有沒有別的寫法? 有。 ``` document.getElementById("myElement").style.cssText = ` display: block; position: absolute; `; ``` 這樣寫也跟原生css寫起來差不多,比較舒服。 用這種比較分離畫面與程式的寫法, 體感上爽度更高。 就不會是找到元素後還要.style.display="none" 一直.屬性.進去點點點這樣。 ## 說好的三種呢? ``` function setStyle( objId, propertyObject ) { var elem = document.getElementById(objId); for (var property in propertyObject) elem.style[property] = propertyObject[property]; } setStyle('myElement', {'fontsize':'12px', 'left':'200px'}); ``` 這種寫成function的風格我也很愛,感覺很舒服,跟第一個的有點類似卻不太一樣。 這個是把迴圈弄再函式了,所以呼叫一次就會全部修改。 第一個則是把物件內的東西丟給迴圈,一次一次去做改動變化, 邏輯上一模一樣,手段跟風格差異而已唷。 [stackoverflow](https://stackoverflow.com/questions/3968593/how-can-i-set-multiple-css-styles-in-javascript) ## 心得 使用這個小技巧很有趣,之後我們會應用到「JS油猴系列-遮蔽廣告腳本,自定義新增名單」文章裡面。 當要寫腳本,很需要JS動態修改CSS的時候,這種小技巧就佔了很大的角色呢٩(๑•̀ω•́๑)۶! 是不是沒有想到居然可以用JS修改呀! 加上我認為比較差的寫法,就不只三種了呢。 趕快選一種喜歡的, 練習成自己的寶藏知識吧!

發文點子

分類: node類 腳本類 ajax類 頁面製作類 研究類 --------------------------------------------------------------------------- 1. 用JS動態修改CSS 的三種寫法 (完成) 2. 用node.js來寫聊天室--->進階篇章:對戰小遊戲 3. 串API練習-寫出qrcode製作器 4.串API練習-使用post完成"reurl.cc縮網址"功能 5. 串API練習-查詢股價用ajax,再用d3.js視覺化整理 6.寫出chrome離線小恐龍遊戲 7.JS油猴系列-遮蔽廣告腳本,自定義新增名單 (完成) 8.網頁製作入門-寫一個sitcom官網示範教學 9.瀑布流呈現-結合前端爬蟲抓PTT表特版圖片 10. hackmd視覺化-讓自己的筆記變成部落格 (X) :整理自己所學 11.把元素轉成圖片的三種方法:介紹html2canvas--->各種套件都介紹 12. jQuery CDN 掛點解決方案?簡單的 JavaScript 讓您有本地替代方案! https://mnya.tw/cc/word/1428.html --------------------------------------------------------------- 前端小試身手 1.至頂按鈕 自動隱藏且可直接使用的網頁回頂部按鈕 https://mnya.tw/cc/word/1735.html 2.捲動軸 3.用 JavaScript 做一個 Tooltips 功能 https://www.letswrite.tw/js-tooltips/ 4. Javascript,XML 轉 JSON https://www.letswrite.tw/xml-to-json/ -------------------------------------------------------------------------- CSS Tooltip Generator https://www.cssportal.com/css-tooltip-generator/ ![](https://i.imgur.com/lBtfanS.png)

自學網頁の嬰兒教材:JavaScript(八)作業分享

第1課 ── 碼表應用程式 https://replit.com/@superyngo1/vue3timer 第一次獨立開發,css苦手,看前輩做的這麼漂亮就練習了一個文字背景漸變 第2課 ── 筆記應用程式 https://replit.com/@superyngo1/vue3notebook 看到學長的範例就練習建置vue的sfc和typescript 花了一些心思設定筆記的選取順序和刪除變換 css苦手 第3課 ── 分帳應用程式 https://replit.com/@superyngo1/vue3billsplitter?v=1 被typescrtpt的型別檢查搞得很苦 看了一些youtube教學css比較會排版面了 規格要求外新增了待付的選項 第4課 ── 行事曆應用程式 https://replit.com/@superyngo1/vue3calendar 漸漸掌握vue的資料框架 看前輩用很多vue標籤覺得厲害 仍對typescrtpt頭痛 第5課 ── 體重追蹤應用程式 https://replit.com/@superyngo1/Weight-Tracker-Chart 放棄建置(以及typescript)回到用cdn導入vue 寫的愉快 感謝學長拆雷 第6課 ── 天氣查詢應用程式 https://replit.com/@superyngo1/vue3weathercheck 第一次導入api 還算輕鬆 看到學長寫法用v-show去控制顯示資料原來還可以這樣 第7課 ── 國家資訊與地圖應用程式 https://replit.com/@superyngo1/vue3-World-Map-with-Country-info 原本以為只要資料串好 格式設定好應該很簡單 結果卡在一堆低級錯誤(for loop格式寫錯等) 還糾結為何flag顯示不出來圖案(看學長的可以) 最後才發現原來是瀏覽器的問題 心累

JavaScript 語言中 13 個經常令新手困惑的設計

寫 JavaScript 偶爾會踩到一些小地雷,會讓新手思考很久很久,這邊整理一份清單! 原文出處:https://dev.to/codeofrelevancy/unexpected-moments-of-javascript-that-will-challenge-your-understanding-of-the-language-4834 --- 1️⃣ ``` 2 == [2] // true ``` 使用 `==` 做比較,可能會對變數型別做一些額外的轉換 在本例中,數字 2 被轉換為字串,陣列“[2]”也被轉換為字串。導致兩個值都是“2”。這就是為什麼比較結果為“真”。 通常建議使用 [嚴格相等運算符 `===`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) 而不是 `==`避免意外結果.. 更多案例: ``` '123' == 123 // true 'foo' == NaN // false undefined == null // true NaN === NaN // false NaN == NaN // false 0 == null // false ``` --- 2️⃣ ``` [] == ![] // true ``` 將空陣列“[]”與通過取反建立的“布林值”進行比較(使用 [`!` 運算符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT)) 一個非空陣列 `[]`。這種比較的結果是 `true`,乍一看似乎出乎意料。 在 JS 中,每個值在 `boolean` 上下文中可以是 `true` 或 `false`。空陣列是真值,這意味著它在“布林”上下文中被視為“真”。當我們對其應用 `!` 運算符時,它會轉換為 `false`。 另一方面,通過否定非空陣列建立的“boolean”值為“false”。當我們使用 `==` 運算符,JS 執行類型約束,這意味著它會在比較它們之前嘗試將值轉換為通用類型。因此,空陣列被轉換為 `false`,結果兩邊都是 `false`。最後,比較返回“true”。 探索更多: ``` ['a', 'b'] !== ['a', 'b'] // true ['a', 'b'] == ['a', 'b'] // false [1, 2] + [3, 4] // "1,23,4" ``` --- 3️⃣ ``` null == undefined // true ``` [double equals `==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality) 用於比較兩個值是否相等,同時忽略它們的資料類型。當使用雙等號運算符比較值“null”和“undefined”時,它們被認為是相等的,比較結果將為“true”。這是因為“null”和“undefined”都表示缺少值,並且在這種情況下彼此非常等價。 使用嚴格相等運算符: ``` null === undefined // false ``` --- 4️⃣ ``` typeof NaN // number typeof null // object ``` 在 JS 中,[typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) 是一個運算符,用於確定值或變數的類型。 [NaN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/NaN) 代表 **Not a Number** 是 JS 中的一個特殊值,代表一個 ` undefined` 或 `unrepresentable` 數值。 當您將 `typeof` 與 `NaN` 一起使用時,它將返回 `number`。這可能看起來很奇怪,但這是因為 `NaN` 在技術上是 JS 中的數字資料類型,儘管它代表的實際上不是數字。 當 typeof 應用於 null 時,它返回字串 object 。這是因為“null”被認為是一個表示空物件引用的特殊值。 `null` 本身不是一個物件,而是一個原始值。這被認為是 JS 語言設計中的一個怪癖。 探索更多: ``` typeof function(){} // "function" null instanceof Object // false ``` --- 5️⃣ ``` true == "1" // true false == "0" // true ``` JS 將字串“1”轉換為“布林”值“true”,將字串“0”轉換為“false”,因為任何非空字串都被視為_truthy_,而在另一端則被視為_falsy_。因此,比較變成了 true == true 是 true 和 false == false 是 true 。 探索更多: ``` 1 + true // 2 1 - true // 0 '' == false // true 0 == false // true true + false // 1 ``` --- 6️⃣ ``` "1" + 1 // "11" 2 + "2" // "22" "5" - 3 // 2 ``` 當您將 [+ 運算符](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Addition) 與“字串”和“數字”一起使用時,數字會被轉換到一個字串並連接到該字串。 如果可以將“字串”解析為“數字”,它將[減去](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Subtraction)“數字”來自“字串”。 所以, `"1" + 1` 變成字串 `"11"` `2 + "2"` 變成字串 `"22"` `"5" - 3` 變成數字 `2` 探索更多: ``` +"1" // 1 -"1" // -1 +true // 1 -true // -1 +false // 0 -false // -0 +null // 0 +undefined // NaN 1 / "2" // 0.5 "2" / 1 // 2 1 / 0 // Infinity -1 / 0 // -Infinity 3 * "abc" // NaN true > false // true undefined + 1 // NaN undefined - 1 // NaN undefined - undefined // NaN undefined + undefined // NaN null + 1 // 1 null - 1 // -1 null - null // 0 null + null // 0 Infinity + 1 // Infinity Infinity - 1 // Infinity Infinity - Infinity // NaN Infinity + Infinity // Infinity Infinity / Infinity // NaN ``` --- 7️⃣ ``` "b" + "a" + + "a" + "a" // "baNaNa" ``` 它連接字串 `b`、字串 `a`、由表達式 `+"a"` 和字串 `a` 產生的字串。 `+"a"` 將字串 `a` 強制轉換為計算結果為 `NaN`(不是數字)的數字,因為 `a` 不是有效數字。 當我們連接“b”、“a”、“NaN”(表示為空字串)和“a”時,我們得到字串“baNaNa”。 --- 8️⃣ ``` !{} // false {} == !{} // false {} == {} // false ``` 當我們將一個空物件“{}”與一個否定的空物件“!{}”進行比較時。驚嘆號 ! 是一個邏輯運算符,它否定物件的值,所以 !{} 返回 false 因為對像在 JS 中被認為是真實的。我們實際上是在將 `{}` 與 `false` 進行比較,結果是 `false` 值,因為它們在值或資料類型上不相等。 在最後一個表達式中,我們正在比較兩個空物件“{}”。儘管它們可能看起來相同,但它們是兩個獨立的物件,在內存中具有不同的引用,因此它們在值或資料類型上並不相等。最後,比較也會產生一個“假”值。 當您在用大括號`{}` 包裹的兩個物件之間使用**plus operator +** 時,它嘗試將物件連接為字串。 探索更多: ``` {} + [] === "" // false !!{} // true !![] // true [] + [] // "" [] + {} // "[object Object]" {} + [] // "[object Object]" {} + {} // "[object Object][object Object]" [] == false // true !!'' // false !!0 // false !!null // false !!undefined // false ``` --- 9️⃣ ``` 7 > 6 > 5 // false ``` 首先,“7 > 6”的計算結果為“真”,因為 7 大於 6。 接下來,評估“true > 5”。在 JS 中,`true` 強制轉換為數字 `1`,`false` 強制轉換為 `0`。所以 `1 > 5` 是 `false`,因為 `1` 不大於 `5`。 所以最後,`7 > 6 > 5` 等價於 `true > 5`,即 `false`。 探索更多: ``` 5 < 6 < 7 // true 0 > null // false ``` --- 1️⃣0️⃣ ``` Math.max() // -Infinity Math.min() // Infinity ``` `Math.max()` 和 `Math.min()` 是可用於分別查找一組數字中的最大值和最小值的函數。 當不帶任何參數呼叫時,`Math.max()` 返回 `-Infinity`,它表示 JS 中可能的最小 `number`,另一方面,`Math.min()` 返回 `Infinity`,它表示可能的最大 ` JS 中的數字`。 這種行為是有道理的,因為如果沒有提供數字,則 `Math.max()` 沒有要返回的最大數字,同樣,沒有為 `Math.min()` 返回的最小數字 --- 1️⃣1️⃣ ``` parseInt('08') // 8 parseInt('08', 10) // 8 parseInt('0x10') // 16 ``` `parseInt('08')` 將字串 `08` 轉換為整數 `8`。如果您要編寫“parseInt('08', 10)”,該函數仍將返回“8”。 這背後的原因是因為 parseInt 函數的第二個參數是指定要使用的編號系統的基數。比方說:`binary`、`octal`、`decimal`、`hexadecimal` 等。如果未指定基數,`parseInt` 將嘗試根據字串格式檢測基數。在上面的例子中,“08”被認為是一個八進制數,因為它以“0”開頭,所以它被轉換為十進制數“8”。 `parseInt('0x10')` 將 `hexadecimal` 字串 `0x10` 轉換為整數 `16`。基數也未指定,但前綴“0x”表示該數字應被視為“十六進制”數字,因此它被轉換為“16”作為十進制數。 探索更多: ``` parseFloat('3.14.15') // 3.14 parseFloat('0.0') // 0 ``` --- 1️⃣2️⃣ ``` (function(x) { delete x; return x; })(1); // 1 ``` 採用參數“x”的匿名函數。在函數內部,它試圖刪除 `x` 變數,但這是不可能的,因為 `x` 是函數參數,無法刪除。然後該函數返回 `x` 的值。 當使用參數“1”呼叫此函數時,函數內部的“x”值將設置為“1”。在這種情況下,刪除操作沒有效果,函數只是返回 `x` 的值,即 `1` --- 1️⃣3️⃣ ``` for (var i = 0; i < 3; ++i) { setTimeout(() => console.log(i), 1000); // returns 3 three times } for (let i = 0; i < 3; ++i) { setTimeout(() => console.log(i), 1000); // returns 0 1 2 } ``` 這是因為 `var` 在函數範圍內建立了一個綁定,所以在一秒超時後循環已經執行完了,因此你得到了 3 次 `3`。通過使用 `let`,您可以在塊作用域(循環)綁定變數,因此它返回您期望的值,因為 `i` 指的是該循環迭代中的值。 --- 簡單分享,希望對您有幫助!