数组的声明方式有两种:前端
字面量方式
:var arr = []数组构造函数
构造数组:var arr = new Array()以上两种没啥区别,注意:若在构造函数里面只写一个数字 new Array(5)
时这个数字不是第一个值是 5
的意思,而是新建立的这个数组长度是 5
node
var arr = new Array(10);
console.log(arr) // (10) [empty × 10]
复制代码
不能够溢出读
能够溢出写
var arr = [1,2];
console.log(arr[3]) // undefined
arr[5] = 5;
console.log(arr) // (6) [1, 2, empty × 3, 5]
复制代码
reverse
:使数组倒序
push
:在数组的末尾增长数据,数据类型、数量不限,返回增长后的数组长度 pop
:从数组末尾删除一位数据,同时返回这个被删除的数据,没有参数
shift
:从数组最前面删除一位数据,同时返回这个数据,没有参数
unshift
:在数组最前面添加数据,和 push 同样的用法
splice
web
splice (从第几位开始, 截取多少长度,在切口处添加新的数据)数组
const a = [1,2,3,4,5,6,7];
a.splice(0, 3) // 从 0 开始删除 3 个元素返回 [1, 2, 3]
console.log(a) // [4, 5, 6, 7]
a.splice(-1, 1) // 从 -1 开始日后删除 1 个元素返回 [7]
console.log(a) // [1,2,3,4,5,6]
a.splice(0, 2, '添加的元素') // 返回 [1, 2]
console.log(a) // ['添加的元素',3,4,5,6,7]
复制代码
sort
:对数组的元素进行排序,能够在这个方法中传入一个参数(一个函数),该函数可自定义排序规则,不然就按照 ASCII
码来排序浏览器
concat
:链接多个数组,返回新的数组
join
:让数组的每个数据以传入参数做为分隔符链接成字符串markdown
join
链接成字符串便可split
操做恰好和 join
操做相反,split
是把字符串以某种方式分割成数组slice
:slice(从该位开始截取, 截取到该位)
,返回选定元素数据结构
filter
:这个方法起过滤做用,它一样不会改变原数组,而是返回一个原数组的子集,一样会传递一个方法,每一个元素都会调用这个方法,只有返回 true 的元素才会被添加到新数组里,返回 false 的则不会闭包
some
、every
:函数
every
是若每一个元素通过传递的方法断定以后都返回 true,则最后才返回 truesome
是只要有一个元素返回 true,那么就返回 truereduce
:使用指定的函数将数组元素进行组合,最后变成一个值post
/* total: 必需,初始值或计算结束后的返回值 currentValue: 必需,当前元素 currentIndex: 可选,当前元素的索引 arr: 可选,当前元素所属的数组对象 initialValue: 可选,传递给函数的初始值,至关于 total 的初始值 */
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);
复制代码
reduce 的用法可参考:juejin.cn/post/684490…
map
:可传递一个指定的方法,让数组中的每一个元素都调用一遍这个方法,最后返回一个新数组,注意:map
方法最后有返回值
既然 map
不会改变原数组,那 forEach
呢?之前查 map
和 forEach
的区别时常常看到这样一句话:
forEach() 方法不会返回执行结果,而是 undefined,即 forEach() 会修改原来的数组,而 map() 方法会获得一个新的数组并返回
个人理解是使用 forEach
遍历一个数组,修改数组 item 的值就会改变原数组,但最近看到一些文章说 forEach
并不必定会改变原数组, 所以作了一些测试以下
原始数据类型 -> 不会改动原数组
const arr = [1, 2, 3, 4];
arr.forEach(item => {
item = item * 3;
})
console.log(arr); // [1,2,3,4]
复制代码
引用类型 -> 相似对象数组能够改变
const arr = [
{
name: 'aa',
age: 18
},
{
name: 'bb',
age: 20
}
];
arr.forEach(item => {
if(item.name === 'aa') {
item.age = 25;
}
})
console.log(arr); // [{name: "aa", age: 25}, {name: "bb", age: 20}]
复制代码
此时若想要操做里面的基本数据类型,就用 arr[index]
的形式赋值改变便可
let arr = ['1',1,{'1': 1},true,2]
arr.forEach((item,index)=>{
arr[index] = 2
});
console.log(arr); // [2, 2, 2, 2, 2]
复制代码
缘由:上面基本数据类型也被改变了,由于使用 forEach
方法时对于每一个数据都建立了一个变量 item
,操做的是 item
变量,对于基本数据类型 item
变量就是新建立的一个内存,item
变量改变并不影响基本原来地址的改变,而 item
变量对应的是引用数据类型时,实际仍是一个引用地址,操做它仍旧操做的是对应的堆内存
提问:map
真的不会改变原数组吗?
const arr = [1, 2, 3]
const result = arr.map(item => {
item = item * 2;
return item;
});
console.log('arr', arr); // [1, 2, 3]
console.log('result', result); // [2, 4, 6]
复制代码
能够看到,item
虽然从新被赋值成了 item * 2
,但最后打印的结果显示 arr
并无改变。这彷佛印证了 map
真的不会改变原数组。别着急,再来测试一下当数组元素为 引用类型
的状况
const arr = [
{ name: 'Tom', age: 16 },
{ name: 'Aaron', age: 18 },
{ name: 'Denny', age: 20 }
]
const result = arr.map(item => {
item.age = item.age + 2;
return item;
});
console.log('arr', arr);
console.log('result', result);
复制代码
获得的结果以下图,能够看到原数组也被改变了
所以经过上面的例子能够得出结论,map 不会改变原始数组
的说法并不严谨,而应该说当数组中元素是原始值类型,map 不会改变原数组;是引用类型时则会改变原数组
(1)
map
方法体现的是数据不可变的思想,该思想认为全部的数据都是不能改变的,只能经过生成新的数据来达到修改的目的,所以直接对数组元素或对象属性进行操做的行为都是不可取的
(2)这种思想其实有不少好处,最直接的就是避免了数据的隐式修改,immutable.js
是实现数据不可变的一个库,可经过专属的API
对引用类型进行操做,每次造成一个新的对象
正确的作法应该是声明一个新变量来存储 map
的结果,而不是去修改原数组
const arr = [
{ name: 'Tom', age: 16 },
{ name: 'Aaron', age: 18 },
{ name: 'Denny', age: 20 }
];
const result = arr.map(item => ({
...item,
age: item.age + 2
}));
console.log('arr', arr);
console.log('result', result);
复制代码
forEach
和map
不修改调用它的原数组自己,可是能够在callback
执行时改变原数组
数组里的数据是如何引用的呢?
- JS 的数据有基本数据类型和引用数据类型,同时引出堆内存和栈内存的概念
- 对于基本数据类型,它们在栈内存中直接存储变量名和值
- 而引用数据类型的真实数据存储在堆内存中,它在栈内存中存储的是变量名和堆内存的地址。一旦操做了引用数据类型,实际操做的是对象自己,因此数组里的数据相应改变
上面的测试都是修改原数组中某个对象元素的某个属性,若直接修改数组的某个对象呢?
const arr = [
{
name: 'aa',
age: 18
},
{
name: 'bb',
age: 20
}
];
// forEach
// 注意,改变单次循环整个 item 是无效的
arr.forEach(item => {
if(item.name === 'aa') {
item = {
name: 'cc',
age: 30
};
}
})
console.log(arr); // [{name: "aa", age: 18}, {name: "bb", age: 20}]
// map
const arr1 = arr.map(item => {
item = {
name: 'cc',
age: 30
}
return item;
})
console.log(arr1, arr);
// [{name: "cc", age: 30}, {name: "cc", age: 30}]
// [{name: "aa", age: 18}, {name: "bb", age: 20}]
复制代码
这是由于不管是 forEach
仍是 map
,所传入的 item
都是原数组所对应的对象的地址,当修改 item
某一个属性后指向这个 item
对应的地址的全部对象都会改变。但若直接将 item
从新赋值, 则会另开辟内存存放,那 item
就和原数组所对应的对象没有关系了, 不论如何修改 item
, 都不会影响原数组
索引属性
访问元素且拥有 length
属性的对象,没有数组的其余方法,如 push、forEach、indexOf
等,一旦使用会报错// 一个简单的类数组对象
const arrLike = {
0: 'JavaScript',
1: 'Java',
2: 'Python',
length: 3
}
复制代码
const arr = ['JavaScript', 'Java', 'Python'];
// 访问
console.log(arr[0]); // JavaScript
console.log(arrLike[0]); // JavaScript
// 赋值
arr[0] = 'new name';
arrLike[0] = 'new name';
// 获取长度
console.log(arr.length); // 3
console.log(arrLike.length); // 3
复制代码
类数组的精妙在于它和 JS 原生的 Array
相似,可是它是自由构建的。它来自开发者对 JS 对象的扩展,即对于它的原型 prototype
咱们能够自由定义,而不会污染到 JS 原生的 Array
nodeList[0]
能够取到第一个子元素。但当咱们用console.log(nodeList instanceof Array)
则会返回 false
,也就是说它并非数组的实例,即不是数组咱们常常会遇到各类类数组对象,最多见的即是 argumengs
,arguments
是一个经典的类数组对象。在函数体中定义了 Arguments
对象,其包含函数的参数和其它属性,以 arguments
变量来指代,如
function fn(name, age, job) {
console.log(arguments);
}
fn('tn', '18', '前端')
复制代码
在控制台打印结果如图
能够看到 arguments
中包含了 函数传递的参数
、length
、 callee
等属性
length
属性表示的是实参的长度,即调用函数时传入的参数个数callee
属性则指向函数自己,可经过它来调用函数自身。在一些匿名函数或当即执行函数里进行递归调用函数自己时,因为该函数没有函数名,不能用函数名的方式调用,就能够用 arguments.callee
来调用Array.prototype.slice.call(arguments)
:若不传参数则就是返回原数组的一个拷贝
Array.prototype.slice.call(arrayLike).forEach(function(item, index){
...
})
复制代码
Array.prototype.slice.call(arguments)
至关于 Array.prototype.slice.call(arguments, 0)
,借用了数组原型中的 slice
方法,经过 call
显式绑定把一个数组(或类数组)的子集,做为一个数组返回。因此当后面的做用对象是一个类数组时,就会把这个类数组对象转换为了一个新的数组,至关于赋予了 arguments
这个对象 slice
方法
除了使用 Array.prototype.slice.call(arguments)
,也能够简单的使用 [].slice.call(arguments)
来代替
// 一个通用的转换函数
const toArray = (arrLike) => {
try {
return Array.prototype.slice.call(arrLike);
} catch(e) {
let arr = [];
for(let i = 0, len = arrLike.length; i < len; i ++) {
arr[i] = arrLike[i];
}
return arr;
}
}
复制代码
类数组只有索引值和长度,没有数组的各类方法,若要类数组调用数组的方法,可使用 Array.prototype.method.call
来实现
const a = {'0':'a', '1':'b', '2':'c', length:3}; // 类数组
Array.prototype.join.call(a, '+'); // "a+b+c"
Array.prototype.slice.call(a, 0); // ["a", "b", "c"]
Array.prototype.map.call(a, function(x) {
return x.toUpperCase();
}); // ['A','B','C']
复制代码
Array.from
:Array.from()
是 ES6
中新增的方法,能够将两类对象转为真正的数组:类数组对象和可遍历对象(部署了 Iterator
接口的数据结构),包括 ES6 新增的数据结构 Set
和 Map
不考虑兼容性的状况下,只要有 length
属性的对象均可用此方法转换成数组
const arr = Array.from(arguments);
复制代码
扩展运算符 ...
:ES6
中的扩展运算符 ...
也能将某些数据结构转换成数组,这种数据结构必须有 遍历器接口(Symbol.iterator)
,若一个对象没有部署这个接口就没法转换
const args = [...arguments];
复制代码
Array.from(object)
和上文提到的 Array.from(arguments)
相似,注意
object
中必须有 length
属性,返回的数组长度取决于 length
长度 ,若没有 length
属性,则转换后的数组是一个空数组key
值必须是 数值型或字符串型的数字
,如 {1:"bar"}
或 {"1":"bar"}
而不是 {"name":"bar"}
length
必须大于最大 key
值,若 key
值大于 length
,不在 Array.from
返回的浅拷贝数组里// obj 没有 length 值
const obj = { 1: 'bar', 2: 42 };
Array.from(obj) //[]
// obj 有 length 值
const obj = { 1: 'bar', 2: 42 ,length:4};
Array.from(obj) //[undefined, "bar", 42, undefined]
// obj 有 length 值,但 key 值不是数值
const obj = { name: 'bar', age: 42 ,length:2};
Array.from(obj) //[undefined,undefined]
// obj 有 length 值,key 值是数值,且 key 值在 length 内
const obj = { 1: 'bar', 2: 42 ,length:4};
Array.from(obj) //[undefined, "bar", 42, undefined]
// obj 有 length 值,key 值是数值且 key 值不在 length 内
const obj = { 8: 'bar', 6: 42 ,length:4};
Array.from(obj) //[undefined, undefined, undefined, undefined]
复制代码
Object.values(object)
与 Array.from
不一样的是 Object.values
不须要 length
属性,返回一个对象全部可枚举属性值
//返回结果根据对象的 values 大小从小到大输出
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj); // ["b", "c", "a"]
复制代码
Object.keys(object)
返回一个对象自身的可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致
//返回结果根据对象的 keys 大小从小到大输出
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.keys(obj); // ["2", "7", "100"]
复制代码
Object.entries(object)
返回一个给定对象自身可枚举属性的键值对数组
const obj16 = { foo: 'bar', baz: 42 };
Object.entries(obj16); // [["foo", "bar"], ["baz", 42]]
复制代码
for…in
// 返回对象 key
function getObjKeys(obj) {
let keys = []
for(let prop in obj)
keys.push(prop);
return keys;
}
const obj = { foo: 'bar', baz: 42 };
console.log(getObjKeys(obj)); // ["foo", "baz"]
// 返回对象 value
function getObjValues(obj) {
let values = []
for(let prop in obj)
values.push(obj[prop]);
return values;
}
const obj = { foo: 'bar', baz: 42 };
console.log(getObjValues(obj)); // ["bar", 42]
复制代码
hasOwnPropery
方法能够判断某属性是不是该对象的实例属性for (var key in myObject) {
if(myObject.hasOwnProperty(key)){
console.log(key);
}
}
复制代码
JS 获取对象属性长度
const obj = { name: 'bar', age: 42};
// 获取可枚举属性的长度
Object.keys(obj).length
// 带有不可枚举属性
Object.getOwnPropertyNames(obj).length
复制代码
for…in
只遍历对象自身和继承的可枚举的属性
Object.keys()
返回对象自身的全部可枚举的属性的键名
JSON.stringify()
只串行化对象自身的可枚举的属性
Object.assign()
忽略 enumerable
为 false
的属性,只拷贝对象自身的可枚举的属性
在 ES5 中,有个 Array.isArray()
方法来检测是不是数组,在 ES5 以前要检测数据是不是数组类型仍是有点麻烦的
typeof
:数组和对象都会返回 object
,所以没法区分数组和对象
instanceof
用 instanceof
检测时,只要当前的构造函数的 prototype
属性出如今实例对象的原型链上(能够经过__proto__
在原型链上找到它),检测出来的结果都是 true
语法:[实例对象] instanceof [构造函数]
var oDiv = document.getElementById("div1");
// HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object
console.log(oDiv instanceof HTMLDivElement); // true
console.log(oDiv instanceof Node); // true
console.log(oDiv instanceof Object); // true
console.log([] instanceof Array); // true
console.log(/^$/ instanceof RegExp); // true
console.log([] instanceof Object); // true
复制代码
基本数据类型的值是不能用 instanceof
来检测
console.log(1 instanceof Number); // false
复制代码
如下可行是由于被封装成对象,因此 true
const num = new Number(1);
num instanceof Number; // true
复制代码
constructor
constructor
的原理其实和 instanceof
有点像,也是基于面向对象和原型链的。一个实例对象如果一个构造函数的实例的话,那它原型上的 constructor
其实也就指向了这个构造函数,能够经过判断它的 constructor
来判断它是否是某个构造函数的实例
console.log([].constructor === Array);// true
console.log([].constructor === Object); // false
// constructor 可避免 instanceof 检测数组时用 Object 也是 true 的问题
console.log({}.constructor === Object); // true
console.log([].constructor === Object); // false
复制代码
使用constructor
判断的时候要注意,若原型上的 constructor
被修改了,这种检测可能就失效了
function a() {}
a.prototype = {
x: 1
}
let b = new a();
b.constructor === a; // false
复制代码
上面为 false 的缘由是 constructor
这个属性实际上是 a.prototype
的属性,在给 a.prototype
赋值时其实覆盖了以前的整个 prototype
,也覆盖了 a.prototype.constructor
,这时压根就没有这个属性,若非要访问这个属性,只能去原型链上找,这时候会找到 Object
a.prototype.constructor === Object; // true
b.constructor === Object; // true
复制代码
要避免这个问题,咱们在给原型添加属性时最好不要整个覆盖,而是只添加须要的属性,上面的改成:
a.prototype.x = 1;
复制代码
若必定要整个覆盖,记得把 constructor 加回来
a.prototype = {
constructor: a,
x: 1
}
复制代码
到如今为止它们是好用的,但它们都存在潜藏问题:web
浏览器中可能有多个窗口或者窗体,每一个窗体都有本身的 JS 环境和全局对象且每一个全局对象有本身的构造函数,所以一个窗体中的对象将不多是另外窗体中的构造函数的实例。如:在 iframe
之间来回传递数组,而 instanceof
不能跨帧。虽然窗体之间的混淆并不常发生,但这个问题已经证实 constructor
和 instanceof
都不是真正可靠的检测数组类型
Object.prototype.toString.call(value)
找到 Object
原型上的 toString
方法,执行且让方法中的 this
指向 value
(value
就是要检测数据类型的值)
调用某个值的内置 toString()
方法在全部浏览器中都返回标准的字符串结果,对于数组来讲返回的字符串为 "[object Array]"
,这个方法对识别内置对象都很是有效
Object.prototype.toString.call([]) === "[object Array]";
复制代码
实现 is
系列检测函数:createValidType
函数使用闭包保存数据状态的特性,批量生成 is
系列函数
const dataType = {
'[object Null]' : 'null',
'[object Undefined]' : 'undefiend',
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regexp',
'[object Object]' : 'object',
'[object Error]' : 'error'
},
toString = Object.prototype.toString;
function type(obj) {
return dataType[toString.call(obj)];
}
// 生成 is 系列函数
function createValidType() {
for(let p in dataType) {
const objType = p.slice(8, -1);
(function(objType) {
window['is' + objType] = function(obj) {
return type(obj) === objType.toLowerCase();
}
})(objType)
}
}
createValidType();
console.log(isObject({})); // true
console.log(isDate(new Date())); // true
console.log(isBoolean(false)); // true
console.log(isString(1)); // false
console.log(isError(1)); // false
console.log(isError(new Error())); // true
console.log(isArray([])); // true
console.log(isArray(1)); // false
// 同时也实现了 type 函数,用以检测数据类型
onsole.log(type({})); // "object"
console.log(type(new Date())); // "date"
console.log(type(false)); // "boolean"
console.log(type(1)); // "number"
console.log(type(1)); // "number"
console.log(type(new Error())); // "error"
console.log(type([])); // "array"
console.log(type(1)); // "number"
复制代码