您的位置:首页 > Web前端 > JavaScript

javascript函数

2015-08-23 15:23 801 查看
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。

定义一个函数

函数声明

function print(){
// ...
}


函数表达式

var print = function (){
// ...
};


使用Function构造函数

var add = new Function("x","y","return (x+y)");
// 相当于定义了如下函数
// function add(x, y) {
//   return (x+y);
// }


除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。如果只有一个参数,该参数就是函数体。

Function构造函数可以不使用new命令,返回结果完全一样。

函数名的提升

JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会被提升到代码头部。所以,下面的代码不会报错。

f();
function f(){}


如果是采用赋值语句定义函数,JavaScript就会报错。

f();
var f = function (){};
// TypeError: undefined is not a function


当调用f的时候,f只是被声明,还没有被赋值,等于undefined,所以会报错。

如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。这与变量声明提前有关。

var f = function() {
console.log ('1');
}

function f() {
console.log('2');
}

f()
// 1


不能在条件语句中声明函数

根据ECMAScript的规范,不得在非函数的代码块中声明函数,最常见的情况就是if和try语句。

if (foo) {
function x() { return; }
}

try {
function x() {return; }
} catch(e) {
console.log(e);
}


上面代码分别在if代码块和try代码块中声明了两个函数,按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。

但是由于存在函数名的提升,所以在条件语句中声明函数是无效的,这是非常容易出错的地方。

if (false){
function f(){}
}

f()
// 不报错


由于函数f的声明被提升到了if语句的前面,导致if语句无效,所以上面的代码不会报错。要达到在条件语句中定义函数的目的,只有使用函数表达式。

if (false){
var f = function (){};
}

f()
// undefined


函数的属性和方法

name

name属性返回紧跟在function关键字之后的那个函数名

function f1() {}
f1.name // 'f1'

var f2 = function () {};
f2.name // ''

var f3 = function myName() {};
f3.name // 'myName'


length

length属性返回函数定义中参数的个数

function f(a,b) {}

f.length
// 2


toString

函数的toString方法返回函数的源码

函数作用域

函数内部的变量提升

与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}


上面的代码等同于

function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}


函数本身的作用域

函数本身也是一个值,也有自己的作用域。它的作用域绑定其声明时所在的作用域。

var a = 1;
var x = function (){
console.log(a);
};

function f(){
var a = 2;
x();
}

f() // 1


下面,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。

var x = function (){
console.log(a);
};

function y(f){
var a = 2;
f();
}

y(x)
// ReferenceError: a is not defined


参数

无论提供多少个参数(或者不提供参数),JavaScript都不会报错。被省略的参数的值就变为undefined。需要注意的是,函数的length属性与实际传入的参数个数无关,只反映定义时的参数个数。

没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。

function f(a,b){
return a;
}

f(,1) // error
f(undefined,1) // undefined


默认值

通过下面的方法,可以为函数的参数设置默认值

function f(a){
a = a || 1;
return a;
}

f('') // 1
f(0) // 1


这种写法会对a进行一次布尔运算,只有为true时,才会返回a。可是,除了undefined以外,0、空字符、null等的布尔值也是false。也就是说,在上面的函数中,不能让a等于0或空字符串,否则在明明有参数的情况下,也会返回默认值。

function f(a){
(a !== undefined && a != null)?(a = a):(a = 1);
return a;
}

f('') // ""
f(0) // 0


传递方式

JavaScript的函数参数传递方式只有传值(passes by value),这意味着,在函数体内修改参数值,不会影响到函数外部。

// 修改原始类型的参数值
var p = 2;

function f(p){
p = 3;
}

f(p);
p // 2

// 修改复合类型的参数值
var o = [1,2,3];

function f(o){
o = [2,3,4];
}

f(o);
o // [1, 2, 3]


上面代码分成两段,分别修改原始类型的参数值和复合类型的参数值。两种情况下,函数内部修改参数值,都不会影响到函数外部。

再接着看

// 修改对象的属性值
var o = { p:1 };

function f(obj){
obj.p = 2;
}

f(o);
o.p // 2

// 修改数组的属性值
var a = [1,2,3];

