1.原型和原型链:
:
说到原型和原型链,很多人都是一知半解,今天特地去重温了一下概念,在这做一下总结,以备不时之需;
首先说原型,在js中每个函数都有一个prototype属性,这个属性返回的东西就是这个函数的原型,这个原型上面所挂在的方法,属性,他的实例也可以取到,实例是怎么来的呢,通过new 关键字,new 一个函数,用一个变量接收,这就是这个函数的实例对象,然后,这个函数也被称之为构造函数;
function Person(){
}
let person = new Person()
new 一个狗砸函数的时候都执行了哪些操作呢?
- 创建了一个新对象
- 将狗杂函数里的作用域加到这个对象身上
- 执行这个狗杂函数里的代码
- 返回新对象
然后有了这俩东西以后,我们就可以开始 原型链之旅了:
(先看这些)
console.log(person.__proto__) //Person 构造函数的原型
console.log(person.__proto__.constructor) // Person 构造函数
console.log(Person.prototype) //Person 构造函数的原型
console.log(Person.prototype.constructor) //Person构造函数
console.log(Person.prototype.__proto__) //Object 的原型
console.log(Object.prototype) // Object的原型
console.log(Object.prototype.constructor) // Object 构造函数
console.log(Object.prototype.__proto__) // null
从这些东西里可以得出一个结论:
console.log(person.__proto__ === Person.prototype) //true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(person.__proto__.constructor === Person.prototype.constructor)//true
上一张自己画的图:
这用hasOwnProperty这个方法来举个例子:
function foo(){
// this.name = 'foo'
}
foo.prototype.name = 'heheh'
let myfoo = new foo();
myfoo.name =' asdasd'
console.log(myfoo.hasOwnProperty('name'))//true
foo实例调用 hasOwnProperty 这个方法,他会先去 foo.__proto__上去找(也可以说是Foo的prototype),找到了吗?没有。。
因为Foo的原型上不存在这个方法,好的,你没有,我去找你爹,你爹是谁?foo.proto.proto (或者说是Foo.prototype.proto),他是谁?Object.prototype ,“友好”的js给你在Object的原型上提供了这个方法,你可以调用了,如果说我调用的不是hasOwnProperty这个方法,我调用的是 hasOwnProperty123123 这个方法呢?一样的套路,一直往上找到Object.prototype 这,这有吗?有个粑粑,根本没有,那怎么办呢?继续往上找呗,Object.prototype.proto ,这是个啥,null!!!好了,不给你找了,没有,这个方法没有,我给你个null,你用吧。
这就是原型和原型链,原型之间的关系,查找的过程,就是原型链;
对了,这个hasOwnProperty 方法,他只会在实力内部去查找,不会去原型上查找,原型链更不会,返回boolean 值
以上为个人理解,仅供参考
2.Diff算法???
在Vue 和 React 中 都采用了虚拟DOM ,渲染真实DOM就涉及到了重绘整个页面,整个dom树都会被重新排版,所以在js中DOM操作是特别昂贵的,所以虚拟DOM 就来了;
一段代码解释什么是虚拟DOM:
<div class='div' name='div'>
<p>123</p>
</div>
//对应的 virtual DOM
var Vnode = {
tag:'div',
class:'div',
name:'name',
children:[
{tag:'p',text:'123'}
]
}
virtual DOM 就是将真实的DOM结构数据抽取出来,然后以对象的形式模拟树形结构
diff的比较方式
平级比较,div 和 div 比较, p 和 p , 不会跨级
diff流程图:
通过发布订阅,虚拟dom数据发生变化,通过Dep.notify通知订阅者,订阅者调用patch开始打补丁
往patch里传入 新树和老树,判断是否相同,相同就直接用新的替换了老的,不相同的话执行patch,
patch里面涉及到了几个方法:
sameVnode (oldVnode,Vnode)-----检查新老节点,值不值得比较,就是用不用开始比较,然后执行一系列操作,相当于预比较;
patchVnode(oldVnode,Vnode)----- 找节点,判断新旧是否是一个指向同一个对象树,如果是,return,否则对比新树老树,开始增删,没有改!!!
updateChildren(parentElm, oldCh, newCh) -----这是改!
- 拿节点。粉色是老树下的节点。黄色是新树下的节点;
- 拿新老树开始和结束的下标 oldS S oldE E 头尾交叉对比! 如果头尾交叉比较没有结果 就会用key进行比较
- 插入真实dom
放一个原文地址:https://www.cnblogs.com/wind-lanyan/p/9061684.html
总结:
diff算法就是虚拟dom更新节点计算区域差异的方法,dom树结构发生变化后,set方法调用 ,Dep.notify 通知给所有的watcher,订阅者通过调用patch给真实的dom 开始打补丁;
再往细说:patch 里有几个主要的方法,
sameVnode :检查节点值不值得比较,
patchVnode:对比新老节点,如果执行的同一个DOM树,那么直接return,否则的话新树与老树开始对比,新树有的,老树没有的,往老树上加,新树没有老树有的,从老树上删,然后调用
updateChildren:拿下新老树节点,根据下标开始头尾交叉对比,如果头尾交叉不匹配,没结果,可根据key比较,这里是一边比较边打补丁的,比较结束,插入真实dom;
3.React / Vue 中key的作用???
说到key 首先要说 虚拟DOM,虚拟DOM是他们为了避免页面重绘,性能浪费的一个手段,用对象的方式去代替dom结构体,VUE 和 REACT 都采用了DIff算法来对比新老DOM树,在diff算法过程中,key值起了很大的因素,他首次出现是在sameVnode这个函数里,可以通过key来判断是否值的比较,然后呢进入updateChild函数,不设置key,会只进行头尾交叉比较,设置了key会在头尾交叉比较结束后,用key值进行比较,如果相同的key出现,就直接利用节点,可以最大化的利用节点;
总结:
- 管理可复用的元素,提高diff算法中节点利用率
- 标识
4.函数防抖和节流
我博客里有关于防抖和节流的概念和用法,在这再说一下把,首先他们都是处理高频问题的,什么是高频问题?高频问题即使不断的、连续的,比如我们的 input 事件,scroll事件,mousemove事件等等
防抖:
目的:在高频触发事件期内,然后不触发业务逻辑,在停止高频出发后 n秒,执行相应的业务逻辑;
//不立即执行,触发事件后不执行,等wait 秒后再执行
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);
}
}
//立即执行,这个是触发事件立即执行,wait 秒内再次触发就不执行了
function debounce(func,wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
节流:
高频触发事件期间,函数会触发,不是触发完以后才执行,是间隔多少秒,函数执行一次,稀释执行频率,限制执行频率
//时间戳做
function throttle(func, wait) {
let 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)
}
}
}
放个地址:https://www.jianshu.com/p/c8b86b09daf0
节流:高频触发的事件,在指定的单位时间内,只响应第一次;
防抖:高频触发的事件,在指定的单位时间内,只响应最后一次,如果在指定时间再次触发,则重新计算时间
5.js 设计模式
首先设计模式都是面向对象的,js的设计模式有很多,这做几个经典的,常用到的总结:
工厂模式:
工厂模式就是利用函数封装一个 new Object() 返回一个对象,可以利用函数传参的方式,一个模板,多次使用,就跟 工厂一样,弄出来一个机器,不断的使用,就像造轮子;
function factory(age,name) {
let o = new Object()
o.age = age
o.name = name
return o
}
let obj1 = factory(15, 'rose')
let obj2 = factory(20, 'kobe')
缺点:对象识别问题,
单例模式(单子模式,单体模式)
单例单例,就是一个!唯一的,页面只允许有一个实例对象存在,不管你new 了几次,他只创建一个实例对象,场景:单个不重复的模态框
自执行函数 + 闭包
function CreateMask(html) {
this.html = html
this.create()
}
CreateMask.prototype.create = function() {
var div = docunment.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
var Single = (function(){
var instance
return function(html) {
if(!instance) instance = new CreateMask(html)
return instance
}
})()
var a = new Single('aaaaaa')//页面展示aaaaaaa
var b = new Single('bbbbbb')//页面仍然展示aaaaa
console.log(a === b) // true
发布订阅模式:
多个订阅者(一般是注册的函数)同时监听同一个数据对象,当这个数据对象发生变化的时候会执行一个发布事件,通过这个发布事件会通知到所有的订阅者,使它们能够自己改变对数据对象依赖的部分状态。
let event = {
clientList: {},
listen(key, fn) {
if(!this.clientList[key]){
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trigger(type, money) {
let fns = this.clientList[type]
if(!fns || fns.length === 0) {
return false
}
fns.forEach(fn => fn.apply(this, [money]))
}
}
let houseObj = Object.assign({},event)
houseObj.listen('海景别墅', price => {
console.log('海景别墅'+ price)
})
houseObj.listen('茅草屋', price => {
console.log('茅草屋'+ price)
})
houseObj.trigger('海景别墅',88888)
houseObj.trigger('茅草屋',999)
个人理解,就是通过事件监听值的变化,比如 某宝某店铺要双十一了要打折,那么首先你得先关注他们店铺,关注了以后商品价格发生变化,自然通知你那里,我这边降价了,因为你那边属于一个观察者模式,所以自然看的到。。。。所以发布订阅者模式也叫观察者模式;一种一对多的依赖关系,当一个对象的状态发 生改变时,所有依赖于它的对象都将得到通知
6.vue自定义指令
Vue.directive('focus', {
bind: function (el) {
// 注意:每个函数中呢,第一个参数永远是el, 表示被绑定了指令的那个元素,这个el参数,是一个原生的js对象
el.focus()
},
// 在元素刚绑定指令的时候,元素还没有放到dom中去,这个时候,调用focus方法没有作用
// 因为一个元素只有插入dom之后,才能获取焦点
inserted: function (el) {
el.focus()
},
})
7.页面输入地址,敲下回车,到页面渲染完毕都经历了啥??
这个问题呢,其实主要是说这个dom渲染的过程,但是往更细的说的话,就可以扯到http的缓存机制,tcp连接…等等
- 首先回去找http 缓存,是否有缓存命中,如果命中,则利用缓存,未命中,请求服务器,获取,再设置缓存。
- 解析url的协议,域名,端口,将其还原成ip地址
- 建立TCP连接,三次握手开始,客户端发送syn,seq 到服务端,服务端验证返回akc ,客户端拿到再此发送seq和ack给服务端,建立完成。
- 浏览器向服务器发送http请求 https://www.cnblogs.com/chenqf/p/6386163.html
- 处理HTML标记并构建DOM树,处理CSS标记并构建DOM树 ,合成 render DOM https://www.cnblogs.com/tootwo2/p/7208890.html
- layout 计算页面节点位置
- 通过显卡将页面画到屏幕
8.HTTP?
http 是超文本传输协议,负责客户端和服务器端之前数据的传输,固然也叫通信协议。
HTTP 的缓存机制:
报文:
HTTP 传输中的数据块被称之为HTTP报文,报文中包括请求头部和请求主体
头 ====》 附加信息,缓存信息,规则信息,都在Response Headers 里
体 ====》 你真正想要拿到的东西,http 真正传输的数据
缓存:
客户端先发送 http 请求的时候先去缓存数据库里找,有的话并且max-age 没有过期,那就用缓存,否则就请求服务器,服务器给出数据和缓存规则,给出max-age然后客户端拿到存到缓存数据库
缓存规则:
缓存规则有很多
- 强制缓存,没那么复杂就判断是否命中,命中判断有效期,没过就用,过了就请求,请求回来再存起来,又是一个新的有效期,具体体现见Headers里的Cache-control 里的 max-age。
- 对比缓存:请求时在缓存数据库找,命中拿到缓存标识,服务器校验,校验成功返回304,使用缓存中的数据,不成功返回200,向服务器发送请求,服务器返回数据和缓存信息规则,然后客户端再存起来,具体体现在Etag 和 last-modified:。
对比缓存画张图:
总结:
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。返回200重新请求服务器获取数据跟缓存信息,标识,存进缓存库
原文链接:https://www.cnblogs.com/chenqf/p/6386163.html
9.js 的执行机制:
js 是一个 单线程 的语言(因为作为web脚本语言涉及到的dom操作比较多,多线程的话线程A删除dom1,线程B不删除dom1还往里加了一个dom2,那么到底根据谁为准?所以js设计者将其设计成了单线程),那就意味着他的所有任务都是在一个通道排队等待执行的,但是js 有事件循环机制,将任务分为同步和异步任务;
生活中我们说的同步可能就是一起去吃饭,一起打王者,但是在计算机中呢?他是指代码的执行顺序,是依次执行,按照顺序执行的,排队等待;异步呢就是为了解决代码执行的阻塞问题,但是顺序会发生变化。
js执行的时候
- 判断任务是同步还是异步
- 同步直接进入主线程,开始执行
- 异步进入 event table 里
- 异步任务达成某条件后将回调函数推入异步任务队列
- 主线程执行栈清空后开始执行异步任务队列里的任务
宏任务(macro-task):setTimeout setInterval 整体script 代码块
微任务(micro-task):promise then
个人理解:事件循环就是先执行一个宏任务,然后里面会有很多的任务,在判断每个任务属于同步还是异步,同步就直接在主进程走就行了,异步的话,如果是宏任务(setTimeout …)放在宏任务队列,微任务放在微任务队列,然后先执行同步任务,之后判断本次事件过程中有没有微任务,有的话执行掉,么有就执行下一个宏任务,下一个宏任务又会带来微任务,然后还是先执行了同步任务,再执行带来的微任务。那么就构成了事件循环(event loop)。
放个题:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
//script start async1 start async2 promise1 script end async1 end promise2 setTimeout
//变式一
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});
console.log('script end');
//script start ,async1 start,promise1,promise3,script end,promise2,async1 end,promise4,setTimeout
//微任务:1.promise2 2.async1 end 3.promise4
//宏:1 setTimeout
//变式二
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function() {
console.log('setTimeout1')
},0)
}
async function async2() {
//更改如下:
setTimeout(function() {
console.log('setTimeout2')
},0)
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout3');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
//script start,async1 start,promise1,script end,promise2,setTimeout3 setTimeout2 setTimeout1
//宏任务:setTimeout3 setTimeout2 setTimeout1
//微任务:promise2
//变式三
async function a1 () {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2 () {
console.log('a2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})
promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
console.log('script end')
//script start,a1 start,a2,promise1,a1 end,promise2.then,promise3,setTimeout
//宏:setTimeout
//微:promise1,a1 end,promise2.then,promise3
链接:
同步异步:https://www.jb51.net/article/158992.htm
执行机制:https://juejin.im/post/59e85eebf265da430d571f89#heading-3