前端高阶面试题之JS

. Javascript有哪几种数据类型

  1. 基本数据类型:
    字符串 String
    数字 Number
    布尔 Boolean
  2. 复合数据类型:
    数组 Array
    对象 Object
  3. 特殊数据类型:
    Null 空对象
    Undefined 未定义

判断js中的数据类型的几种方法?
typeof、instanceof、 constructor、 prototype、 $.type()/jquery.type()

2. Js的事件委托是什么,原理是什么

事件委托: 利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
原理:事件委托是利用事件的冒泡原理来实现的
事件冒泡: 就是事件从最深的节点开始,然后逐步向上传播事件。

. 如何阻止冒泡 和 默认事件

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。
默认事件 : 浏览器默认行为

停止冒泡: 
window.event ? window.event.cancelBubble = true : e.stopPropagation();
阻止默认事件: 
window.event ? window.event.returnValue = false : e.preventDefault();

. 怎么添加、移除、复制、创建、和查找节点

(1). 创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点

(2). 添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore()

(3). 查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性 ==> previousSibling nextSibling firstChild lastChild
querySelector() //选择器

. 如何快速合并两个数组?

(a). arrA.concat(arrB)
(b). Array.prototype.push.apply(arrA,arrB);
(c). Array.prototype.concat.apply(arrA,arrB);
(d). Array.prototype.concat.call(arrA,arrB);
(e). 数组转成字符串拼接在切割成数组, 或者是循环其中一个数组等...
性能自测对比:
Array.prototype.concat.call > Array.prototype.concat.apply > concat > Array.prototype.push.apply

. 隐式转换的场景有哪些?

  1. 如果x不是正常值(比如抛出一个错误),中断执行。
  2. 如果y不是正常值,中断执行。
  3. 如果Type(x)与Type(y)相同,执行严格相等运算x === y。
  4. 如果x是null,y是undefined,返回true。
  5. 如果x是undefined,y是null,返回true。
  6. 如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
  7. 如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
  8. 如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
  9. 如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
  10. 如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
    Number( {toString() {return 1} } ) 为1
    对象和其他类型比较的时候 对象转化成为PrimitiveValue
  11. 如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
  12. 不满足上述情况返回false。

.说一说闭包

有权限访问其它函数作用域内变量的一个函数。

在js中,变量分为全局变量和局部变量,局部变量的作用域属于函数作用域,在函数执行完以后作用域就会被销毁,内存也会被回收,但是由于闭包是建立在函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会被销毁,此时的子函数——也就是闭包,便拥有了访问上级作用域中变量的权限,即使上级函数执行完以后作用域内的值也不会被销毁。

闭包解决了什么?
本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
由于闭包可以缓存上级作用域,这样函数外部就可以访问到函数内部的变量。
也起到保护全局不受污染又能隐藏变量的作用。

.call / apply / bind 有啥区别

都是替换函数中不想要的this, call 和 apply 是临时的, bind 是永久的
call: call(thisObj, obj1, obj2...)
要求传入函数的参数必须单独传入
apply: apply(thisObj, [argArray])
要求传入函数的参数必须放入数组中整体传入
apply会将数组打散为单个参数值分别传入
bind: 永久绑定函数中的this, 作用如下:

  1. 创建一个和原函数功能完全一样的新函数.
  2. 将新函数中的this永久绑定为指定对象
  3. 将新函数中的部分固定参数提前永久绑定

call 与 apply 的 thisObj: 在fun函数运行时指定的this值。需要注意的是,指定的 this 值并不一定是该函 数执行时真正的this值,如果这个函数处于非严格模式下,则指定为 null 和 undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

.说一说重载

重载: 相同函数名,不同参数列表的多个函数,在调用时可自动根据传入参数的不同,选择对应的函数执行。
严格意义上讲 js 中是没有重载的,因为后定义的函数会覆盖前面的同名函数,但是我们可以通过一些方法来模拟重载。

