點進來之后你的噩夢就要來了,接下來你要面對上百道面試題,那么,如果你——
- 是個小白菜:
- 推薦使用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執行上下文
執行棧:
- 首先棧特點:先進后出
- 當進入一個執行環境,就會創建出它的執行上下文,然后進行壓棧,當程序執行完成時,它的執行上下文就會被銷毀,進行彈棧。
- 棧底永遠是全局環境的執行上下文,棧頂永遠是正在執行函數的執行上下文
- 只有瀏覽器關閉的時候全局執行上下文才會彈出
閉包
面試官:什么是閉包?閉包的作用?閉包的應用?⭐⭐⭐⭐⭐
答:
函數執行,形成私有的執行上下文,使內部私有變量不受外界干擾,起到保護和保存的作用
作用:
- 保護
- 避免命名沖突
- 保存
- 解決循環綁定引發的索引問題
- 變量不會銷毀
- 可以使用函數內部的變量,使變量不會被垃圾回收機制回收
應用:
- 設計模式中的單例模式
- 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?⭐⭐⭐⭐
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()
)
- 頁面已經渲染完成,並且
beforeUpdate
data
改變后,對應的組件重新渲染之前
updated
data
改變后,對應的組件重新渲染完成
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布局⭐⭐⭐⭐⭐
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的。
算法
冒泡算法排序⭐⭐⭐⭐⭐
// 冒泡排序
/* 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));
斐波那契數列⭐⭐⭐⭐⭐
// num1前一項
// num2當前項
function fb(n, num1 = 1, num2 = 1) {
if(n == 0) return 0
if (n <= 2) {
return num2
} else {
return 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
震驚!你竟然看完了,看來你距離大神就差一點點了!
ok,今天的文章就到這里了。
- 如果你覺得文章不錯的話,可以收藏點贊,也可以關注上我,今后我可能會根據一些大公司的面試題進行在總結。
- 如果你覺得文章特別水的話