# Js常見面試題
## 數據類型
### JS的基本數據類型
`Undefined、Null、Boolean、Number、String`新增:`Symbol`
### JS有哪些內置對象?
`Object`?是?`JavaScript`?中所有對象的父對象
數據封裝類對象:`Object、Array、Boolean、Number`?和?`String`
其他對象:`Function、Arguments、Math、Date、RegExp、Error`
### JS中使用typeof能得到的哪些類型?(考點:JS變量類型)
~~~javascript
typeof undefinded //undefined
typeof null // object
typeof'abc' // string
typeof 123 // number
typeof [] // object
typeof {} // object
typeof true //boolean
typeof b // b 沒有聲明,但是還會顯示 undefined
~~~

### 何時使用`===`何時使用`==`?(考點:強制類型轉換)
`==`?比較兩個值相等返回`ture`
`===`?比較兩者的值和類型都相等才返回`true`?嚴格相等
`0,NAN,null,undefinded,'',false`?在`if`語句中會強制轉化為?`false`
~~~javascript
if (obj.a == null) {
// 這里相當于 obj.a === null || obj.a ===undefinded, 簡寫形式
// 這里jquery 源碼中推薦的寫法
}
~~~

### `null,undefined`?的區別?
1、`null`:?`Null`類型,代表“空值”,代表一個空對象指針,使用`typeof`運算得到?`“object”`,所以你可以認為它是一個特殊的對象值。
2、`undefined`:?`Undefined`類型,當一個聲明了一個變量未初始化時,得到的就是`undefined`。
ps:一句話簡單來說`null`和`undefine`值比較是相等,但類型不同。
### Javascript創建對象的幾種方式?
1、對象字面量的方式
~~~javascript
var = {}
~~~

2、通過構造函數方式創建。
~~~javascript
var obj = new Object();
~~~

3、通過`Object.create()`方式創建。
~~~javascript
var obj = Object.create(Object.prototype);
~~~

### 翻轉一個字符串
可以先將字符串轉成一個數組,然后用數組的`reverse()+join()`方法。
~~~javascript
let str="hello word";
let b=[...str].reverse().join("");//drow olleh
~~~

### 描述`new`一個對象的過程
1、創建一個新對象
2、`this`指向這個對象
3、執行代碼,即對`this`賦值
4、隱式返回`this`
### JS按存儲方式區分變量類型
~~~javascript
// 值類型
var a = 10
var b = a
a = 11
console.log(b) // 10
// 引用類型
var obj1 = {x : 100}
var obj2 = obj1
obj1.x = 200
console.log(obj2) // 200
~~~

### 如何判斷一個變量是對象還是數組?
1、`instanceof`方法?`instanceof`運算符是用來測試一個對象是否在其原型鏈原型構造函數的屬性
~~~javascript
var arr = [];
arr instanceof Array; // true
~~~

2、`constructor`方法?`constructor`屬性返回對創建此對象的數組函數的引用,就是返回對象相對應的構造函數
~~~javascript
var arr = [];
arr.constructor == Array; //true
~~~

3、最簡單的方法,這種寫法兼容性最好使用`Object.prototype.toString.call()`
~~~javascript
function isObjArr(value){
if (Object.prototype.toString.call(value) === "[object Array]") {
console.log('value是數組');
}else if(Object.prototype.toString.call(value)==='[object Object]'){//這個方法兼容性好一點
console.log('value是對象');
}else{
console.log('value不是數組也不是對象')
}
}
~~~

4、`ES5`新增方法`isArray()`
~~~javascript
var a = new Array(123);
var b = new Date();
console.log(Array.isArray(a)); //true
console.log(Array.isArray(b)); //false
~~~

ps:千萬不能使用`typeof`來判斷對象和數組,因為這兩種類型都會返回`"object"`。
### 如何對一個數組去重?
1、`Set`結構去重(`ES6`用法)。
`ES6`提供了新的數據結構`Set`。它類似于數組,但是成員的值都是唯一的,沒有重復的值。
~~~javascript
[...new Set(array)];
~~~

2、遍歷,將值添加到新數組,用`indexOf()`判斷值是否存在,已存在就不添加,達到去重效果。
~~~javascript
let a = ['1','2','3',1,NaN,NaN,undefined,undefined,null,null, 'a','b','b'];
let unique= arr =>{
let newA=[];
arr.forEach(key => {
if( newA.indexOf(key)<0 ){ //遍歷newA是否存在key,如果存在key會大于0就跳過push的那一步
newA.push(key);
}
});
return newA;
}
console.log(unique(a)) ;//["1", "2", "3", 1, NaN, NaN, undefined, null, "a", "b"]
// 這個方法不能分辨NaN,會出現兩個NaN。是有問題的,下面那個方法好一點。
~~~

3、利用`for`嵌套`for`,然后`splice`去重(`ES5`中最常用)。
~~~javascript
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一個等同于第二個,splice方法刪除第二個
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}沒有去重,兩個null直接消失了
~~~

