JS函数()

212 阅读7分钟

易混

  1. 函数声明(function statement)

    使用function关键字声明一个函数,再指定一个函数名,叫函数声明
    如:function fn(){};

  2. 匿名函数 ( anonymous functions )

    使用function关键字声明一个函数,但是没有命名,这个函数就叫匿名函数
    如:function(){};

  3. 函数表达式( function expression )(赋值操作-函数没有声明提升)

    把一个匿名函数当做值传给一个变量,叫函数表达式,是最常见的函数表达式语法
    如:let fn = function(){};

变量名解析顺序

 同一作用域下,相同变量名,后面的赋值会覆盖前面的;

同一作用域下,如果变量名和函数名相同,那么函数名会覆盖变量名,不论它们在代码的顺序如何;但是它们的初始化赋值是按顺序执行的;

函数

1,函数创建

  • 开辟一个堆内存
  • 把函数体中的代码当做字符串存储进去
  • 把堆内存的存储地址赋值给函数名/变量名
  • 函数在哪创建,那么他执行的时候所需要查找的上级作用域就是谁

2,函数的执行

  • 形成一个全新的私有的作用域 / 执行上下文 / 私有栈内存(执行一次产生一个,多个之间也不会产生影响)
  • 形参赋值,变量提升
  • 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
  • 遇到一个变量,首先判断他是不是私有变量(形参和在私有作用域中声明的变量就是私有变量),是私有的变量就操作自己的变量即可;不是私有的就向上级作用域查找一直找到全局作用域为止 ==>作用域链查找机制
  • 私有变量和外界变量没有必然关系,可以理解为被私有栈内存保护起来了,这种机制就是闭包的保护机制

3,关于堆栈内存释放问题

函数执行就会形成栈内存(从内存中分配的一块空间),如果内存都不销毁释放,就很容易造成栈内存溢出(内存爆满,电脑卡死)

  • 堆内存释放问题
堆内存产生 ==>创建一个引用类型的值,就会产生一个堆内存
堆内存释放 ==>如果当前创建的堆内存不被其他东西占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放

let obj = {name:'js'};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放对象的堆内存,需要手动解除变量和值的关联(null:空对象指针)
obj=null;
oop=null
  • 栈内存释放问题
栈内存产生 ==>
    1,打开浏览器形成的全局作用域
    2,执行函数形成的私有作用域
    3,ES6中let/const形成的块级作用域都是栈内存
    4,.....
    
栈内存释放 ==>
    全局栈内存:关掉页面的时候才会销毁掉
    私有栈内存:
      1,一般函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除死递归,死循环模式)
        function fn(){//...}
        fn();//函数执行形成栈,执行完成栈内存销毁
      2,但是一旦栈内存中的某个东西(一般堆地址),被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁
        (特点:私有作用域中的私有变量等信息也保留了下来)
        function fn(){
            return function(){
            //...
            }
        }
        let f = fn();//f占用了fn执行形成的栈内存中的一个东西(堆地址),则fn形成的栈内存不能立即被释放掉了

闭包

1,函数执行形成不被释放的私有栈内存,保护里边的变量不被外界干扰(外边修改不了私有的,私有的也无法修改外边的)

2,形成不销毁的栈内存,里面的私有变量保存了下来

 闭包题1:
 var i = 5;
    function fn(i){
        return function(n){
            console.log(n + (++i))
        }
    }
    var f = fn(1); //fn(1)不销毁
    f(2); //4  2 + ++1(1是上级作用域中的i) ==>fn(1)中的i进行++操作,1变为2  f(2)销毁
    fn(3)(4);//8
    fn(5)(6);//12 fn执行形成新的栈内存与之前执行不会产生影响
    f(7);//10  f执行的上级作用域也是fn(1),在此之前i已经进行了++操作
    console.log(i); //全局5
    //解题: 闭包
    /*
        var f = fn(1) //将fn(1)执行后的结果赋值给f
        1.f(2) n=2 n+(++i) i为fn的形参结果是1 所以++i的结果结果就是2, 最后结果=>4
        2.fn(3)(4) 函数执行->全新的栈内存  4+(++3) =>8  
        3.fn(5)(6) 函数执行->全新的栈内存  6+(++5) =>12 
        4.f(7) 7+(++2)=>10  i的值是f(2)执行后进行++操作后的值
        5.i的值还是5
    */
闭包题2:
var i = 10
function fn(){ //
    i -= 2;
    return function(n){  //
        console.log((++i)-n)
    }
}

var f = fn(); //全局i = 8->9-10
f(1);//8
f(2);//8
fn()(3);//6//fn执行全新栈内存  全局i由10又变为8->9 
fn()(4);//4//fn执行全新栈内存   全局i由9又变为7->8
f(5);//8->9 - 5 = 4
console.log(i);//9
 // 整个解题思路:
 /*
    函数内部return一个函数-->闭包->保护内部变量
    函数中的i是全局的,所以将fn()执行后全局的i变为8 f为return后的函数 小f执行只操作函数内部的代码
    1.然后执行f(1) 没有形参赋值,提前声明全局下面的8进行++操作变为9, 所以f(1)的结果是9-1=8
    2.执行f(2) 全局下的i由9变为10 结果为10-2=8
    3.fn()(3)首先新的函数执行,形成新的栈内存,fn()先执行全局i由10进行-2操作变为8,然继续执行(3)全局i进行++变为9 结果9-3=6
    4.fn()(4)新的函数执行,形成新的栈内存,fn()执行全局i由9变为7,继续执行全局下的i由7变为8 结果 8-4=4
    5.f(5)执行的是return后面的函数,全局i值为8,然后进行++操作变为9,最后结果是9-5=4
    6.最后的全局i就是4
  */

参照b站视频

作用域与上下文

深入理解

作用域(scope) 是指变量的可访问性,上下文(context)是指 this 在同一作用域内的值。特别需要注意的是,执行期上下文中的上下文这个词语是指作用域而不是上下文

函数的三种角色

构造函数 Function的实例对象 普通函数

易混

  1. 函数声明优于变量声明
function a(){}
var a;
console.log(a);//a(){}
var a = 1; 
function a() {}
alert(a);//1

相当于:
a = function() {}
var a;//没意义
a = 1; // 重新赋值
alert(a);//1
 function f(){
  n = 10
}
f()
console.log(window.n);//10
  1. 案例
1.
var n = 1;
fn(n);
function fn() {
    console.log(n);//undefined
    var n = 2;
}                  //=> fn执行没有形参赋值,只有变量提升
var n = 1;
fn(n);
function fn(n) {
    console.log(n);// 1
    var n = 2;
}               //=>新参赋值 n=1var声明的n变量提升,由于已经存在n,所以不会重新定义变量
var n = 1;
fn(n);
function fn(n) {
    console.log(n); //f n(){}
    function n() {}
    var n = 2;
}       
 //=> 执行顺序
 n = 1
 n = function(){}//将1替换
 var n //没有意义
  1. 总结
1. 当变量和函数重名的时候,函数的优先级高于变量。
2. 函数体内部,局部变量(函数内部声明过的)的优先级比同名的全局变量高。
3. 未使用var关键字定义的变量都是全局变量。全局变量都是window对象的属性
4. 预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在var之上。
如果有形参,就先给形参赋值
5. 预解析变量是只声明,不赋值,默认为undefined。函数声明加定义
6. '变量重名时,不会重新声明,但是执行代码的时候会重新赋值。'
7. 函数重名时,后者会覆盖前者。

VO/AO

变量对象:Variable Object, VO
激活对象:AO