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.name
let { 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的值将会是undefined
let { name, age, value } = node;
console.log(name); // "阿猫"
console.log(age); // 20
console.log(value); // undefined
复制代码
不过我们也可以手动指定他的默认值。(这个和函数的参数默认值很像)
let node = {
name: "阿猫",
age:20
};
//手动添加value的默认值为3
let { name, age, value = 3} = node;
console.log(name); // "阿猫"
console.log(age); // 20
console.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")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.endsWith("!")); // true
console.log(msg.endsWith("world!")); // true
console.log(msg.endsWith("r", 9)); // true
console.log(msg.includes("o")); // true
console.log(msg.includes("x")); // false
console.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);
复制代码
true
true
复制代码
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));
复制代码
浏览器打印如下:
true
false
false
{ name: '阿狗' }
false
false
false
复制代码
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 }
false
true
false
复制代码
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 }
false
true
true
复制代码
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"));//true
setList.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"))//a
console.log( map.has("a") ) //true
console.log( map.has("d") ) //false
map.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中高级的语法,可以转成低级的语法,如下Babel网站
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对象的时的一些复杂操作。