The effort of a cup of tea, grasp the scope & scope chain

R-C.jpgDrink the right yogurt, and get twice the result with half the effort! For some obscure, almost mythological terms, don't scratch your head, let's go to the code directly, add plain language to render, and see how we handle the little hill in javascript today - scope & scope chain , not only refined.

foreword

What we need to know first is the engine. The work of the engine is simple and rude, that is, it is responsible for the execution of javascript code from start to finish. A good friend of the engine is the compiler, which is mainly responsible for code analysis and compilation; another good friend of the engine is today's protagonist-scope. So what is scope used for? What does a scope chain have to do with scope?

First, the scope (scope)

Definition of Scope: Scope is the accessibility of variables, functions and objects in some specific part of the code at runtime.

1. Classification of scopes
  1. global scope
var name="global";
function foo(){
    console.log(name);
}
foo();//global

The name variable is not declared inside the function foo(), but the value of name is still printed, indicating that the function can access the global scope and read the name variable. Another example:

hobby='music';
function foo(){
    hobby='book';
    console.log(hobby);
}
foo();//book

Here, neither the global scope nor the function foo() declares the variable hobby, so why is there no error? This is because it is hobby='music';written in the global scope, even if there is no var, let, const declaration, it will be hung on the window object, so the function foo() can not only read, but also modify the value. That is to say, it is hobby='music';equivalent to window.hobby='music';.

  1. function body scope

The scope of the function body is achieved by hiding the internals. In other words, as we often say, the inner scope can access the outer scope, but the outer scope cannot access the inner scope. The reason, when it comes to the scope chain, is easily solved.

function foo(){
    var age=19;
    console.log(age);
}
console.log(age);//ReferenceError:age is not defined

Obviously, there is no age variable in the global scope, but the function foo() has it inside, but it cannot be accessed from the outside, so an error will be reported naturally, and the function foo() will not be executed if it is not called.

  1. block scope

The block-level scope is not surprising, such as the let scope we have contacted, the code block {}, the scope when the for loop uses let, if, while, switch and so on. However, the premise of a deeper understanding of block-level scope is that we need to recognize these terms first:

--标识符:能在作用域生效的变量。函数的参数,变量,函数名。需要格外注意的是:函数体内部的标识符外部访问不到

--函数声明:function 函数名(){}

--函数表达式: var 函数名=function(){}

--自执行函数: (function 函数名(){})();自执行函数前面的语句必须有分号,通常用于隐藏作用域。

接下来我们就用一个例子,一口气展示完吧

function foo(sex){
    console.log(sex);
}
var f=function(){
    console.log('hello');
}
var height=180;
(
    function fn(){
        console.log(height);
    }
)();
foo('female');
//依次打印:
//180
//female
//hello

分析一下:标识符:foo,sex,height,fn;函数声明:function foo(sex){};函数表达式:var f=function(){};自执行函数:(function fn(){})();需要注意,自执行函数fn()前面的var height=180;语句,分号不能抛弃。否则,你可以试一下。

二、预编译

说好只是作用域和作用域链的,但是考虑到理解作用域链的必要性,这里还是先聊聊预编译吧。先讨论预编译在不同环境发生的情况下,是如何进行预编译的。

  1. 发生在代码执行之前

(1)声明提升

console.log(b);
var b=123;//undefined

这里打印undefined,这不是报错,与Refference:b is not defined不同。这是代码执行之前,预编译的结果,等同于以下代码:

var b;//声明提升
console.log(b);//undefined
b=123;

(2)函数声明整体提升

test();//hello123  调用函数前并没有声明,但是任然打印,是因为函数声明整体提升了
function test(){
    var a=123;
    console.log('hello'+a);
}

2.发生在函数执行之前

理解这个只需要掌握四部曲

(1)创建一个AO(Activation Object)

(2)找形参和变量声明,然后将形参和变量声明作为AO的属性名,属性值为undefined

