浅谈让你的代码更简短,更整洁,更易读的ES6小技巧

写在文章前面

这篇文章翻译自ES6 tips and tricks to make your code cleaner, shorter, and easier to read!. 文章就代码整洁方面对es6进行了总结。如有错误欢迎指出。

template literals 模板字符串

模板字符串使字符串的使用变得比以前更简单了,他们以反引号开始(`),并且能过使用${变量}来插入变量。我们来比较一下下面两行代码。

var fName = 'Peter', sName = 'Smith', age = 43, job= 'photographer';
var a = 'Hi, I\'m ' + fName + ' ' + sName + ', I\'m ' + age + ' and work as a ' + job + '.';
var b = `Hi, I'm ${ fName } ${ sName }, I'm ${ age } and work as a ${ job }.`;

一切都变得很美好了是不是,代码更易读了是不是?你可以在大括号内放入任何东西:变量,等式,或者函数的调用。 我将会在后面的整个文章的示例中使用这些方式。

块级作用域语法

JavaScript是使用函数作用域的,这就是为什么我们是为什么我们越来越频繁的使用匿名的立即执行函数表达式(iife)来实现整个JavaScript文件的封装。我们这么做是为了把所有的变量隔离在文件内从而避免变量冲突。
现在我们有了块级作用域和两个崭新的块级作用域的变量声明

let declaration let命令

这个命令和var很相似但却又有着显著的不同。因为他是有块级作用域的,声明一个相同名字的新变量可以完全不影响外部的变量。

var a = 'car' ;
{
  let a = 5;
  console.log(a) // 5
}
console.log(a) // car

因为他是被限制在块级作用域的,他解决了那道非常经典的面试题:“下面这个代码的输出是什么,如何修改让他运行之后成为你想的那个样子?”

for (var i = 1; i < 5; i++){
  setTimeout(() => { console.log(i); }, 1000);
}

这个例子中,输出是“5 5 5 5 5”因为变量i在每次迭代中都会改变。

如果我们把var变为let,一切都变了。 现在,每次循环都会创建一个全新的块级作用域吧i限制在当前的循环,他可以理解为这样:

{let i = 1; setTimeout(() => { console.log(i) }, 1000)}
{let i = 2; setTimeout(() => { console.log(i) }, 1000)}
{let i = 3; setTimeout(() => { console.log(i) }, 1000)}
{let i = 4; setTimeout(() => { console.log(i) }, 1000)}
{let i = 5; setTimeout(() => { console.log(i) }, 1000)}

var 和 let的另外一个区别是 let 不会像 var一样被变量提升

{
  console.log(a); // undefined
  console.log(b); // ReferenceError
  var a = 'car';
  let b = 5;
}

因为他有更为局限的作用域,以及更能被预测的行为,因此一些人甚至认为你应该使用let来代替var, 除非当你真的特别需要变量提升或者更宽松的作用域范围,你再使用var

Const

在以前,如果你想在JavaScript中声明一个常量, 习惯性的做法是使用全大写来命名。然鹅,这不是真的去保护了这个变量不能被更改---只是让其他的开发者知道,这是一个常量,它不应该被更改。

现在我们有了const命令.

const没有让变量完全不可变,只是锁定他的赋值,当你有一个复杂的变量(数组或者对象)的时候,值还是可以被修改的。

{
  const d = [1, 2, 3, 4];
  const dave = { name: 'David Jones', age: 32};
  d.push(5);
  dave.job = "salesman";
  console.log(d); // [1, 2, 3, 4, 5]
  console.log(dave); // { age: 32, job: "salesman", name: 'David Jones'}
}

Problem with block scoping functions函数块级作用域化带来的问题

函数的声明也可以限制在块级作用域中。

{
  bar(); // works
  function bar() { /* do something */ }
}
bar(); // doesn't work

但是当你在一个if语句中声明一个函数的时候问题来了。

想一下这种情况:

if ( something) {
  function baz() { console.log('I passed') }
} else {
  function baz() { console.log('I didn\'t pass') }
}
baz();

在ES6之前,这两个函数声明都被变量提升,而且结果一定是I didn't pass 不论条件中的something是什么。但现在我们会得到输出ReferenceError, 因为 baz一直被限定在块级作用域内。

Spread 扩展运算符

ES6介绍了...操作符,这个操作符指的就是‘扩展运算符‘。他的主要用途有两个:1. 将一个数组或者对象放到一个新的数组或者对象中 2. 将数组中的多个参数合并在一起

第一个用途可能是你将会使用的最多的。所以我们先来看他。

let a = [3, 4, 5];
let b = [1, 2, ...a, 6];
console.log(b); // [1, 2, 3, 4, 5, 6]

如果我们想把一个数组内的一组参数传递给函数,这个时候扩展运算符就十分的有用了。

function foo(a, b, c) {
console.log(`a=${a}, b=${b}, c=${c}`)
}
let data = [5, 15, 2];
foo( ...data); // a=5, b=15, c=2

一个对象也可以扩展的,它会把每个键值对写入新的对象中。( 对象扩展已经在提议的第四阶段,而且将会在es2018中正式出现 。但这种特性目前只被chrome60及以后的版本,Firefox55及以后,node 6.4.0及以后的版本所支持)【译者注:在2ality博客中的es2018一文中得知,在刚刚结束的TC39会议中,ECMA2018的特性被敲定了。

let car = { type: 'vehicle ', wheels: 4};
let fordGt = { make: 'Ford', ...car, model: 'GT'};
console.log(fordGt); // {make: 'Ford', model: 'GT', type: 'vehicle', wheels: 4}

扩展运算符的另一个特点是,他可以生成一个新的数组或者对象. 下面的这个例子,就是b就是新建的数组,但c只是引用同一个数组。

let a = [1, 2, 3];
let b = [ ...a ];
let c = a;
b.push(4);
console.log(a); // [1, 2, 3]
console.log(b); // [1, 2, 3, 4] 不同的数组
c.push(5);
console.log(a); // [1, 2, 3, 5]
console.log(c); // [1, 2, 3, 5] 同一个数组

第二个用法是把变量聚集到一个数组里面。当你不知道一个函数到底有多少的传参的时候会这个方法会变得非常的有用。

function foo(...args) {
  console.log(args);
}
foo( 'car', 54, 'tree'); // [ 'car', 54, 'tree' ]

Default Parameter 参数默认值

函数现在可以使用默认的参数值来定义了。不传参或者未定义值都被初始化为默认值。但是需要注意的是,null和false都会被强转为0.

function foo( a = 5, b = 10) {
  console.log( a + b);
}
foo(); // 15
foo( 7, 12 ); // 19
foo( undefined, 8 ); // 13
foo( 8 ); // 18
foo( null ); // 10 as null is coerced to 0

默认值的类型可以不仅仅是值类型---还可以是表达式或者函数。

function foo( a ) { return a * 4; }
function bar( x = 2, y = x + 4, z = foo(x)) {
  console.log([ x, y, z ]);
}
bar(); // [ 2, 6, 8 ]
bar( 1, 2, 3 ); //[ 1, 2, 3 ]
bar( 10, undefined, 3 ); // [ 10, 14, 3 ]

Destructuring解构

解构是拆开等号左边的数组或者对象的过程。这个数组或者对象可以来自一个变量,一个函数,或者一个等式

let [ a, b, c ] = [ 6, 2, 9];
console.log(`a=${a}, b=${b}, c=${c}`); //a=6, b=2, c=9

function foo() { return ['car', 'dog', 6 ]; }
let [ x, y, z ] = foo();
console.log(`x=${x}, y=${y}, z=${z}`); // x=car, y=dog, z=6

对象类型的结构,可以在花括号内列出对象的键来提取键值对。

function bar() { return {a: 1, b: 2, c: 3}; }
let { a, c } = bar();
console.log(a); // 1
console.log(c); // 3
console.log(b); // undefined

有时,你可能想提取出值然后费赔给新的变量,这个可以通过在等号左侧使用一个“key:variable”(键:变量名)来完成。

function baz() {
  return {
    x: 'car',
    y: 'London',
    z: { name: 'John', age: 21}
  };
}
let { x: vehicle, y: city, z: { name: driver } } = baz();
console.log(
  `I'm going to ${city} with ${driver} in their ${vehicle}.`
); // I'm going to London with John in their car.

