面试的时候面试官是这样问我Js基础的,角度真刁钻

13,439 阅读13分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战


大佬们好,我是江湖不渡i,专业切图仔。🦞

    看了目录可能大佬们会觉得我写都是一些老生常谈的问题,基本上面试题汇总的文章都有。其实不是这样的,我并没有写一些大家都知道的东西,那些东西掘金上大佬们已经总结的很详细了。下面的内容都是在一些常见问题换一个角度来写的分析,比如说这个知识依赖于另外什么样一个知识,还有就是可能有些意外的可能平常我们自己回顾的时候想不到的点。我相信总会有那么一两个是你之前没曾想到过的,如果真的有帮助到哪位,希望大佬点个赞,哈哈。⚽️⚽️🌶️

变量声明

三个关键字:var、let、const

Tipes: var在所有ECMAScript版本都能使用,let、const只能在ECMAScript6和以后的版本使用。

提问环节:

❓❓❓三个关键字有啥区别

  • var声明的范围是函数作用域,let、const声明的是块级作用域
  • var声明变量会提升到函数作用域顶部同时初始化,let、const变量提升到作用域顶部但是不会初始化,在声明和初始化之间的区域叫做暂时性死区(TDZ)禁止读写,所以在我们看来let、const不会变量提升。
  • var可以重复声明覆盖自己声明过的变量但是不能重复声明覆盖let、const声明的变量,let、const不连自己声明过的变量都不能重复声明覆盖。
  • var、let声明的时候可以不赋初始值并且可以随时修改,const声明的时候必须初始化,而且不能修改。(引用数据类型不能修改指针,可以修改内容)
  • 全局声明的时候,var声明变量挂在在window对象,let、const因为ES6中,全局对象的属性和全局变量脱钩,所以没有在window上在对应的块级作用域的活动对象上。
  • let、const相比var,在块级作用域比函数作用域更早终止的情况下有助于垃圾回收提升性能。

❓❓❓什么是块级作用域?
    对应的还有全局作用域,函数作用域。块级作用域由ES6新增,一对{}包裹的就是块级作用域,块级作用域要配合let、const使用,对于var来说是没有块级作用域的。

❓❓❓为什么要引入块级作用域?
    为了解决var声明变量具有变量提升特性的缺陷。

❓❓❓变量提升是怎么造成的?
    因为JS引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,js在执行上下文的时候,会进行两个阶段:1.分析 2.执行,进入执行上下文时,首先会处理函数声明,其次会处理变量声明,在分析的过程中变量已经被声明挂在了变量对象上面,只不过没有具体赋值,所以这些变量和函数能在它们真正被声明之前使用。

❓❓❓怎么解决变量提升?
    使用let、const代替var声明变量,在块级作用域里面let、const 声明之前都被称为暂时性死区,这个区域里面引用后面才声明的变量都会报错。❗️暂时性死区会导致之前百分百安全的typeof不再安全。

数据类型

基本数据类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol、Bigint
引用数据类型:对象(Object)(包括:数组(Array)、函数(Function))

Tipes: Symbol、Bigint是在ECMAScript6和以后的版本新增。Object类型是一个基础类型,除此之外的引用类型都从它继承了基本的行为。

提问环节:

❓❓❓基本数据类型和引用数据类型有啥区别?
    基本数据类型只包括自身一般存储在栈内存中,任何方法都不能修改基本数据类型本身的值,都是返回了一个新的。引用数据类型包括指针和内容,指针存储在栈内存,内容存储在堆内存。

❓❓❓怎么判断数据是什么类型?

  • typeof:通过查找js底层的实现,所以只能判断出后面这几种。〈000:对象、010:浮点数、100:字符串、110:布尔、1:整数〉判断null为对象:所有机器码均为0 所以判断null会判断为对象。
  • instanceof:沿着原型链判断左边的原型链是否拥有和右边原型相同的指针,有的话就是相等,没有的话就是不相等,用它可以直接判断是不是数组。
  • Array.isArray():封装了Object.prototype.toString.call(),专门判断是不是数组。
  • Object.prototype.toString.call(): 原始数据类型在创建的时候都会有一个内部属性[[class]],这个属性表示数据类型的种类,Object.prototype.toStrin方法能访问到这个属性会返回这个内部属性(es6之后删除了[[class]],用[NativeBrand],实现效果更加兼容)。

