OpenHarmony-JavaScript-coding-style-guide.md 23.4 KB
Newer Older
N
NEEN 已提交
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
# JavaScript语言通用编程规范

## 目的
规则并不是完美的,通过禁止在特定情况下有用的特性,可能会对代码实现造成影响。但是我们制定规则的目的“为了大多数程序员可以得到更多的好处”, 如果在团队运作中认为某个规则无法遵循,希望可以共同改进该规则。 
参考该规范之前,希望您具有相应的JavaScript语言基础能力,而不是通过该文档来学习JavaScript语言。 

## 总体原则
代码需要在保证功能正确的前提下,满足**可读、可维护、安全、可靠、可测试、高效、可移植**的特征要求。 

## 约定

**规则**:编程时必须遵守的约定  
**建议**:编程时必须加以考虑的约定

无论是“规则”还是“建议”,都必须理解该条目这么规定的原因,并努力遵守。  

## 例外

在不违背总体原则,经过充分考虑,有充足的理由的前提下,可以适当违背规范中约定。  
例外破坏了代码的一致性,请尽量避免。“规则”的例外应该是极少的。

下列情况,应风格一致性原则优先:  
**修改外部开源代码、第三方代码时,应该遵守开源代码、第三方代码已有规范,保持风格统一。**  

## 编程规范

### 命名规范

#### 规则1.1 须使用正确的英文拼写进行命名,禁止使用拼音拼写。

**反例:**`xingming``zhanghao`

**正例:**`username``account`

#### 规则1.2 命名尽量少用缩写,除非是常见词或者业务线的领域词汇。比如:`context`可以简写成`ctx`,`request`可简写成`req`,`response`可简写成`resp`。

**说明:** 完整的单词拼写可以避免不必要的阅读障碍。

**例外:** 循环语种中可以使用`i``j`循环条件变量名。

#### 规则1.3 类名、枚举名、命名空间名采用`UpperCamelCase`风格。

**正例:**

```javascript
// 类名
class User {
  constructor(username) {
    this.username = username;
  }
  
  sayHi() {
    console.log(`hi, ${this.username}`);
  }
}

// 枚举名
const UserType = {
  TEACHER: 0,
  STUDENT: 1
};

// 命名空间
const Base64Utils = {
  encrypt: function(text) {
    // todo encrypt
  },
  decrypt: function(text) {
    // todo decrypt
  }
};
```

#### 规则1.4 变量名、方法名、参数名采用`lowerCamelCase`风格。

**正例:**

```javascript
let msg = 'Hello world';

function sendMsg(msg) {
  // todo send message
}

function findUser(userID) {
  // todo find user by user ID
}
```

#### 规则1.5 静态常量名、枚举值名采用全部大写,单词间使用下划线隔开。

**正例:**

```javascript
const MAX_USER_SIZE = 10000;

const UserType = {
  TEACHER: 0,
  STUDENT: 1
};
```

#### 建议1.6 避免使用否定的布尔变量名,布尔型的局部变量或方法须加上表达是非意义的前缀。

**反例:**

```javascript
let isNoError = true;
let isNotFound = false;
function empty() {}
function next() {}
```

**正例:**

```javascript
let isError = false;
let isFound = true;
function isEmpty() {}
function hasNext() {}
```

### 代码格式

#### 规则2.1 采用2个空格缩进,禁止使用`tab`字符

**说明:** 只允许使用空格(space)进行缩进,每次缩进为2个空格。不允许使用Tab 符进行缩进。

**正例:**

```javascript
const dataSource = [
  {
    id: 1,
    title: 'Title 1',
    content: 'Content 1'
  },
  {
    id: 2,
    title: 'Title 2',
    content: 'Content 2'
  }
];

function render(container, dataSource) {
  if (!container || !dataSource || !dataSource.length) {
    return void 0;
  }

  const fragment = document.createDocumentFragment();
  for (let data of dataSource) {
    if (!data || !data.id || !data.title || !data.content) {
      continue;
    }
    const element = document.createElement("div");
    const textNode = document.createTextNode(`${data.title}: ${data.content}`);
    element.appendChild(textNode);
    element.setAttribute("id", data.id);
    fragment.appendChild(element);
  }
  container.appendChild(fragment);
}

```

