(持续更新...)ES6学习笔记(一)

参考文献:ECMAScript 6 入门 — 阮一峰
引申阅读:Unicode编码方案概述

提醒自己:ES6中不但新增了许多新语法新概念,重要的是还新增了很多API,所以平时在开发中要多查多用!

2015年后的JS标准都可称为ES6


ES5中实行的是 提升 的原则,ES6中不允许 提升,所以在声明之前访问变量都是报错的(let、const等)

变量提升:

var tmp = new Date();
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
f(); // undefined

这段代码本质上是这样的:

var tmp = new Date();
function f() {
  var tmp = undefined;
  console.log(tmp);
  if (false) {
    tmp = 'hello world';
  }
}
f(); // undefined

函数提升:

function f() { console.log('I am outside!'); }
(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }
  f();
}());

这段代码运行在ES5环境中,本质上是这样的:

// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
  function f() { console.log('I am inside!'); } // 把整个函数声明给提升了
  if (false) {
  }
  f(); // 'I am inside!'
}());
// ES6 环境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined; // 是这样提升的,好像更符合我们的理解吧
  if (false) {
    function f() { console.log('I am inside!'); }
  }
  f();
}());
// Uncaught TypeError: f is not a function

  • 顶层对象:

var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

② 在浏览器中顶层对象是window对象,在Node中是global对象。在不同的环境中,顶层对象是不一样的,可不可以统一下?

垫片库system.global模拟了这个提案,可以在所有环境拿到global

// CommonJS 的写法
require('system.global/shim')();
// ES6 模块的写法
import shim from 'system.global/shim'; shim();

上面代码可以保证各种环境里面,global对象都是存在的。

// CommonJS 的写法
var global = require('system.global')();

// ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();

上面代码将顶层对象放入变量global


解构

可遍历的对象才可以解构,比如Object对象(内部属性可遍历)、数组等。

所谓的解构,就是按照对象的结构,将各个变量组合起来,ES6会根据对应关系,一一给这些变量赋值。数组是按照先后顺序依次解构,对象是根据属性名进行解构

对象解构的本质let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。所以,你可以简写成let { foo, bar } = { foo: "aaa", bar: "bbb" };你如果想用其他的变量名,可以这样let { foo: f, bar: b } = { foo: "aaa", bar: "bbb" };f的值就是aaab的值就是bbb

// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

// 正确的写法
let x;
({x} = {x: 1});
知识点引申:最外层如果是{},代表一个代码块;最外层如果是(),代表一个表达式,或语句。
  • 字符串解构

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello'; //字符串对象有 length 属性
len // 5
  • 数值和布尔值的解构赋值

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
  • 函数参数解构赋值

需要特别注意以下这种情况,易出错:

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

上面定义move函数时,包含了两层意思:如果调用move函数时传入参数,则参数按照{x = 0, y = 0}解构;如果调用move函数时未传入参数,则将{}作为默认参数

变化一下:(换汤不换药)

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面定义move函数时,包含了两层意思:如果调用move函数时传入参数,则参数按照{x, y}解构;如果调用move函数时未传入参数,则将{ x: 0, y: 0 }作为默认参数

  • 圆括号的使用

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

啥是赋值语句,啥是声明语句?
答:let a = 1;就是声明语句,a = 1;就是赋值语句;函数声明中的参数声明也是声明语句

啥是模式,啥是非模式?
答:像{ o: { p: n } } = { o: { p: 2 } }; 中,op就是模式, n就是非模式,也就是说,匹配对象中属性名的就是模式部分,和值相关的(本质上就是你声明的变量名)就是非模式部分。

字符串扩展

ES6中对ES5中不能正确处理码点大于0xFFFF的字符进行了优化

没搞明白为啥"\uD842\uDFB7"==="\u{20BB7}"true,而且他们都代表汉字 ""(这个汉字由两个字符组成),他们的二进制表示明明不同啊!有知道原因的吗?(后续:终于搞明白了,参考文献:Unicode编码方案概述,简单来讲就是在Unicode编码中,为全世界所有的字符都指定了唯一的编号,但是前655362^16\uFFFF个编号对应了世界上最常用的字符,同时在这前65536个编号的0xD800-0xDFFF(十进制55296~57343)这个区间是特殊的,我们可以用这个区间的两个编号表示编号大于65536的字符。很明显,"\uD842""\uDFB7"都是处于这个区间的,所以就可以表示大于65536的编号为"\u{20BB7}"的汉字""了)

因为有像 "" 这种文字的存在,所以遍历字符串的最好实现应该用for...of循环,用for (let i = 0; i < text.length; i++)这种循环碰到 "" 这种情况会有问题

标签模板

alert`123` // 这就是标签模板
// 等同于
alert(123)

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

如果模板字符里面有变量,会先将模板字符串先处理成多个参数,再调用函数。

let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

研究上面代码会发现,如果有n个变量(像${}的这种)会将字符串模板分割成n+1

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

let message = SaferHTML`

${sender} has sent you a message.

`; 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, ">"); // Don't escape special characters in the template. s += templateData[i]; } return s; }

标签模板的另一个应用,就是多语言转换(国际化处理)

console.log`123`
// ["123", raw: Array[1]]

上面代码中,console.log接受的参数,实际上是一个数组。该数组有一个raw属性,保存的是转义后的原字符串。

进一步用代码说明:

tag`First line\nSecond line`

function tag(strings) {
  console.log(strings.raw[0]);
  // strings.raw[0] 为 "First line\\nSecond line"
  // 打印输出 "First line\nSecond line"
}

raw属性的这部分功能,String.raw()也能实现,例子:

String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"

String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!"

ES6会对模板字符串中的特殊符号进行转义,比如会转义\u{20bb7},但如果是遇到\unicode这种转义失败的怎么办呢?

  • 在ES6的最新标准中,对标签模板中的字符串如果转义失败,会返回undefined不报错
  • 对模板字符串还是会报错

例如:
let bad = `bad escape sequence: \unicode`; // 报错
tag `bad escape sequence: \unicode`; // 不报错


正则扩展

/foo[^]bar/.test('foo\nbar')
// true

why? 正则表达式中^的两种意思,^可以表示从开头匹配;表示排除,也就是说”[]”代表的是一个字符集,^只有在字符集中才是反向字符集的意思。

这一部分好烦,不想看了!

数值扩展

浮点计算是不准确的,比如0.1 + 0.2 === 0.3 // false
怎么解决?ES6Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。等于 2 的 -52 次方。Number.EPSILON === Math.pow(2, -52)

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true

1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true

上述代码表示,如果浮点计算误差在 2的-50次方 内,就认为是相等的。

在js中,无论对整数还是小数,都是有最大上限和下限的,都有对应的常量表示。超过最大值后,js就类似这样处理了9007199254740993 === 9007199254740992; // true

持续更新中。。。

你可能感兴趣的:((持续更新...)ES6学习笔记(一))