这篇主要是记录下对一篇外文的阅读收获,原文链接。
函数调用模式其实以前也比较清楚,但是对于this这个东西总是吃不透,阅读完这篇文章后才豁然开朗。
首先将函数的几种调用模式以及this的定义列出来,以便结合理解每种调用模式和this之间的关系。
函数调用模式有以下几种:1)方法调用 2)函数调用 3)构造器调用 4)apply/call调用
this的定义:this在js中是一个依赖于使用它的执行环境被解析的关键字,this的值是建立在当前函数被调用的上下文基础上的,取决于在哪里、怎么样调用函数。
1)方法调用
var obj = { value: 0, increment: function() { this.value+=1; } }; obj.increment(); //Method invocation
上面的代码中obj.increment();就是方法调用模式。此时increment函数的调用方式是"对象名.方法名",也就是在调用increment函数时显示的指向了它所属的对象,因此increment函数中的this就指向了obj对象。
2)函数调用
var value;
function increment(){ this.value++; } increment();
这是简单的函数调用,先定义一个函数,在通过圆括号调用。此时因为没有明确指定调用函数时的对象,因此increment函数中的this是指向了全局对象window。令人费解的是,这种情况在子函数中尽然也成立,请看下面代码:
var value = 500; //Global variable var obj = { value: 0, increment: function() { this.value++; var innerFunction = function() { alert(this.value); } innerFunction(); //Function invocation pattern } } obj.increment(); //Method invocation pattern
大家看看,obj.increment();调用完后,alert的值应该是多少呢?500,没错就是500。为什么???
因为innerFunction()是函数调用模式,它里面的this指向的是全局对象window,因此this.value输出的应该是500。
如何让它输出obj对象中的value值呢?修改成下面这样:
var value = 500; //Global variable var obj = { value: 0, increment: function() { var that = this; that.value++; var innerFunction = function() { alert(that.value); } innerFunction(); //Function invocation pattern } } obj.increment();
使用that缓存this对象,innerFunction中调用alert(that.value); 这样输出的结果就是1了。这样之所以可以输出1,是应为js中函数都是闭包(this works because functions in JavaScript are closures)。这句话很费解,解释下:广义上讲,js中的所有子函数都是闭包。我们一般的函数是定义在全局对象中的,因此一般定义的函数其实是全局对象的子函数,因此也是个闭包。这可以解释原文中的那句话,但是最好不要这样理解闭包。
闭包的定义:当你在内嵌函数中使用外部函数作用域内的变量时,就是使用了闭包。用一个常用的类比来解释闭包和类(Class)的关系:类是带函数的数据,闭包是带数据的函数。
根据上面的对闭包的定义,第二段代码中子函数innerFunction调用了父函数的变量that.value,因此形成了闭包。通过函数调用模式调用innerFunction时,它会在定义它的上下文环境中找that.value这个变量,首先找到的就是obj对象中的value值,因此输出是1.
3)构造器调用
这种调用比较好理解,和面向对象语言中的调用方式基本相同。
function obj(){ this.arr=[1,2]; } obj.prototype={ push:function(a){ this.arr.push(a); }, length:function(){ alert(this.arr.length); } }
var o1=new obj();
o1.push(2);
需要注意一点是,之所以将arr放在函数体(构造器)obj中定义,是因为构造器中定义的对象是属于每个实例的,而原型prototype中定义的对象或者方法是所有实例共享的。
4)apply/call调用
这种调用方式也比较简单,两种方法只是传参不一样,其他都一样。作用都是显示的改变调用函数运行的上下文环境。
var add = function(num1, num2) { return num1+num2; } array = [3,4]; add.apply(null,array); //7
也是需要注意一点,apply第一个参数传递为null,并不是说将add函数的运行上下文环境设置为null,而是设置为全局对象window。