函数定义
-
函数声明
function sum(a,b) { return a+b; }
-
函数表达式
let sum = function(a,b){ return a+b; };
与变量初始化一样,函数末尾有分号,该函数可以通过变量sum引用
-
箭头函数
let sum = (a,b)=>{ return a+b; };
箭头函数不能使用arguments、super和new.target也不能用作构造函数。此外,箭头函数也没有prototype属性
-
Function构造函数声明(不推荐)
let sum2 = new Function('a','b','return a+b');
Function构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体。
函数声明和函数表达式的区别
JavaScript引擎在任何代码执行之前,都会先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那行,才会在上下文中生成函数定义。
//正常执行
console.log(add(1,2));//3
//js引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部
function add(a,b){
return a+b;
}
//报错:Uncaught ReferenceError: can't access lexical declaration 'getSum' before //initialization
console.log(getSum(1,2));
//出错是因为函数定义包含在一个变量初始化语句中,而不是函数声明中,意味着代码如果没有执行到下面这行,那么执行上下文中就没有函数的定义。(即使把let关键字替换为var还是会有一样的问题)
let getSum=function(a,b){
return a+b;
}
函数的参数
ECMAScript函数即不关心传入的参数个数,也不关心这些参数的数据类型。定义函数要接收两个参数,并不意味着调用该函数时就需要传递两个参数,可以传一个、三个、甚至一个也不传,解释器都不会报错。
ECMAScript函数的参数在内部表现为一个数组,函数被调用时总会接收一个数组,函数不需要关心这个数组中包含什么。在使用function关键字定义(非箭头)函数时,可在函数内部访问arguments对象,从中获得传入的每个参数值。
function sayHi(name,message) {
console.log('hi '+name+','+message);
}
sayHi('petter','在干嘛呢?');//hi petter,在干嘛呢?
//不声明参数时可以通过arguments数组取得相同的值
function sayHi(){
console.log('hi '+arguments[0]+','+arguments[1]);
}
sayHi('petter','在干嘛呢?');//hi petter,在干嘛呢?
默认参数值
函数可以在声明时设置默认参数值,参数值可以为原始值、对象,也可以为函数调用的返回值。
let index=0;
let numbers=['11','22','33','44'];
function getNumber(){
return numbers[index++];
}
//原始值和函数调用返回值作为默认值
function makeNum(name="petter",number = getNumber()){
console.log(name+number);
}
makeNum();//petter11
makeNum('herry','66')//herry66
makeNum();//petter22
makeNum();//petter33
函数的默认值参数只有在函数被调用时才会求值,不会在函数定义时求值。而且,计算默认值的函数只有在调用函数但未传相应参数时才会被调用。
参数扩展与收集
扩展参数
可迭代对象应用扩展操作符,可将其作为一个参数传入
let values = [1,2,3,4];
function countArguments(){
console.log(arguments.length);
}
countArguments(...values)//4
//因为数组的长度已知,所以在使用扩展操作符传参的时候,并不妨碍在其前面或者后面再传其他的值,包括使用扩展操作符传其他参数。
countArguments(...values,5)//5
countArguments(-1,...values)//5
countArguments(-1,...values,5)//6
countArguments(...values,...[5,6,7])//7
收集参数
在函数定义时,可以使用扩展操作符把不同长度的独立参数组合成一个数组。
//求和
function getSum(...values) {
return values.reduce((x,y)=>x+y,0);
}
getSum(...[1,2,3])//6
函数定义时,因为收集参数结果可变,所以只能把它作为最后一个参数
//报错:Uncaught SyntaxError: Rest parameter must be last formal parameter
function fun1(...values,lastValue){
}
//ok
function fun1(firstValue,...values){
console.log(values);
}
fun1()//[]
fun1(1)//[]
fun1(1,2)//[2]
fun1(1,2,3)//[2, 3]
函数作为值传递
函数名在ECMASript中就是变量,所以可以把函数作为参数传给另一个函数,而且也可以在一个函数中返回另一个函数。
let onChange = (value)=>{
console.log(value,'onChange');
}
let handleChange=(fun)=>{
fun('11111111111');
}
handleChange(onChange); // 11111111111 onChange
函数内部
arguments
arguments除了用于包含函数参数外还有一个callee属性,它是一个指向arguments对象所在函数的指针。在递归函数中使用该属性可以让函数逻辑和函数名解耦。
例如一个阶乘的函数
function factorial(n){
if(n<=1) {
return 1;
}
//以前正常的写法
//return n * factorial(n-1);
//可以使用arguments.callee代替硬编码factorial,这样无论函数名叫什么都可以正常的调用函数
return n * arguments.callee(n-1);
}
下面的情况可以具体看出arguments.callee的用法
let trueFactorial = factorial;
factorial = function(){
return 0}
console.log(trueFactorial(3)); //6
console.log(factorial(3)); //0
把factorial函数指针赋值给trueFactorial,实际上同一个函数的指针又保存在了另一个位置,然后把factorial函数重写了返回0,。如果不使用arguments.callee,调用trueFactorial函数始终返回0。如果使用arguments.callee属性把函数名称解耦,则trueFactorial函数就可以正确计算阶乘,而factorial只能返回0。
this
普通函数
函数在执行时,会在函数体内部自动生成一个this指针。谁直接调用产生这个this指针的函数,this就指向谁。
window.color = 'red';
obj = {
color: 'blue',
};
function sayColor() {
console.log(this);
console.log(this.color);
}
sayColor(); //window对象 //red
window.sayColor(); //window对象 //red
obj.sayColor = sayColor;
obj.sayColor(); //obj对象 // blue
window.obj.sayColor(); //obj对象 // blue
- sayColor是定义在全局上下文中的,直接调用该函数,this则指向window,可以看到调用sayColor()和调用window.sayColor()是一样的。
- 把sayColor赋值给obj之后再调用obj.sayColor(),则此时的this指向了直接调用它的obj对象,所以this.color为blue。
- window.obj.sayColor()因为直接调用sayColor()函数的还是obj所以this指向obj对象。
箭头函数
在箭头函数中,this引用的是定义箭头函数的上下文。也就是说,箭头函数内部的this指向是固定的,普通函数的this指向是可变的。
window.color = 'red';
obj = {
color: 'blue',
sayColor: ()=>{
console.log(this);
console.log(this.color);
},
fun: function(){
console.log(this);
console.log(this.color);
}
};
obj.sayColor(); //window对象 red
obj.fun(); //obj对象 blue
obj.sayColor()方法是一个箭头函数,this指向全局对象,这不是我们预期的结果。这是因为对象不构成单独的作用域,导致箭头函数定义时的作用域为全局作用域。
JavaScript引擎的处理方法是,先在全局空间生成这个箭头函数,然后复制给obj.sayColor,这导致箭头函数内部的this指向全局对象,上面的代码实际上等同于下面的代码
window.color = 'red';
window.sayColor = ()=>{
console.log(this);
console.log(this.color);
}
obj = {
color: 'blue',
sayColor:window.sayColor,
fun: function(){
console.log(this);
console.log(this.color);
}
};
obj.sayColor(); //window对象 red
obj.fun(); //obj对象 blue
由于上面的原因,对象属性建议使用传统的写法定义,不要使用箭头函数定义。
关于this指针指向更加深入的理解可以参考链接:https://www.cnblogs.com/zjjDaily/p/9482958.html
事件回调函数和定时回调函数
在事件回调或者定时回调时,使用普通的函数this往往指向window对象这样并非我们想要的结果,箭头函数可以解决这个问题,因为箭头函数的this会保留定义该函数时的上下文。
window.color = 'red';
function fun1() {
this.color = 'blue';
//箭头函数
setTimeout(()=>{
console.log(this.color);
},1000);
}
function fun2(){
this.color = 'yellow';
//普通函数
setTimeout(function(){
console.log(this.color);
},1000);
}
new fun1(); //blue
new fun2(); //red
caller
函数对象上有个属性:caller,这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null。
function outer(){
console.log(outer.caller);
inner();
}
function inner() {
// 使用arguments.callee代替函数名降低耦合度
// console.log(arguments.callee.caller);
console.log(inner.caller);
}
outer();
//null
// outer(){
// console.log(outer.caller);
// inner();
new.target
ECMAScript中的函数可以作为构造函数实例化一个对象,也可以作为普通函数被直接调用。es6新增了new.target属性来判断是否使用了new关键字调用函数。如果普通函数调用则new.target的值为undefined,如果使用new关键字调用的,则new.target将引用被调用的构造函数。
function Fun() {
if(new.target) {
console.log("构造函数的方式")
console.log(new.target);
}else {
console.log("普通函数调用")
}
}
Fun();
new Fun();
// 普通函数调用
// 构造函数的方式
// ƒ Fun() {
// if(new.target) {
// console.log("构造函数的方式")
// console.log(new.target);
// }else {
// console.log("普通函数调用")
// }
// }
函数的方法
apply()、call()、bind()方法的使用。
apply()和call()这两个方法都可以改变函数中this的指向。
apply()方法接受两个参数:函数内this指向的对象和一个参数数组。第二个参数可以是Array的实例也可以是arguments对象。
window.color = 'red';
let obj = {
color:'blue',
}
function getColor(num1,num2) {
console.log(this);
console.log(this.color);
console.log(`num1 ${
num1},num2 ${
num2}`);
}
getColor.apply(this,[2,3]); //window对象 //red //num1 2,num2 3
getColor.apply(window,[2,3]) //window对象 //red //num1 2,num2 3
getColor.apply(obj,[2,3]); //obj对象 //blue //num1 2,num2 3
第一个参数就是改变函数中this指向的对象,因为在全局作用域中所以传递的this和window所表示的对象相同,color都为red,最后一个把obj作为this指向的对象传递,所以getColor函数中的this就指向了obj对象,color为blue。另外传递的参数也是一个数组。
call()方法和apply()的作用一样,只是传递参数的形式不同。第一个参数跟apply()一样,也是this值,而剩下的要传给被调用函数的参数则是逐个传递的(参数一个一个的列出来)。
例如:
window.color = 'red';
let obj = {
color:'blue',
}
function getColor(num1,num2) {
console.log(this);
console.log(this.color);
console.log(`num1 ${
num1},num2 ${
num2}`);
}
getColor.call(this,2,3); //window对象 //red //num1 2,num2 3
getColor.call(window,2,3) //window对象 //red //num1 2,num2 3
getColor.call(obj,2,3); //obj对象 //blue //num1 2,num2 3
call()和apply()的好处是可以将任意对象设置为任意函数的作用域
bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象。
window.color = 'red';
let obj = {
color:'blue',
}
function getColor(num1,num2) {
console.log(this);
console.log(this.color);
console.log(`num1 ${
num1},num2 ${
num2}`);
}
let objSayColor = getColor.bind(obj,1,2);
objSayColor(); //obj对象 //blue //num1 1,num2 2