30学前端-#笔记# 03 执行上下文

131 阅读5分钟

作用域、变量、this

JS中的词法作用域(静态的作用域)

作用域的定义

定义变量的区域,作用域规定了如何查找变量。

JS 中采用的词法作用域,也就是静态作用域(与之对应的是动态作用域),也就是说变量在定义的时候已经确定。

查找规则是从内到外,依次查找。

函数作用域,是基于函数创建的位置。

  • 函数的作用域在函数定义的时候就确定了
  • 变量在定义的时候已经确定了

思考题(巩固)

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

最终输出都是"local scope"。然而它们到底有什么不同呢?

执行上下文

这一段是自己的理解,建议查看原文 @冴羽

执行上下文栈

JS逐行扫描按段执行,按段执行是什么意思呢?段是指可执行的代码,可执行代码分为:全局代码、函数代码、eval代码。

如何按照顺序执行的?JS引擎创建一个执行栈,首先遇到的是全局代码,作为全局上下文压入栈底,开始从上开始寻找可执行函数代码,如果函数调用了其他函数将依次入栈。

当一个函数执行完毕后,从执行栈弹出。

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();
// 伪代码

// fun1()
ECStack.push(<fun1> functionContext);

// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext);

// 擦,fun2还调用了fun3!
ECStack.push(<fun3> functionContext);

// fun3执行完毕
ECStack.pop();

// fun2执行完毕
ECStack.pop();

// fun1执行完毕
ECStack.pop();

// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext

变量对象

是执行上下文相关的数据作用域,包含函数参数、声明和变量

全局上下文

全局变量对象就是全局对象

  • 在客户端中就是window this === window
  • 是Object的一个实例对象 window instanceof Object // true
  • 作为全局变量的宿主 this.a === a === window.a
补充容易出错的点
let a = 10;
var b = 100;
c = 1000; // 不会变量提升,创建为全局变量,可删除

a // 10
this.a // undefined 
this.b // 100 
this.c // 1000
delete a; // false
delete b; // false
delete c; // true

函数上下文

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。在进入函数上下文时被创建。

执行过程

  1. 进入执行上下文

    1. 创建所有形参,并实参赋值(按指传递),如果没有实参,值则为undefined
    2. 函数声明
    3. 变量声明
    • 函数声明和参数或变量冲突,覆盖
    • 变量声明和参数冲突或函数声明冲突,互不影响
  2. 执行代码

代码执行阶段会顺序执行代码,根据代码修改变量值

思考题

function foo() {
    console.log(a);
    a = 1; // 没有var 不会变量提升
}

foo(); // not defined

function bar() {
    a = 1;
    console.log(a);
}
bar(); // 1
console.log(foo); // 输出函数,变量声明不会影响函数声明

function foo(){
    console.log("foo");
}

var foo = 1;
function foo(fn){
    console.log(fn)
    function fn(){
        console.log(10)
    }
    var fn = 100;
}
foo(1000)
// 输出
// fn(){
//        console.log(10)
//    }
function foo(fn){
    console.log(fn)

    var fn = 100;
    console.log(fn)
}
foo(1000)

// 1000
// 10

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

从规范的角度上理解 this

最终由谁调用就指向谁

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar()); // 2
//示例2
console.log((foo.bar)()); // 2
//示例3
console.log((foo.bar = foo.bar)()); // 1
//示例4
console.log((false || foo.bar)()); // 1
//示例5
console.log((foo.bar, foo.bar)()); // 1


function foo() {
    console.log(this)
}

foo(); // window

闭包

两种形式

  • 函数作为返回值
  • 函数作为参数

表现: 闭包函数携带,函数创建时候的上下文,也就是变量对象。

思考题

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();
for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();

参数按值传递?

先看例子:

  1. 例1,确实互不影响,按值传递
var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1
  1. 例2,明显外部obj被修改了
var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
  1. 例3,obj却保持了不变
var obj = {
    value: 1
};
function foo(o) {
    o = 2;
    console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1

实际上就是一次变量赋值的过程

昨天我理解偏了,回到正轨,我们看一下变量的赋值运算。

var value = 1;
var v = value;
v = 2;
console.log(value); // 1
var obj = {value: 1};
var o = obj;
o.value = 2;
console.log(obj.value); // 2
var obj = {value: 1};
var o = obj;
o = 2; // 重新分配了
console.log(obj.value); // 1

1、2、3的变量赋值分别对应上面的参数传递,结果一致。 这块不用想复杂了,就是个赋值操作没有特殊的区别。

在准备执行上下文环境的时候:

1. 首先创建形参并根据实参赋值,
2. 函数声明
3. 变量声明

值传递总结

  1. 赋值运算按值传递;
  2. 常量类型直接给值;
  3. 引用类型传指针地址;
  4. 如果修改的内部属性,相同的引用的变量都会改变
  5. 对变量直接赋值,则分配了新的引用地址或常量,断开了之前的联系。