第一种方法:

利用函数内部的argument, 在内部用switch语句,根据传入参数的个数或类型调用不同的case语句,从而功能上达到重载的效果。

第二种方法:
运用闭包原理,既然 js 后面的函数会覆盖前面的同名函数,就强行让所有的函数都留在内存里,等需要的时候再去找它。

function methodFn(obj,name,func){
    var old = obj[name];
    console.log(old instanceof Function);
    obj[name] = function(){
        console.log(arguments.length+" "+fnc.length);
        if(arguments.length === fnc.length){
            return fnc.apply(this,arguments);
         }else if(typeof old === "function"){
            return old.apply(this,arguments);
         }
     }
}

.什么是作用域链(scope chain)

作用域链: 由各级作用域对象连续引用,形成的链式结构

函数的声明周期:

  1. 程序开始执行前: 程序会创建全局作用域对象window
  2. 定义函数时
    在window中创建函数名变量引用函数对象
    函数对象的隐藏属性scope指回函数来自的全局作用域对象window
  3. 调用函数时
    创建本次函数调用时使用的AO对象
    在AO对象中添加函数的局部变量
    设置AO的隐藏属性parent 指向函数的祖籍作用域对象。——执行时,如果AO中没有的变量可延parnet向祖籍作用域对象找。
  4. 函数调用后
    函数作用域对象AO释放
    导致AO中局部变量释放

作用域链: 2项任务:

  1. 保存所有的变量
  2. 控制变量的使用顺序: 先用局部,局部没有才延作用域链向下查找。

.什么是原型链

原型: 每创建一个函数,该函数都会自动带有一个 prototype 属性。该属性是一个指针,指向一个对象,该对象称之为 原型对象。其所有的属性和方法都能被构造函数的实例对象共享访问。原型对象 上默认有一个属性 constructor ,也是一个指针,指向其相关联的构造函数。

每个构造函数都有一个原型对象 prototype,原型对象上包含着一个指向构造函数的指针constructor ,而实例都包含着一个指向原型对象的内部指针 __proto__。可以通过内部指针 __proto__ 访问到原型对象,原型对象可以通过 constructor 找到构造函数。

原型类型有个缺点:多个实例对引用类型的操作会被篡改。
因为每次实例化引用类型的数据都指向同一个地址,所以它们读/写的是同一个数据,当一个实例对其进行操作,其他实例的数据就会一起更改。

原型链: 每个对象都有一个原型 __proto__,这个原型还可以有它自己的原型,以此类推,形成一个链式结构即原型链。
如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系
原型链作用: 如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推
new关键字做了什么?

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性、方法)
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
  5. 我们称这个新对象为构造函数的实例。
    instanceof: 用于测试构造函数的 prototype 属性是否出现在对象的原型链中
    obj.hasOwnProperty(prop): 指示对象自身属性中是否具有指定的属性

.缓存 SessionStorage,LocalStorage,Cookie

sessionStorage 是会话级别存储,只要会话结束关闭窗口,sessionStorage 立即被销毁。
localStorage 是持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
sessionStroagelocalStroage 存储大小可以达到 5M。
cookie 的数据会始终在同源 http 请求中携带,在浏览器和服务器之间来回传递。单个cookie 不能超过4K,只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭 。很多浏览器都限制一个站点最多保存20个 Cookie

.说说跨域

跨域:指一个域下的文档或脚本试图去请求另一个域下的资源,由于浏览器同源策略限制而产生。
同源策略: 同协议+同端口+同域名。即便两个不同的域名指向同一个ip地址,也非同源。如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
解决方案:

  1. Vue 配置代理类 proxy
  2. jsonp 利用标签没有跨越的特点,单只能实现get请求不能post请求
  3. CORS 跨域资源共享,只服务端设置Access-Control-Allow-Origin即可,前端无须设置
  4. nginx代理转发
  5. window.name + iframe跨域: 通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域
  6. location.hash + iframe: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
  7. document.domain + iframe跨域(仅限主域相同,子域不同的跨域应用场景):两个页面都通过js强制设置document.domain为基础主域,就实现了同域;

