创建数组的方式
数组对象字面量
const arr = [1,2,3];
new Array
如果参数是数字且是在合理范围内的数字;生成该参数长度的数组,里面内容是empty,empty在一些遍历中是不参与运算的;如果不是数字将参数作为数组元素
new Array('a') // ['a']
Array.from
可以传入一个类数组,或者一个可迭代的对象,比如set、map、已经实现了迭代器的对象等等
Array.of
Array.of() 和 Array() 构造函数之间的区别在于对单个参数的处理:Array.of(7) 创建一个具有单个元素 7 的数组,而new Array(7) 创建一个 length 为 7 的空数组
数组的一些特点
数组长度
一个逗号不代表成员,代表后面还能输入;逗号和逗号之间没有数据代表空元素
console.log([1,].length) // 1
console.log([1,,].length) // 2
console.log(new Array('10').length) // 1
console.log(new Array(10).length) // 10
清空数组的方法 Array.prototype.splice(0) 和 Array.prototype.length = 0
空元素
empty:数组的空位,指数组的某一个位置没有任何值;有空元素的数组的是稀疏数组
console.log([1,,])
V8引擎在存储稀疏数组的时候不是连续的空间,而是散列模式,在访问的时候会去计算hash相对慢一点,但是在存储空间上又会相对节约一些;而连续的空间可以使用索引访问,相对快
避免创建稀疏数组
Array.apply(null,Array(3));
[...new Array(3)]; // [undefined, undefined, undefined]
Array.from(Array(3))
数组不会自动添加分号
( { + - /作为一行代码的开头,不会和前面的代码做分割
const a = [1,2,3];
console.log(a);
[1,2,3].map((v)=>{
console.log(v*v)
})
const a = [1,2,3];
console.log(a) // 这里去掉分号了
[1,2,3].map((v)=>{
console.log(v*v)
})
console.log返回值是undefined,不能被遍历
includes和indexOf
const arr1 = [NaN];
console.log("includes NaN:",arr1.includes(NaN));
console.log("indexOf NaN:",arr1.indexOf(NaN) > -1);
const arr2 = [1,,];
console.log("includes empty:",arr2.includes(undefined));
console.log("indexOf empty:",arr2.indexOf(undefined)>-1);
const arr3 = [undefined];
console.log("includes undefined:",arr3.includes(undefined));
console.log("indexOf undefined:",arr3.indexOf(undefined)>-1);
数组可变长度问题
- length代表数组中的元素个数,数组额外附加属性不计算在内,比如arr.a,因为数组本身也是对象
- length可写,可以通过修改length改变数组长度,比如设置arr.length = 0
- 数组操作不存在越界,找不到下标返回undefined
const array = [1,2,3,4,5,6];
array[10] = 10;
console.log("length:",array.length);
array['test'] = 11;
console.log("length:",array.length);
array.length = 3;
console.log("length:",array.length);
console.log("value:",array[Number.MAX_VALUE + 1000]);
数组中可以改变自身的方法
1、删减操作:pop、shift、splice
2、unshift、push、sort、reverse
3、copyWithin、fill
特别的sort
const array = [1,2,3,4,5,6,7,8,9,10];
array.sort();
console.log(array) // [1, 10, 2, 3, 4, 5, 6, 7, 8, 9]
console.log("10:".charCodeAt()) // 49
console.log("1:".charCodeAt()) // 49
非线性存储问题
默认情况下是线性存储的,下标有序存取;我们尽量有序递增,否则将改变存储规则影响性能
delete删除数组
不会改变数组长度,只是删除该下标的引用
concat性能问题
复制大量数据的时候,push性能比concat好很多
const count = 10000;
const arr = [1, 2, 3, 4, 5];
let newArr = [];
console.time("push");
for(let i=0;i<count;i++){
newArr.push(arr[0],arr[1],arr[2],arr[3],arr[4]);
}
console.timeEnd("push")
// 大概在1秒左右
const count = 10000;
const arr = [1, 2, 3, 4, 5];
let newArr = [];
console.time("concat");
for(let i=0;i<count;i++){
newArr.concat(arr[0],arr[1],arr[2],arr[3],arr[4]);
}
console.timeEnd("concat")
// 大概在3秒左右
批量制造数组
for循环
function creatData() {
const res = [];
for(let i=0;i<1000;i++){
res.push({
name:`name${i+1}`
})
}
return res;
}
console.log(creatData())
特别注意使用map
创建出来的empty不参与运算,如map、filter;详情看juejin.cn/post/712722…
function creatData() {
return new Array(1000).map((v,i)=>{
return {name:`name${i+1}`}
})
}
console.log(creatData())
修改:先填充null再修改
function creatData() {
return new Array(1000).fill(null).map((v,i)=>{
return {name:`name${i+1}`}
})
}
console.log(creatData())
数据量大必然会导致性能问题
Array.from
从[可迭代]或[类数组]对象创建一个新的浅拷贝的数组实例;接收的第二个参数,可以对数组中的每个元素做处理
function creatData() {
return Array.from({length:1000},(v,i)=>{
return {name:`name${i+1}`}
})
}
console.log(creatData())
数组去重
set
const arr1 = [1,2,3];
const arr2 = [3,4,5];
console.log(new Set([...arr1,...arr2]))
// Set(5) {1, 2, 3, 4, 5}
使用for循环
for+indexOf:indexOf不能对NaN去重,可以换成includes,数据量大也会造成性能问题
function mergeArray(arr1,arr2) {
// 复制数组
const arr = arr1.slice(0);
let v;
for(let i=0;i<arr2.length;i++){
v=arr2[i];
if(~arr.indexOf(v)){
continue;
}
arr.push(v)
}
return arr;
}
数组对象去重
function uniqueArray(arr = [],key) {
const keyValues = new Set();
let val;
return arr.filter(obj => {
val = obj[key];
if(keyValues.has(val)){
return false;
}
keyValues.add(val);
return true;
})
}
类数组
是一个具有length属性和从零开始索引的属性,但是没有Array的内置方法,比如forEach、map等的一种特殊对象
特点:
- 是一个普通对象
- 必须有length属性,可以有非负整数索引
- 本身不具备数组的方法
常见的类数组:
- arguments
- DOM,如NodeList、HTMLCollection、DOMTokenList
特别的字符串
具备类数组的所有特征,但类数组一般指对象
数组和类数组的区别
代码判断是否是类数组
function isArrayLikeObject(arr) {
if(arr == null || typeof arr !=="object"){
return false;
}
const lengthMaxValue = Math.pow(2,53) - 1;
// 需要自身带有length属性
if(!Object.prototype.hasOwnProperty.call(arr,"length")) return false;
// 并且length属性的值可以转换成数字
if(typeof arr.length != "number") return false;
// 是否是有限的length值
if(!isFinite(arr.length)) return false;
if(Array === arr.constructor) return false;
if(arr.length >=0 && arr.length < lengthMaxValue){
return true;
}else {
return false;
}
}
类数组转换成数组
slice和concat
const arrayLikeObj = {
0:1,
1:2,
length:2,
}
console.log(Array.prototype.slice.call(arrayLikeObj)); // [1, 2]
console.log(Array.prototype.concat.apply([],arrayLikeObj));// [1, 2]
console.log(Array.prototype.slice.call("arrayLikeObj"));
// ['a', 'r', 'r', 'a', 'y', 'L', 'i', 'k', 'e', 'O', 'b', 'j']
Array.from
console.log(Array.from(arrayLikeObj)) // [1,2]
Array.apply
console.log(Array.apply(null,arrayLikeObj)) // [1,2]
万能数据生成器
const createValues = (creator,length = 10) => Array.from({length},creator)
随机数生成器
const createRandomValues = (len) => createValues(Math.random,len);
序列生成器
const createRange = (start,end,step) => createValues((_,i) => start + (i*step), (end-start) / step + 1);
数据生成器
const createUser = (_,i) => {
return {
name:`user-${i}`,
age:Math.random()*100 >> 0 // 取整
}
}
console.log(createValues(createUser),5)
求数组交集
Array.prototype.filter + includes;不能用于引用数据类型;includes性能消化较大;单次使用includes没有问题,在遍历中多次使用性能很差
function intersectSet(arr1,arr2) {
return [...new Set(arr1)].filter(item => arr2.includes(item))
};
引用类型:Array.prototype.forEach + Map + Key + filter唯一
function intertsect(arr1,arr2,key) {
const map = new Map();
arr1.forEach((val)=> map.set(val[key]));
return arr2.filter(val => map.has(val[key]))
};
求差集
function intertsect(arr1,arr2,key) {
const map = new Map();
arr1.forEach((val)=> map.set(val[key]));
return arr2.filter(val => !map.has(val[key]))
};
数组中去除虚值
虚值是指转为布尔为false的值
Array.prototype.filter(Boolean)
const arr = [false,0,undefined,,"",NaN,9,true,null,false,"test"];
console.log(arr.filter(Boolean)) // [9,true,'test']
获取数组中的最大值和最小值
Math.max.apply(Math,arr)
Math.min.apply(Math,arr)
reduce
获取queryString
queryString是用于页面传递参数;是请求地址问号后拼接的键值对
现代浏览器已有的API:URLSearchParams 和 URL
const urlSP = new URLSearchParams(location.search);
function getQueryString(key) {
return urlSP.get(key);
}
console.log("words:",getQueryString('words'))
const urlObj = new URL(location.href);
function getQueryString(key) {
return urlObj.searchParams.get(key);
}
console.log("words:",getQueryString('words'))
// urlObj.searchParams instanceof URLSearchParams ==> true 其实他们来自一个东西
reduce实现
将location.search取问号后面的参数按照&分割,结果再按照=分割即可得到queryString的键值对
const urlObj = location.search.slice(1).split("&").filter(Boolean)
.reduce((obj,cur)=>{
const arr = cur.split("=");
if(arr.length !=2){
return obj;
}
// 处理可能出现的中文
obj[decodeURIComponent(arr[0])] = decodeURIComponent(arr[1]);
return obj;
},{});
function getQueryString(key) {
return urlObj.searchParams.get(key);
}
折上折
草民版:
function discount(x) {
return x*0.9;
}
function reduce(x) {
return x>200 ? x-50:x;
}
console.log(reduce(discount(100))); // 90
console.log(reduce(discount(230))); // 157
黄金版:无法适应变化;需要优化getPriceMethods方法实现传入函数的返回值刚好是下一个函数的入参
function discount(x) {
return x*0.9;
}
function reduce(x) {
return x>200 ? x-50:x;
}
function getPriceMethods(discount,reduce) {
return function getPrice(x) {
return reduce(discount(x))
}
}
const method = getPriceMethods(discount,reduce)
console.log(method(100));
console.log(method(230));
王者:
function discount(x) {
return x*0.9;
}
function reduce(x) {
return x>200 ? x-50:x;
}
function discountPlus(x) {
return x*0.95
}
function compose(...funcs) {
if(funcs.length==0) return arg => arg;
// 注意执行顺序
return funcs.reduce((a,b) => (...args) => a(b(...args)))
}
const method = compose(discountPlus,reduce,discount)
console.log(method(100));
console.log(method(230));
例子3
假设需要先登录,再获取用户信息,再获取订单;刚好前者的返回值是下一个函数的入参;伪代码如下
function login(...args): Promise<any> {};
function getUesrInfo(...args): Promise<any> {};
function getorders(...args): Promise<any> {};
const order = async (loginParams) {
const loginRes = await login(loginParams);
const userRes = await getUesrInfo(loginRes);
const orderRes = await login(userRes);
console.log(orderRes)
}
升级:
function runPromise(promiseCreators,initData) {
return promiseCreators.reduce((promise,next)=>{
return promise.then((data)=>next(data))
},Promise.resolve(initData))
}
runPromise([login,,getUesrInfo,getorders],loginParams).then((data)=>{
console.log(data);
})
数组分组
const hasOwn = Object.prototype.hasOwnProperty;
function group(arr,fn) {
if(!Array.isArray(arr)){
return arr;
}
if(typeof fn !== "function"){
throw new TypeError("fn必须是一个函数");
}
let v;
return arr.reduce((obj,cur,index)=>{
v=fn(cur,index);
if(!hasOwn.call(obj,v)){
obj[v] = [];
};
obj[v].push(cur);
return obj;
},{})
}
console.log(group(['apple','pear','orange','peach'],v=>v.length))
手写
Array.isArray
作用:确定传递的值是否是Array
const arr = ['1'];
console.log(Array.isArray(arr)) // true
特别的proxy对象代理:如果是个代理对象,看代理对象的target属性是不是Array;proxy虽然使用new,但是他不是一个构造函数,只是将传入内容做了代理
const arr = ['1'];
const proxy = new Proxy(arr,{});
console.log(proxy)
console.log(Array.isArray(proxy))
以下几种方式都是可以的
function myIsArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function myIsArray(arr) {
if(typeof arr !== 'object' || arr === null) return false;
return arr instanceof Array;
}
function myIsArray(arr) {
return Array.prototype.isPrototypeOf(proxy);
}
Array.prototype.entries
返回一个新的Array Iterator对象,该对象中包含数组中的每个索引的键值对
const arr = [1,undefined,null,{}];
const iter = arr.entries();
console.log(iter.next());
for(let [k,v] of iter) {
console.log(k,v)
}
实现next
Array.prototype.myEntries = function () {
const obj = Object(this);
let index = 0;
const len = obj.length;
return {
next() {
if(index < len){
return { value:[index,obj[index++]],done:false};
}
return {value:undefined,done:true};
}
}
}
const arr = [1,2,3];
const iter = arr.myEntries();
console.log(iter.next())
实现迭代器
对象obj是可遍历的(iterable),因为具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法,都会返回一个代表当前成员的信息对象,具有value和done两个属性。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
完整实现
Array.prototype.myEntries = function () {
const obj = Object(this);
let index = 0;
const len = obj.length;
function next() {
if(index < len){
return { value:[index,obj[index++]],done:false};
}
return {value:undefined,done:true};
}
return {
next,
[Symbol.iterator](){
return {next};
}
}
}
const arr = [1,2,3];
const iter = arr.myEntries();
for (const [k,v] of iter) {
console.log(k,v)
}
其实数组上很多方法都有迭代器,比如arr.keys等,所以需要将迭代器实现到数组的原型
Array.prototype[Symbol.iterator] = function () {
const obj = Object(this);
let index = 0;
const len = obj.length;
function next() {
if(index < len){
return { value:[index,obj[index++]],done:false};
}
return {value:undefined,done:true};
}
return {
next
}
}
再实现Array.prototype.entries:
Array.prototype.myEntries = function () {
const obj = Object(this);
const len = obj.length;
const entries = [];
for(let i=0;i<len;i++){
entries.push([i,obj[i]])
}
const itr = this[Symbol.iterator].bind(entries)();
return {
next:itr.next,
[Symbol.iterator]() {return itr}
}
}
const arr = [1,2,3];
const iter = arr.myEntries();
for (const [k,v] of iter) {
console.log(k,v)
}
Array.prototype.includes
特点是能识别NaN
const arr = [1,2,3,null,undefined,NaN,""];
console.log(arr.includes(null)) // true
console.log(arr.includes(undefined)) // true
console.log(arr.includes(NaN)) // true
第二个参数position的特点:
1、如果不是数字变成数字
2、如果是NaN变成0
3、如果是正无穷返回false,负无穷返回0
4、返回与number符号相同且大小为floor(abs(number))的数值
5、如果是undefined变成0
6、设start为min(max(pos, 0), len)
Number.isNaN = function (param) {
if(typeof param === "number"){
return isNaN(param);
}
return false;
}
function toIntegerOrInfinity(params) {
const num = Number(params);
if(Number.isNaN(num) || num == 0){
return 0;
}
if(num === Infinity || num == -Infinity){
return num;
}
const inter = Math.floor(Math.abs(num));
return num < 0 ? -inter : inter;
}
Array.prototype.myIncludes = function(item,fromIndex) {
if(this == null){
throw new TypeError('无效的this')
}
const obj = Object(this);
const len = obj.length;
if(len <=0){
return false;
}
const n = toIntegerOrInfinity(fromIndex);
if(fromIndex === undefined){
return 0;
}
if(n === Infinity){
return false;
}
if(n === -Infinity){
return 0;
}
let k = n > 0 ? n : len+n;
if(k<0){
k=0;
}
const isNaN = Number.isNaN(item);
for(let i=k;i<len;i++){
if(obj[i] === item){
return true;
}else if(isNaN && Number.isNaN(obj[i])){
return true
}
}
return false;
}
Array.from
从可迭代或类数组对象创建一个新的浅拷贝的数组实例,接收三个参数Array.from(arrayLike, mapFn, thisArg)
arrayLike:必填,想要转换成数组的类数组或可迭代对象,也可以是字符串
mapFn:可选,调用数组每个元素的函数。如果提供,每个将要添加到数组中的值首先会传递给该函数,然后将 mapFn 的返回值增加到数组中
thisArg:可选,执行 mapFn 时用作 this 的值
等同于Array.from(obj).map(mapFn,thisArg)
console.log(Array.from({}))
console.log(Array.from(""))
console.log(Array.from({a:1,length:'10'}))
console.log(Array.from({a:1,length:'s'})) // length不能转换成数字
const max = Math.pow(2,32);
console.log(Array.from({0:1,length:max - 1}))
// 浏览器不会报错,但是会崩溃
console.log(Array.from({0:1,length:max}))
// 浏览器会报错
const toIntegerOrInfinity = function (value) {
const number = Number(value);
if(isNaN(number)) return 0;
// 判断是否是无穷
if(number == 0 || !isFinite(number)) return number;
return (number > 0 ? 1 : -1)*Math.floor(Math.abs(number));
}
const ToLength = function (value) {
const len = toIntegerOrInfinity(value);
return Math.min(Math.max(len,0),Number.MAX_SAFE_INTEGER)
}
const isCallable = function (fn){
return typeof fn === "function" || Object.prototype.toString.call(fn) === "[object Function]";
}
Array.myForm = function(arrayLike,mapFn,thisArg){
const C = this;
if(arrayLike == null){
throw new TypeError('需要传入一个对象')
}
if(typeof mapFn !== 'undefined' && typeof mapFn != "function"){
throw new TypeError('如果传入mapFn必须是个方法')
}
const items = Object(arrayLike);
const len = ToLength(items.length);
if(len <= 0){
return [];
}
// 判断c是否可调用
const A = isCallable(C) ? Object(new C(len)) : new Array(len);
for(let i=0;i<len;i++){
const value = items[i];
if(mapFn){
A[i] = typeof thisArg === 'undefined' ? mapFn(value,i) : mapFn.call(thisArg,value,i);
}else {
A[i] = value;
}
}
return A;
}
function MyArray(len) {
return new Array(len * 2);
}
console.log(Array.myForm.call(MyArray,{length:5}))