JavaScript编码规则

技术文档网 2021-04-16

JavaScript编码规则

目的:改善协作编码、代码质量。

var

声明变量必须用var。

防止变量变为全局变量,污染全局环境。

常量

基本类型number、string、boolean是常量值。对象更应该是常量值。

命名

NAMES_LIKE_THIS

全部大写暗示着常量,相当于@const NAMES_LIKE_THIS。

/**
 * Request timeout in milliseconds.
 * @type {number}
 */
goog.example.TIMEOUT_IN_MILLISECONDS = 60;

@const声明常量指针

@const声明的变量or属性,不可变。

@const声明的方法,不能被子类重载。

@const声明的构造函数,不能被继承。(类似于java的final)

@const和const

一致的,只不过IE不支持const,所以不要使用const来声明常量。

封号

语句末尾必须加封号。

防止意外错误。

函数和封号

函数表达式末尾需要加封号,函数声明末尾不需要加封号。

var foo = function() {
  return true;
};

function foo() {
  return true;
}

-1 == resultOfOperation(); || die();

易错

var x = {
  'i': 1,
  'j': 2
}

(function() {

})()

嵌套函数

很好,很强大。

代码块中声明函数

虽然大多数脚本引擎支持,但是ECMAScript标准不支持,ECMAScript标准支持在脚本or函数中声明函数;

<!-- 不推荐 -->
if (x) {
  function foo() {}
}
<!-- 推荐 -->
if (x) {
  var foo = function() {};
}

异常

不需要自定义异常。

注意异步函数调用后的异常捕获。

标准API

尽量使用标准API,利于移植(portability)、兼容(compatibility)。

<!-- 不推荐 -->
string[3]
<!-- 推荐 -->
string.charAt(3)

封装基本类型为对象

禁止。因为会引起混乱。

var x = new Boolean(false);
if (x) {
  console.log('hi'); // 会运行,跟预期不一,因为x是对象,为true。
}

多级原型继承

不推荐。因为容易出错,建议用第三方API,如:goog.inherits()。

function D() {
  goog.base(this)
}
goog.inherits(D, B);

D.prototype.method = function() {
  ...
};

类方法、属性定义

  • 方法定义:依附于原型对象(prototype)。
  • 属性定义:定义在构造函数中。
/* 方法定义 */
Foo.prototype.bar = function() {

};
/* 属性定义 */
function Foo() {
  this.bar = value;
}

注:不要给new创建的对象添加属性、方法,因为会降低性能。

Current JavaScript engines optimize based on the "shape" of an object, adding a property to an object (including overriding a value set on the prototype) changes the shape and can degrade performance. 详见:https://developers.google.com/v8/design#prop_access

delete

避免在对象上使用delete,因为删除对象属性(更改对象属性个数)比给对象属性重新赋值慢。

/* 不推荐 */
Foo.prototype.dispose = function() {
  this.property_ = null;
};
/* 推荐 */
Foo.prototype.dispose = function() {
  delete this.property_;
};

注:特殊情况除外,比如:if (key in obj)

closures

小心使用。

/* 不推荐 */
function foo(element, a, b) {
  element.onclick = function() { /* uses a and b */ };
}
/* 推荐 */
function foo(element, a, b) {
  element.onclick = bar(a, b);
}

function bar(a, b) {
  return function() { /* uses a and b */ };
}

不推荐:element.onclick事件触发后,牵涉到function() {}function foo(element, a, b) {},作用域foo始终无法被垃圾收集。且是循环引用,a memory leak。

推荐:element.onclick事件触发后,只牵涉到function() {}

注:function() {} 就称为closure。

eval

仅用于 code loaders 和 REPL (Read–eval–print loop)

危险,一旦用户输入的字符串被执行。

关于RPC,可以使用JSON格式,且用JSON库(douglascrockford/JSON-js)来解析。

