前端开发规范

参考airbnb规范,该规范是airbnb定义的eslint规范,从github上来看,受欢迎程度很高,选择这个做为规范标准

ESLint基础知识

安装

npm install --save-dev eslint

设置基本配置(生成.eslintrc)

./node_modules/.bin/eslint --init

运行相关检查

./node_modules/.bin/eslint 待检查js文件

parserOptions检验ecma版本,以及ecma的一些特性
{
 "parserOptions": {
  "ecmaVersion": 6, //指定ECMAScript支持的版本,6为ES6
  "sourceType": "module", //指定来源的类型,有两种”script”或”module”
  "ecmaFeatures": {
   "jsx": true//启动JSX
  },
 }
}
Parser设定对脚步的解析,默认是使用esprima,还可以设置为babel-eslint(目前项目都会使用es6的一些语法,所以这个参数一般都要设为babel-eslint)

对脚步解析是指把源码转成可AST(Abstract Syntax Tree抽象语法树,是源代码语法所对应的树状结构)

"parser": "babel-eslint" // 要先安装babel-eslint
Environments指定脚本的运行环境,不同的环境会有不同的预定义全局变量。
"env": {
    "browser": true,
    "commonjs": true,
    "es6": true
}, // 可设定参数很多,具体可以参考官方文档
extends设置继承的规则集,可以是单个字符也可以是多个配置的数组
"extends": "eslint:recommended", // 启用核心配置规则

除了可以使用核心配置外,还可以使用可别人共享的配置,extends属性值可以省略包名的前缀 eslint-config-,比如使用eslint-config-standard

使用这个规则时,需要先安装

npm install --save-dev eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node

在相关配置中使用这个规则

"extends": "standard"

另外虽然可以自己设定相关配置,然后共享出来,这里直接选用airbnb的标准进行代码检查

eslint-config-airbnb的标准需要eslint-plugin-jsx-a11y、eslint-plugin-react、eslint-plugin-import依赖,安装时要一并安装

npm install --save-dev eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import eslint-plugin-react

"extends": "airbnb"
globals 设置可以是全局变量的变量("extends": "eslint:recommended"会启用no-undef禁用未声明的变量)
// var1和var2是全局变量,var1可以被重写,var2不可以被重写
{
    "globals": {
        "var1": true,
        "var2": false
    },
}
eslint rules规则
rules: {
    rules name: [错误级别, 相应规则]
}

rules name:就是对应的限制规则

错误级别:

  • "off"/0 关闭规则
  • "warn"/1 警告
  • "error"/2 错误
"rules": {
    "indent": ["error", 4] // 限制使用4个空格
}
plugins eslint允许使用的第三方插件,在使用第三方插件时,如果需要设置相关规则可以使用插件名/规则ID的形式添加
{
    "plugins": [
        "plugin1"
    ],
    "rules": {
        "eqeqeq": "off",
        "curly": "error",
        "quotes": ["error", "double"],
        "plugin1/rule1": "error"
    }
}
部分代码过滤,不进行相关检查
// 使用方式如下,如果下面的代码不加eslint相关过滤信息,在进行eslint检查时会报no-console的错误,加上后不再进行相关检查,这个方式最好不要使用,除非是在开发过程中临时调试代码时使用
var add = function () {
    /* eslint-disable */
    console.log('add');
    /* eslint-enable */
};
// 和注释一样,也可以使用//来过滤
alert('foo'); // eslint-disable-line
设置使用eslint时的检查范围

使用eslint检查代码时,ESLint将自动在要检测的文件目录里寻找配置文件,然后紧接着是父级目录,一直到文件系统的根目录(当有多个检查文件时,离检测的文件最近的.eslintrc文件有最高优先级),在项目开发时,我们一般会希望检查规则一致,所以通常只会在项目的根目录设置一个.eslintrc,避免在其他子目录下添加这个配置文件,如果想要eslint只检查当前目录忽略其他目录,可以在配置中添加root: true,把相关检测限制到一个特定的项目

home
└── user
    ├── .eslintrc <- 根目录配置
    └── projectA
        ├── .eslintrc  <- 项目配置
        └── lib
            ├── .eslintrc  <- 库文件配置
            └── main.js
// 如果在lib下的配置文件中设置了root:true,在对main.js文件检查时会忽略项目配置            

如果同一目录下 .eslintrc 和 package.json 同时存在,.eslintrc 优先级高会被使用,package.json 文件将不会被使用,不要在package.json的文件中添加检测规则文件

设置eslint检查的忽略文件

在项目根目录创建一个.eslintignore文件,用来告诉ESLint去忽略特定的文件和目录,设定规则如下:

  • 以#开头的被当作注释,不会影响忽略
  • 路径是相对于.eslintignore的位置
  • 忽略模式同.gitignore规范
  • 以!开头是否定模式,它将会重新包含一个之前
// 一个模拟的例子,下面例子表示在lib目录下所有目录下的js都不进行代码检查
src/assets/lib/**/*.js

airbnb规范标准-js部分

标准中分成建议使用和校验(下面使用[0]代码建议使用,[1]代表校验),建议使用并不会报错,校验会报错(判断依据是我使用airbnb官方实例,在配置好的环境中运行,看是否会报错)

引用

  • 对变量的声明不要使用var,如果是声明后不再重现分配的变量要使用const,如果是重现分配的变量要使用let [1]
  • 注意let和const的块级作用域 [0]

对象

  • 使用对象直接量创建对象 [1]
// bad
const item = new Object();

// good
const item = {};
  • 如果一个对象的key值包含动态属性,要保证对象的所有属性都是在一个地方进行定义 [0]
function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};
// 从上面的代码我们可以看出,这个建议的是要我们把与对象相关的属性,都在一个地方定义,便于维护
  • 在定义方法时,使用简写的方式进行定义 [1]
// bad
const atom = {
  value: 1,
  addValue: function (value) {
    return atom.value + value;
  },
};

// good
const atom = {
  value: 1,
  addValue(value) {
    return atom.value + value;
  },
};
  • 在定义对象属性时,如果key值和value值同名,要使用简写形式 [1]
