执行上下文
函数执行上下文
一个函数运行之前,会创建一个内存空间,该空间中有执行该函数所需要的数据,从而为函数执行提供支持。
执行上下文栈(call stack):所有执行上下文组成的内存空间。
全局执行上下文
所有JS代码执行之前,都必须有该环境。(只有一个)
JS引擎始终执行的是栈顶的上下文。
执行上下文中的内容
-
确定this的指向
- 直接调用函数,this指向全局对象
- 在函数外,this指向全局对象
- 通过对象调用或new一个函数,this指向调用的对象或新对象
-
确定VO变量对象
Variable Object: 记录了该环境中所有声明的参数,声明的变量和声明的函数。
对象存在堆中。
变量对象如何记录
-
确定所有形参值以及arguments。
-
确定通过var声明的变量,值为undefined。
若第一步有同名变量,则忽略。 -
确定函数中通过字面量声明的函数,值为函数值,
若上面两步有同名变量,则覆盖。
面试题1
var foo = 1;
function bar(){
console.log(foo);
if(!foo){
var foo = 10;
}
console.log(foo);
}
bar();
/*
1. 全局上下文预编译:
foo: undefined
bar: fn
*/
// 开始执行代码
/*
1. 全局上下文:
foo: 1
bar: fn
*/
// bar函数调用,就会创建函数上下文
/*
2. bar函数上下文:
foo: undefined
1. 全局上下文:
foo: 1
bar: fn
*/
// 开始执行代码
/*
看bar函数的上下文
输出undefined
foo:10
输出10
*/
面试题2
var a = 1;
function b(){
console.log(a);
a = 10;
return;
function a(){}
}
b();
console.log(a);
/**
函数b上下文:
a: fn
全局上下文:
a:1
b:fn
*/
输出结果:function a, 1
面试题3
console.log(foo); // foo["C"]
var foo = "A";
console.log(foo); // "A"
var foo = function () {
console.log('B');
}
console.log(foo); // foo["B"]
foo(); // "B"
function foo() {
console.log('C');
}
console.log(foo); // foo["B"]
foo(); // "B"
/**
* 全局:
* foo: fn["B"]
*/
面试题4
var foo = 1;
function bar(a){
var a1 = a;
var a = foo;
function a(){
console.log(a);
}
a1();
}
bar(3);
/**
函数a1:
输出1
函数bar:
a: 1
a1:fn[a]
全局:
foo: 1
bar: fn
*/
作用域链
用于解释闭包。
-
变量对象中包含一个额外的属性,该属性指向创建该变量对象的函数本身。
-
每个函数在创建时,会有一个隐藏属性
[[scope]],它指向创建该函数的AO。 -
当访问一个变量时,会先查找自身VO中是否存在,如果不存在,则依次查找
[[scope]]属性。
var g = 0;
function A(){
var a = 1;
function B(){
var b = 2;
var C = function(){
var c = 3;
console.log(c,b,a,g);
}
C();
}
B();
}
A();
图解:
当访问一个变量时,会先查找自身VO中是否存在,如果不存在,则依次查找
[[scope]]属性。
上面代码console.log(c,b,a,g)的输出就是根据作用域链来查找的。
代码分析
- 最后输出结果: 1,2,报错(count is not defined)
举一反三
var count = 100;
function A(){
var count = 0;
// 返回一个函数,相当于新创建了一个函数,该函数的[[scope]]指向创建该函数的AO上下文
return function(){
count++;
// 看函数声明时的变量
console.log(count);
}
}
var test = A();
test();
test();
console.log(count);
- 输出结果:1,2,100
例1
var a = 1;
// 看函数A声明时
function A(){
console.log(a);
}
function Special(){
var a = 5;
var B = A;
B();
}
Special();
- 创建函数就形成了闭包
某些浏览器会优化[[scope]]作用域链,将不需要的属性去掉。
面试题1
function func() {
let i = 0;
return () => {
i++;
return i;
}
}
const func1 = func();
const func2 = func();
console.log(func1())// 1
console.log(func2())// 1
console.log(func1()) // 2
console.log(func2()) // 2
面试题2
var foo = {
n: 1
};
(function (foo) {
console.log(foo.n); // 全局的foo n=1
foo.n = 3; //全局的foo n=3
var foo = { // 将自己的VO中指向对象{n:2}
n: 2
};
console.log(foo.n); // 自己的VO中的 n=2
})(foo);
console.log(foo.n); // 全局的foo 3
面试题3
const obj2 = {
key: 'value11'
}
function get(obj2){
obj2 = {
key: 'value22'
}
console.log(obj2); // { key: 'value22' }
}
get(obj2);
console.log(obj2); // { key: 'value11' }
面试题4
var food = 'rice';
(function (){
var food = 'noodle';
var eat = function(){
console.log(food);
}
eat(); // noodle
})()
面试题5
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
- 如何解决上面的闭包问题
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 1000)
})(i);
}