ES6 学习笔记(下)

认真学习了一遍ES6,发现很多很好用的功能。
学习资料:《ECMAScript 6 入门》

好啦,继续下半部分的学习。

Proxy

Proxy 用于修改对象的某些操作行为。套用书上的栗子,实现了对象的set、get方法拦截。

var obj = new Proxy({}, {
    get: function (target, key, receiver) {
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
        console.log(`setting ${key}!`);
        return Reflect.set(target, key, value, receiver);
    }
});

obj.count = 1
obj.name = 'jack'
obj.count++

// setting count
// setting name
// getting count
// setting count

实例方法整理

  • get 方法拦截属性的读取操作。
  • set 方法拦截赋值操。
  • apply 方法拦截函数的调用。
  • has 方法拦截 HasProperty 操作,即查找对象中是否有某属性。可用来隐藏一些属性不被 in 运算符发现。
  • construct 方法拦截 new 指令。即在 new 指令创建实例的时候可以对对象中的参数进行一些初始化修改操作。
  • deleteProperty方法拦截 delete 指令,可用来保护某些对象属性无法被删除。
  • defineProperty方法拦截了Object.defineProperty操作。
  • getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined
  • getPrototypeOf方法主要用来拦截获取对象原型。
  • isExtensible方法拦截Object.isExtensible操作。
  • ownKeys方法用来拦截对象自身属性的读取操作。
  • preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
  • setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。

apply方法的使用

var target = function () { return 'I am the target'; };
var handler = {
    apply: function () {
        return 'I am the proxy';
    }
};

var p = new Proxy(target, handler);

console.log(p())
// "I am the proxy"

has方法的使用

var handler = {
    has (target, key) {
        if (key[0] === '_') {
            return false;
        }
        return key in target;
    }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy)
// false

construct方法的使用

var p = new Proxy(function () {}, {
    construct: function(target, args) {
        console.log('called: ' + args.join(', '));
        return { value: args[0] * 5 + 12 };
    }
});

console.log(new p(1))
console.log(new p(1).value)

// call: 1
// { value: 17 }
// call: 1
// 17

所以,我理解的 Proxy 对象主要功能就是拦截对象属性的一些操作。

Reflect

我理解的Reflect对象:

  • 是Object的高级版本,Object对对象的操作方法Reflect对象都有,并且未来操作对象的新方法只放在Reflect对象中有。
  • 发生错误不会报错而是返回false,可直接在判断中使用。
  • 让Object操作都变成函数行为,统一表现形式。某些Object操作是命令式,比如 name in objdelete obj[name] ,而 Reflect.has(obj, name)Reflect.deleteProperty(obj, name) 让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。Proxy对象拦截对象属性方法,进行重新定义。而Reflect对象立即执行对象属性方法。下面例子中使用set和get做演示。
var myObject = {
    foo: 1,
    bar: 2,
    get baz() {
        return this.foo + this.bar;
    },
}

console.log(Reflect.get(myObject, 'foo')) 
console.log(Reflect.get(myObject, 'bar')) 
console.log(Reflect.get(myObject, 'baz')) 

console.log(Reflect.set(myObject, 'foo', 100))
console.log(myObject.foo)

// 1
// 2
// 3
// true
// 100

Promise 对象

Promise 对象登场啦~这是 ES6 语法中非常常用的对象。
我理解的 Promise 对象:

  • Promise 对象让异步操作的写法从回调函数变为链式操作,可读性更强。
  • Promise 对象一旦改变,就会锁死,不再改变。

基本用法
Promise 的定义,定义一个 Promise对象,参数为resolve和reject,resolve为执行成功的方法,而reject为执行失败的方法。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

基本调用方式

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise 函数一旦用 new 指令创建,立即执行。并且数据为不可变。

let success = true
let name = 'jack'

const promise = new Promise((resolve, reject) => {
    console.log('create')
    if (success) {
        resolve(name)
    } else {
        reject('error promise')
    }
})

success = false
name = 'rose'
console.log('before then')

promise.then(value => {
    console.log(value)
}).catch(error => {
    console.log(error)
})

// create
// before then
// jack

由此可见,在定义了Promise之后,我们再去修改 name 返回的还是定义Promise时候的值。说明了Promise对象定义即执行,并且不可变。

Promise 推荐使用 promise.then().catch() 写法。

// bad
promise.then(value => {
    console.log(value)
}, error => {
    console.log(error)
})

// good
promise.then(value => {
    console.log(value)
}).catch(error => {
    console.log(error)
})

