别催了~ 正在从服务器偷取页面 . . .

javascript作用域


作用域问题

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符

var a=2;//如果注释这一行结果是undefined
function f(){
  console.log(a);
}

function f1(){
  var a=3;
  f();
}

f1();//2

这个例子是一个常见的问题,或许多数人会认为调用f1应该输出的是3,会把这个问题理解成一个作用域链的问题,但是其实不全是,第九行是f函数在f1里面调用,不是在f1里定义,f的上级作用域是全局,所以其实作用域链是f函数作用域–>全局作用域和f1函数作用域–>全局作用于,所以调用f1输出的是3。这就是体现了一个词法作用域的概念,就是它是一个静态的作用域,是编写代码时决定的,跟在哪里调用没关系。

改成如下,作用域就是f函数作用域–>f1函数作用域–>全局作用域

var a=2;//如果注释这一行结果是undefined

function f1(){
  var a=3;
  function f(){
     console.log(a)
   }
   f()
}

f1();//3

全局作用域

全局作用域就是在程序任何地方都能访问,window对象的内置属性都属于全局作用域

函数作用域

函数作用域指的是,属于这个函数的全部变量可以在函数的范围内使用,但是函数外不行。

function test() {
  var x = 1;
  if (true) {
    var x = 2;  // 同样的变量,已重复赋值
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

调用栈如下

因为变量x处于函数test的执行上下文中,函数test编译后变量x的值首先为undefined,接着执行赋值语句,先赋值为1,之后又赋值为2,所以,执行最后两个输出语句时,输出的值是2

块级作用域

简单理解就是{ }内的作用域就称为块级作用域,比如if、while等括号里。

var a=2;

if(a===2){
let b=2*a;
console.log(b);//4
}
console.log(b);//报错uncaught ReferenceError: b is not defined,如果let改成var就是4

let不会在块级作用域变量提升,var会

{
console.log(a);//报错uncaught ReferenceError: b is not defined,如果let改成var就是undefined
let a=2;
}

例子分析:

//把上面的test例子的x变量用let定义
function test() {
  var y=3;
  let x = 1;
  if (true) {
    var a=5;
    let z=4;
    let x = 2;  // 同样的变量,已重复赋值
    console.log(x);  // 2
  }
  console.log(x);  // 1
  console.log(y);//3
}

test的执行上下文:

上面的图是test执行到第8行后的执行上下文,let声明的变量和var的不同,函数内部通过var声明的变量,在编译阶段全都var被存放到变量环境里面了,通过let声明的变量会在编译阶段会被存放到词法环境(Lexical Environment)中,而且不同块是不同是词法环境,if块是单独的一个环境。词法环境是一个栈结构,第9行的输出是if块里的x为2,之后块执行完毕,if块的环境弹出,之后输出的x是底下的1。
一个变量的查找的过程的从词法环境的栈顶到栈底,再从变量环境里从底往上找(如上图的红线)

var、let、const的区别

作用域 变量提升 重复声明同一变量 声明的变量能否改 成为window对象的属性
var 函数作用域 可以
let 块级作用域 没有 不可以
const 块级作用域 没有 不可以 不能

一个常见的问题

for(var i = 0; i < 5; i++){
  setTimeout(function(){
      console.log(i);   
  },0); 
};//5 5 5 5 5
解决方法
方法一
for(let i = 0; i < 5; i++){
  setTimeout(function(){
      console.log(i);   
  },0); 
};
方法二
for (var i = 0; i < 5; i++) {
    (function(i) {
      setTimeout(function () {
        console.log(i);
      }, 0)
    })(i)
  };

什么是暂时性死区?

MDN上的定义:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let?retiredLocale=he

https://juejin.cn/post/6983702070293430303

个人感觉不用去纠结let有没有变量提升,变量提升就是一段代码在编译阶段是能够识别到var和let创建的变量的,只会对二者的操作不一样:对var定义的变量初始化为undefined,而let定义的变量仍然处于未初始化状态。

在用let声明某变量之前引用该变量,就会出现暂时性死区。其实就是因为let没有变量提升,而var有。

console.log(abc);//报错 Cannot access 'abc' before initialization
let abc;

变量提升

主要有以下3条规律:

1.变量和函数在内的所有声明都会在执行代码前先被处理,先编译后执行。

var a=2;//相当于   var a;a=2;console.log(a);
console.log(a);//2

console.log(b)//相当于  var b; console.log(b); b=1;
var b=1;//undefined

2.函数声明会提升,但是函数表达式不会

f();//2

function f(){
  var a=2;
  console.log(a)
}

f1();   //TypeError

var f1=function fun(){ ...}

3.变量和函数名称相同时,函数会先提升,然后才是变量(个人感觉这个结论不太对)

f();//1

var f;

function f(){
console.log(1);

}

f=function(){
console.log(2);
}

如果定义了两个同名的函数,后定义的会覆盖前定义的

总结1:
所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值undefined

变量提升的原理

一段JavaScript代码在执行之前需要被JavaScript引擎编译,编译完成之后,才会进入执行阶段
编译阶段
编译的结果是形成执行上下文和可执行代码,
执行上下文是JavaScript执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如this、变量、对象以及函数等。执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容。
可执行代码就是除了声明外的代码

f()
console.log(a)
var a=1
function f(){
  console.log('函数')
}

这段代码在编译时,第3行有var声明的变量,js引擎会在在环境对象中创建一个名为a的属性,并使用undefined对其初始化,第4行有一个通过function定义的函数,所以它将函数定义存储到堆(heap)中,并在环境对象中创建一个f的属性,然后将该属性值指向堆中函数的位置。
在执行阶段的时候函数f执行输出“函数”,执行第二行时,环境对象中a的值为undefined,输出undefined,之后再执行a=1

作用域链

一般情况下,变量取值到创建这个变量的函数作用域去取值,但是如果在当前作用域中没有查找到值,就会想上级作用域去查找,直到查到全局作用域,这么查找的过程形成的链条叫作用域链。

var a=1;

function f(){
  var b=2;
  function f1(){
   console.log(a+b);
  }
  return f1;
}
var x=f();
x();

文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
评论
  目录