前端中的簡單程式設計題-陣列(1)

thomaszhou發表於2018-02-26

一邊學習前端,一邊通過部落格的形式自己總結一些東西,當然也希望幫助一些和我一樣開始學前端的小夥伴。

如果出現錯誤,請在評論中指出,我也好自己糾正自己的錯誤

前端中的簡單程式設計題-陣列(1)

author: thomaszhou

陣列的內建方法應用

判斷是否是陣列型別

很多時候我們需要對JavaScript中資料型別( Function、String、Number、Undefined、Boolean和Object)做判斷。在JavaScript中提供了typeof操作符可以對這些常用的資料型別做判斷。但要使用typeof來判斷資料是不是一個陣列,就不起作用了

     console.log(typeof function () {return;}); // function
	 console.log(typeof "a"); // string
	 console.log(typeof 123); // number
	 console.log(typeof a); //undefined
	 console.log(typeof true); // boolean
	 console.log(typeof NaN); // number
	 console.log(typeof !NaN); //boolean
	 console.log(typeof {name:"leo",age: "37"}); // object
	 console.log(typeof ["leo","37"]); // object
	 console.log(typeof null); // object
複製程式碼

es5的isArray()函式

var arr = [1,2,3,45];
function isArray(obj){
    return Array.isArray(obj);
	}
alert(isArray(arr));
複製程式碼

ECMAScript 5 還有著非常大的相容性,所以我們並不能完美的使用原生判斷,當使用ie6/7/8的時候就會出現問題。

construct和instanceof

  • constructor屬性返回對建立此物件的函式的引用,使用此屬性可以檢測陣列型別
  • 除了使用constructor自身屬性之外,還可以使用instanceof。 instanceof用來判斷某個建構函式的prototype是否存在於被檢測物件的原型鏈裡。也就是判斷前面的物件是否是後者物件的類或者例項。
var arr = [1,2,3,45];
console.log(arr.constructor === Array);

console.log(arr instanceof Array);
複製程式碼
  • 缺點:需要注意的是,當在不同的window或iframe裡構造的陣列時會失敗。這是因為每一個iframe都有它自己的執行環境,彼此之間並不共享原型鏈,所以此時的判斷一個物件是否為陣列就會失敗。此時我們有一個更好的方式去判斷一個物件是否為陣列。 當你在多個frame中回來跳的時候,這兩種方法就慘了。由於每一個frame都有自己的一套執行環境,跨frame例項化的物件彼此並不共享原型鏈,通過instanceof操作符和constructor屬性檢測的方法自然會失敗。

Object.prototype.toString

 var is_array = function(obj){
    return Object.prototype.toString.call(obj) === '[object Array]';
};
	
alert(is_array(arr));
複製程式碼

使用Object.prototype.toString方法來檢測物件型別。toString將返回[object type] type為物件型別。下面是物件檢測顯示的結果。

     var toString = Object.prototype.toString; 
     console.log(toString.call(dd));//[object Function] 
	 console.log(toString.call(new Object));//[object Object] 
	 console.log(toString.call(new Array));//[object Array] 
	 console.log(toString.call(new Date));//[object Date] 
	 console.log(toString.call(new String));//[object String] 
	 console.log(toString.call(Math));//[object Math] 
	 console.log(toString.call(undefined));//[object Undefined] 
	 console.log(toString.call(null));//[object Null]
複製程式碼

因此我們可以利用物件的Object.prototype.toString方法檢測物件是否為陣列。在frame下也通過。

  • 最佳檢測 -Array和Object.prototype.toString結合

最佳檢測方法就是,不管原生isArray是否可用,都回歸到object.prototype.toString的檢測上。

  • Object.prototype.toString的行為:首先,取得物件的一個內部屬性[[Class]],然後依據這個屬性,返回一個類似於"[object Array]"的字串作為結果(看過ECMA標準的應該都知道,[[]]用來表示語言內部用到的、外部不可直接訪問的屬性,稱為“內部屬性”)。利用這 個方法,再配合call,我們可以取得任何物件的內部屬性[[Class]],然後把型別檢測轉化為字串比較,以達到我們的目的。
  • call改變toString的this引用為待檢測的物件,返回此物件的字串表示,然後對比此字串是否是[object Array],以判斷其是否是Array的例項。為什麼不直接o.toString()?嗯,雖然Array繼承自Object,也會有toString方法,但是這個方法有可能會被改寫而達不到我們的要求,而Object.prototype則是老虎的屁股,很少有人敢去碰它的,所以能一定程度保證其“純潔性”:)

JavaScript 標準文件中定義: [[Class]] 的值只可能是下面字串中的一個:Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String。

 var isArray = function () {
    		if (Array.isArray) {
    			return Array.isArray;
    	    }
    	    //下面語法不明白
    		var objectToStringFn = Object.prototype.toString, arrayToStringResult = objectToStringFn.call([]);
    		return function (subject) {
    				return objectToStringFn.call(subject) === arrayToStringResult;
    	    };
  }();
  alert(isArray(arr));// true
複製程式碼

複製陣列

我們都知道陣列是引用型別資料。這裡使用slice,map複製一個陣列,原陣列不受影響。

let list1 = [1, 2, 3, 4];
let newList = list1.slice();//推薦slice
list1.push(5); // [1,2,3,4,5]
console.log(newList); //[1,2,3,4]
console.log(list1); //[1, 2, 3, 4, 5]

let list2 = [5,6,7,8];
let newList2 = list2.concat();
newList2.push(9); // 
console.log(newList2); //[5, 6, 7, 8, 9]
console.log(list2); //[1, 2, 3, 4, 5]
複製程式碼

轉換成陣列

類陣列(NodeList)轉陣列(Array),函式引數轉陣列

  • 通過三種方式實現:如slice,這裡加多陣列的from方法,ES6語法糖
getElementsByClassName獲取的dom元素的資料型別為HTMLCollection,雖然類似於陣列可以遍歷,但是該資料型別不存在陣列中用於運算元組的方法,通過y以下方法轉換後,就可以使用陣列的內建方法

//返回的不是真正的Array(你無法使用filter、map、reduce等方法)
const nodeList = document.querySelectorAll('div');  
// 方法1: 使用Array.from
const arrayList1 = Array.from(nodeList);
// 方法2: 使用slice
const arrayList2 = Array.prototype.slice.call(nodeList);
// 方法3: 使用ES6語法糖
const arrayList3 = [...nodeList];
複製程式碼
  • 函式引數 轉成 陣列

函式引數轉陣列 將函式引數轉陣列,利用arguments偽陣列形式,再用slice拷貝為新陣列。

function argsParam() {
     //arguments偽陣列形式,再用slice拷貝為新陣列
     return Array.prototype.slice.call(arguments);
}
 
console.log(argsParam(1,2,3,4)); //[1, 2, 3, 4]
複製程式碼
  • 那Array.Prototype.slice的內部實現是什麼?
    Array.prototype.slice = function(start,end){
       var result = new Array();
       start = start || 0;
       end = end || this.length; //this指向呼叫的物件,當用了call後,能夠改變this的指向,也就是指向傳進來的物件,這是關鍵
       for(var i = start; i < end; i++){
            result.push(this[i]);
       }
       return result;
    }
複製程式碼
  • 其他:Array.prototype.slice.call能將具有length屬性的物件轉成陣列,除了IE下的節點集合(因為ie下的dom物件是以com物件的形式實現的,js物件與com物件不能進行轉換)
var a = {length:2, 0:'zero', 1: 'first'};
    console.log(Array.prototype.slice.call(a));  // ["zero", "first"]

    var b = {length:3, 0:'zero', 1: 'first', 2 : 'zero'};
    console.log(Array.prototype.slice.call(b));  // ["zero", "first", "zero"]

    var c = {length:3, 2 : 'zero'};
    console.log(Array.prototype.slice.call(c));  //  [empty, empty , "zero"]

複製程式碼

重複n個字元

  • 利用Array建構函式傳參,再使用join函式分隔指定的字串
/**
    @params
    num: 重複次數
    str: 重複字串
**/
function repeatStr(num, str) {
    return new Array(num+1).join(str);
}

console.log(repeatStr(5, 's'));//sssss
複製程式碼

建立n乘n二位矩陣

建立n乘n二維矩陣,並初始化資料 使用Array物件傳入陣列length引數,呼叫fill再用map迴圈fill替換對應的值返回一個新陣列

/**
    @params
    num: 矩陣次數
    str: 矩陣陣列中的值,由於fill函式替換所以值都是一致的
**/
function arrayMatrix(num, matrixStr) {
    return Array(num).fill(null).map(() => Array(num).fill(matrixStr));
}
//  ["a", "a", "a", "a"]  ["a", "a", "a", "a"] ["a", "a", "a", "a"] ["a", "a", "a", "a"]
console.log(arrayMatrix(4, 'a'));
複製程式碼

陣列去重

!!! 方法12345對於數字1和字串1這類的問題,是無法去重的,但是方法6可以解決這個問題,但是也會導致需要設定最後是留下字串的1還是數字1

  • 法一:雙層for迴圈(不用新的記憶體空間-但是時間複雜度很高)
    • 第一層遍歷陣列,第二層用第一層得到的值來和後面所有的值依次比較,相同則用splice()方法從陣列中提除該重複元素。
  • 法二:遍歷陣列法(不用新的記憶體空間-但是時間複雜度很高)
    • 實現思路:新建一新陣列,遍歷傳入陣列,遍歷的值如果和新陣列內的元素不重複,就加入該新陣列中;注意:判斷值是否在陣列的方法“indexOf”是ECMAScript5 方法,以下不支援,需多寫一些相容低版本瀏覽器程式碼,
    • indexOf就是找到新陣列的第一個某值,方便查詢是否重複
function unique1(array){ 
var n = []; //一個新的臨時陣列 
//遍歷當前陣列 
for(var i = 0; i < array.length; i++){ 
//如果當前陣列的第i已經儲存進了臨時陣列,那麼跳過, 
//否則把當前項push到臨時陣列裡面 
if (n.indexOf(array[i]) == -1) 
n.push(array[i]); 
} 
return n; 
}

複製程式碼

通過unique1.apply(arr)或unique1.call(arr)呼叫,為什麼?

  • 法三:陣列下標判斷法
    • 還是得呼叫“indexOf”效能跟遍歷陣列法差不多,實現思路:如果當前陣列的第i項在當前陣列中第一次出現的位置不是i,那麼表示第i項是重複的,忽略掉。否則存入結果陣列
unction unique3(arr){
       var n = [];
       n.push(arr[0]);
       len = arr.length;
       //從第二項開始遍歷 
       for(var i = 1; i < len; i++) {
       //如果當前陣列的第i項在當前陣列中第一次出現的位置不是i, 
        //那麼表示第i項是重複的,忽略掉。否則存入結果陣列 
           if (arr.indexOf(arr[i]) == i) {
               n.push(arr[i]);
           }
       }
       return n;
   }
   console.log(unique3(arr));
複製程式碼
  • 方法四:排序後相鄰去除法
    • 雖然原生陣列的”sort”方法排序結果不怎麼靠譜,但在不注重順序的去重裡該缺點毫無影響。實現思路:給傳入陣列排序,排序後相同值相鄰,然後遍歷時新陣列只加入不與前一值重複的值。
function unique4(array){ 
    array.sort(); 
    var re=[array[0]]; 
    for(var i = 1; i < array.length; i++){ 
        if( array[i] !== re[re.length-1]){ 
            re.push(array[i]); 
        } 
    } 
    return re; 
} 
複製程式碼
  • 法五:物件鍵值對法(最優):思路如下
    • 建立一個JavaScript物件以及新陣列
    • 使用for迴圈遍歷原陣列,每次取出一個元素與JavaScript物件的鍵做對比
    • 如果不包含,將存入物件的元素的值推入到結果陣列中,並且將存入object物件中該屬性名的值設定為1
//    use 'strict';
var arr = ['1',1,2,3,4,4,45,65,'b','a','b'];
function unique4 (arr) {
    var newArr = [],// 構建一個新陣列存放結果
        object = {};
    len = arr.length;
    // for迴圈時,每次取出一個元素與物件進行對比 
    // 如果這個元素不重複,則將它存放到結果數中 
    // 同時把這個元素的內容作為物件的一個屬性,並賦值為1, 
    // 存入到第2步建立的物件中
    for (var i = 0; i < len; i++) {
    // 檢測在object物件中是否包含遍歷到的元素的值
        if (!object[typeof arr[i] + arr[i]]) {
        // 如果不包含,將存入物件的元素的值推入到結果陣列中
            object[typeof arr[i] + arr[i]] = 1
            //存入object物件中該屬性名的值設定為1
            newArr.push(arr[i]);
        }
    }
//    console.log(object);
    return newArr;
}
console.log(unique4(arr));
複製程式碼

test:

var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique3(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]
複製程式碼

不同的鍵可能會被誤認為一樣;例如: a[1]、a["1"] 。這種方法所費時間:621ms。這種方法所費時間是最短,但就是佔用記憶體大一些

  • 法六:不區分字串和數字的去重(物件鍵值法)
    • 物件的key,在存入的時候,就自動轉化成string型別,所以像前四種方法針對1和'1'是無法區分,但是如果將值放進物件的key中,1和'1'的比較就會程式設計'1'和'1'的比較,也就是i 字串間的比較
function unique4 (arr) {
       var newArr = [],
           object = {};
       len = arr.length;
       for (var i = 0; i < len; i++) {
         if (!object[arr[i]]) {
           object[arr[i]] = 1;
           newArr.push(arr[i]);
         }
       }
       console.log(object);
       return newArr;
   }
   console.log(unique4(arr));
複製程式碼
  • ES6去重

注意set返回的也是一個不重複的類陣列形式要使用Array.from或者別的方法方法轉成陣列形式

function unique (arr) { 
    return Array.from(new Set(arr)) 
}
或者:
const unique = arr => [...new Set(arr)];
複製程式碼

Array union (合併陣列去重)

用 a 和 b 的所有值建立一個 Set 並轉換成一個陣列。

const union = (a, b) => Array.from(new Set([...a, ...b]));
// union([1,2,3], [4,3,2]) -> [1,2,3,4]
複製程式碼

過濾陣列中的非唯一值

  • 將Array.filter()用於僅包含唯一值的陣列。
  • 思路:indexOf和lastIndexOf找的位置相同,那就是唯一的值
const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));

// console.log(filterNonUnique([1,2,2,3,4,4,5])); // [1,3,5]
複製程式碼

相關文章