摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。即使這個時間周期內,小明取得多次滿分。創建作用域鏈在執行期上下文的創建階段,作用域鏈是在變量對象之后創建的。這種一層一層的關系,就是作用域鏈。
關于【Step-By-Step】
Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答案,整理最一份較優答案出來,因本人水平有限,有誤的地方,大家及時指正。
如果想 加群 學習,掃碼 二維碼 (點擊查看),添加我為好友,驗證信息為加入組織,我拉你進群。
更多優質文章可戳: https://github.com/YvetteLau/...
__
本周面試題一覽:
節流(throttle)函數的作用是什么?有哪些應用場景,請實現一個節流函數
說一說你對JS執行上下文棧和作用域鏈的理解?
什么是BFC?BFC的布局規則是什么?如何創建BFC?
let、const、var 的區別有哪些?
深拷貝和淺拷貝的區別是什么?如何實現一個深拷貝?
6. 節流(throttle)函數的作用是什么?有哪些應用場景,請實現一個節流函數。(2019-05-27) 節流函數的作用節流函數的作用是規定一個單位時間,在這個單位時間內最多只能觸發一次函數執行,如果這個單位時間內多次觸發函數,只能有一次生效。
舉例說明:小明的媽媽和小明約定好,如果小明在周考中取得滿分,那么當月可以帶他去游樂場玩,但是一個月最多只能去一次。
這其實就是一個節流的例子,在一個月的時間內,去游樂場最多只能觸發一次。即使這個時間周期內,小明取得多次滿分。
節流應用場景1.按鈕點擊事件
2.拖拽事件
3.onScoll
4.計算鼠標移動的距離(mousemove)
節流函數實現利用時間戳實現
function throttle (func, delay) { var lastTime = 0; function throttled() { var context = this; var args = arguments; var nowTime = Date.now(); if(nowTime > lastTime + delay) { func.apply(context, args); lastTime = nowTime; } } //節流函數最終返回的是一個函數 return throttled; }
利用定時器實現
function throttle(func, delay) { var timeout = null; function throttled() { var context = this; var args = arguments; if(!timeout) { timeout = setTimeout(()=>{ func.apply(context, args); clearTimeout(timeout); timeout=null }, delay); } } return throttled; }
時間戳和定時器的方式都沒有考慮最后一次執行的問題,比如有個按鈕點擊事件,設置的間隔時間是1S,在第0.5S,1.8S,2.2S點擊,那么只有0.5S和1.8S的兩次點擊能夠觸發函數執行,而最后一次的2.2S會被忽略。
組合實現,允許設置第一次或者最后一次是否觸發函數執行
function throttle (func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function () { previous = options.leading === false ? 0 : Date.now() || new Date().getTime(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function () { var now = Date.now() || new Date().getTime(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判斷是否設置了定時器和 trailing timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; }
使用很簡單:
btn.onclick = throttle(handle, 1000, {leading:true, trailing: true});
點擊查看更多7. 說一說你對JS執行上下文棧和作用域鏈的理解?(2019-05-28)
在開始說明JS上下文棧和作用域之前,我們先說明下JS上下文以及作用域的概念。
JS執行上下文執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。
執行上下文類型分為:
全局執行上下文
函數執行上下文
eval函數執行上下文(不被推薦)
執行上下文創建過程中,需要做以下幾件事:
創建變量對象:首先初始化函數的參數arguments,提升函數聲明和變量聲明。
創建作用域鏈(Scope Chain):在執行期上下文的創建階段,作用域鏈是在變量對象之后創建的。
確定this的值,即 ResolveThisBinding
作用域作用域負責收集和維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限?!?摘錄自《你不知道的JavaScript》(上卷)
作用域有兩種工作模型:詞法作用域和動態作用域,JS采用的是詞法作用域工作模型,詞法作用域意味著作用域是由書寫代碼時變量和函數聲明的位置決定的。(with 和 eval 能夠修改詞法作用域,但是不推薦使用,對此不做特別說明)
作用域分為:
全局作用域
函數作用域
塊級作用域
JS執行上下文棧(后面簡稱執行棧)執行棧,也叫做調用棧,具有 LIFO (后進先出) 結構,用于存儲在代碼執行期間創建的所有執行上下文。
規則如下:
首次運行JavaScript代碼的時候,會創建一個全局執行的上下文并Push到當前的執行棧中,每當發生函數調用,引擎都會為該函數創建一個新的函數執行上下文并Push當前執行棧的棧頂。
當棧頂的函數運行完成后,其對應的函數執行上下文將會從執行棧中Pop出,上下文的控制權將移動到當前執行棧的下一個執行上下文。
以一段代碼具體說明:
function fun3() { console.log("fun3") } function fun2() { fun3(); } function fun1() { fun2(); } fun1();
Global Execution Context (即全局執行上下文)首先入棧,過程如下:
偽代碼:
//全局執行上下文首先入棧 ECStack.push(globalContext); //執行fun1(); ECStack.push(作用域鏈functionContext); //fun1中又調用了fun2; ECStack.push( functionContext); //fun2中又調用了fun3; ECStack.push( functionContext); //fun3執行完畢 ECStack.pop(); //fun2執行完畢 ECStack.pop(); //fun1執行完畢 ECStack.pop(); //javascript繼續順序執行下面的代碼,但ECStack底部始終有一個 全局上下文(globalContext);
作用域鏈就是從當前作用域開始一層一層向上尋找某個變量,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關系,就是作用域鏈。
如:
var a = 10; function fn1() { var b = 20; console.log(fn2) function fn2() { a = 20 } return fn2; } fn1()();
fn2作用域鏈 = [fn2作用域, fn1作用域,全局作用域]
8. 什么是BFC?BFC的布局規則是什么?如何創建BFC?(2019-05-29) 什么是BFCBFC 是 Block Formatting Context 的縮寫,即塊格式化上下文。我們來看一下CSS2.1規范中對 BFC 的說明。
Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with "overflow" other than "visible" (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.浮動、絕對定位的元素、非塊級盒子的塊容器(如inline-blocks、table-cells 和 table-captions),以及overflow的值不為visible(該值已傳播到視區時除外)為其內容建立新的塊格式上下文。
因此,如果想要深入的理解BFC,我們需要了解以下兩個概念:
1.BoxBox2.Formatting Context
Box 是 CSS 布局的對象和基本單位,頁面是由若干個Box組成的。
元素的類型 和 display 屬性,決定了這個 Box 的類型。不同類型的 Box 會參與不同的 Formatting Context。
Formatting ContextFormatting Context 是頁面的一塊渲染區域,并且有一套渲染規則,決定了其子元素將如何定位,以及和其它元素的關系和相互作用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),FFC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 為 CC3 中新增。
BFC布局規則BFC內,盒子依次垂直排列。
BFC內,兩個盒子的垂直距離由 margin 屬性決定。屬于同一個BFC的兩個相鄰Box的margin會發生重疊【符合合并原則的margin合并后是使用大的margin】
BFC內,每個盒子的左外邊緣接觸內部盒子的左邊緣(對于從右到左的格式,右邊緣接觸)。即使在存在浮動的情況下也是如此。除非創建新的BFC。
BFC的區域不會與float box重疊。
BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素。反之也如此。
計算BFC的高度時,浮動元素也參與計算。
如何創建BFC根元素
浮動元素(float 屬性不為 none)
position 為 absolute 或 relative
overflow 不為 visible 的塊元素
display 為 inline-block, table-cell, table-caption
BFC的應用1.防止 margin 重疊
兩個div直接的 margin 是50px,發生了 margin 的重疊。
根據BFC規則,同一個BFC內的兩個兩個相鄰Box的 margin 會發生重疊,因此我們可以在div外面再嵌套一層容器,并且觸發該容器生成一個 BFC,這樣 就會屬于兩個 BFC,自然也就不會再發生 margin 重疊
2.清除內部浮動
container 的高度沒有被撐開,如果我們希望 container 的高度能夠包含浮動元素,那么可以創建一個新的 BFC,因為根據 BFC 的規則,計算 BFC 的高度時,浮動元素也參與計算。
3.自適應多欄布局
根據規則,BFC的區域不會與float box重疊。因此,可以觸發生成一個新的BFC,如下:9. let、const、var 的區別有哪些?(2019-05-30)
聲明方式 | 變量提升 | 暫時性死區 | 重復聲明 | 塊作用域有效 | 初始值 | 重新賦值 |
---|---|---|---|---|---|---|
var | 會 | 不存在 | 允許 | 不是 | 非必須 | 允許 |
let | 不會 | 存在 | 不允許 | 是 | 非必須 | 允許 |
const | 不會 | 存在 | 不允許 | 是 | 必須 | 不允許 |
a = 10; var a; //正常
a = 10; let a; //ReferenceError2.相同作用域中,let 和 const 不允許重復聲明,var 允許重復聲明。
let a = 10; var a = 20; //拋出異常:SyntaxError: Identifier "a" has already been declared3.cosnt 聲明變量時必須設置初始值
const a;//SyntaxError: Missing initializer in const declaration4.const 聲明一個只讀的常量,這個常量不可改變。
這里有一個非常重要的點即是:復雜數據類型,存儲在棧中的是堆內存的地址,存在棧中的這個地址是不變的,但是存在堆中的值是可以變得。有沒有相當常量指針/指針常量~
const a = 20; const b = { age: 18, star: 500 }
一圖勝萬言,如下圖所示,不變的是棧內存中 a 存儲的 20,和 b 中存儲的 0x0012ff21(瞎編的一個數字)。而 {age: 18, star: 200} 是可變的。思考下如果想希望一個對象是不可變的,應該用什么方法?
5.let/const 聲明的變量僅在塊級作用域中有效。而 var 聲明的變量在塊級作用域外仍能訪問到。{ let a = 10; const b = 20; var c = 30; } console.log(a); //ReferenceError console.log(b); //ReferenceError console.log(c); //30
在 let/const 之前,最早學習JS的時候,也曾被下面這個問題困擾:
期望: a[0]() 輸出 0 , a[1]() 輸出 1 , a[2]() 輸出 2 , ...
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
雖然后來知道了為什么,但是想要得到自己需要的結果,還得整個閉包,我...我做錯了什么,要這么對我...
var a = []; for (var i = 0; i < 10; i++) { a[i] = (function(j){ return function () { console.log(j); } })(i) } a[6](); // 6
有了 let 之后,終于不要這么麻煩了。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
美滋滋,有沒有~
美是美了,但是總得問自己為什么吧~
var i 為什么輸出的是 10,這是因為 i 在全局范圍內都是有效的,相當于只有一個變量 i,等執行到 a[6]() 的時候,這個 i 的值是什么?請大聲說出來。
再看 let , 我們說 let 聲明的變量僅在塊級作用域內有效,變量i是let聲明的,當前的 i 只在本輪循環有效,所以每一次循環的 i 其實都是一個新的變量。有興趣的小伙伴可以查看 babel 編譯后的代碼。
6.頂層作用域中 var 聲明的變量掛在window上(瀏覽器環境)var a = 10; console.log(window.a);//107.let/const有暫時性死區的問題,即let/const 聲明的變量,在定義之前都是不可用的。如果使用會拋出錯誤。
只要塊級作用域內存在let命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。
var a = 10; if (true) { a = 20; // ReferenceError let a; }
在代碼塊內,使用 let/const 命令聲明變量之前,該變量都是不可用的,也就意味著 typeof 不再是一個百分百安全的操作。
console.log(typeof b);//undefined console.log(a); //ReferenceError let a = 10;10. 深拷貝和淺拷貝的區別是什么?如何實現一個深拷貝?(2019-05-31)
深拷貝和淺拷貝是針對復雜數據類型來說的。
深拷貝深拷貝復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制。 深拷貝后的對象與原來的對象是完全隔離的,互不影響,對一個對象的修改并不會影響另一個對象。淺拷貝
淺拷貝是會將對象的每個屬性進行依次復制,但是當對象的屬性值是引用類型時,實質復制的是其引用,當引用指向的值改變時也會跟著變化。
可以使用 for in、 Object.assign、 擴展運算符 ... 、Array.prototype.slice()、Array.prototype.concat() 等,例如:
let obj = { name: "Yvette", age: 18, hobbies: ["reading", "photography"] } let obj2 = Object.assign({}, obj); let obj3 = {...obj}; obj.name = "Jack"; obj.hobbies.push("coding"); console.log(obj);//{ name: "Jack", age: 18,hobbies: [ "reading", "photography", "coding" ] } console.log(obj2);//{ name: "Yvette", age: 18,hobbies: [ "reading", "photography", "coding" ] } console.log(obj3);//{ name: "Yvette", age: 18,hobbies: [ "reading", "photography", "coding" ] }
可以看出淺拷貝只最第一層屬性進行了拷貝,當第一層的屬性值是基本數據類型時,新的對象和原對象互不影響,但是如果第一層的屬性值是復雜數據類型,那么新對象和原對象的屬性值其指向的是同一塊內存地址。來看一下使用 for in 實現淺拷貝。
let obj = { name: "Yvette", age: 18, hobbies: ["reading", "photography"] } let newObj = {}; for(let key in obj){ newObj[key] = obj[key]; //這一步不需要多說吧,復雜數據類型棧中存的是對應的地址,因此賦值操作,相當于兩個屬性值指向同一個內存空間 } console.log(newObj); //{ name: "Yvette", age: 18, hobbies: [ "reading", "photography" ] } obj.age = 20; obj.hobbies.pop(); console.log(newObj); //{ name: "Yvette", age: 18, hobbies: [ "reading" ] }深拷貝實現
1.深拷貝最簡單的實現是: JSON.parse(JSON.stringify(obj))
let obj = { name: "Yvette", age: 18, hobbies: ["reading", "photography"] } let newObj = JSON.parse(JSON.stringify(obj));//newObj和obj互不影響 obj.hobbies.push("coding"); console.log(newObj);//{ name: "Yvette", age: 18, hobbies: [ "reading", "photography" ] }
JSON.parse(JSON.stringify(obj)) 是最簡單的實現方式,但是有一點缺陷:
1.對象的屬性值是函數時,無法拷貝。
let obj = { name: "Yvette", age: 18, hobbies: ["reading", "photography"], sayHi: function() { console.log(sayHi); } } let newObj = JSON.parse(JSON.stringify(obj)); console.log(newObj);//{ name: "Yvette", age: 18, hobbies: [ "reading", "photography" ] }
2.原型鏈上的屬性無法獲取
function Super() { } Super.prototype.location = "NanJing"; function Child(name, age, hobbies) { this.name = name; this.age = age; } Child.prototype = new Super(); let obj = new Child("Yvette", 18); console.log(obj.location); //NanJing let newObj = JSON.parse(JSON.stringify(obj)); console.log(newObj);//{ name: "Yvette", age: 18} console.log(newObj.location);//undefined;原型鏈上的屬性無法獲取
3.不能正確的處理 Date 類型的數據
4.不能處理 RegExp
5.會忽略 symbol
6.會忽略 undefined
let obj = { time: new Date(), reg: /d{3}/, sym: Symbol(10), name: undefined } let obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj2); //{ time: "2019-06-02T08:16:44.625Z", reg: {} }
2.實現一個 deepClone 函數
如果是基本數據類型,直接返回
如果是 RegExp 或者 Date 類型,返回對應類型
如果是復雜數據類型,遞歸。
function deepClone(obj) { //遞歸拷貝 if(obj instanceof RegExp) return new RegExp(obj); if(obj instanceof Date) return new Date(obj); if(obj === null || typeof obj !== "object") { //如果不是復雜數據類型,直接返回 return obj; } /** * 如果obj是數組,那么 obj.constructor 是 [Function: Array] * 如果obj是對象,那么 obj.constructor 是 [Function: Object] */ let t = new obj.constructor(); for(let key in obj) { //如果 obj[key] 是復雜數據類型,遞歸 if(obj.hasOwnProperty(key)){//是否是自身的屬性 t[key] = deepClone(obj[key]); } } return t; }
測試:
function Super() { } Super.prototype.location = "NanJing"; function Child(name, age, hobbies) { this.name = name; this.age = age; this.hobbies = hobbies; } Child.prototype = new Super(); let obj = new Child("Yvette", 18, ["reading", "photography"]); obj.sayHi = function () { console.log("hi"); } console.log(obj.location); //NanJing let newObj = deepClone(obj); console.log(newObj);// console.log(newObj.location);//NanJing 可以獲取到原型鏈上的屬性 newObj.sayHi();//hi 函數屬性拷貝正常參考文章:
[1] https://www.ecma-internationa...
[2] 【譯】理解 Javascript 執行上下文和執行棧
[3] https://css-tricks.com/deboun...
[4] https://github.com/mqyqingfen...
[5] https://www.cnblogs.com/coco1...
[6] https://www.cnblogs.com/wangf...
[7] https://www.w3.org/TR/2011/RE...
[8] https://github.com/mqyqingfen...
謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
關注公眾號,加入技術交流群。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/114749.html
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。即使這個時間周期內,小明取得多次滿分。創建作用域鏈在執行期上下文的創建階段,作用域鏈是在變量對象之后創建的。這種一層一層的關系,就是作用域鏈。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答...
摘要:禁止內聯腳本執行規則較嚴格,目前發現使用。合理使用上報可以及時發現,利于盡快修復問題。因為事件會從目標元素一層層冒泡至對象。允許給一個事件注冊多個監聽。表示在捕獲階段觸發,表示在冒泡階段觸發。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的答案,整理最一份...
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。那個率先改變的實例的返回值,就傳遞給的回調函數。通過插入標簽的方式來實現跨域,參數只能通過傳入,僅能支持請求。因此清除浮動,只需要觸發一個即可。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的...
閱讀 1090·2021-10-08 10:04
閱讀 3528·2021-08-05 10:01
閱讀 2286·2019-08-30 11:04
閱讀 1805·2019-08-29 15:29
閱讀 852·2019-08-29 15:12
閱讀 1677·2019-08-26 12:11
閱讀 3125·2019-08-26 11:33
閱讀 1170·2019-08-26 10:23