JS函数
函数其实也是一种对象
1.定义一个函数
具名函数
有具体名字的函数
function 函数名(形参1,形参2 ...){
语句...
return 返回值
}
// 特殊情况
let a = function fn(x,y){return x + y;}
fn(1,3); // fn is not defined 报错
// 具名函数生命在'='的右边时,作用域仅在'='右边,别的地方使用只能用 a 变量 a(1,3)
匿名函数
function(形参1,形参2 ...){ // 就是具名函数去掉函数名
语句...
return 返回值
}
let a = fuction(x,y){return x + y;} // 也叫函数表达式
箭头函数
let f1 = x => x * x;
let f2 = (x,y) => x + y; // 多个参数()不能省略
let f3 = (x,y) => {return x + y}; // 写了return 不能省略{}
let f4 = (x,y) => ({name:x,age:y}); // 直接返回对象会出错,需要加个圆括号
使用构造函数创建一个函数
let f = new Function('x','y','return x + y');
// 基本没人用,但是能让你知道函数是由谁构造出来的,说有函数都是Function构造出来的,包括Object,Array,Function也是
2.函数的要素
每个函数都有的东西
- 调用时机
- 作用域
- 闭包
- 形式参数
- 返回值
- 调用栈
- 函数提升
- arguments(除了箭头函数)
- this (除了箭头函数)
调用时机
JS在执行代函数的时候,由于变量的值会改变,所以在不用的时间,不同位置执行函数结果可能会不一样
作用域
每个函数都会创建一个作用域
function fn(){
let a = 1;
}
console.log(a); // a 不存在
全局变量与局部变量
function fn(){
let a = 1; // 在函数中或者在{}中声明的变量就是局部变量
}
<script>
let a = 1; // 在顶级作用域声明的变量是全局变量
function fn(){
window.c = 1; // 挂在window对象上的属性也是全局变量
}
</script>
函数作用域的嵌套
function f1(){
let a = 1;
function f2(){
let a = 2;
consoloe.log(a);
}
console.log(a);
a = 3;
f2();
}
f1(); // 2 1
作用域规则
如果多个作用域有同名变量a
- 那么查找a的声明时,就向上取最近的作用域
- 简称「就近原则」
- 查找a的过程与函数执行无关
- 但a的值与函数执行有关
闭包
function f1(){
let a = 1;
function f2(){
let a = 2;
function f3(){
console.log(a); // 这里的a用到了外部作用域的变量,形成闭包
}
a = 22;
f3();
}
console.log(a);
a = 100;
f2()
}
f1();
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
形式参数
形式参数的意思就是非实际参数
function add(x,y){
return x + y;
}
// 其中x和y就是形参,因为并不是实际的参数
add(1,2);
// 调用add时,1和2 是实际参数 ,会被赋给x和y
形参可认为是变量声明
上面的代码相当于下面的代码
function add(){
var x = arguments[0];
var y = arguments[1];
return x + y;
}
返回值
每个函数都有返回值
function hi(){
consloe.log('hi'); //不写return 返回值 为 undefined
}
function hi(){
return consloe.log('hi'); //返回值为console.log('hi')的返回值undefined
}
函数执行完成以后才会返回,只有函数有返回值
调用栈
什么是调用栈呢?
JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈,等函数执行完了,就会把环境弹(pop)出来,然后return到之前的环境,继续执行后续代码
递归函数的使用
求n的阶乘
function f(n){
return n !== 1 ? n * f(n -1) : 1;
}
f(4);
// 相当于
= 4 * f(3)
= 4 * (3 * f(2))
= 4 * (3 * 2 * f(1))
= 4 * (3 * (2))
= 4 * (6)
= 24
递归函数的调用栈
- Chrome 11000 - 12000 之间
- Firefox 26773
- Node 12536
爆栈:调用栈中压入的栈过多,程序就会崩溃
函数提升
不管你把具名函数声明在哪里,它都会跑到第一行
let a = function(){ // 函数表达式不会被函数提升
console.log('hi');
}
this关键字
function fn(){ console.log(this); // 这里this是window对象 }
function fn(){ console.log(this); } fn.call(1); // 通过call方法可以改变this的值为所传参数,如果参数不是对象,会自动被封装成对象,undefined会变成window对象,可以使用'use strict'严格模式来阻止这种行为 fn.call(1,2,3);// 此时1为this,2,3为arguments
###### this原理解析
```js
let person = {
name:'ouyang',
sayHi('这里有隐藏的this'){
console.log('你好,我叫' + this.name)
}
}
// person.sayHi()
// 相当于 person.sayHi(person)
// 然后person被传给了 this 了 (person是个地址)
// 这样,每个函数都能用this获取一个未知对象的引用了
3.JS函数的call调用方式
call
let person = {
name:'frank',
sayHi(){
consoloe.log(this.name);
}
}
person.sayHi(); // 'frank' 实际上这种调用方式相当于下面这种call方式,只不过是JS隐藏了对象的传参
person.sayHi.call(person); // 'frank' call()里面的第一个参数就是将来执行函数里面的this对象
函数有参数的情况下
function add(x,y){
return x + y;
}
add.call(undefined,1,2); // 3
// 因为第一个参数是表示函数中的this关键字,函数没有用到this,但是有别的参数,第一个位置需要用一个无用值(不一定非得是undefined)来占位,这样就能正常传参数了
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.apply(undefined,[1,2]); // apply跟call用法就是参数的形式不同,apply参数是数组
使用bind绑定this
function f1(p1,p2){
console.log(this,p1,p2);
}
let f2 = f1.bind({name:'frank'}); // f2 就是 f1 绑定了 this 之后的函数
箭头函数
箭头函数没有this和arguments
let fn = () => console.log(this); // 在全局定义箭头函数表达式
fn().call(2); // window
// 结论:箭头函数中的this不受控制,在定义箭头函数的时候就决定了this是在次箭头函数所定义的位置或作用域的this值
立即执行函数
! function (){
var a = 1;
}(); // 最初多是为了创建一个局部变量而产生的的,函数不用显式的调用便会立即执行 前面的符号可以是 + - ~ !等操作符,推荐使用 !
// 方式二
(function (){
var a = 1;
}('这里可以放参数'));
(function (){
var a = 1;
})('这里可以放参数');