1. 程式人生 > 實用技巧 >這次把JS閉包給你講得明明白白

這次把JS閉包給你講得明明白白

什麼是環境與作用域:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head
> <body> <h1>CYY</h1> <script> let name = 'cyy';//全域性環境,不會回收 </script> </body> </html>

函式的環境與作用域原理:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <
meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> <style> </style> </head> <body> <h1>CYY</h1> <script> let name = 'cyy';//全域性環境,不會回收 function func() { // 函式環境 let name
= 'cyy2'; } // 只在函式被呼叫時建立,函式執行結束後會被銷燬 // 函式外部不能使用函式內部的變數,函式內部可以使用函式外部的變數 </script> </body> </html>

延伸函式環境生命週期:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // 函式執行完之後,內部變數就會被銷燬,除非一直在被引用
        // function func() {
        //     let n = 1;
        //     function show() {
        //         console.log(++n);
        //     }
        //     show()
        // }
        // func(); func();


        // function func() {
        //     let n = 1;
        //     return function show() {
        //         console.log(++n);
        //     }
        //     show()
        // }
        // // 每次呼叫會建立新的記憶體地址
        // let res = func(); // 等於把show函式返回給外部
        // res(); res(); res(); res();
        // // 每次呼叫會建立新的記憶體地址
        // let res2 = func(); res2();


        // 不會遞增,因為render方法並沒有被外部引用,所以每次呼叫都會建立新的記憶體空間,無法儲存上一次的變數
        // function func() {
        //     let n = 1;
        //     return function show() {
        //         let m = 1;
        //         function render() {
        //             console.log(++m);
        //         }
        //         render()
        //     }
        //     show()
        // }
        // let res = func(); res(); res(); res(); res();

        function func() {
            let n = 1;
            return function show() {
                let m = 1;
                return function render() {
                    console.log('m:' + ++m);
                    console.log('n:' + ++n);
                }
                render()
            }
            show()
        }
        let res = func()(); // 等於把render函式返回給外部
        res(); res(); res(); res();
    </script>
</body>

</html>

建構函式中的作用域的使用形態:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // 建構函式中的方法一直在被引用,因此變數能夠儲存
        // function Show() {
        //     let n = 1;
        //     this.render = function () {
        //         console.log(++n);
        //     }
        // }
        // let s = new Show();
        // s.render();
        // s.render();

        // 與上述寫法類似
        // function Show() {
        //     let n = 1;
        //     function render() {
        //         console.log(++n);
        //     }
        //     return {
        //         render: render
        //     }
        // }
        // let s = new Show();
        // s.render();
        // s.render();


        function Show() {
            let n = 1;
            this.render = function () {
                console.log(++n);
            }
        }
        // 每次new都會開闢一塊新的空間,因此變數無法被繼續儲存
        let s = new Show(); s.render(); s.render();
        let s2 = new Show(); s2.render(); s2.render();
    </script>
</body>

</html>

什麼是塊級作用域:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // {
        //     let a = 1;
        //     console.log(a);
        // }
        // // console.log(a);

        // {
        //     let a = 1;
        //     console.log(a);
        // }

// var沒有塊級作用域
//         {
//             var a = 1;
//         }
//         console.log(a);
    </script>
</body>

</html>

let-var在for迴圈中的執行原理:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // var沒有塊級作用域,因此i是掛載到全域性的,每次迴圈都改變了全域性的i
        // for (var i = 0; i <= 3; i++) {
        //     console.log(111);
        // }
        // console.log(i);
        // console.log(window.i);

        // let有塊級作用域,因此只在for迴圈中,在全域性無法呼叫
        //         for (let i = 0; i <= 3; i++) {
        //             console.log(111);
        //         }
        //         console.log(i);

        for (var i = 0; i <= 3; i++) {
            setTimeout(function () {
                console.log(i);//一直輸出全域性的i,是4
            });
        }

        for (let i = 0; i <= 3; i++) {
            setTimeout(function () {
                console.log(i);
            });
        }
    </script>
