第⼀部分:基础篇

252 阅读46分钟

三、JavaScript

1 闭包

什么是闭包

  • 闭包就是能够读取其他函数内部变量的函数
  • 闭包是指有权访问另⼀个函数作⽤域中变量的函数,创建闭包的最常⻅的⽅式就是在⼀个函数内创建另⼀个函数,通过另⼀个函数访问这个函数的局部变量,利⽤闭包可以突破作⽤链域

闭包的特性:

  • 函数内再嵌套函数
  • 内部函数可以引⽤外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

说说你对闭包的理解

  • 作用:设计私有的⽅法和变量
  • 优点:避免全局变量的污染
  • 缺点:是闭包会常驻内存,会增⼤内存使⽤量,使⽤不当很容易造成内存泄露 在js中,函数即闭包,只有函数才会产⽣作⽤域的概念

闭包的最⼤⽤处:

  1. 读取函数内部的变量
  2. 让这些变量始终保持在内存中
  3. 封装对象的私有属性和私有⽅法

使⽤闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很⼤,所以不能滥⽤闭包,否则会造成⽹⻚的性能问题,在IE中可能导致内存泄露
  • 解决⽅法是,在退出函数之前,将不使⽤的局部变量全部删除

2 说说你对作⽤域链的理解

  • 作⽤域链的作⽤是保证执⾏环境⾥有权访问的变量和函数是有序的,作⽤域链的变量只能向上访问,变量访问到 window 对象即被终⽌,作⽤域链向下访问变量是不被允许的
  • 简单的说,作⽤域就是变量与函数的可访问范围,即作⽤域控制着变量与函数的可⻅性和⽣命周期

3 JavaScript原型,原型链 ? 有什么特点?

  • 每个对象都会在其内部初始化⼀个属性,就是 prototype (原型),当我们访问⼀个对象的属性时如果这个对象内部不存在这个属性,那么他就会去 prototype ⾥找这个属性,这个
  • prototype ⼜会有⾃⼰的 prototype ,于是就这样⼀直找下去,也就是我们平时所说的原型链的概念
关系: instance.constructor.prototype = instance.proto

特点:JavaScript 对象是通过引⽤来传递的,我们创建的每个新对象实体中并没有⼀份属于⾃⼰的原型副本。当我们修改原型时,与之相关的对象也会继承这⼀改变

  • 当我们需要⼀个属性的时, Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的
  • 就会查找他的 Prototype 对象是否有这个属性,如此递推下去,⼀直检索到 Object 内建对象

4 请解释什么是事件代理

  • 事件代理( Event Delegation ),⼜称之为事件委托。是 JavaScript 中常⽤绑定事件的常⽤技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给⽗元素,让⽗元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使⽤事件代理的好处是可以提⾼性能
  • 可以⼤量节省内存占⽤,减少事件注册,⽐如在 table 上代理所有 td 的 click 事件就⾮常棒
  • 可以实现当新增⼦对象时⽆需再次对其绑定

5 Javascript如何实现继承?

  • 构造继承
  • 原型继承
  • 实例继承
  • 拷⻉继承
  • 原型 prototype 机制或 apply 和 call ⽅法去实现较简单,建议使⽤构造函数与原型混合⽅式
function Parent(){

this.name = 'wang';

}

function Child(){

this.age = 28;

}

Child.prototype = new Parent();//继承了Parent,通过原型

var demo = new Child();

alert(demo.age);

alert(demo.name);//得到被继承的属性

6 谈谈This对象的理解

  • this 总是指向函数的直接调⽤者(⽽⾮间接调⽤者)
  • 如果有 new 关键字, this 指向 new 出来的那个对象
  • 在事件中, this 指向触发这个事件的对象,特殊的是, IE 中的 attachEvent 中的this 总是指向全局对象 Window

7 事件模型

W3C 中定义事件的发⽣经历三个阶段:

  1. 捕获阶段( capturing )、
  2. ⽬标阶段( targetin )、
  3. 冒泡阶段( bubbling )
  • 冒泡型事件:当你使⽤事件冒泡时,⼦级元素先触发,⽗级元素后触发
  • 捕获型事件:当你使⽤事件捕获时,⽗级元素先触发,⼦级元素后触发
  • DOM 事件流:同时⽀持两种事件模型:捕获型事件和冒泡型事件
  • 阻⽌冒泡:在 W3c 中,使⽤ stopPropagation() ⽅法;在IE下设置 cancelBubble = true
  • 阻⽌捕获:阻⽌事件的默认⾏为,例如click - <a>后的跳转。在 W3c 中,使⽤preventDefault() ⽅法,在 IE 下设置 window.event.returnValue = false

8 new操作符具体⼲了什么呢?

  1. 创建⼀个空对象,并且 this 变量引⽤该对象,同时还继承了该函数的原型
  2. 属性和⽅法被加⼊到 this 引⽤的对象中
  3. 新创建的对象由 this 所引⽤,并且最后隐式的返回 this

9 Ajax原理

  • Ajax 的原理简单来说是在⽤户和服务器之间加了—个中间层( AJAX 引擎),通过XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后⽤ javascript来操作 DOM ⽽更新⻚⾯。使⽤户操作与服务器响应异步化。这其中最关键的⼀步就是从服务器获得请求数据
  • Ajax 的过程只涉及 JavaScript 、 XMLHttpRequest 和 DOM 。 XMLHttpRequest 是ajax的核⼼机制
/** 1. 创建连接 **/
var xhr = null;

xhr = new XMLHttpRequest()

/** 2. 连接服务器 **/

xhr.open('get', url, true)

/** 3. 发送请求 **/

xhr.send(null);

/** 4. 接受请求 **/

xhr.onreadystatechange = function(){

if(xhr.readyState == 4){

if(xhr.status == 200){

success(xhr.responseText);

} else {

/** false **/

fail && fail(xhr.status);

}

}

}

ajax 有那些优缺点?

优点:

  1. 通过异步模式,提升了⽤户体验.
  2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占⽤.
  3. Ajax 在客户端运⾏,承担了⼀部分本来由服务器承担的⼯作,减少了⼤⽤户量下的服务器负载。
  4. Ajax 可以实现动态不刷新(局部刷新) 缺点:
  5. 安全问题 AJAX 暴露了与服务器交互的细节。
  6. 对搜索引擎的⽀持⽐较弱。
  7. 不容易调试

10 如何解决跨域问题

  1. ⾸先了解下浏览器的同源策略 同源策略 /SOP(Same origin policy) 是⼀种约定,由Netscape公司1995年引⼊浏览器,它是浏览器最核⼼也最基本的安全功能
  2. 如果缺少了同源策略,浏览器很容易受到 XSS 、 CSFR 等攻击
  3. 所谓同源是指"协议+域名+端⼝"三者相同,即便两个不同的域名指向同⼀个ip地址,也⾮同源

怎样解决跨域问题的呢?

  1. 通过jsonp跨域
var script = document.createElement('script');

script.type = 'text/javascript';

// 传参并指定回调执⾏函数为onBack

script.src = 'http://www.....:8080/login?user=admin&callback=onBack';

document.head.appendChild(script);

// 回调执⾏函数

function onBack(res) {

alert(JSON.stringify(res));

}
  1. document.domain + iframe跨域(此⽅案仅限主域相同,⼦域不同的跨域应⽤场景)
1. ⽗窗⼝:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>

<script>
document.domain = 'domain.com';
var user = 'admin';
</script>

