JavaScript 引擎机制
多线程中较麻烦的问题是“死锁”(多个线程占用对方资源且不释放)和“惊群”(多个线程响应同一个问题)。为了降低浏览器多线程的复杂度,避免 JavaScript 引擎和渲染引擎同时操作 DOM,浏览器设计了一种机制,在某一时刻只有一个线程可以操作 DOM,也就是说 JavaScript 引擎和渲染引擎(内核)会互相阻塞。
如果页面引用的 js 文件执行时间较长,或由于网络原因造成下载 js 文件时间太长,那么浏览器会长时间出于白屏状态,造成较差的用户体验。这也是把 script 标签放到 body 标签尾部的原因,可以等渲染页面结束后再执行 js 引擎。
js 单线程如何实现异步
题外话:依赖浏览器的多线程机制
现代浏览器提供 service worker 的方式让 js 异步执行
数据持久层
- Cookie
历史最悠久,虽然是浏览器端离线存储方式,但更多用在服务器端读写。服务器端可以在响应头设置 Cookie 或从请求头读取 Cookie。如果服务器端设置了 HttpOnly 属性,浏览器端只能查看不能修改。
- LocalStorage / SessionStorage
以键值对的方式存储数据,数据类型为字符串。SessionStorage 会在浏览器关闭后清除,LocalStorage 需要手动清除。
模板引擎
Pug 和 FreeMarker
普通文档流
在 W3C 规范中其实叫 normal flow
遵循普通文档流的元素 position 属性值为 static。根据 display 属性值的不同还会分为 none、inline、inline-block 和 block。
- inline:宽度由内容决定,高度由font-size决定 。左右 padding 和 margin 有效,上下 padding 不占实际空间,上下 margin 无效。
- inline-block:宽高,padding 和 margin 都可以设置。特点 是 inline-block 元素之间会出现间距,可以通过将父元素 font-size 设为 0 解决。
- block:宽高,padding 和 margin 都可以设置。特点 是宽度以父元素为基准自动撑满整行
浮动布局
- 两列布局
左侧宽度固定,右侧宽度自适应
<div class='left'></div>
<div class='right'></div>
.left {
float: left;
height: 100px;
width: 100px;
background-color: #f00;
}
.right {
height: 100px;
margin-left: 100px;
background-color: #0f0;
}
- 圣杯布局
中间元素设 margin,两边元素定位 + margin-left
<div class="container">
<div class="center col"></div>
<div class="left col"></div>
<div class="right col"></div>
</div>
.container {
padding-left: 200px;
padding-right: 150px;
overflow: auto;
}
.col {
float: left;
position: relative;
height: 200px;
}
.center {
width: 100%;
background-color: #f00;
}
.left {
right: 200px;
margin-left: -100%;
width: 200px;
background-color: #0f0;
}
.right {
left: 150px;
margin-left: -150px;
width: 150px;
background-color: #00f;
}
- 双飞翼布局
当圣杯布局中间元素宽度小于左侧元素时布局会错乱。双飞翼布局就是针对这个问题提出的,该布局给中间元素加一个父容器,并使用 margin 给两边留出空间。
<div class="container">
<div class="wrap col">
<div class="center"></div>
</div>
<div class="left col"></div>
<div class="right col"></div>
</div>
* {
box-sizing: border-box;
}
.container {
position: relative;
}
.col {
float: left;
height: 200px;
}
.wrap {
width: 100%;
}
.center {
margin-left: 200px;
margin-right: 150px;
height: 200px;
border: 1px solid #f00;
}
.left {
margin-left: -100%;
width: 200px;
border: 1px solid #0f0;
}
.right {
margin-left: -150px;
width: 150px;
border: 1px solid #00f;
}
样式文件命名
先按照优化后的面向属性原则来抽取公共样式,然后针对各个公共组件按照 BEM 或 AMCSS 原则命名。
前端角度解决跨域
- JSONP(JSON with Padding)
虽然 AJAX 请求必须同源,但 HTML 上通过标签请求的资源比如 CSS 样式文件、图片文件、JS 脚本等可以不同源。因此可以创建一个 script 标签指向一个 CDN 服务器,脚本内部通常声明一个函数或变量以供调用,浏览器识别标签后向指定网站发送 GET 请求获得脚本内容并执行。
- CORS(Cross-Origin Resource Sharing)
虽然浏览器默认采用同源策略,但 W3C 还是开了个后门叫做 “跨域资源共享” 。这种跨域方式需要前后端同时支持,并在返回头部字段做修改。同时针对简单请求和非简单请求需要分别处理。
- 简单请求
请求方法:GET、HEAD、POST
浏览器发送请求时会在头部 Origin 字段带上本次请求的 “源” 信息(协议 + 域名 + 端口),服务器端会返回能够接收的源
- 非简单请求
在发送前先使用 OPTIONS 方法发起一个预检请求到服务器端,以获知服务器端是否允许该请求。预检请求可以避免跨域请求对服务器端的用户数据产生影响。
只有得到服务器端确认允许后才可以发送请求。
- WebSocket
WebSocket 和 HTTP 一样,也是基于 TCP 的一种网络协议。WS 没有同源权限,不过一般用来解决浏览器和服务器端的双向通信问题。
- 反向代理
反向指的是转发规则对客户端不可知,用户不知道后端地址。
利用代理服务器响应客户端请求。它的原理就是服务器端之间的通信是没有同源策略和跨域之说的,只不过要对代理服务器配置对应转发规则。
高效编写/组织代码的心法
- 拆分 —— 基于逻辑功能对代码拆分
- 抽象 —— 集合常用功能对代码抽象
模块管理
- 立即执行函数表达式
函数独有作用域。无法形成模块暴露,只能通过全局对象实现。
- 异步模块定义(AMD)
使用 define 函数定义模块和声明依赖。在声明 myModule 的时候会开始加载 moduleA 和 moduleB,但不能保证加载顺序。只支持浏览器端
define('myModule', ['moduleA', 'moduleB'], function() { ... })
- 共同模块定义(CMD)
使用 define 函数定义模块。在定义模块的时候会给声明函数注入 3 个参数 require、exports、module,require 用来动态引入模块,exports 用来暴露模块接口,module 提供当前模块的一些参数。支持浏览器和 Node.js
define('myModule', function(require, exports, module) {
var a = require('moduleA') // 同步加载
var b = require.async('moduleB') // 异步加载
exports.func = function() { ... }
exports.pa = a.xxx
exports.pb = b.xxx
})
- CommonJS
专注于 Node.js 服务端的模块管理规范。在模块中 exports 对象用于导出变量或函数。
var a = require('moduleA')
exports.func = function() { ... }
exports.pa = a.xxx
- 通用模块定义(UMD)
先判断是否支持 AMD,在判断是否支持 Node.js,否则公开到全局。
- import/export
import 静态加载模块,export 可以指定导出模块的某个变量或函数
双向数据绑定
数据绑定是 Model 层与 View 层的映射关系。
AngularJS
- 数据 => 视图
通过 “脏值检测” 机制,“脏” 数据指的是被修改过的数据。
实现:给每个视图模型 $scope 对象创建一个 $watchers 属性,该属性的值为数组,用来存储待检测对象。对象中包括回调函数、需要检测的值、上次更新的值等。这样每次执行检测的时候只要对数组进行遍历,然后比较数据的变化,如果发生变化则调用回调函数。
- 视图 => 数据
(从使用上来看直接用的指令)事件绑定,加一些细节操作
VueJS
- 数据 => 视图
Object.defineProperty
监听数据的修改,当数据变动时调用 set 函数进行操作。
- 视图 => 数据
(从使用上来看直接用的指令)同样是事件监听,根据事件调用回调函数触发视图更新。
单向数据流
数据流是指组件之间的数据流动。
ReactJS
- 数据 => 视图
通过 React 组件中的 state 变量和 setState 函数控制,将需要渲染的数据赋值到 state 变量上,然后通过 setState 函数更新 state 并同事渲染视图。
- 视图 => 数据
(它们用框架的指令,咱们自己写原生代码)事件监听,调用 setState 来更新视图。
路由
网络把信息源传到目的地址的过程,通常时服务端关注的内容。但前端单页面应用框架出现后,部分路由开始由前端来控制。
- hash
特点:①锚点 # 改变不会引起页面刷新;②hashchange 事件可以监听锚点变化。
- history
history 是可以访问浏览器中用来记录 URL 变化的历史堆栈的全局对象,可以通过它来操作浏览器的前进和后退。
特点:①history.pushState 和 history.replaceState 修改 URL 不会引起页面刷新;②popstate 事件能监听 history 的前进后退跳转函数
Cookie 和 token
根本区别:
基于 token 认证的服务器会把登陆状态信息编码后回传给浏览器;
基于 Cookie 认证的状态信息还是保存在服务器端的会话中,同时客户端 Cookie 会存储一个与 session 对应的 id。