js函数高级(三)

349 阅读5分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

执行上下文与执行上下文栈

变量提升与函数提升

  • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
  • 函数提升: 在函数定义语句之前, 就执行该函数
  • 先有变量提升, 再有函数提升
/*
变量提升与函数提升
*/
var a = 3
function fn () {
  console.log(a)
  var a = 4
}
fn()     //undefined

console.log(b) //undefined  变量提升
fn2() //可调用  函数提升
// fn3() //不能  变量提升

var b = 3
function fn2() {
  console.log('fn2()')
}

var fn3 = function () {
  console.log('fn3()')
}

理解

  • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
  • 执行上下文栈: 用来管理产生的多个执行上下文

分类:

  • 全局: window
  • 函数: 对程序员来说是透明的

生命周期

  • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
  • 函数 : 调用函数时产生, 函数执行完时死亡

执行上下文创建和初始化的过程:

全局执行上下文:

  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    • var定义的全局变量==>undefined, 添加为window的属性
    • function声明的全局函数==>赋值(fun), 添加为window的方法
    • this==>赋值(window)
  • 开始执行全局代码

函数执行上下文:

  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
    • var定义的局部变量==>undefined, 添加为执行上下文的属性
    • function声明的函数 ==>赋值(function), 添加为执行上下文的方法
    • this==>赋值(调用函数的对象), 如果没有指定就是window
    • 形参变量==>赋值(实参)==>添加为执行上下文的属性
    • arguments==>赋值(实参列表的伪数组), 添加为执行上下文的属性
  • 开始执行函数体代码
console.log('gb: '+ i)
var i = 1
foo(1)
function foo(i) {
  if (i == 4) {
    return
  }
  console.log('fb:' + i)
  foo(i + 1) //递归调用: 在函数内部调用自己
  console.log('fe:' + i)
}
console.log('ge: ' + i)
/*
依次输出
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
  
整个过程中产生了几个执行上下文?  5
*/
-->


作用域与作用域链

理解:

  • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
  • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量

分类:

  • 全局
  • 函数
  • js没有块作用域(在ES6之前)

作用

  • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
  • 作用域链: 查找变量

区别作用域与执行上下文

1. 区别1

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

2. 区别2

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

3. 联系

  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域
var x = 10;
function fn() {
  console.log(x);
}
function show(f) {
  var x = 20;
  f();
}
show(fn);  //10

闭包

理解:

  • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
  • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性

作用:

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

  1. 函数执行完后, 函数内部声明的局部变量是否还存在? 一般是不存在, 存在于闭中的变量才可能存在

  2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它

写一个闭包程序

  function fn1() {
    var a = 2;
    function fn2() {
      a++;
      console.log(a);
    }
    return fn2;
  }
  var f = fn1();
  f();
  f();

闭包应用:

  • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
  • 循环遍历加监听
  • JS框架(jQuery)大量使用了闭包

缺点:

  • 变量占用内存的时间可能会过长
  • 可能导致内存泄露
  • 解决:
    • 及时释放 : f = null; //让内部函数对象成为垃圾对象
//代码片段一
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()());  //the window


//代码片段二
var name2 = "The Window";
var object2 = {
  name2 : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name2;
    };
  }
};
alert(object2.getNameFunc()()); //my object

内存溢出与内存泄露

1. 内存溢出

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  1. 内存泄露
  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包

最后

本文作为本人学习总结之用,同时分享给大家
因为个人技术有限,如果有发现错误或存在疑问之处,欢迎指出或指点!不胜感谢!