</body>

</html>

模擬出var的偽塊作用域:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // var沒有塊級作用域,但是有函式作用域,因此可以把程式用函式包起來,匿名函式自執行
        for (var i = 0; i <= 3; i++) {
            (function (i) {
                setTimeout(function () {
                    console.log(i);
                });
            })(i);
        }
    </script>
</body>

</html>

多級作用域巢狀詳解:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // let arr = [];
        // for (let i = 0; i < 4; i++) {
        //     arr.push(function () {
        //         return i;
        //     });
        // }
        // console.log(arr);//在陣列中壓入4個函式,由於函式處於被引用狀態,因此內部的變數得以保留
        // console.log(arr[0]());// 執行陣列中第一個元素的函式
        // console.log(arr[1]());// 執行陣列中第一個元素的函式
        // console.log(arr[2]());// 執行陣列中第一個元素的函式


        // let arr = [];
        // for (var i = 0; i < 4; i++) {
        //     arr.push(function () {
        //         return i;
        //     });
        // }
        // console.log(arr);//在陣列中壓入4個函式,函式操作的i始終是全域性的
        // console.log(arr[0]());// 執行陣列中第一個元素的函式
        // console.log(arr[1]());// 執行陣列中第一個元素的函式
        // console.log(arr[2]());// 執行陣列中第一個元素的函式


        // 使用函式作用域使得i不再是全域性
        let arr = [];
        for (var i = 0; i < 4; i++) {
            (function (i) {
                arr.push(function () {
                    return i;
                });
            })(i);

        }
        console.log(arr);//在陣列中壓入4個函式,函式操作的i始終是全域性的
        console.log(arr[0]());// 執行陣列中第一個元素的函式
        console.log(arr[1]());// 執行陣列中第一個元素的函式
        console.log(arr[2]());// 執行陣列中第一個元素的函式
    </script>
</body>

</html>

什麼是閉包:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // 閉包
        function func() {
            let n = 1;
            return function sum() {
                console.log(++n);
            }
        }
        let a = func();
        a(); a(); a(); a();
    </script>
</body>

</html>

使用閉包獲取區間商品:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>

    </style>
</head>

<body>

    <script>
        // let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        // let res = arr.filter(item => item <= 7 && item >= 3);
        // console.log(res);

        // let res2 = arr.filter(item => item <= 6 && item >= 1);
        // console.log(res2);

        // // 閉包可以訪問到父級的變數
        // function between(a, b) {
        //     return function (item) {
        //         return item <= a && item >= b;
        //     }
        // }
        // let res3 = arr.filter(between(7, 3));
        // console.log(res3);
        // let res4 = arr.filter(between(5, 2));
        // console.log(res4);

        let lessons = [
            { title: 'title1', price: 100, click: 1 },
            { title: 'title2', price: 67, click: 2 },
            { title: 'title3', price: 32, click: 5 },
        ];

        function between(a, b) {
            return function (item) {
                return item.price <= a && item.price >= b;
            }
        }
        let res3 = lessons.filter(between(100, 50));
        console.table(res3);

    </script>
</body>

</html>

移動動畫的閉包使用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>
        button {
            position: absolute;
        }
    </style>
</head>

<body>
    <button message="cyy1">cyy1</button>
    <button message="cyy2">cyy2</button>

    <script>
        let btns = document.querySelectorAll('button');
        btns.forEach(function (btn) {
            btn.addEventListener('click', function () {
                let left = 1;
                console.log(left);
                // 閉包:能夠訪問到外部的變數,一直向外去找
                setInterval(function () {
                    btn.style.left = left++ + 'px';
                }, 10);
            });
        });

    </script>
</body>

</html>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>
        button {
            position: absolute;
        }
    </style>
</head>

