请实现 DOM2JSON 一个函数,能够把一个 DOM 节点输入 JSON 的格局
题目形容:
<div> <span> <a></a> </span> <span> <a></a> <a></a> </span></div>把上诉dom构造转成上面的JSON格局{ tag: 'DIV', children: [ { tag: 'SPAN', children: [ { tag: 'A', children: [] } ] }, { tag: 'SPAN', children: [ { tag: 'A', children: [] }, { tag: 'A', children: [] } ] } ]}
实现代码如下:
function dom2Json(domtree) { let obj = {}; obj.name = domtree.tagName; obj.children = []; domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child))); return obj;}
扩大思考:如果给定的不是一个 Dom 树结构 而是一段 html 字符串 该如何解析?
那么这个问题就相似 Vue 的模板编译原理 咱们能够利用正则 匹配 html 字符串 遇到开始标签 完结标签和文本 解析结束之后生成对应的 ast 并建设相应的父子关联 一直的 advance 截取残余的字符串 直到 html 全副解析结束
对于原型的继承咱们借助寄生组合继承
function Person(obj) { this.name = obj.name this.age = obj.age}Person.prototype.add = function(value){ console.log(value)}var p1 = new Person({name:"番茄", age: 18})function Person1(obj) { Person.call(this, obj) this.sex = obj.sex}// 这一步是继承的要害Person1.prototype = Object.create(Person.prototype)Person1.prototype.play = function(value){ console.log(value)}var p2 = new Person1({name:"鸡蛋", age: 118, sex: "男"})
说一下数组如何去重,你有几种办法?
let arr = [1,1,"1","1",true,true,"true",{},{},"{}",null,null,undefined,undefined]// 办法1let uniqueOne = Array.from(new Set(arr)) console.log(uniqueOne)// 办法2let uniqueTwo = arr => { let map = new Map(); //或者用空对象 let obj = {} 利用对象属性不能反复得个性 let brr = [] arr.forEach( item => { if(!map.has(item)) { //如果是对象得话就判断 !obj[item] map.set(item,true) //如果是对象得话就obj[item] =true 其余一样 brr.push(item) } }) return brr}console.log(uniqueTwo(arr))//办法3let uniqueThree = arr => { let brr = [] arr.forEach(item => { // 应用indexOf 返回数组是否蕴含某个值 没有就返回-1 有就返回下标 if(brr.indexOf(item) === -1) brr.push(item) // 或者应用includes 返回数组是否蕴含某个值 没有就返回false 有就返回true if(!brr.includes(item)) brr.push(item) }) return brr}console.log(uniqueThree(arr))//办法4let uniqueFour = arr => { // 应用 filter 返回符合条件的汇合 let brr = arr.filter((item,index) => { return arr.indexOf(item) === index }) return brr}console.log(uniqueFour(arr))
说一下类组件和函数组件的区别?
1. 语法上的区别:函数式组件是一个纯函数,它是须要承受props参数并且返回一个React元素就能够了。类组件是须要继承React.Component的,而且class组件须要创立render并且返回React元素,语法上来讲更简单。2. 调用形式函数式组件能够间接调用,返回一个新的React元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的render办法来返回一个React元素。3. 状态治理函数式组件没有状态治理,类组件有状态治理。4. 应用场景类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。
说一下你对盒模型的了解?
CSS3中的盒模型有以下两种:规范盒模型、IE盒模型盒模型都是由四个局部组成的,别离是margin、border、padding和content规范盒模型和IE盒模型的区别在于设置width和height时, 所对应的范畴不同1、规范盒模型的width和height属性的范畴只蕴含了content2、IE盒模型的width和height属性的范畴蕴含了border、padding和content能够通过批改元素的box-sizing属性来扭转元素的盒模型;1、box-sizing:content-box示意规范盒模型(默认值)2、box-sizing:border-box示意IE盒模型(怪异盒模型)
写版本号排序的办法
题目形容:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。当初须要对其进行排序,排序的后果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
实现代码如下:
arr.sort((a, b) => { let i = 0; const arr1 = a.split("."); const arr2 = b.split("."); while (true) { const s1 = arr1[i]; const s2 = arr2[i]; i++; if (s1 === undefined || s2 === undefined) { return arr2.length - arr1.length; } if (s1 === s2) continue; return s2 - s1; }});console.log(arr);
冒泡排序--工夫复杂度 n^2
题目形容:实现一个冒泡排序
实现代码如下:
function bubbleSort(arr) { // 缓存数组长度 const len = arr.length; // 外层循环用于管制从头到尾的比拟+替换到底有多少轮 for (let i = 0; i < len; i++) { // 内层循环用于实现每一轮遍历过程中的反复比拟+替换 for (let j = 0; j < len - 1; j++) { // 若相邻元素后面的数比前面的大 if (arr[j] > arr[j + 1]) { // 替换两者 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } // 返回数组 return arr;}// console.log(bubbleSort([3, 6, 2, 4, 1]));
DNS如何工作的
DNS 的作用就是通过域名查问到具体的 IP。DNS 协定提供的是一种主机名到 IP 地址的转换服务,就是咱们常说的域名零碎。是应用层协定,通常该协定运行在UDP协定之上,应用的是53端口号。
因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就呈现了域名。你能够把域名看成是某个 IP 的别名,DNS 就是去查问这个别名的真正名称是什么。
当你在浏览器中想拜访 www.google.com
时,会通过进行以下操作:
- 本地客户端向服务器发动申请查问 IP 地址
- 查看浏览器有没有该域名的 IP 缓存
- 查看操作系统有没有该域名的 IP 缓存
- 查看 Host 文件有没有该域名的解析配置
- 如果这时候还没得话,会通过间接去 DNS 根服务器查问,这一步查问会找出负责
com
这个一级域名的服务器 - 而后去该服务器查问
google.com
这个二级域名 - 接下来查问
www.google.com
这个三级域名的地址 - 返回给 DNS 客户端并缓存起来
咱们通过一张图来看看它的查问过程吧
这张图很活泼的展现了DNS在本地DNS服务器是如何查问的,个别向本地DNS服务器发送申请是递归查问的
本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程
递归查问和迭代查问
- 递归查问指的是查问申请收回后,域名服务器代为向下一级域名服务器发出请求,最初向用户返回查问的最终后果。应用递归 查问,用户只须要收回一次查问申请。
- 迭代查问指的是查问申请后,域名服务器返回单次查问的后果。下一级的查问由用户本人申请。应用迭代查问,用户须要收回 屡次的查问申请。
所以一般而言, 本地服务器查问是递归查问 ,而本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程
DNS缓存
缓存也很好了解,在一个申请中,当某个DNS服务器收到一个DNS答复后,它可能答复中的信息缓存在本地存储器中。返回的资源记录中的 TTL 代表了该条记录的缓存的工夫。
DNS实现负载平衡
它是如何实现负载平衡的呢?首先咱们得分明DNS 是能够用于在冗余的服务器上实现负载平衡。
起因: 这是因为个别的大型网站应用多台服务器提供服务,因而一个域名可能会对应 多个服务器地址。
举个例子来说
- 当用户发动网站域名的 DNS 申请的时候,DNS 服务器返回这个域名所对应的服务器 IP 地址的汇合
- 在每个答复中,会循环这些 IP 地址的程序,用户个别会抉择排在后面的地址发送申请。
- 以此将用户的申请平衡的调配到各个不同的服务器上,这样来实现负载平衡。
DNS 为什么应用 UDP 协定作为传输层协定?
DNS 应用 UDP 协定作为传输层协定的次要起因是为了防止应用 TCP 协定时造成的连贯时延
- 为了失去一个域名的 IP 地址,往往会向多个域名服务器查问,如果应用 TCP 协定,那么每次申请都会存在连贯时延,这样使 DNS 服务变得很慢。
- 大多数的地址查问申请,都是浏览器申请页面时收回的,这样会造成网页的等待时间过长。
总结
- DNS域名零碎,是应用层协定,运行UDP协定之上,应用端口43。
- 查问过程,本地查问是递归查问,顺次通过浏览器缓存
—>>
本地hosts文件—>>
本地DNS解析器—>>
本地DNS服务器—>>
其余域名服务器申请。 接下来的过程就是迭代过程。 - 递归查问一般而言,发送一次申请就够,迭代过程须要用户发送屡次申请。
Loader和Plugin 有什么区别
Loader:直译为"加载器"。Webpack将所有文件视为模块,然而webpack原生是只能解析js文件,如果想将其余文件也打包的话,就会用到loader
。 所以Loader的作用是让webpack领有了加载和解析非JavaScript文件的能力。 Plugin:直译为"插件"。Plugin能够扩大webpack的性能,让webpack具备更多的灵活性。 在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。
参考前端进阶面试题具体解答
DNS残缺的查问过程
DNS服务器解析域名的过程:
- 首先会在浏览器的缓存中查找对应的IP地址,如果查找到间接返回,若找不到持续下一步
- 将申请发送给本地DNS服务器,在本地域名服务器缓存中查问,如果查找到,就间接将查找后果返回,若找不到持续下一步
- 本地DNS服务器向根域名服务器发送申请,根域名服务器会返回一个所查问域的顶级域名服务器地址
- 本地DNS服务器向顶级域名服务器发送申请,承受申请的服务器查问本人的缓存,如果有记录,就返回查问后果,如果没有就返回相干的下一级的权威域名服务器的地址
- 本地DNS服务器向权威域名服务器发送申请,域名服务器返回对应的后果
- 本地DNS服务器将返回后果保留在缓存中,便于下次应用
- 本地DNS服务器将返回后果返回给浏览器
比方要查问 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将申请发送到本地的 DNS 服务器中,本地DNS服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个申请,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。而后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个申请,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。而后本地 DNS 服务器再向其中一个权威域名服务器发送一个申请,最初权威域名服务器返回一个对应的主机名的 IP 地址列表。
DNS同时应用TCP和UDP协定?
DNS占用53号端口,同时应用TCP和UDP协定。 (1)在区域传输的时候应用TCP协定
- 辅域名服务器会定时(个别3小时)向主域名服务器进行查问以便理解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送应用TCP而不是UDP,因为数据同步传送的数据量比一个申请应答的数据量要多得多。
- TCP是一种牢靠连贯,保障了数据的准确性。
(2)在域名解析的时候应用UDP协定
- 客户端向DNS服务器查问域名,个别返回的内容都不超过512字节,用UDP传输即可。不必通过三次握手,这样DNS服务器负载更低,响应更快。实践上说,客户端也能够指定向DNS服务器查问时用TCP,但事实上,很多DNS服务器进行配置的时候,仅反对UDP查问包。
Ajax
它是一种异步通信的办法,通过间接由 js 脚本向服务器发动 http 通信,而后依据服务器返回的数据,更新网页的相应局部,而不必刷新整个页面的一种办法。
面试手写(原生):
//1:创立Ajax对象var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本//2:配置 Ajax申请地址xhr.open('get','index.xml',true);//3:发送申请xhr.send(null); // 谨严写法//4:监听申请,承受响应xhr.onreadysatechange=function(){ if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 ) console.log(xhr.responsetXML)}
jQuery写法
$.ajax({ type:'post', url:'', async:ture,//async 异步 sync 同步 data:data,//针对post申请 dataType:'jsonp', success:function (msg) { }, error:function (error) { }})
promise 封装实现:
// promise 封装实现:function getJSON(url) { // 创立一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest(); // 新建一个 http 申请 xhr.open("GET", url, true); // 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 当申请胜利或失败时,扭转 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 设置谬误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); }; // 设置响应的数据类型 xhr.responseType = "json"; // 设置申请头信息 xhr.setRequestHeader("Accept", "application/json"); // 发送 http 申请 xhr.send(null); }); return promise;}
TCP的流量管制机制
一般来说,流量管制就是为了让发送方发送数据的速度不要太快,要让接管方来得及接管。TCP采纳大小可变的滑动窗口进行流量管制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。
- 当一个连贯建设时,连贯的每一端调配一个缓冲区来保留输出的数据,并将缓冲区的大小发送给另一端。
- 当数据达到时,接管方发送确认,其中蕴含了本人残余的缓冲区大小。(残余的缓冲区空间的大小被称为窗口,指出窗口大小的告诉称为窗口通告 。接管方在发送的每一确认中都含有一个窗口通告。)
- 如果接管方应用程序读数据的速度可能与数据达到的速度一样快,接管方将在每一确认中发送一个正的窗口通告。
- 如果发送方操作的速度快于接管方,接管到的数据最终将充斥接管方的缓冲区,导致接管方通告一个零窗口 。发送方收到一个零窗口通告时,必须进行发送,直到接管方从新通告一个正的窗口。
说一下JSON.stringify有什么毛病?
1.如果obj外面有工夫对象,则JSON.stringify后再JSON.parse的后果,工夫将只是字符串的模式,而不是对象的模式2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的后果将只失去空对象;3、如果obj里有函数,undefined,则序列化的后果会把函数或 undefined失落;4、如果obj里有NaN、Infinity和-Infinity,则序列化的后果会变成null5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则应用JSON.parse(JSON.stringify(obj))深拷贝后,会抛弃对象的constructor;6、如果对象中存在循环援用的状况也无奈正确实现深拷贝;
This
不同状况的调用,
this
指向别离如何。顺带能够提一下es6
中箭头函数没有this
,arguments
,super
等,这些只依赖蕴含箭头函数最靠近的函数咱们先来看几个函数调用的场景
function foo() { console.log(this.a)}var a = 1foo()const obj = { a: 2, foo: foo}obj.foo()const c = new foo()
- 对于间接调用
foo
来说,不论foo
函数被放在了什么中央,this
肯定是window
- 对于
obj.foo()
来说,咱们只须要记住,谁调用了函数,谁就是this
,所以在这个场景下foo
函数中的this
就是obj
对象 - 对于
new
的形式来说,this
被永远绑定在了c
下面,不会被任何形式扭转this
说完了以上几种状况,其实很多代码中的this
应该就没什么问题了,上面让咱们看看箭头函数中的this
function a() { return () => { return () => { console.log(this) } }}console.log(a()()())
- 首先箭头函数其实是没有
this
的,箭头函数中的this
只取决包裹箭头函数的第一个一般函数的this
。在这个例子中,因为包裹箭头函数的第一个一般函数是a
,所以此时的this
是window
。另外对箭头函数应用bind
这类函数是有效的。 - 最初种状况也就是
bind
这些扭转上下文的API
了,对于这些函数来说,this
取决于第一个参数,如果第一个参数为空,那么就是window
。 - 那么说到
bind
,不晓得大家是否思考过,如果对一个函数进行屡次bind
,那么上下文会是什么呢?
let a = {}let fn = function () { console.log(this) }fn.bind().bind(a)() // => ?
如果你认为输入后果是 a
,那么你就错了,其实咱们能够把上述代码转换成另一种模式
// fn.bind().bind(a) 等于let fn2 = function fn1() { return function() { return fn.apply() }.apply(a)}fn2()
能够从上述代码中发现,不论咱们给函数bind
几次,fn
中的this
永远由第一次bind
决定,所以后果永远是window
let a = { name: 'poetries' }function foo() { console.log(this.name)}foo.bind(a)() // => 'poetries'
以上就是
this
的规定了,然而可能会产生多个规定同时呈现的状况,这时候不同的规定之间会依据优先级最高的来决定this
最终指向哪里。首先,
new
的形式优先级最高,接下来是bind
这些函数,而后是obj.foo()
这种调用形式,最初是foo
这种调用形式,同时,箭头函数的this
一旦被绑定,就不会再被任何形式所扭转。
函数执行扭转this
- 因为 JS 的设计原理: 在函数中,能够援用运行环境中的变量。因而就须要一个机制来让咱们能够在函数体外部获取以后的运行环境,这便是
this
。
因而要明确 this
指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如
obj.fn()
,便是obj
调用了函数,既函数中的this === obj
fn()
,这里能够看成window.fn()
,因而this === window
但这种机制并不齐全能满足咱们的业务需要,因而提供了三种形式能够手动批改 this
的指向:
call: fn.call(target, 1, 2)
apply: fn.apply(target, [1, 2])
bind: fn.bind(target)(1,2)
深浅拷贝
浅拷贝:只思考对象类型。
function shallowCopy(obj) { if (typeof obj !== 'object') return let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key] } } return newObj}
简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。
function deepClone(obj) { if (typeof obj !== 'object') return; var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]; } } return newObj;}
简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;function deepClone(target, map = new WeakMap()) { if (map.get(target)) { return target; } // 获取以后值的构造函数:获取它的类型 let constructor = target.constructor; // 检测以后对象target是否与正则、日期格局对象匹配 if (/^(RegExp|Date)$/i.test(constructor.name)) { // 创立一个新的非凡对象(正则类/日期类)的实例 return new constructor(target); } if (isObject(target)) { map.set(target, true); // 为循环援用的对象做标记 const cloneTarget = Array.isArray(target) ? [] : {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop], map); } } return cloneTarget; } else { return target; }}
革除浮动
- 在浮动元素前面增加
clear:both
的空div
元素
<div class="container"> <div class="left"></div> <div class="right"></div> <div style="clear:both"></div></div>
- 给父元素增加
overflow:hidden
或者auto
款式,触发BFC
<div class="container"> <div class="left"></div> <div class="right"></div></div>
.container{ width: 300px; background-color: #aaa; overflow:hidden; zoom:1; /*IE6*/}
- 应用伪元素,也是在元素开端增加一个点并带有
clear: both
属性的元素实现的。
<div class="container clearfix"> <div class="left"></div> <div class="right"></div></div>
.clearfix{ zoom: 1; /*IE6*/}.clearfix:after{ content: "."; height: 0; clear: both; display: block; visibility: hidden;}
举荐应用第三种办法,不会在页面新增div,文档构造更加清晰
节流
节流(throttle
):触发高频事件,且 N 秒内只执行一次。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不论,10 分钟一到我就要发车走人!相似qq飞车的复位按钮。
核心思想:应用工夫戳或标记来实现,立刻执行一次,而后每 N 秒执行一次。如果N秒内触发则间接返回。
利用:节流常利用于鼠标一直点击触发、监听滚动事件。
实现:
// 版本一:标记实现function throttle(fn, wait){ let flag = true; // 设置一个标记 return function(...args){ if(!flag) return; flag = false; setTimeout(() => { fn.call(this, ...args); flag = true; }, wait); }}// 版本二:工夫戳实现function throttle(fn, wait) { let pre = 0; return function(...args) { let now = new Date(); if(now - pre < wait) return; pre = now; fn.call(this, ...args); }}
函数柯里化
什么叫函数柯里化?其实就是将应用多个参数的函数转换成一系列应用一个参数的函数的技术。还不懂?来举个例子。
function add(a, b, c) { return a + b + c}add(1, 2, 3)let addCurry = curry(add)addCurry(1)(2)(3)
当初就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成屡次调用每次传一个参数。
function curry(fn) { let judge = (...args) => { if (args.length == fn.length) return fn(...args) return (...arg) => judge(...args, ...arg) } return judge}
迭代查问与递归查问
实际上,DNS解析是一个蕴含迭代查问和递归查问的过程。
- 递归查问指的是查问申请收回后,域名服务器代为向下一级域名服务器发出请求,最初向用户返回查问的最终后果。应用递归 查问,用户只须要收回一次查问申请。
- 迭代查问指的是查问申请后,域名服务器返回单次查问的后果。下一级的查问由用户本人申请。应用迭代查问,用户须要收回 屡次的查问申请。
个别咱们向本地 DNS 服务器发送申请的形式就是递归查问,因为咱们只须要收回一次申请,而后本地 DNS 服务器返回给我 们最终的申请后果。而本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程,因为每一次域名服务器只返回单次 查问的后果,下一级的查问由本地 DNS 服务器本人进行。