const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  lukeSkywalker: lukeSkywalker,
};

// good
const obj = {
  lukeSkywalker,
};
  • 在定义对象时,简写的属性和非简写的属性要按顺序写,不要混着写 [0]
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  lukeSkywalker,
  episodeThree: 3,
  mayTheFourth: 4,
  anakinSkywalker,
};

// good
const obj = {
  lukeSkywalker,
  anakinSkywalker,
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  episodeThree: 3,
  mayTheFourth: 4,
};
  • 在定义对象时,key值只有在无效识别时添加单引号 [1]
// bad
const bad = {
  'foo': 3,
  'bar': 4,
  'data-blah': 5,
};

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5,
};
  • 不允许直接使用Object.prototype的相关方法,比如hasOwnProperty, propertyIsEnumerable, 和 isPrototypeOf.因为在定义对象时,有可能会覆盖了这些方法 [1]
const item = {
    name: 'rede',
};
// bad
console.log(item.hasOwnProperty('name'));
// good
console.log(Object.prototype.hasOwnProperty.call(item, 'name'));
// best
const has = Object.prototype.hasOwnProperty;
console.log(has.call(item, 'name'));
  • 使用对象展开符浅复制对象,不要使用Object.assign [0]
// very bad
// 本意是复制一个对象,在从复制出的对象上删除某个值,但实际上原始对象的值也会被影响
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a;

// bad
// 可以达到预期目的,但在写法上可读性不好
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
delete copy.a;

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

数组

  • 在定义数组时,使用数组直接量,不要使用new Array的形式 [1]
// bad
const items = new Array();

// good
const items = [];
  • 在向数组添加元素时,不要直接赋值,通过push添加(注意指的是添加,并不包括修改的情况) [0]
const items = [];
// bad
items[items.length] = 'test';
// good
items.push('test');
  • 复制数组时,使用展开操作符 [0]
const items = [1, 2, 4, 8, 16];
// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
  • 在把类数组对象转为数组对象时,使用展开操作符来替代Array.from [0]
const foo = document.querySelectorAll('.foo');
 
// good
const nodes = Array.from(foo);
 
// best
const nodes = [...foo];
  • 使用Array.from代替展开操作符来处理针对数组的映射操作,可以避免操作创建中间数组 [0]
const foo = [1, 2, 3];

// bad
const baz = [...foo].map(x => x + x);

// good
const baz = Array.from(foo, x => x + x);
  • 数组的某些方法会存在回调函数,eslint会强制回调函数使用return,不过airbnb的规范中认为,如果某些数组的回调只是单条语句的那种,是可以省略return的 [0]

主要是一些可以对数组进行迭代的方法,比如every,filter,find,findIndex,map,reduce,some,sort

// airbnb同样标记这种写法为good
[1, 2, 3].map((x) => {
    const y = x + 1;
    return x * y;
});

// 下面的写法也是good,两种写法都没有问题,不过可能下面这种更易读些
[1, 2, 3].map(x => (x + 1)*x)

// airbnb下面标准的是bad,刚开始我也以为是针对这个规则的说法
// 实际运行下,发现针对这种写法报的错误是no-else-return,就是不允许在return之后使用else,哪怕看起来是另外的逻辑分支
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
});

// 后来尝试了下,结合eslint页面中的规则,估计这个地方的代码应该是这样
// 再次运行监测,会报几个错误,包括提到的array-callback-return,就是这里必须设定return的值
const inbox = ['black', 'white', 'yellow'];
inbox.filter((msg) => {
    const { subject, author } = msg;
    if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
    } else {
        return;
    }
});

// good
const inbox = ['black', 'white', 'yellow'];
inbox.filter((msg) => {
    const { subject, author } = msg;
    if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
    }
    return false;
});
  • 如果数组有多行,在数组的中括号前后要进行换行 [0]