function f(a){
a[0]=4;
}

f(a);
a // [4,2,3]


这是为什么呢?

复合类型的变量存储的是地址,传给函数时,会有一个副本。在函数内部,当给变量赋值时
o = [2,3,4];
,原来
o
的地址就变成指向
[2, 3, 4]
了;当
o[0]=4;
时,此时
o
存储的地址并未改变。

不只是在函数传参这一方面,js就是这样的

var arr1 = [1, 2];
var arr2 = arr1;
console.log(arr1); // [1, 2]
console.log(arr2); // [1, 2]

arr2[0] = 0;
console.log(arr1); // [0, 2]
console.log(arr2); // [0, 2]

arr2 = [3, 4];
console.log(arr1); // [1, 2]
console.log(arr2); // [3, 4]


如果需要对某个变量达到传址传递的效果,可以将它写成全局对象的属性

var a = 1;

function f(p){
window[p]=2;
}

f('a');

a // 2


上面代码中,变量a本来是传值传递,但是写成window对象的属性,就达到了传址传递的效果。

同名参数

如果有同名的参数,则取最后出现的那个值

function f(a, a){
console.log(a);
}

f(1,2)
// 2


即使后面的a没有值或被省略,也是以其为准

function f(a, a){
console.log(a);
}

f(1)
// undefined


调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined。这时,如果要获得第一个a的值,可以使用arguments对象。

function f(a, a){
console.log(arguments[0]);
}

f(1)
// 1


arguments对象

由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。

arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。这个对象只有在函数体内部,才可以使用。

arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)

var f = function(a,b) {
arguments[0] = 3;
arguments[1] = 2;
return a+b;
}

f(1, 1)
// 5


可以通过arguments对象的length属性,判断函数调用时到底带几个参数

arguments对象带有一个callee属性,返回它所对应的原函数

var f = function(one) {
console.log(arguments.callee === f);
}

f()
// true


callee属性在某些时候会非常有用,比如在匿名函数中通过callee来递归地调用自身

var factorial = function (x) {
if (x < 1) return 1;
return x * arguments.callee(x - 1);
}


闭包

闭包(closure)就是定义在函数体内部的函数。更理论性的表达是,闭包是函数与其生成时所在的作用域对象(scope object)的一种结合。

闭包的特点在于,在函数外部可以读取函数的内部变量

function f() {
var v = 1;

var c = function (){
return v;
};

return c;
}

var o = f();

o();
// 1


上面代码表示,原先在函数f外部,我们是没有办法读取内部变量v的。但是,借助闭包c,可以读到这个变量。

闭包不仅可以读取函数内部变量,还可以使得内部变量记住上一次调用时的运算结果

function createIncrementor(start) {
return function () {
return start++;
}
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7


立即调用的函数表达式(IIFE)

有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。

function(){ /* code */ }();
// SyntaxError: Unexpected token (


产生这个错误的原因是,Javascript引擎看到function关键字之后,认为后面跟的是函数定义语句,不应该以圆括号结尾。

解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,可以对此进行运算。你可以这样写:

(function(){ /* code */ }());

// 或者

(function(){ /* code */ })();


注意,上面的两种写法的结尾,都必须加上分号。

推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。

var i = function(){ return 10; }();

true && function(){ /* code */ }();

0, function(){ /* code */ }();


甚至像这样写

!function(){ /* code */ }();

~function(){ /* code */ }();

-function(){ /* code */ }();

+function(){ /* code */ }();


new关键字也能达到这个效果

new function(){ /* code */ } // 注意不是Function

new function(){ /* code */ }() // 只有传递参数时,才需要最后那个圆括号。


通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

eval

eval命令的作用是,将字符串当作语句执行

由于eval没有自己的作用域,都在当前作用域内执行,因此可能会修改其他外部变量的值,造成安全问题。

此外,eval的命令字符串不会得到JavaScript引擎的优化,运行速度较慢,也是另一个不应该使用它的理由。

通常情况下,eval最常见的场合是解析JSON数据字符串,正确的做法是这时应该使用浏览器提供的JSON.parse方法。

参考

http://javascript.ruanyifeng.com/grammar/function.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: