基础题
1、请写出你了解的Array方法,至少6个?
push:将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
unshift:将一个或多个元素添加到数组的开头,并返回该数组的新长度。
pop:从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
shift:从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
splice:增删改原素组,返回被修改的元素的数组集合。
/** array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) 中括号内为可选参数 **/ let arr = ['Jan', 'March', 'April', 'June']; //增:在角标为1的位置插入元素,删除之后的0个元素: ['Jan','Tom', 'March', 'April', 'June'] arr.splice(1, 0, 'Tom'); // 改:在角标为1的位置插入元素,删除之后的1个元素,也就达到了替换的目的:['Jan','Luck', 'March', 'April', 'June'] arr.splice(1, 1, 'Luck'); // 删:从角标为1的位置开始删除,删除两个元素: ['Jan', 'April', 'June'] arr.splice(1, 2);
concat:数组合并
此题考察引用数据类型array的了解
2、写出下题输出结果?
console.log(1);//step1:同步执行,打印1 setTimeout(function(){//step2:处理异步--将宏任务回调放入宏任务的EventQueue(下一轮执行时调用,次轮才执行上轮的EventQueue宏任务回调) console.log(5); //setp6:处理异步--从宏任务EventQueue取出回调执行,打印5 },0) new Promise(function(resolve){ resolve(); console.log(6); //step3:同步执行,打印6 }).then(function(){//step4:处理异步--将微任务回调放入微任务的EventQueue,(本轮同步完成后,就执行EventQueue微任务回调) console.log(7);//setp5:处理异步--从微任务EventQueue取出回调执行,打印7。至此第一轮事件循环结束了,我们开始第二轮循环 }); console.log(8);//step3:同步执行,打印8 /**这段代码作为宏任务,进入主线程。 执行同步代码console.log(),立即执行打印1, 遇到setTimeout,那么等待0s后将其回调函数注册后分发到宏任务event queue. 接下来遇到Promise, new Promise作为同步立即执行,then函数分发到微任务event queue 遇到console.log(8), 立即执行打印8 整体代码script作为第一个宏任务执行结束, 查看当前有没有可执行的微任务,执行then的回调。(第一轮事件循环结束了,我们开始第二轮循环) 从宏任务的event queue开始,我们发现了宏任务event queue中还有个setTimeout对应的回调函数,立即执行。 执行结果: 1-6-8-7-5 微任务 promise process.nextTick 宏任务 setTimeout setInterval I/O script 同一次事件循环中 微任务永远在宏任务之前执行,在当前的微任务没有执行完成时,是不会执行下一个宏任务的。 setTimeout就是作为宏任务来存在的,而Promise.then则是具有代表性的微任务 宏任务需要多次事件循环才能执行完,微任务是一次性执行完的 **/
此题考查了同步异步,事件循环,任务队列相关知识点
参考:
https://www.jianshu.com/p/dc9424a4948e
https://www.jianshu.com/p/2a5954b78ff5
https://www.jianshu.com/p/1368d375aa66
3、下边console输出什么?
var name = 'the window'; function func(){ console.log(this.name); }; var obj = { name: 'my obj', getNameFunc:function(fn){ fn && fn(); //打印the window return function(){ return this.name; }; } }; //var fun = object.getNameFunc();返回的是一个function.此时,fun中的this指向是window,所以this.name是The window //var fun= function(){}fun是window中的。相当于window.fun = function(){} console.log(obj.getNameFunc(func)()) //打印the window
此题考查了this相关知识
4、看下边代码,打印什么?
let obj = { a: 0 }; function fn(obj){ obj.a = 1; //函数参数 obj = { a: 2 }; obj.b = 2; console.log(obj);// {a: 2, b: 2} }; fn(obj); console.log(obj) // {a: 1} /** 引用类型的实参,会按照共享传递方式传递,传递的是实参引用的副本, 所以会表现为改变形参的属性会影响外边的,重新赋值则会切断与实参的共享联系不会影响到实参 **/
此题考查了函数参数传递知识
参考:https://www.cnblogs.com/xcsn/p/9158727.html
5、手写一个原生的bind方法?
了解bind方法
// 提到bind方法,估计大家还会想到call方法、apply方法;它们都是Function对象内建的方法,它们的第一个参数都是用来更改调用方法中this的指向。需要注意的是bind 是返回新的函数,以便稍后调用;apply 、call 则是立即调用原函数 function People() { console.log(this); }; var func = People.bind({name:'阎涵'}); func(); //作为普通函数调用,这是bind最常用的场景 new func(); //作为构造的时候,bind的决定this的参数(即第一个参数)失效。这是经常面试的,实际开发没意义
手写一个简单的bind方法
//手写简单的一个bind方法,bind不能额外传递参数,不区分构造和普通使用 Function.prototype.myBind = function(obj){ const oldFun = this; const BindFn = function(){ return oldFun.apply(obj); }; return BindFn; }; //使用 function People() { console.log(this); }; var func = People.myBind({name:'阎涵'}); func(); new func(); //弊端:不能传递参数,而且两种用法都只作为普通函数调用,不能作为构造函数
写一个完整的bind方法
//手写一个bind方法 Function.prototype.myBind = function(obj){ //bind方法预置新参数 const arg = [...arguments]; arg.shift(); //保存原始函数(主要是存储当前this) const oldFun = this; //新创建一个的函数(基于原始函数) const BindFn = function(){ console.warn( this instanceof BindFn?'当构造用':'当普通函数用'); //若普通调用则this是Window对象,然为构造方式调用 this instanceof BindFn && (obj = this); return oldFun.apply(obj,[...arg,...arguments]); //修改this,传递参数[bind预置+自身传递参数] }; // 维护原型关系 BindFn.prototype = this.prototype; //返回(已经绑定this后的修改完的原始函数)新函数 return BindFn; }; //使用 function People() { this.sayHaHa = function(){console.log('哈哈')}; console.log(this,arguments); }; People.prototype.speak = function(){console.log('我在people原型上哦')}; var func = People.myBind({name:'阎涵'},'我是bind预置参数'); func('我是自身传递参数'); //普通使用 const p = new func(); //构造使用 通过维护原型关系,p对象既有People类的实例成员,也有People类的原型成员。 p.sayHaHa(); p.speak(); console.log(p)
编程题
1、给定一个n个对象颜色为红色,白色或蓝色的数组,对其进行排序,使相同颜色的对象相邻,颜色为红色,白色和蓝色。在这里,我们将使用0,1和2的整数分别表示红色,白色和蓝色。比如,输入[2,0,2,1,1,0]排序后[0,0,1,1,2,2]。---力扣(LeetCode)中级算法
2、将类似以下JSON表示的树状结构(可以无限层级)
通过parseDOM函数(使用document.createElement,document.createTextNode,appendChild等方法)
生成一颗DOM树(返回一个element元素)
const JsonTree = { "tagName": "ul", "props": { "className": "list", "data-name": "jsontree" }, "children": [{ "tagName": "li", "children": [{ "tagName": "img", "props": { "src": "//img.alicdn.com/tps/TB1HwXxLpXXXXchapXXXXXXXXXX-32-32.ico", "width": "16px" } }] }, { "tagName": "li", "children": [{ "tagName": "a", "props": { "href": "https://www.aliyun.com", "target": "_blank" }, "children": "阿里云" }] } ] };
答案:
function parseDOM(jsontree) { const { tagName, props, children } = jsontree; const element = document.createElement(tagName); //请实现过程 //.... for (let _key in props) { element[_key] = props[_key]; } if (children && typeof(children) === "object") { for (let i = 0; i < children.length; i++) { element.appendChild(parseDOM(children[i])); } } else { if (children) { element.appendChild(document.createTextNode(children)); } } return element; } document.getElementsByTagName(“body ")[0].appendChild(parseDOM(JsonTree));
首先这个面试题很切合实际,在日常的开发过程中经常会遇到这种类型的数据。主要考我们对递归算法的熟练程度。具体的知识点就是题中列出的3个DOM操作的知识。
参考答案的思路是把每次创建完成的节点添加到父元素中