ES基础面试知识体系汇总

826 阅读8分钟

引子:此文很多语言是基于自身的理解,语言尽量精炼,如有概念性错误,欢迎指正。

1.变量类型和计算

JS数据类型

字符串、数字、布尔、undefined、null、大整数、符号、对象

tring、number、boolean、undefined、null、bigint、symbol、object

值类型和引用类型的区别

只有 object 是引用类型哦,也叫复杂数据类型

  • 基本数据类型直接按值存在栈中,基础数据类型赋值时给值
  • 引用数据类型的数据存在堆内存中,但是数据指针是存放在栈内存中,访问引用数据时,先从栈内存中获取指针,通过指针在堆内存中找到数据,引用数据类型赋值时给地址
image-20220306162856684

typeof

  • 识别所有值类型 number string boolean undefined 新增的SymbolbigInt ,除了null是Object(bug)
  • 识别函数 funcition
  • 识别数组对象都为 Object

instanceof

  • instanceof 运算符可以用来检测“某对象是不是某个类的实例”
  • 也可以判断引用数据类型 Array、Object 和 Function,但是Number,Boolean,String 基本数据类型不能判断

自定义instanceof

image-20220306164307355 image-20220306164209815

Object.prototype.toString.call()

  • 优点:基本和引用数据都能判断
  • 缺点:写法繁琐不容易记,推荐进行封装后使用
function typeOf(data) {
    return Object.prototype.toString.call(data).slice(8, -1);
}

== 运算符

何时使用 === 何时使用 ==

== 不严格比较两边的类型是否相同

image-20220306161851030 image-20220306015845407

字符串拼接

image-20220306161817172

truly 和 falsely 变量

  • truly 变量:!!a === true 或者 Boolean(a)为 true 的变量
  • falsely变量: !!a === false 或者 Boolean(a)为false 的变量
image-20220306162127727

知晓上面有利于判断 if,switch 等条件语句和逻辑判断 & || 的的执行流程

  • if(truly变量){执行语句} if(falsely变量) {不执行该语句}
image-20220306162338765 image-20220306162349087

逻辑判断 && 前面为真继续往后走

逻辑判断 || 前面为假继续往后走

!相当于对当前值转换为布尔值后取反

image-20220306162439525

深拷贝

  • 注意判断值类型和引用类型
  • 注意判断是数组还是对象
  • 递归

深拷贝 yun.y2y7.com

2.作用域链和闭包和this

作用域链

JS的作用域有全局、函数、块级以及严格模式下的 eval 作用域

当前作用域没有定义的变量,这成为 自由变量

当前作用域如果获取该自由变量,就需要向父级作用域一层层向外寻找至全局作用域,这一层层就是作用域链

**注意:**自由变量的寻找, 依据的是函数定义时的作用域链,而不是函数执行时的哦

image-20201231164111249

闭包

理解:读取外层函数内部的变量并且让这些变量始终保持在内存之中

换一种通俗的说法:一个函数中嵌套另一个函数,内部函数使用了外部函数的参数或变量,构成闭包

经典使用闭包解决题目:

<ul>
    <li>编号1,点击我请弹出1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

<script>
    // 点击 li 按顺序打印 1 2 3 4 5
    var list = document.getElementsByTagName('li');
    for (var i = 0; i < list.length; i++) {
        list[i].addEventListener('click', function(i){
            return function(){
                console.log(i + 1)
            }
        }(i))
    }
</script>
image-20220306163303637

闭包的场景:

  1. 函数作为返回值
  2. 函数作为参数传递
image-20220306022552265 image-20220306023019415

利用闭包的特性我们就可以在模拟私有变量的情况下缓存操作内部数据

image-20220306023440137 image-20220306163347411

this

函数的上下文 this 需要调用的时候决定

规则上下文
函数()window(严格模式undefined)
对象.函数()对象
数组[下标]()数组
DOM事件处理函数绑定的DOM元素
call、apply、bind任意指定
IIFEwindow
定时器window
new调用的函数秘密创建出的对象
箭头函数上层作用域

手写call、apply、bind

手写call、apply、bind

3.原型和原型链

原型链

这题复杂,但是我们可以从具体化实例入手一步步解释:

每个构造函数都有显示原型 prototype

每个实例都有隐式原型 _proto_ (方便叫法和写法,勿cue)

构造函数.prototype === 实例的隐式原型 _proto_

当获取对象属性 yunmu.name 或执行方法 yunmu.sayhi() 时先自身寻找,找不到则会去自身 __proto__寻找

实例可以打点访问它的原型的属性和方法,这被称为“原型链查找”

假设有一个数组 arr = [] ,实际上此数组一定是 new Array() 实例化出来的,带入上面模型:

Array.prototype  ===  arr.__proto__

此时我们可以说 arr 的原型是 Array.prototype,这上面有很多方法,根据“原型链查找”规则,arr 就可以直接调用这些方法,例如 arr.push("yunmu")

但是接下来的事情有趣了

Array.prototype 是一个对象,他实际上根本也是 new Object 实例化出来的,所以:

Object.prototype ===  Array.prototype__proto__ 

