为了账号安全,请及时绑定邮箱和手机立即绑定

Airbnb的JavaScript风格指南(ES5)

标签:
JavaScript

用合理的方式编写JavaScript
原文链接

类型

  • 基本类型: 对于基本类型的操作是对值的操作。

    var foo = 1;var bar = foo;
    
    bar = 9;console.log(foo, bar); // => 1, 9
    • string

    • number

    • boolean

    • null

    • undefined

  • 复杂类型: 对于复杂类型的操作是对引用的操作。

    var foo = [1, 2];var bar = foo;
    
    bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
    • object

    • array

    • function

对象

  • 尽量使用字面量创建对象。

    // 不推荐var item = new Object();// 推荐var item = {};
  • 不要使用保留字作为属性键值(在IE8中不被支持),查看更多信息

    // 不推荐var superman = {  default: { clark: 'kent' },  private: true};// 推荐var superman = {  defaults: { clark: 'kent' },  hidden: true};
  • 使用可读性强的同义词来代替保留字。

    // 不推荐var superman = {  class: 'alien'};// 不推荐var superman = {  klass: 'alien'};// 推荐var superman = {  type: 'alien'};

数组

  • 尽量使用字面量创建数组。

    // 不推荐var items = new Array();// 推荐var items = [];
  • 使用Array#push方法而不要直接指定数组下标来添加元素。

    var someStack = [];// 不推荐someStack[someStack.length] = 'abracadabra';// 推荐someStack.push('abracadabra');
  • 当你需要复制一个数组的时候,使用Array#slice。 jsPerf

    var len = items.length;var itemsCopy = [];var i;// 不推荐for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }// 推荐itemsCopy = items.slice();
  • 使用Array#slice还可以将一个类数组对象(例如:arguments对象)转化为真正的数组。

    function trigger() {  var args = Array.prototype.slice.call(arguments);
      ...
    }

字符串

  • 使用单引号''定义字符串.

    // 不推荐var name = "Bob Parr";// 推荐var name = 'Bob Parr';// 不推荐var fullName = "Bob " + this.lastName;// 推荐var fullName = 'Bob ' + this.lastName;
  • 超过100个字符的字符串应该写成多行形式并用字符串连接符+连接。

  • 注意: 如果过度使用字符串连接符可能会影响性能。jsPerf & Discussion.

    // 不推荐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.';// 不推荐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.';// 推荐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.

    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;// 不推荐function inbox(messages) {
      items = '<ul>';  for (i = 0; i < length; i++) {
        items += '<li>' + messages[i].message + '</li>';
      }  return items + '</ul>';
    }// 推荐function inbox(messages) {
      items = [];  for (i = 0; i < length; i++) {
        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标准中将一个代码块block定义为一个表达式列表,但函数声明并没有被当做一个表达式。参考ECMA-262中的相关条目

    // 不推荐if (currentUser) {  function test() {    console.log('Nope.');
      }
    }// 推荐var test;if (currentUser) {
      test = function test() {    console.log('Yup.');
      };
    }
  • 永远不要将一个参数命名为arguments,这么做会覆盖每个函数默认的arguments对象。

    // 不推荐function nope(name, options, arguments) {  // ...stuff...}// 推荐function yup(name, options, args) {  // ...stuff...}

属性

  • 使用点号.来获取对象的属性。

    var luke = {  jedi: true,  age: 28};// 不推荐var isJedi = luke['jedi'];// 推荐var isJedi = luke.jedi;
  • 当需要获取的属性键值是一个变量来给出,可以使用[]来获取。

    var luke = {  jedi: true,  age: 28};function getProp(prop) {  return luke[prop];
    }var isJedi = getProp('jedi');

变量

  • 声明变量的时候永远要写上var,不这么做的话将会得到一个全局变量,并因此污染全局变量命名空间。

    // 不推荐superPower = new SuperPower();// 推荐var superPower = new SuperPower();
  • 每个变量声明使用一个单独的var
    这么做更便于添加新的变量声明,而不用关心;,修改的问题。

    // 不推荐var items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';// 不推荐// (对比上面的代码,你看出错误的地方了吗)var items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';// 推荐var items = getItems();var goSportsTeam = true;var dragonball = 'z';
  • 将未赋值变量的声明放在最后,这有利于后面在给变量赋值的时候需要依赖另一个已赋值变量的情况。

    // 不推荐var i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;// 不推荐var i;var items = getItems();var dragonball;var goSportsTeam = true;var len;// 推荐var items = getItems();var goSportsTeam = true;var dragonball;var length;var i;
  • 将变量声明在其作用域的最顶部,这样可以避免变量提升所带来的问题。

    // 不推荐function () {
      test();  console.log('doing stuff..');  //..other stuff..
    
      var name = getName();  if (name === 'test') {    return false;
      }  return name;
    }// 推荐function () {  var name = getName();
    
      test();  console.log('doing stuff..');  //..other stuff..
    
      if (name === 'test') {    return false;
      }  return name;
    }// 不推荐 - 会产生不必要的函数调用function () {  var name = getName();  if (!arguments.length) {    return false;
      }  this.setFirstName(name);  return true;
    }// 推荐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');
      }
    }
  • 获取更多信息参考:JavaScript Scoping & Hoisting by Ben Cherry.

比较运算符

  • 使用===!==比使用==!=更理想.

  • 条件语句(例如if语句)在判断表达式值的时候,会使用ToBoolean抽象方法将结果强制转化为布尔型,转化规则如下:

    if ([0]) {  // true
      // 数组是一个对象, 对象会被转化为true}
    • Objects转化为true

    • Undefined转化为false

    • Null转化为false

    • Booleans不变

    • Numbers如果是+0, -0或NaN转化为false, 否则转化为true

    • Strings如果是空字符串''转化为false, 否则转化为true

  • 使用简短形式。

    // 不推荐if (name !== '') {  // ...stuff...}// 推荐if (name) {  // ...stuff...}// 不推荐if (collection.length > 0) {  // ...stuff...}// 推荐if (collection.length) {  // ...stuff...}
  • 更多信息参考Truth Equality and JavaScript by Angus Croll.

代码块

  • 多行的代码块使用大括号包围。

    // 不推荐if (test)  return false;// 推荐if (test) return false;// 推荐if (test) {  return false;
    }// 不推荐function () { return false; }// 推荐function () {  return false;
    }
  • 如果使用多行形式的ifelse代码块,将else放在if块关闭大括号的同一行。

    // 不推荐if (test) {
      thing1();
      thing2();
    }else {
      thing3();
    }// 推荐if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }

注释

  • 多行注释使用/** ... */,包括描述,参数类型和值,返回值等信息。

    // 不推荐// make() returns a new element// based on the passed in tag name//// @param {String} tag// @return {Element} elementfunction make(tag) {  // ...stuff...
    
      return element;
    }// 推荐/**
     * make() returns a new element
     * based on the passed in tag name
     *
     * @param {String} tag
     * @return {Element} element
     */function make(tag) {  // ...stuff...
    
      return element;
    }
  • 单行注释使用//,单行注释放在被注释内容的前一行,并在注释前留一个空行。

    // 不推荐var active = true;  // is current tab// 推荐// is current tabvar active = true;// 不推荐function getType() {  console.log('fetching type...');  // set the default type to 'no type'
      var type = this._type || 'no type';  return type;
    }// 推荐function getType() {  console.log('fetching type...');  // set the default type to 'no type'
      var type = this._type || 'no type';  return type;
    }
  • 如果你想指出一个需要重新审查的问题或是想提出一个未解决问题的实现思路,用FIXMETODO在注释中做出标注,这样可以帮助其他开发者快速理解你的意图,这样的注释和普通注释不一样,因为他们是需要被处理的。处理方式为:FIXME -- 需要解决问题或者TODO -- 需要被实现

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

    function Calculator() {  // FIXME: 不应该使用全局变量
      total = 0;  return this;
    }
  • 使用// TODO:指出实现方式。

    function Calculator() {  // TODO: total应该被配置到一个options参数中
      this.total = 0;  return this;
    }

空白符

  • 使用soft tabs并设置成2个空格。

    // 不推荐function () {
    ∙∙∙∙var name;
    }// 不推荐function () {
    ∙var name;
    }// 推荐function () {
    ∙∙var name;
    }
  • 左大括号之前留一个空格。

    // 不推荐function test(){  console.log('test');
    }// 推荐function test() {  console.log('test');
    }// 不推荐dog.set('attr',{  age: '1 year',  breed: 'Bernese Mountain Dog'});// 推荐dog.set('attr', {  age: '1 year',  breed: 'Bernese Mountain Dog'});
  • 控制语句(如if, while)的左小括号前留一个空格,函数的参数列表前不要留空格。

    // 不推荐if(isJedi) {
      fight ();
    }// 推荐if (isJedi) {
      fight();
    }// 不推荐function fight () {  console.log ('Swooosh!');
    }// 推荐function fight() {  console.log('Swooosh!');
    }
  • 将运算符用空格隔开。

    // 不推荐var x=y+5;// 推荐var x = y + 5;
  • 文件最后以一个单独的换行符结尾。

    // 不推荐(function (global) {  // ...stuff...})(this);
    // 不推荐(function (global) {  // ...stuff...})(this);
    
    // 推荐(function (global) {  // ...stuff...})(this);
  • 遇到很长的方法调用链,使用换行缩进的方式(参照下面的代码),并以点号.开头,强调此行是一个方法而非一个新的语句。

    // 不推荐$('#items').find('.selected').highlight().end().find('.open').updateCount();// 不推荐$('#items').
      find('.selected').
        highlight().
        end().
      find('.open').
        updateCount();// 推荐$('#items')
      .find('.selected')
        .highlight()
        .end()
      .find('.open')
        .updateCount();// 不推荐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);// 推荐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);
  • 在代码块结束和新语句之间留一个空行。

    // 不推荐if (foo) {  return bar;
    }return baz;// 推荐if (foo) {  return bar;
    }return baz;// 不推荐var obj = {  foo: function () {
      },  bar: function () {
      }
    };return obj;// 推荐var obj = {  foo: function () {
      },  bar: function () {
      }
    };return obj;

逗号

  • 放在行首的逗号: 不要使用!

    // 不推荐var story = [
        once
      , upon
      , aTime
    ];// 推荐var story = [
      once,
      upon,
      aTime
    ];// 不推荐var hero = {    firstName: 'Bob'
      , lastName: 'Parr'
      , heroName: 'Mr. Incredible'
      , superPower: 'strength'};// 推荐var hero = {  firstName: 'Bob',  lastName: 'Parr',  heroName: 'Mr. Incredible',  superPower: 'strength'};
  • 对象或数组末尾额外的逗号: 不要使用! 这在IE6/7或IE9的quirks模式会导致一些问题。在一些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.

```javascript// 不推荐var hero = {
  firstName: 'Kevin',
  lastName: 'Flynn',
};var heroes = [  'Batman',  'Superman',
];// 推荐var hero = {
  firstName: 'Kevin',
  lastName: 'Flynn'};var heroes = [  'Batman',  'Superman'];
```

分号

  • 请使用!

    // 不推荐(function () {  var name = 'Skywalker'
      return name
    })()// 推荐(function () {  var name = 'Skywalker';  return name;
    })();// 推荐 (当两个定义了立即执行函数的文件被连接使用的时候可以防止意外的错误);(function () {  var name = 'Skywalker';  return name;
    })();

    了解更多

类型转化和强制转化

  • 在语句开始的时候执行类型强制转换。

  • 字符串:

    //  => this.reviewScore = 9;// 不推荐var totalScore = this.reviewScore + '';// 推荐var totalScore = '' + this.reviewScore;// 不推荐var totalScore = '' + this.reviewScore + ' total score';// 推荐var totalScore = this.reviewScore + ' total score';
  • 数字类型转换时使用parseInt,并且总是带上基数。

    var inputValue = '4';// 不推荐var val = new Number(inputValue);// 不推荐var val = +inputValue;// 不推荐var val = inputValue >> 0;// 不推荐var val = parseInt(inputValue);// 推荐var val = Number(inputValue);// 推荐var val = parseInt(inputValue, 10);
  • 如果你遇到一些极端情况,parseInt成为了瓶颈,并且因为性能因素需要使用移位操作,留下注释来解释你这么做的原因。

    // 推荐/**
     * parseInt是我代码缓慢的元凶,
     * 使用移位操作来将字符串转换成数字可解此忧
     */var val = inputValue >> 0;
  • 注意: 使用移位操作的时候请注意,数字(Number)类型是一个64位值,但是移位操作总是会返回一个32位整数(source)。在处理大于32位整数的值的时候,移位操作会导致一些无法预测的现象。讨论。32位有符号整型数的最大值为2,147,483,647:

    2147483647 >> 0 //=> 21474836472147483648 >> 0 //=> -21474836482147483649 >> 0 //=> -2147483647
  • 布尔型:

    var age = 0;// 不推荐var hasAge = new Boolean(age);// 推荐var hasAge = Boolean(age);// 推荐var hasAge = !!age;

命名规范

  • 避免单字母的命名,尽量使用有意义的命名方式。

    // 不推荐function q() {  // ...stuff...}// 推荐function query() {  // ..stuff..}
  • 对象、函数、实例命名时,使用骆驼命名(小驼峰命名)。

    // 不推荐var OBJEcttsssss = {};var this_is_my_object = {};var o = {};function c() {}// 推荐var thisIsMyObject = {};function thisIsMyFunction() {}
  • 构造方法或类的命名使用帕斯卡命名(大驼峰命名)。

    // 不推荐function user(options) {  this.name = options.name;
    }var bad = new user({  name: 'nope'});// 推荐function User(options) {  this.name = options.name;
    }var good = new User({  name: 'yup'});
  • 命名私有属性的时候,以下划线_开头。

    // 不推荐this.__firstName__ = 'Panda';this.firstName_ = 'Panda';// 推荐this._firstName = 'Panda';
  • 要保存一个到this的引用,请使用_this来命名变量。

    // 不推荐function () {  var self = this;  return function () {    console.log(self);
      };
    }// 不推荐function () {  var that = this;  return function () {    console.log(that);
      };
    }// 推荐function () {  var _this = this;  return function () {    console.log(_this);
      };
    }
  • 给你的函数命名,这将有利于调试时代码栈的追踪。

    // 不推荐var log = function (msg) {  console.log(msg);
    };// 推荐var log = function log(msg) {  console.log(msg);
    };
  • 注意: 命名的函数表达式在IE8或以下版本会有一些怪异的表现。参考http://kangax.github.io/nfe/

  • 如果你的源码文件中只导出(exports)了一个类,请保持你的文件名与类名一致。

    // 文件内容class CheckBox {  // ...}module.exports = CheckBox;// 在其他文件中// 不推荐var CheckBox = require('./checkBox');// 不推荐var CheckBox = require('./check_box');// 推荐var CheckBox = require('./CheckBox');

属性存取器

  • 属性存取器方法不是必需的。

  • 如果你要定义属性存取器,按照getVal()和setVal('hello')的模式来命名。

    // 不推荐dragon.age();// 推荐dragon.getAge();// 不推荐dragon.age(25);// 推荐dragon.setAge(25);
  • 如果属性是布尔型,使用isVal()或hasVal()的形式。

    // 不推荐if (!dragon.age()) {  return false;
    }// 推荐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];
    };

构造器

  • 将新增方法赋值给对象的原型(prototype),而不要直接用新的对象覆盖对象的原型。 如果每次都覆盖对象的原型,就不能实现继承了,因为每次你都会直接覆盖掉基类的所有方法。

    function Jedi() {  console.log('new jedi');
    }// 不推荐Jedi.prototype = {  fight: function fight() {    console.log('fighting');
      },  block: function block() {    console.log('blocking');
      }
    };// 推荐Jedi.prototype.fight = function fight() {  console.log('fighting');
    };
    
    Jedi.prototype.block = function block() {  console.log('blocking');
    };
  • 方法可以返回this来帮助构建方法链式调用。

    // 不推荐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(); // => trueluke.setHeight(20); // => undefined// 推荐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事件还是其他的事件)的时候,不要直接传入数据对象本身,将其包装到一个键值对象中再传入,因为你不能确保后续操作中不需要传入其他更多的数据到本事件中。例如, 下面这样就是不好的:

    // 不推荐$(this).trigger('listingUpdated', listing.id);
    
    ...
    
    $(this).on('listingUpdated', function (e, listingId) {  // do something with listingId});

    更好的做法是:

    // 推荐$(this).trigger('listingUpdated', { listingId : listing.id });
    
    ...
    
    $(this).on('listingUpdated', function (e, data) {  // do something with data.listingId});

模块

  • 模块应该以一个!开头,确保如果本模块被合并到另一个第三方模块的末尾,而这个第三方模块忘记了结尾的分号的时候,不会报错。更多解释

  • 文件应该按骆驼命名(小驼峰命名)规则命名。如果文件夹中只有一个文件,文件夹名和文件名保持一致。

  • 添加一个名叫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对象的变量命名以$开头。

```javascript// 不推荐var sidebar = $('.sidebar');// 推荐var $sidebar = $('.sidebar');
```
  • 缓存jQuery选择器结果。

    // 不推荐function setSidebar() {
      $('.sidebar').hide();  // ...stuff...
    
      $('.sidebar').css({    'background-color': 'pink'
      });
    }// 推荐function setSidebar() {  var $sidebar = $('.sidebar');
      $sidebar.hide();  // ...stuff...
    
      $sidebar.css({    'background-color': 'pink'
      });
    }
  • 使用级联形式$('.sidebar ul')$('.sidebar > ul')来查找DOM元素。jsPerf

  • 使用find来查找某个范围内的jQuery对象。

    // 不推荐$('ul', '.sidebar').hide();// 不推荐$('.sidebar').find('ul').hide();// 推荐$('.sidebar ul').hide();// 推荐$('.sidebar > ul').hide();// 推荐$sidebar.find('ul').hide();

ECMAScript 5兼容性

测试

  • 务必进行测试!

    function () {  return true;
    }

性能

资源

请阅读下面链接的内容

工具

其他的代码风格指南

其他的代码风格

深入阅读

参考书籍

博客

播客

License

(The MIT License)

Copyright (c) 2014 Airbnb

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



作者:黄炜杰
链接:https://www.jianshu.com/p/87c10daab4f4


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消