工程化实战内功修炼测试题

server/2024/11/19 18:55:09/

什么是编译器?

compiler也叫编译器,是一种电脑程序,它会将用某种编程语言写成的源代码,转换成另一种编程语言。
从维基百科的定义来看,编译器就是个将当前语言转为其他语言的过程,回到babel上,它所做的事就是语法糖之类的转换,比如ES6/ES7/JSX转为ES5或者其他指定版本,因此称之为compiler也是正确的,换言之,像我们平时开发过程中所谓的其他工具,如:

  • Less/Saas
  • TypeScript/coffeeScript
  • Eslint

词法分析(Lexical Analysis)

将文本分割成一个个的“token”,例如:init、main、init、x、;、x、=、3、;、}等等。同时它可以去掉一些注释、空格、回车等等无效字符;

语法分析(Syntactic Analysis)

我们日常所说的编译原理就是将一种语言转换为另一种语言。编译原理被称为形式语言,它是一类无需知道太多语言背景、无歧义的语言。而自然语言通常难以处理,主要是因为难以识别语言中哪些是名词哪些是动词哪些是形容词。例如:“进口汽车”这句话,“进口”到底是动词还是形容词?所以我们要解析一门语言,前提是这门语言有严格的语法规定的语言,而定义语言的语法规格称为文法。

代码转换(Transformation)

在得到AST后,我们一般会先将AST转为另一种AST,目的是生成更符合预期的AST,这一步称为代码转换。
代码转换的优势:主要是产生工程上的意义

  • 易移植:与机器无关,所以它作为中间语言可以为生成多种不同型号的目标机器码服务;
  • 机器无关优化:对中间码进行机器无关优化,利于提高代码质量;
  • 层次清晰:将AST映射成中间代码表示,再映射成目标代码的工作分层进行,使编译算法更加清晰 ;

代码生成 (Code Generation)

在实际的代码处理过程中,可能会递归的分析(recursive)我们最终生成的AST,然后对于每种type都有个对应的函数处理,当然,这可能是最简单的做法。总之,我们的目标代码会在这一步输出,对于我们的目标语言,它就是HTML了。

完整链路

input => tokenizer => tokens; // 词法分析
tokens => parser => ast; // 语法分析,生成AST
ast => transformer => newAst; // 中间层代码转换
newAst => generator => output; // 生成目标代码

npm package.json 的属性

在这里插入图片描述

npm包版本管理机制

npm包 中的模块版本都需要遵循 SemVer规范——由 Github 起草的一个具有指导意义的,统一的版本号表示规则。实际上就是 Semantic Version(语义化版本)的缩写。
SemVer规范官网: semver.org/

标准版本

SemVer规范的标准版本号采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须以数值来递增。

  • 主版本号(major):当你做了不兼容的API 修改;
  • 次版本号(minor):当你做了向下兼容的功能性新增;
  • 修订号(patch):当你做了向下兼容的问题修正;
    例如:1.9.1 -> 1.10.0 -> 1.11.0

先行版本

当某个版本改动比较大、并非稳定而且可能无法满足预期的兼容性需求时,你可能要先发布一个先行版本。
先行版本号可以加到“主版本号.次版本号.修订号”的后面,先加上一个连接号再加上一连串以句点分隔的标识符和版本编译信息。

  • 内部版本(alpha);
  • 公测版本(beta);
  • 正式版本的候选版本rc: 即 Release candiate;

发布版本

在修改 npm 包某些功能后通常需要发布一个新的版本,我们通常的做法是直接去修改 package.json 到指定版本。如果操作失误,很容易造成版本号混乱,我们可以借助符合 Semver 规范的命令来完成这一操作:

  • npm version patch : 升级修订版本号
  • npm version minor : 升级次版本号
  • npm version major : 升级主版本号

前端模块化规范

CommonJS

Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

  • 所有代码都运行在模块作用域,不会污染全局作用域;
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存;
  • 模块加载的顺序,按照其在代码中出现的顺序,可以动态加载,代码发生在运行时;
  • 导入的值是拷贝的,可以修改拷贝值,不会引起变量污染。

基本语法

  • 暴露模块:module.exports = value或exports.xxx = value
  • 引入模块:require(xxx),如果是第三方模块,xxx为模块名;如果是自定义模块,xxx为模块文件路径

此处我们有个疑问:CommonJS暴露的模块到底是什么? CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

模块的加载机制

CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与ES6模块化有重大差异(下文会介绍),请看下面这个例子:

javascript">// lib.js
var counter = 3;
function incCounter() {counter++;
}
module.exports = {counter: counter,incCounter: incCounter,
};

上面代码输出内部变量counter和改写这个变量的内部方法incCounter。

javascript">// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;console.log(counter);  // 3
incCounter();
console.log(counter); // 3

上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到counter了。这是因为counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

AMD(Asynchronous Module Definition)

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。此外AMD规范比CommonJS规范在浏览器端实现要来着早。
定义暴露模块:

javascript">//定义没有依赖的模块
define(function(){return 模块
})//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){return 模块
})

引入使用模块:

javascript">require(['module1', 'module2'], function(m1, m2){使用m1/m2
})

AMD实现

通过比较是否实用AMD,来说明使用AMD实际使用的效果。

未使用AMD规范
javascript">// dataService.js文件
(function (window) {let msg = 'www.xianzao.com'function getMsg() {return msg.toUpperCase()}window.dataService = {getMsg}
})(window)// alerter.js文件
(function (window, dataService) {let name = 'xianzao'function showMsg() {alert(dataService.getMsg() + ', ' + name)}window.alerter = {showMsg}
})(window, dataService)// main.js文件
(function (alerter) {alerter.showMsg()
})(alerter)// index.html文件
<div><h1>Modular Demo 1: 未使用AMD(require.js)</h1></div>
<script type="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
<script type="text/javascript" src="js/main.js"></script>

最后得到如下结果:

javascript">'WWW.XIANZAO.COM', 'xianzao'

这种方式缺点很明显:首先会发送多个请求,其次引入的js文件顺序不能搞错,否则会报错

使用require.js

RequireJS是一个工具库,主要用于客户端的模块管理。它的模块管理遵守AMD规范,RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。

接下来介绍AMD规范在浏览器实现的步骤:

  1. 下载require.js, 并引入
  • 官网: http://www.requirejs.cn/
  • github : https://github.com/requirejs/requirejs
    然后将require.js导入项目: js/libs/require.js
  1. 创建项目结构
javascript">|-js|-libs|-require.js|-modules|-alerter.js|-dataService.js|-main.js
|-index.html
  1. 定义require.js的模块代码
javascript">// dataService.js文件
// 定义没有依赖的模块
define(function() {let msg = 'www.xianzao.com'function getMsg() {return msg.toUpperCase()}return { getMsg } // 暴露模块
})//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {let name = 'xianzao'function showMsg() {alert(dataService.getMsg() + ', ' + name)}// 暴露模块return { showMsg }
})// main.js文件
(function() {require.config({baseUrl: 'js/', //基本路径 出发点在根目录下paths: {//映射: 模块标识名: 路径alerter: './modules/alerter', //此处不能写成alerter.js,会报错dataService: './modules/dataService'}})require(['alerter'], function(alerter) {alerter.showMsg()})
})()// index.html文件
<!DOCTYPE html>
<html><head><title>Modular Demo</title></head><body><!-- 引入require.js并指定js主文件的入口 --><script data-main="js/main" src="js/libs/require.js"></script></body>
</html>
  1. 页面引入require.js模块
    在index.html引入
javascript"><script data-main="js/main" src="js/libs/require.js"></script>
  1. 引入第三方库
javascript">// alerter.js文件
define(['dataService', 'jquery'], function(dataService, $) {let name = 'Tom'function showMsg() {alert(dataService.getMsg() + ', ' + name)}$('body').css('background', 'green')// 暴露模块return { showMsg }
})
javascript">// main.js文件
(function() {require.config({baseUrl: 'js/', //基本路径 出发点在根目录下paths: {//自定义模块alerter: './modules/alerter', //此处不能写成alerter.js,会报错dataService: './modules/dataService',// 第三方库模块jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错}})require(['alerter'], function(alerter) {alerter.showMsg()})
})()

上例是在alerter.js文件中引入jQuery第三方库,main.js文件也要有相应的路径配置。

总结

通过两者的比较,可以得出AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。

javascript">//定义没有依赖的模块
define(function(require, exports, module){exports.xxx = valuemodule.exports = value
})
javascript">//定义有依赖的模块
define(function(require, exports, module){//引入依赖模块(同步)var module2 = require('./module2')//引入依赖模块(异步)require.async('./module3', function (m3) {})//暴露模块exports.xxx = value
})
javascript">// 引入使用的模块
define(function (require) {var m1 = require('./module1')var m4 = require('./module4')m1.show()m4.show()
})

CMD(Common Module Definition)

CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

CMD实现

  1. 下载sea.js, 并引入
  • 官网: http://seajs.org/
  • github : https://github.com/seajs/seajs
    然后将sea.js导入项目:js/libs/sea.js
  1. 创建项目结构
javascript">|-js|-libs|-sea.js|-modules|-module1.js|-module2.js|-module3.js|-module4.js|-main.js
|-index.html
  1. 定义sea.js的模块代码
javascript">// module1.js文件
define(function (require, exports, module) {//内部变量数据var data = 'xianzao.com'//内部函数function show() {console.log('module1 show() ' + data)}//向外暴露exports.show = show
})// module2.js文件
define(function (require, exports, module) {module.exports = {msg: 'I am xianzao'}
})// module3.js文件
define(function(require, exports, module) {const API_KEY = 'abc123'exports.API_KEY = API_KEY
})// module4.js文件
define(function (require, exports, module) {//引入依赖模块(同步)var module2 = require('./module2')function show() {console.log('module4 show() ' + module2.msg)}exports.show = show//引入依赖模块(异步)require.async('./module3', function (m3) {console.log('异步引入依赖模块3  ' + m3.API_KEY)})
})// main.js文件
define(function (require) {var m1 = require('./module1')var m4 = require('./module4')m1.show()m4.show()
})
  1. 在index.html中引入
javascript"><script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">seajs.use('./js/modules/main')
</script>

最后得到结果如下:

javascript">module1 show(), xianzao
module4 show() I am xianzao
异步引入依赖模块3 abc123

AMD与CMD区别

javascript">// AMD
define(['Module1'], function (module1) {var result1 = module1.exec();return {result1: result1,}
});// CMD
define(function (requie, exports, module) {//依赖就近书写var module1 = require('Module1');var result1 = module1.exec();module.exports = {result1: result1,}
});

从上面的代码比较中我们可以得出AMD规范和CMD规范的区别

  1. 对依赖的处理:
  • AMD推崇依赖前置,即通过依赖数组的方式提前声明当前模块的依赖;
  • CMD推崇依赖就近,在编程需要用到的时候通过调用require方法动态引入;
  1. 在本模块的对外输出:
  • AMD推崇通过返回值的方式对外输出;
  • CMD推崇通过给module.exports赋值的方式对外输出;
特性CommonJSAMDCMD
加载方式同步加载异步加载异步加载(延迟加载依赖)
模块导出module.exports / exportsdefinedefine
依赖管理无需显式声明依赖必须声明依赖延迟声明依赖,按需加载依赖
执行时机模块首次加载时立即执行模块加载时执行模块执行时,依赖被延迟加载
使用环境主要用于 Node.js 环境主要用于浏览器端主要用于浏览器端
模块缓存有(第一次加载后缓存)无(每次加载模块都重新执行)有(延迟加载,模块执行时缓存)

http://www.ppmy.cn/server/143270.html

相关文章

Ansible一键部署Kubernetes集群

一、环境准备 主机 ip地址 角色 k8s-master 192.168.252.141 master k8s-node1 192.168.252.142 node k8s-node2 192.168.252.143 node 二、实战 Ansible部署 主节点安装Ansible yum -y install epel-release yum -y install ansible ansible --version 开启记…

【Patroni官方文档】HA multi datacenter(多数据中心)

在多数据中心部署的PostgreSQL集群的高可用性基于复制技术,这种复制可以是同步的或异步的(即复制模式)。 在这两种情况下,明确以下概念都非常重要: 只有当PostgreSQL拥有并可以更新领导键时,它才能作为主节点或备用领导节点运行。 您应该运行奇数个etcd、ZooKeeper或C…

hadoop分布式文件系统常用命令

前言 搭建完hadoop后&#xff0c;会生成一个hdfs的分布式文件系统。HDFS是一个逻辑上的文件系统&#xff0c;它存储在Hadoop集群的多个节点上&#xff0c;而不是单个机器的本地磁盘上。 常用命令 # 创建目录&#xff0c;-p参数可以创建所有必需的父目录&#xff08;按照层级…

Spring Cloud Gateway(分发请求)

Spring Cloud Gateway 的过滤器和 Spring MVC 的拦截器的区别 过滤器用于整个微服务系统的网关层控制&#xff0c;拦截器则用于单个微服务内部的控制层请求处理。 1. 作用范围 Spring Cloud Gateway 过滤器&#xff1a;过滤器的作用范围是在网关层&#xff0c;主要在请求进入后…

Redis做分布式锁

&#xff08;一&#xff09;为什么要有分布式锁以及本质 在一个分布式的系统中&#xff0c;会涉及到多个客户端访问同一个公共资源的问题&#xff0c;这时候我们就需要通过锁来做互斥控制&#xff0c;来避免类似于线程安全的问题 因为我们学过的sychronized只能对线程加锁&…

Java 核心技术卷 I 学习记录八

Java 核心技术卷 I 学习记录八 六、接口、lambda表达式与内部类3、lambada表达式1、为什么引入lambda表达式2、lambda表达式的语法3、函数式接口4、方法引用5、构造器引用6、变量作用域7、处理lambda表达式8、再谈Comparator 六、接口、lambda表达式与内部类 3、lambada表达式…

【安卓恶意软件检测-论文】DroidEvoler:自我进化的 Android 恶意软件检测系统

DroidEvolver&#xff1a;自我进化的 Android 恶意软件检测系统 摘要 鉴于Android框架的频繁变化和Android恶意软件的不断演变&#xff0c;随着时间的推移以有效且可扩展的方式检测恶意软件具有挑战性。为了应对这一挑战&#xff0c;我们提出了DroidEvolver&#xff0c;这是一…

UML中类图的介绍与使用

类图 UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&#xff09;中的类图&#xff08;Class Diagram&#xff09;是一种静态结构图&#xff0c;它用于展示系统中的类&#xff08;class&#xff09;、接口&#xff08;interface&#xff09;、协作&#x…