es6,es7新特性总结

简介: ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ES6给我们提供了很多js的新特性和规范,使得我们编写Js代码更加灵活和强大,接下来让我们来学习一下吧。

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


ES6给我们提供了很多js的新特性和规范,使得我们编写Js代码更加灵活和强大,接下来让我们来学习一下吧。


let和const


let声明的变量只在它所在的代码块有效。for循环计数很适合此变量


for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

不存在变量提升


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

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

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

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

ES6             的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错


const声明一个只读的常量。一旦声明,常量的值就不能改变


const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值

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


// 将对象彻底冻结
var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。


变量的解构赋值


解构赋值允许指定默认值

let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

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

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

对象的解构


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

// p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量

let { log, sin, cos } = Math;

字符串的解构赋值


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

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
// 获取字符串长度
let {length : len} = 'hello';
len // 5

字符串扩展


字符基础


JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为它们是两个字符。


汉字“𠮷”的码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript 不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。


字符串的遍历器接口


ES6 为字符串添加了遍历器接口,使得字符串可以被for…of循环遍历。


includes(), startsWith(), endsWith()


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

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

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


let d = 'Hello world!';
// 第二个参数表示开始搜索的位置,endsWith第二个参数不同,针对前n个字符
d.startsWith('world', 6) // true
d.endsWith('hello', 5) // true
d.includes('Hello', 6) // false

repeat()


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

如果repeat的参数是负数或者Infinity,会报错。

如果repeat的参数是字符串,则会先转换成数字

参数NaN等同于 0

'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'aa'.repeat(2.3) // 'aaaa'  参数如果是小数会被取整

padStart(),padEnd() 字符串补全

'x'.padStart(5, 'ab') // 'ababx'
'x'.padEnd(4, 'ab') // 'xaba'
// 如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串
'ssss'.padStart(3,'dd') // 'ssss'
// 如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串
'abc'.padStart(10, '0123456789')   // '0123456abc'
// 如果省略第二个参数,默认使用空格补全长度
'ss'.padStart(10)  // '    ss'

模板字符串 ${}


大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性

模板字符串之中还能调用函数

// 简单的模板函数
const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// <table>
//
//   <tr><td><Jane></td></tr>
//   <tr><td>Bond</td></tr>
//
//   <tr><td>Lars</td></tr>
//   <tr><td><Croft></td></tr>
//
// </table>

标签模板


tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推

let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;
function passthru(literals) {
  let result = '';
  let i = 0;
  while (i < literals.length) {
    result += literals[i++];
    if (i < arguments.length) {
      result += arguments[i];
    }
  }
  return result;
}
msg // "The total is 30 (31.5 with tax)"

“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容

let message =
  SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let arg = String(arguments[i]);
    // Escape special characters in the substitution.
    s += arg.replace(/&/g, "&")
            .replace(/</g, "<")
            .replace(/>/g, ">");
    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

String.raw()


String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用

String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"
`Hi\n${2+3}!`  // 此时换行符/n会生效
// 返回 "Hi
//      n5!"

正则的扩展


u 修饰符


ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码


y 修饰符


y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

单单一个y修饰符对match方法,只能返回第一个匹配,必须与g修饰符联用,才能返回所有匹配。


'a1a2a3'.match(/a\d/y) // ["a1"]
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]

y修饰符的一个应用,是从字符串提取 token(词元),y修饰符确保了匹配之间不会有漏掉的字符。

const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
const TOKEN_G  = /\s*(\+|[0-9]+)\s*/g;
tokenize(TOKEN_Y, '3 + 4')
// [ '3', '+', '4' ]
tokenize(TOKEN_G, '3 + 4')
// [ '3', '+', '4' ]
function tokenize(TOKEN_REGEX, str) {
  let result = [];
  let match;
  while (match = TOKEN_REGEX.exec(str)) {
    result.push(match[1]);
  }
  return result;
}
tokenize(TOKEN_Y, '3x + 4')
// [ '3' ]
tokenize(TOKEN_G, '3x + 4')
// [ '3', '+', '4' ]

sticky 属性



表示是否设置了y修饰符 ,返回true/false

