前端面试题汇总(JavaScript面试纯干货)
1 闭包
闭包就是能够读取其他函数内部变量的函数
闭包是指有权访问另⼀个函数作⽤域中变量的函数,创建闭包的最常⻅的⽅式就是在⼀个函数内创建另⼀个函数,通过另⼀个函数访问这个函数的局部变量,利⽤闭包可以突破作⽤。
链域
闭包的特性:
函数内再嵌套函数
内部函数可以引⽤外层的参数和变量
参数和变量不会被垃圾回收机制回收
说说你对闭包的理解
使⽤闭包主要是为了设计私有的⽅法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增⼤内存使⽤量,使⽤不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产⽣作⽤域的概念
闭包的最⼤⽤处有两个,⼀个是可以读取函数内部的变量,另⼀个就是让这些变量始终保持在内存中
闭包的另⼀个⽤处,是封装对象的私有属性和私有⽅法
**好处:**能够实现封装和缓存等;
**坏处:**就是消耗内存、不正当使⽤会造成内存溢出的问题
使⽤闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很⼤,所以不能滥⽤闭包,否则会造成⽹⻚的性能问题,在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
中定义事件的发⽣经历三个阶段:捕获阶段(capturing )、
⽬标阶段 ( targetin )、
冒泡阶段( bubbling )
冒泡型事件:
当你使⽤事件冒泡时,⼦级元素先触发,⽗级元素后触发.
捕获型事件:
当你使⽤事件捕获时,⽗级元素先触发,⼦级元素后触发
DOM
事件流:
同时⽀持两种事件模型:捕获型事件和冒泡型事件
阻⽌冒泡:
在W3c
中,使⽤ stopPropagation()
⽅法;在IE
下设置 cancelBubble =
true
**阻⽌捕获:**阻⽌事件的默认⾏为,例如click - <a>
后的跳转。在 W3c
中,使⽤
preventDefault()
⽅法,在IE
下设置 window.event.returnValue = false
8 new操作符具体⼲了什么呢?
创建⼀个空对象,并且this
变量引⽤该对象,同时还继承了该函数的原型
属性和⽅法被加⼊到this
引⽤的对象中
新创建的对象由 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 有那些优缺点?
优点:
通过异步模式,提升了⽤户体验. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占⽤.
Ajax 在客户端运⾏,承担了⼀部分本来由服务器承担的⼯作,减少了⼤⽤户量下的服 务器负载。
Ajax
可以实现动态不刷新(局部刷新)
缺点:
安全问题AJAX
暴露了与服务器交互的细节。
对搜索引擎的⽀持⽐较弱。
不容易调试。
10 如何解决跨域问题?
⾸先了解下浏览器的同源策略 同源策略/SOP(Same origin policy)
是⼀ 种约定,由Netscape
公司1995
年引⼊浏览器,它是浏览器最核⼼也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS 、 CSFR
等攻击。 所谓同源是指"协议+域名+端⼝"三者相同,即便两个不同的域名指向同⼀个ip
地址,也⾮同源.
那么怎样解决跨域问题的呢?
通过jsonp跨域
document.domain + iframe跨域
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)); }
document.domain + iframe跨域
//父窗口
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe> <script>
document.domain = 'domain.com';
var user = 'admin';
</scrip
//子窗口
document.domain = 'domain.com';
// 获取⽗窗⼝中变量
alert('get js data from parent ---> ' + window.parent.user);
nginx代理跨域
nodejs中间件代理跨域
后端在头部信息⾥⾯设置安全域名
11 模块化开发怎么做?
⽴即执⾏函数,不暴露私有成员
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
}; })();
12 异步加载JS
的⽅式有哪些?
defer,
只⽀持IE
async :
创建 script ,
插⼊到 DOM
中,加载完毕后 callBack
13 那些操作会造成内存泄漏?
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。
setTimeout
的第⼀个参数使⽤字符串⽽⾮函数的话,会引发内存泄漏 闭包使⽤不当
14 XML
和JSON
的区别?
数据体积⽅⾯
JSON
相对于XML
来讲,数据的体积⼩,传递的速度更快些。
数据交互⽅⾯
JSON
与 JavaScript
的交互更加⽅便,更容易解析处理,更好的数据交互
数据描述⽅⾯
JSON
对数据的描述性⽐ XML
较差
传输速度⽅⾯
JSON
的速度要远远快于XML
15 谈谈你对webpack
的看法
WebPack
是⼀个模块打包⼯具,你可以使⽤WebPack
管理你的模块依赖,并编绎输出模块们所需的静态⽂件。它能够很好地管理、打包 Web
开发中所⽤到的HTML 、
Javascript 、 CSS
以及各种静态⽂件(图⽚、字体等),让开发过程更加⾼效。对于不同类型的资源,webpack
有对应的模块加载器。 webpack
模块打包器会分析模块间的依赖关系,最后⽣成了优化且合并后的静态资源 。
16 说说你对AMD
和Commonjs
的理解
CommonJS
是服务器端模块的规范,Node.js
采⽤了这个规范。 CommonJS
规范加载模 块是同步的,也就是说,只有加载完成,才能执⾏后⾯的操作。AMD
规范则是⾮同步加载模块,允许指定回调函数。
AMD
推荐的⻛格通过返回⼀个对象做为模块对象,CommonJS
的⻛格通过对
module.exports
或exports
的属性赋值来达到暴露模块对象的⽬的。
17 常⻅web安全及防护原理
sql
注⼊原理
就是通过把SQL
命令插⼊到 Web
表单递交或输⼊域名或⻚⾯请求的查询字符串,最终 达到欺骗服务器执⾏恶意的SQL
命令
总的来说有以下⼏点
永远不要信任⽤户的输⼊,要对⽤户的输⼊进⾏校验,可以通过正则表达式,或限制⻓度,对单引号和双"-"
进⾏转换等。
永远不要使⽤动态拼装SQL
,可以使⽤参数化的SQL
或者直接使⽤存储过程进⾏数据查询存取。
永远不要使⽤管理员权限的数据库连接,为每个应⽤使⽤单独的权限有限的数据库连接 不要把机密信息明⽂存放,请加密或者hash
掉密码和敏感的信息XSS
原理及防范。
Xss(cross-site scripting)
攻击指的是攻击者往Web
⻚⾯⾥插⼊恶意 html
标签或者javascript
代码。⽐如:攻击者在论坛中放⼀个看似安全的链接,骗取⽤户点击后, 窃取 cookie
中的⽤户私密信息;或者攻击者在论坛中加⼀个恶意表单,当⽤户提交表单的时候,却把信息传送到攻击者的服务器中,⽽不是⽤户原本以为的信任站点。
XSS
防范⽅法
⾸先代码⾥对⽤户输⼊的地⽅和变量都需要仔细检查⻓度和对 ”<”,”>”,”;”,”’”
等字符做过滤;其次任何内容写到⻚⾯之前都必须加以encode,
避免不⼩⼼把html tag
弄出来。这⼀个层⾯做好,⾄少可以堵住超过⼀半的XSS
攻击 。
XSS
与CSRF
有什么区别吗?
XSS
是获取信息,不需要提前知道其他⽤户⻚⾯的代码和数据包。 CSRF
是代替⽤户完成 指定的动作,需要知道其他⽤户⻚⾯的代码和数据包。要完成⼀次CSRF
攻击,受害者必须依次完成两个步骤 登录受信任⽹站A ,
并在本地⽣成Cookie。
在不登出A
的情况下,访问危险⽹站B
CSRF的防御
服务端的CSRF
⽅式⽅法很多样,但总的思想都是⼀致的,就是在客户端⻚⾯增加伪随机数。
通过验证码的⽅法。
18 ⽤过哪些设计模式?
⼯⼚模式:
⼯⼚模式解决了重复实例化的问题,但还有⼀个问题,那就是识别问题,因为根本⽆法 主要好处就是可以消除对象间的耦合,通过使⽤⼯程⽅法⽽不是new
关键字
构造函数模式使⽤构造函数的⽅法,即解决了重复实例化的问题,⼜解决了对象识别的问题,该模式 与⼯⼚模式的不同之处在于 直接将属性和⽅法赋值给this
对象;
19 为什么要有同源限制?
同源策略指的是:协议,域名,端⼝相同,同源策略是⼀种安全协议
举例说明:⽐如⼀个⿊客程序,他利⽤Iframe
把真正的银⾏登录⻚⾯嵌到他的⻚⾯上,当你使⽤真实的⽤户名,密码登录时,他的⻚⾯就可以通过Javascript
读取到你的表单 中input
中的内容,这样⽤户名,密码就轻松到⼿了。
20 offsetWidth/offsetHeight,clientWidth/clientHeight与 scrollWidth/scrollHeight的区别
offsetWidth/offsetHeight
返回值包含content + padding + border,
效果与 e.getBoundingClientRect()
相同
clientWidth/clientHeight
返回值只包含content + padding,
如果有滚动条,也不包 含滚动条。
scrollWidth/scrollHeight
返回值包含content + padding +
溢出内容的尺⼨
21 javascript有哪些⽅法定义对象
对象字⾯量:var obj = {};
构造函数:var obj = new Object();
Object.create(): var obj = Object.create(Object.prototype);
22 常⻅兼容性问题?
png24
位的图⽚在iE6
浏览器上出现背景,解决⽅案是做成PNG8
浏览器默认的margin
和 padding
不同。解决⽅案是加⼀个全局的* {margin:0;padding:0;}
来统⼀,,但是全局效率很低,⼀般是如下这样解决:
IE
下, event
对象有 x , y
属性,但是没有 pageX , pageY
属性
Firefox
下, event
对象有pageX , pageY
属性,但是没有 x,y
属性.
23 说说你对promise的了解
依照Promise/A+
的定义,Promise
有四种状态:
pending:
初始状态, ⾮ fulfilled
或 rejected.
fulfilled:
成功的操作.
rejected:
失败的操作.
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、单进程,单线程
**优点:**⾼并发(最重要的优点)
缺点:
1、只⽀持单核 CPU
,不能充分利⽤ CPU
2、可靠性低,⼀旦代码某个环节崩溃,整个系统都崩溃
27 谈谈你对AMD、CMD
的理解
CommonJS
是服务器端模块的规范,Node.js
采⽤了这个规范。CommonJS
规范加载模 块是同步的,也就是说,只有加载完成,才能执⾏后⾯的操作。AMD
规范则是⾮同步加载 模块,允许指定回调函数
AMD
推荐的⻛格通过返回⼀个对象做为模块对象,CommonJS
的⻛格通过对module.exports
或 exports
的属性赋值来达到暴露模块对象的⽬的。
es6
模块 CommonJS、AMD、CMD
CommonJS
的规范中,每个 JavaScript
⽂件就是⼀个独⽴的模块上下⽂(module context
),在这个上下⽂中默认创建的属性都是私有的。也就是说,在⼀个⽂件定义的 变量(还包括函数和类),都是私有的,对其他⽂件是不可⻅的。
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 说⼏条写JavaScrip
t的基本规范
不要在同⼀⾏声明多个变量 请使⽤ ===/!==
来⽐较 true/false
或者数值使⽤对象字⾯量替代new Array
这种形式。
不要使⽤全局函数
Switch
语句必须带有default
分⽀
If
语句必须使⽤⼤括号
for-in
循环中的变量 应该使⽤var
关键字明确限定作⽤域,从⽽避免作⽤域污 。
33JavaScript
有⼏种类型的值
栈:原始数据类型(Undefined , Null , Boolean , Number 、 String
)
堆:引⽤数据类型(对象、数组和函数)
两种类型的区别是:存储位置不同; 原始数据类型直接存储在栈( stack
)中的简单数据段,占据空间⼩、⼤⼩固定,属于被频繁使⽤数据,所以放⼊栈中存储;
引⽤数据类型存储在堆( heap
)中的对象,占据空间⼤、⼤⼩不固定,如果存储在栈中,将会影响程序运⾏的性能;引⽤数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引⽤值时,会⾸先检索其 在栈中的地址,取得地址后从堆中获得实体。
如下图:
34 javascript
创建对象的⼏种⽅式
javascript
创建对象简单的说,⽆⾮就是使⽤内置对象或各种⾃定义对象, 当然还可以⽤ JSON ;
但写法有很多种,也能混合使⽤。
对象字⾯量的⽅式
person{
firstname:"Mark",lastname:"Yun",
age:25,eyecolor:"black"
};
⽤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();
⽤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⽅法
//⽤⼯⼚⽅式来创建(内置对象)
var wcDog =new Object();
wcDog.name="旺财";
wcDog.age=3;
wcDog.work=function(){
alert("我是"+wcDog.name+",汪汪汪......");
}
wcDog.work();
//⽤原型⽅式来创建
function Dog(){}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
alert(this.name+"是个吃货"); }
var wangcai =new Dog();
wangcai.eat();
//⽤混合⽅式来创建
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
不合法导致解析失败。
38javascript
代码中的"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
模块的概念
47ECMAScript6
怎么写class么
这个语法糖可以让有 OOP
基础的⼈更快上⼿js ,
⾄少是⼀个官⽅的实现了 但对熟悉js
的⼈来说,这个东⻄没啥⼤影响;⼀个 Object.creat()
搞定继承,⽐
class
简洁清晰的多
48 什么是⾯向对象编程及⾯向过程编程,它们的异同和优缺点
⾯向过程就是分析出解决问题所需要的步骤,然后⽤函数把这些步骤⼀步⼀步实现,使⽤ 的时候⼀个⼀个依次调⽤就可以了。
⾯向对象是把构成问题事务分解成各个对象,建⽴对象的⽬的不是为了完成⼀个步骤,⽽是为了描叙某个事物在整个解决问题的步骤中的⾏为
⾯向对象是以功能来划分问题,⽽不是步骤。
49 ⾯向对象编程思想
基本思想是使⽤对象,类,继承,封装等基本概念来进⾏程序设计。
优点
易维护
采⽤⾯向对象思想设计的结构,可读性⾼,由于继承的存在,即使改变需求,那么维 护也只是在局部模块,所以维护起来是⾮常⽅便和较低成本的
易扩展
开发⼯作的重⽤性、继承性⾼,降低重复⼯作量。
缩短了开发周期
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 谈⼀谈箭头函数与普通函数的区别?
函数体内的this
对象,就是定义时所在的对象,⽽不是使⽤时所在的对象不可以当作构造函数,也就是说,不可以使⽤ new
命令,否则会抛出⼀个错误不可以使⽤ arguments
对象,该对象在函数体内不存在。如果要⽤,可以⽤Rest
参数 代替不可以使⽤yield
命令,因此箭头函数不能⽤作 Generator
函数 。
56 谈⼀谈函数中this的指向
this
的指向在函数定义的时候是确定不了的,只有函数执⾏的时候才能确定this
到底指向谁,实际上this
的最终指向的是那个调⽤它的对象 《javascript语⾔精髓》
中⼤概概括了4种调⽤⽅式:
⽅法调⽤模式
函数调⽤模式
构造器调⽤模式
graph LR
A-->B
apply/call调⽤模式
57 异步编程的实现⽅式
回调函数
优点:
简单、容易理解
缺点:
不利于维护,代码耦合⾼
事件监听(采⽤时间驱动模式,取决于某个事件是否发⽣):
优点:
容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
缺点:
事件驱动型,流程不够清晰
发布/订阅(观察者模式)
类似于事件监听,但是可以通过‘消息中⼼ʼ,了解现在有多少发布者,多少订阅者
Promise
对象
优点:
可以利⽤then
⽅法,进⾏链式写法;可以书写错误时的回调函数;
缺点:
编写和理解,相对⽐较难Generator
函数
优点:
函数体内外的数据交换、错误处理机制
缺点:
流程管理不⽅便
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 性能问题的原因之 ⼀。
⼀般我们会采⽤下⼏种⽅式来进⾏数组的遍历
for in
循环
for
循环
forEach
这⾥的forEach
回调中两个参数分别为value , index forEach
⽆法遍历对象IE
不⽀持该⽅法;Firefox
和 chrome
⽀持
forEach
⽆法使⽤ break , continue
跳出循环,且使⽤ return
是跳过本次循环 这两种⽅法应该⾮常常⻅且使⽤很频繁。但实际上,这两种⽅法都存在性能问题
在⽅式⼀中,for-in
需要分析出 array
的每个属性,这个操作性能开销很⼤。⽤在key
已知的数组上是⾮常不划算的。所以尽量不要⽤for-in
,除⾮你不清楚要处理哪些属性,例如 JSON
对象这样的情况
在⽅式二中,循环每进⾏⼀次,就要检查⼀下数组⻓度。读取属性(数组⻓度)要⽐读局部 变量慢,尤其是当array
⾥存放的都是 DOM
元素,因为每次读取都会扫描⼀遍⻚⾯上 的选择器相关元素,速度会⼤⼤降低、
61 gulp是什么
gulp
是前端开发过程中⼀种基于流的代码构建⼯具,是⾃动化项⽬的构建利器;它不仅 能对⽹站资源进⾏优化,⽽且在开发过程中很多重复的任务能够使⽤正确的⼯具⾃动完成Gulp
的核⼼概念:流 **流,**简单来说就是建⽴在⾯向对象基础上的⼀种抽象的处理数据的⼯具。在流中,定义了⼀些处理数据的基本操作,如读取数据,写⼊数据等,程序员是对流进⾏所有操作的,⽽ 不⽤关⼼流的另⼀头数据的真正流向
gulp
正是通过流和代码优于配置的策略来尽量简化任务编写的⼯作 Gulp的特点:
**易于使⽤:**通过代码优于配置的策略,gulp
让简单的任务简单,复杂的任务可管理 构建快速 利⽤Node.js
流的威⼒,你可以快速构建项⽬并减少频繁的 IO 操作 易于学习 通过最少的 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”)
与正则表达字⾯量 var
reg=//
有什么不同?匹配邮箱的正则表达式?
当使⽤ 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 window.onload和
(
d
o
c
u
m
e
n
t
)
.
r
e
a
d
y
∗
∗
原
⽣
‘
‘
J
S
‘
‘
的
‘
‘
w
i
n
d
o
w
.
o
n
l
o
a
d
‘
‘
与
‘
‘
J
q
u
e
r
y
‘
‘
的
‘
‘
(document).ready** 原⽣ ``JS`` 的`` window.onload`` 与`` Jquery ``的 ``
(document).ready∗∗原⽣‘‘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, "a"
// 新建⼀个空的结果数组, 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", un
//利⽤ sort() 排序⽅法,然后根据排序后的结果进⾏遍历及相邻元素⽐对
//⽅法五、利⽤对象的属性不能相同的特点进⾏去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var arrry= [];
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,undefin
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", {…}]
//利⽤ hasOwnProperty 判断是否存在对象属性
//⽅法⼋、利⽤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));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…},
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)
requestAnimationFrame
//兼容性处理
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 项⽬做过哪些性能优化?
减少HTTP
请求数 减少DNS
查询 使⽤ CDN
避免重定向 图⽚懒加载 减少DOM
元素数量 减少DOM
操作
使⽤外部JavaScript
和CSS
压缩 JavaScript 、 CSS 、
字体、图⽚等。
优化CSS Sprite
使⽤ iconfont
字体裁剪
多域名分发划分内容到不同域名
尽量减少iframe
使⽤
避免图⽚src
为空
把样式表放在link
中
把 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实现效果,点击容器内的图标,图标边框变成border 1px solid red,点击空⽩处重置
const box = document.getElementById('box');
function isIcon(target) {
return target.className.includes('icon'); }
box.onClick = function(e) {
e.stopPropagation();
const target = e.target;
if (isIcon(target)) {
target.style.border = '1px solid red';
} }
const doc = document;
doc.onclick = function(e) {
const children = box.children;
for(let i; i < children.length; i++) {
if (isIcon(children[i])) {
children[i].style.border = 'none';
}
} }
89请简单实现双向数据绑定mvvm
<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value) {
input.value = value;
this.value = value;
} });
input.onChange = function(e) {
data.text = e.target.value; }
90实现Storage,使得该对象为单例,并对localStorage
进⾏封装设 置值setItem(key,value)
和getItem(key)
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return instance;
}
setItem = (key, value) => localStorage.setItem(key, value),
getItem = key => localStorage.getItem(key) }
91说说event loop
⾸先,js
是单线程的,主要的任务是处理⽤户的交互,⽽⽤户的交互⽆⾮就 是响应 DOM
的增删改,使⽤事件队列的形式,⼀次事件循环只处理⼀个事件响应,使得脚本执⾏相对连续,所以有了事件队列,⽤来储存待执⾏的事件, 那么事件队列的事件从哪⾥被push
进来的呢。那就是另外⼀个线程叫事件触发线程做的事情了,他的作⽤主要是在定时触发器线程、异步HTTP
请求线程 满⾜特定条件下的回调函数 push
到事件队列中,等待js
引擎空闲的时候去 执⾏,当然js
引擎执⾏过程中有优先级之分,⾸先js
引擎在⼀次事件循环中, 会先执⾏js
线程的主任务,然后会去查找是否有微任务
microtask(promise)
,如果有那就优先执⾏微任务,如果没有,在去查找 宏任务macrotask(setTimeout、setInterval)
进⾏执⾏
92说说事件流
事件流分为两种,捕获事件流和冒泡事件流
捕获事件流从根节点开始执⾏,⼀直往⼦节点查找执⾏,直到查找执⾏到⽬标节点 冒泡事件流从⽬标节点开始执⾏,⼀直往⽗节点冒泡查找执⾏,直到查到到根节点
事件流分为三个阶段,⼀个是捕获节点,
⼀个是处于⽬标节点阶段,⼀个是冒泡阶段
93 说说从输⼊URL
到看到⻚⾯发⽣的全过程
⾸先浏览器主进程接管,开了⼀个下载线程。 然后进⾏HTTP
请求(DNS
查询、IP
寻址等等),中间会有三次捂⼿,等待响应,开始下载响应报⽂。 将下载完的内容转交给Renderer
进程管理。 Renderer
进程开始解析css rule tree
和dom tree
,这两个过程是并⾏的,所以⼀般我会把 link标签放在⻚⾯顶部。 解析绘制过程中,当浏览器遇到link
标签或者script、img
等标签,浏览器会去下载这些内容,遇到时候缓存的使⽤缓存,不适⽤缓存的重新下载资源。css rule tree
和dom tree
⽣成完了之后,开始合成render tree
,这个时候浏览器会进⾏layout
,开始计算每⼀个节点的位置,然后进⾏绘制。 绘制结束后,关闭TCP
连接,过程有四次挥⼿
94描述⼀下this
this
,函数执⾏的上下⽂,可以通过apply
, call
, bind
改变this
的指向。对于匿名函数或者直接调⽤的函数来说,this
指向全局上下⽂(浏览 器为window,NodeJS
为global
),剩下的函数调⽤,那就是谁调⽤它,
this
就指向谁。当然还有es6
的箭头函数,箭头函数的指向取决于该箭头函 数声明的位置,在哪⾥声明,this
就指向哪⾥
95说⼀下浏览器的缓存机制
浏览器缓存机制有两种,⼀种为强缓存,⼀种为协商缓存
对于强缓存,浏览器在第⼀次请求的时候,会直接下载资源,然后缓存在本地,第⼆次请 求的时候,直接使⽤缓存。
对于协商缓存,第⼀次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识 和最后缓存时间,服务端进⾏校验,如果失效则使⽤缓存 协商缓存相关设置
Exprires
:服务端的响应头,第⼀次请求的时候,告诉客户端,该资源什么时候会过期。 Exprires
的缺陷是必须保证服务端时间和客户端时间严格同步。
Cache-control:max-age
:表示该资源多少时间后过期,解决了客户端和服务端时间必 须同步的问题,
If-None-Match/ETag
:缓存标识,对⽐缓存时使⽤它来标识⼀个缓存,第⼀次请求的时 候,服务端会返回该标识给客户端,客户端在第⼆次请求的时候会带上该标识与服务端进 ⾏对⽐并返回 If-None-Match
标识是否表示匹配。
Last-modified/If-Modified-Since
:第⼀次请求的时候服务端返回Last-modified
表明请求的资源上次的修改时间,第⼆次请求的时候客户端带上请求头 If-Modified-Since
,表示资源上次的修改时间,服务端拿到这两个字段进⾏对⽐。
96现在要你完成⼀个Dialog
组件,说说你设计的思路?它应该有什 么功能?
该组件需要提供hook
指定渲染位置,默认渲染在body
下⾯。 然后改组件可以指定外层样式,如宽度等 组件外层还需要⼀层 mask
来遮住底层内容,点击 mask
可以执⾏传进来的 onCancel
函 数关闭 Dialog
。 另外组件是可控的,需要外层传⼊visible
表示是否可⻅。 然后 Dialog
可能需要⾃定义头head
和底部 footer
,默认有头部和底部,底部有⼀个确 认按钮和取消按钮,确认按钮会执⾏外部传进来的onOk
事件,然后取消按钮会执⾏外部 传进来的 onCancel
事件。 当组件的 visible
为 true
时候,设置 body
的 overflow
为hidden
,隐藏 body
的 滚动条,反之显示滚动条。 组件⾼度可能⼤于⻚⾯⾼度,组件内部需要滚动条。 只有组件的 visible
有变化且为 ture
时候,才重渲染组件内的所有内容。
97caller
和 callee
的区别
callee
caller
返回⼀个函数的引⽤,这个函数调⽤了当前的函数。
这个属性只有当函数在执⾏时才有⽤ 如果在javascript
程序中,函数是由顶层调⽤的,则返回 null
functionName.caller: functionName 是当前正在执⾏的函数。
function a() {
console.log(a.caller) }
callee
callee
放回正在执⾏的函数本身的引⽤,它是arguments
的⼀个属性
使⽤callee
时要注意:
这个属性只有在函数执⾏时才有效
它有⼀个length
属性,可以⽤来获得形参的个数,因此可以⽤来⽐较形参和实参个数是 否⼀致,即⽐较arguments.length
是否等于 arguments.callee.length
它可以⽤来递归匿名函数。
function a() {
console.log(arguments.callee) }
98 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**
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
可以。
99谈谈你对重构的理解
⽹站重构:
在不改变外部⾏为的前提下,简化结构、添加可读性,⽽在⽹站前端保持⼀致 的⾏为。也就是说是在不改变UI
的情况下,对⽹站进⾏优化, 在扩展的同时保持⼀致的UI
对于传统的⽹站来说重构通常是: 表格(table
)布局改为 DIV+CSS
使⽹站前端兼容于现代浏览器(针对于不合规范的 CSS
、如对IE6
有效的) 对于移动平台的优化 针对于 SEO
进⾏优化
100 什么样的前端代码是好的
⾼复⽤
低耦合,这样⽂件⼩,好维护,⽽且好扩展。
具有可⽤性、
健壮性、
可靠性、
宽容性等特点
遵循设计模式的六⼤原则
每天一句中文式外语
俄语
1、Извините, у меня к вам просьба.
[一日为尼接,乌 灭尼亚 可 瓦木 普罗西巴]。
对不起,我有事请你帮忙.
2、Скажите,где справочное бюро?
[斯嘎热接,各界 斯普瓦其那也 比优落]?
请问,问讯处在哪里?
3、Скажите,где стоянка(станция)?
[斯嘎热接,各界 丝大牙恩嘎[斯达册呀]]?
请问,出租汽车站(火车站)在哪里?
4、Скажите,где билетная касса?
[斯嘎热 rei接,各界 比列特那呀 嘎萨]?
请问,售票处在哪里?
5、Идите прямо‚потом налево.
一急接 扑里呀吗 把多木 拿列蛙]
往前走,然后向左拐
6、Сколько стоит билет?、
丝过里嘎 丝多一特 比列特]?
车票多少钱?
7、Что вы говорите? Говорите‚ещё раз.
[师多 喂 嘎哇里接?嘎哇里接 也笑拉丝]
您说什么,请再讲一遍.
8、Я прибыл из китая‚я китаец.
[丫 普里背哦 一日 给达亚。丫 给答也次]。
我来自中国。我是中国人