前端面试宝典(基础)

1,874 阅读26分钟

参考中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)并加以拓展,以供自己复习方便,目前还在学习并不断补充。

CSS


1. 盒模型

页面渲染时,dom 元素所采用的布局模型。可通过box-sizing进行设置。根据计算宽高的区域可分为:

  • content-box (W3C 标准盒模型):在内容宽度和高度之外绘制内边距和边框
  • border-box (IE 盒模型):在已设定的宽度和高度之内绘制设定元素的边框及内边距
  • padding-box(浏览器未实现)
  • margin-box (浏览器未实现)

2. BFC

块级格式化上下文,是一个独立的渲染区域,只有Block Box参与,它规定了内部的块级盒子如何布局,且BFC 区域与外部的元素相互隔离。

IE下为 hasLayout,可通过 zoom:1 触发

  • 触发条件(5条):

    • 根元素
    • position: absolute/fixed
    • display: inline-block / table / flex / inline-flex
    • float!==none
    • overflow !== visible
  • 规则(6条):

    1. 同一个 BFC 的两个相邻 Box 垂直排列
    2. 同一个 BFC 内部的垂直方向距离由margin决定,同一个 BFC的两个相邻 Box 的 margin 会发生重叠
    3. BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)
    4. BFC 的区域不会与 float 的元素区域重叠
    5. 计算 BFC 的高度时,浮动子元素也参与计算
    6. 文字层不会被浮动层覆盖,环绕于周围
  • 应用:

    • 阻止 margin重叠

    • 清除内部浮动

    • 自适应两栏布局

    • 可以阻止元素被浮动元素覆盖

    1. 阻止margin重叠
      • 同一个BFC的两个相邻块级Box 的 margin 会发生重叠,破坏三个条件之一即可
    2. 清除浮动(解决高度塌陷:让浮动的子元素可以撑开父级的高度)
      • 原理:计算BFC的高度时,浮动元素也参与计算
      • 开启父元素的BFC
        • overflow:hidden
        • 定位
        • 浮动
    3. 自适应两栏布局
      • 原理:BFC 的区域不会与 float 的元素区域重叠
      • 左侧浮动,右侧开启BFC管理子元素
      #left{
      	width: 200px;
      	background: pink;
      	float: left; 
      }
      #right{
      	background: deeppink;
      	overflow: hidden;
      }
      
    4. 可以阻止元素被浮动元素覆盖

3. 清除浮动

  • 给父级加高度
    • 扩展性不好
  • 开启父级BFC
    • overflow:hidden
    • 定位
    • 浮动
    • ie 6 7底下不支持BFC
  • br标签
    • ie6 不支持
    • 违反了结构 行为 样式相分离的原则
  • 空标签
    • 违反了结构 行为 样式相分离的原则
    • ie6下元素的最小高度为19px
    • 可以尝试给元素的fontsize设为0---> 2px
  • 伪元素 + 开启haslayout
    • 因为ie6 7 下不支持伪元素
    • 所以要额外的去开启haslayout
    /*开启haslayout*/
    .clearfix{
    	*zoom: 1;
    }
    	
    /*ie 6 7 不支持伪元素*/
    .clearfix:after{
    	content: "";
    	display: block;
    	clear: both;
    }
    

4. 层叠上下文

元素提升为一个比较特殊的图层,在三维空间中 (z轴) 高出普通元素一等。

  • 触发条件

    • 根层叠上下文(html)
    • position
    • css3属性
      • flex
      • transform
      • opacity
      • filter
      • will-change
      • -webkit-overflow-scrolling
  • 层叠等级:层叠上下文在z轴上的排序

    • 在同一层叠上下文中,层叠等级才有意义
    • z-index的优先级最高

5. 居中布局

  • 水平居中

    • 行内元素: text-align: center
    • 块级元素: margin: 0 auto
    • absolute + transform
    • flex + justify-content: center
  • 垂直居中

    • line-height: height
    • absolute + transform
    • flex + align-items: center
    • table
  • 水平垂直居中

    • absolute + transform
    #wrap{
    	position: relative;
    	width: 400px;
    	height: 600px;
    	background: pink;
    	margin: 0 auto;
    	}
    #inner{
    	position: absolute;
    	left: 50%;
    	top: 50%;
    	transform: translate3d(-50%,-50%,0);
    	background: deeppink;
    	}
    
    • flex + justify-content + align-items
  • 图片垂直水平居中

    • 利用vertical-align
    #wrap:after{
    	content: "";
    	display: inline-block;
    	height: 100%;
    	width: 0px;
    	background: pink;
    	vertical-align: middle;
    }
    #wrap img{
    	vertical-align: middle;
    }
    

常用布局

三列布局

  • (1)两边固定 当中自适应
  • (2)当中列要完整显示
  • (3)当中列要优先加载

圣杯布局

  • 三列浮动 + 为父级清除浮动:搭建完整的布局框架
  • 使用margin为负值:调整旁边两列的位置(使三列布局到一行上)
  • 父级padding + 从列相对定位:调整中间主列的位置。
  • 完整代码:
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding: 0;
			}
			body{
				min-width: 600px;
			}
			#header,#footer{
				height: 20px;
				text-align: center;
				border: 1px solid  deeppink;
				background: gray;
			}
			#content{
				padding: 0 200px;
			}
			#content .middle{
				float: left;
				width: 100%;
				background: pink;
			}
			#content .left{
				position: relative;
				left:-200px;
				margin-left: -100%;
				float: left;
				width: 200px;
				background: yellow;
			}
			#content .right{
				position: relative;
				right:-200px;
				margin-left:-200px;
				float: left;
				width: 200px;
				background: yellow;
			}

			.clearfix{
				*zoom: 1;
			}
			.clearfix:after{
				content: "";
				display: block;
				clear: both;
			}
			
			
		</style>
	</head>
	<body> 
		<div id="header">header</div>
		<div id="content" class="clearfix">
			<div class="middle">middle</div>
			<div class="left">left</div>		
			<div class="right">right</div>		
		</div>
		<div id="footer">footer</div>
	</body>
</html>

效果: image.png

双飞翼布局

双飞翼、圣杯实现的对比:

  • 两种布局方式都是把主列放在文档流最前面,使主列优先加载。
  • 两种布局方式在实现上也有相同之处,都是让三列浮动,然后通过负外边距形成三列布局。
  • 两种布局方式的不同之处在于如何处理中间主列的位置
    • 圣杯布局是利用父容器的左、右内边距+两个从列相对定位;
    • 双飞翼布局是把主列嵌套在一个新的父级块中利用主列的左、右外边距进行布局调整
  • 双飞翼布局完整代码:
// 1. 三列浮动 + 为父级清除浮动:搭建完整的布局框架
// 2. 使用`margin`为负值:调整旁边两列的位置(使三列布局到一行上)
// 3. 为主列添加左右margin
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding: 0;
			}
			body{
				min-width: 600px;
			}
			
			
			/*头部 脚部样式*/
			#header,#footer{
				border: 1px solid;
				background: gray;
				text-align: center;
			}
			
			/*三列的伪等高布局*/
			#content .middle,#content .left,#content .right{
				/*padding-bottom:10000px ;
				margin-bottom: -10000px;*/
				height: 50px;
				line-height: 50px;
				float: left;
			}
			
			/*双飞翼布局*/
			#content{
				overflow: hidden;
			}
			#content .middle{
				width: 100%;
				background: deeppink;
			}
			#content .middle .m_inner{
				margin: 0 200px;
			}
			#content .left,#content .right{
				background: pink;
				width: 200px;
				text-align: center;
			}
			#content .left{
				margin-left: -100%;
			}
			#content .right{
				margin-left: -200px;
			}
			
		</style>
	</head>
	<body>
		<div id="header">
			<h4>header</h4>
		</div>
		<div id="content">
			<div class="middle">
				<div class="m_inner">
					middle
				</div>
			</div>
			<div class="left">left</div>
			<div class="right">right</div>
		</div>
		<div id="footer">
			<h4>footer</h4>
		</div>
	</body>
</html>

伪等高布局

解决高度不统一问题 添加代码

#content .left,#content .right,#content .middle{
	padding-bottom: 10000px;
	margin-bottom: -10000px;
}
#content{
	overflow: hidden; 
}

粘连布局

粘连布局,又称为stick footer布局 如果页面内容不够长的时候,页脚块粘贴在视窗底部;如果内容足够长时,页脚块会被内容向下推送。 image.png image.png

  • 总结

      1. footer必须是一个独立的结构,与wrap没有任何嵌套关系
      1. footer要使用margin为负来确定自己的位置
      1. wrap区域必须要被自己的子元素撑开
      1. 如果真的想在wrap区域外添加其他结构,这个结构必须定位
  • 实现代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
		<style type="text/css">
			*{
				margin: 0;
				padding: 0;
			}
			html,body{
				height: 100%;
			}
			#wrap{
				min-height: 100%; // 1.设置最小高度
			}
			#footer{
				height: 50px;
				background: pink;
				line-height: 50px;
				text-align: center;
				margin-top: -50px;  // 2. footer设置margin为负的本身高度
			}
			#main{
				text-align: center;
				padding-bottom: 50px; //  3. main设置padding-bottomfooter腾地方
			}
		</style>
	</head>
	<body>
		<div id="wrap">
			<div id="main">
				n <br/>
				n <br/>
			</div>
		</div>
		<div id="footer">footer</div>
	</body>
</html>

6. 选择器优先级

  • !important > 行内样式 > #id > .class /伪类/属性> tag > * > 继承 > 默认
  • 选择器 从右往左 解析

7. link 与 @import 的区别

  • 页面使用css的方式主要有3种:
    • 标签行内引用(内联样式)
    • 内部样式表
    • 外部样式表,其中外部样式引用有link和import两种 link和import都可以对css样式进行外部引用,但它们还是有区别的。
  • link@import区别
    1. 从属关系区别
      • @import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,功能较多,还可以定义 RSS、rel 连接属性等。
    2. 加载顺序区别
      • 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载
    3. 兼容性区别
      • @import是 CSS2.1 才有的语法, IE5+ 才能识别;link标签作为 HTML 元素,不存在兼容性问题。
    4. DOM可控性区别
      • link可以使用 js 动态引入,@import不行
    5. link方式的样式权重高于@import的权重。

8. CSS预处理器(Sass/Less/Postcss)

CSS预处理器的原理: 是将类 CSS 语言通过 Webpack 编译 转成浏览器可读的真正 CSS。在这层编译之上,便可以赋予 CSS 更多更强大的功能,常用功能:

  • 嵌套
  • 变量
  • 循环语句
  • 条件语句
  • 自动前缀
  • 单位转换
  • mixin复用

面试中一般不会重点考察该点,一般介绍下自己在实战项目中的经验即可~

9.CSS动画

transition: 过渡动画

  • transition-property: 属性 -transition-duration: 间隔
  • transition-timing-function: 曲线
  • transition-delay: 延迟
  • 常用钩子: transitionend

animation / keyframes

  • animation-name: 动画名称,对应@keyframes
  • animation-duration: 间隔
  • animation-timing-function: 曲线
  • animation-delay: 延迟
  • animation-iteration-count: 次数
    • infinite: 循环动画
  • animation-direction: 方向
    • alternate: 反向播放
  • animation-fill-mode: 静止模式
    • forwards: 停止时,保留最后一帧
    • backwards: 停止时,回到第一帧
    • both: 同时运用 forwards / backwards
  • 常用钩子: animationend

动画属性

尽量使用动画属性进行动画,能拥有较好的性能表现

  • translate
  • scale
  • rotate
  • skew
  • opacity
  • color

JavaScript


1. 原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。原型对象即为当前实例对象的父对象。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可通过obj.__proto__进行访问。  函数的protype属性

    • 所有函数都有一个特别的属性: prototype : 显式原型属性
    • 所有实例对象都有一个特别的属性: __proto__ : 隐式原型属性
    • 显式原型与隐式原型的关系
      • 函数的prototype: 定义函数时被自动赋值, 默认值是一个空Object对象, 即为原型对象
      • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype
      • 对象的隐式原型的值为其对应构造函数的显式原型的值
  • 构造函数: 可以通过new新建一个对象 的函数。

  • 实例: 通过构造函数和new创建出来的对象,便是实例。

  • 三者关系: 实例通过__proto__指向原型,原型通过constructor指向构造函数构造函数/原型/实例对象的关系(图解)

Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。

// 实例
const o1 = new Object()

则此时, 实例为o1, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:

// 原型
const prototype = Object.prototype

这里我们可以来看出三者的关系:

实例.__proto__ === 原型

原型.constructor === 构造函数

构造函数.prototype === 原型

2.原型链:

别名: 隐式原型链。 原型链是由原型对象组成,所有的实例对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了一个链的结构---->原型链。是一个用来实现继承和共享属性的有限的对象链。

  • 属性查找机制: 当查找对象的属性时,先在自身属性中查找,找到返回,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined

  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。

  • 原型继承:构造函数的实例对象自动拥有构造函数原型对象的属性(方法),利用的就是原型链。 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上。

function Person(name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }
  • Object和Function

    • 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
     console.log(Fn.prototype instanceof Object) // true
     console.log(Object.prototype instanceof Object)// false
     console.log(Function.prototype instanceof Object) // true
    
    • 所有函数都是Function的实例(包含Function)
     console.log(Function.__proto__===Function.protot  ype)
    
    • Object的原型对象是原型链尽头
      console.log(Object.prototype.__proto__) // null
    

3. 探索instanceof

  • instanceof是如何判断的?
    • 表达式: A instanceof B
    • 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
  • Function是通过new自己产生的实例
console.log(Object instanceof Function);
console.log(Object instanceof Object);
console.log(Function instanceof Function);
console.log(Function instanceof Object);
function Foo() {}
console.log(Object instanceof  Foo);

案例图解

4. 变量提升与函数提升

  • 变量声明提升
    • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
    • 值: undefined
  • 函数声明提升
    • 通过function声明的函数, 在之前就可以直接调用
    • 值: 函数定义(对象)
  • 变量先提升,函数再提升

5. 执行上下文(EC):

执行上下文(EC): 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性

  • 它包含三个部分:

    • 变量对象(VO)
    • 作用域链(词法作用域)
    • this指向
  • 它的类型:

    • 全局执行上下文
    • 函数执行上下文
    • eval执行上下文
  • 生命周期

    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:

    • 全局 :
      • var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程

    • 全局执行上下文

      • 在执行全局代码前将window确定为全局执行上下文
      • 对全局数据进行预处理
        • var定义的全局变量==>undefined, 添加为window的属性
        • function声明的全局函数==>赋值(fun), 添加为window的方法
        • this==>赋值(window)
      • 开始执行全局代码
    • 函数执行上下文

      • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象 * 对局部数据进行预处理
        • 形参变量==>赋值(实参)==>添加为执行上下文的属性
        • arguments==>赋值(实参列表), 添加为执行上下文的属性
        • var定义的局部变量==>undefined, 添加为执行上下文的属性
        • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
        • this==>赋值(调用函数的对象)
      • 开始执行函数体代码

执行上下文栈: 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象

  • 代码执行过程:
    • 创建 全局上下文 (global EC)
    • 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
    • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
    • 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行

6. 作用域与作用域链

  • 作用域: 一块代码区域(静态的), 在编码时就确定了, 不会再变化

  • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量

  • 分类:

    • 全局
    • 函数
    • 块作用域(ES6 let)
  • 变量的查找规则

    • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
    • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
    • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
  • 作用

    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文

    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

7. 闭包

  • 如何产生闭包?

    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  • 闭包是什么?

    • 通过chrome工具调试查看得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中
  • 产生闭包的条件?

    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
  • 常见的闭包

    1. 将函数作为另一个函数的返回值
     function fn1() {
       var a = 2;
      function fn2() {
        a++;
        console.log(a);
      }
      return fn2;
     }
     var f = fn1();
     f();
     f();
    
    1. 将函数作为实参传递给另一个函数调用
    function showDelay(msg, time) {
        setTimeout(function () {
         alert(msg)
        }, time)
     }
    showDelay('atguigu', 2000)
    
  • 闭包的作用

    1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
    2. 让函数外部能操作内部的局部变量
  • 闭包的生命周期

    1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
    2. 死亡: 在嵌套的内部函数成为垃圾对象时
  • 闭包的应用 :

    1. 定义JS模块
      • 具有特定功能的js文件
      • 将所有的数据和功能都封装在一个函数内部(私有的)
      • 只向外暴露一个包信n个方法的对象或函数
      • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
    2. 循环遍历加监听
    3. JS框架(jQuery)大量使用了闭包
  • 闭包缺点

    1. 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长,容易造成内存泄露
    • 解决:
      • 能不用闭包就不用
      • 及时释放 : f = null; //让内部函数对象成为垃圾对象
    1. 闭包会在父函数外部,改变父函数内部变量的值。 多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
    • 解决:
      • 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
      • 使用setTimeout包裹,通过第三个参数传入
      • 使用 块级作用域,让变量成为自己上下文的属性,避免共享

8. 内存溢出与内存泄露

  • 内存溢出
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  • 内存泄露
    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:
      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包

9. script 引入方式

  1. html 静态<script>引入
<script src="js/index.js" type="text/javascript"></script>
  1. js 动态插入<script>
var scriptElement=document.createElement("script");
scriptElement.src="js/test.js";
(document.getElementsByTagName("head")[0] || document.body).appendChild(scriptElement);
  1. <script defer>: 异步加载,元素解析完成后执行
  • 延迟脚本defer属性
  • defer:可选。表示脚本可以延迟到文档完全被解析之后再执行。只对外部脚本文件有效。 相当于告诉浏览器立即下载,但延迟执行
  1. <script async>: 异步加载,但执行时会阻塞元素渲染
  • defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载脚本,但不应妨碍页面的其他操作,比如下载其他资源或等待加载其他脚本。

10. 对象的拷贝

  • 数据类型:数据分为基本的数据类型(String, Number, boolean, Null, Undefined)和对象数据类型

    • 基本数据类型:
      • 特点: 存储的是该对象的实际数据
    • 对象数据类型:
      • 特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里
  • 复制数据

    • 基本数据类型存放的就是实际的数据,可直接复制
      let number2 = 2;
      let number1 = number2;
      
    • 克隆数据:对象/数组
      • 区别: 浅拷贝/深度拷贝
      • 判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用
      • 知识点:对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用
      let obj = {username: 'kobe'};
      let obj1 = obj; // obj1 复制了obj在栈内存的引用
      
  • 浅拷贝: 拷贝引用,修改拷贝以后的数据影响原数据

    • 直接赋值给一个变量
    • Object.assign()
    • Array.prototype.slice(): 数组浅拷贝
    • Array.prototype.concat(): 数组浅拷贝
  • 深拷贝: 完全拷贝一个新对象,修改拷贝以后的数据不会影响原数据

    • JSON.parse(JSON.stringify(arr/obj)) :数组或对象深拷贝

      • 具有循环引用的对象时,报错
      • 当值为函数undefined、或symbol时,无法拷贝
    • 实现深度克隆:递归进行逐一赋值

<script type="text/javascript">
  // 复制的对象的方式
  // 浅度复制
  let obj = {username: 'kobe', age: 39, sex: {option1: '男', option2: '女'}};
  let obj1 = obj;
  console.log(obj1);
  obj1.sex.option1 = '不男不女'; // 修改复制的对象会影响原对象
  console.log(obj1, obj);
  
  console.log('-----------');
  // Object.assign();  浅复制
  let obj2 = {};
  Object.assign(obj2, obj);
  console.log(obj2);
  obj2.sex.option1 = '男'; // 修改复制的对象会影响原对象
  console.log(obj2, obj);

  // 深度克隆(复制)

  function getObjClass(obj) {
    let result = Object.prototype.toString.call(obj).slice(8, -1);
    if(result === 'Null'){
      return 'Null';
    }else if(result === 'Undefined'){
      return 'Undefined';
    }else {
      return result;
    }
  }
  // for in 遍历数组的时候遍历的是下标
  let testArr = [1,2,3,4];
  for(let i in testArr){
    console.log(i); // 对应的下标索引
  }

  // 深度克隆
  function deepClone(obj) {
    let result, objClass = getObjClass(obj);
    if(objClass === 'Object'){
      result = {};
    }else if(objClass === 'Array'){
      result = [];
    }else {
      return obj; // 如果是其他数据类型不复制,直接将数据返回
    }
    // 遍历目标对象
    for(let key in obj){
      let value = obj[key];
      if(getObjClass(value) === "Object" || 'Array'){
        result[key] = deepClone(value);
      }else {
        result[key] = obj[key];
      }
    }
    return result;
  }
  
  
  let obj3 = {username: 'kobe',age: 39, sex: {option1: '男', option2: '女'}};
  let obj4 = deepClone(obj3);
  console.log(obj4);
  obj4.sex.option1 = '不男不女'; // 修改复制后的对象不会影响原对象
  console.log(obj4, obj3);


</script> 

11. new运算符的执行过程

  • 新生成一个对象
  • 链接到原型: obj.__proto__ = Con.prototype
  • 绑定this: apply
  • 返回新对象(如果构造函数有自己 retrun 时,则返回该值)

12. 代码的复用

当你发现任何代码开始写第二遍时,就要开始考虑如何复用。一般有以下的方式:

  • 函数封装
  • 继承
  • 复制extend
  • 混入mixin
    • Mixin模式:将多个类的接口混入另一个类。mix函数将多个对象合成为一个类,使用时,只继承这个类即可。
  • 借用apply/call

13. 继承

在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

  • 最优化: 圣杯模式
 function inherit( Target , Origin){
             function F();
             F.prototype = Origin.prototype;
             Target.prototype = new F();
             Target.prototype.constructor = Target;  //把Target的构造函数指向归位
             Target.prototype.uber= Origin.prototype; //为了让我们知道Target真正继承自谁
          }
 var inherit = (function(Target,Origin){
            var F =function (){} ;
            return function(Target,Origin){
                F.prototype = Origin.prototype;
                Target.prototype = new F();
                Target.prototype.constructor =  Target;
                Target.prototype.uber = Origin.prototype;
            }
        })()
  • 使用 ES6 的语法糖 class / extends

14. 类型转换

JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

  • -、*、/、% :一律转换成数值后计算

  • +:

    • 数字 + 字符串 = 字符串, 运算顺序是从左到右
    • 数字 + 对象, 优先调用对象的valueOf -> toString
    • 数字 + boolean/null -> 数字
    • 数字 + undefined -> NaN
  • [1].toString() === '1'

  • {}.toString() === '[object object]'

  • NaN !== NaN +undefinedNaN

  • 数字转字符用toString()

  • 字符转数字用parseInt() or parseFloat()

    • parseInt方法的可选参数是操作数的进制,不是目标的进制
    • parseFloat()转换浮点数时,可以通过toFixed(n)方法指定保留小数位数(n是精确的小数点位数)得到正确结果

15. 类型判断

JS的基本数据类型共有七种:bigInt(bigInt是一种内置对象,是处symbol外的第二个内置类型)、number、string、boolen、symbol、undefined、null复杂数据类型有对象object,包括基本的对象、函数(Function)、数组(Array)和内置对象(Data等)。

  • typeof方法 基本数据类型里,除了**null返回“object”,其他都返回对应类型的字符串。 复杂数据类型里,除了函数返回"function"**,其他均返回“object”
// 基本数据类型
typeof 1 // "number" 
typeof 'a'  // "string"
typeof true  // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 42n // "bigint"
typeof null // "object" 历史遗留原因, JS 的万物皆对象的理论

// 复杂数据类型
typeof({a:1}) // "object"
typeof [1,3] // "object"
typeof(new Date) // "object"
typeof function(){} // "function"
  • object.property.toString.call方法 全部都能返回相应的内置类型
Object.prototype.toString.call(999) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(Symbol()) // "[object Symbol]"
Object.prototype.toString.call(42n) // "[object BigInt]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(true) // "[object Boolean]
Object.prototype.toString.call({a:1}) // "[object Object]"
Object.prototype.toString.call([1,2]) // "[object Array]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(function(){}) // "[object Function]"

判断封装:

function checkType(Target){
    return Object.prototype.toString.call(Target).slice(8,-1)
}
  • obj instanceof Object 只能用来判断复杂数据类型,因为instanceof是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上
[1,2] instanceof Array  // true
(function(){}) instanceof Function // true
({a:1}) instanceof Object // true
(new Date) instanceof Date // true

16. 模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。

  • 分类:

    • es6: import / export
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • requireimport的区别

    • require支持动态导入import不支持,正在提案 (babel 下可支持)
    • require同步 导入,import属于异步导入
    • require值拷贝,导出值变化不会影响导入值;import指向内存地址,导入值会随导出值而变化
  • 模块化规范
    • CommonJS
      • Node.js : 服务器端
      • Browserify : 浏览器端 也称为js的打包工具
      • 基本语法:
        • 定义暴露模块 : exports
          exports.xxx = value
          module.exports = value
          
        引入模块 : require
        var module = require('模块名/模块相对路径')
        
      • 引入模块发生在什么时候?
        • Node : 运行时, 动态同步引入
        • Browserify : 在运行前对模块进行编译/转译/打包的处理(已经将依赖的模块包含进来了), 运行的是打包生成的js, 运行时不存在需要再从远程引入依赖模块
    • AMD : 浏览器端
      • require.js
      • 基本语法
        • 定义暴露模块: define([依赖模块名], function(){return 模块对象})
        • 引入模块: require(['模块1', '模块2', '模块3'], function(m1, m2){//使用模块对象})
        • 配置:
          require.config({
            //基本路径
            baseUrl : 'js/',
            //标识名称与路径的映射
            paths : {
              '模块1' : 'modules/模块1',
              '模块2' : 'modules/模块2',
              'angular' : 'libs/angular',
              'angular-messages' : 'libs/angular-messages'
            },
            //非AMD的模块
            shim : {
              'angular' : {
                  exports : 'angular'
              },
              'angular-messages' : {
                  exports : 'angular-messages',
                  deps : ['angular']
              }
            }
          })
          
    • CMD : 浏览器端
      • sea.js
      • 基本语法
        • 定义暴露模块:
          define(function(require, module, exports){
            通过require引入依赖模块
            通过module/exports来暴露模块
            exports.xxx = value
          })
          
        • 使用模块seajs.use(['模块1', '模块2'])
    • ES6
      • ES6内置了模块化的实现
      • 基本语法
        • 定义暴露模块 : export
          • 暴露一个对象:
            export default 对象
            
          • 暴露多个:
            export var xxx = value1
            export let yyy = value2
            
            var xxx = value1
            let yyy = value2
            export {xxx, yyy}
            
        • 引入使用模块 : import
          • default模块:
            import xxx  from '模块路径/模块名'
            
          • 其它模块
            import {xxx, yyy} from '模块路径/模块名'
            import * as module1 from '模块路径/模块名'
            
      • 问题: 所有浏览器还不能直接识别ES6模块化的语法
      • 解决:
        • 使用Babel将ES6--->ES5(使用了CommonJS) ----浏览器还不能直接支行
        • 使用Browserify--->打包处理----浏览器可以运行

17. 防抖与节流

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

  • 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。

function debounce(fn, wait, immediate) {
    let timer = null

    return function() {
        let args = arguments
        let context = this

        if (immediate && !timer) {
            fn.apply(context, args)
        }

        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}

节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。

function throttle(fn, wait, immediate) {
    let timer = null
    let callNow = immediate
    
    return function() {
        let context = this,
            args = arguments

        if (callNow) {
            fn.apply(context, args)
            callNow = false
        }

        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null
            }, wait)
        }
    }
}

18. this

由于 JS 的设计原理: 在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this

this 引用的规则

  • fn(),这里可以看成 window.fn(),因此 this === window
  • obj.fn(),便是 obj 调用了函数,既函数中的 this === obj
  • 以构造函数形式调用时,this为所生成的对象
  • apply,call,bind调用,this为数指定的对象'

三种方式可以手动修改 this 的指向:

  • call: fn.call(target, 1, 2) 第一个参数:想绑定的值;剩下的参数:调用函数的参数
  • apply: fn.apply(target, [1, 2]) 以数组的形式获取参数
  • bind: fn.bind(target)(1,2) 给一个函数永久绑定this
  • 面试题: 区别bind()call()apply()?
    • fn.bind(obj) : 指定函数中的this, 并返回函数
    • fn.call(obj) : 指定函数中的this,并调用函数

19. ES5/ES6/ES7

由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。

2个新的关键字

  • let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
  • const: 声明常量,无法修改

变量的解构赋值

  • 将包含多个数据的对象(数组)一次赋值给多个变量
  • 数据源: 对象/数组
  • 目标: {a, b}/[a, b]

字符串扩展

  • 模板字符串
    • 作用: 简化字符串的拼接
    • 模板字符串必须用``
    • 变化的部分使用${xxx}定义
  • contains(str) : 判断是否包含指定的字符串
  • startsWith(str) : 判断是否以指定字符串开头
  • endsWith(str) : 判断是否以指定字符串结尾
  • repeat(count) : 重复指定次数

对象扩展

  • 简化的对象写法
     let name = 'Tom';
     let age = 12;
     let person = {
         name,
         age,
         setName (name) {
             this.name = name;
         }
     };
  • Object.assign(target, source1, source2..) : 将源对象的属性复制到目标对象上
  • Object.is(v1, v2) : 判断2个数据是否完全相等
  • __proto__属性 : 隐式原型属性

数组扩展

  • Array.from(v) : 将伪数组对象或可遍历对象转换为真数组
  • Array.of(v1, v2, v3) : 将一系列值转换成数组
  • find(function(value, index, arr){return true}) : 找出第一个满足条件返回true的元素
  • findIndex(function(value, index, arr){return true}) : 找出第一个满足条件返回true的元素下标

函数扩展

  • 箭头函数
    • 用来定义匿名函数 * 基本语法:
      • 没有参数: () => console.log('xxxx')
      • 一个参数: i => i+2
      • 大于一个参数: (i,j) => i+j
      • 函数体不用大括号: 默认返回结果
      • 函数体如果有多个语句, 需要用{}包围 * 使用场景: 多用来定义回调函数
  • 形参的默认值
    • 定义形参时指定其默认的值
  • rest(可变)参数
    • 通过形参左侧的...来表达, 取代arguments的使用
  • 扩展运算符(...)
    • 可以分解出数组或对象中的数据

class类

  • class 定义类
  • constructor() 定义构造方法(相当于构造函数)
  • 一般方法: xxx () {}
  • extends来定义子类
  • super()来父类的构造方法
  • 子类方法自定义: 将从父类中继承来的方法重新实现一遍
  • js中没有方法重载(方法名相同, 但参数不同)的语法

Set / Map: 新的数据结构

  • 容器: 能保存多个数据的对象, 同时必须具备操作内部数据的方法
    • 任意对象都可以作为容器使用, 但有的对象不太适合作为容器使用(如函数)
  • Set的特点: 保存多个value, value是不重复 ====>数组元素去重
  • Map的特点: 保存多个key--value, key是不重复, value是可以重复的
  • API
    • Set()/Set(arr) //arr是一维数组
    • add(value)
    • delete(value)
    • clear();
    • has(value)
    • size
    • Map()/Map(arr) //arr是二维数组
    • set(key, value)
    • delete(key)
    • clear()
    • has(key)
    • size

for--of循环

可以遍历任何容器

  • 数组
  • 对象
  • 伪/类对象
  • 字符串
  • 可迭代的对象

异步解决方案

Promise

  • Promise的使用与实现
    • 解决回调地狱(回调函数的层层嵌套, 编码是不断向右扩展, 阅读性很差)
    • 能以同步编码的方式实现异步调用
    • 在es6之前原生的js中是没这种实现的, 一些第三方框架(jQuery)实现了promise
    • ES6中定义实现API:
    // 1. 创建promise对象
    var promise = new Promise(function(resolve, reject){ 
    // 做异步的操作 
    if(成功) { 
      resolve(result);  // 将Promise状态从Pending变为Fullfilled
    } else { 
      reject(errorMsg); // 将Promise状态从Pending变Rejected
    } 
    }) 
    // 2. 调用promise对象的then()
    promise.then(
    result => console.log(result), //成功的回调
    errorMsg => alert(errorMsg) //失败的回调
    )
    

generator

  • generator:ES6提供的解决异步编程的方案之一,可暂停函数(惰性求值)

    • yield: 暂停代码
    • next(): 继续执行代码
    • 每次返回的是yield后的表达式结果
    function* helloWorld() {
    yield 'hello';
    yield 'world';
    return 'ending';
    }
    
    const generator = helloWorld();
    
    generator.next()  // { value: 'hello', done: false }
    
    generator.next()  // { value: 'world', done: false }
    
    generator.next()  // { value: 'ending', done: true }
    
    generator.next()  // { value: undefined, done: true }
    

await / async

  • await / async: 是generator的语法糖, babel中是基于promise实现。真正意义上去解决异步回调的问题,同步流程表达异步操作

    async function getUserByAsync(){
    let user = await fetchUser();
    return user;
    }
    
    const user = await getUserByAsync()
    console.log(user)
    

20. AST

抽象语法树 (Abstract Syntax Tree),是将代码逐字母解析成树状对象的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础

21. babel编译原理

  • Babel:广为使用的ES6转码器,将ES6代码转为ES5代码,从而在浏览器和其他环境执行

  • babel编译原理

    • babylon将 ES6/ES7 代码解析成 AST
    • babel-traverse 对 AST 进行遍历转译,得到新的 AST
    • 新 AST 通过 babel-generator 转换成 ES5
  • Babel的包构成

    • 核心包

      • babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。
      • babylon:js的词法解析器
      • babel-traverse:用于对AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给plugin用
      • babel-generator:根据AST生成代码
    • 功能包

      • babel-types:用于检验、构建和改变AST树的节点
      • babel-template:辅助函数,用于从字符串形式的代码来构建AST树节点
      • babel-helpers:一系列预制的babel-template函数,用于提供给一些plugins使用
      • babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置
      • babel-plugin-xxx:babel转译过程中使用到的插件,其中babel-plugin-transform-xxx是transform步骤使用的
      • babel-preset-xxx:transform阶段使用到的一系列的plugin
      • babel-polyfill:JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装
      • babel-runtime:功能类似babel-polyfill,一般用于library或plugin中,因为它不会污染全局作用域
    • 工具包

      • babel-cli:babel的命令行工具,通过命令行对js代码进行转译
      • babel-register:通过绑定node.js的require来自动转译require引用的js代码文件

22. 函数柯里化

在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。

const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

23. 数组(array)

  • map: 对数组每一项运行给定函数,返回函数调用结果组成的新数组

  • forEach: 对数组每一项运行给定函数,无返回值。无法break,可以用try/catch中throw new Error来停止

  • filter: 遍历过滤出一个子数组

  • some: 有一项返回true,则整体为true

  • every: 有一项返回false,则整体为false

  • join: 通过指定连接符生成字符串

  • push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项

  • unshift / shift: 头部推入和弹出,改变原数组,返回操作项

  • sort(fn) / reverse: 排序与反转,改变原数组

  • concat: 连接数组,不影响原数组, 浅拷贝

  • slice(start, end): 返回截断后的新数组,不改变原数组

  • splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组

  • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标

  • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)

  • 数组排序:

arr.sort(
(a,b)=>{
    return a-b;  //升序
    return b-a;  //降序
}
)
  • 数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
  • 数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
    return this.toString().split(',').map(item => +item )
}

24. JSON对象

  • 作用: 用于在json对象/数组与js对象/数组相互转换
  • JSON.stringify(obj/arr): js对象(数组)转换为json对象(数组)
  • JSON.parse(json): json对象(数组)转换为js对象(数组)

Ajax

  • ajax:异步请求数据的web开发技术
  • 通俗讲有四个步骤:
      1. 创建Ajax核心对象XMLHttpRequest(考虑兼容性)
    var xhr=null;  
    if (window.XMLHttpRequest)  
      {// 兼容 IE7+, Firefox, Chrome, Opera, Safari  
      xhr=new XMLHttpRequest();  
      } else{// 兼容 IE6, IE5 
      xhr=new ActiveXObject("Microsoft.XMLHTTP");  
      } 
    
      1. 链接到服务器
      //open(方法,待读取文件名,异步传输)
       xhr.open(method,url,async);
      
      • method:请求的类型;GET 或 POST
      • url:文件在服务器上的位置
      • async:true(异步)或 false(同步)
      1. 发送请求
        //发送请求
        xhr.send(string);//post请求时才使用字符串参数,否则不用带参数。
        
      • 注意:post请求一定要设置请求头的格式内容
      xhr.open("POST","test.html",true);  
      xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  
      xhr.send("fname=Henry&lname=Ford");  //post请求参数放在send里面,即请求体
      
      1. 接受返回值
      //同步处理
      document.getElementById("myDiv").innerHTML=xhr.responseText; //获取数据直接显示在页面上
      
      //异步处理
      xhr.onreadystatechange=function()  { 
          if (xhr.readyState==4 &&xhr.status==200)  { 
            document.getElementById("myDiv").innerHTML=xhr.responseText;  
           }
      } 
      
      • responseText 获得字符串形式的响应数据。
      • responseXML 获得XML 形式的响应数据。
      • 什么是readyState?
        • readyState是XMLHttpRequest对象的一个属性,用来标识当前XMLHttpRequest对象处于什么状态。
        • readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义
          • 0:未初始化 -- 尚未调用.open()方法;
          • 1:启动 -- 已经调用.open()方法,但尚未调用.send()方法;
          • 2:发送 -- 已经调用.send()方法,但尚未接收到响应;
          • 3:接收 -- 已经接收到部分响应数据;
          • 4:完成 -- 已经接收到全部响应数据,而且已经可以在客户端使用了;
      • 什么是status?
        • HTTP状态码

浏览器


1. 跨标签页通讯

不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:

  • 通过父页面window.open()和子页面postMessage

    • 异步下,通过 window.open('about: blank')tab.location.href = '*'
  • 设置同域下共享的localStorage与监听window.onstorage

    • 重复写入相同的值无法触发
    • 会受到浏览器隐身模式等的限制
  • 设置共享cookie与不断轮询脏检查(setInterval)

  • 借助服务端或者中间层实现

2. 浏览器架构

  • 用户界面
  • 主进程
  • 内核
    • 渲染引擎
    • js引擎: 负责js程序的编译与运行
      • 执行栈
    • 事件触发线程
      • 消息队列
        • 微任务
        • 宏任务
    • 网络异步线程
    • 定时器线程

浏览器结构如下图: 浏览器

  • 用户界面(User Interface):除了显示请求页面用的浏览器主窗口外,其他部分都属于用户界面,包括地址栏、前进/后退按钮、书签菜单等。
  • 浏览器引擎(Browser engine):查询和操作渲染引擎的接口。
  • 渲染引擎(Rendering engine):负责显示请求的内容。它负责取得网页的内容(HTML、XML、图象等等)、整理信息(例如加入CSS等),以及计算网页的显示方式,并显示在屏幕上。
  • 网络(Networking):网络调用,例如HTTP请求。
  • JavaScript解释器(JavaScriptInterpreter):JavaScript引擎,专门处理JavaScript脚本。
  • 用户界面后端(UI Backend):绘制基本的窗口小部件,比如组合框和窗口。
  • 数据存储(Data Persistence):数据持久层。浏览器在硬盘上存储数据,比如cookie。HTML5定义了网络数据库。

3. 浏览器下事件循环(Event Loop)

事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

  • 微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
  • 宏任务 macrotask(task): setTimout / script / IO / UI Rendering

在事件循环中,用户代理会不断从task队列中按顺序取task执行,每执行完一个task都会检查microtask队列是否为空(执行完一个task的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去task队列中取下一个task执行。

4. 从输入 url 到展示的过程

  • DNS 解析:通过域名找到与之对应的服务器ip

  • TCP 三次握手,建立tcp连接

  • 客户端发送HTTP请求,分析 url,设置请求报文(头,主体)

  • 服务器处理请求 

  • 服务器响应请求,返回请求的文件 (html)

  • 浏览器渲染

    • HTML parser --> DOM Tree

      • 标记化算法,进行元素状态的标记
      • dom 树构建
    • CSS parser --> Style Tree

      • 解析 css 代码,生成样式树
    • attachment --> Render Tree

      • 结合 dom树 与 style树,生成渲染树
    • layout: 布局

    • GPU painting: 像素绘制页面

5. 重绘与回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少

  • 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:

    • 页面初次渲染

    • 浏览器窗口大小改变

    • 元素尺寸、位置、内容发生改变

    • 元素字体大小变化

    • 添加或者删除可见的 dom 元素

    • 激活 CSS 伪类(例如::hover)

    • 查询某些属性或调用某些方法

      • clientWidth、clientHeight、clientTop、clientLeft
      • offsetWidth、offsetHeight、offsetTop、offsetLeft
      • scrollWidth、scrollHeight、scrollTop、scrollLeft
      • getComputedStyle()
      • getBoundingClientRect()
      • scrollTo()

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

最佳实践:

  • css

    • 避免使用table布局
    • 将动画效果应用到position属性为absolutefixed的元素上
  • javascript

    • 避免频繁操作样式,可汇总后统一 一次修改
    • 尽量使用class进行样式修改
    • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
    • 极限优化时,修改样式可将其display: none后修改
    • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

6. 存储

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储持久性储存

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用

  • 持久性存储,可以分为 浏览器端 与 服务器端

    • 浏览器:

      • cookie: 通常用于存储用户身份,登录状态等

        • http 中自动携带, 体积上限为 4K, 可自行设置过期时间
      • localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5M

      • indexDB

    • 服务器:

      • 分布式缓存 redis
      • 数据库

7. Web Worker

现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信。

基本用法:

// 创建 worker
const worker = new Worker('work.js');

// 向主进程推送消息
worker.postMessage('Hello World');

// 监听主进程来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}

限制:

  • 同源限制,不能跨域加载JS
  • 无法使用 document / window / alert / confirm
  • 无法加载本地资源
  • worker内代码不能访问DOM(更新UI)

8. V8垃圾回收机制

垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间 和 老生代空间。

  • 新生代空间: 用于存活较短的对象

    • 又分成两个空间: from 空间 与 to 空间
    • Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法
    • 存活的对象从 from space 转移到 to space
    • 清空 from space
    • from space 与 to space 互换
    • 完成一次新生代GC
  • 老生代空间: 用于存活时间较长的对象

    • 从 新生代空间 转移到 老生代空间 的条件

      • 经历过一次以上 Scavenge GC 的对象
      • 当 to space 体积超过25%
    • 标记清除算法: 标记存活的对象,未被标记的则被释放

      • 增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能
      • 并发标记(最新技术): 不阻塞 js 执行
    • 压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决内存的碎片化

9. 内存泄露

  • 意外的全局变量: 无法被回收
  • 定时器: 未被正确关闭,导致所引用的外部变量无法被释放
  • 事件监听: 没有正确销毁 (低版本浏览器可能出现)
  • 闭包: 会导致父级中的变量无法被释放
  • dom 引用: dom 元素被删除时,内存中的引用未被正确清空

可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化情况,找出异常点。

服务端与网络

1. HTTPS/HTTP协议

1.0 协议缺陷:

  • 无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
  • head of line blocking: 线头阻塞,导致请求之间互相影响

1.1 改进:

  • 长连接(默认 keep-alive),复用

  • host 字段指定对应的虚拟站点

  • 新增功能:

    • 断点续传

    • 身份认证

    • 状态管理

    • cache 缓存

      • Cache-Control
      • Expires
      • Last-Modified
      • Etag

2.0:

  • 多路复用
  • 二进制分帧层: 应用层和传输层之间
  • 首部压缩
  • 服务端推送

https: 较为安全的网络传输协议

  • 证书(公钥)
  • SSL 加密
  • 端口 443

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

  1. https协议需要到ca申请书,一般免费证书较少,因而需要一定费用。   

  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

  3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443

  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

  • TCP:

    • 三次握手

    • 四次挥手

    • 滑动窗口: 流量控制

    • 拥塞处理

      • 慢开始
      • 拥塞避免
      • 快速重传
      • 快速恢复
  • 缓存策略: 可分为强缓存协商缓存

    • Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires

    • 当缓存已经过期时,使用协商缓存

      • 唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,

      • 最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)

        • 如果一致,则直接返回 304 通知浏览器使用缓存
        • 如不一致,则服务端返回新的资源
    • Last-Modified 缺点:

      • 周期性修改,但内容未变时,会导致缓存失效
      • 最小粒度只到 s, s 以内的改动无法检测到
    • Etag 的优先级高于 Last-Modified

2. 常见状态码

  • 1xx: 接受,继续处理
  • 200: 成功,并返回数据
  • 201: 已创建
  • 202: 已接受
  • 203: 成功,但未授权
  • 204: 成功,无内容
  • 205: 成功,重置内容
  • 206: 成功,部分内容
  • 301: 永久移动,重定向
  • 302: 临时移动,可使用原有URI
  • 304: 资源未修改,可使用缓存
  • 305: 需代理访问
  • 400: 请求语法错误
  • 401: 要求身份认证
  • 403: 拒绝请求
  • 404: 资源不存在
  • 500: 服务器错误

3. get / post

  • get: 缓存、请求长度受限、会被历史保存记录
    • 无副作用(不修改资源),幂等(请求次数与资源无关)的场景
  • post: 安全、大数据、更多编码类型 两者详细对比如下图:

4. Websocket

Websocket 是一个 持久化的协议, 基于 http , 服务端可以 主动 push

  • 兼容:

    • FLASH Socket
    • 长轮询: 定时发送 ajax
    • long poll: 发送 --> 有消息时再 response
  • new WebSocket(url)

  • ws.onerror = fn

  • ws.onclose = fn

  • ws.onopen = fn

  • ws.onmessage = fn

  • ws.send()

5. TCP三次握手

建立连接前,客户端和服务端需要通过握手来确认对方:

  • 客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认
  • 服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态
  • 客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态

6. TCP四次挥手

  • 客户端 -- FIN --> 服务端, FIN—WAIT
  • 服务端 -- ACK --> 客户端, CLOSE-WAIT
  • 服务端 -- ACK,FIN --> 客户端, LAST-ACK
  • 客户端 -- ACK --> 服务端,CLOSED

7. Node 的 Event Loop: 6个阶段

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调

  • I/O 阶段: 执行上轮循环残流的callback

  • idle, prepare

  • poll: 等待回调

      1. 执行回调
      1. 执行定时器
      • 如有到期的setTimeout / setInterval, 则返回 timer 阶段 -如有setImmediate,则前往 check 阶段
  • check

    • 执行setImmediate
  • close callbacks

跨域

  • 什么是跨域?

    • 跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
  • 什么是同源策略

    • 同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指协议+域名+端口三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
  • 同源策略限制以下几种行为:

    • 1.) Cookie、LocalStorage 和 IndexDB 无法读取
    • 2.) DOM 和 Js对象无法获得
    • 3.) AJAX 请求不能发送
  • JSONP:

    • 基本思想:网页通过添加一个<script src=‘’>元素,向服务器请求JSON数据(<script>src属性获得js代码, 不受同源政策限制)。服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
    • 由于script脚本只可以通过get发送请求,所以jsonp只可以发送get请求
    • 具体做法:使用jQuery封装的Ajax,只需在客户端的Ajax请求的url中加入url?callback=?即可;
    • 服务器端需写入var callback = req.query.callback;返回的value需要封装成json,res.send(callback+'('+json+')')。
 function jsonp(url, jsonpCallback, success) {
  const script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
  • CORS(Cross-Origin Resource Sharing):
    • 跨域资源共享是一份浏览器技术的规范,以避开浏览器的同源策略,是 JSONP 模式的现代版。
    • 思想:使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功或失败。
    • 与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以让前端工程师用一般的 XMLHttpRequest,这种方式的错误处理比 JSONP 要来的好;另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。现代的浏览器都支持 CORS。
    • 设置 CORS:客户端不需要做什么,只需要在服务器端发送一个响应头即可:‘Access-Control-Allow-Origin’;
      • 如若允许所有域访问:Access-Control-Allow-Origin: *;如:header("Access-Control-Allow-Origin: *");
      • 如若只允许指定域访问:Access-Control-Allow-Origin: 域名A;如:header("Access-Control-Allow-Origin: http://www.test2.com");
  • postMessage

安全

  • XSS攻击: (Cross Site Script) ,跨站脚本攻击
    • 注入恶意代码,浏览器可以执行
    • 危害:cookie被窃取,用户未发布内容被泄露
    • 预防:过滤输入和转义输出
      • cookie 设置 httpOnly
      • 转义页面上的输入内容和输出内容
  • CSRF攻击: Cross-site request forgery),跨站请求伪造
    • 攻击者盗用了你的身份,以你的名义发送恶意请求
    • 不被第三方网站访问到用户的 cookie
    • referer:判断 referer 头,如果不是来自本网站的请求,就判定为CSRF攻击。但是该方法只能防御跨站的csrf攻击,不能防御同站的csrf攻击
    • 使用验证码:每一个重要的post提交页面,使用一个验证码,因为第三方网站是无法获得验证码的。还有使用手机验证码,比如转账是使用的手机验证码。
    • 使用token:每一个网页包含一个web server产生的token,提交时,也将该token提交到服务器,服务器进行判断,如果token不对,就判定位CSRF攻击
    • 将敏感操作又get改为post,然后在表单中使用token. 尽量使用post也有利于防御CSRF攻击。

Vue


1. nextTick

在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态

  • 新版本中默认是microtasks, v-on中会使用macrotasks

  • macrotasks任务的实现:

    • setImmediate / MessageChannel / setTimeout

2. 生命周期

  • _init_

    • initLifecycle/Event,往vm上挂载各种属性
    • callHook: beforeCreated: 实例刚创建
    • initInjection/initState: 初始化注入和 data 响应性
    • created: 创建完成,属性已经绑定, 但还未生成真实dom
    • 进行元素的挂载: $el / vm.$mount()
    • 是否有template: 解析成render function
      • *.vue文件:vue-loader会将<template>编译成render function
    • beforeMount: 模板编译/挂载之前
    • 执行render function,生成真实的dom,并替换到dom tree
    • mounted: 组件已挂载
  • update:

    • 执行diff算法,比对改变是否需要触发UI更新

    • flushScheduleQueue

      • watcher.before: 触发beforeUpdate钩子
      • watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI
    • 触发updated钩子: 组件已更新

  • actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活

  • destroy:

    • beforeDestroy: 销毁开始
    • 销毁自身且递归销毁子组件以及事件监听
      • remove(): 删除节点
      • watcher.teardown(): 清空依赖
      • vm.$off(): 解绑监听
    • destroyed: 完成后触发钩子

3. 数据响应(数据劫持)

数据劫持:vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

