// 答案:
1,== 判断值是否相等; === 判断值和数据类型是否严格相等
2,Javascript堆栈和垃圾回收机制
堆栈溢出
当储存的数据导到某一限制时就会造成堆栈溢出
内存泄漏
当不断向堆中存储数据,而不进行清理,这就是内存泄漏
垃圾回收机制(清除孤儿机制)
语言当中一般分两种,一种是自动清理,一种是手动清理(GC),js中只有自动清理
垃圾回收机制就是将引用对中的地址的对象设置为null,并且将所有引用该地址的对象都设置为null,并且移除事件侦听
不会即时清除,垃圾回收车会根据内存的情况在适当的时候进行清除堆中的对象 内存到达一定程度了才会进行回收
62. ★★★★ 了解 ES6 的 Proxy 吗?
// 答案:
Proxy,代理,是ES6新增的功能,可以理解为代理器(即由它代理某些操作)。
Proxy 对象用于定义或修改某些操作的自定义行为,可以在外界对目标对象进行访问前,对外界的访问进行改写。
new Proxy()表示生成一个 Proxy 实例
-target:目标对象
-handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。
注意:要实现拦截操作,必须是对 Proxy 实例进行操作,而不是针对目标对象 target 进行操作。
63. ★★★★ 深拷贝是什么?项目哪里是用到了深拷贝?
// 答案:
1,在拷贝构造函数中假如只完成了数据成员本身的赋值则称为“浅拷贝”;编译器提供的默认拷贝构造函数就已经可以完成这个任务。
而假如要复制的数据除了属性值本身以外,还要复制附加在数据属性值上的额外内容,那就要自己来写拷贝构造函数了,来完成所谓的“深拷贝”。
举个例子:
若在构造函数中new了一个新的空间存放数据,并且用指针记录了首地址;若是浅拷贝,则在拷贝构造函数中指针值将复制给另一个数据成员,这样就会有两个指针指向同一个空间;这样的话在析构函数里将会对指针所指向的空间进行释放,由于两个指针指向的是同一个空间,在释放第一个指针指向的空间时不会出现什么问题,而释放第二个指针指向的空间时就会因为空间已经被解析过而导致解析的空间不存在的情况,就会造成程序无法终止。
而解决上面这种情况的办法就是使用“深拷贝”,深拷贝是在拷贝构造函数里再new一个新的空间。将数据复制在新空间里,并将拷贝的指针记录这个新空间的首地址,这样在析构函数里就不会有问题了。
2,在某些引用类型值不更新的情况下用深拷贝
64. ★★★ swiper 插件从后台获取数据没问题,css 代码啥的也没问题,但是图片不动,应该怎么解决?
// 答案:
主要原因:
swiper提前初始化了,而这个时候,数据还没有完全出来。
解决方法
从swiper 入手,在swiper中写 observer:true/observeParents:true
let myswiper = new Swiper(“.swiper-container” , {
autoplay: true,
loop: true,
// observer 修改swiper子元素时自动初始化swiper
observer:true,
// observeParents 包括当前父元素的swiper发生变更时也会初始化swiper
observeParents:true,
})
从 Vue 入手,vue中专门提供了提供了一个方法nextTick() 用于解决dom的先后执行问题。
mounted(){
this.$nextTick(function(){
// …操作
let myswiper = new Swiper(“.swiper-container” , {
autoplay: true,
loop: true
})
})
}
65. ★★★★ ES6 中,数组监测怎么实现的(代理)
// 通过ES6的关键字extends实现继承完成Array原型方法的重写
class NewArray extends Array {
constructor(…args) {
// 调用父类Array的constructor()
super(…args)
}
push (…args) {
console.log(‘监听到数组的变化啦!’);
// 调用父类原型push方法
return super.push(…args)
}
// …
}
let list3 = [1, 2];
let arr = new NewArray(…list3);
console.log(arr)
// (2) [1, 2]
arr.push(3);
// 监听到数组的变化啦!
console.log(arr)
// (3) [1, 2, 3]
66. ★★ jQuery 优点和缺点
// 答案:
优点
1.出色的浏览器兼容性
2、出色的DOM操作的封装,使他具备强大的选择器,可以进行快速的DOM元素操作
3、可靠的事件处理机制、jq在处理事件绑定的时候是相当的可靠
4、完善的ajax(对ajax的封装非常好,不需要考虑复杂的浏览器的兼容和XMLhttprequest对象的创建和使用)
5、支持链式操作(什么是链式操作?通过‘.’来操作)和隐士迭代
6、减少服务器的压力和带宽并且加快了加载速度(为什么这么说?原因就是:当你打开网页之前打开了其他的网页,并且该网页也用了cdn的方式来
加载相同版本的jq文件,那么,浏览器就不会加载第二次,为啥舍近求远呢,和生活中的道理一样一样的!)
7、支持丰富的插件,当然你也可以自定义插件,再加上jq的文档也很丰富,对于程序员来说,是一件非常美好的事情
缺点
1.不能向后兼容。
每一个新版本不能兼容早期的版本。举例来说,有些新版本不再支持某些selector,新版jQuery却没有保留对它们的支持,而只是简单的将其移除。这可能会影响到开发者已经编写好的代码或插件。
2.插件兼容性。
与上一点类似,当新版jQuery推出后,如果开发者想升级的话,要看插件作者是否支持。通常情况下,在最新版jQuery版本下,现有插件可能无法正常使用。开发者使用的插件越多,这种情况发生的几率也越高。我有一次为了升级到jQuery 1.3,不得不自己动手修改了一个第三方插件。
3.多个插件冲突。
在同一页面上使用多个插件时,很容易碰到冲突现象,尤其是这些插件依赖相同事件或selector时最为明显。这虽然不是jQuery自身的问题,但却又确实是一个难于调试和解决的问题。
4.jQuery的稳定性。
jQuery没有让浏览器崩溃,这里指的是其版本发布策略。jQuery 1.3版发布后仅过数天,就发布了一个漏洞修正版1.3.1。他们还移除了对某些功能的支持,可能会影响许多代码的正常运行。我希望类似修改不要再出现。
5.对动画和特效的支持差。
在大型框架中,jQuery核心代码库对动画和特效的支持相对较差。但是实际上这不是一个问题。目前在这方面有一个单独的jQuery UI项目和众多插件来弥补此点。
67. ★★★ ES6 class 关键字原理跟 function 什么区别?
// 答案:
function 可以用call apply bind 的方式 来改变他的执行上下文
但是class 却不可以 class 虽然本质上也是一个函数 但是 其内(babel)部做了一层代理 来禁止了这种行为
关于构造器constructor
在function定义的构造函数中,其prototype.constructor属性指向构造器自身
在class定义的类中,constructor其实也相当于定义在prototype属性上
重复定义
function会覆盖之前定义的方法
class会报错
原型或者类中方法的枚举
class中定义的方法不可用Object.keys(Point.prototype)枚举到
function构造器原型方法可被Object.keys(Point.prototype)枚举到,除过constructor
所有原型方法属性都可用Object.getOwnPropertyNames(Point.prototype)访问到
68. ★★★ iframe 跨域问题,页面之间怎么传值?
// 答案:
一般有两个解决方案,一个是建立一个代理页面,通过代理页面传值,
另一个方法是通过H5的postMessage方法传值,今天用的是第二种。
首先,在父页面A中建立一个iframe,其中src要写好子页面B的地址,然后在A页面中写如下方法:
var iframe = document.getElementById(“onemap”);
var msg = {loginName:‘arcgis’,loginPassword:‘Esri1234’};
var childDomain = “https://geoplat.training.com”;
iframe.contentWindow.postMessage(msg,childDomain);
记住,childDomain与A的iframe的src地址不一样,childDomain是域,而src是域中的一个页面
msg是传输的信息,可以是字符串,也可以是对象。
上面的方法一定要写在一个函数中,并通过点击事件调用,如果希望iframe开始为空,点击后在设置src,
可以在设置src之后,通过setTimeout设置一定时间后在传输信息。
在子页面B中,通过对window添加事件获取传输过来的信息:
window.addEventListener(“message”,function(obj){
var name = obj.data.loginName;
var password = obj.data.loginPassword;
login.iframeChildLogin(name,password);
},false);
这样就完成了从不同域的父页面向子页面传值的过程
69. ★★★ 简述 commonJS、AMD 和 CMD
// 答案:
CommonJS导出模块的方法是exports,导入模块的是require,具体规范如下
1)如果一个JS文件中存在exports或require,该JS文件是一个模块
2)模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
3)如果一个模块需要暴露一些API提供给外部使用,需要通过exports导出,exports是一个空的对象,你可以为该对象添加任何需要导出的内容
4)如果一个模块需要导入其他模块,通过require实现,require是一个函数,传入模块的路径即可返回该模块导出的整个内容
【注】CommonJS只是一个规范,相当于告诉你按什么标准制造汽车,但是具体怎么制造还是得看生产商。因此,有了规范以后,nodejs就去实现模块化了
AMD
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
AMD 推崇依赖前置。
CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
CMD 推崇依赖就近
70. ★★★ require.js 源码看过吗?怎么做到异步加载的
/**
- Creates the node for the load command. Only used in browser envs.
*/
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS(‘http://www.w3.org/1999/xhtml’, ‘html:script’) :
document.createElement(‘script’);
node.type = config.scriptType || ‘text/javascript’;
node.charset = ‘utf-8’;
node.async = true;
return node;
};
requirejs 导入模块的方式实际就是创建脚本标签,一切的模块都需要经过这个方法创建。requirejs 使用 onload 事件来处理回调函数:
71. ★★ jQuery,$() 能传什么参数? html 代码怎么解析? 传 function 呢?
这个函数接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组元素。
jQuery 的核心功能都是通过这个函数实现的。
jQuery中的一切都基于这个函数,或者说都是在以某种方式使用这个函数。这个函数最基本的用法就是向它传递一个表达式(通常由 CSS 选择器组成),然后根据这个表达式来查找所有匹配的元素。
默认情况下, 如果没有指定context参数,$()将在当前的 HTML document中查找 DOM 元素;如果指定了 context 参数,如一个 DOM 元素集或 jQuery 对象,那就会在这个 context 中查找。在jQuery 1.3.2以后,其返回的元素顺序等同于在context中出现的先后顺序。
72. ★★ AMD 怎么加载文件的?
AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范
由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出
requireJS主要解决两个问题
多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
看一个使用requireJS的例子
// 定义模块 myModule.js
define([‘dependency’], function(){
var name = ‘Byron’;
function printName(){
console.log(name);
}
return {
printName: printName
};
});
// 加载模块
require([‘myModule’], function (my){
my.printName();
});
语法
requireJS定义了一个函数 define,它是全局变量,用来定义模块
define(id?, dependencies?, factory);
id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
dependencies:是一个当前模块依赖的模块名称数组
factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
在页面上使用require函数加载模块
require([dependencies], function(){});
require()函数接受两个参数
第一个参数是一个数组,表示所依赖的模块
第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
73. ★★ jQuery 怎么找到事件源元素
$(“.btn”).click(function(e){
// e 就是事件对象
e.target; // 事件的目标 dom
e.currentTarget; // 事件处理程序正在处理事件的那个元素
e.srcElement; // 事件的目标 ie
});
74. ★★★★ 模板引擎原理
模板引擎是通过字符串拼接得到的
let template = ‘hello <% name %>!’
let template = 'hello ’ + name + ‘!’
字符串是通过new Function执行的
let name = ‘world’
let template = `
let str = 'hello ’ + name + ‘!’
return str
`
let fn = new Function(‘name’, template)
console.log(fn(name)) // hello world!
将模板转换为字符串并通过函数执行返回
let template = ‘hello <% name %>!’
let name = ‘world’
function compile (template) {
let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
return ' + ${code} + '
})
html = let str = '${html}'; return str
return new Function(‘name’, html)
}
let str = compile(template)
console.log(str(name)) // hello world!
函数只能接收一个name变量作为参数,功能太单一了,一般会通过对象来传参,with来减少变量访问。
with功能
let params = {
name: ‘张三’,
age: 18
}
let str = ‘’
with (params) {
str = 用户${name}的年龄是${age}岁
}
console.log(str) // 用户张三的年龄是18岁
实现简单的模板引擎
let template = ‘hello <% name %>!’
let name = ‘world’
function compile (template) {
let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
return ' + ${code.trim()} + '
})
html = '${html}'
html = let str = ''; with (params) { str = ${html}; } return str
return new Function(‘params’, html)
}
let str = compile(template)
console.log(str({ name })) // hello world!
75. ★★ map 和 foreach 的区别
forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。
map()方法会得到一个新的数组并返回。
76. ★★★★ ES6 的新特性
-
const与let
-
模板字符串
-
解构赋值
-
对象简写法
-
for…of循环
-
展开运算符
-
剩余参数(可变参数)
-
ES6箭头函数
-
参数默认值
10.类和继承
11.模块化规范
77. ★★ 2018/01/01 转换成 2018年/1月/1日
function fun(str){
var date = new Date(str)
return date.getFullYear()+‘年/’+date.getMonth()+‘月/’+date.getDate()+‘日’
}
78. ★★★ 0.1+0.2 等不等于 0.3?自己封装一个让他们相等的方法
在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对JavaScript的数值的理解程度。
在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为false。
方法1:设置一个误差范围值,通常称为”机器精度“,而对于Javascript来说,这个值通常是2-52,而在ES6中,已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true。
function numbersequal(a,b){
return Math.abs(a-b)<Number.EPSILON;
}
方法2:转为整数运算
79. ★★★ 跨域是什么?有哪些解决跨域的方法和方案?
什么是跨域?
所谓的同源是指,域名、协议、端口均为相同。
所谓的跨域,不同的域名、协议、端口皆为不同域
一个域与另一个域名、协议或者端口不同的域的之间访问都叫跨域
解决跨域的方法和方案:
1:通过服务端代理请求。如PHP,服务端语言php是没有跨域限制的,让服务器去别的网站获取内容然后返回给页面。
2:第二种:jsonp跨域
-
jsonp跨域就是利用script标签的跨域能力请求资源
-
jsonp与ajax没有半毛钱关系!!
-
浏览器的同源策略限制了js的跨域能力,但没有限制link img iframe script 的跨域行为
实现方式:
-
利用js创建一个script标签,把json的url赋给script的scr属性,
-
把这个script插入到页面里,让浏览器去跨域获取资源
-
JS先声明好回调函数,插入页面后会代为执行该函数,并且传入json对象为其参数。
注意:
-
jsonp只针对get请求
-
script标签加载回来的资源会被当成js在全局执行
3:CORS 跨域资源共享(xhr2)
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
整个CORS通信过程,都是浏览器自动完成,不需要用户参与
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样
实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信
4:nginx代理跨域
通过nginx服务器转发跨域请求,达到跨域的目的
80. ★★★ 什么是函数式编程?什么的声明式编程?
函数式编程:
函数式编程和声明式编程是有所关联的,因为他们思想是一致的:即只关注做什么而不是怎么做。但函数式编程不仅仅局限于声明式编程。
函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,不仅如此你还可以将函数作为返回值。
声明式编程:
声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。
SQL 语句就是最明显的一种声明式编程的例子,例如:
SELECT * FROM collection WHERE num > 5
除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。
特点:
1:是它不需要创建变量用来存储数据。
2:不包含循环控制的代码如 for, while。
81. ★★★ super() 是否必须执行?不执行怎么让它不报错?
非必须,
在 JavaScript 中,super 指的是父类的构造函数
如果想在构造函数中使用this,你必须首先调用super。 先让父类做完自己的事
不执行无法使用this,
不报错的方法:
1:不使用this
2:手动修正this
82. ★★★ eventloop 渲染在哪一步?
任务队列
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
更新 render
主线程重复执行上述步骤
那么,什么是 microtasks ?规范中规定,task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task(微任务), 并且每个宏任务结束后, 都要清空所有的微任务,这里的 Macro Task也是我们常说的 task 。
(macro)task 主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
microtask主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
遇到 console.log,输出 script end
至此,Event Queue 中存在三个任务,如下表:
宏任务 微任务
setTimeout then1
- then2
执行微任务,首先执行then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了所有微任务
此时,所有的mircotask执行完毕,本轮事件循环结束,UI 开始 render,当 UI render 完毕,开始下一轮事件循环.
执行 setTimeout 任务,输出 setTimeout, 至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout
UI渲染
根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行 UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,
此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。
83. ★★★★ 图片懒加载怎么实现?
原理:随着滚轮滚动,底部的图片会被不断地加载,从而显示在页面上,按需加载,当页面需要显示图片的时候才进行加载,否则不加载
-
页面加载完成时记录每个img标签的src值的字符串,
-
用鼠标滚轮判断图片是否出现在屏幕,如果是,则把记录的src值赋值给src属性
-
然后让image的src来发起请求,获取对应的图片放置到DOM树的这个位置上,从而实现图片的页面渲染!
于是就可以知道,当进入页面的时候,其实我们已经把所有的图片的这个地址信息拿到了,图片懒加载的作用就是让这个图片的src按需发起请求,获取图片。
84. ★★ for-in 循环会遍历出原型上的属性吗?怎么避免遍历到原型上的属性
使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问
只遍历对象自身的属性,而不遍历继承于原型链上的属性,需要使用hasOwnProperty 方法过滤一下。
Object.prototype.say=“cgl”;
var person ={
age: 18
};
for (var key in person) {
if(person.hasOwnProperty(key)){
console.log(key, eval(“person.”+key));
}
}
85. ★★★ 简述call、apply、bind,call 和 apply哪个性能更好?
1、call()
call() 方法调用一个函数, 其具有一个指定的 this值和分别地提供的参数(参数的列表)。 第一个参数:在 fun 函数运行时指定的 this 值;如果指定了 null 或者 undefined 则内部 this 指向 window,后面的参数:指定的参数列表
var fn = function(arg1, arg2) {
};
fn.call(this, arg1, arg2);
总结一下
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总