2. ⼦窗⼝:(http://child.domain.com/b.html)
document.domain = 'domain.com';
// 获取⽗窗⼝中变量
alert('get js data from parent ---> ' + window.parent.user);
  1. nginx代理跨域
  2. nodejs中间件代理跨域
  3. 后端在头部信息⾥⾯设置安全域名

11 模块化开发怎么做?

⽴即执⾏函数,不暴露私有成员

    var module1 = (function(){
    var _count = 0;
    var m1 = function(){
    //...
    };
    var m2 = function(){
    //...
    };
    return {
    m1 : m1,
    m2 : m2
    };
    })();

12 异步加载JS的⽅式有哪些?

    1. defer,只⽀持 IE
    1. async :
    1. 创建 script ,插⼊到 DOM 中,加载完毕后 callBack

13 那些操作会造成内存泄漏?

  1. 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
  2. setTimeout 的第⼀个参数使⽤字符串⽽⾮函数的话,会引发内存泄漏
  3. 闭包使⽤不当

14 XML和JSON的区别?

  1. 数据体积⽅⾯
  • JSON 相对 于XML 来讲,数据的体积⼩,传递的速度更快些。
  1. 数据交互⽅⾯
  • JSON 与 JavaScript 的交互更加⽅便,更容易解析处理,更好的数据交互
  1. 数据描述⽅⾯
  • JSON 对数据的描述性⽐ XML 较差
  1. 传输速度⽅⾯
  • JSON 的速度要远远快于 XML

15 谈谈你对webpack的看法

  1. WebPack 是⼀个模块打包⼯具,你可以使⽤ WebPack 管理你的模块依赖,并编绎输出模块们所需的静态⽂件。
  2. 它能够很好地管理、打包 Web 开发中所⽤到的 HTML 、Javascript 、 CSS 以及各种静态⽂件(图⽚、字体等),让开发过程更加⾼效。
  3. 对于不同类型的资源, webpack 有对应的模块加载器。
  4. webpack 模块打包器会分析模块间的依赖关系,最后 ⽣成了优化且合并后的静态资源

16 说说你对AMD和Commonjs的理解

  1. CommonJS 是服务器端模块的规范, Node.js 采⽤了这个规范。
  2. CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执⾏后⾯的操作。
  3. AMD 规范则是⾮同步加载模块,允许指定回调函数
  • AMD 推荐的⻛格通过返回⼀个对象做为模块对象
  • CommonJS 的⻛格通过对module.exports 或 exports 的属性赋值来达到暴露模块对象的⽬的

17 常⻅web安全及防护原理

sql 注⼊原理:

  • 就是通过把 SQL 命令插⼊到 Web 表单递交或输⼊域名或⻚⾯请求的查询字符串,最终达到欺骗服务器执⾏恶意的SQL命令 总的来说有以下⼏点:
  1. 永远不要信任⽤户的输⼊,要对⽤户的输⼊进⾏校验,可以通过正则表达式,或限制⻓度,对单引号和双 "-" 进⾏转换等
  2. 永远不要使⽤动态拼装SQL,可以使⽤参数化的 SQL 或者直接使⽤存储过程进⾏数据查询存取
  3. 永远不要使⽤管理员权限的数据库连接,为每个应⽤使⽤单独的权限有限的数据库连接不要把机密信息明⽂存放,请加密或者 hash 掉密码和敏感的信息

XSS原理及防范

  • Xss(cross-site scripting) 攻击指的是攻击者往 Web ⻚⾯⾥插⼊恶意 html 标签或者 javascript 代码。⽐如:攻击者在论坛中放⼀个看似安全的链接,骗取⽤户点击后,窃取 cookie 中的⽤户私密信息;或者攻击者在论坛中加⼀个恶意表单,当⽤户提交表单的时候,却把信息传送到攻击者的服务器中,⽽不是⽤户原本以为的信任站点

XSS防范⽅法

  • ⾸先代码⾥对⽤户输⼊的地⽅和变量都需要仔细检查⻓度和对 ”<”,”>”,”;”,”’” 等字符做过滤;其次任何内容写到⻚⾯之前都必须加以encode,避免不⼩⼼把 html tag 弄出来。这⼀个层⾯做好,⾄少可以堵住超过⼀半的XSS 攻击

XSS与CSRF有什么区别吗?

  • XSS 是获取信息,不需要提前知道其他⽤户⻚⾯的代码和数据包。 CSRF 是代替⽤户完成指定的动作,需要知道其他⽤户⻚⾯的代码和数据包。要完成⼀次 CSRF 攻击,受害者必须依次完成两个步骤
  1. 登录受信任⽹站 A ,并在本地⽣成 Cookie
  2. 在不登出 A 的情况下,访问危险⽹站 B

CSRF的防御

  • 服务端的 CSRF ⽅式⽅法很多样,但总的思想都是⼀致的,就是在客户端⻚⾯增加伪随机数
  • 通过验证码的⽅法

18 ⽤过哪些设计模式?

  1. ⼯⼚模式:
  • ⼯⼚模式解决了重复实例化的问题,但还有⼀个问题,那就是识别问题,因为根本⽆法
  • 主要好处就是可以消除对象间的耦合,通过使⽤⼯程⽅法⽽不是 new 关键字
  1. 构造函数模式:
  • 使⽤构造函数的⽅法,即解决了重复实例化的问题,⼜解决了对象识别的问题,该模式与⼯⼚模式的不同之处在于直接将属性和⽅法赋值给 this 对象;

19 为什么要有同源限制?

  • 同源策略指的是:协议,域名,端⼝相同,同源策略是⼀种安全协议
  • 举例说明:⽐如⼀个⿊客程序,他利⽤ Iframe 把真正的银⾏登录⻚⾯嵌到他的⻚⾯上,当你使⽤真实的⽤户名,密码登录时,他的⻚⾯就可以通过 Javascript 读取到你的表单中 input 中的内容,这样⽤户名,密码就轻松到⼿了。 20 offsetWidth/offsetHeight,clientWidth/clientHeightscrollWidth/scrollHeight的区别
  • offsetWidth/offsetHeight 返回值包含content + padding + border,效果与e.getBoundingClientRect()相同
  • clientWidth/clientHeight 返回值只包含content + padding,如果有滚动条,也不包含滚动条
  • scrollWidth/scrollHeight 返回值包含content + padding + 溢出内容的尺⼨

21 javascript有哪些⽅法定义对象

  1. 对象字⾯量: var obj = {};
  2. 构造函数: var obj = new Object();
  3. Object.create(): var obj = Object.create(Object.prototype);

22 常⻅兼容性问题?

  • png24 位的图⽚在iE6浏览器上出现背景
  • 解决⽅案是做成 PNG8
  • 浏览器默认的 margin 和 padding 不同。
  • 解决⽅案是加⼀个全局的 *{margin:0;padding:0;} 来统⼀,,但是全局效率很低

⼀般是如下这样解决:

body,ul,li,ol,dl,dt,dd,form,input,h1,h2,h3,h4,h5,h6,p{
margin:0;
padding:0;
}
  • IE 下, event 对象有 x , y 属性,但是没有 pageX , pageY 属性
  • Firefox 下, event 对象有 pageX , pageY 属性,但是没有 x,y 属性.

23 说说你对promise的了解

依照 Promise/A+ 的定义, Promise 有四种状态:

  1. pending: 初始状态, ⾮ fulfilled 或 rejected.
  2. fulfilled: 成功的操作.
  3. rejected: 失败的操作.
  4. settled: Promise 已被 fulfilled 或 rejected ,且不是 pending
  • 另外, fulfilled 与 rejected ⼀起合称 settled
  • Promise 对象⽤来进⾏延迟( deferred ) 和异步( asynchronous ) 计算

Promise 的构造函数

构造⼀个 Promise ,最基本的⽤法如下:

var promise = new Promise(function(resolve, reject) {

if (...) { // succeed

resolve(result);

} else { // fails

reject(Error(errMessage));

}

});
  • Promise 实例拥有 then ⽅法(具有 then ⽅法的对象,通常被称为 thenable )。 它的使⽤⽅法如下:
promise.then(onFulfilled, onRejected)
  • 接收两个函数作为参数,⼀个在 fulfilled 的时候被调⽤,⼀个在 rejected 的时候被调⽤,接收参数就是 future , onFulfilled 对应 resolve , onRejected 对应reject

24 你觉得jQuery源码有哪些写的好的地⽅

  • jquery 源码封装在⼀个匿名函数的⾃执⾏环境中,有助于防⽌变量的全局污染,然后通过传⼊ window 对象参数,可以使 window 对象作为局部变量使⽤,好处是当 jquery 中访问 window 对象的时候,就不⽤将作⽤域链退回到顶层作⽤域了,从⽽可以更快的访问window对象。同样,传⼊ undefined 参数,可以缩短查找 undefined 时的作⽤域链
  • jquery 将⼀些原型属性和⽅法封装在了 jquery.prototype 中,为了缩短名称,⼜赋值给了 jquery.fn ,这是很形象的写法
  • 有⼀些数组或对象的⽅法经常能使⽤到, jQuery 将其保存为局部变量以提⾼访问速度
  • jquery 实现的链式调⽤可以节约代码,所返回的都是同⼀个对象,可以提⾼代码效率

25 vue、react、angular

  • Vue.js ⼀个⽤于创建 web 交互界⾯的库,是⼀个精简的 MVVM 。它通过双向数据绑定把 View 层和 Model 层连接了起来。实际的 DOM 封装和输出格式都被抽象为了Directives 和 Filters
  • AngularJS 是⼀个⽐较完善的前端 MVVM 框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注⼊等所有功能,模板功能强⼤丰富,⾃带了丰富的 Angular 指令
  • react React 仅仅是 VIEW 层是 facebook 公司。推出的⼀个⽤于构建 UI 的⼀个库,能够实现服务器端的渲染。⽤了 virtual dom ,所以性能很好

26 Node的应⽤场景

特点:

  1. 它是⼀个 Javascript 运⾏环境
  2. 依赖于 Chrome V8 引擎进⾏代码解释
  3. 事件驱动
  4. ⾮阻塞 I/O
  5. 单进程,单线程 优点:⾼并发(最重要的优点)

缺点:

  • 只⽀持单核 CPU ,不能充分利⽤ CPU
  • 可靠性低,⼀旦代码某个环节崩溃,整个系统都崩溃

27 谈谈你对AMD、CMD的理解

  • CommonJS 是服务器端模块的规范, Node.js 采⽤了这个规范。 CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执⾏后⾯的操作。 AMD 规范则是⾮同步加载模块,允许指定回调函数
  • AMD 推荐的⻛格通过返回⼀个对象做为模块对象, CommonJS 的⻛格通过对module.exports 或 exports 的属性赋值来达到暴露模块对象的⽬的

es6模块 CommonJS、AMD、CMD

  • CommonJS 的规范中,每个 JavaScript ⽂件就是⼀个独⽴的模块上下⽂( modulecontext ),在这个上下⽂中默认创建的属性都是私有的。也就是说,在⼀个⽂件定义的变量(还包括函数和类),都是私有的,对其他⽂件是不可⻅的。
  • CommonJS 是同步加载模块,在浏览器中会出现堵塞情况,所以不适⽤
  • AMD 异步,需要定义回调 define ⽅式
  • es6 ⼀个模块就是⼀个独⽴的⽂件,该⽂件内部的所有变量,外部⽆法获取。如果你希望外部能够读取模块内部的某个变量,就必须使⽤ export 关键字输出该变量 es6 还可以导出类、⽅法,⾃动适⽤严格模式

28 那些操作会造成内存泄漏

  • 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
  • setTimeout 的第⼀个参数使⽤字符串⽽⾮函数的话,会引发内存泄漏
  • 闭包、控制台⽇志、循环(在两个对象彼此引⽤且彼此保留时,就会产⽣⼀个循环)

29 web开发中会话跟踪的⽅法有哪些

  • cookie
  • session
  • url 重写
  • 隐藏 input
  • ip 地址

30 介绍js的基本数据类型

Undefined 、 Null 、 Boolean 、 Number 、 String

31 介绍js有哪些内置对象

  • Object 是 JavaScript 中所有对象的⽗对象
  • 数据封装类对象: Object 、 Array 、 Boolean 、 Number 和 String
  • 其他对象: Function 、 Arguments 、 Math 、 Date 、 RegExp 、 Error

32 说⼏条写JavaScript的基本规范

  1. 不要在同⼀⾏声明多个变量
  2. 请使⽤ ===/!== 来⽐较 true/false 或者数值
  3. 使用对象字⾯量替代 new Array 这种形式
  4. 不要使⽤全局函数
  5. Switch 语句必须带有 default 分⽀
  6. If 语句必须使⽤⼤括号
  7. for-in 循环中的变量 应该使⽤ var 关键字明确限定作⽤域,从⽽避免作⽤域污染

33 JavaScript有⼏种类型的值

  • 栈:原始数据类型( Undefined , Null , Boolean , Number 、 String )
  • 堆:引⽤数据类型(对象、数组和函数)
  • 两种类型的区别是:存储位置不同;
  • 原始数据类型直接存储在栈( stack )中的简单数据段,占据空间⼩、⼤⼩固定,属于被频繁使⽤数据,所以放⼊栈中存储;
  • 引⽤数据类型存储在堆( heap )中的对象,占据空间⼤、⼤⼩不固定,如果存储在栈中,将会影响程序运⾏的性能;引⽤数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引⽤值时,会⾸先检索其在栈中的地址,取得地址后从堆中获得实体

34 javascript创建对象的⼏种⽅式

  • javascript 创建对象简单的说,⽆⾮就是使⽤内置对象或各种⾃定义对象,当然还可以⽤ JSON ;但写法有很多种,也能混合使⽤
  1. 对象字⾯量的⽅式
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
  1. ⽤ function 来模拟⽆参的构造函数
function Person(){}

var person=new Person();//定义⼀个function,如果使⽤new"实例化",该function可

person.name="Mark";

person.age="25";

person.work=function(){

alert(person.name+" hello...");

}

person.work();
  1. ⽤ function 来模拟参构造函数来实现(⽤ this 关键字定义构造的上下⽂属性)
function Pet(name,age,hobby){

this.name=name;//this作⽤域:当前对象

this.age=age;

this.hobby=hobby;

this.eat=function(){

alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");

}

}

var maidou =new Pet("⻨兜",25,"coding");//实例化、创建对象

maidou.eat();//调⽤eat⽅法
  1. ⽤⼯⼚⽅式来创建(内置对象)
var wcDog =new Object();

wcDog.name="旺财";

wcDog.age=3;

wcDog.work=function(){

alert("我是"+wcDog.name+",汪汪汪......");

}

wcDog.work();
  1. ⽤原型⽅式来创建
function Dog(){}

Dog.prototype.name="旺财";

Dog.prototype.eat=function(){

alert(this.name+"是个吃货");

}

var wangcai =new Dog();

wangcai.eat();
  1. ⽤混合⽅式来创建
function Car(name,price){

this.name=name;

this.price=price;

}

Car.prototype.sell=function(){

alert("我是"+this.name+",我现在卖"+this.price+"万元");

}

var camry =new Car("凯美瑞",27);

camry.sell();

35 eval是做什么的

  • 它的功能是把对应的字符串解析成 JS 代码并运⾏
  • 应该避免使⽤ eval ,不安全,⾮常耗性能( 2 次,⼀次解析成 js 语句,⼀次执⾏)
  • 由 JSON 字符串转换为JSON对象的时候可以⽤ eval,var obj =eval('('+ str +')')

36 null,undefined 的区别

  • undefined 表示不存在这个值。
  • undefined :是⼀个表示"⽆"的原始值或者说表示"缺少值",就是此处应该有⼀个值,但是还没有定义。当尝试读取时会返回 undefined
  • 例如变量被声明了,但没有赋值时,就等于 undefined
  • null 表示⼀个对象被定义了,值为“空值”
  • null : 是⼀个对象(空对象, 没有任何属性和⽅法)
  • 例如作为函数的参数,表示该函数的参数不是对象;
  • 在验证 null 时,⼀定要使⽤=== ,因为 == ⽆法分别 null 和undefined

37 ["1", "2", "3"].map(parseInt) 答案是多少

  • [1, NaN, NaN] 因为 parseInt 需要两个参数 (val, radix) ,其中 radix 表示解析时⽤的基数。
  • map 传了 3 个 (element, index, array) ,对应的 radix 不合法导致解析失败。

38 javascript 代码中的"use strict";是什么意思

  • use strict 是⼀种 ECMAscript 5 添加的(严格)运⾏模式,这种模式使得 Javascript在更严格的条件下运⾏,使 JS 编码更加规范化的模式,消除 Javascript 语法的⼀些不合理、不严谨之处,减少⼀些怪异⾏为

39 JSON 的了解

  • JSON(JavaScript Object Notation) 是⼀种轻量级的数据交换格式
  • 它是基于 JavaScript 的⼀个⼦集。数据格式简单, 易于读写, 占⽤带宽⼩ JSON 字符串转换为JSON对象:
var obj =eval('('+ str +')');

var obj = str.parseJSON();

var obj = JSON.parse(str);

JSON 对象转换为JSON字符串:

var last=obj.toJSONString();

var last=JSON.stringify(obj);

40 js延迟加载的⽅式有哪些

  • defer 和 async 、动态创建 DOM ⽅式(⽤得最多)、按需异步载⼊ js

41 同步和异步的区别

  • 同步:浏览器访问服务器请求,⽤户看得到⻚⾯刷新,重新发请求,等请求完,⻚⾯刷新,新内容出现,⽤户看到新内容,进⾏下⼀步操作
  • 异步:浏览器访问服务器请求,⽤户正常操作,浏览器后端进⾏请求。等请求完,⻚⾯不刷新,新内容也会出现,⽤户看到新内容

42 渐进增强和优雅降级

  • 渐进增强 :针对低版本浏览器进⾏构建⻚⾯,保证最基本的功能,然后再针对⾼级浏览器进⾏效果、交互等改进和追加功能达到更好的⽤户体验。
  • 优雅降级 :⼀开始就构建完整的功能,然后再针对低版本浏览器进⾏兼容

43 defer和async

  • defer 并⾏加载 js ⽂件,会按照⻚⾯上 script 标签的顺序执⾏
  • async 并⾏加载 js ⽂件,下载完成⽴即执⾏,不会按照⻚⾯上 script 标签的顺序执⾏

44 说说严格模式的限制

  • 变量必须声明后再使⽤
  • 函数的参数不能有同名属性,否则报错
  • 不能使⽤ with 语句
  • 禁⽌ this 指向全局对象

45 attribute和property的区别是什么

  • attribute 是 dom 元素在⽂档中作为 html 标签拥有的属性;
  • property 就是 dom 元素在 js 中作为对象拥有的属性。
  • 对于 html 的标准属性来说, attribute 和 property 是同步的,是会⾃动更新的
  • 但是对于⾃定义的属性来说,他们是不同步的

46 谈谈你对ES6的理解

  • 新增模板字符串(为 JavaScript 提供了简单的字符串插值功能)
  • 箭头函数
  • for-of⽤来遍历数据—例如数组中的值
  • arguments 对象可被不定参数和默认参数完美代替。
  • ES6 将promise 对象纳⼊规范,提供了原⽣的 Promise 对象。
  • 增加了 let 和 const 命令,⽤来声明变量。
  • 增加了块级作⽤域。
  • let 命令实际上就增加了块级作⽤域。
  • 还有就是引⼊ module 模块的概念

47 ECMAScript6 怎么写class

  • 这个语法糖可以让有 OOP 基础的⼈更快上⼿ js ,⾄少是⼀个官⽅的实现了
  • 但对熟悉 js 的⼈来说,这个东⻄没啥⼤影响;⼀个 Object.creat() 搞定继承,⽐class 简洁清晰的多

48 什么是⾯向对象编程及⾯向过程编程,它们的异同和优缺点

  • ⾯向过程就是分析出解决问题所需要的步骤,然后⽤函数把这些步骤⼀步⼀步实现,使⽤的时候⼀个⼀个依次调⽤就可以了
  • ⾯向对象是把构成问题事务分解成各个对象,建⽴对象的⽬的不是为了完成⼀个步骤,⽽是为了描叙某个事物在整个解决问题的步骤中的⾏为
  • ⾯向对象是以功能来划分问题,⽽不是步骤

49 ⾯向对象编程思想

  • 基本思想是使⽤对象,类,继承,封装等基本概念来进⾏程序设计

优点:

  1. 易维护:采⽤⾯向对象思想设计的结构,可读性⾼,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是⾮常⽅便和较低成本的
  2. 易扩展
  3. 开发⼯作的重⽤性、继承性⾼,降低重复⼯作量。
  4. 缩短了开发周期

50 对web标准、可⽤性、可访问性的理解

  • 可⽤性(Usability):产品是否容易上⼿,⽤户能否完成任务,效率如何,以及这过程中⽤户的主观感受可好,是从⽤户的⻆度来看产品的质量。可⽤性好意味着产品质量⾼,是企业的核⼼竞争⼒
  • 可访问性(Accessibility):Web内容对于残障⽤户的可阅读和可理解性
  • 可维护性(Maintainability):⼀般包含两个层次,⼀是当系统出现问题时,快速定位并解决问题的成本,成本低则可维护性好。⼆是代码是否容易被⼈理解,是否容易修改和增强功能

51 如何通过JS判断⼀个数组

instanceof ⽅法:instanceof 运算符是⽤来测试⼀个对象是否在其原型链原型构造函数的属性

var arr = [];

arr instanceof Array; // true

constructor ⽅法:constructor 属性返回对创建此对象的数组函数的引⽤,就是返回对象相对应的构造函数

var arr = [];
arr.constructor == Array; //true

最简单的⽅法(jQuery 正在使⽤)

Object.prototype.toString.call(value) == '[object Array]'

// 利⽤这个⽅法,可以写⼀个返回数据类型的⽅法

var isType = function (obj) {

return Object.prototype.toString.call(obj).slice(8,-1);

}

ES5 新增⽅法 isArray()

var a = new Array(123);
var b = new Date();
console.log(Array.isArray(a)); //true
console.log(Array.isArray(b)); //false

52 谈⼀谈let与var的区别

  • let 命令不存在变量提升,如果在 let 前使⽤,会导致报错
  • 如果块区中存在 let 和 const 命令,就会形成封闭作⽤域
  • 不允许重复声明,因此,不能在函数内部重新声明参数

53 map与forEach的区别

  • forEach ⽅法,是最基本的⽅法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容 item 、数组索引 index 、和当前遍历数组 Array
  • map ⽅法,基本⽤法与 forEach ⼀致,但是不同的,它会返回⼀个新的数组,所以在callback需要有 return 值,如果没有,会返回 undefined

54 谈⼀谈你理解的函数式编程

  • 简单说,"函数式编程"是⼀种"编程范式"(programming paradigm),也就是如何编写程序的⽅法论
  • 它具有以下特性:闭包和⾼阶函数、惰性计算、递归、函数是"第⼀等公⺠"、只⽤"表达式"

55 谈⼀谈箭头函数与普通函数的区别?

  1. 函数体内的 this 对象,就是定义时所在的对象,⽽不是使⽤时所在的对象
  2. 不可以当作构造函数,也就是说,不可以使⽤ new 命令,否则会抛出⼀个错误
  3. 不可以使⽤ arguments 对象,该对象在函数体内不存在。如果要⽤,可以⽤ Rest 参数代替
  4. 不可以使⽤ yield 命令,因此箭头函数不能⽤作 Generator 函数

56 谈⼀谈函数中this的指向

  • this的指向在函数定义的时候是确定不了的,只有函数执⾏的时候才能确定this到底指向谁,实际上this的最终指向的是那个调⽤它的对象 《javascript语⾔精髓》中⼤概概括了4种调⽤⽅式:
  1. ⽅法调⽤模式
  2. 函数调⽤模式
  3. 构造器调⽤模式
  4. apply/call调⽤模式

57 异步编程的实现⽅式

  1. 回调函数
  • 优点:简单、容易理解
  • 缺点:不利于维护,代码耦合⾼
  1. 事件监听(采⽤时间驱动模式,取决于某个事件是否发⽣)
  • 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
  • 缺点:事件驱动型,流程不够清晰
  1. 发布/订阅(观察者模式)
  • 类似于事件监听,但是可以通过‘消息中⼼ʼ,了解现在有多少发布者,多少订阅者
  1. Promise对象
  • 优点:可以利⽤then⽅法,进⾏链式写法;可以书写错误时的回调函数;
  • 缺点:编写和理解,相对⽐较难
  1. Generator函数
  • 优点:函数体内外的数据交换、错误处理机制
  • 缺点:流程管理不⽅便
  1. async函数
  • 优点:内置执⾏器、更好的语义、更⼴的适⽤性、返回的是Promise、结构清晰。
  • 缺点:错误处理机制

58 对原⽣Javascript了解程度

  • 数据类型、运算、对象、Function、继承、闭包、作⽤域、原型链、事件、 RegExp 、JSON 、 Ajax 、 DOM 、 BOM 、内存泄漏、跨域、异步装载、模板引擎、前端 MVC 、路由、模块化、 Canvas 、 ECMAScript

59 Js动画与CSS动画区别及相应实现

CSS3 的动画

优点:

  • 在性能上会稍微好⼀些,浏览器会对 CSS3 的动画做⼀些优化
  • 代码相对简单 缺点:
  • 在动画控制上不够灵活
  • 兼容性不好
  • JavaScript 的动画正好弥补了这两个缺点,控制能⼒很强,可以单帧的控制、变换,同时写得好完全可以兼容 IE6 ,并且功能强⼤。对于⼀些复杂控制的动画,使⽤javascript 会⽐较靠谱。⽽在实现⼀些⼩的交互动效的时候,就多考虑考虑 CSS 吧

60 JS 数组和对象的遍历⽅式,以及⼏种⽅式的⽐较

  • 通常我们会⽤循环的⽅式来遍历数组。但是循环是 导致js 性能问题的原因之⼀。⼀般我们会采⽤下⼏种⽅式来进⾏数组的遍历
  1. for in 循环
  2. for 循环
  3. forEach
  • 这⾥的 forEach 回调中两个参数分别为 value , index

  • forEach ⽆法遍历对象

  • IE不⽀持该⽅法; Firefox 和 chrome ⽀持

  • forEach ⽆法使⽤ break , continue 跳出循环,且使⽤ return 是跳过本次循环

  • 这两种⽅法应该⾮常常⻅且使⽤很频繁。但实际上,这两种⽅法都存在性能问题

  • 在⽅式⼀中, for-in 需要分析出 array 的每个属性,这个操作性能开销很⼤。⽤在key 已知的数组上是⾮常不划算的。所以尽量不要⽤ for-in ,除⾮你不清楚要处理哪些属性,例如 JSON 对象这样的情况

  • 在⽅式2中,循环每进⾏⼀次,就要检查⼀下数组⻓度。读取属性(数组⻓度)要⽐读局部变量慢,尤其是当 array ⾥存放的都是 DOM 元素,因为每次读取都会扫描⼀遍⻚⾯上的选择器相关元素,速度会⼤⼤降低

61 gulp是什么

  • gulp 是前端开发过程中⼀种基于流的代码构建⼯具,是⾃动化项⽬的构建利器;它不仅能对⽹站资源进⾏优化,⽽且在开发过程中很多重复的任务能够使⽤正确的⼯具⾃动完成
  • Gulp的核⼼概念:流
  • 流,简单来说就是建⽴在⾯向对象基础上的⼀种抽象的处理数据的⼯具。在流中,定义了⼀些处理数据的基本操作,如读取数据,写⼊数据等,程序员是对流进⾏所有操作的,⽽不⽤关⼼流的另⼀头数据的真正流向
  • gulp正是通过流和代码优于配置的策略来尽量简化任务编写的⼯作 Gulp的特点:
  1. 易于使⽤:通过代码优于配置的策略,gulp 让简单的任务简单,复杂的任务可管理
  2. 构建快速 利⽤ Node.js 流的威⼒,你可以快速构建项⽬并减少频繁的 IO 操作
  3. 易于学习 通过最少的 API ,掌握 gulp 毫不费⼒,构建⼯作尽在掌握:如同⼀系列流管道

62 说⼀下Vue的双向绑定数据的原理

  • vue.js 则是采⽤数据劫持结合发布者-订阅者模式的⽅式,通过Object.defineProperty() 来劫持各个属性的 setter , getter ,在数据变动时发布消息给订阅者,触发相应的监听回调

63 事件的各个阶段

  • 1:捕获阶段 ---> 2:⽬标阶段 ---> 3:冒泡阶段
  • document ---> target ⽬标 ----> document 由此, addEventListener 的第三个参数设置为 true 和 false 的区别已经⾮常清晰了
  • true 表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件
  • false 表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件

64 let var const

let

  • 允许你声明⼀个作⽤域被限制在块级中的变量、语句或者表达式
  • let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
  • 该变量处于从块开始到初始化处理的“暂存死区”

var

  • 声明变量的作⽤域限制在其声明位置的上下⽂中,⽽⾮声明变量总是全局的
  • 由于变量声明(以及其他声明)总是在任意代码执⾏之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明

const

  • 声明创建⼀个值的只读引⽤ (即指针)
  • 基本数据当值发⽣改变时,那么其对应的指针也将发⽣改变,故造成 const 申明基本数据类型时
  • 再将其值改变时,将会造成报错, 例如 const a = 3 ; a = 5 时 将会报错
  • 但是如果是复合类型时,如果只改变复合类型的其中某个 Value 项时, 将还是正常使⽤

65 快速的让⼀个数组乱序

var arr = [1,2,3,4,5,6,7,8,9,10];

arr.sort(function(){

return Math.random() - 0.5;

})

console.log(arr);

66 如何渲染⼏万条数据并不卡住界⾯

这道题考察了如何在不卡住⻚⾯的情况下渲染数据,也就是说不能⼀次性将⼏万条都渲染出来,⽽应该⼀次渲染部分 DOM ,那么就可以通过requestAnimationFrame 来每 16 ms 刷新⼀次

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<meta http-equiv="X-UA-Compatible" content="ie=edge">

<title>Document</title>

</head>

<body>

<ul>控件</ul>

<script>

setTimeout(() => {

// 插⼊⼗万条数据

const total = 100000

// ⼀次插⼊ 20 条,如果觉得性能不好就减少

const once = 20

// 渲染数据总共需要⼏次

const loopCount = total / once

let countOfRender = 0

let ul = document.querySelector("ul");

function add() {

// 优化性能,插⼊不会造成回流

const fragment = document.createDocumentFragment();

for (let i = 0; i < once; i++) {

const li = document.createElement("li");

li.innerText = Math.floor(Math.random() * total);

fragment.appendChild(li);

}

ul.appendChild(fragment);

countOfRender += 1;

loop();

}

function loop() {

if (countOfRender < loopCount) {

window.requestAnimationFrame(add);

}

}

loop();

}, 0);

</script>

</body>

</html>

67 希望获取到⻚⾯中所有的checkbox怎么做?

不使⽤第三⽅框架

var domList = document.getElementsByTagName(‘input’)

var checkBoxList = [];

var len = domList.length; //缓存到局部变量

while (len--) { //使⽤while的效率会⽐for循环更⾼

if (domList[len].type == ‘checkbox’) {

checkBoxList.push(domList[len]);

}

}

68 怎样添加、移除、移动、复制、创建和查找节点

创建新节点

createDocumentFragment() //创建⼀个DOM⽚段

createElement() //创建⼀个具体的元素

createTextNode() //创建⼀个⽂本节点

添加、移除、替换、插⼊

appendChild() //添加

removeChild() //移除

replaceChild() //替换

insertBefore() //插⼊

查找

getElementsByTagName() //通过标签名称

getElementsByName() //通过元素的Name属性的值

getElementById() //通过元素Id,唯⼀性

69 正则表达式

正则表达式构造函数 var reg=new RegExp(“xxx”) 与正则表达字⾯量 varreg=// 有什么不同?匹配邮箱的正则表达式?

  • 当使⽤ RegExp() 构造函数的时候,不仅需要转义引号(即 \ ”表示”),并且还需要双反斜杠(即 \ 表示⼀个 \ )。使⽤正则表达字⾯量的效率更⾼

邮箱的正则匹配:

var regMail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})

70 Javascript中callee和caller的作⽤?

  • caller 是返回⼀个对函数的引⽤,该函数调⽤了当前函数;
  • callee 是返回正在被执⾏的 function 函数,也就是所指定的 function 对象的正⽂
  • 那么问题来了?如果⼀对兔⼦每⽉⽣⼀对兔⼦;⼀对新⽣兔,从第⼆个⽉起就开始⽣兔⼦;假定每对兔⼦都是⼀雌⼀雄,试问⼀对兔⼦,第n个⽉能繁殖成多少对兔⼦?(使⽤ callee 完成)
var result=[];

function fn(n){ //典型的斐波那契数列

if(n==1){

return 1;

}else if(n==2){

return 1;

}else{

if(result[n]){

return result[n];

}else{

//argument.callee()表示fn()

result[n]=arguments.callee(n-1)+arguments.callee(n-2);

return result[n];

}

}

}

71 原⽣ JS 的 window.onload 与 Jquery 的 $(document).ready(function(){}) 有什么不同?如何⽤原⽣JS实现Jq的 ready ⽅法?

  • window.onload() ⽅法是必须等到⻚⾯内包括图⽚的所有元素加载完毕后才能执⾏。
  • $(document).ready() 是 DOM 结构绘制完毕后就执⾏,不必等到加载完毕