#### 规则2.2 行宽不超过`120`个字符

**说明:** 建议每行字符数不要超过 120 个。如果超过120个字符,请选择合理的方式进行换行。

**例外:** 如果一行注释包含了超过120 个字符的命令或URL,则可以保持一行,以方便复制、粘贴和通过grep查找; 预处理的 error 信息在一行便于阅读和理解,即使超过 120 个字符。

#### 规则3.3 大括号的使用须遵循约定:

1. 如果大括号内为空,则允许简写成`{}`,且无需换行;
2. 左大括号前不换行,括号后换行;
3. 右大括号前换行,括号后还有`else``catch`等情况下不换行,其他情况都换行。

#### 规则3.4 条件语句和循环语句的实现必须使用大括号包裹,即使只有一条语句。

**反例:**

```javascript
if (condition)
  console.log('success');

for(let idx = 0; idx < 5; ++idx)
  console.log(idx);
```

**正例:**

```javascript
if (condition) {
  console.log('success');
}

for(let idx = 0; idx < 5; ++idx) {
  console.log(idx);
}
```

#### 规则2.5 条件语句和循环语句都不允许写在同一行。

**反例:**

```javascript
if (condition) { /* todo something */ } else { /* todo other */ }

let idx = 0;
while(idx < 10) console.log(idx);
```

**正例:**

```javascript
if (condition) {
  /* todo something */
} else {
  /* todo other */
}

let idx = 0;
while(idx < 10) {
  console.log(idx);
}
```

#### 规则2.6 `switch`语句的`case`和`default` 须缩进一层。

**正例:**

```javascript
switch(condition) {
  case 0:
    doSomething();
    break;
  case 1: { // 可以带括号也可以不带
    doOtherthing();
    break;
  }
  default:
    break;
}
```

#### 规则2.7 表达式换行须保持一致性,运算符放行末。

**说明:** 较长的表达式,不满足行宽要求时,需要在适当的位置进行换行。一般在较低优先级运算符或连接符后面阶段,运算符或连接符放行末。运算符、连接符放在行末,表示“未结束,后续还有”。

**正例:**

```javascript
// 假设条件语句超出行宽
if (userCount > MAX_USER_COUNT ||
  userCount < MIN_USER_COUNT) {
  doSomething();
}

const sum =
  number1 +
  number2 +
  number3 +
  number4 +
  number5 +
  number6 +
  number7 +
  number8 +
  number9;
```

#### 规则2.8 多个变量定义和赋值语句不允许写在一行。

**反例:**

```javascript
let maxCount = 10, isCompleted = false;

let pointX, pointY;
pointX = 10; pointY = 0;
```

**正例:**

```javascript
let maxCount = 10;
let isCompleted = false;

let pointX = 0;
let pointY = 0;
```

#### 规则2.9 空格应该突出关键字和重要信息,避免不必要的空格。

**说明:** 空格可以减低代码密度,增加代码的可读性。总体规则如下:

1. `if``elseif``else``switch``while``for`等关键字之后加空格;
2. 小括号内部两侧不加空格;
3. 大括号内部两侧须加空格,但`{}`等简单场景例外;
4. 多重括号之间不加空格;
5. 一元操作符(`&``*``+``-``!`等)之后不加空格;
6. 二元操作符(`=``+``-``*``/``%``|``&``||``&&``<``>``<=``>=``==``!=``===``!==`等)左右两侧加空格;
7. 三元运算符(`?: `)的两侧添加空格;
8. 前置或后置的自增、自减(`++``--`)和变量之间不加空格;
9. 逗号(`,`)前面不加空格,后面加空格;
10. 单行注释`//`后加空格;
11. 行尾不加空格。

#### 规则2.10 表达式语句须以分号结尾。

**反例:**

