餘爲前端菜鳥,感姿式水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣爲引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。前端
本系列代碼及文檔均在 此處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實現遍歷
雖發表於此,卻畢竟爲一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。