/* 假设服务器返回JSON */
{
  "name": "Alice",
  "id": 31502,
  "email": "looking_glass@example.com"
}
/* 不推荐 */
var userInfo = eval(feed);
var email = userInfo['email'];
/* 推荐 */
var userInfo = JSON.parse(feed);
var email = userInfo['email'];

with

不要使用with。

因为对象中的属性会跟局部变量相冲突,导致语义的混乱。

var foo = {
  x: 1;
};
with (foo) {
  var x = 3; // 此处本地变量x会被解析成foo.x,意味着:1.本地变量x没了;2.foo.x的值变为了3,此语句变成了一个setter;
  return x;
}

this

仅在构造函数、对象方法、closure方法中使用。

因为this太灵活,使用中很容易出错,不同语义环境下会指向不同的对象,以下是可能会指向的对象:

  • 全局对象
  • 调用eval的对象
  • 触发事件的节点DOM对象
  • 使用构造函数新创建的对象
  • 使用call()或apply()方法的对象

for-in

仅用于object/map/hash。

不要用于Array,因为for-in是用来循环展现对象(及原型链)的key属性值的,如果某数组被附属了key-value,或者数组原型被扩展了,那么数组的key属性、原型对象上的属性都会被循环呈现;

数组循环使用for-var,根据数组元素length来循环展现。

function printObject(arr) {
  for (var key in arr) {
    print(arr[key]);
  }
}
function printArray(arr) {
  var l = arr.length;
  for (var i = 0; i < l; i++) {
    print(arr[i]);
  }
}
var a = [0,1,2,3];
a.buhu = 'wine';
printObject(a);  // 本意是打印数组a中的数字,结果wine也被打印出来;
printArray(a);  // 只会打印出0123;

数组

仅通过数字index来设置、引用数组元素。非数字index仅用于扩展对象。

var a = [1, 2, 3];
/* 不推荐 */
a.buhu = 'wine';
/* 推荐 */
a[0] = 4;

多行字符串

不要使用“多行字符串”语法,因为ECMAScript不支持,且符号\前后的空白字符容易引起错误。

/* 不推荐 */
var myString = 'A rather long string of English text, an error message \
                actually that just keeps going and going -- an error \
                message to make the Energizer bunny blush (right through \
                those Schwarzenegger shades)! Where was I? Oh yes, \
                you\'ve got an error and all the extraneous whitespace is \
                just gravy.  Have a nice day.';
/* 推荐 */
var myString = 'A rather long string of English text, an error message ' +
    'actually that just keeps going and going -- an error ' +
    'message to make the Energizer bunny blush (right through ' +
    'those Schwarzenegger shades)! Where was I? Oh yes, ' +
    'you\'ve got an error and all the extraneous whitespace is ' +
    'just gravy.  Have a nice day.';

数组和对象字面值

使用字面值创建数组或对象,而非使用构造函数。

/* 不推荐 */
var a1 = new Array(1, 2, 3);

var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;
/* 推荐 */
var a = [x1, x2, x3];

var o2 = {
  a: 0,
  b: 1,
  c: 2,
  'strange key': 3
};

修改内建对象原型

禁止修改内建对象的prototype属性:Object.prototype、Array.prototype、Function.prototype

IE条件注释

不要使用IE条件注释,因为会改变JavaScript语法树,妨碍自动化工具。

/* 不推荐 */
var f = function () {
    /*@cc_on if (@_jscript) { return 2* @*/  3; /*@ } @*/
};

命名

  • 文件名:filenameslikethis.js、filenames-like-this.js
  • 变量名:variableNamesLikeThis
  • 函数名:functionNamesLikeThis
  • 常量名:CONSTANT_VALUES_LIKE_THIS
  • 类 名:ClassNamesLikeThis
  • 枚举名:EnumNamesLikeThis
  • 方法名:methodNamesLikeThis
  • 命名空间名:foo.namespaceNamesLikeThis.bar

文件名

  • 全部小写,避免在区分大小写的系统上引起混乱。
  • 以.js结尾。
  • 标点符号只允许连字符号-或下划线_,建议使用连字符号。

封装性命名

  • 私有(private)属性和方法名以下划线结尾:privateName_
  • 保护(protected)属性和方法名同公共(public)属性和方法名:protectedName

参数命名

  • 可选参数名以opt_开头
  • 可变参数的函数,最后一个参数名为var_args,函数体内不要使用var_args参数,使用arguments数组来引用参数。
  • 可选、可变参数可以使用@param来注解。

Getter方法名 Setters方法名

  • getFoo()、setFoo(value)、isFoo()(获取布尔值时使用)
  • Getter方法不应该对象状态
  • ECMAScript 5不鼓励对属性使用Getter和Setter方法。

命名空间

  • 作用
    • 防止命名冲突
    • 利于代码模块化
  • JavaScript不支持包or命名空间,可用约定来伪造命名空间,一般使用前缀对象名来建立命名空间。
  • 好多JavaScript库提供建立命名空间的API,保持一致性即可。
  • 尊重命名空间的所有者:要在父命名空间下建立子命名空间的话,需要告知父命名空间的所有者。
  • 尽量不要影响已有命名空间,新建独立的命名空间即可。
/* 假设项目or库名为sloth,建立如下sloth命名空间 */
var sloth = {};
sloth.sleep = function() {
  ...
};

别名

给长命名建立本地别名

既然是本地别名,就需要包含在函数中,禁止创建全局别名。

不要给命名空间建立别名。(可以使用goog-scope来建立)

/**
 * @constructor
 */
some.long.namespace.MyClass = function() {
};

/**
 * @param {some.long.namespace.MyClass} a
 */
some.long.namespace.MyClass.staticHelper = function(a) {
  ...
};
/* 不推荐 */
myapp.main = function() {
  var namespace = some.long.namespace;
  namespace.MyClass.staticHelper(new namespace.MyClass());
};
/* 推荐 */
myapp.main = function() {
  var MyClass = some.long.namespace.MyClass;
  var staticHelper = some.long.namespace.MyClass.staticHelper;
  staticHelper(new MyClass());
};

避免访问别名属性,除非别名是枚举类型(enum)。

/** @enum {string} 枚举类型 */
some.long.namespace.Fruit = {
  APPLE: 'a',
  BANANA: 'b'
};

myapp.main = function() {
  var Fruit = some.long.namespace.Fruit;
  switch (fruit) {
    case Fruit.APPLE:
      ...
    case Fruit.BANANA:
      ...
  }
};

自定义toString()方法

可以自定义toString()方法来字符串化对象,但是必须满足2个条件

  1. 必须成功
  2. 无副作用(side-effects)

For example, if toString() calls a method that does an assert, assert might try to output the name of the object in which it failed, which of course requires calling toString().

明确作用域(Explicit scope)

增加可移植性。

For example, don't rely on window being in the scope chain. You might want to use your function in another application for which window is not the content window.

缩进

使用2个空格,不使用tab

if (something) {
  // ...
} else {
  // ...
}

对象、数组初始化

/* 单行 */
var arr = [1, 2, 3];  // No space after [ or before ].
var obj = {a: 1, b: 2, c: 3};  // No space after { or before }.

/* 多行 */
// Object initializer.
var inset = {
  top: 10,
  right: 20,
  bottom: 15,
  left: 12
};

// Array initializer.
this.rows_ = [
  '"Slartibartfast" <fjordmaster@magrathea.com>',
  '"Zaphod Beeblebrox" <theprez@universe.gov>',
  '"Ford Prefect" <ford@theguide.com>',
  '"Arthur Dent" <has.no.tea@gmail.com>',
  '"Marvin the Paranoid Android" <marv@googlemail.com>',
  'the.mice@magrathea.com'
];

// Used in a method call.
goog.dom.createDom(goog.dom.TagName.DIV, {
  id: 'foo',
  className: 'some-css-class',
  style: 'display:none'
}, 'Hello, world!');

/* 推荐(非对齐初始化) */
CORRECT_Object.prototype = {
  a: 0,
  b: 1,
  lengthyName: 2
};
/* 不推荐 */
WRONG_Object.prototype = {
  a          : 0,
  b          : 1,
  lengthyName: 2
};

函数参数

尽量写在同一行上,如果一行超过80字符(80-column),则分行写:

  • 节省空间:每行占80个字符
  • 增加可读性:一行一个参数
  • 缩进:4个空白字符,或者对齐圆括号
// Four-space, wrap at 80, low on space.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // ...
};
// Four-space, one argument per line, emphasizes each argument.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne,
    veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy,
    artichokeDescriptorAdapterIterator) {
  // ...
};
// Parenthesis-aligned, one argument per line, Emphasizes each argument.
function bar(veryDescriptiveArgumentNumberOne,
             veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy,
             artichokeDescriptorAdapterIterator) {
  // ...
}

匿名函数作为参数

为了方便阅读匿名函数,匿名函数体靠左边缩进2空格,或者对齐function关键词左边缩进2空格。

prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

var names = prefix.something.myExcellentMapFunction(
    verboselyNamedCollectionOfItems,
    function(item) {
      return item.name;
    });

空白行

使用空白行来分组逻辑相关的代码。

doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);

nowDoSomethingWith(y);

andNowWith(z);

二元、三元运算符(Binary and Ternary Operators)

运算符始终放在开始行。

// All on one line if it will fit.
var x = a ? b : c;

// Indentation +4 is OK.
var y = a ?
    longButSimpleOperandB : longButSimpleOperandC;

// Indenting to the line position of the first operand is also OK.
var z = a ?
        moreComplicatedB :
        moreComplicatedC;

// the dot operator
var x = foo.bar().
    doSomething().
    doSomethingElse();

圆括号(Parentheses)

  • 不要滥用。
  • 一元操作符(delete, typeof, void)不要使用圆括号。
  • 关键词(return, throw, case, in, new)后不要使用圆括号。

字符串引号

使用单引号,方便创建HTML字符串。

封装性(public、protected、private)

使用JSDoc注解来表明封装性。

@private注解

  • 全局变量和函数:只能在同一文件中被访问。
  • 构造函数:同一文件中被访问、被类静态成员访问、被实例化成员访问。
  • 属性:同一文件中被访问、类静态方法访问、类实例方法访问,不能被子类访问及重载。

@protected注解

  • 不要注解全局变量、函数、构造函数。
  • 属性:同一文件中被访问、类静态方法访问、类实例方法访问,能被子类访问及重载。
// File 1.

/** @constructor */
AA_PublicClass = function() {
  /** @private */
  this.privateProp_ = 2;

  /** @protected */
  this.protectedProp = 4;
};

/** @private */
AA_PublicClass.staticPrivateProp_ = 1;

/** @protected */
AA_PublicClass.staticProtectedProp = 31;

/** @private */
AA_PublicClass.prototype.privateMethod_ = function() {};

/** @protected */
AA_PublicClass.prototype.protectedMethod = function() {};

// File 2.

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_PublicClass.prototype.method = function() {
  // Legal accesses of these two properties.
  return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
};

// File 3.

/**
 * @constructor
 * @extends {AA_PublicClass}
 */
AA_SubClass = function() {
  // Legal access of a protected static property.
  AA_PublicClass.staticProtectedProp = this.method();
};
goog.inherits(AA_SubClass, AA_PublicClass);

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_SubClass.prototype.method = function() {
  // Legal access of a protected instance property.
  return this.protectedProp;
};

类型

