重新认识ES6中的语法糖金沙官网线上:

rest 参数

先从函数的参数传递说起:

function sum(a,b,c){
    let total = a + b + c;
    return total;
}
sum(1, 2, 3);

在 JavaScript 中,函数参数实际上以数组的方式进行传递,参数会被保存在 arguments 数组中,因此上例等价于:

function sum(){
    let total = arguments[0] + arguments[1] + arguments[2];
    return total;
}
sum(1, 2, 3);

不过 arguments 不单单包括参数,也包括了其他东西,因此没法直接用数组函数来操作 arguments。如果要扩展成任意多个数值相加,可以使用循环:

function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total = total + arguments[i];
    }
    return total;
}
sum(1, 2, 3, 4, 6);

es6 则提供了 rest 参数来访问多余变量,上例等价于:

function sum(...num) {
    let total = 0;
    for (let i = 0; i < num.length; i++) {
        total = total + num[i];
    }
    return total;
}
sum(1, 2, 3, 4, 6);

可以以变量形式进行传递:

function sum(...num) {
    let total = 0;
    for (let i = 0; i < num.length; i++) {
        total = total + num[i];
    }
    return total;
}
let nums = [1, 2, 3, 4, 6];
sum(...nums);

在函数中体内,num 就是单纯由参数构成的数组,因此可以用数组函数 reduce 来实现同样的功能:

function sum(...num) {
    return num.reduce( (preval, curval) => {
        return preval + curval;
    })
}
sum(1, 2, 3, 4, 6);

... 还可以与其他参数结合使用,只需要将其他参数放在前面即可:

function sum(total = 0, ...num) {
    return total + num.reduce( (preval, curval) => {
        return preval + curval;
    });
}

let nums = [1,2,3,4];
sum(100, ...nums);

模板字符串

模板字符串是对常规JavaScript字符串的重大改进,不同于在普通字符串中使用单引号或者双引号,模板字符串的声明需要使用反撇号,如下所示:

var text = `This is my first template literal`

因为使用的是反撇号,你可以在模板字符串中随意使用单双引号了,使用时不再需要考虑转义,如下:

var text = `I'm "amazed" at these opportunities!`

模板字符串具有很多强大的功能,可在其中插入JavaScript表达式就是其一。

属性的简写

let data = {
        message: "你好,Vue"
    };

var vm = new Vue({
    el: '#root',
    data:data
})

可以简写成:

let data = {
        message: "你好,Vue"
    };

var vm = new Vue({
    el: '#root',
    data
})

也就是说,可以直接在对象中直接写入变量,当函数的返回值为对象时候,使用简写方式更加简洁直观:

function getPerson(){
    let name = 'Jack';
    let age = 10;

    return {name, age};
    // 等价于
    // return {
    //     name : name,
    //     age : age
    // }

}
getPerson();

Shifting和Spreading

当你想要抽出一个数组的前一个或者两个元素时,常用的解决方案是使用.shift.尽管是函数式的,下述代码在第一次看到的时候却不好理解,我们使用了两次.slicelist中抽离出两个不同的元素。

var list = ['a', 'b', 'c', 'd', 'e']
var first = list.shift()
var second = list.shift()
console.log(first)
// <- 'a'

在ES6中,结合使用拓展和解构,可以让代码的可读性更好:

var [first, second, ...other] = ['a', 'b', 'c', 'd', 'e']
console.log(other)
// <- ['c', 'd', 'e']

除了对数组进行拓展,你同样可以对函数参数使用拓展,下例展示了如何添加任意数量的参数到multiply函数中。

function multiply(left, right) {
  return left * right
}
var result = multiply(...[2, 3])
console.log(result)
// <- 6

向在数组中一样,函数参数中的拓展运算符同样可以结合常规参数一起使用。下例中,print函数结合使用了rest,普通参数,和拓展运算符:

function print(...list) {
  console.log(list)
}
print(1, ...[2, 3], 4, ...[5])
// <- [1, 2, 3, 4, 5]

下表总结了,拓展运算符的常见使用方法:

使用示例 ES5 ES6
Concatenation [1, 2].concat(more) [1, 2, ...more]
Push an array onto list list.push.apply(list, items) list.push(...items)
Destructuring a = list[0], other = list.slice(1) <span class="Apple-tab-span" style="white-space: pre;"> </span>[a, ...other] = list
new and apply new (Date.bind.apply(Date, [null,2015,31,8])) new Date(...[2015,31,8])

箭头函数

在 Vue 中,使用箭头函数的最大好处就是可以让 this 指向 Vue 实例:

var vm = new Vue({
    el:'#root',
    data:{
        tasks:[]
    },
    mounted(){
        axios.get('/tasks')
        .then(function (response) {
            vm.tasks = response.data;
        })
    }
});

由于回调函数的 this 指向全局对象 window,因此,我们需要通过 vm 来访问实例的方法,如果使用箭头函数,则可以写成:

new Vue({
    el:'#root',
    data:{
        tasks:[]
    },
    mounted(){
           axios.get('/tasks')
            .then(response => this.tasks = response.data);
    }
});

箭头函数的 this 对象始终指向定义函数时所在的对象,相当于:

var vm = new Vue({
    el:'#root',
    data:{
        tasks:[]
    },
    mounted(){
        var that = this;
        axios.get('/tasks')
        .then(function (response) {
            that.tasks = response.data;
        })
    }
});

简写箭头函数带来的一些问题

当你的简写箭头函数返回值为一个对象时,你需要用小括号括起你想返回的对象。否则,浏览器会把对象的{}解析为箭头函数函数体的开始和结束标记。

// 正确的使用形式
var objectFactory = () => ({ modular: 'es6' })

下面的代码会报错,箭头函数会把本想返回的对象的花括号解析为函数体,number被解析为label,value解释为没有做任何事情表达式,我们又没有显式使用return,返回值默认是undefined

[1, 2, 3].map(value => { number: value })
// <- [undefined, undefined, undefined]

当我们返回的对象字面量不止一个属性时,浏览器编译器不能正确解析第二个属性,这时会抛出语法错误。

[1, 2, 3].map(value => { number: value, verified: true })
// <- SyntaxError

解决方案是把返回的对象字面量包裹在小括号中,以助于浏览器正确解析:

[1, 2, 3].map(value => ({ number: value, verified: true }))
/* <- [
  { number: 1, verified: true },
  { number: 2, verified: true },
  { number: 3, verified: true }]
*/

函数的简写

函数的简写,在 Vue 中会用到:

Vue({
   el: '#root',
   data:data,
   methods: {
       addName: function() {
           vm.names.push(vm.newName);
           vm.newName = "";
       }
   }
});

可以简写为:

new Vue({
   el: '#root',
   data:data,
   methods: {
       addName() {
           vm.names.push(vm.newName);
           vm.newName = "";
       }
   }
});

在组件中频繁用到:

Vue.component('example',{
    data(){
        return {

        };
    }
});

通过const声明的变量值并非不可改变

使用const只是意味着,变量将始终指向相同的对象或初始的值。这种引用是不可变的。但是值并非不可变。

下面的例子说明,虽然people的指向不可变,但是数组本身是可以被修改的。

const people = ['Tesla', 'Musk']
people.push('Berners-Lee')
console.log(people)
// <- ['Tesla', 'Musk', 'Berners-Lee']

const只是阻止变量引用另外一个值,下例中,尽管我们使用const声明了people,然后把它赋值给了humans,我们还是可以改变humans的指向,因为humans不是由const声明的,其引用可随意改变。people 是由 const 声明的,则不可改变。

const people = ['Tesla', 'Musk']
var humans = people
humans = 'evil'
console.log(humans)
// <- 'evil'

如果我们的目的是让值不可修改,我们需要借助函数的帮助,比如使用Object.freeze

const frozen = Object.freeze(
  ['Ice', 'Icicle', 'Ice cube']
)
frozen.push('Water')
// Uncaught TypeError: Can't add property 3
// object is not extensible

下面我们详细讨论一下constlet的优点

对象的简写

对象字面量

对象字面量是指以{}形式直接表示的对象,比如下面这样:

var book = {
  title: 'Modular ES6',
  author: 'Nicolas',
  publisher: 'O´Reilly'
}

ES6 为对象字面量的语法带来了一些改进:包括属性/方法的简洁表示,可计算的属性名等等,我们逐一来看:

针对之学习 Vue 用到的 es6 特性,做下简单总结。

有用的链接

var 与 let

es6 之前,JavaScript 并没有块级作用域,所谓的块,就是大括号里面的语句所组成的代码块,比如

function fire(bool) {
    if (bool) {
        var foo = "bar";
    }
    console.log(foo);
}

fire(true); //=> bar

虽然变量 foo 位于 if 语句的代码块中,但是 JavaScript 并没有块级作用域的概念,因此被添加到了当前的执行环境 - 即函数中,在函数内都可以访问到。

另外一个令人困惑的地方是变量提升:

function fire(bool) {
    if (bool) {
        var foo = "bar";
    } else {
        console.log(foo);
    }
}
fire(false); //=> undefined

我们都知道,直接访问一个未定义的变量,会报错:

console.log(nope); //=> Uncaught ReferenceError: nope is not defined

但是在上述的例子中,会返回 undefined。也就是说,变量的定义被提升到了作用域的顶部,等价于:

function fire(bool) {
    var foo;
    if (bool) {
           foo = "bar";
    } else {
        console.log(foo);
    }
}
fire(false);

而在 JavaScript 中,声明但是未赋值的变量会被赋值为 undefined,因此,结果输出 undefined

为了解决上述问题,引入了 let 关键字,let 定义的变量。

首先,let 定义的变量只在代码块内有效:

function fire(bool) {
    if (bool) {
        let foo = "bar";
    }
    console.log(foo);
}

fire(true); //=> Uncaught ReferenceError: foo is not defined

其次, let 定义的变量不存在变量提升:

function fire(bool) {
    if (bool) {
        let foo = "bar";
    } else {
        console.log(foo);
    }
}
fire(false); //=> Uncaught ReferenceError: foo is not defined

因此,使用 let,上述问题完全解决,这也告诉了我们,应当尽可能的避免用 var,用 let 来代替,除非你需要用到变量提升。

简写的箭头函数

完整的箭头函数是这样的:

var example = (parameters) => {
  // function body
}

简写1:

当只有一个参数时,我们可以省略箭头函数参数两侧的括号:

var double = value => {
  return value * 2
}

简写2:

对只有单行表达式且,该表达式的值为返回值的箭头函数来说,表征函数体的{},可以省略,return 关键字可以省略,会静默返回该单一表达式的值。

var double = (value) => value * 2

简写3:
上述两种形式可以合并使用,而得到更加简洁的形式

var double = value => value * 2

现在,你肯定学会了箭头函数的基本使用方法,接下来我们再看几个使用示例。

默认参数

在 es6 之前,JavaScript 不能像 PHP 那样支持默认参数,因此需要自己手动定义:

function  takeDiscount(price, discount){
    discount  = discount || 0.9;
    return price * discount;
}
takeDiscount(100);

es6 则允许定义默认参数

function takeDiscount(price, discount = 0.9){
    return price * discount;
}
takeDiscount(100);

甚至可以以函数形式传递参数:

function getDiscount(){
    return 0.9;
}

function takeDiscount(price, discount = getDiscount()){
    return price * discount;
}
takeDiscount(100);

解构赋值

ES6提供的最灵活和富于表现性的新特性莫过于解构了。一旦你熟悉了,它用起来也很简单,某种程度上解构可以看做是变量赋值的语法糖,可应用于对象,数组甚至函数的参数。

const

const 与 let 的基本用法相同,定义的变量都具有块级作用域,也不会发生变量提升。不同的地方在于,const 定义的变量,只能赋值一次。

对于基本类型来说,需要通过赋值来改变其值,因此 const 定义之后就相当于无法改变:

const a = 1;
a = 2;  // Uncaught TypeError: Assignment to constant variable.
++a;  // Uncaught TypeError: Assignment to constant variable.

对于数组和对象来说,值是可以改变的:

const arr  = ["a","b","c"];
arr.push("d");
arr.pop();

那么什么时候使用 const 呢? 在一些不需要重复赋值的场合可以用:

const provinces = [];
const months = [];

总而言之,多用 let 和 const,少用 var 。

块作用域和let 声明

相比函数作用域,块作用域允许我们通过if,for,while声明创建新作用域,甚至任意创建{}块也能创建新的作用域:

{{{{{ var deep = 'This is available from outer scope.'; }}}}}
console.log(deep)
// <- 'This is available from outer scope.'

由于这里使用的是var,考虑到变量提升的存在,我们在外部依旧可以读取到深层中的deep变量,这里并不会报错。不过在以下情况下,我们可能希望这里会报错:

  • 访问内部变量会打破我们代码中的某种封装原则;
  • 父块中已有有一个一个同名变量,但是内部也需要用同名变量;

使用let就可以解决这个问题,let 创建的变量在块作用域内有效,在ES6提出let以前,想要创建深层作用域的唯一办法就是再新建一个函数。使用let,你只需添加另外一对{}

let topmost = {}
{
  let inner = {}
  {
    let innermost = {}
  }
  // attempts to access innermost here would throw
}
// attempts to access inner here would throw
// attempts to access innermost here would throw

for循环中使用let是一个很好的实践,这样定义的变量只会在当前块作用域内生效。

for (let i = 0; i < 2; i++) {
  console.log(i)
  // <- 0
  // <- 1
}
console.log(i)
// <- i is not defined

考虑到let声明的变量在每一次循环的过程中都重复声明,这在处理异步函数时就很有效,不会发生使用var时产生的诡异的结果,我们看一个具体的例子。

我们先看看 var 声明的变量是怎么工作的,下述代码中 i变量 被绑定在 printNumber 函数作用域中,当每个回调函数被调用时,它的值会逐步升到10,但是当每个回调函数运行时(每100us),此时的i的值已经是10了,因此每次打印的结果都是10.

function printNumbers() {
  for (var i = 0; i < 10; i++) {
    setTimeout(function () {
      console.log(i)
    }, i * 100)
  }
}
printNumbers()

使用let,则会把i绑定到每一个块作用域中。每一次循环 i 的值还是在增加,但是每次其实都是创建了一个新的 i ,不同的 i 之间不会相互影响 ,因此打印出的就是预想的0到9了。

function printNumbers() {
  for (let i = 0; i < 10; i++) {
    setTimeout(function () {
      console.log(i)
    }, i * 100)
  }
}
printNumbers()

为了细致的讲述let的工作原理, 我们还需要弄懂一个名为 Temporal Dead Zone 的概念。

var、let 与 const

Const 声明

const声明也具有类似let的块作用域,它同样具有TDZ机制。实际上,TDZ机制是因为const才被创建,随后才被应用到let声明中。const需要TDZ的原因是为了防止由于变量提升,在程序解析到const语句之前,对const声明的变量进行了赋值操作,这样是有问题的。

下面的代码表明,const具有和let一致的块作用域:

const pi = 3.1415
{
  const pi = 6
  console.log(pi)
  // <- 6
}
console.log(pi)
// <- 3.1415

下面我们说说constlet的主要区别,首先const声明的变量在声明时必须赋值,否则会报错:

const pi = 3.1415
const e // SyntaxError, missing initializer

除了必须初始化,被const声明的变量不能再被赋予别的值。在严格模式下,试图改变const声明的变量会直接报错,在非严格模式下,改变被静默被忽略。

const people = ['Tesla', 'Musk']
people = []
console.log(people)
// <- ['Tesla', 'Musk']

请注意,const声明的变量并非意味着,其对应的值是不可变的。真正不能变的是对该值的引用,下面我们具体说明这一点。

模板字符串

模板字符串为 Vue 的组件模板定义带来了巨大的便利,在此之前,需要这样定义一个模板:

let template = '<div class="container"><p>Foo</p></div>';

如果要写成多行,可以用反斜杠:

let template = '<div class="container">
                    <p>Foo</p>
                </div>';

或者使用数组形式:

let template = [
    '<div class="container">',
    '<p>Foo</p>',
    '</div>'
].join('');

如果要嵌入变量,可以写成:

let name = "jack";
let template = `<div class="container"><p>` + name + '</p></div>';

而使用模板字符串,则可以方便的在多行里面编写模板:

