《ES6标准入门》阅读笔记——-symbol及proxy/reflect

写在前面

本系列为在我读完阮一峰老师的《ES6标准入门》第二版之后,所做的阅读笔记的整理。

许多初学ES6, 同时和我一样初次阅读阮老师的这本书的时候, 读第一遍会越发的困惑, 因为阮老师上面说的很多的定义,方法,之前都是没有见过的, 读到后面才发现, 哦~原来是这样.

有一句话叫做”大神的世界我们不懂”, 所以我在初读第一遍《ES6标准入门》这本书的时候,也是踩了不少坑,读书的时候查阅了不少的资料.

所以,在这一系列的笔记教程中,我会从一个初学者的角度,向您讲述ES6的相关知识,在后面介绍的知识我会尽量不提前用,即使提前使用,也会同时做好标注,避免了阅读时各种查阅资料的烦恼.

系列博客将采用一个一个的样例,来说明书中的精华部分(当然,这只是我认为的),同时引导新手,快速入门ES6,并逐步将其投入到生产实践中。

同时,在阅读前也提醒您, 为了系统连贯性的学习ES6的基础知识,建议您从我的博客第一章开始阅读,当然,如果您对对应的知识已经有所了解,那么可以跳转到任意章节阅读,每一篇博客名中均有介绍该博客中涉及到的ES6的内容.

阮一峰老师的这本书是开源的,在其官方博客就可以下载到,但是我强烈建议大家去购买一本书, 一是方便自己查阅ES6中新增的众多API, 二也是表达一下对大神的敬仰.

Symbol

在ES6之前,当我们命名变量的时候,总是会担心,如果我把之前的变量改写了该怎么办呢

那么,现在ES6推出了继 null undefined string number boolean 这五中数据类型以外的第六种简单数据类型symbol

推出的主要目的在于,解决属性名冲突的问题

1
2
let s = Symbol()
console.log(typeof s) // symbol

symbol一般会传入一个字符串作为不同symbol实例的描述,方便进行区分

1
2
3
4
let a = Symbol('a')
let b = Symbol('b')
console.log(a, b) // Symbol(a) Symbol(b)
console.log(a.toString(), b.toString()) // Symbol(a) Symbol(b)

我们查看控制台的打印结果,会发现symbol的值是以红色显示的,而普通字符串则以灰色显示

symbol的几个特征

symbol函数的参数只表示对当前symbol值的表示,因此相同参数的symbol函数的返回值是不相等的

symbol无法与其他类型值进行运算,否则会报错

symbol可以转为布尔值,但是无法转为数值

symbol作为变量时,不能使用点运算符

因为symbol是一个特殊的基本类型,因此使用点运算符是不行的,这样会被理解为是在使用字符串作为变量名称

那么这个时候,可以使用方括号加上symbol名称的方式,来调取这个symbol值

1
2
3
4
5
6
7
8
9
10
var mySymbol = Symbol('aaa')
var a = {}
a.mySymbol = 'hello'
console.log(a[mySymbol]) // undefined
console.log(a.mySymbol) // hello
console.log(a['mySymbol']) // hello

let c = Symbol
let d = c
console.log(d, d === c) // ƒ Symbol() { [native code] } true

作为属性名的symbol

symbol作为属性名,该属性不会出现在for…in for…of 循环中,也不会被Object.keys()/Object.getOwnPropertyNames()返回

但它也不是私有属性,有一个Object.getOwnPropertSymbols方法可以获取指定对象的所有symbol属性名

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {}
var a = Symbol('a')
var b = Symbol.for('b')

obj[a] = 'hello'
obj[b] = 'world'

var objectSymbols = Object.getOwnPropertySymbols(obj)
console.log(objectSymbols) // [Symbol(a), Symbol(b)]

var symbolNames = Object.getOwnPropertyNames(obj)
console.log(symbolNames) // []

由以上可知,使用getOwnPropertySymbols() 方法可以得到对应的symbol值,而getOwnPropertyNames() 是得不到对应的symbol值的

Reflect.ownKeys(obj)

