Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2019-10-12] 進階 JavaScript - Closure #8

Open
gracekrcx opened this issue Oct 13, 2019 · 8 comments
Open

[2019-10-12] 進階 JavaScript - Closure #8

gracekrcx opened this issue Oct 13, 2019 · 8 comments
Labels
JS201 JS201

Comments

@gracekrcx
Copy link
Owner

gracekrcx commented Oct 13, 2019

Closure 是什麼?

  1. 在一個 function 裡 return 另一個 function
  2. 從外面呼叫裏面 function 去改變裏面的 variable,而這個 variable 是有記憶性的,可以 keep 住一些東西
// Closure 範例

function complex(num){
 // 複雜的計算
 console.log('cal')
 return num * num * num
}

function cache(func) {
 var ans = {}  // 有記憶性的變數
 return function(num){ // 要被回傳的 func
  if( ans['num']){
    return ans['num'] 
   }
  ans['num'] = func(num) // 藉由參數傳入的 func
  return ans['num'] 
 }
}
console.log(complex(20))
console.log(complex(20))

const cachedComplex = cache(complex) 
// cache 可以想成是一個有記憶性的機器,可以傳入任何的 function
// cachedComplex 會接收 cache return 回來的 function

console.log(cachedComplex(40))  // 計算
console.log(cachedComplex(40))  // 不計算,直接輸出結果

Closure 為什麼可以 keep 住一些值

因為 EC 裡面的 scopeChain

(every EC has associated with a scope chain. When control enters an EC, a scope chain is created.
每一個 EC 都有一個 scope chain)

從 ECMAScript 看作用域

Activation Object : AO
Variable Object : VO
AO 和 VO 可以看成是一樣的東西,差別很細微,在 global 是 VO 在 function 是 AO

global EC: {
 VO: {

 }
}

function EC: {
 AO: {
  a: undefined
  func: func
 },
 scopeChain: [function EC.AO, [[Scope]]]
}
// 題目
var a = 1
function test(){
 var b = 2
 function inner() {
  var c = 3
  // [innerEC.AO --> 找不到, testEC.AO --> 找到 b = 2 , ....]
  console.log(b) // b = 2
  // [innerEC.AO --> 找不到, testEC.AO --> 找不到, globalEC.VO --> 找到 a = 1]
  console.log(a) // a = 1
 }
 inner()
}

test()
// 拆解
innerEC:{
 AO:{
  c: 3
 },
 scopeChain: [innerEC.AO, inner.[[Scope]]]
 // = [innerEC.AO, testEC.scopeChain]
 // = [innerEC.AO, testEC.AO, globalEC.VO]
}

testEC: {
 AO: {
  b: 2,
  inner: func
 },
 scopeChain: [testEC.AO, test.[[Scope]]]
 // 連結的感覺,[自己的AO, 上層的VO] 
 // scopeChain: [testEC.AO, globalEC.VO]
 // scopeChain: [testEC.AO, { a: 1, test: func }]
}

inner.[[Scope]] = testEC.scopeChain
// 其實就是等於這個 [testEC.AO, globalEC.VO]


globalEC: {
 VO: {
  a: 1,
  test: func
 },
 scopeChain: [globalEC.VO]
}
test.[[Scope]] = globalEC.scopeChain // globalEC.VO
// test 這個 function 的隱藏屬性 [[Scope]]
// 只要是 function 的宣告就會有隱藏屬性 [[Scope]] 

其實 scopeChain 就是他每一個上層的 Execution Context 裏面的 AO 或 VO 物件

  1. closure 其實就是因為 scopeChain 有 reference 到其他 Execution Context 的 AO 或是 VO,而變數就紀錄在AO 或 VO 裡,也因為 return 把 AO 和 VO 保留下來,所以在離開之後還是可以存取到上層的變數

  2. 如果你是以會記住上層資訊的角度來看 closure,那所有的 function 都是 closure

// 範例
var arr = []
var i
for( i=0; i<5; i++) {
 arr[i] = function(){
  console.log(i)
 }
}

arr[0]() // 5  當執行到這裡時,全域的 i 已經是 5 了

/*
改 let 就沒事了,每一圈都有自己的作用域
{
 let i = 0
 arr[0] =function(){
  console.log(i)
 }
}
{
 let i = 0
 arr[1] =function(){
  console.log(i)
 }
}
*/

// 範例修改
var arr = []
for(var i=0; i<5; i++) {
 arr[i] = logN(i)
}

function logN(n){
 return function(){
  console.log(n)  // 多一個作用域去存值
 }
}

arr[0]()

// 為了讓每個 i 都有自己的 EC 所以用 function
// 當 LogN 被呼叫時 
// 會在 VO 裡初始參數 n : 1
// 然後回傳 function 和 神奇箱
// 所以當 arr[1]() 呼叫時,就是執行 return 的 function 然後參數在 神奇箱 裡

Closure 可以應用在哪裡?

範例:(如此寫的原因)不想讓外面可以直接賦值 count 這個變數,所以寫了一個 function 給外面操控

// closure 之前
var count = 0
function addCount() {
 count++
 return count
}

console.log(addCount())
count = 10000     //  <----- 直接操控變數
console.log(addCount())

一個 function (作用域) 裡有一個儲存箱和一個 function 的故事

