ECMAScript 2020新增特性

ECMAScript 2020新特性

Jordan Harband提出的String.prototype.matchAll

String.prototype上的match()方法仅返回完全匹配,但是没有返回关于特定正则组的任意信息。感谢Jordan Harband关于String.prototype.matchAll的提案,可以返回比match()多很多的信息。返回的迭代器除了精确匹配外还给了我们访问所有的正则匹配捕获组。你还记得Gorkem Yakin和Daniel Ehrenberg添加到ECMAScript 2018的具名捕获组吗?matchAll()方法和此能很好的协调。通过下面例子来解释一下。

1
2
3
4
5
6
const text = "From 2019.01.29 to 2019.01.30";
const regexp = /(?<year>\d{4}).(?<month>\d{2}).(?<day>\d{2})/gu;
const results = text.match(regexp);

console.log(results);
// [ '2019.01.29', '2019.01.30' ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const text = "From 2019.01.29 to 2019.01.30";
const regexp = /(?<year>\d{4}).(?<month>\d{2}).(?<day>\d{2})/gu;
const results = Array.from(text.matchAll(regexp));

console.log(results);
// [
// [
// '2019.01.29',
// '2019',
// '01',
// '29',
// index: 5,
// input: 'From 2019.01.29 to 2019.01.30',
// groups: [Object: null prototype] { year: '2019', month: '01', day: '29' }
// ],
// [
// '2019.01.30',
// '2019',
// '01',
// '30',
// index: 19,
// input: 'From 2019.01.29 to 2019.01.30',
// groups: [Object: null prototype] { year: '2019', month: '01', day: '30' }
// ]
// ]

Domenic Denicola提出的import()

不同于ECMAScript 2015中介绍的静态模块,Domenic Denicola提案的动态导入可以实现按需加载。这个类似函数的格式(不是继承自Function .prototype)返回一个很强大的promise。使用场景比如: 按需导入,在一个脚本中计算模块名并加载执行变得可能。

1
2
3
4
5
const modulePage = 'page.js';
import(modulePage)
.then((module) => {
module.default();
});
1
2
3
4
5
(async () => {
const helpersModule = 'helpers.js';
const module = await import(helpersModule)
const total = module.sum(2, 2);
})();

Daniel Ehrenberg提出的BigInt-任意精度整数

感谢Daniel Ehrenberg, Number.MAX_SAFE_INTEGER不再是JavaScript中的一个限制。BigInt是一个能表示任意精度整数的新基础类型。你可以通过使用BigInt方法或者在一个数字后添加n后缀来把一个数字转换为一个新的bigint类型。

1
2
3
4
5
6
7
8
Number.MAX_SAFE_INTERGER
// 9007199254740991

Number.MAX_SAFE_INTEGER + 10 -10
// 9007199254740990 👎

BigInt(Number.MAX_SAFE_INTEGER) + 10n -10n
// 9007199254740991n 👍

Jason Williams, Robert Pamely and Mathias Bynens提出的Promise.allSettled

自从ECMAScript 2015以来,JavaScript仅支持两种promise组合: Promise.all()Promise.race()。感谢Jason Williams, Robert Pamely and Mathias Bynens,现在我们可以使用Promise.allSettled()。用这个方法来处理所有promise都解决时的场景(不管成功或失败)。看看下面的例子,并没有使用catch捕获异常!

1
2
3
4
5
Promise.allSettled([
fetch("https://api.github.com/users/pawelgrzybek").then(data => data.json()),
fetch("https://api.github.com/users/danjordan").then(data => data.json())
])
.then(result => console.log(`All profile settled`));

还有Promise.any()有潜力很快进入ECMAScript规范中,在文章“Promise组合解释”中介绍了相关内容。

Jordan Harband提出的globalThis

那么在JavaScript中什么是全局的this?是在浏览器中的window,在worker中的self,在Nodejs中的global或者其他… 这种混乱结束了!感谢Jordan Harband,我们现在可以使用globalThis关键字了。

Kevin Gibbons提出的for-in机制

