开发规范:Javascript开发规范

  1. 类型
  2. 对象
  3. 数组
  4. 字符串
  5. 函数
  6. 属性
  7. 变量
  8. 提升
  9. 比较运算符 & 等号
  10. 注释
  11. 空白
  12. 逗号
  13. 分号
  14. 类型转化
  15. 命名规则
  16. 存取器
  17. 构造函数
  18. 事件
  19. 模块
  20. jQuery
  21. ECMAScript 5 兼容性
  22. 测试
  23. 性能
  24. 资源
  25. 谁在使用


类     型

  • 原始值: 存取直接作用于它自身。
  • string
  • number
  • boolean
  • null
  • undefined
var  foo = 1;
var  bar = foo;
bar = 9;
console.log(foo, bar);  // => 1, 9
  • 复杂类型: 存取时作用于它自身值的引用。
  • object
  • array
  • function
var  foo = [1, 2];
var  bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]);  // => 9, 9


对象

  • 使用直接量创建对象。
// bad
var  item =  new  Object();
// good
var  item = {};
  • 不要使用保留字作为键名,它们在 IE8 下不工作。
// bad
var  superman = {
     default : { clark:  'kent'  },
     private:  true
};
 
// good
var  superman = {
     defaults: { clark:  'kent'  },
     hidden:  true
};
  • 使用同义词替换需要使用的保留字。
// bad
var  superman = {
     class:  'alien'
};
 
// bad
var  superman = {
     klass:  'alien'
};
 
// good
var  superman = {
     type:  'alien'
};


数组

  • 使用直接量创建数组。
// bad
var  items =  new  Array();
 
// good
var  items = [];
  • 向数组增加元素时使用 Array#push 来替代直接赋值。
var  someStack = [];
 
// bad
someStack[someStack.length] =  'abracadabra' ;
 
// good
someStack.push( 'abracadabra' );
  • 当你需要拷贝数组时,使用 Array#slice。jsPerf
var  len = items.length;
var  itemsCopy = [];
var  i;
 
// bad
for  (i = 0; i < len; i++) {
     itemsCopy[i] = items[i];
}
 
// good
itemsCopy = items.slice();
  • 使用 Array#slice 将类数组对象转换成数组。
function  trigger() {
     var  args = Array.prototype.slice.call(arguments);
     ...
}


字符串

  • 使用单引号 '' 包裹字符串。
// bad
var  name =  "Bob Parr" ;
 
 
// good
var  name =  'Bob Parr' ;
 
// bad
var  fullName =  "Bob "  this .lastName;
 
// good
var  fullName =  'Bob '  this .lastName;
  • 超过 100 个字符的字符串应该使用连接符写成多行。
  • 注:若过度使用,通过连接符连接的长字符串可能会影响性能。jsPerf
// bad
var  errorMessage =  'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.' ;
 
// bad
var  errorMessage =  'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.' ;
 
// good
var  errorMessage =  'This is a super long error that was thrown because '  +
   'of Batman. When you stop to think about how Batman had anything to do '  +
   'with this, you would get nowhere fast.' ;
  • 程序化生成的字符串使用 Array#join 连接而不是使用连接符。尤其是 IE 下:jsPerf.(http://jsperf.com/ya-string-concat)
var  items;
var  messages;
var  length;
var  i;
 
messages = [{
     state:  'success' ,
     message:  'This one worked.'
}, {
     state:  'success' ,
     message:  'This one worked as well.'
}, {
     state:  'error' ,
     message:  'This one did not work.'
}];
 
length = messages.length;
 
// bad
function  inbox(messages) {
     items =  '<ul>' ;
 
     for  (i = 0; i < length; i++) {
         items +=  '<li>'  + messages[i].message +  '</li>' ;
     }
     return  items +  '</ul>' ;
}
 
// good
function  inbox(messages) {
     items = [];
 
     for  (i = 0; i < length; i++) {
         // use direct assignment in this case because we're micro-optimizing.
         items[i] = '<li> ' + messages[i].message + ' </li> ';
     }
 
     return ' <ul> ' + items.join(' ') + ' </ul>';
}


函数

  • 函数表达式
