- js基础知识
- ES6常考知识点
- js进阶知识点
- 浏览器基础知识及常考知识点
- 网络基础知识
- webpack&Vue
- 设计模式
js基础知识
1.原始类型的有哪几种?null是对象吗?
1.1js中的原始类型有
undefined
null
symbol
string:string类型的值是不会变化的,无论调用任何方法
number:js中的类型是浮点类型,使用过程中的bug在计算的时候0.1 + 0.2 !== 0.3
boolean
1.2 null不是对象,虽然在检测时候显示的object
2. Object类型
2.1 对象类型和原始类型的不同之处?
除了原始类型其与的都是object类型
原始类型存放的是值
对象类型存放的是地址
2.2 函数参数是对象会发生什么问题?
函数参数是对象的情况下,内部修改形参地址的会导致传入的对象参数指向新的地址空间
和传入之前的实参指向不同的地址空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function test(person) {
person.age = 23;
//person指向新的地址空间
person = {
name: 'yyy',
age: "30"
}
return person
}
var p1 = {
name: 'hhh',
age: "10"
}
var p2 = test(p1)
console.log(p1 == p2);//false
console.log(p1.age)//23
console.log(p2.age)//30所以最后
person
拥有了一个新的地址(指针),也就和p1
没有任何关系了,导致了最终两个变量的值是不相同的。
3 typeof vs instanceof(判断类型)
- typeof:
- 除了null之外可以判断所有的原始数据类型
- 对象数据类型中除了函数function,其他的都判断为object,不能判断对象具体的数据类型
- instanceof:
- 可以判断对象的具体数据类型,原因:instanceof内部机制是通过原型链中是否含有该类的原型
- 不能直接判断原始类型,需要转化才能判断原始类型
- instanceof转化
Symbol.hasInstance
等价于typeof 'hello'=='string'
1
2
3
4
5
6
7//将instanceof转化为可以判断原始数据类型,这里通过自定义instanceof的方法等价于
class PrimativeString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimativeString);//true
3.2 判断具体的对象类型
Object.prototype.tosString().call(xx)
:可以对象的类型字符串instanceof
:通过查找原型链的方式
3.3 将伪数组转化为真正数组
- Array.from()
- […arr]
3.4判断对象中是不是含有属性
- in
- hasOwnProperty()
4 .类型转化

类型转化分为
boolean:在条件判断时,除了
undefined
,null
,false
,NaN
,''
,0
,-0
,其他所有值都转为true
,包括所有对象。number
字符串
对象转原始类型:调用内置的
[[ToPrimitive]]
函数,对于该函数来说,算法逻辑一般来说如下:如果已经是原始类型了,那就不需要转换了
调用
x.valueOf()
,如果转换为基础类型,就返回转换的值调用
x.toString()
,如果转换为基础类型,就返回转换的值如果都没有返回原始类型,就会报错
可以重写
Symbol.toPrimitive
1
2
3
4
5
6
7
8
9
10
11
12
13let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
//重写原始类型的转化
[Symbol.toPrimitive]() {
return 2
}
}
1 + a // => 3
四则运算符
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
- 那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
1
2
3
4
5
6
71 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3" 将数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3
'a' + + 'b' // -> "aNaN"//+ 'b'转数字失败为NaN
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN 数组[1,2]转数字为NaN比较运算符
- 如果是对象,就通过
toPrimitive
转换对象 - 如果是字符串,就通过
unicode
字符索引来比较
1
2
3
4
5
6
7
8
9let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true 因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。- 如果是对象,就通过
5. this(如何正确判断 this?箭头函数的 this 是什么?)
5.1 this调用情况(4)
- 直接调用,this->window
- 通过实例对象调用,this->实例对象
- 通过new的方式创建,this永远指向->指向的实例对象,且不会被改变
- 改变this指向的api(bind,apply,call),this->api的第一个参数
箭头函数中this指向包裹箭头函数的第一个普通函数,这里继续扩展箭头函数
5.2 this指向的优先级
new
的方式优先级最高>bind
这些函数>obj.foo()
这种调用方式>foo
这种调用方式,同时,箭头函数的this
一旦被绑定,就不会再被任何方式所改变。- 针对单个规则使用this指向那个问题

5.3 bind函数被调用多次this的指向问题?
- 注意bind返回的是函数,bind调用 多次指向的始终是第一次绑定的this
1 | const a = {} |
1 | // fn.bind().bind(a) 等于 |
5.4 箭头函数和普通函数的区别
- 箭头函数中没有this,this指向的是包裹箭头函数的第一个不是箭头函数的普通函数的this
- 箭头函数不能当做构造函数,不能通过new来创建会报错
- 箭头函数中没有arguments对象,可以用rest参数代替
- 箭头函数中没有yield命令,不能用作generator函数
6.==
vs ===
6.1 比较
==
会先对比类型,类型不一致则会进行类型转换然后在比较值,类型相同则会在比较值,类型转化直接见上文===
不会进行类型的转化,会直接比较类型和值
7 闭包
- 闭包:匿名函数内部可以访问函数外部的变量,延伸变量的作用域范围,拥有了自己单独的作用域
- 闭包:
- 立即执行函数IEF就是一个闭包,可以传入函数外部的变量来进行接收
- 补充立即执行函数:
- 一是不必为函数命名,避免了污染全局变量
- 二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
7.1 循环中使用闭包解决 var
定义函数的问题
- 问题
1 | // 问题:循环中不能每次打印出传入的循环制 |
- 闭包的方式解决:IFE
1 | //解决方案:闭包的方案:理解执行函数 |
- 将i的值作为参数传入setTimeOut的第三个参数,当函数执行的时候回自动传入参数1中的function函数中,定义形参直接接受就行
1 | //将值作为参数传入setTimeOut中(当函数执行的时候直接传入) |
- 通过Es6块级作用域let来定义变量
1 | // 3.解决方案:块级作用域Let来定义变量 |
8 深拷贝和浅拷贝
8.1深拷贝,浅拷贝和赋值的区别
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
概念
- 浅拷贝:浅拷贝中拷贝的是地址,共享同一块内存空间,修改其中一个,另外一个也会改变
- 深拷贝:深拷贝是从堆栈中完整的拷贝一份出来,开辟了新的内存空间,修改值不互相影响
8.2 浅拷贝的实现
- 通过Object.assign(target,sources)实现:拷贝的所有可枚举的值,指向的是同一个引用
1 |
|
- 通过展开运算符实现:
...
这里和
1 | // 2,通过展开运算符实现浅拷贝 |
通过函数库中的loadsh的_.clone()方法
1
2
3
4
5
6
7
8
9
10// 浅拷贝3. lodash.clone(value)
var _ = require('lodash');
var b = {
name: 'zhangsan',
age: '45',
foo: {}
}
var c = _.clone(b)
console.log(c == b);//false
console.log(b.foo == c.foo);//true,子对象指向同一个引用Array.prototype.concat()
Array.prototype.slice()
8.3 深拷贝
- JSON.parse(JSON.Stringify()):将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
- 不足:
- 会忽略
undefined
- 会忽略
symbol
- 不能序列化函数
- 不能解决循环引用的对象
- 会忽略
- 不足:
1 | // (1)JSON.parse(JSON.stringify(arr)) |
- 函数库lodash.cloneDeep()
1 | // (2)lodash.cloneDeep() |
- jquery.extend()
- 手写递归方法实现深拷贝:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
- ES6新提出的两种数据结构weakSet和weakMap,表示都是弱引用,对值的引用都不计入垃圾回收机制
- hasOwnProperty()用来遍历对象中的属性
1 | // (3)手写实现递归函数 |
9 js执行过程中堆,栈以及浏览器事件机制(even loop)和Node中的even loop
9.1js内存机制
javascript具有自动垃圾回收机制,js内存中的每个数据都需要存在内存空间,
js内存空间分为
- 栈空间:存储基本数据类型和调用函数
- 堆空间:存储的是引用数据类型,堆空间的引用数据类型是不能直接操作,需要在栈空间中声明变量(存储的是地址)作为引用来指向堆空间中的地址,如果需要操作堆空间中的数据必须通过===栈空间的引用变量===
永远都是栈中的代码先执行,在从队列中依次读取事件事件,加入到栈中执行
9.2 浏览器中的事件机制(Event loop)
进程和线程的区别,
进程是资源分配的最小单位,线程是CPU调度的最小单位
二者都是CPU工作时间段的描述
- 进程描述了运行指令及加载和保存上下文所需的时间,是一个程序
- 线程是进程中更小的单位,描述执行一个指令所需的时间
- 一个进程中可以有很多线程
- 比如打开一个页面,就是创建了一个进程,一个进程可以有很多线程,比较js引擎线程,渲染线程,http请求线程,请求结束后,线程可能就销毁
js单线程带来的好处
- js是单线程执行的,也就是每次只有主线程执行
- js渲染引擎线程和页面渲染线程,在页面加载中js引擎线程会阻止界面的渲染,表明这两个线程是 互斥的,主要原因是:js可以操作dom元素会影响页面的渲染,单线程的话就可以解决这种问题
- 同时单线程可以节省内存,节约上下文切换时间(没有锁)
基本概念
- 事件循环:指的是执行完一个宏任务之后,然后执行清空所有的微任务,在执行下一个宏任务,在执行所有的微任务的循环过程,每次任务执行完毕都要进行页面的渲染,保证js内部宏任务和dom同步
- 任务:都是存放在回调队列中
- 宏任务task(macrotask):script,setTimeOut,setInterval
,
setImmediate,IO,UI render - 微任务jobs(microtask):promise , ajax , Object.observe,MutationObserver,process.nextTick
- 宏任务task(macrotask):script,setTimeOut,setInterval
浏览器中的事件机制理解
执行栈:存储函数调用的执行栈
JS
在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到Task
(有多种task
) 队列中。一旦执行栈为空,Event Loop
就会从Task
队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说JS
中的异步还是同步行为Even loop完整的执行顺序
- 执行同步代码,这是宏任务
- 执行完宏任务之后,判断是不是有微任务
- 执行完所有微任务清空栈
- 从回调队列中执行下一次的宏任务,宏任务中异步代码的执行
在even loop中,如果宏任务中的异步代码有大量的计算和dom操作,为了更快的响应界面,可以把它们放在微任务中

