欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ES6常用语法(下)

程序员文章站 2023-11-03 15:26:21
Symbol类型 ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的 ......
  • symbol类型
     es5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 es6 引入symbol的原因
 
     es6 引入了一种新的原始数据类型symbol,表示独一无二的值。它是 javascript 语言的第七种数据类型,前六种是:undefinednull、布尔值(boolean)、字符串(string)、数值(number)、对象(object)
 
     symbol 值通过symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 symbol 类型。凡是属性名属于 symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
 
let s = symbol();

typeof s
// "symbol"
上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是 symbol 数据类型,而不是字符串之类的其他类型
注意,symbol函数前不能使用new命令,否则会报错。这是因为生成的 symbol 是一个原始类型的值,不是对象。也就是说,由于 symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
symbol函数可以接受一个字符串作为参数,表示对 symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
 
let s1 = symbol('foo');let s2 = symbol('bar');

s1 // symbol(foo)
s2 // symbol(bar)

s1.tostring() // "symbol(foo)"
s2.tostring() // "symbol(bar)"
上面代码中,s1s2是两个 symbol 值。如果不加参数,它们在控制台的输出都是symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。
 
注意,symbol函数的参数只是表示对当前 symbol 值的描述,因此相同参数的symbol函数的返回值是不相等的。
 
 
// 没有参数的情况
let s1 = symbol();
let s2 = symbol();

s1 === s2 // false

// 有参数的情况
let s1 = symbol('foo');
let s2 = symbol('foo');

s1 === s2 // false
上面代码中,s1s2都是symbol函数的返回值,而且参数相同,但是它们是不相等的。
symbol 值也可以转为布尔值,但是不能转为数值
 
 
let sym = symbol();
boolean(sym) // true
!sym // false
if (sym) {
// ...
}

number(sym) // typeerror
sym + 2 // typeerror
 
作为属性名的 symbol
由于每一个 symbol 值都是不相等的,这意味着 symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性,能防止某一个键被不小心改写或覆盖。
 
// 第一种写法
let a = {};
a[mysymbol] = 'hello!';
// 第二种写法
let a = {
  [mysymbol]: 'hello!'};
// 以上写法都得到同样结果
a[mysymbol] // "hello!"
注意,symbol 值作为对象属性名时,不能用点运算符。
 
const mysymbol = symbol();
const a = {};

a.mysymbol = 'hello!';
a[mysymbol] // undefined
a['mysymbol'] // "hello!"
上面代码中,因为点运算符后面总是字符串,所以不会读取mysymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 symbol 值。
同理,在对象的内部,使用 symbol 值定义属性时,symbol 值必须放在方括号之中。
 
 
let s = symbol();

let obj = {
  [s]: function (arg) { ... }};

obj[s](123);
上面代码中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个 symbol 值。
 
 

 
 
  • set和map结构
es6 提供了新的数据结构 set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
set 本身是一个构造函数,用来生成 set 数据结构。
 
const s = new set();

[2, 3, 5, 4, 5, 2, 2].foreach(x => s.add(x));

for (let i of s) {
  console.log(i);}
// 2 3 5 4
//s=[2,3,5,4] 
//数组去重的方法(1)
 
上面代码通过add方法向 set 结构加入成员,结果表明 set 结构不会添加重复的值。
 
set 函数可以接受一个数组(获取dom的nodelist对象)作为参数,用来初始化。
 
// 例一
const set = new set([1, 2, 3, 4, 4]);
          [...set]
// [1, 2, 3, 4]

// 例二
const items = new set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 例三
const set = new set(document.queryselectorall('div'));
set.size // 56
上面代码也展示了一种去除数组重复成员的方法。
 
// 去除数组的重复成员(2)
[...new set(array)]
向 set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。set 内部判断两个值是否不同,使用的算法叫做“same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是nan等于自身,而精确相等运算符认为nan不等于自身。
 
let set = new set();
let a = nan;
let b = nan;
set.add(a);
set.add(b);
set // set {nan}
 
set 结构的实例有以下属性
  • constructor:构造函数,默认就是set函数。
  • size:返回set实例的成员总数。
set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面四个操作方法
 
- add(value):添加某个值,返回 set 结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为set的成员。
- clear():清除所有成员,没有返回值。
 
s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false
 
array.from方法可以将 set 结构转为数组。
 
 
const items = new set([1, 2, 3, 4, 5]);
const array = array.from(items);
 
set 结构的实例默认可遍历
 
let set = new set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);}
// red
// green
// blue
 
set 结构的实例与数组一样,也拥有foreach方法,用于对每个成员执行某种操作,没有返回值。
 
 
set = new set([1, 4, 9]);
set.foreach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
上面代码说明,foreach方法的参数就是一个处理函数。该函数的参数与数组的foreach一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。
 
扩展运算符和 set 结构相结合,就可以去除数组的重复成员。
 
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new set(arr)];
// [3, 5, 2]
 
map结构
javascript 的对象(object),本质上是键值对的集合(hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
 
es6 提供了 map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,object 结构提供了“字符串—值”的对应,map 结构提供了“值—值”的对应,是一种更完善的 hash 结构实现。如果你需要“键值对”的数据结构,map 比 object 更合适。
 
 

map 结构的实例有以下属性和操作方法。

(1)size 属性

 
size属性返回 map 结构的成员总数。
 
const map = new map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
 

(2)set(key, value)

 
set方法设置键名key对应的键值为value,然后返回整个 map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
 
const m = new map();

m.set('edition', 6)      // 键是字符串
m.set(262, 'standard')    // 键是数值
m.set(undefined, 'nah')  // 键是 undefined

 
 
set方法返回的是当前的map对象,因此可以采用链式写法。
 
let map = new map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');
 
 
(3)get(key)
 
get方法读取key对应的键值,如果找不到key,返回undefined
 
const m = new map();

const hello = function() {console.log('hello');};
m.set(hello, 'hello es6!') // 键是函数

m.get(hello) // hello es6!

 
 
(4)has(key)
 
has方法返回一个布尔值,表示某个键是否在当前 map 对象之中。
 
 
const m = new map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')    // true
m.has('years')      // false
m.has(262)          // true
m.has(undefined)    // true

 
 
 
(5)delete(key)
 
delete方法删除某个键,返回true。如果删除失败,返回false
 
const m = new map();
m.set(undefined, 'nah');
m.has(undefined)    // true

m.delete(undefined)
m.has(undefined)      // false

 
(6)clear()
 
clear方法清除所有成员,没有返回值。
 
let map = new map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
 

map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • foreach():遍历 map 的所有成员。
 
需要特别注意的是,map 的遍历顺序就是插入顺序
 
 
const map = new map([
  ['f', 'no'],
  ['t',  'yes'],]);

for (let key of map.keys()) {
  console.log(key);}
// "f"
// "t"
for (let value of map.values()) {
  console.log(value);}
// "no"
// "yes"
for (let item of map.entries()) {
  console.log(item[0], item[1]);}
// "f" "no"
// "t" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);}
// "f" "no"
// "t" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);}
// "f" "no"
// "t" "yes"
map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
 
const map = new map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],]);

[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
 

 
 
  • generators生成器函数
     generator 函数是 es6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
     generator 函数有多种理解角度。语法上,首先可以把它理解成,generator 函数是一个状态机,封装了多个内部状态。
     执行 generator 函数会返回一个遍历器对象,也就是说,generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 generator 函数内部的每一个状态。
     形式上,generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
 
function* helloworldgenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';}

var hw = helloworldgenerator();
上面代码定义了一个 generator 函数helloworldgenerator,它内部有两个yield表达式(helloworld),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
 
     然后,generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
 
yield 表达式
 
由于 generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

 
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined
 
     下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
 
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

上面代码一共调用了四次next方法。

第一次调用,generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hellodone属性的值false,表示遍历还没有结束。

第二次调用,generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值worlddone属性的值false,表示遍历还没有结束。

第三次调用,generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

 
第四次调用,此时 generator 函数已经运行完毕,next方法返回对象的value属性为undefineddone属性为true。以后再调用next方法,返回的都是这个值。
 
总结一下,调用 generator 函数,返回一个遍历器对象,代表 generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。