作用域

作用域直白的意思的作用范围

块级作用域:{}之间的称为块级作用域。在JavaC语言中有块级作用域,而JavaScript中没有块级作用域。

词法作用域

JavaScript中采用的是词法作用域。在代码编写过程中,不需要运行,变量的作用域就已经确定。

动态作用域:变量的作用域取决于当时的执行环境。

词法作用域的规则

  • 词法作用域中的访问原则——就近原则
    首先在当前作用域中查找,如果没有就去上一级的作用域中查找,直到全局作用域,如果都没有返回undefined或者报错(函数)。
    注意:外层作用域不能访问内层作用域。

  • 只有函数才能限定作用域范围,函数内允许访问函数外的数据。

    1
    2
    3
    4
    5
    6
    var a = 10;
    function fun() {
    console.log(a); //10
    var b = 1;
    }
    console.log(b); //undefined
  • 变量提升

变量提升

变量提升时只会提升到当前作用域的顶端。

JavaScript的解析过程

第一阶段:预处理(预解析)
现将代码读取到内存中检查,会将所有的声明在此时进行标记。所谓的标记就是让JS解释器知道有这个名字,后面在使用名字的时候,不会出现未定义的错误。这个标记的过程就是提升。

  • var定义的变量
  • 用声明方式创建的函数
    在函数表达式中,只会提升var声明的变量,并不会把整个函数表达式提升

第二阶段:执行阶段

覆盖问题

  • 函数名和函数名一样时,后面的会覆盖前面的
  • 变量名和变量名一样时,后面的会覆盖前面的
  • 函数名和变量名一样时,优先读取函数。即可看成只提升函数的声明,不提升同名的变量声明。

作用域链

  1. 函数可以创建限定的作用域
  2. 函数内可以声明一个函数
  3. 函数中的函数也可以创建函数
  4. 所以,形成一条向上的作用域链

  5. 最里层的函数可以逐级向上读取作用域

  6. 外层的作用域读取不到里层的作用域

综合笔试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Foo() {
getName = function(){
console.log("1");
};
return this;
}
Foo.getName = function() {
console.log("2");
};
Foo.prototype.getName = function(){
console.log("3");
};
var getName = function() {
console.log("4");
};
function getName(){
console.log("5");
}
//求出以下输出的结果
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

思考过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//预解析
function Foo() {
getName = function(){
console.log("1");
};
return this;
}
var getName; //变量声明提升
function getName(){ //函数声明提升
console.log("5");
}
Foo.getName = function() {
console.log("2");
};
Foo.prototype.getName = function(){
console.log("3");
};
getName = function() {
console.log("4");
};

  1. Foo.getName();
    首先先声明了一个构造函数,其中有getName这个属性,但是随后对该属性进行了重新赋值,覆盖了原来的属性。所以,此时执行覆盖后的函数,输出2
  2. getName();
    getName()只需要看最后两个函数。首先函数表达式进行变量提升,将var getName提升到顶端,但是函数主体依然不动。最后一个函数声明直接提升到var getName的后面,但是还是在第四个函数的前面。所以最后输出最后面的函数,就是4
  3. Foo().getName();
    先执行Foo(),得到Foo返回值对象的getName()。在执行Foo()时,返回值的this指向windowgetName = function(){console.log("1");};是一个函数赋值语句,是全局变量,先向当前Foo函数作用域内寻找getName变量,没有找到,在全局作用域中查找,找到了全局中getNameconsole.log(4)语句,将此变量的值赋值为function(){console.log("1");};完成覆盖。
  4. getName();
    因为上一步Foo().getName();执行完成后,变成window.getName()输出1
  5. new Foo.getName();
    由于优先级的关系,.语法优先级高于new,相当于new (Foo.getName) (),实际上将getName函数作为了构造函数来执行,执行new 2输出2
  6. new Foo().getName();
    先执行创建构造函数new Foo(),再用实例里的getName方法,实例不能调用构造函数中方法,去原型对象中找到这个方法输出3
  7. new new Foo().getName();
    相当于new ((new Foo()).getName)在6的基础上,new 3输出结果3