js分类、比较及数组

# 简介

# Java和JavaScript的区别

Java JavaScript
Java是一种OOP编程语言。 JavaScript是一种OOP脚本语言。
它创建在虚拟机或浏览器中运行的应用程序 代码在浏览器或node环境上运行
需要编译Java代码。 JS代码都是文本的形式。

# 基本类型

# 分类

7种基本类型:Number、String、Boolean、Null、Undefined、Symbol(ES6), BigInt。其他全部都是 Object(引用类型)。【SSBNNUB】

BigInt是ES10新加的一种JavaScript数据类型,用来表示表示大于 2^53 - 1 的整数,2^53 - 1是ES10之前,JavaScript所能表示最大的数字

const bigNum = BigInt(1728371927189372189739217)
console.log(typeof bigNum) // bigint
1
2

多少种数据类型,别傻傻答6种了,要答8种,把ES6的Symbol和ES10的BigInt也加上去;总共8种 (opens new window)

# 存储位置

原始数据类型直接存储在**栈(stack)**中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。

引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

# 特点

# 基本数据类型的特点:

直接存储在栈(stack)中的数据; Symbol不能new, 因为Symbol是一个原始类型的数据类型,不是对象使用 Symbol 替换string可以避免不同的模块属性的冲突。还可以将Symbol设置为私有与其他原始类型不同,Symbol没有字面量语法let mySymbol = Symbol();

# 引用数据类型的特点:

存储的是该对象在栈中引用,真实的数据存放在堆内存里;

基础示例

var a=10; var b=a; b=20;  console.log(a)//10; console.log(b)//20; 互相不影响
1

引用示例

var a={x:10, y:20}; var b=a; b.x=100; b.y=200; 
console.log(a)//{x:100,y:200}; console.log(b)//{x:100,y:200}

var a={age:20}; var b=a; b.age=21; console.log(a.age==b.age)//true
var a={age:20}; var b=a; a = 1; console.log(b) // {age:20}
//此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。
1
2
3
4
5
6

分析:

# 类型及判断比较

Symbol(ES6)、String、Boolean、Number、Null、Undefined; 简写:SSBNNU 栈(stack) Object;(数组Array,函数Function,对象Object); 栈(stack),堆(heap) ===,typeof, instanceof, constructor, call()

# 基本数据类型

Symbol(ES6)、String、Boolean、Number、Null、Undefined; 简写:SSBNNU 栈(stack)

# 内置对象

Object 是 JavaScript 中所有对象的父对象

数据封装类对象:Object、Array、Boolean、Number 和 String

其他对象:Function、Arguments、Math、Date、RegExp、Error

# 判断方式对比

  • ===能用于判断null和undefined,因为这两种类型的值都是唯一的

    undefined === null; // false,类型不相同
    undefined !== null;  // true, 类型不相同
    
    1
    2
  • typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,*但不能判断null、array、object等,*但是 typeof null === 'object';

  • instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性但它不能检测null 和 undefined; 基础类型没有 __proto__

  • **constructor作用和instanceof非常相似。**但constructor检测 Object与instanceof不一样,还可以处理基本数据类型的检测但它不能检测null 和 undefined

    const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象

  • Object.prototype.toString.call() 最准确最常用的方式;

    toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

    对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

及ES6新加入的方法Array.isArray():检测某个值是否为数组(ES6)

typeof优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof undefined);       // undefined
console.log(typeof function(){});    // function

console.log(typeof null);            // object
console.log(typeof []);              // object 
console.log(typeof {});              // object

typeof Symbol(); // symbol 有效
typeof [] ; //object 无效
typeof new Function(); // function 有效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

instanceof优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象 缺点:Number,Boolean,String基本数据类型不能判断

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
1
2
3
4
5
6

**Object.prototype.toString.call()**优点:精准判断数据类型 缺点:写法繁琐不容易记,推荐进行封装后使用

var toString = Object.prototype.toString;
console.log(toString.call(2));                      //[object Number]
console.log(toString.call(true));                   //[object Boolean]
console.log(toString.call('str'));                  //[object String]
console.log(toString.call([]));                     //[object Array]
console.log(toString.call(function(){}));           //[object Function]
console.log(toString.call({}));                     //[object Object]
console.log(toString.call(undefined));              //[object Undefined]
console.log(toString.call(null));                   //[object Null]

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 检测引用类型

  1. 通过 instanceof 判断引用类型;测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
  2. 通过 constructor 判断引用类型(constructor是可写的,慎用); 打印: Array
  3. 通过 Object.prototype.toString.call 检测 [[class]];打印:[object Function]

# 原理及使用

# instanceof 的原理

instanceof 内部机制是通过判断对象的原型链中是不是能找到对应的的prototype; 基础类型没有 __proto__

f 的隐式原型 __proto__Foo.prototype ,是相等的,所以返回 true