// closure 之後

function createCounter() {
 // 儲存箱
 var count = 0
 function addCount() {
  count++
  // 這個 function 會回傳外面箱子的值
  return count
 }
  // 回傳一個 function
  return addCount
}

createCounter()
// ƒ addCount() {
//  count++
//  return count

var counter = createCounter()  // ♥♥♥ 把 function addCount 的 scopeChain 帶到了 global
console.log(counter())
console.log(counter())
console.log(counter())
console.log(counter())

隱藏著某些資訊

function creatWallet(initMoney) {
 let money = initMoney
 
 return {
  add: function(num){
   money += num
  },
  deduct : function(num){
   if(num > 10){
    money -= 10
   } else {
    money - num
   }
  },
  getMoney(){
   return money
  }
 }
}

var myVallet = creatWallet(100)   // 這裡的回傳是物件,裡面包含 3 個 function
myVallet.add(100)
myVallet.deduct(20)
myVallet.getMoney()

參考資源

所有的函式都是閉包:談 JS 中的作用域與 Closure

@gracekrcx
Copy link
Owner Author

gracekrcx commented May 24, 2020

function mul(x) {
    return (y) => {
        console.log('x', x)  // 2
        console.log('y', y)  // 11
        return x * y
    }
}
mul(2)(11)  // 22
  1. The mul is now a higher-order function because it returns a function
  2. This means we can use it to build other, more specific functions by using different arguments.

mul 是一個 HO function,他回傳了一個 function,所以現在可以再使用另一個 argument 去執行 function

@gracekrcx
Copy link
Owner Author

what are the benefits of HOs?

  1. 解決 repeating logic over and over.
  2. 所以把重複的 logic 放在一個相同的地方去做使用

@gracekrcx
Copy link
Owner Author

const numbers = [1, 5, 8, 10, 21]

const createAddingFunction = x => arr => {
 return arr.map(item => item + x)
}

const numbersPlusOne = createAddingFunction(1)

console.log(numbersPlusOne(numbers)) 
//[2, 6, 9, 11, 22]

@gracekrcx
Copy link
Owner Author

  1. 很複雜,重複在做的那個 function,就可以考慮使用 Closure
  2. 因為重複在做那個 function,所以結果有可能一直重複,所以就用一個神奇的箱子把結果記下來
  3. return 回來的那個 function 就包含了一直重複要做的那個 function 和神奇的記憶箱

@gracekrcx gracekrcx added the JS201 JS201 label Jun 15, 2020
@gracekrcx
Copy link
Owner Author

gracekrcx commented Mar 31, 2021

Hooks 運用了 closure 記住 states
Getting Closure on React Hooks by Shawn Wang | JSConf.Asia 2019

@gracekrcx
Copy link
Owner Author

gracekrcx commented Apr 1, 2021

let add = (a) => a*2
let discount = (a) => a*-2
let order = (init)=>{
  let money = init
  return function(func, num){
    money += func(num)
    return money
  }
}
let initMoney = 1000
let b = order(initMoney)
// 靠 order() return 回來的 scope (function) 產生 closure
// 只有一個 instance : b
 
console.log(b(add,20))
console.log(b(add,20))
console.log(b(discount,20))

@gracekrcx
Copy link
Owner Author

gracekrcx commented Jul 27, 2022

// Getting Closure on React Hooks by Shawn Wang
// [Closure] makes it possible for a function to have "private" variables - W3Schools
// 跟到一半

const React =(
  function(){
    let hooks =[] 
    let idx = 0
    function useState(initVal){
      const state =  hooks[idx] || initVal
      const _idx = idx
      const setState = newVal => {
        hooks[_idx] = newVal
      }
      idx++
      return [state, setState]
    }
    function render(Component){
      idx = 0
      const C = Component()
      C.render()
      return C
    }
    function useEffect(cb, depArray){
      const oldDeps = hooks[idx]
      let hasChange = true
      // detect change
      if(oldDeps){
        hasChange = depArray.some(
          (dep, i)=> !Object.is(dep, oldDeps[i]))
      }
      if(hasChange) cb()
      hooks[idx] = depArray
      idx++
    }
    return {useState, render, useEffect}
  }
)()

function Component(){
  
  const [count, setCount] = React.useState(1)
  const [text, setText] = React.useState('apple')
  React.useEffect(()=>{
    console.log('jscfffff')
  },[count]) 
  return {
    render: ()=> console.log({count,text}),
    click: ()=> setCount(count + 1),
    type: word=> setText(word)
  }
}

var App = React.render(Component)
App.click()
var App = React.render(Component)
App.type('foo')
var App = React.render(Component)
 

 

@gracekrcx
Copy link
Owner Author

Example

function addAcount(){
  let box = 0
  return function(val){
    box += val
    return box
  }
}

let addMemoy = addAcount()

let res = addMemoy(100)
res = addMemoy(200)
res = addMemoy(300)
res = addMemoy(400)
console.log('res--->:', res)
function foo(){
  let box = 0
  return function(){
    box+=1
    return box
  }
}

let plus1 = foo()

console.log(plus1())
console.log(plus1())
console.log(plus1())
console.log(plus1())
console.log(plus1())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS201 JS201
Projects
None yet
Development

No branches or pull requests

1 participant