4、`forEach`遍歷,然后利用`Object.keys(對象)`返回這個對象可枚舉屬性組成的數組,這個數組就是去重后的數組。
~~~javascript
let a = ['1', '2', '3', 1,NaN,NaN,undefined,undefined,null,null, 'a', 'b', 'b'];
const unique = arr => {
var obj = {}
arr.forEach(value => {
obj[value] = 0;//這步新添加一個屬性,并賦值,如果不賦值的話,屬性會添加不上去
})
return Object.keys(obj);//`Object.keys(對象)`返回這個對象可枚舉屬性組成的數組,這個數組就是去重后的數組
}
console.log(unique(a));//["1", "2", "3", "NaN", "undefined", "null", "a", "b"]
~~~

## 作用域和閉包
### `var、let、const`之間的區別
`var`聲明變量可以重復聲明,而`let`不可以重復聲明
`var`是不受限于塊級的,而`let`是受限于塊級
`var`會與`window`相映射(會掛一個屬性),而`let`不與`window`相映射
`var`可以在聲明的上面訪問變量,而`let`有暫存死區,在聲明的上面訪問變量會報錯
`const`聲明之后必須賦值,否則會報錯
`const`定義不可變的量,改變了就會報錯
`const`和`let`一樣不會與`window`相映射、支持塊級作用域、在聲明的上面訪問變量會報錯
### 說明`This`幾種不同的使用場景
1、作為構造函數執行
2、作為對象屬性執行
3、作為普通函數執行
4、`call apply bind`
### 談談`This`對象的理解
1、`this`總是指向函數的直接調用者(而非間接調用者)
2、如果有`new`關鍵字,`this`指向`new`出來的那個對象
3、在事件中,`this`指向觸發這個事件的對象,特殊的是,`IE`中的`attachEvent`中的`this`總是指向全局對象`Window`
### 作用域
`ES5`作用域分為 全局作用域 和 函數作用域。
`ES6`新增塊級作用域,塊作用域由?`{ }`包括,`if`語句和?`for`語句里面的`{ }`也屬于塊作用域。
~~~javascript
// 塊級作用域
if (true) {
var name = 'zhangsan'
}
console.log(name)
// 函數和全局作用域
var a = 100
function fn () {
var a = 200
console.log('fn', a)
}
~~~

### 用`JS`創建10個`<a>`標簽,點擊的時候彈出來對應的序號?(考點:作用域)
~~~javascript
var i
for (i = 0; i < 10; i++) {
(function (i) {
var a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
})(i)
}
~~~

### 說說你對作用域鏈的理解
1、作用域鏈的作用是保證執行環境里有權訪問的變量和函數是有序的,作用域鏈的變量只能向上訪問,變量訪問到`window`對象即被終止,作用域鏈向下訪問變量是不被允許的
2、簡單的說,作用域就是變量與函數的可訪問范圍,即作用域控制著變量與函數的可見性和生命周期
3、通俗來說,一般情況下,變量取值到 創建 這個變量 的函數的作用域中取值。 但是如果在當前作用域中沒有查到值,就會向上級作用域去查,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈
~~~javascript
var a = 100
function fn () {
var b = 200
// 當前作用域沒有定于的變量,即‘自由變量’
console.log(a)
console.log(b)
}
fn()
console.log(a) 去父級作用域找a自由變量 作用域鏈
~~~

### 閉包
閉包就是能夠讀取其他函數內部變量的函數
閉包是指有權訪問另一個函數作用域中變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量,利用閉包可以突破作用鏈域
~~~javascript
function F1 () {
var a = 100
// 返回一個函數,函數作為返回值
return function () {
console.log(a)
}
}
// f1 得到一個函數
var f1 = F1()
var a = 200
f1() // 100 定義的時候父級作用域
~~~

### 閉包的特性
函數內再嵌套函數
內部函數可以引用外層的參數和變量
參數和變量不會被垃圾回收機制回收
### 說說你對閉包的理解
使用閉包主要是為了設計私有的方法和變量。閉包的優點是可以避免全局變量的污染,缺點是閉包會常駐內存,會增大內存使用量,使用不當很容易造成內存泄露。在js中,函數即閉包,只有函數才會產生作用域的概念
閉包的最大用處有兩個,一個是可以讀取函數內部的變量,另一個就是讓這些變量始終保持在內存中
閉包的另一個用處,是封裝對象的私有屬性和私有方法
好處:能夠實現封裝和緩存等;
壞處:就是消耗內存、不正當使用會造成內存溢出的問題
### 使用閉包的注意點
由于閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露 解決方法是,在退出函數之前,將不使用的局部變量全部刪除
### 閉包的使用場景
閉包的兩個場景:函數作為返回值 和 函數作為參數傳遞
~~~javascript
function F1 () {
var a = 100
// 返回一個函數,函數作為返回值
return function () {
console.log(a)
}
}
// f1 得到一個函數
var f1 = F1()
var a = 200
f1() // 100 定義的時候父級作用域
function F2 (fn) {
var a = 200
fn()
}
F2(f1)
~~~

