那么,值类型和引用类型有什么区别呢?最主要的一个,就是当变量类型为值类型时,变量存储的就是变量值本身,而当变量类型为引用类型时,变量存储的并不是变量值,而只是一个指向变量值的指针,访问引用类型的变量值时,首先是取到这个指针,然后是根据这个指针去获取变量值。如果将一个引用类型的变量值赋给另一个变量,最终结果是这两个变量同时指向了一个变量值,修改其中一个会同时修改到另一个: 复制代码 代码如下: var a = { name:"linjisong", age:29 }; var b = a;//将引用类型的变量a赋给变量b,a、b同时指向了a开始指向的那个对象 b.name = "oulinhai";//修改b指向的对象,也就是修改了a指向的对象 console.info(a.name);//oulinhai b = {//将变量重新赋值,但是b原来指向的对象没有变更,也就是a指向的对象没有变化 name:"hujinxing", age:23 }; console.info(a.name);//oulinhai
(2)函数名是指向函数对象的引用类型变量 复制代码 代码如下: function fn(p){ console.info(p); } console.info(fn);//fn(p),可以将fn作为一般变量来访问 var b = fn; b("function");//function,可以对b使用函数调用,说明b指向的对象(也就是原来fn指向的对象)是一个函数
(1)作为一种对象,函数也有和普通对象类似的创建方式,使用new调用构造函数Function(),它可以接受任意数量的参数,最后一个参数作为函数体,而前面的所有参数都作为函数的形式参数,前面的形式参数还可以使用逗号隔开作为一个参数传入,一般形式为: 复制代码 代码如下: var fn = new Function(p1, p2, ..., pn, body); //或者 var fn = Function(p1, p2, ..., pn, body); //或者 var fn = new Function("p1, p2, ..., pn", q1, q2, ..., qn, body); //或者 var fn = Function("p1, p2, ..., pn", q1, q2, ..., qn, body);
例如: 复制代码 代码如下: var add = new Function("a","b","return a + b;"); console.info(add(2,1));//3 var subtract = Function("a","b","return a - b;"); console.info(subtract(2,1));//1 var sum = new Function("a,b","c","return a + b + c;"); console.info(sum(1,2,3));//6
(2)从上下文区分,这个说起来简单,就是:只允许表达式出现的上下文中的一定是函数表达式,只允许声明出现的上下文的一定是函数声明。举一些例子: 复制代码 代码如下: function fn(){};//函数声明 //function fn(){}(); // 异常,函数声明不能直接调用 var fn = function fn(){};//函数表达式 (function fn(){});//函数表达式,在分组操作符内 +function fn(){console.info(1);}();//1,函数表达式,出现在操作符+之后,因此可以直接调用,这里,也可以使用其它的操作符,比如new new function fn(){console.info(2);}();//2,函数表达式,new操作符之后 (function(){ function fn(){};//函数声明 });
还有一个问题,这里我们怎么确定变量类型是在初始化时候而不是在变量声明提升时候改变的呢?看下面的代码: 复制代码 代码如下: console.info(typeof fn);//function function fn(){ } var fn; console.info(typeof fn);//function
可以看到,声明提升后类型为function,并且由于没有初始化代码,最后的类型没有改变。
关于函数声明和函数表达式,还有一点需要注意的,看下面的代码: 复制代码 代码如下: if(true){ function fn(){ return 1; } }else{ function fn(){ return 2; } } console.info(fn());// 在Firefox输出1,在Opera输出2,在Opera中声明提升,后面的声明会覆盖前面的同级别声明
if(true){ gn = function(){ return 1; }; }else{ gn = function(){ return 2; }; } console.info(gn());// 1,所有浏览器输出都是1
在ECMAScript规范中,命名函数表达式的标识符属于内部作用域,而函数声明的标识符属于定义作用域。 复制代码 代码如下: var sum = function fn(){ var total = 0, l = arguments.length; for(; l; l--) { total += arguments[l-1]; } console.info(typeof fn); return total; } console.info(sum(1,2,3,4));//function,10 console.info(fn(1,2,3,4));//ReferenceError
除了全局作用域,还有一种函数作用域,在函数作用域中,参与到声明提升竞争的还有函数的参数。首先要明确的是,函数作用域在函数定义时不存在的,只有在函数实际调用才有函数作用域。 复制代码 代码如下: // 参数与内部变量,参数优先 function fn(inner){ console.info(inner);// param console.info(other);// undefined var inner = "inner"; var other = "other"; console.info(inner);// inner console.info(other);// other } fn("param");
// 参数与内部函数,内部函数优先 function gn(inner){ console.info(inner);// inner()函数 console.info(inner());// undefined function inner(){ return other; } var other = "other"; console.info(inner);// inner()函数 console.info(inner());// other } gn("param");
函数是对象,函数名是指向函数对象的引用类型变量,这使得我们不可能像一般面向对象语言中那样实现重载: 复制代码 代码如下: function fn(a){ return a; } function fn(a,b){ return a + b; }
console.info(fn(1)); // NaN console.info(fn(1,2));// 3
不要奇怪第8行为什么输出NaN,因为函数名只是一个变量而已,两次函数声明会依次解析,这个变量最终指向的函数就是第二个函数,而第8行只传入1个参数,在函数内部b就自动赋值为undefined,然后与1相加,结果就是NaN。换成函数表达式,也许就好理解多了,只是赋值了两次而已,自然后面的赋值会覆盖前面的: 复制代码 代码如下: var fn = function (a){ return a; } fn = function (a,b){ return a + b;}