let template = `
    <div class="container">
        <p>Foo</p>
    </div>
`

由于模板字符串的空格和换行会被保留,为了不让首行多出换行符,可以写成:

let template = `<div class="container">
                            <p>Foo</p>
                        </div>
                    `

或者使用 trim() 方法从字符串中移除 前导 空格、尾随空格和行终止符。

let template = `
    <div class="container">
        <p>Foo</p>
    </div>
`.trim();

模板字符串嵌入变量或者表达式的方式也很简单:

let name = "jack";
let template = `
    <div class="container">
        <p>${name} is {100 + 100}</p>
    </div>
`.trim();

标记模板

默认情况下,JavaScript会把解析为转义符号,对浏览器来说,以开头的字符一般具有特殊的含义。比如说n意味着新行,u00f1表示ñ等等。如果你不想浏览器执行这种特殊解析,你也可以使用String.raw来标记模板。下面的代码就是这样做的,这里我们使用了String.row来处理模板字符串,相应的这里面的n没有被解析为新行。

var text = String.raw`"n" is taken literally.
It'll be escaped instead of interpreted.`
console.log(text)
// "n" is taken literally.
// It'll be escaped instead of interpreted.

我们添加在模板字符串之前的String.raw前缀,这就是标记模板,这样的模板字符串在被渲染前被该标记代表的函数预处理。

一个典型的标记模板字符串如下:

tag`Hello, ${ name }. I am ${ emotion } to meet you!`

实际上,上面标记模板可以用以下函数形式表示:

tag(
  ['Hello, ', '. I am ', ' to meet you!'],
  'Maurice',
  'thrilled'
)

我们还是用代码来说明这个概念,下述代码中,我们先定义一个名为tag函数:

function tag(parts, ...values) {
  return parts.reduce(
    (all, part, index) => all + values[index - 1] + part
  )
}

然后我们调用使用使用标记模板,不过此时的结果和不使用标记模板是一样的,这是因为我们定义的tag函数实际上并未对字符串进行额外的处理。

var name = 'Maurice'
var emotion = 'thrilled'
var text = tag`Hello, ${ name }. I am ${ emotion } to meet you!`
console.log(text)
// <- 'Hello Maurice, I am thrilled to meet you!'

我们看一个进行额外处理的例子,比如转换所有用户输入的值为大写(假设用户只会输入英语),这里我们定义标记函数upper来做这件事:

function upper(parts, ...values) {
  return parts.reduce((all, part, index) =>
    all + values[index - 1].toUpperCase() + part
  )
}
var name = 'Maurice'
var emotion = 'thrilled'
upper`Hello, ${ name }. I am ${ emotion } to meet you!`
// <- 'Hello MAURICE, I am THRILLED to meet you!'

既然可以转换输入为大写,那我们再进一步想想,如果提供合适的标记模板函数,使用标记模板,我们还可以对模板中的表达式进行各种过滤处理,比如有这么一个场景,假设表达式的值都来自用户输入,假设有一个名为sanitize的库可用于去除用户输入中的html标签,那通过使用标记模板,就可以有效的防止XSS攻击了,使用方法如下。

function sanitized(parts, ...values) {
  return parts.reduce((all, part, index) =>
    all + sanitize(values[index - 1]) + part
  )
}
var comment = 'Evil comment<iframe src="http://evil.corp">
    </iframe>'
var html = sanitized`<div>${ comment }</div>`
console.log(html)
// <- '<div>Evil comment</div>'

ES6中的另外一个大的改变是提供了新的变量声明方式:letconst声明,下面我们一起来学习。

解构赋值

解构赋值可以方便的取到对象的可遍历属性:

let person = {
    firstname : "steve",
    lastname : "curry",
    age : 29,
    sex : "man"
};

let {firstname, lastname} = person;
console.log(firstname, lastname);

// 等价于
// let firstname = person.firstname;
// let lastname = person.lastname;

可以将其用于函数传参中:

function greet({firstname, lastname}) {
    console.log(`hello,${firstname}.${lastname}!`);
};
greet({
    firstname: 'steve',
    lastname: 'curry'
});

 

函数默认参数

在ES6中,我们可以给函数的参数添加默认值了,下例中我们就给参数 exponent 分配了一个默认值:

function powerOf(base, exponent = 2) {
  return Math.pow(base, exponent)
}

箭头函数同样支持使用默认值,需要注意的是,就算只有一个参数,如果要给参数添加默认值,参数部分一定要用小括号括起来。

var double = (input = 0) => input * 2

我们可以给任何位置的任何参数添加默认值。

function sumOf(a = 1, b = 2, c = 3) {
  return a + b + c
}
console.log(sumOf(undefined, undefined, 4))
// <- 1 + 2 + 4 = 7

在JS中,给一个函数提供一个包含若干属性的对象字面量做为参数的情况并不常见,不过你依旧可以按下面方法这样做:

var defaultOptions = { brand: 'Volkswagen', make: 1999 }
function carFactory(options = defaultOptions) {
  console.log(options.brand)
  console.log(options.make)
}
carFactory()
// <- 'Volkswagen'
// <- 1999

不过这样做存在一定的问题,当你调用该函数时,如果传入的参数对象只包含一个属性,另一个属性的默认值会自动失效:

carFactory({ make: 2000 })
// <- undefined
// <- 2000

函数参数解构就可以解决这个问题。

剩余参数rest

使用rest, 你只需要在任意JavaScript函数的最后一个参数前添加三个点...即可。当rest参数是函数的唯一参数时,它就代表了传递给这个函数的所有参数。它起到和前面说的.slice一样的作用,把参数转换为了数组,不需要你再对arguments进行额外的转换了。

function join(...list) {
  return list.join(', ')
}
join('first', 'second', 'third')
// <- 'first, second, third'

rest参数之前的命名参数不会被包含在rest中,

function join(separator, ...list) {
  return list.join(separator)
}
join('; ', 'first', 'second', 'third')
// <- 'first; second; third'

在箭头函数中使用rest参数时,即使只有这一个参数,也需要使用圆括号把它围起来,不然就会报错SyntaxError,使用示例如下:

var sumAll = (...numbers) => numbers.reduce(
  (total, next) => total + next
)
console.log(sumAll(1, 2, 5))
// <- 8

上述代码的ES5实现如下:

// ES5的写法
function sumAll() {
  var numbers = Array.prototype.slice.call(arguments)
  return numbers.reduce(function (total, next) {
    return total + next
  })
}
console.log(sumAll(1, 2, 5))
// <- 8

constlet的优点

新功能并不应该因为是新功能而被使用,ES6语法被使用的前提是它可以显著的提升我们代码的可读写和可维护性。let声明在大多数情况下,可以替换var以避免预期之外的问题。使用let你可以把声明在块的顶部进行而非函数的顶部进行。

有时,我们希望有些变量的引用不可变,这时候使用const就能防止很多问题的发生。下述代码中 在checklist函数外给items变量传递引用时就非常容易出错,它返回的todo API和items有了交互。当items变量被改为指向另外一个列表时,我们的代码就出问题了。todo API 用的还是items之前的值,items本身的指代则已经改变。

var items = ['a', 'b', 'c']
var todo = checklist(items)
todo.check()
console.log(items)
// <- ['b', 'c']
items = ['d', 'e']
todo.check()
console.log(items)
// <- ['d', 'e'], would be ['c'] if items had been constant
function checklist(items) {
  return {
    check: () => items.shift()
  }
}

这类问题很难debug,找到问题原因就会花费你很长一段时间。使用const运行时就会报错,可以帮助你可以避免这种问题。

如果我们默认只使用cosntlet声明变量,所有的变量都会有一样的作用域规则,这让代码更易理解,由于const造成的影响最小,它还曾被提议作为默认的变量声明。

总的来说,const不允许重新指定值,使用的是块作用域,存在TDZ。let则允许重新指定值,其它方面和const类似,而var声明使用函数作用域,可以重新指定值,可以在未声明前调用,考虑到这些,推荐尽量不要使用var声明了。

数组解构

数组解构的语法和对象解构是类似的。区别在于,数组解构我们使用中括号而非花括号,下面的代码中,通过结构,我们在数组coordinates中提出了变量 x,y 。 你不需要使用x = coordinates[0]这样的语法了,数组解构不使用索引值,但却让你的代码更加清晰。

