前端javascript面試題經典集合(2020年最新)

1. call和apply的區別是什麼,哪個性能更好一些

fn.call(obj, 10, 20, 30)
fn.apply(obj, [10, 20, 30])
call性能要比apply性能好一點,(尤其是傳遞給函數的參數超過三個的時候),所以後期研發的時候,可以使用call多一點

let arr = [10, 20, 30]
obj = {}
function fn(x, y, z) {}
fn.apply(obj, arr)
fn.call(obj, ...arr) // 基於ES6的展開運算符也可以實現把數組中的每一項一次傳遞給函數

實現性能測試:任何代碼性能測試讀書和測試環境有關係的,例如cpu、內存、GPU等電腦當前性能不會有相同的情況,不同瀏覽器也會導致性能上的不同

// console.time 可以測試出一段程序執行的時間
// console.profile()在火狐瀏覽器中安裝firebug,可以更精準的獲取到每個步驟所消耗的時間
console.time('A')
for(let i = 0; i< 100000; i++) {
    
}
console.timeEnd('A')

2. 實現(5).add(3).minus(2) ,使其輸出結果爲: 6

~function() {
  // 每一個方法執行完,都要返回number這個類型的實例,這樣纔可以用繼續調取number類型原型中的方法
  function check(n) {
    n = Number(n);
    return isNaN(n)? 0 : n;
  }
    function add(n){
    n = check(n)
    return this + n
  }
  function minus(n) {
    n = check(n)
    return this -n
  }
  
  Number.prototype.add = add;
  Number.prototype.minus = minus

}()

3. 箭頭函數與普通函數(function)的區別是什麼?構造函數(function)可以使用new生成實力,那麼箭頭函數可以用嗎?

箭頭函數和普通函數的區別:

  1. 箭頭函數語法比普通函數更加簡潔(ES6中每一種函數都可以使用形參賦默認值和剩餘運算符)
  2. 箭頭函數沒有自己的this,它裏面出現的this是繼承函數所處的上下文中的this(使用call/apply等任何方式都無法改變this的指向)
let fn = x => x + 1

// 普通函數
let obj = {
    name: 'lanfeng'
}
function fn1() {
    console.log(this)
}
fn1.call(obj) // {name: "lanfeng"}

// 箭頭函數
let fn2 = () => {
    console.log(this)
}
fn2.call(obj) //Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}

document.body.onclick = () => {
//=> this: window不是當前操作的body了
}

// 回調函數: 把伊尼戈函數B作爲實參傳遞給另外一個函數A,函數A在執行的時候,可以把傳遞進來的函數去執行(執行N次,可傳值)
function each(arr, callback) {
  for(let i = 0; i < arr.length; i++) {
    let item = arr[i],
        index = i;
    // 接收回調函數返回的結果,如果是false,則結束循環
    let flag = callback.call(arr,item, index)
    if(flag === false) {
        break;
    }
  }
}
each([10, 20, 30, 40], function(item, index){
    // this:原始操作數組
})
  1. 箭頭函數中沒有arguments(類數組),只能基於...arg獲取傳遞參數集合(數組)
let fn = (...arg) => {
    console.log(arguments) //Uncaught ReferenceError: arguments is not defined
  console.log(arg) // [10, 20, 30n]
}
fn(10, 20, 30)

  1. 箭頭函數不能被new執行(因爲箭頭函數沒有this,也沒有prototype)

4. 如何把一個字符串的大小寫取反(大寫變小寫,小寫變大寫)

let str = "lanfengQIUqiu前端"
str = str.replace(/[a-zA-Z]/g, content => {
    // content:每一次正則匹配的結果
  // 驗證是否爲大寫字母:把字母轉換爲大寫和之前是否一樣,之前是大寫的:在ASII表中找到大寫字母的取值範圍進行判斷
   return content.toUpperCase() === content ? content.toLowerCase() : content.toUpperCase()
})
console.log(str) // LANFENGqiuQIU前端

5. 實現一個字符串匹配算法,從字符串s中,查找是否存在字符串T,若存在返回所在位置,不存在返回-1(如果並不能基於indexOf/includes等內置的方法,你會如何處理呢)

循環原始字符串中的每一項,讓每一項從當前位置向後截取T.length個字符,然後和T進行比較,
如果不一樣,繼續循環,如果一樣返回當前索引即可(循環結束)

