整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          原生JS手寫絲滑流暢的元素拖拽效果

          提到元素拖拽,通常都會先想到用 HTML5 的拖拽放置 (Drag 和 Drop) 來實現(xiàn),它提供了一套完整的事件機制,看起來似乎是首選的解決方案,但實際卻不是那么美好,主要是它的樣式太過簡陋,無法實現(xiàn)更高級的用戶體驗:

          這是瀏覽器默認的拖拽效果,點住拖拽任意圖片或文字都會產(chǎn)生。

          筆者因為之前有個小項目需要經(jīng)常參考稿定設計,一直有留意其元素拖拽的效果(如下圖),所以接下來我將以這種效果為藍本,使用原生 JS 實現(xiàn)一個富有動感的 自定義拖拽 效果,話不多說直接開摸。

          實現(xiàn)原理

          首先說下思路,我們需要知道鼠標的三個事件,分別是 mousedownmousemovemouseup ,當點擊按下的時候,克隆一個絕對定位的元素,并標識下"拖拽中"的狀態(tài),接著在 mousemove 中就可以判斷應該執(zhí)行的具體方法,從而讓元素隨著鼠標移動起來。

          在監(jiān)聽事件的 event 對象中,有幾個參數(shù)是比較重要的:clientXclientY 標識的鼠標當前橫坐標和縱坐標,offsetXoffsetY 表示相對偏移量,可以在 mousedown 鼠標按下時記錄初始坐標,在 mouseup 鼠標抬起時判斷是否在目標區(qū)域中,如果是則用鼠標獲取到的當前的偏移量 - 初始坐標得到元素實際在目標區(qū)域中的位置。

          為了閱讀體驗,以下所有代碼均有部分省略,文末可查看完整源碼地址,代碼量并不多。

          基礎界面

          先簡單實現(xiàn)一個兩欄布局界面,并應用上一些 CSS 效果:

          <div id="app">
            <div class="slide">
              <div id="list">
                <img class="item" src="......." />
                <img  .........
              </div>
            </div>
            <div class="content"></div>
          </div>
          #app {
            width: 100vw;
            height: 100vh;
            display: flex;
          }
          .active {
            cursor: grabbing;
          }
          
          .slide {
            width: 260px;
            height: 100%;
            overflow: scroll;
            border-right: 1px solid rgba(0,0,0,.15);
            #list {
              user-select: none;
              .item {
                background: rgba(0,0,0,.15);
                width: 120px;
                display: inline-block;
                break-inside: avoid;  
                margin-bottom: 4px;
              }
              .item:hover {
                cursor: grab;
                filter: brightness(90%);
              }
              .item:active {
                cursor: grabbing;
              }
            }
            .grid {
                column-count: 2;
                column-gap: 0px;
            }
          }
          .slide::-webkit-scrollbar {
            display: none; /* Chrome Safari */
          }
          
          #content {
            position: relative;
            flex: 1;
            height: 100%;
            margin-left: 45px;
            background: rgba(0,0,0,.07);
            .item {
              position: absolute;
              transform-origin: top left;
            }
          }

          利用濾鏡 filter: brightness(90%); 調(diào)節(jié)明亮度可以快速實現(xiàn)一個鼠標覆蓋的動態(tài)效果,無需額外制作遮罩:

          使用偽類激活 cursorgrabgrabbing 可以設置抓取動作的圖標:

          實現(xiàn)元素抓取

          利用事件委托機制為選擇列表添加 mousedown 事件監(jiān)聽,實現(xiàn)抓取的原理是在鼠標按下時克隆按下的元素,并把克隆出來的元素設置成絕對定位,讓它"浮"起來:

          let dragging = false
          let cloneEl = null // 克隆元素
          let initial = {} // 初始化數(shù)據(jù)記錄
          ......
          // 選中了元素
          cloneEl = e.target.cloneNode(true) // 克隆元素
          cloneEl.classList.add('flutter') // 使其浮動
          e.target.parentElement.appendChild(cloneEl) // 加入到列表中
          dragging = true // 標記拖動開始
          
          // TODO: 初始化克隆元素的定位并記錄,方便后面移動時計算位置
          ........
          .flutter {
            position: absolute;
            z-index: 9999;
            pointer-events: none;
          }

          將鼠標的坐標設置為克隆元素的絕對定位值(lefttop),就會像下圖所示這樣,此時減去 offset 偏移量,就能讓克隆元素覆蓋在本體上面。

          初始化的值需要記錄起來方便后續(xù)計算,同時我們用 dragging 變量標記了狀態(tài)(拖動中),接下來配合移動鼠標的監(jiān)聽事件就能將元素“抓”起來了:

          // 鼠標移動
          window.addEventListener("mousemove", (e) => {
            if (dragging && cloneEl) {
              // TODO: 處理元素的移動:改變 left top 定位
              // x 軸(left)計算方法:e.clientX - initial.offsetX
              // y 軸(top)計算方法:e.clientY - initial.offsetY
            }
          })

          上面只是實現(xiàn)了元素的拖動,但是"克隆"的效果實在太明顯了,為了讓元素看起來更像是拖出來的而不是復制出來的,我們還要讓本體隱藏,同時DOM結(jié)構(gòu)不能丟失,這時只需在按下拖動時給本體元素設置個 opacity: 0,結(jié)束時再改回透明度1就能搞定。

          雖然到這功能就算實現(xiàn)了,但實際效果還是有點僵硬,參考稿定設計中的元素放開時會固定回到一個位置,然后再收回去,這個過渡又有點鬼畜,不夠流暢。其實只需讓元素回退過程有一個自然地動畫就行,transition 就能實現(xiàn):

          .is_return {
            transition: all 0.3s;
          }
          // 鼠標抬起
          window.addEventListener("mouseup", (e) => {
            dragging = false
            if (cloneEl) {
                cloneEl.classList.add('is_return') // 加上過渡動畫
                changeStyle(......) // 設置回元素的初始位置
                setTimeout(() => {
                  cloneEl.remove() // 移除元素
                }, 300)
            }
          })

          最終我在動作結(jié)束時給克隆元素添加了過渡屬性,然后直接設置回初始坐標讓克隆元素回到它的出生地點,用定時器在過渡動畫持續(xù)的相同時間后移除克隆元素,這樣就有了一個平滑穩(wěn)定的回退動畫。

          性能優(yōu)化

          由于在改變元素狀態(tài)的過程中需要頻繁進行多個 CSS 操作,為降低回流重繪的成本,最好將多個操作合并起來處理,這里利用了 cssText 來實現(xiàn):

          // 改變漂浮元素:x、y、縮放倍率
          function moveFlutter(x, y, d = 0) {
            const scale = d ? initial.width + d < initial.fakeSize ? `transform: scale(${(initial.width + d) / initial.width})` : null : null
            const options = [`left: ${x}px`, `top: ${y}px`]
            scale && options.push(scale)
            // 將CSS處理成數(shù)組,然后丟進DOM操作方法中一次執(zhí)行
            changeStyle(options)
          }
          // 合并多個操作
          function changeStyle(arr) {
            const original = cloneEl.style.cssText.split(';')
            original.pop()
            cloneEl.style.cssText = original.concat(arr).join(';') + ';'
          }

          實現(xiàn)拖拽放大

          放大我們可以使用 transform: scale 來實現(xiàn),只需要將拖動位置之間的距離當做變化系數(shù)(假設為d),那么scale變化數(shù)值即為(元素寬度 + d)/元素寬度,而放大的最終倍數(shù)必定為 圖片實際寬度/元素的寬度,只要判斷不超過這個邊界就可以。(這個圖片實際寬高在真實業(yè)務場景中建議在上傳資源時就記錄在數(shù)據(jù)庫,這里我是模擬的隨機一個原圖尺寸)。

          兩點間距離計算公式為:

          代碼實現(xiàn):

          // 計算兩點之間距離
          function distance({ clientX, clientY }) {
            const { clientX: x, clientY: y } = initial // 獲取初始的坐標
            const b = clientX - x;
            const a = clientY - y;
            return Math.sqrt(Math.pow(b, 2) + Math.pow(a, 2))
          }
          
          window.addEventListener("mousemove", (e) => {
            if (dragging && cloneEl) {
              const d = distance(e) // 計算距離
              moveFlutter(e.clientX - initial.offsetX, e.clientY - initial.offsetY, d)
            }
          })
          function moveFlutter(x, y, d = 0) {
            let scale = ''
            // 如果距離大于0,且寬度+距離小于實際寬度
            if( d && initial.width + d <= initial.fakeSize ) {
                scale = `transform: scale(${(initial.width + d) / initial.width})`
            }
            // TODO ... changeStyle ...
          }

          效果演示:

          注意元素都要設置 transform-origin: top left; 改變縮放原點到左上角,否則默認(中心為原點)的轉(zhuǎn)換會發(fā)生比較明顯的偏移。

          實現(xiàn)放置

          其實拖拽放置有點像是"復制"與"粘貼",前面我們實現(xiàn)了復制,放置主要就是將元素粘貼到畫布當中,流程步驟如下:

          1. 如果鼠標在目標區(qū)域,拷貝元素到畫布中,如果不在畫布中,執(zhí)行倒退動畫
          2. 2. 刪除元素
          // 完成處理
          function done(x, y) {
            if (!cloneEl) { return }
            const newEl = cloneEl.cloneNode(true)
            newEl.classList.remove('flutter')
            newEl.src = cloneEl.getAttribute('raw') // 設置原圖地址
            newEl.style.cssText = `left: ${x - initial.offsetX}px; top: ${y - initial.offsetY}px;`
            document.getElementById('content').appendChild(newEl)
            // TODO: 元素移除
          }

          判斷是否在畫布內(nèi)抬起很簡單,往畫布上綁定mouseup監(jiān)聽事件即可,克隆的新元素必須刪除無用的屬性和class,此時設置元素的lefttop即可將元素放置進畫布中,關(guān)鍵點在于畫布內(nèi)的target有可能是錯的,因為如果鼠標抬起的區(qū)域已經(jīng)放置了元素,那么相對偏移量就得我們自己計算了,使用getBoundingClientRect方法獲取畫布本身相對于視窗的偏移,鼠標坐標減去畫布本身的偏移就是元素在畫布中的位置了。

          document.getElementById('content').addEventListener("mouseup", (e) => {
            if (e.target.id !== 'content') {
              const lostX = e.x - document.getElementById('content').getBoundingClientRect().left
              const lostY = e.y - document.getElementById('content').getBoundingClientRect().top
              done(lostX, lostY)
            } else { done(e.offsetX, e.offsetY) }
          })

          只貼了部分關(guān)鍵代碼,完整代碼文末查看。

          邊界判斷

          如果不對邊界情況進行處理可能會導致拖動時發(fā)生意外的中斷,無法正確回收克隆元素。

          // 鼠標離開了視窗
          document.addEventListener("mouseleave", (e) => {
            end()
          })
          // 用戶可能離開了瀏覽器
          window.onblur = () => {
            end()
          }

          體驗優(yōu)化

          參考稿定設計中元素拖拽是直接賦值原圖的,原圖大小通常無法控制,免不了需要加載時間,造成卡頓空白的問題,在網(wǎng)絡不夠快時體驗尤其尷尬:

          我的優(yōu)化思路是利用瀏覽器加載過同一張圖片就會優(yōu)先讀緩存的機制,先用一個Image加載原圖,等其加載完畢再把拖拽元素的src改成原圖,這樣瀏覽器會"自動"幫我們優(yōu)化這個過程,只需要注意一點,由于這是個異步任務,所以一定要做好對應標記,不然手速快的時候控制不好觸發(fā)順序。

          function simulate(url, flag) {
            cloneEl.setAttribute('raw', url)
            const image = new Image()
            image.src = url
            image.onload = function () {
              // 異步任務,克隆節(jié)點可能已不存在,flag標記是否拖動的還是當前目標
              cloneEl && initial.flag === flag && (cloneEl.src = url)
            }
          }

          效果演示,故意加大了圖片的分辨率差異:


          以上就是文章的全部內(nèi)容,感謝看到這里,希望對你有所幫助或啟發(fā)!創(chuàng)作不易,如果覺得文章寫得不錯,可以點贊收藏支持一下,也歡迎關(guān)注,我會更新更多實用的前端知識與技巧,我是茶無味的一天,期待與你共同成長~

          相關(guān)鏈接

          [1] 完整代碼地址: https://juejin.cn/post/7145447742515445791/#heading-9
          [2] 關(guān)于作者:
          https://book.palxp.com

          CSDN 編者按】Wasm 是否會取代 JavaScript ?這是 WebAssembly 發(fā)布之初不少人發(fā)出的疑問,本文作者在通過各項基準測試之后,回答了這個問題。

          作者 | Loraine Lawson 責編 | 彎月
          出品 | CSDN(ID:CSDNnews)

          鏈接:https://thenewstack.io/javascript-vs-wasm-which-is-more-energy-efficient-and-faster/

          JavaScript 還是 WebAssembly(簡稱 Wasm),究竟哪個運行速度更快、更節(jié)能?葡萄牙米尼奧大學對這個問題展開了研究,并得出了結(jié)論:雖然在實驗室微基準測試方面,JavaScript 比 Wasm 更節(jié)能、更快,但在實際應用程序中,Wasm 在速度和節(jié)能方面皆優(yōu)于 JavaScript,有時能高出 30%。

          請不要忘記,如今 Wasm 還處于發(fā)展的早期階段。

          研究員兼軟件工程師 Jo?o De Macedo 表示:“Wasm 仍處于起步階段,只有時間能告訴我們它將如何發(fā)展。在我們看來,Wasm 完全有可能戰(zhàn)勝原生應用,并幫助網(wǎng)絡瀏覽器成為 21 世紀的操作系統(tǒng)。”


          微基準測試與實際應用


          該研究于 2022 年發(fā)表,不僅參考了微觀基準,也考慮了實際情況。

          Jo?o De Macedo解釋道:“微基準測試是一種程序,用于跟蹤和測量某個明確定義的任務的性能,例如持續(xù)時長、操作速率、帶寬等。微基準測試是測量軟件系統(tǒng)性能的主要方法之一,因此,Wasm 也不例外。”

          由于 Wasm 的主要目標之一是提高 Web 應用程序的性能,因此比較 Wasm 和 JS 的運行時和節(jié)能的表現(xiàn)非常重要。

          從微基準測試來看,在有些情況下,JavaScript 在速度和節(jié)能方面的表現(xiàn)都超過了 Wasm。然而,在 Google Chrome 和微軟 Edge 上,Wasm 不僅比 JavaScript 更節(jié)能,而且性能也更好。但是,JavaScript 在 Mozilla Firefox 上確實比 Wasm 具有更好的性能,而且大多數(shù)時候的差異很明顯。

          盡管如此,最終 Wasm 仍將在實際的應用程序中占據(jù)主導地位。

          報告稱,“初步結(jié)果表明,WebAssembly 雖然仍處于起步階段,但已開始超越 JavaScript,并且 WebAssembly 的成長空間也更大。統(tǒng)計分析表明,與 JavaScript 相比,WebAssembly 表現(xiàn)出了顯著的性能差異。”


          JavaScript 與 Wasm 研究的實際表現(xiàn)


          該研究通過 Wasmboy 基準測試,測量了 Wasm 和 JavaScript 在實際應用程序中的表現(xiàn)。Wasmboy 基準測試是一個 Gameboy/Gameboy Color 模擬器,是用Typescript 編寫的Wasm基準測試。Wasmboy 是用 JavaScript/TypeScript 編寫的,創(chuàng)建的主要目標是比較 AssemblyScript 編譯器生成的 Wasm 與 TypeScript 編譯器生成的 ES6 最新版 JavaScript 之間的運行時性能。

          報告稱:“該游戲機包括六個開源游戲,可以從游戲機中運行。我們更新了 WasmBoy 的源代碼,指定了執(zhí)行游戲的瀏覽器。”

          因此,總共有六款游戲在三種瀏覽器(Chrome、Edge 和 Firefox)上運行,使用兩種語言,這樣團隊就有了 36 個獨特的樣本。

          此外,他們還使用了 PSPDFKit 基準測試。該基準測試使用的軟件支持在任何平臺上查看、注釋和填寫 PDF文檔中的表格。該報告指出,創(chuàng)建開源基準是為了評估將軟件移植到 Wasm 生態(tài)系統(tǒng)的可能性,并比較 Wasm 與 JavaScript 的實現(xiàn)。該團隊修改了應用程序的源代碼,使用這兩種語言(was 和 asm.js)執(zhí)行多個輸入。為了利用實際輸入執(zhí)行基準測試,該團隊考慮了五個不同的 pdf 文檔,其中包括將一本書分為三個部分、一篇科學論文和 20 張幻燈片。

          報告稱,“與 Wasmboy 基準測試類似,我們編寫了一些 makefile,在不同瀏覽器中自動執(zhí)行測試”,結(jié)果得到了在三種瀏覽器中運行的、用兩種語言編寫的五個示例程序,也就是說共有 30 個各不相同的程序。

          此外,此次研究還考慮了各種微基準,這些程序最初是用 C 編寫的,然后使用 Emscripten 編譯器編譯成了 Wasm 和 JavaScript。還有一些其他的語言也可編譯為 Wasm,其中包括 C/C++、Rust、Go、Python 和 AssemblyScript(TypeScript 的一種形式)。

          有關(guān)微基準測試和研究其他方面的詳細信息,請參見 Jo?o De Macedo、Rui Abreu、Rui Pereira 和 Jo?o Saraiva 的論文《WebAssembly與JavaScript:能源和運行時性能》(https://ieeexplore.ieee.org/document/9830108)。

          總的來說,他們可以通過這種方法檢查JS 和 Wasm 如何以不同方式處理規(guī)模和輸入大小。之前有研究使用了這種方法,但只檢查了虛擬機的性能。De Macedo 的研究希望了解真實世界的應用程序,因此該團隊開發(fā)了一個框架來測量基于瀏覽器的環(huán)境中的性能。


          Wasm 是否會取代 JavaScript?


          De Macedo 認為,“也許永遠不會,因為 JS 更適合不需要超高性能的網(wǎng)頁。目前,Wasm 只能作為 JS 的補充,而不能取而代之。但是,如果 Wasm 得到進一步發(fā)展,就有可能在某些應用程序中取代 JS,因為 Wasm 的加載時間更快,而且資源的使用效率更高。”

          De Macedo 認為,從長遠來看 Wasm 將帶來顛覆性的改變。

          他表示:“Wasm 不僅會徹底改變Web,而且還有可能顛覆技術(shù)市場的多個領(lǐng)域,包括云,盡管越來越多的組織采用了容器模式,但并未能真正滿足每個人都需求。”

          :如果你不知道王垠(垠神)是誰,可以先搜一下。

          《給Java說句公道話》

          很多JavaScript程序員也盲目地鄙視Java,而其實JavaScript比Python和Ruby還要差。不但具有它們的幾乎所有缺點,而且缺乏一些必要的設施。JavaScript的各種“WEB框架”,層出不窮,似乎一直在推陳出新,而其實呢,全都是在黑暗里瞎蒙亂撞。JavaScript的社區(qū)以幼稚和愚昧著稱。你經(jīng)常發(fā)現(xiàn)一些非常基本的常識,被JavaScript“專家”們當成了不起的發(fā)現(xiàn)似的,在大會上宣講。我看不出來JavaScript社區(qū)開那些會議,到底有什么意義,仿佛只是為了拉關(guān)系找工作。

          Python湊合可以用在不重要的地方,Ruby是垃圾,JavaScript是垃圾中的垃圾。原因很簡單,因為Ruby和JavaScript的設計者,其實都是一知半解的民科。然而世界就是這么奇怪,一個徹底的垃圾語言,仍然可以宣稱是“程序員最好的朋友”,從而得到某些人的愛戴……

          《編程的宗派》

          面向?qū)ο笳Z言不僅有自身的根本性錯誤,而且由于面向?qū)ο笳Z言的設計者們常常是半路出家,沒有受到過嚴格的語言理論和設計訓練卻又自命不凡,所以經(jīng)常搞出另外一些奇葩的東西。比如在JavaScript里面,每個函數(shù)同時又可以作為構(gòu)造函數(shù)(constructor),所以每個函數(shù)里面都隱含了一個this變量,你嵌套多層對象和函數(shù)的時候就發(fā)現(xiàn)沒法訪問外層的this,非得bind一下。Python的變量定義和賦值不分,所以你需要訪問全局變量的時候得用global關(guān)鍵字,后來又發(fā)現(xiàn)如果要訪問“中間層”的變量,沒有辦法了,所以又加了個nonlocal關(guān)鍵字。Ruby先后出現(xiàn)過四種類似lambda的東西,每個都有自己的怪癖…… 有些人問我為什么有些語言設計成那個樣子,我只能說,很多語言設計者其實根本不知道自己在干什么!

          《談語法》

          在 C 這樣的語言里,由于結(jié)構(gòu)上有很多限制,所以才覺得那樣的語法還可以。可是一旦加入 Lisp 的那些表達能力強的結(jié)構(gòu),就發(fā)現(xiàn)越來越難看。JavaScript(node.js)就是對此最好的一個證據(jù)。

          《論對東西的崇拜》

          如果你了解一點歷史就會發(fā)現(xiàn),今天非常流行的 JavaScript,其實不過是一個“沒能正確實現(xiàn)的 Scheme”。

          《如何掌握所有的程序語言》

          合理的入門語言

          所以初學者要想事半功倍,就應該從一種“合理”的,沒有明顯嚴重問題的語言出發(fā),掌握最關(guān)鍵的語言特性,然后由此把這些概念應用到其它語言。哪些是合理的入門語言呢?我個人覺得這些語言都可以用來入門:Scheme、C、Java、Python、JavaScript

          那么相比之下,我不推薦用哪些語言入門呢?Shell、PowerShell、AWK、Perl、PHP、Basic、Go、Rust

          《我的第一次和最后一次 Hackathon 經(jīng)歷》

          一進門就感覺這跟一般的 meetup 氣氛很不一樣。這大周末晚上的,清一色的爺們,沒有一個女人,也沒有笑聲。而且里面的人說話都很奇怪,不正眼看人,有些好像怒目相向的樣子,說出話來就像在查你戶口。有幾次有人問我是干什么的,我剛一開口,他們一句話不回,扭頭就跟其他人說話去了。只有一個頭發(fā)花白的大叔工程師對我挺友好的,于是我們就聊起來。旁邊有個華人工程師盯著一個15寸的 Macbook,后來也聊起來,開門見山就問我用什么語言。我也忘了我說什么了,只記得他很自豪的說自己用 JavaScript,而且那是最高配置的 Macbook,是 Retina 顯示器的。

          程序語言的常見設計錯誤(2) - 試圖容納世界》

          今天我來談一下另外一種錯誤的傾向,這種傾向也導致了很多錯誤,并且繼續(xù)在導致錯誤的產(chǎn)生。

          今天我要說的錯誤傾向叫做“試圖容納世界”。這個錯誤導致了 Python,Ruby 和 JavaScript 等“動態(tài)語言”里面的一系列問題。

          《怎樣尊重一個程序員》

          認識和承認計算機系統(tǒng)里的歷史遺留糟粕

          很多不尊重人現(xiàn)象的起源,都是因為某些人偏執(zhí)的相信某種技術(shù)就是世界上最好的,每個人都必須知道,否則他就不是一個合格的程序員。這種現(xiàn)象在Unix(Linux)的世界尤為普遍。Unix系統(tǒng)的鼓吹者們(我曾經(jīng)是其中之一)喜歡到處布道,告訴你其它系統(tǒng)的設計有多蠢,你應該遵從Unix的“哲學”。他們仿佛認為Unix就是世界終極的操作系統(tǒng),然而事實卻是,Unix是一個設計非常糟糕的系統(tǒng)。它似乎故意被設計為難學難用,容易犯錯,卻美其名曰“強大”,“靈活”。眼界開闊一點的程序員都知道,Unix的設計者其實基本不懂設計,他們并不是世界上最好的程序員,卻有一點做得很成功,那就是他們很會制造宗教,煽動人們的盲從心理。Unix設計者把自己的設計失誤推在用戶身上,讓用戶覺得學不會或者搞錯了都是自己的錯。

          如果你對計算機科學理解到一定程度,就會發(fā)現(xiàn)我們其實仍然生活在計算機的石器時代。特別是軟件系統(tǒng),建立在一堆歷史遺留的糟糕設計之上。各種蹩腳腦殘的操作系統(tǒng)(比如Unix,Linux),程序語言(比如C++,JavaScript,PHP,Go),數(shù)據(jù)庫,編輯器,版本控制工具,…… 時常困擾著我們,這就是為什么你需要那么多的所謂“經(jīng)驗”和“知識”。然而,很多IT公司不喜歡承認這一點,他們一向以來的作風是“一切都是程序員的錯!”,“作為程序員,你應該知道這些!” 這就造成了一種“皇帝的新裝現(xiàn)象”——大家都不喜歡用一些設計惡劣的工具,卻都怕別人嘲笑或者懷疑自己的能力,所以總是喜歡顯示自己“會用”,“能學”,而沒有人敢說它難用,敢指出設計者的失誤。

          我看完之后的感覺:

          說得真TM對,吐槽都吐到點子上了。JS 這么垃圾,為什么垠神還推薦入門用 JS 呢。可能有些語言比 JS 還垃圾吧,哈哈。雖然觀點上有矛盾,但是牛逼的人的大腦都是能容忍矛盾的,問題不大。

          為什么我要發(fā)這篇文章:

          如果你不能接受 JS 的缺點,說明你不愛 JS。


          主站蜘蛛池模板: 精品人妻无码一区二区色欲产成人| 成人国产精品一区二区网站| 无码人妻精品一区二区三区66| 麻豆亚洲av熟女国产一区二| 久热国产精品视频一区二区三区 | 国产伦精品一区二区三区免.费| 精品一区二区三区在线观看l | 波多野结衣的AV一区二区三区| 久久久久人妻一区精品性色av | 在线播放国产一区二区三区 | 一本一道波多野结衣一区| 成人欧美一区二区三区在线视频| 久久精品无码一区二区app| 中文字幕AV一区二区三区人妻少妇| 国产高清在线精品一区小说| 糖心vlog精品一区二区三区| 人妻无码视频一区二区三区 | 日韩综合无码一区二区| 亚洲国产成人久久一区二区三区| 伊人色综合一区二区三区| 黑巨人与欧美精品一区| 精品一区二区三区无码免费视频 | 九九无码人妻一区二区三区| 国产a∨精品一区二区三区不卡| 无码AV一区二区三区无码| 国产精品一区二区毛卡片| 午夜DV内射一区二区| 亚洲片一区二区三区| 色一情一乱一伦一区二区三区| 波多野结衣一区二区三区高清在线| 精品在线一区二区三区| 日韩一区在线视频| 韩国精品一区二区三区无码视频 | 一级特黄性色生活片一区二区| 国产日韩视频一区| 亚洲AV日韩综合一区| 成人丝袜激情一区二区| 国产精品一区二区久久乐下载 | 国产在线精品观看一区| 无码日韩AV一区二区三区| 鲁大师成人一区二区三区|