極客時間重學前端--javascript語法(一)在script標籤寫export爲何會拋錯?

腳本和模塊

JavaScript 有兩種源文件,一種叫作腳本,一種叫作模塊。這個區分是在 ES6 引入了模塊機制開始的,在 ES5 和以前的版本中,就只有一種源文件類型(就只有腳本)。javascript

腳本是能夠由瀏覽器或者 node 環境引入執行的,而模塊只能由 JavaScript 代碼用 import 引入執行。java

實際上模塊和腳本之間的區別僅僅在因而否包含 import 和 export。node

import 聲明 咱們首先來介紹一下 import 聲明,import 聲明有兩種用法,一個是直接 import 一個模塊,另外一個是帶 from 的 import,它能引入模塊裏的一些信息。面試

import "mod"; // 引入一個模塊
import v from "mod";  // 把模塊默認的導出值放入變量 v
複製代碼
  • 直接 import 一個模塊,只是保證了這個模塊代碼被執行,引用它的模塊是沒法得到它的任何信息的。
  • 帶 from 的 import 意思是引入模塊中的一部分信息,能夠把它們變成本地的變量。

帶 from 的 import 細分又有三種用法,咱們能夠分別看下例子:瀏覽器

import x from "./a.js" 引入模塊中導出的默認值。
import {a as x, modify} from "./a.js"; 引入模塊中的變量。
import * as x from "./a.js" 把模塊中全部的變量以相似對象屬性的方式引入。
複製代碼

咱們看一個例子,假設有兩個模塊 a 和 b。咱們在模塊 a 中聲明瞭變量和一個修改變量的函數,而且把它們導出。咱們用 b 模塊導入了變量和修改變量的函數。 模塊 a:app

export var a = 1;

export function modify(){
    a = 2;
}
複製代碼

模塊 b:異步

import {a, modify} from "./a.js";

console.log(a);

modify();

console.log(a);
複製代碼

當咱們調用修改變量的函數後,b 模塊變量也跟着發生了改變。這說明導入與通常的賦值不一樣,導入後的變量只是改變了名字,它仍然與原來的變量是同一個。async

export聲明

咱們再來講說 export 聲明。與 import 相對,export 聲明承擔的是導出的任務。 模塊中導出變量的方式有兩種,一種是獨立使用 export 聲明,另外一種是直接在聲明型語句前添加 export 關鍵字。函數

export {a, b, c};
複製代碼

咱們也能夠直接在聲明型語句前添加 export 關鍵字,這裏的 export 能夠加在任何聲明性質的語句以前,整理以下:ui

var
function (含 async 和 generator) class let const 複製代碼

export 還有一種特殊的用法,就是跟 default 聯合使用。export default 表示導出一個默認變量值,它能夠用於 function 和 class。這裏導出的變量是沒有名稱的,可使用import x from "./a.js"這樣的語法,在模塊中引入。

var a = {};
export default a;
複製代碼

可是,這裏的行爲跟導出變量是不一致的,這裏導出的是值,導出的就是普通變量 a 的值,之後 a 的變化與導出的值就無關了,修改變量 a,不會使得其餘模塊中引入的 default 值發生改變。 在 import 語句前沒法加入 export,可是咱們能夠直接使用 export from 語法。

export a from "a.js"
複製代碼

函數體

函數體實際上有四種,下面,我來分別介紹一下。 普通函數體,例如:

function foo(){
    //Function body
}
複製代碼

異步函數體,例如:

async function foo(){
    //Function body
}
複製代碼

生成器函數體,例如:

function *foo(){
    //Function body
}
複製代碼

異步生成器函數體,例如:

function *foo(){
    //Function body
}
複製代碼

上面四種函數體的區別在於:可否使用 await 或者 yield 語句。 關於函數體、模塊和腳本能使用的語句,我整理了一個表格,你能夠參考一下:

預處理

不理解預處理機制咱們就沒法理解 var 等聲明類語句的行爲,而不理解指令序言,咱們就沒法解釋嚴格模式。

JavaScript 執行前,會對腳本、模塊和函數體中的語句進行預處理。預處理過程將會提早處理 var、函數聲明、class、const 和 let 這些語句,以肯定其中變量的意義。 由於一些歷史包袱,這一部份內容很是複雜,首先咱們看一下 var 聲明。 var 聲明 var 聲明永遠做用於腳本、模塊和函數體這個級別,在預處理階段,不關心賦值的部分,只管在當前做用域聲明這個變量。

var a = 1;

function foo() {
    console.log(a);
    var a = 2;
}

foo(); // undefined

複製代碼

這段代碼聲明瞭一個腳本級別的 a,又聲明瞭 foo 函數體級別的 a,咱們注意到,函數體級的var出如今 console.log 語句以後。 可是預處理過程在執行以前,因此有函數體級的變量 a,就不會去訪問外層做用域中的變量 a 了,而函數體級的變量 a 此時尚未賦值,因此是 undefined。咱們再看一個狀況:

var a = 1;

function foo() {
    console.log(a);
    if(false) {
        var a = 2;
    }
}

foo(); // undefind

複製代碼

這段代碼比上一段代碼在var a = 2以外多了一段 if,咱們知道 if(false) 中的代碼永遠不會被執行,可是預處理階段並無論這個,var 的做用可以穿透一切語句結構,它只認腳本、模塊和函數體三種語法結構。因此這裏結果跟前一段代碼徹底同樣,咱們會獲得 undefined。 咱們看下一個例子,咱們在運行時部分講過相似的例子。

var a = 1;

function foo() {
    var o= {a:3}
    with(o) {
        var a = 2;
    }
    console.log(o.a);
    console.log(a);
}

foo();
複製代碼

在這個例子中,咱們引入了 with 語句,咱們用 with(o) 建立了一個做用域,並把 o 對象加入詞法環境,在其中使用了var a = 2;語句。 在預處理階段,只認var中聲明的變量,因此一樣爲 foo 的做用域建立了 a 這個變量,可是沒有賦值。 在執行階段,當執行到var a = 2時,做用域變成了 with 語句內,這時候的 a 被認爲訪問到了對象 o 的屬性 a,因此最終執行的結果,咱們獲得了 2 和 undefined。

這是js的設計失誤, 由於早年 JavaScript 沒有 let 和 const,只能用 var,又由於 var 除了腳本和函數體都會穿透,人民羣衆發明了「當即執行的函數表達式(IIFE)」這一用法,用來產生做用域,例如:

for(var i = 0; i < 20; i ++) {
    void function(i){
        var div = document.createElement("div");
        div.innerHTML = i;
        div.onclick = function(){
            console.log(i);
        }
        document.body.appendChild(div);
    }(i);
}
複製代碼

這段代碼很是經典,經常在實際開發中見到,也常常被用做面試題,爲文檔添加了 20 個 div 元素,而且綁定了點擊事件,打印它們的序號。 咱們經過 IIFE 在循環內構造了做用域,每次循環都產生一個新的環境記錄,這樣,每一個 div 都能訪問到環境中的 i。 若是咱們不用 IIFE:

for(var i = 0; i < 20; i ++) {
    var div = document.createElement("div");
    div.innerHTML = i;
    div.onclick = function(){
        console.log(i);
    }
    document.body.appendChild(div);
}
複製代碼

這段代碼的結果將會是點每一個 div 都打印 20,由於全局只有一個 i,執行完循環後,i 變成了 20。

指令序言機制

"use strict";
function f(){
    console.log(this);
};
f.call(null);
複製代碼

這段代碼展現了嚴格模式的用法,我這裏定義了函數 f,f 中打印 this 值,而後用 call 的方法調用 f,傳入 null 做爲 this 值,咱們能夠看到最終結果是 null 原封不動地被當作 this 值打印了出來,這是嚴格模式的特徵。 若是咱們去掉嚴格模式的指令須要,打印的結果將會變成 global。

相關文章
相關標籤/搜索