❓❓❓数组的length是元素的个数,函数有length吗?length是什么?
    函数的length就是第一个有默认值的参数之前的参数的个数,也可以说是必须要穿的形参的个数。

❓❓❓const a = {}; const b = Symbol(123); const c = Symbol(123); a[b] = 'b'; a[c] = 'c'; console.log(a[b]); 输出是是什么?为什么?
答案: b;
分析:先看这个:

// 前置问题
const a = {}; 
const b = {key: 123}; 
const c = {key: 123}; 
a[b] = 'b'; 
a[c] = 'c'; 
console.log(a[b]); // c
// 题目
const a = {}; 
const b = Symbol(123); 
const c = Symbol(123); 
a[b] = 'b'; 
a[c] = 'c'; 
console.log(a[b]); // b

    先看我们的前置问题,因为对象的key只能是字符串,所以任何数据类型当作对象的key的时候都会默认转化为字符串,所以前置题目的b和c都被转化成[Object, Object],所以打印c就不用过多说明了。那么题目为什么打印的是c?因为Symbol声明的是一个独一无二的值,所以他们也是不相等的。Tipes: Symbol对象是不能强制转化类型成为字符串或者数组,作为对象的key的时候还是自身而且不能通过常规的手段去获取,只能通过Object.getOwnPropertySymbols来获取(返回的是Symbol数组)。Symbol对象可以转化成数组或者布尔值,所以Symbol对象可以用于逻辑运算,但是不能用于数学运算。

闭包和作用域链

闭包:有权访问另一个函数作用域中变量的函数,可以突破作用链。实现变量和函数的无序访问。
作用域链:当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链,由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

Tipes: 利用闭包可以突破作用链,作用域链能合理的解释闭包的原理。

提问环节:

❓❓❓闭包为什么会造成变量不被回收造成内存泄漏?
    首先我们都知道闭包是有权访问另外一个函数作用域变量的函数,这说明什么?就是闭包里面一直保持着对另外一个函数作用域内部变量的引用,那么变量回收是什么时候呐?当变量进入执行环境的时候,js垃圾回收机制会将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量,从逻辑上讲,永远不能释放进入环境的变量。那么闭包一直保持着使用,那么这个变量还有变量所在的函数作用域都不会被回收,js运行内存就那么多,闭包数量多了,一来而去内存就泄漏了。

❓❓❓为什么闭包能突破作用域链?
    我们要先知道突破是什么意思?当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链,由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。如果我们不想按照顺序来访问,我就想访问和我同级的函数内部的变量怎么办? 这时候闭包就发挥了它的作用了,因为闭包保持着对一个函数作用域的引用,那么这个函数作用域就一直不会被销毁,而且我们在哪里使用闭包都可以,只要闭包被创建了,这样不就突破了作用域链了吗?

异步处理方案

发展:Callback -> Promise-> Generator -> Async/await

Tipes: Promise、Generator、Async/await是在ECMAScript6和以后的版本新增。

提问环节:

❓❓❓为什么会有异步编程机制?
    首先要知道什么是同步什么是异步,同步就是一个任务执行完之后才能执行下一个任务,异步就是多个任务可以同时执行。从概念就能知道,如果说有一个任务的执行花费了大量的时间,只有同步的话肯定会阻塞代码的执行,所以异步编程就是为了优化因为计算量大而时间长的操作,保证系统的稳定性

❓❓❓异步编程和Eventloop的关系以及为什么有Eventloop?
    我们知道了异步编程的概念,多个任务同时执行,但是执行的结果总要有一个统一的队列里面去反馈到系统。那么Eventloop的作用是什么呐?为了让这些事件有条不紊地进行,JS引擎需要对之执行的顺序做一定的安排。比如我们的宏任务和微任务的执行时机。

