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. 前端性能如何優化?一般從哪幾個方面考慮?
- 原則:多使用內存、緩存、減少計算、減少網絡請求
- 方向:加載頁面,頁面渲染,頁面操作流暢度
a-zA-Z ↩︎