Js面试总结

100 阅读6分钟

1. let、var、const的区别

  • var 关键字
  1. 没有块级作用域的概念
  2. 有全局作用域和函数作用域的概念
  3. 存在变量提升
  4. 全局作用域用var声明的变量会被挂载到window对象下
  5. 同一作用域中允许重复声明
  • let 关键字
  1. 有块级作用域的概念
  2. 不存在变量提升
  3. 同一块级作用域中不允许重复声明
  4. 不存在全局作用域的概念
  5. 暂时性死区
  • const 关键字
  1. 用于声明常量,与let特性一样,仅有两处差别
  2. 声明后必须初始化
  3. 常量值不能改变

暂时性死区

ES6规定,let、const命令会使区块形成封闭的作用域,若在声明前使用变量,就会报错

2. 原型与原型链的理解

  • 每个对象都有一个__proto__属性,该属性指向自己的原型对象
  • 每个构造函数都有一个prototype属性, 该属性指向实例对象的原型对象
  • 原型对象里的constructor指向构造函数本身

每个对象都有自己的原型对象,而原型对象本身也有自己的原型对象,从而形成一条原型链条 当试图访问对象的属性时,它不仅在该对象上查找, 还会在该对象的原型上查找,以及对象的原型的原型上查找,依次层层向上查找, 直到找到一个匹配的属性或达到原型链的末尾

3. 执行上下文的理解

执行上下文是评估和执行js代码的环境的抽象概念, 每当js代码在运行的时候, 他都是在执行上下文中运行

js中执行上下文有三种

  1. 全局执行上下文

这是默认或者说基础的执行上下文,任何不在函数内部的代码都在全局执行上下文中。它会执行两件事, 创建一个全局的Windows对象(浏览器环境下),并且设置this的值定于这个全局对象,一个程序中只会有一个全局执行上下文

  1. 函数执行上下文

每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有自己的执行上下文,不过是在函数执行时创建的,函数的上下文可以有任意多个。每当一个新的执行上下文被创建,他会按照定义的顺序执行一系列的步骤

  1. Eval函数执行上下文

执行在Eval函数内部的代码也有属于自己的执行上下文

调用栈

调用栈时解析器的一中机制, 可以在脚本调用多个函数时, 跟踪每个函数在完成时应该返回的控制点(如什么函数正在执行, 什么函数被这个函数调用, 写一个调用的函数是谁)

  • 当脚本要调用一个函数时, 解析器会把该函数添加到调用栈中并且执行这个函数
  • 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到他们被上个程序调用的位置
  • 当函数运行结束后。解析器将他们从栈中取出, 并在主代码列表中继续执行代码
  • 如果栈占用的空间比分配给他的空间还大, 那么会导致栈溢出

4. 作用域与作用域链的理解

1. 什么是作用域

ES5中只存在两种作用域:全局作用域与函数作用域 在js中,我们将作用域定义为一套规则, 这套规则用来管理引擎如何在当前作用域以及嵌套的作用域中根据标识符名称进行变量名查找。ES6新增了块级作用域

2. 什么是作用域链

当访问一个变量时, 编译器在执行这段代码的时,会首先从当前作用域中查找是否有这个标识符, 如果没有, 就会去父级作用域中查找, 如果父级作用域中没有找到继续向上查找, 直到全局作用域为止。 而作用域就是当前作用域与上层作用域的一系列变量对象组成, 他保证了当前执行的作用域对符合访问权限的变量和函数的有序访问

5. this指向

  1. 在函数体中, 非显示或者隐式地简单调用函数, 在严格模式下, 函数内部的this会绑定到undefined, 在非严格模式下则会被绑定到全局对象上(window/global)
  2. 一般使用new方法调用构造函数时, 构造函数内部的this会被绑定到新创建的对象上
  3. 一般通过call/apply/bind方法调用函数的时候, 函数体内的this会被绑定到指定的参数对象上
  4. 一般通过上下文对象调用函数的时, 函数体内的this会被绑定到该对象上
  5. 在箭头函数中, this的指向是由外层作用域来决定的

6. apply call bind 区别

  • 三者都可以改变函数的 this 对象指向。
  • 三者第一个参数都是 this 要指向的对象,如果如果没有这个参数或参数为 undefined 或 null,则默认指向全局 window。
  • 三者都可以传参,但是 apply 是数组,而 call 是参数列表,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入
  • bind 是返回绑定 this 之后的函数,便于稍后调用;apply 、call 则是立即执行
  • bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例

7. 手写new操作符

    function _new() {
        var fn = arguments[0];
        var args = Array.prototype.slice.call(arguments, 1);
        var obj = Object.create(fn.prototype);
        var result = fn.apply(obj, args);
        if(result && (typeof result === 'object' || typeof result === "function" )) {
            return result
        }
        return obj
    }

8. setTimeout模拟setInterval

    function _setinterval(fn, delay=1000) {
            let timer = null;
            let clear = false;

            function _interval(){
                if(clear) {
                    clear = false;
                    clearTimeout(timer);
                    return
                }
                fn();
                timer = setTimeout(_interval, delay)
            }

            timer = setTimeout(_interval, delay)
            return () => {
                clear = true
            }
        }

9. 发布订阅模式

    class EventEmitter {
            constructor() {
                this.events = {};
            }

            on(type, callback) {
                if(!this.events[type]) {
                    this.events[type] = [callback]
                } else {
                    this.events[type].push(callback)
                }
            }

            off(type, callback) {
                if(!this.events[type]) {
                    return
                }
                this.events[type] = this.events[type].filter(fn => fn !== callback)
            }

            emit(type, ...rest) {
                this.events[type] && this.events[type].forEach(element => element.apply(this, ...rest));
            }

            once(type, callback) {
                function fn() {
                    callback();
                    this.off(type, fn  )
                }
                this.on(type, fn)
            }
        }