因爲不可抗逆之因素,在金九銀十的後半段開始求職。 面試的確能夠驅動學習,驅動知識的歸類整理。 以此文記錄面試過程當中遇到的題目,僅供分享,不喜勿噴。javascript
本文題目皆來自於本人真實面試經歷 (持續更新中)css
涉及公司有(包括但不限於,僅供參考):html
簡述js事件循環?前端
簡答:vue
首先,因爲js是單線程,全部任務須要排隊。又爲了不因IO慢等緣由致使的阻塞,任務被分紅了「同步任務sync」和「異步任務async」。html5
其次,java
1.同步任務都在主線程上執行,造成一個「執行棧」(後進先出)。node
2.主線程以外,還存在一個「任務隊列」(先進先出)。異步任務有了運行結果後,就會將回調函數放置在任務隊列中。jquery
3.一旦調用棧清空,就會讀取「任務隊列」的回調函數到棧內等待主線程的執行webpack
這是循環的三步驟。
而後,重點要說明的是,任務隊列分爲宏任務隊列和微任務隊列,
每當調用棧清空的時候,先去讀取微任務隊列的全部微任務(例:Promise.then),再去讀取宏任務隊列的宏任務(例:setTimeout)。
每執行完一個宏任務後,都需檢查微任務隊列是否爲空,若是爲空,則執行下一個宏任務,不然,將會先把微任務隊列的微任務所有讀取執行完。
宏任務:script,setTimeout,setImmediate,promise中的executor
微任務:promise.then,process.nextTick
進一步: 我的理解QAQ
去食堂打飯,只排了一隊,窗口的阿姨問你要什麼啊?
1.若是你只要一個叉燒和燒鴨的雙拼飯(已經作好了放在窗口),阿姨直接打好飯遞給了你,而後下一個;
2.若是你要臘肉炒飯(須要現場炒),阿姨會把這個需求告訴專門作炒飯的阿叔,而後讓你在旁邊等,不要影響下一位同窗打飯;
3.阿叔把飯炒好了,遞給了阿姨,阿姨此時正在給另一個同窗打雙拼,她也會先把手上的雙拼打完遞給那位同窗,纔會把臘肉炒飯遞給你;
4.可是辛苦的阿叔除了作炒飯以外呢,還須要作手抓餅。若是你不幸由於打賭輸了須要幫傻_b舍友帶一個或幾個手抓餅。善良的阿叔會在炒完你的飯以後把手抓餅也一塊兒給作了,再去作下一份炒飯。
這個場景裏,打飯的隊伍是「主線程」執行棧,打雙拼飯是「同步任務」,作炒飯、作手抓餅是「異步任務」,作好的炒飯是「宏任務」,作好的手抓餅是「微任務」;
參考連接:
Tasks, microtasks, queues and schedules
推薦閱讀:
要求手寫!(部分人(也就是我)從jquery開始寫頁面,再到用vue,每每會不重視或者已經忘記原生怎麼寫的了)
示例:
參考連接:
簡答:
爲了實現「繼承」這個經常使用面嚮對象語言中最基本的概念,javaScript 基於原型鏈作出了實現。
訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,在沿着隱式原型_proto_這條鏈向上找
(由於obj._proto_===obj.constructor.prototype,對象的隱式原型_proto_等於構造這個對象的函數的顯示原型prototype)
複製代碼
進一步:
1.object instanceof constructor ,instanceof 運算符用來檢測 constructor.prototype 是否存在於參數 object 的原型鏈上
2.基本類型(Undefined、Null、Boolean、Number 和 String)、引用類型(Object、Array 和 Function)
3.一切(引用類型)都是對象,對象是屬性的集合Object本質上是由一組無序的名值對組成的
4.對象都是經過函數建立的
5.每一個函數function都有一個prototype,即原型。每一個對象都有一個__proto__,可成爲隱式原型,指向建立該對象的函數的prototype(這句話不理解,請先背下來)
Function.prototype.__proto__ === Object.prototype
obj.__proto__=== Object.prototype //Object.prototype
確是一個特例——它的__proto__指向的是null,切記切記!
複製代碼
幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例。
參考連接:
簡答:
閉包是函數和聲明該函數的詞法環境的組合。- MDN
在這樣的詞法環境下,阻止變量回收機制對變量的回收,能夠訪問函數內部做用域的變量。
function init() {
var name = "Mozilla"; // name 是一個被 init 建立的局部變量
function displayName() { // displayName() 是內部函數,一個閉包
alert(name); // 使用了父函數中聲明的變量
}
displayName();
}
init();
複製代碼
進一步
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c=a();
c();
複製代碼
在Javascript中,若是一個對象再也不被引用,那麼這個對象就會被GC回收。若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。由於函數a被b引用,b又被a外的c引用,這就是爲何函數a執行後不會被回收的緣由。(來源:百科)
優:
① 能夠讀取函數內部的變量
② 讓這些變量的值始終保持在內存中,不會在f1調用後被自動清除。
劣:
① 使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題。
實際遇到
let r=[]
for (let i=0;i<5;i++) {
let index=i+1
let res_all=vm.$apid.allService(100,1,index).then((val)=>{//allService(100,1,1) size,current,serviceType
let res=val.data.data.records
r[i] = res.map(item => {
return {
name: item.serviceName,
key: item.serviceId,
icon: vm.$store.state.myBaseUrl+vm.$store.state.devUrl+item.imgUrl,
cat: index
}
})
console.log(r)
})
}//循環請求異步問題
複製代碼
參考連接:
推薦閱讀:
特注:閉包、做用域、原型鏈、js數據類型,將在學習過程當中的某一個點融合在一塊兒,這是js的「最基本」!
面試題:addEventListener 循環綁定,如何改進下面的代碼能正確實現?有幾種方法?
<a>1</a><a>2</a><a>3</a>
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + i);
}, 'false');
};
// 這個代碼是錯誤的,由於變量i歷來就沒被locked住
// 相反,當循環執行之後,咱們在點擊的時候i纔得到數值
// 由於這個時候i操真正得到值
// 因此說不管點擊那個鏈接,最終顯示的都是I am link #3(若是有3個a元素的話)
複製代碼
解決1:用閉包解決
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', (function (num) {
return function(e){
e.preventDefault();
alert('I am link #' + num);
}
})(i), 'false');
};
//由於i相對匿名函數是外面的變量,就把循環綁定的時候,將i的值傳入到匿名函數內,
就能夠了。所以須要在匿名函數(事件函數)外包裹一個匿名函數, 並當即執行。
解決2:把var換成let
var elems = document.getElementsByTagName('a');
for (let i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + i);
}, 'false');
};
//var 命令的變量提高機制,var 命令實際只會執行一次。
//而 let 命令不存在變量提高,因此每次循環都會執行一次,聲明一個新變量(但初始化的值不同)。
//for 的每次循環都是不一樣的塊級做用域,
//let 聲明的變量是塊級做用域的,因此也不存在重複聲明的問題。
let生命變量的for循環裏,每一個匿名函數實際上引用的都是一個新的變量
解決3:沒用到閉包解決
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].num = i;
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + this.num);
}, 'false');
};
複製代碼
參考文獻:
面試題:
(function(){
console.log(a)//undefined
var a=1;
})()
(function(){
console.log(a)//報錯: Cannot access 'a' before initialization
let a=1;
})()
(function(){
{let a=1}
console.log(a)//報錯:a is not defined
})()
複製代碼
簡答:
js 有變量提高和函數提高,指的是用 var聲明變量 或用 function 函數名(){ } 聲明的,會在 js預解析 階段提高到頂端;(es6的let 和 const 不會提高)其次,函數提高優先級 高於 變量提高
簡答:
官網:
本質上,Webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。
當 Webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle
記憶點:靜態模塊打包器、依賴關係圖、bundle
從前端工程化角度看(「前端工程化」是加分項):
Webpack等相似工具是前端工程化工具。前端工程化是把前端開發工做帶入到更加系統和規範體系的一系列過程。這個過程會包括源代碼的預編譯、模塊處理、代碼壓縮等構建方面的工做。
Webpack的「一切皆模塊」以及「按需加載」兩大特性使得它更好地服務於工程化。
記憶點:預編譯、代碼壓縮、模塊處理、、按需加載
進一步:
Webpack 主要在打包中處理了下面這些問題:
1.從入口文件開始分析整個應用的依賴樹
2.將每一個依賴模塊包裝起來,並放到一個數組中等待調用
3.實現模塊加載的方法,並提供到模塊執行的環境中,使得模塊間能夠互相調用
4.將執行入口文件的邏輯放在一個當即執行函數表達式中
複製代碼
module.exports = {
// 配置打包選項 development開發環境
mode: 'development', // production 生產環境
// 指定入口文件:要打包的文件
entry: './src/js/index.js',
// 指定輸出文件:打包以後的文件
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.min.js'
},
// 配置資源的加載器 loader
module: {
rules: [
// 配置js的加載器(把ES6轉化爲ES3/5代碼)
{
test: /\.jsx?$/,
loader: 'babel-loader',
//打包除這個文件以外的文件
exclude: path.join(__dirname, './node_modules'),
//打包包括的文件
include: path.join(__dirname, './src')
},
// 配置css的加載器
{
// 匹配.css結尾的文件
test: /\.css$/,
// 配置css文件的加載器,處理順序:從右向左
use: ['style-loader', 'css-loader']
},
// 配置less的加載器
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
// 配置插件
plugins: [
new CleanWebpackPlugin(),
// 動態生成html
new HtmlWebpackPlugin({
title: '測試標題',
template: 'index.html'
})
],
// 配置實時預覽環境
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 5000
}
}
複製代碼
參考連接:
推薦閱讀:
簡答:
用webpack構建多頁應用能夠有2種思路,
1.多頁面單配置。主要把entry和plugins中的html-webpack-plugin進行改造便可。
2.多頁面多配置。多頁面單配置的優勢在於,不一樣頁面能夠共享相同代碼,容易實現長緩存。缺點主要是隨着項目的日漸龐大,打包速度會有明顯降低。
e.g. 多entry,修改plugin
var webpack = require('webpack');
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
// 配置入口
entry: {
about: './src/pages/about/about.js',
contact: './src/pages/contact/contact.js'
},
// 配置出口
output: {
path: __dirname + "/dist/",
filename: 'js/[name]-[hash:5].js',
publicPath: '/',
},
module: {
loaders: [
//解析.js
{
test: '/\.js$/',
loader: 'babel',
exclude: path.resolve(__dirname, 'node_modules'),
include: path.resolve(__dirname, 'src'),
query: {
presets: ['env']
}
},
// css處理
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
// less處理
{
test: /\.less$/,
loader: 'style-loader!css-loader!less-loader'
},
// 圖片處理
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
query: {
name: 'assets/[name]-[hash:5].[ext]'
},
},{
test: /\.(htm|html)$/i,
use:[ 'html-withimg-loader']
}
]
},
plugins: [
new ExtractTextPlugin(__dirname + '/assert/css/common.less'),
// minify:{
// removeComments: true,//刪除註釋
// collapseWhitespace:true//刪除空格
// }
new HtmlWebpackPlugin({
filename: __dirname + '/dist/about.html',
inject: 'head',
template: 'html-withimg-loader!'+__dirname + "/src/pages/about/about.html",
chunks: ['about'],
inlineSource: '.(js|css)$'
}),
new HtmlWebpackPlugin({
inject: 'head',
filename: __dirname + '/dist/contact.html',
template: __dirname + "/src/pages/contact/contact.html",
chunks: ['contact'],
inlineSource: '.(js|css)$'
}),
//設置每一次build以前先刪除dist
new CleanWebpackPlugin(
['dist/*', 'dist/*',],  //匹配刪除的文件
{
root: __dirname,           //根目錄
verbose: true,           //開啓在控制檯輸出信息
dry: false           //啓用刪除文件
}
)
],
// 起本地服務,我起的dist目錄
devServer: {
contentBase: "./dist/",
historyApiFallback: true,
inline: true,
hot: true,
host: '192.168.1.107',//個人局域網ip
}
}
複製代碼
進一步: 單頁面應用與多頁面應用的區別
單頁面應用(SinglePage Web Application,SPA) | 多頁面應用(MultiPage Application,MPA) | |
---|---|---|
組成 | 一個外殼頁面和多個頁面片斷組成 | 多個完整頁面構成 |
資源共用(css,js) | 共用,只需在外殼部分加載 | 不共用,每一個頁面都須要加載 |
刷新方式 | 頁面局部刷新或更改 | 整頁刷新 |
url 模式 | a.com/#/pageone a.com/#/pagetwo |
a.com/pageone.html a.com/pagetwo.html |
用戶體驗 | 頁面片斷間的切換快,用戶體驗良好 | 頁面切換加載緩慢,流暢度不夠,用戶體驗比較差 |
轉場動畫 | 容易實現 | 沒法實現 |
數據傳遞 | 容易 | 依賴 url傳參、或者cookie 、localStorage等 |
搜索引擎優化(SEO) | 須要單獨方案、實現較爲困難、不利於SEO檢索 可利用服務器端渲染(SSR)優化 | 實現方法簡易 |
試用範圍 | 高要求的體驗度、追求界面流暢的應用 | 適用於追求高度支持搜索引擎的應用 |
開發成本 | 較高,常需藉助專業的框架 | 較低 ,但頁面重複代碼多 |
維護成本 | 相對容易 | 相對複雜 |
參考連接:
簡答:
Vue內部經過Object.defineProperty方法屬性攔截的方式,把data對象裏每一個數據的讀寫轉化成getter/setter,當數據變化時通知視圖更新。
具體說一下Object.defineProperty:
Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。
Object.defineProperty(obj, prop, descriptor)
obj:要在其上定義屬性的對象。
prop:要定義或修改的屬性的名稱。
descriptor:將被定義或修改的屬性描述符。
複製代碼
進一步:
Object.defineProperty()具體實現:
//這段代碼過重要了,請記住!
let hr={
skill:'',
experience:''
}
Object.defineProperty(hr, 'skill', {
get(){
console.log('必備技能:')
return '不放鴿子'
},
set(newVal){
console.log('經驗要求:')
}
})
//讀:
console.log(hr.skill)
//寫:
hr.skill='五年起步'
複製代碼
控制檯打印
必備技能:
不放鴿子
經驗要求:
"五年起步"
複製代碼
發佈訂閱模式
如今已經能夠檢測到數據的讀和寫,而後就須要通知視圖的更新了.
這裏是典型的發佈訂閱模式,在這個模式下:數據是發佈者(Observer),依賴對象是訂閱者(Watcher),他們須要一箇中間人來傳遞,那就是訂閱器(Dep)。
總結:實現數據的雙向綁定,首先要對數據進行劫持監聽,因此咱們須要設置一個監聽器Observer,用來監聽全部屬性。若是屬性發生變化了,就須要告訴訂閱者Watcher看是否須要更新。由於訂閱者Watcher是有不少個,因此咱們須要有一個消息訂閱器Dep來專門收集這些訂閱者,而後在監聽器Observer和訂閱者Watcher之間進行統一管理。
附:從這張生命週期圖看數據和視圖在什麼時候雙向更新
參考連接:
推薦閱讀:
簡答:
在寫v-for的時候,都須要給元素加上一個key屬性
key的主要做用就是用來提升渲染性能的!
key屬性能夠避免數據混亂的狀況出現 (若是元素中包含了有臨時數據的元素,若是不用key就會產生數據混亂)
特注:
當 v-if 與 v-for 一塊兒使用時,v-for 具備比 v-if 更高的優先級,這意味着 v-if 將分別重複運行於每一個 v-for 循環中。 因此,不推薦v-if和v-for同時使用!
$nextTick 是在下次 DOM 更新循環結束以後執行延遲迴調用,在修改數據以後使用nextTick,則能夠在回調中獲取更新後的
怎麼理解:看下面這個例子就豁然開朗
DOM:
初始化數據: 點擊事件:觸發changeMsg後:
能夠看到,msg已經變成了Hello world,在changeMsg()方法中,先修改msg的值成爲‘Hello world’,而後經過拿到dom的值再依次修改msg一、msg二、msg3的值,此時修改獲得的msg1依然是‘hello vue’,this.$nextTick中修改獲得的msg2則是‘hello world’,msg3依然是‘hello vue’,也就是說,在changeMsg()方法觸發後,修改了msg的值,可是此時再經過dom取到的值還未改變,因此能夠知道:
vue響應式的改變一個值之後,此時的dom並不會當即更新,若是須要在數據改變之後當即經過dom作一些操做,可使用$nextTick得到更新後的dom。
參考連接:
簡答:
虛擬 DOM 的實現原理主要包括如下 3 部分:
用 JavaScript 對象模擬真實 DOM 樹,對真實 DOM 進行抽象; diff 算法 — 比較兩棵虛擬 DOM 樹的差別; pach 算法 — 將兩個虛擬 DOM 對象的差別應用到真正的 DOM 樹。
推薦閱讀
簡答:
瀏覽器的緩存是爲了性能的優化,經過重複調用本地緩存,減小Http請求這樣的方式,達到減小延遲、減小帶寬、下降網絡負荷的做用。
過程:瀏覽器發請求,訪問緩存,無緩存結果,發起HTTP請求,返回結果和緩存,存放緩存
緩存類型有:cookie、LocalStorage、sessionStorage
進一步:
客戶端直接從源站點獲取數據,當服務器訪問量大時會影響訪問速度,進而影響用戶體驗,且沒法保證客戶端與源站點間的距離足夠短,適合傳輸數據。
CDN解決的正是如何將數據快速可靠地從源站點傳遞到客戶端,經過CDN對數據的分發,用戶能夠從一個距離較近的服務器獲取數據,而不是源站點,從而達到快速訪問、且能減小源站點負載壓力的目的。
參考連接:
簡答:
1.DNS解析 2.TCP鏈接 3.發送HTTP請求 4.服務器處理請求並返回HTTP報文 5.瀏覽器解析渲染頁面 6.鏈接結束
簡答:
優化方法,結合雅虎軍規35條標準,能夠大體分爲這麼6類(非嚴格,有按本身的理解整理):
分類名 | 內容 |
---|---|
網絡 | 1.減小請求大小和次數;2.減小DNS查找,避免重定向;3.是異步請求可緩存;4.預加載、延遲加載、按需加載;5.減小Dom數量等; |
服務 | 1.使用CDN;2.Gzip組件壓縮;3.配置Etag;4.儘早刷新Buffer等; |
緩存 | 1.減少Cookie;2.CDN緩存; |
CSS/JavaScript | 1.Css放上面,Js放下面,避免阻塞;2.壓縮Css、Js;3.減小Dom訪問操做;4.減小重繪,避免迴流; |
圖片 | 1.壓縮圖片;2.用iconfont;3.用雪碧圖; |
其它 | 1.控制組件大小;2.使用預編譯語言,打包成模塊,按需加載; |
參考連接:
簡答:
xss: 跨站腳本攻擊(Cross Site Scripting)是最多見和基本的攻擊 WEB 網站方法,攻擊者經過注入非法的 html 標籤或者 javascript 代碼,從而當用戶瀏覽該網頁時,控制用戶瀏覽器。
csrf:跨站點請求僞造(Cross-Site Request Forgeries),也被稱爲 one-click attack 或者 session riding。冒充用戶發起請求(在用戶不知情的狀況下), 完成一些違背用戶意願的事情(如修改用戶信息,刪初評論等)。
防範:同源檢測、雙重cookie檢測等。
簡答:
名稱 | 內容 |
---|---|
靜態佈局(static layout) | 無論瀏覽器尺寸具體是多少,網頁佈局始終按照最初寫代碼時的佈局來顯示。 |
流式佈局(Liquid Layout) | 柵欄系統(網格系統):表明:bootstrap |
自適應佈局(Adaptive Layout) | 屏幕分辨率變化時,頁面裏面元素的位置會變化而大小不會變化。 |
響應式佈局(Responsive Layout) | 每一個屏幕分辨率下面會有一個佈局樣式,即元素位置和大小都會變。 |
彈性佈局(rem/em佈局) | rem/em區別:rem是相對於html元素的font-size大小而言的,而em是相對於其父元素。 |
進一步: rem與px的轉換
假設設計稿寬度爲750px,查看750px寬度的頁面對應的html{font-size:XXXpx}.
假設頁面寬750px,html{font-size:100px},即100px=1rem。此時想要設置一個按鈕的寬度,在設計稿中按鈕爲200px90px,那麼轉換以後的按鈕即爲2rem.9rem
html{//750的屏幕
font-size=10px;
/*font-size=62.5% //這裏就是10/16x100%=62.5% 也就是默認10px的字號*/
}
@media screen and (min-width: 640px){
html{
font-size: ?;
}
}
問號裏的值是多少?
解:
750/640=10/x
複製代碼
需求: 只用css實現,一個div分上下兩部分,上部分高度不固定,下面部分自動填滿剩餘高度
方法一:用flex 佈局
.wrapper{
display:flex;
flex-direction: column; //豎軸方向
}
.body{
flex:auto; //自動鋪滿剩餘空間
}
複製代碼
方法二:用絕對定位,而後在dom中增長一塊高度佔位的盒子
推薦閱讀:
要求手寫!
function binary_search(arr,target) {
let min=0
let max=arr.length-1
while(min<=max){
let mid=Math.ceil((min+max)/2)
if(arr[mid]==target){
return mid
}else if(arr[mid]>target){
max=mid-1
}else if(arr[mid]<target){
min=mid+1
}
}
return "null"
}
console.log(binary_search([1,5,7,19,88],19))//3
複製代碼
var arr=[10,20,50,100,40,200];
for(var i=0;i<arr.length-1;i++){
for(var j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
var temp=arr[j]
arr[j]=arr[j+1]
arr[j+1]=temp
}
}
}
console.log(arr)
複製代碼
1.不考慮Set()
2.不考慮雙層for循環,性能太差
利用對象的屬性不重複:
function distinct(arr) {
let result = []
let obj = {}
for (let i of arr) {
if (!obj[i]) {
result.push(i)
obj[i] = 1
}
}
return result
}
複製代碼