JS基礎知識學習--真題總結

JS真題

題目一

1. var和let const的區別

答:

  • var是ES5語法,let、const是ES6語法; var有變量提升

  • var和let是變量,可修改; const是常量,不可修改;

  • let、const有塊級作用域,var沒有

2. typeof返回哪些類型

答:

  • undefined、string、number、boolean、symbol

  • object(注意,typeof null === ‘object’)

  • function

3. 列舉強制類型轉換和隱式類型轉換

答:

  • 強制:parseInt、parseFloat、toString等

  • 隱式:if、邏輯運算、==、+拼接字符串

題目二

1. 手寫深度比較,模擬lodash的isEqual

function isObject(obj){
  return typeof obj === 'object' && obj !== null
}

function isEqual(obj1, obj2) {
  if(!isObject(obj1) || !isObject(obj2)){
    // 值類型
    return obj1 === obj2
  }
  if(obj1 === obj2){
    return true
  }
  // 兩個都是對象或者數組,而且不相等

  // 1. 先判斷keys個數
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)
  if (obj1Keys.length !== obj2Keys.length) {
    return false
  }
  // 2. 以obj1爲基準,和obj2 一次遞歸比較
  for (let key in obj1) {
    // 遞歸比較
    const res = isEqual(obj1[key], obj2[key])
    if (!res) {
      return false
    }
  }
  return true
}

2. split()和join()的區別

​ 答:split是字符串的方法,用來分割字符串爲數組,'1-2-3'.split('-') // [1, 2, 3]

​ join是數組方法,用來拼接數組爲字符串,[1, 2, 3].join('-') // '1-2-3'

3. 數組的pop、push、unshift、shift分別做什麼

  • pop : 從 數組末尾彈出一個元素,無參,返回彈出的元素,原位操作

  • push : 從數組末尾加入元素,參數是加入的元素,按順序加入,返回數組長度,原位操作

  • shift : 從數組的開頭彈出一個元素,無參,返回彈出的元素,原位操作

  • unshift : 從數組的開頭加入元素,參數是加入的元素,按順序一起加入,返回數組的長度,原位操作

    const arr = [10, 20, 30, 40]
    // pop
    // const popRes = arr.pop()
    // console.log(arr, popRes) // [10, 20, 30] 3
    
    // push
    // const pushRes = arr.push(50, 60)
    // console.log(arr, pushRes) // [ 10, 20, 30, 40, 50, 60 ] 6
    
    // unshift
    // const unshiftRes = arr.unshift(-10, -20)
    // console.log(arr, unshiftRes) // [ -10, -20, 10, 20, 30, 40 ] 6
    
    // shift
    // const shiftRes = arr.shift()
    // console.log(arr, shiftRes) // [ 20, 30, 40 ] 10
    

擴展:數組的API,有哪些純函數?

​ 純函數:1. 不改變源數組(沒有副作用); 2. 返回一個數組

  • concat

  • map

  • filter

  • slice

    // concat
    // const arr1 = arr.concat([50, 60, 70])
    // console.log(arr, arr1) // [ 10, 20, 30, 40 ] [10, 20, 30, 40, 50, 60, 70]
    
    // map
    // const arr2 = arr.map(val => val * 10)
    // console.log(arr, arr2) // [ 10, 20, 30, 40 ] [ 100, 200, 300, 400 ]
    
    // filter
    // const arr3 = arr.filter(val => val > 25)
    // console.log(arr, arr3) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    
    // slice
    // const arr4 = arr.slice()
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 10, 20, 30, 40 ]
    

非純函數: push pop shift unshift forEach some every

題目三

