总纲:前端面试知识点大全
目录
1.5 LHS(left-hand side)和RHS(right-hand side)查询
3.你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?
4.请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?
7.document.write() 与 innerHTML
1.闭包 与 作用域、作用域链、执行环境
1.1 闭包
《JS高程》定义:有权访问另一个函数作用域内变量的函数都是闭包。通俗说法,可以访问其他函数内部变量的函数。
闭包的作用:
1、函数柯里化的核心
2、匿名自执行函数(可以访问全局对象,同时有不会污染全局)
3、缓存结果(父函数的变量不会释放)
4、代码封装
5、实现类和继承
闭包的缺点:
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
1.2 执行环境(执行上下文)
《JS高程》中的解释是:执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都至少有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
通俗的说,所谓的执行环境,可以看做代码当前运行的环境。
全局执行环境(上下文)是最外围的一个执行环境。在web浏览器中,全局执行环境指的是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
某个执行环境的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到应用程序退出(例如关闭网页或浏览器)时才会被销毁。
执行环境会随着函数的调用和返回,不断的重建和销毁。但变量对象在有变量引用(如闭包)的情况下,将留在内存中不被销毁。整个变量对象不销毁,而不是某个变量不销毁。
三类执行环境(上下文):
1、全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,js引擎最先进入的就是这个环境
2、函数级别的代码 – 当执行一个函数时,运行函数体中的代码
3、Eval的代码 – 在Eval函数内运行的代码(这个不常使用,也不推荐使用,故不作了解)
PS:不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。函数上下文的个数是没有任何限制的,每到调用执行一个函数时,js 引擎就会自动新建出一个函数上下文。在外部的上下文中是无法直接访问到其内部上下文里的变量的,但是内部上下文可以访问到外部上下文中的的变量。
存在一个执行环境栈,全局执行环境肯定是在最底部。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
参考链接:https://blog.csdn.net/iamchuancey/article/details/78230791
1.3 作用域
ES5只有函数作用域和全局作用域,没有块级作用域,ES6引入let和const,有了块级作用域。一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。其本身是一套规则,用于确定在何处以及如何查找标识符,规定了函数和变量可使用的位置
1.4 作用域链
《JS高程》:作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
保证对执行环境有权访问的所有变量和函数的有序访问。每个函数都有一个执行环境,定义了变量或函数有权访问的其他数据,与之关联的就是一个变量对象,这个对象中包含了当前函数可访问的变量及函数。当代码在执行环境中执行时,就形成了作用域链。而作用域链就是指向这些变量对象的一个指针列表,他的最前端指向的就是当前执行环境的变量对象,最后面指向window全局执行环境的变量对象。
作用域链的延长:with会将对象绑定到作用域链的顶端,try...catch语句中的catch捕获到的对象会被放到作用域顶端,离开的时候会自动销毁。
作用域的作用:使用作用域链主要是进行标识符的查询,标识符解析就是沿着作用域链一级一级地搜索标识符的过程,而作用域链就是要保证对变量和函数的有序访问
1.5 LHS(left-hand side)和RHS(right-hand side)查询
在引擎执行的第一步操作中,对变量a进行了查询,这种查询叫做LHS查询。实际上,引擎查询共分为两种:LHS查询和RHS查询 。
从字面意思去理解,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。
更准确地讲,RHS查询与简单地查找某个变量的值没什么区别,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。
1.6 小结
1、执行环境有全局执行环境(也称为全局环境)和函数执行环境之分
2、每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用域链
3、函数的局部环境不仅有权访问函数作用域中的变量,而且有权访问其包含(父)环境,乃至全局环境,沿作用域链向外访问变量
4、全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据
5、变量的执行环境有助于确定应该合适释放内存
【1】javascript使用的是词法作用域。对于函数来说,词法作用域是在函数定义时就已经确定了,与函数是否被调用无关。通过作用域,可以知道作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态的
【2】对于函数来说,执行环境是在函数调用时确定的,执行环境包含作用域内所有变量和函数的值。在同一作用域下,不同的调用(如传递不同的参数)会产生不同的执行环境,从而产生不同的变量的值。所以执行环境是动态的
深入理解javascript作用域系列第五篇:https://www.cnblogs.com/xiaohuochai/p/5722905.html
js作用域与执行环境:https://www.cnblogs.com/ukerxi/p/8027236.html
2.匿名函数
在js中分为匿名函数和命名函数,匿名函数常配合闭包和IIFE使用,用于减少全局变量的污染。也可以用变量绑定匿名函数,这样变量就会指向匿名函数。
有一种特殊情况 const func = function bar(){};
这种情况下外部使用bar()会是未定义,bar主要用于函数内部使用,比如递归。
3.你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?
内部一些模块或者组件使用模块的方式,外部调用和正常使用时使用继承的方式。
4.请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?
第一个是声明定义一个Person函数
第二个是直接调用Person()函数,并将返回值赋值给变量person
第三个是创建了一个Person实例对象
5.apply call bind
5.1 区别
1、apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
2、apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
3、apply 、 call 、bind 三者都可以利用后续参数传参,call和bind一个一个写,apply传入数组;
4、bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
5.2 实现原理
apply 和call 是返回执行后的结果,所以在要绑定对象的对象内绑定一个函数执行即可。参数使用arguments导出一个数组将剩余的参数传入。
1. 将函数设为对象的属性
2. 执行该函数
3. 删除该函数
(注:arguments是一个类似数组的东西,只有索引和长度。在不手动的情况下可以使用Array.prototype.slice.call(arguments);但是这里是手动情况所以要一点点读取导出。)
bind 的话,对于执行上面使用eval直接运算函数。返回函数的时候使用一个闭包
参考博文:https://blog.csdn.net/qq_40479190/article/details/78324270
5.3 手动实现
call和apply就是将函数加入到绑定的对象中,然后在这个对象中执行函数。
比如:bar.call(foo),将this绑定到foo对象上,执行bar方法,就相当于,
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar();
call的模拟实现:
Function.prototype.myCall = function(context) {
//call方法,第一项可以是null或者没有,默认指向window
var context = context || window;
//获取调用call的函数,用this获取
context.fn = this;
//存放后面的数组
var args = [];
//可以ES5的语法,args = Array.prototype.slice.call(arguments, 1);
for(var i=1,len=arguments.length;i<len; i++) {
//只有变成字符串,在eval时才能转成代码执行
args.push('arguments['+ i +']');
}
//直接调用toString方法
var result = eval('context.fn('+ args +')');
delete context.fn;
return result;
};
apply的模拟实现:
Function.prototype.myApply = function(context, argsArr) {
//call方法,第一项可以是null或者没有,默认指向window
var context = context || window;
//获取调用call的函数,用this获取调用方法的对象(这里是函数调用的apply,所以this指向函数本身,函数也是对象)
context.fn = this;
var result;
if (!argsArr) {
result = context.fn();
} else {
var args = [];
for(var i = 0, len = argsArr.length; i < len; i++) {
args.push('argsArr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
};
bind的模拟实现:
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this之后的序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.myBind2 = function(context) {
var aArgs = Array.prototype.slice.call(arguments, 1);
var that = this;
return function() {
return that.apply(context, aArgs.concat(Array.prototype.slice.call(arguments)))
}
}
6.new操作符
一个new操作符的内部行为,比如let func = new Func(params);
第一步:新建一个空对象,let obj=new Object();
第二步:为这个新建对象设置原型,继承构造函数的原型 obj.__proto__ = Func.prototype;//有可能是基本类型
第三步:使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。let result = Func.call(obj,params); 执行构造函数并绑定this
第四步:判断构造函数的返回类型,基本类型或无返回值就返回obj,是对象就返回result,即return typeof result === 'object' ? result : obj;
上述步骤具体等同于:
new Animal('cat') = {
var obj = {};
obj.__proto__ = Animal.prototype;
var result = Animal.call(obj,"cat");
return typeof result === 'object'? result : obj;
}
7.document.write() 与 innerHTML
document.write()用于写入文档内容,可以传入多个字符串,写入的字符串会被HTML解析;返回undefined
注意事项:
1、如果document.write()在DOMContentLoaded或load事件的回调函数中,当文档加载完成,则会先清空文档(自动调用document.open()),再把参数写入body内容的开头。也就是说原文档内容会被清除
2、在异步引入的js和DOMContentLoaded或load事件的回调函数中运行document.write(),运行完后,最好手动关闭文档写入(document.close())。异步引用外部js文件,JS文件中必须先运行document.open()清空文档,然后才能运行document.write(),参数写在body内容的开头。如果不先运行document.open(),直接运行document.write(),则无效。
document.write() 只能重新渲染绘制整个页面
innerHTML 可以回流页面的一部分
8.特性检测 特性推断 UA字符串嗅探
8.1 能力检测(也称为特性检测)
能力检测是识别浏览器的能力,只需要确定浏览器支持特定的能力,就能给出解决方案
通用方法检测默写方法和属性是否可用或者存在,其次利用typeof进行能力检测
8.2 怪癖检测
也就是bug检测,如果有bug就先修复
8.3 用户代理检测
navigator.useAgent属性访问,可以获取用户代理字符串。这种方法优先级最靠后,因为可以欺骗。
例如:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36
9.Ajax工作原理
参考:https://segmentfault.com/a/1190000004322487
https://blog.csdn.net/sinat_36521655/article/details/79986394
9.1 作用
局部刷新页面。就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息。
9.2 使用
const xhr = new XMLHttpRequest() //或者IE情况下使用ActiveXObject('Microsoft.XMLHttp')
xhr.open('POST','ajax.php',true);//默认就是async异步
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send("fname=suifeng&lname=nifeng");
//异步
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && xhr.status === 200){
document.getElementById("id").innerHTML = xhr.responseText;
}
}
//同步
xhr.open('GET','ajax.txt',false);
xhr.send(null);
document.getElementById("id").innerHTML = xhr.responseText;
9.3 响应数据格式
responseText:获得字符串形式的响应数据(DOMString)
responseXML:获得XML形式的响应数据(document)
9.4 state表示xhr的状态
0:请求未初始化,尚未调用open()方法
1:服务器连接已建立,已调用open(),尚未调用send()
2:请求已接收,已调用send(),尚未收到响应
3:请求处理中,已经接收部分响应数据
4:请求已完成,且响应已就绪。数据全部接收,可以使用
9.5 status表示http状态码
200:“ok”
204:无内容
301:(永久重定向)
302:(临时重定向)
304:(未修改)服务器资源未修改,http响应不会返回网页内容
403:(禁止)服务器拒绝请求
404:(未找到)服务器找不到请求的资源
500:(服务器内部错误)服务器遇到错误,无法完成请求
502:(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
503:(服务不可达)服务器维护或者过载,无法发完成请求
504:(网关超时)服务器作为网关或代理,但没有及时从上游服务器收到请求
9.6 优点
1、最大的一点是页面无刷新,用户的体验非常好。
2、使用异步方式与服务器通信,具有更加迅速的响应能力。
3、可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。
4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。
9.7 缺点
1、ajax不支持浏览器back按钮。
2、安全问题 AJAX暴露了与服务器交互的细节。
3、对搜索引擎的支持比较弱。
4、破坏了程序的异常机制。
5、不容易调试。
10.跨域
https://www.cnblogs.com/2050/p/3191744.html
跨域请求:从一个域的网页去请求另一个域的资源,实现不同的域之间进行数据传输或通信。协议、域名、端口有任何一处不一样都被视为跨域。
PS:1、如果是协议和端口造成的跨域问题,前端是无能为力的;2、在跨域问题上,域仅仅是通过“URL的首部(协议、域名、端口)”来识别,而不会根据域名对应的IP地址是否相同来判断。(也就是IP地址没用)
10.1 图片ping
它是与服务器进行简单、单项跨域通信的一种方式。请求的数据是通过查询字符串形式发送的(比如http://www.example.com/test.html?name=Jhon),他的响应通常书像素图或者204(无内容)响应,通过监听load和error事件,可以知道响应是什么时候收到的。
var img = new Image();
img.onload = img.onerror = function () {
console.log("receive");
};
img.src = "http://www.example.com/test.html?name=Jhon";
用途:图像ping最常用于跟踪用户点击页面或者动态广告曝光次数。
缺点:1、只能发送get请求;2、无法访问服务器的响应文本
10.2 JSONP
JSON with padding(填充式JSON或参数式JSON)。通过动态生成script标签,然后添加src,产生get请求,且不会发生跨域问题。通过get请求添加callback参数的方式,后台返回由callback函数包裹JSON数据的方式传回给前端,这个代码会在前端作为js代码直接执行,只要前端这边提前写好了callback函数即可。(需要后台配合)
优点:兼容性更好,可以在旧浏览器中运行,在请求完毕后可以通过调用callback的方式回传结果
缺点:1、只支持get请求;2、安全问题(请求返回的代码中可能存在安全隐患,需要字符串过滤?);3、要确定jsonp请求是否失败并不容易;4、需要商量基础token作为身份验证,不然是个网站都可以拿到网站的数据。
10.3 CORS(跨域资源共享 )
请求头部设置Origin:URL,响应头部设置Acess-Control-Access-Origin:URL(或者*,表示所有的都行),两个URL相同就可以实现跨域访问了。
https://www.cnblogs.com/loveis715/p/4592246.html
10.4 document.domain
document.domain="example.com"的方式进行修改二级域名,实现后两个域名相同的网页通信。一般适用于不同子域的框架之间的通信,这样可以获取别的框架页面的脚本的对象和属性
如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe(利用iframe框架的contentWindow属性可以获取该页面window对象,里面包含了页面内的所有活动属性和对象),这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。
10.5 window.name
window.name:window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
10.6 postMessage
postMessage:跨文档消息则是通过向Window实例发送消息来完成的。在使用时,软件开发人员需要通过调用一个Window的postMessage()函数来向该Window实例发送消息。此时Window实例内部的onmessage事件将被触发,进而使得该事件的消息处理函数被调用。但是在接收到消息的时候,消息处理函数首先需要判断消息来源的合法性,以避免恶意用户通过发送消息的方式来非法执行代码。otherWindow.postMessage(message, targetOrigin, [transfer]);
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage