📔
饥人谷前端体系课程笔记
黑马程序员笔记前端面试押题前端精进
  • 课程大纲
  • Git入门
    • 3. 软件安装详解
    • 4. Mac环境搭建
    • 5. 命令行入门
    • 6. 本地仓库
    • 7. Git远程仓库-GitHub
  • HTML全解
    • 8. HTML概览
    • 9. HTML标签
    • 10. HTML重难点
    • 11. HTML实践 & 手机调试
  • CSS全解
    • 12. CSS基础
    • 13. CSS布局(上)
    • 14. CSS布局(下)
    • 15. CSS定位
    • 16. CSS动画
  • HTTP全解
    • 17. URL 是什么
    • 18. 请求和响应 & Node.js Server
  • JS全解
    • 19. JavaScript概览
    • 20. 内存图与JS世界(精品课)
    • 21. Canvas 实践—画图板
    • 22. JS语法
    • 23. JS数据类型
    • 24. JS 对象
    • 25. JS 对象分类
    • 26. JS 数组
    • 27. JS 函数
    • 28. JS 实战,会动的代码
    • 29. JS运算符
    • 30. JS总结
  • JS编程接口
    • 31. DOM编程
    • 32. 手写DOM库
    • 33. JQuery中的设计模式(上)
    • 34. JQuery中的设计模式(下)
    • 35. DOM事件与事件委托
  • 项目 前端导航站点
    • 前端导航项目笔记
  • 前后分离
    • 40. AJAX的原理
    • 41. 异步与Promise
    • 42. 跨域、CORS、JSONP
    • 43. 静态服务器
    • 44. AJAX实战:Cookie、Session
  • JS进阶MVC
    • 48. MVC(上)
    • 49. MVC(中)
    • 50. MVC(下)
  • 项目构建
    • 51. Webpack(上)
    • 52. Webpack(下)
  • 算法与数据结构
    • 62. 伪代码与流程图
  • React全解
    • 75. React起手式
    • 76. React类组件和函数组件
    • 77. Class组件详解
    • 78. 函数组件
    • 79. Hooks原理解析
    • 80. Hooks各个击破
    • 81. 精通Redux
  • Node.js
    • 84. Node.js技术架构
    • 85. 文件模块
    • 86. 单元测试之文件模块
    • 87. 调试 Node.js 程序
    • 88. 静态服务器
    • 89. 命令行翻译工具
    • 90. 操作数据库
    • 91. 数据库基础知识
    • 92. Stream 流
    • 93. child_process
    • 94. 总结
  • TypeScript
    • 112. 基础
    • 113. 泛型
  • Next.js全解
    • Next.js 上
    • Next.js 下
  • ES6精讲
    • Promise,async/await
  • 大屏可视化笔记
    • 大屏可视化项目
  • SCSS全解
    • SCSS全解
  • 拓展
    • 一次性弄懂性能优化
    • Web性能优化
由 GitBook 提供支持
在本页
  • 27. JS 函数
  • 四种方式定义函数
  • 定义一个函数
  • 函数自身和函数调用
  • 函数的要素
  • 调用时机
  • 作用域
  • 闭包
  • 形式参数
  • 返回值
  • 调用栈
  • 函数提升
  • arguments 和this
  • 箭头函数
  • 里面的this就是外面的this
  • 就算你加call都没有
  • 立即执行函数
  • 原理

这有帮助吗?

  1. JS全解

27. JS 函数

27. JS 函数

四种方式定义函数

函数是对象,函数是一种特殊的对象

定义一个函数

具名函数

function 函数名(形式参数1,形式参数2){
	语句
	return 返回值
}

匿名函数

上面的具名函数,去掉函数名就是匿名函数

let a = function(x,y){return x + y}

也叫函数表达式

以下代码能打印出结果吗?不能会报"Uncaught ReferenceError:fn is not defined at :1:1"

let a = fucntion fn(x,y){return x + y}
fn(1,2)

因为=右边的函数,作用域只在右边,只能通过a来调用,除非把=去掉,变成全局作用域。

箭头函数

let f1 = x => x*x

let f2 = (x,y) => x * y

如果超过两个语句,需要加{}和return

let f2 = (x,y) => {
console.log('hi')
return x*y
}

如果要返回一个对象,对象两边要加(),否则会被当作label对待

let f4 = x => ({name:x})
f4('frank')

构造函数(没人用)

let f = new Function('x','y','return x + y')

基本没人用,但是能让你知道函数是谁构造的

所有函数都是Function构造出来的

包括Object,Array,Function也是。

函数自身和函数调用

fn和fn()的区别,函数和函数调用

函数自身

let fn = () = > console.log('hi')
fn

结果:不会有任何结果,因为fn没有执行。

有圆括号才是调用fn()就能打印出hi

再进一步

let fn = () => console.log('hi')
let fn2 = fn
fn2()

结果

fn保存了匿名函数的地址

这个地址被复制给了fn2

fn2()调用了匿名函数

fn和fn2都是匿名函数的引用而已

真正的函数既不是fn也不是fn2

函数的要素

每个函数都有这些东西

  • 调用时机

  • 作用域

  • 闭包

  • 形式参数

  • 返回值

  • 调用栈

  • 函数提升

  • arguments(除了箭头函数)

  • this(除了箭头函数)

调用时机

调用时机不同,结果不同

例子1

let a = 1
function fn(){
	console.log(a)
}

问:上面这段代码打印出多少?

答:不知,因为没有调用代码

例子2

let a = 1
function fn(){
	console.log(a)
}
fn()

问:上面这段代码打印出多少?

答:1

例子3

let a = 1
function fn(){
	console.log(a)
}
a = 2
fn()

问:上面这段代码打印出多少?

答:2

例子4

let a = 1
function fn(){
	console.log(a)
}
fn()
a = 2

问:上面这段代码打印出多少?

答:1

例子5

let a = 1
function fn(){
	setTimeout(()=>{
		console.log(a)
	},0)
}

fn()
a = 2

问:上面这段代码打印出多少?

答:2

setTimeout是在整个程序执行完之后才会执行,那么等整个程序全部执行完了,a已经是2了。

例子6

let i = 0
for(i = 0; i< 6; i++){
	setTimeout(()=>{
		console.log(i)
	},0)
}

问:上面这段代码打印出多少?

答:不是0,1,2,3,4,5,而是6个6。因为setTimeout会在循环结束之后才会打印出来

例子7

for(let i = 0 ; i < 6; i++){
	setTimeout(()=>{
		console.log(i)
	},0)
}

问:上面这段代码打印出多少?

答:是0,1,2,3,4,5, 因为JS在for和let一起用的时候会加东西,每次循环会多创建一个i。

作用域

每个函数都会默认创建一个作用域。

例子1

function fn(){
	let a = 1
}
console.log(a) //a 不存在

问:是不是因为fn没执行导致的

答:就算fn执行了,也访问不到作用域里面的a

例子2

function fn(){
	let a = 1
}
fn()
console.log(a)// a还是不存在

全部变量V.S局部变量

在顶级作用域声明的变量是全局变量,window的属性是全局变量,其他都是局部变量。

函数可嵌套

作用域也可嵌套

例子3

function f1(){
	let a = 1
	function f2(){
		let a = 2
		console.log(a)
	}
		console.log(a)
		a = 3
		f2()
}
f1() // 1 2

作用域规则

如果多个作用域有同名变量a

  • 那么查找a的声明时,就向上取最近的作用域

  • 简称“就近原则”

  • 查找a的过程与函数执行无关,这种叫做静态作用域,也叫词法作用域(编译原理知识),相反的就叫动态作用域

  • 但a的值与函数执行有关

例子4

function f1(){
	let a = 1
	function f2(){
		let a = 2
		function f3(){
			console.log(a)
		}
			a = 22
			f3()
	}
	console.log(a)
	a = 100
	f2()
}
f1()//1 22

闭包

闭包刚刚讲过了,讲过了吗,

重看例子4

function f1(){
	let a = 1
	function f2(){
		let a = 2
		function f3(){
			console.log(a)
		}
			a = 22
			f3()
	}
	console.log(a)
	a = 100
	f2()
}
f1()

形式参数

形式参数的意思就是非实际参数

function add(x,y){
	return x + y
}
//其中x和y就是形参,因为并不是实际的参数
add(1,2)
//调用add时,1和2是实际参数,会被赋值给x y

形式参数的本质是变量声明

//上面的代码近似等价于下面代码
function add(){
	var x = arguments[0]
	var y = arguments[1]
	return x + y
}

形式参数可多可少,形式参数只是给参数取名字

返回值

每个函数都有返回值

function hi(){
	console.log('hi')
}
hi()
//没写return,所以返回值是undefined
function hi(){
	return console.log('hi')
}
hi()
//返回值为console.log('hi')的值,即undefined

函数执行完了后才会返回

只有函数有返回值

1+2返回值为3

1+2值为3

调用栈

什么是调用栈

JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈。等函数执行完了,就会把环境弹(pop)出来。然后return到之前到环境,继续执行后续代码。

举例

console.log(1)
console.log('1+2的结果为'+add(1,2))
console.log(2)

递归函数

阶乘

function f(n){
	return n!== 1 ? n * f(n-1) : 1
}

理解递归

f(4)
= 4 * f(3)
= 4 * (3 * f(2))
= 4 * (3 * (2 * f(1)))
= 4 * (3 * (2 * (1)))
= 4 * (3 * (2))
= 4 * (6)
= 24
//先递归,再回归

递归函数的调用栈

递归函数的调用栈很长,请画出阶乘6的调用栈

调用栈最长有多少

function computeMaxCallStackSize(){
	try{
		return 1 + computeMaxCallStackSize();
	}catch(e){
		//报错说明 stack overflow了
		return 1
	}
}

Chrome 12578

Firefox 26773

Node 12536

爆栈:如果调用栈中压入的帧过多,程序就会崩溃。

函数提升

什么是函数提升

function fn(){}

不管你把具名函数声明在哪里,它都会跑到第一行

add(1,2)
function add(x,y){
 return x + y
}
>3
let add = 1
function add(){}
>Uncaught SyntaxError: Identifier 'add' has already been declared
//会报错的原因是,add函数会提升,let不允许已经声明过的变量再声明。但是var可以

什么不是函数提升

let fn = function(){}

这是赋值,右边的匿名函数声明不会提升

add(1,2)
let add = function(x,y){return x+y}

arguments 和this

每个函数都有,除了箭头函数

function fn(){
	console.log(arguments)
	console.log(this)
}

箭头函数

没有arguments和this

里面的this就是外面的this

console.log(this)//window
let fn = () => console.log(this)
fn()//window

就算你加call都没有

fn.call({name:'frank'})//window

立即执行函数

只有JS有的变态玩意,现在用得少

原理

ES5时代,为了得到局部变量,必须引入一个函数,但是,这个函数如果有名字,就得不偿失,于是这个函数必须是匿名函数。声明匿名函数,然后立即价格()执行它。但是JS标准认为这种语法不合法,所以JS程序员寻求各种办法,最终发现,只要在匿名函数前面价格运算符即可。!,~,(),+,-都可以,但是这里面有些运算符会往上走,所以方方推荐永远用!来解决。

function (){
	var a = 2
	console.log(a)
}()
//js认为以上的语法是错误的,于是程序员发现了下面代码可以运行

!function (){
	var a = 2
	console.log(a)
}()

//新版的JS如何造局部变量呢
{
 let a = 2
 console.log(2)
}

为什么不要使用括号?

console.log('hi')
(function(){
	var a = 2
	console.log(a)
}())
>Uncaught TypeError: console.log(...)is not a function at....

这段代码是整个JS里面唯一需要加分号的地方,因为不加分号,console.log()return的是undefined,最后就会变成如下形式,所以会报错。

undefined(function(){...})
上一页26. JS 数组下一页28. JS 实战,会动的代码

最后更新于3年前

这有帮助吗?

Screen Shot 2022-01-20 at 12.12.40 AM
Screen Shot 2022-01-20 at 12.04.09 AM