JavaScript学习笔记(六)——函数
函数
1、函数定义
总共有三种函数定义的方式:函数声明语句、函数表达式、内置构造函数。
-
函数声明语句
function functionName(parameters) {
//执行的代码
}
函数声明后不会立即执行,会在我们需要的时候调用到。
小练习:定义一个求阶乘的函数。
function fn2(n) {
var s = n;
var result = 1;
for (var n; n >= 1; n--) {
result *= n;
}
console.log(s + '的阶乘等于' + result);
}
fn2(12);
2.函数表达式
var functionName = function (parameters) {
//执行的代码
};
//函数以分号结尾,因为它实际上是一个执行语句
以上函数实际上是一个 匿名函数 (函数没有名称),函数存储于变量中,故通常不加函数名。
当写递归函数时,也可加上函数名。
定义一个表达式函数:
var square = function factorial(h) {
if(h===1) {return 1;}
else {return h*factorial(h-1);}
}
console.log(square(3));
3.Function内置构造函数
在以上实例中,我们了解到函数通过关键字 function 定义。
函数同样可以通过内置的 JavaScript 函数构造器(Function())定义。
var functionName = new Function("parameters","执行的代码")
//注意引号不可省
这种方式不推荐,无法写递归。
用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。所以,在 JavaScript 中,很多时候,你需要避免使用 new 关键字。
var y = "global";
function constructFunction() {
var y = "local";
function test(){};
return new Function("return y"); // 无法获取局部变量,作用域始终是全局作用域
}
alert( constructFunction()() );
补充:函数可以嵌套在其他函数里面,也就是在函数里面可以定义函数
function distance (x1, y1, x2, y2) {
var n = 10;
function square(h) { console.log(n);return h*h;}
return Math.sqrt(square(x2-x1) + square(y2-y1)).toFixed(2);
}
被嵌套的函数可以访问嵌套他们的函数的变量或参数。
2、 函数调用
javascript一共有4种调用模式:函数调用模式、方法调用模式、构造器调用模式和间接调用模式。
每种方式的不同在于 this 的初始化。
-
函数调用模式
var myfunc = function(a,b){ console.log(this); return a+b; } alert( myfunc(3,4) );
函数调用模式中:
a, this是指向Window的
b, 返回值是由return语句决定的,如果没有return则表示没有返回值
-
方法调用模式
先定义一个对象,然后在对象的属性中定义方法,通过myobject.property来执行方法。
var name = "james"; var obj = { name : "wade", fn1 : function () { console.log(this.name); } }; obj.fn1(); //wade
方法调用模式中:
a, this 是指向调用该方法的对象
b, 返回值还是由return语句决定,如果没有return表示没有返回值
-
构造器调用模式
如果函数或者方法调用之前带有关键字new,它就当成构造函数调用。
function Fn () { this.name = "james"; this.age = 32; console.log(this); }; var fn1 = new Fn(); //Fn整个对象 console.log(fn1); //Fn整个对象
通过上面的代码结果分析,会得到以下结论(构造函数调用模式中):
a, this是指向构造函数的实例
b, 如果没有添加返回值的话,默认的返回值是this
但是如果手动添加返回值之后呢?
function Fn1 () { this.name = "james"; return "wade" ; }; var fn1 = new Fn1(); console.log(fn1); console.log(fn1.name); function Fn2 () { this.name = "james"; return [1,2,3]; }; var fn2 = new Fn2(); console.log(fn2); console.log(fn2.name);
而通过上面的代码结果分析,优化上面的结论:
a, this是指向构造函数的实例
b, 如果没有添加返回值的话,默认的返回值是this
c, 如果有返回值,且返回值是简单数据类型(Number,String,Boolean··)的话,最后仍回返回this
d, 如果有返回值,且返回值是复杂数据类型(对象)的话,最终返回该对象,所以上面的fn2是指向数组,所以fn2.name为undefined
-
间接调用模式
也称之为“apply、call调用模式” 或 “上下文调用模式”。
var myobject={}; var sum = function(a,b){ console.log(this); return a+b; }; var sum2 = sum.call(myobject,10,30); alert(sum2);
由之前所学,this指向由传入的第一个参数决定。
再看看,下面函数调用中this指向如何呢?
function f1(){ console.log(this); } f1.call(null); f1.call(undefined); f1.call(123); f1.call("abc"); f1.call(true); f1.call([1,2,3]);
通过上面的代码结果分析,得出以下结论(上下文调用模式中):
a, 传递的参数不同,this的指向不同,this会指向传入参数的数据类型
b, 返回值是由return决定,如果没有return表示没有返回值。
3、函数参数
函数可以传进来任意多个参数,无论传进来的参数是什么类型,甚至可以不传参。
function add(x){
return x+1;
}
console.log(add());//NaN
特殊情况1:同名形参
在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。
function add(x,x,x){
return x;
}
console.log( add(1,2,3) );//3
在严格模式下,出现同名形参会抛出语法错误
特殊情况2:参数个数与传入的实参个数不相等时
当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined值。当实参多于形参,则只使用有效的实参,多出部分没影响。
function add(x,y){
console.log(x,y);
}
add(1);//1 undefined
总的来说,函数参数分为两类:函数显式参数(Parameters)与隐式参数(Arguments)
显式参数(Parameters)
function functionName(parameter1, parameter2, parameter3) {
// 要执行的代码……
}
函数显式参数在函数定义时列出(即形参)。
函数调用未传参时,参数会默认设置为: undefined。有时这是可以接受的,但是建议最好为参数设置一个默认值:
function myFunction(x, y) {
if (y === undefined) {
y = 0;
}
return x+y;
}
console.log(myFunction(1)); //打印1 因为设置了当之传入一个参数,也就是y为undefined的时候默认值为0,所以返回1+0=1
或者,更简单的方式:
function myFunction(x, y) {
y = y || 0;// ||当y存在值时就为那个值,否则默认为0,这个方法在后面将大量用到
}
隐式参数(Arguments)
JavaScript 函数有个内置的对象 arguments 对象。
argument 对象包含了函数调用的参数数组(实参数组)。
通过这种方式你可以很方便的找到最大的一个参数的值:
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var max = arguments[0];
if(arguments.length < 2) return max;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
// max=arguments[i]>max?arguments[i]:max
}
return max;
}
console.log(x);
//返回传入参数的最大值500
同理我们也可以的到传入参数的累计和:
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
console.log(x);//所有参数的值相加为871
-
arguments对象与传入参数的映射规则:
function sum(a,b){
arguments[1]=4; //把4赋值给第二个参数也就是b
console.log(arguments[1]);
console.log(b);
}
sum(1);
sum(1,2);
//依次打印 4 undefined 4 4
arguments对象与形参是相互独立的,但又存在映射规则:
-
当传入参数与形参个数相等时,arguments对象与形参才是一一对应的;
-
当传入参数与形参个数不等时,arguments对象与有传入参数的形参才存在映射规则。
练习一道阿里巴巴2013年的一道笔试题:
下面代码中console.log的结果是[1,2,3,4,5]的选项是()AC
//A
function foo(x){
console.log(arguments)
return x;
}
foo(1,2,3,4,5)
//B
function foo(x){
console.log(arguments)
return x;
}(1,2,3,4,5) //无输出
//C
(function foo(x){
console.log(arguments)
return x;
})(1,2,3,4,5)
//D
function foo(){
bar.apply(null,arguments);
}
function bar(x){
console.log(arguments);
}
foo(1,2,3,4,5)