函数
函数是 JS 中封装可复用代码块的核心语法,用于实现特定功能、减少代码冗余;它既是可执行的代码块,也是可被实例化的构造函数(在 JavaScript 中,所有函数默认都具备构造函数的能力...箭头函数除外)
定义方式
// 函数声明
function 函数名(参数1, 参数2, ...) {
// 函数体(执行逻辑)
return 返回值; // 可选,无return则返回undefined
}
// 函数表达式(匿名函数赋值,不支持预解析)
const 函数名 = function(参数1, 参数2, ...) {
// 函数体
return 返回值;
};
// 箭头函数(ES6新增,简洁写法,无this/arguments)
const 函数名 = (参数1, 参数2, ...) => {
// 函数体
return 返回值;
};
// 简化版:单参数省略(),单行返回省略{}和return
const 函数名 = 参数 => 返回值;箭头函数
箭头函数对比普通函数有三个区别
无自身
this:this指向外层作用域的this(而非调用者)jsthis.name = '张三' const obj = { name: '李四', // 普通函数,this指向调用它的对象 fun1: function () { console.log(this.name) }, // 箭头函数,this指向外部作用域 fun2: () => { console.log(this.name) } } obj.fun1() // 李四 obj.fun2() // 张三无
arguments对象:需用剩余参数...替代不能作为构造函数:不能用
new调用
参数
形参:函数定义时括号内的参数(相当于 变量占位符),函数内操作的就是形参
实参:外部调用时实际传递的参数,给形参赋值
JS 允许参数个数不匹配:实参少于形参则形参为undefined,实参多于形参则多余参数可通过arguments获取
默认参数
// 默认参数(ES6,形参默认值)
function sayHi(name = '游客') {
console.log(`你好,${name}!`);
}
sayHi(); // 输出:你好,游客!(未传参用默认值)
sayHi('张三'); // 输出:你好,张三!(传参覆盖默认值)可变参数
// 剩余参数(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)); // 输出:10arguments
// arguments(伪数组,接收所有实参,兼容老版本)
function fn() {
console.log(arguments); // 输出:[1,2,3](伪数组)
console.log(arguments[0]); // 输出:1
}
fn(1,2,3);调用
在JS中是存在 Function 的类型的。也就是说,函数是可以被当成参数一样传递的
在调用函数的时候格式一定为 函数名()。如果仅仅写函数名没有任何意义(除非有一个变量接收),因为仅写函数名不会让函数执行,而是会获得函数的对象
函数变量作用域
全局作用域:函数外声明的变量 / 函数,整个脚本可访问
局部作用域:函数内声明的变量,仅函数内可访问
函数内优先访问自身局部变量,找不到则向上找全局变量
new 对象
函数是可以被当成构造函数来 new 的
function Student(name) {
this.name = name
// 想要被外部调用就必须挂载到这个函数上
this.sayHi = function () {
console.log('你好,我是' + this.name)
}
}
let stu = new Student("张三")
stu.sayHi()流程:
- 创建空对象 →
{} this指向这个空对象 →this = {}- 执行函数体 →
this = { name: '张三' } - 函数返回基本类型,引擎自动返回新对象 →
stu1 = { name: '张三' } stu1.__proto__指向Student.prototype
返回值
使用 new 函数名() 创建对象,那么如果这个函数返回的数据是基本类型(数字、字符串...)那么返回值会被丢弃。但是如果返回值是一个对象
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 一个实例都会拷贝一份所有的内部函数。对内存不怎么友好
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 之外还可以通过 原型链 来挂载函数,也是最推荐的做法
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 函数名() 的语法糖)。在关于函数的叙述中,如果把内部函数挂载到原型上得手动去挂载
function Student(name) {
this.name = name
}
// 通过原型链挂载
Student.prototype.sayHi = function () {
console.log('你好,我是' + this.name)
}如果函数一多就会很麻烦,而且在后续的继承扩展的时候会很麻烦。而通过类会很方便
class Student {
// 构造函数
constructor(name) {
this.name = name
}
// 内部方法(自动挂载到原型上)
sayHi() {
console.log('你好,我是' + this.name)
}
}继承
继承:extends 、super
// 学生类(基类)
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(原型链继承)
成员分类
实例可用
要求必须把类实例化后才能使用的数据
class Student {
constructor(name) {
this.name = name
}
sayHi() {
console.log(`你好,我是${this.name}`)
}
}
let stu = new Student("张三")
stu.sayHi()类本身可用
通过 static 关键字进行修饰函数/属性
class Student {
// 静态属性
static position = "学生"
// 静态方法
static getPosition() {
return console.log(`职位是${this.position}`)
}
}
// 只能通过类名直接调用
console.log(Student.position);
Student.getPosition()私有成员
私有成员以 # 为开头(ES2022 新增)。它让属性/方法只能在类内部访问,外部/子类都无法操作,解决了属性被外部随意修改的问题
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)用于对属性的读取/赋值做逻辑拦截,让属性访问更安全
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);