js 基礎面試題

Array 有多少種經常使用方法

不改變array的方法

indexOf() 和lastIndexOf()

  1. indexof() : 返回元素在數組的第一次出現的索引,從0開始。若數組不存在該元素,則返回-1。
var arr = [1, 2, 2];
arr.indexOf(1); //0
arr.indexOf(10); //-1
複製代碼
  1. lastIndexOf(): 返回元素在數組最後一次出現的索引,若是沒有出現則返回-1.
var arr = [1, 2, 2];
arr.lastIndexOf(2); //2
arr.lastIndexOf(10); //-1
複製代碼

slice()方法

與字符串的substring()方法同樣,截取數組的一部分,返回一個新的數組。html

  1. slice(start)索引從start開始截取
var arr = [1, 2, 2, 5, 6];
arr.slice(2) // [2, 5, 6]
複製代碼
  1. slice(start,end)索引從start開始到索引end結束。一般,接受2個參數做爲一個左閉右開區間,即包括開始索引位置的元素,但不包括結束索引位置的元素。
var arr = [1, 2, 2, 5, 6];
arr.slice(1,3) // [2, 2]
複製代碼
  1. slice()沒有參數,則是複製整個數組。
var arr = [1, 2, 2, 5, 6];
arr.slice();
複製代碼

concat():合併數組。

把當前的數組和另外一個數組鏈接起來,並返回一個新的數組。es6

  1. 方法的參數能夠有多個,也能夠任意任意類型,數值、字符串、布爾值、數組、對象 均可以,參數會被被添加到新的數組中。
var arr1 =  [1, 2, 3,4,5,6];
var arr2 = ['a','b','c'];
var arr3 = arr1.concat(arr2);
arr3;   //[1, 2, 3, 4, 5, 6, "a", "b", "c"]
複製代碼
  1. 注意,若是參數是數組, 會被拉平一次,即數組會被拆開來,加入到新的數組中。具體看示例:
var arr1 = [1, 2, 3];
var arr2 = arr1.concat(66,'abc',true,[10,20],[30,[31,32]],{x:100});
arr2;  //[1, 2, 3, 66, "abc", true, 10, 20, 30, [31,32], {x:100}]
複製代碼

join(): 轉成字符串。

它會把當前Array的每一個元素都用指定的字符串鏈接起來,而後返回鏈接後的字符串。數組

  1. 參數是用來指定鏈接的字符串。見示例代碼:
var arr = [1, 2, 3];
arr.join('*')   //"1*2*3"
複製代碼
  1. 若是沒有指定參數,默認是用 "," 鏈接。
var arr = [1, 2, 3];
arr.join()   //"1,2,3"
複製代碼

toString(): 返回數組的字符串形式

var arr = [1, 2, 3];
arr.toString() // "1,2,3"
複製代碼

valueOf():返回數組自己

var arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]
複製代碼

map():

  1. 對數組的全部成員依次調用一個函數,返回值是一個新數組。
arr.map(function(elem, index, arr) {
    return elem * index;
}); 
//[0, 2, 6]
複製代碼
  1. map方法接受一個函數做爲參數,該函數調用時,map方法會將其傳入3個參數,分別是當前成員、當前位置和數組自己(後2個參數可選)。
arr.map(function(elem, index, arr) {
    return elem * index;
}); 
//[0, 2, 6]
複製代碼
  1. map方法還能夠接受第2個參數,表示回調函數執行時this所指向的對象。

forEach():

與map方法很類似,也是遍歷數組的全部成員,執行某種操做。注意:forEach方法通常沒有返回值瀏覽器

var arr = [1, 2, 3];
function log(element, index, array) {
    console.log('[' + index + '] = ' + element);
}
arr.forEach(log);
// [0] = 1
// [1] = 2
// [2] = 3
複製代碼

filter(): 刪選

var arr = [1, 2, 3, 4, 5];
arr.filter(function (elem, index, arr) {
  return index % 2 === 1;
});
//[2, 4]
複製代碼

some()和every()

相似「斷言」(assert),用來判斷數組成員是否符合某種條件。bash

  1. 接受一個函數做爲參數,全部數組成員依次執行該函數,返回一個布爾值。該函數接受三個參數,依次是當前位置的成員、當前位置的序號和整個數組。
  2. some方法是隻要有一個數組成員的返回值是true,則整個some方法的返回值就是true,不然false。
