[记录] JavaScript 中的函数
函数:[JavaScript 教程]
函数是一段可以反复调用的代码块。可以传递参数,不同的参数会返回不同的值。
函数声明的三种方法:
1. function 命令
function 命令声明的代码块,就是一个函数。 function 命令后面是函数名,函数名后面是一对圆括号(), 里面可以传入参数。函数体放在大括号里面。
function show(name) { // 代码块... console.log( name ); }
2. 函数表达式
除了用 function 命令声明函数, 还可以采用变量赋值的写法。
var show = function(name) { // 代码块... console.log( name ); };
这种写法是将一个匿名函数赋值给变量。因为赋值语句的等号右侧只能放表达式,所有这个匿名函数称函数表达式。
带名的函数表达式,函数名在函数体内有效,在函数体外部无效。
var show = function abc() { // 代码块... // 命名函数表达式 // 每个函数都会有一个name的属性,这里的区别就在于 show.name 是 abc };
注意: 函数作为表达式出现,则会忽略名称。函数表达式需要在语句结尾加上分号,表示语句结束。
3. Function 构造函数
Function 构造函数可以不使用new命令,返回结果完全一样。这种声明方式少人使用。 var show = new Function( "x", "y", "return x + y" ); // 等同于 function show(x, y) { return x + y; } Function 构造函数的最后一个参数会被当作函数体,如果只有一个参数,该参数就是函数体。
函数调用:
有4种调用模式:函数调用模式、方法调用模式、构造器调用模式、间接调用模式;
1. 函数调用模式
function init(){ // 代码块... // 非严格模式下,this指向全局对象window // 'use strict' // 严格模式下, this是undefined } init(); // window.init(); 可以通过 this 来判断当前是否是严格模式 var strict = (function(){ return !this; }());
2. 方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。
var myObject = { value: 100, init: function() { console.log( this.value ); } } myObject.init(); // 100
3. 构造器调用模式,用new关键字来新建一个函数对象的调用; (this指向被绑定到的构造函数实例上)
var init = function (status) { this.status = status; } init.prototype.getStatus = function () { return this.status; }; var test = new init('构造函数'); console.log( test.getStatus() ); // 构造函数; this指向test
4. 间接调用模式 (this指向指定调用的对象)
call()、apply()是函数的两个方法,可以用来间接地调用函数。
var obj = {}; function show(x, y) { return x + y; } show.call(obj, 1, 2); //3 this => obj show.apply(obj, [1, 2]); //3 this => obj
函数的重复声明:
变量的重复声明是无用的,不会覆盖之前同一作用域声明的变量。
但函数的重复声明会覆盖前面的声明的同名函数或同名变量。
变量的重复声明变量无用
var num = 10; var num; console.log( num ); // 10
覆盖同名变量
var num; function num() { console.log(1); } num(); // 1
覆盖同名函数
function num() { console.log("A"); } num(); // B function num() { console.log("B"); }
JavaScript 引擎将函数名视同变量名,所有采用 function 命令声明函数,都会被提升到代码头部。
// 先调用 show(); // 后声明,由于"变量提升", 所以代码可以正常执行 function show() { .. } 采用赋值语句定义函数,先调用,再声明就会报错。 show(); var show = function() {} // TypeError: show is not a function 等同以下形式 var show; show(); show = function() {}; 注意: 同时采用 function 命令 和 赋值语句 声明同一个函数,最后采用的总是赋值语句定义的。 var show = function() { console.log("A"); }; function show() { console.log("B"); } show(); // A
函数本身的作用域:
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
函数的属性和方法:
A. name 属性
函数的 name 属性返回函数的名字 function show() {} show.name; // show 变量赋值定义的函数,name 属性返回变量名。 var show = function () { // 匿名函数表达式 }; show.name; // show var show = function abc() { // 有名函数表达式 } show.name; // abc
B. length 属性 (函数形参的个数)
function show(a, b){ // a,b 是函数的形参 } show.length; // 2
C. toString() 方法: 返回一个字符串,内容是函数的源码;
function show() { // 注释 hide(); } show.toString(); 结果: "function show() { // 注释 hide(); }"
关于删除:
function 命令声明函数无法删除,和变量声明一样 function show() { console.log("A"); } delete show; // false show(); // A
关于函数返回值:(return 有终止程序的作用)
所有函数都有返回值,没有return语句时,默认返回内容是undefined,return语句不会阻止finally的执行
function show() { try{ return "A"; }catch(error) { return "B"; }finally{ return "C"; } } show(); // C
作为构造函数使用时,在调用之前前面加上了new,如果有return语句,返回值不是一个对象,则会忽略,则返回值是this(new出来的新对象);
function show() { // 约定:如果一个函数要做为构造函数的话,首字母需大写。 this.num = 2; return 1; // 返回的是一个基本类型,构造函数会忽略 } var test = new show(); console.log( test ); // { num: 2 } console.log( test.constructor ); // show(){ this.num = 2; return 1; }
如果返回值是一个对象,则返回该对象。
function show() { this.num = 2; return { num: 1 }; } var test = new show(); console.log( test ); // { num: 1 } console.log( test.constructor ); // Object(){ [native code] }
关于函数的参数:
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
// 在函数声明时圆括号内的参数,叫形式参数 - 简称形参;
function test(a, b, c){ // 形参 - 在函数内部相当于新定义的变量 // var a, b, c; // 获取形参的长度: test.length; // arguments 是函数的实参列表, 记录的是函数实际传入的参数 // [1,2,3] arguments一个类数组 // 形参个数 和 实参列表arguments一一对应,如有一方修改则都改, // 例外:当实参小于形参时,修改形参变量,对应的arguments为undefined return a; }
// 函数调用的时候掺入的参数,叫做实际参数 - 简称实参;
test(1, 2); // 1; 实参比形参多, 则会忽略多传入的实参 test(); // undefined; 形参比实参多, 内部用到则默认为undefined test( , 1); // 不能省略靠前的参数,如果要省略只有显示传入undefined // 报错 SyntaxError: Unexpected token ,(...) test(undefined, 1); // undefined
同名形参: (在实际开发过程中肯定是不允出现的)
A. 在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。
function test(a, a, a) { console.log(a); } test(1,2,3); // 3 test(1,2); // undefined
B. 在严格模式下,出现同名形参会抛出语法错误。
function test(a,a,a){ 'use strict'; return a; } test(1,2,3); // SyntaxError: Duplicate parameter name not allowed in this context
以对象为参数:
当一个函数的形参过多时,在调用的时候必须传入和形参对应的数据。
function show(name, sex, age) { // 在传入实参数是必须一一对应,name(名称),sex(性别),age(年龄) console.log('姓名:' + name ',性别:' + sex + ', 年龄:' + age); } 正常顺序 show('jing', '女', '18'); // 姓名:jing,性别:女, 年龄:18 错误顺序 show('jing', '18', '女'); // 姓名:jing,性别:18, 年龄:女 通过key/value对的形式来传入参数,参数的顺序就无关紧要了。 function show(option) { console.log( '姓名:' + option.name + ',性别:' + option.sex + ', 年龄:' + option.age ); } show({age: 18, name:'jing', sex: '女'}); // 姓名:jing,性别:女, 年龄:18
以函数为参数:
函数本身是一个对象,因此可以将函数作为另一个函数的参数,实现函数回调。
function show( fn ) { // 判断当前传入的参数类型是否是函数,如果是函数则立即执行; if(typeof(fn) === 'function'){ fn(); // 执行回调 } } show(function(){ console.log('A'); }); // A
arguments对象:(函数内部的实参数列表)
由于JavaScript允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这是就arguments对象的由来。
arguments对象包含了函数运行时的所有参数,arguments[下标]来获取,只有在函数体内才可以使用。
function show() { console.log(arguments[0]); console.log(arguments[1]); } show("A", "B"); // A // B
当形参和实参的格式相同时,arguments对象的值和对应的形参的值保持同步
function show(num1, num2) { console.log( num1, arguments[0] ); // 1 1 arguments[0] = 7; console.log( num1, arguments[0] ); // 7 7 num1 = 10; console.log( num1, arguments[0] ); // 10 10 } show(1); 注意: 虽然形参和实参列表的值相同,但不是相同的命名空间。他们的命名空间是独立的,但值是同步的。
在严格模式下,arguments对象的值和形参的值是 独立的
function show(num1, num2) { 'use strict' console.log( num1, arguments[0] ); // 1 1 arguments[0] = 7; console.log( num1, arguments[0] ); // 1 7 num1 = 10; console.log( num1, arguments[0] ); // 10 7 } show(1);
当形参并没有对应的实参时,arguments对象的值和形参的值并不对应
function show(num1, num2) { console.log( num1, arguments[0] ); // undefined, undefined num1 = 10; arguments[0] = 7; console.log( num1, arguments[0] ); // 10 7 } show();
arguments对象的内部属性callee
该属性是一个指针,指向拥有这个arguments对象的函数。
var show = function() { console.log( arguments.callee === show ); } show(); // true 可以通过 arguments.callee 达到调用函数自身的目的。 注意:这个属性在严格模式下是禁用的。
通过递归(函数调用自身)实现的阶乘函数
function factorial(num) { if( num <= 1 ){ return 1; }else{ return num * factorial(num-1); } } factorial(5); // 120
使用arguments.callee消除函数解耦
function factorial(num) { if( num <= 1 ){ return 1; }else{ return num * arguments.callee(num-1); } } factorial(5); // 120
闭包:
理解闭包,必须理解变量作用域。
JS的两种作用域: 全局作用域和函数作用域。函数内部可以直接读取全局变量。
// 函数内部读取全局变量
var num = 10; function show() { cosnole.log( num ); } show(); // 10
函数外部无法读取函数内部声明的变量。
function show() { var num = 10; } console.log( num ); // ReferenceError: num is not defined
链式作用域,子对象可以向上寻找父对象的变量。
// 函数内部嵌套函数,内部函数可以访问外部的变量
function show(){ var num = 10; function show2() { console.log( num ); // 10 } return show2; }
函数 show 的返回值是函数 show2, 由于 show2 可以读取 show 的内部变量, 所以就可以在外部获得 show 的内部变量了。
show2 函数就是闭包, 能够读取其他函数内部变量的函数。
只有函数内部的子函数才能读取内部变量,简单理解成,定义在一个函数内部的函数。
闭包最大的特点,就是它可以记住诞生的环境,比如 show2 记住了它诞生的环境 show,所以从show2可以得到show的内部变量。
闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大的两个用处,1. 可以读取函数内部变量。 2. 让这些变量始终保持在内存中。
function count(start) { return function () { return start++; } } var init = count(5); init(); // 5; init(); // 6; init(); // 7 通过闭包,start的状态被保留了, 每一次调用都是在上一次调用的基础上进行计算。
闭包的另一个用处: 是封装对象的私有属性和私有方法。
function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; } var p1 = Person('yuxi'); p1.setAge(18); p1.getAge(); // 18 函数 Person 的内部变量 _age, 通过闭包 getAge 和 setAge,变成了返回对象 p1 的私有变量。
注意: 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以会消耗很大内存。因此不能滥用闭包,否则会造成网页的性能问题。