// bad
const arr = [
  [0, 1], [2, 3], [4, 5],
];
const numberInArray = [
    1, 2,
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const numberInArray = [
    1, 
    2,
];

解构

  • 优先使用对象解构来访问对象属性 [1]
// bad
function getFullName(user) {
    const firstName = user.firstName;
    const lastName = user.lastName;
    return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
    const { firstName, lastName } = user;
    return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}
  • 优先使用数组解构从数组索引创建变量 [0]
const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;
  • 用对象解构去处理多个返回值 [1]
// 
const input = {
    left: '0px', right: '0px', top: '0px', bottom: '0px',
};
// bad
function processInput(input) {
    const left = input.left;
    const right = input.right;
    const top = input.top;
    const bottom = input.bottom;
    return [left, right, top, bottom];
}
const [left, __, top] = processInput(input);

// good
function processInput({
    left, right, top, bottom,
}) {
    return {
        left, right, top, bottom,
    };
}
const { left, top } = processInput(input);

字符串

  • 字符串使用单引号 [1]
// bad
const name = "Capt. Janeway";
// bad
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
  • 当字符串过长时,不要使用字符串连接符写成多行 [1][0]
// bad
// 此时报的检查错误是no-multi-str,保证字符串不分两行书写
const 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
// 此时运行代码检查并不会报错,针对这种写法应该是建议不要这么写
const 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
// 要是感觉代码过长不方便看时,可以在编译器上做相关设置,设置自动换行
const 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.';
  • 当字符串和变量要进行连接时,要使用模版字面量 [1]
// bad
function sayHi(name) {
    return 'How are you, ' + name + '?';
}

// bad
// 检查的错误template-curly-spacing,禁止模版字符串前后使用空格
function sayHi(name) {
  return `How are you, ${ name }?`;
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}
  • 针对字符串不要使用eval [1]

  • 在字符串中只在有意义的地方使用转义字符 [1]

// bad
const foo = '\'this\' is \"quoted\"';
// good
const foo = '\'this\' is "quoted"';

函数

  • 在定义函数时,使用指定名词的函数表达式,而不是匿名表达式 [0]

关于这点建议,最开始其实并不能理解,因为自己平时使用最多的也就是第二种方式,后来参考github这片文章的讨论上的讨论,感觉这么定义还是有必要的,针对这个我也单独写了一篇总结,有兴趣可以去看看

// bad 
function add (a, b) {
    return a + b;
}

// bad 
const add = function (a, b) {
    return a + b;
}

// good 
const add = function myAdd (a, b) {
    return a + b;
}
  • 立即执行函数要用大括号包裹起来 [1]
// bad
const x = function () { return { y: 1 };}()
...
// not bad
// 随着现在模块的普及立即执行函数使用的应该不多了
const x = (function () { return { y: 1 }; }());
  • 禁止循环中出现函数,如果需要在循环中定义函数,最好把相关函数使用变量定义好再使用,避免形成闭包 [0]

此处例子是我自己添加非airbnb例子

// bad
// 下面循环中如果把i的定义换成var,那就会形成一个最经典的闭包问题,数组funcs所有的返回值都是10,使用let会避免这个问题
const funcs = [];
for (let i = 0; i < 10; i + 1) {
    funcs[i] = function () {
        return i;
    };
}
...
// 如果使用babel转上面的代码
// 在这里可以看出babel进行转换时会按这规则,把本来定义的函数提取出来单独定义成一个变量,再进行使用
"use strict";
var funcs = [];
var _loop = function _loop(i) {
    funcs[i] = function () {
        return i;
    };
};
for (var i = 0; i < 10; i + 1) {
    _loop(i);
}

// not bad
const funcs = [];
const printI = function printI(i) {
    return i;
};
for (let i = 0; i < 10; i + 1) {
    funcs[i] = printI(i);
}
...
// babel转码后为
// 转码前后其实变化不大
"use strict";
var funcs = [];
var printI = function printI(i) {
    return i;
};
for (var i = 0; i < 10; i + 1) {
    funcs[i] = printI(i);
}
  • 禁止在代码块中使用函数声明,不过可以使用函数表达式 [1]
// bad
const currentUser = true;
if (currentUser) {
    function test() {
        console.log('Nope.');
    }
}

// good 
const currentUser = true;
let test;
if (currentUser) {
    test = () => {
        console.log('Nope.');
    };
}
  • 函数接收的形参不可以使用argumenst [1]
// bad 
function foo(name, options, arguments) {
  // ...
}
  • 函数内部不要使用使用arguments来替代获取形参,可以使用rest参数获取多余参数 [1]
//bad
function concatenateAll() {
    const args = Array.prototype.slice.call(arguments);
    return args.join('');
}
// good
function concatenateAll(...args) {
    return args.join('');
}
  • 使用默认参数语法,不要去使用存在变化的函数参数 [1]
// bad
// 这个会报no-param-reassign,禁止对 function 的参数进行重新赋值
function handleThings(opts) {
    opts = opts || {};
    ...
}
// bad
// 报的错误同上
function handleThings(opts) {
    if (opts === 0) {
        opts = {};
    }
}
// good
function handleThings(opts = {}) {
    // ...
}
  • 避免对函数默认参数进行不可预期的操作 [1]
// bad
// 检查时会报no-plusplus,禁用++
// 这个例子应该是要说明,不对默认参数设置不确定的因素,就比如这里的b值,两次调用返回值都不同,会令人费解
let b = 1;
function count(a = b++) {
    console.log(a);
}
count(); // 1
count(); // 2
  • 始终将默认参数,放到最后 [0]
// bad
function handleThings(opts = {}, name) {
    return { opts, name };
}
// good
function handleThings(name, opts = {}) {
    return { opts, name };
}
  • 禁用Function构造函数创建函数 [1]
// bad
const x = new Function('a', 'b', 'return a + b');
// good
const x = function backAdd(a, b) {
    return a + b;
};
  • 强制在函数圆括号前和代码块之前使用统一的空格 [1]
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const f = function () {};
const g = function () {};
const h = function () {};
  • 禁止对函数参数再赋值 [1]
// bad
// 怎么都想不到为什么要对参数进行赋值,报错为no-param-reassign
function foo(bar) {
    bar = 13;
}
...
// bad
function foo(bar) {
    bar.name = 'foo';
}
  • 在函数括号内使用一致的换行,如果是多行会要求最后一项带逗号 [1]
// normal
// 不使用换行
function foo(bar, baz, quux) {
// ...
}
// bad
// 会报function-paren-newline要求使用一致的换行
function foo(bar, 
    baz, 
    quux) {
// ...
}
// good
// 最后一行添加了逗号
function foo(
    bar,
    baz,
    quux,
) {
// ...
}

箭头函数

  • 当必须使用匿名函数时(比如回调函数),可以使用箭头函数 [1]
// bad
[1, 2, 3].map(function (x) {
    const y = x + 1;
    return x * y;
}); 
// good
[1, 2, 3].map((x) => {
    const y = x + 1;
    return x * y;
});
  • 如果回调函数只是简单的单行语句,可以省略return [0]
// good
[1, 2, 3].map((x) => {
    const y = x + 1;
    return x * y;
});

// good,提高可读性
[1, 2, 3].map(x => (x + 1) * x)
  • 如果表达式垮多行,将其包裹在括号中,可以提高代码的可读性 [0]
// not good
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
));
// good
['get', 'post', 'put'].map(httpMethod => (
    Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
    )
));
  • 箭头函数体只有一个参数时且不使用大括号,可以省略圆括号。其它任何情况,参数都应被圆括号括起来 [0]
// bad
// arrow-parens
[1, 2, 3].map((x) => x * x);
// good
[1, 2, 3].map(x => x * x);

// bad
// 因为此时使用了大括号,箭头函数后面跟随了代码块
[1, 2, 3].map(x => {
    const y = x + 1;
    return x * y;
});
  • 禁止在可能与比较操作符相混淆的地方使用箭头函数 [1]
// bad
// 此时的箭头符号和比较符号看起来很像
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;

// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);

