原生js读取properties_纯原生Javascript之OFD电子发票文件渲染

汇编语言的优点是能用来写任何东西,汇编语言的缺点是你不会用它来写任何东西

能被web平台实现的最终一定会被实现在web平台上

所谓的Atwood's Law是这样的:

Any application that can be written in JavaScript, will eventually be written in JavaScript.

这句话的出处是Jeff Atwood在2007年写的博客《The Principle of Least Power》,Jeff Atwood是Stack Overflow的联合创始人。

如何评价Atwood's Law呢?

一方面,他的预测在某种程度上确实对了,这10多年来,JS的发展确实非常惊人,现在的我们可以用JS来做各种事情,比如写网页、写APP、写小程序、写APP、写后端,写IOT;JS的生态系统也异常繁荣与开放,GitHub与NPM有着各种工具与框架;从2015年开始,ECMAScript每年会发布一个新的版本,增加新的特性;Node.js的更新也非常稳定,基本上按照预定的日期发布新的版本,优化性能,增加特性...我们无法在10多年前对JS的发展有这样的判断,但是从目前的情况来看,JS的前景还是非常好的。

另一方面,JS真的无所不能吗?它可以替代其他编程语言吗?作为JS开发者,我觉得这显然是不可能的。每种编程语言都有自己擅长的事情,或者说每种语言都有自己的一亩三分地,比如Python在AI领域如日中天,现在风头貌似盖过了其他所有语言;Java在后端领域依然是霸主,使用Node.js写后端的依然寥寥无几,这一点从招聘网站就能看出来;Golang在底层基础架构领域也非常受欢迎,业界炙手可热的工具比如Docker与Kuberntes用的都是Go....用JS替代Python/Java/Golang等语言在各自领域的作用,基本上是不可能的,也没有必要,这都用不着去分析技术细节的优劣,人家用得好好的,咱有必要去重复造轮子吗?

------------------------------------------------------------------------------

上一篇所谓终结篇有个小缺憾,是引入了vue.js和element-ui两个外部源src

90f7d4ce37338fc414a53f972c1c2365.png

今天学习了一下ES6的知识,把这两个依赖也去掉了,实现了用纯原生Javascript ES6规范的脚本来渲染电子发票文件。

HTML 部分全部用

标签布局,共约120行代码

Javascript部分无任何外部引用,共约24000行代码

样式表



HTML 部分:

loading.gif


loading.gif


Javascript部分:

loading.gif


样式表

loading.gif



部署在Tomcat上的web单页应用大小809K


loading.gif


效果:


loading.gif


loading.gif



------------------------------------------------------------------------------


ECMAScript 发展史
  • 1998 年 ECMAScript 2.0 发布

  • 1999 年 ECMAScript 3.0 发布

  • 2007 年 提出了 ECMAScript 4.0 因为太激进被各大浏览器厂商及互联网公司联合抵制,最终没有发布。

  • 2008 年 放弃4.0计划,改为3.1优化计划,只做了小部分更新,我们现在写的最早的版本大概就是 3.1 版本了。

  • 2009 年 发布了ES5

  • 2011 年 修正了ES5 发布了 ES5.1 这个标准是目前国标版本,也就是说所有的2011年之后的浏览器必须支持的版本。

  • 2015 年 经过 2013 年多次波折和延期发布,终于发布了ES6 版本。

  • 因为ES6版本发生了加大的变更,同时太多的想法也在慢慢计划和实施,所以ECMAScript 从2015年起, 每年都会更新一版。

  • ES2015 以后的版本我们通常称之为ES6 +



ES6的主要新特性:


声明变量的方法

ES5只有两种声明变量的方法:var命令和function命令。


ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。


ES6新增了const命令const声明一个只读的常量。一旦声明,常量的值就不能改变。


ES6除了添加letconst命令,另外两种声明变量的方法:import命令和class命令。所以,ES6一共有6种声明变量的方法。


Class语法

JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。

function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如C++和Java)差异很大,很容易让新学习这门语言的程序员感到困惑。


ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。

//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法。


Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。


import语法

ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。

历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。

// CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。

// ES6模块
import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。

由于ES6模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽JavaScript的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

除了静态加载带来的各种好处,ES6模块还有以下好处。

  • 不再需要UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。目前,通过各种工具库,其实已经做到了这一点。

  • 将来浏览器的新API就能用模块格式提供,不再必要做成全局变量或者navigator对象的属性。

  • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。

