data processing

512 阅读22分钟

判断是不是数组,isArray最靠谱。

按照条件来判断,every/some给答案

是否包含此元素,includes最快速。

find/findIndex很相似,按条件给第一个值。

indexOf/lastIndexOf也很强,有没有来在哪忙。

from和of,都能用来生数组。

concat当红娘,数组结婚她帮忙。

filter瘦身有一套,不想要的都不要。

map整容有实力,改头换面出新意。

slice就像买切糕,想切哪来就下刀。

自力更生很重要,copyWithin自己搞。

fill就像填大坑,想往哪扔往哪扔。

搬山摸金四兄弟,pop、push、shift、unshift不难记。

造反其实很容易,reverse一下看好戏。

sort排序有技巧,能小大来能大小。

splice要认识,能插能删有本事。

forEach最熟悉,有人说它是万能滴。

keys、values、entries,遍历数组新方式。

算总账,不要慌,reduce、reduceRight帮你忙。

toString,join变字符,toLocaleString不常用。

当里个当,当里个当,数组32方法,猥琐发育不要浪,嘿!不要浪!

变量拼接[模板字符串]

let activity = `${state == 0'活动已开启''活动已结束'}`;

创建一个数组:

    // 常用的字面量方式:
    var a = [3, 11, 8];  // [3,11,8];
    // 构造器:
    // 实际上 new Array === Array,加不加new 一点影响都没有。
    var a = Array(); // [] 
    var a = Array(3); // [,,] 
    var a = Array(3,11,8); // [ 3,11,8 ]

方法:

数组原型提供了非常多的方法,一类会改变原数组的值,一类是不会改变原数组,以及数组的遍历方法。

改变原数组的方法(9个):

let a = [1,2,3];
ES5:
     a.splice()/ a.sort() / a.pop()/ a.shift()/  a.push()/ a.unshift()/ a.reverse()
ES6:
     a.copyWithin() / a.fill

对于这些能够改变原数组的方法,要注意避免在循环遍历中改变原数组的选项,比如: 改变数组的长度,导致遍历的长度出现问题。

splice() 向数组中添加/删除数组元素

定义: splice() 方法向数组中添加/删除元素,然后返回被删除的元素

语法: array.splice(index,howmany,item1,.....,itemX)

参数:

  1. index:添加/删除元素的索引,使用负数可从数组结尾处规定位置。
  2. howmany:可选。要删除的元素数量。如果设置为 0,则不会删除元素。
  3. item1, ..., itemX: 可选。向数组添加新的元素。

返回值: 如果有元素被删除,返回包含被删除元素的新数组。

eg1:删除元素

    // 从数组下标0开始,删除3个元素
    let a = [1, 2, 3, 4, 5, 6, 7];
    let item = a.splice(0, 3); // [1,2,3]
    console.log(a); // [4,5,6,7]
    // 从最后一个元素开始删除3个元素,因为最后一个元素,所以只删除了7
    let item = a.splice(-1, 3); // [7]
    

eg2: 删除并添加

     let a = [1, 2, 3, 4, 5, 6, 7];
    let item = a.splice(0,3,'添加'); // [1,2,3]
    console.log(a); // ['添加',4,5,6,7]
    // 从数组下标0开始,删除3个元素,并添加元素'添加'
     let b = [1, 2, 3, 4, 5, 6, 7];
    let item = b.splice(-2,3,'添加1','添加2'); // [6,7]
    console.log(b); // [1,2,3,4,5,'添加1','添加2']
    // 从数组最后第二个元素开始,删除3个元素,并添加两个元素'添加1'、'添加2'

eg3: 不删除只添加:

    let a = [1, 2, 3, 4, 5, 6, 7];
    let item = a.splice(0,0,'添加1','添加2'); // [] 没有删除元素,返回空数组
    console.log(a); // ['添加1','添加2',1,2,3,4,5,6,7]
    let b = [1, 2, 3, 4, 5, 6, 7];
    let item = b.splice(-1,0,'添加1','添加2'); // [] 没有删除元素,返回空数组
    console.log(b); // [1,2,3,4,5,6,'添加1','添加2',7] 在最后一个元素的前面添加两个元素

从上述三个栗子可以得出:

  1. 数组如果元素不够,会删除到最后一个元素为止
  2. 操作的元素,包括开始的那个元素
  3. 可以添加很多个元素
  4. 添加是在开始的元素前面添加的

sort() 对数组内元素排序

定义: sort()方法对数组元素进行排序,并返回这个数组。

参数可选: 规定排序顺序的比较函数。

默认情况下sort()方法没有传比较函数的话,默认按字母升序,如果不是元素不是字符串的话,会调用toString()方法将元素转化为字符串的Unicode(万国码)位点,然后再比较字符。

    // 字符串排列 看起来很正常
    var a = ["Banana", "Orange", "Apple", "Mango"];
    a.sort(); // ["Apple","Banana","Mango","Orange"]
    // 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的
    var a = [10, 1, 3, 20,25,8];
    console.log(a.sort()) // [1,10,20,25,3,8];

比较函数的两个参数:

sort的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b 接收两个将要比较的元素:

  • 若比较函数返回值<0,那么a将排到b的前面;
  • 若比较函数返回值=0,那么a 和 b 相对位置不变;
  • 若比较函数返回值>0,那么b 排在a 将的前面; sort排序常见用法
  1. 数组元素为数字的升序、降序:

     var array =  [10, 1, 3, 4,20,4,25,8];
     // 升序 a-b < 0   a将排到b的前面,按照a的大小来排序的 
     // 比如被减数a10,减数是20  10-20 < 0   被减数a(10)在减数b(20)前面   
     array.sort(function(a,b){
       return a-b;
     });
     console.log(array); // [1,3,4,4,8,10,20,25];
     // 降序 被减数和减数调换了  20-10>0 被减数b(20)在减数a(10)的前面
     array.sort(function(a,b){
       return b-a;
     });
     console.log(array); // [25,20,10,8,4,4,3,1];
    
  2. 数组多条件排序

     var array = [{id:10,age:2},{id:5,age:4},{id:6,age:10},{id:9,age:6},{id:2,age:8},{id:10,age:9}];
         array.sort(function(a,b){
             if(a.id === b.id){// 如果id的值相等,按照age的值降序
                 return b.age - a.age
             }else{ // 如果id的值不相等,按照id的值升序
                 return a.id - b.id
             }
         })
      // [{"id":2,"age":8},{"id":5,"age":4},{"id":6,"age":10},{"id":9,"age":6},{"id":10,"age":9},{"id":10,"age":2}] 
    
  3. 自定义比较函数,天空才是你的极限

类似的:运用好返回值,我们可以写出任意符合自己需求的比较函数

    var array = [{name:'Koro1'},{name:'Koro1'},{name:'OB'},{name:'Koro1'},{name:'OB'},{name:'OB'}];
    array.sort(function(a,b){
        if(a.name === 'Koro1'){// 如果name是'Koro1' 返回-1 ,-1<0 a排在b的前面
            return -1
        }else{ // 如果不是的话,a排在b的后面
          return 1
        }
    })
    // [{"name":"Koro1"},{"name":"Koro1"},{"name":"Koro1"},{"name":"OB"},{"name":"OB"},{"name":"OB"}] 

pop() 删除数组的最后一个元素

定义: pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。

参数: 无。

    let  a =  [1,2,3];
    let item = a.pop();  // 3
    console.log(a); // [1,2]

shift() 删除数组的第一个元素

定义: shift()方法删除数组的第一个元素,并返回这个元素。

参数: 无。

    let  a =  [1,2,3];
    let item = a.shift();  // 1
    console.log(a); // [2,3]

push() 向数组的末尾添加元素

定义:push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

参数: item1, item2, ..., itemX ,要添加到数组末尾的元素

    let  a =  [1,2,3];
    let item = a.push('末尾');  // 4
    console.log(a); // [1,2,3,'末尾']

unshift() 向数组的开头添加元素

定义:unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

参数: item1, item2, ..., itemX ,要添加到数组开头的元素

    let  a =  [1,2,3];
    let item = a.unshift('开头');  // 4
    console.log(a); // ['开头',1,2,3]

reverse() 颠倒数组中元素的顺序

定义: reverse() 方法用于颠倒数组中元素的顺序。

