ES6新语法
前言:
ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript的历史
ES6 从开始制定到最后发布,整整用了 15 年。
ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。
2000 年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是** 2000 年**。
为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。
2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。
2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。
2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是 ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在 2013 年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。
2011 年 6 月,ECMAscript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。
2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。
2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。
等待了多年,总算等到了ES6,下面介绍一部分的新语法,本文介绍的语法如下:
1. 块级作用域
由于在ES5之前,没有块级作用域,有些时候很不方便,所以ES6新增了块级作用域。
块级声明是指该声明的变量无法被代码块外部访问。块作用域,又被称为词法作用域(lexical scopes),可以在如下的条件下创建:
- 在函数内部定义
- 在代码块(即 {} )内部定义
块级作用域是很多类C语言的工作机制,ECMAScript 6 引入块级声明的目的是增强 JavaScript 的灵活性,同时又能与其它编程语言保持一致.
1.1 Let声明
let定义块级作用域变量
1.没有变量的提升,必须先声明后使用.
2.let声明的变量,不能与前面的let,var,conset声明的变量重名
//全局执行上下文 { //局部执行上下文 //console.log(a)//此处浏览器报错未声明 let a = 10;//let定义只能在当前上下文使用 var b = "abc";//全局作用域变量 console.log(a);//10 //let a = 10//浏览器提示错误 Uncaught SyntaxError: Identifier 'a' has already been declared console.log(b);//bac } console.log(b);//abc // console.log(a);//浏览器提示错误复制代码
1.2 Const声明
const 定义只读变量
1.const声明变量的同时必须赋值,const声明的变量必须初始化,一旦初始化完毕就不允许修改
2.const声明变量也是一个块级作用域变量
3.const声明的变量没有“变量的提升”,必须先声明后使用
4.const声明的变量不能与前面的let,var,const声明的变量重名
{ const con = "abc" console.log(con)//也只能在当前执行上下文中使用}//console.log(con)//con is not defined 浏览器提示错误,con未声明复制代码
1.3 循环中的块级绑定
使用var声明的循环变量在循环结束后仍然可以访问到。 使用let声明的循环变量,在循环结束之后会立即销毁。
for(let i = 0; i < 3; i++){ // 循环结束之后会立即销毁 i console.log(i); } console.log(i); //此处无法访问到 i 。复制代码
1.4 循环中的函数
看下面的代码,是输出10个10,而不是0,1,2,…
var funcs = []; for (var i = 0; i < 10; i++) { funcs.push(function () { console.log(i); }); } funcs.forEach(function (func) { func(); // 输出 "10" 共10次 });复制代码
解决办法需要使用函数的自执行特性。
var funcs = [];for (var i = 0; i < 10; i++) { funcs.push((function(value) { return function() { console.log(value); } }(i)));}funcs.forEach(function(func) { func(); // 输出 0,1,2 ... 9});复制代码
如果使用let声明变量,则完全可以避免前面的问题。 这是ES6规范中专门定义的特性。在for … in和for … of循环中也适用
var funcs = []; for (let i = 0; i < 10; i++) { funcs.push(function () { console.log(i); }); } funcs.forEach(function (func) { func(); // 输出 0,1,2 ... 9 })复制代码
说明:let 声明使得每次迭代都会创建一个变量 i,所以循环内部创建的函数会获得各自的变量 i 的拷贝。每份拷贝都会在每次迭代的开始被创建并被赋值。
2.神奇的解构赋值
2.1 什么是解构
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。解构只能用于数组或对象,所以应该注意,其他原始类型的值都可以转为相应的对象 ,除了undefined和null本质上。
解构写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
2.2 对象的解构
2.2.1 对象解构的基本形式
对象结构的语法就是在赋值语句的左侧使用类似对象字面量的结构。
let node = { name: "阿猫", age:20};//这里就相当于声明了两个变量: type = node.type; name:node.namelet { name,age } = node;console.log(name); // "阿猫"console.log(age); // 20复制代码
在上面的结构中必须要初始化。否则会出现语法错误。
var { name,age };// 语法错误!let { name,age };// 语法错误!const { name,age };// 语法错误!复制代码
2.2.2 解构赋值表达式
如果声明的变量想改变他们的值,也可以使用解构表达式。
let node = { name: "阿猫", age:20},name = "阿狗",age = 5;//注意:此处必须要在圆括号内才能使用解构表达式({ name,age } = node);console.log(name); // "阿猫""console.log(age); // 20复制代码
2.2.3 对象解构时的默认值
如果赋值号右边的对象中没有与左边变量同名的属性,则左边的变量会是 undefined
let node = { name: "阿猫", age:20};//因为node中没有叫value的属性,所以valued的值将会是undefinedlet { name, age, value } = node;console.log(name); // "阿猫"console.log(age); // 20console.log(value); // undefined复制代码
不过我们也可以手动指定他的默认值。(这个和函数的参数默认值很像)
let node = { name: "阿猫", age:20};//手动添加value的默认值为3let { name, age, value = 3} = node;console.log(name); // "阿猫"console.log(age); // 20console.log(value); // 3复制代码
2.2.4 赋值给不同的变量名
在前面的操作中,都是把对象的属性值,赋值给同名变量。
其实也可以赋值给不同名的变量。
let node = { name: "阿猫", age:20};// localName才是要定义的新的变量。 name是node的属性let { name: localName, age: localAge} = node;console.log(localName); // "阿猫"console.log(localAge); // 20复制代码
注意:冒号后面才是要定义的新的变量,这个可以我们的对象字面量不太一样!
这个地方也可以使用默认值。
let node = { type: "Identifier" };let { type: localType, name: localName = "bar" } = node;console.log(localType); // "Identifier"console.log(localName); // "bar"复制代码
2.3 数组的解构
2.3.1 数组解构基本语法
数据解构的语法和对象解构看起来类似,只是将对象字面量替换成了数组字面量,而且解构操作的是数组内部的位置(索引)而不是对象中的命名属性,例如:
let colors = [ "red", "green", "blue" ];let [ firstColor, secondColor ] = colors;console.log(firstColor); // "red"console.log(secondColor); // "green"如果只想取数组中的某一项,则可以不用命名。let colors = [ "red", "green", "blue" ];//只取数组中的第三项。let [ , , thirdColor ] = colors;console.log(thirdColor); // "blue"复制代码
2.3.2 解构表达式
你可以想要赋值的情况下使用数组的解构赋值表达式,但是和对象解构不同,没必要将它们包含在圆括号中,例如:
let colors = [ "red", "green", "blue" ], firstColor = "black", secondColor = "purple";[ firstColor, secondColor ] = colors; //可以不用加括号。当然添加也不犯法console.log(firstColor); // "red"console.log(secondColor); // "green"复制代码
数组解构表达式有一个很常用的地方,就是交换两个变量的值。在以前一般定义一个第三方变量进行交换,例如下面的代码:
let a = 3, b = 4, temp;temp = a;a = b;b = temp;console.log(a);console.log(b)复制代码
那么在ES6中完全可以抛弃第三方变量这种方式,使用我们的数组解构表达式
let a = 3, b = 4;//左侧和前面的案例是一样的,右侧是一个新创建的数组字面量。[a, b] = [b, a];console.log(a);console.log(b)复制代码
2.4 解构JSON
var jike = { "name":"tom","age":"23","sex":"男"};var {name,age,sex}=jike;console.log(name,age,sex)//tom 23 男function cal(a,b){ var ret1 = a+b; var ret2 = a-b; var ret3 = a*b; var ret4 = a/b; return [ret1,ret2,ret3,ret4]}var [r1,r2,r3,r4] = cal(10,5);console.log(r1,r2,r3,r4)//15 5 50 2复制代码
3. 字符串功能的增强
3.1 查找子字符串
- includes(要查找的文本) 判断字符串内是否有此文本
- startsWith(要查找的文本) 判断文本是否在字符串开头
- endsWith(要查找的文本) 判断文本是否在字符串结尾
使用如下:
var msg = "Hello world!";console.log(msg.startsWith("Hello")); // trueconsole.log(msg.startsWith("o")); // falseconsole.log(msg.startsWith("o", 4)); // trueconsole.log(msg.endsWith("!")); // trueconsole.log(msg.endsWith("world!")); // trueconsole.log(msg.endsWith("r", 9)); // trueconsole.log(msg.includes("o")); // trueconsole.log(msg.includes("x")); // falseconsole.log(msg.includes("o", 8)); // false复制代码
- trim():除去字符串左右空格的。
- trimLeft():除去字符串的左边空格
- rimRight():除去字符串的右边空
var msg = " Hello "console.log(msg)//" Hello "console.log(msg.trim())//"Hello"console.log(msg.trimLeft())//"Hello "console.log(msg.trimRight())//" Hello"复制代码
一般配合正则表达式,用的比较的。
- repeat(复制的次数):复制字符串
var msg = "abc"console.log(msg.repeat(3));//abcabcabc复制代码
- padStart(补齐的位数,填充的的字符):用提供的字符,向前补齐位数
var msg = "abc"console.log(msg.padStart(10,"*"));//*******abc复制代码
- padEnd(补齐的位数,填充的的字符):用提供的字符,向前补齐位数
var msg = "abc"console.log(msg.padEnd(10,"*"));//abc*******复制代码
3.2 模板字符串
模板字面量是 ECMAScript 6 针对 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的回应:
多行字符串 针对多行字符串的形式概念(formal concept)。 基本的字符串格式化 将字符串中的变量置换为值的能力。 转义 HTML 能将字符串进行转义并使其安全地插入到 HTML 的能力。 模板字面量以一种全新的表现形式解决了这些问题而不需要向 JavaScript 已有的字符串添加额外的功能。
3.2.1 基本语法
使用一对反引号 “(tab正上方的按键)来表示模板字面量。
let message = `Hello world!`; //使用模板字面量创建了一个字符串console.log(message); // "Hello world!"console.log(typeof message); // "string"console.log(message.length); // 12复制代码
注意:如果模板字符串中使用到了反引号,则应该转义。但是单双引号不需要转义
3.2.2 多行字符串
在ES5之前JavaScript是不支持多行字符串的。(但是在以前的版本中有一个大家都认为是bug的方式可以写出多行字符串,就是在尾部添加一个反斜杠 \)
var s = "abc \ aaaaaa"; console.log(s); //但是输出的结果中不包括换行复制代码
但是在ES6中字符串的模板字面量轻松的解决了多行字符串的问题,而且没有任何新的语法
var s = `abc aaaaa dsalfja dfadfja`; console.log(s);复制代码
但是要注意: 反引号中的所有空格和缩进都是有效字符。
4. 数组的扩展
4.1 数组推导
数组推导就是直接通过现有数组生成新数组的一种简化写法,通过for...of结构,允许多重循环。注:新数组会立即在内存中生成,这时如果原数组是一个很大的数组,将会非常耗费内存。
var a1 = [1, 2, 3, 4];var a2 = [for (i of a1) i * 2];a2 // [2, 4, 6, 8]var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];[for (year of years) if (year > 2000 && year < 2010) year];// [ 2006]复制代码
4.2 数组处理扩展方法
Array.from():用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象,其中包括ES6新增的Set和Map结构。Array.from()还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理。
Array.from({ 0: "a", 1: "b", 2: "c", length: 3 });// [ "a", "b" , "c" ]Array.from(arrayLike, x => x * x);// 等同于Array.from(arrayLike).map(x => x * x);复制代码
Array.of()方法用于将一组值,转换为数组。弥补数组构造函数Array()的不足。
Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array(3) // [undefined, undefined, undefined]复制代码
数组实例的find()用于找出第一个符合条件的数组元素;数组实例的findIndex()用于返回第一个符合条件的数组元素的位置。这两个方法都可以发现NaN,弥补了IndexOf()的不足。
数组实例的fill()使用给定值,填充一个数组。
数组实例的entries(),keys()和values()用于遍历数组,它们都返回一个遍历器,可以用for...of循环进行遍历。keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
5. 对象功能的拓展
5.1 创建对象
ES5中创建对象的几种方法
- 字面量法
- 工厂模式
- 构造函数
- 组合方式 构造函数+原型模式
在ES6中可以使用class创建一个对象
class 类名{ constructor(参数){ this.属性 = 参数; } running{ //对象中简写方法,省略了function }}复制代码
注意:
- lass 是关键字,后面紧跟类名,类名首字母大写,采取的是大驼峰命名法则。类名之后是{}。
- 在{}中,不能直接写语句,只能写方法,方法不需要使用关键字。
- 方法和方法之间没有逗号。不是键值对
5.2 新增的方法
5.2.1 属性相关的方法
(1) Obect.getOwnProertyDescriptor();
获取一个对象中某个属性的详细信息。
var a = 1;console.log(Object.getOwnPropertyDescriptor(window,"a"))复制代码
浏览器打印如下:
{value: 1, writable: true, enumerable: true, configurable: false} configurable:false enumerable:true value:1 writable:true __proto__:Object复制代码
(2) Object.defineProperty( )
精细化设置一个对象的属性的
- configurable:是否可删除
- writable:是否可修改
- enumerale:是否可以枚举
- value:值
var obj = {};Object.defineProperty(obj,"name",{ configurable:false, writable:true, enumerale:false, value:"阿毛"})复制代码
浏览器打印如下:
> delete obj.name;< false> obj.name = "阿猫"< "阿猫"> obj.name< "阿猫"复制代码
(3) Object.defineProperties()
var obj = {};Object.defineProperties(obj,{ "name":{ configurable:false, writable:false, enumerable:false, value:"阿毛" }, "age":{ configurable:true, writable:true, enumerable:true, value:"20" }});复制代码
浏览器打印如下:
> obj< { age: "20", name: "阿毛"}> delete obj.name;< false> delete obj.age< true> obj< { name: "阿毛"}复制代码
(4) getOwnPropertyNames()
获取自的属性,以数组的格式返回的。
var obj = { name:"阿毛", age:20};复制代码
浏览器打印如下:
> Object.getOwnPropertyNames(obj);< (2) ["name", "age"] 0: "name" 1: "age" length: 2 __proto__: Array(0)复制代码
(5) Object.keys()
var obj = { name:"阿毛", age:20};复制代码
浏览器打印如下:
> Object.getOwnPropertyNames(obj);< (2) ["name", "age"] 0: "name" 1: "age" length: 2 __proto__: Array(0)复制代码
使用Object.getOwnPropertyNames()和Object.keys()都可以得到一个对象的属性名,属性名是放在一个数组中的。
对于对象的遍历目前有三种方式:
- for in
- Object.keys()
- Object.getOwnPropertyNames()
for in : 会输出自身以及原型链上可枚举的属性。 Object.keys() : 用来获取对象自身可枚举的属性键 Object.getOwnPropertyNames() : 用来获取对象自身的全部属性名
(6) Object.values();
获取对象的值,放到数组中。
var obj = { name:"阿毛", age:20};复制代码
浏览器打印如下:
> Object.values(obj);< (2) ["阿毛", 20]复制代码
5.2.2 继承相关的方法
(1) Object.create()
使用Object.create比较适合对字面量对象的继承。
(2) getPrototypeOf
getPrtotypeOf 获取创建这个对象的那个构造器的prototype属性。
var obj = { name : "阿毛", age : 20}console.log(Object.getPrototypeOf(obj) == Object.prototype);console.log(Object.getPrototypeOf([]) == Array.prototype);复制代码
truetrue复制代码
5.2.3 防篡改方法
可以对对象进行保护,分成三个级别:
(1) 设置不可拓展: preventExtensions()
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。如下:
var obj = { name : "阿猫", age : 20}Object.preventExtensions(obj);obj.name = "阿狗";delete obj.age;obj.sex = "man";console.log(obj);复制代码
浏览器打印:
{ name: '阿狗' }复制代码
(2) 封闭对象:seal()
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
var obj = { name : "阿猫", age : 20}Object.seal(obj);obj.name = "阿狗";delete obj.age;obj.sex = "man";console.log(obj);复制代码
浏览器打印:
{ name: '阿狗', age: 20 }复制代码
(3) 冻结对象: freeze()
Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。该方法返回被冻结的对象。如下:
var obj = { name : "阿猫", age : 20}Object.freeze(obj);obj.name = "阿狗";delete obj.age;obj.sex = "man";console.log(obj);复制代码
浏览器打印:
{ name: '阿猫', age: 20 }复制代码
也可以使用isExtensible, isSealed, isFrozen来判断当处处理如个保护状态,如下:
1.Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
==只有这个返回值默认为true,因为这个方法是判断是否可以拓展,而默认的是可以==
var obj = { name : "阿猫", age : 20}console.log(Object.isExtensible(obj));console.log(Object.isSealed(obj));console.log(Object.isFrozen(obj));Object.preventExtensions(obj)console.log(Object.isExtensible(obj));console.log(Object.isSealed(obj));console.log(Object.isFrozen(obj));复制代码
浏览器打印如下:
truefalsefalse{ name: '阿狗' }falsefalsefalse复制代码
2. Object.isSealed() 方法判断一个对象是否被密封。
var obj = { name : "阿猫", age : 20}Object.seal(obj)console.log(Object.isExtensible(obj));console.log(Object.isSealed(obj));console.log(Object.isFrozen(obj));复制代码
浏览器打印如下:
{ name: '阿狗', age: 20 }falsetruefalse复制代码
3. Object.isFrozen()方法判断一个对象是否被冻结。
var obj = { name : "阿猫", age : 20}Object.freeze(obj)console.log(Object.isExtensible(obj));console.log(Object.isSealed(obj));console.log(Object.isFrozen(obj));复制代码
浏览器打印如下:
{ name: '阿猫', age: 20 }falsetruetrue复制代码
5.2.4 Object.assign
Object.assign就去用于对象的合并,将源对象的所有的可枚举属性,复制到目标对象。
代码如下:
var obj1 = {};var obj2 = { name:"阿猫", age:20}var obj3 = { name:"阿狗"}Object.assign(obj1,obj2,obj3)//obj1是目标对象,obj2、obj3都是源对象console.log(obj1);复制代码
浏览器打印如下:
{ name: '阿狗', age: 20 }复制代码
==注意细节:==
- 第一个参数是目标对象,assign这个方法把其它的参数中的属性全部加在第一个参数身上。
- 第一个参数在属性复制过程中,可以会被修改,后面的会覆盖前面的属性
- assign这个方法的返回值就是第一个参数的引用,也就是返回了第一个参数。
- aassign这个方法会把原型上面的发展也拷贝了。
- assign不能拷贝继承过来的属性
- assign也不拷贝不可枚举的属性
如下:
var obj1 = {};var obj2 = { name : "阿猫"}Object.defineProperty(obj2,"age",{ configurable:true, writable:true, value:20, enumerable:false})Object.assign(obj1,obj2);console.log(obj1);复制代码
浏览器打印如下:
{ name: '阿猫' }复制代码
- assign是浅复制,如下:
var obj1 = {};var obj2 = { name:"阿猫", friends:["阿狗","阿郎"]}Object.assign(obj1,obj2);console.log(obj1);obj2.friends.push("阿毛");console.log(obj1)复制代码
浏览器打印如下:
{ name: '阿猫', friends: [ '阿狗', '阿郎' ] }{ name: '阿猫', friends: [ '阿狗', '阿郎', '阿毛' ] }复制代码
在对源对象的应用类型数据修改后,目标对象也修改了
5.3 对象扩展运算符
数组中的扩展运算符:
var arr1 = [1,2,3];var arr2 = [...arr1]console.log(arr2);复制代码
作用:用于取出参数对象的所有可遍历属性,拷贝当前对象中。
var obj1 = { name : "阿猫", age : 20, friends : ["阿狗,阿毛"]}var obj2 = {...obj1}console.log(obj2);obj1.friends.push("阿郎")console.log(obj2);复制代码
效果如下
{ name: '阿猫', age: 20, friends: [ '阿狗,阿毛' ] }{ name: '阿猫', age: 20, friends: [ '阿狗,阿毛', '阿郎' ] }复制代码
还可以这样用:
var obj1 = { name : "阿猫",}var obj2 = { age : 20}var obj3 = {...obj1,...obj2}console.log(obj3);复制代码
打印如下:
{ name: '阿猫', age: 20 }复制代码
6. 函数的新增特性
6.1.1 函数参数的默认值
JavaScript函数的最大的一个特点就是在传递参数的时候,参数的个数不受限制的。为了健壮性考虑,一般在函数内部需要做一些默认值的处理。
function makeRequest(url, timeout, callback) { timeout = timeout || 2000; callback = callback || function() {};}复制代码
其实上面的默认值方法有个bug:当timeout是0的时候也会当做假值来处理,从而给赋值默认值2000.
ES6从语言层面面上增加了 默认值的 支持。看下面的代码:
//这个函数如果只传入第一个参数,后面两个不传入,则会使用默认值。如果后面两个也传入了参数,则不会使用默认值。function makeRequest(url, timeout = 2000, callback = function() {}) { // 其余代码复制代码
6.1.2 默认参数对 arguments 对象的影响
在非严格模式下,arguments总是能反映出命名参数的变化。看下面的代码:
function foo(a, b) { //非严格模式 console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true } foo(1, 2);复制代码
在ES5的严格模式下,arguments只反映参数的初始值,而不再反映命名参数的变化!
function foo(a, b) { //严格模式 "use strict" console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //false。 修改a的值不会影响到arguments[0]的值 console.log(arguments[1] === b); //false } foo(1, 2);复制代码
当使用ES6参数默认值的时候,不管是否是在严格模式下,都和ES5的严格模式相同。看下面的代码:
function foo(a, b = 30) { console.log(arguments[0] === a); //true console.log(arguments[1] === b); //true a = 10; b = 20; console.log(arguments[0] === a); //false。 由于b使用了默认值。虽然a没有使用默认值,但是仍然表现的和严格模式一样。 console.log(arguments[1] === b); //false。 b使用了默认值,所以表现的和严格模式一样。 } foo(1, 2);复制代码
注意:如果这样调用foo(1),则 a == 1, b == 30, arguments[0] == 1, arguments[1] == undefined。也就是说默认值并不会赋值给arguments参数。
6.1.3 默认参数表达式 (Default Parameter Expressions)
参数的默认值,也可以是一个表达式或者函数调用等。看下面的代码
function getValue() { return 5; } function add(first, second = getValue()) { //表示使用getValue这个函数的返回值作为second的默认值。 return first + second; } console.log(add(1, 1)); // 2. 调用add函数的时候,传入了第二个参数,则以传入的参数为准。 console.log(add(1)); // 6。 调用add函数的时候,没有传入第二个参数,则会调用getValue函数。复制代码
有一点需要要注意:getValue()只会在调用add且不传入第二个参数的时候才会去调用。不是在解析阶段调用的。
let value = 5; function getValue() { return value++; } function add(first, second = getValue()) { // return first + second; } console.log(add(1, 1)); // 2 console.log(add(1)); // 6。 console.log(add(1)); // 7 console.log(add(1)); // 8复制代码
由于默认值可以表达式,所以我们甚至可以使用前面的参数作为后面参数的默认值。
function add(first, second = first) { // 使用第一个参数作为第二个参数的默认值 return first + second; }复制代码
注意:可以把前面的参数作为后面参数的默认值,但是不能把后面的参数作为第一个参数的默认值。这可以前面说的let和const的暂存性死区一个意思。
function add(first = second, second)) { // 这种写法是错误的 return first + second;}复制代码
6.2 箭头函数
ECMAScript 6 最有意思的部分之一就是箭头函数。正如其名,箭头函数由 “箭头”(=>)这种新的语法来定义。
其实在别的语言中早就有了这种语法结构,不过他们叫拉姆达表达式。
6.2.1 箭头函数语法
基本语法如下:
(形参列表)=>{ //函数体}复制代码
箭头函数可以赋值给变量,也可以像匿名函数一样直接作为参数传递。
示例1:
var sum = (num1, num2) =>{ return num1 + num2; } console.log(sum(3, 4)); //前面的箭头函数等同于下面的传统函数 var add = function (num1, num2) { return num1 + num2; } console.log(add(2, 4))复制代码
如果函数体内只有一行代码,则包裹函数体的 大括号 ({ })完全可以省略。如果有return,return关键字也可以省略。 如果函数体内有多条语句,则 {} 不能省略。
示例2:
var sum = (num1, num2) => num1 + num2; console.log(sum(5, 4)); //前面的箭头函数等同于下面的传统函数 var add = function (num1, num2) { return num1 + num2; } console.log(add(2, 4)); //如果这一行代码是没有返回值的,则方法的返回自也是undefined var foo = (num1, num2) => console.log("aaa"); console.log(foo(3,4)); //这个地方的返回值就是undefined复制代码
如果箭头函数只有一个参数,则包裹参数的小括号可以省略。其余情况下都不可以省略。当然如果不传入参数也不可以省略
示例3:
var foo = a=> a+3; //因为只有一个参数,所以()可以省略 console.log(foo(4)); // 7复制代码
如果没有需要传入的参数,而且还不想添加传统的大括号和return,则必须给整个对象添加一个小括号 ()
示例4:
var foo = ()=>({ name:"lisi", age:30}); console.log(foo()); //等同于下面的; var foo1 = ()=>{ return { name:"lisi", age : 30 }; }复制代码
6.2.2 使用箭头函数实现函数自执行
var person = (name => { return { name: name, age: 30 } } )("zs"); console.log(person);复制代码
6.2.3 箭头函数中无this绑定
在ES5之前this的绑定是个比较麻烦的问题,稍不注意就达不到自己想要的效果。因为this的绑定和定义位置无关,只和调用方式有关。
在箭头函数中则没有这样的问题,在箭头函数中,this和定义时的作用域相关,不用考虑调用方式
箭头函数没有 this 绑定,意味着 this 只能通过查找作用域链来确定。如果箭头函数被另一个不包含箭头函数的函数囊括,那么 this 的值和该函数中的 this 相等,否则 this 的值为 window。
var PageHandler = { id: "123456", init: function () { document.addEventListener("click", event => this.doSomething(event.type), false); // 在此处this的和init函数内的this相同。 }, doSomething: function (type) { console.log("Handling " + type + " for " + this.id); } }; PageHandler.init();复制代码
看下面的一段代码:
var p = { foo:()=>console.log(this) //此处this为window } p.foo(); //输出为 window对象。 并不是我想要的。所以在定义对象的方法的时候应该避免使用箭头函数。//箭头函数一般用在传递参数,或者在函数内部声明函数的时候使用。复制代码
说明:
箭头函数作为一个使用完就扔的函数,不能作为构造函数使用。也就是不能使用new 的方式来使用箭头函数。 由于箭头函数中的this与函数的作用域相关,所以不能使用call、apply、bind来重新绑定this。但是虽然this不能重新绑定,但是还是可以使用call和apply方法去执行箭头函数的。
6.2.4 无arguments绑定
虽然箭头函数没有自己的arguments对象,但是在箭头函数内部还是可以使用它外部函数的arguments对象的。
function foo() { //这里的arguments是foo函数的arguments对象。箭头函数自己是没有 arguments 对象的。 return ()=>arguments[0]; //箭头函数的返回值是foo函数的第一个参数 } var arrow = foo(4, 5); console.log(arrow()); // 4复制代码
7. 新增数据结构
7.1 Set数据结构
set和数据差不多,也是一种集合,区别在于:它里面的值都是唯一的,没有重复的。
创建一个set对象如下
var setList = new Set(); setList.add("a"); setList.add("c"); setList.add("b"); setList.add("a"); //重复,所以添加失败。注意这个地方并不会保存。 console.log(setList.size); // 长度是3复制代码
set的方法有add()、has()、delete()、clear()等方法,分别用于添加、查询、删除、和清空的功能。
var setList = new Set(["a","b","c","d"]);console.log(setList);//{"a", "b", "c", "d"}setList.add("e");console.log(setList);//{"a", "b", "c", "d", "e"}console.log(setList.has("b"));//truesetList.delete("c");console.log(setList);//{"a", "b", "d", "e"}setList.clear();console.log(setList);//{}复制代码
使用set可以轻松的实现将一组数据去重
var arr1 = [1,1,2,2,3,3];var arr2 = [...(new Set(arr1))]console.log(arr2);//[1,2,3]复制代码
7.2 Map数据结构
它类似于对象,里面存放也是键值对,区别在于:对象中的键名只能是字符串,如果使用map,它里面的键可以是任意值。
创建一个map对象如下:
var map = new Map(); map.set("a", "lisi"); map.set("b", "zhangsan"); map.set("b", "zhangsan222"); // 第二次添加,新的value会替换掉旧的 console.log(map.get("a")); console.log(map.get("b")); //zhangsan222 console.log(map.get("c")); //undefined.如果key不存在,则返回undefined console.log(map.size); //2复制代码
map中的方法有:
- has(key) - 判断给定的 key 是否在 map 中存在
- delete(key) - 移除 map 中的 key 及对应的值
- clear() - 移除 map 中所有的键值对
var map = new Map();map.set("a","a");map.set("b","b");map.set("c","c");console.log( map.get("a"))//aconsole.log( map.has("a") ) //trueconsole.log( map.has("d") ) //falsemap.clear();console.log(map);//Map {}复制代码
8.Class
ES6从形式上,向主流的面向对象的语言靠拢,前面我们都是创建构造器,然后去new构造器,构造器就相当于一个类,在ES6中,就可以使用class来创建对象了。
8.1 class创建对象
上文说到,通过class创建对象的格式如下:
class 类名{ constructor(参数){ this.属性 = 参数; } running{ //对象中简写方法,省略了function }}复制代码
不使用Class,去创建一个类,如下:
function persion(name,age){ this.name = name, this.age = age;}persion.prototype.say = function(){ console.log(`我是${ this.name},${ this.age}岁`)}var p1 = new persion("阿毛",20);复制代码
在ES6中,可以使用class来声明一个类了,如下:
class Persion{ constructor(name,age){ this.name = name; this.age = age; } say(){ console.log(`${ this.name}的年龄是${ this.age}`) }}var p1 = new Persion("abc",12);p1.say();复制代码
在浏览器中访问之,如下:
abc的年龄是12复制代码
虽然使用class非常给力,但是class是属性JS中高级的语法,可以转成低级的语法,如下
8.2使用extends实现继承
格式:
class 子类 extends 父类{ constructor(参数){ super(参数); this.参数 = 值; }}复制代码
注意:
- 使用 extends 关键字来实现继承
- 在子类中的构造器 constructor 中,必须要显式调用父类的 super 方法,如果不调用,则 this 不可用 在ES5的继承如下:
function Persion(name,age){ this.name = name; this.age = age;}Persion.prototype.say = function(){ console.log(`I'm ${ this.name} and ${ this.age}`);}function GoodMan(name,age,sex){ //继承属性 Persion.call(this,name,age); this.sex = sex;}//继承方法 利用浅拷贝继承方法for (var p in Persion.prototype) { GoodMan.prototype[p] = Persion.prototype[p];}//GoodMan 自己的方法GoodMan.prototype.do = function () { console.log(`I'm ${ this.name},I'm ${ this.sex}`);}var p1 = new GoodMan("阿毛",20,"男");p1.do();p1.say();复制代码
使用ES6中的extends来实现继承:
class Persion{ constructor(name ,age){ this.name = name; this.age = age; } say(){ console.log(`I'm ${ this.name} and ${ this.age}`); }}class GoodMan extends Persion{ constructor(name,age,sex){ super(name,age); this.sex = sex; } do(){ console.log(`I'm ${ this.name},I'm ${ this.sex}`); }}p1 = new GoodMan("阿毛",20,"男");p1.do();p1.say();复制代码
在浏览器中效果如下:
I'm 阿毛,I'm 男I'm 阿毛 and 20复制代码
8.3 类的静态方法 static
直接通过类名来访问的方法就是静态方法。如:Math.abs();这里的 abs()是静态方法。
class Persion{ constructor(name,age){ this.name = name; this.age = age; } say(){ console.log(`${ this.name}的年龄是${ this.age}`) } static run(){ console.log("running......") }}Persion.run();复制代码
调用时就是Persion.run();
==静态方法只能用类名调用!!!!==
静态方法也可以继承
class Father{ static foo(){ console.log("我是父类的静态方法"); } } class Son extends Father{ } Son.foo(); //子类也继承了父类的静态方法。 这种方式调用和直接通过父类名调用时一样的。复制代码
总结:
整个ES6的新特性和用法学习下来,觉得比较有用的点如下:
- 优先使用let,因为相较var来说,let可以形成作用域,不易产生变量污染的情况。
- 不常变动的值使用const,方便阅读,也防止不小心的串改。
- 熟悉新加入的对字符串的处理函数,简化了对于字符串处理的过程。
- 使用模板字符串即 (`` ,不仅仅方便了字符串的整合,同时也省去样式的操作
- 使用匿名函数的场合,一律改为使用箭头函数。
- 增加Set和Map的使用率,可以减少仅仅使用Array和Object对象的时的一些复杂操作。