SOURCE

console 命令行工具 X clear

                    
>
console
//for(let i = 0; i < 10 ; i++){}
//console.log("test let", i);

// compare
var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = function() {
        console.log(i);
    }
}
a[6]();

var b = [];
for (let j = 0; j < 10; j++) {
    b[j] = function () {
        console.log('test let', j);
    }
}
b[6]();


if (true) {
    var testLocalVar = 'hhahaahah';
}


console.log(testLocalVar);

//chapter 8
/*
 * 1.函数参数的默认值
 *  基本用法
 *  与解构赋值默认值结合
 *  参数默认值的位置
 *  函数的length属性
 *  作用域
 */
 
 function log(x, y) {
     if (typeof y === 'undefined') {
         y = 'world';
     }

     console.log('es5给参数y设置默认取值的方式', x, y);
 }

log('hello');
log('hello', 'xiaokeai');
log('hello', '');

function logEs6(x, y = 'world') {
    console.log('es6可以直接在参数定义的后面给参数设置默认值:', x, y);
}

logEs6('hello');
logEs6('hello', 'xiaokeai');
logEs6('hello', '');

function foo(x = 5) {
    //let x = 1; // 参数变量是默认声明的,所以不能用let或const再次声明
    //const x = 2;
}

//使用参数默认值时,函数不能有同名参数,否则报错,e.g function fooSameParam(x, x, y = 1) {}

let defaultTestX = 99;
function testFuncDefaultParam(params = defaultTestX + 1) {
    console.log('参数默认值不是传值的,是每次都重新计算默认值表达式的值', params);
}

testFuncDefaultParam();
defaultTestX = 600;
testFuncDefaultParam();

// 参数默认值可与解构赋值的默认值结合起来使用
function testFuncCombine({param1, param2 = 5}) {
    console.log("代码只使用了对象的解构赋值默认值,没有使用函数参数默认值,且若函数调用时没有提供参数,变量param1和param2不会生成,从而报错:", param1, param2);
}

testFuncCombine({});
testFuncCombine({param1: 1});
testFuncCombine({param1: 1, param2: 2});
//testFuncCombine(); // 没提供参数导致解构时不生成变量,从而报错

function testFuncCombinePro({param1, param2 =  5} = {}) {
    console.log("通过提供函数参数的默认值,可以避免这种情况:", param1, param2);
}

testFuncCombinePro();

//函数参数的默认值和解构赋值的默认值结合使用时,调用无对应参数时,函数参数的默认值生效,然后才是解构赋值的默认值生效
function method1({x = 0, y = 0} = {}) {
    // 函数参数的默认值是空对象,对象设置了解构赋值的默认值
    console.log('函数默认值先生效', x, y);
}

function method2({x, y} = {x: 0, y: 0}) {
    // 函数参数的默认值是一个有具体属性的对象,但没有设置对象解构赋值的默认值
    console.log('解构赋值的默认值后生效', x, y);
}

method1();
method2();
method1({x: 3, y: 9});
method2({x: 3, y: 9});
method1({x: 3});
method2({x: 3});
method1({});
method2({});
method1({z: 99});
method2({z: 99});

//通常情况下,定义了默认值的参数,应是函数的尾参数。若非尾部参数设置默认值,实际该参数无法省略
function funcParam(x = 1, y) {
    console.log('参数1和参数2:', x, y);
}

funcParam();
//funcParam(, 1); //会报错
funcParam(undefined, 1);// 显示输入undefined可以触发该参数的默认值,但是null不会

//指定默认值后,函数的length属性将返回没有指定默认值的参数的个数。即,指定默认值,length属性将失真
console.log('返回没有指定默认值的参数的个数:', (function(a) {}).length, (function (a = 5) {}).length);
//函数length属性的含义就是该函数预期传入的参数个数。设定默认值后,预期传入就不包括了
// 且若设置了默认值的参数不是尾参数,那length属性也不再计入后面的参数了
console.log('设置默认值的参数不是尾参数,那从设置默认值到之后的参数都不计入length', (function (a = 0, b, c) {}).length);
console.log('同上', (function (a, b = 1, c) {}).length);

/**作用域**
 * 一旦设置了参数的默认值,函数进行声明初始化时,参数就会形成一个单独的作用域(context)。
 */
var testScopeX = 1;

function funcScope(testScopeX, paramY = testScopeX) {
    console.log('参数paramY的默认值为testScopeX。调用该函数时,参数会形成一个单独作用域,在该作用域中,默认值变量testScopeX指向的是第一个参数,而非全局变量', paramY);
} 

funcScope(4);

function funcScope1(paramY = testScopeX) {
    let testScopeX = 3;
    console.log('调用函数时,参数形成单独作用域,该作用域中变量testScopeX无定义,所以指向外层的全局变量testScopeX', paramY);
}

funcScope1();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope2(paramY = testScopeX) {
    var testScopeX = 66;
    console.log('2.调用函数时,参数形成单独作用域,该作用域中变量testScopeX无定义,所以指向外层的全局变量testScopeX', paramY);
}

funcScope2();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope3(testScopeX = testScopeX) {
    console.log('调用会报错,参数实际执行的是let testScopeX = testScopeX,由于暂时性死区的原因,会报未定义的错误');
}

