现代 JavaScript 参考
简介
初心
本文档是一份JavaScript速查表,你在现代项目中会经常遇到,以及最新的代码示例。
本指南不是为了教你从头开始学习JavaScript,而是为了帮助那些可能不熟悉当前代码库(例如React)所用到JavaScript概念的开发人员。
此外,我有时还会提供一些个人的建议,这些建议可能是有争议的,但我也会注意到,当我这么做的时候,这是我个人的建议。
注意:这里介绍的大多数概念来自于最新的JavaScript语言(ES2015,通常称为ES6)。你可以在这里找到新增的特性,网站做得很棒。
补充资源
当您努力了解一个新概念时,我建议你在以下资源网站上寻找相关的答案:
- MDN(MozillaDeveloperNetwork)
- 你所不知道的JS(书)
- ES6新特性和例子
- WesBos的博客(ES6)
- Reddit(JavaScript)
- 用Google来查找特定的博客和资源
- StackOverflow
目录
- 现代JavaScript速查
- 简介
- 初心
- 参考资料
- 目录
- 概念
- 变量声明:var,const,let
- 简单说明
- 简单的示例
- 详细说明
- 扩展阅读
- 箭头函数
- 简单的示例
- 详细说明
- 简洁性
- this引用
- 有用的资源
- 函数参数默认值
- 扩展阅读
- 解构对象和数组
- 用示例代码说明
- 有用的资源
- 数组方法–map/filter/reduce
- 简单的示例
- 说明
- Array.prototype.map()
- Array.prototype.filter()
- Array.prototype.reduce()
- 扩展阅读
- 展开操作符“…”
- 简单的代码示例
- 说明
- 应用于迭代(如数组)
- 函数剩余参数
- 对象属性展开
- 扩展阅读
- 对象属性简写
- 说明
- 扩展阅读
- Promises
- 简单的代码示例
- 说明
- 创建promise
- Promise处理器的用法
- 扩展阅读
- 模板字符串
- 简单的代码示例
- 扩展阅读
- 带标签(tag)的模板字符串
- 扩展阅读
- ES6模块的导入/导出(imports/exports)
- 用示例代码说明
- 命名导出
- 默认导入/导出(imports/exports)
- 扩展阅读
- JavaScript中的this
- 扩展阅读
- 类(Class)
- 简单的示例
- 扩展阅读
- Extends和super关键字
- 简单的代码示例
- 扩展阅读
- 异步和等待(AsyncAwait)
- 简单的代码示例
- 用示例代码说明
- 错误处理
- 扩展阅读
- 真值/假值(Truthy/Falsy)
- 扩展阅读
- 静态方法
- 简单说明
- 简单的代码示例
- 详细说明
- 在静态方法调用另一个静态方法
- 在非静态方法调用静态方法
- 扩展阅读
- 词汇表
- 作用域(Scope)
- 变量的可变性
概念
变量声明:var,const,let
在JavaScript中,有3个关键字可用于声明一个变量,他们之间有些差异。那些是var,let和const。
简单说明
使用const关键字声明的变量无法重新分配,而let和var可以。
我建议总是默认使用const来声明你的变量,如果你需要改变它,或者稍后需要重新分配,那么实用let。
作用域 | 可否重新分配 | 可变性 | 暂时性死区 | |
---|---|---|---|---|
const | Block | No | Yes | Yes |
let | Block | Yes | Yes | Yes |
var | Function | Yes | Yes | No |
简单的示例
constperson="Nick"; person="John"//将会引起错误,person不能重新分配
letperson="Nick"; person="John"; console.log(person)//"John",使用let允许重新分配
详细说明
变量的作用域大致意思是“在哪里可以访问这个变量”。
var
var声明的变量是函数作用域,这意味着当在函数中创建变量时,该函数中的所有内容都可以访问该变量。
此外,函数中创建的函数作用域变量无法在此函数外访问。
我建议你把它看作一个X作用域变量,意味着这个变量是X的属性。
functionmyFunction(){ varmyVar="Nick"; console.log(myVar);//"Nick"-在这个函数中myVar可被访问到 } console.log(myVar);//抛出错误ReferenceError,在函数之外myVar则无法访问
继续关注变量的作用域,这里是一个更微妙的例子:
functionmyFunction(){ varmyVar="Nick"; if(true){ varmyVar="John"; console.log(myVar);//"John" //实际上,myVar是函数作用域,我们只是将之前myVar的值"Nick"抹去,重新声明为"John" } console.log(myVar);//"John"-看看if语句块中的指令是如何影响这个值的 } console.log(myVar);//抛出错误ReferenceError,在函数之外myVar则无法访问
此外,在执行过程中,var声明的变量被移动到范围的顶部。这就是我们所说的变量声明提升(varhoisting)。
这部分的代码:
console.log(myVar)//undefined--不会报错 varmyVar=2;
在执行时,被解析为:
varmyVar; console.log(myVar)//undefined--不会报错 myVar=2;
愚人码头注:变量提升和函数声明提升可以查看:JavaScript中的Hoisting(变量提升和函数声明提升)
let
var和let大致相同,但是用let声明的变量时有以下几个特性:
- 块级作用域(blockscoped)
- 在被分配之前,无法访问使用
- 在同一个作用域之下,不能重新声明
我们来看看我们前面的例子,采用块级作用域(blockscoping)后的效果:
functionmyFunction(){ letmyVar="Nick"; if(true){ letmyVar="John"; console.log(myVar);//"John" //实际上myVar在块级作用域中,(在if语句块中)我们只是创建了一个新变量myVar。 //此变量在if语句块之外不可访问, //完全独立于创建的第一个myVar变量! } console.log(myVar);//"Nick",可以看到if语句块中的指令不会影响这个值 } console.log(myVar);//抛出错误ReferenceError,myVar无法在函数外部被访问。
现在,我们来看看let(和const)变量在被赋值之前不可访问是什么意思:
console.log(myVar)//抛出一个引用错误ReferenceError! letmyVar=2;
与var变量不同的是,如果你在let或者const变量被赋值之前读写,那么将会出现错误。这种现象通常被称为暂存死区(Temporaldeadzone)或者TDZ。
注意:从技术上讲,let和const变量声明时也存在提升,但并不代表它们的赋值也会被提升。但由于它被设计成了赋值之前无法使用,所以我们直观感觉上它没有被提升,但其实是存在提升的。如果想了解更多细节,请看这篇文章。
此外,您不能重新声明一个let变量:
letmyVar=2; letmyVar=3;//抛出语法错误SyntaxError
const
const声明的变量很像let,但它不能被重新赋值。
总结一下const变量:
- 块级作用域
- 分配之前无法访问
- 在同一个作用域内部,不能重新声明
- 不能被重新分配
constmyVar="Nick"; myVar="John"//抛出一个错误,不允许重新分配
constmyVar="Nick"; constmyVar="John"//抛出一个错误,不允许重新声明
但这里有一个小细节:const变量并不是不可变,具体来说,如果const声明的变量是object和array类型的值,那它是可变的。
对于对象:
constperson={ name:'Nick' }; person.name='John'//这是有效的!person变量并非完全重新分配,只是值被改变 console.log(person.name)//"John" person="Sandra"//抛出一个错误,因为用const声明的变量不能被重新分配
对于数组:
constperson=[]; person.push('John');//这是有效的!person变量并非完全重新分配,只是值被改变 console.log(person[0])//"John" person=["Nick"]//抛出一个错误,因为用const声明的变量不能被重新分配
扩展阅读
- HowletandconstarescopedinJavaScript–WesBos
- TemporalDeadZone(TDZ)Demystified
箭头函数
ES6JavaScript更新引入了箭头函数,这是另一种声明和使用函数的方法。以下是他们带来的好处:
- 更简洁
- this的值继承自外围的作用域
- 隐式返回
简单的示例
- 简洁性和隐式返回
functiondouble(x){returnx*2;}//传统的方法 console.log(double(2))//4
constdouble=x=>x*2;//相同的函数写成带有隐式返回的箭头函数 console.log(double(2))//4
- this的引用
在箭头函数中,this意味着封闭执行上下文的this值。基本上,使用箭头函数,在函数中调用函数之前,您不需要执行“that=this”这样的的技巧。
functionmyFunc(){ this.myVar=0; setTimeout(()=>{ this.myVar++; console.log(this.myVar)//1 },0); }
详细说明
简洁性
箭头函数在许多方面比传统函数更简洁。让我们来看看所有可能的情况:
- 隐式VS显式返回
显式返回(explicitreturn)是指在函数体中明确的使用return这个关键字。
functiondouble(x){ returnx*2;//这个函数显示返回x*2,并且使用了*return*关键字 }
在函数传统的写法中,返回总是显式的。但是如果是使用箭头函数,你可以执行隐式返回(implicitreturn),这表示你不需要使用关键字return来返回一个值。
要做隐式回传,代码必须用一行语句写完。
constdouble=(x)=>{ returnx*2;//这里是显式返回 }
由于这里只有一个返回值,我们可以做一个隐式的返回。
constdouble=(x)=>x*2;
这样做,我们只需要移除括号以及return关键字。这就是为什么它会被称为隐式返回,return关键字不在了,但是这个函数确实会返回x*2。
注意:如果你的函数没有回传一个值(这种作法有副作用),那么它将不会做显式或隐式返回。
另外,如果你想隐式地返回一个对象(object),你必须用括号包裹,否则它将与块大括号冲突:
constgetPerson=()=>({name:"Nick",age:24}) console.log(getPerson())//{name:"Nick",age:24}--箭头函数隐式地返回一个对象
- 只有一个参数
如果你的函数只接受一个参数,你可以省略包裹它的括号。如果我们拿上述的double代码做为举例:
constdouble=(x)=>x*2;//这个箭头函数只接受一个参数
包裹参数的括号是可以被省略:
constdouble=x=>x*2;//这个箭头函数只接受一个参数
- 没有参数
当没有为箭头函数提供任何参数时,你就必须加上括号,否则将会抛出语法错误。
()=>{//提供括号,一切都能正常运行 constx=2; returnx; }
=>{//没有括号,这不能正常运行! constx=2; returnx; }
this引用
要理解箭头函数的精妙之处,你就必须知道this在JavaScript中是如何运作的。
在一个箭头函数中,this等同于封闭执行上下文的this值。这意味着,一个箭头函数并不会创造一个新的this,而是从它的外围作用域中抓取的。
如果没有箭头函数,你想在一个函数内部的函数中通过this访问变量,你就只能使用that=this或者是self=this这样的技巧。
举例来说,你在myFunc中使用setTimeout函数:
functionmyFunc(){ this.myVar=0; varthat=this;//that=this技巧 setTimeout( function(){//在这个函数作用域中,一个新的*this*被创建 that.myVar++; console.log(that.myVar)//1 console.log(this.myVar)//undefined--见上诉函数声明 }, 0 ); }
但是如果你使用箭头函数,this是从它的外围作用域中抓取的:
functionmyFunc(){ this.myVar=0; setTimeout( ()=>{//this值来自它的外围作用域,在这个示例中,也就是myFunc函數 this.myVar++; console.log(this.myVar)//1 }, 0 ); }
有用的资源
- Arrowfunctionsintroduction–WesBos
- JavaScriptarrowfunction–MDN
- Arrowfunctionandlexicalthis
函数参数默认值
从ES2015JavaScript更新之后开始,你可以通过下列的语法为函数的参数设定默认值:
functionmyFunc(x=10){ returnx; } console.log(myFunc())//10--没有提供任何值,所以在myFunc中10做为默认值分配给x console.log(myFunc(5))//5--有提供一个参数值,所以在myFunc中x等于5 console.log(myFunc(undefined))//10--提供undefined值,所以默认值被分配给x console.log(myFunc(null))//null--提供一个值(null),详细资料请见下文
默认参数应用于两种且仅两种情况:
- 没有提供参数
- 提供undefined未定义参数
换句话说,如果您传入null,则不会应用默认参数。
注意:默认值分配也可以与解构参数一起使用(参见下一个概念以查看示例)。
扩展阅读
- Defaultparametervalue–ES6Features
- Defaultparameters–MDN
解构对象和数组
解构(destructuring)是通过从存储在对象或数组中的数据中提取一些值来创建新变量的简便方法。
举个简单的实例,destructuring可以被用来解构函数中的参数,或者像是React项目中this.props这样的用法。
用示例代码说明
- Object
我们考虑一下以下对象的所有属性:
constperson={ firstName:"Nick", lastName:"Anderson", age:35, sex:"M" }
不使用解构:
constfirst=person.firstName; constage=person.age; constcity=person.city||"Paris";
使用解构,只需要1行代码:
const{firstName:first,age,city="Paris"}=person;//就是这么简单! console.log(age)//35--一个新变量age被创建,并且其值等同于person.age console.log(first)//"Nick"--一个新变量first被创建,并且其值等同于person.firstNameAnewvariablefirstiscreatedandisequaltoperson.firstName console.log(firstName)//Undefined--person.firstName虽然存在,但是新变量名为first console.log(city)//"Paris"--一个新变量city被创建,并且因为person.city为undefined(未定义),所以city将等同于默认值也就是"Paris"。
注意:在const{age}=person;中,const关键字后的括号不是用于声明对象或代码块,而是解构(structuring)语法。
- 函数参数
解构(structuring)经常被用来解构函数中的对象参数。
不使用解构:
functionjoinFirstLastName(person){ constfirstName=person.firstName; constlastName=person.lastName; returnfirstName+'-'+lastName; } joinFirstLastName(person);//"Nick-Anderson"
在解构对象参数person这个参数时,我们可以得到一个更简洁的函数:
functionjoinFirstLastName({firstName,lastName}){//通过解构person参数,我们分别创建了firstName和lastName这两个变数 returnfirstName+'-'+lastName; } joinFirstLastName(person);//"Nick-Anderson"
箭头函数中使用解构,使得开发过程更加愉快:
constjoinFirstLastName=({firstName,lastName})=>firstName+'-'+lastName; joinFirstLastName(person);//"Nick-Anderson"
- Array
我们考虑一下以下数组:
constmyArray=["a","b","c"];
不使用解构:
constx=myArray[0]; consty=myArray[1];
使用解构:
const[x,y]=myArray;//就是这么简单! console.log(x)//"a" console.log(y)//"b"
有用的资源
- ES6Features–DestructuringAssignment
- DestructuringObjects–WesBos
- ExploringJS–Destructuring
数组方法–map/filter/reduce
map,filter和reduce都是数组提供的方法,它们源自于函数式编程。
总结一下:
- Array.prototype.map()接受一组数组,在其元素上执行某些操作,并返回具有转换后的元素数组。
- Array.prototype.filter()接受一组数组,依照元素本身决定是否保留,并返回一个仅包含保留元素的数组。
- Array.prototype.reduce()接受一组数组,将这些元素合并成单个值(并返回)。
我建议尽可能地使用它们来遵循函数式编程(functionalprogramming)的原则,因为它们是可组合的,简洁的且优雅的。
通过这三种方法,您可以避免在大多数情况下使用for和forEach。当你试图做一个for循环时,尝试用map,filter和reduce来组合试试。起初你可能很难做到这一点,因为它需要你学习一种新的思维方式,但一旦你得到它,事情会变得更容易。
愚人码头注:JavaScript函数式编程建议看看以下几篇文章
- JavaScript中的Currying(柯里化)和PartialApplication(偏函数应用)
- 一步一步教你JavaScript函数式编程(第一部分)
- 一步一步教你JavaScript函数式编程(第二部分)
- 一步一步教你JavaScript函数式编程(第三部分)
- JavaScript函数式编程术语大全
简单的示例
constnumbers=[0,1,2,3,4,5,6]; constdoubledNumbers=numbers.map(n=>n*2);//[0,2,4,6,8,10,12] constevenNumbers=numbers.filter(n=>n%2===0);//[0,2,4,6] constsum=numbers.reduce((prev,next)=>prev+next,0);//21
通过组合map,filter和reduce来计算10分以上的学生成绩总和sum:
conststudents=[ {name:"Nick",grade:10}, {name:"John",grade:15}, {name:"Julia",grade:19}, {name:"Nathalie",grade:9}, ]; constaboveTenSum=students .map(student=>student.grade)//我们将学生数组映射到他们成绩的数组中 .filter(grade=>grade>=10)//我们过滤成绩数组以保持10分以上的元素 .reduce((prev,next)=>prev+next,0);//我们将合计所有10分以上的成绩 console.log(aboveTenSum)//44--10(Nick)+15(John)+19(Julia),低于10的Nathalie被忽略
说明
让我们考虑一下下列数组:
constnumbers=[0,1,2,3,4,5,6];
Array.prototype.map()
constdoubledNumbers=numbers.map(function(n){ returnn*2; }); console.log(doubledNumbers);//[0,2,4,6,8,10,12]
这里发生了什么?我们在numbers这个数组中使用.map方法,map将会迭代数组的每个元素,并传递给我们的函数。该函数的目标是生成并返回一个新的值,以便map可以替换掉原本的数组。
我们来解释一下这个函数,使之更清楚一点:
constdoubleN=function(n){returnn*2;}; constdoubledNumbers=numbers.map(doubleN); console.log(doubledNumbers);//[0,2,4,6,8,10,12]
numbers.map(doubleN)将会产生[doubleN(0),doubleN(1),doubleN(2),doubleN(3),doubleN(4),doubleN(5),doubleN(6)]而它们分别等同于[0,2,4,6,8,10,12]。
注意:如果你不需要返回一个新的数组,且只想执行一个带有副作用的循环,使用for/forEach循环会更为符合你的需求。
Array.prototype.filter()
constevenNumbers=numbers.filter(function(n){ returnn%2===0;//如果"n"符合条件返回true,如果"n"不符合条件则false。 }); console.log(evenNumbers);//[0,2,4,6]
我们在numbers数组中使用.filter方法,过滤器遍历数组中的每个元素,并将其传递给我们的函数。函数的目标是返回一个布尔值,它将确定当前值是否被保留。过滤之后返回的数组将只包含保留值。
Array.prototype.reduce()
reduce方法的目标是将迭代数组中的所有元素,减少到只留下单一值。如何聚合这些元素取决于你。
constsum=numbers.reduce( function(acc,n){ returnacc+n; }, 0//累加器迭代变量的初始值 ); console.log(sum)//21
就像.map和.filter方法一样,.reduce方法被应用在数组上并接收一个函数做为第一个参数。
下面是一些差异:
- .reduce接受两个参数
第一个参数是一个函数,将在每个迭代步骤中被调用。
第二个参数是在第一个迭代步骤(读取下一个用的)的累加器变量的值(此处是acc)。
- 函数参数
作为.reduce的第一个参数传递的函数需要两个参数。第一个(此处是acc)是累加器变量,而第二个参数(n)则是当前元素。
累加器变量的值等于上一次迭代步骤中函数的返回值。在迭代过程的第一步,acc等于你做为.reduce时第二个参数所传递的值(愚人码头注:也就是累加器初始值)。
进行第一次迭代
acc=0因为我们把0做为reduce的第二个参数
n=0number数组的第一个元素
函数返回acc+n–>0+0–>0
进行第二次迭代
acc=0因为它是上次迭代所返回的值
n=1number数组的第二个元素
函数返回acc+n–>0+1–>1
进行第三次迭代
acc=1因为它是上次迭代所返回的值
n=2number数组的第三个元素
函数返回acc+n–>1+2–>3
进行第四次迭代
acc=3因为它是上次迭代所返回的值
n=3number数组的第四个元素
函数返回acc+n–>3+3–>6
[…]进行最后一次迭代
acc=15因为它是上次迭代所返回的值
n=6number数组的最后一个元素
函数返回acc+n–>15+6–>21
因为它是最后一个迭代步骤了,.reduce将返回21。
扩展阅读
- Understandingmap/filter/reduceinJS
展开操作符“…”
ES2015已经引入了展开操作符...,可以将可迭代多个元素(如数组)展开到适合的位置。
简单的代码示例
constarr1=["a","b","c"]; constarr2=[...arr1,"d","e","f"];//["a","b","c","d","e","f"]
functionmyFunc(x,y,...params){ console.log(x); console.log(y); console.log(params) } myFunc("a","b","c","d","e","f") //"a" //"b" //["c","d","e","f"]
const{x,y,...z}={x:1,y:2,a:3,b:4}; console.log(x);//1 console.log(y);//2 console.log(z);//{a:3,b:4} constn={x,y,...z}; console.log(n);//{x:1,y:2,a:3,b:4}
说明
应用于迭代(如数组)
如果我们有以下两个数组:
constarr1=["a","b","c"]; constarr2=[arr1,"d","e","f"];//[["a","b","c"],"d","e","f"]
arr2的第一个元素是一个数组,因为arr1是被注入到arr2之中的。但我们真正想要得到的arr2是一个纯字母的数组。为了做到这点,我们可以将arr1展开(spread)到arr2。
通过展开操作符:
constarr1=["a","b","c"]; constarr2=[...arr1,"d","e","f"];//["a","b","c","d","e","f"]
函数剩余参数
在函数参数中,我们可以使用rest操作符将参数注入到我们可以循环的数组中。这里已经有一个argument对象绑定到每个函数上,等同于把数组中的所有参数都传递给函数。
functionmyFunc(){ for(vari=0;i但是如果说,我们希望创造的是一个包含各科成绩和平均成绩的新学生。将前两个参数提取为两个单独的变量,并把剩下的元素生成一个可迭代的数组是不是更加方便呢?
这正是rest操作符允许我们做的事情!
functioncreateStudent(firstName,lastName,...grades){ //firstName="Nick" //lastName="Anderson" //[10,12,6]--"..."将传递所有剩余参数,并创建一个包含它们的"grades"数组变量 constavgGrade=grades.reduce((acc,curr)=>acc+curr,0)/grades.length;//根据grade计算平均成绩 return{ firstName:firstName, lastName:lastName, grades:grades, avgGrade:avgGrade } } conststudent=createStudent("Nick","Anderson",10,12,6); console.log(student); //{ //firstName:"Nick", //lastName:"Anderson", //grades:[10,12,6], //avgGrade:9.333333333333334 //}注意:在这个示例中,createStudent函数其实并不太好,因为我们并没有去检查grades.length是否存在又或者它等于0的情况。但是这个例子现在这样写,能够更好的帮助我们理解剩余参数的运作,所以我没有处理上述的这种情况。
对象属性展开
对于这一点,我建议你阅读有关rest操作符以前的有关迭代和函数参数的相关说明。
constmyObj={x:1,y:2,a:3,b:4}; const{x,y,...z}=myObj;//这里是对象被解构 console.log(x);//1 console.log(y);//2 console.log(z);//{a:3,b:4} //z是对象解构后的剩余部分:myObj对象除去x和y属性后剩余部分被解构 constn={x,y,...z}; console.log(n);//{x:1,y:2,a:3,b:4} //这里z对象的属性展开到n中扩展资源
- TC39–Objectrest/spread
- Spreadoperatorintroduction–WesBos
- JavaScript&thespreadoperator
- 6Greatusesofthespreadoperator
对象属性简写
将变量分配给一个对象属性时,如果变量名称和属性名称相同,你可以执行以下操作:
constx=10; constmyObj={x}; console.log(myObj.x)//10说明
通常(ES2015之前)当你声明一个新的对象字面量并且想要使用变量作为对象属性值时,你会写这样类似的代码:
constx=10; consty=20; constmyObj={ x:x,//将x分配给myObj.x y:y//将y变量给myObj.y }; console.log(myObj.x)//10 console.log(myObj.y)//20如您所见,这样的作法其实相当重复,因为myObj的属性名称与要分配给这些属性的变量名相同。
使用ES2015,当变量名与属性名称相同时,您可以进行以下简写:
constx=10; consty=20; constmyObj={ x, y }; console.log(myObj.x)//10 console.log(myObj.y)//20扩展资源
- Propertyshorthand–ES6Features
Promises
Promises是一个可以从异步函数(参考)同步返回的对象。
可以使用Promises来避免回调地狱(callbackhell),而且它们在现代JavaScript项目中越来越频繁地遇到。
简单的代码示例
constfetchingPosts=newPromise((res,rej)=>{ $.get("/posts") .done(posts=>res(posts)) .fail(err=>rej(err)); }); fetchingPosts .then(posts=>console.log(posts)) .catch(err=>console.log(err));说明
当你在执行Ajax请求时,响应不是同步的,因为资源请求需要时间。如果你要的资源由于某些原因(404)不可用,甚至可能永远都不会请求到。
为了处理这类情况,ES2015为我们提供了promises。Promises可以有三种不同的状态:
- 等待中(Pending)
- 达成(Fulfilled)
- 拒绝(Rejected)
假设我们希望使用promises去进行Ajax请求以获取X资源。
创建promise
我们首先要创建一个promise。我们将会使用jQuery的get方法对X资源执行Ajax请求。
constxFetcherPromise=newPromise(//使用"new"关键字创建promise,并把它保存至一个变量 function(resolve,reject){//Promise构造函数需要一个带有着resolve和reject这两个参数的函数作为参数 $.get("X")//执行Ajax请求 .done(function(X){//一旦请求完成... resolve(X);//...把X值做为参数去resolvepromise }) .fail(function(error){//如果请求失败... reject(error);//...把error做为参数去rejectpromise }); } )如上示例所示,Promise对象需要一个带有两个参数(resolve和reject)的执行函数。这两个参数会把pending状态的promise分别进行fulfilled和rejected的处理。
Promise在实例创建后处于待处理状态,并且它的执行器函数立即执行。一旦在执行函数中调用了resolve或reject函数,Promise将调用相关的处理程序。
Promise处理器的用法
为了获得promise结果(或错误),我们必须通过执行以下操作来附加处理程序:
xFetcherPromise .then(function(X){ console.log(X); }) .catch(function(err){ console.log(err) })如果promise成功,则执行resolve,并执行.then参数所传递的函数。
如果失败,则执行reject,并执行.catch参数所传递的函数。
注意:如果promise在相应的处理程序附加时已经fulfilled或rejected,处理程序将被调用,
因此在异步操作完成和其处理程序被附加之间没有竞争条件。(参考:MDN)(MDN)。扩展阅读
- JavaScriptPromisesfordummies–JecelynYeen
- JavaScriptPromiseAPI–DavidWalsh
- Usingpromises–MDN
- Whatisapromise–EricElliott
- JavaScriptPromises:anIntroduction–JakeArchibald
- Promisedocumentation–MDN
模板字符串
模板字符串是一种单行和多行字符串的表达式插值(expressioninterpolation)。
换句话说,它是一种新的字符串语法,你可以更方便地在JavaScript表达式中使用(例如变量)。
简单的代码示例
constname="Nick"; `Hello${name},thefollowingexpressionisequaltofour:${2+2}`; //HelloNick,thefollowingexpressionisequaltofour:4扩展阅读
- Stringinterpolation–ES6Features
- ES6TemplateStrings–AddyOsmani
带标签(tag)的模板字符串
模板标签是可以作为模板字符串(templateliteral)的前缀函数。当一个函数被这钟方式调用时,第一个参数是出现在模板插值变量之间的字符串数组,并且随后的参数是插值变量的值。可以使用展开运算符...捕获所有这些参数。(参考:MDN)。
注意:名为styled-components的著名库很大程度上依赖于此功能。
以下是他们工作的玩具示例。
functionhighlight(strings,...values){ //愚人码头注:为了更好的理解函数的参数,我增加了这样两行代码,特殊说明见示例代码下面的说明; console.log(strings);//(3)["Ilike","on",".",raw:Array(3)] console.log(values);//(2)["jam","toast"] constinterpolation=strings.reduce((prev,current)=>{ returnprev+current+(values.length?""+values.shift()+"":""); },""); returninterpolation; } constcondiment="jam"; constmeal="toast"; highlight`Ilike${condiment}on${meal}.`; //"Ilikejamontoast."——-愚人码头注开始——-
愚人码头注,关于第一个参数:
标签函数的第一个参数是一个包含了字符串字面值的数组(在本例中分别为”Ilike“,”on“和”.”)。如果我们这样调用highlight`Ilike${condiment}on${meal}`(注意最后面没”.”),那么第一个参数还是一个3个元素的数组:[“Ilike“,”on“,“”],特别注意最后一个元素是的空字符串””;
字符串的raw属性
strings确实是一个数组,但是它还有一个raw属性。其属性值strings.raw也是一个数组,其元素分别表示strings中对应的,经过转义之前在模板字符串(Templateliterals)中由用户输入的字符串。我们来看一个例子:
constmyTag=(strs,...exprs)=>{ console.log(strs);//(3)["x","\y","",raw:Array(3)] console.log(strs.raw);//(3)["x","\\y","" console.log(exprs);//[1,2] }; constobj={a:1,b:2}; constresult=myTag`x${obj.a}\\y${obj.b}`;上例中"\y"未转义之前对应的字符串为"\\y",显然这两个字符串长度是不同的。
String.raw是ES2015,内置对象String的一个静态方法,把它作为Tag,可以做到只替换嵌入表达式而不转义字符。
constraw=String.raw`1\\2\\${1+2}`; console.log(raw);//1\\2\\3 console.log(raw.length);//7 constx=`1\\2\\${1+2}`; console.log(x);//1\2\3 console.log(x.length);//5规避问题
Templateliterals遵从字符串的转义规则:
(1)以\u开头,后跟4个16进制数字,例如,\u00B1表示±
(2)以\u开头,使用大括号括起来的16进制数字,例如,\u{2F804}表示你
(3)以\x开头,后跟2个16进制数字,例如,\xB1表示±
(4)以\开头,后跟10进制数字,用来表示八进制字面量(注:strictmode下不支持)解释器遇到\u和\x,如果发现后面的字符不满足以上条件,就会报语法错。例如,
>latex`\unicode` >UncaughtSyntaxError:InvalidUnicodeescapesequence不再展开,具体参考:Templateliterals。
愚人码头注,关于后面的剩余参数:
在第一个参数后的每一个参数,都是已经求值后的替换表达式。看下面这个例子:
vara=5; varb=10; functiontag(strings,...values){ console.log(values[0]);//15 console.log(values[1]);//50 returnvalues[0]+values[1];//65 } tag`Hello${a+b}world${a*b}`;上例中,剩余的2个参数值分别是15和50;
——-愚人码头注结束——-
一个更有趣的例子:
functioncomma(strings,...values){ returnstrings.reduce((prev,next)=>{ letvalue=values.shift()||[]; value=value.join(","); returnprev+next+value; },""); } constsnacks=['apples','bananas','cherries']; comma`Ilike${snacks}tosnackon.`; //"Ilikeapples,bananas,cherriestosnackon."扩展阅读
- WesBosonTaggedTemplateLiterals
- Libraryofcommontemplatetags
ES6模块的导入/导出(imports/exports)
ES6模块用于访问模块中显式导出的模块中的变量或函数。
我强烈建议您查看MDN上有关import/export(请参阅下面的扩展阅读资源),它们写的既简洁又完整。
用示例代码说明
命名导出
命名导出用于从一个模块导出多个值。
注意:您命名导出的变量是一等公民(first-classcitizens)。
//mathConstants.js exportconstpi=3.14; exportconstexp=2.7; exportconstalpha=0.35; //------------- //myFile.js import{pi,exp}from'./mathConstants.js';//命名导入--类似于解构语法 console.log(pi)//3.14 console.log(exp)//2.7 //------------- //mySecondFile.js import*asconstantsfrom'./mathConstants.js';//将所有导出的值注入到constants变量中 console.log(constants.pi)//3.14 console.log(constants.exp)//2.7虽然命名导入看起来像是解构(destructuring),但它们具有不同的语法,并且不一样。他们不支持默认值,也不支持深层次的解构。
此外,您可以使用别名,但语法不同于解构中使用的语法:
import{fooasbar}from'myFile.js';//foo被导入并注入到一个新的bar变量中默认导入/导出(imports/exports)
关于默认导出,每个模块只能有一个默认导出。默认导出可以是函数,类,对象或其他任何东西。这个值被认为是“主要”的导出值,因为它将是最简单的导入。参考:MDN。
//coolNumber.js constultimateNumber=42; exportdefaultultimateNumber; //------------ //myFile.js importnumberfrom'./coolNumber.js'; //默认导出,将独立于其名称,自动注入到number这个变量; console.log(number)//42函数导出:
//sum.js exportdefaultfunctionsum(x,y){ returnx+y; } //------------- //myFile.js importsumfrom'./sum.js'; constresult=sum(1,2); console.log(result)//3扩展阅读
- ECMAScript6Modules(模块)系统及语法详解
- ES6Modulesinbulletpoints
- Export–MDN
- Import–MDN
- UnderstandingES6Modules
- Destructuringspecialcase–importstatements
- MisunderstandingES6Modules–KentC.Dodds
- ModulesinJavaScript
JavaScript中的this
this操作符的行为与其他语言不同,在大多数情况之下是由函数的调用方式决定。(参考:MDN)。
this概念有很多细节,并不是那么容易理解,我强烈建议你深入了解下面的扩展阅读。因此,我会提供我个人对于this的一点理解和想法。我是从YehudaKatz写的这篇文章学到了这个提示。
functionmyFunc(){ ... } //在每个述句后面,你都可以在myFunc中找到this的值 myFunc.call("myString","hello")//"myString"--首先,.call的参数值被注入到*this* //在非严格模式下(non-strict-mode) myFunc("hello")//window--myFunc()是myFunc.call(window,"hello")的语法糖 //在严格模式下(strict-mode) myFunc("hello")//undefined--myFunc()是myFunc.call(undefined,"hello")的语法糖varperson={ myFunc:function(){...} } person.myFunc.call(person,"test")//person对象--首先,.call的参数值被注入到*this* person.myFunc("test")//person对象--person.myFunc()是person.myFunc.call(person,"test")的语法糖 varmyBoundFunc=person.myFunc.bind("hello")//创造了一个函数,并且把"hello"注入到*this* person.myFunc("test")//person对象--bind方法对原有方法并无造成影响 myBoundFunc("test")//"hello"--myBoundFunc是把带有"hello"的person.myFunc绑定到*this*扩展阅读
- UnderstandingJavaScriptFunctionInvocationand“this”–YehudaKatz
- JavaScriptthis–MDN
类(Class)
JavaScript是一个基于原型的语言(然而Java是基于类别的语言)。ES6引入了JavaScript类,它们是用于基于原型的继承的语法糖,而不是一种新的基于类继承的模型(参考).
如果您熟悉其他语言的类,那么类(class)这个词的确容易理解出错。如果真的有此困扰,请避免在这样的认知下思考JavaScript类的行为,并将其视为完全不同的新概念。
由于本文档不是从根本上教你JavaScript语言,我会相信你知道什么是原型,以及它们的行为。如果没有,请参阅示例代码下面列出的扩展阅读,以方便你去理解这些概念:
- UnderstandingPrototypesinJS–YehudaKatz
- AplainEnglishguidetoJSprototypes–SebastianPorto
- Inheritanceandtheprototypechain–MDN
简单的示例
ES6之前,原型語法:
varPerson=function(name,age){ this.name=name; this.age=age; } Person.prototype.stringSentence=function(){ return"Hello,mynameis"+this.name+"andI'm"+this.age; }使用ES6类(class)*语法:
classPerson{ constructor(name,age){ this.name=name; this.age=age; } stringSentence(){ return"Hello,mynameis"+this.name+"andI'm"+this.age; } } constmyPerson=newPerson("Manu",23); console.log(myPerson.age)//23 console.log(myPerson.stringSentence())//"Hello,mynameisManuandI'm23扩展阅读
更好的理解原型:
- UnderstandingPrototypesinJS–YehudaKatz
- AplainEnglishguidetoJSprototypes–SebastianPorto
- Inheritanceandtheprototypechain–MDN
更好的理解类:
- ES6ClassesinDepth–NicolasBevacqua
- ES6Features–Classes
- JavaScriptClasses–MDN
Extends和super关键字
extends关键字用于类声明或类表达式中,以创建一个类,该类是另一个类的子类(参考:MDN)。子类继承超类的所有属性,另外可以添加新属性或修改继承的属性。
super关键字用于调用对象的父对象的函数,包括其构造函数。
- super关键字必须在构造函数中使用this关键字之前使用。
- 调用super()调用父类构造函数。如果要将类的构造函数中的一些参数传递给其父构造函数,则可以使用super(arguments)来调用它。
- 如果父类有一个X的方法(甚至静态),可以使用super.X()在子类中调用。
简单的代码示例
classPolygon{ constructor(height,width){ this.name='Polygon'; this.height=height; this.width=width; } getHelloPhrase(){ return`Hi,Iama${this.name}`; } } classSquareextendsPolygon{ constructor(length){ //这里,它调用父类的构造函数的length, //提供给Polygon的width和height。 super(length,length); //注意:在派生的类中,在你可以使用'this'之前,必须先调用super()。 //忽略这个,这将导致引用错误。 this.name='Square'; this.length=length; } getCustomHelloPhrase(){ constpolygonPhrase=super.getHelloPhrase();//通过super.X()语法访问父级方法 return`${polygonPhrase}withalengthof${this.length}`; } getarea(){ returnthis.height*this.width; } } constmySquare=newSquare(10); console.log(mySquare.area)//100 console.log(mySquare.getHelloPhrase())//'Hi,IamaSquare'--Square继承自Polygon并可以访问其方法 console.log(mySquare.getCustomHelloPhrase())//'Hi,IamaSquarewithalengthof10'注意:如果我们在Square类中调用super()之前尝试使用this,将引发一个引用错误:
classSquareextendsPolygon{ constructor(length){ this.height;//引用错误,必须首先调用super()! //这里,它调用父类的构造函数的length, //提供给Polygon的width和height。 super(length,length); //注意:在派生的类中,在你可以使用'this'之前,必须先调用super()。 //忽略这个,这将导致引用错误。 this.name='Square'; } }扩展阅读
- Extends–MDN