定义方式
1.函数声明
函数声明一个重要特征是函数声明提升,即在执行代码前,会先读取函数声明
sayHi()
function sayHi(){
}
2.函数表达式
sayHi(); // 报错,function(){}, 没有name,也叫匿名函数
var sayHi = function(){
}
递归
递归函数是一个函数通过名字调用自身的情况下构成,如下
function factorial(num){
if(num<=1){
return 1
}else{
return num*factorial(num-1)
}
}
console.log(factorial(4)); // 正常
var another = factorial;
factorial=null;
another(4); // factorial is not a function
- 解决方案1 使用 arguments.callee
这个属性指向arguments指向的函数,但是在严格模式下,arguments.callee 会报错,无法调用
function factorial(num){
if(num<=1){
return 1
}else{
return num*arguments.callee(num-1)
}
}
- 解决方案2 使用命名函数表达式
var factorial = function f (num){
if(num<=1){
return 1
}else{
return num*f(num-1)
}
}
var another = factorial;
factorial=null;
console.log(another(4)); // 24
闭包
闭包指有权访问另外一个函数变量的函数,常见方式函数内创建另外一个函数
function createComparisonFuction(propertyName){
// 外部函数
return function(object1,object2){
// 内部函数
console.log(this);// window
var value1 = object1[propertyName]; // 访问外包函数的变量
var value2 = object2[propertyName];
if(value1<value2){
return -1
}else if(value1>value2){
return 1
}else{
return 0
}
}
}
上面例子中,内部函数代码都能访问外部函数中的propertyName。
不管是这个函数被返回,还是在其他地方被调用,都能访问到。
这是为什么呢。
原因
函数第一次被调用,会创建一个执行环境 (execution context)及相应作用域链,并把作用域链赋值给一个特殊的内部属性(即[[scope]])。
然后,使用 this,augements 和其他命名参数的值来初始化函数的活动对象(activation object)。
但在作用域链中,外部函数的活动对象始终处于第二位,一直到作用域链终点的全局执行环境
函数执行过程中,为读写变量的值,需要在作用域链中查找变量,例子如下
function compare (value1,value2){
if(value1<value2){
return -1
}else if(value1>value2){
return 1
}else{
return 0
}
}
console.log(compare(5,10));
以上代码定义 compare() 函数,又在全局作用域调用它。
第一次调用 compare()时,会创建一个包含 this、arguments、value1 的活动对象。
全局执行环境的变量对象(包含this,result,compare)在compare()执行环境的作用域中则处于第二位。
在另外一个函数内部定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域中。
所以createComparisonFuction()函数内部定义的匿名函数的作用域中,实际上会将包含外部函数createComparisonFuction() 的活动对象,如下代码
var compare = createComparisonFuction('name')
var result = compare({'name':'ni'},{'name':'gre'});
在匿名函数从 createComparisonFuction() 被返回后,它的作用域链被包含初始值 为createComparisonFuction() 函数的活动对象,这样匿名函数就可以访问为createComparisonFuction中的所有变量
由于闭包会携带包含它的函数作用域,因此会比其他函数占更多的内存。过多使用闭包会导致内存问题。
闭包与变量
由于作用域链这种配置引出一个作用,闭包只能取得包含函数任何变量的最后一个值。
别忘了闭包保存的是整个变量对象,而不是某个特殊变量 例子如下
function createFunctions(){
var result = new Array();
for (var i = 0; i <= 10; i++) {
result[i]=function(){
// 这里的i取的是 createFunctions 作用域下 i,所以都是i最后一个值
return i
}
}
return result;
}
console.log(createFunctions())
可以通过创建另外一个匿名函数强制让闭包的行为符合预期
function createFunctions(){
var result = new Array();
for (var i = 0; i <= 10; i++) {
result[i]=function(num){
// 这里的i取的是 匿名函数中num,为匿名函数活动对象
return function(){
return num
}
}(i)
}
return result;
}
console.log(createFunctions())
this 的问题
this 对象是运行时基于函数的执行环境绑定:
- 全局中,this为window
- 但函数作为某个对象的方法调用,this等于那个对象
匿名函数执行环境具有全局性,通常指向window
var name = 'the window';
var object = {
name:'my object',
getNameFunc:function(){
return function(){
return this.name
}
}
}
console.log(object.getNameFunc());// the window
以上代码创建个全局变量name,又创建个包含name属性的对象
当函数被调用,其活动对象都会自动获取两个特殊变量:this跟 arguments。
内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永不可能直接获取到函数外部这两个变量
特殊情况下this值会变
var name = 'the window';
var object = {
name:'my object',
getName:function(){
return this.name
}
}
console.log(object.getName());// my object
console.log((object.getName)());// my object
console.log((object.getName=object.getName)());// the window
模仿块级作用域
- es 没有块级作用域
function outputNumbers(count){
for (var i = 0; i < count; i++) {
console.log(i,'里面')
}
var i;// 重新声明,这里的声明会忽视
console.log(i,'外面');// 6
}
outputNumbers(5)
这个例子中 es从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)
匿名函数可以用来模仿块计作用域避免这个问题
function outputNumbers(count){
(function(){
for (var i = 0; i < count; i++) {
console.log(i,'里面')
}
})()
console.log(i,'外面 报错了 i is nodifined');// 因为此种i已经是里面那个匿名函数的活动对象
}
outputNumbers(5);
// var _a
// var _a =12
// console.log(_a)
私有变量
严格上说,es没有私有变量。函数内部可以私有,例子如下
function add(num1,num2){
var sum = num1 + num2;
return sum
}
这个函数内部可以访问 sum,num1,num2 这几个变量,但在外部无法访问。
但是如果这个函数内部创建一个闭包,那么闭包也通过自己的作用域链访问这些变量。利用中一点就可以创建用于访问私有变量的方法。
我们把有权访问私有变量私有函数的方法称为特权方法。 创建特权方法
- 构造函数中定义特权方法
function My(num1,num2){
// 私有变量跟方法
var sum = 0
function privateFunction(){
return false
}
// 特权放方法
this.publicMethod = function(){
sum++;
return privateFunction()
}
}
对这个变量而言除了 publicMethod 方法,其他方法无法访问里面私有属性
2.静态私有变量
通过在私有作用域中定义私有变量或函数,也可以创建特权方法。例子如下
(function (num1,num2){
// 私有变量跟方法
var sum = 0
function privateFunction(){
return false
}
MyObject = function(){
// 特权放方法
} // 这个未使用var会在全局变量中,但是也有个问题,在严格模式下回报错。不推荐使用
MyObject.prototype.publicMethod = function(){
sum++;
return privateFunction()
}
})()