-
Notifications
You must be signed in to change notification settings - Fork 0
Description
[TOC]
HTML & CSS
浏览器内核
浏览器内核分两部分:渲染引擎(layout engineer 或者 Rendering Engine)和 JS 引擎。由于JS执行引擎越来约独立,内核就倾向于只指渲染引擎。
- Trident:IE内核
DOCTYPE及其作用
DOCTYPE用来声明文档类型和DTD(Document type definition,文档类型定义)规范,不是HMLT标签。指明了浏览器应该用哪个版本HTML。
<!-- HTML5 -->
<!DOCTYPE html>
<!-- HTML 4.01 Strict 该 DTD 包含所有 HTML 元素和属性,但不包括展示性的和弃用的元素(比如 font) -->
<!-- HTML 4.01 Transitional 该 DTD 包含所有 HTML 元素和属性,但不包括展示性的和弃用的元素(比如 font) -->在HTML5中已废弃的元素有
<big>、<center>、<element>等,具体可在MDN:link:查看。
常用标签
section、nav、article、header、footer、main…… @
HMTL语义化
布局
- 浮动
- 定位
- flex
- table
- grid
优缺点
Flex
一维布局,一次只能处理一行或一列(相比一较与Grid)。
-
容器
flex-flowflex-direction:主轴方向 row | row-reverse | column | column-reverseflex-wrap:交叉轴 nowrap | wrap | wrap-reverse
justify-content:主轴对齐方式 flex-start | flex-end | center | space-between | space-aroundalign-items:交叉轴对齐方式 flex-start | flex-end | center | baseline | stretchalign-content:多根轴线的对齐方式,只有一根轴线时该属性不起作用 flex-start | flex-end | center | space-between | space-around | stretch
-
项目
-
order:排列顺序,较小值在前 -
flex:默认 0 1 auto,即不放大,随容器缩小,由内容撑起flex-grow:放大比例,默认0,不放大flex-shrink:缩小比例,默认为1,随容器缩小flex-basis:分配多余空间之前,项目占据的主轴空间,默认auto,项目本来大小
-
align-self:相对交叉轴对齐方式,覆盖align-itmesauto | flex-start | flex-end | center | baseline | stretch
-
Grid
网格布局,每个网格都有起止线,每两条线之间都有设定好的距离。
清除浮动
垂直居中
仅居中元素定宽高适用:
absolute + margin auto
.wp { position: relative: .box { position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; } }absolute + 负margin
.wp { position: relative: .box { position: absolute; top: 50% left: 50% margin-top: calc(10rem / 2); margin-left: calc(10rem / 2); } }absolute + calc
.wp { position: relative: .box { position: absolute; top: calc(50% - 10rem / 2); left: calc(50% - 10rem / 2); } }
居中元素不定宽高适用:
- absolute + transform
.wp { position: relative; .box { position: absolute; top: 50%; left: 50%; transfrom: translate(-50%, -50%); } }
- writing-mode
- lineheight:
.wp { height: 10rem; line-height: 10rem; .box { display: inline-block; verticle-align: middle; line-height: initial; // 默认继承会撑满容器 } }
- table
- css-table
.wp { display: table-cell; text-align: center; vertical-align: middle; .box { display: inline-block; } }
- flex
.wp { display: flex; justify-content: center; align-items: center; }
- grid
.wp { display: grid; .box { justify-self: center; align-self: center; } }
盒模型
box-sizing: content-box | border-box
JS获取盒模型的尺寸
- dom.style.width/height
- Dom.currentStyle.width/heigth
- window.getComputedStyle(dom).width/height
- Dom.getBoundingClientRect().width/height
边距融合
margin塌陷
- 0宽边宽或内边距
- 父设置
overflow: hidden最佳实践设置在伪类中 BFC
BFC
-
概念
BFC(Block formatting context)"块级格式化上下文"。它是一个独立的渲染区域。文档流分为定位流、浮动流和普通流三种。而普通流就是指BFC中的FC。
FC是formatting context 格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。
常见的FC有BFC、IFC(行级格式化上下文),还有GFC(网格布局格式化上下文)和FFC(自适应格式化上下文)
-
触发条件
-
根元素或包含,即HTML元素
-
float的值不为none
-
overflow的值不为visible
-
display的值为
行内块 inline-block
表格单元格 table-cell
表格标题 table-caption
弹性元素 flex
网格元素 grid
-
position的值为absolute或fixed
-
-
布局规则
- 内部的Box(盒模型)会在垂直方向,一个接一个地放置
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
- 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此
- BFC的区域不会与float box重叠
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
- 计算BFC的高度时,浮动元素也参与计算
-
使用场景
- 自适应两栏布局
- 可以阻止元素被浮动元素覆盖
- 可以包含浮动元素——清除内部浮动
- 分属于不同的BFC时可以阻止margin重叠
- ……
选择器性能分析
-
从右到左的选择器匹配方式决定了右边的第一个关键选择器很重要
-
后代选择器特别是当选择器在标签或通用类别中
-
提高性能的关键是匹配规则,对于一个给定的元素,需要匹配的规则越少,样式的解析就会越快。避免通用规则,请确保规则不以通用类型选择器作为结束,规则越多,匹配越慢,效率越低
-
尽量使用子选择器代替后代选择器,后者效率会慢很多
-
依赖继承属性
-
用唯一的 Class 名称来定位一个节点往往比组合定位更加快捷
-
选择器效率排序
- id选择器(#myid)
- 类选择器(.myclassname)
- 标签选择器(div,h1,p)
- 相邻选择器(h1+p)
- 子选择器(ul < li)
- 后代选择器(li a)
- 通配符选择器(*)
- 属性选择器(a[rel=”external”])
- 伪类选择器(a:hover,li:nth-child)
CSS规范和最佳实践
DOM事件�
DOM事件级别
-
DOM0级事件
- DOM标签上直接绑定
- JavaScript DOM元素上绑定
DOM0级事件处理程序的缺点在于一个处理程序无法同时绑定多个处理函数
-
DOM2级事件
addEventListener的第三个参数默认false,表示在冒泡阶段触发,反之true则在捕获阶段触发。 -
DOM3级事件
补充事件种类和自定义事件。
自定义事件:
-
new Event()
var event = new Event('build'); // Listen for the event. elem.addEventListener('build', function (e) { ... }, false); // Dispatch the event. elem.dispatchEvent(event);
-
new customEvent()
-
DOM事件模型
原微软只支持冒泡(bubbling),Netscape只支持捕获(capturing),W3C作出让步,统一模型为先捕获后冒泡。
- DOM事件流
- 捕获具体流程
Event对象
-
Event.preventDefault()
阻止事件的默认行为,
<a>标签的跳转或表单提交等。 -
Event.stopPropagation()
阻止事件进一步传播,捕获还是冒泡取决于当前事件的触发类型。
// 事件传播到 element 元素后,就不再向下传播了 element.addEventListener('click', function (event) { event.stopPropagation(); }, true); // 事件冒泡到 element 元素后,就不再向上冒泡了 element.addEventListener('click', function (event) { event.stopPropagation(); }, false);
stopPropagation方法只会阻止【该元素的当前事件(冒泡或者捕获)】的传播,不会阻止该节点的其他click事件的监听函数。也就是说,不是彻底取消click事件,它还可以正常创建一个新的click事件。
element.addEventListener('click', function (event) { event.stopPropagation(); console.log(1); }); element.addEventListener('click', function(event) { // 会触发 console.log(2); }
-
Event.stopImmediatePropagation()
阻止事件传播和元素上的同类型事件——只是阻止了同类型的事件,其他类型的事件依旧照常执行。
element.addEventListener('click', function (event) { // 会触发 console.log(‘改方法内的可以执行’); event.stopImmediatePropagation(); // 会触发 console.log(1); }); element.addEventListener('click', function(event) { // 不会被触发 console.log(2); });
-
Event.currentTarget
-
Event.target
event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget。简单来说:currentTarget始终是监听事件者,而target是事件的真正发出者。
文件
- 上传
- 读文件
- 二进制大文件和文件切割
<input type="file" multiple>
复制粘贴
JavaScript
原型和原型链
JavaScript是基于原型的编程语言——一切皆对象——每个对象都有一个原型对象,原型区别于类和实例,原型对象是实例对象的“模板”,对象以其原型对象为模板从原型继承属性和方法。任何对象都可以作为另外一个对象的原型对象,以此来共享属性(继承)。
对象属性操作时属性的查找先查找本地属性,没查找到则在其原型中查找,仍未找到就查找原型的原型,直到Object.prototype。这种层层查找的关系即为原型链。
基于OOP的语言中,子类实例中继承的属性和方法都是复制于父类,而 JavaScript 中是依赖于对象实例和其构造器之间建立的一个链接,通过原型链上溯查找属性和方法。
-
几条规则
- 所有的引用类型都可以自定义添加属性
- 所有的引用类型都有自己的隐式原型(proto)
- 函数都有自己的显式原型(prototype)
- 所有的引用类型的隐式原型都指向对应构造函数的显示原型
- 使用引用类型的某个自定义属性时,如果没有这个属性,会去该引用类型的__proto__(也就是对应构造函数的prototype)中去找
原型链
-
_proto_
__proto__隐式原型,所有引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通对象。 -
prototype
prototype显示原型,所有函数都有一个prototype属性,属性值也是一个普通对象。
所有引用类型(数组、对象和函数)的__proto__指向它的构造函数的prototype属性值。当一个对象实例中没有一个属性时会从其构造函数的prototype(即__proto__)中查找。
instanceof
instanceof运算符用于测试构造函数的prototype原型对象是否出现在被测试对象的原型链(_proto_)中。instanceof判断一个引用类型是什么引用类型,是通过__proto__(隐式原型一层一层往上找,能否找到对应构造函数的prototype)
- isPrototypeOf
- instanceof
- hasOwnProperty
一些关键字
-
__proto__:指向对象原型,非规范定义,可通过Object.getPrototypeOf获取 -
Prototype:
-
constructor
this
1.默认绑定
独立函数调用时,this指向全局对象,如果使用严格模式,那么全局对象无法使用默认绑定,this绑定至undefined并抛错(TypeError: this is undefined)
2.隐式绑定
当函数作为引用属性被添加到对象中,隐式绑定规则会把函数调用中的this绑定到这个上下文对象
3.显示绑定
运用apply call 方法,在调用函数时候绑定this,也就是指定调用的函数的this值
4.new绑定
就是使用new操作符的时候的this绑定。
函数
闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
继承
-
Object.create()
var a = {a: 1}; // a ---> Object.prototype ---> null var b = Object.create(a); // b ---> a ---> Object.prototype ---> null console.log(b.a); // 1 (继承而来)
-
ES6 class extends
class Super {} class Sub extends Super {} var sub = new Sub(); Sub.prototype.constructor === Sub; // ② true sub.constructor === Sub; // ④ true sub.__proto__ === Sub.prototype; // ⑤ true Sub.__proto__ === Super; // ⑥ true Sub.prototype.__proto__ === Super.prototype; // ⑦ true
-
ES5
function Super() {} function Sub() {} Sub.prototype = new Super(); Sub.prototype.constructor = Sub; var sub = new Sub(); Sub.prototype.constructor === Sub; // ② true sub.constructor === Sub; // ④ true sub.__proto__ === Sub.prototype; // ⑤ true Sub.prototype.__proto__ == Super.prototype; // ⑦ true
实质上就是将子类的原型设置为父类的实例。
ES6和ES5的继承是一模一样的,只是多了class 和extends ,ES6的子类和父类,子类原型和父类原型,通过__proto__ 连接。🔗
异步编程
- 概念
- 多Promise不分状态全部捕获
- 设置超时
ES6+
作用域
函数 -> 块
class
Class 是ES5原型链继承的语法糖。
证明:class 类的数据类型就是函数,类本身就指向构造函数。事实上,类的所有方法都定义在类的prototype属性上面。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
// 构造函数
function Point () {}
Point === Point.prototype.constructor // true同ES5不同点:
- 方法不可枚举(非class定义时需要用Object.defineProperty()手动定义非枚举类型)
- 不存在变量提升,真正执行前仍然声明的变量在临时性死区(var 由于声明和初始化是同时发生的,所以不会有临时性死区过程),真正执行时才初始化(离开临时性死区)和赋值。
- 仅且只能在严格模式运行
- 只能用new调用
解构
String、Number、Boolean, null, Undefined
严格模式
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象,ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
闭包
** 突破作用域链,将函数内部变量可以在外部访问**
数组常用方法
拷贝一个数组
不操作原数组并返回新数组的均可,都为潜拷贝。
slice、concat、Array.from
遍历方法
-
for 所有循环条件都可选
-
for...in 可枚举属性(键),环境不同顺序可能不同,原型链上添加的方法默认可枚举。
-
for...of ES6,任何实现了Iterator接口的数据结构均可,无键名,适合Map。
-
Array.forEach 数组内置,无法终止循环(
return只退出回调函数体,break(continue)只能用于原生循环中,比如while、do/while、for、for in和for of)。
ES6扩展
-
includes: string原型链也有该方法
-
Array.from: 可接受两个参数。转换源为类数组或iterable接口
模块化
Commonjs
同步加载,Node.js 支持的原生模块化方案。
AMD
AMD,Asynchronous Module Definition,异步模块定义。AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,这里形似依赖注入。
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 CMD 推崇依赖就近。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething() // 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething() // ...
})ES6 Module
“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
ES6 module 的复杂在于默认模块的导出和导入,这里需要记住,export default命令的本质是将后面的值,赋给default变量。
对于非默认导出,导入时都使用类似对象“解构”的模式,由于默认default导出其实是导出了一个变量default,所以不能使用“对象解构”。
关于循环依赖,ES6根本不会关心是否发生了"循环加载",只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
通信和存储
同源策略
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
- Cookie、localStorage、sessionStorage和IndexDB无分读取
- DOM无法获取
- AJAX请求无法发送
不受同源策略影响的标签有:
- script
- link
- img
- Video 和 audio 多媒体资源
- object、embed和applet插件
- frame
- @font-face 字体
前后端通信
-
Ajax
-
fetch
-
WebSocket
一种基于 TCP 的应用层协议,协议建立始于 HTTP 然后升级协议到 ws (或 wws),可以用于持久的双工通信。
-
CORS
跨域的“正统”解决办法。
Ajax
-
XMLHttpRequest对象工作流程
-
兼容性
-
事件触发条件
-
事件触发顺序
实现一个Ajax请求类
跨域
-
JSONP
JSON with Padding,使用
script标签的跨域实现。使用脚本创建<script>元素,发起跨域请求,返回结果形如callback(json),callback函数并将序列化的JSON数据取出。只支持GET请求。 -
Hash
-
postMessage
-
WebSocket
一种新的协议,不受同源策略影响。
-
CORS
支持跨域通信的Ajax, 全称是跨域资源共享(Cross-origin resource sharing)。
服务端资源响应头设置:
Access-Control-Allow-Origin: * // 允许访问的地址,通配符 * 标识所有请求源 Access-Control-Request-method: GET,POST
请求发起时浏览器会自动发送一个 OPTIONS “预检”请求,用来获取服务器支持的请求方式和检查请求来源是否安全。
浏览器存储
-
cookies
-
Storage
HTML5新API,以域名为分割单位,有至少5M的数据存储空间。同步存储。
-
localStorage
类似数据库,持久化存储。由于类似DB的生命周期特性,可在多个标签数据间共享数据。
-
sessionStorage
当前浏览器标签关闭后生命周期结束,标签之间不能数据共享(由于生命周期是维持在标签期间,所以标签之间的sessionStorage数据禁止共享是符合常规的)。
-
-
indexDB
HTTP族
HTTP协议特点
- 简单快速
- 灵活
- 无连接
- 无状态
HTTP报文组成
请求方法
-
GET
-
POST
- GET浏览器会退无害,POST会再次提交请求
- GET产生的URL地址可收藏,POST不可以
- GET会被浏览器主动缓存,POST需要手动设置
- GET请求参数会被完整保留在浏览器历史记录中,POST参数不会被保存
- GET请求URL参数长度有限,POST无限制
- GET只接受ASCII字符,POST无限制
- GET参数安全性不如POST
-
PUT
-
DELETE
-
HEAD
状态码
-
1xx:指示信息,表示请求已接收,继续处理
-
2xx:成功,表示请求已被成功接收
200 OK:请求成功
206 Partial Content:客户端发送了带有Range头的GET请求,服务器完成
-
3xx:重定向,要完成请求要进一步操作
301Moved Permanently:请求已转移到新URL
302 Found:请求内容临时转移到新URL
304 Not Modified:客户端存在缓存资源并发出有条件性的请求,服务器告诉客户端文件未过期
-
4xx:客户端错误,请求语法错误或请求无法实现
400 Bad Request:客户端请求有语法错误
401 Unauthorized:未授权
403 Forbidden:禁止访问
404 Not Found:资源不存在
-
5xx:服务器错误,服务器未能给出合法请求结果
500 Internal Server Error:服务器运行错误
503 Server Unavailable:请求未完成或服务器临时过载或当机,一段时间后可用
持久连接
当使用Keep-Alive模式(HTTP1.1,持久连接、连接重用)时,避免了后继请求时重新建立连接。在HTTP1.1版本支持。
管线化
普通持久连接下,请求消息过程类似:
请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3 ……
管线化后:
请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3 ……
- 管线化通过持久连接完成,仅HTTP1.1支持
- 只有GET和HEAD请求可完成,POST有限制
- 初次连接不应启动管线机制,服务器不一定支持HTTP1.1
- 管线化不会影响响应返回的顺序
- HTTP1.1要求服务器支持管线化,但不要求对响应进行管线化处理,只要响应不失败即可
HTTPS
HTTPS = HTTP + TLS
HTTPS主要解决了中间人攻击MITM(Man In The Middle Attack):
- 域名污染,DNS抢答
- ARP(Address Resolution Protocol)欺骗
RSA 非对称加密
RSA:三位数学家Rivest、Shamir 和 Adleman
- M:Hello
- M-ASCII:104 101 108 108 111
- EM:M ^ e % N
- M:EM ^ d % N
TCP
位码即tcp标志位,有6种标示:
- SYN(synchronous建立联机)
- ACK(acknowledgement 确认)
- PSH(push传送) FIN(finish结束)
- RST(reset重置) URG(urgent紧急)
- Sequence number(顺序号码)
- Acknowledge number(确认号码)
三次握手
- SYN → (Synchronize,同步序列,建立建立连接)
- SYN/ACK ← (Acknowledgement,确认标志 )
- ACK →
四次挥手
可以是任意端发起。
- ACK/FIN → (Finish)
- ACK ←
- ACK/FIN ←
- ACK →
慢启动
慢启动(Slow Start),是传输控制协议(TCP)使用的一种阻塞控制机制。 慢启动也叫做指数增长期。 慢启动是指每次TCP接收窗口收到确认时都会增长。
框架和库
- 组件的理解和分类
- 变化侦测机制
- 路由
- 状态和数据流管理
- 数据抓取和同步
- CSS 管理方案
- 构建工具链
- 同构/服务端渲染
- 跨平台渲染
- 类型系统
- 构建时优化
- 运行时优化
- Web Components 和框架的关系
- Web Assembly 和框架的关系
Angular.js
依赖注入DI
-
推断注入
推断注入下,对于压缩代码后形参(注入依赖名)会改变的问题不用过于担心,有ng-annotate库对其进行处理。
ng-annotate 库需要在注入函数内部的行首写‘ngInject’标识,其实最后转义结果等同使用
$inject显式注入。angular.module('myApp', []) .factory('greeter', function() { return { greet: function(msg) {alert(msg);} } }) .controller('MyController',function($scope, greeter) { //其中function中的greeter就是依赖注入 $scope.sayHello = function() { greeter.greet("Hello!"); }; });
-
显式注入
函数也即对象,在函数下挂载
MyController.$inject = ['$scope', 'greeter’];,参数名需要同步顺序。var app = angular.module('myApp', []) .factory('greeter', function() { return { greet: function(msg) {alert(msg);} } }); function MyController(renamed$scope, renamedGreeter) { renamed$scope.sayHello = function() { renamedGreeter.greet("Hello!"); }}; MyController.$inject = ['$scope', 'greeter']; app.controller('MyController', MyController);
-
行内注入
行内声明直接传入参数数组,最后一个参数是依赖注入的目标函数,参数名需同步顺序。
angular.module('myApp', []) .factory('greeter', function() { return { greet: function(msg) {alert(msg);} } }) .controller('MyController', ['$scope', 'greeter', function($scope, greeter) { // 这里注入的方式不一样了 // 在定义一个AngularJS的对象时,行内声明的方式允许我们直接传入一个参数数组而不是一个函数。 // 数组的元素是字符串,它们代表的是可以被注入到对象中的依赖的名字,最后一个参数就是依赖注入的目标函数对象本身。 $scope.sayHello = function() { greeter.greet("Hello!"); }; }]);
Angular.js缺点
-
脏检测机制
数据发生变化就会触发检测机制,随着项目体量和复杂度的增加,频繁检测会有性能问题
触发检测机制
- DOM事件,文本输入或按钮ng-click等
- 浏览器Location变更事件($location)
- XHR 相应事件 ($http)
- Timer 事件(
$timeout、$interval) - 执行
$digest或$apply
$apply already in progress
- 手动调用了
$socpe.$apply()或$scope.$digst()
解决:$scope.$$phase ($digest or $apply)
那些地方需要手动执行:
- 异步回调中
- 框架之外执行,比如 Jquery中执行
Angular
angular提供了两种编译方式:
- 即时(JIT)编译,它会在浏览器中运行时编译你的应用
- 预先(AOT)编译,它会在构建时编译你的应用。
AOT使用tsc编译TS代码,JIT使用ngc。
AOT:
- 渲染更快;
- 需要的异步请求更少——模板、样式内联;
- 体积更小;
- 提早错误暴露;
- 更安全。
开发器使用JIT, 产品期使用AOT。
特点
-
Angular-cli
-
SSR
服务器端渲染,可以使10S加载完成的单页应用1S加载完成。还可以针对每一个人视图去做SEO优化
-
跨平台
移动端和桌面端兼容。创建跨平台应用,手机应用,提供了可以在移动端和桌面端通用的ui组件。
-
真·开箱即用
Angular是一套完善的开发框架,区别于“库”。
Vue.js
特点
- 简单灵活,快速上手
- SSR只能依赖其他服务器渲染库。
React
特点
- 虚拟DOM
- 只有MVC中的V层,重点在UI层,需要其他技术配合。
MVVM的认识
双向绑定
Data -> view : Object.defineProperty (angularjs 脏检测,angular Object.defineProperty),具体🔗。
View -> Data: Input 事件。
-
Object.defineProperty
-
用法
-
Object.defineProperty与Reflect.defineProperty
A. Object.defineProperty 方法后续会迁移
B. Reflect.defineProperty 返回布尔值
-
手写对象监测
-
-
设计模式
- 观察者设计模式
- 伪代码实现
生命周期
- 节点
- 节点触发时机
源码和实现
浏览器及渲染
浏览器渲染过程
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
render Tree
https://segmentfault.com/a/1190000010298038
RenderLayer 树,满足以下任意一点的就会生成独立一个 RenderLayer。
- 页面的根节点的RenderObject
- 有明确的CSS定位属性(
relative,absolute或者transform) - 是透明的
- 有CSS overflow、CSS alpha遮罩(alpha mash)或者CSS reflection
- 有CSS 滤镜(fliter)
- 3D环境或者2D加速环境的canvas元素对应的RenderObject
- video元素对应的RenderObject
不是所有属性动画消耗的性能都一样,其中消耗最低的是transform和opacity两个属性著作权归作者所有。
CSS、JavaScript 解析阻塞
- CSS不会阻塞DOM解析,但是会阻塞页面渲染(render Tree)——所以要尽早下载CSS文件
- JavaScript 脚本(加载和执行)会阻塞DOM解析和渲染(render Tree),defer 加载异步,执行放到最后,async 加载异步,加载完就执行。
- JS脚本需要查询CSS信息,所以JS脚本还必须等待CSSOM树构建完才可以执行。这将相当于CSS阻塞了JS脚本,同时JS脚本阻塞了DOM树构建。
重排Reflow
所有DOM元素都有盒模型,将盒子定位到它该出现的位置即为Reflow。
触发:
- 增加、删除、修改DOM节点;
- 移动DOM位置或动画;
- 修改CSS样式,display、position、size ……;
- Resize窗口或滚动;
- 修改字体属性;
- ……
重绘Repaint
盒模型对应有盒子的位置、大小、颜色等属性,浏览器按照这些特性渲染盒子的过程即为Repaint。
触发:
- 重排;
- DOM改动;
- CSS改动,颜色、visible等非几何改变;
- ……
浏览器性能的瓶颈之一在于DOM渲染的代价很高,很多优化方案的思路也是从避免重排重绘出发。浏览器的性能分析中,重排(reflow)导致Rendering 事件,重绘(repaint)导致Painting 事件。CSS属性出发重绘和重排的信息信息可参考csstriggers。
提高页面性能优化
浏览器通过队列化批量执行来优化重排过程,但是部分操作会导致强制队列刷新执行——确保得到的是期望的值:
- offsetTop, offsetLeft, offsetWidth, offsetHeigth
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeigth
提高网页性能,就是要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染。
-
DOM 的多个读操作(或多个写操作)应该放在一起
// bad div.style.left = div.offsetLeft + 10 + "px"; div.style.top = div.offsetTop + 10 + "px"; // good var left = div.offsetLeft; var top = div.offsetTop; div.style.left = left + 10 + "px"; div.style.top = top + 10 + "px";
- 样式表越简单,重排和重绘就越快。
- 重排和重绘的DOM元素层级越高,成本就越高。
- table元素的重排和重绘成本,要高于div元素
-
缓存后续会用到的会触发重排的操作结果,如获取位置等
-
样式修改通过修改class或csstext属性一次性改变样式
// bad var left = 10; var top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // good el.className += " theclassname"; // good el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
-
批量修改DOM,避免使用操作真实DOM,通过改变离线DOM(虚拟DOM)加工元素后一次性生效改动
-
DocumentFragment是没有父级的最小文档对象(轻量的document),不是DOM树的一部分,其属性参数改变不会引起重排(reflow)。
const fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.querySelector('body').appendChild(fragment);
-
cloneNode()
返回节点的副本。
const old = document.querySelector('body'); const cloneNode = old.cloneNode(true); appendDataToElement(cloneNode, data); old.parentNode.replaceChild(cloneNode, old);
-
-
元素脱离文档流——减少重排范围
- 绝对定位(包括fixed)
-
CSS技巧型
- 操作前
display: none,操作结束恢复显示,共两次重新渲染。 - position属性为absolute或fixed,由于不受其他元素的影响,会较少次数的引起重排。
- 操作前
-
使用虚拟DOM库(react等)
-
其参数是一个回调函数,指定操作在下次执行重绘(repaint)前执行,动画和滚动事件(scroll)等密集型重新渲染任务时使用。
使用 setTimeout 实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,而且还浪费 CPU 资源。而 rAF 则完全不同,当页面处理未激活的状态下,该页面的屏幕绘制任务也会被系统暂停,因此跟着系统步伐走的 rAF 也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了 CPU 开销。
requestAnimationFrame 具有函数节流效果。
-
该方法也可以用来调节重新渲染。它指定只有当一帧的末尾有空闲时间,才会执行回调函数。
-
对于大量计算密集型操作可单独放在worker线程中计算,主线程只用于UI渲染。
JS运行机制
浏览器线程
-
JS单线机制
-
浏览器线程机制
- GUI渲染线程
- javascript引擎线程
- 浏览器事件触发线程
- HTTP请求线程
除了
GUI渲染线程和js引擎线程是互斥的(安全考虑,js可影响渲染)。其他线程相互之间,都是可以并行执行的。
Event Loop
js引擎线程会循环从 任务队列 中读取事件并且执行,这种运行机制称作 Event Loop (事件循环)。
-
任务队列
-
同步任务
-
异步任务
优先同步任务,后处理异步任务。会被放入任务队列的回调有:setTimeout、setInterval、DOM事件、Promise、ajax等。
异步执行任务放入任务队列的时机? https://juejin.im/post/5b06ab416fb9a07a9919413d
-
错误监控
-
前端错误分类
-
即时运行错误的捕获方式
A. try...catch
B. window.onerror
-
资源加载错误
A. Object.onerror
B. Performance.getEntries()
C. Error事件捕获
跨域的JS运行错误是否可以捕获,错误是什么,应该如何处理?
- 在script标签添加crossOrigin属性
- 资源响应头Access-Control-Allow-Origin: *
-
上报错误
A. Ajax上报
B. Image对象上报
(new Image()).src='https://test.com/?a=1'
-
页面性能和缓存
页面性能关注的指标有:白屏时间、首屏时间、整页时间、DNS时间、CPU占用率等。
页面的生命周期
- DOMContentLoaded:已完全加载HTML,DOM树已构建完毕,但
等外部资源还未加载完成。此时JavaScript 可以访问DOM节点,初始化页面。
- load:已加载完毕所有资源,包括图表、样式等。
- Beforeunload:页面离开时触发,在未提交保存的页面关闭时可用于提示用户保存修改内容。
DOMContentLoaded 和脚本
HTML 中的 <script>脚本文件的执行永远跟UI渲染线程互斥,<script>脚本在设置了 defer 或 async 属性时,脚本异步的下载(未设置时脚本加载和UI线程也是互斥的),但defer延迟到DOM树创建好再执行,后者异步执行执行,defer强调顺序——DOM树创建好执行,多脚本按照出现顺序执行,async强调异步——下载完即执行,多脚本时与页面出现顺序无关。
网络传输性能优化
网络链路:
浏览器在接收到请求后大致流程是:重定向 → 拉取缓存 → DNS解析 → 建立TCP连接 → 发起请求 → 接收响应 → 处理HTML元素 → 加载完成。
浏览器缓存
所有浏览器缓存都是针对非首次访问,所以浏览器缓存策略对首屏加载是没有优化作用的。Safari 对HTML资源强制协商缓存(304)。
-
强缓存(不进行询问请求,200)
Expires Expires: Thu Jun 28 2018 22:32:05 GMT+0800 (中国标准时间)(时间标准可能不一致)
Cache-Control: Cache-Control: max-age=3600
都存在时
Cache-Control优先级高。 -
协商缓存(先询问请求,304)
Last-Modified -> If-Modified-Since: Thu Jun 28 2018 22:32:05 GMT+0800 (中国标准时间)
Etag -> If-None-Match (Hash),优先级比Modified高
之所以拉取缓存会出现200、304两种不同的状态码,取决于浏览器是否有向服务器发起验证请求。 只有向服务器发起验证请求并确认缓存未被更新,才会返回304状态码。
资源打包压缩
硬件提升
升级硬件,买好的CDN服务,请好的开发工程师可以很大很高效的提高性能。
-
资源压缩合并,减少HTTP请求
-
非核心代码异步加载
A. defer
B. async
-
利用浏览器缓存 -> 缓存分类 -> 缓存原理
-
浏览器缓存
-
强缓存(不进行询问请求)
Expires Expires: Thu Jun 28 2018 22:32:05 GMT+0800 (中国标准时间)(时间标准可能不一致)
Cache-Control: Cache-Control: max-age=3600
都存在时
Cache-Control优先级高。 -
协商缓存(先询问请求)
Last-Modified If-Modified-Since Last-Modified: Thu Jun 28 2018 22:32:05 GMT+0800 (中国标准时间)
Etag If-None-Match (Hash)
-
-
代理服务器缓存
-
网关缓存 (负载均衡、CDN)
-
serviceWorker
-
-
使用CDN
由于浏览器对同一域名下的请求的个数的限制(Chrome 是6个),一般进一步优化方案是使用多个CDN域名部署资源。
-
预解析DNS
内存泄露
分析
- performance
- memory
场景场景
- 意外的全局变量
- 被遗忘的定时器和回调函数
- 闭包
- DOM引用
安全
CSRF
-
概念
-
概念:跨站请求伪造,cross-site request forgery。
-
原理
在B网站引诱用户访问A网站(用户之前登陆过A网站,浏览器cookie缓存了身份验证信息),调用A网站的接口攻击A网站。
-
特点
- 需要登陆;
- 接口存在漏洞。
-
-
防御
-
Token验证
登陆成功后服务器下发token令牌存到用户本地,再次访问时要主动发送token,浏览器只能主动发cookie,做不到主动发token。
-
Referer验证
判断页面来源是否自己站点的页面,不是不执行请求。
-
隐藏令牌
令牌放在http header头中,而不是链接中。
-
XSS
-
概念
-
概念:跨域脚本攻击,cross-site scripting,页面注入JS脚本。
-
攻击方式
- 反射型,XSS代码在URL中存储,提交服务器后再相应返回浏览器并解析执行。整个过程是一次反射过程。
- 存储型,代码会存储在服务端(数据库、内存或文件系统)
-
-
防御措施
-
编码
对用户输入HTLM进行entity 实体编码
-
过滤
- 移除用户上传DOM的部分属性,如onerror等
- 移除用户上传的Style节点、Script、Iframe节点等。
-
校正
- 避免直接对HMLT entity 解码
- 使用DOM Parse转换,校正不配对的DOM标签
-
SQL注入
正则表达式
- /i (忽略大小写)
- /g (全文查找出现的所有匹配字符)
- /m (多行查找)
- /gi(全文查找、忽略大小写)
- /ig(全文查找、忽略大小写)
算法
算法的衡量标准:时间复杂度、空间复杂度。
- 时间复杂度,反应程序执行时间随输入增长而增长的量级。
- 空间复杂度,反应随问题规模的增长消耗存储空间的量级。
排序
常见的排序算法有:冒泡、快速、选择、希尔、归并、堆排序等。
快速
function quickSort(sorData = []) {
if (!Array.isArray(sorData))
throw Error(`TypeError: ${sorData} is not Array!`);
if (sorData.length <= 1)
return sorData;
const left = [],
right = [];
const _pointVal = sorData[0];
for (let i = 1, len = sorData.length; i < len; i++) {
const _currentVal = sorData[i];
if (_currentVal <= _pointVal)
left.push(_currentVal);
else
right.push(_currentVal);
}
return quickSort(left).concat(_pointVal, quickSort(right));
}https://segmentfault.com/a/1190000009426421
选择
https://segmentfault.com/a/1190000009366805
希尔
https://segmentfault.com/a/1190000009461832
冒泡
比较
二叉树
二叉查找树(BST)实现
BST:对于任意一个节点 n,
- 其左子树(left subtree)下的每个后代节点(descendant node)的值都小于节点 n 的值;
- 其右子树(right subtree)下的每个后代节点的值都大于节点 n 的值;
- 任意节点的左右子树也为BST。
class BinarySearchTree {
constructor() {
this.root = null;
}
Node(key) {
let left = null;
let right = null;
return {
key,
left,
right
}
}
insert(key) {
let newNode = this.Node(key);
if (this.root === null) {
// 如果根节点为空,那么插入的节点就为根节点
this.root = newNode;
} else {
// 如果根节点不为空
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
// 当新节点比父节点小,插入左边
if (newNode.key < node.key) {
// 左边没有内容则插入
if (node.left === null) {
node.left = newNode;
} else {
// 有内容就继续递归,直到没有内容然后可以插入
this.insertNode(node.left, newNode);
}
} else {
// 右边和左边相同,不重复说明
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
}堆栈、队列、链表
递归
递归最常见的两个例子是斐波那契数列和阶乘。
function factorial(n) {
if (n === 0) {
return 1
}
return n * factorial(n - 1)
}-
调用栈
递归调用每次函数在未到达终止条件时都会压入调用栈(压栈),如果我们如下调用
factorial(5),调用站如下:如果调用参数特别大,调用栈长度会非常大,导致内存占用飙升甚至内存溢出程序奔溃。
-
尾递归
压栈导致了调用栈不断加深,尾递归通过设置累加参数,实现尾递归调用,空间复杂度O(n) -> O(1)。表现形式就是递归调用时函数最后的执行是函数调用,而不是包含函数调用的表达式。
比如:
// 尾调用 function f(x){ return g(x); } // 非尾调用 function f(x){ return g(x) + 1; }
阶乘的尾递归:
function factorial(n, total = 1) { if (n === 0) { return total } return factorial(n - 1, n * total) }
注意: 1. 尾递归使得递归函数调用更加安全,并非效率的提升。甚至会变慢。
-
proper tail calls (tail call optimisation)
proper tail calls (tail call optimisation) 即尾递归优化是ES6的提案,但是在浏览器端实现的只有safari,也就是除Safari外的浏览器都未实现尾递归优化。
如下chrome浏览器中尾递归调用:
node.js中通过
--harmony_tailcalls参数实现尾递归调用。尾调用优化已经被实现但是没有在特性中默认支持的理由目前正在TC39标准委员会中讨论
波兰式和逆波兰式
设计模式
PWA
Web Workers
Web workers 创建的线程是系统级别的,也就是说JavaScript可以创建真的多线程。
WebSocket
概念
WebSocket 是HTML5版本中一种新的协议。websocket 连接建立基于HTTP,初期先发送HTTP,然后升级协议,此时状态码是101(Switching protocols)。
- 建立于 TCP 协议之上的应用层;
- 一旦建立连接(直到断开或者出错)服务端与客户端握手后则一直保持连接状态,是持久化连接;
- 服务端可通过实时通道主动下发消息,基于TCP的双工通信;
- 数据接收的「实时性(相对)」与「时序性」;
连接建立
Request
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket // 升级到websocket协议
Connection: Upgrade // 连接需要升级协议,而非HTTP
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== // 浏览器随机生成base64,用来验证服务端是否支持 ebsocket 算法
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13 // 13版本,W3C公布的稳定版
Origin: http://example.comResponse
HTTP/1.1 101 Switching Protocols // 101 切换协议
Upgrade: websocket // 标识切换成功
Connection: Upgrade // 标识切换成功
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= // 根据 Request 中的Sec-WebSocket-Key 计算的 base64,返回错误则建立连接失败
Sec-WebSocket-Protocol: chat在Nginx等代理工具中同样需要升级协议 (http -> web socket):
location /socket/ {
proxy_pass https://localhost:18080/;
proxy_http_version 1.1;
proxy_set_header Connection Upgrade;
proxy_set_header Upgrade websocket;
}wws
Websocket使用 ws 或 wss 的统一资源标志符,其中 wss 表示在 TLS 之上的 Websocket(即基于SSL的ws,类似HTTPS),ws的默认端口是80,wws的默认端口是443 。如:
ws://example.com/chat
wss://example.com/chat心跳机制 ping/pong
Chrome 或 Nginx 会把持续60s 没有活动的Websocket关闭,同时网络世界具体路线和经过的设备是不确定的,不确定的防火墙、路由、反向代理等工具会自作主张把长时间无消息的socket关闭。
websocket规范定义了心跳机制,一方可以通过发送ping(opcode 0x9)消息给另一方,另一方收到ping后应该尽可能快的返回pong(0xA)。
HTTP2.0
HTTP/2 只支持 HTTPS ,建立连接前先握手,浏览器会发送一个 Client Hello 的包给服务端,这个包里包含了他是否支持 HTTP/2 的信息。
多路复用 (Multiplexing)
多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」,Chrome 是6个限制。这也是为何一些站点会有多个静态资源 CDN 域名的原因之一。
HTTP2协议把通信的最小基本单位缩小为一个一个帧,并行的在同一个TCP连接上双向交换信息。
二进制分帧(frame)
跟HTTP1.x 相比,HTTP2 不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段,突破 HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。关键之一就是在 应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。
HTTP1.x的首部信息对应封装到 HEADER frame,Request Body 封装到DATA frame。
单一长链接
HTTP/2 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。
HTTP 性能优化的关键并不在于高带宽,而是低延迟。HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接。Websocket协议也是基于此。
总结:
- 单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大
- 由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快
首部压缩(Header Compression)
HTTP/1.1并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生。而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。
服务端推送(Server Push)
即一个请求和可以对应多个相应。
工程化
Gulp
Gulp是一个基于流(pipe)的自动化构建工具。 除了可以管理和执行任务,还支持监听文件、读写文件。
- gulp.task 注册任务
- gulp.run 执行任务
- gulp.watch 监听文件变化
- gulp.src 读文件
- gulp.dest 写文件
Webpack
Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。
执行过程:
- 解析webpack配置参数,合并从shell传入和webpack.config.js文件里配置的参数,生产最后的配置结果。
- 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
- 从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
- 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
- 递归完后得到每个文件的最终结果,根据entry配置生成代码块chunk。
- 输出所有chunk到文件系统。
Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。Loader:模块转换器,用于把模块原内容按照需求转换成新内容。Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
Rollup
Rollup 的亮点在于能针对 ES6 源码进行 Tree Shaking 以去除那些已被定义但没被使用的代码,但webpack也有该功能。
Node.js
Path.resolve() 和 Path.join() 区别
- path.resolve 会把路径解析成一个绝对路径
其他
浏览器通知 Notification API
// 权限判断, Notification.permission 只读属性
// default :不知道用户的选择,默认。
// granted :用户允许。
// denied :用户拒绝。
Notification.requestPermission().then(permission => {
if(permission === 'granted'){
console.log('用户允许通知');
}else if(permission === 'denied'){
console.log('用户拒绝通知');
}
});
// 权限通过时发送一条通知
const n = new Notification('状态更新提醒',{
body: '你的朋友圈有3条新状态,快去查看吧',
tag: 'linxin',
icon: 'https://avatars2.githubusercontent.com/u/14943597?s=460&v=4',
requireInteraction: true
});
n.onclick = function(){
window.open(n.data.url, '_blank'); // 打开网址
n.close(); // 并且关闭通知
}文件读取 FileReader API
-
文件预览
常规方法是「上传后再预览」,可实现上传前预览的方法有
FileReader和URL.createObjectURL。<!DOCTYPE html> <html lang="en"> <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>文件预览</title> </head> <body> <section> <h3>FileReader</h3> <input type="file" onchange="onUpload1(this.files[0])"> </section> <section> <h3>URL.createObjectURL</h3> <input type="file" onchange="onUpload2(this.files[0])"> </section> </body> <script> function onUpload1(file) { const fs = new FileReader(); fs.readAsDataURL(file); // 文件读取为 Data URL fs.onload = e => { const result = e.target.result, body = selector => document.querySelector(selector); if (/image/g.test(file.type)) { const img = `<img src="${result}">`; // 'beforebegin' 元素自身的前面。 // 'afterbegin' 插入元素内部的第一个子节点之前。 // 'beforeend' 插入元素内部的最后一个子节点之后。 // 'afterend' 元素自身的后面。 body('section:nth-of-type(1)').insertAdjacentHTML('beforeend', img); return; } if (/video/g.test(file.type)) { const video = `<video controls src="${result}"></video>`; body('section:nth-of-type(1)').insertAdjacentHTML('beforeend', video); } }; } function onUpload2(file) { const blob = new Blob([file]), // 文件转成二进制 url = URL.createObjectURL(blob), // 转成URL body = selector => document.querySelector(selector); if (/image/g.test(file.type)) { const img = `<img src="${url}">`; img[0].onload = e => URL.revokeObjectURL(this.src); // 释放createObjectURL创建的对象 body('section:nth-of-type(2)').insertAdjacentHTML('beforeend', img); return; } if (/video/g.test(file.type)) { const video = `<video controls src="${url}"></video>`; body('section:nth-of-type(2)').insertAdjacentHTML('beforeend', video); video[0].onload = e => URL.revokeObjectURL(this.src); // 释放createObjectURL创建的对象 } } </script> </html>
service worker
- 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。
- 一旦被 install,就永远存在,除非被手动 unregister
- 用到的时候可以直接唤醒,不用的时候自动睡眠
- 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
- 离线内容开发者可控
- 能向客户端推送消息
- 不能直接操作 DOM
- 必须在 HTTPS 环境下才能工作
- 异步实现,内部大都是通过 Promise 实现
题
实现数组MAP
📌
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array }[,
thisArg])💯
Array.prototype._map = function(fn, context) {
if (typeof fn !== 'function')
throw Error(`TypeError: ${fn} not a function !`);
const temp = [];
for (let i = 0, len = temp.length; i < len; i++) {
temp.push(fn.call(context, this[i], i, this));
}
return temp;
}
Object.defineProperty(Array.prototype, '_map', {enumerable: false})数组扁平(降维)
📌 [1, [2], [3, [4, [5], 5, 6], 7, 8], [9]] => [1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
🔥
- 递归
- 数组方法
- ES6+
💯
// 扩展运算符, concat 的参数可以是具体数值或数组
function flatten(arr = []) {
while(arr.some(a => Array.isArray(a))) {
arr = [].concat(...arr);
}
return arr;
}
// reduce 实现
function flatten(arr = []) {
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) && cur.length
? flatten(cur)
: cur
);
}, []);
}
// 常规实现
function flatten(arr = []) {
let result = [];
arr.forEach(a => {
result = result.concat(Array.isArray(a)
? flatten(a)
: a
);
});
return result;
}
// 其次可借助数组方法 flat !!!算法 递归 数组方法
累加器 Currying
📌
add(1)(2)(3)(4, 5, 6) // 21🔥
- 柯里化
- 隐式转换
💯
function add (...rest) {
const process = (...r) => {
rest = rest.concat(r);
return process;
}
process.valueOf = () => rest.reduce((n1, n2) => n1 + n2);
return process;
}判断一个数字是否出现过
// 闭包
function isFirst() {
const cache = [];
return num => {
if (typeof num !== 'number') return;
if (cache.includes(num)) return true;
else {
cache.push(num);
return false;
}
};
}遍历DOM
💯
function domTreeInterator(node, processFn) {
if (!node.nodeType)
throw Error(`TypeError: ${node} is not element node!`);
processFn(node);
node = node.firstElementChild;
while(node){
domTreeInterator(node, processFn);
node = node.nextSibling;
}
}写一个能遍历对象和数组的通用forEach函数
function _forEach(sorData, fn) {
if (!sorData) return;
const isArray = Array.isArray(sorData),
isObj = typeof sorData === 'object' && (sorData.constructor === Object || sorData.constructor === undefined);
if (!(isArray || isObj)) return new Error('Error type!')
if (isArray) {
sorData.forEach((...args) => fn(...args));
}
if (isObj) {
const entries = Object.entries(sorData);
for (const [key, value] of Object.entries(sorData)) {
fn(value, key, sorData);
}
}
}解析URL查询参数
📌 如下:
const url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseQuery(url);
// 结果
{
user: 'anonymous',
id: [123, 456], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文
enabled: true, // 未指定值的 key 约定值为 true
}🔥
方法很多,可以用 URLSearchParams ,其执行结果可直接用在 for...of 中, 可以不使用 entries()
💯
function parseQuery(url = '') {
if (typeof url !== 'string' || !url.includes('?')) return;
const queryObj = {}, uri = decodeURIComponent(url).trim();
const query = new URLSearchParams(/\?.+/.exec(uri)[0]);
for (let [k, v] of query) { // 等同for (let [k, v] of query.entries()) {
if (v && !isNaN(+v)) v = +v;
if (v !== undefined) {
if (k in queryObj) {
const _cache = queryObj[k];
queryObj[k] = [].concat(_cache, v)
} else {
queryObj[k] = v;
}
} else {
queryObj[k] = true;
}
}
return queryObj;
}实现一个简单的模板引擎
📌 如下:
render('我是{{name}},年龄{{age}},性别{{sex}}', {
name:'姓名', age:18
});
// 我是姓名,年龄18,性别undefined。🔥
- String.prototype.replace()
- 正则表达式,非贪婪模式
replace如果第一个参数是正则表达式, 并且其为全局匹配模式, 那么第二个参数为函数则每次匹配都会被调用,函数返回值是替换字符串。
💯
function render(tpl = '', data = {}) {
return tpl.replace(/\{\{(\S+?)\}\}/g, (match, p) => data[p]);
}数字变成逗号分隔格式(千位符)
📌 12345678.98765 => 12,345,678.98765
🔥
- 正则表达式,向前查找(先行断言,后行断言在ES9中开始支持,子表达式的一种特殊情况),词的边界
- Replace
所谓单词的边界是指一个能够用来构成单词的字符(数字、字母和下划线即\w)和一个不能用来构成单词的字符(\W)之间的位置。
💯
// 1. 从字符串的开头正向后查找1到多次数字(默认贪婪模式, 从开头查找是为避免小数位)
// 2. 正向前查找三个连续数字1到多次
// 3. 找到位置,插入「,」
'12345678.98765'.replace(/(?<=^\d+)(?=(\d{3})+\b)/g, ',');SameValue 算法
📌 ES6 新API Object.is ,规范参考
🔥
- 除以下两条外 其他同严格等于
- NaN === NaN // true
- +0 === -0 // false
💯
Object.defineProperty(Object, 'sameVal', {
value(x, y) {
if (x === undefined || y === undefined) return false;
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
}
// if(x.toString() === 'NaN') {
// return y.toString() === 'NaN';
// }
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true,
});自动填充对象
📌
> var tree = Tree();
> tree
{ }
> tree.branch1.branch2.twig = "green";
> tree
{ branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
{ branch1: { branch2: { twig: "green" },
branch3: { twig: "yellow" }}}💯
function Tree() {
return new Proxy({}, {
get (target, key, receiver) {
console.log(`${key}`);
if (!(key in target)) {
target[key] = Tree();
}
return Reflect.get(target, key, receiver);
}
});
}缓存代理的工厂函数
💯
function getCacheProxy(fn, cache = new Map()) {
return new Proxy(fn, {
apply(target, context, argsList) {
const args = argsList.join('');
if (cache.has(args)) {
return cache.get(args);
}
const res = fn(...argsList)
cache.set(args, res);
return res;
}
});
}
const getFibProxy = getCacheProxy(getFib);
getFibProxy(40); // 102334155
getFibProxy(40); // 输出40的缓存结果: 102334155表单验证代理
📌
// 表单对象
const userForm = {
account: '',
password: '',
}
// 验证方法
const validators = {
account(value) {
// account 只允许为中文
const re = /^[\u4e00-\u9fa5]+$/;
return {
valid: re.test(value),
error: '"account" is only allowed to be Chinese'
}
},
password(value) {
// password 的长度应该大于6个字符
return {
valid: value.length >= 6,
error: '"password "should more than 6 character'
}
}
}💯
const getValidateProxy = (target, validators) => {
return new Proxy(target, {
_validators: validators,
set(target, prop, value) {
if (value === '') {
console.error(`"${prop}" is not allowed to be empty`);
return target[prop] = false;
}
const validResult = this._validators[prop](value);
if(validResult.valid) {
return Reflect.set(target, prop, value);
} else {
console.error(`${validResult.error}`);
return target[prop] = false;
}
}
})
}
const userFormProxy = getValidateProxy(userForm, validators);
userFormProxy.account = '123'; // "account" is only allowed to be Chinese
userFormProxy.password = 'he'; // "password "should more than 6 character数字转换成中文大写
📌 10001 -> 一万零一
判断a字符串是否包含在b字符串中(KPM算法)
📌 实现一个函数,可以判断 a 字符串是否被包含在 b 字符串中,不借助原生API和正则
🔥
- 算法
💯
简单类型变量的监听
📌
// 有一个全局变量 a,有一个全局函数 b,实现一个方法bindData,执行后,a的任何赋值都会触发b的执行。
var a = 1;
function b(){
console.log('a的值发生改变');
}
bindData();
a = 2; // 此时输出 a的值发生改变指定目录文件读取
📌 实现一个函数,返回指定目录下的所有JavaScript文件列表。
🔥
- Node.js
- 递归
💯
const {readdirSync, existsSync, statSync} = require('fs');
const {join} = require('path');
function getJSFiles({path = __dirname, type = 'js'} = {}) {
if (!existsSync(path)) return [];
let result = [], re = new RegExp(`\.${type}$`, 'gi');
const dirs = readdirSync(path);
dirs.forEach(dir => {
const curPath = join(path, dir);
if (statSync(curPath).isDirectory()) {
result = result.concat(getJSFiles({path: curPath, type}));
} else {
if (re.exec(curPath)) result.push(curPath);
}
});
return result;
}在一个函数调用之前调用其它方法
📌
function foo (a, b, c) {
console.log('函数本身执行:', a, b, c)
}
foo.before((p1, p2) => void console.log(p1 + p2), 11, 33)
>
> 现在执行 before 的默认统一操作!
> 现在执行回调函数!
> 44
> 现在调用函数本身!
> 函数本身执行: 11 33 undefined💯
Object.defineProperty(Function.prototype, 'before', {
value: function (...rest) {
console.log('> 现在执行 before 的默认统一操作!');
// …… 这里是一些默认操作
console.log('\n> 现在执行回调函数!');
if (typeof rest[0] === 'function') {
const cb = rest.shift();
cb(...rest);
}
// 调用上下文对象(函数)——原函数本身
console.log('\n> 现在调用函数本身!');
this(...rest);
},
enumerable: false,
configurable: false,
writable: true
});
// 使用 ES6 Proxy 同样可以实现
function proxyFn(fn = () => void 0) {
return new Proxy(fn, {
apply: function (target, object, args) {
console.log('现在执行 before 的默认统一操作!');
target.apply(object, args);
}
});
}实现ng1版本的依赖注入DI
📌
- 推断注入
- 显式注入
- 行内注入
🔥
- 得到模块的依赖项
- 查找依赖项所对应的对象
- 执行时注入
💯
拷贝
extend
对象相等判断
柯里化
------------
我有一个 100 * 100 的位置,需要放一个未知大小的图片,可以做到居中?
react相关
jQuery 和 react 有什么区别,用 jQuery 设计 MVC 架构会怎么设计?
如何设计一个好的组件?
React组件,发送异步请求应该在什么阶段发送?
vue相关
现在在使用vue1还是2?1到2也是有很多变化,变化是什么,怎么踩啃的?
你是如何理解mvvm框架?vue是怎么实现mvvm的?
vue 有没有遇到数据明明更新却没有触发重绘?
Angular相关
node相关
可视化相关
有一张图片,想要做马赛克处理,可以怎么实现?可能会存在什么问题?
canvas 与 svg 的区别?canvas高清屏问题?
如果让你实现一个简单的,愤怒的小鸟小游戏,你们怎么是实现?要点:斜抛模型,物理模型?
如何检测两个多边形是否有碰撞?
如何去展示20000个人,相互间人际关系的数据?怎么去体现六度空间的理论?
如果再给你一组最近30天的数据,数据的内容是淘宝上的有购买买家数,绘制两个曲线图,我希望两个曲线图能够联动,你会怎么去做?
为什么使用svg 画一条曲线会存在虚化的问题?要怎么解决?
心形线是怎么绘制的? 怎么去实现心形线生成动画?
算法
判断两棵二叉树是否结构相同
其他
你觉得挑战最大的项目是什么?你在这个项目中的角色是什么?解决了什么问题?
你有什么想向我了解的?




