ECMAScript遗留了一个关于for-in循环顺序的详细描述。感谢Kevin Gibbons所付出的努力,为for-in机制定义了一系列规则。(原文: Thanks to Kevin Gibbons who finally put some TLC and defined a set in stone set of rules for for-in mechanics.)

Gabriel Isenberg, Claude Pache and Dustin Savery提出的optional chaining

读取层次很深的对象属性时通常是容易出错并且对应代码也不易阅读。感谢Gabriel Isenberg, Claude Pache and Dustin Savery,这件事情现在变得简单了。如果你是一个TypeScript用户,那么你不会发现什么新的特性,因为在3.7版本中TypeScript已经实现了这个特性。喜欢!

1
2
3
4
5
// 之前
const title = data && data.article && data.article.title

// 现在
const title = data?.article?.title

Gabriel Isenberg 提出的空值联合

空值联合添加了一个新的短路原则操作符来处理默认值。Gabriel Isenberg做了很棒的工作。这个特性结合optional chanining特性使用。不同于||操作符,空值联合操作符??仅在左边的值为严格的nullundefined时起左右。

1
2
3
4
5
"" || "default value"
// default value

"" ?? "default value"
// ""
1
const title = data?.article?.title ?? "What's new in ECMAScript 2020"

Domenic Denicola提出的import.meta

Domenic Denicola提出的import.meta提案添加一个host相关的元数据对象到当前执行的模块中。

1
2
console.log(import.meta.url)
// file:///Users/pawelgrzybek/main.js

EXPORT * AS NS FROM “MOD”

这是一个添加到规范中的有用特性,可以让开发者导出其他模块命名空间下的对象到一个新的名称下。

1
export * as ns from "mod"

参考

(深入JavaScript系列)new背后的原理及实现

面试中遇到关于JavaScript中new关键字背后的实现原理,了解大概的原理,但是表达出来不是很清楚,表示掌握得不够完全,这里查了一些资料,做一下整理。

原理

例如我们做了new的调用操作,

1
new ConstructorFunction(arg1, arg2);

new操作

背后实际上发生了这些步骤:

  1. 创建一个新的空对象,对象类型为简单的object
  2. 设置这个空对象的实例原型(内部的、不可访问的[[prototype]]属性,部分浏览器可通过__proto__进行访问,ES5开始可通过Object.getPrototypeOf(obj)取得)为构造函数的prototype属性(每个函数对象都拥有一个prototype属性)
  3. this变量指向这个新创建的对象
  4. 以这个新创建的对象为上下文执行构造函数
  5. 如果构造函数有返回非空的对象,则返回该对象,否则返回第一步中创建的对象。

原型的几个概念

这里涉及到几个概念:

  1. 构造函数,配合使用new关键字的函数可称为构造函数
  2. 实例原型对象,在Ecma标准中,通过[[prototype]]表示,在部分浏览器中使用__proto__(非标准的,不建议使用)来表示,ES5开始可使用Object.getPrototypeOf()读取,ES6开始可使用Object.setPrototypeOf()方法进行设置(仅支持完全替换对象或者设为null)
  3. 原型对象,构造函数的prototype属性

比较难理解的是[[prototype]]这个属性,每个对象都拥有一个内部的[[prototype]]属性。这个对象是创建对象的时候设置的,创建包括new、通过Object.create()或者用文本字面量,并且只能通过Object.getPrototypeOf()Object.setPrototypeOf()方法进行操作。

原型链和继承

说明

一旦通过new操作实例化一个对象后,如果这个实例上查找某个属性并不存在,脚本会通过[[prototype]]对象向上一级继续查找,也就是通过原型链的方式进行往上查找。这种方式和在传统的类继承方式是类似的,在JavaScript中通过原型链的形式来继承父类的属性和方法。

函数中,除了拥有隐藏的[[prototype]]属性,还有一个prototype属性,这个属性可以访问、修改和添加希望给实例继承的属性和方法。

原型链实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObjMaker = function() { this.a = 'first'; }
// ObjMaker是一个普通函数,并且可以作为一个构造函数使用

ObjMaker.prototype.b = 'second';
// 像其他函数一样,ObjMaker拥有一个可访问的属性prototype可以被修改,这里增加了一个属性b。
// objMaker还有一个内部的属性[[prototype]],可以通过上述的两个方法进行访问和修改,
// 修改的话仅直接替换为其他对象或者设置为null

obj1 = new ObjMaker();
// 这里会发生前面所说的几件事情
// 首先创建一个空的对象obj1
// 然后将obj1的内部实例对象[[prototype]]设置为ObjMaker的prototype值,
// 并且设置this上下文为obj1,执行构造函数。因此obj1.a可以拿到first值

console.log(obj1.a);
// 输出first

console.log(obj1.b);
// 输出second,解释: obj1并没有属性b,因此会去obj1的内部实例对象属性[[prototype]]上查找,也就是ObjMaker的prototype对象,找到b之后返回对应的值

继承实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SubObjMaker = function() {};
SubObjMaker.prototype = new ObjMaker(); // 这种继承模式已经废弃
// 因为使用了new,SubObjMaker.prototype的内部实例对象属性[[prototype]]
// 会被设置为ObjMaker.prototype属性。
// 现在通常会使用ES5中的Object.create()方法来实现。
// SubObjMaker.prototype = Object.create(ObjMaker.prototype);

SubObjMaker.prototype.c = 'third';
obj2 = new SubObjectMaker();
// obj2的实例原型对象[[prototype]]被设置为SubObjectMaker.prototype属性,
// SubObjectMaker.prototype的[[prototype]]属性为ObjMaker.prototype
// 形成了一条如下的原型链
// ojb2 -> SubObjMaker.prototype -> ObjMaker.prototype

console.log(obj2.c);
// 输出third,通过原型链查找,在SubObjMaker.prototype上找到

console.log(obj2.b);
// 输出second, 通过原型链查找,在ObjMaker.prototype上找到

console.log(obj2.a);
// 输出first, 通过原型链查找,在SubObjMaker.prototype上找到,
// 因为SubObjMaker.prototype是由ObjMaker构造生成的,因此脚本会自动赋值

实现

1
2
3
4
5
6
7
8
9
10
11
function newOperator(ConStr, args) {
var thisValue = Object.create(ConStr.prototype);
// 构建一个空对象并实现继承

var result = ConStr.apply(thisValue, args);
if (typeof result === 'object' && result != null) {
// 构造函数可能会返回对象,这里要增加判断,并且要排除null,因为null的类型检测为object
return ret;
}
return thisValue;
}

欢迎访问我的博客 https://blog.bookcell.org

参考

ES6模块-团队分享

大纲

  • 前端模块化的历史
  • ES6模块的使用
  • ES6模块背后的思考
  • 组件和模块
  • 参考文章

前端模块化的历史

前端模块化的历史

  • 全局Function模式
  • 命名空间模式
  • IIFE模式
  • IIFE模式(引入依赖)
  • CommonJS
  • AMD
  • CMD
  • ES6 模块

ES6模块的使用

什么是模块?

模块是通过不同于脚本的形式进行加载的JavaScript文件。区别在于:

  1. 模块代码自动运行在严格模式,并无法退出
  2. 模块中定义的变量只在模块的作用域中存在,不会自动添加到全局的作用域
  3. 模块中的thisundefined
  4. 模块中不支持HTML风格的注释<!-- comments -->
  5. 模块必须要导出任何可以在模块外可用的一切
  6. 模块可以从其他模块导入内容

基本导出

使用export关键字可以从模块中导出变量、函数和类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

// export function
export function sum(num1, num2) {
return num1 + num1;
}

// export class
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}

// this function is private to the module
function subtract(num1, num2) {
return num1 - num2;
}

// define a function...
function multiply(num1, num2) {
return num1 * num2;
}

// ...and then export it later
export { multiply };

导出说明:

  1. 每个导出的函数和类都有一个名字,除了使用default关键字外无法使用上面的语法导出匿名函数和类
  2. 查看multiply()函数,你可以不导出声明,支持导出引用
  3. 查看substract()函数,外部不可访问,没有显式导出的变量、函数和类对于模块是私有的

