【前端面试课程重点总结】
长文警告⚠⚠⚠
题目引出:
typeof能判断哪些类型?
何时使用
===
何时使用==
?window.onload和DOMContentLoaded的区别?
JS创建10个
<a>
标签,点击的时候弹出对应的序号手写节流throttle、防抖debounce
Promise解决了什么问题?
以上题目的考点分析:
- JS变量类型
- 强制类型转换
- 页面加载过程
- JS作用域
- 性能、体验优化
- JS异步
思考:
拿到一个面试题,你第一时间看到的是什么?
如何看待网上搜出来的永远做不完的题海?
如何对待接下来遇到的面试题?
如何搞定所有面试题?
回答:
- 拿到一个面试题,第一时间看到–>
考点
- 如何看待做不完的题海->
不变应万变(题可变,考点不变)
- 如何对待接下来的题目->
题目到知识点,再到题目
前端知识体系
仅针对本课程
- 高效学习三部曲:
- 找准知识体系;
- 可以训练;
- 及时反馈
知识体系:结构化的知识范围
涵盖所有知识点;结构化、有组织、易扩展
从哪些方面梳理?
W3C标准
ECMA 262标准
开发环境
运行环境
知识体系:
- CSS基础知识(布局、移动端响应式、动画等)
- JS基础语法(变量、原型、作用域、异步、模块化)
- JS-Web-API(DOM、BOM、事件、ajax、存储)
- 开发环境(git、webpack、调试)
- 运行环境(页面加载过程、性能优化、浏览器安全及漏洞)
- http协议(状态码、restful api、headers、缓存策略)
面试准备:
什么是面试?
经过组织者精心设计
以交谈和观察为主要手段
评价知识、能力和经验,综合素质
你的简历如何被HR拿到?
- 员工内推
- 猎头推荐
- hr收集(主动搜索、接收邮件)
面试流程:
- 一面(考察基础知识)
- 二面(交叉面试|业务场景、项目经验)–始于技术知识,终于项目经验
- 三面(技术之外的话题,leader面)
- hr面试
校招和社招的区别?
- 校招看重基础知识和能力,主要在一面
- 社招看重经验,主要在二面(基础知识要过关)
- 社招,工作时间越长,越偏重经验
JD是什么?
- JD是用人单位发布的招聘信息
- 职位描述
- 岗位要求
从JD中能看到什么?
- 工作内容
- 技术栈
- 经验要求
不要过于在意JD
如何写简历?
- 简历包含的内容
- 简历中需要注意的问题
- 案例
划重点
- 简历就像高考作文——阅卷时间非常短
- 内容整洁
- 直击重点,表现出自己的优势
简历包含的内容
- 个人信息 |必备:姓名、性别、电话、邮箱、籍贯、年龄
- 教育经历 |最高学历;学校、专业、入学和毕业时间
- 专业技能 |表现出自己的核心竞争力;内容不要太多(3、5条即可);太基础的不要写,例如会使用vscode
- 工作经历 |如实写;写明公司,职位,入职离职时间即可;若有空窗期,如实写明即可
- 项目经历 |写2-4个具有说服力的项目(视工作时间);项目描述,技术栈,个人角色;技巧:可以把别人的项目写上,只要你能hold住
- 博客和开源 |有博客或者开源作品,会让你更有竞争力;切记:需要真的有内容,不可临时抱佛脚;可以从现在开始,慢慢积累,为以后的工作做准备(提升个人竞争力)
注意事项:
- 界面不要太花哨,简洁明了即可
- 注意用词,精通、熟练等慎用
- 不可造假,会被拉入黑名单(项目经历那里,不是造假!!)
面试前的注意事项:
- 能加班吗?——能!除非你特别自信,可以找到其他机会
- 不要挑战面试官,即使他错了
- 遇到不会的问题,要表现出自己积极的一面
其他情况:
- 参考 https://www.imooc.com/article/300475
HTML&CSS面试题
- HTML面试题
- 如何理解HTML语义化?
- 让人更容易读懂(增加代码可读性)
- 让搜索引擎更容易读懂(SEO)
- 默认情况下,哪些HTML标签是块级元素、哪些是内联元素?
- display:block/table; 有div h1~h6 table ul ol p 等
- display:inline/inline-block; 有 span img input button 等
CSS面试题
分析知识模块:
- 布局
- 盒子模型的宽度如何计算
- margin纵向重叠的问题
- margin负值的问题
- BFC理解和应用
- float布局的问题,以及clearfix
- flex画色子
- 定位
- absolute和relative分别依据什么定位
- 居中对齐有哪些实现方式
- 图文样式
- line-height的继承问题
- 响应式
- rem是什么
- 如何实现响应式
- CSS3
- 关于CSS3动画
盒模型宽度计算
首先需要知道offsetWidth=(内容宽度+内边距+边框),无外边距
因此答案是 100+ 10x2 + 1x2 = 122 px;
补充:如果让offsetWidth等于100px,该如何做?
设置 box-sizing: border-box;
//设置此项后,width就包括了内容宽度+内边距+边框
margin 纵向重叠问题
- 相邻元素的margin-top和margin-bottom会发生重叠
- 空白内容的
<p></p>
也会重叠 - 答案是 15px
margin负值问题
- 对margin的top left right bottom 设置负值,有何效果?
- margin-top和margin-left负值,元素向上、向左移动
- margin-right负值,右侧元素左移,自身不受影响
- margin-bottom负值,下方元素上移,自身不受影响
BFC理解与应用
什么是BFC?如何应用?
什么是BFC?
- Block format context , 块级格式化上下文
- 一块独立的渲染区域,内部元素的渲染不会影响边界以外的元素
形成BFC的常见条件
float 不是none
position是absolute或fixed
overflow不是visible
display是flex / inline-block 等
BFC的常见应用
清除浮动
<!DOCTYPE html> <html> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><style type="text/css">.container {background-color: #f1f1f1;}.left {float: left;}.bfc {overflow: hidden; /* 触发元素 BFC */}</style> </head> <body><div class="container bfc"><img src="https://www.imooc.com/static/img/index/logo.png" class="left" style="magin-right: 10px;"/><p class="bfc">某一段文字……</p></div> </body> </html>
未设置BFC之前:(图片设置float后,跑到了容器的外面,脱离了标准文档流)
设置BFC之后:
float布局
如何实现圣杯布局和双飞翼布局
圣杯布局和双飞翼布局的目的
三栏布局,中间一栏最先加载和渲染(内容最重要)
两侧内容固定,中间内容随着宽度自适应
一般用于PC网页
圣杯布局:
采用设置padding的方式为左右留白
双飞翼布局:
采用设置margin的方式为两边留白
圣杯布局和双飞翼布局的技术总结
- 使用float布局
- 两侧使用margin负值,以便和中间内容横向重叠
- 防止中间内容被两侧覆盖,一个用padding 一个用margin
手写clearfix
/* 手写 clearfix */ .clearfix:after {content:'';display: table;clear: both; } .clearfix {*zoom: 1; /* 兼容IE低版本,可写可不写 */ }
flex布局
flex实现一个三点的色子
常用语法回顾:(必须掌握)
flex-direction 主轴方向(横向/纵向)
justify-content 主轴对齐方式(从开始对齐/从结束对齐)
align-items 交叉轴(与主轴垂直的轴)对齐方式 (从开始对齐/从结束对齐/居中对齐)
flex-wrap 是否换行
align-self 子元素在交叉轴的对齐方式(从开始对齐/从结束对齐/居中对齐)
CSS - 定位
absolute和relative分别依据什么定位
relative 依据自身定位
absolution 依据最近一层的定位元素定位
定位元素:
- absolute relative fixed
- body
示例代码如下:
<title>absote relative 定位问题</title> <style type="text/css">body {margin: 20px;}.relative {position: relative;width: 400px;height: 200px;border: 1px solid #ccc;top: 20px;left: 50px;}.absolute {position: absolute;width: 200px;height: 100px;border: 1px solid blue;top: 20px;left: 50px;}</style> </head><body><p>absolute 和 relative 定位问题</p><div class="relative"><div class="absolute">this is absolute</div></div> </body>
居中对齐有哪些实现方式
- 水平居中
- inline 元素:text-align:center
- block元素:margin:auto
- absolute元素:left:50% + margin-left 负值
- 垂直居中
- inline 元素:line-height 的值等于height值
- absolute 元素:top: 50% + margin-top 负值(水平居中的话,必须知道宽高)
- absolute 元素:transform(-50%,-50%)
- absolute 元素:top,left,bottom,right = 0 + margin:auto(既要保证浏览器兼容性,又要保证子元素的尺寸是未知的情况下用此方法)
- 水平居中
CSS - 图文样式
line-height 如何继承
- 写具体数值,如30px,则继承该值(比较好理解)
- 写比例,如 2/1.5 ,则继承该比例(比较好理解)
- 写百分比,如200%,则继承计算出来的值(考点)
答案是 40px
rem 是什么?
rem是一个长度单位
- px,绝对长度单位,最常用
- em,相对常用单位,相对于父元素,不常用
- rem,相对长度单位,相对于根元素,常用于响应式布局
响应式布局的常用方案
- media-query,根据不同的屏幕宽度设置根元素font-size
- rem,基于根元素的相对单位
CSS - 响应式 - vw/vh
rem 的弊端
阶梯性
网页的视口尺寸
- window.screen.height //屏幕高度
- window.innerHeight //网页视口高度
- document.body.clientHeight //body高度
vw/vh
vh 网页视口高度的1/100
vw 网页视口宽度的1/100
vmax 取两者最大值;vmin 取两者最小值
window.innerHeight === 100vh
window.innerWidth === 100vw
JS
变量类型和计算
- 题目
- 知识点
- 解答
值类型和引用类型的区别
值类型
引用类型
深入分析:
值类型一般存储在栈中(图中key表示变量名,value表示变量值)
在计算机中变量在存储时栈和堆是同时存在的:
栈是从上往下存放
堆是从下往上存放
对于引用类型,是把变量的内存地址存在栈中,然后这个内存地址指向堆中的内存变量
例子中
b.age=21
就改变了堆中的val
值,所以导致a.age
也会相应改变(a也指向内存地址1)
那为什么需要采用这种方式来进行变量赋值呢?
值类型占用的存储空间较少,直接赋值是没问题的;
而引用类型就不同了,可能会非常大,直接放到栈中,
会导致存储地址太大不好管理,第二会复制时会导致复制过程非常慢。因此,计算机采用此存储机制来作区分
常见值类型
注意:(用const定义一个常量时必须赋值,否则报错)
undefined、数字、字符串、布尔值以及Symbol(ES6中的)
常见引用类型
对象、数组、null以及函数
typeof 运算符
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
只能识别出来是不是引用类型,不能识别出具体的哪一个引用类型
手写深拷贝
/*** 深拷贝*/let result = {};const obj1 = {age: 18,name: "xiaodu",address: {city: "上海",},arr: ["a", "b", "c"], };// const obj2 = obj1; const obj2 = deepClone(obj1) obj2.address.city = '北京' obj2.arr[0] = 'd' //console.log(obj1.address.city); // 北京 console.log(obj1.arr[0]); // a//现在需要实现的是 改变obj2的值不会影响到obj1的值 /*** 深拷贝* @param {Object} obj 要拷贝的对象**/ function deepClone(obj = {}) {if (typeof obj !== "object" || obj == null) {//如果不是数组和对象或者是null,则直接返回objreturn obj;}//初始化返回结果let result;if (obj instanceof Array) {result = [];} else {result = {};}for (let key in obj) {//保证 key不是原型的属性if (obj.hasOwnProperty(key)) {//递归调用!!!result[key] = deepClone(obj[key]);}}//返回结果return result }
变量计算—类型转换(注意某些坑)
字符串拼接
注意数字和字符相加时会返回字符串(只要出现字符串了,此时的
+
就是代表字符串拼接符了)==
==
会出现很多不可预测的情况,此时我们只需要记住:if语句和逻辑运算
truly 变量:!!a === true 的变量
falsely 变量:!!a === false 的变量
逻辑判断
typeof 能判断哪些类型?
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
何时使用
==
,何时使用===
?除了
== null
之外,其他一律用===
值类型和引用类型的区别?
手写深拷贝
- 注意判断值类型和引用类型
- 注意判断是数组还是对象
- 递归
原型和原型链
题目
- 如何准确判断一个变量是否是数组?
- 手写一个简易的jQuery,考虑插件和扩展性
- class 的原型本质,怎么理解?
知识点
- class 和继承
- 类型判断 instanceof
- 原型和原型链
class
- constructor
- 属性
- 方法
class Student {constructor(name,number) {this.name = namethis.number = number}sayHi(){console.log(`你好!我的姓名是${this.name},学号是${this.number}`);}
}//通过类声明对象/实例
const zhangsan = new Student('张三',1001)
console.log(zhangsan.name)
zhangsan.sayHi()
控制台打印结果如下:
- 继承
- extends
- super
- 扩展或重写方法
//父类
class People {constructor(name) {this.name = name}eat() {console.log(`${this.name} eat something`);}
}//子类
class Student extends People {constructor(name,number) {super(name) //通过super传递给父类执行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}`)}
}//实例
const lisi = new Student('李四',1003)
console.log(lisi.name)
console.log(lisi.number)
lisi.sayHi()
lisi.eat()//实例
const wanglaoshi = new Teacher('王老师','语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()
- 类型判断
原型
原型关系
- 每个
class
都有显示原型prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class
的prototype
- 每个
基于原型的执行规则
- 获取属性 xialuo/lisi.name 或执行方法xialuo.sayHi() 时
- 先在自身属性和方法寻找
- 如果找不到则自动去
__proto__
中查找
原型链
可理解为以下的图解:
可用
hasOwnProperty
方法验证是否是自身的方法那么问题又来了,
hasOwnProperty
方法又是哪来的呢?其实此方法来自于顶层的
Object
,Object的__proto__
永远指向null
重要提示:
class 是ES6语法规范,由ECMA委员会发布
ECMA只规定语法规则,即我们代码的书写规范,不规定如何实现
以上实现方式都是V8引擎的实现方式,也是主流的
再看 instanceof
回答问题
如何准确判断一个变量是数组
- a instanceof Array
class 的原型本质
- 原型和原型链的图示
- 属性和方法的执行规则
手写简易jQuery考虑插件和扩展性代码演示
class jQuery {constructor(selector) {const result = document.querySelector(selector);const length = result.length;for (let i = 0; i < length; i++) {this[i] = result[i];}this.length = length;this.selector = selector;}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((elem) => {elem.addEventListener(type, fn, false);});}//扩展很多 DOM API}//插件 jQuery.prototype.dialog = function (info) {alert(info) }//"造轮子" class myjQuery extends jQuery {constructor(selector) {super(selector)}// 扩展自己的方法addClass(className) {...}style(data) {...} }
作用域和闭包
- 题目
- this 的不同应用场景,如何取值
- 手写bind 函数
- 实际开发中闭包的应用场景,举例说明
此时 i 是全局变量,当点击事件执行时,此时 i 的值已经变为了 10 ,因此会出现无论点击哪一个 li 标签,都会弹出数字 10
知识点
- 作用域和自由变量
- 闭包
- this
作用域
全局作用域
函数作用域
块级作用域(ES6新增)
自由变量
一个变量在当前作用域没有定义,但被使用了
向上级作用域,一层一层依次寻找,直到找到为止
如果到全局作用域都没找到,则报错
xx is not defined
注意看图中的
return a+a1+a2+a3;
这句代码,a并没有在fn3函数中定义,它会依次往外寻找,直到在最外层找到,其余a1和a2也类似。如果找到全局也没找到,则说明没有定义此变量,会报错。
闭包
作用域应用的特殊情况
函数作为参数被传递
函数作为返回值被返回
第一个调用 fn() 时返回的是 100;第二个print(fn) 返回的是 100。
所有的自由变量的查找,是在
函数定义
的地方,向上级作用域查找;不是在执行的地方!!!
this
- 作为普通函数 ————直接返回window
- 使用 call apply bind ————传入什么就绑定什么
- 作为对象方法被调用 ————返回对象本身
- 在class方法中调用 ————返回当前对象/实例本身
- 箭头函数 ————找它的上级作用域中this的值来确定
this取什么值是在函数执行时被确定的,不是在函数定义时被确定的
fn1 作为普通函数调用执行,此时 this 代表 window;
fn1 通过call方法执行,此时 this 代表{ x: 100 };
fn2是通过bind方法(会返回一个新的函数)改变了this的指向,此时fn2 的 this 代表 { x: 200 }。
箭头函数的
this
的取值,取决于它上级函数作用域的this
值
手写bind函数
控制台的输出结果为:
实际开发中闭包的应用
- 隐藏数据
- 比如做一个简单的cache工具
关于前面 li 的题目的正确代码应该为:
把 let i 定义在括号里,定义的是一个for里面的块级作用域,每次执行的时候,都会生成一个新的块,因此就可以在点击的时候弹出对应的 i 值
异步和单线程
题目
同步和异步的区别是什么?
手写Promise加载一张图片
前端使用异步的场景有哪些?
场景题:
答案:13542
知识点
单线程和异步
应用场景
callback hell 和 Promise
单线程和异步
JS是单线程语言,只能同时做一件事
浏览器和 nodejs 已支持 JS 启动
进程
,如 Web WorkerJS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
遇到等待(网络请求、定时任务)不能卡住
异步和同步
左边代码执行结果:100 300 200
右边代码执行结果:100 弹出200 点击ok(不主动点击的话,会一直卡住) 打印 300
基于 JS 是单线程语言
异步不会阻塞代码执行
同步会阻塞代码执行
- 应用场景
- 网络请求,如 ajax 图片加载
- 定时任务,如setTimeout
打印 start => 执行网络请求 => 打印 end => 什么时候网络请求回来什么时候执行
执行start,(定义一个图片img,定义一个onload事件,类似于callback函数) => 图片加载(加载时间不确定) => 打印 end => 等待图片加载完成再执行 onload 事件及对应的回调函数
callback hell
什么是回调地狱?
可以这样回答:
1.因为javascript是单线程的,所以有些需要等待的地方,需要使用回调函数。
2.由于某些业务的问题,在代码中会一次性写多层的回调嵌套,回调嵌套后的代码的维护难度,和无法排除bug。这个就被称作回调地狱。Promise是ES6标准新增的一个API,意在解决上述的回调问题,并且被广泛使用。使用Promise编写也會提高程序的可读性、可维护性,使用Promise,每个函数调用不需要知道下一个执行程序应该转到哪里,执行顺序完全交给调用方。
小结
单线程和异步,异步和同步的区别
异步由单线程的基础作决定的,异步不会导致代码的阻塞
前端异步的应用场景:网络请求 & 定时任务
Promise 解决 callback hell(解决了callback嵌套调用的问题)
JS 异步 - 进阶
主要讲解 JS异步的原理和进阶内容
- 主要内容
- event loop
- promise 进阶
- async / await
- 微任务 / 宏任务
先来看几个面试题
- 请描述 event loop (事件循环/事件轮询)的机制,可画图
- 什么是宏任务和微任务,两者有什么区别?
- Promise 有哪三种状态?如何变化?
场景题 - promise then 和 catch 的连接:
场景题 - async/await 语法:
场景题 - promise 和 setTimeout 的顺序:
场景题 - 外加 async/await 的顺序问题(深度题):
- event loop(事件循环/事件轮询)
- JS 是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
首先我们需要知道一个问题:JS 如何执行的?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
总结 event loop 过程1
- 同步代码,一行一行放在 Call Stack 执行
- 遇到异步,会先"记录"下,等待时机(定时、网络请求等)
- 时机一到,就移动到 Callback Queue
总结 event loop 过程2
- 如果 Call Stack 为空(即同步代码执行完)Event Loop 开始工作
- 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
- 然后继续轮询查找(永动机一样)
DOM事件和 event loop
JS 是单线程的
异步(setTimeout,ajax等)使用回调,基于 event loop
DOM 事件也使用回调,基于 event loop
Promise
三种状态
状态的表现和变化
then 和 catch 对状态的影响
三种状态
pending resolved rejected
pending -> resolved 或 pending -> rejected
变化不可逆
const p1 = new Promise((resolve,reject) => {})
console.log('p1',p1); // pendingconst p2 = new Promise((resolve,reject) => {setTimeout(() => {resolve()})
})
console.log('p2',p2); // pending 一开始打印时
setTimeout(() => console.log('p2-setTimeout',p2)) // resolvedconst p3 = new Promise((resolve,reject) => {setTimeout(() => {reject()})
})
console.log('p3',p3); // pending 一开始打印时
setTimeout(() => console.log('p3-setTimeout',p3)) // rejected
状态的表现
pending 状态,不会触发 then 和 catch
resolved 状态,会触发后续的 then 回调函数
rejected 状态,会触发后续的 catch 回调函数
const p1 = Promise.resolve(100); // resolved // console.log("p1", p1); p1.then((data) => {console.log("data", data); }).catch(err => {console.log('err',err) })const p2 = Promise.reject("err"); // rejected // console.log("p2", p2); p2.then((data) => {console.log("data2", data);}).catch(err => {console.log('err2',err) })
then 和 catch 改变状态
then 正常返回 resolved,里面有报错则返回 rejected
catch 正常返回 resolved,里面有报错则返回 rejected
Promise 总结
- 三种状态,状态的表现和变化
- then 和 catch 对状态的影响(重要)
- then 和 catch 的链式调用(常考)
async / await
异步回调 callback hell
Promise then catch 链式调用,但也基于回调函数
async / await 是同步语法,彻底消灭回调函数
function loadImg(src) {const p = new Promise((resolve, reject) => {const img = document.createElement("img");img.onload = () => {resolve(img);};img.onerror = () => {const err = new Error(`图片加载失败 ${src}`);reject(err);};img.src = src;});return p; }const src1 = 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'; const src2 = 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'!(async function () {// 同步的写法// img1const img1 = await loadImg(src1)console.log(img1.height,img1.width);// img2const img2 = await loadImg(src2)console.log(img2.height,img2.width); })()
async / await 和 Promise 的关系
- async / await 是消灭异步回调的终极武器
- 但和 Promise 并不互斥,反而,两者相辅相成
- 执行 async 函数,返回的是 Promise 对象
- await 相当于 Promise 的 then
- try…catch 可捕获异常,代替了 Promise 的 catch
异步的本质
- async / await 是消灭异步回调的终极武器
- JS 还是单线程,还得是有异步,还得是基于 event loop
- async / await 只是一个语法糖(但这颗糖真香!)
for … of
- for … in ( 以及 forEach for )是常规的同步遍历
- for … of 常用于异步的遍历
function muti(num) {return new Promise(resolve => {setTimeout(() => {resolve(num*num)},1000)})
}const nums = [1,2,3]// 同步循环执行 先一次性执行完 再出执行结果
// nums.forEach(async (i) => {// const res = await muti(i)
// console.log(res);
// })// 异步循环执行 一个一个执行
!(async function () {for(let i of nums) {const res = await muti(i)console.log(res);}
})()
微任务microTask和宏任务macroTask
什么是宏任务,什么是微任务
event loop 和 DOM 渲染
微任务和宏任务的区别
宏任务:setTimeout,setInterval,Ajax,DOM事件
微任务:Promise async / await
微任务执行时机比宏任务要早(先记住)
那为什么微任务比宏任务执行早一些呢?
- event loop 和 DOM渲染
- 再次回归一遍 event loop 的过程
- JS 是单线程的,而且和DOM渲染共用一个线程
- JS 执行的时候,得留一些时机供DOM渲染
再次看一遍event loop 过程
当调用栈里执行完所有同步代码之后,先会尝试渲染DOM,也就是一个轮询结束后,会尝试进行DOM渲染,然后再触发 event loop
- 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次Event Loop
宏任务和微任务的区别
宏任务:DOM 渲染后触发,如 setTimeout
微任务:DOM 渲染前触发,如 Promise
先演示现象,稍后追究原理
const $p1 = $("<p>一段文字</p>"); const $p2 = $("<p>一段文字</p>"); const $p3 = $("<p>一段文字</p>"); $("#container").append($p1).append($p2).append($p3) console.log("length", $("#container").children().length); // 3 alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染') //(alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)// 微任务:DOM 渲染前触发 Promise.resolve().then(() => {console.log('length1',$('#container').children().length) // 3alert('Promise then') // DOM 渲染了吗? no })// 宏任务:DOM 渲染后触发 setTimeout(() => {console.log('length2',$('#container').children().length) // 3alert('setTimeout') // DOM 渲染了吗? yes })
从 event loop 解释,为何微任务执行更早
遇到微任务时,会放进另外一个微任务队列里,不会经过Web APIs
为什么?
- 微任务是 ES6 语法规定的
- 宏任务是由浏览器规定的
解答JS异步的面试题
描述 event loop 机制(可画图)
- 自行回顾 event loop 的过程
- 和 DOM 渲染的关系
- 微任务和宏任务在 event loop 过程中的不同处理
什么是宏任务和微任务,两者区别
- 宏任务:setTimeout,setInterval,Ajax,DOM事件
- 微任务:Promise,async / await
- 微任务执行时机比宏任务更早
Promise 的三种状态,如何变化?
pending resolved rejected
pending -> resolved 或 pending -> rejected
变化不可逆
手写Promise:
主要考察 面试者的设计思路和代码能力
- 初始化 & 异步调用
- then catch 链式调用
- API .resolve .reject .all .race
/*** @description MyPromise*/class MyPromise {state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'value = undefined // 成功后的值reason = undefined // 失败后的原因resolveCallbacks = [] // pending 状态下,存储成功的回调rejectedCallbacks = [] // pending 状态下,存储失败的回调constructor(fn) {const resolveHandler = (value) => {if(this.state === 'pending') {this.state = 'fulfilled'this.value = valuethis.resolveCallbacks.forEach(fn => fn(this.value))}}const rejectHamdler = (reason) => {if(this.state === 'pending') {this.state = 'rejected'this.reason = reasonthis.rejectedCallbacks.forEach(fn => fn(this.reason))}}try {fn(resolveHandler, rejectHamdler)} catch (err) {rejectHamdler(err)}}then(fn1, fn2) {// 当 pending 状态下,fn1 fn2 会被存储到 callbacks 中fn1 = typeof fn1 === 'function' ? fn1 : (v) => vfn2 = typeof fn2 === 'function' ? fn2 : (e) => eif(this.state === 'pending') {// pending 状态下 即变化的时候才执行 执行的时候需要存储 存储的是函数 此函数是在状态变化的时候才会被执行const p1 = new MyPromise((resolve,reject) => {this.resolveCallbacks.push(() => {try {const newValue = fn1(this.value)resolve(newValue)} catch (err) {reject(err)}})this.rejectCallbacks.push(() => {try {const newReason = fn2(this.reason)reject(newReason)} catch (err) {reject(err)}})})return p1}if(this.state === 'fulfilled') {const p1 = new MyPromise((resolve,reject) => {try {const newValue = fn1(this.value)resolve(newValue)} catch (err) {reject(err)}})return p1}if(this.state === 'rejected') {const p1 = new MyPromise((resolve,reject) => {try {const newReason = fn2(this.reason)reject(newReason)} catch (err) {reject(err)}})return p1}}// 就是 then 的一个语法糖,简单模式catch(fn) {return this.then(null, fn)}
}MyPromise.resolve = function (value) {return new MyPromise((resolve,reject) => resolve(value))
}
MyPromise.reject = function (reason) {return new MyPromise((resolve,reject) => reject(reason))
}MyPromise.all = function (promiseList = []) {const p1 = new MyPromise((resolve,reject) => {const result = [] // 存储 promiseList 所有的结果const length = promiseList.lengthlet resolvedCount = 0promiseList.forEach(p => {p.then(data => {result.push(data)// resolvedCount 必须在 then 里面做 ++// 不能用 indexresolvedCount++if(resolvedCount === length) {//已经遍历到了最后一个promiseresolve(result)}}).catch(err => {reject(err)})})})return p1
}MyPromise.race = function (promiseList = []) {let resolved = false // 标记const p1 = new Promise((resolve,reject) => {promiseList.forEach(p => {p.then(data => {if (!resolved) {resolve(data)resolved = true}}).catch((err) => {reject(err)})})})return p1
}
从JS基础知识到JS Web API
- js基础知识,规定语法(ECMA 262 标准)
- JS Web API ,网页操作的API(W3C 标准)
- 前者是后者的基础,两者结合才能真正实际应用
回顾 JS 基础知识:
变量的类型和计算
原型和原型链
作用域和闭包
JS Web API 相关知识
- DOM
- BOM
- 事件绑定
- ajax
- 存储
DOM 的本质是什么?
DOM 操作
题目
- DOM 是哪种数据结构
- DOM 操作的常用 API
- attr 和 property 的区别
- 一次性插入多个DOM 节点,考虑性能
知识点
- DOM 本质
- DOM 节点操作
- DOM 结构操作
- DOM 性能
DOM 本质
DOM 结构和 XML类似,DOM的本质是一棵树,首先html本身就是树型结构,是从html文件解析出来的一棵树
DOM 节点操作
获取 DOM 节点
根据ID名获取;
根据标签名获取;
根据className获取;
根据css选择器获取
attribite
property
DOM 节点的 property
控制台打印结果为:
通过 property 修改的是js变量的一个属性,设置后不会对标签产生什么影响
DOM 节点的 attribute
控制台打印结果为:
可以通过设置attribute真正作用到DOM结构上去的,可以修改标签属性
总结
- property: 修改对象属性,不会体现到 html 结构中
- attribute:修改html属性,会改变html结构
- 两者都有可能引起DOM重新渲染
DOM 结构操作
- 新增 / 插入 节点
- 获取子元素列表,获取父元素
- 删除子元素
代码如下:
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)// 获取父元素
console.log( p1.parentNode )// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {if (child.nodeType === 1) {return true}return false
})
console.log('div1ChildNodesP', div1ChildNodesP)div1.removeChild( div1ChildNodesP[0] )
DOM 性能
DOM 操作非常“昂贵”,避免频繁的DOM操作
对 DOM 查询做缓存
将频繁操作改为一次性操作
频繁操作的代码如下:
题目答案
DOM 是哪种数据结构
- 树(DOM树)
DOM 操作的常用 API
获取DOM节点,以及节点的property和attribute、获取父节点,获取子节点、新增节点、删除节点等
- DOM 节点操作
- DOM 结构操作
- attribute 和 property
attr 和 property 的区别
- property: 修改对象属性,不会体现到 html 结构中,例如对象中的name、age、address
- attribute:修改html属性,会改变html结构,例如:width、height、class、attr-name
- 两者都有可能引起 DOM 重新渲染
一次性插入多个DOM 节点,考虑性能
- 创建文档片段,将频繁操作改为一次性操作
BOM 操作(Browser Object Model)
题目
如何识别浏览器的类型
可以通过
usrAgent
来进行判断浏览器的类型。分析拆解 url 各个部分
包括 href , protocal , pathname , search , hash
知识点
- navigator
- screen
- location
- history
navigator 和 screen
location 和 history
JS-Web-API-事件
题目
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 无限下拉的图片列表,如何监听每个图片的点击?
知识点
- 事件绑定
- 事件冒泡
- 事件代理
事件绑定
通用的事件绑定函数如下:(过于简洁,后面有拓展情况)
事件冒泡
注释掉标记的那行代码后,点击激活,控制台输出结果为:
首先触发了p1的激活事件,由于事件冒泡,它会往上冒,触发到body身上去,然后就触发了body上的取消事件;通过
event.stopPropagation()
可阻止冒泡事件代理
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用(适合那种代码结构复杂,不好去每个都绑定click事件的)
通用的事件绑定函数(拓展)
ajax
题目
- 手写一个简易的 ajax
- 跨域的常用实现方式
知识点
- XMLHttpRequest
- 状态码
- 跨域:同源策略,跨域解决方案
解答
XMLHttpRequest
注意:这里应该使用
===
,而不是==
xhr.readyState
- 0 - UNSET 尚未调用 open 方法
- 1 - OPENED open 方法已被调用
- 2 - HEADERS_RECEIVED send 方法已被调用,header 已被接收
- 3 - LOADING 下载中,responseText 已有部分内容
- 4 - DONE 下载完成
xhr.status
- 2xx - 表示成功处理请求,如200
- 3xx - 表示重定向,浏览器直接跳转,如 301 302 304
- 4xx - 表示客户端请求错误,如 404 (请求地址错误)403(客户端没有权限)
- 5xx - 服务端错误
跨域
- 什么是跨域(同源策略)
- JSONP
- CORS(服务端支持)
同源策略
- ajax 请求时,浏览器要求当前网页和server 必须同源(安全)
- 同源:协议、域名、端口,三者必须一致
- 前端:http://a.com:8080/ ; server:https://b.com/api/xxx
加载图片 css js 可无视同源策略
<img src=跨域的图片地址/>
<link href=跨域的css地址/>
<script src=跨域的js地址></script>
<img />
可用于统计打点,可用于第三方统计服务<link /> <script>
可用于CDN,CDN 一般都是外域<script>
可实现 JSONP
跨域
- 所有的跨域,都必须经过 server 端允许和配合
- 未经 server 端允许就实现跨域,说明浏览器有漏洞,危险信号
实现跨域的常见方式
JSONP
访问http://imooc.com/,服务端一定返回一个html文件吗?
服务器可以任意动态拼接数据返回,只要符合html格式要求
同理于
<script src="https//immooc.com/getData.js">
<script>
可绕过跨域限制所以,
<script>
就可以获得跨域的数据,只要服务端愿意返回
-jQuery 实现 jsonp-
- CORS - 服务器设置 http header
题目解答
手写一个简易的ajax
扩展写法
function ajax(url) {const p = new Promise((resolve, reject) => {const xhr = new XMLHttpRequest()xhr.open('GET', url, true)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {resolve(JSON.parse(xhr.responseText))} else if (xhr.status === 404 || xhr.status === 500) {reject(new Error('404 not found'))}}}xhr.send(null)})return p }const url = '/data/test.json' ajax(url) .then(res => console.log(res)) .catch(err => console.error(err))
跨域的实现方式
- JSONP
- CORS
存储
题目
描述 cookie localStorage sessionStorage 区别
- 容量
- API 易用性
- 是否跟随 http 请求发送出去
知识点
- cookie
- localStorage 和 sessionStorage
cookie
- 本身用于浏览器和 server 通讯
- 被 “借用” 到本地存储来
- 可用 document.cookie = ‘…’ 来修改
cookie 的缺点:
- 存储太小,最大4KB
- http 请求时需要发送到服务端,增加请求数据量
- 只能用 document.cookie = ‘…’ 来修改,太过简陋
localStorage 和 sessionStorage
- HTML5 专门为存储而设计,最大可存5M
- API 简单易用 setItem getItem
- 不会随着 http 请求被发送出去
- localStorage 数据会永久存储,除非代码或手动删除
- sessionStorage 数据只存在于当前会话,浏览器关闭则清空
- 一般用 localStorage 会更多一些
http 面试题
- 前端工程师开发界面
- 需要调用后端的接口,提交/获取 数据——http 协议
- 要求事先掌握好 ajax
-几个题目 - 1 -
- http 常见的状态码有哪些 ?
- http 常见的 header 有哪些?
- 什么是 Restful API (首先需要说明白methods,还得知道Restful API 和传统API设计的不同)
-几个题目 - 2 -
描述一下 http 的缓存机制(重要)
http 状态码
状态码分类
- 1xx 服务器收到请求
- 2xx 请求成功,如 200
- 3xx 重定向,如 302
- 4xx 客户端错误,如 404
- 5xx 服务端错误,如 500
常见状态码
200 成功
301 永久重定向(配合 location,浏览器自动处理)
302 临时重定向(配合 location,浏览器自动处理)
304 资源未被修改
404 资源未找到
403 没有权限
500 服务器错误
504 网关超时
关于协议和规范
- 就是一个约定
- 要求大家都跟着执行
- 不要违反规范,例如 IE 浏览器
http methods
- 传统的 methods
- get 获取服务器的数据
- post 向服务器提交数据
- 简单的网页功能,就这两个操作
- 现在的 methods
- get 获取数据
- post 新建数据
- patch / put 更新数据
- delete 删除数据
- Restful API
- 一种新的API设计方法(早已推广使用)
- 传统 API 设计:把每个 url 当作一个功能
- Restful API 设计:把每个 url 当作一个唯一的资源
如何设计成一个资源?
尽量不用 url 参数
- 传统 API 设计:/api/list?pageIndex=2
- Restful API 设计:/api/list/2
用 method 表示操作类型
传统 API 设计(把url当作一个功能去设计的)
- post 请求 /api/create-blog
- post 请求 /api/update-blog?id=100
- get 请求 /api/get-blog?id=100
Restful API 设计(把url 当作一个唯一的资源标识去设计的)
- post 请求 /api/blog
- post 请求 /api/blog/100
- get 请求 /api/blog/100
- 传统的 methods
http headers
- 常见的 Request Headers
- Accept 浏览器可接收的数据格式
- Accept-Encoding 浏览器可接收的压缩算法,如 gzip
- Accept-Language 浏览器可接收的语言,如 zh-CN
- connection: keep-alive 一次TCP 连接重复使用
- cookie
- Host (请求的域名)
- User-Agent(简称UA) 浏览器信息
- Content-type 发送数据的格式,如 application/json
- 常见的 Response Headers
- Content-type 返回数据的格式,如 application/json
- Content-length 返回数据的大小,多少字节
- Content-Encoding 返回数据的压缩算法,如 gzip
- Set-Cookie (用于服务端修改cookie)
- 演示
自定义 header
缓存相关的 headers
- Cache-Control Expires
- Last-Modified If-Modified-Since
- Etag If-None-Match
- 常见的 Request Headers
http 缓存
http 为何需要缓存?
- 关于缓存的介绍
- http 缓存策略(强制缓存 + 协商缓存)
- 刷新操作方式,对缓存的影响
什么是缓存?
浏览器第一次访问一个网址时,本地没有任何信息时,服务端需要把数据传给浏览器(图片、文件等);当浏览器第二次访问此网址时,浏览器是否有必要把所有资源再次重新访问一遍?这是没有必要的的,这就是缓存;我们可以把一些没有必要重新获取的东西不再重新获取,不管是以哪种方式暂存一份
为什么需要缓存?
让页面加载速度更快,我们需要尽量减少网络请求的体积和数量,这样才能让网络请求更快一些
哪些资源可以被缓存?
静态资源(js css img)
http 缓存 - 强制缓存
Cache-Control
- Response Headers 中
- 控制强制缓存的逻辑
- 例如 Cache-Control: max-age=31536000(单位是秒)
如果遇到缓存失效的情况,会再次请求服务端,重新获取资源
- cache-control 的值
- max-age 缓存的最大过期时间
- no-cache 不使用强制缓存,交给服务端处理
- no-store 不用强制缓存也不让服务端做缓存
- private 只允许最终用户做缓存
- oublic 允许中间路由或代理做缓存
关于 Expires(知道即可)
- 同在 Response Headers 中
- 同为控制缓存过期
- 已被 Cache-Control 代替
http 缓存 - 协商缓存(对比缓存)
- 服务端缓存策略
- 服务端判断客户端资源,是否和服务端资源一样
- 一致则返回 304,否则返回 200 和最新的资源
资源标识
在 Response Headers 中,有两种
Last-Modified 资源的最后修改时间
Etag 资源的唯一标识(一个字符串,类似人类的指纹)
Headers 示例
请求示例
第二次访问时,比如说通过
Last-Modified
或者Etag
命中了缓存的话,返回 304(此时并未返回新的资源,因此Size非常小)Last-Modified 和 Etag
- 会优先使用 Etag
- 因为Last-Modified 只能精确到秒级(秒级对于计算机来说很宽泛,计算机是以毫秒为单位的)
- 如果资源被重复生成,而内容不变,则 Etag 更精确
http 缓存 - 综述
-刷新页面对http缓存的影响-
三种刷新操作
- 正常操作:地址栏输入 url,跳转链接,前进后退等
- 手动刷新:F5,点击刷新按钮,右击菜单刷新
- 强制刷新:ctrl + F5
不同刷新操作,不同的缓存策略
- 正常操作:强制缓存有效,协商缓存有效
- 手动刷新:强制缓存失效,协商缓存有效
- 强制刷新:强制缓存失效,协商缓存失效
小结
- 强制缓存 Cache-Control
- 协商缓存 Last-Modified 和 Etag,304 状态码
- 完整的流程图(不参照任何资料画出来)
https
- http 和 https
- 加密方式:对称加密,非对称加密
- https 证书
http 和 https
- http 是明文传输,敏感信息容易被中间劫持
- https = http + 加密,劫持了也无法解密
- 现代浏览器已经开始强制 https 协议
加密方式
- 对称加密:一个 key 同负责加密、解密
- 非对称加密:一对 key,A 加密之后,只能用 B 来解密
- https 同时用到了这两种加密方式(成本低、安全性较高)
https 证书
- 中间人攻击
- 使用第三方证书(慎用免费、不合格的证书)
- 浏览器校验证书
关于开发环境
- 面试官想通过开发环境了解候选人的实际工作情况
- 开发环境的工具,能体现工作产出的效率
- 会以聊天形式为主,不会问具体的问题
开发环境
- git
- 调试工具
- 抓包
- webpack babel
- linux 常用命令
git
- 最常用的代码版本管理工具
- 大型项目需要多人协作开发,必须熟练使用 git
- 如果你不知道或者之前不用 git ,不会通过面试
- Mac OS 自带 git 命令,windows 可去官网下载安装
- git 服务端常见的有 github coding.net 等
- 大公司会搭建自己的内网 git 服务
常用 git 命令
- git add . 加上所有文件
- git checkout xxx 还原文件到之前状态
- git commit -m “xxx” 提交一行记录
- git push origin master 推送到服务端
- git pull origin master 从服务端下载
- git branch 查看分支
- git checkout -b xxx / git checkout xxx 切换分支
- git merge xxx 合并分支
git 常用命令演示:
- git checkout -b feature-login 在xxx基础上新建一个feature-login分支
- git branch 查看当前所在分支(此时在新分支上修改,不会影响原分支功能)
- git status 查看当前有哪些改动
- git checkout master(分支名) 切换到master分支
- git fetch 从服务端拉取所有分支
- git merge feature-login 合并当前代码到当前(master)分支
- git push origin master 提交到服务端
- git log 查看提交记录
场景:当你原本需要在master新建一个分支完成注销功能编写时,你在master分支已经完成了注销功能的编写,此时该怎么处理?
答:难道需要git checkout . ?然后重新写?万一改的比较多,重新写太麻烦了。此时需要 git stash
命令(把你当前需要修改的东西搁在一边)然后git checkout -b feature-logout
新建一个新分支,接着开放暂存的文件git stash pop
;然后就可以 git add .
和git commit -m "注销功能"
chrome 调试工具
- 一般不会面试考察
- 但这是前端工程师必备的技能(不算知识)
主要包括:
Elements
当前dom结构的展示,可以选中某个元素以及相关的样式和绑定的事件
Console
在控制台可以打印一些东西
debugger
在 source 中可以查看源代码,在源代码文件中写上debugger,表示在此处打了一个断点;也可以直接在调试窗口打断点
Network
刷新页面,可以查看各个类型的资源的加载具体情况以及返回的状态码
Application
可以查看localStorage、sessionStorage以及cookie以及对本地存储的一些数据进行删除操作
抓包
- 移动端h5页面,查看网络请求,需要用工具抓包
- windows 一般用 fiddler
- Mac OS 一般用 charles
- 手机和电脑连同一个局域网
- 将手机代理到电脑上
- 手机浏览网页
- 查看网络请求
- 网址代理
- https
webpack 和 babel
- ES6 模块化,浏览器暂不支持
- ES6 语法,浏览器并不完全支持
- 压缩代码,整合代码,以让网页加载更快
如何配置 webpack ?
首先保证安装了nodejs
查看node版本
node -v
初始化npm环境
npm init -y
安装webpack以及打包工具
npm install webpack webpack-cli -D
注意:出错的话,可以借助淘宝镜像安装
npm install webpack webpack-cli -D --registry=https://registry.npmmirror.com
配置 webpack(在
webpack.config.js
文件里配置)const path = require('path')module.exports = {// mode 可选 development 或 production ,默认为后者// production 会默认压缩代码并进行其他优化(如 tree shaking)mode: 'development',entry: path.join(__dirname, 'src', 'index'),output: {filename: 'bundle.js',path: path.join(__dirname, 'dist')}, }
打包文件
npm run build
安装插件:解析html 和 启动服务的插件
npm install html-webpack-plugin -D
nom install webpack-dev-server -D
安装完成之后可以在
package.json
文件中查看到:
引入插件:
const HtmlWebpackPlugin = require('html-webpack-plugin')
安装 babel (高级语法转为低级语法)
npm install @babel/core @babel/preset-env babel-loader -D
core babel 的核心
preset-env babel 的配置集合
babel-loader 给webpack用的插件
在目录下新建一个
.babelrc
的配置文件{"presets": ["@babel/preset-env"],"plugins": [] }
继续配置 webpack
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {// mode 可选 development 或 production ,默认为后者// production 会默认压缩代码并进行其他优化(如 tree shaking)mode: 'development',entry: path.join(__dirname, 'src', 'index'),output: {filename: 'bundle.js',path: path.join(__dirname, 'dist')},module: {rules: [{test: /\.js$/,loader: ['babel-loader'],include: path.join(__dirname, 'src'),exclude: /node_modules/},]},plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html'),filename: 'index.html'})],devServer: {port: 3000,contentBase: path.join(__dirname, 'dist'), // 根目录open: true, // 自动打开浏览器} }
ES6 模块化
新建一个 fn.js
文件
function fn() {console.log('fn')
}
const name = 'b'export { // 注意这里不能有 default !!!fn,name
}
然后在 index.js
文件中可以导入刚才的模块
// 导入
import { fn, name, obj } from './fn' // 解构赋值
fn()
console.log('name is ', name)
console.log(obj)
如何配置 webpack 生产环境
- 新建一个
webpack.prod.js
文件
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {mode: 'production',entry: path.join(__dirname, 'src', 'index'),output: {filename: 'bundle.[contenthash].js',path: path.join(__dirname, 'dist')},module: {rules: [{test: /\.js$/,loader: ['babel-loader'],include: path.join(__dirname, 'src'),exclude: /node_modules/},]},plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'src', 'index.html'),filename: 'index.html'})]
}
在 package.json
中改变此行代码为:
"build": "webpack --config webpack.prod.js",
运行 npm run build
命令
可在 dist
文件夹下发现新增了一个文件:
前端用到的 linux 常用命令有哪些
- 公司的线上机器一般都是 linux(参考阿里云)
- 测试机也需要保持一致,用 linux
- 测试机或者线上机出了问题,本地又不能复现,需要去排查
登录线上机
ssh 用户名@192.168.10.21(ip地址或机器名称)
查看所有文件夹(平铺)/ 查看文件夹(列表)
ls -a
ll
清屏
clear
新建文件夹
mkdir abc(文件夹名)
删除文件夹
rm -rf abc
进入某个目录
cd dist(目录名)
修改文件名
mv index.html index1.html
移动目录(到上级目录)
mv bundle.58cf28598ed2e217d4b3.js ../bundle.58cf28598ed2e217d4b3.js
拷贝文件
cp a.js a1.js
删除文件(可以不用 rf,删除文件夹时必须用)
rm a1.js
新建文件
touch d.js
新建文件并且打开
vi d.js 或者 vim d.js
编辑内容
按下键盘的
i
键即可输入内容,利用上下左右键可移动光标保存:需要先关闭INSERT模式,点击左上角的
ESC键
关闭,输入:w
即可保存
打开文件后退出
:q
不想保存可直接退出(强制退出)
:q!
查看文件所有内容
cat package.json
查看文件前面几行的内容
head package.json
查看文件末尾几行的内容
tail package.json
查找内容(比如查找babel关键字是否存在某文件)
grep "babel" package.json(文件名)
查看命令文档教程
vimtutor
运行环境
- 运行环境即浏览器(server 端有 nodejs)
- 下载网页代码,渲染出页面,期间会执行若干 JS
- 要保证代码在浏览器中:稳定且高效
- 网页加载过程
- 性能优化 & 体验优化
- 安全
页面加载过程
题目
- 从输入 url 到渲染出页面的整个过程
- window.onload 和 DOMContentLoaded 的区别
知识点
- 加载资源的形式
- 加载资源的过程
- 渲染页面的过程
解答
- 从输入 url 到渲染出页面的整个过程
- 下载资源:各个资源类型,下载过程
- 渲染页面:结合 html css javascript 图片等
- window.onload 和 DOMContentLoaded 的区别
- window.onload 资源全部加载完才能执行,包括图片
- DOMContentLoaded DOM渲染完成即可,图片可能尚未下载
- 从输入 url 到渲染出页面的整个过程
资源的形式
- html 代码
- 媒体文件,如图片、视频等
- javascript css
加载过程
- DNS 域名解析:域名 -> IP 地址
- 浏览器根据 IP 地址向服务器发起 http 请求
- 服务器处理 http 请求,并返回给浏览器
渲染过程 -1
- 根据 HTML 代码生成 DOM Tree
- 根据 CSS 代码生成 CSSOM
- 将 DOM Tree 和 CSSOM 整合形成 Render Tree(渲染树)
渲染过程 -2
- 根据 Render Tree 渲染页面
- 遇到
<script>
则暂停渲染,优先加载并执行 JS 代码,完成之后再继续(因为这部分代码可能会改变当前渲染的结果) - 直至把 Render Tree 渲染完成
网页加载和渲染的示例:
示例 - 1
示例 - 2
思考:为何建议把 css 放在 head 中?
答:因为把css规则代码在这个dom树生成之前就完成加载,然后dom树生成之后就直接和css整合生成一个渲染树,这样可以一步渲染完成,不用重复进行了
示例 - 3
思考:为何建议把script代码放在最后?
答:假设不放在最后,渲染过程中遇到js代码,可能会停止渲染,加载完js之后又去继续渲染,会导致页面渲染过程比较长。我们期望的情况是把所有能渲染出来的东西先全部渲染出来,渲染完之后再把script代码进行执行
示例 - 4
渲染加载过程遇到img,并不会停止渲染,如果图片资源过大,这个位置会先空着,等待图片加载完毕会塞过来
window.onload 和 DOMContentLoaded
性能优化
- 是一个综合性问题,没有标准答案,但要求尽量全面
- 某些细节问题可能会单独提问:手写防抖、节流
- 只关注核心点,针对面试
性能优化原则:
- 多使用内存、缓存或其他方法
- 减少 CPU 计算量,减少网络加载耗时
- (适用于所有编程的性能优化——空间换时间)
从何入手
- 让加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码, SSR 服务器端渲染,缓存
- 使用更快的网络:CDN
- 让渲染更快
- CSS 放在 head,JS 放在 body 最下面
- 尽早开始执行 JS,用 DOMContentLoaded 触发
- 懒加载(图片懒加载,上滑加载更多)
- 对 DOM 查询进行缓存
- 频繁 DOM 操作,合并到一起插入 DOM 结构
- 节流 throttle 防抖 debounce
前端性能优化示例:
资源合并
把三个js文件合并为一个,可以减少网络请求的时间
缓存
- 静态资源加 hash 后缀,根据文件内容计算 hash
- 文件内容不变,则 hash 不变,则 url 不变
- url 和文件不变,则会自动触发 http 缓存机制,返回 304(资源未修改)
CDN
使用CDN可以更大程度满足网络的性能,是专门做静态文件的服务,根据地域来做网络服务的,CDN是非常快的
SSR(Server-Side Rendering 服务端渲染)
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非 SSR(前后端分离):先加载网页,再加载数据,再渲染数据
- 早期的 JSP ASP PHP,现在的 Vue React SSR
懒加载
开始的时候先加载一个比较小的预览图,什么时候需要什么时候加载,比如上滑屏幕,滑到某个位置才加载需要的图片
缓存 DOM 查询
多个 DOM 操作一起插入到 DOM 结构
尽早开始 JS 执行
手写防抖
监听一个输入框的,文字变化后触发 change 事件
直接使用 keyup 事件,则会频繁触发 change 事件
防抖:用户输入结束或暂停时,才会触发 change 事件
对于 输入框input1 设置防抖
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 = nullreturn 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))
手写节流
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置
- 直接用 drag 事件,则会频繁触发,很容易导致卡顿
- 节流:无论拖拽速度多快,都会每隔100ms触发一次
const div1 = document.getElementById('div1')div1.addEventListener('drag',function (e) {console.log(e.offsetX, e.offsetY);
})
拖拽 div1 时,会一直频繁触发 drag 事件
可设置一个定时器
const div1 = document.getElementById('div1')let timer = null
div1.addEventListener('drag', function (e) {if (timer) {return}timer = setTimeout(() => {console.log(e.offsetX, e.offsetY)timer = null}, 100)
})
可以把以上代码封装成节流函数
// 节流
function throttle(fn, delay = 100) {let timer = nullreturn 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)
安全
问题:常见的 web 前端攻击方式有哪些?
- XSS 跨站请求攻击
- XSRF 跨站请求伪造
XSS 攻击
- 一个博客网站,我发表一篇博客,其中嵌入
<script>
脚本 - 脚本内容:获取 cookie,发送到我的服务器(服务器配合跨域)
- 发布这篇博客,有人查看它,我轻松收割访问者的 cookie
- 一个博客网站,我发表一篇博客,其中嵌入
XSS 预防
替换特殊字符,如
<
变为<
>
变为>
<script>
变为<script>
,直接显示,而不会作为脚本执行前端要替换,后端也要替换,都做总不会有错
这里推荐一个工具
XSRF 攻击 - 1
你正在购物,看中了某个商品,商品 id 是 100
付费接口是 xxx.com/pay?id=100,但没有任何验证
我是攻击者,我看中了一个商品,id 是 200
我向你发送一封电子邮件,邮件标题很吸引人
但邮件正文隐藏着
<img src=xxx.com/pay?id=200 />
你一查看邮件,就帮我购买了 id 是 200 的商品
XSRF 预防
- 使用 post 请求
- 增加验证,例如密码、短信验证码、指纹等
【前端面试课程重点总结】相关推荐
- 前端面试 | JavaScript知识点 | 课程笔记
前端面试课程笔记 以上方链接内课程内容为主,知识点可能不全面,仅作为自用备忘 Ch3 作用域和闭包 3.1 作用域和自由变量 题目: this的不同应用场景,如何取值? 手写bind函数 实际开发中闭 ...
- 前端面试官问闭包,怎样回答脱颖而出
闭包这个话题一直都是前端面试的重点,下面我将结合自己的春招面试经验,关于闭包这个问题讲讲技术面试官会对它进行怎么一个提问? 1.闭包是什么? 闭包是js的一种语法特性. 闭包就是能够读取其他函数内部变 ...
- 前端面试真题系列(一)-李游Leo-专题视频课程
前端面试真题系列(一)-49人已学习 课程介绍 在鱼龙混杂的前端行业中,面试一直是一门非常重要的课程,尤其是笔试题. 而真实的面试题意味着你已经得到了面试邀请,在进入这家公司之前的第一 ...
- LienJack-2年前端面试心路历程(字节跳动、YY、虎牙、BIGO)
LienJack-2年前端面试心路历程(字节跳动.YY.虎牙.BIGO) 大厂面经 字节跳动 1 面 对 tree-shaking 的了解 虽然生产模式下默认开启,但是由于经过 babel 编译全部模 ...
- 2019 web 前端面试总结(内附面经)
这篇文章不适合拿到 BAT 的大佬及自制力特别差的人 本文只是提供复习的思路,以及我自己的一些面经,并没有具体的题目 基本情况 据说先把 offer 亮出来才能吸引你们看下去.目前一共有五个.分别是顺 ...
- 2 年前端面试字节跳动、YY、虎牙、BIGO心路历程总结
作者 | LienJack 来源 | https://juejin.im/post/5e85ec79e51d4547153d0738 本文将先从个人背景讲起,然后谈谈在字节跳动.虎牙.YY 以及 BI ...
- 看完跳槽少说涨 5 K,前端面试从准备到谈薪完全指南(全是精华)
首先我们将从以下几个角度来聊聊面试这件事情 面试题篇 面试题只能应对 1 - 2 面,刷题固然重要,但是对于项目相关的准备也是必须的.一般来说目前面试题能准备的范围如下: JS 基础 / 进阶相关HT ...
- 看完跳槽少说涨 5 K,前端面试从准备到谈薪完全指南(近万字精华)
本文将从以下几个角度来聊聊面试这件事情 文章首发自我的 Github,欢迎关注. 面试题篇 面试题只能应对 1 - 2 面,刷题固然重要,但是对于项目相关的准备也是必须的.一般来说目前面试题能准备的范 ...
- 校招社招必备核心前端面试问题与详细解答
本文总结了前端老司机经常问题的一些问题并结合个人总结给出了比较详尽的答案.网易阿里腾讯校招社招必备知识点. 原理讲解参考:前端增长-重新定义大前端 在线课程:网易云课堂课程 思否课堂 官方博 ...
最新文章
- 2021计算机技能高考考纲,2021年湖北省技能高考技能考试大纲(计算机类)(16页)-原创力文档...
- IP选路与动态选路协议(六)
- 运用Edraw为WPF应用程序嵌入Office文档的方法总结
- matlab储备池算法,储备池计算概述.pdf
- WebP 在减少图片体积和流量上的效果如何?WebP 技术实践分享
- 自动驾驶红旗车,背后站着小马哥
- 打车平台Lyft获Magna 2亿美元投资,携手打造自动驾驶汽车
- 负载均衡和故障转移的使用案例
- PHP常用函数总结(一):
- 关于使用npm下载资源包的一些常用命令
- 使用融云 SDK 避坑指南之 iOS13 推送失败
- OutMan——C语言中的冒泡排序、选择排序、折半查找以及指针的介绍
- 郭德纲家训--话糙理不糙
- win8.1快捷操作集合
- XRouter 一个轻量级的Android路由框架,基于ARouter上进行改良,优化Fragment的使用,可结合XPage使用
- 张学友-歌神同行.叁(国语篇)2019【SACD-ISO】
- Shader效果实现:双色渐变
- Intellij IDEA 在win10 中输入法的输出框不跟随
- STM8S105S4T6C和STM8S105C6T6对比
- html+word+clou,AE脚本:Word Cloud 1.0.3_文字云排版动画脚本+教程
热门文章
- 云计算进化史及服务模式
- 【运筹学】对偶理论 : 互补松弛定理应用 ( 原问题与对偶问题标准形式 | 已知原问题最优解求对偶问题最优解 | 使用单纯形法求解 | 使用互补松弛定理公式一求解 | 互补松弛定理公式二无效 ) ★★
- 深剖程序环境与预处理机制
- Adb shell命令直接打开语言设置界面
- Aquarius's Trial F - 6 HDU - 2102 A计划
- Google软件测试之道(读书笔记)
- 讲讲Python爬虫绕过登录的小技巧
- FM/AM收音机原理
- java网络编程实用精解_Java网络编程实用精解
- 2021国潮新消费大会落幕,新国潮产业进入“黄金十年”