1. 判断js类型
1. typeof 只能检测基本数据类型(boolean、undefined、string、number、symbol,Object,function),而null ,Array、Object ,使用typeof出来都是Object。无法检测具体是哪种引用类型。
2. instanceof (...是...的实例吗) instanceof的内部机制是通过判断对象的原型链中是不是找到类型的prototype 但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true
3. Object.prototype.toString.call()
Object.prototype.toString.call('An')
// <!-- // "[object String]" -->
Object.prototype.toString.call(1)
// <!-- // "[object Number]" -->
Object.prototype.toString.call(Symbol(1))
// <!-- // "[object Symbol]" -->
Object.prototype.toString.call(null)
// <!-- // "[object Null]" -->
Object.prototype.toString.call(undefined)
// <!-- // "[object Undefined]" -->
Object.prototype.toString.call(function(){})
// <!-- // "[object Function]" -->
Object.prototype.toString.call({name: 'An'})
// <!-- // "[object Object]" -->
Object.prototype.toString.call(arguments)
// <!-- // "[object Arguments]" -->
这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
Object.prototype.toString.call() 常用于判断浏览器内置对象时。
如果单独判断是否是数组ES6增,还可以使用Array.isArray([])
2. ES5和ES6声明变量的方式
ES5有两种:var 和 function
ES6有六种:let,const,class,import(这是除了var和function后新增的)
注意let,const,class声明的全局变量也不会和全局对象的属性挂钩,所以这个地方也可以出考题点
3. 闭包的概念?优缺点
概念: 闭包就是能读取其他函数内部变量的函数
优点:
- 避免全局变量污染
- 希望一个变量长期存储在内存中(缓存变量) 缺点:
- 内存泄漏(消耗
- 常驻内存,增加内存使用量
4. 浅拷贝与深拷贝
1. 浅拷贝:
Object.assign() Arrau.prototype.slice() 扩展运算符 ...
2. 深拷贝
JSON.parse(JSON.stringofy()) 递归函数 function cloneObject(obj){ const newObj = {} if (typeof obj !== 'object) return obj for (let attr in obj) { // 如果某个属性还是引用类型,递归调用 newObj[attr] = cloneObject(newObj[attr]) } return newObj }
使用
需要对数组一部分操作,但是又想保留原数组,就需要注意了; 这里有个需求场景:服务器返回一组数据,该数据展示在一个列表中,并且该数据的前20条需要在播报中进行数据改造后进行滚动播报。这时就要注意,播报的20条一定要进行深拷贝,否则,将影响列表中的展示
msgList() {
const { msg_board = [], topic } = this.msgBoardInfo;
let msgBoard = msg_board ? msg_board.slice(0, 20) : [];
const temp = `<span style="color:#FF9AD3">${topic}</span>`
return (msgBoard = msgBoard.map(i => {
// 深拷贝
i = JSON.parse(JSON.stringify(i));
i.sepical = true;
i.content = i.content.replace(
/{topic}/g,
temp
);
return i;
}));
}
},
上述中因为已知嵌套层级,所以可以直接进行一次map,如果需要进行深拷贝抽离,就是如下
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj={}){
/* 1.判断是基本类型还是引用类型 */
/* 如果不是对象和数组,或者为null,没有必要做深拷贝,直接返回 */
if(typeof obj !== 'object' || obj == null) {
return obj
}
/* 判断是数组还是对象 */
/* 初始化返回结果,如果obj是数组,则返回数组格式,如果obj是对象,则返回对象格式 */
let result
if(obj instanceof Array) {
result = []
} else {
result = {}
}
/* 递归 */
/* 返回结果 */
for(let key in obj) {
/* 判断一下key是不是obj自己拥有的属性,保证key不是原型的属性 */
if(obj.hasOwnProperty(key)) {
//递归调用
result[key] = deepClone(obj[key])
}
}
return result
}
5. 数组去重的方法有哪些
1. ES6的set
let arr1 = [,1,2,3,4,5,5,6]
let arr2 = [...new Set(arr)]
2. reduce()
let arr1 = [,1,2,3,4,5,5,6]
let arr2 = arr1.reduce((res, cur)=> {
if (!res[cur]) {
res.push(cur)
}
return res
}, [])
3. filter(首次出现的位置与其索引匹配,就表示为新,否则为重)
let arr = [1,1,2,3,4,5,5,6]
let arr2 = arr.filter(function(item,index) {
// indexOf() 方法可返回某个指定的 字符串值 在字符串中首次出现的位置
return arr.indexOf(item) === index
})
但是该方法有个问题,就是[1,'1']会被当做相同的元素,最终输入[1]
6. DOM事件有哪些阶段,谈谈你对事件代理的理解(和事件委托同事)
事件的阶段: 捕获阶段-目标阶段-冒泡阶段 代理的理解: 事件不是直接绑定到某个元素上,而是绑定到该元素的父元素上,进行触发事件操作时(例如'click'),再通过条件判断,执行事件触发后的语句(例如'alert(e.target.innerHTML)'),示例如下
document.onclick = function(event){
//IE doesn't pass in the event object
event = event || window.event;
//IE uses srcElement as the target
var target = event.target || event.srcElement;
switch(target.id){
case "help-btn":
openHelp();
break;
case "save-btn":
saveDocument();
break;
case "undo-btn":
undoChanges();
break;
//others?
}
}
好处:(1)使代码更简洁;(2)节省内存开销
7. js执行机制、事件循环
js语言最大的特点就是单线程,同一时间只能做一件事情。 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前面一个任务耗时很长,后一个任务聚不得不一直等待。 js语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(syncchronouns),另一种是异步任务(asyncchronouns),在所有同步任务执行之前,任何异步任务是不会执行的。 当我们打开一个网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类的占用资源大耗时很久的任务,就是异步任务。
概况是: 同步和异步任务分别进入不同的执行“场所”,同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入 Event Queue。主线程的任务执行完毕为空,会去Event Queue读取对应的函数,仅需主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环) 我们不禁要问了,那怎么知道主线程执行栈为空啊?js 引擎存在 monitoring process 进程,会持续不 断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 那里检查是否有等待被调用的函数。
说完 JS 主线程的执行机制,下面说说经常被问到的 JS 异步中 宏任务(macrotasks)、微任务 (microtasks)执行顺序。JS 异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入 Event Queue,然后再执行微任务,将微任务放入 Event Queue,但是,这两个 Queue 不是一个 Queue。 当你往外拿的时候先从微任务里拿这个回调函数,然后再从宏任务的 Queue 拿宏任务的回调函数。如 下图
宏任务:整体代码 script,setTimeout,setInterval
8. 介绍一下promise.all
Promise.all()方法将多个Promise实例包装成一个Promise对象(p),接受一个数组(p1,p2,p3)作 为参数,数组中不一定需要都是Promise对象,但是一定具有Iterator接口,如果不是的话,就会调用 Promise.resolve将其转化为Promise对象之后再进行处理。 使用Promise.all()生成的Promise对象(p)的状态是由数组中的Promise对象(p1,p2,p3)决定的。
- 如果所有的Promise对象(p1,p2,p3)都变成fullfilled状态的话,生成的Promise对象(p)也会变 成fullfilled状态,p1,p2,p3三个Promise对象产生的结果会组成一个数组返回给传递给p的回调函数。
- 如果p1,p2,p3中有一个Promise对象变为rejected状态的话,p也会变成rejected状态,第一个被rejected的对象的返回值会传递给p的回调函数。Promise.all()方法生成的Promise对象也会有一个catch方法来捕获错误处理,但是如果数组中的Promise对象变成rejected状态时,并且这个对象还定义了catch的方法,那么rejected的对象会执行自己的catch方法。并且返回一个状态为fullfilled的Promise对象,Promise.all()生成的对象会接受这个Promise对象,不会返回rejected状态。
9.async 和 await
主要考察宏任务 微任务,搭配promise,询问一些输出的顺序 原理: async 和 await 用了同步的方式去做异步,async定义的函数的返回值都是promise,await后面的函数都会先执行一遍,然后就会跳出整个async函数来执行后面的js栈的代码
10. ES6的class和构造函数的区别
class的写法只是语法糖,和之前的prototype差不多,但是还是有细微差别的,下面看看:
ES5之前的构造函数
function MathHandle(x, y) {
this.x = x
this.y = y
}
MathHandle.prototype.add = function () {
return this.x + this.y
}
var m = new MathHandle(1, 2)
typeof MathHandle // 'function'
MathHandle.prototype.constructor === MathHandle // true
m.__proto__ === MathHandle.prototype // true
ES6 class重写
class MathHandle {
constructor(x, y) {
this.x = x
this.y = y
}
add() {
return this.x + this.y
}
}
const m = new MathHandle(1, 2)
console.log(typeof MathHandle) // 'function'
console.log(MathHandle.prototype.constructor === MathHandle) // true
console.log(m.__proto__ === MathHandle.prototype) // true
从以上可以看出ES6的class只是ES5构造函数的语法糖
区别
(1)严格模式 类和模块内部,默认的就是严格模式,所以不需要使用use strict指定运行模式。只需要你把代码写 类或模块中,就只有严格模式可以用。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言都升级到了严格模式。
(2)不存在变量提升
new Foo(); // ReferenceError
class Foo {}
(3) 方法默认是不可枚举的
ES6 中的 class,它的方法(包括静态方法和实例方法)默认是不可枚举的,而构造函数默认是可枚举 的。细想一下,这其实是个优化,让你在遍历时候,不需要再判断 hasOwnProperty 了
(4)class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有 [[construct]],不能使用 new 来调用。
(5)class 必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也 可以执行。 6. ES5 和 ES6 子类 this 生成顺序不同
ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例。ES6 的继承先 生成父类实例,再 调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象
(6)7. ES6可以继承静态方法,而构造函数不能
11. js的垃圾回收机制讲一下
1.概述
js的垃圾回收机制是为了防止内存泄漏,内存泄漏的含义就是当已经不需要某快内存时这块内存还存在着; 垃圾回收机制就是简写的不定期的寻找到不再使用的变量,并释放掉他们所指向的内存。
2. 常见的四种内存泄漏:** 全局变量,为清除的定时器,闭包,以及dom的引用**
- 全局变量不用var申明,相当于挂载到window对象上,如:b= 1; 解决: 使用严格模式
- 被遗忘的定时器和回调函数
- 闭包
- 没有清理的DOM元素的引用
12. 对前端性能优化有什么了解?一般都通过哪几方面去优化
- 减少请求数量
- 减少资源大小
- 优化网络连接
- 优化资源加载
- 减少重绘回流
- 性能更好的api
- webpack优化
13. 介绍一下rAF(requestAnimationFrame)
专门用来做动画,不卡顿,用法和setTimeout一样 定时器一直是js动画的核心技术,但它们不够精准,因为定时器时间参数是指将执行代码放入UI线程队列中等待的时间。如果前面有其他任务队列执行时间过长,则会导致动画延迟,效果不精等问题。 所以处理动画循环的关键是知道延迟多长时间合适:时间要足够短,才能让动画看起来柔滑平顺,避免斗鱼性能损耗;时间要足够长,才能让浏览器准备好变化渲染。这个时候rAF就出现了,采用系统时间间隔(大多数浏览器刷新频率是60Hz,相当于1000ms/60) 保持最贱绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销; 也不会因为间隔时间过长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,并且rAF会把每一帧中的所有DOM操作集中起来,再一次重绘或回流中就完成。