//funcScope3();

let funcBody = 'outer';

function funcScope4(func = () => funcBody) {
    let funcBody = 'inner';
    console.log('若参数是一个函数,那该函数的作用域也遵守该规则', func());
}

funcScope4();

// 以下是对比
function funcScope5(testScopeX, Y = function() {testScopeX = 2}) {
 console.log('打印testScope:', testScopeX);
}

funcScope5();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope6(testScopeX, Y = function() {testScopeX = 6}) {
    Y();
    console.log('funcScope6的testScopeX:', testScopeX);
}

funcScope6();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope7(testScopeX, Y = function() {testScopeX = 7}) {
    var testScopeX = 11;
    console.log('funcScope7的testScopeX', testScopeX);
}

funcScope7();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope8(testScopeX, Y = function() {testScopeX = 8}) {
    var testScopeX = 16;
    Y();
    console.log('funcScope8的testScopeX', testScopeX);
}

funcScope8();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope9(testScopeX, Y = function() {testScopeX = 9}) {
    testScopeX = 18;
    console.log('funcScope9的testScopeX', testScopeX);
}

funcScope9();
console.log('全局变量testScopeX的值', testScopeX);

function funcScope10(testScopeX, Y = function() {testScopeX = 10}) {
    testScopeX = 20;
    Y();
    console.log('funcScope10的testScopeX', testScopeX);
}

funcScope10();
console.log('全局变量testScopeX的值', testScopeX);

// 实际应用:(1)利用参数默认值,可指定某一个参数不能省略,若省略则报错;(2)可将参数默认值设为undefined,表明参数可省略

/*
 * 2.rest参数
 *  形式:...变量名
 *  功能:获取函数的多余参数,从而不需要使用arguments对象
 *      rest参数的变量是一个数组,该变量将多余的参数放入数组中
 */

// example-add-any-num, 利用rest参数来实现任意个数的参数的求和
function addAnyNum(...values) {
    let sum = 0;

    for(var val of values) {
        sum += val;
    }

    return sum;
}

console.log('利用rest参数实现的对任意个数的参数进行求和:', addAnyNum(3, 4, 5, 6));

// compare arguments with rest
// (1)能否直接使用数组方法:
//      arguments对象不是数组,是类数组对象。所以在使用数组方法时,需要用Array.prototype.slice.call现将其转换为数组;
//      rest参数本身就是一个真正的数组,因此数组的正常方法都可以使用
//  (2) rest参数之后不能有其他参数,即rest参数只能是最后一个参数,否则报错

function sortNumbers() {
    return Array.prototype.slice.call(arguments).sort();
}

const sortNumbersByRest = (...numbers) => numbers.sort();

//利用rest参数改写数组push方法的例子
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
        console.log('给数组即第一个参数传入元素:', item);
    });
}

var pushArrayByRest = [];
push(pushArrayByRest, 3, 4, 5, 6);

//chapter 9
/*
 *1.扩展运算符: ...
 * 功能: 用以将数组转为用逗号分隔的参数序列
 * 用法:
 *  (1)扩展运算符后可以放置表达式;
 *  (2)扩展运算符后若是空数组,则不产生任何效果;
 *  (3)只有函数调用时,扩展运算符才可以放入圆括号,否则报错;
 * 
 */
function add(x, y, z) {
    let  sum = x + y + z;
    console.log ('三数之和' + sum);
}

const numbersArr = [3, 6, 9];
add(...numbersArr);

let x = 1;
let followExpression = [...(x && x > 0 ? ['a'] : []), 'b'];
console.log('扩展运算符后跟表达式的结果:    ' + followExpression);
x = 0;
followExpression = [...(x && x > 0 ? ['a'] : [], 'b')];
console.log('扩展运算符后是空数组则不会有任何效果: ' + followExpression);
// (...[1, 2]); // 不是函数调用,扩展运算符在此处报错
// console.log((...[1, 2])); // 同上,报错
// console.log(...[1, 2]); // 是函数调用,扩展运算符在此处不会报错

/* 
 * 1.1 扩展运算符的用途
 *  (1)代替函数的apply方法:...可展开数组,所以不需apply方法,就可将数组转为函数的参数;
 *  (2)复制数组: 数组是复合数据类型,直接复制,只是复制了指向底层数据结构的指针,而非克隆一个全新数组,而扩展运算符提供了简洁写法;
 *  (3)合并数组:要注意浅拷贝
 *  (4)与解构赋值结合:在数组的解构赋值时只能放在最后一个参数
 *  (5)字符串转为数组:
 *  (6)实现了Iterator接口的对象:都可以被扩展运算符转为真正的数组;
 *  (7)Map和Set结构, Generator函数:扩展运算符本质是调用数据结构内部的Iterator接口
 *
 */
 // Es5的apply方法是一个可以接受数组作为第二个参数来调用某个函数
 // Es6中则可以直接使用...
 function testApply(x, y, z) {
     console.log('利用Es5的apply方法实现将数组转为为函数的参数序列', x, y, z);
 }
 testApply.apply(null, ['param1', 'param2', 'param3']);
 testApply(...['Es6Param1', 'Es6Param2', 'Es6param3']);

// example-push数组, Es5和Es6的写法
var arr1 = [0, 1, 2],
    arr2 = [3, 4, 5],
    arr3 = [6, 7, 8, 9];

Array.prototype.push.apply(arr1, arr2);
console.log('Es5将一个数组push到另一个数组尾部:', arr1);
arr1.push(...arr3);
console.log('Es6使用扩展运算符将arr3push到arr1中:', arr1);

// example-copyArray, 实现复制一个新数组,Es5和Es6写法
var a1 = [1, 2],
    a2 = a1,
    a3,
    a4;

a2[0] = 3;
console.log('Es5直接复制数组,复制的是引用', a1, a2);
a2 = a1.concat();
a2[0] = 4;
console.log('Es5可以利用concat来实现克隆一个新数组', a1, a2);
a3 = [...a2];
a3[0] = 5;
console.log('Es6可以直接使用扩展运算符实现克隆出一个新数组:', a2, a3);
[...a4] = a1;
a4[1] = 8;
console.log('Es6实现克隆出一个新数组的另一种写法:', a1, a4);

// example-array-concat, 实现数组合并的Es5和Es6写法,但两者都是浅拷贝!
const array1 = ['arr1Elem1', 'arr1Elem2'];
const array2 = ['arr2Elem1', 'arr2Elem2'];
const array3 = ['arr3Elem1'];

var Es5NewArray = array1.concat(array2, array3);
console.log('Es5利用concat实现多个数组合并:',Es5NewArray);
console.log('Es6利用扩展运算符实现多个数组合并:', [...array1, ...array2, ...array3]);
var arrCopy = [{foo: 1}],
    arrCopy2 = [{bar: 2}];

const arrCopy3 = arrCopy.concat(arrCopy2);
const arrCopy4 = [...arrCopy, ...arrCopy2]; // arrCopy3和arrCopy4都是新数组,但是其成员都是对原数组成员的引用,即浅拷贝,只是将指针拷贝,指针指向的值若有修改,也会同步到新数组
console.log('Es5数组合并后都是浅拷贝: ', arrCopy3, arrCopy3[0] === arrCopy[0]);
console.log('Es6数组合并后也都是浅拷贝:', arrCopy4, arrCopy4[0] === arrCopy[0]);

// example-解构赋值结合,用以生成新数组
var numList = [0, 1, 2, 3, 4];
var fstNum = numList[0],
    restNums = numList.slice(1);
console.log('Es5利用slice来生成数组:', restNums);

var es6NumList = [6, 7, 8, 9, 10],
    [es6FstNum, ...es6RestNums] = es6NumList;
console.log('Es6利用解构赋值和扩展运算符生成数组:第一个元素' + es6FstNum + '  ,剩余元素构成的数组' + es6RestNums);
const [first, ...rest] = []; // first取值为undefined, rest取值为[]
const [fstElem, ...restElems] = ["first"]; // fstElem取值为first", restElems取值为[]
// const [...test, last] = [1, 2, 3, 4]; // 扩展运算符用于数组赋值只能放在参数的最后一位,否则报错;
// const [testFst, ...middle, testLast] = [1, 2, 3, 4, 5]; // 同理,在数组解构赋值时,扩展运算符只能放参数的最后一位,否则报错

// example-string, 利用扩展运算符可以将字符串转为数组,且可以利用扩展运算符改写,使之能正确处理四字节Unicode字符
var strToArr = [...'hello\uD83D\uDE80y'];
console.log('扩展运算符将字符串转为真正数组,且能正确识别四个字节的Unicode字符:' + strToArr + '数组长度:' + strToArr.length);
function length(str) {
    return [...str].length;
}
console.log('test four Byte Unicode length:', length('test\uD83D\uDE80'));

// example-iterator-object,扩展运算符可以将任何定义了遍历器接口的对象转换为数组
let domNodeList = document.querySelectorAll('div'); // querySelectorAll返回的是一个类似数组的对象,且该对象实现了Iterator接口
let domNodeArr = [...domNodeList];
console.log('扩展运算符可将实现了Iterator接口的NodeList对象转化为真正的数组: ', domNodeArr);

/*
Number.prototype[Symbol.iterator] = function*() {
    let i = 0;
    let num = this.valueOf();

    while(i < num) {
        yield i++;
    }
}
console.log('若定义了Number对象的遍历器接口,就可以利用扩展运算符将5自动转换为一个数组:', [...5]); 
*/

// example-map-set-Generator, 扩展运算符本质是在内部调用数据结构的Iterator接口,凡是有Iterator接口的对象都可以使用扩展运算符
let mapToArr = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
console.log('Map结构因为具有Iterator接口,可用扩展运算符转为数组:', [...mapToArr.keys()]);

/*
const go = function*() {
    yield 1;
    yield 2;
    yield 3;
};
console.log('Generator函数运行返回的结果是一个遍历器对象,可用扩展运算符转为数组:', [...go()]);
*/

// chapter 9
/*
 * 2. Array的扩展方法:
 *  (1)Array.from():将类似数组对象和可遍历对象这两类对象转为数组
 *  (2)Array.of():
 */ 

// example-array-from,将类似数组对象、可遍历对象转为真正的数组
let arrayLikeObj = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
var es5arrLiketoArr = [].slice.call(arrayLikeObj);
console.log('Es5利用slice和call实现将类似数组转化为数组', es5arrLiketoArr);
let es6ArrLiketoArr = Array.from(arrayLikeObj);
console.log('Es6利用Array.from实现将类似数组对象转为数组', es6ArrLiketoArr);

//实际中,类似数组的对象,如DOM操作返回的NodeList集合,以及函数内部的arguments对象
let nodelist = document.querySelectorAll('div');
Array.from(nodelist).filter(divNode => {
    return divNode.textContent.length > 6;
});
console.log('nodeList转换为数组并且过滤后,', nodelist);

function testArgumentsToArr(param1, param2) {
    console.log('函数参数arguments', arguments);
    console.log('使用Array.from转换为数组婚后', Array.from(arguments));
}
testArgumentsToArr('test1', 'test2');

let namesSet = new Set(['a', 'b']);
console.log('只要是部署了Iterator接口的数据结构,Array.from都能将其转换为数组', Array.from(namesSet));

console.log('若参数是一个真正的数组,Array.from会返回一个一模一样的新数组', Array.from([1, 2, 3]));

// 扩展运算符本质是调用遍历器接口(Symbol.iterator),无该接口就无法转换
// Array.from还支持类似数组的对象的转换,类似数组的对象本质是必须要有length属性
console.log('只要有length属性的对象,都可以用Array.from转换', Array.from({length: 3}));
// Array.from可以接受第二个参数,类似数组的map方法,用以对每个元素进行处理,然后将处理后的值放入返回的数组
console.log('Array.from的第二个参数类似于数组的map函数:', Array.from({'0': 1, '1': 2, '2': 3, length:3}, x => x * x));
console.log('等价于数组的map函数:', Array.from({'0': 1, '1': 2, '2': 3, length:3}, x => x * x));

// Array.from的其他用途
// (1) 返回各种数据的类型;
// (2) 提供map功能;
// (3) 若map函数里用到了this关键字,还可以传入Array.from的第三个参数用来绑定this
// (4) 将字符串转为数组,然后返回字符串的长度,以正确处理Unicode字符

// example-array-of, Array.of方法用以将一组值转化为数组
// 历史原因:构造函数Array(),因参数个数的不同,行为会有差异
console.log('Array构造函数无参时结果是一个空数组:', Array());
console.log('Array构造函数有一个参数时,表示几个元素:', Array(3));
console.log('Array构造函数多于一个参数时,参数就是具体的元素值:', Array(3, 9, 27));
console.log('Array.of方法的目的就是来解决Array函数由于参数不同而导致的重载问题,行为统一:', Array.of(3));
console.log('多于一个参数时:', Array.of(3, 7, 78));
console.log('即Array.of基本可以替代Array()或new Array(),并且具有统一行为:将一组数值转换为数组:', Array.of(), Array.of(undefined));
// Array.of的ES5写法
function ArrayOf() {
    return [].slice.call(arguments );
}

// chapter 9
/*
 * 3. 数组的实例的扩展方法:
 *  (1)copyWithin():
 *  (2)find() 和 findIndex():
 *  (3)fill():
 *  (4)entries()、keys()、values():
 *  (5)includes():
 *  (6)flat()和flatMap():
 */

/*
 * example-copyWithin
 *  功能:在当前数组内部,将指定位置的成员复制到其他位置(覆盖原有成员),并返回当前数组,会修改原数组
 *  Array.prototype.copyWithin(target, start = 0, end = this.length) 
 *  参数:
 *   target(必选参数):从该位置开始替换数据,若为负值,表示倒数;
 *   start(可选参数):从该位置开始读取数据,默认为0.若为负值,则从末尾开始计
 *   end(可选参数):到该位置前停止读取数据,默认是数组长度。若为负值,从末尾开始计
 */ 
console.log('使用数组实例的copyWithin方法复制某个位置的元素到指定位置:', [1, 2, 3, 4, 5].copyWithin(0, 3));
console.log('当start和end参数为负值时的情况:', [1, 2, 3, 4, 5].copyWithin(0, -2, -1))
console.log('类似数组对象,将3号位置复制到0号位:', [].copyWithin.call({length: 5, 3: 1}, 0, 3));

/* example-find
 * 功能: 用以找出第一个符合条件的数组成员
 * 参数: 
 *  第一个参数(必选):是一个回调函数
 *  第二个参数(可选):用以绑定回调函数的this对象
 * 返回:对数组成员依次执行回调函数,直到找到第一个返回值为true的成员,返回该数组成员;若无符合条件的,返回undefined
 */
console.log('数组实例的find方法:找某个满足条件的成员', [1, 4, -5, 10].find((n) => n < 0));
let testArrayInstanceFind = [1, 5, 10, 15];
let arrInstanceFindRes = testArrayInstanceFind.find(function (value, index, arr) {
    console.log('find方法的回调函数可以接受三个参数:当前值,当前位置,原数组:', value, index, arr);
    return value > 9;
});
console.log('寻找的结果:', arrInstanceFindRes);
function testParam2(age) {
    return age > this.age;
}
let personObj = {name: 'Jack', age: 20};
console.log('使用find的第二个参数来指定调用回调函数的this值:', [10, 18, 22, 20].find(testParam2, personObj));


/* example-findIndex
 * 功能: 返回第一个符合条件的数组成员的位置,若都不符合,返回-1;
 * 参数:同find一样
 */

console.log('findIndex的使用:', [1, 5, 10, 15].findIndex(function (value, index, arr) {
    return value > 9;
}));
console.log('indexOf无法发现NaN:', [NaN].indexOf(NaN));
console.log('findeIndex可以发现NaN:', [NaN].findIndex(y => Object.is(NaN, y)));


/*example-fill
 * 功能:使用给定值,填充一个数组.若被填充的值类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象
 * 参数:
 * (1)第一个参数:给定填充的值
 * (2)第二个参数(可选):指定填充的起始位置
 * (3)第三个参数(可选):指定填充的结束位置
 */
console.log('利用fill方法可以对空数组做初始化操作', new Array(5).fill(7));
console.log('fill方法填充元素后,数组中已有的元素会被全部抹去:', ['a', 'b', 'c'].fill(8));
console.log('fill的第二个和第三个参数可分别指定填充的起始位置:', ['q', 'v', 'b', 'd'].fill(9, 1, 3));
let fillObjArr = new Array(3).fill({name: 'milke'});
fillObjArr[0].name = 'chao';
console.log('fill的填充值为对象时,填充的是同一个内存地址,不是对象的深拷贝:', fillObjArr[0].name, fillObjArr[1].name, fillObjArr[2].name);
let fillArrObjArr = new Array(4).fill([]);
fillArrObjArr[0].push(3);
console.log('fill的填充值为对象时,如数组,也是浅拷贝:', fillArrObjArr);

/* example-entries-keys-values,
 * 功能:用以遍历数组,返回一个遍历器对象,可用for...of进行遍历
 *  entries(): 是对键值对进行遍历
 *  keys(): 只对键名进行遍历
 *  values(): 只对键值进行遍历
 * 若不使用for...of遍历,可手动调用遍历器对象的next方法,进行遍历
 */
for (let [index, elem] of ['a', 'b', 'c'].entries()) {
    console.log('利用entries遍历数组的键值对:', index, elem);
}

for(let index of ['elem1', 'elem2', 'elem3'].keys()) {
    console.log('利用keys方法对数组的键名进行遍历:', index);
}

for(let value of ['elem1', 'elem2', 'elem3'].values()) {
    console.log('利用values对数组的键值进行遍历:', value);
}

let testNextEntries = ['one', 'two', 'three'].entries();
console.log('手动调用next方法来完成对数组的遍历:', testNextEntries.next().value)
console.log('手动调用next方法来完成对数组的遍历', testNextEntries.next().value);
console.log('手动调用next方法来完成对数组的遍历', testNextEntries.next().value);

/* example-includes
 * Array.prototype.includes()
 * 功能:判断某个数组是否包含给定值,返回布尔值
 * 参数:
 * (1)第一个参数: 指定值
 * (2)第二个参数: 搜索的起始位置,默认为0,若参数为负数,则表示倒数的位置,若此时大于数组长度,则会重置为从0开始
 * 其他:
 *  在没有该方法之前,通常使用数组的indexOf方法来判断数组是否包含某个值
 */
console.log('利用includes来判断数组是否包含指定值:', [1, 2, 3].includes(2));
console.log('includes也可以判断出NaN:', [2, 3, NaN, 4].includes(NaN));
console.log('includes可以指定搜索的起始位置:', [1, 2, 3].includes(3, 3)); 
console.log('Es5用indexOf来判断一方面语义化不强,还需要对返回值做判断,另一方面会对NaN做误判:', [NaN].indexOf(NaN));  

// Map和Set结构有一个has方法,需要和includes方法区分
// Map结构的has方法是用来查找键名的; Set结构的has方法是用来查找值的

/* example-flat
 * Array.prototype.flat()
 * 功能: 数组的成员有时还是数组,flat的作用就是将嵌套的数组“拉平”,变成一维数组,返回一个新数组,不影响原数组
 * 参数:
 *  可选,表示想要拉平的层数,默认为1,即只拉平一层
 * 其他:
 *  (1)若不管有几层嵌套,都转成一维数组,则可以用Infinity作为参数
 *  (2)原数组若有空位,flat会跳过空位
 */
let flatArr = [1, 2, [3, 4], [5, [6, 7]]];
console.log('flat将数组拉平且不影响原数组,默认拉平一层。原数组,拉平后:', flatArr.length, flatArr.flat(), flatArr.flat().length);
console.log('flat的参数可以指定拉平的层数:', flatArr.flat(2), flatArr.flat(2).length);
console.log('flat会跳过原数组的空位:', [1, 2, , 3, [5, 6]].flat().length);

