历时将近6年的时间来制定的新 ECMAScript 标准 ECMAScript 6(亦称 ECMAScript Harmony,简称 ES6)终于在 2015年 6月正式发布,自从上一个标准版本 ES5 在 2009 年发布以后,ES6 就一直以新语法、新特性的优越型吸引著众多 Javascript 开发者,驱使他们积极尝鲜
由于 ES6 是在2015年发布的,所以也叫ES2015
以后ECMAScript 标准一年已更新,统一使用年份命名:ES2016、ES2017
块级作用域绑定
在ES5之前,不存在块级作用域,在编程的时候很多时候会带来很多的不便,ES6新增了块级作用域,补足了在这方面的缺陷
块级生命指的是该声明的变量无法被代码块外部访问,块级作用域,又被称为词法作用域(lexical scopes),可以再如下的条件创建:
- 函数内部
- 在代码块(即{})内部
块级作用域是很多类C语言的工作机制,ECMAScript 6 引入块级声明的目的是增强 JavaScript 的灵活性,同时又能与其他编程语言保持一致
let声明
使用let声明变量的语法和使用var声明的语法是一样的,但是let声明的变量的作用域会限制在当前的代码块中,这是let与var的最大区别
<script type="text/javascript">
let a = 10;
if(a > 5) {
console.log(b); // 用let声明的变量没有声明前这一特性,所以此处也访问不到(报错)
let b = 20;
console.log(b)
}
console.log(b) // 由于b是在if块中使用let声明的,所以此处无法访问到(报错)
</script>
注意
- 用let声明的变量具有块级作用域,只能在声明的块中访问,在块外面无法访问
- 用let声明的变量也没有声明提前这一特性
- 在同一个块中,let声明的变量也不能重复声明
- 在声明变量的时候尽量使用let,慢慢的抛弃var
const声明
在 ES6 使用 const 来声明的变量称之为常量,这意味着他们不能再次被赋值,由于这个原因,所有的const声明的变量都必须在声明处初始化,const声明的常量和let变量一样也是具有块级作用域的特性
<script type="text/javascript">
var a = 20;
if (true) {
const b = 20;
b = 30; // 错误,常量不能重新赋值
const c; // 错误,常量声明的同时必须赋值
}
</script>
注意:
- const的新特性除了声明的是常量外,其他与let一样
- 在let和const声明前的这段区域称之为暂存性死区(The Temporal Dead Zone —TDZ)
- 使用let和const声明的变量和常量不再是window的属性,也就是说通过window.a是无法访问到的
循环中的块级绑定
使用var声明的循环变量在循环结束后仍然可以访问到,使用let声明的循环变量,在循环结束之后会立即销毁
<script type="text/javascript">
for(let i = 0; i < 3; i++) { // 循环结束之后会立即销毁i
console.log(i)
}
console.log(i) // 此处无法访问到i
</script>
循环中的函数
// 下面的代码,是输出10个10,而不是0,1,2,...
<script type="text/javascript">
var funcs = [];
for(var i = 0; i < 10; i++) {
funcs.push(function(){
console.log(i)
})
}
funcs.forEach(function(func) {
func(); //输出10个10
})
</script>
如果使用let声明变量,则完全可以避免前面的问题,这是ES6规范中专门定义的特性。在for…in和for…of 循环中也适用
<script type="text/javascript">
var funcs = [];
for(let i = 0; i < 10; i ++) {
funcs.push(function(){
console.log(i)
})
}
funcs.forEach(function(func){
func() // 输出0,1,2...9
})
</script>
let 声明使得每次迭代都会创建一个变量i,所以循环内部创建的函数会获得各自的变量 i 的拷贝,每份拷贝都会在每次迭代的开始被创建并被赋值
带默认参数的函数
JavaScript 函数的最大的一个特点就是在传递参数的时候,参数的个数不受限制的,为了健壮性考虑,一般在函数内部需要做一些默认值的处理
function makeRequest(url,timeout,callback){
timeout = timeout || 2000;
callback = callback || function(){}
}
其实上面的默认值方法有个bug:当timeout是0的时候也会当做假值来处理,从而给赋值默认值2000
// ES6从语言层面上增加了默认值的支持,看下面的代码
// 这个函数如果只传入第一个参数,后面两个不传入,则会使用默认值
// 如果后面两个也传入了参数,则不会使用默认值
function makeRequest(url,timeout = 2000,callback = function(){}){
// 其余代码
}
默认参数对 arguments 对象的影像
在非严格模式下,arguments 总是能反映出命名参数的变化,看下面的代码:
<script type="text/javascript">
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)
</script>
在ES5的严格模式下,arguments只反映参数的初始值,而不再反映命名参数的变化!
<script type="text/javascript">
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)
</script>
当使用ES6参数默认值的时候,不管是否是在严格模式下,都和ES5的严格模式相同,看下面的代码:
<script type="text/javascript">
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)
</script>
注意:如果这样调用foo(1),则 a == 1,b == 30,arguments[0] == 1, arguments[1] == undefined,也就是说默认值并不会赋值给arguments参数
默认参数表达式
参数的默认值,也可以是一个表达式或者函数调用等,看下面的代码:
<script type="text/script">
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函数
</script>
有一点需要注意:getValue() 只会在调用add且不传入第二个参数的时候才会去调用,不是在解析阶段调用的
<script type="text/javascript">
let value = 5
function getValue(){
return value++
}
function add(first,second = getValue()){
return first + second
}
console.log(add(1,2)) // 2
console.log(add(1)) // 6
console.log(add(1)) // 7
console.log(add(1)) // 8
</script>
由于默认值可以表达式,所以我们甚至可以使用前面的参数作为后面参数的默认值
function add(first, second = first){ // 使用第一个参数作为第二个参数的默认值
return first + second
}
注意:可以把前面的参数作为后面参数的默认值,但是不能把后面的参数作为第一个参数的默认值,这可以前面说的let和const的暂存性死区一个意思
function add(first = second, second){ // 这种写法是错误的
return first + second
}
未命名参数的问题
JavaScript 并不是限制传入的参数的数量,在调用函数的时候,传入的实参的个数超过形参的个数的时候,超过的部分就称为了未命名参数,在ES5之前,我们一般可以通过arguments对象来获取到未命名参数的值,但是略显繁琐
<script type="text/javascript">
function foo(a){
console.log(a)
console.log(arguments[1]) // 取得传入的多余的参数
}
foo(2,3)
</script>
ES6,提供了一种更加优雅处理未命名参数的问题:剩余参数(Rest Parameters)
语法:function a (a, … b){ }
剩余参数使用三个点(…)和变量名表示
<script type="text/javascript">
function foo(a,...b){
console.log(a)
console.log(b instanceof Array) // true,多余的参数都被放入了b中,b其实就是一个数组
}
foo(2,3,4,6)
</script>
注意:
- 函数最多只能有一个剩余参数b,而且这个剩余参数必须位于参数列表的最后位置
- 虽然有了剩余参数,但是arguments仍然存在,但是arguments完全无视了剩余参数的存在
- 剩余参数是在函数声明的时候出现的
函数中的扩展运算符
例如:Math中的max函数可以返回任意多个参数中的最大值,但是如果这些参数在一个数组中,则没有办法直接传入,以前通用的做法是使用apply方法
看下面的代码:
<script type="text/javascript">
let values = [25,50,75,100]
console.log(Math.max.apply(Math,values)) // 100
</script>
上面这种方法虽然可行,但是总是不是那么直观
使用ES6提供的扩展运算符可以很容易的解决这个问题,在数组前加前缀 …(三个点)
<script type="text/javascript">
let values = [25,50,75,100]
console.log(Math.max(...values)) // 使用扩展运算符,相当于拆解了数组了
console.log(Math.max(...values,200)) // 也可以使用扩展运算符合参数的混用,则这个时候就有5个参数与比较了
</script>
注意:剩余参数和扩展运算符都是使用三个点作为前缀,但是他们呢使用的位置是不一样的
- 剩余参数是用在函数的声明的时候的参数列表中,而且必须在参数列表的后面
- 扩展运算符是用在函数调用的时候作为实参来传递的,在实参中的位置没有限制
ECMAScript 6 最有意思的部分之一就是箭头函数,正如其名,箭头函数由“箭头”(=>)这种新的语法来定义
其实在别的语言中早就有了这种语法结构,不过他们叫拉姆达表达式
箭头函数语法
// 基本语法如下:
(形参列表) => {
// 函数体
}
// 箭头函数可以赋值给变量,也可以像匿名函数一样直接作为参数传递
<script type="text/javascript">
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))
</script>
如果函数体内只有一行代码,则包裹函数体的大括号 ({ }) 完全可以省略,如果有return,return 关键字也可以省略
如果函数体内有多条语句,则 { } 不能省略
<script type="text/javascript">
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
</script>
如果箭头函数只有一个参数,则包裹参数的小括号可以省略,其余情况下都不可以省略,当然如果不传入参数也不可以省略
<script type="text/javascript">
var foo = a => a + 3 // 因为只有一个参数,所以()可以省略
console.log(foo(4)) // 7
</script>
如果想直接返回一个js对象,而且还不想添加传统的大括号和return,则必须给整个对象添加一个小括号()
<script type="text/javascript">
var foo = () => ({name:'lisi',age:30})
console.log(foo())
// 等同于下面的
var foo1 = () => {
return {
name:'lisi',
age:30
}
}
</script>
使用箭头函数实现函数自执行
<script type="text/javascript">
var person = (name => {
return {
name:name,
age:30
}
})('zs')
console.log(person)
</script>
箭头函数中无this绑定(No this Binding)
在ES5之前this的绑定是个比较麻烦的问题,稍不注意就达不到自己想要的效果,因为this的绑定和定义位置无关,只和调用方式有关
在箭头函数中则没有这样的问题,在箭头函数中,this和定义时的作用域相关,不用考虑调用方式
箭头函数没有this绑定,意味着this只能通过查找作用域链来确定,如果箭头函数被另一个不包含箭头函数的函数囊括,那么this的值和该函数中的this相等,否则this的值为window
<script type="text/javascript">
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()
</script>
看下面的一段代码:
<script type="text/javascript">
var p = {
foo:() => console.log(this) // 此处this为window
}
p.foo()
// 输出为window对象,并不是我想要的,所以在定义对象的方法的时候应该避免使用箭头函数
// 箭头函数一般用在传递参数,或者在函数内部声明函数的时候使用
</script>
说明:
- 箭头函数作为一个使用完就扔的函数,不能作为构造函数使用,也就是不能使用new的方式来使用箭头函数
- 由于箭头函数中的this与函数的作用域相关,所以不能使用call、apply、bind来重新绑定this,但是虽然this不能重新绑定,但是还是可以使用call和apply方法去执行箭头函数的
== 无arguments绑定==
虽然箭头函数没有自己的arguments对象,但是在箭头函数内部还是可以使用它外部函数的arguments对象的
<script type="text/javascript">
function foo(){
// 这里的arguments是foo函数的arguments对象,箭头函数自己没有arguments对象的
return () => arguments[0] // 箭头函数的返回值是foo函数的第一个参数
}
var arrow = foo(4,5)
console.log(arrow())
</script>
在JavaScript中,几乎多有的类型都是对象,所以使用好对象,对提示JavaScript的性能很重要
ECMAScript 6 给对象的各个方面,从简单的语法扩展到操作与交互,都做了改进
对象类别
ECMAScript 6 规范明确定义了每种对象类别,理解该术语对于从整体上认识该门语言显得十分重要,对象类别包括:
- 普通对象(ordinary object)拥有 JavaScript 对象所有的默认行为
- 特异对象(exotic object)的某些内部行为和默认的有所差异
- 标准对象(standard object)是ECMAScript 6 中定义的对象,例如Array,Date等,他们既可能是普通也可能是特异对象
内置对象(built-in object)指JavaScript 执行环境开始运行时已存在的对象,标准对象均为内置对象