简体   繁体   English

Javascript-了解范围链的参考优先级

[英]Javascript - understanding the scope chain's reference priority

I would like to better understand the scope chain the hopefully closure better and so the following code: 我想更好地了解范围链,希望可以更好地关闭,因此下面的代码:

function greet(whattosay){

    return function(name) {
        whattosay = "not hi"; //change the hi message
        console.log(whattosay + ' ' + name);
        function insideAnon() {
            console.log(whattosay);
        }

    }


}
var sayHi = greet('Hi'); //get in return the anon function
sayHi("John"); //print not hi John on the console

I have recently learned that every "variable environment" is actually an array or an object with properties, and so each function has a reference to it's parent's function variable environment object/array. 我最近了解到,每个“可变环境”实际上都是一个数组或具有属性的对象,因此每个函数都有对其父对象的函数变量环境对象/数组的引用。 But my question is, what exactly is the scope chain?(I know that it is supposedly going "down" up until the global level), My current perception (Not sure) is that each child has it's parent and grandparent (and so on) variable environment references inside of it, and so it first check it's parent's variable environment object, if a variable is not there it looks at it's grandparent's reference and so on. 但是我的问题是,范围链到底是什么?(我知道它应该一直“下降”到全球范围),我目前的看法(不确定)是每个孩子都有父母和祖父母(依此类推) )内部的变量环境引用,因此它首先检查其父对象的变量环境对象,如果不存在该变量,它将查看其祖父母的引用,依此类推。 Or is it more like - Check the parent variable environment object reference, if not there that parent checks his parent's reference and so on. 还是更像-检查父变量环境对象引用,如果不存在,则检查父对象的引用,依此类推。 I HOPE i was very clear at what I was trying to say - How it really goes down the "chain". 我希望我很清楚我在说什么-它实际上是如何在“链条”上发展的。

You can get references about scope chains from this question: 您可以从以下问题获取有关范围链的参考:

Scope Chain in Javascript JavaScript范围链

Each function creates a scope object that contains the arguments for that function and the local variables defined in that function. 每个函数都会创建一个范围对象,该对象包含该函数的参数以及该函数中定义的局部变量。

Since Javascript uses lexical scoping, that means that a given piece of code can access variables defined within the current function or within any parent function. 由于Javascript使用词法作用域,因此这意味着给定的一段代码可以访问当前函数或任何父函数中定义的变量。 Javascript makes this work by maintaining a chain of scope objects so each scope has a reference to its parent scope. Javascript通过维护一系列作用域对象来完成这项工作,因此每个作用域都有对其父作用域的引用。

When a given piece of code accesses a variable like: 当给定的代码段访问变量时,例如:

foo = 1;

Javascript, looks in the current scope object to see if something named foo exists there. Javascript,在当前作用域对象中查找以查看是否存在名为foo对象。 If so, that variable is used. 如果是这样,则使用该变量。 If not, it gets the reference to the parent scope, checks that scope for something named foo and continues this process up the parent chain until it gets to the global scope and thus can go no further. 如果没有,它将获取对父作用域的引用,检查该作用域是否包含名为foo并继续此过程直至父链,直到到达全局作用域为止,因此无法继续进行下去。

So, in this example: 因此,在此示例中:

function one() {
    var foo = 2;

    function two() {
        var fee = 3;

        function three() {
            foo = 1;
        }
        three();
    }
    two();
}
one();

The function one() will execute and define foo and two in its scope. 函数one()将执行并在其范围内定义footwo It will then call two() which will execute and define fee and three in its scope. 然后它将调用two()来执行并定义fee并在其范围内定义three It will then call three() which will execute and attempt to change the value of foo . 然后它将调用three() ,它将执行并尝试更改foo的值。 It will first look in the scope object belonging to three , but it will not find anything named foo there so it will go to the parent scope of three which will be the two scope object. 它将首先查看属于three的作用域对象,但在那里找不到任何名为foo东西,因此它将转到three的父作用域,这将是two作用域对象。 It will not find a variable named foo in that scope object so it will go to the parent object of two which will be the scope object of one . 它不会找到一个变量命名为foo在该范围内的对象,因此会去的父对象two ,这将是范围对象的one There it will find a variable named foo so it will change the value of that variable. 在那里它将找到一个名为foo的变量,因此它将更改该变量的值。


