ES6编码规范全部(含node部分)
2016-04-23 09:30
671 查看
http://my.oschina.net/zhangstephen/blog/541289
二、字符串模板
三、数组类型
四、解构类型
五、函数
六、arrow箭头函数
七、对象
八、类
九、模块
十、Iterators 和 Generators
十一、属性访问
十二、map + set + weakmap + weakset 数据结构
十三、promise、symbols、proxies
十四、统一码
十五、进制数支持
十六、不建议使用reflect对象和tail calls尾调用
const和let使用时注意,let 和 const 都是块作用域的
超过80个字符的字符串应该使用字符串连接换行注:如果过度使用长字符串连接可能会对性能有影响。jsPerf & Discussion
编程构建字符串时,使用字符串模板而不是字符串连接
模板给你一个可读的字符串,简洁的语法与适当的换行和字符串插值特性。
如果你不知道数组的长度,使用 push
使用 ... 来拷贝数组,不要使用 Array.from、Array.of等数组的新的内置API,Array新api用于适合的场景
嵌套结构的对象层数不能超过3层
解构语句中统一不使用圆括号
对象解构
对象解构元素与顺序无关对象指定默认值时仅对恒等于undefined ( !== null ) 的情况生效
若函数形参为对象时,使用对象解构赋值
若函数有多个返回值时,使用对象解构,不使用数组解构,避免添加顺序的问题
已声明的变量不能用于解构赋值(语法错误)
数组解构时数组元素与顺序相关
例如交换数组两个元素的值
将数组成员赋值给变量时,使用数组解构
函数有多个返回值时使用对象解构,而不是数组解构。
这样你就可以随时添加新的返回值或任意改变返回值的顺序,而不会导致调用失败。
函数声明拥有函数名,在调用栈中更加容易识别。并且,函数声明会整体提升,而函数表达式只会提升变量本身。这条规则也可以这样描述,始终使用箭头函数来代替函数表达式。
绝对不要在一个非函数块(if,while,等等)里声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但是它们解析不同注:ECMA-262 把 块 定义为一组语句,函数声明不是一个语句。阅读 ECMA-262 对这个问题的说明
绝对不要把参数命名为 arguments, 这将会覆盖函数作用域内传过来的 arguments 对象
永远不要使用 arguments,使用 ... 操作符来代替
... 操作符可以明确指定你需要哪些参数,并且得到的是一个真实的数组,而不是 arguments 这样的类数组对象。
使用函数参数默认值语法,而不是修改函数的实参
箭头函数提供了更简洁的语法,并且箭头函数中 this 对象的指向是不变的,this 对象绑定定义时所在的对象,这通常是我们想要的。如果该函数的逻辑非常复杂,请将该函数提取为一个函数声明。
箭头函数总是用括号包裹参数,省略括号只适用于单个参数,并且还降低了程序的可读性
立即执行的匿名函数
不要使用保留字(reserved words)作为键,否则在 IE8 下将出错
创建对象时使用计算的属性名,而不要在创建对象后使用对象的动态特性,这样可以在同一个位置定义对象的所有属性。
使用定义对象方法的简短形式
使用定义对象属性的简短形式,书写起来更加简单,并且可以自描述。这里和es5有些不同,需要注意下
将所有简写的属性写在对象定义的最顶部,这样可以更加方便地知道哪些属性使用了简短形式。
类名与花括号须保留一个空格间距,类中的方法定义时,括号 ) 也须与花括号 { 保留一个空格间距
定义类时,方法的顺序如下:
constructor
public get/set 公用访问器,set只能传一个参数
public methods 公用方法,公用相关命名使用小驼峰式写法(lowerCamelCase)
private get/set 私有访问器,私有相关命名应加上下划线 _ 为前缀
private methods 私有方法
如果不是class类,不使用new
使用 extends 关键字来继承
这是一个内置的继承方式,并且不会破坏 instanceof 原型检查。
推荐使用import和export来做模块加载
import / export 后面采用花括号{ }引入模块的写法时,建议在花括号内左右各保留一个空格
不要使用通配符 * 的 import,这样确保了一个模块只有一个默认的 export 项
不要直接从一个 import 上 export
虽然一行代码看起来更简洁,但是有一个明确的 import 和一个明确的 export 使得代码行为更加明确。
多变量要导出时应采用对象解构形式
导出单一一个类时,确保你的文件名就是你的类名
导出一个默认小驼峰命名的函数时,文件名应该就是导出的方法名
导出单例、函数库或裸对象时,使用大驼峰命名规则
generators。不推荐使用,或者非常谨慎地使用。
生成器不是用来写异步的,虽然确实有这样一个效果,但这仅仅是一种Hack。异步在未来一定是属于async和await这两个关键字的,但太多人眼里生成器就是写异步用的,这会导致滥用。暂时推荐用promise来实现异步。
使用中括号[] 操作符来访问变量属性
但是,推荐使用weakmap和weakset,而不是map和set,除非必须使用。普通集合会阻止垃圾回收器对这些作为属性键存在的对象的回收,有造成内存泄漏的危险
当你的元素或者键值有可能不是字符串时,推荐使用WeakMap和WeakSet。
有移除操作的需求时,使用WeakMap和WeakSet。
当仅需要一个不可重复的集合时,使用WeakSet优先于普通对象,而不要使用{foo: true}这样的对象。
当需要遍历功能时,使用WeakMap和WeakSet,因为其可以简单地使用for..of进行遍历,性能更高
symbol用于对象的键和私有属性,使用过于复杂,没有使用必要,容易扰乱外层作用域。总之不要使用
Proxy可以监听对象身上发生了什么事情,并在这些事情发生后执行一些相应的操作,没有特别要注意的,尽情用吧。
github地址:https://github.com/ouvens/ecmaScript-2015-babel-rules
Ecmascript6 规范(含node部分)
一、类型规范二、字符串模板
三、数组类型
四、解构类型
五、函数
六、arrow箭头函数
七、对象
八、类
九、模块
十、Iterators 和 Generators
十一、属性访问
十二、map + set + weakmap + weakset 数据结构
十三、promise、symbols、proxies
十四、统一码
十五、进制数支持
十六、不建议使用reflect对象和tail calls尾调用
一、类型规范
对于常量或不修改的变量声明使用const,对于只在当前作用域下有效的变量,应使用let,全局变量使用var。将所有 const 变量放在一起,然后将所有 let 变量放在一起const foo = 1; let foo1 = 2; let bar = foo; bar = 9; foo1 = 3; console.log(foo, bar); // => 1, 9 console.log(foo, bar, str); // => 1, 9,'ouven'
const和let使用时注意,let 和 const 都是块作用域的
// const and let only exist in the blocks they are defined in. { let a = 1; const b = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError
二、字符串
使用单引号 '// bad var name = "Bob Parr"; // good var name = 'Bob Parr'; // bad var fullName = "Bob " + this.lastName; // good var fullName = 'Bob ' + this.lastName;
超过80个字符的字符串应该使用字符串连接换行注:如果过度使用长字符串连接可能会对性能有影响。jsPerf & Discussion
// 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.';
编程构建字符串时,使用字符串模板而不是字符串连接
模板给你一个可读的字符串,简洁的语法与适当的换行和字符串插值特性。
// bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return ['How are you, ', name, '?'].join(); } // good function sayHi(name) { return `How are you, ${name}?`; }
三、数组类型
使用字面量语法创建数组// bad const items = new Array(); // good const items = [];
如果你不知道数组的长度,使用 push
const someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
使用 ... 来拷贝数组,不要使用 Array.from、Array.of等数组的新的内置API,Array新api用于适合的场景
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items]; // not good const foo = [1,2,3]; const nodes = Array.from(foo);
四、解构 Destructuring
使用对象的多个属性时请建议使用对象的解构赋值,解构赋值避免了为这些属性创建临时变量或对象。即使转化成es5都是一样的嵌套结构的对象层数不能超过3层
// not good let obj = { 'one': [ { 'newTwo': [ { 'three': [ 'four': '太多层了,头晕晕' ] } ] } ] }; // good let obj = { 'one': [ 'two', { 'twoObj': '结构清晰' } ] };
解构语句中统一不使用圆括号
// not good [(a)] = [11]; // a未定义 let { a: (b) } = {}; // 解析出错 // good let [a, b] = [11, 22];
对象解构
对象解构元素与顺序无关对象指定默认值时仅对恒等于undefined ( !== null ) 的情况生效
若函数形参为对象时,使用对象解构赋值
// not good function someFun(opt) { let opt1 = opt.opt1; let opt2 = opt.opt2; console.log(op1); } // good function someFun(opt) { let { opt1, opt2 } = opt; console.log(`$(opt1) 加上 $(opt2)`); } function someFun({ opt1, opt2 }) { console.log(opt1); }
若函数有多个返回值时,使用对象解构,不使用数组解构,避免添加顺序的问题
// not good function anotherFun() { const one = 1, two = 2, three = 3; return [one, two, three]; } const [one, three, two] = anotherFun(); // 顺序乱了 // one = 1, two = 3, three = 2 // good function anotherFun() { const one = 1, two = 2, three = 3; return { one, two, three }; } const { one, three, two } = anotherFun(); // 不用管顺序 // one = 1, two = 2, three = 3
已声明的变量不能用于解构赋值(语法错误)
// 语法错误 let a; { a } = { b: 123};
数组解构时数组元素与顺序相关
例如交换数组两个元素的值
let x = 1; let y = 2; // not good let temp; temp = x; x = y; y = temp; // good [x, y] = [y, x]; // 交换变量
将数组成员赋值给变量时,使用数组解构
const arr = [1, 2, 3, 4, 5]; // not good const one = arr[0]; const two = arr[1]; // good const [one, two] = arr;
函数有多个返回值时使用对象解构,而不是数组解构。
这样你就可以随时添加新的返回值或任意改变返回值的顺序,而不会导致调用失败。
function processInput(input) { // then a miracle occurs return [left, right, top, bottom]; } // the caller needs to think about the order of return data const [left, __, top] = processInput(input); // good function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // the caller selects only the data they need const { left, right } = processInput(input);
五、函数
使用函数声明而不是函数表达式函数声明拥有函数名,在调用栈中更加容易识别。并且,函数声明会整体提升,而函数表达式只会提升变量本身。这条规则也可以这样描述,始终使用箭头函数来代替函数表达式。
// bad const foo = function () { }; // good function foo() { }
绝对不要在一个非函数块(if,while,等等)里声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但是它们解析不同注:ECMA-262 把 块 定义为一组语句,函数声明不是一个语句。阅读 ECMA-262 对这个问题的说明
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good if (currentUser) { var test = function test() { console.log('Yup.'); }; }
绝对不要把参数命名为 arguments, 这将会覆盖函数作用域内传过来的 arguments 对象
// bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... }
永远不要使用 arguments,使用 ... 操作符来代替
... 操作符可以明确指定你需要哪些参数,并且得到的是一个真实的数组,而不是 arguments 这样的类数组对象。
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
使用函数参数默认值语法,而不是修改函数的实参
// really bad function handleThings(opts) { opts = opts || {}; } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } } // good function handleThings(opts = {}) { // ... }
六、箭头函数 Arrow Functions
当必须使用函数表达式时(例如传递一个匿名函数时),请使用箭头函数箭头函数提供了更简洁的语法,并且箭头函数中 this 对象的指向是不变的,this 对象绑定定义时所在的对象,这通常是我们想要的。如果该函数的逻辑非常复杂,请将该函数提取为一个函数声明。
// bad "use strict"; var fn = function fn(v) { return console.log(v); }; // good var fn= (v=>console.log(v));
箭头函数总是用括号包裹参数,省略括号只适用于单个参数,并且还降低了程序的可读性
// bad [1, 2, 3].forEach(x => x * x); // good [1, 2, 3].forEach((x) => x * x);
立即执行的匿名函数
// 函数表达式 // immediately-invoked function expression (IIFE) // good,看起来就很厉害 (() => { console.log('Welcome to the Internet. Please follow me.'); })();
七、对象
使用对象字面量创建对象// bad var item = new Object(); // good var item = {};
不要使用保留字(reserved words)作为键,否则在 IE8 下将出错
// bad var superman = { class: 'superhero', default: { clark: 'kent' }, private: true }; // good var superman = { klass: 'superhero', defaults: { clark: 'kent' }, hidden: true };
创建对象时使用计算的属性名,而不要在创建对象后使用对象的动态特性,这样可以在同一个位置定义对象的所有属性。
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 };
使用定义对象方法的简短形式
// bad const atom = { value: 1, addValue: function (value) { return atom.value + value; } }; // good const atom = { value: 1, addValue(value) { return atom.value + value; } };
使用定义对象属性的简短形式,书写起来更加简单,并且可以自描述。这里和es5有些不同,需要注意下
const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker }; // good const obj = { lukeSkywalker };
将所有简写的属性写在对象定义的最顶部,这样可以更加方便地知道哪些属性使用了简短形式。
const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJedisWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJedisWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4 };
八、类
总是使用 class 关键字,避免直接修改 prototype,class 语法更简洁,也更易理解。// 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; } }
类名与花括号须保留一个空格间距,类中的方法定义时,括号 ) 也须与花括号 { 保留一个空格间距
// not good class Foo{ constructor(){ // constructor } sayHi() { // 仅保留一个空格间距 } } // good class Foo { constructor() { // constructor } sayHi() { // 仅保留一个空格间距 } }
定义类时,方法的顺序如下:
constructor
public get/set 公用访问器,set只能传一个参数
public methods 公用方法,公用相关命名使用小驼峰式写法(lowerCamelCase)
private get/set 私有访问器,私有相关命名应加上下划线 _ 为前缀
private methods 私有方法
// good class SomeClass { constructor() { // constructor } get aval() { // public getter } set aval(val) { // public setter } doSth() { // 公用方法 } get _aval() { // private getter } set _aval() { // private setter } _doSth() { // 私有方法 } }
如果不是class类,不使用new
// not good function Foo() { } const foo = new Foo(); // good class Foo { } const foo = new Foo();
使用 extends 关键字来继承
这是一个内置的继承方式,并且不会破坏 instanceof 原型检查。
// bad const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function() { return this._queue[0]; } // good class PeekableQueue extends Queue { peek() { return this._queue[0]; } }
九、模块
总是在非标准的模块系统中使用标准的 import 和 export 语法,我们总是可以将标准的模块语法转换成支持特定模块加载器的语法。推荐使用import和export来做模块加载
// bad const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // best import { es6 } from './AirbnbStyleGuide'; export default es6;
import / export 后面采用花括号{ }引入模块的写法时,建议在花括号内左右各保留一个空格
// not good import {lightRed} from './colors'; import { lightRed} from './colors'; // good import { lightRed } from './colors';
不要使用通配符 * 的 import,这样确保了一个模块只有一个默认的 export 项
// bad import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide';
不要直接从一个 import 上 export
虽然一行代码看起来更简洁,但是有一个明确的 import 和一个明确的 export 使得代码行为更加明确。
// bad // filename es6.js export default { es6 } from './airbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6;
多变量要导出时应采用对象解构形式
// not good export const a= 'a'; export const b= 'b'; // good export const a= 'a'; export const b= 'b'; export default { a, b };
导出单一一个类时,确保你的文件名就是你的类名
// file contents class CheckBox { // ... } module.exports = CheckBox; // in some other file // bad const CheckBox = require('./checkBox'); // bad const CheckBox = require('./check_box'); // good const CheckBox = require('./CheckBox');
导出一个默认小驼峰命名的函数时,文件名应该就是导出的方法名
function makeStyleGuide() { } export default makeStyleGuide;
导出单例、函数库或裸对象时,使用大驼峰命名规则
const AirbnbStyleGuide = { es6: { } }; export default AirbnbStyleGuide;
十、Iterators 和 Generators
Iterators。性能比较差,对于数组来说大致与Array.prototype.forEach相当,比不过原生的for循环,而且用起来比较麻烦,数组提供了for...of,对象提供了for...in,不推荐使用迭代器。const numbers = [1, 2, 3, 4, 5]; // bad var iterator = numbers[Symbol.iterator](); var result = iterator.next(); let sum = 0; while (!result.done) { sum += result.value; result = iterator.next(); } // good let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // good let sum = 0; numbers.forEach((num) => sum += num); sum === 15; // best (use the functional force) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15;
generators。不推荐使用,或者非常谨慎地使用。
生成器不是用来写异步的,虽然确实有这样一个效果,但这仅仅是一种Hack。异步在未来一定是属于async和await这两个关键字的,但太多人眼里生成器就是写异步用的,这会导致滥用。暂时推荐用promise来实现异步。
十一、属性访问
使用点 . 操作符来访问常量属性const luke = { jedi: true, age: 28 }; // bad const isJedi = luke['jedi']; // good const isJedi = luke.jedi;
使用中括号[] 操作符来访问变量属性
var luke = { jedi: true, age: 28 }; function getProp(prop) { return luke[prop]; } var isJedi = getProp('jedi');
十二、map + set + weakmap + weakset 数据结构
新加的集合类型,提供了更加方便的获取属性值的方法,可以检查某个属性是属于原型链上还是当前对象的,并用获取对象的set和get方法但是,推荐使用weakmap和weakset,而不是map和set,除非必须使用。普通集合会阻止垃圾回收器对这些作为属性键存在的对象的回收,有造成内存泄漏的危险
// not good, Maps var wm = new Map(); wm.set(key, { extra: 42 }); wm.size === 1 // not good, Sets var ws = new Set(); ws.add({ data: 42 }); // good, Weak Maps var wm = new WeakMap(); wm.set(key, { extra: 42 }); wm.size === undefined // good, Weak Sets var ws = new WeakSet(); ws.add({ data: 42 });//因为添加到ws的这个临时对象没有其他变量引用它,所以ws不会保存它的值,也就是说这次添加其实没有意思 // not good let object = {}, object.hasOwnProperty(key) // good let object = new WeakSet(); object.has(key) === true;
当你的元素或者键值有可能不是字符串时,推荐使用WeakMap和WeakSet。
// bad var obj = { 3:'value' }; // good var ws = new WeakSet(); ws.add(3, 'value');
有移除操作的需求时,使用WeakMap和WeakSet。
// bad var obj = { 'key':'value' }; delete obj.key; // good var ws = new WeakSet(); ws.add('key', 'value'); ws.remove('key');
当仅需要一个不可重复的集合时,使用WeakSet优先于普通对象,而不要使用{foo: true}这样的对象。
// bad var obj = { 'key':'value' }; // good var ws = new WeakSet(); ws.add('key', 'value');
当需要遍历功能时,使用WeakMap和WeakSet,因为其可以简单地使用for..of进行遍历,性能更高
// bad var obj = { key:'value', key1: 'value1' }; for(var key in obj){ } // good var ws = new WeakSet(); ws.add('key', 'value').add('key1', 'value1'); for(var key of ws){ }
十三、promise、symbols、proxies
promise是一种异步处理模式。发promise申明和调用分开,推荐异步方式使用Promise。// not good (new Promise(resolve, reject){}) .then(cunction(){},function(){}) .then(); // good var promise = new Primise(function(resolve, reject){}); promise .then(function(){},function(){]}) .then();
symbol用于对象的键和私有属性,使用过于复杂,没有使用必要,容易扰乱外层作用域。总之不要使用
// good function MyClass(privateData) { let key = privateData; } //not good const key = Symbol('key'); function MyClass(privateData) { this[key] = privateData; } const object = new MyClass("hello") object['key'] === undefined //无法访问该属性,因为是私有的
Proxy可以监听对象身上发生了什么事情,并在这些事情发生后执行一些相应的操作,没有特别要注意的,尽情用吧。
十四、不要使用统一码
字符串支持新的Unicode文本形式,也增加了新的正则表达式修饰符u来处理码位,但是一般不要这样处理,会减低程序可读性且处理统一码速度会降低// not good '字符串'.length == 6 // 新加的:正则支持统一码'u', 但仍建议不使用 // not good '字符串'.match(/./u)[0].length == 6 '字符串'.codePointAt(0) == 0x20BB7
十五、进制数支持
加入对二进制(b)和八进制(o)字面量的支持。该特性可以使用// ok 0b111110111 === 503 // true 0o767 === 503 // true
十六、不建议使用reflect对象和tail calls尾调用
没有使用的必要性参考:
http://bubkoo.com/2015/04/05/javascript-style-guide/?utm_source=tuicoolhttps://github.com/gf-rd/es6-coding-style#%E5%A3%B0%E6%98%8Ehttps://github.com/ES-CN/es6features/blob/master/README.md?utm_source=tuicool&utm_medium=referralgithub地址:https://github.com/ouvens/ecmaScript-2015-babel-rules
相关文章推荐
- 新时代编辑神器:Atom
- 基于 Node.js 实现前后端分离
- 【一天一道LeetCode】#24. Swap Nodes in Pairs
- 【一天一道LeetCode】#24. Swap Nodes in Pairs
- 搭建Hadoop2集群出现Datanode启动不了的问题及解决办法
- Node.js#0基础
- Install Latest Versions NodeJS NPM on ubuntu
- 菜鸟学习nodejs--安装nodejs
- 欢迎使用CSDN-markdown编辑器
- NodeJS项目构建工具Grunt
- Pomelo网易开源基于Node.js的游戏服务器框架pomelo
- 从Node.js分裂出Io.js事件看开源软件谁做主
- nodejs php md5 加密不一致问题
- Node 究竟是什么
- Hadoop学习9--动态增加datanode
- node应用通过multer模块实现文件上传
- 关于NodeJS的Cookie和Session机制
- Windows环境下使用Node.js
- 旧版 npm 安装 node_modules 模块目录层次过深,导致 win 下无法删除
- MonoDevelop几个常用的快捷键