总结一些面试前端关于JavaScript的一些知识
一、闭包
什么是闭包?
闭包就是函数嵌套函数,内部函数可以访问外部函数的变量,当内部函数在外部被调用的时候,这时就形成了闭包。
闭包的作用?
- 闭包最大的作用就是隐藏变量,闭包的一大特性就是内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后
- 基于此特性,JavaScript可以实现私有变量、特权变量、储存变量等
- 我们就以私有变量举例,私有变量的实现方法很多,有靠约定的(变量名前加_),有靠Proxy代理的,也有靠Symbol这种新数据类型的。
闭包的特性
1、函数嵌套函数。
2、内部函数可以访问外部函数的变量。
3、变量不会被垃圾回收机制所回收。
一、什么是垃圾回收机制
就是间歇的不定期的寻找不再使用的变量,并释放掉它们所指向的内存,通常用的垃圾回收机制有两种,分别是标记清除和引用计数。
二、什么是标记清除
标记清除是垃圾回收机制两个方法之一,意思就是当变量进入环境时,就将这个变量标记为“进入环境”,当变量离开环境时,就将其标记为“离开环境”,然后来跟踪这两个变量的变化。
三、什么是引用计数
就是跟踪记录每个值被引用的次数,每个变量开始时都为1,每声明一个变量并将一个引用类型的值赋给这个变量时,这个值就会加1。获得另外一个变量的时候就会减1。当该值为0的时候,就会被回收。
闭包的优点
变量长期驻扎在内存中,不会被全局所污染,可以隔离作用域,私有成员存在。
闭包的缺点
常驻内存,会增大内存的使用量,使用不当会造成内存泄漏。
二、谈谈你对原型链的理解?
JavaScript 的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是自身的原型。
什么是原型链?
当一个对象调用自身不存在的属性和方法时,会先在自身的私有属性上去找,如果找不到就再去proto关联的prototype再去寻找,如果还没有找到,那就会继续再往上寻找,直到找到或者返回underfind就会停止。
三、说一下JS继承
JS继承实现方式也很多,主要分ES5和ES6继承的实现
先说一下ES5是如何实现继承的
ES5实现继承主要是基于prototype来实现的,具体有三种方法
一是原型链继承:即 B.prototype=new A()
二是借用构造函数继承(call或者apply的方式继承)
四、说一下JS原生事件如何绑定
JS原生绑定事件主要为三种:
一、是html事件处理程序
html事件现在早已不用了,就是在html各种标签上直接添加事件,类似于css的行内样式,缺点是不好维护,因为散落在标签中,也就是耦合度太高
例如:<button onclick="事件处理函数()">点我触发</button>
二、是DOM0级事件处理程序
DOM0级事件,目前在PC端用的还是比较多的绑定事件方式,兼容性也好,主要是先获取dom元素,然后直接给dom元素添加事件
var btn = document.getElementById("id元素");
btn.onclick = function (){
//要处理的逻辑代码
}
事件如何移除呢?很简单:btn.οnclick=null;置为空就行
优点:兼容性好
缺点:只支持冒泡,不支持捕获
三、是DOM2级事件处理程序
DOM2级事件,移动端用的比较多,也有很多优点,提供了专门的绑定和移除方法
var btn = document.getElementById("id元素");
//绑定事件的写法
btn.addEventListener("click",绑定的事件处理函数名,false);
//移除事件的写法
btn.removeEventListener("click",要移除的事件处理函数名,false);
优点:支持给个元素绑定多个相同事件,支持冒泡和捕获事件机制。
五、说一下JS原生常用dom操作方法?
js原生dom操作方法有 :
-
查找:
- getElementByid,
- getElementsByTagName, //这个方法是获取是给标签绑定的
- querySelector,
- querySelectorAll
-
插入:
- appendChild,insertBefore //男生们要记住两个单词呀
-
删除:
- removeChild
-
克隆:
- cloneNode
-
设置和获取属性:
- setAttribute(“属性名”,”值”)
- getAttibute(“属性名”)
六、说一下ES6新增特性?
- 新增了块级作用域(let,const)
- 提供了定义类的语法糖(class)
- 新增了一种基本数据类型(Symbol)
- 新增了变量的解构赋值
- 函数参数允许设置默认值,新增了箭头函数
- 对象和数组新增了扩展运算符
- ES6 新增了模块化(import/export)
- ES6 新增了 Set 和 Map 数据结构
- ES6 新增了生成器(Generator)和遍历器(Iterator)
七、JS设计模式有哪些
-
单例模式:
就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
-
观察者模式:
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
-
工厂模式:
在JavaScript中创建对象的一种方式是用new关键字配合构造函数。然而,在有些情况下,客户端不知道或者不应该知道要实例化几个备选对象中的哪一个。工厂方法允许客户端委托对象创建,同时仍然保留对要实例化的类型的控制。
八、说一下你对JS面向对象的理解
三大特征分别为:封装,继承和多态
JS的面向对象主要是基于function来实现的,通过function来模拟类,通过prototype来实现类方法的共享的。
九、说一下JS数组常用方法
在开发中,数组使用频率很频繁,JS数组常用方法有 :
- push 在数组末尾添加一个或者多个元素,并且返回数组长度
- pop 删除数组最后一个元素
- unshift 在数组开头添加一个或者多个元素,并且返回数组长度
- shift 删除数组开头第一个元素
- splice 可以在数组中添加或删除或替换
- join 把数组中的所有元素转换一个字符串
- concat 用于连接两个或多个字符串
- forEach 用于调用数组的每个元素,并将元素传递给回调函数
- filter 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
- map 返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,就是用来遍历数组的和forEach差不多啦
- sort 数组的元素进行排序,可以声明一个函数,通过定义可以升序或降序
- some 用于检测数组中的元素是否满足指定条件,只要有一个满足条件就会返回true
- every 用于检测数组中的元素是否满足指定条件,但要全部满足条件才会返回true
好多,不过都是平时开发中很常用的方法,大家可以补充一点儿es6的
十、说一下JS数组内置遍历方法有哪些和区别
JS数组内置遍历(遍历就是循环的意思)方法主要有:
-
forEach
- 这个方法是为了取代for循环遍历数组的,返回值为undefined例如:
let arrInfo=[4,6,6,8,5,7,87]
arrInfo.forEach((item,index,arr)=>{
//遍历逻辑
})
其中:
item代码遍历的每一项,
index:代表遍历的每项的索引,
arr代表数组本身
-
filter
- 是一个过滤遍历的方法,如果返回条件为true,则返回满足条件为true的新数组
let arrInfo = [4,16,6,8,45,7,87]
let resultArr = arrInfo.filter((item,index,arr) => {
//例如返回数组每项值大于9的数组
return item > 9
})
-
map
- 这个map方法主要对数组的复杂逻辑处理时用的多,特别是react中遍历数据,也经常用到,写法和forEach类似
-
some
- 这个some方法用于只要数组中至少存在一个满足条件的结果,返回值就为true,否则返回fasel, 写法和forEach类似
-
every
- 这个every方法用于数组中每一项都得满足条件时,才返回true,否则返回false, 写法和forEach类似
十一、说一下JS作用域
JS作用域也就是JS识别变量的范围,作用域链也就是JS查找变量的顺序
- 先说作用域,JS作用域主要包括全局作用域、局部作用域和ES6的块级作用域
- 全局作用域:也就是定义在window下的变量范围,在任何地方都可以访问,
- 局部作用域:是只在函数内部定义的变量范围
- 块级作用域:简单来说用let和const在任意的代码块中定义的变量都认为是块级作用域中的变量,例如在for循环中用let定义的变量,在if语句中用let定义的变量等等
另外: 1. 尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对bug查找不利。
2. 而所谓的作用域链就是由最内部的作用域往最外部,查找变量的过程.形成的链条就是作用域链
十二、说一下从输入URL到页面加载完中间发生了什么?
大致过程是这样的 :
-
通过DNS服务器:url => ip地址;
-
到达ip地址对应的服务器;
-
服务器接收用户的请求;
-
把处理后的结果返回给客户端;
-
客户端把结果渲染到浏览器即可,最后页面显示出来。
-
最后再关闭tcp连接
十三、说一下JS事件代理(也称事件委托)是什么,及实现原理?
首先呢,提到JS事件代理(委托),那就要提一下事件冒泡了,因为这个就是运用了事件冒泡的机制的。
通俗的讲,事件就是onclick,onmouseover,onmouseout,等就是事件,委托呢,就是让别人来做,这个事件本来是加在某些元素上的,然而你却加到别人身上来做,完成这个事件。
也就是:利用冒泡的原理,把事件加到父级上,触发执行效果。
好处呢:1,提高性能。2,减少代码量,没有那么冗余。
//这一段是html的代码
<ul id="ul1">
2 <li>111</li>
3 <li>222</li>
4 <li>333</li>
5 <li>444</li>
6 </ul>
7
8 <script type="text/javascript">
9 window.onload = function(){
10 var oUl = document.getElementById('ul1');
11 var aLi = oUl.children;
12 console.log(aLi);
13
14 //传统方法,li身上添加事件,需要用for循环,找到每个li
15 for (var i=0;i<aLi.length;i++) {
16 aLi[i].onmouseover = function() {
17 this.style.background = 'red';
18 }
19 aLi[i].onmouseout = function(){
20 this.style.background = '';
21 }
22 }//for结束
25 }
26 </script>
十四、说一下js数据类型有哪些?
基本数据类型
- number
- string
- Boolean
- null
- undefined
- symbol(Es6新增)
复合类型有(就是复杂数据类型也就是引用类型)
- Object
- function
十五、说一下 call,apply,bind区别
call,apply,bind主要作用都是改变this指向的,但使用上略有区别,说一下区别
- call和apply的主要区别是在传递参数上不同,call后面传递的参数是以逗号的形式分开的,apply传递的参数是数组形式 [Apply是以A开头的,所以应该是跟Array(数组)形式的参数]
- bind返回的是一个函数形式,后面需要带上一个括号,如果要执行,则后面要再加一个小括号 例如:bind(obj,参数1,参数2,)(),bind只能以逗号分隔形式,不能是数组形式
十六、JavaScript的作用域链理解吗
JavaScript属于静态作用域,即声明的作用域是根据程序正文在编译时就确定的,有时也称为词法作用域。
其本质是JavaScript在执行过程中会创造可执行上下文,可执行上下文中的词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,这些引用串联起来一直指向全局的词法环境,因此形成了作用域链。
十七、ES6模块与CommonJS模块有什么区别?
ES6 Module和CommonJS模块的区别:
- CommonJS是对模块的浅拷贝,ES6 Module是对模块的引用,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const
- import的接口是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。
ES6 Module和CommonJS模块的共同点:
- CommonJS和ES6 Module都可以对引入的对象进行赋值,即对对象内部属性的值进行改变。
十八、null与undefined的区别是什么?
null表示为空,代表此处不应该有值的存在,一个对象可以是null,代表是个空对象,而null本身也是对象,简单点说,null是定义了没有赋值。
undefined表示『不存在』,JavaScript是一门动态类型语言,成员除了表示存在的空值外,还有可能根本就不存在(因为存不存在只在运行期才知道),这就是undefined的意义所在。这个就是定义都没有定义。
十九、箭头函数的this指向哪里?
1.普通函数的this:指向它的调用者,如果没有调用者则默认指向window。
2.箭头函数的this:箭头函数并没有属于自己的this,指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this。如果有调用它的话,它的this指向就会变成调用它的那个对象。
二十、async/await是什么?
一、async函数返回一个Promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句,并且声明的是一个异步函数。
解决嵌套复杂的.then()链,在函数前面加上async函数返回的就是一个promise对象。
案例:任务二需要任务一来完成,任务三需要任务二来完成。
二、await关键字必须位于async函数内部。
三、await关键字后面需要一个promise对象,如果不是的话就调用resolve转换它。
四、await关键字的返回结果就是其后面Promise执行的结果,可能是resolved或者rejected的值。
二十一、async/await相比于Promise的优势?
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担
- Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
- 错误处理友好,async/await可以用成熟的try/catch,Promise的错误捕获非常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在一个返回表达式的箭头函数中设置断点,如果你在一个.then代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的『每一步』。
二十二、JavaScript的基本类型和复杂类型是储存在哪里的?
栈 :
1.存储基本数据类型。
2.存储空间小。
3.查找迅速,性能高。
堆 :
1.存多种数据类型。
2.存储空间大。
3.速度慢。
二十三、简述同步与异步的区别
-
同步:
- 浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
- 代码从上往下依次执行,执行完当前代码,才能执行下面的代码。(阻塞)
-
异步:
-
浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
-
代码从上往下依次执行,没执行完当前代码,也能执行下面的代码。(非阻塞)
-
二十四、JavaScript垃圾回收原理?
一、什么是垃圾回收机制
就是间歇的不定期的寻找不再使用的变量,并释放掉它们所指向的内存,通常用的垃圾回收机制有两种,分别是标记清除和引用计数。
二、什么是标记清除
标记清除是垃圾回收机制两个方法之一,意思就是当变量进入环境时,就将这个变量标记为“进入环境”,当变量离开环境时,就将其标记为“离开环境”,然后来跟踪这两个变量的变化。
三、什么是引用计数
就是跟踪记录每个值被引用的次数,每个变量开始时都为1,每声明一个变量并将一个引用类型的值赋给这个变量时,这个值就会加1。获得另外一个变量的时候就会减1。当该值为0的时候,就会被回收。
二十五、深拷贝和浅拷贝的区别?如何实现
简单来说就是深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝
浅拷贝
浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质上复制是其引用,当引用指向的值改变时也会跟着变化。其实就是引用。
深拷贝
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响, 对一个对象的修改并不会影响另一个对象。
使用递归来实现深拷贝 :
1、将要拷贝的数据 obj 以参数的形式传参。
2、声明一个变量 来储存我们拷贝出来的内容。
3、判断 obj 是否是引用类型数据,如果不是,则直接赋值即可( 可以利用 obj instanceof Type 来进行判断)。
4、由于用 instanceof 判断array 是否是object的时候,返回值为true, 所以我们在判断的时候,直接判断obj 是否是Array 就可避免这个问题。
5、根据判断的不同类型,再给之前的变量赋予不同的类型: [ ] : { }
6、循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数。
7、最后 将 这个变量 return 出来即可。
<script>
var Maxobj = {
name: '3ho',
msg: {
age: 18
},
color: ['pink', 'yellow']
}
var Minobj = {}
function twoObj(newobj, oldobj) {
for (k in oldobj) {
//遍历拿到值
let item = oldobj[k]
if (item instanceof Array) {
newobj[k] = []
twoObj(newobj[k], item)
}
else if (item instanceof Object) {
newobj[k] = {}
twoObj(newobj[k], item)
}
else {
newobj[k] = item
}
}
}
twoObj(Minobj, Maxobj)
Minobj.name = 'hhh' // hhh为改变的对象
console.log(Minobj)
console.log(Maxobj)
</script>
二十六、什么是JavaScript原型,原型链 ? 有什么特点?
JavaScript 的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是自身的原型。
什么是原型链?
当一个对象调用自身不存在的属性和方法时,会先在自身的私有属性上去找,如果找不到就再去proto关联的prototype再去寻找,如果还没有找到,那就会继续再往上寻找,直到找到或者返回underfind就会停止。听说最后返回的是Object和Null两种,就连js的创始人也不清楚。
二十七、json和jsonp的区别?
因为浏览器有同源策略,所以ajax不能请求数据,但是script的src不受同源策略的影响所以通过
创建script 标签来加载服务器返回的数据 ,数据就是一个函数调用 比如 fn({code:0,msg:成功})
并在本地创建这个函数 这样这个函数就可以调用成功,获取函数里的参数
json返回的是一串json格式数据;而jsonp返回的是脚本代码(包含一个函数调用)。
二十八、如何阻止冒泡?
冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。
方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。
//阻止冒泡行为
function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation )
//因此它支持W3C的stopPropagation()方法
e.stopPropagation();
else
//否则,我们需要使用IE的方式来取消事件冒泡
window.event.cancelBubble = true;
}
二十九、如何阻止默认事件?
方法是e.preventDefault(),IE则是使用e.returnValue = false
//阻止浏览器的默认行为
function stopDefault( e ) {
//阻止默认浏览器动作(W3C)
if ( e && e.preventDefault )
e.preventDefault();
//IE中阻止函数器默认动作的方式
else
window.event.returnValue = false;
return false;
}
三十、 JavaScript事件流模型都有什么?
“事件冒泡”:事件开始由最具体的元素接受,然后逐级向上传播
“事件捕捉”:事件由最不具体的节点先接收,然后逐级向下,一直到最具体的
“DOM 事件流”:三个阶段:事件捕捉,目标阶段,事件冒泡。
三十一、谈谈cookie的弊端?
1.Cookie 数量和长度的限制。最多只能有 20 条 cookie,每个长度不能超过4KB,否则会被截掉。
2.安全性问题。如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了。
三十二、哪些操作会造成内存泄漏?
内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用 数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象 的内存即可回收。
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
- 闭包
- 控制台日志
- 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
三十三、你如何优化自己的代码?
代码重用
避免全局变量(命名空间,封闭空间,模块化 mvc…)
拆分函数避免函数过于臃肿
三十四、JavaScript 中的强制转型是指什么?
在 JavaScript 中,两种不同的内置类型间的转换被称为强制转型。强制转型在 JavaScript 中有两种形式:显式和隐式。
这是一个显式强制转型的例子:
var a = "42";
var b = Number( a );
a; // "42"
b; // 42 -- 是个数字!
这是一个隐式强制转型的例子:
var a = "42";
var b = a * 1; // "42" 隐式转型成 42
a; // "42"
b; // 42 -- 是个数字!
三十五、解释 JavaScript 中的相等性
JavaScript 中有严格比较和类型转换比较:
- 严格比较(例如 ===)在不允许强制转型的情况下检查两个值是否相等;
- 抽象比较(例如 ==)在允许强制转型的情况下检查两个值是否相等。
var a = "42";
var b = 42;
a == b; // true
a === b; // false
一些简单的规则:
- 如果被比较的任何一个值可能是 true 或 false,要用 ===,而不是 ==;
- 如果被比较的任何一个值是这些特定值(0、“”或 []),要用 ===,而不是 ==;
- 在其他情况下,可以安全地使用 ==。它不仅安全,而且在很多情况下,它可以简化代码,并且提升代码可读性。
三十六、解释 JavaScript 中“undefined”和“not defined”之间的区别
在 JavaScript 中,如果你试图使用一个不存在且尚未声明的变量,JavaScript 将抛出错误“var name is not defined”,让后脚本将停止运行。但如果你使用 typeof undeclared_variable,它将返回 undefined。
在进一步讨论之前,先让我们理解声明和定义之间的区别。
“var x”表示一个声明,因为你没有定义它的值是什么,你只是声明它的存在。
var x; // 声明 x
console.log(x); // 输出: undefined
“var x = 1”既是声明又是定义(我们也可以说它是初始化),x 变量的声明和赋值相继发生。在 JavaScript 中,每个变量声明和函数声明都被带到了当前作用域的顶部,然后进行赋值,这个过程被称为提升(hoisting)。
当我们试图访问一个被声明但未被定义的变量时,会出现 undefined 错误。
var x; // 声明
if(typeof x === 'undefined') // 将返回 true
当我们试图引用一个既未声明也未定义的变量时,将会出现 not defined 错误。
console.log(y); // 输出: ReferenceError: y is not defined
三十七、匿名和命名函数有什么区别?
var foo = function() { // 赋给变量 foo 的匿名函数
// ..
};
var x = function bar(){ // 赋给变量 x 的命名函数 bar
// ..
};
foo(); // 实际执行函数
x();
三十八、什么是 JavaScript 中的提升操作?
提升(hoisting)是 JavaScript 解释器将所有变量和函数声明移动到当前作用域顶部的操作。有两种类型的提升:
- 变量提升——非常少见
- 函数提升——常见
无论 var(或函数声明)出现在作用域的什么地方,它都属于整个作用域,并且可以在该作用域内的任何地方访问它。
var a = 2;
foo(); // 因为`foo()`声明被"提升",所以可调用
function foo() {
a = 3;
console.log( a ); // 3
var a; // 声明被"提升"到 foo() 的顶部
}
console.log( a ); // 2