参数: 无

    let  a =  [1,2,3];
    a.reverse();  
    console.log(a); // [3,2,1]
    应用:价格升降序,距离远近等`

copyWithin() 指定位置的元素复制到其他位置

定义: 在当前数组内部,将指定位置的元素复制到其他位置,并返回这个数组。

语法:

    array.copyWithin(target, start = 0, end = this.length)

参数:

三个参数都是数值,如果不是,会自动转为数值.

  1. target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  2. start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
  3. end(可选):到该位置前停止读取数据,默认等于数组长度。使用负数可从数组结尾处规定位置。

浏览器兼容(MDN): chrome 45,Edge 12,Firefox32,Opera 32,Safari 9, IE 不支持

eg:

    // -2相当于3号位,-1相当于4号位
    [1, 2, 3, 4, 5].copyWithin(0, -2, -1)
    // [4, 2, 3, 4, 5]
    var a=['OB1','Koro1','OB2','Koro2','OB3','Koro3','OB4','Koro4','OB5','Koro5']
    // 2位置开始被替换,3位置开始读取要替换的 5位置前面停止替换
    a.copyWithin(2,3,5)
    // ["OB1","Koro1","Koro2","OB3","OB3","Koro3","OB4","Koro4","OB5","Koro5"] 

从上述栗子:

  1. 第一个参数是开始被替换的元素位置
  2. 要替换数据的位置范围:从第二个参数是开始读取的元素,在第三个参数前面一个元素停止读取
  3. 数组的长度不会改变
  4. 读了几个元素就从开始被替换的地方替换几个元素

fill() 填充数组

定义: 使用给定值,填充一个数组。

参数:

第一个元素(必须): 要填充数组的值

第二个元素(可选): 填充的开始位置,默认值为0

第三个元素(可选):填充的结束位置,默认是为this.length

MDN浏览器兼容

    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]
    ['a', 'b', 'c'].fill(7, 1, 2)
    // ['a', 7, 'c']

不改变原数组的方法(8个):

    ES5:
        slice、join、toLocateString、toStrigin、cancat、indexOf、lastIndexOf、
    ES7:
        includes

slice() 截取数组中的元素,浅拷贝

定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。

注意:字符串也有一个slice() 方法是用来提取字符串的,不要弄混了。

语法:

    array.slice(begin, end);

参数:

begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。

end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。

    let a= ['hello','world'];
    let b=a.slice(0,1); // ['hello']
    a[0]='改变原数组';
    console.log(a,b); // ['改变原数组','world'] ['hello']
    b[0]='改变拷贝的数组';
     console.log(a,b); // ['改变原数组','world'] ['改变拷贝的数组']

如上:新数组是浅拷贝的,元素是简单数据类型,改变之后不会互相干扰

如果是复杂数据类型(对象,数组)的话,改变其中一个,另外一个也会改变

    let a= [{name:'OBKoro1'}];
    let b=a.slice();
    console.log(b,a); // [{"name":"OBKoro1"}]  [{"name":"OBKoro1"}]
    // a[0].name='改变原数组';
    // console.log(b,a); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
    // b[0].name='改变拷贝数组',b[0].koro='改变拷贝数组';
    //  [{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]

原因在定义上面说过了的:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。

join() 将数组中所有元素通过指定分隔符(默认是逗号)连接成字符串

定义: join() 方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。

语法:

    array.join(str)

参数:

str(可选): 指定要使用的分隔符,默认使用逗号作为分隔符。

    let a= ['hello','world'];
    let str=a.join(); // 'hello,world'
    let str2=a.join('+'); // 'hello+world'

使用join方法或者下文说到的toString方法时,当数组中的元素也是数组或者是对象时会出现什么情况?

    let a= [['OBKoro1','23'],'test'];
    let str1=a.join(); // OBKoro1,23,test
    let b= [{name:'OBKoro1',age:'23'},'test'];
    let str2 = b.join(); // [object Object],test
    // 对象转字符串推荐JSON.stringify(obj);

所以,join()/toString()方法在数组元素是数组的时候,会将里面的数组也调用join()/toString(),如果是对象的话,对象会被转为[object Object]字符串。

toLocaleString() 数组转字符串

定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。

语法:

    array.toLocaleString()

参数:无。

    let a=[{name:'OBKoro1'},23,'abcd',new Date()];
    let str=a.toLocaleString(); // [object Object],23,abcd,2018/5/28 下午1:52:20 

如上述栗子:调用数组的toLocaleString方法,数组中的每个元素都会调用自身的toLocaleString方法,对象调用对象的toLocaleString,Date调用Date的toLocaleString

toString() 数组转字符串 不推荐

定义: toString() 方法可把数组转换为由逗号链接起来的字符串。

语法:

    array.toString()

参数: 无。

该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。

值得注意的是:当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串

   let b= [ 'toString','演示'].toString(); // toString,演示
   let a= ['调用toString','连接在我后面']+'啦啦啦'; // 调用toString,连接在我后面啦啦啦

cancat 合并数组,浅拷贝

定义: 方法用于合并两个或多个数组,返回一个新数组。

语法:

    var newArr =oldArray.concat(arrayX,arrayX,......,arrayX)

参数:

arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个。

eg1:

    let a = [1, 2, 3];
    let b = [4, 5, 6];
    //连接两个数组
    let newVal=a.concat(b); // [1,2,3,4,5,6]
    // 连接三个数组
    let c = [7, 8, 9]
    let newVal2 = a.concat(b, c); // [1,2,3,4,5,6,7,8,9]
    // 添加元素
    let newVal3 = a.concat('添加元素',b, c,'再加一个'); 
    // [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"]
   // 合并嵌套数组  会浅拷贝嵌套数组
   let d = [1,2 ];
   let f = [3,[4]];
   let newVal4 = d.concat(f); // [1,2,3,[4]]
    // 应用:下拉加载下一页,返回值是新的数组
  // 注意使用时,需要赋值

...扩展运算符 合并数组和对象

因为ES6的语法更简洁易懂,...运算符可以实现cancat的每个栗子,且更简洁和具有高度自定义数组元素位置的效果。

    let a = [2, 3, 4, 5]
    let b = [ 4,...a, 4, 4]
    console.log(a,b); //  [2, 3, 4, 5] [4,2,3,4,5,4,4]

更多关于扩展符的详细内容移步阮一峰大神的ECMAScript 6 入门

-   可以用来合并 复制 `Object` 对象。
-   直接展示对象 (`...obj`) 会抛出错误!
let obj1 = {
  a: '1',
  b: '2',
  c: '3'
}

let obj2 = {
  b: '4',
  a: '1',
  c: '0'
}

// 存在重复key  以第二个参数为准
let obj3 = {...obj1, ...obj2}
console.log(obj3)  // { a: '1', b: '4', c: '0' }

// 不存在重复值 
let obj4 = {...obj1, ...obj2}
console.log(obj4)  // { a: '1', b: '2', c: '3', d: '4', e: '1', f: '0' }

// 复制对象 互不影响
let obj5 = {...obj1}
obj5['b'] = '1111'
console.log(obj5, obj1)   // { a: '1', b: '1111', c: '3' } { a: '1', b: '2', c: '3' }

// 解构剩余参数  需要注意的是 剩余参数一定要在最后边
const {a, ...res} = obj1
console.log(a, res) // 1 {b: '2', c: '3'}

扩展运算符 + set 数组去重
const arr = ['a','b','c','a','b'];
const newArr = [...new Set(arr)];

indexOf() 查找数组是否存在某个元素,找到返回下标,不存在返回-1

定义: 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

语法:

    array.indexOf(searchElement,fromIndex)

参数:

searchElement(必须):被查找的元素

fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。

严格相等的搜索:

数组的indexOf搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等===搜索元素,即数组元素要完全匹配才能搜索成功。

注意:indexOf()不能识别NaN

eg:

    let a=['啦啦',2,4,24,NaN]
    console.log(a.indexOf('啦'));  // -1 
    console.log(a.indexOf('NaN'));  // -1 
    console.log(a.indexOf('啦啦')); // 0

使用场景:

  1. 数组去重
  2. 根据获取的数组下标执行操作,改变数组中的值等。
  3. 判断是否存在,执行操作。
    let unique= arr =>{
         let newA=[];
        arr.forEach(key => {
           if( newA.indexOf(key)<0 ){ //遍历newA是否存在key,如果存在key会大于0就跳过push的那一步
             newA.push(key);
           }
        });
        return newA;
    }
    console.log(unique(a)) ;//["1", "2", "3", 1, NaN, NaN, undefined, null, "a", "b"]
//ps:这个方法不能分辨NaN,会出现两个NaN。是有问题的,下面那个方法好一点。
  1. 删除重复值

您可能已经将 indexOf() 与 for 循环一起使用,该循环返回第一个找到的索引或较新的 includes() 从数组中返回布尔值 true/false 以找出/删除重复项。 这是我们有两种更快的方法。

const array  = [5,4,7,8,9,2,7,5];
array.filter((item,idx,arr) => arr.indexOf(item) === idx);
// or
const nonUnique = [...new Set(array)];
// 输出: [5, 4, 7, 8, 9, 2]

lastIndexOf() 查找指定元素在数组中的最后一个位置

定义: 方法返回指定元素,在数组中的最后一个的索引,如果不存在则返回 -1。(从数组后面往前查找)

语法:

    arr.lastIndexOf(searchElement,fromIndex)

参数:

searchElement(必须): 被查找的元素

fromIndex(可选): 逆向查找开始位置,默认值数组的长度-1,即查找整个数组。

关于fromIndex有三个规则:

  1. 正值。如果该值大于或等于数组的长度,则整个数组会被查找。

  2. 负值。将其视为从数组末尾向前的偏移。(比如-2,从数组最后第二个元素开始往前查找)

  3. 负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。

     let a=['OB',4,'Koro1',1,2,'Koro1',3,4,5,'Koro1']; // 数组长度为10
     // let b=a.lastIndexOf('Koro1',4); // 从下标4开始往前找 返回下标2
     // let b=a.lastIndexOf('Koro1',100); //  大于或数组的长度 查找整个数组 返回9
     // let b=a.lastIndexOf('Koro1',-11); // -1 数组不会被查找
     let b=a.lastIndexOf('Koro1',-9); // 从第二个元素4往前查找,没有找到 返回-1
    

includes() 查找数组中是否包含某个元素 返回布尔

定义: 返回一个布尔值,表示某个数组是否包含给定的值

语法:

    array.includes(searchElement,fromIndex=0)

参数:

searchElement(必须):被查找的元素

fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。

includes方法是为了弥补indexOf方法的缺陷而出现的:

  1. indexOf方法不能识别NaN,includs可以
  2. indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于-1,表达不够直观

eg:

    let a=['OB','Koro1',1,NaN];
    // let b=a.includes(NaN); // true 识别NaN
    // let b=a.includes('Koro1',100); // false 超过数组长度 不搜索
    // let b=a.includes('Koro1',-3);  // true 从倒数第三个元素开始搜索 
    // let b=a.includes('Koro1',-100);  // true 负值绝对值超过数组长度,搜索整个数组
条件判断[includes]
 if(type == 1 || type == 2 || type == 3) {
  // do something
}
// 或者
switch(type){
  case 1:
  case 2:
  case 3:
    // dom something
}
es6 多参数判断
if([1,2,3].includes(type)){
  // do something
}
// 以前
if (value == 0 || value == 1 || value == 100 || value == 2000) {
  // xxx
}
// 现在
if ([0, 1, 100, 2000].indexOf(value) >= 0) {
  // xxx
}
if ([0, 1, 100, 2000].includes(value)) {
  // xxx
}
 

兼容性(MDN): chrome47, Firefox 43,Edge 14,Opera 34, Safari 9,IE 未实现。


Array.of() 返回由所有参数值组成的数组

定义:返回由所有参数值组成的数组,如果没有参数,就返回一个空数组。

目的:Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题。

    let a = Array.of(3, 11, 8); // [3,11,8]
    let a = Array.of(3); // [3]

Arrary.from() 将类似数组的对象转为真正的数组

定义:用于将两类对象转为真正的数组(不改变原对象,返回新的数组)。

参数:

第一个参数(必需):要转化为真正数组的对象。

第二个参数(可选): 类似数组的map方法,对每个元素进行处理,将处理后的值放入返回的数组。

第三个参数(可选): 用来绑定this。

    // 1. 对象拥有length属性
    let obj = {0: 'a', 1: 'b', 2:'c', length: 3};
    let arr = Array.from(obj); // ['a','b','c'];
    
    // 2. 部署了 Iterator接口的数据结构 比如:字符串、Set、NodeList对象
    let arr = Array.from('hello'); // ['h','e','l','l','o']
    let arr = Array.from(new Set(['a','b'])); // ['a','b']
    
    Array.from + Set 数组去重
    const arr = ['a','b','c','a','b'];
    const newArr = Array.from(new Set(arr));//abc

遍历方法(12个):

js中遍历数组并不会改变原始数组的方法总共有12个:

    ES5:
        forEach、everysomefilter、map、reduce、reduceRight、
    ES6:
        find、findIndex、keys、values、entries

关于遍历:

  • 关于遍历的效率,可以看一下这篇详解JS遍历
  • 尽量不要在遍历的时候,修改后面要遍历的值
  • 尽量不要在遍历的时候修改数组的长度(删除/添加)

forEach 遍历数组

定义: 按升序为数组中含有效值的每一项执行一次回调函数。

语法:

    array.forEach(function(currentValue, index, arr), thisValue)

参数: (当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

关于forEach()你要知道

  • 无法中途退出循环,只能用return退出本次回调,进行下一次回调。
  • 它总是返回 undefined值,即使你return了一个值。

下面类似语法同样适用这些规则

    1. 对于空数组是不会执行回调函数的
    2. 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
    3. 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历。
    4. 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。

eg:

    let a = [1, 2, ,3]; // 最后第二个元素是空的,不会遍历(undefined、null会遍历)
    let obj = { name: 'OBKoro1' };
    let result = a.forEach(function (value, index, array) { 
      a[3] = '改变元素';
      a.push('添加到尾端,不会被遍历')
      console.log(value, 'forEach传递的第一个参数'); // 分别打印 1 ,2 ,改变元素
      console.log(this.name); // OBKoro1 打印三次 this绑定在obj对象上
      // break; // break会报错
      return value; // return只能结束本次回调 会执行下次回调
      console.log('不会执行,因为return 会执行下一次循环回调')
    }, obj);
    console.log(result); // 即使return了一个值,也还是返回undefined
    // 回调函数也接受接头函数写法

every 数组中所有元素都符合条件才返回true

定义: 方法用于检测数组所有元素是否都符合函数定义的条件

语法:

    array.every(function(currentValue, index, arr), thisValue)

参数:(当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

方法返回值规则:

  1. 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
  2. 如果所有元素都满足条件,则返回 true。=

eg:

    function isBigEnough(element, index, array) { 
      return element >= 10; // 判断数组中的所有元素是否都大于10
    }
    let result = [12, 5, 8, 130, 44].every(isBigEnough);   // false
    let result = [12, 54, 18, 130, 44].every(isBigEnough); // true
    // 接受箭头函数写法 
    [12, 5, 8, 130, 44].every(x => x >= 10); // false
    [12, 54, 18, 130, 44].every(x => x >= 10); // true

some 数组有符合条件的一项就返回true

定义:数组中的是否有满足判断条件的元素,如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。

语法:

    array.some(function(currentValue, index, arr), thisValue)

参数:(当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

方法返回值规则:

  1. 如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。

  2. 如果没有满足条件的元素,则返回false

     function isBigEnough(element, index, array) {
       return (element >= 10); //数组中是否有一个元素大于 10
     }
     let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
     let result = [12, 5, 8, 1, 4].some(isBigEnough); // true
    

filter 根据条件过滤原始数组,返回新数组

定义: 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法:

    let new_array = arr.filter(function(currentValue, index, arr), thisArg)

参数:(当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

eg:

     let a = [32, 33, 16, 40];
    let result = a.filter(function (value, index, array) {
      return value >= 18; // 返回a数组中所有大于18的元素
    });
    console.log(result,a);// [32,33,40] [32,33,16,40]
    
 从数组中过滤出虚假值
    Falsy值喜欢`0``undefined``null``false``""``''`可以很容易地通过以下方法省略
    const array = [3, 0, 6, 7, '', false];
    array.filter(Boolean);
    // 输出
    (3) [3, 6, 7]

map 对数组中的每个元素进行处理,返回新的数组

定义:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

语法:

    let new_array = arr.map(function(currentValue, index, arr), thisArg)

参数:(当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

eg:

let a = ['1','2','3','4'];
let result = a.map(function (value, index, array) {
  return value + '新数组的新元素'
});
console.log(result, a); 
// ["1新数组的新元素","2新数组的新元素","3新数组的新元素","4新数组的新元素"] ["1","2","3","4"]

// 配合箭头函数,对每一个元素进行翻倍
const mapArr2 = mapArr.map((num, index, arr) => 2 * num)
console.log(mapArr2)
[ 2, 4, 6, 8, 10 ]

reduce 为数组提供累加器,合并为一个值

定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。

语法:

    array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

参数:

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. total(必须),初始值, 或者上一次调用回调返回的值
    2. currentValue(必须),数组当前元素的值
    3. index(可选), 当前元素的索引值
    4. arr(可选),数组对象本身

initialValue(可选): 指定第一次回调 的第一个参数。

回调第一次执行时:

  • 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
  • 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
  • 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。

eg:

reduce第二个参数有的话,函数内第一个参数等于reduce的第二个参数
reduce没有第二个参数时,函数内第一个参数等于数组的第一个元素,第二个参数等于数组的第二个元素
    // 数组求和 
    let sum = [0, 1, 2, 3].reduce(function (a, b) {
      return a + b;
    }, 0);
    // 6
    // 将二维数组转化为一维 将数组元素展开
    let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
      (a, b) => a.concat(b),[]);

     // [0, 1, 2, 3, 4, 5]
// 统计元素出现个数
const nameArr = ['林三心', 'sunshine_lin', '林三心', '林三心', '科比']
const totalObj = nameArr.reduce((pre, next) => {
  if (pre[next]) {
    pre[next]++
  } else {
    pre[next] = 1
  }
  return pre
}, {})
console.log(totalObj) // { '林三心': 3, sunshine_lin: 1, '科比': 1 }

 reduce 代替 map 和 filter

// 这里先随便给一个数组,然后需求是拿到数组*2以后大于30的新数组
// 先看一下基本操作

const numList = [10,20,30,40]

// 看一下非常普遍的用法 
numList.map(num => num * 2).filter(num => num > 30)

// 再来看改造后的
numList.reduce((arr,num)=>{
   const newNum =  num * 2
   newNum > 30 && arr.push(newNum)
},[])

reduceRight 从右至左累加

这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。

find()& findIndex() 根据条件找到数组成员

find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。

findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

这两个方法

语法:

    let new_array = arr.find(function(currentValue, index, arr), thisArg)
     let new_array = arr.findIndex(function(currentValue, index, arr), thisArg)

参数:(当前遍历元素,下标 ,数组本身)

function(必须): 数组中每个元素需要调用的函数。

    // 回调函数的参数
    1. currentValue(必须),数组当前元素的值
    2. index(可选), 当前元素的索引值
    3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

这两个方法都可以识别NaN,弥补了indexOf的不足.

eg:

        // find
        let a = [1, 4, -5, 10].find((n) => n < 0); // 返回元素-5
        let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n));  // 返回元素NaN
        // findIndex
        let a = [1, 4, -5, 10].findIndex((n) => n < 0); // 返回索引2
        let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n));  // 返回索引4

浏览器兼容(MDN):Chrome 45,Firefox 25,Opera 32, Safari 8, Edge yes,

Array.flat()用于拉平嵌套的数组

可以把多维的数组转化为一维数组

数组的成员有时还是数组,Array.flat()用于将嵌套的数组“拉平”,变成一维的数组。
该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat() 读音【fu la t】

flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组。
可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1[1, 2, [3, [4, 5]]].flat()
上面代码中,表示想要拉平的层数,默认为1
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
上面代码中,flat()的参数为2,表示要“拉平”两层的嵌套数组。
// [1, 2, 3, 4, 5]

如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

//嵌套数组key,value拍平
const deps = {
    '采购部':[1,2,3],
    '人事部':[5,8,12],
    '行政部':[5,14,79],
    '运输部':[3,64,105],
}
let member = Object.values(deps).flat(Infinity);

// 移除数组中的空项
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

现在给你一个需求
let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"];
转为[ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
第一种
console.log(arr.map(x => x.split(" ")).flat());
// [ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
//第二种
console.log(arr.flatMap(x => x.split(" ")));
// [ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]

at()返回对应下标的值

我们都知道JavaScript不支持数组索引值为负索引。 
那么想要表示数组的最后一个成员,不能写成arr[-1],只能使用arr[arr.length - 1]。

为了解决负索引这个问题,es6中为数组实例增加了at()方法,接受一个整数作为参数。
返回对应位置的成员,支持负索引。
这个方法不仅可用于数组, 也可用于字符串和类型数组( TypedArray)。
如果参数位置超出了数组范围,at()返回undefined。

const arr = [100, 120, 18, 130, 4];
console.log(arr.at(1)) //120
console.log(arr.at(-1)) //4
console.log(arr.at(-5)) //100
console.log(arr.at(-6)) //undefined

keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值

定义:三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值。

语法:

    array.keys()
    array.values()
    array.entries()

参数:无。

遍历栗子(摘自ECMAScript 6 入门):

    for (let index of ['a', 'b'].keys()) {
      console.log(index);
    }
    // 0
    // 1
    
    for (let elem of ['a', 'b'].values()) {
      console.log(elem);
    }
    // 'a'
    // 'b'
    
    for (let [index, elem] of ['a', 'b'].entries()) {
      console.log(index, elem);
    }
    // 0 "a"
    // 1 "b"

for..of中如果遍历中途要退出,可以使用break退出循环。

如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历:

    let letter = ['a', 'b', 'c'];
    let entries = letter.entries();
    console.log(entries.next().value); // [0, 'a']
    console.log(entries.next().value); // [1, 'b']
    console.log(entries.next().value); // [2, 'c']

entries()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 7.1

keys()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 8,

注意:目前只有Safari 9支持,,其他浏览器未实现,babel转码器也还未实现


数据类型转换

转换为number类型

  1. 其他数据类型 转 Number
  • parseInt() : 转换整数
  • parseFloat() :转换小数
  • Number() : 其他数据类型转数字 (boolean,undefined,null)

注意点 数据类型转换并没有改变原有变量的值,而是产生一个新的值

  1. Number() : 其他数据类型转数字
  • a. 可以解析整数和小数
  • b. 只要有任意非数字字符得到NaN
  • c.布尔类型转数字会得到false=>0和true=>1
  • null --> 0 undefined --> NaN

转换为字符串类型

  1. +号
  2. String(数据)
  3. 变量名.toString()
  • a. 如果是undefined与null,这种方式会报错
  • b. 这种方式可以支持进制转换。 例如把 十进制转十六进制
const val = 1 + ""; // 通过+ ''空字符串转化
console.log(val); // "1"
console.log(typeof val); // "string"

const val1 = String(1);
console.log(val1); // "1"
console.log(typeof val1); // "string"

转换为boolean类型

Boolean(数据)

  • 1.false: 有7种数据会得到false 0 -0 NaN '' undefined null false
  • 2.true: 除false 7种之外的一切数据

转时间戳

    +new Date()      // 1634006099827
    Number(new Date())// 1634006099827
    Date.parse(new Date()) //1664355942000
 精确到毫秒

    let time =Date.parse(new Date())

 精确到秒

    let time =Date.parse(new Date()).toString().substr(0,10)

字符串与数组之间的相互转换

字符串转换为数组 str.split(','); // 以逗号,为拆分的字符串 
数组转换为字符串 arr.join(','); // 把数组项拼接成字符串,以逗号,分隔

隐式转换

隐式转换 : 当运算符两边的 ‘数据类型不一致’ 的时候,编译器会转成一致后运算

  • (1)转换数字 : 算术运算符 + - * / %
  • (2)转换字符串 : 连接符+ (+号两边只要有一边是字符串,此时+就是连接符)
  • (3)转换布尔: 逻辑非 !

类数组 or 伪数组 转数组

  • 类数组对象 、类数组 转数组
let pseudoArray = {
  0: '1',
  1: '2',
  2: '3',
  length: 3
}

例1 通过数组 `slice`
let arr = [].slice.call(pseudoArray)
console.log(arr) //  ["1", "2", "3"]

例2 通过 es6 数组方法 `from`
let arr = Array.from(pseudoArray)
console.log(arr) //  ["1", "2", "3"]
  • 类数组转数组 (通过数组参数 Arguments, 通过 document.getElementByxxx 获取的数据等)
// 通过扩展运算符
let divs = document.getElementsByTagName('div')
let arr = [...divs]
arr.push('1')

// Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,
// 其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)
let arr1 = Object.entries(divs)
arr1.push('1')

Array

数组是否为判断

Array.isArray()

  • 语法:Array.isArray(obj)
  • 参数:需要检查的对象
  • 返回:true,false

利用数组交换值

[a, b] = [b, a];

数组交集

普通数组

const arr1 = [1, 2, 3, 4, 5 , 8 ,9],arr2 = [5, 6, 7, 8, 9];
const intersection = arr1.filter(function (val) { return arr2.indexOf(val) > -1 })
console.log(intersection) //[5, 8, 9]

数组对象 数组对象目前仅针对value值为简单的Number,String,Boolan数据类型 文中JSON.stringif比较对象是简写方法,完整的对象比较请看技巧24.对象是否相等

const arr1 = [
    { name: 'name1', id: 1 },
    { name: 'name2', id: 2 }, 
    { name: 'name3', id: 3 },
    { name: 'name5', id: 5 }
    ];
const arr2 = [
    { name: 'name1', id: 1 },
    { name: 'name2', id: 2 },
    { name: 'name3', id: 3 }, 
    { name: 'name4', id: 4 }, 
    { name: 'name5', id: 5 }
    ];
const result = arr2.filter(function (v) {
  return arr1.some(n => JSON.stringify(n) === JSON.stringify(v))
})
console.log(result); // [{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name5', id: 5 }]

数组并集

普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const result = arr1.concat(arr2.filter(v => !arr1.includes(v)))
console.log(result) //[1, 2, 3, 4, 5, 8, 9, 6, 7]
复制代码

数组对象

const arr1 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }];
const arr2 = [{ name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
let arr3 = arr1.concat(arr2);
let result = [];
let obj = [];
result = arr3.reduce(function (prev, cur, index, arr) {
  obj[cur.id] ? '' : obj[cur.id] = true && prev.push(cur);
  return prev;
}, []);
console.log(result); //[{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]
复制代码

数组差集

数组arr1相对于arr2所没有的 普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const diff = arr1.filter(item => !new Set(arr2).has(item))
console.log(diff) //[ 1, 2, 3, 4 ]
复制代码

数组对象

// 对象数组
let arr1 = [
    { name: 'name1', id: 1 },
    { name: 'name2', id: 2 }, 
    { name: 'name3', id: 3 }
];
let arr2 = [
    { name: 'name1', id: 1 }, 
    { name: 'name4', id: 4 }, 
    { name: 'name5', id: 5 }
];
let result = arr1.filter(function (v) {
  return arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
})
console.log(result); // [ { name: 'name2', id: 2 }, { name: 'name3', id: 3 } ]

实现并集、交集、和差集

let a = new Set([1, 2, 3]); 
let b = new Set([4, 3, 2]); 
// 并集 
let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} 
// 交集 
let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} 
// (a 相对于 b 的)差集 
let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}

数组补集

两个数组各自没有的集合 普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const difference = Array.from(new Set(arr1.concat(arr2).filter(v => !new Set(arr1).has(v) || !new Set(arr2).has(v)))) 
console.log(difference) //[ 1, 2, 3, 4, 6, 7 ]
复制代码

数组对象

let arr1 = [
    { name: 'name1', id: 1 },
    { name: 'name2', id: 2 }, 
    { name: 'name3', id: 3 }
];
let arr2 = [
    { name: 'name1', id: 1 }, 
    { name: 'name4', id: 4 }, 
    { name: 'name5', id: 5 }
];
let arr3 = arr1.concat(arr2);
let result = arr3.filter(function (v) {
  return arr1.every(n => JSON.stringify(n) !== JSON.stringify(v)) || arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
})
console.log(result); // [{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]

总结一下,差集就是数组arr1相对于arr2所没有的集合,补集是两个数组各自没有的集合

数组去重

普通数组

console.log(Array.from(new Set([1, 2, 3, 3, 4, 4]))) //[1,2,3,4]
console.log([...new Set([1, 2, 3, 3, 4, 4])]) //[1,2,3,4]

数组对象

const arr = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }, { name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
 const result = [];
 arr.forEach(item=>{
    !result.some(v => JSON.stringify(v) === JSON.stringify(item)) && result.push(item)
 })
 console.log(result) //[{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]

数组内对象去重(重复键)

一般情况下对象数组中的对象都有其唯一键,所以不需判断所有属性都相等。

arrDistinctByKey(arr, key){
  const temp = new Map(); // 使用Map可以比对多种类型,不限于字符串
  return arr.filter((item) => !temp.has(item[key]) && temp.set(item[key], true));
}

// 常用于过滤列表
const list = [{id: 1, name: 'xiaoming'}, {id: 1, name: 'xiaoming'}, {id: 2, name: 'xiaoliang'}];
const newArr = arrDistinctByKey(list, 'id');
// newArr: [{id: 1, name: 'xiaoming'}, {id: 2, name: 'xiaoliang'}]
复制代码

数组排序

普通数组

console.log([1, 2, 3, 4].sort((a, b) => a - b)); // [1, 2,3,4] 升序
console.log([1, 2, 3, 4].sort((a, b) => b - a)); // [4,3,2,1] 降序
复制代码

数组对象

const arr1 = [{ name: "Rom", age: 12 }, { name: "Bob", age: 22 }].sort((a, b) => { return a.age - b.age })//升序
const arr2 = [{ name: "Rom", age: 12 }, { name: "Bob", age: 22 }].sort((a, b) => { return -a.age + b.age })//降序
console.log(arr2) // [{ name: 'Bob', age:22 }, { name: 'Rom', age: 12 }]
console.log(arr1) // [ { name: 'Rom', age: 12 }, { name: 'Bob', age: 22 } ]
复制代码

两个种类型数组都可以使用sort排序,sort是浏览器内置方法; 默认是升序排序,默认返回一个函数,有两个参数: (a, b) => a - b 是升序; (a, b) => b - a 是降序。

最大值和最小值

普通数组

Math.min(...[1, 2, 3, 4]) //1
Math.max(...[1, 2, 3, 4]) //4
Math.max.apply(this, [1, 2, 3, 4]) //4
[1, 2, 3, 4].reduce((prev, cur, curIndex, arr) => {
   return Math.max(prev, cur);
}, 0) //4

取数组对象中id的最大值和最小值

const arr = [
    { id: 1, name: 'jack' },
    { id: 2, name: 'may' },
    { id: 3, name: 'shawn' },
    { id: 4, name: 'tony' }
]
//最大值
const arr1 = Math.max.apply(Math, arr.map(item => { return item.id }))
//最小值
const arr1 = Math.max.apply(Math, arr.map(item => { return item.id }))
const arr2 = arr.sort((a, b) => { return b.id - a.id })[0].id
console.log(arr1) // 4
console.log(arr2) // 4

数组求和

普通数组

[1, 2, 3, 4].reduce(function (prev, cur) {
  return prev + cur;
}, 0) //10 

数组对象

const sum = [{age:1},{age:2}].reduce(function (prev, cur) {
  return prev + cur.age;
}, 0) //3
console.log(sum)

数组合并

普通数组

const arr1 = [1, 2]; const arr2 = [3, 4]; arr1.push(...arr2);//[1,2,3,4]
const arr1 =[1, 2, 3, 4].concat([5, 6]) //[1,2,3,4,5,6]
const arr2 =[...[1, 2, 3, 4],...[4, 5]] //[1,2,3,4,5,6]
const arrA = [1, 2], arrB = [3, 4]
const arr3 =[].concat.apply(arrA, arrB)//arrA值为[1,2,3,4]

综合性能对比: push() > concat() > […array1,…array2]  , 扩展运算符基本会慢10倍以上,不推荐使用。

数组对象


const arr4 = [{ age: 1 }].concat([{ age: 2 }])
const arr5 = [...[{ age: 1 }],...[{ age: 2 }]]
console.log(arr4) //[ { age: 1 }, { age: 2 } ]
console.log(arr5) // [ { age: 1 }, { age: 2 } ]
复制代码

数组是否包含值

普通数组

console.log([1, 2, 3].includes(4)) //false
console.log([1, 2, 3].indexOf(4)) //-1 如果存在换回索引
console.log([1, 2, 3].find((item) => item === 3)) //3 如果数组中无值返回undefined
console.log([1, 2, 3].findIndex((item) => item === 3)) //2 如果数组中无值返回-1
复制代码

数组对象

const flag = [{age:1},{age:2}].some(v=>JSON.stringify(v)===JSON.stringify({age:2}))
console.log(flag)
复制代码

数组每一项都满足

普通数组

[1, 2, 3].every(item => { return item > 2 })
复制代码

数组对象

const arr = [{ age: 3 }, { age: 4 }, { age: 5 }]
arr.every(item => { return item.age > 2 }) // true
复制代码

数组有一项满足

普通数组

[1, 2, 3].some(item => { return item > 2 })
复制代码

数组对象

const arr = [{ age: 3 }, { age: 4 }, { age: 5 }]
arr.some(item => { return item.age < 4 }) // true
复制代码

版本号排序

方法一

function sortNumber(a, b) {
  return a - b
}
const b = [1,2,3,7,5,6]
const a = ["1.5", "1.5", "1.40", "1.25", "1.1000", "1.1"];

console.log(a.sort(sortNumber)); // [ 1, 2, 3, 5, 6, 7 ]
console.log(b.sort(sortNumber)); //[ '1.1000', '1.1', '1.25', '1.40', '1.5', '1.5' ]
复制代码

可见sort排序对整数可以,类似版本号这个格式就不适用了,因为sort函数在比较字符串的时候,是比较字符串的Unicode进行排序的。

方法二

//假定字符串的每节数都在5位以下
//去除数组空值||空格
if (!Array.prototype.trim) {
  Array.prototype.trim = function () {
    let arr = []; this.forEach(function (e) {
      if (e.match(/\S+/)) arr.push(e);
    })
    return arr;
  }
}

//提取数字部分
function toNum(a) {
  let d = a.toString();
  let c = d.split(/\D/).trim();
  let num_place = ["", "0", "00", "000", "0000"], r = num_place.reverse();
  for (let i = 0; i < c.length; i++) {
    let len = c[i].length;
    c[i] = r[len] + c[i];
  }
  let res = c.join('');
  return res;
}

//提取字符
function toChar(a) {
  let d = a.toString();
  let c = d.split(/.|\d/).join('');
  return c;
}

function sortVersions(a, b) {

  let _a1 = toNum(a), _b1 = toNum(b);
  if (_a1 !== _b1) return _a1 - _b1;
  else {
    _a2 = toChar(a).charCodeAt(0).toString(16);
    _b2 = toChar(b).charCodeAt(0).toString(16);
    return _a2 - _b2;
  }
}

let arr1 = ["10", "5", "40", "25", "1000", "1"];
let arr2 = ["1.10", "1.5", "1.40", "1.25", "1.1000", "1.1"];
let arr3 = ["1.10c", "1.10b", "1.10C", "1.25", "1.1000", "1.10A"];
console.log(arr1.sort(sortVersions)) //[ '1', '5', '10', '25', '40', '1000' ]
console.log(arr2.sort(sortVersions)) //[ '1.1', '1.5', '1.10', '1.25', '1.40', '1.1000' ]
console.log(arr3.sort(sortVersions)) // [ '1.10A', '1.10C', '1.10b', '1.10c', '1.25', '1.1000' ]

可以看出这个函数均兼容整数,非整数,字母; 字母排序是根据Unicode排序的,所以1.10b在1.10C的后面

对象转数组

将数组的key和value转化成数组

Object.keys({ name: '张三', age: 14 }) //['name','age']
Object.values({ name: '张三', age: 14 }) //['张三',14]
Object.entries({ name: '张三', age: 14 }) //[[name,'张三'],[age,14]]
Object.fromEntries([name, '张三'], [age, 14]) //ES10的api,Chrome不支持 , firebox输出{name:'张三',age:14}

数组转对象

将数组的值转化为对象的value

const arrName = ['张三', '李四', '王五']
const arrAge=['20','30','40']
const arrDec = ['描述1', '描述2', '描述3']
const obj = arrName.map((item,index)=>{
  return { name: item, age: arrAge[index],dec:arrDec[index]}
})

console.log(obj) // [{ name: '张三', age: '20', dec: '描述1' },{ name: '李四', age: '30', dec: '描述2' },{ name: '王五', age: '40', dec: '描述3' }]

let arr = [{key: name, value: '张三'}, {key: age, value: 18}]
function arrToObj(arr) {
    let obj = {}
    arr.map(item => {
        obj[item.key] = item.value;
    })
    console.log(obj) // {name: '张三', age: 18}
}
arrToObj(arr);

数组转字符串

Array.prototype.toString()

  • 语法:arr.toString()
  • 返回:数组的字符串表示

Array.prototype.toLocaleString()

  • 语法:
    • arr.toLocaleString();
    • arr.toLocaleString(locales);
    • arr.toLocaleString(locales, options);
  • 参数:
    • locales BCP 47语言标签字符串
    • options 可选 配置参数对象
  • 返回:数组的字符串表示

Array.prototype.join()

  • 语法:
    • arr.join()
    • arr.join(separator)
  • 参数:separator 可选 分割符
  • 返回:数组的字符串表示

var months = ['Jan', 'Feb', 'Mar', 'Apr'];

months.toString(); // "Jan,Feb,Mar,Apr"

months.join('-'); // "Jan-Feb-Mar-Apr"

数组解构赋值

// 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1,2,3

// 接收参数多于 数组容量
let [a, b, c, d] = [1, 2, 3];
console.log(a, b, c, d); // 1,2,3, undefined 相当于声明未赋值

// 解构默认赋值
let [a, b, c=1] = [1, 2, 3];
console.log(a, b, c); // 1,2,3  数组中有对应元素 默认赋值无效 所以c = 3

数组遍历解构[for..of]取数组的元素

let a = [{ score: 40, name: 'jack' }, { score: 70, name: 'tom' }, { score: 90, name: 'lucy' }]
// 元素查找: for..of + 解构
let user = []
for(let { score, name } of a){
  if(score > 60) user.push(name)
}

快速创建一个指定长度的数组并填充内容

const array = new Array(100).fill('');
// (100) ['', '', ..., '']
复制代码
const array = Array.from(new Array(100).keys());
  // (100) [0, 1, ..., 98, 99]
复制代码
const array = Array.from({length: 100}, (v,i) => i);
  // (100) [0, 1, ..., 98, 99]
复制代码

清空数组

有些情况下不使用arr=[]。原因是如下 arr=[]创建一个新的数组,并将对它的引用分配了给用到的变量。其他引用不
受影响,但仍指向原始数组,有内存泄漏的风险,而arr.length=0修改了原数组,其他变量访问到的成了修改后的。

删除数组元素

很多同学会用 delete 删除数组的值,这样做数组长度并不会发生变化,并且取值会是 undefined

image.png

推荐使用 splice 来删除数组元素

const array = ["a", "b", "c", "d"] 
array.splice(0, 2) // ["a", "b"]

去除数组中的无效值

有时候因为一些异常,导致收集数据的数组内容不正确,此时就需要对数组的值进行处理。

//使用filter
let arr = [1, 2, 3, undefined,null, '', ];
arr = arr.filter(item=>item);
console.log(arr)  [1,2,3]

判断集合/数组是否为空

数组为空时length为0 为空:array == undefined || array.length <= 0 (顺序不能调换)

不为空: array !==undefined && array.length > 0

if(arr.length){}//数组为空不执行

if(arr.length==0){}//数组为空执行

数组深拷贝

简单数组拷贝,只能拷贝一层,不适用于对象数组

 const arr = [1, 2, 3];

 const arr1 = arr.slice(0); // slice

 const arr2 = arr.concat(); // concat

 const arr3 = [...arr]; // 扩展运算符

 const arr4 = Array.from(arr); // from
复制代码

使用JSON

const arr = [{age: 1}, {age: 2}, {age: 3}];
const newArr = JSON.parse(JSON.stringify(arr));
复制代码

但是此种方式并不可靠,以下几种情况需要注意:

const obj = {
  nan: NaN, // NaN拷贝后变成null
  infinityMax:1.7976931348623157E+10308,  // 浮点数的最大值拷贝后变成null
  infinityMin:-1.7976931348623157E+10308, // 浮点数的最小值拷贝后变成null
  undef: undefined, // 拷贝后直接丢失
  fun: () => 'func', // 拷贝后直接丢失
  date:new Date, // 时间类型拷贝后会被变成字符串类型数据
}

复制代码

这不能怪JSON,因为人家本意就不是做深拷贝的,只是开发者的一厢情愿。

JSON.stringify 的目的是将数据转成 json 格式,而 json 有自己的规范,并不是 js 专属,它是跨语言的,各种语言都用统一的 json 格式来通信,所以只能支持各种语言常用的数据类型。

更可靠一些的,使用下面方法实现:

/**
* 深拷贝
* @param {Object|Array} target  拷贝对象
* @returns {Object|Array} result 拷贝结果
*/
  function deepCopy(target) {
    if (Array.isArray(target)) { // 处理数组
      return target.map(item => deepCopy(item));
    } 

    if (Object.prototype.toString.call(target)=== '[object Object]') { // 处理对象
       // 先将对象转为二维数组,再将二维数组转回对象(这个过程还是浅拷贝)
       // 所以使用map方法将二维数组里的元素进行深拷贝完了再转回对象
       return Object.fromEntries(Object.entries(target).map(([k, v]) => [k, deepCopy(v)]));
    }
    return target; // 深拷贝要处理的就是引用类型内部属性会变化的情况,像正则、Error、函数、Date这种一般不会发生变化的直接返回原数据就可以
  }
复制代码

上面的方法已经足够覆盖你的大多数场景了。 但是它也不是完美的,例如Map,Set等还是浅拷贝,而且存在循环引用会导致栈溢出的问题,戳这里去看更好的深拷贝方案,深拷贝渐进式解决方案

在数组中每隔n个元素插入一个新元素

const n = 2;
const list = Array.from(new Array(10).keys()); // mock数据
const addElement = {a: 1}; // 新增元素

// 优化for循环
for (let i = 0, len = list.length; i < Math.floor(len / n); i++) {
  // i: 0; 1; 2; 3; 4; 5
  // (i + 1) * n) + i: 2; 5; 8; 11; 14
  list.splice(((i + 1) * n) + i, 0, addElement);  // 相应位置上添加元素
}
console.log(list); // [0, 1, {…}, 2, 3, {…}, 4, 5, {…}, 6, 7, {…}, 8, 9, {…}]

修改数组内对象的所有key

let arr = [
    {
        mingzi:'小明',
        nianling:23
    },
    {
        mingzi:'小明',
        nianling:23
    },
    {
        mingzi:'小明',
        nianling:23
    },
    {
        mingzi:'小明',
        nianling:23
    },
    {
        mingzi:'小明',
        nianling:23
    },
]

 function changeSelectKey(keyMap,query){
    let newArr = query;
    newArr.map((item, index)=>{
        let obj = newArr[index];
        for(let key in obj){
            let newkey = keyMap[key];
            if(newkey){
                obj[newkey] = obj[key];
                delete obj[key];
            }
        }
    })
    return newArr;
}

let keyMap = {
    mingzi:'name',
    nianling:'age'
}

changeSelectKey(keyMap, arr)
console.log(arr)
// [{name: "小明", age: 23},{name: "小明", age: 23},{name: "小明", age: 23},{name: "小明", age: 23}]

统一删除或过滤数组中的每一项对象的指定属性

需求描述

有一数组,数组中的每一项放置的是一个个的对象,结构如下:

      let arr = [
            {
                name: "孙悟空",
                age: 500,
                home: "花果山"
            },
            {
                name: "猪八戒",
                age: 88,
                home: "高老庄"
            },
            {
                name: "沙和尚",
                age: 1000,
                home: "通天河"
            },
        ]

想加工数据以后,使得每一项的home属性都不要,只留name和age属性。

方式一(map映射,指定属性不映射过去)

       let newArr = arr.map((item) => {
            let obj = {
                name: item.name,
                age: item.age
            }
            return obj
        })
        console.log(newArr);

方式二(过滤遍历并delete删除指定属性)

       let newArr = arr.filter((item, index) => {
            return delete item.home // 注意:打印一下delete item.home返回的是true
        });
        console.log(newArr);

** 方式三(普通遍历直接删除指定属性)**

        // forEach遍历
        arr.forEach((item) => {
            delete item.home
        });
        console.log(arr);
        
        // for in 遍历
        for (let key in arr) {
            delete arr[key].home
        }
        console.log(arr);

Object

遍历对象

let data = {a:1, b:2}

for (item in data) {
    console.log(item) // a, b
}

Object.keys(data) // ['a' , 'b']
Object.values(data) // [1, 2]
Object.getOwnPropertyNames(data) // ["a", "b"]
Reflect.ownKeys(data) // ["a", "b"]

Object.keys 返回对象的key或者数组的下标组成的数组

  • Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

可以用来获取对象的key的集合,进而可以获得对应key的value
const obj = {
  name: '元神',
  age: 22,
  gender: '男'
}

const keys = Object.keys(obj)
console.log(keys) // [ 'name', 'age', 'gender' ]

Object.values 获取对象的value组成的数组

  • Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组。
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj)); // ['a', 'b', 'c']

Object.entries 获取对象keyval键值对组成的数组

  • Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]

Object.fromEntries 把键值对组成的数组转换为一个对象。

  • Object.fromEntries()  把键值对列表转换为一个对象。
  • 序列化 Object.entries 为对象
//  map 转obj
const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }

// k v 数组转 obj
const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

他还有一个用处,就是把`Map转为对象`
const map = new Map()
map.set('name', '林三心')
map.set('age', 22)
map.set('gender', '男')

console.log(map) // Map(3) { 'name' => '林三心', 'age' => 22, 'gender' => '男' }

const obj = Object.fromEntries(map)
console.log(obj) // { name: '林三心', age: 22, gender: '男' }

对象解构赋值

// 对象解构赋值
let { name, age } = { name: "一个大胖子", age: 10 };
console.log(name, age); // 一个大胖子, 10

// 解构元素多于实际对象元素  多出的字段为 undefined
let { name, age, sex  } = { name: "一个大胖子", age: 10 };
console.log(name, age, sex); // 一个大胖子, 10, undefined

// 默认赋值 对象存在对应元素 默认赋值无效, 所以age 依然等于10
// 如果被解构对象无对应元素 则 age 等于 20
let { name, age = 20 } = { name: "一个大胖子", age: 10 };
console.log(name, age); // 一个大胖子, 10

// 解构别名
// 解构时可以将对象元素重命名,此时访问name 等于空字符串 fullName 为解构值
let { name:fullName, age } = { name: "一个大胖子", age: 10 };
console.log(name, age, fullName); // "", 10, "一个大胖子"

const obj = {
  name: '通天塔',
  age: 22,
  gender: '男',
  doing: {
    morning: '摸鱼',
    afternoon: '摸鱼',
    evening: 'sleep'
  }
}
// 嵌套解构
const { doing: { evening } } = obj
console.log(evening) // sleep

对象合并[...扩展运算符]

let a = {
  name'jack',
  age30
}
let b = {
  sex'男',
  like'游泳'
}
let c = [1,3,5]
let d = [2,4,6]

//以前
Object.assign({},a,b)
let e = c.concat(d)
//现在
let user = { ...a, ...b }
let arr = [...c,...d] 
// 数组去重
let arr = [...new Set([...c,...d])] 
// 或者
let arr = Array.from(new Set([...c,...d]))

对象变量属性

const flag = true;
const obj = {
    a: 0,
    [flag ? "c" : "d"]: 2
};
// obj => { a: 0, c: 2 }

对象多余属性删除

const { name, age, ...obj } = { name: '张三', age: 13, dec: '描述1', info: '信息' }
console.log(name)  // 张三
console.log(age)  // 13
console.log(obj)  // {dec: '描述1', info: '信息' }

对象嵌套属性解构

const { info:{ dec} } = { name: '张三', age: 13, info:{dec: '描述1', info: '信息' }}
console.log(dec) // 描述1

解构对象属性别名

const { name:newName } = { name: '张三', age: 13 }
console.log(newName)  // 张三

解构对象属性默认值

const { dec='这是默认dec值' } = { name: '张三', age: 13 }
console.log(dec) //这是默认dec

拦截对象

利用Object.defineProperty拦截对象 无法拦截数组的值

let obj = { name: '', age: '', sex: '' },
  defaultName = ["这是姓名默认值1", "这是年龄默认值1", "这是性别默认值1"];
Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, { // 拦截整个object 对象,并通过get获取值,set设置值,vue 2.x的核心就是这个来监听
    get() {
      return defaultName;
    },
    set(value) {
      defaultName = value;
    }
  });
});

console.log(obj.name); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
console.log(obj.age); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
console.log(obj.sex); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
obj.name = "这是改变值1";
console.log(obj.name); // 这是改变值1
console.log(obj.age);  // 这是改变值1
console.log(obj.sex); // 这是改变值1

let objOne = {}, defaultNameOne = "这是默认值2";
Object.defineProperty(obj, 'name', {
  get() {
    return defaultNameOne;
  },
  set(value) {
    defaultNameOne = value;
  }
});
console.log(objOne.name); // undefined
objOne.name = "这是改变值2";
console.log(objOne.name); // 这是改变值2

利用proxy拦截对象

let obj = { name: '', age: '', sex: '' }
let handler = {
  get(target, key, receiver) {
    console.log("get", key); 
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("set", key, value); // set name 李四  // set age 24
    return Reflect.set(target, key, value, receiver);
  }
};
let proxy = new Proxy(obj, handler);
proxy.name = "李四";
proxy.age = 24;

defineProterty和proxy的对比: 1.defineProterty是es5的标准,proxy是es6的标准; 2.proxy可以监听到数组索引赋值,改变数组长度的变化; 3.proxy是监听对象,不用深层遍历,defineProterty是监听属性; 4.利用defineProterty实现双向数据绑定(vue2.x采用的核心)

对象深度拷贝

JSON.stringify深度克隆对象; 1.无法对函数 、RegExp等特殊对象的克隆; 2.会抛弃对象的constructor,所有的构造函数会指向Object; 3.对象有循环引用,会报错

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    iteratee(array[index], index);
  }
  return array;
}

function isObject(target) {
  const type = typeof target;
  return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
  return Object.prototype.toString.call(target);
}

function getInit(target) {
  const Ctor = target.constructor;
  return new Ctor();
}

function cloneSymbol(targe) {
  return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
  const reFlags = /\w*$/;
  const result = new targe.constructor(targe.source, reFlags.exec(targe));
  result.lastIndex = targe.lastIndex;
  return result;
}

function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=().+(?=)\s+{)/;
  const funcString = func.toString();
  if (func.prototype) {
    const param = paramReg.exec(funcString);
    const body = bodyReg.exec(funcString);
    if (body) {
      if (param) {
        const paramArr = param[0].split(',');
        return new Function(...paramArr, body[0]);
      } else {
        return new Function(body[0]);
      }
    } else {
      return null;
    }
  } else {
    return eval(funcString);
  }
}

function cloneOtherType(targe, type) {
  const Ctor = targe.constructor;
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(targe);
    case regexpTag:
      return cloneReg(targe);
    case symbolTag:
      return cloneSymbol(targe);
    case funcTag:
      return cloneFunction(targe);
    default:
      return null;
  }
}

function clone(target, map = new WeakMap()) {

  // 克隆原始类型
  if (!isObject(target)) {
    return target;
  }

  // 初始化
  const type = getType(target);
  let cloneTarget;
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type);
  } else {
    return cloneOtherType(target, type);
  }

  // 防止循环引用
  if (map.get(target)) {
    return map.get(target);
  }
  map.set(target, cloneTarget);

  // 克隆set
  if (type === setTag) {
    target.forEach(value => {
      cloneTarget.add(clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆对象和数组
  const keys = type === arrayTag ? undefined : Object.keys(target);
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value;
    }
    cloneTarget[key] = clone(target[key], map);
  });

  return cloneTarget;
}

console.log(clone({
  name: '张三', age: 23,
  obj: { name: '李四', age: 46 },
  arr: [1, 2, 3]
})) // { name: '张三', age: 23, obj: { name: '李四', age: 46 }, arr: [ 1, 2, 3 ] }

对象深度克隆实际上就是要兼容Array,RegExp,Date,Function类型; 克隆函数可以用正则取出函数体和参数,再定义一个函数将取出来的值赋值进去 详细请戳对象深度拷贝

对象是否相等

如果用JSON.stringify转化属性顺序不同,也不相等; 而且不支持无法对函数 、RegExp等特殊对象的克隆

function deepCompare(x, y) {
  var i, l, leftChain, rightChain;

  function compare2Objects(x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
      return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
      return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
      (x instanceof Date && y instanceof Date) ||
      (x instanceof RegExp && y instanceof RegExp) ||
      (x instanceof String && y instanceof String) ||
      (x instanceof Number && y instanceof Number)) {
      return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
      return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
      return false;
    }

    if (x.constructor !== y.constructor) {
      return false;
    }

    if (x.prototype !== y.prototype) {
      return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
      return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }
    }

    for (p in x) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }

      switch (typeof (x[p])) {
        case 'object':
        case 'function':

          leftChain.push(x);
          rightChain.push(y);

          if (!compare2Objects(x[p], y[p])) {
            return false;
          }

          leftChain.pop();
          rightChain.pop();
          break;

        default:
          if (x[p] !== y[p]) {
            return false;
          }
          break;
      }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; 
  }

  for (i = 1, l = arguments.length; i < l; i++) {

    leftChain = []; //Todo: this can be cached
    rightChain = [];

    if (!compare2Objects(arguments[0], arguments[i])) {
      return false;
    }
  }

  return true;
}

const obj1 = { 
  name: '张三', age: 23, 
  obj: { name: '李四', age: 46 }, 
  arr: [1, 2, 3],
  date:new Date(23),
  reg: new RegExp('abc'),
  fun: ()=>{}
 }
const obj2 = { 
  name: '张三', age: 23, 
  obj: { name: '李四', age: 46 }, 
  arr: [1, 2, 3],
  date: new Date(23),
  reg: new RegExp('abc'),
  fun: ()=>{}
 }

console.log(deepCompare(obj1,obj2)) // true

判断对象是否相等,实际上就是要处理Array,Date,RegExp,Object,Function的特殊类型是否相等

对象转化为字符串

通过字符串+Object 的方式来转化对象为字符串(实际上是调用 .toString() 方法)

'the Math object:' + Math.ceil(3.4)                // "the Math object:4"
'the JSON object:' + {name:'曹操'}              // "the JSON object:[object Object]"

覆盖对象的toString和valueOf方法来自定义对象的类型转换

2  * { valueOf: ()=>'4' }                // 8
'J' + { toString: ()=>'ava' }                // "Java"

当+用在连接字符串时,当一个对象既有toString方法又有valueOf方法时候,JS通过盲目使用valueOf方法来解决这种含糊; 对象通过valueOf方法强制转换为数字,通过toString方法强制转换为字符串

'' + {toString:()=>'S',valueOf:()=>'J'}  //J

对象遍历[for..in]合并对象里的数组

let score = {
  'class-1':[10,30,50,70],
  'class-2':[20,40,50,70],
  'class-3':[30,60,80,90],
}
let arr = []
for(let key in score){
  arr = [...arr,...score[key]]
}
//arr[10,30,50,70,20,40,50,70,30,60,80,90]

对象内数组取交集(相同键)

``` js
arrIntersectionByKey(arr1, arr2, key) {
  const arr2Keys = arr2.map(item => item[key]);
  return arr1.filter(item => arr2Keys.includes(item[key]));
}

// 例如找出用户已领券中包含的本次即将发放的立减券,弹窗告知用户已领取其中的某些券
const receivedCoupons = [{ name: '立减券' },{ name: '折扣券' }]; // 已领的券
const welfareCoupons = [{ stockId: '立减券' }]; // 本次要发放的福利券
// 用户已领取的福利券,不再重复发放
arrIntersectionByKey(receivedCoupons,welfareCoupons, 'name');
// [{ name: '立减券' }]
```

js判断对象是否为空对象的几种方法

将json对象转化为json字符串,再判断该字符串是否为"{}"**
var data = {};
var b = (JSON.stringify(data) == "{}");
alert(b);//true

使用ES6的Object.keys()方法
与下面的方法类似,是ES6的新方法, 返回值也是对象中属性名组成的数组
alert(Object.keys(obj).length == 0);//true

判断对象有没有某一属性判断

handleEdit(todo)
{
  if (Object.prototype.hasOwnProperty.call(todo, 'todo里的属性'))
  {
    todo.isEdit = true
  }else{
    this.$set(todo,'isEdit',true)
  }
},

也可以用hasOwnProperty : 判断对象属性是否存在
​
if(object.hasOwnProperty('name')){
  console.log('exit property')
}

添加对象属性

当给对象添加属性时,如果属性名是动态变化的,该怎么处理。

let obj = {};
let index = 1;
let key = `topic${index}`;
obj[key] = '话题内容';
复制代码

改进

let obj = {};
let index = 1;
obj[`topic${index}`] = '话题内容';

获取对象属性值

const name = obj && obj.name;

对象动态属性

声明对象时,如果属性是动态的,可以这样声明:

const dynamic = 'color';
var item = {
    brand: 'Ford',
    [dynamic]: 'Blue'
}
console.log(item); 
// { brand: "Ford", color: "Blue" }

判断对象属性是否为空字符串

 console.log(Object.keys(res.result.userInfo.extension).length == 0)

Function

函数隐式返回值

(()=>3)()  //3
(()=>(
   3
))()

函数省略大括号,或者将大括号改成小括号可以确保代码以单个语句的形式进行求值

函数自执行

const Func = function() {}(); // 常用

(function() {})(); // 常用
(function() {}()); // 常用
[function() {}()];

new function() {};
new function() {}();
void function() {}();
typeof function() {}();
delete function() {}();

+ function() {}();
- function() {}();
~ function() {}();
! function() {}();

函数异步执行

Promise

Promise.reject('这是第二个 reject 值').then((data)=>{
  console.log(data)
}).catch(data=>{
  console.log(data) //这是第二个 reject 值
})
复制代码

Generator

function* gen(x) {
  const y = yield x + 6;
  return y;
}

// yield 如果用在另外一个表达式中,要放在()里面
// 像上面如果是在=右边就不用加()
function* genOne(x) {
  const y = `这是第一个 yield 执行:${yield x + 1}`;
  return y;
}

const g = gen(1);
//执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值
g.next() // { value: 7, done: false }
//调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行
// 执行完成会返回一个 Object,
// value 就是执行 yield 后面的值,done 表示函数是否执行完毕
g.next() // { value: undefined, done: true }
// 因为最后一行 return y 被执行完成,所以done 为 true

Async/Await

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2); //something 和 hello async
}
test();

空参数判断

let value=undefined;
    //为空参数判断提示
    if (['', undefined, null].indexOf(value) >= 0) {
        alert("不能为空,请检查")
    }

多个参数判断

const isSimon =
  person.getAge() > 30 &&
  person.getName() === "simon" &&
  person.getOrigin() === "sweden";
if (isSimon) {
  // ...
}

String

获取字符串长度

JavaScript中的字符串有一个length属性,该属性可以用来获取字符串的长度:

const str = 'hello';
str.length   // 输出结果:5

获取字符串指定位置的值

charAt()和charCodeAt()方法都可以通过索引来获取指定位置的值:

· charAt() 方法获取到的是指定位置的字符;

· charCodeAt()方法获取的是指定位置字符的Unicode值。

(1)charAt()

charAt() 方法可以返回指定位置的字符。其语法如下:

string.charAt(index)

index表示字符在字符串中的索引值:

const str = 'hello';
str.charAt(1)  // 输出结果:e 

我们知道,字符串也可以通过索引值来直接获取对应字符,那它和charAt()有什么区别呢?来看例子:

const str = 'hello';
str.charAt(1)  // 输出结果:e 
str[1]         // 输出结果:e 
str.charAt(5)  // 输出结果:'' 
str[5]         // 输出结果:undefined

可以看到,当index的取值不在str的长度范围内时,str[index]会返回undefined,而charAt(index)会返回空字符串;除此之外,str[index]不兼容ie6-ie8,charAt(index)可以兼容。

(2)charCodeAt()

charCodeAt():该方法会返回指定索引位置字符的 Unicode 值,返回值是 0 - 65535 之间的整数,表示给定索引处的 UTF-16 代码单元,如果指定位置没有字符,将返回 NaN:

let str = "abcdefg";
console.log(str.charCodeAt(1)); // "b" --> 98

通过这个方法,可以获取字符串中指定Unicode编码值范围的字符。比如,数字0~9的Unicode编码范围是: 48~57,可以通过这个方法来筛选字符串中的数字,当然如果你更熟悉正则表达式,会更方便。

检索字符串是否包含特定序列

这5个方法都可以用来检索一个字符串中是否包含特定的序列。其中前两个方法得到的指定元素的索引值,并且只会返回第一次匹配到的值的位置。后三个方法返回的是布尔值,表示是否匹配到指定的值。

注意:这5个方法都对大小写敏感!

(1)indexOf()

indexOf():查找某个字符,有则返回第一次匹配到的位置,否则返回-1,其语法如下:

string.indexOf(searchvalue,fromindex)

该方法有两个参数:

· searchvalue:必需,规定需检索的字符串值;

· fromindex:可选的整数参数,规定在字符串中开始检索的位置。它的合法取值是 0 到 string.length - 1。如省略该,则从字符串的首字符开始检索。

let str = "abcdefgabc";
console.log(str.indexOf("a"));   // 输出结果:0
console.log(str.indexOf("z"));   // 输出结果:-1
console.log(str.indexOf("c", 4)) // 输出结果:9

(2)lastIndexOf()

lastIndexOf():查找某个字符,有则返回最后一次匹配到的位置,否则返回-1

let str = "abcabc";
console.log(str.lastIndexOf("a"));  // 输出结果:3
console.log(str.lastIndexOf("z"));  // 输出结果:-1

该方法和indexOf()类似,只是查找的顺序不一样,indexOf()是正序查找,lastIndexOf()是逆序查找。

(3)includes()

includes():该方法用于判断字符串是否包含指定的子字符串。如果找到匹配的字符串则返回 true,否则返回 false。该方法的语法如下:

string.includes(searchvalue, start)
​

该方法有两个参数:

· searchvalue:必需,要查找的字符串;

· start:可选,设置从那个位置开始查找,默认为 0。

let str = 'Hello world!';
str.includes('o')  // 输出结果:true
str.includes('z')  // 输出结果:false
str.includes('e', 2)  // 输出结果:false

(4)startsWith()

startsWith():该方法用于检测字符串是否以指定的子字符串开始。如果是以指定的子字符串开头返回 true,否则 false。其语法和上面的includes()方法一样。

let str = 'Hello world!';
str.startsWith('Hello') // 输出结果:true
str.startsWith('Helle') // 输出结果:false
str.startsWith('wo', 6) // 输出结果:true

(5)endsWith()

endsWith():该方法用来判断当前字符串是否是以指定的子字符串结尾。如果传入的子字符串在搜索字符串的末尾则返回 true,否则将返回 false。其语法如下:

string.endsWith(searchvalue, length)

该方法有两个参数:

· searchvalue:必需,要搜索的子字符串;

· length: 设置字符串的长度,默认值为原始字符串长度 string.length。

let str = 'Hello world!';
str.endsWith('!')       // 输出结果:true
str.endsWith('llo')     // 输出结果:false
str.endsWith('llo', 5)  // 输出结果:true

可以看到,当第二个参数设置为5时,就会从字符串的前5个字符中进行检索,所以会返回true。

连接多个字符串

concat()

concat() 方法用于连接两个或多个字符串。该方法不会改变原有字符串,会返回连接两个或多个字符串的新字符串。其语法如下:

string.concat(string1, string2, ..., stringX)

其中参数 string1, string2, ..., stringX 是必须的,他们将被连接为一个字符串的一个或多个字符串对象。

let str = "abc";
console.log(str.concat("efg"));          //输出结果:"abcefg"
console.log(str.concat("efg","hijk")); //输出结果:"abcefghijk"

虽然concat()方法是专门用来拼接字符串的,但是在开发中使用最多的还是加操作符+,因为其更加简单。

字符串分割成数组

split()

split() 方法用于把一个字符串分割成字符串数组。该方法不会改变原始字符串。其语法如下:

string.split(separator,limit)

该方法有两个参数:

· separator:必需。字符串或正则表达式,从该参数指定的地方分割 string。

· limit:可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

let str = "abcdef";
str.split("c");    // 输出结果:["ab", "def"]
str.split("", 4)   // 输出结果:['a', 'b', 'c', 'd'] 

如果把空字符串用作 separator,那么字符串中的每个字符之间都会被分割。

str.split("");     // 输出结果:["a", "b", "c", "d", "e", "f"]

可以通过str.split("")[索引]取值

其实在将字符串分割成数组时,可以同时拆分多个分割符,使用正则表达式即可实现:

const list = "apples,bananas;cherries"
const fruits = list.split(/[,;]/)
console.log(fruits);  // 输出结果:["apples", "bananas", "cherries"]

截取字符串

substr()、substring()和 slice() 方法都可以用来截取字符串。

(1) slice()

slice() 方法用于提取字符串的某个部分,并以新的字符串返回被提取的部分。其语法如下:

string.slice(start,end)

该方法有两个参数:

· start:必须。 要截取的片断的起始下标,第一个字符位置为 0。如果为负数,则从尾部开始截取。

· end:可选。 要截取的片段结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。

上面说了,如果start是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推:

let str = "abcdefg";
str.slice(1,6);   // 输出结果:"bcdef" 
str.slice(1);     // 输出结果:"bcdefg" 
str.slice();      // 输出结果:"abcdefg" 
str.slice(-2);    // 输出结果:"fg"
str.slice(6, 1);  // 输出结果:""

注意,该方法返回的子串包括开始处的字符,但不包括结束处的字符。

(2) substr()

substr() 方法用于在字符串中抽取从开始下标开始的指定数目的字符。其语法如下:

string.substr(start,length)

该方法有两个参数:

· start 必需。要抽取的子串的起始下标。必须是数值。如果是负数,那么该参数声明从字符串的尾部开始算起的位置。也就是说,-1 指字符串中最后一个字符,-2 指倒数第二个字符,以此类推。

· length:可选。子串中的字符数。必须是数值。如果省略了该参数,那么返回从 stringObject 的开始位置到结尾的字串。

let str = "abcdefg";
str.substr(1,6); // 输出结果:"bcdefg" 
str.substr(1);   // 输出结果:"bcdefg" 相当于截取[1,str.length-1]
str.substr();    // 输出结果:"abcdefg" 相当于截取[0,str.length-1]
str.substr(-1);  // 输出结果:"g"

(3) substring()

substring() 方法用于提取字符串中介于两个指定下标之间的字符。其语法如下:

string.substring(from, to)

该方法有两个参数:

· from:必需。一个非负的整数,规定要提取的子串的第一个字符在 string 中的位置。

· to:可选。一个非负的整数,比要提取的子串的最后一个字符在 string 中的位置多 1。如果省略该参数,那么返回的子串会一直到字符串的结尾。

注意: 如果参数 from 和 to 相等,那么该方法返回的就是一个空串(即长度为 0 的字符串)。如果 from 比 to 大,那么该方法在提取子串之前会先交换这两个参数。并且该方法不接受负的参数,如果参数是个负数,就会返回这个字符串。

let str = "abcdefg";
str.substring(1,6); // 输出结果:"bcdef" [1,6)
str.substring(1);   // 输出结果:"bcdefg" [1,str.length-1]
str.substring();    // 输出结果:"abcdefg" [0,str.length-1]
str.substring(6,1); // 输出结果 "bcdef" [1,6)
str.substring(-1);  // 输出结果:"abcdefg"

注意,该方法返回的子串包括开始处的字符,但不包括结束处的字符。

字符串大小写转换

toLowerCase() 和 toUpperCase()方法可以用于字符串的大小写转换。

(1)toLowerCase()

toLowerCase():该方法用于把字符串转换为小写。

let str = "adABDndj";
str.toLowerCase(); // 输出结果:"adabdndj"

(2)toUpperCase()

toUpperCase():该方法用于把字符串转换为大写。

let str = "adABDndj";
str.toUpperCase(); // 输出结果:"ADABDNDJ"

我们可以用这个方法来将字符串中第一个字母变成大写:

let word = 'apple'
word = word[0].toUpperCase() + word.substr(1)
console.log(word) // 输出结果:"Apple"

字符串模式匹配

replace()、match()和search()方法可以用来匹配或者替换字符。

(1)replace()

replace():该方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。其语法如下:

string.replace(searchvalue, newvalue)

该方法有两个参数:

· searchvalue:必需。规定子字符串或要替换的模式的 RegExp 对象。如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象。

· newvalue:必需。一个字符串值。规定了替换文本或生成替换文本的函数。

let str = "abcdef";
str.replace("c", "z") // 输出结果:abzdef

执行一个全局替换, 忽略大小写:

let str="Mr Blue has a blue house and a blue car";
str.replace(/blue/gi, "red");    // 输出结果:'Mr red has a red house and a red car'

注意: 如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。

(2)match()

match():该方法用于在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。其语法如下:

string.match(regexp)
​

该方法的参数 regexp 是必需的,规定要匹配的模式的 RegExp 对象。如果该参数不是 RegExp 对象,则需要首先把它传递给 RegExp 构造函数,将其转换为 RegExp 对象。

注意: 该方法返回存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。

let str = "abcdef";
console.log(str.match("c")) // ["c", index: 2, input: "abcdef", groups: undefined]
​

(3)search()

search()方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。其语法如下:

string.search(searchvalue)
​

该方法的参数 regex 可以是需要在 string 中检索的子串,也可以是需要检索的 RegExp 对象。

注意: 要执行忽略大小写的检索,请追加标志 i。该方法不执行全局匹配,它将忽略标志 g,也就是只会返回第一次匹配成功的结果。如果没有找到任何匹配的子串,则返回 -1。

返回值: 返回 str 中第一个与 regexp 相匹配的子串的起始位置。

let str = "abcdef";
str.search(/bcd/)   // 输出结果:1

移除字符串收尾空白符

trim()、trimStart()和trimEnd()这三个方法可以用于移除字符串首尾的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。

(1)trim()

trim() 方法用于移除字符串首尾空白符,该方法不会改变原始字符串:

let str = "  abcdef  "
str.trim()    // 输出结果:"abcdef"

注意,该方法不适用于null、undefined、Number类型。

(2)trimStart()

trimStart() 方法的的行为与trim()一致,不过会返回一个从原始字符串的开头删除了空白的新字符串,不会修改原始字符串:

const s = '  abc  ';
​
s.trimStart()   // "abc  "

(3)trimEnd()

trimEnd() 方法的的行为与trim()一致,不过会返回一个从原始字符串的结尾删除了空白的新字符串,不会修改原始字符串:

const s = '  abc  ';
​
s.trimEnd()   // "  abc"

获取字符串本身

valueOf()和toString()方法都会返回字符串本身的值,感觉用处不大。

(1)valueOf()

valueOf():返回某个字符串对象的原始值,该方法通常由 JavaScript 自动进行调用,而不是显式地处于代码中。

let str = "abcdef"
console.log(str.valueOf()) // "abcdef"

(2)toString()

toString():返回字符串对象本身

let str = "abcdef"
console.log(str.toString()) // "abcdef"

重复一个字符串

repeat() 方法返回一个新字符串,表示将原字符串重复n次:

'x'.repeat(3)     // 输出结果:"xxx"
'hello'.repeat(2) // 输出结果:"hellohello"
'na'.repeat(0)    // 输出结果:""

如果参数是小数,会向下取整:

'na'.repeat(2.9) // 输出结果:"nana"

如果参数是负数或者Infinity,会报错:

'na'.repeat(Infinity)   // RangeError
'na'.repeat(-1)         // RangeError

如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。

'na'.repeat(-0.9)   // 输出结果:""

如果参数是NaN,就等同于 0:

'na'.repeat(NaN)    // 输出结果:""

如果repeat的参数是字符串,则会先转换成数字。

'na'.repeat('na')   // 输出结果:""
'na'.repeat('3')    // 输出结果:"nanana"

补齐字符串长度

padStart()和padEnd()方法用于补齐字符串的长度。如果某个字符串不够指定长度,会在头部或尾部补全。

(1)padStart()

padStart()用于头部补全。该方法有两个参数,其中第一个参数是一个数字,表示字符串补齐之后的长度;第二个参数是用来补全的字符串。

如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串:

'x'.padStart(1, 'ab') // 'x'

如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串:

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

如果省略第二个参数,默认使用空格补全长度:

'x'.padStart(4) // '   x'

padStart()的常见用途是为数值补全指定位数,笔者最近做的一个需求就是将返回的页数补齐为三位,比如第1页就显示为001,就可以使用该方法来操作:

"1".padStart(3, '0')   // 输出结果: '001'
"15".padStart(3, '0')  // 输出结果: '015'

(2)padEnd()

padEnd()用于尾部补全。该方法也是接收两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串:

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

字符串解构赋值

//  字符串解构 与数组基本相同  规则参考数组解构
let [a,b,c] = '123'
console.log(a,b,c) // 1,2,3

字符串去重

let str = "352255"; 
let unique = [...new Set(str)].join(""); // 352

字符串翻转

function reverseStr(str = "") {
  return str.split("").reduceRight((t, v) => t + v);
}

const str = "reduce123";
console.log(reverseStr(str)); // "321recuder"

Number

数字千分位

方法一:

function thousandNum(num = 0) {
  const str = (+num).toString().split(".");
  const int = nums => nums.split("").reverse().reduceRight((t, v, i) => t + (i % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  const dec = nums => nums.split("").reduce((t, v, i) => t + ((i + 1) % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  return str.length > 1 ? `${int(str[0])}.${dec(str[1])}` : int(str[0]);
}

thousandNum(1234); // "1,234"
thousandNum(1234.00); // "1,234"
thousandNum(0.1234); // "0.123,4"
console.log(thousandNum(1234.5678)); // "1,234.567,8"

方法二

console.log('1234567890'.replace(/\B(?=(\d{3})+(?!\d))/g, ","))
console.log((1234567890).toLocaleString())
复制代码

判断小数是否相等

肯定有人会说这还不简单,直接用'==='比较; 实际上0.1+0.2 !==0.3,因为计算机不能精确表示0.1, 0.2这样的浮点数,所以相加就不是0.3了

Number.EPSILON=(function(){   //解决兼容性问题
    return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
})();
//上面是一个自调用函数,当JS文件刚加载到内存中,就会去判断并返回一个结果
function numbersequal(a,b){ 
    return Math.abs(a-b)<Number.EPSILON;
  }
//接下来再判断   
const a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //这里就为true了

双位运算符

双位运算符比Math.floor(),Math.ceil()速度快

~~7.5                // 7
Math.ceil(7.5)       // 8
Math.floor(7.5)      // 7


~~-7.5        		// -7
Math.floor(-7.5)     // -8
Math.ceil(-7.5)      // -7

所以负数时,双位运算符和Math.ceil结果一致,正数时和Math.floor结果一致

取整和奇偶性判断

取整

3.3 | 0         // 3
-3.9 | 0        // -3

parseInt(3.3)  // 3
parseInt(-3.3) // -3

// 四舍五入取整
Math.round(3.3) // 3
Math.round(-3.3) // -3

// 向上取整
Math.ceil(3.3) // 4
Math.ceil(-3.3) // -3

// 向下取整
Math.floor(3.3) // 3
Math.floor(-3.3) // -4

判断奇偶数

const num=5;
!!(num & 1) // true
!!(num % 2) // true

Boolean

判断数据类型

function dataTypeJudge(val, type) {
  const dataType = Object.prototype.toString.call(val).replace(/[object (\w+)]/, "$1").toLowerCase();
  return type ? dataType === type : dataType;
}
console.log(dataTypeJudge("young")); // "string"
console.log(dataTypeJudge(20190214)); // "number"
console.log(dataTypeJudge(true)); // "boolean"
console.log(dataTypeJudge([], "array")); // true
console.log(dataTypeJudge({}, "array")); // false

可判断类型:undefined、null、string、number、boolean、array、object、symbol、date、regexp、function、asyncfunction、arguments、set、map、weakset、weakmap

使用Boolean过滤数组假值

const compact = arr => arr.filter(Boolean)
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34])  //[ 1, 2, 3, 'a', 's', 34 ]

短路运算

||(或)

const flag = false || true //true
// 某个值为假时可以给默认值
const arr = false || []

&&(与)

const flag1 = false && true //false
const flag2 = true && true //true

switch 简写

可以用对象替代switch,提高代码可读性

switch(a) {
  case '张三':
    return 'age是12'
  case '李四':
    return 'age是120'
}
​
// 使用对象替换后
const obj ={
  '张三': 'age12',
  '李四': 'age120',
}
console.log(obj['张三'])

?. 和 ??

  • 先说说?.,中文名为可选链

比如我们需要一个变量,是数组且有长度,才做某些操作

const list = null
// do something
if (list && list.length) {
  // do something
}
​
// 使用可选链
const list = null
// do something
if (list?.length) {
  // do something
}

比如有一个对象,我要取一个可能不存在的值,甚至我们都不确定obj是否存在

const obj = {
  cat: {
    name: '哈哈'
  }
}
const dog = obj && obj.dog && obj.dog.name // undefined// 可选链
const obj = {
  cat: {
    name: '哈哈'
  }
}
const dog = obj?.dog?.name // undefined

比如有一个数组,我不确定它存不存在,存在的话就取索引为1的值

const arr = null
// do something
const item = arr && arr[1]
​
// 可选链
const arr = null
// do something
const item = arr?.[1]

比如有一个函数,我们不确定它存不存在,存在的话就执行它

const fn = null
// do something
const res = fn && fn()
​
// 可选链
const fn = null
// do something
const res = fn?.()
  • 再说说??,中文名为空位合并运算符

请看以下代码,咱们使用||运算符,只要左边是假值,就会返回右边的数据

const a = 0 || '林三心' // 林三心
const b = '' || '林三心' // 林三心
const c = false || '林三心' // 林三心
const d = undefined || '林三心' // 林三心
const e = null || '林三心' // 林三心

??||最大的区别是,在??这,只有undefined和null才算假值

const a = 0 ?? '林三心' // 0
const b = '' ?? '林三心' // ''
const c = false ?? '林三心' // false
const d = undefined ?? '林三心' // 林三心
const e = null ?? '林三心' // 林三心

for of 和 for in

  • for in :遍历方法,可遍历对象和数组
  • for of :遍历方法,只能遍历数组,不能遍历非iterable对象

先看for in:

const obj = { name: '林三心', age: 22, gender: '男' }
const arr = [1, 2, 3, 4, 5]for(let key in obj) {
  console.log(key)
}
name
age
gender
​
for(let index in arr) {
  console.log(index)
}
0 1 2 3 4

再看 for of:

for(let item of arr) {
  console.log(item)
}
1 2 3 4 5

Set 和 Map

  • Set

先说说Set的基本用法

// 可不传数组
const set1 = new Set()
set1.add(1)
set1.add(2)
console.log(set1) // Set(2) { 1, 2 }// 也可传数组
const set2 = new Set([1, 2, 3])
// 增加元素 使用 add
set2.add(4)
set2.add('林三心')
console.log(set2) // Set(5) { 1, 2, 3, 4, '林三心' }
// 是否含有某个元素 使用 has
console.log(set2.has(2)) // true
// 查看长度 使用 size
console.log(set2.size) // 5
// 删除元素 使用 delete
set2.delete(2)
console.log(set2) // Set(4) { 1, 3, 4, '林三心' }

再说说Set的不重复性

// 增加一个已有元素,则增加无效,会被自动去重
const set1 = new Set([1])
set1.add(1)
console.log(set1) // Set(1) { 1 }// 传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, '林三心', 3, 3, '林三心'])
console.log(set2) // Set(4) { 1, 2, '林三心', 3 }
Set`的不重复性中,要注意`引用数据类型和NaN
// 两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: '林三心'}, 2, {name: '林三心'}])
console.log(set1) // Set(4) { 1, { name: '林三心' }, 2, { name: '林三心' } }
​
​
// 如果是两个对象是同一指针,则能去重
const obj = {name: '林三心'}
const set2 = new Set([1, obj, 2, obj])
console.log(set2) // Set(3) { 1, { name: '林三心' }, 2 }
​
咱们都知道 NaN !== NaNNaN是自身不等于自身的,但是在Set中他还是会被去重
const set = new Set([1, NaN, 1, NaN])
console.log(set) // Set(2) { 1, NaN }

利用Set的不重复性,可以实现数组去重

const arr = [1, 2, 3, 4, 4, 5, 5, 66, 9, 1]
​
// Set可利用扩展运算符转为数组哦
const quchongArr = [...new Set(arr)]
console.log(quchongArr) // [1,  2, 3, 4, 5, 66, 9]
  • Map
Map`对比`object`最大的好处就是,key不受`类型限制
// 定义map
const map1 = new Map()
// 新增键值对 使用 set(key, value)
map1.set(true, 1)
map1.set(1, 2)
map1.set('哈哈', '嘻嘻嘻')
console.log(map1) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }
// 判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈')) // true
// 获取map中某个key对应的value 使用 get(key)
console.log(map1.get(true)) // 2
// 删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
console.log(map1) // Map(2) { true => 1, 1 => 2 }// 定义map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }