JS相關
1.變量提高
ES6以前咱們通常使用var來聲明變量,提高簡單來講就是把咱們所寫的相似於var a = 123;這樣的代碼,聲明提高到它所在做用域的頂端去執行,到咱們代碼所在的位置來賦值。javascript
function test() { console.log(a); // undefined a = 123; }
test();
執行順序以下:html
function test() { var a; console.log(a); // undefined a = 123; } test();
2.函數提高
javascript中不只僅是變量聲明有提高的現象,函數的聲明也是同樣;具名函數的聲明有兩種方式:1. 函數聲明式 2. 函數字面量式java
function test() {} // 函數式聲明 let test = function() {} // 字面量聲明
函數提高是整個代碼塊提高到它所在的做用域的最開始執行算法
console.log(f); function f() { console.log(1); } // 至關於如下代碼 function f() { console.log(1); } console.log(f);
foo(); //1 var foo; function foo () { console.log(1); } foo = function () { console.log(2); }
根因分析:javascript引擎並將var a和a = 2看作是兩個單獨的聲明,第一個是編譯階段的任務,而第二個則是執行階段的任務。這意味着不管做用域中的聲明出如今什麼地方,都將在代碼自己被執行前首先進行處理,能夠將這個過程形象地想象成全部的聲明(變量和函數)都會被「移動」到各自做用域的最頂端,這個過程被稱爲提高。
3.bind、call、apply
call和apply實際上是同一個東西,區別只有參數不一樣,call是apply的語法糖,因此就放在一塊兒說了,這兩個方法都是定義在函數對象的原型上的(Function.prototype),call和apply方法的做用都是改變函數的執行環境,第一個參數傳入上下文執行環境,而後傳入函數執行所需的參數。傳入call的參數只能是單個參數,不能是數組。apply可傳入數組。話很少說直接上代碼,看下面的例子:數組
function ga() { let x=1;
} function gb(y) { return x+y; } gb(2) //調用發生報錯,由於拿不到x的值 gb.call(ga,2); //使gb在ga環境中執行,能夠拿到x,運行正常
上面的代碼中因爲gb()函數執行依賴於ga()中的變量,因此咱們使用了call將gb的運行環境變成了ga。promise
function gg(x,y,z){ let a=Array.prototype.slice.call(arguments,1,2) //經過slice方法獲取到了第二個參數 return a; //返回[2] } gg(1,2,3)
// arguments是一個類數組對象,它自己不能調用數組的slice方法,使用call將執行slice方法的對象由數組變爲了arguments。
使用apply改寫上面的方法瀏覽器
function gg(x,y,z){ let d=[1,2] let a=Array.prototype.slice.apply(arguments,d) //經過slice方法獲取到了第二個參數 return a; //返回[2] } gg(1,2,3)
使用apply和call實現繼承緩存
function Parent(name) { this.name = name; this.sayHello = function() { alert(name); } } function Child(name) { // 子類的this傳給父類 Parent.call(this, name); } let parent = new Parent("張三"); let child = new Child("李四"); parent.sayHello(); child.sayHello();
bind和apply區別是apply會馬上執行,而bind只是起一個綁定執行上下文的做用。看下面的例子:數據結構
function ga() { let x=1; (function gb(y) { return x+y; }).bind(this) //使用bind將gb函數的執行上下文綁定到ga上 } gb(2) //運行正常,獲得3 // 有些狀況下爲了方便咱們能夠直接將ga綁定,而不用在調用的時候再使用apply。
4.原型&原型鏈
在JavaScript中,每一個函數都有一個prototype屬性,這個屬性指向函數的原型對象(原型就是一個Object的實例,是一個對象)app
每一個對象(除null外)都會有的屬性,叫作__proto__,這個屬性會指向該對象的原型;絕大部分瀏覽器都支持這個非標準的方法訪問原型,然而它並不存在於 Person.prototype 中,實際上,它是來自於 Object.prototype ,與其說是一個屬性,不如說是一個 getter/setter,當使用 obj.__proto__ 時,能夠理解成返回了 Object.getPrototypeOf(obj)。
每一個原型都有一個constructor屬性,指向該關聯的構造函數
當讀取實例的屬性時,若是找不到,就會查找與對象關聯的原型中的屬性,若是還查不到,就去找原型的原型,一直找到最頂層爲止
原型的原型是什麼?
其實原型對象就是經過 Object 構造函數生成的,結合以前所講,實例的 __proto__ 指向構造函數的 prototype
簡單的回顧一下構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那麼假如咱們讓原型對象等於另外一個類型的實例,結果會怎樣?顯然,此時的原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數的指針。假如另外一個原型又是另外一個類型的實例,那麼上述關係依然成立。如此層層遞進,就構成了實例與原型的鏈條。這就是所謂的原型鏈的基本概念。
如圖所示:藍色即爲原型鏈。
5. this指向
面嚮對象語言中 this 表示當前對象的一個引用。
但在 JavaScript 中 this 不是固定不變的,它會隨着執行環境的改變而改變。
- 在方法中,this 表示該方法所屬的對象。
- 若是單獨使用,this 表示全局對象。
- 在函數中,this 表示全局對象。
- 在函數中,在嚴格模式下,this 是未定義的(undefined)。
- 在事件中,this 表示接收事件的元素。
- 相似 call() 和 apply() 方法能夠將 this 引用到任何對象
function foo() { console.log(this.a) } var a = 1 foo() var obj = { a: 2, foo: foo } obj.foo() // 以上二者狀況 `this` 只依賴於調用函數前的對象,優先級是第二個狀況大於第一個狀況 // 如下狀況是優先級最高的,`this` 只會綁定在 `c` 上,不會被任何方式修改 `this` 指向 var c = new foo() c.a = 3 console.log(c.a) 1 2 undefined 3
6.堆和棧
這裏先說兩個概念:一、堆(heap)二、棧(stack)
堆 是堆內存的簡稱。
棧 是棧內存的簡稱。
說到堆棧,咱們講的就是內存的使用和分配了,沒有寄存器的事,也沒有硬盤的事。
各類語言在處理堆棧的原理上都大同小異。堆是動態分配內存,內存大小不一,也不會自動釋放。棧是自動分配相對固定大小的內存空間,並由系統自動釋放。
javascript的基本類型就5種:Undefined、Null、Boolean、Number和String,它們都是直接按值存儲在棧中的,每種類型的數據佔用的內存空間的大小是肯定的,並由系統自動分配和自動釋放。這樣帶來的好處就是,內存能夠及時獲得回收,相對於堆來講,更加容易管理內存空間。
javascript中其餘類型的數據被稱爲引用類型的數據 : 如對象(Object)、數組(Array)、函數(Function) …,它們是經過拷貝和new出來的,這樣的數據存儲於堆中。其實,說存儲於堆中,也不太準確,由於,引用類型的數據的地址指針是存儲於棧中的,當咱們想要訪問引用類型的值的時候,須要先從棧中得到對象的地址指針,而後,在經過地址指針找到堆中的所須要的數據。
說來也是形象,棧,線性結構,後進先出,便於管理。堆,一個混沌,雜亂無章,方便存儲和開闢內存空間;
7.generate,async, await 參考https://blog.csdn.net/qdmoment/article/details/86672907
generator生成器的設計原理:
- 狀態機,簡化函數內部狀態存儲;
- 半協程實現
- 上下文凍結
應用場景:
- 異步操做的同步化表達
- 控制流管理
- 部署 Iterator 接口
- 做爲數據結構
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用yield
語句註明
Generator 函數是協程在 ES6 的實現,最大特色就是能夠交出函數的執行權(即暫停執行)
generator生成器和iterator遍歷器是對應的,咱們知道iterator遍歷器是給不一樣數據結構提供統一的數據接口機制,那麼相對的generator生成器是生成這樣一個遍歷器,進而使數據結構擁有iterator遍歷器接口。換一種方法來講,generator函數提供了可供遍歷的狀態,因此generator是一個狀態機,在其內部封裝了多個狀態,這些狀態可使用iterator遍歷器遍歷。
注意:既然generator是一個狀態機,因此直接運行generator()函數,並不會執行,相反的是生成一個指向內部狀態的指針對象,即一個可供遍歷的遍歷器。
想運行generator,必須調用遍歷器對象的next方法,使得指針移向下一個狀態,直到遇到下一個yield表達式(或return語句)爲止。Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。
const test = testGen(); test.next() // { value: '1', done: false } test.next() // { value: '2', done: false } test.next() // { value: 'ending', done: true } test.next() // { value: undefined, done: true } // 函數有三個狀態 1,2,return function* testGen() { yield '1'; yield '2'; return 'end'; }
Generator的原型方法:
Generator.prototype.throw(),Generator.prototype.return()
throw() 在函數體外拋出錯誤,而後在 Generator 函數體內捕獲
return():返回給定的值,而且終結遍歷 Generator 函數
next()、throw()、return() 的共同點
做用都是讓 Generator 函數恢復執行,而且使用不一樣的語句替換yield表達式(帶入參)
next()是將yield表達式替換成一個值
throw()是將yield表達式替換成一個throw語句
return()是將yield表達式替換成一個return語句
async函數
async 函數的實現原理,就是將 Generator 函數和自動執行器,包裝在一個函數裏。
(看了不少遍還不是很明白~)
async function fn(args) { // ... } // 等同於 function fn(args) { return spawn(function* () { // ... }); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
8.如何實現一個 Promise
promise的核心原理其實就是發佈訂閱模式,經過兩個隊列來緩存成功的回調(onResolve)和失敗的回調(onReject)。
promise的特色:
- new Promise時須要傳遞一個executor執行器,執行器會馬上執行(是在主線程執行,區別於then)
- 執行器中傳遞了兩個參數:resolve成功的函數、reject失敗的函數,他們調用時能夠接受任何值的參數value
- promise狀態只能從pending態轉onfulfilled,onrejected到resolved或者rejected,而後執行相應緩存隊列中的任務
- promise實例,每一個實例都有一個then方法,這個方法傳遞兩個參數,一個是成功回調onfulfilled,另外一個是失敗回調onrejected
- promise實例調用then時,若是狀態resolved,會讓onfulfilled執行而且把成功的內容看成參數傳遞到函數中
- promise中能夠同一個實例then屢次,若是狀態是pengding 須要將函數存放起來 等待狀態肯定後 在依次將對應的函數執行 (發佈訂閱)
(1) 構造函數
function Promise(resolver) {}
(2) 原型方法
Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
(3) 靜態方法
Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
function Promise (executor) { var self = this;//resolve和reject中的this指向不是promise實例,須要用self緩存 self.state = 'padding'; self.value = '';//緩存成功回調onfulfilled的參數 self.reson = '';//緩存失敗回調onrejected的參數 self.onResolved = []; // 專門存放成功的回調onfulfilled的集合 self.onRejected = []; // 專門存放失敗的回調onrejected的集合 function resolve (value) { if(self.state==='padding'){ self.state==='resolved'; self.value=value; self.onResolved.forEach(fn=>fn()) } } function reject (reason) { self.state = 'rejected'; self.value = reason; self.onRejected.forEach(fn=>fn()) } try{ executor(resolve,reject) }catch(e){ reject(e) } } Promise.prototype.then=function (onfulfilled,onrejected) { var self=this; if(this.state==='resolved'){ onfulfilled(self.value) } if(this.state==='rejected'){ onrejected(self.value) } if(this.state==='padding'){ this.onResolved.push(function () { onfulfilled(self.value) }) } } Promise.prototype.catch = function (onrejected) { return this.then(null, onrejected) }; Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason) }) }; Promise.resolve = function (value) { return new Promise((resolve, reject) => { resolve(value); }) }; Promise.all=function (promises) { return new Promise((resolve,reject)=>{ let results=[],i=0; for(let i=0;i<promises.length;i++){ let p=promises[i]; p.then((data)=>{ processData(i,data) },reject) } function processData (index,data) { results[index]=data; if(++i==promises.length){ resolve(results) } } }) }; //在每一個promise的回調中添加一個resolve(就是在當前的promise.then中添加),有一個狀態改變,就讓race的狀態改變 Promise.race=function (promises) { return new promises((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ let p=promises[i]; p.then(resolve,reject) } })
9.垃圾回收機制
通常來講沒有被引用的對象就是垃圾,就是要被清除, 有個例外若是幾個對象引用造成一個環,互相引用,但根訪問不到它們,這幾個對象也是垃圾,也要被清除。
JS中最多見的垃圾回收方式是標記清除。
工做原理:是當變量進入環境時,將這個變量標記爲「進入環境」。當變量離開環境時,則將其標記爲「離開環境」。標記「離開環境」的就回收內存。
工做流程:
1. 垃圾回收器,在運行的時候會給存儲在內存中的全部變量都加上標記。
2. 去掉環境中的變量以及被環境中的變量引用的變量的標記。
3. 再被加上標記的會被視爲準備刪除的變量。
4. 垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收他們所佔用的內存空間。
引用計數 方式
工做原理:跟蹤記錄每一個值被引用的次數。
工做流程:
1. 聲明瞭一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1。
2. 同一個值又被賦值給另外一個變量,這個引用類型值的引用次數加1.
3. 當包含這個引用類型值的變量又被賦值成另外一個值了,那麼這個引用類型值的引用次數減1.
4. 當引用次數變成0時,說明沒辦法訪問這個值了。
5. 當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所佔的內存。
新生代算法(http://newhtml.net/v8-garbage-collection/)
新生代中的對象通常存活時間較短,使用 Scavenge GC 算法。
在新生代空間中,內存空間分爲兩部分,分別爲 From 空間和 To 空間。在這兩個空間中,一定有一個空間是使用的,另外一個空間是空閒的。新分配的對象會被放入 From 空間中,當 From 空間被佔滿時,新生代 GC 就會啓動了。算法會檢查 From 空間中存活的對象並複製到 To 空間中,若是有失活的對象就會銷燬。當複製完成後將 From 空間和 To 空間互換,這樣 GC 就結束了。
老生代算法
老生代中的對象通常存活時間較長且數量也多,使用了兩個算法,分別是標記清除算法和標記壓縮算法。
在講算法前,先來講下什麼狀況下對象會出如今老生代空間中:
- 新生代中的對象是否已經經歷過一次 Scavenge 算法,若是經歷過的話,會將對象重新生代空間移到老生代空間中。
- To 空間的對象佔比大小超過 25 %。在這種狀況下,爲了避免影響到內存分配,會將對象重新生代空間移到老生代空間中。
10. 深拷貝
這個問題一般能夠經過 JSON.parse(JSON.stringify(object))
來解決。
可是該方法也是有侷限性的:
- 會忽略
undefined
- 會忽略
symbol
- 不能序列化函數
- 不能解決循環引用的對象
手動實現:
// 定義一個深拷貝函數 接收目標target參數 function deepClone(target) { // 定義一個變量 let result; // 若是當前須要深拷貝的是一個對象的話 if (typeof target === 'object') { // 若是是一個數組的話 if (Array.isArray(target)) { result = []; // 將result賦值爲一個數組,而且執行遍歷 for (let i in target) { // 遞歸克隆數組中的每一項 result.push(deepClone(target[i])) } // 判斷若是當前的值是null的話;直接賦值爲null } else if(target===null) { result = null; // 判斷若是當前的值是一個RegExp對象的話,直接賦值 } else if(target.constructor===RegExp){ result = target; }else { // 不然是普通對象,直接for in循環,遞歸賦值對象的全部值 result = {}; for (let i in target) { result[i] = deepClone(target[i]); } } // 若是不是對象的話,就是基本數據類型,那麼直接賦值 } else { result = target; } // 返回最終結果 return result; }