当前位置 : 主页 > 网络编程 > JavaScript >

ES6语法汇总 中 函数 数组 对象 set map

来源:互联网 收集:自由互联 发布时间:2021-06-28
gistfile1.txt 函数/** * Created by Administrator on 2017/11/12. */function log(x, y) { y = y||'world' //检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值
gistfile1.txt
函数

/**
 * Created by Administrator on 2017/11/12.
 */
function log(x, y) {
    y = y||'world'  //检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。
    console.log(x,y)
}
log('hello')  //hello world
log('hello','china')  //hello china
log('hello','')  //hello world

// ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。简洁多了
function newLog(x, y = 'world' ) {
    console.log(x,y)
}
newLog('hello','')  //hello world

function fetch(url, {body = '', method = 'GET', headers = {}}) {
    console.log(method)
}
fetch('http://example.com',{})  //GET
// fetch('http://example.com')  //error

function m1({x = 0, y = 0}={}) {
    console.log(x,y)
}
m1() //0 0
m1({x:2,y:2})  //不可以用等号 必须用冒号  2 2
m1({x:3})  //3 0

function m2({x,y}={x:2,y:2}) {
    console.log(x,y)
}
m2()  //2 2
m2({x:3}) //3 undefined

// 函数的 length 属性 length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数
console.log((function (a) {}).length)      // 1
console.log((function (a=5) {}).length)  //0
console.log((function (a, b = 1, c = 3) {}).length)  //1
// 这是因为length属性的含义是,该函数预期传入的参数个数。
// 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
console.log((function (a = 1, b, c) {}).length)  //0
console.log((function (a, b = 1, c) {}).length)  // 1


// 作用域
var a = 2
function f(x,y=x) {  //调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x
    console.log(y)
}
f(3)  //3


// var aa = 1
// function ff(aa=aa) {  //参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“
// }
// ff() //aa is not defined

let  foo = 'outer'
function bar(func=()=>foo) {
    let foo = 'inner'
    console.log(func())  //函数bar的参数func的默认值是一个匿名函数,返回值为变量foo
}
bar()     //outer


//其实编辑器很智能  他能识别 要是一个值就会都会亮起来
var xx = 1
function fuza(xx,y=function () {xx=2}) {
    var xx = 3
    y()
    console.log(xx)
}
fuza()  //3

//ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function add(...values) {
    let sum = 0
    for (var val of values){
        sum += val
    }
    return sum
}
console.log(add(2,5,3))  //10


function sortNumbers() {
    return Array.prototype.slice.call(arguments).sort()
}
console.log(sortNumbers(2,3,4))  //[2,3,4]

//第二种写法
const sortNumbers2 = (...numbers) => console.log(numbers.sort())
sortNumbers2(2,3,4,4) //[ 2, 3, 4, 4 ]



// 注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错
// function f(a, ...b, c) {
//     // ...
// }

// ES6 允许使用“箭头”(=>)定义函数。
// 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// let getTempItem = id => {id:2,name:'xuliang'}  //error
let getTempItem = id => ({id:2,name:'xuliang'})

// 箭头函数的一个用处是简化回调函数。
// [1,2,3].map(function (x) {
//     return x*x
// })
[1,2,3].map(x=>x*x)


function fooo() {     //setTimeout(code,millisec)  用于在指定的毫秒数后调用函数或计算表达式。
    setTimeout(()=>{console.log('id:',this.id)},100)
}
var id = 100
//call([thisObj[,arg1[, arg2[,   [,.argN]]]]])
// call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
// 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj
fooo.call({id:42})  //id: 42
// fooo({id:42})   //id undefined


//setInterval(code,millisec[,"lang"]) setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
function Timer() {
    this.s1 = 0
    this.s2 = 0
    setInterval(()=>this.s1++,1000)
    setInterval(function () {
        this.s2++;
    }, 1000);
}
var timer = new Timer()
setTimeout(()=>console.log('s1:',timer.s1),3100)  // s1: 3
setTimeout(()=>console.log('s2:',timer.s2),3100)   //s2: 0
//Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