.垃圾回收机制

标记清除法(常用): (1).标记阶段:垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,并称为可到达对象; (2).清除阶段:对堆内存从头到尾进行线性遍历,如发现有对象没有被标识为可到达对象,则将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作;

优点: 实现简单
缺点: 可能会造成大量的内存碎片

引用计数清除法: (1).引用计数的含义就是跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型赋值给该变量时,这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,这个值的引用次数就减1。 (2).当这个引用次数变成0时,则说明没有办法再访问这个值了,就可以将其所占的内存空间给回收。这样,垃圾收集器下次再运行时,就会释放那些引用次数为0的值所占的内存。

优点: 可即刻回收垃圾
缺点: 计数器值的增减处理繁重,实现繁琐复杂,循环引用无法回收

.箭头函数与普通 function 的区别

1.是匿名函数,不能作为构造函数,不能使用new, 没有construct方法
2.不可使用arguments对象,该对象在函数体内不存在。如要用,可用 rest 参数代替
3.不绑定this,会捕获其所在的上下文的this值,作为自己的this值
4.箭头函数没有原型,也就是没有prototype属性
5.箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()
6.如果箭头函数被包含在一个非箭头函数内,this值就与该函数相等,否则this值就是全局对象
7.箭头函数不能当做Generator函数,不能使用yield关键字

.事件队列

JavaScript语言的一大特点就是单线程,同一个时间只能做一件事。
作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
setTimeout()
将事件插入到了事件队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。
当主线程时间执行过长,无法保证回调会在事件指定的时间执行。
浏览器端每次setTimeout会有4ms的延迟,当连续执行多个setTimeout,有可能会阻塞进程,造成性能问题。
setImmediate()
事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行。和setTimeout(fn,0)的效果差不多。
服务端node提供的方法。浏览器端最新的api也有类似实现:window.setImmediate,但支持的浏览器很少。
process.nextTick()
插入到事件队列尾部,但在下次事件队列之前会执行。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。
大致流程:当前”执行栈”的尾部–>下一次Event Loop(主线程读取”任务队列”)之前–>触发process指定的回调函数。
服务器端node提供的办法。用此方法可以用于处于异步延迟的问题。
可以理解为:此次不行,预约下次优先执行。

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

  1. "主线程"(执行栈)和任务队列<先进先出>(主要是各种I/O操作)的通信,被称为事件循环
  2. 宏任务task(macro-task):script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI render
  3. 微任务jobs(micro-task):process.nextTick,Promise,Async/Await(实际就是promise),MutationObserver(html5新特性)
  4. 事件循环机制:主线程 ->所有微任务 ->宏任务(先进先执行,如果里面有微任务,则下一步先执行微任务,否则继续执行宏任务)

.构造函数 和 class的区别

  1. 类的内部所有定义的方法,都是不可枚举的
  2. 类和模块的内部,默认就是严格模式(this 实际指向的是undefined),非严格模式指向window,所以不需要使用use strict指定运行模式
  3. 类不存在变量提升(hoist)
  4. 类的方法内部如果含有this,它默认指向类的实例
  5. 类的静态方法不会被实例继承,静态方法里的this指的是类,而不是他的实例
  6. 类的继承extends,在constructor中使用super,让this指向当前类,等同于Parent.prototype.constructor.call(this)
  7. 类的数据类型就是函数,类本身就指向构造函数;使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致;Object.assign方法可以很方便地一次向类添加多个方法;prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为一致;

