ES6

x==y 运算符

1.如果x不是正常值(比如抛出一个错误),中断执行。
2.如果y不是正常值,中断执行。
3.如果Type(x)与Type(y)相同,执行严格相等运算x === y。
4.如果x是null,y是undefined,返回true。
5.如果x是undefined,y是null,返回true。
6.如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
7.如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
8.如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
9.如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
10.如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
11.如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
12.返回false。

Es6的7种数据类型:undefined, null, Boolean, String, Number, Object, Symbol

ES6申明变量的6种方法:var, let, const, function, import, class

Set, 成员值不重复

1
2
3
4
5
6
7
8
9
10
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每个成员

Map, 键值对的集合

1
2
3
4
5
6
7
8
9
10
11
Map.prototype.constructor:构造函数。
Map.prototype.size:返回Set实例的成员总数。
Map.prototype.set(key, value):添加某个值,返回本身。
Map.prototype.get(key):获得某个值。
Map.prototype.delete(key):删除某个值,返回一个布尔值,表示删除是否成功。
Map.prototype.has(key):返回一个布尔值,表示该值是否为Set的成员。
Map.prototype.clear():清除所有成员,没有返回值。
Map.prototype.keys():返回键名的遍历器
Map.prototype.values():返回键值的遍历器
Map.prototype.entries():返回键值对的遍历器
Map.prototype.forEach():使用回调函数遍历每个成员

顶层对象

  1. 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  2. 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  3. Node 里面,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。
  1. 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  2. 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  3. 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

String

ES5 methoddescription
String.fromCharCode()从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符
String.prototype.charCodeAt()从字符返回对应的码点
String.prototype.charAt()返回字符串给定位置的字符

ES6新增了几个专门处理4字节码点的函数

ES6 methoddescription
String.fromCodePoint()从Unicode码点返回对应字符,可以识别码点大于0xFFFF的字符
String.prototype.codePointAt()从字符返回对应的码点
String.prototype.at()返回字符串给定位置的字符
Sring.raw()返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "
// for-of 可识别大于0xFFFF的码点
for (let i of text) {
  console.log(i);
}
// "𠮷"

'中' === '\u4e2d' // true

String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!"
String.raw`Hi\\n`
// 返回 "Hi\\\\n"


JavaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。举例来说,字符串里面不能直接包含反斜杠,一定要转义写成\\或者\u005c

U+005C:反斜杠(reverse solidus)  
U+000D:回车(carriage return)  
U+2028:行分隔符(line separator)  
U+2029:段分隔符(paragraph separator)  
U+000A:换行符(line feed)   
模板字符串
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 模板字符串还能嵌套
const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
标签模板
let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
1
2
3
4
5
6
7
8
9
10
# 避免 XSS 攻击

function htmlEscape(literals, ...placeholders) {
return placeholders.reduce((pre, val, i) => pre + literals[i] + val
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;'), '') + literals[literals.length - 1]
}

RegExp

// ES5 type 1
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;

// ES5 type 2
var regex = new RegExp(/xyz/i);
// 等价于
var regex = /xyz/i;

// ES6
new RegExp(/abc/ig, 'i').flags
// "i"

// u修饰符,支持4字节unicode
/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true

字符串的正则方法: match() replace() search() split()

String.prototype.match 调用 RegExp.prototype[Symbol.match]
String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
String.prototype.search 调用 RegExp.prototype[Symbol.search]
String.prototype.split 调用 RegExp.prototype[Symbol.split]

Symbol

每一个Symbol值都是不相等的,意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名属性。

let s = Symbol('name');
s // Symbol(name)
s.toString(); // "Symbol(name)"

let s = Symbol();
let a = {};
a[s] = 'hello';
let b = {
    [s]: 'hello'
};
let c = {};
Object.defineProperty(c, s, {value: 'hello'});

a[s] // 'hello'
b[s] // 'hello'
c[s] // 'hello'

let s = Symbol();
let obj = {
    [s]: function(arg) {}
};
obj[s](123);

// 增强对象的写法
let obj = {
    [s](arg) {}
};


Symbol值作为属性名时,该属性是公开属性,不是私有属性

遍历

const objectSymbols = Object.getOwnPropertySymbols(obj);
Reflect.ownKeys(obj);

Array

Array 在 Javascript 中是一个对象, Array 的索引是属性名。首先, Javascript 中的 Array 在内存上并不连续,其次, Array 的索引并不是指偏移量。实际上, Array 的索引也不是 Number 类型,而是 String 类型的。我们可以正确使用如 arr[0] 的写法的原因是语言可以自动将 Number 类型的 0 转换成 String 类型的 “0″ 。所以,在 Javascript 中从来就没有 Array 的索引,而只有类似 “0″ 、 “1″ 等等的属性。有趣的是,每个 Array 对象都有一个 length 的属性,导致其表现地更像其他语言的数组。但为什么在遍历 Array 对象的时候没有输出 length 这一条属性呢?那是因为 for-in 只能遍历“可枚举的属性”, length 属于不可枚举属性,实际上, Array 对象还有许多其他不可枚举的属性。

const arr = [1, 2, 3];
for(let i = 0; i< arr.length; i++) {
    console.log(arr[i]);
}

for-in 循环遍历的是对象的属性,而不是数组的索引。因此, for-in 遍历的对象便不局限于数组,还可以遍历对象。
需要注意的是, for-in 遍历属性的顺序并不确定,即输出的结果顺序与属性在对象中的顺序无关,也与属性的字母顺序无关,与其他任何顺序也无关。

const arr = [1, 2, 3];
let index;
for(index in arr) {
    console.log("arr[" + index + "] = " + arr[index]);
}

forEach 不能 break 和 return;
for-in 缺点更加明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。

const arr = ['a', 'b', 'c'];
for(let data of arr) {
    console.log(data);
}

任何部署了Iterator接口的对象,都可以用for-of循环遍历
for-of 跟 forEach 相比,可以正确响应 break, continue, return。
for-of 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。
for-of 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。
for-of 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。
但需要注意的是,for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用 for-in 循环(这也是它的本职工作)。

最后要说的是,ES6 引进的另一个方式也能实现遍历数组的值,那就是 Iterator。上个例子:

const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }


every: 循环在第一次 return false 后返回
some: 循环在第一次 return true 后返回
filter: 返回一个新的数组,该数组内的元素满足回调函数
map: 将原数组中的元素处理后再返回
reduce: 对数组中的元素依次处理,将上次处理结果作为下次处理的输入,最后得到最终结果。

尾调用

尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数。

尾调用优化,只保留内层函数的调用帧

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

尾递归,由于只存在一个调用帧,不会发生栈溢出错误

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

es6中只有严格模式'use strict'才能开启尾递归优化

// 蹦床函数,将递归执行转为循环执行
function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}

// 真正的尾递归优化
function tco(f) {
  let value;
  let active = false;
  const accumulated = [];

  return function accumulator(...args) {
    accumulated.push(args); // 第二次及以后还是会push参数
    if (!active) {
      // 第二次及以后返回undefined ,从而避免递归执行
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift()); // f运行 =》 push参数 =》 while条件为真继续执行
      }
      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
可枚举性
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }
目前,有四个操作会忽略enumerable为false的属性。

for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for…in

for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。

严格模式主要有以下限制

​ 变量必须声明后再使用

​ 函数的参数不能有同名属性,否则报错

​ 不能使用with语句

​ 不能对只读属性赋值,否则报错

​ 不能使用前缀 0 表示八进制数,否则报错

​ 不能删除不可删除的属性,否则报错

​ 不能删除变量delete prop,会报错,只能删除属性delete global[prop]

​ eval不会在它的外层作用域引入变量

​ eval和arguments不能被重新赋值

​ arguments不会自动反映函数参数的变化

​ 不能使用arguments.callee

​ 不能使用arguments.caller

​ 禁止this指向全局对象

​ 不能使用fn.caller和fn.arguments获取函数调用的堆栈

​ 增加了保留字(比如protected、static和interface)

参考链接:
1.http://es6.ruanyifeng.com/#docs/