ES6常用的新语法学习笔记

简介

ES6是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

1.let和const

let

let和var 差不多,都是用来声明变量,但是let的声明只能在{}内,以下代码输入都是10

var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = function () {//将a[i]赋予一个方法,输出i
        console.log(i);
    };
}a[2](); // 10

但是如果把var i=0换成let i=0;的话当调用a[数字i的范围]的时候输出的就是你填入的数字,把let声明理解为局部对象,像下面这段代码:

for (let i = 0; i < 3; i++) {

  let i = 'abc';

  console.log(i);}

只会输出三次abd,这个输出的i是最近的i,而不是循环条件中的i

另外,如果使用var未定义的对象会输出undefined,而使用let的话则会报错

 

暂时性死区

var tmp = 123;
if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
}

上方代码会tmp is not defined的错误,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

 

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

 

let不允许在相同作用域内{},重复声明同一个变量。

// 报错function func() {

  let a = 10;

  var a = 1;}

// 报错function func() {

  let a = 10;

  let a = 1;}

 

 

允许在块级作用域内声明函数。

函数声明类似于var,即会提升到全局作用域或函数作用域的头部。

同时,函数声明还会提升到所在的块级作用域的头部。

像以下代码执行只会输出 I am inside

function f() {
    console.log('I am outside!');
}
(function () {
    function f() {
        console.log('I am inside!');
    }
    if (false) {
    }
    f();
}());

 

const命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。const一旦声明变量,就必须立即初始化,不能留到以后赋值。对于const来说,只声明不赋值,就会报错。

const声明的常量,也与let一样不可重复声明。

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功foo.prop = 123;

foo.prop // 123

// 将 foo 指向另一个对象,就会报错foo = {}; // TypeError: "foo" is read-only

ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。

var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

 

2.变量的解构赋值

数组的解构赋值

es6允许写成这样:let [a, b, c] = [1, 2, 3];

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。

 

let [x, y, z] = new Set(['a', 'b', 'c']);

x // "a"

这是一个set集合,事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

 

ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

 

let [x = 1] = [undefined];

x // 1let [x = 1] = [null];

x // null

上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined

 

对象的解构赋值

let { foo, bar } = { foo: "aaa", bar: "bbb" };

foo // "aaa"bar // "bbb"

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

 

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };

baz // "aaa"

let obj = { first: 'hello', last: 'world' };let { first: f, last: l } = obj;

f // 'hello'

l // 'world'

 

let { foo: baz } = { foo: "aaa", bar: "bbb" };

baz // "aaa"foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

对象的解构也可以指定默认值。默认值生效的条件是,对象的属性值严格等于undefined

var {x = 3} = {x: undefined};

x // 3

var {x = 3} = {x: null};

x // null

上面代码中,属性x等于null,因为nullundefined不严格相等,所以是个有效的赋值,导致默认值3不会生效。

如果解构失败,变量的值等于undefined

let {foo} = {bar: 'baz'};

foo // undefined

 

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';

a // "h"

b // "e"

c // "l"

d // "l"

e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';

len // 5

数值和布尔值的解构赋值 

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象

let {toString: s} = 123;

s === Number.prototype.toString // true

let {toString: s} = true;

s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError

let { prop: y } = null; // TypeError

 

函数参数的解构赋值

函数的参数也可以使用解构赋值。

function add([x, y]){

  return x + y;}

add([1, 2]); // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量xy。对于函数内部的代码来说,它们能感受到的参数就是xy

下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);

// [ 3, 7 ]

 

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

 

用途

(1)交换变量的值

let x = 1;let y = 2;

[x, y] = [y, x];

(2)从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

// 返回一个数组

function example() {

  return [1, 2, 3];

}let [a, b, c] = example();

// 返回一个对象

function example() {

  return {

    foo: 1,

    bar: 2

  };}

let { foo, bar } = example();

任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();

map.set('first', 'hello');

map.set('second', 'world');

for (let [key, value] of map) {

  console.log(key + " is " + value);}

// first is hello// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。

// 获取键名for (let [key] of map) {

  // ...}

// 获取键值for (let [,value] of map) {

  // ...}

 

3.字符串的扩展

使得字符串可以被for...of循环遍历。把这个字符串当做集合遍历输出

for (let codePoint of 'foo') {

  console.log(codePoint)

}

 

String 的新方法:

includes():返回布尔值,表示是否找到了参数字符串。

startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。

endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

这三个方法都支持第二个参数,表示开始搜索的位置。

repeat方法返回一个新字符串,表示将原字符串重复n次。

 

padStart()用于头部补全,padEnd()用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

'x'.padEnd(4, 'ab') // 'xaba'

如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'    'xxx'.padEnd(2, 'ab') // 'xxx'

matchAll方法返回一个正则表达式在当前字符串的所有匹配

String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串:

String.raw`Hi\n${2+3}!`;

// 返回 "Hi\\n5!"

String.raw`Hi\u000A!`;

// 返回 "Hi\\u000A!"

 

模板字符串 

$('#result').append(

  'There are <b>' + basket.count + '</b> ' +

  'items in your basket, ' +

  '<em>' + basket.onSale +

  '</em> are on sale!');

上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。

$('#result').append(`

  There are <b>${basket.count}</b> items

   in your basket, <em>${basket.onSale}</em>

  are on sale!

`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。模板字符串之中还能调用函数:

function fn() {

  return "Hello World";}

`foo ${fn()} bar`

    

 

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

上面代码在模板字符串之中,放置了一个常规模板。该模板使用<%...%>放置 JavaScript 代码,使用<%= ... %>输出 JavaScript 表达式。

 

4.数值的扩展

Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

对于非数值,Math.trunc内部使用Number方法将其先转为数值。

Math.trunc('123.456') // 123

Math.trunc(true) //1

Math.trunc(false) // 0

Math.trunc(null) // 0

对于空值和无法截取整数的值,返回NaN。

Math.trunc(NaN);      // NaN

Math.trunc('foo');    // NaN

Math.trunc();         // NaN

Math.trunc(undefined) // NaN

Math.sign()

Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

它会返回五种值。

  • 参数为正数,返回+1;
  • 参数为负数,返回-1;
  • 参数为 0,返回0;
  • 参数为-0,返回-0;
  • 其他值,返回NaN。

Math.cbrt()

Math.cbrt方法用于计算一个数的立方根。

Math.cbrt(-1) // -1Math.cbrt(0)  // 0Math.cbrt(1)  // 1Math.cbrt(2)  // 1.2599210498948734

对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。

Math.cbrt('8') // 2Math.cbrt('hello') // NaN

 

Math.clz32()

JavaScript 的整数使用 32 位二进制形式表示,Math.clz32方法返回一个数的 32 位无符号整数形式有多少个前导 0。

Math.clz32(0) // 32Math.clz32(1) // 31Math.clz32(1000) // 22Math.clz32(0b01000000000000000000000000000000) // 1Math.clz32(0b00100000000000000000000000000000) // 2

Math.imul()

Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。

Math.imul(2, 4)   // 8Math.imul(-1, 8)  // -8Math.imul(-2, -2) // 4

Math.fround()

Math.fround方法返回一个数的32位单精度浮点数形式。

对于32位单精度格式来说,数值精度是24个二进制位(1 位隐藏位与 23 位有效位),所以对于 -224 至 224 之间的整数(不含两个端点),返回结果与参数本身一致。

Math.hypot()

Math.hypot方法返回所有参数的平方和的平方根。

指数运算符

ES2016 新增了一个指数运算符(**)。

2 ** 2 // 4

2 ** 3 // 8

这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)

2 ** 3 ** 2

// 512

上面代码中,首先计算的是第二个指数运算符,而不是第一个。

指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

let a = 1.5;

a **= 2;

// 等同于 a = a * a;

let b = 4;

b **= 3;

// 等同于 b = b * b * b;

在js中的<<和>>位运算符也能使用

 

5.函数的扩展

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {

  console.log(x, y);}

log('Hello') // Hello Worldlog('Hello', 'China') // Hello Chinalog('Hello', '') // Hello

 

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。和java中的类似

function add(...values) {

  let sum = 0;

  for (var val of values) {

    sum += val;

  }

  return sum;}

add(2, 5, 3) // 10

上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。

函数的name属性返回函数的名字.

 

箭头函数

var f = v => v;

// 等同于

var f = function (v) {

  return v;

};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;

// 等同于var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;

// 等同于var sum = function(num1, num2) {

  return num1 + num2;};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;

// 等同于

var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;

// 等同于

var sum = function(num1, num2) {

  return num1 + num2;

};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

 

 

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错

// 报错let getTempItem = id => { id: id, name: "Temp" };

// 不报错let getTempItem = id => ({ id: id, name: "Temp" });

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn();

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同于function full(person) {

  return person.first + ' ' + person.last;}

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest (...var)参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

function foo() {

  setTimeout(() => {

    console.log('id:', this.id);

  }, 100);}

var id = 21;

foo.call({ id: 42 });

// id: 42

上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

function Timer() {

  this.s1 = 0;

  this.s2 = 0;

  // 箭头函数  

setInterval(() => this.s1++, 1000);

  // 普通函数  

setInterval(function () {

    this.s2++;

  }, 1000);}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);

setTimeout(() => console.log('s2: ', timer.s2), 3100);

// s1: 3// s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。

什么是尾调用?

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。但是必须是一个函数最后的一个操作是return 另一个函数才叫尾调用.

比如:函数mn都属于尾调用,因为它们都是函数f的最后一步操作。

function f(x) {

  if (x > 0) {

    return m(x)

  }

  return n(x);}

尾调用优化

function f() {

  let m = 1;

  let n = 2;

  return g(m + n);}

f();

// 等同于function f() {

  return g(3);}

f();

// 等同于g(3);

如果函数g不是尾调用,函数f就需要保存内部变量mn的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。

这就叫做“尾调用优化”

6.数组的扩展

扩展运算符 

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

该运算符主要用于函数调用。

function push(array, ...items) {

  array.push(...items);}

function add(x, y) {

  return x + y;}

 

const numbers = [4, 38];add(...numbers) // 42

上面代码中,array.push(...items)add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

 

7.class, extends, super

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念。新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

 

上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实例对象可以共享的。  extends和super和java中的类似,就不说了

8.template string

 

大家可以先看下面一段代码:

$("#result").append(

  "There are <b>" + basket.count + "</b> " +

  "items in your basket, " +

  "<em>" + basket.onSale +

  "</em> are on sale!"

);

我们要用一堆的'+'号来连接文本与变量,而使用ES6的新特性模板字符串``后,我们可以直接这么来写:

$("#result").append(`

  There are <b>${basket.count}</b> items

   in your basket, <em>${basket.onSale}</em>

  are on sale!

`);

用反引号(\)来标识起始,用${}`来引用变量,而且所有的空格和缩进都会被保留在输出之中

9.import export

假设我们有两个js文件: index.js和content.js,现在我们想要在index.js中使用content.js返回的结果,我们要怎么做呢?

//index.js

import animal from './content'

//content.js

export default 'A cat'

ES6 module的其他高级用法

//content.js

export default 'A cat'  

  export function say(){

    return 'Hello!'

}    

export const type = 'dog' 

上面可以看出,export命令除了输出变量,还可以输出函数,甚至是类(react的模块基本都是输出类)

 

//index.js

import { say, type } from './content' 

 let says = say()

console.log(`The ${type} says ${says}`)  //The dog says Hello

这里输入的时候要注意:大括号里面的变量名,必须与被导入模块(content.js)对外接口的名称相同。

如果还希望输入content.js中输出的默认值(default), 可以写在大括号外面。

 

 

猜你喜欢

转载自blog.csdn.net/qq_41594146/article/details/83382172
今日推荐