(3)将实参和形参统一

(4)在函数体内找函数声明,将函数名作为AO对象的属性名,属性值予函数体 那么接下来就放大招了:

var global='window';
function foo(name,sex){
    console.log(name);
    function name(){};
    console.log(name);
    var nums=123;
    function nums(){};
    console.log(nums);
    var fn=function(){};
    console.log(fn);
}
foo('html');

这里的结果是什么呢?分析如下:

//从上到下
//1、创建一个AO(Activation Object)
AO:{
    //2、找形参和变量声明,然后将形参和变量声明作为AO的属性名,属性值为undefined
    name:undefined,
    sex:undefined,
    nums=undefined,
    fn:undefined,
    //3、将实参和形参统一
    name:html,
    sex:undefined,
    nums=123,
    fn:function(){},
    //4、在函数体内找函数声明,将函数名作为AO对象的属性名,属性值予函数体
    name:function(){},
    sex:undefined,
    fn:function(){},
    nums:123//这里不仅存在nums变量声明,也存在nums函数声明,但是取前者的值
    
    以上步骤得到的值,会按照后面步骤得到的值覆盖前面步骤得到的值
}
//依次打印
//[Function: name]
//[Function: name]
//123
//[Function: fn]

3.发生在全局(内层作用域可以访问外层作用域)

同发生在函数执行前一样,发生在全局的预编译也有自己的三部曲:

(1) Create a GO (Global Object) object (2) Find the global variable declaration, use the variable declaration as the property name of GO, and the property value is undefined (3) Find the function declaration globally, and use the function name as the property name of the GO object, The attribute value is assigned to the function body, for example:

var global='window';
function foo(a){
    console.log(a);
    console.log(global);
    var b;
}
var fn=function(){};
console.log(fn);
foo(123);
console.log(b);

This example is relatively simple. The same steps and ideas will not be repeated in the analysis. I believe you already know it. The print results are:

[Function: fn]
123
window
ReferenceError: b is not defined

Alright, getting on track, let's move on to the scope chain.

Third, the scope chain

The scope chain can help us find out, why the inner layer can access the outer layer, but the outer layer cannot access the inner layer? But again, before we can understand the scope chain, we need to see some more obscure abstract terms.

  1. 执行期上下文: When a function executes, an object called an execution context (AO object) is created. An execution context defines the environment in which a function executes. Each time a function is executed, the corresponding execution context is unique, so calling a function multiple times will result in the creation of multiple execution contexts. When the function is executed, the execution contexts generated by it will be destroyed.
  2. 查找变量: Search down from the top of the scope chain.

3. [[scope]]: Scoped properties, also known as implicit properties, are only supported by the engine itself. Function scope, inaccessible, where the binding of the runtime context is stored.

Let's take a look at the built-in properties of the function first:

function test(){//函数被创建的那一刻,就携带name,prototype属性
      console.log(123);
}
console.log(test.name);//test
console.log(test.prototype);//{} 原型
// console.log(test[[scope]]);访问不到,作用域属性,也称为隐式属性

// test() --->AO:{}执行完毕会回收
// test() --->AO:{}执行完毕会回收

Let's see how the scope chain is implemented:

var global='window';
function foo(){
    function fn(){
        var fn=222;
    }
    var foo=111;
    console.log(foo);
}
foo();

analyze:

GO:{
    foo:function(){}
}
fooAO:{
    foo:111,
    fn:function(){}
}
fnAO:{
    fn:222
}
// foo定义时 foo.[[scope]]---->0:GO{}
// foo执行时 foo.[[scope]]---->0:AO{}  1:GO{}  后访问的在前面
//fn定义时 fn.[[scope]]---->0:fnAO{} 1:fooAO{}  2:GO{}
fnAO:fn的AO对象;fooAO:foo的AO对象

image.png

To sum up:作用域链就是[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

Guess you like

Origin juejin.im/post/7116516393100853284