这样的话,arr 就有两层原型:

  1. arr 的 原型 是 Array.prototype
  2. arr 的原型的原型是 Object.prototype

于是一个链条实际上形成了:

arr ===> Array.prototype ===> Object.prototype

图示的话就是:

0IO7h4.png

原型链的好处毋庸置疑:JS任何数组都可以直接访问统一定义在原型链上面的属性和push、toString方法,真是妙不可言

有点继承内味了,但是不支持私有属性

实现继承

ES5寄生组合继承

image-20220306154709161

ES6类继承

  • class本质是语法糖、继承依旧是基于原型实现的
image-20220306155629732

new 做了什么

  1. 创建临时对象,继承构造函数 prototype 属性
  2. 执行构造函数,使函数上下文this 指向临时对象
  3. 默认返回临时对象

自定义new

4.异步和单线程

JS是一门单线程语言,即同一时间只能做一件事情

浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker,但有诸多限制:无法读取主线程DOM,弹窗,无法访问本地文件,只能通过消息跟主线程通信

这也很好理解,如果多处同时执行,我 html 在生成结构,JS 又在修改结构,我听谁的呢?

正是由于这种性质我们才更需要异步的机制,因为如果一个 Ajax 请求较慢,使用同步在请求期间就什么都干不了,如果使用异步,我们就可以先做别的事情,有结果了通知即可,所以有一个结论:

  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行
image-20220306165340410

异步场景

  • 定时任务: setTimeout setInverval
  • 网络请求,如 Ajax 图片加载

DdWVBR.png

DdWcEq.png

DdWq56.png

DdWvxe.png

// 小试牛刀
console.log(1); // 1
setTimeout(function () {
  console.log(2); // 5
}, 1000);
console.log(3); // 2
setTimeout(function () {
  console.log(4); // 4
}, 0);
console.log(5); // 3

EventLoop

浏览器的EventLoop

  1. 同步代码放在 call stack 里面一行行执行
  2. 遇到异步(定时,网络请求)记录
  3. 同步代码执行完先执行微任务
  4. 尝试 DOM 渲染
  5. Event Loop 开始工作,轮询查找 Callback Queue 执行宏任务

宏任务:setTimeout 、setInterval 、Ajax、requestAnimationFrame、DOM事件

微任务:Promise 、 async/await、queueMicrotask

image-20220306171517689

Promise顺序的面试题

image-20220306171006530

第一题:

image-20220306170746558

第二题:

image-20220306170834233

第三题:

image-20220306170906360

Promise和setTimeout的顺序

image-20220306172009084

async/await 的顺序问题

image-20220306172202621 image-20220306172438723

image-20220306172451513

Node的EventLoop

六个阶段:

  1. Timers:执行 setTimeout 以及 setInterval 的回调
  2. Pending callbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用
  3. Idle, prepare:闲置阶段 - node 内部使用
  4. IO Poll:执行 poll 中的 I/O 队列,检查定时器是否到时间
  5. Check:存储 setImmediate API 的回调函数
  6. Closing callbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等

循环体会不断运行以检测是否存在没有调用的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空

宏任务:setInterval、setTimeout、 setImmediate

微任务:Promise.then、 async/await、Promise.catch、 Promise.finally、process.nextTick

当微任务事件队列中存在可以执行的回调函数时,会先执行微任务再进入下一个阶段执行回调函数

process.nextTick() 此方法的回调函数优先级最高,会在事件循环之前被调用

node事件循环的过程:

  • 执行同步代码
  • 执行 process.nextTick 和微任务(前者优先级更高)
  • 按照顺序执行 6 个类型的宏任务(每当开始之前都执行当前的微任务)
  • ...

浏览器和Nodejs事件循环的区别

  • 事件循环的大概模式相同
  • 宏任务有优先级区分
  • process.nextTick 在微任务的优先级更高

但是,process.nextTick 在最新版 nodejs 中不被推荐使用,推荐使用 setImmediate

原因在于 process.nextTick 是在当前帧结束后立即执行,会阻断IO并且有最大数量限制(递归时会有问题)

setImmediate 不会阻断 IO ,更像是 setTimeout(fun, 0)

5.严格模式

  • Javascript 设计之初,有很多不合理、不严谨、不安全之处,所有就有了严格模式避免潜在隐患
  • 一般情况下,开发环境用 ES 或者 Typescript ,打包出的 js 代码使用严格模式

开启严格模式

代码(或一个函数)一开始插入一行 'use strict' 即可开启严格模式

'use strict' // 全局开启

function fn() {
    'use strict' // 某个函数开启

}

严格模式的特点

  • 全局变量必须声明
  • 函数参数不能重名
  • 禁止使用 with
  • 创建 eval 作用域
  • 禁止 this 指向全局作用域
'use strict'
n = 10 // ReferenceError: n is not defined

// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
function fn(x, x, y) {
    return
}

var obj = { x: 10 }
with (obj) {
    // Uncaught SyntaxError: Strict mode code may not include a with statement
    console.log(x)
}

var x = 10
eval('var x = 20; console.log(x)')
console.log(x) // 10

function fn() {
    console.log('this', this) // undefined
}
fn()