// 匿名函数表达式
var  anonymous =  function () {
     return  true ;
};
 
// 命名函数表达式
var  named =  function  named() {
     return  true ;
};
 
// 立即调用的函数表达式(IIFE)
( function  () {
     console.log( 'Welcome to the Internet. Please follow me.' );
}());
  • 永远不要在一个非函数代码块(if、while 等)中声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但它们的解析表现不一致。
  • 注: ECMA-262 把  定义为一组语句。函数声明不是语句。阅读对 ECMA-262 这个问题的说明。(http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=97)
// bad
if  (currentUser) {
     function  test() {
         console.log( 'Nope.' );
     }
}
 
// good
var  test;
if  (currentUser) {
     test =  function  test() {
         console.log( 'Yup.' );
     };
}
  • 永远不要把参数命名为 arguments。这将取代函数作用域内的 arguments 对象。
// bad
function  nope(name, options, arguments) {
     // ...stuff...
}
 
// good
function  yup(name, options, args) {
     // ...stuff...
}


属性

  • 使用 . 来访问对象的属性。
var  luke = {
     jedi:  true ,
     age: 28
};
 
// bad
var  isJedi = luke[ 'jedi' ];
 
// good
var  isJedi = luke.jedi;
  • 当通过变量访问属性时使用中括号 []
var  luke = {
     jedi:  true ,
     age: 28
};
 
function  getProp(prop) {
     return  luke[prop];
}
 
var  isJedi = getProp( 'jedi' );


变量

  • 总是使用 var 来声明变量。不这么做将导致产生全局变量。我们要避免污染全局命名空间。
// bad
superPower =  new  SuperPower();
 
// good
var  superPower =  new  SuperPower();
  • 使用 var 声明每一个变量。
    这样做的好处是增加新变量将变的更加容易,而且你永远不用再担心调换错 ; 跟 ,
// bad
var  items = getItems(),
     goSportsTeam =  true ,
     dragonball =  'z' ;
 
// bad
// (跟上面的代码比较一下,看看哪里错了)
var  items = getItems(),
     goSportsTeam =  true ;
     dragonball =  'z' ;
 
// good
var  items = getItems();
var  goSportsTeam =  true ;
var  dragonball =  'z' ;
  • 最后再声明未赋值的变量。当你需要引用前面的变量赋值时这将变的很有用。
// bad
var  i, len, dragonball,
     items = getItems(),
     goSportsTeam =  true ;
 
// bad
var  i;
var  items = getItems();
var  dragonball;
var  goSportsTeam =  true ;
var  len;
 
// good
var  items = getItems();
var  goSportsTeam =  true ;
var  dragonball;
var  length;
var  i;
  • 在作用域顶部声明变量。这将帮你避免变量声明提升相关的问题。
// bad
function  () {
     test();
     console.log( 'doing stuff..' );
 
     //..other stuff..
 
     var  name = getName();
 
     if  (name ===  'test' ) {
         return  false ;
     }
 
     return  name;
}
 
// good
function  () {
     var  name = getName();
 
     test();
     console.log( 'doing stuff..' );
 
     //..other stuff..
 
     if  (name ===  'test' ) {
         return  false ;
     }
 
     return  name;
}
 
// bad - 不必要的函数调用
function  () {
     var  name = getName();
 
     if  (!arguments.length) {
         return  false ;
     }
 
     this .setFirstName(name);
 
     return  true ;
}
 
// good
function  () {
     var  name;
 
     if  (!arguments.length) {
         return  false ;
     }
 
     name = getName();
     this .setFirstName(name);
 
     return  true ;
}


提升

  • 变量声明会提升至作用域顶部,但赋值不会。
// 我们知道这样不能正常工作(假设这里没有名为 notDefined 的全局变量)
function  example() {
     console.log(notDefined);  // => throws a ReferenceError
}
 
// 但由于变量声明提升的原因,在一个变量引用后再创建它的变量声明将可以正常工作。
// 注:变量赋值为 `true` 不会提升。
function  example() {
     console.log(declaredButNotAssigned);  // => undefined
     var  declaredButNotAssigned =  true ;
}
 
