基础不牢,地动山摇!!!
基础第二篇juejin.cn/post/707008…
2022.1.12
1.vue2 和 vue3的区别是什么?vue3新增了什么特性?
区别:
1.双向数据绑定原理发生了变化
vue2双向数据绑定是利用ES5的Object.defineProperty()
对数据进行劫持,结合发布订阅模式来实现
vue3中使用ES6的Proxy
API对数据代理
2.根节点
vue2只有一个根节点
vue3可以有多个根节点
3.API
vue2使用选项类型API(Options API): 用属性来分割组件
vue3使用组合型API(Composition API): 用方法来分割组件
组合API可以根据逻辑兴趣轻松组织代码,选项类型API的问题是this
,由于代码依赖组件,难以按兴趣单元汇总
4.建立数据
vue2把数据放在data中
export default {
data() {
return {
name: ''
}
}
}
vue3使用setup
方法,在组件初始化构造的时候触发
<template>
<div>
<h2> {{ state.name }} </h2>
</div>
</template>
<script>
import { reactive } from 'vue' //引入reactive声明数据为响应性数据
export default {
props: {
title: String
},
setup () { // 使用setup()方法来返回我们的响应性数据
const state = reactive({
name: '',
})
return { state }
}
}
</script>
5.生命钩子变化
vue2 | vue3 |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
6.传参不同
父传子:vue3通过props接收并通过 toRefs转成响应式 toRefs(props)
子传父:在Vue2中会调用到this.$emit然后传入事件名和参数对象。vue3中没有this,只能通过setup中的参数传递
7.vue3较Vue2体积小、速度快
8.在vue2中,主要使用的是观察者模式,不管数据多大,都会对数据进行创建检查者,而vue3对数据进行了懒观察,仅对可见部分数据进行了懒观察,大大节省了性能
新增的特性:
Performance:性能优化
Tree-shaking support:支持摇树优化
Composition API:组合API
Fragment,Teleport,Suspense:新增的组件
Better TypeScript support:更好的TypeScript支持
Custom Renderer API:自定义渲染器
2.Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?
defineProperty API的弊端:
- vue2是通过Object.defineproperty中的getter和setter函数进行数据劫持完成数据响应的
- 无法直接监听属性的新增和删除
- 无法直接监听数组(尽管重写了数组方法,但也仅能完成7个数组方法的监听) Proxy的优势:
- 作为vue3中替代defineProperty 的新API,相当于给一个对象的外层加了一层拦截,这层拦截可以做很多操作,比如对数据信息的过滤、修改、收集数据信息
- 因为是在整个对象外层进行了拦截,所以可以操作对象中的属性同时监听属性的新增和删除
- Proxy可以监听数组的变化
3.Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的Options Api 有什么区别?
Options API:当前我们使用的API是Options API,vue2.0中为了向组件中添加逻辑,我们填充属性,例如data、methods、mounted以及computed等,这个API最大的缺点,是其本身并不是有效的js代码,我们在使用options API 的时候,需要确切了解我们具体可以访问到哪些属性,以及我们访问到的当前属性的行为。在后台,VUE需要将此属性转换为工作代码,因为我们无法从自动建议和类型检查中受益,因此给我们在使用相关属性时,造成了一定弊端
Composition API:vue3.0中通过Composition API来解决这一问题,目的是为了将组件中的可用属性,作为js的函数直接暴露出来的机制解决上面我们所处的问题,其代码更易读,更易理解和学习,没有任何幕后操作
Composition API的好处不仅仅是以不同的方式进行编码,更重要的是对于代码的重用,不受模板和组件范围的限制,也可以准确的知道我们可以使用哪些属性,由于幕后没有什么操作,所以编辑器可以帮助我们进行类型检查和建议
2022.1.13
4.试解释下setTimeout和setInterval定时器无法按时执行的原因
JS是单线程,所以异步事件(比如鼠标点击和定时器)仅在线程空闲时才会被调度运行,代码执行时异步事件任务会按照将它们添加到队列的顺序执行,而setTimeout() 的第二个参数只是告诉JS再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。
5.函数调用的几种方式
4种:一般形式的函数调用、作为对象的方法调用、使用 call 和 apply 动态调用、使用 new 间接调用
那么既然都有apply/call,为什么没有bind呢,因为bind返回的是一个函数,并没有调用,实现上相当于只是在一个对象上挂了一个属性方法,此外IIFE属于自执行函数,js引擎会直接执行,并不需要调用(个人观点有待商榷)
6.现有全局构造函数function f(){}
,说出f.__proto__ === f.__proto__.constructor.__proto__
的输出结果
function f(){}
f.__proto__ === f.__proto__.constructor.__proto__ //true
关于函数:
任何函数的_proto_都是Function.prototype,并且Function._proto_和Function.prototype是相等的,函数是个特殊情况,其他的不会出现自己的隐式原型和自己的显式原型一样的情况
Function._proto_ === Function.prototype // true
// 对于函数自己生成自己的说法解释
// Function作为一个内置对象,是运行前就已经存在的东西,所以根本就不会根据自己生成自己
// 这种现象可能的解释: 1、只是简单表明一种关系,2、为了和其他函数保持一致
解析:
f.__proto__ == Function.prototype
Function.prototype.constructor == Function
Function.__proto__ == Function.prototype
扩展:
1.为啥用setTimeout
模拟setInterval
会比较好
setInterval
会漏执行
间隔执行器会每到固定间隔就添加此执行器到队列
- 如果队列中已经有此执行器的任务,就终止添加
- 如果没有未执行的当前同个执行器任务,就正常添加
- 如果有个当前通个执行器任务正在被处理,说明任务已经从队列中移走了,所以也添加
例如间隔是1s执行,那么每过1s,都会添加一次,但如果interval代码很长,要执行2s,执行代码时间大于设置的时间会漏执行
2.关于promise值穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// 1
.then
或.catch
的参数期望是函数,传入非函数则会发生值穿透
3.移动端click:为何会有300ms延迟
由于手机屏幕小,用户在浏览网页是时候可能需要放大屏幕,而300ms延迟就是用来判断用户操作时是双指还是三指操作
2022.1.14
7.输入一个url到页面渲染发生了什么?
1.浏览器地址栏输入 URL 并回车
2.浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期
3.DNS 解析 URL 对应的 IP
4.根据 IP 建立 TCP 连接(三次握手)
5.发送 http 请求
6.服务器处理请求,浏览器接受 HTTP 响应
7.浏览器解析并渲染页面
8.关闭 TCP 连接(四次挥手)
8.flex:1的属性组成?
flex由flex-grow
、flex-shrink
和 flex-basis
三个属性的缩写
flex-grow
: 属性定义项目的放大比例,默认为0
,即如果存在剩余空间,也不放大
flex-shrink
: 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
flex-basis
: 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto
,即项目的本来大小。
9.Promise.all()
和Promise.race()
和Promise.allsettled()
有什么区别?
Promise.all()
: 接受一个promise数组作为参数,如果不是则会调用Promise.resolve()
方法,将参数转为Promise
实例再进一步处理(参数可以不是数组,但必须具有Iterator
接口,且返回的每个成员都是Promise
实例),当数组内每个成员的状态变为成功状态时,返回由成员返回值组成的数组。当数组内有一个或多个成员变为失败状态时,返回第一个失败状态成员的返回值
Promise.race()
: 参数同Promise.all()
,只要数组成员有一个成员状态改变,Promise.race()
返回的promise实例状态就会改变
Promise.allsettled()
(ES2020):参数同Promise.all()
,Promise.all()
可以确定所有请求都成功了,但是只要有一个请求失败,他就会报错,不管另外的请求是否结束,而Promise.allsettled()
来确定一组异步操作是否都结束了(不管成功或失败),当数组每个成员状态都改变时,Promise.allsettled()
返回的新promise对象状态才会改变
10.说一下nextTick是做什么的?
nextTick()
是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数,nextTick多次调用会维持一个数组,之后会异步的把数组中的方法以此执行,这样的话用户就会在视图更新之后再获取到真实的dom元素。
nextTick
和$nextTick
的区别:
1.nextTick(callback)
:当数据发生变化,更新后执行回调。在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
2.$nextTick(callback)
:当dom发生变化,更新后执行的回调。将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
3.这两个方法没有太大的不同。区别在于:nextTick(callback)
是全局的方法;而$nextTick(callback)
是回调的this
自动绑定到调用它的实例上;所以用的更多的是$nextTick(callback)
11.useImperativeHandles和forwardRef的作用?
ref 是为了获取某个节点的实例,但是函数式组件(PureComponent)是没有实例的,不存在 this 的,这种时候是拿不到函数式组件的 ref 的。为了解决这个问题,由此引入 React.forwardRef
, React.forwardRef
允许某些组件接收 ref,并将其向下传递给子组件
- useImperativeHandle:在函数式组件中,用于定义暴露给父组件的ref方法。
- React.forwardRef: 将ref父类的ref作为参数传入函数式组件中,本身props只带有children这个参数,这样可以让子类转发父类的ref,当父类把ref挂在到子组件上时,子组件外部通过forwrardRef包裹,可以直接将父组件创建的ref挂在到子组件的某个dom元素上
2022.1.17
12. 说一下 options 请求
options 请求可以用来询问支持的请求方法,用来跨域请求,也就是我们常说的预检请求。
跨域共享标准规范要求: 对于可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求,来判断服务端是否允许跨域请求
主要用途:
- 获取服务器支持的所有 HTTP 请求方法
- 检查访问权限(CORS跨域资源共享)
详细内容可参考:面试官:说说你对 options 请求的理解
13. 跨域是什么?怎么解决跨域
跨域: 浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制
同源策略: 限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互
同源: 协议、端口号、域名必须一致
解决跨域:
1. CORS
关键是服务器,只要服务器实现了 CORS 请求就可以跨源通信了
2. JSONP
原理: 利用 script
标签没有跨域限制,通过 script
标签 src 属性,发送带有 callback
参数的 GET 请求,服务端收到请求后将结果拼凑到 callback
函数中,返回给浏览器,让浏览器去执行。
缺点:
- 只支持 GET 方法
- 不安全,可能会遭到 XSS 攻击 3. postMessage 可解决的问题:
- 页面和其打开的新窗口之间的数据传递
- 多页面之间的数据传递
- 页面和其 iframe 之间的数据传递
- 以上三种的跨域数据传递
用法:
postMessage(data, origin)
- data: h5 规定支持任意基本数据类型和可复制对象,但部分浏览器只支持字符串,所以最好使用
JSON.stringify
序列化一下 - origin: 协议 + 主机 + 端口号,也可以设置为
*
表示支持传递给任意窗口,如果要和当前窗口同源可以设置为/
4. document.domain + iframe
仅限主域相同,子域不同的情况
原理: 两个页面都强制设置document.domain
为基础主域,就实现了同域
5. nginx 代理
原理和 CORS 跨域一样,通过设置请求响应头Access-Controll-Allow-Origin
等字段
- nodejs 中间件
原理同 nginx 代理,都是启用一个代理服务器实现数据的转发 - location.hash + iframe
原理: a 与 b 跨域通信,用 c 来做中间件。不同域之间用 iframe 的location.hash
传值,相同域直接使用parent.parent
访问对象。 - window.name + iframe
- WebSocket 协议
14. http 和 tcp 的区别
http是应用层协议,是用来说明请求的一些信息的,有请求头请求行请求体,在传输过程的作用是提供一个网址,供后面层去查询ip地址
tcp是传输层协议,应用层协议被传输层加上tcp首部,传给下一层,首部中提供了端口号,tcp是有差错控制的,靠首部校验和这个字段来做一些检验操作,保证传输过程中没有出错。
http 协议是建立在 tcp 协议之上的一种应用
为什么http无连接,但http基于tcp来实现,tcp却是面向连接:
TCP的面向连接是基于网络底层的数据传输,HTTP的无连接是基于应用层面的沟通交互,HTTP使用TCP是为了保证数据传输的可靠性和完整性
15.什么是节流防抖,以及应用场景
节流
在一定时间内,如果再次触发事件,则不予处理,直到计时完成才能再次触发
// 定时器版本
function throttle(fn, await){
let timer = null
return function () {
let context = this, args = arguments
if (!timer) {
const timer = setTimeout(() => {
clearTimeout(timer)
fn.apply(context, args)
}, await)
}
}
}
// 时间戳版本
function throttle(fn, await) {
let curTime = Date.now()
return function() {
let context = this, args = arguments, nowTime = Date.now()
// 如果两次时间间隔超过了规定时间,则执行函数
if (nowTime - curTime >= await) {
curTime = Date.now()
return fn.apply(context, args)
}
}
}
应用场景: 解决一个需要频繁发生的事件,防止事件触发太多次,可使用在scroll函数的事件监听上,降低事件调用频率
防抖
在一定时间内,如果再次触发事件,则取消计时,重新计时
function debounce(fn ,await) {
let timer = null
return function () {
let context = this, args = arguments
if (timer) {
clearTimeout(timer)
}
const timer = setTimeout(() => {
fn.apply(context, args)
clearTimeout(timer)
}
}
应用场景: 输入框Onchange事件实现远程实时查询,触发时取消上次的事件
2022.1.18
16.let var const 的区别
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
17.判断对象中是否包含某个属性 (至少3种)
1.点( .
)或者方括号( [ ]
)
let obj = {a: 1}
console.log(obj.b) // undefined
let obj = {a: 1}
console.log(obj['b']) // undefined
可根据obj.b == undefined或obj[b] == undefined
判断是否存在该属性。但是,这种方法有一个弊端,当obj存在b属性但b属性值为undefined
时,会判断出错
2.in
运算符
如果指定的属性在指定的对象或其原型链中,则in
运算符返回true
let obj = {
a: 1,
b: undefined
}
'a' in obj // true
'b' in obj // true
'c' in obj // false
let obj = {a: 1}
let obj1 = Object.create(obj)
'a' in obj1 // true
这种方法可正常判断属性值为undefined
的情况,但是无法判断出属性是对象的属性还是对象原型上的属性
3.hasOwnProperty()
let obj = {a: 1}
let obj1 = Object.create(obj,{
b:{
value: '1',
enumerable: true
}
})
obj1.hasOwnProperty('b') //true 自身属性
obj1.hasOwnProperty('c') //false 不存在
obj1.hasOwnProperty('a') //false 原型链上属性
4.Object.keys(obj).includes('a')
let obj = {a: 1}
Object.keys(obj).includes('a') // true
5.Reflect.has()
静态方法Reflect.has()
作用与in
操作符相同
let obj = {a: 1}
Reflect.has(obj, 'a') //true
18.vue-router的实现
1.hash 实现
hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新,通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:
- 通过浏览器前进后退改变 URL
- 通过标签改变 URL
- 通过window.location改变URL history 实现 history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
2.history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:
- 通过浏览器前进后退改变 URL 时会触发 popstate 事件
- 通过pushState/replaceState或标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState的调用和标签的点击事件来检测 URL 变化
- 通过js 调用history的back,go,forward方法课触发该事件所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
核心原理参考: juejin.cn/post/685457…
19.JS执行上下文创建过程
一个执行上下文的生命周期包括两个阶段:
1.创建阶段 在这个阶段,执行上下文分别创建变量对象,建立作用域链,以及确定this指向
2.代码执行阶段完成变量赋值,函数引用,以及执行其他代码
相关文章:
juejin.cn/post/684490…
juejin.cn/post/684490…
2022.1.19
20.css画扇形
/*方式1*/
.sector {
height: 0;
width: 0;
border-top: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-radius: 50%;
}
/*方式2*/
.sector {
height: 0;
width: 0;
border: 50px solid transparent;
border-radius: 50%;
border-top-color: red;
/*任意角度*/
transform:rotate(90deg);
}
21.有了解过浏览器层(渲染层/复合层等)和GPU渲染(如何触发GPU渲染?GPU渲染的好处)么
总结后期更新。。。
相关文章:juejin.cn/post/684490…
segmentfault.com/a/119000001…
22.object.defineProperty有哪些描述符
-
configurable
当且仅当该属性的
configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为false
。 -
enumerable
当且仅当该属性的
enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。
默认为false
。
数据描述符还具有以下可选键值:
-
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为undefined
。 -
writable
当且仅当该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被赋值运算符
改变。
默认为false
。
存取描述符还具有以下可选键值:
-
get
属性的 getter 函数,如果没有 getter,则为
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为undefined
。 -
set
属性的 setter 函数,如果没有 setter,则为
undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。
默认为undefined
。 描述符默认值汇总 -
拥有布尔值的键
configurable
、enumerable
和writable
的默认值都是false
。 -
属性值和函数的键
value
、get
和set
字段的默认值为undefined
。
23.object.freeze()有什么用
- Object.freeze() 是 ES5 的新增特性,可以冻结一个对象,冻结的意思是不能向这个对象添加新的属性,不能修改其已有的属性值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。防止对象被修改,但不能防止被修改引用。如果你有一个巨大的数组或 Object,并且确信数据不会修改,使用 Object.freeze() 可以让性能大幅提升,多用于展示。
- freeze可以用来实现const
- object.freeze 可以让 vue 不添加响应
24.call,bind,apply的区别
- call/bind/apply都用来改变this指向,当第一个参数传入null或undefined时,this指向window,在传参的时候,bind可传入多个参数,而apply传入的时数组或类数组对象
- bind返回的是一个函数,而apply/call返回的是执行结果
- 当bind返回的函数作为构造函数的时候,bind时传入的this值会失效,但是参数依然生效
- bind可实现两次传参,调用函数的时候会把两次的传参合并起来,类似于柯里化传参
2022.1.20
25.说说for...in
和for...of
的区别,用什么方法可以过滤掉原型链上的属性名?怎样定义不可枚举的属性?用该方法定义的属性名在不改变方法描述的情况下怎样得到该属性名?
如何判断是否有迭代能力Array.prototype.hasOwnProperty(Symbol.iterator)
- for...in可以遍历对象和数组,for...of不能遍历对象,但如果你想迭代一个对象的属性,你可以用for...in循环或内建的Object.keys()方法
- for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键
- for...in遍历的索引为字符串类型
- for..of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合,但是不能遍历对象
- for...of与forEach()不同的是,它可以正确响应break、continue和return语句
- 具有迭代器对象(Iterator) 才可以使用for...of 一句话就是遍历数组使用for...of,遍历对象使用for...in
使用Object.hasOwnProperty()
过滤原型链上的属性名
定义不可枚举的属性可使用Object.defineProperty()
和Object.defineProperties()
(补充:这两者的功能相同,Object.defineProperties()
可以理解为Object.defineProperty()
的升级版,可修改多个对象的属性)设置enumerable
描述符为false
通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for...in
或 Object.keys
方法)
获取不可枚举的属性时,可使用Object.getOwnPropertyNames()
方法,该方法可返回目标对象的可枚举值和不可枚举值,同时,还可使用Reflect.ownKeys()
来获取。Reflect.ownKeys()
返回值相当于Object.getOwnPropertyNames
(target).concat(Object.getOwnPropertySymbols
(target))
26.说说slice和splice的区别
splice改变原数组,slice不会
splice
- 添加或删除元素,返回删除的元素
- 会改变原数组
- 语法:
array.splice(start[,num, item1...itemN)
// 删除,第二个参数可选,没有则删除第一个参数位置以后所有的元素,为0则不删除
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.splice(2,1)
console.log(arr)// ['a', 'b', 'd']
console.log(arr1)// ['c']
// 新增
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.splice(2, 0, 'h')
console.log(arr)// ['a', 'b','h', 'c', 'd']
console.log(arr1)// []
slice
- 返回被选中的元素
- 不改变原数组
- 语法:
array.slice([start,end])
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice()
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['a', 'b', 'c', 'd']
// 1位置开始,2位置结束,但不包括2
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice(1,2)
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['b']
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice(1)
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['b', 'c', 'd']
27.说说mouseover和mouseenter的区别
- 不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件(支持冒泡)。对应mouseout
- 只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件(不支持冒泡)。对应mouseleave
2022.1.21
28.简单说下js的执行过程(变量提升,执行上下文,编译阶段,执行阶段,异步方法处理)ps:代码中存在多个相同的变量和函数他们怎么处理的
29.介绍下谷歌浏览器控制台的前端常用的调试工具
Element面板: 用于查看和编辑当前页面中的 HTML 和 CSS 元素。
Network面板:用于查看 HTTP 请求的详细信息,如请求头、响应头及返回内容等。
Sources面板:用于查看和调试当前页面所加载的脚本的源文件。
Resource面板:用于查看当前页面所请求的资源文件,如 HTML,CSS 样式文件等。
Performance面板:分析页面加载的过程,进而提供减少页面加载时间、提升响应速度的方案,用于优化前端页面,加速网页加载速度等。
Console面板:用于显示脚本中所输出的调试信息,或运行测试脚本等。
30.请计算出时钟的时针和分针的角度(两个角度的较小者,四舍五入)。时间以HH:mm的格式传入。例angle('12:00')// 0
,angle('23:30')// 165
LeetCode题目:leetcode-cn.com/problems/an…
2022.1.24
31. 如何判断一个对象是空对象
1.for...in
function checkObj (obj) {
for(let i in obj) {
return false
}
return true
}
let obj = {}
console.log(checkObj(obj))// true
let obj = {a:1}
console.log(checkObj(obj))// false
我们都知道for...in
可以遍历原型上的属性,所以得注意到这点
let obj = {a:1}
let obj1 = Object.create(obj)
console.log(obj1) // {}
console.log(checkObj(obj1)) // false
2.JSON.stringify()
(此方法有一定的漏洞且耗时)
let obj = {}
if (JSON.stringify(obj) === '{}') {
return true //是空对象
}
注意:
1. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
2. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
3. undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。
4. 所有以symbol为属性键的属性都会被完全忽略掉,即便replacer参数中强制指定包含了它们。
5. 不可枚举的属性会被忽略
const a = {a: undefined}
let b = JSON.stringify(a)
console.log(b) //{}
const a = {a: ()=> {}}
let b = JSON.stringify(a)
console.log(b) //{}
3.Object.keys()
function checkObj(obj) {
let arr = Object.keys(obj)
return arr.length > 0?false:true
}
4.Object.getOwnPropertyNames()
(最优选择)
let obj = {};
let arr = Object.getOwnPropertyNames(obj);
console.log( arr.length == 0 ); //true
区别:Object.keys()
返回可枚举的,Object.getOwnPropertyNames()
返回所有的(可枚举和不可枚举,但是不包括Symbol
32. HTTP/1.0和HTTP/1.1有什么区别
- 连接方面,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。
- 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
- 缓存方面,在 http1.0 中主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。
- http1.1 中新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。
- http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。
33. 已知如下数组:var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
let arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
// 扁平化
let flatArr = arr.flat(4)
// 去重
let disArr = Array.from(new Set(flatArr))
// 排序
let result = disArr.sort(function(a, b) {
return a-b
})
console.log(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
flat()
方法对node版本有要求,至少需要12.0以上
34.什么是GPU加速,如何使用GPU加速,GPU加速的缺点
GPU
硬件加速是指应用 GPU
的图形性能对浏览器中的一些图形操作交给 GPU
来完成,因为 GPU
是专门为处理图形而设计
,所以它在速度和能耗
上更有效率
GPU
加速通常包括以下几个部分:Canvas2D
,布局合成(Layout Compositing)
, CSS3转换(transitions)
,CSS3 3D变换(transforms)
,WebGL
和视频(video)
。
GPU实现动画的优缺点
优点:
利用了GPU合成图层实现动画,可以做到动画平滑、流畅动画合成工作在GPU线程,不会被CPU的js运行阻塞
缺点:
绘图层必须传输到GPU,当图层较多时传输过程可能会导致渲染缓慢
每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃
复合层合成需要更多的时间
2022.1.25
35.this 指向问题
function fn (){ console.log(this) }
var arr = [fn]
arr[0]()
详解:zhuanlan.zhihu.com/p/174160691
36.如何判断元素在当前可视区域内
以图片显示为例:
window.innerHeight
是浏览器可视区的高度;document.body.scrollTop || document.documentElement.scrollTop
是浏览器滚动的过的距离;imgs.offsetTop
是元素顶部距离文档顶部的高度(包括滚动条的距离);- 内容达到显示区域的:
img.offsetTop < window.innerHeight + document.body.scrollTop;
37.requestAnimationframe 相比 setTimeout在执行动画时候的优势
requestAnimationframe优点:
- CPU节能: 当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停
- 函数节流:在高频率事件( resize, scroll 等)中,为了防止在一个刷新间隔内发生多次函数执行,RequestAnimationFrame 可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销,一个刷新间隔内函数执行多次时没有意义的,因为多数显示器每 16.7ms 刷新一次,多次绘制并不会在屏幕上体现出来
- 减少dom操作: requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。 setTimeout 执行动画的缺点:它通过设定间隔时间来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是:
- settimeout 任务被放入异步队列,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;
- settimeout 的固定时间间隔不一定与屏幕刷新间隔时间相同,会引起丢帧。
38.react 中 setState是同步的还是异步的
setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。
- 异步: 在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
- 同步: 在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener 、setTimeout、setInterval 等事件中,就只能同步更新。
39.定义变量a ,让a==1&&a==2&&a==3 为true
2022.1.26
40.vue的$set原理
41.$nextTick 原理
鲨鱼哥文章:juejin.cn/post/693970…
42.简述vue diff算法过程
鲨鱼哥文章:juejin.cn/post/695343…
43.vue中的methods为什么可以访问到实例,属性写在data中this为什么可以直接访问到
若川大佬文章:juejin.cn/post/701092…
44.输出题
const async1 = async () => {
console.log('async1');
setTimeout(() => {
console.log('timer1')
}, 2000)
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 end')
return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then(res => console.log(res))
setTimeout(() => {
console.log('timer2')
}, 1000)
/** 输出
* script start
* async1
* promise1
* script end
* 1
* timer2
* timer1
**/
解析:
第一轮:
- 定义一个async1函数,无事发生
- 输出script start
- 执行async1和then,然后将then里的回调函数放进微任务记为p1
- 输出async1,将setTimeout:timer1放入宏任务记为s1,输出promise1,由于await和这个promise没有resolve或者reject,则async1在这里堵塞,且返回一个promise状态为pending
- 输出script end
- 遇到promise.resolve(1),将他后面的then回调放入微任务记为p2
- 遇到setTimeout:timer2放入宏任务记为t2
- 检查微任务队列:p1和p2
- 执行p1,发现async的状态为pending,所以进行输出
- 执行p2,发现then的回调函数不是函数,所以带着resolve(1)向下穿透,一直透透透,到了最后一个then,输出1
- 微任务执行完毕 第二轮:
- 根据宏任务的延时时间执行t2,输出timer2
- 输出timer1
2022.1.27
45.事件捕获和冒泡
冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
46.pointer-event属性不同值的作用
pointer-events:auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit
pointer-events属性有很多值,但是对于浏览器来说,只有auto和non两个值可用,其它的几个是针对SVG的(本身这个属性就来自于SVG技术)。
pointer-events属性值详解:
auto——效果和没有定义pointer-events属性相同,鼠标不会穿透当前层。在SVG中,该值和visiblePainted的效果相同。
none——元素不再是鼠标事件的目标,鼠标不再监听当前层而去监听下面的层中的元素。但是如果它的子元素设置了pointer-events为其它值,比如auto,鼠标还是会监听这个子元素的
47.weakmap和weakset的用处和场景
WeakMap
WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存
WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
WeakSet
用于存储DOM节点,而不用担心这些节点从文档移除时会引发内存泄露
即可以用来避免内存泄露的情况
2022.1.28
48.webpack构建过程有什么优化手段?webpack优化打包体积,可以怎么做?
提升构建速度个人认为可以从三个维度入手,一个是利用缓存,二是利用机器性能,三是减少需要打包的内容部分内容预打包主要措施有:
- 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码
- 通过 externals 配置来提取常⽤库
- 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
- 使⽤ Happypack 实现多线程加速编译
- 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度
- 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码 优化打包体积:
- 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
- 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
- Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
再补充一些优化前端性能的点:
webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。 - Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
- 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
49.react和vue都使用了vdom,为什么我们需要使用vdom,vdom有什么好处?替我们解决了什么问题?
(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能
看一下页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler
下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶
- 真实DOM∶ 生成HTML字符串+重建所有的DOM元素
- 虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新
Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。
(2)跨平台
Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。
50.对 SPA 单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
-
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
-
基于上面一点,SPA 相对对服务器压力小;
-
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
-
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
-
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
-
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
2022.2.07
51、“==”比较时候的相互转换规则
1 == true //true
1 === true // false
- 先判断两者类型是否相同,相同进行判断,不同则进行强制类型转换
- 先判断是否是
null
和undefined
对比,是的话直接返回true
- 判断两者类型如果是
string
和number
,是则将字符串转换为number
'1' == 1 //true
'2' == 1 //false
- 如果其中有一方为
boolean
,将boolean
转换为number
// true转换为1,false是0
1 == true // true
0 == false // true
- 其中一方为
object
,另一方是string
、number
、symbol
,将object
转换为原始类型再进行对比
为了将值转换为相应的基本类型值,抽象操作ToPrimitive会首先(通过内部操作DefaultValue)检查该值是有valueOf()
方法.如果有并且返回基本类型值,就是用该值进行强制类型转换。如果没有就使用toString()
的返回值(如果存在)来进行强制类型转换
如果valueOf()
和toString()
均不返回基本类型值,会产生TypeError
错误
52、事件传播机制/事件委托/事件代理
事件传播机制分为三个阶段:
- 第一阶段:从window对象传导到目标节点,称为“捕获阶段”
- 第二阶段:在目标节点上触发,称为“目标阶段”
- 第三阶段:从目标节点传导回window对象,称为“冒泡阶段”
事件委托(事件代理)本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。
好处: 使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理
缺点: 事件委托会影响页面性能,主要影响因素有:
1、元素中,绑定事件委托的次数;
2、点击的最底层元素,到绑定事件元素之间的DOM
层数;
局限性: focus、blur
之类的事件没有事件冒泡机制,所以无法实现事件委托;mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的。
53、mouseover/mouseenter的区别
区别请看27题juejin.cn/post/705232…
扩展(场景题):
如果在axios的拦截器里面统一捕获了401的后端错误,然后前端页面报‘登录失效请重新登陆’。如果登录失效,当一进页面发了十个请求,不做处理是会立即弹出十条报错提醒的。请问如何解决这样不友好的报错提示?
2022.2.08
54、如何防御XSS攻击
- 可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)。另一种是对需要插入到 HTML 中的代码做好充分的转义。对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。
- 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。
- CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。
- 通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式
- 对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。
55、简述TCP的三次握手和四次挥手
三次握手:
- 第一次握手:客户端向服务器端发送连接请求报文段
- 第二次握手:服务端收到请求报文段后,如果同意连接则发送一个应答
- 第三次握手:当客户端收到连接同意的应答后,还要向服务器发送一个确认报文连接成功 四次挥手:
- 第一次挥手:若客户端认为数据发送完成,则向服务端发送连接释放请求
- 第二次挥手:服务端收到连接释放请求后,告诉应用层要释放TCP连接
- 第三次挥手:如果服务器端还没有发送完数据则会继续发送,发送完成后向客户端发送释放通知
- 第四次挥手:客户端收到释放通知后,向服务器发送确认应答,如果在规定的时间内没有收到重发通知则结束
56、闭包的应用场景有哪些?
1、采用函数引用方式的setTimeout调用;2、小范围代替全局变量;3、有权访问私有变量和私有函数的公有方法。4、节流防抖
2022.2.09
57、如何转化类数组成数组?
第一种:Array.prototype.slice.call(likeArray)
function sum(a, b) {
let args = Array.prototype.slice.call(arguments);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3
第二种:Array.from(likeArray)
function sum(a, b) {
let args = Array.from(arguments);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3
第三种:Es6展开运算符
function sum(a, b) {
let args = [...arguments];
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3
第四种:Array.prototype.splice.call(likeArray,0)
function sum(a, b) {
let args = Array.prototype.splice.call(arguments, 0);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3
第五种:Array.prototype.concat.apply([], likeArray)
function sum(a, b) {
let args = Array.prototype.concat.apply([], arguments);
console.log(args.reduce((sum, cur) => sum + cur));//args可以调用数组原生的方法啦
}
sum(1, 2);//3
58、forEach中return有效果吗?如何中断forEach循环?
有效果,但是只是不走本次循环,后边的还是会走
使用break/continue
无效,会报语法错误
let nums = [1, 2, 3];
nums.forEach((item, index) => {
return;//无效
})
中断方法:
使用try监视代码块,在需要中断的地方抛出异常。
官方推荐方法(替换方法):
- 用every和some替代forEach函数;
- every在碰到return false的时候,中止循环;
- some在碰到return true的时候,中止循环
59、["1", "2", "3"].map(parseInt)
答案是多少?
数组的map函数,接受三个参数,当前值,当前索引,当前数组。 parseInt接受两个参数,需要转换的字符串,基数(基数取值范围2~36)
['1','2','3'].map((item, index) => {
return parseInt(item, index)
})
// parseInt('1', 0) 1
// parseInt('2', 1) NaN
// parseInt('3', 2) NaN
扩展:
为什么forEach中return不能结束循环
首先大概实现下forEach
Array.prototype.myEach = function(callback) {
for (var i = 0; i < this.length; i++)
callback(this[i], i, this);
};
那么为什么不能使用return跳出循环呢,是因为在foreach中用return相当于函数中使用return,那它只是影响了回调函数,并不会影响到函数上级的for循环,同理,在函数中使用break/continue
会报语法错误,这和在forEach中使用break/continue
是同样的效果
常见的类数组有哪些?
arguments
- Dom元素用attributes属性获取NameNodeMap对象
- 通过Dom元素的children属性获取元素的子元素集合即HTMLCollection集合对象
- 通过Dom元素的childNodes 属性返回包含被选节点的子节点的NodeList 注: HTMLCollection集合中只有DOM元素节点,NodeList中包含Dom元素节点和文本节点(text)和注释节点
2022.02.10
60、localhost:3000 与 localhost:5000 的 cookie 信息是否共享
根据同源策略,cookie 是区分端口的,但是浏览器实现来说,“cookie 区分域,而不区分端口,也就是说,同一个 ip 下的多个端口下的 cookie 是共享的!
61、Array(100).map(x => 1) 结果是多少
Array(100)
将会创建一个稀疏数组 (sparse array),即不存在真实元素,节省内存空间。在控制台上显示为 [empty]
,正因为没有元素,所以它也不会有 map
操作,所以 Array(100).map(x => 1)
仍然返回为 [empty]
62、输出题
const obj = {
a: 3,
b: 4,
c: null,
d: undefined,
get e() {},
}
console.log(JSON.stringify(obj))
输出:{"a":3,"b":4,"c":null}
2022.02.11
63.common.js和es6中模块引入的区别?
CommonJS是一种模块规范, 最初被应用于Nodejs, 成为Nodejs的模块规范。运行在浏览器端的JavaScript由于也缺少类似的规范,在ES6出来之前,前端也实现了一套相同的模块规范(例如: AMD),用来对前端模块进行管理。自ES6起,引入了一套新的ES6 Module规范,在语言标准的层面.上实现了模块功能,而且实现得相当简单,有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对ES6 Module兼容还不太好,我们平时在Webpack中使用的export和import,会经过Babel转换为CommonJS规范。在使用上的差别主要有:
CommonJs模块输出的是一个值的拷贝, ES6 模块输出的是值的引用;
CommonJs模块是运行时加载,ES6 模块是编译时输出接口;
CommonJs 是单个值导出,ES6 Module可以导出多个;
CommonJs是动态语法可以写在判断里,ES6 Module静态语法只能写在顶层。
64.用JS判断给定的字符串是否是同构的?
65.为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
blog.csdn.net/weixin_3043…
如何遍历类数组请看57题
2022.02.14
66.判断字符串以字母开头,后面可以是数字,下划线,字母,长度为6-30
var reg=/^[a-zA-Z]\w{5,29}$/
67.Vue的父组件和子组件生命周期钩子函数执行顺序
加载渲染过程: 父beforeCreate
-> 父created
-> 父beforeMount
-> 子beforeCreate
-> 子created
-> 子beforeMount
-> 子mounted
-> 父mounted
子组件更新过程: 父beforeUpdate
-> 子beforeUpdate
-> 子updated
-> 父updated
父组件更新过程: 父beforeUpdate
-> 父updated
销毁过程: 父beforeDestroy
-> 子beforeDestroy
-> 子destroyed
-> 父destroyed
68.Proxy 与 Object.defineProperty 优劣对比
Proxy
名词解释:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
- 劫持方式:代理整个对象,只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性
- 本质:Proxy 本质上属于元编程非破坏性数据劫持,在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念
Object.defineProperty
名词解释:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- 劫持方式:只能劫持对象的属性,不能直接代理对象
- 流程:get中进行依赖收集,set数据时通知订阅者更新
- 存在的问题:虽然 Object.defineProperty 通过为属性设置 getter/setter
能够完成数据的响应式,但是它并不算是实现数据的响应式的完美方案,某些情况下需要对其进行修补或者hack,这也是它的缺陷 - 主要表现在两个方面:
- 无法检测到对象属性的新增或删除 ,不能监听数组的变化。
优劣势
Proxy 的优势如下:
- Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性;
- 数组新增删除修改时,Proxy可以监听到;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
- 如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty;
- 对象上定义新属性时,Proxy可以监听到;
Object.defineProperty 的优势如下:
- 兼容性好,支持 IE9及以上,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
2022.02.15
69.输出运算的结果:let a = 10+null+true+[]+undefined+'你好'+null+[]+10+false
与字符串进行+
运算时会执行拼接,其他情况下转为数值类型相加
答案:'11undefined你好null10false'
70.事件循环:输出什么?页面会有什么变化?
document.body.style="background:red"
console.log(1)
Promise.resolve().then(()=>{
console.log(2)
document.body.style="background:yellow"
})
console.log(3)
微任务之后会进行一次渲染操作
输出:1 3 2
页面变为黄色
71.请说一下ts函数重载是什么
2022.02.16
72. 描述下vue自定义指令(你是怎么用自定义指令的)?
自定义指令: 我们可以自己写一个自定义指令去操作DOM元素,以达到代码复用的目的。注意,在 Vue 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
全局注册指令:Vue.directive('name', {})
局部注册指令:directives: {focus: {}}
自定义指令对象提供了钩子函数供我们使用,这些钩子函数都为可选。
使用:节流防抖(segmentfault.com/a/119000002…)
73. 介绍一下Vue单页应用和多页应用的区别?
mm | 多页应用模式MPA | 单页应用模式SPA |
---|---|---|
应用构成 | 由多个完整页面构成 | 一个外壳页面和多个页面片段构成 |
跳转方式 | 页面之间的跳转是从一个页面跳转到另一个页面 | 页面片段之间的跳转是把一个页面片段删除或隐藏,加载另一个页面片段并显示出来。这是片段之间的模拟跳转,并没有开壳页面 |
跳转后公共资源是否重新加载 | 是 | 否 |
URL模式 | http://xxx/page1.html http://xxx/page1.html | http://xxx/shell.html#page1 http://xxx/shell.html#page2 |
用户体验 | 页面间切换加载慢,不流畅,用户体验差,特别是在移动设备上 | 页面片段间的切换快,用户体验好,包括在移动设备上 |
能否实现转场动画 | 无法实现 | 容易实现(手机app动效) |
页面间传递数据 | 依赖URL、cookie或者localstorage,实现麻烦 | 因为在一个页面内,页面间传递数据很容易实现 |
搜索引擎优化(SEO) | 可以直接做 | 需要单独方案做,有点麻烦 |
特别适用的范围 | 需要对搜索引擎友好的网站 | 对体验要求高的应用,特别是移动应用 |
搜索引擎优化(SEO) | 可以直接做 | 需要单独方案做,有点麻烦 |
开发难度 | 低一些,框架选择容易 | 高一些,需要专门的框架来降低这种模式的开发难度 |
74. Vue 是如何收集依赖的?
2022.02.17
75. 如何判断一个变量是否为对象,如何判断一个变量是否为数组,有哪些方法
如何判断一个变量是否为对象
instanceof
constructor
Object.prototype.toString.call()
如何判断一个变量是否为数组Array.isArray
Object.prototype.tostring.call()
instanceof
- 原型链
obj.__proto__ ==== Array.prototype
Array.prototype.isPrototypeOf()
76. 箭头函数和普通函数有什么区别
- 箭头函数写法比普通函数更加简洁
- 箭头函数没有自己的
this
,并且继承来的this
不会改变 - 箭头函数没有
prototype
(没有显示原型,但是有隐式原型__proto__) - 箭头函数不能用作
Generator
函数,不能使用yield
关键字 - 箭头函数没有自己的
arguments
band
、call
、apply
不能改变箭头函数this
指向
77. Vue中computed、methods和watch的区别
methods就是正常用proxy代理到vm下,触发的时候就直接调用,watch和computed都有自己专属的watcher对象,每个k都对应一个watcher,watch是直接触发绑定的,computed则是懒加载,watch的响应式绑定是在observe()的时候做的,computed的响应式则是要单独做,也是用的defineProperty,computed并不会随着回调中数据更新直接重新计算,数据更新只会改变computed下的watcher中的dirty字段,而真正的重新极端是只有用户访问computed下属性的时候会去判断computed的watcher的dirty字段,根据是否是脏数据来决定要不要重新计算
78.JS作用域+this指向+原型的考题的代码输出
function Foo(){
getName = function(){console.log(1)} //注意是全局的window.
return this;
}
Foo.getName = function(){console.log(2)}
Foo.prototype.getName = function(){console.log(3)}
var getName = function(){console.log(4)}
function getName(){
console.log(5)
}
Foo.getName(); //2
getName(); //4
Foo().getName(); //1
getName(); //1
new Foo().getName(); //3
扩展:
写一个方法,让promiseList中的promise按顺序执行,不允许使用async,await,generator
const promise1 = () => Promise.resolve(1)
const promise2 = () => {
new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 3000)
})
}
const promise3 = () => {
new Promise(resolve => {
setTimeout(() => {
resolve(3)
}, 2000)
})
}
const promiseList = [promise1, promise2, promise3]
2022.02.18
79.深拷贝和浅拷贝有几种实现方式,分别举例
讲的全的一篇文章:www.lucklnk.com/godaddy/det…
80.如何避免回流与重绘?
css中
- 尽可能在DOM树的最末端改变class
- 避免设置多层内联样式
- 动画效果用到position属性为absolute或fixed的元素上
- 避免使用table布局
- 使用css3硬件加速,可以让transform、opacity、filters等动画效果不会引起重排重绘 js中
- 使用resize(浏览器窗口大小)、scroll时进行节流防抖处理
- 批量修改元素时,可以先让元素脱离文档流,等修改完毕后再放入文档流
- 在获取offsetWidth这类属性值时,可以将使用变量将查询的结果储存起来,避免多次查询
81.如何减少 Webpack 打包体积
82.GET方法URL长度限制的原因
在Http1.1协议中并没有提出针对URL的长度进行限制,RFC协议里面是这样描述的,HTTP协议并不对URI的长度做任何的限制,服务器端 必须能够处理任何它们所提供服务多能接受的URI,并且能够处理无限长度的URI,如果服务器不能处理过长的URI,那么应该返回414状态码, 虽然Http协议规定了,但是Web服务器和浏览器对URI都有自己的长度限制。此外,对URL的长度进行限制可以防止有人恶意使用大数据频繁发请求攻击服务器
2022.02.21
83.思考题
flex-basis表示在flex items 被放入flex容器之前的大小,也就是items的理想或者假设大小,但是并不是其真实大小,其真实大小取决于flex容器的宽度,flex items的min-width,max-width等其他样式
答案:50 250 50 300
分析:
item1
: 当flex-basis和width属性同时存在时,width属性不生效,flex item的宽度为flex-basis设置的宽度
item2
: flex
属性由flex-grow、flex-shrink、flex-basis
组成,默认值为 0 1 auto,它会覆盖设置的flex-basis值。
item3
: max-width决定了flex items的最大宽度
item4
: min-width决定了flex items的最小宽度,flex-basis会覆盖flex里的flex-basis值,此处相当于flex: 0 1 50px
,浏览器预留了50px位置,剩余空间均等分配500/2 = 250px,最后item4为300px
84.说一下slice splice split的区别和作用
split
- 将字符串分割成数组
- 语法:
str.split([separator, limit])
,参数:分割符、返回数组的最长长度 - 返回值为一个数组
let str = 'abcd d'
console.log(str.split()) // ['abcd d']
console.log(str.split(' ')) // ['abcd', 'd]
console.log(str.split('')) // ['a', 'b', 'c', 'd', ' ', 'd']
splice
- 添加或删除元素,返回删除的元素
- 会改变原数组
- 语法:
array.splice(start[,num, item1...itemN)
// 删除,第二个参数可选,没有则删除第一个参数位置以后所有的元素,为0则不删除
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.splice(2,1)
console.log(arr)// ['a', 'b', 'd']
console.log(arr1)// ['c']
// 新增
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.splice(2, 0, 'h')
console.log(arr)// ['a', 'b','h', 'c', 'd']
console.log(arr1)// []
slice
- 返回被选中的元素
- 不改变原数组
- 语法:
array.slice([start,end])
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice()
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['a', 'b', 'c', 'd']
// 1位置开始,2位置结束,但不包括2
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice(1,2)
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['a', 'c', 'd']
let arr = ['a', 'b', 'c', 'd']
let arr1 = arr.slice(1)
console.log(arr)// ['a', 'b', 'c', 'd']
console.log(arr1)// ['b', 'c', 'd']
85.vue如何检测数组变化
vue内部没有通过Object.defineProperty
去对数组进行观测,因为消耗严重,而是通过重写数组的7个方法,并对数组中不是基本数据类型的进行检测,重写的方法是:push shift pop unshift reverse sort splice
基本思路:
1.在对数据进行检测的时候,判断对象类型,根据添加的__ob__
属性判断数据是否被观测
export function observe (data) {
//如果是对象类型才观测
if(!isObject(data)) {
return
}
//判断data有没有被观测
if(data.__ob__) {
return
}
return new Observer(data)
}
Observer
类中的判断
class Observer{
constructor(data) {//对对象中的所有属性进行劫持
Object.defineProperty(data, '__ob__', {
value: this,
enumerable: false //不可枚举
})
// data.__ob__ = this //所有被劫持过的属性都有__ob__
if(Array.isArray(data)) {
//数组劫持,对数组原来方法改写,切片编程
data.__proto__ = arrayMethods
//如果数组中的数据是对象类型,需要监控对象的变化
this.observeArray(data)
}else {
this.walk(data) //对象劫持
}
}
observeArray(data) { //对数组中的数组和数组中的对象再次劫持
data.forEach(item => {
observe(item)
})
}
walk(data) {//传入的对象
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
}
2.获取数组原型
3.新建一个对象继承数组方法
4.重写数组的七个方法。
5.将新增的数组继续劫持进行观测
6.返回方法执行结果
let oldArrayPrototype = Array.prototype
// 继承数组方法(保留原有数组功能)
export let arrayMethods = Object.create(oldArrayPrototype)
// arrayMethods.__proto__ = Array.prototype
//只有这7个可以改变原数组
let methods = [
'push',
'shift',
'unshift',
'pop',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
// 用户调用的如果是以上七个方法,会用重写的,负责调用原来的数组方法
arrayMethods[method] = function (...args) {
// 此处this是数组
oldArrayPrototypep[method].call(this, ...args)
let inserted
let ob = this.__ob__//根据当前数组获取observe实例
switch(method){
case 'push':
case 'unshift':
inserted = args // 就是新增的内容
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
// 如果有新增的内容要继续劫持 需要观测数组每一项,而不是数组
if(inserted) ob.observeArray(inserted)
}
})
2022.02.22
86. 用webpack怎么做性能优化?
看第48题
87. 从地址栏输入url到显示页面都发生了什么?
看第7题
88. 说一说你们项目Vue后台管理系统怎么做的路由权限和按钮权限?
两种场景:
动态路由:www.cnblogs.com/suntongxue/…
指令形式:www.jianshu.com/p/c33996350…
2022.02.23
89.谈谈对window.requestAnimationFrame
的理解
requestAnimationFrame 方法会告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用回调函数来更新动画。
优点:
1、requestAnimationFrame 自带函数节流功能,采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间的过短,造成过度绘制,增加页面开销,也不会因为间隔时间过长,造成动画卡顿,不流程,影响页面美观。
浏览器的重绘频率一般会和显示器的刷新率保持同步。大多数采用 W3C规范,浏览器的渲染页面的标准频率也为 60 FPS(frames/per second)即每秒重绘60次,requestAnimationFrame的基本思想是 让页面重绘的频率和刷新频率保持同步,即每 1000ms / 60 = 16.7ms执行一次。
通过 requestAnimationFrame 调用回调函数引起的页面重绘或回流的时间间隔和显示器的刷新时间间隔相同。所以 requestAnimationFrame 不需要像 setTimeout 那样传递时间间隔,而是浏览器通过系统获取并使用显示器刷新频率。例如在某些高频事件(resize,scroll 等)中,使用 requestAnimationFrame 可以防止在一个刷新间隔内发生多次函数执行,这样保证了流程度,也节省了开销
2、该函数的延时效果是精确的,没有setTimeout或setInterval不准的情况(JS是单线程的,setTimeout 任务被放进异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列的任务是否需要开始执行,造成时间延时)。
setTimeout的执行只是在内存中对图像属性进行改变,这个改变必须要等到下次浏览器重绘时才会被更新到屏幕上。如果和屏幕刷新步调不一致,就可能导致中间某些帧的操作被跨越过去,直接更新下下一帧的图像。即 掉帧
使用 requestAnimationFrame 执行动画,最大优势是能保证回调函数在屏幕每一次刷新间隔中只被执行一次,这样就不会引起丢帧,动画也就不会卡顿
3、节省资源,节省开销
在之前介绍requestAnimationFrame执行过程,我们知道只有当页面激活的状态下,页面刷新任务才会开始,才执行 requestAnimationFrame,当页面隐藏或最小化时,会被暂停,页面显示,会继续执行。节省了 CPU 开销。
注意:当页面被隐藏或最小化时,定时器setTimeout仍在后台执行动画任务,此时刷新动画是完全没有意义的(实际上 FireFox/Chrome 浏览器对定时器做了优化:页面闲置时,如果时间间隔小于 1000ms,则停止定时器,与requestAnimationFrame行为类似。如果时间间隔>=1000ms,定时器依然在后台执行)
4、能够在动画流刷新之后执行,即上一个动画流会完整执行
90.谈谈对以下几个页面生命周期事件的理解:DOMContentLoaded,load,beforeunload,unload
DOMContentLoaded
— 浏览器已经完全加载了HTML,DOM树已经构建完毕,但是像是<img>
和样式表等外部资源可能并没有下载完毕。load
— 浏览器已经加载了所有的资源(图像,样式表等)。beforeunload/unload
-- 当用户离开页面的时候触发。
91. vue/react常见的性能优化方案
2022.02.24
92.你在项目中有自己封装过组件么, 请列举下你使用过的属性,并说明作用.
开放题,结合自身项目回答
93.简述下深拷贝,浅拷贝的方法
94.简述EventLoop执行顺序
事件循环从宏任务队列开始,一开始宏任务队列中只有一个script(整体代码)任务,遇到任务源时,分发到相应的任务队列中.异步任务可分为task和micrtask两类(requestAnimationFrame既不属于macrotask,也不属于micrtask),不同的API注册的异步任务会依次进入自身对应的队列中,然后等待event loop 将他们依次压入执行栈中执行.执行栈执行完同步任务后,检查执行栈是否为空,如果为空,检查微任务队列是否为空,如果微任务队列不为空,则一次性执行完所有的微任务.如果微任务为空,则执行下一个宏任务。每次单个宏任务执行完之后,都会检查微任务队列是否为空,如果不为空,则会按照先进先出的方式执行完所有的微任务,然后执行下一个宏任务,以此循环。每次宏任务产生的微任务队列都是新创建的 宏任务队列只有一个.
95.axios 和Ajax 本质的区别是什么
axios是通过 promise 实现对 ajax 技术的一种封装。就像 ajax 是 通过 jQuery 来封装一样。 也就是说,jQuery 将请求技术进行了封装 变成了 ajax , 而 通过 promise 又把 ajax 进行封装就成了 axios。
2022.02.25
96. 说一下冒泡排序和快速排序
冒泡排序: 重复遍历要排序的元素列,依次比较两个相邻的元素,前一个元素若比后一个元素大则互换位置。以升序排序为例,最大的元素会在第一次遍历后“冒泡”到数组的末端。假如数组长度为n,在n-1次遍历后可完成排序。 实现:
let arr = [1, 5, 2, 9, 7, 4, 2, 3, 6, 8]
function bubbleSort(arr) {
let time = arr.length - 1
while (time) {
let i = 0
while (i<time) {
if (arr[i] > arr[i+1]) [arr[i], arr[i+1]] = [arr[i+1], arr[i]]
i ++
}
time --
}
}
bubbleSort(arr)
快速排序: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
实现:
let arr = [1, 5, 2, 9, 7, 4, 2, 3, 6, 8]
function quickSort(arr) {
if (arr.length <= 1) return arr
let pivotVal = arr[0],
smallers = [],
biggers = [],
idx = 1
while (idx < arr.length) {
if (pivotVal > arr[idx]) {
smallers.push(arr[idx])
} else {
biggers.push(arr[idx])
}
idx ++
}
return quickSort(smallers).concat(pivotVal, quickSort(biggers))
}
quickSort(arr)
97. localstorage、sessionstorage、cookie的区别
- cookie: 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。
- sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。
- localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。
98. 说出下面打印的结果
console.log( true + 1 ); //2
console.log( 'name'+true ); //nametrue
console.log( undefined + 1 ); // NaN
console.log( typeof undefined ); //undefined
console.log( typeof (undefined+1) ); // number
console.log( typeof NaN ); //number
console.log( typeof null ); //object
2022.02.28
99.webpack 工作流程
Webpack 的运⾏流程是⼀个串⾏的过程,从启动到结束会依次执⾏以下流程:
- 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;
- 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编译;
- 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;
- 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,再找出该模块依赖的模块,再递归本步骤直到所有⼊⼝依赖的⽂件都经过了本步骤的处理;
- 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。
在以上过程中,Webpack 会在特定的时间点⼴播出特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并且插件可以调⽤ Webpack 提供的 API 改变 Webpack 的运⾏结果。
100.React setState 过程
101.Vue 数据绑定原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。