基本导入

基本格式:

1
import { identifier1, identifier2 } from "./example.js";

说明: 从一个模块中导入一个绑定,就类似定义一个const的内容,也就是无法定义重名变量、导入前使用和修改值。

导入一个单一绑定

1
2
3
4
5
6
// import just one
import { sum } from "./example.js";

console.log(sum(1, 2)); // 3

sum = 1; // error

尽管example.js中定义了多个导出,你可以只导入其中一个;为了在浏览器和Node.js中保持兼容性,尽量在文件名前使用相对路径。

导入多个绑定

1
2
3
4
// import multiple
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber)); // 8
console.log(multiply(1, 2)); // 2

全部导入

特殊的应用场景支持导入整个模块作为一个对象,类似导入一个库文件,所有的模块导出均作为对象属性。

1
2
3
4
5
// import everything
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); // 2

不管多少次使用import对一个模块进行导入操作,模块只会执行一次;在导入模块的代码执行后,实例化的模块在保存在内存中并在其他import语句使用时复用。

1
2
3
import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";

代码中example.js只会执行一次.

模块语法不支持在语句或者函数中使用,也就是说不支持动态的导入和导出。ECMAScript dynamic-import Stage3

导入绑定的修改

对导入的内容是无法修改的,但是在模块内部可以。

模块导出:

1
2
3
4
export var name = "Nicholas";
export function setName(newName) {
name = newName;
}

模块导入:

1
2
3
4
5
6
7
import { name, setName } from "./example.js";

console.log(name); // "Nicholas"
setName("Greg");
console.log(name); // "Greg"

name = "Nicholas"; // error

setName函数回到模块内执行并修改变量name的内容,随后这个修改会反映到导入的name这个绑定。

重命名导出和导入

有时候,你可能不想使用变量、函数、类在模块中的原始名字,你可以在导入和导出的时候使用as关键字来指定新的名字

导出时重命名

1
2
3
4
5
function sum(num1, num2) {
return num1 + num2;
}

export { sum as add };

导入

1
import { add } from "./example.js";

导入时重命名

1
2
3
import { add as sum } from "./example.js";
console.log(typeof add); // "undefined"
console.log(sum(1, 2)); // 3

模块中的默认值

模块中的默认值是指使用default指定的一个单独变量、函数或者类,每个模块中只能设定一个默认导出,指定多个默认导出会报语法错误。

导出默认值

直接导出

1
2
3
export default function(num1, num2) {
return num1 + num2;
}

函数可以不写名字,因为模块本身代表这个函数。

导出引用

1
2
3
4
5
function sum(num1, num2) {
return num1 + num2;
}

export default sum;

重命名导出

1
2
3
4
5
function sum(num1, num2) {
return num1 + num2;
}

export { sum as default };

导入默认值

javascript
// import the default
import sum from “./example.js”;

console.log(sum(1, 2)); // 3

1
2
3
4
5
6
7
8
9
10
11
12

1. 默认导入不需要大括号
2. 名称可以代表任意模块中的默认函数
3. 默认导入的语法是最精简的,ES6模块规范制定者希望默认值作为主流的使用场景

