函数:[JavaScript 教程]
函数是一段能够反复调用的代码块。能够传递参数,不一样的参数会返回不一样的值。
函数声明的三种方法:
1. function 命令
function 命令声明的代码块,就是一个函数。 function 命令后面是函数名,函数名后面是一对圆括号(), 里面能够传入参数。函数体放在大括号里面。javascript
function show(name) { // 代码块... console.log( name ); }
2. 函数表达式
除了用 function 命令声明函数, 还能够采用变量赋值的写法。html
var show = function(name) { // 代码块... console.log( name ); };
这种写法是将一个匿名函数赋值给变量。由于赋值语句的等号右侧只能放表达式,全部这个匿名函数称函数表达式。
带名的函数表达式,函数名在函数体内有效,在函数体外部无效。 java
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被绑定到该对象。 app
var myObject = { value: 100, init: function() { console.log( this.value ); } } myObject.init(); // 100
3. 构造器调用模式,用new关键字来新建一个函数对象的调用; (this指向被绑定到的构造函数实例上)ide
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
函数的重复声明:性能
变量的重复声明是无用的,不会覆盖以前同一做用域声明的变量。
但函数的重复声明会覆盖前面的声明的同名函数或同名变量。
变量的重复声明变量无用this
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 的私有变量。
注意: 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,因此会消耗很大内存。所以不能滥用闭包,不然会形成网页的性能问题。