前端常见的面试流程
一面(基础知识)
JS基础知识
框架基本使用
二面(高级特性+原理)
- 框架高级特性
- 框架原理
三面(设计+经验)
- 项目设计能力
- 工作经验和环境
HTML
1.如何理解HTML语义化?
- 让人更容易读懂(增加代码的可读性)
- 让搜索引擎更容易读懂(SEO)
2. 块状元素&内联元素
- display: block/table; 元素独占一行 (div,h1~6,table,ul,ol,p)
- display:inline/inline-block; 元素不独占一行 (span,image,input,button)等
CSS
布局相关问题
- 盒子模型宽度计算?
- margin 纵向重叠的问题?
- margin负值?
- BFC 理解
- float布局,以及clearfix
- flex布局
1. 盒模型宽度的计算
<!-- 如下代码,请问 div1 的 offsetWidth 是多大? -->
#div1{
width:100px;
padding:10px;
border: 1px solid #ccc;
margin: 10px;
}
offsetWidth = width + padding-top/bottom + border-top/bottom = 100 + 10*2 +1*2 = 122px
// 如果让 offsetWidth = 100px 如何做?
box-sizing: border-box;
2. margin纵向重叠
- 相邻元素的 margin-top 和 margin-bottom 会发生重叠
- 空白内容的也会重叠
- 下面代码的最终 margin = 15px
// margin = 15px
p{
font-size: 16px;
line-height: 1;
margin-top:10px;
margin-bottom:15px;
}
<p>AAAAA</p>
<p>BBBBB</p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
3. margin 负值(<0的值)
- 对margin的top,left,right,bootom设置负值,有何结果?
- margin-top 和 margin-left 负值,元素向上、向左移动
- margin-right 负值,右侧元素左移,自身不受影响
- margin-bottom 负值,下方元素上移,自身不受影响
4. BFC理解应用?
-
BFC – Block format context 块级格式化上下文
-
一块独立渲染区域,内部元素的渲染不会影响外界元素
形成 BFC 的常见条件
- float 不是 none
- position 是 absolute 或 fixed
- overflow 不是 visible
- display 是 flex inline-block等
用途
- 清除浮动
5. float布局
-
如何实现圣杯布局和双飞翼布局?
-
圣杯布局和双飞翼布局目的
- 三栏目布局:中间一栏最先加载和渲染(内容重要)
- 两侧的内容固定,中间内容随着宽度自适应
- 一般用于PC网页
-
技术总结
- 使用 float
- 两侧使用 margin 负值,以便中间内容横向重叠
- 防止中间内容被两侧覆盖,一个用padding 一个用margin
-
-
手写 clearfix
.clearfix:after{ display:block; content:''; clear:both; } .clearfix{ *zoom:1; // 兼容低版本 }
6. flex布局
-
flex实现一个三点色子
<!-- 父元素 --> flex-direction justify-content align-items flex-wrap <!-- 子元素 --> align-self
.box{
display: flex;
justify-content: space-between; /*两端对齐*/
}
.item{
/*背景色、大小、边框*/
}
.item:nth-child(2){
align-self: center; /*第二项居中对其*/
}
.item:nth-child(3){
align-self: flex-end; /*第三项尾对齐*/
}
定位
1. absolute 和 relative 分别依据什么定位?
- relative 依据自身定位
- absolute 依据最近一层的定位元素定位
- absulote relative fixed
- body
2. 居中对其有哪些实现方式?
- 水平居中
-
inline 元素:
text-align:center
-
block 元素:
margin :auto
-
absolute 元素
left:50% + margin-left 负值(必须知道子元素的尺寸)
- 垂直居中
-
inline 元素:
line-height 的值等于 height
-
block 元素:
margin :auto
-
absolute 元素
-
top:50% + margin-top 子元素的一半负值 (必须知道子元素的尺寸)
-
通过 transform(-50%,-50%) CSS3的属性(不知道子元素的宽高)
top:50%; transform:translate(-50%,-50%) // 只写垂直方向的 transform:translateY(-50%)
-
top,left,bootom,right = 0 + margin:auto (不需要知道子元素的宽高)浏览器兼容性好
-
图文样式
- line-height 如何继承?
-
写具体数值,如 30px,则继承改值
-
写比例,如 2/1.5, 则继承该比例
-
百分比:
-
子元素继承父元素的
// P 标签的行高是 40px body{ font-size: 20px; line-height: 200%; } p{ font-size: 16px; } <body> <p>这是一行文字</p> </body>
响应式
rem是什么?
rem是一个长度单位。r 代表的是 root
- px ,是绝对长度单位,最常用
- em, 相对长度单位, 相对于父元素, 不常用
- rem , 相对长队单位, 相对于根元素,常用语响应式布局
body{
font-size: 100px;
}
p{
font-size: 0.16rem; // = 16px
}
响应式布局的实现方案 – media?
-
media-query,根据不同的屏幕宽度设置根元素 font-size
-
rem, 基于根元素相对单位
// 一个项目一般只写一次
@media {}
## JS
// 问题:
- typeof能判断哪些类型?
- 何时使用 === 何时使用 ==
- window.onload 和 DOMContentLoaded的区别?
- JS创建 10个a标签,点击的时候弹出对应的序号
- 手写节流、防抖
- Promise解决了什么问题?
// 思考问题的结论
- 考点
- 不变应万变(题不变,考点不变)
- 题目到知识点,再到题目
### 知识模块一:基础知识
### 1. 变量类型和计算
>常见题目:
>
>- typeof能判断哪些类型?
>
> 值类型:number,undefinde,string,symbol,boolen, [object,array,function]---object
>
>- 何时使用 === 何时使用 ==?
>
> 除了 null 和 undefined 外其它都有 ===
>
>- 值类型和引用类型的区别?
>
> let obj1 = {x:100}
>
> let obj2 = obj1
>
> let x1 = obj1.x // 这块执行的是值类型的赋值
>
> obj2.x = 101
>
> x1 = 102
>
> console.log(obj1) // { x: 101 }
>
>- 手写深拷贝
#### 值类型 vs 引用类型
```js
// 值类型: undefined 、number、string、symbol、boolen
let a = 100
let b = a
a = 200
console.log(b) // 100
// 引用类型:array、object、null(特殊引用类型,指针指向空地址)、函数
let a = { age: 20}
let a = b
b.age = 21
console.log(a.age) // 21
判断 typeof 运算符
-
识别出所有的值类型
-
识别函数
-
判断是否是引用类型(不可再细分)
type 判断所有的值类型( \ 函数 \ 引用类型 ) | ||
---|---|---|
let a | typeof a | undefined |
const str=‘abc’ | typeof str | string |
const n = 100 | typeof n | number |
const b =true | typeof b | boolean |
const s = Symbol(‘a’) | typeof s | symbol |
typeof function() {} | 能识别引用类型但不能继续识别 | object |
typeof null | 能识别引用类型但不能继续识别 | object |
typeof [‘a’,‘b’] | 能识别引用类型但不能继续识别 | object |
typeof {x:100} | 能识别引用类型但不能继续识别 | object |
深拷贝
const obj = {
age:20,
address:{
city:'beijing'
},
arr:['read','write']
}
const obj2 = obj1; // 浅拷贝
const obj2 = deepClone(obj1); // 深拷贝
/**
* 深拷贝
* @param obj 要拷贝的对象
*/
function deepClone(obj={}){
// 不是对象或数组,直接返回
if(typeof(obj) != 'object' || obj == null){
return obj
}
// 初始化返回结果
let result
if ( obj instanceof Array) {
result = []
} else {
result = {}
}
if ( let key in obj) {
// 保证 key 不是原型的属性
if(obj.hasOwnProperty(key)){
// 递归调用 !!!!
result[key] = deepClone(obj[key])
}
}
return result
}
// 方法二
function deepClone2(arr){
return JSON.parse(JSON.stringfy(arr))
}
变量计算 - 类型转换
-
字符串拼接
const a = 100 + 10 // 110 const b = 100 + '10' // 10010 const c = true + '10' // 'true10'
-
==
- 除了 == null | undefined之外,其它一律用 ===
// 变量先转换再比较 100 == '100' // true 0 == '' //true 0 == false //true false == '' //true null == undefined // true
-
if 语句和逻辑运算符
-
truly 变量:
!!a === true
-
falsely 变量:
!!b === false
-
逻辑判断
console.log(0 && 10) // 0 console.log('abc' || '') // abc console.log(!window.abc) //true
// 以下是 falsely 变量,其它都是truely 变量 !!0 === false !!NaN === false !!‘’ === false !!null === false !!undefined === false !!false === false -
2. 原型和原型链
ES5 的继承
题目:
如何判断一个变量是不是数组?
arr instanceof Array
class的原型本质,怎么理解?
原型和原型链的图示
属性和方法的执行规则
手写一个简易的JQuery,考虑插件和扩展性
class JQuery {
constructor(selector){
const result = document.querySelectorAll(selector )
const length = result.length
for (let i = 0; i<length; i++){
this[i] = result[i]
}
this.lenght = length
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i<this.length; i++){
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(element=>{
elem.addEventListener(type,fn,false)
})
}
}
// 插件
JQuery.prototype.dialog = function (info) {
console.log(info)
}
// “造轮子”
class myJQuery extends JQuery{
constructor(selector){
super(selector)
}
// 扩展自己的方法
addclass(classname){}
style(data){}
}
class 实现继承
// class 和 class继承
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat`)
}
}
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
}
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`姓名 ${this.name} 教授 ${this.major}`)
}
}
instanceof 判断类型
- Object 是默认所有类的父类
- instanceof 是基于原型链的
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Object // true
理解原型和原型链
《图示和使用规则》
-
原型
- 每个 class 都有显示原型 prototype
- 每个实例都有隐式原型
__proto__
- 实例的隐式原型
__proto__
指向 每个类的显示原型 prototype
xiaoming.__proto__ Student.prototype console.log(xiaoming.__proto__ === Student.prototype) // true
-
原型链
- instanceof 是基于原型链的
- hasOwnProperty()
object.__proto__
=== null
Student.prototype.__proto__ People.prototype Student.prototype.__proto__ === People.prototype
重要提示!!!
- class 是ES6语法规范,有ECMA委员会发布
- ECMA只规定语法规则,即我们代码的书写规范,不确定如何实现
- 以上实现方式都是V8引擎的实现方式,也是主流的
3. 作用域和闭包
题目
this 的不同应用场景,如何取值?
手写 bind,返回一个新的函数。
function fn(){} Function.prototype.bind1 = function(){ // 将参数拆解为数组 const args = Array.prototype.slice.call(arguments) // 获取 this (数组的第一项) const t = args.shift() // fn1.bind(...) 中的 fn1 const self = this // 返回一个函数 return function() { return self.apply(t,args) } }
实际开发中的闭包应用场景,举例说明
- 作为参数
- 作为函数返回值
// 隐藏数据,只提供API // 简单做一个 cache的工具 function createCache(){ const data = {} return { set:function(key,value){ data[key] = value }, get:function(key){ return data[key] } } }
作用域和自由变量
-
全局作用域
-
函数作用域
-
块级作用域(ES6新增 { })
if(x){ let x = 3 } console.log(x) // 报错
let a = 0
function fn1(){
let a1 = 1
function fn2(){
let a2 = 2
function fn3(){
let a3 = 3
return a+a1+a2+a3;
}
fn3()
}
fn2()
}
fn1()
- 自由变量
- 一个变量在当前作用域没有定义,但是却被使用了
- 向上级作用域,一层一层一次寻找,直到找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
闭包
闭包–所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方
作用域应用的特殊情况,有两种表现:
-
函数作为返回值
-
函数作为参数
// 函数作为返回值 function create(){ let a = 100 return function(){ console.log(a) } } const fn = create() const a = 200 fn() // 100 // 函数作为参数 function print(fn){ let b = 200 fn() } let b = 100 function fn(){ console.log(b) } print(fn) // 100 【闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方】
4. this的几种情况
this取什么值是在函数执行的时候确认的,不是在定义的时候确认的
- 作为普通函数 : window
- 使用 call \ apply \ bind
- 作为对象方法被调用 :this 是对象
- 在 class 方法中调用 : 实例本身
- 箭头函数 : 自己本身不会有this的值,取它上级作用域的值
- setTimeout 中指向 window
- 原型中的 this: 有时会出现undefined , 因为会当做对象本身执行
xialuo.__proto__.sayHi()
xialuo.__proto__会作为对象
function fn1(){
console.log(this)
}
fn1()// window
fn1.call({x:100}) // {x:100}
const fn2 = fn1.bind({x:200})
fn2() // {x:200} bind会返回一个新的函数再重新执行返回的函数
5. 异步和单线程
题目:
同步和异步的区别是什么?
手写 Promise 加载一张图片
const url = '' function loadImg(src){ const p = new Promise((resolve,reject)=>{ const img = document.creteElement('img') img.onload = ()=>{ resolve(img) } img.onerror = ()=>{ const err = new Error(`图片加载失败 ${src}`) reject(err) } img.src = src } ) return p; } loadImg(url).then(img1=>{ return img //返回普通对象 }).then(img1=>{ return loadImg(url2) // 返回 promise }).then(img2=>{ }).catch(err=>console.log(err))
前端使用异步的场景有哪些?
- 等待的场景:网络请求 & 定时任务
单线程和异步
- JS是单线程语言,只能同时做一件事情
- 浏览器和 nodejs 已支持 JS 启动进程,如 web worker
- JS 和 DOM 渲染共用同一个线程,因为JS可以修改DOM结构
- 遇到等待(网路请求,定时任务)不能卡住
- 需要异步
- 回调 callback 的形式
异步和同步
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
异步在前端的使用场景
-
网络请求,如 ajax图片加载
-
定时任务,如 setTimeout
// 网络请求 console.log('start') $.get('./data.json',function(data){ console.log('loaded') }) console.log('end') // 图片加载 console.log('start') let img = document.creteElement('img') img.onload = function (){ console.log('loaded') } img.src = '/xxx.png' console.log('end') // 定时器 setTimeout(function(){console.log(111)},1000) setInterval(function(){console.log(111)},1000)
callback hell 和 Promise
// callback hell
$.get(url1,(data1)=>{
$.get(url2,(data2)=>{
$.get(url3,(data3)=>{
})
})
})
// promise
function getData(){
return new Promise((resove,reject)=>{
$.ajax({
url,
success(data){
resove()
},
error(err){
reject(err)
}
})
})
}
const url1 = '';
const url2 = '';
const url3 = '';
getData(url1).then(data1=>{
console.log(data1)
return getData(url2)
}).then(data2=>{
console.log(data2)
return getData(url3)
}).then(data3=>{
console.log(data3)
}).catch(err => console.error(err))
知识模块二
- JS基础知识,规定的是语法(ECMA 262 标准)
- JS Web API ,网页操作的API(W3C标准)
- 前者是后者的基础,两者结合才能真正实际应用
6. JS-Web-API-DOM / JS-Web-API-BOM
6-1 DOM
题目
DOM 是那种数据结构?
树
常用 API
- 节点操作
- 结构操作
- attr / property
attribute vs property
- property :修改对象属性,不会体现到 html 结构中
- attribute : 修改 html 属性,会改变 html 的结构
- attributes 和 property 都可能影响DOM结构的重新渲染
- 尽量使用 property
《Document Object Model》
- vue 和 react 矿建应用广泛,封装了DOM操作
- 但 DOM操作一直都是前端工程师的基础、必备知识
- 只会vue而不懂DOM操作的前端程序员,不会太长久
DOM 本质
- 前身 <?xml>
- 一棵树
DOM 节点操作
-
attributes 和 property 都可能影响DOM结构的重新渲染
-
attribute: 修改 html 属性,会改变 html 的结构
- p.getAttribute(‘data-name’)
- p.setAttribute(‘data-name’,‘icmmo’)
-
property 形式:修改对象属性,不会体现到 html 结构中
- nodeName
- nodeType
- style.[height|width]
- className
-
获取DOM节点
- document.getElementById
- document.getElementsByClassName // 集合
- document.getElementsByTagName // 集合
- document.querSelectorAll // 集合 |选择器
- document.querySelector
DOM 结构操作
《DOM属于那种数据结构》
-
新增、插入 节点
-
获取子节点列表,获取父节点
-
删除子节点
// 新增、插入 const div1 = document.getElementById('div1') const p1 = document.createElement('p') div1.appendChild(p1) // 对现有节点重新插入的话,会移动 const div2 = document.getElementById('div2') const p1 = document.getElementById('p1') div2.appendChild(p1) p1.parentNode const div1ChildNodes = div1.childNodes const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child=>{ if(child.nodeType === 1) return true return false }) // 删除 div1.removeChild(div1ChildNodesP[0])
DOM 性能操作的性能优化
-
DOM 操作非常 ”昂贵“, 避免频繁操作 DOM 操作
-
对 DOM 查询做缓存
-
将频繁操作改为一次性操作
// 创建一个文档片段,此时还没有插入到 DOM 树中 const frag = document.createDocumentFrgment()
6-2 BOM
题目
- 如何识别浏览器的类型?
- 分析拆解 url 各个部分
Browser Object Model
navigator 浏览器信息
- userAgent 查看当前浏览器的信息 , 简称
ua
screen 屏幕
- width
- height
location 地址
- location.href
- location.protocol
- localtion.host
- location.search
- location.hash # 后面内容
- location.pathname
history
- history.back()
- history.forward()
8. JS-Web-API-事件
题目:
编写一个通用的事件监听函数
function bindEvent( elem, type, selector, fn){ if ( fn == null ){ // 三个参是代理 fn = selector selector = null } elem.addEventListener(type,event =>{ const target = event.target if(selector){ // 代理 if(target.matches(selector)){ fn.call(target, event) } }else{ // 普通绑定 fn.call(target, event) } }) }
描述事件冒泡的流程
基于DOM树形结构
事件会顺着触发元素网上冒泡
应用场景:代理
无限下拉的图片列表,如何监听每个图片的点击?
- 事件代理
- 用 e.target 获取触发元素
- 用 matche 来判断是否触发元素
事件绑定
// 通用绑定事件绑定函数
function bindEvent( elem, type,fn){
elem.addEventListener(type,fn)
}
const btn = document.getElementById('btn1')
bindEvent(btn,'click', function(event){
console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为; eg: a 标签的跳转事件
event.stopPropagation() // 阻止事件冒泡
alert('click')
})
事件冒泡
event.preventDefault()
event.stopPropagation()
// 冒泡机制
事件代理
事件代理是在事件冒泡的基础上做的
- 代码简洁
- 减少浏览器内存占用
- 只在父元素上挂事件
- 但是,不要滥用;(瀑布流式的结构)
<div id="div3">
<a href="#">A</a>
<a href="#">B</a>
<a href="#">C</a>
<a href="#">D</a>
</div>
const div3 = document.getElementById('div3')
bindEvent(div3,'click', function(event){
event.preventDefault()
alert(this.innerHTML)
})
9. JS-Web-Ajax
题目
手写一个简易的 ajax
function ajax(url){ const p = new Promise((resolve,reject)=>{ const xhr = new XMLHTTPRequest() xhr.open('GET','/api/test.json',true) xhr.onreadystatechange = function(){ if(xhr.readystate === 4){ if(xhr.state === 200){ resolve(JSON.parse(xhr.responseText)) } } else if(xhr.state === 404){ reject(new Error(`404 not found`)) } } xhr.send(null) }) return p }
跨域的常用实现方式
- JSONP
- CORS 单纯的服务端实现
XMLHttpRequest
- xhr.readyState
- 0 - UNSET 尚未调用 open
- 1- OPENED open 已经被调用
- 2 - HEADERS_RECEIVED send 方法已经被调用,header 已被接收
- 3 - LOADING 下载中,responsText 已经有部分内容
- 4 - DONE 下载完成
- xhr.state http协议的状态码
// get 请求
const xhr = new XMLHttpRequest()
xhr.open("GET","/api",true) // true 是异步的请求
xhr.onreadystatechange = function(){
// 这里的函数异步执行,可参考之前 JS 基础中的异步模块
if(xhr.readyState === 4){
if(xhr.status === 200){
alert(xhr.responseText)
}
}
}
xhr.send(null)
// post
const xhr = new XMLHttpRequest()
xhr.open("POST","/login",true) // true 是异步的请求
xhr.onreadystatechange = function(){
// 这里的函数异步执行,可参考之前 JS 基础中的异步模块
if(xhr.readyState === 4){
if(xhr.status === 200){
alert(xhr.responseText)
}
}
}
const postData = {
name:'xxx',
age:1
}
xhr.send(JSON.stringify(postData)) // 转换成 字符串发送
状态码
- 2xx 表示成功处理请求
- 3xx 重定向;
- 301 永久重定向
- 302 临时重定向
- 304 资源未改变,浏览器用缓存的内容
- 4xx 客户端请求错误
- 404 地址不存在
- 403 没有权限
- 5xx 服务端错误
同源策略和跨域
什么是跨域(同源策略)?
-
ajax 请求时,浏览器要求当前网页和 server 必须同源(安全)
-
同源:协议、域名、端口,三者必须一致
-
防盗链限制是服务器做的
-
加载 css js 可无视同源策略
<img src=跨域的图片地址/>
<link src=跨域的css地址/>
<script src=跨域的js地址></script>
<img/>
图片可用于统计打点,可使用第三方统计服务<link/><script>
可以使用CDN,CDN一般都是外域<script>
可实现JSONP
-
-
跨域
- 所有的跨域,都必须经过 server 端的允许和配合
- 未经 server 端允许就实现跨域,说明浏览器有漏洞,危险新号
JSONP
-
访问 https://xx.com ,服务端一定会返回一个 html 吗?
服务端可以任意动态拼接数据返回,只要复合 html 格式要求
-
同理
<script src="https://xxxxx/getData.js">
只要符合js的格式就可以 -
实现原理
<script>
可以绕过跨域- 服务器可以任意动态凭借数据返回
- 所以,
<script>
就可以获得跨域数据,只要服务端愿意返回就可以
<script> window.callback = function(data){ console.log(data) } </script> <script src = "https://xxxx.com/getData.js?username=xxx&callback=callback"></script> <!-- 将返回 callback({x:1,y:2}) -->
-
Jquery 封装的使用示例
$.ajax({ url:'http://xxxx:8080/x-origin.json', dataType:'jsonp', jsonpCallback:'callback', success:function(data){ console.log(data) } })
CORS – 服务器端设置 http header
// 第二个参数是允许跨域的于名称,不建议直接写“*”
res.setHeader("Access-Control-Allow-Origin", '*' ) // 允许所有跨域网站的响应值
res.setHeader("Access-Control-Allow-Origin", 'www.baidu.com' ) // 跨域网站中仅允许baidu返回的响应者
res.setHeader("Access-Control-Allow-Headers", 'Content-Type, X-Custom-Header' ) // 允许所有跨域网站的响应值
res.setHeader("Access-Control-Allow-Methods", 'POST, GET, HEAD, DELETE' ) // 允许这四个方法
res.setHeader("Access-Control-Allow-Methods", '*' ) // 允许所有方法
// 接收跨域的cookie
res.setHeader("Access-Control-Allow-Credentials", 'true' )
ajax常用的插件
-
jquery
$ajax({ type:'', contentType:'', url:'', data:'', success:function(){}, error:function(){} })
-
fetch 新的api
- fetch(),返回的 Promise不会被标记为 reject
- fetch(), 不会从服务端发送或接收任何 cookies
fetch(url,{}) .then() .then() .catch(e=>{})
-
axios 支持 browers and nodejs
- support promise
10. 存储
题目:描述 cookie、localStorage、vs sessionStorge
容量
易用性
是否跟随 http 发送
Cookie
- 本身用于浏览器和server通讯
- 被 ”借用“ 到本地存储
- 存储最大是 4kb
- http 请求时候需要发送到服务端,增加请求数据量
- 只能用 document.cookie = ‘ ’ 来修改
localStorage && SessionStorage
-
HTML5专门为存储设计,最大存储5M
-
API 简单易用 getItem \ setItem
-
不会随着 http 发送到服务器
-
差异
- localStorage 数据会永久存储,除非代码或手动删除
- sessionStorage 数据只存储于当前会话,浏览器关闭则清空
- localStorage 用的多一些
11. 节流防抖
- applay 的应用
手写节流 throttle
-
拖拽一个元素时,要随时拿到该元素被拖拽的位置
-
直接用 drag 事件,则会频繁触发,很容易导致卡顿
-
节流:无论拖拽速度多快,都会每隔 100ms 触发一次
const div1 = document.getElementById('div1')
let timer = null
div1.addEventListener('drag', function(e){
if(timer){
return
}
timer = setTimeout(()=>{
console.log(offset.X,offset.Y)
// 清空定时器
timer = null
},100)
})
// 节流
function throttle(fn, delay = 500){
let timer = null
return function(){
if(timer){
return
}
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
}
div1.addEventListener('drag', throttle(function(e){
console.log(e.offsetX,e.offsetY)
},200))
手写防抖 debounce
- 监听一个输入框,文字变化后触发 change 事件
- 直接用 keyup事件,会频繁触发 change 事件
- 防抖:用户输入结束或暂停时,才会触发 change 事件
const input1 = document.getElementById('input1')
let timer = null
input1.addEventListener('keyup', function(){
if(timer){
clearTimeOut(timer)
}
timer = setTimeout(()=>{
// 模拟触发 change 事件
console.log(input1.value)
// 清空定时器
timer = null
},500)
})
// 防抖
function debounce(fn, delay = 500){
// timer 在闭包中
let timer = null
return function(){
if(timer){
clearTimeOut(timer)
}
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
}
input1.addEventListener('keyup',debounce(function(e){
console.log(e.target)
console.log(input1.value)
}),600)
框架
Vue 面试题
v-show 和 v-if 的区别?
答:v-show 是通过 css 来控制, v-if 通过vue 本身的机制实现组件动态显示和销毁
组件频繁切换用 v-show, 切换一次后不频繁切换使用
keep-alive 的区别 — 缓存,不会走到 destory 生命周期函数去销毁重新渲染
为何 v-for 中要用 key?
答:
描述 Vue 组件生命周期(有父子组件的情况)
答: 单组件 — 生命周期(创建、更新、销毁)
父子组件 —
Vue 组件如何通讯
答: 父子组件 :属性和$emit触发
组件之间 :触发事件的方式 event. o n ( ) e v e n t . on() event. on()event.emit() event.$off()
vuex的方式
描述组件渲染和更新的过程
双向数据绑定 v-model 的实现原理
React 面试题
- React 组件如何通讯
- JSX 本质是什么
- context 是什么,有何用途?
- shouldComponentUpdate 的用途:性能优化
- 描述 redux 单项数据流
- setState 是同步还是异步?
框架综合应用
- 基于 React 设计一个 todolist (组件结构,redux state 数据结构)
- 基于 Vue 设计一个购物车(组件结构, vuesx state 数据结构)
wevpack 面试题
- 前端代码为何要进行构建和打包?
- module chunk bundle 分别什么意思?有何区别?
- loader 和 plugin 区别?
- webpack 如何实现懒加载?
- webpack 常见性能优化
- babel-runtime 和 babel-polyfill 的区别
如何应对?
- 框架的使用(基本使用,高级特性,周边插件)
- 框架原理(基本原理的了解,热门技术的深度,全面性)
- 框架的实际应用,即设计能力(组件结构,数据结构)
1. Vue
先学vue2再学vue3
-
vue2还会被继续使用,面试还会继续考察
-
vue2的语法,绝大部分会被vue3支持
-
Vue和React越来越接近
- Vue3 Options API 对应 React class Component
- Vue3 Composition API 对应 React Hooks
Vue使用 - 知识点
- 基本组件使用,组件使用 – 常用,必须会
- 高级特性 – 不常用,但是体现深度
- Vuex 和 Vue-router 的使用
自己看文档:
- 行。但是是一种低效方式
- 文档是一个备忘录,给会用的人查阅,并不是入门教程
- 文档全面冗长,细节过多,不能突出考试重点
Vue 基本使用 - 知识点
1. 自己用 vue-cli 创建项目
$vue create project-name
$
2. 指令、插值
-
插值、表达式
-
指令、动态属性
-
v-html: 会有xss 风险, 会覆盖子组件
<template> <p>文本插值:{{message}}</p> <p> JS 表达式 {{ flag? 'yes':'no'}} (只能是表达式,不能是js语法) </p> <p :id="dynamicId"> 动态属性 </p> <p v-html="rawHtml"> <span>有xss风险</span> <span>【注意】使用 v-html 之后,将会覆盖子元素</span> </p> </template> <script> export default { data() { return { message:'ssd', flag: true, rawHtml: '指令- 原始 html <b>加粗</b><i>斜体</i>', dynamicId: `id-${Date.now()}` } } } </script>
3. computed 和 watch
-
computed 有缓存,data不变则不会重新计算
<template> <p> num:{{num}} </p> <p> double1 {{double1}} </p> <input v-modle="double2"/> </template> <script> export default { data() { return { num: 20 } }, computed: { double1() { return this.num*2; }, double2() { get() { return this.num*2; }, set(val) { this.num = val/2; } } } } </script>
-
watch 如何深度监听?
-
watch 监听引用类型,拿不到 oldValue !!!
<template> <p>name:{{name}}</p> <div> <input v-module="name"/> <input v-module="info.city"/> </div> </template> <script> export default{ date(){ name:'xxx', info:{ city:'北京' } }, watch:{ name(val,oldVal){ console.log('watch name', oldVal, val) // 值类型,可以正常拿到 oldVal和 val }, info:{ handler(oldValue, val){ console.log('watch info', oldVal, val)// 引用类型,拿不到 oldVal。因为指针相同,但是已经指向了新的 val }, deep:true // 深度监听 } } } </script>
4. class 和style
-
使用动态属性
-
使用驼峰写法
<template> <p :class="{black:isBlack, yellow:isYellow}"> 使用class类 </p> <p :class="[black,yellow]"> 使用class数组 </p> <p :style="styleData"> 使用class类 </p> </template> <script> export default{ data() { return { isBlack: true, isYellow: true, black: 'black', yellow: 'yellow', styleData: { fontSize: '40px', //转换驼峰写法 color: 'red', backgroundColor:'#ccc' //转换驼峰写法 } } } } </script>
5. 条件渲染
-
v-if \ v-else-if \ v-else 的用法,可使用变量,也可以使用 === 表达式
-
v-if 和 v-show 的区别
-
v-if 和 v-show 的使用场景
-
v-if 判断DOM节点中不渲染
-
v-show DOM 节点中渲染只是不显示
-
v-if 更新的不频繁
-
v-show 切换的比较频繁
<template> <div> <p v-if="type === 'a'">A</p> <p v-else-if="type === 'b'">B</p> <p v-else>other</p> <p v-show="type === 'a'"> A by v-show</p> <p v-show="type === 'b'"> B by v-show </p> </div> </template> <script> export default{ data() { return { type: 'a' } } } </script>
-
6. 循环(列表)渲染
-
如何遍历对象?----- 也可使用 v-for
-
key 的重要性。key 不能乱写(如random或者index)
- 与业务相关的一个值
- key是v-for里边的唯一标识,key可以标识组件的唯一性,为了更好的区别各个组件,key的作用主要是为了高效的更新虚拟DOM
-
v-for 和 v-if 不能一起使用!!!
<template> <div> <ul> <li v-for="(item,index) in listArr" :key="item.id">{{index}} - {{item.id}} - {{item.title}}</li> <li v-for="(val,index,key) in listObj" :key="key">{{index}} - {{key}} - {{val.title}} </li> </ul> </div> </template> <script> export default{ data() { return { listArr: [ {id:'a',title:'标题1'}, {id:'b',title:'标题2'}, ], listObj: { a:{title:'标题1'}, b:{title:'标题2'}, } } } } </script>
7. 事件
-
event 参数,自定义参数
- event 是原生的
- 事件被挂在到当前元素
-
事件修饰符,按键修饰符
<!-- 事件修饰符 --> <!-- 阻止事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThis"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用的事件捕获模式 --> <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis"> ... </div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 事件不是从内部元素触发的 --> <div v-on:click.self="doThat"> ... </div> <!-- 按键修饰符 --> <!-- 即使 Alt 或 Shift 被一同按下时也会触发 --> <button @click.ctrl="onClick"> A</button> <!-- 有且只有 Ctrl 被按下的时候才触发 --> <button @click.ctrl.exact="onCtrlClick"> A</button> <!-- 没有任何系统修饰符被按下的时候才触发 --> <button @click.exact="onClick"> A</button>
-
【观察】事件被绑定到哪里 ?
<template> <div> <p>{{num}}</p> <button @click="increment1">+1</button> <button @click="increment2(2,$event)">+2</button> </div> </template> <script> export default{ data() { return { num:0 } }, methods: { increment1($event){ console.log('event', event, event.__proto__.constructor) // MouseEvent 是原生的event 对象 console.log(event.target) // console.log(event.currentTarget) // 注意,事件 this.num++ }, increment2(val,str,event){ console.log(event.target) this.num = this.num + val }, loadHandler() { // do some thing } } } </script>
8. 表单
- v-model
- 常见表单项 textarea checkbox radio select
- 修饰符 lazy number trim
Vue 组件使用 - 知识点
1. 组件间的通讯
props 和 $emit (父子组件)
-
父传递给子
父组件通过一个属性传递到子组件,子组件这定义 props 接收
-
子传递给父
父亲组件定义事件 : @事件名称
子组件触发事件 : this.$emit(‘事件名称’,参数)
父 – 子 : 传递一个事件
子 – 父 : 触发一个信息
自定义事件(兄弟组件)
- 创建自定义事件实例: component / event.js 导出vue的一个实例
- event.$on(‘事件名称’,事件方法) // 绑定自定义事件
- event.$emit(‘事件名称’,参数) // 触发自定义事件
- event.$off(‘事件名称’,事件方法)// 销毁自定义事件,防止内存泄漏
<!--- component event.js --->
import Vue from 'vue';
export default new Vue();
<!--- child1 --->
import event from 'event.js';
event.$emit('事件名称',参数) //来触发一个事件
<!--- child2 --->
import event from 'event.js';
event.$on('事件名称',方法);
beforeDestory() {
event.$off('事件名称',方法)
}
2. 组件生命周期
生命周期(单个组件)
-
挂载阶段 created / mounted
-
更新阶段 update
-
销毁阶段 destroy
- 解除绑定、销毁子组件以及事件监听
-
created VS mounted
- created 生成变量,页面并未渲染,但是 vue 的初始化已经完成
- mounted 页面渲染已经完成
生命周期(父子组件)
- created 创建由外向内(父 – 子)
- mounted 渲染由内像外(子 – 父)
Vue 高级特性
- 不是每一个都很常用,但是用到的时候必须知道
1. 自定义 v-model
<template>
<input type="text"
:value = "text1"
@input="$emit('change',$event.target.value)"
<!---
1. 上面的 input 使用了 :value 而不是 v-model ;
2. 上面的 change 和 model.event 要对应起来;
3. text1 属性对饮起来
--->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text
event: 'change'
},
props: {
text1: String,
default(){
return ''
}
}
}
</script>
<!--- 使用 --->
<CustomInput :text1="name"/>
2. $nextTick and refs
-
$nextTick
- vue 是异步渲染
- data 改变之后,DOM 不会立刻渲染
- $nextTick 会在 DOM 渲染之后触发,以获取最新的DOM 节点
-
refs
- 获取DOM元素
<template> <div> <ul ref='ul1'> <li v-for="(item,index) in list" :key="index">{{item}}</li> </ul> <button @click="addItem">添加一项</button> </div> </template> <script> export default{ name:'app', data() { return { list: ['a','b','c','d'] } }, methods:{ addItem() { this.list.push(`${Date.now()}`) this.list.push(`${Date.now()}`) this.list.push(`${Date.now()}`) // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调 // 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次 this.$nextTick(()=>{ // 获取 DOM 元素 const ulElem = this.$refs.ul1 console.log(ulElem.childNodes.length) }) } } } </script>
3. slot 插槽
-
基本使用: 让父组件给子组件传递一段内容
-
作用域插槽:ScopedSlot
<!-- 子定义 --> <a :href="url"> <slot :slotData="website">{{website.subTitle}}</slot> </a> <!-- 父使用 --> <ScopedSlotDemo :url="website.url"> <template v-slot="slotProps"> {{slotProps.slotData.title}} </template> </ScopedSlotDemo>
-
具名插槽
<!-- 子定义 --> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> <!-- 父使用 --> <NamedSlot> <!-- 缩写 <template #header> --> <template v-slot:header></template> <p> 将 插入到main slot 中,即未命名的slot </p> <template v-slot:footer></template> </NamedSlot>
<template>
<div>
<a :href="url">
<slot>默认内容,即父组件没设置内容时,这里显示</slot>
</a>
</div>
</template>
<script>
export default{
props:['url'],
data() {
return {}
}
}
</script>
<!-- ScopedSlot.vue -->
<template>
<div>
<a :href="url">
<slot :slotData="website">{{website.subTitle}}</slot>
</a>
</div>
</template>
<script>
export default{
props:['url'],
data() {
return {
website: {
url:'',
title:'儿子scoped',
subTitle:'er2'
}
}
}
}
</script>
<!-- UseSlot -->
<template>
<div>
<SlotDemo :url="website.url">
{{website.url}}
</SlotDemo>
<ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo>
</div>
</template>
<script>
import SlotDemo from './SlotDemo'
export default{
name:'app',
compontents:{
'SlotDemo',
'ScopedSlotDemo'
},
data() {
return {
name:'',
website: {
url:'',
title:'父亲',
subTitle:''
}
}
}
}
</script>
4. 动态、异步组件
4-1 动态组件
-
根据数据,动态渲染的场景。即组件类型不确定
-
:is=“component-name” 用法
<component :is="NextTickName"></component>
4-2 异步组件(性能优化)
-
import() 函数
-
按需加载,异步加载大组件
<script> export default{ components: { FormData : import('../BaseUse/FormDemo') } } </script>
5. keep-alive (缓存组件)(性能优化)
-
缓存组件:频繁切换,不要重复渲染
- destroyed 不会走到
-
Vue 常见的性能优化
-
keep-alive 在 vue层级控制的,v-if 是原生的js控制的
<template> <keep-alive> <KeepAliveStageA v-if="state===A"/> <KeepAliveStageB v-if="state===B"/> <KeepAliveStageC v-if="state===C"/> </keep-alive> </template> <script> export default { data(){ return { state:'A' } }, mounted(){}, destroyed(){} } </script>
6. mixin
-
多个组件有相同的
逻辑
,抽离出来 -
minx并不是完美的解决方案,会有一些问题
- 来源不明确,不利于阅读
- 多个mixin会照成命名冲突(data/method); mounted 是个生命周期的东西所以不会冲突
- mixin组件可能会出现多对多的关系,复杂度比较高
-
Vue3提出的CompositionAPI旨在解决这些问题
<!-- mixin.js --> export default{ data(){ return { city:'北京' } }, methods:{ showName(){ console.log(this.name) } }, mounted(){ console.log('mixin mounted',this.name) } } <!-- MixinDemo --> <template> <h1> {{city}} </h1> <button @click="showName"> 打印 </button> </template> <script> import myMixin from './mixin' export default{ mixins:[myMixin], // 可以添加多个,会自动合并起来 data(){ return { name:'', major:'' } }, methods:{}, mounted(){ console.log('comm',this.name) } } </script>
vuex 知识点串讲
- 面试考点并不多(熟悉了vue之后,vuex没有难度)
- 但是基本概念、基本使用和API 必须掌握
- 可能会考察 state 的数据结构
- state
- getters
- action
- mutation
- 用于 Vue 组件
- dispatch
- commit
- mapState
- mapGetters
- mapActions
- mapMutations
vue-router 知识点串讲
-
官网 : router.vuejs.org/zh/guid/
-
路由模式(hash, H5 history)
- hash模式(默认模式) http://abc.com/#/user/10
- H5 history 模式 http://abc.com/user/10 (需要server端支持,因此无需特需原因用 hash 的默认模式)
-
路由配置(动态路由、懒加载)
const router = new VueRouter({ routes: [ // 动态路径参数 以:开头,能命中 ‘user/10’ ‘user/2’ 等格式的路由 {path:'/user/:id',component:User}, {path:'',component:()=>import() // 懒加载} ] })
Vue 原理
- 面试为何会考察原理?知其然知其所以然;了解原理,才能应用更好;大厂造轮子(技术KPI)
- 面试中如何考察?以何种方式?
- 考察重点,而不是考察细节
- 和使用相关的原理,如 vdom、模板渲染
- 整体流程是否全面?热门技术是否有深度?
- Vue原理包括哪些?
- 组件化
- 响应式
- vdom 和 diff
- 模板编译
- 渲染过程
- 前端路由
** 组件化基础 — Vue MVVM **
1. 很久以前的组件化
jsp,php…
只是静态渲染
2. 数据驱动视图(MVVM,setState)
-
传统组件,只是静态渲染,更新还要依赖于操作DOM
-
数据驱动视图 — Vue MVVM (Model ViewModel View)
- ViewModel 是一个链接层,如 view 修改 model
-
数据驱动视图 — React setState
** Vue 响应式 **
- 组件 data 的数据一旦变化,就会立刻触发视图的更新
- 实现数据驱动视图的第一步
核心API - Object.defineProperty
-
如何实现?
-
Object.defineProperty 的一些缺点(Vue 3.0 启用 Proxy:Proxy 有兼容性问题,无法用 polyfill)
const data = {}
const name = ‘zhangsan’
Object.defineProperty(data,“name”, {
get: function(){
return name
},
set: function(newVal){
value = newVal
}
})
<font color=red><b>深度监听data变化</b></font>
- 深度监听,需要递归到底,一次性计算量大
- 新增属性/删除属性,监听不到(所以需要 Vue.set Vue.delete)
- defineProperty 无法监听数组
```js
// 重新定义数组的原型
const oldArrayProperty = Array.prototype
const arrProto =
function defineReactive(target, key, value){
// 深度监听
observe()
Object.defineProperty( target, key, {
get: function(){
return value
},
set: function(newVal){
value = newVal
updateView() // 触发更新
}
})
}
function observe(target){
if(typeof target!= 'object' || target === null) {
return target
}
// 重新定义各属性
for(let key in target){
defineReactive(target, key, target[key])
}
}
function updateView(){
console.log("视图更新")
}
const data = {
name:'zhangsan',
age:21,
info:{
city:'北京'
},
nums : [2,3,5]
}
observe(data)
data.name = 'lisi'
data.info.city = 'shanhai'
** 虚拟 DOM**
2. React
React 基本使用
React 高级特性
React 原理
3. webpack和babel
Webpack 配置
性能优化
babel
4. 项目设计
状态设计
组件设计
组件通讯
5. 项目流程
全栈
面试技巧
- 简历:简洁明了,突出个人技能和项目经验,技术栈
- 可以把个人博客、开源作品放在简历中(但博客要有内容)
- 不要造假,保证能力上的真实性(斟酌用词,如精通xxxx)
- 注意事项
- 如何看待加班:像借钱,救急不救穷
- 千万不要挑战面试官,反考面试官
- 学会给面试官惊喜,证明你能想到更多,做的更好,但是不要太多
- 遇到不会的问题,说出你知道的部分即可,但是别岔开话题
- 谈谈你的缺点:说一下你最近在学什么即可