// 箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。

function foooo() {
    return ()=>{
        return ()=>{
            return ()=>{
                console.log('id:',this.id)
            }
        }
    }
}

var fffff = fooo.call({id:1})  //这样调用也是一种方式
// var t1 =  fffff.call({id:2})()()





/**
 * Created by Administrator on 2017/11/13.
 */
// 箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。
// 函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
// 双冒号运算符的运算结果,还是一个对象,因此可以采用链式写法。

// 尾递归
// 递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
// 上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。
function factorial(n) {
    if (n===1) return 1
    else return n*factorial(n-1)
}

// 如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。
function factorial2(n, total) {
    if(n===1) return total
    else return factorial2(n-1,n*total)
}
console.log(factorial2(5,1))  //120


// 还有一个比较著名的例子,就是计算 Fibonacci 数列,也能充分说明尾递归优化的重要性。
// 非尾递归的 Fibonacci 数列实现如下。
function Fibonacci(n) {
    if (n<=1){return 1}
    return Fibonacci(n-1)+Fibonacci(n-2)
}

console.log(Fibonacci(10))
// console.log(Fibonacci(100))  //// 堆栈真的溢出

function Fibonacci2(n, ac1 = 1, ac2 = 1) {  //采用 ES6 的函数默认值变得简洁  可以所以调用时不用提供这个值
    if(n<=1){return ac2}
    return Fibonacci2(n-1,ac1,ac2+ac1)
}

console.log(Fibonacci2(100)) //100
console.log(Fibonacci2(1000))  //1000

//“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6 是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。

// 以下三种情况,都不属于尾调用。
//
// // 情况一
// function f(x){
//     let y = g(x);
//     return y;
// }
//
// // 情况二
// function f(x){
//     return g(x) + 1;
// }
//
// // 情况三
// function f(x){
//     g(x);
// }

// 尾递归优化只在严格模式下生效,那么正常模式下,或者那些不支持该功能的环境中,有没有办法也使用尾递归优化呢?回答是可以的,就是自己实现尾递归优化。
//
// 它的原理非常简单。尾递归之所以需要优化,原因是调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出。怎么做可以减少调用栈呢?就是采用“循环”换掉“递归”。
//
// 下面是一个正常的递归函数。











数组
/**
 * Created by Administrator on 2017/11/13.
 */
// 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1,2,3])
// 该运算符主要用于函数调用。
// 扩展运算符后面还可以放置表达式。
const arr = [...(true?['a']:[])]

// 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

// 数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
const a2 = a1;

// a2[0] = 2;
// a1 // [2, 2]


// 扩展运算符提供了数组合并的新写法。 [...arr1, ...arr2, ...arr3]

// 扩展运算符可以与解构赋值结合起来,用于生成数组。 [a, ...rest] = list

// 扩展运算符还可以将字符串转为真正的数组。[...'hello']

// 扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
    [1,'one'],
    [2,'two'],
    [3,'three']
])
let arre = [...map.keys()]
console.log(arre)    //[ 1, 2, 3 ]

//变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
const go = function* () {
    yield 1
    yield 2
    yield 3
}
console.log(...go())  //1 2 3

// 如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a:1,b:2}  //原来这是对象 不是map
// console.log(...obj.keys) //error
// console.log(...obj)  //error














/**
 * Created by Administrator on 2017/11/13.
 */
// 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1,2,3])
// 该运算符主要用于函数调用。
// 扩展运算符后面还可以放置表达式。
const arr = [...(true?['a']:[])]

// 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

// 数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
const a2 = a1;

// a2[0] = 2;
// a1 // [2, 2]


// 扩展运算符提供了数组合并的新写法。 [...arr1, ...arr2, ...arr3]

// 扩展运算符可以与解构赋值结合起来,用于生成数组。 [a, ...rest] = list

// 扩展运算符还可以将字符串转为真正的数组。[...'hello']

// 扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
    [1,'one'],
    [2,'two'],
    [3,'three']
])
let arre = [...map.keys()]
console.log(arre)    //[ 1, 2, 3 ]

//变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。
const go = function* () {
    yield 1
    yield 2
    yield 3
}
console.log(...go())  //1 2 3

// 如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a:1,b:2}  //原来这是对象 不是map
// console.log(...obj.keys) //error
// console.log(...obj)  //error


// Array.from() 可以通过以下方式来创建数组对象:
// 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
// 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
// 扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
let arrayLike = {
    "0":"a",
    "1":"b",
    "2":"c",
    length:3
}

let arr2 = Array.from(arrayLike)
console.log(arr2)   //[ 'a', 'b', 'c' ]
// 只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
// Array.from('foo');

// 实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
// var args = Array.from(arguments);

// Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

// Array.from()的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的Unicode字符,算作两个字符的bug。
function countSymbols(string) {
    return Array.from(string).length;
}



// Array.of方法用于将一组值,转换为数组。
console.log(Array.of(3,19,18))  //[ 3, 19, 18 ]
console.log(Array.of(3).length)  //1

// Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一
console.log(Array.of(undefined))  //[ undefined ]

// 数组实例的 copyWithin()
console.log( [1,2,3,4,5].copyWithin(0,3) )  //[ 4, 5, 3, 4, 5 ]
console.log( [1,2,3,4,5].copyWithin(0,3,4) ) //[ 4, 2, 3, 4, 5 ]

// 数组实例的 find() 和 findIndex()  ES6都不支持啊
// [1,4,-5].find( (n) => n<0 )
// 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
// [1,5,15,10].findIndex(function (value, index, arr) {
//     return value>9
// })

//另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。
console.log( [NaN].indexOf(NaN) )  //-1

// fill方法使用给定值,填充一个数组。
console.log( ['a','b','c'].fill(7) )   //[ 7, 7, 7 ]
console.log( ['a','b','c'].fill(7,1,2) )   //[ 'a', 7, 'c' ]


// 数组实例的 entries(),keys() 和 values()
for(let index of ['a','b'].keys()){
    console.log( index )    // 0 1
}for (let [index,elem] of ['a','b'].entries()){
    console.log(index,elem)
}

// 数组实例的 includes()
console.log( [1,2,3].includes(1) )  //true
console.log( [1,'a',NaN].includes('a') )  //true

// 另外,Map 和 Set 数据结构有一个has方法,需要注意与includes区分。
// Map 结构的has方法,是用来查找键名的,比如   Set 结构的has方法,是用来查找值的

// forEach方法
//     [,'a'].forEach((x,i) => console.log(i)); // 1


// ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
//
// forEach(), filter(), every() 和some()都会跳过空位。
// map()会跳过空位,但会保留这个值
// join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
// // filter方法
// ['a',,'b'].filter(x => true) // ['a','b']
//
// // every方法
//     [,'a'].every(x => x==='a') // true
//
// // some方法
//     [,'a'].some(x => x !== 'a') // false
//
// // map方法
//     [,'a'].map(x => 1) // [,1]
//
// // join方法
//     [,'a',undefined,null].join('#') // "#a##"
//
// // toString方法
//     [,'a',undefined,null].toString() // ",a,,"












对象 


/**
 * Created by Administrator on 2017/11/13.
 */
const foo = 'bar'
const baz = {foo}
console.log(baz)  //{ foo: 'bar' }


const bazz = {foo:'baz'}
console.log( bazz)  //{ foo: 'baz' }

function f(x, y) {
    return {x:x,y:y}
}
console.log( f(1,2) )    //{ x: 1, y: 2 }

const o = {
    method(){
        return "hello!"
    }
}

const oo = {
    method:function () {
        return "hello"
    }
}

// ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内
let lastword = 'last word';
const aa = {
    'fisrst':'hello',
    [lastword]:'world',
    ['a'+'bc']:123
}
console.log( aa['fisrst'] )  //hello  冒号不能少
console.log( aa[lastword] )  // world 冒号不能少
console.log( aa['last word'] )  //world
console.log( aa['lastword'] )  //undefined
console.log( aa['abc'] )   //123




// 方法的 name 属性 函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
const person = {
    sayName(){
        console.log('hello')
    }
}

console.log(person.sayName.name)  //sayName


// ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
// ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

// 不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
// +0 === -0 //true
// NaN === NaN // false
//
// Object.is(+0, -0) // false
// Object.is(NaN, NaN) // true



// Object.assign() Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
// 注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = {a:1}
const source1 = { b:2 }
const source2 = { c:3 }
Object.assign(target,source1,source2)
console.log(target)       //{ a: 1, b: 2, c: 3 }

// Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a:{b:2}}
const obj2 = Object.assign({},obj1)
obj1.a.b = 3
console.log( obj2.a.b )  //3

// 同名属性的替换/
const target22 = {a:{b:'c',d:'e'}}
const source22 = {a:{b:'hello'}}
console.log( Object.assign(target22,source22) )   //{ a: { b: 'hello' } }



// 常见用途
// Object.assign方法有很多用处。
// (1)为对象添加属性
class  Point{
    constructor(x,y){
        Object.assign(this,{x,y})   //Point.x 确实可以取得到
    }
}

// (2)为对象添加方法
// Object.assign(SomeClass.prototype,{  //很简明的写法
//     someMethod(arg1,arg2){},
//     anotherMethod(){}
// })

// (3)克隆对象
function clone(origin) {
    return Object.assign({},origin)
}
// 上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
// 不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone2(origin) {
    let originProto = Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto), origin);
}

// (4)合并多个对象
const merge = (target,...source222)=>Object.assign(target,...source222)



// 6 属性的可枚举性和遍历

// 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let objjj = {foo:123}
console.log( Object.getOwnPropertyDescriptor(objjj,'foo') )
//     { value: 123,
//     writable: true,
//     enumerable: true,
//     configurable: true }

// 描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性。
// 目前,有四个操作会忽略enumerable为false的属性。
//
// for...in循环:只遍历对象自身的和继承的可枚举的属性。
// Object.keys():返回对象自身的所有可枚举的属性的键名。
// JSON.stringify():只串行化对象自身的可枚举的属性。
// Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
// 属性的遍历
// ES6 一共有 5 种方法可以遍历对象的属性。
//
// (1)for...in
//
// for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
//
// (2)Object.keys(obj)
//
// Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
//
// (3)Object.getOwnPropertyNames(obj)
//
// Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
//
// (4)Object.getOwnPropertySymbols(obj)
//
// Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
//
// (5)Reflect.ownKeys(obj)
//
// Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
//
// 以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
//
// 首先遍历所有数值键,按照数值升序排列。
// 其次遍历所有字符串键,按照加入时间升序排列。
// 最后遍历所有 Symbol 键,按照加入时间升序排列。
// Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// // ['2', '10', 'b', 'a', Symbol()]
// 上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性。


// super 关键字
// 我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
const proto = {
    foo:'hello'
}
const objjjjj = {
    find(){
        return super.foo
    }
}
Object.setPrototypeOf(objjjjj,proto)
console.log( objjjjj.find() )   //hello


// Object.keys(),Object.values(),Object.entries()








/**
 * Created by Administrator on 2017/11/13.
 */
// ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
// Set 本身是一个构造函数,用来生成 Set 数据结构。
const sett = new Set([1,2,3,4,4])
console.log( ...sett )   //1 2 3 4

// Set 结构的实例有以下属性。
//
// Set.prototype.constructor:构造函数,默认就是Set函数。
// Set.prototype.size:返回Set实例的成员总数。
// Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
//
// add(value):添加某个值,返回 Set 结构本身。
// delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
// has(value):返回一个布尔值,表示该值是否为Set的成员。
// clear():清除所有成员,没有返回值。
// 上面这些属性和方法的实例如下。

