前言

上帝七天创造了世界,而Brendan Eich十天创造了Javascript。上帝创造的人尚且如此不完美,何况人创造的编程语言。刚出生的JavaScript是从不同语言借来的特性拼凑起来的怪胎,就像是弗兰克斯坦用尸体拼凑起来的科学怪人一样。但这没有什么可以苛责的,10天时间真的太短了,10天时间可能都不够初学者学会一门语言,更何况设计一门语言。

但是,虽然一开始出身不好又有什么关系呢!由于JavaScript是为浏览器服务的,而浏览器对应的Web技术一直在发展,所以JavaScript也跟随着发展。随着ECMAScript 2015的发布,JavaScript基本上已经脱胎换骨,拥有了很多现代语言的特性,身上各种怪异的成分也越来越少了。

JavaScript一开始属于Netscape,后来微软的Internet Explorer加入了这场竞争。于是JavaScript被ECMA接管,进行后续的改进。ECMA是一个标准化组织,它提出JavaScript的新标准,由其他浏览器厂商去实现。ECMAScript 2015则是ECMA在2015年推出的关于JavaScript的新标准。

全局对象(Global Object)和全局作用域(Global Scope)

先不说JavaScript是否是面向对象的语言(Object-Oriented),JavaScript肯定是基于对象的语言。首先,JavaScript有一个全局的对象(Global Object),在不同的地方有不同的名字

  • 在浏览器里面,这个全局对象就是window
  • 在WebWorker里面叫WorkerGlobalScope
  • 在NodeJS里面叫做global

变量

执行JavaScript脚本的时候,这个全局对象就等同于全局作用域(Global Scope)。脚本中创建的所有变量(不是用var声明的)都属于这个全局作用域:

a = 1

function f() {
  b = 1
}

f()

// 如果是浏览器,global Object是window,如果是NodeJS,global Object就是global
global = typeof global == 'object' ? global : window

console.log(a === global.a) // 输出true
console.log(b === global.b) // 输出true
  • 在浏览器中执行上面的代码,可以验证输出都是true
  • 如果在NodeJS的执行上面的代码,结果也都是true

函数

在JavaScript里面,函数和变量一样,都是对象的属性:

function hi() {
  console.log("Hi, there!")
}

global = typeof global == 'object' ? global : window

console.log(hi === global.hi) // 输出true
global.hi() // 输出"Hi, there!"

// 函数和变量一样,都是对象的属性,所以可以赋给另外一个变量
hey = hi

hey() // 输出"Hi, there!"

通过arguments属性找到函数体

在浏览器环境中运行下面的代码:

function f() { console.log("f()"); }
typeof(f) # 输出 "function"
window.f === f # 输出true

上面的代码定义了一个函数体{ console.log("f()"); },并将其保存在全局对象window的属性f中。

那么函数体和调用这个函数体时产生的函数调用对象之间存在什么关系呢?可以浏览器环境执行下面的代码示例:

function f() {
    console.log(arguments.callee)
}

f(); # 会打印出f()的函数体

简单得说,函数调用对象会有一个名叫arguments的属性,这个属性原本是用来记录命令行参数的,但是这个属性带有一个子属性,也就是arguments.callee,指向与之对应的函数体本身。也就是说,函数调用对象,会有一个引用,指向函数体本身。

参考

(未完待续)