浅谈让你的代码更简短,更整洁,更易读的ES6小技巧
写在文章前面
这篇文章翻译自ES6tipsandtrickstomakeyourcodecleaner,shorter,andeasiertoread!.文章就代码整洁方面对es6进行了总结。如有错误欢迎指出。
templateliterals模板字符串
模板字符串使字符串的使用变得比以前更简单了,他们以反引号开始(`),并且能过使用${变量}来插入变量。我们来比较一下下面两行代码。
varfName='Peter',sName='Smith',age=43,job='photographer'; vara='Hi,I\'m'+fName+''+sName+',I\'m'+age+'andworkasa'+job+'.'; varb=`Hi,I'm${fName}${sName},I'm${age}andworkasa${job}.`;
一切都变得很美好了是不是,代码更易读了是不是?你可以在大括号内放入任何东西:变量,等式,或者函数的调用。我将会在后面的整个文章的示例中使用这些方式。
块级作用域语法
JavaScript是使用函数作用域的,这就是为什么我们是为什么我们越来越频繁的使用匿名的立即执行函数表达式(iife)来实现整个JavaScript文件的封装。我们这么做是为了把所有的变量隔离在文件内从而避免变量冲突。
现在我们有了块级作用域和两个崭新的块级作用域的变量声明
letdeclarationlet命令
这个命令和var很相似但却又有着显著的不同。因为他是有块级作用域的,声明一个相同名字的新变量可以完全不影响外部的变量。
vara='car'; { leta=5; console.log(a)//5 } console.log(a)//car
因为他是被限制在块级作用域的,他解决了那道非常经典的面试题:“下面这个代码的输出是什么,如何修改让他运行之后成为你想的那个样子?”
for(vari=1;i<5;i++){ setTimeout(()=>{console.log(i);},1000); }
这个例子中,输出是“55555”因为变量i在每次迭代中都会改变。
如果我们把var变为let,一切都变了。现在,每次循环都会创建一个全新的块级作用域吧i限制在当前的循环,他可以理解为这样:
{leti=1;setTimeout(()=>{console.log(i)},1000)} {leti=2;setTimeout(()=>{console.log(i)},1000)} {leti=3;setTimeout(()=>{console.log(i)},1000)} {leti=4;setTimeout(()=>{console.log(i)},1000)} {leti=5;setTimeout(()=>{console.log(i)},1000)}
var和let的另外一个区别是let不会像var一样被变量提升
{ console.log(a);//undefined console.log(b);//ReferenceError vara='car'; letb=5; }
因为他有更为局限的作用域,以及更能被预测的行为,因此一些人甚至认为你应该使用let来代替var,除非当你真的特别需要变量提升或者更宽松的作用域范围,你再使用var
Const
在以前,如果你想在JavaScript中声明一个常量,习惯性的做法是使用全大写来命名。然鹅,这不是真的去保护了这个变量不能被更改---只是让其他的开发者知道,这是一个常量,它不应该被更改。
现在我们有了const命令.
const没有让变量完全不可变,只是锁定他的赋值,当你有一个复杂的变量(数组或者对象)的时候,值还是可以被修改的。
{ constd=[1,2,3,4]; constdave={name:'DavidJones',age:32}; d.push(5); dave.job="salesman"; console.log(d);//[1,2,3,4,5] console.log(dave);//{age:32,job:"salesman",name:'DavidJones'} }
Problemwithblockscopingfunctions函数块级作用域化带来的问题
函数的声明也可以限制在块级作用域中。
{ bar();//works functionbar(){/*dosomething*/} } bar();//doesn'twork
但是当你在一个if语句中声明一个函数的时候问题来了。
想一下这种情况:
if(something){ functionbaz(){console.log('Ipassed')} }else{ functionbaz(){console.log('Ididn\'tpass')} } baz();
在ES6之前,这两个函数声明都被变量提升,而且结果一定是Ididn'tpass不论条件中的something是什么。但现在我们会得到输出ReferenceError,因为baz一直被限定在块级作用域内。
Spread扩展运算符
ES6介绍了...操作符,这个操作符指的就是‘扩展运算符‘。他的主要用途有两个:1.将一个数组或者对象放到一个新的数组或者对象中2.将数组中的多个参数合并在一起
第一个用途可能是你将会使用的最多的。所以我们先来看他。
leta=[3,4,5]; letb=[1,2,...a,6]; console.log(b);//[1,2,3,4,5,6]
如果我们想把一个数组内的一组参数传递给函数,这个时候扩展运算符就十分的有用了。
functionfoo(a,b,c){ console.log(`a=${a},b=${b},c=${c}`) } letdata=[5,15,2]; foo(...data);//a=5,b=15,c=2
一个对象也可以扩展的,它会把每个键值对写入新的对象中。(对象扩展已经在提议的第四阶段,而且将会在es2018中正式出现。但这种特性目前只被chrome60及以后的版本,Firefox55及以后,node6.4.0及以后的版本所支持)【译者注:在2ality博客中的es2018一文中得知,在刚刚结束的TC39会议中,ECMA2018的特性被敲定了。
letcar={type:'vehicle',wheels:4}; letfordGt={make:'Ford',...car,model:'GT'}; console.log(fordGt);//{make:'Ford',model:'GT',type:'vehicle',wheels:4}
扩展运算符的另一个特点是,他可以生成一个新的数组或者对象.下面的这个例子,就是b就是新建的数组,但c只是引用同一个数组。
leta=[1,2,3]; letb=[...a]; letc=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]同一个数组
第二个用法是把变量聚集到一个数组里面。当你不知道一个函数到底有多少的传参的时候会这个方法会变得非常的有用。
functionfoo(...args){ console.log(args); } foo('car',54,'tree');//['car',54,'tree']
DefaultParameter参数默认值
函数现在可以使用默认的参数值来定义了。不传参或者未定义值都被初始化为默认值。但是需要注意的是,null和false都会被强转为0.
functionfoo(a=5,b=10){ console.log(a+b); } foo();//15 foo(7,12);//19 foo(undefined,8);//13 foo(8);//18 foo(null);//10asnulliscoercedto0
默认值的类型可以不仅仅是值类型---还可以是表达式或者函数。
functionfoo(a){returna*4;} functionbar(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 functionfoo(){return['car','dog',6];} let[x,y,z]=foo(); console.log(`x=${x},y=${y},z=${z}`);//x=car,y=dog,z=6
对象类型的结构,可以在花括号内列出对象的键来提取键值对。
functionbar(){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”(键:变量名)来完成。
functionbaz(){ return{ x:'car', y:'London', z:{name:'John',age:21} }; } let{x:vehicle,y:city,z:{name:driver}}=baz(); console.log( `I'mgoingto${city}with${driver}intheir${vehicle}.` );//I'mgoingtoLondonwithJohnintheircar.
此外,对象的结构允许给多个变量赋值。
let{x:first,x:second}={x:4}; console.log(first,second);//4,4
对象字面量和属性的简洁表达法
当你从许多参数创建对象字面量的时候,ES6允许你在键与变量名字相同的情况下省略该键。
leta=4,b=7; letc={a:a,b:b}; letconcise={a,b}; console.log(c,concise)//{a:4,b:7},{a:4,b:7}
这个还可以与解构一起用来使你的代码更干净整洁。
functionfoo(){ return{ name:'Anna', age:56, job:{company:'Tesco',title:'Manager'} }; } //preES6 leta=foo(),name=a.name,age=a.age,company=a.job.company; //ES6destructuringandconciseparameters let{name,age,job:{company}}=foo();
简洁表示法还可以用于解构对象并把它传入函数。方法1和2是你在es6之前要怎么做,方法三是使用解构和简洁表达法。
letperson={ name:'Anna', age:56, job:{company:'Tesco',title:'Manager'} }; //method1 functionold1(person){ varyearOfBirth=2018-person.age; console.log(`${person.name}worksat${person.job.company}andwasbornin${yearOfBirth}.`); } //method2 functionold1(person){ varage=person.age, yearOfBirth=2018-age, name=person.name, company=person.job.company; console.log(`${name}worksat${company}andwasbornin${yearOfBirth}.`); } //method3 functiones6({age,name,job:{company}}){ varyearOfBirth=2018-age; console.log(`${name}worksat${company}andwasbornin${yearOfBirth}.`); }
通过使用ES6,我们能提取出age,name,和company,而不需要任何其他的变量声明。
动态属性名称
ES6添加了使用动态分配的键创建或添加属性的功能。
letcity='sheffield_'; leta={ [city+'population']:350000 }; a[city+'county']='SouthYorkshire'; console.log(a);//{sheffield_population:350000,sheffield_county:'SouthYorkshire'}
箭头函数
箭头函数有两个比较重要的特点:他们的结构以及他们的this指向
他们比传统的函数有更简单的结构因为他们不需要关键字function而且他们可以自动返回在箭头后面的一部分,无论箭头后面的是什么。
varfoo=function(a,b){ returna*b; } letbar=(a,b)=>a*b;
如果函数有多于一个的计算式,可以使用花括号来包起来,然后函数返回块作用域返回的任何内容。
箭头函数一个最重要的用途之一就是应用在数组的相关函数中,像.map,.forEach,.sort等等。
letarr=[5,6,7,8,'a']; letb=arr.map(item=>item+3); console.log(b);//[8,9,10,11,'a3']
在拥有一个更短的表达方式的同时,箭头函数还修复了有关于this绑定行为经常出现的问题。ES6之前解决这个问题通常是使用一个self变量来存储这个指向。
varclickController={ doSomething:function(..){ varself=this; btn.addEventListener( 'click', function(){self.doSomething(..)}, False ); } };
这个this的赋值是一定要做的,因为this的绑定是动态的。这就意味着this在eventlistener内部和在doSomething内部指的并不是同一个东西。
在箭头函数内部,this的绑定是语义上的就是指当前的,而不是动态的。这也是箭头函数的主要设计特点。
虽然这种词法上的this很棒,但是有些时候,他却不是我们想要的那样。
leta={ oneThing:(a)=>{ letb=a*2; this.otherThing(b); }, otherThing:(b)=>{....} }; a.oneThing(6);
当我们使用a.oneThing(6), 这个this.otherThing(6)会抛出引用失败的错误,因为this没有指向对象a,而是指向了环境作用域。如果你正在使用ES6的代码使用ES6的语法,这个是你需要注意的事情。
for...ofloops(for...of循环)
ES6新添加了一种方式来迭代数组中的每个值,这个方式是与已经存在的for...in的通过索引的循环方式不同。
leta=['a','b','c','d']; //ES6 for(varvalofa){ console.log(val); }//"a""b""c""d" //pre-ES6 for(varidxina){ console.log(idx); }//0123
使用新的for...of循环,在每个循环内部保存了一个letval=a[idx]。
数组,字符串,generator以及从collection在标准JavaScript中都是可迭代的。普通的对象无法正常的使用for...of来迭代,除非你自己定义一个迭代器。
NumberLiterals数字字面量
ES5代码很好处理了十进制和十六进制的数字格式,但并未指定八进制的格式。实际上,八进制在严格模式中是被禁止使用的。
ES6添加了一个全新的格式,在最开始的0后面添加一个o来声明一个八进制的数。与此同时,在es6中还添加了二进制格式。
Number(29)//29 Number(035)//35inoldoctalform. Number(0o35)//29innewoctalform Number(0x1d)//29inhexadecimal Number(0b11101)//29inbinaryform
更多
ES6还提供了我们很多很多其他的方式来使我们的代码更简洁,更易读,以及更稳定。我的目标时写一篇这篇文章的延续,来包括一些ES6中不太知名的部分。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。