此外,对象的结构允许给多个变量赋值。

let { x: first, x: second } = { x: 4 };
console.log( first, second ); // 4, 4

对象字面量和属性的简洁表达法

当你从许多参数创建对象字面量的时候,ES6允许你在键与变量名字相同的情况下省略该键。

let a = 4, b = 7;
let c = { a: a, b: b };
let concise = { a, b };
console.log(c, concise) // {a: 4, b: 7}, {a: 4, b: 7}

这个还可以与解构一起用来使你的代码更干净整洁。

function foo() {
  return {
    name: 'Anna',
    age: 56,
    job: { company: 'Tesco', title: 'Manager' }
  };
}
// pre ES6
let a = foo(), name = a.name, age = a.age, company = a.job.company;
// ES6 destructuring and concise parameters
let { name, age, job: {company}} = foo();

简洁表示法还可以用于解构对象并把它传入函数。方法1和2是你在es6之前要怎么做, 方法三是使用解构和简洁表达法。

let person = {
  name: 'Anna',
  age: 56,
  job: { company: 'Tesco', title: 'Manager' }
};
// method 1
function old1( person) {
  var yearOfBirth = 2018 - person.age;
  console.log( `${ person.name } works at ${ person.job.company } and was born in ${ yearOfBirth }.`);
}
// method 2
function old1( person) {
  var age = person.age,
    yearOfBirth = 2018 - age,
    name = person.name,
    company = person.job.company;
  console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
}
// method 3
function es6({ age, name, job: {company}}) {
  var yearOfBirth = 2018 - age;
  console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
}

