Table of contents
關注問題
this 的不同應用場景,如何取值?
手寫 bind 函數
知識點
作用域和自由變量
全局作用域
函數作用域
塊級作用域
閉包:兩種常見方式 & 自由變量查找規則
this
閉包
- 作用域應用特殊狀態,有兩種狀況
function create() {
let a = 100;
return function () {
console.log(a);
};
}
let fn = create();
let a = 200;
fn(); // 100
function print(fn) {
let a = 200;
fn();
}
let a = 100;
function fn() {
console.log(a);
}
print(fn); // 100
閉包:所有的自由變量(上面範例為 a)的查找,是在函数定義的地方,向上级作用域查找 不是在執行的地方!!
函數做為參數被傳遞
函數作為返回值被返回
閉包的應用
緩存機制:手寫 cache 緩存函數
function cached(fn) {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
} else {
const val = fn(...args);
cache[key] = val;
return val;
}
};
}
這段程式碼定義了一個名為 cached
的高階函式,它的參數是一個函式 fn
。cached
的作用是建立一個緩存(cache),用於存儲 fn
函式運算後的結果。由於緩存是使用閉包(closure)的方式實現的,因此 cache
變數將被儲存在函式的作用域中,不會因為函式的調用而被清除。
cached
函式內部定義了一個箭頭函式,它的作用是對於每一次函式調用,檢查傳入的引數(args)是否已經有過相同的呼叫,如果是,則從緩存(cache)中取回之前儲存的結果並直接返回,否則就將函式運算得到的結果放入緩存中,以便後續調用時可以直接返回。這樣就可以減少計算時間,提高效率。
具體地,cached
先聲明了一個空物件 cache
,用來存儲緩存內容。接著返回一個箭頭函式,該函式通過擴展運算符將所有的引數都收集到一個名為 args
的陣列中。函式將引數轉換為一個 JSON 字串,並使用該字串作為緩存的鍵(key)。
接下來,函式檢查是否有該鍵對應的緩存。如果是,直接返回緩存內容;否則,調用 fn
函式,得到結果值並將其存入緩存中,然後返回該值。
最終,當使用 cached
函式時,將需要緩存的函式作為引數傳遞給 cached
函式,返回的值是一個使用了緩存的新函式,可以在需要時重複使用該函式,而不必重新運算。
const key = JSON.stringify(args)
的作用?
const key = JSON.stringify(args);
這行程式碼的作用是把傳入的 args
陣列轉換成一個字串,用作緩存的鍵(key)。
以一個簡單的例子為例,假設我們有以下函式:
const add = (a, b) => a + b;
現在,我們使用 cached
函式來創建一個可以緩存結果的新函式:
const cachedAdd = cached(add);
當我們第一次使用 cachedAdd(2, 3)
時,由於緩存中沒有相應的結果,所以它會調用 add(2, 3)
,並將結果儲存在緩存中。此時 key
的值會是 "[2,3]"
,因為 args
陣列裡面只有兩個元素,分別是 2
和 3
,並使用 JSON.stringify
方法將其轉換成一個字串。
接下來,當我們再次使用 cachedAdd(2, 3)
時,緩存中已經有相應的結果,所以它會直接返回緩存中的值,而不需要再次調用 add
函式。這樣就可以節省計算時間,提高效率
...args 是什麼?
...args
是 JavaScript 中的擴展運算符,它可以將一個可迭代物件(例如陣列、字串、Map 或 Set 等)展開成多個獨立的值。在這段程式碼中,...args
操作符被用來將傳入 cached
函式的引數列表轉換為一個包含這些引數的陣列。
例如,在下面的程式碼中:
function sum(a, b, c) {
return a + b + c;
}
const args = [1, 2, 3];
console.log(sum(...args)); // 6
使用擴展運算符 ...args
將 args
陣列展開,傳入 sum
函式,相當於呼叫 sum(1, 2, 3)
,得到結果 6
。
在 cached
函式中,...args
用來將傳入 cached
函式的引數列表展開成一個陣列,這樣就可以使用 JSON.stringify
方法將其轉換為字串,作為 cache
物件的鍵。
回到 cache
程式碼中,使用 JSON.stringify
方法將 args
陣列轉換為字串,JSON.stringify
方法的作用是將一個 JavaScript 值轉換為一個 JSON 字串,而在 JSON 字串中,陣列的表示方式是使用方括號 []
包圍其內部的元素,元素之間使用逗號 ,
分隔。
因此,當 args
陣列的值為 [2, 3]
時,JSON.stringify(args)
的結果會是 "[2,3]"
,即一個字串,裡面包含了 args
陣列中的兩個元素 2
和 3
,並使用方括號包圍,元素之間使用逗號分隔。這樣做的好處是能夠保證 key
的唯一性,因為不同的引數會導致不同的 key
值,從而保證每個 key
只對應一個結果。
this 的不同應用場景,如何取值?
當作普通函數被調用時,是由調用的對象來決定,不是聲明的對象。所以影響
this
值不是宣告的時機,要看在什麼時候被調用。如果
this
不被當作函式的方法調用時,默認指向全局物件,也就是Window
。特別注意的是,在嚴格模式下,this
會為undefined
。下列程式碼要注意的是,使用 var 宣告才會將 player 變數綁定在全域物件
Window
上。使用let
宣告的話並不會綁定在Window
物件。var player = 'Faker' function selectPlayerPool(){ console.log(this) // window console.log(this.player) // Faker } selectPlayerPool()
作為物件方法調用時,
this
會指向物件本身const faker = { name:'Lee Sang-Hyeok', callFaker() { console.log(`3 champions record ${Lee Sang-Hyeok}`) } } faker.callFaker() // 3 champions record Lee Sang-Hyeok
構造函式調用,也就是使用
new
字符創建出新的物件,this
會指向這個物件本身bind, call, apply 方法調用
箭頭函式:箭頭函式沒有自己的
this
值,箭頭函式的 this 會繼承外在函式的 this,假如外在函式也是箭頭函式,就繼續用上級尋找,直到最後找到全域環境的this
。
手寫 bind
bind
也可以改變 this 指定的對象,這裡展示手寫 bind 幫助我們更加理解這個語法的作用
function fn1(a, b, c) {
console.log(this, "this");
console.log(a, b, c);
return "this is fn1";
}
Function.prototype.bind1 = function () {
// 將參數拆為數組
const args = Array.prototype.slice.call(arguments);
console.log(args, "args");
// 獲取 this (數組第一項)
const t = args.shift();
// this 指的就是 fn1
// this 在 class 之中,原型的函數,代表的就是對象的本身
// fn1.bind(...) 中的 fin1
const self = this;
// 返回一個函數
return function () {
return self.apply(t, args);
};
};
const fn2 = fn1.bind1({ x: 100 }, 10, 20, 30); // bind 返回函數
const res = fn2();
console.log("res", res); // this is fn1