// 解释器会把变量声明提升到作用域顶部,意味着我们的例子将被重写成:
function  example() {
     var  declaredButNotAssigned;
     console.log(declaredButNotAssigned);  // => undefined
     declaredButNotAssigned =  true ;
}
  • 匿名函数表达式会提升它们的变量名,但不会提升函数的赋值。
function  example() {
     console.log(anonymous);  // => undefined
 
     anonymous();  // => TypeError anonymous is not a function
 
     var  anonymous =  function  () {
         console.log( 'anonymous function expression' );
     };
}
  • 命名函数表达式会提升变量名,但不会提升函数名或函数体。
function  example() {
     console.log(named);  // => undefined
 
     named();  // => TypeError named is not a function
 
     superPower();  // => ReferenceError superPower is not defined
 
     var  named =  function  superPower() {
         console.log( 'Flying' );
     };
}
 
// 当函数名跟变量名一样时,表现也是如此。
function  example() {
     console.log(named);  // => undefined
 
     named();  // => TypeError named is not a function
 
     var  named =  function  named() {
         console.log( 'named' );
     }
}
  • 函数声明提升它们的名字和函数体。
function  example() {
     superPower();  // => Flying
 
     function  superPower() {
         console.log( 'Flying' );
     }
}


比较运算符 & 等号

  • 优先使用 === 和 !== 而不是 == 和 !=.
  • 条件表达式例如 if 语句通过抽象方法 ToBoolean 强制计算它们的表达式并且总是遵守下面的规则:

    • 对象 被计算为 true
    • Undefined 被计算为 false
    • Null 被计算为 false
    • 布尔值 被计算为 布尔的值
    • 数字 如果是 +0、-0 或 NaN 被计算为 false,否则为 true
    • 字符串 如果是空字符串 '' 被计算为 false,否则为 true
if  ([0]) {
     // true
     // 一个数组就是一个对象,对象被计算为 true
}
  • 使用快捷方式。
// bad
if  (name !==  '' ) {
     // ...stuff...
}
 
// good
if  (name) {
     // ...stuff...
}
 
// bad
if  (collection.length > 0) {
     // ...stuff...
}
 
// good
if  (collection.length) {
     // ...stuff...
}
  • 了解更多信息在 Truth Equality and JavaScript by Angus Croll.(http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108)

  • 使用大括号包裹所有的多行代码块。
// bad
if  (test)
return  false ;
 
// good
if  (test)  return  false ;
 
// good
if  (test) {
     return  false ;
}
 
// bad
function  () {  return  false ; }
 
// good
function  () {
     return  false ;
}
  • 如果通过 if 和 else 使用多行代码块,把 else 放在 if 代码块关闭括号的同一行。
// bad
if  (test) {
     thing1();
     thing2();
}
else  {
     thing3();
}
 
// good
if  (test) {
     thing1();
     thing2();
else  {
     thing3();
}


注释

  • 使用 /** ... */ 作为多行注释。包含描述、指定所有参数和返回值的类型和值。
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function  make(tag) {
     // ...stuff...
 
 
     return  element;
}
 
// good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function  make(tag) {
 
 
     // ...stuff...
 
 
     return  element;
}
  • 使用 // 作为单行注释。在评论对象上面另起一行使用单行注释。在注释前插入空行。
// bad
var  active =  true ;   // is current tab
 
// good
// is current tab
var  active =  true ;
 
// bad
function  getType() {
     console.log( 'fetching type...' );
     // set the default type to 'no type'
     var  type =  this .type ||  'no type' ;
 
     return  type;
}
 
// good
function  getType() {
     console.log( 'fetching type...' );
 
     // set the default type to 'no type'
     var  type =  this .type ||  'no type' ;
 
     return  type;
}
  • 给注释增加 FIXME 或 TODO 的前缀可以帮助其他开发者快速了解这是一个需要复查的问题,或是给需要实现的功能提供一个解决方式。这将有别于常见的注释,因为它们是可操作的。使用 FIXME -- need to figure this out 或者 TODO -- need to implement

  • 使用 // FIXME: 标注问题。

function  Calculator() {
 
     // FIXME: shouldn't use a global here
     total = 0;
 
     return  this ;
}
  • 使用 // TODO: 标注问题的解决方式。
function  Calculator() {
 
     // TODO: total should be configurable by an options param
     this .total = 0;
  
     return  this ;
}


空白

  • 使用 2 个空格作为缩进(如果习惯了用Tab 可以在编辑器中把Tab设置为2个空格。
// bad
function  () {
∙∙∙∙ var  name;
}
 
// bad
function  () {
var  name;
}
 
// good
function  () {
∙∙ var  name;
}
  • 在大括号前放一个空格。
// bad
function  test(){
     console.log( 'test' );
}
 
// good
function  test() {
     console.log( 'test' );
}
 
// bad
dog.set( 'attr' ,{
     age:  '1 year' ,
     breed:  'Bernese Mountain Dog'
});
 
// good
dog.set( 'attr' , {
     age:  '1 year' ,
     breed:  'Bernese Mountain Dog'
});
  • 在控制语句(ifwhile 等)的小括号前放一个空格。在函数调用及声明中,不在函数的参数列表前加空格。
// bad
if (isJedi) {
     fight ();
}
 
// good
if  (isJedi) {
     fight();
}
 
// bad
function  fight () {
     console.log ( 'Swooosh!' );
}
 
// good
function  fight() {
     console.log( 'Swooosh!' );
}
  • 使用空格把运算符隔开。
// bad
var  x=y+5;
 
// good
var  x = y + 5;
  • 在文件末尾插入一个空行。
// bad
( function  (global) {
     // ...stuff...
})( this );
 
// bad
( function  (global) {
     // ...stuff...
})( this );↵
 
// good
( function  (global) {
     // ...stuff...
})( this );↵
  • 在使用长方法链时进行缩进。使用前面的点 . 强调这是方法调用而不是新语句。
// bad
$( '#items' ).find( '.selected' ).highlight().end().find( '.open' ).updateCount();
 
// bad
$( '#items' ).
     find( '.selected' ).
         highlight().
         end().
     find( '.open' ).
         updateCount();
 
// good
$( '#items' )
     .find( '.selected' )
         .highlight()
         .end()
     .find( '.open' )
         .updateCount();
 
// bad
var  leds = stage.selectAll( '.led' ).data(data).enter().append( 'svg:svg' ).classed( 'led' true )
     .attr( 'width' , (radius + margin) * 2).append( 'svg:g' )
     .attr( 'transform' 'translate('  + (radius + margin) +  ','  + (radius + margin) +  ')' )
     .call(tron.led);
 
// good
var  leds = stage.selectAll( '.led' )
         .data(data)
     .enter().append( 'svg:svg' )
         .classed( 'led' true )
         .attr( 'width' , (radius + margin) * 2)
     .append( 'svg:g' )
         .attr( 'transform' 'translate('  + (radius + margin) +  ','  + (radius + margin) +  ')' )
         .call(tron.led);
  • 在块末和新语句前插入空行。
// bad
if  (foo) {
     return  bar;
}
return  baz;
 
// good
if  (foo) {
     return  bar;
}
 
return  baz;
 
// bad
var  obj = {
     foo:  function  () {
},
bar:  function  () {
     }
};
return  obj;
 
// good
var  obj = {
     foo:  function  () {
     },
 
     bar:  function  () {
     }
};
 
return  obj;


逗号

  • 行首逗号: 不需要
// bad
var  story = [
     once
     , upon
     , aTime
];
 
// good
var  story = [
     once,
     upon,
     aTime
];
 
// bad
var  hero = {
     firstName:  'Bob'
     , lastName:  'Parr'
     , heroName:  'Mr. Incredible'
     , superPower:  'strength'
};
 
// good
var  hero = {
     firstName:  'Bob' ,
     lastName:  'Parr' ,
     heroName:  'Mr. Incredible' ,
     superPower:  'strength'
};
  • 额外的行末逗号:不需要。这样做会在 IE6/7 和 IE9 怪异模式下引起问题。同样,多余的逗号在某些 ES3 的实现里会增加数组的长度。在 ES5 中已经澄清了 (source):

> Edition 5 clarifies the fact that a trailing comma at the end of an ArrayInitialiser does not add to the length of the array. This is not a semantic change from Edition 3 but some implementations may have previously misinterpreted this.

// bad
var  hero = {
     firstName:  'Kevin' ,
     lastName:  'Flynn' ,
};
 
var  heroes = [
     'Batman' ,
     'Superman' ,
];
 
// good
var  hero = {
     firstName:  'Kevin' ,
     lastName:  'Flynn'
};
 
var  heroes = [
     'Batman' ,
     'Superman'
];


分号

  • 使用分号。
// bad
( function  () {
     var  name =  'Skywalker'
     return  name
})()
 
// good
( function  () {
     var  name =  'Skywalker' ;
     return  name;
})();
 
// good (防止函数在两个 IIFE 合并时被当成一个参数
;( function  () {
     var  name =  'Skywalker' ;
     return  name;
})();
[了解更多](http://stackoverflow.com/a/7365214/1712802).


类型转换

  • 在语句开始时执行类型转换。
  • 字符串:
//  => this.reviewScore = 9;
 
// bad
var  totalScore =  this .reviewScore +  '' ;
 
// good
var  totalScore =  ''  this .reviewScore;
 
// bad
var  totalScore =  ''  this .reviewScore +  ' total score' ;
 
// good
var  totalScore =  this .reviewScore +  ' total score' ;
  • 使用 parseInt 转换数字时总是带上类型转换的基数。
var  inputValue =  '4' ;
 
// bad
var  val =  new  Number(inputValue);
 
// bad
var  val = +inputValue;
 
// bad
var  val = inputValue >> 0;
 
// bad
var  val = parseInt(inputValue);
 
// good
var  val = Number(inputValue);
 
// good
var  val = parseInt(inputValue, 10);
  • 如果因为某些原因 parseInt 成为你所做的事的瓶颈而需要使用位操作解决性能问题(http://jsperf.com/coercion-vs-casting/3)时,留个注释说清楚原因和你的目的。
// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
var  val = inputValue >> 0;
  • 注: 小心使用位操作运算符。数字会被当成 64 位值(http://es5.github.io/#x4.3.19),但是位操作运算符总是返回 32 位的整数(source(http://es5.github.io/#x11.7))。位操作处理大于 32 位的整数值时还会导致意料之外的行为。最大的 32 位整数是 2,147,483,647:
2147483647 >> 0  //=> 2147483647
2147483648 >> 0  //=> -2147483648
2147483649 >> 0  //=> -2147483647
  • 布尔:
var  age = 0;
 
// bad
var  hasAge =  new  Boolean(age);
 
 
// good
var  hasAge = Boolean(age);
 
// good
var  hasAge = !!age;


 

存取器

  • 属性的存取函数不是必须的。
  • 如果你需要存取函数时使用 getVal() 和 setVal('hello')
// bad
dragon.age();
 
// good
dragon.getAge();
 
// bad
dragon.age(25);
 
// good
dragon.setAge(25);
  • 如果属性是布尔值,使用 isVal() 或 hasVal()
// bad
if  (!dragon.age()) {
     return  false ;
}
 
// good
if  (!dragon.hasAge()) {
     return  false ;
}
  • 创建 get() 和 set() 函数是可以的,但要保持一致。
function  Jedi(options) {
     options || (options = {});
     var  lightsaber = options.lightsaber ||  'blue' ;
     this .set( 'lightsaber' , lightsaber);
}
 
Jedi.prototype.set =  function  set(key, val) {
     this [key] = val;
};
 
Jedi.prototype.get =  function  get(key) {
     return  this [key];
};


构造函数

  • 给对象原型分配方法,而不是使用一个新对象覆盖原型。覆盖原型将导致继承出现问题:重设原型将覆盖原有原型!
function  Jedi() {
     console.log( 'new jedi' );
}
 
// bad
Jedi.prototype = {
     fight:  function  fight() {
         console.log( 'fighting' );
     },
 
     block:  function  block() {
         console.log( 'blocking' );
     }
};
 
// good
Jedi.prototype.fight =  function  fight() {
     console.log( 'fighting' );
};
 
Jedi.prototype.block =  function  block() {
     console.log( 'blocking' );
};
  • 方法可以返回 this 来实现方法链式使用。
// bad
Jedi.prototype.jump =  function  jump() {
     this .jumping =  true ;
     return  true ;
};
 
Jedi.prototype.setHeight =  function  setHeight(height) {
     this .height = height;
};
 
var  luke =  new  Jedi();
luke.jump();  // => true
luke.setHeight(20);  // => undefined
 
// good
Jedi.prototype.jump =  function  jump() {
     this .jumping =  true ;
     return  this ;
};
 
Jedi.prototype.setHeight =  function  setHeight(height) {
     this .height = height;
     return  this ;
};
 
var  luke =  new  Jedi();
 
luke.jump()
     .setHeight(20);
  • 写一个自定义的 toString() 方法是可以的,但是确保它可以正常工作且不会产生副作用。
function  Jedi(options) {
     options || (options = {});
     this .name = options.name ||  'no name' ;
}
 
Jedi.prototype.getName =  function  getName() {
     return  this .name;
};
 
Jedi.prototype.toString =  function  toString() {
     return  'Jedi - '  this .getName();
};


事件

  • 当给事件附加数据时(无论是 DOM 事件还是私有事件),传入一个哈希而不是原始值。这样可以让后面的贡献者增加更多数据到事件数据而无需找出并更新事件的每一个处理器。例如,不好的写法:
// bad
$( this ).trigger( 'listingUpdated' , listing.id);
 
 
     ...
 
$( this ).on( 'listingUpdated' function  (e, listingId) {
     // do something with listingId
});
 
// good
$( this ).trigger( 'listingUpdated' , { listingId : listing.id });
 
     ...
 
$( this ).on( 'listingUpdated' function  (e, data) {
     // do something with data.listingId
});


模块

  • 模块应该以 ! 开始。这样确保了当一个不好的模块忘记包含最后的分号时,在合并代码到生产环境后不会产生错误。详细说明(https://github.com/airbnb/javascript/issues/44#issuecomment-13063933)
  • 文件应该以驼峰式命名,并放在同名的文件夹里,且与导出的名字一致。
  • 增加一个名为 noConflict() 的方法来设置导出的模块为前一个版本并返回它。
  • 永远在模块顶部声明 'use strict';
// fancyInput/fancyInput.js
 
! function  (global) {
     'use strict' ;
 
     var  previousFancyInput = global.FancyInput;
 
     function  FancyInput(options) {
         this .options = options || {};
     }
 
     FancyInput.noConflict =  function  noConflict() {
         global.FancyInput = previousFancyInput;
         return  FancyInput;
     };
 
     global.FancyInput = FancyInput;
}( this );

⬆ 回到顶部

jQuery

  • 使用 $ 作为存储 jQuery 对象的变量名前缀。
// bad
var  sidebar = $( '.sidebar' );
 
// good
var  $sidebar = $( '.sidebar' );
  • 缓存 jQuery 查询。
// bad
function  setSidebar() {
     $( '.sidebar' ).hide();
 
     // ...stuff...
 
     $( '.sidebar' ).css({
         'background-color' 'pink'
     });
}
 
// good
function  setSidebar() {
     var  $sidebar = $( '.sidebar' );
     $sidebar.hide();
 
     // ...stuff...
 
     $sidebar.css({
         'background-color' 'pink'
     });
}
  • 对 DOM 查询使用层叠 $('.sidebar ul') 或 父元素 > 子元素 $('.sidebar > ul')。 jsPerf(http://jsperf.com/jquery-find-vs-context-sel/16)
  • 对有作用域的 jQuery 对象查询使用 find
// bad
$( 'ul' '.sidebar' ).hide();
 
// bad
$( '.sidebar' ).find( 'ul' ).hide();
 
// good
$( '.sidebar ul' ).hide();
 
// good
$( '.sidebar > ul' ).hide();
 
// good
$sidebar.find( 'ul' ).hide();


ECMAScript 5 兼容性

  • 参考 Kangax 的 ES5 兼容表.(http://kangax.github.com/es5-compat-table/)

⬆ 回到顶部

测试

  • Yup.
function  () {
     return  true ;
}

⬆ 回到顶部

性能

⬆ 回到顶部

推荐阅读

工具

其它风格指南

其它风格

进一步阅读


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值