余为前端菜鸟,感姿式水平匮乏,难观前端之大局。遂决定循前端知识之脉络,以兴趣为引,辅以几分坚持,望于己能解惑致知、于同道能助力一二,岂不美哉。前端
本系列代码及文档均在 此处java
依然很忙,继续啃老本。。。git
javaScript第七种原始数据类型Symbolgithub
let s = Symbol('foo')
数组
经过Symbol函数生成,每一个Symbol类型的变量值都独一无二,做为一种相似于字符串的数据结构,能够避免变量名冲突数据结构
Symbol函数接收一个参数用于描述该Symbol实例,不影响生成的Symbol实例的值app
Symbol值不能与其余类型进行运算(模板字符串中也不能够),能够显示转为字符串和布尔值(String(), Boolean())less
Symbol做为属性名函数
.
,由于.
是去取字符串对应属性名[s]
不然也会被当作字符串Symbol.for
, Symbol.keyFor
ui
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo"); // 先搜索全局,已存在该key则返回已存在的
Symbol.keyFor(s2) // undefined
复制代码
Symbol.hasInstance
对象的Symbol.hasInstance
属性指向一个内部方法,其余对象使用instanceOf判断实例时,会调用这个内部方法
class Even {
static [Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0;
}
}
1 instanceOf Even
复制代码
Symbol.isConcatSpreadable
表示该对象用于Array.prototype.concat()时是否能够展开,数组默承认展开,默认值为undefined,对象默认不可展开
Symbol.species
指向当前对象的构造函数,创造实例时会调用这个方法,即便用该属性返回的函数做为构造函数
static get [Symbol.species]() {
return this;
}
复制代码
Symbol.match
, Symbol.replace
, Symbol.split
, Symbol.search
Symbol.iterator
指向该对象的默认遍历器方法
对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器
详见后续章节
Symbol.toPrimitive
Symbol.toStringTag
指向一个方法,在该对象上调用Object.prototype.toString()
时,若是该属性存在,则他的返回值会出如今toString方法返回的字符串之中,好比[Object Array]
新增内置对象举个例子:JSON[Symbol.toStringTag]:'JSON'
Set构造函数生成,成员值惟一,(判断与===区别在于NaN),两个空对象视为不一样
实例属性和方法
Set.prototype.constructor
, Set.prototype.size
add(value)
, delete(value)
, has(value)
, clear()
Array.from能够将Set转为数组
// 数组去重
function dedupe(array) {
return Array.from(new Set(array));
// return [...new Set(array)]
}
复制代码
遍历
Set.prototype[Symbol.iterator] === Set.prototype.values
...
内部使用for ... of
,故可使用[...Set]
,转为数组后能够方便使用数组方法如map和filternew WeakSet()
能够接收任何具备 Iterable
接口的对象做为参数,但必须注意加入WeakSet
的成员必须为对象add(value)
, delete(value)
, has(value)
,没有size属性,不可遍历(没有forEach和clear方法)键值对的集合(Hash结构),键和对象不同,不局限于字符串。
任何具备 Iterator 接口、且每一个成员都是一个双元素的数组的数据结构均可以做为Map构造函数的参数
只有对同一个对象的引用或者严格相等的简单类型(包括NaN)才会生成同样的Map
实例属性和方法
Map.prototype.constructor
, Map.prototype.size
set()
, get()
, delete(value)
, has(value)
, clear()
遍历
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
复制代码
键名只能为对象
WeakMap的键名所指向的对象,不计入垃圾回收机制
WeakMap有如下三个方法:get
, set
, delete(value)
, has(value)
,没有size属性,不可遍历(没有forEach和clear方法)
let a = {}
Object.defineProperty(a, 'b', {
set(x) {
if (x>100) {
throw new RangeError('invalid range')
}
this.b = x
}
})
复制代码
动手之后发现一个问题...这样会栈溢出,由于在set内再set了b的值,无限循环...变通一下:let a = {}
Object.defineProperty(a, 'b', {
get(x) {
return this.c
}
set(x) {
if (x>100) {
throw new RangeError('invalid range')
}
this.c = x
}
})
复制代码
然而总要这么写感受很麻烦,并且若是是对一类属性进行操做时,重复写很不必,换用Proxy写法:let a = {}
let handler = {
set(obj, prop, value, receiver) {
if (prop === 'b') {
if (value>100) {
throw new RangeError('invalid range')
}
}
obj[prop] = value
}
}
let proxy = new Proxy(a, handler)
复制代码
看起来也舒服多了,并且能够根据属性名在set方法内作判断,更可扩展代理proxy
let target = {};
let handler = {};
let proxy = new Proxy(target, handler);
// 将代理的全部内部方法转发至目标
proxy.a = 1 => target.a = 1;
target.b = 4 => proxy.b = 4;
target !== proxy
target.__proto__ === proxy.__proto__
// 应在代理对象上操做,代理才能生效
handler = {get(){return 12}}
target.v // undefined
proxy.v // 12
复制代码
Proxy支持的拦截操做
get(target, propKey, receiver) // proxy.foo, proxy['foo']
set(target, propKey, value, receiver) //proxy.foo = v, proxy['foo'] = v
has(target, propKey) // propKey in proxy
deleteProperty(target, propKey) // delete proxy[propKey]
ownKeys(target) // Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy)
getOwnPropertyDescriptor(target, propKey) // Object.getOwnPropertyDescriptor(proxy, propKey)
defineProperty(target, propKey, propDesc) // Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs)
preventExtensions(target) // Object.preventExtensions(proxy)
getPrototypeOf(target) // Object.getPrototypeOf(proxy)
isExtensible(target) // Object.isExtensible(proxy)
setPrototypeOf(target, proto) // Object.setPrototypeOf(proxy, proto)
apply(target, object, args) // 拦截 Proxy 实例做为函数调用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
construct(target, args) // new proxy(...args)
复制代码
代理句柄handler 句柄对象的方法能够复写代理的内部方法,具体为上述的14种。
举个🌰
function Tree() {
return new Proxy({}, handler);
}
var handler = {
get: function (target, key, receiver) {
if (!(key in target)) {
target[key] = Tree(); // 自动建立一个子树
}
return Reflect.get(target, key, receiver);
}
}
var tree = new Tree()
tree.branch1.branch2.twig = "green"
复制代码
再来个🌰
// 实现对in操做符隐藏属性
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);
'_prop' in proxy // false
复制代码
特别注意
若是目标对象不可扩展或者目标对象的属性不可写或者不可配置时,代理不能生效,可能会报错
需注意一些特定的方法对返回值有要求,不如重写isExtensible方法时,返回值与目标对象的isExtensible属性应一致,不然会报错
利用代理重写能够作不少事情好比隐藏属性、对某些属性、操做符屏蔽、拦截内在方法而且加上本身想要的逻辑处理去获得预期结果等
Proxy.revocable
返回一个对象,proxy属性对应Proxy实例,revoke属性为revoke方法能够取消Proxy实例
```js
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 1
revoke()
proxy.foo // TypeError: Revoked
```
复制代码
this问题
// jane的name属性实际存储在外部的WeakMap对象的_name上,致使后续取不到值
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined
复制代码
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
复制代码
静态方法
对应于Proxy可覆写的方法,有13个静态方法
注意
let p = {
a: 'a'
};
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value, receiver)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
// defineProperty
复制代码
在Reflect.set传入receiver的时候触发了Proxy.defineProperty,不传入receiver时不会触发defineProperty拦截
复制代码
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
/**************************/
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
复制代码
why Iterator
js中数据集合的概念愈来愈多,若是能有一种统一的访问方式将是极好的。Iterator的设计就基于此,经过为相应数据结构部署iterator接口让该数据结构可经过统一的方式:for...of遍历
遍历过程:
// 遍历器生成函数
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
复制代码
一种数据结构,只要部署了Iterator接口,就视为可遍历的
Symbol.iterator
属性键为Symbol对象,值为一个函数,即遍历器生成函数,执行该函数会返回一个遍历器对象,该对象具备一个next方法,调用该方法能够返回{value, done}对象,表明了当前成员的信息class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
复制代码
实现指针
function Node(value) {
this.value = value;
this.next = null
}
// for...of时会调用改遍历器生成函数
Node.prototype[Symbol.iterator]= function() {
// 返回的遍历器对象
var iterator = {
next: next
}
// 当前成员
var current = this
next() {
if(current) {
var value = current.value;
// 移动指针
current = current.next;
return { done: false, value: value };
}
return { done: true, value: undefined };
}
return iterator
}
// 新建对象,由于在原型上实现的遍历器生成函数,因此每一个实例都实现了遍历器接口
var one = new Node(1);
var two = new Node(2);
var three = new Node(3);
// 当前成员的next指向下一个成员,在next方法中实现指针移动
one.next = two;
two.next = three;
// 对对象使用for...of时,去查找[Symbol.iterator]属性,找到后循环调用next方法,直到返回值得done属性为true
for (var i of one){
console.log(i); // 1, 2, 3
}
复制代码
若是Symbol.iterator
方法对应的不是遍历器生成函数,则会报错
解构赋值
扩展运算符
任何实现了Iterator接口(可遍历)的数据结构均可以经过...
将其转化为数组
yield*
后跟一个可遍历数据结构时,会调用该结构的遍历器接口
for...of
, Array.from
, Map
, Set
, Promise.all()
, Promise.race()
字符串
for...of可以正确识别32位UTF-16字符
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
// 固然你也能够修改Symbol.iterator方法达到你想要的遍历结果
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
复制代码
return, throw方法
这两个方法都是在设置遍历器生成函数时可选的,通常配合generator使用,因此下次再说
数组
Map, Set
// Map遍历返回的是数组[k, v],Set返回的是值
for (let [key, value] of map) {
console.log(key + ' : ' + value);
}
复制代码
类数组对象
利用Array.from将其转化为数组,再使用数组的遍历器接口用for...of实现遍历
虽发表于此,却毕竟为一人之言,又是每日学有所得之笔记,内容未必详实,看官老爷们还望海涵。