轉載於https://github.com/poetries/FE-Interview-Questions,by poetriesjavascript
當執行
JS
代碼時,會生成執行環境,只要代碼不是寫在函數中的,就是在全局執行環境中,函數中的代碼會產生函數執行環境,只此兩種執行環境。css
b() // call b console.log(a) // undefined var a = 'Hello world' function b() { console.log('call b') }
想必以上的輸出你們確定都已經明白了,這是由於函數和變量提高的緣由。一般提高的解釋是說將聲明的代碼移動到了頂部,這其實沒有什麼錯誤,便於你們理解。可是更準確的解釋應該是:在生成執行環境時,會有兩個階段。第一個階段是建立的階段,
JS
解釋器會找出須要提高的變量和函數,而且給他們提早在內存中開闢好空間,函數的話會將整個函數存入內存中,變量只聲明而且賦值爲undefined
,因此在第二個階段,也就是代碼執行階段,咱們能夠直接提早使用html
b() // call b second function b() { console.log('call b fist') } function b() { console.log('call b second') } var b = 'Hello world'
var
會產生不少錯誤,因此在 ES6中引入了let
。let
不能在聲明前使用,可是這並非常說的let
不會提高,let
提高了,在第一階段內存也已經爲他開闢好了空間,可是由於這個聲明的特性致使了並不能在聲明前使用前端
call
和 apply
都是爲了解決改變 this
的指向。做用都是相同的,只是傳參的方式不一樣。call
能夠接收一個參數列表,apply
只接受一個參數數組let a = { value: 1 } function getValue(name, age) { console.log(name) console.log(age) console.log(this.value) } getValue.call(a, 'yck', '24') getValue.apply(a, ['yck', '24'])
bind
和其餘兩個方法做用也是一致的,只是該方法會返回一個函數。而且咱們能夠經過bind
實現柯里化java
對於實現如下幾個函數,能夠從幾個方面思考node
window
this
指向,讓新的對象能夠執行該函數。那麼思路是否能夠變成給新的對象添加一個函數,而後在執行完之後刪除?Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } var _this = this var args = [...arguments].slice(1) // 返回一個函數 return function F() { // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
Function.prototype.myCall = function (context) { var context = context || window // 給 context 添加一個屬性 // getValue.call(a, 'yck', '24') => a.fn = getValue context.fn = this // 將 context 後面的參數取出來 var args = [...arguments].slice(1) // getValue.call(a, 'yck', '24') => a.fn('yck', '24') var result = context.fn(...args) // 刪除 fn delete context.fn return result }
Function.prototype.myApply = function (context) { var context = context || window context.fn = this var result // 須要判斷是否存儲第二個參數 // 若是存在,就將第二個參數展開 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }
prototype
屬性,除了 Function.prototype.bind()
,該屬性指向原型。__proto__
屬性,指向了建立該對象的構造函數的原型。其實這個屬性指向了 [[prototype]]
,可是 [[prototype]]
是內部屬性,咱們並不能訪問到,因此使用 _proto_
來訪問。__proto__
來尋找不屬於該對象的屬性,__proto__
將對象鏈接起來組成了原型鏈。Object.prototype.toString.call(xx)
。這樣咱們就能夠得到相似 [object Type]
的字符串。instanceof
能夠正確的判斷對象的類型,由於內部機制是經過判斷對象的原型鏈中是否是能找到類型的 prototype
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭頭函數實際上是沒有
this
的,這個函數中的this
只取決於他外面的第一個不是箭頭函數的函數的this
。在這個例子中,由於調用a
符合前面代碼中的第一個狀況,因此this
是window
。而且this
一旦綁定了上下文,就不會被任何代碼改變webpack
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) // 還有種就是利用 call,apply,bind 改變 this,這個優先級僅次於 new
async
和await
相比直接使用Promise
來講,優點在於處理 then 的調用鏈,可以更清晰準確的寫出代碼。缺點在於濫用await
可能會致使性能問題,由於await
會阻塞代碼,也許以後的異步代碼並不依賴於前者,但仍然須要等待前者完成,致使代碼失去了併發性git
下面來看一個使用 await
的代碼。github
var a = 0 var b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 a = (await 10) + a console.log('3', a) // -> '3' 20 } b() a++ console.log('1', a) // -> '1' 1
b
先執行,在執行到 await 10
以前變量 a
仍是 0
,由於在 await
內部實現了 generators
,generators
會保留堆棧中東西,因此這時候 a = 0
被保存了下來await
是異步操做,遇到await
就會當即返回一個pending
狀態的Promise
對象,暫時返回執行代碼的控制權,使得函數外的代碼得以繼續執行,因此會先執行 console.log('1', a)
a = 10
Generator
是ES6
中新增的語法,和Promise
同樣,均可以用來異步編程web
// 使用 * 表示這是一個 Generator 函數 // 內部能夠經過 yield 暫停代碼 // 經過調用 next 恢復執行 function* test() { let a = 1 + 2; yield 2; yield 3; } let b = test(); console.log(b.next()); // > { value: 2, done: false } console.log(b.next()); // > { value: 3, done: false } console.log(b.next()); // > { value: undefined, done: true }
從以上代碼能夠發現,加上
*
的函數執行後擁有了next
函數,也就是說函數執行後返回了一個對象。每次調用next
函數能夠繼續執行被暫停的代碼。如下是Generator
函數的簡單實現
// cb 也就是編譯過的 test 函數 function generator(cb) { return (function() { var object = { next: 0, stop: function() {} }; return { next: function() { var ret = cb(object); if (ret === undefined) return { value: undefined, done: true }; return { value: ret, done: false }; } }; })(); } // 若是你使用 babel 編譯後能夠發現 test 函數變成了這樣 function test() { var a; return generator(function(_context) { while (1) { switch ((_context.prev = _context.next)) { // 能夠發現經過 yield 將代碼分割成幾塊 // 每次執行 next 函數就執行一塊代碼 // 而且代表下次須要執行哪塊代碼 case 0: a = 1 + 2; _context.next = 4; return 2; case 4: _context.next = 6; return 3; // 執行完畢 case 6: case "end": return _context.stop(); } } }); }
Promise
是 ES6
新增的語法,解決了回調地獄的問題。Promise
當作一個狀態機。初始是 pending
狀態,能夠經過函數 resolve
和 reject
,將狀態轉變爲 resolved
或者 rejected
狀態,狀態一旦改變就不能再次變化。then
函數會返回一個 Promise
實例,而且該返回值是一個新的實例而不是以前的實例。由於 Promise
規範規定除了 pending
狀態,其餘狀態是不能夠改變的,若是返回的是一個相同實例的話,多個 then
調用就失去意義了。 對於 then
來講,本質上能夠把它當作是 flatMap
// 三種狀態 const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一個函數參數,該函數會當即執行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用於保存 then 中的回調,只有當 promise // 狀態爲 pending 時纔會緩存,而且每一個實例至多緩存一個 _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 若是 value 是個 Promise,遞歸執行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 異步執行,保證執行順序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 異步執行,保證執行順序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用於解決如下問題 // new Promise(() => throw Error('error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 規範 2.2.7,then 必須返回一個新的 promise var promise2; // 規範 2.2.onResolved 和 onRejected 都爲可選參數 // 若是類型不是函數須要忽略,同時也實現了透傳 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === 'function' ? onResolved : v => v; onRejected = typeof onRejected === 'function' ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 規範 2.2.4,保證 onFulfilled,onRjected 異步執行 // 因此用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject);