V少JS基础班之第五弹
文章目录
- 一、 前言
- 二、本节涉及知识点
- 三、重点内容
- 1- 函数的定义
- 2- 函数的构成
- 1.函数参数详解
- 1) 参数个数不固定
- 2) 默认参数
- 3) arguments 对象(类数组)
- 4)剩余参数(Rest 参数)
- 5)函数参数是按值传递的
- 6)解构参数传递
- 7)参数校验技巧(JavaScript 没有类型限制,需要手动校验)
- 2.函数返回值详解
- 3- 函数的分类
- 1- 函数声明式:
- 2- 函数表达式:
- 3- 箭头函数:
- 4- 构造函数:
- 5- IIFE:
- 6- 生成器函数:
- 7- 异步函数:
- 8- 类方法:
- 9- 匿名函数:
- 4- 函数的调用
- 0- 函数的调用一般有一下几种方式
- 1- 变量提升:
- 2- 构造函数和函数声明式的区别:
一、 前言
第五弹内容是函数。 函数是JavaScript语言中不可或缺的一部分。它没有显得那么难,但是其重要程度无可忽视。js代码中哪哪都有它的身影, 属于那种必学的知识点。那我们就开始今天的函数学习。
来了来了他来了, 终于第五弹函数部分更新了。前段时间确实在处理一个网站没有精力去写文章,如果和市面文章一样拼凑函数的知识点也很快。但是我始终觉得,市面上的函数讲解都不太适合爬虫,要么就是简单的认识函数,要么就是太过偏向js开发。所以一直迟迟未发布第五弹(当然也有自己偷懒的成分,大家自行忽略)。本系列预计是6个月。中间出了点小插曲,专栏完成周期需要适当往后延一延,但是后续更新也会更加紧凑,为了赶上进度,会不定期的多发一弹,缩短更新时间,还望大家多多谅解。
本系列为一周一更,计划历时6个月左右。从JS最基础【变量与作用域】到【异步编程,密码学与混淆】。希望自己能坚持下来, 也希望给准备入行JS逆向的朋友一些帮助, 脸皮厚了一点,明目张胆的要点赞,评论和收藏。也是希望如果本专栏真的对大家有帮助可以点个赞,有建议或者疑惑可以在下方随时问。
先预告一下【V少JS基础班】的全部内容。看着很少,其实,正儿八经细分下来其实挺多的,第一个月的东西也一点不少。
第一个月【变量 、作用域 、BOM 、DOM 、 数据类型 、操作符】
第二个月【函数、闭包、原型链、this】
第三个月【面向对象编程、异步编程、nodejs】
第四个月【密码学、各类加密函数】
第五个月【jsdom、vm2、express】
第六个月【基本请求库、前端知识对接】
==========================================================
二、本节涉及知识点
函数:基础、 构成、 分类、调用、this、 作用域、 特殊函数本章内容:函数的定义(函数的本质是什么)函数的构成(参数,函数体,函数返回)函数的分类(各种函数)函数的调用(函数的调用放假)
==========================================================
三、重点内容
1- 函数的定义
什么是函数: 在 JavaScript 中,函数(Function)是一种可复用的代码块,它用于执行特定任务或计算返回值。
函数可以接收输入(称为“参数”),经过处理后返回一个结果(称为“返回值”)。
口语解释: 函数就是一个固定结构的代码块。构建它以实现指定功能,它提供一个引用方式供项目使用。
最常规的函数:
function demo(){console.log('hello wold')
}
为什么要使用函数?
代码复用:可以将重复的逻辑封装成函数,避免冗余代码。
提高可读性与模块化:函数让程序结构更清晰,有利于分工协作和后期维护。
便于测试与调试:将逻辑封装为函数后,可以单独测试每个函数是否正确。
可以作为参数传递:JavaScript 是函数式语言的一部分,函数可以像数据一样被传来传去。
好!到这里我们先停一停。 我再问一嘴,js中的函数是什么?
ok,这时候大家可能说是一个特殊的代码块。
是的, 但是这不是函数的本质。 在js中,函数的本质是对象。请记住这句话, 所有函数在 JavaScript 中都是对象。并且它们都是可调用的对象。
当然对象是什么后面会讲到。好,那我们现在继续
==========================================================
2- 函数的构成
在编程中,一个函数通常由以下几个部分构成:函数名(用于标识函数)、参数(用于接收输入)、函数体(包含具体的执行语句)以及返回值(用于输出结果)。
function demo(){console.log('hello wold')
}
1.函数参数详解
我们以普通函数为例, 来讲解参数
1) 参数个数不固定
JavaScript 是动态语言,传参个数不匹配不会报错:
function add(a, b) {console.log(a + b);
}add(1); // NaN(b 是 undefined)
add(1, 2, 3); // 3(多余的参数会被忽略)
2) 默认参数
从 ES6 开始,支持为参数设置默认值:
function greet(name = "Guest") {console.log("Hello, " + name);
}greet(); // Hello, Guest
greet("Tom"); // Hello, Tom
3) arguments 对象(类数组)
函数内部可以使用 arguments 获取所有传入的实参,不论是否定义了形参:
function showArguments() {console.log(arguments);
}showArguments(1, 2, 3); // [1, 2, 3],是一个类数组
4)剩余参数(Rest 参数)
用 … 收集多余参数为一个数组,更现代:
function sum(...numbers) {return numbers.reduce((a, b) => a + b, 0);
}console.log(sum(1, 2, 3)); // 6
5)函数参数是按值传递的
原始类型(如 Number, String)是值传递
引用类型(如 Object, Array)是对象引用的地址传递(但本质还是按值)
function change(x) {x = 10;
}
let a = 5;
change(a);
console.log(a); // 5,没变function modify(obj) {obj.name = "Changed";
}
let user = { name: "Tom" };
modify(user);
console.log(user.name); // Changed
6)解构参数传递
你可以在函数参数中直接使用解构:
function display({ name, age }) {console.log(name + " is " + age);
}display({ name: "Alice", age: 30 }); // Alice is 30
7)参数校验技巧(JavaScript 没有类型限制,需要手动校验)
1. 检查参数是否传入
function greet(name) {if (name === undefined) {throw new Error("name is required");}console.log("Hello, " + name);
}
2. 类型判断
function setAge(age) {if (typeof age !== 'number') {throw new TypeError("age must be a number");}
}
3. 使用默认值+校验结合
function register(name = "", age = 0) {if (!name) throw new Error("Name cannot be empty");
}
2.函数返回值详解
在这里我们用普通函数和构造函数做一个比较, 来观察这个返回值。 我们首先看下普通函数。
function normalFunc() {return { name: "普通函数返回值" };
}const result = normalFunc();
console.log(result); // 👉 { name: "普通函数返回值" }
说明: 普通函数中,return 语句决定了返回值。如果没有 return,则返回 undefined。
我们再来看下构造函数:
function Person() {this.name = "构造函数默认返回值";
}const p = new Person();
console.log(p); // 👉 { name: "构造函数默认返回值" }
构造函数中,默认返回的是 this 指向的对象,即新创建的实例对象。
特殊情况对比: 构造函数中主动 return
function Person() {this.name = "被忽略";return { name: "手动返回对象" };
}const p = new Person();
console.log(p); // 👉 { name: "手动返回对象" }
构造函数中,如果返回的是对象,则这个对象会 覆盖默认的 this 返回值。
function Person() {this.name = "正常 this";return 123;
}const p = new Person();
console.log(p); // 👉 { name: "正常 this" }
如果返回的是非对象类型(如数字、字符串、布尔等),则会 忽略 return,仍然返回 this。
这里大家可能会有点疑惑构造函数是什么。 没关系,我们看完函数的分类就会明白。
==========================================================
3- 函数的分类
函数声明:标准函数定义方式,支持提升,常用于基础函数调用和回调。
函数表达式:函数作为变量的值,无法提升,常用于基础函数调用和回调。
箭头函数:简洁的语法,继承 this,适合简化代码,回调处理。
构造函数:用于创建对象实例,用于对象实例化。
IIFE:立即执行的匿名函数,常用于创建私有作用域,用于私有作用域。
生成器函数:带有 yield 的函数,用于生成多个值。常用于控制流程、异步操作。
异步函数:返回 Promise,使异步代码更简洁,常用于简化异步操作。
类方法:类中的方法,作用于类实例,用于面向对象设计。
1- 函数声明式:
函数声明语法或函数声明式,这是最常见的一种定义函数的方式。
function greet() {console.log("Hello, world!");
}
特点:
函数名必须指定。
可以在函数声明之前调用(提升)。
函数声明会被提升到当前作用域的顶部。函数声明会被提升到代码的顶部,可以在定义之前调用。
2- 函数表达式:
函数表达式将函数作为一个值赋给变量。与函数声明式不同,函数表达式在被定义后才能使用。
const myFunction = function() {console.log("Hello");
};
特点:
不提升:函数表达式不会被提升,在定义之前不能调用它。
myFunction(); // 错误:myFunction is not a functionconst myFunction = function() {console.log("Hello");
};
3- 箭头函数:
箭头函数是 ES6 引入的一种更简洁的函数定义方式,它采用了不同的语法,并且与传统函数有一些关键的区别,特别是在 this 的处理上。
const myFunction = () => {console.log("Hello");
};
并且
特性 | 普通函数 | 箭头函数 |
---|---|---|
this 绑定 | 动态,取决于调用者 | 静态,取决于定义时上下文 |
arguments | 有自己的 arguments | 没有自己的 arguments |
使用 new | 可以作为构造函数 | 无法作为构造函数,不能使用 new |
语法 | 较为繁琐,需要 function 关键字 | 语法简洁,常用于回调和匿名函数 |
原型(prototype) | 有自己的 prototype | 没有 prototype,不能作为构造函数 |
yield(生成器) | 支持 | 不支持 |
4- 构造函数:
构造函数用于创建对象实例,使用 function 声明并通过 new 关键字调用。
function Person(name) {this.name = name;
}const john = new Person("John");
console.log(john.name); // "John"
特点:
可使用 new 实例化对象。
内部的 this 指向新创建的对象。
构造函数名称通常首字母大写。
5- IIFE:
IIFE 是立即执行的函数表达式,定义后立即执行,常用于创建私有作用域。
(function() {console.log("IIFE executed");
})();
特点:
定义后立即执行。
不会污染全局命名空间。
常用于模块化或封装代码。
外层括号将其变为表达式,否则语法会被当成函数声明。
6- 生成器函数:
生成器函数可以在执行过程中多次暂停和恢复,使用 function* 定义,使用 yield 暂停。
function* counter() {yield 1;yield 2;yield 3;
}const gen = counter();
console.log(gen.next()); // { value: 1, done: false }
特点:
使用 function* 定义。
使用 yield 暂停执行,配合 .next() 控制流程。
适合用于惰性迭代、异步控制流等场景。
返回一个实现了迭代器协议的对象。
7- 异步函数:
异步函数用于处理异步操作,返回一个 Promise,使用 await 等待异步结果。
async function fetchData() {const data = await fetch("https://example.com");return data;
}
特点:
使用 async 声明。
函数总是返回一个 Promise。
使用 await 可以等待 Promise 结果,避免回调地狱。
可结合 try/catch 捕获异步错误。
8- 类方法:
类方法是定义在类中的函数,用于描述对象的行为。
class Animal {speak() {console.log("Animal speaks");}
}const dog = new Animal();
dog.speak(); // "Animal speaks"
特点:
使用 class 语法定义。
定义在类体中,方法之间不需要逗号。
类方法不能当作构造函数(不能用 new 调用)。
有 prototype 绑定行为,适合面向对象编程。
9- 匿名函数:
匿名函数,就是没有名的函数,一般出现就直接调用。
1. (function(o) {console.log(o);})('hello world');
2. ~(function(o) {console.log(o);return 10})('hello world');
3. +function(o) {console.log(o);}('hello world');
4. -function(o) {console.log(o);}('hello world');
5. !function(o) {console.log(o);}('hello world');
6. void function(o) {console.log(o);}('hello world');
7. (0, function(o) {console.log(o);})('hello world')
8. ((a,b)=>{return a+b})(1, 2)
==========================================================
4- 函数的调用
0- 函数的调用一般有一下几种方式
1- 函数名()
2- 函数名. call(thisArg, param1, param2, ...)
3- 函数名.apply(thisArg, [param1,param2,...])
4- 匿名自执行
5- 对象.方法
调用方式 | 关键语法 | this 指向 |
---|---|---|
普通调用 | fn() | 全局对象 or undefined(严格模式) |
对象方法调用 | obj.fn() | obj |
构造函数调用 | new Fn() | 新建的实例对象 |
显式调用 | fn.call/apply/bind(...) | 显式传入的对象 |
箭头函数 | () => {} | 定义时所在作用域 |
自执行函数 | (function(){})() | 同普通函数调用 |
1- 变量提升:
大家应该对函数有了一个初步的了解了。 但是还是没有细致了解。 我们说函数声明式和函数表达式的区别在于函数的提升。 是什么意思。 首先函数声明式是这样的:
function greet() {console.log("Hello, world!");
}
什么是变量提升:
我们先了解变量提升。
JavaScript在执行代码前,它会先进行一个编译阶段(预处理阶段)。在这个阶段,JS 引擎会扫描整个作用域,提前把所有的变量声明(var)、函数声明(function)提到当前作用域的顶部,这就形成了“变量提升”
变量提升发生在 JavaScript 引擎在执行代码前的编译阶段,会先分析标识符、生成抽象语法树(AST)、创建作用域,并在内存中提前分配变量声明和函数声明的空间。
所以在代码执行之前, 作用域就已经生成了【这里也是个很重要的知识点, JavaScript 中的作用域是在代码编译阶段就被创建的,而不是等到函数执行时】。
ok, 到这里大家就应该明白了, 前面说的。 函数声明式,可以变量提升。 因为在编译时 greet() 就被编译成了function,存储在了作用域中
而在函数表达式中:
const myFunction = function() {console.log("Hello");
};
只有 myFunction 这个变量被声明了。 我们无法去确定他是个函数。 所以说我们不可以在代码执行到表达式之前先进行函数调用。 【这里说明一下是。 我们可以提前调用myFunction这个变量,而且他的返回值会是】
这里说明一下是。 我们可以提前调用myFunction这个变量,
而且他的返回值会是 undefined。 但是我们不可以调用myFunciton()
因为此时的myFunction是个变量,而不是一个函数。
所以需要在赋值操作完成之后才能调用
2- 构造函数和函数声明式的区别:
我们上面看了函数声明式和函数表达式的区别, 主要在变量的提升上。 那我们现在看一看函数表达式和构造函数的区别
函数声明式:
function greet(name) {this.name = name;console.log(this.name);
}
构造函数:
function Person(name) {this.name = name;console.log(this.name);
}
可能上述我们举例,使用函数声明式时不会携带this, 但其实函数声明式在js代码中运用的非常广泛。 类似于这种:
const person = {name: "Alice",sayName: function showName() {console.log(this.name);}
};person.sayName(); // 输出 "Alice"
那除了函数名的命名规范,构造函数和函数声明式就没有区别了吗?其实不然, 他们的区别不在函数的构造如何。而是看怎么调用。 一个函数如果是用于new一个对象, 那他就是个构造函数。 而如果是被对象.调用的方式,那他就不是一个构造函数。
听起来可能会有点绕。 我口语化表达一下就是:“构造函数”不是一个函数的身份,而是一种使用方式。函数声明式是一个普通函数,如果它用于new一个新的对象,那他就能被当成构造函数使用如果调用函数时不是用于new一个对象,那它就不能称之为构造函数。
那构造函数和普通函数的使用差别在什么地方呢。 以下是构造函数和普通函数的区别:
差异点 | 构造函数 (new Func()) | 普通函数 (Func() 或 obj.Func()) |
---|---|---|
调用方式 | 必须通过 new 关键字调用 | 直接调用或作为对象的方法调用 |
this 指向 | 指向新创建的对象 | 取决于调用方式(全局对象 / 严格模式下为 undefined / 调用者对象) |
返回值 | 默认返回 this 指向的对象,除非显式返回对象 | 返回值取决于 return 语句 |
原型绑定 | 自动绑定到 构造函数.prototype 上 | 不会自动挂载到任何原型上 |
是否用作构造器 | 设计用于创建实例对象 | 用于执行某些逻辑,不一定用于创建对象 |
总结:
函数这章太难写了。 写少了没有内容。 写多了又会携带很多
this,上下文,原型链的知识。 在这个边界里反复横跳。
比如: 1- 在参数里我们可以说,除了常规变量我们还能看到call、apply显示传递this参数。 2- 返回值,如果将函数作为返回值,涉及到的闭包知识点3- 还有函数中的作用域&上下文
如果深讲感觉不太好,有点头重脚轻。改了好多次,始终觉得差点意思。
还是决定把高阶函数放在闭包和this章节中讲解。函数章节就先发布,后续再持续优化吧。