```javascript
let username = 'jack'
let birthday = '1997-09-01'

console.log(`${username}'s birthday is ${birthday}`)
```

**正例:**

```javascript
let username = 'jack';
let birthday = '1997-09-01';

console.log(`${username}'s birthday is ${birthday}`);
```

#### 建议2.11 优先使用单引号包裹字符串。

**正例:**

```javascript
let message = 'wolrd';
console.log(message);
```

### 代码规范

#### 规则3.1 声明变量时须使用`var`、`let`或`const`关键字进行声明,避免变量暴露到全局作用域上。

**说明:** 不使用`var``let``const`关键字声明变量,会导致将变量暴露到全局作用域上,这样可能会覆盖全局作用域上的同名变量,进而引发GC无法有效回收内存的问题;另外,当变量中包含敏感信息时,暴露到全局作用域可能会导致信息泄露问题。另外,**尽量对所有的引用使用`const`,不要使用 `var`;如果你一定需要可变动的引用,使用 `let` 代替 `var`**。因为`const``let`的作用域更小,写代码更容易控制;const可确保无法对引用重新赋值,const引用的指针不可变,重新赋值会报错,避免了不小心的重新赋值给覆盖了。

**反例:**

```javascript
function open() {
  url = 'http://127.0.0.1:8080'; //url会暴露到全局作用域上
  //todo something
}
open();
console.log(url); //url可以被访问到,输出内容:http://127.0.0.1:8080
```

**正例:**

```javascript
function open() {
358
  let url = 'http://127.0.0.1:8080';
N
NEEN 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
  // todo something
}
open();
console.log(url); //报错:Uncaught ReferenceError: url is not defined
```

```javascript
function open() {
  const url = 'http://127.0.0.1:8080';
  //todo something
}
open();
console.log(url); //报错:Uncaught ReferenceError: url is not defined
```

#### 规则3.2 函数块内须使用函数表达式声明函数

**说明:** 虽然很多 JS 引擎都支持块内声明函数,但它不属于 ECMAScript规范,各个浏览器糟糕的实现相互不兼容,有些也与未来 ECMAScript草案相违背。另外,ECMAScript5不支持块作用域,对于所有的控制流都不是独立的作用域,其内部声明的变量或者函数都是在其父函数或者脚本的作用域中,导致块内函数或者变量的声明会存在覆盖现象。如果确实需要在块中定义函数,应该使用函数表达式来初始化。

**反例:**

```javascript
function bar(name) {
  if (name === "hotel") {
    // 1、定义一个foo函数,其作用域不是if代码块,而是bar函数作用域。
    function foo() {
      console.log("hotel foo A");
    }
  } else {
    // 2、再重复定义一次foo函数,覆盖上面if条件分支下的foo函数定义。
    function foo() {
      console.log("hotel foo 2");
    }
  }
  foo && foo();
}
bar("hotel"); // 输出结果总是显示"hotel foo 2"
```

**正例:**

```javascript
function bar(name) {
  var foo;
  if (name == "hotel") {
    foo = function () {
      console.log("hotel foo 1");
    };
  } else {
    foo = function () {
      console.log("hotel foo 2");
    }
  }
  foo && foo();
}
bar("hotel"); // 正确输出"hotel foo 1"
```

#### 规则3.3 禁止封装基本类型

**说明:** JavaScript中有五种基本数据类型:Undefined、Null、Boolean、Number和String。基本数据类型的值是不可更改的。JavaScript中使用基本数据类型对象只是值,不包含器封装对象的方法和属性,在不需要使用属性和方法的时候,不需要使用其封装类型。

**反例:**

```javascript
var isShow = new Boolean(false);
if (isShow) {
  alert('hi'); //被执行,界面弹出显示:hi
}
```

**正例:**

```javascript
var isShow = false;
if (isShow) {
  alert('hi');
}
```

#### 规则3.4 禁止使用`with`

**说明:** 使用 with让你的代码在语义上变得不清晰,因为with的对象,可能会与局部变量产生冲突,从而改变程序原本的用义。

**反例:**

```javascript
var foo = { x: 5 };
with(foo) {
  var x = 3;
  console.log(x); //输出:5
}
```

#### 规则3.5 `this`仅可在对象构造函数、方法、闭包中使用

**说明:** 在JavaScript里面,this指针代表的是执行当前代码的对象的所有者。this具有特殊的语义:

+ 全局对象(大多数情况下)
+ 调用者的作用域(使用eval时)
+ DOM 树中的节点(添加事件处理函数时)
+ 新创建的对象(使用一个构造器)
+ 其他对象(如果函数被 call() 或 apply())

```javascript
var User = function(username) {
  this.username = username;
};
var user = new User('John');
console.log(user.username); // 输出:John

var ClickCounter = {
  value: 0,
  click: function() {
    ++this.value;
  },
  getValue() {
    return this.value;
  }
};
console.log(Counter.getValue()); //输出:0
Counter.click();
console.log(Counter.getValue()); //输出:1
```

#### 规则3.6 禁止使用IE下的条件注释

**说明:** 在IE下使用`\@cc_on`语句或使用`\@if``\@set`语句可以激活条件编译。尽管可以通过注释来兼容IE以外的浏览器,但它妨碍自动化工具的执行,因为在运行时,它们会改变JavaScript 语法树。

**反例:**

```javascript
var f = function () {
  /*@cc_on @*/
  /*@if (@_jscript_version >= 4)
       alert("JavaScript version 4 or better");
    @else @*/
  alert("Conditional compilation not supported by this scripting engine.");
  /*@end @*/
};
```

#### 规则3.7 禁止修改内置对象的原型

**说明:** 内置对象作为一套公共接口,具有约定俗成的行为方式,修改其原型,可能破坏接口语义,或导致调试时的诡异现象。

**反例:**

```javascript
Array.prototype.indexOf = function () { return -1 }
var arr = [1, 1, 1, 1, 1, 2, 1, 1, 1];
console.log(aar.indexOf(2)); // 输出:-1
```

#### 规则3.8 禁止直接使用`Object.prototype`的内置属性

**说明:** ECMAScript5.1新增了`Object.create`,它创建一个新对象,使用现有的对象来提供新创建的对象的proto。`Object.create(null)`是用于创建用作map的对象的常用模式。当该对象具有`Object.prototype`同名的属性时,可能会导致意外行为或漏洞。例如,web服务器解析来自客户端的JSON输入并且使用`hasOwnProperty`直接调用生成的对象是不安全的,因为恶意客户端可能会发送类似的JSON值`'{ "hasOwnProperty": 1 }'` 并导致服务器崩溃。

**反例:**

```javascript
var hasBarProperty = foo.hasOwnProperty("bar");
var isPrototypeOfBar = foo.isPrototypeOf(bar);
var barIsEnumerable = foo.propertyIsEnumerable("bar");
```

**正例:**

```javascript
var hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar");
var isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar);
var barIsEnumerable = {}.propertyIsEnumerable.call(foo, "bar");
```

#### 规则3.9 使用`Object.getPrototypeOf`函数而不要使用`_proto_`

说明:ES5引入`Object.getPrototypeOf`函数作为获取对象原型的标准API,但在这之前大量的JavaScript引擎早就使用一个特殊的`proto`属性来达到相同的目的。然而,`proto`它本质上是一个内部属性,而不是一个正式的对外的API,目前浏览器必须部署这个属性,但其他运行环境不一定部署,因此,该属性并不是完全兼容的。例如,对于拥有null原型的对象,不同的环境处理的不一样。

```javascript
var empty = Object.create(null);
"_proto_" in empty; //有些环境返回false,有些环境返回true
```

所以无论从语义的角度,还是从兼容性的角度,都不要使用proto这个属性,而是使用`Object.getPrototypeOf()`来代替。无论在什么环境中,`Object.getPrototypeOf`函数都是有效的,而且也是提取对象原型更加标准、可移植的方法。

#### 规则3.10 不要使用函数构造器创建函数

说明:定义函数的方法包括3种:函数声明、Function构造函数和函数表达式。不管用哪种方法定义函数,它们都是Function对象的实例,并将继承Function对象所有默认或自定义的方法和属性。以函数构造器创建函数的方式类似于字符串`eval()`,可以接受任何字符串形式作为它的函数体,这就会有安全漏洞的风险。

**反例:**

```javascript
var func = new Function('a', 'b', 'return a + b');
var func2 = new Function('alert("Hello")');
```

**正例:**

```javascript
function func(a, b) {
  return a + b;
}