### 實際開發中閉包的應用
~~~javascript
// 閉包實際應用中主要用于封裝變量 收斂權限
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
// 使用
var firstLoad = new isFirstLoad()
firstLoad(10) //true
firstLoad(10) //false
firstLoad(30) //true
~~~

## 原型和原型鏈
### JavaScript原型,原型鏈 ? 有什么特點?
每個對象都會在其內部初始化一個屬性,就是`prototype`(原型), 當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么他就會去`prototype`里找這個屬性,這個`prototype`又會有自己的`prototype`,于是就這樣一直找下去,也就是我們平時所說的原型鏈的概念
原型和原型鏈關系
**關系:**`instance.constructor.prototype = instance.__proto__`
原型和原型鏈特點
`JavaScript`對象是通過引用來傳遞的,我們創建的每個新對象實體中并沒有一份屬于自己的原型副本。當我們修改原型時,與之相關的對象也會繼承這一改變 當我們需要一個屬性的時,`Javascript`引擎會先看當前對象中是否有這個屬性, 如果沒有的 就會查找他的Prototype對象是否有這個屬性,如此遞推下去,一直檢索到?`Object`?內建對象
PS: 1.所有的引用類型(數組,對象,函數)都具有對象的特性,即可自由擴展屬性(除了‘`null`’)除外
2.所有的引用類型(數組,對象,函數)都有一個`_proto_`(隱式原型)屬性,屬性值是一個普通對象
3.所有的函數,都有一個`prototype`(顯示原型)的屬性,也是一個普通對象
4.所有的引用類型(數組,對象,函數)`_proto_`屬性值指向他的構造的‘`prototype`’ 屬性值
### 當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的`_proto_`(即它的構造函數的`prototype`) 中尋找
~~~javascript
// 構造函數
function Foo(name, age){
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
//創建實例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
//測試
f.printName()
f.alertName()
~~~

### 循環對象自身的屬性
~~~javascript
var item
for (item in f) {
// 高級瀏覽器已經在 for in 中屏蔽了來自原型的屬性
// 但是這里建議大家還是加上這個判斷,保證程序的健壯性
if (f.hasOwnProperty(item)) {
console.log(item)
}
}
~~~

### 原型鏈
~~~javascript
f.toString() //到f._proto_._proto_ 中去找
~~~

## 異步和單線程
### 同步和異步的區別是什么?分別舉一個同步和異步的例子
`同步交互`:指發送一個請求,需要等待返回,然后才能夠發送下一個請求,有個等待過程。
`異步交互`:指發送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。
`相同的地方`:都屬于交互方式,都是發送請求。
`不同的地方`:一個需要等待,一個不需要等待。
**簡單而言**,同步就是必須一件一件的做事,等前一件事做完后才能做下一件事。而異步這是把事情指派給別人后,接著繼續做下一件事,不必等別人返回的結果。
**舉例:**
1、廣播,就是一個異步例子。發起者不關心接收者的狀態。不需要等待接收者的返回信息; 在部分情況下,我們的項目開發中都會優先選擇不需要等待的異步交互方式。
2、電話,就是一個同步例子。發起者需要等待接收者,接通電話后,通信才開始。需要等待接收者的返回信息 比如銀行的轉賬系統,對數據庫的保存操作等等,都會使用同步交互操作。
ps:?`alert`是同步,`setTimeout`和`setInterval`是異步,同步會阻塞代碼執行,而異步不會
### 異步編程的實現方式?
1、`回調函數`
優點:簡單、容易理解
缺點:不利于維護,代碼耦合高
2、`事件監聽(采用時間驅動模式,取決于某個事件是否發生):`
優點:容易理解,可以綁定多個事件,每個事件可以指定多個回調函數
缺點:事件驅動型,流程不夠清晰
3、`發布/訂閱(觀察者模式)`
類似于事件監聽,但是可以通過‘消息中心’,了解現在有多少發布者,多少訂閱者
4、`Promise對象`
優點:可以利用`then`方法,進行鏈式寫法;可以書寫錯誤時的回調函數;
缺點:編寫和理解,相對比較難
5、`Generator函數`
優點:函數體內外的數據交換、錯誤處理機制
缺點:流程管理不方便
6、`async函數`
優點:內置執行器、更好的語義、更廣的適用性、返回的是`Promise`、結構清晰。
缺點:錯誤處理機制
### 定時器的執行順序或機制。
簡單來說:**因為`js`是單線程的,瀏覽器遇到`setTimeout`或者`setInterval`會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的待執行事件隊列里面,等到瀏覽器執行完當前代碼之后會看一下事件隊列里面有沒有任務,有的話才執行定時器的代碼。**?所以即使把定時器的時間設置為`0`還是會先執行當前的一些代碼。
### 一個關于`setTimeout`的筆試題
~~~javascript
console.log(1)
setTimeout(function () {
console.log(2)
},0)
console.log(3)
setTimeout(function () {
console.log(4)
},1000)
console.log(5)
//結果為 1 3 5 2 4
~~~

### 前段使用異步的場景
1、定時任務:`setTimeout,setInverval`
2、網絡請求:`ajax`請求,動態`<img>`加載
3、事件綁定
## 數組和對象`API`
### map與forEach的區別?
1、`forEach`方法,是最基本的方法,就是遍歷與循環,默認有3個傳參:分別是遍歷的數組內容`item`、數組索引`index`、和當前遍歷數組`Array`
2、`map`方法,基本用法與`forEach`一致,但是不同的,它會返回一個新的數組,所以在`callback`需要有`return`值,如果沒有,會返回`undefined`
### JS 數組和對象的遍歷方式,以及幾種方式的比較
通常我們會用循環的方式來遍歷數組。但是循環是 導致js 性能問題的原因之一。一般我們會采用下幾種方式來進行數組的遍歷
`for in循環`
`for循環`
`forEach`
1、這里的?`forEach`回調中兩個參數分別為?`value,index`
2、`forEach`?無法遍歷對象
3、`IE`不支持該方法;`Firefox`?和?`chrome`?支持
4、`forEach`?無法使用?`break,continue`?跳出循環,且使用?`return`?是跳過本次循環
5、可以添加第二個參數,為一個數組,回調中的this會指向這個數組,若沒有添加,則是指向`window`;
在方式一中,`for-in`需要分析出`array`的每個屬性,這個操作性能開銷很大。用在?`key`?已知的數組上是非常不劃算的。所以盡量不要用`for-in`,除非你不清楚要處理哪些屬性,例如`JSON`對象這樣的情況
在方式2中,循環每進行一次,就要檢查一下數組長度。讀取屬性(數組長度)要比讀局部變量慢,尤其是當`array`里存放的都是?`DOM`元素,因為每次讀取都會掃描一遍頁面上的選擇器相關元素,速度會大大降低
### 寫一個能遍歷對象和數組的通用`forEach`函數
~~~javascript
function forEach(obj, fn) {
var key
// 判斷類型
if (obj instanceof Array) {
obj.forEach(function (item, index) {
fn(index, item)
})
} else {
for (key in obj) {
fn ( key, obj[key])
}
}
}
~~~

### 數組`API`
~~~javascript
map: 遍歷數組,返回回調返回值組成的新數組
forEach: 無法break,可以用try/catch中throw new Error來停止
filter: 過濾
some: 有一項返回true,則整體為true
every: 有一項返回false,則整體為false
join: 通過指定連接符生成字符串
push / pop: 末尾推入和彈出,改變原數組, 返回推入/彈出項【有誤】
unshift / shift: 頭部推入和彈出,改變原數組,返回操作項【有誤】
sort(fn) / reverse: 排序與反轉,改變原數組
concat: 連接數組,不影響原數組, 淺拷貝
slice(start, end): 返回截斷后的新數組,不改變原數組
splice(start, number, value...): 返回刪除元素組成的數組,value 為插入項,改變原數組
indexOf / lastIndexOf(value, fromIndex): 查找數組項,返回對應的下標
reduce / reduceRight(fn(prev, cur), defaultPrev): 兩兩執行,prev 為上次化簡函數的return值,cur 為當前值(從第二項開始)
~~~

### 對象`API`
~~~javascript
var obj = {
x: 100,
y: 200,
z: 300
}
var key
for (key in obj) {
// 注意這里的 hasOwnProperty,再講原型鏈的時候講過
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key])
}
}
~~~