❓❓❓promise真的解决了回调地狱吗?
    回调地狱是什么?就是因为最开始的异步执行方案Callback在执行复杂的事件逻辑的时候需要大量嵌套,导致代码阅读性很差。那promise真的解决了回调地狱的问题吗?在我看来并没有,因为在复杂的逻辑操作的时候,promise的不断then链式调用也会让人觉得有点费解,但是从一定程度上来说promise的代码比起来Callback更加优雅一点,所以说promise只是缓解了回调地狱。

❓❓❓Generator函数的意义是什么?
    在我觉得Generator函数的出现就是使我们可以控制函数的执行和停止,我们都知道promise的状态改变之不能终止的,Generator函数的出现就为我们提供了一种中止的可能性。

❓❓❓async/await会阻塞代码执行吗?
    async/await的作用就是让我们的异步代码看着更像同步代码,也可以说让我们的同步代码可以异步执行。那么async/await会不会阻塞代码的执行呐?毕竟我们要等待await后面的代码有结果之后才能继续往下走。所以说async/await实际上是会阻塞代码的执行,但是阻塞的也仅仅是那个函数作用域内部的代码,也就是await关键字后面的代码,对于外面的整个执行上下文是没有影响的,因为async关键字会让函数返回一个promise对象。

Http请求

常用方案:Ajax、fetch

Tipes: Ajax、fetch都是原生Api和axios、jq的ajax不是同一层级的东西。

提问环节:
❓❓❓为什么要在Ajax的基础上升级fetch?
    Ajax是基于XMLHttpRequest对象,XHR本身的架构不够清晰,而且Ajax使用过于繁琐,而且回调函数会造成回调地狱。升级到fetch,脱离的XHR语法更加简洁,基于标准 Promise 实现,支持 async/await。

❓❓❓Http一些不常见的状态码是什么意思?
    只说304,当协商缓存命中的时候就会返回304,告诉客户端可以用缓存。

❓❓❓Cookie在http请求中的作用?
    Cookie除了能做本地存储之外,因为http是无状态的,客户端在和服务端通信的过程中并不能根据http去判断对象的身份,所以cookie的另外一个作用就是保存状态,让服务端知道客户端干了什么,这也是为什么cookie为什么在每次发送请求的时候都会自动带上。

js引擎单线程

一个进程由一个或多个线程组成,线程是建立在进程的基础上的一次程序运行单位。

提问环节:
❓❓❓为什么设计成单线程?
    我们要知道js主要是实现用户与浏览器的交互,以及操作dom。那么假如js是多线程,那么多个线程修改同一个dom,那么最后浏览器遵循哪个结果呐?所以为了避免产生意外的复杂操作,js只能是单线程。虽然HTML5提出Web Worker标准允许多线程,但是子线程是不允许修改dom的而且完全受主线程全程控制。

常见内置错误编码

1.ReferenceError:引用的变量不存在。
2.TypeErroe:数据类型不正确的错误。
3.RangeError:数据值不在其所允许的范围之内(超出边界)。
4.4.SyntaxErroe:语法错误

链接

# 面试的时候面试官是这样问我跨域的,又学到了

结语

    磨磨唧唧的写了好久,过程中有好多的东西都是之前自己觉得掌握的很好的实际上表述出来的时候才发现自己了解的一知半解。文章里面也没写那些一看大家都知道的,起码都是我之前不知道遇到了才知道东西,也算是给自己查缺补漏了吧,哈哈。文章有什么错误或者理解有偏差的地方,希望大佬们指出,🙏谢谢啦。对于基础知识有总结的同学非常希望能一起讨论讨论还有再学习react的同学,可以加我vx哦lp_18714,同时也在线求一个react大佬带带我,react太难了。😭

截屏2021-11-27 下午10.30.34.png

最后祝各位大佬学习进步,事业有成!🎆🎆🎆