Function
函数名就是函数体的引用。可以将函数名作为参数传递给另一个函数,也可以在一个函数内部返回另一个函数,以便在函数体外调用。例如Array.prototype.sort接收一个函数作为参数,该参数规定了比较的规则,如果我们想对数组中的对象按照某个属性来进行排序,可以这样做:
function compareByProperty(property) {
return function(o1, o2) {
var v1 = o1[property];
var v2 = o2[property];
if (v1 < v2) {
return -1;
} else if (v1 > v2) {
return 1;
} else {
return 0;
}
}
}
var data = [{name: 'lily', age: 10}, {name: 'anna', age: 20}];
data.sort(compareByProperty('name'));
console.log(data);
1.this
this指向函数据以执行的环境对象。即上下文。是基于函数的执行环境绑定的。
通俗来说,就是调用这个函数的对象。
当函数在被调用的时候(进入执行的上下文时),会生成一个执行环境(excution context),内部有一个属性[scope]指向作用域链,作用域链中的每一项指向一个变量对象(variable object),变量对象中维护了当前函数自身的变量,并且此时活动对象会自动取得this和arguments的值。搜索这两个值只会搜索自身的变量对象,不会沿着作用域链进行搜索。
必须是在一个函数内部定义了一个函数,内部函数才能访问外部函数的变量对象,如果函数是在外部定义但被另一个函数调用了,是不能访问外部函数的变量对象的。
也就是说作用域链的生成和函数定义的位置有关,而this与arguments的值是与函数被调用的环境有关。
‘以下部分参考《JS权威指南》’:
函数的调用形式有:
- 函数
- 方法
- 构造函数
- 通过call和apply间接调用
通常作为函数来调用的时候不会使用this,但可以通过this来判断是否是严格模式。
this是关键字,不是变量,不能赋值。嵌套的函数不会从外部函数中继承this值,如果嵌套函数作为函数调用,this值为window或undefined(严格模式)。
function outter() {
console.log('I am outter function!');
function inner() {
console.log('I am inner function');
console.log( this === window ); //非严格模式true
}
inner();
}
因此不要觉得嵌套函数的this指向调用外层函数的上下文或者是外层函数,要取得外层函数的this,需要将this保存到一个变量中。
2.arguments.callee指向当前执行的函数;
arguments.callee.caller指向调用当前函数的函数,如果当前函数处于全局作用域中,则caller为null.
3.call和apply
call和apply可以扩充作用域,使得对象和方法不需要有耦合关系。
4.闭包
作用域链和变量对象
函数在进入执行环境(开始调用还未执行到具体语句)时候,会产生一个excution context其中有一个属性[scope]指向了作用域链,而这个作用域链中的每一项又指向了可访问到的变量对象。
变量对象保存了当前执行环境中可访问到的变量,当函数被调用时有arguments、形参、通过function声明的局部函数是具有具体的值的,而其他通过var声明的变量一开始是undefined,直到执行到当前的语句才被赋予具体的值。闭包
闭包就是可以访问到外部函数中变量的函数,即在闭包的作用域链中持有对外部函数变量对象的引用。当外部函数执行完毕后,其作用域链被销毁,如果返回的闭包还存在,则仍然持有对外部函数的变量对象的引用。因此使用闭包是很消耗内存的。
此外,闭包只能取得包含函数中任何变量的最后一个值。在闭包中引用某个变量时,会沿着作用域链在变量对象中进行搜索。
例如:
function createFun() {
var result = [];
for (var i = 0; i < 10; i++) {
result[i] = function() {
console.log(i);
};
}
return result;
}
var res = createFun();
res[0](); //10
result数组中存了10个函数并返回了,可以在外部使用。当这些内部函数调用的时候,执行到console.log(i)时会从变量对象中搜寻该标识符,在自己的变量对象中没有找到,就会沿着作用域链向上查找外部函数的变量对象,现在,在外部函数的变量对象中是这样的内容:
VO:{
result: [//10个匿名函数的引用],
i: 10
}
此时i已经为10了。
可以在外部再包裹一层,来保存每一次的i值:
function createFun() {
var result = [];
for (var i = 0; i < 10; i++) {
result[i] = function(num) {
return function() {
console.log(num);
};
}(i);
}
return result;
}
var res = createFun();
res[1](); //1
当调用某个闭包函数resindex时,需要访问num变量,此时会沿着作用域链进行搜索,闭包的作用域链是这样的:
VO res[index]: {
//nothing
}
VO 包裹res[index]的匿名函数: {
arguments: num,
num: i //是指当前的i,每次循环当前的i都被保存下来
}
VO createFun: {
result: [//10个匿名函数的引用],
i: 10
}
因此当res[index]沿着作用域链搜索时,会在包裹它的匿名函数的变量对象中找到num,而这个num的值是对应的i的值。
闭包中的this:
每个函数被调用时,其活动对象自动获得this和arguments,在搜索时只搜索自己的活动对象,所以闭包不能获得外部函数的这两个值。而闭包通常是在全局环境中调用,因此闭包内部的this通常是指window。
5.块级作用域
JS中对于重复声明的变量会视而不见。
立即执行的匿名函数可以模拟块级作用域,这样可以避免向全局环境中增加过多的变量。
6.私有变量
在函数内部定义局部变量和局部函数,然后返回一个闭包,在这个闭包中对局部变量和局部函数进行访问。闭包是外部访问局部变量/函数的唯一入口,该闭包称作特权函数。特权函数有两种形式:
- 作为构造函数的方法:
function Func() {
var privateVar = ...;
function privateFunc() {}
this.publicMethod = function() {
privateVar++;
return privateFunc();
};
}
该方式的缺点是,每个实例都要创建一个自己的方法。
- 静态私有变量
(function() {
var privateVar = ...;
function privateFunc() {}
MyObject = function() {}; //构造函数
MyObject.prototype.publicMethod = function() {//原型增强复用
privateVar++;
return privateFunc();
};
})();
该方式需要一个全局的构造函数(此处只能用函数表达式),并在原型中定义特权方法,该特权方法是一个闭包,能够取得外部函数的私有变量和函数。该方式MyObject的所有实例共享私有变量和私有函数。
注意此处的MyObject虽然是一个全局变量,但该构造函数的声明仍然是在匿名函数内部进行的,因此,在该构造函数内部也是可以访问到外部匿名函数的私有变量的。注意闭包的定义是:
在一个函数内部定义了另一个函数,就创建了闭包
- 模块模式
指为单例增加私有变量和特权方法:
var singleton = function() {
var privateVar = ...;
function privateFunc() {}
return {
publicProperty: ...,
publicMethod: function() {
privateVar++;
return privateFunc();
}
};
}();
这里的单例都是Object的实例。
- 增强的模块模式
适用于单例必须是某个类的实例:
var singleton = function() {
var privateVar = ...;
function privateFunc() {}
var obj = new MyObject(); //特定的类型实例
obj.publicProperty = ...;
obj.publicMethod = function() {
privateVar++;
return privateFunc();
};
return obj;
}();