javascript函数
2015-08-23 15:23
801 查看
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。
函数表达式
使用Function构造函数
除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。如果只有一个参数,该参数就是函数体。
Function构造函数可以不使用new命令,返回结果完全一样。
如果是采用赋值语句定义函数,JavaScript就会报错。
当调用f的时候,f只是被声明,还没有被赋值,等于undefined,所以会报错。
如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。这与变量声明提前有关。
上面代码分别在if代码块和try代码块中声明了两个函数,按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。
但是由于存在函数名的提升,所以在条件语句中声明函数是无效的,这是非常容易出错的地方。
由于函数f的声明被提升到了if语句的前面,导致if语句无效,所以上面的代码不会报错。要达到在条件语句中定义函数的目的,只有使用函数表达式。
name属性返回紧跟在function关键字之后的那个函数名
length
length属性返回函数定义中参数的个数
toString
函数的toString方法返回函数的源码
上面的代码等同于
下面,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。
没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。
这种写法会对a进行一次布尔运算,只有为true时,才会返回a。可是,除了undefined以外,0、空字符、null等的布尔值也是false。也就是说,在上面的函数中,不能让a等于0或空字符串,否则在明明有参数的情况下,也会返回默认值。
上面代码分成两段,分别修改原始类型的参数值和复合类型的参数值。两种情况下,函数内部修改参数值,都不会影响到函数外部。
再接着看
这是为什么呢?
复合类型的变量存储的是地址,传给函数时,会有一个副本。在函数内部,当给变量赋值时
不只是在函数传参这一方面,js就是这样的
如果需要对某个变量达到传址传递的效果,可以将它写成全局对象的属性
上面代码中,变量a本来是传值传递,但是写成window对象的属性,就达到了传址传递的效果。
即使后面的a没有值或被省略,也是以其为准
调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined。这时,如果要获得第一个a的值,可以使用arguments对象。
arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。这个对象只有在函数体内部,才可以使用。
arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)
可以通过arguments对象的length属性,判断函数调用时到底带几个参数
arguments对象带有一个callee属性,返回它所对应的原函数
callee属性在某些时候会非常有用,比如在匿名函数中通过callee来递归地调用自身
闭包的特点在于,在函数外部可以读取函数的内部变量
上面代码表示,原先在函数f外部,我们是没有办法读取内部变量v的。但是,借助闭包c,可以读到这个变量。
闭包不仅可以读取函数内部变量,还可以使得内部变量记住上一次调用时的运算结果
产生这个错误的原因是,Javascript引擎看到function关键字之后,认为后面跟的是函数定义语句,不应该以圆括号结尾。
解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,可以对此进行运算。你可以这样写:
注意,上面的两种写法的结尾,都必须加上分号。
推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。
甚至像这样写
new关键字也能达到这个效果
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
由于eval没有自己的作用域,都在当前作用域内执行,因此可能会修改其他外部变量的值,造成安全问题。
此外,eval的命令字符串不会得到JavaScript引擎的优化,运行速度较慢,也是另一个不应该使用它的理由。
通常情况下,eval最常见的场合是解析JSON数据字符串,正确的做法是这时应该使用浏览器提供的JSON.parse方法。
定义一个函数
函数声明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
函数的属性和方法
namename属性返回紧跟在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相关文章推荐
- jstl入门
- 《JavaScript DOM 编程艺术》读书笔记
- 用原生Javascript对象中的访问器属性实现双向数据绑定
- Gson补充
- JavaScript第二天总结
- gson使用教程-翻译5
- JavaScript第一天总结
- gson使用教程-翻译4
- Extjs的下拉框例子
- Javascript跳转页面和打开新窗口等方法
- CSS javascript 结合实现悬浮固定菜单效果
- three js JavaScript编写的WebGL第三方库
- javascript对象创建
- 用js -- interval 实现当前时间
- Enums in JavaScript
- 封装XML或Json通信数据
- 在一个js文件中引用另一个js文件
- fastjson的使用
- javascript中curring的实现
- js添加创建节点和合并节点