0%

new.target属性是ES6中新增的函数属性,new.target用于检测函数是否使用new关键字调用

如果函数是正常调用的,new.target的值为undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数

1
2
3
4
5
6
7
8
9
function King() {
if (!new.target) {
throw 'King must be instantiated unsing "new"!';
}
console.log('King instantiated unsing "new"!')
}

new King(); // King instantiated unsing "new"!
King(); // Error: King instantiated unsing "new"!

三个方法都是用来从字符串中提取子字符串的,因为是提取,三个方法都不改变原字符串

slice

语法

1
str.slice(startIndex, [endIndex])

参数

  • startIndex:从该索引开始
  • endIndex(可选):在该索引处结束提取字符串(不含),默认为字符串长度

返回值

返回提取出来的子字符串

substring

语法

1
str.substring(startIndex, [endIndex])

参数

  • startIndex:从该索引开始
  • endIndex(可选):在该索引处结束提取字符串(不含),默认为字符串长度

返回值

返回提取出来的子字符串

substr

语法

1
str.substr(startIndex, [length])

参数

  • startIndex:从该索引开始
  • length(可选):提取的字符数

返回值
返回提取出来的子字符串

区别

三者的主要区别在于面对0参数的处理上

  • slice方法会将所有负值参数都使用负值+length的方法进行处理
  • substr方法会将第一个负参数值使用负值+length的方法进行处理,第二个负参数转为0
  • substring方法会将所有的负参数转为0

回顾

  • 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)
  • 默认情况下,所有的原型的对象自动获得一个名为constructor的属性,指回与之关联的构造函数
  • 原型对象上包含由构造函数的实例共享的属性和方法
  • 在自定义构造函数时,原型对象默认只会获得constructor属性,其他的所有方法都继承自Object
  • 每次调用构造函数创建一个新实例,这个实例内部[[prototype]]指针就会被赋值为构造函数的原型对象,一般我们通过__proto__进行访问对象的原型
  • 每个函数都是Function类型的实例,所有的函数都继承或间接继承Function.prototype
  • Function也有属性和方法,跟其他引用类型一样

分析

分解1:构造函数 原型 实例之间的关系

这部分是最容易理解的,不再赘述,不理解的看这篇原型和原型链 | 理解原型和原型层级

分解2: Function.prototype的__proto__指向Object.prototype

因为Function.prototype本身是一个function类型(用typeof检测一下),Function.prototype本身是没有valueof属性的,需要从Object.prototype中继承

分解3:Object的__proto__指向 Function.prototype

Object是构造函数,在回顾中说道:**每个函数都是Function类型的实例,所有的函数都继承或间接继承Function.prototype**,所以Object作为构造函数的实例,Object__proto__指向Function.prototype就容易理解了

同理,Foo的proto指向 Function.prototype也就不奇怪了(Foo是构造函数)

分解4:Function的__proto__指向Function.prototype

这里可以这样理解Function的__proto__指向Function.prototype是为了保证原型链的完整性,让Function能够获得Object.prototype上的方法

参考

原型和原型链 | 理解原型和原型层级

HTTP状态码是非常重要的一个知识点,尤其是对于前端工作者来说,我们在发送网络请求获取资源的时候,经常返回不同的状态码,那么各种状态码都代表什么含义呢~让我们一探究竟吧!

类别 含义 描述
1xx Informational (信息性状态码) 接收的请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要进行附加操作继而完成请求
4xx Client Error(客户端错误状态码) 服务器无法处理请求
5xx Server Error(服务器错误状态码) 服务器请求错误

2XX(Sucess成功状态码)

  • 200 OK:客户端发来的请求被服务端正常处理了
  • 204 No Content:客户端发送的请求已经在服务端正常处理了,但是没有返回的内容,响应报文中不包含实体的主体部分(一般在只需要从客户端往服务端发送信息,而服务器端不需要往客户端发送内容时使用)
  • 206 Partial Conent:客户端进行了范围请求,而服务器端执行了这部分GET请求,响应报文中包含由Content-Range指定范围的实体内容

3XX(Redirection重定向状态码)

3xx响应结果表明浏览器需要执行某些特殊的处理以正确处理请求

  • 301 Moved Permanently(永久重定向):请求的资源已经被分配到新的URL,以后应使用资源指定的URL

  • 302 Found(临时重定向):请求的资源被分配到新的URL,希望用户(本次)能够使用新的URL访问资源

    • 登录首页自动重定向到活动页面
    • 未登录用户访问用户中心重定向到登录页面
    • 访问404后重定向到首页
  • 303 See Other:由于请求对应的资源存在着另一个URL,应使用GET方法重定向获取请求资源,和302类似,但是303状态码明确表示客户端应当采用GET获取资源(消息确认页面或者上传进度页面)

  • 304 Not Modified(浏览器缓存相关):客户端发送附带条件的请求时,服务器端允许请求访问资源,但未满足条件的情况,304状态码返回时,不包含任何响应的主体部分

  • 307 Temporary Redirect(临时重定向):和302类似

4XX(Client Error客户端错误状态码)

4xx响应结果表示客户端是错误所在

  • 400 Bad Request:请求报文中存在语法错误,当错误发生时,需修改请求的内容后,再次发送请求
  • 401 Unauthorized:发送的请求需要由通过HTTP认证的认证信息,若之前已进行过一次请求,则表示用户认证失败
  • 403 Forbidden:请求资源的访问被服务器拒绝
  • 404 Not Found:没有在服务器找到资源
  • 405 Method Not Allowed:客户端请求的方法虽然能被服务器识别,但是服务器禁止使用该方法

5XX(Server Error服务器错误状态码)

  • 500 Internal Server Error:服务器端在执行请求时发生了错误
  • 502 Bad Gateway:扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的
  • 503 Service Unavailable:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
  • 504 Gateway Timeout:网关或者代理的服务器无法在规定的时间内获得想要的响应

Method Mean
GET(获取资源) 发送一个请求获取服务器上的某一资源,返回实体主体
POST(传输实体主体) 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)
数据被包含在请求体中,POST 请求可能会导致新的资源的建立和/或已有资源的修改
PUT(传输文件) 从客户端向服务器传送的数据取代指定文档的内容
DELETE (删除文件) 请求服务器删除指定页面
OPTIONS (询问支持的方法) 查询针对请求URI指定的资源支持的方法
CONNECT(连接方式) HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器
TRACE(测试诊断) 回显服务器收到的请求,主要用于测试或诊断
PATCH(局部更新) 对已知资源的局部更新

GET 和 POST 的区别

  • 请求参数:GET请求参数是通过URL传递的,多个参数以&连接,POST请求放在request body中
  • 请求缓存:GET请求会被缓存,而POST请求不会,除非手动设置
  • 收藏为书签:GET请求支持,POST请求不支持
  • 安全性:POST比GET安全,GET请求在浏览器回退时是无害的,而POST会再次请求
  • 历史记录:GET请求参数会被完整保留在浏览历史记录里,而POST中的参数不会被保留
  • 编码方式:GET请求只能进行url编码,而POST支持多种编码方式
  • 对参数的数据类型:GET只接受ASCII字符,而POST没有限制

forEach

forEach方法:针对每一个元素执行提供的函数,没有返回值,直接修改原数组

1
2
3
4
let arr = [1,2,3];
arr.forEach((item, index, array) => {
// 执行某些操作
})

map

map方法:返回一个数组,其中数组的每一项都是对原始数组中同样位置的元素运行传入函数而返回的结果,并且仅对每一项已分配值得索引调用

1
2
3
let arr = [1,2,3];
let newArr = arr.map((item, index, array) => item*2);
console.log(newArr); // [2,4,6]

适用场景

  • map方法适用于创建一个与原数组元素一一对应的数组,map方法还可以进一步结合filterreduce
  • forEach方法适用于并不打算改变数据,而是想用数组的元素做一下操作的时候,比如打印,存入其他数组等

reduce相关语法

1
arr.reduce(callbackFn, [initalValue]);

callbackFn:reducer函数

包含四个参数:

  • previousValue:上一次调用callbackFn的返回值,在第一次调用的时候,如果指定了initalValue,则其值为initalValue,否则为数组索引为0的元素
  • currentValue:数组正在处理的元素,在第一次调用的时候,如果指定了initalValue,其值为数组索引为0的元素,否则为数组索引为1的元素
  • currentIndex:数组正在处理的元素的索引,如果指定了initalValue,起始索引为0,否则为1
  • array:用于遍历的数组

initalValue(可选):

  • 第一次调用时作为previousValue的值,这是一个可选的参数

注意点

  • reducer不会改变原数组
  • reducer中的callbackFn需要返回值(因为需要作为下一次调用的previousValue)

reduce源码实现

实现思路

  • 类型检验:回调函数是否对函数,数组是否为空等
  • 初始值提供检测,用于确定初始previousValue的值
  • 返回累计值

代码实现

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
56
57
58
59
Array.prototype.myReduce = function(callbackFn, initalValue) {
// Step1:类型检验
if (this === null) {
throw new TypeError('Array.prototype.reduce called on null or undefined');
};

if (typeof callbackFn !== 'function') {
throw new TypeError('Callback must be a function');
};

const obj = Object(this); // #1
const lenValue = obj.length;
const len = lenValue >>> 0; // #2
if (len === 0 && !initalValue) {
throw new TypeError('The array contains no elements and initalValue is not provided')
};

let k = 0;
let accumulator; // 这里变量名为accumulator十分贴合reduce函数作为累加器使用的性质
// Step2:初始accumulator的值确定
if (initalValue) {
// 有initalValue,accumulator就为初始值
accumulator = initalValue;
} else {
// 没有initalValue,就需要把数组的第一个值即arr[0]作为初始值
// 这里别着急,我们还需要做进一步检测
let kPressent = false; // #3
while (!kPressent && k < len) {
const pK = String(k);
kPressent = obj.hasOwnProperty(pK); // 第一个检测到合法的元素作为
if (kPressent) {
accumulator = obj[pK];
};
k++;
};
if (!kPressent) { // 数组没有合法元素,报错
throw new TypeError('The array contains error elements');
};
};

// 到这里我们已经确定了accumulator的值
// 注意:如果initalValue存在,k=0,如果不存在,则accumulator = 数组第一个合法元素,k因为之前也++了,k=k

// Step3:确定callback的返回值
while (k < len) {
if (k in obj){ // # 4
accumulator = callbackFn(accumulator, obj[k], k, obj);
};
k++;
};
return accumulator;
};



// test:
const arr = [1,2,3,4,5];
const sum = arr.myReduce((prev, curr) => prev+curr, 0);
console.log(sum); // 15

说明

  • #1:Object(this)

    Object构造函数作为一个工厂函数,能够根据传入的值的类型返回相应原始值包装类型的实例

    • null或者undefined:将会创建并返回一个空对象
    • 基本类型的值:构造其包装类型的对象
    • 引用类型的值:仍然返回这个值,经他们复制的变量保有和源对象相同的引用地址
  • #2:len = lenValue >>> 0

>>>表示无符号右移,为了保证结果为非负整数

  • #3: 获取数组第一个有效元素

    我觉得这里主要为了检测空值,虽然之前说了若是没有initalValue,previousValue就为array[0],但是若出现数组前面有空值或者全部为空值的情况,previous的取值是需要慎重的,所以初始化previousValue准确地说应该是数组中合法的第一个元素

  • #4:跳过空值(补课:空值检测以及reduce对存在空值的数组处理

reduce应用场景

累和&&累积

1
2
3
4
let arr = [1,2,3,4,5];

let sum = arr.reduce((prev, curr) => prev+curr, 0); // 15
let mul = arr.reduce((prev, curr) => prev*curr, 1); // 120

求最大值/最小值

1
2
3
4
let arr = [2,4,7,3,5,8,10,9];

let maxValue = arr.reduce((prev, curr) => Math.max(prev, curr)); // 10
let minValue = arr.reduce((prev, curr) => Math.min(prev, curr)); // 2

数组去重

1
2
3
4
5
6
7
8
let arr = [3,7,4,2,5,3,2,8];

let newArr = arr.reduce((prev, curr) => {
if (prev.indexOf(curr) === -1) {
prev.push(curr);
};
return prev;
}, []); // [3, 7, 4, 2, 5, 8]

实现map函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .map(callback)  callback = function(item, index, arr) {...}
Array.prototype.myMap = function(callback) {
if(typeof callback === 'function') {
return this.reduce((prev, item, index, arr) => {
prev.push(callback(item, index, arr));
return prev;
}, [])
} else {
throw new Error('callback is not a fucntion');
};
};

// test
let arr = [1,2,3,4,5];
let newArr = arr.myMap(item => item*2); // [2,4,6,8,10]

实现filter函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// .filter(callback)  callback(item, index, arr) {...}

Array.prototype.myFilter = function(callback) {
if (typeof callback === 'function') {
return this.reduce((prev, item, index, arr) => {
if (callback(item, index, arr)) {
prev.push(item);
};
return prev;
}, [])
} else {
throw new Error('callback is not a fucntion');
}
};

// test
let arr = [1,2,3,4,5];
let filterArr = arr.myFilter(item => item > 3); // [4,5]

实现compose函数

compose函数是指将函数按顺序执行,将若干个函数组合成一个函数来执行,并且每个函数执行的结果都能作为下一个函数的参数