function ready(fn){

if(document.addEventListener) { //标准浏览器

document.addEventListener('DOMContentLoaded', function() {

//注销事件, 避免反复触发

document.removeEventListener('DOMContentLoaded',arguments.cal

fn(); //执⾏函数

}, false);

}else if(document.attachEvent) { //IE

document.attachEvent('onreadystatechange', function() {

if(document.readyState == 'complete') {

document.detachEvent('onreadystatechange', arguments.calle

fn(); //函数执⾏

}

});

}

};

72 addEventListener()和attachEvent()的区别

  • addEventListener() 是符合W3C规范的标准⽅法; attachEvent() 是IE低版本的⾮标准⽅法
  • addEventListener() ⽀持事件冒泡和事件捕获; - ⽽ attachEvent() 只⽀持事件冒泡
  • addEventListener() 的第⼀个参数中,事件类型不需要添加 on ; attachEvent() 需要添加 'on'
  • 如果为同⼀个元素绑定多个事件, addEventListener() 会按照事件绑定的顺序依次执⾏,attachEvent() 会按照事件绑定的顺序倒序执⾏

73 获取⻚⾯所有的checkbox

var resultArr= [];

var input = document.querySelectorAll('input');

for( var i = 0; i < input.length; i++ ) {

if( input[i].type == 'checkbox' ) {

resultArr.push( input[i] );

}

}

//resultArr即中获取到了⻚⾯中的所有checkbox

74 数组去重⽅法总结

⽅法⼀、利⽤ES6 Set去重(ES6中最常⽤)

function unique (arr) {

return Array.from(new Set(arr))

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}

⽅法⼆、利⽤for嵌套for,然后splice去重(ES5中最常⽤)

function unique(arr){

for(var i=0; i<arr.length; i++){

for(var j=i+1; j<arr.length; j++){

if(arr[i]==arr[j]){ //第⼀个等同于第⼆个,splice⽅法删除

arr.splice(j,1);

j--;

}

}

}

return arr;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]
  • 双层循环,外层循环元素,内层循环时⽐较值。值相同时,则删去这个值。
  • 想快速学习更多常⽤的 ES6 语法

⽅法三、利⽤indexOf去重

function unique(arr) {



if (!Array.isArray(arr)) {

console.log('type error!')

return

}

var array = [];

for (var i = 0; i < arr.length; i++) {

if (array .indexOf(arr[i]) === -1) {

array .push(arr[i])

}

}

return array;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0,
  • 新建⼀个空的结果数组, for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则 push 进数组

⽅法四、利⽤sort(),根据排序后的结果进⾏遍历及相邻元素⽐对

function unique(arr) {

if (!Array.isArray(arr)) {

console.log('type error!')

return;

}

arr = arr.sort()

var arrry= [arr[0]];

for (var i = 1; i < arr.length; i++) {

if (arr[i] !== arr[i-1]) {

arrry.push(arr[i]);

}

}

return arrry;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", underfind]

⽅法五、利⽤对象的属性不能相同的特点进⾏去重

function unique(arr) {

if (!Array.isArray(arr)) {

console.log('type error!')

return

}

var array= [];

var obj = {};

for (var i = 0; i < arr.length; i++) {

if (!obj[arr[i]]) {

arrry.push(arr[i])

obj[arr[i]] = 1

} else {

obj[arr[i]]++

}

}

return arrry;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined]

console.log(unique(arr))

//[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}]

⽅法六、利⽤includes

function unique(arr) {

if (!Array.isArray(arr)) {

console.log('type error!')

return

}

var array =[];

for(var i = 0; i < arr.length; i++) {

if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值

array.push(arr[i]);

}

}

return array

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}

⽅法七、利⽤hasOwnProperty,判断是否存在对象属性

function unique(arr) {

var obj = {};

return arr.filter(function(item, index, arr){

return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof

})

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,und

console.log(unique(arr))

//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]

⽅法⼋、利⽤filter

function unique(arr) {

return arr.filter(function(item, index, arr) {

//当前元素,在原始数组中的第⼀个索引==当前索引值,否则返回当前元素

return arr.indexOf(item, 0) === index;

});

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

⽅法九、利⽤递归去重

function unique(arr) {

var array= arr;

var len = array.length;

array.sort(function(a,b){ //排序后更加⽅便去重

return a - b;

})

function loop(index){

if(index >= 1){

if(array[index] === array[index-1]){

array.splice(index,1);

}

loop(index - 1); //递归loop,然后数组去重

}

}

loop(len-1);

return array;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr))

//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a",

⽅法⼗、利⽤Map数据结构去重

function arrayNonRepeatfy(arr) {

let map = new Map();

let array = new Array(); // 数组⽤于返回结果

for (let i = 0; i < arr.length; i++) {

if(map .has(arr[i])) { // 如果有该key值

map .set(arr[i], true);

} else {

map .set(arr[i], false); // 如果没有该key值

array .push(arr[i]);

}

}

return array ;

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefi

console.log(unique(arr))

//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a",
  • 创建⼀个空 Map 数据结构,遍历需要去重的数组,把数组的每⼀个元素作为key 存到 Map 中。由于 Map 中不会出现相同的 key 值,所以最终得到的就是去重后的结果

⽅法⼗⼀、利⽤reduce+includes

function unique(arr){

return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cu

}

var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefin

console.log(unique(arr));

⽅法⼗⼆、[...new Set(arr)]

[...new Set(arr)]

//代码就是这么少----(其实,严格来说并不算是⼀种,相对于第⼀种⽅法来说只是简化了代码)

75 (设计题)想实现⼀个对⻚⾯某个节点的拖曳?如何做?(使⽤原⽣JS)

  • 给需要拖拽的节点绑定 mousedown , mousemove , mouseup 事件
  • mousedown 事件触发后,开始拖拽
  • mousemove 时,需要通过 event.clientX 和 clientY 获取拖拽位置,并实时更新位置
  • mouseup 时,拖拽结束
  • 需要注意浏览器边界的情况

76 Javascript全局函数和全局变量

全局变量

  • Infinity 代表正的⽆穷⼤的数值。
  • NaN 指示某个值是不是数字值。
  • undefined 指示未定义的值。

全局函数

  • decodeURI() 解码某个编码的 URI 。
  • decodeURIComponent() 解码⼀个编码的 URI 组件。
  • encodeURI() 把字符串编码为 URI。
  • encodeURIComponent() 把字符串编码为 URI 组件。
  • escape() 对字符串进⾏编码。
  • eval() 计算 JavaScript 字符串,并把它作为脚本代码来执⾏。
  • isFinite() 检查某个值是否为有穷⼤的数。
  • isNaN() 检查某个值是否是数字。
  • Number() 把对象的值转换为数字。
  • parseFloat() 解析⼀个字符串并返回⼀个浮点数。
  • parseInt() 解析⼀个字符串并返回⼀个整数。
  • String() 把对象的值转换为字符串。
  • unescape() 对由 escape() 编码的字符串进⾏解码

77 使⽤js实现⼀个持续的动画效果

定时器思路

var e = document.getElementById('e')

var flag = true;

var left = 0;

setInterval(() => {

left == 0 ? flag = true : left == 100 ? flag = false : ''

flag ? e.style.left = ` ${left++}px` : e.style.left = ` ${left--}px`

}, 1000 / 60)

//兼容性处理

window.requestAnimFrame = (function(){

return window.requestAnimationFrame ||

window.webkitRequestAnimationFrame ||

window.mozRequestAnimationFrame ||

function(callback){

window.setTimeout(callback, 1000 / 60);

};

})();

var e = document.getElementById("e");

var flag = true;

var left = 0;

function render() {

left == 0 ? flag = true : left == 100 ? flag = false : '';

flag ? e.style.left = ` ${left++}px` :

e.style.left = ` ${left--}px`;

}

(function animloop() {

render();

requestAnimFrame(animloop);

})();

使⽤css实现⼀个持续的动画效果

animation:mymove 5s infinite;

@keyframes mymove {

from {top:0px;}

to {top:200px;}

}
  • animation-name 规定需要绑定到选择器的 keyframe 名称。
  • animation-duration 规定完成动画所花费的时间,以秒或毫秒计。
  • animation-timing-function 规定动画的速度曲线。
  • animation-delay 规定在动画开始之前的延迟。
  • animation-iteration-count 规定动画应该播放的次数。
  • animation-direction 规定是否应该轮流反向播放动画

78 封装⼀个函数,参数是定时器的时间,.then执⾏回调函数

function sleep (time) {

return new Promise((resolve) => setTimeout(resolve, time));

}

79 怎么判断两个对象相等?

obj={
a:1,
b:2
}

obj2={
a:1,
b:2
}

obj3={
a:1,
b:'2'
}

可以转换为字符串来判断

JSON.stringify(obj)==JSON.stringify(obj2);//true

JSON.stringify(obj)==JSON.stringify(obj3);//false

80 项⽬做过哪些性能优化?

  1. 减少 HTTP 请求数
  2. 减少 DNS 查询
  3. 使⽤ CDN
  4. 避免重定向
  5. 图⽚懒加载
  6. 减少 DOM 元素数量
  7. 减少 DOM 操作
  8. 使⽤外部 JavaScript 和 CSS
  9. 压缩 JavaScript 、 CSS 、字体、图⽚等
  10. 优化 CSS Sprite
  11. 使⽤ iconfont
  12. 字体裁剪
  13. 多域名分发划分内容到不同域名
  14. 尽量减少 iframe 使⽤
  15. 避免图⽚ src 为空
  16. 把样式表放在 link 中
  17. 把 JavaScript 放在⻚⾯底部

81 浏览器缓存

  • 浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下

  • 先根据这个资源的⼀些 http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;

  • 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另⼀些 request header验证这个资源是否命中协商缓存,称为 http 再验证,如果命中,服务器将请求返回,但不返回资源,⽽是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;

  • 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源; 区别是,强缓存不对发送请求到服务器,但协商缓存会。

  • 当协商缓存也没命中时,服务器就会将资源发送回客户端。

  • 当 ctrl+f5 强制刷新⽹⻚时,直接从服务器加载,跳过强缓存和协商缓存;

  • 当 f5 刷新⽹⻚时,跳过强缓存,但是会检查协商缓存;

强缓存

  • Expires(该字段是 http1.0 时的规范,值为⼀个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)
  • Cache-Control:max-age(该字段是 http1.1 的规范,强缓存利⽤其 max-age 值来判断缓存资源的最⼤⽣命周期,它的值单位为秒)

协商缓存

  • Last-Modified(值为资源最后更新时间,随服务器response返回)
  • If-Modified-Since(通过⽐较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存)
  • ETag(表示资源内容的唯⼀标识,随服务器 response 返回)
  • If-None-Match(服务器通过⽐较请求头部的 If-None-Match 与当前资源的 ETag 是否⼀致来判断资源是否在两次请求之间有过修改,如果没有修改,则命中协商缓存)

82 WebSocket

  • 由于 http 存在⼀个明显的弊端(消息只能有客户端推送到服务器端,⽽服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使⽤轮询,⽽轮询效率过低,并不适合。于是 WebSocket 被发明出来
  • 相⽐与 http 具有以下有点
  • ⽀持双向通信,实时性更强;
  • 可以发送⽂本,也可以⼆进制⽂件;
  • 协议标识符是 ws ,加密后是 wss ;
  • 较少的控制开销。连接创建后, ws 客户端、服务端进⾏数据交换时,协议控制的数据包头部较⼩。在不包含头部的情况下,服务端到客户端的包头只有 2~10 字节(取决于数据包⻓度),客户端到服务端的的话,需要加上额外的4字节的掩码。⽽ HTTP 协议每次通信都需要携带完整的头部;
  • ⽀持扩展。ws协议定义了扩展,⽤户可以扩展协议,或者实现⾃定义的⼦协议。(⽐如⽀
  • 持⾃定义压缩算法等)
  • ⽆跨域问题。

实现⽐较简单,服务端库如 socket.io 、 ws ,可以很好的帮助我们⼊⻔。⽽客户端也只需要参照 api 实现即可

83 尽可能多的说出你对 Electron 的理解

  • 最重要的⼀点, electron 实际上是⼀个套了 Chrome 的 nodeJS 程序

所以应该是从两个⽅⾯说开来

  • Chrome (⽆各种兼容性问题);
  • NodeJS( NodeJS 能做的它也能做)

84 深浅拷⻉

浅拷⻉

  • Object.assign
  • 或者展开运算符

深拷⻉

  • 可以通过 JSON.parse(JSON.stringify(object)) 来解决
let a = {

age: 1,

jobs: {

first: 'FE'

}

}

let b = JSON.parse(JSON.stringify(a))

a.jobs.first = 'native'

console.log(b.jobs.first) // FE

该⽅法也是有局限性的

  • 会忽略 undefined
  • 不能序列化函数
  • 不能解决循环引⽤的对象

85 防抖/节流

防抖

在滚动事件中需要做个复杂计算或者实现⼀个按钮的防⼆次点击操作。可以通过函数防抖动来实现

// 使⽤ underscore 的源码来解释防抖动

/**

* underscore 防抖函数,返回函数连续调⽤时,空闲时间必须⼤于或等于 wait,func 才会执⾏

*

* @param {function} func 回调函数

* @param {number} wait 表示时间窗⼝的间隔

* @param {boolean} immediate 设置为ture时,是否⽴即调⽤函数

* @return {function} 返回客户调⽤函数

*/

_.debounce = function(func, wait, immediate) {

var timeout, args, context, timestamp, result;

var later = function() {

// 现在和上⼀次时间戳⽐较

var last = _.now() - timestamp;

// 如果当前间隔时间少于设定时间且⼤于0就重新设置定时器

if (last < wait && last >= 0) {

timeout = setTimeout(later, wait - last);

} else {

// 否则的话就是时间到了执⾏回调函数

timeout = null;

if (!immediate) {

result = func.apply(context, args);

if (!timeout) context = args = null;

}

}

};

return function() {

context = this;

args = arguments;

// 获得时间戳

timestamp = _.now();

// 如果定时器不存在且⽴即执⾏函数

var callNow = immediate && !timeout;

// 如果定时器不存在就创建⼀个

if (!timeout) timeout = setTimeout(later, wait);

if (callNow) {

// 如果需要⽴即执⾏函数的话 通过 apply 执⾏

result = func.apply(context, args);

context = args = null;

}

return result;

};

};

对于按钮防点击来说的实现

  • 开始⼀个定时器,只要我定时器还在,不管你怎么点击都不会执⾏回调函数。⼀旦定时器结束并设置为 null,就可以再次点击了
  • 对于延时执⾏函数来说的实现:每次调⽤防抖动函数都会判断本次调⽤和之前的时间间隔,如果⼩于需要的时间间隔,就会重新创建⼀个定时器,并且定时器的延时为设定时间减去之前的时间间隔。⼀旦时间到了,就会执⾏相应的回调函数

节流

防抖动和节流本质是不⼀样的。防抖动是将多次执⾏变为最后⼀次执⾏,节流是将多次执⾏变成每隔⼀段时间执⾏

/**

* underscore 节流函数,返回函数连续调⽤时,func 执⾏频率限定为 次 / wait

*

* @param {function} func 回调函数

* @param {number} wait 表示时间窗⼝的间隔

* @param {object} options 如果想忽略开始函数的的调⽤,传⼊{leading: false

* 如果想忽略结尾函数的调⽤,传⼊{trailing: false

* 两者不能共存,否则函数不能执⾏

* @return {function} 返回客户调⽤函数

*/

_.throttle = function(func, wait, options) {

var context, args, result;

var timeout = null;

// 之前的时间戳

var previous = 0;

// 如果 options 没传则设为空对象

if (!options) options = {};

// 定时器回调函数

var later = function() {

// 如果设置了 leading,就将 previous 设为 0

// ⽤于下⾯函数的第⼀个 if 判断

previous = options.leading === false ? 0 : _.now();

// 置空⼀是为了防⽌内存泄漏,⼆是为了下⾯的定时器判断

timeout = null;

result = func.apply(context, args);

if (!timeout) context = args = null;

};

return function() {

// 获得当前时间戳

var now = _.now();

// ⾸次进⼊前者肯定为 true

// 如果需要第⼀次不执⾏函数

// 就将上次时间戳设为当前的

// 这样在接下来计算 remaining 的值时会⼤于0

if (!previous && options.leading === false) previous = now;

// 计算剩余时间

var remaining = wait - (now - previous);

context = this;

args = arguments;

// 如果当前调⽤已经⼤于上次调⽤时间 + wait

// 或者⽤户⼿动调了时间

// 如果设置了 trailing,只会进⼊这个条件

// 如果没有设置 leading,那么第⼀次会进⼊这个条件

// 还有⼀点,你可能会觉得开启了定时器那么应该不会进⼊这个 if 条件了

// 其实还是会进⼊的,因为定时器的延时

// 并不是准确的时间,很可能你设置了2秒

// 但是他需要2.2秒才触发,这时候就会进⼊这个条件

if (remaining <= 0 || remaining > wait) {

// 如果存在定时器就清理掉否则会调⽤⼆次回调

if (timeout) {

clearTimeout(timeout);

timeout = null;

}

previous = now;

result = func.apply(context, args);

if (!timeout) context = args = null;

} else if (!timeout && options.trailing !== false) {

// 判断是否设置了定时器和 trailing

// 没有的话就开启⼀个定时器

// 并且不能不能同时设置 leading 和 trailing

timeout = setTimeout(later, remaining);

}

return result;

};

};

86 谈谈变量提升?

当执⾏ JS 代码时,会⽣成执⾏环境,只要代码不是写在函数中的,就是在全局执⾏环境中,函数中的代码会产⽣函数执⾏环境,只此两种执⾏环境 接下来让我们看⼀个⽼⽣常谈的例⼦, var

b() // call b

console.log(a) // undefined

var a = 'Hello world'

function b() {

console.log('call b')

}

变量提升

  • 这是因为函数和变量提升的原因。通常提升的解释是说将声明的代码移动到了顶部,这其实没有什么错误,便于⼤家理解。但是更准确的解释应该是:在⽣成执⾏环境时,会有两个阶段。第⼀个阶段是创建的阶段,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存⼊内存中,变量只声明并且赋值为 undefined ,所以在第⼆个阶段,也就是代码执⾏阶段,我们可以直接提前使⽤

在提升的过程中,相同的函数会覆盖上⼀个函数,并且函数优先于变量提升

b() // call b second

function b() {

console.log('call b fist')

}

function b() {

console.log('call b second')

}

var b = 'Hello world'
  • 复制代码 var 会产⽣很多错误,所以在 ES6 中引⼊了 let 。 let 不能在声明前使⽤,但是这并不是常说的 let 不会提升, let 提升了,在第⼀阶段内存也已经为他开辟好了空间,但是因为这个声明的特性导致了并不能在声明前使⽤

87 什么是单线程,和异步的关系

  • 单线程 - 只有⼀个线程,只能做⼀件事
  • 原因 - 避免 DOM 渲染的冲突:
    • 浏览器需要渲染 DOM
    • JS 可以修改 DOM 结构
    • JS 执⾏的时候,浏览器 DOM 渲染会暂停
    • 两段 JS 也不能同时执⾏(都修改 DOM 就冲突了)
    • webworker ⽀持多线程,但是不能访问 DOM
  • 解决⽅案 - 异步

88 是否⽤过 jQuery 的 Deferred

  • $.Deferred() 是一个构造函数,用来返回一个链式实用对象方法来注册多个回调,并且调用回调队列,传递任何同步或异步功能成功或失败的状态。

89 ajax、axios、fetch区别

jQuery ajax

$.ajax({

type: 'POST',

url: url,

data: data,

dataType: dataType,

success: function () {},

error: function () {}

});

优缺点:

  • 本身是针对 MVC 的编程,不符合现在前端 MVVM 的浪潮
  • 基于原⽣的 XHR 开发, XHR 本身的架构不清晰,已经有了 fetch 的替代⽅案
  • JQuery 整个项⽬太⼤,单纯使⽤ ajax 却要引⼊整个 JQuery ⾮常的不合理(采取个性化打包的⽅案⼜不能享受CDN服务)

axios

method: 'post',

url: '/user/12345',

data: {

firstName: 'Fred',

lastName: 'Flintstone'

}

})

.then(function (response) {

console.log(response);

})

.catch(function (error) {

console.log(error);

});

优缺点:

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • ⽀持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • ⾃动转换 JSON 数据
  • 客户端⽀持防⽌ CSRF/XSRF

fetch

try {

let response = await fetch(url);

let data = response.json();

console.log(data);

} catch(e) {

console.log("Oops, error", e);}

优缺点:

  • fetcht 只对⽹络请求报错,对 400 , 500 都当做成功的请求,需要封装去处理
  • fetch 默认不会带 cookie ,需要添加配置项
  • fetch 不⽀持 abort ,不⽀持超时控制,使⽤ setTimeout 及 Promise.reject 的实现的超时控制并不能阻⽌请求过程继续在后台运⾏,造成了量的浪费
  • fetch 没有办法原⽣监测请求的进度,⽽XHR可以