var arr = [1, 2, 3, 4];
arr.some(function (elem, index, arr) {
  return elem >= 3;
});
// true
複製代碼
  1. every方法則是全部數組成員的返回值都是true,才返回true,不然false。
var arr = [1, 2, 3, 4];
arr.every(function (elem, index, arr) {
  return elem >= 3;
});
// false
複製代碼
  1. 注意,對於空數組,some方法返回false,every方法返回true

reduce()和reduceRight():

依次處理數組的每一個成員,最終累計爲一個值。閉包

  1. reduce是從左到右處理(從第一個成員到最後一個成員)
arr = [1, 2, 3]
arr.reduce(function(x, y){
  console.log(x, y)
  return x + y;
});
// 1 2
// 3 3
// 6
複製代碼
  1. reduceRight則是從右到左處理(從最後一個成員到第一個成員)
rr.reduceRight(function(x, y){
  console.log(x, y)
  return x + y;
});
// 3 2
// 5 1
// 6
複製代碼

改變原數組的方法

push():

向數組的末尾添加若干元素。返回值是改變後的數組長度。app

var arr = [1, 2];
arr.push(3) ;// 3
arr; //  [1, 2, 3]
arr.push('b','c'); //5
arr; //[1, 2, 3, "b", "c"]
arr.push([10,20]); //6
arr; //[1, 2, 3, "b", "c", [10,20]]
複製代碼

pop()

刪除數組最後一個元素。返回值是刪除的元素。函數

var arr =[1, 2, 3, "b", "c", [10,20]];
arr.pop(); //[10, 20]
arr;  // [1, 2, 3, "b", "c"]
複製代碼

unshift()

向數組頭部添加若干元素。返回值是改變後的數組長度。ui

var arr = [1, 2];
arr.unshift(3,4 );  //4
arr;  // [3, 4, 1, 2]
複製代碼

shift()

刪除數組第一個元素。返回值是刪除的元素this

var arr = ['a', 'b', 1, 2];
arr.shift(); //'a'
arr;  //['b', 1, 2]
複製代碼

sort()

數組排序。

  1. 默認是將全部元素轉換成字符串,再按字符串Unicode碼點排序。返回值是新的數組。
var arr = [1, 2, 12, 'a', 'b', 'ab', 'A', 'B']
arr.sort();  //[1, 12, 2, "A", "B", "a", "ab", "b"] 注意:12排在了2的前面
複製代碼
  1. 若是元素都是數字,要按從小到大排序,能夠傳入一個回調函數做爲參數。
var arr = [1, 2, 12, 100]

arr.sort(function(a,b){
    return a-b;
});
// [1, 2, 12, 100]
複製代碼

reverse():

顛倒數組中元素的位置

var arr = [1, 2, 12, 'a', 'b', 'ab', 'A', 'B'];
arr.reverse();
//["B", "A", "ab", "b", "a", 12, 2, 1]
複製代碼

array.splice()

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

  • 參數 start 爲開始的索引,deletecount 表示要移除的數組元素的個數。item 爲要添加進數組的元素

若是 deleteCount 大於 start 以後的元素的總數,則從 start 後面的元素都將被刪除(含第 start 位)。 若是 deleteCount 被省略了,或者它的值大於等於array.length - start(也就是說,若是它大於或者等於start以後的全部元素的數量),那麼start以後數組的全部元素都會被刪除。 若是 deleteCount 是 0 或者負數,則不移除元素。這種狀況下,至少應添加一個新元素

  1. 只刪除,不添加。能夠傳入2個參數:
var arr = ['Alibaba', 'Tencent', 'Baidu', 'XiaoMi', '360'];

// 從索引2開始刪除3個元素
arr.splice(2, 3); // 返回刪除的元素 ['Baidu', 'XiaoMi', '360']
arr; // ['Alibaba', 'Tencent']
複製代碼
  1. 只添加,不刪除。第2個參數設爲0,即不刪除元素。
arr.splice(2, 0, 'Toutiao', 'Meituan', 'Didi'); // 返回[],由於沒有刪除任何元素
arr; //["Alibaba", "Tencent", "Toutiao", "Meituan", "Didi"]
複製代碼
  1. 先刪除若干元素,而後在刪除的位置上在添加若干個元素。
var  arr =["Alibaba", "Tencent", "Toutiao", "Meituan", "Didi"]
arr.splice(2,2,'Apple','Google');  //["Toutiao", "Meituan"]
arr; //["Alibaba", "Tencent", "Apple", "Google", "Didi"]
複製代碼