浏览器使用ES6模块的语法如下。

<script type="module" src="foo.js">script>



变量的解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

以前,为变量赋值,只能直接指定值。

var a = 1;
var b = 2;
var c = 3;

ES6允许写成下面这样。

var [a, b, c] = [1, 2, 3];

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。



ES6 异步操作和Async函数


异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本没法用,非卡死不可。

ES6诞生以前,异步编程的方法,大概有下面四种。

  • 回调函数

  • 事件监听

  • 发布/订阅

  • Promise 对象

ES6将JavaScript异步编程带入了一个全新的阶段,ES7的Async函数更是提出了异步编程的终极解决方案。


所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。

回调函数

JavaScript语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它的英语名字callback,直译过来就是"重新调用"。


Promise

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。

fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
});
});

不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就称为"回调函数噩梦"(callback hell)。

Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用Promise,连续读取多个文件......


Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。

那么,有没有更好的写法呢?......


  1.      ECMAScript 6 字符串的扩展

  2. ECMAScript 6 正则的扩展

  3. ECMAScript 6 数值的扩展

  4. ECMAScript 6 数组的扩展

  5. ECMAScript 6 函数的扩展

  6. ECMAScript 6 对象的扩展

  7. ECMAScript 6 Symbol

  8. ECMAScript 6 Proxy和Reflect

  9. ECMAScript 6 二进制数组

  10. ECMAScript 6 Set和Map数据结构

  11. ECMAScript 6 Iterator和for...of循环

  12. ECMAScript 6 Generator 函数

  13. ECMAScript 6 Promise对象

  14. ECMAScript 6 异步操作和Async函数

  15. ECMAScript 6 修饰器(Decorator)


ES6的编程风格:

块级作用域

(1)let取代var

ES6提出了两个新的声明变量的命令:letconst。其中,let完全可以取代var,因为两者语义相同,而且let没有副作用。

'use strict';

if (true) {
let x = 'hello';
}

for (let i = 0; i < 10; i++) {
console.log(i);
}

上面代码如果用var替代let,实际上就声明了两个全局变量,这显然不是本意。变量应该只在其声明的代码块内有效,var命令做不到这一点。

var命令存在变量提升效用,let命令没有这个问题。

'use strict';

if(true) {
console.log(x); // ReferenceError
let x = 'hello';
}

上面代码如果使用var替代letconsole.log那一行就不会报错,而是会输出undefined,因为变量声明提升到代码块的头部。这违反了变量先声明后使用的原则。

所以,建议不再使用var命令,而是使用let命令取代。

(2)全局常量和线程安全

letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。这符合函数式编程思想,有利于将来的分布式运算。

// bad
var a = 1, b = 2, c = 3;

// good
const a = 1;
const b = 2;
const c = 3;

// best
const [a, b, c] = [1, 2, 3];

const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。

所有的函数都应该设置为常量。

长远来看,JavaScript可能会有多线程的实现(比如Intel的River Trail那一类的项目),这时let表示的变量,只应出现在单线程运行的代码中,不能是多线程共享的,这样有利于保证线程安全。

字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

// bad
const a = "foobar";
const b = 'foo' + a + 'bar';

// acceptable
const c = `foobar`;

// good
const a = 'foobar';
const b = `foo${a}bar`;
const c = 'foobar';

解构赋值

使用数组成员对变量赋值时,优先使用解构赋值。

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

函数的参数如果是对象的成员,优先使用解构赋值。

// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}

// good
function getFullName(obj) {
const { firstName, lastName } = obj;
}

// best
function getFullName({ firstName, lastName }) {
}

如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。

// bad
function processInput(input) {
return [left, right, top, bottom];
}

// good
function processInput(input) {
return { left, right, top, bottom };
}

const { left, right } = processInput(input);

对象

单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。

// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};

// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};

对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。

// bad
const a = {};
a.x = 3;

// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });

// good
const a = { x: null };
a.x = 3;

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};

上面代码中,对象obj的最后一个属性名,需要计算得到。这时最好采用属性表达式,在新建obj的时候,将该属性与其他属性定义在一起。这样一来,所有属性就在一个地方定义了。

另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。

var ref = 'some value';

// bad
const atom = {
ref: ref,

value: 1,

addValue: function (value) {
return atom.value + value;
},
};

// good
const atom = {
ref,

value: 1,

addValue(value) {
return atom.value + value;
},
};

数组

使用扩展运算符(...)拷贝数组。

// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];

