77777777777777777777777777777777777777777777777777777777777777777777777777
一、html和css部分
1、如何理解CSS的盒子模型?
標準盒子模型:寬度=內容的寬度(content)+ border + padding
低版本IE盒子模型:寬度=內容寬度(content+border+padding)
複製程式碼
2、BFC?
* 什麼是 BFC
BFC(Block Formatting Context)格式化上下文,是 Web 頁面中盒模型佈局的 CSS 渲染模式,指一個獨立的渲染區域或者說是一個隔離的獨立容器。
* 形成 BFC 的條件
* 浮動元素,float 除 none 以外的值
* 定位元素,position(absolute,fixed)
* display 為以下其中之一的值 inline-block,table-cell,table-caption
* overflow 除了 visible 以外的值(hidden,auto,scroll)
* BFC 的特性
* 內部的 Box 會在垂直方向上一個接一個的放置。
* 垂直方向上的距離由 margin 決定
* bfc 的區域不會與 float 的元素區域重疊。
* 計算 bfc 的高度時,浮動元素也參與計算
* bfc 就是頁面上的一個獨立容器,容器裡面的子元素不會影響外面元素。
複製程式碼
3、如何清除浮動?
不清楚浮動會發生高度塌陷:浮動元素父元素高度自適應(父元素不寫高度時,子元素寫了浮動後,父元素會發生高度塌陷)
* clear清除浮動(新增空div法)在浮動元素下方新增空div,並給該元素寫css樣式: {clear:both;height:0;overflow:hidden;}
* 給浮動元素父級設定高度
* 父級同時浮動(需要給父級同級元素新增浮動)
* 父級設定成inline-block,其margin: 0 auto居中方式失效
* 給父級新增overflow:hidden 清除浮動方法
* 萬能清除法 after偽類 清浮動(現在主流方法,推薦使用)
.float_div:after{
content:".";
clear:both;
display:block;
height:0;
overflow:hidden;
visibility:hidden;
}
.float_div{
zoom:1
}
複製程式碼
4、用純CSS建立一個三角形的原理是什麼?
span {
width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
複製程式碼
5、css3實現0.5px的細線?
/* css */
.line {
position: relative;
}
.line:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background-color: #000000;
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
}
/* html */
<div class="line"></div>
複製程式碼
6、css實現三欄佈局
左右固定,中間自適應。
- flex方式
<!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>
.box {
display: flex;
justify-content: center;
height: 200px;
}
.left {
width: 200px;
background-color: red;
height: 100%;
}
.content {
background-color: yellow;
flex: 1;
}
.right {
width: 200px;
background-color: green;
}
</style>
</head>
<body>
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
</body>
</html>
複製程式碼
- 絕對定位方式
<!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>
.box {
position: relative;
height: 200px;
}
.left {
width: 200px;
background-color: red;
left: 0;
height: 100%;
position: absolute;
}
.content {
background-color: yellow;
left: 200px;
right: 200px;
height: 100%;
position: absolute;
}
.right {
width: 200px;
background-color: green;
right: 0;
height: 100%;
position: absolute;
}
</style>
</head>
<body>
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
</body>
</html>
複製程式碼
- 浮動方式
<!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>
.box {
height: 200px;
}
.left {
width: 200px;
background-color: red;
float: left;
height: 100%;
}
.content {
background-color: yellow;
height: 100%;
}
.right {
width: 200px;
background-color: green;
float: right;
height: 100%;
}
</style>
</head>
<body>
<div class="box">
<div class="left"></div>
<div class="right"></div>
<div class="content"></div>
</div>
</body>
</html>
複製程式碼
7、讓一個div垂直居中
- 寬度和高度已知的
<!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>
.box {
width: 400px;
height: 200px;
position: relative;
background: red;
}
.content {
width: 200px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -100px;
margin-top: -50px;
background: green;
}
</style>
</head>
<body>
<div class="box">
<div class="content"></div>
</div>
</body>
</html>
複製程式碼
- 寬度和高度未知
<!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>
.box {
width: 400px;
height: 200px;
position: relative;
background: red;
}
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: green;
}
</style>
</head>
<body>
<div class="box">
<div class="content"></div>
</div>
</body>
</html>
複製程式碼
- flex佈局
<!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>
.box {
width: 400px;
height: 200px;
background: red;
display: flex;
justify-content: center;
align-items: center;
}
.content {
width: 200px;
height: 100px;
background: green;
}
</style>
</head>
<body>
<div class="box">
<div class="content"></div>
</div>
</body>
</html>
複製程式碼
二、JS
1、閉包
閉包概念
能夠讀取其他函式內部變數的函式。
或簡單理解為定義在一個函式內部的函式,內部函式持有外部函式內變數的引用。
複製程式碼
閉包用途
1、讀取函式內部的變數
2、讓這些變數的值始終保持在記憶體中。不會再f1呼叫後被自動清除。
3、方便呼叫上下文的區域性變數。利於程式碼封裝。
原因:f1是f2的父函式,f2被賦給了一個全域性變數,f2始終存在記憶體中,f2的存在依賴f1,因此f1也始終存在記憶體中,不會在呼叫結束後,被垃圾回收機制回收。
複製程式碼
閉包缺點
1、由於閉包會使得函式中的變數都被儲存在記憶體中,記憶體消耗很大,所以不能濫用閉包,否則會造成網頁的效能問題,在IE中可能導致記憶體洩露。解決方法是,在退出函式之前,將不使用的區域性變數全部刪除。
2、閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。
複製程式碼
閉包應用場景
閉包應用場景之setTimeout
//setTimeout傳遞的第一個函式不能帶引數
setTimeout((param) => {
alert(param)
}, 1000);
//通過閉包可以實現傳參效果
function func(param) {
return function() {
alert(param)
}
}
var f1 = func('汪某');
setTimeout(f1, 1000)//汪某
複製程式碼
2、js中函式執行
在 ES5.1 裡面函式是這樣執行的(不討論use strict和一些特殊情況,JS好複雜的),按如下順序執行:
1. 確定“this”的值 (確切的來說,this在JS裡面不是一個變數名而是一個關鍵字)
2. 建立一個新的作用域
3. 處理形參/實參(沒有定義過才宣告,無論如何都重新賦值,沒有對應實參則賦值為"undefined"):
對於每一個傳入的實參,按照從左往右的順序依次執行:如果對應的形參在本作用域中還沒有定義,則在本作用域中宣告形參,並賦值。如果已經定義過了,則重新給其賦值。(沒有對應實參則賦值為"undefined")(沒有定義:就是“沒有宣告”的意思)
4. 處理函式定義(沒有定義過才宣告,無論如何都重新賦值):
對該函式中所有的定義的函式,按照程式碼寫的順序依次執行:如果這個變數名在本作用域中還沒有定義,則在本作用域中宣告這個函式名,並且賦值為對應的函式,如果定義了這個變數,在可寫的情況下重新給這個變數賦值為這個函式,否則丟擲異常。
5. 處理 "arguments"(沒有定義過才宣告和賦值):
如果在本作用域中沒有定義 arguments,則在本作用域中宣告arguments並給其賦值。
6. 處理變數宣告(沒有定義過才宣告,不賦值):
對於所有變數宣告,按照程式碼寫的順序依次執行:如果在本作用域中沒有定義這個變數,則在本作用域中宣告這個變數,賦值為undefined
7. 然後執行函式程式碼。(當然是去變數定義裡面的 var 執行)
複製程式碼
3、new一個物件的過程中發生了什麼嘛
1. 建立空物件;
var obj = {};
2. 設定新物件的constructor屬性為建構函式的名稱,設定新物件的__proto__屬性指向建構函式的prototype物件;
obj.__proto__ = ClassA.prototype;
3. 使用新物件呼叫函式,函式中的this被指向新例項物件:
ClassA.call(obj);//{}.建構函式();
4. 如果無返回值或者返回一個非物件值,則將新物件返回;如果返回值是一個新物件的話那麼直接直接返回該物件。
複製程式碼
4、巨集任務跟微任務
- macro-task(巨集任務):包括整體程式碼script,setTimeout,setInterval
- micro-task(微任務):Promise,process.nextTick
5、防抖和節流
綜合應用場景
- 防抖(debounce):就是指觸發事件後在 n 秒內函式只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。
- search搜尋聯想,使用者在不斷輸入值時,用防抖來節約請求資源。
- window觸發resize的時候,不斷的調整瀏覽器視窗大小會不斷的觸發這個事件,用防抖來讓其只觸發一次
- 節流(throttle):就是指連續觸發事件但是在 n 秒中只執行一次函式。節流會稀釋函式的執行頻率。
- 滑鼠不斷點選觸發,mousedown(單位時間內只觸發一次)
- 監聽滾動事件,比如是否滑到底部自動載入更多,用throttle來判斷 所謂防抖,就是指觸發事件後在 n 秒內函式只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。
防抖函式分為非立即執行版和立即執行版。
- 非立即執行版的意思是觸發事件後函式不會立即執行,而是在 n 秒後執行,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。
- 立即執行版的意思是觸發事件後函式會立即執行,然後 n 秒內不觸發事件才能繼續執行函式的效果。
/**
* @desc 函式防抖
* @param func 函式
* @param wait 延遲執行毫秒數
* @param immediate true 表立即執行,false 表非立即執行
*/
function debounce(func,wait,immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
複製程式碼
所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函式。 節流會稀釋函式的執行頻率。
對於節流,一般有兩種方式可以實現,分別是時間戳版和定時器版。
- 時間戳版的函式觸發是在時間段內開始的時候
- 定時器版的函式觸發是在時間段內結束的時候。
/**
* @desc 函式節流
* @param func 函式
* @param wait 延遲執行毫秒數
* @param type 1 表時間戳版,2 表定時器版
*/
function throttle(func, wait ,type) {
if(type===1){
var previous = 0;
}else if(type===2){
var timeout;
}
return function() {
let context = this;
let args = arguments;
if(type===1){
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}else if(type===2){
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}
複製程式碼
6、陣列的常用方法
改變原陣列的方法
- splice() 新增/刪除陣列元素
語法:arrayObject.splice(index,howmany,item1,.....,itemX)
引數:
1.index:必需。整數,規定新增/刪除專案的位置,使用負數可從陣列結尾處規定位置。
2.howmany:可選。要刪除的專案數量。如果設定為 0,則不會刪除專案。
3.item1, ..., itemX: 可選。向陣列新增的新專案。
返回值: 如果有元素被刪除,返回包含被刪除專案的新陣列。
複製程式碼
- sort() 陣列排序
語法:arrayObject.sort(sortby)
引數:
1.sortby 可選。規定排序順序。必須是函式。。
返回值: 返回包排序後的新陣列。
複製程式碼
- pop() 刪除一個陣列中的最後的一個元素
語法:arrayObject.pop()
引數:無
返回值: 返回被刪除的元素。
複製程式碼
- shift() 刪除陣列的第一個元素
語法:arrayObject.shift()
引數:無
返回值: 返回被刪除的元素。
複製程式碼
- push() 向陣列的末尾新增元素
語法:arrayObject.push(newelement1,newelement2,....,newelementX)
引數:
1.newelement1 必需。要新增到陣列的第一個元素。
2.newelement2 可選。要新增到陣列的第二個元素。
3.newelementX 可選。可新增若干個元素。
返回值: arrayObject 的新長度。
複製程式碼
- unshift() 向陣列的開頭新增一個或更多元素
語法:arrayObject.unshift(newelement1,newelement2,....,newelementX)
引數:
1.newelement1 必需。要新增到陣列的第一個元素。
2.newelement2 可選。要新增到陣列的第二個元素。
3.newelementX 可選。可新增若干個元素。
返回值: arrayObject 的新長度。
複製程式碼
- reverse() 顛倒陣列中元素的順序
語法:arrayObject.reverse()
引數:無
返回值: 顛倒後的新陣列。
複製程式碼
- copyWithin() 指定位置的成員複製到其他位置
語法: array.copyWithin(target, start = 0, end = this.length)
引數:
1.target(必需):從該位置開始替換資料。如果為負值,表示倒數。
2.start(可選):從該位置開始讀取資料,預設為 0。如果為負值,表示倒數。
3.end(可選):到該位置前停止讀取資料,預設等於陣列長度。如果為負值,表示倒數。
返回值: 返回當前陣列。
複製程式碼
- fill() 填充陣列
語法: array.fill(value, start, end)
引數:
1.value 必需。填充的值。
2.start 可選。開始填充位置。
3.end 可選。停止填充位置 (預設為 array.length)
返回值: 返回當前陣列。
複製程式碼
不改變原陣列的方法
- slice() 淺拷貝陣列的元素
語法: array.slice(begin, end);
引數:
1.begin(可選): 索引數值,接受負值,從該索引處開始提取原陣列中的元素,預設值為0。
2.end(可選):索引數值(不包括),接受負值,在該索引處前結束提取原陣列元素,預設值為陣列末尾(包括最後一個元素)。
返回值: 返回一個從開始到結束(不包括結束)選擇的陣列的一部分淺拷貝到一個新陣列物件,且原陣列不會被修改。
複製程式碼
- join() 陣列轉字串
語法:array.join(str)
引數:
1.str(可選): 指定要使用的分隔符,預設使用逗號作為分隔符。
返回值: 返回生成的字串。
複製程式碼
- concat() 合併兩個或多個陣列
語法: var newArr =oldArray.concat(arrayX,arrayX,......,arrayX)
引數:
1.arrayX(必須):該引數可以是具體的值,也可以是陣列物件。可以是任意多個。
返回值: 返回返回合併後的新陣列。
複製程式碼
- indexOf() 查詢陣列是否存在某個元素
語法:array.indexOf(searchElement,fromIndex)
引數:
1.searchElement(必須):被查詢的元素
2.fromIndex(可選):開始查詢的位置(不能大於等於陣列的長度,返回-1),接受負值,預設值為0。
返回值: 返回下標
複製程式碼
- lastIndexOf() 查詢指定元素在陣列中的最後一個位置
語法:arr.lastIndexOf(searchElement,fromIndex)
引數:
1.searchElement(必須): 被查詢的元素
2.fromIndex(可選): 逆向查詢開始位置,預設值陣列的長度-1,即查詢整個陣列。
返回值: 方法返回指定元素,在陣列中的最後一個的索引,如果不存在則返回 -1。(從陣列後面往前查詢)
複製程式碼
- includes() 查詢陣列是否包含某個元素
語法: array.includes(searchElement,fromIndex=0)
引數:
1.searchElement(必須):被查詢的元素
2.fromIndex(可選):預設值為0,參數列示搜尋的起始位置,接受負值。正值超過陣列長度,陣列不會被搜尋,返回false。負值絕對值超過長陣列度,重置從0開始搜尋。
返回值: 返回布林
複製程式碼
7、立即執行函式
宣告一個匿名函式,馬上呼叫這個匿名函式。目的是保護內部變數不受汙染。
(function(n1, n2) {
console.log("這是匿名函式的自執行的第一種寫法,結果為:" + (n1 + n2))
})(10, 100);
(function start(n1, n2) {
console.log("這是函式宣告方式的自執行的第一種寫法,結果為:" + (n1 + n2))
})(10, 100);
(function(n1, n2) {
console.log("這是匿名函式的自執行的第二種寫法,結果為:" + (n1 + n2))
}(10, 100));
(function start(n1, n2) {
console.log("這是函式宣告方式的自執行的第二種寫法,結果為:" + (n1 + n2))
}(10, 100));
複製程式碼
8、js原型和原型鏈
每個物件都會在其內部初始化一個屬性,就是prototype(原型),當我們訪問一個物件的屬性時,如果這個物件內部不存在這個屬性,那麼他就會去prototype裡找這個屬性,這個prototype又會有自己的prototype,於是就這樣一直找下去,也就是我們平時所說的原型鏈的概念。
關係:instance.constructor.prototype = instance.proto
特點:JavaScript物件是通過引用來傳遞的,我們建立的每個新物件實體中並沒有一份屬於自己的原型副本,當我們修改原型時,與之相關的物件也會繼承這一改變。 當我們需要一個屬性時,JavaScript引擎會先看當前物件中是否有這個屬性,如果沒有的話,就會查詢它的prototype物件是否有這個屬性,如此遞推下去,一致檢索到Object內建物件。
function Func(){}
Func.prototype.name = "汪某";
Func.prototype.getInfo = function() {
return this.name;
}
var person = new Func();
console.log(person.getInfo());//"汪某"
console.log(Func.prototype);//Func { name = "汪某", getInfo = function() }
複製程式碼
參考:js原型和原型鏈
9、js中call,apply,bind
參考:JavaScript中call,apply,bind方法的總結。
10、Promise
一句話概括Promise:Promise物件用於非同步操作,它表示一個尚未完成且預計在未來完成的非同步操作。
promise是用來解決兩個問題的:
- 回撥地獄,程式碼難以維護,常常第一個的函式的輸出是第二個函式的輸入這種現象
- promise可以支援多個併發的請求,獲取併發請求中的資料
這個promise可以解決非同步的問題,本身不能說promise是非同步的
/*Promise 的簡單實現*/
class MyPromise {
constructor(fn) {
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
this.state = "PADDING";
this.value = "";
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {
if (this.state === "PADDING") {
this.state = "RESOLVED";
this.value = value;
this.resolvedCallbacks.forEach(cb => cb());
}
}
reject(value) {
if (this.state === "PADDING") {
this.state = "REJECTED";
this.value = value;
this.rejectedCallbacks.forEach(cb => cb());
}
}
then(resolve = function() {}, reject = function() {}) {
if (this.state === "PADDING") {
this.resolvedCallbacks.push(resolve);
this.rejectedCallbacks.push(reject);
}
if (this.state === "RESOLVED") {
resolve(this.value);
}
if (this.state === "REJECTED") {
reject(this.value);
}
}
}
複製程式碼
11、async/await
如何使用 Async 函式
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
複製程式碼
上面程式碼指定50毫秒以後,輸出hello world。 進一步說,async函式完全可以看作多個非同步操作,包裝成的一個 Promise 物件,而await命令就是內部then命令的語法糖。
待補充。。。
12、深拷貝、淺拷貝
淺拷貝和深拷貝都只針對於引用資料型別,淺拷貝只複製指向某個物件的指標,而不復制物件本身,新舊物件還是共享同一塊記憶體;但深拷貝會另外創造一個一模一樣的物件,新物件跟原物件不共享記憶體,修改新物件不會改到原物件;
區別:淺拷貝只複製物件的第一層屬性、深拷貝可以對物件的屬性進行遞迴複製;
淺拷貝的實現方式- 自定義函式
function simpleCopy (initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
複製程式碼
- ES6 的 Object.assign()
let newObj = Object.assign({}, obj);
複製程式碼
- ES6 的物件擴充套件
let newObj = {...obj};
複製程式碼
深拷貝的實現方式
- JSON.stringify 和 JSON.parse
用 JSON.stringify 把物件轉換成字串,再用 JSON.parse 把字串轉換成新的物件。
let newObj = JSON.parse(JSON.stringify(obj));
複製程式碼
- lodash
用 lodash 函式庫提供的 _.cloneDeep 方法實現深拷貝。
var _ = require('lodash');
var newObj = _.cloneDeep(obj);
複製程式碼
- 自己封裝
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
// for...in 會把繼承的屬性一起遍歷
for (let key in obj) {
// 判斷是不是自有屬性,而不是繼承屬性
if (obj.hasOwnProperty(key)) {
//判斷ojb子元素是否為物件或陣列,如果是,遞迴複製
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = this.deepClone(obj[key]);
} else {
//如果不是,簡單複製
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
複製程式碼
13、跨域
跨域需要針對瀏覽器的同源策略來理解,同源策略指的是請求必須是同一個埠,同一個協議,同一個域名,不同源的客戶端指令碼在沒有明確授權的情況下,不能讀寫對方資源。
受瀏覽器同源策略的影響,不是同源的指令碼不能操作其他源下面的物件。想要操作另一個源下的物件是就需要跨域。
- jsonp
- iframe
- 跨域資源共享(CORS)
- nginx 代理跨域
14、for in 和 for of
- for in
1.一般用於遍歷物件的可列舉屬性。以及物件從建構函式原型中繼承的屬性。對於每個不同的屬性,語句都會被執行。
2.不建議使用for in 遍歷陣列,因為輸出的順序是不固定的。
3.如果迭代的物件的變數值是null或者undefined, for in不執行迴圈體,建議在使用for in迴圈之前,先檢查該物件的值是不是null或者undefined
複製程式碼
- for of
1.for…of 語句在可迭代物件(包括 Array,Map,Set,String,TypedArray,arguments 物件等等)上建立一個迭代迴圈,呼叫自定義迭代鉤子,併為每個不同屬性的值執行語句
複製程式碼
遍歷物件
var s = {
a: 1,
b: 2,
c: 3
}
var s1 = Object.create(s);
for (var prop in s1) {
console.log(prop); //a b c
console.log(s1[prop]); //1 2 3
}
for (let prop of s1) {
console.log(prop); //報錯如下 Uncaught TypeError: s1 is not iterable
}
for (let prop of Object.keys(s1)) {
console.log(prop); // a b c
console.log(s1[prop]); //1 2 3
}
複製程式碼
15、如何阻止冒泡?
冒泡型事件:事件按照從最特定的事件目標到最不特定的事件目標(document物件)的順序觸發。
w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true。
//阻止冒泡行為
function stopBubble(e) {
//如果提供了事件物件,則這是一個非IE瀏覽器
if ( e && e.stopPropagation )
//因此它支援W3C的stopPropagation()方法
e.stopPropagation();
else
//否則,我們需要使用IE的方式來取消事件冒泡
window.event.cancelBubble = true;
}
複製程式碼
16、如何阻止預設事件?
w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false
//阻止瀏覽器的預設行為
function stopDefault( e ) {
//阻止預設瀏覽器動作(W3C)
if ( e && e.preventDefault )
e.preventDefault();
//IE中阻止函式器預設動作的方式
else
window.event.returnValue = false;
return false;
}
複製程式碼
17、var,let,const
//變數提升
console.log(a); // undefined
console.log(b); // 報錯
console.log(c); // 報錯
var a = 1;
let b = 2;
const c = 3;
// 全域性宣告
console.log(window.a) // 1
// 重複宣告
let b = 200;//報錯
複製程式碼
其實這裡很容易理解,var是可以變數提升的。而let和const是必須宣告後才能呼叫的。 對於let和const來說,這裡就是暫緩性死區。
18、Class
es6新增的Class其實也是語法糖,js底層其實沒有class的概念的,其實也是原型繼承的封裝。
class People {
constructor(props) {
this.props = props;
this.name = '汪某';
}
callMyName() {
console.log(this.name);
}
}
class Name extends People { // extends 其實就是繼承了哪個類
constructor(props) {
// super相當於 把類的原型拿過來
// People.call(this, props)
super(props)
}
callMyApple() {
console.log('我是汪某!')
}
}
let a = new Name('啊啊啊')
a.callMyName(); //汪某
a.callMyApple(); // 我是汪某!
複製程式碼
19、Set
Set資料結構類似陣列,但所有成員的值唯一。
let a = new Set();
[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
for(let k of a){
console.log(k)
};
// 1 2 3 4 5
複製程式碼
基本使用
let a = new Set([1,2,3,3,4]);
[...a]; // [1,2,3,4]
a.size; // 4
// 陣列去重
[...new Set([1,2,3,4,4,4])];// [1,2,3,4]
複製程式碼
方法
- add(value):新增某個值,返回 Set 結構本身。
- delete(value):刪除某個值,返回一個布林值,表示刪除是否成功。
- has(value):返回一個布林值,表示該值是否為Set的成員。
- clear():清除所有成員,沒有返回值。
let a = new Set();
a.add(1).add(2); // a => Set(2) {1, 2}
a.has(2); // true
a.has(3); // false
a.delete(2); // true a => Set(1) {1}
a.clear(); // a => Set(0) {}
複製程式碼
20、Map
Map結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。
let a = new Map();
let b = {name: 'leo' };
a.set(b,'my name'); // 新增值
a.get(b); // 獲取值
a.size; // 獲取總數
a.has(b); // 查詢是否存在
a.delete(b); // 刪除一個值
a.clear(); // 清空所有成員 無返回
複製程式碼
基本使用
- 傳入陣列作為引數,指定鍵值對的陣列。
let a = new Map([
['name','wzx'],
['age',23]
])
複製程式碼
- 如果對同一個鍵多次賦值,後面的值將覆蓋前面的值。
let a = new Map();
a.set(1,'aaa').set(1,'bbb');
a.get(1); // 'bbb'
複製程式碼
- 如果讀取一個未知的鍵,則返回undefined。
new Map().get('asdsad'); // undefined
複製程式碼
- 同樣的值的兩個例項,在 Map 結構中被視為兩個鍵。
let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222
複製程式碼
方法
- keys():返回鍵名的遍歷器。
- values():返回鍵值的遍歷器。
- entries():返回所有成員的遍歷器。
- forEach():遍歷 Map 的所有成員。
let a = new Map([
['name', 'leo'],
['age', 18]
])
for (let i of a.keys()) {
console.log(i)
};
//name
//age
for (let i of a.values()) {
console.log(i)
};
//leo
//18
for (let i of a.entries()) {
console.log(i)
};
//["name", "leo"]
a.forEach((v, k, m) => {
console.log(`key:${k},value:${v},map:${m}`)
})
//["age", 18]
複製程式碼
三、手擼程式碼
1、實現一個new操作符
function New(func) {
var res = {};
if (func.prototype !== null) {
res.__proto__ = func.prototype;
}
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return;
ret;
}
return;
res;
}
var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);
複製程式碼
2、實現一個call或 apply
- call
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
複製程式碼
- apply
Function.prototype.apply2 = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
複製程式碼
參考:JavaScript深入之call和apply的模擬實現
3、實現一個Function.bind
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fbound = function () {
self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;
}
複製程式碼
4、實現一個繼承
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype.constructor = Child;
var parent = new Parent('汪某');
parent.sayName();// parent name: 汪某
var child = new Child('son', '汪某');
複製程式碼
5、手寫一個Promise(中高階必考)
面試夠用版
function myPromise(constructor) {
let self = this;
self.status = "pending"
//定義狀態改變前的初始狀態
self.value = undefined;
//定義狀態為resolved的時候的狀態
self.reason = undefined;
//定義狀態為rejected的時候的狀態
function resolve(value) {
//兩個==="pending",保證了狀態的改變是不可逆的
if (self.status === "pending") {
self.value = value;
self.status = "resolved";
}
}
function reject(reason) {
//兩個==="pending",保證了狀態的改變是不可逆的
if (self.status === "pending") {
self.reason = reason;
self.status = "rejected";
}
}
//捕獲構造異常
try {
constructor(resolve, reject);
} catch (e) {
reject(e);
}
}
//同時,需要在 myPromise的原型上定義鏈式呼叫的 then方法:
myPromise.prototype.then = function(onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
//測試一下:
var p = new myPromise(function(resolve, reject) {
resolve(1)
});
p.then(function(x) {
console.log(x)
})
複製程式碼
高階版請參考:史上最最最詳細的手寫Promise教程
6、手寫防抖(Debouncing)和節流(Throttling)
完整版詳見上方,此處給出面試版
// 防抖函式
function debounce(fn, wait) {
let timer;
return function() {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, wait)
}
}
複製程式碼
// 節流函式
function throttle(fn, wait) {
let prev = new Date();
return function() {
const args = arguments;
const now = new Date();
if (now - prev > wait) {
fn.apply(this, args);
prev = new Date();
}
}
}
複製程式碼
7、手寫一個JS深拷貝
面試版
function deepCopy(obj) {
//判斷是否是簡單資料型別,
if (typeof obj == "object") {
//複雜資料型別
var result = obj.constructor == Array ? [] : {};
for (let i in obj) {
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
} else {
//簡單資料型別 直接 == 賦值
var result = obj;
}
return result;
}
複製程式碼
四、VUE
1、Vue2.0的雙向資料繫結原理是什麼?
vue.js 是採用資料劫持結合釋出者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。
//vue實現資料雙向繫結的原理就是用Object.defineproperty()重新定義(set方法)物件設定屬性值和(get方法)獲取屬性值的操縱來實現的。
//Object.property()方法的解釋:Object.property(引數1,引數2,引數3) 返回值為該物件obj
//其中引數1為該物件(obj),引數2為要定義或修改的物件的屬性名,引數3為屬性描述符,屬性描述符是一個物件,主要有兩種形式:資料描述符和存取描述符。這兩種物件只能選擇一種使用,不能混合使用。而get和set屬於存取描述符物件的屬性。
//這個方法會直接在一個物件上定義一個新屬性或者修改物件上的現有屬性,並返回該物件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myapp">
<input v-model="message" /><br>
<span v-bind="message"></span>
</div>
<script type="text/javascript">
var model = {
message: ""
};
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].onkeyup = function() {
model[this.getAttribute("v-model")] = this.value;
}
}
// 觀察者模式 / 鉤子函式
// defineProperty 來定義一個物件的某個屬性
Object.defineProperty(model, "message", {
set: function(newValue) {
var binds = myapp.querySelectorAll("[v-bind=message]");
for (var i = 0; i < binds.length; i++) {
binds[i].innerHTML = newValue;
};
var models = myapp.querySelectorAll("[v-model=message]");
for (var i = 0; i < models.length; i++) {
models[i].value = newValue;
};
this.value = newValue;
},
get: function() {
return this.value;
}
})
</script>
</body>
</html>
複製程式碼
Vue3.0將用原生Proxy替換Object.defineProperty
為什麼要替換Object.defineProperty?
- 在Vue中,Object.defineProperty無法監控到陣列下標的變化,導致直接通過陣列的下標給陣列設定值,不能實時響應。
- Object.defineProperty只能劫持物件的屬性,因此我們需要對每個物件的每個屬性進行遍歷。
什麼是Proxy
- Proxy是 ES6 中新增的一個特性,翻譯過來意思是"代理",用在這裡表示由它來“代理”某些操作。 Proxy 讓我們能夠以簡潔易懂的方式控制外部對物件的訪問。其功能非常類似於設計模式中的代理模式。
- Proxy 可以理解成,在目標物件之前架設一層“攔截”,外界對該物件的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
- 使用 Proxy 的核心優點是可以交由它來處理一些非核心邏輯(如:讀取或設定物件的某些屬性前記錄日誌;設定物件的某些屬性值前,需要驗證;某些屬性的訪問控制等)。 從而可以讓物件只需關注於核心邏輯,達到關注點分離,降低物件複雜度等目的。
2、請詳細說下你對vue生命週期的理解?
總共分為8個階段建立前/後,載入前/後,更新前/後,銷燬前/後
- beforeCreate 建立前執行(vue例項的掛載元素$el和資料物件data都為undefined,還未初始化)
- created 完成建立 (完成了data資料初始化,el還未初始化)
- beforeMount 載入前(vue例項的$el和data都初始化了,但還是掛載之前為虛擬的dom節點,data.message還未替換。)
- mounted 載入後html已經渲染(vue例項掛載完成,data.message成功渲染。)
- beforeUpdate 更新前狀態(view層的資料變化前,不是data中的資料改變前)
- updated 更新狀態後
- beforeDestroy 銷燬前
- destroyed 銷燬後 (在執行destroy方法後,對data的改變不會再觸發周期函式,說明此時vue例項已經解除了事件監聽以及和dom的繫結,但是dom結構依然存在)
說一下每一個階段可以做的事情
- beforeCreate:可以在這裡加一個loading事件,在載入例項時觸發。
- created:初始化完成時的事件寫這裡,如果這裡結束了loading事件,非同步請求也在這裡呼叫。
- mounted:掛在元素,獲取到DOM節點
- updated:對資料進行處理的函式寫這裡。
- beforeDestroy:可以寫一個確認停止事件的確認框。
附上一張中文解析圖
3、動態路由定義和獲取
在 router 目錄下的 index.js 檔案中,對 path 屬性加上 /:id。
使用 router 物件的 params.id 獲取
4、vue-router 有哪幾種導航鉤子?
三種
- 全域性導航鉤子(跳轉前進行判斷攔截)
- router.beforeEach(to, from, next),
- router.beforeResolve(to, from, next),
- router.afterEach(to, from ,next)
- 元件內鉤子
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
- 單獨路由獨享元件
- beforeEnter
5、元件之間的傳值通訊?
- 父元件向子元件傳值:
- 子元件在props中建立一個屬性,用來接收父元件傳過來的值;
- 在父元件中註冊子元件;
- 在子元件標籤中新增子元件props中建立的屬性;
- 把需要傳給子元件的值賦給該屬性
- 子元件向父元件傳值:
- 子元件中需要以某種方式(如點選事件)的方法來觸發一個自定義的事件;
- 將需要傳的值作為$emit的第二個引數,該值將作為實參傳給響應事件的方法;
- 在父元件中註冊子元件並在子元件標籤上繫結自定義事件的監聽。
6、vuex
是一個能方便vue例項及其元件傳輸資料的外掛 方便傳輸資料,作為公共儲存資料的一個庫
state: 狀態中心
mutations: 更改狀態,同步的
actions: 非同步更改狀態
getters: 獲取狀態
modules: 將state分成多個modules,便於管理
應用場景:單頁應用中,元件之間的狀態。音樂播放、登入狀態、加入購物車。
網上找的一個通俗易懂的瞭解vuex的例子
公司有個倉庫
1.State(公司的倉庫)
2.Getter(只能取出物品,包裝一下,不能改變物品任何屬性)
3.Muitation(倉庫管理員,只有他可以直接儲存到倉庫)
4.Action(公司的物料採購員,負責從外面買東西和接貨, 要往倉庫存東西,告訴倉庫管理員要存什麼)
非常要注意的地方:只要重新整理或者退出瀏覽器,倉庫清空。
7、Vue hash 路由和 history 路由的區別
hash模式url裡面永遠帶著#號,我們在開發當中預設使用這個模式。那麼什麼時候要用history模式呢?如果使用者考慮url的規範那麼就需要使用history模式,因為history模式沒有#號,是個正常的url適合推廣宣傳。當然其功能也有區別,比如我們在開發app的時候有分享頁面,那麼這個分享出去的頁面就是用vue或是react做的,我們們把這個頁面分享到第三方的app裡,有的app裡面url是不允許帶有#號的,所以要將#號去除那麼就要使用history模式,但是使用history模式還有一個問題就是,在訪問二級頁面的時候,做重新整理操作,會出現404錯誤,那麼就需要和後端人配合讓他配置一下apache或是nginx的url重定向,重定向到你的首頁路由上就ok啦。
router有兩種模式:hash模式(預設)、history模式(需配置mode: 'history')
hash | history | |
---|---|---|
url顯示 | 有#,很Low | 無#,好看 |
回車重新整理 | 可以載入到hash值對應頁面 | 一般就是404掉了 |
支援版本 | 支援低版本瀏覽器和IE瀏覽器 | 支援低版本瀏覽器和IE瀏覽器 |
8、diff演算法
五、計算機
1、DNS 解析的詳細過程
當我們在瀏覽器中輸入一個URL,例如”www.google.com”時,這個地址並不是谷歌網站真正意義上的地址。網際網路上每一臺計算機的唯一標識是它的IP地址,因此我們輸入的網址首先需要先解析為IP地址,這一過程叫做DNS解析。
DNS解析是一個遞迴查詢的過程。例如,我們需要解析”www.google.com”時,會經歷以下步驟:
- 在本地域名伺服器中查詢IP地址,未找到域名;
- 本地域名伺服器迴向根域名伺服器傳送請求,未找到域名;
- 本地域名伺服器向.com頂級域名伺服器傳送請求,未找到域名;
- 本地域名伺服器向.google.com域名伺服器傳送請求,找到該域名,將對應的IP返回給本地域名伺服器。
2、簡述三次握手
HTTP協議是使用TCP協議作為其傳輸層協議的,在拿到伺服器的IP地址後,瀏覽器客戶端會與伺服器建立TCP連線。該過程包括三次握手:
- 第一次握手:建立連線時,客戶端向服務端傳送請求報文
- 第二次握手:伺服器收到請求報文後,如同意連線,則向客戶端傳送確認報文
- 第三次握手,客戶端收到伺服器的確認後,再次向伺服器給出確認報文,完成連線。
三次握手主要是為了防止已經失效的請求報文欄位傳送給伺服器,浪費資源。
3、四次揮手
客戶端與伺服器四次揮手,斷開tcp連線。
- 第一次揮手:客戶端想分手,傳送訊息給伺服器
- 第二次揮手:伺服器通知客戶端已經接受到分手請求,但還沒做好分手準備
- 第三次回收:伺服器已經做好分手準備,通知客戶端
- 第四次揮手:客戶端傳送訊息給伺服器,確定分手,伺服器關閉連線