JavaScript Core -- 函数详解(作用域&&参数值传递&&this关键字&&函数声明)

    函数声明和函数表达式

    javasceipt中函数就是对象,函数的名字也只是一个指向函数对象的指针,不会与某个函数绑定,所以一个函数可以有多个名字。 

   我们声明函数时可以有两种选择:函数声明法,表达式定义法

    函数声明法:

  function sum (num1 ,num2){ 
      return num1+num2
  }
  表达式定义法:
  var sum = function(num1,num2){ 
      return num1+num2
  };/注意,一定要在结尾添加分号,因为想一想,你见过的所有表达式后面都会有一个 ; 对吧?
  他们是有区别的 :解析器会率先读取函数声明,并使其在执行任何代码之前可以访问(函数声明提升放到代码树的顶部)。至于函数表达式,则必须等到解析器执行到他所在的代码行,才会真正执行。下面的两个函数使用不同的方式定义,调用的时候就会产生不同的的效果。

 

    

   PS: JavaScript是解释型语言,但它并不是直接逐步执行的,JavaScript解析过程分为先后两个阶段,一个是预处理阶段,另外一个就是执行阶段。在预处理阶段JavaScript解释器将把JavaScript脚本代码转换到字节码,然后第二阶段JavaScript解释器借助执行环境把字节码生成机械码,并顺序执行。

console.log(a); //Error:a is not defined ,直接报错,下面语句没法执行,以下结果为注释该句后结果
console.log(b) //undefined
var b="Test";
console.log(b);//Test
    也就说JavaScript值执行第一句语句之前就已经将函数/变量声明预处理了,var b=”Test” 相当于两个语句,var b;(undefined结果的来源,在执行第一句语句之前已经解析),b=”Test”(这句是顺序执行的,在第二句之后执行)。这也是为什么我们可以在方法声明语句之前就调用方法的原因

    再次确认:函数名只是个变量,所以函数名可以作为值来使用,也可作为参数传递,然后该函数中的内容相当于直接放到了当前接受参数的函数中,通过下面这个案例进行诠释。

    案例1:改造sort函数:js中默认的sort()函数时按照每个位置上的值的编码来进行排序的,会出现 1<10<5的现象,下面按照我么的意愿来改造这个函数

  var values = [0,1,5,10,15];
  values.sort(compare);
  alert(values);//0 ,1 ,5 ,10 ,15
  function compare(value1,value2){
    return value1 - value2;//返回负数,升序;
  }
  var data = [{name:"feng",age:25},{name:"laing",age:28}];
  data.sort(compareObj("age"));//按照每个对象的age进行排序
  alert(JSON.stringify(data));//从对象中取出字符串 {name:"feng",age:25},{name:"laing",age:28}
  function compareObj(age){
    return function(obj1,obj2){
        var value1 = obj1.age;
        var value2 = obj2.age;
        return value2 - value1;//返回正数,降序{name:"laing",age:28},{name:"feng",age:25}
        }
  }

this

    this是javaScript的一个普通的关键字,代表函数运行时,自动生成的一个内部对象,只能在函数内部使用,比如

    function test(){
      this.x = 1;
    }
    同时this又是一个很特殊的关键字,他很灵活,在不同的场合下使用,this的值会发生变化,但是无外乎一个原则: this指向的永远是当前调用函数的那个对象   下面在不同的函数调用场合,为大家介绍this的使用方法

    1)纯粹的函数调用:函数最通用的方式就是全局调用,因此this就指向全局对象Global

    function  f1(){ 
        return  this; 
    } 
    alert(f1()  ===  window);  //  true, this指向调用f1()的全局变量window(浏览器中为window对象,以下我们默认在浏览器环境下执行js代码,全局对象为window
     为了证明this就是全局对象,我们再来一个小例子
    var x = 1;
  function test(){
    this.x = 0;
  }
  test();
  alert(x); //0

    这个函数第一行定义了一个全局变量x并复制为1 此时window.x = 1第五行在全局环境下调用test() ,在test()函数内部this指向window对象,且window.x = 0,所以在最后一行alert()的时候调用全局变量x的值为0

    再看下面一个例子

    var myObj = {
        value :3
    };
    var add = function(a,b){
        return a+b;
    };
    myObj.double = function(){

        var helper = function(){
            this.value = add(this.value,this.value);
            alert(this.value)
        };
        helper();//以函数的形式调用
    };
    //以方法的形式调用
    myObj.double();//

    以上代码达不到目的,因为以此模式调用时,this被绑定到了全局变量。这是语言设计上的一个错误,倘若语言设计正确,那么当内部函数被调用时,this应该绑定到外部函数的this变量。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。幸运的是,有一个很容易的解决方案:如果该方法定义了一个变量并给他赋值this,那么内部函数就可以通过那个变量访问到this. 按照约定,我们可以把那个变量命名that:

    var myObj = {
        value :3
    };
    var add = function(a,b){
        return a+b;
    };
    myObj.double = function(){
        var that = this;//解决办法

        var helper = function(){
            that.value = add(that.value,that.value);
            alert(that.value)
        };
        helper();//以函数的形式调用
    };
    //以方法的形式调用
    myObj.double();//6

    2)作为对象方法的调用:此时this指向这个上级对象

    function test(){
    console.log(this.x);//此时this指向的就是调用test()的对象o
  }
  var o = {};
  o.x = 1;
  o.m = test;
  o.m(); // 1

    3)作为构造函数调用: 如果一个函数前面带上一个new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会绑定到那个新对象上

   function test(){
    this.x = 1;
  }
  var o = new test();
  alert(o.x); // 1

    为了加深印象,我们再上一个比较复杂的例子

    function MyClass(){
        this.a = 37;
    }
    var o = new MyClass();
    alert(o.a)//37
    function C2(){
       this.a= 37;
       return{a:38};
    } 
    o = new C2(); 
    alert(o.a)// 38,这里比较特殊,因为C2中return 了一个新的对象所以this对象绑定到了返回的对象上
        一个函数总会有一个返回值,如果没有指定则返回undefined。如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)

    4)apply调用:apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this指的就是这第一个参数。apply()的参数为空时,默认调用全局对象

    var x = 0;
  function test(){
    alert(this.x);
  }
  var o={};
  o.x = 1;
  o.m = test;
  o.m.apply(); //0

apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。如果把最后一行代码修改为

    o.m.apply(o); //1
    运行结果就变成了1,证明了这时this代表的是对象o。


函数属性&&arguments

     1.理解参数&&没有重载:ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。因为ECMAScript中的参数在内部是用一个数组来表示的。函数接收到的始终是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。实际上,在函数体内可以通过arguments对象来访问这个参数数组(arguments[0]是第一个元素,以此类推),可以使用Length属性来确定到底传进来了多少个参数。

    function sayHi(firN,SecN){
        alert('hello'+firN+SecN);
    }
    //替换成
    function sayHi(){
	alert('hello'+arguments[0]+arguments[1])
    }
    所以说:命名的参数只提供便利,不是必须的。 arguments对象的长度是由传入参数个数决定的,不是由定义函数时的命名参数的个数决定的 所以,就没有函数签名,ECMAScript也就没有重载。所谓的参数是由0~多值组成的数组表示的,后定义的函数会覆盖先定义的同名函数。

    下面一个例子用来说明可以通过arguments动态的修改参数

    function  foo(x,  y,  z)  { 
        arguments.length;  //  2 
        arguments[0];  //  1 
        arguments[0]  =  10; 
        x;  //  change  to  10; 
        arguments[2]  =  100; 
        z;  //  still  undefined  !!! 
        arguments.callee  ===  foo;  //  true 
    } 
    foo(1,  2); 
    foo.length;  //  3 

    2.参数的值传递 :ECMAScript中所有函数的参数都是按值传递的。参数把函数外部的值复制给函数内部的,就和把值从一个变量复制到另一个变量以一样。

    在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象的一个元素),仅仅是具有相同的值。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。再额外举一个例子,证明对象时按值传递的

 function setName(obj){
	obj.name = "feng";
	obj = new Object();//1
	obj.name = "haha";//2
  }
  var person = new Object();
  setName(person);
  alert(person.name);//feng

    无论加不加1 2两行都输出feng,说明,即使在函数内部修改了参数的值,但原始的引用(person->obj)仍然未变。好比把a1钥匙复制成a1,a2,现在两把钥匙都能开开A门,但是把a2改成开B门的样子后,原来a1仍然可以开A门,而此时a2与a1无关了。


 
 