<body>
    <button message="cyy1">cyy1</button>
    <!-- <button message="cyy2">cyy2</button> -->

    <script>
        // let btns = document.querySelectorAll('button');
        // btns.forEach(function (btn) {
        //     let left = 1; // 點選之後不會形成新的環境,解決抖動的問題;但是每點選一次會生成一個新的環境,會重複定義多個定時器,導致動畫越來越快
        //     let interval = false; // 節流閥,防抖
        //     btn.addEventListener('click', function () {
        //         if (interval == false) {
        //             interval = true;
        //             // let left = 1; // 每次點選後會形成新的環境,使得left從1開始運動,導致動畫抖動
        //             console.log(left);
        //             // 閉包:能夠訪問到外部的變數,一直向外去找
        //             setInterval(function () {
        //                 btn.style.left = left++ + 'px';
        //             }, 10);
        //         }

        //     });
        // });

        // let btns = document.querySelectorAll('button');
        // btns.forEach(function (btn) {
        //     let bind = false;
        //     btn.addEventListener('click', function () {
        //         if (bind == false) {
        //             bind = true;
        //             let left = 1;
        //             // 閉包:能夠訪問到外部的變數,一直向外去找
        //             setInterval(function () {
        //                 btn.style.left = left++ + 'px';
        //             }, 10);
        //         }

        //     });
        // });

        let btns = document.querySelectorAll('button');
        btns.forEach(function (btn) {
            let bind = false;
            btn.addEventListener('click', function () {
                if (bind == false) {
                    let left = 1;
                    // 閉包:能夠訪問到外部的變數,一直向外去找
                    // setInterval返回定時器編號
                    bind = setInterval(function () {
                        btn.style.left = left++ + 'px';
                    }, 10);
                    console.log(bind);
                }

            });
        });

    </script>
</body>

</html>

利用閉包根據欄位排序商品:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>
        button {
            position: absolute;
        }
    </style>
</head>

<body>

    <script>

        let goods = [
            { name: 'name1', click: 99, price: 19 },
            { name: 'name2', click: 122, price: 327 },
            { name: 'name3', click: 44, price: 991 },
        ];

        function order(field, type = "asc") {

            return function (a, b) {
                if (type == 'asc') {
                    return a[field] > b[field] ? 1 : -1;
                } else {
                    return a[field] > b[field] ? -1 : 1;
                }

            }
        }
        let res = goods.sort(order('click', 'desc'));
        console.table(res);


    </script>
</body>

</html>

閉包的記憶體洩露解決方法:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>
        button {
            position: absolute;
        }
    </style>
</head>

<body>
    <div desc="cyy">CYY</div>
    <div desc="girl">GIRL</div>

    <script>

        // let divs = document.querySelectorAll('div');
        // divs.forEach(function (div) {
        //     div.addEventListener('click', function () {
        //         console.log(div.getAttribute('desc'));
        //         console.log(div); // 存在記憶體洩露問題
        //     });
        // });

        let divs = document.querySelectorAll('div');
        divs.forEach(function (div) {
            let desc = div.getAttribute('desc');
            div.addEventListener('click', function () {
                // 非同步呼叫點選
                console.log(desc);
                console.log(div);
            });
            div = null; // 同步執行
        });

    </script>
</body>

</html>

this在閉包中的歷史遺留問題:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <style>
        button {
            position: absolute;
        }
    </style>
</head>

<body>
    <script>

        // let obj = {
        //     name: 'cyy',
        //     show() {
        //         return function () {
        //             return this.name;
        //         }
        //     }
        // }
        // console.log(obj.show()()); // this指向window
        // let func = obj.show(); func(); // 與上述寫法相同

        //使用箭頭函式解決問題
        // let obj = {
        //     name: 'cyy',
        //     show() {
        //         return () => {
        //             return this.name;
        //         }
        //     }
        // }
        // console.log(obj.show()());


// 使用bind改變this的指向
        let obj = {
            name: 'cyy',
            show() {
                return function () {
                    return this.name;
                }
            }
        }
        let func = obj.show(); console.log(func.bind(obj)()); 



    </script>
</body>

</html>