日常筆記

weixin_33807284發表於2016-09-14

JavaScript單執行緒與瀏覽器多執行緒

  1. Javascript是單執行緒的:因為JS執行在瀏覽器中,是單執行緒的,每個window一個JS執行緒。作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM。若以多執行緒的方式操作這些DOM,則可能出現操作的衝突。假設有兩個執行緒同時操作一個DOM元素,執行緒1要求瀏覽器刪除DOM,而執行緒2卻要求修改DOM樣式,這時瀏覽器就無法決定採用哪個執行緒的操作。
  2. 瀏覽器不是單執行緒的,引擎可能存在如下執行緒:

    • javascript引擎執行緒
    • 介面渲染執行緒
    • 瀏覽器事件觸發執行緒
    • Http請求執行緒等

Event Loop

事件迴圈機制:
主執行緒從"任務佇列"中讀取事件,這個過程是迴圈不斷的,所以整個的這種執行機制又稱為Event Loop(事件迴圈)。主執行緒執行的時候,產生堆(heap)和棧(stack),棧中的程式碼呼叫各種外部API,它們在"任務佇列"中加入各種事件(click,load,done)。只要棧中的程式碼執行完畢,主執行緒就會去讀取"任務佇列",依次執行那些事件所對應的回撥函式。

  • 執行棧:所有同步任務都在主執行緒上執行,形成一個執行棧;
  • 任務佇列:主執行緒之外,還存在一個"任務佇列"(task queue)。只要非同步任務有了執行結果,就在"任務佇列"之中放置一個事件,當棧為空時,就會從任務佇列中取出一個任務並執行。

定時器:
定時器包括setTimeout與setInterval兩個方法。它們的第二個引數是指定其回撥函式推遲每隔多少毫秒數後執行。
零延遲 setTimeout(func, 0):零延遲並不是意味著回撥函式立刻執行。它取決於主執行緒當前是否空閒與“任務佇列”裡其前面正在等待的任務。當計時器時間規定的時候,回撥函式會被新增到“任務佇列”中,等到執行棧的任務執行完畢,就會來執行這裡的任務。所以,有的時候即使計時器已經到0了,也會不立即執行計時器中的回撥任務。

Ajax非同步請求:
在發起ajax請求的時候,瀏覽器會開一個執行緒去執行這一步驟,發請求這一步是屬於執行棧中的同步任務,在請求發完之後就會執行棧中的下一個任務,而等到請求有了結果,就會把結果放入“任務佇列”中,等待執行;

JavaScript非同步程式設計

JavaScript是單執行緒的,所謂“單執行緒”,就是同一時間只能執行一個任務。如果有多個任務,就必須排隊,直到前一個任務完成。這種模式的好處就是實現比較簡單,尤其在瀏覽器環境中,可以避免很多不必要的麻煩。壞處就是如果一個任務耗時很長,那麼該任務後面的任務就必須一直等待,導致整個頁面都卡住無法繼續往下執行。

為了解決這個問題,JavaScript將任務的執行模式分為兩類,一個是同步模式,另一個是非同步模式。

"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,然後再執行,程式的執行順序與任務的排列順序是一致的、同步的;"非同步模式"則完全不同,每一個任務有一個或多個回撥函式(callback),前一個任務結束後,不是執行後一個任務,而是執行回撥函式,後一個任務則是不等前一個任務結束就執行,所以程式的執行順序與任務的排列順序是不一致的、非同步的。

