javascript 函数 引起内存占满_在出栈函数的形参


JavaScript函数

为什么JavaScript中要设计函数的概念?

程序的本质是解决问题,通过逐条语句的编写可以实现目标。但是,当遇到重复的问题时,再实现一遍逻辑就会使得程序变得臃肿,所以我们需要将相同的问题抽离、包装,并在需要时,能够以最简单的方式使用重复逻辑
JavaScript 函数就是被设计为执行特定代码的代码块。


function add(a,b){
    return a + b;
}
add(1,2);
add(2,2);


函数的基本概念

函数的定义

  1. 函数式声明
function add(a,b){  // a 和 b 都是 add 函数的形参
    return a + b;   // return 的值就是函数的执行结果,没有 return 时函数的执行结果为 undefined
}


  1. 函数表达式
var add = function(a,b){
    return a + b;
}


  1. 函数生成器声明
function * add(a,b){
    yield a + b;
}


  1. 箭头函数
add = (a,b) => {
    return a + b;
}


  1. 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进制的地址)


javascript 函数 引起内存占满_在出栈函数的形参_02


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中, AOVO的一种,区别是 AO通常指的是进出栈较为频繁的对象


javascript 函数 引起内存占满_私有变量_03


函数的创建和执行

函数创建时,会从内存中新建一块堆内存来存储代码块。在函数执行时,会从堆中取出代码字符串,存放在新建的栈中

创建函数

  1. 开辟堆内存(16进制得到内存地址)
  2. 声明当前函数的作用域(函数创建的上下文才是他的作用域,和在那执行的无关)
  3. 把函数的代码以字符串的形式存储在堆内存中(函数再不执行的情况下,只是存储在堆内存中的字符串)
  4. 将函数堆的地址,放在栈中供变量调用(函数名)

执行函数

  1. 会形成一个全新的执行上下文EC(xx)(目的是供函数体中的代码执行),然后进栈(ECStack执行环境栈)执行
  2. 在私有上下文中有一个存放变量的变量对象AO(xx)
  3. 代码执行之前需要做的事
  • 初始化作用域链<自己的上下文,函数的作用域>
  • 初始化this(箭头函数没有this)
  • 初始化arguments实参集合(箭头函数没有arguments)
  • 形参赋值(形参变量是函数的私有变量,需要存储在AO中)
  • 变量提升(在私有上下文中声明的变量都是私有变量)
  1. 代码执行(把之前在函数堆中存储的字符串拿过来在当前上下文中执行)
  2. 根据实际情况判断当前上下文是否出栈释放

作用域链的查找机制,在代码执行过程中,遇到一个变量,首先查看是否是自己的私有变量,如果是自己的私有变量,就直接操作私有变量,如果不是自己的私有变量,则按照 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);


javascript 函数 引起内存占满_私有变量_04