點進來之后你的噩夢就要來了,接下來你要面對上百道面試題,那么,如果你——
- 是個小白菜:
- 推薦使用2~3周的時間來消化接下來的面試題,
- 遇到不會的沒聽說過名詞請立刻去搜;
- 文章中只是簡答,如果想要詳細了解的話還需要你自覺去搜索
- 如果你是個大神:
- 好叭先給您拜個早年,大哥大嫂過年好,
- 請溫柔點黑我,
順便,如果有錯誤的地方請各位一定要指出,免得誤導更多人,
接下來的題我會根據重點程度使用?來標記,?越多標明越重點,滿星是5顆星
ok,你準備好了嗎?咱們開始吧!

JS
資料型別
面試官:JavaScript中什么是基本資料型別什么是參考資料型別?以及各個資料型別是如何存盤的??????
答:
基本資料型別有
- Number
- String
- Boolean
- Null
- Undefined
- Symbol(ES6新增資料型別)
- bigInt
參考資料型別統稱為Object型別,細分的話有
- Object
- Array
- Date
- Function
- RegExp
基本資料型別的資料直接存盤在堆疊中;而參考資料型別的資料存盤在堆中,每個物件在堆中有一個參考地址,參考型別在堆疊中會保存他的參考地址,以便快速查找到堆記憶體中的物件,
順便提一句,堆疊記憶體是自動分配記憶體的,而堆記憶體是動態分配記憶體的,不會自動釋放,所以每次使用完物件的時候都要把它設定為null,從而減少無用記憶體的消耗
型別轉換
面試官:在JS中為什么0.2+0.1>0.3?????
答:
因為在JS中,浮點數是使用64位固定長度來表示的,其中的1位表示符號位,11位用來表示指數位,剩下的52位尾數位,由于只有52位表示尾數位,
而0.1轉為二進制是一個無限回圈數0.0001100110011001100......(1100回圈)
小數的十進制轉二進制方法:https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html
要知道,小數的十進制轉二進制的方法是和整數不一樣的,推薦看一看
由于只能存盤52位尾數位,所以會出現精度缺失,把它存到記憶體中再取出來轉換成十進制就不是原來的0.1了,就變成了0.100000000000000005551115123126,而為什么02+0.1是因為
// 0.1 和 0.2 都轉化成二進制后再進行運算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
// 轉成十進制正好是 0.30000000000000004
面試官:那為什么0.2+0.3=0.5呢?????
// 0.2 和 0.3 都轉化為二進制后再進行計算
0.001100110011001100110011001100110011001100110011001101 +
0.0100110011001100110011001100110011001100110011001101 =
0.10000000000000000000000000000000000000000000000000001 //尾數為大于52位
// 而實際取值只取52位尾數位,就變成了
0.1000000000000000000000000000000000000000000000000000 //0.5
答:0.2 和0.3分別轉換為二進制進行計算:在記憶體中,它們的尾數位都是等于52位的,而他們相加必定大于52位,而他們相加又恰巧前52位尾數都是0,截取后恰好是0.1000000000000000000000000000000000000000000000000000也就是0.5
面試官:那既然0.1不是0.1了,為什么在console.log(0.1)的時候還是0.1呢????
答:在console.log的時候會二進制轉換為十進制,十進制再會轉為字串的形式,在轉換的程序中發生了取近似值,所以列印出來的是一個近似值的字串
面試官:判斷資料型別有幾種方法?????
答:
-
typeof- 缺點:
typeof null的值為Object,無法分辨是null還是Object
- 缺點:
-
instanceof- 缺點:只能判斷物件是否存在于目標物件的原型鏈上
-
constructor -
Object.prototype.toString.call()-
一種最好的基本型別檢測方式
Object.prototype.toString.call();它可以區分 null 、 string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 資料型別,
-
缺點:不能細分為誰誰的實體
-
// -----------------------------------------typeof
typeof undefined // 'undefined'
typeof '10' // 'String'
typeof 10 // 'Number'
typeof false // 'Boolean'
typeof Symbol() // 'Symbol'
typeof Function // ‘function'
typeof null // ‘Object’
typeof [] // 'Object'
typeof {} // 'Object'
// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo();
var d = new Number(1)
console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false -->不能判斷字面量的基本資料型別
// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(e.constructor);//? Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function
console.log(date.constructor.name)// Date
console.log(arr.constructor.name) // Array
console.log(reg.constructor.name) // RegExp
//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call("abc")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]"
console.log(Object.prototype.toString.call(date));// "[object Date]"
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
instanceof原理?????
- instanceof原理實際上就是查找目標物件的原型鏈
function myInstance(L, R) {//L代表instanceof左邊,R代表右邊
var RP = R.prototype
var LP = L.__proto__
while (true) {
if(LP == null) {
return false
}
if(LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({},Object));
面試官:為什么typeof null是Object????
答:
因為在JavaScript中,不同的物件都是使用二進制存盤的,如果二進制前三位都是0的話,系統會判斷為是Object型別,而null的二進制全是0,自然也就判斷為Object
這個bug是初版本的JavaScript中留下的,擴展一下其他五種標識位:
000物件1整型010雙精度型別100字串110布爾型別
面試官:==和===有什么區別?????
答:
===是嚴格意義上的相等,會比較兩邊的資料型別和值大小
- 資料型別不同回傳false
- 資料型別相同,但值大小不同,回傳false
==是非嚴格意義上的相等,
-
兩邊型別相同,比較大小
-
兩邊型別不同,根據下方表格,再進一步進行比較,
- Null == Undefined ->true
- String == Number ->先將String轉為Number,在比較大小
- Boolean == Number ->現將Boolean轉為Number,在進行比較
- Object == String,Number,Symbol -> Object 轉化為原始型別
面試官:手寫call、apply、bind?????
答:
- call和apply實作思路主要是:
- 判斷是否是函式呼叫,若非函式呼叫拋例外
- 通過新物件(context)來呼叫函式
- 給context創建一個
fn設定為需要呼叫的函式 - 結束呼叫完之后洗掉
fn
- 給context創建一個
- bind實作思路
- 判斷是否是函式呼叫,若非函式呼叫拋例外
- 回傳函式
- 判斷函式的呼叫方式,是否是被new出來的
- new出來的話回傳空物件,但是實體的
__proto__指向_this的prototype
- new出來的話回傳空物件,但是實體的
- 判斷函式的呼叫方式,是否是被new出來的
- 完成函式柯里化
Array.prototype.slice.call()
call:
Function.prototype.myCall = function (context) {
// 先判斷呼叫myCall是不是一個函式
// 這里的this就是呼叫myCall的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}
// 不傳引數默認為window
context = context || window
// 保存this
context.fn = this
// 保存引數
let args = Array.from(arguments).slice(1) //Array.from 把偽陣列物件轉為陣列
// 呼叫函式
let result = context.fn(...args)
delete context.fn
return result
}
apply
Function.prototype.myApply = function (context) {
// 判斷this是不是函式
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
let result
// 默認是window
context = context || window
// 保存this
context.fn = this
// 是否傳參
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind
Function.prototype.myBind = function(context){
// 判斷是否是一個函式
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存呼叫bind的函式
const _this = this
// 保存引數
const args = Array.prototype.slice.call(arguments,1)
// 回傳一個函式
return function F () {
// 判斷是不是new出來的
if(this instanceof F) {
// 如果是new出來的
// 回傳一個空物件,且使創建出來的實體的__proto__指向_this的prototype,且完成函式柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出來的改變this指向,且完成函式柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
面試官:字面量創建物件和new創建物件有什么區別,new內部都實作了什么,手寫一個new?????
答:
字面量:
- 字面量創建物件更簡單,方便閱讀
- 不需要作用域決議,速度更快
new內部:
- 創建一個新物件
- 使新物件的
__proto__指向原函式的prototype - 改變this指向(指向新的obj)并執行該函式,執行結果保存起來作為result
- 判斷執行函式的結果是不是null或Undefined,如果是則回傳之前的新物件,如果不是則回傳result
手寫new
// 手寫一個new
function myNew(fn, ...args) {
// 創建一個空物件
let obj = {}
// 使空物件的隱式原型指向原函式的顯式原型
obj.__proto__ = fn.prototype
// this指向obj
let result = fn.apply(obj, args)
// 回傳
return result instanceof Object ? result : obj
}
面試官:字面量new出來的物件和 Object.create(null)創建出來的物件有什么區別???
答:
-
字面量和new創建出來的物件會繼承Object的方法和屬性,他們的隱式原型會指向Object的顯式原型,
-
而
Object.create(null)創建出來的物件原型為null,作為原型鏈的頂端,自然也沒有繼承Object的方法和屬性
執行堆疊和執行背景關系
面試官:什么是作用域,什么是作用域鏈?????
答:
- 規定變數和函式的可使用范圍稱作作用域
- 每個函式都有一個作用域鏈,查找變數或者函式時,需要從區域作用域到全域作用域依次查找,這些作用域的集合稱作作用域鏈,
面試官:什么是執行堆疊,什么是執行背景關系?????
答:
執行背景關系分為:
- 全域執行背景關系
- 創建一個全域的window物件,并規定this指向window,執行js的時候就壓入堆疊底,關閉瀏覽器的時候才彈出
- 函式執行背景關系
- 每次函式呼叫時,都會新創建一個函式執行背景關系
- 執行背景關系分為創建階段和執行階段
- 創建階段:函式環境會創建變數物件:arguments物件(并賦值)、函式宣告(并賦值)、變數宣告(不賦值),函式運算式宣告(不賦值);會確定this指向;會確定作用域
- 執行階段:變數賦值、函式運算式賦值,使變數物件編程活躍物件
- eval執行背景關系
執行堆疊:
- 首先堆疊特點:先進后出
- 當進入一個執行環境,就會創建出它的執行背景關系,然后進行壓堆疊,當程式執行完成時,它的執行背景關系就會被銷毀,進行彈堆疊,
- 堆疊底永遠是全域環境的執行背景關系,堆疊頂永遠是正在執行函式的執行背景關系
- 只有瀏覽器關閉的時候全域執行背景關系才會彈出
閉包
很多人都吃不透js閉包,這里推薦一篇文章:徹底理解js中的閉包
面試官:什么是閉包?閉包的作用?閉包的應用??????
答:
函式執行,形成私有的執行背景關系,使內部私有變數不受外界干擾,起到保護和保存的作用
作用:
- 保護
- 避免命名沖突
- 保存
- 解決回圈系結引發的索引問題
- 變數不會銷毀
- 可以使用函式內部的變數,使變數不會被垃圾回識訓制回收
應用:
- 設計模式中的單例模式
- for回圈中的保留i的操作
- 防抖和節流
- 函式柯里化
缺點
- 會出現記憶體泄漏的問題
原型和原型鏈
面試官:什么是原型?什么是原型鏈?如何理解?????
答:
原型: 原型分為隱式原型和顯式原型,每個物件都有一個隱式原型,它指向自己的建構式的顯式原型,
原型鏈: 多個__proto__組成的集合成為原型鏈
- 所有實體的
__proto__都指向他們建構式的prototype - 所有的
prototype都是物件,自然它的__proto__指向的是Object()的prototype - 所有的建構式的隱式原型指向的都是
Function()的顯示原型 - Object的隱式原型是null
繼承
面試官:說一說 JS 中的常用的繼承方式有哪些?以及各個繼承方式的優缺點,?????
答:
原型繼承、組合繼承、寄生組合繼承、ES6的extend
原型繼承
// ----------------------方法一:原型繼承
// 原型繼承
// 把父類的實體作為子類的原型
// 缺點:子類的實體共享了父類建構式的參考屬性 不能傳參
var person = {
friends: ["a", "b", "c", "d"]
}
var p1 = Object.create(person)
p1.friends.push("aaa")//缺點:子類的實體共享了父類建構式的參考屬性
console.log(p1);
console.log(person);//缺點:子類的實體共享了父類建構式的參考屬性
組合繼承
// ----------------------方法二:組合繼承
// 在子函式中運行父函式,但是要利用call把this改變一下,
// 再在子函式的prototype里面new Father() ,使Father的原型中的方法也得到繼承,最后改變Son的原型中的constructor
// 缺點:呼叫了兩次父類的建構式,造成了不必要的消耗,父類方法可以復用
// 優點可傳參,不共享父類參考屬性
function Father(name) {
this.name = name
this.hobby = ["籃球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.constructor = Son
var s = new Son("ming", 20)
console.log(s);
寄生組合繼承
// ----------------------方法三:寄生組合繼承
function Father(name) {
this.name = name
this.hobby = ["籃球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
var s2 = new Son("ming", 18)
console.log(s2);
extend
// ----------------------方法四:ES6的extend(寄生組合繼承的語法糖)
// 子類只要繼承父類,可以不寫 constructor ,一旦寫了,則在 constructor 中的第一句話
// 必須是 super ,
class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
constructor(y) {
super(200) // super(200) => Father.call(this,200)
this.y = y
}
}
記憶體泄露、垃圾回識訓制
面試官:什么是記憶體泄漏?????
答:
? 記憶體泄露是指不再用的記憶體沒有被及時釋放出來,導致該段記憶體無法被使用就是記憶體泄漏
面試官:為什么會導致的記憶體泄漏?????
答:
記憶體泄漏指我們無法在通過js訪問某個物件,而垃圾回識訓制卻認為該物件還在被參考,因此垃圾回識訓制不會釋放該物件,導致該塊記憶體永遠無法釋放,積少成多,系統會越來越卡以至于崩潰
面試官:垃圾回識訓制都有哪些策略??????
答:
- 標記清除法
- 垃圾回識訓制獲取根并標記他們,然后訪問并標記所有來自它們的參考,然后在訪問這些物件并標記它們的參考…如此遞進結束后若發現有沒有標記的(不可達的)進行洗掉,進入執行環境的不能進行洗掉
- 參考計數法
- 當宣告一個變數并給該變數賦值一個參考型別的值時候,該值的計數+1,當該值賦值給另一個變數的時候,該計數+1,當該值被其他值取代的時候,該計數-1,當計數變為0的時候,說明無法訪問該值了,垃圾回識訓制清除該物件
深拷貝和淺拷貝
手寫淺拷貝深拷貝?????
// ----------------------------------------------淺拷貝
// 只是把物件的屬性和屬性值拷貝到另一個物件中
var obj1 = {
a: {
a1: { a2: 1 },
a10: { a11: 123, a111: { a1111: 123123 } }
},
b: 123,
c: "123"
}
// 方式1
function shallowClone1(o) {
let obj = {}
for (let i in o) {
obj[i] = o[i]
}
return obj
}
// 方式2
var shallowObj2 = { ...obj1 }
// 方式3
var shallowObj3 = Object.assign({}, obj1)
let shallowObj = shallowClone1(obj1);
shallowObj.a.a1 = 999
shallowObj.b = true
console.log(obj1); //第一層的沒有被改變,一層以下就被改變了
// ----------------------------------------------深拷貝
// 簡易版
function deepClone(o) {
let obj = {}
for (var i in o) {
// if(o.hasOwnProperty(i)){
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
// }
}
return obj
}
var myObj = {
a: {
a1: { a2: 1 },
a10: { a11: 123, a111: { a1111: 123123 } }
},
b: 123,
c: "123"
}
var deepObj1 = deepClone(myObj)
deepObj1.a.a1 = 999
deepObj1.b = false
console.log(myObj);
// 簡易版存在的問題:引數沒有做檢驗,傳入的可能是 Array、null、regExp、Date
function deepClone2(o) {
if (Object.prototype.toString.call(o) === "[object Object]") { //檢測是否為物件
let obj = {}
for (var i in o) {
if (o.hasOwnProperty(i)) {
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
}
return obj
} else {
return o
}
}
function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
}
// 繼續升級,沒有考慮到陣列,以及ES6中的map、set、weakset、weakmap
function deepClone3(o) {
if (isObject(o)) {//檢測是否為物件或者陣列
let obj = Array.isArray(o) ? [] : {}
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
return obj
} else {
return o
}
}
// 有可能碰到回圈參考問題 var a = {}; a.a = a; clone(a);//會造成一個死回圈
// 回圈檢測
// 繼續升級
function deepClone4(o, hash = new map()) {
if (!isObject(o)) return o//檢測是否為物件或者陣列
if (hash.has(o)) return hash.get(o)
let obj = Array.isArray(o) ? [] : {}
hash.set(o, obj)
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone4(o[i], hash)
} else {
obj[i] = o[i]
}
}
return obj
}
// 遞回易出現爆堆疊問題
// 將遞回改為回圈,就不會出現爆堆疊問題了
var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
var b1 = { b: { c: { d: 1 } } }
function cloneLoop(x) {
const root = {};
// 堆疊
const loopList = [ //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
{
parent: root,
key: undefined,
data: x,
}
];
while (loopList.length) {
// 深度優先
const node = loopList.pop();
const parent = node.parent; //{} //{a:1,b:2}
const key = node.key; //undefined //c
const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' } //{ c1: 3, c2: { c21: 4, c22: 5 } }}
// 初始化賦值目標,key 為 undefined 則拷貝到父元素,否則拷貝到子元素
let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次回圈
loopList.push({
parent: res,
key: k,
data: data[k],
})
} else {
res[k] = data[k];
}
}
}
}
return root
}
function deepClone5(o) {
let result = {}
let loopList = [
{
parent: result,
key: undefined,
data: o
}
]
while (loopList.length) {
let node = loopList.pop()
let { parent, key, data } = node
let anoPar = parent
if (typeof key !== 'undefined') {
anoPar = parent[key] = {}
}
for (let i in data) {
if (typeof data[i] === 'object') {
loopList.push({
parent: anoPar,
key: i,
data: data[i]
})
} else {
anoPar[i] = data[i]
}
}
}
return result
}
let cloneA1 = deepClone5(a1)
cloneA1.c.c2.c22 = 5555555
console.log(a1);
console.log(cloneA1);
// ------------------------------------------JSON.stringify()實作深拷貝
function cloneJson(o) {
return JSON.parse(JSON.stringify(o))
}
// let obj = { a: { c: 1 }, b: {} };
// obj.b = obj;
// console.log(JSON.parse(JSON.stringify(obj))) // 報錯 // Converting circular structure to JSON
深拷貝能使用hash遞回的方式寫出來就可以了
不過技多不壓身,推薦還是看一看使用while實作深拷貝方法
單執行緒,同步異步
面試官:為什么JS是單執行緒的??????
**答:**因為JS里面有可視的Dom,如果是多執行緒的話,這個執行緒正在洗掉DOM節點,另一個執行緒正在編輯Dom節點,導致瀏覽器不知道該聽誰的
面試官:如何實作異步編程?
**答:**回呼函式
面試官:Generator是怎么樣使用的以及各個階段的變化如何????
答:
-
首先生成器是一個函式,用來回傳迭代器的
-
呼叫生成器后不會立即執行,而是通過回傳的迭代器來控制這個生成器的一步一步執行的
-
通過呼叫迭代器的next方法來請求一個一個的值,回傳的物件有兩個屬性,一個是value,也就是值;另一個是
done,是個布爾型別,done為true說明生成器函式執行完畢,沒有可回傳的值了, -
done為true后繼續呼叫迭代器的next方法,回傳值的value為undefined
狀態變化:
- 每當執行到
yield屬性的時候,都會回傳一個物件 - 這時候生成器處于一個非阻塞的掛起狀態
- 呼叫迭代器的next方法的時候,生成器又從掛起狀態改為執行狀態,繼續上一次的執行位置執行
- 直到遇到下一次
yield依次回圈 - 直到代碼沒有
yield了,就會回傳一個結果物件done為true,value為undefined
面試官:說說 Promise 的原理?你是如何理解 Promise 的??????
- 做到會寫簡易版的promise和all函式就可以
答:
class MyPromise2 {
constructor(executor) {
// 規定狀態
this.state = "pending"
// 保存 `resolve(res)` 的res值
this.value = undefined
// 保存 `reject(err)` 的err值
this.reason = undefined
// 成功存放的陣列
this.successCB = []
// 失敗存放的陣列
this.failCB = []
let resolve = (value) => {
if (this.state === "pending") {
this.state = "fulfilled"
this.value = value
this.successCB.forEach(f => f())
}
}
let reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected"
this.value = value
this.failCB.forEach(f => f())
}
}
try {
// 執行
executor(resolve, reject)
} catch (error) {
// 若出錯,直接呼叫reject
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.value)
}
if (this.state === "pending") {
this.successCB.push(() => { onFulfilled(this.value) })
this.failCB.push(() => { onRejected(this.reason) })
}
}
}
Promise.all = function (promises) {
let list = []
let count = 0
function handle(i, data) {
list[i] = data
count++
if (count == promises.length) {
resolve(list)
}
}
return Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
handle(i, res)
}, err => reject(err))
}
})
}
面試官:以下代碼的執行順序是什么?????
答:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1()
console.log('script start')
//執行到await時,如果回傳的不是一個promise物件,await會阻塞下面代碼(當前async代碼塊的代碼),會先執行async外的同步代碼(在這之前先看看await中函式的同步代碼,先把同步代碼執行完),等待同步代碼執行完之后,再回到async內部繼續執行
//執行到await時,如果回傳的是一個promise物件,await會阻塞下面代碼(當前async代碼塊的代碼),會先執行async外的同步代碼(在這之前先看看await中函式的同步代碼,先把同步代碼執行完),等待同步代碼執行完之后,再回到async內部等promise狀態達到fulfill的時候再繼續執行下面的代碼
//所以結果為
//async1 start
//async2
//script start
//async1 end
面試官:宏任務和微任務都有哪些?????
答:
- 宏任務:
script、setTimeOut、setInterval、setImmediate - 微任務:
promise.then,process.nextTick、Object.observe、MutationObserver - 注意:Promise是同步任務
面試官:宏任務和微任務都是怎樣執行的?????
答:
- 執行宏任務script,
- 進入script后,所有的同步任務主執行緒執行
- 所有宏任務放入宏任務執行佇列
- 所有微任務放入微任務執行佇列
- 先清空微任務佇列,
- 再取一個宏任務,執行,再清空微任務佇列
- 依次回圈
例題1
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});
console.log('4');
new Promise(function(resolve){
console.log('5');
resolve();
}).then(function(){
console.log('6')
});
setTimeout(function(){
console.log('7')
});
function bar(){
console.log('8')
foo()
}
function foo(){
console.log('9')
}
console.log('10')
bar()
決議
- 首先瀏覽器執行Js代碼由上至下順序,遇到setTimeout,把setTimeout分發到宏任務Event Queue中
- new Promise屬于主執行緒任務直接執行列印2
- Promis下的then方法屬于微任務,把then分到微任務 Event Queue中
- console.log(‘4’)屬于主執行緒任務,直接執行列印4
- 又遇到new Promise也是直接執行列印5,Promise 下到then分發到微任務Event Queue中
- 又遇到setTimouse也是直接分發到宏任務Event Queue中,等待執行
- console.log(‘10’)屬于主執行緒任務直接執行
- 遇到bar()函式呼叫,執行建構式內到代碼,列印8,在bar函式中呼叫foo函式,執行foo函式到中代碼,列印9
- 主執行緒中任務執行完后,就要執行分發到微任務Event Queue中代碼,實行先進先出,所以依次列印3,6
- 微任務Event Queue中代碼執行完,就執行宏任務Event Queue中代碼,也是先進先出,依次列印1,7,
- 最終結果:2,4,5,10,8,9,3,6,1,7
例題2
setTimeout(() => {
console.log('1');
new Promise(function (resolve, reject) {
console.log('2');
setTimeout(() => {
console.log('3');
}, 0);
resolve();
}).then(function () {
console.log('4')
})
}, 0);
console.log('5'); //5 7 10 8 1 2 4 6 3
setTimeout(() => {
console.log('6');
}, 0);
new Promise(function (resolve, reject) {
console.log('7');
// reject();
resolve();
}).then(function () {
console.log('8')
}).catch(function () {
console.log('9')
})
console.log('10');
運行結果: 5 7 10 8 1 2 4 6 3
變數提升
面試官:變數和函式怎么進行提升的?優先級是怎么樣的?????
答:
- 對所有函式宣告進行提升(除了函式運算式和箭頭函式),參考型別的賦值
- 開辟堆空間
- 存盤內容
- 將地址賦給變數
- 對變數進行提升,只宣告,不賦值,值為
undefined
面試官:var let const 有什么區別?????
答:
- var
- var宣告的變數可進行變數提升,let和const不會
- var可以重復宣告
- var在非函式作用域中定義是掛在到window上的
- let
- let宣告的變數只在區域起作用
- let防止變數污染
- 不可在宣告
- const
- 具有let的所有特征
- 不可被改變
- 如果使用const宣告的是物件的話,是可以修改物件里面的值的
面試官:箭頭函式和普通函式的區別?箭頭函式可以當做建構式 new 嗎??????
- 箭頭函式是普通函式的簡寫,但是它不具備很多普通函式的特性
- 第一點,this指向問題,箭頭函式的this指向它定義時所在的物件,而不是呼叫時所在的物件
- 不會進行函式提升
- 沒有arguments物件,不能使用arguments,如果要獲取引數的話可以使用
rest運算子 - 沒有
yield屬性,不能作為生成器Generator使用 - 不能new
- 沒有自己的this,不能呼叫call和apply
- 沒有prototype,new關鍵字內部需要把新物件的
_proto_指向函式的prototype
面試官:說說你對代理的理解???
- 代理有幾種定義方式
- 字面量定義,物件里面的 get和set
- 類定義, class 中的
get和set - Proxy物件,里面傳兩個物件,第一個物件是目標物件target,第二個物件是專門放get和set的
handler物件,Proxy和上面兩個的區別在于Proxy專門對物件的屬性進行get和set
- 代理的實際應用有
- Vue的雙向系結 vue2用的是
Object.defineProperty,vue3用的是proxy - 校驗值
- 計算屬性值(get的時候加以修飾)
- Vue的雙向系結 vue2用的是
面試官:為什么要使用模塊化?都有哪幾種方式可以實作模塊化,各有什么特點????
- 為什么要使用模塊化
- 防止命名沖突
- 更好的分離,按需加載
- 更好的復用性
- 更高的維護性
面試官:exports和module.exports有什么區別????
- 匯出方式不一樣
exports.xxx='xxx'module.export = {}
exports是module.exports的參考,兩個指向的是用一個地址,而require能看到的只有module.exports
面試官:JS模塊包裝格式有哪些????
-
commonjs
- 同步運行,不適合前端
-
AMD- 異步運行
- 異步模塊定義,主要采用異步的方式加載模塊,模塊的加載不影響后面代碼的執行,所有依賴這個模塊的陳述句都寫在一個回呼函式中,模塊加載完畢,再執行回呼函式
-
CMD- 異步運行
- seajs 規范
面試官:ES6和commonjs的區別???
commonjs模塊輸出的是值的拷貝,而ES6輸出的值是值的參考commonjs是在運行時加載,是一個物件,ES6是在編譯時加載,是一個代碼塊commonjs的this指向當前模塊,ES6的this指向undefined
哎呀呀呀,不簡單,你竟然都看到這里了,看看進度條,已經達到一半了
不過——在這之前,先問問自己,前面的都掌握了嗎??
如果你還沒有,趕緊滾回去看!
如果你掌握前面的了,那么準備迎接下一個boss——計算機網路

