JS 的函数

174 阅读6分钟

定义方式

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()时,会创建一个包含 thisargumentsvalue1 的活动对象。

全局执行环境的变量对象(包含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 这几个变量,但在外部无法访问。
但是如果这个函数内部创建一个闭包,那么闭包也通过自己的作用域链访问这些变量。利用中一点就可以创建用于访问私有变量的方法。

我们把有权访问私有变量私有函数的方法称为特权方法。 创建特权方法

  1. 构造函数中定义特权方法
 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()
   }
})()