变量类型需用JSDoc注解来明确说明。

  • Boolean
    • true、false、Boolean(0)
  • Number
    • 1、1.0、-5、1e5、Math.PI
  • String
    • 'Hello'、new String('World')new String(42)
  • Array
    • ['foo', 0.3, null]、[['one', 'two', 'three'], ['foo', 'bar']]
  • Object
    • {foo: 'abc', bar: 123, baz: null}、var obj = {}; obj[1] = 'bar';
  • Function
    • function(x, y) { return x * y; }
  • Date
    • new Date()
  • RegExp
    • new RegExp('hello')、/world/g
  • null
  • undefined
  • void
    • function f() { return; }
  • 自定义类型
    • function SomeClass() {} new SomeClass();
  • 枚举
    • var MyEnum = { BLUE: '#0000dd', RED: '#dd0000' };
  • Element
    • document.createElement('div')
  • Node
    • document.body.firstChild
  • HTMLInputElement
    • htmlDocument.getElementsByTagName('input')[0]

类型转换(type cast)

在不能精确推导出类型的地方,使用JSDoc注解来明确类型。

/** @type {number} */

可空(Nullable )、可选(Optional)、未定义(undefined)

使用JSDoc注解来声明参数可空(Nullable )、可选(Optional)

  • @param {Object} 可null,不可undefined
  • @param {!Object} 不可null,不可undefined
  • @param {Object=} 可选,可为null,可为undefined
  • @param {!Object=} 不可选,不可为null,可为undefined
/* 注解:参数value可空 */
/**
 * Some class, initialized with a value.
 * @param {Object} value Some value.
 * @constructor
 */
function MyClass(value) {
  /**
   * Some value.
   * @type {Object}
   * @private
   */
  this.myValue_ = value;
}
/* 注解:参数value不可空。编译器可检测空值value,并给出警告 */
/**
 * Some class, initialized with a non-null value.
 * @param {!Object} value Some value.
 * @constructor
 */
function MyClass(value) {
  /**
   * Some value.
   * @type {!Object}
   * @private
   */
  this.myValue_ = value;
}

/* 注解:参数opt_value可选。可能值为:Object, null, undefined。 */
/**
 * Some class, initialized with an optional value.
 * @param {Object=} opt_value Some value (optional).
 * @constructor
 */
function MyClass(opt_value) {
  /**
   * Some value.
   * @type {Object|undefined}
   * @private
   */
  this.myValue_ = opt_value;
}

/**
 * Takes four arguments, two of which are nullable, and two of which are
 * optional.
 * @param {!Object} nonNull Mandatory (must not be undefined), must not be null.
 * @param {Object} mayBeNull Mandatory (must not be undefined), may be null.
 * @param {!Object=} opt_nonNull Optional (may be undefined), but if present,
 *     must not be null!
 * @param {Object=} opt_mayBeNull Optional (may be undefined), may be null.
 */
function strangeButTrue(nonNull, mayBeNull, opt_nonNull, opt_mayBeNull) {
  // ...
};

注释

使用JSDoc,语法基于JavaDoc,好多工具从注释中提取元数据来做代码验证和优化。

文件、类、方法、属性、函数、参数、返回值都应该有详细的文字描述。

行内(inline)注释用//,比如:this.myValue_ = opt_value; // this is inline comments.

文件注释

简述文件内容、依赖、兼容性等。版权公告和作者信息是可选的。

/**
 * @fileoverview Description of file, its uses and information
 * about its dependencies.
 */

类注释

描述类和构造函数。

/**
 * Class making something fun and easy.
 * @param {string} arg1 An argument that makes this more interesting.
 * @param {Array.<number>} arg2 List of numbers to be processed.
 * @constructor
 * @extends {goog.Disposable}
 */
project.MyClass = function(arg1, arg2) {
  // ...
};
goog.inherits(project.MyClass, goog.Disposable);

方法、函数注释

描述方法、参数、返回值。

/**
 * Operates on an instance of MyClass and returns something.
 * @param {project.MyClass} obj Instance of MyClass which leads to a long
 *     comment that needs to be wrapped to two lines.
 * @return {boolean} Whether something occurred.
 */
function PR_someMethod(obj) {
  // ...
}

属性注释

/** @constructor */
project.MyClass = function() {
  /**
   * Maximum number of things per pane.
   * @type {number}
   */
  this.someProperty = 4;
}

