JavaScript编码规范
Posted 明月清辉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript编码规范相关的知识,希望对你有一定的参考价值。
前端编码风格规范—— JavaScript 规范
JavaScript 规范
全局命名空间污染与 IIFE
总是将代码包裹成一个 IIFE(Immediately-Invoked Function Expression),用以创建独立隔绝的定义域。这一举措可防止全局命名空间被污染。
IIFE 还可确保你的代码不会轻易被其它全局命名空间里的代码所修改(i.e. 第三方库,window 引用,被覆盖的未定义的关键字等等)。
不推荐
- var x = 10,
- y = 100;
- // Declaring variables in the global scope is resulting in global scope pollution. All variables declared like this
- // will be stored in the window object. This is very unclean and needs to be avoided.
- console.log(window.x + ‘ ‘ + window.y);
推荐
- // We declare a IIFE and pass parameters into the function that we will use from the global space
- (function(log, w, undefined){
- ‘use strict‘;
- var x = 10,
- y = 100;
- // Will output ‘true true‘
- log((w.x === undefined) + ‘ ‘ + (w.y === undefined));
- }(window.console.log, window));
IIFE(立即执行的函数表达式)
无论何时,想要创建一个新的封闭的定义域,那就用 IIFE。它不仅避免了干扰,也使得内存在执行完后立即释放。
所有脚本文件建议都从 IIFE 开始。
立即执行的函数表达式的执行括号应该写在外包括号内。虽然写在内还是写在外都是有效的,但写在内使得整个表达式看起来更像一个整体,因此推荐这么做。
不推荐
- (function(){})();
推荐
- (function(){}());
so,用下列写法来格式化你的 IIFE 代码:
- (function(){
- ‘use strict‘;
- // Code goes here
- }());
如果你想引用全局变量或者是外层 IIFE 的变量,可以通过下列方式传参:
- (function($, w, d){
- ‘use strict‘;
- $(function() {
- w.alert(d.querySelectorAll(‘div‘).length);
- });
- }(jQuery, window, document));
严格模式
ECMAScript 5 严格模式可在整个脚本或独个方法内被激活。它对应不同的 javascript 语境会做更加严格的错误检查。严格模式也确保了 javascript 代码更加的健壮,运行的也更加快速。
严格模式会阻止使用在未来很可能被引入的预留关键字。
你应该在你的脚本中启用严格模式,最好是在独立的 IIFE 中应用它。避免在你的脚本第一行使用它而导致你的所有脚本都启动了严格模式,这有可能会引发一些第三方类库的问题。
不推荐
- // Script starts here
- ‘use strict‘;
- (function(){
- // Your code starts here
- }());
推荐
- (function(){
- ‘use strict‘;
- // Your code starts here
- }());
变量声明
总是使用 var
来声明变量。如不指定 var,变量将被隐式地声明为全局变量,这将对变量难以控制。如果没有声明,变量处于什么定义域就变得不清(可以是在 Document 或 Window 中,也可以很容易地进入本地定义域)。所以,请总是使用 var 来声明变量。
采用严格模式带来的好处是,当你手误输入错误的变量名时,它可以通过报错信息来帮助你定位错误出处。
不推荐
- x = 10;
- y = 100;
推荐
- var x = 10,
- y = 100;
理解 JavaScript 的定义域和定义域提升
在 JavaScript 中变量和方法定义会自动提升到执行之前。JavaScript 只有 function 级的定义域,而无其他很多编程语言中的块定义域,所以使得你在某一 function 内的某语句和循环体中定义了一个变量,此变量可作用于整个 function 内,而不仅仅是在此语句或循环体中,因为它们的声明被 JavaScript 自动提升了。
我们通过例子来看清楚这到底是怎么一回事:
原 function
- (function(log){
- ‘use strict‘;
- var a = 10;
- for(var i = 0; i < a; i++) {
- var b = i * i;
- log(b);
- }
- if(a === 10) {
- var f = function() {
- log(a);
- };
- f();
- }
- function x() {
- log(‘Mr. X!‘);
- }
- x();
- }(window.console.log));
被 JS 提升过后
- (function(log){
- ‘use strict‘;
- // All variables used in the closure will be hoisted to the top of the function
- var a,
- i,
- b,
- f;
- // All functions in the closure will be hoisted to the top
- function x() {
- log(‘Mr. X!‘);
- }
- a = 10;
- for(i = 0; i < a; i++) {
- b = i * i;
- log(b);
- }
- if(a === 10) {
- // Function assignments will only result in hoisted variables but the function body will not be hoisted
- // Only by using a real function declaration the whole function will be hoisted with its body
- f = function() {
- log(a);
- };
- f();
- }
- x();
- }(window.console.log));
根据以上提升过程,你是否可理解以下代码?
有效代码
- (function(log){
- ‘use strict‘;
- var a = 10;
- i = 5;
- x();
- for(var i; i < a; i++) {
- log(b);
- var b = i * i;
- }
- if(a === 10) {
- f = function() {
- log(a);
- };
- f();
- var f;
- }
- function x() {
- log(‘Mr. X!‘);
- }
- }(window.console.log));
正如你所看到的这段令人充满困惑与误解的代码导致了出人意料的结果。只有良好的声明习惯,也就是下一章节我们要提到的声明规则,才能尽可能的避免这类错误风险。
提升声明
为避免上一章节所述的变量和方法定义被自动提升造成误解,把风险降到最低,我们应该手动地显示地去声明变量与方法。也就是说,所有的变量以及方法,应当定义在 function 内的首行。
只用一个 var
关键字声明,多个变量用逗号隔开。
不推荐
- (function(log){
- ‘use strict‘;
- var a = 10;
- var b = 10;
- for(var i = 0; i < 10; i++) {
- var c = a * b * i;
- }
- function f() {
- }
- var d = 100;
- var x = function() {
- return d * d;
- };
- log(x());
- }(window.console.log));
推荐
- (function(log){
- ‘use strict‘;
- var a = 10,
- b = 10,
- i,
- c,
- d,
- x;
- function f() {
- }
- for(i = 0; i < 10; i++) {
- c = a * b * i;
- }
- d = 100;
- x = function() {
- return d * d;
- };
- log(x());
- }(window.console.log));
把赋值尽量写在变量申明中。
不推荐
- var a,
- b,
- c;
- a = 10;
- b = 10;
- c = 100;
推荐
- var a = 10,
- b = 10,
- c = 100;
总是使用带类型判断的比较判断
总是使用 ===
精确的比较操作符,避免在判断的过程中,由 JavaScript 的强制类型转换所造成的困扰。
如果你使用 ===
操作符,那比较的双方必须是同一类型为前提的条件下才会有效。
如果你想了解更多关于强制类型转换的信息,你可以读一读 Dmitry Soshnikov 的这篇文章。
在只使用 ==
的情况下,JavaScript 所带来的强制类型转换使得判断结果跟踪变得复杂,下面的例子可以看出这样的结果有多怪了:
- (function(log){
- ‘use strict‘;
- log(‘0‘ == 0); // true
- log(‘‘ == false); // true
- log(‘1‘ == true); // true
- log(null == undefined); // true
- var x = {
- valueOf: function() {
- return ‘X‘;
- }
- };
- log(x == ‘X‘);
- }(window.console.log));
明智地使用真假判断
当我们在一个 if 条件语句中使用变量或表达式时,会做真假判断。if(a == true)
是不同于 if(a)
的。后者的判断比较特殊,我们称其为真假判断。这种判断会通过特殊的操作将其转换为 true 或 false,下列表达式统统返回 false:false
, 0
, undefined
, null
, NaN
, ‘‘
(空字符串).
这种真假判断在我们只求结果而不关心过程的情况下,非常的有帮助。
以下示例展示了真假判断是如何工作的:
- (function(log){
- ‘use strict‘;
- function logTruthyFalsy(expr) {
- if(expr) {
- log(‘truthy‘);
- } else {
- log(‘falsy‘);
- }
- }
- logTruthyFalsy(true); // truthy
- logTruthyFalsy(1); // truthy
- logTruthyFalsy({}); // truthy
- logTruthyFalsy([]); // truthy
- logTruthyFalsy(‘0‘); // truthy
- logTruthyFalsy(false); // falsy
- logTruthyFalsy(0); // falsy
- logTruthyFalsy(undefined); // falsy
- logTruthyFalsy(null); // falsy
- logTruthyFalsy(NaN); // falsy
- logTruthyFalsy(‘‘); // falsy
- }(window.console.log));
变量赋值时的逻辑操作
逻辑操作符 ||
和 &&
也可被用来返回布尔值。如果操作对象为非布尔对象,那每个表达式将会被自左向右地做真假判断。基于此操作,最终总有一个表达式被返回回来。这在变量赋值时,是可以用来简化你的代码的。
不推荐
- if(!x) {
- if(!y) {
- x = 1;
- } else {
- x = y;
- }
- }
推荐
- x = x || y || 1;
这一小技巧经常用来给方法设定默认的参数。
- (function(log){
- ‘use strict‘;
- function multiply(a, b) {
- a = a || 1;
- b = b || 1;
- log(‘Result ‘ + a * b);
- }
- multiply(); // Result 1
- multiply(10); // Result 10
- multiply(3, NaN); // Result 3
- multiply(9, 5); // Result 45
- }(window.console.log));
分号
总是使用分号,因为隐式的代码嵌套会引发难以察觉的问题。当然我们更要从根本上来杜绝这些问题[1] 。以下几个示例展示了缺少分号的危害:
- // 1.
- MyClass.prototype.myMethod = function() {
- return 42;
- } // No semicolon here.
- (function() {
- // Some initialization code wrapped in a function to create a scope for locals.
- })();
- var x = {
- ‘i‘: 1,
- ‘j‘: 2
- } // No semicolon here.
- // 2. Trying to do one thing on Internet Explorer and another on Firefox.
- // I know you‘d never write code like this, but throw me a bone.
- [ffVersion, ieVersion][isIE]();
- var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
- // 3. conditional execution a la bash
- -1 == resultOfOperation() || die();
So what happens?
- JavaScript 错误 —— 首先返回 42 的那个 function 被第二个 function 当中参数传入调用,接着数字 42 也被“调用”而导致出错。
- 八成你会得到 ‘no such property in undefined’ 的错误提示,因为在真实环境中的调用是这个样子:
x[ffVersion, ieVersion][isIE]()
. die
总是被调用。因为数组减 1 的结果是NaN
,它不等于任何东西(无论resultOfOperation
是否返回NaN
)。所以最终的结果是die()
执行完所获得值将赋给THINGS_TO_EAT
.
Why?
JavaScript 中语句要以分号结束,否则它将会继续执行下去,不管换不换行。以上的每一个示例中,函数声明或对象或数组,都变成了在一句语句体内。要知道闭合圆括号并不代表语句结束,JavaScript 不会终结语句,除非它的下一个 token 是一个中缀符[2] 或者是圆括号操作符。
这真是让人大吃一惊,所以乖乖地给语句末加上分号吧。
澄清:分号与函数
分号需要用在表达式的结尾,而并非函数声明的结尾。区分它们最好的例子是:
- var foo = function() {
- return true;
- }; // semicolon here.
- function foo() {
- return true;
- } // no semicolon here.
嵌套函数
嵌套函数是非常有用的,比如用在持续创建和隐藏辅助函数的任务中。你可以非常自由随意地使用它们。
语句块内的函数声明
切勿在语句块内声明函数,在 ECMAScript 5 的严格模式下,这是不合法的。函数声明应该在定义域的顶层。但在语句块内可将函数申明转化为函数表达式赋值给变量。
不推荐
- if (x) {
- function foo() {}
- }
推荐
- if (x) {
- var foo = function() {};
- }
异常
基本上你无法避免出现异常,特别是在做大型开发时(使用应用开发框架等等)。
在没有自定义异常的情况下,从有返回值的函数中返回错误信息一定非常的棘手,更别提多不优雅了。不好的解决方案包括了传第一个引用类型来接纳错误信息,或总是返回一个对象列表,其中包含着可能的错误对象。以上方式基本上是比较简陋的异常处理方式。适时可做自定义异常处理。
在复杂的环境中,你可以考虑抛出对象而不仅仅是字符串(默认的抛出值)。
- if(name === undefined) {
- throw {
- name: ‘System Error‘,
- message: ‘A name should always be specified!‘
- }
- }
标准特性
总是优先考虑使用标准特性。为了最大限度地保证扩展性与兼容性,总是首选标准的特性,而不是非标准的特性(例如:首选 string.charAt(3)
而不是 string[3]
;首选 DOM 的操作方法来获得元素引用,而不是某一应用特定的快捷方法)。
简易的原型继承
如果你想在 JavaScript 中继承你的对象,请遵循一个简易的模式来创建此继承。如果你预计你会遇上复杂对象的继承,那可以考虑采用一个继承库,比如 Proto.js by Axel Rauschmayer.
简易继承请用以下方式:
- (function(log){
- ‘use strict‘;
- // Constructor function
- function Apple(name) {
- this.name = name;
- }
- // Defining a method of apple
- Apple.prototype.eat = function() {
- log(‘Eating ‘ + this.name);
- };
- // Constructor function
- function GrannySmithApple() {
- // Invoking parent constructor
- Apple.prototype.constructor.call(this, ‘Granny Smith‘);
- }
- // Set parent prototype while creating a copy with Object.create
- GrannySmithApple.prototype = Object.create(Apple.prototype);
- // Set constructor to the sub type, otherwise points to Apple
- GrannySmithApple.prototype.constructor = GrannySmithApple;
- // Calling a super method
- GrannySmithApple.prototype.eat = function() {
- // Be sure to apply it onto our current object with call(this)
- Apple.prototype.eat.call(this);
- log(‘Poor Grany Smith‘);
- };
- // Instantiation
- var apple = new Apple(‘Test Apple‘);
- var grannyApple = new GrannySmithApple();
- log(apple.name); // Test Apple
- log(grannyApple.name); // Granny Smith
- // Instance checks
- log(apple instanceof Apple); // true
- log(apple instanceof GrannySmithApple); // false
- log(grannyApple instanceof Apple); // true
- log(grannyApple instanceof GrannySmithApple); // true
- // Calling method that calls super method
- grannyApple.eat(); // Eating Granny Smith\nPoor Grany Smith
- }(window.console.log));
使用闭包
闭包的创建也许是 JS 最有用也是最易被忽略的能力了。关于闭包如何工作的合理解释。
切勿在循环中创建函数
在简单的循环语句中加入函数是非常容易形成闭包而带来隐患的。下面的例子就是一个典型的陷阱:
不推荐
- (function(log, w){
- ‘use strict‘;
- // numbers and i is defined in the current function closure
- var numbers = [1, 2, 3],
- i;
- for(i = 0; i < numbers.length; i++) {
- w.setTimeout(function() {
- // At the moment when this gets executed the i variable, coming from the outer function scope
- // is set to 3 and the current program is alerting the message 3 times
- // ‘Index 3 with number undefined
- // If you understand closures in javascript you know how to deal with those cases
- // It‘s best to just avoid functions / new closures in loops as this prevents those issues
- w.alert(‘Index ‘ + i + ‘ with number ‘ + numbers[i]);
- }, 0);
- }
- }(window.console.log, window以上是关于JavaScript编码规范的主要内容,如果未能解决你的问题,请参考以下文章