使用Array.from方法,将类似数组的对象转为数组。

const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);

函数

立即执行函数可以写成箭头函数的形式。

(() => {
console.log('Welcome to the Internet.');
})();

那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this。

// bad
[1, 2, 3].map(function (x) {
return x * x;
});

// good
[1, 2, 3].map((x) => {
return x * x;
});

// best
[1, 2, 3].map(x => x * x);

箭头函数取代Function.prototype.bind,不应再用self/_this/that绑定 this。

// bad
const self = this;
const boundMethod = function(...params) {
return method.apply(self, params);
}

// acceptable
const boundMethod = method.bind(this);

// best
const boundMethod = (...params) => method.apply(this, params);

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。

所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

// bad
function divide(a, b, option = false ) {
}

// good
function divide(a, b, { option = false } = {}) {
}

不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。

// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}

// good
function concatenateAll(...args) {
return args.join('');
}

使用默认值语法设置函数参数的默认值。

// bad
function handleThings(opts) {
opts = opts || {};
}

// good
function handleThings(opts = {}) {
// ...
}

Map结构

注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。

let map = new Map(arr);

for (let key of map.keys()) {
console.log(key);
}

for (let value of map.values()) {
console.log(value);
}

for (let item of map.entries()) {
console.log(item[0], item[1]);
}

Class

总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。

// bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}

// good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}

使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。

// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
}

// good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}

模块

首先,Module语法是JavaScript模块的标准写法,坚持使用这种写法。使用import取代require

// bad
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;

// good
import { func1, func2 } from 'moduleA';

使用export取代module.exports

// commonJS的写法
var React = require('react');

var Breadcrumbs = React.createClass({
render() {
return <nav />;
}});

module.exports = Breadcrumbs;
// ES6的写法
import React from 'react';

const Breadcrumbs = React.createClass({
render() {
return <nav />;
}});

export default Breadcrumbs

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,不要export default与普通的export同时使用。

不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。

// bad
import * as myObject './importModule';

// good
import myObject from './importModule';

如果模块默认输出一个函数,函数名的首字母应该小写。

function makeStyleGuide() {
}

export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写。

const StyleGuide = {
es6: {
}
};

export default StyleGuide;





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PDF(Portable Document Format,便携式文档格式)和OFD(Open Financial Document,开放金融文件格式)都是电子发票的常用格式之一。电子发票作为一种重要的财务凭证,具有便捷性和环保性等优点。因此,对于电子发票格式的解析是非常重要的。 PDF格式的电子发票可以通过Adobe Acrobat等软件进行打开和编辑,它有良好的兼容性和可靠性。同时,PDF格式还可以通过数字签名等方式进行安全加密和验证。 OFD格式的电子发票是一种由中国金融信息标准化技术委员会制定的标准格式,它具有大容量、高效率、安全可靠的优点。与PDF相比,OFD可以实现更多的业务流程和管理需求,得到了越来越广泛的应用。 在解析电子发票的过程中,需要注意格式的兼容性和安全性。同时,需要对发票的内容进行验证和解析,以确保其真实性和合法性。随着电子商务和数字化技术的不断发展,电子发票的标准化和规范化将成为未来的趋势,电子发票的解析和管理也将变得更加智能和高效。 ### 回答2: PDF电子发票OFD电子发票均属于电子发票的一种格式,它们都具有可存储、可传递等特点,已经得到广泛应用。因其具有数字化、自动化、便捷性及环保节能等优势,已成为现代电子商务的重要组成部分。 在解析PDF和OFD电子发票时,首先需要理解其文档结构和数据格式。PDF在文件头部和尾部均应当有%%EOF标识,以此表示其为PDF文件,其结构包括文档信息、对象结构、交叉引用表、加密和压缩信息等。OFD则采用XML文档格式,由多个层级的节点组成。OFD采用标准的XML语法,其中包含了发票的开具机构、收款方、明细、税费等信息,信息结构清晰且易于解析。 解析PDF和OFD电子发票时需要使用相应的解析工具,常见的有PDFBox、iText、OFD Reader等工具。这些工具可以帮助我们解析电子发票中的文本、图片、表格等元素,从而将其转化为可读性强、易于处理的数据格式。 综上所述,PDF和OFD电子发票解析是一项重要的技术,对于电子商务及电子发票的推广普及有着至关重要的作用。随着技术的进步和应用的推广,我们相信PDF和OFD电子发票的解析也会得到更好的发展和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值