ES6学习笔记

本文作者:IMWeb coolriver 原文出处:IMWeb社区 未经同意,禁止转载

本文为初步阅读ECMAScript6入门后的一些记录与感想。

简介

ES6的设计目标,是使得JavaScript语言可以用来编写大型复杂的应用程序,成为企业级开发语言。ES6将于2015年6月正式发布。

现在最新版本的Nodejs上可以通过`--harmony`参数开启ES6的部分支持。如果旧的Nodejs版本不支持或者想体验更多的ES6 特性
可以 使用Google的[traceur](https://github.com/google/traceur-compiler)工具。traceur可以在前端编译ES6代码,也可以
在node上 编译或执行ES6代码。
  • traceur在node上安装: npm install -g traceur
  • 直接执行ES6文件: traceur test.es6.js
  • 编译ES6文件到ES5文件:traceur --script test.es6.js --out test.es5.js ES6在很方面都对ES5有增强,下面将从不同方面对ES6在ES5上的加强进行介绍。

新增块级作用域(let与const关键字)

let声明变量

  在ES5中,除了全局作用域外,限定所声明的变量的作用域是函数作用域。这使得ES5在很多情况下为了模拟块级作用域(避免变量名污染)需要使用立即执行的匿名函数。在ES6中新增了声明块级使用域变量的关键字let。与var相比,使用let声明的变量有如下特点:

  1. let声明的变量所在的作用域为块级。
  2. let声明的变量不存在变量提升。
  3. let声明的变量不允许重复声明,否则会报错。 使用let可以替代ES5中为了模拟块级作用域而使用的立即执行的匿名函数:
//ES5实现方法:
(function(){
    for (var i = 0;i < 10;i++){
        //doSomething
    }
})();

//ES6实现方法:
for (let i = 0;i < 10;i++){
    //doSomething
}
  • 需要注意的是,ES6中函数本身的作用域在其块级作用域之类(相当于使用let声明了),这样,在if条件内声明的函数就不会像ES5因函数提升而总会被声明。

const声明常量

  ES6中可以使用const关键字来声明常量,被声明的常量不能被修改。与使用let声明的变量类似,const声明的常量为块级作用域,不存在变量提升,且不可重复声明。   const只限定就是所以的地址不能改变,意味着如果声明的目标为对象,那么对象的属性是可以修改的。书中建议如果要使对象为常量的话可以配合Object.freeze()函数来实现:

const foo = Object.freeze({a:1});

//foo = {b:2} 无法修改foo
//foo.a = 2   无法修改foo.a

  以上方法中的Object.freeze()函数本身有局限性,它只能冻结对象的属性不被修改,并不能冻结它的属性的属性被修改。如果要实现将对象内部所有属性冻结,需要使用自定义的强化的冻结函数。关于深度冻结对象的方法在codewars上的一个题目有描述,具体方案如下:

Object.deepFreeze = function (object) {
    Object.freeze(object);
    Object.keys(object).forEach(function(key) { 
        if(typeof(object[key]) == 'object') 
        return Object.deepFreeze(object[key]); 
    });
}

   通过以上deepFreeze即可实现完全将对象常量化。效果如下:

const foo = Object.freeze({a:[1]}); //使用原始的冻结函数
foo.a.push(2); //本操作可以使foo.a变为[1,2]

const foo2 = Object.deepFreeze({a:[1]}); //使用深度冻结函数
foo2.a.push(2); //本操作无法改变foo2.a

全局对象属性

  全局对象是最顶层的对象,在浏览器环境指的是window对象,在Node.js指的是global对象。ES5规定,所有全局变量都是全局对象的属性。   ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。

变量的解构赋值

  ES6中新增了一种赋值方法,可以批量地从对象(或数组)中提取出相应的值赋值到一组对应的变量上。下面为数组形式的解构赋值:

//数组的解构赋值
var [a,b,c] = [1,2,3];

//相当于
var a = 1,b = 2,c = 3;

  下面为对象形式的解构赋值:

//对象的解构赋值方式一
var {bar, foo} = {foo: "aaa", bar: "bbb"};
foo //"aaa"
bar //"bbb"

//对象的解构赋值方式二
var {first: f, last: l} = {first: "hello", last: "world"};
f //"hello"
l //"world"

解构赋值可以带来很多便利:

  • 变量交换 :[x, y] = [y, x]
  • 函数返回多个值赋值给多个变量
  • 可以方便地将一组参数与变量名对应起来。
  • 方便提取JSON数据
  • 方便设置函数参数默认值。(解构赋值可以指定默认值)
  • 遍历Map:for (let [key, value] of map){}
  • 加载模块时指定需要加载的方法:const {SourceMapConstumer, SourceNode} = require("source-map")

字符串扩展

  • ES6中提供了一级新的方法来解决ES5里某些中文字符(UTF-16编码大于0XFFFF)编码的问题。另外ES5中使用码点"\uXXXX"来表示字符的方法不能表示大于0XFFFF的字符,在ES6中可以使用大括号将码点括起来就可以表示大于0XFFFF的字符:"\u{XXXX}"
  • ES6对正则表达式添加了u修饰符,用来正确处理大于\uFFFF的Unicode字符。点(.)字符在正则表达式中,解释为除了换行以外的任意单个字符。对于码点大于0xFFFF的Unicode字符,点字符不能识别,必须加上u修饰符:
var s = "?";

/^.$/.test(s) // false
/^.$/u.test(s) // true

*传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

includes():返回布尔值,表示是否找到了参数字符串. startsWith():返回布尔值,表示参数字符串是否在源字符串的头部. endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部.

  • repeat()返回一个新字符串,表示将原字符串重复n次。可以方便地构建由重复字符或字符串构成的字符串:
"x".repeat(3) // "xxx"
"hello".repeat(2) // "hellohello"
  • 字符串模板。模板字符串用``进行定义,中间使用到的变量值使用${}进行嵌入。使用字符串模板可以很方便地将变量与字符串结合,而不用使用大量的加号进行拼接:
$("#result").append(`
  There are ${basket.count} items
   in your basket,${basket.onSale}
  are on sale!
`);

数值扩展

  • ES6提供了新的表示二进制和八进制数据的写法,二进制用前缀0b表示,八进制用前缀0o表示。新的八进制表示方法弥补了ES5中前缀0写法存在的问题。
0b111110111 === 503 // true
0o767 === 503 // true
  • ES6将ES5中的全局方法:isFinite(),isNaN(),parseInt(), parseFloat()移至Number上,分别变为Number.isFinite(),Number.isNaN(),Number.parseInt(), Number.parseFloat()。这样做是为了逐步减少全局方法,使语言逐步模块化。
  • Math对象新增的方法:

Math.trunc(n) 去除一个数的小数部分 Math.sign(b) 判断一个数是正数、负数还是0. Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine) Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine) Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent) Math.cbrt(x) 返回x的立方根 Math.clz32(x) 返回x的32位二进制整数表示形式的前导0的个数 Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine) Math.expm1(x) 返回e?x - 1 Math.fround(x) 返回x的单精度浮点数形式 Math.hypot(...values) 返回所有参数的平方和的平方根 Math.imul(x, y) 返回两个参数以32位整数形式相乘的结果 Math.log1p(x) 返回1 + x的自然对数 Math.log10(x) 返回以10为底的x的对数 Math.log2(x) 返回以2为底的x的对数 Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)

