函数是对象
定义函数的4种方式
具名函数
// 函数名为fn
function fn (x, y) {
return x + y
}
fn() // 调用函数
匿名函数
// 将函数的地址复制给a, 右边的也叫函数表达式
let a = function (x, y) {
return x + y
}
a() // 调用函数
箭头函数
let f1 = () => console.log("hello") // 无参数
f1()
let f2 = x => x * x // 一个参数, 省略了return
f2(10)
let f3 = (x, y) => {return x + y} // 两个参数就要加(), 有return或2个以上语句就要加{}
f3(2, 3)
let f4 = x => ({name: x}) // {}优先解释为代码块,返回对象要加()
构造函数
let fn = new Function('x', 'y', 'return x + y')
// 所有函数都由Function构造出来
调用时机
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)
}
// 打印出6个6
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 打印出0,1,2,3,4,5
// JS在for和let一起用时会加东西,每次循环都保留那个i,再新创建一个i
作用域
let 的作用域只在{}里
function fn(){
let a = 1
}
fn()
console.log(a) // 访问不到a
在顶级作用域声明的变量为全局变量
window的属性为全局变量
其他为局部变量
给window加属性,可以在任何位置
let b = 1
function f2() {
window.c = 2 // 给window添加属性
}
f2() // 必须执行,才能声明c
function f1() {
console.log(c)
}
f1()
函数嵌套
function f1(){
let a = 1
function f2(){
let a = 2
console.log(a) // // step2: a = 2
}
console.log(a) // step1: a = 1
a = 3
f2()
}
f1()
// 输出结果为 1, 2
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
console.log(a)
}
a = 22
f3() // step2: a = 22
}
console.log(a) // step1: a = 1
a = 100
f2()
}
f1()
// 输出结果为: 1, 22
作用域规则
- 如果多个作用域有同名变量a, 查找a的声明时,向上取最近的作用域
- 简称“就近原则”
- 查找a的过程与函数执行无关
- 但a的值与函数执行有关
- 与函数执行无关的作用域叫静态作用域(词法作用域)
闭包 closure**
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
形参和返回值
形参 parameter 实参 argument
形参只是声明
function add(x) {
return x + arguments[1]
}
add(1, 2)
返回值
- 只有函数才有返回值
- 且每个函数都有返回值,没写return则返回undefined
function xxx() {
return console.log("hi") // 返回值是undefined
}
调用栈
JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈。
等函数执行完毕,就会把环境pop出来,回到之前的环境,继续执行后续代码
递归:先递进后回归 递进是压栈,回归是弹栈
函数提升
function fn(){}
不管把具名函数声明在哪里,都会跑到第一行
let fn = function(){}
用匿名函数赋值变量不会函数提升
let不允许重复声明
arguments 和 this
JS三座大山
- 闭包
- this
- AJAX
翻过三座大山才算入门
arguments是包含所有参数的伪数组
调用fn就传入arguments
不给条件,this默认指向window
用call传入this,如果传入的不是对象,会被自动转为对象
‘use strict’ 阻止转换
调用对象里的函数,this默认传的是对象本身
可以使用call显示的传入this
call第一个参数是this, 之后的参数都是arguments
总结:
- 在 new fn() 调用中,fn 里的 this 指向新生成的对象,这是 new 决定的
- 在 fn() 调用中, this 默认指向 window,这是浏览器决定的
- 在 obj.fn() 调用中, this 默认指向 obj,这是 JS 的隐式传 this
- 在 fn.call(xxx) 调用中,this 就是 xxx,这是开发者通过 call 显式指定的 this
- 在 arrow() 调用中,arrow 里面的 this 就是 arrow 外面的 this,因为箭头函数里面没有自己的 this
- 在 arrow.call(xxx) 调用中,arrow 里面的 this 还是 arrow 外面的 this,因为箭头函数里面没有自己的 this
以后都使用call调用函数
fn.call(undefined, 1, 2, 3)
fn.apply(undefined, [1, 2, 3])
绑定this
使用.bind让this不被改变
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name:'xxx'})
f2() // 等价于 f1.call({name:'xxx'})
// 也可以绑定参数
let f3 = f1.bind({name:'xxx'}, 1, 2}
箭头函数没有arguments和this
立即执行函数
立即执行函数用来造局部变量
用let可以轻松制造局部变量