基础函数及一些常用的函数方法1

发布时间 2023-12-30 01:37:35作者: 奇迹会出现

js 函数的概念

 

JavaScript 使用关键字 function 定义函数。

 

函数可以通过声明定义,也可以是一个表达式。

js的参数(形参 实参  剩余参数  默认参数  参数规则)及 函数提升

形参和实参如下,下面中的a,b就是形参,形参是虚的,而1,2是实参,实参是实的,是我们自己传入进去的。

 

function foo(a, b) {
    console.log([a, b]);
}

foo(1, 2); // 输出 [1, 2]

 

 

 

1.默认参数

如果调用函数时缺少提供实参,那么形参默认值为 undefined,函数sayHi调用时,没有传入实参而输出确实everyone,所以everyone是它的默认参数,它要在函数中声明

function sayHi(name) {
    name = name || 'everyone';
    
    console.log( 'Hello ' + name + '!');
}

sayHi(); // 输出 'Hello everyone!' 

上面代码虽然简单明了,但缺点在于如果传入的实参对应布尔值为false,就不起作用了。所以需要更精准的话可以用 if 语句或者三元表达式,判断参数是否等于 undefined

// if 语句判断
function sayHi(name) {
    if (name === undefined) {
        name = 'everyone';
    }
    
    console.log( 'Hello ' + name + '!');
}

// 三元表达式判断
function sayHi(name) {
    name =  (name !== undefined) ? name : 'everyone';
    
    console.log( 'Hello ' + name + '!');
}

 

默认值不但可以是一个值,它还可以是任意合法的表达式,甚至是函数调用:

function sayHi(name = 'every'+'one') {
    console.log( 'Hello ' + name + '!');
}

sayHi(); // 输出 'Hello everyone!' 
//--------------------------------------
function foo() {
    console.log('调用foo');
    return 'Tony';
}

function sayHi(name = foo()) {
    console.log( 'Hello ' + name + '!');
}
          
sayHi(); // 输出 '调用foo'
         // 输出 'Hello Tony!' 

sayHi(undefined); // 输出 '调用foo'
                  // 输出 'Hello Tony!' 

sayHi('John'); // 输出 'Hello John!'

 

参数默认值的位置

通常我们给参数设置默认值,是为了调用函数时可以适当省略参数的传入,这里要注意的是,有多个参数时,设置了默认值的参数如果不是放在尾部,实际上它是无法省略的。

function fn(x = 1, y) {
    console.log([x, y]);
}

fn(); // 输出 [1, undefined]
fn(2); // 输出 [2, undefined]
fn(, 2); // 报错,语法错误(这里不支持像数组那样的空槽)
fn(undefined, 2); // 输出 [1, 2] (那还不如传个 1 方便呢!)

上面例子中,给形参 x 设置的默认值就显得没有任何意义了。因此,设置默认值的参数放在尾部是最好的做法:

function fn(x, y = 2) {
    console.log([x, y]);
}

fn(); // 输出 [undefined, 2]
fn(1); // 输出 [1, 2]
fn(1, 1) // 输出 [1, 1]
参数的省略问题

在多个参数设置了默认值的情况下,那么问题又来了,你并不能省略比较靠前的参数,而只给最后的一个参数传入实参。

function fn(x, y = 2, z = 3) {
    console.log([x, y, z]);
}

fn(1, , 10) // 报错

 

前面我们知道,可以通过传入对象的这种方式去避免参数顺序的限制。那参数默认值如何实现呢?用 || 、 if 语句或者三元表达式去判断也是解决办法,但这样就显得有些落后了。接下来要讨论的是另外两种 ES6 中的全新方式。

function fn(obj={}){
                let defaultobj={
                    x:undefined,
                    y:5,
                    z:2
                }
                //object.ass是拷贝是意思object.assign(target,source)
                //把source中的源对象拷贝到target目标对象中
                let result = Object.assign(defaultobj,obj);
                console.log([result.x,result.y,result.z])
            }
            fn({x:4,z:10})//输出[4,5,10]

 

上面的例子之中,defaultObj 中的属性会被 obj 的相同属性覆盖,obj 中如果有其他属性会分配给 defaultObj 。这里用一个变量接收返回的合并对象。

参数默认值和解构赋值结合使用

函数调用时,实参和形参的匹配实际上是一个隐式的赋值过程,所以,参数传递也可以进行解构赋值:

function fn({ x, y = 2, z = 3 }) {
    console.log([x, y, z]);
}

fn({}); // 输出 [undefined, 2, 3]
fn({ x: 1, z: 10 }); // 输出 [1, 2, 10]

在上面的例子中,如果函数调用时不传任何参数,也会产生报错,因为这导致了参数初始化时解构赋值失败,相当于执行了 {x, y = 2, z = 3} = undefined 这样的代码。你可以利用参数默认值的语法,给 {x, y = 2, z = 3} 设置一个默认的解构对象,使得不传参函数也能够顺利执行:

function fn({ x, y = 2, z = 3 } = {}) {
    console.log([x, y, z]);
}

fn(); // 输出 [undefined, 2, 3]

 

参数默认值的作用域与暂时性死区

还有一个小细节,一旦有参数设置了默认值,那么它们会形成自己的作用域(包裹在(...)中),因此不能引用函数体中的变量:

function foo(a = b) {
    let b = 1;
}

foo(); // 报错,b 未定义

 

 它也符合普通作用域的规则:

let b = 2;

function foo(a = b) {
    let b = 1;
    return a;
}

foo(); // 2

 

 上面的例子中,形参a先获取到了全局变量b的值。当然,如果形参作用域中存在一个形参 b 的话,它优先获取到的是当前作用域的:

let b = 2;

function foo(b = 3 ,a = b) {
    return a;
}

foo(); // 3

给多个参数设置默认值,它们会按顺序初始化的,遵循“暂时性死区”的规则即前面的参数不能引用后面的参数

function foo(a = b, b = 2) {
    return a + b;
}

foo(); // 报错,b 在初始化之前不能访问

 

 剩余参数

ES6 提供了**剩余参数(rest)**的语法(...变量名),它可以收集函数多余的实参(即没有对应形参的实参),这样就不再需要使用 arguments 对象来获取了。形参使用了 ... 操作符会变成一个数组,多余的实参都会被放进这个数组中

剩余参数基本用法:

function sum(a, ...values) {
 
    for (let val of values) {
        a += val;
    }
    
    return a;
}

sum(0, 1, 2, 3); // 6

上面例子中,在参数初始化时,首先根据参数位置进行匹配,把 0 赋值给 a ,然后剩余的参数 1、2、3 都会被放进数组 values 中。这个东西刚接触的话不是能马上理解,多看几遍发现是a的累加

剩余参数的位置
剩余参数必须是最后一个形参,否则会报错。
// 报错
function fn1(a, ...rest, b) {
    console.log([a, b, rest]);
} 

// 正确写法
function fn2(a, b, ...rest) {
    console.log([a, b, rest]);
}

fn2(1, 2, 3, 4) // 输出 [1, 2, [3, 4]]
展开语法

前面我们知道了如何把多余的参数收集为一个数组,但有时候我们需要做一些相反的事,例如要把一个数组中的元素分别传入给某个函数,而不是传入一个数组,像这样:

function sum(...values) {
    let sum = 0;
    
    for (let val of values) {
        sum += val;
    }
    
    return sum;
}

let arr = [1, 2, 3, 4];

sum(arr); // "01,2,3,4"

上面例子的函数会把所有传进来的数值累加,如果直接传入一个数组,就得不到我们想要的结果。例子中传入一个数组, values 的值会变成 [[1, 2, 3, 4]],导致数组 values 中只有一个元素,而这个元素的类型是数组。那么函数返回值就是数值 0 和数组 [1, 2, 3, 4]相加的结果了,两者各自进行了类型的隐式转换变成字符串,然后再相加,是一个字符串拼接的效果。

比较可行的是借助 apply() 方法

sum.apply(null, arr); // 10

它甚至可以随意搭配常规值使用,没有前后位置限制,还可以同时传入多个可迭代对象:

sum(-1, ...arr); // 9
sum(...arr, 5); // 15
sum(-1, ...arr, 5); // 14
sum(-1, ...arr, ...[5, 6, 7]); // 27

 

 函数提升

函数提升只针对具名函数,而对于赋值的匿名函数,并不会存在函数提升。

console.log(a);    // f a()
console.log(b);    //undefined     

function a(){
    console.log('hello')
}

var b=function(){
    console.log('world')
}

变量提升与函数提升的优先级

即函数提升只会提升函数声明(函数提升是可以直接在函数声明之前调用该函数,并能成功执行它),而不会提升函数表达式(函数表达式就可以看作成变量提升)。


console.log(foo1); // [Function: foo1]
foo1(); // foo1
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
function foo1 () {
    console.log("foo1");
};
var foo2 = function () {
    console.log("foo2");
};

函数提升优先级高于变量提升,且不会被同名变量声明覆盖,但是会被变量赋值后覆盖。而且存在同名函数与同名变量时,优先执行函数。


console.log(foo);
function foo(){
    console.log("foo");
}

var foo = 1;