数组的扩展

  • 新增方法Array.from(),可以将类数组对象(例如函数中的arguments)和遍历对象(如ES6中的Map和Set对象)。该函数可以方便地替代ES5中使用Array.prototype.slice来进行数组转换。
  • 新增方法Array.of(),用来将一组值转换为数组。该函数主要用于解决ES5中使用Array()构造数组的不足(因为参数个数的不同导致构造行为的不同):
//ES5:
Array() // []
Array(3) // [undefined, undefined, undefined]
Array(3,11,8) // [3, 11, 8]

//ES6:
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
  • 新增数组实例方法:find()findIndex()。两者的参数都是一个回调函数,返回第一个回调函数返回值为true的元素的值(或下标)。这两个函数解决了ES5中indexOf()函数不能找到NaN元素的问题。
  • 新增数组实例方法:fill(),使用指定值对数组进行填充。参数为一个时将数组所有元素替换为参数的值,参数为三个时,将指定起始位置(第二个参数)和终止位置(第三个参数)替换为目标值(第一个参数)
  • 新增数组实例方法:ectries(),keys(),values(),三个都返回遍历器:
for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
  • 新增数组推导的写法,用于快捷地从当前数组生成指定数组(ES5中可以使用map()filter()实现):
//ES5:
var a1 = [1, 2, 3, 4];
var a2 = a1.map(function(i){return i * 2});
var a3 = a1.filter(function(i){return i > 2});
var a4 = a1.filter(function(i){return i > 2})
           .map(function(i){return i * 2});
