ES6学习笔记

1.let 和 const 命令

let 特性:

  • 用来声明变量,只在它所在的代码块中有效,所以在此块级作用域中不可重复声明;
  • 不存在变量提升,声明后必须使用;
  • 块级作用域中未声明就是用会报错(暂时性死区);

例1:

// var 存在变量提升,undefined是一种数据类型
console.log(foo); // 输出undefined
var foo = 2;

// let 不声明就使用会报错
console.log(bar); // 报错ReferenceError
let bar = 2;

const 特性:

  • 具有上面 let 的三个特性;
  • 声明后所指向的内存地址不会变动: 声明的简单类型的数据(数值、字符串、布尔值)等同于常量,复合类型的数据(比如对象、数组)只保存指针,原数据的数据结构是不是可变的,不能控制,;
const a = {value:1}
a.value = 2
console.log(a) //{value:2}

const b =[1,2,3]
b.push(4)
console.log(b) //[1,2,3,4]

2.解构赋值

ES6 允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

1.数组的解构


let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined   解构不成功,变量的值就等于undefined。
z // []

2.对象的解构
解构对象时变量名与属性同名才能取到正确的值;

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

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

当然变量名与属性名不一致时,可以这样写:

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'

对象的解构赋值的内部机制是先找到同名属性,然后在赋给对应的变量,所以,真正被赋值的是后者而不是前者。对象可以嵌套解构,可以指定默认值;其他类型也可以解构赋值,这里就不一一说明了;
3.解构赋值的部分使用场景
使用场景有很多,比如交换变量的值;

let x = 1;
let y = 2;

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

提取 JSON 数据:

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

遍历 Map 结构

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.数据类型的扩展

1.字符串的扩展

字符串的扩展有很多,我这里写一下我平时常用的几点。

 let s = 'Hello world!';

s.startsWith('Hello') // true  ;表示参数字符串是否在原字符串的尾部
s.endsWith('!') // true ;表示参数字符串是否在原字符串的头部
s.includes('o') // true ;是否找到参数字符串

repeat 方法返回一个新字符串,表示将原字符串重复n次。参数如果是小数则取整,不能小于等于-1,-1(不包含)到1(不包含)视同为0,NAN也是一样,数字型的字符串会自动转换,不是则为“”;

'hello'.repeat(2) // "hellohello"

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

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4) // '   x',第二个参数不填表示为空格

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

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12" //用来提示字符串格式

还有一个很重要的扩展。模板字符串的写法:用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,变量名卸载${}之中,或者也可以调用函数,也可以嵌套模板。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 传统写法为
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

通过JSX函数还可以将一个DOM字符串转为React对象,具体实现

2.数值的扩展

在Number的对象上提供了Number.isFinite()和Number.isNaN()方法,分别用来检查一个数值是否是有限的,和检查一个值是否为NaN;

