一、HTML
和CSS
相关
1、src
和href
的区别
src
指向的资源,一般会嵌入到页面中,所以会阻塞式的进行下载;
href
指向的资源,是并行下载的
2、BFC
是什么
BFC
就是一块独立的布局空间,该空间的布局,不会影响空间外部的布局。
所以可以利用BFC
来解决坍塌、重合等问题。
可以使用浮动、绝对/相对定位等方法来将一块区域变为BFC
。
3、如何实现水平垂直居中布局
绝对定位
如果子元素,有固定的宽高:
.class {
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
绝对定位
如果子元素没有固定宽高,则使用百分比移动,然后使用过渡:
.class {
position: absolute;
top: 50%;
left: 50%;
transform: translate(50%, 50%);
}
flex
.class {
display: flex;
justify-content: center;
align-items: center;
}
inline-block
将子元素设置为inline-block
,然后父元素设置内容居中
.container {
line-height: 100px;
text-align: center;
}
.class {
display: inline-block;
}
二、JavaScript
基础
1、关于new
操作符
1.1 构造函数调用时,判断是否使用了new
操作符
如果是通过new
来调用的函数,在函数内部会有一个对象new.target
,可以通过是否有这个,来进行判断。
1.2 new
操作符的实现过程
new
操作符,用于创建一个类型的实例。使用了new
操作符的构造函数,会执行以下步骤:
- 使用当前构造函数的原型,来创建一个对象
- 将该对象,绑定为构造函数的上下文
- 然后执行构造函数,给对象进行初始化
- 最后返回构造函数的
this
。如果构造函数手动返回了对象,则会覆盖掉this
。
1.3 手写一个new
函数,能实现new
操作符的功能
function Base() {
this.name = '基类'
}
//打印标准的实例
const testInstance = new Base()
console.log(testInstance)
// 模拟new的函数
function handleNew(cls) {
const instance = Object.create(cls.prototype);
cls.call(instance)
return instance
}
// 测试实例
const newInstance = handleNew(Base)
console.log(newInstance)
// 判断类型
console.log(newInstance instanceof Base)
2、变量相关let
,const
,var
2.1 var
历史语法。
在执行环境的活动对象上进行取值,未声明或已声明未赋值,都是undefined
。 所以表现为变量声明后,代码提升到了作用域顶部,只不过值是undefined
。
同理,使用function
声明的函数,也会放在活动对象上,所以在任何地方声明后,都可以调用。
如果一个变量,即使用了var
声明,也使用了function
声明,那么在代码刚刚开始执行的时候,这个变量的值就是函数。
2.2 let
和const
- 只在块级作用域生效
- 暂时性死区,也就是在作用域里,只有声明之后才能使用
- 不允许重复声明
const
声明的变量,指针固定,所以基本类型的数据不可以修改;引用类型的数据可以更改堆内容
2.3 作用域和作用域链
作用域,就是执行栈中,一个执行环境的可访问行。
一个执行环境,包含了互动对象,作用域链等部分。
执行栈顶部的执行环境,作用域链中会包含执行栈底部环境的作用域链,这样在顶部的执行环境,就能完成沿着作用域链的变量查找。
2.4 闭包
当一个执行环境中,使用了另外一个执行环境的变量,造成另外的执行环境无法释放内存的情况,就是闭包。
就是函数,和其上下文,形成了绑定的引用,可以用于匿名自执行函数、结果缓存等。
3、数组
数组相关的API:
Array.from
:将可迭代对象,转为数组keys
,values
,entries
:将数组的内容,包装为一个迭代器返回,可以用for...of
等方法进行迭代flat
:拉平数组,可以传入参数,代表拉平的层数,如果全部拉平,可以传入Infinity
fill
:使用一个值,填充数组。如果是对象,则数组所有元素持有的是一个对象filter
,map
:返回数组元素的浅拷贝,对数组的每一个元素,都调用一个回调函数pop
,push
,shift
,unshift
:对数组元素进行操作,返回数组长度slice(start,end)
:返回指定区间的数组元素的浅拷贝splice(start,count,items)
:原地更换数组数据,并将切片出来的数据,进行返回sort
排序,回调返回负数,交换位置;所以a-b
是从小到大排列at
:返回固定索引的元素,可以为负数Array.isArray
:用来判断是否为数组
4、箭头函数
- 内部没有this,this是从作用域链中查找到的上一层执行栈
- 都是匿名函数
- 没有原型对象,也没有super关键字,所以不可以当作构造函数,
new
的话会报错 - 可以解决内部函数的this指向问题。因为内部函数直接调用时,this是指向全局,而不是当前函数作用域的this,除非使用
bind
,call
等方法解决。利用箭头函数可以解决
5、this
的指向
5.1 this
指向
- 默认绑定:函数没有调用上下文,直接执行,this绑定到全局
- 隐式绑定:有调用上下文,例如通过对象调用,this绑定到上下文
- 显式绑定:通过api进行强制绑定,例如
bind
,call
等 new
操作符:this就是new
时新建的那个对象
5.2 bind
,call
bind
:用于永久的更改函数执行时的this,该函数会返回一个新的函数call
:临时绑定this来执行函数
6、原型
原型,用于实现类型,和类型之间的继承。
6.1 原型的指向
原型对象,就是构造函数的一个属性,构造函数的prototype
属性,指向构造函数的原型对象。
当使用构造函数来实例化一个对象的时候,会在实例化对象上,增加一个__proto__
属性,该属性也指向原型对象。
原型对象,用于给实例对象提供通用的属性和方法,使某个构造函数的多个实例化对象拥有相同功能的方法。当在实例化对象上进行属性或方法的查找时,如果未发现该属性或方法,就会沿着__proto
进入到原型对象中进行查找。
instanceof
操作符就是来判断某个构造函数,是否能在一个实例化对象的原型链中查询到,从而判断该对象是否属于这个类型。
6.2 继承
在ES5
中,需要基于原型,手动实现类型的继承:
// 声明 父类
function BaseCls() {
this.name = '父类';
}
//声明子类
function ChildCls() {
BaseCls.call(this);
this.name = "子类";
}
// 实现原型继承
ChildCls.prototype = Object.create(BaseCls.prototype)
6.3 判断类型
使用内置的函数Object.toString.call(obj)
可以判断一个对象,是否为内建类型的实例。
例如:
Object.prototype.toString.call('') // [object String]
Object.prototype.toString.call(new Date()) // [object Date]
但是,无法判断是否为自定义的引用类型。
7、==
和 ===
7.1 ==
相等
相等判断比较宽松
- 如果是比较两个引用类型,就比较指针
- 如果是比较两个基本类型,会触发类型隐式转换
- 一个基本类型,和一个引用类型,会调用引用类型的
toString
方法,转为基本类型 - 对于
null
和undefined
,对上null
或undefined
都为true - 基础类型隐式转换,有string先转string,没有的话判断number,转number
7.2 ===
严格相等
不会进行类型转换,直接进行类型和值的校验
7.3 Object.is
相当于加强版的严格相等,其中-0和0不相等;NaN
和NaN
相等
8、for...in
和for...of
8.1 for...of
在可迭代对象上,创建一个迭代。
可迭代对象包括Array
, String
, Map
等
8.2 for...in
在任意的一个对象上,迭代其可枚举属性,包括继承的可枚举属性。
9、函数
9.1 函数柯里化
函数柯里化,就是函数返回一个可执行的函数,也是闭包的经典用法。
9.2 防抖
多次触发,只执行一个函数,用于屏蔽频繁操作
9.3 节流
稀释函数执行的次数,功能实现类似于防抖
JavaScript实现:
let status = 1; // 0,1
let timer = null;
function fn() {
if (status === 1) {
console.log('执行')
timer = setTimeout(() => {
status = 0;
timer = clearTimeout(timer)
}, 2000)
} else {
console.log('节流中')
}
}
10、事件
事件冒泡的阶段:事件捕获 -> 目标触发 -> 事件冒泡
阻止事件默认行为:preventDefault
阻止事件冒泡:stopPropagation
11、promise
11.1 promise
介绍
promise
是一种异步编程的解决方案。它是一个对象,来存储异步执行的状态,有进行中、结束、失败三个状态。
promise
在初始化执行的时候,处理所有的操作,然后把异步执行的状态进行存储,并且返回promise
对象。
然后在需要读取promise
对象异步结果的地方,执行then
就能读取成功状态的异步结果。
11.2 async/await
async
就是promise
的语法糖,使用了async
标识的函数,其返回值自动封装为一个promise
对象。
在使用了async
标记的函数内部,通过使用await
来添加一个异步回调操作,也就是说await
就是then
的语法糖。
12、proxy
12.1 proxy代理
proxy代理,就是重载对象的各种操作符,然后执行一些特定的监听处理函数。这些操作符包括.
操作符、迭代等。
proxy的优点:
- 不会对对象进行入侵,只是一层拦截,然后就可以将操作转发到对象上
- 重载了对象的所有操作符,包括
.
,in
,delete
,索引等
相比较与原始Vue2中使用的Object.defineProperty
的缺点:
- 需要对对象的所有属性都设置存取器才能实现操作拦截
- 部分操作无法进行拦截,例如数组的索引操作,这时就需要重写数组的操作方法
- 未来该API会被删除掉,所有操作都以标准化的
reflect
对象进行操作
12.2 reflect
reflect也是标准的对象操作API,将所有的对象操作,进行标准化的耦合。
并且,reflect对象在使用get和set处理函数的时候,可以给函数设置this,能解决部分边界情况造成的this丢失
三、Vue
相关
1、vue-router
是如何监听url变化的
对于使用了hash
模式的路由来说,当url变化时,是不会重新请求后端的。
所以这时候就需要使用原生的监听模式onhashchange
来监听url是否发生了变化
如果是使用了history
模式的路由,当路由变化时,不能直接刷新url,而是需要使用原生的history
API来 将要变更的url加入到url栈中,以此来实现页面不刷新。但是在此情况下,需要后端进行支持,否则后端url会报错。
2、Vue的组件生命周期
- setup:
- beforCreate
- init Options API
- created
- complate template
- beforeMount
- init render, create & insert dom
- mounted
- beforeUpdate
- patch
- updated
- beforeUnmount
- unmounted
3、Vue-router
的路由守卫
- 导航触发
- 失活的组件里调用
beforeRouteLeave
- 调用全局前置守卫
beforeEach
- 重用的组件里调用
beforeRouteUpdate
- 然后在路由配置的页面里,调用
beforeEnter
- 然后解析组件
- 在被激活的组件里,调用
beforeRouteEnter
- 全局解析后的守卫
beforeResolve
- 导航成功
- 全局后置钩子,注意,不是守卫
afterEach