JavaScript 函数是一种特殊的对象。
1. 定义函数
1.1 具名函数
语法:
function 函数名(形式参数1,形式参数2,...,形式参数 n){
语句
return 返回值
}
举例:
function fn(x,y){
return (x+y)
}
1.2 匿名函数
去掉具名函数中的函数名,就是匿名函数。 语法:
let a = function (形式参数1,形式参数2,...,形式参数 n){
语句
return 返回值
}
举例:
let a = function { // 变量 a 只是用来存放这个匿名函数的地址,该函数的作用域只存在于等于号的右边,在其他地方调用该函数会报错该函数未定义
return (x+y)
}
1.3 箭头函数
let f1 = x => x*x
// 一个形式参数,对应一个值
let f2 = (x,y) => x+y
// 如果形式参数有两个或者多个,要用括号把形式参数包起来
let f3 = (x,y) => {return x+y}
// return 语句要用花括号包起来
let f4 = (x,y) => {
cosole.log('hi')
return x*y
}
// 如果有多个输出语句,要用花括号包起来,最后要用 return
let f5 = (x,y) => ({name:x, age:y})
// 如果返回的是对象,要用括号把对象包起来
注意:箭头函数没有 arguments 和 this。
1.4 构造函数
语法:
let fn = new Function('形式参数1','形式参数2',...,形式参数 n, 'return 语句')
举例:
let fn = new Function('x','y','return x+y')
2. 函数的要素
函数的要素有: 调用时机,作用域,闭包,形式参数,返回值,调用栈,函数提升,arguments 和 this。
2.1 调用时机
函数调用的时间不同,得到的结果也会不同。
例子1:
let a = 1
function fn(){
console.log(a)
}
fn() // 得到的值是 1
例子2:
let a = 1
function fn(){
console.log(a)
}
a = 2
fn() // 得到的值是 2,因为在函数 fn 被调用之前, a 的值已经变成了 2
例子3:
let a = 1
function fn(){
console.log(a)
}
fn() // 得到的值是 1,因为函数 fn 被调用的时候,a 的值是 1
a = 2
例子4:
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn() // 打印出 2, 因为 setTimeout 的执行是等其他语句运行完以后再执行的
a = 2
例子5:
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
/* 打印出了 6 次 6 ,因为 i 的值只有一个, setTimeout 等其他语句运行完再执行,
所以 for 循环已经结束了,这时候的 i 是 6, 所以 console.log(i) 会打印出6,
因为 for 循环循环了 6 次,所以打印了 6 次*/
例子6:
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
/* 打印出了 0,1,2,3,4,5
因为 for 和 let 一起使用的时候,每次循环都会多创建一个 i ,
因此 i 有 5 个值,每次循环得到的 i 的值都不一样,因此能打印出 0,1,2,3,4,5 */
2.2 作用域
每个函数都会默认创建一个作用域。 在顶级作用域声明的变量是全局变量,window 的属性是全局变量。
例子1:
function fn(){
let a = 1
}
fn()
console.log(a) // a 不存在,因为 a 的作用域是在函数里,函数外没有对 a 进行声明
例子2:
function f1(){
let a = 1
function f2(){
let a = 2
console.log(a) // 在函数 f2 里面, a = 2
}
console.log(a) // 在函数 f1 里面, a = 1
a = 3
f2() // a = 2
}
f1() // a = 1
2.3 闭包
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。
2.4 形式参数
形式参数就是非实际参数,可以认为形式参数就是变量声明。
function add(x, y){ // x,y 是形式参数
return x+y
}
// 以上代码可以等价于以下代码
function add(){
var x = arguments[0] // 形式参数其实就是变量声明
var y = arguments[1]
return x+y
}
2.5 返回值
- 每个函数都有返回值
- 没写 return, 返回值就是 undefined
- 函数执行完才会有返回值
- 只有函数有返回值
2.6 调用栈
什么是调用栈?
JS 引擎在调用一个函数前, 需要把函数所在的环境 push 到一个数组里,这个数组就叫做调用栈,等函数调用完以后,就把这个环境 pop 弹出来,然后 return 到之前的环境,然后继续执行后面的代码。
2.7 函数提升
不管把具名函数声明在哪里,它都会跑到取去第一行,类似于变量提升。
2.8 arguments
当我们在 JS 中在调用一个函数的时候,我们经常会给这个函数传递一些参数,JS 把传入到这个函数的全部参数存储在一个叫做 arguments 的东西里面, arguments 是一个伪数组。
function fn(){ // 声明一个函数
console.log(arguments) // 打印 arguments
}
fn(1,2,3,4) // 给这个函数传入 4 个参数,这就是传 arguments 的方法
Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] // 得到包含这 4 个参数的伪数组 arguments
0: 1
1: 2
2: 3
3: 4
callee: ƒ fn()
length: 4
Symbol(Symbol.iterator): ƒ values()
__proto__: Object
2.9 this
如果不给任何的条件,this 默认指向 window。
利用函数 fn.call(xxx,1,2,3) 传 this 和 arguments , xxx 会被传为 this , 1,2,3 会被传为 arguments
function fn(){ // 声明一个函数
console.log(this); // 打印 this
console.log(arguments) // 打印 arguments
}
fn.call(1,2,3,4) // 给这个函数传入 4 个参数
Number {1} // 1 被传为 this,并且 JS 会将这个 1 变成一个对象
__proto__: Number
[[PrimitiveValue]]: 1
Arguments(3) [2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] // 2,3,4 被传为 arguments
0: 2
1: 3
2: 4
callee: ƒ fn()
length: 3
Symbol(Symbol.iterator): ƒ values()
__proto__: Object
JS 在每个函数里面都加了 this
let person = {
name: 'pan',
sayHi(this){
console.log(`你好,我叫` + this.name)
}
}
// 当这个函数被调用的时候
person.sayHi() 相当于 person.sayHi(person)
// 但是,JS 将这个形参 person 传给了 this ,(this = person) 所以相当于
person.sayHi(this)
// 但是这个 this 是隐藏的,所以相当于
person.sayHi() 就会和函数里面的 this 相对应
// person.sayHi() 会隐式地把 person 作为 this 传给 sayHi ,方便 sayHi 获取 person 对应的对象
this 的两种调用方法
let person = {
name: 'pan',
sayHi(){
console.log(`你好,我叫` + this.name)
}
}
person.sayHi() // 你好,我叫pan
// 这是第一种方法,直接调用,那么 this 就是相当于 person
person.sayHi.call({name:'Eric'}) // 你好,我叫Eric
// 第二种方法,使用 call 指定 this,可以选择传什么给 this