JavaScript函数
函数的概念
什么是函数
在 JS 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。虽然 for 循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JS 中的函数。
函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。
- 不使用函数的弊端:
- 冗余代码太多
- 需求变更, 需要修改很多的代码
- 使用函数的好处:
- 冗余代码变少了
- 需求变更, 需要修改的代码变少了
- 不使用函数的弊端:
1 | // 不使用函数: |
1 | // 使用函数 |
函数的使用
声明函数
- 函数定义步骤
- 书写函数的固定格式
- 给函数起一个有意义的名称
- 为了提升代码的阅读性
- 函数名称也是标识符的一种, 所以也需要遵守标识符的命名规则和规范
- 确定函数的形参列表
- 看看使用函数的时候是否需要传入一些辅助的数据
- 将需要封装的代码书写到 { } 中
- 确定函数的返回值
- 可以通过 return 数据; 的格式, 将函数中的计算结果返回给函数的调用者
1 | // 声明函数 |
- function 是声明函数的关键字,必须小写
- 由于函数一般是为了实现某个功能才定义的,所以通常我们将函数名命名为动词,比如 getSum
匿名函数
1 | // JavaScript中的函数和数组一样, 都是引用数据类型(对象类型) |
调用函数
1 | // 调用函数 |
- 调用的时候千万不要忘记添加小括号
- 口诀:函数不调用,自己不执行。
- 注意:声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码。
1 | // 函数使用分为两步: 声明函数 和 调用函数 |
函数的封装
函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口
简单理解:封装类似于将电脑配件整合组装到机箱中
案例-求1-100累加和
1 | // 利用函数计算1-100之间的累加和 |
函数的使用小结
- 01 函数是做什么的(作用)?
- 02 声明函数用什么关键词?
- 03 如何调用函数?
- 04 封装是什么意思?
函数的参数
形参和实参
- 在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参。
- 参数的作用 : 在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。
1 | // 1. 函数可以重复相同的代码 |
案例-求任意两个数的和
1 | // 1. 利用函数求任意两个数的和 |
函数参数的传递过程
1 | // 声明函数 |
- 调用的时候实参是传递给形参的
- 形参简单理解为:不用声明的变量
- 实参和形参的多个参数之间用逗号(,)分隔
形参与实参个数不匹配
1 | function sum(num1, num2) { |
1 | // 函数形参实参个数匹配 |
函数形参的默认值
- 在 ES6 之前可以通过逻辑运算符来给形参指定默认值
1 | function getSum(a, b) { |
- 从 ES6 开始, 可以直接在形参后面通过 = 指定默认值
1 | function getSum(a = "指趣学院", b = "知播渔教育") { |
- ES6 开始的默认值还可以从其它的函数中获取
1 | function getSum(a = "指趣学院", b = getDefault()) { // 形参 b 的默认值就是 getDefault 函数调用后的返回值 |
函数作为参数传递
1 | // 将函数作为其他函数的参数 |
函数参数小结
函数可以带参数也可以不带参数
声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
调用函数的时候,函数名括号里面的是实参
多个参数中间用逗号分隔
形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配
函数的返回值
函数返回值
有的时候,我们会希望函数将值返回给调用者,此时通过使用 return 语句就可以实现。
return 语句的语法格式如下:
1 | // 声明函数 |
- 在使用 return 语句时,return 语句后面的代码会停止执行,并返回指定的值
- 如果函数没有 return ,返回的值是 undefined
1 | // 1.函数是做某件事或者实现某种功能 |
return 语句
有的时候,我们会希望函数将值返回给调用者,此时通过使用 return 语句就可以实现。
例如,声明了一个 sum() 函数,该函数的返回值为 666,其代码如下:
1 | // 声明函数 |
1 | // 3. 代码验证 |
案例-求两个数的最大值
1 | // 利用函数 求两个数的最大值 |
案例-求数组中的最大值
1 | // 利用函数求数组 [5,2,99,101,67,77] 中的最大数值。 |
return终止函数
- return 语句之后的代码不被执行。
1 | function add(num1,num2){ |
1 | // 1. return 会终止函数 |
return只能返回一个值
- return 只能返回一个值。如果用逗号隔开多个值,以最后一个为准。
1 | // 2. return 只能返回一个值 |
1 | // 2.一个函数可以有返回值也可以没有返回值 |
函数作为返回值
1 | // 将函数作为其他函数的返回值 |
返回值案例
- 案例:创建一个函数,实现两个数之间的加减乘除运算,并将结果返回
1 | var a = parseFloat(prompt('请输入第一个数')); |
1 | // 3. 我们求任意两个数的加减乘除的结果 |
函数的默认返回值
函数没有 return ,则默认返回 undefined.
函数都是有返回值的
- 如果有return, 则返回 return 后面的值
- 如果没有return ,则返回 undefined
1 | // 4. 我们的函数如果有return 则返回的是 return 后面的值,如果函数没有 return 则返回undefined |
break/continue/return
- break :结束当前的循环体(如 for、while)
- continue :跳出本次循环,继续执行下次循环(如 for、while)
- return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码
1 | // 4.return的作用和break相似, 所以return后面不能编写任何语句(永远执行不到) |
其他案例
写一个函数,用户输入任意两个数字的任意算术运算(简单的计算器小功能),并能弹出运算后的结果。
写一个函数,用户输入任意两个数字的最大值,并能出弹运算后的结果。
写一个函数,用户输入任意三个不同数字的最大值,并能弹出运算后的结果。
写一个函数,用户输入一个数判断是否是素数,并返弹出回值(又叫质数,只能被1和自身整数的数)
arguments的使用
console.log()
1 | // 1. 因为console.log();也是通过()来调用的, 所以log也是一个函数 |
arguments
当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。
arguments 展示形式是一个伪数组,因此可以进行遍历。
伪数组具有以下特点:
- 具有 length 属性
按索引方式储存数据
- 不具有数组的 push , pop 等数组方法
1 | // arguments 的使用 只有函数才有 arguments 对象,而且是每个函数都内置好了这个arguments |
案例-求任意个数的最大值
1 | function maxValue() { |
1 | // 利用函数求任意个数的最大值 |
扩展运算符
- 扩展运算符在等号左边, 将剩余的数据打包到一个新的数组中,只能写在最后
1 | let [a, ...b] = [1, 3, 5]; // a = 1; b = [3, 5]; |
- 扩展运算符在等号右边, 将数组中的数据解开
1 | let arr1 = [1, 3, 5]; |
- 扩展运算符在函数的形参列表中的作用
- 将传递给函数的所有实参打包到一个数组中
- 和在等号左边一样, 也只能写在形参列表的最后,否则会报错
1 | function getSum(...values) { |
1 | function getSum(a, ...values) { |
函数案例
函数封装-翻转数组
1 | // 利用函数翻转任意数组 reverse 翻转 |
函数封装-对数组冒泡排序
1 | // 利用函数冒泡排序 sort 排序 |
判断闰年
- 要求:输入一个年份,判断是否是闰年(闰年:能被4整除并且不能被100整数,或者能被400整除)
1 | // 利用函数判断闰年 |
函数调用函数
函数互相调用
函数可以调用另外一个函数.
因为每个函数都是独立的代码块,用于完成特殊任务,因此经常会用到函数相互调用的情况。
1 | function fn1() { |
案例-判断2月的天数
- 如果是闰年,则 2 月份是 29 天, 如果是平年,则 2 月份是 28 天
1 | // 定义判断是否为闰年的函数 |
函数的声明方式
命名函数
- 利用函数关键字 function 自定义函数方式。
1 | // 声明定义方式 |
- 因为有名字,所以也被称为命名函数
- 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
1 | // 1. 利用函数关键字自定义函数(命名函数) |
匿名函数
- 利用函数表达式方式的写法如下:
1 | // 这是函数表达式写法,匿名函数后面跟分号结束 |
- 因为函数没有名字,所以也被称为匿名函数
- 这个 fn 里面存储的是一个函数
- 函数表达式方式原理跟声明变量方式是一致的
- 函数调用的代码必须写到函数体后面,否则会报错,因为 undefined 不是函数,不能加括号调用执行
1 | // 2. 函数表达式(匿名函数) |
箭头函数
什么是箭头函数?
- 箭头函数是 ES6 中新增的一种定义函数的格式
- 目的: 就是为了简化定义函数的代码
在 ES6 之前如何定义函数
1 | function 函数名称(形参列表){ |
- 从 ES6 开始如何定义函数
1 | let 函数名称 = (形参列表) =>{ |
1 | // function say() { |
- 箭头函数的注意点
- 在箭头函数中如果只有一个形参, 那么()可以省略
- 在箭头函数中如果{}中只有一句代码, 那么{}也可以省略
1 | // ES6之前的函数定义 |
递归函数
- 什么是递归函数?
- 递归函数就是在函数中自己调用自己, 我们就称之为递归函数
- 递归函数在一定程度上可以实现循环的功能
1 | /* |
1 | // 使用递归函数实现 |
- 递归函数的注意点
- 每次调用递归函数都会开辟一块新的存储空间, 所以性能不是很好
立即执行函数
1 | <body> |
JavaScript作用域
作用域概述
- 通常来说, —段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用增强了程序的可靠性,减少了名字冲突。
作用域的分类
在 JavaScript 中 { } 外面的作用域, 我们称之为全局作用域
在 JavaScript 中函数后面 { } 中的的作用域, 我们称之为”局部作用域”
在 ES6 中只要 { } 没有和函数结合在一起, 那么应该 “块级作用域”
块级作用域和局部作用域区别
- 在块级作用域中通过 var 定义的变量是全局变量
- 在局部作用域中通过 var 定义的变量是局部变量
无论是在块级作用域还是在局部作用域, 省略变量前面的 let 或者 var 就会变成一个全局变量
在 JavaScript 中,根据作用域的不同,变量可以分为两种:
- 全局变量
- 局部变量
1 | { |
1 | { |
1 | function test() { |
1 | if(true){ |
1 | function test() { |
变量
- 在 JavaScript 中定义变量有两种方式
- ES6 之前:
var 变量名称;
- ES6 开始:
let 变量名称;
- ES6 之前:
- 两种定义变量方式的区别
1 | // 2.1 是否能够定义同名变量 |
1 | // 2.2是否能够先使用后定义 |
1 | // 2.3是否能被{}限制作用域 |
全局变量
- 在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
- 全局变量在代码的任何位置都可以使用
- 特殊情况下,在函数内不使用 var/let 关键字声明的变量也是全局变量(不建议使用)
1 | // 1.JavaScript作用域 : 就是代码名字(变量)在某个范围内起作用和效果 目的是为了提高程序的可靠性更重要的是减少命名冲突 |
局部变量
- 在局部作用域下声明的变最叫做局部变量(在函数内部定义的变量)
- 局部变量只能在该函数内部使用
- 函数的形参实际上就是局部变量
1 | // 4. 局部作用域(函数作用域) 在函数内部就是局部作用域 这个代码的名字只在函数内部起效果和作用 |
局部/全局变量的区别
- 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只在函数内部使用, 当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁, 因此更节省内存空间
1 | // 2. 局部变量 在局部作用域下的变量 后者在函数内部的变量就是 局部变量 |
1 | // ES5 中没有块级作用域 js的作用域: 全局作用域 局部作用域 |
作用域案例
1 | // 1.找出下列哪些是全局变量,哪些是局部变量 |
1 | // 2.找出下列哪些是全局变量,哪些是局部变量 |
1 | // 3.下列代码运行是否会报错 |
1 | // 4.下列代码运行是否会报错 |
1 | // 5.下列代码运行是否会报错 |
作用域链
- 注意点: 初学者在研究”作用域链”的时候最好将 ES6 之前和 ES6 分开研究
- 需要明确:
- 1 ES6 之前定义变量通过 var
- 2 ES6 之前没有块级作用域, 只有全局作用域和局部作用域
- 3 ES6 之前函数大括号外的都是全局作用域
- 4 ES6 之前函数大括号中的都是局部作用域
- ES6 之前作用域链
- 01 全局作用域我们又称之为 0 级作用域
- 02 定义函数开启的作用域就是1级/2级/3级/…作用域
- 03 JavaScript 会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链
- 0 —> 1 —-> 2 —-> 3 —-> 4
- 04 除 0 级作用域以外, 当前作用域级别等于上一级+1
- 变量在作用域链查找规则
- 01 先在当前找, 找到就使用当前作用域找到的
- 02 如果当前作用域中没有找到, 就去上一级作用域中查找
- 03 以此类推直到 0 级为止, 如果 0 级作用域还没找到, 就报错
1 | // 全局作用域 / 0级作用域 |
1 | // 全局作用域 / 0级作用域 |
- 只要是代码,就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数,那么在这个作用域中就又可以诞生—个作用域
- 根据在内部函数可以访问外部函数变量的这种机制, 用链式查找决定哪些数据能被内部函数访问, 就称作作用域链
1 | // 作用域链 : 内部函数访问外部函数的变量,采取的是链式查找的方式来决定取那个值 这种结构我们称为作用域链 就近原则 |
作用域链案例
1 | // 案例1 : 结果是几? |
JavaScript预解析
预解析
- JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步,预解析和代码执行(先定义后执行)。
- 什么是预解析
- 浏览器在执行 JS 代码的时候会分成两部分操作:预解析以及逐行执行代码
- 也就是说浏览器不会直接执行代码, 而是加工处理之后再执行,
- 这个加工处理的过程, 我们就称之为预解析
- 预解析规则
- 将变量声明和函数声明提升到当前作用域最前面
- 将剩余代码按照书写顺序依次放到后面
- 注意点
- 通过 let 定义的变量不会被提升(不会被预解析)
1 | // 1问 |
预解析案例
1 | // 案例1 |
1 | // // 案例2 |
1 | // // 案例3 |
1 | // 案例4 |
函数的 this 指向
this 的指向
- 定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
1 | // 定义函数 |
1 | // 定义函数 |
1 | // 定义函数 |
- 函数在调用时, JavaScript 会 默认给 this 绑定一个值
- this 的 绑定和定义的位置(编写的位置) 没有关系;
- this 的 绑定和调用方式以及调用的位置有关系
- this 是 在运行时被绑定
函数独立调用时的 this
- 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
- 严格模式(“use strict”)下, 独立调用的函数中的 this 指向的是 undefined
1 | // 定义函数 |
1 | // 2.函数定义在对象中, 但是是独立调用,this 指向 Window |
1 | // 3.高阶函数 |
方法通过对象调用的 this
- 也就是它的调用位置中,是通过 某个对象发起的函数调用 。
1 | // 隐式绑定 |
1 | function foo() { |
1 | function foo() { |
通过 new 绑定时的 this
JavaScript 中的函数可以当做一个类的构造函数来使用,也就是使用 new 关键字。
new 创建对象的过程:
创建新的空对象
将 this 指向这个空对象
执行函数体中的代码
没有显示返回非空对象时, 默认返回这个对象
1 | function foo() { |
- new 创建对象的过程的另一种描述:
- 创建一个全新的对象;
- 这个新对象会被执行 prototype 连接;
- 这个新对象会绑定到函数调用的 this 上( this 的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象;
call/apply/bind 显示绑定
call
- 因为明确的绑定了 this 指向的对象,所以称之为 显式绑定 。
- 显示绑定后, this 就会明确的指向绑定的对象
1 | function foo(name, age, height) { |
apply
- 因为明确的绑定了 this 指向的对象,所以称之为 显式绑定 。
- 显示绑定后, this 就会明确的指向绑定的对象
1 | function foo(name, age, height) { |
bind
- 使用 bind 方法, bind() 方法创建一个新的 绑定函数( bound function BF )
- 绑定函数是一个 exotic function object (怪异函数对象 ECMAScript 2015 中的术语)
- 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
1 | function foo(name, age, height, address) { |
1 | function foo(name, age, height, address) { |
内置函数的调用绑定
- 这些内置函数会要求我们传入 另外一个函数
- 我们自己并不会显示的调用这些函数 ,而且 JavaScript 内部或者第三方库内部会帮助我们执行
定时器中的 this
1 | // 1 定时器 |
事件回调中的 this
1 | // 2 按钮的点击监听 |
forEach 中的 this
1 | // 3 forEach forEach的第二个参数为回调函数的this的指向 |
this 绑定的优先级
- 01 默认规则的优先级最低
- 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定 this
- 02 显示绑定优先级高于隐式绑定
- 03 new 绑定优先级高于隐式绑定
- 04 new 绑定优先级高于 bind
- new 绑定和 call 、 apply 是不允许同时使用的,所以不存在谁的优先级更高
- new 绑定可以和 bind 一起使用, new 绑定优先级更高
显式绑定高于隐式绑定
1 | function foo() { |
1 | function foo() { |
new 绑定高于隐式绑定
1 | // 2.new绑定优先级高于隐式绑定 |
new 高于显示绑定
1 | // 3.new/显式 |
this 的特殊情况的绑定
- 情况一:如果在显示绑定中,我们传入一个 null 或者 undefined ,那么这个显示绑定会被忽略,使用默认规则
1 | // 特殊情况一: 显式绑定null/undefined, 那么使用的规则是默认绑定 |
- 情况二:创建一个函数的 间接引用 ,这种情况使用默认绑定规则。
1 | // 2.情况二: 间接函数引用 |
- 感谢你赐予我前进的力量