前言
本篇是面試系列的JavaScript的基礎篇。文章主要圍繞着基礎類型、做用域、閉包、原型、繼承、this的指向、深淺拷貝、字符串和數組的方法、節流、防抖、函數式編程等知識展開的。面試
正文
基礎類型
JavaScript的數據類型主要分爲兩類:原始類型、引用類型ajax
原始類型:6種編程
- null 【JS的數據底層都是用二進制進行存儲的,前三位爲0會被判斷成對象,null是全都爲0】
- undefined
- Number【雙精度64位二進制格式的值——數字、±Infinity、NaN】
- String
- Boolean
- Symbol【ES6新增定義,實例是惟一且不可變的】
引用類型:windows
在內存中,存儲的形式:數組
- 原始類型,會被保存到棧內存中
- 引用類型,會被保存到堆內存中
類型之間的賦值方式:promise
- 原始類型的賦值:對值類型的拷貝,會複製一份值
- 引用類型的賦值:對引用地址的拷貝
基礎類型的隱式轉換也是面試考察的重點,Symbol類型有興趣的小夥伴也能夠手寫實現一下【面試系列——手寫代碼實現(一)】瀏覽器
執行上下文 | 做用域 | 閉包
JS中存在的執行上下文類型:緩存
- 全局上下文:windows對象
- 函數上下文:每次調用函數時,建立一個新的上下文
- eval上下文
每一個執行上下文,都存在三個屬性:做用域、變量、this閉包
做用域:JS只有全局做用域和函數做用域app
- var建立的變量只有函數做用域
- let和const建立的變量既有函數做用域,也有塊級做用域
做用域鏈:JavaScript引擎在尋找一個變量名的時候,會在當前做用域進行查找,若是沒有,就會繼續往外層做用域進行查找,直到全局做用域爲止,這就造成了一個做用域鏈。
閉包:引用外部函數變量的內部函數【紅寶書的定義】
主要使用場景:
- setTimeout、setInterval、setImmediate之類的定時器、事件回調、ajax請求的回調
- 被外部函數做爲函數返回,或者返回對象中引用內部函數的狀況
能夠使用當即執行函數(IIFE)來實現閉包,代碼以下:
(function(i){
console.log(i);
})(i);
複製代碼
閉包只是存儲外部變量的引用,不會拷貝外部變量的值;閉包引用的變量會被存放到堆內存中。
this | apply | call | bind
this的指向,是在執行上下文被建立的時候,被肯定的。
this的綁定規則總共有下面五種:
- 默認綁定【嚴格 | 非嚴格】
- 默認模式下,this指向全局對象
- 嚴格模式下,this指向undefined
- 隱式綁定:函數調用時,會有一個上下文對象,函數的this會綁定在這個上下文對象上面
- 顯式綁定:經過apply/call/bind的方式,來實現this的顯式綁定
- new綁定:JS構造函數經過new操做符進行調用,此處的this指向新建立的對象實例
- 箭頭函數綁定:引用外層上下文的this
- 箭頭函數的this,至關於普通變量
- 尋找箭頭函數的this,就至關於尋找外層做用域
- 若是改變了外層做用域的this,就能夠改變箭頭函數的this
this的指向優先級,依次按照 箭頭函數 > new操做符 > 顯示綁定 > 隱式綁定 > 默認綁定
apply | call | bind的做用:改變函數中this的指向
apply和call的區別:
- 相同點:
- 第一個參數都是指向函數中的this
- 均可以接受一個參數
- 不一樣點:
- apply只能接受兩個參數,其中後一個參數能夠是數組、類數組、對象
- call能夠接受多個參數
apply/call和bind的區別:
- apply和call是函數執行時調用的
- bind是函數定義時調用的
this的指向是面試考察的重點,通常都會以題目的形式進行考察;apply、call、bind是須要手寫實現的【面試系列——手寫代碼實現(一)】
原型和繼承
原型(prototype):給其餘對象提供共享屬性的對象
隱式引用(proto):全部對象,都存在一個隱式引用,指向它的原型
構造函數(Constructor):構造函數,它的原型指向實例的原型
- 構造函數和普通函數的區別:
- 使用new操做符生成實例的函數就是構造函數
- 直接調用的就是普通函數
- Symbol是基礎類型,不是構造函數,它經過Symbol()建立實例
原型鏈:如圖所示
圖中,__proto__造成的鏈條組合,就是原型鏈
繼承方式:
- 原型鏈繼承:本質就是重寫原型對象,代之以新對象的實例
- 缺點:
- 多個實例對引用類型進行篡改
- 子類型原型上的構造函數被重寫了
- 給子類型添加屬性和方法須要在替換原型以後
- 建立子類型以後,沒法向父類構造函數傳參
- 構造函數繼承:使用父類構造函數來加強子類實例,等同於複製父類的實例給子類(不使用原型)
- 核心:superType.call(this)
- 缺點:
- 只能繼承父類實例的屬性和方法,不能複製父類原型上的屬性和方法
- 沒法實現複用,每一個實例都會有父類實例的副本,影響性能
- 組合繼承:上述兩種方式的組合,用原型鏈的方式繼承原型上的屬性和方法,用構造函數的方式,來實現實例屬性和方法的繼承
- 寄生組合式繼承:ES6 Class的繼承原理
- 子類實例繼承了父類實例的屬性和方法
- 原型鏈的繼承
- 父類構造函數上的屬性繼承
原型,基本只要記住原型鏈的圖,面試都沒有太大的問題;繼承的概念比較深澀難懂,須要多加實踐。new、instanceof、ES6的繼承都是須要手寫實現的。詳見【面試系列——手寫代碼實現(一)】
淺拷貝和深拷貝
淺拷貝:建立一個新對象,將原對象的屬性值賦值給新對象
- 使用場景:Object.assign、展開符(...)、Array.prototype.slice
Object.assign:主要將一個和多個對象的可枚舉屬性進行合併
深拷貝:拷貝一個對象的所有屬性,拷貝完成以後,兩個對象相互不影響
- 使用場景:JSON.parse(JSON.stringify(obj))
其中JSON拷貝的缺點:
- 會忽略undefined
- 會忽略Symbol
- 不能序列化函數
- 不能拷貝循環引用對象
- 不能正確調用new Date()
- 不能處理正則
深拷貝和淺拷貝是面試考察的重點,主要是手寫實現深拷貝,在面試題中出現次數不少【面試系列——手寫代碼實現(一)】
異步處理
異步解決方案:
- Raw Callback Style:樸素函數做爲回調函數,接受error,data等參數
- Promise Callback Style:經過{ then }對象,去處理onFulfilled和onRejected兩個回調函數
- Generator Callback Style:經過 * 號和yield關鍵詞,將多層嵌套的callback扁平化的語法糖
- Async/await Callback Style:經過async和await關鍵詞,將Promise+Generator標準化和語法化的產物
Promise:是一個類,經過new來進行聲明。
Promise有三種狀態:
- pending:等待狀態,初始化狀態,可轉變成fulfilled或rejected
- fulfilled:成功狀態,不可轉變狀態,且有一個不可變值(value)
- rejected:失敗狀態,不可轉變狀態,且有一個不可變的失敗緣由(reason)
then方法:參數包含兩個函數
- onFulfilled - 成功時執行的函數【參數是上一次返回的成功值】
- onRejected - 失敗時執行的函數【參數是上一次返回的失敗緣由】
all方法:返回一個Promise,Promise返回一個結果是全部promise的結果數組
race方法:返回一個Promise,Promise返回一個結果值【最快響應的那個Promise的值】
Generator函數就是一個狀態機,內部包含多個狀態
- yield做爲一種暫停標誌
- next方法能夠訪問下一個狀態
Async:Generator的語法糖,是一個經過異步執行並隱式返回Promise對象的函數
async對generator的改進:
- 內置執行器
- 返回Promise
- await後面能夠是Promise,也能夠是原始值
- 更好的語義
異步處理,是一個常考的命題。Promise,常常被用在開發環境中,瞭解其原理是必不可少的。面試中,也會被要求去實現一個簡單的Promise或者Promise方法。async和await也會被問及,它的實現原理也是須要掌握的。具體實現部分能夠查看【面試系列——手寫代碼實現(一)】
模塊化
常見的模塊化方案:
CommonJS
- 一個文件就是一個模塊,經過執行該文件來加載模塊
- 每一個模塊內部,module變量表明當前模塊,這個變量就是一個對象,exports是它對外的接口
- require命令第一次加載該腳本時,就會執行整個腳本,在內存中生成一個對象(屢次加載,只有第一次會被運行,結果被緩存)
- 特色:
- 全部代碼都運行在模塊做用域,不會污染全局做用域
- 獨立性是模塊最重要的特色,模塊內部最好不會和程序其餘部分直接交互
- 模塊屢次加載,只有第一次會被執行,並將結果會被緩存下來;若是想要從新執行,就得清除緩存
- 模塊加載的順序,按照其在代碼中的位置
- CommonJS,它是同步加載,不適合瀏覽器
AMD
- 它採用異步的方式加載模塊,模塊的加載不會影響它後面語句的運行。全部依賴這個模塊的代碼,都會被放到回調函數裏面去
- AMD經過define的方式來定義模塊
define(id?, dependencies?, factory);
- 使用時,依然經過require關鍵詞
require([module], callback);
CMD
- 它是seajs所推廣的一種模塊化方法
- 與AMD的區別是:
- 對於依賴的模塊,AMD是提早執行的,CMD是延遲執行的;【requirejs2.0,也改爲了延遲執行】
- AMD推崇依賴前置,CMD推崇就近依賴
ES6 Module
- 設計思想:儘可能的靜態化,使得在編譯的時候,就能清楚模塊之間的依賴關係,以及輸入和輸出的變量。【AMD和CMD,都是在運行時肯定的】
- 經過import和export關鍵詞組成,export做爲對外接口,import是輸入其餘模塊的功能
- 還有一個export default命令用於指定模塊的默認輸出【一個模塊只有一個默認輸出】
ES6 Module 和 CommonJS 的區別:
- 前者是值的引用,後者是值的拷貝
- 前者是編譯時輸出接口,後者是運行時加載
require的性能問題:因爲它是值的拷貝,比較佔用內存
CommonJS和ES6 Module是考察的重點,尤爲是它們之間的區別。本篇概述的比較淺顯,能夠看一些詳解的文章,詳細瞭解一下。
防抖和節流
防抖:事件觸發n秒後,執行回調,若是在這n秒內再次被觸發,就從新計時
- 實現:
- 返回一個函數,函數內部會去清除計時器
- 而後從新執行setTimeout進行計時
節流:事件在一段時間內只會被觸發一次,若是屢次觸發,只有一次生效。
- 實現:
- 返回一個函數,函數內部定義一個last【上回執行的時間】
- 獲取當前時間now,來經過last+delay的比較,看是否執行回調
防抖和節流是平常開發工做也常常會使用的優化方法,面試必考題。面試者須要對其實現原理,聊熟於心,可以熟練編寫代碼。詳見【面試系列——手寫代碼實現(一)】
函數式編程
特性:
- 函數是一等公民——函數能夠跟其餘變量同樣,做爲其餘函數的輸入輸出
- 不可變量
- 純函數——沒有反作用的函數,不修改函數外部的變量
- 引用透明——一樣的輸入,一定是一樣的輸出
- 惰性計算
React就是函數式編程的典型表明,ReactView = render(data);
優點:
- 更好的狀態管理——它的宗旨就是無狀態的
- 更簡單的複用
- 更加優雅的組合
高階函數:只要知足如下兩點中的一點便可
函數柯里化:就是部分求值,將使用多個參數的函數轉出一系列一個參數的函數的方式,並接受剩餘參數的函數
函數式編程,近些年來挺火的。雖然JavaScript名義上不是一種函數式編程語言,可是這種方式,能夠提升編碼效率;總體的上手成本仍是比較高的,並且深澀難懂。可是將來,函數式編程仍然會持續火爆。
總結
最後但願分享的內容,對你的面試能有幫助。這些知識點只是淺顯的概述,仍然須要你去看一些文章,深刻理解。網上也有不少深刻講解的系列,好比冴羽老師的。