JavaScript 函数

175 阅读5分钟

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