JS非同步程式設計的4中方法包括:

  • 回撥函式:

    f1();
    f2();
    
    function f1(callback){
        setTimeout(function(){
            callback();
        })
    }
    
    f1(f2);
  • 事件監聽:採用事件驅動模式。任務的執行不取決於程式碼的順序,而取決於某個事件是否發生。

    //jQuery
    f1.on('done', f2);
    
    function f1(){
       setTimeout(function () {
           //do something
         f1.trigger('done');//執行完成後,立即觸發done事件,從而開始執行f2。
       }, 1000);
    }
  • 釋出/訂閱:又稱觀察者模式

    jQuery.subscribe("done", f2);//訂閱'done'這個訊號
    
    function f1(){
       setTimeout(function () {
         //do something
         jQuery.publish("done");//f1()執行完之後,發出'done'這個訊號,f2開始執行
       }, 1000);
    }
    
    jQuery.unsubscribe("done", f2);//取消訂閱
  • Promises:ES6原生提供了Promise物件。所謂Promise物件,就是代表了未來某個將要發生的事件(通常是一個非同步操作)。它的好處在於,有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件還提供了一整套完整的介面,使得可以更加容易地控制非同步操作。

    var promise = new Promise(function(resolve, reject) {
      if (/* 非同步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    
    promise.then(function(value) {
      // success
    }, function(value) {
      // failure
    });

ES6模組管理

ECMAScript 6基於export和import,定義了模組的匯出和匯入規範,在語言標準層面實現了模組機制。該標準的目標是建立一種能夠相容CommoneJS和AMD兩標準的規範,即可以像CommoneJS一樣語法簡潔、使用單一的介面且支援迴圈依賴,又可以像AMD支援非同步載入和可配置的模組載入。

大致來說,當 JS 引擎執行一個模組的時候,它的行為大致可歸納為以下四步:

  1. 解析:引擎實現會閱讀模組的原始碼,並且檢查是否有語法錯誤。
  2. 載入:引擎實現會(遞迴地)載入所有被引入的模組。
  3. 連結:引擎實現會為每個新載入的模組建立一個作用域,並且將模組中的宣告繫結填入其中,包括從其他模組中引入的。

JS載入

動態載入常用的4種方法:

  1. document.write:document.write("<script src='package.js'><\/script>");
  2. 動態改變已有script的src屬性:js.src = "package.js";
  3. 動態建立script元素:

    var script = document.createElement("script");
    script.src = "XXX";
    script.type = "XXX";
    document.head.appendChild(script);
  4. 用XMLHTTP取得要指令碼的內容,再建立 Script 物件。

專案中載入JS的方法:
在頁面底部寫一個自呼叫函式,載入入口JS,然後入口中的方法執行,載入需要的其他JS。

<script>
    (function(G,D){var el=D.createElement('script'),d="XXX';
        el.src=((G._moduleConfig&&G._moduleConfig.domain)?G._moduleConfig.domain:d)+"XXX?t=XXX";
        D.getElementsByTagName('head')[0].appendChild(el);
    })(this,document);
</script>

在VM平臺上,可以選擇將幾個模組分組打包到一個入口檔案中,然後在這個入口檔案中,將每個組動態生成一個script標籤,插入head中。

非同步載入:
瀏覽器在渲染一個頁面的時候,遇到一個<script>標籤就會去下載對應的js檔案,如果採用同步載入JS的方式,會造成頁面阻塞,直到JS載入完成再繼續渲染,所以有了非同步載入JS的形式。

常見的非同步載入JS方式,將js程式碼包裹在匿名函式中並立即執行,動態生成<script>標籤去拉取js,可以實現多個js檔案並行下載,只有在解析執行的時候會阻塞渲染。另外,可以使用script標籤的asyncdefer屬性。

延遲載入:
延遲載入就是一開始並不載入這些暫時不用的js,而是在需要的時候或稍後再通過js 的控制來非同步載入。
也就是將 js 切分成許多模組,頁面初始化時只載入需要立即執行的 js ,然後其它 js 的載入延遲到第一次需要用到的時候再載入。
特別是頁面有大量不同的模組組成,很多可能暫時不用或根本就沒用到。

script的兩階段載入與延遲執行
JS的載入其實是由兩階段組成:下載內容(download bytes)和執行(parse and execute)。
瀏覽器在下載完 js 的內容後就會立即對其解析和執行,不管是同步載入還是非同步載入。
前面說的非同步載入,解決的只是下載階段的問題,但程式碼在下載後會立即執行。
而瀏覽器在解析執行 JS 階段是阻塞任何操作的,這時的瀏覽器處於無響應狀態。
我們都知道通過網路下載 script 需要明顯的時間,但容易忽略了第二階段,解析和執行也是需要時間的。script的解析和執行所花的時間比我們想象的要多,尤其是script 很多很大的時候。有些是需要立刻執行,而有些則不需要(比如只是在展示某個介面或執行某個操作時才需要)。
這些script 可以延遲執行,先非同步下載快取起來,但不立即執行,而是在第一次需要的時候執行一次。
利用特殊的技巧可以做到下載與執行的分離。比如將 JS 的內容作為object物件載入快取起來,所以就不會立即執行了,然後在第一次需要的時候再執行。

SQL相關

  1. 多表查詢:

    • 定義:根據特定的連線條件從不同的表中獲取所需的資料;
    • 語法:SELECT table1.column, table2.column FROM table1, table2 WHERE table1.column1 = table2.column2;
    • 注意:where不要省了,省略where即為笛卡爾集,而且where條件要有效,兩張表間有一個相同的欄位,才好進行有效的多表查詢;
    • 簡化:為了簡化SQL書寫,可為表名定義別名,格式:from 表名別名,如:from emp e,dept d;
    • 優化:建議使用表的別名及表字首,使用表別名可以簡化查詢,而使用表字首則可以提高查詢效能;
  2. 連線型別:從資料顯示方式來講有:內連線和外連線。

    • 內連線:只返回滿足連線條件的資料。

      • 例:select empno,ename,sal,dname,loc from emp,dept where emp.deptno=dept.deptno;
      • 等值連線:在連線條件中使用等於號(=)運算子比較被連線列的列值,其查詢結果中列出被連線表中的所有列,包括其中的重複列。
      • 不等值連線:在連線條件使用除等於運算子以外的其它比較運算子比較被連線的列的列值。這些運算子包括>、>=、<=、<、!>、!<和<>。
      • 自然連線:在連線條件中使用等於(=)運算子比較被連線列的列值,但它使用選擇列表指出查詢結果集合中所包括的列,並刪除連線表中的重複列。
    • 外連線:除了返回滿足連線條的行以外,還返回左(右)表中,不滿足條件的行,稱為左(右)連線;

      • 左外連線:是以左表為基準,將左表沒有的對應項顯示,右表的列為NULL

        • select * from book as a left join stu as b on a.sutid = b.stuid
      • 右外連線:是以右表為基準,將a.stuid = b.stuid的資料進行連線,然以將右表沒有的對應項顯示,左表的列為NULL

        • select * from book as a right join stu as b on a.sutid = b.stuid
      • 全連線:完整外部聯接返回左表和右表中的所有行。當某行在另一個表中沒有匹配行時,則另一個表的選擇列表列包含空值。如果表之間有匹配行,則整個結果集行包含基表的資料值。

        • select * from book as a full outer join stu as b on a.sutid = b.stuid
  3. CI框架常用的資料庫操作:

    • 常規查詢:$this->db->query('YOUR QUERY HERE');
    • 查詢繫結:查詢繫結可以簡化你的查詢語法,它通過系統自動的為你將各個查詢組裝在一起。 參考下面的例子:
    $sql = "SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?";
    $this->db->query($sql, array(3, 'live', 'Rick'));
    • $this->db->count_all():該方法用於獲取資料表的總行數,第一個引數為表名;
    • 查詢構造器類:odeIgniter 提供了查詢構造器類,查詢構造器允許你使用較少的程式碼來在資料庫中 獲取、新增或更新資料。有時只需要一兩行程式碼就能完成資料庫操作。CodeIgniter 並不需要為每個資料表提供一個類,而是使用了一種更簡單的介面。

      • 查詢:
      $query = $this->db->get('mytable');  // Produces: SELECT * FROM mytable
      
      $query = $this->db->get('mytable', 10, 20);
      
      // Executes: SELECT * FROM mytable LIMIT 20, 10
      // (in MySQL. Other databases have slightly different syntax)
      • $this->db->select():該方法用於編寫查詢語句中的 SELECT 子句:
      $this->db->select('title, content, date');
      $query = $this->db->get('mytable');
      
      // Executes: SELECT title, content, date FROM mytable
      • $this->db->from():該方法用於編寫查詢語句中的 FROM 子句:
      $this->db->select('title, content, date');
      $this->db->from('mytable');
      $query = $this->db->get();  // Produces: SELECT title, content, date FROM mytable
      • $this->db->join():該方法用於編寫查詢語句中的 JOIN 子句:
      $this->db->select('*');
      $this->db->from('blogs');
      $this->db->join('comments', 'comments.id = blogs.id');
      $query = $this->db->get();
      
      // Produces:
      // SELECT * FROM blogs JOIN comments ON comments.id = blogs.id
      
      //你可以傳入第三個引數指定連線的型別,有這樣幾種選擇:left,right,outer,inner,left outer 和 right outer 。
      
      $this->db->join('comments', 'comments.id = blogs.id', 'left');
      // Produces: LEFT JOIN comments ON comments.id = blogs.id
      • $this->db->where():該方法提供了4中方式讓你編寫查詢語句中的 WHERE 子句(所有的資料將會自動轉義,生成安全的查詢語句。):
      1.簡單的 key/value 方式:
      
      $this->db->where('name', $name); // Produces: WHERE name = 'Joe'
      注意自動為你加上了等號。
      
      如果你多次呼叫該方法,那麼多個 WHERE 條件將會使用 AND 連線起來:
      
      $this->db->where('name', $name);
      $this->db->where('title', $title);
      $this->db->where('status', $status);
      // WHERE name = 'Joe' AND title = 'boss' AND status = 'active'
      
      2.自定義 key/value 方式:
      
      為了控制比較,你可以在第一個引數中包含一個比較運算子:
      
      $this->db->where('name !=', $name);
      $this->db->where('id <', $id); // Produces: WHERE name != 'Joe' AND id < 45
      
      3.關聯陣列方式:
      
      $array = array('name' => $name, 'title' => $title, 'status' => $status);
      $this->db->where($array);
      // Produces: WHERE name = 'Joe' AND title = 'boss' AND status = 'active'
      你也可以在這個方法裡包含你自己的比較運算子:
      
      $array = array('name !=' => $name, 'id <' => $id, 'date >' => $date);
      $this->db->where($array);
      
      4.自定義字串:
      
      你可以完全手工編寫 WHERE 子句:
      
      $where = "name='Joe' AND status='boss' OR status='active'";
      $this->db->where($where);
      $this->db->where() 方法有一個可選的第三個引數,如果設定為 FALSE,CodeIgniter 將不保護你的表名和欄位名。
      
      $this->db->where('MATCH (field) AGAINST ("value")', NULL, FALSE);
      • $this->db->like():該方法用於生成 LIKE 子句,在進行搜尋時非常有用。
      簡單 key/value 方式:
      
      $this->db->like('title', 'match');
      // Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
      如果你多次呼叫該方法,那麼多個 WHERE 條件將會使用 AND 連線起來:
      
      $this->db->like('title', 'match');
      $this->db->like('body', 'match');
      // WHERE `title` LIKE '%match%' ESCAPE '!' AND  `body` LIKE '%match% ESCAPE '!'
      可以傳入第三個可選的引數來控制 LIKE 萬用字元(%)的位置,可用選項有:'before','after' 和 'both' (預設為 'both')。
      
      $this->db->like('title', 'match', 'before');    // Produces: WHERE `title` LIKE '%match' ESCAPE '!'
      $this->db->like('title', 'match', 'after'); // Produces: WHERE `title` LIKE 'match%' ESCAPE '!'
      $this->db->like('title', 'match', 'both');  // Produces: WHERE `title` LIKE '%match%' ESCAPE '!'
      關聯陣列方式:
      
      $array = array('title' => $match, 'page1' => $match, 'page2' => $match);
      $this->db->like($array);
      // WHERE `title` LIKE '%match%' ESCAPE '!' AND  `page1` LIKE '%match%' ESCAPE '!' AND  `page2` LIKE '%match%' ESCAPE '!'
      • $this->db->group_by():$this->db->group_by("title"); // Produces: GROUP BY title;
      • $this->db->order_by():$this->db->order_by('title', 'DESC');
      • $this->db->limit():$this->db->limit(10); // Produces: LIMIT 10
      • $this->db->count_all_results():該方法用於獲取特定查詢返回結果的數量,也可以使用查詢構造器的這些方法: where(),or_where(),like(),or_like() 等等。
      • $this->db->delete():$this->db->delete('mytable', array('id' => $id)); // Produces: // DELETE FROM mytable // WHERE id = $id

為什麼chrome瀏覽器的速度很快

  1. Chrome 把瀏覽器上做的每件事都拆成獨立的程式,每個tab都是一個獨立的程式,並利用程式間通訊來完成它們之間的同步,windows系統下可以通過工作管理員看到許多chrome 標籤頁的程式。
  2. 在還沒點選 URL 之前,Chrome 已經在幫使用者載入了

參考文章:
JavaScript的載入和執行效能優化
Javascript 非同步載入詳解
ES6 的模組系統
seaJs學習筆記之seaJs的非同步載入和載入多個js檔案
Promise物件
Javascript非同步程式設計的4種方法
JavaScript 執行機制詳解:再談Event Loop
JavaScript:徹底理解同步、非同步和事件迴圈(Event Loop)
JavaScript單執行緒和瀏覽器事件迴圈簡述
Javascript是單執行緒的深入分析
查詢輔助函式
SQL的幾種連線:內連線、左聯接、右連線、全連線、交叉連線
SQL基礎-->多表查詢
CHROME程式間通訊
瀏覽器是如何工作的
瀏覽器是怎樣工作的:渲染引擎,HTML解析
等......

相關文章