1. 程式人生 > 其它 >js面試題小結(1)

js面試題小結(1)

技術標籤:面試陣列物件深拷貝效能優化web常見攻擊git常用命令手寫ajax

1.效能優化

1.1優化原則和方向

原則
- 多使用記憶體、快取或者其他方法
- 減少 CPU 計算、較少網路

方向
- **載入頁面和靜態資源**
- **頁面渲染**

1.2方法:
## 載入資源優化
- 靜態資源的壓縮合並(JS程式碼壓縮合並、CSS程式碼壓縮合並、雪碧圖)
- 靜態資源快取(資源名稱加 MD5 戳)
- 使用 CND 讓資源載入更快
- 使用 SSR 後端渲染,資料直接突出到 HTML 中

## 渲染優化
- CSS 放前面 JS 放後面
- 懶載入(圖片懶載入、下拉載入更多)
- 減少DOM 查詢,對 DOM 查詢做快取

- 減少DOM 操作,多個操作儘量合併在一起執行(`DocumentFragment`)
- 事件節流
- 儘早執行操作(`DOMContentLoaded`)

1.3 事件節流
例如要在文字改變時觸發一個 change 事件,通過 keyup 來監聽。使用節流。

var textarea = document.getElementById('text')
var timeoutId
textarea.addEventListener('keyup', function () {
    if (timeoutId) {
        clearTimeout(timeoutId)
    }
    timeoutId = setTimeout(function () {
        // 觸發 change 事件
    }, 100)
})

1.4 儘早執行操作

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

2.常見的 web 攻擊方式有哪些,簡述原理?如何預防?
2.1前端端最常見的攻擊就是 XSS(Cross Site Scripting,跨站指令碼攻擊):很多大型網站(例如 FaceBook 都被 XSS 攻擊過)。舉一個例子,我在一個部落格網站正常發表一篇文章,輸入漢字、英文和圖片,完全沒有問題。但是如果我寫的是惡意的 js 指令碼,例如獲取到`document.cookie`然後傳輸到自己的伺服器上,那我這篇部落格的每一次瀏覽,都會執行這個指令碼,都會把自己的 cookie 中的資訊偷偷傳遞到我的伺服器上來。

預防 XSS 攻擊就得對輸入的內容進行過濾,過濾掉一切可以執行的指令碼和指令碼連結。大家可以參考[xss.js](https://github.com/leizongmin/js-xss)這個開源工具。

簡單總結一下,XSS 其實就是攻擊者事先在一個頁面埋下攻擊程式碼,讓登入使用者去訪問這個頁面,然後偷偷執行程式碼,拿到當前使用者的資訊。

2.2還有一個比較常見的攻擊就是 CSRF/XSRF(Cross-site request forgery,跨站請求偽造):它是借用了當前操作者的許可權來偷偷的完成某個操作,而不是拿到使用者的資訊。例如,一個購物網站,購物付費的介面是`http://buy.com/pay?id=100`,而這個介面在使用時沒有任何密碼或者 token 的驗證,只要開啟訪問就付費購買了。一個使用者已經登入了`http://buy.com`在選擇商品時,突然收到一封郵件,而這封郵件正文有這麼一行程式碼`<img src="http://buy.com/pay?id=100"/>`,他訪問了郵件之後,其實就已經完成了購買。

預防 CSRF 就是加入各個層級的許可權驗證,例如現在的購物網站,只要涉及到現金交易,肯定輸入密碼或者指紋才行。

3.JS中使用`typeof`能得到的哪些型別

typeof undefined // undefined
typeof 'abc' // string
typeof 123 // number
typeof true // boolean
typeof {}  // object
typeof [] // object
typeof null // object
typeof console.log // function

4.說明 this 幾種不同的使用場景
- 作為建構函式執行
- 作為物件屬性執行
- 作為普通函式執行
- call apply bind

5.前端使用非同步的場景有哪些
setTimeout setInterval
網路請求

6.建構函式

function DomElement(selector) {
    var result = document.querySelectorAll(selector)
    var length = result.length
    var i
    for (i = 0; i < length; i++) {
        this[i] = selectorResult[i]
    }
    this.length = length
}
// 修改原型
DomElement.prototype = {
    constructor: DomElement,
    get: function (index) {
        return this[index]
    },
    forEach: function (fn) {
        var i
        for (i = 0; i < this.length; i++) {
            const elem = this[i]
            const result = fn.call(elem, elem, i)
            if (result === false) {
                break
            }
        }
        return this
    },
    on: function (type, fn) {
        return this.forEach(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
}

// 使用
var $div = new DomElement('div')
$div.on('click', function() {
    console.log('click')
})

7.用於`replace`的示例

function trim(str) {
    return str.replace(/(^\s+)|(\s+$)/g, '')
}

8.獲取`2017-06-10`格式的日期

function formatDate(dt) {
    if (!dt) {  dt = new Date()  }
    var year = dt.getFullYear()
    var month = dt.getMonth() + 1
    var date = dt.getDate()
    if (month < 10) {
        // 強制型別轉換
        month = '0' + month
    }
    if (date < 10) {
        // 強制型別轉換
        date = '0' + date
    }
    // 強制型別轉換
    return year + '-' + month + '-' + date
}
var dt = new Date()
var formatDate = formatDate(dt)
console.log(formatDate)

9.獲取隨機數,要求是長度一直的字串格式

var random = Math.random()
var random = random + '0000000000'  // 後面加上 10 個零
var random = random.slice(0, 10)
console.log(random)

10.寫一個能遍歷物件和陣列的`forEach`函式

function forEach(obj, fn) {
    var key
    if (obj instanceof Array) {
        // 準確判斷是不是陣列
        obj.forEach(function (item, index) {
            fn(index, item)
        })
    } else {
        // 不是陣列就是物件
        for (key in obj) {
            fn(key, obj[key])
        }
    }
}
var arr = [1,2,3]
// 注意,這裡引數的順序換了,為了和物件的遍歷格式一致
forEach(arr, function (index, item) {console.log(index, item)})
var obj = {x: 100, y: 200}
forEach(obj, function (key, value) {console.log(key, value)})

11.如何檢測瀏覽器的型別

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)

12.拆解url的各部分

console.log(location.href)
console.log(location.protocol) // 'http:' 'https:'
console.log(location.pathname) // '/learn/199'
console.log(location.search)
console.log(location.hash)

13.編寫一個通用的事件監聽函式

function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, function (e) {
        var target
        if (selector) {
            target = e.target
            if (target.matches(selector)) {
                fn.call(target, e)
            }
        } else {
            fn(e)
        }
    })
}

