js中的作用域和作用域链

作用域就是变量与函数的可访问范围。在js中只有 全局作用域函数作用域 ,并没有块级作用域。

全局作用域

在所有函数外定义的变量、声明的函数就是全局作用域,在全部环境下都可以访问。

var a = 111;

function fn(){
    console.log(a);
}

fn(); // 打印了111

在函数中没有使用 var 关键词声明的变量

function fn(){
    a = 111;
}

fn();

console.log( a ); // 打印了111

作为 window 对象的属性赋值

function fn(){
    window.a = 111;
}

fn();

console.log(a); // 111 ,同样可以拿到a变量

注:所有的全局变量都是 window 对象的属性,可以通过 window[propertyName]来访问

函数用域

js中函数具有自己的作用域,在函数中使用var关键词声明的变量函数外是访问不到的,但是在函数中却可以很方便的访问到函数外定义的变量。在其中起作用的就是函数的作用域链。

作用域链的作用就是让函数在运行的时候查找需要用到的标识符,当console.log(a)的时候就会在作用域链中查找a如果找到了就返回找到的值,若没有找到就会报错,这个和在对象中查找不存在的属性只会返回undefined不同。

var a = 111;

function fn(){
    var b = 222;
    console.log(a);
}

fn(c); // 111
console.log(b); // ReferenceError: b is not defined 访问为定义变量的错误

在定义函数的时候js引擎会为函数生成一个[[scope]]属性(函数也是对象所以可以有属性),这个属性存放着当前执行环境的作用域链。

当函数执行的时候会创建一个执行上下文,执行上下文定义了函数的执行环境。每个执行环境都有自己的作用域链(不是函数定义时的[[scope]]属性)用于标识符解析,执行上下文复制函数定义时被定义的[[scope]]属性值到自己的作用域链上,并且会创建一个 活动对象(对象包括arugments,this,声明的变量等)添加到自身作用域链的顶端。

函数定义

函数定义时的作用域属性

函数执行

函数执行时的作用域链

闭包

function fn(){
    var c = 111;
    return function bibao(){
        return c;
    }
}

var d = fn();  // 这时候的d是fn内部定义的函数bibao

console.log( d() ); // 函数bibao 返回了fn的内部变量c,所以在全局环境下就可以在外部访问fn内部变量,这就是闭包。
  1. 首先定义了函数fn,fn生成了[[scope]]属性,window对象在该属性中。
  2. 执行fn 的时候 创建了一个fn的执行上下文,执行上下文有自己的作用域链,作用域链中包括了 fn 的[[scope]]属性,同时创建了一个变量对象,变量对象中包括arguments对象、this、 变量c等等。
  3. 在fn函数中定义函数bibao,生成bibao的[[scope]]属性,该属性会复制当前执行上下文的作用域链到自身。
  4. 当执行fn的时候返回bibao函数,注意bibao函数的[[scope]]属性中是有fn的执行环境的作用域链。
  5. 执行d的时候也就是执行了bibao函数。
  6. 执行bibao函数的时候bibao函数会创建一个执行的上下文,上下文会复制bibao函数的[[scope]]属性来创建自己的作用域链,同时创建一个活动对象来添加到自己的作用域链顶端。
  7. 返回 变量c 这个c并不在 bibao 函数自己的活动对象上,但是却在bibao函数执行上下文的作用域链里面,所以可以访问到。

以上就是我理解的闭包的形成。

按照上面的理解js中闭包存在于很多地方,只要在函数中访问上级作用域中的变量就形成了闭包。

var a = 111;

function fn(){
    console.log(a);
}

fn(); // 111 这里打印出来的111也不是自身活动对象上的变量,是上级作用域的变量,所以这也是闭包

改变作用域链

with(obj)

将对象推入作用域链前端,让with代码块中可以访问到相关的变量。

with({a: '111'}){
  console.log(a); // 111
}

try{}catch(ex){}

然后把异常对象推入一个可变对象并置于作用域的头部

函数声明和函数表达式

在js中使用这两种方法定义函数有显著的差别。

函数声明可以放在函数调用的后面,也就是说可以先调用函数再声明函数。但是函数表达式的定义放在了函数调用后面的话就会报错。

fn(); // 会报错

var fn = function(){
    console.log('111');
};

实际执行的是

var fn;

fn(); // 相当于undefined()

fn = function(){
    console.log('111');
}

用函数表达式正确的写法是先声明赋值之后再调用。

var fn = function(){
    console.log('111');
};

fn();

下面是函数声明的方式。

fn();

function fn(){
    console.log('22222');
}

实际执行可以看成

function fn(){
    console.log('22222');
}

fn();

为甚会这样呢?js会把声明提前(函数声明和变量声明),但是赋值并不会提前,所以才会看到上面一系列奇怪的现象。

根据先定义后使用的规范,建议使用函数表达式,不使用函数声明。

猜你喜欢

转载自blog.csdn.net/lettertiger/article/details/79117575