Parkeeers

Parkeeers 查看完整档案

北京编辑天津理工大学  |  cs 编辑某不知名小公司  |  前端开发工程师 编辑 github.com/parkeeers 编辑
编辑

求知若渴,保持谦逊

个人动态

Parkeeers 赞了文章 · 6月9日

前/后端生成excel方法

一、通过后端处理导出

1.) 后端响应头设置

前端若想下载一个根据查询参数生成的excel报表文件,需要后端设置请求内容的类型,具体设置如下:

    response.reset();
    response.setContentType("application/vnd.ms-excel;charset=utf-8"); // 表明是excel类型
    //response.setContentType("application/octet-stream");  // 或设成通用二进制流类型
    response.setHeader("Content-Disposition", "attachment;filename=test.xls"));  // 将请求的内容存为一个名为test的文件

2.) 前端相应的代码

正常情况下,用以下两种方法均可:

window.open('/url/download?param=xxx')
window.location = '/url/download?param=xxx'   // 不加href,是因为有些浏览器有兼容问题

但如果window.open是在一个ajax回调里执行,浏览器会被安全拦截,解决方案:

$.ajax({
    ....
    success: function() {
        var win = window.open()
        win.location = '/url/download?param=xxx'
    }
})

或直接用window.location

$.ajax({
    ....
    success: function() {
        window.location = '/url/download?param=xxx'
    }
})

二、前端直接将json数据处理成excel文件

具体完整代码如下:

<button onclick='tableToExcel()'>json导出xls文件</button>
<script>
  function tableToExcel() {
      var jsonData = [{
          name: '路人甲',
          phone: '123456',
          email: '123@123456.com'
      }, {
          name: '炮灰乙',
          phone: '123456',
          email: '123@123456.com'
      }]

      //列标题
      var table = '<tr><td>姓名</td><td>电话</td><td>邮箱</td></tr>'

      //循环遍历,每行加入tr标签,每个单元格加td标签
      jsonData.forEach(function(item) {
          table += '<tr>';
          for (let key in item) {
              //增加\t为了不让表格显示科学计数法或者其他格式
              table += `<td>${item[key] + '\t'}</td>`
          }
          table += '</tr>';
      })

      //Worksheet名
      var worksheet = 'Sheet1'
      //下载的表格模板数据
      var template = `<html xmlns:o="urn:schemas-microsoft-com:office:office" 
                            xmlns:x="urn:schemas-microsoft-com:office:excel" 
                            xmlns="http://www.w3.org/TR/REC-html40">
                        <head>
                          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
                          <!--[if gte mso 9]>
                          <xml>
                            <x:ExcelWorkbook>
                              <x:ExcelWorksheets>
                                <x:ExcelWorksheet>
                                  <x:Name>${worksheet}</x:Name>
                                  <x:WorksheetOptions>
                                    <x:DisplayGridlines/>
                                  </x:WorksheetOptions>
                                </x:ExcelWorksheet>
                              </x:ExcelWorksheets>
                            </x:ExcelWorkbook>
                          </xml>
                          <![endif]-->
                        </head>
                        <body>
                          <table>${table}</table>
                        </body>
                      </html>`
      // 下载模板
      window.location.href = 'data:application/vnd.ms-excel;base64,' + base64(template)
  }
  //输出base64编码
  function base64(s) { return window.btoa(unescape(encodeURIComponent(s))) }
</script>
查看原文

赞 17 收藏 9 评论 0

Parkeeers 赞了文章 · 6月8日

认识node核心模块--从Buffer、Stream到fs

原文地址在我的博客

node中的Buffer和Stream会给刚接触Node的前端工程师们带来困惑,原因是前端并没有类似概念(or 有我们也没意识到)。然而,在后端,在node中,Buffer和Stream处处体现。Buffer是缓冲区的意思,Stream是流的意思。在计算机中,缓冲区是存储中间变量,方便CPU读取数据的一块存储区域;流是类比水流形容数据的流动。Buffer和Stream一般都是字节级操作。本文将介绍这两个模块的具体细节后再介绍文件模块,以让读者有更清晰的认识。

正文

二进制缓冲区Buffer

在前端,我们只需做字符串级别的操作,很少接触字节、进制等底层操作,一方面这足以满足日常需求,另一方面Javascript这种应用层语言并不是干这个的;然而在后端,处理文件、网络协议、图片、视频等时是非常常见的,尤其像文件、网络流等操作处理的都是二进制数据。为了让javascript能够处理二进制数据,node封装了一个Buffer类,主要用于操作字节,处理二进制数据。

// 创建一个长度为 10、且用 30 填充的 Buffer。
const buf1 = Buffer.alloc(10, 30)
console.log(buf1)// <Buffer 1e 1e 1e 1e 1e 1e 1e 1e 1e 1e>
// 字符串转Buffer
const buf2 = Buffer.from('javascript')
console.log(buf2)// <Buffer 6a 61 76 61 73 63 72 69 70 74>
// 字符串转 buffer
console.log(buf2.toString())// javascript
console.log(buf2.toString('hex')) //6a617661736372697074

一个 Buffer 类似于一个整数数组,可以取下标,有length属性,有剪切复制操作等,很多API也类似数组,但Buffer的大小在被创建时确定,且无法调整。Buffer处理的是字节,两位十六进制,因此在整数范围就是0~255。

可以看到,Buffer可以与string互相转化,还可以设置字符集编码。Buffer用来处理文件I/O、网络I/O传输的二进制数据,string用来呈现。在处理文件I/O、网络I/O传输的二进制数据时,应该尽量以Buffer形式直接传输,速度会得到很好的提升,但操作字符串比操作Buffer还是快很多的。

Buffer内存分配与性能优化

Buffer是一个典型的javascript与C++结合的模块,与性能有关的用C++来实现,javascript 负责衔接和提供接口。Buffer所占的内存不是V8分配的,是独立于V8堆内存之外的内存,通过C++层面实现内存申请、javascript 分配内存。值得一提的是,每当我们使用Buffer.alloc(size)请求一个Buffer内存时,Buffer会以8KB为界限来判断分配的是大对象还是小对象,小对象存入剩余内存池,不够再申请一个8KB的内存池;大对象直接采用C++层面申请的内存。因此,对于一个大尺寸对象,申请一个大内存比申请众多小内存池快很多。

流Stream

前面讲到,流类比水流形容数据的流动,在文件I/O、网络I/O中数据的传输都可以称之为流,流是能统一描述所有常见输入输出类型的模型,是顺序读写字节序列的抽象表示。数据从A端流向B端与从B端流向A端是不一样的,因此,流是有方向的。A端输入数据到B端,对B就是输入流,得到的对象就是可读流;对A就是输出端、得到的对象是可写流。有的流即可以读又可以写,如TCP连接,Socket连接等,称为读写流(Duplex)。还有一种在读写过程中可以修改和变换数据的读写流称为Transform流。

在node中,这些流中的数据就是Buffer对象,可读、可写流会将数据存储到内部的缓存中,等待被消费;DuplexTransform 则是都维护了两个相互独立的缓存用于读和写。 在维持了合理高效的数据流的同时,也使得对于读和写可以独立进行而互不影响。

在node中,这四种流都是EventEmitter的实例,它们都有close、error事件,可读流具有监听数据到来的data事件等,可写流则具有监听数据已传给低层系统的finish事件等,DuplexTransform 都同时实现了 ReadableWritable 的事件和接口 。

值得一提的是writable的drain事件,这个事件表示缓存的数据被排空了。为什么有这个事件呢?起因是调用可写流的write和可读流的read都会有一个缓存区用来缓存写/读的数据,缓存区是有大小的,一旦写的内容超过这个大小,write方法就会返回false,表示写入停止,这时如果继续read完缓存区数据,缓存区被排空,就会触发drain事件,可以这样来防止缓存区爆仓:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on('data', function (chunk) {
    if (ws.write(chunk) === false) {
        rs.pause();
    }
});

rs.on('end', function () {
    ws.end();
});

ws.on('drain', function () {
    rs.resume();
});

一些常见流分类:

  • 可写流:HTTP requests, on the client、HTTP responses, on the server、fs write streams、zlib streams、crypto streams、TCP sockets、child process stdin、process.stdout, process.stderr

  • 可读流:HTTP responses, on the client、HTTP requests, on the server、fs read streams、zlib streams、crypto streams、TCP sockets、child process stdout and stderr、process.stdin

  • 可读可写流:TCP sockets、zlib streams、crypto streams

  • 变换流:zlib streams、crypto streams

另外,提到流就不得不提到管道的概念,这个概念也非常形象:水流从一端到另一端流动需要管道作为通道或媒介。流也是这样,数据在端之间的传送也需要管道,在node中是这样的:

// 将 readable 中的所有数据通过管道传递给名为 file.txt 的文件
const readable = getReadableStreamSomehow();
const writable = getWritableStreamSomehow('file.txt');
// readable 中的所有数据都传给了 'file.txt'
readable.pipe(writable);

// 对流进行链式地管道操作
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);

注意,只有可读流才具有pipe能力,可写流作为目的地。

pipe不仅可以作为通道,还能很好的控制管道里的流,控制读和写的平衡,不让任一方过度操作。另外,pipe可以监听可读流的data、end事件,这样就可以构建快速的响应:

// 一个文件下载的例子,使用回调函数的话需要等到服务器读取完文件才能向浏览器发送数据
var http = require('http') ;
var fs = require('fs') ;
var server = http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        res.end(data);
    }) ;
}) ;
server.listen(8888) ;

// 而采用流的方式,只要建立连接,就会接受到数据,不用等到服务器缓存完data.txt
var http = require('http') 
var fs = require('fs') 
var server = http.createServer(function (req, res) {
    var stream = fs.createReadStream(__dirname + '/data.txt') 
    stream.pipe(res) 
}) 
server.listen(8888) 

因此,使用pipe即可解决上面那个爆仓问题。

fs文件模块

fs文件模块是高阶模块,继承了EventEmitter、stream、path等底层模块,提供了对文件的操作,包括文件的读取、写入、更名、删除、遍历目录、链接POSIX文件系统等操作。与node设计思想和其他模块不同的是,fs模块中的所有操作都提供了异步和同步两个版本。fs模块主要由下面几部分组成:

  • 对底层POSIX文件系统的封装,对应于操作系统的原生文件操作

  • 继承Stream的文件流 fs.createReadStream和fs.createWriteStream

  • 同步文件操作方法,如fs.readFileSync、fs.writeFileSync

  • 异步文件操作方法, fs.readFile和fs.writeFile

模块API架构如下:

fs主要操作

读写操作:

const fs = require('fs'); // 引入fs模块
/* 读文件 */

// 使用流
const read = fs.createReadStream('sam.js',{encoding:'utf8'});
read.on('data',(str)=>{
    console.log(str);
})
// 使用readFile
fs.readFile('test.txt', {}, function(err, data) {
    if (err) {
        throw err;
    }
    console.log(data);
});
// open + read
fs.open('test.txt','r',(err, fd) => {
    fs.fstat(fd,(err,stat)=>{
        var len = stat.size;  //检测文件长度
        var buf = new Buffer(len);
        fs.read(fd,buf,0,len,0,(err,bw,buf)=>{
            console.log(buf.toString('utf8'));
            fs.close(fd);
        })
    });
});

/* 写文件与读取文件API形式类似 */

读/写文件都有三种方式,那么区别是什么呢?

  • createReadStream/createWriteStream创建一个将文件内容读取为流数据的ReadStream对象,这个方法主要目的就是把数据读入到流中,得到是可读流,方便以流进行操作

  • readFile/writeFile:Node.js会将文件内容视为一个整体,为其分配缓存区并且一次性将文件内容读/写取到缓存区中,在这个期间,Node.js将不能执行任何其他处理,所以当读写大文件的时候,有可能造成缓存区“爆仓”

  • read/write读/写文件内容是不断地将文件中的一小块内容读/写入缓存区,最后从该缓存区中读取文件内容

同步API也是如此。其中最常用的是readFile,读取大文件则采取用,read则提供更为细节、底层的操作,而且read要配合open。

获取文件的状态:

fs.stat('eda.txt', (err, stat) => {
  if (err)
    throw err
  console.log(stat)
})
/* 
Stats {
  dev: 16777220,
  mode: 33279,
  nlink: 1,
  uid: 501,
  gid: 20,
  rdev: 0,
  blksize: 4194304,
  ino: 4298136825,
  size: 0,
  blocks: 0,
  atimeMs: 1510317983760.94, - 文件数据最近被访问的时间
  mtimeMs: 1510317983760.94, - 文件数据最近被修改的时间。
  ctimeMs: 1510317983777.8538, - 文件状态最近更改的时间
  birthtimeMs: 1509537398000,
  atime: 2017-11-10T12:46:23.761Z,
  mtime: 2017-11-10T12:46:23.761Z,
  ctime: 2017-11-10T12:46:23.778Z,
  birthtime: 2017-11-01T11:56:38.000Z 
}*/

监听文件:

const FSWatcher = fs.watch('eda.txt', (eventType, filename) => {
    console.log(`${eventType}`)
})
FSWatcher.on('change', (eventType, filename) => {
    console.log(`${filename}`)
})
// watch和返回的FSWatcher实例的回调函数都绑定在了 change 事件上

fs.watchFile('message.text', (curr, prev) => {
  console.log(`the current mtime is: ${curr.mtime}`);
  console.log(`the previous mtime was: ${prev.mtime}`);
})

监听文件仍然有两种方法:

  • watch 调用的是底层的API来监视文件,很快,可靠性也较高

  • watchFile 是通过不断轮询 fs.Stat (文件的统计数据)来获取被监视文件的变化,较慢,可靠性较低,另外回调函数的参数是 fs.Stat 实例

因此尽可能多的使用watch,watchFile 用于需要得到文件更多信息的场景。

其他

创建、删除、复制、移动、重命名、检查文件、修改权限...

总结

由Buffer到Stream,再到fs文件模块,将它们串联起来能对整块知识有更清晰的认识,也对webpack、gulp等前端自动化工具构建工作流的机制和实现有了更深的了解。学习其他知识亦是如此——知道来龙去脉,知道为什么会存在,知道它们之间的联系,就能让碎片化的知识串联起来,能让它们make sense,能够让自己“上的厅堂、下得厨房”。

参考:

nodeJs高阶模块--fs

deep into node

查看原文

赞 27 收藏 41 评论 1

Parkeeers 回答了问题 · 1月6日

解决【已解决】iframe如何能继续加载使用x-frame-options: SAMEORIGIN的网页

统一回复处理方案:纯前端无法解决。解决方案:将不能跨域访问的url的host设置为你网站的域名,这一步是为了即使浏览器对url发起请求,因为host是你服务器的域名,请求也会进入到你的服务器;第二步配置Nginx转发,将该url对应的请求的头部,加上more_clear_headers: x-frame-options,去掉响应头里的x-frame-options限制。这样就类似于模拟了从浏览器空页面直接访问的效果,规避了同源限制。第二步的Nginx方案等都是为了修改请求头,也可以使用Node做中间件,去修改请求头

关注 3 回答 1

Parkeeers 发布了文章 · 2019-11-27

【待完善】谷歌开源内部代码评审规范

【转载】InfoQ
谷歌成立于 1998 年,以搜索起家,到目前为止已经发展了 21 年。在过去的 21 年中,谷歌不断创新,开发了七款产品,拥有超过 10 亿级活跃用户,谷歌的工程师文化一直被认为是优秀且特别的。近日,谷歌开源了其内部一直在使用的代码评审规范,InfoQ 对其进行了翻译和整理,分享给广大开发者,看看谷歌工程师是如何评审代码的。

代码评审标准

代码评审的主要目的是确保代码库的整体质量随时间推移逐步得到提升,所有代码评审工具和过程都是为了实现这一目标而设计的。
为了实现这个目标,必须做出一系列权衡。首先,开发人员的开发任务必须要有所进展。如果他们不提交改进的代码,代码库质量就得不到改善。此外,如果评审人员过于严格,开发人员就没有动力进行持续改进。
评审人员的职责是确保每个 CL(变更列表)的质量,保证代码库整体质量不会随着时间的推移而下降。这是一项艰巨的任务,因为代码库整体质量常常会随着每次提交代码质量的小幅下降而退化,特别是有时候开发团队时间很紧,并认为必须走捷径才能完成交付任务。
评审人员要对他们评审的代码负起责任,确保代码库保持一致性和可维护性。

以下是可在代码评审中使用的准则:
一般来说,如果 CL 达到可以提升系统整体代码质量的程度,就可以让它们通过了,即使它们可能还不完美。
这是所有代码评审准则的最高原则。
当然,也有例外的时候。例如,如果 CL 中包含了系统不需要的功能,那么即使代码写得很好,评审人员也可以拒绝让它们通过。
这个世界上没有“完美”的代码,只有更好的代码。评审人员不应该要求开发人员对 CL 中的每一个微小部分都进行细致入微的打磨,而应该在满足需求和变更重要性之间做出权衡。评审人员不应该追求完美,而应该追求持续改进。如果一个 CL 能够从整体上提高系统的可维护性、可读性和可理解性,那它就不应该仅仅因为它不够“完美”而被延迟几天甚至几周。
评审人员应该提供建议,告诉开发人员哪些方面可以做得更好。但如果这些建议不是很重要,可以在前面加上像“Nit:”这样的前缀,让开发人员知道这只是一个改进建议,他们也可以选择忽略。

指导

代码评审的一个作用是向开发人员传授知识,比如关于一门语言、一个框架或一般软件设计原则的知识。分享知识是提升系统代码质量的一个组成部分。但要注意,如果你的建议纯粹是带有教育性质的,并且对于满足本文所描述的标准来说并不是那么重要,那么请在前面加上“Nit:”,或者以其他方式告诉开发人员,他们并不一定要在 CL 中解决这些问题。

原则

  • 客观的技术和数据比个人意见和偏好更重要。
  • 在代码风格方面,可以参考谷歌风格指南。任何没有在这个风格指南中出现的东西(比如空格等)都属于个人偏好。代码风格应该与原有代码保持一致,如果之前没有规定代码风格,可以使用代码提交者的代码风格。
  • 软件设计从来就不只风格问题,也不只是个人偏好问题。它们建立在一些基本原则之上,所以我们应该基于这些原则做出权衡,而不只是基于个人偏好。有时候,一个问题有多种解决方案,如果开发人员能够证明(通过数据或基于可靠的工程原理)几种解决方案是同样有效的,那么评审人员应该接受开发人员的选择,否则就应该基于软件设计标准原则做出决定。
  • 如果没有其他适用的原则,评审人员可以要求开发人员与当前代码库保持一致,只要不破坏系统的整体代码质量。

解决冲突

在代码评审过程中出现冲突时,开发人员和评审人员首先要尝试根据本文、CL 作者指南和评审人员指南达成一致意见。
如果很难达成一致意见,评审人员和开发人员可以进行面对面会议或者视频会议,而不是只是试图通过代码评审评论板来解决冲突。
如果还不能解决问题,那么就要考虑把问题升级,进行更广泛的团队讨论。让团队负责人参与进来,请求代码维护人员作出决定,或请求工程经理提供帮助。不要因为开发人员和评审人员无法达成一致意见就让 CL 一直挂在那里。

代码评审要注意哪些事情?

设计

代码评审中最重要的部分是 CL 的总体设计。CL 中不同代码段之间的交互是有意义的吗?这个变更应该属于代码库,还是属于某个包?它与系统的其他部分可以良好地集成吗?现在是引入这个变更的好时机吗?

功能

这个 CL 是否达到了开发人员的目的?开发人员的意图对代码用户来说有好处吗?代码“用户”可以是指最终用户(他们受代码变更的影响)和开发人员(将来要“使用”这些代码)。
大多数情况下,我们希望开发人员先测试好 CL,确保它们能够正确运行。但作为评审人员,你仍然要考虑一些边缘情况,比如查找并发问题,尝试像用户一样思考问题,并找出只是通过阅读代码无法看到的错误。
如果愿意,你也可以验证一下 CL。如果一个 CL 会影响用户,比如做出了 UI 变更,那么这是验证 CL 的好时机。如果只是看代码,很难理解一些变更将如何影响用户。对于这样的更改,如果不方便自己运行,可以让开发人员提供功能演示。
另一个重要的考虑点是 CL 中是否存在可能导致死锁或竞态条件的并发问题。只是简单地运行代码很难发现这类问题,通常需要有人(开发人员和评审人员)仔细思考这些问题,确保不会把它们引入到系统中。

复杂性

CL 比实际需要的更复杂吗?从每一层面检查 CL,细到每一行代码,它们是不是太复杂了?函数是否过于复杂?类复杂吗?“太复杂”通常意味着“阅读代码的人难以很快理解它们”,也意味着“开发人员在调用或修改这些代码时可能会引入 bug”。
过度设计是一种特殊的复杂性,开发人员把代码写得比实际需要的更通用,或者增加了系统当前不需要的功能。评审人员要警惕过度设计,鼓励开发人员只解决现在需要解决的问题,而不是将来可能需要解决的问题。未来的问题应该在它们出现之后再去解决,因为到了那个时候我们可以看到它们的实际状况和需求。

测试

要求开发人员进行单元测试、集成测试或端到端测试。一般来说,CL 中应该包含测试,除非这个 CL 只是为了处理紧急情况。
确保 CL 中的测试是正确、合理和有用的。因为测试本身无法测试自己,而且我们很少会为测试编写测试,所以必须确保测试是有效的。
如果代码出了问题,测试会失败吗?如果代码发生改动,它们会误报吗?每一个测试都有断言吗?是否按照不同的测试方法对测试进行分类?
请记住,测试代码也是需要维护的。

命名

开发人员是否使用了良好的命名方式?好的命名要能够充分表达一个项(变量、类名等)是什么或者用来做什么,但又不至于让人难以阅读。

注释

开发人员有没有用自然语言写出清晰的注释?他们所写的注释都是必需的吗?通常,注释应该用于解释代码的用处,而不是解释它们在干什么。如果代码不够清晰,无法自解释,那就应该简化代码。当然也有一些例外(例如,正则表达式和复杂的算法,如果能够解释它们在做什么,会让阅读代码的人受益匪浅),但大多数注释都应该指出代码中不可能包含的信息,比如这些代码背后的缘由。
CL 附带的其他注解也很重要,比如告知一个可以移除的待办事项,或者一个不要做出代码变更的建议,等等。
注意,注释不同于类、模块或函数文档。文档的目的是为了说明代码的用途、用法和行为。

代码风格

谷歌为主要编程语言和大多数次要编程语言提供了代码风格指南,所以要确保 CL 遵循了适当的指南。
如果你想对指南中没有提及的风格做出改进,可以在注释前面加上“Nit:”,让开发人员知道这是一个你认为可以改进的地方,但不是强制性的。但请不要只是基于个人偏好来阻止 CL 的提交。
开发人员不应该将风格变更与其他变更放在一起,这样很难看出 CL 发生了哪些变化,导致合并和回滚变得更加复杂。如果开发人员想要重新格式化整个文件,让他们将重新格式化后的文件作为单独的 CL,并将功能变更作为另一个 CL。

文档

如果 CL 导致用户构建、测试、交互或发布代码的方式发生了变化,请确保相关的文档也得到了更新,包括 README、g3doc 页和其他生成的参考文档。如果 CL 有移除或弃用代码,请考虑一下是否也应该删除相关的文档。如果文档缺失,要向开发人员索要。

查看每一行代码

查看每一行代码。有些东西可以看一看,比如数据文件、生成的代码或大型数据结构,但不要只是粗略地扫一下类、函数或代码块,并假定它们都能正常运行。显然,有些代码需要仔细检查,至于是哪些代码完全取决于你,但你至少应该要理解这些代码都在做些什么。
如果代码很复杂或者你难以快速看懂它们,导致评审速度变慢,你要让开发人员知道,并在进行进一步评审之前让他们做一些澄清。如果你看不懂这些代码,其他开发人员很可能也看不懂。因此,要求开发人员澄清代码其实也是在帮助未来的开发人员更好地理解代码。
如果你理解代码,但又觉得没有资格做代码评审,可以确保有资格的 CL 评审人员在代码评审时考虑到了安全性、并发性、可访问性、国际化等问题。

上下文

代码评审工具通常只显示被修改的代码,但有时候你需要查看整个文件,确保代码变更是有意义的。例如,你可能只看到新添加了四行代码,但如果你看一下整个文件,会发现这四行代码位于一个 50 多行的方法中,这个时候需要将这个方法拆分为更小的方法。
你需要基于整个系统来考量 CL。这个 CL 是提升了系统的代码质量,还是让整个系统变得更复杂、更不可测?不要接受导致系统代码质量退化的 CL。大多数系统都是因为累积了很多小的变更而变复杂的,所以要尽量避免小的变更带来的复杂性。

好的一面

如果你在 CL 中看到一些不错的东西,要让开发人员知道,特别是当他们以一种很好的方式解决了问题。代码评审通常只关注错误的东西,但其实也应该鼓励和赞赏好的代码实践。有时候,让开发人员知道他们做对了事情比让他们知道做错了事情更有价值。

