Skip to content

函数

函数是 JS 中封装可复用代码块的核心语法,用于实现特定功能、减少代码冗余;它既是可执行的代码块,也是可被实例化的构造函数(在 JavaScript 中,所有函数默认都具备构造函数的能力...箭头函数除外)

定义方式

js
// 函数声明
function 函数名(参数1, 参数2, ...) {
    // 函数体(执行逻辑)
    return 返回值; // 可选,无return则返回undefined
}

// 函数表达式(匿名函数赋值,不支持预解析)
const 函数名 = function(参数1, 参数2, ...) {
    // 函数体
    return 返回值;
};

// 箭头函数(ES6新增,简洁写法,无this/arguments)
const 函数名 = (参数1, 参数2, ...) => {
    // 函数体
    return 返回值;
};
// 简化版:单参数省略(),单行返回省略{}和return
const 函数名 = 参数 => 返回值;

箭头函数

箭头函数对比普通函数有三个区别

  1. 无自身thisthis指向外层作用域的this(而非调用者)

    js
    this.name = '张三'
    const obj = {
        name: '李四',
        // 普通函数,this指向调用它的对象
        fun1: function () {
            console.log(this.name)
        },
        // 箭头函数,this指向外部作用域
        fun2: () => {
            console.log(this.name)
        }
    }
    obj.fun1() // 李四
    obj.fun2() // 张三
  2. arguments对象:需用剩余参数...替代

  3. 不能作为构造函数:不能用new调用

参数

形参:函数定义时括号内的参数(相当于 变量占位符),函数内操作的就是形参

实参:外部调用时实际传递的参数,给形参赋值

JS 允许参数个数不匹配:实参少于形参则形参为undefined,实参多于形参则多余参数可通过arguments获取

默认参数

js
// 默认参数(ES6,形参默认值)
function sayHi(name = '游客') {
    console.log(`你好,${name}!`);
}
sayHi(); // 输出:你好,游客!(未传参用默认值)
sayHi('张三'); // 输出:你好,张三!(传参覆盖默认值)

可变参数

js
// 剩余参数(ES6,接收多传的实参,数组格式)
function sum(...nums) {
    return nums.reduce((total, num) => total + num, 0);
}
console.log(sum(1,2,3)); // 输出:6
console.log(sum(1,2,3,4)); // 输出:10

arguments

js
// arguments(伪数组,接收所有实参,兼容老版本)
function fn() {
    console.log(arguments); // 输出:[1,2,3](伪数组)
    console.log(arguments[0]); // 输出:1
}
fn(1,2,3);

调用

在JS中是存在 Function 的类型的。也就是说,函数是可以被当成参数一样传递的

在调用函数的时候格式一定为 函数名()。如果仅仅写函数名没有任何意义(除非有一个变量接收),因为仅写函数名不会让函数执行,而是会获得函数的对象

函数变量作用域

全局作用域:函数外声明的变量 / 函数,整个脚本可访问

局部作用域:函数内声明的变量,仅函数内可访问

函数内优先访问自身局部变量,找不到则向上找全局变量

new 对象

函数是可以被当成构造函数来 new

js
function Student(name) {
    this.name = name
    // 想要被外部调用就必须挂载到这个函数上
    this.sayHi = function () {
        console.log('你好,我是' + this.name)
    }
}
let stu = new Student("张三")
stu.sayHi()

流程:

  1. 创建空对象 → {}
  2. this 指向这个空对象 → this = {}
  3. 执行函数体 → this = { name: '张三' }
  4. 函数返回基本类型,引擎自动返回新对象 → stu1 = { name: '张三' }
  5. stu1.__proto__ 指向 Student.prototype

返回值

使用 new 函数名() 创建对象,那么如果这个函数返回的数据是基本类型(数字、字符串...)那么返回值会被丢弃。但是如果返回值是一个对象

js
function Student(name) {
    this.name = name
    this.sayHi = function () {
        console.log('你好,我是' + this.name)
    }
    // 返回对象
    return { name: this.name }
}

let stu = new Student('张三')

那么,会把原本 new 的对象覆盖。得到的不是 new 出来的对象而是 return 后面的返回对象

原型链挂载

如果直接把函数挂载到 this 上,那么固然被 new 后函数能够被调用,但是每 new 一个实例都会拷贝一份所有的内部函数。对内存不怎么友好

js
function Student(name) {
    this.name = name
    this.sayHi = function () {
        console.log('你好,我是' + this.name)
    }
}

let stu1 = new Student("张三")
let stu2 = new Student("李四")
console.log(stu1.sayHi == stu2.sayHi); // 结果:false

除了 this 之外还可以通过 原型链 来挂载函数,也是最推荐的做法

js
function Student(name) {
    this.name = name
}

// 通过原型链挂载
Student.prototype.sayHi = function () {
    console.log('你好,我是' + this.name)
}

let stu1 = new Student("张三")
let stu2 = new Student("李四")
console.log(stu1.sayHi == stu2.sayHi); // 结果:true

类:class

它是构造函数的语法糖(也可以说是 new 函数名() 的语法糖)。在关于函数的叙述中,如果把内部函数挂载到原型上得手动去挂载

js
function Student(name) {
    this.name = name
}

// 通过原型链挂载
Student.prototype.sayHi = function () {
    console.log('你好,我是' + this.name)
}

如果函数一多就会很麻烦,而且在后续的继承扩展的时候会很麻烦。而通过类会很方便

js
class Student {
    // 构造函数
    constructor(name) {
        this.name = name
    }
    // 内部方法(自动挂载到原型上)
    sayHi() {
        console.log('你好,我是' + this.name)
    }
}

继承

继承:extendssuper

js
// 学生类(基类)
class Student {
    constructor(name) {
        this.name = name
    }
    sayHi() {
        console.log(`你好,我是${this.name}`)
    }
}
// 班长类(派生类)
class SquadLeader extends Student {
    position = "班长";
    constructor(name) {
        super(name)
    }
    getPosition() {
        return console.log(`职位是${this.position}`)
    }
}

// 班委类(派生类)
class CommitteeMember extends Student {
    position = "班委";
    constructor(name) {
        super(name)
    }
    // 重写父类方法
    sayHi() {
        console.log(`你好,我是${this.position}${this.name}`)
    }
}

子类通过 extends 来继承父类,并通过在构造方法中使用 super() 来调用父类的构造方法。super 必须写在子类构造函数的第一行(先初始化父类 this,才能用 this

子类可重写父类方法,也可通过 super.方法名() 调用父类原方法

继承的本质:子类的 prototype.__proto__ 指向父类的 prototype(原型链继承)

成员分类

实例可用

要求必须把类实例化后才能使用的数据

js
class Student {
    constructor(name) {
        this.name = name
    }
    sayHi() {
        console.log(`你好,我是${this.name}`)
    }
}

let stu = new Student("张三")
stu.sayHi()

类本身可用

通过 static 关键字进行修饰函数/属性

js
class Student {
    // 静态属性
    static position = "学生"
    // 静态方法
    static getPosition() {
        return console.log(`职位是${this.position}`)
    }
}
// 只能通过类名直接调用
console.log(Student.position);
Student.getPosition()

私有成员

私有成员以 # 为开头(ES2022 新增)。它让属性/方法只能在类内部访问,外部/子类都无法操作,解决了属性被外部随意修改的问题

js
class Student {
    #name // 声明私有属性(必须先声明)
    constructor(name) {
        this.#name = name
    }
    // 私有方法
    #sayHi() {
        console.log(`你好,我是${this.#name}`)
    }

    // 提供公共方法间接访问私有成员
    publicSayHi() {
        this.#sayHi()
    }
}

let stu = new Student("张三")
stu.publicSayHi()

私有成员必须先声明再使用(不能直接在构造函数里写 this.#name = name

之前约定以下划线(_)为开头比如 _name 表示私有,外部仍可修改;#name 是语法级私有,强制不可访问

存取器

存取器(getter/setter)用于对属性的读取/赋值做逻辑拦截,让属性访问更安全

js
class Student {
    #name
    // 读取 name 属性时触发
    get name() {
        return this.#name
    }
    // 赋值 name 的时候触发
    set name(newName) {
        this.#name = newName
    }
}

let stu = new Student()
stu.name = "张三"
console.log(stu.name);