**具名和默认混合**
```javascript
export let color = "red";

export default function(num1, num2) {
return num1 + num2;
}
1
2
3
4
import sum, { color } from "./example.js";

console.log(sum(1, 2)); // 3
console.log(color); // "red"

默认值要在非默认值前面导入。

重命名默认值

1
2
3
4
5
// equivalent to previous example
import { default as sum, color } from "example";

console.log(sum(1, 2)); // 3
console.log(color); // "red"

重新导出一个绑定

几种重新导出的方式

1
2
import { sum } from "./example.js";
export { sum }
1
export { sum } from "./example.js";
1
export { sum as add } from "./example.js";

全部导出

1
export * from "./example.js";

这里只能导出所有的具名导出,不包括默认导出。如果要处理默认导出,你需要显式的导入后再导出。

没有绑定的导入

有些模块并没有导出任何内容,仅仅是在全局作用域中做了些修改;虽然模块无法直接修改全局作用域的变量、函数和类,但是对于內建对象的修改可以反映到其他模块中。

1
2
3
4
5
6
7
8
9
10
11
// module code without exports or imports
Array.prototype.pushAll = function(items) {

// items must be an array
if (!Array.isArray(items)) {
throw new TypeError("Argument must be an array.");
}

// use built-in push() and spread operator
return this.push(...items);
};

这个模块对Array原型增加一个pushAll方法.

1
2
3
4
5
6
import "./example.js";

let colors = ["red", "green", "blue"];
let items = [];

items.pushAll(colors);

这段代码导入和执行模块代码,添加pushAll方法到array原型中。

没有绑定的导入通常用于创建polyfill或者shim。

加载ES6 模块

  • 使用打包工具和Babel转译为ES5

  • 使用script标签,并设置typemodule(IE11不兼容,使用前查看兼容性)

默认使用defer属性,按照代码出现的顺序进行加载,执行需要在文档解析完成后,执行顺序也是按照代码出现的顺序。

1
2
3
4
5
6
7
8
9
10
11
12
<!-- this will execute first -->
<script type="module" src="module1.js"></script>

<!-- this will execute second -->
<script type="module">
import { sum } from "./example.js";

let result = sum(1, 2);
</script>

<!-- this will execute third -->
<script type="module" src="module2.js"></script>

如果模块代码中又引入其他的模块,执行的顺序会变得复杂一些:

加载顺序:

  1. Download and parse module1.js.
  2. Recursively download and parse import resources in module1.js.
  3. Parse the inline module.
  4. Recursively download and parse import resources in the inline module.
  5. Download and parse module2.js.
  6. Recursively download and parse import resources in module2.js

执行顺序:

  1. Recursively execute import resources for module1.js.
  2. Execute module1.js.
  3. Recursively execute import resources for the inline module.
  4. Execute the inline module.
  5. Recursively execute import resources for module2.js.
  6. Execute module2.js.
  • web worker
1
2
// load module.js as a module
let worker = new Worker("module.js", { type: "module" });

ES6模块背后的思考

对比CommonJS和AMD

CommonJS

在Node.js中的主要实现方式,特性如下:

  • 紧凑的语法
  • 同步加载
  • 主要使用: 服务端

Asynchronous Module Definition(AMD)

主要的实现是RequireJS,特性如下:

  • 稍微复杂的语法
  • 异步加载
  • 主要使用: 浏览器

ES6模块

目标:

  • 偏爱默认导出
  • 静态模块结构
  • 支持同步和异步加载
  • 支持模块间的循环依赖

优势:

  • 比CommonJS更紧凑的语法
  • 结构可以被静态分析(静态检测、优化等)
  • 对循环引用比CommonJS更好
  • 支持异步加载

八卦: ES6 Module的主要设计者Dave Herman和Sam Tobin Hochstadt。

组件和模块

  • 模块为基础,组件基于模块
  • 组件侧重UI封装,针对,包括html,css,js和image;模块侧重数据、功能封装

组件化参考张云龙-前端工程

参考文章

ES6中的模块化

这里主要是针对ES6 Module技术出现的讨论,对比历史的一些模块化解决方案。

背景、初衷、目标

背景

简单的讲,随着前端应用的日益庞大和复杂,对于代码的拆分复用要求更高,也就出现了对代码模块化的需求。

在ES6 Module出现前,社区中出现的两个方案CommonJS和AMD:

CommonJS

在Node.js中的主要实现方式,特性如下:

  • 紧凑的语法
  • 同步加载
  • 主要使用: 服务端

Asynchronous Module Definition(AMD)

主要的实现是RequireJS,特性如下:

  • 稍微复杂的语法
  • 异步加载
  • 主要使用: 浏览器

这里提一句,ES6 Module的主要设计者Dave Herman 和 Sam Tobin Hochstadt。

初衷

这里讲的ES6 Module的设计初衷,在ES6 Module出现前社区已经在模块化上有一些令人印象深刻的变通方案,以CommonJS和AMD为代表,这两者各有优缺点并且不兼容,因此ES6 Module的设计初衷是为了吸取这两者的优点实现ECMAScript的标准。

目标

ES6 Module的目标是出一个让CommonJS和AMD社区都能接受的方案:

  • 偏爱默认导出
  • 静态模块结构
  • 支持同步和异步加载
  • 支持模块间的循环依赖

优势和劣势(trade-off)

ES6 Module实际达成的方案特性如下:

  • 比CommonJS更紧凑的语法
  • 结构可以被静态分析(静态检测、优化等)
  • 对循环引用比CommonJS更好
  • 支持异步加载

适用的场景(业务场景、技术场景)

适用于浏览器端和服务端(Nodejs),目前(2019年)主流的浏览器大部分都不支持ES6 Module的特性,因此使用的话还得使用Babel来转译。

组成部分和关键点

ES6 Module分两部分:

  • 声明式语法(import和export)
  • 编程式加载API: 支持配置如何加载模块以及条件加载

使用场景

在浏览器侧的使用

使用<script>进行加载

通过type属性值为module来标识模块进行加载,支持外部文件和内联的方式,默认使用defer的行为,也可以指定async

1
2
3
4
5
6
7
8
9
10
11
<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>

<!-- include a module inline -->
<script type="module">

import { sum } from "./example.js";

let result = sum(1, 2);

</script>

通过Worker进行加载

1
2
// load module.js as a module
let worker = new Worker("module.js", { type: "module" });

通过打包工具进行打包

使用webpack或者browserify等打包工具进行打包后使用。

声明式语法

声明式语法分为两种类型:具名导出(每个模块有多个)和默认导出(每个模块一个)。

具名导出(named exports)

通过关键字export前缀可以导出多个内容,并有名称来进行区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

默认导出(default exports)

一个模块只有一个默认导出,并且默认导出尤其容易进行导入。

1
2
3
4
5
6
//------ myFunc.js ------
export default function () { ... };

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

对于class的使用

1
2
3
4
5
6
//------ MyClass.js ------
export default class { ... };

//------ main2.js ------
import MyClass from 'MyClass';
let inst = new MyClass();

默认导出其实可以理解为特殊的具名导出

导入:

1
2
import { default as foo } from 'lib';
import foo from 'lib';

导出:

1
2
3
4
5
6
//------ module1.js ------
export default 123;

//------ module2.js ------
const D = 123;
export { D as default };

答疑

  • 为什么我们需要具名导出(named exports)?

The answer is that you can’t enforce a static structure via objects and lose all of the associated advantages (described in the next section).

如果通过对象进行导出会丢失静态结构,从而失去原先的优势。

  • ES6 Module输出的是值的引用,并且是在编译时输出接口。

编程式语法

针对ES6 Module的编程式实现,目前规范处于stage 3。

使用编程式语法,可以做到:

  • 对模块和脚本进行编程操作
  • 配置模块的加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<nav>
<a href="books.html" data-entry-module="books">Books</a>
<a href="movies.html" data-entry-module="movies">Movies</a>
<a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>

<main>Content will load here!</main>

<script>
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", e => {
e.preventDefault();

import(`./section-modules/${link.dataset.entryModule}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
});
}
</script>

已有的实现和它之间的对比

有关模块化的进程,前端模块化详解一句讲解得比较详细了,可以参考。

参考文档

JavaScript中对象的几种创建方式

记录JavaScript中创建对象的几种方法。

工厂模式

工厂模式是一种设计模式,抽象创建具体对象的过程,用函数来封装特定接口并创建对象的细节。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 工厂模式 */
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

console.log(person1);
console.log(person2);

优点:
解决创建多个相似对象。

缺点:
没有解决对象识别的问题。

阅读更多

前端数据可视化库的选型

Web前端中有时会需要将数据通过图表的形式展示,而选择一个JavaScript的可视化库是比较重要的。
选择需要多个因素进行权衡,项目的需求、开发周期、人员的技术、文档、问题的解决、功能,下面是针对
github上星数较多的数据可视化库针对各角度做一下对比,方便后续的选型有所帮助。

阅读更多