1. 數組slice和splice的區別

  • 功能區別(slice-切片, splice-剪切)

  • 參數和返回值

  • 是否純函數

    // slice 純函數
    // const arr4 = arr.slice()
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 10, 20, 30, 40 ]
    // const arr4 = arr.slice(2, 4) // start, end
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    // const arr4 = arr.slice(1) // start
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 20, 30, 40 ]
    // const arr4 = arr.slice(-2) // lastest start
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    // const arr4 = arr.slice(-3, -2) // lastest start, lastest end
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 20 ]
    
    // splice 非純函數
    // const spliceRes = arr.splice(1, 2, 'a', 'b', 'c') // start, length, 
    // console.log(arr, spliceRes) // [ 10, 'a', 'b', 'c', 40 ] [ 20, 30 ]
    // const spliceRes = arr.splice(1, 2)
    // console.log(arr, spliceRes) // [ 10, 40 ] [ 20, 30 ]
    // const spliceRes = arr.splice(1, 0, 'a', 'b', 'c') // 只增加不剪切
    // console.log(arr, spliceRes) // [ 10, 'a', 'b', 'c', 20, 30,  40 ] []
    

2. [10, 10, 10, 10].map(parseInt) 返回結果是什麼

  • map的參數和返回值:參數:數組的每個元素,對應元素下標。返回值:新數組

  • parseInt參數和返回值:參數:待轉換的該進制數字,進制。返回值:轉換過的十進制數

    const res = [10, 10, 10, 10].map(parseInt)
    // 拆解,相當於:
    // const res = [10, 10, 10, 10].map((item, index) => {
    //   return parseInt(item, index)
    // })
    // parseInt(num, base = 10)  其中base = 0時,相當於base = 10,也就是相當於10進制轉換
    console.log(res) // [ 10, NaN, 2, 3 ]
    

3. ajax請求的get和post的區別

  • get一般用於查詢操作,post一般用於用戶提交操作
  • get參數拼接在url上,post放在請求體內(數據體積可更大)
  • 安全性:post易於防止CSRF

題目四

1. 函數call和apply的區別

​ 答:call的參數是直接傳入,而apply的參數是放到數組中

​ fn.call(this, p1, p2, p3)

​ fn.apply(this, arguments)

2. 事件代理(委託)是什麼

​ 答:在父元素上定義一個事件,監聽子元素觸發時的冒泡。 如果子元素想要阻止冒泡,則使用:e.stopPropagation()

3. 閉包是什麼,有什麼特性,有什麼負面影響

  • 回顧作用域和自由變量
  • 回顧閉包應用場景:作爲參數被傳入,作爲返回值被返回
  • 回顧:自由變量的查找,要在函數定義的地方(而非執行的地方)
  • 影響:變量會常駐內存,得不到釋放(但不一定會造成內存泄漏,內存泄漏一般屬於Bug,而閉包不是Bug,而是故意爲之)。閉包不要亂放。

題目五

1. 如何阻止事件冒泡和默認行爲

  • event.stoppropagation()
  • event.preventDefault()

2. 查找、添加、刪除、移動DOM節點的方法

  • 查找節點:document.getElementById(“id”)、document.getElementsByTagName(“h1”)、document.getElementsByClassName(“container”)、document.querySelector("#div")、document.querySelectorAll("#div")
  • 添加節點:document.createElement(“span”)
  • 插入、移動節點:ele.appendChild§
  • 獲取父元素:ele.parentNode
  • 獲取子元素:ele.childNodes(含有文本節點)、ele.children(不含文本節點)
  • 刪除子元素:ele.removeChild

3. 如何減少DOM操作

  • 緩存DOM查詢結果

  • 多次DOM操作,合併到一次插入

    const list = document.getElementById('list')
    // 創建一個文檔片段
    const fragment = document.createDocumentFragment()
    for(let i = 0; i < 10; i++){
      const li = document.createElement('li')
      li.innerHTML = i
      // 插入到文檔片段,文檔片段是在內存中
      fragment.appendChild(li)
    }
    // 將文檔片段一次性插入
    list.appendChild(fragment)
    

題目六

1. 解釋jsonp的原理,爲何它不是真正的ajax?

  • 瀏覽器的同源策略(服務端沒有同源策略)和跨域

  • 哪些HTML標籤能繞過跨域? script、link、img

  • ajax是通過XMLHTTPRequest實現的,jsonp是通過script標籤實現的

  • 實現跨域必須得到服務器端的允許和配合

    // jsonp  
    	<script>
        window.callback = function (data) {
          console.log(data)
        }
      </script>
    
      <script src="http://localhost:8002/JSONP.js?username=xxx&callback=abc"></script>
    

