天天看点

JS学习笔记(九)函数JS学习笔记(九)函数

JS学习笔记(九)函数

文章目录

  • JS学习笔记(九)函数
    • 一、函数的定义
      • 1.1 函数声明与函数表达式
    • 二、箭头函数
    • 三、参数
    • 四、没有重载
    • 五、默认参数值
      • 5.1 默认参数作用域与暂时性死区
    • 六、参数扩展与收集
      • 6.1 扩展参数
      • 6.2 收集参数
    • 七、函数作为值
    • 八、函数内部
      • 8.1 arguments
      • 8.2 this(重点)
        • 标准函数
        • 箭头函数
      • 8.3 new.target
    • 九、函数属性和方法
    • 十、闭包
      • 10.1 this对象
    • 十一、立即调用的函数表达式

一、函数的定义

  • 利用function关键字的函数声明
function sum(num1,num2) {
    return num1+num2;
}
           
  • 函数表达式(匿名函数)
let sum = function(num1,num2) {
    return num1+num2;
}
           
  • 箭头函数
let sum = (num1,num2) => {
    return num1+num2;
}
           

1.1 函数声明与函数表达式

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

       函数声明会在任何代码执行之前先被读取并添加到执行上下文。称作“函数声明提升”

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

并不是因为用了let,使用var也会碰到同样的问题

二、箭头函数

  • 箭头函数的简洁语法时候嵌入函数的场景,若只有一个参数,可以不用括号
  • 箭头函数不能使用arguments、super 和 new.target,也不能用作构造函数
  • 箭头函数没有prototype属性

虽然箭头函数没有arguments对象,当可以在包装函数中把它提供给箭头函数

function foo() {
    let bar = () => {
        console.log(arguments[0]); //5
    }
    bar();
}

foo(5);
           

三、参数

JS学习笔记(九)函数JS学习笔记(九)函数

       在使用function关键字定义(非箭头)函数时,可以在函数内部访问arguments对象,从中取得传进来的每个参数值,且他的值始终会与对应的命名参数同步

function howManyArgs() {
    console.log(arguments.length);
}

howManyArgs("string",45); //2 
howManyArgs(); //0
howManyArgs(12); //1
           

四、没有重载

ES函数不能重载,因为ES函数没有签名(接收参数的类型和数量),因为参数是由包含零个或多个值的数组表示的。若定义了两个同名函数,后定义的会覆盖先定义的

五、默认参数值

       在使用默认参数时,**arguments对象的值不反应参数的默认值,只反映传给函数的参数。**修改命名参数也不影响arguments对象,他始终以调用函数时传入的值为准

function foo(name='niki') {
    name = 'Louis';
    return `King ${arguments[0]}`
}

console.log(foo()); //undefined
console.log(foo('Elvira')); //King Elvira
           

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

       给多个参数定义默认值实际跟使用let顺序声明变量一样,因为参数是按顺序初始化的,所有后定义默认值的参数可以引用先定义的参数,也遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的

function foo(name='niki',numerals = name) {
    return `King ${name} ${numerals}`
}

console.log(foo()); //King niki niki
           

六、参数扩展与收集

       扩展操作符既可以用于调用函数时传参,也可以用于定义函数

6.1 扩展参数

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

function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
}

console.log(getSum(-1, ...values, 5)); //14
console.log(getSum(...values, ...[5, 6, 7])); //28
           

       因为数组长度已知,所以在使用扩展操作符传参时,并不妨碍在前面或后面再传其他值,包括使用扩展操作符传其他参数

6.2 收集参数

在函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组。收集参数的结果会得到一个Array实例

function getSum(...values) {
    return values.reduce((x,y) => x+y,0);
}

console.log(getSum(1,2,3));
           

因为收集参数的结果可变,所以只能把它当作最后一个参数

//不可以
function(...values,lastvalue) {
    ...
}

//可以
function(firstvalue,...values) {
    ...
}
           

七、函数作为值

因为函数名在ES中就是变量,所以函数可以用在任何可以使用变量的地方。不仅可以把函数作为参数传给另一个函数,还可以在另一个函数中返回另一个函数

八、函数内部

8.1 arguments

       arguments是一个类数组对象,包含调用函数时传入的所有参数。arguments含有callee属性,他是一个指向arguments对象所在函数的指针

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

      这个阶乘呢个函数要正确执行必须保证函数名时factorial,从而导致紧密耦合。使用arguments.callee就可以让函数逻辑与函数名解耦。

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

此时无论函数叫什么名称,都可以引用正确的函数

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

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

8.2 this(重点)

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'
           

箭头函数

箭头函数中,this引用的是定义箭头函数的上下文。

window.color = 'red';
let o ={
    color:'blue'
};

let sayColor = () => console.log(this.color);

sayColor(); //'red'
o.sayColor = sayColor;
o.sayColor(); //'red'
           

       在事件回调或定时回调中调用某个函数时,this的指向可能并非想象的对象,此时将回调函数写成箭头函数就可以解决问题,这是因为箭头函数中的this会保留定义该函数时的上下文:

function King() {
    this.royaltyName = 'Henry';
    //this 引用 King 实例
    setTimeout(() => console.log(this.royaltyName),1000);
}

function Queen() {
    this.royaltyName = 'Niki';
    // this 引用window对象
    setTimeout(function() { console.log(this.royaltyName);},1000);
}
new King(); //Henry
new QUeen();//undefined
           

8.3 new.target

ES6 新增了检测函数是否使用new关键字调用的new.target属性。若正常调用,new.target 为undefined;若使用new关键字,则new.target将引用被调用的构造函数

九、函数属性和方法

       每个函数都有两个属性:length 和 prototype

       prototype时保存引用类型所有实例方法的地方,意味着toString()、valueOf()等方法实际上都保存在prototype上。proyotype 属性不可枚举,用for-in循环不会返回这个属性

       函数都有两个方法:apply() 和 call(),这两个方法都会以指定的this值来调用函数,即会设置调用函数时函数体内this对象的值。

       apply()方法接收两个参数:函数内this的值和一个参数数组。第二个参数可以是Array的实例,但也可以是arguments对象

       call()第一个参数与apply()一样,而剩下的要传给被调用的函数的参数则是逐个传递的。换句话说,通过call()向函数传参时,必须将参数一个一个的列出来。

function sum (num1,num2) {
    return num1 +num2;
}

function callSum(num1,num2) {
    return sum.call(this,num1,num2);
}

function callSum2 (num1,num2) {
    return sum.apply(this,arguments);
}
           

       apply 和 call 真正强大的地方是控制函数调用上下文即函数体内this的能力

window.color = 'red';
let o ={
    color:'blue'
};

function sayColor() { //全局函数
    console.log(this.color);
}

sayColor(); //'red'
sayColor.call(this);  //red
sayColor.call(window);//red
sayColor.call(o);	//blue
           

十、闭包

闭包是指那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的

function createComparisonFunc(propertyName) {
    return function (obj1, obj2) {
        let val1 = obj1[propertyName];
        let val2 = obj2[propertyName];

        if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }
    };
}
           

       内部的匿名函数引用了外部函数的变量propertyName,在这个内部函数返回并在其他地方被使用后,仍然引用着那个变量。

       在调用一个函数时,会为这个函数创建一个执行上下文,并创建一个作用域链。然后用arguments和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数的作用域链上的第二个对象

       函数执行时,每个执行上下文中都会由一个包含变量的对象。全局上下文中的角变量对象,他在代码执行期间始终存在,而函数局部上下文中的叫活动对象。只在函数执行期间存在

function compare(val1,val2) {
    if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }
	}
}
           
JS学习笔记(九)函数JS学习笔记(九)函数
let compare = createComparisonFunc('name');
let result = copmare({name:'niki'},{name:'jhon'});
           
JS学习笔记(九)函数JS学习笔记(九)函数

       在函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中。因此,在createComparisonFunc()中,匿名函数的作用域链中实际上包含createComparisonFunc()的活动对象.createComparisonFunc()的活动对象并不能在他执行后销毁,因为匿名函数的作用域链中仍然有对他的引用。在createComparisonFunc()执行完毕后,其执行上下文的作用域链会被销毁,但他的活动对象仍然会保留在内存中,直到匿名函数被销毁后才会被销毁。

//创建比较函数
let compare = createComparisonFunc('name');
//调用函数
let result = copmare({name:'niki'},{name:'jhon'});
//接触对函数的引用,就可以释放内存了
compare = null;
           

10.1 this对象

若内部函数没有使用箭头函数定义,则this对象会在运行时绑定到执行函数的上下文。

  • 若在全局函数中调用,则this在非严格模式下等于window,严格模式下等于undefined
  • 若作为某个对象的方法调用,则this等于这个对象。
  • 若是匿名函数,其不会绑定到某个对象,this指向window
window.id = 'The window';
let obj = {
    id: 'My obj',
    getId() {
        return function () {
            return this.id;
        }
    }
}
console.log(obj.getId()()); //'The window'
           

       每个函数在调用时都会自动创建两个变量:this和argument。内部函数永远不可能直接访问外部函数的这两个变量。但若吧this保存到闭包可以访问的另一个变量里是可以的。

window.id = 'The window';
let obj = {
    id: 'My obj',
    getId() {
        let that = this;
        return function () {
            return that.id;
        }
    }
}
console.log(obj.getId()()); //'My obj'
           

       在定义匿名函数之前,先把外部函数的this保存到变量that中

十一、立即调用的函数表达式

       立即调用的匿名函数又称作立即调用的函数表达式(IIFE:Immediately invoked function expression),类似于函数声明,被包含在括号中,所以会被解释为函数表达式,紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。

(function() {
    //块级作用域
})
           

继续阅读