JavaScript基础之 数据类型(数组,可迭代对象)

257 阅读3分钟

数组

数组,存储有序的集合。
数组,可以存储任何类型的元素。

创建数组

创建一个空数组有两种语法:

// 创建空数组
let arr = new Array();
let arr = [];

// 创建并初始化
let arr = new Array("a", "b");
let arr = ["a", "b"];

// new Array() 使用单个数字参数来指定数组长度
let arr = new Array(3);
alert(arr.length); // 3

可以通过下标替换或新增元素:

let fruits = ["Apple", "Orange", "Plum"];
fruits[2] = "Pear"; // 现在变成 ["Apple", "Orange", "Pear"]
fruits[3] = 'Lemon'; // 现在变成 ["Apple", "Orange", "Pear", "Lemon"]

// 新增下标可以不连续
fruits[6] = "grape"; // 现在变成["Apple", "Orange", "Pear", "Lemon", "", "", "grape"]

数组的栈方法和队列方法

JavaScript中的数组,既可以用作队列,也可以用作栈。
在计算机科学中,允许这样的操作的数据结构被称为双端队列(deque)

队列(queue):FIFO(First-in-First-out) 先进先出
栈:LIFO(Last-in-First-out) 后出先进

pop/push方法(栈方法)

pop 取出并返回数组的最后一个元素

let fruits = ["apple", "orange", "pear"];
alert(fruits.pop()); // pear
alert(fruits); // apple, orange 

push 在数组末端添加元素(可以添加多个)

let fruits = ["apple", "orange", "pear"];
fruits.push("grape"); 
alert(fruits); // apple, orange, pear, grape

shift/unshift方法(队列方法)

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

let fruits = ["apple", "orange", "pear"];
alert(fruits.shift()); // apple
alert(fruits); // orange, pear

unshift 在数组的首端添加元素(可以添加多个)

let fruits = ["orange", "pear"];
fruits.unshift("apple"); // 
alert(fruits); // apple, orange, pear

性能

结论pop/push方法运行的比较快,而shift/unshift比较慢

原因shift/unshift除了添加或移除首端元素之外,还需要对其他所有元素重新编号。pop/push不需要移动任何东西,

遍历数组

for循环

运行的最快,可兼容旧版本浏览器。

let fruits = ["apple", "orange", "pear"];

for (let i = 0; i < fruits.length; i++) {
    alert(fruits[i]);
}

for...of方法

for...of不能获取当前元素的索引,只是获取元素值。

let fruits = ["apple", "orange", "pear"];

for (let fruit of fruits) {
    alert(fruit);
}

forEach(func)方法

let fruits = ["apple", "orange", "pear"];

fruits.forEach((item, index, array) => {
    alert(`${item} is at index ${index} in ${array}`);
})

语法:
*arr*.forEach( callback( currentValue [, index ,array] )[, thisArg] ) 依次向callback函数传入3个参数:

  1. 数组当前项的值
  2. 数组当前项的索引(可选)
  3. 数组对象本身(可选) 如果thisArg参数有值,则每次callback函数被调用时,this都会只想thisArg参数;如果省略了thisArg参数,或值为nullundefinedthis则只想全局对象。

更多详细内容

关于 "length"

length有两点需要注意:

  1. 它实际上不是数组里的元素的个数,而是最大的数字索引值加1。 例如:一个数组只有一个元素,但是这个元素的索引值很大,那么这个数组的length也会很大。
let fruits = [];
fruits[123] = "apple"
alert(fruits.length); // 124
  1. length是可写的。 如果我们手动增加它,则不会发生任何事。但是如果我们减少它,数组就会被截断。该过程是不可逆的。

常用方法

arr.splice()

语法:

arr.splice(start [, deleteCount, elem1, ... , elemn ])

它从索引start开始(允许负向索引),删除deleteCount个元素,并在当前位置插入elem1, ..., elemn。最后返回已被删除的元素的数组。

  • 删除元素
let arr = ["I", "study", "JavaScript"];
let result = arr.splice(1, 1);

alert(arr); // I, JavaScript
alert(result); // study
  • 替换元素
let arr = ["I", "study", "JavaScript", "right", "now"];
let result = arr.splice(0, 3, "Let's", "dance");

alert(arr); // Let's, dance, right, now
alert(result); // I, study, JavaScript
  • 新增元素
let arr = ["I", "study", "JavaScript"];
arr.splice(2, 0, "complex", "language");

alert(arr); // I, study, complex, language, JavaScript

arr.slice()

语法:

arr.slice([start, end]);

它返回一个新数组,将所有从索引startend(不包含end)的数组项复制到一个新的数组(不影响原数组)。(允许负向索引)

let arr = ["I", "study", "JavaScript", "right", "now"];

alert( arr.slice(0,3) ); // I, study, JavaScript
alert( arr.slice(-2) ); // right, now
alert( arr.slice() );// I, study, JavaScript,right, now

arr.concat()

arr.concat(arg1, arg2 ...)

它返回一个新数组,其中包含来自于其他数组和其他项的值

如果参数argN是一个数组,那么其中的所有元素都会被复制;否则,将复制参数本身。

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert(arr.concat(arrayLike)); // 1,2,[object Object]
console.log(arr.concat(arrayLike));

但是,如果类似数组的对象具有Symbol.isConcatSpreadable属性,那么它就会被concat当作一个数组来处理:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  1: "else",
  length: 2,
  [Symbol.isConcatSpreadable]: true
};

alert(arr.concat(arrayLike)); // 1,2,something,else

查找元素

arr.indexOf()/arr.lastIndexOf()和arr.includes()
  • 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

alert(arr.includes(1)); // true

❗ 这些方法使用的是严格相等===。所以如果我们搜索false,会精确到的确是false而不是数字0

indexOf/lastIndexOf不同的是,includes能正确处理NaN

const arr = [Nan]
alert(arr.indexOf(NaN)) // -1
alert(arr.includes(NaN)) // true
arr.find()/arr.findIndex()

主要用来查找对象数组中具有特定条件的对象。 语法:

let result = arr.find(function(item, index, array){}[, thisArg]);

依次对数组中的每个元素调用该函数,如果它返回true,则搜索停止并返回item。如果没有搜索到,则返回undefined。 例如,

let user = [
    { id: 1, name: "John" },
    { id: 2, name: "Tom" },
    { id: 3, name: "Jack" }
];

let users = user.find(item => item.id == 1);

alert(users.name); // John

arr.findIndex()方法与arr.find()方法基本是一样的,但它返回找到元素的索引,而不是元素本身。未找到任何内容时返回-1.

let user = [
    { id: 1, name: "John" },
    { id: 2, name: "Tom" },
    { id: 3, name: "Jack" }
];

let users = user.findIndex(item => item.id == 1);

alert(users); // 0
arr.filter()

find方法搜索的是使函数返回true的第一个元素。
filter方法返回所有匹配元素组成的数组。

let result = arr.filter(function(item, index, array){}[, thisArg]);

例如:

let users = [
    {id:1, name:"John"},
    {id:2, name:"Tom"},
    {id:3, namd:"Lily"}
];

let user = users.filter(item => item.id < 3 )

alert(user);
alert(user.length); // 2

转换数组

arr.map()

arr.map方法是最有用和经常使用的方法之一。 它对每个数组的每个元素都调用函数,并返回结果数组。

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

例如:

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths);  // 5,6,7
arr.reverse()

arr.reverse方法用于颠倒arr中元素的顺序。

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

alert(arr); // 5,4,3,2,1
arr.reduce()/arr.reduceRight()

reduce()方法对数组中的每个元素执行一个你提供的函数,将其结果汇总为单个返回值。

arr.reduce(callback(accumulator, item [, index [,array]]) [, initial])

callback执行数组中每个值的函数,包含四个参数: accumulator——是上一个函数调用的结果,第一次等于initial
item——当前的数组元素
index——当前索引(可选)
array——数组本身(可选)
initial——作为第一次调用callback函数时的第一个参数的值。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce将报错。

let arr = [1, 2, 3, 4, 5]
let result = arr.reduce((sum, item) => sum + item, 0);
alert(result); // 15

如果没有初始值,那么reduce会将数组的第一个元素作为初始值,并从第二个元素开始迭代。

let arr = [1, 2, 3, 4, 5]
let result = arr.reduce((sum, item) => sum + item);
alert(result); // 15

arr.reduceRightarr.reduce方法的功能一样,只是遍历从右到左。

数组排序sort(fn)

sort()方法是对数组进行原位排序,更改元素的顺序,并返回数组。

arr.sort([compareFunction(firstEl, secondEl)])

compareFunction可选

  用来指定按某种顺序进行排列的函数。如果省略,元素按照字符串的各个字符的Unicode位点进行排序。
firstEl第一个用于比较的元素。
secondEl第二个用于比较的元素。

如果指明了compareFunction,那么数组会按照调用该函数的返回值排序。即ab是两个将要被比较的元素:

  • 如果compareFunction(a, b) < 0 ,那么a会被排列到b之前
  • 如果compareFunction(a, b) > 0 ,那么b会被排列在a之前。
  • 如果compareFunction(a, b) = 0 ,a和b的相对位置不变。
  • compareFunction(a, b)必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
let arr = [1, 15, 2];

arr.sort((a, b) => a - b);

alert(arr); // 1,2,15

使用localeCompare比较字符串
默认情况下,它通过字母的的代码比较字母。对于许多字母,最好使用str.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(对的!)

数组-字符串转换

arr.split()

使用指定的分隔符字符串将一个String对象分割成子字符串数组。

str.split([separator [, limit]]);

separator指定表示每个拆分应发生的点的字符串。 limit一个整数,限定返回的分割片段数量。
例如:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(", ", 2);

for(let name of arr){
    alert(`${name}`); // Bilbo   Gandalf
}

如果分隔符为空字符串,则将str原字符串中每个字符的数组形式返回。

let str = "test";
alert(str.split("")); // t,e,s,t
arr.join()

将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。

arr.join([separator])
  • 如果缺省参数,数组元素用,分隔
  • 如果参数为空字符串,则所有元素之间都没有任何字符。
  • 如果数组元素为undefined,null,它会被转换为空字符串。

例如:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(";");

alert(str); // Bilbo;Gandalf;Nazgul

判断数组

Array.isArray

判断数组,返回布尔值。

Array.isArray(value);

例如:

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

数组是基于对象的,不构成单独的语言类型。 所以typeof不能帮助从数组中区分出普通对象。

可迭代对象(Iterable object)

可以应用for...of的对象被称为可迭代的
数组是可迭代的。但不仅仅是数组,很多其他内建对象也都是可迭代的,例如字符串也是可迭代的。

字符串是可迭代的

数组和字符串是使用最广泛的内建可迭代对象。
对于一个字符串,for...of遍历他的每个字符:

for(let char of "apple"){
    alert(char); // a p p l e 
}

原文链接:zh.javascript.info/array zh.javascript.info/array-metho…

Symbol.iterable

从技术上来说,可迭代对象必须实现Symbol.iterable方法。Symbol.iterable方法会被for...of自动调用,也可以直接调用它。
通过自己创建一个对象,我们可以轻松地掌握可迭代的概念。

let range = {
    from: 1,
    to: 5,
};

接下来,我们为对象添加一个名为Symbol.iterable的方法。

let range = {
    from: 1,
    to: 5,
};

range[Symbol.iterator] = function () {

    return {
        current: this.from,
        last: this.to,

        next() {
            if (this.current <= this.last) {
                return { done: false, value: this.current++};
            } else {
                return { done: true };
            }
        }
    }
    
}

for (let num of range) {
    alert(num);
}
  1. for...of循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个迭代器(iterator) ——一个有next方法的对象。
  2. 从此开始,for...of仅适用于这个被返回的对象。
  3. for...of循环希望取得下一个数值,他就调用这个对象的next()方法。
  4. next()方法返回的结果的格式必须是{done: Boolean, value: any},当done=true时,表示迭代结束,否则value是下一个值。

显示调用迭代器

let str = "hello";

let iterator = str[Symbol.iterator]();

while(true){
    let result = iterator.next();
    if(result.done) break;
    alert(result.value);
}

可迭代(Iterable)和类数组(array-like)

  • Iterable,是实现了Symbol.iterator方法的对象。
  • Array-like,是有索引和length属性的对象。

我们可能会遇到可迭代对象或类数组对象,或两者兼有。
⭐ 字符串是可迭代的(for...of对他们有效),又是类数组的(它们有数值索引和length对象)
⭐ 上面例子中的range对象,是可迭代的,但不是类数组对象,它没有索引属性和length属性
⭐ 下面例子中的对象是类数组的,但不是可迭代的

let arrayLike = {
    0: "Hello",
    1: "World",
    length: 2
};

// Uncaught TypeError: arrayLike is not iterable
for(let item of arrayLike){
    alert(item);
}

可迭代对象和类数组对象通常都不是数组,它们没有pushpop等方法。

Array.from

Array.from方法,将可迭代对象或类数组对象转化为真正的数组,然后我们就可以对它应用数组的方法。

Array.from(obj [, mapFn, thisArg])

obj:想要转换成数组的类数组对象或可迭代对象
mapFn:如果指定了该参数,新数组中的每个元素会执行该回调函数
thisArg:执行回调函数mapFnthis对象。

例1:

let arrayLike = {
    0: "Hello",
    1: "World",
    length: 2
};

let arr = Array.from(arrayLike);

for(let item of arr){
    alert(item); // Hello   World
}

alert(arr.pop()); // World

例2: 接着上文举的range的例子

let arr = Array.from(range);
alert(arr); // 1,2,3,4,5
// 数组的toString转化方法生效

例3: 接着使用range可迭代对象

let arr = Array.from(range, num => num * num);

alert(arr); // 1,4,9,16,25 

例4:

let str = '𝒳😂';

let chars = Array.from(str);

alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2

例5:

function slice(str, start, end){
    return Array.from(str).slice(start, end).join("");
}

let str = "𝒳😂𩷶";

alert( slice(str, 1, 3) ); // 😂𩷶

// 原生方法不支持识别代理对(UTF-16 扩展字符)
alert( str.slice(1,3) ); // 乱码

原文链接:zh.javascript.info/iterable