Giter VIP home page Giter VIP logo

frontend-roadmap's People

Contributors

suchenrain avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

frontend-roadmap's Issues

深入理解跨域及常见解决方法

什么是跨域?

浏览器的同源策略

在解释跨域的概念之前,先让我们来了解下浏览器的同源策略,这也是为什么会有跨域的由来。

同源策略是一项约定,是浏览器的行为,限制了从同一个源下的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

所谓同源是指 协议+域名+端口 三者都相同,不满足这个条件即为非同源,即使两个不同域名指向同一IP地址。 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。 不同域之间相互请求资源,就算作跨域

协议 子域名 主域名 端口号 资源地址
http:// www. abc.com :8080 /scripts/jquery.js
https:// cdn. abc.com :3000 /b.js

同源策略限制的内容

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,响应结果被浏览器拦截(即请求发送了,服务器响应了)

注意:有三个标签是允许跨域加载资源的:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

总结一下就是: 同源策略是浏览器的一种安全行为,是为了阻止一个域下的文档或脚本读取另一个域下的资源污染自身,所以拦截了响应。 这也是为什么表单提交可以跨域(因为没有获取新的内容)。

常用的解决方案

1.JSONP (json with padding)

1) 原理

利用<script>标签不受跨域限制,将回调函数名作为参数附带在请求中,服务器接受到请求后,进行特殊处理:把接收到的函数名和需要给它的数据拼接成一个字符串返回,客户端会调用相应声明的函数,对返回的数据进行处理。

image

2) 示例

封装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)

})

3) 优缺点

简单兼容性好,解决主流浏览器跨域数据访问。缺点是仅支持GET方法,且需要服务器做支持才能实现。

2.CORS

CORS(cross-origin resource share)跨域资源共享 只是在 HTTP 协议内部,新增了若干个 header字段 ,来制定 跨域资源共享 的实现规则。

目前所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键在于后端。只要后端实现了 CORS,就实现了跨域。根据浏览器发送的请求可以分为两种情况。

1) 简单请求

若请求满足所有下述条件,则该请求可视为“简单请求”:

  • 使用下列方法之一:
    • GET
    • HEAD
    • POST
  • Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限于下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

对于简单请求,只服务端设置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')
})

image

2) 复杂请求

不符合以上条件的请求就肯定是复杂请求了。
复杂请求的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中进行相应的处理。

3.ngnix反向代理

1) 跨域原理

同源策略是浏览器的安全策略,不是HTTP协议的一部分。而服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,因此也就不存在跨越问题。

2) 实现思路

通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

image

3) 示例代码

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...');

4.Nodejs中间件代理

1)原理

原理和上面的nginx大致相同,都是利用服务器之间无需遵守同源策略,通过一个代理服务器,实现请求的转发以及设置CORS。

image

2) 实现思路

node + express + http-proxy-middleware 搭建proxy服务器

3) 示例代码

前端代码:

<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知识

前端三大件:HTML+CSS+JS

今天我们就来说一说HTML,可能很多人觉得这个太简单了,就是平常写网页的一堆元素。然而越是基础的东西人们越容易忽略,所以特意梳理了下相关知识,希望加深对它的理解。

如果你觉得本文对你有所帮助,欢迎猛戳Github(梳理前端知识体系全集)

HTML思维导图

是什么

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">

上面分别是 HTML4XHTML 的声明部分,其中的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:不占独立区域,靠自身内容支撑结构,和相邻元素和睦相处,宽高无效,但水平方向可以设置paddingmargin
  • 内联块级 inline-block:和其它inline元素同行显示,同时也可以设置宽高/margin/padding(水平垂直
block inline inline-block
独占一行,自上而下的排列 自左向右排序,宽度不够的时候换行 和其他inline元素同行显示
可设置宽度,默认是auto(margin+border+padding+content=父级元素的宽度) 设定具体的宽度是不起作用的,由文字内容决定 可以设置宽度,未指定时靠内容撑开
可设置高度,默认是0,靠内容撑开 不生效 可以设置高度,未指定时靠内容撑开
padding/margin两个方向均可改变元素位置 水平方向padding/margin可改变元素位置 padding/margin两个方向均可改变元素位置

按照元素类别

HTML5中的每个元素都可以归为零个或多个类别,这些类别将具有相似特征的元素分组在一起。w3c中使用的主要类别如下:

  • Metadata content(元数据元素)是可以被用于说明其他内容的表现或行为,或在当前文档和其他文档之间建立联系的元素。
  • Flow content(流元素)是在应用程序和文档的主体部分中使用的大部分元素。
  • Sectioning content(区块型元素)是用于定义标题及页脚范围的元素。
  • Heading content(标题型元素)定义一个区块/章节的标题。
  • Phrasing content(语句型元素)用于标记段落级文本的元素。
  • Embedded content(嵌入型元素)引用或插入到文档中其他资源的元素。
  • Interactive content(交互型元素)专门用于与用户交互的元素。

元素类别

元素的嵌套

你可能听说过以下常见的元素的规则:

<!-- 块级元素可以包含内联元素 -->
<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语义化,就是用最恰当的元素标签标记内容结构。

为什么需要语义化呢?

  • 在没有CSS样式的条件下,也能很好地呈现出内容结构、代码结构;
  • 语义化HTML会使HTML结构变的清晰,有利于维护代码和添加样式;
  • 方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以意义的方式来渲染网页;
  • 提升搜索引擎优化(SEO)的效果。和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重;
  • 便于团队开发和维护,语义化更具可读性,是下一步吧网页的重要动向,遵循W3C标准的团队都遵循这个标准,可以减少差异化。
  • 通常语义化HTML会使代码变的更少,使页面加载更快。

那怎么写语义化的HTML呢?

  • 尽可能少的使用无语义的万能标签divspan
  • 在语义不明显时,既可以使用div或者p时,尽量用p, 因为p在默认情况下有上下间距,对兼容特殊终端有利;
  • 不要使用纯样式标签,如:bfontu等,改用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)
  • 统一默认样式:修改统一

css reset的话,可以在网络上找到一些简单的代码或者简单的通过以下来重置样式:

html *{
    margin:0;
    padding:0
    ...
}

又或者通过统一的样式来处理,比如normalize.css

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.