小技巧

布尔表达式中的值

  • null、undefined、''、0 被认为false
  • '0'、[]、{} 被认为true
/* 不推荐 */
if (y != null && y != '') {
/* 推荐 */
if (y) {

条件运算符

  • null、undefined、''、0 被认为false
  • '0'、[]、{} 被认为true
/* 不推荐 */
if (val) {
  return foo();
} else {
  return bar();
}
/* 推荐 */
return val ? foo() : bar();

/* 推荐 */
var html = '<input type="checkbox"' +
    (isChecked ? ' checked' : '') +
    (isEnabled ? '' : ' disabled') +
    ' name="foo">';

|| 和 &&

布尔或运算符||,也可称为默认运算符。

/* 不推荐 */
function foo(opt_win) {
  var win;
  if (opt_win) {
    win = opt_win;
  } else {
    win = window;
  }
  // ...
}
/* 推荐 */
function foo(opt_win) {
  var win = opt_win || window;
  // ...
}

布尔与运算符&&。

/* 不推荐 */
if (node) {
  if (node.kids) {
    if (node.kids[index]) {
      foo(node.kids[index]);
    }
  }
}
/* 推荐 */
if (node && node.kids && node.kids[index]) {
  foo(node.kids[index]);
}

var kid = node && node.kids && node.kids[index];
if (kid) {
  foo(kid);
}

node && node.kids && node.kids[index] && foo(node.kids[index]);

遍历节点

/* 不推荐 */
var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}
/* 推荐 */
var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
  doSomething(paragraph);
}

通过firstChild和nextSibling来遍历节点

var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}

参考引用

相关文章

  1. webpack相关知识

    能够处理JS文件的互相依赖关系 能够处理JS的兼容问题 安装 全局安装 npm install webpack -g 项目安装 npm install webpack --save-

  2. Javascript基础和ES6

    HTML的基础事件 onmouseover、onmouseout表示JS事件的鼠标移入鼠标移出 document.getElementById('id') JS中选择HTML的ID元素&lt;in

  3. JavaScript编码规则

    JavaScript编码规则 目的:改善协作编码、代码质量。 var 声明变量必须用var。 防止变量变为全局变量,污染全局环境。 常量 基本类型number、string、boolean是常量值。对

  4. JavaScript获取和设置CSS属性

    获取样式 元素对象的宽高位置距离等属性 如offsetWidht、cilentWidht、scrollWidth…… let oWidth=obj.offsetWidth; 注意: 只能获取属

  5. javascript作用域、上下文、this和闭包详解

    词法作用域lexical scope 定义在词法阶段的作用域。词法作用域是变量和语句块在定义时所处的位置决定的。 全局 块级:在{}之内是一个块级作用域(ES6之前没有块级作用于只有函数内的局部作用

随机推荐

  1. webpack相关知识

    能够处理JS文件的互相依赖关系 能够处理JS的兼容问题 安装 全局安装 npm install webpack -g 项目安装 npm install webpack --save-

  2. Javascript基础和ES6

    HTML的基础事件 onmouseover、onmouseout表示JS事件的鼠标移入鼠标移出 document.getElementById('id') JS中选择HTML的ID元素&lt;in

  3. JavaScript编码规则

    JavaScript编码规则 目的:改善协作编码、代码质量。 var 声明变量必须用var。 防止变量变为全局变量,污染全局环境。 常量 基本类型number、string、boolean是常量值。对

  4. JavaScript获取和设置CSS属性

    获取样式 元素对象的宽高位置距离等属性 如offsetWidht、cilentWidht、scrollWidth…… let oWidth=obj.offsetWidth; 注意: 只能获取属

  5. javascript作用域、上下文、this和闭包详解

    词法作用域lexical scope 定义在词法阶段的作用域。词法作用域是变量和语句块在定义时所处的位置决定的。 全局 块级:在{}之内是一个块级作用域(ES6之前没有块级作用于只有函数内的局部作用