/* example-flatMap
 * Array.prototype.flatMap()
 * 功能:对原数组的每个成员执行一个函数(相当于map功能),然后对返回值组成的数组执行flat方法。返回新数组,不影响原数组
 * 参数:
 *   回调函数:可接受三个参数:当前数组成员,当前成员位置,原数组
 *   第二个参数: 用来绑定回调函数里的this
 * 其他:
 *  flatMap只能展开一层数组
 */
let flatMapArr = [2, 3, 4].flatMap((x) => [x, x * 2]);
console.log('flatMap遍历数组执行回调函数,且对返回值执行flat:', flatMapArr, flatMapArr.length);
let flatMapArr1 = [2, 3, 4].flatMap((x) => [[x * 2]]);
console.log('flatMap默认只展开一层:', flatMapArr1, flatMapArr1.length, flatMapArr1[0].length); 
let flatMapArr2 = [2, 3, 5].flatMap(function callback(current, index, arr) {
    console.log('flatMap的回调函数的参数:', current, index, arr);
});

 // chapter 9
 /* 4. 数组的其他扩展:
  *  (1)数组的空位:空位是指数组的某个位置没有任何值(注意:空位不是undefined,undefined依然是有值的)。e.g. Array构造函数返回的数组都是空位。
  *  (2)Array.prototype.sort()的排序稳定性:
  */
console.log('Array构造函数返回的数组都是空位:', Array(3));
console.log('Array总若数组成员取值是undefined,那也是有值的,不是空位!', [undefined, undefined, ,]);
console.log('in元素可以区分出一个位置是否有值:即undefined取值和空位:', 0 in [undefined, undefined], 0 in [, , ,]);

// Es5对空位的处理很不一致,大多数情况下会忽略空位
// forEach、 filter、reduce、every和some会跳过空位
console.log('Es5中forEach方法会跳过空位:', [, 'a'].forEach((x, i) => console.log('第' + i + '个位置的元素是:' + x)));
console.log('Es5中filter方法会跳过空位:', ['a', , 'b'].filter(x => true));
console.log('Es5中every也会跳过空位:', [, 'a'].every(x => x === 'a'));
console.log('Es5中reduce方法会跳过空位处理:', [1, , 3].reduce((x, y) => x + y));
console.log('Es5中的some方法会跳过空位:', [, 'a'].some(x => x !== 'a'));

// Es5的map方法会跳过空位,但会保留这个值
console.log('Es5中的map方法会跳过空位,但会保留该值:', [, 'a'].map(x => 1));

// Es5的join和toString会将空位视为undefined,而undefined和null会被处理成空字符串
console.log('join方法会将空位视为undefined,进而转化为空字符串:', [, 'a', undefined, null].join('#'));
console.log('toString方法也会将空位视为undefined,进而转化为空字符串:', [, 'a', undefined, null].toString());

// Es6明确将空位转为undefined
console.log('Es6中的Array.from方法将数组的空位转为undefined,不会忽略空位', Array.from(['a', , 'b']));// ['a', undefined, 'b']
console.log('Es6中的扩展运算符也会将空位转为undefined', [...['a', , 'b']]);
console.log('Es6中的copyWithin会连空位一起拷贝:', [, 'a', 'b', ,].copyWithin(2, 0));
console.log('Es6中的fill会将空位视为正常的数组位置:', new Array(3).fill('a'));

let testEmptyPosArr = [, ,];
for (let i of testEmptyPosArr) {
    console.log('for...of循环也会遍历空位:', i);
}

// Es6中的entries, keys, values, find, findIndex也会将空位处理成undefined
// 注意: 因为空位的处理规则非常不统一,所以还是尽量避免出现空位
console.log('entries方法将空位处理成undefined:', [...[, 'a'].entries()]); // [[0, undefined], [1, 'a']]
console.log('keys方法将空位处理成undefined:', [...[, 'a'].keys()]); // [0, 1]
console.log('values方法将空位处理成undefined:', [, 'a'].values()); // [undefined, 'a']
console.log('find方法将空位处理成undefined:', [, 'a'].find(x => true)); //undefined
console.log('findIndex方法将空位处理成undefined:', [, 'a'].findIndex(x => true));

/* example-sort-stable
 * 排序稳定性是排序算法的重要属性,指排序关键字相同的项目,排序前后的顺序不变
 * 常见的排序算法: 
 *   插入排序、合并排序、冒泡排序等是稳定的;
 *   堆排序、快速排序等是不稳定的
 * 不稳定排序的主要缺点:
 *   多重排序时可能产生问题
 * 早先ES对sort方法的默认排序算法是否稳定没有规定,留给浏览器决定,导致某些实现不稳定
 * Es2019明确规定,Array.prototype.sort的默认排序算法必须稳定。
 */ 

/*
 * chapter 13 Set & Map数据结构
 * Set
 * WeakSet
 * Map
 * WeakMap
 */
