JavaScript函数
为什么JavaScript中要设计函数的概念?
程序的本质是解决问题,通过逐条语句的编写可以实现目标。但是,当遇到重复的问题时,再实现一遍逻辑就会使得程序变得臃肿,所以我们需要将相同的问题抽离、包装,并在需要时,能够以最简单的方式使用重复逻辑
JavaScript 函数就是被设计为执行特定代码的代码块。
function add(a,b){
return a + b;
}
add(1,2);
add(2,2);
函数的基本概念
函数的定义
- 函数式声明
function add(a,b){ // a 和 b 都是 add 函数的形参
return a + b; // return 的值就是函数的执行结果,没有 return 时函数的执行结果为 undefined
}
- 函数表达式
var add = function(a,b){
return a + b;
}
- 函数生成器声明
function * add(a,b){
yield a + b;
}
- 箭头函数
add = (a,b) => {
return a + b;
}
- Function构造函数
add = new Function(a,b){
return a + b;
}
函数的参数
- arguments对象
在除箭头函数以外的方式来定义函数,函数的参数都会自动挂载到
arguments对象
上(不论是否传递形参,都会按顺序将参数挂载上去)
- 剩余运算符
箭头函数中没有
arguments对象
,但是某些情况下我们是不知道箭头函数传递进来的参数的具体数量,这时候就可以利用剩余运算符将参数收集
add = (...args) => { // ... 剩余运算符将参数收集到 args 中,并以数组的形式存储
return args.reduce((total,item)=>{ // reduce 遍历数组,并接收一个函数作为累加器
return total + item;
})
}
函数的返回值
return
语句不但用于返回函数的计算结果,当函数执行到return
时,就会立即停止函数的执行(不论return的下一的语句是什么)
function add(a,b,c){
return a + b; // 函数执行到这一步时,会直接将 a+b 的结果返回,并立即结束当前函数的执行
return b + c; // 该语句不会被执行
}
add(1,1,1)
函数体中没有return
的时候,会将函数体中的所有代码都执行一遍,并返回undefined
JS函数浏览器中的存储(堆)
浏览器在读取到函数时,并非直接将函数体中代码执行,而是会将函数以字符串的形式存储在浏览器的内存中,这个过程浏览器会分配一块堆内存用于函数的存储,并记录函数存储的位置(16进制的地址)
JS函数的运行机制(栈)
JavaScript的运行环境
- 浏览器(不同厂家对应不同的引擎)
- node(V8引擎)
- webview(V8引擎)
JS代码的执行环境--ECStack
- ECStack(Execution Context Stack)
ECStack
执行环境栈,它也是浏览器从内存中拿出的一块内存,但是是栈结构内存,作用是执行代码,而非保存代码。
- EC(Execution Context)
EC
执行上下文,EC
不同于ECStack
它虽然同样是栈内存,但是它的作用是为了在函数执行时,区分全局和函数作用域执行所处的不同作用域(保障每个词法作用域下的代码的独立性)
- GO(Global Object)
浏览器会将供JS调用的属性和方法存放在
GO
中(内置对象),同时浏览器会声明一个名为window
的属性指向这个对象
- AO & VO (Active Object & Variable Object)
代码执行时,会创建变量,
VO
就是用于存储这些变量的空间。VO
通常用于存放全局变量(全局变量一般情况下是不被释放的),而函数执行或其他情况下形成的私有上下文的变量存储在AO
中,AO
是VO
的一种,区别是AO
通常指的是进出栈较为频繁的对象
函数的创建和执行
函数创建时,会从内存中新建一块堆内存来存储代码块。在函数执行时,会从堆中取出代码字符串,存放在新建的栈中
创建函数
- 开辟堆内存(16进制得到内存地址)
- 声明当前函数的作用域(函数创建的上下文才是他的作用域,和在那执行的无关)
- 把函数的代码以字符串的形式存储在堆内存中(函数再不执行的情况下,只是存储在堆内存中的字符串)
- 将函数堆的地址,放在栈中供变量调用(函数名)
执行函数
- 会形成一个全新的执行上下文EC(xx)(目的是供函数体中的代码执行),然后进栈(ECStack执行环境栈)执行
- 在私有上下文中有一个存放变量的变量对象AO(xx)
- 代码执行之前需要做的事
- 初始化作用域链<自己的上下文,函数的作用域>
- 初始化this(箭头函数没有this)
- 初始化arguments实参集合(箭头函数没有arguments)
- 形参赋值(形参变量是函数的私有变量,需要存储在AO中)
- 变量提升(在私有上下文中声明的变量都是私有变量)
- 代码执行(把之前在函数堆中存储的字符串拿过来在当前上下文中执行)
- 根据实际情况判断当前上下文是否出栈释放
作用域链的查找机制,在代码执行过程中,遇到一个变量,首先查看是否是自己的私有变量,如果是自己的私有变量,就直接操作私有变量,如果不是自己的私有变量,则按照
scope-chain
,向上级上下文查找,如果上级有则拿过来操作,没有则一直向上查找,直到EC(全局)
var x = [1,2]
function fn(y){
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y);
}
fn(x);
console.log(x);