Home  >  Article  >  Web Front-end  >  JavaScript internal properties [[Scope]] and scope chain and their performance issues

JavaScript internal properties [[Scope]] and scope chain and their performance issues

黄舟
黄舟Original
2017-02-28 14:57:031405browse

I have been learning JavaScript for a long time

Today I plan to recall the knowledge of scope
The knowledge of scope is very basic and very important
Now I will summarize the knowledge in JavaScript Knowledge about scope and scope chain

Scope

What is scope?
Scope is the area where variables can be referenced and functions can take effect
It limits your ability to obtain and modify values ​​in the memory space
All languages ​​have scopes
We can understand that scope is a set of rules for js engines to find variables based on names
Understanding scope, we can understand a series of issues such as closures

[[Scope ]]

As we all know, a function is a special executable object
Since it is an object, it can have properties
There is this internal property [[Scope]] in the function (we cannot use it, For use by js engines)
When a function is created, this internal property will contain the collection of objects in the scope where the function was created.
This collection is linked in a chain and is called the scope chain of the function.
Each object in the scope chain is called a variable object (Variable Object).
Every variable object exists in the form of key-value pairs
For an example, look at the following global function

var a = 1;function foo(){    ...}

When the foo function is created, a global object GO (Global Object) is inserted into its scope chain, including all globally defined variables

// 伪代码
foo.[[Scope]] = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        ......
        a: undefined, // 预编译阶段还不知道a值是多少
        foo: function(){...},
    }
}

Execution environment

When the function is executed , will create an internal object called execution environment/execution context (execution context)
It defines the environment when a function is executed
The execution environment is unique each time the function is executed
The function will be called multiple times Create an execution environment for the first time
And after the function is executed, the execution environment will be destroyed
The execution environment has its own scope chain, which is used to parse identifiers

You may be a little confused when you see this. Let me explain my understanding to you again
[[Scope]] and execution context save scope chains, but they are not the same thing
Now let’s clarify the difference
[[Scope] ]The attribute is generated when the function is created and will always exist
The execution context is generated when the function is executed and will be destroyed when the function is executed

Instance

I will expand the above example Let’s explain in detail

var a = 1;function foo(x, y){
    var b = 2;    function bar(){
        var c = 3;
    }
    bar();
}
foo(100, 200);

Now I will explain the scope chain and execution environment in detail through these lines of code
I still recommend that all students take a look at the precompilation I wrote

  • First of all, in the flow of execution flow, the function foo() is created in the global environment (created in the pre-compilation stage), so the foo function has the attribute [[Scope]]

// 伪代码:foo函数创建产生[[Scope]]对象
foo.[[Scope]] = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: undefined, //预编译阶段还不知道a的值是多少,执行过程中会修改
        foo: function(){...},
        ......
    }
}
  • Before the foo function is executed, an execution context is created (I will write the execution context as EC for the time being, and I don’t know what the internal name is). The execution context obtains the internal name of foo. The scope chain (copy) saved by the [[Scope]] attribute is then pre-compiled before the execution of the foo function to generate an active object AO (Active Object). This object is pushed into the front end of the EC scope chain

