导图社区 前端面试之道
这是一篇关于前端面试之道的思维导图,包括前端基础、浏览器、VUE、前端安全、工程化、算法、前端优化等内容。
编辑于2021-09-09 13:15:09前端面试之道
基础
HTML
盒模型
IE盒模型:width=content+border+padding
标准盒模型:width=content
BFC
BFC 是指浏览器中创建了一个独立的渲染区域,该区域内所有元素的布局不会影响到区域外元素的布局,这个渲染区域只对块级元素起作用
创建
1、float的值不是none。2、position的值不是static或者relative。3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex4、overflow的值不是visible
作用
1.利用BFC避免margin重叠2.自适应两栏布局3.消除浮动
语义化
1.当页面样式加载失败的时候能够让页面呈现出清晰的结构2.有利于 seo 优化,利于被搜索引擎收录(更便于搜索引擎的爬虫程序来识别)3.便于项目的开发及维护,使 html 代码更具有可读性,便于其他设备解析。
浏览器常见兼容性问题
1.IE6 双边距 bug:块属性标签 float 后,又有横行的 margin 情况下,在 ie6 显margin比设置的大。
2.3 像素问题 使用 float 引起的 使用 dislpay:inline -3px
3.超链接 hover 点击后失效 使用正确的书写顺序 link visited hover active
4.Ie z-index 问题 给父级添加 position:relative
5.Png 透明 使用 js 代码 改
6.Min-height 最小高度 !Important 解决’
7.select 在 ie6 下遮盖 使用 iframe 嵌套
8. 没有办法定义1px 左 右 的 宽 度 容 器 ( IE6 默 认 的 行 高 造 成 的 , 使 用over:hidden,zoom:0.08 line-height:1px)
9.IE5-8 不支持 opacity,解决办法:.opacity {opacity: 0.4filter: alpha(opacity=60); /* for IE5-7 */-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; /* for IE}
10. png24 为的图片在 iE6 浏览器上出现背景,解决方案是做成 PNG8.
Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示, 可通过加入CSS 属性 -webkit-text-size-adjust: none; 解决
常见元素
块级元素:div ul ol li dl dt dd h1 h2 h3 h4…p table tr
行内元素: a b br i span input select img strong
Css 盒模型:内容,border ,margin,padding
HTML5
1. 拖拽释放(Drag and drop) API
2. 语义化更好的内容标签(header,nav,footer,aside,article,section)
3. 音频、视频 API(audio,video)
4. 画布(Canvas) API
5. 地理(Geolocation) API
6. 本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失;
7. sessionStorage 的数据在浏览器关闭后自动删除
8. 表单控件,calendar、date、time、email、url、search
9. 新的技术 webworker, websocket, Geolocation
CSS
line-height
px:固定的行高
%:基于font-size的百分比大小
数字:基于font-size的倍数大小
选择器
1.id 选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul < li)
6.后代选择器(li a)
7.通配符选择器( * )
8.属性选择器(a[rel = "external"])
9.伪类选择器(a: hover, li: nth - child)
css3新增伪类选择器
p:first-of-type 选择属于其父元素的首个 <p> 元素的每个 <p> 元素。
p:last-of-type 选择属于其父元素的最后 <p> 元素的每个 <p> 元素。
p:only-of-type 选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。
p:only-child 选择属于其父元素的唯一子元素的每个 <p> 元素。
p:nth-child(2) 选择属于其父元素的第二个子元素的每个 <p> 元素。
:enabled、:disabled 控制表单控件的禁用状态。
:checked,单选框或复选框被选中。
rgba()和 opacity 的透明效果有什么不同
rgba()和 opacity 都能实现透明效果,但最大的不同是 opacity 作用于元素,以及元素内的所有内容的透明度,
而 rgba()只作用于元素的颜色或其背景色。(设置 rgba 透明的元素的子元素不会继承透明效果!)
CSS3
1. CSS3 实现圆角(border-radius),阴影(box-shadow),
2. 对文字加特效(text-shadow、),线性渐变(gradient),旋转(transform)
3.transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px)
skew(-9deg,0deg);// 旋转,缩放,定位,倾斜
4. 增加了更多的 CSS 选择器 多背景 rgba
5. 在 CSS3 中唯一引入的伪元素是 ::selection.
6. 媒体查询,多栏布局
7. border-image
JS
事件委托
JS事件代理就是通过给父级元素绑定事件,不给子级元素绑定事件,然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,在原生js里面就是通过event对象的taget属性实现。
优点
1.提升性能,减少事件注册,利于减少内存开销2.简化了dom节点更新时,相应事件的更新。如新添加的子节点不用注册事件,移除dom不需要注销事件
BOM
BOM(Browser Object Model)即浏览器对象模型,提供了独立于内容 而与浏览器窗口进行交互的对象;由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对象是window;BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性.
window
setTimerout、setInterval、alert、confirm、prompt、open、scrollTo
location
window.location对象:用于获得当前页面的地址 (URL),并把浏览器重定向到新的页面。在编写时可不使用 window 这个前缀。
location.herf = 'url地址'
hash 返回#号后面的字符串,不包含散列,则返回空字符串。
host 返回服务器名称和端口号
pathname 返回目录和文件名。 /project/test.html
search 返回?号后面的所有值。
port 返回URL中的指定的端口号,如URL中不包含端口号返回空字符串
portocol 返回页面使用的协议。 http:或https:
navigator
window.navigator 对象包含有关访问者浏览器的信息。在编写时可不使用 window 这个前缀。
navigator.platform:操作系统类型;
navigator.userAgent:浏览器设定的User-Agent字符串。
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
userAgent是最常用的属性,用来完成浏览器判断
offset
1, offsetWidth的实际宽度offsetWidth = width + 左右padding + 左右boder
2, offsetHeith的实际高度offsetHeith = height + 上下padding + 上下boder
3, offsetTop实际宽度offsetTop:当前元素 上边框 外边缘 到 最近的已定位父级(offsetParent) 上边框 内边缘的 距离。如果父级都没有定位,则分别是到body 顶部 和左边的距离.
4, offsetLeft实际宽度offsetLeft:当前元素 左边框 外边缘 到 最近的已定位父级(offsetParent) 左边框 内边缘的距离。如果父级都没有定位,则分别是到body 顶部 和左边的距离
client
1,clientWidth的实际宽度clientWidth = width+左右padding
2,clientHeigh的实际高度clientHeigh = height + 上下padding
3,clientTop的实际宽度clientTop = boder.top(上边框的宽度)
4,clientLeft的实际宽度clientLeft = boder.left(左边框的宽度)
scroll
1,scrollWidth实际宽度scrollWidth:获取指定标签内容层的真实宽度(可视区域宽度+被隐藏区域宽度)。
2,scrollHeight的实际高度scrollHeight:获取指定标签内容层的真实高度(可视区域高度+被隐藏区域高度)
3,scrollTop :内容层顶部 到 可视区域顶部的距离。
4.scrollLeft:内容层左端 到 可视区域左端的距离.
DOM
添加 删除 替换 插入节点
obj.appendChild()
obj.insertBefore() //原生的 js 中不提供 insertAfter();
obj.replaceChild()//替换
obj.removeChild()//删除
注:此处的obj为父元素
DOM节点的获取
IE6,7,8
父节点:parentNode
下一个兄弟节点:nextSibling
上个兄弟节点:previousSibling
第一个子节点:firstChild
最后一个子节点:lastChild
所有子节点:childNodes
注:高级浏览器用以上方法会获取到空、文本、注释节点
火狐、谷歌等高级浏览器
父节点:parentElementNode
下一个兄弟节点:nextElementSibling
上个兄弟节点:previousElementSibling
第一个子节点:firstElementChild
最后一个子节点:lastElementChild
所有子节点:children
内容操作:
innerHTML:插入可执行的标签,标签和样式会被解析,常用于动态的生成页面元素
innerText:插入文本内容,标签和样式会被当做文本处理
冒泡&默认事件
阻止冒泡
event.stoppropagtionevent.cancelBubble(IE浏览器)
阻止默认事件
event.preventDefaultevent.returnValue(IE浏览器)
event的兼容写法
IE:window.eventwindow.event.srcElement
eventevent.target
原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
js高级程序设计
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
New过程?
new过程
新生成了一个对象
链接到原型
绑定 this
返回新对象
实现一个new?
function create() {let obj = {} let Con = [].shift.call(arguments) obj.__proto__ = Con.prototype let result = Con.apply(obj, arguments) return result instanceof Object ? result : obj}
继承的几种方式
1.原型链继承
基本思想是利用原型,将父类的实例作为子类的原型function SuperType(){ this.property = true;}。SuperType.prototype.getSuperValue = function(){return this.property;};function SubType(){this.subproperty = false;}//继承了 SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){return this.subproperty;};var instance = new SubType();alert(instance.getSuperValue()); //true缺点:最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。function SuperType(){this.colors = ["red", "blue", "green"];}function SubType(){}//继承了 SuperTypeSubType.prototype = new SuperType();var instance1 = new SubType();instance1.colors.push("black");alert(instance1.colors); //"red,blue,green,black"var instance2 = new SubType();alert(instance2.colors); //"red,blue,green,black"原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上前面刚刚讨论过的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。
2.借用构造函数(经典继承)
在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。function SuperType(){this.colors = ["red", "blue", "green"];}function SubType(){// 继承了 SuperTypeSuperType.call(this);}var instance1 = new SubType();instance1.colors.push("black");alert(instance1.colors); //"red,blue,green,black"var instance2 = new SubType();alert(instance2.colors); //"red,blue,green"
借用构造函数的问题如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。
3.组合继承(伪经典继承),最常用
指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];}SuperType.prototype.sayName = function(){alert(this.name);};function SubType(name, age){//继承属性SuperType.call(this, name);this.age = age;}//继承方法SubType.prototype = new SuperType();SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function(){alert(this.age);};var instance1 = new SubType("Nicholas", 29);instance1.colors.push("black");alert(instance1.colors); //"red,blue,green,black"instance1.sayName(); //"Nicholas";instance1.sayAge(); //29var instance2 = new SubType("Greg", 27);alert(instance2.colors); //"red,blue,green"instance2.sayName(); //"Greg";instance2.sayAge(); //27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且, instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。
4.原型继承
在 object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲, object() 对传入其中的对象执行了一次浅拷贝
原始方案function object(o){function F(){}F.prototype = o;return new F();}
ECMAscript5var person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]};var anotherPerson = Object.create(person);anotherPerson.name = "Greg";anotherPerson.friends.push("Rob");var yetAnotherPerson = Object.create(person);yetAnotherPerson.name = "Linda";yetAnotherPerson.friends.push("Barbie");alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
5.寄生继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){var clone = object(original); //通过调用函数创建一个新对象clone.sayHi = function(){ //以某种方式来增强这个对象alert("hi");};return clone; //返回这个对象} createAnother() 函数接收了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象( original )传递给 object() 函数,将返回的结果赋值给 clone 。再为 clone 对象添加一个新方法 sayHi() ,最后返回 clone 对象。可以像下面这样来使用 createAnother() 函数:var person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]};var anotherPerson = createAnother(person);anotherPerson.sayHi(); //"hi"
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的 object() 函数不是必需的;任何能够返回新对象的函数都适用于此模式。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
6.寄生组合式继承
function inheritPrototype(subType, superType){var prototype = object(superType.prototype); //创建对象prototype.constructor = subType; //增强对象subType.prototype = prototype; //指定对象}
第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用 inherit-Prototype() 函数的语句,去替换前面例子中为子类型原型赋值的语句了
它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和 isPrototypeOf() 。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
创建对象的几种方法
1.工厂模式:function createPerson(name, age, job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){alert(this.name);};return o;}var person1 = createPerson("Nicholas", 29, "Software Engineer");var person2 = createPerson("Greg", 27, "Doctor");缺点:工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
2.构造函数模式:function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.sayName = function(){alert(this.name);};}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor");缺点:使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中, person1 和 person2 都有一个名为sayName() 的方法,但那两个方法不是同一个 Function 的实例。ECMAScript 中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。
3.原型模式使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){alert(this.name);};var person1 = new Person();person1.sayName(); //"Nicholas"var person2 = new Person();person2.sayName(); //"Nicholas"alert(person1.sayName == person2.sayName); //true缺点:原型中所有属性是被很多实例共享的,因为实例与原型之间的连接只不过是一个指针,而非一个副本。function Person(){}Person.prototype = {constructor: Person,name : "Nicholas",age : 29,job : "Software Engineer",friends : ["Shelby", "Court"],sayName : function () {alert(this.name);}};var person1 = new Person();var person2 = new Person();person1.friends.push("Van");alert(person1.friends); //"Shelby,Court,Van"alert(person2.friends); //"Shelby,Court,Van"alert(person1.friends === person2.friends); //true
4.组合使用构造函数模式和原型模式创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.friends = ["Shelby", "Court"];}Person.prototype = {constructor : Person,sayName : function(){alert(this.name);}}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor");person1.friends.push("Van");alert(person1.friends); //"Shelby,Count,Van"alert(person2.friends); //"Shelby,Count"alert(person1.friends === person2.friends); //falsealert(person1.sayName === person2.sayName); //true
5.寄生构造函数模式通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。function Person(name, age, job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){alert(this.name);};return o;}var friend = new Person("Nicholas", 29, "Software Engineer");friend.sayName(); //"Nicholas"返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖 instanceof 操作符来确定对象类型。
实现防抖节流
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。
function debounce(func, wait) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args) }, wait); }}
节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
function throttle(func, wait) { var previous = 0; return function() { let now = Date.now(); let context = this; let args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } }}
function throttle(func, wait) { let timeout; return function() { let context = this; let args = arguments; if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } }}
call,bind,apply
作用
在特定的作用域中调用函数,都是用来改变函数的运行时的上下文对象(执行上下文),或者说是改变函数内部的this指向。bind返回的是对应的函数,不会立即调用,而call、apply都是立即调用的。
差异
bind
这个方法会创建一个函数的实例,其中this值会被绑定到传给bind()函数的值。
call
call()方法和apply()的作用相同,区别仅在于接收的参数的方式不一样,使用call时候参数必须逐个列举出来传给函数(多个参数)
apply
apply()方法接收两个参数,一个是在其中运行函数的作用域,一个是参数数组
闭包?
闭包就是能够读取其他函数内部变量的函数。
闭包的缺点:滥用闭包函数会造成内存泄露,因为闭包中引用到的包裹函数中定义的变量都永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数
promise
Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:等待中(pending)完成了 (resolved)拒绝了(rejected)这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变
async/await
一个函数如果加上 async ,那么该函数就会返回一个 Promise,async的返回值会自动被promise.resolve()包裹。await会阻塞其后面的代码,先执行await函数,然后执行async外的函数,最后执行await下面的函数
浏览器内核
IE 浏览器的内核 Trident、Mozilla 的 Gecko、google 的 WebKit、Opera 内核 Presto;
Vue
v-for中key的作用
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
响应式原理
vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调
nextTick作用
nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。
浏览器
浏览器渲染机制
1.创建DOM树:解析HTML元素,构建DOM树(标记化和树构建)2.创建样式表(styleRules),分析CSS文件和元素上的inline样式,生成页面样式表。3.创建Render树,将DOM树和样式表关联起来,构建成Render树,这一过程被称为Attachment,每个节点都有attach方法,接受样式信息,返回一个Render对象,这些Render对象最终被构建成一个Render树。4.布局layout,有了Render树浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标。5.绘制Pointing,Render树和节点显示坐标都有了,就调用每个节点的point方法,把他们绘制出来。
操作DOM的代价
每次请求,浏览器都会重构DOM树,完成后面一系列的流程,相当于前几次的计算都是做无用功。
虚拟DOM
虚拟DOM实质上是一个JS对象,用来模拟界面DOM,每次对DOM操作时不会立即操作DOM,而是将更新的diff内容保存到本地的JS对象中,最终将这个对象一次性的attach到DOM树上,再进行后续操作。
好处:
页面的更新可以先全部反映在JS对象(虚拟DOM)中,JS对象保存在内存中,操作内存中的JS对象的速度更快,等更新完成后再将最终的JS对象映射成真实DOM,交由浏览器去绘制。这样可以减少页面的重绘和回流,以及构建render的计算,提升性能。
异步事件
微任务(microtask)
promise,process.nextTick,MutationObserver
宏任务(macrotask)
setTimeout、setInterval、setImmediate,I/O,UI rendering
异步执行过程
JS是单线程语言,只能有一个线程处理事件。JS 引擎会将所有任务按照类别分到这两个队列中,遇到宏任务会放入主线程执行,并将宏任务的callback放入宏任务的事件队列(event queue)中,遇到微任务会将微任务放到event table中,并将注册后的回调函数放入event queue中。待所有宏任务执行完毕后,查看微任务队列中是否有待执行的函数,如果有就放入主线程执行,执行完毕后从宏事件队列中取出宏事件执行,周而复始。
总结
Event Loop执行顺序如下:1.首先执行同步代码,这属于宏任务2.当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行3.执行所有微任务4.当执行完所有微任务后,如果必要会渲染页面5.然后开始下一轮Event Loop,执行宏任务中的异步代码,也就是setTimeout中的回调函数
跨域
浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,此时cookie,localStorage,indexDB无法读取,DOM和JS对象无法获取,Ajax无法发送。允许资源的嵌入如img,script。不允许跨域读操作。
解决方案
JSONP
就是利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据
优点:1.兼容性好2.简单易用
缺点:1.只支持get方法2.安全性不好,容易受XSS攻击
CORS(主流)
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符*则表示所有网站都可以访问资源。虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求:以 Ajax 为例,当满足以下条件时,会触发简单请求1.使用下列方法之一:GET、HEAD、POST2.Content-Type 的值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
复杂请求:不符合以上条件的请求就肯定是复杂请求了。对于复杂请求来说,首先会发起一个预检请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域
postMessage
window.postMessage(messagetargetOrigin)方法是HTML5新引进的特性,可用它来向其它window对象发送消息,IE8+ ,Firfox,chorme,Opera均支持
Nginx、node代理跨域
webScoket协议跨域
执行栈
执行栈是一个存储函数调用的栈结构,遵循先进后出的原则。当开始执行 JS 代码时,首先会执行一个 main 函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈
浏览器储存
cookie
一般由服务器生成,可以设置过期时间,大小限制4K,每次请求都会携带在header中,会对请求性能产生影响
应注意安全性!
value 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识http-only 不能通过 JS 访问 Cookie,减少 XSS 攻击secure 只能在协议为 HTTPS 的请求中携带same-site 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击
缺点:1.`Cookie`数量和长度的限制。每个 domain 最多只能有 20 条 cookie,每个 cookie 长度不能超过 4KB,否则会被截掉。2.安全性问题。如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了。3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
localStorage
除非被清理,否则一直存在,限制在5M
sessionStorage
页面关闭就清理,限制在5M
indexDB
除非被清理,否则一直存在,大小无限制
图解
子主题
重绘和回流
重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘回流是布局或者几何属性需要改变就称为回流。回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
导致性能问题
改变 window 大小
改变字体
添加或删除样式
文字改变
定位或者浮动
盒模型
前端安全
XSS攻击
XSS攻击,全称Cross Site Scripting,跨站脚本攻击。 跨站:所有运行的逻辑都必须是在本站,当非本站的脚本运行在本站为跨站。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。
两种方式:url参数直接注入 ---反射型攻击存储到DB后读取时注入 ---注入型攻击注入点:HTML节点内容HTML属性(img onerror属性)JavaScrit代码富文本目前浏览器自带防御,但是有限,浏览器默认X-XSS-Protection为1,可以拦截反射型,也就是url参数直接注入html节点内容和html属性,但JavaScript代码和富文本不会。
解决方法1.转义HTML2.转义JS(JSON.stringify)3.富文本过滤4.CSP,CSP,全称Content Security Policy,内容安全策略,用于指定哪些内容可执行,它是一个http头。(`Content-Security-Policy`,`default-src 'self'`)
持久型也就是攻击的代码被服务端写入进数据库中,这种攻击危害性很大,因为如果网站访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。
SQL注入
用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入
CSRF
CSRF攻击,全称Cross Site Request Forgy,跨站请求伪造。它的意思是在其它的网站对目标网站发送请求,而这些请求是在用户不知情的情况下完成的。它是发生在匿名的情况下,第三方网站,带上Cookies,向你的网站发送请求,不访问你的前端。
攻击原理用户登录A网站A网站确认身份B网站向A网站发送请求(带A网站的身份)防范措施1.禁止第三方网站带Cookies2.在前端页面加入验证信息:验证码(用户体验差)、token3.禁止第三方网站请求:验证referer
点击劫持
点击劫持,是通过用户点击完成的操作,而用户完全不知情的情况下,它的原理是通过iframe标签嵌套,然后把opaccity透明度设置为0。
点击劫持防御措施(两者结合):1.JavaScript禁止内嵌2.请求头X-FRAME-OPTIONS禁止内嵌
工程化
https://www.jianshu.com/p/42e11515c10f
算法
排序
1.冒泡排序:
从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素for(var i = 0;i< arr.length-1;i++){ for(var j =0;j<arr.length-i;j++){ if(arr[j]>arr[j+1]){ [arr[j],arr[j+1]] = [arr[j+1],arr[j]] } } }
2.选择排序
找出每一轮遍历的最小值,然后与索引位置元素替换for (var i = 0; i < arr.length - 1; i++){ var min = i; for(var j = i + 1;j < arr.length; j++){ if(arr[min] > arr[j]){ min = j } } if(min!==i) [arr[min],arr[i]] = [arr[i],arr[min]]; }
3.插入排序
默认第一个元素为最小值,取下一个索引元素与当前已排序的元素比较,若当前元素大就交换位置for(var i = 1; i < arr.length; i++){ for(var j = i -1;j>=0&&arr[j]>arr[j+1];j--){ [arr[j],arr[j+1]] = [arr[j+1],arr[j]] } }
4.希尔排序
动态的设置间隔,将间隔两端的数var len = arr.length; for (var w = Math.floor(len/2);w>0;w=Math.floor(w/2)){ for(var i = w; i < arr.length; i++){ for(var j = i -w;j>=0&&arr[j]>arr[j+w];j-=w){ [arr[j],arr[j+w]] = [arr[j+w],arr[j]] } } }
5.快速排序
在数据集之中,找一个基准点,建立两个数组,分别存储左边和右边的数组,利用递归进行下次比较。function quickSort(arr) { var num = Math.floor(arr.length/2) var citem = arr.splice(num,1)[0]; var left = []; var right = []; for(var i = 0; i<arr.length; i++){ if(arr[i]>citem){ right.push(arr[i]) }else{ left.push(arr[i]) } } return [...left,citem,...right] }
数组去重
1.利用indexOf
function uniq(array){ var temp = []; //一个新的临时数组 for(var i = 0; i < array.length; i++){ if(temp.indexOf(array[i]) == -1){ temp.push(array[i]); } } return temp;}
2.对象键值法
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } let res = [], obj = {} for (let i = 0; i < arr.length; i++) { if (!obj[arr[i]]) { res.push(arr[i]) obj[arr[i]] = 1 } else { obj[arr[i]]++ } } return res}
3.set与解构去重
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } return [...new Set(arr)]}
4.下标判断法(类似方法1)
function unique2(arr){ var hash=[]; for (var i = 0; i < arr.length; i++) { if(arr.indexOf(arr[i])==i){ hash.push(arr[i]); } } return hash;}
5.filter(类似于方法4)
var r = arr.filter(function(element,index,self){ return self.indexOf(element) === index; });
6.先排序,后删除一样的元素
其他
从 URL 输入到页面展现背后发生了什么事
1.DNS 解析:将域名解析成 IP 地址2.TCP 连接:TCP 三次握手3.发送 HTTP 请求4.服务器处理请求并返回 HTTP 报文5.浏览器解析渲染页面6.断开连接:TCP 四次挥手
一次完整的 HTTP 事务是怎么一个过程
一.域名解析二.发起TCP的3次握手三.建立TCP连接后发起http请求四.服务器端响应http请求,浏览器得到html代码五. 浏览器解析html代码,并请求html代码中的资源六.浏览器对页面进行渲染呈现给用户
浏览器的内核有哪些?分别有什么代表的浏览器
1.Trident(IE浏览器)
2.Gecko(Firefox 内核)
3.Webkit(chorm和Safari)
4.Presto(opera,已弃用改用blink)
5.blink(chorm)
HTTP
三次握手,四次挥手
TCP三次握手
TCP 四次挥手
子主题
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
http和https
一、HTTP和HTTPS的基本概念
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
二、HTTP和HTTPS的区别
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。HTTPS和HTTP的区别主要如下:1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。4、HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。
三,HTTPS
(1)客户使用HTTPS的URL访问Web服务器,要求与Web服务器建立SSL连接。 (2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。 (3)客户端的浏览器与Web服务器开始协商SSL/TLS连接的安全等级,也就是信息加密的等级。 (4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。 (5)Web服务器利用自己的私钥解密出会话密钥。 (6)Web服务器利用会话密钥加密与客户端之间的通信。
常见状态码
2XX 成功
200 OK,表示从客户端发来的请求在服务器端被正确处理204 No content,表示请求成功,但响应报文不含实体的主体部分206 Partial Content,进行范围请求
3XX 重定向
301 moved permanently,永久性重定向,表示资源已被分配了新的 URL302 found,临时性重定向,表示资源临时被分配了新的 URL303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况307 temporary redirect,临时重定向,和302含义相同
4XX 客户端错误
400 bad request,请求报文存在语法错误401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息403 forbidden,表示对请求资源的访问被服务器拒绝404 not found,表示在服务器上没有找到请求的资源
5XX 服务器错误
500 internal sever error,表示服务器端在执行请求时发生了错误503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
前端缓存
HTTP缓存
强缓存
1.不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)
2.存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
3.存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
规则
当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。这两者的区别就是前者是绝对时间,而后者是相对时间。Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义。
Cache-Control
public:所有内容都将被缓存(客户端和代理服务器都可缓存)private:所有内容只有客户端可以缓存,Cache-Control的默认取值no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
协商缓存(对比缓存)
对比缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。实际项目中他们也的确经常一同出现。
Last-Modified & If-Modified-Since(需搭配cache-control)
1.服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间,例如Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT2.浏览器将这个值和内容一起记录在缓存数据库中。3.下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段4.服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。但是他还是有一定缺陷的:如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
Etag & If-None-Match(配合Cache-Control使用)
为了解决上述问题,出现了一组新的字段 Etag 和 If-None-MatchEtag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。Etag 的优先级高于 Last-Modified
强缓存与协商缓存区别:强缓存不发请求到服务器,协商缓存会发请求到服务器。
如何设置前端缓存
1、html页面缓存的设置主要是在<head>标签中嵌入<meta>标签,这种方式只对页面有效,对页面上的资源无效1.1、html页面禁用缓存的设置如下:<meta http-equiv="pragma" content="no-cache">// 仅有IE浏览器才识别的标签,不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求<meta http-equiv="cache-control" content="no-cache">// 其他主流浏览器识别的标签<meta http-equiv="expires" content="0"> // 仅有IE浏览器才识别的标签,该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段1.2、html设置缓存如下:<meta http-equiv="Cache-Control" content="max-age=7200" />// 其他主流浏览器识别的标签<meta http-equiv="Expires" content="Mon, 20 Aug 2018 23:00:00 GMT" />// 仅有IE浏览器才识别的标签2.静态资源的缓存一般是在web服务器上配置的,常用的web服务器有:nginx、apache。
注意
1、强缓存情况下,只要缓存还没过期,就会直接从缓存中取数据,就算服务器端有数据变化,也不会从服务器端获取了,这样就无法获取到修改后的数据。决解的办法有:在修改后的资源加上随机数,确保不会从缓存中取。例如:http://www.kimshare.club/kim/common.css?v=22324432http://www.kimshare.club/kim/common.2312331.css2、尽量减少304的请求,因为我们知道,协商缓存每次都会与后台服务器进行交互,所以性能上不是很好。从性能上来看尽量多使用强缓存。3、在Firefox浏览器下,使用Cache-Control: no-cache 是不生效的,其识别的是no-store。这样能达到其他浏览器使用Cache-Control: no-cache的效果。所以为了兼容Firefox浏览器,经常会写成Cache-Control: no-cache,no-store。
前端优化
(1) 减少 http 请求次数:CSS Sprites, JS、CSS 源码压缩、图片大小控制合适;网页Gzip,CDN 托管,data 缓存 ,图片服务器。
(2) 前端模板 JS+数据,减少由于 HTML 标签导致的带宽浪费,前端用变量保存 AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
(3) 用 innerHTML 代替 DOM 操作,减少 DOM 操作次数,优化 javascript 性能。
(4) 当需要设置的样式很多时设置 className 而不是直接操作 style。
(5) 少用全局变量、缓存 DOM 节点查找的结果。减少 IO 读取操作。
(6) 避免使用 CSS Expression(css 表达式)又称 Dynamic properties(动态属性)。
(7) 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
(8) 避免在页面的主体布局中使用 table,table 要等其中的内容完全下载之后才会显示出来,显示比 div+css 布局慢。
刷题
闭包
指有权访问另一个函数作用于中的变量的函数
this指向问题
深浅拷贝
浅拷贝
Object.ssign()
拓展运算符...
arr.slice()
深拷贝
递归法遍历对象,直到拷贝完成
Json.parse(Json.stringify())局限性:会忽略 undefined,symbol不能序列化函数不能解决循环引用的对象
为什么 0.1 + 0.2 != 0.3
因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。IEEE 754 双精度版本(64位)将 64 位分为了三段第一位用来表示符号接下去的 11 位用来表示指数其他的位数用来表示有效位,也就是用二进制表示 0.1 中的 10011(0011)那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002
手写一个XMLHttpRequest?
var xhr;if(window.XMLHttpRequest)检查浏览器是否支持XMLHttpRequest对象{ xhr=new XMLHttpRequest();}else{ xhr=new ActionXObject("Microsoft.XMLHTTP");}xhr.onreadystatechang=functioin(){ // 状态发生变化时,函数被回调 if(xhr.readystate===4){ // 成功完成 if(xhr.status===200) { //成功,拿到响应文本 success(xhr.responseText); } else{//失败,返回响应码 fail(xhr.status); } }}//发送请求到服务器xhr.open('Get','/URL/);//第三个参数默认为true 异步 ,false为同步xhr.send();//get方式不需要参数,post方式需要把body部分以字符串或者FormData对象传进去。
移动端