犹记得第一次写JS的时候,还是从定义一个函数开始。
function first() {
....
}
那个时候只是把函数作为一个组织代码的方式而已,过后不久,写网页应用,jquery占据了我的绝大部分时间。写的一手很溜的JQ选择器,是我自认为牛逼的开始。浮华褪尽,装逼再多,也慢慢明白需要返璞归真。因此,近来开始研习js的基本功,理解它一些不可思议的魔法。偶得心得一二。
0x00 引言
函数是JS的第一型对象,对象在JS中拥有以下特性:
可以通过字面量创建
赋值给变量,数组和其他对象的属性
作为参数传递函数
作为函数的返回值
拥有动态创建并赋值的属性
也就是说,function f(){};
其实是建立了一个f
的变量,指向一个函数实体。
0x01 属性
函数也是有属性的,这一点不要惊讶。正如上面那个例子提到的。function f(){};
,f
函数可以通过一个叫name
属性,查看函数的名字:
> function f(){};
<·function f(){}
>f.name
<·"f"
而其中还有一个特别注明的属性–prototype
。在很多JS的面向对象的教学上面都会提到这个给函数动态增加属性和方法的函数属性。那么这个属性是什么呢?
其实只要简单输出的一下,你就明白:
> f.prototype
<· object{}
这个属性代表的是一个对象,而如果你继续探究的话,就要说到另一个属性constructor
,这个属性,稍微面向对象编程基础的人,都能明白,这个属性代表的是构造器。这个属性是函数和对象都有的,表示这个函数或者对象的构造器是那一个。
如果我们查看prototype
的构造器的话,你会发现:
> f.prototype.constructor
<· function f(){}
其实就是函数f
,这样你就明白为什么,prototype
属性为什么能够给函数增加属性和方法了,应为这东西就是该函数本身作为构造器构造的一个对象。
prototype
和constructor
谈到构造器的时候,再深入说明。需要明白的是,函数也是有属性的。
函数还有一个特殊的属性arguments
,透过字面意思你也能知道,这个属性指向的是调用这个函数的参数列表。这是一个特殊的对象。它可以被当作数组遍历使用,但是他不是一个数组,所以不能使用数组的方法来操作它。
正是应为这个属性的存在,所以即使函数调用函数的参数对象是不完整的也依旧可以调用这个函数。而额外的参数,也可以通过arguments
来调用使用。
0x02 上下文
上下文(context
)是很多语言都有的一个概念。在Java中有着十分严格的规定和约束,但是JS中却是灵活多变。在函数的内部,上下文用this
表示。在Java等面向对象语言中,this
表示的是调用这个方法或者属性的对象。
例如:obj.foo = function(){return this;};
这样的方式很好理解,调用obj.foo()
方法,返回的是对象obj
。
但如果我们直接生命一个函数,没有把它赋予给任何一个对象呢?
> function f() {return this;};
> f();
<· window{...}
这样很好理解,当没有给一个函数指定上下文的时候,它的上下文是window
。当然,这是在浏览器中,其他环境中我还没有研究。但远离应该是相同的,即没有赋予上下文环境的时候,赋予的最顶级的上下文环境。
将一个函数赋予给一个对象作为它的方法,是一种改变函数上下文的方式。如果我们想要动态的改变函数的上下文呢。就比如,一段代码,我们要等运行到一个结果后,才能指定这个函数的上下文来调用。这就是JS中常用的回调机制,而这个时候就要用到的函数的两个特殊的方法call()
和apply()
。
这两个方法的作用,都是为一个函数指定上下文。不同的是,调用的参数不同。
call:
call
方法第一个参数接收的是要指定给函数的上下文,之后的参数依次赋予函数对应的参数。f.call(obj, arg1, arg2, arg3)
apply:
apply
方法第一个参数也是接收的指定给函数的上下文,第二个参数接收的是一个数组或者arguments
。将数组的元素依次匹配到函数上,或者将arguments
传递给函数调用。f.apply(obj, [arg1, arg2, arg3])
或f.apply(obj, arguments)
0x03 闭包
闭包这种特性,可以理解为作用域。当然,这和Java等语言中的作用域是不一样。JS中的“作用域”更加具有函数式语言的特性。
举个栗子:
if(flag) {
var a = 3;
} else {
var a = 4;
}
console.log(a);
这样的代码中,a
是可以被调用的。而在Java中这是会报错。这就是说明,JS中的“作用域”其实不是和Java中那样是以代码块为原子的,而是以函数为原子的。这就被称之为闭包。
闭包的原则,很通俗的一条语言可以概括你的是我的,我的还是我的。
即一个函数的中的资源,不可以被外部访问,但其内部可以访问。如果下面一个函数的嵌套:
function a() {
function b() {
function c() {
...
}
}
}
a
外面不能调用b
和c
,a
中可以调用b
,但不能调用c
。依次类推,内部可以访问外部,外部不能访问内部。