看完生命周期后,里面的watcher等内容其实是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher ) 和 依赖收集器( Dep ),其核心是 defineProperty这个方法,它可以 重写属性的 get 与 set 方法,从而完成监听数据的改变。

  • Observe (观察者)观察 props 与 state

    • 遍历 props 与 state,对每个属性创建独立的监听器( watcher )
  • 使用 defineProperty 重写每个属性的 get/set(defineReactive

    • get: 收集依赖
      • Dep.depend()
        • watcher.addDep()
    • set: 派发更新
      • Dep.notify()
      • watcher.update()
      • queenWatcher()
      • nextTick
      • flushScheduleQueue
      • watcher.run()
      • updateComponent()

双向数据绑定www.jianshu.com/p/23180880d…

4. virtual dom 原理实现

  • 创建 dom 树

  • 树的diff,同层对比,输出patchs(listDiff/diffChildren/diffProps)

    • 没有新的节点,返回
    • 新的节点tagName与key不变, 对比props,继续递归遍历子树
      • 对比属性(对比新旧属性列表):
        • 旧属性是否存在与新属性列表中
        • 都存在的是否有变化
        • 是否出现旧列表中没有的新属性
    • tagName和key值变化了,则直接替换成新节点
  • 渲染差异

    • 遍历patchs, 把需要更改的节点取出来
    • 局部更新dom

5. Proxy 相比于 defineProperty 的优势

  • 数组变化也能监听到
  • 不需要深度遍历监听
let data = { a: 1 }
let reactiveData = new Proxy(data, {
	get: function(target, name){
		// ...
	},
	// ...
})

6. vue-router

  • mode
    • hash
    • history
  • 跳转
    • this.$router.push()
    • <router-link to=""></router-link>
  • 占位
    • <router-view></router-view>

7. vuex

  • state: 状态中心
  • mutations: 更改状态
  • actions: 异步更改状态
  • getters: 获取状态
  • modules: 将state分成多个modules,便于管理

算法


1. 五大算法

  • 贪心算法: 局部最优解法
  • 分治算法: 分成多个小模块,与原问题性质相同
  • 动态规划: 每个状态都是过去历史的一个总结
  • 回溯法: 发现原先选择不优时,退回重新选择
  • 分支限界法

2. 二分查找

  • 二分查找的前提为:数组、有序。
  • 逻辑为:优先和数组的中间元素比较,如果等于中间元素,则直接返回。如果不等于则取半继续查找。
  • 二分查找,递归实现。
function binarySearch(target,arr,start,end) {
    var start   = start || 0;
    var end     = end || arr.length-1;

    var mid = parseInt(start+(end-start)/2);
    if(target==arr[mid]){
        return mid;
    }else if(target>arr[mid]){
        return binarySearch(target,arr,mid+1,end);
    }else{
        return binarySearch(target,arr,start,mid-1);
    }
    return -1;
}
  • 不使用递归实现
function binarySearch(target,arr) {
    var start   = 0;
    var end     = arr.length-1;

    while (start<=end){
        var mid = parseInt(start+(end-start)/2);
        if(target==arr[mid]){
            return mid;
        }else if(target>arr[mid]){
            start   = mid+1;
        }else{
            end     = mid-1;
        }
    }
    return -1;
}

3. 基础排序算法

  • 冒泡排序: 比较相邻的两个元素,如果前一个比后一个大,则交换位置(第一轮保证了后面最大)
    function bubleSort(arr) {
	    var len = arr.length;
	    for (let outer = 0 ; outer < len - 1; outer++) {
	        for(let inner = 0; inner <= len - 1 - outer; inner++) {
	            if(arr[inner] > arr[inner + 1]) {
	                [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
	            }
	        }
	    }
	    return arr;
	}
  • 选择排序: 遍历自身以后的元素,最小的元素跟自己调换位置(第一轮保证了前面最小)
function selectSort(arr) {
    var len = arr.length;
    for(let i = 0 ;i < len - 1; i++) {
        for(let j = i ; j<len; j++) {
            if(arr[j] < arr[i]) {
                [arr[i],arr[j]] = [arr[j],arr[i]];
            }
        }
    }
    return arr
}
  • 插入排序: 将元素插入到已排序好的数组中(保证了前i个有序)
function insertSort(arr) {
    for(let i = 1; i < arr.length; i++) {  //外循环从1开始,默认arr[0]是有序段
        for(let j = i; j > 0; j--) {  //j = i,将arr[j]依次插入有序段中
            if(arr[j] < arr[j-1]) {
                [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
            } else {
                break;
            }
        }
    }
    return arr;
}

4. 高级排序算法

  • 快速排序:快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序

    • 选择基准值(base),原数组长度减一(基准值),使用 splice
    • 循环原数组,小的放左边(left数组),大的放右边(right数组);
    • concat(left, base, right)
    • 递归继续排序 left 与 right
    function quickSort(arr) {
      if(arr.length <= 1) {
          return arr;  //递归出口
      }
      var left = [],
          right = [],
          current = arr.splice(0,1); 
      for(let i = 0; i < arr.length; i++) {
          if(arr[i] < current) {
              left.push(arr[i])  //放在左边
          } else {
              right.push(arr[i]) //放在右边
          }
      }
      return quickSort(left).concat(current,quickSort(right));
      }
    
  • 希尔排序:不定步数的插入排序,插入排序

  • 归并排序

    • 第一步,将数组一分为2,接着将分成的数组继续一分为2,直到长度为1
    • 当递归到了尽头,向上回溯,对于两个有序的数组,我们将它们合并成一个有序数组,从而完成整个归并排序(归并 从下往上)
    function merge(left, right) {
    var result = [];
    while(left.length > 0 && right.length > 0) {
       if(left[0] < right[0]) {
           result.push(left.shift());
       }
       else {
           result.push(right.shift());
       }
    }
    /* 当左右数组长度不等.将比较完后剩下的数组项链接起来即可 */
    return result.concat(left).concat(right);
    }
    function mergeSort(arr){
    	if(arr.length==1) {return arr};
    	var mid=Math.floor(arr.length/2);
    	var left_arr=arr.slice(0,mid),right_arr=arr.slice(mid);
    	return merge(mergeSort(left_arr),mergeSort(right_arr));
     }
    
  • 堆排序

  • 口诀: 插冒归基稳定,快选堆希不稳定

5. 递归运用:(斐波那契数列): 爬楼梯问题

function cStairs(n) {
    if(n === 1 || n === 2) {
        return n;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}

6. 数据树

  • 二叉树: 最多只有两个子节点

    • 完全二叉树:二叉树除开最后一层,其他层结点数都达到最大,最后一层的所有结点都集中在左边(左边结点排列满的情况下,右边才能缺失结点)。

    • 满二叉树

      • 深度为 h, 有 n 个节点,且满足 n = 2^h - 1
  • 二叉查找树: 是一种特殊的二叉树,能有效地提高查找效率

    • 小值在左,大值在右
    • 节点 n 的所有左子树值小于 n,所有右子树值大于 n

  • 遍历节点

    • 前序遍历
        1. 根节点
        1. 访问左子节点,回到 1
        1. 访问右子节点,回到 1
    • 中序遍历
        1. 先访问到最左的子节点
        1. 访问该节点的父节点
        1. 访问该父节点的右子节点, 回到 1
    • 后序遍历
        1. 先访问到最左的子节点
        1. 访问相邻的右节点
        1. 访问父节点, 回到 1
  • 插入与删除节点

7. 天平找次品

有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少需要称多少次能保证一定找到假币?

  • 三等分算法:
    • 将硬币分成3组,随便取其中两组天平称量

      • 平衡,假币在未上称的一组,取其回到 1 继续循环
      • 不平衡,假币在天平上较轻的一组, 取其回到 1 继续循环