9.3 Node中的Event Loop
面试中问题:Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行顺序?
Node中的Event loop和浏览器中事件循环集中是完全不同的
Node 中的Event loop是划分为6个阶段,他们会按照顺序反复执行,每当进入一个阶段,都会从回调队列中拿出函数进行执行,当队列空或者回调函数数量达到系统执行的阈值,就会进入下一个阶段
timer阶段:执行的是setTimeOut和setInterval回调,并且是由poll阶段控制的,在node中执行的时间也是准确时间,只能是尽快执行
I/o阶段:会处理上一轮循环中的少数未执行的I/O回调
idle,prepare阶段:内部已经实现
poll阶段:是非常重要的阶段,
- 系统会两件事
- 回到timer阶段执行回调
- 执行I/O回调
- 进入poll阶段,如果没有设定timer,会发生以下
- poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统阈值
- poll队列为空,会进行如下操作
- 如果有
setImmediate
回调需要执行,poll阶段会停止,回到check阶段执行回调 - 没有
setImmediate
回调需要执行,会等待回调加入到执行队列中并立即执行(这里同样会设置超时时间,防止一直等待下去)
- 如果有
- 如果设定了timer并且队列为空,会判断是都有timer超时,有的话直接回到timer阶段执行回调
- 系统会两件事
check阶段:执行
setImmediate
回调close callbacks阶段:执行close事件
上面是宏任务执行情况,微任务执行会在每个阶段完成前清空微任务队列
注意:Node中的event loop和浏览器中Event loop相同点:微任务永远在宏任务之前执行
process.nextTick执行顺序
- 这个函数是独立于event loop之外的,有自己的队列,每个阶段完成之后 ,如果存在nextTick队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。
9.4 小结
- 栈:(执行栈:存储函数调用的栈结构,先进后出)
- 存储的基本数据类型和调用函数
- 按值访问
- 存储的值大小固定
- 由系统自动分配内存空间
- 空间小,运行效率高
- 栈中的DOM,ajax,setTimeout会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行。
- 微任务和宏任务
执行栈的存储是有限制的,递归的过程中存过多的调用函数没有及时的释放会出现栈溢出的情况
- 堆
- 存储引用数据类型
- 按引用访问
- 存储的值大小不定,可动态调整
- 主要用来存放对象
- 空间大,但是运行效率相对较低
- 无序存储,可根据引用直接获取
js中存储的对象,数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象
10 原型和原型链
10.1 原型
- 每个函数都有属性
prototype
属性,除了Function.prototype.bind()
,指向自己的原型对象 - 每个实例对象都有
__proto__
属相,指向创建该对象的contructor的原型,其实这个属性指向的是了prototype
,prototype
是内部属性外部无法进行访问,通过_proto__
来访问 - 对象可以通过
__proto__
来查找不属于自己的属性,__proto__
将原型连接起来形成原型链
10.2 小结
Object
是所有对象的爸爸,所有对象都可以通过__proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过__proto__
找到它- 函数的
prototype
是一个对象 - 对象的
__proto__
属性指向原型,__proto__
将对象和原型连接起来组成了原型链
11 原型继承和class继承
11.1 原型如何实现继承
- 本质是将子类的原型指向父类
- 组合继承:在子类的构造函数中通过
Parent.call(this)
继承父类的属性,然后改变子类的原型为new Parent()
来继承父类的函数。
1 | // 1.组合继承:将子类的原型指向父类,就可以直接继承父类的方法和属性 |
11.2 class如何实现继承
class
实现继承的核心在于使用extends
表明继承自哪个父类,并且在子类构造函数中必须调用super
,因为这段代码可以看成Parent.call(this, value)
。
1 | // 2. class继承实现 |
11.3 class的本质
- class的本质是function
1 | class Person{ |
ES6常考知识点
1. var let const
- let 和const是ES6中,
- 存在块级作用域,
- 没有变量的提升,使用之前必须先声明
- const 用于定义常量,内存地址,
- let用于定于变量,该块处于从块开始到初始化处理的
暂时性死区
- var
- 存在变量的提升,会自动在内存中创建空间,可以先使用后使用,此时变量的值undefined
- let并不会自动在内存总创建空间,而是在初始化的时候才会开辟内存空间
- 小结
- 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
var
存在提升,我们能在声明之前使用。let
、const
因为暂时性死区的原因,不能在声明前使用var
在全局作用域下声明变量会导致变量挂载在window
上,其他两者不会let
和const
作用基本一致,但是后者声明的变量不能再次赋值
2 .promise(解决异步编程)
2.1 promise的api
解决的问题:promise解决了回调地狱问题
promise的API:promise是ES6中提出解决 异步编程的问题,可以进行 then方式链式调用,用来进行延迟和异步计算
- promise中存在的状态有pending状态,fullfilled,rejected三种状态,初始状态时pending,一旦转变为其他两种状态就不能在改变
- 主要是promise中传入的函数参数resolve()和reject()函数会根据promise的状态进行调用,fullfilled状态调用resolve函数,rejected状态调用reject函数
- promise的then()方式返回的都是promise实例(和之前的实例时不一样的),传递的参数是两个函数,函数中掺入值用于异步接收resolve()和reject()函数传递过来的值,then方式可以链式调用,当状态发生改变时执行promise的回调栈中加入的回调函数
1
promise.then(onFulfilled, onRejected)
promise在执行过程中是同步执行,then方法在执行过程中是异步执行,会加入入到循环队列中(次数可扩展even loop继续讲解)
2.2 promise的实现
1 | // 三个常量用于表示状态 |
3 generator(解决异步编程)
- 遍历器对象生成函数,可以交出函数的执行权,可以解决异步编程的问题
- 特点
function
和函数名之间通过*
连接- 函数体内部通过表达式
yield
来控制状态(暂停代码) - 通过next()来控制下一步状态的执行
- 实现
1 | function* test() { |
4 async/await(解决异步编程,使用前提是promise对象)
- 异步函数是generator函数的语法糖。有更好的语义、更好的适用性、返回值是
Promise
。 - async->
*
(同步执行) - await->yield(异步执行) ,await返回的是一个promise对象,会被加入到队列中
1 | async function timeout (ms) { |
5 异步编程的小结
- async/await和promise的区别
async
和await
相比直接使用Promise
来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用await
可能会导致性能问题,因为await
会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性
- 异步编程的实现
- 传统的异步编程
- 回调函数(不利于维护,代码耦合高)
- 事件监听(事件驱动型,流程不够清晰)
- 发布订阅(观察者)(类似于事件监听,可以通过消息中心,了解现有多少发布者有多少订阅者)
- ES6中的实现方式:
- promise
- generator
- async/await
- 传统的异步编程
- 传统的异步编程实现方式之一:协程,多个线程互相协作,完成异步任务。
6 模块化
6.1模块化优势
- 解决命名冲突
- 提高代码的复用性
- 提高代码的可维护性
6.2模块化实现的方式
- 立即执行函数IIFE
- AMD/CMD
- CommonJS(在node中使用,现在在webpack中也使用)
1 | var module = require('./a.js') |
- ES module(ES6中提出来的模块化概念)主要在前端中使用(浏览器)
1 | // 引入模块 API |
CommonJS和ES Module的区别
- ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别
- CommonJs是动态导入的,导入就会立即执行,后者不支持
- CommonJs是同步导入,用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大;后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
- CommonJs在导出的时候是值拷贝,每次导出的值变了,必须重新进行导入,后者采用的是实时绑定的方式,导入和导出都是指向同一个内存空间,是地址拷贝
- ES Module 会编译成
require/exports
来执行的
7 proxy可以实现什么功能
7.1 基本概念
proxy:代理,可以自定义对象中的操作(traps),通过操作new的proxy实例对象就可以直接操作被代理的对象,
Proxy 就像在目标对象之间的一个代理,任何对目标的操作都要经过代理。代理就可以对外界的操作进行过滤和改写。
用法:
let p = proxy(target,handle)
- target:被代理的对象
- handle(参数是代理函数对象):
handler
是一个对象,其属性是当执行一个操作时定义代理的行为的函数。,其中的traps包括如下图
应用: Vue3.0 中将会通过
Proxy
来替换原本的Object.defineProperty
来实现数据响应式
7.2补充Reflect
Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect不是一个函数对象,因此它是不可构造的。Reflect
的所有的方法都是静态的就和Math
一样,目前它还没有静态属性。Reflect
对象的方法与Proxy
对象的方法相同。有十三种静态方法has(target, key)
与in
操作符一样,让判断操作都变成函数行为。deleteProperty(target, key)
与delete
操作符一样,让删除操作变成函数行为,返回布尔值代表成功或失败。construct(target, argumentsList[, newTarget])
与new
操作符一样,target
构造函数,第二参数是构造函数参数类数组,第三个是new.target的值。get(target, key[, receiver])
与obj[key]
一样,第三个参数是当要取值的key
部署了getter
时,访问其函数的this
绑定为receiver
对象。注意这里的receiver在代理中指向的是proxyset(target, key, value[, receiver])
设置target
对象的key
属性等于value
,第三个参数和set
一样。返回一个布尔值。
1 | reflect是ES6中新添加的方法,用于拦截javascript操作 |
7.3 this的指向
- 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的
this
关键字会指向 Proxy 代理。
7.4通过proxy实现数据的响应式
1 | let onWatch = (obj, setBind, getLogger) => { |
- 自定义
set
和get
函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。 - 需要实现一个 Vue 中的响应式,需要我们在
get
中收集依赖,在set
派发更新,之所以 Vue3.0 要使用Proxy
替换原本的 API 原因在于Proxy
无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是Proxy
可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了
8 reduce map filter
map:用于生成一个新的数组,将每个元素拿出来做完变换在存储在新的数组中
map
的回调函数接受三个参数,分别是当前索引元素,索引,原数组1
2
3
4
5
6[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]
['1','2','3'].map(parseInt)
第一轮遍历 parseInt('1', 0) -> 1 转化为八进制
第二轮遍历 parseInt('2', 1) -> NaN
第三轮遍历 parseInt('3', 2) -> NaN,2进制的表示中不存在3parseint(string,radix),
- radix表示的进制,范围在2-36,在这个范围之外是不能转化为数字的返回NaN
- radix为0,undefined或者未指定会默认做以下情况
- string以
0x
开头默认十六进制 - string以
0
开头默认八进制 - string以任何其他值开头的,默认为十进制
- string以
filter:返回一个新的数组,遍历的时候将条件返回为true的元素存在新的数组中,可以过滤数组中的元素
1
2
3
4
5//filter
var arrFilter = [1,4,6].filter(function(index) {
return index>4;
});
console.log(arrFilter)//[6]reduce:
reduce
可以将数组中的元素通过回调函数最终转换为一个值。实现累加1
2
3
4
5
6
7
8
9
10
//reduce用于累加求和,第三个参数表示acc的初始值
var arrSum = [3,4,6,7].reduce((acc,cur)=>acc+=cur,0)
console.log(arrSum)//20
var arrSum1 = [3,6,7,9].reduce((acc,cur)=>{
acc.push(cur);
return acc//注意这里一定要返回acc赋值给acc
},[])
console.log(arrSum1)//[3,6,7,9]是数组的拷贝
9 JS异步编程及常考面试题
9.1 并发和并行的区别
- 并发:是宏观概念,当有两个任务,在一段时间内通过任务间的切换完成了两个任务,实际是同一个时刻只有任务在执行
- 并行:微观概念,两个任务在同一个时刻同时执行的情况是并行情况
9.2异步编程的集中方式举例分析
回调函数
通过逻辑嵌套的方式实现异步编程,会出现回调地狱的问题
回调函数的根本问题:
- 逻辑嵌套,耦合性太高,牵一发而动全身
- 不利于处理错误,
1 | ajax(url, () => { |
generator
理解:
- generator是一个遍历器生成函数,可以交出控制权,实现异步编程
- 返回的是一个迭代器
- 通过yield控制状态
- 通过next控制下一步的执行
可以解决回调函数中的回调地狱问题
1 | function *fetch() { |
promise
Promise
实现了链式调用,也就是说每次调用then
之后返回的都是一个Promise
,并且是一个全新的Promise
,原因也是因为状态不可变。如果你在then
中 使用了return
,那么return
的值会被Promise.resolve()
包装Promise
也很好地解决了回调地狱的问题
1 | ajax(url) |
缺点:无法取消promise,错误需要通过回调函数捕获
Promise 构造函数执行和 then 函数执行有什么区别?前者同步执行,后者异步执行
async/await
- 特点
- 函数加上
async
返回的就是一个promise对象,将函数的返回值通过promise.resolve()进行了包装,和then中处理返回值一样,通过await直接接收到返回值 - await必须和async同时使用
- await 内部实现了
generator
,其实await
就是generator
加上Promise
的语法糖,且内部实现了自动执行generator
- await是解决回到地狱问题的终极方案
- 函数加上
- 优势:相对于promise来说,异步函数的优势在于then的调用链,代码简洁,也能很优雅的解决回调地狱的问题
- 问题:滥用
await
可能会导致性能问题,因为await
会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性
常用的定时器函数(setTimeout、setInterval、requestAnimationFrame)
setTimeOut: 并不是延时多久就多久执行,js是单线程执行的,前面的代码如果影响性能,后面的代码并不会定时执行
setInterval:和前者类似,是每隔一段时间执行一次,也不能保证准时执行
- 不建议使用:(1)不能保证在于其时间执行任务,(2)存在累计执行的问题
requestAnimationFrame:自带函数的节流功能,可以保证在16.6毫秒内只执行一次(不掉帧的情况),该函数的延时效果是精确的(多数显示屏的默认频率是60HZ,即1秒刷新60次,1/60*1000 = 16.6ms)调用还函数的是windows所以显示屏的默认刷新频率是有关系的
- 通过requestAnimationFrame实现循环定时器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//通过requestAnimationFrame实现循环定时器
function intervalTimer(callback, interval) {
let timer;
const now = Date.now;//这里只是声明,并没有调用
let startTime = now();//调用now()方法
let endTime = startTime;
//浏览器重绘之前调用的执行的回调函数
const loop = () => {
//设置定时器,浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
timer = window.requestAnimationFrame(loop);
endTime = now();//最终时间就是当前时间
if (endTime - startTime >= interval) {
//达到指定时间执行函数,并肩记录时间的值初始化
endTime = startTime = now()
// 执行回调函数
callback(timer)
}
}
timer = window.requestAnimationFrame(loop);
return timer
}
//调用
let a = 0;
intervalTimer((timer) => {
console.log(1);
a++
if (a == 3) {
//取消
window.cancelAnimationFrame(timer)
}
}, 1000)
9.3面试题:通过js封装一个定时器
1 | /* |
js进阶知识点
1 bind&call&&apply
三者的区别
- 相同点:三者都是用来改变this的指向
- 不同点:
- call和apply作用相同,除了接收第一个参数相同之外
- call还可以接收一个参数列表
- apply接收一个参数数组
- bind和其他方法作用一样,bind返回的是一个函数,bind可以实现函数柯里化
- call和apply作用相同,除了接收第一个参数相同之外
手动实现
apply实现
1 | // 2.模拟实现apply(可接收数组列列表) |
call实现
1 | // 1.模拟实现call(可接受参数列表) |
bind实现
bind
返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new
的方式,我们先来说直接调用的方式- 对于直接调用来说,这里选择了
apply
的方式实现,但是对于参数需要注意以下情况:因为bind
可以实现类似这样的代码f.bind(obj, 1)(2)
,所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
- 最后来说通过
new
的方式,在之前的章节中我们学习过如何判断this
,对于new
的情况来说,不会被任何方式改变this
,所以对于这种情况我们需要忽略传入的this
1 | Function.prototype.myBind = function (context) { |
2 new
2.1 new的原理
- 在调用new的过程中共发生了四件事
- 创建一个新的对象
- 链接到;原型
- 绑定了this
- 返回新对象
- 手动实现new
1 | function create() { |
2.2 通过new创建的对象和通过字面量创建的对象有什么区别
- 两者创建方式实质都是通过new来创建的
1 | function fun(){} |
- 从性能还是可读性方面来说,推荐使用字面量的创建方式
- 通过new创建对象需要通过作用域链一层一层找到Object,
- 使用字面量的方式,可以直接找到Object,没有查找的过程
3 instanceof原理和手动实现
instanceof可以准确的判断对象的类型,原理:通过查找原型链的方式判断是不是含有该类的原型
手动实现;
- 首先获取类型的原型
- 然后获得对象的原型
- 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
1 | function myInstanceof(instance, className) { |
4 面试题:为什么 0.1 + 0.2 != 0.3
原因:
- js中采用的是浮点数标准(IEEE754双精度版本),很多十进制小数用二进制表示都是无限循环的,循环的数字被裁剪了,就会出现精度丢失的问题,造成0.1在二进制中并不是准确的值
解决
通过原生的方式来解决
1
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3
5 垃圾回收机制(GC[Garbage collection in V8])
5.1 面试题:V8引擎中的垃圾回收机制(自动进行了内存的分配和管理)
V8实现了准确的GC,GC采用的是 分代式垃圾回收机制,根据对象的存活时间将内存的垃圾进行不同的分代,然后对不同的分代采用不同的垃圾回收算法
V8将内存分为新生代和老生代两部分
新生代算法
- 新生代中的对象存活时间一般较短,采用的是Scavenge GC算法
- 新生代中内存分为from空间和to空间,两个空间中必定有一个空间是使用的,另一个是空闲的,新创建的对象会先被加入到from空间,当from空间满时,调用新生代GC算法,,算法检查from空间中存活的对象并复制到to空间,有失活的对象就会销毁
- 当复制完成后from空间和to空间就会交换,GC结束
老生代算法
老生代中的对象存活时间较长且数量较多,采用的是两个算法: 标记清除算法和标记压缩算法
对象来源:
- 老生代中的对象是经历过一次scavenge算法的存活对象,会从新生代空间移动到老生代空间
- 当to空间的大小超过25%,为了不影响内存空间的分配,会将对象从新生代空间转移到老生代空间中
标记清除算法的启动的情况
- 某一个空间没有分块的时候
- 空间对象超过限制
- 空间不能保证新生代内存中的对象移动到老生代空间中
在这个阶段中,会遍历堆中所有的对象,然后标记活的对象,在标记完成后,销毁所有没有被标记的对象
经过标记清除算法清除对象后会造成堆内存出现碎片,碎片超过一定的限制就会启动标记压缩算法:压缩过程中会将存活的对象移动到一端,直到所有对象都移动完成然后清理掉不需要的内存。
5.2 V8引擎内存有限制的原因(1.5G,64位):
- js的单线程机制
- 垃圾回收机制
- 首先JS是单线程运行的,这意味着一旦进入到垃圾回收,那么其它的各种运行逻辑都要暂停; 另一方面垃圾回收其实是非常耗时间的操作,如果内存空间没有限制,JS代码执行会一直没有响应,造成应用卡顿,导致应用性能和响应能力直线下降
6 js面试思考题
6.1 JS 分为哪两大类型?都有什么各自的特点?你该如何判断正确的类型?
- 对于原始类型来说,你可以指出
null
和number
存在的一些问题。对于对象类型来说,你可以从垃圾回收的角度去切入,也可以说一下对象类型存在深浅拷贝的问题。 - 对于判断类型来说,你可以去对比一下
typeof
和instanceof
之间的区别,也可以指出instanceof
判断类型也不是完全准确的。
6.2 原型的理解
- 起码说出原型小节中的总结内容,然后还可以指出一些小点,比如并不是所有函数都有
prototype
属性,然后引申出原型链的概念,提出如何使用原型实现继承,继而可以引申出 ES6 中的class
实现继承。
6.3 bind、call 和 apply 各自有什么区别?
- 首先肯定是说出三者的不同,如果自己实现过其中的函数,可以尝试说出自己的思路。然后可以聊一聊
this
的内容,有几种规则判断this
到底是什么,this
规则会涉及到new
,那么最后可以说下自己对于new
的理解。
6.4 ES6中使用过什么
- class->原型->继承
- promise->异步编程
- proxy->响应式原理
- let->变量提升->块级作用域->三者区别
6.5 JS 是如何运行的?
- js单线程机制,内存机制
- 进程和线程的区别
- 执行栈
- 浏览器中的event loop&node中的even loop对比
- 宏任务和微任务
- 垃圾回收机制
浏览器基础知识及常考知识点
1.事件机制
面试题: 事件的触发过程是怎样的?了解事件代理吗
1.1 事件触发的三个阶段:
document向事件触发处传播,遇到注册的捕获事件会触发(事件捕获阶段)
传播到事件触发处时触发注册的事件
从事件触发阶段往document处传播,遇到注册的冒泡事件会触发(事件冒泡阶段)
事件触发一般会按照上面的顺序执行,如果在同一个目标节点同时注册冒泡事件和捕获事件,事件触发会按照注册的顺序执行
1
2
3
4
5
6
7node.addEventlistener('click',(event)=>{
console.log('冒泡')
},false)//冒泡事件第三个参数数false
node.addEventListener('click',(event)=>{
console.log(;捕获)
},true)//捕获事件第三个参数是false
执行顺序是先冒泡后捕获
1.2 事件注册
注册事件使用
addEventListener
,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值useCapture
参数来说,该参数默认值为false
,useCapture
决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性capture
:布尔值,和useCapture
作用一样once
:布尔值,值为true
表示该回调只会调用一次,调用后会移除监听passive
:布尔值,表示永远不会调用preventDefault
阻止事件执行:希望事件只触发在目标上,
- 使用
stopPropagation
来阻止事件的进一步传播,通常我们认为stopPropagation
是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。(能阻止事件冒泡和事件捕获) stopImmediatePropagation
同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。(除了阻止事件冒泡和事件捕获之外,还能阻止事件目标处其他事件的执行)
- 使用
1.3 事件代理
- 如果一个父节点的子节点是动态生成的,子节点需要注册事件就需要将事件注册代理到父节点上
1 | <script> |
- 事件代理较直接给目标注册事件好处
- 节省内存
- 不需要给子节点注销事件
2 跨域
面试题:什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
2.1 跨域
- 跨域出现主要是对浏览器的限制,出于浏览器的安全考虑,有同源策略。即端口,域名,协议不同的就是跨域,ajax请求就会失败
- 同源策略主要防止的是CSRF攻击,CSRF(跨站请求伪造)主要是利用用户的登录状态发起恶意的请求,该种策略并不能完全防止攻击,因为在请求跨域的情况下,请求确实是发送出去的,但是响应会被浏览器拦截
2.2 跨域解决
jsonp
- JSONP方式:利用
script
标签没有跨域限制的漏洞,通过script标签指向一个目标地址并提供一个回调函数来接收数据在通信的时候
1 | <script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script> |
优缺点:
- 简单且兼容性好
- 只能应用于get请求
jsonp封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function handleParams(data) {
let params = '';
for (let attr in data) {
data[key] && (params += '&' + attr + '=' + data[attr]);
}
return params
}
export default function (options) {
// 1.获取jsonp的url
const url = options.url;
// 2.创建script标签
const script = document.createElement('script');
// 3.创建callback函数名(不重复)
const callback = 'jsonp' + Math.random().toString().replace('.', '');
// 4.监听windows上的jsonp调用
window[callback] = options.success;
// 5.拼接参数,发送请求
let params = handleParams(options.data);
script.src = url + '?callback=' + callback + params
// 6.标签追加到页面
document.body.appendChild(script);
// script添加onload()事件,执行结束,移出script标签
script.onload = function () {
document.body.removeChild(script)
}
}
CORS
CORS:跨域资源共享
需要浏览器和服务器端同时支持,IE 8 和 9 需要通过
XDomainRequest
来实现。浏览器会自动进行CORS通信,实现跨域资源共享的关键是后端,需要开启跨域访问的权限,服务器端需要设置
Access-Control-Allow-origin
,表明哪些域名可以访问,设置通配符的话表示所有的网站可以本方案和前端没有什么关系,通过这种方式解决跨域问题发送请求分为 简单请求和复杂请求
简单请求(ajax为例,发送简单请求的情况)
- 方法之一:get,head,post
- content-Type下列之一:text/plain,multipart/form-data,application/x-www-form-urlencoded
请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。复杂请求:(简单请求之外)
首先会发起一个预检请求,该请求是
option
方法的,通过该请求来知道服务端是否允许跨域请求。预检请求来说,如果你使用过 Node 来设置 CORS 的话,该请求会验证你的
Authorization
字段.预检请求也会进入回调中,也会触发next
方法,因为预检请求并不包含Authorization
字段,所以服务端会报错。解决方案:
在回调中过滤掉options方法
1
2
3res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
document.domain
- 该方式只适用于 二级域名相同的情况,
a.test.com
和b.test.com
适用于该方式 - 只需要给页面添加
document.domain = 'test.com'
表示二级域名都相同就可以实现跨域
postMessage
- 该方式主要用于获取嵌入页面中的第三方页面的数据,一个页面发送数据,另一个页面判断来源并接受数据
1 | // 发送消息端 |
服务器端解决
- 同源策略是浏览器给ajax限制的,服务器端没有限制,可以让服务器端去获取信息在响应给自己的客户端(引入express框架中的request第三方模块)
nginx代理服务器
- 在代理服务器中拦截请求转发给实际的请求网站
websocket通信
- :是一种双向通信协议,在建立连接之后客户端和服务器端都能主动向对方发送或者接收数据而不受同源策略的限制
3. 存储(浏览器)
涉及面试题:有几种方式可以实现存储功能,分别有什么优缺点?什么是 Service Worker?
3.1cookie,sessionStorage,localStorage,indexDB
cookie不建议用于存储,没有大量存储要求使用localStorage和sessionStorage,对于不怎么改变的数据使用localStorage存储 ,否则使用sessionStorage
对于cookie来说涉及到安全问题
3.2 session和cookie
session
: 是一个抽象概念,开发者为了实现中断和继续等操作,将user agent
和server
之间一对一的交互,抽象为“会话”,进而衍生出“会话状态”,也就是session
的概念cookie
:它是一个实际存在的东西,http
协议中定义在header
中的字段,可以认为是session
的一种后端无状态实现
现在我们常说的
session
,是为了绕开cookie
的各种限制,通常借助cookie
本身和后端存储实现的,一种更高级的会话状态实现
1 | session` 的常见实现要借助`cookie`来发送 `sessionID |
3.3 Service Worker
- Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
- Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到
install
事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。 - 开发者工具中的
Application
看到 Service Worker 已经启动,在 Cache 中也可以发现我们所需的文件已被缓存
4 浏览器的缓存(性能优化)
4.1 缓存优势
- 浏览器缓存是性能优化中一种简单高效的方式,可以显著减少网络传输所带来的的损耗(因为在网络请求中一般分为三个阶段:发送请求,服务器端处理,浏览器响应,缓存可以在第一阶段和第三阶段都会进行优化,有缓存的情况下可以不发送请求,或者发送请求但是发现数据没变不用返回数据,这样就减少了数据的响应)
4.2 缓存位置
- 从缓存位置上来说,是具有 优先级的,会依次查找缓存都没有找到,才会去发送请求
- 划分:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
- 网络请求
Service Worker
- 优点:它的缓存机制和浏览器内建的其他缓存机制不同,它允许我们 自由控制缓存哪些文件,如何匹配缓存,如何读取缓存,并且缓存是 持续性
- 如果在Service Worker中没有命中缓存的话,他会通过fetch()函数去获取数据.也就是说,在SW中没有命中缓存就会通过缓存查找优先级去查找数据,但是不管我们是从MemoryCache还是从网络请求中回去的数据,都会显示是从Service Worker中获取的数据
Memory Cache
- 是内存中的缓存,读取内存中的数据肯定是要比磁盘中的数据快,
- 缺点:内存中的缓存虽然 读取高效,但是持续性较短,随着进程的释放而释放,一旦页面关闭,内存中的缓存也就释放了
- 注意:虽然内存中的缓存高效,但是不能将所有的额文件都放在内存中,毕竟内存空间有限,是比硬盘小很多
Disk Cache
- 是硬盘中的缓存,读取速度慢,什么都能存储,胜在 容量和存储时效上
- 所有浏览器的缓存机制中,DiskCache是覆盖面最大的,他可以根据http header中字段判断哪些资源需要缓存,哪些资源不缓存直接使用,哪些资源已经过期需要重新缓存.并且在跨站的情况下,相同地址的资源一旦被硬盘缓存下来,就不会在去请求数据
Push Cache
- 是http2中的内容,当以上的三种缓存都没有命中的情况下才会去使用. 缓存时间较短,随着会话的释放而释放
网络请求
- 在以上情况都没有命中的情况下只能发送网络请求,网络请求的每个接口都是选好缓存策略的
4.3 缓存策略
浏览器的缓存策略分为: 强缓存和协商缓存,所有的缓存都是在http header中进行设置的
强缓存
强缓存可以设置两种http header来实现:Expires和Cache-Control.使用强缓存期间不必请求,返回的状态码为200
Expires是http1中内容,表示在该时间之后过期, 受限于本地时间,本地时间改变可能造成缓存失效
1
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Cache-Control是http1.1内容,表示缓存在设置的一段时间之后过期, 优先级高于Expires
1
Cache-control: max-age=30
协商缓存
在强缓存失效的情况下,就会使用协商缓存,发送请求验证资源是否有更新
协商缓存通过设置http header两种方式:Last-Modified和ETag
当浏览器发起请求验证资源时,如果资源没有发生改变,就会返回304状态码,并修改缓存有效的时间
Last-Modified 和 If-Modified-Since
Last-Modified
表示本地文件最后修改日期,If-Modified-Since
会将Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。
ETag 和 If-None-Match
ETag
类似于文件指纹,If-None-Match
会将当前ETag
发送给服务器,询问该资源ETag
是否变动,有变动的话就将新的资源发送回来。并且ETag
优先级比Last-Modified
高。
4.4 应用场景应用缓存策略
频繁变动的资源:首先需要使用 Cache-Control: no-cache
使浏览器每次都请求服务器,然后配合 ETag
或者 Last-Modified
来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
代码文件:这里特指html文件之外的文件,html文件不存要缓存或者缓存的时间较短
一般通过工具包来打包代码,通过对文件名进行哈希处理,只有当代码修改后才会产生新的文件名,可以给文件设置缓存的期限cache-control:为一年,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。
5 浏览器渲染
5.1 浏览器渲染原理
浏览器接收到 HTML 文件并转换为 DOM 树
将 CSS 文件转换为 CSSOM 树(这一过程其实是很消耗资源浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。)
生成渲染树:我们生成
DOM
树和CSSOM
树以后,就需要将这两棵树组合为渲染树,当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用GPU
绘制,合成图层,显示在屏幕上。
5.2 操作dom慢的原因
- 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。
5.3 插入几万个DOM如何实现界面不卡
- 本质:分批次部分渲染dom
- 实现方式:
- 通过window属性
requestAnimationFrame
(要求浏览器在下次重绘之前调用指定的回调函数更新动画)去循环的插入dom - 通过虚拟滚动(virtual Scroll)的方式:这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。
- 通过window属性
5.4 阻塞界面渲染的情况
- 渲染的前提是生成渲染树,html和css会阻塞,(降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。)
- 解析
script
标签,会暂停dom,完成之后再从暂停位置继续构建(首屏渲染块,需要将script标签的加载放在body
或者通过 延迟(defer)和异步(async))- defer:表示js文件可以并行下载,会在页面加载完成之后执行
- async:没有任何依赖的js文件,加上async不会阻塞渲染
5.5 重绘和回流(影响性能)
概念
- 重绘和回流会在设置节点样式的时候频繁出现
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color
就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
- 关系:回流一定引起重绘,重绘不一定会引起回流,回流所需的成本比重绘高
发生情况
- 改变
window
的大小 - 改变文字的大小
- 字体改变
- 增加或者删除节点
- 盒模型
- 浮动或定位
重绘和回流和event loop有关(一帧可能会做的)
event loop每次执行完微任务都需要判断是否进行页面的渲染,保证js操作和dom的同步性,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。
然后判断是不是有resize和scroll操作,如果有的话触发事件,至少也是16.6ms触发一次,并且知道节流功能
判断是否触发了media query
更新动画并发送事件
判断是否有全屏操作事件
执行requestAnimationFrame回调
执行
intersectionObserver
会回调,该方法用于判断元素是否可见,可以用于懒加载,但是兼容性不好更新界面
5.6 减少重绘和回流方案
- 用
transform
代替top
- 用
visibility
代替display:none
- css选择符从右往左匹配查找,避免节点层级过高
- 不使用table布局
- 频繁重绘和回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,
video标签
可以自动转化为图层will-change
video
,iframe
5.7 思考题(关键渲染路径)
面试题:在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。
当发生
DOMContentLoaded
事件后,就会生成渲染树,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了。从文件大小考虑
从
script
标签使用上来考虑从 CSS、HTML 的代码书写上来考虑
从需要下载的内容是否需要在首屏使用上来考虑
6 浏览器内核的理解,以及主流浏览器的内核
6.1 浏览器内核的理解
- 内核主要分为两部分:渲染引擎和js引擎
- 渲染引擎负责取得网页的内容和计算网页的显示方式,然后输出到显示器或者打印机.浏览器内核的不同对网页的解析方式会有所不同
- .js引擎主要是解析和执行javascript代码实现页面的动态效果
6.3 主流浏览器的内核
- IE: trident内核
- firefox:gecko内核
- safari:webkit内核
- opera:起初presto->谷歌内核blink
- 谷歌:blink
7 浏览器性能优化
- 图片优化
- 减少像素点
- 减少每个像素点能够显示的颜色
- 图片加载优化:base64格式,雪碧图,正确的图片格式,webp(更好的数据图像压缩算法),小图使用png/svg,图片使用jpeg
- DNS预解析
- 节流防抖
- 预加载
- 预渲染
- 懒执行
- 懒加载
- CDN:
- 原理:CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。
- 使用:可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。
- 重绘和回流
7.1 防抖和节流
防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
1
2
3
4
5
6
7
8
9
10function debounce(fun,waittime){
var timer = 0;
return function (...args){
if(timer) clearTimeOut(timer);
timer = setTimeOut(()=>{
//this的真是指向并不是debounce的调用者,而是返回闭包的调用者
fun.apply(this,args)
},waittime)
}
}节流(throttle):每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者
resize
事件1
2
3
4
5
6
7
8
9
10function throttle(fun,waitTime){
var lastTime = Date.now()
return function(...args){
//当前时间
if(Date.now()-lastTime>waitTime){
lastTime = Date.now()
fun.apply(this,args)
}
}
}
8 从输入URL到浏览器页面展示的步骤
- 首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,(DNS 解析)
- 然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接, (TCP 连接)
- 随后我们向服务端抛出我们的 HTTP 请求,(HTTP 请求抛出)
- 服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,(服务端处理请求,HTTP 响应返回)
- 拿到响应数据的浏览器就可以开始走一个渲染的流程。 (浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户)
- 渲染完毕,页面便呈现给了用户,并时刻等待响应用户的操作
9 安全防范(XSS,CSRF)
9.1 XSS
- XSS:
XSS
通过修改HTML
节点或者执行JS
代码来攻击网站。- 分为持久性和非持久性
- 持久性:就是恶意 的代码被服务器写入数据库 中
- 非持久性: 修改url参数的方式加入攻击代码,诱导用户访问链接从而进行攻击
- 预防:
- 最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义
- CSP(Content-Security-Policy)方式:本质就是设置白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。
- 分为持久性和非持久性
9.2 CSRF
- CSRF:跨站请求伪造(英语:
Cross-site request forgery
),也被称为one-click attack
或者session riding
,通常缩写为CSRF
或者XSRF
, 是一种挟制用户在当前已登录的Web
应用程序上执行非本意的操作的攻击方法,CSRF
就是利用用户的登录态发起恶意请求- 预防遵循以下几个规则:
Get
请求不对数据进行修改- 不让第三方网站访问到用户
Cookie
- 阻止第三方网站请求接口
- 请求时附带验证信息,比如验证码或者
token
- 具体
- same-sit属性:设置cookie属性中的same-sit表示不允许携带cookie信息在跨域请求 的时候
- 验证Referer:通过验证 Referer 来判断该请求是否为第三方网站发起的。
- token:服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。
- 密码安全
- 加盐:通常需要对密码加盐,然后进行几次不同加密算法的加密
面试题:cookie 和 token 都存放在 header 中,为什么不会劫持 token?
- token是为了防止SCRF攻击,不是xss攻击,CSRF攻击的原因是浏览器会自动带上cookie,而浏览器不会自动带上token
面试题2:介绍如何实现token加密
- 需要一个secret(随机数)
- 后端利用secret和加密算法(如:HMAC-SHA256)对payload(如账号密码)生成一个字符串(token),返回前端
- 前端每次request在header中带上token
- 后端用同样的算法解密
9.3 点击劫持
点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过
iframe
嵌套的方式嵌入自己的网页中,并将iframe
设置为透明,在页面中透出一个按钮诱导用户点击。预防:
- X-FROM-OPTION:
X-FRAME-OPTIONS
是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用iframe
嵌套的点击劫持攻击。 - JS防御:当通过
iframe
的方式加载页面时,攻击者的网页直接不显示所有内容了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<head>
<style id="click-jack">
html {
display: none !important;
}
</style>
</head>
<body>
<script>
if (self == top) {
var style = document.getElementById('click-jack')
document.body.removeChild(style)
} else {
top.location = self.location
}
</script>
</body>- X-FROM-OPTION:
9.4 中间人攻击
- 中间人攻击就是同时和服务器端和客户端建立连接,并且让双方认为该连接时安全的,此时攻击人已经控制了整个通信过程,不仅能获取双方的通信信息,还能修改通信信息
- 场景:不建议在公共的wifi中使用上网很容易发生攻击人攻击
- 预防: 增加一个安全通道来传输信息,https就可以防止中间人攻击,也不是绝对安全,攻击人会将https为http进行攻击
网络基础知识
1. http和https的区别
1.1 http请求
- 组成:请求行,首部和实体
状态码:
2XX 成功
- 200 OK,表示从客户端发来的请求在服务器端被正确处理
- 204 No content,表示请求成功,但响应报文不含实体的主体部分
- 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容
- 206 Partial Content,进行范围请求
3XX 重定向
- 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
- 302 found,临时性重定向,表示资源临时被分配了新的 URL
- 303 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,表示服务器端在执行请求时发生了错误
- 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能
- 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
1.2 GET和POST请求的区别
- Get 请求能缓存,Post 不能
- Post 相对 Get 安全一点点,因为Get 请求都包含在 URL 里(当然你想写到
body
里也是可以的),且会被浏览器保存历史纪录。Post 不会,但是在抓包的情况下都是一样的。 - URL有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的
- Post 支持更多的编码类型且不对数据类型限制
1.3 TLS(传输层安全协议)
- HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议和ssl协议进行了加密(在传输层和应用层传输之间)
- TLS 协议位于传输层之上,应用层之下。首次进行 TLS 协议传输需要两个 RTT( Round-Trip Time):往返时延) ,接下来可以通过 Session Resumption 减少到一个 RTT。
- 使用到两种加密技术
- 对称加密:加密和解密都采用同一种算法
- 非对称加密:有公钥和私钥之分,公钥公开用于加密,私钥用于解密(注意和数字签名正好是反的,数字签名是私钥加密公钥解密)
1.4 http2的多路复用
- HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
- 多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
HTTP2中- 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
- 单个连接上可以并行交错的请求和响应,之间互不干扰
- 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。
面试题:http1为什么不能实现多路复用
- HTTP/1.1 不是二进制传输,而是通过文本进行传输。由于没有流的概念,在使用并行传输(多路复用)传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求,所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。
1.4 https的握手过程
- 客户端使用https的url访问web服务器,要求与服务器建立ssl连接
- web服务器收到客户端请求后, 会将网站的证书(包含公钥)传送一份给客户端
- 客户端收到网站证书后会检查证书的颁发机构以及过期时间, 如果没有问题就随机产生一个秘钥
- 客户端利用公钥将会话秘钥加密, 并传送给服务端, 服务端利用自己的私钥解密出会话秘钥
- 之后服务器与客户端使用秘钥加密传输
1.5 https握手过程中如何验证证书的合法性
- (1)首先浏览器读取证书中的证书所有者、有效期等信息进行校验,校验证书的网站域名是否与证书颁发的域名一致,校验证书是否在有效期内 (2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发 (3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。 (4)如果找到,那么浏览器就会从操作系统中取出颁发者CA 的公钥(多数浏览器开发商发布 版本时,会事先在内部植入常用认证机关的公开密钥),然后对服务器发来的证书里面的签名进行解密 (5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比 (6)对比结果一致,则证明服务器发来的证书合法,没有被冒充
1.6 HTTPS中间人攻击的过程
- 过程
- 服务器向客户端发送公钥。
- 攻击者截获公钥,保留在自己手上。
- 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
- 客户端收到伪造的公钥后,生成加密hash值发给服务器。
- 攻击者获得加密hash值,用自己的私钥解密获得真秘钥。
- 同时生成假的加密hash值,发给服务器。
- 服务器用私钥解密获得假秘钥。
- 服务器用加秘钥加密传输信息
- 解决:务端在发送浏览器的公钥中加入CA证书(CA中加入非对称的公钥),浏览器可以验证CA证书的有效性
1.7 http1,http.1和http2的区别
http1:无状态,无连接
- 无状态:不记录状态鑫信息,通过cookie进行身份认证和状态记录
- 无连接:出现缺陷:无法复用连接,队头阻塞
http1.1:
- 长连接:keep-alive
- 管道化:请求可以不按照顺序发送,响应还是按照顺序响应的
- 缓存处理:cache-control
- 断点传输:在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率
http2:
- 多路复用
- 二进制分帧
- 头部压缩
- 服务器传送:主动推送其他资源
1.8 http301和302状态码
对seo优化的影响:
- 301表示永久性转移,更利于seo优化,301重定向是网页更改地址后对搜索引擎友好的最好方法,只要不是暂时搬移的情况,都建议使用301来做转址。 如果我们把一个地址采用301跳转方式跳转的话,搜索引擎会把老地址的PageRank等信息带到新地址,同时在搜索引擎索引库中彻底废弃掉原先的老地址。旧网址的排名等完全清零
二者的应用场景
- 301应用场景: 域名到期不想继续用这个,换了地址
- 302应用场景: 做活动时候,从首页跳到活动页面,
1.9 接口如何进行防刷
- 总调用次数受限制。这个一般是在后端做限制,单位时间内最多可调用次数。
- 同一客户端次数限制。这个前端的一般使用是给接口调用加锁,在返回结果或者一定时间之后解锁。
2 tcp和udp的区别
UDP:面向无连接的,以数据报文的方式传送数据,存在数据丢失的的情况(不可靠性),没有流量控制的算法,(高效)不用建立连接 UDP 的头部开销小,只有八字节;(传输方式)支持单播,多播,广播
应用:直播和王者荣誉,应用在比较实时的场景
小结:
- UDP 相比 TCP 简单的多,不需要建立连接,不需要验证数据报文,不需要流量控制,只会把想发的数据报文一股脑的丢给对端
- 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为
3 tcp
3.1 tcp的三次握手
- 客户端发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,服务器由SYN=1知道客户端要求建立联机(SYN_SEND)
- 服务器收到请求后要确认联机信息,向A发送ack number=(客户端的seq+1),syn=1,ack=1,随机产生seq=7654321的包(SYN_RECEIVED)
- 客户端收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,客户端会再发送ack number=(服务器的seq+1),ack=1,服务器收到后确认seq值与ack=1则连接建立成功。(ESTABLISED)
3.2为什么需要三次握手
- 防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。
3.3 tcp四次回收
lient向Server发送FIN包,表示Client主动要关闭连接,然后进入FIN_WAIT_1状态,等待Server返回ACK包。此后Client不能再向Server发送数据,但能读取数据。
Server收到FIN包后向Client发送ACK包,然后进入CLOSE_WAIT状态,此后Server不能再读取数据,但可以继续向Client发送数据。
Client收到Server返回的ACK包后进入FIN_WAIT_2状态,等待Server发送FIN包。
Server完成数据的发送后,将FIN包发送给Client,然后进入LAST_ACK状态,等待Client返回ACK包,此后Server既不能读取数据,也不能发送数据。
Client收到FIN包后向Server发送ACK包,然后进入TIME_WAIT状态,接着等待足够长的时间(2MSL)以确保Server接收到ACK包,最后回到CLOSED状态,释放网络资源。
Server收到Client返回的ACK包后便回到CLOSED状态,释放网络资源。
为什么 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态?
- 了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进入 CLOSED 状态,如果确认应答因为网络问题一直没有到达,那么会造成 B 不能正常关闭。
3.4 为什么需要四次
- TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。
3.5 ARQ协议
- ARQ (Automatic Repeat-reQuest,ARQ)协议也就是自动重传协议。通过确认和超时机制保证了数据的正确送达,ARQ 协议包含停止等待 ARQ 和连续 ARQ 两种协议。
3.6 小结
- 建立连接需要三次握手,断开连接需要四次握手
- 滑动窗口解决了数据的丢包、顺序不对和流量控制问题(作用服务器端)
- 拥塞处理实现了对流量的控制,保证在全天候环境下最优的传递数据(作用于网络)
- 包括四种算法
- 慢开始
- 拥塞避免
- 快速重传
- 快速恢复
- 包括四种算法
webpack&Vue
1 webpack性能优化
面试题:有哪些方式可以减少 Webpack 的打包时间?有哪些方式可以让 Webpack 打出来的包更小?
1.1 有哪些方式可以减少WebPack的打包时间
- 优化loader的文件搜索范围
- 将babel编译过的文件缓存起来
- Happypack将Loader的同步执行转化为并行
- DllPlugin可以将特定的类库提前打包引入
- 开启代码压缩:uglifyJS
1.2 减少Webpack打包后的体积
- 按需加载:我们就可以使用按需加载,将每个路由页面单独打包为一个文件(路由懒加载)
- Scope Hoisting:分析模块之间的依赖关系,尽可能的将打包出来的文件合并到一个函数中去
- Tree Shaking:实现删除项目中未被引入的代码
1.3 打包工具的核心原理:
- 找出入口文件所有的依赖关系
- 通过构建CommonJs代码来获取
exports
导出的内容
2 MVVM
2.1MVVM理解
组成:view(视图),viewModel(连接view和model的连接层,主要是组件),model(数据模型)
在mvvm架构下,view和model之见时没有直接联系的,是通过viewmodel进行交互,model和viewmodel之间的交互的双向的,因此view的数据变换会同步到model,model的数据变化也会反应在view中
viewmodel通过双向数据绑定将view和model联系起来,view和model之间的同步工作完全是自动化的不需要人为的干涉.开发者只需要关注业务和逻辑处理即可,不需要操作dom,不要关注数据同步问题,复杂的状态维护完全有mvvm来统一管理
UI 也是通过 数据驱动来完成,数据一旦更新就会刷新相应的的UI,UI改变也会同步对应的数据
mvvm中最核心的就是数据的双向绑定(比如vue中的数据劫持)
Vue
内部使用了Object.defineProperty()
来实现双向绑定,通过这个函数可以监听到set
和get
的事件。- proxy也可以实现数据的双向绑定
通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓。
2.2 虚拟dom可以提高性能
虚拟
dom
相当于在js
和真实dom
中间加了一个缓存,利用dom diff
算法避免了没有必要的dom
操作,从而提高性能具体实现
- 用
JavaScript
对象结构表示 DOM 树的结构;然后用这个树构建一个真正的DOM
树,插到文档当中 - 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把2所记录的差异应用到步骤1所构建的真正的
DOM
树上,进行局部视图更新
- 用
Virtual Dom优势:
- 将 Virtual DOM 作为一个兼容层,让我们还能对接非 Web 端的系统,实现跨端开发。
- 同样的,通过 Virtual DOM 我们可以渲染到其他的平台,比如实现 SSR、同构渲染等等。
- 实现组件的高度抽象化
3 前端路由的原理
- 原理:监听url的变化,然后匹配路由规则,显示相应的页面并且无须刷新页面,
- 目前前端使用的路由方式有两种
- hash模式:
www.test.com/#/
当#
后面的哈希值发生变化时,可以通过hashChange
事件来监听url的变化从而进行页面的跳转.无论哈希值如何变化,服务器端接收到的url请求永远是不变的``www.test.com`- hash模式兼容性较好并且简单
- history模式: 是html5新提出的功能,主要使用
history.pushState
和history.replaceState
改变url- 通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。
- hash模式:
- 两种模式的对比
- Hash 模式只可以更改
#
后面的内容,History 模式可以通过 API 设置任意的同源 URL - History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串
- Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置
index.html
页面用于匹配不到静态资源的时候
- Hash 模式只可以更改
4 Vue基础知识
4.1 vue生命周期(钩子函数)
- vue生命周期:vue实例从创建到销毁的过程,从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
- 总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
- 创建前/后: 在
beforeCreate
阶段,vue
实例的挂载元素el
和数据对象data
都为undefined
,还未初始化。在created
阶段,vue
实例的数据对象data
有了,el还没有 - 载入前/后:在
beforeMount
阶段,vue
实例的$el
和data
都初始化了,但还是挂载之前为虚拟的dom
节点,data.message
还未替换。在mounted
阶段,vue
实例挂载完成,data.message
成功渲染。 - 更新前/后:当
data
变化时,会触发beforeUpdate
和updated
方法 - 销毁前/后:在执行
destroy
方法后,对data
的改变不会再触发周期函数,说明此时vue
实例已经解除了事件监听以及和dom
的绑定,但是dom
结构依然存在 keep-alive
独有的生命周期,分别为activated
和deactivated
。用keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated
钩子函数,命中缓存渲染后会执行actived
钩子函数。
- 创建前/后: 在
4.2 组件通信
- 组件通信划分为以下一种
- 父子组件通信
- 兄弟组件通信
- 跨多层级组件通信
- 任意组件
父子组件的通信
父组件通过
props
传递数据给子组件,子组件通过emit
发送事件传递数据给父组件,这两种方式是最常用的父子通信实现办法。(易于监控数据的流动)- 典型的单向数据流父组件通过
props
传递数据,子组件不能直接修改props
, 而是必须通过发送事件的方式告知父组件修改数据。
- 典型的单向数据流父组件通过
使用语法糖
v-model
直接实现,v-model
会默认会解析称为value的props
和input
事件,这种语法糖是典型的双向绑定通过访问
$parent
或者$children
对象来访问组件实例中的方法和数据。使用
$listeners
和.sync
这两个属性。$listeners
属性会将父组件中的 (不含.native
修饰器的)v-on
事件监听器传递给子组件,子组件可以通过访问$listeners
来自定义监听器。
兄弟组件通信
- 通过查找父组件中的子组件实现,也就是
this.$parent.$children
,在$children
中可以通过组件name
查询到需要的组件实例,然后进行通信。
跨多层次组件通信
- 使用 Vue 2.2 新增的 API
provide / inject
,
任意组件
- Event Bus事件总线的方式
- vuex
4.3 extend
- 扩展组件生成构造器,与
$mount
一起使用
4.3 mixin 和 mixins 区别
- mixin:用于全局混入,会影响到每一个组件实例,通常插件就是这样使用
- mixins是最常用的扩展组件的方式,如果多个组件有相同的业务逻辑,可以单独抽离出来,通过mixins混入代码,比如上拉加载数据这种业务逻辑
- 注意:mixins中混入的钩子函数会优先于组件内的函数执行,并且在遇到同名选项的时候会进行选择性的合并
4.4 computed和watch的区别
- computed是计算属性,,依赖于其他属性计算值,并且congputed的属性有缓存,只要计算值发生变化才会返回内容
- wath监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作
- 使用场景:一般来说需要依赖别的属性来动态获得值的时候可以使用
computed
,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用watch
。
4.5 v-show和v-if的区别
v-show
只是在display: none
和display: block
之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说v-show
在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。v-if
的话就得说到 Vue 底层的编译了。当属性初始为false
时,组件就不会被渲染,直到条件为true
,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。- v-if是惰性加载机制,只有在必要的时候才会进行渲染组件,减少整个页面的初始化开销
4.6 组件中的data什么时候可以使用对象
- 组件复用的时候所有组件都会共享data,修改其中的data会影响其他所有的组件,所以需要将data写成函数,每次用到就调用一次函数获得新的数据
- 在创建new Vue的时候可以使用data对象,根组件中的data数据不进行共享的
4.7 vue中使用key的原因:
- key的作用就是更新组件时判断两个节点是否相同。相同就复用,不相同就删除旧的创建新的。
5 Vue进阶
5.1 响应式原理(数据双向绑定的原理)
vue
实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通Javascript
对象传给 Vue 实例来作为它的data
选项时,Vue 将遍历它的属性,用Object.defineProperty()
将它们转为getter/setter
。用户看不到getter/setter
,但是在内部它们让Vue
追踪依赖,在属性被访问和修改时通知变化。(通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调,)vue的数据双向绑定 将
MVVM
作为数据绑定的入口,整合Observer
,Compile
和Watcher
三者,通过Observer
来监听自己的model
的数据变化,通过Compile
来解析编译模板指令(vue
中是用来解析模板字符串的
),最终利用watcher
搭起observer
和Compile
之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input
)—>数据model
变更双向绑定效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<input id="input" type="text" />
<div id="text"></div>
let input = document.getElementById("input");
let text = document.getElementById("text");
let data = { value: "" };
Object.defineProperty(data, "value", {
set: function(val) {
text.innerHTML = val;
input.value = val;
},
get: function() {
return input.value;
}
});
input.onkeyup = function(e) {
data.value = e.target.value;
};- 缺陷:不能监控数组下标的变化(解决:采用数组方法操作数组),每次都都需要遍历data中的所有属性,并且只劫持属性,不能劫持对象
还可以通过proxy代理实现双向绑定
5.2编译过程
- Vue通过编译器将模板通过几个阶段编译为render函数,然后通过执行render函数生成virtual dom,最终映射为真是的dom
- 编译过程:
- 将模板解析为AST
- 优化AST
- 将AST转化为render函数
- 总的来说,,
Vue complier
是将template
转化成一个render
字符串。
5.3 NextTick 原理分析
- nextTick:可以让我们在dom更新循环之后执行延迟回调,用于获取更新后的dom
6 bable原理
ES6、7
代码输入 ->babylon
进行解析 -> 得到AST
(抽象语法树)->plugin
用babel-traverse
对AST
树进行遍历转译 ->得到新的AST
树->用babel-generator
通过AST
树生成ES5
代码
7 前端监控
- 前端监控分为三种:
- 页面埋点:一般会监控以下数据
- PV / UV(页面被访问的次数,访问用户数)
- 停留时长
- 流量来源
- 用户交互
- 性能监控:只需要调用
performance.getEntriesByType('navigation')
这行代码 - 异常监控
- 是使用
window.onerror
拦截报错 - 对于跨域的代码运行错误会显示
Script error.
对于这种情况我们需要给script
标签添加crossorigin
属性 - 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过
arguments.callee.caller
来做栈递归 - 对于异步代码来说,可以使用
catch
的方式捕获错误 - 接口异常就相对来说简单了,可以列举出出错的状态码
- 是使用
- 页面埋点:一般会监控以下数据
设计模式
1.工厂模式
- 工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。(提供结构就可以创建实例)
2 单例模式
- 全局缓存和全局状态管理(vuex)只需要一个对象,采用单例模式,(全文只会创建一个,创建之前都会进行判断,存在责部门则不会再创建实例对象)
3 适配器模式
- 适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。
4 装饰者模式
- 装饰模式不需要改变已有的接口,作用是给对象添加功能。
5 代理模式
- 代理是为了控制对对象的访问,不让外部直接访问到对象。(事件代理就用到了代理模式。)
6 发布订阅模式
- 也叫观察者模式,通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知
- 如何实现响应式也是使用了该模式。对于需要实现响应式的对象来说,在
get
的时候会进行依赖收集,当改变了对象的属性时,就会触发派发更新。
7 外观模式
- 外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。
本文链接: https://sparkparis.github.io/2020/06/10/%E9%9D%A2%E8%AF%95-4/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!