假设有这样两个函数,一个求和函数,一个累积函数

1
2
3
4
5
6
7
function sum(value) {
return value += 20;
}

function mul(value) {
return value *= 10;
};

一般情况下,会这样使用:

1
2
3
4
let value = 10;
let res = sum(value);
res = mul(res);
console.log(res); // 300

若是有compose函数,将可以实现以下效果:

1
2
3
4
5
6
7
8
9
10
const fn = compose(sum, mul);
console.log(fn(value)); // 300

function compose() {
let args = [...arguments];

return function(x) {
return args.reduce((prev, curr) => curr(prev),x)
};
};

按顺序执行Promise

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
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input)
)
}

// promise function 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5)
})
}

// promise function 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2)
})
}

// function 3 - will be wrapped in a resolved promise by .then()
function f3(a) {
return a * 3
}

// promise function 4
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4)
})
}

const promiseArr = [p1, p2, f3, p4]
runPromiseInSequence(promiseArr, 10)
.then(console.log) // 1200

实现数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// flat(depth) 
function myFlat(arr, depth) {
if (depth < 1) return arr;
if (Array.isArray(arr)) {
return arr.reduce((prev, cur) => {
return Array.isArray(cur) ? prev.concat(myFlat(cur, depth-1)) : prev.concat(cur);
}, [])
} else {
throw new Error('this is not an array');
}
};

// test
const arr = [1, 2, [3,4, [5]], [6, 7], [8,[9, [10]]]]
myFlat(arr, 2) // [1, 2, 3,4, 5, 6, 7, 8,9, [10]]

统计数组元素出现次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function count(arr) {
return arr.reduce((prev, curr) => {
if (prev.has(curr)) {
prev.set(curr, prev.get(curr)+1);
} else {
prev.set(curr, 1);
};
return prev;
}, new Map())
};


// test
let arr = [1,2,3,1,2,4,5,6,5,6,1,2,3];
let m = count(arr); // {1=>3, 2=>3, 3=>3, 4=>1, 5=>5, 6=>2}

使用函数组合实现管道

1
2
3
4
5
6
7
8
9
10
function increment(input) { return input + 1;}
function decrement(input) { return input - 1; }
function double(input) { return input * 2; }
function halve(input) { return input / 2; }

let pipeline = [increment, double, decrement];

const result = pipeline.reduce(function(total, func) {
return func(total);
}, 8); // 17

知识补充

空值检测

原始值包装类型

参考

MDN reduce

你应该知道的JS: reduce的n种应用

6个关于Reduce() 应用场景的用例

JavaScript之Array.reduce源码解读

前情提要

举例1

之前在做算法题的时候遇到这样一个问题:我想创建一个n*m 的数组,于是我采用这样的方式

1
2
const n = 10, m = 10
const grid = Array(n).map(() => Array(m))

结果,grid打印的结果为[empty × 10],这说明map根本没起作用,阅读红宝书的时候发现在第四版141页数组空位的地方有说明,原话是“map()会跳过空位置”,用专业术语说,**map仅对每一项已分配值的索引调用**,所以要是我想实现我想要的效果,我应该写如下代码:

1
2
3
const n = 10, m = 10
const grid = Array(n).fill(0).map(() => Array(m))
// [Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10), Array(10)] length = 10

举例2

我们已经直到,map方法会跳过空位置,并且红宝书第四版P140页提到”ES6中普遍将空位当作存在的元素,值为undefined“,下面的例子证实了这个说法

1
2
let arr = [1,2,,4];
console.log(arr[2] === undefined); // true

再看下面的代码:

1
2
3
4
5
6
7
8
9
let arr1 = [1,2,,4];
let arr2 = [1,2,undefined,4];

let arr1_n = arr1.map(item => {
return 2
}); // [2,2,,2]
let arr2_n = arr2.map(item => {
return 2
}); // [2,2,2,2]

咦?undefined的位置没有被跳过,而空值显然被跳过了

于是引发了两个问题:

  1. 尽管空值位置的值是undefined,但是和undefined还是有本质的区别,究竟是怎么样的区别呢?
  2. map是怎么判断是空值还是undefined的呢?通过arr[index] === undefined显然是行不通的

要解答这两个问题,就要回归到上面说的**map仅对每一项已分配值的索引调用**,undefined数据基本数据类型,当然属于值

空位和undefined

产生空位的操作

以下操作均会产生空位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Array构造函数传一个数值表示数组长度
let arr1 = Array(5); // [,,,,]

// 数组字面量创建
let arr2 = [,,,,,];

// length属性
let arr3 = [];
arr3.length = 5; // [,,,,,]

// 通过索引增加数组元素是超过数组长度
let arr4 = [1,2,3];
arr4[10] = 5; // [1,2,3,,,,,,,,,5]

// 删除数组元素产生空位
let arr5 = [1,2,3];
delete arr5[1]; // [1,,3]

用in操作符或者hasOwnProperty()检测空值和undefined

1
2
3
4
5
6
7
8
9
0 in [undefined, undefined, undefined];   // true
0 in [,,,] // false


let a = [1,2,,4];
let o = Object(a);

o.hasOwnProperty(2); // false
o.hasOwnProperty(1); // true

说明[undefined, undefined, undefined]在索引0处有值,[,,,]在索引0处没有值

不同方法对空值的处理

忽略空值

map():跳过空位,但会保留这个值

forEach(), filter(), every()some()reduce():跳过空位

join()toString()Array.from展开运算符:会将空位视为空字符串

fill():将空位视为正常值

copyWithin():连着空位一起拷贝

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// map()
let arr = [1,,3];
let newArr = arr.map(item => item*2); // [2,,6];

// filter
let arr = [1,2,3,,,5,6];
arr.filter(item => item < 4); // [1,2,3];

// join
let arr = [1,,3];
arr.join('-') // 1--3

// fill
let arr = new Array(5).fill(0); // [0, 0, 0, 0, 0]

// copyWithin()
let arr = [1,2,3,4,,6,7];
arr.copyWithin(1, 3, 5); // [1,4,,4,,6,7]

// reduce()
let arr = [1,2,3,,4];
arr.reduce((prev, cur) => prev+cur); // 10

参考

JS中的数组空位和undefined

红宝书第四版P113:为了方便操作原始值,ECMAScript 提供了3 种特殊的引用类型:Boolean、Number 和String

装箱和拆箱

  • 装箱:将基本数据类型转换为对应的引用数据类型(分为隐式装箱和显式装箱)
    • 隐式装箱:当读取一个基本类型值时,后台会创建一个该基本类型所对应的基本包装类型对象
    • 显式装箱:通过 new 包装类型的方式进行转换
  • 拆箱:将引用数据类型转换为基本数据类型

看下面这个例子:

1
2
var s1 = 'someText';    // 后台偷偷操作了,隐式!
var s2 = s1.substring(2);

分析:

s1是一个保存字符串的变量,是一个原始值,在第二行调用了调用了substring()方法,并把结果保存在s2 中。原始值本身不是一个对象,照理说不应该有方法,但是事实确实调用方法成功了。实际上,当第二行访问s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下3 步:

  • 创建一个String 类型的实例;
  • 调用实例上的特定方法
  • 销毁实例

以上3步可抽象为下列代码:

1
2
3
var s1 = new String('someText');    // new 了, 显式!
var s2 = s1.substring(2);
s1 = null; // 这一步不可忽略,实例必须销毁

从上面的例子我们不难看出:

引用类型与原始值包装类型的主要区别在于对象的生命周期

具体来说:在通过new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间,这意味着不能在运行时给原始值添加属性和方法

1
2
3
let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined

创建原始值包装类型【装箱】的方式有:

  • 显式地使用Boolean、Number 和String 构造函数创建原始值包装对象
  • Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例
1
2
let obj = new Object("some text");
console.log(obj instanceof String); // true

需要注意的是:使用new 调用原始值包装类型的构造函数,与调用同名的转型函数并不一样(注意区分转型函数和构造函数

1
2
3
4
5
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number" 转型函数的类型检测为原始值类型
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object" 原始值包装类型检测为object

Boolean

Boolean 是对应布尔值的引用类型

创建Boolean对象

1
let booleanObject = new Boolean(true);

Boolean实例的变化

Boolean实例重写valueOf()方法,返回一个原始值true或者falsetoString()方法被调用时也会被覆盖,返回字符串''true''或者''false''

看下面这个例子:

1
2
3
4
5
6
7
8
9
let falseObject = new Boolean(false);     // 包装了一个值为false的Boolean对象
let res = falseObject && true;
console.log(res); // true

/*
对于res的打印结果,可能会有点懵
回顾下上面的内容,我们要知道typeof new Boolean(false) === 'object',在布尔表达式中,所有的对象都会转化为true
这里注意区分,原始布尔表达式中 false && true 等于 false,但本例子中 实际是 true && true 自然为 true
*/

Boolean对象与原始布尔值的区别

  • typeof返回不一样
1
2
3
4
5
let falseObject = new Boolean(false); 
let falseValue = false;

typeof falseObject; // object
typeof falseValue; // boolean
  • instanceof检测不一样
1
2
3
4
5
6
7
8
9
10
11
12
let falseObject = new Boolean(false); 
let falseValue = false;

typeof falseObject; // object
typeof falseValue; // boolean

falseObject instanceof Boolean // true
falseValue instanceof Boolean // false

/*
因为Boolean对象是Boolean类型的实例
*/

Number

Number 是对应数值的引用类型

创建Number对象

1
let numberObject = new Number(10);

Number实例的变化

Number实例重写valueOf()方法,toString()方法和toLocaleString()方法

valueOf()方法返回Number 对象表示的原始数值,另外两个方法返回数值字符串

toString()方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串

1
2
3
4
5
6
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

Number类型新方法

数值格式化为字符串

toFix()

toFixed()方法返回包含指定小数点位数的数值字符串,接受一个参数表示小数位数

1
2
let num = 10;
console.log(num.toFixed(2)); // "10.00"
toExponential()

toExponential(),返回以科学记数法(也称为指数记数法)表示的数值字符串,接受一个参数表示小数位数

toPrecision()

toPrecision()方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式

这个方法接收一个参数,表示结果中数字的总位数(不包含指数)

isInteger()

ES6 新增了Number.isInteger()方法,用于辨别一个数值是否保存为整数

.isSafeInteger()

IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从Number.MIN_SAFE_INTEGER(-2^53 + 1)到Number.MAX_SAFE_INTEGER(2^53 - 1)。对超出这个范围的数值,即使尝试保存为整数,IEEE 754 编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用Number.isSafeInteger()方法

Number对象与原始数值的区别

  • typeof返回不一样
1
2
3
4
5
let numberObject = new Number(10); 
let numberValue = 10;

typeof numberObject; // object
typeof numberValue; // number
  • instanceof检测不一样
1
2
3
4
5
6
7
8
9
10
11
12
let numberObject = new Number(10); 
let numberValue = 10;

typeof numberObject; // object
typeof numberValue; // number

numberObject instanceof Number // true
numberValue instanceof Number // false

/*
因为Number对象是Number类型的实例
*/

String

String 是对应字符串的引用类型

创建String对象

1
let stringObject = new String('hello world');

String实例

  • String 对象的方法可以在所有字符串原始值上调用
  • 3 个继承的方法valueOf()toLocaleString()toString()都返回对象的原始字符串值
  • 每个String对象都有一个length属性,表示字符串中字符的数

String类型方法

JavaScript字符

  • length:表示字符串中字符的数量
  • charAt():返回给定索引位置的字符,由传给方法的整数参数指定
1
2
let message = "abcde";
console.log(message.charAt(2)); // "c"
  • charCodeAt(): 可以查看指定码元的字符编码,这个方法返回指定索引位置的码元值,索引以整数指定
1
2
3
4
5
let message = "abcde";
// Unicode "Latin small letter C"的编码是U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制99 等于十六进制63
console.log(99 === 0x63); // true
  • fromCharCode(): 用于根据给定的UTF-16 码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串
1
2
3
4
5
6
7
8
9
10
11
12
// Unicode "Latin small letter A"的编码是U+0061
// Unicode "Latin small letter B"的编码是U+0062
// Unicode "Latin small letter C"的编码是U+0063
// Unicode "Latin small letter D"的编码是U+0064
// Unicode "Latin small letter E"的编码是U+0065
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"
  • codePointAt(): codePointAt()接收16 位码元的索引并返回该索引位置上的码点(code point)。码点是Unicode 中一个字符的完整标识
1
2
3
4
5
let message = "ab☺de";
console.log(message.codePointAt(1)); // 98
console.log(message.codePointAt(2)); // 128522
console.log(message.codePointAt(3)); // 56842
console.log(message.codePointAt(4)); // 100
  • fromCodePoint(): 方法接收任意数量的码点,返回对应字符拼接起来的字符串
1
2
console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // ab☺de
console.log(String.fromCodePoint(97, 98, 128522, 100, 101)); // ab☺de

字符串规范化方法

  • normalize(): 通过比较字符串与其调用normalize()的返回值,就可以知道该字符串是否已经规范化

字符串拼接方法

  • concat(): 用于将一个或多个字符串拼接成一个新字符串,不改变原字符串
1
2
3
4
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
  • 加号操作符(+): 当加号操作符有一方为字符串时,则执行字符串拼接

字符串提取方法

  • slice()
  • substr()
  • substring()

字符串slice substr substring的区别

字符串位置方法

  • indexOf()
  • lastIndexOf()

这两个方法从字符串中搜索传入的字符串,并返回位置,如果没找到,则返回-1

两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串

1
2
3
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7

字符串包含方法

  • startsWith()
  • endsWith()
  • includes()

这些方法都会从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值

区别在于,startsWith()检查开始于索引0 的匹配项,endsWith()检查开始于索引(string.length - substring.length)的匹配项,而includes()检查整个字符串

startsWith()和includes()方法接收可选的第二个参数,表示开始搜索的位置,如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符

1
2
3
4
5
6
7
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false

字符串删除空格

  • trim(): 这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果,不影响原字符串
1
2
3
4
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
  • trimLeft()
  • trimRight()

trimLeft()和trimRight()方法分别用于从字符串开始和末尾清理空格符

字符串重复方法

  • repeat(): 这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
1
2
3
let stringValue = "na ";
console.log(stringValue.repeat(16) + "batman");
// na na na na na na na na na na na na na na na na batman

字符串填充方法

  • padStart()
  • padEnd()

这两个方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件

这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格

1
2
3
4
5
let stringValue = "foo";
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"
console.log(stringValue.padEnd(6)); // "foo "
console.log(stringValue.padEnd(9, ".")); // "foo......"

可选的第二个参数并不限于一个字符,如果提供了多个字符的字符串,则会将其拼接并截断以匹配指定长度

此外,如果长度小于或等于字符串长度,则会返回原始字符串

1
2
3
4
5
let stringValue = "foo";
console.log(stringValue.padStart(8, "bar")); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"
console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"

字符串迭代与解构

  • @@iterator: 表示可以迭代字符串的每个字符
  • for-of: 在for-of 循环中可以通过这个迭代器按序访问每个字符
  • 解构操作符: 字符串可以通过解构操作符来解构

字符串大小转换

  • toUpperCase()
  • toLocaleLowerCase()
  • toLowerCase()
  • toLocaleUpperCase()

字符串模式匹配方法

  • match(): 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象
1
2
3
4
5
6
7
let text = "cat, bat, sat, fat";
let pattern = /.at/;
// 等价于pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0
  • search(): 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象,返回模式第一个匹配的位置索引,如果没找到则返回1
1
2
3
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
  • replace(): 方法接收两个参数,第一个参数可以是一个RegExp 对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记
1
2
3
4
5
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"
  • htmlEscape(): 将一段HTML 中的4 个字符替换成对应的实体:小于号、大于号、和号,还有双引号(都必须经过转义)
  • split(): 会根据传入的分隔符将字符串拆分成数组
1
2
3
4
let colorText = "red,blue,green,yellow";
let colors1 = colorText.split(","); // ["red", "blue", "green", "yellow"]
let colors2 = colorText.split(",", 2); // ["red", "blue"]
let colors3 = colorText.split(/[^,]+/); // ["", ",", ",", ",", ""]

字符串比较方法

  • localeCompare(): 比较两个字符串,返回如下3 个值中的一个
    • 如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值,一般为-1
    • 如果字符串与字符串参数相等,则返回0
    • 如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值,一般为1
1
2
3
4
let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1

创建数组

(1)new Array() 【ES5】

创建数组可以通过Array构造函数的方式构造数组 (new 操作符可以省略)

1
2
let colors = new Array('red', 'green', 'yellow'); 
console.log(colors); // ['red', 'green', 'yellow']

特殊:当传入一个数值的时候,会创建一个指定数组的数组,用逗号创建的数组空位,值为undefined(关于数组空位,发现一些好玩的,请看这篇博文

1
2
3
let arr = Array(10);  // 这里省略了new操作符
console.log(arr); // [empty × 10]
console.log(arr.length); // 10

(2)Array.of() 【ES6】

Array.of()创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型

Array.of()代替了之前用Array.prototype.slice.call(arguments)转数组的笨拙写法

Array.of()Array构造函数的区别在于处理单个数组的情况

1
2
Array(10);   // [empty × 10]
Array.of(10); // [10]

(3)Array.from() 【ES6】

Array.from()创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型

语法(注意:容易被忽略)

1
Array.from(arrayLike, mapFn, thisArg);

参数

  • arrayLike:类数组或者可迭代对象(如Map,Set等)或者有一个length属性和可索引元素的结构
  • mapFn(可选):用于增强数组元素的回调函数
  • thisArg(可选):执行回调时的this对象

举例

1
2
3
4
5
6
7
8
9
10
11
// 第二个参数的妙用
let arrLike = {
'0': 1,
'1': 2,
'2': 3,
'3': 4,
length: 4,
};

let arr = Array.from(arrLike, item => item * 3);
console.log(arr); // [3,6,9,12]

Array.from()对现有数组是浅拷贝

1
2
3
let arr1 = [5,6,7,8];
let arr2 = Array.from(arr1);
console.log(arr1 === arr2); // false

数组索引

(1)中括号索引法

1
2
let colors = ['red', 'green', 'yellow'];
console.log(colors[1]); // green

(2)Array.length()

Array.length()返回或设置一个数组中的元素个数

1
2
let colors = ['red', 'green', 'yellow'];
console.log(colors.length); // 3

注意:数组的length并不只是可读的,而是可以通过修改length属性,从数组的末尾删除或者添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 删除
let colors = ['red', 'green', 'yellow'];
colors.length = 2;
console.log(colors); // ['red', 'green']

// 添加
let colors = ['red', 'green', 'yellow'];
colors.length = 4;
console.log(colors[3]); // undefined

// 使用length属性可以方便为数组末尾添加元素
let colors = ['red', 'green', 'yellow'];
colors[colors.length] = 'pink';
console.log(colors); // ['red', 'green', 'yellow', 'pink']

检测数组

(1)Array.isArray()

1
2
Array.isArray([1,2,3]);   // true
Array.isArray({name: 'Katrina'}); // false

思考:检测一个对象是否是数组的方法有?

迭代器方法

(1)Array.prototype.values() 【ES6】

values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值

(2)Array.prototype.keys() 【ES6】

keys() 方法返回一个包含数组中每个索引键的Array Iterator对象

(3)Array.prototype.entries() 【ES6】

entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const a = ['foo', 'bar', 'baz', 'qux'];

const aValues = Array.from(a.values());
const aKeys = Array.from(a.keys());
const aEntries = Array.from(a.entries());

console.log(aValues); // ['foo', 'bar', 'baz', 'qux']
console.log(aKeys); // [0, 1, 2, 3]
console.log(aEntries); // [[0, 'foo'], [1, 'bar'], [2, 'baz'], [3, 'qux']]


// 利用解构赋值可以很容易拆分键值对
for (let [key, value] of aEntries) {
console.log(key, value);
};

/*
0 'foo'
1 'bar'
2 'baz'
3 'qux'
*/

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// entries()
Array.prototype.myEntries = function(arr) {
let res = [];

for (let i = 0; i < this.length; i++) {
res.push([i, this[i]])
};
return res;
};


// test
const a = ['foo', 'bar', 'baz', 'qux'];
console.log(a.myEntries());

/*
[[0, 'foo'], [1, 'bar'], [2, 'baz'], [3, 'qux']]
*/

复制和填充

(1)批量复制:copyWithin() 【ES6】

copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度

语法

1
arr.copyWithin(target, start, end)

参数

  • target:从这个位置开始填充
  • start(可选):填充的内容从这个位置开始截取
  • end(可选):填充的内容到这个位置截取完毕(不包含end)

注意

  • 负索引会被计算成负索引+length

  • copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围

举例

1
2
let arr = [0,1,2,3,4,5];
arr.copyWithin(0, 2, 4); // [2,3,2,3,4,5]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
需要注意:
1. copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围
2. 不会改变原数组的长度

*/
Array.prototype.myCopyWithin = function(target, start, end) {
target < 0 ? target +length : target;
start < 0 ? start +length : start;
end < 0 ? end +length : end;

const arr = this;
for (let i = start; i < end; i++) {
arr[target] = arr[i]
};
return arr;
};

// test
let arr = [0,1,2,3,4,5];
arr.copyWithin(0, 2, 4); // [2,3,2,3,4,5]

(2)填充数组:fill() 【ES6】

fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素,不包括终止索引,会改变原数组

语法

1
arr.fill(value, start, end)

参数

  • value:用于填充数组的元素
  • start:起始索引,默认为0
  • end:终止索引,默认为this.length

注意

  • 负索引会被计算成负索引+length

  • fill()静默忽略超出数组边界、零长度及方向相反的索引范围

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = [1,2,3,4,5,6,7];

// 索引过低忽略
arr.fill(0, -3, 4);
console.log(arr); // [1,2,3,4,5,6,7]

// 索引过高忽略
arr.fill(0, 20, 24);
console.log(arr); // [1,2,3,4,5,6,7]

// 索引反向忽略
arr.fill(0, 6, 3);
console.log(arr); // [1,2,3,4,5,6,7]

// 索引部分可用,填充可用部分
arr.fill(0, 3, 20);
console.log(arr); // [1,2,3,0,0,0,0]

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
需要注意:
1. fill()静默忽略超出数组边界、零长度及方向相反的索引范围
2. 会改变原数组
*/

Array.prototype.myFill = function(value, start, end) {
start < 0 ? start + length : start;
end < 0 ? end + length : end;
end > this.length ? this.length : end;
for (let i = start; i < end; i++) {
this[i] = value;
};

return this;
};

// test
let arr = [1,2,3,4,5,6,7];
arr.fill(0, 3, 20);
console.log(arr); // [1,2,3,0,0,0,0]

转换方法

(1)toString() && toLocaleString() && valueOf()

valueOf()返回数组本身

toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串

toLocaleString()返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 “,”)隔开

举例

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
let colors = ['red', 'green', 'yellow'];

colors.toString(); // 'red,green,yellow'
colors.valueOf(); // ['red', 'green', 'yellow']

alert(colors.toString()) // 'red,green,yellow'
alert(colors.valueOf()) // 'red,green,yellow'
alert(colors) // 'red,green,yellow'

/*
这里被显式调用toString()方法和valueOf()方法,分别返回数组的字符串表示
最后alert(colors),因为alert期待字符串,所以调用了toString()方法
*/


let person1 = {
toLocaleString() {
return "Nikolaos";
},
toString() {
return "Nicholas";
}
};
let person2 = {
toLocaleString() {
return "Grigorios";
},
toString() {
return "Greg";
}
};
let people = [person1, person2];
alert(people); // Nicholas,Greg 调用toString()
alert(people.toString()); // Nicholas,Greg 调用toString()
alert(people.toLocaleString()); // Nikolaos,Grigorios 调用toLocaleString()

(2)join()

join()返回以指定分隔符分隔数组元素的字符串

1
2
let colors = ['red', 'green', 'yellow'];
console.log(colors.join('-')); // 'red-green-yellow'

注意:如果数组中某一项是null或者undefined,则返回值会以空字符串表示

手写实现

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
/*
需要注意:
1. separator默认为逗号分隔
2. 如果数组中某一项是``null``或者``undefined``,则返回值会以空字符串表示
*/
Array.prototype.myJoin = function(separator = ',') {
let res = '';

for (let i = 0; i < this.length; i++) {
this[i] = this[i] === undefined || this[i] === null ? '' : this[i];
if (i < this.length - 1) {
res += (this[i] + separator);
} else if (i === this.length - 1) {
res += this[i];
};
};

return res;
};


// test
const a = [1,,3,4,undefined,6];
console.log(a.myJoin()); // 1,,3,4,,6
console.log(a.myJoin('-')); // 1--3-4--6

栈和队列方法

(1)push() 【ES5】

向数组末尾添加元素,返回修改后数组的长度

手写实现

1
2
3
4
5
6
7
8
9
10
/*
需要注意:返回的是修改后数组的长度
*/
Array.prototype.myPush = function() {
return [...this, ...arguments].length;
};

// test
let a = [1,2,4,5,6];
console.log(a.myPush(8,9)); //7

(2)pop() 【ES5】

删除数组的最后一项,同时减少数组的length值,返回被删除的元素

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
需要注意:
1. 要减少数组的length值
2. 返回被删除的元素
3. 数组长度为0时返回undefined
*/
Array.prototype.myPop = function() {
if (this.length === 0) return undefined;
const res = this[this.length - 1];
this.length = this.length - 1;

return res;
};

let a = [1,2,4,5,6];
console.log(a.myPop()); // 6
console.log(a.length); // 4

(3)shift() 【ES5】

删除数组的第一项,同时减少数组的length值,返回被删除的元素

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
需要注意:
1. 要减少数组的length值
2. 返回被删除的元素
3. 数组长度为0时返回undefined
*/
Array.prototype.myShift = function() {
if (this.length === 0) return 0;
const res = this[0];
for (let i = 1; i < this.length; i++) {
this[i-1] = this[i];
};
this.length = this.length - 1;
return res;
};

let a = [1,2,4,5,6];
console.log(a.myShift()); // 1
console.log(a); // [2,4,5,6]

(4)unshift() 【ES5】

在数组开头添加元素,返回修改后数组的长度

手写实现

1
2
3
4
5
6
7
8
9
10
/*
需要注意:返回的是修改后数组的长度
*/
Array.prototype.myUnshift = function() {
return [...arguments, ...this].length;
};

// test
let a = [1,2,4,5,6];
console.log(a.myUnshift(8,9)); //7

排序方法

补课啦:常见的排序算法及JS实现

(1)数组排序:sort() 【ES5】

sort() 方法用原地算法(即不创建额外的空间)对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的

语法

1
arr.sort(compareFunction)

参数

  • compareFunction(可选):用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的 Unicode 位点进行排序
1
2
3
4
5
6
7
8
9
function compareFunction(value1, value2) {
if (value1 < value2) {
return -1; // 负值,value1排在value2前面
} else if (value1 > value2) {
return 1; // 正值,value1排在value2后面
} else {
return 0;
}
};

(2)翻转数组:reverse() 【ES5】

reverse() 方法将数组中元素的位置颠倒,并返回该数组,该方法会改变原数组

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
改变原数组
*/
Array.prototype.myReverse = function() {
if (this.length <= 1) return this;
let left = 0, right = this.length -1;
while (left <= right) {
[this[left], this[right]] = [this[right], this[left]];
left++;
right--;
};
return this;
};

// test
const arr = ['h', 'e', 'l', 'l', 'o'];
console.log(arr.myReverse()); // ['o', 'l', 'l', 'e', 'h']

操作方法

(1)合并数组:concat() 【ES5】

concat() 方法用于合并两个或多个数组,此方法会首先创建一个当前数组的副本,然后再把参数添加到副本末尾,所以不会更改现有数组,而是返回一个新数组

语法

1
var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

参数

  • valueN:可以是数组或者值

注意

可以使用Symbol.isConcatSpreadable来控制传入的类数组对象是否强制打平,true为强制打平

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let a1 = [1,2,3];
let a2 = {
[Symbol.isConcatSpreadable]:true,
length: 2,
0: 4,
1: 5,
};
let a3 = {
[Symbol.isConcatSpreadable]:false,
length: 2,
0: 4,
1: 5,
};

// 强制打平
let a1_2 = a1.concat(a2);
console.log(a1_2); // [1,2,3,4,5]

// 不强制打平
let a1_3 = a1.concat(a3);
console.log(a1_3); // [1,2,3, {[Symbol.isConcatSpreadable]:false,length: 2,0: 4,1: 5}]

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
注意:此方法会创建一个当前数组的副本,并不会修改原数组
*/
Array.prototype.myConcat = function() {
let args = [...arguments];
let res = this;

for (let i = 0; i < args.length; i++) {
if (typeof args[i] === 'object') {
res = [...res, ...args[i]];
} else {
res[res.length] = args[i];
}
};
return res;
};


// test
const a1 = [1,2,3];
const a2 = [5,6];
console.log(a1.myConcat(4, a2)); // [1,2,3,4,5,6]

(2)截取数组:slice() 【ES5】

slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end),原始数组不会被改变

语法

1
arr.slice(start, end);

参数

  • start(可选):起始索引,默认为0
  • end(可选):终止索引(不含),默认为this.length

注意

  • 遇到负索引需要进行索引+length处理
  • start超过原数组的索引范围返回空数组
  • end大于原数组的长度,则截取到原数组的末尾

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Array.prototype.mySlice = function(start, end) {
start > 0 ? start : start +length;
end > 0 ? end : end +length;
end > this.length ? this.length : end;

let res = [];
for (let i = start; i < end; i++) {
res.push(this[i]);
};

return res;
};

// test
const arr = ['h', 'e', 'l', 'l', 'o'];
console.log(arr.mySlice(2,4)); // ['l', 'l'];
console.log(arr); // ['h', 'e', 'l', 'l', 'o']

(3)插入元素:splice() 【ES5】

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容,此方法会改变原数组

语法

1
arr.splice(start, deletcount, item1, item2,...)

参数

  • start:起始位置
  • deletecount(可选):要移除数组元素的个数,0或者负数表示不移除
  • item1 item2(可选):要添加的元素

返回值

由被删除的元素组成的一个数组

如果只删除了一个元素,则返回只包含一个元素的数组

如果没有删除元素,则返回空数组

方法

  • 删除:传2个参数
  • 插入:传3个参数
  • 替换:传3个参数

手写实现

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
/*
改变原数组
*/
Array.prototype.mySlice = function() {
let args = [...arguments];
let start = Array.prototype.shift.call(args);
let deletecunt = Array.prototype.shift.call(args);

let left = this.slice(0, start); // 左边数组
let right = this.slice(start+deletecount, this.length); // 右边数组

let res = [...left, ...atgs, ... right];

// 改变原数组
for (let i = 0; i < res.length; i++) {
this[i] = res[i];
};

return this.slice(start, start + deletcount); // 返回删除的数据
};

// test
var myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];
var removed = myFish.splice(3, 1);
console.log(removed); // ['mandarin']


var myFish = ['angel', 'clown', 'drum', 'sturgeon'];
var removed = myFish.splice(2, 1, "trumpet");
console.log(removed); // ['drum]

搜索和位置方法

(1)按严格相等搜索:indexOf() 【ES5】 && lastIndexOf() 【ES5】 && inclues() 【ES6】

indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1

lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1,从数组的后面向前查找,从 fromIndex 处开始

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

注意

  • 三者都可以指定fromIndex,即从哪一项开始寻找
  • 三者都采用严格相等搜索,即===比较
  • 找到目标之后不会继续往下找

手写实现

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
/*
严格相等
*/
// indexOf()
Array.prototype.myIndexOf = function(num, fromIndex = 0) {
for (let i = fromIndex; i < this.length; i++) {
if (this[i] === num) {
return i;
};
};
return -1;
};

// test
const arr = [1,2,3,4,5,6];
console.log(arr.myIndexOf(2)) // 1
console.log(arr.myIndexOf(2, 4)) // -1


// includes()
Array.prototype.myIncludes = function(num, fromIndex = 0) {
for (let i = fromIndex; i < this.length; i++) {
if (this[i] === num) {
return true;
};
};
return false;
};

// test
const arr = [1,2,3,4,5,6];
console.log(arr.myIncludes(2)) // true
console.log(arr.myIncludes(2, 4)) // false

(2)按断言函数搜索:find() && findIndex() 【ES6】 ALL

find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回undefined

findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引,若没有找到对应元素则返回-1

注意

  • 两者都采用断言函数搜索,断言函数接收3个参数:元素 索引 数组本身
  • 因此两个的参数都是一个回调函数
  • 找到符合条件的元素后就不会再继续

手写实现

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
Array.prototype.myFind = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
}

const arr = this;
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i], i, arr)) {
return arr[i];
};
};

return undefined;
};

// test
const arr = [1,2,3,4,5,6];
const res = arr.myFind((item, index, arr) => item > 4);
console.log(res, arr); // 5 [1,2,3,4,5,6]



// findIndex()
Array.prototype.myFindIndex = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
}

const arr = this;
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i], i, arr)) {
return i;
};
};

return -1;
};

迭代方法

(1)every() 【ES5】

every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回一个布尔值

举例

1
2
3
4
5
6
7
let arr = [3,4,5,6,7];
arr.every(item => item > 2); // true
arr.every(item => item > 4); // false

/*
特点:必须每一项元素都满足条件
*/

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Array.prototype.myEvery = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
};

for (let i = 0; i < this.length; i++) {
if (!callback(this[i], i, this)) {
return false;
}
};
return true;
};

// test
let arr = [3,4,5,6,7];
arr.myEvery(item => item > 2); // true
arr.myEvery(item => item > 4); // false

(2)some() 【ES5】

some() 方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试,它返回一个布尔值

举例

1
2
3
4
5
6
7
let arr = [3,4,5,6,7];
arr.some(item => item > 2); // true
arr.some(item => item > 4); // true

/*
特点:只要有一项元素满足即可
*/

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Array.prototype.mySome = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
};

for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
return true;
}
};
return false;
};

// test
let arr = [3,4,5,6,7];
arr.mySome(item => item > 4); // true
arr.mySome(item => item > 8); // false

(3)filter() 【ES5】

filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素

举例

1
2
let arr = [3,4,5,6,7];
arr.filter(item => item > 5); // [ 6, 7]

手写实现

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
// 迭代法
Array.prototype.myFilter = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
};

let res = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
res.push(this[i]);
};
};
return res;
};

let arr = [3,4,5,6,7];
arr.myFilter(item => item > 5); // [ 6, 7]

// 借助reduce
Array.prototype.myFilter = function(callback) {
if (typeof callback === 'function') {
return this.reduce((prev, item, index, arr) => {
if (callback(item, index, arr)) {
prev.push(item);
};
return prev;
}, [])
} else {
throw new Error('callback is not a fucntion');
}
};

// test
let arr = [3,4,5,6,7];
arr.myFilter(item => item > 5); // [ 6, 7]

(4)forEach() 【ES5】

forEach() 方法对数组的每个元素执行一次给定的函数,没有返回值,改变原数组

举例

1
2
3
let arr = [3,4,5,6,7];
arr.forEach(item => item*2);
console.log(arr); // [6,8,10,12,14]

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
注意: 改变原数组

*/
Array.prototype.myForEach = function(callback, thisArg) {
const arr = this;

for (let i = 0; i < arr.length; i++) {
callback.call(thisArg, arr[i]);
};
};

const obj = {
num: 10
};
const arr = [1, 2, 3, 4, 5, 6];

arr.myForEach(function (value, index, arr) {
console.log(value + this.num); // 依次打印:11 12 13 14 15 16
}, obj);
console.log(arr); // [1, 2, 3, 4, 5, 6]

(5)map() 【ES5】

map() 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成

举例

1
2
3
let arr = [3,4,5,6,7];
let newArr = arr.map(item => item*2);
console.log(newArr); // [6,8,10,12,14]

注意

  • map仅对每一项已分配值得索引调用
1
2
let a = [1,2,,4];
let newA = a.map(item => item*2); // [2,4,,8]

手写实现

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
// 迭代法
Array.prototype.myMap = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
};
let arr = this;
let res = [];

for (let i = 0; i < arr.length; i++) {
let value = callback(arr[i], i, arr);
res.push(value);
};
return res;
};

// test
let a = [1,2,4];
let newA = a.myMap(item => item*2); // [2,4,8]


// reduce改进
Array.prototype.myMap = function(callback) {
if (typeof callback !== 'function') {
throw new Error('callback must be a function');
};

let arr = this;
return arr.reduce((prev, curr, index, arr) => {
prev.push(callback(curr, index, arr));
return prev;
}, [])
};

// test
let a = [1,2,4];
let newA = a.myMap(item => item*2); // [2,4,8]

归并方法

(1)reduce() 【ES5】

reduce()接收两个参数:归并函数和归并起点的初始值

其中归并函数接收四个参数:上一个归并值、当前项、当前项的索引、数组本身。归并函数返回的任何值都会成为下一次调用同一个函数的第一个参数,即归并值,如果没有归并起点的初始值,则把第一个元素作为初始值,迭代从第二个元素开始

reduce的应用详见:强大的reduce

手写实现

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
Array.prototype.myReduce = function(callbackFn, initalValue) {
// Step1:检验
if (this === null) {
throw new Error('Array.prototype.reduce called on null or undefined');
};
if (typeof callbackFn !== 'function') {
throw new Error('Callback must be a function');
};

let obj = Object(this);
const lenValue = obj.length;
const len = lenValue >>> 0;

if (!len && !initalValue) {
throw new TypeError('The array contains no elements and initalValue is not provided')
};

// Step2: 确定accumulator初始值
let k = 0;
let accumulator;

if (initalValue) {
accumulator = initalValue;
} else {
let kPressent = false;
while (!kPressent && k < len) {
const pK = String(k);
kPressent = obj.hasOwnProperty(pK); // 是否有效
if (kPressent) {
accumulator = obj[pK];
};
k++;
};
if (!kPressent) {
throw new TypeError('The array contains error elements');
};
};

// Step3:确定返回值
while (k < len) {
if (k in obj) {
accumulator = callbackFn(accumulator, obj[k], k, obj);
};
k++;
};
return accumulator;
};

// test
const arr = [1,2,3,4,5];
const sum = arr.myReduce((prev, curr) => prev+curr, 0);
console.log(sum); // 15

(2)reduceRight() 【ES5】

reduceRight()reduce()唯一不同的就是遍历方向是从最后一项到第一项,其余全部相同

其他

(1)数组扁平化:flat() 【ES6】

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

语法

1
arr.flat([depth])

参数

  • depth(可选):指定要提取嵌套数组的深度,默认为1
1
2
3
4
5
6
7
8
9
10
11
function myFlat(arr, depth = 1) {
if (depth < 1) return arr;
return arr.reduce((prev, curr, index, arr) => {
return Array.isArray(curr) ? prev.concat(myFlat(curr, depth - 1)) : prev.concat(curr);
}, [])
};

// test
const arr = [1, 2, [[3,4]]]
myFlat(arr, 1) // [1, 2, [3,4]]
myFlat(arr, 2) // [1, 2, 3, 4]

总结

(1)改变原数组方法

push

unshift

pop

shift

reverse

splice

sort

(2)ES6新增方法

Array.from

Array.of

copyWithin()

fill()

find()

findIndex()

entries()

keys()

values()

includes()

flat()

知识补充

数组空位

map和forEach的区别

强大的reduce

深浅拷贝

参考

MDN Array