// good
const itemHeight = (item) => {
    const { height, largeSize, smallSize } = item;
    return height > 256 ? largeSize : smallSize;
};

类和构造函数

  • 总是使用class,避免使用prototype [0]
// bad
function Queue(contents = []) {
    this.queue = [...contents];
}
Queue.prototype.pop = function () {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
};
// good
class Queue {
    constructor(contents = []) {
        this.queue = [...contents];
    }
    pop() {
        const value = this.queue[0];
        this.queue.splice(0, 1);
        return value;
    }
}
  • 对类的继承使用extends [1]
// good
// 这种写法简单明了
class student {
    constructor({ id } = {}) {
        this.name = `stu-${id}`;
    }
    backName() {
        return this.name;
    }
}
class classmates extends student {
    constructor({ id } = {}) {
        super({ id });
        this.id = id;
    }
    id() {
        return this.id;
    }
}
  • 方法可以返回this来进行链式调用 [0]
// normal
class student {
    constructor({ id } = {}) {
        this.name = `stu-${id}`;
    }
    setAge({ age } = {}) {
        this.age = age;
    }
    setGender({ gender } = {}) {
        this.gender = gender;
    }
    backName() {
        return this.name;
    }
    backInfo() {
        return `name: ${this.name}, age: ${this.age}, gender: ${this.gender}`;
    }
}
const stu = new student({id: 2333});
stu.setAge({age: 18});
stu.setGender({gender: 'man'});
stu.backInfo();

// good
class student {
    constructor({ id } = {}) {
        this.name = `stu-${id}`;
    }
    setAge({ age } = {}) {
        this.age = age;
        return this;
    }
    setGender({ gender } = {}) {
        this.gender = gender;
        return this;
    }
    backName() {
        return this.name;
    }
    backInfo() {
        return `name: ${this.name}, age: ${this.age}, gender: ${this.gender}`;
    }
}
const stu = new student({id: 2333});
stu.setAge({age: 18}).setGender({gender: 'man'});
stu.backInfo();
  • 定义类时可以定义一个toString方法,只要保证该方法不会产生意外的副作用 [0]
class student {
    constructor({ id } = {}) {
        this.name = `stu-${id}`;
    }
    setAge({ age } = {}) {
        this.age = age;
        return this;
    }
    setGender({ gender } = {}) {
        this.gender = gender;
        return this;
    }
    backName() {
        return this.name;
    }
    backInfo() {
        return `name: ${this.name}, age: ${this.age}, gender: ${this.gender}`;
    }
    toString() {
        return this.backName();
    }
}
  • 如果构造函数只是一个空的构造函数或只是简单的调用父类,此时构造函数可以省略 [1]
// bad
// 因为此时构造函数constructor没有任何作用,此时没必要设置
class Jedi {
    constructor() {}

    getName() {
        return this.name;
    }
}
// bad
// 此时只是透传数据,没必要使用构造函数
class Rey extends Jedi {
    constructor(...args) {
        super(...args);
    }
}
// godd
class Rey extends Jedi {
    constructor(...args) {
        super(...args);
        this.name = 'Rey';
    }
}
  • 类的成员禁止重名 [1]
// bad
// 很明显下面的bar会覆盖上面的bar方法
class Foo {
    bar() { return 1; }
    bar() { return 2; }
}

模块

  • 使用模块import/export输入输出代码块 [1]
  • 使用import时,不要使用通配符,最好明确要导入的代码块 [0]
// 另外一个模块文件,比如是index.js
export const firstName = 'rede';
export const lastName = 'li';
// 引用该模块
// bad
// 按webpack 4+以上的版本会静态分析index.js只导入需要的代码块,所以明确导入的代码块会更有利于减少打包体积
import * as index from './index';

console.log(index.firstName);
// best
// 原例子上还提到了一个good的写法,不过看了下,感觉这种写法最好,结合编译器,还能减小文件代码
import { firstName } from './index';

console.log(firstName);
  • 不要从import中直接导出(export) [0]
// bad
export { firstName as default } from './index';
// good
// 这样更加明晰
import { firstName } from './index';

export default firstName;
  • 同一个路径的导入只在一个地方导入,禁止重复导入 [1]
// bad
import { firstName } from './index';
import { lastName } from './index';

export default { firstName, lastName };
// good
import { firstName, lastName } from './index';

export default { firstName, lastName };
  • 不要export可变绑定 [1]
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo };
  • 如果一个文件只导出一个模块,默认export优于命名export [1]
// bad
const foo = 3;
export { foo };
// good
const foo = 3;
export default { foo };
  • 将所有import导入放在非导入语句上面 [1]
// bad
import { firstName, lastName, backName } from './index';
backName();
import { name } from './test';

export default { firstName, lastName, name };
// good
import { firstName, lastName, backName } from './index';
import { name } from './test';

backName();

export default { firstName, lastName, name };
  • 多行导入应该像多行数组那样进行缩进 [0]
// bad
import { firstName, lastName, year } from './main';

export { firstName, lastName, year };
// good
import {
    firstName,
    lastName,
    year,
} from './main';

export { firstName, lastName, year };
  • 禁止在模块的import中使用Webpack加载语法 [1]
// bad
import fooSass from 'css!sass!foo.scss';
// good
import fooSass from 'foo.scss';

迭代器(Iterators)和生成器(Generators)

  • 不要使用iterators,请使用高阶函数,例如map、reduce而不是for-in、for-of这样的循环 [1]
// bad
// no-restricted-syntax这个检查是禁用了特定语法
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
for (const num of numbers) {
    sum += num;
}

export default { sum };
// good
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
numbers.forEach((num) => {
    sum += num;
});

export default { sum };
// best
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((total, num) => total + num, 0);

export default { sum };
// bad
// 此时检查并不会报错,只是语法建议,不过和下面的good、best进行比较,best写法更加明晰
const numbers = [1, 2, 3, 4, 5];
const increasedByOne = [];
for (let i = 0; i < numbers.length; i + 1) {
    increasedByOne.push(numbers[i] + 1);
}

export default { increasedByOne };
// good
const numbers = [1, 2, 3, 4, 5];
const increasedByOne = [];
numbers.forEach((num) => {
    increasedByOne.push(num + 1);
});

export default { increasedByOne };
// best
const numbers = [1, 2, 3, 4, 5];
const increasedByOne = numbers.map(num => num + 1);

export default { increasedByOne };
  • 现在不要使用generators(生成器) [0]
// 下面的代码来源阮一峰老师的《ECMAScript 6 入门》的Generator 函数的语法,在浏览器中运行是可以正常运行的,但按照建议规范的建议是因为现在还没有好的方式转为ES5,所以不建议使用
function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

const hw = helloWorldGenerator();
export default { hw };
  • 如果使用了generators,function*是定义generators的专用语法,不可以把function和*分开写 [1]

属性

  • 使用点语法来访问对象属性 [1]
// bad
const luke = {
    jedi: true,
    age: 28,
};
const isJedi = luke['jedi'];

export default { isJedi };
// good
const luke = {
    jedi: true,
    age: 28,
};
const isJedi = luke.jedi;

export default { isJedi };
  • 当通过变量访问属性时要使用中括号 [0]
const luke = {
    jedi: true,
    age: 28,
};
 
function getProp(prop) {
    return luke[prop];
}
 
const isJedi = getProp('jedi');

export default { isJedi };
  • 求幂运算使用求幂运算符 [1]
// bad
const binary = Math.pow(2, 10);
// good
const binary = 2 ** 10;

变量

  • 总是使用const或let声明变量,避免全局变量污染 [1]
  • 每个便利在声明时,都要使用const或let [1]
// bad
const items = 'rede',
    goSportsTeam = true,
    dragonball = 'z';

export { items, goSportsTeam, dragonball };
// good
const items = 'rede';
const goSportsTeam = true;
const dragonball = 'z';

export { items, goSportsTeam, dragonball };
  • 将所有的const和let的定义分组 [0]
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
  • 在需要的地方再对变量进行分配 [0]
// bad
// 这里的checkName中name会调用getName函数,但是name并不是总会返回,如果hasName为test时,并不会返回,所以,应该把对name的定义延后
const getName = function backName() {
    return 'rede';
};

function checkName(hasName) {
    const name = getName();

    if (hasName === 'test') {
        return false;
    }

    return name;
}
// good
const getName = function backName() {
    return 'rede';
};

function checkName(hasName) {
    if (hasName === 'test') {
        return false;
    }
    const name = getName();

    return name;
}
  • 禁止使用连续赋值 [1]
// bad
const a = b = c = 1;
// good
const a = 1;
const b = a;
const c = a;
  • 避免使用++或--运算符 [1]
// bad
let num = 1;
num++;
--num;
// good
let num = 1;
num += 1;
num -= 1;
  • 在=号前后要避免进行换行,如果变量名超过最长限制,要统一换行方式 [0]
// bad
// 此时的换行是没有必要的
const foo
  = 'superLongLongLongLongLongLongLongLongString';
// good
const foo = 'superLongLongLongLongLongLongLongLongString';  
// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();
// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

变量提升

  • var声明会被提升至他们作用域的顶部,但相应的赋值不会提升,let和const的声明并不会提升,因为其形成了一个暂时性死区
(function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
})()  
...
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
  const declaredButNotAssigned = true;
}
  • 匿名函数表达式的变量也会被提升,但函数体不会被提升
function example() {
    console.log(anonymous); // => undefined
 
    anonymous(); // => TypeError anonymous is not a function 输入错误,anonymous 不是一个函数
 
    var anonymous = function () {
    console.log('anonymous function expression');
    };
}
  • 命名的函数表达式变量也会被提升,但函数体不会被提升
function example() {
    console.log(named); // => undefined
 
    named(); // => TypeError named is not a function,输入错误,named 不是一个函数
 
    superPower(); // => ReferenceError superPower is not defined, ReferenceError(引用错误)superPower 未定义
    
    var named = function superPower() {
        console.log('Flying');
    };
}
  • 函数声明的名词和函数体都会被提升
function example() {
    superPower(); // => Flying
 
    function superPower() {
    console.log('Flying');
    }
}

比较运算符和等号

  • 使用===和!==,避免使用==和!= [1]

  • 诸如if语句之类的条件语句会把其中的值强制进行布尔值转换,遵循以下简单规则

    • Objects求值为true
    • Undefined和Null求值为false
    • Numbers如果是+0,-0或NaN求值为false,其他为true
    • Strings如果是''求值为false,其他为true
  • 对于布尔值使用简写,但对于数字和字符串要进行显式比较 [0]

const isValid = true;
// bad
// isValid是布尔值
if (isValid === true) {
    // ...
}
// good
if (isValid) {
    // ...
}
...
const name = 'rede';
// bad
// 比如代码块中依赖对name是否为空进行相关逻辑,这时省略对''进行比较是不会影响功能的,但是对代码的可读性会产生影响
if (name) {
    // ...
}
// good
if (name !== '') {
    // ...
}
...
const collection = [];
// bad
if (collection.length) {
  // ...
}
// good
if (collection.length > 0) {
  // ...
}
  • 在case和default中,如果要创建包含词法声明的语句块(let、const、function和class)要使用大括号进行包裹 [1]
// bad
// 词法声明在整个switch语句都是可见的,在多个case子句试图定义相同变量时会报no-redeclare(禁止重新声明变量)
const num = 1;
switch (num) {
case 1:
    let name = 'rede';
    break;
case 2:
    let name = 'tom';
    break;
}
// good
const num = 1;
switch (num) {
case 1: {
    const name = 'rede';
    break;
}
case 2: {
    const name = 'tom';
    break;
}
}
  • 使用三元操作时,避免进行嵌套 [1]
// bad
// 会降低代码的易读性
const bar = 'bar';
const bing = 'bind';
const foo = bar === 'bars' ? (bing === 'bing' ? 'bars bing' : 'bars bind') : 'bar';