sett.add(1).add(2).add(2)
console.log(sett)  //Set { 1, 2, 3, 4 }
//
// 遍历操作
// Set 结构的实例有四个遍历方法,可以用于遍历成员。
//
// keys():返回键名的遍历器
// values():返回键值的遍历器
// entries():返回键值对的遍历器
// forEach():使用回调函数遍历每个成员
// 需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
//

// 因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1,2,3])
let b = new Set([2,3,4])
//bing集
let union = new Set([...a,...b])
//jiaoji
let intersect = new Set([...a].filter(x => b.has(x)))
//差集
let difference = new Set([...a].filter(x => b.has(x)))


// 如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。

// // 方法一
// let set = new Set([1, 2, 3]);
// set = new Set([...set].map(val => val * 2));
// // set的值是2, 4, 6
//
// // 方法二
// let set = new Set([1, 2, 3]);
// set = new Set(Array.from(set, val => val * 2));
// // set的值是2, 4, 6



// Map
// JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
// 为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
const map = new Map([
    ['name','张三'],
    ['title','Author']
])
console.log(map)  //Map { 'name' => '张三', 'title' => 'Author' }
console.log(map.size)  //2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"


// 由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
const mappp = new Map();

const k1 = ['a'];
const k2 = ['a'];

mappp
    .set(k1, 111)
    .set(k2, 222);

console.log(mappp.get(k1) )// 111
console.log(mappp.get(k2)) // 222

// 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
let map = new Map();

map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3

map.set(NaN, 123);
map.get(NaN) // 123

// 遍历方法
// Map 结构原生提供三个遍历器生成函数和一个遍历方法。
//
// keys():返回键名的遍历器。
// values():返回键值的遍历器。
// entries():返回所有成员的遍历器。
// forEach():遍历 Map 的所有成员。


与其他数据结构的互相转换
(1)Map 转为数组

前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(...)。

const myMap = new Map()
    .set(true, 7)
    .set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2)数组 转为 Map

将数组传入 Map 构造函数,就可以转为 Map。

new Map([
    [true, 7],
    [{foo: 3}, ['abc']]
])
// Map {
//   true => 7,
//   Object {foo: 3} => ['abc']
// }
(3)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 }
(4)对象转为 Map

function objToStrMap(obj) {
    let strMap = new Map();
    for (let k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
(5)Map 转为 JSON

Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。

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"]]]'
(6)JSON 转为 Map

JSON 转为 Map,正常情况下,所有键名都是字符串。

function jsonToStrMap(jsonStr) {
    return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是数组转为 JSON 的逆操作。

function jsonToMap(jsonStr) {
    return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
WeakMap
含义
WeakMap结构与Map结构类似,也是用于生成键值对的集合。

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2

// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
WeakMap与Map的区别有两点。

首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
上面代码中,如果将数值1和Symbol值作为 WeakMap 的键名,都会报错。

其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
    [e1, 'foo 元素'],
    [e2, 'bar 元素'],
];
上面代码中,e1和e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明。这就形成了arr对e1和e2的引用。

一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存。

// 不需要 e1 和 e2 的时候
// 必须手动删除引用
arr [0] = null;
arr [1] = null;
上面这样的写法显然很不方便。一旦忘了写,就会造成内存泄露。

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"
上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

也就是说,上面的 DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}
上面代码中,键值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 内部的引用依然存在。

WeakMap 的语法
WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性。因为没有办法列出所有键名,某个键名是否存在完全不可预测,跟垃圾回收机制是否运行相关。这一刻可以取到键名,下一刻垃圾回收机制突然运行了,这个键名就没了,为了防止出现不确定性,就统一规定不能取到键名。二是无法清空,即不支持clear方法。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。

const wm = new WeakMap();

// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
网友评论