JavaScript变量声明、变量作用域、作用域链、函数、闭包、原型

摘自《JavaScript权威指南(第六版)》

1. 变量声明

  • 在js程序中,使用一个变量之前应该先声明,变量用var来声明,在全局作用域可以没有var声明全局变量, 但在函数内没有var声明也是全局变量,但是必须是函数执行后才生效。
scope='global';
function checkscope2() {
    scope='local';
    myscope='local';
    return [scope,myscope];
}
console.log(myscope); //  输出 undefined 此时未解析
console.log(checkscope2()); // 输出 ["local", "local"]
scope;// 输出 local
console.log(myscope); //  local 
  • js是动态语言,声明不需要给变量指定数据类型,该语言会在第一次赋值给变量时,在内部将数据类型记录下来,Python也一样。而静态语言如C,JAVA,它的数据类型是在编译期间检查的,即声明变量必须指定数据类型。

2. 变量作用域

  • 一个变量的作用域是程序源代码中定义这个变量的区域。全局变量拥有全局的作用域。函数内声明的变量只有函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。

  • 在函数体内,局部变量的优先级高于同名的全局变量。如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所覆盖。

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); //输出local scope
  • 函数作用域和声明提前
    JavaScript没有块级作用域,取而代之地使用了函数作用域(function scope):变量在它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
/**
** 代码中在不同位置定义了变量i,j和k,它们都在同一个作用域内——这三个变量在函数体内均是有定义的。
这意味着变量在声明之前甚至已经可用。JavaScript这个特性被非正式地称为声明提前。
**/
function test(o) {
    var i=o;
    if(typeof(o) == 'object') {
        var j = o;
        for(var k=0; k<10; k++) {
            console.log(k);
        }
    }
    console.log(j);
}
/**
* 输出是undefined, 变量存在但未赋值
**/
function f() {
    var scope;
    console.log(scope);
    scope = "local";
    console.log(scope);
}
  • 作为属性的变量
    当你声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性,当使用var声明变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。
var truevar = 1; //声明一个不可删除的全局变量
fakevar = 2; //创建全局对象 的一个可删除的属性
this.fakevar2 = 3; // 同上
delete truevar; //  => false 变量并没有被删除
delete fakevar(2); // => true 变量被删除

JavaScript全局变量是全局对象的属性,这是ECMAScript规范中强制规定的,对于局部变量则没有如此规定,局部变量当做跟函数调用相关的某个对象的属性。ECMAScript 3 规范称该对象为“调用对象”,ECMAScript 5 规定称为“声明上下文对象”。JavaScript可以允许this关键字来引用全局对象,却没有办法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不可见的内部实现。

3. 作用域链

  • 全局变量在程序中始终都是有定义的,局部变量在声明它的函数体内以及它所嵌套的函数内始终是有定义的;

  • 如果将每一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表。这组对象定义了这段代码“作用域中”的变量。

  • 当JavaScript需要查找变量x的值的时候(这个过程叫“变量解析”),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象不存在x属性,则查找链上的下一个对象,以此类推。如果没找到,则抛一个引用异常。

  • 在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链有一个全局对象组成。在不包含嵌套的函数体内,作用域上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建了一个更长的表示函数调用作用域的“链”。 对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。 因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都有微妙的差别——在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

4. 函数和闭包

  • JavaScript 采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是调用时决定的。为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过这种作用域相互关联起来,函数体内的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。

  • 从技术的角度将,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链,定义大多数函数的作用域链在调用函数时依然有效,但这并不影响闭包。当调用函数时闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时,事情就变得微妙。当一个函数嵌套了另一个函数,外部函数将嵌套的函数对象作为返回值返回的时候,往往会发生这种事情。

理解闭包首先要了解嵌套函数的词法作用域规则:

/**
* JavaScript函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的。
嵌套的函数f()定义在这个作用域链里,其中的变量scope一定是局部变量,不论
何时何地执行f(),这种绑定在执行f()时依然有效。因此最后一行代码返回的是“local”。闭包能够捕捉到局部变量和参数,并一直保存下来,看起来想这些
变量绑定到了在其中定义它们的外部函数。
**/
var scope = 'global';
function checkscope() {
    var scope = "local";
    function f(){return scope;}
    return f;
}
checkscope()(); //输出local
/** 
* 代码定义了一个立即调用的函数,返回值也是函数,嵌套的函数可以访问
* 外部函数的counter变量。但外部函数返回之后,其它任何代码无法访问counter
* 只有内部函数能够访问它。如果有多个嵌套函数,也可以访问它,这多个嵌套函* 数共享一个作用域链。
**/
var uniqueInteger = (function() {
      var counter = 0; 
      return function() { return counter++; };
}());
uniqueInteger(); // 0 
uniqueInteger(); // 1


/**
*  每次调用counter()都会创建一个新的作用域和一个新的私有变量。因此,如果
* 调用counter()两次,则会得到两个计数器对象,而且彼此包含不同的私有变量。
**/
function counter() {
      var n = 0;
      return {
              count: function() { return n++; },
              reset: function() { n = 0; }
      };
}
var c = counter(), d = counter(); //创建两个计数器
c.count();  // => 0;
d.count(); // => 0 它们互不干扰
c.reset(); // reset() 和 count() 方法共享状态
c.count(); // => 0 因为我们重置了c
d.count(); // => 1 没有重置d
  • 书写闭包需要注意: this是JavaScript的关键字而不是变量,每个函数调用都包含一个this值,如果闭包在外部函数里是无法访问this的。除非外部函数将this转存为一个变量:
var self = this;

5. 原型和原型链

  • 每个函数都包含一个prototype属性,这个属性是指向一个对象的引用。这个对象称做“原型对象”。每个函数都包含不同的原型对象。在JavaScript中,类的所有实例对象都从同一个原型对象上继承属性。因此,原型对象是类的核心。

  • 推荐阅读 JavaScript深入之从原型到原型链
    下面摘一段代码和两个图(侵删)

5.1 原型
function Person() {

}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

console.log(person.__proto__ === Person.prototype); // true
console.log(Person === Person.prototype.constructor); // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
JavaScript变量声明、变量作用域、作用域链、函数、闭包、原型_第1张图片
image.png
5.2 原型链
JavaScript变量声明、变量作用域、作用域链、函数、闭包、原型_第2张图片
image.png

你可能感兴趣的:(JavaScript变量声明、变量作用域、作用域链、函数、闭包、原型)