14.手動編寫一個 ajax,不依賴第三方庫

var xhr = new XMLHttpRequest()
xhr.open("GET", "/api", false)
xhr.onreadystatechange = function () {
    // 這裡的函式非同步執行,可參考之前 JS 基礎中的非同步模組
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {  alert(xhr.responseText)  }
    }
}
xhr.send(null)

xhr.readyState 的狀態嗎說明
- 0 - (未初始化)還沒有呼叫send()方法
- 1 -(載入)已呼叫send()方法,正在傳送請求
- 2 -(載入完成)send()方法執行完成,已經接收到全部響應內容
- 3 -(互動)正在解析響應內容
- 4 -(完成)響應內容解析完成,可以在客戶端呼叫了

### status
http 狀態嗎有 `2xx` `3xx` `4xx` `5xx` 這幾種,比較常用的有以下幾種
- 200 正常
- 404 找不到資源
- 5xx 伺服器端出錯了

15.跨域的幾種實現方式

①JSONP;②伺服器端設定 http header

16.請描述一下 cookie,sessionStorage 和 localStorage 的區別?

## cookie 有它致命的缺點:

- 儲存量太小,只有 4KB
- 所有 http 請求都帶著,會影響獲取資源的效率
- API 簡單,需要封裝才能用

## locationStorage 和 sessionStorage
後來,HTML5標準就帶來了`sessionStorage`和`localStorage`,先拿`localStorage`來說,它是專門為了瀏覽器端快取而設計的。其優點有:

- 儲存量增大到 5M
- 不會帶到 http 請求中
- API 適用於資料儲存 `localStorage.setItem(key, value)` `localStorage.getItem(key)`

17.寫出一些常用的 git 命令

- git add .
- git checkout xxx
- git commit -m "xxx"
- git push origin master
- git pull origin master
- git stash / git stash pop

18.簡述多人使用 git 協作開發的基本流程

- git branch
- git checkout -b xxx / git checkout xxx
- git merge xxx

19.上線和迴歸

### 上線原理
- 將測試完成的程式碼提交到git版本庫的master分支
- 將當前伺服器的程式碼全部打包並記錄版本號,備份
- 將master分支的程式碼提交覆蓋到線上伺服器,生成新版本號

### 回滾原理
- 將當前伺服器的程式碼打包並記錄版本號,備份
- 將備份的上一個版本號解壓,覆蓋到線上伺服器,並生成新的版本號

20.從輸入url到得到html的詳細過程

- 瀏覽器根據 DNS 伺服器得到域名的 IP 地址
- 向這個 IP 的機器傳送 http 請求
- 伺服器收到、處理並返回 http 請求
- 瀏覽器得到返回內容