/*
 * Set数据结构
 * 一、Set本身是一个构造函数,用以构造类似数组的数据结构
 *  (1)Set数据结构类似数组,但成员具有唯一性,无重复值;
 *  (2)Set构造函数,接受具有iterator接口的数据结构作为参数,e.g数组
 *  (3)可用add方法给Set数据结构添加成员,但不会添加重复成员;
 *  (4)Set有自己比较两个成员是否相等的算法
 *      i. 其不会做类型转换,类似===;
 *      ii. NaN和NaN是相等的;
 *      iii. 对象会被认为是不同成员,e.g,set中的{}和{}
 *   (5) Set的遍历顺序就是插入顺序!!
 * 
 * 二、Set的应用,可为数组做去重操作,为字符串去重
 * 
 * 三、Set结构的实例的属性和方法
 *   #属性:
 *  (1)Set.prototype.constructor: 即构造函数,为其本身;
 *  (2)Set.prototype.size: 返回成员数量;
 * 
 *  ----------------------------------------------------
 *   #方法:
 *   增删改查
 *  (1)Set.prototype.add(value):为该数据结构添加一个成员,返回set本身
 *  (2)Set.prototype.delete(value): 删除某个成员,返回布尔值以表示删除是否成功
 *  (3)Set.prototype.has(value): 查询是否有该成员;
 *  (4)Set.prototype.clear(): 清除所有成员;
 *  ----------------------------------------------------
 *   遍历方法
 *  (1)Set.prototype.keys():用以遍历键名的遍历器
 *  (2)Set.prototype.values():用以遍历键值的遍历器
 *  (3)Set.prototype.entries(): 用以遍历键值键名的便利器
 *  (4)Set.prototype.foreach(): 让每个成员都运行一遍回调函数
 *  -----------------------------------------------------
 * 
 * i. keys()、values()、entries()都是返回遍历器对象
 * ii. Set的键名和键值是一样的,因此keys和values行为一致;
 * iii. Set的实例部署了iterator接口,因此本身可遍历,可直接使用for...of对其遍历
 * iv. 扩展运算符...内部使用for...of,因此扩展运算符+Set,可实现数组去重
 * v. 数组的map和filter可间接用于Set结构
 * vi. Set可实现并集、交集、差集
 * vii. 同步改变Set原来结构,需要使用映射
 *    a) 数组的map
 *    b) 数组的from
 */ 
var set = new Set([1, 1, 2, 3, 4, 5, 5, 5]);
console.log('Set的实例的成员,没有重复值', set);
console.log('set的成员个数', set.size);

for(let item of set) {
    console.log('set自身部署了iterator接口', item);
}

set.add(7);
set.delete(2);
if (set.has(3)) {
    console.log('set含有成员3');
}

for(let key of set.keys()) {
    console.log('使用keys方法遍历set的键名', key);
}

for(let value of set.values()) {
    console.log('使用values方法遍历set的键值', value);
}

for(let [key, value] of set.entries()) {
    console.log('使用entries遍历set的键值对', key, value);
}

set.forEach(function(item) {
    return item * 2;
});

console.log('使用forEach对每个成员进行操作后的结果', set);

/*
 * WeakSet数据结构
 *   (1)WeakSet也是不重复值的集合;
 *   (2)WeakSet与Set有两点区别:
 *        i. WeakSet的成员只能是对象,其他类型值会报错;
 *        ii. WeakSet成员对象的引用是弱引用,不计入垃圾回收机制;
 *   --------------------------------------------------------
 *   #语法
 *  (1)WeakSet本身是构造函数,用以创建WeakSet结构;
 *  (2)WeakSet的参数可接受具有iterator接口的对象,其所有成员会
 *       自动成为WeakSet实例的成员
 *   ---------------------------------------------------------
 *   #方法
 *   WeakSet.prototype.add(value): 添加成员,成员必须是对象;
 *   WeakSet.prototype.delete(value):删除成员;
 *   WeakSet.prototype.has(value): 判断是否有该成员
 *   ---------------------------------------------------------
 *   #遍历
 *   WeakSet中成员是弱引用,不计入垃圾回收机制。若外部对成员对象的引用变为0
 *   就会垃圾回收,WeakSet中成员自动消失。
 *   因此,WeakSet无遍历功能,也无size属性。
 *   ---------------------------------------------------------
 *   #应用
 *   WeakSet可用于存储DOM节点,不必担心DOM节点从文档中移除时的内存泄露
 */
var ws = new WeakSet([[1, 2], [3, 4]]),
    key = [7, 8];

ws.add([5, 6]);
// ws.add(5); // WeakSet的成员只能是对象
console.log('ws', ws);
ws.add(key);
console.log('ws成员中有【7, 8】吗', ws.has(key));

