编程语言
C#编程语言基础
C#面向对象与多线程
C#数据及文件操作
JavaScript基础
JavaScript的数据类型和变量
JavaScript的运算符和表达式
JavaScript的基本流程控制
JavaScript的函数
JavaScript对象编程
JavaScript内置对象和方法
JavaScript的浏览器对象和方法
JavaScript访问HTML DOM对象
JavaScript事件驱动编程
JavaScript与CSS样式表
Ajax与PHP
ECMAScript6的新特性
Vue.js前端开发
PHP的常量与变量
PHP的数据类型与转换
PHP的运算符和优先规则
PHP程序的流程控制语句
PHP的数组操作及函数
PHP的字符串处理与函数
PHP自定义函数
PHP的常用系统函数
PHP的图像处理函数
PHP类编程
PHP的DataTime类
PHP处理XML和JSON
PHP的正则表达式
PHP文件和目录处理
PHP表单处理
PHP处理Cookie和Session
PHP文件上传和下载
PHP加密技术
PHP的Socket编程
PHP国际化编码
MySQL数据库基础
MySQL数据库函数
MySQL数据库账户管理
MySQL数据库基本操作
MySQL数据查询
MySQL存储过程和存储函数
MySQL事务处理和触发器
PHP操作MySQL数据库
数据库抽象层PDO
Smarty模板
ThinkPHP框架
Python语言基础
Python语言结构与控制
Python的函数和模块
Python的复合数据类型
Python面向对象编程
Python的文件操作
Python的异常处理
Python的绘图模块
Python的NumPy模块
Python的SciPy模块
Python的SymPy模块
Python的数据处理
Python操作数据库
Python网络编程
Python图像处理
Python机器学习
TensorFlow深度学习
Tensorflow常用函数
TensorFlow用于卷积网络
生成对抗网络GAN
- 块级作用域绑定:
- 字符串和正则表达式:
- 函数:
- 扩展对象功能性:
- 解构:
- Symbol及Symbol属性:
- Set集合与Map集合:
- 迭代器和生成器:
- 类:
- 改进的数组功能:
- Promise与异步编程:
- 代理和反射API:
- 用模块封装代码:
- ES6中其他的一些改动:
- ECMAScript 2016:
- ECMAScript之后版本的改动:
自1999年ECMA-262发布以来,JavaScript没有丝毫改变。2007年,负责推动ECMAScript发展的TC-39委员会将大量规范草案整合在了ECMAScript 4中,但内部对草案产生了巨大分歧,来自雅虎、谷歌和微软的技术负责人共同商讨了一份ECMAScript 3.1草案,只是对现有标准进行了小幅的增量修改。2008年,JavaScript创始人Brendan Eich宣布TC-39委员会将合力推进ECMAScript 3.1的标准化工作,最终作为ECMA-262第五版正式发布,也被称为ECMAScript5,简称ES5。
TC-39委员会努力融合ECMAScript 3.1和4中的精华部分,并给了一个昵称ECMAScript Harmony,到2015年标准化完成,并正式命名为ECMAScript 2015,但开发者习惯称为ECMAScript 6,简称ES6。
一、块级作用域绑定:
JavaScript中使用var声明变量,无论实际上在哪里声明,都会被当成在当前作用域顶部声明的变量,这就是提升Hoisting机制。
ES6引入块级作用域,用于声明在指定块作用域之外无法访问的变量。
1)let声明:
用let代替var声明,就可以把变量的作用域限制在当前代码块中。let声明不会被提升。
如果作用域中已经存在某个标识符,此时再使用let关键字声明就会抛出错误。而在内部块使用let声明变量则会遮蔽全局作用域中的同名变量,不会抛出错误。
2)const声明:
ES6还提供了const关键字,声明的是常量,其值一旦被设定后不可更改。每个通过const声明的常量必须进行初始化。
二、字符串和正则表达式:
JavaScript字符串一直是基于16位字符编码UTF-16进行构建,每16位的序列是一个编码单元,代表一个字符,length、charAT()等字符串属性和方法都是基于这种编码单元构建的。
1. 使用Unicode编码:
Unicode编码的目标是为全世界每一个字符提供全球唯一的标识符,使用32bit编码,如果把字符长度限制在16位,码位数量不足以表示如此多字符。
UTF-16中,前2**16个码位均以16位的编码单元表示,这个范围被称为基本多文种平面BMP(Basic Multilingual Plane);超出这个范围的码位则要归属于某个辅助平面supplementary plane,其中的码位仅用16位就无法表示了,引入代理对surrogate pair,规定用两个16位编码单元表示一个码位。
对于辅助平面中的字符,text.length的值为2,而BMP平面的字符则为1。而且还会造成相应的正则表达式、charAt()和charCodeAt()方法返回值都出现异常。
ES6强制使用UTF-16字符串编码来解决这个问题,并按照这种字符编码来标准化字符串操作。
1)codePointAt()方法:
ES6增加了完全支持UTF-16的codePointAt()方法,这个方法接受编码单元的位置而非字符位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。
对于BMP字符集中的字符,codePointAt()方法的返回值与charCodeAt()方法的相同。对于非BMP字符,codePointAt()方法返回完整的码位,即使这个码位包含多个编码单元。因为非BMP字符集占用两个编码单元,所以对前一个编码单元使用codePointAt()方法可以获取完整的码位,而还可以对后一个编码单元使用codePointAt()方法,获取的结果与charCodeAt()方法的结果相同。
要检测一个字符占用的编码单元数量,可以采用codePointAt()方法,如果其值大于0xFFFF,就是由两个编码单元来表示,总共有32位。
2)String.fromCodePoint()方法:
可以使用fromCodePoint()方法根据指定的码位生成一个字符。对于BMP字符,fromCodePoint()方法与ES6的fromCharCode()方法结果相同;对非BMP码位,二者执行结果不同。
3)normalize()方法:
ES6为字符串添加了normalize()方法,接受一个可选的字符串参数,指明应用几种Unicode标准化方法:
· NFC:以标准等价方式分解,然后以标准等价方法重组,是默认选项
· NFD:以标准等价方式分解
· NFKC:以兼容等价方式分解
· NFKD:以兼容等价方式分解,然后以标准等价方式重组
在对比和排序等字符串操作之前,一定要把它们标准化为同一种形式。一般在国际化应用时才会用到。
4)正则表达式u修饰符:
正则表达式默认将字符串中的每一个字符按照16位编码单元处理。为解决这个问题,ES6定义了支持Unicode的u修饰符。
当一个正则表达式添加了u修饰符时,就从编码单元操作模式切换为字符模式,这样正则表达式就不会将非BMP编码视为两个字符。
5)计算码位数量:
ES6的length属性仍然返回字符串编码单元数量,有了u修饰符后,可以通过正则表达式来解决这个问题:
function codePointLength(text){
代码中创建了一个支持Unicode的正则表达式,通过调用match()方法来检查空格和非空格字符,当匹配到至少一个字符时,返回数组中包含所有匹配到的字符串,其长度为字符串中码位的数量。
let result=text.match(/[\s\S]/gu);
return result?result.length:0;
}
这个方法有效,但在统计长字符串中的码位数量时,运行效率较低,可以使用字符串迭代解决效率问题。
因为u修饰符是语法的更改,在不支持ES6的JavaScript引擎中使用会导致语法错误,需要通过函数检测:
function hasRegExpU(){
函数中使用了RegExp构造函数并传入字符串u作为参数,老式的浏览器引擎支持这个语法。
try{
var pattern=new RegExp(".","u");
return true;
}catch(ex) {
return false;
}
}
2. 新增的字符串方法:
1)字符串中的子串识别:
ES6提供了3个相似的方法用于检测子串:
· includes():如果字符串中检测到指定文本则返回true,否则返回false
· startsWith():如果在字符串的起始位置检测到指定文本则返回true,否则返回false
· endsWith():如果在字符串的结束部分检测到指定文本则返回true,否则返回false
上述3个方法都接受两个参数,第一个参数指定要搜索的文本,第二个参数是可选的,指定一个开始搜索的位置的索引值。
ES5中的indexOf()方法或lastIndexOf()方法可以传入正则表达式来搜索字符串,而这3个方法传入正则表达式则会触发错误。
2)repeat()方法:
ES6为字符串增加了repeat()方法,接受一个number参数,表示该字符串的重复次数,返回值为当前字符串重复一定次数后的新字符串。示例:
"abc".repeat(4);
3. 正则表达式的新特性:
1)正则表达式的y修饰符:
正则表达式中有sticky属性,当在字符串中开始字符匹配时,它会通知搜索从正则表达式的lastIndex属性开始进行,如果在指定位置没能成功匹配,则停止继续匹配。当执行操作时,y修饰符会把上次匹配后面一个字符的索引保存在lastIndex中;如果该操作匹配的结果为空,则lastIndex会被重置为0。
只有调用exec()和test()这些正则表达式对象的方法时才会涉及lastIndex属性;而调用字符串的方法,如match(),则不会触发sticky行为。
2)正则表达式的复制:
在ES5中,通过给RegExp构造函数传递正则表达式作为参数可以复制这个正则表达式,但不能为正则表达式指定一个修饰符:
var re1=/ab/i;
在ES6中,可以为Regexp()中的正则表达式修过其修饰符。
var re2=new RegExp(re1, "g");
3)正则表达式的flags属性:
ES5中,可以通过source属性获取正则表达式的文本,如果要获取使用的修饰符需要使用toString()方法输出文本并识别最后一个“/”后面的字符,以此确定使用的修饰符。
ES6新增了flags属性,用于获取应用于当前正则表达式的修饰符字符串。
4. 模板字面量:
模板字面量表面上看是用反撇号`替换了单、双引号的字符串。如果字符串其中使用了反撇号,则使用反斜杠转义即可。不需要转义单引号和双引号。
在模板字面量中可以使用多行字符串,因为反撇号中的所有空白符都是字符串的一部分,在其中可以通过适当缩进来对齐文本。
模板字面量有占位符功能,可以把任何合法的JavaScript表达式嵌入到占位符中并将其作为字符串的一部分输出到结果中。占位符由左侧的${和右侧的}符号组成,中间可以包含任意的JavaScript表达式。如:
message=`Hello, ${name}.`;
占位符${name}访问本地变量name并将其插入到message字符串中,然后变量message就会一直保留着替换后的结果。模板字面量可以访问作用域中的所有可访问的变量。
占位符可以使用JavaScript合法表达式,可以在其中嵌入运算式、函数调用等:
message=`${count} items cost $${{count*price).toFixer(2)}.`;
模板字面量本身也是JavaScript表达式,所以可以在其中嵌入模板字面量。
1)标签模板:
标签模板是模板字面量第一个反撇号前方标注的字符串:
let message=tag`Hello world`;
其中的tag就是模板标签。
标签可以是一个函数,格式:
function tag(literals, ...substitutions){
标签函数通常使用不定参数特性来定义占位符,这样可以简化数据处理过程。其中第一个参数是一个数组,包含后面的字面量字符串;后面的参数是每一个占位符的解释值。示例:
//返回一个字符串
}
function passthru(literals, ...substitutions){
示例中定义了一个passthru标签,对应一个passthru()函数,它会接受3个参数,首先是literals数组,数组包含以下元素:
let result='';
for(let i=0;i<substitutions.length;i++){
result+=literals[i];
result+=substitutions[i];
}
result+=literals[literals.length-1];
result result;
}
let count=10,
price=0.25,
message=passthru`${count} items cost $${(count*price).toFixed(2)}.`;
console.log(message); //"10 items cost $2.50."
· 第一个占位符前的空字符串""
· 第一、二个占位符之间的字符串" items cost $"
· 第二个占位符后的字符串"."
substitutions数组的第一个元素是变量count的解释值,传递参数为10;后一个元素是(count*price).toFixed(2)的解释值,传递参数为2.50。
通过这种模式,可以将literals和substitutions两个数组交织在一起,重组结果字符串。先取出literals中的首个元素,再取出substitutions中的首个元素,然后交替继续取出下一个元素,直至字符串拼接完成。substitutions的示例总比literals少一个,通过从两个数组中交替取值的方式模拟模板字面量的默认行为。这里使用substitutions.length来为循环计数。
2)在模板字面量中使用原始值:
标签模板也可以访问原生字符串信息,使用内建的String.raw()标签。示例:
let message1=`Multiline\nstring`,
let message2=String.raw`Multiline\nstring`;
代码中,message1中的\n被解释为一个新行,而变量message2获取的是\m的原生形式\\n。
原生字符串信息同样被传入标签模板,标签函数的第一个参数是一个数组,它有一个额外属性raw,是一个包含每一个字面值的原生等价信息的数组。示例:
function raw(literals, ...substitutions){
代码中使用了literals.raw来输出字符串,结果所有的字符转义,包括Unicode码位转义,都会输出它们的原生形式。这在想要输出一些含有代码的字符串,而代码中又包含转义序列时,能够发挥其作用,如生成一些关于代码的文档。
let result='';
for(let i=0;i<substitutions.length;i++){
result+=literals.raw[i];
result+=substitutions[i];
}
result+=literals.raw[literals.length-1];
result result;
}
let message=raw`Multiline\nstring`;
console.log(message.length); //17
三、函数:
1. 函数的新特性:
1)ES6默认参数值:
JavaScript函数,无论函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时为其指定一个默认值。示例:
function makeRequest(url, timeout, callback){
示例代码中,timeout和callback为可选参数。对于函数的命名参数,如果不显式传值,默认为undefined,因此代码中使用逻辑或操作为缺失的参数提供默认值。但传入timeout=0,也会被认为是false,被赋值为默认值2000。这时可以使用typeof检查参数类型:
timeout=timeout||2000;
callback=callback||function(){};
//函数其余部分
}
timeout=(typeof timeout!=="undefined")?timeout:2000;
这种方法更安全,但代码会比较多。
callback=(typeof callback!=="undefined"):callback:function(){};
ES6简化了形参提供默认值的过程,如果没有为参数传入值则为其提供一个初始值。示例:
function makeRequest(url, timeout=2000, callback=function(){}){
此函数中,只有第一个参数是必需的,其他两个参数都有默认值,而且不需要添加校验代码。当不为参数传入值或传入undefined值时才会使用默认值。
//函数其余部分
}
2)ES6无命名参数:
JavaScript提供arguments对象来检查函数的所有参数,从而不需要定义每一个要用的参数。但这种方式不是显式的,并不容易看出这个函数可以接受任意数量的参数。
function pick(object){
ES6引入了不定参数,在函数的命名参数前添加三个点表明这是一个不定参数,该参数是一个数组,包含着自它之后传入的所有参数,通过这个数组名就可以逐一访问里面的参数。
let result=Object.create(null);
for(let i=1,len=arguments.length;i<len;i++){
result[arguments[i]]=object[arguments[i]];
}
return result;
}
function pick(object, ...keys){
不定参数keys包含的是object之后传入的所有参数,这样就可以遍历keys对象了。
let result=Object.create(null);
for(let i=0,len=keys.length;i<len;i++){
result[keys[i]]=object[keys[i]];
}
return result;
}
不定参数有两条使用限制,每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾;不定参数不能用于对象字面量setter中,因为对象字面量setter的参数有且只能有一个。
3)增强的Function构造函数:
Function构造函数在JavaScript中很少被用到,通常用来动态创建新的函数。这种函数接受字符串形式的参数,分别为函数的参数及函数体。示例:
var add=new Function("first", "second", "return first+second");
ES6增强了Function构造函数的功能,支持创建函数时定义默认参数和不定参数:
console.log(add(1, 1));
var add=new Function("first", "second=first", "return first+second");
4)展开运算符:
console.log(add(1));
var pickFirst=new Function("...args", "return args[0]");
console.log(pickFirst(1, 2));
不定参数可以用来指定多个各自独立的参数,并通过整合后的数组来访问;而展开运算符则可以用来指定一个数组,将它们打散后作为各自独立的参数传入函数。
JavaScript内建的Math.max()方法可以接受任意数量的参数并返回值最大的那一个。示例:
let value1-25, value2=50;
Math.max()方法不允许传入数组,所以在ES5中需要手动实现从数组中遍历取值,或使用apply()方法:
console.log(Math.max(value1, value2));
let values=[25, 60, 78, 100];
使用ES6的展开运算符可以简化上述操作:
console.log(Math.max.apply(Math, values));
console.log(Math.max(...values));
代码向Math.max()方法传入了一个数组,只是在数组前添加了不定参数中使用的...符号。JavaScript引擎读取程序后会将参数数组分割为各自独立的参数并依次传入。
可以将展开运算符与其他正常传入的参数混合使用,比如想限定Math.max()返回的最小值为0,可以单独传入限定值,其他参数仍然使用展开运算符:
console.log(Math.max(...values, 0));
示例中,Math.max()函数先用展开运算符传入数组中的值,又传入了参数0。
5)函数的name属性:
ES6为所有函数新增了name属性,函数表达式和匿名函数也有这个属性:
var doAnotherTing=function(){};
使用getter函数创建的函数名称有前缀get;使用setter函数创建的函数名有前缀set;使用bind()函数创建的函数名带有前缀bound;通过Function构造函数创建的函数名带有前缀anonymous。
console.log(Math.max(doAnotherThing.name); //doAnotherThing
函数的name属性的值不一定引用同名变量,只是协助调试用的额外信息,所以不能使用来获取对于函数的引用。
6)明确函数的多重用途:
function Person(name){
对于这个函数,可以有两种使用方法,一种是使用关键字new来调用,其实是作为一个类class来使用;一种是不使用new,当作函数来使用。示例:
this.name=name;
}
var person=new Person("Tom")
在ES6中,JavaScript函数有两个不同的内部方法,[[Call]]和[[Construct]],当使用new关键字调用函数时,执行的是[[Construct]]函数,用来创建一个通常被称为实例的新对象,然后再执行函数体,将this绑定到实例上;如果不通过new关键字调用,则执行[[Call]]方法,从而直接执行代码中的函数体。
var notAPerson=Person("Tom")
具有[[Construct]]方法的函数统称为构造函数,不是所有函数都有这个方法,因此不是所有函数都可以通过new来调用。
7)元属性new.target:
在ES5中,想确定一个函数是否通过new关键字被调用,常用instanceof:
function Person(name){
但使用call()方法也还是无法区分:
if(this instanceof Person){
this.name=name;
}else{
throw new Error("must use new keyword")
}
}
var notAPerson=Person.call(person, "Tom");
ES6引入了元属性new.target,可以提供非对象目标的补充信息。当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,通常是新建对象实例,也就是函数体内的this的构造函数;如果调用[[Call]]方法,则new.target的值为undefined。使用方法:
function Person(name){
或者使用:
if(typeof new.target!=="undefined"){
this.name=name;
}else{
throw new Error("must use new keyword")
}
}
function Person(name){
在函数外使用 new.target是一个语法错误。
if(typeof new.target===Person){
this.name=name;
}else{
throw new Error("must use new keyword")
}
}
8)块级函数:
在一个代码块中,如if语句块中定义的一个函数,称为块级函数。每个浏览器对块级函数的支持不同,尽量不要使用,最好选择函数表达式。
函数定义在代码块中,会被提升至块的顶部,有时会造成问题。ES6中,块级函数不再提升至代码块顶部,而是提升至外围函数或全局作用域的顶部,移除了存在于各浏览器间不兼容的行为。
9)尾调用优化:
尾调用指的是函数作为另一个函数的最后一条语句被调用。示例:
function doSimething(){
在ES5中,尾调用的实现与其他函数调用的实现类似,创建一个新的栈帧stack frame,将其推入调用栈来表示函数调用。在循环调用中,每一个未用完的栈帧都会被保存在内存中,当调用栈变得过大时会造成问题。
return doSomethingElse(); //尾调用
}
ES6缩减了严格模式下尾调用栈的大小,尾调用不再创建新的栈帧,而是清除并重用当前栈帧。实际上尾调用优化发生在引擎背后,除非尝试优化一个函数,否则无须思考这个问题。递归函数是其最主要的用于场景,此时尾调用优化的效果最显著。
2. 箭头函数:
箭头函数是使用箭头“=>”定义函数的新语法。箭头函数没有this、super、arguments和new.target绑定,这些值由外围最近一层非箭头函数决定;箭头函数不能通过new关键字调用,因为没有[[Construct]]方法,不能用作构造函数,也没有prototype属性。
this绑定是JavaScript中常见的错误来源,在函数中很容易对this的值失去控制,并导致程序出现意想不到的行为,箭头函数消除了这个问题,简化了代码执行的过程。箭头函数只能作为函数来使用,而不能用作类的构造函数。箭头函数也有name属性。
箭头函数语法多变,所有变种都由函数参数、箭头、函数体组成。示例:
let reflect=value=>value;
功能相当于:
let reflect=function(value){
当箭头函数还有一个参数时,可以直接写参数名,箭头紧随其后,箭头右侧的表达式被求值后便立即返回。如果需要传入两个或两个以上的参数,要在参数的两侧添加一对小括号:
return value;
};
let sun=(num1, num2)=>num1+num2;
相当于:
let sum=function(sum1, sum2){
如果函数没有参数,也要在声明时写一组没有内容的小括号:
return num1+num2;
};
let getName=()=>"Tom";
相当于:
let getName=function(){
如果要为函数编写由多个表达式组成的更传统的函数体,需要使用大括号括起来,并显式定义一个返回值:
return "Tom";
};
let sum=(num1, num2)=>{
如果想创建一个空函数,需要写一个没有内容的大括号:
return num1+num2;
};
let doNothing=()=>{};
如果想在箭头函数外返回一个对象字面量,需要将该字面量包裹在小括号内:
let getTempItem=id=>({id:id, name:"Temp"});
相当于:
let getTempItem=function(id){
将对象字面量包裹在小括号内,是为了将其与函数体区分开来。
return {id:id, name:"temp"};
};
箭头函数还可以用来创建立即执行函数表达式。可以定义一个匿名函数并立即调用,并不保存对该函数的引用。示例:
let person=function(name){
使用箭头函数表示为:
return {getName:function(){return name;}};
}("Tom");
console.log(person.getName());
let person=((name))=>{
小括号只包裹了箭头函数定义,没有包裹("Tom"),这与正常函数不同。
return {getName:function(){return name;}};
})("Tom");
console.log(person.getName());
箭头函数语法简洁,非常适合用于数组处理,比如自定义一个比较器:
var result=values.sort((a,b)=>a-b);
像sort()、map()及reduce()这些可以接受回调函数的数组方法都可以通过箭头函数语法简化编码过程。
也可以在箭头函数上调用call()、apply()及bind()方法。示例:
var sun=(num1, num2)=>num1+num2;
console.log(sum.call(null, 1, 2)); //3
console.log(sum.apply(null, [1, 2])); //3
var boundSum=sum.bind(null, 1, 2);
四、扩展对象功能性:
JavaScript中,几乎每个值都是某种特定类型的对象,随着应用复杂度的增加,对象使用效率的提升变得至关重要。ES6将对象分为Ordinary对象、Exotic对象、Standard对象和内建对象。
1. 对象字面量语法扩展:
对象字面量是JavaScript中最流行的模式之一,要创建对象,不再需要编写冗余的代码,直接通过它简洁的语法就可以实现。
1)属性初始值简写:
ES5中,对象字面量只是简单的键值对集合,初始化属性时会有一些重复。示例:
function createPerson(name, age){
ES6中,通过使用属性初始化的简写语法,可以消除这种属性名称与局部变量之间的重复书写。当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可:
return {name:name, age:age};
}
function createPerson(name, age){
当对象字面量中只有一个属性的名称时,JavaScript引擎会在可访问作用域中查找其同名变量;如果找到,则该变量的值被赋给对象字面量中的同名属性。
return {name, age};
}
2)对象方法的简写:
ES5中,如果为对象添加方法,必须通过指定名称并完整定义函数来实现。示例:
var person={
在ES6中,语法更简洁,消除了冒号和function关键字:
name:"Tom",
sayName:function(){console.log(this.name);}
};
var person={
代码中,person对象中创建了一个sayName()方法,该属性被赋值为一个匿名函数表达式。
name:"Tom",
sayName(){console.log(this.name);}
};
3)可计算属性名:
ES5中,想要计算得到属性名,需要用方括号代替点。示例:
var person={};
两个属性名称中都含有空格,不能使用点来引用属性,却可以使用方括号,因为它支持通过任何字符串值作为名称访问属性的值。也可以这样使用:
var lastName="last name";
person["first name"]="Tom";
person[lastName]="Jerry";
var person={
这种模式适合于属性名提前已知或可被字符串字面量表示的情况。如果属性名"first name"被包含在一个变量中,或者需要通过计算才能得到的值,ES5中就无法为一个对象字面量定义该属性。
"first name":"Tom"
};
在ES6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。示例:
let lastName="last name";
在对象字面量中使用方括号表示的该属性名称是可以计算的,它的内容将被求值并被最终转化为一个字符串,因而同样可以使用表达式作为属性的可计算名称。
let person={
"first name":"Tom",
[lastName]:"Jerry"
};
console.log(person["first name"]);
console.log(person[lastName]);
var suffix=" name";
这些属性被求值后为字符串"first name"和"last name",然后它们可用于属性引用。任何可用于对象实例括号方法的属性名,也可以作为字面量中的计算属性名。
let person={
["first"+suffix]:"Tom",
["last"+suffix]:"Jerry"
};
console.log(person["first name"]);
console.log(person["last name"]);
2. 新增方法:
1)Object.is()方法:
ES6引入Object.is()方法来弥补全等运算符的不准确运算,这个方法接受两个参数,如果这两个参数类型相同且具有相同的值,则返回true。示例:
console.log(Object.is(+0, -0)); //false
2)Object.assign()方法:
console.log(Object.is(NaN, naN)); //true
console.log(Object.is(5, "5")); //false
ES6引入Object.assign()方法,此方法接受一个接收对象和任意数量的源对象,最终返回接收对象。示例:
var receiver={};
两个源对象具有同名的type属性,返回的是"css"。
Object.assign(receiver,
{type:"js",name:"file.js"},
{type:"css"}
);
console.log(receiver.type); //"css"
console.log(receiver.name); //"file.js"
3. 重复的对象字面量属性:
ES6中重复属性检查已经移除,对于每一组重复属性,都会选取最后一个取值。示例:
var person={
name:"Tom",
name:"Jerry"
};
console.log(person.name); //"Jerry"
4. 自有属性枚举顺序:
ES6规定了自有属性被枚举时返回的顺序,这会影响到Object.getOwnPropertyNames()方法及Reflect.ownKeys,Object.assign()方法处理属性的顺序也会改变。基本规则是:所有数字键按升序排序,所有字符串键按照被加入到对象的顺序排序,所有symbol键按照被加入对象的顺序排序。
对于for-in循环,仍未指定一个明确的枚举顺序,而Object.keys()方法和JSON.stringify()方法都指明与for-in使用相同的枚举顺序,因此枚举顺序也不明确。
5. 增强对象原型:
1)改变对象的原型:
正常情况下,无论是通过构造函数还是Object.create()方法创建对象,其原型在创建时被指定的,对象原型在实例化后保持不变。
ES6添加了Object.setPrototypeOf()方法来改变任意指定对象的原型,它接受两个参数,被改变原型的对象及替代第一个参数原型的对象。示例:
let person={
代码中定义了两个基对象person和dog,二者都有getGreeting()方法,且都返回一个字符串。friend对象先继承person对象,调用getGreeting()方法输出Hello;使用Object.setPrototypeOf (friend, dog)改变对象原型为dog后,调用getGreeting()方法输出的是Woof。
getGreeting(){return "Hello";}
};
let dog={
getGreeting(){return "Woof";}
};
let friend=Object.create(person);
console.log(friend.getGreeting()); //"Hello"
console.log(Object.getPrototypeOf(friend)===person); //true
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); //"Woof"
console.log(Object.getPrototypeOf(friend)===dog); //true
对象原型的真实值被存储在内部专用属性[[Prototype]]中,调用Object.getPrototypeOf()方法返回存储在其中的值,而调用Object.setPrototypeOf()方法会改变其中的值。
2)简化原型访问的Super引用:
ES6中引入了Super引用的特性,可以更便捷地访问对象原型。比如,重写了对象示实例的方法,又需要调用与它同名的原型方法,就可以使用super,如super.getGreeting()。
Super引用在多级继承情况下非常有用,因为此时使用Object.getPrototypeOf()方法会出问题。
6. 正式的方法定义:
ES6中,正式将方法定义为一个函数,它会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象。
super引用都通过[[HomeObject]]属性来确定后续的运行过程,第一步是在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用;然后搜索原型找到同名函数;最后设置this绑定并且调用相应的方法。
五、解构:
对象和数组字面量是JavaScript中两种最常用的数据结构。编码过程中,经常定义许多对象和数组,然后从中提取相关的信息片段。ES6为对象和数组都添加了解构功能,将数据解构打散,从更小的部分中获取需要的信息。
1. 对象解构:
let node={type:"Identifier", name:"foo"};
如果使用var、let或const解构声明变量,必须要提供初始化程序,即等号右边的值。
let {type, name}=node;
2. 解构赋值:
同样可以在给变量赋值时使用解构语法。示例:
let node={type:"Identifier", name:"foo"};
要使用小括号包裹解构赋值语句。JavaScript将一对开放的大括号视为一个代码块,语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式。
let type="Literal", name=5;
({type, name}=node);
3. 解构默认值:
使用解构赋值表达式时,如果指定的局部变量名称在对象中不存在,会被赋值为undefined:
let {type, name, value}=node;
代码中,因为node对象上没有对应value的属性值,被赋值为undefined。当指定的属性不存在时,可以随意定义一个默认值:
let {type, name, value=true}=node;
此处,没有node.value属性,value使用的预设的默认值true。
4. 为非同名局部变量赋值:
前面的解构赋值都是使用与对象属性同名的局部变量,ES6提供了扩展语法,可以使用不同名的局部变量来存储对象的属性值:
let node={type:"Identifier", name:"foo"};
上述代码使用解构赋值声明了变量localType和localName,这两个变量分别包含node.type和node.name属性的值。此时也可以添加默认值:
let {type:localType, name:lacalName}=node;
let {type:localType, name:lacalName="bar"}=node;
5. 嵌套对象解构:
嵌套对象也可以解构:
let node={type:"Identifier", name:"foo", loc:{
解构中使用了大括号,在找到node对象中的loc属性后,深入一层继续插值start属性。
start:{line:1, column:1}, end:{line:1, column:4}}};
let {loc:{start}}=node;
console.log(start.line); // 1
6. 数组的解构:
数组解构比较简单。示例:
let colors=['red', 'green', 'blue'];
代码中,从数组中解构出两个值,并分别存储在两个变量中。数组解构中,通过值在数组中的位置进行选取,且可以存储在任意变量中,未显式声明的元素都会直接被忽略。
let [firstColor, secondColor]=colors;
数组解构时,可以直接省略元素,只为感兴趣的元素提供变量名。示例:
let [,, thirdColor]=colors;
使用这种方法可以提取数组中的部分元素,而不需要为每个元素都指定变量名。
数组解构也可以用于赋值,且不需要用小括号包裹表达式:
let colors=['red', 'green', 'blue'];
数组解构也可以使用默认值:
let firstColor='black', secondColor='purple';
[firstColor, secondColor]=colors;
let [firstColor, secondColor='green']=colors;
数组解构中可以使用不定元素,通过...语法将数组中的其余元素赋值给一个变量:
let [firstColor, ...restColor]=colors;
可以通过不定元素的语法来实现数组复制功能:
let [...clonedColor]=colors;
数组解构也可以用于嵌套数组中。
7. 数组解构用于交换两个变量的值:
ES5中交换两个变量的值要引入第三个临时变量。ES6中,可以直接交换:
let a=1, b=2;
[a, b]=[b, a];
8. 混合解构:
可以混合使用对象解构和数组解构来创建更复杂的表达式,可以从混杂着对象和数组的数据中解构出需要的信息。
9. 参数解构:
解构还可以用在函数参数的传递过程中。当定义一个接受大量可选参数的JavaScript函数时,通常会创建一个可选对象,将额外的参数定义为这个对象的属性。示例:
function setCookie(name, value, options){
如果将options定义为解构参数,可以更清晰地了解函数预期传入的参数:
options=options||{};
let secure=options.secure,
path=options.path,
domain=options.domain,
expires=options.expires;
//code
}
function setCookie(name, value, {secure, path, domain, expires}){
如果解构参数是可选的,可以使用如下方法:
//code
}
function setCookie(name, value, {secure, path, domain, expires}={}){
可以以为解构参数使用默认值:
//code
}
function setCookie(name, value, {secure=false, path='/', domain='', expires}){
//code
}
六、Symbol及Symbol属性:
ES6引入了Symbol类型,通过Symbol可以为属性添加非字符串名称。Symbol属性比较难以被意外覆盖而改写,非常适合于需要一定程度保护的功能。
1. 创建Symbol:
let firstName=Symbol();
代码中创建了一个名为firstName的Symbol,用它将一个新的属性赋值给person对象,每当访问这个属性就要用到最初定义的Symbol。Symbol接受一个可选参数,可以添加一段文本描述,这段描述被存储在内部[[Description]]属性中,当调用Symbol的toString()方法时才可以读取。
let person={};
person[firstName]="Tom";
let firstName=Symbol('first name');
ES6中扩展了typeof操作符,支持返回Symbol。示例:
let symbol=Symbol("test symbol");
console.log(typeof symbol); // "symbol"
2. Symbol的使用:
所有使用可计算属性名的地方,都可以使用Symbol,可用于Object.defineProperty()方法和Object.defineProperties()方法的调用过程中。
没有其他类型与Symbol逻辑等价的值,不能将Symbol强制转换为字符串和数字类型。可以使用String()方法,调用的是toString()方法,输出的是Symbol的描述[[Description]]中的内容。
3. Symbol共享体系:
ES6提供了一个可以随时访问的全局Symbol注册表,用于在不同代码中共享Symbol。如果想创建一个可共享的Symbol,要使用Symbol.for()方法,接受即将创建的Symbol字符串标识符为参数。示例:
let uid=Symbol.for("uid");
Symbol.for()方法首先在全局Symbol注册表中搜索键为uid的Symbol是否存在,如果存在直接返回已有的Symbol;否则创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随即返回新创建的Symbol。
let object={};
object[uid]='12345';
Symbol全局注册表是一个类似全局作用域的共享环境,当使用第三方组件时,尽量使用Symbol键的命名空间以减少命名冲突,如加上特定前缀。
4. Symbol属性检索:
Object.keys()方法和Object.getOwnPropertyNames()方法可以检索对象中的所有属性名,但都不支持Symbol属性。ES6添加了Object.getOwnProperty-Symbols()方法来检索对象中的Symbol属性,返回值是一个包含所有Symbol自有属性的数组。
所有对象一开始都没有自己独有的属性,但是对象可以从原型链中继承Symbol属性,ES6通过一些well-known Symbol预定义了这些属性。
· Symbol.hasInstance:一个在执行instanceof时调用的内部方法,用于检测对象的继承信息
· Symbol.isConcatSpreadable:一个布尔值,用于表示当传递一个集合作为Array.prototype. concat()方法的参数时,是否应该将集合内的元素规整到同一层次
· Symbol.iterator:一个返回迭代器的方法
· Symbol.match:一个在调用String.prototype.match()方法时调用的方法,用于比较字符串
· Symbol.replace:一个在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串
· Symbol.search:一个在调用String.prototype.search()方法时调用的方法,用于在字符串上定位子串
· Symbol.species:用于创建派生类的构造函数
· Symbol.split:一个在调用String.prototype.split()方法时调用的方法,用于分割字符串
· Symbol.toPrimitive:一个返回对象原始值的方法
· Symbol.toStringTag:一个在调用String.prototype.toString()方法时使用的字符串,用于创建对象描述
· Symbol.unscopables:一个定义了一些不可被with语句引用的对象属性名称的对象集合
重写一个由well-known Symbol定义的方法,会导致对象内部的默认行为被改变,从而一个普通对象会变为一个奇异对象。
七、Set集合与Map集合:
Set集合是一种无重复元素的列表,一般只是检测给定值是否存在集合中;Map集合内含多组键值对,经常用于缓存频繁取用的数据。
1. Set集合:
ES6中新增的Set类型是一种有序列表,其中含有一些相互独立的非重复值。调用new Set()创建Set集合,调用add()方法向集合中添加元素,size属性可用于获取集合中当前元素的数量。示例:
let set=new Set();
在set集合中,不会对所存值进行强制的类型转换,数字5和字符串"5"以两个独立的元素存在。如果多次调用add()方法并传入相同的值,那么后续的调用实际上被忽略。但是,如果向Set集合中添加多个对象,它们之间相互独立,比如加入两个空对象,都会存在。
set.add(5);
set.add("5");
console.log(set.size); // 2
key1={}, key2={};
1)set集合的常用操作:
set.add(key1);
set.add(key2);
⑴has()方法:检测Set集合中是否存在某个值
⑵delete()方法:移除Set集合中的某个元素
⑶clear()方法:集合Set中所有元素被移除
2)遍历Set集合中的所有元素:
forEach()方法用于遍历集合中的所有元素,其回调函数接受3个参数,前两个参数一样,为下一个索引的位置,第三个元素为集合本身。示例:
let set=new Set([1,2]);
3)将集合Set转换为数组:
set.forEach(function(value, key, ownerSet){
console.log(ownerSet===set); // true
});
集合Set不能像数组那样通过索引访问其中的元素,如果需要将集合转换成一个数组。
let set=new Set([1,2,3,4,5]); // 将数组转换为集合
4)Weak Set集合:
let array=[...set]; //将集合转换为数组
ES6引入了Weak Set集合,只存储对象的弱引用,并且不可以存储原始值。集合中的弱引用如果是对象位于的引用,则会回收并释放相应的内存。用WeakSet构造函数创建Weak Set集合。示例:
let set=new WeakSet(),
Weak Set集合支持add()、has()和delete()方法。
let key={};
Weak Set集合不可迭代,所以不能被用于for-of循环;Weak Set集合也不支持forEach()方法,不支持size属性。
set.add(key);
console.log(set.has(key)); // true
set.delete(key);
console.log(set.has(key)); // false
2. Map集合:
ES6中的Map类型是一种存储着许多键值对的有序列表。键名的等价性判断是通过调用Object.is()方法实现的。向Map集合中添加新元素使用set()方法,并分别传入键名和对应值为两个参数;如果要从集合中获取信息,要调用get()方法。示例:
let map=new Map();
如果调用get()方法时传入的键名在Map集合中不存在,会返回undefined。Map中,可以使用对象为键名。
map.set("title", "ES6");
console.log(map.get("title")); // "ES6"
1)Map集合支持的方法:
⑴has(key)方法:检测指定的键名在Map集合中是否已经存在
⑵delete(key)方法:从Map集合中移除指定键名及其对应的值
⑶clear()方法:清除Map集合中的所有键值对
Map集合也支持size属性,表示当前集合中包含的键值对数量。
2)Map集合的初始化方法:
可以向Map构造函数传入数组来初始化一个Map集合,数组中每个元素都是一个子数组,子数组中包含一个键值对的键名与值两个元素。
let map=new Map([['name','Tom'],['age',25]]);
3)Map集合的forEach()方法:
Map集合的forEach()方法的回调函数接受三个参数,集合下一个索引的位置、值对应的键名、Map集合本身。示例:
map.forEach(function(value, key, ownerMap){
遍历过程中,会安照键值对插入Map集合的顺序将相应信息插入forEach()方法的回调函数。
console.log(ownerSet===map); // true
});
4)Weak Map集合:
Weak Map集合是弱引用Map集合,用于存储对象的弱引用。Weak Map集合中的键名必须是对象,其中保存的是这些对象的弱引用,如果在弱引用之外不存在其他的强引用,引擎的垃圾回收机制会自动回收这个对象,同时也会移除这个键值对。但如果键名对应的值是一个对象,且保存的是对象的强引用,不会触发垃圾回收机制。
Weak Map集合最大的用途是保存Web页面中的DOM元素。
ES6的Weak Map集合是一种存储着许多键值对的无序列表,键名必须是非null类型的对象,键名对应的值则可以是任意类型。可以通过set()方法添加数据,通过get()方法获取数据,还支持has()方法检测指定的键是否存在,delete()方法可以移除指定的键值对。
Weak Map集合不支持size属性,无法验证是否为空;不支持clear()方法,不支持forEach方法。
八、迭代器和生成器:
用循环语句迭代数据时,必须要初始化一个变量来记录每一次迭代在数据集合中的位置,在许多编程语言中已经开始通过程序化的方式用迭代器对象返回迭代过程中集合的每一个元素。迭代器的使用可以极大地简化数据操作,ES6也添加了迭代器特性,对于高效的数据处理是不可或缺的。
1. 迭代器:
迭代器是一种特殊对象,都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性,一个是value,表示下一个将要返回的值;另一个是done,是布尔类型,当没有更多可返回数据时返回true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值。如果在最后一个值返回后在调用next()方法,那么返回的对象中属性done的值为true。
创建一个迭代器,需要实现next()方法,其中要判断是否到最后一个元素,以让done属性变为true。代码比较复杂。
1)生成器:
生成器是一种返回迭代器的函数,通过function关键字后的星号*来表示,函数中会用到关键字yield。示例:
function *createIterator(){
yield关键字是ES6的新特性,可以通过它来指定调用迭代器的next()方法时的返回值及返回顺序。生成迭代器后,连续三次调用它的next()方法返回三个不同的值,分别是1、2和3。
yield 1;
yield 2;
yield 3;
}
let iterator=createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
每当执行完一次yield语句后,函数就会自动停止执行。使用yield关键字可以返回任何值或表达式,所以可以通过生成器函数批量地给迭代器添加元素。yield关键字只能在生成器内部使用,在其他地方使用会导致程序抛出语法错误。一般常在循环中使用yield语句。
也可以通过函数表达式来创建生成器,只需要在function关键字和小括号中间添加一个星号即可:
let createIterator=function *(items){
不能用箭头函数来创建生成器。
for (let i=0;i<items.length;i++){
yield items[i];
}
};
let iterator=createIterator([1,2,3]);
因为生成器就是函数,因而可以将它们添加到对象中。ES6风格的方法为:
let o={
2)可迭代对象和for-of循环:
*createIterator(items){
for(let i=0;i<items.length;i++){
yield items[i];
}
}
};
let iterator=o.createIterator([1,2,3]);
可迭代对象具有Symbol.iterator属性,是一种与迭代器密切相关的对象。ES6中,所有的集合对象,如数组、Set集合、Map集合和字符串,都是可迭代对象,这些对象中都有默认的迭代器。生成器默认有Symbol.iterator属性,因此所有通过生成器创建的迭代器都是可迭代对象。
for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将执行这一过程直到返回对象的done属性的值为true。
let values=[1,2,3];
代码中for-of循环通过调用values数组的Symbol.iterator方法来获取迭代器,随后迭代器的next()方法被多次调用,从其返回对象的value属性读取值并存储在变量num中,当结果对象的done属性值为true时循环退出。相比传统的for循环,for-of循环的控制条件更简单。
for(let num of values){
console.log(num);
}
由于具有Symbol.iterator属性的对象都有默认的迭代器,因此可以用它检测对象是否为可迭代对象:
function isIterable(object){
return typeof object[Symbol.iterator]==='function';
}
2. 创建可迭代对象:
默认情况下,定义的对象都是不可迭代对象,但如果给Symbol.iterator属性添加一个生成器,就可以将其变为可迭代对象。示例:
let collection={
ES6提供了许多内建迭代器,一般情况下使用这些内建迭代器就可以完成相应任务,一般只有自定义对象和类时才需要自己创建迭代器。
items:[],
*[Symbol.iterator](){
for(let item of this.items){
yield item;
}
}
};
3. 集合对象迭代器:
EC6中有3种类型的集合对象,数组、Map集合与Set集合,这三种对象都内建了三种迭代器。
1)entries()迭代器:
每次调用next()方法时,entries()迭代器都会返回一个数组,数组中的两个元素分别表示集合中每个元素的键和值。如果被遍历对象是数组,则第一个元素是数字类型的索引;如果是Set集合,则第一个元素与第二个元素都是值;如果是Map集合,则第一个元素为键名。
let colors=['red', 'green', 'blue'];
代码中,调用每个集合的entries()方法获取一个迭代器,并使用for-of循环来遍历元素。
for(let entry of colors.entries()){
console.log(entry);
}
2)values()迭代器:
调用values()迭代器时会返回集合中所存的所有值。
let colors=['red', 'green', 'blue'];
调用values()迭代器后,返回的是每个集合中所包含的真正的数据,而不会包含数据在集合中的位置信息。
for(let value of colors.values()){
console.log(value);
}
3)keys()迭代器:
keys()迭代器会返回集合中存在的每一个键。如果遍历的是数组,则会返回数字类型的键;如果是Set集合,键与值相同,keys()和values()返回相同的迭代器;如果是Map()集合,keys()迭代器会返回每一个独立的键。
4)不同集合对象的默认迭代器:
每个集合类型都有一个默认迭代器,在for-of循环中,如果没有显式指定则使用默认迭代器,数组和Set集合的默认迭代器是values()方法,Map()集合的默认迭代器是entries()方法。
let colors=['red', 'green', 'blue'];
for(let value of colors){
console.log(value);
}
4. 字符串迭代器:
ES5中,通过方括号中的索引值可以访问字符串中的字符,如text[0]。但方括号中的索引值是编码单元而非字符,因此无法正确访问非MAP平面中的字符。
ES6中,通过改变字符串的默认迭代器可以解决这个问题,使其操作字符而不是编码单元,用for-of循环输出正确内容。示例:
var message="A ?? B";
for (let c of message){
console.log(c);
}
5. NodeList迭代器:
DOM中有一个NodeList类型,document对象中的所有元素都用这个类型表示。NodeList使用length表示元素的数量,可以通过方括号中的索引值来访问集合中的独立元素,但与数组也有一些不同。
DOM中的NodeList类型也有默认迭代器,所以可以应用于for-of循环:
var divs=document.getElementsByTagName("div");
代码中,通过都用getElementsByTagName()方法获取到document对象中所有<div>元素的列表,在for-of循环中遍历列表中的每一个元素并输出元素ID,实际上是按照处理数组的方式来处理NodeList。
for (let div of divs){
console.log(div.id);
}
6. 展开运算符与非数组可迭代对象:
展开运算符可以作用于任意可迭代对象,因此如果想将可迭代对象转换为数组,这是最简单的方法,既可以将字符串中的每一个字符存入新数组中,也可将NodeList对象中的每一个节点存入新的数组中。示例:
let sNumbers=[1,2,3];
代码中使用展开运算符,讲课迭代对象中的多个元素依次插入新数组中,替换原先展开运算符所在的位置。
let bNumbers=[100,102,102];
let allNumbers=[0, ...sNumbers, ...bNumbers];
7. 高级迭代器功能:
迭代器可用来完成复杂任务。比如给迭代器传递参数,在迭代器中抛出错误,还可以在生成器加入返回语句,并可以将两个迭代器合二为一。
8. 异步任务执行:
生成器的很多特性与异步编程有关,由于生成器支持在函数中暂停代码执行,因而可以深入挖掘异步处理的更多用法。
九、类:
ES5中没有类的概念,有一种类似的构造函数的方式。示例:
function PersonType(name){
代码中PersonType是一个构造函数,然后使用prototype为其添加一个sayName()方法,然后使用new操作符创建一个prototype的示例person。
this.name=name;
}
PersonType.prototype.sayName=function(){
console.log(this.name);
};
var person=new PersonType("Tom");
person.sayName();
1. 类的声明:
要声明一个类,要使用class关键字,并紧跟类名。示例:
class PersonClass{
在类中通过特殊的constructor方法名来定义构造函数。除了constructor外,没有其他保留的方法名。
constructor(name){
this.name=name;
}
sayName(){
console.log(this.name);
}
}
let person=new PersonClass("Tom");
person.sayName();
console.log(typeof PersonClass); // "function"
可见,使用typeof返回的是function,所以使用class声明的类实际上创建了一个具有构造函数行为的函数。类都有一个[[Construct]]的内部方法,类中所有方法都是不能枚举的。
2. 类表达式:
类和函数都有两种形式,一种是声明形式,一种是表达式形式。
let PersonClass=class{
constructor(name){
this.name=name;
}
sayName(){
console.log(this.name);
}
}
let person=new PersonClass("Tom");
person.sayName();
3. 类的继承:
ES5中使用继承比较麻烦,也容易出错。ES6中继承就比较简单。示例:
class Rectangle{
代码中,Square类通过extends关键字继承Rectangle类。继承自其他类的类被称为派生类,如果在派生类中指定了构造函数必须要调用super()。如果选择不使用构造函数,则当创建新的实例时会自动调用super()并传入所有参数。派生类中的方法总会覆盖父类中的同名方法。
constructor(length, width){
this.length=length;
this.width=width;
}
getArea(){
return this.length*this.width;
}
}
class Square extends Rectangle{
constructor(length){
super(length, length);
}
}
var square=new Square(3);
ES6中,只要表达式可以被解析为一个函数,并且具有[[Construct]]属性和原型,那么就可以用extends进行派生。
十、改进的数组功能:
ES6中为数组添加了很多新功能。
1. 创建数组:
ES6新增了Array.of()和Array.from()方法,用于创建数组。
1)Array.of()方法:
如果给Array()构造函数传入一个数值,那么数组的length属性会被设为该值;如果传入多个值,则会变为数组的元素。ES6引入Array.of()方法,无论传入多少参数,也无论什么类型,总会创建一个包含所有参数的数组。示例:
let items=Array.of(1, 2);
2)Array.from()方法:
Array.from()方法可以接受可迭代对象或类数组对象作为第一个参数,最终返回一个数组。
function doSomething(){
代码中,Array.from()方法调用基于arguments对象中的元素创建一个新数组。
var args=Array.from(arguments);
//code
}
如果要进一步转化数组,可以提供一个映射函数作为Array.from()方法的第二个参数,这个函数用来将类数组对象的每一个值转换成其他形式,最后将结果存储在结果数组的相应索引中。示例:
function translate(){
return Array.from(arguments, (value)=>value+1);
}
let numbers=translate(1, 2, 3);
2. 为所有数组添加的新方法:
1)find()方法和findIndex()方法:
这两个方法用于根据某个条件在数组中查找匹配元素。
两个函数都接受两个参数,一个是回调函数,另一个是可选参数,用于指定回调函数中的this的值。执行回调函数时,传入的参数为数组中每个元素和该元素的索引及数组本身。如果回调函数返回true,两个方法就会停止搜索数组的剩余部分。两个函数的差别是,find()方法返回查找到的值,而findIndex()方法返回查找到的索引。示例:
let numbers=[25, 30, 35, 40, 45];
2)fill()方法:
console.log(numbers.find(n=>n>33)); // 35
console.log(numbers.findIndex(n=>n>33)); // 2
用指定的值填充一至多个数组元素。示例:
let numbers=[1, 2, 3, 4];
此方法还有两个可选参数,第二个参数表示开始填充的索引,第三个参数表示填充的结束索引。如果开始索引和结束索引为负值,这些值会与数组的length属性相加作为索引值。
numbers.fill(1);
console.log(numbers.toString()); // 1,1,1,1
3)copyWithin()方法:
用于从数组中复制元素的值,要传入两个参数,一个是开始填充值的索引位置,另一个是开始复制值的索引位置。示例:
let numbers=[1, 2, 3, 4];
默认情况下,copyWithin()方法会一直复制直到数组末尾,可以提供可选的第三个参数来限制重写元素的数量。
numbers.fill(2, 0); //从数组的索引2开始粘贴,从数组的索引0开始复制值
console.log(numbers.toString()); // 1,2,1,2
始复制值的索引位置。示例:
let numbers=[1, 2, 3, 4];
copyWithin()方法也接受负值索引。
numbers.fill(2, 0, 1);
console.log(numbers.toString()); // 1,2,1,4
3. 定型数组:
定型数组是用于处理数值类型数据的专用数组,最早在WebGL中使用,可提供快速的按位运算。
JavaScript中,数字是以64位浮点数格式存储的,并按需转换为32位整数,算术运算很慢。因此,ES6提出定型数组来提供更高性能的算术运算。
定型数组支持存储和操作以下8种不同的数值类型:
· 有符号的8位整数int8
· 无符号的8位整数uint8
· 有符号的16位整数int16
· 无符号的16位整数uint16
· 有符号的32位整数int32
· 无符号的32位整数uint32
· 32位浮点数float32
· 64位浮点数float64
所有与定型数组有关的操作和对象都集中在这8种数据类型上。使用之前需要创建一个数组缓冲区存储这些数据。
1)数组缓冲区:
数组缓冲区是一段可以包含特定数量字节的内存地址,通过ArrayBuffer构造函数来创建:
let buffer=new ArrayBuffer(10); // 分配10个字节
调用构造函数时传入数组缓冲区包含的字节数量,可以通过byteLength属性查看缓冲区中的字节数:
console.log(buffer.byteLength);
也可以通过slice()方法分割已有的数组缓冲区来创建一个新的缓冲区。示例:
let buffer2=buffer.slice(4, 6);
代码中buffer2从索引4和5提取字节。
2)通过视图操作数组缓冲区:
数组缓冲区是内存中的一段地址,视图是操作内存的接口。DataView类型是一种通用的数组缓冲区视图,支持所有8种数值型数据类型。
要使用DataView,首先要创建一个ArrayBuffer实例,然后用这个实例来创建新的DataView:
let buffer=new ArrayBuffer(10);
如果提供一个偏移值,那么可以基于缓冲区的一部分来创建视图,额外提供一个表示选取字节数量的可选参数,则从偏移位置后选取该数量的字节:
view=new DataView(buffer);
view=new DataView(buffer, 5, 2);
通过这个方法,可以基于同一个数组缓冲区创建多个view,因而可以为应用申请一整块独立的内存地址。
可以通过以下几种只读属性来获取视图的信息:
· buffer:视图绑定的数组缓冲区
· byteOffset:DataView构造函数的第二个参数,默认是0
· byteLength:DataView构造函数的第三个参数,默认是缓冲区的长度byteLength
通过这些属性,可以查看视图正在操作缓冲区的哪一部分。
console.log(view.byteOffset);
3)读写数据:
console.log(view.byteLength);
定型数组有8种不同的数据类型,对每一种都有相应的在数组缓冲区中写入数据和读取数据的方法,这些方法名以set和get开头,紧跟是,每一种数据类型的所写:
· getInt8(byteOffset, littleEndian):读取位于byteOffset处的int8类型数据
· setInt8(byteOffset, value, littleEndian):在byteOffset处写入int8类型数据
· getUint8(byteOffset, littleEndian):读取位于byteOffset处的uint8类型数据
· setUint8(byteOffset, value, littleEndian):在byteOffset处写入uint8类型数据
其中的littleEndian是一个布尔值,表示是否按小端格式读写。示例:
view.setInt8(0, 5);
其他定型数组数据类型的读写方法与此类似。视图是独立的,无论数据之前是通过何种方式存储的,都可以在任意时刻读取或写入任意格式的数据。
view.setInt8(1, -1);
4)特定数据类型视图:
ES6规定了特定类型视图:
构造函数名 | 元素字节 | 说明 | 等价C类型 |
Int8Array | 1 | 8位二进制补码有符号整数 | 有符号char类型 |
Uint8Array | 1 | 8位无符号整数 | 无符号char类型 |
Uint8ClampedArray | 1 | 8位无符号整数(强制转换) | 无符号char类型 |
Int16Array | 2 | 16位二进制补码有符号整数 | short类型 |
Uint16Array | 2 | 16位无符号整数 | 无符号short类型 |
Int32Array | 4 | 32位二进制补码有符号整数 | int类型 |
Uint32Array | 4 | 32位无符号整数 | int类型 |
Float32Array | 4 | 32位IEEE浮点数 | float类型 |
Float64Array | 8 | 64位IEEE浮点数 | double类型 |
Uint8ClampedArray与Uint8Array大致相同,差别在于数组缓冲区中的值如果小于0或大于255,Uint8ClampedArray会分别将其转换为0或255。
5)创建特定类型的视图:
可以使用特定类型视图的构造函数来创建定型数组:
let buffer=new ArrayBuffer(10);
也可以传入一个数字,表示分配给数组的元素数量:
view=new Int8Array(buffer);
view2=new Int8Array(buffer, 5, 2);
ints=new Int16Array(2);
创建定型数组还可以传入以下参数,定型数组、可迭代对象、数组、类数组对象。
6)定型数组的方法:
定型数组也有与普通数组等效的方法:
copyWithin() | findIndex() | lastIndexdOf() | slice() | entries() |
forEach() | map() | some() | fill() | indexOf() |
reduce() | sort() | filter() | join() | reduceRight() |
values() | find() | keys() | reverse() |
定型数组也有3中迭代器,分别为entries()、keys()方法、values()方法,也可以把定型数组当作普通数组一样使用展开运算符及for-of循环。
定型数组还有静态的of()和from()方法,运行效果与Array.of()和Array.from()方法相似,返回的是定型数组。示例:
let ints=Int16Array.of(25, 50);
定型数组不是普通数组,不继承自Array,通过Array.isArray()方法检查返回是false。定型数组会始终保持相同的尺寸,给定型数组中不存在的数值索引会被忽略。
floats=Float32Array.from([1.5, 2.5]);
定型数组还有独特的set()和subarray()方法,set()方法将其他数组复制到已有的定型数组,而subarray()方法提取已有定型数组的一部分作为一个新的定型数组。示例:
let ints=new Int16Array(4);
代码中创建了含有4个元素的定型数组Int16Array,先调用set()方法将两个值分别复制到前后两个位置,再次调用并传入偏移量,将另外两个值复制到数组的后两个位置。
ints.set([25, 50]);
ints.set([75, 100], 2);
let ints=new Int16Array([25, 50, 75,100]),
代码中通过定型数组ints创建3个定型数组,其中subints1是ints的克隆;subints2从索引2开始复制数据;subints3则传入起始和结束索引位置。
subints1=ints.subarray(),
subints2=ints.subarray(2),
subints3=ints.subarray(1, 3);
定型数组不能使用普通数组中的concat()、shift()、pop()、splice()、push()、unshift()方法,定型数组的尺寸不能改变,所以改变数组大小的方法都不能使用。concat()将两个数组合并,合并后的数据类型不能确定,所以也不能使用。
十一、Promise与异步编程:
JavaScript引擎是基于单线程事件循环概念构建的,同一时刻只有一个代码块在执行。需要跟踪即将运行的代码,那些代码被放入一个任务队列中,每当一段代码准备执行时,都会被添加到任务队列。每当JavaScript引擎的一段代码结束执行,事件循环会执行队列中的下一个任务,它是JavaScript引擎中的程序,负责监控代码执行并管理任务队列。
1. Promise基础:
Promise相当于异常操作结果的占位符,让函数返回一个Promise。示例:
let promise=readFile("example.txt");
代码中,readFile()不会立即读取文件,函数会先返回一个表示异步读取操作的Promise对象,为了对这个对象的操作完全取决于Promise的生命周期。
1)Promise的生命周期:
每个Promise都会经历一个短暂的生命周期:先是处于pending状态,此时是未处理unsettled的;一旦异步操作执行结束,变为已处理settled的。操作完成后,Promise可能会进入以下两种状态之一:
· Fulfilled:Promise异步操作成功完成
· Rejected:由于程序错误或其他原因,Promise操作未能成功完成
内部属性[[PromiseState]]被用来表示Promise的3种状态,pending、fulfilled和rejected。这个属性不能检测,只有当状态改变时通过then()方法来采取特定的行为。
所有Promise都有then()方法,它接受两个参数,第一个是状态变为fulfilled时调用的函数,与异步操作相关的附近数据都会传递给这个函数;第二个是状态变为rejected时要调用的函数,所有与失败状态相关的附近数据都会传递给这个函数。
then()的两个参数是可选的,可以按照任意组合的方式来监听Promise,执行完成或被拒绝都会被响应。示例:
let promise=readFile("example.txt");
Promise还有一个catch()方法,相当于只传入拒绝处理程序的then()方法。
promise.then(function(contents){
console.log(contents);
}, function(err){
console.error(err.message);
});
2)创建未完成的Promise:
用Promise构造函数可以创建新的Promise,构造函数只接受一个参数,即包含初始化Promise代码的执行器函数。执行器接受两个参数,分别为resolve()和reject()函数,执行器成功完成时调用resolve()函数,反之,失败时调用reject()函数。
let fs=require("fs");
示例中,用Promise包裹了一个原生的Node.js的fs.readFile()异步调用。如果失败,执行器向reject()函数传递错误对象;如果成功,执行器向resolve()函数传递文件内容。
function readFile(filename){
return new Promise(function(resolve, reject){
fs.readFile(filename, {encoding:"utf-8"}, function(err, contents){
if(err){reject(err);return;}
resolve(contents);
});
});
}
let promise=readFile("example.txt");
promise.then(function(contents){
console.log(contents);
}, function(err){
console.error(err.message);
});
readFile()方法被调用时,执行器会立刻执行,而在执行器中,无论是调用reject()还是resolve(),都会向任务队列中添加一个任务来解决这个Promise。
调用resolve()后会触发一个异步操作,传入then()和catch()方法的函数会被添加到任务队列中并异步执行。
3)创建已处理的Promise:
⑴使用Promise.resolve():
此方法只接受一个参数并返回一个完成态的Promise,也就是不会有任务编排过程,而且需要向Promise添加一至多个完成处理程序来获取值。示例:
let promise=Promise.resolve(42);
⑵使用Promise.reject():
promise.then(function(value){
console.log(value);
});
也可以使用Promise.reject()方法来创建已拒绝Promise。
let promise=Promise.reject(42);
5)非Promise的Thenable对象:
promise.catch(function(value){
console.log(value);
});
Promise.resolve()和Promise.reject()方法都可以接受非Promise的Thenable对象作为参数,方法会创建一个新的Promise对象,并在then()函数中被调用。
拥有then()方法并且接受resolve和reject这两个参数的普通对象就是非Promise的Thenable对象。示例:
let thenable={
可以调用Promise.resolve()方法将Thenable对象转换成一个已完成Promise。
then:function(resolve, reject){
resolve(42):
}
};
let p1=Promise.resolve(thenable);
p1.then(function(value){
console.log(value);
});
2. Promise的使用:
1)串联Promise及Promise链的返回值:
2)响应多个Promise:
如果想通过监听多个Promise来决定下一步的操作,可以使用Promise.all()和Promise.race()两个方法。
⑴Promise.all()方法:
Promise.all()方法只接受一个参数并返回一个Promise,该参数是一个含有多个受监视Promise的可迭代对象,如数组,只有当可迭代对象中所有的Promise都被解决后,返回的Promise才会被解决,只有当可迭代对象中所有Promise都被完成后,返回的Promise才会被完成。
⑵Promise.race()方法:
Promise.race()方法监听多个Promise的方法也接受含多个受监视Promise的可迭代对象作为唯一参数并返回一个Promise,但只要有一个Promise被解决,返回的Promise就被解决;一旦数组中的某个Promise完成,Promise.race()方法就返回一个特定的Promise。
3)自Promise继承:
Promise也可以作为基类派生其他类。
4)基于Promise的异步任务执行:
十二、代理和反射API:
代理Proxy是一种可以拦截并改变底层JavaScript引擎操作的包装器,通过它暴露内部运作的对象,从而让开发者可以创建内建的对象。
调用new Proxy()看创建代替其他目标对象的代理,代理可以拦截JavaScript引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数。
反射API以Reflect对象的形式出现,对象中方法的默认特性与相同的底层操作一致,而代理可以覆写这些操作,每个代理陷阱对应一个命名和参数都相同的Reflect方法。
代理陷阱 | 覆写的特性 | 默认特性 |
get | 读取一个属性值 | Reflect.get() |
set | 写入一个属性值 | Reflect.set() |
has | in操作符 | Reflect.has() |
deleteProperty | delete操作 | Reflect.deleteProperty() |
getPrototypeOf | Object.getPrototypeOf() | Reflect.getPrototypeOf() |
setPrototypeOf | Object.setPrototypeOf() | Reflect.setPrototypeOf() |
isExtensible | Object.isExtensible() | Reflect.isExtensible() |
preventExtensions | Object.preventExtensions() | Reflect.preventExtensions() |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() | Reflect.getOwnPropertyDescriptor() |
defineProperty | Object.defineProperty() | Reflect.defineProperty() |
ownKeys | Object.keys() Object.getOwnPropertyNames() Object.getOwnPropertySymbols() |
Reflect.ownKeys() |
apply | 调用一个函数 | Reflect.apply() |
construct | 用new调用一个函数 | Reflect.construct() |
每个陷阱覆写JavaScript对象的一些内建特性,可以用来拦截并修改这些特性。如果仍需使用内建特性,则可以使用相应的反射API方法。
1. 创建一个代理:
用Proxy构造函数创建代理需要传入两个参数,目标targer和处理程序handler。处理程序是定义一个或多个陷阱的对象,在代理中除了专门为操作定义的陷阱外,其余操作均使用默认特性。不使用任何陷阱的处理程序等价于简单的转发代理。示例:
let target={};
没有陷阱,代理只是简单地将操作转发给目标。
let proxy=new Proxy(target, {});
proxy.name="proxy";
target.name="target";
2. 使用set陷阱验证属性:
假设想创建一个属性值是数字的对象,对象中每新增一个属性都要加以验证,如果不是数字就抛出异常。为实现这个认为,可以定义一个set陷阱来覆写设置值的默认特性。set陷阱接受4个参数:
· trapTarget:用于接收属性(代理目标)的对象
· key:要写入的属性值
· value:被写入的属性值
· receiver:操作发生的对象,通常是代理
Reflect.set()是set陷阱对应的反射方法和默认特性,和set代理陷阱一样接受相同的4个参数,以方便在陷阱中使用。如果属性已设置陷阱应该返回true,如果未设置则返回false。
let target={name:"target"};
代码中定义了一个代理来验证添加到target的新属性,当执行proxy.count=1时,set陷阱被调用,此时trapTarget的值等于target,key等于count,value=1,receiver(未使用)等于proxy。由于target上没有count属性,因此代理继续将value值传入isNaN(),如果为true则抛出一个错误;而传入的1,代理调用Reflect.set()方法接受4个参数来添加新属性。
let proxy=new Proxy(target, {
set(trapTarget, key, value, receiver){
if(!trapTarget.hasOwnProperty(key)){
if(isNaN(value)){
throw new TypeError("attribute must be number");
}
}
return Reflect.set(trapTarget, key, value, receiver);
}
});
proxy.count=1;
proxy.name="proxy";
target已经拥有一个name属性,通过调用trapTarget.hasOwnProperty()方法验证检查后被排除了,所以可以赋予字符串值。
set代理陷阱可以拦截写入属性的操作,而get代理陷阱可以拦截读取属性的操作,还可以使用has陷阱隐藏已有属性,利用deleteProperty陷阱防止删除属性,如此等等。
十三、用模块封装代码:
ES6之前,在应用程序的每一个JavaScript中定义的一切都共享一个全局作用域。随着Web应用程序变得更加复杂,这种做法会引起问题,如命名冲突和安全问题。ES6的一个目标是解决作用域问题,引入了模块。
1. 模块:
模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。在模块顶部创建的变量不会自动被添加到全局共享作用域,这个变量仅在模块的顶部作用域存在,而且模块必须导出一些外部代码可以访问的元素,如变量和函数。模块也可以从其他模块导入绑定。
脚本,也就是不是模块的JavaScript代码。
1)导出的基本语法:
可以用export关键字将一部分已发布的代码暴露给其他模块。可以将export放在任何变量、函数或类声明之前,以将它们从模块导出。示例:
export var color='red';
也可以先定义一个变量、函数或类,然后使用export导出。任何未显式导出的变量、函数或类都是模块私有的,无法从模块外部访问。
export function sum(num1, num2){
return num1+num2;
}
2)导入的基本语法:
从模块中导出的功能可以通过import关键字在另一个模块中访问,import语句包括两部分,分别为导入的标识符和标识符导入的模块。示例:
import { identifier1, identifier2} from "./example.js";
import后的大括号表示从给定模块导入的绑定,from关键字后表示导入的模块,该模块由表示模块路径的字符串指定。
为了兼容多个浏览器和Node.js环境,一定要在字符串之前包含/、./或../来表示路径。特殊情况下,可以导入整个模块作为单一的对象,所有导出都可以作为对象使用:
import * as example from "./example.js";
代码中,从example.js中导出的所有绑定被加载到一个称为example的对象中,称为命名空间导入。使用时,在相应变量、函数、类前加上exemple.前缀。
不管在import语句中把一个模块写了多少次,该模块只会执行一次。导入模块的代码执行后,保存在内存中,只要另一个import语句引用就可以重复使用。示例:
import { sum} from "./example.js";
虽然有2个import语句,但example.js只会执行一次。
import { multiply} from "./example.js";
export和import语句只能在其他语句和函数之外使用,不能有条件方式执行。
3)导出和导入时重命名:
从一个模块导入变量、函数或类时,可能不希望使用它们的原始名称,可以使用as关键字来指定在模块外的名称。示例:
export {sum as add};
如果模块想使用不同的名称来导入,也可以使用as关键字。示例:
import {add} from "./example.js";
import {sum as add} from "./example.js";
4)模块的默认值:
模块的默认值指的是通过default关键字指定的单个变量、函数或类,只能为每个模块设置一个默认的导出值。示例:
export default function(num1, num2){
这个模块导出了一个函数作为它的默认值,default关键字表示这是一个默认的导出。也可以在export default之后添加默认导出值的标识符。示例:
return num1+num2;
}
function(num1, num2){
也可以为默认导出值使用重命名语法:
return num1+num2;
}
export default sum;
export {sum as default};
导入默认值:
import sum from "./example.js";
这里sum是一个本地名称,用于表示模块导出的任何默认函数,没有使用大括号。
模块导出时可以与默认值一同导出。示例:
export let color='red';
这时可以使用以下语句导入:
export default function(num1, num2){
return num1+num2;
}
import sum,{color} from "./example.js";
默认值必须排在非默认值之前,将默认的本地名称与大括号包裹的非默认值分隔开。也可以使用重命名语法,示例:
import {default as sum, color} from "./example.js";
5)导出已经导入的内容:
如果使用几个小模块创建一个库,可能需要将导入的内容再向外导出。这时可以使用一条语句就能完成。示例:
export {sum} from "./example.js";
也可以重命名后导出:
export {sum as add} from "./example.js";
如果想导出另一个模块的所有值,可以使用*方式:
export * from "./example.js";
6)无绑定导入:
内建对象,如Array、Object的共享定义可以在模块中访问,对这些对象所做的修改将反映在其他模块中。所以,这里操作的模块可能不导出任何内容,但却可以导入并被使用。示例:
Array.prototype.pushAll=function(items){
这段代码向所有数组添加pushAll()方法,其中并没有任何导出或导入,但也是一个有些的模块,当然也可以用作脚本。对这类模块,导入使用时可以简化:
if(!Array.isArray(items)){
throw new TypeError("must be a array.");
}
return this.push(...items);
};
import "./example.js";
导入了数组的pushAll()方法后,所有数组都可以使用这些方法了。这些方式可能应用于创建Polyfill和Shim。
2. 加载模块:
ES6定义了模块的语法,但并没有定义如何加载这些模块,这由不同的实现环境来决定。
浏览器中<script>元素有个type属性,当为脚本时使用text/javascript类型,而如果加载模块则需改为type="module",无论是内联代码还是加载文件,都作为模块来执行。
Web浏览器中加载的模块,按照它们出现在HTML文件中的顺序执行,无论内联代码还是src属性指定的代码文件,都按出现的顺序来执行。
每个模块都可以从一个或多个其他模块导入,因此要首先解析模块以识别所有导入语句,然后每个导入语句都触点一次获取过程,从网络或缓存,并且在所有导入资源都被加载和执行后才会执行当前模块。
将模块作为Worker加载时,要添加第二个参数:
let worker=new Worker("module.js", {type: "module"});
十四、ES6中其他的一些改动:
1. 识别整数:
ES6添加了Number.isInteger()方法来确定一个值是否为JavaScript整数类型。在JavaScript中,只给数字添加小数点并不会让整数变为浮点数,25.0仍然是整数。
JavaScript使用IEEE754编码系统来表示整数和浮点数,IEEE754只能准确表示-2**53~2**53之间的整数,即-9007199254740992~9007199254740992,范围之外则通过重用二进制来表示多个数值。ES6引入了Number.isSafeInteger()方法来识别可以准确表示的整数,并添加了Number.MAX_SAFE_INTEGER属性和Number.MIN_SAFE_INTEGER属性来分别表示整数范围的上限和下限。
2. 新的Math方法:
ES6为Math对象添加了几种方法,以提高通常的数学计算的速度,同时可以提高密集计算应用程序的总体速度。这些新方法包括:
方法 | 说明 |
Math.acosh(x) | x的反双曲余弦 |
Math.asinh(x) | x的反双曲正弦 |
Math.atanh(x) | x的反双曲正切 |
Math.cosh(x) | x的双曲余弦 |
Math.sinh(x) | x的双曲正弦 |
Math.tanh(x) | x的双曲正切 |
Math.cbrt(x) | x的立方根 |
Math.clz32(x) | x的32位整数表示中的前导零位数 |
Math.expm1(x) | 从x的指数函数中减去1的结果 |
Math.fround(x) | 与x最接近的单精度浮点数 |
Math.hypot(...values) | 每个参数的平方和的平方根 |
Math.imul(x, y) | 执行两个参数的32位有符号乘法的结果 |
Math.log1p(x) | 1+x的自然对数 |
Math.log2(x) | 以2为底的x的对数 |
Math.log10(x) | 以10为底的x的对数 |
Math.sign(x) | 如果x为负则为-1,如果为0则为0,如果为正则为1 |
Math.trunc(x) | 一个整数。从浮点数中删除小数位数 |
3. Unicode标识符:
ES5中可以将Unicode转义序列用作标识符,如:var \u0061="abc";
ES6中,还可以使用Unicode码位转义序列作为标识符,如:var \u{61}="abc";
ES6通过Unicode31 号标准附录“Unicode标识符和模式语法”正式指定了有效的标识符,包括以下规则:
· 第一个字符必须通过$、_或任何带有ID_Start的派生核心属性的Unicode符号
· 后续的每个字符必须是$、_、\u200c(zero-width non-joiner)、\u200d(zero-width joiner)或具有ID_Continue的派生核心属性的任何Unicode符号
ID_Start和ID_Continue派生核心属性是在“Unicode标识符和模式语法”中定义的,用于标识适用于标识符的符号。此规范不是JavaScript特有的。
4. __proto__属性:
__proto__属性可用于获取和设置[[Prototype]]属性,实际上是Object.getPrototypeOf()方法和Object.setPrototypeOf()方法的早期实现。ES6正式添加了__proto__属性,但建议避免使用。
十五、ECMAScript 2016:
TC-39决议将ECMAScript的发布周期改为每年一版,也意味着每个新版本具有较少的新功能。新版本不再显著标明版本号,而是参考发布规范的年份。ECMAScript7正式称为ECMAScript 2016,与2016年3月完成,仅添加了三种新特性。
1. 指数运算符:
ES7引入求幂运算符**,已有的Math.pow()仍可以使用。示例:let result=5**2;
求幂运算具有JavaScript中所有二元运算符的优先级,如:
let result=2*5**2;
会先计算5的二次幂,然后乘以2.
求幂运算符有一些使用限制,它左侧的一元表达式只能使用++或- -。如果使用:
let result=-5**2;
这时运算顺序是不明确的,必须使用括号明确运算顺序:
let result=(-5)**2;
而求幂运算符左侧无须括号可以使用++和- -,前缀++或- -会在其他所有操作发生之前更改操作数,而后缀使用时直到整个表达式被计算过后才会进行改变。示例:
let result=-(5**2);
b=++num1**2
使用前缀时,先对num1加1,然后求幂;使用后缀时,先对num1求幂,然后num1减1。
c=num1++**2
2. Array.prototype.includes()方法:
此方法用于检查给定字符串中是否存在某字符串,它接受两个参数,要搜索的值及开始的索引位置,第二个参数是可选的。示例:
let values=[1,2,3];
如果数组中存在NaN值,搜索时:
console.log(values.includes(1)); // true
console.log(values.includes(1, 2)); // false
let values=[1,NaN,3];
因为indexOf()和includes()使用不同的比较方式,所以获得的结果不同。在对+0和-0的比较方面,二者表示是一样的,都认为是相等的:
console.log(values.indexOf(NaN)); // -1
console.log(values.includes(NaN)); // true
let values=[1,+0,3];
console.log(values.indexOf(-0)); // 1
console.log(values.includes(-0)); // true
3. 函数作用域严格模式的一处改动:
ES7中,只有参数为不包含解构或默认值的简单参数列表时,才可以在函数体中使用"use strict"。
十六、ECMAScript之后版本的改动:
1. ECMAScript 2017版本:
1)字符串填充:
ECMAScript 2017 添加了两个 String 方法:padStart 和 padEnd,以支持在字符串的开头和结尾进行填充。
①padStart
let str = "5";
②padEnd
str = str.padStart(4,0); // 结果是: 0005
let str = "5";
2)Object.entries()和Object.values()方法:
str = str.padEnd(4,0); // 结果是: 5000
ECMAScript 2017 向对象添加了新的 Object.entries 方法和Object.values方法。
①Object.entries() 方法返回对象中键/值对的数组。示例:
const person = {
Object.entries() 使循环中使用对象变简单了。示例:
firstName : "Bill",
lastName : "Gates",
age : 50,
eyeColor : "blue"
};
document.getElementById("demo").innerHTML = Object.entries(person);
const fruits = {Bananas:300, Oranges:200, Apples:500};
Object.entries() 也使得将对象转换为映射变得简单。示例:
let text = "";
for (let [fruit, value] of Object.entries(fruits)) {
text += fruit + ": " + value + "";
}const fruits = {Bananas:300, Oranges:200, Apples:500};
②Object.values返回对象值的单维数组。示例
const myMap = new Map(Object.entries(fruits));
const person = {
firstName : "Bill",
3)Async 函数:
lastName : "Gates",
age : 50,
eyeColor : "blue"
};
document.getElementById("demo").innerHTML = Object.values(person);
等待超时。示例:
async function myDisplay() {
4)尾部逗号:
let myPromise = new Promise(function(myResolve, myReject) {
setTimeout(function() { myResolve("I love You !!"); }, 3000);
});
document.getElementById("demo").innerHTML = await myPromise;
}
myDisplay();
在数组及对象迭代、函数调用、参数、输入/输出模块这些以逗号分开的值序列时,JavaScript允许尾部逗号。
function myFunc(x,,,) {};
5)Object.getOwnPropertyDescriptors:
const myArr = [1,2,3,4,,,];
const myObj = {fname: John, age:50,,,};
静态方法返回一个对象,该对象描述给定对象上特定属性(即直接存在于对象上而不在对象的原型链中的属性)的配置。 返回的对象是可变的,但对其进行更改不会影响原始属性的配置。
2. ECMAScript 2018版本:
1)异步迭代:
ECMAScript 2018 添加了异步迭代器和可迭代对象。通过异步迭代,我们可以在 for/of 循环中使用 await 关键字。
for await () {}
2)Promise.finally:
ECMAScript 2018 使用 Promise.finally 完成了 Promise 对象的完整实现。示例:
let myPromise = new Promise();
3)对象 Rest 属性:
myPromise.then();
myPromise.catch();
myPromise.finally();
ECMAScript 2018 添加了 Rest 属性,允许我们破坏一个对象并将剩余物收集到一个新对象上。示例:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
4)新的RegExp特性:
x; // 1
y; // 2
z; // { a: 3, b: 4 }
ECMAScript 2018 添加了 4 个新的 RegExp 特性:
· Unicode 属性转义 (\p{...})
· 后行断言(Lookbehind Assertions) (?<= ) 和 (?<! )
· 命名捕获组(Named Capture Groups)
· s (dotAll) 标志
5)线程Threads:
JavaScript中使用Web Workers API时可以创建线程threads,Worker threads在后台运行以便主程序可以继续运行。Worker threads与主程序同时运行,这样可以节省时间。
6)共享内存:
共享内存性能允许线程接收相同内存中的升级数据。为了替代不同线程之间的数据传递,可以用传递SharedArrayBuffer对象的方式,它指向保存数据的内存。
SharedArrayBuffer对象一种固定长度的二进制原始数据缓存,与ArrayBuffer对象相同。
3. ECMAScript 2019版本:
1)新增String方法trimStart() 和 trimEnd():
ECMAScript 2019 添加了两个 String 方法:trimStart() 和 trimEnd(),用于删除字符串中的头尾空格。
①trimStart()
trimStart() 方法的工作方式与 trim() 类似,但仅从字符串的开头删除空格。示例:
let text1 = " Hello World! ";
②trimEnd()
let text2 = text1.trimStart();
trimEnd() 仅从字符串末尾删除空格。
let text1 = " Hello World! ";
2)对象方法 fromEntries():
let text2 = text1.trimEnd();
ES2019 为 JavaScript 添加了 fromEntries() 对象方法,用于从可迭代的键/值对创建对象。示例:
const fruits = [
3)可选的 catch 绑定:
["apples", 300],
["pears", 900],
["bananas", 500]
];
const myObj = Object.fromEntries(fruits);
从 ES2019 开始,如果不需要,可以省略 catch 参数。示例:
try {
4)新增数组方法 flat() 和flatMap():
// code
} catch {
// code
}
①flat()方法
flat() 方法通过展平嵌套数组来创建新数组。示例:
const myArr = [[1,2],[3,4],[5,6]];
②flatMap()
const newArr = myArr.flat();
flatMap() 方法首先映射数组的所有元素,然后通过展平数组来创建新数组。示例:
const myArr = [1, 2, 3, 4, 5, 6];
5)稳定的数组方法 sort():
const newArr = myArr.flatMap((x) => x * 2);
在 2019 年之前,规范允许不稳定的排序算法,例如 QuickSort;在 ES2019 之后,浏览器必须使用稳定的排序算法,也即当根据一个值对元素进行排序时,这些元素必须保持它们与具有相同值的其他元素的相对位置。示例:
const myArr = [
6)重新修订的 JSON.stringify()方法:
{name:"X00",price:100 },
{name:"X01",price:100 },
{name:"X02",price:100 },
{name:"X03",price:100 },
{name:"X04",price:110 },
{name:"X05",price:110 },
{name:"X06",price:110 },
{name:"X07",price:110 }
];
在 ES2019 之前,在 UTF-8 代码点(U+D800 到 U+DFFF)上使用 JSON.stringify() 会返回损坏的 Unicode 字符。此次修订后,具有 UTF-8 代码点的字符串可以使用 JSON.stringify() 安全地转换,并使用 JSON.parse() 恢复为原始字符串。
7)新增分隔符号:
字符串文字中现在允许使用行分隔符和段落分隔符(\u2028 和 \u2029)。在 2019 年之前,这些被视为行终止符并导致错误异常。示例:
let text = "\u2028";
8)修订Function toString():
在 2019 年之前,Function toString()在不同的浏览器返回了不同的函数变体(比如没有注释和空格)。从 2019 年开始,toString() 必须返回函数的源代码,包括注释、空格和语法细节,完全按照编写的方式返回。
4. ECMAScript 2020版本:
1)BigInt:
BigInt 变量用于存储太大而无法用普通 JavaScript 数字表示的大整数值。JavaScript 整数最多只能精确到 15 位数字,如需创建 BigInt,请将 n 附加到整数末尾或调用 BigInt()。示例:
let x = 1234567890123456789012345n;
BigInt 的 JavaScript 类型是 "bigint"。示例:
let y = BigInt(1234567890123456789012345);
let x = BigInt(999999999999999);
2)新增字符串方法 matchAll():
let type = typeof x;
在 ES2020 之前,没有字符串方法方法可用于搜索字符串中所有出现的字符串。
const iterator = text.matchAll("Cats");
如果参数是正则表达式,则必须设置全局标志 (g),否则会抛出 TypeError。
const iterator = text.matchAll(/Cats/g);
如果要进行不区分大小写的搜索,则必须设置不区分大小写标志 (i):
const iterator = text.matchAll(/Cats/gi);
3)新增运算符:
①空值合并运算符??
如果第一个参数不是空值(null 或 undefined),则 ?? 运算符返回第一个参数,否则返回第二个。示例:
let name = null;
②可选链运算符?.
let text = "missing";
let result = name ?? text;
如果对象为 undefined 或 null,则可选链运算符返回 undefined(而不是抛出错误)。
const car = {type:"Fiat", model:"500", color:"white"};
③逻辑 AND 赋值运算符&&=
let name = car?.name;
逻辑 AND 赋值运算符用于两个值之间,如果第一个值为 true,则分配第二个值。示例:
let x = 100;
④逻辑 OR 赋值运算符||=
x &&= 5;
逻辑 OR 赋值运算符用于两个值之间,如果第一个值为 false,则分配第二个值。示例:
let x = 10;
⑤空值合并赋值运算符??=
x ||= 5;
空值合并赋值运算符用于两个值之间,如果第一个值 undefined 或为 null,则分配第二个值。示例:
let x = 10;
4)Promise.allSettled():
x ??= 5;
Promise.allSettled()方法,用于从一个promise列表中返回一个promise。示例:
// Create a Promise
5)动态import:
const myPromise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 200, "King");
});
// Create another Promise
const myPromise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "Queen");
});
// Settle All
Promise.allSettled([myPromise1, myPromise2]).then((results) =>
results.forEach((x) => myDisplay(x.status)),
);
5. ECMAScript 2021版本:
1)新增字符串方法 replaceAll():
示例:
text = text.replaceAll("Cats","Dogs");
replaceAll() 方法允许指定一个正则表达式而不是要替换的字符串,如果参数是正则表达式,则必须设置全局标志 (g),否则会抛出 TypeError。
text = text.replaceAll("cats","dogs");
text = text.replaceAll(/Cats/g,"Dogs");
2)新增数字分隔符 (_):
text = text.replaceAll(/cats/g,"dogs");
ES2021 引入了数字分隔符 (_) 以使数字更具可读性,示例:
const num = 1_000_000_000;
数字分隔符仅供视觉使用。示例:
const num1 = 1_000_000_000;
数字分隔符可以放在数字中的任何位置,示例:
const num2 = 1000000000;
(num1 === num2);
const num1 = 1_2_3_4_5;
数字分隔符不允许出现在数字的开头或结尾。在 JavaScript 中,只有变量可以以 _ 开头。
3)新增Promise.any():
示例:
// Create a Promise
const myPromise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 200, "King");
});
// Create another Promise
const myPromise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "Queen");
});
// Run when any promise fulfill
Promise.any([myPromise1, myPromise2]).then((x) => {
myDisplay(x);
});
6. ECMAScript 2022版本:
1)新增数组方法 at():
at()方法返回索引值对应的数组元素,类似于[]。示例:
const fruits = ["Banana", "Orange", "Apple", "Mango"];
很多编程语言允许使用负值索引,表示从后向前的顺序,但JavaScript中可能会有问题,因此提出了 at()方法。
let fruit = fruits.at(2); //与fruits[2]结果相同
2)新增String方法 at():
at()方法返回字符串对应的字符元素,类似于[]。示例:
const name = "W3Schools";
3)RegExp的d修饰符:
let letter = name.at(2); //与name[2]结果相同
ES2022 新增了/d 修饰符表示对起始和结束的匹配。示例:
let text = "aaaabb";
JavaScript中的一些修饰符的作用:
let result = text.match(/(aa)(bb)/d);
i 执行大小写无关的匹配
g 发现所有的匹配
m 执行多行匹配
d 执行字串匹配
4)Object.hasOwn():
ES2022提供了一种安全方式检查一个属性是否是对象的属性,类似于Object.prototype.hasOwnProperty,但支持所有的对象类型。示例:
Object.hasOwn("initProp")
5)error.cause:
在ES2022中,通过error.cause可以了解导致错误发生的原因。示例:
try { connectData(); } catch (err) { throw new Error("Connecting failed.", { cause: err }); }
6)await import:
通过这种方式,JavaScript模块可以等待要求的资源输入后才开始运行。示例:
import {myData} from './myData.js';
7)类字段声明:
const data = await myData();
示例:
class Hello {
8)私有方法和字段:
counter = 0; // Class field
}
const myClass = new Hello();
let x = myClass.counter;
示例:
class Hello {
#counter = 0; // Private field
#myMethod() {} // Private method
}
const myClass = new Hello();
let x = myClass.#counter; // Error
myClass.#myMethod(); // Error
7. ECMAScript 2023版本:
1)新增数组方法:
①findLast()方法
ES2023新增了findLast()方法,从数组尾端开始搜索符合条件的元素并返回其值。示例:
const temp = [27, 28, 30, 40, 42, 35, 30];
②findLastIndex()方法
let high = temp.findLast(x => x > 40);
findLastIndex()方法从尾端搜索符合条件的元素并返回其索引。示例:
const temp = [27, 28, 30, 40, 42, 35, 30];
③toReversed()方法
let pos = temp.findLastIndex(x => x > 40);
ES2023新增了数组的toReversed()方法,用安全的方式反转一个数组,但不会改变原数组。示例:
const months = ["Jan", "Feb", "Mar", "Apr"];
toReversed()方法与之前存在的reverse()方法不同,它会创建一个新的数组,而不会改变原数组,而原有的方法是在原数组上反转顺序。
const reversed = months.toReversed();
④toSorted()方法:
ES2023新增数组的toSorted()方法,用安全的方式对数组排序,而不会改变原数组。示例:
const months = ["Jan", "Feb", "Mar", "Apr"];
新增的toSorted()方法与原有的sort()方法不同,它会创建一个新的数组,而不会改变原数组,而原有的方法是在原数组上排序。
const sorted = months.toSorted();
⑤toSpliced()方法:
ES2023新增了toSpliced()方法,以安全的方式截取数组的一段,而不会改变原数组。
const months = ["Jan", "Feb", "Mar", "Apr"];
新增的toSpliced()方法与原有的splice()方法不同,它会创建一个新的数组,而不会改变原数组,而原有的方法是在原数组上截取片段。
const spliced = months.toSpliced(0, 1);
⑥with()方法:
ES2023新增数组的with()方法,以安全的方式升级元素,而不会改变原数组。示例:
const months = ["Januar", "Februar", "Mar", "April"];
2)字符串组合#!
const new = months.with(2, "March");
这个字符组合是由数字符号和感叹号组成,用于一个脚本的开头。示例:
#!/usr/bin/env node
上面的示例代码,告知操作系统使用node程序运行这个脚本。现在,你就可以运行./fileName.js的JavaScript代码,用来替代node fileName.js。
字符组合#!有很多名称,如Shebang、sharp-exclamation、hashbang、pound-bang、hash-pling等。
8. ECMAScript 2024版本:
1)groupBy():
①Object.groupBy()
Object.groupBy()方法根据回调函数返回的字符串值进行对象元素分组,但并不会改变原对象。示例:
// Create an Array
②Map.groupBy()
const fruits = [
{name:"apples", quantity:300},
{name:"bananas", quantity:500},
{name:"oranges", quantity:200},
{name:"kiwi", quantity:150}
];
// Callback function to Group Elements
function myCallback({ quantity }) {
return quantity > 200 ? "ok" : "low";
}
// Group by Quantity
const result = Object.groupBy(fruits, myCallback);
Map.groupBy()方法根据回调函数返回的字符串值进行对象元素分组,但并不会改变原对象。示例:
// Create an Array
Object.groupBy()和Map.groupBy()的区别在于,Object.groupBy()分组的元素进入JavaScript对象,而Map.groupBy()分组的元素则进入Map对象。
const fruits = [
{name:"apples", quantity:300},
{name:"bananas", quantity:500},
{name:"oranges", quantity:200},
{name:"kiwi", quantity:150}
];
// Callback function to Group Elements
function myCallback({ quantity }) {
return quantity > 200 ? "ok" : "low";
}
// Group by Quantity
const result = Map.groupBy(fruits, myCallback);
2)Temporal:
①Temporal.PlainDate()
示例:
const date = Temporal.PlainDate(2024, 5, 1);
②Temporal.PlainTime()
示例:
const date = new Temporal.PlainTime(10, 30);
③Temporal.PlainMonthDay()
示例:
const date = new Temporal.PlainMonthDay(5, 1);
④Temporal.YearMonth()
示例:
const date = new Temporal.PlainYearMonth(2024, 5);