函数是javascript中最重要的内容,也是其相对其余语言来讲在设计上比较有意思的地方。javascript许多高级特性也或多或少和函数相关。本文将以函数为中心,对函数的各个关键知识点作简要介绍。javascript
理解函数是对象,是准确理解函数的第一步。下面的代码就建立了一个函数对象。java
var sum = new Function("num1", "num2", "return num1 + num2;");
每一个函数都是Function类型的实例。Function构造函数能够接受多个参数,最后一个参数是函数体,其余参数均为函数的形参。因为其书写的不优雅和两次解析致使的性能问题,这种方式不常常被采用,可是这种写法对于理解函数就是对象是很是有帮助的。通常地,咱们都用字面的方式来建立函数。数组
var sum = function(num1, num2){ return num1 + num2; } //或者 function sum(num1, num2){ return num1 + num2; }
以上两种定义函数的方法分别叫作函数表达式和函数声明,二者的效果是等价的,区别在于解析器向执行环境加载数据时对二者的处理不同。解析器会率先读取函数声明来建立函数对象,保证其在任何代码执行以前可用;对于函数表达式,则必须等到解析器执行到对应的代码行,函数对象才被建立。浏览器
在javascript中,函数对象和其余对象同样,均被视为一等公民。因此函数能够被引用、能够做为参数被传递或做为返回值返回,这使得函数的使用很是的灵活。闭包
函数对象表明了一个过程,和大多数语言同样经过函数调用表达式能够调用这个过程。可是javascript的函数对象还提供了另外两种调用方式,call和apply方法。call和apply方法的第一个参数用于指定执行环境中this的绑定,后面的参数用于指定函数的实际参数。call和apply的惟一区别是实参的形式不同,call是用逗号分割,apply则是以数组传递。例如:app
//函数调用表达式 sum(1, 2); //call方法 sum.call(this, 1, 2); //apply方法 sum.apply(this, [1, 2]);
无论用哪一种调用方式,最终都是经过函数对象的[[Call]]方法实际调用这个过程。[[Call]]方法是javascript引擎内部使用的一个方法,程序不能直接访问它。[[Call]]方法接受两个参数,第一个参数指定this的绑定值,第二个参数指定函数的参数列表。为了表达方便,后面咱们将[[Call]]方法的第一个参数称做thisArg。函数对象的call方法和apply方法能够显示指定thisArg,函数表达式则是隐式指定这个参数的。例如:函数
var foo = function(){ console.log(this); }; var obj = {name:'object'}; foo(); obj.foo = foo; obj.foo();
代码在浏览器的执行结果以下:性能
Window {top: Window, window: Window, location: Location...} Object {name: "object", foo: function}
从执行结果能够看出,obj.foo()这种调用方法,隐式将调用它的对象obj做为了thisArg。可是为何foo()这种调用方式this的绑定值是window这个全局对象?难道foo()这种调用方式将全局对象默认指定为thisArg?其实不是这样的。thisArg并非和this关键字的绑定一一对应的,其中有一个转换过程。以下:
1.若是thisArg为undefined或者null,则this的绑定为全局对象。
2.若是thisArg不是Object类型,则将thisArg强制转型为Object类型并绑定到this。
3.不然this的绑定就为thisArg。
其实foo()这种调用方式thisArg的值为undefined,经过以上的转换过程将this绑定为全局对象。this
前面提到过执行环境(Execution Context)这个概念,简单来讲执行环境就是函数在执行时所依赖的一个数据环境,它决定了函数的行为。程序执行流每次进入函数代码时都会建立一个新的执行环境。活动的执行环境在逻辑上造成了一个栈的结构。当函数执行完毕,其执行环境从栈中弹出并销毁。prototype
每一个执行环境都包含一个重要的组件:词法环境(Lexical Environment)。词法环境定义了javascript程序标识符到变量或函数的关联关系。词法环境包含了环境记录(Environment Record)和一个到外层词法环境的引用(若是有的话,不然为null)。环境记录记录了当前做用域下的变量或函数的绑定状况。有两种类型的环境记录,声明式环境记录(Declarative Environment Records)和对象环境记录(Object Environment Records)。声明式环境记录包含了当前做用域下标识符到变量声明和函数声明的绑定。对象环境记录是一个和特定对象绑定的环境记录,用于临时改变标识符的解析状况,好比在with子句中。
函数对象都有一个[[Scope]]属性,函数对象在建立时会将当前执行环境的词法环境的值赋予给[[Scope]]属性。这个属性是引擎的内部属性,程序没法访问到它。当程序流进入到函数时,javascript引擎会建立新的执行环境,同时也建立对应的词法环境。引擎会将当前做用域声明的变量和函数绑定到词法环境,同时将[[Scope]]属性的引用也添加到词法环境。程序在进行标识符解析的时候,会优先从当前的词法环境中搜索,搜索失败则向外层词法环境搜索,若是到最外层的全局环境还没搜索到则会抛出异常。
嵌套定义的函数会造成javascript中一个有趣的特性:闭包。闭包的造成是因为内层函数引用了外层函数在建立它时的词法环境。即便外层函数已经返回,执行环境已经销毁,可是内层函数依然可以经过词法环境的引用访问外层函数中定义的变量或函数。
with子句和catch子句都能临时改变当前的词法环境。他们的方式是有些区别的。先看with子句。
function foo(){ var background = '#ccc'; with(document){ body.style.background = background; } }
当执行流进入foo时,这时会建立一个声明式词法环境。执行流进入with子句的时候,引擎会建立一个对象环境记录。此时with子句中的标识符解析都会先从document这个对象中查找。当with子句执行完以后,对象环境记录销毁。
try{ //do something }catch(e){ //handel error }
catch子句也能临时改变当前的词法环境。和with子句不同的是,它会建立一个声明式词法环境,将catch子句中的参数绑定到这个词法环境。
函数对象还有个很是重要的内部方法[[Construct]],当咱们将new操做符应用到函数对象时就调用了[[Construct]]方法。此时的函数充当构造器的角色。下面的代码就经过[[Construct]]建立了一个对象。
var Dog = function(){ } var dog = new Dog();
[[Construct]]方法的执行过程以下。
1.建立一个空对象obj。
2.设置obj的内部属性[[Class]]为Object。
3.设置obj的内部属性[[Extensible]]为true。
4.设置obj的[[Prototype]]属性:若是函数对象prototype的值为对象则直接赋给obj,不然赋予Object的prototype值。
5.调用函数对象的[[Call]]方法并将结果赋给result。
6.若是result为对象则返回result,不然返回obj。
每一个javascript对象都有一个[[Prototype]]的内部属性,[[Prototype]]的值为一个对象,叫作原型对象。当程序在访问javascript对象的某个属性时,首先会在当前对象中搜索,搜索失败则到原型链中搜索,直到搜索到相应值,不然就为undefined。javascript的这种特性叫作原型继承。[[Construct]]方法的第四步是实现原型继承的关键,它指定了javascript对象的[[Prototype]]属性。
var Dog = function(){ } var animal = {}; Dog.prototype = animal; var dog = new Dog();
上面代码建立出来的dog对象的原型就为animal,它“继承”了animal对象的属性。原型继承是另一种面向对象的模型,相对于“类”的继承模型来讲,原型继承更加符合咱们的现实世界的模型。原型继承在javascript也是有很是广的用途。
函数这条线将javascript许多核心内容串起来了,我的以为这也是javascript最有意思的地方。本文主要是根据Ecma-262第五版规范中相关内容进行的总结和整理,因为能力有限,若有理解上的错误,望批评指出。