做用域與執行上下文的區別

  1. 做用域分全局做用域和函數做用域,因爲js沒有塊級做用域(es6裏規定了塊級做用域,詳情可自行查看),函數做用域能夠用於隔離變量,不一樣做用域下同名變量不會有衝突的。做用域只是一個「地盤」,做用域是一個抽象的概念,其中沒有變量。要經過做用域對應的執行上下文環境來獲取變量的值。做用域中變量的值是在執行過程當中產生的肯定的,而做用域倒是在函數建立時就肯定了。
  2. 執行全局代碼時,會產生一個執行上下文環境,每次調用函數都又會產生執行上下文環境。當函數調用完成時,這個上下文環境以及其中的數據都會被消除(固然了閉包並不會乖乖就範),處於活動狀態的執行上下文環境只有一個。 參考推薦: www.cnblogs.com/wangfupeng1…

this指向

參考:developer.mozilla.org/zh-CN/docs/… 函數的調用方式決定了this的值。記住調用方式決定了this的值。this不能在執行期間被賦值,而且在每次函數被調用時this的值也可能會不一樣。

this 的定義:當前執行代碼的環境對象

this 值的幾種區分狀況:

全局環境

不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象。

// 在瀏覽器中, window 對象同時也是全局對象:
console.log(this === window); // true

a = 37;
console.log(window.a); // 37

this.b = "MDN";
console.log(window.b)  // "MDN"
console.log(b)         // "MDN"
複製代碼

函數(運行內)環境

簡單調用:

  • 非嚴格模式下,this默認指向全局對象
function f1(){
  return this;
}
//在瀏覽器中:
f1() === window;   //在瀏覽器中,全局對象是window

//在Node中:
f1() === global;
複製代碼
  • 嚴格模式下,this將會默認爲undefined。
function f2(){
  "use strict"; // 這裏是嚴格模式
  return this;
}

f2() === undefined; // true
複製代碼

call/apply方法調用,改變this的指向

call和apply 均可以傳遞參數,call 是傳遞多個參數,而apply則傳數組傳遞參數。

// 將一個對象做爲call和apply的第一個參數,this會被綁定到這個對象。
var obj = {a: 'Custom'};

// 這個屬性是在global對象定義的。
var a = 'Global';

function whatsThis(arg) {
  return this.a;  // this的值取決於函數的調用方式
}

whatsThis();          // 'Global'
whatsThis.call(obj);  // 'Custom'
whatsThis.apply(obj); // 'Custom'
複製代碼

bind方法,改變this的指向

ECMAScript 5 引入了 Function.prototype.bind,調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數(注意永久綁定到第一個參數上,也就是無論綁定多少次bind,都是指向第一個參數),不管這個函數是如何被調用的。

function f(){
  return this.a;
}

var g = f.bind({a:"azerty"});
console.log(g()); // azerty

var h = g.bind({a:'yoo'}); // bind只生效一次!
console.log(h()); // azerty

var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
複製代碼

箭頭函數,改變this指向

在箭頭函數中,this與封閉詞法環境的this保持一致。在全局代碼中,它將被設置爲全局對象:

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
複製代碼

重難點:

// 建立一個含有bar方法的obj對象,
// bar返回一個函數,
// 這個函數返回this,
// 這個返回的函數是以箭頭函數建立的,
// 因此它的this被永久綁定到了它外層函數的this。
// bar的值能夠在調用中設置,這反過來又設置了返回函數的值。
var obj = {
  bar: function() {
    var x = (() => this);
    return x;
  }
};

// 做爲obj對象的一個方法來調用bar,把它的this綁定到obj。
// 將返回的函數的引用賦值給fn。
var fn = obj.bar();

// 直接調用fn而不設置this,
// 一般(即不使用箭頭函數的狀況)默認爲全局對象
// 若在嚴格模式則爲undefined
console.log(fn() === obj); // true

// 可是注意,若是你只是引用obj的方法,
// 而沒有調用它
var fn2 = obj.bar;
// 那麼調用箭頭函數後,this指向window,由於它從 bar 繼承了this。
console.log(fn2()() == window); // true
複製代碼

特別注意的是,當方法是對象裏的屬性時,若是調用的不是方法 obj.bar()這種形式,而是obj.bar這種形式,後面再去調用的時候,前者的this指向當前對象obj,然後者指向全局對象。根本緣由是因爲執行的上下文不同致使的,但願細細品味。

做爲對象的方法

當函數做爲對象裏的方法被調用時,它們的 this 是調用該函數的對象。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // logs 37
複製代碼

