JavaScript函数

函数

函数是专门用于封装代码的,函数是一段可以随时被反复执行的代码块

函数的格式
function 函数名称(形参列表){
    被封装的代码;
}
不使用函数的弊端
  • 冗余代码太多
  • 需求变更, 需要修改很多的代码
使用函数的好处
  • 冗余代码变少了
  • 需求变更, 需要修改的代码变少了
函数定义的步骤
  • 书写函数的固定格式

  • 给函数起一个有意义的名称

    • 为了提升代码的阅读性
    • 函数名称也是标识符的一种, 所以也需要遵守标识符的命名规则和规范
  • 确定函数的形参列表

    • 看看使用函数的时候是否需要传入一些辅助的数据
  • 将需要封装的代码拷贝到 {}

  • 确定函数的返回值

    • 可以通过return 数据的格式, 将函数中的计算结果返回给函数的调用者
function getSum(a, b){   // a = num1, b = num2;
    let res = a + b;     // let res = 10 + 20; let res = 30;
    return res;          // 将res返回给函数的调用者
}

let num1 = 10;
let num2 = 20;
let result = getSum(num1, num2); // let result = res; let result = 30;
console.log(result);   //  30
函数定义的方式
// 在ES6之前函数的定义
// 方式一
function 函数名称(形参列表){
    需要封装的代码;
}

// 方式二
let 函数名称 = function(形参列表){
    需要封装的代码;
}
函数的注意点
  • 一个函数可以有形参也可以没有形参(零个或多个)
  • 一个函数可以有返回值也可以没有返回值
  • 函数没有通过return明确返回值, 默认返回undefined
  • return的作用和break相似, 所以return后面不能编写任何语句(永远执行不到)
  • 调用函数时实参的个数和形参的个数可以不相同
  • JavaScript中的函数和数组一样, 都是引用数据类型(对象类型),所以也可以保存到一个变量中
function getSum(a,b) {
    console.log(a, b);
    return a + b;
}
getSum(10,20);   // 10 20
getSum(10);      // 10 undefined
getSum(10,20,30) // 10 20

let say = function () {
    console.log("hello world");
}
say();   // hello world
函数的arguments
  • arguments的作用:保存所有传递给函数的实参
  • 每个函数中都有一个叫做arguments的东西
  • arguments其实是一个伪数组
function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; i++){
        let num = arguments[i];
        sum += num;
    }
    return sum;
}
let res = getSum(10, 20, 30, 40);   // 100
console.log(res);
函数扩展运算符
  • 扩展运算符在函数形参列表中的作用:将传递给函数的所有实参打包到一个数组中
  • 和在等号左边一样, 也只能写在形参列表的最后
function getSum(...values) {
    let sum = 0;
    for (let i = 0; i < values.length; i++){
        let num = values[i];
        sum += num;
    }
    return sum;
}
let res = getSum(10, 20, 30, 40);
console.log(res);

function getSum(a, ...values) {
// function getSum(...values, a) {
    console.log(a);
    console.log(values);
}
getSum(10, 20 , 30);
函数形参默认值
  • 在ES6之前,可以通过逻辑运算符来给形参指定默认值
function getSum(a, b) {
    a = a || 123;
    b = b || 'abc';
    console.log(a, b);
}
getSum();            // a = 123, b = 'abc'
getSum(456, "def");  // a = 456, b = 'def'
  • 从ES6开始,可以直接在形参后面通过=指定默认值
function getSum(a = 123,b = 'abc') {
  console.log(a, b);
}
getSum();            // a = 123, b = 'abc'
getSum(456, "def");  // a = 456, b = 'def'
  • 从ES6开始的默认值还可以从其它的函数中获取
function getSum(a = 123,b = getDefault()) {
  console.log(a, b);
}
function getDefault() {
    return 'abc'
}
getSum();            // a = 123, b = 'abc'
getSum(456, "def");  // a = 456, b = 'def'
函数作为参数和返回值
  • 函数作为参数
let say = function () {
    console.log("hello world");
}
function test(fn) { // let fn = say;
    fn();
}
test(say);
  • 函数作为返回值
function test() {
// 在其它编程语言中函数是不可以嵌套定义的
// 在JavaScript中函数是可以嵌套定义的
    let say = function () {
        console.log("hello world");
    }
    return say;
}
let fn = test(); // let fn = say;
fn();
匿名函数
  • 匿名函数就是没有名称的函数
  • 匿名函数不能够只定义不使用