2. document load和ready的區別

  • load是頁面的資源全部加載完才執行

  • ready是DOM渲染完就執行,此時圖片、視頻還可能沒有加載完

    window.addEventListener('load', function () {
      // 頁面的全部資源加載完才執行,包括圖片、視頻等
    })
    document.addEventListener('DOMContentLoaded', function () {
      // DOM 渲染完即可執行,此時圖片、視頻還可能沒有加載完
    })
    

3. == 和 === 的不同

  • == 會嘗試類型轉換
  • === 嚴格相等
  • 哪些場景採用== :判斷對象屬性是否爲null:a.xxx == null。 其他場合一律用===

題目七

1. 函數聲明和函數表達式的區別

  • 函數聲明:function fn () {…}
  • 函數表達式: const fn = function () {…}
  • 函數聲明會在代碼執行前預加載,而函數表達式不會

2. new Object()和Object.create()的區別

  • {}等同於new Object(),原型Object.prototype

  • Object .create(null)沒有原型

  • Object.create({…})可指定原型

    const obj1 = {a: 1}
    const obj2 = new Object(obj1) // obj1 === obj2
    
    const obj1 = {a: 1}
    const obj2 = new Object({a: 1}) // obj1 !== obj2, isEqual(obj1, obj2) === true
    
    const obj1 = {a: 1}
    const obj2 = Object.create(obj1) // obj2.__proto__ === obj1
    

3. this的場景題

  • this的取值在執行的時候再知道
const User = {
  count: 1,
  getCount: function () {
    return this.count
  }
}
console.log(User.getCount()) // 1

const func = User.getCount
console.log(func()) // undefined

題目八

1. 關於作用域和自由變量的場景題 -1

let i
for(i = 1; i <= 3; i++) {
setTimeout(function () {
  console.log(i)
}, 0)
}
// 4 4 4

2. 判斷字符串以字母開頭,後面字母數組下劃線,長度6-30

  • const reg = /1\w{5, 29}$/

    // 小寫英文字母
    /^[a-z]+$/
    
    // 英文字母
    /^[a-zA-Z]+$/
      
    // 日期格式 2019-12-1
    /^\d{4}-\d{1,2}-\d{1,2}$/
    
    // 用戶名
    /^[a-zA-Z]\w{5,17}$/
    
    // 簡單的IP地址匹配
    /\d+\.\d+\.\d+\.\d+/
    

3. 關於作用域和自由變量的場景題 -2

let a = 100
function test() {
  console.log(a)
  a = 10
  console.log(a)
}
test()
console.log(a)
// 100 10 10

題目九

1. 手寫字符串trim方法,保證瀏覽器兼容性

String.prototype.trim = function () {
  return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
// 原型、this、正則表達式

2. 如何獲取多個數字中的最大值

function max() {
  const arr = Array.prototype.slice.call(arguments)
  let res = arr[0]
  arr.forEach(item => {
    if(item > res)res = item
  })
  return res
}
console.log(max(3, -1, 20, 4, 34))
console.log(Math.max(3, -1, 20, 4, 34))

3. 如何用JS實現繼承

  • class繼承
  • prototype繼承

題目十

1. 如何捕獲JS程序中的異常

try {
  // TODO
} catch(e) {
  console.log(e) // 手動捕獲異常
} finally {
  // TODO
}

// 自動捕獲
window.onerror = function (message, source, lineNum, colNum, error) {
  // 第一,對跨域的js, 如CDN的,不會有詳細的報錯信息
  // 第二,對於壓縮的js,還要配合sourceMap,反查到未壓縮代碼的行、列
}

2. 什麼是JSON

  • json是一種數據格式,本質是一段字符串
  • json格式和JS對象結構一致,對JS語言更友好
  • Window.JSON是一個全局對象:JSON.stringify將json轉化成字符串,JSON.parse將字符串轉化成json

3. 獲取當前頁面url參數

  • 傳統方式,查找location.search

    // 傳統方式
    function query(name) {
      const search = location.search.substr(1)
      // search: 'a=10&b=20&c=30'
      const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
      const res = search.match(reg)
      console.log(res)
      // 數組第一個是完全匹配,第二個是匹配的第一個括號裏面的內容,第3個是匹配的第2個括號裏面的內容,第4個是匹配的第3個括號裏面的內容,
      // ["&b=20&", "&", "20", "&"]
      if(res === null) {
        return null
      }
      return res[2]
    }
    console.log(query('b'))
    
  • 新的API,URLSearchParams

    // URLSearchParams
    function query(name) {
      const search = location.search
      const p = new URLSearchParams(search)
      return p.get(name)
    }
    console.log(query('b'))
    

題目11

1. 將url參數解析爲JS對象

// 傳統方式,分析search
function queryToObj() {
  const res = {}
  const search = location.search.substr(1) // 去掉前面的?
  search.split('&').map(paramStr => {
    const arr = paramStr.split('=')
    res[arr[0]] = arr[1]
  })
  return res
}
console.log(queryToObj()) // {a: "10", b: "20", c: "30"}

// 使用URLSearchParams
function queryToObj2(){
  const res = {}
  const pList = new URLSearchParams(location.search)
  pList.forEach((val, key) => {
    res[key] = val
  })
  return res
}
console.log(queryToObj2()) // {a: "10", b: "20", c: "30"}

2. 手寫數組flatern,考慮多層級

function flat(arr){
  // 驗證arr中有沒有深層數組
  const isDeep = arr.some(item => item instanceof Array)
  if(!isDeep)return arr
  const res = Array.prototype.concat.apply([], arr)
  return flat(res) // 遞歸
}

const res = flat([1, 2, [3, [4]], 5])
console.log(res)

3. 數組去重

// 傳統方式 比較慢 可兼容
function unique(arr) {
  const res = []
  arr.forEach(item => {
    if(res.indexOf(item) < 0) {
      res.push(item)
    }
  })
  return res
}

console.log(unique([30, 20, 40, 50, 20, 30]))

// set方式 (無序結構,不能重複) 快 新的API,可能有兼容性問題
function unique2(arr){
  const set = new Set(arr)
  return [...set]
}
console.log(unique2([30, 20, 40, 50, 20, 30]))

題目12

1. 手寫深拷貝

/**
 * 深拷貝
 * @param {Object} obj 要拷貝的對象
 */
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) return obj
  let newObj
  if (obj instanceof Array) {
    newObj = []
  } else {
    newObj = {}
  }
  for (let key in obj) {
    if (obj.hasOwnProperty(key))
      newObj[key] = deepClone(obj[key])
  }
  return newObj
}

// Object.assign是淺拷貝,只拷貝第一層級,不要踩坑

2. 介紹一下RAF: window.requestAnimationFrame

  • 想要動畫流暢,更新頻率要60幀/s,即16.67ms更新一次視圖

  • setTimeout要手動控制頻率,而RAF瀏覽器會自動控制

  • 後臺標籤或隱藏iframe中,RAF會暫停,而setTimeout依然執行

    // 3s 把寬度從100px變爲640px,即增加540px
    // 60幀/s, 3s 180幀,每次變化3px
    let curWidth = 100
    const maxWidth = 1000
    const $div1 = $("#div1")
    
    // setTimeout
    // function animate(){
    //   curWidth += 3
    //   $div1.css('width', curWidth)
    //   if(curWidth < maxWidth) {
    //     setTimeout(animate, 16.7); // 得自己控制時間
    //   }
    // }
    // animate()
    
    function animate(){
      curWidth += 3
      $div1.css('width', curWidth)
      if(curWidth < maxWidth) {
        window.requestAnimationFrame(animate)
      }
    }
    animate()
    

3. 前端性能如何優化?一般從哪幾個方面考慮?

  • 原則:多使用內存、緩存、減少計算、減少網絡請求
  • 方向:加載頁面,頁面渲染,頁面操作流暢度

  1. a-zA-Z ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章