## 日期和隨機數
### 獲取`2020-06-10`格式的日期
~~~javascript
Date.now() // 獲取當前時間毫秒數
var dt = new Date()
dt.getTime() //獲取毫秒數
dt.getFullYear() // 年
dt.getMonth() // 月 (0-11)
dt.getDate() // 日 (0-31)
dt.getHours() // 小時 (0-23)
dt.getMinutes() //分鐘 (0-59)
dt.getSeconds() //秒 (0-59)
~~~

ps:?`ES6`?新增`padStart(),padEnd()`方法可用來返回`06`格式日期
### 獲取隨機數,要求是長度一致的字符串格式
~~~javascript
var random = Math.random()
var random = random + '0000000000'
console.log(random)
var random = random.slice(0, 10)
console.log(random)
~~~

## `DOM`和`BOM`操作
### `DOM`是哪種基本的數據結構
`DOM`是一種樹形結構的數據結構
### `DOM`操作的常用`API`有哪些
1、獲取`DOM`節點,以及節點的`property`和`Attribute`
2、獲取父節點,獲取子節點
3、新增節點,刪除節點
### `DOM`節點的`Attribute`和`property`有何區別
`property`只是一個`JS`對象的屬性修改和獲取
`Attribute`是對`html`標簽屬性的修改和獲取
### `DOM`的節點操作
**創建新節點**
~~~javascript
createDocumentFragment() //創建一個DOM片段
createElement() //創建一個具體的元素
createTextNode() //創建一個文本節點
~~~

