函数
函数是专门用于封装代码的,函数是一段可以随时被反复执行的代码块
函数的格式
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); //报错
- 在局部作用域中通过
var
或let
定义的变量是局部变量
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