.说说继承

  1. 原型链继承(在实例化一个类时,新创建的对象复制了父类构造函数的属性和方法,并将proto指向父类的原型对象,当在子类上找不到对应的属性和方法时,将会在父类实例上去找。)
    缺点1:引用缺陷(修改其中一个Small实例的父类变量会影响所有继承Big的实例)
    缺点2:无法为不同的实例初始化继承来的属性
  2. 构造函数继承(在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上)
    缺点:无法访问原型上的方法
  3. small.getAge() //small.getAge is not a function
    组合式继承(将原型链继承和构造函数继承组合到一起, 综合了原型链继承和构造函数继承的优点)
    小缺点:调用了两次父类构造函数
  4. extends继承(class和extends是es6新增的,class创建一个类,extends实现继承)

.for in 和 for of的区别

for in 遍历的是数组的索引,for of 遍历的是数组的元素值
一般是使用 for in 来遍历对象,for of 遍历数组

for inindex 索引为字符串型数字,不能直接进行几何运算
for in 遍历顺序有可能不是按照实际数组的内部顺序
因为 for in 是遍历可枚举的属性,也包括原型上的属性。如果不想遍历原型上的属性,可以通过hasOwnProperty来判断某个属性是属于原型还是实例上

for of 只是遍历数组的内部,不会遍历原型上的属性和索引
也可以通过ES5的 Object.keys(obj) 来获取实例对象上的属性组成的数组

.map 和 forEach有何区别

相同点:

  1. 都是循环遍历数组中的每一项
  2. forEach 和 map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
  3. 匿名函数中的 this 都是指向 window
  4. 只能遍历数组, 都不会改变原数组
    不同点:
  5. map() 取出原数组中每个元素,执行相同操作后,放入一个新数组中返回,不修改原数组,仅返回新数组
var 新数组 = arr.map(function(val,i,arr){
       return 新值;
     });
  1. forEach 对原数组中每个元素执行相同操作,直接修改原数组,返回值为undefined, 不可以链式调用
    ···
    arr.forEach(function(val,i,arr){
    arr[i] = 新值;
    })
    ···

.filter 和 reduce

filter : 筛选出原数组中符合条件的元素组成新数组,原数组不变。

var subArr=arr.filter(function(val,i,arr){
    return 判断条件
})

reduce: 将数组中每个元素的值,汇总成一个最终结果

var result=arr.reduce(function(prev,val,i,arr){
    return prev+val;//累加
},base);

.Virtual Dom 的优势在哪里?

重点: VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差?

首先我们需要知道:

DOM 引擎、JS 引擎 相互独立,但又工作在同一线程(主线程) JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最后激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量处理”优化, 引擎间切换的单位代价将迅速积累若其中有强制重绘的 DOM API 调用,重新计算布局、重新绘制图像会引起更大的性能消耗。

其次是 VDOM 和真实 DOM 的区别和优化:

虚拟 DOM 不会立马进行排版与重绘操作
虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗
虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部。

.说说堆栈

栈内存一般储存基础数据类型,遵循后进先出的原则,大小固定并且有序;堆内存一般储存引用数据类型,JavaScript不允许直接访问堆内存中的位置,因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从栈中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据

栈:存储基础数据类型;按值访问;存储的值大小固定;由系统自动分配内存空间;空间小,运行效率高;先进后出,后进先出;栈中的DOM,ajax,setTimeout会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行

堆: 存储引用数据类型;按引用访问;存储的值大小不定,可动态调整;主要用来存放对象;空间大,但是运行效率相对较低;无序存储,可根据引用直接获取

实现一个函数clone 可以对 Javascript 中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制

Object.prototype.clone = function(){
    var obj = this.constructor === Array? [] : {};
    for(var e in this){
        obj [e] = typeof this[e] === "object" ? this[e].clone() : this[e];
    }
    return obj ;
};

//重写trim方法
if(!String.prototype.trim){
String.prototype.trim = function(){
return this.replace(/^\s+/,"").replace(/\s+/,"");
}

你可能感兴趣的:(前端高阶面试题之JS)