总结

在进行代码评审时,你要确保:
良好的代码设计。
功能对代码用户来说是有用的。
UI 变更应该是合理的。
并行编程是安全的。
代码复杂性不要超过应有的程度
不需要实现可能会在未来出现的需求。
有适当的单元测试。
精心设计的测试用例。
使用了清晰的命名方式。
清晰而有用的代码注释,要解释“为什么”,而不是“什么”。
恰如其分的代码文档化。
代码要遵循风格指南。
检查每一行代码,查看上下文,确保你正在改进代码质量,并为表现不错的开发人员点赞。
检查 CL
在知道了代码评审要关注哪些东西之后,如何有效地进行跨文件代码评审呢?
代码变更有意义吗?它们有没有良好的描述?
先看一下代码变更中最重要的部分,它整体设计得如何?
按照适当的顺序检查 CL 的其余部分。
第一步:从整体查看代码变更
先看一下 CL 描述,看看这个 CL 做了些什么。做出这个变更有意义吗?如果这个变更是不必要的,请立即做出回复,并解释为什么不应该发生这个变更。在你拒绝这样的变更时,可以向开发人员建议他们应该做些什么。
例如,你可以说:“看起来你在这方面做得不错,谢谢!不过,我们正打算移除这个系统,所以现在不想对它做任何修改。或许你可以重构一下另外一个类”?
注意,评审人员在拒绝一个 CL 并提供替代建议时要做得很有礼貌。礼貌是很重要的,因为作为开发人员,我们要彼此尊重,即使可能意见不一致。
如果有很多 CL 是你不希望出现的,就要考虑重新调整开发团队或外部贡献者的开发流程,以便在开发新的 CL 之前进行更多的沟通。提前告诉人们哪些事情不要做,这比等他们做完了这些事情再把它们扔掉或者进行彻底重写要好得多。
第二步:检查 CL 的主要部分
找到 CL 的主要文件。通常一个 CL 会有一个包含了主要逻辑变更的文件,也就是 CL 的主要部分。先看看这些主要部分,有助于了解整个上下文,加快代码评审速度。如果 CL 太大,以致于你无法确定哪些部分是主要的,可以询问开发人员,或者让他们把 CL 拆分成多个 CL。
如果 CL 的主要部分存在严重的设计问题,要立即回复开发人员,即使你还没有时间检查 CL 的其余部分。这个时候检查 CL 的其余部分可能是在浪费时间,因为如果主要部分存在严重的设计问题,那么其他部分就变得无关紧要了。
为什么要立即回复开发人员?原因有二:
开发人员在发出一个 CL 之后会继续开始后续的开发工作。如果你正在评审的 CL 存在严重的设计问题,他们也需要重写后续的 CL。所以,最好赶在开发人员在有问题的设计上花费不必要的时间之前告诉他们。
大的设计变更比小的变更需要更长的时间。为了让开发人员能够在截止日期之前提交代码,同时又能保持代码库的质量,要尽早让他们开始重写工作。
第三步:按照适当的顺序检查 CL 的其余部分
在确认整体 CL 没有严重的设计问题之后,试着按照某种逻辑顺序来检查其他文件,确保不会错过任何一个需要检查的文件。通常,在你检查完主要文件之后,按照代码评审工具显示它们的顺序来浏览每个文件就可以了。你也可以在检查主要代码之前先查看测试代码,这样可以对代码变更有一个大致的概念。
代码评审的速度
为什么代码评审要快速进行?
在谷歌,我们对开发团队的整体交付速度(而不是针对个体开发人员写代码的速度)进行了优化。个体开发速度也很重要,但其重要性比不上整个团队的开发速度。
如果代码评审的速度很慢,就会发生以下这些事情:
团队的整体开发速度降低了。如果个体开发人员无法快速地对评审做出响应,可能是因为他们有其他事情要做。但是,如果每个 CL 都要等待一次又一次的评审,那么其他成员的新特性和 bug 修复就会被延迟,可能是几天、几周甚至是几个月。
开发人员开始对代码评审流程提出抗议。如果评审人员要隔几天才回复一次,但每次都要求对 CL 进行重大修改,开发人员可能会觉得很沮丧。通常,他们会抱怨评审人员太过严苛。如果评审人员能够快速提供反馈,抱怨就会消失,即使他们要求做出的修改是一样的。代码评审过程的大多数抱怨实际上可以通过加快评审速度来解决。
代码质量受影响。如果评审速度很慢,开发人员的压力也会随之增加,因为他们不能提交不甚完美的 CL。缓慢的评审流程还会阻碍代码清理、重构和对现有 CL 做出进一步改进。
代码评审应该要多快?
如果你不是在集中精力完成手头的任务,那就应该在第一时间评审代码。
对代码评审做出响应最好不要超过一个工作日。
如果遵循这些原则,那么一个典型的 CL 在一天内(如果需要的话)可以进行多轮评审。
速度和中断
有一种情况,即如果你正在集中精力完成手头的任务,比如写代码,那就不要打断自己去做代码评审。研究表明,开发人员被中断之后可能需要很长时间才能恢复到之前的状态。因此,从团队整体上看,在写代码时打断自己比让另一个开发人员等待代码评审要付出更大的代价。
所以,对于这种情况,可以等到你手头工作可以停了再开始代码评审。可以是在完成手头的编码任务之后,午饭后,会议结束后,休息结束后,等等。
快速响应
我们所说代码评审速度指的是响应时间,而不是 CL 完成整个评审过程并提交到代码库所需的时间。理想情况下,整个评审过程也应该是很快的,但单次评审请求的响应速度比整个过程的响应速度更重要。
有时候可能需要很长时间才能完成整个评审过程,但在整个过程中评审人员的快速响应可以极大减轻开发人员对“慢”评审的沮丧感。
如果你太忙了,可以先向开发人员发送一个响应,让他们知道你什么时候可以开始评审,或者建议让其他可以更快做出响应的评审人员来评审代码,或者提供一些初步反馈。
最重要的是评审人员要花足够的时间进行评审,确保代码符合标准。但不管怎样,最好响应速度还是要快一些。
跨时区代码评审
在进行跨时区代码评审时,试着在开发人员还在办公室的时候做出响应。如果他们已经回家了,那么最好可以确保他们在第二天回到办公室时可以看到代码评审已经完成。
带有注解的 LGTM
为了加快代码评审速度,对于以下两种情况,评审人员应该给出 LGTM(Look Good to Me,没有问题)或者通过,即使他们在 CL 中留下了未解决的问题:
评审人员确信开发人员将会处理好评审人员给出的建议和意见。
其余的改动是次要的,不一定要求开发人员完成。
当开发人员和评审人员处于不同的时区时,最好可以使用带有注解的 LGTM,否则开发人员可能需要等上一整天才能获得“LGTM,批准”。
大型的 CL
如果有人给你发了一个很大的代码评审,而你不确定是否有足够时间完成评审,通常的做法是要求开发人员把 CL 拆分成几个较小的 CL。这样做通常是合理的,对评审人员来说是有好处的,即使开发人员需要做点额外的工作。
如果一个 CL 不能拆分成更小的 CL,并且你没有足够的时间进行快速评审,至少要对 CL 的总体设计写一些注解,并发给开发人员。评审人员的目标之一是在不影响代码质量的情况下快速对开发人员做出响应,或者让他们能够快速采取进一步行动。
持续改进代码评审
如果你遵循了这些指导原则,并且对代码评审过程严格要求,你会发现,随着时间的推移,整个代码评审过程会变得越来越快。开发人员知道为了保证代码质量需要做些什么,并从一开始就向你发送非常棒的 CL,这样评审所需的时间就会越来越少。评审人员也学会了如何快速做出响应。但不要为了提高评审速度而牺牲代码评审标准或质量——从长远来看,这样做并不会让任何事情变得更快。
紧急情况
在一些紧急情况下,CL 必须非常快速地通过整个评审过程,在质量方面会有些许的放松。请参看这些“紧急情况”,看看哪些符合紧急情况标准,哪些不符合。
怎样写评审注解
概要
礼貌。
解释你的理由。
给出明确的方向,指出问题,并让开发人员决定如何在两者之间做出权衡。
鼓励开发人员简化代码,或者添加代码注释,而不只是让他们解释代码的复杂性。
礼貌
一般来说,礼貌和尊重是很重要的。一个是要确保你的评论是针对代码而不是针对开发人员。你不一定要一直这么做,但当你想说一些可能会让开发人员感到激动或有争议的话时,绝对有必要这么做。例如:
不好的说法:“为什么你要在这个地方使用线程,这样做显然不会获得任何好处”。
好的说法:“在这里使用并发模型增加了系统复杂性,但我看不到任何实际的性能好处,所以这段代码最好使用单线程,而不是多线程”。
解释理由
从上面的正面示例可以看出,这样有助于开发人员理解你为什么要给出这些建议。你并不一定总是要在评审中提供这些信息,但如果你能够为你的意图、所遵循的最佳实践或你的建议将如何改进代码质量给出更多的解释会更好。
给予指导
一般来说,修复 CL 是开发人员的责任,而不是评审人员的责任。你不需要为开发人员提供详细的解决方案或者为他们写代码。
不过,这并不意味着评审人员就不应该帮助开发人员。你最好可以在指出问题和给予指导之间做出权衡。指出问题,并让开发人员做出决策,这样有助于开发人员学到东西,并让代码评审变得更容易。这样还可以产出更好的解决方案,因为开发人员比评审人员更了解代码。
不过,有时候直接给出指令、建议或代码会更有用。代码评审的主要目的是获得尽可能好的 CL。第二个目的是提高开发人员的技能,这样以后需要的评审就会越来越少。
接受注解
如果你要求开发人员解释一段你不理解的代码,他们通常会去重写代码,并把代码写得更清晰。有时候在代码中添加注解也是一种恰当的做法,只要它不只是用来解释太过复杂的代码。
不要只是把注解写在代码评审工具里,因为这对于将来要阅读代码的人来说并没有多大帮助。只有少数情况可以接受这种做法,例如,你对评审的东西不太熟悉,而开发人员的解释却是很多人所熟知的。
代码评审回推
有时候,开发人员会回推代码评审。他们可能不同意你的意见,或者他们抱怨你太严格了。
谁是对的?
如果开发人员不同意你的意见,先花点时间想一下他们是不是对的。通常,他们比你更熟悉代码,所以可能对代码的某些方面更了解。他们的论点有道理吗?从代码质量角度来看,他们的回推是有道理的吗?如果是,就让他们知道他们是对的,这个问题就解决了。
然而,开发人员并不总是正确的。在这种情况下,评审人员要进一步解释为什么他们的建议是正确的。
如果评审人员认为他们的建议可以改善代码质量,并认为评审所带来的代码质量改进值得开发人员做出额外的工作,那么他们就应该坚持。改善代码质量往往是由一系列的小步骤组成的。
有时候你需要花很多时间反复解释,但要始终保持礼貌,并让开发人员知道你知道他们在说什么。
激动的开发人员
有时候,评审人员会认为如果他们坚持要开发人员做出改动,会让开发人员感到不安。开发人员有时候确实会感到沮丧,但这种感觉通常都很短暂,之后他们会非常感谢你帮助他们提高了代码质量。如果你在评审过程中表现得很有礼貌,开发人员一点都不会感到不安,这种担心可能是多余的。通常,令开发人员感到不安的是你写注解的方式,而不是你对代码质量的坚持。
稍后再解决
一种常见的回推原因是开发人员希望尽快完成任务。他们不想经过一轮又一轮的代码评审,他们说他们会在后续的 CL 中解决遗留问题,你现在让 CL 通过就可以了。一些开发人员会做得很好,他们在提交 CL 后立即就开发后续的 CL。但经验表明,开发人员开发原始 CL 的时间越长,他们进行后续修复的可能性就越小。除非开发人员在提交 CL 之后立即进行修复,否则在通过之后通常不会再去做这件事情。这并不是因为开发人员不负责任,而是因为他们有很多工作要做,而修复工作通常会被遗忘。所以,最好让开发人员马上把 CL 修复掉。
如果 CL 引入了新的复杂性,在提交之前必须将其清理掉,除非是紧急情况。如果 CL 暴露了一些目前还无法解决的问题,开发人员需要把 bug 记录下来,并将其分配给自己,这样它就不会被遗漏。他们还可以在代码中加入 TODO 注释,指向已经记录好的 bug。
抱怨评审太严格
如果你之前的代码评审很放松,然后突然变得严格起来,可能会引起一些开发人员的抱怨。不过没关系,加快代码评审速度通常会让这些抱怨逐渐消失。
有时候,这些抱怨可能需要几个月的时间才能消除,但开发人员到最后通常会看到代码评审的价值,因为他们看到了严格的代码评审有助于产出优秀的代码。有时候,抗议最大声的人甚至会成为你最坚定的支持者。
解决冲突
如果你遵循了上述方法,但仍然会在评审过程中遇到无法解决的冲突,请再次参阅代码评审标准,了解那些有助于解决冲突的指导原则。
谷歌风格指南:
http://google.github.io/style...
CL 作者指南:
https://google.github.io/eng-...
评审人员指南:
https://google.github.io/eng-...
代码风格指南:
http://google.github.io/style...
紧急情况指南:
https://google.github.io/eng-...
查看原文:
https://google.github.io/eng-...

查看原文

赞 0 收藏 0 评论 0

Parkeeers 回答了问题 · 2019-11-22

Blog里如何插入jsfiddle的代码?

关注 20 回答 21

Parkeeers 发布了文章 · 2019-11-22

【补充中】基于svg使用anime.js构建动画

之前有调研过动画实现的方式,以及动画的种类(传送门:前端动画调研)。最近好好研究了一下svg的使用,在研究svg的同时又萌生了制作svg动画的想法,于是今天上场的主角就是使用JS驱动的动画库anime.js。文章前部分主要介绍使用anime.js完成对DOM元素的动画制作,后半部分会简单介绍一下anime.js与svg如何搭配使用。

1.Anime.js

Anime.js是简单,强大的轻量级JavaScript动画库,它可作用于css 属性,SVG,DOM 属性 以及JavaScript对象。根据官方的这段介绍,可以看出其对Canvas绘制是并不支持的,如果你需要对Canvas进行深度操作,推荐查看PixiJSCreateJS。通过anime.js我们可以使用js去编写原本需要在css中书写很长的keyframes,也可以通过使用timeline来同时处理多个元素的动画效果,这一切都不需要再编写css。看一段简单的代码:

// 引入animejs
import anime from 'animejs/lib/anime.es.js';

// 初始化anime方法,即可对指定元素激活动画效果
anime({
  targets: 'div',
  translateX: 250,
  rotate: 360,
  backgroundColor: '#FFF',
  duration: 800
});

上述代码的意味着,当调用anine()方法时,便将所有的div标签立即执行向右移动250px,同时旋转360deg,并将颜色同时修改为白色,整个动画持续800ms。需要注意的是,targets默认使用document.querySelectorAll(),所以这里所有的div标签都会注入刚才定义的动
通过这段代码是不是觉得定义一个动画非常的简单,有种将css代码放入JS对象中的感觉。那么anime.js是如何实现关键帧keyframes,接着往下看。

2.keyframes关键帧

在css中,我们可以通过transition实现线性的动画,通过设置duration以及作用的元素,即可实现一段简单的如放大缩小动画。
https://jsfiddle.net/o02yda5j/
然而如果需要实现更加复杂的非线性多段动画,必然要使用animations,借助@keyframes也可以实现较为复杂的动画,这里有个简单栗子
https://jsfiddle.net/o02yda5j/
在这段代码中,我们将动画设置为4帧,translate分别在每一帧中定义一次,如果使用anime.js声明多帧将变得非常简单且清晰:

anime({
    targets: 'div',
    translateX: [
        { value: 200 },
        { value: 200 },
        { value: 0 },  // 第三帧
        { value: 0 },
    ],
    translateY: [
        { value: 0 },
        { value: 100 },
        { value: 100 },  // 第三帧
        { value: 0 },
    ],
})

我们还可以在每一帧定义持续时间,延时,以及贝塞尔曲线。如果没有对每一帧定义持续时间duration,该帧将会与其他未声明duration的帧平分animation的duration。不知道你是否注意到,通过在每一帧设置不同的延时delay,配合不同的css属性,将会很轻松就实现非常复杂的动画。不过缺点也非常明显,就是需要计算每一帧的duration+delay对下一帧的影响,因为动画本身就是不同的元素在不同的节点发生的变形构建的。即便如此,在anime.js手动计算两者对下一帧的影响,依然要比@keyframes写法更为高效直观。如下代码可以实现一个小球以不同的贝塞尔曲线进行矩形运动。

anime({
  targets: '.property-keyframes-demo .el',  // 指定动画元素,省去了css多少的代码...
  translateX: [ // 只参与两帧,需要注意,每个css属性不要求帧数都一样
    { value: 250, duration: 1000, delay: 500 },
    { value: 0, duration: 1000, delay: 500 }
  ],
  translateY: [
    { value: -40, duration: 500 },
    { value: 40, duration: 500, delay: 1000 },
    { value: 0, duration: 500, delay: 1000 }
  ],
  scaleX: [
    { value: 4, duration: 100, delay: 500, easing: 'easeOutExpo' },  // 第一帧发生在动画开始的500ms后,持续时间为100ms,贝塞尔曲线为easeOutExpo
    { value: 1, duration: 900 },
    { value: 4, duration: 100, delay: 500, easing: 'easeOutExpo' },
    { value: 1, duration: 900 }
  ],
  scaleY: [
    { value: [1.75, 1], duration: 500 },
    { value: 2, duration: 50, delay: 1000, easing: 'easeOutExpo' },
    { value: 1, duration: 450 },
    { value: 1.75, duration: 50, delay: 1000, easing: 'easeOutExpo' },
    { value: 1, duration: 450 }
  ],
  rotate: [
    { value: 1.2 },
  ],
  width: [
    { value: 200 },
  ],
  easing: 'easeOutElastic(1, .8)',
  loop: true
});

但是anime的keyframes也不是万能的,如果你的动画写的足够复杂,那么势必帧数就要增大,再加上多个css属性在每一帧如果都有体现,如上图,计算同一属性的不同帧,以及不同属性的不同帧,将会异常困难。因为你总是无法准确统计每一帧duration+delay对其他帧的影响,加入你想修改某一帧的duration,你就需要修改整个动画的受其影响的每一帧。如何解决,我们引入timeline,将复杂动画的过程以时间线的形式进行切片,分段处理。换句话讲使用timeline,将动画由串型改为并行。下图表示不同属性不同帧的执行情况,水平黑线表示duration,其白色间隔为delay延时。

image.png

3.Timeline 时间线

Animation本身也自带timeline时间线的概念,但是很遗憾,支持度非常的差,就连chrome支持度也不是很好
image.png。那么什么是timeline呢,学过视频剪辑的同学应该对这个很熟悉,简单讲,通过关键帧keyframes,一个完整的动画可以被分成若干个部分去执行,但是仅仅是一个元素的动画;timeline的基础单位是一个完整的动画,它是由一个一个动画组成的整体动画,允许你将多个动画进行组合,这是区别keyframes的根本。
详细的内容,官方文档以及实例解释的很清楚了,这里想说明几个使用中关键的地方。
1. timeline timeoffset
初始化一个时间线代码如下:

const timeline_1 = anime.timeline({
    easing: 'easeOutExpo',
    duration: 750
});

timeline_1
.add(paramters, timeoffset)
.add(paramters, timeoffset)
.add(paramters, timeoffset);

需要强调的是timeoffset参数,默认timeline上的每个动画是串型执行,即上个动画执行完毕,下个动画才能执行;但是好在add方法第二个参数允许声明各个timeline上的动画执行时间,这样就可以实现多个动画元素并行执行,对于构建复杂的动画体验极好。
2. 什么时候使用timeline
虽然使用一个timeline就可以实现复杂的动画,但是对于复杂的动画,使用一个timeline同样避免不了写一大堆动画元素内的keyfreams,所以对于复杂的动画,建议引入多个timeline,这样每个动画里各个帧的时间参照线就会随着新创建的timeline而清空,所以写keyframes更为友好。

4. 动画事件方法

对于复杂的动画,如果遇到需要监听动画各个状态怎么破,不要担心,anime.js内置多种对于动画监听的事件方法,

查看原文

赞 0 收藏 0 评论 0

Parkeeers 收藏了文章 · 2019-11-21

Vue中父子组件生命周期执行顺序初探

如今前端框架都流行组件化,页面元素都可以使用组件进行高度概括,那么处理组件之间的关系就如同处理页面架构一样重要。正确理解组件之间的关系,才能让代码按照我们与预料方式工作。最近参与了一个Vue.js的项目,在处理父子嵌套组件之间关系时遇到了较大的阻力,虽然问题最后解决了,但是以花费大量时间为代价的,记录在这里,希望下次不踩同样的坑,能更高效的处理此类问题。

0 问题描述

同react,vue组件也有一套完整的生命周期,不同阶段有不同的分工。总体来讲经常会用的生命周期钩子有以下几种:

  • created
  • mounted
  • destroy

通常会在这些钩子中处理一些异步请求,最常见的就是发起网络请求调用api获取数据。

这里有个问题:在单一组件中,钩子的执行顺序是created -> mounted -> destroyed,但当父子组件嵌套时,父组件和子组件各拥有各自独立的钩子函数,这些父子组件的这些钩子是如何交融执行,且执行顺序又是怎样的呢?

最近开发中遇到的一个“诡异”的问题,就是由于对父子组件生命周期钩子执行顺序理解不透彻引起的。问题是这样的:有一个组件有由一系列子组件组成,子组件又被分解成组件,这样下来就构成了三级组件。需求是在组件显示在页面上之后,再将数据初始化进行回显。父组件获取数据后传递到子组件,要求子组件根据这个值将内部元数据进行过滤和加工。那么在子组件中什么时机下才能获取父组件传递过来的新值呢?。

我的做法是这样的:最高层父组件的mounted中发起请求获取数据,通过vue的响应机制以props的形式传递到子组件,在子组件的mounted中拿到对应的props进行处理。这样做法要求父组件的mounted时机先于子组件的mounted,但事实是这样吗?显然不是。

这样导致的问题就是,数据无法正确的回显。

1 探究

探究的方法是:写一个有父子嵌套关系的组件,分别在他们的钩子函数中打印日志,观察执行顺序。得到的结果如图所示,父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载。
图片描述

子组件挂载完成后,父组件还未挂载。所以组件数据回显的时候,在父组件mounted中获取api的数据,子组件的mounted是拿不到的。

仔细看看父子组件生命周期钩子的执行顺序,会发现created这个钩子是按照从外内顺序执行,所以回显场景的解决方案是:在created中发起请求获取数据,依次在子组件的created中会接收到这个数据。

2 结论

Vue父子组件生命周期钩子的执行顺序遵循:从外到内,然后再从内到外,不管嵌套几层深,也遵循这个规律。

组件化的设计思路大抵相同,React中父子组件生命周期钩子执行顺序,具体没做探究,但是值得一提的是react父组件的componentDidMount也是晚于子组件componentDidMount执行的。

3 其他

关于回显,问题在于如何在子组件中知道远程数据回来了,并且通过对远程数据的加工处理,最终形成正确的回显。处理按照钩子的顺序获取数据,在vue中还有一个特性watch,是否可以通过watch属性的方式来更新回显呢?这种方法有待于探究。

查看原文

Parkeeers 发布了文章 · 2019-11-20

在vue项目中引用并使用svg

占坑:如何在vue项目中引入svg文件

查看原文

赞 0 收藏 0 评论 0

Parkeeers 关注了专栏 · 2019-11-07

SVG开发与应用

专注于分享 svg 使用经验、技巧、以及教程的网站。 当然,这里不止于SVG,对于SVG动效设计的最佳搭档的GreenSock、Snapsvg、Mojs等动效js也会进行涉猎。 投资在学习上的时间总有一天会回赠给你自己 ! 为了获得访问svgtrick的最佳体验,推荐使用 Google Chrome 等现代浏览器来浏览。

关注 55

Parkeeers 提出了问题 · 2019-10-14

解决【已解决】iframe如何能继续加载使用x-frame-options: SAMEORIGIN的网页

背景

项目中需要引入第三方详情页做审核展示,所以需要将第三方详情页以iframe的形式动态加载到当前审核页面中作为预览的一部分,但是发现部分详情页,在响应头增加了如下字段:x-frame-options: SAMEORIGIN,限制了只能同源的网站才能引用,这样直接导致非同源的iframe失效了。

问题

网站页面做了x-frame-options: SAMEORIGIN处理,这样限制了iframe嵌入该页面,有没有什么办法能使iframe依然能嵌入该页面呢

关注 3 回答 1

认证与成就

  • 获得 216 次点赞
  • 获得 11 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-05
个人主页被 922 人浏览