[toc]
## JavaScript
### JS中的8種數據類型及區別
包括值類型(基本對象類型)和引用類型(復雜對象類型)
**基本類型(值類型):** Number(數字),String(字符串),Boolean(布爾),Symbol(符號),null(空),undefined(未定義)在內存中占據固定大小,保存在棧內存中
**引用類型(復雜數據類型):** Object(對象)、Function(函數)。其他還有Array(數組)、Date(日期)、RegExp(正則表達式)、特殊的基本包裝類型(String、Number、Boolean) 以及單體內置對象(Global、Math)等 引用類型的值是對象 保存在堆內存中,棧內存存儲的是對象的變量標識符以及對象在堆內存中的存儲地址。
**使用場景:**
Symbol:使用Symbol來作為對象屬性名(key) 利用該特性,把一些不需要對外操作和訪問的屬性使用Symbol來定義
BigInt:由于在 Number 與 BigInt 之間進行轉換會損失精度,因而建議僅在值可能大于253 時使用 BigInt 類型,并且不在兩種類型之間進行相互轉換。
傳送門 ?[# JavaScript 數據類型之 Symbol、BigInt](https://juejin.cn/post/7000754813801775111)
### JS中的數據類型檢測方案
#### 1.typeof
```js
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'mc'); // string
console.log(typeof Symbol) // function
console.log(typeof function(){}); // function
console.log(typeof console.log()); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
```
優點:能夠快速區分基本數據類型
缺點:不能將Object、Array和Null區分,都返回object
#### 2.instanceof
```js
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
```
優點:能夠區分Array、Object和Function,適合用于判斷自定義的類實例對象
缺點:Number,Boolean,String基本數據類型不能判斷
#### 3.Object.prototype.toString.call()
```js
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call('mc')); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function(){})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
```
優點:精準判斷數據類型
缺點:寫法繁瑣不容易記,推薦進行封裝后使用
#### instanceof 的作用
用于判斷一個引用類型是否屬于某構造函數;
還可以在繼承關系中用來判斷一個實例是否屬于它的父類型。
#### instanceof 和 typeof 的區別:
typeof在對值類型number、string、boolean 、null 、 undefined、 以及引用類型的function的反應是精準的;但是,對于對象{ } 、數組[ ] 、null 都會返回object
為了彌補這一點,instanceof 從原型的角度,來判斷某引用屬于哪個構造函數,從而判定它的數據類型。
### var && let && const
ES6之前創建變量用的是var,之后創建變量用的是let/const
**三者區別**:
1. var定義的變量,`沒有塊的概念,可以跨塊訪問`, 不能跨函數訪問。\
let定義的變量,只能在塊作用域里訪問,不能跨塊訪問,也不能跨函數訪問。\
const用來定義常量,使用時必須初始化(即必須賦值),只能在塊作用域里訪問,且不能修改。
1. var可以`先使用,后聲明`,因為存在變量提升;let必須先聲明后使用。
1. var是允許在相同作用域內`重復聲明同一個變量`的,而let與const不允許這一現象。
1. 在全局上下文中,基于let聲明的全局變量和全局對象GO(window)沒有任何關系 ;\
var聲明的變量會和GO有映射關系;
1. `會產生暫時性死區`:
> 暫時性死區是瀏覽器的bug:檢測一個未被聲明的變量類型時,不會報錯,會返回undefined\
> 如:console.log(typeof a) //undefined\
> 而:console.log(typeof a)//未聲明之前不能使用\
> let a
6. let /const/function會把當前所在的大括號(除函數之外)作為一個全新的塊級上下文,應用這個機制,在開發項目的時候,遇到循環事件綁定等類似的需求,無需再自己構建閉包來存儲,只要基于let的塊作用特征即可解決
### 作用域和作用域鏈
創建函數的時候,已經聲明了當前函數的作用域==>`當前創建函數所處的上下文`。如果是在全局下創建的函數就是`[[scope]]:EC(G)`,函數執行的時候,形成一個全新的私有上下文`EC(FN)`,供字符串代碼執行(進棧執行)
定義:簡單來說作用域就是變量與函數的可訪問范圍,`由當前環境與上層環境的一系列變量對象組成`\
1.全局作用域:代碼在程序的任何地方都能被訪問,window 對象的內置屬性都擁有全局作用域。\
2.函數作用域:在固定的代碼片段才能被訪問
作用:作用域最大的用處就是`隔離變量`,不同作用域下同名變量不會有沖突。
**作用域鏈參考鏈接**一般情況下,變量到 創建該變量 的函數的作用域中取值。但是如果在當前作用域中沒有查到,就會向上級作用域去查,直到查到全局作用域,這么一個查找過程形成的鏈條就叫做作用域鏈。
### 閉包的兩大作用:保存/保護
- **閉包的概念**
函數執行時形成的私有上下文EC(FN),正常情況下,代碼執行完會出棧后釋放;但是特殊情況下,如果當前私有上下文中的某個東西被上下文以外的事物占用了,則上下文不會出棧釋放,從而形成不銷毀的上下文。 函數執行函數執行過程中,會形成一個全新的私有上下文,可能會被釋放,可能不會被釋放,不論釋放與否,他的作用是:
(1)保護:劃分一個獨立的代碼執行區域,在這個區域中有自己私有變量存儲的空間,保護自己的私有變量不受外界干擾(操作自己的私有變量和外界沒有關系);
(2)保存:如果當前上下文不被釋放【只要上下文中的某個東西被外部占用即可】,則存儲的這些私有變量也不會被釋放,可以供其下級上下文中調取使用,相當于把一些值保存起來了;
我們把函數執行形成私有上下文,來保護和保存私有變量機制稱為`閉包`。
> 閉包是指有權訪問另一個函數作用域中的變量的函數--《JavaScript高級程序設計》
**稍全面的回答**: 在js中變量的作用域屬于函數作用域, 在函數執行完后,作用域就會被清理,內存也會隨之被回收,但是由于閉包函數是建立在函數內部的子函數, 由于其可訪問上級作用域,即使上級函數執行完, 作用域也不會隨之銷毀, 這時的子函數(也就是閉包),便擁有了訪問上級作用域中變量的權限,即使上級函數執行完后作用域內的值也不會被銷毀。
- **閉包的特性**:
- 1、內部函數可以訪問定義他們外部函數的參數和變量。(作用域鏈的向上查找,把外圍的作用域中的變量值存儲在內存中而不是在函數調用完畢后銷毀)設計私有的方法和變量,避免全局變量的污染。
1.1.閉包是密閉的容器,,類似于set、map容器,存儲數據的
1.2.閉包是一個對象,存放數據的格式為 key-value 形式
- 2、函數嵌套函數
- 3、本質是將函數內部和外部連接起來。優點是可以讀取函數內部的變量,讓這些變量的值始終保存在內存中,不會在函數被調用之后自動清除
- **閉包形成的條件**:
1. 函數的嵌套
1. 內部函數引用外部函數的局部變量,延長外部函數的變量生命周期
- **閉包的用途**:
1. 模仿塊級作用域
1. 保護外部函數的變量 能夠訪問函數定義時所在的詞法作用域(阻止其被回收)
1. 封裝私有化變量
1. 創建模塊
- **閉包應用場景**
閉包的兩個場景,閉包的兩大作用:`保存/保護`。 在開發中, 其實我們隨處可見閉包的身影, 大部分前端JavaScript 代碼都是“事件驅動”的,即一個事件綁定的回調方法; 發送ajax請求成功|失敗的回調;setTimeout的延時回調;或者一個函數內部返回另一個匿名函數,這些都是閉包的應用。
- **閉包的優點**:延長局部變量的生命周期
- **閉包缺點**:會導致函數的變量一直保存在內存中,過多的閉包可能會導致內存泄漏
### JS 中 this 的情況
1. 普通函數調用:通過函數名()直接調用:`this`指向`全局對象window`(注意let定義的變量不是window屬性,只有window.xxx定義的才是。即let a =’aaa’; this.a是undefined)
2. 構造函數調用:函數作為構造函數,用new關鍵字調用時:`this`指向`新new出的對象`
3. 對象函數調用:通過對象.函數名()調用的:`this`指向`這個對象`
4. 箭頭函數調用:箭頭函數里面沒有 this ,所以`永遠是上層作用域this`(上下文)
5. apply和call調用:函數體內 this 的指向的是 call/apply 方法`第一個參數`,若為空默認是指向全局對象window。
6. 函數作為數組的一個元素,通過數組下標調用的:this指向這個數組
7. 函數作為window內置函數的回調函數調用:this指向window(如setInterval setTimeout 等)
### call/apply/bind 的區別
相同:
1、都是用來改變函數的this對象的指向的。\
2、第一個參數都是this要指向的對象。\
3、都可以利用后續參數傳參。
不同:
apply和call傳入的參數列表形式不同。apply 接收 arguments,call接收一串參數列表
```
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
```
bind:語法和call一模一樣,區別在于立即執行還是等待執行,bind不兼容IE6~8
bind 主要就是將函數綁定到某個對象,bind()會創建一個函數,返回對應函數便于稍后調用;而apply、call則是立即調用。
總結:基于Function.prototype上的 ` apply 、 call 和 bind `調用模式,這三個方法都可以顯示的指定調用函數的 this 指向。`apply`接收參數的是數組,`call`接受參數列表,`` bind`方法通過傳入一個對象,返回一個` this ` 綁定了傳入對象的新函數。這個函數的 `this`指向除了使用`new `時會被改變,其他情況下都不會改變。若為空默認是指向全局對象window。
參考:? [call、apply、bind三者的用法和區別](https://blog.csdn.net/hexinyu_1022/article/details/82795517)
### 箭頭函數的特性
1. `箭頭函數沒有自己的this`,會捕獲其所在的上下文的this值,作為自己的this值
1. `箭頭函數沒有constructor`,是匿名函數,不能作為構造函數,不能通過new 調用;
1. `沒有new.target 屬性`。在通過new運算符被初始化的函數或構造方法中,new.target返回一個指向構造方法或函數的引用。在普通的函數調用中,new.target 的值是undefined
1. `箭頭函數不綁定Arguments 對象`。取而代之用rest參數...解決。由于 箭頭函數沒有自己的this指針,通過 call() 或 apply() 方法調用一個函數時,只能傳遞參數(不能綁定this),他們的第一個參數會被忽略。(這種現象對于bind方法同樣成立)
1. 箭頭函數通過 call() 或 apply() 方法調用一個函數時,只傳入了一個參數,對 this 并沒有影響。
1. 箭頭函數沒有原型屬性 Fn.prototype 值為 undefined
1. 箭頭函數不能當做Generator函數,不能使用yield關鍵字
參考:[箭頭函數與普通函數的區別](https://www.cnblogs.com/biubiuxixiya/p/8610594.html)
### 原型 && 原型鏈
**原型關系:**
- 每個 class都有顯示原型 prototype
- 每個實例都有隱式原型 `__proto__`
- 實例的 `__proto__` 指向對應 class 的 prototype
? **原型:** ?在 JS 中,每當定義一個對象(函數也是對象)時,對象中都會包含一些預定義的屬性。其中每個`函數對象`都有一個`prototype`?屬性,這個屬性指向函數的`原型對象`。
原型鏈:函數的原型鏈對象constructor默認指向函數本身,原型對象除了有原型屬性外,為了實現繼承,還有一個原型鏈指針__proto__,該指針是指向上一層的原型對象,而上一層的原型對象的結構依然類似。因此可以利用__proto__一直指向Object的原型對象上,而Object原型對象用Object.prototype.__ proto__ = null表示原型鏈頂端。如此形成了js的原型鏈繼承。同時所有的js對象都有Object的基本防范
**特點:** ?`JavaScript`對象是通過引用來傳遞的,我們創建的每個新對象實體中并沒有一份屬于自己的原型副本。當我們修改原型時,與之相關的對象也會繼承這一改變。
### new運算符的實現機制
1. 首先創建了一個新的`空對象`
1. `設置原型`,將對象的原型設置為函數的`prototype`對象。
1. 讓函數的`this`指向這個對象,執行構造函數的代碼(為這個新對象添加屬性)
1. 判斷函數的返回值類型,如果是值類型,返回創建的對象。如果是引用類型,就返回這個引用類型的對象。
### EventLoop 事件循環
`JS`是單線程的,為了防止一個函數執行時間過長阻塞后面的代碼,所以會先將同步代碼壓入執行棧中,依次執行,將異步代碼推入異步隊列,異步隊列又分為宏任務隊列和微任務隊列,因為宏任務隊列的執行時間較長,所以微任務隊列要優先于宏任務隊列。微任務隊列的代表就是,`Promise.then`,`MutationObserver`,宏任務的話就是`setImmediate setTimeout setInterval`
JS運行的環境。一般為瀏覽器或者Node。 在瀏覽器環境中,有JS 引擎線程和渲染線程,且兩個線程互斥。 Node環境中,只有JS 線程。 不同環境執行機制有差異,不同任務進入不同Event Queue隊列。 當主程結束,先執行準備好微任務,然后再執行準備好的宏任務,一個輪詢結束。
#### **瀏覽器中的事件環(Event Loop)**
事件環的運行機制是,先會執行棧中的內容,棧中的內容執行后執行微任務,微任務清空后再執行宏任務,先取出一個宏任務,再去執行微任務,然后在取宏任務清微任務這樣不停的循環。
- eventLoop 是由JS的宿主環境(瀏覽器)來實現的;
- 事件循環可以簡單的描述為以下四個步驟:
1. 函數入棧,當Stack中執行到異步任務的時候,就將他丟給WebAPIs,接著執行同步任務,直到Stack為空;
1. 此期間WebAPIs完成這個事件,把回調函數放入隊列中等待執行(微任務放到微任務隊列,宏任務放到宏任務隊列)
1. 執行棧為空時,Event Loop把微任務隊列執行清空;
1. 微任務隊列清空后,進入宏任務隊列,取隊列的第一項任務放入Stack(棧)中執行,執行完成后,查看微任務隊列是否有任務,有的話,清空微任務隊列。重復4,繼續從宏任務中取任務執行,執行完成之后,繼續清空微任務,如此反復循環,直至清空所有的任務。

- 瀏覽器中的任務源(task):
- `宏任務(macrotask)`:\
宿主環境提供的,比如瀏覽器\
ajax、setTimeout、setInterval、setTmmediate(只兼容ie)、script、requestAnimationFrame、messageChannel、UI渲染、一些瀏覽器api
- `微任務(microtask)`:\
語言本身提供的,比如promise.then\
then、queueMicrotask(基于then)、mutationObserver(瀏覽器提供)、messageChannel 、mutationObersve
傳送門 ? [# 宏任務和微任務](https://juejin.cn/post/7001881781125251086)
#### **Node 環境中的事件環(Event Loop)**
`Node`是基于V8引擎的運行在服務端的`JavaScript`運行環境,在處理高并發、I/O密集(文件操作、網絡操作、數據庫操作等)場景有明顯的優勢。雖然用到也是V8引擎,但由于服務目的和環境不同,導致了它的API與原生JS有些區別,其Event Loop還要處理一些I/O,比如新的網絡連接等,所以Node的Event Loop(事件環機制)與瀏覽器的是不太一樣。
 執行順序如下:
- `timers`: 計時器,執行setTimeout和setInterval的回調
- `pending callbacks`: 執行延遲到下一個循環迭代的 I/O 回調
- `idle, prepare`: 隊列的移動,僅系統內部使用
- `poll輪詢`: 檢索新的 I/O 事件;執行與 I/O 相關的回調。事實上除了其他幾個階段處理的事情,其他幾乎所有的異步都在這個階段處理。
- `check`: 執行`setImmediate`回調,setImmediate在這里執行
- `close callbacks`: 執行`close`事件的`callback`,一些關閉的回調函數,如:socket.on('close', ...)
### setTimeout、Promise、Async/Await 的區別
1. setTimeout
settimeout的回調函數放到宏任務隊列里,等到執行棧清空以后執行。
1. Promise
Promise本身是**同步的立即執行函數**, 當在executor中執行resolve或者reject的時候, 此時是異步操作, 會先執行then/catch等,當主棧完成后,才會去調用resolve/reject中存放的方法執行。
```js
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout
```
1. async/await
async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操作完成,再執行函數體內后面的語句。可以理解為,是讓出了線程,跳出了 async 函數體。
```js
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 輸出順序:script start->async1 start->async2->script end->async1 end
```
傳送門 ? [# JavaScript Promise 專題](https://juejin.cn/post/6999651011304357925)
### Async/Await 如何通過同步的方式實現異步
Async/Await就是一個**自執行**的generate函數。利用generate函數的特性把異步的代碼寫成“同步”的形式,第一個請求的返回值作為后面一個請求的參數,其中每一個參數都是一個promise對象.
### 介紹節流防抖原理、區別以及應用
`節流`:事件觸發后,規定時間內,事件處理函數不能再次被調用。也就是說在規定的時間內,函數只能被調用一次,且是最先被觸發調用的那次。
`防抖`:多次觸發事件,事件處理函數只能執行一次,并且是在觸發操作結束時執行。也就是說,當一個事件被觸發準備執行事件函數前,會等待一定的時間(這時間是碼農自己去定義的,比如 1 秒),如果沒有再次被觸發,那么就執行,如果被觸發了,那就本次作廢,重新從新觸發的時間開始計算,并再次等待 1 秒,直到能最終執行!
`使用場景`:\
節流:滾動加載更多、搜索框搜的索聯想功能、高頻點擊、表單重復提交……\
防抖:搜索框搜索輸入,并在輸入完以后自動搜索、手機號,郵箱驗證輸入檢測、窗口大小 resize 變化后,再重新渲染。
```js
/**
* 節流函數 一個函數執行一次后,只有大于設定的執行周期才會執行第二次。有個需要頻繁觸發的函數,出于優化性能的角度,在規定時間內,只讓函數觸發的第一次生效,后面的不生效。
* @param fn要被節流的函數
* @param delay規定的時間
*/
function throttle(fn, delay) {
//記錄上一次函數觸發的時間
var lastTime = 0;
return function(){
//記錄當前函數觸發的時間
var nowTime = Date.now();
if(nowTime - lastTime > delay){
//修正this指向問題
fn.call(this);
//同步執行結束時間
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function () {
console.log('scllor事件被觸發了' + Date.now());
}, 200);
/**
* 防抖函數 一個需要頻繁觸發的函數,在規定時間內,只讓最后一次生效,前面的不生效
* @param fn要被節流的函數
* @param delay規定的時間
*/
function debounce(fn, delay) {
//記錄上一次的延時器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新設置新的延時器
timer = setTimeout(function(){
//修正this指向問題
fn.apply(this);
}, delay);
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按鈕被點擊了' + Date.now());
}, 1000);
```
- JavaScript
- 1. DOM事件流
- 2. 模擬 new, Object create(), bind
- 5. 封裝函數進行字符串駝峰命名的轉換
- 6. 什么是promise
- 7. 判斷一個數是否為數組
- 10. __proto__和prototype以及原型,原型鏈,構造函數
- 11. 繼承
- 12. 閉包
- 13. 回調函數
- 14. var 和 let 區別
- 15. this、bind、call、apply
- 16.undefined和null的區別
- 17.內存泄漏
- 18.垃圾回收機制
- html css
- 1. 元素垂直水平居中
- 2. 清除浮動
- 3. bootstrap柵格系統
- 4. px rpx em rem vw 的區別
- 5. 兩種盒子模型
- 6. 合集
- web類
- 1. html5的新特性以及理解(web標簽語義化)
- 2. 什么是路由,關于前端路由和后端路由
- 3. 對優質代碼的理解
- 4. cookie 和 sessionStorage和localStorage
- 5. 瀏覽器內核
- 6. http 狀態碼
- 7. href 和 src 的區別
- 8. link 和 @import 的區別
- 9. http 狀態碼
- 10. websocket
- 11. 瀏覽器解析url
- 12.http緩存
- vue
- 1.vue2和vue3有哪些區別
- 1. 對 mvvvm 的理解
- 2. mvvm的優缺點
- 3. 數據雙向綁定的原理
- 4. 生命周期
- 5. 組件如何通信
- 6. computed和watch的區別
- 7. proxy 和 Object.defineProperty
- 8. 虛擬dom和 diff算法
- 9. 路由的嵌套與傳參
- 10. 路由導航鉤子
- 11. axios 的理解
- 12. vue自定義指令 diretive
- 13. diff 的實現
- 14. 實現一個簡單的雙向綁定
- 15. 為什么 data 是一個函數
- 題譜
- js
- 手寫篇
- css
- vue
- react
- 算法
- 自我介紹
- 八股文
- 源項目地址
- 1.計算機網絡
- 2.瀏覽器
- 3.html和css
- 4.javascript
- 6.typescript
- 7.vue
- 8.react
- 大廠面試
- 面試題大全
- 常見性能優化
- 面試實戰
- 面試分析
- 押題
- 1.微前端在項目中的實際應用
- 2.性能優化
- vue相關
- 1.說一說HashRouter和HistoryRouter的區別和原理
- 無敵之路,牛客網面試題自測記錄
- 前端基礎
- 1.html
- 2.js基礎
- 珠峰性能優化
- WebWorker
- url到渲染
- 瀏覽器加載機制
- 自我介紹1
- 手寫題
- 1.compose
- 2.setTimeout模擬setInterval
- 3.手寫數組拍平
- 4.手寫promise.all
- 5.手寫深拷貝
- webpack
- 實戰