执行环境&&作用域

    每一个执行环境(以下简称,环境)都有一个与之相关联的变量对象(Variable  Object,  缩写为VO),环境中定义的所有变量和函数都保存在这个对象中,它是一个抽象概念中的“对象”,它用于存储执行上下文中的: 变量,函数声明,函数参数。

    某个环境中所有代码执行完毕后该环境被销毁(windows对象在关闭浏览器网页后)。每个函数都有自己的环境,当执行流进入一个函数时,函数的环境被推入一个环境栈中,但函数执行完毕后,栈将其环境弹出,将控制权返回给之前的环境。作用域链从当前函数的活动对象开始,以最外层的全局执行环境的变量对象为尽头,过程中直到找到需要的标识符为止。即内部环境可以通过一条作用域链访问所有的外部环境。

    举个例子:

  var  a  =  10; 
  function  test(x)  { 
    var  b  =  20; 
  } 
  test(30);
    这个函数对应的VO结构如下

  VO(globalContext)  =  { //全局变量对象
    a  :  10, 
    test  :  <ref  to  function> 
  }; 
  VO(test  functionContext)  =  { //函数变量对象
    x  :  30, 
    b:  20 
  };
    下面详细的对变量的声明和赋值过程进行拆分

     一、变量初始化阶段:VO按照如下顺序填充:(也可以用来解释函数式声明的优先定义)
    1.  函数参数  (若未传入,初始化该参数值为undefined) 
    2.  函数声明  (若发生命名冲突,会覆盖) 
    3.  变量声明  (初始化变量值为undefined,若发生命名冲突,会忽略。)

  function  test(a,  b)  { 
    var  c  =  10; 
    function  d()  {} 
    var  e  =  function  _e()  {}; 
    (function  x()  {}); 
    b  =  20;  
  }   
  test(10); 
   对应的初始化情况为
  AO(test)  =  { 
    a:  10, 
    b:  undefined, 
    c:  undefined, 
    d:  <ref  to  func  "d"> 
    e:  undefined 
  };
   具体的过程如下:首先进行函数参数的初始化:a被初始化为10,而b未传入复制为undefined( 本质上为数组中arguments[1] == undefined);然后进行函数声明,d被声明为函数引用;然后进行变量声明c被声明为undefined。 注意一点,变量如果已声明却未赋值,则显示undefined;但调用一个未声明的变量则会报错,这两者是不同的。你能区分一下的情况吗
    //未声明
    alert(x);//error
    //声明未赋值
    var x;
    alert(x);//undefine 
    //赋值前调用
    alert(x);//undefined ,因为alert()处于赋值语句之前,在函数初始化阶段x首先被初始化为undefined,所以此处x值为undefined,但是并不会报错
    var x = 10;
 
 

      这里的第二点可以用来验证一下函数声明提升(就是函数式声明的方式可以再其在执行任何代码之前可以访问它),我们在举两个例子来说明一下函数冲突的解决方式

    function foo(x,y,z){
        function x(){};
        alert(x)
    }
    foo(100);// alert funxtion x(){},发生冲突时,变量x被声明为一个函数引用覆盖掉了传入的参数声明

       再举个例子说明一下第三点 
 

    function foo(x,y,z){
        function func(){};
        var func;
        alert(func)
    }
    foo(100);// alert funxtion x(){},当变量声明冲突时,会自动忽略掉,保持原有的声明状态


      二、代码执行阶段
   AO(test)  =  { 
    a:  10, 
    b:  20, 
    c:  10, 
    d:  <reference  to  FunctionDeclaration  "d"> 
    e:  function  _e()  {}; 
   };
     举个例子说明一下现在的状况
   function foo(x,y,z){
        function func(){};
        var func = 1;
        alert(func)
   }
   foo(100);//1,因为的func已经被赋值了x,已经不是初始化的默认值了
     案例三,也是一道经典的JavaScript面试题,这道题搞对了,你就通关了

    alert(x);    //  function                
    var  x  =  10; 
    alert(x);   //  10                 
    x  =  20; 
    function  x()  {} 
    alert(x);    //  20 
    if  (true)  { 
      var  a  =  1; 
    }  else  { 
      var  b  =  true; 
    } 
    alert(a);    //  1  
    alert(b);    //  undefined




我们把函数从头到尾摸了一遍,有收获到东西吗?白了个白~

你可能感兴趣的:(JavaScript Core -- 函数详解(作用域&&参数值传递&&this关键字&&函数声明))