面試題
var 和 let const 的區別
- var 是 ES5 及之前的語法,let const 是 ES6 語法
- var 和 let 是變量,可修改;const 是常量,不可修改
- var 有變量提升,let const 沒有
- var 沒有塊級作用域,let const 有 (ES6 語法有塊級作用域)
// var 變量提升
console.log('a', a)
var a = 100// let 沒有變量提升
console.log('b', b)
let b = 200
// var 沒有塊級作用域
for (var i = 0; i < 10; i++) {var j = 1 + i
}
console.log(i, j)// let 有塊級作用域
for (let x = 0; x < 10; x++) {let y = 1 + x
}
console.log(x, y)
typeof 有哪些返回類型?
// 判斷所有值類型
let a
console.log(a) // 'undefined'
const str = 'abc'
typeof str // 'string'
const n = 100
typeof n // 'number'
const b = true
typeof b // 'boolean'
const s = Symbol('s')
typeof s // 'symbol'
列舉強制類型轉換和隱式類型轉換
- 強制
parseInt
parseFloat
- 隱式
if
,==
,+
拼接字符串
手寫深度比較,如 lodash isEqual
// 實現如下效果
const obj1 = {a: 10, b: { x: 100, y: 200 }}
const obj2 = {a: 10, b: { x: 100, y: 200 }}
isEqual(obj1, obj2) === true
// 判斷是否是 object
function isObject(obj) {return typeof obj === 'object' && obj !== null
}
// 全相等
function isEqual(obj1, obj2) {if (!isObject(obj1) || !isObject(obj2)) {// 值類型,不是對象或數組(注意,equal 時一般不會有函數,這里忽略)return obj1 === obj2}if (obj1 === obj2) {// 兩個引用類型全相等(同一個地址)return true}// 兩個都是引用類型,不全相等// 1. 先取出 obje2 obj2 的 keys,比較個數const obj1Keys = Object.keys(obj1)const obj2Keys = Object.keys(obj2)if (obj1Keys.length !== obj2Keys.length) {// keys 個數不相等,則不是全等return false}// 2. 以 obj1 為基準,和 obj2 依次遞歸比較for (let key in obj1) {// 遞歸比較const res = isEqual(obj1[key], obj2[key])if (!res) {// 遇到一個不相等的,則直接返回 falsereturn false}}// 3. 都相等,則返回 truereturn true
}
split()
和 join()
的區別
'1-2-3'.split('-')
[1,2,3].join('-')
數組的 pop
push
unshift
shift
分別做什么
注意以下幾點
- 函數作用是什么?
- 返回值是什么?
- 對原數組是否造成影響?
- 如何對原數組不造成影響?
concat
slice
map
filter
【擴展】數組 API 的純函數和非純函數
純函數 —— 1. 不改變來源的數組; 2. 返回一個數組
- concat
- map
- filter
- slice
const arr = [100, 200, 300]
const arr1 = arr.concat([400, 500])
const arr2 = arr.map(num => num * 10)
const arr3 = arr.filter(num => num > 100)
const arr4 = arr.slice(-1)
非純函數
情況1,改變了原數組
- push
- reverse
- sort
- splice
情況2,未返回數組
- push
- forEach
- reduce
- some
數組 slice 和 splice 的區別?
slice - 切片;splice - 剪接;
// slice()
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice() // arr2 和 arr1 不是一個地址,純函數,重要!!!// arr.slice(start, end)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(1, 4) // [20, 30, 40]// arr.slice(start)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(2) // [30, 40, 50]// 負值
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(-2) // [40, 50]
// arr.splice(index, howmany, item1, ....., itemX)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.splice(1, 2, 'a', 'b', 'c') // [20, 30]
// arr1 會被修改,不是純函數,即有副作用
[10, 20, 30].map(parseInt)
的結果是什么?
// 拆解開就是
[10, 20, 30].map((num, index) => {return parseInt(num, index)// parseInt 第二個參數是進制// 如果省略該參數或其值為 0,則數字將以 10 為基礎來解析。如果它以 “0x” 或 “0X” 開頭,將以 16 為基數。// 如果該參數小于 2 或者大于 36,則 parseInt() 將返回 NaN
})
// 可以對比
[10, 20, 30].map((num, index) => {return parseInt(num, 10)
})
ajax 請求中 get 和 post 的區別
- get 一般用于查詢操作,post 一般用于提交操作
- get 參數在 url 上,post 在請求體內
- 安全性:post 請求易于防止 CSRF
(post 代碼演示:網頁,postname)
call 和 apply 的區別
fn.call(this, p1, p2, p3)
fn.apply(this, arguments)
事件委托(代理)是什么
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {e.stopPropagation() // 注釋掉這一行,來體會事件冒泡alert('激活')
})
bindEvent(body, 'click', e => {alert('取消')
})
閉包是什么,有什么特性,對頁面有什么影響
知識點回顧
- 回歸作用域和自由變量
- 閉包的應用場景:函數作為參數被傳入,函數作為返回值被返回
- 關鍵點:自由變量的查找,要在函數定義的地方,而不是執行的地方
對頁面的影響
- 變量內存得不到釋放,可能會造成內存積累(不一定是泄露)
// 自由變量示例 —— 內存會被釋放
let a = 0
function fn1() {let a1 = 100function fn2() {let a2 = 200function fn3() {let a3 = 300return a + a1 + a2 + a3}fn3()}fn2()
}
fn1()
// 閉包 函數作為返回值 —— 內存不會被釋放
function create() {let a = 100return function () {console.log(a)}
}
let fn = create()
let a = 200
fn() // 100// 閉包 函數作為參數 —— 內存不會被釋放
function print(fn) {let a = 200fn()
}
let a = 100
function fn() {console.log(a)
}
print(fn) // 100
如何阻止事件冒泡和默認行為
event.stopPropagation()
event.preventDefault()
添加 刪除 替換 插入 移動 DOM 節點的方法
(粘貼一下代碼片段,作為回顧)
怎樣減少 DOM 操作?
- 緩存 DOM 查詢結果
- 多次操作,合并到一次插入
(粘貼一下代碼片段,作為回顧)
解釋 jsonp 的原理,以及為什么不是真正的 ajax
- 瀏覽器的同源策略,什么是跨域?
- 哪些 html 標簽能繞過跨域?
- jsonp 原理
document load 和 document ready 的區別
window.addEventListener('load', function () {// 頁面的全部資源加載完才會執行,包括圖片、視頻等
})
document.addEventListener('DOMContentLoaded', function () {// DOM 渲染完即可執行,此時圖片、視頻還可能沒有加載完
})
==
和 ===
的不同
- == 會嘗試進行類型轉換
- === 嚴格相等
函數聲明與函數表達式的區別?
const res = sum(10, 20)
console.log(res) // 30// 函數聲明
function sum(x, y) {return x + y
}
const res = sum(100, 200)
console.log(res) // 報錯!!!// 函數表達式
const sum = function(x, y) {return x + y
}
new Object()
和 Object.create()
的區別
示例一
const obj1 = {a: 10,b: 20,sum() {return this.a + this.b}
}
const obj2 = new Object({a: 10,b: 20,sum() {return this.a + this.b}
})
const obj3 = Object.create({a: 10,b: 20,sum() {return this.a + this.b}
})
// 分別打印看結構
示例二
const obj1 = {a: 10,b: 20,sum() {return this.a + this.b}
}
const obj2 = new Object(obj1)
console.log(obj1 === obj2) // true
const obj3 = Object.create(obj1)
console.log(obj1 === obj3) // falseconst obj4 = Object.create(obj1)
console.log(obj3 === obj4) // false// 然后修改 obj1 ,看 obj2 obj3 和 obj4
obj1.printA = function () {console.log(this.a)
}
對作用域上下文和 this 的理解,場景題
const User = {count: 1,getCount: function() {return this.count}
}
console.log(User.getCount()) // what?
const func = User.getCount
console.log( func() ) // what?
對作用域和自由變量的理解,場景題
let i
for(i = 1; i <= 3; i++) {setTimeout(function(){console.log(i)}, 0)
}
// what?
判斷字符串以字母開頭,后面可以是數字,下劃線,字母,長度為 6-30
const reg = /^[a-zA-Z]\w{5,29}$/
- 查看正則表達式規則 https://www.runoob.com/regexp/regexp-syntax.html
- 查看常見正則表達式
/\d{6}/ // 郵政編碼
/^[a-z]+$/ // 小寫英文字母
/^[A-Za-z]+$/ // 英文字母
/^\d{4}-\d{1,2}-\d{1,2}$/ // 日期格式
/^[a-zA-Z]\w{5,17}$/ // 用戶名(字母開頭,字母數字下劃線,5-17位)
/\d+\.\d+\.\d+\.\d+/ // 簡單的 IP 地址格式
以下代碼,分別 alert 出什么?
let a = 100
function test() { alert(a)a = 10alert(a)
}
test()
alert(a)
// what?
手寫 trim 函數,保證瀏覽器兼容性
String.prototype.trim= function (){return this.replace(/^\s+/,"").replace(/\s+$/,"")
}
知識點:原型,this,正則
如何獲取多個數值中的最大值?
Math.max(10, 30, 20, 40)
// 以及 Math.min
function max() {const nums = Array.prototype.slice.call(arguments) // 變為數組let max = 0nums.forEach(n => {if (n > max) {max = n}})return max
}
如何用 JS 實現繼承?
class 代碼
程序中捕獲異常的方法
try {// todo
} catch (ex) {console.error(ex) // 手動捕獲 catch
} finally {// todo
}
// 自動捕獲 catch(但對跨域的 js 如 CDN 的,不會有詳細的報錯信息)
window.onerror = function (message, source, lineNom, colNom, error) {
}
什么是 JSON ?
首先,json 是一種數據格式標準,本質是一段字符串,獨立于任何語言和平臺。注意,json 中的字符串都必須用雙引號。
{"name": "張三","info": {"single": true,"age": 30,"city": "北京"},"like": ["籃球", "音樂"]
}
其次,JSON 是 js 中一個內置的全局變量,有 JSON.parse
和 JSON.stringify
兩個 API 。
獲取當前頁面 url 參數
自己實現
// const url = 'https://www.xxx.com/path/index.html?a=100&b=200&c=300#anchor'
function query(name) {const search = location.search.substr(1) // 去掉前面的 `?`const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')const res = search.match(reg)if (res === null) {return null}return decodeURIComponent(res[2])
}
console.log( query('a') )
console.log( query('c') )
新的 API URLSearchParams
const pList = new URLSearchParams(location.search)
pList.get('a')
將 url 參數解析為 JS 對象?
自己編寫
function queryToObj() {const res = {}const search = location.search.substr(1) // 去掉前面的 `?`search.split('&').forEach(paramStr => {const arr = paramStr.split('=')const key = arr[0]const val = arr[1]res[key] = val})return res
}
新的 API URLSearchParams
function queryToObj() {const res = {}const pList = new URLSearchParams(location.search)pList.forEach((val, key) => {res[key] = val})return res
}
實現數組 flatern ,考慮多層級
function flat(arr) {// 驗證 arr 中,還有沒有深層數組,如 [1, [2, 3], 4]const isDeep = arr.some(item => item instanceof Array)if (!isDeep) {return arr // 沒有深層的,則返回}// 多深層的,則 concat 拼接const res = Array.prototype.concat.apply([], arr) // 回歸上文,apply 和 call 的區別return flat(res) // 遞歸調用,考慮多層
}
flat([[1,2], 3, [4,5, [6,7, [8, 9, [10, 11]]]]])
數組去重
要考慮:
- 順序是否一致?
- 時間復雜度
ES5 語法手寫。
// 寫法一
function unique(arr) {const obj = {}arr.forEach(item => {obj[item] = 1 // 用 Object ,去重計算高效,但順序不能保證。以及,非字符串會被轉換為字符串!!!})return Object.keys(obj)
}
unique([30, 10, 20, 30, 40, 10])
// 寫法二
function unique(arr) {const res = []arr.forEach(item => {if (res.indexOf(item) < 0) { // 用數組,每次都得判斷是否重復(低效),但能保證順序res.push(item)}})return res
}
unique([30, 10, 20, 30, 40, 10])
用 ES6 Set
// 數組去重
function unique(arr) {const set = new Set(arr)return [...set]
}
unique([30, 10, 20, 30, 40, 10])
手寫深拷貝
粘貼代碼
【注意】Object.assign
不是深拷貝,可以順帶講一下用法
Object.assign(obj1, {...})
const obj2 = Object.assign({}, obj1, {...})
介紹一下 RAF requestAnimationFrame
想用 JS 去實現動畫,老舊的方式是用 setTimeout 實時刷新,但這樣效率非常低,而且可能會出現卡頓。
- 想要動畫流暢,更新頻率是 60幀/s ,即每 16.6ms 更新一次試圖。太慢了,肉眼會感覺卡頓,太快了肉眼也感覺不到,資源浪費。
- 用 setTimeout 需要自己控制這個頻率,而 requestAnimationFrame 不用自己控制,瀏覽器會自動控制
- 在后臺標簽或者隱藏的
<iframe>
中,setTimeout 依然會執行,而 requestAnimationFrame 會自動暫停,節省計算資源
(代碼演示)
對前端性能優化有什么了解?一般都通過那幾個方面去優化的?
原則
- 多使用內存、緩存或者其他方法
- 減少 CPU 計算、較少網絡
方向
- 加載頁面和靜態資源
- 頁面渲染