整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          最近很火的Vue Vine是如何實現一個文件中寫多個組件

          在今年的Vue Conf 2024大會上,沈青川大佬(維護Vue/Vite 中文文檔)在會上介紹了他的新項目Vue Vine。Vue Vine提供了全新Vue組件書寫方式,主要的賣點是可以在一個文件里面寫多個vue組件。相信你最近應該看到了不少介紹Vue Vine的文章,這篇文章我們另辟蹊徑來講講Vue Vine是如何實現在一個文件里面寫多個vue組件。

          看個demo

          我們先來看普通的vue組件,about.vue代碼如下:

          <template>
            <h3>i am about page</h3>
          </template>
          
          <script lang="ts" setup></script>

          我們在瀏覽器中來看看編譯后的js代碼,代碼如下:

          const _sfc_main = {};
          
          function _sfc_render(_ctx, _cache) {
            return _openBlock(), _createElementBlock("h3", null, "i am about page");
          }
          
          _sfc_main.render = _sfc_render;
          export default _sfc_main;

          從上面的代碼可以看到普通的vue組件編譯后生成的js文件會export default導出一個_sfc_main組件對象,并且這個組件對象上面有個大名鼎鼎的render函數。父組件只需要import導入子組件里面export default導出的_sfc_main組件對象就可以啦。

          搞清楚普通的vue組件編譯后是什么樣的,我們接著來看一個Vue Vine的demo,Vue Vine的組件必須以.vine.ts 結尾,home.vine.ts代碼如下:

          async function ChildComp() {
            return vine`
              <h3>我是子組件</h3>
            `;
          }
          
          export async function Home() {
            return vine`
            <h3>我是父組件</h3>
              <ChildComp  />
            `;
          }

          如果你熟悉react,你會發現Vine 組件函數和react比較相似,不同的是return的時候需要在其返回值上顯式使用 vine 標記的模板字符串。

          在瀏覽器中來看看home.vine.ts編譯后的代碼,代碼如下:

          export const ChildComp = (() => {
            const __vine = _defineComponent({
              name: "ChildComp",
              setup(__props, { expose: __expose }) {
                // ...省略
              },
            });
          
            function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
              return _openBlock(), _createElementBlock("h3", null, "我是子組件");
            }
          
            __vine.render = __sfc_render;
            return __vine;
          })();
          
          export const Home = (() => {
            const __vine = _defineComponent({
              name: "Home",
              setup(__props, { expose: __expose }) {
                // ...省略
              },
            });
          
            function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
              return (
                _openBlock(),
                _createElementBlock(
                  _Fragment,
                  null,
                  [_hoisted_1, _createVNode($setup["ChildComp"])],
                  64,
                )
              );
            }
          
            __vine.render = __sfc_render;
            return __vine;
          })();

          從上面的代碼可以看到組件ChildCompHome編譯后是一個立即調用函數,在函數中return了__vine組件對象,并且這個組件對象上面也有render函數。想必細心的你已經發現了在同一個文件里面定義的多個組件經過編譯后,從常規的export default導出一個默認的vue組件對象變成了export導出多個具名的vue組件對象。

          接下來我們將通過debug的方式帶你搞清楚Vue Vine是如何實現一個文件內導出多個vue組件對象。

          createVinePlugin函數

          我們遇見的第一個問題是需要找到從哪里開始著手debug?

          來看一下官方文檔是接入vue vine的,如下圖:

          從上圖中可以看到vine是一個vite插件,以插件的形式起作用的。

          現在我們找到了一切起源就是這個VineVitePlugin函數,所以我們需要給vite.config.ts文件中的VineVitePlugin函數打個斷點。如下圖:

          接下來我們需要啟動一個debug終端。這里以vscode舉例,打開終端然后點擊終端中的+號旁邊的下拉箭頭,在下拉中點擊Javascript Debug Terminal就可以啟動一個debug終端。

          在debug終端執行yarn dev,在瀏覽器中打開對應的頁面,比如:http://localhost:3333/ 。此時代碼將會停留在我們打的斷點VineVitePlugin函數調用處,讓代碼走進VineVitePlugin函數,發現這個函數實際定義的名字叫createVinePlugin,在我們這個場景中簡化后的createVinePlugin函數代碼如下:

          function createVinePlugin() {
            return {
              name: "vue-vine-plugin",
              async resolveId(id) {
                // ...省略
              },
              async load(id) {
                // ...省略
              },
              async transform(code, id) {
                const { fileId, query } = parseQuery(id);
                if (!fileId.endsWith(".vine.ts") || query.type === QUERY_TYPE_STYLE) {
                  return;
                }
                return runCompileScript(code, id);
              },
              async handleHotUpdate(ctx) {
                // ...省略
              }
            };
          }

          從上面的代碼可以看到插件中有不少鉤子函數,vite會在對應的時候調用這些插件的鉤子函數,比如當vite解析每個模塊時就會調用transform等函數。

          transform鉤子函數的接收的第一個參數為code,是當前文件的code代碼字符串。第二個參數為id,是當前文件路徑,這個路徑可能帶有query。

          transform鉤子函數中先調用parseQuery函數根據當前文件路徑拿到去除query的文件路徑,以及query對象。

          !fileId.endsWith(".vine.ts") 的意思是判斷當前文件是不是.vine.ts結尾的文件,如果不是則不進行任何處理,這也就是為什么文檔中會寫Vue Vine只支持.vine.ts結尾的文件。

          query.type === QUERY_TYPE_STYLE的意思是判斷當前文件是不是css文件,因為同一個vue文件會被處理兩次,第一次處理時只會處理template和script這兩個模塊,第二次再去單獨處理style模塊。

          transform鉤子函數的最后就是調用runCompileScript(code, id)函數,并且將其執行結果進行返回。

          runCompileScript函數

          接著將斷點走進runCompileScript函數,在我們這個場景中簡化后的runCompileScript函數代碼如下:

          const runCompileScript = (code, fileId) => {
            const vineFileCtx = compileVineTypeScriptFile(
              code,
              fileId,
              compilerHooks,
              fileCtxMap,
            );
          
            return {
              code: vineFileCtx.fileMagicCode.toString(),
            };
          };

          從上面的代碼可以看到首先會以code(當前文件的code代碼字符串)為參數去執行compileVineTypeScriptFile函數,這個函數會返回一個vineFileCtx上下文對象。這個上下文對象的fileMagicCode.toString(),就是前面我們在瀏覽器中看到的最終編譯好的js代碼。

          compileVineTypeScriptFile函數

          接著將斷點走進compileVineTypeScriptFile函數,在我們這個場景中簡化后的compileVineTypeScriptFile函數代碼如下:

          function compileVineTypeScriptFile(
            code: string,
            fileId: string,
            compilerHooks: VineCompilerHooks,
            fileCtxCache?: VineFileCtx,
          ) {
            const vineFileCtx: VineFileCtx = createVineFileCtx(
              code,
              fileId,
              fileCtxCache,
            );
            const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
            doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
            transformFile(
              vineFileCtx,
              compilerHooks,
              compilerOptions?.inlineTemplate ?? true,
            );
          
            return vineFileCtx;
          }

          在執行compileVineTypeScriptFile函數之前,我們在debug終端來看看接收的第一個參數code,如下圖:

          從上圖中可以看到第一個參數code就是我們寫的home.vine.ts文件中的源代碼。

          createVineFileCtx函數

          接下來看第一個函數調用createVineFileCtx,這個函數返回一個vineFileCtx上下文對象。將斷點走進createVineFileCtx函數,在我們這個場景中簡化后的createVineFileCtx函數代碼如下:

          import MagicString from 'magic-string'
          
          function createVineFileCtx(code: string, fileId: string) {
            const root = babelParse(code);
            const vineFileCtx: VineFileCtx = {
              root,
              fileMagicCode: new MagicString(code),
              vineCompFns: [],
              // ...省略
            };
            return vineFileCtx;
          }

          由于Vue Vine中的組件和react相似是組件函數,組件函數中當然全部都是js代碼。既然是js代碼那么就可以使用babel的parser函數將組件函數的js代碼編譯成AST抽象語法樹,所以第一步就是使用code去調用babel的parser函數生成AST抽象語法樹,然后賦值給root變量。

          我們在debug終端來看看得到的AST抽象語法樹是什么樣的,如下圖:

          從上圖中可以看到在body數組中有兩項,分別對應的就是ChildComp組件函數和Home組件函數。

          接下來就是return返回一個vineFileCtx上下文對象,對象上面的幾個屬性我們需要講一下。

          • root:由.vine.ts文件轉換后的AST抽象語法樹。
          • vineCompFns:數組中存了文件中定義的多個vue組件,初始化時為空數組。
          • fileMagicCode:是一個由magic-string庫new的一個對象,對象中存了在編譯時生成的js代碼字符串。magic-string是由svelte的作者寫的一個庫,用于處理字符串的JavaScript庫。它可以讓你在字符串中進行插入、刪除、替換等操作,在編譯時就是利用這個庫生成編譯后的js代碼。toString方法返回經過處理后的字符串,前面的runCompileScript函數中就是最終調用vineFileCtx.fileMagicCode.toString()方法返回經過編譯階段處理得到的js代碼。

          findVineCompFnDecls函數

          我們接著來看compileVineTypeScriptFile函數中的第二個函數調用findVineCompFnDecls

          function compileVineTypeScriptFile(
            code: string,
            fileId: string,
            compilerHooks: VineCompilerHooks,
            fileCtxCache?: VineFileCtx,
          ) {
            // ...省略
            const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
            // ...省略
          }

          通過前一步我們拿到了一個vineFileCtx上下文對象,vineFileCtx.root中存的是編譯后的AST抽象語法樹。

          所以這一步就是調用findVineCompFnDecls函數從AST抽象語法樹中提取出在.vine.ts文件中定義的多個vue組件對象對應的Node節點。我們在debug終端來看看組件對象對應的Node節點組成的數組vineCompFnDecls,如下圖:

          從上圖中可以看到數組由兩個Node節點組成,分別對應的是ChildComp組件函數和Home組件函數。

          doAnalyzeVine函數

          我們接著來看compileVineTypeScriptFile函數中的第三個函數調用doAnalyzeVine

          function compileVineTypeScriptFile(
            code: string,
            fileId: string,
            compilerHooks: VineCompilerHooks,
            fileCtxCache?: VineFileCtx,
          ) {
            // ...省略
            doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
            // ...省略
          }

          經過上一步的處理我們拿到了兩個組件對象的Node節點,并且將這兩個Node節點存到了vineCompFnDecls數組中。

          由于組件對象的Node節點是一個標準的AST抽象語法樹的Node節點,并不能清晰的描述一個vue組件對象。所以接下來就是調用doAnalyzeVine函數遍歷組件對象的Node節點,將其轉換為能夠清晰的描述一個vue組件的對象,將這些vue組件對象組成數組塞到vineFileCtx上下文對象的vineCompFns屬性上。

          我們在debug終端來看看經過doAnalyzeVine函數處理后生成的vineFileCtx.vineCompFns屬性是什么樣的,如下圖:

          從上圖中可以看到vineCompFns屬性中存的組件對象已經能夠清晰的描述一個vue組件,上面有一些我們熟悉的屬性propsslots等。

          transformFile函數

          我們接著來看compileVineTypeScriptFile函數中的第四個函數調用transformFile

          function compileVineTypeScriptFile(
            code: string,
            fileId: string,
            compilerHooks: VineCompilerHooks,
            fileCtxCache?: VineFileCtx,
          ) {
            // ...省略
            transformFile(
              vineFileCtx,
              compilerHooks,
              compilerOptions?.inlineTemplate ?? true,
            );
            // ...省略
          }

          經過上一步的處理后在vineFileCtx上下文對象的vineCompFns屬性數組中已經存了一系列能夠清晰描述vue組件的對象。

          在前面我們講過了vineFileCtx.vineCompFns數組中存的對象能夠清晰的描述一個vue組件,但是對象中并沒有我們期望的render函數、setup函數等。

          所以接下來就需要調用transformFile函數,遍歷上一步拿到的vineFileCtx.vineCompFns數組,將所有的vue組件轉換成對應的立即調用函數。在每個立即調用函數中都會return一個__vine組件對象,并且這個__vine組件對象上都有一個render屬性。

          學習過 JavaScript 的可能會了解,JavaScript 的宿主瀏覽器只有一個線程運行 JavaScript,除了 JavaScript 的線程,瀏覽器中單個頁面還有一些其他線程,例如:UI 線程負責處理渲染 DOM 元素;GUI 線程用于處理與用戶交互的邏輯;網絡線程用于發送接收 HTTP 請求;file 線程用于讀取文件;定時器線程處理定時任務等等。

          單線程原因

          為什么不能像很多高級語言一樣支持多線程呢?假定 JavaScript 同時有兩個線程,一個線程在HTML中創建了一個標簽元素,另一個線程刪除了這個標簽,這時瀏覽器應該執行什么操作?瀏覽器中 JavaScript 的主要用途是操作 DOM 。這決定了它只能是單線程,否則會帶來很復雜的同步問題。為了避免復雜性,大部分主流瀏覽器的 JavaScript 運行環境只支持單線程。

          JavaScript 的事件驅動

          既然 JavaScript 只支持單線程,那么有人可能會好奇為什么瀏覽器中的 JavaScript 可以同時發送多個網絡請求或者執行多個事件回調函數呢?

          這是因為 JavaScript 是基于事件驅動,當需要進行網絡請求時,JavaScript 線程會把請求發送給 network 線程執行,并等待執行結果;當進行文件讀取時則調用 file 線程,然后等待結果。然后 JavaScript 會一直輪詢事件庫 event loop,直到有事件完成,這時瀏覽器會驅動 JavaScript 去執行事件的回調函數。這就是 JavaScript 的事件驅動模型。

          web worker誕生

          單線程的最大問題是不能利用多核 CPU 的優點,HTML5 推出的 Web Worker 標準,允許 JavaScript 創建多線程,但是子線程受主線程約束,且不得操作 DOM 。所以,這個新標準不會產生多線程同步的問題。

          適用場景

          Web Worker 能解決傳統的 JavaScript 單線程出現的執行阻塞問題,因而適合以下幾種業務場景:

          • 并行計算;
          • ajax 輪詢;
          • 耗時的函數執行;
          • 數據預處理/加載。

          函數介紹

          1、創建

          初始化一個 Web Worker,由于不是所有的瀏覽器都支持 Web Worker,所以需要判斷一下瀏覽器是否支持:

          if (window.Worker) {//判斷瀏覽器是否支持web worker
              var worker = new Worker('test.js');//創建一個線程,參數為需要執行的JavaScript文件
          }

          2 、向線程傳遞參數

          新的線程的上下文環境跟原宿主環境相對獨立的,所以變量作用域不同,如果需要互相讀取變量的話需要通過消息發送的方式傳輸變量,例如:

          worker.postMessage('test'); //數據類型可以是字符串
          worker.postMessage({method: 'echo', args: ['Work']});//數據類型可以是對象

          3 、主線程接受消息

          跟上述場景類似,主線程也需要通過監聽的方式獲取輔線程的消息:

          worker.onmessage = function (event) {
            console.log('接收到消息: ' + event.data);
          }

          4 、線程加載腳本

          子線程內部也可以通過函數加載其他腳本:

          importScripts('script1.js','script2.js');

          5 、關閉線程

          // 主線程中關閉子線程
          worker.terminate();
          // 子線程關閉自身
          self.close();

          使用 JavaScript 多線程實現非阻塞全排列

          1 、什么是全排列

          從 n 個不同元素中任取 m(m≤n)個元素,按照一定的順序排列起來,叫做從 n 個不同元素中取出 m 個元素的一個排列。當 m=n 時所有的排列情況叫全排列。

          2 、為什么使用多線程處理

          這里并非突出使用 JavaScript 實現全排列的優勢,而是在實際項目中類似這種科學運算相關的算法可能會消耗一定的 CPU,由于 JavaScript 是解釋型語言,運算性能是它的弱項,而且瀏覽器中運行的 JavaScript 又是單線程的,所以一旦出現性能問題可能會導致線程阻塞,阻塞之后會導致頁面卡頓,非常影響用戶體驗。使用 webworker 的多線程功能將這個運算函數單獨 fork 出一個子線程去運行,運行完成之后發送結果給主線程,可以有效避免性能問題。

          3 、代碼示例

          <html>
          <head>
          <meta http-equiv="Content-Type" content="text/html; 
          charset=UTF-8">
          <title>JavaScript實現全排列</title>
          <script type="text/JavaScript">
          function combine() {//點擊按鈕向webworker線程發送請求  
              var worker = new Worker('http://wiki-code.oss-cn-beijing.aliyuncs.com/html5/js/worker.js');
              worker.postMessage(document.getElementById("str").value);
                  worker.onmessage= function (event) {                        
          	        document.getElementById("result").innerHTML  =   event.data ; //監聽JavaScript線程的結果
          	    };
          }
          </script>
          </head>
          <body>
              <input type="text" id="str" />
              <button onclick="combine()">全排列</button>
              結果是:<div id="result" style="width:500px;height:500px;word-break: break-all;"></div>
          </body>
          </html>

          worker.js 代碼如下:

          function getGroup(data, index = 0, group = []) {//生成全排列
              var need_apply = new Array();
              need_apply.push(data[index]);
              for(var i = 0; i < group.length; i++) {
                  need_apply.push(group[i] + data[index]);
              }
              group.push.apply(group, need_apply);
              if(index + 1 >= data.length) return group;
              else return getGroup(data, index + 1, group);
          }
          onmessage = function(message){//監聽主線程的數據請求
              var msg = message.data;
              if(msg == "") postMessage("請輸入正確的字符串");
              else {
                  var data = msg.split("");//將字符串轉數組
                  postMessage(getGroup(data));
              }
          }

          上述代碼實現了一個使用 JavaScript 的 Web Worker 實現的全排列的功能。上半部分是主線程的代碼,主要實現了創建子線程、發送數據給子線程、接收子線程的消息這幾個功能;下半部分是子線程,子線程主要負責運算,并將運算結果發送給主線程。

          總結

          早期的 JavaScript 由于考慮操作 DOM 的一致性問題,以及當時的網頁沒有過多的交互所以不需要大量的計算,所以只支持單線程。這在多核 CPU 時代的劣勢愈發明顯,所以 HTML5 中推出多線程解決這個問題。回顧本章主要介紹了 Web Worker 的使用方式以及其適用場景。

          更多詳細內容:https://developer.mozilla.org/zh-CN/docs/Web/API/Worker

          章有代碼部分,如果有閱讀困難,請到原文閱讀:https://blog.jing.do/4212

          前言:本篇文章是我在查詢時偶然間發現的,雖然年代久遠但是依舊非常適合入門學習,特此翻譯下分享給大家,順便給大家加了一些備注方便閱讀(特意加粗刷存在感)。原文全文和鏈接在最后

          ———————-以下是正文———————-

          備注:本篇文章是34歲的程序員Ayman Hourieh在2006年發布的。我找到了他網站的鏈接,但是網站已經不存在了。我用Wayback Machine找到了文章的鏡像。為了讓更多人看到把他摘錄分享出來。我只做了一些鏈接的修改。

          JavaScript是一個功能強大的面向對象語言。表面上看他他和JAVA和C非常相似,但是他卻截然不同,其核心在于JavaScript更像一個功能性語言。本篇文章是一些JavaScript的小提示,一部分提供一些類C語言的功能模擬,另一部分是想要提高性能并探討下腳本語言中一些比較難懂的東西。索引如下:

          多用途的Array

          隊列

          二叉樹

          String Concatenation 對比 Array.join

          給對象綁定方法

          使用自定義排序

          Assertion

          靜態局部變量

          null, undefined, and delete

          深層嵌套

          使用Firebug

          $ 和 $$

          console.log(format, obj1, …)

          console.trace()

          inspect(obj)

          多用途的Array

          盡管JavaScript在數據結構方面看起來很奇特,他的Array比其他編程語言(如C ++或Java)用途更加廣泛。 它通常用作數組或關聯數組,后面將演示如何將其用作堆棧,隊列和二叉樹。 復用Array而不是編寫其他的數據結構有兩個好處:首先,不用浪費時間去重寫已經存在的功能,其次,內置瀏覽器對JavaScript的運行將更高效。

          大家都知道棧是后進先出:最后插入的會被最先移除。array有兩個方法來實現棧的功能。他們是push()和pop()。push()用于插入item到array的尾部,而pop()則用于移除并返回最后一個item。以下是代碼的實例:

          varstack=[];stack.push(2);// 當前棧是 [2]stack.push(5);// 當前棧是 [2, 5]vari=stack.pop();// 當前棧是 [2]alert(i);// 顯示 5

          隊列

          隊列是先進先出的:現行插入的將會被最先移除。array可以用push()和shift()來實現隊列。push()用于插入item到尾部,shift()用于移除第一個item。案例如下:

          varqueue=[];queue.push(2);// 現在的隊列 [2]queue.push(5);// 現在的隊列 [2, 5]vari=queue.shift();// 現在的隊列 [5]alert(i);// 顯示 2

          值得注意的是,array還有一個unshift()的函數。這個函數用于將item放到array的頭部。所以棧也可以使用unshift()/shift(),而隊列可以用unshift()/pop()。

          如果這些函數名稱讓你迷茫了(譯者注:因為unshift()是處理頭部,所以相對應的棧需要從頭部出去,隊列需要換到尾部),你可以給他們取個別名,比如,創建隊列的方法名為add和remove:

          varqueue=[];queue.add=queue.push;queue.remove=queue.shift;queue.add(1);vari=queue.remove();alert(i);

          二叉樹

          二叉樹是在樹的節點表示數據。每個節點有一個數據并且有兩個子節點(左叉樹和右叉樹)。在C語言里,這種數據結構通常使用指針來實現。這種實現方法也可以在JavaScript中使用對象和引用來實現。然而,對于一些小的二叉樹,有一種更簡單便捷的方法,array的第一個item作為樹的頭。 如果可以使用以下公式計算節點i,則索引左右節點:

          leftChild(i)=2i+1rightChild(i)=2i+2

          這張圖揭示了這個算法: (來自于Wikipedia):

          正如你所看到的,這種方法并不是JavaScript的獨有之處,但是在處理小的二叉樹時非常有用。 例如,您可以編寫自己的函數來獲取和設置節點的值或子節點,并遍歷二叉樹,這些方法與做計算或for循環一樣簡單。但是,這種方法的缺點是隨著樹的深度增加,浪費的存儲空間越來越多。

          String Concatenation 對比 Array.join

          大家都知道,如果做太多的字符串鏈接會讓性能下降(譯者注:不知道的去補課),并且這個非常容易避免。如果你想要用各個字符來組成一個字符串,最差的方法是使用+把所有的字符結合到一起:

          str='';for(/* each piece */){str+=piece;// bad for performance!}returnstr;

          這種方法將使用太多的字符串鏈接,會讓性能枯竭。

          在JavaScript中有個更好的辦法,就是Array.join(),他可以讓所有array內的元素連接成一個字符串:

          vartmp=[];for(/* each piece */){tmp.push(piece);}str=tmp.join(' ');// 用空格作為分隔符returnstr;

          該方法不會受到額外的字符串對象的影響,通常執行的非常高效

          給對象綁定方法

          任何使用JavaScript事件的人都可能遇到了一種情況,他們需要將對象的方法分配給事件處理程序。 這里的問題是事件處理程序會被HTML調用,即使它們最初被綁定到另一個對象。 為了解決這個問題,我用一個函數將對象和方法綁定; 它他會運行對象和方法,并返回一個在該對象調用該方法的函數。 我在Prototype中找到了一個竅門,并且寫了以下函數來在不包含Prototype的項目中使用它:

          functionbind(obj,method){returnfunction(){returnmethod.apply(obj,arguments);}}

          如何使用:

          varobj={msg:'Name is',buildMessage:function(name){returnthis.msg+' '+name;}}alert(obj.buildMessage('John'));// displays: Name is Johnf=obj.buildMessage;alert(f('Smith'));// displays: undefined Smithg=bind(obj,obj.buildMessage);alert(g('Smith'));// displays: Name is Smith

          使用自定義排序

          排序是一個常見的工作。 JavaScript提供了一種排序數組的方法。 但是,該方法默認按字母順序排列 —— 非字符串元素在排序之前被強制轉換為字符串,這個使得數字排序會有意想不到的結果:

          varlist=[5,10,2,1];list.sort()// list is now: [1, 10, 2, 5]

          這個解釋很容易: 數字被強制轉換成了字符串,所以10編程了’10’而2變成了’2’,那么JavaScript對比兩個字符串的時候,先對比第一個字符。如果str1的第一個字符出現在字符集中的第一個字符之前,則str1被認為是“小于”str2。 在我們的情況下,’1’在’2’之前,所以’10’小于’2’。

          幸運的是,JavaScript提供一個重寫機制,讓我們可以自定義如何排序,我們用a和b兩個元素最為例子:

          如果a

          如果a=b,返回0

          如果a>b,返回大于0

          寫這樣的程序比較容易:

          functioncmp(a,b){returna-b;}

          我們現在可以使用這個方法來做排序:

          varlist=[5,10,2,1];list.sort(cmp);//listisnow:[1,2,5,10]

          Array.sort()牛逼的地方是允許更復雜的排序。 假設你有一個論壇帖子,每個帖子看起來像:

          varpost={id:1,author:'...',title:'...',body:'...'}

          如果你想用id排序,只需要創建以下函數:

          functionpostCmp(a,b){returna.id-b.id;}

          可以說,使用瀏覽器的方法進行排序將比在JavaScript中實現排序函數更有效。 但是,數據應該在服務器端進行排序。所以,除非必要,否則不應該讓數據在客戶端排序。

          Assertion

          Assertion是一種常用的調試技術,它用于確保表達式在執行期間計算為真。 如果表達式計算為假,則表示代碼中可能出現的錯誤。 JavaScript缺少一個內置的Assertion功能,但幸運的是,它很容易編寫一個。 如果傳遞的表達式的計算結果為假,以下實現會拋出AssertException類型的異常:

          functionAssertException(message){this.message=message;}AssertException.prototype.toString=function(){return'AssertException: '+this.message;}functionassert(exp,message){if(!exp){thrownewAssertException(message);}}

          自己拋出異常并不是非常有用,但是當與有用的錯誤消息或調試工具結合使用時,您可以檢測到有問題的部分。

          您還可以使用以下代碼段檢查異常是否為Assertion異常:

          try{// ...}catch(e){if(einstanceofAssertException){// ...}}

          該函數的使用類似于C或Java:

          assert(obj!=null,'Object is null');

          如果obj碰巧為null,Firefox將在JavaScript控制臺中打印以下消息:

          uncaughtexception:AssertException:Objectisnull

          Static Local Variables

          大家知道,一些語言像C ++,他們有靜態變量的概念,用于函數調用。 JavaScript并不支持此技術。 然而,“功能也是對象”讓這個功能成為可能。 方法是:將靜態變量變為函數的屬性。 假設我們要創建一個計數器函數:

          functioncount(){if(typeofcount.i=='undefined'){count.i=0;}returncount.i++;}

          當第一次調用count時,count.i是未定義的,所以if條件為true,count.i為0。因為我們將變量存儲為一個函數屬性,它將在函數調用之間保留它的值, 因此它可以被認為是一個靜態變量。

          這里有個性能提升小技巧:

          functioncount(){returncount.i++;}count.i=0;

          雖然第一個例子將Count的所有邏輯封裝在主體中,但第二個例子更為有效。

          null, undefined, and delete

          因為JavaScript有undefined和null所以他不同于其他語言。null是一個特別的數值,他表示沒有數值。null會被認為一個特別的對象,因為typeof null會返回null。

          undefined表示變量沒有被定義,或者定義了但沒有給數值。以下情況都會顯示undefined:

          // i is not declared anywhere in codealert(typeofi);vari;alert(typeofi);

          雖然undefined和null是兩個不同的類型,但是如果使用 == ,會被判定為相等,但是如果是 === 則不等。

          JavaScript還有一個刪除操作符,”undefines”一個屬性,可以將其應用于對象屬性和數組成員,使用var聲明的變量不能被刪除,而是隱式聲明(implicitly declared )的變量可以:

          varobj={val:'Some string'}alert(obj.val);// displays 'Some string'deleteobj.val;alert(obj.val);// displays 'undefined'

          深層嵌套

          如果您需要在深層嵌套對象上執行多個操作,最好將其引用到臨時變量中,而不是每次對其進行解引用。 例如,假設您想在文本字段上執行一系列操作:

          document.forms[0].elements[0]

          建議您存儲到變量中,并使用此變量而不是以上構造:

          varfield=document.forms[0].elements[0];// Use field in loop

          每個點都導致一個操作來檢索一個屬性,在一個循環中,這些操作是相加的,所以最好做一次,將對象存儲在變量中并重新使用它。

          Using Firebug

          Firefox有一個非常棒的擴展,用于調試名為Firebug的JavaScript代碼。 它提供一個具有斷點和堆棧視圖的調試器,以及一個JavaScript控制臺。 它還可以監視Ajax請求。 此外,擴展提供了一組JavaScript函數和對象來簡化調試。 您可以在Firebug的文檔頁面中詳細研究它們。 這里有一些我覺得有用的:

          $ and $$

          熟悉Prototype的人馬上就認出他們了,

          $() 接受一個字符串參數,并返回其ID是傳遞的字符串的DOM元素。(譯者注:Jquery

          $('nav')// returns the element whose id is #nav.

          $$()返回DOM的數組

          $$('div li.menu')// returns an array of li elements that are// located inside a div and has the .menu class

          console.log(format, obj1, …)

          console對象提供顯示log消息的方法,這個將比alert更加好用,console.log()有點像C里面的printf,他會將輸入轉化為字符串在console中展示:

          vari=1;console.log('i value is %d',i);// prints:// i value is 3

          console.trace()

          此方法打印一個堆棧跟蹤調用它。 它不需要輸入參數

          inspect(obj)

          該功能需要一個參數。 它切換到檢查選項卡并檢查傳遞的對象。


          主站蜘蛛池模板: asmr国产一区在线| 呦系列视频一区二区三区| 国产精品区一区二区三在线播放| 美女视频免费看一区二区| 色婷婷香蕉在线一区二区| 成人一区专区在线观看| 国产一区二区三区在线看片| 国产精品xxxx国产喷水亚洲国产精品无码久久一区| 免费无码一区二区三区蜜桃大| 免费高清av一区二区三区| 亚洲中文字幕丝袜制服一区 | 在线视频一区二区三区| 亚洲欧美日韩一区二区三区 | 亚洲一区二区三区香蕉| 91久久精品国产免费一区| 国模无码一区二区三区| 中文字幕一区二区在线播放| 无码人妻精品一区二区三区在线 | 无码AⅤ精品一区二区三区| 国产亚洲情侣一区二区无码AV| 久久久久久一区国产精品| 一区二区三区无码高清| 久久久91精品国产一区二区三区| 欲色aV无码一区二区人妻 | 国产萌白酱在线一区二区| 99精品一区二区免费视频| 国产成人精品一区二区三区无码| 国产免费一区二区三区在线观看| 国产SUV精品一区二区88| 久久99国产精品一区二区| 久久国产香蕉一区精品| 又硬又粗又大一区二区三区视频| 日本内射精品一区二区视频| 日本一区二区三区在线看| 久久国产香蕉一区精品| 亚洲一区二区三区在线视频| 无码人妻久久一区二区三区蜜桃 | 精品久久国产一区二区三区香蕉| 日韩精品一区在线| 性色AV 一区二区三区| 国产丝袜视频一区二区三区|