数组
1. 数组基础
数组所有方法汇总:
堆栈方法:push pop shift unshift
通用方法:toString valueOf
拼接的方法:concat join
构建的方法:new Array('10') Array.from() Array.of()
删除的方法:splice slice
排序的方法:sort reverse
索引相关:indexOf lastIndexOf includes find findIndex
迭代:forEach map filter reduce reduceRight some every
entries keys values
声明数组的三种方法: 所有数组都继承于Array.prototype
var arr1 = []; //数组字面量
var arr2 = new Array(); // 通过系统内置的Array构造函数声明数组(不推荐)
var arr3 = Array(); // 不使用new
数组对象底层机制转换: 对象模拟数组
var arr = [1,2,3,4,5];
var obj = {
0:1,
1:2,
2:3,
3:4,
4:5
};
console.log(arr[2]); //3
console.log(obj[2]); //3
var obj1 = {
name: 'a'
};
obj1.name -> obj1['name']
稀松数组: 数组最后一位有逗号没有用,其他地方都有用
var arr = [,,];
console.log(arr.length); //2
new Array(1,3,5,,7)参数不能有多余逗号
var arr1 = new Array(1,3,5,,7);// 报语法错误,最后一个参数后面加逗号不报错
设置arr1的长度(元素)
var arr1 = new Array(5);//[空属性 × 5]
var arr1 = new Array('a'); // ["a"]
数组就是对象的另一种形式
var arr = [1,2,3];
console.log(arr[3]); // undefined
var obj = {
0:1,
1:2,
2:3
};
console.log(obj[3]);
arr[3] = 'b';
arr[1] = 'a';
console.log(arr); // [1,"a",3,"b"]
2. 数组方法(Array.prototype查看所有方法)
2.1 数组实现增删改的方法
这部分都会修改原有的数组
2.1.1 push
向数组末尾增加内容
返回新增后数组的长度
let ary = [10, 20]
let res = ary.push(30,"AA",{name:'小王'});
console.log(res, ary);
原理: 基于原生js操作键值对的方法,也可以向末尾追加一项新的内容
ary[ary.length] = 40;
console.log(res,ary);//4 [10,20,30,"AA",40]
重写push
Array.prototype.myPush = function(){
for(var i=0; i< arguments.length; i++){
this[this.length] = arguments[i];
}
return this.length;
}
arr.myPush(1,2,3);// 多个
2.1.2 unshift
向数组开头加
返回新增后数组的长度
let ary = [10,20];
let res = ary.unshift(30,"AA");
console.log(res,ary);//4 [30,'AA',10,20]
原理: 基于原生ES6展开运算符,把原有的ary克隆一分,在新的数组中创建第一项,其余的内容使用原始ary中的信息即可,也实现了向开始追加的效果
ary = [100,...ary];
console.log(ary);//[100,30,'AA',10,20]
重写unshift 方法一:
Array.prototype.myUnshift = function(){
var pos = 0;
for(var i = 0; i<arguments.length; i++){
this.splice(pos, 0 , arguments[i]);
pos ++;
}
return this.length;
}
arr.myUnshift('a','b','c');
console.log(arr);
方法二:
Array.prototype.myUnshift = function(){
var argArr = Array.prototype.slice.call(arguments);
var newArr = argArr.concat(this);// this是原先的arr
return newArr;
}
var newArr = arr.myUnshift('a','b','c');
console.log(newArr);
2.1.3 shift
删除第一项
返回删除的那一项
let ary = [10,20,30,40];
let res = ary.shift();
console.log(res,ary);//10 [20,30,40]
原理: 基于原生js中的delete,把数组当做普通对象,确实可以删除掉某一项内容,但是不会影响数组本身的结构特点(length长度不会跟着修改),真实项目中杜绝这样的删除使用
delete ary[0];
console.log(ary);//{1:30,2:40,length:3}
//删除第一项
ary.splice(0,1);
2.1.4 pop
删除最后一项
返回删除的那一项
let ary = [10,20,30,40];
let res = ary.pop();
console.log(res,ary);//40 [10,20,30]
原理: 基于原生js让数组长度干掉一位,默认干掉的就是最后一项;
ary.length--;//ary.length = ary.length -1
console.log(ary);//[10,20]
//删除最后一项
ary.splice(ary.length-1);
2.1.5 splice
向数组中任意位置删除一项\ n,m 从索引n开始删除m个元素 (m不写,是删除到末尾) 返回删除的部分用新数组存储起来
let ary = [10,20,30,40,50,60,70,80,90];
let res = ary.splice(2,4);
console.log(res,ary);//[30,40,50,60] [10,20,70,80,90];
作用: 基于这种方法可以清空一个数组,把原始数组中的数组以新数组存储起来(有点类似数组的克隆:把原来数组克隆一分一模一样的给新数组)
res = ary.splice(0);
console.log(res,ary);//[10,20,70,80,90] []
//删除最后一项
ary.splice(ary.length-1);
//删除第一项
ary.splice(0,1);
console.log(ary);
向数组中任意位置增加/修改一项
n,m,x 从索引n开始删除m个元素 ,用x占用删除的部分
n,0,x 从索引n开始,一个都不删,把x放到索引n的前面
把删除的部分用新数组存储起来返回
let ary = [10,20,30,40,50];
let res = ary.splice(1,2,'AA');
console.log(res,ary);//[20,30] [10,'AA',40,50]
实现增加
ary.splice(3,0,'哈哈哈');// [10, 'AA', 40, '哈哈哈', 50]
console.log(ary);
向数组末尾追加
ary.splice(ary.length,0,'BB');
console.log(ary);//[10, 'AA', 40, '哈哈哈', 50, 'BB']
向数组开始追加
ary.splice(0,0,'CC');
console.log(ary);//['cCC', 10, 'AA', 40, '哈哈哈', 50, 'BB']
2.2 数组的查询和拼接
这部分数组的方法,原来数组不会改变
2.2.1 slice
实现数组的查询
[n,m) 从索引 n 开始,找到索引为 m(可以为负) 的地方(不包含m这一项)
把找的的内容以一个新数组的形式返回
let ary = [10,20,30,40,50];
let res = ary.slice(1,3);
console.log(res,ary);//[20,30] [10,20,30,40,50];
m不写找到末尾
res = ary.slice(1);
console.log(res);// [20,30,40,50]
数组的克隆,参数0不写也可以
res = ary.slice(0);
console.log(res);//[10,20,30,40,50]
如果slice()的参数有负值,那么就以数组长度加上这个负数来确定位置,案例如下
长度为6,slice(-5,-1)就相当于slice(1,5)
如果结束位置小于开始位置,则返回空数组
2.2.2 concat
实现数组的拼接
返回拼接后的新数组(原来数组不变)
let ary1 = [10,20,30];
let ary2 = [40,50,60]
let res = ary1.concat('AA');
console.log(res);//[10,20,30,'AA']
2.2.3 toString
把数组转换为字符串
返回转换后的字符串,每一项用逗号分隔
let ary = [10,20,30];
let res = ary.toString();
console.log(res);//"10,20,30"
console.log([].toString());//""
console.log([12].toString());//"12"
2.2.4 join
把数组转为字符串
params:指定的分隔符('字符串格式')
返回:转换后的字符串,每一项用分隔符分隔,默认为逗号
let ary = [10,20,30];
let res = ary.join('');
console.log(res);//"102030"
let res = ary.join('|');
console.log(res);//"10|20|30"
let res = ary.join();
console.log(res);//"10,20,30"
let res = ary.join('+');
console.log(res);//"10+20+30"
console.log(eval(res));//60 eval把字符串变为js表达式执行
2.2.5 indexOf/lastIndeOf
检测当前项在数组中第一次或者最后一次出现位置的索引(在IE6-8不兼容)
params: 要检索的这项内容
返回:这一项出现的位置索引值(数字),如果数组中没有这一项,返回的结果是-1
let ary = [10,20,30,10,20,30]];
console.log(ary.indexOf(20));// 1
console.log(ary.lastIndexOf(20));// 4
// 想验证ary中是否包含'AA'
if(ary.indexOf('AA' > -1)){
//不包含
}
// 也可以直接使用ES6新提供的includes方法判断
if(ary.includes('前端开发')){
// 包含:如果存在返回的是TRUE
}
2.2.6 split
把字符串转为数组
params:指定的分隔符(字符串格式) , 截取的长度
return: 转换后的数组,每一项用分隔符分隔,默认为逗号
var arr = ['a','b','c','d'];
var str1 = arr.join('-');
console.log(str1);//a-b-c-d
var arr1 = str1.split('-',3);
console.log(arr1);//["a","b","c"]
2.2.7 reverse
把数组倒过来排列
返回:排列后的新数组
let ary = [12,3,4,5,34];
ary.reverse();
console.log(ary);//[34,5,4,3,12]
2.2.8 sort
实现数组排序
params: 可以没有,也可以是个函数
返回:排序后的新数组
let ary = [7,8,5,2,4,6];
ary.sort();
console.log(ary);//[2,4,5,6,7,8,9]
注意: sort方法如果不传递参数,是无法处理10以上数字排序的(默认按照第一个字符来排序,不是我们想要的效果)
想要实现多位数正常排序,需要个sort传递一个函数,函数中返回a-b实现升序排序,返回 b-a 实现降序(冒泡排序)
ary = [12,15,6,7,43];
//ary.sort(function(a,b){return a-b;}));
//1. 参数a,b
//2. 返回值: 1、负值,a就排前面
// 2、正值,b就排前面
// 3、0保持不变
自定义排序:
arr.sort(function(a,b){
if(a>b){
return 1;
}else{
return -1;
}
}
==>
ary.sort((a,b)=>{
// a 和 b是相邻的两项
return a - b;
});
console.log(ary);
实现随机排序
arr.sort(function(a,b){
/*
var rand = Math.random();
if(rand - 0.5 > 0){
return 1;
}else{
return -1;
}
*/
return Math.random() - 0.5;
});
数组对象排序
var arr = [
{
son: 'Jenny',
age:14
},
{
son: 'jone',
age:4
},
...
];
arr.sort(function(a,b){
return a.age - b.age;
});
面试题: 数组按照元素的字节数排序
// unicode 0-255都是1个字节 256以后都是2个字节
var arr = ['我爱你','OK','Hello','你说what','可以'];
arr.sort(function(a,b){
return getBytes(a) - getBytes(b);
});
function getBytes(str){
var bytes = str.length;
for(var i = 0; i< str.length; i++){
if(str.charCodeAt(i) > 255){
bytes++;
}
}
return bytes;
}
console.log(getBytes('我爱你'));//6
3. 数组去重方案
方法一:
var arr = [0,0,1,1,1,2,3,3];
Array.prototype.unique = function(){
var temp = {},
newArr = [];
for(var i = 0; i < this.length; i++){
if(!temp.hasOwnProperty(this[i]){
temp[this[i]] = this[i];
newArr.push(this[i]);
}
}
return new Arr;
}
arr.unique();
/*
判断对象中有没有这个属性来判断要不要给数组添加
var obj = {
0:0
};
var arr = [0, 1, 2, 3];
*/
4. 字符串去重
方法一:
var str = '111222333aa';
String.prototype.unique = function(){
var temp = {},
newStr = '';
for(var i = 0;i < this.length; i++){
if(!temp.hasOwnProperty(this[i]){
temp[this[i]] = this[i];
newStr += this[i];
}
}
return newStr;
}
console.log(str.unique());//'123a'
方法二:
// 找出第一个不重复的字母
var str = 'fjaeoiafiodshgoieioeaoinoai';
function test(str){
var temp = {};
for(var i = 0; i< str.length; i++){
if(temp.hasOwnProperty(str[i]){
temp[str[i]]++;
}else{
temp[stri]] = 1;
}
}
for(var key in temp){
if(temp[key] ===1){
return key;
}
}
}
console.log(test(str));
5. 类数组
function test(){
/*
参数:
callee: 宿主函数 test
Symbol.iterator 可迭代对象标志
arguments 没有继承-> Array.prototype
类数组的标志:
1.length
2.从0开始的属性下标
3.没有数组的内置方法(build-in methods/object)可以使用- internal methods内部方法,不可以使用
*/
console.log(arguments.callee); // test函数
console.log(test); // test函数,所以不用callee
console.log(arguments);
console.log(typeof(arguments)); // 类数组 object
console.log(arguments.toString()); // [object Arguments]
console.log(Array.isArray(arguments)); // false
}
test(1,2,3,4,5,6);
var arr = [1,2,3,4,5,6];
console.log(arr); //[1, 2, 3, 4, 5, 6]
arguments是非箭头函数的其他函数的内置的局部变量
var test = (...args) => {
console.log(args);// [1,2,3]
console.log(Array.isArray(args);// true
};
test(1,2,3);
类数组转为数组阻止JS引擎优化的解决方案
function test1(){
// slice 用在arguments身上会阻止JS引擎做一些特定的优化
var argArr = [].slice.call(arguments);
}
test(1,2,3);
// 解决方法1
var arr = [];
for(var v of arguments){
arr.push(v);
}
console.log(arr);
// 解决方法2
var argArr = arguments.length === 1 ? [arguments[0]] : Array.apply(null,arguments); // 有一项放进来,多项用apply展开
5.1 使用arguments的情况
实参个数>形参个数
function test(a,b,c){
console.log(arguments[3]);//4
}
test(1,2,3,4);
也可以处理arguments(for循环做运算)
function test(a,b,c){
console.log(arguments[3]);//4
for(var i=0; i<arguments.length; i++){
arguments[i] += 1;
}
console.log(arguments);// [2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
test(1,2,3,4);
不定参数
function test(){
console.log(arguments[3]);//4
}
test(1,2,3,4);
5.2 形参和实参对应关系-共享
跟踪 形实参默认情况下是会有共享关系
//跟踪
function test (a){
arguments[0] = 10;
console.log(a,arguments[0]);// 10 10
}
test(1);
不跟踪1: 形参中但凡有一个参数有默认值,arguments都不会对应跟踪参数最终的值\
function test (a = 100){
//arguments[0] = 10;
// console.log(a, arguments[0]);//1 10
a = 1000;
console.log(a, arguments[0]); // 1000 1
}
test(1);
function test (a, b, c = 10){
arguments[0] = 100;
arguments[1] = 200;
arguments[2] = 300;
console.log(a, arguments[0]); // 1 100
console.log(b, arguments[1]); // 2 200
console.log(c, arguments[2]); // 3 300
}
test(1,2,3);
不跟踪2: 不定参数
// 不跟踪
function test (...args){
arguments[0] = 100;
arguments[1] = 200;
arguments[2] = 300;
console.log(args[0], arguments[0]); // 1 100
console.log(args[1], arguments[1]); // 2 200
console.log(args[2], arguments[2]); // 3 300
}
test(1,2,3);
不跟踪3: 参数解构
// 不跟踪
function test ({a,b,c}){
arguments[0] = 100;
arguments[1] = 200;
arguments[2] = 300;
console.log(a, arguments[0]); // 1 100
console.log(b, arguments[1]); // 2 200
console.log(c, arguments[2]); // 3 300
}
test({
a:1,
b:2,
c:3
});
不跟踪4: 严格模式也不跟踪,不共享
// 不跟踪
function test (a,b,c){
'use strict';
a = 10;
b = 20;
c = 30;
console.log(a,b,c);// 10 20 30
console.log(arguments); //[1,2,3]
console.log(arguments.callee);//去掉了这个
arguments[0] = 10;
arguments[1] = 20;
arguments[2] = 30;
console.log(a, arguments[0]); // 1 10
console.log(b, arguments[1]); // 2 20
console.log(c, arguments[2]); // 3 30
}
test(1,2,3);
6. 简化switch语法
用数组简化
function test(day){
var weekday = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
weekday[day - 1] !== undefined ? console.log(weekday[day-1]):console.log('I don\'t know');
}
// 在不用判断的情况下去掉减一,运用数组的特性解决=>在数组前面加一个逗号即可
test(0);
7. 副作用
副作用的函数不仅仅只返回了一个值还做了其他的事情(操作系统文件、操作数据库、发送HTTP请求、console.log、修改全局变量...)
在JS中,我们不是要完全消除副作用,而是避免不应该出现的副作用 如果没有副作用的话,我们的程序只能进行计算
function test(a,b){
return a + b;
}
const result = test(1,2);
但是我们日常开发中,经常需要写数据库,通过外界提供的接口将我们的计算结果进行使用
7.1 纯函数:输入和输出的数据都是显式的
函数与外界交换数据的唯一渠道 -- 参数和返回值 函数从外部接收到的所有数据都有参数传入到函数内部 函数从内部输出到外部的数据都通过返回值传递到函数外部
7.2 非纯函数:
函数通过除了参数和返回值的方法和外界进行数据交换,比如在函数内部修改全局变量
局部副作用
let result = 0;
function test(num){
for(let i = 0; i< num; i++){
result += i;
}
return result;
}
test(3) //3
test(3) //6
function test(num){
let result = 0;
for(let i = 0; i< num; i++){
result += i;
}
return result;
}
test(3) //3
test(3) //3
7.3 引用透明性:
对于一个纯函数而言,给定相同的参数,返回值都相同,所以我们可以用计算值代替纯函数的调用,可以用计算的结果第一表达式的能力称之为引用透明性
function test(a){
return a + 1
}
test(3) + test(3) =>
4 + 4
8. ECMAScript内置对象之Array数组方法重写
8.1 Array.prototype.copyWithin
copyWithin -> ES2015 es6
参数:target start end
// 1. [3, 4)
const arr = [1, 2, 3, 4, 5];
const newArr = arr.copyWithin(0, 3, 4);
console.log(newArr); //[4,2,3,4,5]
// 2. end > len -1,取到末尾,最后取了几位,就从target位置覆盖几位
const newArr = arr.copyWithin(0, 3, 5);
console.log(newArr); //[4,5,3,4,5]
// 3. target > len -1,不发生任何替换
const newArr = arr.copyWithin(5, 1, 2);
console.log(newArr); //[1, 2, 3, 4, 5]
// 4. 当target > start 正常替换
const newArr = arr.copyWithin(3, 1, 3);
console.log(newArr); // [1, 2, 3, 2, 3]
// 5. start或end是负数 start+length,end+length
// 0, 2, 4
const newArr = arr.copyWithin(0, -3, -1);
console.log(newArr); // [3, 4, 3, 4, 5]
// 6. 如果没有start 取整个数组的元素
// copyWithin 是不改变数组长度
const newArr = arr.copyWithin(3);
console.log(newArr); // [1, 2, 3, 1, 2]
// 7. 如果没有end,从end取到最后,然后从target替换对应长度
const newArr = arr.copyWithin(1, 3);
console.log(newArr); // [1, 4, 5, 4, 5]
// 8. 返回的是原数组引用
console.log(newArr === arr); // true
作用:移动数组元素
复制 -> 全选target及符合复制的元素集合的长度的元素集合 -> 粘贴
const arr1 = [
{
id: 1,
name: '张三'
},
{
id: 2,
name: '李四'
},
{
id: 3,
name: '王五'
},
{
id: 4,
name: '赵六'
},
{
id: 5,
name: '刘七'
}
];
const target1 = arr1[0];
const newArr1 = arr1.copyWithin(0, 2, 3);
const target2 = arr1[0];
console.log(target1 === target2);// false
console.log(newArr1);
// 0:{id: 3, name: '王五'}
// 1:{id: 2, name: '李四'}
// 2:{id: 3, name: '王五'}
// 3:{id: 4, name: '赵六'}
// 4:{id: 5, name: '刘七'}
this不一定非要指向一个数组,也可以指向一个对象
var obj = {
0: 1,
1: 2,
2: 3,
3: 4,
4: 5,
length: 5
};
const newObj = [].copyWithin.call(obj, 0, 2, 4);
console.log(newObj);// {0: 3, 1: 4, 2: 3, 3: 4, 4: 5, length: 5}
console.log(obj === newObj); // true
/*
有符号的位移:
+ 左移:5 << 2 => 20(5 -> 101 -> 10100 -> 20)
+ 右移:5 >> 2 => 1(5 -> 101 -> 1 -> 1)
无符号的位移:>>> <<<
+ xxx.length >>> 0, 移动0位保证是正整数
*/
Array.prototype.myCopyWithin = function (target) {
if (this == null) {
throw new TypeError('this is null or not defined');
}
// 变为引用值
var obj = Object(this),
// 保证len是一个正整数
len = obj.length >>> 0,
start = arguments[1],
end = arguments[2],
count = 0,
dir = 1;
// 保证target是整数
target = target >> 0;
target = target < 0 ?
Math.max(len + target, 0) :
Math.min(target, len);
// 存在? 整数 : 0
start = start ? start >> 0 : 0;
start = start < 0 ?
Math.max(len + start, 0) :
Math.min(start, len);
end = end ? end >> 0 : len;
end = end < 0 ?
Math.max(len + end, 0) :
Math.min(end, len);
// (2, 2, 4)
// [1,2, 3,4, 5] 2,3
// 复制几位
count = Math.min(end - start, len - target);
// (3, 2, 5) 3< 2+3
if (start < target && target < (start + count)) {
dir = -1;
start += count - 1;
target += count - 1;
}
while (count > 0) {
if (start in obj) {
obj[target] = obj[start];
} else {
delete obj[target];
}
// 往后移动一位
start += dir;
target += dir;
count--;
}
return obj;
}
const arr = [1, 2, 3, 4, 5];
const newArr = arr.myCopyWithin(0, 3, 4);
console.log(newArr);
8.2
字符串
1. charAt通过下标去取值
var str = 'chengxiaohui';
// 通过下标取值
var res = str.charAt(0);//"c"
// 如果找不到,返回值是空串
var res2 = str.charAt(12);//""
// 如果直接是索引去取值,找不到,返回是undefined
var res3 = str[12];//undefined
2. charCodeAt通过下标取值对应的ascii码值
var str = 'chengxiaohui';
// 通过下标取值
var res = str.charCodeAt(0);//99 "c"==>ASCII码
console.log(res);//99
3. indexOf/lastIndexOf
第一个参数:找的内容
第二个参数:开始找的位置(indexOf)/找到哪终止(lastIndexOf)
一个参数的情况indexOf
var str = 'chengxiaohui';
// 一个参数,在整个字符串中找
var res = str.indexOf("x");
// 返回值是找到的下标
console.log(res);//5
一个参数的情况lastIndexOf
var str = 'chengxiaohui';
// 一个参数,在整个字符串中找最后一次出现的下标
var res = str.lastIndexOf("h");
// 返回值是找到的下标
console.log(res);//9
两个参数的情况indexOf
var str = 'chengxiaohui';
// 从下标5开始找,找"h"
var res = str.indexOf("h",5);
// 返回值是找到的下标
console.log(res);//9
两个参数的情况lastIndexOf
var str = 'chengxiaohui';
// 截止到下标5,找"h"
var res = str.lastIndexOf("h",5);
// 返回值是找到的下标
console.log(res);//1
4. slice
作用:查找字符串中特定位置的字符
参数:[n,m)
n:起始索引(包含), m结束索引(不包含)
返回值:查找的字符
- 从索引n(包括)开始查找到索引m(不包含)结束的字符
- 如果索引m不写,就是查找到最后
- 如果n只写一个0,或者不写就是复制一份
- 也可以为负数,转换成正数的规律:str.length+负的索引值
var str="zhufengpeixun"
str.slice(1,3) ===>"hu"
str.slice(0) ===>复制一份
str.slice() ===>复制一份
var res=str.slice(-3,-1); ===>“xu”
5.1 substring(n,m)
subString 和slice 基本都一样,唯一不同在于,subString 不支持负数索引,而slice支持负数索引
5.2 substr(n,m)
- 作用:从索引n开始截取m个字符
- 参数:n,m(个数)
- 返回值:截取的字符串
- 也支持从负索引开始
var str="qianduan";
var res=str.substr(-3,2); ====>"ua"
6.toUpperCase()
toUpperCase(); 把字符串转换为大写
7. toLowerCase()
toLowerCase(); 把字符串转换为小写
8.replace()
- 作用:把字符串中某部分的字符替换成另一部分字符
- 参数:(str1,str2)第一个参数代表的是要替换的字符或者是正则;第二个参数代表的是替换后的字符
- 返回值:替换后的字符串
var str="qianduan2018qianduan2019qianduan";
var res=str.replace("qianduan","前端");
//===>"前端2018qianduan2019qianduan"
var res=str.replace(/qianduan/g,"前端");
console.log(res)===>"前端2018前端2019前端"
9. split
- 作用:按照指定的字符把字符串分割成数组
- 参数:分割符
- 返回值:分割后的数组
split 和 join 对比记忆
var str="1-2-3";
var res=str.split("-");
console.log(res); ===> ["1", "2", "3"]