a2 // [2, 4, 6, 8]
a3 // [3, 4]
a4 // [6,8]

//ES6:
var a1 = [1, 2, 3, 4];
var a2 = [for (i of a1) i * 2];
var a3 = [for (i of a1) if (i > 2) i];
var a4 = [for (i of a1) if (i > 2) i * 2];//同时实现ES5中的map和filter 

a2 // [2, 4, 6, 8]
a3 // [3, 4]
a4 // [6,8]
  • 新增方法Array.observe()Array.unobserve()方法用于监听(取消监听)数组的变化(包括add,update,delete,splice四种操作引起的数组变化)

对象的扩展

  • 新增简洁的对象属性写法:
function f( x, y ) {
  return { x, y, method(){return 1;}};
}

// 等同于

function f( x, y ) {
  return { x: x, y: y, method: function(){return 1}};
}
  • 允许在定义字面量对象时,使用表达式作为对象的属性名,通过把表达式放在方括号内:
let propKey = 'foo';

let obj = {
   [propKey]: true,
   ['a'+'bc']: 123,
   ['h'+'ello'](){return 'hi';} 
};
  • 新增Object.is()方法。用于比较两个值是否严格相等,相对于运算符===有两个不同:一是+0不等于-0,二是NaN等于自身。
  • 新增Object.assign()方法。将源对象(第一个参数后的所有参数)的所有可枚举属性复制到目标对象(第一个参数),后面的属性值会覆盖前面的同名属性。可用于:

为对象添加属性和方法 克隆对象(不能复制原型链上的属性,可以和Object.create()配合来弥补) 合并多个对象 为属性指定默认值

  • 通过__proto__属性直接获取和操作对象的方式,虽然不在ES6标准中,但是所有最新版本的浏览器都部署了这个属性。ES6推荐使用Object.setPrototypeOf()来设置对象的原型,使用Object.getPrototypeOf()来获取对象的原型。
  • 新增Symbol类型来解决属性名冲突的问题:
// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");

s1 === s2 // false
  • 新增Proxy类型,且物修改某些操作的默认行为。相当于在语言层面做出修改,属于“元编程”,即对编程语言进行编程:
var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35;
  }
});

proxy.times // 35
proxy.name // 35
proxy.title // 35
  • 新增Object.observe()Object.unobserve()方法用于监听(和取消监听)对象(以及数组的变化)。可以方便地实现数据绑定。以上两方法不属于ES6,而属于ES7,但是在Chrome 33起就已经支持。

函数的扩展

  • 设置函数参数的默认值:
//ES5:
function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

//ES6:
function log(x, y = 'World') {
  console.log(x, y);
}

//参数默认值和解构赋值配合使用:
function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
  • 新增rest参数来获取函数的多余参数,这样就不需要用arguments对象。rest参数搭配一个数组就是,多余的参数会放入数组中:
function add(...values) {
   let sum = 0;
   for (var val of values) {
      sum += val;
   }
   return sum;
}

add(2, 5, 3) // 10

rest参数注意点:

rest参数后不能再有其他参数,否则会报错 函数的length属性不包括rest参数

  • 扩展运算符...,相当于rest参数的逆运算,将一个数组转换为用逗号分隔的参数序列,主要用于函数调用(ES5中需要使用apply函数来实现):
//ES:
function f(x, y, z) { }
var args = [0, 1, 2];
f.apply(null, args);

//ES6:
function f(x, y, z) { }
var args = [0, 1, 2];
f(...args);

扩展运算符可以将一个数值扩展成数组,还可以将类似数组的对象转换为真正的数组:

[...5]
// [0, 1, 2, 3, 4, 5]

[..."hello"]
// [ "h", "e", "l", "l", "o" ]

var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);


let arr = [...map.keys()]; // [1, 2, 3]

var go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]
  • 使用箭头来定义函数:=>。箭头函数能够简化函数定义的写法,且能解决常规定义函数时this变量在挂靠 时的变更。
var f = v => v;
//等同于下面写法
var f = function(v) {
    return v;
};

//箭头函数支持的写法如下:
var f = () => 5;
var sum = (num1, num2) => num1 + num2;
var sum = (num1, num2) => { return num1 + num2; }

使用箭头函数时需要注意以下几点:

函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象 不可当作构造函数,也就是说不能使用new命令,否则会抛出错误 不可以使用arguments对象,因为该对象在函数体内不存在

  • ES6中对函数有尾调用优化。尾调用在函数式编程中是一个重要概念,指某个函数的最后一步是调用另一个函数:
//下面属于尾调用:
function f(x){
  return g(x);
}

function f(x) {
  if (x > 0) {
    return m(x)
  }
  return n(x);
}

//下面不属于尾调用:
function f(x){
  let y = g(x);
  return y;
}

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

尾调用优化只保留内层函数的调用帧,而不需要将上层函数的调用帧存放于调用栈中。尾调用优化可以节省内存。在递归函数中,如果调用自身的函数为尾调用,那么就可以进行尾递归优化,很大地节省了递归函数执行过程中耗费的内存。如将一些递归函数改写为尾调用的模式即可极大地优化程序执行效率和耗费内存:

//原始写法:
function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

//改写为尾调用(尾递归):
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

Set和Map数据结构

  • 新增Set数据结构,类似于数组,但是成员的值是唯一的,不存在重复的值。添加重复的值不会改变原结构中的内容。在Set结构中加入值时不进行类型转换,且判断新增值与原有值是否相同的比较方法类似于===运算符,唯一的例外是NaN等于自身。Set的基本使用方法如下:
var s = new Set();

[2,3,5,4,5,2,2].map(x => s.add(x))

for (i of s) {console.log(i)}
// 2 3 4 5

var items = new Set([1,2,3,4,5,5,5,5]);

items.size 
// 5

Set结构有以下属性:

Set.prototype.constructor:构造函数,默认就是Set函数。 Set.prototype.size:返回Set的成员总数。

Set结构有以下方法:

add(value):添加某个值,返回Set结构本身。 delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 has(value):返回一个布尔值,表示该值是否为Set的成员。 clear():清除所有成员,没有返回值 values():返回一个值的遍历器 keys():返回一个键的遍历器 entries():返回一个键值对的遍历器 forEach(fn):对每个成员执行某种操作,返回修改后的Set结构

  • 新增WeakSet结构。与Set类似,也是不重复的值的集合。与Set有两个不同点:

  1. WeakSet中的成员只能是对象而不能是其它类型的值。
  2. WeakSet中的对象都是弱引用,垃圾回收机制不考虑WeakSet对该对象的引用(因此Wea kSet中的成员是可遍历的)

WeakSet结构没有size属性,有以下三个方法:

add(value):向WeakSet实例添加一个新成员 delete(value):清除WeakSet实例的指定成员 has(value):返回一个布尔值,表示某个值是否在WeakSet实例中

WeakSet的一个用处是存储DOM节点,这样就不用担心节点从文档被移除时引起内存泄漏  
  • 新增Map数据结构。类似于对象,是一个键值对的集合,但是键的范围不像对象一样仅限于字符串,各类型的值(包括对象)都可以当作Map实例的键值。基本使用方法如下:
var m = new Map();
var o = {p: "Hello World"};

m.set(o, "content")
m.get(o) // "content"

var map = new Map([ ["name", "张三"], ["title", "Author"]]);

map.size // 2
map.has("name") // true
map.get("name") // "张三"
  如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键,包括0和-0。另外,虽然NaN不严格相等于自身,但Map将其视为同一个键。  
  如果Map的键是对象(或数组),则只有两个对象的地址相同时,才将两者视为同一个键。

Map结构有如下属性和方法:

size:返回成员总数 set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则 键值会被更新,否则就新生成该键。 get(key):读取key对应的键值,如果找不到key,返回undefined。 has(key):返回一个布尔值,表示某个键是否在Map数据结构中。 delete(key):删除某个键,返回true。如果删除失败,返回false。 clear(): 清除所有成员,没有返回值。 keys():返回键名的遍历器。 values():返回键值的遍历器。 entries():返回所有成员的遍历器。

  • 新增WeakMap结构。与Map类似,区别是它只接受对象作为键名(null除外),且键名指向的对象不讲稿垃圾回收机制。用于存储DOM元素时,可防止内存泄漏:
var wm = new WeakMap();
var element = document.querySelector(".element");

wm.set(element, "Original");
wm.get(element) // "Original"

element.removeChild(element);
element = null;
wm.get(element) // undefined

WeakMap实例没有size属性,且只有四个方法可用:get(),set(),has(),delete()

新增Iterator(遍历器)和for...of循环

  Iterator是一种接口规格,任何数据结构只要部署了这个接口就可以使用for...of进行遍历操作。Iterator为各种数据结构白日做梦了统一简便的接口,且使得数据结构中的成员能按照某种次序排列。   Iterator提供一个指针,默认指向数据结构的起始位置,第一次调用Iterator的next方法可以将指针指向第一个成员,第二次调用就指向第二个成员,直到指向数据结构的结束位置。ES6中的Iterator接口要求在每次调用next方法时返回一个{value: v, done: bool}格式的对象,value表示当前成员的值,done表示遍历是否结束。下面的例子在现有数组上模拟了一个Iterator:

function makeIterator(array){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  }
}

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏从零开始学 Web 前端

从零开始学 Web 之 ES6(五)ES6基础语法三

在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧!

922
来自专栏黑泽君的专栏

字符串的案例代码

字符串的案例   A:模拟用户登录   B:字符串的遍历   C:统计字符串中大写、小写及数字字符的个数   D:把字符串的首字母转成大写,其他...

990
来自专栏ShaoYL

深刻理解----修饰变量----关键字

35611
来自专栏从零开始学 Web 前端

04 - JavaSE之异常处理

2.throw new someExpresion("错误原因"); 表示的是手动抛出异常。 **

1374
来自专栏marsggbo

python的with语句,超级强大

With语句是什么? 有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文...

21310
来自专栏大前端开发

ES6特性之:Spread操作符

Spread操作符(...),也称作展开操作符,作用是将可迭代的(Iterable)对象进行展开。

962
来自专栏从零开始学 Web 前端

从零开始学 Web 之 ES6(四)ES6基础语法二

Promise是一个对象,代表了未来某个将要发生的事件(,这个事件通常是一个异步操作)

751
来自专栏前端儿

ES6笔记(5)-- Generator生成器函数

Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

881
来自专栏java架构师

WCF学习笔记(三)

今天遇到了一个很牛X的问题,多人一起解决很久,无果。 然,锲而不舍,一下午,从网络海洋里捞啊捞,终于觅得善果。 感谢这位大神,原文地址:http://hi.ba...

2816
来自专栏python3

python 字符编码与转换

比如一款游戏《大话西游》用的是gbk编码开发的。出口到欧美国家,是无法直接运行的。

1392

扫码关注云+社区

领取腾讯云代金券