1. HTML
1. 必考:你是如何理解 HTML 语义化的?
荒野阶段:最开始是 PHP 后端写 HTML,不会 CSS,于是就用 table 来布局。table 使用展示表格的。严重违反了 HTML 语义化,索然很快就会写出页面,但是后期维护很麻烦。
美工阶段:后来有了专门的写 CSS 、html 的前端,他们只会使用 DIV + CSS 布局,主要是用 float 和绝对定位布局。稍微符合一点,但还是不够语义化,因为不知道每个div什么意思,其实是对于第一种方法的换汤不换药的做法。
前端阶段:再后来,前端越来越专业化,知道 HTML 的各个标签的用法,于是会使用恰当的标签来展示内容,而不是傻傻的全用 div,会尽量使用标题用 h1~h6、列表用ul/ol>li、段落用 p、导航栏用header, 侧边栏用aside,底部用footer,链接用a标签,图片用img等等标签分别表示不同的意思。目前我们写页面的就是html语义化标签。
语义化的好处是易读、有利于SEO等。
2.meta viewport 是做什么用的,怎么写?
这个标签是控制页面在移动端不要缩小显示。具体写法是
一开始,所有页面都是给PC准备的,乔布斯推出 iPhone 3GS,页面是不适应手机屏幕的,所以乔布斯的工程师想了一个办法,默认把手机模拟成 980px,页面缩小。
后来,智能手机普及,这个功能在部分网站不需要了,所以我们就用 meta:vp 让手机不要缩小我的网页。
3.你用过哪些 HTML 5 标签?
最常见的有:
内容相关的:header/main/footer/article等
-
功能相关的:canvas/video.audio等
* canvas绘制(的用法等):mdn,首先获取canvas,然后获取它的二维上下文,在设置笔的颜色最后设置笔刷的范围,就能绘图了。 * mdn,添加视频 video标签 用到的属性有:
4.H5 是什么?
移动端页面就叫做H5,注意注意!html5是一项标准,而H5是是一门技术,他俩不是一个概念。
2. CSS
1. 必考:两种盒模型分别说一下
- border-box怪异(IE)和模型: width = padding + content + border
- content-box标准盒模型:width = content
高度和宽度类似。
两种盒模型比较:正常情况下都用border-box,原因:border-box写起来更方便。
2. 必考:如何居中?
- 水平居中:
- 块级元素:
- 内联元素:
- 垂直居中:
- parent没有指定高度:child垂直居中方法:padding :10px 0;
- parent 的 height 写死了,就很难把 .child 居中,以下是垂直居中的七种方法:
1. table自带功能:【http://js.jirengu.com/gaquk/2/】
2. 100%高度的after before 加上inline-block: 【http://js.jirengu.com/poveg/1/】【http://js.jirengu.com/poveg/3/】
3. div装成table: 【http://js.jirengu.com/yusux/6/】
4. margin-top:-50%:【http://js.jirengu.com/sugex/1/】
5. translate: -50%: 【http://js.jirengu.com/sugex/3/】
6. absolute margin-auto : 【http://js.jirengu.com/sugex/5/】
7. flex布局:【http://js.jirengu.com/sugex/7/】
3. 考:flex 怎么用,常用属性有哪些?
Flex是Flexible Box的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。Flex布局有两层,采用flex布局的元素称为flex容器,其子元素则自动成flex item,即项目.
-
flex容器属性:
flex-direction:决定项目的排列方向。 flex-wrap:即一条轴线排不下时如何换行。 flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。 justify-content:定义了项目在主轴上的对齐方式。(justify) align-items:定义项目在交叉轴上如何对齐。 align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。(换行会产生多轴)
flex-item属性:
order:定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex-grow:定义项目的放大比例,如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
flex-shrink:定义了项目的缩小比例,默认为1,如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)。
flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
举例子问题:在行栏中,最左边是一个按钮,右边有两个按钮
实现方法:
- logo
-
- 登录
-
- 注册
-
4.必考:BFC 是什么?
中文翻译:块级格式化上下文,举个例子:我如果给一个div添加overflow:hidden;那么它里面的浮动元素就会被包裹起来。
- 举例(抽象问题具体化:不能直接回答BFC是什么,而是举含有BFC的例子):
- overflow:hidden;是清楚浮动,但是不建议使用这个清楚浮动,而是用 .clearfix{}来清楚浮动
- overflow:hidden;消除父子margin 合并。
5. CSS 选择器优先级
选择器优先级的说法 class第一位,id第二位,……是错的,在css2时带还能用,但是目前css3时带是不能用。【注意】如果hr引导你往这个错误的方向,那就说这个。
1. 越具体优先级越高
2. 写在后面的覆盖写在前面的
3. !important的优先级最高,但是建议少用
6. 清除浮动说一下(背代码)
- .clearfix{
content:'';
display:block/table;
clear:both;
}
把这个clearfix加到容器上,里面的子元素的浮动就被清楚了
3.原生 js
1. 必考:ES 6 语法知道哪些,分别怎么用?
1. 作用域:
- var 声明的变量没有块级作用域m在语句块里声明的变量作用域是其所在的函数或者 script 标签内,你可以在语句块外面访问到它。
- let : 声明块级变量,是有块级作用域的,而且不能重复声明。在处理构造函数的时候,可以通过let声明而不是闭包来创建一个或多个私有成员。
- const:声明块级变量,是有块级作用域的,常量的值不能通过重新赋值来改变,并且不能重新声明。
2. 箭头函数:
- 箭头函数的语法比函数表达式更简洁,没有自己的this\arguments等,箭头函数更适用于那些需要匿名函数的地方,并且不能用作构造函数。箭头函数只有IE浏览器不能兼容。
- sum = (a, b) => a + b
- nums.forEach( v => { console.log(v) })
3. 模板字符串:
1. 实现多行字符串:
```
console.log(`string text line 1
string text line 2`)
```
- 插入表达式:
var a = 10; var b = 5; console.log(`Fifteen is ${a + b } and not ${a * b} `)
4. 解构赋值:可以将属性/值从对象/数组中取出,赋值给其他变量,对象或数组诸葛对应值或表达式。
- 结构数组:
[a,b] = [10,20]
console.log(a); //10
console.log(b); //20
[a,b,...rest] = [10,20,30,40,50]
console.log(rest); //[30,40,50]
- 交换变量:
var a = 10;
var b = 20;
[a,b] = [b,a];
- 用正则表达式匹配提取值:用正则表达式的 exec()方法匹配字符串会返回一个数组,该数组第一个值是完全匹配正则表达式的字符串,然后的值是匹配正则表达式括号内内容部分。解构赋值允许你轻易地提取出需要的部分,忽略完全匹配的字符串——如果不需要的话。
function parseProtocol(url) {
var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
if (!parsedURL) {
return false;
}
console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]
var [, protocol, fullhost, fullpath] = parsedURL;
return protocol;
}
console.log(parseProtocol('https://developer.mozilla.org/en-US/Web/JavaScript')); // "https"
- 结构对象:
//基本赋值
var obj = {p:42,q:true}
var {p.q} = obj
console.log(p) //42
console.log(q) //true
//无声明赋值
var a,b;
({a,b} = {a:1,b:2})
- 对象解构中的 Rest
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
a; // 10
b; // 20
rest; // { c: 30, d: 40 }
5. 模块
1.导入:import:
- 静态的inport语句带入由另一个模板导出的绑定
//语法(必须在严格模式下,type='module'的script标签中使用)
import defaultExport from "Moudule-name"
- 类似于函数的动态 import(),它不需要依赖
type="module"
的script标签。
在 script 标签中使用nomodule
属性,可以确保向后兼容
let module = await import('/modules/my-module.js')
- 导出:export:在创建 javascript 模块时,export语句从模块中导出函数、对象或原始值,以便其他程序通过import 导入并使用他们。
//导出单个单个特性
export let name1,name2,...,nameN;(var const 一样)
export function FunName(){}
export class ClassName{}
//导出列表
export {name1,name2,...,nameN}
//重命名导出
export {Variable1 as name1,variable2 as name2,...,namwN}
- 默认导出:export default
export default expression
export default function(){}
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
4.模块重定向
export {defaullt} from './other-module';
exprt * from './other-module';
6.class 类 :类其实是一个特殊的函数,类语法糖有两个部分组成:类表达式和类声明
- 类声明:不像函数声明,类声明不会声明提升,所以需要先声明,否则返回一个ReferenceError
class Rectangle{
constructor(width,height){
this.width = width;
this.height = height;
}
}
- 类表达式:
//匿名类:
let Rectangle = class {
constructor(width,height){
this.width = width;
this.height = height;
}
}
//具名类
let Rectangle = class Rectangle{
constructor(width,height){
this.width = width;
this.height = height;
}
}
- 使用extends创建子类(注意:如果在子类中使用构造函数则在使用之前调用super())
class Animal{
constructor(){
this.name = name
}
speak(){
console.log(this.name + 'makes a noise')
}
}
class Dog extends Animal{
speak(){
console.log(this.name + 'barks')
}
}
var dog = new Dog('mike');
dog.speak();//mike barks
- 也可以继承传统的基于函数的类
function Animal(name){
this.name = name
}
Animal.prototype.speak = function (){
console.log(this.name + 'make a noise')
}
class Dog extends Animal{
speak(){
console.log(this.name + 'barks')
}
}
var dog = new Dog('mike')
dog.speak(); //mike barks
- 调用超类
class Cat{
constructor(name){
this.name = name
}
speak(){
console.log(this.name + 'makes a noise')
}
}
class Lion extends Cat{
speak(){
super.speak();
console.log(this.name + 'roars')
}
}
7.迭代器
for of VS for in :
这两个语句都是迭代一些东西;
for in 以任意顺序迭代对象的(synbol以外的)可枚举属性;
for of 遍历可迭代对象要迭代的数据。
- 一句话总结:for of 遍历的结果是值,for in 遍历的结果是索引。
- 举例一:
let arr = ["Apple","Samsung","Nokia","Xiaomi"];
for (let value of arr){
console.log(value)
}
// Apple Samsung Nokia Xiaomi
for (let x in arr){
cosnole.log(x)
}
// 0 1 2 3
- 举例二:
let obj = { Apple:"iPhone X", Samsung:"Galaxy 6", Nokia:"Lumia",Xiaomi:"Note 3" }
for(let x of obj){
console.log(x)
}
// !! 这样就报错了(obj is not iterable)原因是这个对象的值的索引必须是可迭代的
for(let x in obj){
console.log(x)
}
// Apple Samsung Nokia Xiaomi
- 举例三:
// for-of 遍历可以迭代的任意对象。而 for-in 遍历的是必需有明确索引的对象
function* company(){
yield 'Apple';
yield 'Samsung';
yield 'Nokia';
yield 'Xiaomi';
}
for(let x of company()){
console.log(x)
}
// Apple Samsung Nokia Xiaomi
for(let x in company()){
console.log(x)
}
// !! 这将什么都不会输出
- 总结:for of 遍历的结果是值,并且被遍历的对象必须是可以迭代的,for in 遍历的结果是索引,并且这个索引必须是明确的。
8.Symbol :
ES5的属性名都是字符串,容易造成属性名冲突,ES6引进了一种新的原始数据类型:Symbol,表示独一无二值(每个从Symbol()返回的symbol值都是唯一的)。
Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
它每次都会创建一个新的 symbol类型:
//没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 //false
//有参数的情况下:
var s1 = Symbol('foo');
var s2 = Symbol('foo');
s1 === s2 //false
2. 必考 Promise、Promise.all、Promise.race 分别怎么用?
promise对象用于返回一个异步操作的最终完成结果。
-
promise对象使用方法:
- .then方法:
$.ajax(……).then(成功函数,失败函数) - 链式 .then方法:
$.ajax(……).then(成功函数,失败函数).then(成功函数2,失败函数2)
- .then方法:
如何自己生成promise对象:
function fn(){
return new Promise((resolve, reject)=>{
成功时调用 resolve(数据)
失败时调用 reject(错误)
})
}
fn().then(success, fail).then(success2, fail2)
- Promise.all()的用法:
promise.all(iterable)方法返回一个promise实例,其参数iterable是可迭代对象,接受一个或多个值的数组(比如,立即值、promise、thenable)。它返回一个promise,此方法在集合多个 promise 的返回结果时很有用。
举例:promise1和promise2都成功才会调用success1
Promise.all([promise1, promise2]).then(success1, fail1)
- promise.race()的用法:顾名思义,Promise.race就是赛跑的意思,意思就是说,promise1和promise2只要有一个成功就会调用success1,不管结果本身是成功还是失败状态。所接受的参数是一样的,接收多个值的数组。
Promise.race([promise1, promise2]).then(success1, fail1)
3. 手写函数防抖和函数节流
- 函数节流:可以理解为CD冷却时间(就是在指定时间段内不能执行函数):
function fn(){}
CD = false
button.oncLick = function(){
if(CD){
}else{
fn()
CD = true
var timerId = setTimeout(()=>{
CD = false
} ,3000)
}
}
- 防抖函数:简单理解为把任务带着一起做,比如说我是一个送外卖的,然后规定如果定了一个外卖就再等5分钟,如果来了继续再等5分钟,如果5分钟之内没来过就开始把积累的外卖都送。
function fn(){}
var timerId = null
button.onclick = function(){
if(timerId){
window.clearTimeout(timerId)
}
fn()
timerId = null
timerId = setTimeout(()=>{
timerId = null
},5000)
}
}
4. 必考:手写AJAX(背诵代码)
function get/post(url,data,callback){
var xhr = new XMLHttpRequest()
xhr.open('POST',"/xxx")
xhr.setRequestHeader("Content-type":"application/x-www-form-url-encoded")
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.response.Status === 200 || xhr.status === 304){
callback(response)
}
}
xhr.send("a = 1 & b = 2")
}
5. 考:这段代码里的 this 是什么?
函数里的this就看函数是怎么调用的:
- fn() 在正常情况下:window;在'use strict'下:undefined
- obj.fn() this =>obj
- fn.call(xx) this=>xx
- fn.appy(xx) this => xx
- fn.bind(xx) this=> xx
- new Fn() this=>新的对象(生成的实例)
- fn = () => {} this=>外面的的this
6, 必考:闭包/立即执行函数是什么?
- 闭包是什么?
由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
//这里就有闭包,local 变量和 bar 函数就组成了一个闭包(Closure)。
function foo(){
var local = 1
function bar(){
local++
return local
}
return bar //只是为了 bar 能被使用,也跟闭包无关,把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了
}
var func = foo()
func()
- 闭包的作用:
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁,由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的Ajax成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的便利。开发者不用去写钩子函数来操作上级函数作用域内部的变量了。闭包随处可见,一个Ajax请求的成功回调,一个事件绑定的回调方法,一个setTimeout的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
7. 必考:什么是 JSONP,什么是 CORS,什么是跨域?
1. JSONP:
JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器集成JavaScript返回至 客户端,通过javascript callback形式实现跨域访问。
-
JSONP的完整过程:
- 请求方:一个网站的前端(浏览器):创建一个script,src指向响应方,同时传一个查询参数callback=xxx;不用callbackName,而是用callback。
- 响应方:另一个网站的后端(服务器),根据查询参数callbackName,构造形如xxx.call(undefined,'你要的参数')响应。
- 浏览器接收到响应就会执行回调函数xxx.call(undefined,'你要的参数')。
- 请求方就会知道了他想要的数据(执行函数体里面的执行语句)。
这就是JSONP
-
行业约定:
- 查询参数要用callback来指定,在jQuery中用jQuery_callback。
- 回调函数名不固定,就用用随机函数名,简单又避免函数名重复的问题,例如不叫xxx ,而是jQuery12343这种随机数;只要符合变量名规则就行。
- 调用函数后马上删除,避免污染全局变量。
为什么SJONP只能发起不能发post请求?
因为JSONP是通过动态创建scriipt实现的,动态创建的script的时候只能发送get请求,不能发送post请求.jQuery实现Ajax:
$.ajax({
url : "http://jack.com:8080/pay",
dataType : "jsonp",
success : function(response){
if(response === "success"){
... //执行语句
}
}
})
2. CORS:跨源资源分享
Cross-Origin Resource Sharing,是W3C标准,它是解决Ajax跨源请求的根本方法,CORS 允许发送任何类型的请求
用法:
response.setHeader('Access-Control-Allow-Origin':'http://haha.com:8080')
//设置CORS,http://haha.com:8001这个网站可以访问本服务器响应内容
后台用 JSONP 还是 CORS? 首先JSONP 不能发起POST请求但可以实现跨源请求。CORS可以发起多种类型的请求(后台必须要用POST请求的话只能用CORS),但是需要访问的话在后台设置CORS才能实现跨源请求。
3. 跨域:
跨域是指从一个域名的网页去请求另一个域名的资源。比如从http://www.baidu.com/ 页面去请求 http://www.google.com 的资源。跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域。
8. 常考:async/await 怎么用,如何捕获异常?
捕获异常就是抛出promise.reject异常值
aysnc function fn(){
try{
var z = await.promise.reject(3)
}catch(e){
console.log(e) //3
}
}
fn();
9.常考:如何实现深拷贝?(背代码)
- JSON来实现深拷贝:
var a = {...} var b = JSON.parse( JSON.stringify(a) )
缺点:JSON 不支持函数、引用、undefined、RegExp、Date……
- 递归:
function clone(object){
var object2
if(! (object instanceof Object) ){
return object
}else if(object instanceof Array){
object2 = []
}else if(object instanceof Function){
object2 = eval(object.toString())
}else if(object instanceof Object){
object2 = {}
}
//你也可以把 Array Function Object 都当做 Object 来看待,参考 https://juejin.im/post/587dab348d6d810058d87a0a
for(let key in object){
object2[key] = clone(object[key])
}
return object2
}
判断类型:
检查循环引用(环):
10. 常考:如何用正则实现 trim()?
function trim(string){
return string.replace(/^\s | \s+$/g,' ')
}
11. 考:不用 class 如何实现继承?用 class 又如何实现?
- 不用class实现继承:原型链
function Animal(){ this.body = '肉体' } Animal.prototype.move = function(){ } function Human(name){ Animal.apply(this, arguments) this.name = name } // Human.prototype.__proto__ = Animal.prototype // 非法 var f = function(){} f.prototype = Animal.prototype Human.prototype = new f() Human.prototype.useTools = function(){} var qinglin = new Human()
- 使用class 实现继承:
class Animal{
constructor(){
this.body = '肉体'
},
move(){}
}
class Dog extends Animal{
constructor(name){
super(name)
this.name = name
},
useTools(){}
}
var qinglin = new Human()
12. 常考:如何实现数组去重?
- 第一种方案:
//计数排序的逻辑(只能正整数)
var a = [4,2,5,6,3,4,5]
var hashTab = {}
for(let i=0; i
- 第二种方案:
function unique(array){
return [...new set(array)]
}
13. 弃:== 相关题目(反着答)
14. 送命题:手写一个 Promise
function Promise(executor) {
let self = this;
self.status = 'pending'; //等待态
self.value = undefined; //成功的返回值
self.reason = undefined; //失败的原因
function resolve(value){
if(self.status === 'pending'){
self.status = 'resolved';
self.value = value;
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
try{
executor(resolve, reject);
}catch(e){
reject(e);// 捕获时发生异常,就直接失败
}
}
//onFufiled 成功的回调
//onRejected 失败的回调
Promise.prototype.then = function (onFufiled, onRejected) {
let self = this;
if(self.status === 'resolved'){
onFufiled(self.value);
}
if(self.status === 'rejected'){
onRejected(self.reason);
}
}
module.exports = Promise;
4. Dom
1. 考:事件委托
//错误版(但是可能能过)bug 在于,如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对。
ul.addEventListener('click',function(e){
if(e.target.tagName.tiLowerCase === 'li'){
console.log('li被点击了')
fn() //执行谋个函数
}
})
最标准版:
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}
2. 曾考:用 mouse 事件写一个可拖曳的 div
//html
//js
var dragging = false
var position = null
xxx.addEventListener('mousedown',function(e){
dragging = true
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){
if(dragging === false){return}
console.log('hi')
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(xxx.style.left || 0)
const top = parseInt(xxx.style.top || 0)
xxx.style.left = left + deltaX + 'px'
xxx.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function(e){
dragging = false
})
5. HTTP
1. 考:HTTP 状态码知道哪些?分别什么意思?
状态码 202 表示:服务器已接受请求,但尚未处理。
状态码 204 表示:请求处理成功,但没有资源可返回。
状态码 206 表示:服务器已经成功处理了部分 GET 请求。
状态码 301 表示:请求的资源已被永久的分配了新的 URI。
状态码 302 表示:请求的资源临时的分配了新的 URI。
状态码 400 表示:请求报文中存在语法错误。
状态码 401 表示:发送的请求需要有通过 HTTP 认证的认证信息。
状态码 403 表示:对请求资源的访问被服务器拒绝了。
状态码 404 表示:服务器上无法找到请求的资源。
状态码 500 表示:服务器端在执行请求时发生了错误。
状态码 503 表示:服务器暂时处于超负债或正在进行停机维护,现在无法处理请求。
2. 大公司必考:HTTP 缓存有哪几种?
- 几种缓存:
Etag —— 是根据浏览器和服务器资源的唯一特征标识符(比如MD5),果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
Expires —— 自然也需要有个东西来启用缓存和定义缓存时间,对http1.0而言,Expires就是做这件事的首部字段。 Expires的值对应一个GMT(格林尼治时间),指定的是过期时间点(相对于服务器时间而言),来决定是否重新发起请求。这个方法会有bug,但是如果用户的本地时间错乱了,可能会有问题
Cache-Control —— 设置一个时间段,它意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一段时间,在后续这段时间内,用户重新访问该资源则无须发送请求。 当然这种组合的方式也会有些限制,比如 no-cache 就不能和 max-age、min-fresh、max-stale 一起搭配使用。 - 之间区别:
- Expire和Cache-Control之间区别:前者是指定过期时间点,而后者是指定有效缓存的时间段。
- Etag 和 Cache-Control之间的区别:Cache-Control的读取时从浏览器本地文件中读取的(无请求),而Etag是有请求的。
- 近几年有些大公司使用 PWA来缓存:那么浏览器的http缓存顺序是什么?
3. 考:GET 和 POST 的区别
-
错误答案:
- POST安全,GET不安全,
- Get 有长度限制(URL 长度有1024字节),而Post没有长度限制(一般URL的长度限制在4M),
- Get的参数是放在URL上,Post的参数放在消息体里面,
- Get只需要一个报文,Post需要两个以上。
- Get是幂等的,Post不幂等。
正确答案: Get是获取数据,Post是提交数据。其他的没有本质的区别。
4. ookie V.S. LocalStorage V.S. SessionStorage V.S. Session
- Cookie —— http协议的一部分
- session —— 存在服务器中的一小段哈希表(每个用户都有自己的sessionId请求时存放在session中,),是服务器与浏览器之间的一段时间内的会话。
- LocalStorage —— html5提供的API ,常用属性有 setItem()/getItem()/LocalStorage.clear()/removeItem().实现了不同页面中保存变量且其值不变。让后端有了记忆力。
- Cookie 与 session之间的关系:
a. Cookie 存在浏览器的文件里,Session 存在服务器的文件里
b.Session 是基于 Cookie 实现的,具体做法就是把 SessionID 存在 Cookie 里 - Cookie 与 LocalStorage之间的关系:
a. Cookie大小约4kb,LocalStorage一般有5M,
b. Cookie 会被发送到服务器,LocalStorage不会
c.Cookie 会存储用户信息,LocalStorage存储没用的东西。 - SessionStorage 与 LocalStorage之间的关系:
a.LocalStorage 一般不会自动过期(除非用户手动清除),而 SessionStorage 在session(会话)结束时过期(如关闭浏览器).
5. HTTP1和 HTTP2的区别:
HTTP2的新特性:
a. 多路复用 (MultiPlexing) —— 解决了线头阻塞的问题,减少了 TCP 连接数量和 TCP 连接慢启动造成的问题
b. 服务端推送 (Server Push) —— 浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求。
c. Header 压缩 (HPACK) —— 使用 HPACK 算法来压缩首部内容
d. 二进制分帧层 (Binary Framing Layer)—— 帧是数据传输的最小单位,以二进制传输代替原本的明文传输,原本的报文消息被划分为更小的数据帧。
6. 框架
6.1 Vue
1. 必考:watch 和 computed 和 methods 区别是什么?
watch是监听数据,computed是计算属性;
watch和computed的最大区别是:computed是有缓存的,如果computed依赖的属性没有变化,computed属性就不会重新激计算。而watch则是看到一次计算一次。
2. 必考:Vue 有哪些生命周期钩子函数?分别有什么用?
Vue的生命周期:
- beforeCreate : 实例初始化之后,数据观测(data observoe)和事件配置(event/watcher)调用之前调用。
- created : 实例初始化之后,数据观测和事件配置调用之前调用。
- beforeMount : 挂载组件开始之前被调用,相关的render函数首次被调用
- Mounted : el 被创建的vm.$el 替换,被挂载到实例上之后调用该钩子,
- beforeUpdate : 数据更新时,发生在虚拟DOM 打补丁之前。
- update : 由于数据更新导致的虚拟DOM重新渲染和打补丁之后会调用。
- beforeDestroy :实例被销毁之前,这个时候实例仍然完全可用。
- destroyed : 实例销毁之后调用,调用后Vue 实例指示的所有东西都会被解除绑定,所有事件监听都会被移除,所有 的子实例也会被销毁。
Vue 的 Mounted生命周期进行数据请求。
3. 考:Vue 如何实现组件间通信?
- 父子组件: $emit('xxx',data) on('xxx',function(){})
- 爷孙组件之间: 两次 v-on 通过爷爷 => 爸爸 => 孙子
- 任意组件之间通信:eventBus = new Vue() 来通信;主要API: eventBus.emit ; 任意组件之间也可以使用 Vuex来通信。
- Vuex是专门为Vue应用程序开发的状态管理工具。Vuex的核心概念有以下几种:
state => 基本数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
4. 必考:Vue 数据响应式怎么做到的?(以前的概念:双向绑定)
v-model实现响应式:一句话总结就是:在数据渲染时使用prop渲染数据,将prop绑定到子组件自身的数据上,修改数据时更新自身数据来替代prop,watch子组件自身数据的改变,触发事件通知父组件更改绑定到prop的数据。
a. Object.defineProperty 通过 getter 和 setter 劫持了对象赋值的过程,在这个过程中可以进行更新 dom 操作等等。
b. Vue 不能检测到对象属性的添加或删除,解决方法是手动调用 Vue.set 或者 this.$set.
const component = {
model : {
prop : 'value',
event : 'change'
},
props : ['value'],
template:`
`,
methods : {
handleInput(e){
this.$emit('change',e.target.value)
}
}
}
5. 必考:Vue.set 是做什么用的?
Vue不能识别添加或删除属性的变化,vue不能在已经创建的实力上动态的添加新的根级响应式属性,而是必须用 Vue.set方法将响应式属性添加到嵌套对象上。
6. Vuex 你怎么用的?
Vuex是Vue.js应用的程序的状态管理工具。
其核心概念有以下:
state : 基本数据
getters : 基本数据派生的数据
mutations : 提交更改数据的方法,同步
actions : 相当于装饰器,封装mutations,使之可以异步实现
mudules : 模块化Vuex;
7. VueRouter 你怎么用的?
VueRouter是Vue.js官方的路由器管理。常考点:重定向模式,history模式,导航守卫,路由懒加载(import('./Foo.vue')//组件路径,返回promise对象)。
1. 重定向模式:“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b。
2. 导航守卫:导航守卫是路由跳转过程中的一些钩子函数,路由跳转是一个大的过程,这个大的过程分为跳转前中后等多个细节过程,在每一个过程中都有一个函数,这些个函数能让你操作一些其他的操作的机会,这就是导航守卫。
导航守卫有三种模式:
* 全局的:指路由在实例上直接操作的函数:钩子函数按执行顺序包括beforeEach、beforeResolve、afterEach三个。
* 单个路由独享的:指单个路由配置的时候可以设置的函数,只有一个函数:beforeEnter,其参数有to,from,next.
* 组件内的:组件内执行的钩子函数,类似于组件内的生命周期,钩子函数按执行顺序包括beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave三个。
3. history模式:history模式采用html5的新特性,提供了两个新方法,pushState ,replaceState,可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
4. 路由懒加载:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
结合 Vue 的异步组件和 Webpack 的[代码分割功能],轻松实现路由组件的懒加载。
使用方法:
import('./Foo.vue') // 返回 Promise。
const router = new VueRouter({
routes: [
{
path: '/Foo',
name: 'Home',
component:() = import('../views/home')
}
]
})
8. 路由守卫是什么?(导航守卫的钩子函数来完成)
-
- 业务:
- 监听整个项目的路由变化情况 全局的前置守卫
- 监听某个路由的变化情况 路由的独享守卫
- 监听的路由组件的路由变化情况 组件内的导航守卫
- 业务:
6.2 React
1. 必考:受控组件 V.S. 非受控组件
举个例子:
2. 必考:React 有哪些生命周期函数?分别有什么用?(Ajax 请求放在哪个阶段?)
- 生命周期函数:
一、初始化阶段:
getDefaultProps:获取实例的默认属性
getInitialState:获取每个实例的初始化状态
constructor :声明State变量
componentWillMount:组件即将被装载、渲染到页面上
render:组件在这里生成虚拟的DOM节点
componentDidMount:组件真正在被装载之后
二、运行中状态:
componentWillReceiveProps:组件将要接收到属性的时候调用
shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止
render调用,后面的函数不会被继续执行了)
componentWillUpdate:组件即将更新不能修改属性和状态render:组件重新描绘
componentDidUpdate:组件已经更新三
销毁阶段:
componentWillUnmount:组件即将销毁 - 异步请求:ajax异步请求数据应该放在 componentDidMount函数中。
3. 必考:React 如何实现组件间通信?
- 父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯
- 子组件向父组件通讯: props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中
- 兄弟组件通信: 找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 跨层级通信: Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过
- 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入event模块进行通信
- 全局状态管理工具: 借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态
4. 必考:shouldComponentUpdate 有什么用?
shouldComponentUpdate 这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在 shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。
5. 考:虚拟 DOM 是什么?
react操作中render的结果并不能得到一个真正的DOM节点,而是一个轻量级的javascript对象,我们称之为虚拟DOM。
就是用来模拟DOM的一个对象,有一些常用属性,并且更新 UI 主要就是通过对比(DIFF)旧的虚拟 DOM 树 和新的虚拟 DOM 树的区别完成的,对真实的DOM进行最小化的修改。
- 优点:虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能.
- 缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
6. 必考:什么是高阶组件?
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。最常见的高阶组件是 Redux 的 connect 函数。比如 connect(mapState)(MyComponent) 接受组件 MyComponent,返回一个具有状态的新 MyComponent 组件。除了简单分享工具库和简单的组合,HOC 最好的方式是共享 React 组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。
- 高阶组件是一个以组件为参数并返回一个新组件的函数。
- 如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。
7. React diff 的原理是什么?
对比两颗DOM树的区别,找出其中不同的部分。React diff 作为Virtual DOM的加速器,其算法上的改进优化是 React 整个界面渲染的基础,以及性能提高的保障,同时也是 React 源码中最神秘、最不可思议的部分,本文将剖析 React diff 的不可思议之处。
8. 考 Redux 是什么?
Redux是一个库,它是javascript状态容器(事件分发中心),提供可以预策划的状态管理。核心概念的名词:action reducer store 单向数据流。
常用的API 有:store.dispatch(action) store.getState() 等。
9. connect 的原理是什么?
是react-redux库提供的API,是基于react和redux封装的函数,负责连接React和Redux,把组件和store连接起来组成一个新的组件。
* 获取state: connect通过context获取Provider中的store,通过store.getState()获取整个store tree 上所有state
- 包装原组件: 将state和action通过props的方式传入到原组件内部wrapWithConnect返回一个ReactComponent对象Connect,Connect重新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps, mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给WrappedComponent
- 监听store tree变化: connect缓存了store tree中state的状态,通过当前state状态和变更前state状态进行比较,从而确定是否调用this.setState()方法触发Connect及其子组件的重新渲染
10. (组件的)状态(state)和属性(props)之间有何不同?
- State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。
- Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据--回调函数也可以通过 props 传递。
7. TypeScript
1. never 类型是什么?
never是不应该出现的类型,never类型可以理解为没有返回指的函数或者总是会抛出错误的函数。
//举例:
function(){while return{}}//如果函数内含有 while(true) {}
function foo(){
throw new Error('not implemented')
}
//foo的返回类型是never
【注意】never仅能被赋值给另一个never
viod表示没有任何类型。never表示永远不存在的类型。
2. TypeScript 比起 JavaScript 有什么优点?
目前主流框架都已经使用了typescript,Vue3.0\React都已经使用了,typescript是javascript的超集,也就是说js能做到的东西typescript都能做到,javascript程序员会的东西typescript程序员都会,js程序员不会的他们也都会,所以typescript一定是以后的主流。其优点:typescript提供了类型约束,因此可控,更容易重构,做更大的项目,更容易维护。
- bug 显著减少,之前会遇到的 xxx 为空的问题几乎不会出现了,类型相关 bug 直线减少。
- 应用更可控,当你需要约束某些代码的时候,用类型就能很简单地做到,比如 React 里强制写 displayName 方便调试。
- 查文档更方便,以前要打开浏览器看文档,现在直接查看定义就基本明白了。
项目目前只支持 JS,也没有关系,只需要加一个 ts-loader 或者 awesome-typescript-loader 就能提供 TypeScript 支持,TS 可以和 JS 共存。学完 JS 后,只需要学习一下类型声明就可以掌握 TS 了。TS 就是在 JS 上加上类型声明,这样我们就能知道代码是否「大概」正确。
另外,这种方式速度非常快,快到你只要修改代码,TS 就能告诉你代码是否「大概」正确。
从而避免很多 bug。
8. Webpack
参考链接【https://zhuanlan.zhihu.com/p/44438844】
1. 必考:有哪些常见 loader 和 plugin,你用过哪些?
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
常见的loader—— 加载器
* html —— pug-loader markdown-loader
* css —— style-loader less-loader scss-loader postloader
* js —— babel-loader
* 图片 —— url-loader eslint-loader
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试
- image-loader:加载并且压缩图片文件
- babel-loader:把 ES6 转换成 ES5
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
- eslint-loader:通过 ESLint 检查 JavaScript 代码
常见的plugin—— 用Plugin来扩展webpack功能
- html —— html-webpack-plugin
- css —— ex tract-text-plugin
- js ——
- define-plugin:定义环境变量
- commons-chunk-plugin:提取公共代码
- uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
2. loader 和 plugin的区别是什么?
- 不同的作用:
a. Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
b . Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。 - 不同的用法:
a. Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
b. Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。
3. 必考:如何按需加载代码?
Vue UI组件库的按需加载 为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。
不过很多组件库已经提供了现成的解决方案,如Element出品的[babel-plugin-component](https://link.zhihu.com/?target=https%3A//github.com/ElementUI/babel-plugin-component)
和AntDesign出品的[babel-plugin-import](https://link.zhihu.com/?target=https%3A//github.com/ant-design/babel-plugin-import)
安装以上插件后,在.babelrc
配置中或babel-loader
的参数中进行设置,即可实现组件按需加载了。
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
单页应用的按需加载 现在很多前端项目都是通过单页应用的方式开发的,但是随着业务的不断扩展,会面临一个严峻的问题——首次加载的代码量会越来越多,影响用户的体验。
通过import(*)
语句来控制加载时机,webpack内置了对于import(*)
的解析,会将import(*)
中引入的模块作为一个新的入口在生成一个chunk。 当代码执行到import(*)
语句时,会去加载Chunk对应生成的文件。import()
会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill
4. 必考:如何提高构建速度?
- 多入口情况下,使用CommonsChunkPlugin来提取公共代码
- 通过externals配置来提取常用库
- 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行
- 预编译,再通过DllReferencePlugin将预编译的模块加载进来。
- 使用Happypack 实现多线程加速编译(多核CPU进行打包)
- 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
- 使用Tree-shaking和Scope Hoisting来剔除多余代码(Tree-Shaking不要说,说了会追问)
5. 义出的文件过大怎么办?
- commonsChunkPlugin: 提取通用模块(最新的webpack已经不使用了这个插件)
- 压缩 js,css,图片
- 使用动态加载:import()
9. 网络安全
1. 考:什么是 XSS?如何预防?
XSS —— Cross-Site Scripting 跨站脚本。
用户 A 提交评论「小谷你好」到服务器,然后用户 B 来访问网站,看到了 A 的评论「小谷你好」,这里没有 XSS。
恶意用户 H 提交评论「」,然后用户 B 来访问网站,这段脚本在 B 的浏览器直接执行,恶意用户 H 的脚本就可以任意操作 B 的 cookie,而 B 对此毫无察觉。有了 cookie,恶意用户 H 就可以伪造 B 的登录信息,随意访问 B 的隐私了。而 B 始终被蒙在鼓里。
防范方法:
-
- 后台模板问题
评论内容:
$content 的内容,没有经过任何过滤,原样输出。
要解决这个原因,只需要后台输出的时候,将可疑的符号 < 符号变成 < (HTML实体)就行。
- 前端代码问题
$p.html(content)
或者
$p = $(''+ content +'
')
content 内容又被原样输出了。解决办法就是不要自己拼 HTML,尽量使用 text 方法。如果一定要使用 HTML,就把可疑符号变成 HTML 实体。
2. 必考:什么是 CSRF?如何预防?
Cross Site Request Forgery,跨站请求伪造。其原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。
10. 开放题目
1. 必考:你遇到最难的问题是怎样的?
2. 你在团队的突出贡献是什么?
3. 最近在关注什么新技术
阮一峰的课本,(微博)。
4. 有没有看什么源码,看了后有什么记忆深刻的地方,有什么收获
看一些源代码,命名规范的重要性,从而提高代码的可读性。
设计模式,轮播思路。
11. 个性化题目
1. PWA
2. echarts.js / d3.js
3. three.js
4. flutter
5. SSR
12. 算法 + 数据库
13. git 版本控制:
1. github创建仓库:
命令行:
git init
git commit -m "first commit"
git add --all
创建远程仓库(复制即可)
git push -u origin master
2. 查看版本:
git log (--prety=online //输出信息太多时可以用) //显示最近到最远的提交日志
git checkout -b //如果版本次数太多可以写成 HEAD~100
//和上一步git checkout 一样功能的命令行有 :
gitk //功能和git checkout -b 一样,一图形化工具显示显示已提交版本
- 注意:
git log 很很多选项:
选项 说明
-p 按补丁格式显示每个更新之间的差异
--word-diff 按 word diff 格式显示差异
--stat 显示每次更新的文件修改统计信息
--shortstat 只显示 --stat 中最后的行数修改添加移除统计
--name-only 仅在提交信息后显示已修改的文件清单
--name-status 显示新增、修改、删除的文件清单
--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符
--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)
--graph 显示 ASCII 图形表示的分支合并历史
--pretty 使用其他格式显示历史提交信息可用的选项包括oneline,short,full,fuller 和format(后跟指定格式)
--oneline `--pretty=oneline --abbrev-commit` 的简化用法
3. 切换版本:
git reset --HEAD^ //返回第一个版本
git reflog 命令行提供了查询commit id 的功能,查询到的id 写在git reset后面就会返回指定版本。
14. 垃圾回收机质:
javascript有自动垃圾收集机制(GC:Garbage Collection),也就是说执行环境会负责管理代码执行过程中使用的内存,开发人员不用再过于担心内存使用的问题,所需内存的分配与无用内存的回收完全实现了自动管理。
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
- 垃圾回收方式:
- 标记清除:大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。
垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量。 - 引用计数:这种方式常常会引起内存泄漏,低版本的IE使用这种方式。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收。
- 标记清除:大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。