Interviewer : JS 클로저와 일반적인 애플리케이션 시나리오 (클로저의 역할)에 대한 이해에 대해 이야기합니다.

JS 클로저 및 일반적인 애플리케이션 시나리오 이해 (클로저의 역할)

클로저를 사용하는 주된 목적은 개인 메서드와 변수를 디자인하는 것입니다.

  • 장점 은 변수의 오염을 피할 수 있다는 것입니다.
  • 단점 은 클로저가 메모리에 상주하여 메모리 사용량이 증가한다는 것입니다. 부적절하게 사용하면 쉽게 메모리 누수가 발생할 수 있습니다.

js에서 함수는 클로저이며 함수는 범위 개념을 갖습니다.

1. 가변 범위

변수 범위에는 전역 변수지역 변수의 두 가지가 있습니다 . js에서 전역 변수는 함수 내부에서 읽을 수 있지만 함수 내부의 지역 변수는 함수 외부에서 읽을 수 없습니다.

2. 함수 내부의 변수를 외부에서 읽는 방법은 무엇입니까?

function f1(){
        var n = 123;
        function f2(){    //f2是一个闭包
            alert(n)
        }    
        return f2;
    }
  • Node.js 체인 범위 : 자식 개체는 부모 개체 수준의 모든 변수를 수준별로 조회하지만 그 반대는 아닙니다.
  • f2는 f1의 변수를 읽을 수 있으며, f2가 반환 값으로 사용되는 한 f1 외부에서 f1의 내부 변수를 읽을 수 있습니다.

3. 폐쇄 개념

  • 다른 함수의 내부 변수를 읽을 수있는 함수입니다.
  • 또는 단순히 함수 내부에 정의 된 함수로 이해하면 내부 함수는 외부 함수의 변수에 대한 참조를 보유합니다.

4. 폐쇄의 목적

  • 함수 내부의 변수 읽기
  • 이러한 변수의 값은 항상 메모리에 유지됩니다. f1이 호출 된 후에는 자동으로 지워지지 않습니다.
  • 컨텍스트의 지역 변수를 호출하는 것이 편리합니다. 코드 패키징에 도움이됩니다.
    이유 : f1은 f2의 부모 함수이고, f2는 전역 변수에 할당되고, f2는 항상 메모리에 존재하며, f2의 존재는 f1에 따라 달라 지므로 f1도 항상 메모리에 존재하며 이후 가비지 수집 메커니즘에 의해 재활용되지 않습니다. 전화가 끝났습니다.

5. 폐쇄에 대한 이해

다음은 몇 가지 예입니다.

/**
 * [init description]
 * @return {[type]} [description]
 */
function init() {
    var name = "Chrome";    //创建局部变量name和局部函数alertName

    function alertName() { //alertName()是函数内部方法,是一个闭包
        alert(name); //使用了外部函数声明的变量,内部函数可以访问外部函数的变量
    }
    alertName();
}
init();

소스 코드에서 변수가 선언 된 위치가 범위로 ​​사용되며 중첩 함수는 외부 범위에서 선언 된 변수에 액세스 할 수 있습니다.

/**
 * [outFun description]
 * @return {[type]} [description]
 */
function outFun(){
    var name = "Chrome";
    function alertName(){
        alert(name);
    }
    return alertName;   //alertName被外部函数作为返回值返回了,返回的是一个闭包
}

var myFun = outFun();
myFun();

클로저에는 함수 + 어휘 환경이 있습니다.
어휘 환경은 함수가 생성 될 때 액세스 할 수있는 모든 변수를 나타냅니다.
myFun은 alertName ()과 클로저가 생성 될 때 존재했던 문자열 "Chrome"으로 구성된 클로저를 나타냅니다.
alertName ()은 name에 대한 참조를
보유 하고 myFunc는 alertName ()에 대한 액세스를 보유
하므로 myFunc가 호출 될 때 name은 여전히 ​​액세스 가능한 상태입니다.

/**
 * [add description]
 * @param {[type]} x [description]
 */
function add(x){
    return function(y){
        return x + y;
    };
}

var addFun1 = add(4);
var addFun2 = add(9);

console.log(addFun1(2)); //6
console.log(addFun2(2));  //11

add는 매개 변수 x를 받아들이고, 함수를 반환하고, 매개 변수는 y를 반환하고, x + y를 반환합니다.
add는 매개 변수를 전달하는 함수 팩토리로, 매개 변수 및 기타 매개 변수를 평가하는 함수를 만들 수 있습니다.
addFun1과 addFun2는 모두 클로저입니다. 동일한 함수 정의를 사용하지만 어휘 환경이 다릅니다 .addFun1에서 x는 4이고 후자는 5입니다.

6. 폐쇄 신청 시나리오

setTimeout 전달 매개 변수

//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
    alert(param)
},1000)


//通过闭包可以实现传参效果
function func(param){
    return function(){
        alert(param)
    }
}
var f1 = func(1);
setTimeout(f1,1000);

콜백

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
</head>
<style>
    body{
        font-size: 12px;
    }
    h1{
        font-size: 1.5rem;
    }
    h2{
        font-size: 1.2rem;
    }
</style>
<body>

    <p>哈哈哈哈哈哈</p>
    <h1>hhhhhhhhh</h1>
    <h2>qqqqqqqqq</h2>

    <a href="#" id="size-12">12</a>
    <a href="#" id="size-14">14</a>
    <a href="#" id="size-16">16</a>

<script>
    function changeSize(size){
        return function(){
            document.body.style.fontSize = size + 'px';
        };
    }

    var size12 = changeSize(12);
    var size14 = changeSize(14);
    var size16 = changeSize(16);

    document.getElementById('size-12').onclick = size12;
    document.getElementById('size-14').onclick = size14;
    document.getElementById('size-16').onclick = size16;
    //我们定义行为,然后把它关联到某个用户事件上(点击或者按键)。我们的代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上
</script>
</body>
</html>

패키지 변수

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>闭包模拟私有方法</title>
    <link rel="stylesheet" href="">
</head>
<body>
<script>
    //用闭包定义能访问私有函数和私有变量的公有函数。
    var counter = (function(){
        var privateCounter = 0; //私有变量
        function change(val){
            privateCounter += val;
        }
        return {
            increment:function(){   //三个闭包共享一个词法环境
                change(1);
            },
            decrement:function(){
                change(-1);
            },
            value:function(){
                return privateCounter;
            }
        };
    })();

    console.log(counter.value());//0
    counter.increment();
    counter.increment();//2
    //共享的环境创建在一个匿名函数体内,立即执行。
    //环境中有一个局部变量一个局部函数,通过匿名函数返回的对象的三个公共函数访问。

</script>
</body>
</html>

노드 루프에 대한 클릭 이벤트 바인딩

image.png

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<body>

<p id="info">123</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>

<script>
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        var item = infoArr[i];
        document.getElementById(item.id).onfocus = function(){
            showContent(item.content)
        }
    }
}
setContent()

</script>
</body>
</html>

위의 코드는 원래 구현을 목적으로 한 것입니다. 다른 상자를 클릭하면 다른 정보가 표시되므로 마지막 항목 인 "your age"만 표시됩니다.

분석 :

  • 루프에서 세 개의 클로저가 생성되며 동일한 어휘 환경 항목을 사용합니다. item.content는 변수 변수입니다.
  • onfocus가 실행되면 item.content가 결정되고, 이때 루프가 종료되고 세 개의 클로저가 공유하는 항목이 배열의 마지막 항목을 가리 킵니다.

해결 :

/**
 * 解决方法1     通过函数工厂,则函数为每一个回调都创建一个新的词法环境
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function callBack(content){
    return function(){
        showContent(content)
    }
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        var item = infoArr[i];
        document.getElementById(item.id).onfocus = callBack(item.content)
    }
}
setContent()
/**
 * 解决方法2        绑定事件放在立即执行函数中
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        (function(){
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        })()//放立即执行函数,立即绑定,用每次的值绑定到事件上,而不是循环结束的值
    }
}
setContent()
/**
 * 解决方案3        用ES6声明,避免声明提前,作用域只在当前块内
 */
function showContent(content){
    document.getElementById('info').innerHTML = content;
};

function setContent(){
    var infoArr = [
        {'id':'email','content':'your email address'},
        {'id':'name','content':'your name'},
        {'id':'age','content':'your age'}
    ];
    for (var i = 0; i < infoArr.length; i++) {
        let item = infoArr[i];      //限制作用域只在当前块内
        document.getElementById(item.id).onfocus = function(){
            showContent(item.content)
        }
    }
}
setContent()

7. 장점, 단점 및 솔루션

장점 :

  • 글로벌 변수의 오염 방지
  • 함수 내에서 변수 읽기 가능
  • 메모리에 변수를 유지할 수 있습니다.

단점 :

  • 1. 클로저는 메모리에 남아 메모리 사용량을 증가 시키며, 부적절한 사용은 쉽게 메모리 누수를 유발할 수 있습니다 . 해결책은 함수를 종료하기 전에 사용하지 않는 모든 지역 변수삭제하는 것입니다.
  • 2. 클로저는 부모 함수 외부에 있으며 부모 함수 내부의 변수 값을 변경합니다. 따라서 부모 함수를 객체로 사용하고 클로저를 공용 메서드로, 내부 변수를 private 값으로 사용하는 경우이 때주의해야합니다. 부모 함수의 내부 변수 값을 자유롭게 변경하십시오. .

호출이 끝난 후에는 가비지 수집 메커니즘에 의해 클로저가 재활용되지 않습니다. 여기서 가비지 수집 메커니즘을 확장 해 보겠습니다 .

8. 확장 · 쓰레기 수거 메커니즘

메모리가 더 이상 필요하지 않으면 해제해야합니다. 여기서 가장 어려운 작업은 "어떤 할당 된 메모리가 더 이상 필요하지 않은지"찾는 것 입니다. 이를 확인하려면 가비지 수집 메커니즘이 필요합니다.

1. 참조 계산 가비지 컬렉션

이것은 가장 진보 된 가비지 수집 알고리즘입니다.
이 알고리즘은 "객체가 참조 하는가"로 "객체가 더 이상 필요하지 않은지 여부"의 정의를 단순화합니다.
객체에 대한 참조가없는 경우 (0 참조), 객체는 가비지 수집 메커니즘에 의해 재활용됩니다.

예:

var o = {
  a: {
    b:2
  }
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集


var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1;      // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有

var oa = o2.a; // 引用“这个对象”的a属性
               // 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
           // 但是它的属性a的对象还在被oa引用,所以还不能回收

oa = null; // a属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

제한 사항 : 순환 참조 :

알고리즘에는 제한이 있습니다. 순환 참조를 처리 할 수 ​​없습니다. 다음 예에서는 두 개의 개체가 만들어지고 서로를 참조하여주기를 형성합니다. 호출 된 후 함수 범위를 벗어나므로 더 이상 유용하지 않고 재활용 할 수 있습니다. 그러나 참조 계산 알고리즘은 서로에 대한 참조가 하나 이상 있다는 점을 고려하므로 재활용되지 않습니다.

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

순환 참조의 실제 예 :

IE 6, 7은 참조 계수를 사용하여 DOM 개체를 가비지 수집합니다. 이 메서드는 개체가 순환 참조 될 때 종종 메모리 누수를 유발합니다.

var div;
window.onload = function(){
  div = document.getElementById("myDivElement");
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join("*");
};

위의 예에서 DOM 요소 myDivElement의 CircularReference 속성은 myDivElement를 참조하여 순환 참조를 발생시킵니다. 속성이 제거되거나 null로 설정되지 않은 경우 참조 계산 가비지 수집기는 DOM 트리에서 삭제 되더라도 항상 메모리에 남아있는 DOM 요소에 대한 참조를 하나 이상 갖게됩니다. 이 DOM 요소에 많은 양의 데이터가있는 경우 (위의 lotsOfData 속성에서와 같이)이 데이터가 차지하는 메모리가 해제되지 않습니다.

2. 마크 앤 스윕 알고리즘

이 알고리즘은 "객체가 더 이상 필요하지 않은지 여부"의 정의를 "객체를 사용할 수 있는지 여부"로 단순화했습니다.

이 알고리즘은 루트라는 객체가 설정되어 있다고 가정합니다 (Javascript에서 루트는 전역 객체). 가비지 수집기는 주기적으로 루트에서 시작하여 루트에서 참조 된 모든 개체를 찾은 다음 이러한 개체에서 참조하는 개체를 찾습니다. 루트에서 시작하여 가비지 수집기는 사용 가능한 모든 개체를 찾고 사용할 수없는 모든 개체를 수집합니다.

이 알고리즘은 "참조가 0 인 객체"를 항상 사용할 수 없기 때문에 이전 알고리즘보다 낫지 만 그 반대가 반드시 사실 인 것은 아닙니다. "순환 참조"를 참조하십시오.

2012 년 이후 모든 최신 브라우저는 마크 스윕 가비지 수집 알고리즘을 사용했습니다. JavaScript 가비지 수집 알고리즘의 모든 개선 사항은 마크 스윕 알고리즘의 개선을 기반으로하며 마크 스윕 알고리즘 자체 및 "객체가 더 이상 필요하지 않은지 여부"에 대한 단순화 된 정의는 개선되지 않았습니다.

순환 참조는 더 이상 문제가되지 않습니다.
참조 계수 예제에서 함수 호출이 반환 된 후 전역 개체에서 두 개체를 가져올 수 없습니다. 따라서 가비지 수집기에서 수집합니다. 두 번째 예제는 동일합니다. 일단 div와 해당 이벤트 처리를 루트에서 가져올 수 없으면 가비지 수집기에 의해 재활용됩니다.

제한 사항 : 루트 개체에서 쿼리 할 수없는 개체는 지워집니다.
이는 제한 사항이지만 실제로는 유사한 상황이 거의 발생하지 않으므로 개발자는 가비지 수집 메커니즘에 대해 신경 쓰지 않습니다.

참조 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management

이 기사 링크 : https://blog.csdn.net/qq_39903567/article/details/115010640

추천

출처blog.csdn.net/qq_39903567/article/details/115010640