在最近的面試中,以及面試別人的過程當中,深深的瞭解到如今不少前端大佬們深深的被各類框架淹沒,或者在業務中沒法自拔。忽略了基礎,淪爲CV信徒,問題靠百度。不多去思考,或者去總結了,固然也包括我本身,因此決定夯實基礎,從新出發。html
第一節,講解數據類型,及其常規的檢測方法,數據存儲形式,垃圾回收,包括面試中經常要求的手寫的一些方法。前端
JS數據類型分爲兩大類:基本類型和引用類型。web
基本類型包含:string, number. boolean, undefined, null, symbol六種; 引用類型有:對象一種。面試
面試官:「那麼什麼是基本類型什麼是引用類型呢?」算法
小明:「基本類型是簡單的數據段,而引用類型是可能由多個值構成。segmentfault
面試官:「那麼簡單類型和引用類型是如何存儲的呢?」 小紅:」簡單類型按值訪問,存儲在內存棧中,引用類型,按引用訪問,地址存儲在棧中,內容存儲在堆中。因此但進行簡單類型複製的時候,複製的是一個值,而引用類型複製的時候複製的實際上是一個引用地址,因此就有了深拷貝和淺拷貝一說。「數組
面試官:「回答的不錯,下一題。」瀏覽器
使用typeof可以檢測出數據的類型,當檢測基本類型的時候很好用,可是沒法檢測出對象的具體類型,例如Date和RegExp.看下面的示例。數據結構
typeof '1' === 'string'
typeof 1 === 'number' typeof {} === 'object' typeof undefined === 'undefined' typeof null === 'object' // null被認爲是一個空對象 typeof Symbol(1) === 'symbol' typeof NaN === 'number' typeof Function === 'function' typeof true === 'boolean' 複製代碼
typeof可以返回的類型有以上幾種:string,number,object,function,undefined,symbol,boolean 七種,可是沒法知道是否是數組等。app
檢測數組提供了兩個操做符,isArray和instanceof。
Array.isArray() 用於肯定傳遞的值是不是一個 Array
Array.isArray([1,2,3]) // true
複製代碼
用於檢測 某個對象是不是某個構造函數的實例
[] instanceof Array // true
var a = {} a instanceof Object // true 複製代碼
可是檢測數組的時候仍是建議用isArray,由於某些場景下 instanceof並不能很好的起做用。
var iframe = document.createElement('iframe');
document.body.appendChild(iframe); xArray = window.frames[window.frames.length-1].Array; var arr = new xArray(1,2,3); // [1,2,3] Array.isArray(arr); // true arr instanceof Array; // false 複製代碼
Object.protptype.toString.call(obj)
使用這個方法可以準確的檢測出全部的類型。
Object.protptype.toString.call({}) === "[object Object]"
Object.protptype.toString.call([]) === "[object Array]" 複製代碼
爲何這樣子可以準確的檢測類型呢?由於Object的原型對象上有一個toString的方法,全部的其餘類型的數值都繼承了來自對象的toString方法。 可是因爲每一個類型都各自改寫了toString的方法,因此咱們必需要調用Ojeact.prototype上的toString方法。看下面👇:
[].toString() // 結果是 「」
Array.prototype.hasOwnProperty('toString') // true Number.prototype.hasOwnProperty('toString') // true String.prototype.hasOwnProperty('toString') // true 咱們再刪掉數組原型上的toString方法 delete Array.prototype.toString [].toString() // [object Array] Object.protptype.toString.call([]) === "[object Array]" 複製代碼
結果一致,由於刪掉了,數組原型上的toString他會根據原型鏈去查找對象上的toString方法。
實現一個檢測類型的方法:
function detectType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() } 複製代碼
js中有三個特殊對象。String,Boolean, Number,經過new關鍵字生成的都是屬於對象類型,若是用typeof檢測的話。平時開發的時候須要注意一下。
Number 類型應該是 ECMAScript 中最使人關注的數據類型了,這種類型使用 IEEE754 格式來表示 整數和浮點數值(浮點數值在某些語言中也被稱爲雙精度數值)。
什麼是IEEE754格式呢? 複製代碼說實話,我看不太懂,專業術語太多了,記住js浮點型數值採用的雙精度,即64位(8個字節) 複製代碼
因爲0.1和0.2轉換成二進制分別是:0.00011001100110011001100(1100無限循環) 0.001100110011001100110011(0011無限循環)。 js採用的是64位(8字節),因此會捨棄掉後面一些循環的部分,就致使了這類問題的出現。 tips: 不要用浮點數進行運算,除非你對精度沒有要求。
那麼如何解決浮點數計算的問題呢?你們應該都能想到,先轉換成整數,而後運算好之後再轉換成浮點數。話很少說,上代碼。
function transform (num1, num2, type) {
const typeList = ['add', 'min', 'multi', 'divide'] if (!num1 || !num2 || !type) { throw Error('num1 & num2 & type are required') } if (!typeList.includes(type)) { throw Error("There are four types can be accepted 'add', 'min', 'multi', 'divide'") } let maxLen = 0 let copyNum1 = 0 let copyNum2 = 0 let l = num1.toString().split('.').length > 2 ? num1.toString().split('.')[1].length : 0 let r = num2.toString().split('.').length > 2 ? num2.toString().split('.')[1].length : 0 maxLen = l < r ? r : l copyNum1 = floatToInt(num1, maxLen) copyNum2 = floatToInt(num2, maxLen) if (type === 'add') { return (copyNum2 + copyNum1) / Math.pow(10, maxLen) } else if (type === 'min') { return (copyNum1 - copyNum2) / Math.pow(10, maxLen) } else if (type === 'divide') { return copyNum1 / copyNum2 } else if (type === 'multi') { return (copyNum1 * copyNum2) / Math.pow(Math.pow(10, maxLen), 2) } } function floatToInt (num, len) { return num * Math.pow(10, len) } 複製代碼
JavaScript 具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程當中使用的內存。 而在 C 和 C++之類的語言中,開發人員的一項基本任務就是手工跟蹤內存的使用狀況,這是形成許多問 題的一個根源。在編寫 JavaScript 程序時,開發人員不用再關心內存使用問題,所需內存的分配以及無 用內存的回收徹底實現了自動管理。這種垃圾收集機制的原理其實很簡單:找出那些再也不繼續使用的變 量,而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間), 週期性地執行這一操做。 下面咱們來分析一下函數中局部變量的正常生命週期。局部變量只在函數執行的過程當中存在。而在 這個過程當中,會爲局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。而後在函數中使 用這些變量,直至函數執行結束。此時,局部變量就沒有存在的必要了,所以能夠釋放它們的內存以供 未來使用。在這種狀況下,很容易判斷變量是否還有存在的必要;但並不是全部狀況下都這麼容易就能得 出結論。垃圾收集器必須跟蹤哪一個變量有用哪一個變量沒用,對於再也不有用的變量打上標記,以備未來收 回其佔用的內存。用於標識無用變量的策略可能會因實現而異,但具體到瀏覽器中的實現,則一般有兩 個策略。
概念就是一旦沒有被引用就會被回收。可是存在一種特例就是循環引用的問題。這種狀況下就會出現沒法回收。致使內存泄漏。
var a = {
b: 1 } var c = { d: 2 } a.c = c c.a = a 複製代碼
此時a和c將得不到回收。最開始js的回收機制就是如此。此時只能手動清除
a = null c = null 複製代碼
設置爲null並不意味着立刻會清除。只會爲了將其移除執行環境,等待下次收集器回收。
標記清除算法是一種垃圾回收算法,它是第一個能夠回收被循環引用的數據結構的垃圾回收算法.如今仍舊有許多經常使用的垃圾回收技術使用各類各樣的標記清除算法的變體.
在使用標記清除算法時,未引用對象並不會被當即回收.取而代之的作法是,垃圾對象將一直累計到內存耗盡爲止.當內存耗盡時,程序將會被掛起,垃圾回收開始執行.當全部的未引用對象被清理完畢時,程序纔會繼續執行.
標記清除算法又被叫作追蹤式垃圾收集器,這是由於這種算法追蹤被程序所引用的全部對象.在程序中能夠直接訪問的對象是指經過堆棧上的本地變量或者任意靜態變量說引用的對象.從垃圾回收的角度來看,這種對象,叫作根(roots).若是一個對象是被另外的可(直接或者間接)訪問的對象中的域引用,則這個對象能夠被間接訪問.可訪問的對象被稱爲可用的(live),其餘的對象被稱爲垃圾(garbage).
這裏講幾個概念
簡單地說,「可達性」 值就是那些以某種方式可訪問或可用的值,它們被保證存儲在內存中。
1.全局變量 2.內部的 3.當前做用域鏈上的其餘變量或者函數 4.本地函數的局部變量和參數 這些稱之爲根
若是引用或者引用鏈可以在根中找到,那麼就說明這個值是可達的。
例如:
var a = {
b: 'xx' } var c= 1 a.b = c 複製代碼
window => a => b => a.b = c a在全局變量中,a中有個b屬性的值爲變量c,說明c在可以在根中找到,說明c是可達的。若是咱們讓 a = null,那麼在全局中a的引用就丟失了。此時變量c就是不可達的,會被移除執行環境等待被回收。 上圖:
切斷引用 a = null:
當切斷的時候,a對b的引用就丟失了,圓圈部分就變得不可達,就會被標記,等待被清除。
instanceof的原理是查詢是否在原型上出現過,出現過就返回true,不然爲false,思路很簡單,不斷獲取左邊的__proto__直到等於右邊的prototype,則返回true不然等於null的時候爲false
function myInstanceOf (left, right) {
const rightProto = right.prototype let leftVaule = left.__proto__ while (true) { if (leftVaule === null) { return false } else if (leftVaule === rightProto) { return true } leftVaule = leftVaule.__proto__ } } 複製代碼
不一樣的對象在底層都表示爲二進制,在Javascript中二進制前(低)三位存儲其類型信息。
000: 對象
010: 浮點數
100:字符串
110: 布爾
1: 整數
複製代碼
typeof null 爲"object", 緣由是由於 不一樣的對象在底層都表示爲二進制,在Javascript中二進制前(低)三位都爲0的話會被判斷爲Object類型,null的二進制表示全爲0,天然前三位也是0,因此執行typeof時會返回"object",當對象有"[[Call]]"方法的時候會返回function,不然就是object.
Object.assign()
Object.create() // 都是淺拷貝 複製代碼
1.常見的有JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
複製代碼
可是這裏有弊端,沒法解決循環引用的問題,以及時間類型的拷貝,會丟失掉時區。
2.第二種手寫一下,這裏進攻僅供參考。須要特殊處理的地方還有不少,好比ArrayBuffer,多緯數組,循環引用等問題。
function detectType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() } function deepClone (parent, obj = {}) { const newObj = Array.isArray(obj) ? [] : {} for (let key in parent) { if (detectType(parent[key]) === 'array') { newObj[key] = parent[key].concat([]) } else if (detectType(parent[key]) === 'regexp') { newObj[key] = new parent[key].constructor(obj) } else if (detectType(parent[key]) === 'date') { newObj[key] = new parent[key].constructor(parent[key].getTime()) } else if (detectType(parent[key]) === 'object') { const temp = {} newObj[key] = temp deepClone(parent[key], temp) } else { newObj[key] = parent[key] } } return newObj } 3.使用lodash等工具庫,不過仍是要了解原理哦 複製代碼
下期主要講一下對象和函數
本文使用 mdnice 排版