Skip to content

深入理解JS中声明提升、作用域(链)和this关键字 #16

Open
@creeperyang

Description

@creeperyang

这个issue试图阐述JavaScript这门语言的3个难点:声明提升作用域(链)和this

首先推荐https://github.com/getify/You-Dont-Know-JS,这是一本非常棒的JavaScript书籍,几乎所有的JS知识点都包括并且详细解释了。看一遍相信必有大收获。

1. 声明提升

大部分编程语言都是先声明变量再使用,但在JS中,事情有些不一样:

console.log(a); // undefined
var a = 1;

上面是合法的JS代码,正常输出undefined而不是报错Uncaught ReferenceError: a is not defined。为什么?就是因为声明提升(hoisting)。

1.1 变量声明

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var

语法:

var varname1 [= value1 [, varname2 [, varname3 ... [, varnameN]]]];

变量名可以是任意合法标识符;值可以是任意合法表达式。

重点:

  • 变量声明,不管在哪里发生(声明),都会在任意代码执行前处理。(Variable declarations, wherever they occur, are processed before any code is executed. )。
  • var声明的变量的作用域就是当前执行上下文(execution context),即某个函数,或者全局作用域(声明在函数外)。
  • 赋值给未声明的变量,当执行时会隐式创建全局变量(成为global的属性)。

声明变量和未声明变量的区别

  • 声明变量通常是局部的,未声明变量通常全局的。
  • 声明变量在任意代码执行前创建,未声明变量直到赋值时才存在。
  • 声明变量是execution context(function/global)的non-configurable 属性,未声明变量则是configurable。

es5 strict mode,赋值给未声明的变量将报错。

1.2 定义函数(Defining functions

定义一个函数有两种方式:函数声明(function definition/declaration/statement)和函数表达式( function expression)。

1.2.1 function definition

语法:function name(arguments) {}

对参数而言,primitive parameter是传值,对象是传引用。

1.2.2 function expression

语法:var fun = function (arguments) {}

函数表达式中函数可以不需要名字,即匿名函数。

1.2.3 其它

还可以用 Function构造函数来创建函数。

函数内部引用函数本身有3种方式。比如var foo = function bar(){};

  • 函数名字,即bar()
  • arguments.callee()
  • foo()

1.3 声明提升

1.1提到,var 声明的变量会在任意代码执行前处理,这意味着在任意地方声明变量都等同于在顶部声明——即声明提升。1.2特意强调了函数定义,因为声明提升中,需要综合考虑一般变量和函数。

在JavaScript中,一个变量名进入作用域的方式有 4 种:

  1. Language-defined:所有的作用域默认都会给出 thisarguments 两个变量名(global没有arguments);
  2. Formal parameters(形参):函数有形参,形参会添加到函数的作用域中;
  3. Function declarations(函数声明):如 function foo() {};
  4. Variable declarations(变量声明):如 var foo,包括_函数表达式_。

函数声明和变量声明总是会被移动(即hoist)到它们所在的作用域的顶部(这对你是透明的)。

而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。

一个详细的例子:

function testOrder(arg) {
    console.log(arg); // arg是形参,不会被重新定义
    console.log(a); // 因为函数声明比变量声明优先级高,所以这里a是函数
    var arg = 'hello'; // var arg;变量声明被忽略, arg = 'hello'被执行
    var a = 10; // var a;被忽视; a = 10被执行,a变成number
    function a() {
        console.log('fun');
    } // 被提升到作用域顶部
    console.log(a); // 输出10
    console.log(arg); // 输出hello
}; 
testOrder('hi');
/* 输出:
hi 
function a() {
        console.log('fun');
    }
10 
hello 
*/

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions