js面试题
1、实现一个函数,判断输入是不是回文字符串
function run(str){ if(typeOf str !== 'string') return false; return str.split('').reverse().join('') === str; }
2、实现Storage, 使得该对象为单例,并对localStorage进行封装设置值setItem(key, value)和getItem(key)
var instance = null; class Storage{ static getInstance(){ if(!instance){ instance = new Storage(); } return instance; } setItem = (key, value) => localStorage.setItem(key, value); getItem = (key) => return localStorage.getItem(key); }
所有在类中定义的方法都会被实例继承,但如果一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用的。如果在实例上调用静态方法,会报错,表示不存在该方法。类Storage上可直接调用getInstance方法:Storage.getInstance();
父类的静态方法可以被子类继承。也可在子类中通过super()方法调用父类的静态方法。
3、简单实现双向数据绑定mvvm。
<input id="input" />
let data = {}; const input = document.getElementById('input'); Object.defineProperty(data, 'text', { set: function(value){ input.value = value; this.value = value; } }) input.onChange = function(e){ data.text = e.target.value; }
4、实现效果,点击容器内的图标,图标边框变成border: 1px solid red, 点击空白处重置。
<div id="box"> <a class="icon"><i class="fa fa-camera"></i></a> </div> //css #box{
display: flex;
justify-content: center;
align-items: center; width: 100px; height: 100px; background-color: #0f0; } .icon{ display: inline-block; font-size: 20px; }
js:
function isIcon(target){ return target.className.includes('icon'); } var box = document.getElementById('box'); box.onclick = function(e){ e.stopPropagation(); target = e.target; if(isIcon(target)){ target.style.border= '1px solid red'; } else { var children = box.children; for(var i=0; i < children.length; i++) { if (isIcon(children[i])) { children[i].style.border = 'none'; } } } }
5、说说event loop
js是单线程的,主要任务是与用户交互,而用户的交互无非就是响应DOM的增删改,使用事件队列的形式,用来存储待执行的事件,
所有任务分为同步任务和异步任务,同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而是进入“任务队列”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
任务队列是一个事件的队列(包括:IO设备的事件,鼠标点击、页面滚动等用户产生的事件),只要执行栈中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务于是结束等待状态,进入执行栈,开始执行。主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
6、说说事件流
事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流。
事件传播的顺序对应浏览器的两种事件流模型:捕获事件流和冒泡事件流。
捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。
冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。
DOM标准规定事件流包括三个阶段,事件捕获阶段,处于目标节点阶段,事件冒泡阶段。实际目标(<div>)在捕获阶段时不会接收事件的,事件在目标阶段发生并处理,但是事件处理会被看成是冒泡阶段的一部分。note: 所有事件都要经过捕获阶段和处于目标阶段,但有些事件会跳过冒泡阶段,如,获得输入焦点的focus事件和失去输入焦点的blur事件。
7、有一个数组[1,2,3,4],实现一个算法,得到这个数组的全排列的数组,比如[2,1,3,4],[2,1,4,3]....,算法时间复杂度是多少
方法一:按字典序生成全排列:从最后一个元素往前走,我们想让它是递增的,如果碰见了不递增的,那么就让右边中略大于该数字的数字出头顶替这个违背了递增规律的数字。翻转被替换掉的数字右边的数字
8、说说从输入URL到看到页面发生的全功成,越详细越好。
在浏览器中输入url到显示网页主要包含两个部分:网络通信和页面渲染
浏览器地址栏输入url并按下回车后,先进行DNS域名解析,实现网址到IP地址的转换,获取到服务器IP地址后,便会开始建立一次连接,这是由TCP协议完成的,主要通过三次握手进行连接。
第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认; 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手
连接建立后浏览器向服务器发送HTTP请求,完整的HTTP请求包含请求起始行,请求头部,请求主题三部分。服务器在收到浏览器的HTTP请求后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。如果说响应的内容是HTML文档的话, 就需要浏览器进行解析渲染呈现给用户。整个过程涉及两个方面:解析和渲染。在渲染页面之前,需要构建DOM树和CSSOM树,在浏览器还没接收到完整的HTML文件时,它就开始渲染页面了,在遇到外部链入的脚本标签或样式标签或图片时,会再次发送HTTP请求重复上述的步骤。在收到CSS文件后会对已经渲染的页面重新渲染,加入它们应有的样式,图片文件加载完立刻显示在相应的位置。在这一过程中可能会触发页面的重绘(Repaint)和重排(Reflow)。
Reflow,也称作Layout,中文叫回流,一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树,这个过程称为Reflow。 Repaint,中文重绘,意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就OK了,这个过程称为Repaint。
最后关闭TCP连接或继续保持连接。通过四次挥手关闭连接。
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
9、说一下webpack一些plugin,怎么使用wepack对项目进行优化,
10、使下面代码运行正常
a = [1,2,3,4,5]; // Implement this a.multiply(); console.log(a); // [1,2,3,4,5,1,4,9,16,25]
解:给Array添加一个原型方法
Array.prototype.multiply = function(){ Array.prototype.push.apply(a, this.map(item=>item*item)); };
11、以下代码在javascript中返回‘false’,请说明为什么
0.2+0.1===0.3 //false
因为浮点数在计算机中以二进制形式存储时除了能表示成x/2^n的数可以被精确表示外,其余小数都只能存成一个近似值,此例中0.2,0.1都是近似表示,相加后值为0.30000000000000004
12、javascript中有哪几种数据类型?
5种基本数据类型:undefined, null, boolean, string, number,
1种引用数据类型:object,
es6中新增了一种原始数据类型:symbol
13、手写实现一个promise
14、现有一个函数A和函数B,请实现B继承A
//方法1、原型链继承,(核心:将父类的实例作为子类的原型) function A(){} function B(){} B.prototype = new A(); //方法2、构造继承,(核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类) function A(){} function B(){ A.call(this); } //方法3、实例继承,(核心:为父类实例添加新特性,作为子类实例返回) function A(){} function B(){ var instance = new A(); return instance; } //方法4、拷贝继承 function A(){} function B(){ var instance = new A(); for(var p in instance){ B.prototype[p] = instance[p] } } //方法5、组合继承,(核心:通过调用父类构造函数,继承父类的属性并保留传参的优//点,然后通过将父类实例作为子类原型,实现函数复用) function A(){} function B(){ A.call(this); } B.prototype = new A();
15、说出js中的几种继承方式,以及他们的优缺点
原型链继承:
构造继承:
实例继承:
拷贝继承:
组合继承:
16、描述一下this
this, 函数执行的上下文,可以通过apply, call, bind改变this的指向。
对于匿名函数或是直接调用的函数来说,this指向全局上下文(浏览器为window, nodejs为globa),作为对象的方法调用,此时this就指向这个上级对象,作为构造函数调用,这时this就指向这个新对象。
es6的箭头函数,箭头函数中this的指向取决于该剪头函数声明的位置,在哪里声明,this就指向哪里。
setTimeout()里匿名函数和箭头函数中this指向?
apply()参数为空时,默认调用全局对象,参数不为空,此时this指的就是第一个参数表示的对象。
17、作用域
(function(){ var a = b =5; })(); console.log(b); //5
立即执行函数中变量a用关键字var声明,所以a是一个局部变量,而b是全局变量
18、在String对象上定义一个repeatify函数,这个函数接受一个整数参数,来明确字符串需要重复几次,这个函数要求字符串重复指定的次数。例如:
console.log('hello'.repeatify(3)); 应该打印出hellohellohello
String.prototype.repeatify = String.prototype.repeatify || function(times){ var str=''; for(var i=0; i<times; i++){ str += this; } return str; }
19、变量提升
function test(){ console.log(a); console.log(foo()); var a = 1; function foo(){return 2;} } test();
打印结果:undefined 2
20、this
var fullname = 'John Doe'; var obj = { fullname: 'Colin Ihrig', prop: { fullname: 'Aurelio De Rosa', getFullname: function(){ return this.fullname; } } }; console.log(obj.prop.getFullname()); var test = obj.prop.getFullname; console.log(test());
打印结果:Aurelio De Rosa. 和. John Doe
21、修复前一个问题,让最后一个console.log()打印输出Aurelio De Rosa
console.log(test.call(obj.prop));
22、闭包
var nodes = document.getElementsByTagName('button'); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener('click', function() { console.log('You clicked element #' + i); }); }
如果用户点击第一个和第四个按钮的时候,控制台分别打印的结果是什么?
答案:两次打印You clicked element #node_length, 其中node_length是nodes节点的个数
23、修复上题,使得点击第一个按钮输出0,点击第二个按钮时输出1,以此类推
方法一、将执行函数移到循环的外面
function handlerWrapper(i) { return function() { console.log('You clicked element #' + i); } } var nodes = document.getElementsByTagName('button'); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener('click', handlerWrapper(i)); }
方法二、使用立即执行函数表达式再建一个闭包
var nodes = document.getElementsByTagName('button'); for (var i = 0; i < nodes.length; i++) { nodes[i].addEventListener('click', (function(i){ return function(){ console.log('You clicked element #' + i); } })(i)); }
24、写一个isPrime()函数,当其为质数时返回true,否则返回false
function isPrime(number) { // If your browser doesn't support the method Number.isInteger of ECMAScript 6, // you can implement your own pretty easily if (typeof number !== 'number' || !Number.isInteger(number)) { // Alternatively you can throw an error. return false; } if (number < 2) { return false; } if (number === 2) { return true; } else if (number % 2 === 0) { return false; } var squareRoot = Math.sqrt(number); for(var i = 3; i <= squareRoot; i += 2) { if (number % i === 0) { return false; } } return true; }