ES6中定义了一个新的API Reflect.ownKeys(obj) 可以返回所有类型的键名称,包括常规键名和symbol键名

1
2
3
4
5
6
7
8
9
10
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
}

console.log(Object.keys(obj)) // ["enum", "nonEnum"]

console.log(Reflect.ownKeys(obj))
//["enum", "nonEnum", Symbol(my_key)]

Symbol.for() 和 Symbol.keyFor()

有时候我们希望重新使用同一个symbol值, Symbol.for方法可以做到这一点,它接收一个字符串作为参数,然后搜索没有以该字符串作为名称的Symbol值. 如果有,就返回这个Symbol值, 否则就新建一个以该字符串为名称的Symbol值

1
2
3
4
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')

console.log(s1 === s2) // true

上面的代码中,虽然s1和s2都是Symbol值,但是它们都是同一个Symbol.for生成的,所以是同一个值

Symbol()和Symbol.for()两种写法,都会生成新的Symbol,但是后者会被登记在全局环境中供 搜索,但是前者不会

1
2
console.log(Symbol.for('bar') === Symbol.for('bar')) // true
console.log(Symbol('bar') === Symbol('bar')) // false

上面的代码中,由于Symbol()没有登记机制,所以每次调用都会返回不同的值

Symbol.keyFor()方法用于返回一个已经登记的Symbol类型值的key

1
2
3
4
5
let s3 = Symbol.for('aaa')
let s4 = Symbol('bbb')

console.log(Symbol.keyFor(s3)) // aaa
console.log(Symbol.keyFor(s4)) // undefined

Proxy概述

proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程, 即对编程语言进行编程

Proxy 可以理解为在目标对象前架设一个拦截层,外界对该对象的访问都必须先通过这一层拦截,因此提供了一钟机制来对外界的访问进行过滤和改写

所以,Proxy 也可以理解为一种代理器

1
2
3
4
5
6
7
8
9
10
11
12
13
let 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 // setting count
obj.count++ // getting count setting count

上面的代码说明, proxy实际上重载了点运算符, 即用自己的定义覆盖了语言的原始定义

一个小技巧是,可以将Proxy对象设置到object.proxy属性,从而可以在object对象上调用

1
2
3
var object = {
proxy: new Proxy(target, handler)
}

那么我们就可以直接定义一个proxy对象,并在需要的时候使用Object.create() 进行对象的创建即可

1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, property) {
return 35
}
})

let obj = Object.create(proxy)

console.log(obj.time)

Reflect

Reflect对象和Proxy对象一样, 也是ES6为了操作对象而提供的新api

reflect对象的设计目的主要有以下几个

将object对象的一些明显属于语言层面的方法放到reflect对象上. 现阶段, 某些方法同时在object和reflect对象上部署, 未来的新方法将只部署在reflect对象上

修改某些object方法的返回结果, 让其变得更加合理, 如:Object.defineProperty(obj, name, desc) 在无法定义属性时会抛出一个错误, 而Reflect.defineProperty(obj, name, desc)则会返回一个false

让Object操作都变成函数行为, 某些object操作是命令式, 比如name in objdelete obj[name], 而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让他们变成了函数行为

Reflect对象的方法和Proxy对象的方法一一对应, 只要是Proxy对象的方法, 就能在Reflect对象上找到相应的方法. 也就是说, 不管Proxy怎么修改默认行为, 你总可以在Reflect上获取默认行为

1
2
3
4
5
6
7
8
9
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver)
if(success) {
log('property ' + name + ' on ' + target + ' set to ' + value)
}
return success
}
})

上面的代码中,proxy方法拦截了target对象的属性赋值行为, 它采用Reflect.set方法赋值给对象的属性, 然后再部署额外的功能.

(完)

文章目录
  1. 1. 写在前面
  2. 2. Symbol
    1. 2.1. symbol的几个特征
    2. 2.2. 作为属性名的symbol
    3. 2.3. Reflect.ownKeys(obj)
    4. 2.4. Symbol.for() 和 Symbol.keyFor()
  3. 3. Proxy概述
  4. 4. Reflect
|