console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true
1
2
3
function instanceof(obj, target) {// 实现 instanceof
 // 验证如果为基本数据类型,就直接返回 false
  const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
  if(baseType.includes(typeof(obj))) { return false }

  obj = obj.__proto__// 获得对象的原型
  while (true) {// 判断对象的类型是否等于类型的原型
    if (obj === null) {// 如果__proto__ === null 说明原型链遍历完毕
      return false
    }
    // 如果存在 obj.__proto__ === target.prototype;说明对象是该类型的实例
    if (obj === target.prototype) {
      return true
    }
    obj = obj.__proto__// 原型链上查找
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

示例:现在有 x instanceof y 一条语句,则其内部实际做了如下判断:

while(x.__proto__!==null) {
    if(x.__proto__===y.prototype) {
        return true;
    }
    x.__proto__ = x.__proto__.proto__;
}
if(x.__proto__==null) {return false;}
//x会一直沿着隐式原型链__proto__向上查找直到x.__proto__.__proto__......===y.prototype为止,如果找到则返回true,也就是x为y的一个实例。否则返回false,x不是y的实例。
1
2
3
4
5
6
7
8

# 如果用 instanceof 判断基础类型会怎么样?

let str = '123';
console.log(str instanceof String) // -> false; 因为基础类型没有 `__proto__`
1
2

但是如果更改了实现检查的话; 静态方法Symbol.hasInstance就可以判断

class StringType {
  static [Symbol.hasInstance](val) {
    return typeof val === 'string'
  }
}
console.log(str instanceof StringType) // -> true
1
2
3
4
5
6
# 使用

对象的深拷贝操作比较,下面有示例;

function isObject (obj: any): Boolean {// 借鉴 Vue 源码的 object 检测方法
  //typeof null === 'object';null 是基础类型,不是 Object,故要先排除这种情况;
  return obj !== null && typeof obj === 'object'
}
1
2
3
4
//定义检测数据类型的功能函数 
function checkedType(target) { 
	return Object.prototype.toString.call(target).slice(8, -1) //获取从第九个到倒数第二个字符
}//targetType ==='Object'; 比如[object String]获取 String
1
2
3
4
//验证是否是对象或数组(前提是toString()方法没有被重写过)
function isArray(value){//安全类型检测
  return Object.prototype.toString.call(value) == "[object Array]";
}
function isFunction(value){// 注:在ie中在以COM对象形式实现的任何函数,isFunction()都将返回false
  return Object.prototype.toString.call(value) == "[object Function]";
}
1
2
3
4
5
6
7
function simpleClone(obj) {//浅拷贝
  let newObj = {};
  for (let i in obj) {
    newObj[i] = obj[i];
  }
  return newObj;
}
//方式一:直接用instancof/typeof判断;【推荐】简洁;
function deepClone(obj){
  //var newObj = Array.isArray(obj) ? [] : {}
  var newObj = obj instanceof Array?[]:{};
  for(var i in obj){
    newObj[i]=typeof obj[i]=='object'? deepClone(obj[i]):obj[i]; 
  }
  return newObj;
} 
function deepClone(obj) {//第二种方式;跟方式一类似;
  let result;
  if (typeof obj == 'object') {
    result = isArray(obj) ? [] : {}
    for (let i in obj) {
      result[i] = isObject(obj[i])||isArray(obj[i])?deepClone(obj[i]):obj[i]
    }
  } else {
    result = obj
  }
  return result
}
function isObject(obj) {
  return Object.prototype.toString.call(obj) == "[object Object]"
}
function isArray(obj) {
  return Object.prototype.toString.call(obj) == "[object Array]"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

在类型判断的问题上, 基础上推荐阅读 lodash (opens new window) 的源代码.

# 类型转换

# null 和 undefined 区别

null 表示一个对象是“没有值”的值,也就是值为“空”; undefined 表示一个变量声明了没有初始化(赋值);

undefined是基本数据类型 表示未定义 缺少的意思。

null是引用数据类型,是对象(历史原因),现在为基本类型,表示空对象 ,

ps:在验证null时,一定要使用 === ,因为 == 无法分别 null 和 undefined; undefined是从null派生出来的 所以undefined==nulltrue;

undefined不是一个有效的JSON,而null是; undefined的类型(typeof)是undefined; null的类型(typeof)是object;

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

  • (1)变量被声明了,但没有赋值时,就等于undefined。
  • (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
  • (3)对象没有赋值的属性,该属性的值为undefined。
  • (4)函数没有返回值时,默认返回undefined。

null表示**"没有对象",即该处不应该有值**。典型用法是:

  • (1) 作为函数的参数,表示该函数的参数不是对象
  • (2) 作为对象原型链的终点。

# null和undefined的异同点有哪些?

相同点

  • 都是空变量
  • 都是假值,转布尔值都是false
  • null == undefined 为 true

不同点

  • typeof判断null为object,判断undefined为undefined
  • null转数字为0,undefined转数字为NaN
  • null是一个对象未初始化,undefined是初始化了,但未定义赋值
  • null === undefined 为 false

# 为什么typeof null 是object?

不同的数据类型在底层都是通过二进制表示的,二进制前三位为000则会被判断为object类型,而null底层的二进制全都是0,那前三位肯定也是000,所以被判断为object

# undefined >= undefined 为什么是 false ?

按照隐式转换规则,可转换成NaN >= NaN,NaN 不等于 NaN,也不大于,所以是false

# null >= null 为什么是 true?

按照隐式转换规则,可转换成0 >= 0,0 等于 0,所以是true

# undeclared 和 undefined 区别

undeclared的变量是程序中不存在且未声明的变量。 如果程序尝试读取未声明变量的值,则会遇到运行时错误。undefined的变量是在程序中声明但未赋予任何值的变量,如果程序试图读取未定义变量的值,则返回undefined的值。

  • undefined:声明了变量,但是没有赋值
  • undeclared:没有声明变量就直接使用
var a; //undefined
b;    // b is not defined
1
2

# ===== 及Object.is()的区别

== : 只进行值的比较,会进行数据类型的转换。 === : 不仅进行值得比较,不进行转换。还要进行数据类型的比较。

===== 的区别在于: == 运算发生的隐式类型转换,而 === 在执行比较运算前不会发生隐式的类型转换

一言以蔽之==先转换类型再比较,===先判断类型,如果不是同一类型直接为false

{a: 1} == "[object Object]" //true, 左边会执行 .toString()
1

Object.is():用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致;不同之处只有两个:一是+0不等于-0,二是NaN等于自身。Object.is(v1, v2) 修复了 === 的一些BUG (-0和+0, NaN和NaN)

+0 === -0 //true           NaN === NaN//false    
Object.is(+0, -0) //false  Object.is(NaN, NaN) //true
1
2
# ==类型转换过程
  1. 如果类型不同,进行类型转换
  2. 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
  3. 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
  4. 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
  5. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断

# NaN

NaN 即非数值(Not a Number),NaN 属性用于引用特殊的非数字值,该属性指定的并不是不合法的数字

NaN 属性 与 Number.Nan 属性相同。NaN 也属于 number 类型,并且 NaN 不等于自身;

提示: 请使用 isNaN() 来判断一个值是否是数字。原因是 NaN 与所有值都不相等,包括它自己。

Object.is():用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致;不同之处只有两个:一是+0不等于-0,二是NaN等于自身。Object.is(v1, v2) 修复了 === 的一些BUG (-0和+0, NaN和NaN)

+0 === -0 //true           NaN === NaN//false    
Object.is(+0, -0) //false  Object.is(NaN, NaN) //true
1
2

NaN 是非常特殊的值,它不和任何类型的值相等,包括它自己,同时它与任何类型的值比较大小时都返回false;有Object.is方法后就不是了

示例:

console.log(typeof(NaN));//number
console.log(typeof(undefined));//undefined
console.log(typeof(null));//object
1
2
3
# 特点
  • NaN不等于自身,也就是 NaN === NaNfalse
  • NaN为假值,转布尔值为false
  • NaN本质是一个number,typeof NaN === number
# 各种区别

# isNaN 与 Number.isNaN的区别?

  • isNaN:除了判断NaN为true,还会把不能转成数字的判断为true,例如'xxx'
  • Number.isNaN:只有判断NaN时为true,其余情况都为false

# 隐式转换

# 隐式转换规则
  • 1、转成string类型: +(字符串连接符)
  • 2、转成number类型:++/--(自增自减运算符) + - * / %(算术运算符) > < >= <= == != === !=== (关系运算符)
  • 3、转成boolean类型:!(逻辑非运算符)

switch case得注意类型,比如type是int或者string类型;通过type = +type转换成int类型;

var b = parseInt("01");
console.log("b=" + b);//b=1
var c = parseInt("09/08/2009");
console.log("c=" + c);//c=9
1
2
3
4
//2+5+'3' //73  //由于2和5是整数,它们将以数字形式相加。因为3是一个字符串,它将与 7 拼接,结果是73。

var y = 1;
if (eval(function f() {})) {
  y += typeof F;
}
console.log(y);//1undefined
//eval(function f(){})返回函数f(){}(为真)。因此,在if语句中,执行typeof f返回undefined,
//因为if语句代码在运行时执行,而if条件中的语句在运行时计算。
1
2
3
4
5
6
7
8
9
# 双等号左右两边的转换规则
  • 1、null == undefined 为 true
  • 2、如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;
  • 3、如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值
  • 4、如果一个操作数是对象,另一个操作数不是,则调用对象的toString()方法,用得到的基本类型值按照前面的规则进行比较

# undefined >= undefined 为什么是 false ?

按照隐式转换规则,可转换成NaN >= NaN,NaN 不等于 NaN,也不大于,所以是false

# null >= null 为什么是 true?

按照隐式转换规则,可转换成0 >= 0,0 等于 0,所以是true

# [] == ![] 为什么是 true ?

按照双等号左右两边的转换规则

  • 1、! 优先级高于 ==[]不是假值,所以先转换成 [] == false
  • 2、右边为布尔值,false先转数字0,所以可转换为[] == 0
  • 3、左边为对象,[]调用toString转为 '',转换为'' == 0
  • 4、左边为字符串,''转换为0,最终为 0 == 0
# 类型转换逻辑

所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。

类型先通过 valueOftoString 进行隐式转换;

# valueOf 与 toString
  • 1、valueOf偏向于运算,toString偏向于显示
  • 2、对象转换时,优先调用toString
  • 3、强转字符串优先调用toString,强转数字优先调用valueOf
  • 4、正常情况下,优先调用toString
  • 5、运算操作符情况下优先调用valueOf

调用valueOf

调用者 返回值 返回值类型
Array 数组本身 Array
Boolean 布尔值 Boolean
Date 毫秒数 Number
Function 函数本身 Function
Number 数字值 Number
Object 对象本身 Object
String 字符串 String

调用toString

调用者 返回值 返回值类型
Array 数组转字符串,相当于Array.join() String
Boolean 转字符串'true'、'false' String
Date 字符串日期,如'Fri Dec 23 2016 11:24:47 GMT+0800 (中国标准时间)' String
Number 数字字符串 String
Object '[object Object]' String
String 字符串 String
# 示例

注意:特例,null->0

# 如何让 (a == 1 && a == 2)条件成立?

更改 valueOf 方法就可以实现

let a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
console.log(a == 1 && a == 2);
1
2
3
4
5
6
7
8

# (a == 1 && a == 2 && a == 3) 有可能是 true 吗?

对象类型转换

当两个类型不同时进行==比较时,会将一个类型转为另一个类型,然后再进行比较。 比如Object类型与Number类型进行比较时,Object类型会转换为Number类型。 Object转换为Number时,会尝试调用Object.valueOf()Object.toString()来获取对应的数字基本类型。

// 第一种方法
var a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
console.log(a == 1 && a == 2 && a == 3) // true

// 第二种方法
var a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); // true

// 第三种方法
var val = 0;
Object.defineProperty(window, 'a', {
    get: function () {
        return ++val;
    }
});
console.log(a == 1 && a == 2 && a == 3) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

defineProperty

使用一个defineProperty,让 a 的返回值为三个不同的值。

var val = 0;
Object.defineProperty(window, 'a', { // 这里要window,这样的话下面才能直接使用a变量去 ==
    get: function () {
        return ++val;
    }
});
console.log(a == 1 && a == 2 && a == 3) // true
1
2
3
4
5
6
7

# [] == ![] 结果是什么?

分析

右边

  1. 由于 ! 优先级比 == 高,先执行 !
  2. ![] 得到 false
  3. 进行 相等性判断 (opens new window)详见【上面的==类型的转换过程】
  4. false 转化为数字 0

左边

  1. 执行 [].valueOf() 原始值 还是 []
  2. 执行 [].toString() 得到 ''
  3. '' 转化为数字 0

所以:0 == 0 ,答案是 true

验证类型先通过 valueOftoString 进行隐式转换;

let arr1 = [];
let arr2 = [];
console.log(arr1 == !arr2) // -> true
arr1.toString = () => {
    console.log(111)
    return 1
}
console.log(arr1 == !arr2) 
// -> 111
// -> false
1
2
3
4
5
6
7
8
9
10

# JS的装箱和拆箱

**装箱:**把基本数据类型转化为对应的引用数据类型的操作

看以下代码,s1只是一个基本数据类型,他是怎么能调用indexOf的呢?

const s1 = 'samy_'
const index = s1.indexOf('_')
console.log(index) // 4
1
2
3

原来是JavaScript内部进行了装箱操作

  • 1、创建String类型的一个实例;
  • 2、在实例上调用指定的方法;
  • 3、销毁这个实例;
var temp = new String('samy')
const index = temp.indexOf('_')
temp = null
console.log(index) // 8
1
2
3
4

**拆箱:**将引用数据类型转化为对应的基本数据类型的操作

通过valueOf或者toString方法实现拆箱操作

var objNum = new Number(123);  
var objStr =new String("123");   
console.log( typeof objNum ); //object
console.log( typeof objStr ); //object 
console.log( typeof objNum.valueOf() ); //number
console.log( typeof objStr.valueOf() ); //string

console.log( typeof objNum.toString() ); // string 
console.log( typeof objStr.toString() ); // string
1
2
3
4
5
6
7
8
9

# 整型

# JavaScript最大安全数字与最小安全数字?

console.log(Number.MAX_SAFE_INTEGER)// 9007199254740991
console.log(Number.MIN_SAFE_INTEGER)// -9007199254740991
1
2

注意new跟不new的区别; constructor跟constructor()的区别;

var n1= Number(1)
console.log(n1);//1
console.log(n1.__proto__);//[Number: 0]
console.log(n1.constructor);//[Function: Number]
console.log(n1.constructor());//0

var n2= new Number(1)
console.log(n2);//[Number: 1]

console.log(typeof n1);//number
console.log(typeof n2);// object;包装对象;
//所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。
console.log(n2.valueOf());//1
1
2
3
4
5
6
7
8
9
10
11
12
13

# 进制转换

# 规则方式

  • 十进制转其他; toString(2/8/16/32/0x); 当调用参数不是在2-32时,抛出 TypeError 异常。 当调用该方法的对象不是 Number 时抛出 TypeError 异常

    var x=300;
    console.log(x);
    console.log("2进制---"+x.toString(2));//100101100
    console.log("8进制---"+x.toString(8));//454
    console.log("16进制---"+x.toString(16));//12c
    console.log("32进制---"+x.toString(32));//9c
    // console.log("32进制---"+x.toString(11111));//toString() radix argument must be between 2 and 36
    
    1
    2
    3
    4
    5
    6
    7
  • 其他转十进制 ;parseInt(x,2/8/16/32/0x)); 如果字符串参数小于 2 或者大于 36或者第一个字符不能被转换为数字,那么 parseFloat() 会返回 NaN。

    var x='300';
    console.log("2进制转10进制---"+parseInt(x,2));//NaN
    console.log("8进制转10进制---"+parseInt(x,8));//192
    console.log("16进制转10进制---"+parseInt(x,16));//768
    
    1
    2
    3
    4
  • 其他转其;他先用parseInt转成十进制再用toString转到目标进制

    parseInt(num).toString(8)  //十进制转八进制
    parseInt(num).toString(16)   //十进制转十六进制
    parseInt(num,2).toString(8)   //二进制转八进制
    parseInt(num,2).toString(16)  //二进制转十六进制
    parseInt(num,8).toString(2)   //八进制转二进制
    parseInt(num,8).toString(16)  //八进制转十六进制
    parseInt(num,16).toString(2)  //十六进制转二进制
    parseInt(num,16).toString(8)  //十六进制转八进制
    
    parseInt(num,8);   //八进制转十进制
    parseInt(num,16);   //十六进制转十进制
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# 十进制转二进制(考虑小数)

# toString(radix)

该转换可以用 toString() 方法来实现

numberObject.toString(radix)radix:2 ~ 36 之间的整数。若省略该参数,则使用基数 10。但是要注意,如果该参数是 10 以外的其他值,则 ECMAScript 标准允许实现返回任意值;

如果想将十进制数转换为二进制数(整数和小数都可以用这种方法),只需要使用 numberObject.toString(radix) 方法即可;

因为 toSting() 返回的是表示该对象的字符串,所以如果最终结果是要二进制数值的话,可以再用 Number() 函数转换一下即可

const decimalNum = 123 
console.log(decimalNum.toString(2))  // "1111011"
const decimalFloatNumber = 123.125
console.log(decimalFloatNumber.toString(2))  // "1111011.001"
1
2
3
4

# 二进制转十进制(考虑小数)

网上例子基本都是用 parseInt(string, radix),但如果二进制数有小数部分,那小数部分的数值会被直接舍去,不符合一些场景的需求设定;

# parseInt(string, radix)

parseInt(string, radix) 函数可解析一个字符串,并返回一个整数;radix:该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN

实现二进制(整数部分&小数部分)转换为十进制的一种方法是:将二进制数分为整数和小数两部分,分别转换为十进制数,然后再组合成最终的十进制数值

将二进制数的整数部分和小数部分分别取出

  • 先将二进制数转换为字符串
  • split 方法,取出二进制数的整数部分和小数部分
const binaryFloatNum = 1111011.111
const binaryFloatNumStr = binaryFloatNum.toString()
console.log(binaryFloatNumStr)  // "1111011.111"
const binaryFloatNumArr = binaryFloatNumStr.split(".")
console.log(binaryFloatNumArr)  // ["1111011", "111"]
1
2
3
4
5

将整数部分转换为十进制

  • 直接用 parseInt(string, radix) ; parseInt(binaryNum, 2)

将小数部分转换为十进制数

  • 取出小数部分
  • 将小数部分的每位取出
  • 将取出的每位分别转换为十进制数
  • 将转换之后的各个十进制数相加,得到由整个二进制小数部分转换成的十进制数
/**
* 将二进制小数部分转换为十进制数
* @param binaryFloatPartArr 二进制小数部分中由小数各位组成的数组
*/
function eachBinaryFloatPartToDecimal(binaryFloatPartArr) {
    return binaryFloatPartArr.map((currentValue, index) => {
        return Number(currentValue) * Math.pow(2, (-(index + 1)))
    })
}
//const eachDecimalFloatPartNum = eachBinaryFloatPartToDecimal(["1", "1", "1"])
//console.log(eachDecimalFloatPartNum)  // [0.5, 0.25, 0.125]

/**
* 将二进制小数(包含整数部分和小数部分)转换为十进制数
* @param binaryNum 二进制数(可能是整数,也可能是小数)
*/
function binaryFloatToDecimal(binaryNum) {
    // 如果该二进制只有整数部分则直接用 parseInt(string, radix) 处理
    if (Number.isInteger(binaryNum)) {
        return parseInt(binaryNum, 2)
    } else {
        const binaryFloatNumArr = binaryNum.toString().split(".")

        // 将二进制整数转换为十进制数
        const binaryIntParStr = binaryFloatNumArr[0]
        const decimalIntPartNum = parseInt(binaryIntParStr, 2)

        // 将二进制小数部分转换为十进制数
        const binaryFloatPartArr = binaryFloatNumArr[1].split("")
        const eachDecimalFloatPartNum = eachBinaryFloatPartToDecimal(binaryFloatPartArr)
        const deciamlFloatPartNum = eachDecimalFloatPartNum.reduce((accumulator, currentValue) => { return accumulator + currentValue })
        return decimalIntPartNum + deciamlFloatPartNum
    }
}

console.log(binaryFloatToDecimal(1111011.111))  // 123.875
console.log(binaryFloatToDecimal(1111011))  // 123
console.log(binaryFloatToDecimal(0.111))  // 0.875
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# ["1", "2", "3"].map(parseInt) 答案是多少

parseInt() 函数能解析一个字符串,并返回一个整数,需要两个参数 (val, radix),其中 radix 【该值介于 2 ~ 36 之间,并且字符串中的数字不能大于radix才能正确返回数字结果值】; 但此处 map 传了 3 个 (element, index, array),

因为二进制里面,没有数字3,导致出现超范围的radix赋值和不合法的进制解析,才会返回NaN 所以["1", "2", "3"].map(parseInt) 答案也就是:[1, NaN, NaN]

 function parseInt(str, radix) {
     return str + '-' + radix;
 };
 var a=["1", "2", "3"];
 a.map(parseInt);  // ["1-0", "2-1", "3-2"] 不能大于radix
1
2
3
4
5
parseInt("10");			//返回 10
parseInt("19",10);		//返回 19 (10+9)
parseInt("11",2);		//返回 3 (2+1)
parseInt("17",8);		//返回 15 (8+7)
parseInt("1f",16);		//返回 31 (16+15)
parseInt("010");		//未定:返回 10 或 8
1
2
3
4
5
6

# 精确度问题

# 为什么 0.1 + 0.2 为什么不等于 0.3

原因:JavaScript中小数是浮点数,需转二进制进行运算,有些小数无法用二进制表示,所以只能取近似值,所以造成误差;

js遵循IEEE 754 双精度版本(64位)标准的语言都有的问题。计算机无法识别十进制,JS会将十进制转换为对应的二进制(二进制即:01)。所以 0.1 在二进制表示为

// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
1
2
console.log(0.1.toString(2));
// -> 0.0001100110011001100110011001100110011001100110011001101
console.log(0.2.toString(2));
// -> 0.001100110011001100110011001100110011001100110011001101
1
2
3
4

原生解决办法:parseFloat((0.1 + 0.2).toFixed(10)),不完全正确;

console.log(0.1 + 0.2); //0.30000000000000004
console.log(parseFloat((0.1 + 0.2).toFixed(10))); //0.3
1
2

另一种方案:把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完成后再进行降级(除以10的n次幂),即:

(0.1*10 + 0.2*10)/10 == 0.3 //true
1

# 解决 JS 的精确度问题

  1. 目前主流的解决方案是 : 先变成整数运算,然后再变回小数

    先乘再除; (0.1*10 + 0.2*10)/10 == 0.3 //true

    • 比如精确到小数点后2位;.toFixed(8)
    • 先把需要计算的数字都 乘1000
    • 计算完成后再把结果 除1000
  2. 原生解决办法:parseFloat((0.1 + 0.2).toFixed(10))普通计算

  3. 使用新基础类型 BigInt (兼容性很差)

  4. 用第三方库;大型计算;

ps: toFixed()方法可把Number四舍五入为指定小数位数的数字

# Math的常用

方法 作用
Math.max(...arr) 取arr中的最大值
Math.min(...arr) 取arr中的最小值
Math.ceil(小数) 小数向上取整
Math.floor(小数) 小数向下取整
Math.round(小数) 小数四舍五入
Math.sqrt(num) 对num进行开方
Math.pow(num, m) 对num取m次幂
Math.random() * num 取0-num的随机数

# 字符串

# 方法

charCodeAt

/*假设:一个英文字符占用一个字节,一个中文字符占用两个字节*/
function getBytes(str) {
  var len = str.length, bytes = len, i = 0;
  for (; i < len; i++) {
    if (str.charCodeAt(i) > 255) {
      bytes++;
    }
  }
  return bytes;
}
console.log(getBytes("语,as")); //5
1
2
3
4
5
6
7
8
9
10
11

# 转义字符

JS转义字符能够在不破坏应用程序的情况下编写特殊字符

转义字符(\)用于处理特殊字符,如单引号、双引号、撇号和&号,在字符前放置反斜杠使其显示。

示例:document.write("I am a \"good\" boy")

# slice(),substring(),substr()的区别

slice()方法返回一个索引和另一个索引之间的字符串,语法如下:跟Array中的slice的用法类似;

str.slice(beginIndex[, endIndex])

substring()方法返回一个索引和另一个索引之间的字符串,语法如下:跟splice及Array中的slice的用类似

str.substring(indexStart, [indexEnd])

substr()方法返回从指定位置开始的字符串中指定字符数的字符,语法如下:不设置最后一个就是截取到尾部

str.substr(start, [length])

**substring()方法的参数表示起始和结束索引,substr()方法的参数表示起始索引和要包含在生成的字符串中的字符的长度,**示例如下:

# slice, substring 和 substr 的区别?

方法 参数 描述
slice slice(start, end) 从start开始,截取到end - 1,如果没有end,则截取到左后一个元素;
substring substring(start,end) 返回从start位置开始到end位置的子串**(不包含end)**
substr substr(start,length) 返回从start位置开始length长度的子串

slice的注意点

  • start > end:返回空字符串
  • start < 0:start = 数组长度 + start

substring功能与slice大致相同

区别之处

  • start > end:互换值

substr注意点

  • start < 0:start = 数组长度 + start
  • length超出所能截取范围,需要做处理
  • length < 0:返回空字符串

# 数组的slice 与 splice 的区别?

方法 参数 描述
slice slice(start, end) 从start开始,截取到end - 1,如果没有end,则截取到左后一个元素,不影响原数组
splice splice(start, num, item1,item2, ...) 从start索引开始,截取num个元素,并插入item1、item2到原数组里,影响原数组

# 数组中splice,删增改操作

var arr1 = ['samy', 2, 3, 'zh'];
var arr2 = ['samy', 2, 3, 'zh'];
var arr3 = ['samy', 2, 3, 'zh'];
var index = 0
arr1.splice(index, 1)
console.log("===删除===", arr1);//[ 2, 3, 'zh' ]
arr2.splice(index, 0, 'add')
console.log("===增加===", arr2);//[ 'add', 'samy', 2, 3, 'zh' ]
arr3.splice(index, 1, 'mod')
console.log("===修改===", arr3);//[ 'mod', 2, 3, 'zh' ]
1
2
3
4
5
6
7
8
9
10

# 数组中includes 比 indexOf好在哪?

includes可以检测NaN,indexOf不能检测NaN,includes内部使用了Number.isNaNNaN进行了匹配;

var str = 'Mozilla';
console.log(str.slice(2, 5));  //zil 跟substring的一致;
console.log(str.slice(1));  //ozilla
console.log(str.slice(2));  //zilla
console.log(str.slice());  //Mozilla

var text = 'Mozilla';
console.log(text.substring(2,5)); // => "zil"
console.log(text.substr(2,3)); // => "zil"

console.log(text.substring(2)); // zilla 不设置最后一个就是截取到尾部;跟substr一样;
console.log(text.substr(2)); // zilla  不设置最后一个就是截取到尾部;
//截取最后4个字母
var jiequ=str.substring(myvalue.length-4,myvalue.length);//fghi
1
2
3
4
5
6
7
8
9
10
11
12
13
14

截取文件信息:

const fileName = path.basename(file.filepath)//获取文件名
const fileExt = path.extname(file.filepath).replace(/^./, '')//获取文件后缀

const size = await ctx.helper.fileSize(file.filepath)
const buf = fse.readFileSync(file.filepath)
const md5Str = md5(buf)

//其他
//获取文件名:最简单实现;
let filename = "example.jpg";
let fileExt = filename.split('.').pop();  //jpg
fileExt = filename.substring(filename.lastIndexOf(".")+1)//jpg
ext = filename.substring(filename.lastIndexOf('.'),filename.length).toLowerCase()//.jpg

function getFileExtendingName (filename) {
  var reg = /\.[^\.]+$/;
  var matches = reg.exec(filename);//[ '.png', index: 3, input: 'dog.png' ]
  if (matches) {
    return matches[0];
  }
  return '';
}
var fName = 'dog.png';
console.log(getFileExtendingName(fName));   // ".png"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 正则表达式

# 元字符

预定义类:代表特殊含义的元字符

代码 等同于 匹配
. IE下[^/n],其它[^/n/r] 匹配除换行符之外的任何一个字符
/d [0-9] 匹配数字
/D [^0-9] 匹配非数字字符
/s [ /n/r/t/f/x0B] 匹配一个空白字符
/S [^ /n/r/t/f/x0B] 匹配一个非空白字符
/w [a-zA-Z0-9_] 匹配字母数字和下划线
/W [^a-zA-Z0-9_] 匹配除字母数字下划线之外的字符
A|B 以匹配A或B
^ ^表示行的开头 ^\d表示必须以数字开头
$ $表示行的结束 \d$表示必须以数字结束

其他:

\d : 0-9之间的任意一个数字  \d只占一个位置
\w : 数字,字母 ,下划线 0-9 a-z A-Z _
\s : 空格或者空白等
\D : 除了\d
\W : 除了\w
\S : 除了\s
\s : 任何空白字符。 其中包括空格、制表符和换页符。 [ \f\n\r\t\v]
\S : 任何非空白字符。[^\f\n\r\t\v]
 . : 除了\n之外的任意一个字符
 \ : 转义字符
 | : 或者
() : 分组
\n : 匹配换行符
\b : 匹配边界 字符串的开头和结尾 空格的两边都是边界 => 不占用字符串位数 表示单词边界
\B : 里面一个比较特殊的匹配代码,表示单词的分界处. 表示非单词边界
 ^ : 限定开始位置 => 本身不占位置
 $ : 限定结束位置 => 本身不占位置
[a-z] : 任意字母 []中的表示任意一个都可以
[^a-z] : 非字母 []中^代表除了
[abc] : abc三个字母中的任何一个 [^abc]除了这三个字母中的任何一个字符

^\d+$/ : 匹配一整行1个以上的数字
^和$用来匹配位置:^表示行首,$表示行尾;
\d表示数字,即0-9;
+表示重复1次以上;

cookie = cookie.replace(/^\s+|\s+$/g, '');//替换全部空格为空,或如果cookie以一个或多个空格结束,替换全部空格为空。
\s: space, 空格
+: 一个或多个
|:或者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

量词: 代表次数的量词元字符 (下表量词单个出现时皆是贪婪量词)

代码 描述 示例
* 匹配前面的子表达式零次或多次; 0到多个 zo* 能匹配 "z" 以及 "zoo"。 *** 等价于{0,}**
+ 匹配前面的子表达式一次或多次;1到多个 'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}
? 匹配前面的子表达式零次或一次;0次或1次 可有可无 "do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}
{n} n 是一个非负整数。匹配确定的 n 次;正好n次 'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o
{n,} n 是一个非负整数。至少匹配n 次;n到多次 'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次;n次到m次 "o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'

特殊标志

g标志 ,表示全局匹配

i标志,表示忽略大小写

m标志,表示执行多行匹配

需要使用转义符' \ '的15个特殊字符

/ \ [ ] ( ) { } ? + * | . ^ $
1

# 正则的特性

  • 贪婪性

    所谓的贪婪性就是正则在捕获时,每一次会尽可能多的去捕获符合条件的内容。 如果我们想尽可能的少的去捕获符合条件的字符串的话,可以在量词元字符后加?

  • 懒惰性

    懒惰性则是正则在成功捕获一次后不管后边的字符串有没有符合条件的都不再捕获。 如果想捕获目标中所有符合条件的字符串的话,我们可以用标识符g来标明是全局捕获

var str = '123aaa456';
var reg = /\d+/;  //只捕获一次,一次尽可能多的捕获
var res = str.match(reg)
console.log(res)// ["123", index: 0, input: "123aaa456"]
reg = /\d+?/g; //解决贪婪性、懒惰性
res = str.match(reg)
console.log(res)// ["1", "2", "3", "4", "5", "6"]
1
2
3
4
5
6
7

# 常用方法

test、exec、match、replace、search 和split方法

# 实例方法
  • reg.test(str) 用来验证字符串是否符合正则 符合返回true 否则返回false
  • reg.exec(str) 用来捕获符合规则的字符串
    • exec的捕获还受分组()的影响
    • 如果正则没有加'g'标识符,则exec捕获的每次都是同一个,当正则中有'g'标识符时, 匹配到全部的数组;
# 字符串方法
  • str.match(reg) 如果匹配成功,就返回匹配成功的数组,如果匹配不成功,就返回null
    • 当全局匹配时,match方法会一次性把符合匹配条件的字符串全部捕获到数组中,如果想用exec来达到同样的效果需要执行多次exec方法。
  • str.replace() 把正则匹配成功的字符串替换为新的字符串;
  • str.search() 查找与正则匹配的值;
  • str.split() 按照指定的正则把字符串分割为字符串数组;

reg.test(str)和reg.exec(str)的示例:

var str = 'abc';
var reg = /\w+/;
console.log(reg.test(str));  //true

var str = 'abc123cba456aaa789';
var reg = /\d+/;
console.log(reg.exec(str))
//  ["123", index: 3, input: "abc123cba456aaa789"];
console.log(reg.lastIndex)
// lastIndex : 0 
//reg.exec捕获的数组中 
// [0:"123",index:3,input:"abc123cba456aaa789"]
//0:"123" 表示我们捕获到的字符串
//index:3 表示捕获开始位置的索引
//input 表示原有的字符串

var str = 'abc123cba456aaa789';
var reg = /\d+/g;  //此时加了标识符g
console.log(reg.lastIndex)
// lastIndex : 0 
console.log(reg.exec(str))
//  ["123", index: 3, input: "abc123cba456aaa789"]
console.log(reg.lastIndex)
// lastIndex : 6
console.log(reg.exec(str))
// ["456", index: 9, input: "abc123cba456aaa789"]
console.log(reg.lastIndex)
// lastIndex : 12
console.log(reg.exec(str))
// ["789", index: 15, input: "abc123cba456aaa789"]
console.log(reg.lastIndex)
// lastIndex : 18
console.log(reg.exec(str))
// null
console.log(reg.lastIndex)
// lastIndex : 0
//每次调用exec方法时,捕获到的字符串都不相同;
//lastIndex :这个属性记录的就是下一次捕获从哪个索引开始。当未开始捕获时,这个值为0。如果当前次捕获结果为null。那么lastIndex的值会被修改为0.下次从头开始捕获。而且这个lastIndex属性还支持人为赋值。

//exec的捕获还受分组()的影响
var str = '2016-01-05';
var reg = /-(\d+)/g
// ["-01", "01", index: 4, input: "2016-01-05"]
//"-01" : 正则捕获到的内容
//"01"  : 捕获到的字符串中的小分组中的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

str.match(reg)的示例:

//match和exec的用法差不多
var str = 'abc123cba456aaa789';
var reg = /\d+/;
console.log(reg.exec(str));
//["123", index: 3, input: "abc123cba456aaa789"]
console.log(str.match(reg));
//["123", index: 3, input: "abc123cba456aaa789"]

//当我们进行全局匹配时,二者的不同就会显现出来了
//当全局匹配时,match方法会一次性把符合匹配条件的字符串全部捕获到数组中,如果想用exec来达到同样的效果需要执行多次exec方法。
var str = 'abc123cba456aaa789';
var reg = /\d+/g;
console.log(reg.exec(str));
// ["123", index: 3, input: "abc123cba456aaa789"]
console.log(str.match(reg));
// ["123", "456", "789"]

//尝试着用exec来简单模拟下match方法的实现。
 String.prototype.myMatch = function (reg) {
    var arr = [];
    var res = reg.exec(this);
    if (reg.global) {
        while (res) {
            arr.push(res[0]);
            res = reg.exec(this)
        }
    }else{
        arr.push(res[0]);
    }
    return arr;
}
var str = 'abc123cba456aaa789';
var reg = /\d+/;
console.log(str.myMatch(reg))
// ["123"]
var str = 'abc123cba456aaa789';
var reg = /\d+/g;
console.log(str.myMatch(reg))
// ["123", "456", "789"]

//match和exec都可以受到分组()的影响,不过match只在没有标识符g的情况下才显示小分组的内容,如果有全局g,则match会一次性全部捕获放到数组中
var str = 'abc';
var reg = /(a)(b)(c)/;
console.log( str.match(reg) );
// ["abc", "a", "b", "c", index: 0, input: "abc"]
console.log( reg.exec(str) );
// ["abc", "a", "b", "c", index: 0, input: "abc"]

//当有全局g的情况下
var str = 'abc';
var reg = /(a)(b)(c)/g;
console.log( str.match(reg) );
// ["abc"]
console.log( reg.exec(str) );
// ["abc", "a", "b", "c", index: 0, input: "abc"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

str.replace(reg)的示例:

var str = 'a111bc222de';
var res = str.replace(/\d/g,'Q')
console.log(res)// "aQQQbcQQQde"

//str.replace(reg,fn); replace的第二个参数也可以是一个函数
var str = '2016-01-06';
str = str.replace(/-\d+/g,function(){
    console.log(arguments)
})
//["-01", 4, "2016-01-06"]
//["-06", 7, "2016-01-06"]
//"2016undefinedundefined"
//从打印结果我们发现每一次输出的值似乎跟exec捕获时很相似,既然与exec似乎很相似

var str = '2016-01-06'; //分组处理
str = str.replace(/-(\d+)/g,function(){
    console.log(arguments)
})
//["-01", "01", 4, "2016-01-06"]
//["-06", "06", 7, "2016-01-06"]
//"2016undefinedundefined"
//从结果看来我们的猜测没问题。
//此外,我们需要注意的是,如果需要替换replace中正则找到的字符串,函数中需要一个返回值去替换正则捕获的内容。

//通过replace方法获取url中的参数的方法
(function(pro){
    function queryString(){
        var obj = {}, reg = /([^?&#+]+)=([^?&#+]+)/g;
        this.replace(reg,function($0,$1,$2){
            obj[$1] = $2;
        })
        return obj;
    }
    pro.queryString = queryString;
}(String.prototype));
// 例如 url为 https://www.baidu.com?a=1&b=2
// window.location.href.queryString();
// {a:1,b:2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//查找出现最多的字符串的个数
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
 // 使其按照一定的次序排列
str = str.split('').sort().join('');// "aaabbbbbcccccccc"
//[...new Set('ababbc')].join('')// "abc" //上面的方法也可以用于,去除字符串里面的重复字符。
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {
  console.log($0, "-------", $1);//cccccccc ------- c
    if(num < $0.length){
        num = $0.length;
        char = $1;
    }
});
console.log(str);//aaabbbbbcccccccc
console.log(num);//8
console.log(char);//c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 零宽断言

用于查找在某些内容(但并不包括这些内容)之前或之后的东西,如\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。

"()”代表分组操作 需要注意的这里的分组的索引值是从1开始的;

  • 零宽度正预测先行断言 (?=exp) 字符出现的位置的右边必须匹配到exp这个表达式。
  • 零宽度负预测先行断言 (?!exp) 字符出现的位置的右边不能是exp这个表达式。
  • 零宽度正回顾后发断言 (?<=exp) 字符出现的位置的前边是exp这个表达式。
  • 零宽度负回顾后发断言 (?<!exp) 字符出现的位置的前边不能是exp这个表达式。
var str = "i'm singing and dancing";
var reg = /\b(\w+(?=ing\b))/g
var res = str.match(reg);
console.log(res)
// ["sing", "danc"]
//注意一点,这里说到的是位置,不是字符。
var str = 'abc';
var reg = /a(?=b)c/;
console.log(res.test(str));  // false
//reg中a(?=b)匹配字符串'abc' 字符串a的右边是b这个匹配没问题,接下来reg中a(?=b)后边的c匹配字符串时是从字符串'abc'中a的后边b的前边的这个位置开始匹配的,这个相当于/ac/匹配'abc',显然结果是false了

var str = 'nodejs';
var reg = /node(?!js)/;
console.log(reg.test(str)) // false

var str = '¥998$888';
var reg = /(?<=\$)\d+/;
console.log(reg.exec(str)) //[ '888', index: 5, input: '¥998$888' ]

var str = '¥998$888';
var reg = /(?<!\$)\d+/;
console.log(reg.exec(str)) //998
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 示例

# 正则表达式的创建
//对象的方式创建
var reg=new RegExp(/\d{5}/);
var str="myPhoneID10086";
//调用方法验证字符串是否匹配
var flag=reg.test(str);
console.log(flag);//true

//字面量的方式创建正则表达式
var reg=/\d{5}/;
var flag=reg.test("myPhoneID10086");
console.log(flag);//true
1
2
3
4
5
6
7
8
9
10
11
# 动态规则
  • 用eval()处理; 【推荐】
  • 用regExp对象处理
var r = /^abc$/;//写死;
var str = "abc";
var r = eval('/^'+str+'$/');//用eval()处理
console.log(r.exec("abc"));

var e = RegExp("^"+str + "$","i");//用regExp处理
console.log(e.exec("abc"));

var result = str.replace(/a/g, 'A');//不动态的设置;
1
2
3
4
5
6
7
8
9

放入原型:

String.prototype.replaceAll = function (FindText, RepText) {
    regExp = new RegExp(FindText, "g");
    return this.replace(regExp, RepText);
}
1
2
3
4

常见规则 参考工具 (opens new window)

校验数字的表达式

  • 数字:^[0-9]*$
  • n位的数字:^\d{n}$
  • 至少n位的数字**:^\d{n,}$**
  • m-n位的数字:^\d{m,n}$
  • 零和非零开头的数字:^(0|[1-9][0-9]*)$
  • 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
  • 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})$
  • 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
  • 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
  • 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
  • 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^+?[1-9][0-9]*$
  • 非零的负整数:^-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
  • 非负整数:^\d+$ 或 ^[1-9]\d*|0$
  • 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
  • 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d*.\d*|0.\d*[1-9]\d*|0?.0+|0$
  • 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d*.\d*|0.\d*[1-9]\d*))|0?.0+|0$
  • 正浮点数:^[1-9]\d*.\d*|0.\d*[1-9]\d*$ 或 ^(([0-9]+.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*.[0-9]+)|([0-9]*[1-9][0-9]*))$
  • 负浮点数:^-([1-9]\d*.\d*|0.\d*[1-9]\d*)$ 或 ^(-(([0-9]+.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*.[0-9]+)|([0-9]*[1-9][0-9]*)))$
  • 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d*.\d*|0.\d*[1-9]\d*|0?.0+|0)$