21.瀏覽器渲染頁面的過程

- 根據 HTML 結構生成 DOM Tree
- 根據 CSS 生成 CSS Rule
- 將 DOM 和 CSSOM 整合形成 RenderTree
- 根據 RenderTree 開始渲染和展示
- 遇到`<script>`時,會執行並阻塞渲染

22.window.onload 和 DOMContentLoaded 的區別

- 頁面的全部資源載入完才會執行,包括圖片、視訊等
- DOM 渲染完即可執行,此時圖片、視訊還沒有載入完

23.ES6

- 搭建 ES6 編譯環境(使用 babel 編譯,使用 webpack 做模組化處理)
- modules:關鍵字`import` `export`
- 瑣碎的功能:let/const,多行字串/模板變數,解構賦值,塊級作用域,函式預設引數,箭頭函式
- class:用法、和原型的關係、繼承
- Promise:用法

// 載入一張圖片
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject()
        }
        img.src = src
    })
    return promise   
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
    console.log(img.width)
}, function () {    
    console.log('failed')
}).then(function (img) {
    console.log(img.height)
})

24.event-loop

將 js 中要執行的每個任務都做一個劃分,同步執行的放在“執行棧”,而非同步執行的**將**放在(不是馬上就放進去)“非同步佇列”。然後,將“執行棧”放在主執行緒中執行,挨個任務排隊執行,執行到最後就立馬去看“非同步佇列”是否有資料,有資料就拿到主執行緒中執行,執行完再去看“非同步佇列”是否有資料。

25.非同步的變同步的解決方案(非同步本身沒問題,但callback hell是有問題的,所以需要處理)

25.1 Promise (then的方式,參考23)

25.2 asyc-await(注意,async/await 並不是取代了 promise ,而是利用 promise 之後的另外一種寫法,取代了 then 函式,寫起來更加像同步程式碼,看著從上到下書寫,也從上到下執行)

function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {  resolve(img)  }
        img.onerror = function () {  reject()  }
        img.src = src
    })
    return promise
}
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
const load = async function () {
    const result1 = await loadImg(src1)
    console.log(result1)
    const result2 = await loadImg(src2)
    console.log(result2)
}
load()

26.陣列物件深拷貝(重點在遞迴呼叫,例如在物件層級好幾層(特別深的時候))

function deepClone(obj = {}) {
	//如果不是物件直接返回
	if(typeof obj !== 'object' || obj == null) {  return obj  }
	let result;
	if(obj instanceof Array) {
		result = []
	} else {
		result = {}
	}
	for(let key in obj) {
		// 保證key不是原型屬性
		if(obj.hasOwnProperty(key)) {
			result[key] = deepClone(obj[key])
		}
	}
	return result
}

27.手寫jQuery

class Jquery{
	constructor(selector){
		const result = document.querySelectorAll(selector)
		const length = result.length;
		for(let i=0;i<result.length;i++){
			this[i]=result[i]
		}
		this.length = result.length
		this.selector = selector
	}
	get(index){
		return this[index]
	}
	each(fn){
		for (let i=0;i<this.length;i++){
			const elem = this[i]
			fn(elem)
		}
	}
	on(type,fn){
		return this.each(elem=>{
			elem.addEventListener(type,fn,false)
		})
	}
}

28.手寫bind函式

Function.prototype.bind1 = function(){
	// 將引數拆解為陣列
	const args = Array.prototype.slice.call(arguments)
	
	// 獲取this(陣列第一項)
	const t = args.shift()
	
	// fn1.bind(...) 中的fn1
	const self = this
	
	return function(){
		return self.apply(t,args)
	}
}

29.防抖(輸入框輸入keyword,做搜尋)

function debounce(fn,delay=500){
	let timer = null
	return function(){
		if(timer){
			clearTimeout(timer)
		}
		
		timer = setTimeout(()=>{
			fn.apply(this,arguments)
			timer = null
		},delay)
	}
}

30.節流(拖拽根據位置做一些邏輯操作)

function throttle(fn, delay = 100) {
	let timer = null
	return function() {
		if(timer) {
			return
		}

		timer = setTimeout(() => {
			fn.apply(this, arguments)
			timer = null
		}, delay)
	}
}

31.事件繫結(普通事件和事件委託)

function bindEvent(elem,type,selector,fn){
	if(fn==null){
		fn=selector
		selector = null 
	}
	
	elem.addEventListener(type,event=>{
		let target = event.target
		
		if( selector ){
			if(target.matches(selector)){
				fn.call(target,event)
			}
		}else{
			fn.call(target,event)
		}
	})
}