还有一个很重要的扩展。模板字符串的写法:用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,变量名卸载${}之中,或者也可以调用函数,也可以嵌套模板。
``` javascript  
Number.isFinite('10'); // false 非数值一律返回false
Number.isNaN(NaN) // true  参数不是数值一律返回NaN,只有NaN才返回true
Number.isNaN(15) // false

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

Math.trunc('123.456') // 123  内部会自动转换为数值
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc();         // NaN
Math.trunc(undefined) // NaN  空值和无法截取整数的返回NaN

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

Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN

3.函数的扩展

对函数的拓展最大的特点就是能为函数的参数指定默认值。

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

const p = new Point();
p // { x: 0, y: 0 }

函数的name属性,返回改函数的函数名。

const bar = function baz() {};
bar.name // "baz"

(new Function).name // "anonymous"

还有一个重大更新:箭头函数。

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

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

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

  • 函数体内的this对象是定义时所在的对象,是固定的,而不是使用时的对象,因为箭头函数没有自己的this对象。
  • 不能使用new命令。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  • 箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

双冒号运算符是并排的两个冒号(::)。冒号左边是对象,邮编是函数,表示将左边的对象作为this对象,绑定到右边的函数上面。

4.扩展运算符

三个点(...),主要用于函数调用,比如将一个数组,变为参数序列。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

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

// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

//复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。

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

Array.from()可以将类似数组的对象和可遍历的对象转换为真正的数组;

// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// 函数内部的arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}  assign方法用的浅拷贝

Object.assign方法有很多用处,比如为对象添加属性,为对象添加方法,合并对象,为属性指定默认值等等;

// 为对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});//Object.assign方法,将x属性和y属性添加到Point类的对象实例。
  }
}
// 将多个对象合并到某个对象。
const merge =
  (target, ...sources) => Object.assign(target, ...sources);

扩展运算符的结构赋值不能复制继承自原型对象的属性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined   对象o3复制了o2,但是只赋值了o2自身的属性,没有赋值它的原型对象o1的属性。

如果想完整克隆一个对象,还拷贝对象原型的属性可以用一下方法。

// 写法一 :__proto__属性在非浏览器的环境不一定部署
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

4.Symbol

Symbol是一种新的原始数据类型,值是通过Symbol()函数生成,可以设置多个,属性名属于Symbol类型的都是独一无二的,即使继承也不会传递,其值也不能用于运算。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果

Symbol还可以通过Symbol.for()生成,但与Symbol()有很大区别。

Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

如果有需求是跨iframe或者service worker的,Symbol.for设置的值是全局环境的,是登记了的,而Symbol()写法没有登记机制,每次调用都会返回一个不同的值,如果要获取已登记的Symbol类型值得key可以通过Symbol.keyFor()方法。

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

5.Set和Map数据结构

Set 同Java中的Set一样的,不能有重复的数据,内置了增-add()、删-delete()、判断-has()、清空-clear()、转换数组-Array.from().;另外还有一些遍历的方法,但是在遍历中不能改变原Set结构。

let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']

//  去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]

// 改变原Set结构思路
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6

// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

WeakSet结构与Set类似,只不过成员只能是对象,而且其中的队形都是弱引用,适合临时存放一组对象,不可遍历。

Map用法同Java中也差不多,键值对结构,可以把对象当做键,但是只有对同一个对象的引用,Map结构才会视为同一个键。


const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined 表面是同一个键,实际内存地址不一样

const k1 = ['b'];
const k2 = ['b'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

6.Proxy

Proxy勇于修改某些操作的默认行为,简单的说就是在访问目标对象之前做拦截接,要对目标访问都必须先通过注册拦截,这样就可以对外界的访问进行过滤和改写了。
需要用到了再具体研究。

7.Reflect

Reflect有多用处,比如让object操作都变成函数行为:


delete obj[name];
//改为
Reflect.deleteProperty(obj, name);

将Object 对象的一些明显属于语言内部的方法放到Reflect对象上;

// 老写法
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

还有以下Reflect与Proxy结合使用的例子,这里也不一一举例了。

8.Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
从语法上说,Promise是一个对象,可以获取一部操作的消息。
Promise对象有两个特点:对象的状态(pengding/fufilled/rejected)不收外界影响;一旦状态改变就不会再变,任何时候都可以得到这个结果。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

9.Generator函数

Generator函数从语法上可以理解为一个状态机,封装了多个内部状态;执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
Generator函数两个特征;function关键字和函数名之间有个*号,函数体内部使用yield表达式,定义不同的内部状态。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

yield表达式就是暂停标志。yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,当处于for...of循环体内时,循环会自动调用。
当一个对象不具备 Itreator接口是,无法使员工for...of遍历,这是可以用Genertaorg函数为这个对象加上遍历器接口;或者把Generator函数添加到对象的Symbol,iterator属性上。

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • *node下用express框架,实现一个简单的mvc *构建工具:gulp / babel / webpack ...
    韩娜爱吃辣_前端程序媛阅读 1,085评论 0 1
  • 《ECMAScript6 入门》阮一峰 读书笔记 let和constlet声明的变量仅在块级作用域内有效,var声...
    亲爱的孟良阅读 716评论 1 2
  • 什么是ES6? ECMAScript 6.0 是继ECMAScript 5.1 之后 JavaScript 语...
    多多酱_DuoDuo_阅读 1,092评论 0 4
  • ECMAScript 6.0( 以下简称ES6) 是JavaScript语言的下一代标准。 ECMAScript和...
    EarthChen阅读 438评论 0 0
  • 盼望着,盼望着,二月——冬天的童话,随着轻盈、洁白的雪花姐姐,迎着性格刚烈、但又不失柔软的风哥哥飘然而至。...
    刘婧_阅读 415评论 1 2