极客时间重学前端--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。

相关文章
相关标签/搜索