一些面试题总结
1.请用原生代码实现选取目标元素所有满足条件的父级元素。
描述:如选出id为target的所有含有类名为father的上级元素
思路:使用递归实现,但是尽量不要创建额外的全局变量
代码:
var a=document.getElementById("this"); function getParent(dom){ var d=dom.parentNode; var arr=[]; if(d){ if(d.className=="target"){ arr.push(d); } if(d.parentNode){ return arr.concat(getParent(d)); }else{ return arr; } }else{ return arr; } } console.log(getParent(a));
2.使用深度优先的原则遍历一棵树
描述:其实就是采用递归去遍历一个json串,如代码中结构的数据所示
输出:
var tree={ leaf1:{ leaf2:"value2" }, leaf4:{ leaf6:{ leaf7:{ leaf8:"value8", leaf10:"value10" }, leaf9:"value9" } }, leaf3:"value3" }; function ergodicTree(tree,arr){ arr=arr||[]; for(var item in tree){ var i=tree[item]; var brr=[]; brr.push(item); if(typeof i=="object"){ ergodicTree(i,arr.concat(brr)); }else{ brr.push(i); console.log(arr.concat(brr)); } } }
3.用js实现快速排序
function quick(arr){ var left=[]; var right=[]; if(arr.length<=1){return arr;} var index=Math.floor(arr.length/2); var mid=arr.splice(index,1)[0]; for(var i=0;i<arr.length;i++){ if(arr[i]>mid){ right.push(arr[i]); }else{ left.push(arr[i]); } } return quick(left).concat([mid],quick(right)); }
4.简述Object.getOwnPropertyNames / Object.keys 及 for...in 的区别
getOwnPrppertyNames 能够列举出当前object的全部非原型属性,包括可枚举的和不可枚举的。
keys 只能列举出object的非原型可枚举属性
for...in 能遍历object的全部可枚举属性
5.简述for...in 和 for...of的异同
相同点:二者都是有遍历、迭代的功能
不同点: 1.当二者遍历相同类型的数据时,for...in 遍历时的参数是数据的key,而for...of是数据的value
2.for...in 可以遍历object、array等, 而 for...of 只能遍历map set array等可迭代变量
6.react中子组件如何更新父组件的state
在子组件中调用父组件中定义的函数,这个函数通过props传到子组件中
7.vue中,如何封装一个组件,在不适用template的前提下,输出一下dom结构:<component-a><component-b foo="bar"/></component-a>?
很简单,使用vue的render createElement 函数
Vue.component('component-b',{ props:{ foo:{ type:String, required:true } }, render(h){ return h('div',['组件B',this.$props.foo]) } }); Vue.component('component-a',{ render(h){ return h('div',['组件A',h('component-b',{ props:{ foo:'bar' } })]); } }) export default { name:'tcm', render(h){ return h('div',[h('component-a')]); } }
8.在SPA项目中如何实现动态模块依赖?
分别以vue和react +webpack 为例:
vue: 在vue的单文件中不需要额外的plugin或loader配置,直接使用如下方式即可动态加载模块及组件
// 加载js模块 import('./asyncMod').then(res=>{ console.log(res.default); res.default(); }); // 加载组件 const Layout = () => import('./page/layout');
react: react中的配置比较复杂,babel7 环境下需要 安装 @babel/plugin-syntax-dynamic-import 插件
其中加载异步模块与vue是一样的
而异步加载组件则需要进一步封装一个高阶组件
import React, { Component } from "react"; export default function asyncComponent(importComponent) { class AsyncComponent extends Component { constructor(props) { super(props); this.state = { component: null }; } componentDidMount() { // const { default: component } = await importComponent(); let _this=this; importComponent().then(res=>{ _this.setState({ component: res.default }); }) } render() { const C = this.state.component; return C ? <C {...this.props} /> : null; } } return AsyncComponent; }
使用时调用高阶组件即可:
const Layout = asyncComponent(()=>import('./page/layout'));
9.content-type/content-encoding/transfer-encoding分别是什么含义?分别有哪些值?
content-type: 体头部用于指示资源的MIME类型 。 常见的值为 text/html; charset=utf-8 等等
transfer-encoding: 消息首部指明了将http请求实体安全传递给用户所采用的编码形式。 值为: chunked gzip deflate identity compress
详细参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Transfer-Encoding
content-encoding: 是一个实体消息首部,用于对特定媒体类型的数据进行压缩。当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。这个消息首部用来告知客户端应该怎样解码才能获取在 Content-Type
中标示的媒体类型内容
值为: gzip br deflate identity compress
详细参考: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Encoding
10. 编写一个函数,计算n个数字的和,n>=0,要求:不能使用循环,必须使用尾递归,算法复杂度为O(n).
function sum(num,total){ if(!total){ total = 0; } total += num; if(num<=0){ return total; } else { return sum(num-1,total); } }
function sum(num){
if(num<=1){
return num;
} else {
return num+sum(num-1)
}
}
11. 实现js深拷贝,要求:a.不能使用循环数组下标做数组拷贝(考虑到稀疏数组)。b.不能拷贝对象原型链上的属性
注: 这个问题提的有点傻逼,不循环数组下标还要深拷贝,对象数组怎么办?这他娘的怎么深拷贝?
不考虑内容是对象的情况下: newArr = arr.slice() 就实现了深拷贝
(这里默认不采用 JSON.parse 和 JSON.stringify)
function judgeType(s){ if(s==null){ return 'null'; } let str = Object.prototype.toString.call(s); let reg = /\[\w+\s(\w+)\]/; var ss = str.match(reg)[1]; return ss.toLowerCase(); } function copyArray(arr){ let aa = []; arr.forEach(e=>{ switch(judgeType(e)){ case 'array': aa.push(copyArray(e)); break; case 'object': aa.push(copyObject(e)); break; default: aa.push(e); } }); return aa; } function copyObject(obj){ let keys = Object.getOwnPropertyNames(obj); let oo = {}; keys.forEach(e=>{ let val = obj[e]; switch(judgeType(val)){ case 'array': oo[e] = copyArray(val); break; case 'object': oo[e] = copyObject(val); break; default: oo[e] = val; } }); return oo; }
12. exports 和 module.exports 有什么区别?AMD 和 CMD有何区别?
exports 是指向module.exports的一个指针,相当于 exports = module.exports 。
它是指向module.exports的,所以在使用时不能直接赋值
// exports指向了包含了a函数的对象,而不是module.exports,导致模块化失效 exports = { a(){ ..... } } // 正确使用方法 exports.a = function a(){.....}
AMD讲究的是依赖前置,就是在定义模块的时候就把依赖写在define函数的引用中
CMD讲究依赖就近,在define函数体中随便requrire,用的时候就requrie即可
// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... }) // AMD 默认推荐的是 define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() // 此处略去 100 行 b.doSomething() //... })
13.React 生命周期和 setState操作关系 及 setState在合成事件和原生事件中执行的区别
生命周期中setState的使用情况:
无意义使用:componentWillMount,componentWillUnmount;
有条件使用:componentDidUpdate;
禁止使用:componentWillUpdate,shouldComponentUpdate;
正常使用:componentWIllReceiveProps,componentDidMount。
生命周期中setState是否触发更新:
componentWillMount和componentWillReceiveProps中,setState会被react内部处理,而不触发render;
其他生命周期均正常出发更新渲染。
setState在原生事件中是同步执行,在合成事件中是异步执行
因为合成事件自己实现了冒泡机制,整个冒泡过程在batchedUpdates这个批处理事务中处理,在合成事件中的setState走到enqueueUpdate的时候,就都会被放进dirtyComponents中等待批处理,此时等待close操作才能将dirtyComponents中存储的state落实到渲染中。所以整个过程是异步的。
而原生事件中没有这一过程,说到底同步还是异步看之前有没有进行过batchedUpdates批处理。
14.阐述一下http缓存策略
缓存策略分为强制缓存和对比缓存。
强制缓存:只要请求了一次,在有效时间内,不会再请求服务器(请求都不会发起),直接从浏览器本地缓存中获取资源
cache-control 和 Expires ,同时存在时cache-control 优先级高于Expires
对比缓存:无论是否变化,是否过期都会发起请求,如果内容没过期,直接返回304,从浏览器缓存中拉取文件,否则直接返回更新后的内容
Etag / If-None-Match 优先级高于 Last-Modified / If-Modified-Since
二者对比:强缓存优先级 > 对比缓存优先级
15.阐述macro-task和micro-task 的区别
第一步:浏览器先执行一个macrotask;执行的过程中,创造了新的macrotask(setTimeout之类的),然后接着执行,把promise加入到micro-task队列里面
第二步:浏览器执行microtask(例如promise),这里会将microtask里面所有任务都取出
第三步:重复,浏览器会再执行一个macrotask
总的来说:macrotask每次只取一个,而microtask会一次取完
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
- I/O
- UI rendering
microtask包括:
- process.nextTick
- Promises
- Object.observe
- MutationObserver
16.如何根据页面的坐标获取相对应的元素?
document.elementFromPoint(x,y)
17.如何在前端页面发起导出excel请求?
async handleExportClick(){ this.makePost(); return false; }, makePost(date){ let inputs = '<input type="hidden" name="date" value="'+ this.messageDataDate +'"/>'; let $form=$('<form action="/api/mm/export/excel" method="post">'+inputs+'</form>'); $form.appendTo('body').submit().remove(); },
18.用js单向链表实现队列,实现队列的push 和 shift 功能
function ListNode(val){ this.val = val; this.next = null; } function createList(vals){ var head = {}; var curr = head; vals.forEach(e=>{ curr = curr.next = new ListNode(e); }) return head.next; } class MyListNode { constructor(vals){ this.List = createList(vals); } push(val){ var list = this.List; var head = {}; var curr = head; while(true){ curr = curr.next = list; if(list.next===null){ curr = curr.next = new ListNode(val); console.log(head,curr); break; } list = list.next; } this.List = head.next; } shift(){ this.List = this.List.next; } }
20. react 和 vue 多层组件嵌套,在不使用redux和vuex的情况下,最外层父组件如何向最里层传值?
react : 使用context
- 建立context数据初始化对象state,并用React.createContext(state) 生成context
- 在父组件中引用context对象,并放入static属性contextType(此属性名称固定,不得更改)中
- 子组件也引用context对象并放入static属性contextType中,在引用数据时使用this.context即可获取
vue: 使用provide/inject
- 最外层父组件中定义名为Provide(不可更改)方法,该方法的返回值为对象或者函数 Provide(){return {a:1,b:2,c:3};}
- 在最内层子组件中使用inject 属性,属性值为数组,inject:['a','b'],在组件中通过 this.a this.b即可使用数据