面试准备 js篇

96 阅读9分钟

1.闭包

闭包没有那么复杂,本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的

闭包的优点

优点:闭包因为长期驻扎在内存中。可以重复使用变量,不会造成变量污染
缺点:闭包会使函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,可能会导致内存泄露。解决方法是在退出函数之前,将不使用的变量全部删除。

2.作用域?作用域链?

什么是作用域

作用域是变量以及函数所能访问的区域与集合,换句话说,作用域决定了变量和其他资源的可见性。

全局作用域和函数作用域以及块级作用域

把作用域分为两种,一种是全局作用域,一种是函数作用域,全局作用域里面声明的变量和方法能在程序中任何地方被访问。而函数作用域中的变量和资源只能在函数中被访问,函数外部访问不到。es6中新增一个块级作用域,使用let和const声明的变量都会拥有块级作用域,使用var声明的则没有,块级作用域是把变量包在一个大括号里面,大括号外部则访问不到,而使用var在大括号里面声明的变量属于全局变量

什么是作用域链

当JavaScript在使用一个变量的时候,首先js引擎会在当前作用域下去寻找改变量,如果找到则使用改变量,如果访问不到则会到其外层的作用域去寻找,直到访问到改变量或者已经到达了全局作用域之下,如果在全局作用域之下还找不到该变量,它就会在全局范围内隐式声明该变量或者是直接报错

3.原型?原型链?

JavaScript中万物都是对象,每一个对象都会拥有一个隐式原型_proto_,当你试图访问一个对象的属性时,它不仅仅是在该对象上搜寻,还会在该对象的原型上寻找,以及该对象的原型的原型,一层一层地往上寻找,直到找到改变量或者到达了原型链的末端,准确的说,这些属性和方法是定义在Object的构造器函数之上的prototype属性,而非实例本身

每个函数对象都有一个protoype属性,也就是函数的原型对象,构造函数生成的实例有一个_proto_属性,指向构造函数的原型对象prototype,这也是原型链构成的原因,而原型对象的_proto_又会指向其构造器的prototype,就这样一层一层,当你去访问一个属性的时候,会依次向上搜寻,这就是原型链的作用

4.this指向

是什么?

this关键字是函数运行时自动生的一个内部对象,只能在函数内部使用

不同场景下的this

  • 1.在全局作用域下,this指向是window对象
var name = 'Jenny';
function person() {
    return this.name;
}
console.log(person());
  • 2.匿名函数中的this指的都是window对象
var name = "The Window";
  var object = {
    name : "My Object",
    //var that=this; //可以更改this指向,使之指向当前的object对象
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());  // 输出为  The Window“”

  • 3.当函数作为对象的属性被调用时,this指向的是该调用的对象
  • 4.当构造函数初始化一个实例的时候,会把this指向这个实例对象
  • 5.在箭头函数中运用到的this,会指向其上级所在的执行上下文

5.执行上下文,执行栈?

概念

简单来说,执行上下文就是js代码的执行环境,代码都是在执行上下文中运行。执行上下文有三种:全局执行上下文、函数执行上下文、eval函数执行上下文

生命周期

创建->执行->回收

  • 创建阶段:确定了this的指向,创建词法环境,创建变量环境 。词法环境分为全局环境以及函数环境,全局环境中有window对象,函数环境中:用户在函数内定义的变量都会存储在函数环境中,包含了arguments对象 变量环境:具有上面词法环境中定义的所有属性,在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )绑定
  • 执行阶段:执行变量赋值、代码执行
  • 回收阶段: 执行上下文出栈等待虚拟机回收上下文

执行栈

也叫调用栈,具有先进后出的特点 举个例子:简单分析一下流程: 创建全局上下文请压入执行栈 first函数被调用,创建函数执行上下文并压入栈 执行first函数过程遇到second函数,再创建一个函数执行上下文并压入栈 second函数执行完毕,对应的函数执行上下文被推出执行栈,执行下一个执行上下文first函数 first函数执行完毕,对应的函数执行上下文也被推出栈中,然后执行全局上下文 所有代码执行完毕,全局上下文也会被推出栈中,程序结束

6.事件模型

事件与事件流

js中的事件,可以理解为在html文档中或者浏览器中发生的一种交互操作,使得网页具备互动性,常见的有加载时间,鼠标事件、自定义事件

事件流的三个阶段

  • 1.事件捕获阶段
  • 2.处于目标阶段
  • 3.事件冒泡阶段
    • 事件的捕获阶段是一种从上到下的传播,由最上层接收事件,到具体的触发结点最后接受事件
    • 事件冒泡则是一种由下到上的传播,在内层具体节点接受的事件会传播到最高层的父节点

事件模型可以分为三种:

  • 原始事件模型(DOM0级) :在dom元素上直接绑定,绑定速度较快 btn.onclick
  • 标准事件模型(DOM2级)
  • IE事件模型(基本不用)

7.事件代理

事件代理,俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素,事件流都会经过三个阶段,捕获、目标、冒泡,事件委托就是处于冒泡阶段完成的,事件委托,通常会把一组元素的事件委托给这组元素的父元素,绑定事件的是父元素,这样的话可以通过绑定一个事件在父元素上来实现对多个元素的事件监听。这样可以减少页面所需的内存,提高整体性能。动态绑定,减少重复操作 focus、blur这些事件没有事件冒泡机制,所以无法进行委托绑定事件 mousemove、mouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

8.new操作符做了什么

  • 1.创建了一个空的对象
  • 2.将对象与构造函数通过原型链连接起来
  • 3.将构造函数的this绑定在该对象上面
  • 4.执行构造函数的代码,为空对象添加属性和方法
  • 5.根据构造函数的返回值进行判断,如果是原始值则被忽略,如果是引用类型则返回一个对象
function myNew(Func, ...args){
    // 创建一个新对象
    const obj = {}
    // 原型链连接
    const proto = Object.getPrototypeOf(obj)
    proto = Func.prototype
    // this指向改变
    let result = Func.apply(obj,args)
    // 判断返回值类型
    return result instanceof Object ? result : obj
}

9.JavaScript中内存泄露的几种情况?垃圾回收机制

内存泄露是指程序未能释放已经不再使用的内存,并不是指物理上的消失,而是由于设计错误,导致在释放该内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

JavaScript中有自动垃圾回收机制也就是GC,原理:垃圾收集器会定期收集那些不再使用的变量,然后释放其内存。主要有两种方式

  • 1.标记清除(常用)
  • 2.引用计数

** 常见的内存泄露情况**

  • 1.意外的全局变量,在函数内没有使用var,let,const声明。
  • 2.闭包中外部对函数内变量的引用没有被解除
  • 3.没有清理对dom元素的引用等

10什么是防抖,节流

防抖和节流都是一种面对高频率触发的一种优化性能的方式。

防抖

  • 防抖 (debounce): 将多次高频操作优化为只在最后一次执行。

  • 原理:在事件被触发n秒后,再去执行回调函数。如果n秒内该事件被重新触发,则重新计时。结果就是将频繁触发的事件合并为一次,且在最后执行。

  • 使用场景:用户输入,只需再输入完成后做一次输入校验即可。

节流

  • 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作。
  • 原理:规定一个时间n,n秒内,将触发的事件合并为一次并执行。
  • 使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。