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