跨域
面試官:跨域的方式都有哪些?他們的特點是什么 ?????
-
JSONP?????
-
JSONP通過同源策略涉及不到的"漏洞",也就是像img中的src,link標簽的href,script的src都沒有被同源策略限制到 -
JSONP只能get請求 -
原始碼:
function addScriptTag(src) { var script = document.createElement("script") script.setAttribute('type','text/javascript') script.src = src document.appendChild(script) } // 回呼函式 function endFn(res) { console.log(res.message); } // 前后端商量好,后端如果傳資料的話,回傳`endFn({message:'hello'})`
-
-
document.domain?
-
只能跨一級域名相同的域(www.qq.om和www.id.qq.com , 二者都有qq.com)
-
使用方法
-
>表示輸入,<表示輸出 ,以下是在www.id.qq.com網站下執行的操作 -
> var w = window.open("https://www.qq.com") < undefined > w.document ? VM3061:1 Uncaught DOMException: Blocked a frame with origin "https://id.qq.com" from accessing a cross-origin frame. at <anonymous>:1:3 > document.domain < "id.qq.com" > document.domain = 'qq.com' < "qq.com" > w.document < #document
-
-
-
location.hash+iframe??-
因為hash傳值只能單向傳輸,所有可以通過一個中間網頁,a若想與b進行通信,可以通過一個與a同源的c作為中間網頁,a傳給b,b傳給c,c再傳回a
-
具體做法:在a中放一個回呼函式,方便c回呼,放一個
iframe標簽,隨后傳值 -
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); // 向b.html傳hash值 setTimeout(function() { iframe.src = iframe.src + '#user=admin'; }, 1000); // 開放給同域c.html的回呼方法 function onCallback(res) { alert('data from c.html ---> ' + res); } </script> -
在b中監聽哈希值改變,一旦改變,把a要接收的值傳給c
-
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); // 監聽a.html傳來的hash值,再傳給c.html window.onhashchange = function () { iframe.src = iframe.src + location.hash; }; </script> -
在c中監聽哈希值改變,一旦改變,呼叫a中的回呼函式
-
<script> // 監聽b.html傳來的hash值 window.onhashchange = function () { // 再通過操作同域a.html的js回呼,將結果傳回 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); }; </script>
-
-
-
window.name+iframe??- 利Access用
window.name不會改變(而且很大)來獲取資料, - a要獲取b的資料,b中把資料轉為json格式放到
window.name中
- 利Access用
-
postMessage?- a視窗向b視窗發送資料,先把data轉為json格式,在發送,提前設定好messge監聽
- b視窗進行
message監聽,監聽到了以同樣的方式回傳資料, - a視窗監聽到message,在進行一系列操作
-
CORS?????- 通過自定義請求頭來讓服務器和瀏覽器進行溝通
- 有簡單請求和非簡單請求
- 滿足以下條件,就是簡單請求
- 請求方法是HEAD、POST、GET
- 請求頭只有
Accept、AcceptLanguage、ContentType、ContentLanguage、Last-Event-Id
- 簡單請求,瀏覽器自動添加一個Origin欄位
- 同時后端需要設定的請求頭
- Access-Control-Allow-Origin --必須
- Access-Control-Expose-Headers
XMLHttpRequest只能拿到六個欄位,要想拿到其他的需要在這里指定
- Access-Control-Allow-Credentials --是否可傳cookie
- 要是想傳cookie,前端需要設定
xhr.withCredentials = true,后端設定Access-Control-Allow-Credentials
- 同時后端需要設定的請求頭
- 非簡單請求,瀏覽器判斷是否為簡單請求,如果是非簡單請求,則 瀏覽器先發送一個header頭為option的請求進行預檢
- 預檢請求格式(請求行 的請求方法為OPTIONS(專門用來詢問的))
- Origin
- Access-Control-Request-Method
- Access-Control-Request-Header
- 瀏覽器檢查了Origin、Access-Control-Allow-Method和Access-Control-Request-Header之后確認允許就可以做出回應了
- 通過預檢后,瀏覽器接下來的每次請求就類似于簡單請求了
- 預檢請求格式(請求行 的請求方法為OPTIONS(專門用來詢問的))
-
nginx代理跨域????
- nginx模擬一個虛擬服務器,因為服務器與服務器之間是不存在跨域的,
- 發送資料時 ,客戶端->nginx->服務端
- 回傳資料時,服務端->nginx->客戶端
網路原理
面試官:講一講三次握手四次揮手,為什么是三次握手四而不是兩次握手??????
-
客戶端和服務端之間通過三次握手建立連接,四次揮手釋放連接
-
三次握手,客戶端先向服務端發起一個SYN包,進入SYN_SENT狀態,服務端收到SYN后,給客戶端回傳一個ACK+SYN包,表示已收到SYN,并進入SYN_RECEIVE狀態,最后客戶端再向服務端發送一個ACK包表示確認,雙方進入establish狀態,
- 之所以是三次握手而不是兩次,是因為如果只有兩次,在服務端收到SYN后,向客戶端回傳一個ACK確認就進入establish狀態,萬一這個請求中間遇到網路情況而沒有傳給客戶端,客戶端一直是等待狀態,后面服務端發送的資訊客戶端也接受不到了,
-
四次揮手,首先客戶端向服務端發送一個FIN包,進入FIN_WAIT1狀態,服務端收到后,向客戶端發送ACK確認包,進入CLOSE_WAIT狀態,然后客戶端收到ACK包后進入FIN_WAIT2狀態,然后服務端再把自己剩余沒傳完的資料發送給客戶端,發送完畢后在發送一個FIN+ACK包,進入LAST_ACK(最后確認)狀態,客戶端收到FIN+ACK包后,再向服務端發送ACK包,在等待兩個周期后在關閉連接
- 之所以等待兩個周期是因為最后服務端發送的ACK包可能會丟失,如果不等待2個周期的話,服務端在沒收收到ACK包之前,會不停的重復發送FIN包而不關閉,所以得等待兩個周期
面試官:HTTP的結構????
- 請求行 請求頭 空行 請求體
- 請求行包括 http版本號,url,請求方式
- 回應行包括版本號,狀態碼,原因
HTTP頭都有哪些欄位????
- 請求頭
- cache-control 是否使用快取
- Connection:keep-alive 與服務器的連接狀態
- Host 主機域
- 回傳頭
- cache-control
- etag 唯一標識,快取用的
- last-modified最后修改時間
面試官:說說你知道的狀態碼?????
- 2開頭的表示成功
- 一般見到的就是200
- 3開頭的表示重定向
- 301永久重定向
- 302臨時重定向
- 304表示可以在快取中取資料(協商快取)
- 4開頭表示客戶端錯誤
- 403跨域
- 404請求資源不存在
- 5開頭表示服務端錯誤
- 500
網路OSI七層模型都有哪些?TCP是哪一層的????
- 七層模型
- 應用層
- 表示層
- 會話層
- 傳輸層
- 網路層
- 資料鏈路層
- 物理層
- TCP屬于傳輸層
面試官:http1.0和http1.1,還有http2有什么區別?????
- http0.9只能進行get請求
- http1.0添加了POST,HEAD,OPTION,PUT,DELETE等
- http1.1增加了長連接keep-alive,增加了host域,而且節約帶寬
- http2 多路復用,頭部壓縮,服務器推送
面試官:https和http有什么區別,https的實作原理??????
- http無狀態無連接,而且是明文傳輸,不安全
- https傳輸內容加密,身份驗證,保證資料完整性
- https實作原理?????
- 首先客戶端向服務端發起一個隨機值,以及一個加密演算法
- 服務端收到后回傳一個協商好的加密演算法,以及另一個隨機值
- 服務端在發送一個公鑰CA
- 客戶端收到以后先驗證CA是否有效,如果無效則報錯彈窗,有過有效則進行下一步操作
- 客戶端使用之前的兩個隨機值和一個預主密鑰組成一個會話密鑰,在通過服務端傳來的公鑰加密把會話密鑰發送給服務端
- 服務端收到后使用私鑰解密,得到兩個隨機值和預主密鑰,然后組裝成會話密鑰
- 客戶端在向服務端發起一條資訊,這條資訊使用會話秘鑰加密,用來驗證服務端時候能收到加密的資訊
- 服務端收到資訊后回傳一個會話秘鑰加密的資訊
- 都收到以后SSL層連接建立成功
面試官:localStorage、SessionStorage、cookie、session 之間有什么區別?????
- localStorage
- 生命周期:關閉瀏覽器后資料依然保留,除非手動清除,否則一直在
- 作用域:相同瀏覽器的不同標簽在同源情況下可以共享localStorage
- sessionStorage
- 生命周期:關閉瀏覽器或者標簽后即失效
- 作用域:只在當前標簽可用,當前標簽的iframe中且同源可以共享
- cookie
- 是保存在客戶端的,一般由后端設定值,可以設定過期時間
- 儲存大小只有4K
- 一般用來保存用戶的資訊的
- 在http下cookie是明文傳輸的,較不安全
- cookie屬性有
- http-only:不能被客戶端更改訪問,防止XSS攻擊(保證cookie安全性的操作)
- Secure:只允許在https下傳輸
- Max-age: cookie生成后失效的秒數
- expire: cookie的最長有效時間,若不設定則cookie生命期與會話期相同
- session
- session是保存在服務端的
- session的運行依賴sessionId,而sessionId又保存在cookie中,所以如果禁用的cookie,session也是不能用的,不過硬要用也可以,可以把sessionId保存在URL中
- session一般用來跟蹤用戶的狀態
- session 的安全性更高,保存在服務端,不過一般為使服務端性能更加,會考慮部分資訊保存在cookie中
localstorage存滿了怎么辦????
- 劃分域名,各域名下的存盤空間由各業務組統一規劃使用
- 跨頁面傳資料:考慮單頁應用、采用url傳輸資料
- 最后兜底方案:情調別人的存盤
怎么使用cookie保存用戶資訊???
- document.cookie(“名字 = 資料;expire=時間”)
怎么洗掉cookie???
- 目前沒有提供洗掉的操作,但是可以把它的Max-age設定為0,也就是立馬失效,也就是洗掉了
面試官:Get和Post的區別?????
https://www.zhihu.com/question/28586791
- 冪等/不冪等(可快取/不可快取)
- get請求是冪等的,所以get請求的資料是可以快取的
- 而post請求是不冪等的,查詢查詢對資料是有副作用的,是不可快取的
- 傳參
- get傳參,引數是在url中的
- 準確的說get傳參也可以放到body中,只不過不推薦使用
- post傳參,引數是在請求體中
- 準確的說post傳參也可以放到url中,只不過不推薦使用
- get傳參,引數是在url中的
- 安全性
- get較不安全
- post較為安全
- 準確的說兩者都不安全,都是明文傳輸的,在路過公網的時候都會被訪問到,不管是url還是header還是body,都會被訪問到,要想做到安全,就需要使用https
- 引數長度
- get引數長度有限,是較小的
- 準確來說,get在url傳參的時候是很小的
- post傳參長度不受限制
- get引數長度有限,是較小的
- 發送資料
- post傳參發送兩個請求包,一個是請求頭,一個是請求體,請求頭發送后服務器進行驗證,要是驗證通過的話就會給客戶端發送一個100-continue的狀態碼,然后就會發送請求體
- 字符編碼
- get在url上傳輸的時候只允許ASCII編碼
面試官:講講http快取?????
https://www.jianshu.com/p/9c95db596df5
- 快取分為強快取和協商快取
- 強快取
- 在瀏覽器加載資源時,先看看
cache-control里的max-age,判斷資料有沒有過期,如果沒有直接使用該快取 ,有些用戶可能會在沒有過期的時候就點了重繪按鈕,這個時候瀏覽器就回去請求服務端,要想避免這樣做,可以在cache-control里面加一個immutable. - public
- 允許客戶端和虛擬服務器快取該資源,cache-control中的一個屬性
- private
- 只允許客戶端快取該資源
- no-storage
- 不允許強快取,可以協商快取
- no-cache
- 不允許快取
- 在瀏覽器加載資源時,先看看
- 協商快取
- 瀏覽器加載資源時,沒有命中強快取,這時候就去請求服務器,去請求服務器的時候,會帶著兩個引數,一個是
If-None-Match,也就是回應頭中的etag屬性,每個檔案對應一個etag;另一個引數是If-Modified-Since,也就是回應頭中的Last-Modified屬性,帶著這兩個引數去檢驗快取是否真的過期,如果沒有過期,則服務器會給瀏覽器回傳一個304狀態碼,表示快取沒有過期,可以使用舊快取, etag的作用- 有時候編輯了檔案,但是沒有修改,但是
last-modified屬性的時間就會改變,導致服務器會重新發送資源,但是etag的出現就完美的避免了這個問題,他是檔案的唯一標識
- 有時候編輯了檔案,但是沒有修改,但是
- 瀏覽器加載資源時,沒有命中強快取,這時候就去請求服務器,去請求服務器的時候,會帶著兩個引數,一個是
快取位置:
- 記憶體快取Memory-Cache
- 離線快取Service-Worker
- 磁盤快取Disk-Cache
- 推送快取Push-Cache
面試官:tcp 和udp有什么區別?????
-
連接方面
- tcp面向連接,udp不需要連接
- tcp需要三次握手四次揮手請求連接
- tcp面向連接,udp不需要連接
-
可靠性
- tcp是可靠傳輸;一旦傳輸程序中丟包的話會進行重傳
- udp是不可靠傳輸,但會最大努力交付
-
作業效率
- UDP實時性高,比TCP作業效率高
- 因為不需要建立連接,更不需要復雜的握手揮手以及復雜的演算法,也沒有重傳機制
- UDP實時性高,比TCP作業效率高
-
是否支持多對多
- TCP是點對點的
- UDP支持一對一,一對多,多對多
-
首部大小
- tcp首部占20位元組
- udp首部占8位元組
面試官:從瀏覽器輸入url后都經歷了什么????????????????????具重要!
- 先進行DNS域名決議,先查看本地hosts檔案,查看有沒有當前域名對應的ip地址,若有直接發起請求,沒有的話會在本地域名服務器去查找,該查找屬于遞回查找,如果本地域名服務器沒查找到,會從根域名服務器查找,該程序屬于迭代查找,根域名會告訴你從哪個與服務器查找,最后查找到對應的ip地址后把對應規則保存到本地的hosts檔案中,
- 如果想加速以上及之后的http請求程序的話可以使用快取服務器CDN,CDN程序如下:
- 用戶輸入url地址后,本地DNS會決議url地址,不過會把最終決議權交給CNAME指向的CDN的DNS服務器
- CDN的DNS服務器會回傳給瀏覽器一個全域負載均衡IP
- 用戶會根據全域負載均衡IP去請求全域負載均衡服務器
- 全域負載均衡服務器會根據用戶的IP地址,url地址,會告訴用戶一個區域負載均衡設備,讓用戶去請求它,
- 區域負載均衡服務器會為用戶選擇一個離用戶較近的最優的快取服務器,并把ip地址給到用戶
- 用戶想快取服務器發送請求,如果請求不到想要的資源的話,會一層層向上一級查找,知道查找到為止,
- 進行http請求,三次握手四次揮手建立斷開連接
- 服務器處理,可能回傳304也可能回傳200
- 回傳304說明客戶端快取可用,直接使用客戶端快取即可,該程序屬于協商快取
- 回傳200的話會同時回傳對應的資料
- 客戶端自上而下執行代碼
- 其中遇到CSS加載的時候,CSS不會阻塞DOM樹的決議,但是會阻塞DOM樹的渲染,并且CSS會阻塞下面的JS的執行
- 然后是JS加載,JS加載會影響DOM的決議,之所以會影響,是因為JS可能會洗掉添加節點,如果先決議后加載的話,DOM樹還得重新決議,性能比較差,如果不想阻塞DOM樹的決議的話,可以給script添加一個
defer或者async的標簽,- defer:不會阻塞DOM決議,等DOM決議完之后在運行,在
DOMContentloaed之前 - async: 不會阻塞DOM決議,等該資源下載完成之后立刻運行
- defer:不會阻塞DOM決議,等DOM決議完之后在運行,在
- 進行DOM渲染和Render樹渲染
- 獲取html并決議為Dom樹
- 決議css并形成一個cssom(css樹)
- 將cssom和dom合并成渲染樹(render樹)
- 進行布局(layout)
- 進行繪制(painting)
- 回流重繪
- 回流必將引起重繪,重繪不一定引起回流
滑動視窗和擁塞視窗有什么區別???
決議TCP之滑動視窗(影片演示)
以影片的形式解釋滑動視窗,
- 滑動視窗
- 發送視窗永遠小于或等于接收視窗,發送視窗的大小取決于接收視窗的大小
- 控制流量來保證TCP的可靠傳輸(不控制流量的話可能會溢位)
- 發送方的資料分為
- 1已發送,接收到ACK的
- 2已發送,未接收到ACK的
- 3未發送,但允許發送的
- 4未發送,但不允許發送的
- 2和3表示發送視窗
- 接收方
- 1.已接收
- 2.未接受但準備接受
- 3.未接受不準備接受
- 擁塞視窗
- 防止過多的資料注入到網路中,這樣可以使網路中的路由器或鏈路不致過載,
- 是一個全域性的程序
- 方法
- 慢開始、擁塞避免、快重傳、快恢復
什么是CDN?????
關于 cdn、回源等問題一網打盡
1.首先訪問本地的 DNS ,如果沒有命中,繼續遞回或者迭代查找,直到命中拿到對應的 IP 地址,
2.拿到對應的 IP 地址之后服務器端發送請求到目的地址,注意這里回傳的不直接是 cdn 服務器的 IP 地址,而是全域負載均衡系統的 IP 地址
4.全域負載均衡系統會根據客戶端的 IP地址和請求的 url 和相應的區域負載均衡系統通信
5.區域負載均衡系統拿著這兩個東西獲取距離客戶端最近且有相應資源的cdn 快取服務器的地址,回傳給全域負載均衡系統
6.全域負載均衡系統回傳確定的 cdn 快取服務器的地址給客戶端,
7.客戶端請求快取服務器上的檔案
什么是xss?什么是csrf??????
- xss腳本注入
- 不需要你做任何的登錄認證,它會通過合法的操作(比如在url中輸入、在評論框中輸入),向你的頁面注入腳本(可能是js、hmtl代碼塊等),
- 防御
- 編碼:對用戶輸入的資料進行HTML Entity 編碼,把字符轉換成 轉義字符,Encode的作用是將$var等一些字符進行轉化,使得瀏覽器在最終輸出結果上是一樣的,
- 過濾:移除用戶輸入的和事件相關的屬性,
- csrf跨域請求偽造
- 在未退出A網站的前提下訪問B,B使用A的cookie去訪問服務器
- 防御:token,每次用戶提交表單時需要帶上token(偽造者訪問不到),如果token不合法,則服務器拒絕請求
OWASP top10 (10項最嚴重的Web應用程式安全風險串列)都有哪些????
-
SQL注入
- 在輸入框里輸入sql命令
-
失效的身份驗證
- 拿到別人的cookie來向服務端發起請求,就可以做到登陸的目的
-
敏感資料泄露
- 明文傳輸狀態下可能被抓包攔截,這時候就造成資料泄露
- 想做到抓包,比如在網吧,共享一個貓上網,這時候抓包就可行,方法網上一搜一大把
- 不過此風險大部分網站都能得到很好的解決,https或者md5加密都可以
- 明文傳輸狀態下可能被抓包攔截,這時候就造成資料泄露
-
XML 外部物體
-
失效的訪問控制
-
安全配置錯誤
-
XSS
-
不安全的反序列化
-
使用含有已知漏洞的組件
-
不足的日志記錄和監控
怎么樣?計算機網路是不是沒有想象中的那么難,如果你沒看過癮的話,推薦你這篇文章:【長文】前端需要了解的計算機網路知識
是不是得感激我一下【手動滑稽】

面試官:什么是回流 什么是重繪??????
- 回流
- render樹中一部分或全部元素需要改變尺寸、布局、或著需要隱藏而需要重新構建,這個程序叫做回流
- 回流必將引起重繪
- 重繪
- render樹中一部分元素改變,而不影響布局的,只影響外觀的,比如顏色,該程序叫做重繪
- 頁面至少經歷一次回流和重繪(第一次加載的時候)
雜項
事件冒泡和事件捕捉有什么區別?????
- 事件冒泡
- 在addEventListener中的第三屬性設定為false(默認)
- 從下至上(兒子至祖宗)執行
- 事件捕捉
- 在addEventListener中的第三屬性設定為true
- 從上至下(祖宗到兒子)執行
什么是防抖?什么是節流?手寫一個?????
- 防抖
- n秒后在執行該事件,若在n秒內被重復觸發,則重新計時
- 節流
- n秒內只運行一次,若在n秒內重復觸發,只有一次生效
// ---------------------------------------------------------防抖函式
function debounce(func, delay) {
let timeout
return function () {
let arg = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func(arg)
}, delay);
}
}
// ---------------------------------------------------------立即執行防抖函式
function debounce2(fn, delay) {
let timer
return function () {
let args = arguments
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) { fn(args) }
}
}
// ---------------------------------------------------------立即執行防抖函式+普通防抖
function debounce3(fn, delay, immediate) {
let timer
return function () {
let args = arguments
let _this = this
if (timer) clearTimeout(timer)
if (immediate) {
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) { fn.apply(_this, args) }
} else {
timeout = setTimeout(() => {
func.apply(_this, arguments)
}, delay);
}
}
}
// ---------------------------------------------------------節流 ,時間戳版
function throttle(fn, wait) {
let previous = 0
return function () {
let now = Date.now()
let _this = this
let args = arguments
if (now - previous > wait) {
fn.apply(_this, arguments)
previous = now
}
}
}
// ---------------------------------------------------------節流 ,定時器版
function throttle2(fn, wait) {
let timer
return function () {
let _this = this
let args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(_this, arguments)
}, wait);
}
}
}
函式柯里化原理?????
function add() {
var args = Array.prototype.slice.call(arguments)
var adder = function () {
args.push(...arguments)
return adder
}
adder.toString = function () {
return args.reduce((prev, curr) => {
return prev + curr
}, 0)
}
return adder
}
let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)
什么是requestAnimationFrame?????
-
requestAnimationFrame請求資料幀可以用做影片執行
-
可以自己決定什么時機呼叫該回呼函式
-
能保證每次頻幕重繪的時候只被執行一次
-
頁面被隱藏或者最小化的時候暫停執行,回傳視窗繼續執行,有效節省CPU
-
var s = 0 function f() { s++ console.log(s); if (s < 999) { window.requestAnimationFrame(f) } } window.requestAnimationFrame(f)
js常見的設計模式?????
-
單例模式、工廠模式、建構式模式、發布訂閱者模式、迭代器模式、代理模式
-
單例模式
-
不管創建多少個物件都只有一個實體
-
var Single = (function () { var instance = null function Single(name) { this.name = name } return function (name) { if (!instance) { instance = new Single() } return instance } })() var oA = new Single('hi') var oB = new Single('hello') console.log(oA); console.log(oB); console.log(oB === oA);
-
-
工廠模式
-
代替new創建一個物件,且這個物件想工廠制作一樣,批量制作屬性相同的實體物件(指向不同)
-
function Animal(o) { var instance = new Object() instance.name = o.name instance.age = o.age instance.getAnimal = function () { return "name:" + instance.name + " age:" + instance.age } return instance } var cat = Animal({name:"cat", age:3}) console.log(cat);
-
-
建構式模式
-
發布訂閱者模式
-
class Watcher { // name模擬使用屬性的地方 constructor(name, cb) { this.name = name this.cb = cb } update() {//更新 console.log(this.name + "更新了"); this.cb() //做出更新回呼 } } class Dep {//依賴收集器 constructor() { this.subs = [] } addSubs(watcher) { this.subs.push(watcher) } notify() {//通知每一個觀察者做出更新 this.subs.forEach(w => { w.update() }); } } // 假如現在用到age的有三個地方 var w1 = new Watcher("我{{age}}了", () => { console.log("更新age"); }) var w2 = new Watcher("v-model:age", () => { console.log("更新age"); }) var w3 = new Watcher("I am {{age}} years old", () => { console.log("更新age"); }) var dep = new Dep() dep.addSubs(w1) dep.addSubs(w2) dep.addSubs(w3) // 在Object.defineProperty 中的 set中運行 dep.notify()
-
-
代理模式
-
迭代器模式
JS性能優化的方式?????
- 垃圾回收
- 閉包中的物件清楚
- 防抖節流
- 分批加載(setInterval,加載10000個節點)
- 事件委托
- 少用with
- requestAnimationFrame的使用
- script標簽中的defer和async
- CDN
Vue
Vue雙向系結
資料劫持: vue.js是采用資料劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在資料變動時發布訊息給訂閱者,觸發相應的監聽回呼
闡述一下你所理解的MVVM回應式原理?????
vue是采用資料劫持配合發布者-訂閱者的模式的方式,通過Object.defineProperty()來劫持各個屬性的getter和setter,在資料變動時,發布訊息給依賴收集器(dep中的subs),去通知(notify)觀察者,做出對應的回呼函式,去更新視圖
MVVM作為系結的入口,整合Observer,Compile和Watcher三者,通過Observer來監聽model資料變化,通過Compile來決議編譯模板指令,最終利用Watcher搭起Observer,Compile之間的通信橋路,達到資料變化=>視圖更新;視圖互動變化=>資料model變更的雙向系結效果,
雜亂筆記
-
data中每一個資料都系結一個Dep,這個Dep中都存有所有用到該資料的觀察者
-
當資料改變時,發布訊息給dep(依賴收集器),去通知每一個觀察者,做出對應的回呼函式
-
const dep = new Dep() // 劫持并監聽所有屬性 Object.defineProperty(obj, key, { enumerable: true, configurable: false, get() { // 訂閱資料變化時,在Dep中添加觀察者 Dep.target && dep.addSub(Dep.target) return value }, set: (newVal) => { if (newVal !== value) { this.observe(newVal) value = newVal } // 告訴Dep通知變化 dep.notify() }, })
面試官:說說vue的生命周期?????
beforeCreate- 創建之前,此時還沒有data和Method
Created- 創建完成,此時data和Method可以使用了
- 在Created之后beforeMount之前如果沒有el選項的話那么此時生命周期結束,停止編譯,如果有則繼續
beforeMount- 在渲染之前
mounted- 頁面已經渲染完成,并且
vm實體中已經添加完$el了,已經替換掉那些DOM元素了(雙括號中的變數),這個時候可以操作DOM了(但是是獲取不了元素的高度等屬性的,如果想要獲取,需要使用nextTick())
- 頁面已經渲染完成,并且
beforeUpdatedata改變后,對應的組件重新渲染之前
updateddata改變后,對應的組件重新渲染完成
beforeDestory- 在實體銷毀之前,此時實體仍然可以使用
destoryed- 實體銷毀后
面試官:vue中父子組件的生命周期?????
- 父子組件的生命周期是一個嵌套的程序
- 渲染的程序
- 父
beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 父
- 子組件更新程序
- 父
beforeUpdate->子beforeUpdate->子updated->父updated
- 父
- 父組件更新程序
- 父
beforeUpdate->父updated
- 父
- 銷毀程序
- 父
beforeDestroy->子beforeDestroy->子destroyed->父destroyed
- 父
Vue中的nextTick?????
-
nextTick
- 解釋
nextTick:在下次 DOM 更新回圈結束之后執行延遲回呼,在修改資料之后立即使用這個方法,獲取更新后的 DOM,
- 應用
- 想要在Vue生命周期函式中的
created()操作DOM可以使用Vue.nextTick()回呼函式 - 在資料改變后要執行的操作,而這個操作需要等資料改變后而改變DOM結構的時候才進行操作,需要用到
nextTick
- 想要在Vue生命周期函式中的
- 解釋
面試官:computed和watch的區別?????
- computed
- 計算屬性,依賴其他屬性,當其他屬性改變的時候下一次獲取computed值時也會改變,
computed的值會有快取
- 計算屬性,依賴其他屬性,當其他屬性改變的時候下一次獲取computed值時也會改變,
watch- 類似于資料改變后的回呼
- 如果想深度監聽的話,后面加一個
deep:true - 如果想監聽完立馬運行的話,后面加一個
immediate:true
面試官:Vue優化方式?????
-
v-if 和v-show
-
使用
Object.freeze()方式凍結data中的屬性,從而阻止資料劫持 -
組件銷毀的時候會斷開所有與實體聯系,但是除了
addEventListener,所以當一個組件銷毀的時候需要手動去removeEventListener -
圖片懶加載
-
路由懶加載
-
為減少重新渲染和創建dom節點的時間,采用虛擬dom
面試官:Vue-router的模式?????
- hash模式
- 利用onhashchange事件實作前端路由,利用url中的hash來模擬一個hash,以保證url改變時,頁面不會重新加載,
- history模式
- 利用pushstate和replacestate來將url替換但不重繪,但是有一個致命點就是,一旦重繪的話,就會可能404,因為沒有當前的真正路徑,要想解決這一問題需要后端配合,將不存在的路徑重定向到入口檔案,
面試官:MVC與MVVM有什么區別?????
哎呀呀,這個要參考的就多了,
mvc和mvvm的區別
基于Vue實作一個簡易MVVM
不好意思!耽誤你的十分鐘,讓MVVM原理還給你
- MVC
- Model(模型)是應用程式中用于處理應用程式資料邏輯的部分,通常模型物件負責在資料庫中存取資料,
- View(視圖)是應用程式中處理資料顯示的部分,通常視圖是依據模型資料創建的,
- Controller(控制器)是應用程式中處理用戶互動的部分,
- 通常控制器負責從視圖讀取資料,控制用戶輸入,并向模型發送資料,
diff演算法?????
- diff演算法是指對新舊虛擬節點進行對比,并回傳一個patch物件,用來存盤兩個節點不同的地方,最后利用patch記錄的訊息區域更新DOM
虛擬DOM的優缺點?????
- 缺點
- 首次渲染大量DOM時,由于多了一層虛擬DOM的計算,會比innerHTML插入慢
- 優點
- 減少了dom操作,減少了回流與重繪
- 保證性能的下限,雖說性能不是最佳,但是它具備區域更新的能力,所以大部分時候還是比正常的DOM性能高很多的
Vue的Key的作用 ????
- key
- key主要用在虛擬Dom演算法中,每個虛擬節點VNode有一個唯一標識Key,通過對比新舊節點的key來判斷節點是否改變,用key就可以大大提高渲染效率,這個key類似于快取中的etag,
Vue組件之間的通信方式?????
-
子組件設定props + 父組件設定
v-bind:/:- 父傳子
-
子組件的$emit + 父組件設定
v-on/@- 子傳父
-
任意組件通信,新建一個空的全域Vue物件,利用 e m i t 發 送 , emit發送, emit發送,on接收
-
傳說中的$bus
-
任意組件
-
Vue.prototype.Event=new Vue(); Event.$emit(事件名,資料); Event.$on(事件名,data => {});
-
-
Vuex
- 里面的屬性有:
- state
- 存盤資料的
- 獲取資料最好推薦使用getters
- 硬要使用的話可以用MapState, 先參考,放在compute中
...mapState(['方法名','方法名'])
- getters
- 獲取資料的
- this.$store.getters.xxx
- 也可使用mapGetters 先參考,放在compute中,
...mapGetters(['方法名','方法名'])
- mutations
- 同步操作資料的
- this.$store.commit(“方法名”,資料)
- 也可使用mapMutations ,使用方法和以上一樣
- actions
- 異步操作資料的
- this.$store.dispatch(“方法名”,資料)
- 也可使用mapActions ,使用方法和以上一樣
- modules
- 板塊,里面可以放多個vuex
- state
- 里面的屬性有:
-
父組件通過
v-bind:/:傳值,子組件通過this.$attrs獲取- 父傳子
- 當子組件沒有設定props的時候可以使用
this.$attrs獲取到的是一個物件(所有父組件傳過來的集合)
-
祖先組件使用provide提供資料,子孫組件通過inject注入資料
-
p a r e n t / parent/ parent/children
-
refs—$ref
-
還有一個,這個網上沒有,我自己認為的,我覺得挺對的,slot-scope,本身父組件使用slot插槽是無法獲取子組件的資料的,但是使用了slot-scope就可以獲取到子組件的資料(擁有了子組件的作用域)
Vue-router有哪幾種鉤子函式?????
- beforeEach
- 引數有
- to(Route路由物件)
- from(Route路由物件)
- next(function函式) 一定要呼叫才能進行下一步
- 引數有
- afterEach
- beforeRouterLeave
Webpack
webpack常用的幾個物件及解釋????
-
entry 入口檔案
-
output 輸出檔案
-
一般配合node的path模塊使用
-
// 入口檔案 entry:"./src/index.js", output:{ // 輸出檔案名稱 filename:"bundle.js", // 輸出的路徑(絕對路徑) path:path.resolve(__dirname,"dist") //利用node模塊的path 絕對路徑 }, // 設定模式 mode:"development"
-
-
-
mode 設計模式
-
module(loader)
-
里面有一個rules陣列對某種格式的檔案進行轉換處理(轉換規則)
-
use陣列決議順序是從下到上逆序執行的
-
module:{ // 對某種格式的檔案進行轉換處理(轉換規則) rules:[ { // 用到正則運算式 test:/\.css$/, //后綴名為css格式的檔案 use:[ // use陣列決議順序是從下到上逆序執行的 // 先用css-loader 再用style-loader // 將js的樣式內容插入到style標簽里 "style-loader", // 將css檔案轉換為js "css-loader" ] } ] } // -----------------------------------------------------vue的 module.exports={ module:{ rules:[ { test: /\.vue$/, use:["vue-loader"] } ] } }
-
-
plugin
-
插件配置
-
const uglifyJsPlugin = reqiure('uglifyjs-webpack-plugin') module.exports={ plugin:[ new uglifyJsPlugin() //丑化 ] }
-
-
devServer
-
熱更新
-
devServer:{ // 專案構建路徑 contentBase:path.resolve(__dirname,"dist"), // 啟動gzip亞索 compress:true, // 設定埠號 port:2020, // 自動打開瀏覽器:否 open:false, //頁面實時重繪(實時監聽) inline:true }
-
-
resolve
-
配置路徑規則
-
alias 別名
-
module.exports= { resolve:{ //如果匯入的時候不想寫后綴名可以在resolve中定義extensions extensions:['.js','.css','.vue'] //alias:別名 alias:{ //匯入以vue結尾的檔案時,會去尋找vue.esm.js檔案 'vue$':"vue/dist/vue.esm.js" } } }
-
-
babel(ES6轉ES5)
- 下載插件
babel-loader,在module(loader)中配置
- 下載插件
loader和plugin的區別是什么????
- loader
- loader是用來決議非js檔案的,因為Webpack原生只能決議js檔案,如果想把那些檔案一并打包的話,就需要用到loader,loader使webpack具有了決議非js檔案的能力
- plugin
- 用來給webpack擴展功能的,可以加載許多插件
CSS/HTML
flex布局?????
這個我就不例舉了,看看阮一峰老師的文章叭!Flex 布局教程
grid布局????
同樣是阮一峰老師的,CSS Grid 網格布局教程
常見的行內元素和塊級元素都有哪些??????
- 行內元素 inline
- 不能設定寬高,不能自動換行
- span、input、img、textarea、label、select
- 塊級元素block
- 可以設定寬高,會自動換行
- p、h1/h2/h3/h4/h5、div、ul、li、table
- inline-block
- 可以設定寬高,會自動換行
請說明px,em,rem,vw,vh,rpx等單位的特性?????
- px
- 像素
- em
- 當前元素的字體大小
- rem
- 根元素字體大小
- vw
- 100vw是總寬度
- vh
- 100vh是總高度
- rpx
- 750rpx是總寬度
常見的替換元素和非替換元素???
- 替換元素
- 是指若標簽的屬性可以改變標簽的顯示方式就是替換元素,比如input的type屬性不同會有不同的展現,img的src等
- img、input、iframe
- 非替換元素
- div、span、p
first-of-type和first-child有什么區別????
- first-of-type
- 匹配的是從第一個子元素開始數,匹配到的那個的第一個元素
- first-child
- 必須是第一個子元素
doctype標簽和meta標簽?????
- doctype
- 告訴瀏覽器以什么樣的檔案規范決議檔案
- 標準模式和兼容模式
- 標準模式 ->正常,排版和js運作模式都是以最高標準運行
- 兼容模式->非正常
script標簽中defer和async都表示了什么?????
-
眾所周知script會阻塞頁面的加載,如果我們要是參考外部js,假如這個外部js請求很久的話就難免出現空白頁問題,好在官方為我們提供了defer和async
-
defer
-
<script src="d.js" defer></script> <script src="e.js" defer></script> -
不會阻止頁面決議,并行下載對應的js檔案
-
下載完之后不會執行
-
等所有其他腳本加載完之后,在
DOMContentLoaded事件之前執行對應d.js、e.js
-
-
async
-
<script src="b.js" async></script> <script src="c.js" async></script> -
不會阻止DOM決議,并行下載對應的js檔案
-
下載完之后立即執行
-
-
補充,
DOMContentLoaded事件- 是等HTML檔案完全加載完和決議完之后運行的事件
- 在
load事件之前, - 不用等樣式表、影像等完成加載
什么是BFC??????
- BFC是一個獨立渲染區域,它絲毫不會影響到外部元素
- BFC特性
- 同一個BFC下margin會重疊
- 計算BFC高度時會算上浮動元素
- BFC不會影響到外部元素
- BFC內部元素是垂直排列的
- BFC區域不會與float元素重疊
- 如何創建BFC
- position設為absolute或者fixed
- float不為none
- overflow設定為hidden
- display設定為inline-block或者inline-table或flex
如何清除浮動?????
-
額外標簽clear:both
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .fahter{ width: 400px; border: 1px solid deeppink; } .big{ width: 200px; height: 200px; background: darkorange; float: left; } .small{ width: 120px; height: 120px; background: darkmagenta; float: left; } .clear{ clear:both; } </style> </head> <body> <div class="fahter"> <div class="big">big</div> <div class="small">small</div> <div class="clear">額外標簽法</div> </div> </body>
-
-
利用BFC
-
overflow:hidden -
.fahter{ width: 400px; border: 1px solid deeppink; overflow: hidden; }
-
-
使用after(推薦)
-
<style> .clearfix:after{/*偽元素是行內元素 正常瀏覽器清除浮動方法*/ content: ""; display: block; height: 0; clear:both; visibility: hidden; } .clearfix{ *zoom: 1;/*ie6清除浮動的方式 *號只有IE6-IE7執行,其他瀏覽器不執行*/ } </style> <body> <div class="fahter clearfix"> <div class="big">big</div> <div class="small">small</div> <!--<div class="clear">額外標簽法</div>--> </div>
-
什么是DOM事件流?什么是事件委托?????
- DOM事件流
- 分為三個階段
- 捕獲階段
- 目標階段
- 冒泡階段
- 在addeventListener()的第三個引數(useCapture)設為true,就會在捕獲階段運行,默認是false冒泡
- 分為三個階段
- 事件委托
- 利用冒泡原理(子向父一層層穿透),把事件系結到父元素中,以實作事件委托
link標簽和import標簽的區別????
- link屬于html,而@import屬于css
- 頁面被加載時,link會同時被加載,而@import參考的css會等到頁面加載結束后加載,
- link是html標簽,因此沒有兼容性,而@import只有IE5以上才能識別,
- link方式樣式的權重高于@import的,
演算法
這里推薦一個排序演算法的影片網站,應該是一個國外團隊做的,Sorting Algorithms
冒泡演算法排序?????
// 冒泡排序
/* 1.比較相鄰的兩個元素,如果前一個比后一個大,則交換位置,
2.第一輪的時候最后一個元素應該是最大的一個,
3.按照步驟一的方法進行相鄰兩個元素的比較,這個時候由于最后一個元素已經是最大的了,所以最后一個元素不用比較, */
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
bubbleSort(Arr)
console.log(Arr, "after");
快速排序?????
/*
快速排序是對冒泡排序的一種改進,第一趟排序時將資料分成兩部分,一部分比另一部分的所有資料都要小,
然后遞回呼叫,在兩邊都實行快速排序,
*/
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var middle = Math.floor(arr.length / 2)
var middleData = arr.splice(middle, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < middleData) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([middleData], quickSort(right))
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
var newArr = quickSort(Arr)
console.log(newArr, "after");
插入排序????
function insertSort(arr) {
// 默認第一個排好序了
for (var i = 1; i < arr.length; i++) {
// 如果后面的小于前面的直接把后面的插到前邊正確的位置
if (arr[i] < arr[i - 1]) {
var el = arr[i]
arr[i] = arr[i - 1]
var j = i - 1
while (j >= 0 && arr[j] > el) {
arr[j+1] = arr[j]
j--
}
arr[j+1] = el
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
insertSort(Arr)
console.log(Arr, "after");
是否回文?????
function isHuiWen(str) {
return str == str.split("").reverse().join("")
}
console.log(isHuiWen("mnm"));
正則運算式,千分位分隔符????
function thousand(num) {
return (num+"").replace(/\d(?=(\d{3})+$)/g, "$&,")
}
console.log(thousand(123456789));
陣列去重?????
var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
// 最low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
if (!newArr2.includes(arr[i])) {
newArr2.push(arr[i])
}
}
console.log(newArr2);
// 最low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
var item = arr2[i]
for (let j = i + 1; j < arr2.length; j++) {
var compare = arr2[j];
if (compare === item) {
arr2.splice(j, 1)
j--
}
}
}
console.log(arr2);
// 基于物件去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {}
for (let i = 0; i < arr3.length; i++) {
let item = arr3[i]
if (obj[item]) {
arr3[i] = arr3[arr3.length - 1]
arr3.length--
i--
continue;
}
obj[item] = item
}
console.log(arr3);
console.log(obj);
// 利用Set
let newArr1 = new Set(arr)
console.log([...newArr1]);
let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);
斐波那契數列?????
// num1前一項
// num2當前項
function fb(n, num1 = 1, num2 = 1) {
if (n <= 1) {
return num2
} else {
fb(n - 1, num2, num1 + num2)
}
}
陣列去重的方式?????
var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
// 最low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
if (!newArr2.includes(arr[i])) {
newArr2.push(arr[i])
}
}
console.log(newArr2);
// 最low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
var item = arr2[i]
for (let j = i + 1; j < arr2.length; j++) {
var compare = arr2[j];
if (compare === item) {
arr2.splice(j, 1)
j--
}
}
}
console.log(arr2);
// 基于物件去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {}
for (let i = 0; i < arr3.length; i++) {
let item = arr3[i]
if (obj[item]) {
arr3[i] = arr3[arr3.length - 1]
arr3.length--
i--
continue;
}
obj[item] = item
}
console.log(arr3);
console.log(obj);
// 利用Set
let newArr1 = new Set(arr)
console.log([...newArr1]);
let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
//利用reduce
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);
git
git的常用命令?????
- commit之后撤回
- git reset soft HEAD^
- 分支
- git branch xx 創建分支
- git checkout xx切換分支
- 添加
- git add .
- git push
- git commit -m

git常用命令與常見面試題總結
震驚!你竟然看完了,看來你距離大神就差一點點了!


ok,今天的文章就到這里了,
- 如果你覺得文章不錯的話,可以收藏點贊,也可以關注上我,今后我可能會根據一些大公司的面試題進行在總結,
- 如果你覺得文章特別水的話

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/250701.html
標籤:其他