通过使用ES6,我们能提取出age,name,和 company,而不需要任何其他的变量声明。

动态属性名称

ES6添加了使用动态分配的键创建或添加属性的功能。

let city= 'sheffield_';
let a = {
  [ city + 'population' ]: 350000
};
a[ city + 'county' ] = 'South Yorkshire';
console.log(a); // {sheffield_population: 350000, sheffield_county: 'South Yorkshire' }

箭头函数

箭头函数有两个比较重要的特点: 他们的结构以及他们的this 指向

他们比传统的函数有更简单的结构因为他们不需要关键字function 而且他们可以自动返回在箭头后面的一部分,无论箭头后面的是什么。

var foo = function( a, b ) {
  return a * b;
}
let bar = ( a, b ) => a * b;

如果函数有多于一个的计算式,可以使用花括号来包起来,然后函数返回块作用域返回的任何内容。

箭头函数一个最重要的用途之一就是应用在数组的相关函数中,像.map,.forEach,.sort等等。

let arr = [ 5, 6, 7, 8, 'a' ];
let b = arr.map( item => item + 3 );
console.log(b); // [ 8, 9, 10, 11, 'a3' ]

在拥有一个更短的表达方式的同时,箭头函数还修复了有关于this绑定行为经常出现的问题。ES6之前解决这个问题通常是使用一个self变量来存储这个指向。

var clickController = {
  doSomething: function (..) {
    var self = this;
    btn.addEventListener(
      'click',
      function() { self.doSomething(..) },
      False
    );
  }
};

这个this的赋值是一定要做的,因为this的绑定是动态的。这就意味着this在eventlistener内部和在doSomething内部指的并不是同一个东西。

在箭头函数内部,this的绑定是语义上的就是指当前的,而不是动态的。这也是箭头函数的主要设计特点。

虽然这种词法上的this很棒,但是有些时候,他却不是我们想要的那样。

let a = {
  oneThing: ( a ) => {
     let b = a * 2;
     this.otherThing(b);
  },
  otherThing: ( b ) => {....}
};
a.oneThing(6);

当我们使用a.oneThing(6),  这个this.otherThing(6) 会抛出引用失败的错误,因为this没有指向对象a,而是指向了环境作用域。如果你正在使用ES6的代码使用ES6的语法,这个是你需要注意的事情。

for...of loops (for...of循环)

ES6新添加了一种方式来迭代数组中的每个值,这个方式是与已经存在的for...in的通过索引的循环方式不同。

let a = ['a', 'b', 'c', 'd' ];
// ES6
for ( var val of a ) {
  console.log( val );
} // "a" "b" "c" "d"
// pre-ES6
for ( var idx in a ) {
  console.log( idx );
} // 0 1 2 3

使用新的for ... of循环,在每个循环内部保存了一个let val = a[idx]。

