打卡JS容易忘记的API(二) 执行上下文 JS 代码在遇到一个函数执行时,会进入预编译
状态,生成执行上下文,然后再执行。做题目谨记解题的几大过程,此类题目就迎刃而解,变得很简单
预编译之前需要了解的 a() function a ( ) { console .log('a' ) }
口诀:
函数声明整体提升(函数声明永远提升到js文件最前面 )
变量 声明提升
任何变量,如果变量未经声明就赋值,归全局所有
function a ( ) { var b = c = 20 console .log(window .c) console .log(window .b) } a()
一切声明的全局变量,就归 window 所有 (可以理解为 window 是全局的域对象,定义变量b,相当于往仓库里添加 b)
var b = 10 console .log(b) console .log(window .b)
预编译过程 函数预编译 预编译发生在函数执行的前一刻
创建 AO 对象 — 执行期上下文对象(Activation Object)
找形参 和变量声明 ,将变量和形参名作为 AO 属性名,值为 undefined (变量提升)
将实参值和形参相统一
在函数里面找函数声明 ,将函数名作为属性值挂上,值为函数体
在这过程中还有两个步骤:arguments
作为属性,值为实参数组,this作为属性,this为 window
function fn (a ) { console .log(a) var a = 123 console .log(a) function a ( ) {} console .log(a) var b = function ( ) console .log (b ) function d ( ) {}} fn(1 ) 预编译环节 1. 创建 AO 对象(执行期上下文)2. AO{ a: undefined , b: undefined } 3. AO{ a: 1 , b: undefined }, 4. AO{ a: func a(), b: undefined , d: func d() } 预编译完成之后 AO对象相当于电脑里的东西,然后去执行代码更新AO 1. 打印 func a()2. a 被赋值 123 ,打印 123 3. function a ( ) 已经被提升,跳过4. b 被赋值 fun ( ) 打印 fun ( )
全局预编译 全局预编译过程相比函数预编译过程少了第三步 ,没有参数的概念。如果又存在全局又存在函数则首先考虑全局在考虑函数预编译 。执行代码过程中如果AO有该变量,就先取AO的,没有再去GO中取。
创建 GO 全局对象(GO === window),Global Object
寻找声明变量,赋值undefined
寻找函数声明,函数名作为属性名,函数体作为值
this作为属性,值为window
console .log(test)function test (test ) { console .log(test) var test = 234 console .log(test) function test ( ) {} } test(1 ) var test = 123 全局预编译 1. GO { test: function test ( ) } 执行过程: 1. 打印function test ( ) 2. 跳过声明函数 执行函数test (1 )前一刻,进入函数预编译 1. AO { test:undefined } 2. AO{ test:1 } 3. AO{ test:func } 执行过程: 1. 打印func2. AO{ test:234 } 3. 打印 234
遇到此问题口诀: 1. 首先处理全局对象 2. 处理函数执行前预编译,也就是分析出执行上下文对象 3. 执行期间赋值
例题 function test (a,b ) { console .log(a) c = 0 var c a = 3 b = 2 console .log(b) function b ( ) {} function d ( ) {} console .log(b) } test(1 )
查看答案
1、2、2
按照上述步骤分析: 1. AO{ a:undefined, b:undefined, c:undefined } 2. AO{ a:1, b:undefined, c:undefined } 3. AO{ a:1, b:func(), c:undefined, d:func() } 代码执行: 1. 打印1 2. AO{ a:3, b:2, c:9, d:func } 3. 打印2 4. 打印2 5. 结果 1,2,2
function test (a,b ) { console .log(a) console .log(b) var b = 234 console .log(b) a = 123 console .log(a) function a ( ) {} var a b = 234 var b = function ( ) {} console .log(a) console .log(b) } test(1 )
查看答案
func,undefined,234,123,123,func
1. AO{ a:undefined, b:undefined, } 2. AO{ a: 1, b: undefined } 3. AO{ a: func() b:undefined } 执行阶段: 1. 打印func 2. 打印undefined 3. AO{ a: 1, b:234 } 4. 打印234 5. AO{ a:123, b:234 } 6. 打印123 7. AO{ a:123, b:func } 8. 打印123, 9. 打印func
global = 100 function fn ( ) { console .log(global ) global = 200 console .log(global ) var global = 300 console .log(global ) } fn() var global
查看答案
undefined,200,300
1. 首先是GO{ global:undefined, } 2. GO{ global:undefined, fn:function } 执行过程: 1. GO{ global:100, fn:function } 执行fn函数前一刻,进入函数预编译 1. AO{ global:undefined } 执行过程: 1. 打印undefined 2. AO{ global:200 } 3. 打印200 4. AO{ global:300 } 5. 打印300
function test ( ) { console .log(b) if (a){ var b = 100 } console .log(b) c = 234 console .log(c) } var atest() a = 10 console .log(c)
查看答案
undefined、undefined、234、234
1. GO{ a : undefined } 执行: 执行过程中执行test()之前进入函数预编译 1. 此处不需要管任何其他干扰因素(if for),上面的四个步骤就是真理 AO{ b: undefined } 下面都是全局执行的第一步,只不过是函数的内部执行 ························· 函数执行过程: 1. 打印undefined 2. if条件不成立 3. 打印undefined 4. 注意c是未经声明就赋值的变量。给GO GO{ a:undefined, c: 234 } 5. 打印234 ··························· 2. GO{ a: 10, c: 234 } 3. 打印 234
function bar ( ) {return foofoo=10 function foo ( ) {}var foo =11 } console .log(bar)console .log(bar())
查看答案
过程也很简单,就是打印前一刻进入函数预编译,但是预编译直接return了,AO里面的foo就是个function,然后继续执行全局代码打印出function,第一个bar是全局GO中的属性,第二个是执行结果
a = 100 function demo (e ) { function e ( ) {} arguments [0 ] = 2 console .log(e) if (a) { var b = 123 function c ( ) {} } var c a =10 var a console .log(b) f = 123 console .log(c) console .log(a) } var ademo(1 ) console .log(a)console .log(f)
查看答案
作用域和作用域链 什么是作用域和作用域链 [[scope]]: js的函数的可以视为一个对象,对象中有一些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中之一,[[scope]]指的就是我们所说的作用域,其中存储了执行期上下文的集合
作用域链: [[scope]]中所存储的执行期上下文对象的集合层链式链接 ,我们把这种链式链接叫做作用域链
作用域链的角度分析问题 原则: 自由变量的查找永远都是作用域链顶部向下查找
function a ( ) { function b ( ) { var b = 234 console .log(b) } var a = 123 b() } var glob = 333 a()
1. 首先是 a函数 的定义,此时a的[[scope]]存放的是GO 2. 接着 a函数 的执行,此时a的[[scope]]顶部添加了 a函数的执行期上下文对象AO 3. 接着 a函数 的执行导致了 b函数的定义,此时 b函数借用 a函数的[[scope]],这里就是一模一样的作用域 4. 接着就是 b函数的执行,b的[[scope]]添加自己的执行期上下文对象AO 5. b函数 执行完成,销毁自己的执行期上下文AO,此时b的[[scope]]回到定义阶段 6. 接着就是 a函数执行完成,销毁自己的执行期上下文AO,此时a的[[scope]]只有GO .............. 1. a定义 a.[[scope]] = [GO:{glob:333,a:func}] 2. a执行 a.[[scope]] = [AO:{a:123,b:func},GO:{glob:333,a:func}] 3. b定义 b.[[scope]] = [AO:{a:123,b:func},GO:{glob:333,a:func}] 4. b执行 b.[[scope]] = [bAO:{b:234},AO:{a:123,b:func},GO:{glob:333,a:func}] 5. b销毁 b.[[scope]] = [AO:{a:123,b:func},GO:{glob:333,a:func}] 6. a销毁 a.[[scope]] = [GO:{glob:333,a:func}]
图解流程:
例题
尝试用作用域分析以下函数
function a ( ) { function b ( ) { function c ( ) { } c() } b() } a()
查看答案
分析区分定义和执行期的各个函数的 [[scope]]
即可
看题输入答案,并解释为什么
function a ( ) { function b ( ) { var bbb = 234 console .log(aaa) } var aaa = 123 return b } var global = 100 var demo = a()demo()
查看答案
``` </details> ## 闭包 ### 闭包的概念 当内部函数被保存到了外部,就叫做闭包。闭包的产生会照成原有作用域链未释放,导致内存泄漏。 内存泄漏: 占用的多,剩下的少了  但凡是内部函数被保存到了外部,都属于闭包 ```js function a() { var num = 100 function b() { num ++ console.log(num) } return b } var demo = a() demo() demo()
原理分析: 首先是a定义,此时a的作用域只包含了全局执行上下文GO, 然后就是a的执行,这个时候作用域链顶端插入a函数的执行期上下文aAO, 里面有num属性:100,然后a执行导致了b的定义,b拿了a的劳动成果直接将作用域链搬过来, 接着b并没有执行,而是返回到了外部。返回后a执行完成,销毁了自己的执行期上下文, 但是没有影响到b搬过来的执行期上下文,接着b执行,产生自己的执行期上下文, 自己的上下文没有就去a里面拿,然后加1。然后执行完成,销毁。又执行一次, 又产生了一个新的执行期上下文,num在原有基础上+1,最后执行完成销毁。 所以最后打印的是101,102
function test ( ) { var arr = []; for (var i = 0 ;i < 10 ;i++){ arr[i] = function ( ) { console .log(i) } return arr; } var myArr = test(); for (var j = 0 ;j < 10 ;j++){ myArr[j](); }
原理分析: 首先保存出去10个函数和test都形成了闭包, 而且都是使用公用的同一个i变量, 在这些函数保存出去之前 i变量已经变成了10, 然后再去争先恐后的执行,所以打印10个10
闭包的作用
实现公有变量
例如: 实现累加器
function a ( ) { var num = 0 function b ( ) { num ++ console .log(num) } return b } var demo = a()demo() demo()
可以做缓存(存储结构)
function eater ( ) { var food = "" var obj = { eat: function ( ) { console .log("i am eating" + food) food = "" }, push:function (myFood ) { food = myFood } } return obj } var eater1 = eater()eater1.push('banana' ) eater1.eat()
可以实现封装,属性私有化
使用闭包可以封装数据,外部无法访问,只能够调用函数来获取里面的值
function createCache ( ) { const data = {} return { get: function (key ) { return data[key] }, set:function (key,value ) { data[key] = value } } }
模块化开发,防止污染全局变量
闭包的几种表现形式
var a = 1 ;function foo ( ) { var a = 2 ; function baz ( ) { console .log(a); } bar(baz); } function bar (fn ) { fn(); } foo();
var count = (function ( ) { var num = 0 return function ( ) { num++ console .log(num) } }()) count() count()
立即执行函数 此类函数只会执行一次,并且直接销毁内存,不会占用内存空间,常常用来初始化数据
(function ( ) { console .log(1 ) }()) var a = (function (b,c ) {}(1 ,2 ))
规则
var x = 1 if (function f ( ) {}){ x+=typeof f; } console .log(x)