suchenrain / frontend-roadmap Goto Github PK
View Code? Open in Web Editor NEW梳理前端过程中的一些知识点
梳理前端过程中的一些知识点
在解释跨域的概念之前,先让我们来了解下浏览器的同源策略,这也是为什么会有跨域的由来。
同源策略是一项约定,是浏览器的行为,限制了从同一个源下的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
所谓同源是指 协议
+域名
+端口
三者都相同,不满足这个条件即为非同源,即使两个不同域名指向同一IP地址。 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。 不同域之间相互请求资源,就算作跨域
。
协议 | 子域名 | 主域名 | 端口号 | 资源地址 |
---|---|---|---|---|
http:// | www. | abc.com | :8080 | /scripts/jquery.js |
https:// | cdn. | abc.com | :3000 | /b.js |
同源策略限制的内容:
注意:有三个标签是允许跨域加载资源的:
<img src=XXX>
<link href=XXX>
<script src=XXX>
总结一下就是: 同源策略是浏览器的一种安全行为,是为了阻止一个域下的文档或脚本读取另一个域下的资源污染自身,所以拦截了响应。 这也是为什么表单提交可以跨域(因为没有获取新的内容)。
利用<script>
标签不受跨域限制,将回调函数名作为参数附带在请求中,服务器接受到请求后,进行特殊处理:把接收到的函数名和需要给它的数据拼接成一个字符串返回,客户端会调用相应声明的函数,对返回的数据进行处理。
封装jsonp请求
<script>
function jsonp({
url,
params,
cb
}) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[cb] = function (data) {
resolve(data)
document.body.removeChild(script);
}
params = {
...params,
cb
}
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: {
wd: 'Iloveyou'
},
cb: 'show'
}).then(data => {
console.log(data)
})
</script>
上述代码向http://localhost:3000/say?wd=Iloveyou&cb=show
发起请求,服务器返回show('我不爱你')
,因而前台将会调用show方法。
let express = require('express')
let app = express()
app.get('/say', function (req, res) {
let { wd, cb } = req.query
console.log(wd)
console.log(cb)
res.end(`${cb}('我不爱你')`)
})
var server = app.listen(3000, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
简单兼容性好,解决主流浏览器跨域数据访问。缺点是仅支持GET
方法,且需要服务器做支持才能实现。
CORS(cross-origin resource share)跨域资源共享 只是在 HTTP 协议内部,新增了若干个 header字段 ,来制定 跨域资源共享
的实现规则。
目前所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键在于后端。只要后端实现了 CORS,就实现了跨域。根据浏览器发送的请求可以分为两种情况。
若请求满足所有下述条件,则该请求可视为“简单请求”:
对于简单请求,只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考nginx反向代理中设置proxy_cookie_domain 和 NodeJs中间件代理中cookieDomainRewrite参数的设置。
<script>
let xhr = new XMLHttpRequest();
document.cookie = 'name=chen';
xhr.withCredentials = true
xhr.open('POST', 'http://localhost:4000', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.response)
}
}
}
xhr.send();
</script>
const http = require('http')
const server = http.createServer((request, response) => {
if (request.url === '/') {
if (request.method === 'GET') {
response.writeHead(200, { 'Access-Control-Allow-Origin': 'http://localhost:3000' })
response.end("{name:'chen',password:'test'}")
}
if (request.method === 'POST') {
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:3000',
'Access-Control-Allow-Credentials': true,
// 此处设置的cookie还是http://localhost:4000的而非http://localhost:3000,因为后端也不能跨域写cookie(nginx反向代理可以实现),
// 但只要http://localhost:4000中写入一次cookie认证,后面的跨域接口都能从http://localhost:4000中获取cookie,从而实现所有的接口都能跨域访问
'Set-Cookie': 'l=a123456;Path=/;Domain=http://localhost:3000;HttpOnly' // HttpOnly的作用是让js无法读取cookie
})
response.end('true')
}
}
response.end('false')
})
server.listen(4000, () => {
console.log('the server is running at http://localhost:4000')
})
不符合以上条件的请求就肯定是复杂请求了。
复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为 预检
请求,该请求是 option
方法的,通过该请求来知道服务端是否允许跨域请求。
复杂请求例子:
// index.html
<script>
let xhr = new XMLHttpRequest();
document.cookie = 'name=chen';
xhr.withCredentials = true;
xhr.open('GET', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'chen')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log(xhr.response);
console.log(xhr.getResponseHeader('name'))
}
}
}
xhr.send();
</script>
let express = require('express')
let app = express();
let whiteList = ['http://localhost:3000']
app.use(function (req, res, next) {
let origin = req.headers.origin
if (whiteList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
// res.end();
}
}
next();
})
app.put('/getData', function (req, res) {
console.log(req.headers)
res.setHeader('name', 'jw')
res.end('i don\'t love you')
})
app.get('/getData', function (req, res) {
console.log(req.headers)
res.end('i love u')
})
app.use(express.static(__dirname))
app.listen(4000, () => {
console.log('serve at 4000')
})
上述代码由http://localhost:3000/index.html向http://localhost:4000/跨域请求,正如我们上面所说的,后端是实现 CORS 通信的关键,需要对引起跨域的因素在OPTION中进行相应的处理。
同源策略是浏览器的安全策略,不是HTTP协议的一部分。而服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,因此也就不存在跨越问题。
通过nginx
配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
nginx相关配置:
server {
listen 80;
server_name www.domain1.com;
location / {
root [前端代码路径];
index index.html;
}
}
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里的domain
add_header Access-Control-Allow-Origin http://www.domain1.com;
add_header Access-Control-Allow-Credentials true;
}
}
前端代码:
// index.html
<script>
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
</script>
Nodejs后台:
// server.js
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写cookie
res.writeHead(200, {
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
原理和上面的nginx大致相同,都是利用服务器之间无需遵守同源策略,通过一个代理服务器,实现请求的转发以及设置CORS。
node + express + http-proxy-middleware 搭建proxy服务器
前端代码:
<script>
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:4000/?user=admin', true);
xhr.send();
</script>
中间件:
var express = require('express')
var proxy = require('http-proxy-middleware')
var app = express();
app.use('/', proxy({
target: 'http://www.domain2.com:8080',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyReq: function (proxyRes, req, res) {
res.setHeader('Access-Control-Allow-Origin', 'http://www.domain1.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');
},
// 修改响应中的cookie域
cookieDomainRewrite: 'www.domain1.com' // false 为不修改
}))
app.listen(81, () => {
console.log('Proxy server is running at port 81...')
})
后台接口:
var express = require('express')
var app = express();
var qs = require('querystring')
app.use(function (req, res, next) {
var params = qs.parse(req.url.substring(2));
res.setHeader('Set-Cookie', 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly')
res.write(JSON.stringify(params));
res.end();
})
app.listen(8080, () => {
console.log('Server is running at port 8080...')
})
前端三大件:HTML
+CSS
+JS
今天我们就来说一说HTML
,可能很多人觉得这个太简单了,就是平常写网页的一堆元素。然而越是基础的东西人们越容易忽略,所以特意梳理了下相关知识,希望加深对它的理解。
如果你觉得本文对你有所帮助,欢迎猛戳 ⭐ Github(梳理前端知识体系全集)
HTML
(HyperText Markup Language)超文本标记语言。顾名思义,它是一门语言,用来标记文档结构的语言。就像你写 word 一样,有各种格式和大纲,HTML
描述了网页文档的结构,标记各种区块。
如果你很早以前就接触过 html,那你肯定知道下面的写法:
<!-- HTML4.01 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
上面分别是 HTML4
和 XHTML
的声明部分,其中的DTD
规定了可用的元素和属性,这样浏览器才能正确地渲染网页。HTML4/4.01(SGML)
时代,语法较为随意,比如标签可以不闭合/大写/属性可为空等,所以各大浏览器会有语法容错机制,自动补全标签闭合等。到了后来,大家觉得这并不是浏览器该做的事情,所以有更为严格的XHTML(xml)
:必须小写/闭合/属性带引号等等。但是XHTML
愈加严格繁琐的语法让人不耐烦了,所以 HTML4 的下一个版本也即HTML5
横空出世,轻松的语法,更多语义化的元素以及表单增强等等。
<!-- HTML 5 声明 -->
<!DOCTYPE html>
注:HTML5
是主流和未来,所以下文内容均是以 HTML5
为参考。
HTML
文档由各种元素和属性组成,描述了网页的结构。
HTML
文档元素从上至下包括:
doctype
:文档声明head
部分:包含页面设定,标题,引用资源等
meta
title
style
link
script
base
body
部分:网页呈现出来的部分
div
/section
/article
/aside
/header
/main
/footer
p
span
/em
/strong
/i
table
/thead
/tbody
/th
/tr
/td
a
form
/input
/select
/button
/textarea
block
: 独占一行或多行,可以设置宽高及对齐等属性inline
:不占独立区域,靠自身内容支撑结构,和相邻元素和睦相处,宽高无效,但水平方向
可以设置padding
和margin
inline-block
:和其它inline
元素同行显示,同时也可以设置宽高/margin/padding(水平
和垂直
)block | inline | inline-block |
---|---|---|
独占一行,自上而下的排列 | 自左向右排序,宽度不够的时候换行 | 和其他inline元素同行显示 |
可设置宽度,默认是auto(margin+border+padding+content=父级元素的宽度) | 设定具体的宽度是不起作用的,由文字内容决定 | 可以设置宽度,未指定时靠内容撑开 |
可设置高度,默认是0,靠内容撑开 | 不生效 | 可以设置高度,未指定时靠内容撑开 |
padding/margin两个方向均可改变元素位置 | 水平方向padding/margin可改变元素位置 | padding/margin两个方向均可改变元素位置 |
HTML5中的每个元素都可以归为零个或多个类别,这些类别将具有相似特征的元素分组在一起。w3c中使用的主要类别如下:
你可能听说过以下常见的元素的规则:
<!-- 块级元素可以包含内联元素 -->
<div><span></span></div>
<!-- 块级元素可以包含某些块级元素 -->
<section><div></div></section> <!--正确-->
<p><div></div></p> <!--错误-->
<!-- form/a 不能再嵌套自身 -->
<a><a></a></a>
<!-- 内联元素一般不能嵌套块级元素 -->
<body><a><div></div></a><body> <!--HTML4中不合法(但浏览器支持)/但HTML5中是合法的-->
其中关于HTML4的嵌套规则可以参考这里, 而HTML5中对元素重新做了分类,嵌套关系根据元素的content model
进行合法确定。比如上面的a>div
在HTML5中就是合法的。参考HTML5中的a
定义,它的内容模型定义为transparent
(透明),透明的意思就是在计算合法性的时候,会忽略a
本身,嵌套关系需要根据a
的父标签来决定。请看下面嵌套:
<!--第一种嵌套-->
<div>
<a>
<div></div>
</a>
</div>
<!--第二种嵌套-->
<p>
<a>
<div></div>
</a>
</p>
第一种是合法嵌套,因为相当于div
嵌套div
,而第二种则不合法,因为相当于p
嵌套div
,浏览器会进行猜测解析,不妨在浏览器测试一下哦。
先来看两段html代码:
<!--使用万能div-->
<div class="header"></div>
<div class="left"></div>
<div class="container">
<div class="content"></div>
</div>
<div class="footer"></div>
<!--不使用div-->
<header></header>
<nav></nav>
<main>
<article></article>
<aside></aside>
</main>
<footer></footer>
对于上面两种写法,第二种就是HTML语义化。可能有人觉得这两种写法没什么太大区别呀,结构都很清晰,但是如果脱了css这层外衣,你觉得哪种写法更容易理解呢?所谓HTML语义化,就是用最恰当的元素标签标记内容结构。
为什么需要语义化呢?
那怎么写语义化的HTML呢?
div
和span
;div
或者p
时,尽量用p
, 因为p
在默认情况下有上下间距,对兼容特殊终端有利;b
、font
、u
等,改用css
设置。strong
或者em
标签中(浏览器预设样式,能用CSS指定就不用他们),strong
默认样式是加粗(不要用b),em
是斜体(不用i);caption
,表头用thead
,主体部分用tbody
包围,尾部用tfoot
包围。表头和一般单元格要区分开,表头用th
,单元格用td
;fieldset
标签包起来,并用legend
标签说明表单的用途;input
标签对应的说明文本都需要使用label
标签,并且通过为input
设置id
属性,在lable
标签中设置for=someld
来让说明文本和相对应的input
关联起来。现代浏览器对于HTML元素都提供了了默认的样式,比如body默认有一定的padding,下拉框/按钮等都有默认的外观。然而,这些默认的样式某些情况下会带来问题,比如我们想要定制下拉框的外观,那就需要花费精力去处理默认样式,提高了定制成本。
解决的方向大概有两个:
css reset
的话,可以在网络上找到一些简单的代码或者简单的通过以下来重置样式:
html *{
margin:0;
padding:0
...
}
又或者通过统一的样式来处理,比如normalize.css
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.