校验字符的表达式

  • 汉字:^[\u4e00-\u9fa5]{0,}$
  • 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
  • 长度为3-20的所有字符:^.{3,20}$
  • 由26个英文字母组成的字符串:^[A-Za-z]+$
  • 由26个大写英文字母组成的字符串:^[A-Z]+$
  • 由26个小写英文字母组成的字符串:^[a-z]+$
  • 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
  • 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
  • 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
  • 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
  • 可以输入含有^%&',;=?$"等字符:[^%&',;=?$\x22]+
  • 禁止输入含有~的字符:[^~\x22]+
String.prototype.trim = function() {//去除字符串前后空格
    return this.replace(/(^\s+)|(\s+$)/g, '');
};
//()”代表分组操作; 需要注意的这里的分组的索引值是从1开始的,所以取第一个分组的方法是$1
function telFormat(tel, space) {//11位手机号格式化; 用到补位符号:$1
    space = space ? space : ' ';
return String(tel).replace(/(\d{3})(\d{4})(\d{3})/, '$1' + space + '$2' + space + '$3');
}
1
2
3
4
5
6
7
8

# 实现千位分隔符 ; 方式一:正则方式; 方式二:正常思维逻辑;

function numFormat(num) {//功能兼容性最全; str.replace( /\B(?=(?:\d{3})+$)/g, ',' );
  var res = num.toString().replace(/\d+/, function(n) {// 先提取整数部分
    return n.replace(/(\d)(?=(\d{3})+$)/g, function($0, $1, $2, $3) { {//正则匹配数据;
      //console.log('------->', $0, $1, $2, $3);
      return $1 + ",";//直接替换匹配的那个字符,加上,
    });
  });
  return res;
}
// var a = 1234567894532;
var a = `a1234567894532`;
var b = 673439.4542;
console.log(numFormat(a)); // "1,234,567,894,532"
// console.log(numFormat(b)); // "673,439.4542"

//方法一:使用正则表达式
// 正则表达式 \d{1,3}(?=(\d{3})+$)  表示前面有1~3个数字,后面的至少由一组3个数字结尾。
// ?=表示正向引用,可以作为匹配的条件,但匹配到的内容不获取,并且作为下一次查询的开始。
// $& 表示与正则表达式相匹配的内容,具体的使用可以查看字符串replace()方法的
function format (num) {
  // var reg=/\d{1,3}(?=(\d{3})+$)/g; 
  var reg=/(\d)(?=(\d{3})+$)/g; 
  return (num + '').replace(reg, '$&,');
  //str.replace( /\B(?=(?:\d{3})+$)/g, ',' ); //不能处理小数;
}
var a = `a1234567894532`;
var b = 673439.4542;
var c = 13123903243;
// console.log(format(a));
// console.log(format(b));
console.log(format(c));

'1234567'.replace(/\B(?=(?:\d{3})+$)/g, ',')  //不能处理小数;
// 1,234,567先匹配第一个非单词边界(1和2之间),然后?=预测后面的内容为\d{3}三个连续的数字,+做循环,不断检测三个连续数字,(234 567),当数字匹配完之后,开始检查是否为字符串结尾,若为结尾,则匹配成功;接着从第二个单词边界开始(2和3之间),……最后查询结束符合匹配的就在1和2之间 4和5之间
//(?:\d{3})+$ 表示一串长度可以被三整除的数字直至末尾

//第二种:方法-正常思维算法
function format(num){
  num=num+'';//数字转字符串
  var str="";//字符串累加
  for(var i=num.length-1,j=1;i>=0;i--,j++){
    if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况
      str+=num[i]+",";//加千分位逗号
      continue;
    }
    str+=num[i];//倒着累加数字
  }
  return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

以上匹配方式图示:基本思路 ![](./_image/01.js-cate-arr/11-33-31.jpg) ![](./_image/01.js-cate-arr/11-34-02.jpg)

# 实现HTML编码,将< / > " & ` 等字符进行转义,避免 XSS 攻击 。

function htmlEncode(str) {
    //匹配< / > " & `
    return str.replace(/[<>"&\/`]/g, function(rs) {
        switch (rs) {
            case "<":
                return "<";
            case ">":
                return ">";
            case "&":
                return "&";
            case "\"":
                return """;
            case "/": 
                return "/"
            case "`":
                return "'"
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 提取URL的部分获取参数示例:
function parseQueryString(url) {
  var params = {}, arr = url.split("?");
  if (arr.length <= 1) return params;
  arr = arr[1].split("&");
  for (var i = 0, l = arr.length; i < l; i++) {
    var a = arr[i].split("=");
    params[a[0]] = a[1];
  }
  return params;
}
var url = "http://baidu.com?key0=0&key1=1&key2=2";
var ps = parseQueryString(url);
console.log(ps);//{ key0: '0', key1: '1', key2: '2' }
console.log(ps["key1"]);//1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

正则方式实现:replace + 分组实现;

//通过replace方法获取url中的参数的方法
(function(pro){
    function queryString(){
        var obj = {}, reg = /([^?&#+]+)=([^?&#+]+)/g;
        this.replace(reg,function($0,$1,$2){
            obj[$1] = $2;
        })
        return obj;
    }
    pro.queryString = queryString;
}(String.prototype));
// 例如 url为 https://www.baidu.com?a=1&b=2
// window.location.href.queryString();
// {a:1,b:2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 如何将 JS 日期转换为ISO标准

toISOString() 方法用于将js日期转换为ISO标准。 它使用ISO标准将js Date对象转换为字符串。如:

var date = new Date();
var n = date.toISOString();
console.log(n);
// YYYY-MM-DDTHH:mm:ss.sssZ
1
2
3
4

# 数组

  • Array它把下标变成数字,用其作属性。它比真正的数组慢,但用起来更方便。
  • Array本质还是对象,其原型继承自Array.prototype,向上再继承自Object.prototype
  • 使用delete arr[2],并不能减少length,而只是删除了对应的属性(变成empty)
  • Array的方法是设计为对象通用的,对象也能调用数组的方法
let obj = {
  '2': 3,
  '3': 4,
  'length': 2,
  'splice': Array.prototype.splice,
  'push': Array.prototype.push
}
console.log(obj);
// { '2': 3,'3': 4, length: 2, splice: [Function: splice], push: [Function: push] }
obj.push(1)
obj.push(2)
console.log(obj);
// { '2': 1, '3': 2, length: 4, splice: [Function: splice], push: [Function: push] }
1
2
3
4
5
6
7
8
9
10
11
12
13

# length

length属性是可写的:length属性的值总是比最大的那个整数键大1数组清空的一个有效方法,就是将length属性设为0

值得注意的是,由于数组本质上是对象的一种,所以我们可以为数组添加属性,但是这不影响length属性的值。 **将数组的键分别设为字符串和小数,结果都不影响length属性。**因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0 示例:

# 空位问题

数组的空位是可以读取的,返回undefined;比如:var a = [, , ,]; a[1] // undefined 使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性

# 使用数组

# 数组去重

去除数组的重复成员

//方法一:普通遍历筛选法;
//方式二:filter及map方法特性;
const res = new Map();
return arr.filter((a) => !res.has(a) && res.set(a, 1))
//方法三:set方法; 解构[...]等同于Array.from()
[...new Set(array)]//Array.from(new Set(array))
[...new Set('ababbc')].join('')// "abc" //上面的方法也可以用于,去除字符串里面的重复字符。
//方式四:lodash方法;
_.unionBy(array, [iteratee=_.identity]);  _.compact(array);
rightList: _.unionBy([...rightList, ...rightListN], 'rowId');
1
2
3
4
5
6
7
8
9
10

# Set应用场景

  • 去重字符串[...new Set(str)].join("")
  • 去重数组[...new Set(arr)]Array.from(new Set(arr))
  • 集合数组
    • 声明:const a = new Set(arr1)const b = new Set(arr2)
    • 并集new Set([...a, ...b])
    • 交集new Set([...a].filter(v => b.has(v)))
    • 差集new Set([...a].filter(v => !b.has(v)))

# 数组清空

//方法一:把变量arrayList设置为一个新的空数组。如果在其他任何地方都没有对原始数组arrayList的引用,则建议这样做,因为它实际上会创建一个新的空数组。
//应该小心使用这种清空数组的方法,因为如果你从另一个变量引用了这个数组,那么原始的引用数组将保持不变。
arrayList = []

//方法二:通过将其length设置为0来清除现有数组。这种清空数组的方式还会更新指向原始数组的所有引用变量。 
//因此,当你想要更新指向arrayList的所有引用变量时,此方法很有用。
arrayList.length = 0;
//使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性
//数组的空位是可以读取的,返回undefined;比如:var a = [, , ,]; a[1] // undefined

//方式三:这种清空数组的方法也将更新对原始数组的所有引用。
arrayList.splice(0, arrayList.length);

//方式四:实现也可以空数组,但通常不建议经常使用这种方式
while(arrayList.length){
  arrayList.pop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 常用方法

栈方法 push() 可以接收任意数量的参数,把他们逐个添加到数组的末尾,返回修改后数组的长度 pop() 从数组末尾移除最后一项,返回移除的项 队列方法 unshift() 向数组前端添加任意个项并返回新数组的长度 shift() 移除数组的第一项并返回该项 排序 sort(compare): compare函数接收两个参数,如果返回负数,则第一个参数位于第二个参数前面;如果返回零,则两个参数相等;如果返回正数,第一个参数位于第二个参数后面; (a,b) => (b-a) // 降序,升序相反 Array.sort(); 如果不带参数调用sort,数组以字母顺序进行排序升序; 如果数组中包含undefined元素,它们会被排到数组的尾部

操作方法 concat(数组 | 一个或多个元素) ;合并数组,返回新数组 slice(起始位置 ,[结束位置]);切分数组,返回新数组,新数组不包含结束位置的项 splice(起始位置,删除的个数,[插入的元素]) // 删除|插入|替换数组,返回删除的元素组成的数组,会修改原数组; reverse(): 反转数组元素的顺序 Array.join()是Array.split()方法的逆向操作;以指定分隔符连接,如果不指定,默认以,连接; Array.toString(): 返回一个字符串,表示指定的数组及其元素。跟join类似;

总括:

方法 作用 是否影响原数组
push 在数组后添加元素,返回数组长度
pop 删除数组最后一项,返回被删除项
shift 删除数组第一项,并返回被删除项
unshift 数组开头添加元素,返回新数组长度
reserve 反转一个数组,返回修改后的数组
sort 排序一个数组,返回修改后的数组
splice 截取数组,返回被截取的区间
join 将一个数组所有元素连接成字符串并返回这个字符串
concat arr1.concat(arr2, arr3) 连接数组
join arr.join(x)将arr数组元素连接成字符串并返回这个字符串
map 操作数组每一项并返回一个新数组
forEach 遍历数组,没有返回值
filter 对数组所有项进行判断,返回符合规则的新数组
every 数组每一项都符合规则才返回true
some 数组有符合规则的一项就返回true
reduce 接收上一个return和数组的下一项
flat 数组扁平化
slice 截取数组,返回被截取的区间
Object.prototype.toString.call(target).slice(8, -1)////targetType ==='Object'

Vue.set(vm.items, indexOfItem, newValue)// Vue.set
vm.$set(vm.items, indexOfItem, newValue)// vm.$set,Vue.set的一个别名
vm.items.splice(indexOfItem, 1, newValue)// Array.prototype.splice; 底层实现方式;

var arr=['s', 'a', 'm', 'y']
console.log(arr.toString())//s,a,m,y
console.log(arr.join())//s,a,m,y

//args保存的就是提前绑定的参数列表; 取第1个参数后面的数据;这个方式参数调用的有参数形式。【bind时用到】
var args = Array.prototype.slice.call(arguments,1); //如果是用...args就不用这么考虑;直接操作;

//【函数在柯里化时用到】
var _args = [].slice.call(arguments); // 将参数转化为数组
Array.prototype.unshift.apply(_args, args);// 将上次的参数与当前参数进行组合并修正传参顺序
return curried.apply(this, args.concat(args2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

默认重头到尾截取,类似于赋值;

function arrT() {//单个参数时,apply跟call的功能一致;
  var arr = Array.prototype.slice.apply(arguments)//[ 1, 2, 3 ]
  var arr2 = Array.prototype.slice.call(arguments)//[ 1, 2, 3 ]
}
arrT(1,2,3)
1
2
3
4
5

# 数组遍历

# for循环

  • 简单for
  • 循环for-in(最慢):**循环遍历的是对象的属性,而不是数组的索引。**因此, for-in 遍历的对象便不局限于数组,还可以遍历对象。
  • forEach(最快):没有返回值; a.forEach(function(item, idx, arr){})
  • map有返回值
  • for-of(ES6+):

# 方法及比较

  • forEach/map 不能 break 和 return;

  • for-in 缺点更加明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。 for in更适合遍历对象;

  • for-of优点:推荐

    • 跟 forEach 相比,可以正确响应 break, continue, return。
    • for-of 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。
    • 其不仅可以遍历数组,还可以遍历类数组对象和其他可迭代对象。
    • for-of 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。
    • for-of 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。

# forEach 、for in、 for of各自的特点是什么?

  1. forEach 只能遍历数组,且不能中断(break等无效); 可查看后面forEach内部源码的实现;
  2. for in 遍历的是对象的可枚举属性;结合hasOwnPropery方法可以判断某属性是否是该对象的实例属性;
  3. for of 遍历的是对象的迭代器属性;

记住,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值

for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

for-of/in示例:

for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}

Array.prototype.method = function() {
  console.log(this.length);
};
var myArray = [1, 2, 6];
myArray.name = "数组";
for (var value in myArray) {
  console.log(value);
}
// 0
// 1
// 2
// name
// method
console.log('---for of-----');
for (var value of myArray) {
  console.log(value);
}
// 1
// 2
// 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
    console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
    console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
	console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
1
2
3
4
5
6
7
8
9
10
11

# 其他遍历相关方法

some()、every()、filter()、map()、forEach()都是对数组的每一项调用函数进行处理。

– some()、every()的返回值 :true / false

– filter()、map()的返回值 :一个新数组

– forEach()无返回值。当然forEach也可以改变原数组:return

使用some()、every()、filter()、map()、forEach()都不改变原数组

find返回通过指定条件(函数内判断)的数组的第一个元素的值; 不创建新数组

arr.filter()、includes()、find()、findIndex(); indexOf(), lastIndexOf()//方法对大小写敏感

# forEach如何跳出循环?

forEach是不能通过break或者return来实现跳出循环的,为什么呢?实现过forEach的同学应该都知道,forEach的的回调函数形成了一个作用域,在里面使用return并不会跳出,只会被当做continue

那怎么跳出循环呢?可以利用try catch

function getItemById(arr, id) {
  var item = null;
  try {
    arr.forEach(function (curItem, i) {
      if (curItem.id == id) {
        item = curItem;
        throw Error();
      }
    })
  } catch (e) {
  }
  return item;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 归并方法: reduce/reduceRight

对数组中的元素依次处理,将上次结果作为下次处理的输入,最后得到最终结果。接收一个函数作为累加器;

数组有两个归并方法

  • reduce()方法从左到右遍历数组;reduce是ES5的数组api, 参数有函数和默认初始值; 函数有四个参数, pre(上一次的返回值),cur(当前值), curIndex(当前值索引), arr(当前数组)

  • 而reduceRight()从右到左遍历数组;

数组只有一项或是个空数组不做遍历操作; 如果归并的数组为空,则会报错;

//函数有四个参数, pre(上一次的返回值),cur(当前值), curIndex(当前值索引), arr(当前数组)
var a = [1,2,3].reduce(function(prev,item,key,array){
    console.log(9);  //打印了 两次9
    return prev+item;
});
console.log(a);//6
//输出:9 9 6

//数组只有一项或是个空数组不做遍历操作; 如果归并的数组为空,则会报错;
var a = [1].reduce(function(prev,item,key,array){
    console.log(2);  //不打印
    return prev+item;
});
console.log(a);//1
//没有打印2,直接输出1,说明reduce的函数压根没执行,直接将原数组返回了
//如果这个数组是空的,调用reduce()或reduceRight()后会直接报错,所以在进行上面操作时最好判断下是否为空数组,不为空再做处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

比如:求数组中的最大值; Math.max()是Math对象内置的方法**,参数是字符串**;

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
1
2
3
4
5

# 遍历方法类似原理

# foreach的类似实现原理
// if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fn) {
  for (var i = 0; i < this.length; i++) {
    fn(this[i], i, this);
  }
};
// }
["a", "b", "c"].forEach(function(value, index, array) {
  console.log((value, "Is in position " + index + " out of " + (array.length - 1)));
});
1
2
3
4
5
6
7
8
9
10
# map的类似实现原理
if(Array.prototype.map===undefined){//**map的实现原理**,浏览器不支持map属性
  Array.prototype.map=function(fun){
    var newArr=[]; //创建空数组: newArr
    for(var i=0;i<this.length;i++){//遍历当前数组中每个元素
      //如果当前元素不是undefined
      if(this[i]!==undefined){//判断稀疏数组
        var r = fun(this[i],i,this);//调用fun传入当前元素值,位置i,当前数组,将结果保存在r中
        newArr[i]=r; //将newArr的i位置赋值为r
      }
    }//(遍历结束)
    return newArr;//返回newArr
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Array.prototype.sx_map = function (callback) {
    const res = []
    for (let i = 0; i < this.length; i++) {
        res.push(callback(this[i], i, this))
    }
    return res
}
1
2
3
4
5
6
7
# reduce的类似实现原理
if(Array.prototype.reduce===undefined){//**reduce的实现原理**,如果浏览器不支持reduce属性
  Array.prototype.reduce = function(fun,base){
    base===undefined&&(base=0);//默认第一项
    for(var i=0;i<this.length;i++){
      if(this[i]!==undefined){
        base=fun(base,this[i],i,this);
      }
    }
    return base;
  }
}
1
2
3
4
5
6
7
8
9
10
11
Array.prototype.sx_reduce = function (callback, initValue) {
    let start = 0, pre;
    //有初始化默认值情况下;
    if (initValue) {
        pre = initValue
    } else {
        //美亚初始化默认值情况下,取数组第一个数据
        pre = this[0]
        start = 1
    }
    for (let i = start; i < this.length; i++) {
        pre = callback(pre, this[i], i, this)
    }
    return pre
}

// 计算所有num相加
const sum = players.sx_reduce((pre, next) => {
    return pre + next.num
}, 0)
console.log(sum) // 85
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# every的类似实现原理
if(Array.prototype.every===undefined){//**every的实现原理**,如果浏览器不支持every属性
  Array.prototype.every=function(fun){
    //遍历当前数组中每个元素
    for(var i=0;i<this.length;i++){
      if(this[i]!==undefined){
        //调用fun,依次传入当前元素值,位置i,当前数组作为参数,将返回值,保存在变量r中
        var r=fun(this[i],i,this);
        if(r==false){ return false; }
      }
    }//(遍历结束)
    return true;//返回true
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# some的类似实现原理
if(Array.prototype.some===undefined){//**some的实现原理**,如果浏览器不支持some属性
  Array.prototype.some=function(fun){
    for(var i=0;i<this.length;i++){
      if(this[i]!==unefined){
        var r=fun(this[i],i,this);//要点
        if(r==true){ return true; }
      }
    }
    return false;
  }    
}
//检测数组中是否有元素大于18
1
2
3
4
5
6
7
8
9
10
11
12
# slice的类似实现原理
Array.prototype.slice = function(start,end){  
      var result = new Array();  
      start = start || 0;  
      //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键  
      end = end || this.length; 
      for(var i = start; i < end; i++){  
           result.push(this[i]);  
      }  
      return result;  
 } 
1
2
3
4
5
6
7
8
9
10

# reduce模拟其他遍历实现

# reduce -> map

map 方法接收一个回调函数,函数内接收三个参数,当前项、索引、原数组,返回一个新的数组,并且数组长度不变。 知道了这些特征之后,我们用 reduce 重塑 map

const testArr = [1, 2, 3, 4]
Array.prototype.reduceMap = function(callback) {
  return this.reduce((acc, cur, index, array) => {
    const item = callback(cur, index, array)
    acc.push(item)
    return acc
  }, [])
}
testArr.reduceMap((item, index) => {
  return item + index
})
// [1, 3, 5, 7]
1
2
3
4
5
6
7
8
9
10
11
12
# reduce -> forEach

forEach 接收一个回调函数作为参数,函数内接收四个参数当前项、索引、原函数、当执行回调函数 callback 时,用作 this 的值,并且不返回值。

const testArr = [1, 2, 3, 4]
Array.prototype.reduceForEach = function(callback) {
  this.reduce((acc, cur, index, array) => {
    callback(cur, index, array)
  }, [])
}

testArr.reduceForEach((item, index, array) => {
  console.log(item, index)
})
// 1234
// 0123
1
2
3
4
5
6
7
8
9
10
11
12

只要看得懂 reduce -> map ,转 forEach 只是改改结构的问题。

# reduce -> filter

filter 同样接收一个回调函数,回调函数返回 true 则返回当前项,反之则不返回。回调函数接收的参数同 forEach

const testArr = [1, 2, 3, 4]
Array.prototype.reduceFilter = function (callback) {
   return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      acc.push(cur)
    }
    return acc
  }, [])
}
testArr.reduceFilter(item => item % 2 == 0) // 过滤出偶数项。
// [2, 4]
1
2
3
4
5
6
7
8
9
10
11

filter 方法中 callback 返回的是 Boolean 类型,所以通过 if 判断是否要塞入累计器 acc ,并且返回 acc 给下一次对比。最终返回整个过滤后的数组。

# reduce -> find

find 方法中 callback 同样也是返回 Boolean 类型,返回你要找的第一个符合要求的项。

const testArr = [1, 2, 3, 4]
const testObj = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
Array.prototype.reduceFind = function (callback) {
  return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      if (acc instanceof Array && acc.length == 0) {
      	acc = cur
      }
    }
    // 循环到最后若 acc 还是数组,且长度为 0,代表没有找到想要的项,则 acc = undefined
    if ((index == array.length - 1) && acc instanceof Array && acc.length == 0) {
      acc = undefined
    }
    return acc
  }, [])
}
testArr.reduceFind(item => item % 2 == 0) // 2
testObj.reduceFind(item => item.a % 2 == 0) // {a: 2}
testObj.reduceFind(item => item.a % 9 == 0) // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 类数组

# 定义及说明

属性特点

  1. 必须有length属性;
  2. 类数组不是数组,通过 Array.isArray() 会返回 false;
  3. 类数组通过 Array.from 可以转换为数组;
  4. 属性要为索引(数字)属性;

经常遇见的类数组

  • 字符串
    • 唯一的原生类数组
  • arguments
    • arguments完全可以使用...args代替,这样不定参数就是真数组
    • arguments在箭头函数中被移除
  • DOM中nodeList;

# argument

详细操作:见函数部分讲解;

# 使类数组拥有数组的方法

方式一Array.prototype.slice.apply(argument); 理论上来说这个比较快,直接在原型上查找slice方法;但实际上比较慢

方式二[].slice.apply(arguments); 理论上来说这个比较慢;实际上比较快;

//Object.prototype.toString.call(arguments)//[object Arguments]
Array.prototype.slice.call(arguments) //arguments是类数组(伪数组)
Array.prototype.slice.apply(arguments)
Array.from(arguments)
[...arguments]
var args = Array.prototype.slice.call(arguments);//方法一
var args = [].slice.call(arguments, 0);//方法二
//方法三:遍历法
var args = []; 
for (var i = 1; i < arguments.length; i++) { 
  args.push(arguments[i]);
}
//通用函数
var toArray = function(s){
  try{
    return Array.prototype.slice.call(s);
  } catch(e){
    var arr = [];
    for(var i = 0,len = s.length; i < len; i++){
      //arr.push(s[i]);
      arr[i] = s[i];  //据说这样比push快
    }
    return arr;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function test(a,b,c,d) {
  // return Array.prototype.slice.call(arguments,1); //取第一个到最后一个;
  return [].slice.call(arguments,1); //跟上面方法一样; 就是数组的.slice(1)
}
console.log(test("a","b","c","d")); //[ 'b', 'c', 'd' ]
1
2
3
4
5

# 数组常用操作(算法相关)

可以参照loadshjs的方法使用

# 扁平化n维数组

_.flatten([1, [2, [3, [4]], 5]]); // => [1, 2, [3, [4]], 5]

.flattenDeep([1, [2, [3, [4]], 5]]); //.flattenDepth(array, 1);

1.终极篇

[1,[2,3]].flat(1) //[1,2,3]
[1,[2,3,[4,5]]].flat(2) //[1,2,3,4,5]
[1,[2,3,[4,5]]].toString()  //'1,2,3,4,5'
[1[2,3,[4,5[...]].flat(Infinity) //[1,2,3,4...n]
1
2
3
4

Array.flat(n)是ES10扁平数组的api,n表示维度,n值为Infinity时维度为无限大;

实质是利用递归和数组合并方法concat实现扁平

2.开始篇

function flatten(arr) {
    while(arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
flatten([1,[2,3]]) //[1,2,3]
flatten([1,[2,3,[4,5]]) //[1,2,3,4,5]
         
function flatten(arr){
    return arr.reduce(function(prev,item){
        return prev.concat(Array.isArray(item)?flatten(item):item);
    },[]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

原始普通写法;

function flatten(arr){
    var res = [];
    for(var i=0;i<arr.length;i++){
        if(Array.isArray(arr[i])){
            res = res.concat(flatten(arr[i]));
        }else{
            res.push(arr[i]);
        }
    }
    return res;
}
1
2
3
4
5
6
7
8
9
10
11

# 去重

1.终极篇

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

//方法一:普通遍历筛选法;
//方法二:set方法; ..解构[...]等同于Array.from()
[...new Set(array)]//Array.from(new Set(array))
[...new Set('ababbc')].join('')// "abc" //上面的方法也可以用于,去除字符串里面的重复字符。
//方式三:filter及map方法特性;
const res = new Map();
return arr.filter((a) => !res.has(a) && res.set(a, 1))
//方式四:lodash方法;
_.uniqBy(array, [iteratee=_.identity]);  _.compact(array)
1
2
3
4
5
6
7
8
9
10
11
12

set是ES6新出来的一种一种定义不重复数组的数据类型 Array.from是将类数组转化为数组 ...是扩展运算符,将set里面的值转化为字符串

2.开始篇

Array.prototype.distinct = function() {
    const map = {}
    const result = []
    for (const n of this) {
        if (!(n in map)) {
            map[n] = 1
            result.push(n)
        }
    }
    return result
}
[1,2,3,3,4,4].distinct(); //[1,2,3,4]
1
2
3
4
5
6
7
8
9
10
11
12

取新数组存值,循环两个数组值相比较; 通过临时变量比较:obj[arr[i]] = 1;或通过数组indexof查找【t推荐】;

function fn(arr){
   var obj = {};
   var newArr = [];
   for(var i=0;i<arr.length;i++){
       if(!obj[arr[i]]){//或者:
           obj[arr[i]] = 1;
           newArr.push(arr[i]);
       }
   }
   return newArr;
}
var arr = [2,4,7,3,5,2,8,7];
console.log(fn(arr));
1
2
3
4
5
6
7
8
9
10
11
12
13
let arr = [1,'1',2,'2',1,2,'x','y','f','x','y','f'];
function unique1(arr){
	let result = [arr[0]];
	for (let i = 1; i < arr.length; i++) {
		let item = arr[i];
		if(result.indexOf(item) == -1){
			result.push(item);
		}
	}
	return result;
}
console.log(unique1(arr));
1
2
3
4
5
6
7
8
9
10
11
12

# 最大值

1.终极篇

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

//两个功能是一样的;比如:func是Math.max的话;
//return func(..._args);
//return func.apply(null, _args);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Math.max()是Math对象内置的方法,参数是字符串;

2.开始篇 先排序再取值

# 排序

1.终极篇

[1,2,3,4].sort((a, b) => a - b); // [1, 2,3,4],默认是升序
[1,2,3,4].sort((a, b) => b - a); // [4,3,2,1] 降序
1
2

sort是js内置的排序方法,参数为一个函数

2.开始篇

# 冒泡排序

Array.prototype.bubleSort=function () {
    let arr=this,len = arr.length;
    for (let outer = len; outer >= 2; outer--) {
      for (let inner = 0; inner <= outer - 1; inner++) {
        if (arr[inner] > arr[inner + 1]) {//升序
          [arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]];
          console.log([arr[inner], arr[inner + 1]]);
        }
      }
    }
    return arr;
  }
[1,2,3,4].bubleSort() //[1,2,3,4]    
1
2
3
4
5
6
7
8
9
10
11
12
13

# 选择排序

  Array.prototype.selectSort=function () {
      let arr=this,len = arr.length;
      for (let i = 0, len = arr.length; i < len; i++) {
    		for (let j = i, len = arr.length; j < len; j++) {
      		if (arr[i] > arr[j]) {
        		[arr[i], arr[j]] = [arr[j], arr[i]];
      		}
    	}
  }
    return arr;
  }
  [1,2,3,4].selectSort() //[1,2,3,4] 
1
2
3
4
5
6
7
8
9
10
11
12

# 二分法查找有序数组

所谓二分法查找法,也就是折半查找,它是一种在有序数组查找特定元素的搜索算法;二分法查找的前提;数组必须为有序

var arr = [1,2,3,4,5,6];
function getIndex1(arr,key){
    var low = 0;
    var high = arr.length-1;
    while(low<=high){
        var mid = Math.floor((low+high)/2);
        if(key===arr[mid]){
            return mid;
        }else if(key>arr[mid]){
            low = mid+1;//left
        }else{
            height = mid-1;//right
        }
    }
    return -1;
}
console.log(getIndex1(arr,5));//4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 实现数组的随机排序

//方法一:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort1(arr){
  for(var i = 0,len = arr.length;i < len; i++ ){
    var rand = parseInt(Math.random()*len);
    var temp = arr[rand];
    arr[rand] = arr[i];
    arr[i] = temp;
  }
  return arr;
}
console.log(randSort1(arr));

//方法二:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){
  var mixedArray = [];
  while(arr.length > 0){
    var randomIndex = parseInt(Math.random()*arr.length);
    mixedArray.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return mixedArray;
}
console.log(randSort2(arr));

//方法三:推荐,简洁;
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
  return Math.random() - 0.5;
})
console.log(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 取出最大的三个数的下标

首先记录之前下标在排序,这样取出前三个就行了,然后在拿到下标就可以了; 后面循环取出前三个即可

var arr = [1, 2, 65, 98, 87, 35, 7, 10, 6]
function retrunAarr(arr) {
    var arr = arr.map((item, index) => {
        return [item, index]
    }).sort((a, b) => {
        return b[0] - a[0]
    })
    return arr
}
console.log(retrunAarr(arr));
// [ [ 98, 3 ],
//   [ 87, 4 ],
//   [ 65, 2 ],
//   [ 35, 5 ],
//   [ 10, 7 ],
//   [ 7, 6 ],
//   [ 6, 8 ],
//   [ 2, 1 ],
//   [ 1, 0 ] ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 找出正数组的最大差值

在这个数组里面,找到最小值及最大值,然后相减

function getMaxProfit(arr) {
  var minPrice = arr[0];
  var maxProfit = 0;
  for (var i = 0; i < arr.length; i++) {
    var currentPrice = arr[i];
    minPrice = Math.min(minPrice, currentPrice);
    var potentialProfit = currentPrice - minPrice;
    maxProfit = Math.max(maxProfit, potentialProfit);
  }
  return maxProfit;
}

var arr = [10, 5, 11, 7, 8, 9];
console.log(getMaxProfit(arr));//6
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 求和

1.终极篇

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

2.开始篇

function sum(arr) {
  var len = arr.length;
  if(len == 0){
    return 0;
  } else if (len == 1){
    return arr[0];
  } else {
    return arr[0] + sum(arr.slice(1));
  }
}
sum([1,2,3,4]) //10
1
2
3
4
5
6
7
8
9
10
11

利用slice截取改变数组,再利用递归求和

# 合并

1.终极篇

[1,2,3,4].concat([5,6]) //[1,2,3,4,5,6]
[...[1,2,3,4],...[4,5]] //[1,2,3,4,5,6]
let arrA = [1, 2], arrB = [3, 4]
Array.prototype.push.apply(arrA, arrB))//arrA值为[1,2,3,4]
1
2
3
4

2.开始篇

let arr=[1,2,3,4];
 [5,6].map(item=>{
   arr.push(item)
 })
 //arr值为[1,2,3,4,5,6],注意不能直接return出来,return后只会返回[5,6]
1
2
3
4
5

# 求两数组中不同元素

const arr1 = [1,2,3,4,5]
const arr2 = [2,3,1,0,5]
const ans = arr1.filter(v => arr2.indexOf(v) == -1 )//[4]

const a = new Set(arr1) const b = new Set(arr2)
new Set([...a, ...b])//并集  
new Set([...a].filter(v => b.has(v)))//交集  
new Set([...a].filter(v => !b.has(v)))//差集
1
2
3
4
5
6
7
8

# 对象和数组转化

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}
1
2
3
4

# 对象数组

[{count:1},{count:2},{count:3}].reduce((p, e)=>p+(e.count), 0)
1

# 类数组转化

1.终极篇

Array.prototype.slice.call(arguments) //arguments是类数组(伪数组)
Array.prototype.slice.apply(arguments)
Array.from(arguments)
[...arguments]
1
2
3
4

类数组: 表示有length属性,但是不具备数组的方法 call,apply: 是改变slice里面的this指向arguments, 所以arguments也可调用数组的方法 Array.from是将类似数组或可迭代对象创建为数组 ...是将类数组扩展为字符串,再定义为数组

2.开始篇

Array.prototype.slice = function(start,end){  
      var result = new Array();  
      start = start || 0;  
      //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键  
      end = end || this.length; 
      for(var i = start; i < end; i++){  
           result.push(this[i]);  
      }  
      return result;  
 }
1
2
3
4
5
6
7
8
9
10

# 实现数组的随机排序

//方法一:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort1(arr){
  for(var i = 0,len = arr.length;i < len; i++ ){
    var rand = parseInt(Math.random()*len);
    var temp = arr[rand];
    arr[rand] = arr[i];
    arr[i] = temp;
  }
  return arr;
}
console.log(randSort1(arr));

//方法二:
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){
  var mixedArray = [];
  while(arr.length > 0){
    var randomIndex = parseInt(Math.random()*arr.length);
    mixedArray.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return mixedArray;
}
console.log(randSort2(arr));

//方法三:推荐,简洁;
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
  return Math.random() - 0.5;
})
console.log(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 技巧

# 判断是否包含值

1.终极篇

includes(),find(),findIndex()是 ES6的api

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

2.开始篇

[1,2,3].some(item=>{
  return item===3
}) //true 如果不包含返回false
1
2
3

# 每一项设置值

1.终极篇

[1,2,3].fill(false) //[false,false,false] 
1

fill是ES6的方法

2.开始篇

[1,2,3].map(() => 0)
1

# 每一项是否满足

[1,2,3].every(item=>{return item>2}) //false
1

every是ES5的api,每一项满足返回 true

# 有一项满足

[1,2,3].some(item=>{return item>2}) //true
1

some是ES5的api,有一项满足返回 true

# 过滤数组

[1,2,3].filter(item=>{return item>2}) //[3]
1

filter是ES5的api,返回满足添加的项的数组

# 其他相关算法

# 不借助临时变量,进行两个整数的交换

//方法一 ES6
var a = 1, b = 2;
[a,b] = [b,a];
console.log(a,b)

// 方法二 异或运算,同为0或者同为1都为0,10为1
var c = 3, d = 4;
c = c ^ d;
d = c ^ d;
c = c ^ d;
console.log(c,d)
1
2
3
4
5
6
7
8
9
10
11

# 查找字符串中出现最多的字符和个数

//查找出现最多的字符串的个数
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
 
 // 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"
//[...new Set('ababbc')].join('')// "abc" //上面的方法也可以用于,去除字符串里面的重复字符。
console.log(str);//aaabbbbbcccccccc

// 定义正则表达式;匹配多个字符串;
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {
    //console.log($0, "-------", $1);//cccccccc ------- c
    if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
});
console.log(str);//aaabbbbbcccccccc
console.log(num);//8
console.log(char);//c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

直接实现,不用正则;

function findMaxDuplicateChar(str) {
  if (str.length == 1) {
    return str;
  }
  let charObj = {};
  for (let i = 0; i < str.length; i++) {
    if (!charObj[str.charAt(i)]) {
      charObj[str.charAt(i)] = 1;
    } else {
      charObj[str.charAt(i)] += 1;
    }
  }
 // console.log(charObj);//{ a: 6, f: 2, j: 1, g: 1, h: 1, d: 2, r: 1, s: 2, e: 1, n: 1 }
  let maxChar = "",
    maxValue = 1;
  for (var k in charObj) {
    if (charObj[k] >= maxValue) {
      maxChar = k;
      maxValue = charObj[k];
    }
  }
  return maxChar;
}
var tmp = "afjghdfraaaasdenas";
console.log(findMaxDuplicateChar(tmp));//a 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 统计元素出现个数【推荐】

const nameArr = ['samy', 'zh_x', 'samy', 'samy', '科比']
const totalObj = nameArr.reduce((pre, next) => {
  if (pre[next]) {
    pre[next]++
  } else {
    pre[next] = 1
  }
  return pre
}, {})
console.log(totalObj) // { 'samy': 3, zh_x: 1, '科比': 1 }
1
2
3
4
5
6
7
8
9
10

# 实现超出整数存储范围的两个大整数相加

实现超出整数存储范围的两个大整数相加function add(a,b)。注意a和b以及函数的返回值都是字符串。

字符串处理,手动计算进位;

function addBigNum(a, b) {
  const n1 = a.split('').reverse();
  const n2 = b.split('').reverse();
  const len = Math.max(n1.length, n2.length);
  console.log(n1, n2, len);
  const sum = [];
  for (let i = 0; i < len; i++) {
      const tmp = Number(n1[i] || 0) + Number(n2[i] || 0);
      console.log('---tmp--', tmp);
      if (tmp + (sum[i] || 0) > 9) {
          sum[i] = (sum[i] || 0) + tmp - 10;
          sum[i + 1] = 1;
          console.log('->9--', sum[i], sum[i+1]);
      } else {
          sum[i] = (sum[i] || 0) + tmp;
          console.log('-<9--', sum[i]);
      }
      console.log(sum[i]);
  }
  console.log(sum);
  return sum.reverse().join('');
}

console.log(addBigNum(90+'', 2090+''))
// console.log(addBigNum(10000000000+'', 20000000000+''))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上次更新: 2022/04/15, 05:41:26
×