var func2 = function(a, b) {
  return a + b;
}
```

#### 建议3.11 在使用原型`prototype`实现继承时,尽量使用现有稳定的库方法而非自行实现

**说明:** 多级原型结构是指JavaScript中的继承关系。当定义一个D类,且把B类作为其原型,那么就获得了一个多级原型结构。这些原型结构会变得很复杂。使用现有的稳定的库方法如`the Closure`库的`goog.inherits()`或其他类似的函数,可避免不必要的编码失误,将是更好的选择。

#### 建议3.12 定义类时,应在原型下定义方法,在构造方法内定义属性

**说明:** JavaScript中有多种方法可以给构造函数添加方法或成员,但是使用原型定义方法,可以降低内存占用,提高运行效率。

**反例:**

```javascript
function Animals() {
  this.walk = function() {}; // 这样会导致每个实例上都创建一个walk方法
}
```

**正例:**

```javascript
function Animals() {}

Animals.prototype.walk = function() {};
```

#### 建议3.13 使用闭包时,应避免构成循环引用,导致内存泄露

**说明:** JavaScript是一种垃圾收集式语言,其对象的内存是根据对象的创建分配给该对象的,并且会在没有对该对象的引用时由浏览器收回。JavaScript的垃圾收集机制本身并没有问题,但浏览器在为DOM对象分配和恢复内存的方式上有些出入。IE和Firefox均使用引用计数来为DOM对象处理内存。在引用计数系统中,每个所引用的对象都会保留一个计数,以获悉有多少对象正在引用它。如果计数为零,那么该对象就会被销毁,其占用的内存也会返回给堆。虽然这种解决方案总的来说还算有效,但是在循环引用方面却存在一些盲点。 当两个对象互相引用时,就构成了循环引用,其中对象的引用计数值都被赋为1。在纯垃圾收集系统中,循环引用问题不大:如果涉及的两个对象中有一个对象被任何其他对象引用,那么这两个对象都将被垃圾收集。而在引用计数系统中,这两个对象都不能被销毁,原因是引用计数永远不能为0。在同时使用了垃圾收集和引用计数的混合系统中,将会发生泄漏,因为系统不能正确识别循环引用。在这种情况下,DOM对象和JavaScript对象均不能被销毁。 循环引用很容易创建。在JavaScript最为方便的编程结构之一——闭包中,循环引用尤其突出。闭包会持有其外部作用域(包括局部变量、参数及方法)的引用,当闭包本身又被作用域成员(常见于DOM对象)持有时便构成循环引用,进一步导致内存泄露。

**反例:**

```javascript
function setClickListener(element, a, b) {
  element.onclick = function() {
    // 在这里用到a和b
  };
};
```

在上述代码中,即使没有使用element,闭包也保留了element、a和b的引用。由于element也保留了对闭包的引用,因此产生了循环引用,不能被GC回收。

**正例:**

```javascript
function setClickListener(element, a, b) {
  element.onclick = createHandler(a, b);
}

function createHandler(a, b) {
  // 通过添加另外一个函数来避免闭包本身,进而组织内存泄露
  return function() {
    // 在这里用到a和b
  }
}
```

#### 建议3.14 警惕JavaScript的浮点数

**说明:** JavaScript具有单一数字类型:`IEEE 754`双精度浮点数。拥有单一数字类型是JavaScript的最佳功能之一。多种数字类型可能是复杂性,混淆和错误的根源。但是,二进制浮点类型有一个最大的缺点是,它不能准确地表示小数部分,会导致让人意外的精度问题,见下面示例。

示例代码1:

```javascript
console.log(0.1 + 0.2 === 0.3); // 输出:false。所以通常禁止直接使用==或===来比较浮点数。
```

示例代码2:

```javascript
var sum1 = (0.1 + 0.2) + 0.3;
console.log(sum1); // 输出:0.6000000000000001

var sum2 = 0.1 + (0.2 + 0.3);
console.log(sum2); // 输出:0.6。所以对于二进制浮点数,(a + b) + c 不能保证产生于a + (b + c)相同的结果。
```

有效的解决方法有以下几种:

1. 尽可能的采用整数值运算,因为整数在表示是不需要舍入;

2. 使用JavaScript的原生方法`Number.prototype.toFixed(digits)`,`digist`参数表示小数点后数字的个数,不使用指数法,在必要时会进行四舍五入。使用该方法,在判断浮点数运算结果前对计算结果进行精度缩小。示例代码如下所示:

   ```javascript
   parseFloat(0.1 + 0.2).toFixed(1); //0.3
   ```

3. ES6 新增了一个极小的常量`Number.EPSILON =.220446049250313e-16 `,约等于`0.00000000000000022204``Number.EPSILON`的出现是用来判断浮点数的计算误差,如果浮点数计算得到的误差不超过`Number.EPSILON`的值,就表示可以接受这样的误差。示例代码如下所示:

   ```javascript
   function isNumberEquals(one, other) {
     return Math.abs(one - other) < Number.EPSILON;
   }
   var one = 0.1 + 0.2;
   var other = 0.3;
   console.log(isNumberEquals(one, other)); // 输出:true
   ```

4. 使用一些支持精确运算的类库方法,如`math.js`

   ```html
   <!DOCTYPE html>
   <html>
     <head>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.0.0/math.js"></script>
       <script type="text/javascript">
         function fn_click() {
           math.config({
             number: "BigNumber",
           });
           var result = math.add(math.bignumber(0.1), math.bignumber(0.2));
           alert(result);
         }
       </script>
     </head>
     <body>
       <input type="button" value="mathjs(0.1+0.2)" onclick="fn_click();" />
     </body>
   </html>
   
   ```

#### 建议3.15 不要使用可变参数的Array构造函数

**说明:** 通常不鼓励使用构造函数`new Array`的方法来构造新数组,因为当构造函数只有一个参数的时候,可能会导致意外情况,另外Array的全局定义也可能被重新修改,所以提倡使用数组文字表示法来创建数组,也就是`[]`表示法。
**反例:**

```javascript
const arr1 = new Array(x1, x2, x3);
const arr2 = new Array(x1, x2);
const arr3 = new Array(x1);
const arr4 = new Array();
```

除了第三种情况之外,其他都可以正常工作,如果`x1`是个整数,那么`arr3`就是长度为`x1`,值都为`undefined`的数组。如果`x1`是其他任何数字,那么将抛出异常,如果它是其他任何东西,那么它将是单元数组。

**正例:**

```javascript
const arr1 = [x1, x2, x3];
const arr2 = [x1, x2];
const arr3 = [x1];
const arr4 = [];
```

这种写法,就会省去很多麻烦。

同理,对于对象,同样不要使用`new Object()`,而是使用`{}`来创建。

#### 规则3.16 构建字符串时,优先使用字符串模板而不是字符串链接。

**说明:** 模板字符串表达更简洁,更具可读性。

**反例:**

```javascript
function sayHi(name) {
  console.log('hi, ' + name);
}
```

**正例:**

```javascript
function sayHi(name) {
  console.log(`hi, ${name}`);
}
```

#### 规则3.17 数组遍历采用`for...of`,对象遍历采用`for...in`。

**反例:**

```javascript
let numbers = [1, 2, 3, 4];
let sum = 0;
for (let number in numbers) {
  sum += number;
}
// sum === 00123;
```

**正例:**

```javascript
let numbers = [1, 2, 3, 4];
let sum = 0;
for (let number of numbers) {
  sum += number;
}
// sum === 10
```