1、Symbol
Symbol是es6引入的一种原始数据类型,一种类似字符串的数据类型,表示独一无二的值。Symbol函数不能使用new命令,否则会报错。
javascript其中数据类型: undefined、null、Boolean、String、Number、Object、Sysmol
注:Symbol值不能与其他类型的值进行运算,会报错。
ley sym = Symbol('my symbol);
"your symbol is" + sym
TypeError: can't convert symbol to string
但是,Symbol值可以显式转为字符串
let sys = Symbol('my symbol');
String(sys); // 'Symbol(My symbol)'
sys.toString(); 'Symbol(My symbol)'
Symbol.prototype.description
const sym = Symbol('foo');
String(sym); // 'Symbol('foo')'
sym.toString(); // 'Symbol('foo')'
sym.description // 'foo'
Symbol可以作为对象的属性名,但是不能用点运算符
let mySymbol = Symbol();
let obj = {
[mySymbol]: 'hello world'
}
Object.defineProperty(obj, mySymbol, {value: 'hello world'});
obj[mySymbol] // 'hello world'
Symbol作为属性名时,在遍历对象的属性时,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
let obj = {};
let a = Symbol('a');
obj[a] = 'hello';
const objectSymbol = Object.getOwnPropertySymbols('obj');
objectSymbol // [Symbol(a)]
Reflect.ownKeys()方法会返回所有类型的键名,包括常规键名和Symbol键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
}
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
消除魔术字符串:在代码中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。
例:
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
const shapeType = {
triangle: 'Triangle'
};
// 上述代码中的 'Triangle' 替换成 shapeType.triangle
Symbol.for()、Symbol.keyFor()
Symbol.for(): 可以重新使用同一个Symbol值,Symbol接受一个字符串为参数,然后搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建一个以该字符串作为名称的Symbol值,并将其注册到全局。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.keyFor(s1); // 'foo'
2、Set、WeakSet、Map、WeakMap 数据结构
1)Set本身是一个构造函数,用来生成Set生成类似数组的数据结构,但成员值都是唯一的,没有重复的值。
set结构的实例有一下属性: add、size、delete、has、clear等方法
let s = new Set([1, 2, 3, 4]); // 可接受数组作为参数,用来初始化,并去除数组中重复的方法。
s.add(5); // add方法向Set结构加入成员,返回set结构本身
s.size(); // 获取实例的成员总数,
s.delete(value); // 删除某个值, 返回一个Boolean值,表示删除是否成功。
s.has(value); // 返回一个人Boolean布尔值,表示值是否为Set的成员
s.clear(); // 清除所有成员,没有返回值。
遍历操作:
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries(): 返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历成员
2)、WeakSet结构与Set类似,也是不重复的值的集合
与Set之间的区别:
WeakSet的成员只能是对象,而不是其他类型的值。
WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,即就是其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占的内存,不用考虑该对象是否还存在于WeakSet之中。
3)、Map
Map是类似javascript对象,本质上是键值对的集合(hash结构),键范围不限于字符串,各种类型的值都可以当作键。(传统对象只能用字符串当做键)
Map对象的方法:add、get、has、delete等
const map = new Map();
const o = {p: 'hello world'};
// 0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
map.set(o, 'content'); // 向map添加一个属性
map.size(); // 1 获取map对象属性的个数
map.get(key); // 'content' 读取key对应的键值,如果找不到,返回undefined
map.has(key); // true 是否具有某个属性 返回Boolean
map.delete(key); // true 删除某个属性,返回Boolean
Map的遍历方法
Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。
Map与其他数据结构的相互转换
Map转为数组:使用扩展运算符(...)
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
数组转为Map:将数组转入Map构造函数,就可以转化为Map
const map = new Map([[true, 7], ['name';, 'koga']]) // {true: 7, name: 'koga}
Map转为对象:如果Map的键不是字符串,键名会被转换为字符串,再作为对象的键名
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap) // {yes: true, no: false}
对象转化为Map:对象转为Map 可以通过Object.entries()
let obj = {"a": 1, "b": 2};
let map = new Map(Object.entries(obj));
也可以自己实现一个转换函数
function objToMap(obj){
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[key]);
}
return strMap
}
objToMap({yes: true, no: false}); // Map {"yes" => true, "no" => false}
Map 转为 JSON: 分为两种情况
Map的键名都是字符串
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap); // '{"yes":true,"no":false}'
Map键名有非字符串,可以转为数组JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
JSON转为Map
正常情况下,所有键名都是字符串
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
另外特殊情况,整个JSON就是一个数组
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
4、WeakMap
weakMap与Map的区别
weakMap只接受对象作为键名(null除外),不能接受其他类型的值作为键名。
weakMap的键名所指向的对象,不计入垃圾回收机制。
WeakMap只有四个方法:
get()、set()、has()、delete()等
generator和async
const getData = () => new Promise(resolve => {
setTimeout(() => {
resolve('data')
}, 1000)
});
5、generator
function* testG(){
// await 被编译成了yield
const data = yield getData();
console.log('data: ', data);
const data2 = yield getData();
console.log('data2: ', data2);
return 'success';
}
const gen = testG();
const genPromise = gen.next().value;
genPromise.then((value1) => {
const genPromise2 = gen.next(value1).value;
genPromise2.then((value2) => {
const genPromise3 = gen.next(value2).value;
console.log(genPromise3);
})
})
6、async/await
async function test() {
const data = await getData();
console.log('data: ', data);
const data2 = await getData();
console.log('datas: ', data2);
return 'success'
}
使用generator实现async
function* testG(){
// await 被编译成了yield
const data = yield getData();
console.log('data: ', data);
const data2 = yield getData();
console.log('data2: ', data2);
return 'success';
}
function asyncToGenerator(generatorFunc){
return function (){
// 先调用generator函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, arguments);
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let generatorResult;
// 这个方法需要包裹在try catch中
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
try {
generatorResult = gen[key](arg);
}catch (error){
return reject(error);
}
// gen.next() 得到的结果是一个 { value, done } 的结构
const {value, done} = generatorResult;
if(done){
// 如果已经完成了 就直接resolve这个promise
// 这个done是在最后一次调用next后才会为true
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
// 这个value也就是generator函数最后的返回值
return resolve(value);
}else{
// 除了最后结束的时候外,每次调用gen.next()
// 其实是返回 { value: Promise, done: false } 的结构,
// 这里要注意的是Promise.resolve可以接受一个promise为参数
// 并且这个promise参数被resolve的时候,这个then才会被调用
// 这个value对应的是yield后面的promise
// value这个promise被resove的时候,就会执行next
// 并且只要done不是true的时候 就会递归的往下解开promise
// 对应gen.next().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // 此时done为true了 整个promise被resolve了
// // 最外部的test().then(res => console.log(res))的then就开始执行了
// })
// })
return Promise.resolve(value).then((val) => {
step('next', val);
},
// 如果promise被reject了 就再次进入step函数
// 不同的是,这次的try catch中调用的是gen.throw(err)
// 那么自然就被catch到 然后把promise给reject掉啦
(err) => {
step('throw', err);
})
}
}
step('next');
})
}
}
const test = asyncToGenerator(testG);
test().then((res) => console.log(res))
6、Proxy和Object.defineProperty区别
proxy 的意思是代理,一般叫他拦截器,可以拦截对象上的一个操作。
用法如下:
通过new的方式创建对象,第一个参数是被拦截的对象,第二个参数是对象操作的描述。实例化后返回一个新对象,当我们对这个新对象进行操作时就会调用我们描述中对应的方法。
new Proxy(target, {
get(target, property) {
},
set(target, property){
},
deleteProperty(target, property){
}
})
Proxy 和 Object.definedProperty的区别:
Object.definedProperty 只能监听到属性的读写。而Proxy除读写外还可以监听属性的删除 ,方法的调用等。通常情况下我们想要见识数组的变化,基本要依赖重写数组方法的方式实现,这也是vue的实现方式,而Proxy可以直接监视数组的变化。
const list = [1, 2, 3];
const list = new Proxy(list, {
set(target, property, value) {
target[property] = value;
return true;
}
});
Proxy是以非入侵的方式监管了对象的读写,而definedProperty需要按特定的方式定义对象。