// 有名函数
function say() {
    console.log("hello");
}
let say = function() {
    console.log("hello");
}


// 匿名函数
function() {
    console.log("world");
}
匿名函数的应用场景
  • 作为其他函数的参数
function test(fn) { 
    fn();
}
test(function () {
    console.log("hello world");
});
  • 作为其他函数的返回值
function test() {
    return function () {
        console.log("hello world");
    };
}
let fn = test();
fn();
  • 作为一个立即执行的函数
// 如果想让匿名函数立即执行, 那么必须使用()将函数的定义包裹起来才可以
(function () {
    console.log("hello world");
})();
箭头函数
  • 箭头函数是ES6中新增的一种定义函数的格式
  • 目的是为了简化定义函数的代码
// 箭头函数的定义格式
let 函数名称 = (形参列表) => {
    需要封装的代码;
}

function say() {
     console.log("hello world");
}
say();

let say = () => {
    console.log("hello world");
}
say();
箭头函数的注意点
  • 在箭头函数中如果只有一个形参, 那么()可以省略
// 以下两个函数等同
let say = (name) => {
    console.log("hello  " + name);
}

let say = name => {
    console.log("hello  " + name);
}
  • 在箭头函数中如果{}中只有一句代码, 那么{}也可以省略
// 以下两个函数等同
let say = (name) => {
    console.log("hello  " + name);
}

let say = name => console.log("hello  " + name);
递归函数
  • 递归函数就是在函数中自己调用自己
  • 递归函数在一定程度上可以实现循环的功能
  • 每次调用递归函数都会开辟一块新的存储空间,所以性能不是很好
  • 由于其性能不是很好,所以在企业开发中运用的并不多
function login() {
    // 1.接收用户输入的密码
    let pwd = prompt("请输入密码");
    // 2.判断密码是否正确
    if(pwd !== "123456"){
        login();
    }
    // 3.输出欢迎回来
    alert("欢迎回来");
}
login();
函数中变量作用域
  • 在JavaScript中{}外面的作用域称为全局作用域
  • 在JavaScript中函数后面{}中的的作用域称之为局部作用域
  • 在ES6中只要{}没有和函数结合在一起,那么称为块级作用域
  • 在ES6之前没有块级作用域
常见块级作用域
{
    // 块级作用域
}
if(false){
    // 块级作用域
}
while (false){
    // 块级作用域
}
for(;;){
    // 块级作用域
}
do{
    // 块级作用域
}while (false);
switch () {
    // 块级作用域
}
常见局部作用域
function functionName() {
    // 局部作用域
}
块级作用域和局部作用域区别
  • 在块级作用域中通过var定义的变量是全局变量
{
    var num = 123;   // 全局变量
}
console.log(num);     // 123

if(true){
    var num = 666;    // 全局变量
}
console.log(num);     // 666
  • 在块级作用域中通过let定义的变量是局部变量
{
    let num = 123;  // 局部变量
}
console.log(num);  //报错
  • 在局部作用域中通过varlet 定义的变量是局部变量
function test() {
    var value = 666; // 局部变量
    let num = 123;   // 局部变量
}
console.log(value);   // 报错
console.log(num);     // 报错
  • 无论是在块级作用域还是在局部作用域,省略变量前面的 let 或者 var 就会变成一个全局变量(不推荐使用)
function test() {
    num = 123;   // 全局变量
}  
test();
console.log(num);
  • 在不同的作用域范围内,是可以出现同名的变量的
{
    var num = 123;
    {
        var num = 456;
    }
}
console.log(num);  // 456


{
    let num = 123;
    {
        let num = 456;
    }
}
  • 只要出现了let,在相同的作用域内,就不能出现同名的变量
{
    let num = 123;
    var num = 456; // 会报错
}

{
    var num = 123;
    let num = 456; // 会报错
}
作用域链
ES6之前的作用域链
  • 全局作用域我们又称之为0级作用域
  • 定义函数开启的作用域就是1级/2级/3级/...作用域
  • JavaScript会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链
  • 除0级作用域以外, 当前作用域级别等于上一级+1
// 全局作用域 / 0级作用域
var num = 123;
function demo() {
    // 1级作用域
    // var num = 456;
    function test() {
        // 2级作用域
        // var num = 789;
        console.log(num);
    }
    test();
}
ES6作用域链
  • 全局作用域我们又称之为0级作用域
  • 定义函数或者代码块都会开启的作用域就是1级/2级/3级/...作用域
  • JavaScript会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链
  • 除0级作用域以外, 当前作用域级别等于上一级+1
// 全局作用域 / 0级作用域
// let num = 123;
{
    // 1级作用域
    // let num = 456;
    function test() {
        // 2级作用域
        // let num = 789
        console.log(num)
    }
    test();
}
变量在作用域链查找规则
  • 先在当前找, 找到就使用当前作用域找到的
  • 如果当前作用域中没有找到, 就去上一级作用域中查找
  • 以此类推直到0级为止,如果0级作用域还没找到,就报错
函数预解析
什么是预解析

浏览器在执行JS代码的时候会分成两部分操作:预解析以及逐行执行代码也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,这个加工处理的过程, 我们就称之为预解析

预解析规则
  • 将变量声明和函数声明提升到当前作用域最前面
  • 将剩余代码按照书写顺序依次放到后面
  • 通过let定义的变量不会被提升(不会被预解析)
函数预解析
  • ES6之前定义的函数预解析
// ES6之前的这种定义函数的格式,是会被预解析的,所以可以提前调用
console.log(say)
say();
function say() {
    console.log("hello world");
}
// 预解析之后
/*
function say() {
    console.log("hello world");
}
console.log(say);
say();
*/

// 如果将函数赋值给一个var定义的变量, 那么函数不会被预解析, 只有变量会被预解析
console.log(say);  // undefined
say();             // say is not a function
var say = function() {
    console.log("hello world");
}
// 预解析之后
/*
var say; //undefined
say();
say = function() {
    console.log("hello itzb");
}
*/
  • ES6定义函数的格式
say(); // say is not defined
let say = () => {
    console.log("hello world");
}
预解析练习
// 1.下列程序的执行结果是什么?
var num = 123;
fun();
function fun() {
    console.log(num);   // undefined 
    var num = 666;
}
// 预解析之后
/*
var num;
function fun() {
    var num;
    console.log(num);
    num = 666;
}
num = 123;
fun();
*/

// 2.下列程序的执行结果是什么?
var a = 666;
test();
function test() {
    var b = 777;
    console.log(a);
    console.log(b);
    console.log(c);
    var a = 888;
    let c = 999;
}
// 预解析之后
/*
var a;
function test() {
    var b;
    var a;
    b = 777;
    console.log(a);   // undefined
    console.log(b);   // 777
    console.log(c);   // 报错
    a = 888;
    let c = 999;
}
a = 666;
test();
*/

// 3.下列程序的执行结果是什么?
if(true){
    function demo() {
        console.log("hello demo1111111111");
    }
}else{
    function demo() {
        console.log("hello demo2222222222");
    }
}
// 在高级浏览器中, 不会对{}中定义的函数进行提升
// 只有在低级浏览器中, 才会按照正常的方式解析
// 低级浏览器中预解析之后
/*
function demo() {
    console.log("hello demo1111111111");
}
function demo() {
    console.log("hello demo2222222222");
}
if(true){}else{}
demo();
*/

// 4.下列程序的执行结果是什么?
// 如果变量名称和函数名称同名, 那么函数的优先级高于变量
// 在企业开发中千万不要让变量名称和函数名称重名
console.log(value); // 会输出函数的定义
var value = 123;
function value() {
    console.log("fn value");
}
console.log(value); 
// 预解析之后
/*
function value() {
    console.log("fn value");
}
console.log(value);  // 会输出函数的定义
var value;
value = 123;
console.log(value);  // 会输出123
*/
箭头函数和普通函数的区别
  • 普通函数/方法中的this,谁调用就是this就是谁
  • 箭头函数中的this,是父作用域的this,并不是调用者
  • 箭头函数中的this只看它的父作用域,不能通过bind/apply/call修改
let p = {
        name:'wxm',
        say:function () {
            console.log(this);
        },
        run:() => {
            console.log(this);
        }
    };

    p.say(); // {name: "wxm", say: ƒ, run: ƒ}
    p.run(); // Window

你可能感兴趣的:(JavaScript函数)