/*
 * Map数据结构
 * 一、Map是构造函数,用以创建Map数据结构
 *   (1) Map是键值对集合;
 *   (2) Map与对象的区别:
 *         对象的键只能是字符串,Map的键可以是任何类型;
 *         即对象是“字符串:值”,Map是“值: 值”
 *   (3) Map构造函数,接受任何具有Iterator接口的结构,且每个成员为双元素的数组作为参数
 *   (4) 尤其注意Map的键为对象时,不是同一引用的对象,是不同的键
 *         即Map的键绑定的是内存地址
 *   (5) Map在判断键是否为同一个时,NaN和NaN是同一个,简单类型会做严格判断
 * 二、Map实例的属性和方法
 *   ----------------------------------------------------------------------
 *   #属性
 *   size: Map成员数量
 *   ----------------------------------------------------------------------
 *   #操作方法
 *  (1)Map.prototype.set(key, value): 给Map添加键值对,并返回完整Map。
 *       i. 对于同一键名的多次赋值,后面会覆盖前面;
 *       ii. 由于set完成后返回完整Map,所以可用链式方式做set
 *  (2)Map.prototype.get(key): 获取Map中某个键名所对应的值,若无该key,则返回undefined
 *  (3)Map.prototype.has(key): 判断Map中是否含有key成员;
 *  (4)Map.prototype.delete(key): 删除Map中的对应成员;
 *  (5)Map.prototype.clear(): 清楚Map的全部成员;
 *   -----------------------------------------------------------------------
 *   #遍历方法
 *  (1)Map.prototype.keys():遍历Map的所有键名,返回遍历器;
 *  (2)Map.prototype.values(): 遍历Map的所有键值,返回遍历器;
 *  (3)Map.prototype.entries(): 遍历Map的键值对,返回遍历器;
 *  (4)Map.prototype.forEach(): 遍历Map的所有成员
 * 
 *   注意:
 *   (1) Map本身部署了iterator接口,所以可用for...of直接对Map实例进行遍历;
 *   (2) Map的遍历顺序就是插入顺序!
 *   -----------------------------------------------------------------------
 *   #Map与其他数据结构的转换
 *   (1) Map转换为数组: 利用...扩展符;
 *   (2) 数组转为为Map: 将数组传入Map构造函数;
 *   (3) Map转换为对象:
 *       i. 若Map的键名都是字符串,那可以无损的转为对象;
 *       ii. 若Map的键名是非字符串,则键名会变为相应的字符串;
 *   (4) 对象转换为Map: 可以通过Object.entries()或自己封装
 *   (5) Map转为JSON: 
 *       i. Map的键名是字符串:可先转成Object,再利用JSON.stringify转为JSON;
 *       ii. Map的键名是非字符串,可选择转为数组JSON;
 *   (6) JSON转为Map:
 *       i. 正常若键名都是字符串:则可以;
 *       ii. 特殊情况,整个JSON是数组,每个成员都是两个成员,就可以转为Map;
 */
 var map = new Map([[true, 'ok'], [1, 2]]);

 map.set([], '数组1');
 map.set([], '数组2');
 console.log('map的成员个数(键名不是同一个引用的对象,被视作不同键)', map.size); // 4

 for (let [key, value] of map) {
     console.log('map的成员', key, value);
 }

 console.log('map的true成员的值:', map.get(true));
 console.log('map是否具有1成员:', map.has(1));

 for(let key of map.keys()) {
     console.log('遍历map的key:', key);
 }

 for(let value of map.values()) {
     console.log('遍历map的value:', value);
 }

map.delete(1);
for(let [key, value] of map.entries()) {
    console.log('遍历map的key:' + key + ' 和value:'  + value);
}

console.log('用扩展运算符将Map转换为数组:', [...map]);
var arrToMap = new Map([
    ['key1', 1],
    [{key: 2}, 2]
]);

for(let [key, value] of arrToMap) {
    console.log('数组转化为map的key和value', key, value);
}

function stringMapToObj(stringMap) {
    var obj = Object.create(null);

    for(let [key, value] of stringMap) {
        console.log('key,value:', key, value);
        obj[key] = value;
    }

    return obj;
}

var strMap = new Map().set('yes', true).set('no', false);
console.log('键都为字符串的Map转为对象', stringMapToObj(strMap).yes, stringMapToObj(strMap).no);

/*
 * WeakMap数据结构
 *   (1) WeakMap数据结构和Map类似,也是键值对集合;
 *   (2) WeakMap与Map有两点区别:
 *       i. WeakMap的成员的键名只能是对象,不接受其他类型作为键名;
 *       ii. WeakMap的成员的键名所指向的对象是弱引用,不计入垃圾回收机制;
 *   (3) WeakMap设计的目的在于: 想在某个对象上存储一些跟该对象相关的信息
 *         一旦对象不被需要,也不需要手动删除WeakMap中的引用
 *   (4) WeakMap的弱引用只是键名,而不是键值;
 *   ------------------------------------------------------------------
 *   #WeakMap的语法
 *   (1) 因为键名的弱引用,所以无遍历方法,也无size属性;
 *   (2) 无法清空;
 *   (3) 有set、get、has和delete方法可用;
 *   ------------------------------------------------------------------
 *   #WeakMap的用途
 *  (1)用DOM节点做键名,然后与该节点有关的信息,
 *       当节点被删除时,相关信息都自动消失,防止内存泄露;
 *  (2)部署私有属性
 */ 

/*
 * chapter 21
 * Class的基本语法
 *  静态方法
 *  实例属性的新写法
 *  静态属性
 *  私有方法和私有属性
 *  new.target 属性
 */ 

/*
 * 实用Proxy,获取方法时,自动绑定this?
 * Symbol值得唯一性?将私有方法的名字命名为一个Symbol值
 */

/*
 * Class的继承
 */
<div class="parent">
    <div class="child1">hhhhhh</div>
    <div class="child2">lllllllll</div>
</div>