一、定义
1、什么是函数?
函数是一种对象。
2、如何定义一个函数?
具名函数
function 函数名(形参1,形参2){
语句
return 返回值
}
匿名函数
let a = function(x,y)(return x+y)
- 将具名函数去掉函数名即可,也叫函数表达式
- 等号右边的部分叫函数表达式
- 左边的为变量,保存函数的地址
let a = function fn(x,y){return x+y}
- 函数名fn的作用域局限于等号右边,并不是全局
箭头函数
let f1 = x => x*x
let f2 = (x,y) => x*y
let f3 = (x,y) => {
console.log('hello')
return x*y
}
let f4 = x => {name:x} //报错,{}会被当作代码块的符号使用
let f4 = x => ({name:x}) //正确,圆括号的加入使得{name:x}成为一个对象返回给x
构造函数
let f = new Function('x','y','return x+y')
- 基本没人用,但是可以让你我了解到所有函数都是Funtion构造出来的
- 包括Object、Array、Function
3、函数自身&函数调用
let fn = () => console.log('hello')
- 输入fn,没有输出,因为fn是函数本身
- 输入fn(),输出'hello',因为fn()是函数执行/调用
二、函数的要素
函数的要素 = 每个函数都有的东西
1、调用时机
let i = 0
for(i=0;i<6;i++){
setTimeout(() => { //setTimeout是等一会
console.log(i)
},0)
}
打印出6个6,而不是1,2,3,4,5,6;因为setTimeout函数会让console.log()在for循环全部执行完后执行
for(let i=0;i<6;i++){
setTimeout(() => {
console.log(i)
},0)
}
打印出0,1,2,3,4,5,而不是6个6,因为JS会在for、let一起用的时候加东西,每次循环会多创建一个i
2、作用域
全局作用域
- 一个函数中用let、const声明的变量是局部变量,仅在包裹变量的{}中有效
- 顶级作用域中声明的变量是全局变量,比如let a = 1,
- 挂到window上的属性是全局变量,比如window.c = 10,且window.c不需要写在最外边,可以写在任何地方,都有c=2
- 为什么Object可以随意使用,就是因为它是挂在window上的属性,作用域是全局
局部作用域
- 局部作用域可以嵌套
- 如果多个作用域有同名变量a,查找a的声明时,向上取最近的作用域,简称就近原则
- 作用域与函数的执行与否无关,即静态作用域
3、闭包
定义:如果一个函数用到了外部的变量,那么这个函数和这个变量叫做闭包,下面代码中的a和f3组成了闭包
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()
4、形式参数
定义:不是实际参数。
形参的本质就是变量声明
function add(x,y){
return x+y
}
- x,y是形参
- 调用add时,1和2是实际参数,会被复制给x,y
- 上面的代码近似等价以下代码
function add(){
var x = arguments[0]
var y = arguments[1]
return x+y
}
5、返回值
- 每一个函数都有返回值
- 只有函数有返回值,1+2的值为3(1+2不是函数)
- 函数执行完毕后才有返回值
function hi(){console.log('hi')}
hi() //没写return,所以返回值为undefined
function hi(){return console.log('hi')}
hi() //返回值为console.log('hi')的值,即undefined
6、调用栈
定义
JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈。
等函数执行完了,就会把环境弹(pop)出来,然后return到之前的环境,继续执行后续代码。
先压栈,后弹栈。
递归函数(举例阶乘)
function fn(n){
return n === 1 ? 1 : n*f(n-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
先递进,后回归。
递进是压栈的过程,回归是弹栈的过程。
f(4)压栈4次
爆栈
如果调用栈中压入的帧太多,程序就会崩溃
7、函数提升
定义
- 不管你把具名函数声明在哪里,它都会被提升到第一行优先执行
- 使用let声明的匿名函数不存在函数提升,let是赋值语句
8、arguments和this
除了箭头函数,每个函数都有arguments和this。二者都是JS原创的部分,但是并不优秀。
function fn(){
console.log(arguments)
cosole.log(this)
}
如何传arguments?
- 调用fn即可传arguments,它是包含所有参数的伪数组
- fn(1,2,3),则arguments就是伪数组[1,2,3]
如何传this?
- 只能用fn.call()传入this
- 当传入undefined或空时,this会指向window
- 可用fn.call(xxx,1,2,3)同时传this和arguments
- 如果传入的this不是对象,会被自动转化为对象(JS的糟粕)
- 如果xxx是空或者undefined,xxx会变为window
- fn.call(1,2,3,4),1是this,2,3,4则是arguments
如何使传入的this不被自动封装成对象?
function fn(){
'use strict' //添加这行代码
cosole.log(this)
fn.call(1) //返回数字1,而不是对象{1}
(8)、如何理解 this?
假如没有this
let person = {
name:'lu',
sayHi(){
console.log('你好,我叫'+person.name)
}
}
person.name //返回'lu'
- 用直接保存了对象地址的变量person来获取name
- 这种方法叫做引用
以上方法的问题一
let sayHi = function(){
console.log('你好,我叫' + person.name)
}
let person = {
name:'frank',
'sayHi':sayHi
}
- 先定义函数sayHi,再定义对象person
- person如果改名,函数就挂了
- 我们不希望sayHi函数中出现person的引用
以上方法的问题二
class Person{
constructor(name){
this.name = name //这里的this是new强制指定的
}
sayHi(){
console.log(???)
}
}
- 这里只有类,没有创建对象,不可能获取对象的引用
- 那么如何拿到对象的name属性?
所以,如何在不知道对象名的情况下拿到这个对象的引用?
一种比较笨拙的方法
//对象
let person = {
name:'lu',
sayHi(p){
console.log('你好,我叫' + p.name)
}
}
//类
class Person{
consturctor(name){
this.name = name
}
sayHi(p){
console.log('你好,我叫' + p.name)
}
}
Python的解决方法
class Person:
def __init__(self,name):
self.name = name
def sayHi(self):
print('Hi,I am' + self.name)
person = Person('frank')
person.sayHi()
- 每个函数都接受一个额外的self,这个self就是待创建的对象
- person.sayHi() 等价于 person.sayHi(person),person 被传给 self了
- 得到一个未来的对象的引用
JavaScript的解决方法:发明一个关键字this,来获取那个未来的对象
let person = {
name:'frank',
sayHi(){ //关键字this,代表以后你创建的那个对象
console.log('你好,我叫' + this.name)
}
}
- person.sayHi() 相当于 person.sayHi(person),隐式地把 person 作为 this 传给 sayHi()
- person 是个地址,它被传给 this 了
- 这样,每个函数都能用 this 获取一个未知对象的引用了
如何调用?
- 小白调用法:person.sayHi(),自动将person传入函数,作为this
- 大师调用法:person.sayHi.call(person),手动将person传入函数,作为this
默认使用大师调用法,这样可以清晰地看到传入的this是什么,举例:
let person{
name:'lu',
sayHi(){
console.log(this.name)
}
}
person.sayHi.call({name:1}) //这里指定传入的this为{name:1},所以打印出的this.name为1
>1
9、this的两种使用方法
隐式传递
- fn(1,2) //等价于fn.call(undefined,1,2)
- obj.child.fn(1) //等价于obj.child.fn.call(obj.child,1)
显式传递
- fn.call(undefined,1,2) //此时this是undefined,形参是1和2
- fn.apply(undefined,[1,2])
10、绑定this
使用.bind可以让this不被改变
function f1(p1,p2){
console.log(p1,p2)
}
let f2 = f1.bind({name:'lu'}) //f2就是一个将this固定为{name:'lu'}的新f1,等价于f1.call({name:'lu'})
.bind还可以绑定其他函数
let f3 = f1.bind({name:'lu'},'hello') //等价于f1.call({name:'lu'},'hello'),这里的'hello'就是第一个形参p1
11、箭头函数
没有arguments和this,箭头函数自身改变不了this,只拿外面的this
let fn = () => console.(this)
fn()
> Window