前言:写这篇文章的主要目的是为了方便自己,也想给大家提供一个资料,有不对的地方,大家多多指出。
注意要点(听清楚面试官的问题,想一想再作答,语速放慢,遇到不会的问题不要慌,要主动问面试官问题的答案,目光直视面试官)
面试官上午好,我叫**,今天来应聘贵公司的XXX岗位。我从事前端开发三年多,有X年多的Vue开发经验,在上家公司主要从事后台管理系统,混合App等项目开发。平常喜欢逛一些技术社区丰富自己的技术,像思否,CSDN之类,并且自己也独立开发了个人博客网站,记录自己的工作总结和学习心得。 我的性格比较外向,跟同事朋友相处时比较融洽,在工作中代码开发时我喜欢全心全意的投入,对于工作我总抱着认真负责的态度。面试官,以上是我的介绍,谢谢。(根据自己的具体情况来介绍,介绍一下自己的优势)
这一块一般都会问的,特别是你的年限特别长的时候,你最好把你最近的一个项目好好准备一下,讲一下你在这个项目中当任的角色,这个项目具体内容,亮点和难点。
CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距margin,边框border,填充padding,和实际内容content。
有两种盒模型,标准 盒模型和 怪异 盒模型,区别在于,元素的宽高大小表现为content的大小,而怪异盒模型则表现为content + padding + border的大小
正常流布局 浏览器默认的HTML布局方式,就是按照文档的顺序一个一个显示出来,块元素独占一行,行内元素共享一行
浮动布局 使用 float 属性,使元素脱离文档流,浮动起来。
定位布局 通过 position 属性来进行定位。(这里常考绝对定位于相对定位基准问题,下面再讲)
使用display布局 在css中实现页面布局的主要方法是设定display属性的值。此属性允许我们更改默认的显示方式。 通过将一些内容从block转换为inline(反之亦然)来更改默认表示形式,更重要的是可以通过设置flex 或 grid 进行布局:
flex布局 通过容器和轴进行布局设置。其中,容器分为父容器和子容器。轴分为主轴和交叉轴。
grid 网格布局:可以实现二维布局方式,和 table 布局差不多,基本概念-网格线(Grid Lines) 网格的水平和垂直的分界线 、网格轨道(Grid Track) 好比表格中行或列,分为grid column和grid row 、网格单元格(Grid Cell) 好比表格中的单元格 、网格区域(Grid Area) 好比表格合并单元格后的区域(用的不是很多)
多列布局 : 一种把内容按列排序的方式,就像文本在报纸上排列那样。使用 column-count属性设置需要多少列,也可以使用column-width以至少某个宽度的尽可能多的列来填充容器。
定位position的几个属性要清楚:
Static
positioning)是每个元素默认的属性——它表示“将元素放在文档布局流的默认位置。Relative
positioning)允许我们相对于元素在正常的文档流中的位置移动它——包括将两个元素叠放在页面上。这对于微调和精准设计(design pinpointing)非常有用。Absolute
positioning)脱离文档流。我们可以将元素相对于页面的 元素边缘固定,或者相对于该元素的最近被定位祖先元素(nearest positioned ancestor element)。绝对定位在创建复杂布局效果时非常有用,例如通过标签显示和隐藏的内容面板或者通过按钮控制滑动到屏幕中的信息面板。Fixed
positioning)与绝对定位非常类似,但是它是将一个元素相对浏览器视口固定。 这在创建类似在整个页面滚动过程中总是处于屏幕的某个位置的导航菜单时非常有用。Sticky
positioning)它会让元素先保持和position: static一样的定位,当它的相对视口位置(offset from the viewport)达到某一个预设值时,他就会再像position: fixed一样定位(某些网站头导航栏滚动到一定位置固定到屏幕)。再回答这个问题:
absolute 绝对定位 相对于最近的已定位的祖先元素, (有已定位指position不是static的元素祖先元素)如果无已定位祖先元素, 以body元素为偏移参照基准, 完全脱离了标准文档流。
relative:相对定位元素的定位是相对其正常位置。设置了relative的元素仍然处在文档流中,元素的宽高不变,设置偏移量也不会影响其他元素的位置。
共同点:改变行内元素的呈现方式,都脱离了文档流; 不同点:absolute的”根元素“是可以设置的,fixed的“根元素”固定为浏览器窗口
flex
:优点在于其容易上手,根据flex规则很容易达到某个布局效果。缺点是:浏览器兼容性比较差,只能兼容到ie9及以上。
浮动
:浮动元素是脱离文档流,要做清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等。浮动布局的优点就是比较简单,兼容性也比较好。只要清除浮动做的好,是没有什么问题的。
给父级定义height
缺点:扩展性不好
父级 overflow:hidden
定位 浮动 开启BFC布局
浮动/定位盒子的特点高宽都由内容撑开 缺点:IE6会失效,添加样式zoom:1;
触发拥有布局
(haslayout)
标签清除浮动
缺点:IE6不支持
空标签清除浮动 缺点:违反了结构行为样式相分离的原则.
伪元素清除浮动 clearfix
clearfix:{
*zoom:1; /*IE6 IE7不支持伪类元素 开启haslayout*/
}
clearfix::after{
content:'';
display:block;
clear:both;
}
BFC Block Formatting Context(BFC | 块级格式化上下文),是Web页面中盒模型布局的CSS渲染模式,是一个隔离的独立容器。
怎样形成一个BFC? 块级格式化上下文由以下之一创建:
BFC用处
BFC规则:
实现水平垂直布局基本就是将上面几种方式结合使用,这里总结了7种常用的布局方法,其公共的 CSS
代码如下所示:
body {
margin: 0;
}
.parent {
height: 500px;
width: 500px;
background-color: #eebefa;
margin: 0 auto;
}
.child {
height: 300px;
width: 300px;
background-color: #f783ac;
}
其 HTML
结构也是固定的,就是一个父级包裹一个子级,这里的子级是固定的300px*300px,代码如下:
最终的实现效果如下:
步骤如下:
容器元素行高等于容器高度
通过 text-align: center; 实现水平居中
将子级元素设置为水平块级元素
通过 vertical-align: middle; 实现垂直居中
实现CSS代码如下:
.parent {
/* 1. 设置行高等于容器高度 */
line-height: 500px;
/* 通过 text-align: center; 实现水平居中 */
text-align: center;
}
.child {
/* 将子级元素设置为水平块级元素 */
display: inline-block;
/* 通过 vertical-align: middle; 实现垂直居中 */
vertical-align: middle;
}
步骤如下:
使子元素相对于容器元素定位
子元素开启绝对定位
设置该元素的偏移量,值为50% 减去宽度/高度的一半
实现CSS代码如下:
.parent {
/* 1. 使子元素相对于本元素定位 */
position: relative;
}
.child {
/* 2. 开启绝对定位 */
position: absolute;
/* 3. 设置该元素的偏移量,值为 50%减去宽度/高度的一半 */
left: calc(50% - 150px);
top: calc(50% - 150px);
}
步骤如下:
使子元素相对于容器元素定位
子元素开启绝对定位
设置该元素的偏移量,值为50%
通过外边距-值的方式将元素移动回去
实现CSS代码如下:
.parent {
/* 1. 使子元素相对于本元素定位 */
position: relative;
}
.child {
/* 2. 开启绝对定位 */
position: absolute;
/* 3. 设置该元素的偏移量,值为 50% */
left: 50%;
top: 50%;
margin-left: -150px;
margin-top: -150px;
}
步骤如下:
使子元素相对于容器元素定位
子元素开启绝对定位
将子元素拉满整个容器
通过margin:auto实现水平垂直居中
实现CSS代码如下:
.parent {
/* 1. 使子元素相对于本元素定位 */
position: relative;
}
.child {
/* 2. 开启绝对定位 */
position: absolute;
/* 3. 将子元素拉满整个容器 */
top: 0;
left: 0;
right: 0;
bottom: 0;
/* 4. 通过 margin:auto 实现水平垂直居中 */
margin: auto;
}
步骤如下:
使子元素相对于容器元素定位
子元素开启绝对定位
设置该元素的偏移量,值为50%
通过 translate
反向偏移的方式,实现居中
实现 CSS 代码如下:
.parent {
/* 1. 使子元素相对于本元素定位 */
position: relative;
}
.child {
/* 2. 开启绝对定位 */
position: absolute;
/* 3. 设置该元素的偏移量,值为 50%*/
left: 50%;
top: 50%;
/* 通过translate反向偏移的方式,实现居中 */
transform: translate(-50%, -50%);
}
步骤如下:
将元素设置为 Flex
布局
通过 justify-content: center 以及 align-items: center 实现或者 margin: auto; 实现。
实现CSS代码如下:
.parent {
/* 1. 将元素设置为 Flex 布局 */
display: flex;
/* 2. 通过 justify-content 以及 align-items: center 实现 */
/* justify-content: center;
align-items: center; */
}
.child {
/* 或者通过 margin auto 实现 */
margin: auto;
}
Grid
方案的实现方式相对来说比较简单,方式也较多。
实现CSS代码如下:
.parent {
/* 1. 元素设置为Grid 元素 */
display: grid;
/* 通过 items 属性实现*/
/* align-items: center; */
/* justify-items: center; */
/* items 的缩写 */
/* place-items: center; */
/* 或者通过 content 属性 */
/* align-content: center; */
/* justify-content: center; */
/* content 的缩写 */
/* place-content: center; */
}
.child {
/* 或者通过 margin auto 实现 */
/* margin: auto; */
/* 或者通过 self 属性 */
/* align-self: center;
justify-self: center; */
/* self 的缩写 */
place-self: center;
}
实现水平垂直居中布局的方式大多是通过上面两种布局的方式相结合。
网页生成过程:
HTML
被HTML解析器解析成DOM
树css
则被css解析器解析成CSSOM
树DOM
树和CSSOM
树,生成一棵渲染树(Render Tree
)flow
),即将所有渲染树的所有节点进行平面合成paint
)在屏幕上重排(也称回流): 当DOM
的变化影响了元素的几何信息(DOM
对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 触发:
重绘: 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。 触发:
color、background、box-shadow
等属性重排优化建议:
DOM
元素position:absolute
或fixed
元素,对其他元素影响不大GPU
加速,translate
使用3D
变化transform
不重绘,不回流 是因为transform
属于合成属性,对合成属性进行transition/animate
动画时,将会创建一个合成层。这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。浏览器会通过重新复合来创建动画帧。
display: none;
1. DOM 结构:浏览器不会渲染 display 属性为 none 的元素,不占据空间;
2. 事件监听:无法进行 DOM 事件监听;
3. 性能:动态改变此属性时会引起重排,性能较差;
4. 继承:不会被子元素继承,毕竟子类也不会被渲染;
5. transition:transition 不支持 display。
visibility: hidden;
1. DOM 结构:元素被隐藏,但是会被渲染不会消失,占据空间;
2. 事件监听:无法进行 DOM 事件监听;
3. 性 能:动态改变此属性时会引起重绘,性能较高;
4. 继 承:会被子元素继承,子元素可以通过设置 visibility: visible; 来取消隐藏;
5. transition:visibility 会立即显示,隐藏时会延时
opacity: 0;
1. DOM 结构:透明度为 100%,元素隐藏,占据空间;
2. 事件监听:可以进行 DOM 事件监听;
3. 性 能:提升为合成层,不会触发重绘,性能较高;
4. 继 承:会被子元素继承,且,子元素并不能通过 opacity: 1 来取消隐藏;
5. transition:opacity 可以延时显示和隐藏
string、number、boolean、null、undefined、object(function、array)、symbol(ES10 BigInt)
typeof
主要用来判断数据类型 返回值有string、boolean、number、function、object、undefined。
instanceof
判断该对象是谁的实例。null
表示空对象 undefined
表示已在作用域中声明但未赋值的变量闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript高级程序设计》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,
即使函数是在当前词法作用域之外执行 ——《你不知道的JavaScript》
原型: 对象中固有的__proto__
属性,该属性指向对象的prototype
原型属性。
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype
所以这就是我们新建的对象为什么能够使用toString()
等方法的原因。
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
this
对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this
等于window
,而当函数被作为某个对象调用时,this等于那个对象。 在实际开发中,this
的指向可以通过四种调用模式来判断。
this
指向全局对象。this
指向这个对象。this
指向这个用new
新创建的对象。apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,`` bind方法通过传入一个对象,返回一个
this 绑定了传入对象的新函数。这个函数的
this指向除了使用
new `时会被改变,其他情况下都不会改变。六、new
prototype
对象。this
指向这个对象,执行构造函数的代码(为这个新对象添加属性)作用域
负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链
。
JS
是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then
,MutationObserver
,宏任务的话就是setImmediate setTimeout setInterval
ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。 过程:
XMLHttpRequest
对象;open
方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false)
;onreadystatechange
事件,当readystate
等于4时返回responseText
;event.stopPropagation()
或者 ie下的方法 event.cancelBubble = true;
//阻止事件冒泡
// 第一层为深拷贝
Object.assign()
Array.prototype.slice()
扩展运算符 ...
JSON.parse(JSON.stringify())
递归函数function cloneObject(obj) { var newObj = {} //如果不是引用类型,直接返回 if (typeof obj !== 'object') { return obj } //如果是引用类型,遍历属性 else { for (var attr in obj) { //如果某个属性还是引用类型,递归调用 newObj[attr] = cloneObject(obj[attr]) } } return newObj }
如何实现一个深拷贝
详细解析赋值、浅拷贝和深拷贝的区别
1.ES6 的 Set
let arr = [1,1,2,3,4,5,5,6]
let arr2 = [...new Set(arr)]
2.reduce()
let arr = [1,1,2,3,4,5,5,6]
let arr2 = arr.reduce(function(ar,cur) {
if(!ar.includes(cur)) {
ar.push(cur)
}
return ar
},[])
3.filter()
// 这种方法会有一个问题:[1,'1']会被当做相同元素,最终输入[1]
let arr = [1,1,2,3,4,5,5,6]
let arr2 = arr.filter(function(item,index) {
// indexOf() 方法可返回某个指定的 字符串值 在字符串中首次出现的位置
return arr.indexOf(item) === index
})
Promise.all()方法将多个Promise实例包装成一个Promise对象(p),接受一个数组(p1,p2,p3)作为参数,数组中不一定需要都是Promise对象,但是一定具有Iterator接口,如果不是的话,就会调用Promise.resolve将其转化为Promise对象之后再进行处理。
使用Promise.all()生成的Promise对象(p)的状态是由数组中的Promise对象(p1,p2,p3)决定的。
主要考察宏任务和微任务,搭配promise,询问一些输出的顺序
原理:async 和 await 用了同步的方式去做异步,async 定义的函数的返回值都是 promise,await 后面的函数会先执行一遍,然后就会跳出整个 async 函数来执行后面js栈的代码
前端性能优化的七大手段
/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
// 第一种实现
function createNew() {
let obj = {} // 1.创建一个空对象
let constructor = [].shift.call(arguments)
// let [constructor,...args] = [...arguments]
obj.__proto__ = constructor.prototype // 2.链接到原型
let result = constructor.apply(obj, arguments) // 3.绑定this值
// let result = constructor.apply(obj, args)
return typeof result === 'object' ? result : obj // 4.返回新对象
}
function People(name,age) {
this.name = name
this.age = age
}
let peo = createNew(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)
// 未添加异步处理等其他边界情况
// ①自动执行函数,②三个状态,③then
class Promise {
constructor (fn) {
// 三个状态
this.state = 'pending'
this.value = undefined
this.reason = undefined
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
let reject = value => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = value
}
}
// 自动执行函数
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
// then
then(onFulfilled, onRejected) {
switch (this.state) {
case 'fulfilled':
onFulfilled(this.value)
break
case 'rejected':
onRejected(this.reason)
break
default:
}
}
}
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let arg = [...arguments].slice(1)
let result = context.fn(...arg)
delete context.fn
return result
}
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let arg = [...arguments].slice(1)
return function F() {
// 处理函数使用new的情况
if (this instanceof F) {
return new _this(...arg, ...arguments)
} else {
return _this.apply(context, arg.concat(...arguments))
}
}
}
更多介绍:bind方法的实现
回到顶部
浅拷贝:// 1. ...实现
let copy1 = {...{x:1}}
// 2. Object.assign实现
let copy2 = Object.assign({}, {x:1})
深拷贝:
// 1. JOSN.stringify()/JSON.parse()
// 缺点:拷贝对象包含 正则表达式,函数,或者undefined等值会失败
let obj = {a: 1, b: {x: 3}}
JSON.parse(JSON.stringify(obj))
// 2. 递归拷贝
function deepClone(obj) {
let copy = obj instanceof Array ? [] : {}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return copy
}
// 基础版
function deepClone(origin) {
if (origin == undefined || typeof origin != 'object') {
return origin
}
var result = new origin.constructor()
for (var k in origin) {
result[k] = typeof origin[k] === 'object' ? deepClone(origin[k]) : origin[k]
}
return result
}
// 解决循环引用版
function deepClone(origin, wm = new WeakMap()) {
if (origin == undefined || typeof origin != 'object') {
return origin
}
var val = wm.get(origin)
// 如果 wm 存在,则直接返回
if (val) {
return wm.get(origin)
}
var target = new origin.constructor()
wm.set(origin, target)
for (var k in origin) {
target[k] = typeof origin[k] === 'object' ? deepClone(origin[k], wm) : origin[k]
}
return target
}
// 思路:在规定时间内未触发第二次,则执行
function debounce (fn, delay) {
// 利用闭包保存定时器
let timer = null
return function () {
let context = this
let arg = arguments
// 在规定时间内再次触发会先清除定时器后再重设定时器
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context, arg)
}, delay)
}
}
function fn () {
console.log('防抖')
}
addEventListener('scroll', debounce(fn, 1000))
// 基础版1:时间戳(第一次触发会执行,但不排除不执行的可能,请思考一下哦)
function throttle(fn, delay) {
var prev = Date.now()
return function(...args) {
var dist = Date.now() - prev
if (dist >= delay) {
fn.apply(this, args)
prev = Date.now()
}
}
}
// 基础版2:定时器(最后一次也会执行)
function throttle(fn, delay) {
var timer = null
return function(...args) {
var that = this
if(!timer) {
timer = setTimeout(function() {
fn.apply(this, args)
timer = null
}, delay)
}
}
}
// 进阶版:开始执行、结束执行
function throttle(fn, delay) {
var timer = null
var prev = Date.now()
return function(...args) {
var that = this
var remaining = delay - (Date.now() - prev) // 剩余时间
if (remaining <= 0) { // 第 1 次触发
fn.apply(that, args)
prev = Date.now()
} else { // 第 1 次之后触发
timer && clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(that, args)
}, remaining)
}
}
}
function fn () {
console.log('节流')
}
addEventListener('scroll', throttle(fn, 1000))
// 思路:右边变量的原型存在于左边变量的原型链上
function instanceOf(left, right) {
let leftValue = left.__proto__
let rightValue = right.prototype
while (true) {
if (leftValue === null) {
return false
}
if (leftValue === rightValue) {
return true
}
leftValue = leftValue.__proto__
}
}
柯里化函数的定义:将多参数的函数转换成单参数的形式。
柯里化函数实现的原理:利用闭包原理在执行可以形成一个不销毁的作用域,然后把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个最少参数函数。
第一种:固定传入参数,参数够了才执行
/**
* 实现要点:柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数呢?
* 柯里化函数需要记住你已经给过他的参数,如果没给的话,则默认为一个空数组。
* 接下来每次调用的时候,需要检查参数是否给够,如果够了,则执行fn,没有的话则返回一个新的 curry 函数,将现有的参数塞给他。
*
*/
// 待柯里化处理的函数
let sum = (a, b, c, d) => {
return a + b + c + d
}
// 柯里化函数,返回一个被处理过的函数
let curry = (fn, ...arr) => { // arr 记录已有参数
return (...args) => { // args 接收新参数
if (fn.length <= (...arr,...args)) { // 参数够时,触发执行
return fn(...arr, ...args)
} else { // 继续添加参数
return curry(fn, [...arr, ...args])
}
}
}
var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)
sumPlus(1, 2)(3)(4)
sumPlus(1, 2, 3)(4)
第二种:不固定传入参数,随时执行
/**
* 当然了,柯里化函数的主要作用还是延迟执行,执行的触发条件不一定是参数个数相等,也可以是其他的条件。
* 例如参数个为0的情况,那么我们需要对上面curry函数稍微做修改
*/
// 待柯里化处理的函数
let sum = arr => {
return arr.reduce((a, b) => {
return a + b
})
}
let curry = (fn, ...arr) => { // arr 记录已有参数
return (...args) => { // args 接收新参数
if (args.length === 0) { // 参数为空时,触发执行
return fn(...arr, ...args)
} else { // 继续添加参数
return curry(fn, ...arr, ...args)
}
}
}
var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)()
sumPlus(1, 2)(3)(4)()
sumPlus(1, 2, 3)(4)()
参考链接:js如何用一句代码实现函数的柯里化(ES6)
// 思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
// 组件通信,一个触发与监听的过程
class EventEmitter {
constructor () {
// 存储事件
this.events = this.events || new Map()
}
// 监听事件
addListener (type, fn) {
if (!this.events.get(type)) {
this.events.set(type, fn)
}
}
// 触发事件
emit (type) {
let handle = this.events.get(type)
handle.apply(this, [...arguments].slice(1))
}
}
// 测试
let emitter = new EventEmitter()
// 监听事件
emitter.addListener('ages', age => {
console.log(age)
})
// 触发事件
emitter.emit('ages', 18) // 18
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
详细的实现见:这应该是最详细的响应式系统讲解了
// hash路由
class Route{
constructor(){
// 路由存储对象
this.routes = {}
// 当前hash
this.currentHash = ''
// 绑定this,避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
let imgs = document.querySelectorAll('img')
// 可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
function lazyLoad () {
// 滚动卷去的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
for (let i = 0; i < imgs.length; i ++) {
// 图片在可视区冒出的高度
let x = clientHeight + scrollTop - imgs[i].offsetTop
// 图片在可视区内
if (x > 0 && x < clientHeight+imgs[i].height) {
imgs[i].src = imgs[i].getAttribute('data')
}
}
}
// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)
// 1. 简单流程
// 实例化
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 发送请求
xhr.send(data)
// 设置状态变化回调处理请求结果
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
// 2. 基于promise实现
function ajax (options) {
// 请求地址
const url = options.url
// 请求方法
const method = options.method.toLocaleLowerCase() || 'get'
// 默认为异步true
const async = options.async
// 请求参数
const data = options.data
// 实例化
const xhr = new XMLHttpRequest()
// 请求超时
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout
}
// 返回一个Promise实例
return new Promise ((resolve, reject) => {
xhr.ontimeout = () => reject && reject('请求超时')
// 监听状态变化回调
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve && resolve(xhr.responseText)
} else {
reject && reject()
}
}
}
// 错误回调
xhr.onerror = err => reject && reject(err)
let paramArr = []
let encodeData
// 处理请求参数
if (data instanceof Object) {
for (let key in data) {
// 参数拼接需要通过 encodeURIComponent 进行编码
paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
encodeData = paramArr.join('&')
}
// get请求拼接参数
if (method === 'get') {
// 检测url中是否已存在 ? 及其位置
const index = url.indexOf('?')
if (index === -1) url += '?'
else if (index !== url.length -1) url += '&'
// 拼接url
url += encodeData
}
// 初始化
xhr.open(method, url, async)
// 发送请求
if (method === 'get') xhr.send(null)
else {
// post 方式需要设置请求头
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
xhr.send(encodeData)
}
})
}
特性 | cookie | localStorage | sessionStorage | indexedDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
数据存储大小 | 4K | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在 header,中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
补充:cookie 原本并不是用来储存的,而是用来与服务端通信的,需要存取请自行封装 api。
而 localStorage 则自带 getItem 和 setItem 方法,使用很方便。
localStorage 注意点:
浏览器解析文档的过程中,如果遇到 script 标签,会立即解析脚本,停止解析文档(因为 JS 可能会改变 DOM 和 CSS,如果继续解析会造成浪费)。
如果是外部 script, 会等待脚本下载完成之后在继续解析文档。现在 script 标签增加了 defer 和 async 属性,脚本解析会将脚本中改变 DOM 和 css 的地方> 解析出来,追加到 DOM Tree 和 Style Rules 上
基于对渲染过程的了解,推荐一下优化:
强制缓存是我们在第一次请求资源时在 http 响应头设置一个过期时间,在时效内都将直接从浏览器进行获取,常见的 http 响应头字段如 Cache-Control 和 Expires
协商缓存是我们通过 http 响应头字段 etag 或者 Last-Modified 等判断服务器上资源是否修改,如果修改则从服务器重新获取,如果未修改则 304 指向浏览器缓存中进行获取
你需要知道的HTTP常识
可能是全网最全的http面试答案
如何优雅的谈论HTTP/1.0/1.1/2.0
HTTP1.1 是当前使用最为广泛的HTTP协议
HTTPS 介绍:HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。
握手过程的简单描述如下:
1.浏览器将自己支持的一套加密规则发送给网站。
2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
3.获得网站证书之后浏览器要做以下工作:
a. 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
b. 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
c. 使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
4.网站接收浏览器发来的数据之后要做以下的操作:
a. 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
b. 使用密码加密一段握手消息,发送给浏览器。
5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
这里浏览器与网站互相发送加密的握手消息并验证,目的是为了保证双方都获得了一致的密码,并且可以正常的加密解密数据。其中非对称加密算法用于在握手过程中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而HASH算法用于验证数据的完整性。由于浏览器生成的密码是整个数据加密的关键,因此在传输的时候使用了非对称加密算法对其加密。非对称加密算法会生成公钥和私钥,公钥只能用于加密数据,因此可以随意传输,而网站的私钥用于对数据进行解密,所以网站都会非常小心的保管自己的私钥,防止泄漏。TLS握手过程中如果有任何错误,都会使加密连接断开,从而阻止了隐私信息的传输。正是由于HTTPS非常的安全,攻击者无法从中找到下手的地方,于是更多的是采用了假证书的手法来欺骗客户端,从而获取明文的信息。
a. 浏览器请求资源时首先命中资源的Expires 和 Cache-Control,Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效,可以通过Cache-control: max-age指定最大生命周期,状态仍然返回200,但不会请求数据,在浏览器中能明显看到from cache字样。
b. 强缓存失效,进入协商缓存阶段,首先验证ETagETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据客户端上送的If-None-Match值来判断是否命中缓存。
c. 协商缓存Last-Modify/If-Modify-Since阶段,客户端第一次请求资源时,服务服返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间。再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。
九种跨域方式实现原理
如何跨域我们已经明白了,如果这样问:浏览器没有同源策略会有什么危险?是不是有点瞬间懵逼?
下面是摘选的事例,参考:AJAX跨域访问被禁止的原因
假设有一个黑客叫做小黑,他从网上抓取了一堆美女图做了一个网站,每日访问量爆表。
为了维护网站运行,小黑挂了一张收款码,觉得网站不错的可以适当资助一点,可是无奈伸手党太多,小黑的网站入不敷出。
于是他非常生气的在网页中写了一段js代码,使用ajax向淘宝发起登陆请求,因为很多数人都访问过淘宝,所以电脑中存有淘宝的cookie,不需要输入账号密码直接就自动登录了,然后小黑在ajax回调函数中解析了淘宝返回的数据,得到了很多人的隐私信息,转手一卖,小黑的网站终于盈利了。
如果跨域也可以发送AJAX请求的话,小黑就真的获取到了用户的隐私并成功获利了!!!
后续会持续更新。