《JavsScript高级程序设计》-- 函数

函数定义

  1. 函数声明

    function sum(a,b) {
          
          
        return a+b;
    }
    
  2. 函数表达式

    let sum = function(a,b){
          
          
        return a+b;
    };
    

    与变量初始化一样,函数末尾有分号,该函数可以通过变量sum引用

  3. 箭头函数

    let sum = (a,b)=>{
          
          
        return a+b;
    };
    

    箭头函数不能使用arguments、super和new.target也不能用作构造函数。此外,箭头函数也没有prototype属性

  4. 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	
  1. sayColor是定义在全局上下文中的,直接调用该函数,this则指向window,可以看到调用sayColor()和调用window.sayColor()是一样的。
  2. 把sayColor赋值给obj之后再调用obj.sayColor(),则此时的this指向了直接调用它的obj对象,所以this.color为blue。
  3. 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

猜你喜欢

转载自blog.csdn.net/weixin_43398820/article/details/118282035