var coordinates = [12, -7]
var [x, y] = coordinates
console.log(x)
// <- 12

数组解构也允许你跳过你不想用到的值,在对应地方留白即可:

var names = ['James', 'L.', 'Howlett']
var [ firstName, , lastName ] = names
console.log(lastName)
// <- 'Howlett'

和对象解构一样,数组解构也允许你添加默认值:

var names = ['James', 'L.']
var [ firstName = 'John', , lastName = 'Doe' ] = names
console.log(lastName)
// <- 'Doe'

在ES5中,你需要借助第三个变量,才能完成两个变量值的交换,如下:

var left = 5, right = 7;
var aux = left
left = right
right = aux

使用解构,一切就简单多了:

var left = 5, right = 7;
[left, right] = [right, left]

我们再看看函数解构。

词法作用域

我们在箭头函数的函数体内使用的this,arguments,super等都指向包含箭头函数的上下文,箭头函数本身不产生新的上下文。下述代码中,我们创建了一个名为timer的对象,它的属性seconds用以计时,方法start用以开始计时,若我们在若干秒后调用start方法,将打印出当前的seconds值。

// ES5
var timer = {
  seconds: 0,
  start() {
    setInterval(function(){
      this.seconds++
    }, 1000)
  }
}

timer.start()
setTimeout(function () {
  console.log(timer.seconds)
}, 3500)

> 0

// ES6
var timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++
    }, 1000)
  }
}

timer.start()
setTimeout(function () {
  console.log(timer.seconds)
}, 3500)
// <- 3

第一段代码中start方法使用的是常规的匿名函数定义,在调用时this将指向了windowconsole出的结果为undefined,想要让代码正常工作,我们需要在start方法开头处插入var self = this,然后替换匿名函数函数体中的thisself,第二段代码中,我们使用了箭头函数,就不会发生这种情况了。

还需要说明的是,箭头函数的作用域也不能通过.call,.apply,.bind等语法来改变,这使得箭头函数的上下文将永久不变。

我们再来看另外一个箭头函数与普通匿名函数的不同之处,你猜猜,下面的代码最终打印出的结果会是什么:

function puzzle() {
  return function () {
    console.log(arguments)
  }
}
puzzle('a', 'b', 'c')(1, 2, 3)

答案是1,2,3,原因是对常规匿名函数而言,arguments指向匿名函数本身。

作为对比,我们看看下面这个例子,再猜猜,打印结果会是什么?

function puzzle() {
  return ()=>{
    console.log(arguments)
  }
}
puzzle('a', 'b', 'c')(1, 2, 3)

答案是a,b,c,箭头函数的特殊性决定其本身没有arguments对象,这里的arguments其实是其父函数puzzle的。

前面我们提到过,箭头函数还可以简写,接下来我们一起看看。

在字符串中插值

通过模板字符串,你可以在模板中插入任何JavaScript表达式了。当解析到表达式时,表达式会被执行,该处将渲染表达式的值,下例中,我们在字符串中插入了变量name

var name = 'Shannon'
var text = `Hello, ${ name }!`
console.log(text)
// <- 'Hello, Shannon!'

模板字符串是支持任何表达式的。使用模板字符串,代码将更容易维护,你无须再手动连接字符串和JavaScript表达式了。

看下面插入日期的例子,是不是又直观又方便:

`The time and date is ${ new Date().toLocaleString() }.`
// <- 'the time and date is 8/26/2015, 3:15:20 PM'

表达式中还可以包含数学运算符:

`The result of 2+3 equals ${ 2 + 3 }`
// <- 'The result of 2+3 equals 5'

鉴于模板字符串本身也是JavaScript表达式,我们在模板字符串中还可以嵌套模板字符串;

`This template literal ${ `is ${ 'nested' }` }!`
// <- 'This template literal is nested!'

模板字符串的另外一个优点是支持多行字符串;

本文由金沙官网线上发布于Web前端,转载请注明出处:重新认识ES6中的语法糖金沙官网线上:

您可能还会对下面的文章感兴趣: