js 基础-数据类型-数组

236 阅读5分钟

数组

存储有序的集合:数组又叫 双端队列(deque),支持队列+堆的功能

声明

let arr = new Array();
let arr = [];

数组可以存储任何类型的元素。

// 混合值
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// 获取索引为 1 的对象然后显示它的 name
alert( arr[1].name ); // John
// 获取索引为 3 的函数并执行
arr[3](); // hello

队列(queue)

  • push 在末端添加一个元素.
  • shift 取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一。

  • push 在末端添加一个元素.
  • pop 从末端取出一个元素.

shift

取出数组的第一个元素并返回它:

let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.shift() ); // 移除 Apple 然后 alert 显示出来
alert( fruits ); // Orange, Pear

unshift

在数组的首端添加元素:

let fruits = ["Orange", "Pear"];
fruits.unshift('Apple');
alert( fruits ); // Apple, Orange, Pear

pushunshift 方法都可以一次添加多个元素:

let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );

数组是对象

  • arr[0] 实际上是来自于对象的语法,与 obj[key] 相同,arr 是对象,而数字用作键(key)
  • 数组可以动态添加属性,但是会失去原来针对有序集合的一些优化。变成普通对象处理
let fruits = []; // 创建一个数组
fruits[99999] = 5; // 分配索引远大于数组长度的属性
fruits.age = 25; // 创建一个具有任意名称的属性

性能

push/pop效率更高,shift/unshift 慢

循环

for..of 只循环值

let fruits = ["Apple", "Orange", "Plum"];
// 遍历数组元素
for (let fruit of fruits) {
  alert( fruit );
}

for..in 处理正常数组信息,还会遍历出其他信息

length

不是数组里元素的个数,而是最大的数字索引值加一

let fruits = [];
fruits[123] = "Apple";
alert( fruits.length ); // 124

设置length 会改变总长度,而且不可逆

let arr = [1, 2, 3, 4, 5];
arr.length = 2; // 截断到只剩 2 个元素
alert( arr ); // [1, 2]
arr.length = 5; // 又把 length 加回来
alert( arr[3] ); // undefined:被截断的那些数值并没有回来

new Array()

创建一个 指定了长度,却没有任何项 的数组,默认都是 undefined

let arr = new Array(2); // 会创建一个 [2] 的数组吗?
alert( arr[0] ); // undefined!没有元素。
alert( arr.length ); // length 2

toString

let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
  • 数组没有 Symbol.toPrimitive,也没有 valueOf,它们只能执行 toString 进行转换.
  • 所以这里 [] 就变成了一个空字符串,[1] 变成了 "1"[1,2] 变成了 "1,2"
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"

比较数组

  • 仅当两个对象引用的是同一个对象时,它们才相等 ==
  • 如果 == 左右两个参数之中有一个参数是对象,另一个参数是原始类型,那么该对象将会被转换为原始类型,转换规则如 对象 — 原始值转换 一章所述。
  • ……nullundefined 相等 ==,且各自不等于任何其他的值。
  • 严格比较 === 更简单,因为它不会进行类型转换。

数组是不同的对象。所以它们不相等

alert( [] == [] ); // false
alert( [0] == [0] ); // false
  • 原始类型和数组对象进行比较。数组 [] 被转换为原始类型以进行比较,被转换成了一个空字符串 ''
  • 接下来的比较就是原始类型之间的比较.
alert( 0 == [] ); // true
alert('0' == [] ); // false
// 上面等价于 在 [] 被转换为 '' 后
//上面等价于 
alert( 0 == '' ); // true,因为 '' 被转换成了数字 0
alert('0' == '' ); // false,没有进一步的类型转换,是不同的字符串

总结

数组是一种特殊的对象,适用于存储和管理有序的数据项。

  • 声明:
// 方括号 (常见用法)
let arr = [item1, item2...];
// new Array (极其少见)
let arr = new Array(item1, item2...);

调用 new Array(number) 会创建一个给定长度的数组,但不含有任何项。

  • length 属性是数组的长度,准确地说,它是数组最后一个数字索引值加一。它由数组方法自动调整。
  • 如果我们手动缩短 length,那么数组就会被截断。 我们可以通过下列操作以双端队列的方式使用数组:
  • push(...items) 在末端添加 items 项。
  • pop() 从末端移除并返回该元素。
  • shift() 从首端移除并返回该元素。
  • unshift(...items) 从首端添加 items 项。 遍历数组的元素:
  • for (let i=0; i<arr.length; i++) — 运行得最快,可兼容旧版本浏览器。
  • for (let item of arr) — 现代语法,只能访问 items。
  • for (let i in arr) — 永远不要用这个。

比较数组时,不要使用 == 运算符(当然也不要使用 >< 等运算符),因为它们不会对数组进行特殊处理。它们通常会像处理任意对象那样处理数组,这通常不是我们想要的。

但是,我们可以使用 for..of 循环来逐项比较数组。

问题:输入数字求和

  • 使用 prompt 向用户索要值,并存在数组中。
  • 当用户输入了非数字、空字符串或者点击“取消”按钮的时候,问询结束。计算并返回数组所有项之和。
function sumInput() {
  let totalArr = []
  let bol = true
  while(true){
    let input = prompt("请输入数字")
    let bol = (  input === '' || input === null || !isFinite(input) )
    if(bol) {
      break 
    }
    totalArr.push(+input)
  }
    let total = 0
    for val of totalArr{
        total += val
    }
     return total
}
sumInput()

问题:求数组的连续子数组,返回最大和

找出所有项的和最大的 arr 数组的连续子数组,返回最大和

  alert( getMaxSubSum([-1, 2, 3, -9,11]) ); // 5
  alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11
  alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3
  alert( getMaxSubSum([1, 2, 3]) ); // 6


//方案1 双循环
let a = [-1, 2, 3, -9, 11]
function getMaxSubSum(a) { 
    // let a = [-1, 2, 3, -9, 11] 
    let maxSum = 0
    let maxI = 0
    for (let i = 0; i < a.length; i++) {
        let nowArrSum = 0
        for(let j = i; j < a.length; j++) { 
            nowArrSum += a[j]
            maxSum = Math.max(maxSum,nowArrSum)
        } 
    }
    console.log("maxSum",maxSum)
    return maxSum
}
getMaxSubSum(a)


//优化方案
let a = [-1, 2, 3, -9,11]
getMaxSubSumFirst(a)
function getMaxSubSumFirst(arr) { 
    let sumMax = 0
    let tempTotal = 0
    for (let i = 0; i <arr.length; i++) {
        let temp = arr[i] 
        if (temp < 0) {
            tempTotal = 0
        } else { 
            tempTotal += temp
            sumMax = Math.max(sumMax,tempTotal)
        }
    }
    console.log("getMaxSubSumFirst",sumMax)
    return sumMax
}

数组方法

添加删除

splice

改变原有数组

let arr = ["I", "go", "home"];
delete arr[1]; //变成 ["I", undefined, "home"]; 依然占用位置,长度3

arr.splice(start[, deleteCount, elemInsert1, ..., elemInsertN])

//只删除
let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // 从索引 1 开始删除 1 个元素 结果为 ["I", "JavaScript"];长度2
//只新增
let arr = ["I", "study", "JavaScript"];
arr.splice(2, 0, "complex", "language");// 从索引 2 开始 删除 0 个元素,然后插入 "complex" 和 "language"
alert( arr ); //结果  ["I", "study", "complex", "language", "JavaScript"]

允许负向索引,注意负不从0开始,从-1开始

let arr = [1, 2, 5];
arr.splice(-1, 0, 3, 4);// 从索引 -1(尾端前一位) ,删除 0 个元素,然后插入 3 和 4
alert( arr ); // [1,2,3,4,5]

slice

它会返回一个新数组,原数组不变化。将所有从索引 startend(不包括 end)的数组项复制到一个新的数组。startend 都可以是负数,在这种情况下,从末尾计算索引。

arr.slice([start], [end])

let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素)
alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)

concat

arr.concat(arg1, arg2...) 创建一个新数组,拼接内容arg1可以是数组[]或者值val

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

它只复制数组中的元素。其他对象整体添加

let arr = [1, 2];
let arrayLike = {
  0: "something",
  length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]

特殊情况:Symbol.isConcatSpreadable 属性

let arr = [1, 2];
let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else

搜索元素/遍历

forEach

方法允许为数组的每个元素都运行一个函数

arr.forEach(function(item, index, array) {
  // ... do something with item
});

indexOf/lastIndexOf 和 includes

等同于string字符串的操作

  • arr.indexOf(item, from) 从索引 from 开始搜索 item,如果找到则返回索引,否则返回 -1
  • arr.lastIndexOf(item, from) —— 和上面相同,只是从右向左搜索。
  • arr.includes(item, from) —— 从索引 from 开始搜索 item,如果找到则返回 true(译注:如果没找到,则返回 false)。
let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

//带有from的查找
let arr = [1, 0, false, 0, false];
alert( arr.indexOf(0,2) ); // 3 如果从第三个位置开始查找,则是第二个0,位置为3

不严格判断使用 includes

const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1(应该为 0,但是严格相等 === equality 对 NaN 无效)
alert( arr.includes(NaN) );// true(这个结果是对的)

find 和 findIndex

let result = arr.find(function(item, index, array) {
  // 如果返回 true,则返回 item 并停止迭代
  // 对于假值(falsy)的情况,则返回 undefined
});

filter

find是匹配一个,filter匹配多个,返回数组

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];
let someUsers = users.filter(item => item.id < 3);
alert(someUsers); // [  {id: 1, name: "John"}, {id: 2, name: "Pete"},]

转换数组

map

它对数组的每个元素都调用函数,并返回结果数组。

let result = arr.map(function(item, index, array) {
  // 返回新值而不是当前元素
})

sort()

排序,改变原有数组

let arr = [ 1, 2, 15 ];
// 该方法重新排列 arr 的内容
arr.sort(); //这里默认会转化所有数据为字符串,默认按第一位在unicode编码表的顺序比较
alert( arr );  // 1, 15, 2  所以排序变成 第一位优先比较

自定义排序方法sort(fn)

function compareNumeric(a, b) {
  if (a > b) return 1; // 如果第一个值比第二个值大
  if (a == b) return 0; // 如果两个值相等
  if (a < b) return -1; // 如果第一个值比第二个值小
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr);  // 1, 2, 15

//优化1 比较函数只需要返回一个正数表示“大于”,一个负数表示“小于”。
let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
alert(arr);  // 1, 2, 15
//优化2
arr.sort( (a, b) => a - b );

语言的比较localeCompare

let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich 
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam  

reverse 倒叙

let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1

split 拆分

let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}

//长度限制,2之后的不处理
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf

//参数为空字符串,为单字拆解
let str = "test";
alert( str.split('') ); // t,e,s,t

//参数为undefined 或不输入,内容不变
let str = "test";
alert( str.split('') ); // test

join 粘合

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串
alert( str ); // Bilbo;Gandalf;Nazgul

reduce/reduceRight

根据数组计算单个值。

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

该函数一个接一个地应用于所有数组元素,并将其结果“搬运(carry on)”到下一个调用。

  • accumulator —— 是上一个函数调用的结果,第一次等于 initial(如果提供了 initial 的话)。
  • item —— 当前的数组元素。
  • index —— 当前索引。
  • arr —— 数组本身。
  • initial 为初始传入值:
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
//可以省略初始值0  //如果没有初始值,那么 reduce 会将数组的第一个元素作为初始值,并从第二个元素开始迭代。
let result = arr.reduce((sum, current) => sum + current); 

reduceRight 是从右边到左边遍历

Array.isArray

alert(Array.isArray({})); // false
alert(Array.isArray([])); // true

通用参数thisArg

findfiltermap,除了 sort 是一个特例,都有附加参数 thisArg

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg 是可选的最后一个参数

我们使用 army 对象方法作为过滤器,thisArg 用于传递上下文(passes the context):

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};
let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

// 找到 army.canJoin 返回 true 的 user,这里使用的是army的上下文
let soldiers = users.filter(army.canJoin, army);
alert(soldiers);//结果 [ {age: 20},{age: 23}]

练习

//题目1 实现如下效果
camelize("list-style-image") == 'listStyleImage';
camelize("-webkit-transition") == 'WebkitTransition';

function camelize(str) {
   return str.split('-').map((item,index)=> {
      return index === 0 ? item:item[0].toUpperCase() + item.slice(1)
    }).join("") 
}
//题目2 实现如下效果
let arr = [5, 3, 8, 1];
filterRangeInPlace(arr, 1, 4); // 删除了范围在 1 到 4 之外的所有值
alert( arr ); // [3, 1]

function filterRangeInPlace(arr,start,end) { 
    for( let i = arr.length-1; i>=0; i--){
       if ( arr[i] >= start && arr[i] <= end ){

       }else {
        arr.splice(i,1)
       }
    }
}

//题目3 实现如下效果
let arr = ["HTML", "JavaScript", "CSS"];
let sorted = copySorted(arr);
alert( sorted ); // CSS, HTML, JavaScript
alert( arr ); // HTML, JavaScript, CSS (no changes)
//方法1
function copySorted(arr) {
    copy = arr.map((item)=>item) 
    return ["HTML", "JavaScript", "CSS"].sort( (a,b) => a > b?1:-1)
}
//方法2
function copySorted(arr) {
    return arr.slice().sort()
}

//题目4 实现如下效果
let calc = new Calculator;
alert( calc.calculate("3 + 7") ); // 10
calc.addMethod("*", (a, b) => a * b);
calc.addMethod("/", (a, b) => a / b);
calc.addMethod("**", (a, b) => a ** b);
let result = calc.calculate("2 ** 3");
alert( result ); // 8
//方法1 方法定义
function Calculator() {
  this.methods = {
    "-": (a, b) => a - b,
    "+": (a, b) => a + b
  };
  this.calculate = function(str) {
    let split = str.split(' '),
      a = +split[0],
      op = split[1],
      b = +split[2];
    if (!this.methods[op] || isNaN(a) || isNaN(b)) {
      return NaN;
    }
    return this.methods[op](a, b);
  };

  this.addMethod = function(name, func) {
    this.methods[name] = func;
  };
}

//方法2 类定义
class Calculator {
    constructor(){
        this.symbolObj = {"+":(a, b) => a + b}
    }
    calculate(params) {
       let [num1,symbol,num2] = params.split(" ")
       if(this.symbolObj[symbol]) {
        return this.symbolObj[symbol](Number(num1),Number(num2))
       } 
    }
    addMethod(symbol,fn) {
        this.symbolObj[symbol] = fn
    }
}

//题目4  随机排列数组
//编写函数 shuffle(array) 来随机排列数组的元素。
//多次运行 shuffle 可能导致元素顺序的不同。例如:
let arr = [1, 2, 3];
shuffle(arr);// arr = [3, 2, 1]
shuffle(arr);// arr = [2, 1, 3]
shuffle(arr);// arr = [3, 1, 2]
//方法1
function shuffle(arr) {
    arr.sort((a,b) => Math.random() - 0.5)
}

//方法2 Fisher — Yates 算法 /高纳德( Knuth)随机置乱算法
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1)); // 从 0 到 i 的随机索引
    // 交换元素 array[i] 和 array[j]
    // 我们使用“解构分配(destructuring assignment)”语法来实现它
    // let t = array[i]; array[i] = array[j]; array[j] = t
    [array[i], array[j]] = [array[j], array[i]];
  }
}
// 所有可能排列的出现次数
let count = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};
for (let i = 0; i < 1000000; i++) {
  let array = [1, 2, 3];
  shuffle(array);
  count[array.join('')]++;
}
// 显示所有可能排列的出现次数
for (let key in count) {
  alert(`${key}: ${count[key]}`);
}


//题目5  实现数组去重
let strings = ["Hare", "Krishna", "Hare", "Krishna",
  "Krishna", "Krishna", "Hare", "Hare", ":-O"
];

alert( unique(strings) ); // Hare, Krishna, :-O

//方法1 
function unique(arr) { 
    let resList = []
    for (item of arr) {
        if (resList.indexOf(item) < 0) {
            resList.push(item)
        }
    }
    return resList
}

//方法2
function unique(arr) {
    let set = new Set(arr) 
    return Array.from(set)
}

//方法3 
function unique(arr) {
    arr.sort()
    for (let index = arr.length -1; index >0; index--) {
        const element = arr[index];
        if(arr[index] === arr[index - 1]){
            arr.splice(index,1)
        }
    }
    return arr.reverse()
}

//题目6 从数组创建键(值)对象

let users = [
    {id: 'john', name: "John Smith", age: 20},
    {id: 'ann', name: "Ann Smith", age: 24},
    {id: 'pete', name: "Pete Peterson", age: 31},
  ];
  
  let usersById = groupById(users);
  /*
 输出
  usersById = {
    john: {id: 'john', name: "John Smith", age: 20},
    ann: {id: 'ann', name: "Ann Smith", age: 24},
    pete: {id: 'pete', name: "Pete Peterson", age: 31},
  }
  */
//方法1
  function groupById(users) {
    const obj = {}
    for( item of users) {
        obj[item.id] = item
    }
    return obj
  }

//方法2
  function groupById(users) {
    return users.reduce((preObj,obj)=>{
         preObj[obj.id] = obj
         return preObj
    },{})
  }