export default { foo };
// good
const bar = 'bar';
const bing = 'bind';
const barVal = bar === 'bars' ? 'bars' : 'bar';
const foo = bing === 'bing' ? `${barVal} bing` : `${barVal} bind`;

export default { foo };
  • 避免使用一些不必要的三元运算 [1]
// bad
const answer = 1;
const isYes = answer === 1 ? true : false;

export default { isYes };
// good
const answer = 1;
const isYes = answer === 1;

export default { isYes };
  • 当多个运算符混合在一个语句中,要添加适当的括号,不要将**和%与+、-,*,/混在一起使用,能提高代码的可读性 [1]
// bad
const a = 1;
const b = 0;
const c = 4;
const d = 3;
const foo = a && b < 0 || c > 0 || d + 1 === 0;
const bar = a ** b - 5 % d;

export default { foo, bar };
// good
const a = 1;
const b = 0;
const c = 4;
const d = 3;
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
const bar = (a ** b) - (5 % d);

export default { foo, bar };

代码块

  • 使用大括号包裹多行代码,单行代码可以强制直接出现不需要换行 [1]
const test = true;
let boo = '';
// bad
if (test)
    boo = 'boo';
// good
if (test) boo = 'boo';    

export default { boo };
  • 如果通过if 和 else使用多行代码块,要把else放在if代码块闭合括号的同一行 [1]
// bad
const name = 'rede';
let foo = '';
if (name === 'rede') {
    foo = 'foo'
}
else {
    foo = 'bar';
}

export default { foo };
// good
const name = 'rede';
let foo = '';
if (name === 'rede') {
    foo = 'foo';
} else {
    foo = 'bar';
}

export default { foo };
  • 如果一个if代码块使用了return语句,后面的else可以省略,在else if块中return可分成多个if来return [1]
// bad
function foo (val) {
    if (val) {
        return 'foo';
    } else {
        return 'bar';
    }
}

export default { foo };
// good
function foo(val) {
    if (val) {
        return 'foo';
    }
    return 'bar';
}

export default { foo };
// bad
function foo(val) {
    if (val === 'foo') {
        return 'foo';
    } else if (val === 'bar') {
        return 'bar';
    } else if (val === 'test') {
        return 'test';
    }
    return 'rede';
}

export default { foo };
// good
function foo(val) {
    if (val === 'foo') {
        return 'foo';
    }
    if (val === 'bar') {
        return 'bar';
    }
    if (val === 'test') {
        return 'test';
    }
    return 'rede';
}

export default { foo };

控制语句

  • 如果控制语句太长或超过最大行长度,那么每个分组条件可以放单独一行,但要注意把运算符放在每行的起始处 [0]
// not good
const nv1 = 2;
const nv2 = 3;
const nv3 = 4;
const nv4 = 5;
const nv5 = 6;
const nv6 = 7;
let foo = '';
// not good
if (nv1 === 1 && nv2 === 2 && nv3 === 3 && nv4 === 4 && nv5 === 5 && nv6 === 6) foo = 'foo';
// not good
if (nv1 === 1 && nv2 === 2 && nv3 === 3 && 
    nv4 === 4 && nv5 === 5 && nv6 === 6) foo = 'foo';
// good 
if (nv1 === 1 && nv2 === 2 && nv3 === 3 
    && nv4 === 4 && nv5 === 5 && nv6 === 6) foo = 'foo';

export default { foo };

注释

  • 多行注释使用/**...*/ [0]
// good
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
  • 单行注释使用 // ,将单行注释放在续注释的语句上方。在注释之前放置一个空行,除非它位于代码块的第一行。 [0]
// bad
const active = true;  // is current tab
 
// good
// is current tab
const active = true;
// bad
function getType() {
    console.log('fetching type...');
    // set the default type to 'no type'
    const type = this.type || 'no type';
 
    return type;
}
 
// good
function getType() {
    console.log('fetching type...');
 
    // set the default type to 'no type'
    const type = this.type || 'no type';
 
    return type;
}
 
// also good
function getType() {
    // set the default type to 'no type'
    const type = this.type || 'no type';
 
    return type;
}
  • 所有注释符和注释内容用一个空格隔开,让它更容易阅读 [0]
// bad
//is current tab
// good
// is current tab
// bad
/**
 *make() returns a new element
 *based on the passed-in tag name
 */
 // good
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
  • 给注释增加FIXME或TODO的前缀,可以帮助其他开发者快速了解这个是否是一个需要重新复查的问题,或是你正在为需要解决问题提出的解决方案,有别于常规注释 [0]

  • 使用 // FIXME来标识需要修正的问题

  • 使用 // TODO来标识需要实现的问题

还可以使用 // XXX说明注释处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明

空白

  • 使用一致的缩进 [1]

这里默认是使用4个空格

  • 在大括号前放置一个空格 [1]
// 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',
});
  • 在控制语句的小括号前放一个空格,在函数调用及声明中,不在函数的参数列表前加空格 [1]
const isJedi = true;
// bad
if(isJedi) {
    console.log('dd');
}
// good
if (isJedi) {
    console.log('dd');
}
// bad
function fight () {
    console.log ('Swooosh!');
}
// good
function fight() {
    console.log('Swooosh!');
}
  • 使用空格把运算符隔开 [1]
// bad
const x=y+5;
// good
const x = y + 5;
  • 在文件末尾插入一个空行 [1]
  • 长方法链式调用时使用缩进,使用一个点开头,强调该行是一个方法调用,不是一个新的声明 [1]

估计是怕链式调用太长不方便看

// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// good
$('#items').find('.selected').highlight().end()
    .find('.open')
    .updateCount();
  • 在语句块后和下条语句前留一个空行 [0]
const foo = true;
let bar = '';
// bad
if (foo) {
    bar = 'bar';
}
export default { bar };
// good
if (foo) {
    bar = 'bar';
}

export default { bar };
  • 块级代码中禁用多余的空行 [1]
// bad
function bar() {
 
    console.log(foo);
 
}
// good
function bar() {
    console.log(foo);
}
  • 不要在圆括号前后加空格 [1]
// bad
if ( foo ) {
    console.log(foo);
}
// good
if (foo) {
    console.log(foo);
}
  • 不要在中括号前后添加空格 [1]
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
  • 在大括号前后添加空格 [1]
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
  • 避免有超过100个字符(包括空格)的代码行,如果有超过,要适当考虑把代码进行换行 [1]

保证代码的可读性和可维护性

// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
// good
const foo = jsonData
    && jsonData.foo
    && jsonData.foo.bar
    && jsonData.foo.bar.baz
    && jsonData.foo.bar.baz.quux
    && jsonData.foo.bar.baz.quux.xyzzy;
// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
// good
$.ajax({
  method: 'POST',
  url: 'https://airbnb.com/',
  data: { name: 'John' },
})
  .done(() => console.log('Congratulations!'))
  .fail(() => console.log('You have failed this city.'));    

逗号

  • 在行开头处不要使用逗号 [1]
// bad
const hero = {
    firstName: 'Ada'
    , lastName: 'Lovelace'
    , birthYear: 1815
    , superPower: 'computers'
};
// good
const hero = {
    firstName: 'Ada',
    lastName: 'Lovelace',
    birthYear: 1815,
    superPower: 'computers',
};
  • 在结尾添加逗号 [1]
// bad
const hero = {
    firstName: 'Ada',
    lastName: 'Lovelace',
    birthYear: 1815,
    superPower: 'computers'
};
// good
const hero = {
    firstName: 'Ada',
    lastName: 'Lovelace',
    birthYear: 1815,
    superPower: 'computers',
};

因为通过git diff时,能体现出这些差异

// bad - 没有结尾逗号的 git diff 差异比较
const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - 有结尾逗号的 git diff 差异比较
const hero = {
     firstName: 'Florence',
     lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};

分号

  • 禁止使用自动分号插入,都要加入分号 [1]

类型转换

  • 在声明语句的开始处就执行强制类型转换
  • 转为字符串时,禁止原始包装实例
const reviewScore = 9;
// bad
// 此时typeof totalScore为Object并不是string
const totalScore = new String(reviewScore); 
// bad
// 调用的事 reviewScore.valueOf()
const totalScore = reviewScore + ''; 
// bad
// 不能保证返回一个字符串
const totalScore = reviewScore.toString(); 
// good
const totalScore = String(reviewScore);
  • 转为数字类型时,禁止使用原始包装实例,如果使用parseInt要必须有基数 [1]
const inputValue = '4';
// bad
// 不要使用原始包装实例
const val = new Number(inputValue);
// bad
const val = +inputValue;
// bad
const val = inputValue >> 0;
// good
const val = Number(inputValue);
// bad
// 没基数
const num = parseInt('071');
// good
const num = parseInt('071', 10);
  • 避免使用按位运算符 [1]
// bad
const val = inputValue >> 0;
  • 进行布尔值转换时,避免使用原始包装实例 [1]
const age = 0;
// bad
const hasAge = new Boolean(age);
// good
const hasAge = Boolean(age);
// best
const hasAge = !!age;

命名规则

  • 避免使用单字母名词 [0]
  • 当命名对象,函数和实例时使用驼峰式命名 [1]
// bad
// 一般命名分为驼峰和下划线拼写写法,这里建议使用驼峰式写法
const this_is_my_object = {};
// good
const thisIsMyObject = {};
  • 当命名构造函数或类时使用PascalCase式命名(帕斯卡拼写法,即首字母大写) [0]
// bad
function user(options) {
    this.name = options.name;
}
 
const bad = new user({
    name: 'nope',
});
// good
class User {
    constructor(options) {
    this.name = options.name;
    }
}
 
const good = new User({
    name: 'yup',
});
  • 变量禁止使用下划线开头或结尾 [1]
// bad
const __firstName__ = 'Panda';
// good
const firstName = 'Panda';
  • 不要存储this引用 [0]
// bad
function foo() {
    const self = this;
    return function () {
        console.log(self);
    };
}
// good
function foo() {
    return () => {
        console.log(this);
    };
}
  • 文件名应与其默认导出的名词完全匹配 [0]
// 文件1
class CheckBox {
    // ...
}
export default CheckBox;
 
// 文件2
export default function fortyTwo() { return 42; }
 
// 文件3
export default function insideDirectory() {}

// good
import CheckBox from './CheckBox'; // export/import/filename 单词首字母大写命名
import fortyTwo from './fortyTwo'; // export/import/filename 驼峰式命名
import insideDirectory from './insideDirectory'; 
  • 当导出一个默认函数时使用驼峰式命名,文件名应该和你的函数名字一致 [0]
function makeStyleGuide() {
    // ...
}
 
export default makeStyleGuide;
...
文件名为makeStyleGuide
  • 当导出一个构造函数/类/单例/函数库/纯对象时使用PascalCase式命名 [0]
  • 首字母缩写的词,应该总是全部大写,或全部小写 [0]
// bad
import SmsContainer from './containers/SmsContainer';
// bad
const HttpRequests = [
    // ...
];
// good
import SMSContainer from './containers/SMSContainer';
 
// good
const HTTPRequests = [
    // ...
];
// also good
const httpRequests = [
    // ...
];
// best
import TextMessageContainer from './containers/TextMessageContainer';
  • 如果变量是被导出的,或者只是常量,或者可以确信变量不会改变,可以使用大写,使用大写的变量可以帮助使用者确定使用的变量不会改变 [0]

存取器

  • 属性的存取器不是必须
  • 避免使用js的getters/setters,因为会导致意想不到的副作用,而且会很难测试维护,可以使用存取器函数,使用getVal及setVal [0]
// bad
class Dragon {
    constructor(val) {
        this.name = val;
        this.year = 0;
    }
    get age() {
        // ...
        return this.year;
    }

    set age(value) {
        // ...
        this.year = value;
    }
}
// good
class Dragon {
    constructor(val) {
        this.name = val;
        this.year = 0;
    }
    getAge() {
        // ...
        return this.year;
    }

    setAge(value) {
        // ...
        this.year = value;
    }
}
  • 也可以创建 get() 和 set() 函数, 但要保持一致 [0]

标准库

  • 使用Number.isNaN代替全局isNaN [1]
// bad
// no-restricted-globals禁用特定的全局变量
isNaN('1.2');
// good
Number.isNaN('1.2.3');
  • 使用Number.isFinite代替isFinite [1]
// bad
isFinite('2e3');
// good
Number.isFinite('2e3');

airbnb规范标准-css部分

这部分内容是建议

  • 格式

    • 使用 2 个空格作为缩进。
    • 类名建议使用破折号代替驼峰法。如果你使用 BEM,也可以使用下划线(参见下面的 OOCSS 和 BEM)。
    • 不要使用 ID 选择器。
    • 在一个规则声明中应用了多个选择器时,每个选择器独占一行。
    • 在规则声明的左大括号 { 前加上一个空格。
    • 在属性的冒号 : 后面加上一个空格,前面不加空格。
    • 规则声明的右大括号 } 独占一行。
    • 规则声明之间用空行分隔开。
  • 注释

    • 建议使用行注释 (在 Sass 中是 //) 代替块注释。
    • 建议注释独占一行。避免行末注释。
    • 给没有自注释的代码写上详细说明,比如:
      • 为什么用到了 z-index
      • 兼容性处理或者针对特定浏览器的 hack
  • 不建议使用ID选择器

ID选择器会带来不必要的优先级,而且ID选择器是不可复用的

  • JavaScript钩子

避免在Css和JavaScript中绑定相同的类,不利于后续维护,因为写定后就相当于样式和js文件绑定了,改样式名词会造成页面逻辑出错。如果要涉及到与样式有关的操作,添加.js-前缀

  • 定义边框无样式时,使用0代替none

  • 不要让嵌套选择器深度超过3层

Google HTML/CSS代码风格

并非按条条写,只选取了一部分

HTML规则

  • 对于图片和其他媒体文件,样式表和脚本,请尽量使用https协议,除非相关文件不支持
  • 标签,属性,属性值,css选择器,属性,和属性值只使用小写
  • 删除行尾不必要的空格
  • 指定页面编码为utf-8

<meta charset="utf-8">

  • 添加合理的注释,说明相关代码是做什么的,只在关键代码处添加相关注释,避免过多添加增加HTML和CSS的代码量
    • 使用TODO注释,说明代码功能
  • 文档类型首选HTML5标准

<!DOCTYPE html>

  • 保证HTML代码的有效性,可以借助W3C HTML validator分析页面,修正一些明显的错误

这个工具会分析出静态文件不符合语意化的地方,不过这个检查标准比较严格,仅供参考

  • 根据HTML元素的语义使用相关元素
  • 为有意义的多媒体元素提供备选文案

比如一个详情页的产品图,就是页面中有意义的图片,要保证这类多媒体元素要有备选文案,一方面是保证元素在加载不出时,页面不至于报错,另一方面也是给盲人提供提示文字,但如果是一些类似背景图片的元素就没必要添加备选文案

  • 将行为和表现分开

严格遵循结构,表现和行为的分离,尽量保证三者交互保持最低,确保所有表现都放到样式表中,所有行为都放到脚本中,要尽量减少外链

// bad
<!DOCTYPE html>
<title>HTML sucks</title>
<link rel="stylesheet" href="base.css" media="screen">
<link rel="stylesheet" href="grid.css" media="screen">
<link rel="stylesheet" href="print.css" media="print">
<h1 style="font-size: 1em;">HTML sucks</h1>
<p>I’ve read about this on a few sites but now I’m sure:
  <u>HTML is stupid!!1</u>
<center>I can’t believe there’s no way to control the styling of
  my website without doing everything all over again!</center>
...
// good
<!DOCTYPE html>
<title>My first CSS-only redesign</title>
<link rel="stylesheet" href="default.css">
<h1>My first CSS-only redesign</h1>
<p>I’ve read about this on a few sites but today I’m actually
  doing it: separating concerns and avoiding anything in the HTML of
  my website that is presentational.
<p>It’s awesome!
  • 省略样式表和脚本类型的type属性
  • 引用属性值时,使用双引号
  • 对class命名时要能保证光从名字就能看出是干什么用的,命名也要简短易懂

CSS规则

  • 避免使用CSS类型选择器
// bad
div.error {}
// good
.error {}
  • 写属性值时尽量使用缩写
// bad
padding-bootom: 2em;
padding-left: 1em;
padding-right: 1em;
padding-top: 0;
...
// good
padding: 0 1em 2em;
  • 除非必要,否则省略0后面的单位
flex: 0px; 
flex: 1 1 0px; /* IE11下会要求添加单位 */
  • 省略0开头小数前面的0
// bad
font-size: 0.8em;    
// good
font-size: .8em;
  • 十六进制尽可能使用3个字符
// bad
color: #eebbcc;
// good
color: #ebc;
  • 用-连接命名,增进对名字的理解和查找
// bad
.demoimage {}
.error_status {}
// good
.video-id {}
  • 避免使用css hacks
  • 选择器在大括号前要添加一个空格
// bad
.test{
    ...
}
// good
.test {
    ...
}
  • 在每个声明尾部都加上分号
// bad
.test {
    display: block;
    height: 100px
}
// good
.test {
    display: block;
    height: 100px;
}
  • 在属性名冒号结束后添加一个空格
// bad
h3 {
    font-size:16px;
}
// good
h3 {
    font-size: 16px;
}
  • 当有多个选择器时,每个选择器都要独立新行
// bad
h1, h2, h3 {

}
// good
h1,
h2,
h3 {

}
  • 多个规则之间要隔行
// bad
html {
    ...
}
body {
    ...
}
// good
html {
    ...
}

body {
    ...
}

这些规则表明看起来会增加css代码量,但实际上我们现在结合webpack等工具,会对css进行压缩,多余的空格会被删除,这些规则方便后续代码的维护

  • 按模块功能写注释
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容