数组,字符串,generator以及从collection 在标准JavaScript中都是可迭代的。普通的对象无法正常的使用for...of来迭代,除非你自己定义一个迭代器。

Number Literals 数字字面量

ES5代码很好处理了十进制和十六进制的数字格式,但并未指定八进制的格式。实际上,八进制在严格模式中是被禁止使用的。

ES6 添加了一个全新的格式,在最开始的0后面添加一个o来声明一个八进制的数。与此同时,在es6中还添加了二进制格式。

Number( 29 ) // 29
Number( 035 ) // 35 in old octal form.
Number( 0o35 ) // 29 in new octal form
Number( 0x1d ) // 29 in hexadecimal
Number( 0b11101 ) // 29 in binary form

更多

ES6还提供了我们很多很多其他的方式来使我们的代码更简洁,更易读,以及更稳定。我的目标时写一篇这篇文章的延续,来包括一些ES6中不太知名的部分。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • ES6小技巧之代替lodash

    Javascript ES6标准实行后,Lodash或者Ramada中的一些功能我们就不需要了,可以使用ES6的语法来实现 获取Object中指定键值 我们现在可以使用解包的方法快速获取对象中指定键值的值 const obj = { a:1, b:2, c:3, d:4 }; // 获取obj中a与b的值 const {a,b} = obj; // 也可以给他们取别名 const {a:A, b:B} = obj; 这个小技巧非常的方便,也是最基础的使用方法 排除Object中不需要的键值 既然

  • 你点的 ES6一些小技巧,请查收

    ES6出来已经有好几年了,同时很多新特性可以被巧妙地运用在项目中.我想要列下其中一些,希望它们对你有用. 如果你还知道其他一些小技巧,欢迎留言.我很高兴把它们补充进来. 1. 强制要求参数 ES6提供了默认参数值机制,允许你为参数设置默认值,防止在函数被调用时没有传入这些参数. 在下面的例子中,我们写了一个required()函数作为参数a和b的默认值.这意味着如果a或b其中有一个参数没有在调用时传值,会默认required()函数,然后抛出错误. const required = () =>

  • 不得不知的ES6小技巧

    如果你还知道其他一些小技巧,欢迎留言~ 很高兴把它们补充进来. 1. 强制要求参数 ES6提供了默认参数值机制,允许你为参数设置默认值,防止在函数被调用时没有传入这些参数. 在下面的例子中,我们写了一个required()函数作为参数a和b的默认值.这意味着如果a或b其中有一个参数没有在调用时传值,会默认required()函数,然后抛出错误. const required = () => {throw new Error('Missing parameter')}; const add = (

  • 浅谈让你的代码更简短,更整洁,更易读的ES6小技巧

    写在文章前面 这篇文章翻译自ES6 tips and tricks to make your code cleaner, shorter, and easier to read!. 文章就代码整洁方面对es6进行了总结.如有错误欢迎指出. template literals 模板字符串 模板字符串使字符串的使用变得比以前更简单了,他们以反引号开始(`),并且能过使用${变量}来插入变量.我们来比较一下下面两行代码. var fName = 'Peter', sName = 'Smith', ag

  • 浅谈如何提高PHP代码质量之单元测试

    1.单元测试 通过实现单一责任原则(我们的代码应该只关注功能的单个部分),我们将确保在测试期间,我们只会同时关注项目的一小部分 通过使用 Liskov 替换原则和依赖倒置原则,我们的代码不会关心我们是否注入模拟依赖关系,只要它们实现了适当的接口 在单元测试中,我们确实希望用模拟对象替换所有依赖的服务,因此我们一次只测试一个类.但模拟是什么?它们是实现与其他对象相同的接口的对象,但它们的行为是受控的.例如,假设我们在创建一个价格比较服务,我们利用另一个服务来获取当前的汇率.在测试我们的比较器时,我

  • 浅谈如何提高PHP代码的质量

    概述 我们可以将此归咎于许多原因,但这肯定不仅仅是因为 PHP 生态系统缺乏适当的测试工具.在本文中,我想向您展示一个简单的设置,用于项目的基本质量测试. 我不会详述任何特定的工具,而是专注于设定测试环境. 本文中有一个演示代码可以在 GitHub 上找到:https://github.com/mkosiedowski/php-testing-demo如果你对这篇文章中的例子有任何问题,可以参考. 1 必备条件 我假设您熟悉 PHP 7.1 语法,您可以使用 Composer 和 PSR-4 来

  • 浅谈Java循环中的For和For-each哪个更快

    目录 for-each实现方法 基准测试 原因分析 结论 对于Java循环中的For和For-each,哪个更快 通过本文,您可以了解一些集合遍历技巧. Java遍历集合有两种方法.一个是最基本的for循环,另一个是jdk5引入的for each.通过这种方法,我们可以更方便地遍历数组和集合.但是你有没有想过这两种方法?哪一个遍历集合更有效? for-each实现方法 For-each不是一种新语法,而是Java的语法糖.在编译时,编译器将此代码转换为迭代器实现,并将其编译为字节码.我们可以通过

  • 浅谈如何提高PHP代码质量之端到端集成测试

    概述 在这里,我们可以使用为行为驱动开发构建的工具--官方 PHP 的 Cucumber 实现--Behat.我们可以通过运行以下代码来安装它: $ php composer.phar require --dev behat/behat 增加一个目标到 build.xml(在本文的第一部分中描述了 Phing 设置) <target name="behat"> <exec executable="bin/behat" passthru="

  • 浅谈一段java代码是如何执行的

    本文分享自华为云社区<一段java代码是如何执行的>,原文作者:技术火炬手 . 当你学会了java语言之后,你写了一些代码,然后你想要执行你的代码,来达成某些功能.那么,你都知道这段代码都是如何执行的吗? 1. 编译成class 众所周知,java代码是不能直接在jvm上执行的,执行的是class文件,将java代码编程class文件,需要编译 常用的编译方法是:javac xxx.java 但目前常见的java编辑工具,如eclipse和ideal都自带自动编译动能 2. jvm的构成 让我

  • 浅谈如何测试Python代码

    目录 一.介绍 二.测试范围 三.单元测试 四.第一个测试用例 五.异常测试 六.mounttab.py 七.测试覆盖率 八.总结 一.介绍 编写测试检验应用程序所有不同的功能.每一个测试集中在一个关注点上验证结果是不是期望的.定期执行测试确保应用程序按预期的工作.当测试覆盖很大的时候,通过运行测试你就有自信确保修改点和新增点不会影响应用程序. 知识点 单元测试概念 使用 unittest 模块 测试用例的编写 异常测试 测试覆盖率概念 使用 coverage 模块 二.测试范围 如果可能的话,

  • 浅谈PHP 闭包特性在实际应用中的问题

    呃,其实大部分情况下是可以的,而有些方面还是令人非常的困扰,下面慢慢道来. 很多语言的都提供了非常优雅和漂亮的操作数组的方法.在下面的例子中,会使用 PHP5.3 以及其他语言提供的闭包功能,用于展示如何"客观的"操作迭代数组. 译注:原文作者比较火星,我不了解 Groovy 以及 Scala 语言,所以这里我加上 Javascript 的实现. 在开始之前先说明下,本例子仅仅是阐明观点,并没有考虑性能等其他方面的因素. "货比三家" 用个简单的例子开始,有下面个数

  • 浅谈Java中的this作为返回值时返回的是什么

    有时会遇到this作为返回值的情况,那么此时返回的到底是什么呢? 返回的是调用this所处方法的那个对象的引用,读起来有点绕口哈,有没有想起小学语文分析句子成份的试题,哈哈. 一点点分析的话,主干是"返回的是引用": 什么引用呢?"那个对象的引用": 哪个对象呢?"调用方法的那个对象": 调用的哪个方法呢?"调用的是this所位于的方法":这样就清楚了. 再总结一下就是,this作为返回值时,返回的是调用某方法的对象的引用,这

  • 浅谈python中统计计数的几种方法和Counter详解

    1) 使用字典dict() 循环遍历出一个可迭代对象中的元素,如果字典没有该元素,那么就让该元素作为字典的键,并将该键赋值为1,如果存在就将该元素对应的值加1. lists = ['a','a','b',5,6,7,5] count_dict = dict() for item in lists: if item in count_dict: count_dict[item] += 1 else: count_dict[item] = 1 2) 使用defaultdict() defaultdi

随机推荐