flags 属性



返回正则表达式的修饰符

如何让 . 匹配包括换行符(行终止符)的所有字符


//U+000A 换行符(\n)
//U+000D 回车符(\r)
//U+2028 行分隔符(line separator)
//U+2029 段分隔符(paragraph separator)
/foo[^]bar/.test('foo\nbar') // true

ES5先行断言


”先行断言“指的是,x只有在y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。”先行否定断言“指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。

ES5组匹配


正则表达式使用圆括号进行组匹配


const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31

数值扩展


Number.isFinite()


Number.isFinite()用来检查一个数值是否为有限的(finite),如果参数类型不是数值,一律返回false


Number.isNaN()


用来检查一个值是否为NaN


Number.parseInt(), Number.parseFloat()


ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。


Number.isInteger()


Number.isInteger()用来判断一个数值是否为整数。

如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数,


// 由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。
Number.isInteger(3.0000000000000002) // true

安全整数和 Number.isSafeInteger()


JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值

ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内


Math 对象的扩展


Math.trunc()


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

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

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


// 对于没有部署这个方法的环境,可以用下面的代码模拟
Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};

指数运算符(**)


注意,在 V8 引擎中,指数运算符与Math.pow的实现不相同,对于特别大的运算结果,两者会有细微的差异。


let a = 1.5;
a **= 2;
// 等同于 a = a * a;
Math.pow(99, 99)
// 3.697296376497263e+197
99 ** 99
// 3.697296376497268e+197

函数扩展


函数参数的默认值


function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

参数默认值的位置


通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。


function f(x, y = 5, z) {
  return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]

函数的 length 属性


指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。


(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
// rest 参数不会计入length属性
(function(...args) {}).length // 0
// 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

箭头函数


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


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

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


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


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


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


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

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


this指向的固定化


this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数


尾调用优化


调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数


function f(x){
  return g(x);
}

尾递归


函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。


下面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
factorial(5) // 120
如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}
factorial(5, 1) // 120

尾递归优化



它的原理非常简单。尾递归之所以需要优化,原因是调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出。怎么做可以减少调用栈呢?就是采用“循环”换掉“递归”。


蹦床函数


function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}
// 然后,要做的就是将原来的递归函数,改写为每一步返回另一个函数。
function sum(x, y) {
  if (y > 0) {
    return sum.bind(null, x + 1, y - 1);
  } else {
    return x;
  }
}
trampoline(sum(1, 100000))

真正的尾递归优化

unction tco(f) {
  var value;
  var active = false;
  var accumulated = [];
  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}
var sum = tco(function(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1)
  }
  else {
    return x
  }
});
sum(1, 100000)
// 100001


目录
相关文章
|
6月前
|
JavaScript 前端开发 开发者
ES6的常用新特性17条
ES6,也称为ECMAScript 2015,是JavaScript语言的一个重大更新,它引入了许多新的语法特性。以下是ES6的一些主要语法
|
2天前
|
前端开发
ES6 中常用的新特性
ES6 中常用的新特性
|
3月前
|
JavaScript 前端开发
ES6新特性(二)
ES6新特性(二)
|
6月前
|
JavaScript 前端开发 开发者
es6的新特性
es6的新特性
55 7
|
JavaScript 前端开发 网络架构
关于ES6新特性的总结 1
关于ES6新特性的总结
53 0
|
6月前
|
JavaScript
ES6 新特性 ES6使用 超实用
ES6 新特性 ES6使用 超实用
|
JavaScript 前端开发 开发者
ES6 新特性
这是每个前端开发者都应该掌握的技能。ES6是JavaScript的一个重要的版本升级,它带来了许多新特性和语法糖,让我们的代码更加简洁高效。无论你是新手还是有经验的开发者,掌握ES6都会让你的编码变得更加愉快和高效。那么,让我们开始吧!
|
前端开发
关于ES6新特性的总结 2
关于ES6新特性的总结
40 0
|
Java
es6中简单常用的新特性
es6中简单常用的新特性
54 0
|
前端开发 JavaScript
es6的8条新特性总结
es6的8条新特性总结
134 0