在script标签写export为什么会抛错|type module import ES5 ES6 预处理 指令序言 JavaScript JS

今天我们进入到语法部分的学习。在讲解具体的语法结构之前,这一堂课我首先要给你介绍一下 JavaScript 语法的一些基本规则。

脚本和模块

首先,JavaScript 有两种源文件,一种叫做脚本,一种叫做模块。这个区分是在 ES6 引入了模块机制开始的,在 ES5 和之前的版本中,就只有一种源文件类型(就只有脚本)。

脚本是可以由浏览器或者 node 环境引入执行的,而模块只能由 JavaScript 代码用 import 引入执行。

从概念上,我们可以认为脚本具有主动性的 JavaScript 代码段,是控制宿主完成一定任务的代码;而模块是被动性的 JavaScript 代码段,是等待被调用的库。

我们对标准中的语法产生式做一些对比,不难发现,实际上模块和脚本之间的区别仅仅在于是否包含 import 和 export。

脚本是一种兼容之前的版本的定义,在这个模式下,没有 import 就不需要处理加载“.js”文件问题。

现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加 type=“module”。如果引入脚本,则不需要 type。

<script type="module" src="xxxxx.js"></script>

这样,就回答了我们标题中的问题,script 标签如果不加type=“module”,默认认为我们加载的文件是脚本而非模块,如果我们在脚本中写了 export,当然会抛错。

脚本中可以包含语句。模块中可以包含三种内容:import 声明,export 声明和语句。普通语句我们会在下一课专门给你讲解,下面我们就来讲讲 import 声明和 export 声明。

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"​​ 把模块中所有的变量以类似对象属性的方式引入。

第一种方式还可以跟后两种组合使用。

  • ​import d, {a as x, modify} from "./a.js"​
  • ​import d, * as x from "./a.js"​

语法要求不带 as 的默认值永远在最前。注意,这里的变量实际上仍然可以受到原来模块的控制。

我们看一个例子,假设有两个模块 a 和 b。我们在模块 a 中声明了变量和一个修改变量的函数,并且把它们导出。我们用 b 模块导入了变量和修改变量的函数。

模块 a:

export var a = 1;export function modify(){a = 2;
}

模块 b:

import {a, modify} from "./a.js";console.log(a);modify();console.log(a);

当我们调用修改变量的函数后,b 模块变量也跟着发生了改变。这说明导入与一般的赋值不同,导入后的变量只是改变了名字,它仍然与原来的变量是同一个。

export 声明

我们再来说说 export 声明。与 import 相对,export 声明承担的是导出的任务。

模块中导出变量的方式有两种,一种是独立使用 export 声明,另一种是直接在声明型语句前添加 export 关键字。

独立使用 export 声明就是一个 export 关键字加上变量名列表,例如:

export {a, b, c};

我们也可以直接在声明型语句前添加 export 关键字,这里的 export 可以加在任何声明性质的语句之前,整理如下:

  • var

  • function (含 async 和 generator)

  • class

  • let

  • const

export 还有一种特殊的用法,就是跟 default 联合使用。export default 表示导出一个默认变量值,它可以用于 function 和 class。这里导出的变量是没有名称的,可以使用import x from "./a.js"这样的语法,在模块中引入。

export default 还支持一种语法,后面跟一个表达式,例如:

var a = {};export default a;

但是,这里的行为跟导出变量是不一致的,这里导出的是值,导出的就是普通变量 a 的值,以后 a 的变化与导出的值就无关了,修改变量 a,不会使得其他模块中引入的 default 值发生改变。

在 import 语句前无法加入 export,但是我们可以直接使用 export from 语法。

export a from "a.js"

JavaScript 引擎除了执行脚本和模块之外,还可以执行函数。而函数体跟脚本和模块有一定的相似之处,所以接下来,给你讲讲函数体的相关知识。

函数体

执行函数的行为通常是在 JavaScript 代码执行时,注册宿主环境的某些事件触发的,而执行的过程,就是执行函数体(函数的花括号中间的部分)。

我们先看一个例子,感性地理解一下:

setTimeout(function(){    console.log("go go go");
}, 10000)

这段代码通过 setTimeout 函数注册了一个函数给宿主,当一定时间之后,宿主就会执行这个函数。

你还记得吗,我们前面已经在运行时这部分讲过,宿主会为这样的函数创建宏任务。

当我们学习了语法之后,我们可以认为,宏任务中可能会执行的代码包括“脚本 (script)”“模块(module)”和“函数体(function body)”。正因为这样的相似性,我们把函数体也放到本课来讲解。

函数体其实也是一个语句的列表。跟脚本和模块比起来,函数体中的语句列表中多了 return 语句可以用。

函数体实际上有四种,下面,我来分别介绍一下。

普通函数体,例如:

function foo(){    //Function body}

异步函数体,例如:

async function foo(){    //Function body}

生成器函数体,例如:

function *foo(){    //Function body}

异步生成器函数体,例如:

async function *foo(){    //Function body}

上面四种函数体的区别在于:能否使用 await 或者 yield 语句。

关于函数体、模块和脚本能使用的语句,我整理了一个表格,你可以参考一下:

讲完了三种语法结构,我再来介绍两个 JavaScript 语法的全局机制:预处理和指令序言。

这两个机制对于我们解释一些 JavaScript 的语法现象非常重要。不理解预处理机制我们就无法理解 var 等声明类语句的行为,而不理解指令序言,我们就无法解释严格模式。

预处理

JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义。

因为一些历史包袱,这一部分内容非常复杂,首先我们看一下 var 声明。

var 声明

var 声明永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。

我们还是从实例来进行学习。

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

这段代码声明了一个脚本级别的 a,又声明了 foo 函数体级别的 a,我们注意到,函数体级的var出现在 console.log 语句之后。

但是预处理过程在执行之前,所以有函数体级的变量 a,就不会去访问外层作用域中的变量 a 了,而函数体级的变量 a 此时还没有赋值,所以是 undefined。我们再看一个情况:

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

这段代码比上一段代码在 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。

这个行为是 JavaScript 公认的设计失误之一,一个语句中的 a 在预处理阶段和执行阶段被当做两个不同的变量,严重违背了直觉,但是今天,在 JavaScript 设计原则“don’t break the web”之下,已经无法修正了,所以你需要特别注意。

因为早年 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。

function 声明

function 声明的行为原本跟 var 非常相似,但是在最新的 JavaScript 标准中,对它进行了一定的修改,这让情况变得更加复杂了。

在全局(脚本、模块和函数体),function 声明表现跟 var 相似,不同之处在于,function 声明不但在作用域中加入变量,还会给它赋值。

我们看一下 function 声明的例子:

console.log(foo);function foo(){
}

这里声明了函数 foo,在声明之前,我们用 console.log 打印函数 foo,我们可以发现,已经是函数 foo 的值了。

function 声明出现在 if 等语句中的情况有点复杂,它仍然作用于脚本、模块和函数体级别,在预处理阶段,仍然会产生变量,它不再被提前赋值:

console.log(foo);if(true) {    function foo(){}
}

这段代码得到 undefined。如果没有函数声明,则会抛出错误。

这说明 function 在预处理阶段仍然发生了作用,在作用域中产生了变量,没有产生赋值,赋值行为发生在了执行阶段。

出现在 if 等语句中的 function,在 if 创建的作用域中仍然会被提前,产生赋值效果,我们会在下一节课继续讨论。

class 声明

class 声明在全局的行为跟 function 和 var 都不一样。

在 class 声明之前使用 class 名,会抛错:

console.log(c);class c{
}

这段代码我们试图在 class 前打印变量 c,我们得到了个错误,这个行为很像是 class 没有预处理,但是实际上并非如此。

我们看个复杂一点的例子:

var c = 1;function foo(){    console.log(c);    class c {}
}foo();

这个例子中,我们把 class 放进了一个函数体中,在外层作用域中有变量 c。然后试图在 class 之前打印 c。

执行后,我们看到,仍然抛出了错误,如果去掉 class 声明,则会正常打印出 1,也就是说,出现在后面的 class 声明影响了前面语句的结果。

这说明,class 声明也是会被预处理的,它会在作用域中创建变量,并且要求访问它时抛出错误。

class 的声明作用不会穿透 if 等语句结构,所以只有写在全局环境才会有声明作用,这部分我们将会在下一节课讲解。

这样的 class 设计比 function 和 var 更符合直觉,而且在遇到一些比较奇怪的用法时,倾向于抛出错误。

按照现代语言设计的评价标准,及早抛错是好事,它能够帮助我们尽量在开发阶段就发现代码的可能问题。

指令序言机制

脚本和模块都支持一种特别的语法,叫做指令序言(Directive Prologs)。

这里的指令序言最早是为了 use strict 设计的,它规定了一种给 JavaScript 代码添加元信息的方式。

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

这段代码展示了严格模式的用法,我这里定义了函数 f,f 中打印 this 值,然后用 call 的方法调用 f,传入 null 作为 this 值,我们可以看到最终结果是 null 原封不动地被当做 this 值打印了出来,这是严格模式的特征。

如果我们去掉严格模式的指令需要,打印的结果将会变成 global。

"use strict"是 JavaScript 标准中规定的唯一一种指令序言,但是设计指令序言的目的是,留给 JavaScript 的引擎和实现者一些统一的表达方式,在静态扫描时指定 JavaScript 代码的一些特性。

例如,假设我们要设计一种声明本文件不需要进行 lint 检查的指令,我们可以这样设计:

"no lint";"use strict";function doSth(){    //......}//......

JavaScript 的指令序言是只有一个字符串直接量的表达式语句,它只能出现在脚本、模块和函数体的最前面。

我们看两个例子:

function doSth(){    //......}"use strict";var a = 1;//......

这个例子中,"use strict"没有出现在最前,所以不是指令序言。

'use strict';function doSth(){    //......}var a = 1;//......

这个例子中,'use strict'是单引号,这不妨碍它仍然是指令序言。

结语

今天,我们一起进入了 JavaScript 的语法部分,在开始学习之前,我先介绍了一部分语法的基本规则。

我们首先介绍了 JavaScript 语法的全局结构,JavaScript 有两种源文件,一种叫做脚本,一种叫做模块。介绍完脚本和模块的基础概念,我们再来把它们往下分,脚本中可以包含语句。模块中可以包含三种内容:import 声明,export 声明和语句。

最后,我介绍了两个 JavaScript 语法的全局机制:预处理和指令序言。

最后,给你留一个小任务,我们试着用 babel,分析一段 JavaScript 的模块代码,并且找出它中间的所有 export 的变量。

更多内容:👉开发者网站--技术前沿

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/691059.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何防止被恶意刷接口?

在面试时&#xff0c;经常会被问一个问题&#xff1a;如何防止别人恶意刷接口&#xff1f; 这是一个非常有意思的问题&#xff0c;防范措施挺多的。今天这篇文章专门跟大家一起聊聊&#xff0c;希望对你会有所帮助。 1 防火墙 防火墙是网络安全中最基本的安全设备之一&#x…

2023年全球软件开发大会(QCon北京站2023)2月:核心内容与学习收获(附大会核心PPT下载)

本次峰会是一个汇集了最新技术趋势、最佳实践和创新思维的盛会。对于从事软件开发和相关领域的专业人士来说&#xff0c;参加这样的大会将有助于他们了解行业动态、提升技能水平、拓展职业视野&#xff0c;并与同行建立联系和合作。 本次峰会包含&#xff1a;AI基础架构、DevO…

用户空间与内核通信(一)

在Linux中&#xff0c;内核空间与用户空间是操作系统中的两个主要部分&#xff0c;它们有着明显的区别和不同的功能。 内核空间&#xff1a; 内核空间是操作系统内核运行的区域&#xff0c;它包括了操作系统内核代码、数据结构和设备驱动程序等。内核空间位于虚拟地址空间的最…

Linux程序性能分析60秒+

Linux性能分析大师Brendan Gregg有一篇非常著名的博客&#xff0c;介绍在性能分析开始的60秒内&#xff0c;利用标准的Linux命令行工具&#xff0c;执行一次充分的性能检查&#xff0c;获得系统资源利用率和进程运行情况的整体概念&#xff0c;查看是否存在异常、评估饱和度。本…

安全架构设计理论与实践

一、考点分布 安全架构概述&#xff08;※※&#xff09;安全模型&#xff08;※※※&#xff09;信息安全整体架构设计网络安全体系架构设计区块链技术&#xff08;※※&#xff09; 二、安全架构概述 被动攻击&#xff1a;收集信息为主&#xff0c;破坏保密性 主动攻击&#…

Electron实战之菜单与托盘

菜单、托盘是桌面端应用必备的功能之一&#xff0c;我们通常会在菜单上配置应用常用的&#xff1a;偏好设置、显示隐藏、打开文件等功能&#xff0c;在托盘内设置&#xff1a;退出、重启、帮助等辅助性功能&#xff0c;帮助用户方便快捷地控制应用的一些系统功能。系统托盘实际…

SQL Developer 小贴士:显示RAC配置

前提&#xff1a; 已建立2节点RAC已在SQL Developer中建立了2个连接&#xff0c;分别到RAC的两个节点 然后单击菜单View>DBA&#xff0c;分别连接RAC节点1和节点2&#xff0c;并组织成目录&#xff08;不必须&#xff0c;但建议&#xff09;。 在两处可以体现为RAC配置。第…

代码随想录算法训练营|二叉树总结

二叉树的定义&#xff1a; struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode():val(0),left(nullptr),right(nullptr){}TreeNode(int val):val(val),left(nullptr),right(nullptr){}TreeNode(int val,TreeNode* left,TreeNode* right):val(val),left(left),…

智能家居中可自行收集能量的无电池的无线设备

此图片来源于网络 1、背景 ZigBee是一种基于IEEE 802.15.4标准的低速短距离无线通信技术&#xff0c;用于创建个人区域网络。其名称来源于蜜蜂的八字舞&#xff0c;因为蜜蜂通过这种舞蹈来与同伴传递花粉的所在方位信息&#xff0c;从而构成了群体中的通信网络。ZigBee技术具…

白话微机:6.解释RTOS以及一些考研面试问题

一. 前言&#xff08;总结世界观&#xff09; 很久很久以前&#xff0c;有这样一个世界&#xff0c;这个世界有着现实世界一样的元素&#xff1a;那里的人又有一个别的名字叫做“数据”&#xff0c;人有0有1&#xff1b;人们也有住房&#xff0c;这些住房在这个世界叫做“存储器…

6-酮-前列环素F1α(6-keto-PGF1α) ELISA检测试剂盒

高灵敏ELISA试剂盒&#xff0c;3小时内可检测低至1.40 pg/ml 6-酮前列腺素F1α 6-酮-前列环素F1α&#xff08;6-keto-PGF1α&#xff09;是前列环素&#xff08;PGI2&#xff09;的稳定水解产物。由于前列环素在缓冲液中的半衰期很短&#xff08;2-3分钟&#xff09;&#xff…

第四篇【传奇开心果系列】Python文本和语音相互转换库技术点案例示例:pyttsx3自动化脚本经典案例

传奇开心果短博文系列 系列短博文目录Python文本和语音相互转换库技术点案例示例系列 短博文目录前言一、雏形示例代码二、扩展思路介绍三、批量处理文本示例代码四、自定义语音设置示例代码五、结合其他库和API示例代码六、语音交互系统示例代码七、多语言支持示例代码八、添加…

JavaSE——面向对象基础(1/4)-面向对象编程、程序中的对象、对象的产生、对象的执行原理、类和对象的一些注意事项

目录 面向对象编程 程序中的对象 对象的产生 对象的执行原理 类和对象的一些注意事项 面向对象编程 开发一个一个的对象&#xff0c;把数据交给对象&#xff0c;再调用对象的方法来完成对数据的处理。 例如设计一个学生的对象&#xff0c;其中有姓名和成绩等&#xff0c…

【DDD】学习笔记-应用服务

Eric Evans 为运用领域驱动设计的系统架构划定了层次&#xff0c;在领域层和展现层之间引入了应用层&#xff08;Application Layer&#xff09;&#xff1a;“应用层要尽量简单&#xff0c;不包含业务规则或者知识&#xff0c;而只为下一层&#xff08;指领域层&#xff09;中…

Unity3D中刚体、碰撞组件、物理组件的区别详解

前言 Unity3D提供了丰富的功能和组件&#xff0c;其中包括刚体、碰撞组件和物理组件。这些组件在游戏开发中起着非常重要的作用&#xff0c;能够让游戏世界更加真实和有趣。本文将详细介绍这三种组件的区别以及如何在Unity3D中实现它们。 对惹&#xff0c;这里有一个游戏开发…

MAC M1安装vmware和centos7虚拟机并配置静态ip

一、下载vmware和centos7镜像 1、VMWare Fusion 官网的下载地址是&#xff1a;下载地址 下载好之后注册需要秘钥&#xff0c;在官网注册后使用免费的个人秘钥 2、centos7 下载地址&#xff1a; https://biosyxh.cn:5001/sharing/pAlcCGNJf 二、虚拟机安装 直接将下…

算法沉淀——多源 BFS(leetcode真题剖析)

算法沉淀——多源 BFS&#xff08;leetcode真题剖析&#xff09; 01.矩阵02.飞地的数量03.地图中的最高点04.地图分析 多源 BFS 是指从多个源点同时进行广度优先搜索的算法。在传统的 BFS 中&#xff0c;我们通常从一个起始点开始&#xff0c;逐层遍历所有的相邻节点。而在多…

探索AI视频生成新纪元:文生视频Sora VS RunwayML、Pika及StableVideo——谁将引领未来

探索AI视频生成新纪元&#xff1a;文生视频Sora VS RunwayML、Pika及StableVideo——谁将引领未来 sora文生视频&#xff0c;探索AI视频生成新纪元 由于在AI生成视频的时长上成功突破到一分钟&#xff0c;再加上演示视频的高度逼真和高质量&#xff0c;Sora立刻引起了轰动。在S…

408计算机网络--基础概论

学习计算机网络走以前需要首先明白一个大的概念&#xff0c;计算机网络通常分为通信子网&#xff08;实现数据通信&#xff09;和资源子网&#xff08;实现资源共享/数据处理&#xff09;七层妖塔 计算机网络&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0…

Rabbitmq入门与应用(三)-RabbitMQ开发流程

RabbitMQ开发流程 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>配置MQ 最简配置 spring:rabbitmq:host: mq的安装机器ipport: 5672username: ad…