当一个函数不作为其它对象的属性时,默认this引用window
- var name = "abc";
- // js会有一个预解析的过程,先执行函数里的变量声明,但不执行赋值
- function testGlobal() {
- // 所以这里查找变量的时候,搜索到函数里的name
- // 此时有name的声明,就是下面那个,但是并没有执行赋值
- // 所以是undefined
- alert(name); //2处:undefined
- var name = "def";
- // 这里调用这个函数的时候,上面的赋值语句已经执行
- // 所以是def
- alert(name); //3处:def
- // this - 谁调用指向谁
- // js里方法和对象是松散的耦合
- // 多数情况下函数可以随意得绑定到别的对象上去运行
- alert(this.name); //4处:abc
- this. name = "ghi";
- alert(this.name); //5处:ghi
- }
- // 搜索变量找到全局作用域的 name
- alert(name); //1处:abc
- // window直接调用这个方法
- testGlobal();
- // 函数里this. name = "ghi"; 经过上面的函数执行实际是window.name='ghi';
- alert(name); //6处:ghi
ECMAScript 认可两类对象:原生(Native)对象和宿主(Host)对象,其中宿主对象包含一个被称为内置对象的原生对象的子类(ECMA 262 3rd Ed Section 4.3)。原生对象属于语言,而宿主对象由环境提供,比如说可能是文档对象、DOM 等类似的对象。
原生对象具有松散和动态的命名属性(对于某些实现的内置对象子类别而言,动态性是受限的--但这不是太大的问题)。对象的命名属性用于保存值,该值可以是指向另一个对象(Objects)的引用(在这个意义上说,函数也是对象),也可以是一些基本的数据类型,比如:String、Number、Boolean、Null 或 Undefined。其中比较特殊的是 Undefined 类型,因为可以给对象的属性指定一个 Undefined 类型的值,而不会删除对象的相应属性。而且,该属性只是保存着 undefined 值。
由于所有对象都有原型,而原型本身也是对象,所以原型也可能有原型,这样就构成了所谓的原型链。原型链终止于链中原型为 null 的对象。Object
构造函数的默认原型就有一个 null 原型,因此:
var objectRef = new Object(); //创建一个普通的 JavaScript 对象。
创建了一个原型为 Object.prototype
的对象,而该原型自身则拥有一个值为 null 的原型。也就是说, objectRef
的原型链中只包含一个对象-- Object.prototype
。
所有 JavaScript 代码都是在一个执行环境中被执行的。全局代码(作为内置的JS 文件执行的代码,或者 HTML
页面加载的代码)是在我称之为“全局执行环境”的执行环境中执行的,而对函数的每次调用(
有可能是作为构造函数)同样有关联的执行环境。通过 eval
函数执行的代码也有截然不同的执行环境
当调用一个 JavaScript 函数时,该函数就会进入相应的执行环境。如果又调用了另外一个函数(或者递归地调用同一个函数),则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。
在创建执行环境的过程中,会按照定义的先后顺序完成一系列操作。首先,在一个函数的执行环境中,会创建一个“活动”对象。活动对象是规范中规定的另外一种机制。之所以称之为对象,是因为它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过 JavaScript 代码直接引用活动对象。
为函数调用创建执行环境的下一步是创建一个 arguments
对象,这是一个类似数组的对象,它以整数索引的数组成员一一对应地保存着调用函数时所传递的参数。这个对象也有 length
和 callee
属性(这两个属性与我们讨论的内容无关,详见规范)。然后,会为活动对象创建一个名为“arguments”的属性,该属性引用前面创建的 arguments
对象。
接着,为执行环境分配作用域。作用域由对象列表(链)组成。每个函数对象都有一个内部的 [[scope]]
属性(该属性我们稍后会详细介绍),这个属性也由对象列表(链)组成。指定给一个函数调用执行环境的作用域,由该函数对象的 [[scope]]
属性所引用的对象列表(链)组成,同时,活动对象被添加到该对象列表的顶部(链的前端)。
最后,要为使用 this
关键字而赋值。如果所赋的值引用一个对象,那么前缀以 this
关键字的属性访问器就是引用该对象的属性。如果所赋(内部)值是 null,那么 this
关键字则引用全局对象。
创建全局执行环境的过程会稍有不同,因为它没有参数,所以不需要通过定义的活动对象来引用这些参数。但全局执行环境也需要一个作用域,而它的作用域链实际上只由一个对象--全局对象--组成。全局执行环境也会有变量实例化的过程,它的内部函数就是涉及大部分 JavaScript 代码的、常规的顶级函数声明。而且,在变量实例化过程中全局对象就是可变对象,这就是为什么全局性声明的函数是全局对象属性的原因。全局性声明的变量同样如此。
全局执行环境也会使用 this
对象来引用全局对象。
在 ECMAScript 中,函数也是对象。函数对象在变量实例化过程中会根据函数声明来创建,或者是在计算函数表达式或调用 Function
构造函数时创建。
通过函数声明或函数表达式创建的函数对象,其内部的 [[scope]]
属性引用的则是创建它们的执行环境的作用域链。