JavaScript 声明之道

JavaScript中创建变量或函数称为声明变量或声明函数。

一、声明变量

声明变量可通过 var、let、const来定义。

1、var 声明语句声明一个变量,并可选地将其初始化为一个值

  • 用 var 声明的变量的作用域是它当前的执行上下文

function x() {
  y = 1;   // 在严格模式(strict mode)下会抛出 ReferenceError 异常
  var z = 2;
}

x();

console.log(y); // 打印 "1",不使用var变量变成全局的
console.log(z); // 抛出 ReferenceError: z 未在 x 外部声明
  • 声明变量是它所在上下文环境的不可配置属性,非声明变量是可配置的(如非声明变量可以被删除

var a = 1;
b = 2;

delete this.a; // 在严格模式(strict mode)下抛出TypeError,其他情况下执行失败并无任何提示。
delete this.b;

console.log(a, b); // 抛出ReferenceError。
// 'b'属性已经被删除。
  •  执行的顺序
var x = y, y = 'A';
console.log(x + y); // undefinedA

在这里,x 和 y 在代码执行前就已经创建了,而赋值操作发生在创建之后。当"x = y"执行时,y 已经存在,所以不抛出ReferenceError,并且它的值是'undefined'。所以 x 被赋予 undefined 值。然后,y 被赋予'A'。于是,在执行完第一行之后,x === undefined && y === 'A' 才出现了这样的结果。

  • 多个变量的初始化
var x = 0;

function f(){
  var x = y = 1; // x在函数内部声明,y不是!
  console.log(x, y); // 1, 1
}
f();

console.log(x, y); // 0, 1
// x 是全局变量。
// y 是隐式声明的全局变量。
  • 隐式全局变量和外部函数作用域
var x = 0;  // x是全局变量,并且赋值为0。

console.log(typeof z); // undefined,因为z还不存在。

function a() { // 当a被调用时,
  var y = 2;   // y被声明成函数a作用域的变量,然后赋值成2。

  console.log(x, y);   // 0 2 

  function b() {       // 当b被调用时,
    x = 3;  // 全局变量x被赋值为3,不生成全局变量。
    y = 4;  // 已存在的外部函数的y变量被赋值为4,不生成新的全局变量。
    z = 5;  // 创建新的全局变量z,并且给z赋值为5。 
  }         // (在严格模式下(strict mode)抛出ReferenceError)

  b();     // 调用b时创建了全局变量z。
  console.log(x, y, z);  // 3 4 5
}

a();                   // 调用a时同时调用了b。
console.log(x, z);     // 3 5
console.log(typeof y); // undefined,因为y是a函数的本地(local)变量。

2、变量提升

由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用,这个行为叫做“hoisting”。“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置 。

function do_something() {
  console.log(bar); // undefined
  var bar = 111;
  console.log(bar); // 111
}

// is implicitly understood as: 
function do_something() {
  var bar;
  console.log(bar); // undefined
  bar = 111;
  console.log(bar); // 111
}

3、let声明 

let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。

let允许你声明一个作用域被限制在 级中的变量、语句或者表达式。与 var 关键字不同的是, var声明的变量只能是全局或者整个函数块的。 var 和 let 的不同之处在于后者是在编译时才初始化。

就像const 一样,let不会在全局声明时(在最顶部的范围)创建window 对象的属性

  • 作用域规则 

 let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数

function varTest() {
  var x = 1;
  {
    var x = 2;  // 同样的变量!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

 在程序和方法的最顶端,let不像 var 一样,let不会在全局对象里新建一个属性。比如:

位于函数或代码顶部的var声明会给全局对象新增属性, 而let不会。例如:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined 
  • 重复声明  

在同一个函数或块作用域中重复声明同一个变量会引起SyntaxError 

if (x) {
  let foo;
  let foo; // SyntaxError thrown.
}

在 switch 语句中只有一个块,你可能因此而遇到错误。

let x = 1;
switch(x) {
  case 0:
    let foo;
    break;
    
  case 1:
    let foo; // SyntaxError for redeclaration.
    break;
}
let x = 1;

{
  var x = 2; // SyntaxError for re-declaration
}

// x var声明的x会提升至块的顶部,导致重复声明

 然而,需要特别指出的是,一个嵌套在 case 子句中的块会创建一个新的块作用域的词法环境,就不会产生上诉重复声明的错误。

let x = 1;

switch(x) {
  case 0: {
    let foo;
    break;
  }  
  case 1: {
    let foo;
    break;
  }
}
  •  暂时性死区

 与通过  var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

function do_something() {
  console.log(bar); // undefined
  console.log(foo); // ReferenceError
  var bar = 1;
  let foo = 2;
}
  •  用在块级作用域中时, let将变量的作用域限制在块内, 而var声明的变量的作用域是在函数内。
var a = 1;
var b = 2;

if (a === 1) {
  var a = 11; // the scope is global
  let b = 22; // the scope is inside the if-block

  console.log(a);  // 11
  console.log(b);  // 22
} 

console.log(a); // 11
console.log(b); // 2

4、const 声明

const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。

// 注意: 常量在声明的时候可以使用大小写,但通常情况下全部用大写字母。 

// 定义常量MY_FAV并赋值7
const MY_FAV = 7;

// 报错
MY_FAV = 20;

// 输出 7
console.log("my favorite number is: " + MY_FAV);

// 尝试重新声明会报错 
const MY_FAV = 20;

//  MY_FAV 保留给上面的常量,这个操作会失败
var MY_FAV = 20; 

// 也会报错
let MY_FAV = 20;

// 注意块范围的性质很重要
if (MY_FAV === 7) { 
    // 没问题,并且创建了一个块作用域变量 MY_FAV
    // (works equally well with let to declare a block scoped non const variable)
    let MY_FAV = 20;

    // MY_FAV 现在为 20
    console.log('my favorite number is ' + MY_FAV);

    // 这被提升到全局上下文并引发错误
    var MY_FAV = 20;
}

// MY_FAV 依旧为7
console.log("my favorite number is " + MY_FAV);

// 常量要求一个初始值
const FOO; // SyntaxError: missing = in const declaration

// 常量可以定义成对象
const MY_OBJECT = {"key": "value"};

// 重写对象和上面一样会失败
MY_OBJECT = {"OTHER_KEY": "value"};

// 对象属性并不在保护的范围内,下面这个声明会成功执行
MY_OBJECT.key = "otherValue";

// 也可以用来定义数组
const MY_ARRAY = [];
// It's possible to push items into the array
// 可以向数组填充数据
MY_ARRAY.push('A'); // ["A"]
// 但是,将一个新数组赋给变量会引发错误
MY_ARRAY = ['B']

二、函数声明

 

函数表达式(Function Expression):将函数定义为表达式语句(通常是变量赋值,也可以是自调用形式)的一部分。函数表达式可以是命名的,也可以是匿名的。可以没有函数名,常被成为匿名函数,如果有,函数名也只存在自身函数作用域,并且函数表达式不能以“function”开头,函数表达式可以存储在变量或者对象属性中。

函数声明(Function Declaration):是一种独立的结构,它会声明一个具名函数,并必须以function开头。且函数声明会进行函数提升,使它能在其所在作用域的任意位置被调用,即后面的代码可以将此函数通过函数名赋值给变量或者对象属性。

Function()构造器:使用Function()构造器 函数,不推荐。

// 函数声明
function f1() {
     console.log("声明);
}

// 函数表达式
var f2 = function() {
      console.log("表达式");  
}

// Function()构造器
var f3 =new Function()

函数提升

区分函数的创建是否需要被提升,如果是函数声明就需要被提升创建,但如果是表达式,那么可以在执行时再创建。

hello();

var hello = function(){
    alert("Hello, world!");
}

// TypeError: hello is not a function

 函数提升:就是指允许先调用函数,再进行声明,因为声明会自动提升至调用前面执行。函数声明,会将整个函数进行提升.而函数表达式则不会提升,它是在引擎运行时进行赋值,且要等到表达式赋值完成后才能调用。

hello();

function hello() {
    alert("Hello, world!");
}

如何判断函数声明与函数表达式

  • 匿名函数必然是函数表达式
  • 如果有名字的函数作为赋值表达式的一部分那么他也是一个表达式
  • 如果有名字的函数被括号“()”括住,那么他也是一个表达式

函数提升和变量提升 

函数声明的优先级高于变量声明,变量提升,变量声明在后


console.log(bar);  // f bar() { console.log(123) }
console.log(bar()); // undefined 原因是bar函数没有返回值,所以返回undefined
var bar = 456;
function bar() {
    console.log(123); // 123
}
console.log(bar); // 456
bar = 789;
console.log(bar); // 789
console.log(bar()) // bar is not a function

 


// js执行步骤
 
// 函数提升,函数提升优先级高于变量提升
var bar = function() {
    console.log(123)
};
// 变量提升,变量提升不会覆盖(同名)函数提升,只有变量再次赋值时,才会被覆盖
var bar;
console.log(bar);
console.log(bar());
// 变量赋值,覆盖同名函数字面量
bar = 456;
console.log(bar);
// 再次赋值
bar = 789
console.log(bar);
console.log(bar());

总结

  • Javascript中声明会被提升;
  • 对于变量显式声明提升的仅仅是声明,赋值并未被提升;
  • 对于函数声明由于其赋值和声明是一体的,所以提升的是整个函数的定义;
  • 变量隐式声明和函数表达式不会被提升。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值