一名【合格】前端工程师的自检清单之作用域和闭包

Author Avatar
w4ctech 6月27日
  • 在其它设备中阅读本文章

最后更新于2019年06月27日; 如遇到问题,请留言及时通知站长

  • 1、理解词法作用域和动态作用域

    1. 作用域 : 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。
    2. 词法作用域 :也叫静态作用域,在你写代码时将变量和块作用域写在哪里来决定,也就是词法作用域在你书写代码时就确定了。
    3. 动态作用域:是不关心函数和变量是如何声明以及在何处声明的,只关心它们从何处调用。
  • 2、理解JavaScript的作用域和作用域链

    1. JS中遵循的是词法作用域,在 js 中作用域分全局作用域和局部作用域。js的所有代码默认是在全局作用域,局部作用域大多数是指函数作用域。
    • 全局作用域:
    1. 最外层函数和在最外层函数外面定义的变量拥有全局作用域;
    2. 所有未定义直接赋值的变量自动声明为拥有全局作用域;
    3. 所有window对象的属性拥有全局作用域;
    • 局部作用域:
    1. 是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。
    2. 作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。
    • 块级作用域:
    1. 块级作用域是ES6中新添加的概念,常指的是 {} 中的语句,如ifswitch条件语句或forwhile循环语句,不像函数,它们不会创建一个新的作用域。块级作用域通常通过letconst来体现。
    • 作用域链:
    1. 在作用域的多层嵌套中查找自由变量的过程是作用域链的访问机制。而层层嵌套的作用域,通过访问自由变量形成的关系叫做作用域链。

js 是解释型语言,它的运行分为两个阶段:解释和执行。

解释阶段:词法分析;语法分析;作用域规则确定。

执行阶段:创建执行上下文;执行函数代码;垃圾回收。

var tt = 'aa';
function test(){
    alert(tt);
    var tt = 'dd';//变量被重新定义,声明被会提升,不会遵循作用域链查找机制
    alert(tt);
}
test();//undefined  dd
  • 3、理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题

    1. 作用域是静态的,是已经确定的;而执行上下文是动态的,它会随着函数的调用而创建和回收。
    2. 执行上下文栈是用来管理执行上下文,程序在运行的时候执行上下文是一个进栈出栈的过程;
var ECStask=[];//创建一个执行上下文栈

function fun3(){  
    console.log('fun3')
}

function fun2(){
fun3()
}

function fun1(){
fun2()
}
fun1();
//首先是执行全局上下午,它是第一个进栈的,也是栈的常驻会员
//ECStask=[globalContext]
//调用fun1()
ECStask.push(<fun1> functionContext);
// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext);
// fun2中调用了 fun3 
ECStack.push(<fun3> functionContext);
// fun3执行完毕ECStack.pop();
// fun2执行完毕ECStack.pop();
// fun1执行完毕ECStack.pop();
// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext
  • 4、this的原理以及几种不同使用场景的取值

    1. this对象是在运行时基于函数的执行环境绑定的:
    2. 在全局作用域中 this 等于 widows,而当函数被作为某个对象的方法调用时,this 等于那个对象。
    • 不同场景的取值:
    1. 当函数是普通函数时,this仍指向window
    2. 当函数作为构造函数使用的时候,this指的是它即将new出来的对象;
    3. 当函数作为对象的方法时,方法中的this指向该对象;
    4. 当一个函数被callapply或者bind调用时,this的值就取传入的对象的值。
    5. 在一个HTML DOM事件处理程序里,this始终指向这个处理程序所绑定的HTML DOM节点。
    6. 箭头函数内部的this是词法作用域,由上下文确定。
  • 5、闭原型链包的实现原理和作用,可以列举几个开发中闭包的实际应用

    1. 闭包 :是指有权访问另一个函数作用域中的变量的函数。

      • 常见方式:在一个函数内部创建另一个函数。
      • 实际应用:把函数当作返回值、把函数当作参数,定时器等等。
  • 6、理解堆栈溢出和内存泄漏的原理,如何防止

    1. 堆栈溢出 :由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址,一般在递归中产生。堆栈溢出很可能由无限递归产生,但也可能仅仅是过多的堆栈层级。

    1. 内存泄漏:是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还 (delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。简单的理解就是不再使用的内存,没有及时释放,就是内存泄漏。
    2. 如何防止:对于递归导致的堆栈溢出,常用的方法就是使用闭包或匿名函数;对于内存泄漏常见的方法就是再不实用变量的时候,解除引用,或指向null

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,你只要清除主要引用就可以了。 ES6 考虑到了这一点,推出了两种新的数据结构:WeakSetWeakMap。它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用。

  • 7、如何处理循环的异步操作

    1. 使用闭包;
    2. 使用回调函数;
    3. 使用递归;
    4. 使用promise;
    5. 使用async/await

不同的执行环境有不同的逻辑,但都离不开上述方法。

  • 8、理解模块化解决的实际问题,可列举几个模块化方案并理解其中原理

    • 为什么要模块化开发:

      1. 方便代码管理(防止变量命名冲突);
      2. 方便调试(单元测试);
      3. 方便团队协作(公用模块的调用,业务模块的分配等等);
      4. 便于阅读(引用清晰,各司其职);

关于模块化的发展有一个清晰的流程,从最初的commonJsAMD异步加载,再到CMD按需加载,最后到现在的ES6的模块体系的建立。

commonJS:是服务器端模块的规范,核心思想是:允许模块通过require方法来同步加载所要依赖的其他模块,然后通过exportsmodule.exports来导出需要暴露的接口。

AMD:采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是require.js(还有个js库:curl.js

CMD:推崇依赖就近,按需加载,只有在用到某个模块时再去require。实际上CMD就是 sea.js在推广过程中对模块定义的规范化的产出。

//AMD推崇的依赖关系前置:在定义模块时就要声明要依赖的模块
define(['a', 'b', 'c', 'd'], function (a, b, c, d) { // 依赖必须一开始就写好
a.doSomething()  // 此处省略100行
...
b.doSomething()
...
})

//CMD推崇依赖就近,按需加载,只有在用到某个模块时再去
requiredefine(function (require, exports, modules) {  
    var a = require('a');
    a.doSomething();  // 此处省略100行
    ...
    var b = require("b");//按需加载
    b.doSomething();
})

ES6:使用export关键字来导出模块,使用import关键字引用模块,import可以实现指定加载。

本文链接:https://i.w4ctech.cn/frontWeb/Scope_Closure.html
This blog is under a CC BY-NC-SA 3.0 Unported License