函数声明与函数表达式、变量提升; 作用域

1,345 阅读5分钟
console.log(foo());
function foo() {
  return bar();
  var bar = function(){ return 5};
  var bar = () => 6;
  var bar = (function(){return 7})();
  function bar() {return 8;}
}

var a = 5;
function todo() {
  var a = 9;
  return function(){
    a=7
  }
}
todo()();
console.log(a);

试试做做上面两题;看console.log能打印出什么。

答案:第一题 8  第二题:5

做对了吗,恭喜你;做错了的小伙伴们,请继续往下看哦。

第一题考的是函数声明与函数表达式、变量提升

在定义一个函数的时候通常有两种声明方式:

function bar() {return 8;}    // 函数声明
var bar = function(){ return 5}   // 函数表达式

不同之处

  1. 函数表达式后面加括号可以直接执行
  2. 函数声明会提前预解析

我们来看 一个例子

foo();         //  函数声明
foo_later();     //  foo_later is not a function

function foo(){ console.log('函数声明'); }
var foo_later = function(){ console.log('函数表达式'); }

变量提升(hoist)

JavaScript解析器会在自身函数作用域内将变量和函数声明提前(hoist),也就是说,上面的例子其实被解析器理解解析成了以下形式:

function foo(){ console.log('函数声明'); }    // 函数声明全部被提前
var foo_later;     // 函数表达式(变量声明)仅将变量提前,赋值操作没有被提前

foo();              //  函数声明
foo_later();    //  foo_later is not a function


foo_later = function(){ console.log('函数表达式'); }

所以 函数声明会被提到顶部;变量也会被提升,但是赋值仍然保留在原地

我们再回头看第一题 就会 被解析成什么样

console.log(foo()); // 放到最后
function foo() {
  return bar();
  var bar = function(){ return 5}; //变量提升,赋值留原地
  var bar = () => 6;   //变量提升,赋值留原地
  var bar = (function(){return 7})();  //变量提升,赋值留原地
  function bar() {return 8;} // 全部提升
}

function foo() {
  var bar; // undefined
  function bar() {return 8;} //function
  return bar(); // 8;结束了

  bar = function(){ return 5};
  bar = () => 6;
  bar = (function(){return 7})();
  
}
console.log(foo()); // 8

补充一点函数表达式

定义里面的指定的函数名是不是被提升的

function text7() {
  a(); // TypeError "a is not a function" 
  b();
  c(); // TypeError "c is not a function" 
  d(); // ReferenceError "d is not defined"

  var a = function() {};    // a指向匿名函数 
  function b() {};          // 函数声明 
  var c = function d() {};  // 命名函数,只有c被提升,d不会被提升。

  a();
  b();
  c();
  d(); // ReferenceError "d is not defined"
}
text7();

下面解析有错❌

注意区分块级作用域和函数作用域

if(true){
  function aaa(){
    alert('1');
  }  
}
else{
  function aaa(){
    alert('2');
  }
}

aaa();

与我们预想的不同,该段代码弹出的是“2”.这是因为两个函数声明在if语句被执行之前就被预解析了,所以if语句根本没有用,调用aaa()的时候直接执行了下面的函数。

上面解析有错

**函数的声明比变量的声明的优先级要高,**一旦变量被赋值后,将会输出变量

function text6() {
  function a() {}
  var a;
  log(a);                //打印出a的函数体

  var b;
  function b() {}
  log(b);                 //打印出b的函数体 

  // !注意看,一旦变量被赋值后,将会输出变量
  var c = 12
  function c() {}
  log(c);                 //12
  
  function d() {}
  var d = 12
  log(d);                //12
}
text6();

请问下面这道题的答案

function text6() {
   var a = 1;
   function b() {
      a = 10;
      return;
      function a() {}
    }
    b();
    console.log(a);         //  ? 1
}
text6();

这里需要注意的是,在function b()中,

var a = () {} //  a类型function  ;console.log(typeof a); function

a=10; // 重新把10复制给a, 此时的a是function b()中的内部变量 10

return; //

function a() {} // 被提升所以,外面输出的a 依旧是最开始定义的全局变量

function text6() {
  var a = 1;
  function b() {
    a = 10;
    return;
    
  }
  b();
  console.log(a); // 10  b()没有定义 a所以向上查找还是 text6中的a;所以text6中的a被改变了
}
text6();

第二题考的作用域

  1. 全局作用域;
  2. 局部作用域;

在js中有四种方式 进入作用域;

  • 语言内置: 所有的作用域里都有this和arguments;(需要注意的是arguments在全局作用域是不可见的)
  • 形式参数: 函数的形式参数会作为函数体作用域的一部分;
  • 函数声明: 像这种形式: function foo() {};
  • 变量声明: 像这样: var foo;

在JavaScript中全局变量的作用域比较简单,它的作用域是全局的,在代码的任何地方都是有定义的。然而函数的参数和局部变量只在函数体内有定义,只存在于局部作用域中。另外局部变量的优先级要高于同名的全局变量,也就是说当局部变量与全局变量重名时,局部变量会覆盖全局变量(如下面例子)。

var num = 1;          
function func() {
   var num = 2;      
    return num;
}
console.log(func(),num); //2 1

注:声明局部变量时一定要定义否则会向上查找,如果没找到,一直查到window,解释器会将该变量当做全局对象window的属性。

var num2 = 1;          
function func() {
   num2 = 2;  num3=3    
    return num2;
}
console.log(func(),num2,window.num3); //2 2 3

函数作用域内部声明的变量,会成为函数的局部变量, 只在函数体内有效,只能在函数体内访问,函数体外部是不可访问的(不可见),不会改变函数体外的变量。

由于只能在函数体内部识别局部变量;因此能够在不同函数中使用同名变量。

注意:不要滥用window全局变量,能被任何局部作用域修改。

var a = 5;
function todo() {
  var a = 9;
  return function(){
    a=7
  }
}
todo()();
console.log(a);

所以刚开始的第二题; 全局变量 a =5; 局部变量声明 a=9; 局部变量只在函数体内有效;虽然改成了7但也不能改变函数体外的值;所以 全局的console.log(a)是5