(function() {
  /**
    * 循環原始字符串中的每一項,讓每一項從當前位置向後截取T.length個字符,然後和T進行比較,
       如果不一樣,繼續循環,如果一樣返回當前索引即可(循環結束)
  **/
    function myIndexOf(T) {
    let lenT = T.length,
        lenS = this.length,
        res = -1
    if(lenT > lenS) return -1
    for(let i = 0; i < lenS- lenT + 1; i++) {
        let char = this[i];
      if(this.substr(i,lenT) === T) {
        res = i;
        break;
      }
    }
     return res
  }
  String.prototype.myIndexOf = myIndexOf
 
})();

 let S = 'zhufengpeixun',
      T = 'pei'
  console.log(S.myIndexOf(T))

正則處理

(function() {
  /**
    * 正則處理:exec方法
  **/
    function myIndexOf(T) {
    let reg = new RegExp(T),
        res = reg.exec(this);
    return res === null ? -1 : res.index;
  }
  String.prototype.myIndexOf = myIndexOf
 
})();

 let S = 'zhufengpeixun',
      T = 'pei'
  console.log(S.myIndexOf(T))

5. 在輸入框中如何判斷輸入的是一個正確的網址,例如:用戶輸入一個字符串,驗證是否符合URL網址格式

let str = "https://lanfeng.blog.csdn.net/"
let reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/]*)+)?(\?[^#]+)?(#.+)?$/i
// 1. 協議: // http/https/ftp
// 2. 域名
// 3. 請求路徑
// 4. 問號傳參
console.log(reg.exec(str))

6.

function Foo() {
    Foo.a = function() {
    console.log(1)
  }
  this.a = function() {
    console.log(2)
  }
}
// 把Foo當做類,在原型上設置實例公有的屬性方法 => 實例.a()
Foo.prototype.a = function() {
    console.log(3)
}

// 把Foo當做普通對象設置私有屬性方法 => Foo.a()
Foo.a = function() {
    console.log(4)
}
Foo.a(); //4
let obj = new Foo(); // obj可以調取原型上的方法 Foo.a
obj.a(); //2 //私有屬性中有a
Foo.a(); //1

7. 編寫代碼實現圖片懶加載

  • 前端性能優化的重要方案,通過圖片或者數據的延遲加載,我們可以加快頁面渲染的速度,讓第一次打開頁面的速度變快,只有滑動到某個區域,我們才加載真實的圖片,這樣也可以節省加載的流量
  • 處理方案:把所有需要延遲加載的圖片用一個盒子包起來,設置寬高和默認佔位圖;開始讓所有的img的src爲空,把真實圖片地址放到img的自定義屬性上,讓img隱藏;等到所有其他資源都加載完之後,我們再開始加載圖片;對於很多圖片,需要當頁面滾動的時候,當前圖片區域完全顯示出來之後再加載真實圖片

8. 編寫一條正則,用來驗證此規則:一個6~16位的字符串,必須同時包含有大小寫字母和數字

let reg = /^(?!^[a-zA-Z]+$)(?!^[0-9]+$)(?!^[a-z0-9]+$)(?!^[A-Z0-9]+$)[a-zA-Z0-9]{6,16}/

9. 1-10位:數字、字母、下劃線組成字符串,必須有_

let reg = /(?!^[a-zA-Z0-9]+$)^\w{1, 10}$/

10. 實現一個$attr(name, value)遍歷,屬性爲name,值爲value的元素集合,例如:

let ary = $attr('class', 'box') // 獲取頁面中所有class爲box的元素

function $attr(property, value) {
    let elements = document.getElementsByTagName('*'),
      arr = [];
  [].forEach.call(elements, item => { 
    // 存儲的是當前元素property對應的屬性值
    let itemValue = item.getAttribute(property);
    if(property === 'class') {
        // 樣式類屬性名要特殊處理
      new RegExp('\\b' + value +'\\b').test(itemValue) ? arr.push(item) ? null
      return;
    }
    if(itemValue === value) {
        // 獲取的值和傳遞的值校驗成功:當前就是我們想要的
      arr.push(item)
    }
  })
  
  return arr 
}

10. 英文字母漢字組成的字符串,用正則給英文單詞前後加空格

let str = 'no作no死,你能你can, 不能no嗶嗶',
    reg = /\b[a-z]+\b/ig;
str = str.replace(reg, value => {
    return ' ' + value + ' ';
}).trim();
console.log(str) // no 作 no 死,你能你 can , 不能 no 嗶嗶

11. 編寫一個程序,將數組扁平化,並去除其中重複部分數據,最終得到一個升序且不重複的數組

let arr = [[1,2,3],[3,4,5,5],[6,7,8,9,[11, 12,[12,13,[14]]]],10]
arr = arr.flat(Infinity)

//使用ES6中推廣的Array.prototype.flat處理
console.log(arr)
[...new Set(arr)]
arr = Array.from(new Set(arr).sort((a,b) => z-b))

//把數組變成字符串(數組tostring之後,不管數組多少級,最後都會變爲以逗號分隔的字符串),直接扁平化了
arr = arr.toString().split(',').map(item => {
    return Number(item)
})

//JSON.stringify(arr)也可以扁平化數組
arr = JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => Number(item))

//遞歸處理
~function () {
    function myFlat() {
    let result = [],
        _this = this;
    // 循環arr中的每一項,把不是數組的存儲到新數組中
    let fn = (arr) => {
      for(let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if(Array.isArray(item)) {
            fn(item);
          continue;
        }
        result.push(item)
      }
        
        };
    fn(_this);
    return result;
  }
  Array.prototype.myFlat = myFlat
}();
arr = arr.myFlat()

11. 有關構造函數

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function() {
    console.log('wangwang');
}
Dog.prototype.sayName = function() {
    console.log('my name is' + this.name)
}
//基於內置的new關鍵詞,可以創建Dog的一個實例sanmao,實例可以調取原型上的屬性和方法,實現一個_new方法,也能模擬出內置new後的結果

// Fn當前要new的類
// arg後期需要給構造函數傳遞的參數信息
function _new(Fn, ...arg) {
  //let obj = {}
  //obj.__proto__ = Fn.prototype //創建一個空對象,讓他的原型鏈指向Fn.prototype
  let obj = Object.create(Fn.prototype)
    Fn.call(obj, ...arg)
  return obj
}
/**
    * let sanmao = new Dog('三毛');
  * 1. 像普通函數執行一樣,形成一個私有的作用域
  *    + 形參賦值
  *    + 變量提升
  * 2. 默認創建一個對象,讓函數中的this執行這個對象,這個對象就是當前類的一個實例
  * 3. 代碼執行
  * 4. 默認把創建的對象返回
**/
let sanmao = _new(Dog,'三毛');

sanmao.bark(); // wangwang
sanmao.sayName(); //"my name is 三毛"
console.log(sanmao instanceof Dog) // true

12. 有關數組合並和排序

let ary1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'];
let ary2 = ['A', 'B', 'C', 'D']
// 合併後的數組爲:['A1', 'A2','A', 'B1', 'B2','B', 'C1', 'C2','C', 'D1', 'D2', 'D'];
ary2 = ary2.map(item => item + '嵐峯')
let arr = ary1.concat(ary2)
arr = arr.sort((a,b) => a.localeCompare(b)).map(item =>{
    return item.replace('嵐峯', '')
})

console.log(arr)
let ary1 = ['D1', 'D2','A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
let ary2 = ['B','A', 'C', 'D']
// 合併後的數組爲:['D1', 'D2', 'D', 'A1', 'A2','A', 'B1', 'B2','B', 'C1', 'C2','C'];
let n = 0;
for(let i = 0; i< ary2.length; i++) {
    let item2 = ary2[i];
  for(let k = 0; k< ary1.length; k++) {
    let item1 = ary1[k];
    if(item1.includes(item2)) {
        n = k;
      continue;
    }
    break;
  }
  ary1.splice(n+1, 0, item2)
}

console.log(arr)

13. 定時器是異步編程:每一輪循環設置定時器,無需等定時器觸發執行,繼續下一輪循環(定時器觸發的時候,循環已經結束)

for(var i = 0; i < 10; i++) {
  (function(i){
    setTimeout(() => {
        console.log(i)
    }, 1000)
  })(i)

}

//第二種
for(var i = 0; i < 10; i++) {
  setTimeout(((i) => {
        return () => {
        console.log(i)
      }
    })(i), 1000)

}

//第三種 可以基於bind預先處理機制:在循環的時候就把每次執行函數需要輸出的結果,預先傳給函數即可
for(var i = 0; i< 10; i++) {
    var fn = function(i) {
    console.log(i)
  };
  setTimeout(fn.bind(null, i), 1000)
}
//第四種,var變爲let
for(let i = 0; i < 10; i++) {
  setTimeout(() => {
        console.log(i)
    }, 1000)

}

14. 有關變量

var b = 10;
(function() {
    b = 20;
  console.log(b) //20
})()
console.log(b) // 20

15.

/**
    * == 進行比較的時候,如果左右兩邊數據類型不一樣,則先轉換爲相同的數據類型,然後再進行比較
  * 1. {} == {} 兩個對象進行比較,比較的是堆內存的地址
  * 2. null == undefined 相等的, null === undefined 不相等
  * 3. NaN和誰都不相等 NaN == NaN 不相等
  * 4. [12] == '12' 對象和字符串相比較,是把對象toString轉換爲字符串後再進行比較的
  * 5. 剩餘所有情況在進行比較的時候,都是轉換爲數字(前提數據類型不一樣)
     對象轉數字:先轉換爲字符串,再轉化爲數字
     字符串轉數字:只要出現一個非數字字符,結果就是NaN
     undefined轉數字NaN
  [12] == true =>false
  [] == false  => 0== 0 => true
  [] == 1  => 0 == 1 false
**/

// 1. 對象和數字比較:先把對象.toString()變爲字符串,然後再轉換爲數字
var a = {
    n: 0,
  toString: function () {
    return  ++this.n
  }
};
// a.toString(); // 此時調取的就不再是Object.prototype.toString了,調取的是自己私有的
if(a == 1 && a ==2 & a==3) {
    console.log(1)
}
  
// 2. shift: 刪除數組的第一項,把刪除的內容返回,原有數組改變
var a = [1,2,3]
a.toString = a.shift
if(a == 1 && a == 2 && a==3) {
    console.log('Ok')
}

// 3. 
Object.defineProperty(window, 'a', {
    get: function() {   
    this.value ? this.value++ : this.value = 1
 
    return this.value
  }
});
if(a == 1 && a == 2 && a==3) {
    console.log('Ok')
}

16. 數組push方法原理運用

/**
    * Array.prototype.push = function(val) {
        this[this.length] = val
       //this.length在原來的基礎上加1
       return this.length;
    }
**/

let obj = {
    2:3, //=>1
  3: 4, //=> 2
  length: 2, // => 3 => 4
  push: Array.prototype.push
}
obj.push(1) // this.obj => obj[obj.length] = 1 => obj[2] = 1 => obj.length = 3
obj.push(2) // this.obj => obj[obj.length] = 2 =>  obj[3] = 2 => obj.length = 4
console.log(obj) 

17. 數組當中三大經典排序算法: 冒泡排序

/**
    * 冒泡排序思想
  * 讓數組中的當前項項和後一項進行比較,如果當前項比後一項大,則兩項交換位置(讓大的靠後)即可
**/
let ary = [12, 8, 24, 16, 1]

/**
    * bubble:實現冒泡排序
  * ary[Array] 需要排序的數組
  * @return 
  [Array] 排序h後的新數組
**/
function bubble(ary) {
  //外層循環i控制比較的輪數
  for(let i = 0; i<ary.length; i++) {
    // 裏面循環控制每一輪比較的次數
   for(let j = 0; j< ary.length-1-i; j++) {
    if(ary[j]> ary[j+1]) {
        temp = ary[j];
      ary[j] = ary[j+1];
      ary[j+1] = temp
    }
   }
    
  }
  return ary
}
let ary = [12, 8, 24, 16, 1]
ary = bubble(ary)
console.log(ary)

18. 數組當中三大經典排序算法: 插入排序

/**
    * insert:實現插入排序
  * ary[Array] 需要排序的數組
  * @return 
  [Array] 排序h後的新數組
**/
function insert(ary) {
  // 1. 準備一個新數組,用來存儲抓到手裏的牌,
  let handle = [];
  handle.push(ary[0])
  for(let i = 1; i<ary.length; i++) {
    // A是新抓的牌
    let A = ary[1]
    // 和handle手裏的牌依次比較(從後向前)
    for(let j = handle.length - 1; j>=0; j--) {
        let B =  handle[j]
      
      // 如果當前新牌A比要比較的B大,把A放到B的後面
      if(A>B) {
        handle.splice(j+1, 0 , A)
        breadk;
      }
      // 已經比到第一項,我們把新牌放到手中最前面即可
      if(j===0) {
        handle.unshift(A)
      }
    }
  }
    return handle
}
let ary = [12, 8, 24, 16, 1]
ary = insert(ary)
console.log(ary)

19. 數組當中三大經典排序算法: 快速排序

/**
    * quick:實現快速排序
  * ary[Array] 需要排序的數組
  * @return 
  [Array] 排序h後的新數組
**/
function quick(ary) {
  //結束遞歸(當ary中小於等於一項,則不用處理)
  if(ary.length <=1) {
    return aryy
  }
  // 1. 找到數組的中間項,在你原有的數組中把它拆除
  let middleIndex = Math.floor(arry.length/2);
  let middleValue = ary.splice(middleIndex, 1)[0]
  
  // 2. 準備左右兩個數組,循環剩下數組中的每一項,比當前項曉的放到左邊數組中,反之放到右邊數組
  let aryLeft = [],
      aryRight = [];
  for(let i = 0; i< ary.length; i++) {
    let item = ary[i]
    item < middleValue ? aryLeft.push(item) : aryRight.push(item);
  }
  // 3. 遞歸方式讓左右兩邊的數組持續這樣處理,一直到左右兩邊都排好序爲止
  
  return quick(aryyLeft)concat(middleValue, quick(aryRight));
}
let ary = [12, 8, 24, 16, 1]
ary = quick(ary)
console.log(ary)

20.

某公司1到12月份的銷售額存在一個對象裏面
如下: {
1: 222,
2: 123,
5: 888
}
請把數據處理爲如下結構:[222, 123, null, null,888, null, null, null, null, null, null , null]

//第一種方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}
let arr = new Array(12).fill(null).map((item, index) => {
    return obj[index + 1] || null
})
console.log(arr)

// 第二種方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}
obj.length = 13
let arr = Array.from(obj).slice(1).map(item => {
 typeof item === 'undefined' ? null : item
})
console.log(arr)

// 第三種方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}

let arr = new Array(12).fill(null)
Object.keys(obj).forEach(item => {
    arr[item-1] = obj[item];
})
console.log(arr)

21. 給定兩個數組,寫一個方法來計算它們的交集

// 第一種方法
let num1 = [1,2,2,1]
let num2 = [2,2]
//輸出結果[2, 2]

let arr = [];
for(let i = 0; i< num1.length; i++) {
    let item1 = num1[i]
  for(let k = 0; k< num2.length; k++) {
    let item2 = num2[k]
    if(item1 === item2) {
     arr.push(item1)
      break;
    }
  }
}
console.log(arr)

// 第二種方法
let num1 = [1,2,2,1]
let num2 = [2,2]
let arr = [];
num1.forEach(item => {
    num2.includes(item) ? arr.push(item) : null
})
console.log(arr)

22.

實現一個add函數,滿足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3) // 6
add(1)(2)(3)(4); //10
add(1)(2, 3); // 6
add(1, 2)(3); //6
add(1,2,3); // 6
函數柯里化:預先處理的思想(利用閉包的機制)

let obj = {name: 'lanfeng'}
function fn(...arg) {
    console.log(this, arg)
}
// 點擊的時候fn中的this => obj arg=> [100, 200, 事件對象]
document.body.onclick = fn.bind(obj, 100, 200);
// 執行bind方法,會返回一個匿名函數,當事件觸發,匿名函數執行,我們仔處理fn即可
document.body.onclick = function(ev) {
    //ev事件對象:給每個元素的某個事件綁定方法,當事件觸發會執行這個方法,並且把當前事件的相關信息傳遞給這個函數“事件對象”
}
function currying(fn, length) {
    length = length || fn.length;
  return function(...args) {
    if(args.length >= length) {
        return fn(...args);
    }
    return currying(fn.bind(null, ...args), length-args.length);
  }
}
function add(n1, n2, n3) {
    return n1 + n2 + n3;
}
add = currying(add, 3)
console.log(add(1)(2)(3))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章