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是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();//
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。
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无关了。
某个环境中所有代码执行完毕后该环境被销毁(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
我们把函数从头到尾摸了一遍,有收获到东西吗?白了个白~