javascript 作用域链及闭包,AO,VO,执行环境
下面的文章内容会根据理解程度不断修正。
js变量作用域:
定义:变量在它申明的函数体以及函数体内嵌套的任意函数体内有定义。
function AA(){ var bb='我是AA内部变量'; function TT(){
alert(bb);
}
alert(bb);
TT();
}
AA();
如上图,两次弹出的都是“我是AA内部变量”。
JS的变量作用域是函数级的,也就是在AA内部申明的变量,在AA内部任意位置,包括它嵌套的函数内也是有定义的。
在函数AA外面,bb就是没有定义的。当然如果去掉bb前面var,bb变量就会自动变成全局变量,此时bb在函数AA外也会有效。
JS变量提升:
定义:函数体内申明的变量,会被自动提前到最前面申明。
function AA(){
alert(bb);//undefined
alert(cc);//报错,变量未定义
var bb='我是AA内部变量';
alert(bb);//我是AA内部变量
}
如上,会依次 返回 undefined,报错,我是AA内部变量
第一次alert为什么没有报错呢?这就是变量提升的原因,js执行时会自动将变量申明提升到最前面,但是赋值并不会因此提升。
提升之后就等价于下面这样。
function AA(){
var bb; //定义自动提前,不赋值,
alert(bb);
bb='我是AA内部变量';
}
JS执行环境
执行环境或者叫执行上下文,我理解为代码执行时所处的环境,这个环境决定了它有权访问哪些变量或者函数。
var bb='全局变量'
function AA(){
var bb='局部变量';
var s=function(){
alert(bb);
}
s();
return s;
}
var cc=AA();//局部变量
cc(); //局部变量
如上,依次进入的执行环境是window,AA,s
window---全局执行环境
AA,s ---函数执行环境
变量对象VO[variable object] 活动对象AO[activation object]
每个执行环境都有一个变量对象,这个变量对象的属性绑定了在这个环境里定义的所有变量和函数,形参。VO理解为代码编译时产生。
VO绑定以下属性:
1.函数形参
2.函数申明
3.变量申明
函数被调用后,执行环境就切换成了对应的函数,此时活动对象就会产生。也就是说AO可以理解为函数执行时产生的。
进入函数执行环境后,实际上AO就相当于函数的VO,只是说在函数执行环境里 VO属性不能被直接访问,所以生成AO来替代访问。
var bb='全局变量'
function AA(y){
var bb='局部变量';
function s(){
alert(bb);
}
}
AA(5);
上面代码依次进入的执行环境有两个,首先window,然后是函数AA
全局执行环境window:VO绑定的属性依次,函数AA,变量bb
函数AA执行环境:VO绑定的属性依次是,形参y=5,函数s,变量bb
在全局执行环境中,VO属性是可以被访问的,而进入函数执行环境后VO属性不能被直接访问,此时会生成活动对象AO替代VO,可以访问AO属性。
VO/AO产生的过程也是变量提升的过程,优先提升函数,然后是变量。
此时VO[function]===AO
注:在函数执行环境中,用表达式的方式申明的函数,对应的函数表达式不会加入VO
function AA(){
var sub = function _sub(a, b){
alert(typeof _sub);
return a - b;
} }
sub作为变量会加入VO,_sub作为函数表达式则不会加入。
JS作用域链
作用域链包含了执行环境有权访问的变量、函数的有序访问。它是一个由变量对象(VO/AO)组成的单向链表,主要用来进行变量查找。
JS内部有一个[[scope]]属性,这个属性就是指向作用域链的顶端。
var bb='全局变量'
function AA(y){
var bb='局部变量';
function s(){
var z=0;
alert(bb);
}
s();
}
AA(5);
暂且理解JS在代码编译时创建作用域链,分析上面的代码的 作用域链:
全局执行环境:[[scope]]----->VO[AA,bb] 只有全局VO,[[scope]]直接指向VO。
函数AA执行环境:[[scope]]---->VO[[y,s,bb]VO[[AA,bb]],首先全局VO压入栈,然后函数AA VO压入栈顶,[[scope]]属性指向栈顶,变量、函数搜索就从栈顶开始。
函数s执行环境:[[scope]]--->VO[[z]]VO[[y,s,bb]VO[[AA,bb]],首先全局VO压入栈,然后依次AA,s压入栈,s处于栈顶,[[scope]]属性直接指向s的VO。
应用:比如调用s,进入s执行环境,在执行alert时,首先会去查找bb的申明,会先在作用域链的顶端查找,没查到就会沿着链继续往下查找,直到查到就停止。
总结:
函数执行时,将当前的函数的VO放在链表开头,后面依次是上层函数,最后是全局对象。变量查找则依次从链表的顶端开始。JS有个内部属性[[scope]],这个属性包含了
函数的作用域对象的集合,这个集合就称为函数的作用域链。它决定了,哪些变量或者函数能在当前函数中被访问,以及它的访问顺序。
闭包
我理解为,函数能够访问另一个函数中的变量,这样就构成了一个闭包。只要某个变量在另外一个函数中还存在引用,那么这个变量的值在内存中就不会被释放,除非这个函数不会再执行。
function getvalue(){
for(var i=0;i<10;i++){
var yy=i;
setTimeout(
function(){
var zz=document.getElementById('tt').innerText;
zz+=',';
zz+=yy;
document.getElementById('tt').innerText=zz;
}
,
100)
}
}
定时器中的匿名函数会在for结束之后依次执行,匿名函数中引用了变量yy,根据作用域链规则查找,首先在匿名函数中寻找yy的定义,没有找到,然后去它的上层查找。
yy定义在getvalue中,又yy被其他函数引用着,所以它的结果不会被释放。
而for循环执行结束之后yy的结果是9,所以最后的输出会是:,9,9,9,9,9,9,9,9,9,9
那如果我们想要输出0123456789怎么办呢?看下面的代码
function getvalue(){
for(var i=0;i<10;i++){
var yy=i;
(
function(yy)
{
setTimeout(
function(){
var zz=document.getElementById('tt').innerText;
zz+=',';
zz+=yy;
document.getElementById('tt').innerText=zz;
}
,
100)
}
)(yy)
}
}
上面在定时器外面套了一个立即执行函数
(function(yy){
....
})(yy)
代码执行到这个匿名函数时,这个匿名函数会自动执行,也就是for循环,每次循环到这里这个函数就会立即执行掉,并把参数yy传入匿名函数中。
现在来看定时器中的匿名函数的作用域链。
[[scope]]--->VO[[变量z]] VO[[形参yy,匿名function]] VO[[匿名function,变量yy]] VO[[函数getvalue]]
此时,定时中的匿名函数引用的变量yy,从作用域链中查找可以发现,它来自于上层立即执行函数的形参,而立即执行函数是每次
for循环都会立即执行并把参数传入。我们知道,只要某个变量在另外一个函数中还存在引用,那么这个变量的值在内存中就不会被释放,
系统会每次把立即函数执行的形参传入值保存起来,所以定时器的中的匿名函数在执行结果就会是下面这样。
浅谈this
一般而言,this指向执行环境所处的环境,也就是函数被调用时所处的环境。看到资料说:函数调用f(x,y)其实内部是f.call(this, x, y)这样执行的,所以this是在调用的时候传入的。
如果函数是直接执行的那么this指向window,如果有调用者,那么this指向调用者。
var a=1;
var BB=function(){
alert(this.a);
} var DD={
a:4,
f:BB
}
BB();//1 this 指向window
var zz=DD.f;
zz(); //1 this指向window
DD.f(); //4 this指向f的调用者DD
上面的例子说明,作为对象方法时,this指向的是函数的调用者,直接调用或者在一般函数中调用时,this指向的就是全局对象
var a=1;
var BB=function(){
this.a=2;
}
var zz=new BB();//this 指向new出来的对象
alert(a); //1 所以this.a赋值不会影响全局变量中的a,此时的this指向的不是全局对象
alert(zz.a);//2
BB();//直接执行,this指向window,执行之后,全局变量a的值被改变
alert(a); //2
上面说明,作为构造函数时,函数里的this指向的是new出来的对象。
var a=1;
function BB(){
alert(this.a);
}
function CC(){
this.a=3;
}
BB(); //
var dd=new CC();
BB.apply(dd); //3 实际上this指向dd
BB.call(dd);//3 实际上this指向dd
上面说明,call和apply可以改变this的指向,使用call、apply时,传入的第一个参数就会成为this的指向对象。
javascript 作用域链及闭包,AO,VO,执行环境的更多相关文章
- 个人理解的javascript作用域链与闭包
闭包引入的前提个人理解是为从外部读取局部变量,正常情况下,这是办不到的.简单的闭包举例如下: function f1(){ n=100; function f2(){ alert(n); } retu ...
- 深入javascript作用域链到闭包
我之前用过闭包,用过this,虽然很多时候知道是这么一回事,但是确实理解上还不够深入.再一次看javascript高级程序设计这本书时,发现一起很多疑难问题竟然都懂了,所以总结一下一些理解,难免有错, ...
- js隐式类型转换,预编译、递归、作用域,作用域链、闭包、立即执行函数、继承圣杯模式
隐式类型转换 调用Number()当有运算符(加减乘除,求余)时,会调用Number()转为数字再运算,除了 加 当 有字符串时就变身成拼接Boolean();String(); typeof()st ...
- JavaScript 作用域链与闭包
作用域链 在某个作用域访问某个变量或者函数时,会首先在自己的局部环境作用域中搜寻变量或者函数,如果本地局部环境作用域中有该变量或者函数,则就直接使用找到的这个变量值或者函数:如果本地局部环境作用域中没 ...
- JavaScript作用域链与闭包的理解
作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域 链的工作原理. 1. 全局作用域(Global Scope) (1)最外层函数和 ...
- javaScript执行环境、作用域链与闭包
一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...
- JavaScript作用域链和垃圾回收机制
作用域链 基本概念: 在了解作用域链和内存之前,我们先了解两个概念,分别是执行环境和变量对象. 执行环境:定义变量或者函数有权访问的其他数据,决定了它们各自的行为.每个对象都有自己的执行环境. 变量对 ...
- 【进阶2-2期】JavaScript深入之从作用域链理解闭包(转)
这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/18 红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一 ...
- 图解Javascript——作用域、作用域链、闭包
什么是作用域? 作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围.全局变量拥有全局作用域,局部变量则拥有局部作用域. js是一种没有块级作用域的语言(包括if.for等语句的 ...
随机推荐
- java Socket多线程聊天程序
参考JAVA 通过 Socket 实现 TCP 编程 参考java Socket多线程聊天程序(适合初学者) 以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包 ...
- BZOJ_4003_[JLOI2015]城池攻占_可并堆
BZOJ_4003_[JLOI2015]城池攻占_可并堆 Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 ...
- 机器学习web服务化实战:一次吐血的服务化之路
背景 在公司内部,我负责帮助研究院的小伙伴搭建机器学习web服务,研究院的小伙伴提供一个机器学习本地接口,我负责提供一个对外服务的HTTP接口. 说起人工智能和机器学习,python是最擅长的,其以开 ...
- 鸟哥的Linux私房菜笔记第四章
前言 对着<鸟哥的Linux私房菜-基础版>做了简化笔记.不想让自己知其然而不知其所然.所以写个博客让自己好好巩固一下,当然不可能把书中的内容全部写下来.在这里就简化一点把命令写下来. 让 ...
- 基于tcp实现远程执行命令
命令执行服务器: # Author : Kelvin # Date : 2019/1/30 20:10 from socket import * import subprocess ip_conf = ...
- Java语言编程 - Java第一个程序HelloWorld
3.1 新建Java文件 首先新建一个文件夹,用于存放写的Java程序,例如我存放Java程序的位置为” D:\Files\code\java”. 在该文件夹中,右键新建一个文本文档 将文件名重命名为 ...
- asp.net core系列 48 Identity 身份模型自定义
一.概述 ASP.NET Core Identity提供了一个框架,用于管理和存储在 ASP.NET Core 应用中的用户帐户. Identity添加到项目时单个用户帐户选择作为身份验证机制. 默认 ...
- .net core 在网络高并发下提高JSON的处理效率
现有的webapi一般都基于JSON的格式来处理数据,由于JSON是一个文本类的序列化协议所以在性能上自然就相对低效一些.在.net中常用Newtonsoft.Json是最常用的组件,由于提供简便基于 ...
- Windows核心编程第二章,字符串的表示以及宽窄字符的转换
目录 Windows核心编程,字符串的表示以及宽窄字符的转换 1.字符集 1.1.双字节字符集DBCS 1.2 Unicode字符集 1.3 UTF-8编码 1.4 UTF - 32编码. 1.5 U ...
- 详解mybatis映射配置文件
一 mybatis 映射文件结构 mybatis映射配置文件存在如下顶级元素,且这些元素按照如下顺序被定义. cache – 给定命名空间的缓存配置. cache-ref – 其他命名空间缓存配置的 ...