On thing that is different about Javascript scope objects compared to something like a C++ stack frame is that they can continue to live after the function that created them has finished executing. 与C ++堆栈框架之类的Java范围对象不同的是,它们可以在创建它们的函数完成执行后继续存在。 This is a very common structure in Javascript and is often referred to as a closure. 这是Javascript中非常常见的结构,通常称为闭包。 Here's a simple example of that: 这是一个简单的例子:

function initializeCntr() {
     var cntr = 0;
     document.getElementById("test").addEventListener("click", function(e) {
         ++cntr;
         document.getElementById("clickCnt").innerHTML = cntr;
     });
}

initializeCntr();

In this short code example, there are three scopes present, the global scope (that contains the initializeCntr symbol), the initializeCntr scope that contains the cntr variable and the scope that belongs to the anonymous function that is set as the event handler. 在此短代码示例中,存在三个作用域:全局作用域(包含initializeCntr符号),包含cntr变量的initializeCntr作用域和属于设置为事件处理程序的匿名函数的作用域。

When you call initializeCntr() , it creates a function scope object that contains the variable cntr . 当您调用initializeCntr() ,它将创建一个包含变量cntr的函数范围对象。 It then runs the addEventListener() method and passes a reference to the inline anonymous event handler for addEventListener() . 然后它运行addEventListener()方法,并传递到在线匿名事件处理程序的引用addEventListener() The initializeCntr() method then finishes execution. 然后, initializeCntr()方法完成执行。 But, because the inline anonymous function passed as the event handler contains a reference to the cntr variable in the initializeCntr scope object, that scope object is NOT garbage collected even though initializeCntr() has finished executing. 但是,由于作为事件处理程序传递的嵌入式匿名函数在initializeCntr范围对象中包含对cntr变量的引用,因此即使initializeCntr()完成执行,该范围对象也不会被垃圾回收。 Instead, it is kept alive because of the lasting reference to the cntr variable in it. 相反,由于持久引用了其中的cntr变量,因此它保持活动状态。 When that click event handler is then called sometime in the future, it can use that cntr variable. 然后在将来某个时间调用该click事件处理程序时,它可以使用该cntr变量。 In fact, the initalizeCntr scope will be kept alive as long as the event handler is active. 实际上,只要事件处理程序处于活动状态, initalizeCntr范围将保持活动状态。 If the event handler is removed or the DOM object it is attached to is removed, then and only then would the initializeCntr scope object be eligible for garbage collection and eventually get freed. 如果事件处理程序被删除或它所附加的DOM对象被删除,则只有在那时, initializeCntr作用域对象才有资格进行垃圾回收并最终被释放。

Some JS implementations are smart enough to not necessarily retain the entire scope object and everything in it, but instead to only retain the elements of the scope object that are specifically referenced in child scopes that are still active. 一些JS实现很聪明,不必保留整个作用域对象及其中的所有内容,而是只保留在仍处于活动状态的子作用域中专门引用的作用域对象的元素。 So, if there were other variables next to cntr that were not used in the event handler, those other variables could be freed. 因此,如果cntr旁边还有其他未在事件处理程序中使用的变量,则可以释放这些其他变量。

FYI, this longer lasting scope is called a closure. 仅供参考,这种更长久的范围称为闭包。 Closures are very useful concepts in Javascript and something not available in a language like C++ (because they rely on garbage collection). 闭包在Javascript中是非常有用的概念,而某些东西在C ++等语言中是不可用的(因为它们依赖于垃圾回收)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM