笔记之javascript--07--Function

1、 Function类型是js中引用类型之一,每个函数实际上都是Function类型的实例对象,具有自己的属性和方法。正因为函数式对象,所以函数名实际上也是一个指向函数对象的指针。

2. 常用的函数定义方式

1. 函数声明:

?
1
2
3
function sum(a , b ){
return a+b;
}

2. 表达式:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var sum = functiot (){
return a+b;
}; //注意分号
  //两种方式的区别:
  //解释器会率先读取函数声明,并使其在执行之前可以访问,
而使用表达式则必须等到解析器执行到它所在的代码行,才会真正被解释执行
(变量声明提前,而值留在原地).
alert (sum (10 ,10));
function sum(a ,b){
   return a+b;
}
//↑上面的代码会正常执行,因为在代码执行前,解析器通过函数声明提升,读取并将函数声明添加到执行环境中,放到代码树的顶部
alert ( typeof sum);
alert(sum(10 , 10));
var sum = function (a ,b){
   return a+b;
}
//↑报错,原因在于函数位于一个初始化语句中,不是一个函数声明,不会被提前,而只会把var sum提前,用typeof操作符显示sum是undefined,所以报错

3. new 构造函数,虽然这种用法也是函数表达式,但该用法不推荐。因为这种语法会导致解析两次代码(第一次是解析常规的ECMAScript代码,第二次是解析传入构造函数中的字符串),影响性能。
使用 Function 构造函数,构造函数可以接受任意数量的参数,但最后一个参数始终都被看成是函数体,前面的参数则枚举出了新函数的参数。

var sum=new Function('num1','num2','return num1+num2;');
sum;// 
function anonymous(num1,num2
/**/) {
return num1+num2;
}

当使用不带圆括号的函数名是访问函数指针,而非调用函数。

3. 函数名仅仅保存指向函数对象的指针,因此函数名与包含对象指针的其他变量没什么不同,也就是说,一个函数对象可以有多个名字:

?
1
2
3
4
5
6
7
8
function sum(a , b ){
return a+b;
}
console.log(sum(2 ,3)); //5
var anotherSum = sum; //变量anotherSum也指向了同一个函数对象
console.log(anotherSum(4 , 5)); //9
sum = null ; //sum变量不再保存函数对象的指针了
console.log(anotherSum(1 , 3)); //anotherSum这个变量仍能调用 4. JS为何没有重载这个概念。
?

4. JS为何没有重载这个概念。

?
1
2
3
4
5
6
7
8
function add(a){
return a+3 ;
}
function add(a){
return a+5;
}
var result = add(3); //8
//两个函数同名了,结果只能是后一个函数覆盖前一个,所以不能重载

不能实现重载的原因:

  1. ECMAScript函数没有签名,因为其参数是由包含零个或多个值的数组来表示的。没有函数签名,真正的重载是不可能做到的。在ECMAScript中定义两个名字相同的的函数,则该名字只属于后定义的函数。如何实现类似于Java中的重载呢,其实可以通过判断传入函数的参数类型和个数来做出不同响应。
    复制代码
    function reload(){
       if(arguments.length==0){
           console.log('没传参');
       }else if(arguments.legth==1){
          console.log('传了一个参数');
      }
    }
    复制代码
  2. 深入理解:将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念。
    复制代码
    function add(){
      return 100;
    }
    function add(num){
     return num+200; 
    }
    
    //实际上和下面代码没什么区别
    function add(){
      return 100;
    }
    add=function(num){
     return num+200; 
    }
    复制代码

 

5. 函数的内部属性:函数内部,有两个特殊的对象,arguments和this

1. arguments:

   arguments是个类数组对象,包含着传入函数的所有参数,这个对象有一个叫callee的属性,属性值是一个指针,指向拥有这个arguments对象的函数本身

?
1
2
3
4
5
6
7
8
9
10
11
12
13
function foo (){
var a =arguments.callee;
return a.toString();
}
foo();
/*
返回结果:
  "function sum(){
  var a =arguments.callee;
  return a.toString();
  }"
也就是说,一个函数内部,arguments.callee指的就是这个函数本身。这个函数在递归调用时有点用,有许多缺陷,在ES5严格模式被移除
*/

arguments :类数组对象,包含传入函数中所有参数。是每个函数自身的属性,之所以可以直接访问 arguments ,是因为命名空间??以下变化是为了加强JavaScript语言的安全性,这样第三方代码就不能在相同的环境下窥视其他代码了。

  •  callee 属性:是一个指针,指向拥有 arguments 对象的函数。严格模式访问会导致错误。
    复制代码
    //一般阶乘函数
    function factorial(num){
       if(num<=1){ return 1;}
       else {
         return num*factorial(num-1);
      }
    }
    复制代码

    定义阶乘函数用到递归算法,这样定义是没问题。
    缺点:这个函数的执行与函数名 factorial 紧紧耦合在一起。万一出现改变函数指向的这种情况就不太好了,

    factorial=function(){}
    factorial(3);// undefiend

    为了消除这种现象。

  • function factorial(num){   
       if(num<=1){     return 1;    }
       else{     
          return num*arguments.callee(num-1);    
      } 
    }

    这样无论引用函数使用的是什么名字都可以保证完成递归。  

2. this:简单来说,this指的就是函数执行的环境对象,在哪个对象中执行,this就指哪个对象。

3. caller :不止是ECMAScript5中新增函数对象上的属性,还是 arguments 上的属性。保存着调用当前函数的函数的引用。如果是在全局作用域中调用当前函数,它的值为 null 。

 Object.getOwnPropertyNames(Function);// ["length", "name", "arguments", "caller", "prototype"] 

复制代码
function outer(){
  inner();
}
function inner(){
  console.log(inner.caller); //为了实现更松散的耦合,arguments.callee.caller
}

outer();// function outer(){ inner()}
复制代码

严格模式下不能为函数的 caller 属性赋值,否则会导致出错。

 

4. length属性:表示函数希望接受的参数个数

?
1
2
3
4
function add(a ,b ,c){
return a+b+c;
}
add.length; //3

5. 著名的prototype属性,简单来说,是一个对象,是通过调用构造函数而创建的一个对象,包含可以由特定类型的所有实例共享的属性和方法。 

6.作为值的函数

因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

复制代码
function callSomeFunction(someFunction,someArgument){
  return someFunction(someArgument);
}

function concated(str){
  return "Hi "+str;
}

callSomeFunction(concated,'xx');// 'Hi xx' 
复制代码

从一个函数中返回另一个函数的应用:假设有一个对象数组,想要根据某个对象属性对数组进行排序,但传给 sort() 方法的比较函数要接收两个参数,即要比较的。我们需要一种方式来指明按照哪个属性来排序。我们可以定义一个函数它接收一个属性名,然后根据这个属性名来创建一个比较函数。默认情况下, sort 函数会调用每个对象的 toString() 方法以确定它们的次序。

复制代码
function createCompare(property){
  return function(obj1,obj2){
    var value1=obj1[property],
        value2=obj2[property];   
    if(value1<value2) return -1;
    else if(value1>value2)  return 1;
    else return 0;
  }
}
复制代码
var data=[{name:'aa',age:20},{name:'bb',age:12},{name:'cc',age:30}];
data.sort(createCompare("age"));// [{name:'bb',age:12},{name:'aa',age:20},{name:'bb',age:30}]

 

7.函数的属性和方法

  • length:表示函数希望接收的命名参数的个数(也就是定义的形参的个数)。
    复制代码
    function sayName(name){
      //
    }
    function sum(num1,num2){
      //
    }
    function sayHi(){
     // 
    }
    
    sayName.length;// 1
    sum.length;// 2
    sayHi.length;// 0
    复制代码
  • prototype:对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。诸如toStringvalueOf等方法实际上都保存在Object.prototype名下(原生构造函数比如Function,Array等 在自己原型上重写了toString)。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。 Object.getOwnPropertyDescriptor(Function,'prototype');// Object {writable: false, enumerable: false, configurable: false}
  • 每个函数上有两个可用的方法:applycall。这两个方法实际上是在Function.prototype上, Object.getOwnPropertyNames(Function.prototype);// ["length", "name", "arguments", "caller", "apply", "bind", "call", "toString", "constructor"] 它是在JavaScript引擎内部实现的。因为是属于Function.prototype,所以每个Function的实例都可以用(自定义的函数也是Function的实例)。都是在特定的作用域或自定义的上下文中调用执行函数,实际上等于设置函数体内 this 对象的值。
  1.  apply :参数一为在其中运行函数的作用域,参数二为参数数组(可以是数组,也可以是 arguments 对象)。
    复制代码
    function sum(num1,num2){
      return num1+num2;
    }
    
    function callSum1(num1,num2){
      return sum.apply(this,arguments);//sum.apply(this,[num1,num2])
    }
    
    callSum1(10,30);// 40
    复制代码

     严格模式下,未指定环境对象而调用函数, this 值不会转型为 window 。除非明确把函数添加到某个对象或者调用 apply 或 call ,否则 this 值将是 undefined 

  2.  call :参数一没有变化,变化的是其余参数都是直接传递给函数,参数必须都列出来。
    function callSum1(num1,num2){
      retrun sum.call(this,num1,num2);
    }
    
    callSum1(10,30);// 40

     call 和 apply 真正强大的地方是能够扩充函数赖以运行的作用域,改变函数的执行环境。

  3. 传递参数并调用函数并非call()和apply()的用武之地,二者真正强大的地方是扩充函数运行的作用域

  4. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var color = 'red' ;
    var obj = {
    color : 'blue'
    }
    function foo(){
    console.log( this .color);
    }
    foo(); //'red'
    foo.call( this ); //'red'
    foo.call(obj); //'blue'
    //最后一次调用foo()函数的执行环境变了,其中的this指向了obj对象,
    所以是'blue'

       使用call()和apply()扩充作用域的最大好处,就是使对象与方法之间解耦


  5.  bind :ECMAScript5定义的方法,也是 Function.prototype 上的方法。用于控制函数的执行上下文,返回一个新函数,这个函数的 this 值会被绑定到传给 bind() 函数中的值。
    复制代码
    window.color="red";
    var o={color:'blue'};
    function sayColor(){
      console.log(this.color);
    }
    
    var newobj=sayColor.bind(o);
    newobj;// function sayColor(){
      console.log(this.color);
    }
    newobj==sayColor;// false
    newobj();// blue
    复制代码

     深入理解:可以将函数绑定到指定环境的函数。接收一个函数和一个环境,返回在给定环境中调用给定函数的函数。

    function bind(func,context){
      return function(){
        func.apply(context,arguments);//这里创建了一个闭包,arguments使用的返回的函数的,而不是bind的
      }
    }

    当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。

    function bind(func,context,args){
       return function(){
          func.call(context,args);
       };
    } 
  1. 4. ES5定义了一个新方法:bind(),返回一个函数,这个函数中this值会被绑定到传给bind()函数的值

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var x = 9;
    var module = {
    x: 81,
    getX: function () { return this .x; }
    };
    module.getX(); // 81
    var retrieveX = module.getX;
    retrieveX(); // 9, 因为在这种情况下, "this" 指向全局变量
    var boundGetX = retrieveX.bind(module); //把retrieveX()函数中的this永远与module绑定,再调用这个函数永远都是在module对象中运行
    boundGetX(); // 81

  2.  toString,toLocaleString :返回函数代码的字符串形式,返回格式因浏览器而异,有的返回源码,有的返回函数代码的内部表示,由于存在差异,用这个也实现不了什么功能。
  3.  valueOf :返回函数的自身引用。