前端知识必备

201 阅读11分钟

js相关

闭包

闭包说的通俗一点就是可以在函数外部访问到函数内部的变量。因为正常情况下函数外部是访问不到函数内部作用域变量的,作用域分为了全局.函数级.块级作用域.

表象判断是不是闭包:函数嵌套函数,内部函数被 return 内部函数调用外层函数的局部变量

优点:可以隔离作用域,不造成全局污染
缺点:由于闭包长期驻留内存,则长期这样会导致内存泄露
如何解决内存泄露:将暴露外部的闭包变量置为 null
适用场景:我在性能优化的过程中,使用节流防抖函数就是闭包的原理,导航栏获取下标的使用

image.png 项目后期的时候进行了一定的性能优化.比方说:对级联菜单的功能,使用 了节流防抖的方式,减少了 http 请求,
或者一个注册模块的时候,要实时(oninput)去后台验证用户名是否已注册 给出相应的提示.

闭包的应用的时候,处理能够进行导航栏菜单获取当前下标.
巧妙的转嫁到节流防抖函数上.如果忘了节流防抖函数可以提一下 lodash 函数库.

垃圾回收机制

通俗的说法就是js是一个弱类型语言.不用的就会被js引擎的垃圾回收机 制(GC)自动回收掉,据我了解是用了一种标记回收的方式吧,就是从根节点上把 能访问到的都做上标记.定期检测.没有标记的就被清除掉.释放掉内存.因为闭包的一直被引用着就一直有标记就回收不了了.可以置为 null 就可以,当然垃圾回收机制的打标记.js 引擎也做了一定的优化.来提升性能.大体上我就了解这些 [再有兴趣的可以参考 segmentfault.com/a/119000001…]

垃圾回收机制有没有了解
js 引擎在进行垃圾标识的时候,为了提高性能,有一些处理方式, 我记得好像能分代清除(哪些不常活跃,就不检查)另一个叫空闲时间回收 增量回收,我的理解就是分开,类似异步的检测来理解,是否放开

说一下对js中原型链的理解

原型链是理解 JS 面向对象很重要的一点,这里主要涉及到两个点,一是 __proto__ ,二是 prototype,
举个例子吧,这样还好说点,例如:我用 function创建一个 Person 类,然后用 new Person 创建一个对象的实例假如叫 p1 吧,在Person 类的原型 prototype 添加一个方法,例如:play 方法,那对象实例 p1如何查找到 play 这个方法呢,有一个查找过程,具体流程是这样的:

首先在 p1 对象实例上查找是否有 play 方法,如果有则调用执行,如没 有则用 p1.__proto__(_proto_是一个指向的作用,指向上一层的原型)往创建p1 的类的原型上查找,也就是说往 Person.prototype 上查找,如果在Person.prototype 找 到 play 方 法 则 执 行 , 否 则 继 续 往 上 查 找 , 则 用Person.prototye.__proto__继续往上查找,找到 Object.prototype,如果Object.prototype有play方法则执行之,否则用Object.prototype.__proto__继续再往上查找,但 Object.prototpye.__proto__上一级是 null,也就是原型 链的顶级,结束原型链的查找,这是我对原型链的理解

Tips 附加分.你像我最近做的项目里面有些公共常用的方法.我放到了 utils 里面.然后在 vue 的 main.js 里面导入进来.然后通过 Vue.prototype.方法名绑定到原型上,就利用了这种原型链的原理

说一下JS继承(含ES6的)?问有两个类A和B,B怎么继承A?

JS 继承实现方式也很多,主要分 ES5 和 ES6 继承的实现

先说一下 ES5 是如何实现继承的

ES5 实现继承主要是基于 prototype 来实现的,具体有三种方法

一是原型链继承:即

B.prototype=new A()  

二是借用构造函数继承(call 或者 apply 的方式继承)

function B(name,age) {
    A.call(this,name,age)
}  

三是组合继承
组合继承是结合第一种和第二种方式

再说一下 ES6 是如何实现继承的
ES6 继承是目前比较新,并且主流的继承方式,用 class 定义类用 extends继承类用 super()表示父类,

例如:创建 Aclass A 
constructor() {
//构造器代码,new 时自动执行
}
方法 1( ) { //A 类的方法 }
方法 2( ) { //A 类的方法 }
}
创建 B 类并继承 Aclass B extends A {
constructor() {
super() //表示父类
}
}
实例化 B 类: var b1=new B( )
b1.方法 1( )

说一下 JS 原生事件如何绑定

JS 原生绑定事件我知道的有三种吧:

一是 html 事件处理程序(这里我觉得直接写到标签上的更接地气,嘎嘎)
二是 DOM0 级事件处理程序
三是 DOM2 级事件处理程序

html

其中:html 事件现在早已不用了,就是在 html 各种标签上直接添加事件,类似 于 css 的行内样式,缺点是不好维护,因为散落在标签中,也就是耦合度太高
例如:<button onclick=”事件处理函数”>点我</button>

DOMO

第二类是 DOM0 级事件,目前在 PC 端用的还是比较多的绑定事件方式,兼容性也 好,主要是先获取 dom 元素,然后直接给 dom 元素添加事件

例如:
var btn=document.getElementById(‘id 元素’)
btn.onclick=function() {
//要处理的事件逻辑
}

DOM0 事件如何移除呢?很简单:btn.onclick=null;置为空就行
优点:兼容性好
缺点:只支持冒泡,不支持捕获

DOM2

第三类是 DOM2 级事件,移动端用的比较多,也有很多优点,提供了专门的绑定和 移除方法
例如:
var btn=document.getElementById(‘id 元素’)
//绑定事件
btn.addEventListener(‘click’,绑定的事件处理函数名,false)
//移除事件
btn.removeEventListener(‘click’,要移除的事件处理函数名,false)
优点:支持给个元素绑定多个相同事件,支持冒泡和捕获事件机制

说一下 JS 原生常用 dom 操作方法

js 原生 dom 操作方法有?
查找
getElementByid,
getElementsByTagName,
querySelector,
querySelectorAll
插入
appendChild,
insertBefore
删除
removeChild
克隆
cloneNode
设置和获取属性
setAttribute("属性名","值"),
getAttibute("属性名")

说一下 ES6 新增特性?

ES6 新增特性常用的主要有:
let/const,箭头函数模板字符串解构赋值模块的导入(import)和导出(exportdefault/export),Promise,还有一些数组字符串的新方法,其实有很多,我平时常用的就这些.

相关问题:
1、Const 声明一个常量.是对象的话.能不能修改里面的值?
可以的. 因为这个常量的不能修改指的是指向的地址不能修改,我只是修改里面的内容.堆空间地址是不变,所以可以修改

2、箭头函数里面的 this 指向是什么样子的?
箭头函数和普通函数的区别是:
箭头函数指向定义时.定义的时候 this 就确定了.指向它的外层.
普通函数是指向调用时.谁调用的我.this 就指向的谁

模版字符串:${ 变量 }
解构赋值: 数组解构,函数参数解构 对象解构

JS 设计模式有哪些(单例模式观察者模式等)

JS 设计模式有很多,但我知道的有单例模式,观察者模式

单例模式:就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在 JavaScript 里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

观察者模式: 观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化

说一下你对 JS 面向对象的理解

JS 面向对象主要基于 function 来实现的,通过 function 来模拟类,通过prototype 来实现类方法的共享,跟其他语言有着本质的不同,自从有了 ES6后,把面向对象类的实现更像后端语言的实现了,通过 class来定义类,通过extends 来继承父类,其实 ES6 类的实现本质上是一个语法糖,不过对于开发简单了好多。

说一下 JS 数组常用方法

在 开 发 中 , 数 组 使 用 频 率 很 频 繁 , JS 数 组 常 用 方 法 有:push,pop,unshift,shift,splice,join,concat,forEach,filter,map,sort,some,every,includes reduce 好多,不过都是平时开发中很常用的方法,大家可以补充一点儿 es6 的

说一下 JS 数组内置遍历方法有哪些和区别

JS 数组内置遍历(遍历就是循环的意思)方法主要有:

forEach:这个方法是为了取代 for 循环遍历数组的,返回值为 undefined 例如:

let arrInfo=[4,6,6,8,5,7,87]
arrInfo.forEach((item,index,arr)=>{
//遍历逻辑
})
其中:
item 代码遍历的每一项,
index:代表遍历的每项的索引,
arr:代表数组本身

filter:是一个过滤遍历的方法,如果返回条件为 true,则返回满足条件为 true 的新数组

let arrInfo=[4,16,6,8,45,7,87]
let resultArr=arrInfo.filter((item,index,arr)=>{
    //例如返回数组每项值大于 9 的数组
    return item>9
})

map:这个 map 方法主要对数组的复杂逻辑处理时用的多,特别是 react 中遍历数据,也经常用到,写法和 forEach 类似

some:这个 some 方法用于只要数组中至少存在一个满足条件的结果,返回值就为 true,否则返回 fasel, 写法和 forEach 类似

every:这个 every 方法用于数组中每一项都得满足条件时,才返回 true,否则返回 false, 写法和 forEach 类似

数组的 reduce 方法

// 这是一个做数组每一项值的累加的方法吧.就是可以把
前面的计算结果与后面的每一项
// 继续合并 当时做了一个功能就用到了这个 reduce
的方法.
// 一个分类的选择左侧是分类的目录,右侧选择数量.
然后左侧分类上要有选择的总量
// 有点儿像美团上面的店铺点餐那种的 里面的时候
就通过计算属性计算后端返回的选中的值
// 里面通 过 reduce 拿 到分 类里 面,各个 商品 的选项
reduce 里面是一个函数
// 函数的第一个参数是每次累加的和,第二个参数是数组
下一个元素,然后是下标
// 还可以给他提供初始值

说一下 JS 作用域和作用域链

JS作用域也就是 JS 识别变量的范围,作用域链也就是 JS 查找变量的顺序

作用域

先说作用域,JS 作用域主要包括全局作用域、局部作用域和 ES6 的块级作用域
全局作用域:也就是定义在 window 下的变量范围,在任何地方都可以访问
局部作用域:是只在函数内部定义的变量范围
块级作用域:简单来说用 let 和 const 在任意的代码块中定义的变量都认为是块级作用域中的变量,例如在 for 循环中用 let 定义的变量,在 if 语句中 用 let 定义的变量等等 注:尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对 bug查找不利。

作用域链

而所谓的作用域链就是由最内部的作用域往最外部,查找变量的过程. 形成的链条就是作用域链 原型链往外层找,找到 null 作用域链,从找到的地方外外层,一直找到 window

说一下从输入URL到页面加载完中间发生了什么?

DOM 树 构建一个样式表.组合成一颗 render 树,页面经过重绘(重塑)和回流 的过程. 答:大致过程是这样的:

  1. DNS 解析
  2. TCP 连接
  3. 发送 HTTP 请求
  4. 服务器处理请求并返回需要的数据
  5. 浏览器解析渲染页面
  6. 连接结束 输入了一个域名,域名要通过 DNS 解析找到这个域名对应的服务器地址(ip),通过 TCP 请求链接服务,通过 WEB 服务器(apache)返回数据,浏览器根据返回数据构建 DOM 树,再把 css 形成一个样式表.这两个东西结合,变成了 render 树.页面上通过重绘和回流的过程,渲染出页面来

说一下 JS 事件代理(也称事件委托)是什么,及实现原理?

$("ul").on("click","li",function(){})
能够给动态添加的元素绑定事件

JS 事件代理就是通过给父级元素(例如:ul)绑定事件,不给子级元素(例如:li)绑定事件,然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,在原生 js 里面是通过 event 对象的 targe 属性实现

var ul = document.querySelector("ul");
ul.onclick = function(e){//e 指 event,事件对象
    var target = e.target || e.srcElement; //target 获取触发事件的目标(li)
    if(target.nodeName.toLowerCase() == 'li'){//目标(li)节点名转小写
    字母,不转的话是大写字母
    alert(target.innerHTML)
   }
}

jq 方式实现相对而言简单 $(“ul”).on(“click”,“li”,function(){//事 件逻辑}) 其中第二个参数指的是触发事件的具体目标,特别是给动态添加的 元素绑定事件,这个特别起作用

说一下 JS 数据类型有哪些

JS 数据类型有:

基本数据类型:number,string,Boolean,null,undefined,
ES6新增 symbol,bigInt

复合类型:Object,function,Array

说一下 call,apply,bind 区别

call,apply,bind 主要作用都是改变 this 指向的,但使用上略有区别,说 一下区别:

call 和 apply 的主要区别是在传递参数上不同,
call后面传递的参数是以逗号的形式分开的
apply 传递的参数是数组形式 [Apply 是以 A 开头的, 所以应该是跟 Array(数组)形式的参数]
bind 返回的是一个函数形式,如果要执行,则后面要再加一个小括号
例如:bind(obj,参数 1,参数 2,)(),bind 只能以逗号分隔形式,不能是数组形式

git相关

1、 你们公司项目是如何管理的?
答:主要通过 git 来进行项目版本控制的
2、 说几个 git 常用命令
答:我工作中常用的有 git add ,git status,git commit –m,git push,git pull 等
3、说一下多人操作同一个文件,如果出现冲突该如何解决
答:当遇到多人协作修改同一个文件时出现冲突,我先将远程文件先 git pull 下来,手动修改冲突代码后,再 git add ,git commit,git push 再上传到远程 仓库。如果 pull 也 pull 不下来提示冲突的话,可以先通过 git stash 暂存下 来,然后再 pull 拉取,然后 git stash pop,取出原来写的,手动修改,然后提交

Ajax相关

(1)说一下你是如何与后端进行数据交互的

答:我和后端通过 ajax 来进行数据交互的,通过统一制定的接口文档,来实现前 后端高效开发,如果接口文档不能详细说明,或者接口文档上的参数请求不出数据, 我会主动和后端工程师沟通,直到完成跟接口相关的业务开发。当然这其中为了验 证一些接口问题,会用到一些辅助工具,比方说,runapi 这种在线测试工具

(2) 如果后端数据接口没有准备好,你是如何工作的

答:如果后端接口还没有准备好,我会和后端工程师沟通,通过制定接口返回数据 的格式,然后前端通过一些mock数据的工具(上家公司使用的是easymock,贼简单) 来批量生成假数据,可以让前端和后端同时开发,而无需等待后端数据接口写好再 开发,这样提升项目整体的开发效率

(3) 说一下你对 ajax 的同源策略的理解

答:ajax 同源策略是因为安全的考虑,ajax 不允许访问不同域名下的资源即所谓 同源策略的意思。

(4) 说一下什么情况下会产生跨域及产生跨域的解决方案和实现原理?

答:产生跨域的情况有:不同协议,不同域名,不同端口以及域名和 ip 地址的访 问都会产生跨域。

跨域的解决方案目前有三种主流解决方案:

跨域是浏览器做出的限制,和后端没关系
1、jsonp
jsonp 实现原理:主要是利用动态创建 script 标签请求后端接口地址,然后传递 callback 参数,后端接收 callback,后端经过数据处理,返回 callback 函数调用的形式,callback 中的参数就是 json

2、 通过代理的方式(前端代理和后端代理)
前端代理我在 vue 中使用那个 vue.config.js 里面配置一个proxy,里面有个 target 属性指向跨域链接.修改完重启项目就可以了.实际上就是启动了一个代理服务器.绕开同源策略,在请求的时候,通过代理服务器获取到数据再转给浏览器

3、 CORS
CORS 全称叫跨域资源共享,主要是后台工程师设置后端代码来达到前端跨域请求的

注:现在主流框架都是用代理和 CORS 跨域实现的

说一下原生 ajax 的交互过程(即流程)

答:交互流程:
1、先创建 XHR 对象即 XMLHttpRequest() 2、然后 open 准备发送,open 中有三个参数一是提交方式 get 和 post,二是接口地址,三是同步和异步
3、第三步是用 send 发送
4、第四步再发送的过程中通过onreadystatechange来监听接收的回调函数,可以通过判断 readyState==4 和 status==200 来判断是否成功返回,然后通过 responseText 接收成功返回的数据

说一下你对同步和异步的理解

同步就可以说代码是从上往下来执行的。
异步可以认为成有些代码是并行执行吧,耗时的任务。不会阻塞后面代码的执行。

异步解决方案主要有三个:

  1. 回调函数
  2. promise(重点掌握)
  3. generator(了解)
  4. async 和 await(重点掌握) 其实这一块涉及到了一个 js 的运行机制吧。哪些异步的任务都开启了异步的线程队列来处理。最后放入异步队列里面。主线程执行完毕后从异步队列里面取出最靠前的执行。就是事件循环机制(eventLoop);

下面这块不用主动回答:
那如果 ajax 请求.我想要用同步的方式来执行.怎么办?
Ajax 原生的话,open 方法的后面设置成 false;jq 里面有一个 async 参数设置成 false 如果,我想同时执行多个 ajax 不管前后顺序,但是都执行完之后再做操作怎么办? Promise.All 方法

事件循环机制 EventLoop 有没有听说过:

这个东西我研究过.是 js的一个底层运行原理,js是单线程的,但是也有一些耗时任务,会影响执行效率.代码都在主线程中执行,当遇见你像ajax请求.setTimeout 定时器时候,会单独开启异步线程.异步线程耗时之后会推入异步队列中等待执行.然后当主线程执行完毕之后.会到异步队列中取出到主线程中执行.然后再去异步队列中取第二个.这个来回取的过程就是您所说的事件循环(eventLoop)吧

image.png

微任务宏任务

问题一般是.promise 和 setTimeout 谁先执行?
这里面的执行.是牵扯到一个微任务宏任务的执行顺序的问题.你像 setTimeout setInterval 都是任务的.promise .then. catch 任务的,当这个任务执行的时候,会先执行宏任务里面的代码,然后在执行宏任务中微任务的代码.只有当前宏任务执行完毕,才会去执行下一个宏任务.我就是这样理解的.

ajax 缓存如何解决?

答:通过在文件名后面添加随机数(也称为文件指纹)来实现,主要原理是浏览器对访问过的文件,首先会检测第二次请求的文件 url 在浏览器是否缓存过,如果缓存过就使用,否则如果是一个新的文件 url,则从服务器重新请求

说一下 javaScript 原生,jQuery,vue,react,小程序 ajax 与后台交互主要用的什么技术

1、javaScript 原生 Ajax:用的是 XMLHttpRequest 对象
2、jQuery 中的 Ajax: $.ajax(),$.getJSON(),$.get(),$.post()
3、vue 中的 Ajax:vue-resource(vue1.x 中用),axios(主流)
微信 小程 序 Ajax:用 的 是小 程序 内置 的 wx.request()写法 和 jquery 的$.ajax()类似,参数 url,success,data,method,fail 等

说一下你对 http 状态码的了解多少?

1xx(临时响应)
表示临时响应并需要请求者继续执行操作的状态代码

2xx (成功)
表示成功处理了请求的状态码。

常见的 2 开头的状态码有:200 – 服务器成功返回网页

3xx (重定向)
表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向

常见的 3 字开头的状态码有:
301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应 时,会自动将请求者转到新位置。
302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应 继续使用原有位置来进行以后的请求。
304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响 应时,不会返回网页内容。

4xx(请求错误)
这些状态代码表示请求可能出错,妨碍了服务器的处理。

常见的 4 字开头的状态有:
404 – 请求的网页不存在
401 没权限吧.当时在我们项目里面的时候 token 值过期就是返回401.我在响应拦截器做了处理

5xx(服务器错误)
这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

常见的以 5 开头的状态码有:
500 (服务器内部错误) 服务器遇到错误,无法完成请求。
503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这 只是暂时状态。

你上家公司项目是前后端分离的吗

对,是前后端分离的项目,但开始不是,开始前后端代码混在一起写的,后来 重构成前后端分离的项目,方便前端和后端自己维护的代码。

注:所谓前后端分离就是前后端职责明晰:后端只提供数据接口地址返回 json 数据,前端用 ajax 请求 json 数据,页面的所有业务逻辑都是前端来写,让前端控 制力更强,并且减轻后端逻辑处理压力。

深拷贝,浅拷贝

我觉得深拷贝和浅拷贝就是对一个数据类型的操作吧,因为在工作的过程中, 我们经常要去操作一些复杂数据类型,当我们对一个数据类型拷贝出一个副本来,修改了副本之后,原数据对象发送改变,就是浅拷贝,原来的数据类型不发生改变,就是深拷贝.其实这个地方就是涉及一个堆栈内存的问题.因为引用 数据类型是存储在堆内存里面的,保存的是一个内存地址的指针指向..浅拷贝 就是指针指向相同.所以修改了其中一个,另一个也是访问的堆内存里面的相 同位置.所以都会发生变换.深拷贝相当于把里面的引用类型,重新在堆内存里 面开辟了一块空间.有了新的一个地址指针的指向.就相互不影响了.项目里面 的时候,我经常使用 JSON.parse(JSON.stringify(深拷贝的对象))的方式来实现吧.也可以用一个递归的方式.来实现,用的多的话.封装成一个函数,放到 utils.js 中,在引用的地方导入调用.补充一下引申问题(不用主动回答的):JSON.parse 方式有没有什么缺点. 一般情况下都没问题.但是你像函数,正则,时间对象,undefined null 拷贝是有问题的.. 怎么解决?如果包含了一些无法拷贝的情况时,可以使用递归的方式, 就是进去对一些对应的类型做检测. 数组对象重新拓展一份,其它类型都返 回原来的.然后如果值是引用类型,继续递归调用..当时里面还引用了 lodash js 库,直接调用里面的方法. 对象浅拷贝可以理解为改变一个对象属性值,另一个对象属性也会改变,即互相影响 对象深拷贝即就是说改变一个对象属性,另一个对象的属性值不会发生改变 附递归代码:

image.png

深拷贝三种实现方式:

1、递归递归去复制所有层级属性;
2、用JSON对象的parse和stringify实现;
3、借用JQ的extend方法。

浅拷贝三种实现方式

1.基于Object.assign

const obj = Object.assign(item)
obj.children = item.children&&item.children.filter((item, i) => i < 2)
return obj

2.基于for in

 const obj = {}
  for (let key in item) {
  obj[key] = item[key]
 }
 obj.children = item.children &&item.children.filter((item, i) => i < 2)
 return obj

  1. ...原地展开
 return {
    ...item,
    children: item.children &&item.children.filter((item, i) => i < 2)
    }