數組的聲明方式有兩種:前端
字面量方式
: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"
複製代碼