JavaScript---函数

1.函数的声明及调用

1.1函数的概念

JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。

1.2函数的声明

声明方式:严格意义上两种方式

  • 方式一
 function 函数名()
 {
      函数体;
 }

(1)function 声明函数的关键字 全部小写
(2)函数一般是做某件事情,函数名为动词 sayHi
(3)函数不调用,自己不执行

  • 方式二
    箭头函数
let arrowSum = (a, b) =>
 {
     return a + b;
 };

箭头函数也可以不用大括号,但这样会改变函数的行为。使用大括号就说明包含“函数体”,可以在一个函数中包含多条语句,跟常规的函数一样。如果不使用大括号,那么箭头后面就只能有一行代码,比如一个赋值操作,或者一个表达式。

箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用arguments、super和new.target,也不能用作构造函数。此外,箭头函数也没有prototype属性。

1.2.1函数声明与函数表达式

JavaScript引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。
而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
 return num1 + num2;
}

以上代码可以正常运行,因为函数声明会在任何代码执行之前先被读取并添加到执行上下文。这个过程叫作函数声明提升(function declaration hoisting)。在执行代码时,JavaScript引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。如果把前面代码中的函数声明改为等价的函数表达式,那么执行的时候就会出错:

// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
 return num1 + num2;
};

上面的代码之所以会出错,是因为这个函数定义包含在一个变量初始化语句中,而不是函数声明中。这意味着代码如果没有执行到绿色的那一行,那么执行上下文中就没有函数的定义,所以上面的代码会出错。这并不是因为使用let而导致的,使用var关键字也会碰到同样的问题:

//会出错
console.log(sum(10, 10));
var sum = function(num1, num2) {
 return num1 + num2;
};

1.3函数的调用

函数调用:两种方式调用

  • 调用方式一:名字(); 函数可以多次调用
//函数声明
function fn(){
    console.log(1);
} 
//函数的调用
fn();
  • 调用方式二:在事件中调用,直接写函数名,不使用括号
//函数声明
function fn(){
    console.log(1);
}
 
//函数在事件中的调用
document.onclick = fn;

2.函数的传参

2.1参数

形参:形式上的参数——给函数声明一个参数;
实参:实际的参数——在函数调用时给形参赋的值

function 函数名(形参1,形参2....)  //在声明函数的小括号里是形参  (形式上的参数)
{
}
函数名(实参1,实参2....);  //在函数调用的小括号里面是实参 (实际参数)

ECMAScript函数的参数跟大多数其他语言不同。ECMAScript函数既不关心传入的参数个数,也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你可以传一个、三个,甚至一个也不传,解释器都不会报错。之所以会这样,主要是因为ECMAScript函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,但函数并不关心这个数组中包含什么。如果数组中什么也没有,那没问题;如果数组的元素超出了要求,那也没问题。事实上,在使用function关键字定义(非箭头)函数时,可以在函数内部访问arguments对象,从中取得传进来的每个参值。

function getSum(num1 ,num2)
 {
      console.log(num1 + num2);
  }
  //1.如果实参和形参个数一样则正常输出结果
  getSum(1,2);
  //2.如果实参个数多余形参个数,会取到形参的个数
  getSum(1,2,3);
  //3.如果实参的个数小于形参的个数  多余的形参定义为undefined 最终结果为NaN(不是数字)
  //形参可以看做不用声明的变量  num2是一个变量,但没有接收值,结果就是undefined
  getSum(1); //NaN
  //建议,尽量让实参个数与形参个数匹配

2.2函数的不定参(可变参)—关键字arguments

function doAdd(num1, num2) {
 if (arguments.length === 1) {
 console.log(num1 + 10);
 } else if (arguments.length === 2) {
 console.log(arguments[0] + num2);
 }
}

在这个doAdd()函数中,同时使用了两个命名参数和arguments对象。命名参数num1保存着与arugments[0]一样的值,因此使用谁都无所谓。(同样,num2也保存着跟arguments[1]一样的值。)

arguments对象的值始终会与对应的命名参数同步。

function doAdd(num1, num2) {
 arguments[1] = 10;
 console.log(arguments[0] + num2);
}

这个doAdd()函数把第二个参数的值重写为10。因为arguments对象的值会自动同步到对应的命名参数,所以修改arguments[1]也会修改num2的值,因此两者的值都是10。但这并不意味着它们都访问同一个内存地址,它们在内存中还是分开的,只不过会保持同步而已。另外还要记住一点:如果只传了一个参数,然后把arguments[1]设置为某个值,那么这个值并不会反映到第二个命名参数。这是因为arguments对象的长度是根据传入的参数个数,而定义函数时给出的命名参数个数确定的。对于命名参数而言,如果调用函数时没有传这个参数,那么它的值就是undefined。这就类似于定义了变量而没有初始化。比如,如果只给doAdd()传了一个参数,那么num2的值就是undefined。严格模式下,arguments会有一些变化。首先,像前面那样给arguments[1]赋值不会再影响num2的值。就算把arguments[1]设置为10,num2的值仍然还是传入的值。其次,在函数中尝试重写arguments对象会导致语法错误。(代码也不会执行。)

3.函数内部

3.1 arguments

arguments对象,它是一个类数组对象,包含调用函数时传入的所有参数。这个对象只有以function关键字定义函数(相对于使用箭头语法创建函数)时才会有。虽然主要用于包含函数参数,但arguments对象其实还有一个callee属性,是一个指向arguments对象所在函数的指针。来看下面这个经典的阶乘函数:

//求阶乘
function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
}

阶乘函数一般定义成递归调用的,就像上面这个例子一样。只要给函数一个名称,而且这个名称不会变,这样定义就没有问题。但是,这个函数要正确执行就必须保证函数名是factorial,从而导致了紧密耦合。使用arguments.callee就可以让函数逻辑与函数名解耦

 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
}

这个重写之后的factorial()函数已经用arguments.callee代替了之前硬编码的factorial。这意味着无论函数叫什么名称,都可以引用正确的函数。

let trueFactorial = factorial;
factorial = function() {
 return 0;
};
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0

这里,trueFactorial变量被赋值为factorial,实际上把同一个函数的指针又保存到了另一个位置。然后,factorial函数又被重写为一个返回0的函数。如果像factorial()最初的版本那样不使用arguments.callee,那么像上面这样调用trueFactorial()就会返回0。不过,通过将函数与名称解耦,trueFactorial()就可以正确计算阶乘,而factorial()则只能返回0。

3.2 this

另一个特殊的对象是this,它在标准函数和箭头函数中有不同的行为。
1.在标准函数中,this引用的是把函数当成方法调用的上下文对象,这时候通常称其为this值(在网页的全局上下文中调用函数时,this指向windows)。来看下面的例子:

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'

定义在全局上下文中的函数sayColor()引用了this对象。这个this到底引用哪个对象必须到函数被调用时才能确定。因此这个值在代码执行的过程中可能会变。如果在全局上下文中调用sayColor(),这结果会输出"red",因为this指向window,而this.color相当于window.color。而在把sayColor()赋值给o之后再调用o.sayColor(),this会指向o,即this.color相当于o.color,所以会显示"blue"。

2. 在箭头函数中,this引用的是定义箭头函数的上下文。下面的例子演示了这一点。在对sayColor()的两次调用中,this引用的都是window对象,因为这个箭头函数是在window上下文中定义的:

window.color = 'red';
let o = {
 color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'

注意: 函数名只是保存指针的变量。因此全局定义的sayColor()函数和o.sayColor()是同一个函数,只不过执行的上下文不同。

3.3 caller

caller这个属性引用的是调用当前函数的函数,或者如果是在全局作
用域中调用的则为null。比如:

function outer() {
 inner();
}
function inner() {
 console.log(inner.caller);
}
outer();

以上代码会显示outer()函数的源代码。这是因为ourter()调用了inner(),inner.caller指向outer()。

3.4 new.target

ECMAScript中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ECMAScript6新增了检测函数是否使用new关键字调用的new.target属性。如果函数是正常调用的,则new.target的值是undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数。

function King() {
 if (!new.target) {
 throw 'King must be instantiated using "new"'
 }
 console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值