then方法链式写法表达,then方法的返回值可以传递给下一个then方法。

const promise = new Promise((resolve, reject) => {
    resolve('jack')
})

promise.then(value => {
    console.log(value)
    return 'violet' + value
}).then(value => {
    console.log(value)
    return 'welcome to ' + value
}).then(value => {
    console.log(value)
    return  value + ' blog'
}).then(value => {
    console.log(value)
})

// jack
// violetjack
// welcome to violetjack
// welcome to violetjack blog

catch方法用于捕获Promise对象的异常行为(可能是 reject 函数返回的错误,也可能是throw new Error('error'))。
Promise.all() 方法将多个Promise实例包装成一个Promise实例。如下示例,如果p1、p2、p3都执行成功,则执行then方法,返回的参数为三个实例的参数数组;如果有任意一个Promise实例报错,则在catch方法中返回该实例的错误信息。

const p1 = new Promise((resolve, reject) => {
    resolve('jack')
})

const p2 = new Promise((resolve, reject) => {
    // resolve('rose')
    reject('rose error')
})

const p3 = new Promise((resolve, reject) => {
    resolve('james')
})

Promise.all([p1, p2, p3]).then(values => {
    console.log(values)
}).catch(error => {
    console.log(error)
})

// ["jack", "rose", "james"]
// rose error

Promise.race() 方法用法与 all 方法一致,唯一不同点就是多个 Promise实例中只作用域最快有反映的Promise实例,并且返回该实例的正确或错误信息。如果多个Promise同时触发,按顺序返回第一个Promise实例。
Promise.resolve() 和 Promise.reject()

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

Iterator 和 for...of 循环

默认Iterator接口部署在数据对象的 Symbol.iterator 属性中,Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。只要数据对象有了 Symbol.iterator 属性就可以进行遍历。如下对象添加了 Symbol.iterator 属性后实现了遍历操作。

let obj = {
    data: [ 'hello', 'world' ],
    [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
            next() {
                if (index < self.data.length) {
                    return {
                        value: self.data[index++],
                        done: false
                    };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
};

console.log([...obj])

// ["hello", "world"]

Iterator 接口主要供for...of消费。
for...in 循环读取键名,for...of 循环读取键值。

const arr = ['red', 'green', 'blue'];

for(let v of arr) {
    console.log(v); // red green blue
}
for(let k in arr){
    console.log(k) // 0 1 2
}

Generator 函数的语法

一种异步解决方案。函数执行返回一个对象,而函数中的数据只有在对象使用 next() 方法才会返回下一个用 yield 或者 return 定义的数据,否则对象状态就凝固在那里。

function* helloGenerator() {
    yield 'hello'
    yield 'world'
    return 'generator'
}

var h = helloGenerator()
console.log(h.next())
console.log(h.next())
console.log(h.next())
console.log(h.next())

// { value: 'hello', done: false}
// { value: 'world', done: false}
// { value: 'generator', done: true}
// { value: undefined, done: true}

next() 方法传值—— next() 方法返回的是 yield 表达式的计算结果。如果 next(value) 方法中传入value参数,则参数将替换上一个 yield 数据。如下示例中,12 替换了(yield (x + 1))13 替换了yield (y / 3),最后得到结果为42。

function* foo(x) {
  var y = 2 * (yield (x + 1));
  // value  = 5 + 1
  var z = yield (y / 3);
  // y = 2 * 12 value = 24 / 3
  return (x + y + z);
  // z = 13 y = 24 z = 13 value = 5 + 24 + 13
}

var a = foo(5);
a.next() // Object{value:6, done:false}
// 如果不传递数据,则y=NaN
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);

b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

throw方法用于捕捉错误,return方法类似于 Genterator 函数的 return xxx 返回某个值,随后再使用next方法返回的都是 undefined
对于 next、throw、return,引用书上的解释更清晰点。

next() 是将 yield 表达式替换成一个值。
throw() 是将 yield 表达式替换成一个 throw 语句。
return() 是将 yield 表达式替换成一个 return 语句。

yield* 用于将其他 Generator 函数合并到当前函数中,用法如下:

function* bar() {
    yield 'a'
    yield 'b'
}

function* foo() {
    yield 'x'
    yield* bar()
    yield 'y'
}

for (let v of foo()){
    console.log(v)
}

// x
// a
// b
// y

Generator 函数不能直接用 new 指令实例化对象,需要包装为普通函数再 new

function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() {
  return gen.call(gen.prototype);
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

自动执行所有Generator函数的方法:

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }

  next();
}

function* g() {
  // ...
}

run(g);

以上自动执行器还可以使用 co 模块来实现。

async 函数

async函数用于处理异步操作,它是对Generator函数的改进。它相比于Generator有以下几个优点:

  • 内置执行器:相比于Generator 要自定义或者用 co 模块来实现自动执行器效果,async函数自带自动执行器。
  • 更好的语义:async 和 await,比起 * 和 yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • 更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  • 返回Promise:async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

我个人对async的感觉是写法方便、代码理解简单、代码写法也符合逻辑、操作异步行为方便。
下面写了 Generator 函数和 async 函数实现异步的代码的对比。

const readFile = function (fileName) {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            console.log(`reading ${fileName}`)
            resolve(fileName)
        }, 1000)
    });
};

// Generator 写法
const gen = function* () {
    const f1 = yield readFile('/etc/fstab');
    const f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
};

var g = gen()
g.next().value.then(value => {
    g.next(value).value.then(value => {
        g.next(value)
    })
})

// async 写法
async function gan() {
    const f1 = await readFile('/etc/fstab');
    const f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}

gan()

两种函数的实现结果是一样的。
但从上面的例子中可以看出,Generator 函数需要不断调用next方法,并且将上一个next方法的结果传递给当前next方法当做参数。而async函数直接调用函数本身就会自动往下执行。Generator多了一步执行的过程。
另外,async await的语义很清晰,就算没学过ES6的大致都能看懂是什么意思啦~

Class

class其实就是一个函数实例化的语法糖具体功能也类似Java这类有Class的语言
所以,下面两种写法的结果是相等的。
传统写法

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

Point.prototype.toString = function () {
    return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);
console.log(p)

ES6 Class写法

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}

var p = new Point(1, 2);
console.log(p)

结果

对象结果如上图所示,构造函数中this对象的属性在实例中,而Class里面的函数再实例对象的 __proto__ 中。
使用 new target 在构造函数中判断对象是否为 new 指令创建的。

// 另一种写法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

修饰器

类似于Java的修饰器 @Override , 现有一个提案,将修饰器加入到 ECMAScript 中。

Module

在 ES6 中添加了模块化功能,很常见用法也很简单。
模块加载

import { stat, exists, readFile } from 'fs'; // 多个模块加载
import { lastName as surname } from './profile.js'; // 模块加载重命名
import * as circle from './circle'; // 整理加载

模块输出

// 输出变量
export let a = 100
// 输出方法
export function hello() {
    console.log('hello world')
}
// 输出多个变量
const b = 200
const c = 300
const d = 400
export {b, c, d}
// 输出变量重命名
export {b as value}
// 输出默认值
export default function () {
    console.log('hello default')
}

CommonJS 语法中模块输出和加载的写法

let { stat, exists, readFile } = require('fs');

module.exports = {
  counter: counter,
  incCounter: incCounter,
};

ArrayBuffer

ArrayBuffer对象、TypedArray视图和DataView视图是 JavaScript 操作二进制数据的一个接口。

二进制数组由三类对象组成。
(1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
(2)TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。
(3)DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。
简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray 视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。

这方面知识点不太常用,了解下,等到用的时候查查就是了。

其他

规格文件是计算机语言的官方标准,详细描述语法规则和实现方法。
一般来说,没有必要阅读规格,除非你要写编译器。因为规格写得非常抽象和精炼,又缺乏实例,不容易理解,而且对于解决实际的应用问题,帮助不大。但是,如果你遇到疑难的语法问题,实在找不到答案,这时可以去查看规格文件,了解语言标准是怎么说的。规格是解决问题的“最后一招”。

最后

好啦~终于把下半部分写完了。有点虎头蛇尾,一开始写的东西很具体,到后来内容有点少。主要是因为前面部分我觉得是比较麻烦和常用的。写这篇博客主要是系统复习下ES6语法,简略地提一下各个语法的用法、注意点。大致知道了有些什么,以后遇到问题知道如何查资料如何解决就好了。
感觉自己写博客速度忒慢了,写ES6笔记断断续续花了我十个小时……
最后呢,还是那句话——由自己整理写出博客的知识点才是真正牢牢掌握的知识点!至此,我对ES6语法的理解加深了很多。看到此文的你可以去试试用写博客的方式来复习知识点哦~
希望我写的东西能帮助到一些朋友。

关于我

VioletJack,内驱工程师专注于Vue前端相关的知识点整理、源码学习、内容分享。欢迎喜欢我文章的朋友关注我哦,我会努力产出优质内容让我们始终相信:code change world!

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

推荐阅读更多精彩内容