JS 函数进阶

函数

函数定义构成

1.函数名称标识符。
函数名称是函数声明语句的必须部分。
2.一对圆括号。
包含0个或多个由逗号隔开的标识符,这些为函数的形参。
3.一对花括号。
其中包含0条或多条JavaScript语句。这些语句构成了函数体:一旦调用函数,就会执行这些语句。
函数只会执行到函数体中的return语句处,其后面的语句不再执行。

函数定义方式

1.函数声明语法
function add(num1, num2) {
  			return num1 + num2;
}
2.函数表达式
let sub = function (num1, num2) {
  	return num1 - num2;
}

若使用函数表达式创建的函数,函数标识符只能在该函数的作用域内使用,因此若使用函数标识符调用函数,则会报错:该函数标识符未被定义。若在函数内部输出该函数标识符,则会返回该函数代码。
※ 函数声明与函数表达式的区别
函数声明会将函数提升,函数表达式不能将函数提升,提升的是变量,因此,若用函数表达式定义函数时,要先定义才能调用。

3.箭头函数
let mul = (mun1, num2) => num1 * num2;
let mul = (mun1, num2) => {
  			return num1 * num2;
};
// 当函数体只有一句return语句时,可以省略花括号。

let f1 = () => {
  	console.log("arrow function");
};
let f2 = x => x ** 2;
// 当函数有多个形参时,要用圆括号将其括起来。
let xx = ((x) => x ** x)(4);
// 箭头函数也可以立即调用,先将圆括号将函数括起来,后面在跟一个圆括号将实参进行传递。
4.Function构造函数

这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。

let sum = new Function(
    "num1",
    "num2",
    "let result=num1+num2; return result;"
  );

※ 不推荐使用这种语法来定义函数,因为这段代码会被解释两次:第一次是将它当作常规ECMAScript 代码,第二次是解释传给构造函数的字符串。这显然会影响性能。

Arguments对象

一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素。
要确定接收到的参数个数,可以访问 arguments.length 属性。

  1. 函数表达式内部支持arguments 对象,而箭头函数内部不支持arguments对象。
  2. Arguments包含剩余参数,若剩余参数没有接受,则是一个空数组。

参数默认值

Es6中增加了参数默认值,可以在定义函数的时候对形参进行默认值的赋值,当没有实参进行传递或者传递的实参类型为undefined或实参是undefined时,该参数则将为默认值。

function f2(name = "User", age = 0) {
  	console.log(name, age);
}

扩展参数

function sum() {
  let r = 0;
  for (let i = 0; i < arguments.length; i++) {
    r += arguments[i];}
  return r;
}
let nums = [1, 2, 3, 4, 5];
// 如果要把nums当做实参传到sum()时,则要用到扩展运算符
console.log(sum(...nums));
// 此时就能把nums中所有的元素传入到函数中。

剩余参数

function sum2(name, ...scores) {
    let r = scores.reduce((x, y) => x + y, 0);
    console.log(`${name}总分为:${r}`);
};
sum2("Tom", 80, 90, 100); // Tom总分为:270。

剩余参数会将实参中剩余的参数全部传入到函数中,且剩余参数只能位于参数列表的末尾。
※ 扩展参数位于函数的实参位置,而剩余参数则位于函数的形参位置。

函数调用

构成函数主体的JavaScript代码在定义之时并不会执行,只有调用该函数时,它们才会执行。

有4种方式来调用JavaScript函数:

  1. 作为函数
  2. 作为方法
  3. 作为构造函数
  4. 通过它们的call()和apply()方法间接调用
方法调用
方法是属于某个特定对象才能调用的函数。
调用上下文

1.关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。
2.This是通过计算得出来的,每个作用域有不同的this

间接调用

使用函数对象的call( )和apply( )方法可以间接调用函数。
第一个参数指定调用上下文(函数内部的this),第二个参数给函数传递参数。

回调函数

被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。

函数属性

length属性

1.在函数体里,arguments.length表示传入函数的实参的个数。
2.而函数本身的length属性是只读的,它代表函数声明的实际参数的数量。

prototype属性

每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”(prototype object)。
每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。

函数方法

call( )和apply( )方法

通过调用方法的形式来间接调用函数。
call()和apply()的第一个实参是要调用函数的主体对象,它是调用上下文,在函数体内通过this来获得对它的引用。

bind( )方法
  1. 将函数绑定至某个对象,且可以绑定参数值。
  2. 当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。
  3. (以函数调用的方式)调用新的函数将会把原始的函数f()当做o的方法来调用。 传入新函数的任何实参都将传入原始函数。
  4. 通过bind()为函数绑定调用上下文后,返回的函数不能通过call和apply修改调用上下文对象。

示例1:

let obj = {
  x: 10,
  show: function (y) {
    let r = "";
    for (let i = 0; i < y; i++) {
      r += this.x + " ";
    }
    console.log(r);
  },
};
obj.show(4); // 10 10 10 10 

let ss = obj.show.bind({ x: 1000 });
ss(3); // 1000 1000 1000

let sum = function (x, y) {
  return x + y;
};
let succ = sum.bind(null, 1);
console.log(succ(2)); // 3

let obj = {};
obj.supAdd = sum.bind(obj, 10); 
/* ƒ (x, y) {
 * return x + y;}
 */

//偏函数,固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接受剩余的参数。
console.log(obj.supAdd(20)); // 30

偏函数的好处:

  1. 通过创建一个名称易懂的独立函数,调用是无需每次传入第一个参数,因为第一个参数通过bind提供了固定值。
  2. 当我们有一个很通用的函数,为了方便提供一个较常用的变体。

示例2:

let obj = { z: 1000 };

function sum(x, y) {
    let r = x + y;
    if (Object.getPrototypeOf(this) != global /*window*/ ) {
        console.log(this);
        r += this.z;
    };
    return r;
};
obj.sum = sum;
console.log(obj.sum(20, 30)); // 1050
console.log(obj.sum.call({ z: 1 }, 2, 3)); // 6
// 当函数没有用bind方法进行绑定时,可以通过call方法对调用上下文对象进行修改

obj.sum = sum.bind(obj);
console.log(obj.sum(100, 200)); // 1300
console.log(obj.sum.apply({ z: 1 }, [50, 60])); // 1110
// 当函数用bind方法绑定后,apply方法将不能修改调用上下文的属性值

函数闭包

JavaScript采用词法作用域(lexical scoping)。

  1. 函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
  2. 为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链


函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包(closure)”。

  1. 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
var  scope  =  "globle scope";

function  checkscope()  {  
    var  scope  =  "local scope";  
    function  f()  {     return  scope;   }  
    return  f();
}
console.log(checkscope()); // local scope
var  scope  =  "globle scope";

function  checkscope()  {  
    var  scope  =  "local scope";  
    function  f()  {     return  scope;   }  
    return  f;
}
var  ff  =  checkscope();
console.log(ff()); // local scope

它们可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。

闭包原理

  1. 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收。
  2. 每次调用JavaScript函数的时候,都会为之创建一个新的对象(活动对象Activation
    Object)用来保存局部变量,把这个对象添加至作用域链中。
  3. 当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。
    1. 如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。
    2. 如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。
  4. 如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。
    1. 它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值