// 伪代码:foo函数执行前产生执行期上下文EC复制foo中[[Scope]]属性保存的作用域链
foo.EC = {
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
// 伪代码:foo函数预编译产生AO活动对象,挂载到foo中EC作用域链的最前端
foo.EC = {
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: undefined,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
  • The foo function creates the bar function in the precompilation stage, so the bar function creates the attribute [[Scope]], which contains the collection of objects in the scope where bar is created, and It just copied the foo.EC

// 伪代码:bar函数创建产生[[Scope]]对象
bar.[[Scope]] = {
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: undefined,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
  • bar function execution. The process is similar to the foo function execution, so I just wrote the final result

// 伪代码:bar函数执行产生执行上下文
bar.EC = {
    AO: {
        this: window,
        arguments: [],
        c: undefined,
    },
    AO: {
        this: window,
        arguments: [100,200],
        x: 100,
        y: 200,
        b: 2,
        bar: function(){...}
    },
    GO: {
        this: window ,
        window: ... ,
        document: ... ,
        a: 1,
        foo: function(){...},
        ......
    }
}
  • After the bar function is executed, the execution environment is destroyed, which is equivalent to delete bar.EC

  • foo function is executed. , the execution environment is destroyed, which is equivalent to delete foo.EC

  • End of program

Variable parsing process

I don’t know if you understand what I have written so much.
The js engine uses the rules of the scope chain to search for variables (to be precise, it should be the scope chain of the execution context)
The search process uses the above In terms of code, for example, if I add a line console.log(a);
to the bar function, then when the bar function is executed, the js engine wants to print a, so it searches on the scope chain.
There is no AO in the first layer
There is no AO in the second layer
The third layer GO found the variable a
So the value of variable a was returned
I believe that after everyone understands the scope, Will understand why the global environment cannot access the local environment

性能问题

今天写high了,像吃了炫迈一样,那就顺便把性能问题也说清楚了吧
js引擎查找作用域链是为了解析标识符
占用了时间理所当然的产生了性能开销
所以解析标识符有代价,你的变量在执行环境作用域链的位置越深,读写速度就越慢
这很容易理解
在函数中读写局部变量总是最快的,读写全局变量通常最慢
当然了,这些额外的性能开销对于优化js引擎(比如chrome的V8 (⊙▽⊙))来说可能微不足道,甚至可以毫不夸张的说没有性能损失
但是还是要照顾大多浏览器
所以推荐大家养成这样的编码习惯:尽量使用局部变量(缓存)
我举一个小例子

function demo(){
    var a = document.getElementById('a');
    var b = document.getElementById('b');
    var c = document.getElementById('c');
    a.onclick = function(){        ...
    }
    a.style.left = ...;
    a.style.top = ...;
    b.style.backgroundColor = ...;
    c.className = ...;
    document.body.appendChild(...);
    document.body.appendChild(...);
    document.body.appendChild(...);
}

这段代码看起来缓存了,优化了代码
但其实这段代码执行过程中,js引擎解析标识符,要查找6次document
而且document存在于window对象
也就是作用域链的最末尾
所以我们再进行缓存,包括document.body、a.style
再加上单一var原则
重构函数

function demo(){
    var doc = document,
        bd = doc.body,
        a = doc.getElementById('a'),
        b = doc.getElementById('b'),
        styleA = a.style;
    a.onclick = function(){        ...
    }
    styleA.left = ...;
    styleA.top = ...;
    styleA.backgroundColor = ...;
    b.className = ...;
    bd.appendChild(...);
    bd.appendChild(...);
    bd.appendChild(...);
}

总结

其实写了这么多,还有一个问题我没写到,就是作用域链在某些特殊情况下是可以动态改变的
比如with()、eval()等等,当然这些都不建议使用,我总结了一篇文章
有兴趣的同学可以看看 ->传送门<-
还是总结一下今天写的作用域链相关知识

  • 作用域是变量能够引用、函数能够生效的区域

  • 函数创建时,产生内部属性[[Scope]]包含函数被创建的作用域中对象的集合(作用域链)
    作用域链上每个对象称为可变对象(Variable Obejct),
    每一个可变对象都以键值对形式存在(VO要细分的话,全局对象GO和活动对象AO)

  • 函数执行时,创建内部对象叫做执行环境/执行上下文(execution context)
    它定义了一个函数执行时的环境,函数每次执行时的执行环境独一无二
    函数执行结束便会销毁

  • js引擎就通过函数执行上下文的作用域链规则来进行解析标识符(用于读写),从作用域链顶端依次向下查找

  • 尽量缓存局部变量,减少作用域查找性能开销(照顾未优化浏览器)

 以上就是JavaScript内部属性[[Scope]]与作用域链及其性能问题的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn