定义一个函数:
具名函数
function 函数名(形式参数1,形式参数2){
语句
return 返回值
}
匿名函数
- 上面的具名函数,去掉函数名就是匿名函数
let a = function(x,y){return x+y}- 也叫函数表达式
如果你的函数声明在等号右边,那么你的整个函数作用域就只在等号右边,其他地方你要用这个函数只能用a,但是没有这个等号就成为了全局作用域

箭头函数
let f1 = x => x*x
let f2 = (x,y) => x+y //圆括号不能省
let f3 = (x,y) => {return x+y} //花括号不能省
let f4 = (x,y) => ({name:x, age:y})
//直接返回对象会出错,需要加个圆括号,不加圆括号js会以为是个labal标签

用构造函数
let f = new Function('x','y','return x+y')
- 基本没人用,但是能让你知道函数是谁构造的
- 所有函数都是
Function构造出来的 - 包括
Object、Array、Function也是
- 函数自身(
fn)与函数调用(fn())的区别
let fn = () => console.log('hi')
let fn2 = fn
fn2()
结果:
fn保存了匿名函数的地址- 这个地址被复制给了
fn2 fn2()调用了匿名函数fn和fn2都是匿名函数的引用而已- 真正的函数既不是
fn也不是fn2
函数的要素
- 调用时机
let a = 1
function fn(){
console.log(a)
}
问打印出多少? - 不知,因为没有调用代码
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()
问打印出几? - 2
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2
问打印出几? - 1
let a = 1
function fn(){
setTimeout(() => {
console.log(a)
},0)
}
fn()
a = 2
问打印出多少? - 2
let i = 0
for(i = 0 ; i < 6 ; i++){
setTimeout(() => {
console.log(i)
},0)
}
问打印出多少?
不是0、1、2、3、4、5,而是6个6

for(let i = 0 ; i < 6 ; i++){
setTimeout(() => {
console.log(i)
},0)
}
问打印出多少? 是0,1,2,3,4,5.因为JS在for和let一起用的时候会加东西,每次循环会创建一个i

- 作用域 每个函数都会创建一个作用域
function fn(){
let a = 1
}
console.log(a) //a不存在,只作用于函数内代码里面
问:是不是因为fn没法执行导致? - 就算fn执行了也访问不到作用域里面的a
function fn(){
let a = 1
}
fn()
console.log(a) //a还是不存在,只作用于函数内代码里面
- 全局变量与局部变量:在顶级作用域声明的变量是全局变量,window的属性是全局变量(写在哪里都是全局变量),其他都是局部变量
- 函数可嵌套,作用域也嵌套
function f1(){
let a = 1
function f2(){
let a = 2
console.log(a)
}
console.log(a)
a = 3
f2() // 2
}
f1() // 1
- 作用域规则:如果多个作用域有同名变量
a,那么查找a的声明时,就向上取最近的作用域,简称「就近原则」;查找a的过程与函数执行无关,但a的值与函数执行有关 - 跟函数执行没有关系的叫静态作用域(又名词法作用域);有关的叫动态作用域
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
console.log(a)
}
a = 22
f3() // 22
}
console.log(a)
a = 100
f2()
}
f1()
- 闭包
- JS的函数会就近寻找最近的变量,就叫闭包
- 如果一个函数用到了外部的变量,那么这个函数加这个变量,就叫做闭包;
- 形式参数
- 形式参数的意思就是非实际参数
function add(x,y){
return x+y
} //其中x和y就是形参,因为并不是实际的参数
add(1,2) // 调用add时,1和2是实际参数,会被复制一份,传给x y
- 形参可认为是变量声明
//上面的代码近似等价于下面的代码
function add(){
var x = arguments[0] //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,打印值是打印值,返回值是返回值,不同,这个命令虽然打印了hi,但是返回值是undefined
- 函数执行完了后才会返回
- 只有函数有返回值
- 调用栈
- 什么是调用栈?JS引擎在嗲用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈,等函数执行完了,就会把环境弹(pop)出来,然后ruturn到之前的环境,继续执行后续代码
console.log(1)
console.log('1+2的结果为' + add(1,2))
console.log(2)
- 但是递归很可能把栈压满。递归函数:
- 阶乘
function f(n){
return n !== 1? n*f(n-1) : 1
}
理解递归:

先递进再回归
- 递归函数的调用栈很长,调用栈最长有多长
function computeMaxCallStackSize(){
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
//报错说明 stack overflow 了
return 1;
}
}
// Chrome 12578
// Firefox 26773
// Node 12536
- 爆栈:如果调用栈中压入的帧过多,程序就会崩溃
- 函数提升
- 什么是函数提升:不管你把具名函数声明在哪里,它都会跑到第一行
function fn(){}
- 什么不是函数提升?
let fn = function(){} //这是赋值,右边的匿名函数声明不会提升

let有一个特性,如果这个函数已经被声明了,就不允许再let;但是var可以

- 先声明再使用

arguments(每个函数都有,除了箭头函数)
- 是个包含所有参数的伪数组,
- 如何传
arguments:调用fn即可传arguments;fn(1,2,3)那么arguments就是[1,2,3]伪数组
this(每个函数都有,除了箭头函数)
- 如果不给任何条件,
this默认指向window - 如何传
this:目前可以用fn.call(xxx,1,2,3)传this和arguments,而且xxx会被自动转化成对象(糟粕),xxx是this,[2,3,4]是arguments - 如果传给
this的不是一个对象,this会自动帮你封装成对象
1是对象:

而当前面传入一个'use strict'时,再传入什么就是什么了:

-
this是隐藏参数,arguments是普通参数 -
假设没有this:
let person = {
name: 'frank',
sayHi(){
console.log('你好,我叫' + person.name)
}
}
- 分析:我们可以用直接保存了对象地址的变量获取
'name',把这种办法简称为引用
let sayHi = function(){
console.log('你好,我叫' + person.name)
}
let person = {
name:'frank'
'sayHi': sayHi
}
分析:person如果改名,sayHi函数就挂,sayHi函数甚至有可能在另一个文件里面,所以我们不希望sayHi函数里出现person引用
class Person{
constructor(name){
this.name = name
//这里的this是new强制指定的
}
sayHi(){
console.log(???)
} // 如果对象还没有生成,就没法引用这个对象
}
分析:这里只有类,还没创建对象,故不可能获取对象的引用,那么如何拿到对象的name?因此,需要一种办法拿到对象,这样才能获取对象的name属性
一种土方法:用参数
- 对象
let person = {
name: 'frank'
sayHi(p){
console.log('你好,我叫' + p.name)
}
}
person.sayHi(person)
- 类
class Person{
constructor(name){this.name = name}
sayHi(p){
console.log('你好,我叫' + p.name)
}
}
JS在每个函数里加了this
- 用this获取那个对象
let person = {
name: 'frank'
sayHi(this){
console.log('你好,我叫' + this.name)
}
}
person.sayHi()相当于person.sayHi(person)
然后person被传给this了(person是个地址)这样,每个函数都能用this获取一个为知对象的引用,person.sayHi()会隐式地把person作为this传给sayHi,方便sayHi获取person对应的对象
- 两种调用
- 小白调用法
- person.sayHi(),会自动把person传到函数里,作为this
- 大师调用法:
- person.sayHi.call(person),需要自己手动把person传到函数里,作为this

function add(x,y){
return x+y
}
- 没有用到this
- add.call(undefined,1,2) //3
- 为什么要多写一个undefined?因为第一个参数要作为this,但是代码里没有用this,所以只能用undefined占位,其实用null也可以
Array.prototype.forEach2 = function(fn){
for(let i = 0 ; i < this ; i++){
fn(this[i], i, this)
}
}
this是什么?由于大家使用forEach的时候总是会用arr.forEach2,所以arr就被自动传给forEach2了this一定是数组么?不一定,比如:Array.prototype.forEach2.call({0:'a',1:'b'})
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)
fn.qpply(undefined,[1,2])
- 绑定this
- 使用.bind可以让this不被改变
function f1(p1,p2){
console.log(this,p1,p2)
}
let f2 = f1.bind({name: 'frank'})
//那么f2就是f1绑定了this之后的新函数
f2() //等价于f1.call({name:'frank'})
- .bind还可以绑定其他参数
let f3 = f1.bind({name: 'frank'},'hi')
f3() //等价于f1.call({name:'frank'},hi)
箭头函数
- 没有arguments和this
- 里面的this就是外面的this
console.log(this)//window
let fn = () => console.log(this)
fn()//window
就算你加加call都没有
fn.call({name:'frank'}) //window

立即执行函数
- 原理:
- ES5时代,为了得到局部变量,必须引入一个函数,但是这个函数如果有名字,就得不偿失,于是这个函数必须是匿名函数,声明匿名函数,然后立即加个()执行它,但是JS标准认为这种语法不合法,所以JS程序员寻求各种办法,最终发现,只要在匿名函数面前加个运算符即可,
! ~ () + -都可以,但是这里面有些运算符会往上走,所以建议永远用!来解决,因为感叹号或者圆括号会往上看,前一个表达式的结果可能会影响立即执行函数,最好的方法是前面语句后面加个分号

新版JS创造局部变量

可能出现的bug:

