js相关
闭包
闭包说的通俗一点就是可以在函数外部访问到函数内部的变量。因为正常情况下函数外部是访问不到函数内部作用域变量的,作用域分为了全局.函数级.块级作用域.
表象判断是不是闭包:函数嵌套函数,内部函数被 return 内部函数调用外层函数的局部变量
优点:可以隔离作用域,不造成全局污染
缺点:由于闭包长期驻留内存,则长期这样会导致内存泄露
如何解决内存泄露:将暴露外部的闭包变量置为 null
适用场景:我在性能优化的过程中,使用节流防抖函数就是闭包的原理,导航栏获取下标的使用
项目后期的时候进行了一定的性能优化.比方说:对级联菜单的功能,使用
了节流防抖的方式,减少了 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()表示父类,
例如:创建 A 类
class A {
constructor() {
//构造器代码,new 时自动执行
}
方法 1( ) { //A 类的方法 }
方法 2( ) { //A 类的方法 }
}
创建 B 类并继承 A 类
class 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 树,页面经过重绘(重塑)和回流 的过程. 答:大致过程是这样的:
- DNS 解析
- TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回需要的数据
- 浏览器解析渲染页面
- 连接结束 输入了一个域名,域名要通过 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 接收成功返回的数据
说一下你对同步和异步的理解
同步就可以说代码是从上往下来执行的。
异步可以认为成有些代码是并行执行吧,耗时的任务。不会阻塞后面代码的执行。
异步解决方案主要有三个:
- 回调函数
- promise(重点掌握)
- generator(了解)
- async 和 await(重点掌握) 其实这一块涉及到了一个 js 的运行机制吧。哪些异步的任务都开启了异步的线程队列来处理。最后放入异步队列里面。主线程执行完毕后从异步队列里面取出最靠前的执行。就是事件循环机制(eventLoop);
下面这块不用主动回答:
那如果 ajax 请求.我想要用同步的方式来执行.怎么办?
Ajax 原生的话,open 方法的后面设置成 false;jq 里面有一个 async 参数设置成 false
如果,我想同时执行多个 ajax 不管前后顺序,但是都执行完之后再做操作怎么办?
Promise.All 方法
事件循环机制 EventLoop 有没有听说过:
这个东西我研究过.是 js的一个底层运行原理,js是单线程的,但是也有一些耗时任务,会影响执行效率.代码都在主线程中执行,当遇见你像ajax请求.setTimeout 定时器时候,会单独开启异步线程.异步线程耗时之后会推入异步队列中等待执行.然后当主线程执行完毕之后.会到异步队列中取出到主线程中执行.然后再去异步队列中取第二个.这个来回取的过程就是您所说的事件循环(eventLoop)吧
微任务宏任务
问题一般是.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
库,直接调用里面的方法.
对象浅拷贝可以理解为改变一个对象属性值,另一个对象属性也会改变,即互相影响
对象深拷贝即就是说改变一个对象属性,另一个对象的属性值不会发生改变
附递归代码:
深拷贝三种实现方式:
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
- ...原地展开
return {
...item,
children: item.children &&item.children.filter((item, i) => i < 2)
}