摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。的狀態由決定,分成以下兩種情況只有的狀態都變成,的狀態才會變成,此時的返回值組成一個數組,傳遞給的回調函數。
關于【Step-By-Step】
Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答案,整理最一份較優答案出來,因本人水平有限,有誤的地方,大家及時指正。
如果想 加群 學習,可以通過文末的公眾號,添加我為好友。
更多優質文章可戳: https://github.com/YvetteLau/...
__
本周面試題一覽:
什么是閉包?閉包的作用是什么?
實現 Promise.all 方法
異步加載 js 腳本的方法有哪些?
請實現一個 flattenDeep 函數,把嵌套的數組扁平化
可迭代對象有什么特點?
15. 什么是閉包?閉包的作用是什么? 什么是閉包?閉包是指有權訪問另一個函數作用域中的變量的函數,創建閉包最常用的方式就是在一個函數內部創建另一個函數。
創建一個閉包function foo() { var a = 2; return function fn() { console.log(a); } } let func = foo(); func(); //輸出2
閉包使得函數可以繼續訪問定義時的詞法作用域。拜 fn 所賜,在 foo() 執行后,foo 內部作用域不會被銷毀。
無論通過何種手段將內部函數傳遞到所在的詞法作用域之外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。如:
function foo() { var a = 2; function inner() { console.log(a); } outer(inner); } function outer(fn){ fn(); //閉包 } foo();閉包的作用
能夠訪問函數定義時所在的詞法作用域(阻止其被回收)。
私有化變量
function base() { let x = 10; //私有變量 return { getX: function() { return x; } } } let obj = base(); console.log(obj.getX()); //10
模擬塊級作用域
var a = []; for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i); } a[6](); // 6
創建模塊
function coolModule() { let name = "Yvette"; let age = 20; function sayName() { console.log(name); } function sayAge() { console.log(age); } return { sayName, sayAge } } let info = coolModule(); info.sayName(); //"Yvette"
模塊模式具有兩個必備的條件(來自《你不知道的JavaScript》)
必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會創建一個新的模塊實例)
封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態。
閉包的缺點閉包會導致函數的變量一直保存在內存中,過多的閉包可能會導致內存泄漏
16. 實現 Promise.all 方法在實現 Promise.all 方法之前,我們首先要知道 Promise.all 的功能和特點,因為在清楚了 Promise.all 功能和特點的情況下,我們才能進一步去寫實現。
Promise.all 功能Promise.all(iterable) 返回一個新的 Promise 實例。此實例在 iterable 參數內所有的 promise 都 fulfilled 或者參數中不包含 promise 時,狀態變成 fulfilled;如果參數中 promise 有一個失敗rejected,此實例回調失敗,失敗原因的是第一個失敗 promise 的返回結果。
let p = Promise.all([p1, p2, p3]);
p的狀態由 p1,p2,p3決定,分成以下;兩種情況:
(1)只有p1、p2、p3的狀態都變成 fulfilled,p的狀態才會變成 fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
(2)只要p1、p2、p3之中有一個被 rejected,p的狀態就變成 rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
Promise.all 的特點Promise.all 的返回值是一個 promise 實例
如果傳入的參數為空的可迭代對象,Promise.all 會 同步 返回一個已完成狀態的 promise
如果傳入的參數中不包含任何 promise,Promise.all 會 異步 返回一個已完成狀態的 promise
其它情況下,Promise.all 返回一個 處理中(pending) 狀態的 promise.
Promise.all 返回的 promise 的狀態
如果傳入的參數中的 promise 都變成完成狀態,Promise.all 返回的 promise 異步地變為完成。
如果傳入的參數中,有一個 promise 失敗,Promise.all 異步地將失敗的那個結果給失敗狀態的回調函數,而不管其它 promise 是否完成
在任何情況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組
Promise.all 實現僅考慮傳入的參數是數組的情況
/** 僅考慮 promises 傳入的是數組的情況時 */ Promise.all = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { resolve([]); } else { let result = []; let index = 0; for (let i = 0; i < promises.length; i++ ) { //考慮到 i 可能是 thenable 對象也可能是普通值 Promise.resolve(promises[i]).then(data => { result[i] = data; if (++index === promises.length) { //所有的 promises 狀態都是 fulfilled,promise.all返回的實例才變成 fulfilled 態 resolve(result); } }, err => { reject(err); return; }); } } }); }
可使用 MDN 上的代碼進行測試
考慮 iterable 對象
Promise.all = function (promises) { /** promises 是一個可迭代對象,省略對參數類型的判斷 */ return new Promise((resolve, reject) => { if (promises.length === 0) { //如果傳入的參數是空的可迭代對象 return resolve([]); } else { let result = []; let index = 0; let j = 0; for (let value of promises) { (function (i) { Promise.resolve(value).then(data => { result[i] = data; //保證順序 index++; if (index === j) { //此時的j是length. resolve(result); } }, err => { //某個promise失敗 reject(err); return; }); })(j) j++; //length } } }); }
測試代碼:
let p2 = Promise.all({ a: 1, [Symbol.iterator]() { let index = 0; return { next() { index++; if (index == 1) { return { value: new Promise((resolve, reject) => { setTimeout(resolve, 100, "foo"); }), done: false } } else if (index == 2) { return { value: new Promise((resolve, reject) => { resolve(222); }), done: false } } else if(index === 3) { return { value: 3, done: false } }else { return { done: true } } } } } }); setTimeout(() => { console.log(p2) }, 200);17. 異步加載 js 腳本的方法有哪些? 標簽中增加 async(html5) 或者 defer(html4) 屬性,腳本就會異步加載。
defer 和 async 的區別在于:
defer 要等到整個頁面在內存中正常渲染結束(DOM 結構完全生成,以及其他腳本執行完成),在window.onload 之前執行;
async 一旦下載完,渲染引擎就會中斷渲染,執行這個腳本以后,再繼續渲染。
如果有多個 defer 腳本,會按照它們在頁面出現的順序加載
多個 async 腳本不能保證加載順序
動態創建 script 標簽動態創建的 script ,設置 src 并不會開始下載,而是要添加到文檔中,JS文件才會開始下載。
let script = document.createElement("script"); script.src = "XXX.js"; // 添加到html文件中才會開始下載 document.body.append(script);XHR 異步加載JS
let xhr = new XMLHttpRequest(); xhr.open("get", "js/xxx.js",true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { eval(xhr.responseText); } }18. 請實現一個 flattenDeep 函數,把嵌套的數組扁平化 利用 Array.prototype.flat
ES6 為數組實例新增了 flat 方法,用于將嵌套的數組“拉平”,變成一維的數組。該方法返回一個新數組,對原數組沒有影響。
flat 默認只會 “拉平” 一層,如果想要 “拉平” 多層的嵌套數組,需要給 flat 傳遞一個整數,表示想要拉平的層數。
function flattenDeep(arr, deepLength) { return arr.flat(deepLength); } console.log(flattenDeep([1, [2, [3, [4]], 5]], 3));
當傳遞的整數大于數組嵌套的層數時,會將數組拉平為一維數組,JS能表示的最大數字為 Math.pow(2, 53) - 1,因此我們可以這樣定義 flattenDeep 函數
function flattenDeep(arr) { //當然,大多時候我們并不會有這么多層級的嵌套 return arr.flat(Math.pow(2,53) - 1); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));利用 reduce 和 concat
function flattenDeep(arr){ return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));使用 stack 無限反嵌套多層嵌套數組
function flattenDeep(input) { const stack = [...input]; const res = []; while (stack.length) { // 使用 pop 從 stack 中取出并移除值 const next = stack.pop(); if (Array.isArray(next)) { // 使用 push 送回內層數組中的元素,不會改動原始輸入 original input stack.push(...next); } else { res.push(next); } } // 使用 reverse 恢復原數組的順序 return res.reverse(); } console.log(flattenDeep([1, [2, [3, [4]], 5]]));19. 可迭代對象有什么特點
ES6 規定,默認的 Iterator 接口部署在數據結構的 Symbol.iterator 屬性,換個角度,也可以認為,一個數據結構只要具有 Symbol.iterator 屬性(Symbol.iterator 方法對應的是遍歷器生成函數,返回的是一個遍歷器對象),那么就可以其認為是可迭代的。
可迭代對象的特點具有 Symbol.iterator 屬性,Symbol.iterator() 返回的是一個遍歷器對象
可以使用 for ... of 進行循環
let arry = [1, 2, 3, 4]; let iter = arry[Symbol.iterator](); console.log(iter.next()); //{ value: 1, done: false } console.log(iter.next()); //{ value: 2, done: false } console.log(iter.next()); //{ value: 3, done: false }原生具有 Iterator 接口的數據結構:
Array
Map
Set
String
TypedArray
函數的 arguments 對象
NodeList 對象
自定義一個可迭代對象上面我們說,一個對象只有具有正確的 Symbol.iterator 屬性,那么其就是可迭代的,因此,我們可以通過給對象新增 Symbol.iterator 使其可迭代。
let obj = { name: "Yvette", age: 18, job: "engineer", *[Symbol.iterator]() { const self = this; const keys = Object.keys(self); for (let index = 0; index < keys.length; index++) { yield self[keys[index]];//yield表達式僅能使用在 Generator 函數中 } } }; for (var key of obj) { console.log(key); //Yvette 18 engineer }參考文章:
[1] MDN Promise.all
[2] Promise
[3] Iterator
謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。 https://github.com/YvetteLau/...
關注公眾號,加入技術交流群。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104782.html
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。那個率先改變的實例的返回值,就傳遞給的回調函數。通過插入標簽的方式來實現跨域,參數只能通過傳入,僅能支持請求。因此清除浮動,只需要觸發一個即可。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的...
摘要:實例擁有構造函數屬性,該屬性返回創建實例對象的構造函數。在考慮對象而不是自定義類型和構造函數的情況下,寄生式繼承也是一種有用的模式。在子類的構造函數中,只有調用之后,才能使用關鍵字,否則報錯。 不積跬步無以至千里。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀...