浅谈JS作用域链(scope chain)

作用域链:

JS权威指南指出”JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.” 
ECMA262中所述 任何执行上下文时刻的作用域, 都是由作用域链(scope chain)来实现.在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性.
在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.

不必刻意去记概念。当然上面的话,我是记不住,看着也有点绕,下面是我自己这几天学习的体会:


每当执行一个函数就进入一个新的的作用域,使用一个变量或是赋

值。首先从自己的当前作用域内部找变量,找到就输出,找不到就是

往当前函数所在上层的作用域找,(上层的作用域就是当前函数声明的作用域)

基础知识:

console.log(a) //undefined (变量提升,在函数内部同样适用)
var a = 1

fn() // "2"      //函数声明前置
function fn(){
    console.log('2')
}     

相关JavaScript知识点请参考阮一峰JavaScript教程

实例剖析:


🌰①
var x = 10
bar() 
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo()
}
/*
var x

x = 10

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

function bar() {
  var x = 30
  foo()
}
*/

10

分析

变量 x 提升到头部,bar()函数声明前置,函数foo()的变量x的作用域不是在bar()内部,从函数foo()本身也没有找到,往其上层作用域找到为var x = 10全局变量找到,bar()输出的结果是10

🌰②
var a = 1
function fn1(){
  function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2
  return fn3
}
var fn = fn1()
fn() //输出多少

/*
function fn1(){
  var a
 // 函数fn1()内部的变量a, 提升到函数顶部
  function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  a = 2
  return fn3
}
*/

2

结果分析:

可简单的看出输出的结果是fn2()内的cobsole(2)的打印值,fn2()的变量对应的作用域,首先从自身既函数fn2()内部作用域找变量。
内部无法找到就往上层找,上层所对应的作用域即是fn1()内部,内部变量 var a = 2**『函数内部变量提升』,所以fn(2)的变量对应的作用域在函数fn1()内部,console.log(a)对应的a的值是2
fn = fn2() = fn(3)
所以输出结果既是函数fn2()的值2

🌰③
var a = 1
function fn1(){
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2
  return fn3
}
function fn2(){
  console.log(a)
}
var fn = fn1()
fn() //输出多少

1

结果分析:

还是上例同样的套路,fn2()中a对应的作用域,从自己本身找不到,所以往上层找,就是全局变量 a (a = 1),

function fn2(){
  console.log(a)
}

a的值为1
fn2()的结果为1
fn = fn1() = f3(), 函数fn1()fn3的作用域都不是fn2()的变量a所对应的作用域。

🌰④
var a = 1
function fn1(){
  function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
    var a = 4
  }
  var a = 2
  return fn3
}
var fn = fn1()
fn() //输出多少

undefined

结果分析:

return fn3 , fn1() = fn3() = fn2();

fn2()对应的作用域为它的上级作用域fn3()

 function fn3(){
    function fn2(){
      console.log(a)
    }
    fn2()
    var a = 4
  }

fn2()的的输出结果是undefined,这是由于在

 function fn3(){
       // ...
      // ...
   var a = 4
 }

fn3()中变量a 提升到函数内首部,但是函数赋值是在调用fn2()之后,
所以console.log(a)中的a,并没有被赋值,既输出结果是undefined
如果把 (var a = 4)放在调用fn2()之前则输出的结果是4

🌰⑤
var a = 1
var c = { name: 'curry', age: 2 }
function f1(n) {
  ++n
}
function f2(obj) {
  ++obj.age
}
f1(a) 
f2(c) 
f1(c.age) 
console.log(a) 
console.log(c)  

// 1
// { name: 'curry', age:3 }

结果分析:
var a = 1
function f1(n){
  ++n
}
f1(a)
console.log(a) 

等同

var a = 1
function f1(){
  var n = arguments[0]
 // 假设 n 为 函数 f1 的第一个参数
  ++n
}
f1(a)
console.log(a) 

arguments[0]传递进来,arguments[0] = 1++n,大括号内的变量增加1,但是没有返回值,所以和目前的a并没什么关系(并不能改变a的值),
函数f1(a),对应的结果是++a,但是它并没有输出,所以console.log(a)
中的a还是 a = 1(全局变量),既输出结果1
即使 上面的f1(n)返回了值,console.log(a)的值依然是1

var a = 1
function f1(n){
  return ++n
}
f1(a)
console.log(a)
// 2
// 打印的结果是 1

解析:

function f1(n){
  return ++n
}
// 等价于
function f1() {
  var n = arguments[0]
  return ++n
}

上面的🌰中 函数f1(n)中的返回值,可以从根据数据在内存中储存的方式来探究。首先全局变量a是一个简单类型,var n = arguments[0]其中arguments[0]就是变量a,把 变量 a的值赋值给n,此时an都是存在stack里面,且相互独立;当return ++n的时候,就是给n的值加1,而此时a的值并不会跟着变化,还是原来的值。
如果是下面的这种情况打印出来的值也是2

var a = 1
function f1() {
  return ++a
 // 如果是上面的 ++a 没有return,后面的console.log(a)打印的值也是 2 
 // ++a
}
f1()
console.log(a) // 打印出来的a的值是2

var c = { name: 'curry', age: 2 }
function f2(obj){
   // var obj = c 
  // 把c赋值给obj,把c的地址和指针赋值给obj了

  // c = 0x0001
  c与obj其实就是两个名字不同但是***值与地址***相同的值

  ++obj.age
}
f2(c) 
f1(c.age) 
console.log(c)     
image.png
相当于把 `obj = c`,
把c赋值给obj,把c的地址和指针赋值给obj了,
c与obj其实就是两个名字不同但是***值与地址***相同的值

f2(obj)虽然也不会输出结果,但是但是因为obj的地址改变了,则c的地址也改变了
所以console.log(c) 的结果是++obj.age { name: 'curry', age:3 }


总结:

  • 1.函数在执行的过程中,先从自己内部找变量
  • 2.如果找不到,再从创建当前函数所在的作用域去找, 以此往上
  • 3.注意找的是变量的当前的状态

版权声明:本文为博主原创文章,未经博主许可不得转载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容