**添加、移除、替換、插入**
~~~
appendChild() //添加
removeChild() //移除
replaceChild() //替換
insertBefore() //插入
~~~

**查找**
~~~javascript
getElementsByTagName() //通過標簽名稱
getElementsByName() //通過元素的Name屬性的值
getElementById() //通過元素Id,唯一性
~~~

**如何檢測瀏覽器的類型**
可以通過檢測`navigator.userAgent`?在通過不通瀏覽器的不通來檢測
~~~javascript
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
~~~

**拆解`url`的各部分**
使用`location`里面的`location.href location.protocol location.pathname location.search location.hash`來獲取各種參數
~~~javascript
console.log(location.href)
console.log(location.host) //域名
console.log(location.protocol)
console.log(location.pathname) //路徑
console.log(location.search) // 參數
console.log(location.hash)
// history
history.back()
history.forward()
~~~

## 事件機制
### 請解釋什么是事件代理
事件代理(`Event Delegation`),又稱之為事件委托。是?`JavaScript`?中常用綁定事件的常用技巧。顧名思義,“事件代理”即是把原本需要綁定的事件委托給父元素,讓父元素擔當事件監聽的職務。事件代理的原理是`DOM`元素的事件冒泡。
**使用事件代理的好處是:**
1、可以提高性能
2、可以大量節省內存占用,減少事件注冊,比如在`table`上代理所有`td`的`click`事件就非常棒
3、可以實現當新增子對象時無需再次對其綁定
### 事件模型
`W3C`中定義事件的發生經歷三個階段:`捕獲階段(capturing)、目標階段(targetin)、冒泡階段(bubbling)`
`冒泡型事件:`當你使用事件冒泡時,子級元素先觸發,父級元素后觸發
`捕獲型事件:`當你使用事件捕獲時,父級元素先觸發,子級元素后觸發
`DOM事件流:`<時支持兩種事件模型:捕獲型事件和冒泡型事件
`阻止冒泡:`在`W3c`中,使用`stopPropagation()`方法;在`IE`下設置`cancelBubble = true`
`阻止捕獲:`阻止事件的默認行為,例如`click - <a>后`的跳轉。在`W3c`中,使用`preventDefault()`方法,在`IE`下設置`window.event.returnValue = false`
### 編寫一個通用的事件監聽函數
~~~javascript
function bindEvent(elem,type,selector,fn){
if(fn==null){
fn = selector;
selector = null
}
elem.addEventListener(type,function(e){
var target;
if(selector){
target = e.target;
if(target.matches(selector)){
fn.call(target,e)
}
}else{
fn(e)
}
})
}
~~~

### 描述事件冒泡流程
當給某元素綁定一個事件的時候,首先會觸發自己綁定的,然后會逐層向上級查找事件,這就是事件冒泡
~~~javascript
var p1 = document.getElementById('p1')
var body = document.body
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
bindEvent(p1, 'click', function (e) {
e.stopPropagation()
var target = e.target
alert("激活")
})
bindEvent(body, 'click', function (e) {
var target = e.target
alert("取消")
})
~~~

### 對于一個無限下拉加載圖片的頁面,如何給每個圖片綁定事件
可以使用代理,通過對父級元素綁定一個事件,通過判斷事件的`target`屬性來進行判斷,添加行為
~~~javascript
var div1 = document.getElementById('div1')
var div2 = document.getElementById('div2')
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
bindEvent(div1, 'click', function (e) {
var target = e.target
alert(target.innerHTML)
})
bindEvent(div2, 'click', function (e) {
var target = e.target
alert(target.innerHTML)
})
~~~

## AJAX
### `Ajax`原理
`Ajax`的原理簡單來說是在用戶和服務器之間加了—個中間層(`AJAX`引擎),通過`XmlHttpRequest`對象來向服務器發異步請求,從服務器獲得數據,然后用`javascript`來操作`DOM`而更新頁面。使用戶操作與服務器響應異步化。這其中最關鍵的一步就是從服務器獲得請求數據
`Ajax`的過程只涉及`JavaScript、XMLHttpRequest和DOM`。`XMLHttpRequest`是ajax的核心機制
### 手動編寫一個`ajax`,不依賴第三方庫
~~~javascript
// 1. 創建連接
var xhr = null;
xhr = new XMLHttpRequest()
// 2. 連接服務器
xhr.open('get', url, true)
// 3. 發送請求
xhr.send(null);
// 4. 接受請求
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}
~~~

`ajax`的優點和缺點
`ajax的優點`
1、無刷新更新數據(在不刷新整個頁面的情況下維持與服務器通信)
2、異步與服務器通信(使用異步的方式與服務器通信,不打斷用戶的操作)
3、前端和后端負載均衡(將一些后端的工作交給前端,減少服務器與寬度的負擔)
4、界面和應用相分離(`ajax`將界面和應用分離也就是數據與呈現相分離)
`ajax的缺點`
1、ajax不支持瀏覽器back按鈕
2、安全問題?`Aajax`暴露了與服務器交互的細節
3、對搜索引擎的支持比較弱
4、破壞了`Back`與`History`后退按鈕的正常行為等瀏覽器機制。
### 什么情況下會碰到跨域問題?有哪些解決方法?
跨域問題是這是瀏覽器為了安全實施的同源策略導致的,同源策略限制了來自不同源的`document、腳本`,同源的意思就是兩個`URL`的域名、協議、端口要完全相同。
`script`標簽`jsonp`跨域、`nginx`反向代理、`node.js`中間件代理跨域、后端在頭部信息設置安全域名、后端在服務器上設置`cors`。
ps:有三個標簽允許跨域加載資源:?`<img src=xxx> <link href = xxx> <script src=xxx>`
三個標簽場景:
`<img>`?用于打點統計,統計網站可能是其他域
`<link> <script>`?可以使用`CDN`,`CDN`的也是其他域
`<script>`可以用于JSONP
**??跨域注意事項**
所有的跨域請求都必須經過信息提供方允許
如果未經允許即可獲取,那么瀏覽器同源策略出現漏洞
### `XML`和`JSON`的區別?
**數據體積方面**
`JSON`相對于`XML`來講,數據的體積小,傳遞的速度更快些。
**數據交互方面**
`JSON`與`JavaScript`的交互更加方便,更容易解析處理,更好的數據交互
**數據描述方面**
`JSON`對數據的描述性比`XML`較差
**傳輸速度方面**
`JSON`的速度要遠遠快于`XML`
### `get`和`post`的區別
1、`get`和`post`在`HTTP`中都代表著請求數據,其中get請求相對來說更簡單、快速,效率高些
2、`get`相對`post`安全性低
3、`get`有緩存,`post`沒有
4、`get`體積小,`post`可以無限大
5、`get`的`url`參數可見,`post`不可見
6、`get`只接受`ASCII`字符的參數數據類型,`post`沒有限制
7、`get`請求參數會保留歷史記錄,`post`中參數不會保留
8、`get`會被瀏覽器主動`catch,post`不會,需要手動設置
9、`get`在瀏覽器回退時無害,`post`會再次提交請求
什么時候使用`post`?
**`post`一般用于修改服務器上的資源,對所發送的信息沒有限制。比如**
1、無法使用緩存文件(更新服務器上的文件或數據庫)
2、向服務器發送大量數據(`POST`?沒有數據量限制)
3、發送包含未知字符的用戶輸入時,`POST`?比?`GET`?更穩定也更可靠
### `TCP`建立連接為什么需要三次?
* 為了實現可靠數據傳輸,?`TCP`?協議的通信雙方, 都必須維護一個序列號, 以標識發送出去的數據包中, 哪些是已經被對方收到的。 三次握手的過程即是通信雙方相互告知序列號起始值, 并確認對方已經收到了序列號起始值的必經步驟
* 如果只是兩次握手, 至多只有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認
## ES6
### `ES5`的繼承和`ES6`的繼承有什么區別?
`ES5`的繼承時通過`prototype`或構造函數機制來實現。**`ES5`的繼承實質上是先創建子類的實例對象,然后再將父類的方法添加到`this`上**(`Parent.apply(this)`)。
`ES6`的繼承機制完全不同,**實質上是先創建父類的實例對象`this`(所以必須先調用父類的`super()`方法),然后再用子類的構造函數修改`this`**。
具體的:`ES6`通過`class`關鍵字定義類,里面有構造方法,類之間通過`extends`關鍵字實現繼承。子類必須在`constructor`方法中調用`super`方法,否則新建實例報錯。因為子類沒有自己的`this`對象,而是繼承了父類的`this`對象,然后對其進行加工。如果不調用`super`方法,子類得不到`this`對象。
ps:`super`關鍵字指代父類的實例,即父類的`this`對象。在子類構造函數中,調用`super`后,才可使用`this`關鍵字,否則報錯
### `javascript`如何實現繼承?
1、`構造繼承`
2、`原型繼承`
3、`實例繼承`
4、`拷貝繼承`
`原型prototype`機制或`apply和call`方法去實現較簡單,建議使用構造函數與原型混合方式
~~~javascript
function Parent(){
this.name = 'wang';
}
function Child(){
this.age = 28;
}
Child.prototype = new Parent();//繼承了Parent,通過原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被繼承的屬性
}
~~~

### 談談你對`ES6`的理解
新增模板字符串(為`JavaScript`提供了簡單的字符串插值功能)
箭頭函數
`for-of`(用來遍歷數據—例如數組中的值。)
`arguments`對象可被不定參數和默認參數完美代替。
`ES6`將`promise`對象納入規范,提供了原生的`Promise`對象。
增加了`let和const`命令,用來聲明變量。
增加了塊級作用域。
`let`命令實際上就增加了塊級作用域。
還有就是引入`module`模塊的概念
### 談一談箭頭函數與普通函數的區別?
* 函數體內的`this`對象,就是定義時所在的對象,而不是使用時所在的對象
* 不可以當作構造函數,也就是說,不可以使用`new`命令,否則會拋出一個錯誤
* 不可以使用`arguments`對象,該對象在函數體內不存在。如果要用,可以用`Rest`參數代替
* 不可以使用`yield`命令,因此箭頭函數不能用作`Generator`函數
### `forEach、for in、for of`三者區別
`forEach`更多的用來遍歷數
`for in`一般常用來遍歷對象或`json`
`for of`數組對象都可以遍歷,遍歷對象需要通過和`Object.keys()`
`for in`循環出的是`key,for of`循環出的是`value`
### `Set、Map`的區別
應用場景`Set`用于數據重組,Map用于數據儲存
**`Set:`**
1,成員不能重復
2,只有鍵值沒有鍵名,類似數組
3,可以遍歷,方法有`add, delete,has`
**`Map:`**
1,本質上是健值對的集合,類似集合
2,可以遍歷,可以跟各種數據格式轉換
### `promise`對象的用法,手寫一個`promise`
`promise`是一個構造函數,下面是一個簡單實例
~~~javascript
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
~~~

### 請描述一下`Promise`的使用場景,'`Promise`'它所解決的問題以及現在對于異步操作的解決方案。
`Promise`的使用場景:`ajax`請求,回調函數,復雜操作判斷。
`Promise`是`ES6`為了解決異步編程所誕生的。
異步操作解決方案:`Promise、Generator`、定時器(不知道算不算)、還有`ES7`的`async`
### `ECMAScript6`?怎么寫`class`,為什么會出現`class`這種東西?
這個語法糖可以讓有`OOP`基礎的人更快上手`js`,至少是一個官方的實現了 但對熟悉`js`的人來說,這個東西沒啥大影響;一個`Object.creat()`搞定繼承,比`class`簡潔清晰的多
## 算法和其他
### 冒泡排序
每次比較相鄰的兩個數,如果后一個比前一個小,換位置
~~~javascript
var arr = [3, 1, 4, 6, 5, 7, 2];
function bubbleSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
var temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
console.log(bubbleSort(arr));
~~~

### 快速排序
采用二分法,取出中間數,數組每次和中間數比較,小的放到左邊,大的放到右邊
~~~javascript
var arr = [3, 1, 4, 6, 5, 7, 2];
function quickSort(arr) {
if(arr.length == 0) {
return []; // 返回空數組
}
var cIndex = Math.floor(arr.length / 2);
var c = arr.splice(cIndex, 1);
var l = [];
var r = [];
for (var i = 0; i < arr.length; i++) {
if(arr[i] < c) {
l.push(arr[i]);
} else {
r.push(arr[i]);
}
}
return quickSort(l).concat(c, quickSort(r));
}
console.log(quickSort(arr));
~~~

### 懶加載
~~~javascript
<img id="img1" src="preview.png" data-realsrc = "abc.png"/>
<script type = "text/javascript">
var img1 = document.getElementById("img1")
img1.src = img1.getAttribute('data-realsrc')
</script>
~~~

### 緩存`DOM`查詢
~~~javascript
// 未緩存 DOM查詢
var i
for (i = 0; i < document.getElementByTagName('p').length; i++) {
//todo
}
//緩存了DOM查詢
var pList = document.getElementByTagName('p')
var i
for (i= 0; i < pList.length; i++) {
// todo
}
~~~

### 合并`DOM`插入
~~~javascript
var listNode = document.getElementById('list')
//要插入10個li標簽
var frag = document.createDocumentFragment();
var x,li
for (x = 0; x < 10; x++) {
li = document.createElement('li')
li.innerHTML = "List item" + x
frag.appendChild(li)
}
listNode.appendChild(frag)
~~~

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

### 盡早操作
~~~javascript
window.addEventListener('load', function () {
//頁面的全部資源加載完才會去執行,包括圖片,視頻
})
document.addEventListener('DOMContentLoaded', function() {
//DOM 渲染完即可執行,此時圖片,視頻還可能沒有加載完成
})
~~~

### 淺拷貝
首先可以通過?`Object.assign`?來解決這個問題
~~~
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
~~~

### 深拷貝
這個問題通常可以通過`JSON.parse(JSON.stringify(object))`?來解決
~~~javascript
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
~~~

### 數組降維
~~~javascript
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
~~~

如果想將一個多維數組徹底的降維,可以這樣實現
~~~javascript
const flattenDeep = (arr) => Array.isArray(arr)
? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
: [arr]
flattenDeep([1, [[2], [3, [4]], 5]])
~~~

### 預加載
在開發中,可能會遇到這樣的情況。有些資源不需要馬上用到,但是希望盡早獲取,這時候就可以使用預加載 預加載其實是聲明式的?`fetch`,強制瀏覽器請求資源,并且不會阻塞?`onload`?事件,可以使用以下代碼開啟預加載
~~~
<link rel="preload" href="http://example.com">
復制代碼
~~~

預加載可以一定程度上降低首屏的加載時間,因為可以將一些不影響首屏但重要的文件延后加載,唯一缺點就是兼容性不好
### 預渲染
可以通過預渲染將下載的文件預先在后臺渲染,可以使用以下代碼開啟預渲染
~~~
<link rel="prerender" href="http://poetries.com">
~~~

## 性能優化
### JavaScript性能優化
~~~
1、盡可能把 <script> 標簽放在 body 之后,避免 JS 的執行卡住 DOM 的渲染,最大程度保證頁面盡快地展示出來
2、盡可能合并 JS 代碼:提取公共方法,進行面向對象設計等……
3、CSS 能做的事情,盡量不用 JS 來做,畢竟 JS 的解析執行比較粗暴,而 CSS 效率更高。
4、盡可能逐條操作 DOM,并預定好 CSs 樣式,從而減少 reflow 或者 repaint 的次數。
5、盡可能少地創建 DOM,而是在 HTML 和 CSS 中使用 display: none 來隱藏,按需顯示。
6、壓縮文件大小,減少資源下載負擔。
~~~

### JavaScript幾條基本規范
~~~
1、不要在同一行聲明多個變量
2、請使用===/!==來比較true/false或者數值
3、使用對象字面量替代new Array這種形式
4、不要使用全局變量
5、Switch語句必須帶有default分支
6、函數不應該有時候有返回值,有時候沒有返回值
7、For循環必須使用大括號
8、IF語句必須使用大括號
9、for-in循環中的變量 應該使用var關鍵字明確限定作用域,從而避免作用域污染
~~~

- 吳小瓊每日10題
- 小程序常見面試題
- Js常見面試題
- HTML+CSS常見面試題
- 03.24
- 【01】談談Vue中的$.nextTick的理解
- 【02】請書寫css 萬能浮動清除法
- 【03】HTML5里的video標簽支持哪些視頻格式?
- 【04】請書寫透明度opacity的IE兼容寫法
- 【05】簡述原生ajax請求過程,get和post的區別
- 【06】new操作符具體干了什么呢?
- 【07】請手寫冒泡排序
- 【08】微信小程序有哪些事件及頁面傳參的方法?
- 【09】vue的路由hash模式 和 history模式 區別
- 【10】vue路由的鉤子函數有哪些,什么情況用
- 03.25
- 【01】CSS3有哪些新特性?
- 【02】HTML5有哪些新特性
- 【03】列舉IE和標準下有哪些JS兼容性的寫法
- 【04】談談你對原型鏈的理解
- 【05】DOM怎樣添加、移除、移動、復制、創建和查找節點
- 【06】Vue的生命周期,第一次加載會觸發哪些鉤子函數
- 【07】談一談小程序的生命周期及其區別
- 【08】簡述小程序幾種頁面跳轉方式的區別
- 【09】Vue實現數據雙向綁定的原理是什么
- 【10】vuex是什么?怎么使用?哪種功能場景使用它?
- 03.26
- 【01】請闡述import和require的區別
- 【02】export與export default 的區別
- 【03】微信小程序主包和分包區別
- 【04】小程序解析富文本有哪些方式,以及區別
- 【05】$(document).ready和window.onload的區別?
- 【06】請分別用ES5和ES6實現數組去重
- 【07】vue編程式的導航跳轉傳參的方式有哪些?
- 【08】判斷數據類型的方法有哪些,有什么區別
- 【09】less和sass的特點和區別
- 【10】如何去掉inline-block元素之間的間隙
- 03.27
- 【01】端口號的取值范圍是多少?
- 【02】cookie、sessionStorage和localStorage區別?
- 【03】數組的常用方法有那些?
- 【04】正則驗證,match()與test()函數的區別?
- 【05】Vue組件通信 父子、兄弟、非父子
- 【06】vue 為什么采用Virtual DOM?
- 【07】如何解決Vue 數組/對象更新 視圖不更新的情況
- 【08】例舉3種強制類型轉換和2種隱式類型轉換
- 【09】計算1-10000中出現的0 的次數
- 【10】計算字串中每個字符出現的次數