請注意,這樣的行爲,根本不受函數定義方式或位置的影響。

var o = {prop: 37};

function independent() {
  return this.prop;
}

o.f = independent;

console.log(o.f()); // logs 37
複製代碼

一樣,this 的綁定只受最靠近的成員引用的影響。在下面的這個例子中,咱們把一個方法g看成對象o.b的函數調用。在此次執行期間,函數中的this將指向o.b。事實證實,這與他是對象 o 的成員沒有多大關係,最靠近的引用纔是最重要的。

o.b = {g: independent, prop: 42};
console.log(o.b.g()); // 42
複製代碼

即this指向最後一個調用方法的對象

原型鏈中的 this

對於在對象原型鏈上某處定義的方法,一樣的概念也適用。若是該方法存在於一個對象的原型鏈上,那麼this指向的是調用這個方法的對象,就像該方法在對象上同樣。

var o = {
  f: function() { 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5
複製代碼

此處p繼承自o,可是調用f()方法的是p,則this指向p

getter 與 setter 中的 this

再次,相同的概念也適用於當函數在一個 getter 或者 setter 中被調用。用做 getter 或 setter 的函數都會把 this 綁定到設置或獲取屬性的對象。

function sum() {
  return this.a + this.b + this.c;
}

var o = {
  a: 1,
  b: 2,
  c: 3,
  get average() {
    return (this.a + this.b + this.c) / 3;
  }
};

Object.defineProperty(o, 'sum', {
    get: sum, enumerable: true, configurable: true});

console.log(o.average, o.sum); // logs 2, 6
複製代碼

做爲構造函數

當一個函數用做構造函數時(使用new關鍵字),它的this被綁定到正在構造的新對象。

/*
 * 構造函數這樣工做:
 *
 * function MyConstructor(){
 *   // 函數實體寫在這裏
 *   // 根據須要在this上建立屬性,而後賦值給它們,好比:
 *   this.fum = "nom";
 *   // 等等...
 *
 *   // 若是函數具備返回對象的return語句,
 *   // 則該對象將是 new 表達式的結果。 
 *   // 不然,表達式的結果是當前綁定到 this 的對象。
 *   //(即一般看到的常見狀況)。
 * }
 */

function C(){
  this.a = 37;
}

var o = new C();
console.log(o.a); // logs 37


function C2(){
  this.a = 37;
  return {a:38};
}

o = new C2();
console.log(o.a); // logs 38
複製代碼

雖然構造器返回的默認值是this所指的那個對象,但它仍能夠手動返回其餘的對象(若是返回值不是一個對象,則返回this對象)。

淺拷貝與深拷貝

對象類型在賦值的過程當中實際上是複製了地址,從而會致使改變了一方其餘也都被改變的狀況。一般在開發中咱們不但願出現這樣的問題,咱們可使用淺拷貝來解決這個狀況。

let a = {
  age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
複製代碼

淺拷貝

Object.assign實現淺拷貝

不少人認爲這個函數是用來深拷貝的。其實並非,Object.assign 只會拷貝全部的屬性值到新的對象中,若是屬性值是對象的話,拷貝的是地址,因此並非深拷貝。

let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
複製代碼

... 來實現淺拷貝

let a = {
  age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
複製代碼

一般淺拷貝就能解決大部分問題了,可是當咱們遇到以下狀況就可能須要使用到深拷貝了

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native
複製代碼

淺拷貝只解決了第一層的問題,若是接下去的值中還有對象的話,那麼就又回到最開始的話題了,二者享有相同的地址。要解決這個問題,咱們就得使用深拷貝了。

深拷貝

這個問題一般能夠經過 JSON.parse(JSON.stringify(object)) 來解決。

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
複製代碼

可是該方法也是有侷限性的:

  • 會忽略 undefined
  • 會忽略 symbol
  • 不能序列化函數
  • 不能解決循環引用的對象
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
複製代碼

若是你有這麼一個循環引用對象,你會發現並不能經過該方法實現深拷貝()

image.png

在遇到函數、 undefined 或者 symbol 的時候,該對象也不能正常的序列化

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}
複製代碼

上述狀況中,該方法會忽略掉函數和 undefined 。 深度拷貝的實現方法:

  • 手寫簡單的深度拷貝
function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非對象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}

let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2
複製代碼
  • 使用Lodash庫的clone 與 cloneDeep方法。

持續更新中。。。。

相關文章
相關標籤/搜索