一週彙總
1.如何正確判斷this的指向?
2.JS中原始型別有哪幾種?null 是物件嗎?原始資料型別和複雜資料型別有什麼區別?
3.說一說你對HTML5語義化的理解
4.如何讓 (a == 1 && a == 2 && a == 3) 的值為true?
5.防抖(debounce)函式的作用是什麼?有哪些應用場景,請實現一個防抖函式。
更多優質文章可戳: github.com/YvetteLau/B…
1.如何正確判斷this的指向?(2019-05-20)
如果用一句話說明 this 的指向,那麼即是: 誰呼叫它,this 就指向誰。
但是僅通過這句話,我們很多時候並不能準確判斷 this 的指向。因此我們需要藉助一些規則去幫助自己:
this 的指向可以按照以下順序判斷:
全域性環境中的 this
瀏覽器環境:無論是否在嚴格模式下,在全域性執行環境中(在任何函式體外部)this 都指向全域性物件 window
;
node 環境:無論是否在嚴格模式下,在全域性執行環境中(在任何函式體外部),this 都是空物件 {}
;
是否是 new
繫結
如果是 new
繫結,並且建構函式中沒有返回 function 或者是 object,那麼 this 指向這個新物件。如下:
建構函式返回值不是 function 或 object。
function Super(age) {
this.age = age;
}
let instance = new Super('26');
console.log(instance.age); //26
複製程式碼
建構函式返回值是 function 或 object,這種情況下 this 指向的是返回的物件。
function Super(age) {
this.age = age;
let obj = {a: '2'};
return obj;
}
let instance = new Super('hello');
console.log(instance.age); //undefined
複製程式碼
你可以想知道為什麼會這樣?我們來看一下 new
的實現原理:
- 建立一個新物件。
- 這個新物件會被執行
[[原型]]
連線。 - 屬性和方法被加入到 this 引用的物件中。並執行了建構函式中的方法.
- 如果函式沒有返回其他物件,那麼 this 指向這個新物件,否則 this 指向建構函式中返回的物件。
function new(func) {
let target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
//排除 null 的情況
if (res && typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}
複製程式碼
函式是否通過 call,apply 呼叫,或者使用了 bind 繫結,如果是,那麼this繫結的就是指定的物件【歸結為顯式繫結】。
function info(){
console.log(this.age);
}
var person = {
age: 20,
info
}
var age = 28;
var info = person.info;
info.call(person); //20
info.apply(person); //20
info.bind(person)(); //20
複製程式碼
這裡同樣需要注意一種特殊情況,如果 call,apply 或者 bind 傳入的第一個引數值是 undefined
或者 null
,嚴格模式下 this 的值為傳入的值 null /undefined。非嚴格模式下,實際應用的預設繫結規則,this 指向全域性物件(node環境為global,瀏覽器環境為window)
function info(){
//node環境中:非嚴格模式 global,嚴格模式為null
//瀏覽器環境中:非嚴格模式 window,嚴格模式為null
console.log(this);
console.log(this.age);
}
var person = {
age: 20,
info
}
var age = 28;
var info = person.info;
//嚴格模式丟擲錯誤;
//非嚴格模式,node下輸出undefined(因為全域性的age不會掛在 global 上)
//非嚴格模式。瀏覽器環境下輸出 28(因為全域性的age會掛在 window 上)
info.call(null);
複製程式碼
隱式繫結,函式的呼叫是在某個物件上觸發的,即呼叫位置上存在上下文物件。典型的隱式呼叫為: xxx.fn()
function info(){
console.log(this.age);
}
var person = {
age: 20,
info
}
var age = 28;
person.info(); //20;執行的是隱式繫結
複製程式碼
預設繫結,在不能應用其它繫結規則時使用的預設規則,通常是獨立函式呼叫。
非嚴格模式: node環境,執行全域性物件 global,瀏覽器環境,執行全域性物件 window。
嚴格模式:執行 undefined
function info(){
console.log(this.age);
}
var age = 28;
//嚴格模式;拋錯
//非嚴格模式,node下輸出 undefined(因為全域性的age不會掛在 global 上)
//非嚴格模式。瀏覽器環境下輸出 28(因為全域性的age不會掛在 window 上)
//嚴格模式丟擲,因為 this 此時是 undefined
info();
複製程式碼
箭頭函式的情況:
箭頭函式沒有自己的this,繼承外層上下文繫結的this。
let obj = {
age: 20,
info: function() {
return () => {
console.log(this.age); //this繼承的是外層上下文繫結的this
}
}
}
let person = {age: 28};
let info = obj.info();
info(); //20
let info2 = obj.info.call(person);
info2(); //28
複製程式碼
2.JS中原始型別有哪幾種?null 是物件嗎?原始資料型別和複雜資料型別有什麼區別?(2019-05-21)
目前,JS原始型別有六種,分別為:
- Boolean
- String
- Number
- Undefined
- Null
- Symbol(ES6新增)
ES10新增了一種基本資料型別:BigInt
複雜資料型別只有一種: Object
null 不是一個物件,儘管 typeof null
輸出的是 object
,這是一個歷史遺留問題,JS 的最初版本中使用的是 32 位系統,為了效能考慮使用低位儲存變數的型別資訊,000 開頭代表是物件,null
表示為全零,所以將它錯誤的判斷為 object
。
基本資料型別和複雜資料型別的區別為:
- 記憶體的分配不同
-
基本資料型別儲存在棧中。
-
複雜資料型別儲存在堆中,棧中儲存的變數,是指向堆中的引用地址。
- 訪問機制不同
- 基本資料型別是按值訪問
- 複雜資料型別按引用訪問,JS不允許直接訪問儲存在堆記憶體中的物件,在訪問一個物件時,首先得到的是這個物件在棧記憶體中的地址,然後再按照這個地址去獲得這個物件中的值。
- 複製變數時不同(a=b)
- 基本資料型別:a=b;是將b中儲存的原始值的副本賦值給新變數a,a和b完全獨立,互不影響
- 複雜資料型別:a=b;將b儲存的物件記憶體的引用地址賦值給了新變數a;a和b指向了同一個堆記憶體地址,其中一個值發生了改變,另一個也會改變。
let b = {
age: 10
}
let a = b;
a.age = 20;
console.log(b); //{ age: 20 }
複製程式碼
- 引數傳遞的不同(實參/形參)
函式傳參都是按值傳遞(棧中的儲存的內容):基本資料型別,拷貝的是值;複雜資料型別,拷貝的是引用地址
//基本資料型別
let b = 10
function change(info) {
info=20;
}
//info=b;基本資料型別,拷貝的是值得副本,二者互不干擾
change(b);
console.log(b);//10
複製程式碼
//複雜資料型別
let b = {
age: 10
}
function change(info) {
info.age = 20;
}
//info=b;根據第三條差異,可以看出,拷貝的是地址的引用,修改互相影響。
change(b);
console.log(b);//{ age: 20 }
複製程式碼
3.說一說你對HTML5語義化的理解(2019-05-22)
語義化意味著顧名思義,HTML5的語義化指的是合理正確的使用語義化的標籤來建立頁面結構,如 header,footer,nav,從標籤上即可以直觀的知道這個標籤的作用,而不是濫用div。
語義化的優點有:
- 程式碼結構清晰,易於閱讀,利於開發和維護
- 方便其他裝置解析(如螢幕閱讀器)根據語義渲染網頁。
- 有利於搜尋引擎優化(SEO),搜尋引擎爬蟲會根據不同的標籤來賦予不同的權重
語義化標籤主要有:
- title:主要用於頁面的頭部的資訊介紹
- header:定義文件的頁首
- nav:主要用於頁面導航
- main:規定文件的主要內容。對於文件來說應當是唯一的。它不應包含在文件中重複出現的內容,比如側欄、導航欄、版權資訊、站點標誌或搜尋表單。
- article:獨立的自包含內容
- h1~h6:定義標題
- ul: 用來定義無序列表
- ol: 用來定義有序列表
- address:定義文件或文章的作者/擁有者的聯絡資訊。
- canvas:用於繪製影像
- dialog:定義一個對話方塊、確認框或視窗
- aside:定義其所處內容之外的內容。
<aside>
的內容可用作文章的側欄。 - section:定義文件中的節(section、區段)。比如章節、頁首、頁尾或文件中的其他部分。
- figure:規定獨立的流內容(影像、圖表、照片、程式碼等等)。figure 元素的內容應該與主內容相關,但如果被刪除,則不應對文件流產生影響。
- details:描述文件或者文件某一部分細節
- mark:義帶有記號的文字。
語義化應用
例如使用這些視覺化標籤,構建下面的頁面結構:
對於早期不支援 HTML5 的瀏覽器,如IE8及更早之前的版本,我們可以引入 html5shiv 來支援。
4.如何讓 (a == 1 && a == 2 && a == 3) 的值為true?
利用隱式轉換規則
==
操作符在左右資料型別不一致時,會先進行隱式轉換。
a == 1 && a == 2 && a == 3
的值意味著其不可能是基本資料型別。因為如果 a 是 null 或者是 undefined bool型別,都不可能返回true。
因此可以推測 a 是複雜資料型別,JS 中複雜資料型別只有 object
,回憶一下,Object 轉換為原始型別會呼叫什麼方法?
-
如果部署了
[Symbol.toPrimitive]
介面,那麼呼叫此介面,若返回的不是基本資料型別,丟擲錯誤。 -
如果沒有部署
[Symbol.toPrimitive]
介面,那麼根據要轉換的型別,先呼叫valueOf
/toString
- 非Date型別物件,
hint
是default
時,呼叫順序為:valueOf
>>>toString
,即valueOf
返回的不是基本資料型別,才會繼續呼叫toString
,如果toString
返回的還不是基本資料型別,那麼丟擲錯誤。 - 如果
hint
是string
(Date物件默人的hint是string) ,呼叫順序為:toString
>>>valueOf
,即toString
返回的不是基本資料型別,才會繼續呼叫valueOf
,如果valueOf
返回的還不是基本資料型別,那麼丟擲錯誤。 - 如果
hint
是number
,呼叫順序為:valueOf
>>>toString
- 非Date型別物件,
var obj = {
[Symbol.toPrimitive](hint) {
console.log(hint);
return 10;
},
valueOf() {
console.log('valueOf');
return 20;
},
toString() {
console.log('toString');
return 'hello';
}
}
console.log(obj + 'yvette'); //default
//如果沒有部署 [Symbol.toPrimitive]介面,呼叫順序為`valueOf` >>> `toString`
console.log(obj == 'yvette'); //default
//如果沒有部署 [Symbol.toPrimitive]介面,呼叫順序為`valueOf` >>> `toString`
console.log(obj * 10);//number
//如果沒有部署 [Symbol.toPrimitive]介面,呼叫順序為`valueOf` >>> `toString`
console.log(Number(obj));//number
//如果沒有部署 [Symbol.toPrimitive]介面,呼叫順序為`valueOf` >>> `toString`
console.log(String(obj));//string
//如果沒有部署 [Symbol.toPrimitive]介面,呼叫順序為`toString` >>> `valueOf`
複製程式碼
那麼對於這道題,只要 [Symbol.toPrimitive]
介面,第一次返回的值是 1,然後遞增,即成功成立。
let a = {
[Symbol.toPrimitive]: (function(hint) {
let i = 1;
//閉包的特性之一:i 不會被回收
return function() {
return i++;
}
})()
}
console.log(a == 1 && a == 2 && a == 3); //true
複製程式碼
呼叫 valueOf
介面的情況:
let a = {
valueOf: (function() {
let i = 1;
//閉包的特性之一:i 不會被回收
return function() {
return i++;
}
})()
}
console.log(a == 1 && a == 2 && a == 3); //true
複製程式碼
另外,除了i自增的方法外,還可以利用 正則,如下
let a = {
reg: /\d/g,
valueOf () {
return this.reg.exec(123)[0]
}
}
console.log(a == 1 && a == 2 && a == 3); //true
複製程式碼
呼叫 toString
介面的情況,不再做說明。
利用資料劫持
使用 Object.defineProperty
定義的屬性,在獲取屬性時,會呼叫 get
方法。利用這個特性,我們在 window
物件上定義 a
屬性,如下:
let i = 1;
Object.defineProperty(window, 'a', {
get: function() {
return i++;
}
});
console.log(a == 1 && a == 2 && a == 3); //true
複製程式碼
ES6 新增了 Proxy
,此處我們同樣可以利用 Proxy
去實現,如下:
let a = new Proxy({}, {
i: 1,
get: function () {
return () => this.i++;
}
});
console.log(a == 1 && a == 2 && a == 3); // true
複製程式碼
陣列的 toString
介面預設呼叫陣列的 join
方法,重寫陣列的 join
方法。
let a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); //true
複製程式碼
利用 with
關鍵字
我本人對 with
向來是敬而遠之的。不過 with
的確也是此題方法之一:
let i = 0;
with ({
get a() {
return ++i;
}
}) {
console.log(a == 1 && a == 2 && a == 3); //true
}
複製程式碼
5.防抖(debounce)函式的作用是什麼?有哪些應用場景,請實現一個防抖函式。
防抖函式的作用
防抖函式的作用就是控制函式在一定時間內的執行次數。防抖意味著N秒內函式只會被執行一次,如果N秒內再次被觸發,則重新計算延遲時間。
舉例說明:小思最近在減肥,但是她非常貪吃。為此,與其男朋友約定好,如果10天不吃零食,就可以購買一個包(不要問為什麼是包,因為包治百病)。但是如果中間吃了一次零食,那麼就要重新計算時間,直到小思堅持10天沒有吃零食,才能購買一個包。所以,管不住嘴的小思,沒有機會買包(悲傷的故事)...這就是防抖。
不管吃沒吃零食,每10天買一個包,中間想買包,忍著,等到第十天的時候再買,這種情況是節流。如何控制女朋友的消費,各位攻城獅們,get到了嗎?防抖可比節流有效多了!
防抖應用場景
- 搜尋框輸入查詢,如果使用者一直在輸入中,沒有必要不停地呼叫去請求服務端介面,等使用者停止輸入的時候,再呼叫,設定一個合適的時間間隔,有效減輕服務端壓力。
- 表單驗證
- 按鈕提交事件。
- 瀏覽器視窗縮放,resize事件等。
防抖函式實現
- 事件第一次觸發時,
timer
是null
,呼叫later()
,若immediate
為true
,那麼立即呼叫func.apply(this, params)
;如果immediate
為false
,那麼過wait
之後,呼叫func.apply(this, params)
- 事件第二次觸發時,如果
timer
已經重置為null
(即 setTimeout 的倒數計時結束),那麼流程與第一次觸發時一樣,若timer
不為null
(即 setTimeout 的倒數計時未結束),那麼清空定時器,重新開始計時。
function debounce(func, wait, immediate = true) {
let timer;
// 延遲執行函式
const later = (context, args) => setTimeout(() => {
timer = null;// 倒數計時結束
if (!immediate) {
func.apply(context, args);
//執行回撥
context = args = null;
}
}, wait);
let debounced = function (...params) {
let context = this;
let args = params;
if (!timer) {
timer = later(context, args);
if (immediate) {
//立即執行
func.apply(context, args);
}
} else {
clearTimeout(timer);
//函式在每個等待時延的結束被呼叫
timer = later(context, args);
}
}
debounced.cancel = function () {
clearTimeout(timer);
timer = null;
};
return debounced;
};
複製程式碼
immediate
為 true 時,表示函式在每個等待時延的開始被呼叫。
immediate
為 false 時,表示函式在每個等待時延的結束被呼叫。
只要高頻事件觸發,那麼回撥函式至少被呼叫一次。
參考文章:
[1] www.ecma-international.org/ecma-262/6.…
[2] 嗨,你真的懂this嗎?
謝謝各位小夥伴願意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。github.com/YvetteLau/B…