本週面試題一覽:javascript
更多優質文章可戳: github.com/YvetteLau/B…css
節流函數的做用是規定一個單位時間,在這個單位時間內最多隻能觸發一次函數執行,若是這個單位時間內屢次觸發函數,只能有一次生效。html
舉例說明:小明的媽媽和小明約定好,若是小明在週考中取得滿分,那麼當月能夠帶他去遊樂場玩,可是一個月最多隻能去一次。java
這其實就是一個節流的例子,在一個月的時間內,去遊樂場最多隻能觸發一次。即便這個時間週期內,小明取得屢次滿分。git
1.按鈕點擊事件github
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});
複製代碼
在開始說明JS上下文棧和做用域以前,咱們先說明下JS上下文以及做用域的概念。
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。
執行上下文類型分爲:
執行上下文建立過程當中,須要作如下幾件事:
做用域負責收集和維護由全部聲明的標識符(變量)組成的一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。—— 摘錄自《你不知道的JavaScript》(上卷)
做用域有兩種工做模型:詞法做用域和動態做用域,JS採用的是詞法做用域工做模型,詞法做用域意味着做用域是由書寫代碼時變量和函數聲明的位置決定的。(with
和 eval
可以修改詞法做用域,可是不推薦使用,對此不作特別說明)
做用域分爲:
執行棧,也叫作調用棧,具備 LIFO (後進先出) 結構,用於存儲在代碼執行期間建立的全部執行上下文。
規則以下:
以一段代碼具體說明:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
複製代碼
Global Execution Context
(即全局執行上下文)首先入棧,過程以下:
僞代碼:
//全局執行上下文首先入棧
ECStack.push(globalContext);
//執行fun1();
ECStack.push(<fun1> functionContext);
//fun1中又調用了fun2;
ECStack.push(<fun2> functionContext);
//fun2中又調用了fun3;
ECStack.push(<fun3> 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做用域,全局做用域]
BFC 是 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.Box
2.Formatting Context
Box 是 CSS 佈局的對象和基本單位,頁面是由若干個Box組成的。
元素的類型 和 display
屬性,決定了這個 Box 的類型。不一樣類型的 Box 會參與不一樣的 Formatting Context。
Formatting Context 是頁面的一塊渲染區域,而且有一套渲染規則,決定了其子元素將如何定位,以及和其它元素的關係和相互做用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),FFC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 爲 CC3 中新增。
margin
屬性決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊【符合合併原則的margin合併後是使用大的margin】1.防止 margin 重疊
<style> .a{ height: 100px; width: 100px; margin: 50px; background: pink; } </style>
<body>
<div class="a"></div>
<div class="a"></div>
</body>
複製代碼
兩個div直接的 margin
是50px,發生了 margin
的重疊。
根據BFC規則,同一個BFC內的兩個兩個相鄰Box的
margin
會發生重疊,所以咱們能夠在div外面再嵌套一層容器,而且觸發該容器生成一個 BFC,這樣<div class="a"></div>
就會屬於兩個 BFC,天然也就不會再發生margin
重疊
<style> .a{ height: 100px; width: 100px; margin: 50px; background: pink; } .container{ overflow: auto; /*觸發生成BFC*/ } </style>
<body>
<div class="container">
<div class="a"></div>
</div>
<div class="a"></div>
</body>
複製代碼
2.清除內部浮動
<style> .a{ height: 100px; width: 100px; margin: 10px; background: pink; float: left; } .container{ width: 120px; border: 2px solid black; } </style>
<body>
<div class="container">
<div class="a"></div>
</div>
</body>
複製代碼
container 的高度沒有被撐開,若是咱們但願 container 的高度可以包含浮動元素,那麼能夠建立一個新的 BFC,由於根據 BFC 的規則,計算 BFC 的高度時,浮動元素也參與計算。
<style> .a{ height: 100px; width: 100px; margin: 10px; background: pink; float: left; } .container{ width: 120px; display: inline-block;/*觸發生成BFC*/ border: 2px solid black; } </style>
複製代碼
3.自適應多欄佈局
<style> body{ width: 500px; } .a{ height: 150px; width: 100px; background: pink; float: left; } .b{ height: 200px; background: blue; } </style>
<body>
<div class="a"></div>
<div class="b"></div>
</body>
複製代碼
根據規則,BFC的區域不會與float box重疊。所以,能夠觸發生成一個新的BFC,以下:
<style> .b{ height: 200px; overflow: hidden; /*觸發生成BFC*/ background: blue; } </style>
複製代碼
聲明方式 | 變量提高 | 暫時性死區 | 重複聲明 | 塊做用域有效 | 初始值 | 從新賦值 |
---|---|---|---|---|---|---|
var | 會 | 不存在 | 容許 | 不是 | 非必須 | 容許 |
let | 不會 | 存在 | 不容許 | 是 | 非必須 | 容許 |
const | 不會 | 存在 | 不容許 | 是 | 必須 | 不容許 |
a = 10;
var a; //正常
複製代碼
a = 10;
let a; //ReferenceError
複製代碼
let a = 10;
var a = 20;
//拋出異常:SyntaxError: Identifier 'a' has already been declared
複製代碼
const a;//SyntaxError: Missing initializer in const declaration
複製代碼
這裏有一個很是重要的點便是:複雜數據類型,存儲在棧中的是堆內存的地址,存在棧中的這個地址是不變的,可是存在堆中的值是能夠變得。有沒有至關常量指針/指針常量~
const a = 20;
const b = {
age: 18,
star: 500
}
複製代碼
一圖勝萬言,以下圖所示,不變的是棧內存中 a 存儲的 20,和 b 中存儲的 0x0012ff21(瞎編的一個數字)。而 {age: 18, star: 200} 是可變的。思考下若是想但願一個對象是不可變的,應該用什麼方法?
{
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 編譯後的代碼。
var a = 10;
console.log(window.a);//10
複製代碼
只要塊級做用域內存在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;
複製代碼
深拷貝和淺拷貝是針對複雜數據類型來講的。
深拷貝複製變量值,對於非基本類型的變量,則遞歸至基本類型變量後,再複製。 深拷貝後的對象與原來的對象是徹底隔離的,互不影響,對一個對象的修改並不會影響另外一個對象。
淺拷貝是會將對象的每一個屬性進行依次複製,可是當對象的屬性值是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。
可使用 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 函數屬性拷貝正常
複製代碼
3.循環引用
前面的deepClone沒有考慮循環引用的問題,例如對象的某個屬性,是這個對象自己。
function deepClone(obj, hash = new WeakMap()) { //遞歸拷貝
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(obj === null || typeof obj !== 'object') {
//若是不是複雜數據類型,直接返回
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
/** * 若是obj是數組,那麼 obj.constructor 是 [Function: Array] * 若是obj是對象,那麼 obj.constructor 是 [Function: Object] */
let t = new obj.constructor();
hash.set(obj, t);
for(let key in obj) {
//若是 obj[key] 是複雜數據類型,遞歸
if(obj.hasOwnProperty(key)){//是不是自身的屬性
if(obj[key] && typeof obj[key] === 'object') {
t[key] = deepClone(obj[key], hash);
}else{
t[key] = obj[key];
}
}
}
return t;
}
複製代碼
測試代碼:
const obj1 = {
name: 'Yvetet',
sayHi: function(){
console.log('Hi')
},
time: new Date(),
info: {
}
}
obj1.circle = obj1;
obj1.info.base = obj1;
obj1.info.name = obj1.name;
console.log(deepClone(obj1));
複製代碼
[1] www.ecma-international.org/ecma-262/6.…
[2] 【譯】理解 Javascript 執行上下文和執行棧
[3] css-tricks.com/debouncing-…
[5] www.cnblogs.com/coco1s/p/40…
[6] www.cnblogs.com/wangfupeng1…
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。github.com/YvetteLau/B…