js相关基础
# 类型
# 基本类型
# 基本数据类型
# 【SSBNNU】 S
Symbol(ES6)、String、Boolean、Number、Null、Undefined; 简写:SSBNNU 栈(stack)
这些数据可以分为原始数据类型和引用数据类型:
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)
两种类型的区别是:存储位置不同。【S栈,引堆】
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。
# 内置对象
Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error
# 判断方式对比
# typeof
typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,*但不能判断null、array、object等,*但是 typeof null === 'object'
;
# typeof NaN 的结果是什么?
NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出 数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
typeof NaN; // "number"
console.log(typeof(NaN));//number
console.log(typeof(undefined));//undefined
console.log(typeof(null));//object
2
3
4
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN != NaN 为 true。
+0 === -0 //true NaN === NaN//false
Object.is(+0, -0) //false Object.is(NaN, NaN) //true
2
# instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。但它不能检测null 和 undefined; 基础类型没有 __proto__
# constructor
**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)
# 封装
//定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1) //获取从第九个到倒数第二个字符
}//targetType ==='Object'; 比如[object String]获取 String
2
3
4
# 不封装
function isObject (obj: any): Boolean {// 借鉴 Vue 源码的 object 检测方法
//typeof null === 'object';null 是基础类型,不是 Object,故要先排除这种情况;
return obj !== null && typeof obj === 'object'
}
//验证是否是对象或数组(前提是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]";
}
2
3
4
5
6
7
8
9
10
11
# ===
===能用于判断null和undefined,因为这两种类型的值都是唯一的
undefined === null; // false,类型不相同
undefined !== null; // true, 类型不相同
2
+0 === -0 //true NaN === NaN//false
Object.is(+0, -0) //false Object.is(NaN, NaN) //true
2
# 比较实践
# typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object【特殊】
console.log(typeof function(){}); // function
console.log(typeof {}); // object【特殊】
console.log(typeof undefined); // undefined【特殊】
console.log(typeof null); // object【特殊】
# instanceof 只能正确判断引用数据类型,而不能判断基本数据类型
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
# constructor 可以处理基本数据类型的检测;但它不能检测null 和 undefined
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
#
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 的引用
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
# 原理分析
# instanceOf原理
其内部的机制是通过判断在其原型链中能否找到该类型的原型
基础类型没有 __proto__
f
的隐式原型 __proto__
和 Foo.prototype
,是相等的,所以返回 true
。
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
2
3
// 实现 instanceof
function instanceof(obj, target) {
// 验证如果为基本数据类型,就直接返回 false
const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
if(baseType.includes(typeof(obj))) { return false }
obj = obj.__proto__// 获得对象的原型
while (true) {// 判断对象的类型是否等于类型的原型
// 如果__proto__ === null 说明原型链遍历完毕
if (obj === null) return false
// 如果存在 obj.__proto__ === target.prototype;说明对象是该类型的实例
if (obj === target.prototype) return true
obj = obj.__proto__// 原型链上查找
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如果用 instanceof 判断基础类型会怎么样?
let str = '123';
console.log(str instanceof String) // -> false; 因为基础类型没有 `__proto__`
2
但是如果更改了实现检查的话; 静态方法Symbol.hasInstance
就可以判断
class StringType {
static [Symbol.hasInstance](val) {
return typeof val === 'string'
}
}
console.log(str instanceof StringType) // -> true
2
3
4
5
6
# 引入原型链相关
# 应用
# 对象的深拷贝操作比较
# 简化版本比较
function simpleClone(obj) {//浅拷贝
let newObj = {};
for (let i in obj) {
newObj[i] = obj[i];
}
return newObj;
}
//方式一:直接用instancof/typeof判断;【推荐】简洁;
function deepClone(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]"
}
//定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1) //获取从第九个到倒数第二个字符
}//targetType ==='Object'; 比如[object String]获取 String
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
# 类型转换
# ==
与===
转换过程
# ==类型转换过程
- 如果类型不同,进行类型转换
- 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
- 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
- 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
- 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断
# == 操作符的强制类型转换规则
- 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
- 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
- null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
- 对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
- 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
- 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回 false。
# ==
与 ===
及Object.is()的区别
== : 只进行值的比较,会进行数据类型的转换。 === : 不仅进行值得比较,不进行转换。还要进行数据类型的比较。
==
与 ===
的区别在于: ==
运算发生的隐式类型转换,而 ===
在执行比较运算前不会发生隐式的类型转换
一言以蔽之:==
先转换类型再比较,===
先判断类型,如果不是同一类型直接为false
。
{a: 1} == "[object Object]" //true, 左边会执行 .toString()
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
2
# 隐式类型转换
首先要介绍ToPrimitive
方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。
如果值为基本类型,则直接返回值本身;
如果值为对象,其看起来大概是这样:ToPrimitive(obj,type)type
的值为number
或者string
。
(1)当type
为number
时规则如下:
- 调用
obj
的valueOf
方法,如果为原始值,则返回,否则下一步; - 调用
obj
的toString
方法,后续同上; - 抛出
TypeError
异常。
(2)当type
为string
时规则如下:
- 调用
obj
的toString
方法,如果为原始值,则返回,否则下一步; - 调用
obj
的valueOf
方法,后续同上; - 抛出
TypeError
异常。
可以看出两者的主要区别在于调用toString
和valueOf
的先后顺序。默认情况下:
- 如果对象为 Date 对象,则
type
默认为string
; - 其他情况下,
type
默认为number
。
所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。
类型先通过
valueOf
再toString
进行隐式转换;
let a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2);//true
2
3
4
5
6
7
8
# 基础隐式类型转换
JavaScript 中的隐式类型转换主要发生在+、-、*、/
以及==、>、<
这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive
转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive
转换成基本类型,所以最终还是要应用基本类型转换规则):
1:+
操作符
当+
操作符的两边有至少一个string
类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1
2
3
4
5
#
+
操作符什么时候用于字符串的拼接?
根据 ES5 规范 11.6.1 节,
- 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。
- 如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。
- 如果不能转换为字符串,则会将其转换为数字类型来进行计算。
简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
2:-
、\*
、\
操作符
这三个操作符是为数字操作而设计的,所以操作符两边的变量都会被转换成数字,注意NaN
也是一个数字
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN
2
3
3:==
操作符
操作符两边的值都尽量转成number
:
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 //true '0'转为number为0
2
3
4:对于<
和>
比较符
如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true
2
其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true
2
# 对象的隐式转换
对象会被ToPrimitive
转换为基本类型再进行转换:
示范一:
var a = {}
a > 2 // false
2
其对比过程如下:
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果
2
3
4
示范二:
var a = {name:'samy'}
var b = {age: 18}
a + b // "[object Object][object Object]"
2
3
运算过程如下:
a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"
2
3
4
5
# 显示/强制转换
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
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条件中的语句在运行时计算。
2
3
4
5
6
7
8
9
# 进制转换
# 规则方法
十进制转其他;
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
# ["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
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
2
3
4
5
6
# 精度相关
# isNaN 和 Number.isNaN 函数的区别?
- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
- 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,这种方法对于 NaN 的判断更为准确。
# 为什么 0.1 + 0.2 为什么不等于 0.3
js遵循IEEE 754 双精度版本(64位)
标准的语言都有的问题。计算机无法识别十进制,JS会将十进制转换为对应的二进制(二进制即:0
和 1
)。所以 0.1
在二进制表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
2
console.log(0.1.toString(2));
// -> 0.0001100110011001100110011001100110011001100110011001101
console.log(0.2.toString(2));
// -> 0.001100110011001100110011001100110011001100110011001101
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
2
另一种方案:把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完成后再进行降级(除以10的n次幂),即:
(0.1*10 + 0.2*10)/10 == 0.3 //true
# 解决 JS 的精确度问题
- 目前主流的解决方案是 先乘再除;
(0.1*10 + 0.2*10)/10 == 0.3 //true
- 比如精确到小数点后2位;.toFixed(8)
- 先把需要计算的数字都 乘1000
- 计算完成后再把结果 除1000
- 原生解决办法:
parseFloat((0.1 + 0.2).toFixed(10))
普通计算 - 使用新基础类型
BigInt
(兼容性很差) - 用第三方库;大型计算;
ps: toFixed()方法可把Number四舍五入为指定小数位数的数字。
# 变量
# let、const、var的区别
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
是否存在变量提升 | ✔️ | × | × |
是否添加全局属性 | ✔️ | × | × |
能否重复声明变量 | ✔️ | × | × |
是否存在暂时性死区 | × | ✔️ | ✔️ |
是否必须设置初始值 | × | × | ✔️ |
能否改变指针指向 | ✔️ | × |
# const对象的属性可以修改吗
const保证的并不是变量的值不得改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组),变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
# 变量/函数提升
函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。 变量提升是人为实现的问题,而函数提升在当初设计时是有目的的
ES6前:js没有块级作用域{}的概念。(有函数作用域、全局作用域、eval作用域) ; var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;
ES6后:let和const的出现,js有了块级作用域的概念。 在 ES6 中,let
和const
跟 var
、class
和function
一样也会被提升,只是在进入作用域和被声明之间有一段时间不能访问它们,这段时间是临时死区(TDZ)。报错提示:ReferenceError: aLet is not defined //TDZ区域
下面来看一下变量提升可能会导致的问题:
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
fn(); // undefined
2
3
4
5
6
7
8
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello world';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 11
2
3
4
5
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。
函数和变量重名的情况下;
var foo = 3;
function hoistFunction() {
console.log(foo); // function foo() {}
foo = 5;
console.log(foo); // 5
function foo() {}
}
hoistFunction();
console.log(foo); // 3
// 预编译之后
var foo = 3;
function hoistFunction() {
var foo;
foo = function foo() {};
console.log(foo); // function foo() {}
foo = 5;
console.log(foo); // 5
}
hoistFunction();
console.log(foo); // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#
# 正则
# 类数组
# 数组
# 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各自的特点是什么?
forEach
只能遍历数组,且不能中断(break等无效); 可查看后面forEach内部源码的实现;for in
遍历的是对象的可枚举属性;结合hasOwnPropery方法可以判断某属性是否是该对象的实例属性;for of
遍历的是对象的迭代器属性;
记住,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name
# 常用方法
栈方法 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类似;
# 其他遍历相关方法
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()//方法对大小写敏感
# 归并方法: reduce/reduceRight
对数组中的元素依次处理,将上次结果作为下次处理的输入,最后得到最终结果。接收一个函数作为累加器;
数组只有一项或是个空数组不做遍历操作; 如果归并的数组为空,则会报错;
# 遍历方法类似原理
# 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)));
});
2
3
4
5
6
7
8
9
10
# 常用算法
# 合并
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]
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]
2
3
4
5
# 判断是否包含值
1.终极篇
[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
2
3
4
includes(),find(),findIndex()是 ES6的api
2.开始篇
[1,2,3].some(item=>{
return item===3
}) //true 如果不包含返回false
2
3
# 最大值
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
Math.max()是Math对象内置的方法,参数是字符串;
# 求两数组中不同元素
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)))//差集
2
3
4
5
6
7
8