- 👋 Hi, I’m @justcodebryan
- 👀 I’m interested in Front-end
- 🌱 I’m currently learning React & Typescript
Blog Link: justcodebryan blog
justcodebryan's blog github pages source repo
Blog Link: justcodebryan blog
JavaScript
中的变量提升问题是一个很常见的面试题目,在阅读《你不知道的JavaScript》这本书又有了新的感悟。
首先来看书中的两个例子:
// example 1
a = 2;
var a;
console.log(a); // ans:2
// example 2
console.log(a);
var a = 2; // ans:undefined
这两个例子非常简单,解释如下
var a
变量的声明被提升到了最上面,所以在下面进行赋值的时候,成功地将2赋给了a
这个变量。可以看作是下面这样的代码: var a;
a = 2;
console.log(a);
var a;
console.log(a); // 定义了变量a但是没有给它赋值,所以它的值现在是undefined
a = 2;
js
中的函数同样会进行变量提升这一操作,但是函数有两种声明形式
function func() { };
var func = function() { };
需要注意的是函数声明才会将其整个函数提升到整个脚本的最顶上去声明,但是函数表达式会像前面的变量提升一样,先声明变量然后再给它赋值,只不过是赋给变量一个函数(完全可以将其与变量提升一样处理)。在函数表达式的变量提升中,如果出现了先调用然后再声明的情况,会抛出的错误是TypeError
(类型错误), 而不是ReferenceError
(引用错误),这里可以理解为先调用的变量的值应该为undefined
, 而undefined
并不是一个函数,不能调用,所以会抛出TypeError
。
即使是将一个具名的函数赋值给一个变量,也是不能在声明之前调用的:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
...
}
经过变量提升以后,代码应该变成以下这种情况了:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
}
之前在学习变量提升这一块内容的时候,时常会有疑问,如果多个函数变量提升到底会变成什么样?
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
在这段代码中,尽管var foo
是在function foo() ...
声明之前的,但是他是重复的声明,所以被忽略了,因此函数声明会被提升到普通变量之前。但是尽管var
声明会被忽略掉,但是出现在后面的函数声明还是可以覆盖掉前面的
foo(); // 3
function foo() {
console.log(1);
}
var foo = function() {
console.log(2);
}
function foo() {
console.log(3);
}
Javascript
编译器会将var a = 2
看作var a
和a = 2
两个过程CSS 居中方案 📏
将行内元素包裹在一个属性display
为block
的父元素中, 父元素中添加
.box {
text-align: center;
}
块状元素解决方案
需要为父元素设定宽高
.box {
width: 200px;
margin: 0 auto;
}
🌰position
元素已知宽度, 绝对定位+margin
反向偏移(transform)
.wrap {
position: relative;
background-color: orange;
width: 300px;
height: 300px;
}
.example2 {
background-color: red;
width: 100px;
height: 100px;
position: absolute;
left: 50%;
top: 50%;
margin: -50px 0 0 -50px;
/* 第二种: 将margin换成transform, 如下 */
/* transform: translate(-50%, -50%); */
}
🌰flex布局
.wrap {
background-color: #ff8c00;
width: 200px;
height: 200px;
display: flex;
justify-content: center; /* 使子项目水平居中 */
align-items: center; /* 使子项目垂直居中 */
}
.example3 {
background-color: #f00;
width: 100px;
height: 100px;
}
还可以使用table-cell布局, 但是因为该布局对于资源的消耗过大, 现在基本没有使用, 不必了解
🌰 绝对布局
<div class="wrap">
<div class="example3">
居中显示
</div>
</div>
.wrap {
position: relative;
background-color: orange;
width: 200px;
height: 200px;
}
.example3 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: red;
width: 100px;
height: 100px;
margin: auto;
}
🌰 给子元素相对定位, 在通过translateY()
得到垂直居中
.wrap {
position: relative;
background-color: orange;
width: 200px;
height: 200px;
}
.example3 {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: red;
width: 100px;
height: 100px;
margin: 0 auto;
}
🌰 利用inline-block
的vertical-align: middle
去对齐after
伪元素
居中块的尺寸可以做包裹性、自适应内容,兼容性也相当好。缺点是水平居中需要考虑inline-block间隔中的留白(代码换行符遗留问题)
.wrap {
text-align: center;
overflow: auto;
width: 200px;
height: 200px;
background-color: orange;
}
.example3 {
display: inline-block;
background-color: red;
vertical-align: middle;
width: 100px;
height: 100px;
}
.wrap:after {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
margin-left: -0.25em;
/* To offset spacing. May vary by font */
}
🌰display: flex-box
能解决各种排列布局问题
.wrap {
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
-moz-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-moz-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
width: 200px;
height: 200px;
background-color: orange;
}
.example3 {
width: 100px;
height: 100px;
background-color: red;
}
按照软件工程自底而上的概念, 前端测试分为以下四类:
单元测试(Unit Testing)
指的是以原件的单元为单位,对软件进行测试。单元可以是一个函数,也可以是一个模块或一个组件,基本特征就是只要输入不变,必定返回同样的输出。一个软件越容易些单元测试,就表明它的模块化结构越好,给模块之间的耦合越弱。React的组件化和函数式编程,天生适合进行单元测试
集成测试(Integration Testing)
在单元测试的基础上,将所有模块按照设计要求组装成子系统或者系统,进行测试
端对端测试(E2E Testing)
在正式全面的测试之前,对主要功能进行的与测试,确认主要功能是否满足需要,软件是否能正常运行
功能测试
相当于是黑盒测试,测试者不了解程序的内部情况,不需要具备编程语言的专门知识,只知道程序的输入、输出和功能,从用户的角度针对软件界面、功能和外部结构进行测试,不考虑内部的逻辑
建议的测试数量:
单元测试 > E2E测试 > 快照测试
选型: Jest
+ Enzyme
+ enzyme-adapter-react-16
安装依赖, 因为是React16
以上的版本, 所以需要安装插件
yarn add jest enzyme enzyme-adapter-react-16 @types/jest @types/enzyme @types/enzyme-adapter-react-16 ts-jest -D
因为需要使用sass
, 所以需要添加identity-obj-proxy
才能使得测试正常运行
yarn add identity-obj-proxy -D
快照序列化需要enzyme-to-json
yarn add enzyme-to-json -D
在package.json
中添加
{
"scripts":{
"test": "jest --config jest.config.js --no-cache"
}
}
在根目录下添加__mocks__
文件夹, 并且创建js
文件命名为fileTransformer.js
用于mock其他文件, 图片之类的文件
// fileTransformer.js
const path = require('path');
module.exports = {
process(src, filename, config, options) {
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
},
};
在根目录添加一个文件jest.config.js
// jest.config.js文件
module.exports = {
verbose: true,
setupFiles: ['./tests/setup.js'],
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'md'], // 文件扩展名
moduleNameMapper: {
"\\.(css|scss)$": "identity-obj-proxy" // scss文件处理的依赖
},
transform: {
"^.+\\.tsx?$": "ts-jest",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileTransformer.js"
},
testRegex: "(/__test__/.*|(\\.|/)(test|spec))\\.tsx?$", // 正则表达式找到需要测试的文件
globals: {
"ts-jest": {
tsConfig: 'tsconfig.json',
},
},
testPathIgnorePatterns: ['/node_modules/', 'dist'], // 忽略测试的文件夹
collectCoverage: true, // 收集覆盖率
coverageDirectory: './coverage', // 覆盖率报告
snapshotSerializers: ["enzyme-to-json/serializer"] // 快照序列化
};
在根目录下添加tests
文件夹
创建setup.js
// setup.js
const React = require('react');
// eslint-disable-next-line no-console
console.log('Current React Version:', React.version);
/* eslint-disable global-require */
if (typeof window !== 'undefined') {
global.window.resizeTo = (width, height) => {
global.window.innerWidth = width || global.window.innerWidth;
global.window.innerHeight = height || global.window.innerHeight;
global.window.dispatchEvent(new Event('resize'));
};
global.window.scrollTo = () => {};
if (!window.matchMedia) {
Object.defineProperty(global.window, 'matchMedia', {
value: jest.fn(query => ({
matches: query.includes('max-width'),
addListener: jest.fn(),
removeListener: jest.fn(),
})),
});
}
}
// 因为是React16版本的缘故, 需要配置adapter
// 配置全局adapter
const Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
Enzyme.configure({ adapter: new Adapter() });
Object.assign(Enzyme.ReactWrapper.prototype, {
findObserver() {
return this.find('ResizeObserver');
},
triggerResize() {
const ob = this.findObserver();
ob.instance().onResize([{ target: ob.getDOMNode() }]);
},
});
创建setupAfterEnv.ts
:
import toMatchRenderedSnapshot from './matchers/rendered-snapshot';
expect.extend({
toMatchRenderedSnapshot,
});
快照测试生成一个UI
结构, 并用字符串的形式放在__snapshots__
文件里, 通过, 比较两个字符串来判断UI
是否发生改变
创建matchers/rendered-snapshot.ts
import { render } from 'enzyme';
import { ReactElement } from 'react';
// 快照测试
export default function toMatchRenderedSnapshot(
this: jest.MatcherUtils,
jsx: ReactElement<unknown>,
): { message(): string; pass: boolean } {
try {
expect(render(jsx)).toMatchSnapshot();
return {
message: () => 'expected JSX not to match snapshot',
pass: true,
};
} catch (e) {
return {
message: () => `expected JSX to match snapshot: ${e.message}`,
pass: false,
};
}
}
命名规范: ***.test.tsx
/ index.spec.tsx
describe('测试套件标题', () => {
it('测试标题', () => {
})
})
API
此处仅仅列出比较常用的API
afterAll(fn, timeout)
:所有测试用例跑完以后执行的方法
beforeAll(fn, timeout)
:所有测试用例执行之前执行的方法
afterEach(fn)
:在每个测试用例执行完后执行的方法
beforeEach(fn)
:在每个测试用例执行之前需要执行的方法
toHaveBeenCalled()
:用来判断mock function是否被调用过
toHaveBeenCalledTimes(number)
:用来判断mock function被调用的次数
resolves
:用来取出promise为fulfilled时包裹的值,支持链式调用
rejects
:用来取出promise为rejected时包裹的值,支持链式调用
shallow
: 浅渲染, 子组件不会渲染, 效率高, 可以用jQuery
的方式访问组件信息mount
: 完全渲染, 将组件渲染加载成一个真实的DOM
节点render
: 将React
组件渲染成静态的HTML
字符串, 可以用来分析html
结构因为shallow
和mount
返回的是DOM
对象, 可以用simulate
进行交互模拟, render
不可以
shallow
- 一般情况下都能满足
render
- 需要对子组件进行判断
mount
- 需要测试组件的生命周期
类的原型方法可以通过shallow
和mount
方法渲染组件以后, 在通过 spyOn(instance: Prototype, MethodName: string)
来监控并且测试类的原型的方法
注意: 需要在spy
完某一个函数之后对其进行mockRestore
simulate(event, mock)
: 模拟事件, 用来触发事件, event
为事件名称, mock
为一个event object
find(selector)
: 根据选择器查找节点, selector
可以是CSS
中的选择器, 或者是组件的构造函数, 组件的display name
等state()
: 返回根组件的状态, 但是如果使用React Hooks
中的useState
是无法获取到该状态text()
: 返回当前组件的文本内容html()
: 返回当前组件的HTML
代码%stmts
是语句覆盖率(statement coverage)表示是不是每个语句都执行了?
Branch
代表分支, 比如if-else
分支中没有测试到的
Funcs
代表函数, 没有测试到的函数也会在显示百分比
Lines
代表覆盖的行数, 显示已经覆盖的行数的百分比
Uncovered Line
: 表示测试没有覆盖到行数, 具体是哪几行没有被覆盖到
下面会显示通过的测试套件, 测试数量以及快照数量
为什么JS
的解析会阻塞DOM Tree
的解析
因为JS
的解析运行被默认为有可能使用document.write
方法, document.write
方法会向文档流里面写入字节, 这样的话, 会强制刷新剩下的文档
所以为了避免重复操作, 就将剩下的dom
解析给阻塞了
而且在这时的js
脚本能够操作前面已经解析完成的dom
元素, 而不能操作后面还未解析的dom
元素, 因为还未解析出来, dom
元素还不存在
document.write
使用流程:
document.open
document.write
document.close
从另外一个角度理解, 当浏览器在解析DOM树的时候, 已经调用了document.open
了, 那么在这种情况下, 使用document.write
可以直接在文档中间的位置写入内容, 当文档解析完了触发documentloaded
的时候, 浏览器会调用document.close
。这个时候相当于写入文件完成并且管道关闭了,如果还要再进行document.write
这个操作的时候, 需要重新调用document.open
。因为是系统自动调用的, 系统不知道哪个document
,所以就会重新生成一个空白的document
, 并且把document.write
里面要写入的内容写进去, 并且覆盖掉原来的document
, 这样就会造成documentloaded
之后调用document.write
会将整个页面都覆盖掉。
JS script不同标签的行为(async, defer)
async - 异步加载, 同步执行
defer - 异步加载, onload之后执行
CSSOM解析会阻塞DOM Tree的解析或者JS的运行吗
这里共有两种情况:
script
标签, 当整个文件解析到了script标签的时候, 假定CSS标签还未下载完成, 如果script标签里面有get ComptuedStyle的操作, CSS的解析就会阻塞JS的执行, 直到CSS解析完成了以后, 获取了正确的样式, 这样计算出来的结果才是正确的。<html>
<head>
<link src="./index.css" />
<title>test app</title>
</head>
<body>
<script src="./test.js"></script>
</body>
</html>
前面的情况同上, 唯一不同的点就是, script标签里面没有做get computed style的操作。这时,就和一般的操作是一样的。
浏览器并行下载的最大数量
各个浏览器的并行下载数量不同, 之前的最大是6个
Firefox 2: 2
Firefox 3+: 6
Opera 9.26: 4
Opera 12: 6
Safari 3: 4
Safari 5: 6
IE 7: 2
IE 8: 6
IE 10: 8
Edge: 6
Chrome: 6
为什么需要document.write方法
以下内容摘自Stack Overflow
When document.write() is executed after page load, it replaces the entire header and body tag with the given parameter value in string.
The only seem appropriate usage for document.write() is when working third parties like Google Analytics and such for including their scripts. This is because document.write() is mostly available in any browser. Since third party companies have no control over the user’s browser dependencies (ex. jQuery), document.write() can be used as a fallback or a default method.(兼容性问题, 当需要加入google analytics等的脚本, 这是最容易加入依赖的方法)
render tree的行为, 如果CSS发生了改变以后
repaint(重绘)和reflow(重排)
阻塞渲染和阻塞解析
阻塞渲染: CSS的解析会阻塞渲染 -> 基于不要让用户看到没有CSS, 即没有样式的页面, 但是也会有例外的情况, 有可能很久没有返回CSS, 这时, CSS一直无法解析, 那就有可能出现没有样式的页面, 但是最后接收到了页面CSS就会让重新刷新
阻塞解析: JS的运行会阻塞DOM树解析
并行下载: 最大数量下载量 -> 下载的时间和时机看代码的位置和配置
定义: Web
使用一种名为HTTP
(HyperText Transfer Protocol
,超文本传输协议)的协议作为规范,完成从客户端到服务器等一系列运作流程。而协议是指规则的约定。可以说,Web
是建立在HTTP
协议上通信的。
采用请求/响应模型
请求报文
URL
状态行(响应)
特点: 不保存状态的协议 -> 无状态协议
FTP
(File Transfer Protocol
, 文件传输协议)DNS
(Domain Name System
, 域名系统)HTTP
TCP
(Transimission Control Protocol
) - 传输控制协议
UDP
那么高效UDP
(User Data Protocol
) - 用户数据报协议
IP
协议)NIC
(Network Interface Card
, 网络适配器, 网卡), 及光纤等物理可见部分HTTP
有无连接的特性,即每次连接只能处理一个请求,收到响应后立即断开连接。HTTP/1.0
版本(称为串行连接或短连接、短轮询)中每次HTTP
通信后都要断开TCP
连接,所以每个新的HTTP
请求都需要建立一个新的连接。HTTP
请求,只要两端都没有提出断开连接,则持久保持TCP
连接状态,其他请求可以复用这个连接通道。HTTP/1
实现并默认了所有连接都是持久连接,这样客户端发起多个HTTP
请求时就减少了TCP
握手造成的网络资源和通信时间的浪费。TCP
连接HTTP/2.0
多路复用: 每个HTTP
请求都有一个序列标识符, 这样浏览器就可以并发多个请求, 服务器收到数据根据标识符重新排列成不同的请求报文, 而不会导致数据错乱。同样服务器可以返回多个响应给浏览器WebSocket
: HTML5
提出的一种客户端和服务端通讯的全双工协议HTTP
协议使用URI
定位互联网上的资源。概念:
URI
(Universal Resource Identifier
:统一资源标识符)URL
(Universal Resource Locator
:统一资源定位符)URN
(Universal Resource Name
:统一资源名称)。HTTP
版本HTTP/1.0
简单的网页和网络请求, 比较简单
特点: 每次请求都打开一个新的TCP
连接, 收到响应之后立即断开连接
HTTP/1.1
Entity tag
, If-Unmodified-Since
, If-Match
, If-None-Match
等Range
头部Host
头部, 以区分同一个物理主机中的不同虚拟主机的域名HTTP/2.0
两个重要概念:
frame
): 数据传输的最小单位, 每个帧都有序列标识表明该帧属于哪个流stream
): 多个帧组成的数据流, 每个流表示一个请求新特性:
HTTP/1.x
基于文本, 解析存在天然缺陷。TCP
连接可以存在多个流, 服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。encoder
减少需要传输的头部大小, 通讯双方各自cache
一份头部fields
表, 避免重复头部的传输, 减小了需要传输的大小css/js/img
资源伴随着index.html
一起发送到客户端,省去了客户端重复请求的步骤(从缓存中取)## HTTP/3.0
HTTP/2.0
的缺点: 因为多路复用, 同一域名下只需要使用一个TCP
连接, 连接出现了丢包, 整个TCP
都要等待重连, 导致后面的数据都被阻塞了。
HTTP/1.x
因为使用多个TCP
连接, 单个TCP
出现丢包, 只会影响一个连接
HTTP
报文用于HTTP
协议交互的信息被称为HTTP
报文:
HTTP
报文叫请求报文
HTTP
报文叫响应报文
GET
:一般用于获取服务器资源POST
:一般用于传输实体主体PUT
:一般用于传输文件DELETE
:用于删除文件HEAD
:用于获取报文首部,不返回报文主体OPTIONS
:用于询问请求URI
资源支持的方法GET
方法和POST
方法的区别:
2XX
: 成功
3XX
: 重定向(表明浏览器要执行特殊处理)
4XX
: 客户端错误
5XX
: 服务器错误
略
浏览器发送CORS
请求(跨域请求)时, 会将请求分为简单请求与复杂请求
简单请求:
HEAD
、GET
、POST
Content-Type
只能是这几种:
text/plain
multipart/form-data
application/x-www-form-urlencoded
简单请求 - 先执行后判断
@startuml
participant 浏览器 as Browser
participant 服务器 as Server
note left Browser: 检测到请求是CORS请求, 添加origin字段(其中包含页面源信息: 协议、域名、端口)
Browser -> Server: 发送CORS请求
note right Server: 对比origin, 判断是否接受该源
Server -> Browser: 响应请求
note left Browser: 检查响应头是否允许跨域信息, 如果不允许, 浏览器抛出相应的错误信息
@enduml
复杂请求:
PUT
, Delete
方法的ajax
请求JSON
格式的ajax
请求(比如post
数据)ajax
请求复杂请求在发生请求时, 如果是 CORS 请求,浏览器预先发送一个 option 请求。浏览器这种行为被称之为预检请求(注意如果不是跨域请求就不会发生预检请求,比如反向代理)。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.