博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入作用域
阅读量:6682 次
发布时间:2019-06-25

本文共 4364 字,大约阅读时间需要 14 分钟。

作用域链(ES5.1规格视角)

javascript作用域类型

javascript采用的是词法作用域(静态作用域) 与之相对的是动态作用域,以下的例子可以很好地说明

let a = 3function foo() {  console.log(a); }function foowrapper() {  let a = 4  foo()}foowrapper() // 3而不是4复制代码

静态作用域是与我们惯性思维冲突的,从上述例子可以看出,我们第一次看会习惯性往函数调用位置去找变量(动态作用域思维),但是javascript使用的是静态作用域,也就是代码写在哪里,作用域就从那里链接,而不是运行的位置进行作用域链接,这是整个作用域的核心概念

作用域链定义

函数调用上下文作用域链

规格

伪代码 执行环境由词法环境、变量环境 this组成 ,今天我们主要关注点在
变量环境(VO|AO),和词法环境[[scope]]

activeExecutionContext = {    VO: {...}, // or AO    this: thisValue,    Scope: [ // Scope chain      // 所有变量对象的列表      // 用于标识符查找    ]};复制代码
Scope定义
// [[Scope]]表示内部使用的抽象操作,用于描述语言规范本身Scope = AO + [[Scope]]复制代码

函数激活作用域链接过程

在函数进入上下文(函数创建时)AO/VO,Scope定义如下:

Scope = AO|VO + [[Scope]]复制代码

解析 活动对象是作用域数组的第一个对象,被添加到作用域的前端

Scope = [AO].concat([[Scope]])复制代码

函数激活链接实例

var x = 10; function foo() {  var y = 20;   function bar() {    var z = 30;    alert(x +  y + z);  }   bar();} foo(); // 60复制代码

对此,我们有如下的变量/活动对象,函数的的[[scope]]属性以及上下文的作用域链:

全局上下文的变量对象是:

globalContext.VO === Global = {  x: 10  foo: 
};复制代码

在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [  globalContext.VO];复制代码

在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {  y: 20,  bar: 
};复制代码

“foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]]  fooContext.Scope = [  fooContext.AO,  globalContext.VO];复制代码

内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [  fooContext.AO,  globalContext.VO];复制代码

在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {  z: 30};复制代码

“bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.: barContext.Scope = [  barContext.AO,  fooContext.AO,  globalContext.VO];复制代码

对“x”、“y”、“z”的标识符解析如下:

- "x"-- barContext.AO // not found-- fooContext.AO // not found-- globalContext.VO // found - 10- "y"-- barContext.AO // not found-- fooContext.AO // found - 20- "z"-- barContext.AO // found - 30复制代码

一个例外-使用函数构造函数生成函数

var x = 10; function foo() {   var y = 20;   function barFD() { // 函数声明    alert(x);    alert(y);  }   var barFE = function () { // 函数表达式    alert(x);    alert(y);  };   var barFn = Function('alert(x); alert(y);');   barFD(); // 10, 20  barFE(); // 10, 20  barFn(); // 10, "y" is not defined } foo();复制代码

正常来说,在函数创建时获取函数的[[scope]]属性,通过该属性访问到所有父上下文的变量,但是由函数构造函数创建的的函数,其[[scope]]总是唯一的全局对象属性。

动态改变作用域链

eval

eval能够动态生成函数,且eval上下文与当前调用上下文的作用域链一致 示例

function foo(str, a) {var c = 4;eval( str ); // c = 4console.log( a, b ); // a = 1 b = 3}var b = 2;var c = 3;foo( "var b = 3; console.log(c)", 1 ); 复制代码

这里我们使用eval打印了c,可以看到结果是foo作用中的c,这里判断出eval不是像函数构造函数的[[Scope]]总是唯一的全局变量,而b变量能够在foo中访问,也说明了eval与foo是享受了同一个作用域,当然严格模式下,eval会创建自己的作用域,不会影响到当前调用上下文的作用域,如下例:

function foo(str, a) {  'use strict'  var c = 4  eval(str) // 4  console.log(`foo-a ${a} foo-b ${b}`) // 1, 2 而不是 1 3}var b = 2var c = 3foo('var b = 3; console.log("eval-b",b,"eval-c",c)', 1) 复制代码

这里访问b时,输出了2而不是eval中的3,说明严格模式下,eval创建了自己的作用域,不会影响到当前调用的上下文作用域

with、catch

它们将作用变量添加到作用域链最前端,作用域链修改如下:

Scope = withObject | catchObject + AO | VO +[[Scope]]复制代码

with实例

var x = 10, y = 10; with ({
x: 20}) { var x = 30, y = 30; console.log(x) // 30 console.log(y) // 30} console.log(x) // 10console.log(y) // 30复制代码

过程解析:

  1. x = 10, y = 10
  2. {x: 20} 添加到作用域顶端
  3. var x 、var y 在执行代码前以及被提升到with外面
  4. 修改{x: 20} 中的20 为 30
  5. 没在{x: 30}中找到y,在全局作用域中找到了y,将其修改为30
  6. with结束,将{x: 30} 从作用域链中移出。
  7. 此时x为全局作用域x,没有变化,而y在with声明时,变为30

catch实例

try {  ...} catch (ex) {  console.log(ex)}复制代码

作用域链修改为:

var catchObject = {  ex: 
}; Scope = catchObject + AO|VO + [[Scope]]复制代码

知识关联---二维作用域链(作用域链、原型链)查找

为什么会有二维作用域链呢,因为一个属性在对象中没有直接找到,查询会在原型链中继续。也就是搜索属性

过程
  1. 作用域链环节
  2. 深入到原型链环节
可能的情况

二维作用域链是指在作用域查找之后,再去原型链上查找,分为一下两种情况

  1. 基本属性查找,查找到了作用域链末端,全局变量节点,此时会在全局变量的原型链上展开搜索。
  2. 对象属性查找,先要在作用域链上找到对象本身,然后再去对象原型链上找到指定属性。

情况1示例

function foo() {  console.log(x)} Object.prototype.x = 10; foo(); // 10复制代码

过程解析:

  1. 在foo的VO对象没有找到x
  2. 向作用域链下一个节点查找,此时为window
  3. 无法在window上查找到属性x
  4. 此时已经是作用域顶端,便向着window其原型链找到x,辛酸过程如下图
    可以看到window原型链的末端节点为Object,终于在这里找了x,输出了10

情况2示例

function foo() {  console.log(o.x)}let o = {} Object.prototype.x = 200foo(); 复制代码

过程解析:

  1. 在foo的VO对象没有找到o
  2. 在window对象找到了o
  3. 在o自身查找x属性,没有找到
  4. 在o的原型上Object.prototype上找到了x = 200
  5. 输出200

感谢

参考了,感谢他和汤姆大叔的无私奉献,然后我才能发现一些东西别人几年前就看透了,自己好菜呀(划掉),这里是汤姆大叔的,我推荐大看一下他的深入js系列,虽然在2012写的,但是放在现在仍然是教科书级别的干货,例子出于他的文章和《你不知道的javascript》

这篇文章昨晚写到了1点半,今天又花了一早上来整理,深刻的认识到愿意写、写明白技术文章那些人的厉害,写作过程中也认识到自身水平不足,有点驾驭不住技术深度高的文章,嗯,现在一共写了3篇文章,有三颗,希望这次能够多一颗,更多更好啦,更多希望大家提出不同的意见,一起学习。

转载于:https://juejin.im/post/5cbe6cd06fb9a031fe3bc66f

你可能感兴趣的文章
我的友情链接
查看>>
批量上传公钥到Linux服务器
查看>>
nagios+centreon总结之第八章——Centreon 添加nagios检查命令
查看>>
网页中的编码与乱码(1)
查看>>
CentOS源码安装GitLab汉化版
查看>>
yum 命令的使用,及yum仓库的创建
查看>>
iframe高度自适应的6个方法
查看>>
从构建分布式秒杀系统聊聊分布式锁
查看>>
电子邮件传输之SMTP协议
查看>>
AD域用户登录验证
查看>>
如何部署AD站点
查看>>
CISCO路由器常用命令
查看>>
mysql表行转列的用法
查看>>
基于Groovy实现Spring Bean的动态加载
查看>>
Cordova 创建插件
查看>>
什么是geohash(地理散列)
查看>>
关于iOS 5 Could not instantiate class named NSLayoutConstraint错误
查看>>
ITFriend开发日志20140611
查看>>
Jenkins 安装和配置
查看>>
Ubuntu下Fastdfs的安装
查看>>