JavaScript的疑问
哪些环境可以执行JavaScript代码?
Java Script vs Nodejs vs V8
JavaScript 和 TypeScript 是什么关系?
JavaScript 和 ECMAScript是什么关系?
ES5 和ES6 是什么关系?
ECMAScript2015 和 ES6 是什么关系?
export,exports, module.export, require 为什么在乱用?有什么区别?
是什么是模块开发?CommonJs 和 AMD 和ES6 又是什么关系?
Angular ==> TypeScript
Vue ==> JavaScript(ES6)
OpenUI5 == >JavaScript(JQuery)
JavaScript 的发展历史
JavaScript的运行行环境
浏览器可以执行JavaScript 代码,本质上是由于浏览器都带有JavaScript 解释引擎
ie老版本的JScript,ie9以后的Chakra,mozilla的SpiderMonkey,chrome的v8,Safari的Nitro,KDE Konqueror的KJS,js之父用javascript写的Narcissus,java语言写的Rhino,等等,都是js引擎,都能独立在浏览器之外运行。
Node.js不是js引擎,其使用的是v8。
webkit是开源的浏览器引擎,包含了网页排版引擎KHTML和js解释引擎KJS,很多浏览器的核心包括safari,chrome都是基于它发展起来的
JavaScript 是一门编程语言,需要Js引擎才能执行
Nodejs = V8 + nodejs API(内置模块),主要用于服务器端。 内置模块包含文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL)等
V8 是google开源的js 引擎,用于执行JavaScript
JavaScript vs TypeScript vs ECMAScript vs ES5 vs ES6
ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言(个人认为就是一种标准规范)
JavaScript: ECMAScript 标准的各种实现的最常用称呼。这个术语并不局限于某个特定版本的 ECMAScript 规范,并且可能被用于任何不同程度的任意版本的 ECMAScript 的实现。
ECMAScript 5 (ES5):ECMAScript 的第五版修订,于 2009 年完成标准化。这个规范在所有现代浏览器中都相当完全的实现了。
ECMAScript 6 (ES6) / ECMAScript 2015 (ES2015):ECMAScript 的第六版修订,于 2015 年完成标准化。这个标准被部分实现于大部分现代浏览器。
TypeScript: 是JavaScript 的一个超集(TypeScript 并不是一个完全新的语言, 它是 JavaScript 的超集,为 JavaScript 的生态增加了类型机制,并最终将代码编译为纯粹的 JavaScript 代码。),而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作。TypeScript是为大型应用之开发而设计,而编译时它 产生 JavaScript 以确保兼容性
本质上,谈论一门语言首先要讨论的是语言的语法,其次是能理解语法并执行的引擎。从语言本身,我们谈到ES5,ES6其本身是语言的标准,包含了语言的语法和特点,由于历史原因我们把它叫做JavaScript。
TypeScript 本质上可以理解为一门新的语言,它的语法包含了ES5 和ES6
按照ES5的语法写的代码(JavaScript),可以运行在现在流行的浏览器上
按照ES6的语法写的代码(JavaScript),不一定能被现在的浏览器理解并执行,原因就在浏览器内置JS引擎不一定理解ES6的语法。Babel可以将ES6语法代码编译成ES5语法代码
按照TypeScript的语法的代码,不能在浏览器上理解执行,原因就是浏览器内置的JS 引擎不理解TypeScript的语法。按照TypeScript写的代码需要使用TypeScript 编译成ES5/ES6语法的代码,才能在浏览器执行
http://kangax.github.io/compat-table/es6/
JavaScript模块化
JavaScript (ES5)本身是不具有模块化,于是社区设计出了CommonJs Moduels 和 Asynchronous Module Definition(AMD)。 CommonJS和AMD都是JavaScript模块化规范,在ES6之前,Node主要遵循CommonJS,而AMD则主要运用在浏览器端,比如requirejs。
node中导出模块接口就是用module.exports导出模块
module.exports.a = function() {}; module.exports.b = 'xxx';
或者 module.exports = { a : function() {}, b : 'xxx' }
requirejs提供了一个exports变量作为module.exports的别名
ES6中模块使用import和export
在ES6之前,要使用一个模块,必须使用require函数将一个模块引入,但ES6并没有采用这种模块化方案,在ES6中使用import指令引入一个模块或模块中的部分接口,并没有将require写入标准,这也就是说require对于ES6代码而言,只是一个普通函数。
同理,在ES6标准中,导出模块的接口也只能使用export指令,而非exports对象,这也同样意味着module.exports只是node,requirejs等模块化库的自定义变量,而非ES标准接口。
ES6既然是标准,那么就是所有引擎应该去实现的,node和浏览器未来都会直接支持这种模块加载方式,require完成历史使命回家自己玩儿。而且作为node或浏览器,同时可以利用import提供自己的API,比如手机端提供基于网络的定位API,这都不用SDK了,直接内置在客户端内部,import一下就可以了。
不过现在import导入模块还并不是全部环境都支持,使用babel可以让node支持ES6,但在浏览器端,则毫无办法,可能还得暂时依赖require。但是非常不好的消息是,require不是ES6标准,这也就是说如果将来浏览器支持import后,你想用它,就必须升级代码,而不能直接被兼容。
https://2ality.com/2014/09/es6-modules-final.html
https://www.tangshuang.net/2882.html
Angular
Angular 是Google开源的基于JavaScript的前端框架。。。
为什么 Angular 需要编译
Angular 是基于 TypeScript,编译打包的时候会用 tsc 将 TypeScript 编译成 es5 文件,这样在浏览器 JavaScript Virtual Machines (VM) 可以直接运行 es5 代码。那么 Angular 为什么还需要编译呢?在 Angular 中除了 TypeScript 之外,还有 HTML 模板文件,在这些模板文件里有 Angular 自带的组件、指令、管道等等,为了让浏览器可以识别和运行这些东西,那么就需要用 Angular 编译器把这些编译成浏览器可识别和运行的 es5 代码。
Angular 编译机制:JiT vs AoT
Angular 的编译器有两种执行机制:JiT 和 AoT,ng build
和ng serve
是 JiT 的编译方式,ng build --prod
ng build --aot
或者ng serve --aot
是 AoT 的编译方式。一句话概括两者的区别:Angular 编译器(ngc)执行的时机不一样,JiT 是浏览器在渲染页面的时候先把 Angular 编译器下载到本地,然后把 HTML 模板编译成浏览器可识别运行的 es5 代码;AoT 是项目在打包的时候就把 HTML 模板编译成浏览器可识别运行的 es5 代码,在浏览器渲染时不需要下载 Angular 编译器也不需要编译,直接运行这些代码就可以了
运行 ng build (JiT)
在dist目录找到
vendor-es5.js,vendor-es2015.js。我们可以通过工具【source-map-explorer】来看看 vendor 文件里到底有什么。
//先安装 source-map-explorer
npm install -g source-map-explorer
// 运行如下命令,查看 vendor-es5.js
npx source-map-explorer dist/angular-performance/vendor-es5.js
我们可以看到因为 JiT 是在页面渲染的时候编译模板文件,所以需要打包 compiler 源码,compiler 源码就有 1.32M
运行 ng build:prod (AoT)
默认ng build:prod
不会产生 source map 文件,由于我们需要用 source-map-explorer 工具分析 bundle 文件结构,所以需要在运行ng build:prod
也产生 source map 文件。需要改动 angular.json 文件配置,改动如下:
//angular.jso
"architect": {
"build": {
......
......
"configurations": {
"production": {
......
......
"sourceMap": true, // 把sourceMap设置为True
......
......
}
}
}
}
// 运行如下命令,查看main-es5.241e3ea4617330a70446.js
npx source-map-explorer dist/angular-performance/main-es5.241e3ea4617330a70446.js
main 的文件结构如下:可以看到AoT打包的时候,不需要打包compiler源码
总结一下 JiT 和 AoT 的主要区别:
- 打包之后的文件大小不一样,最大的区别是 JiT 需要把 compiler 源码打包进 bundle 文件,而 AoT 不需要。
- bundle 文件的内容也有很大区别:JiT 把 HTML 模板直接内联进 bundle 文件不做任何处理,AoT 会把 HTML 模板文件编译 es5 文件。
- 编译执行的时机不一样,JiT 是在浏览器里执行,需要内联的 HTML 文件编译成 es5 代码;AoT 是在编译打包的时候就把 HTML 文件编译成 es5 代码。
AoT的工作原理(ngc)
AoT 编译实际分了两个步骤:
- 第一步:用 ngc 把模板和 component 编译成 es6(或者是 TypeScript 代码,可以在 tsconfig.json 里配置)。
- 第二步:用 tsc 把这些 TypeScript 编译成 es5。
Vue
Vue 是一套用于构建用户界面的渐进式框架
什么是vue-cli
vue-cli是vue.js的脚手架,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。
主要作用:目录结构、本地调试、代码部署、热加载、单元测试
vue-cli
是基于nodejs+webpack
封装的命令行工具
用vue-cli执行build,实际上是webpack做的。原本需要自己配置webpack的相关配置,被cli简化了。并且按照vue的用户习惯整理了一套构建和目录规范。
Webpack 是什么?
webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler),分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass,TypeScript,vue等),并将其转换和打包为合适的格式供浏览器使用。
但是用vue-cli建立出来的项目根目录并没有webpack.config.js文件,也找不到相关引用。
其实vue-cli中的webpack有个默认配置,如下访问:
以一个文件的方式使用解析好的配置:
有些外部工具可能需要通过一个文件访问解析好的 webpack 配置,比如那些需要提供 webpack 配置路径的 IDE 或 CLI。在这种情况下你可以使用如下路径:
<projectRoot>/node_modules/@vue/cli-service/webpack.config.js
该文件会动态解析并输出vue-cli-service
命令中使用的相同的 webpack 配置,包括那些来自插件甚至是你自定义的配置。
在Vue中调整 webpack 配置最简单的方式就是在 vue.config.js
中的 configureWebpack
选项提供一个对象:
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
new MyAwesomeWebpackPlugin()
]
}
}
该对象将会被 webpack-merge 合并入最终的 webpack 配置。
实际上Vue CLI帮我们做了什么?
- 将浏览器不能识别的代码转换为浏览器可识别的代码,解释less,sass,vue等其他浏览器不能识别的文件
- 一些老版的浏览器可能不支持ES6,这个babel的作用就是能够将ES6转换ES5,达到兼容的目的
- 自动打开浏览器,同时在修改源代码的时候页面实现自动刷新
OpenUI5
OpenUI5 是 SAPUI5 的开源版本, 是基于JQuery 实现的MVC 前端框架
OpenUI5不是ES6规范,对于模块的管理也没有采用CommonJS 或者AMD
OpenUI5框架对module(模块)提供内在的支持。在openui5中,模块指的是可以在浏览器(browser)中加载和执行的JavaScript文件。如何将代码组织成不同的模块,并没有一个统一的规则,取决于开发人员的想法。当然,一般来说,同一个模块的内容应该有着共同的主题,而不是随意将代码分割成不同的文件。
OpenUI5如何加载模块
jQuery.sap.declare()
申明一个模块,作用是确保模块存在jQuery.sap.require()
同步加载代码的依赖模块sap.ui.define()
定义JavaScript模块,异步加载依赖(dependancies)模块,sap.ui.define()
定义的模块具有全局命名空间(global namespace)。sap.ui.require()
异步加载依赖的模块,不具有全局命名空间。
jQuery.sap.declare()
语法:jQuery.sap.declare(sModuleName, bCreateNamespace*?*)
申明一个模块,以确保模块存在。这个语句必须出现在模块代码(也就是代码文件)的第一句。
jQuery.sap.declare函数源码在jquery.sap.global.js,执行时可在sap-ui-core.js中找到。
jQuery.sap.delcare首先通过ui5ToRJS将javascript类名转换为js文件名,例如sap.m.Dialog转换为sap/m/Dialog.js,然后执行declareModule函数。
// A module declaration, ensures that sap.ui.sample exists jQuery.sap.declare("sap.ui.sample.MyClass");
// now it is safe to use that namespace object and assign a new member 'MyClass' to it
// Note that jQuery.sap.declare does not create the MyClass object.
// So the developer can decide whether it should be a function, an object,
// or an instance of a specific type
sap.ui.sample.MyClass = { key1 : 'value1' };
// the following line guarantees that <code>sap.ui.sample.subspace</code> is a valid object
sap.ui.namespace("sap.ui.sample.subspace");
// now one can use this namespace as well
sap.ui.sample.subspace.member1 = 42;
sap.ui.sample.subspace.member2 = 3.141;
本示例代码说明如何使用jQuery.sap.declare
和jQuery.sap.require
创建controller模块。
jQuery.sap.declare("example.MyController");
jQuery.sap.require("sap.ui.core.mvc.Controller");
"use strict";
sap.ui.core.mvc.Controller.extend("example.MyController", {
onInit: function () {
}
});
jQuery.sap.require()
语法:jQuery.sap.require(vModuleName)
确保当前代码继续之前,所指定的模块被加载和执行。如果所需要的模块没有被加载,将会被同步加载和执行,如果已经加载,就忽略。
jQuery.sap.require首先通过ui5ToRJS将javascript类名转换为js文件名,例如sap.m.Dialog转换为sap/m/Dialog.js,然后执行requireModule函数
sap.ui.define()
语法:sap.ui.define(sModuleName*?*, aDependencies*?*, vFactory, bExport*?*)
定义module,比如controller并指定其依赖的模块,所定义的module具有全局命名空间(global namespace)。global namespace的意思是module中的对象在整个Application中可见。函数有四个参数,一般只需要参数2(aDependencies)和参数3(vFactory)。参数2指定依赖的模块,参数3定义一个工厂函数。sap.ui.define()
函数被广泛用于定义controller。一般建议省略参数1(sModuleName),也就是不硬编码模块名,这样对模块的访问较方便。
sap.ui.require()
用于解决(resolve)代码的依赖模块。和jQuery.sap.require()
不同,sap.ui.require()
是异步加载。
使用sap.ui.define()定义controller
刚才介绍了sap.ui.define()
的语法,下面介绍如何使用sap.ui.define()
定义controller。我们以上一篇的代码为例,对controller的代码进行改写。
改写之前的代码:
sap.ui.controller("suppliers.supplieroverview", {
onInit: function() {
var oModel = sap.ui.model.json.JSONModel('models/suppliers.json');
this.getView().setModel(oModel);
}
});
改写之后的代码:
sap.ui.define(["sap/ui/core/mvc/Controller"],
function(Controller){
"use strict";
return Controller.extend("suppliers.supplieroverview", {
onInit: function() {
var oModel = sap.ui.model.json.JSONModel();
oModel.loadData('models/suppliers.json');
this.getView().setModel(oModel);
}
});
}
);
这个代码的模式是固定的,我们一般只需要基于这个代码片段作三个方面的变更:
- 增加依赖的模块到数组中:
sap.ui.define(["sap/ui/core/mvc/Controller", "<other_modules>"],...);
- 变更
Controler.extend()
第一个参数的模块名 - 在
Controler.extend()
的第二个参数中编写自己的函数,函数之间用逗号分开。
OpenUI5的运行环境是ui5-tooling进行编译和打包,没有使用webpack,估计使用的grunt
gulp和grunt是流管理工具,通过一个个task配置执行用户需要的功能,如格式检验,代码压缩等,值得一提的是,经过这两者处理的代码只是局部变量名被替换简化,整体并没有发生改变,还是你的代码。
webpack则进行了更彻底的打包处理,更加偏向对模块语法规则进行转换。主要任务是突破浏览器的鸿沟,将原本浏览器不能识别的规范和各种各样的静态文件进行分析,压缩,合并,打包,最后生成浏览器支持的代码,因此,webapck打包过后的代码已经不是你写的代码了,或许你再去看,已经看不懂啦