杀人者,打虎武松也。
在以前的一篇文章中,我仔细分析了Medium.com用来展示图片的技术,这种技术可以在展示图片的时候让图片从一张模糊的图片过渡到一张清晰的图片。从那以后,我找到了其他一些同样采用相似方式展示图片的网站。下面让我们来看一下Quora, Quartz 以及 Clicktorelease 是如何实现的。 快速导读 图片是网页中最大的静态资源,他们通常会占据三分之二的空间。图片优化和选择正确的图片格式是至关重要的。另外,有些网站会在图片没有加载的时候,使用小的缩略图当作占位符。这种技术通常也被称作“blur-up“。这种技术主要是为了提升用户感知性能,如果搭配懒加载使用,还可以达到节省流量的效果。 当然这项技术还是有其他可替代的技术的。其中一种是使用一张单独的JPEG图片,调整它的扫描脚本使得可以快速渲染出一个原始的像素化的版本,并且在后续扫描中逐步渲染出最终清晰的图片。我推荐你通读Progressive JPEGs and green Martians 去更了解这项技术。 还有一点,如果你喜欢图片加载优化这方面的内容,可以关注Cloudinary’s Blog,里面有很多关于这方面话题的博文。 Quartz Take a look at the following video, recorded when accessing a page on QZ.com with a throttled connection: 看一下下面的这个视频,它记录着当使用慢速的网络访问QZ.com的网页时,Quartz's 的方式按照下面的步骤实现: Request a small <img /> that will be blurred. In their case, they use 50px width images, with a 80% quality. 请求一个小的用于被模糊的<img />。在这种情况下,他们使用宽度为50px,质量为80%的图片。 通过CSS filter应用模糊效果。 请求一个大的图片。 关于Quartz的一个有趣的地方在于,他们使用响应式图片的语法去标记指定大图片。最大的不同点在于他们使用data-srcset属性,这样他们就可以控制何时发起请求并且防止浏览器在标记被解析之后去请求大尺寸的图片。 我发现这种方式非常优雅,因为他们尝试遵循“标准”的方式去处理图片,就是通过增加额外的字段以实现懒加载和实现过度动画。 <figure class="progressive-image featured-image size-extra-large"> <picture style="padding-bottom: 56.1875%; background-image: url(https://qzprod.files.wordpress.com/2017/01/fish.jpg?quality=80&strip=all&w=50);"> <img src="https://qzprod.files.wordpress.com/2017/01/fish.jpg?quality=80&strip=all&w=50"> <img alt="INDIA-fishermen" /> <noscript> <img src="https://qzprod.files.wordpress.com/2017/01/fish.jpg?quality=80&strip=all&w=320" alt="INDIA-fishermen"> </noscript> </picture> <figcaption>...</figcaption> </figure> Quora Quora也是在他们的po文里面实现了图片blur-up技术。例子详见head to this page。 这里我们可以看到当显示模糊占位图片的时候页面的样子。 让我们深入代码来看看到底时如何实现的。首先,让我们来看看HTML标记: <div> <canvas class="qtext_image_placeholder portrait qtext_image zoomable_in zoomable_in_feed" width="499" height="874" data-src="data:image/PNG;base64,UklGRmgAAABXRUJQVlA4IFwAAADwAQCdASoGAAoAAUAmJYgCdEf/g…iD0z/yA/5ipcuk5xHSdrS38j8CkH7s+vKeZu9EwRy0f/KPIlo/+UifdfcpiRcJiRnXXAAAAA=="> </canvas> <img src /> </div> 为什么同时使用data-src和master-src?master_src 图片是当点击图片获取更大尺寸时加载的那张图片。 Quora没有使用模糊效果。他们使用canvas.drawImage()在canvas里渲染小的缩略图。你可以看到通过查看他们的代码发现这个方法,在捆绑在主要JS文件的shared/lazy_load_images模块中可以看到: // draw the data-uri image on the canvas function a(e) { if (e.getAttribute("data-src")) { var t = new Image; t.src = e.getAttribute("data-src"); var i = e.getAttribute("width") , n = e.getAttribute("height") , o = e.getContext("2d"); t.addEventListener("load", function() { o.drawImage(t, 0, 0, i, n) }, false) } } // go through all canvas on the page with a data-src function r() { var e = document.querySelectorAll("canvas[data-src]"); d.insertionQ("canvas[data-src]").every(function(e) { a(e) }), [].forEach.call(e, a) } 简单来说,Quora所使用的技术主要包括: 在canvas中渲染一个内联的非常小的png。 使用Webp(如果支持的话)或者其他格式请求大尺寸图片。 将大尺寸图片设置为<img />元素的资源,然后逐渐变化它的透明度并且隐藏canvas。 Clicktorelease Clicktorelease.com, @thespite,刚刚重新设计过,也为图片设置了懒加载。 这里最重要的部分是缩略图并不是要加载的图片本身,而是缩略图DCT矩阵的值,这使得负载非常非常小。 加载图片的步骤具体如下: 请求thumb-src图片。这是一个16x4的PNG 缩略图,通常大小在300B左右。 创建一个<canvas />并且在上面绘制通过反向计算缩略图的DCT得到的图片。 使用original-src地址请求大尺寸的图片。如果浏览器支持webp格式,那么“.webp”就会被添加到src上,这样就会请求webp格式的图片。 原始的标记大概是这样的: <div> <div> <div> <noscript><img src="/images/graphical-web-2016.jpg"></noscript> </div> </div> 我认为这是一个非常好关于渐进增强的例子,提供了<noscript> 作为替代,并且逐步叠加效果,从一个固定的背景图片,到一个模糊的图片,再到最后的支持webp的大尺寸的图片。 总结 看到实现渐进式图片加载的这些微小的变化非常有趣,其中有使用Javascript实现的,有基于响应式图片实现的,有使用CSS filters实现的,还有使用canvas实现的。 如果你想要一个基础的例子,可以移步my “Reproducing Medium loading image effect” pen。如果你也看到其他的网站实现了同样的技术,请记得一定要告诉我。 相关内容 我在我的RenderConf 2017的演讲Progressive Image Rendering中涉及到这个话题,当然这中间还一并提到其他一些可以使得图片更高效加载的技术。 我也写过关于使用SVG去创建占位符的方法,详见Using SVG as placeholders — More Image Loading Techniques。 原文发布时间:2018-07-02 本文来源掘金,如需转载请紧急联系作者
我本是一名文科专业半路出家的前端开发人员,从最初只会切图和写CSS、Html到现在会写点JS,一路坑坑洼洼,也是经历了很多,从2010年开始就用WordPress开设了自己的博客,虽然内容零零散散的并不多,但是多多少少也留下了时光的缩影,一直希望自己有一个自留地。用Node.js做服务端替换WordPress是去年的一个想法,由于一直腾不出时间,所以拖到了现在。当然了WordPress作为全球用户量最广的开源博客程序,易用性等诸多好处无可厚非,光自己的博客在过去几年就用了很多套模板,也用它做过很多不同风格不同功能的网站, 也许Node.js不是个人博客的最好的开发语言选择,不管是情怀还是其他,我相信一定有前端开发人员跟我一样有想过这样的尝试。市面上开源的博客程序很多,UI模板也相当漂亮,但是自己开发一个属于自己的博客程序,没事的时候折腾折腾,可能会是一件比较美好的事情,最主要的目的是在其折腾的过程中,可以多尝试服务端的功能开发,这对从一个纯前端转向全栈开发工程师是非常好的实践。 git上面有很多开源的Node.js源码,教程也比较详细,有的功能全面,有的相对简约,当然每个人只要选择适合自己的就好,下面简单介绍下我的项目,从技术角度而言,深度有限,大神多提意见。 架构 项目沿用传统的MVC,比较古老的架构,model(对象模型),view(视图),controller(控制器),model通俗的说就是数据库表字段的映射,view就是界面,UI,controller就是操作数据库,一般是接收到路由信息,然后对数据库进行操作,再把数据返回给view层。如果是熟悉后端的开发人员可能一眼就看的懂,不过对于没有做过后端开发的纯前端人员来说,可能需要一些时间去消化。想起我第一次做全栈开发,一个存储CCTV配置数据的项目,基于c#和sqlserver,也是标准的MVC架构,很简单的增删改查,是废了不少功夫。 数据库 数据库这块用的MongoDB,为什么用它,因为简单好用,再就是它是在非关系数据中功能最丰富,最像关系数据库的。操作数据库的工具用了mongoose,api易读,很容易上手。之前是wordpress用的mysql,导出数据到MongdoDB确实没什么特别的好的方法和工具,尝试用了一些工具和脚本,效率不高,最后还是手工完成的,好在本人比较懒,那么多年没几篇内容。 WEB框架和模板 后端用了Express的web框架,页面渲染部分是handlebars模板,个人感觉{{}}大括号的写法比较适合前端开发人员,但是handlebars模板是个弱逻辑语言,有一些不方便,需要helper,特别是分页和评论,逻辑特别复杂,不过如果你喜欢hb模板,可以去git上下helper的库。jade模板(现在改成pug了)会比较方便一些,对一些复杂逻辑的处理比较高效,主要是可以直接在模板中写js语法,但是缩进的写法不是每个前端开发都能习惯的。 前端 如今的前后端分离,MV*框架,工程化,模块化,这些概念大行其道,如果一个做前端的不知道这几个概念,恐怕工作都难找到。但是这个项目没有前后端分离,直接在后端渲染页面,也没有用MVVM框架,工程化和模块化就更不用说了,后台页面用了一个jQuery和BootStrap和一些插件,前台页面好像就一个jQuery,CSS基本手写,我相信前端开发人员看到会比较亲切。我的初衷是希望通过这个项目了解更多后端开发思路和模式,其次个人博客是个传播源,在后端渲染也是为了利于SEO。 原理 我这里简单从一个页面打开到完全加载,程序做了哪些事去论述一下整个项目工作原理,当你打开博客的一个页面,node.js在后端通过路由机制(express提供的路由模块)去匹配到这个页面的url,然后查找对应的controller(就是处理这个URL的函数),在这个controller函数中,对数据库进行一些过滤筛选(用mongoose对数据库进行操作)最终拿到页面需要的数据,然后再把数据传递给对应的模板(handlebars),最终渲染成HTML。 由于时间仓促,博客的功能可能比较简陋,但是基本的功能已经满足了,另外项目也有很多没用到的函数和接口,来不及整理,留待以后扩展吧。博客demo 这是我的博客git地址github.com/frogo/blog,欢迎大家star和fork, Screenshot 原文发布时间:2018年06月29日 原文作者:frogo 本文来源掘金,如需转载请紧急联系作者
前言 做移动端自适应时可能很多人都对自适应和dpr之间的关系产生疑问,也有一些人会疑虑比如我的自适应方案没有加dpr会不会出问题,针对这些疑问我说一下我的见解,希望能解除你的担忧。 1. 什么是尺寸自适应 首先说到自适应,可能在不同人眼里理解不同,特别与响应式的关系,在这里说一下我所理解的自适应,和其与响应式的区别。先说响应式设计,响应式设计表示在不同的屏幕尺寸下,都有良好的布局和内容表现,简单一点的说,就是一个页面可以适配多种不同尺寸的屏幕,而且看上去还是设计良好的。为了实现这个目的,可能会利用js或者css去动态改变布局的尺寸,在这个过程中会伴随元素尺寸的改变,布局的改变,甚至会把元素隐藏,比如在pc端显示的页面转到移动端就会这样。而自适应往往考虑的是另一个方面,就是希望页面的设计与设计稿的设计比例一致,这个也是做自适应的目的,在这个过程中针对不同的屏幕宽度元素的尺寸也会改变,但是一般不会有布局改变,和元素的隐藏,因为设计稿就这样,我们得按设计师妹子的尺寸来写页面。所以按照我以上的说法,那些按照css媒体查询写的自适应严格来说不叫自适应,因为断点之间会造成比例误差,而让误差少一点就得多插值。很明显使用css媒体查询并不是做自适应的好方法,我们需要一种准确的方法来做这个事,这个时候js就出来了,下面将会列举坊间流传甚广的淘宝方案和网易方案。 2. 淘宝方案 点这里可以看到淘宝方案具体的代码flexible 当然具体的代码是做了很多的边界处理和兼容处理的,但是核心可以浓缩为以下代码 (function () { var dpr = window.devicePixelRatio; var meta = document.createElement('meta'); var scale = 1 / dpr; meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale); document.getElementsByTagName('head')[0].appendChild(meta); // 动态设置的缩放大小会影响布局视口的尺寸 function resize() { var deviceWidth = document.documentElement.clientWidth; document.documentElement.style.fontSize = (deviceWidth / 10) +'px'; } resize(); window.onresize = resize; })() 复制代码 这段代码放在浏览器上就能做到自适应了,他的过程是先获取设备的dpr,所谓的dpr就是设备像素比,什么是设备像素比呢,就是单位尺寸内,设备物理像素的个数除以设备独立像素的大小,物理像素就是手机屏幕上一个一个的发光的点,大小是固定的,独立像素也叫做逻辑像素,css设置的像素大小就是逻辑像素,对于dpr等于2的手机屏幕,设置css宽度为1px,其实覆盖的是2个设备物理像素。回到正题,拿到dpr后,通过动态设置meta的viewport值,进行对布局的缩放操作。这里有一个关键,就是设置 width=device-width和initial-scale的大小,在描述两者的作用之前我们先要理解一个概念就是布局视口,布局视口在之前有一个别名叫做初始包含块,而在比较早的文献中初始包含块也叫做画布。理解画布可能比理解布局视口更简单,如果你按比例绘图,很多时候就要参照你所用画布的大小,比如设计师在750px画了一个200px的正方形,如果你要在一张大小是100cm的纸上画,你可能就要这样计算正方形的宽度了 100cm * 200 / 750,可以看到这个计算中是没有用到dpr,你的笔触跨过多少个纸张分子,多少个原子根本就不影响我的绘图比例。我们的画画的过程就相当于设置css的过程,css的尺寸依赖的就是布局视口的大小,而网页的布局视口大小在标准模式下可以这样获取 document.documentElement.clientWidth, 而两个关键的元素设置 width=device-width,initial-scale = scale,做的事情就是先把布局视口放大dpr倍,然后整体缩放相应倍数以适应设备尺寸。这个也很容易验证在控制台打印布局视口大小就行了 这是按照640px设计规范,设计图上标注200px元素大小,可以看出布局视口放大了3倍,然后再整体缩放到设备屏幕大小,由于这里是证明这个过程其实与dpr无关,我现在把scale的大小分别设置为 0.1 和 0.5 var meta = document.createElement('meta'); var scale = 0.1; meta.setAttribute('name', 'viewport'); 复制代码 var meta = document.createElement('meta'); var scale = 0.5; meta.setAttribute('name', 'viewport'); 复制代码 这里可以看到就算我设置scale不等于 1 / drp 的大小也不妨碍我按设计图的比例画出元素 这里要注意两点,因为我是用chrome模拟的,设置的时候发现几个问题 scale的值如果小于0.1布局视口也只能放大10倍,也就是布局视口最多放大10倍 当scale的值大于1时布局视口并不会缩小,而且布局视口不再匹配设备宽度,这种情况实际不会出现。 如果你引入了flexible.js进行测试,要注意删除边界条件,因为缩放影响了布局视口大小,相应的边界条件会触发,导致误认为dpr与自适应有关 要做到自适应关键是让元素的尺寸与布局视口绑定关系,在这里虽然布局视口放大了,但并不影响这种绑定关系,这里淘宝方案把布局视口的宽度分割了十等份,每份的大小相当于布局宽度的十分之一,而把每份的大小分配给根元素的字体大小,元素尺寸就可以设置rem单位来与布局视口绑定关系,以200px尺寸为例,他们比例映射是这样的 200px : 640px => xrem : 10rem 这里的10rem就是布局视口宽度,元素尺寸只要维持这个比例关系就行了,与dpr是没有关系的 x= 10 * 200 / 640 = 3.125rem 这里的计算可能会费一点时间,也有一些插件可以辅助把px转为rem的 但是方案是死的,人是活的,你只要把淘宝固有的十等分改一下就行了,比如设计稿是640px的 改一下 document.documentElement.style.fontSize = (deviceWidth / 6.4) +'px'; 复制代码 分了6.4等份 200px : 640px => xrem : 6.4rem x一看就知道是 2rem 流程: rem => 根元素字体大小 => 布局视口 当然也可以分割为100等份,这样方便为以后使用vw单位进行过渡 那么为什么淘宝要引入dpr,把布局放大再缩小呢,其中一点就是这个方案可以很好地解决1px边框的问题,对于高清屏来说设置1px像素大小,其实横跨的是dpr个设备像素,这样看起来线条不够细,与设计稿就产生出入,而通过布局放大再缩小的方案刚好就弥补了这个问题。但是随之而来也带来一个问题,看上面的截图我们看到字体大小发生了改变,在scale设置为0.1时基本就看不见了,原因是一般我们的字体大小的设置不会使用rem,而是使用px单位,这里的字体大小没有随布局视口的放大而增大,却随页面的整体缩放而缩小了,这里就得要针对不同的dpr做响应的处理,在淘宝的代码中我们可以看到 docEl.setAttribute('data-dpr', dpr); 复制代码 就是通过在根元素上挂载dpr信息,然后设置相应的css属性例如 [data-dpr=2] div{ font-size: 32px } [data-dpr=3] div{ font-size: 48px } 复制代码 特别对于安卓手机,各种神奇的dpr,如果每个都这样设置将会是灾难 所以淘宝非常聪明 var isAndroid = win.navigator.appVersion.match(/android/gi); var isIPhone = win.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = win.devicePixelRatio; if (isIPhone) { // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { dpr = 1; } } else { // 其他设备下,仍旧使用1倍的方案 dpr = 1; } scale = 1 / dpr; 复制代码 够简单直接,安卓高清屏是不存在的, 但是其实影响也不大,就是安卓屏的1px线条粗一点而已 如果除了要做自适应还要做响应式,那也得像上面设置字体一样一个一个设置,因为css媒体查询也是针对布局视口尺寸的。对于淘宝他们来说,肯定有一套工程化的方案来解决这种技术难题,对于遇到这个坑的伙伴估计得自已想办法了,预处理器是必不可少的。 从前面可以知道淘宝引入dpr并不是为了做自适应的,而是为了解决1px问题的,当然也引入了其他难题,既然如此,放弃解决1px问题,不就简单得多,网易方案就是这么做的。 3.网易方案 去除了边界处理和兼容处理,由于没有动态设置meta所以要在head中引入 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> 复制代码 (function () { var dpr = window.devicePixelRatio; function resize() { var deviceWidth = document.documentElement.clientWidth; document.documentElement.style.fontSize = (deviceWidth / 6.4) +'px'; } resize(); window.onresize = resize; })() 复制代码 网易方案没有引入dpr相关的,这也说明了移动端自适应与dpr是无关的 从图片中可以看出和淘宝方案的区别,布局视口没有放大,整个页面也没有缩放,但是并不影响与设计图的比例 200px : 640px => xrem : 6.4rem x= 2rem 流程: rem => 根元素的大小 => 布局视口 既然自适应与dpr无关那么就可以扩展出很多方案了 4. 其他方案 1.在布局视口等于设备宽度时,直接把根元素字体大小绑定到设备宽度大小上 document.documentElement.style.fontSize = (screen.width/ 6.4) +'px'; 复制代码 这里有关相关的文章 基于screen.width的伪响应式开发 2.直接定死布局视口 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta name="viewport" content="width=640, user-scalable=no">//定死为设计稿的尺寸 <meta name="renderer" content="webkit"> <title>定死布局视口</title> <style> html, body { margin: 0 } div { width: 200px; height: 200px; background: red; } body { background: blue; } </style> <script> </script> </head> <body> <div id="size200px">元素大小200px</div> </body> </html> 复制代码 不用rem单位,不用设置js,但是布局视口定死后,就不能用css媒体查询做响应式了,从这里也可以看出viewport属性的作用,就是让布局视口通过缩放来适配屏幕宽度,width=device.width仅仅是让布局视口初始大小等于设备宽度,后面设置的initial-scale是用来缩放布局视口大小,而且默认是布局视口初始大小等于设备宽度,也就是所谓的理想视口,换个说法就是如果你设置了initial-scale你可以不用设置 width=device.width了,淘宝方案你把width=device.width去掉,并不会影响自适应过程,加上主要是防止一些不按规范的浏览器出现兼容问题。如果还不能理解viewport的作用,那么可以参考svg中的viewport和viewBox的关系,原理是一样的。 3.使用新出单位 vw, vw 就是专门为自适应而出现的,100vw就是布局视口的宽度,非常厉害,你也不用设置js了 200px: 640px => xvw : 100vw x=200 * 100 / 640 = 31.25vw 流程: vw => 布局视口 看一下兼容性 兼容还可以,这里也有相关的资料 分享手淘过年项目中采用到的前端技术 总结 移动端尺寸自适应与dpr无关,除了淘宝方案外,其他方案都得处理1px的问题,但也减少针对不同dpr设备做响应式处理的麻烦,而且其中也没有一种一劳永逸的方案能解决全部问题。而作为新出来的单位vw,是时候该入坑了 参考文章 1. 张鑫旭 [基于screen.width的伪响应式开发](https://www.zhangxinxu.com/wordpress/2016/06/pseudo-response-layout-base-on-screen-width/) 2. 大漠 [分享手淘过年项目中采用到的前端技术](https://www.w3cplus.com/css/taobao-2018-year.html) 3. [fleible](https://github.com/huainanhai/flexible/blob/master/public/js/frame/flexible.debug.js) 4. 张鑫旭 [设备像素比devicePixelRatio简单介绍](设备像素比devicePixelRatio简单介绍)复制代码 原文发布时间:2018年06月28日 原文作者:changli2018 本文来源掘金,如需转载请紧急联系作者
提到数据结构与算法都感觉这应该是后端要掌握的知识,对前端来说只要写写页面,绑定事件,向后台发发数据就好了,用不到数据结构与算法,也许对于一些数据查找 简单的for循环就能搞定,也许只是提高了几毫秒而已,可忽略不计,假入node做后台开发的时候, 一次请求节约的几毫秒,千万次请求节约的就不是毫秒的时间的,数据结构是作为高级程序工程师必会的知识 先来看看js数据类型 基本类型(栈 stack) : Number、String 、Boolean、Null 和 Undefined , Symbol(es6 新增); 基本数据类型是按值访问 由高向低分配,栈内存最大是 8MB,(超出报栈溢出), String:是特殊的栈内存 (向高分配大小不定),程序员分配 引用类型(堆 heap) :Object 、Array 、Function 、Data;引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址(指针),向高分配,系统自动分配 一、堆栈空间分配区别: 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈; 堆(操作系统): 一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收,分配方式倒是类似于链表。 二、堆栈缓存方式区别: 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放; 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。 三、堆 、栈数据结构区别: 堆(数据结构):堆可以被看成是一棵树,如:堆排序; 栈(数据结构):一种先进后出的数据结构。 数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成;数据结构的基本操作的设置的最重要的准则是,实现应用程序与存储结构的独立(数据结构=数据的存储+算法) 数据结构分类 逻辑结构:反映数据之间的逻辑关系; 存储结构:数据结构在计算机中的表示; 逻辑结构: 集合:结构中的数据元素除了同属于一种类型外,别无其它关系。(无逻辑关系) 线性结构 :数据元素之间一对一的关系(线性表) 树形结构 :数据元素之间一对多的关系(非线性) 图状结构或网状结构: 结构中的数据元素之间存在多对多的关系(非线性) 复制代码 存储结构: 顺序存储数据结构 链式存储数据结构 索引存储数据结构 散列存储数据结构 复制代码 线性结构 : 队列: 也是一种运算受限的线性表。它只允许在表的一端进行插入,而在另一端进行删除。允许删除的一端称为队头(front),允许插入的一端称为队尾(rear)。先进先出。 栈: 是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶(Top),另一端为栈底(Bottom)。先进后出。top= -1时为空栈,top=0只能说明栈中只有一个元素,并且元素进栈时top应该自增. 后进先出 串 :是零个或多个字符组成的有限序列。长度为零的串称为空串(Empty String),它不包含任何字符。通常将仅由一个或多个空格组成的串称为空白串(Blank String) 注意:空串和空白串的不同,例如“ ”和“”分别表示长度为1的空白串和长度为0的空串。 非线性结构 树:一种非线性结构。树是递归结构,在树的定义中又用到了树的概念 有序数:子节点之间有顺序关系 无序树:子节点之间没有顺序关系 二叉树:一种非线性结构。树是递归结构,在树的定义中又用到了树的概念 二叉树遍历 使得每一个结点均被访问一次,而且仅被访问一次。非递归的遍历实现要利用栈。 先序遍历DLR:根节点->左子树->右子树(广度遍历) 复制代码 中序遍历LDR:左子树->根节点->右子树。必须要有中序遍历才能得到一棵二叉树的正确顺序(广度遍历) 复制代码 后续遍历LRD:左子树->右子树->根节点。需要栈的支持。(广度遍历) 复制代码 层次遍历:用一维数组存储二叉树时,总是以层次遍历的顺序存储结点。层次遍历应该借助队列。(深度遍历) 复制代码 内存: 一条很长一维数组; 算法 算法特征: 有穷性、确定性、可行性、输入、输出 算法设计衡量: 正确性、可读性、健壮性, 时间复杂度, 空间复杂度 算法分类 基本算法(必会) 冒泡排序 function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len - 1; i++) { for (var j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j+1]) { // 相邻元素两两对比 var temp = arr[j+1]; // 元素交换 arr[j+1] = arr[j]; arr[j] = temp; } } } return arr; } 复制代码 快速排序 function swap(items, firstIndex, secondIndex){ var temp = items[firstIndex]; items[firstIndex] = items[secondIndex]; items[secondIndex] = temp; } function partition(items, left, right) { var pivot = items[Math.floor((right + left) / 2)], i = left, j = right; while (i <= j) { while (items[i] < pivot) { i++; } while (items[j] > pivot) { j--; } if (i <= j) { swap(items, i, j); i++; j--; } } return i; } function quickSort(items, left, right) { var index; if (items.length > 1) { index = partition(items, left, right); if (left < index - 1) { quickSort(items, left, index - 1); } if (index < right) { quickSort(items, index, right); } } return items; } var items = [3,8,7,2,9,4,10] var result = quickSort(items, 0, items.length - 1); 复制代码 插入排序 function insertionSort(arr) { var len = arr.length; var preIndex, current; for (var i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while(preIndex >= 0 && arr[preIndex] > current) { arr[preIndex+1] = arr[preIndex]; preIndex--; } arr[preIndex+1] = current; } return arr; } 复制代码 选择排序 function selectionSort(arr) { var len = arr.length; var minIndex, temp; for (var i = 0; i < len - 1; i++) { minIndex = i; for (var j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { // 寻找最小的数 minIndex = j; // 将最小数的索引保存 } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; } 复制代码 时间空间复杂度 在冒泡排序,插入排序,选择排序,快速排序中,在最最坏情况下,快速排序的时间复杂为O(n2) ,插入排序O(n2),选择排序O(n2),冒泡排序O(n2) 关注下面的标签,发现更多相似文章 原文发布时间:2018年06月27日 原文作者:幸福拾荒者 本文来源掘金,如需转载请紧急联系作者
前言 首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。接下来的日子我应该会着力写一系列关于Vue与React内部原理的文章,感兴趣的同学点个关注或者Star。 之前的两篇文章响应式数据与数据依赖基本原理和从Vue数组响应化所引发的思考我们介绍了响应式数据相关的内容,没有看的同学可以点击上面的链接了解一下。如果大家都阅读过上面两篇文章的话,肯定对这方面内容有了足够的知识储备,想来是时候来看看Vue内部是如何实现数据响应化。目前Vue的代码非常庞大,但其中包含了例如:服务器渲染等我们不关心的内容,为了能集中于我们想学习的部分,我们这次阅读的是Vue的早期代码,大家可以checkout到这里查看对应的代码。 之前零零碎碎的看过React的部分源码,当我看到Vue的源码,觉得真的是非常优秀,各个模块之间解耦的非常好,可读性也很高。Vue响应式数据是在Observer模块中实现的,我们可以看看Observer是如何实现的。 发布-订阅模式 如果看过上两篇文章的同学应该会发现一个问题:数据响应化的代码与其他的代码耦合太强了,比如说: //代码来源于文章:响应式数据与数据依赖基本原理 //定义对象的单个响应式属性 function defineReactive(obj, key, value){ observify(value); Object.defineProperty(obj, key, { configurable: true, enumerable: true, set: function(newValue){ var oldValue = value; value = newValue; //可以在修改数据时触发其他的操作 console.log("newValue: ", newValue, " oldValue: ", oldValue); }, get: function(){ return value; } }); } 复制代码 比如上面的代码,set内部的处理的代码就与整个数据响应化相耦合,如果下次我们想要在set中做其他的操作,就必须要修改set函数内部的内容,这是非常不友好的,不符合开闭原则(OCP: Open Close Principle)。当然Vue不会采用这种方式去设计,为了解决这个问题,Vue引入了发布-订阅模式。其实发布-订阅模式是前端工程师非常熟悉的一种模式,又叫做观察者模式,它是一种定义对象间一种一对多的依赖关系,当一个对象的状态发生改变的时候,其他观察它的对象都会得到通知。我们最常见的DOM事件就是一种发布-订阅模式。比如: document.body.addEventListener("click", function(){ console.log("click event"); }); 复制代码 在上面的代码中我们监听了body的click事件,虽然我们不知道click事件什么时候会发生,但是我们一定能保证,如果发生了body的click事件,我们一定能得到通知,即回调函数被调用。在JavaScript中因为函数是一等公民,我们很少使用传统的发布-订阅模式,多采用的是事件模型的方式实现。在Vue中也实现了一个事件模型,我们可以看一下。因为Vue的模块之间解耦的非常好,因此在看代码之前,其实我们可以先来看看对应的单元测试文件,你就知道这个模块要实现什么功能,甚至如果你愿意的话,也可以自己实现一个类似的模块放进Vue的源码中运行。 Vue早期代码使用是jasmine进行单元测试,emitter_spec.js是事件模型的单元测试文件。首先简单介绍一下jasmine用到的函数,可以对照下面的代码了解具体的功能: describe是一个测试单元集合 it是一个测试用例 beforeEach会在每一个测试用例it执行前执行 expect期望函数,用作对期望值和实际值之间执行逻辑比较 createSpy用来创建spy,而spy的作用是监测函数的调用相关信息和函数执行参数 var Emitter = require('../../../src/emitter') var u = undefined // 代码有删减 describe('Emitter', function () { var e, spy beforeEach(function () { e = new Emitter() spy = jasmine.createSpy('emitter') }) it('on', function () { e.on('test', spy) e.emit('test', 1, 2 ,3) expect(spy.calls.count()).toBe(1) expect(spy).toHaveBeenCalledWith(1, 2, 3) }) it('once', function () { e.once('test', spy) e.emit('test', 1, 2 ,3) e.emit('test', 2, 3, 4) expect(spy.calls.count()).toBe(1) expect(spy).toHaveBeenCalledWith(1, 2, 3) }) it('off', function () { e.on('test1', spy) e.on('test2', spy) e.off() e.emit('test1') e.emit('test2') expect(spy.calls.count()).toBe(0) }) it('apply emit', function () { e.on('test', spy) e.applyEmit('test', 1) e.applyEmit('test', 1, 2, 3, 4, 5) expect(spy).toHaveBeenCalledWith(1) expect(spy).toHaveBeenCalledWith(1, 2, 3, 4, 5) }) }) 复制代码 可以看出Emitter对象实例对外提供以下接口: on: 注册监听接口,参数分别是事件名和监听函数 emit: 触发事件函数,参数是事件名 off: 取消对应事件的注册函数,参数分别是事件名和监听函数 once: 与on类似,仅会在第一次时通知监听函数,随后监听函数会被移除。 看完了上面的单元测试代码,我们现在已经基本了解了这个模块要干什么,现在让我们看看对应的代码: // 删去了注释并且对代码顺序有调整 // ctx是监听回调函数的执行作用域(this) function Emitter (ctx) { this._ctx = ctx || this } var p = Emitter.prototype p.on = function (event, fn) { this._cbs = this._cbs || {} ;(this._cbs[event] || (this._cbs[event] = [])) .push(fn) return this } // 三种模式 // 不传参情况清空所有监听函数 // 仅传事件名则清除该事件的所有监听函数 // 传递事件名和回调函数,则对应仅删除对应的监听事件 p.off = function (event, fn) { this._cbs = this._cbs || {} // all if (!arguments.length) { this._cbs = {} return this } // specific event var callbacks = this._cbs[event] if (!callbacks) return this // remove all handlers if (arguments.length === 1) { delete this._cbs[event] return this } // remove specific handler var cb for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i] // 这边的代码之所以会有cb.fn === fn要结合once函数去看 // 给once传递的监听函数其实已经被wrapped过 // 但是仍然可以通过原来的监听函数去off掉 if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1) break } } return this } // 触发对应事件的所有监听函数,注意最多只能用给监听函数传递三个参数(采用call) p.emit = function (event, a, b, c) { this._cbs = this._cbs || {} var callbacks = this._cbs[event] if (callbacks) { callbacks = callbacks.slice(0) for (var i = 0, len = callbacks.length; i < len; i++) { callbacks[i].call(this._ctx, a, b, c) } } return this } // 触发对应事件的所有监听函数,传递参数个数不受限制(采用apply) p.applyEmit = function (event) { this._cbs = this._cbs || {} var callbacks = this._cbs[event], args if (callbacks) { callbacks = callbacks.slice(0) args = callbacks.slice.call(arguments, 1) for (var i = 0, len = callbacks.length; i < len; i++) { callbacks[i].apply(this._ctx, args) } } return this } // 通过调用on与off事件事件,在第一次触发之后就`off`对应的监听事件 p.once = function (event, fn) { var self = this this._cbs = this._cbs || {} function on () { self.off(event, on) fn.apply(this, arguments) } on.fn = fn this.on(event, on) return this } 复制代码 我们可以看到上面的代码采用了原型模式创建了一个Emitter类。配合Karma跑一下这个模块 ,测试用例全部通过,到现在我们已经阅读完Emitter了,这算是一个小小的热身吧,接下来让我们正式看一下Observer模块。 Observer 对外功能 按照上面的思路我们先看看Observer对应的测试用例observer_spec.js,由于Observer的测试用例非常长,我会在代码注释中做解释,并尽量精简测试用例,能让我们了解模块对应功能即可,希望你能有耐心阅读下来。 //测试用例是精简版,否则太冗长 var Observer = require('../../../src/observe/observer') var _ = require('../../../src/util') //Vue内部使用工具方法 var u = undefined Observer.pathDelimiter = '.' //配置Observer路径分隔符 describe('Observer', function () { var spy beforeEach(function () { spy = jasmine.createSpy('observer') }) //我们可以看到我们通过Observer.create函数可以将数据变为可响应化, //然后我们监听get事件可以在属性被读取时触发对应事件,注意对象嵌套的情况(例如b.c) it('get', function () { Observer.emitGet = true var obj = { a: 1, b: { c: 2 } } var ob = Observer.create(obj) ob.on('get', spy) var t = obj.b.c expect(spy).toHaveBeenCalledWith('b', u, u) expect(spy).toHaveBeenCalledWith('b.c', u, u) Observer.emitGet = false }) //我们可以监听响应式数据的set事件,当响应式数据修改的时候,会触发对应的时间 it('set', function () { var obj = { a: 1, b: { c: 2 } } var ob = Observer.create(obj) ob.on('set', spy) obj.b.c = 4 expect(spy).toHaveBeenCalledWith('b.c', 4, u) }) //带有$与_开头的属性都不会被处理 it('ignore prefix', function () { var obj = { _test: 123, $test: 234 } var ob = Observer.create(obj) ob.on('set', spy) obj._test = 234 obj.$test = 345 expect(spy.calls.count()).toBe(0) }) //访问器属性也不会被处理 it('ignore accessors', function () { var obj = { a: 123, get b () { return this.a } } var ob = Observer.create(obj) obj.a = 234 expect(obj.b).toBe(234) }) // 对数属性的get监听,注意嵌套的情况 it('array get', function () { Observer.emitGet = true var obj = { arr: [{a:1}, {a:2}] } var ob = Observer.create(obj) ob.on('get', spy) var t = obj.arr[0].a expect(spy).toHaveBeenCalledWith('arr', u, u) expect(spy).toHaveBeenCalledWith('arr.0.a', u, u) expect(spy.calls.count()).toBe(2) Observer.emitGet = false }) // 对数属性的get监听,注意嵌套的情况 it('array set', function () { var obj = { arr: [{a:1}, {a:2}] } var ob = Observer.create(obj) ob.on('set', spy) obj.arr[0].a = 2 expect(spy).toHaveBeenCalledWith('arr.0.a', 2, u) }) // 我们看到可以通过监听mutate事件,在push调用的时候对应触发事件 // 触发事件第一个参数是"",代表的是路径名,具体源码可以看出,对于数组变异方法都是空字符串 // 触发事件第二个参数是数组本身 // 触发事件第三个参数比较复杂,其中: // method属性: 代表触发的方法名称 // args属性: 代表触发方法传递参数 // result属性: 代表触发变异方法之后数组的结果 // index属性: 代表变异方法对数组发生变化的最开始元素 // inserted属性: 代表数组新增的元素 // remove属性: 代表数组删除的元素 // 其他的变异方法: pop、shift、unshift、splice、sort、reverse内容都是非常相似的 // 具体我们就不一一列举的了,如果有疑问可以自己看到全部的单元测试代码 it('array push', function () { var arr = [{a:1}, {a:2}] var ob = Observer.create(arr) ob.on('mutate', spy) arr.push({a:3}) expect(spy.calls.mostRecent().args[0]).toBe('') expect(spy.calls.mostRecent().args[1]).toBe(arr) var mutation = spy.calls.mostRecent().args[2] expect(mutation).toBeDefined() expect(mutation.method).toBe('push') expect(mutation.index).toBe(2) expect(mutation.removed.length).toBe(0) expect(mutation.inserted.length).toBe(1) expect(mutation.inserted[0]).toBe(arr[2]) }) // 我们可以看到响应式数据中存在$add方法,类似于Vue.set,可以监听add事件 // 可以向响应式对象中添加新一个属性,如果之前存在该属性则操作会被忽略 // 并且新赋值的对象也必须被响应化 // 我们省略了对象数据$delete方法的单元测试,功能类似于Vue.delete,与$add方法相反,可以用于删除对象的属性 // 我们省略了数组的$set方法的单元测试,功能也类似与Vue.set,可以用于设置数组对应数字下标的值 // 我们省略了数组的$remove方法的单元测试,功能用于移除数组给定下标的值或者给定的值,例如: // var arr = [{a:1}, {a:2}] // var ob = Observer.create(arr) // arr.$remove(0) => 移除对应下标的值 或者 // arr.$remove(arr[0]) => 移除给定的值 it('object.$add', function () { var obj = {a:{b:1}} var ob = Observer.create(obj) ob.on('add', spy) // ignore existing keys obj.$add('a', 123) expect(spy.calls.count()).toBe(0) // add event var add = {d:2} obj.a.$add('c', add) expect(spy).toHaveBeenCalledWith('a.c', add, u) // check if add object is properly observed ob.on('set', spy) obj.a.c.d = 3 expect(spy).toHaveBeenCalledWith('a.c.d', 3, u) }) // 下面的测试用例用来表示如果两个不同对象parentA、parentB的属性指向同一个对象obj,那么该对象obj改变时会分别parentA与parentB的监听事件 it('shared observe', function () { var obj = { a: 1 } var parentA = { child1: obj } var parentB = { child2: obj } var obA = Observer.create(parentA) var obB = Observer.create(parentB) obA.on('set', spy) obB.on('set', spy) obj.a = 2 expect(spy.calls.count()).toBe(2) expect(spy).toHaveBeenCalledWith('child1.a', 2, u) expect(spy).toHaveBeenCalledWith('child2.a', 2, u) // test unobserve parentA.child1 = null obj.a = 3 expect(spy.calls.count()).toBe(4) expect(spy).toHaveBeenCalledWith('child1', null, u) expect(spy).toHaveBeenCalledWith('child2.a', 3, u) }) }) 复制代码 源码实现 数组 能坚持看到这里,我们的长征路就走过了一半了,我们已经知道了Oberver对外提供的功能了,现在我们就来了解一下Oberver内部的实现原理。 Oberver模块实际上采用采用组合继承(借用构造函数+原型继承)方式继承了Emitter,其目的就是继承Emitter的on, off,emit等方法。我们在上面的测试用例发现,我们并没有用new方法直接创建一个Oberver的对象实例,而是采用一个工厂方法Oberver.create方法来创建的,我们接下来看源码,由于代码比较多我会尽量去拆分成一个个小块来讲: // 代码出自于observe.js // 为了方便讲解我对代码顺序做了改变,要了解详细的情况可以查看具体的源码 var _ = require('../util') var Emitter = require('../emitter') var arrayAugmentations = require('./array-augmentations') var objectAugmentations = require('./object-augmentations') var uid = 0 /** * Type enums */ var ARRAY = 0 var OBJECT = 1 function Observer (value, type, options) { Emitter.call(this, options && options.callbackContext) this.id = ++uid this.value = value this.type = type this.parents = null if (value) { _.define(value, '$observer', this) if (type === ARRAY) { _.augment(value, arrayAugmentations) this.link(value) } else if (type === OBJECT) { if (options && options.doNotAlterProto) { _.deepMixin(value, objectAugmentations) } else { _.augment(value, objectAugmentations) } this.walk(value) } } } var p = Observer.prototype = Object.create(Emitter.prototype) Observer.pathDelimiter = '\b' Observer.emitGet = false Observer.create = function (value, options) { if (value && value.hasOwnProperty('$observer') && value.$observer instanceof Observer) { return value.$observer } if (_.isArray(value)) { return new Observer(value, ARRAY, options) } else if ( _.isObject(value) && !value.$scope // avoid Vue instance ) { return new Observer(value, OBJECT, options) } } 复制代码 我们首先从Observer.create看起,如果value值没有响应化过(通过是否含有$observer属性去判断),则使用new操作符创建Obsever实例(区分对象OBJECT与数组ARRAY)。接下来我们看Observer的构造函数是怎么定义的,首先借用Emitter构造函数: Emitter.call(this, options && options.callbackContext) 复制代码 配合原型继承 var p = Observer.prototype = Object.create(Emitter.prototype) 复制代码 从而实现了组合继承Emitter,因此Observer继承了Emitter的属性(ctx)和方法(on,emit等)。我们可以看到Observer有以下属性: id: 响应式数据的唯一标识 value: 原始数据 type: 标识是数组还是对象 parents: 标识响应式数据的父级,可能存在多个,比如var obj = { a : { b: 1}},在处理{b: 1}的响应化过程中parents中某个属性指向的就是obj的$observer。 我们接着看首先给该数据赋值$observer属性,指向的是实例对象本身。_.define内部是通过defineProperty实现的: define = function (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value : val, enumerable : !!enumerable, writable : true, configurable : true }) } 复制代码 下面我们首先看看是怎么处理数组类型的数据的 if (type === ARRAY) { _.augment(value, arrayAugmentations) this.link(value) } 复制代码 如果看过我前两篇文章的同学,其实还记得我们对数组响应化当时还做了一个着重的原理讲解,大概原理就是我们通过给数组对象设置新的原型对象,从而遮蔽掉原生数组的变异方法,大概的原理可以是: function observifyArray(array){ var aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; var arrayAugmentations = Object.create(Array.prototype); aryMethods.forEach((method)=> { let original = Array.prototype[method]; arrayAugmentations[method] = function () { // 调用对应的原生方法并返回结果 // do everything you what do ! return original.apply(this, arguments); }; }); array.__proto__ = arrayAugmentations; } 复制代码 回到Vue的源码,虽然我们知道基本原理肯定是相同的,但是我们仍然需要看看arrayAugmentations是什么?下面arrayAugmentations代码比较长。我们会在注释里面解释基本原理: // 代码来自于array-augmentations.js var _ = require('../util') var arrayAugmentations = Object.create(Array.prototype) // 这边操作和我们之前的实现方式非常相似 // 创建arrayAugmentations原型继承`Array.prototype`从而可以调用数组的原生方法 // 然后通过arrayAugmentations覆盖数组的变异方法,基本逻辑大致相同 ['push','pop','shift','unshift','splice','sort','reverse'].forEach(function (method) { var original = Array.prototype[method] // 覆盖arrayAugmentations中的变异方法 _.define(arrayAugmentations, method, function () { var args = _.toArray(arguments) // 这里调用了原生的数组变异方法,并获得结果 var result = original.apply(this, args) var ob = this.$observer var inserted, removed, index // 下面switch这一部分代码看起来很长,其实目的就是针对于不同的变异方法生成: // insert removed inserted 具体的含义对照之前的解释,了解即可 switch (method) { case 'push': inserted = args index = this.length - args.length break case 'unshift': inserted = args index = 0 break case 'pop': removed = [result] index = this.length break case 'shift': removed = [result] index = 0 break case 'splice': inserted = args.slice(2) removed = result index = args[0] break } // 如果给数组中插入新的数据,则需要调用ob.link // link函数其实在上面的_.augment(value, arrayAugmentations)之后也被调用了 // 具体的实现我们可以先不管 // 我们只要知道其目的就是分别对插入的数据执行响应化 if (inserted) ob.link(inserted, index) // 其实从link我们就可以猜出unlink是干什么的 // 主要就是对删除的数据解除响应化,具体实现逻辑后面解释 if (removed) ob.unlink(removed) // updateIndices我们也先不讲是怎么实现的, // 目的就是更新子元素在parents的key // 因为push和pop是不会改变现有元素的位置,因此不需要调用 // 而诸如splce shift unshift等变异方法会改变对应下标值,因此需要调用 if (method !== 'push' && method !== 'pop') { ob.updateIndices() } // 同样我们先不考虑propagate内部实现,我们只要propagate函数的目的就是 // 触发自身及其递归触发父级的事件 // 如果数组中的数据有插入或者删除,则需要对外触发"length"被改变 if (inserted || removed) { ob.propagate('set', 'length', this.length) } // 对外触发mutate事件 // 可以对照我们之前讲的测试用例'array push',就是在这里触发的,回头看看吧 ob.propagate('mutate', '', this, { method : method, args : args, result : result, index : index, inserted : inserted || [], removed : removed || [] }) return result }) }) // 可以回看一下测试用例 array set,目的就是设置对应下标的值 // 其实就是调用了splice变异方法, 其实我们在Vue中国想要改变某个下标的值的时候 // 官网给出的建议无非是Vue.set或者就是splice,都是相同的原理 // 注意这里的代码忽略了超出下标范围的值 _.define(arrayAugmentations, '$set', function (index, val) { if (index >= this.length) { this.length = index + 1 } return this.splice(index, 1, val)[0] }) // $remove与$add都是一个道理,都是调用的是`splice`函数 _.define(arrayAugmentations, '$remove', function (index) { if (typeof index !== 'number') { index = this.indexOf(index) } if (index > -1) { return this.splice(index, 1)[0] } }) module.exports = arrayAugmentations 复制代码 上面的代码相对比较长,具体的解释我们在代码中已经注释。到这里我们已经了解完arrayAugmentations了,我们接着看看_.augment做了什么。我们在文章从Vue数组响应化所引发的思考中讲过Vue是通过__proto__来实现数组响应化的,但是由于__proto__是个非标准属性,虽然广泛的浏览器厂商基本都实现了这个属性,但是还是存在部分的安卓版本并不支持该属性,Vue必须对此做相关的处理,_.augment就负责这个部分: exports.augment = '__proto__' in {} ? function (target, proto) { target.__proto__ = proto } : exports.deepMixin exports.deepMixin = function (to, from) { Object.getOwnPropertyNames(from).forEach(function (key) { var desc =Object.getOwnPropertyDescriptor(from, key) Object.defineProperty(to, key, desc) }) } 复制代码 我们看到如果浏览器不支持__proto__话调用deepMixin函数。而deepMixin的实现也是非常的简单,就是使用Object.defineProperty将原对象的属性描述符赋值给目标对象。接着调用了函数: this.link(value) 复制代码 关于link函数在上面的备注中我们已经见过了: if (inserted) ob.link(inserted, index) 复制代码 当时我们的解释是将新插入的数据响应化,知道了功能我们看看代码的实现: // p === Observer.prototype p.link = function (items, index) { index = index || 0 for (var i = 0, l = items.length; i < l; i++) { this.observe(i + index, items[i]) } } p.observe = function (key, val) { var ob = Observer.create(val) if (ob) { // register self as a parent of the child observer. var parents = ob.parents if (!parents) { ob.parents = parents = Object.create(null) } if (parents[this.id]) { _.warn('Observing duplicate key: ' + key) return } parents[this.id] = { ob: this, key: key } } } 复制代码 其实代码逻辑非常简单,link函数会对给定数组index(默认为0)之后的元素调用this.observe, 而observe其实也就是对给定的val值递归调用Observer.create,将数据响应化,并建立父级的Observer与当前实例的对应关系。前面其实我们发现Vue不仅仅会对插入的数据响应化,并且也会对删除的元素调用unlink,具体的调用代码是: if (removed) ob.unlink(removed) 复制代码 之前我们大致讲过其用作就是对删除的数据解除响应化,我们来看看具体的实现: p.unlink = function (items) { for (var i = 0, l = items.length; i < l; i++) { this.unobserve(items[i]) } } p.unobserve = function (val) { if (val && val.$observer) { val.$observer.parents[this.id] = null } } 复制代码 代码非常简单,就是对数据调用unobserve,而unobserve函数的主要目的就是解除父级observer与当前数据的关系并且不再保留引用,让浏览器内核必要的时候能够回收内存空间。 在arrayAugmentations中其实还调用过Observer的两个原型方法,一个是: ob.updateIndices() 复制代码 另一个是: ob.propagate('set', 'length', this.length) 复制代码 首先看看updateIndices函数,当时的函数的作用是更新子元素在parents的key,来看看具体实现: p.updateIndices = function () { var arr = this.value var i = arr.length var ob while (i--) { ob = arr[i] && arr[i].$observer if (ob) { ob.parents[this.id].key = i } } } 复制代码 接着看函数propagate: p.propagate = function (event, path, val, mutation) { this.emit(event, path, val, mutation) if (!this.parents) return for (var id in this.parents) { var parent = this.parents[id] if (!parent) continue var key = parent.key var parentPath = path ? key + Observer.pathDelimiter + path : key parent.ob.propagate(event, parentPath, val, mutation) } } 复制代码 我们之前说过propagate函数的作用的就是触发自身及其递归触发父级的事件,首先调用emit函数对外触发时间,其参数分别是:事件名、路径、值、mutatin对象。然后接着递归调用父级的事件,并且对应改变触发的path参数。parentPath等于parents[id].key + Observer.pathDelimiter + path 到此为止我们已经学习完了Vue是如何处理数组的响应化的,现在需要来看看是如何处理对象的响应化的。 对象 在Observer的构造函数中关于对象处理的代码是: if (type === OBJECT) { if (options && options.doNotAlterProto) { _.deepMixin(value, objectAugmentations) } else { _.augment(value, objectAugmentations) } this.walk(value) } 复制代码 和数组一样,我们首先要了解一下objectAugmentations的内部实现: var _ = require('../util') var objectAgumentations = Object.create(Object.prototype) _.define(objectAgumentations, '$add', function (key, val) { if (this.hasOwnProperty(key)) return _.define(this, key, val, true) var ob = this.$observer ob.observe(key, val) ob.convert(key, val) ob.emit('add:self', key, val) ob.propagate('add', key, val) }) _.define(objectAgumentations, '$delete', function (key) { if (!this.hasOwnProperty(key)) return delete this[key] var ob = this.$observer ob.emit('delete:self', key) ob.propagate('delete', key) }) 复制代码 相比于arrayAugmentations,objectAgumentations内部实现则简单的多,objectAgumentations添加了两个方法: $add与$delete。 $add用于给对象添加新的属性,如果该对象之前就存在键值为key的属性则不做任何操作,否则首先使用_.define赋值该属性,然后调用ob.observe目的是递归调用使得val值响应化。而convert函数的作用是将该属性转换成访问器属性getter/setter使得属性被访问或者被改变的时候我们能够监听到,具体我可以看一下convert函数的内部实现: p.convert = function (key, val) { var ob = this Object.defineProperty(ob.value, key, { enumerable: true, configurable: true, get: function () { if (Observer.emitGet) { ob.propagate('get', key) } return val }, set: function (newVal) { if (newVal === val) return ob.unobserve(val) val = newVal ob.observe(key, newVal) ob.emit('set:self', key, newVal) ob.propagate('set', key, newVal) } }) } 复制代码 convert函数的内部实现也不复杂,在get函数中,如果开启了全局的Observer.emitGet开关,在该属性被访问的时候,会对调用propagate触发本身以及父级的对应get事件。在set函数中,首先调用unobserve对之间的值接触响应化,接着调用ob.observe使得新赋值的数据响应化。最后首先触发本身的set:self事件,接着调用propagate触发本身以及父级的对应set事件。 $delete用于给删除对象的属性,如果不存在该属性则直接退出,否则先用delete操作符删除对象的属性,然后对外触发本身的delete:self事件,接着调用delete触发本身以及父级对应的delete事件。 看完了objectAgumentations之后,我们在Observer构造函数中知道,如果传入的参数中存在op.doNotAlterProto意味着不要改变对象的原型,则采用deepMixin函数将$add和$delete函数添加到对象中,否则采用函数arguments函数将$add和$delete添加到对象的原型中。最后调用了walk函数,让我们看看walk是内部是怎么实现的: p.walk = function (obj) { var key, val, descriptor, prefix for (key in obj) { prefix = key.charCodeAt(0) if ( prefix === 0x24 || // $ prefix === 0x5F // _ ) { continue } descriptor = Object.getOwnPropertyDescriptor(obj, key) // only process own non-accessor properties if (descriptor && !descriptor.get) { val = obj[key] this.observe(key, val) this.convert(key, val) } } } 复制代码 首先遍历obj中的各个属性,如果是以$或者_开头的属性名,则不做处理。接着获取该属性的描述符,如果不存在get函数,则对该属性值调用observe函数,使得数据响应化,然后调用convert函数将该属性转换成访问器属性getter/setter使得属性被访问或者被改变的时候能被够监听。 总结 到此为止,我们已经看完了整个Observer模块的所有代码,其实基本原理和我们之前设想都是差不多的,只不过Vue代码中各个函数分解粒度非常小,使得代码逻辑非常清晰。看到这里,我推荐你也clone一份Vue源码,checkout到对应的版本号,自己阅读一遍,跑跑测试用例,打个断点试着调试一下,应该会对你理解这个模块有所帮助。 最后如果对这个系列的文章感兴趣欢迎大家关注我的Github博客算是对我鼓励,感谢大家的支持! 原文发布时间:2018年07月01日 原文作者:请叫我王磊同学 本文来源掘金,如需转载请紧急联系作者
说起ajax,大家都不陌生。但是由于目前很多框架或者库等都对网络请求做了封装,导致了很多初学者只知其然而不知其所以然。所以今天我们就详细了解一下ajax的实现原理和封装ajax的关键步骤。 ajax的核心是XMLHttpRequest对象。首先我们先创建一个XMLHTTPRequest对象var xhr = new XMLHttpRequest();。 注意:本文所提及的内容不兼容古老的IE,有想了解的同学自行查阅ActiveXObject相关内容。 XMLHttpRequest 在使用XMLHttpRequest对象的第一步,我们首先要调用open方法来初始化请求参数,xhr.open('get','/test',true),虽然名字叫open,但是此时请求还并没有发送。 open(method, url[, async, username, password]) method:请求类型,例如GET,POST等 url:请求地址(这里有同源限制,就是我们经常会看到的跨域问题啦) async:是否发送异步请求。可选参数,默认为true。 username&password:可选参数,授权验证使用的,但是我们一般不这么用,使用后请求变成这个样子了,http(s)://username:password@url。 如果调用了open方法后再次对它进行调用,则相当于调用了abort方法,abort方法我们在后面介绍。 如果我们想为为请求绑定一些操作,这个时候就可以开始啦。常用的操作有如下几个: setRequestHeader(key, value) 顾名思义,这个方法用于设置请求头内容。 key:要设置的请求头名称 value:对应请求头的值 overrideMimeType(type) 重写服务器返回的MIME类型。通过这个方法可以告诉服务器你想要的数据类型。 注意:以上这些操作必须定义在send方法之前。否则,就拿setRequestHeader来说,你都把请求发出去了再设置还有什么用? 这个时候,我们就可以通过调用send 方法来发送请求了,xhr.send(null)。 send(data) 发送请求,如果是同步请求的话,会阻塞代码的执行,直至收到服务器响应才会继续。 data:发送给服务器的数据。为了兼容不同的浏览器,即使是不需要传数据,也需要传入参数null。 readyStateChanhe() 每次readyState的值改变的时候都会触发这个函数。 getResponseHeader(name) 获取指定响应头部的值,参数是响应头部的名称,并且不区分大小写。 getAllResponseHeaders() 获取服务器发送的所有HTTP响应的头部。 在这里我们穿插几个概念,readyState,这个属性表明了请求的状态,伴随HTTP请求的整个生命周期,它的值表明此时请求所处的阶段,具体如下: readyState 数值 描述 0 初始化,open()尚未调用 1 open()已经调用,但是send未调用 2 已获取到返回头信息 3 正在下载返回体信息 4 请求完成 还有几个较为常用的属性 名称 含义 responseText 响应的文本 status 响应的状态码 statusText 响应的状态信息 responseXML 响应内容是“text/xml”或者是“application/xml”格式的时候,这个属性的值就是响应数据的XMLDOM文档。 我们用下面这段代码做个测试 var xhr = new XMLHttpRequest(); console.log(xhr.readyState) xhr.onreadystatechange = function(){ console.log('------') console.log('readyState:' + xhr.readyState) console.log('ResponseHeaders:' + xhr.getAllResponseHeaders()) console.log('ResponseText:' + xhr.responseText.length) console.log('------') } xhr.open('get','/') xhr.send(null) 复制代码 下图我们可以直观的看到在创建了XMLHttpRequest对象的时候,readyState的值为0。 然后我们定义了onreadystatechange函数,让其打印一些属性,并调用open方法,此时readyState变为1。 最后我们调用send方法,可以看到经历了如下过程: send方法调用之后,readyState变为2,此时responseHeader已经获取到了,responseText为空; 响应数据开始下载,readyState变为3 响应数据下载结束,readyState变为4.我们可以发现此时responseText的长度比之前长。 abort() 取消响应,调用这个方法会终止已发送的请求。我们尝试在之前的代码最后加一句。 xhr.abort(); console.log(xhr.readyState); 复制代码 也就是说,send执行以后,并没有去尝试请求数据,而是直接取消掉了,并且我们发现abort会将readyState的值置为0。 除此之外,XMLHttpRequest还有一个很重要的属性withCredentials,cookie在同域请求的时候,会被自动携带在请求头中,但是跨域请求则不会,除非把withCredentials的值设为true(默认为false)。同时需要在服务端的响应头部中设置Access-Control-Allow-Credentials:true。不仅如此Access-Control-Allow-Origin的值也必须为当前页面的域名。 封装 到此为止,我们终于讲完了XMLHttpRequest的一些常用概念。接下来,我们尝试自己封装一个支持get和post的简易jax请求。 function ajax(url, option){ option = option || {}; var method = (option.method || 'GET').toUpperCase(), async = option.async === undefined ? true : option.async, params = handleParams(option.data); var xhr = new XMLHttpRequest(); if(async){ xhr.onreadystatechange = function () { if (xhr.readyState == 4){ callback(option,xhr); } }; } if (method === 'GET'){ xhr.open("GET",url + '?' + params, async); xhr.send(null) }else if (method === 'POST'){ xhr.open('POST', url, async); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.send(params); } if(!async){ callback(option,xhr); } function callback(opt,obj){ var status = obj.status; if (status >= 200 && status < 300 ){ opt.success && opt.success(obj.responseText,obj.responseXML); }else{ opt.fail && opt.fail(status); } } function handleParams(data) { var arr = []; for (var i in data) { arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(data[i])); } return arr.join('&'); } } // 测试调用 ajax('/xxx',{ method:'POST', data:{ key: 'test' }, success:function(){ console.log('success') }, fail:function(){ console.log('fail') } }); 复制代码 小结 其实ajax实现原理并不复杂,复杂的是容错、兼容性等的处理,使用的时候尽量使用库或者框架提供的封装,避免不必要的漏洞。 原文发布时间:2018年07月02日 原文作者:志如 本文来源掘金,如需转载请紧急联系作者
之前讲了个手写Promise,有人提出手写Promise很遗憾不能将promise.then写成microtask,只能用setTimeout来模拟异步,所以这次来讲一下microtask和macrotask 一、task 和 microtask 先理解一下task(宏任务又称macrotask)和microtask(微任务)。 task有setTimeout、setInterval、setImmediate(node,IE10+)、I/O(node)、UI rendering(网页UI View渲染)等 microtask有process.nextTick(node)、Promises.then(es6)、 Object.observe(废弃)、 MutationObserver(HTML5监听DOM变化) task 被放置于task queue(宏任务队列);microtask被放置于microtask queue(微任务队列) 在浏览器中,通俗来讲microtask queue 独立于task queue,有microtask就先执行microtask,没有就执行task,详细可阅读这篇文章 二、queue(队列)和stack(栈) 然后了解一下queue和stack queue(队列) 先进先出 stack(栈) 函数执行 先进后出 1->2->3依次执行后,从3->2->1释放(销毁) function one(){ console.log(1); function two(){ console.log(2); function three(){ console.log(3); } three(); } two(); } one(); 复制代码 三、浏览器的事件环 1、所有的同步任务全在主线程上执行,形成一个执行栈 2、主线程之外存在一个执行队列 主线程上的所有异步方法(ajax、setTimeout、setImmedaite、MessageChannel),都会在有结果后(回调),放置(回调函数)在任务队列上 3、主线程(执行栈)任务全部完成后,系统读取任务队列放入执行栈上依次执行 4、主线程从任务队列中读取事件,这个过程是循环的 四、node事件环 如图(画了好久。。) 每一个框 都是一个队列 第一个是定时器(timers)队列(执行完毕或最大数量时【队列清空后】,到下一个阶段) 第二个i/o tcp 流 错误处理(错误回调放入,忽略) 第三个内部队列(忽略忽略) 第四个poll轮询队列(文件操作),一直在这个阶段等待,等定时器到达时间,执行对应的回调 第五个check队列,执行node的task,检查setImmediate 第六个关闭例如tcp的队列(继续忽略) promise.then、process.nectTick之类的microtask,会在当每一个阶段切换时,执行 第一次执行,微任务会先执行一次,典型如proxess.nextTick() 1、 setTimeout(()=>{ console.log('timeout'); },0); Promise.resolve().then(data=>{ console.log('promise1'); }); setImmediate(()=>{ console.log('immediate') }); Promise.resolve().then(data=>{ console.log('promise2'); }); process.nextTick((()=>{ console.log('nectTick'); })); 复制代码 nectTick -> promise1 -> promise2 -> timeout -> immediate microtask先执行一次,nextTick最先执行,输出nextTick 再执行promise,输出promise1、promise2 执行timers队列,输出timeout 执行check队列,输出immediate 2、 setTimeout(()=>{ console.log('timeout1'); },0); setTimeout(()=>{ Promise.resolve().then(data=>{ console.log('promise'); }); console.log('timeout2'); },0); process.nextTick((()=>{ console.log('nectTick'); })); 复制代码 nectTick -> timeout1 -> timeout2 -> promise 因为在setTimeout执行完后到下一个阶段时,执行微任务 五、浏览器事件环和node事件环的区别 console.log('script start'); Promise.resolve().then(data=>{ console.log('promise1'); setTimeout(()=>{ console.log('timeout1'); },0); }); setTimeout(()=>{ console.log('timeout2'); Promise.resolve().then(data=>{ console.log('promise2'); }); },0); console.log('script end'); 复制代码 在浏览器中 先执行log,打印出script start 然后将promise1的then里面的回调函数存放于microtask queue中 再把timeout2的回调函数存放在task queue中 执行log,打印出script end 在队列执行完毕后 我们先执行microtask queue内的函数log,打印出promise1 然后将setTimeout2放入task queue microtask queue还有内容吗?没有了,我们就执行task queue 执行task queue的setTimeout2的回调,打印出timeout2 setTimeout2的回调又声明了promise2,我们就把promise2的then存放于microtask queue中 有microtask就执行microtask 所以先执行microtask queue中的回调log,打印出promise2 最后的最后,执行task queue中的回调log,打印出tiemout1 在node中 先打印出script start,promise1回调放入microtask queue,timeout2回调放入task queue,打印出script end 然后和浏览器一样执行microtask queue内的函数log,打印出promise1 将setTimeout2放入task queue 执行task queue的setTimeout2的回调,打印出timeout2 将promise2的then存放于microtask queue中 在node中,此时timers执行完了吗?并没有 所以执行task queue的回调log,打印出timeout1 最后在队列切换(timers切换到下一队列)时,执行microtask queue的回调log,打印出promise2 但是多试几次,发现结果可能有两种 这是为什么呢? 我们来调试(by vsCode)一下,在setTimeout和promise上打上共6个断点。 执行后如图所示 很明显代码调试已证明timeout1先于promise2的then的回调函数执行 造成timeout1和promise2的原因是无法比较then的回调和timeout1的执行速度,所以这么写是没有意义的。 如以下代码 Promise.resolve().then(data => { console.log('p1'); setTimeout(() => { console.timeEnd('t1'); },0); }); setTimeout(() => { console.log('t2'); console.time('t1'); console.time('p2'); Promise.resolve().then(data => { console.timeEnd('p2'); }); },0); 复制代码 结果只和执行速度有关,没有可比性 六、node的其他task node中的task有setTimeout、setInterval、setImmediate、I/O等 下面是个经典面试题 const fs = require('fs'); fs.readFile('./1.txt','utf8',(err,data)=>{ console.log('success'); setTimeout(()=>{ console.log('timeout'); },0); setImmediate(()=>{ console.log('immediate'); }); }) 复制代码 这段代码执行顺序为 success -> immediate -> timeout。 因为在node事件环内在fs所在的poll队列后,只能执行check队列(setImmediate),只有在下一个循环,才走timers队列。 七、总结 1、浏览器中,有microtask,优先执行microtask,没有就执行task。 2、node中,由于node事件环,microtask只在改变队列时执行。task按照node事件环执行 原文发布时间:2018年06月29日 原文作者:卡姆爱卡姆 本文来源掘金,如需转载请紧急联系作者
偶然的一个周末复习了一下 JS 的模块标准,刷新了一下对 JS 模块化的理解。 从开始 Coding 以来,总会周期性地突发奇想进行 Code Review。既是对一段时期的代码进行总结,也是对那一段时光的怀念。 距离上一次 Review 已经过去近两个月,这次竟然把两年前在源续写的代码翻了出来,代码杂乱无章的程度就像那时更加浮躁的自己,让人感慨时光流逝之快。 话不多说,直接上码。 当时在做的是一个境外电商项目(越南天宝商城),作为非 CS 的新手程序员,接触 Coding 时间不长和工程化观念不强,在当时的项目中出现了这样的代码: import.js: 这段代码看起来就是不断地从 DOM 中插进 CSS 和 JS,虽然写得很烂,但是很能反映以前的 Web 开发方式。 在 Web 开发中,有一个原则叫“关注点分离(separation of concerns)“,意思是各种技术只负责自己的领域,不互相耦合混合在一起,所以催生出了 HTML、CSS 和 JavaScript。 其中,在 Web 中负责逻辑和交互 的 JavaScript,是一门只用 10 天设计出来的语言,虽然借鉴了许多优秀静态和动态语言的优点,但却一直没有模块 ( module ) 体系。这导致了它将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的 require、Python 的 import,甚至就连 CSS 都有 @import,但是 JavaScript 任何这方面的支持都没有。而且 JS 是一种加载即运行的技术,在页面中插入脚本时还需要考虑库的依赖,JS 在这方面的缺陷,对开发大型的、复杂的项目形成了巨大障碍。 发展过程 虽然 JS 本身并不支持模块化,但是这并不能阻挡 JS 走向模块化的道路。既然本身不支持,那么就从代码层面解决问题。活跃的社区开始制定了一些模块方案,其中最主要的是 CommonJS 和 AMD,ES6 规范出台之后,以一种更简单的形式制定了 JS 的模块标准 (ES Module),并融合了 CommonJS 和 AMD 的优点。 大致的发展过程: CommonJS(服务端) => AMD (浏览器端) => CMD / UMD => ES Module CommonJS 规范 2009年,Node.js 横空出世,JS 得以脱离浏览器运行,我们可以使用 JS 来编写服务端的代码了。对于服务端的 JS,没有模块化简直是不能忍。 CommonJs (前 ServerJS) 在这个阶段应运而生,制定了 Module/1.0 规范,定义了第一版模块标准。 标准内容: 模块通过变量 exports 来向外暴露 API,exports 只能是一个对象,暴露的 API 须作为此对象的属性。 定义全局函数 require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴露出来的 API。 如果被 require 函数引入的模块中也包含依赖,那么依次加载这些依赖。 特点: 模块可以多次加载,首次加载的结果将会被缓存,想让模块重新运行需要清除缓存。 模块的加载是一项阻塞操作,也就是同步加载。 它的语法看起来是这样的: // a.js module.exports = { moduleFunc: function() { return true; }; } // 或 exports.moduleFunc = function() { return true; }; // 在 b.js 中引用 var moduleA = require('a.js'); // 或 var moduleFunc = require('a.js').moduleFunc; console.log(moduleA.moduleFunc()); console.log(moduleFunc()) 复制代码 AMD 规范(Asynchromous Module Definition) CommonJS 规范出现后,在 Node 开发中产生了非常良好的效果,开发者希望借鉴这个经验来解决浏览器端 JS 的模块化。 但大部分人认为浏览器和服务器环境差别太大,毕竟浏览器端 JS 是通过网络动态依次加载的,而不是像服务端 JS 存在本地磁盘中。因此,浏览器需要实现的是异步模块,模块在定义的时候就必须先指明它所需要依赖的模块,然后把本模块的代码写在回调函数中去执行,最终衍生出了 AMD 规范。 AMD 的主要思想是异步模块,主逻辑在回调函数中执行,这和浏览器前端所习惯的开发方式不谋而合,RequireJS 应运而生。 标准内容: 用全局函数 define 来定义模块,用法为:define(id?, dependencies?, factory); id 为模块标识,遵从 CommonJS Module Identifiers 规范 dependencies 为依赖的模块数组,在 factory 中需传入形参与之一一对应,如果 dependencies 省略不写,则默认为 ["require", "exports", "module"] ,factory 中也会默认传入 require, exports, module,与 ComminJS 中的实现保持一致 如果 factory 为函数,模块对外暴露 API 的方法有三种:return 任意类型的数据、exports.xxx = xxx 或 module.exports = xxx 如果 factory 为对象,则该对象即为模块的返回值 特点: 前置依赖,异步加载 便于管理模块之间的依赖性,有利于代码的编写和维护。 它的用法看起来是这样的: // a.js define(function (require, exports, module) { console.log('a.js'); exports.name = 'Jack'; }); // b.js define(function (require, exports, module) { console.log('b.js'); exports.desc = 'Hello World'; }); // main.js require(['a', 'b'], function (moduleA, moduleB) { console.log('main.js'); console.log(moduleA.name + ', ' + moduleB.desc); }); // 执行顺序: // a.js // b.js // main.js 复制代码 人无完人,AMD/RequireJS 也存在饱受诟病的缺点。按照 AMD 的规范,在定义模块的时候需要把所有依赖模块都罗列一遍(前置依赖),而且在使用时还需要在 factory 中作为形参传进去。 define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){ ..... }); 复制代码 看起来略微不爽 ... RequireJS 模块化的顺序是这样的:模块预加载 => 全部模块预执行 => 主逻辑中调用模块,所以实质是依赖加载完成后还会预先一一将模块执行一遍,这种方式会使得程序效率有点低。 所以 RequireJS 也提供了就近依赖,会在执行至 require 方法才会去进行依赖加载和执行,但这种方式的用户体验不是很好,用户的操作会有明显的延迟(下载依赖过程),虽然可以通过各种 loading 去解决。 // 就近依赖 define(function () { setTimeout(function () { require(['a'], function (moduleA) { console.log(moduleA.name); }); }, 1000); }); 复制代码 CMD 规范(Common Module Definition) AMD/RequireJS 的 JS 模块实现上有很多不优雅的地方,长期以来在开发者中广受诟病,原因主要是不能以一种更好的管理模块的依赖加载和执行,虽然有不足的地方,但它提出的思想在当时是非常先进的。 既然优缺点那么必然有人出来完善它,SeaJS 在这个时候出现。 SeaJS 遵循的是 CMD 规范,CMD 是在 AMD 基础上改进的一种规范,解决了 AMD 对依赖模块的执行时机处理问题。 SeaJS 模块化的顺序是这样的:模块预加载 => 主逻辑调用模块前才执行模块中的代码,通过依赖的延迟执行,很好解决了 RequireJS 被诟病的缺点。 SeaJS 用法和 AMD 基本相同,并且融合了 CommonJS 的写法: // a.js define(function (require, exports, module) { console.log('a.js'); exports.name = 'Jack'; }); // main.js define(function (require, exports, module) { console.log('main.js'); var moduleA = require('a'); console.log(moduleA.name); }); // 执行顺序 // main.js // a.js 复制代码 除此之外,SeaJS 还提供了 async API,实现依赖的延迟加载。 // main.js define(function (require, exports, module) { var moduleA = require.async('a'); console.log(moduleA.name); }); 复制代码 SeaJS 的出现,貌似以一种比较完美的形式解决了 JS 模块化的问题,是 CommonJS 在浏览器端的践行者,并吸收了 RequestJS 的优点。 ES Module ES Module 是目前 web 开发中使用率最高的模块化标准。 随着 JS 模块化开发的呼声越来越高,作为 JS 语言规范的官方组织 ECMA 也开始将 JS 模块化纳入 TC39 提案中,并在 ECMAScript 6.0 中得到实践。 ES Module 吸收了其他方案的优点并以更优雅的形式实现模块化,它的思想是尽量的静态化,即在编译时就确定所有模块的依赖关系,以及输入和输出的变量,和 CommonJS 和 AMD/CMD 这些标准不同的是,它们都是在运行时才能确定需要依赖哪一些模块并且执行它。ES Module 使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,实现一些只能靠静态分析实现的功能(比如引入宏(macro)和类型检验(type system)。 标准内容: 模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。 通过 export 命令定义了模块的对外接口,其他 JS 文件就可以通过 import 命令加载这个模块。 ES Module 可以有多种用法: 模块的定义: /** * export 只支持对象形式导出,不支持值的导出,export default 命令用于指定模块的默认输出, * 只支持值导出,但是只能指定一个,本质上它就是输出一个叫做 default 的变量或方法 */ // 写法 1 export var m = 1; // 写法 2 var m = 1; export { m }; // 写法 3 var n = 1; export { n as m }; // 写法 4 var n = 1; export default n; 复制代码 模块的引入: // 解构引入 import { firstName, lastName, year } from 'a-module'; // 为输入的变量重新命名 import { lastName as surname } from 'a-module'; // 引出模块对象(引入所有) import * as ModuleA from 'a-module'; 复制代码 在使用 ES Module 值得注意的是:import 和 export 命令只能在模块的顶层,在代码块中将会报错,这是因为 ES Module 需要在编译时期进行模块静态优化,import 和 export 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,这种设计有利于编译器提高效率,但也导致无法在运行时加载模块(动态加载)。 对于这个缺点,TC39 有了一个新的提案 -- Dynamic Import,提案的内容是建议引入 import() 方法,实现模块动态加载。 // specifier: 指定所要加载的模块的位置 import(specifier) 复制代码 import() 方法返回的是一个 Promise 对象。 import('b-module') .then(module => { module.helloWorld(); }) .catch(err => { console.log(err.message); }); 复制代码 import() 函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import() 函数与所加载的模块没有静态连接关系,这点也是与 import 语句不相同。import() 类似于 Node 的 require 方法,区别主要是前者是异步加载,后者是同步加载。 通过 import 和 export 命令以及 import() 方法,ES Module 几乎实现了 CommonJS/AMD/CMD 方案的所有功能,更重要的是它是作为 ECMAScript 标准出现的,带有正统基因,这也是它在现在 Web 开发中广泛应用的原因之一。 但 ES Module 是在 ECMAScript 6.0 标准中的,而目前绝大多数的浏览器并直接支持 ES6 语法,ES Module 并不能直接使用在浏览器上,所以需要 Babel 先进行转码,将 import 和 export 命令转译成 ES2015 语法才能被浏览器解析。 总结 JS 模块化的出现使得前端工程化程度越来越高,让使用 JS 开发大型应用成为触手可及的现实(VScode)。纵观 JS 模块化的发展,其中很多思想都借鉴了其他优秀的动态语言(Python),然后结合 JS 运行环境的特点,衍生出符合自身的标准。但其实在本质上,浏览器端的 JS 仍没有真正意义上的支持模块化,只能通过工具库(RequireJS、SeaJS)或者语法糖(ES Module)去 Hack 实现模块化。随着 Node 前端工程化工具的繁荣发展(Grunt/Gulp/webpack),使我们可以不关注模块化的实现过程,直接享受 JS 模块化编程的快感。 在复习 JS 模块化的过程中,对 Webpack 等工具的模块化语法糖转码产生了新的兴趣,希望有时间可以去分析一下模块化的打包机制和转译代码,然后整理出来加深一下自己对模块化实现原理的认识和理解。 期待下一篇。 参考文章: ECMAScript 6 入门 -- 阮一峰 JS 模块化历程 原文发布时间:2018年06月28日 原文作者:Yanjiie 本文来源掘金,如需转载请紧急联系作者
摘要 长文 阅读约需十分钟 跟着走一遍需要一小时以上 约100行代码 前段时间打算写一个给手机端用的假冒控制台 可以用来看console的输出 这一块功能目前已经完成了 但是后来知道有一个腾讯团队的项目vConsole 参考了一下发现他的功能丰富了很多 可以看Network面板等 虽然是阉割过的面板 不过还是可以看到一些网络相关的重要信息 所以自己也想着加上这个Network面板功能 想实现这个功能呢 想到过几种方案 一种是封装一个ajax 但是这种不太现实 要让别人都用自己封装的ajax 对于项目中途需要引入的情况十分不友好 需要改动大量代码 所以这种方案是不可取的 之后选择的方案 就像这里就像实现console面板一样 拦截了console下面的方法 而拦截console对象下的方法 实际上就是重写了他的方法 比如console.log 实际上就是log方法进行了重写 在真正执行log前 先做一些自己想做的事情 思路 这一块一开始想到的其实是XMLHrrpRequest对象有全局的勾子函数 查阅了文档似乎没有发现相关的内容 后来搜索了一些别的实现方式 大多数就是辣鸡站不知道从哪里爬来的内容 也没什么有价值的东西 后来在github上找到了一个仓库(Ajax-hook) 这个库实现的是可以拦截全局的Ajax请求 可以同同名方法或者属性作为勾子 达到拦截的效果 部分内容参考了他的实现思想 实际上实现方式和自定义的console很像 底层运行依然靠原生的xhr对象 但是在上层做了代理 重写了XMLHttpRequest对象 大体实现思路可以列成下面这几步 保留原生xhr对象 将XMLHttpRequest对象置为新的对象 对xhr对象的方法进行重写后放入到新的对象中 对属性进行重写 然后放入到新对象中 重写方法 做的主要就是在执行原生的方法前 提供一个钩子 来执行自定义的内容 重写属性 主要是一些事件触发的属性 比如onreadystatechange 同时还有别的坑点 xhr对象上很多属性是只读的(这一点也是通过Ajax-hook了解到的) 如果在钩子中对只读属性进行修改了 那在往下走也是没有用的 因为压根没有修改成功 不过既然大致思路有了 那就尝试着实现一下吧 实现 实现之前 先想一下怎么使用吧 这样实现过程也会有一个大致的方向 先起个名字 就叫他Any-XHR了 使用的时候通过new AnyXHR()来创建实例 可以在构造函数中来进行钩子的添加 钩子有两种 一种是before(执行前调用)的钩子 一种是after(执行后调用)的钩子 就像这样 构造函数接受两个参数 都是对象 第一个是调用前执行的钩子 另一种是调用后 对象里面的key对应了原生xhr的方法和属性 保持一致即可 let anyxhr = new AnyXHR({ send: function(...args) { console.log('before send'); } }, { send: function(...args) { console.log('after send'); } }); 复制代码 构造函数 有了目标 现在就来实现一下吧 按照所想的 我们要用new关键字来实例化一个对象 就要用构造函数或者class关键字创建一个类 这里随口提一下 ES6的提供的class并不是真正 类似Java或者C++中的类 可以理解成是一种语法糖 本质上还是原型继承 或者说是基于原型代理的 理解的大佬这句话跳过就好 这里采用class关键字来声明 class AnyXHR { constructor(hooks = {}, execedHooks = {}) { this.hooks = hooks; this.execedHooks = execedHooks; } init() { // 初始化操作... } overwrite(proxyXHR) { // 处理xhr对象下属性和方法的重写 } } 复制代码 这里目前只关注构造函数 构造函数接受的两个对象 就是表示执行前的钩子和执行后的钩子 对应的挂到实例的hooks和execedHooks属性上 按照刚才说的步骤 要保留原生的xhr对象 把这个步骤丢到构造函数中 然后做一些初始化操作 初始化的时候要对xhr对象重写 constructor(hooks = {}, execedHooks = {}) { + this.XHR = window.XMLHttpRequest; this.hooks = hooks; this.execedHooks = execedHooks; + this.init(); } 复制代码 init 方法 init里 要做的就是对XMLHttpRequest进行重写 一个xhr实例的每个方法和属性进行重写 这样在new XMLHttpRequest()的时候 new出来的就是我们自己重写的xhr实例了 init() { window.XMLHttpRequest = function() { } } 复制代码 像这样 把XMLHttpRequest赋值为一个新的函数 这样XMLHttpRequest就不是原来的那个了 用户全局访问到的就是我们自己写的这个新的函数 接着就是去实现重写像open、onload、send这些原生xhr实例的方法和属性了 可是怎么重写呢 现在是个空函数 怎么让用户在new XMLHttpRequest后 可以使用send、open方法呢 其实之前有提到了 重写方法 做的主要就是在执行原生的方法前 提供一个钩子 来执行自定义的内容 既然要执行原生的方法 那我们还是需要用到原生的xhr对象 就拿open来说 在用户new XMLHttpRequest的同时 需要再new一个我们保留下来的 原生的XMLHttpRequest对象 然后在自己重写的XMLHttpRequest实例上 挂一个send方法 他做的 就是先执行自定义的内容 完了之后 去执行我们new出来的 保留下来的XMLHttpRequest对象的实例下的open方法 同时把参数传递过去即可 听起来有点绕 可以画画图再细细品味一下 init() { + let _this = this; window.XMLHttpRequest = function() { + this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例 + this.overwrite(this); } } 复制代码 这样在用户new XMLHttpRequest的时候 内部就创建一个保留下来的 原生的XMLHttpRequest实例 比如当用户调用send方法的时候 我们先执行自己想要做的事情 然后再调用this._xhr.send 执行原生xhr实例的send方法就可以了 是不是很简单 完了之后我们进入overwrite方法 这个方法做的事情就是上一句所说的 对每个方法和属性进行重写 其实就是在执行之前和执行之后动点手脚而已 调用_this.overwrite的时候我们需要把this传递过去 这里的this 指向的是new XMLHttpRequest后的实例 属性和方法都是要挂到实例上的 供用户调用 所以把this传递过去 方便操作 这里在用新函数覆盖window.XMLHttpRequest的时候 不可以使用箭头函数 箭头函数不能当作构造函数来使用 所以这里要用this保留的做法 overwrite 方法 要重写方法和属性 要重写的就是send、open、responseText、onload、onreadystatechange等这些 那要一个一个实现吗... 肯定是不现实的 要重写的方法和属性 我们可以通过便利一个原生的xhr实例来获取 原生的xhr实例 已经保留在了覆盖后的XMLHttpRequest对象的实例的_xhr属性上 所以通过遍历_xhr这个原生的xhr实例 就可以拿到需要所有要重写的方法和属性 的key和value overwrite(proxyXHR) { + for (let key in proxyXHR._xhr) { + } } 复制代码 这里的proxyXHR参数指向的是一个修改后的XMLHttpRequest实例 方法和属性要挂到实例上 现对proxyXHR下的_xhr属性进行遍历 可以拿到他下面的所有可遍历的属性和方法 然后区分方法和属性 做不同的处理即可 overwrite(proxyXHR) { for (let key in proxyXHR._xhr) { + if (typeof proxyXHR._xhr[key] === 'function') { + this.overwriteMethod(key, proxyXHR); + continue; + } + this.overwriteAttributes(key, proxyXHR); } } 复制代码 通过typeof来判断当前遍历到的是属性还是方法 如果是方法 则调用overwriteMethod 对这个方法进行改造重写 如果是属性 则通过overwriteAttributes对这个属性进行改造重写 同时把遍历到的属性名和方法名 以及修改后的XMLHttpRequest实例传递过去 那接下来就来实现一下这里两个方法 overwriteMethod 在类中添加这个方法 class AnyXHR { + overwriteMethod(key, proxyXHR) { // 对方法进行重写 + } } 复制代码 这个方法做的 就是对原生的xhr实例下的方法进行重写处理 其实严格来说 把这个操作称作重写是不严格的 就拿send方法来说 并不会对原生的xhr实例下的send方法进行修改 而写新写一个方法 挂到自己实现的xhr实例上 来代替原生的xhr实例来执行 最终send的过程 还是调用原生的send方法 只是在调用前和调用后 多做了两件别的事情 所以这里就是对每个方法 做一个包装 overwriteMethod(key, proxyXHR) { + let hooks = this.hooks; + let execedHooks = this.execedHooks; + proxyXHR[key] = (...args) => { + } } 复制代码 首先保留了hooks和execedHooks 等下会频繁用到 然后我们往新的xhr实例上挂上同名的方法 比如原生的xhr有个send 遍历到send的时候 这里进来的key就是send 所以就会往新的xhr实例上挂上一个send 来替代原生的send方法 当方法被调用的时候 会拿到一堆参数 参数是js引擎(或者说浏览器)丢过来的 这里用剩余参数把他们都接住 组成一个数组 调用钩子的时候 或者原生方法的时候 可以再传递过去 那这里面具体做些什么操作呢 其实就三步 如果当前方法有对应的钩子 则执行钩子 执行原生xhr实例中对应的方法 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行 overwriteMethod(key, proxyXHR) { let hooks = this.hooks; let execedHooks = this.execedHooks; proxyXHR[key] = (...args) => { + // 如果当前方法有对应的钩子 则执行钩子 + if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) { + return; + } + // 执行原生xhr实例中对应的方法 + const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args); + // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行 + execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res); + return res; } } 复制代码 首先第一步 hooks[key]就是判断当前的方法 有没有对应的钩子函数 hooks对象里面保存的就是所有的钩子函数 或者说需要拦截的方法 是在我们执行new AnyXHR(..)的时候传进来的 如果有 则执行 并把参数传递给钩子 如果这个钩子函数返回了false 则中止 不往下走了 这样可以起到拦截的效果 否则就往下走 执行原生xhr实例中对应的方法 原生的xhr实例放在了_xhr这个属性里 所以通过proxyXHR._xhr[key]就可以访问到 同时把参数用apply拍散 传递过去就好了 同时接住返回值 完了之后走第三步 看看有没有执行完原生xhr方法后还要执行的钩子 如果有则执行 然后把返回值传递过去 之后返回原生xhr实例对应的方法执行后反过来的返回值就好了 到这了方法的代理、拦截就完成了 可以去尝试一下了 记得注释一下 this.overwriteAttributes(key, proxyXHR); 这一行 第一次调试 虽然到现在代码不多 不过一下子没绕过来 还是很累的 可以先倒杯水休息一下 到目前为止的完整代码如下 class AnyXHR { constructor(hooks = {}, execedHooks = {}) { this.XHR = window.XMLHttpRequest; this.hooks = hooks; this.execedHooks = execedHooks; this.init(); } init() { let _this = this; window.XMLHttpRequest = function() { this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例 _this.overwrite(this); } } overwrite(proxyXHR) { for (let key in proxyXHR._xhr) { if (typeof proxyXHR._xhr[key] === 'function') { this.overwriteMethod(key, proxyXHR); continue; } // this.overwriteAttributes(key, proxyXHR); } } overwriteMethod(key, proxyXHR) { let hooks = this.hooks; let execedHooks = this.execedHooks; proxyXHR[key] = (...args) => { // 如果当前方法有对应的钩子 则执行钩子 if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) { return; } // 执行原生xhr实例中对应的方法 const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args); // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行 execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res); return res; } } } 复制代码 尝试一下第一次调试 new AnyXHR({ open: function () { console.log(this); } }); var xhr = new XMLHttpRequest(); xhr.open('GET', '/abc?a=1&b=2', true); xhr.send(); 复制代码 可以打开控制台 看看是否有输出 同时可以观察一下对象 和_xhr属性下内容坐下对比看看 overwriteAttributes 方法 这个方法实现稍微麻烦些 内容也会绕一些 可能有一个想法 为什么要监听 或者说代理 再或者说给属性提供钩子呢 有什么用吗 像responseText这种属性 其实不是目标 目标是给像onreadystatechange、onload这样的属性包装一层 这些属性有点类似事件 或者说就是事件 使用的时候需要手动给他们赋值 在对应的时刻会自动调用 可以说是原生xhr提供的钩子 像send、open 可以在请求发出去的时候拦截 而onreadystatechange、onload 可以用来拦截服务的响应的请求 所以有必要对这些属性进行包装 知道了为什么需要对属性做个包装 问题就到了怎么去包装属性了 那onload做例子 xhr.onload = function() { ... }; 复制代码 在用户这样赋值的时候 我们应该做出一些响应 捕获到这个赋值的过程 然后去看看 hooks这个数组中有没有onload的钩子呀 如果有的话 那就在执行原生的xhr实例的onload之前 执行一下钩子就ok了 那问题来时 普通的属性怎么办 比如responseType 那这些属性就不处理了 直接挂到新的xhr实例上去 又有问题了 怎么区分普通的属性 和事件一样的属性呢 其实观察一下就知道 on打头的属性 就是事件一样的属性了 所以总结一下 看看属性是不是on打头 如果不是 直接挂上去 如果是 则看看有没有要执行的钩子 如果有 则包装一下 先执行钩子 再执行本体 如果没有 责直接赋值挂上去 逻辑理清楚了 是不是发现很简单 好 那就动手吧 ??等等 怎么监听用户给onload这样的属性赋值啊??? 可以停一下 仔细想想 这一块就可以用到ES5中提供的 getter和setter方法 这个知识点肯定是很熟悉的 不熟悉可以去翻一下MDN 通过这两个方法 就可以监听到用户对一个属性赋值和取值的操作 同时可以做一些额外的事情 那怎么给要插入的属性设置get/set方法呢 ES5提供了Object.defineProperty方法 可以给一个对象定义属性 同时可以指定她的属性描述符 属性描述符中可以描述一个属性是否可写 是否可以枚举 还有他的set/get等 当然使用字面量的方式 为一个属性定义get/set方法也是可以的 扯了这么多 那就实现一下这个方法吧 overwriteAttributes(key, proxyXHR) { Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR)); } 复制代码 这里就一行代码 就是用Object.defineProperty给自己实现的xhr实例上挂个属性 属性名就是传递过来的key 然后用setProperyDescriptor方法生成属性描述符 同时把key和实例传递过去 描述符里面会生成get/set方法 也就是这个属性赋值和取值时候的操作 setProperyDescriptor 方法 同样的 往类里面加入这个方法 setProperyDescriptor(key, proxyXHR) { } 复制代码 可以拿到要添加的属性名(key)和自己实现的xhr对象实例 属性都要挂到这个实例上 setProperyDescriptor(key, proxyXHR) { + let obj = Object.create(null); + let _this = this; } 复制代码 属性描述符实际上是个对象 这里用Object.create(null)来生成一个绝对干净的对象 防止有一些乱起八糟的属性出现 阴差阳错变成描述 然后保留一下this 之后就实现一下set setProperyDescriptor(key, proxyXHR) { let obj = Object.create(null); let _this = this; + obj.set = function(val) { + } } 复制代码 set方法会在属性被赋值的时候(比如obj.a = 1)被调用 他会拿到一个参数 就是赋值的值(等号右边的值) 然后在里面 就可以做之前罗列的步骤了 看看属性是不是on打头 如果不是 直接挂上去 如果是 则看看有没有要执行的钩子 如果有 则包装一下 先执行钩子 再执行本体 如果没有 责直接赋值挂上去 setProperyDescriptor(key, proxyXHR) { let obj = Object.create(null); let _this = this; obj.set = function(val) { + // 看看属性是不是on打头 如果不是 直接挂上去 + if (!key.startsWith('on')) { + proxyXHR['__' + key] = val; + return; + } + // 如果是 则看看有没有要执行的钩子 + if (_this.hooks[key]) { + // 如果有 则包装一下 先执行钩子 再执行本体 + this._xhr[key] = function(...args) { + (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args)); + } + return; + } + // 如果没有 责直接赋值挂上去 + this._xhr[key] = val; } + obj.get = function() { + return proxyXHR['__' + key] || this._xhr[key]; + } + return obj; } 复制代码 第一步的时候 判断了是不是on打头 不是则原模原样挂到实例上 然后去看看当前的这个属性 在钩子的列表中有没有 有的话 就把要赋的值(val)和钩子一起打包 变成一个函数 函数中先执行钩子 再执行赋的值 只要用的人不瞎鸡儿整 那这里的值基本是函数没跑 所以不判断直接调用 同时参数传递过去 如果没有钩子呢 则直接赋值就好了 这样set方法就ok了 get方法就简单一些了 就是拿值而已 可能有一个地方不太能理解 就是proxyXHR['__' + key] = val; get方法中也有 为什么这里有个__前缀 其实这样 考虑这么一个场景 在拦截请求返回的时候 可以拿到responseText属性 这个属性就是服务端返回的值 可能在这个时候会需要根据responseType统一的处理数据类型 如果是JSON 那就this.responseText = JSON.parse(this.response) 然后就满心欢喜的去成功的回调函数中拿responseText 结果在对他进行属性或者方法访问的时候报错了 打印一下发现他还是字符串 并没有转成功 其实就是因为responseText是只读的 在这个属性的标签中 writable是false 所以可以用到一个代理属性来解决 比如this.responseText = JSON.parse(this.responseText)的时候 首先根据get方法 去拿responseText 这个时候还没有__responseText属性 所以回去原生的xhr实例拿 拿到的就是服务端回来的值 然后经过解析后 又被赋值 复制的时候 在自己实现的xhr实例中 就会多一个__responseText属性 他的值是经过处理后的 那之后再通过responseText取值 通过get方法拿到的就是__responseText的值 这样就通过一层属性的代理 解决了原生xhr实例属性只读的问题 这样大部分逻辑都完成了 记得把前面this.overwriteAttributes(key, proxyXHR);的注释去掉 第二次调试 到这里被绕晕是有可能的 不用太在意 仔细理一下画画图就好了 倒杯水冷静一下 这是到目前为止的完整代码 可以去尝试一下 class AnyXHR { constructor(hooks = {}, execedHooks = {}) { this.XHR = window.XMLHttpRequest; this.hooks = hooks; this.execedHooks = execedHooks; this.init(); } init() { let _this = this; window.XMLHttpRequest = function () { this._xhr = new _this.XHR(); // 在实例上挂一个保留的原生的xhr实例 _this.overwrite(this); } } overwrite(proxyXHR) { for (let key in proxyXHR._xhr) { if (typeof proxyXHR._xhr[key] === 'function') { this.overwriteMethod(key, proxyXHR); continue; } this.overwriteAttributes(key, proxyXHR); } } overwriteMethod(key, proxyXHR) { let hooks = this.hooks; let execedHooks = this.execedHooks; proxyXHR[key] = (...args) => { // 如果当前方法有对应的钩子 则执行钩子 if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) { return; } // 执行原生xhr实例中对应的方法 const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args); // 看看还有没有原生xhr实例对应的方法执行后需要执行的钩子 如果有则执行 execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res); return res; } } setProperyDescriptor(key, proxyXHR) { let obj = Object.create(null); let _this = this; obj.set = function (val) { // 看看属性是不是on打头 如果不是 直接挂上去 if (!key.startsWith('on')) { proxyXHR['__' + key] = val; return; } // 如果是 则看看有没有要执行的钩子 if (_this.hooks[key]) { // 如果有 则包装一下 先执行钩子 再执行本体 this._xhr[key] = function (...args) { (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args)); } return; } // 如果没有 责直接赋值挂上去 this._xhr[key] = val; } obj.get = function () { return proxyXHR['__' + key] || this._xhr[key]; } return obj; } overwriteAttributes(key, proxyXHR) { Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR)); } } 复制代码 调用 new AnyXHR({ open: function () { console.log('open'); }, onload: function () { console.log('onload'); }, onreadystatechange: function() { console.log('onreadystatechange'); } }); $.get('/aaa', { b: 2, c: 3 }).done(function (data) { console.log(1); }); var xhr = new XMLHttpRequest(); xhr.open('GET', '/abc?a=1&b=2', true); xhr.send(); xhr.onreadystatechange = function() { console.log(1); } 复制代码 引入一下jquery 可以尝试着拦截 观察一下控制台 就能看到结果了 接下来还有一些内容需要完成 让整个类更完善 不过剩下的都是很简单的内容了 单例 因为是全局拦截 而且全局下只有一个XMLHttpRequest对象 所以这里应该设计成单例 单例是设计模式中的一种 其实没那么玄乎 就是全局下只有一个实例 也就是说得动点手脚 怎么new AnyXHR拿到的都是同一个实例 修改一下构造函数 constructor(hooks = {}, execedHooks = {}) { // 单例 + if (AnyXHR.instance) { + return AnyXHR.instance; + } this.XHR = window.XMLHttpRequest; this.hooks = hooks; this.execedHooks = execedHooks; this.init(); + AnyXHR.instance = this; } 复制代码 进入构造函数 先判断AnyXHR上有没有挂实例 如果有 则直接返回实例 如果没有 则进行创建 然后创建流程走完了之后 把AnyXHR上挂个实例就好了 这样不管怎么new 拿到的都是都是同一个实例 同时再加一个方法 可以方便拿到实例 getInstance() { return AnyXHR.instance; } 复制代码 动态加入钩子 所有的钩子都维护在两个对象内 每一次方法的执行 都会去读这两个对象 所以只要让对象发生改变 就能动态的加钩子 所以加入一个add方法 add(key, value, execed = false) { if (execed) { this.execedHooks[key] = value; } else { this.hooks[key] = value; } return this; } 复制代码 其中key value两个参数对应的就是属性名 或者方法名和值 execed代表是不是原生的方法执行后再执行 这个参数用来区分添加到哪个对象中 同样的道理 去掉钩子和清空钩子就很简单了 去掉钩子 rmHook(name, isExeced = false) { let target = (isExeced ? this.execedHooks : this.hooks); delete target[name]; } 复制代码 清空钩子 clearHook() { this.hooks = {}; this.execedHooks = {}; } 复制代码 取消全局的监听拦截 这一步其实很简单 把我们自己实现的 重写的XMLHttpRequest变成原来的就好了 原来的我们保留在了this.XHR上 unset() { window.XMLHttpRequest = this.XHR; } 复制代码 重新监听拦截 既然是单例 重新开启监听 那只要把单例清了 重新new就好了 reset() { AnyXHR.instance = null; AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks); } 复制代码 完整代码 class AnyXHR { /** * 构造函数 * @param {*} hooks * @param {*} execedHooks */ constructor(hooks = {}, execedHooks = {}) { // 单例 if (AnyXHR.instance) { return AnyXHR.instance; } this.XHR = window.XMLHttpRequest; this.hooks = hooks; this.execedHooks = execedHooks; this.init(); AnyXHR.instance = this; } /** * 初始化 重写xhr对象 */ init() { let _this = this; window.XMLHttpRequest = function() { this._xhr = new _this.XHR(); _this.overwrite(this); } } /** * 添加勾子 * @param {*} key * @param {*} value */ add(key, value, execed = false) { if (execed) { this.execedHooks[key] = value; } else { this.hooks[key] = value; } return this; } /** * 处理重写 * @param {*} xhr */ overwrite(proxyXHR) { for (let key in proxyXHR._xhr) { if (typeof proxyXHR._xhr[key] === 'function') { this.overwriteMethod(key, proxyXHR); continue; } this.overwriteAttributes(key, proxyXHR); } } /** * 重写方法 * @param {*} key */ overwriteMethod(key, proxyXHR) { let hooks = this.hooks; let execedHooks = this.execedHooks; proxyXHR[key] = (...args) => { // 拦截 if (hooks[key] && (hooks[key].call(proxyXHR, args) === false)) { return; } // 执行方法本体 const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args); // 方法本体执行后的钩子 execedHooks[key] && execedHooks[key].call(proxyXHR._xhr, res); return res; }; } /** * 重写属性 * @param {*} key */ overwriteAttributes(key, proxyXHR) { Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR)); } /** * 设置属性的属性描述 * @param {*} key */ setProperyDescriptor(key, proxyXHR) { let obj = Object.create(null); let _this = this; obj.set = function(val) { // 如果不是on打头的属性 if (!key.startsWith('on')) { proxyXHR['__' + key] = val; return; } if (_this.hooks[key]) { this._xhr[key] = function(...args) { (_this.hooks[key].call(proxyXHR), val.apply(proxyXHR, args)); } return; } this._xhr[key] = val; } obj.get = function() { return proxyXHR['__' + key] || this._xhr[key]; } return obj; } /** * 获取实例 */ getInstance() { return AnyXHR.instance; } /** * 删除钩子 * @param {*} name */ rmHook(name, isExeced = false) { let target = (isExeced ? this.execedHooks : this.hooks); delete target[name]; } /** * 清空钩子 */ clearHook() { this.hooks = {}; this.execedHooks = {}; } /** * 取消监听 */ unset() { window.XMLHttpRequest = this.XHR; } /** * 重新监听 */ reset() { AnyXHR.instance = null; AnyXHR.instance = new AnyXHR(this.hooks, this.execedHooks); } } 复制代码 完成 到此呢整体就完成了 由于缺乏测试 指不定还有bug 另外有些缺陷 就是所有钩子得是同步的 如果是异步顺序会乱 这个问题之后再解决 如果感兴趣可以自己也尝试一下 另外这种拦截的方式 基本上适用任何对象 可以灵活的使用 源码 只要是使用XMLHttpRequest的ajax请求 都可以用他来拦截 如果可以请给个星星 过段时间找实习就靠了 万分感谢! 原文发布时间:2018年06月29日 原文作者:NISAL 本文来源掘金,如需转载请紧急联系作者
node背景,了解一下 (1)体系架构 Node.js主要分为四大部分,Node Standard Library,Node Bindings,V8,Libuv,架构图如下: Node Standard Library 是我们每天都在用的标准库,如Http, Buffer 模块。 Node Bindings 是沟通JS 和 C++的桥梁,封装V8和Libuv的细节,向上层提供基础API服务。 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。 V8 是Google开发的JavaScript引擎,提供JavaScript运行环境,可以说它就是 Node.js 的发动机。 Libuv 是专门为Node.js开发的一个封装库,提供跨平台的异步I/O能力 C-ares:提供了异步处理 DNS 相关的能力。 http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。 代码结构 树形结构查看,使用 tree 命令 nodejs git:(master) tree -L 1 . ├── AUTHORS ├── BSDmakefile ├── BUILDING.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── COLLABORATOR_GUIDE.md ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── LICENSE ├── Makefile ├── README.md ├── ROADMAP.md ├── WORKING_GROUPS.md ├── android-configure ├── benchmark ├── common.gypi ├── config.gypi ├── config.mk ├── configure ├── deps ├── doc ├── icu_config.gypi ├── lib ├── node.gyp ├── out ├── src ├── test ├── tools └── vcbuild.bat 复制代码 进一步查看 deps目录: nodejs git:(master) tree deps -L 1 deps ├── cares ├── gtest ├── http_parser ├── npm ├── openssl ├── uv ├── v8 └── zlib 复制代码 node.js核心依赖六个第三方模块。其中核心模块 http_parser, uv, v8这三个模块在后续章节我们会陆续展开。 gtest是C/C++单元测试框架。 (2)libuv node.js最初开始于2009年,是一个可以让Javascript代码离开浏览器的执行环境也可以执行的项目。 node.js使用了Google的V8解析引擎和Marc Lehmann的libev。Node.js将事件驱动的I/O模型与适合该模型的编程语言(Javascript)融合在了一起。随着node.js的日益流行,node.js需要同时支持windows, 但是libev只能在Unix环境下运行。Windows 平台上与kqueue(FreeBSD)或者(e)poll(Linux)等内核事件通知相应的机制是IOCP。libuv提供了一个跨平台的抽象,由平台决定使用libev或IOCP。在node-v0.9.0版本中,libuv移除了libev的内容。 libuv是一个高性能的,事件驱动的I/O库,并且提供了跨平台(如windows, linux)的API。 随着libuv的日益成熟,它成为了拥有卓越性能的系统编程库。除了node.js以外,包括Mozilla的Rust编程语言,和许多的语言都开始使用libuv。 libuv的官网:http://docs.libuv.org/en/v1.x/,英文的,可以直接参考。 (3)V8 concept[概念] 架构图 现在 JS 引擎的执行过程大致是:源代码 --->抽象语法树 --->字节码 --->JIT--->本地代码。一段代码的抽象语法树示例如下: function demo(name) { console.log(name); } 复制代码 V8 更加直接的将抽象语法树通过 JIT 技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。 在 V8 生成本地代码后,也会通过 Profiler 采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化, 但极大减少了转换时间。 JIT就是即时编译器,可以根据字节码的使用频率对常用的字节码生成本地机器指令(运行时),并且保存下来 PS:Tuborfan 将逐步取代 Crankshaft 随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。 Node,开始学习! 1. Node能够解决什么问题? 应用场景:Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具 Web服务器的瓶颈在于并发的用户量,对比Java和Php的实现方式 Node在处理高并发 , I/O密集场景有明显的性能优势 I/O (input/output 文件的输入输出) 高并发,是指在同一时间并发访问服务器 I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密 2. Node是什么? Node.js 是一个基于 Chrome V8引擎的 JavaScript 运行环境,让JavaScript的执行效率与低端的C语言的相近的执行效率。 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的包管理器npm,是全球最大的开源库生态系统。 3. 进程与线程 进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。 为什么JavaScript是单线程? 这是由 Javascript 这门脚本语言的用途决定的。 Web Worker并没有改变 JavaScript 单线程的本质。 // websocket let worker = new Worker('./1.worker.js'); worker.postMessage(10000); worker.onmessage = function (e) { console.log(e.data); } console.log('main thread') 复制代码 3.1 谈谈浏览器 用户界面-包括地址栏、前进/后退按钮、书签菜单等 浏览器引擎-在用户界面和呈现引擎之间传送指令(浏览器的主进程) 渲染引擎,也被称为浏览器内核(浏览器渲染进程) 一个插件对应一个进程(第三方插件进程) GPU提高网页浏览的体验(GPU进程) 由此可见浏览器是多进程的,并且从我们的角度来看我们更加关心浏览器渲染引擎 3.2 渲染引擎 渲染引擎内部是多线程的,内部包含两个最为重要的线程ui线程和js线程。这里要特别注意ui线程和js线程是互斥的,因为JS运行结果会影响到ui线程的结果。ui更新会被保存在队列中等到js线程空闲时立即被执行. 3.3 js单线程 javascript在最初设计时设计成了单线程,为什么不是多线程呢?如果多个线程同时操作DOM那岂不会很混乱?这里所谓的单线程指的是主线程是单线程的(node其实也是多线程的),所以在Node中主线程依旧是单线程的。 js线程和ui线程 是共享线程 不能两个线程,操作同一个DOM 什么是多线程? 同步 每次请求都会产生一个线程,可能会遇到同时操作一个文件,会给文件加锁 可以开一个工作线程,但是归主线程所管理 需要切换上下文执行 运用线程池可以优化多线程 什么是单线程? 异步 不会占用过多内存,并且不需要在切换执行上下文 什么是进程? 进程是系统分配任务和调度任务的基本单位 多进程 可能会遇到同时操作一个文件会给文件加锁 3.4 其他线程 浏览器事件触发线程(用来控制事件循环,存放setTimeout、浏览器事件、ajax的回调函数) 定时触发器线程(setTimeout定时器所在线程) 异步HTTP请求线程(ajax请求线程) 单线程不需要管锁的问题,这里简单说下锁的概念。例如大家都要去上厕所,厕所就一个,相当于所有人都要访问同一个资源。那么先进去的就要上锁。而对于node来说。就一个人去厕所,所以免除了锁的问题! 3.5. 浏览器中的Event Loop 主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环) V8引擎解析JavaScript脚本。 解析后的代码,调用Node API。 libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。 V8引擎再将结果返回给用户。 queue stack queue队列: 先进先出 push shift stack栈: 函数执行 3.6 Node.js的Event Loop 3.7. 同步与异步 同步和异步关注的是消息通知机制 同步就是发出调用后,没有得到结果之前,该调用不返回,一旦调用返回,就得到返回值了。 简而言之就是调用者主动等待这个调用的结果。 而异步则相反,调用者在发出调用后这个调用就直接返回了,所以没有返回结果。换句话说当一个异步过程调用发出后,调用者不会立刻得到结果,而是调用发出后,被调用者通过状态、通知或回调函数处理这个调用。 补充知识:宏任务 && 微任务(vue $nextTick) 异步方法 // 宏任务 setTimeout // 同步代码执行后才会执行异步 // 根据时间排序,当时间到达后把对应的回调放到队列 setTimeout(() => { console.log(1); setTimeout(() => { console.log(4); }, 1000); }, 1000); setTimeout(() => { console.log(2); }, 2000); setTimeout(() => { console.log(3); }, 3000); 复制代码 // 宏任务 setImmedate // setImmedate只兼容ie,默认是低于 setTimeout setImmediate(function () { console.log('setImmediate') }) setTimeout(function () { console.log('timeout') }, 4); console.log(1); 复制代码 // 宏任务 MessageChannel let messageChannel = new MessageChannel(); let prot2 = messageChannel.port2; // postMessage是异步执行的,要等待同步都执行完后才会被调用 messageChannel.port1.postMessage('111'); console.log(1); prot2.onmessage = function (e) { console.log(e.data); } console.log(2); 复制代码 // 宏任务 MutationObserver废弃了,兼容/性能有问题 let observe = new MutationObserver(function () { alert('已经dom更新好了') }); observe.observe(app,{childList:true}); for(let i = 0;i<1000;i++){ app.appendChild(document.createElement('span')); } 复制代码 3.8. 阻塞与非阻塞I/O 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 3.9. 组合 同步异步取决于被调用者,阻塞非阻塞取决于调用者 同步阻塞 异步阻塞 同步非阻塞 异步非阻塞 4. 什么场合下应该考虑使用Node框架? 当应用程序需要处理大量并发的输入输出,而在向客户端响应之前,应用程序并不需要进行非常复杂的处理。 聊天服务器 电子商务网站 原文发布时间:2018年07月02日 原文作者:张倩1488543336000 本文来源掘金,如需转载请紧急联系作者
我们工作中免不了运用promise用来解决异步回调问题。平时用的很多库或者插件都运用了promise 例如axios、fetch等等。但是你知道promise是咋写出来的呢? 别怕~这里有本promisesA+规范,便宜点10元卖给你了。 1、Promise 的声明 首先呢,promise肯定是一个类,我们就用class来声明。 由于new Promise((resolve, reject)=>{}),所以传入一个参数(函数),秘籍里叫他executor,传入就执行。 executor里面有两个参数,一个叫resolve(成功),一个叫reject(失败)。 由于resolve和reject可执行,所以都是函数,我们用let声明。 class Promise{ // 构造器 constructor(executor){ // 成功 let resolve = () => { }; // 失败 let reject = () => { }; // 立即执行 executor(resolve, reject); } } 复制代码 解决基本状态 秘籍对Promise有规定: Promise存在三个状态(state)pending、fulfilled、rejected pending(等待态)为初始态,并可以转化为fulfilled(成功态)和rejected(失败态) 成功时,不可转为其他状态,且必须有一个不可改变的值(value) 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason) new Promise((resolve, reject)=>{resolve(value)}) resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变。 new Promise((resolve, reject)=>{reject(reason)}) reject为失败,接收参数reason,状态改变为rejected,不可再次改变。 若是executor函数报错 直接执行reject(); 于是乎,我们获得以下代码 class Promise{ constructor(executor){ // 初始化state为等待态 this.state = 'pending'; // 成功的值 this.value = undefined; // 失败的原因 this.reason = undefined; let resolve = value => { // state改变,resolve调用就会失败 if (this.state === 'pending') { // resolve调用后,state转化为成功态 this.state = 'fulfilled'; // 储存成功的值 this.value = value; } }; let reject = reason => { // state改变,reject调用就会失败 if (this.state === 'pending') { // reject调用后,state转化为失败态 this.state = 'rejected'; // 储存失败的原因 this.reason = reason; } }; // 如果executor执行报错,直接执行reject try{ executor(resolve, reject); } catch (err) { reject(err); } } } 复制代码 then方法 秘籍规定:Promise有一个叫做then的方法,里面有两个参数:onFulfilled,onRejected,成功有成功的值,失败有失败的原因 当状态state为fulfilled,则执行onFulfilled,传入this.value。当状态state为rejected,则执行onRejected,传入this.reason onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数 class Promise{ constructor(executor){...} // then 方法 有两个参数onFulfilled onRejected then(onFulfilled,onRejected) { // 状态为fulfilled,执行onFulfilled,传入成功的值 if (this.state === 'fulfilled') { onFulfilled(this.value); }; // 状态为rejected,执行onRejected,传入失败的原因 if (this.state === 'rejected') { onRejected(this.reason); }; } } 复制代码 这下武学初成,可以对付对付江湖小杂毛了,但是对于带setTimeout的江洋大盗还是没辙。 解决异步实现 现在基本可以实现简单的同步代码,但是当resolve在setTomeout内执行,then时state还是pending等待状态 我们就需要在then调用的时候,将成功和失败存到各自的数组,一旦reject或者resolve,就调用它们 类似于发布订阅,先将then里面的两个函数储存起来,由于一个promise可以有多个then,所以存在同一个数组内。 // 多个then的情况 let p = new Promise(); p.then(); p.then(); 复制代码 成功或者失败时,forEach调用它们 class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; // 成功存放的数组 this.onResolvedCallbacks = []; // 失败存放法数组 this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; // 一旦resolve执行,调用成功数组的函数 this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; // 一旦reject执行,调用失败数组的函数 this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { if (this.state === 'fulfilled') { onFulfilled(this.value); }; if (this.state === 'rejected') { onRejected(this.reason); }; // 当状态state为pending时 if (this.state === 'pending') { // onFulfilled传入到成功数组 this.onResolvedCallbacks.push(()=>{ onFulfilled(this.value); }) // onRejected传入到失败数组 this.onRejectedCallbacks.push(()=>{ onRejected(this.reason); }) } } } 复制代码 解决链式调用 我门常常用到new Promise().then().then(),这就是链式调用,用来解决回调地狱 1、为了达成链式,我们默认在第一个then里返回一个promise。秘籍规定了一种方法,就是在then里面返回一个新的promise,称为promise2:promise2 = new Promise((resolve, reject)=>{}) 将这个promise2返回的值传递到下一个then中 如果返回一个普通的值,则将普通的值传递给下一个then中 2、当我们在第一个then中return了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值 秘籍则规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise 首先,要看x是不是promise。 如果是promise,则取它的结果,作为新的promise2成功的结果 如果是普通值,直接作为promise2成功的结果 所以要比较x和promise2 resolvePromise的参数有promise2(默认返回的promise)、x(我们自己return的对象)、resolve、reject resolve和reject是promise2的 class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { // 声明返回的promise2 let promise2 = new Promise((resolve, reject)=>{ if (this.state === 'fulfilled') { let x = onFulfilled(this.value); // resolvePromise函数,处理自己return的promise和默认的promise2的关系 resolvePromise(promise2, x, resolve, reject); }; if (this.state === 'rejected') { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(()=>{ let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.onRejectedCallbacks.push(()=>{ let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }) } }); // 返回promise,完成链式 return promise2; } } 复制代码 完成resolvePromise函数 秘籍规定了一段代码,让不同的promise代码互相套用,叫做resolvePromise 如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报“循环引用”错误 let p = new Promise(resolve => { resolve(0); }); var p2 = p.then(data => { // 循环引用,自己等待自己完成,一辈子完不成 return p2; }) 复制代码 1、判断x Otherwise, if x is an object or function,Let then be x.then x 不能是null x 是普通值 直接resolve(x) x 是对象或者函数(包括promise),let then = x.then 2、当x是对象或者函数(默认promise) 声明了then 如果取then报错,则走reject() 如果then是个函数,则用call执行then,第一个参数是this,后面是成功的回调和失败的回调 如果成功的回调还是pormise,就递归继续解析 3、成功和失败只能调用一个 所以设定一个called来防止多次调用 function resolvePromise(promise2, x, resolve, reject){ // 循环引用报错 if(x === promise2){ // reject报错 return reject(new TypeError('Chaining cycle detected for promise')); } // 防止多次调用 let called; // x不是null 且x是对象或者函数 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+规定,声明then = x的then方法 let then = x.then; // 如果then是函数,就默认是promise了 if (typeof then === 'function') { // 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调 then.call(x, y => { // 成功和失败只能调用一个 if (called) return; called = true; // resolve的结果依旧是promise 那就继续解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失败只能调用一个 if (called) return; called = true; reject(err);// 失败了就失败了 }) } else { resolve(x); // 直接成功即可 } } catch (e) { // 也属于失败 if (called) return; called = true; // 取then出错了那就不要在继续执行了 reject(e); } } else { resolve(x); } } 复制代码 解决其他问题 1、秘籍规定onFulfilled,onRejected都是可选参数,如果他们不是函数,必须被忽略 onFulfilled返回一个普通的值,成功时直接等于 value => value onRejected返回一个普通的值,失败时如果直接等于 value => value,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误reason => throw err 2、秘籍规定onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题 如果onFulfilled或onRejected报错,则直接返回reject() class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; // onRejected如果不是函数,就忽略onRejected,直接扔出错误 onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { // 异步 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'rejected') { // 异步 setTimeout(() => { // 如果报错 try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(() => { // 异步 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { // 异步 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); // 返回promise,完成链式 return promise2; } } 复制代码 大功告成 顺便附赠catch和resolve、reject、race、all方法 class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'rejected') { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); return promise2; } catch(fn){ return this.then(null,fn); } } function resolvePromise(promise2, x, resolve, reject){ if(x === promise2){ return reject(new TypeError('Chaining cycle detected for promise')); } let called; if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if(called)return; called = true; resolvePromise(promise2, y, resolve, reject); }, err => { if(called)return; called = true; reject(err); }) } else { resolve(x); } } catch (e) { if(called)return; called = true; reject(e); } } else { resolve(x); } } //resolve方法 Promise.resolve = function(val){ return new Promise((resolve,reject)=>{ resolve(val) }); } //reject方法 Promise.reject = function(val){ return new Promise((resolve,reject)=>{ reject(val) }); } //race方法 Promise.race = function(promises){ return new Promise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(resolve,reject) }; }) } //all方法(获取所有的promise,都执行then,把结果放到数组,一起返回) Promise.all = function(promises){ let arr = []; let i = 0; function processData(index,data){ arr[index] = data; i++; if(i == promises.length){ resolve(arr); }; }; return new Promise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(data=>{ processData(i,data); },reject); }; }); } 复制代码 如何验证我们的promise是否正确 1、先在后面加上下述代码 2、npm 有一个promises-aplus-tests插件 npm i promises-aplus-tests -g 可以全局安装 mac用户最前面加上sudo 3、命令行 promises-aplus-tests [js文件名] 即可验证 // 目前是通过他测试 他会测试一个对象 // 语法糖 Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve,reject)=>{ dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise; //npm install promises-aplus-tests 用来测试自己的promise 符不符合promisesA+规范 复制代码 原文发布时间:2018年06月27日 原文作者:卡姆爱卡姆 本文来源掘金,如需转载请紧急联系作者
这篇文章主要整理一下自己在使用 webpack 结合 vuejs 或 reactjs 开发过程中图片的处理方法。 我的需求 项目打包之后(假定输出目录为 dist),除了 index.html,将所有的静态资源上传至 cdn,而并非打包之后所有静态资源都在应用服务器上。 index.html 中的图片 因为是 SPA,模版页面唯一要处理的图片就是 favicon,这个资源在 IE 10 及以下浏览器只需要在 dist 根目录下存在 favicon.icon 文件(名称、后缀固定)即可,这种方式已经废弃,更好的做法是使用 link 标签引用,如: <link rel="icon" sizes="192x192" href="/path/to/icon.png"> 复制代码 处理这个图片,我尝试了三个方法: 1.html-webpack-plugin 的 favicon 属性配置 不适合我。 它会将文件输出至 dist 根目录下,与 index.html 同级,引用的是本地图片,而非 cdn 图片。 2.favicons-webpack-plugin 不适合我。 很强大,能根据你给的图片,生成所有类型的 icon 图标,问题有两个:首先是依赖了 phantomjs,墙外的站点,你懂得;再者引用的依旧是本地图片。 组件实现 适合我。 vuejs 的 vue-head 组件,reactjs 的 react-helmet 组件,可以配置 link 方式的 favicon。图片打包上传 cdn 之后,页面的图片地址也为 cdn 地址。 其它 如果你的模版页面有其他诸如 src 图片引用,可参考 html-withimg-loader 页面 Head 大全 vuejs 项目中图片处理 前提:使用 vue-loader v15,webpack 配置好 url-loader 和 alias: module.exports = { // ...其他配置 modules: { rules: [ { test: /\.(jpe?g|png|gif)(\?.*)?$/, use: 'url-loader?limit=1024&name=statics/img/[name]-[hash:5].[ext]', exclude: /node_modules/, }, ] }, resolve: { alias: { Images: path.resolve('public', 'statics', 'img'), }, }, // ...其他配置 } 复制代码 template 中使用 <img src="~Images/logo.png"> <img :src="require('Images/logo.png')"> // 注意 v-bind 复制代码 有个问题我目前没有解决(精简代码),还请大神指教: // 无效,提示无法找到这个资源 <img :src="require(`${img}`)"> data() { img: 'Images/logo.png' } 复制代码 // 有效 <img :src="require(`Images/${img}`)"> data() { img: 'logo.png' } 复制代码 已解决,官网对 require 的说明: A context is created if your request contains expressions, so the exact module is not known on compile time. 样式表中使用 貌似无法使用 alias,只能使用 ~ 和相对路径: body { background: url(Images/logo.png); // alias,错误 background: url(/Images/logo.png); // 有效,但是引用本地文件 background: url(~Images/logo.png); background: url(../../statics/img/logo.png); background: url(./../../statics/img/logo.png); } 复制代码 以 . 开头,将会被看作相对的模块依赖,并按照你的本地文件系统上的目录结构进行解析。 reactjs 中图片处理 前提:webpack 配置好 url-loader 和 alias 使用 不管是组件中还是样式表中,都可以使用 alias 和相对路径: require('Images/logo.png'); require('../../statics/img/logo.png'); require('./../../statics/img/logo.png'); 复制代码 body { background: url(Images/logo.png); background: url(../../statics/img/logo.png); background: url(./../../statics/img/logo.png); } 复制代码 最后 ./ 可有可无,相对路径是以当前的文件为基础,注意 ../ 层数问题;不要以 / 开头,因为最终都是引用的本地图片。 如有谬误,恳请斧正。 原文发布时间:2018年06月29日 原文作者:zhCN_超 本文来源掘金,如需转载请紧急联系作者
在开发一个在线聊天工具时,经常会有过多少毫秒就重复执行一次某操作的需求。“没问题”,大家都说,“用setInterval好了。”我觉得这个点子很糟糕。 原因之一:setInterval无视代码错误 setInterval有个讨厌的习惯,即对自己调用的代码是否报错这件事漠不关心。换句话说,如果setInterval执行的代码由于某种原因出了错,它还会持续不断(不管不顾)地调用该代码。看演示 原因之二:setInterval无视网络延迟 假设你每隔一段时间就通过Ajax轮询一次服务器,看看有没有新数据(注意:如果你真的这么做了,那恐怕你做错了;建议使用“补偿性轮询”(backoff polling))。而由于某些原因(服务器过载、临时断网、流量剧增、用户带宽受限,等等),你的请求要花的时间远比你想象的要长。但setInterval不在乎。它仍然会按定时持续不断地触发请求,最终你的客户端网络队列会塞满Ajax调用。 看示例 原因之三:setInterval不保证执行 与setTimeout不同,你并不能保证到了时间间隔,代码就准能执行。如果你调用的函数需要花很长时间才能完成,那某些调用会被直接忽略。看示例 解决之道很简单:用setTimeout 与其使用setInterval,不如在适当的时刻通过setTimeout来调用函数自身。在前面两个示例中,使用setInterval的函数a都出错了,而使用setTimeout的函数b则表现很好。 如果必须保证间隔相等怎么办? 如果确实要保证事件“匀速”被触发,那可以用希望的延迟减去上次调用所花时间,然后将得到的差值作为延迟动态指定给setTimeout。 不过,要注意的是JavaScript的计时器并不是非常精确。因此你不可能得到绝对“平均”的延迟,即使使用setInterval也不行,原因很多(比如垃圾回收、JavaScript是单线程的,等等)。此外,当前浏览器也会将最小的超时时间固定在4ms到15ms之间。因此不要指望一点误差也没有。 原文发布时间:2018-07-01 本文来源掘金,如需转载请紧急联系作者
Bootstrap本次知识点 1.进度条 2.媒体对象 3.列表组 4.面板 5.响应式嵌入组件 6.well 1.进度条 (1)默认的进度条 <div class="progress"> <div class="progress-bar" style="width:45%;">45%</div> </div> (2)情景变化的进度条 <div class="progress"> <div class="progress-bar progress-bar-info" style="width:60%;">60%</div> </div> <div class="progress"> <div class="progress-bar progress-bar-success" style="width:25%;">25%</div> </div> <div class="progress"> <div class="progress-bar progress-bar-danger" style="width:45%;">45%</div> </div> <div class="progress"> <div class="progress-bar progress-bar-warning" style="width:45%;">45%</div> </div> (3)条纹的进度条 progress-striped <div class="progress progress-striped"> <div class="progress-bar" style="width:45%;">45%</div> </div> (4)动画的进度条 active <div class="progress progress-striped active"> <div class="progress-bar" style="width:45%;">45%</div> </div> (5)堆叠的进度条 <div class="progress"> <div class="progress-bar progress-bar-warning" style="width:45%;">45%</div> <div class="progress-bar progress-bar-success" style="width:25%;">25%</div> </div> 2.媒体对象 <div class="media"> <a href="" class="pull-left"><img class="media-object" src="images/kittens.jpg" alt="" width="95"/></a> <div class="media-body"> <h4 class="media-heading">媒体标题</h4> 这是一些示例文本。这是一些示例文本。 这是一些示例文本。这是一些示例文本。 这是一些示例文本。这是一些示例文本。 这是一些示例文本。这是一些示例文本。 这是一些示例文本。这是一些示例文本。 </div> </div> 3.列表组 (1)向列表组添加国徽 <ul class="list-group"> <li class="list-group-item"><a href="">免费域名注册 <span class="badge pull-right">20</span></a></li> <li class="list-group-item"><a href="">免费 Window 空间托管</a></li> <li class="list-group-item"><a href="">每年更新成本</a></li> </ul> (2)向列表组添加链接 <div class="list-group"> <a href="" class="list-group-item active">免费域名注册</a> <a href="" class="list-group-item">免费 Window 空间托管</a> <a href="" class="list-group-item">每年更新成本</a> </div> (3)向列表组添加自定义内容 <ul class="list-group"> <li class="list-group-item">Cras justo odio</li> <li class="list-group-item">Dapibus ac facilisis in</li> <li class="list-group-item">Morbi leo risus</li> <li class="list-group-item">Porta ac consectetur ac</li> <li class="list-group-item">Vestibulum at eros</li> </ul> 4.面板 (1)面板标题 <div class="panel-heading">标题</div> (2)面板脚注 <div class="panel-footer text-right">by zichen</div> (3)面板主题 <div class="panel panel-primary">...</div> <div class="panel panel-success">...</div> <div class="panel panel-info">...</div> <div class="panel panel-warning">...</div> <div class="panel panel-danger">...</div> (4)带表格的面板 <div class="panel panel-default"> <div class="panel-heading">Panel heading</div> <table class="table"> <tr> <td>学号</td> <td>姓名</td> <td>年龄</td> </tr> </table> </div> (5)带列表组的面板 <div class="panel panel-danger"> <div class="panel-heading">标题</div> <div class="panel-body">面板内容显示区域</div> <ul class="list-group"> <li class="list-group-item">免费域名注册</li> <li class="list-group-item">免费 Window 空间托管</li> <li class="list-group-item">图像的数量</li> <li class="list-group-item">24*7 支持</li> <li class="list-group-item">每年更新成本</li> </ul> <div class="panel-footer text-right">by zichen</div> </div> 5.响应式嵌入组件 根据被嵌入内容的外部容器的宽度,自动创建一个固定的比例,从而让浏览器自动确定内容的尺寸,能够在各种设备上缩放。 这些规则可以直接用于<iframe>、<embed>、<video>和<object>元素。 //16:9 响应式 <div class="embed-responsive embed-responsive-16by9"> <embed width="100%" height="100%" src="https://www.youtube.com/embed/zpOULjyy-n8?rel=0"></embed> </div> //4:3 响应式 <div class="embed-responsive embed-responsive-4by3"> <embed width="100%" height="100%" src="https://www.youtube.com/embed/zpOULjyy-n8?rel=0"></embed> </div> <div class="embed-responsive embed-responsive-16by9"> <iframe class="embed-responsive-item" src="https://www.youtube.com/embed/zpOULjyy-n8?rel=0"></iframe> </div> <div class="embed-responsive embed-responsive-4by3"> <iframe class="embed-responsive-item" src="https://www.youtube.com/embed/zpOULjyy-n8?rel=0"></iframe> </div> 6.well (1)基本的well <div class="well">您好,我在大的 Well 中!</div> (2)尺寸的大小 well-lg well-sm <div class="well well-lg">您好,我在大的 Well 中!</div> <div class="well well-sm">您好,我在大的 Well 中!</div> 本次Bootstrap前端开发框架学习到此完结,欢迎大家参与学习!!感谢大家的支持!谢谢! ^_^ 原文发布时间:2018年06月21日 08:30:14 原文作者:Roger_CoderLife 本文来源CSDN,如需转载请联系原作者
之前的CSDN编译器有问题,所以现在重新整理出来给大家。 先po上效果图: html代码: <script type="text/x-template" id="calendar"> <div id=""> <!-- 年份 月份 --> <div class="month"> <ul> <!--点击会触发pickpre函数,重新刷新当前日期 @click(vue v-on:click缩写) --> <li class="arrow" @click="pickPre(currentYear,currentMonth)"></li> <li class="year-month" @click="pickYear(currentYear,currentMonth)"> <span class="choose-year">{{ currentYear }}</span> <span class="choose-month">{{ currentMonth }}月</span> </li> <li class="arrow" @click="pickNext(currentYear,currentMonth)"></li> </ul> </div> <!-- 星期 --> <ul class="weekdays"> <li>一</li> <li>二</li> <li>三</li> <li>四</li> <li>五</li> <li style="color:red">六</li> <li style="color:red">日</li> </ul> <!-- 日期 --> <ul class="days"> <!-- 核心 v-for循环 每一次循环用<li>标签创建一天 --> <li v-for="dayobject in days"> <!--本月--> <!--如果不是本月 改变类名加灰色--> <span v-if="dayobject.day.getMonth()+1 != currentMonth" class="other-month">{{ dayobject.day.getDate() }}</span> <!--如果是本月 还需要判断是不是这一天--> <span v-else> <!--今天 同年同月同日--> <span v-if="dayobject.day.getFullYear() == new Date().getFullYear() && dayobject.day.getMonth() == new Date().getMonth() && dayobject.day.getDate() == new Date().getDate()" class="active">{{ dayobject.day.getDate() }}</span> <span v-else>{{ dayobject.day.getDate() }}</span> </span> </li> </ul> </div> </script> <div id="app"> <calendar></calendar> </div> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 js代码: Vue.component("calendar", { template: "#calendar", data: function () { return { currentDay: 1, currentMonth: 1, currentYear: 1970, currentWeek: 1, days: [], } }, created() { let that = this; that.initData(null); }, methods: { initData: function (cur) { let that = this; let leftcount = 0; //存放剩余数量 let date; if (cur) { date = new Date(cur); } else { let now = new Date(); let d = new Date(that.formatDate(now.getFullYear(), now.getMonth(), 1)); d.setDate(35); date = new Date(that.formatDate(d.getFullYear(), d.getMonth() + 1, 1)); } that.currentDay = date.getDate(); that.currentYear = date.getFullYear(); that.currentMonth = date.getMonth() + 1; that.currentWeek = date.getDay(); // 1...6,0 if (that.currentWeek == 0) { that.currentWeek = 7; } let str = that.formatDate(that.currentYear, that.currentMonth, that.currentDay); that.days.length = 0; // 今天是周日,放在第一行第7个位置,前面6个 //初始化本周 for (let i = that.currentWeek - 1; i >= 0; i--) { let d = new Date(str); d.setDate(d.getDate() - i); let dayobject = {}; //用一个对象包装Date对象 以便为以后预定功能添加属性 dayobject.day = d; that.days.push(dayobject); //将日期放入data 中的days数组 供页面渲染使用 } //其他周 for (let i = 1; i <= 35 - that.currentWeek; i++) { let d = new Date(str); d.setDate(d.getDate() + i); let dayobject = {}; dayobject.day = d; that.days.push(dayobject); } }, pickPre: function (year, month) { let that = this; // setDate(0); 上月最后一天 // setDate(-1); 上月倒数第二天 // setDate(dx) 参数dx为 上月最后一天的前后dx天 let d = new Date(that.formatDate(year, month, 1)); d.setDate(0); that.initData(that.formatDate(d.getFullYear(), d.getMonth() + 1, 1)); }, pickNext: function (year, month) { let that = this; let d = new Date(that.formatDate(year, month, 1)); d.setDate(35); that.initData(that.formatDate(d.getFullYear(), d.getMonth() + 1, 1)); }, pickYear: function (year, month) { alert(year + "," + month); }, // 返回 类似 2016-01-02 格式的字符串 formatDate: function (year, month, day) { let y = year; let m = month; if (m < 10) m = "0" + m; let d = day; if (d < 10) d = "0" + d; return y + "-" + m + "-" + d }, } }) let vm = new Vue({ el: '#app', }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 css代码: * { margin: 0; padding: 0; } /*日历*/ #calendar { width: 98%; border: 2px solid #A4A7B0; height: 335px; margin-left: 0.5%; } .month { width: 92%; height: 48px; border: 2px solid #FFFFFF; margin-left: 3%; margin-top: 20px; } .month ul { margin: 0; padding: 0; display: flex; margin-top: 11px; justify-content: space-between; } .year-month { flex-direction: column; align-items: center; justify-content: space-around; } .choose-year { padding: 0 20px; font-size: 16px; font-weight: 200; } .choose-month { text-align: center; font-size: 16px; font-weight: 200; } .arrow { width: 3%; height: 25px; } .arrow1 { background: url(left.png) no-repeat 0 0 /100% 100%; margin-left: 44px; } .arrow2 { background: url(right.png) no-repeat 0 0 /100% 100%; margin-right: 44px; } .month ul li { color: #999; font-size: 20px; text-transform: uppercase; letter-spacing: 3px; list-style: none; } .weekdays { margin: 0; color: #FFFFFF; background: #A4A7B0; width: 96.6%; margin-top: 26px; height: 34px; line-height: 34px; margin-left: 2.2%; } .weekdays li { display: inline-block; text-align: center; color: #11616f; font-size: 14px; font-weight: 100; width: 12.7%; } .days { padding: 0; margin: 0; display: flex; flex-wrap: wrap; justify-content: space-around; } .days li { list-style-type: none; display: inline-block; width: 14.2%; text-align: center; padding-bottom: 3px; padding-top: 7px; font-size: 12.78px; color: rgb(14, 220, 235); font-weight: 200; } .days li span span { height: 29.5px; width: 27px; line-height: 29.5px; display: inline-block; } .days li .class-30 { background: url(bg_30.png) no-repeat 0 0 /100% 100%; } .days li .class-60 { background: url(bg_60.png) no-repeat 0 0 /100% 100%; } .days li .class-3060 { background: url(bg_3060.png) no-repeat 0 0 /100% 100%; } .days li .other-month { padding: 5px; color: #84a8ae; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 github地址:https://github.com/AmberWuWu/vue-calendar 原文发布时间:2018年03月13日 10:14:29 原文作者:AmberWu 本文来源CSDN,如需转载请联系原作者
英文:Jake 译文:众成翻译/KING http://zcfy.cc/article/third-party-css-is-not-safe 最近一段时间,关于 通过 CSS 创建 “keylogger”(键盘记录器) 的讨论很多。 有些人呼吁浏览器厂商去“修复”它。有些人则深入研究,表示它仅能影响通过类 React 框架建立的网站,并指责 React。而真正的问题却在于很多人认为第三方内容是“安全”的。 第三方图片 `<img src="https://example.com/kitten.jpg">` 如果我将上述代码引入我的文件中,即表示信任 example.com。对方可能会删除资源,给我一个 404,导致网站不完整,从而破坏这种信任关系。或者,他们可能会用其他非预期的数据来替代小猫图片的数据。 但是,图片的影响仅限于元素本身的内容区域。我可以向用户解释并希望用户相信,“此处的内容来自 example.com,如果它有误,则是原站点的问题,并不是本站造成的”。但这个问题肯定不会影响到密码输入框等内容。 第三方脚本 `<script src="https://example.com/script.js"></script>` 与图片相比,第三方脚本则有更多的控制权。如果我将上述代码引入我的文件中,则表示我赋予了 example.com 完全控制我的网站的权限。该脚本能: 读取/修改页面内容。 监听用户的所有交互。 运行耗费大量计算资源的代码(如 cryptocoin 挖矿程序)。 通过向本站发请求,这样能附带用户的 cookie,转发响应。(译注:盗取用户的 cookie 及其他数据) 读取/修改本地存储。 ......可以做任何对方想做的事情。 “本地存储”非常重要。如果脚本通过 IndexedDB 或缓存 API 发起攻击,则即使在删除脚本后,攻击仍可能在整个站点内继续存在。 如果你引入了其他站点的脚本,则必须绝对相信对方及对方的防护能力。 如果你遭到恶意脚本的攻击,则可设置 Clear-Site-Data header(清空站点数据响应头) 清除站点所有数据。 第三方CSS `<link rel="stylesheet" href="https://example.com/style.css">` 相比图片,CSS 在能力上更接近脚本。像脚本一样,它适用于整个页面。它可以: 删除/添加/修改页面内容。 根据页面内容发起请求。 可响应多种用户交互。 虽然 CSS 不能修改本地存储,也不能通过 CSS 运行 cryptocoin 挖矿程序(也许是可能的,只是我不知道而已),但恶意 CSS 代码仍然能造成很大的损失。 键盘记录器 从引起广泛关注的代码开始讲起: input[type="password"][value$="p"] { background: url('/password?p'); } 如果输入框的 value 属性值以 p 结尾,上述代码将会向 /password?p 发起请求。每个字符都可触发这个操作,通过它能获取到很多数据。 默认情况下,浏览器不会将用户输入的值存储在 value 属性中,因此这种攻击需要依赖某些能同步这些值的东西,如 React。 要应对这个问题,React 可用另一种同步密码字段的方式,或浏览器可限制那些能匹配密码字段属性的选择器。但是,这仅仅是一种虚假的安全。你只解决了在特殊情况下的该问题,而其他情况依旧。 如果 React 改为使用 data-value 属性,则该应对方法无效。如果网站将输入框更改为 type="text",以便用户可以看到他们正在输入的内容,则该应对方法无效。如果网站创建了一个 <better-password-input> 组件并暴露 value 作为属性,则该应对方法无效。 此外,还有很多其他的基于 CSS 的攻击方式: 消失的内容 body { display: none; } html::after { content: 'HTTP 500 Server Error'; } 以上是一个极端的例子,但想象一下,如果第三方仅对某一小部分用户这样做。不但你很难调试,还会失去用户的信任。 更狡猾的方式如偶尔删除“购买”按钮,或重排内容段落。 添加内容 .price-value::before { content: '1'; } 哦,价格被标高了。 移动内容 .delete-everything-button { opacity: 0; position: absolute; top: 500px; left: 300px; } 上面的按钮能做一些重要的操作,设置其为不可见,然后放在用户可能点击的地方。 值得庆幸的是,如果按钮的操作确实非常重要,网站可能会先显示确认对话框。但也不是不可绕过,只需使用更多的 CSS 来欺骗用户点击 “确定” 按钮而不是“取消”按钮即可。 假设浏览器确实采用上面的应对方法解决“键盘记录器”的问题。攻击者只需在页面上找到一个非密码文本输入框(可能是搜索输入框)并将其盖在密码输入框上即可。然后他们的攻击就又可用了。 读取属性 其实,你需要担心的不仅仅是密码输入框。你可能在属性中保存着其他的隐藏内容: <input type="hidden" name="csrf" value="1687594325"> <img src="/avatars/samanthasmith83.jpg"> <iframe src="//cool-maps-service/show?st-pancras-london"></iframe> <img src="/gender-icons/female.png"> <div></div> 所有这些都可以通过 CSS 选择器获取,且能发出请求。 监听交互 .login-button:hover { background: url('/login-button-hover'); } .login-button:active { background: url('/login-button-active'); } 可将 hover 和 active 状态发送到服务器。通过适当的 CSS,你就能获取到用户意图。 读取文本 @font-face { font-family: blah; src: url('/page-contains-q') format('woff'); unicode-range: U+85; } html { font-family: blah, sans-serif; } 在这种情况下,如果页面内有 q 字符,则会发送请求。你可以为不同的字符,并针对特定的元素,创建大量不同的字体。字体也可以包含 ligature(连字),所以你可以在开始检测字符序列。你甚至可以通过将字体技巧与滚动条检测结合起来 来推断内容。 译注:关于 ligature(连字), 可查看 Wikipedia Typographic Ligature 第三方内容不安全 这些只是我所知道的一些技巧,我相信还有更多。 第三方内容在其沙箱区域内具有强大的能力。一张图片或沙盒化的 iframe 仅在一个小范围内的沙箱中,但脚本和样式的范围却是你的页面,甚至是整个站点。 如果你担心恶意用户诱使你的网站加载第三方资源,可以通过 CSP 用作防护手段,其可以限制加载图片,脚本和样式的来源。 你还可以使用 Subresource Integrity(子资源完整性 ) 来确保脚本/样式的内容匹配特定的 hash,否则将不加载。感谢 Hacker News上的Piskvorrr 提醒我! 如果你想了解更多如上述的 hack 技巧,包括滚动条技巧,更多信息请参阅 Mathias Bynens' talk from 2014,Mike West's talk from 2013,或 Mario Heiderich et al.'s paper from 2012(PDF)。是的,这不是什么新东西。 原文发布时间:2018年03月07日 00:00:00 原文作者:vhwfr2u02q 本文来源CSDN,如需转载请联系原作者
作者:xhload3d my.oschina.net/xhload3d/blog/1629064 摘要:感觉目前地铁上的地铁线路图也不是很人性化,很多交互方面没有考虑到用户的需求?感觉总是有各种容易看串的信息,利用 html5 canvas 完成的这个交互式地铁线路图 Demo,如果地铁上的展示信息稍微有点交互会怎么样?不用忙着上车下车,轻轻一点,就能省去很多时间。 前言 前两天在 echarts 上寻找灵感的时候,看到了很多有关地图类似的例子,地图定位等等,但是好像就是没有地铁线路图,就自己花了一些时间捣鼓出来了这个交互式地铁线路图的 Demo,地铁线路上的点是在网上随便下载了一个,这篇文章记录自己的一些收获(毕竟我还是个菜鸟)以及代码的实现,希望能够帮到一些朋友。当然,如果有什么意见的可以直接跟我说,大家一起交流才会进步。 效果图 http://www.hightopo.com/demo/subway/index.html 地图稍微内容有点多,要全部展示,字显得有点小了,但是没关系,可以按照需求放大缩小,字体和绘制的内容并不会失真,毕竟都是用矢量绘制的~ 界面生成 底层的 div 是通过 ht.graph.GraphView 组件生成的,然后就可以利用 HT for Web 提供好的方法,调用 canvas 画笔随便绘制就好,先来看看怎么生成底层 div: addToDOM 函数声明如下: 现在我就可以在这个 div 上乱涂乱画了~首先我获取下载好的地铁线路图上的点,我将它们放在 subway.js 中,这个 js 文件全部都是下载的内容,我没有做其他的改动,主要是将这些点根据线路来分分配添加到数组中,比如: 接下来来描绘地铁线路,我声明了一个数组 lineNum,用来装 js 中所有的地铁线路的编号,以及一个 color 数组,用来装所有的地铁线的颜色,这些颜色的 index 与 lineNum 中地铁线编号的 index 是一一对应的: var lineNum = ['1', '2', '3', '30', '4', '5', '6', '7', '8', '9', '13', '14', '32', '18', '21', '22', '60', '68']; var color = ['#f1cd44', '#0060a1', '#ed9b4f', '#ed9b4f', '#007e3a', '#cb0447', '#7a1a57', '#18472c', '#008193', '#83c39e', '#8a8c29', '#82352b', '#82352b', '#09a1e0', '#8a8c29', '#82352b', '#b6d300', '#09a1e0']; 接着遍历 lineNum,将 lineNum 中的元素和颜色传到 createLine 函数中,根据这两个参数来绘制地铁线路以及配色,毕竟 js 文件中的命名方式也是有规律的,哪一条线路,则命名后面一定会加上对应的数字,所以我们只需要将字符串与这个编号结合即可获得 js 中对应的数组了: createLine 的定义也非常简单,我的代码设置了不少的样式,所以看起来有点多。创建一个 ht.Polyline 管线,我们可以通过 polyline.addPoint() 函数向这个变量中添加具体的点,通过 setSegments 可以设置点的连接方式。 上面代码中添加地铁线上的点有分为几种情况,是因为 js 中设置线的时候 Line68 有一个“跳跃”点的现象,所以我们必须“跳跃”过去,篇幅有限 Line68 数组具体的声明自行看 subway.js。 这里说明一点,如果用的是 addPoint 函数,不设置 segments 时,默认将添加进的点用直线连接,segments 的定义如下: 1: moveTo,占用 1 个点信息,代表一个新路径的起点 2: lineTo,占用 1 个点信息,代表从上次最后点连接到该点 3: quadraticCurveTo,占用 2 个点信息,第一个点作为曲线控制点,第二个点作为曲线结束点 4: bezierCurveTo,占用 3 个点信息,第一和第二个点作为曲线控制点,第三个点作为曲线结束点 5: closePath,不占用点信息,代表本次路径绘制结束,并闭合到路径的起始点 所以我们要做“跳跃”的行为设置 segments 为 1 即可。 最后绘制这些地铁线上的点,这个部分 subway.js 中也分离出来了,命名以“mark_Point”、“t_Point”以及“n_Point”开头,我在前面 js 的展示部分有对这些数组进行解释,大家动动中指划上去看看。 我们在这些点的位置添加 ht.Node 节点,当节点一添加进 dm 数据容器中时,就会在拓扑图上显示,当然,前提是这个拓扑图组件 gv 设置的数据容器是这个 dm。篇幅有限,添加地铁线上的点的代码部分我只展示添加“换乘站点”的点: var tName = 't_Point' + num; var tP = window[tName];//大站点 if(tP) {//有些线路没有“换乘站点” for(let i = 0; i < tP.length; i++) { let node = createNode(tP[i].name, tP[i].value, color[index]);//在获取的线路上的点的坐标位置添加节点 node.s({//设置节点的样式style 'label.scale': 0.05,//文本缩放,可以避免浏览器限制的最小字号问题 'label.font': 'bold 12px arial, sans-serif'//设置文本的font }); node.setSize(0.6, 0.6);//设置节点大小。由于js中每个点之间的偏移量太小,所以我不得不把节点设置小一些 node.setImage('images/旋转箭头.json');//设置节点的图片 node.a('alarmColor1', 'rgb(150, 150, 150)');//attr属性,可以在这里面设置任何的东西,alarmColor1是在上面设置的image的json中绑定的属性,具体参看 HT for Web 矢量手册(http://www.hightopo.com/guide/guide/core/vector/ht-vector-guide.html#ref_binding) node.a('alarmColor2', 'rgb(150, 150, 150)');//同上 node.a('tpNode', true);//这个属性设置只是为了用来区分“换乘站点”和“小站点”的,后面会用上 } } 所有的地铁线路以及站点都添加完毕。但是!你可能会看不见自己绘制的图,因为他们太小了,这个时候可以设置 graphView 拓扑组件上的 fitContent 函数,我们顺便将拓扑图上的所有东西不可移动也设置一下: 这下你的地铁线路图就可以显示啦~接下来看看交互。 交互 首先是鼠标移动事件,鼠标滑过具体线路时,线路会变粗,悬停一会儿还能看到这条线路的编号;当鼠标移动到“换乘站点”或“小站点”,站点对应的图标都会变大并且变色,字体也会变大,鼠标移开图标变回原来的颜色并且字体变小。不同点在于鼠标移动到“换乘站点”时,“换乘站点”会旋转。 动图查看:http://images2017.cnblogs.com/blog/1159588/201802/1159588-20180209144029670-1869445696.gif 鼠标滑动事件,我直接基于 gv 的底层 div 进行的 mousemove 事件,通过 ht 封装的 getDataAt 函数传入事件 event 参数,获取事件下对应的节点,然后就可以随意操作节点了: gv.getView().addEventListener('mousemove', function(e) { var data = gv.getDataAt(e);//传入逻辑坐标点或者交互event事件参数,返回当前点下的图元 if(name) { originNode(name);//不管什么时候都要让节点保持原来的大小 } if (data instanceof ht.Polyline) {//判断事件节点的类型 dm.sm().ss(data);//选中“管道” name = ''; clearInterval(interval); } else if (data instanceof ht.Node) { if(data.getTag() !== name && data.a('tpNode')) {//若不是同一个节点,并且mousemove的事件对象为ht.Node类型,那么设置节点的旋转 interval = setInterval(function() { data.setRotation(data.getRotation() - Math.PI/16); //在自身旋转的基础上再旋转 }, 100); } if(data.a('npNode')) {//如果鼠标移到“小站点”也要停止动画 clearInterval(interval); } expandNode(data, name);////自定义的放大节点函数,比较容易,我不粘代码了,可以去http://hightopo.com/ 查看 dm.sm().ss(data);//设置选中节点 name = data.getTag();//作为“上一个节点”的存储变量,可以通过这个值来获取节点 } else {//其他任何情况则不选中任何内容并且清除“换乘站点”上的动画 dm.sm().ss(null); name = ''; clearInterval(interval); } }); 鼠标悬停在地铁线路上时显示“具体线路信息”,我是通过设置 tooltip 来完成的(注意:要打开 gv 的 tooltip 开关): 然后我利用右下角的 form 表单,单击表单上的具体线路,或者双击拓扑图上任意一个“站点”或者线路,则拓扑图会自适应到对应的部分,将被双击的部分展现到拓扑图的中央。 动图查看:https://images2018.cnblogs.com/blog/1159588/201803/1159588-20180304211422285-588264011.gif form 表单的声明部分我好像还没有解释。。。就是通过 new 一个 ht.widget.FomePane 类创建一个 form 表单组件,通过 form.getView() 获取表单组件的底层 div,将这个 div 摆放在 body 右下角,然后通过 addRow 函数向 form 表单中添加一行的表单项,可以在这行中添加任意多个项,通过 addRow 函数的第二个参数(一个数组),对添加进的表单项进行宽度的设置,通过第三个参数设置这行的高度: 单击“站点”显示红色标注,双击节点自适应放置到拓扑图中央以及双击空白处将红色标注隐藏的内容都是通过对拓扑组件 gv 的事件监听来控制的,非常清晰易懂,代码如下: var node = createRedLight();//创建一个新的节点,显示为“红灯”的样式 gv.mi(function(e) {//ht 中拓扑组件中的事件监听 if(e.kind === 'clickData' && (e.data.a('tpNode') || e.data.a('npNode'))) {//e.kind获取当前事件类型,e.data获取当前事件下的节点 node.s('2d.visible', true);//设置node节点可见 node.setPosition(e.data.getPosition().x, e.data.getPosition().y);//设置node的坐标为当前事件下节点的位置 } else if(e.kind === 'doubleClickData') {//双击节点 gv.fitData(e.data, false, 10);//将事件下的节点自适应到拓扑图的中央,参数1为自适应的节点,参数2为是否动画,参数3为gv与边框的padding } else if(e.kind === 'doubleClickBackground') {//双击空白处 node.s('2d.visible', false);//设置node节点不可见 查看 HT for Web 样式手册(http://www.hightopo.com/guide/guide/core/theme/ht-theme-guide.html#ref_style) } }); 注意 s(style) 和 a(attr) 定义是这样的,s 是 ht 预定义的一些样式属性,而 a 是我们用户来自定义的属性,一般是通过调用字符串来调用结果的,这个字符串对应的可以是常量也可以是函数,还是很灵活的。 最后还做了一个小小的部分,选中“站点”,则该“站点”的上方会显示一个红色的会“呼吸”的用来注明当前选中的“站点”。 “呼吸”的部分是利用 ht 的 setAnimation 函数来完成的,在用这个函数之前要先打开数据容器的动画开关,然后设置动画: 全部代码结束! 总结 这个 Demo 花了我两天时间完成,总觉得有点不甘心啊,但是有时候思维又转不过弯来,花费了不少的时间,但是总的来说收获还是很多的。 我以前一直以为只要通过 getPoints().push 来向多边形中添加点就可以了,求助了大神之后,发现原来这个方法不仅绕弯路而且还会出现各种各样的问题,比如 getPoints 之前,一定要在多边形中已经有 points 才可以,但是在很多情况下,初始化的 points 并不好设置,而且会造成代码很繁琐,直接通过 addPoint 方法,直接将点添加进多边形变量中,并且还会默认将点通过直线的方式连接,也不用设置 segments,多可爱的一个函数。 还有就是因为 ht 默认缩放大小是 20,而我这个 Demo 的间距又很小,导致缩放到最大地铁线路图显示也很小,所以我在 htconfig 中更改了 ht 的默认 zoomMax 属性,记住,更改这个值一定要在所有的 ht 调用之前,因为在 htconfig 中设置的值在后面定义都是不可更改的。 总而言之,这两天我的脑细胞死了不少,也重新生长了不少,人都是在不断进步的嘛~ 原文发布时间:2018年03月14日 00:00:00 原文作者:xhload3d 本文来源CSDN,如需转载请联系原作者
作者简介 周小辉是一名高级软件工程师,从事Java和Web程序开发,近期项目是基于Angular框架开发的web项目,今天他为大家带来项目开发中碰到的难点,该问题是Angular框架下实现动态加载架构的有效方案,希望对大家有所启发。 有这样一个需求,存在父子页面,子页面有个输入框,父页面有个提交按钮,点击提交按钮,触发子页面的输入框参数校验,如果校验失败,则显示提示信息。iframe子页面与父页面通信根据iframe中src属性是同域链接还是跨域链接,通信方式也不同。这里主要描述重点是基于Angular开发语言的交互,因此以同域链接场景为例。 我们知道同域链接场景父页面调用子页面方法: FrameName.window.childMethod(); 当获得子iframe窗口的window对象时就可以交互,子页面在基于传统的js框架,如jquery实现的话,会是下面这样 function childMethod(){ var number= $('#number').value(); if(number=='' || number.length>10){ alert('号码范围为1-10位数字'); } } jquery是基于dom开发的,但是在angular下,我们不直接操作dom,我们一般定义一个ngModel绑定到页面上,通过ngModel来获取值,这时,实现就无从下手了,好在Angular1.0提供了api,而Angular2.0没有开放任何接口 Angular1.0 index.html中实现childMethod function childMethod(){ var controllerScope = $('html[ng-controller="MainCtrl"]').scope(); controllerScope.check(); } //相应的controller实现一个check方法,并声明为rootscope方法就行了 app.controller('MainCtrl', function($scope, $rootScope) { }); app.controller('ChildCtrl', function($scope, $rootScope) { $scope.number = ''; $rootScope.check = function() { if(number=='' || number.length>10){ alert('号码范围为1-10位数字'); } }); Angular2.0 index.html中实现childMethod function childMethod(){ var component = window['child']; component .check(); } 组件实现check方法 export class ChildComponent implements OnInit { number:string; constructor(public changedDetector: ChangeDetectorRef, public translate: TranslateService) { window['child'] = this; } check(){ if(number=='' || number.length>10){ alert('号码范围为1-10位数字'); this.changedDetector.markForCheck(); //解决变量修改失效问题 this.changedDetector.detectChanges(); } } 可以看到,我们在ChildCompent的构造方法中,给window扩展了一个child属性,然后在子页面的index.html可以通过这个属性访问到该组件,是一个比较讨巧的用法;但是这样直接使用会有问题,当我们在ChildComponent 组件的check方法中操作原生的js语法时不会有问题,但是当我们需要修改某个ngModel的值时,会发现失效了,原因是我们通过 window['child'].check()时,浏览器没有监听到事件变化,因而没有重新渲染ChildComponent,我的理解是一般事件都是监听鼠标触发的,此时没有产生鼠标事件,自然监听不到;解决方案就是手动通知组件重新渲染,代码就是ChangeDetectorRef提供的markForCheck,detectChanges方法 原文发布时间:2018年04月18日 13:00:45 原文作者:周小辉 本文来源CSDN,如需转载请联系原作者
作者: 伯乐在线/chokcoco 先上张图,如何使用纯 CSS 制作如下效果? 在继续阅读下文之前,你可以先缓一缓。尝试思考一下上面的效果或者动手尝试一下,不借助 JS ,能否巧妙的实现上述效果。 OK,继续。这个效果是我在业务开发的过程中遇到的一个类似的小问题。其实即便让我借助 Javascript ,我的第一反应也是,感觉很麻烦啊。所以我一直在想,有没有可能只使用 CSS 完成这个效果呢? 定义需求 我们定义一下简单的规则,要求如下: <ul> <li>不可思议的CSS</li> <li>导航栏</li> <li>光标小下划线跟随</li> <li>PURE CSS</li> <li>Nav Underline</li> </ul> 导航栏目的 li 的宽度是不固定的 当从导航的左侧 li 移向右侧 li,下划线从左往右移动。同理,当从导航的右侧 li 移向左侧 li,下划线从右往左移动。 实现需求 第一眼看到这个效果,感觉这个跟随动画,仅靠 CSS 是不可能完成的。 如果想只用 CSS 实现,只能另辟蹊径,使用一些讨巧的方法。 好,下面就借助一些奇技淫巧,使用 CSS 一步一步完成这个效果。分析一下难点: 宽度不固定 第一个难点, li 的宽度是不固定的。所以,我们可能需要从 li 本身的宽度上做文章。 既然每个 li 的宽度不一定,那么它对应的下划线的长度,肯定是是要和他本身相适应的。自然而然,我们就会想到使用它的 border-bottom 。 li { border-bottom: 2px solid #000;} 那么,可能现在是这样子的(li 之间是相连在一起的,li 间的间隙使用 padding 产生): 当然,这里一开始都是没有下划线的,所以我们可能需要把他们给隐藏起来。 li { border-bottom: 0px solid #000;} 推翻重来,借助伪元素 这样好像不行,因为隐藏之后,hover li 的时候,需要下划线动画,而 li 本身肯定是不能移动的。所以,我们考虑借助伪元素。将下划线作用到每个 li 的伪元素之上。 li::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-bottom: 2px solid #000; } 下面考虑第一步的动画,hover 的时候,下划线要从一侧运动展开。所以,我们利用绝对定位,将 li 的伪元素的宽度设置为0,在 hover 的时候,宽度从 width: 0 -> width: 100%,CSS 如下: li::before { content: ""; position: absolute; top: 0; left: 0; width: 0; height: 100%; border-bottom: 2px solid #000; } li:hover::before { width: 100%; } 得到,如下效果: 左移左出,右移右出 OK,感觉离成功近了一步。现在还剩下一个最难的问题: 如何让线条跟随光标的移动动作,实现当从导航的左侧 li 移向右侧 li,下划线从左往右移动。同理,当从导航的右侧 li 移向左侧 li,下划线从右往左移动。 我们仔细看看,现在的效果: 当从第一个 li 切换到第二个 li 的时候,第一个 li 下划线收回的方向不正确。所以,可以能我们需要将下划线的初始位置位移一下,设置为 left: 100%,这样每次下划线收回的时候,第一个 li 就正确了: li::before { content: ""; position: absolute; top: 0; left: 100%; width: 0; height: 100%; border-bottom: 2px solid #000; } li:hover::before { left: 0; width: 100%; } 看看效果: 额,仔细对比两张图,第二种效果其实是捡了芝麻丢了西瓜。第一个 li 的方向是正确了,但是第二个 li 下划线的移动方向又错误了。 神奇的 ~ 选择符 所以,我们迫切需要一种方法,能够不改变当前 hover 的 li 的下划线移动方式却能改变它下一个 li 的下划线的移动方式(好绕口)。 没错了,这里我们可以借助 ~ 选择符,完成这个艰难的使命,也是这个例子中,最最重要的一环。 对于当前 hover 的 li ,其对应伪元素的下划线的定位是 left: 100%,而对于 li:hover ~ li::before,它们的定位是 left: 0。CSS 代码大致如下: li::before { content: ""; position: absolute; top: 0; left: 100%; width: 0; height: 100%; border-bottom: 2px solid #000; transition: 0.2s all linear; } li:hover::before { width: 100%; left: 0; } li:hover ~ li::before { left: 0; } 至此,我们想要的效果就实现拉!撒花。看看: 效果不错,就是有点僵硬,我们可以适当改变缓动函数以及加上一个动画延迟,就可以实现上述开头里的那个效果了。当然,这些都是锦上添花的点缀。 完整的DEMO可以戳这里: CodePen Demo -- 不可思议的CSS光标下划线跟随效果① 最后 本方法最大的瑕疵在于一开始进入第一个 li 的时候,线条只能是从右往左,除此之外,都能很好的实现跟随效果。 更多精彩 CSS 技术文章汇总在我的 Github -- iCSS② ,持续更新,欢迎点个 star 订阅收藏。 好了,本文到此结束,希望对你有帮助 :) 如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。 文章中的链接 ① https://codepen.io/Chokcoco/pen/PRJvLN ② https://github.com/chokcoco/iCSS 原文发布时间:2018年04月18日 14:25:45 原文作者:伯乐在线/chokcoco 本文来源CSDN,如需转载请联系原作者
本次知识点: 1.分页 2.标签 3.徽章 4.巨幕 5.页头 6.缩略图 7.警告框 1.分页 (1)默认的分页 <ul class="pagination"> <li><a href="">«</a></li> <li><a href="">1</a></li> <li><a href="">2</a></li> <li><a href="">3</a></li> <li><a href="">»</a></li> </ul> (2)禁用和激活的状态 <ul class="pagination"> <li class="disabled"><a href="">«</a></li> <li class="active"><a href="">1</a></li> <li><a href="">2</a></li> <li><a href="">3</a></li> <li><a href="">»</a></li> </ul> (3)分页的尺寸 pagination-lg 、 pagination-sm <ul class="pagination pagination-lg"> <li><a href="#">«</a></li> <li><a href="#">1</a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">4</a></li> <li><a href="#">5</a></li> <li><a href="#">»</a></li> </ul> <ul class="pagination pagination-sm"> <li><a href="#">«</a></li> <li><a href="#">1</a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">4</a></li> <li><a href="#">5</a></li> <li><a href="#">»</a></li> </ul> (4)翻页 Pager <ul class="pager"> <li><a href="">previous</a></li> <li><a href="">next</a></li> </ul> (5)对齐的链接 <ul class="pager"> <li class="previous"><a href="">← previous</a></li> <li class="next"><a href="">next →</a></li> </ul> (6)可选的禁用状态 <ul class="pager"> <li class="previous disabled"><a href="">← previous</a></li> <li class="next"><a href="">next →</a></li> </ul> 2.标签 <span class="label label-default">Default</span> <span class="label label-primary">Primary</span> <span class="label label-success">Success</span> <span class="label label-info">Info</span> <span class="label label-warning">Warning</span> <span class="label label-danger">Danger</span> 3.徽章 badge <a href="">Messages <span class="badge">20</span></a> <button class="btn btn-default">Messages <span class="badge">20</span></button> 4.巨幕 jumbotron <div class="jumbotron"> <div class="container"> <h1>hello world!!!</h1> <p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p> <p><a class="btn btn-primary" href="">Learn more</a></p> </div> </div> 5.页头 page-header <div class="page-header"> <h1>Example page header <small>Subtext for header</small> </h1> </div> 6.缩略图 (1)默认样式 <div class="col-md-3 col-sm-6"> <a class="thumbnail" href=""> <img src="images/kittens.jpg" alt=""/> </a> </div> (2)自定义内容 <div class="col-md-3 col-sm-6"> <div class="thumbnail"> <img src="images/kittens.jpg" alt=""/> <div class="caption"> <!--text-center--> <h3>缩略图标签</h3> <p>一些示例文本。一些示例文本。</p> <p> <a href="#" class="btn btn-primary" role="button">按钮</a> <a href="#" class="btn btn-default" role="button">按钮 </a> </p> </div> </div> </div> 7.警告框 (1)基本默认样式 <div class="alert alert-success">成功!很好地完成了提交。</div> <div class="alert alert-info">信息!请注意这个信息。</div> <div class="alert alert-warning">警告!请不要提交。</div> <div class="alert alert-danger">错误!请进行一些更改。</div> (2)可关闭的警告框 <button type="button" class="close" data-dismiss="alert">×</button> (3)警告中的链接 <div class="alert alert-success"> <a href="#" class="alert-link">成功!很好地完成了提交。</a> </div> 原文发布时间:2018年06月13日 09:21:52 原文作者:Roger_CoderLife 本文来源CSDN,如需转载请联系原作者
编译:伯乐在线/cathyhu916 当人们尝试学习 JavaScript , 或者其他编程技术的时候,常常会遇到同样的挑战: 有些概念容易混淆,特别是当你学习过其他语言的时候。 很难找到学习的时间(有时候是动力)。 一旦当你理解了一些东西的时候,却很容易再一次忘记。 可以使用的工具甚多且经常变化,所以不知道从哪里开始入手。 幸运的是,这些挑战最终都可以被战胜。在这篇文章里,我将介绍 6 个思维技巧来帮你更快的学习 JavaScript ,让你成为一个更快乐更多产的程序员。 1.不要让将来的决定阻止你进步 对于很多学习 JavaScript 的人来说,他们问的第一个问题是选用哪个框架(现有框架非常多)。但是如果你还不熟悉原生的 JavaScript ,那这就是个不该问的问题。你会花费你全部的时间去查询不同的框架并且不会取得任何进展。 走出这个让人犹豫不决的陷阱的一个方法是要有一个学习的路线图。比如,要想成为一个前端开发人员,你的路线图大概是这样的: 把学习计划进一步拆分,你可以只用 HTML 和 CSS 做一个功能性的网页。了解其中的具体步骤,你会很容易知道现在需要关注的东西,因此不会浪费时间担心将来要学习的内容。 如果觉得此文章有用的话,更多详情请访问 learning road map for becoming a front-end developer 。 2.不要让自信把你骗进遗忘陷阱 在学习 JavaScript 的过程中,快速理解某个概念可能是最不利于你进步的一件事。请允许我解释一下。 当你理解一些东西并且觉得它言之有理的时候,你会倾向于立即学习下面的内容。可能你会理解下面的内容并继续向下学习。但是很快,你会发现你已经忘记了之前所学到的一些东西,因此你需要重新复习。你很快的瞥一眼之前的概念更新下记忆然后继续往后学习。但是,这次你又忘记了其他的一些东西。你会不停的反反复复直到你发现你完全的迷失了方向。你会感到气馁,休息一下后,你准备重新开始,却发现已经忘记了所有的东西。 幸运的是只需要简单的两步就可以解决这个问题: 1. 限制一次学习的内容总量 2. 认真的练习——写代码 当你学习一些新的概念的时候,一定要多尝试,多应用,多熟悉,甚至将它与其他的概念相结合。在你学习的示例中写代码非常重要,因为这有助于你深刻理解它。同时,限制一次学习的内容总量有助于你记住这些内容,因为记住较少的内容会更容易。 这个过程可能会比仅仅通读一遍就学习其它内容要花费更多的时间,然而实际上它需要的时间更少,因为你不需要来回反复。经过多次尝试,我终于掌握了这种方法。 3. 用正确的心态进行实战练习 很多人认为练习是件重复而又无趣的事情,所以他们常常会跳过练习试着走捷径。如果你试图在 JavaScript 的练习上走捷径,实际上你需要更长的时间来学习它。但是,怎样才能让练习变得更有趣,让你愿意去做练习呢? 尝试转换一下思路: 如果你学了一个新的 JavaScript 的概念却无法尝试,你会有什么样的感受?对于我个人而言,我会觉得懊恼,特别是在我花费了时间去理解它之后。就像一个孩子有了一件新的玩具却不能玩一样。 当你学一些新的 JavaScript 的知识时,试着像对待一个新玩具、一辆新车、一双新鞋或者其它你有兴趣尝试的东西一样。像玩一样练习,而不是像工作一样练习。用新技能做一些很棒的事情。给自己一些惊喜同时展示给你的朋友。 保持娱乐的心态,你会学的更快,记住的时间更长,而且你会觉得更有趣。 4.用Facebook的窍门找时间编程 人们常见的问题之一是没有时间去编程。但是这些人却可以在 Facebook , YouTube , Wikipedia 或者 Reddit 这样的网站上花费数小时的时间。不管你是不是也有这样的情况,其中都有值得学习的地方。 很多时候我只是想看一小会儿的 Facebook ,结果我却在那儿停留了好几个小时。为什么会这样呢?这恰恰是因为我并没有打算在那儿停留太长时间。万事开头难,我发现把目标降低会更容易投入。如果有人问我是否准备在 Facebook 上花费几个小时,我会说不,因为我没有那些时间。然而,我更愿意接受快速查看某件事情的想法,我就是这样被吸引进去的。 好消息是你可以用同样的心理优势去学习编程。不要试图花几个小时去编程,因为你找不到这样的时间。告诉自己只写三分钟的代码,你就不会再为找时间而挣扎了。 5. 思考地越慢,学地越快 这句话听上去有些违背常理,所以我会用一个故事来解释。 我的一个朋友曾经对 JavaScript 的某些特性感到困惑。我让他和我一起过一遍他知道的内容然后解释一下哪一部分让人困惑。当他检查代码片段的时候,我注意到他有些急躁。 “等等!”我说。“慢一点,跟我解释下这里的每一步。” 我的朋友接着向我解释了这段代码的作用。 我又一次打断他,“你还是太急了。再试一次,但是这一次,我需要你逐字逐句的跟我解释每行代码并且告诉我代码的作用。” 这一次,我的朋友能够更好的解释代码中发生的事情。其中的关键是他有花时间去逐步检查每行代码而不是企图一下子理解全部。 在这样的情况下,思考地越慢实际上能让你学地更快。 6.先用简单语言编写复杂代码 如果一段代码太复杂或者陌生,就先用简单语言写出来。这样,你可以在实际编写代码前弄清楚你想要代码做什么。这种方法有两个好处: 1.代码写起来会更容易更快因为你不需要总是停下来去思考它该怎样运行。 2.可以提前捕获 bug 因为你很清楚代码的作用。 结论 我们已经了解了快速学习 JavaScript 的几个方法,你也可以运用这些技巧去学习其他的技能。下面概括一下我们讲的内容: 不要担心将来的决定,要潜心学习。 用对待玩具的心态对待新技能会让练习更加有趣。 就像玩 Facebook , YouTube 或者 Wikipedia 那样,用小目标的方法找时间编程。 慢下来,步子小一点,你会学的更快。 你是怎样学习的呢?你有没有其他的一些秘诀或者技巧是我没有提到的呢?或者你觉得这些都是瞎扯,而进步的唯一方法是一天投入12个小时。无论如何,我期待你们的评论。 原文发布时间:2018年03月12日 00:00:00 原文作者:前端大全 本文来源CSDN,如需转载请联系原作者
1 用户名正则 //用户名正则,4到16位(字母,数字,下划线,减号) var uPattern = /^[a-zA-Z0-9_-]{4,16}$/; //输出 true console.log(uPattern.test("caibaojian")); 2 密码强度正则 //密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符 var pPattern = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/; //输出 true console.log("=="+pPattern.test("caibaojian#")); 3 整数正则 //正整数正则 var posPattern = /^[1-9]\d*$/; //负整数正则 var negPattern = /^-\d+$/; //整数正则 var intPattern = /^-?\d+$/; //输出 true console.log(posPattern.test("42")); //输出 true console.log(negPattern.test("-42")); //输出 true console.log(intPattern.test("-42")); 4 数字正则(可以是整数也可以是浮点数) //正数正则 var posPattern = /^\d*\.?\d+$/; //负数正则 var negPattern = /^-\d*\.?\d+$/; //数字正则 var numPattern = /^-?\d*\.?\d+$/; console.log(posPattern.test("42.2")); console.log(negPattern.test("-42.2")); console.log(numPattern.test("-42.2")); 5 Email正则 //Email正则 var ePattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; //输出 true console.log(ePattern.test(<a href="mailto:99154507@qq.com" rel="external nofollow">99154507@qq.com</a>)); 6 手机号码正则 //手机号正则 var mPattern = /^1[34578]\d{9}$/; //http://caibaojian.com/regexp-example.html //输出 true console.log(mPattern.test("15507621888")); 7 身份证号正则 //身份证号(18位)正则 var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; //输出 true console.log(cP.test("11010519880605371X")); 8 URL正则 //URL正则 var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; //输出 true console.log(urlP.test(<a href="http://caibaojian.com" rel="external nofollow">http://caibaojian.com</a>)); 9 IPv4地址正则 //ipv4地址正则 var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; //输出 true console.log(ipP.test("115.28.47.26")); 10 十六进制颜色正则 //RGB Hex颜色正则 var cPattern = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/; //输出 true console.log(cPattern.test("#b8b8b8")); 11 日期正则 //日期正则,简单判定,未做月份及日期的判定 var dP1 = /^\d{4}(\-)\d{1,2}\1\d{1,2}$/; //输出 true console.log(dP1.test("2017-05-11")); //输出 true console.log(dP1.test("2017-15-11")); //日期正则,复杂判定 var dP2 = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/; //输出 true console.log(dP2.test("2017-02-11")); //输出 false console.log(dP2.test("2017-15-11")); //输出 false console.log(dP2.test("2017-02-29")); 12 QQ号码正则 //QQ号正则,5至11位 var qqPattern = /^[1-9][0-9]{4,10}$/; //输出 true console.log(qqPattern.test("65974040")); 13 微信号正则 //微信号正则,6至20位,以字母开头,字母,数字,减号,下划线 var wxPattern = /^[a-zA-Z]([-_a-zA-Z0-9]{5,19})+$/; //输出 true console.log(wxPattern.test("caibaojian_com")); 14 车牌号正则 //车牌号正则 var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/; //输出 true console.log(cPattern.test("粤B39006")); 15 包含中文正则 //包含中文正则 var cnPattern = /[\u4E00-\u9FA5]/; //输出 true console.log(cnPattern.test("董董董")); 原文链接:http://blog.csdn.net/messicr7/article/details/74908286 原文发布时间:2018年03月13日 10:27:51 原文作者:yufengaotian 本文来源CSDN,如需转载请联系原作者
编译:伯乐在线/Wing 在软件中,性能一直扮演着重要的角色。在Web应用中,性能变得更加重要,因为如果页面速度很慢的话,用户就会很容易转去访问我们的竞争对手的网站。作为专业的web开发人员,我们必须要考虑这个问题。有很多“古老”的关于性能优化的最佳实践在今天依然可行,例如最小化请求数目,使用CDN以及不编写阻塞页面渲染的代码。然而,随着越来越多的web应用都在使用JavaScript,确保我们的代码运行的很快就变得很重要。 假设你有一个正在工作的函数,但是你怀疑它运行得没有期望的那样快,并且你有一个改善它性能的计划。那怎么去证明这个假设呢?在今天,有什么最佳实践可以用来测试JavaScript函数的性能呢?一般来说,完成这个任务的最佳方式是使用内置的performance.now()函数,来衡量函数运行前和运行后的时间。 在这篇文章中,我们会讨论如何衡量代码运行时间,以及有哪些技术可以避免一些常见的“陷阱”。 Performance.now() 高分辨率时间API提供了一个名为now()的函数,它返回一个DOMHighResTimeStamp对象,这是一个浮点数值,以毫秒级别(精确到千分之一毫秒)显示当前时间。单独这个数值并不会为你的分析带来多少价值,但是两个这样的数值的差值,就可以精确描述过去了多少时间。 这个函数除了比内置的Date对象更加精确以外,它还是“单调”的,简单说,这意味着它不会受操作系统(例如,你笔记本上的操作系统)周期性修改系统时间影响。更简单的说,定义两个Date实例,计算它们的差值,并不代表过去了多少时间。 “单调性”的数学定义是“(一个函数或者数值)以从不减少或者从不增加的方式改变”。 我们可以从另外一种途径来解释它,即想象使用它来在一年中让时钟向前或者向后改变。例如,当你所在国家的时钟都同意略过一个小时,以便最大化利用白天的时间。如果你在时钟修改之前创建了一个Date实例,然后在修改之后创建了另外一个,那么查看这两个实例的差值,看上去可能像“1小时零3秒又123毫秒”。而使用两个performance.now()实例,差值会是“3秒又123毫秒456789之一毫秒”。 在这一节中,我不会涉及这个API的过多细节。如果你想学习更多相关知识或查看更多如何使用它的示例,我建议你阅读这篇文章:Discovering the High Resolution Time API。 既然你知道高分辨率时间API是什么以及如何使用它,那么让我们继续深入看一下它有哪些潜在的缺点。但是在此之前,我们定义一个名为makeHash()的函数,在这篇文章剩余的部分,我们会使用它。 function makeHash(source) { var hash = 0; if (source.length === 0) return hash; for (var i = 0; i < source.length; i++) { var char = source.charCodeAt(i); hash = ((hash<<5)-hash)+char; hash = hash & hash; // Convert to 32bit integer } return hash; } 我们可以通过下面的代码来衡量这个函数的执行效率: var t0 = performance.now(); var result = makeHash('Peter'); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result); 如果你在浏览器中运行这些代码,你应该看到类似下面的输出: Took 0.2730 milliseconds to generate: 77005292 这段代码的在线演示如下所示: 记住这个示例后,让我们开始下面的讨论。 缺陷1 – 意外衡量不重要的事情 在上面的示例中,你可以注意到,我们在两次调用performance.now()中间只调用了makeHash()函数,然后将它的值赋给result变量。这给我们提供了函数的执行时间,而没有其他的干扰。我们也可以按照下面的方式来衡量代码的效率: var t0 = performance.now(); console.log(makeHash('Peter')); // bad idea! var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds'); 这个代码片段的在线演示如下所示: 但是在这种情况下,我们将会测量调用makeHash(‘Peter’)函数花费的时间,以及将结果发送并打印到控制台上花费的时间。我们不知道这两个操作中每个操作具体花费多少时间, 只知道总的时间。而且,发送和打印输出的操作所花费的时间会依赖于所用的浏览器,甚至依赖于当时的上下文。 或许你已经完美的意识到console.log方式是不可以预测的。但是执行多个函数同样是错误的,即使每个函数都不会触发I/O操作。例如: var t0 = performance.now(); var name = 'Peter'; var result = makeHash(name.toLowerCase()).toString(); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result); 同样,我们不会知道执行时间是怎么分布的。它会是赋值操作、调用toLowerCase()函数或者toString()函数吗? 缺陷 #2 – 只衡量一次 另外一个常见的错误是只衡量一次,然后汇总花费的时间,并以此得出结论。很可能执行不同的次数会得出完全不同的结果。执行时间依赖于很多因素: 编辑器热身的时间(例如,将代码编译成字节码的时间) 主线程可能正忙于其它一些我们没有意识到的事情 你的电脑的CPU可能正忙于一些会拖慢浏览器速度的事情 持续改进的方法是重复执行函数,就像这样: var t0 = performance.now(); for (var i = 0; i < 10; i++) { makeHash('Peter'); } var t1 = performance.now(); console.log('Took', ((t1 - t0) / 10).toFixed(4), 'milliseconds to generate'); 这个示例的在线演示如下所示: 这种方法的风险在于我们的浏览器的JavaScript引擎可能会使用一些优化措施,这意味着当我们第二次调用函数时,如果输入时相同的,那么JavaScript引擎可能会记住了第一次调用的输出,然后简单的返回这个输出。为了解决这个问题,你可以使用很多不同的输入字符串,而不用重复的使用相同的输入(例如‘Peter’)。显然,使用不同的输入进行测试带来的问题就是我们衡量的函数会花费不同的时间。或许其中一些输入会花费比其它输入更长的执行时间。 缺陷 #3 – 太依赖平均值 在上一节中,我们学习到的一个很好的实践是重复执行一些操作,理想情况下使用不同的输入。然而,我们要记住使用不同的输入带来的问题,即某些输入的执行时间可能会花费所有其它输入的执行时间都长。这样让我们退一步来使用相同的输入。假设我们发送同样的输入十次,每次都打印花费了多长时间。我们会得到像这样的输出: Took 0.2730 milliseconds to generate: 77005292 Took 0.0234 milliseconds to generate: 77005292 Took 0.0200 milliseconds to generate: 77005292 Took 0.0281 milliseconds to generate: 77005292 Took 0.0162 milliseconds to generate: 77005292 Took 0.0245 milliseconds to generate: 77005292 Took 0.0677 milliseconds to generate: 77005292 Took 0.0289 milliseconds to generate: 77005292 Took 0.0240 milliseconds to generate: 77005292 Took 0.0311 milliseconds to generate: 77005292 请注意第一次时间和其它九次的时间完全不一样。这很可能是因为浏览器中的JavaScript引擎使用了优化措施,需要一些热身时间。我们基本上没有办法避免这种情况,但是会有一些好的补救措施来阻止我们得出一些错误的结论。 一种方式是去计算后面9次的平均时间。另外一种更加使用的方式是收集所有的结果,然后计算“中位数”。基本上,它会将所有的结果排列起来,对结果进行排序,然后取中间的一个值。这是performance.now()函数如此有用的地方,因为无论你做什么,你都可以得到一个数值。 让我们再试一次,这次我们使用中位数函数: var numbers = []; for (var i=0; i < 10; i++) { var t0 = performance.now(); makeHash('Peter'); var t1 = performance.now(); numbers.push(t1 - t0); } function median(sequence) { sequence.sort(); // note that direction doesn't matter return sequence[Math.ceil(sequence.length / 2)]; } console.log('Median time', median(numbers).toFixed(4), 'milliseconds'); 缺陷 #4 – 以可预测的方式比较函数 我们已经理解衡量一些函数很多次并取平均值总会是一个好主意。而且,上面的示例告诉我们使用中位数要比平均值更好。 在实际中,衡量函数执行时间的一个很好的用处是来了解在几个函数中,哪个更快。假设我们有两个函数,它们的输入参数类型一致,输出结果相同,但是它们的内部实现机制不一样。 例如,我们希望有一个函数,当特定的字符串在一个字符串数组中存在时,函数返回true或者false,但这个函数在比较字符串时不关心大小写。换句话说,我们不能直接使用Array.prototype.indexOf方法,因为这个方法是大小写敏感的。下面是这个函数的一个实现: function isIn(haystack, needle) { var found = false; haystack.forEach(function(element) { if (element.toLowerCase() === needle.toLowerCase()) { found = true; } }); return found; } console.log(isIn(['a','b','c'], 'B')); // true console.log(isIn(['a','b','c'], 'd')); // false 我们可以立刻发现这个方法有改进的地方,因为haystack.forEach循环总会遍历所有的元素,即使我们可以很快找到一个匹配的元素。现在让我们使用for循环来编写一个更好的版本。 function isIn(haystack, needle) { for (var i = 0, len = haystack.length; i < len; i++) { if (haystack[i].toLowerCase() === needle.toLowerCase()) { return true; } } return false; } console.log(isIn(['a','b','c'], 'B')); // true console.log(isIn(['a','b','c'], 'd')); // false 现在我们来看哪个函数更快一些。我们可以分别运行每个函数10次,然后收集所有的测量结果: function isIn1(haystack, needle) { var found = false; haystack.forEach(function(element) { if (element.toLowerCase() === needle.toLowerCase()) { found = true; } }); return found; } function isIn2(haystack, needle) { for (var i = 0, len = haystack.length; i < len; i++) { if (haystack[i].toLowerCase() === needle.toLowerCase()) { return true; } } return false; } console.log(isIn1(['a','b','c'], 'B')); // true console.log(isIn1(['a','b','c'], 'd')); // false console.log(isIn2(['a','b','c'], 'B')); // true console.log(isIn2(['a','b','c'], 'd')); // false function median(sequence) { sequence.sort(); // note that direction doesn't matter return sequence[Math.ceil(sequence.length / 2)]; } function measureFunction(func) { var letters = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(','); var numbers = []; for (var i = 0; i < letters.length; i++) { var t0 = performance.now(); func(letters, letters[i]); var t1 = performance.now(); numbers.push(t1 - t0); } console.log(func.name, 'took', median(numbers).toFixed(4)); } measureFunction(isIn1); measureFunction(isIn2); 我们运行上面的代码, 可以得出如下的输出: true false true false isIn1 took 0.0050 isIn2 took 0.0150 这个示例的在线演示如下所示: 到底发生了什么?第一个函数的速度要快3倍!那不是我们假设的情况。 其实假设很简单,但是有些微妙。第一个函数使用了haystack.forEach方法,浏览器的JavaScript引擎会为它提供一些底层的优化,但是当我们使用数据索引技术时,JavaScript引擎没有提供对应的优化。这告诉我们:在真正测试之前,你永远不会知道。 结论 在我们试图解释如何使用performance.now()方法得到JavaScript精确执行时间的过程中,我们偶然发现了一个基准场景,它的运行结果和我们的直觉相反。问题在于,如果你想要编写更快的web应用,我们需要优化JavaScript代码。因为计算机(几乎)是一个活生生的东西,它很难预测,有时会带来“惊喜”,所以如果了解我们代码是否运行更快,最可靠的方式就是编写测试代码并进行比较。 当我们有多种方式来做一件事情时,我们不知道哪种方式运行更快的另一个原因是要考虑上下文。在上一节中,我们执行一个大小写不敏感的字符串查询来寻找1个字符串是否在其它26个字符串中。当我们换一个角度来比较1个字符串是否在其他100,000个字符串中时,结论可能是完全不同的。 上面的列表不是很完整的,因为还有更多的缺陷需要我们去发现。例如,测试不现实的场景或者只在JavaScript引擎上测试。但是确定的是对于JavaScript开发者来说,如果你想编写更好更快的Web应用,performance.now()是一个很棒的方法。最后但并非最不重要,请谨记衡量执行时间只是“更好的代码”的一反面。我们还要考虑内存消耗以及代码复杂度。 怎么样?你是否曾经使用这个函数来测试你的代码性能?如果没有,那你是怎么来测试性能的?请在下面的评论中分享你的想法,让我们开始讨论吧! 原文发布时间:2018年03月18日 00:00:00 原文作者:前端大全 本文来源CSDN,如需转载请联系原作者
Bootstrap本次知识点: 1.导航 2.导航条 3.面包屑导航 1.导航(标签) (1)标签页 nav-tabs <ul class="nav nav-tabs"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li><a href="">Message</a></li> </ul> (2)胶囊式标签页 nav-pills <ul class="nav nav-pills"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li><a href="">Message</a></li> </ul> (3)垂直的胶囊式标签页 nav-stacked <ul class="nav nav-pills nav-stacked"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li><a href="">Message</a></li> </ul> (4)两端对齐的标签页 nav-justified <ul class="nav nav-pills nav-justified"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li><a href="">Message</a></li> </ul> (5)禁用的链接 disabled <ul class="nav nav-tabs"> <li class="active"><a href="">Home</a></li> <li class="disabled"><a href="">Project</a></li> <li><a href="">Message</a></li> </ul> (6)带有下拉菜单的标签 <ul class="nav nav-tabs"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li class="dropdown"> <a href="" class="dropdown-toggle" data-toggle="dropdown">Message <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="">关于</a></li> <li><a href="">资讯</a></li> <li><a href="">通讯</a></li> </ul> </li> </ul> (7)带有下拉菜单的胶囊式标签 <ul class="nav nav-pills"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li class="dropdown"> <a href="" class="dropdown-toggle" data-toggle="dropdown">Message <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="">关于</a></li> <li><a href="">资讯</a></li> <li><a href="">通讯</a></li> </ul> </li> </ul> 2.导航条(navbar <nav>标签class属性中添加navbar navbar-default) (1)默认的导航栏 <nav class="navbar navbar-default"> <div class="navbar-header"> <a class="navbar-brand" href="">教育事业</a> </div> <ul class="nav navbar-nav"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li class="dropdown"> <a href="" class="dropdown-toggle" data-toggle="dropdown">Message <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="">关于</a></li> <li><a href="">资讯</a></li> <li><a href="">通讯</a></li> </ul> </li> </ul> </nav> (2)响应式导航栏 <nav class="navbar navbar-default"> <div class="navbar-header"> <button class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="">教育事业</a> </div> <div class="collapse navbar-collapse" id="navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li class="dropdown"> <a href="" class="dropdown-toggle" data-toggle="dropdown">Message <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="">关于</a></li> <li><a href="">资讯</a></li> <li><a href="">通讯</a></li> </ul> </li> </ul> </div> </nav> (3)导航栏中的表单 <form action="" class="navbar-form navbar-right"> <div class="form-group"> <input class="form-control" type="text" placeholder="Search"/> </div> <button class="btn btn-default">Search</button> </form> (4)导航栏中的按钮 navbar-btn <button class="btn btn-default navbar-btn">Submit</button> (5)导航栏中的文本 navbar-text <p class="navbar-text">Signed in as Thomas</p> (6)固定到顶部、底部 navbar-fixed-top 、navbar-fixed-bottom <nav class="navbar navbar-default navbar-fixed-top"> <div class="navbar-header"> <a class="navbar-brand" href="">教育事业</a> </div> <ul class="nav navbar-nav"> <li class="active"><a href="">Home</a></li> <li><a href="">Project</a></li> <li class="dropdown"> <a href="" class="dropdown-toggle" data-toggle="dropdown">Message <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="">关于</a></li> <li><a href="">资讯</a></li> <li><a href="">通讯</a></li> </ul> </li> </ul> </nav> (7)静态的顶部 navbar-static-top <nav class="navbar navbar-default navbar-static-top"> ... </nav> (8)倒置的导航栏 带有黑色背景白色文本的倒置的导航栏 navbar-inverse <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"></div> 3.面包屑导航 <ul class="breadcrumb"> <li><a href="">首页</a></li> <li><a href="">列表</a></li> <li class="active">详情</li> </ul> 原文发布时间:2018年06月11日 15:12:56 原文作者:Roger_CoderLife 本文来源CSDN,如需转载请联系原作者
开发板配置 代码下载 代码移植 功能调试 驱动代码提交 1 开发板配置 我们验证选用的开发板是基于STM32L496VGTx芯片研发的一款物联网开发板。其内核为ARM 32位Cortex-M4 CPU,最高80MHZ的主频率,1MB的闪存,320KB的SRAM,最多支持136个高速IO口,还支持SPI,CAN,I2C,I2S,USB,UART等常用的外设接口。 单板的背面有arduino接口,当前验证使用的外接sensor主要基于I2C总线进行连接。 developer kit开发板环境配置请参考链接: https://github.com/alibaba/AliOS-Things/wiki/AliOS-Things-Studio 2 代码下载 代码下载前,请确认已在github注册账号,链接及注册流程如下: https://github.com/ 为了便于后续的代码的审核提交,注册github账号时请使用本公司的邮箱 打开以下代码链接后,可以通过以下方式下载代码。首先选择代码分支; https://github.com/andy2012zwj/AliOS-Things/tree/aos_udata 然后选择zip格式下载; 3 代码移植 uData介绍以及传感器驱动移植请参考以下链接:https://github.com/alibaba/AliOS-Things/wiki/AliOS-Things-uData-Sensor-Driver-Porting-Guide.zh 以,在developer kit板上,需要注意的地方是总线配置: i2c_dev_t ####_ctx = { .port = 3, /*developer kit上外接I2C的port为3*/ .config.dev_addr = 0x5D, /* 从设备I2C地址 */ }; 4 功能调试 下面以developer kit板为例说明linkkit用例的调试过程。 4.1 编译 example\uDataapp目录下已集成了相关的用例代码,2、3两个章节完成配置修改后,执行以下命令则可以编译用例 aos make udataapp@developerkit 编译完成后,生成的可执行文件为out\udataapp@developerkit\binary\udataapp@developerkit.bin 4.2 文件烧录 本示例采用ST-LINK工具烧写bin文件,用户也可参考developer kit板环境配置说明中的其他方法; 4.3 用例执行 烧录完成后,复位单板,开始运行;如果配置流程没有错误,则可以在串口看到sensor通过udata上报的数据。 其中物理传感器对应的服务类型,请参考结构体udata_type_e; 物理传感器的上报的数据单位,请参考以下链接中的《传感器数据单位》章节 https://github.com/alibaba/AliOS-Things/wiki/AliOS-Things-uData-Sensor-Driver-Porting-Guide.zh 5 驱动代码提交 如果功能测试完成无误,则可以参考以下链接中外部代码提交方式,向AliOS Things提交代码和入申请: https://lark.alipay.com/aliosthings/wiki/workflow-githubcontrib 待AliOS对其做相关的认证后,则可以集成到AliOS Things中。 | Home | Tutorial | Hardware | Porting Guide | Utilities | API Reference | Technical Docs | Certification | Crafted with by AliOS Things Team.
问题汇总: rel_1.3.0 编译运行hardfault问题 一、 下载/安装 Visual Studio Code https://code.visualstudio.com/ 安装 AliOS Studio 插件 安装 C/C++ 插件,搜索c/c++ 安装 alios-studio 插件,搜索alios-studio 二、请使用Windows7 64bit,使用Install_Win脚本 可以直接使用我们准备好的一键安装包:http://p28phe5s5.bkt.clouddn.com/setup_windows.zip 全部默认下一步就好 不要修改该路径,真的会出错,有洁癖,自行研究吧 如果出现如下问题: 更新下: (1)到C:\Python27\Scripts, 执行:pip install wheel (2)安装setuptools,下载setuptools源码setuptools-25.2.0.tar.gz 这是一个压缩文件,将其解压到桌面,并进入该文件夹,输入:python setup.py install 地址:https://pypi.python.org/pypi/setuptools 三、拉取最新版本,从master https://github.com/alibaba/AliOS-Things 四、 下载gcc 五、 在visul studio code 文件->打开文件夹 六、编译 如出现: 添加Python27 的环境变量 七、下载 运行成功: &附录 1、如果是新的板子(焊接了DAP芯片),烧录请注意: 待续。。。
C-SKY IoT开发板(CB2201)快速上阿里云指南 本文基于 AliOS Things 1.3.x版本,手把手教你在C-SKY CB2201开发板上使用MQTT通道上云 1 硬件环境搭建 1.1 开发板准备 1.1.1 CB2201开发板介绍 ① CB2201开发板是杭州中天微自主设计的一款用于开发 IoT 应用的开发板。 ② 板上集成 CH2201 芯片,集成 CPU 调试器CKLink,只需要一根 USB 线就可以进行供电、调试、下载等操作。 ③ 集成两个子板接口,每个子板接口中都集成了UART/SPI/IIC/ADC/PWM/GPIO等外设,可以连接各类接口兼容的功能子板,包括中天微设计的ENC28J60 SPI有线网卡子板,ESP8266-WiFi子板,传感器子板等。 1.1.2 CH2201芯片介绍 ① 内置32BIT C-SKY CK802T CPU@up to 48MHz ② 256KB XIP eFlash,80KB SRAM ③ REE/TEE运行环境隔离 ④ 硬件加解密引擎,支持AES、RSA、SHA、TRNG等算法 ⑥ 34个独立可编程、复用通用输入输出接口,包括:SPI×2, UART×3, I2C×2, ADC×16, PWM×4, GPIO×34, I2S×1, ACMP×1 ⑦ 定时器相关资源:CoreTIM×1, RTC×2, Timer×2, WDT×1 ⑧ 待机功耗 <3uA ⑨ 使用QFP-64-0.4mm封装 1.1.3 ESP8266 WiFi子板 WiFi子板介绍:略 1.1.4 开发板获取: https://item.taobao.com/item.htm?id=570014022304 1.2 开发板连接方法 1.2.1 WiFi子板连接 1.2.2 串口线连接 1.2.3 电源连接 通过USB线供电,图略 2 云端和通道环境搭建 2.1 在云端主要包括以下几步 参考链接( https://github.com/alibaba/AliOS-Things/wiki/Manual-Channel-MQTT ),做如下操作: 1、创建阿里云账号 2、创建测试产品,拿到ProductKey 3、创建测试设备,拿到DeviceName和DeviceSecret 4、下载测试工具 注意:请无视该文档中关于linuxhost的示例,编译方式请参考下面章节。 2.2 三要素设置 修改./framework/protocol/linkkit/iotkit/sdk-encap/imports/iot_import_product.h 中三个宏定义,修改为上一步骤中创建产品和设备时拿到的三要素(ProductKey、DeviceName和DeviceSecret),如下: #elif MQTT_TEST #define PRODUCT_KEY "......" #define DEVICE_NAME "......" #define DEVICE_SECRET "......" #define PRODUCT_SECRET "" #else 注:mqttapp程序所在源码为AliOS-Things/example/mqttapp/mqtt-example.c ( https://github.com/alibaba/AliOS-Things/blob/master/example/mqttapp/mqtt-example.c )。 此时在云端获取的三个参数ProductKey,DeviceName和DeviceSecret分别对应代码中的PRODUCT_KEY,DEVICE_NAME和DEVICE_SECRET三个宏,宏PRODUCT_SECRET无需理会。 3 mqttapp编译 AliOS-Things可以通过命令行和AliOS-Things IDE开发,详见下面说明。 3.1 命令行编译 1、命令行环境搭建: 参考https://github.com/alibaba/AliOS-Things/wiki/Quick-Start 2、命令行编译方式如下: $ aos make mqttapp@cb2201 build完成后可在out/mqttapp@cb2201/binary/ 目录找到生成的bin文件和hex文件。 3.2 AliOS-Things IDE编译 1、AliOS-Things IDE环境搭建: 参考https://github.com/alibaba/AliOS-Things/wiki/Starter-Kit-Tutorial 2、开发环境搭建好后,导入Alios-Things源码。 导入方法1:鼠标直接拖入; 导入方法2:点击菜单栏 “文件(F)” -> “打开文件夹(F)”。 3、Build如下图,选中mqttapp@cb2201,点击右侧"√" build完成后可在out/mqttapp@cb2201/binary/ 目录找到生成的bin文件。 4 固件烧录 4.1 安装烧录软件 1、烧录软件获取:链接:https://pan.baidu.com/s/1CcbCXZ3SJwfBL13_MP9SIg 密码:s6j5 2、解压后,双击CSKYFlashProgrammer.exe打开烧录软件 4.2 烧录 1、选择User Config: AliOS-Things-CB2201-MQTTAPP,并更改AliOS-Things存放路径(即修改下图中“G:\”) 2、开发板首次烧录程序需要选择 “Chip Erase” 用以擦除eFlash,之后开发则选择 “Erase Sectors” 即可。 3、点击下方 "Start Program" 按钮烧写(注意:烧写前需要先退出CskyDebugServer) 5 WiFi配网及数据连接阿里云 5.1 WiFi配网 烧录完成后,点击开发板复位键重启,串口打印如下图所示: 在串口命令行中敲入如下配网命令: # netmgr connect <ssid> <password> 正常联网后,mqttapp会真正开始运行。下图为mqttapp运行日志: 5.2 查看设备是否在线 点击下面链接,登录阿里云账户查看: http://iot.console.aliyun.com/#/product/newlist/region/cn-shanghai 6 调试 6.1 CskyDebugServer安装和使用 1、获取CskyDebugServer(若已安装CDS/CDK,则可略过1和2的步骤) 链接:https://pan.baidu.com/s/1lT7gIoJZylQEOUvXnrpFUg 密码:6bu9 2、安装 解压后双击默认安装。 3、端口设置,如下图: 4、连接开发板 点击 “红色三角形” 按钮,连接成功后,“红色三角形” 按钮会变成 “红色圆形” 按钮,如下图: 6.2 VS Code调试设置 根据已编译并烧录的app@board信息,更新 AliOS-Things/.vscode/launch.json 调试配置文件,比如:已编译并烧录 mqttapp@cb2201以后,更改相关配置如下图: 6.3 开始调试 1、点击 按钮,进入调试界面。 2、选择 “CSKY DEBUG @ Windows/Linux”: 3、点击左上方的 按钮(或F5)启动调试。 4、启动调试以后会自动停到已设置的断点 application_start 函数处; 同时上方会出现调试工具栏,提供常用的单步调试功能; 左侧边栏可以查看变量和函数调用栈。 | Home | Tutorial | Hardware | Porting Guide | Utilities | API Reference | Technical Docs | Certification | Crafted with by AliOS Things Team.
Alios-Things支持几种调试方式,具体作用及使用可参考链接地址 https://github.com/alibaba/AliOS-Things/wiki/Debugging-Overview.zh 今天我们主要关注CLI调试的开启,及增加CLI函数。 1、修改entry.c代码中 kinit 变量的cli_ebable的值为1 [cpp] view plain copy static kinit_t kinit = { .argc = 0, .argv = NULL, .cli_enable = 1 }; “entry.c”文件位于 "xxx\AliOS-Things\platform\mcu\esp8266\bsp"。 2、在项目中添加CLI函数 在此,我使用Example的 netmgrapp 的这个项目代码为例子. 打开"netmgrapp.c"文件的修ncmds结构数组及添加代码如下所示 [cpp] view plain copy static void handle_jack_cmd(char *pwbuf, int blen, int argc, char **argv) { aos_cli_printf("Call me Jack,please\r\n"); } static struct cli_command ncmds[] = { { .name = "test", .help = "test", .function = handle_test_cmd }, { .name = "jack", .help = "jack", .function = handle_jack_cmd }, #ifdef TEST_WIFI_HAL_ONLY { .name = "test_wifi_hal", .help = "test_wifi_hal [start|scan|scan_adv|monitor [mngt]|80211send|get_mac|ip_info|all]", .function = handle_test_wifi_cmd } #endif }; 保存后,在docker的编译环境中执行如下指令 [plain] view plain copy aos make netmgrapp@esp8266 将生成的 netmgrapp@esp8266-0x1000.bin 文件烧录到开发板运行。 3、使用串口工具运行CLI命令 在串口工具中发送 help 指令到MCU进入CLI。从CLI返回的参数中我们可以看到我们刚刚添加的一条指令 "jack". 接着我们通过串口工具发送 jack 指令到MCU,可以看到我们添加的打印信息 :“Call me Jack,please” 运行结果如下图所示 自此,你可以自己添加CLI函数,用以调试开发产品功能.
一、 开发环境搭建 目前国内大多数开发者使用的都是WinXP/Win7/Windows XX做MCU的开发。习惯Windows环境开发的朋友,如果一旦一接触到MCU需要是Linux环境来进行开发编译,第一反应理所应当是“臣妾做不到”!比方说,我们主角ESP8266这颗芯片,原厂提供的就是基于Linux做的开发(早期乐鑫还提供VM的开发环境镜像)。因此,我们为了开发这块芯片,就避免不了与linux打交道。 在Windows上安装虚拟机,再在做芯片的开发,这是目前比较常用的一种方式。但是如果基于VistualStudioCode插件的alios-studio进行开发需linux开发环境的,我是更推荐如下这种方式:使用docker 那么我们现在开始搭建windows下基于docker的ESP8266的alios-things的开发环境。 系统环境: Windows10 应用环境: Docker安装完毕(Windows下安装docker的方式可以到网上查一下) VisualStudioCode(以下简称VSCode) 1、 VSCode安装alios-studio插件,操作如图1所示 图1 注意:记得把C/C++的插件也一并安装好,alios-studio对其有依赖. 2、安装alios-things的docker镜像 方法A: 有能力的同学可以参考我在github给出的dockerfile文件(当然也可以不参考),自己搭建一个docker镜像 https://github.com/lanjackg2003/alios-things-docker.git 方法B: 直接从dockerhub服务器通过pull方式获取我编译好的镜像,方法如图2 https://hub.docker.com/r/jacklan/alios-things-docker 图2 打开命令行,执行如下命令 [plain] view plain copy docker pulljacklan/alios-things-docker 至此,Windows下的支持docker方式的编译环境已经搭建完毕了。 二、 克隆alios-things仓库代码 可以通过安装git工具,并通过git clone指令进行下载 [plain] view plain copy git clone https://github.com/alibaba/AliOS-Things 如图3所示: 图3 当然你也可以使用你熟悉的工具从github中下载alios-things的代码。 三、 编译Esp8266的HelloWorld固件 1、用VSCode打开AliOS-Things代码仓库,如图4 图4 2、 修改Alios-Things的HelloWorld.c代码 如图5,增加了一句代码: printf("esp8266hellowrold example by jack!\r\n"); 图5 3、通过快捷键 CTRL+Shift+` 三个按键,打开终端 图6 我们可以看到,powershell的路径已经是alios-things代码仓库目录了,所以我们接下来把这个目录挂载到docker的容器的工作目录里面。 在powershell中执行cmd切换到命令行 [plain] view plain copy docker run -v %cd%:/home/alios/AliOS-Things --name alios-things-build -it --rm jacklan/alios-things-docker 小窍门:想在powershell直接调起docker的容器可以直接将命令行脚本转城bat文件,然后运行。 图7 后续我会将这个Win_Build.bat也放到github中. 4、 接下来,使用alios-things的cube工具来编译刚刚我们的HelloWorld代码 在docker容器中执行如下命令 [plain] view plain copy aos make helloworld@esp8266 图8 图9 小贴士: 四、 烧写固件到ESP8266开发板 到乐鑫官网下载烧录工具: Flash 下载工具(ESP8266 & ESP32) https://www.espressif.com/zh-hans/support/download/other-tools 使用乐鑫的Windows下载工具,按照如下地址下载: 文件名 烧录地址 boot_v1.7_921600.bin 0x0 esp_init_data_default.bin 0x3fc000 blank.bin 0x3fe000 helloworld@esp8266-0x1000.bin 0x1000 “boot_v1.7_921600.bin" "esp_init_data_default.bin" "blank.bin"这三个文件可以在 “xxx\AliOS-Things\platform\mcu\esp8266\bsp”目录找到。 打开烧写工具,按照表格的地址进行填写后,对ESP8266板子进行烧写即可。 当前我使用的是NodeMCU这个开发板,所以我的烧录配置参数如下图 图10 五、 验证结果 图11 从串口打印中可以看到 “esp8266 hellowrold example by jack!” 的输出信息。 注意: 串口参数: 波特率 : 921600 停止位 : 1 数据位 : 8 奇偶校验 : 无 补充注意: 对于非Win10的 Windows系统,虽然docker也是支持的。但是要注意一点,其实你的docker是跑在你的虚拟机上面 并非真实的windows上的docker。 因此 你是无法直接通过windows的文件路径挂载到docker里面,应该挂载虚拟机下的路径到docker容器。 在此有两个步骤: 1.将windows上的路径,通过虚拟机共享文件夹的方式共享到docker虚拟机 2.挂载时候,使用docker下的共享文件夹路径进行挂载。 如: [plain] view plain copy docker run -v /G_DRIVE/vmshare/alios/AliOS-Things:/home/alios/AliOS-Things --name alios-things-build -it --rm jacklan/alios-things-docker 这句指令的"G_DRIVE"其实是"G盘"与docker虚拟机共享的文件夹的挂载路径。 如果挂载windows路径到docker虚拟机,可以参考以下链接(特别注意设置共享文件夹后,一定要重新虚拟机才会生效) 完整记录在 windows7 下使用 docker 的过程 总结一下,本人是比较推荐win10下使用docker技术进行开发,其他windows下这种方式效率其他没有那么高。部分原因从刚刚的分析过程应该可以体会一二。
目录 1. 简介 2. 基于 AliOS Things 开发 2.1 安装 IDE 2.2 获取 SDK 2.3 导入工程 2.4 SDK编译 2.5 固件下载 3. 使用 TI SDK 开发 略 4. IoT示例使用 4.1 准备工作 4.2 启动配网 4.3 控制设备 1. 简介 本 sdk 提供一个基于 MSP-EXP432P401R 开发板,通过与 Wi-Fi 模块进行 AT 指令串口通信,实现与阿里云 SDS 直连的 IoT 物联网典型开发应用示例源代码。 下面来讲解如何在您的 PC 上安装 CCS 开发环境,并在 CCS 中进行编译,下载本 sdk 工程。 2. 基于 AliOS Things 开发 2.1 安装IDE 点击 CCS 下载,进入开发工具下载列表页面。 在 “IDE Compatibility” 列表中,选择 “MSP432401R REVC” 选项中,适用您 PC 系统类型的 CCS 版本。如: Windows 系统 PC 开发环境,请下载: CCS Windows 对应的 CCS 6.1.1 .zip 文件。 **注意:**下载需先登录。如无账号,请先注册,登录后,才能下载。 下载完成后,解压缩,双击 “ccs_setup_6.1.1.00022.exe” 启动安装。 具体步骤: 进入 “Processor Support” 页面, 请务必勾选 “MSP Ultra Low Power MCUS”,然后其它均默认,点击 “next” 即可。 进入 “CCS Installation” 页面,启动安装,安装过程需耗费几分钟时间,请耐心等待。(请务必保证您的电脑保持联网状态。) 安装进程结束后,建议选择生成桌面快捷方式,以便开发时快速进入。 2.2 获取SDK 请点击进入 AliOS Things 仓库,默认分支:dev_msp432 ,您可以: 如已安装 Git 软件,使用 git 命令克隆到 PC 本地:git clone https://github.com/alibaba/AliOS-Things.git 如未安装 Git 软件,可确认当前在 dev_msp432 分之后,直接下载压缩包文件 PC 本地,点击仓库文件列表右上方, “Download ZIP",下载到本地后解压缩即可。 如遇网络问题,请使用 AliOS Things 国内镜像https://gitee.com/alios-things/AliOS-Things.git 2.3 导入工程 打开CCS IDE,选择 “Project”-->"Import CCS Project"-->"Browse",找到相应的alios-things 目录位置,如: F:\Git\AliOS-Things\platform\mcu\msp432\IOT-MSP432\examples\MSP432P4xx\mxchip\iot_sdk。请记得一定不要勾选 “Copy projects into workspace”。 导入工程后,IDE 显示如下图: 2.4 编译SDK 点击 CCS 的编译图标, 如下图: 编译成功后,显示: 2.5 固件下载 请通过 USB 线连接开发板至PD端USB口,确保设备正常供电。如下图: 点击下载 图标,如下图: 下载过程,如下图: 下载成功后,自动跳入调试模式。 至此,基于AliOS Things 的 IOT 示例源代码已编译下载到 TI 开发板中。 3. 使用TI SDK开发 不属于 AliOS Things 技术范畴,略。 详见 https://github.com/neooxu/IOT-MSP432#3-使用ti-sdk开发 4. IoT示例使用 本示例工程提供一个典型的 IoT 应用示例。 系统结构组成如下图: TI 的 MSP432P401R 单片机, 通过串口连接 EMW3080 Wi-Fi 模块,并与之进行 AT 指令通信,实现与阿里云 SDS 的通信,同时手机 APP 端可监控开发板的外部设备。 使用具体步骤如下: 4.1 准备工作 4.1.1 手机 APP 端 1.下载安装APP: 点击 示例 APP 下载页面,或扫描下方二维码,请根据手机系统类型选择下载。 2.使用手机号码,进行 APP 用户账号注册,验证,登录。 3.请确保手机已经成功连接至现场路由器。 4.1.2 开发板设备端 请使用 microUSB 连接线为开发板供电,并确认红色电源灯常亮,保证供电正常。 4.2 启动配网 打开手机 APP, 点击右上角 “+” 号,选择设备 “TI开发板”,根据提示进行操作。 长按开发板上盖板的 USER 按键,恢复设备出厂设置,此时开发板 OLED 屏最后一行将打印显示:"Restore default",继而跳变至 “Wi-Fi config....” 等待手机配网中; APP 配网输入界面中,设置正确的路由器用户名和密码,启动配网,此后开发板 OLED 屏最后一行将显示 设备的联网状态, “Wi-Fi connected” 代表成功获取路由器的 SSID 和 密码 “Cloud connected” 表示与云服务器连接成功,此时需进行身份认证; 当 APP 提示身份验证时,请短按上盖板的 USER 按键,完成身份认证; 以上步骤完成后,APP 会自动跳转设备列表页面,显示在线设备; 点击设备图标,进入设备控制页面。 ——> ——> 4.3 控制设备 进入设备控制页面,可对各外部设备参数进行监测或控制,或在线调试。 监测: 温湿度值:设备向云端上报数据 开关状态:可通过拨动开发板上的拨码开关体验。 开关1:S1, 开关2: S2。 高电平为关,低电平为开。 控制: RGB灯颜色,饱和度,亮度(通过滑动色彩环 或 滑动条来调整)。 在线调试: APP 调试界面向设备发送字符串,会在 OLED 屏最后一行打印出来(注意屏幕打印不支持中文)。 APP 发送数据会同步显示在接收区,带来 echo 回声体验效果。 ——> 结束语 以上为具体开发流程,请仔细参考,祝学习愉快,谢谢。 | Home | Tutorial | Hardware | Porting Guide | Utilities | API Reference | Technical Docs | Certification | Crafted with by AliOS Things Team.
本文介绍 Windows 下基于 AliOS Things 的 ESP32 应用开发流程,包括环境搭建、程序编译、固件烧写。 AliOS Things AliOS Things 是一款由阿里巴巴开发的轻量级物联网操作系统。具备极致性能,极简开发、云端一体、丰富组件(包括实时操作系统内核,连接协议库、文件系统、libc接口、FOTA、Mesh、语音识别)、安全防护等关键能力,并支持终端设备连接到阿里云IoT云服务平台。可广泛应用在智能家居,智慧城市,工业等领域,降低物联网终端开发门槛,使万物互联更容易,终端设备上云更简单。 项目地址:alibaba/AliOS-Things: AliOS Things released by Alibaba is an open-source implementation of operating system (OS) for Internet of Things (IoT). 在嵌入式实时操作系统大家族中,常见的 µC/OS-III、FreeRTOS 等 RTOS 严格意义上只能算一个 kernel(仅包含 OS 基本服务),随着物联网时代到来,出现了像 AliOS Things、RT-Thread 这些「时髦」的操作系统,大佬们在实时内核的基础上增加了大量组件,囊括通信协议栈、低功耗管理、安全加密算法、FOTA(远程固件升级)等功能,可以说目的十分明确 —— 直奔物联网。 更多关于物联网操作系统的知识,可以参考何小庆老师的 PPT 物联网操作系统研究与思考.pdf ESP32 物联网的大潮下,MCU 迎来一个新的发展机遇。继 ESP8266 之后,乐鑫在 2015 年底又推出了更强大的 ESP32 系列 WiFi 芯片,从参数描述可以看出: ESP32 SoC 为双核 32 位 MCU,主频高达 240 MHz,计算性能可达 600 DMIPS,采用 40 nm 工艺,集成 520 KB SRAM,16 MByte flash。工作电压 2.2 V to 3.6 V。ESP32 专为移动设备、可穿戴电子产品和 IoT 应用而设计,拥有业内最高水平的低功耗芯片的所有特征,例如精细分辨时钟门控、省电模式和动态电压调整等。ESP32 SoC工作温度范围从-40°C 到 +125°C。此外,ESP32 还集成了先进的自校准电路,实现了动态自动调整,可以消除外部电路的缺陷以及适应外部条件的变化。 早在 2016 年乐鑫 ESP32 和阿里云物联网系统 YoC 已经有了合作。去年 10 月份的云栖大会上阿里提出了 AliOS Things,不久之后项目开源便支持了 ESP32,同时为开发者提供了许多开发工具。 ESP32 DevKitC 开发板 ESP32-DevKitC 是搭载了乐鑫最新的 ESP-WROOM-32 模组的 MINI 开发板,能够轻松地插接到面包板,板子包含了用户所需的最小系统,只需连上 USB 线,即可进行开发。此外还具有 USB-UART 转换器 ,复位和下载模式按钮,LDO 稳压器 和微型 USB 连接器 。每个 GPIO 都可供开发者使用。 开发板购买地址:ESP32-DevkitC (Core board开发板)发票不含快递费-淘宝网 那如何把 AliOS Things 编译烧写到 ESP32 DevKitC 呢?来不及解释了,赶紧上车! 所需工具 在 Windows 下进行基于 AliOS Things 开发 ESP32 应用需要准备 安装有 Windows、Linux 或者 Mac 操作系统的 PC 用于编译 ESP32 应用程序的工具链 AliOS Things SDK —— 包含 ESP32 的 API 和用于操作工具链的脚本 编写 C 语言程序的文本编辑器,例如 VS Code ESP32 开发板,例如 ESP32-DevKitC 开发步骤 安装 Visual Studio Code 安装 alios-studio 扩展 获取 AliOS Things SDK(Github 项目源码) 下载 ESP32 工具链 配置 SDK path,新建 helloworld 工程 编译、构建项目 烧写 bin 固件 Step 1:安装 VS Code、alios-studio 扩展 项目 Wiki 已经有详细软件安装说明文档,按照步骤把 VS Code 和 alios-studio 扩展装好即可:AliOS Things Studio · alibaba/AliOS-Things Wiki Step 2:获取 AliOS Things SDK 和 ESP32 工具链 下载 aos 源代码 SDK 即项目仓库源码,从 Github 上 Download Zip 或 Clone 到本地,拷贝到 D:\AliOS-Things-master 目录下(目录自定)。 下载 ESP32 工具链 乐鑫 ESP-IDF 文档中详细描述了如何搭建 ESP32 开发环境,我们需要工具链 Windows all-in-one toolchain 用于编译源代码。 直接下载官方提供的 zip 包即可:https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20171123.zip 同样解压到 D:\msys32 目录下,SDK 和 Toolchain 用于后续配置 VS Code 开发环境。 Step 3:配置 SDK path 与 Toolchain path 打开装好 aos-studio 扩展的 VS Code,点击右下角边状态栏上的 Create Project 新建工程按钮,首次使用会弹出配置 SDK Path 的窗口,把刚才下载的 SDK 目录 D:\AliOS-Things-master 和 ToolChain 目录 D:\msys32\opt\xtensa-esp32-elf\bin 复制到两个 path 文本框中, SDK Version 会自动识别。 20180110 更新:V1.2.0 版本 SDK 设置工具链方式 由于新版本发布后增加了多编译器支持,在 SDK 设置界面去掉了 Toolchain 的选择,而是通过工程配置文件指定工具链目录。在左侧项目管理处找到 .vscode 下的 setting.json 文件,打开后在第一项 aliosStudio.build.toolchain 中填入目录 D:\msys32\opt\xtensa-esp32-elf\bin 即可(注意反斜杠需要转义)。 { "aliosStudio.build.toolchain": "D:\\msys32\\opt\\xtensa-esp32-elf\\bin", "aliosStudio.build.output": "${workspaceRoot}/out", "aliosStudio.inner.yosBin": "aos", ... } 嫌麻烦的话还可以在系统高级设置中将 ToolChain 路径添加到 TOOLCHAIN_PATH 环境变量中。 (已过时)修改 alios-studio Toolchain 判断规则(项目完善后可跳过此步) 此步骤非必须,由于我目前使用的 v1.1.2 版本的 SDK 尚未完善,多少会存在一些小 bug。比如对所添加 Toolchain path 的合法性判断,目前只校验了 arm Toolchain,所以对 ESP32 的 xtensa 工具链会误报非法路径。 阿里工程师在 issue [AliOS-studio][ESP32] tool chain path. · Issue #55 · alibaba/AliOS-Things 里给出了临时解决方法 —— 把 C:\Users\用户名\.vscode\extensions\alios.alios-studio-0.6.6\src\yang-sdk\main.js 文件中以下判断语句注释掉: if (options.toolChain && !fsPlus.existsSync(path.join(dir, `arm-none-eabi-gcc${process.platform === 'win32' ? '.exe' : ''}`))) { return {error: 1, msg: 'path is not a tool chain directory!'}; } 修改完毕后重启 VS Code,按 Ctrl+Shift+P 快捷键调出命令模式,输入 alios-studio: set SDK 继续配置,可正常添加 Toolchain path。 Step 4:新建 helloworld 工程 继续点击新建工程按钮,这回我们终于看到了待选工程模板,勾选 helloworld 工程,滚到下面填写工程保存目录(不能带中文),目标板卡选 esp32devkitc,最后点 Submit 按钮确认提交。 新建工程需要把 sdk 赋值到工程文件夹,比较耗时,请耐心等待。完成后 VS Code 会新打开一个文件夹视图窗口,表示一个 alios-studio 工程。在左侧的目录中打开 helloword.c 文件,其中 application_start 函数是应用程序的入口。helloworld 程序的运行现象是在串口以 5 s 的间隔打印调试字符串。 新建工程视频演示:AliOS Things Tutorial: 1 Hello World 应用 Step 5:编译、构建项目 点击窗口下方状态栏上的 Build 按钮开始编译、构建项目,期间会输出相应信息。如果 SDK 和工具链路径配置 ok 的话项目是可以成功编译的。经过 67.57 s 的漫长等待,终于 Build 完了…… 编译生成的文件在工程的 out 目录下,out\helloworld@esp32devkitc\binary\helloworld@esp32devkitc.bin 是要烧到板子上的固件。 Step 6:烧写 bin 固件 说明:由于官方 IDE 暂不支持 Upload 按钮烧录 ESP32,目前只能手动烧录。 固件烧录是相对独立的过程,原理适用于所有 bin 文件。烧写 ESP32 固件可以通过图形界面的 ESPFlashDownloadTool 软件或者 Python 命令行工具 esptool,两者都十分好上手,下面分别说明烧录方法。 bin 文件烧录地址 在烧写前需要准备 3 个 bin 文件,分别是引导程序(bootloader.bin)、分区表(custom_partitions.bin)和用户程序(helloworld@esp32devkitc.bin)。 引导程序和分区表的 bin 文件在 SDK 目录 D:\AliOS-Things-master\platform\mcu\esp32\bsp 下,用户程序 bin 由 alios-studio 编译得到。3 个 bin 文件烧录地址如下: bin 文件 烧录地址 bootloader.bin 0x1000 custom_partitions.bin 0x8000 helloworld@esp32devkitc.bin 0x10000 系统启动时会从 0x1000 地址处开始执行,引导程序读取分区表确定内存分布及启动规则,然后执行用户程序代码。因此全部 bin 都要烧到正确的地址程序才能正常执行,这一点需要特别注意。 PS:bootloader.bin 和 custom_partitions.bin 首次必须烧写,之后仅烧用户 bin 即可。 20180110 更新:使用 IDE 烧录 好消息!好消息!在更新后的 alios-studio 中支持通过下方 Upload 按钮一键烧录(然后选择对应 COM 号,波特率默认),你不点一下试试么? 使用 ESPFlashDownloadTool 工具烧录 ESPFlashDownloadTool 工具可在 Tools - 乐鑫 Flash 下载工具下载,打开软件后选择 ESP32 DownloadTool,设置不同固件及对应地址、晶振频率、SPI 模式、Flash大小,波特率(决定烧写速度),如下图所示: 将 ESP32 DevKitC 开发板用 Micro-USB 线与电脑连接,安装串口驱动,在烧写软件中选择对应 COM 号,点击 Start 按钮开始下载。 提示:大部分电脑在点击 Start 后会自动复位 ESP32 DevKitC 进入下载模式,如果出现一直等待的情况,请尝试按住 Boot 键不放再下载,或者按住 BooT 键的的同时按一下 EN 键再松开。 使用 esptool 工具烧录 esptool 是采用 Python 语言编写的开源工具(源代码:espressif/esptool: ESP8266 and ESP32 serial bootloader utility),提供方便易用的命令行方式进行操作,常用操作命令: 单个 bin 写 flash 命令: 格式:esptool.py --port 串口号 write_flash 地址 文件名.bin 示例:esptool.py --port COM4 write_flash 0x1000 my_app-0x01000.bin 多个 bin 写 flash 命令: 格式:esptool.py --port 串口号 write_flash 地址1 文件名1.bin 地址2 文件名2.bin 示例:esptool.py --port COM4 write_flash 0x00000 my_app.elf-0x00000.bin 0x40000 my_app.elf-0x40000.bin esptool.py 在 D:\AliOS-Things-master\platform\mcu\esp32\esptool_py\esptool 目录下,可通过「计算机 - 属性 - 高级系统设置 - 环境变量」添加到系统环境变量 Path 中(分号隔开后粘贴路径),以便在命令行中直接使用。 在 CMD 中敲入以下命令,将 bootloader.bin、custom_partitions.bin 和 helloworld@esp32devkitc.bin 三个文件下载到 Flash 对应地址。 esptool.py --port COM30 --baud 921600 write_flash 0x1000 D:\AliOS-Things-master\platform\mcu\esp32\bsp\bootloader.bin 0x8000 D:\AliOS-Things-master\platform\mcu\esp32\bsp\custom_partitions.bin 0x10000 E:\CodeBase\ESP32\AliOS-Things\hello\out\helloworld@esp32devkitc\binary\helloworld@esp32devkitc.bin 脚本实现 每次输命令太麻烦?将以下代码保存为批处理脚本 upload.bat ,并拷贝到工程目录 hello 下,最后在 VS Code 内置的终端中执行脚本实现一键烧录: for /f "delims=" %%t in ('dir /A:-D /S /B out\*@esp32devkitc.bin') do set binPath=%%t esptool.py --port COM30 --baud 921600 write_flash 0x1000 D:\AliOS-Things-master\platform\mcu\esp32\bsp\bootloader.bin 0x8000 D:\AliOS-Things-master\platform\mcu\esp32\bsp\custom_partitions.bin 0x10000 %binPath% 固件 bootloader.bin 和 custom_partitions.bin 从 SDK 目录获取,用户 bin 通过子目录下搜索 “@esp32devkitc.bin” 文件后缀得到。 运行结果 点击 VS Code 下方的 Connect Device 按钮(选好 COM 号,波特率 115200),通过 alios-studio 自带串口工具连接开发板(或使用其他串口工具),如果收到 ESP32 每隔 5 s 发过来的调试信息,说明 helloworld 运行成功! 提示:如果板子不断重启打印错误信息,请检查固件及烧写地址的正确性。 参考资料 RT-Thread物联网操作系统 物联网操作系统研究与思考 - 何小庆 乐鑫发布ESP32芯片 整合WIFI和低功耗蓝牙 为物联网创客带来新福利 - 吴川斌的博客 物联网 WIFI 芯片乐鑫 ESP32 结合阿里云物联网系统 YoC Home · alibaba/AliOS-Things Wiki ESP32-DevKitC Getting Started Guide
阿里开发 AliOS-Things 的主要动机,应该就是让别的商家利用 AliOS-Things 快速接入阿里云平台,所以我们可以直接利用 AliOS-Things 提供的 mqttapp 示例进行快速接入。 关于 AliOS 的系列博客,请参考 AliOS-Things系列学习笔记-目录。 云端操作 在云端的操作主要包括如下几步: 开通物联网套件 创建产品,拿到 ProductKey 创建设备,拿到 DeviceName 和 DeviceSecret 定义 Topic $(PRODUCT_KEY)/$(DEVICE_NAME)/data,并设置权限为”设备具有发布与订阅” 注意第 4 步不要漏掉了,我之前就是没有添加这个 Topic,在云端查看日志一直提示失败。 具体步骤在阿里云的官方写得比较清楚了,请移步 设备接入准备。 修改参数 Demo 程序所在路径是 AliOS-Things/example/mqttapp。由于 AliOS-Things 直接支持作为 Linux 平台的应用程序,所以即使没有 MK3060 开发板的话,也可以直接在 Linux 下跑这个 Demo,亲测没问题。 我们在云端操作时获取的三个参数需要在这里排上用场,根据这三个参数修改文件 AliOS-Things/example/mqttapp/mqtt-example.c 的 38~40 行代码,例如我是: 123 #define PRODUCT_KEY "sbYCVxN7htJ"// "BfKxBDSjWCH"#define DEVICE_NAME "alios-test-device" //"aos_mqtt_test"#define DEVICE_SECRET "cfety4D51cAaHiG9JORTn7vGpVeHBy5O" //"zcBZ5TB9cfAylUGo1flH0o47PxS8Mqu2" 在设备端运行 Linux 平台 如果是 Linux 平台,则直接可以编译并运行程序了。如果你之前操作无误的话,程序运行后会连上阿里云并向其推送消息。 下图是我的示例:  可以看到,订阅和推送 Topic 都成功了。 MK3060 平台 如果是在开发板 MK3060 上运行,则将程序编译并烧写到开发板上后,还需要执行 shell 命令让其连接到 WiFi 热点。连接上热点后,MQTT 程序才会真正运行。 让设备连接到 WiFi 热点需要执行命令 sta,其具体格式是 sta 热点的名字 热点的密码,例如我的热点名是 “ABC”,热点密码是 “ABC8881033”,则我就执行命令: 1 # sta ABC ABC8881033 下图是连接连接热点时的日志截图:  下图是 MQTT 示例运行时的日志截图:  学习资料 MQTT官方文档 MQTT中文文档 阿里云-控制台接入手册 阿里云-设备端快速接入(MQTT) MQTT学习笔记——MQTT协议体验 Mosquitto安装和使用
据了解,新型Anafi折叠后可以放在小箱子之中,而且其重量大约只有320克,和一副羽毛球拍的重量相当。 Parrot推出了名为“Anafi”的新型无人机。作为欧洲无人机行业的领头羊之一,这次Parrot研发的产品无论是在尺寸大小方面,还是重量等方面均有新突破。 据了解,新型Anafi折叠后可以放在小箱子之中,而且其重量大约只有320克,和一副羽毛球拍的重量相当,并且Anafi在满格电量的情况下能够飞行25分钟。但是与此前普遍大型的无人机相比,Anafi的确小的令人惊奇,Parrot官方称,这全部要归功于仿生学设计和卓越的智能电池组合的功劳。 而且Anafi还具备配套的飞行控制APP—FreeFlight6,其能够提供两种手动飞行模式:Film和Sport。Film最高时速能够达到每小时31英里,而后者甚至可加速至每小时33英里。 对于那些驾驶经验尚不丰富的人们来说,FreeFlight 6还提供了自动控制模式,包括SmartDronies,CineShots和Follow Me。 此外,Anafi的机身上也配置了很多高端无人机都具备的变焦功能4K HDR相机,该相机能够进行“3轴稳定”以及“180度垂直旋转”。 但是,令人感到遗憾的是,Anafi一旦运行“无损变焦”的功能之后,其分辨率会有所下降,比如说将变焦倍数调整至2.8倍后图像质量可能下降至1080P。 原文发布时间:2018-06-07 17:02 本文作者:公子阳 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
Free Parasol官方消息称,这款伞还能够借助旋转的螺旋桨为人们送去凉风,以缓解夏日的炎热。 夏天,人们离不开的,除了智能手机之外,可能还有遮阳伞。但是不论什么类型的伞,总归需要手持,只空出来一只手做什么都会感觉不方便,那么有没有一款伞不用手持还可以为我们遮风挡雨呢? 消息称,日本公司Asahi Power Service正在设计一种新型的无人机伞,这项研究可能会解决凡是伞必要手持的历史性难题。 这款无人机伞名为Free Parasol,它不需要手持,就可以帮助大家遮挡太阳。该伞宽约为150厘米,重约5公斤,颗凭借高速旋转的螺旋桨悬浮在头顶的上空,一次充电可以飞行20分钟左右。 Free Parasol官方消息称,这款无人机伞配备了相应的人工智能设备,能自动感应到人的头部,进而锁定,停留在头顶上空,为消费者遮风挡雨。除此之外,这款伞还能够借助旋转的螺旋桨为人们送去凉风,以缓解夏日的炎热。这样人们被解放出来的双手就随心所欲地做其他事情了。 据悉,Free Parasol仍在研究和完善之中,预计在2019年投入使用时,伞重会减轻至1公斤,单次充电飞行的时间可以达到1个小时。 但是,这款伞也面临着在雨天如何防水以及如何应对大风天气等问题。而且,距离人的头部这么近,其仍然面临着安全、噪音以及续航系列问题,这都需要研究者进一步加以完善。 原文发布时间:2018-06-06 21:28 本文作者:公子阳 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
浮动与清除浮动 浮动 float属性的取值 float : left | right | none | inherit 默认值 : none 适用于所有元素,没有继承性 浮动框的范围 浮动元素的外边距边界定义了浮动框的大小 浮动元素的包含块是其最近的块级祖先元素,且包含块的大小为该块级祖先元素的内容区 浮动元素的特点 1.浮动元素会脱离标准文档流(这就是为什么浮动元素不能撑开父元素的高,以及浮动元素会覆盖非浮动元素的原因) 2.浮动元素会生成一个块级框,而无论这个元素本身是什么(比如行内元素。因此行内元素一旦浮动,便能够为其设置宽高) 浮动元素的一些行为及规则(仅列出一些常用的) 1.浮动元素不能超出包含块的边界(不包括底边),这就是为什么多个浮动元素出现在同一水平行时,一旦一行容不下后续的浮动元素,后续的浮动元素会换行的原因 但是,如果浮动元素本身的宽就大于包含块的宽,则允许超出 另外,为浮动元素设置负margin,使其超出包含块也是允许的 2.行内框与一个浮动元素重叠时,其边框、背景、内容都在该浮动元素外显示。块框与一个浮动元素重叠时,其边框、背景在该浮动元素之下显示,而内容在浮动元素之外显示 例如 <style type="text/css"> *{ margin: 0px; padding: 0px; } .div1{ width: 100px; height: 100px; float: left; background-color: red; } span{ background-color: blue; } </style> <body> <div class='div1'></div> <span>qwert</span> </body> 清除浮动 取值 clear : left | right | both | none | inherit 默认值 : none 适用于块级元素(一定注意),无继承性 清除浮动的原理 如果元素设置了clear(非none与inherit值),则用户代理会在元素上外边距的基础上再增加额外的间隔(这个间隔的术语叫‘清除区域’),以将元素移到浮动框下边界的下面(刚好移到下边界即可) 这就是浮动元素不会覆盖清除浮动元素的原因 怎么使浮动元素也能撑开包含块的高 使包含块也浮动 原理是:浮动元素会延伸,从而包含其所有后代浮动元素 清除浮动 <style type="text/css"> *{ margin: 0px; padding: 0px; } .wrapper{ width: 100px; background-color: red; } .inner{ width: 50px; height: 50px; background-color: orange; float: left; } .flag-clear{ clear: both; } </style> <body> <div class='wrapper'> <div class="inner"></div> <div class="flag-clear"></div> </div> </body> 只要flag-clear在清除浮动后,逻辑上在inner后面即可,并不用给flag-clear设置大小。撑开的原理就是前面说的——flag-clear的上外边距会增加 伪元素与clear相结合(常用) <style type="text/css"> *{ margin: 0px; padding: 0px; } .wrapper{ width: 100px; background-color: red; } .inner{ width: 50px; height: 50px; background-color: orange; float: left; } .wrapper::after{ content: ''; display: block; clear: both; } </style> <body> <div class='wrapper'> <div class="inner"></div> </div> </body> ps:本文案参考了以下书籍 《CSS权威指南》 原文发布时间:2018年06月29日 16:47:17 原文作者:earth_smallma 本文来源CSDN,如需转载请联系原作者
作者:吴胜斌 https://www.simbawu.com/article/search/9 在说深拷贝与浅拷贝前,我们先看两个简单的案例: //案例1var num1 = 1, num2 = num1;console.log(num1) //1console.log(num2) //1 num2 = 2; //修改num2console.log(num1) //1console.log(num2) //2//案例2var obj1 = {x: 1, y: 2}, obj2 = obj1;console.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.xconsole.log(obj1) //{x: 2, y: 2}console.log(obj2) //{x: 2, y: 2} 按照常规思维,obj1应该和num1一样,不会因为另外一个值的改变而改变,而这里的obj1 却随着obj2的改变而改变了。同样是变量,为什么表现不一样呢?这就要引入JS中基本类型和引用类型的概念了。 基本类型和引用类型 ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。 打个比方,基本类型和引用类型在赋值上的区别可以按“连锁店”和“单店”来理解:基本类型赋值等于在一个新的地方安装连锁店的规范标准新开一个分店,新开的店与其他旧店互不相关,各自运营;而引用类型赋值相当于一个店有两把钥匙,交给两个老板同时管理,两个老板的行为都有可能对一间店的运营造成影响。 上面清晰明了的介绍了基本类型和引用类型的定义和区别。目前基本类型有: Boolean、Null、Undefined、Number、String、Symbol,引用类型有:Object、Array、Function。之所以说“目前”,因为Symbol就是ES6才出来的,之后也可能会有新的类型出来。 再回到前面的案例,案例1中的值为基本类型,案例2中的值为引用类型。案例2中的赋值就是典型的浅拷贝,并且深拷贝与浅拷贝的概念只存在于引用类型。 深拷贝与浅拷贝 既然已经知道了深拷贝与浅拷贝的来由,那么该如何实现深拷贝?我们先分别看看Array和Object自有方法是否支持: Array var arr1 = [1, 2], arr2 = arr1.slice();console.log(arr1); //[1, 2]console.log(arr2); //[1, 2] arr2[0] = 3; //修改arr2console.log(arr1); //[1, 2]console.log(arr2); //[3, 2] 此时,arr2的修改并没有影响到arr1,看来深拷贝的实现并没有那么难嘛。我们把arr1改成二维数组再来看看: var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice(); console.log(arr1); //[1, 2, [3, 4]] console.log(arr2); //[1, 2, [3, 4]] arr2[2][1] = 5; console.log(arr1); //[1, 2, [3, 5]] console.log(arr2); //[1, 2, [3, 5]] 咦,arr2又改变了arr1,看来slice()只能实现一维数组的深拷贝。 具备同等特性的还有:concat、Array.from() 。 Object 1、Object.assign() var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);console.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 1, y: 2} obj2.x = 2; //修改obj2.xconsole.log(obj1) //{x: 1, y: 2}console.log(obj2) //{x: 2, y: 2}var obj1 = { x: 1, y: { m: 1 } };var obj2 = Object.assign({}, obj1);console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 2}}console.log(obj2) //{x: 2, y: {m: 2}} 经测试,Object.assign()也只能实现一维对象的深拷贝。 2、JSON.parse(JSON.stringify(obj)) var obj1 = { x: 1, y: { m: 1 } };var obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}} obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 2, y: {m: 2}} JSON.parse(JSON.stringify(obj)) 看起来很不错,不过MDN文档 的描述有句话写的很清楚: undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。 我们再来把obj1改造下: var obj1 = { x: 1, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") };var obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}console.log(JSON.stringify(obj1)); //{"x":1}console.log(obj2) //{x: 1} 发现,在将obj1进行JSON.stringify()序列化的过程中,y、z、a都被忽略了,也就验证了MDN文档的描述。既然这样,那JSON.parse(JSON.stringify(obj))的使用也是有局限性的,不能深拷贝含有undefined、function、symbol值的对象,不过JSON.parse(JSON.stringify(obj))简单粗暴,已经满足90%的使用场景了。 经过验证,我们发现JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题。只能祭出大杀器:递归 function deepCopy(obj) { // 创建一个新对象 let result = {} let keys = Object.keys(obj), key = null, temp = null; for (let i = 0; i < keys.length; i++) { key = keys[i]; temp = obj[key]; // 如果字段的值也是一个对象则递归操作 if (temp && typeof temp === 'object') { result[key] = deepCopy(temp); } else { // 否则直接赋值给新对象 result[key] = temp; } } return result; }var obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo") };var obj2 = deepCopy(obj1); obj2.x.m = 2;console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)} 可以看到,递归完美的解决了前面遗留的所有问题,我们也可以用第三方库:jquery的$.extend和lodash的_.cloneDeep来解决深拷贝。上面虽然是用Object验证,但对于Array也同样适用,因为Array也是特殊的Object。 到这里,深拷贝问题基本可以告一段落了。但是,还有一个非常特殊的场景: 循环引用拷贝 var obj1 = { x: 1, y: 2 }; obj1.z = obj1;var obj2 = deepCopy(obj1); 此时如果调用刚才的deepCopy函数的话,会陷入一个循环的递归过程,从而导致爆栈。jquery的$.extend也没有解决。解决这个问题也非常简单,只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可,修改一下代码: function deepCopy(obj, parent = null) { // 创建一个新对象 let result = {}; let keys = Object.keys(obj), key = null, temp= null, _parent = parent; // 该字段有父级则需要追溯该字段的父级 while (_parent) { // 如果该字段引用了它的父级则为循环引用 if (_parent.originalParent === obj) { // 循环引用直接返回同级的新对象 return _parent.currentParent; } _parent = _parent.parent; } for (let i = 0; i < keys.length; i++) { key = keys[i]; temp= obj[key]; // 如果字段的值也是一个对象 if (temp && typeof temp=== 'object') { // 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用 result[key] = DeepCopy(temp, { originalParent: obj, currentParent: result, parent: parent }); } else { result[key] = temp; } } return result; }var obj1 = { x: 1, y: 2 }; obj1.z = obj1;var obj2 = deepCopy(obj1);console.log(obj1); //太长了去浏览器试一下吧~ console.log(obj2); //太长了去浏览器试一下吧~ 至此,已完成一个支持循环引用的深拷贝函数。当然,也可以使用lodash的_.cloneDeep噢~。 原文发布时间:2018年05月10日 16:12:21 原文作者:吴胜斌 本文来源CSDN,如需转载请联系原作者
目前,亚马逊已经取得了初步的成果。 据外媒报道,亚马逊正准备让人工智能算法来替代时尚造型师和设计师。据了解,他们的研究人员正在研究相关的机器学习系统,以便更有效的发现最新时尚趋势并做出反应,甚至是创造时尚。 就在上周,亚马逊还举办了一场机器学习和时尚研讨会,向到场嘉宾公布了自己的亮相人工智能成果: 一个是由位于以色列的亚马逊研究人员所开发的一种机器学习算发。通过分析附加到图像上的几个标签,其便能够推断出某些搭配是否将能够被视为时尚; 还有一个是由旧金山亚马逊研究中心Lab126开发的一种机器学习算发。在运用中,该算法只需学习一些特定的时尚风格,其便能够产生类似的风格。换一种说法,这种人工智能算法就已经是一个简单的人工智能时尚造型师或设计师了。 对此,参加了此次研讨会的康奈尔大学教授卡维塔·巴拉表示:“诸如亚马逊这样的公司一直在努力了解全世界的时尚趋势,其正在改变整个行业。” 事实上,不仅仅是线上的算法研究,在线下场景中,亚马逊也早已开始了自己在时尚界的布局。在研发机器学习算法的同时,亚马逊也开发了自己的服装品牌,并推出Prime Wardrobe服务,让用户在确定购买之前可以试穿衣服。此外,亚马逊的Echo Look应用程序也会给用户一个合适的服装搭配。 原文发布时间:2017-08-25 09:15 本文作者:韩璐 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
2018年的3D感测模组市场产值预估约为51.2亿美元,其中由iPhone贡献的比重高达84.5%。 3月7日,拓璞产业研究院发布报告,预测今年全球智能手机3D感测渗透率将从2017年的2.1%上升至13.1%,苹果仍然是3D感测器件的主要采用者。 2018年全球搭载3D感测模组的智能手机生产总量将达1.97亿支,其中iPhone占据1.65亿支。此外,2018年的3D感测模组市场产值预估约为51.2亿美元,其中由iPhone贡献的比重高达84.5%。预计至2020年,整体产值将达108.5亿美元,而2018至2020年间的复合年均增长率将达到45.6%。 虽然3D感测器件渗透率提升,但是其生产的技术门槛仍然存在。 据行业人士分析,目前生产3D感测模组的技术门槛主要有三。第一,高效率VCSEL(垂直腔面发射激光器)组件生产不易,目前平均光电转换效率仅为30%。 第二,结构光技术的必要组件实验设计以及红外光镜头的CIS模块,都需要极高的技术底蕴。 第三,3D感测模组生产过程需考虑热胀冷缩的问题,提高模组组装的难度。这些因素导致现阶段3D感测模组的生产良率仍处于低位。 虽然为了应对市场需求,部分厂商积极扩产,但是VCSEL整体良率还是比较低。 拓璞产业院认为,VCSEL主要供应商Lumentum与苹果之间存在专利协议,这使得安卓阵营若想在短期内跟进,只能舍弃VCSEL而选择EEL(边射型激光)。但是EEL的光电转换效率较差,且成本较高,这将使安卓阵营的3D感测方案在效率和成本上仍难与苹果匹敌。 原文发布时间:2018-03-07 16:57 本文作者:Lotusun 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
除了筹建标委会,我国也以参与国的身份参与了区块链的国际标准化工作。 昨天,工业和信息化部(以下简称“工信部”)在其官网发布了一条公告。上面显示,为了尽快推动形成完备的区块链标准体系,做好ISO/TC 307技术对口工作,部信息化和软件服务业司在近期就筹建全国区块链和分布式记账技术标准化技术委员会事宜开展专题研究。 另外,公告内容显示,目前,国际标准化组织(ISO)、国际电信联盟(ITU)、万维网联盟(W3C)等国际标准化机构纷纷启动区块链标准化工作。ISO成立了专注于区块链领域的技术委员会TC 307(区块链与分布式记账技术技术委员会),开展基础、身份认证、智能合约等重点方向的标准化工作。中国以参与国(P成员)身份参加相关标准化活动,并取得了积极进展。 在下一步,部信息化和软件服务业司将积极推动相关工作,加快推动标委会成立,更好地服务区块链技术产业发展。 自2018年伊始,区块链就成为了继AI、VR之后的又一热门行业,而我们也看见了行业应用、媒体平台等相关区块链项目相继完成融资。 与此同时,行业应用之外的学术研究方面,中国人民解放军战略支援部队信息工程大学区块链研究院已于2月底在深圳揭牌,这是国内首家以“军民融合发展战略”为政策平台支撑、以核心密码技术和可信原理为核心技术的区块链应用研究机构。 原文发布时间:2018-03-13 13:16 本文作者:韩璐 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
特斯拉称Autopilot是一个不完善的系统,但在事故中表现良好,没有失误。 就此前发生在加利福尼亚州山景城的一场致命的Model X撞车事故,多家媒体指责事件发生归因于Autopilot技术问题,对此,今天,特斯拉官方发布申明,称Autopilot确实是一个不完善的系统,但没有失误。 此前,通过事件调查,在撞车事故现场恢复日志后,特斯拉确认Autopilot已启用,自适应巡航控制的跟车距离设置为最小。对此,特斯拉在申明中表示:“司机早先在驾驶过程中,无论是语音还是信号指示,他都收到了亲手操作警告。并且系统检测出,在碰撞之前司机的双手离开方向盘达六秒钟。而其中大约有五秒钟的时间,司机可以看到150米外的没有障碍的混凝土护栏,但车辆日志显示驾驶员没有采取任何行动,而是直接撞了上去。” 在申明中,特斯拉给出这次车祸的主要原因:撞车事故如此严重是因为在此前的一次事故中衰减器已经受损而未被更换。该公司表示,“我们从未在任何其他碰撞事件中看到Model X的这种损坏程度。” 同时,特斯拉也承认Autopilot是一个不完善的系统,但他们认为系统并没有失误:“特斯拉Autopilot不能完全防止事故的发生,但它可以使事故发生的概率降低。它就是为了让驾驶员、行人和骑自行车的人更安全而设计的。 公众不使用Autopilot是因为大家认为它不安全,这一想法是不对的。全球每年因车祸造成的死亡人数约有125万。如果应用特斯拉车辆的当前安全等级,则意味着每年可以大约拯救90万人的生命。数据分析也表明,自动驾驶汽车的安全性要比非自动驾驶汽车高10倍。” 原文发布时间:2018-03-31 19:16 本文作者:Lynn 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
它们将大幅度提高我国在山、水、林、田、湖、草等自然资源全要素、全覆盖调查监测的能力。 31日中午11点22分,在太原卫星发射中心,我国成功以“一箭三星”方式发射3颗光学卫星。11点56分,3颗卫星成功入轨。这也是我国成功发射并将投入使用的首个民用业务卫星星座。3颗光学卫星又名高分一号02、03、04星,它们充分继承了高分一号卫星的成熟技术,并保持了性能相同、状态一致。该3颗卫星组成了一个星座。 高分一号02、03、04星是国家民用空间基础设施规划的首批业务化应用卫星,卫星可采集空间分辨率全色2米、多光谱优于8米,单星成像幅宽大于60公里的遥感影像。3星组网并投入运行后,可实现同一地区2天重访,15天以内对全球覆盖一遍。 据悉,该卫星星座可获取规模化的1:2.5万—1:5万自然资源调查、监测以及应急等专题应用产品,将大幅度提高我国在山、水、林、田、湖、草等自然资源全要素、全覆盖调查监测的能力。它们将实时掌控自然资源数量、质量、生态动态变化,为国家自然资源资产管理和自然生态监管提供精准信息保障,并广泛应用于防灾减灾、环境保护、交通运输、应急管理等领域。 此外,高分一号02、03、04星还将与此前在轨运行的高分一号01星、今年拟发射的高分六号卫星组网运行,共同构建陆地资源调查监测业务卫星星座。高分一号02、03、04卫星由中国航天科技集团有限公司所属航天东方红卫星有限公司研制。 原文发布时间:2018-03-31 19:19 本文作者:Lynn 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
4月12日,2018第二届中国通信业物联网大会将在京召开。 本次大会以“数字化浪潮下的智慧连接”为主题,携手业界共筑高质量的合作、分享、连接平台,助力打造以运营商为核心的物联网交流合作生态圈。届时,物联网领域顶级专家学者与中国电信、中国移动、中国联通三大运营商代表、知名企业高层同台论道,共助物联网生态升级、共探物联网未来发展。 大会即将启幕,现将已确认参加大会并进行演讲的嘉宾、部分参与企业及精彩看点提前剧透: 院士助阵 学者专家共探IOT未来 首届大会的成功举办奠定了良好的基础,在行业内形成良好影响,本届大会迎来重磅嘉宾,中国工程院院士倪光南将受邀出席大会,并作主旨报告,与产业各界共探IOT未来。 数字化与人工智能的爆发式发展,也引发了更深层次的需求,“智能网络、雾计算、物品流通的标识体系”等话题成行业关注热点,本届大会还有幸邀请到《物联网学报》执行主编、南京邮电大学原副校长朱洪波、中科院微系统与信息技术研究所、中科院无线传感网与通信重点实验室主任杨旸、中国物品编码中心物联网研究室主任张旭出席大会并分享各自在相关领域的研究成果和观点。 运营商同台 开放合作共助物联网生态升级 得益于运营商在窄带物联网(NB-IoT)的积极布局与商用推进,我国窄带物联网(NB-IoT)取得重大突破。2017年也被认为是窄带物联网(NB-IoT)引领物联网规模商用的重要节点。 作为国内首个全面系统关注运营商物联网产业发展与应用创新的高规格活动,2018第二届中国通信业物联网大会进一步受到三大运营商的关注和积极参与。截至目前,已有中国电信股份有限公司物联网分公司总经理赵建军、中国联通集团物联网业务部总经理、中国联通物联网研究院院长陈晓天、中国电信股份有限公司北京研究院物联网与互联网+研发事业部总监江志峰等运营商代表确定将出席大会并作主题演讲,分享运营商在物联网方面的技术创新成果与NB-IoT 部署的最新进展。 同时,还有广州、深圳、江苏、浙江、山东、重庆、天津、河北、内蒙古等地方运营商代表出席大会,参与交流、共探合作,共寻发展。 各大厂商齐亮剑 赋能智慧连接共迎新时代 2017年被业界普遍认为是物联网商业应用元年。而据相关报道显示,2018年开年,窄带物联网即迎来一个生机勃勃的开端,各大厂商纷纷推出各种最新的芯片、软件、服务方案与技术,进一步推动产业升级。 截至目前已有下面的企业嘉宾确定将出席大会并作主题演讲,展示最新产品、技术,分享观点,探讨产业的可持续发展。 华为技术有限公司中国区IoT业务部 陈占林 中兴通讯股份有限公司物联网平台产品线总经理 马金 深圳日海物联技术有限公司执行董事、副总裁 王恩玺 亚信科技首席咨询官,亚信咨询总经理 袁道唯 东软集团通企事业部大数据视觉感知技术与应用中心主任 岳应宁 百度云物联网部高级产品经理 王尊 国美智能科技有限公司CTO 李一冰 Oracle首席物联网技术顾问 董毅 APICloud联合创始人兼CTO 邹达 山东易华录智慧城市CTO 倪志刚 北京顶象技术有限公司营销副总裁 陈振学 更多演讲嘉宾陆续更新中,敬请期待! 同时组委会还将邀请H3C、阿里、京东、亚马逊、容联云通信、利尔达等企业代表出席大会,为业界嘉宾带来他们在物联网领域的规划与布局。 新的一年,三大运营商在物联网领域还将有哪些实践成果、最新规划发布?各大厂商又有哪些创新产品与技术推出?数字经济的推动下,物联网的智慧连接将迎来哪些新的发展机遇与挑战? 4月12日, 2018第二届中国通信业物联网大会为您揭晓答案。 原文发布时间:2018-04-02 15:02 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
一款壁挂式的绘画机器人Utensil,它可以使用织物记号笔、尖锐物或其他绘图工具制作多种多样的艺术作品或设计。 近日,据外媒报道,MIT的机械工程学博士James Penn研发了一款壁挂式的绘画机器人Utensil,其可以使用织物记号笔、尖锐物或其他绘图工具制作多种多样的艺术作品或设计。 据了解,Utensil的工作原理是其采用了一种独特的线缆驱动系统,不但具备闭环反馈控制能力,还可以大面积规模作画,而它的占用面积仅为56平方厘米,可以说是非常小,对于使用者来说,非常方便!最令人惊喜的是,它的操作非常简单,用户只要在电脑上编辑好自己喜欢的图案,然后给Utensil安装上织物记号笔,它就能在几分钟的时间里完成绘画。例如,你在电脑上输入蒙娜丽莎的画像,那么Utensil能以最快的速度画出来,而不需要二次加工。 此外,这台机器人的另一大特色是在T恤上绘制个性化图案。无论你想要什么图案,只要你能想到,它就能完成,且精确度非常高。 不仅如此,Utensil不仅是一个绘画机器,而且它还可以进行激光切割,通过与其他工具的结合而获得新的能力,比如对摄像头(或其他设备)进行可编程的多轴运动控制,激光切割和雕刻,以及铣削。 目前,Utensil已登录Kickstarter上众筹,相信众筹也会很快完成。据相关人士透露,如果一切顺利,那么Utensil将在12月正式出货!随着Utensil的问世,相信很多人人士都慌了吧,我们不禁想到未来机器与人类比赛绘画的场景。 原文发布时间:2018-04-02 21:52 本文作者:Lynn 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
人工智能“新闻主播Yomiko”将模拟真人主播的声音播报记者写成的新闻稿件。 人工智能技术的出现,为很多行业都带来了便利,同时也使人类前所未有的感受到了压力。“人工智能将取代哪些行业的人员?”成为近些年来的热点话题。 近日,据外媒报道,日本NHK电视台将从4月开始在节目中使用人工智能主播,该“主播”将在工作日晚间11时10分播出的“NEWSCHECK11”节目中登场,每周播报一次约5分钟的新闻。 据了解,该人工智能新闻主播名叫“Yomiko”,其将模拟真人主播的声音播报记者写成的新闻稿件。技术人员只需事先让NHK旗下的主播阅读大量新闻稿件并录音,然后将这些语音数据分解为10万个音素。此外,还让Yomiko事先记住日本全国的地名、专有名词、口音等。 当然,假如用户拥有Google Home之类的智能音箱,通过连接手机,也能让Yomiko来播报新闻。此外,通过AR技术,用户也可在手机上与Yomiko进行合影。 据悉,NHK是面向国内外独立发声的机构,也是日本唯一的公共广播电视机构。节目播出公平公正、制作品质较高,其内容丰富,涵盖各个领域的内容,在日本民众心中有极高的地位,在全球传媒领域享有盛誉。此次,推出人工智能主播,可以说是一次巨大的突破。 不过,Yomiko目前仅局限于播报新闻,我们也期待未来Yomiko能有更多突破。 原文发布时间:2018-04-02 21:56 本文作者:Lynn 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
根据方针,日本政府将对自动驾驶汽车与普通汽车一视同仁。 据外媒报道,日本日前针对自动驾驶汽车事故敲定了一项方针,明确了事故中责任方的赔偿等问题。 据了解,日本政府在当天的“未来投资会议”上提出了《自动驾驶相关制度整备大纲》,并将于2019年向国会提交相关法案。 在自动驾驶汽车开始普及的2020~2025年之前,针对有驾驶员乘坐、以有限条件实现驾驶自动化的“3级”以下自动驾驶汽车,该大纲指示出立法和监管的方向性。 另外,大纲中也确认,自主行驶时的事故赔偿责任原则上由车辆所有者承担,可以利用法律强制加入的机动车交通事故责任强制保险(交强险)进行赔付。把自动驾驶汽车与一般汽车相同对待。企业的责任仅限于汽车系统存在明确缺陷之际。 与此同时,黑客入侵导致的事故赔偿与被盗车辆导致的事故损害同样适用政府的救济制度,条件是车辆所有者更新系统等,采取安全举措。 而为了明确责任、查明事故原因,大纲也要求车辆安装行车记录仪,以记录位置信息、方向盘操作和自动驾驶系统的运行状况等。 一直以来,关于自动驾驶事故责任方的归属一直是一个令人头疼的问题,不管是对消费者本身还是对研发企业来讲,这也对自动驾驶汽车的市场拓展也造成了一定的阻碍。 据悉,日本政府预定今年夏季敲定有关自动驾驶汽车安全性的指导方针。 原文发布时间:2018-04-02 22:33 本文作者:韩璐 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
一个耳机,口令式的命令,就可以获得想要的一切。 日前,IDC发布报告称,2018年全年可穿戴设备出货量预计将达到1.329亿部。可以看到,在很多人不看好智能可穿戴行业的当下,该行业的发展依旧很乐观。 但相关数据显示,预计今年可穿戴设备出货量,智能手表占比将占到1/3。此外,由于智能手表的价格相对较高,其所占的消费者在可穿戴设备方面的开支约2/3。可以看到,智能可穿戴设备种类单一,且功能较为贫乏,其市场几乎以智能手环为主。显然,这是不利于行业发展的。 可穿戴设备行业急需新血液的加入,在技术、功能、创新等方面,对可穿戴设备进行升级。 “我们现在处在一个软件的时代,软件必须要安装在手机或者PC等设备上。等到以后,当我们可以实现完全声控,进入声控时代,就不再需要手机等外在设备了。届时,耳机或将成为最主要的消费级产品。我们只要有一个耳机,通过口令式的命令,就可以获得想要的一切。” 深圳市雅乐电子总经理卢升中说。 深圳市雅乐电子主要从事高端智能穿戴产品的研发、设计、生产、销售为一体的综合性智能产品,其自主创立的品牌AW EI用维在全球已拥有超过36个国家的代理商。 目前,雅乐电子已有多款耳机面世,如自带便携充电座的T8真无线蓝牙耳机和具备语音提示功能的A880BL后挂式蓝牙耳机等,均已在其官方商城上线。 除此之外,考虑到市场需求,AW EI用维还研发了更多的蓝牙耳机,充分满足用户对自由感的需求,更具人性化。 “当前,智能穿戴产品有两大发展趋势,智能娱乐和医疗。而未来,穿戴设备的功能必定是娱乐和医疗相结合的。” 卢升中肯定的说。 那么,如何把耳机作为消费者必备和备用的产品?卢升中透露,AW EI用维正在研发可通过人体动脉检测心率、测步、测血压等的耳机,让其成为家庭必用产品。而随着智能穿戴设备产业的发展,该公司预计在三年内实现公司年销售额破十亿的目标,并每年投入不低于总利润25%的研发费用。 原文发布时间:2018-04-02 22:36 本文作者:伶轩 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
特斯拉进一步优化了Model 3的系统及相应按键功能,让操作更加舒适。 据悉,为了让司机不因为操作Dash大触摸中控显示屏而在开车过程中分心,特斯拉发布了对Model 3的进行了软件更新,改变了汽车方向盘上的滚动按钮功能。现在,通过按钮,司机可以调整交通感知的巡航控制设置。 如司机可以通过右滚动按钮的左右滑动来调整Model 3和前面的车辆之间的跟踪距离,而左边的滚动按钮可以让司机调整侧镜和方向盘。当然,这些设置仍然可以从触摸屏控制,而且司机还可以将中控显示屏和滚动按键结合起来操作,如驾驶员可以点击后视镜调整图标,选择左或右侧的后视镜,并上下滚动控制键来调整。 方向盘调整的方式和后视镜是一样的,只是你必须从设置菜单中选择方向盘,这样在开车的时候就可以把更多注意力放在路上。向上或向下推动滚动按钮将增加或减少设定的巡航速度。 此前,为了进一步完善体验,特斯拉就曾不断为系统进行升级。 如为新车软件中融入了Siri,让用户可以实现语音控制,并可以进行远程开启车灯、检查电池电量以及锁车或者解锁等操作。 此外,根据用户的反馈,特斯拉也重新排列了触摸屏图标:最常使用的触摸屏图标更容易接触到,将媒体播放器和电话图标现在位于触摸屏的左侧。 原文发布时间:2018-04-02 23:08 本文作者:Lynn 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
无人驾驶许可证将允许测试SAE指南中所述的4级自动驾驶汽车在特定区域内自主行驶。 据外媒报道,美国加利福利亚州汽车管理局(DMW)宣布,他们将开始接受完全无人驾驶车辆的申请,向符合特定要求的公司发放许可证,允许其在没有人类安全驾驶员的情况下在公共道路上测试自动驾驶车辆。 近年来,因为对自动驾驶汽车测试的宽容性,加州已经成为了自动驾驶公司的“天堂”。不过,对于测试自动驾驶汽车的公司,加州此前要求在公共道路上测试的无人驾驶汽车必须配备安全驾驶员。 而对于此次发放的无人驾驶许可证,加州也制订了一系列必要条件,包括适当的安全措施,以抵御网络攻击、双向通信设备,并且车辆不能在某些指定区域之外自主行驶等。 根据规定,无人驾驶许可证将允许测试SAE指南中所述的4级自动驾驶汽车在特定区域内自主行驶。另外,当发现公司存在不安全行为数据时,DMW将保留立即暂停或撤销无人驾驶测试许可的权利。 近日来,先是Uber撞人致死,后是特斯拉撞车,人们对于自动驾驶安全性的质疑再次提上了一个高度。此时,加州宣布发放无人驾驶测试许可证,对于行业而言,这是一大进步,但在当前环境下,也是一个极其大胆的举动。 原文发布时间:2018-04-03 11:43 本文作者:韩璐 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
首尔市市长:政府有责任推动区块链技术的发展。 近日,据外媒报道,韩国首尔市正在开发自己的加密货币——S-Coin。据该市市长朴元淳介绍,由于首尔在信息和通信领域是全球领先的城市,所以他认为首尔市政府应当研究诸如区块链之类的新技术,S-Coin将用于城市资助的社会福利项目。 同时,朴元淳还认为区块链适用于首尔的所有政府机构,例如首尔市运营的公共交通系统。对于推出的S-Coin加密币,其认为此加密币不仅作为一种城市资助福利项目的支付方式,与此同时还能通过减少电量、水和天然气的使用,以保护环境。 为了实现这一目标,朴元淳表示监管加密货币的相关法律需要进行调整并补充。为了制造S-Coin,需要准备更完善的制度和法律支持。 此外,朴元淳还希望能建立起一个区块链生态系统:“我已经考察了许多区块链公司,但由于监管制度的各种束缚,区块链公司的发展将面临很大的阻碍。而政府所要做的,就是将区块链公司集合起来,领头促进区块链技术的开发。” 据了解,该技术是首尔市区块链总体规划的一部分,预期在今年4月部署完成。而在去年11月,该市专门聘请了三星SDS来帮助他们建立一套区块链城市创新的信息战略计划,这使得其成为了韩国第一个为引进区块链制定路线图的城市。 原文发布时间:2018-04-03 16:33 本文作者:Lotusun 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
缅怀那些逝去的明星科技企业! 雅虎:全球第一家互联网门户网站,市值曾一度高达1280亿美元 1994年-2017年。 雅虎,美国著名的互联网门户网站,全球第一家提供因特网导航服务的网站。其由杨致远和大卫·费罗在1994年成立。 1996 年,雅虎正式在华尔街上市。90 年代是雅虎的“黄金时代”,在那个没有社交网络的年代,雅虎几乎就是互联网的全部。其业务遍及全球24个国家和地区,用户超5亿,市值一度高达1280亿美元。 甚至当初,谷歌的拉里.佩奇都想要把公司以100万美元卖给雅虎,高傲的雅虎拒绝了这家未来自己最大的竞争对手。 结果,在谷歌的竞争下,2017年,雅虎被迫将核心业务卖给美国运营商 Verizon,这个曾经在网络泡沫时期市值高达 1250 多亿美元的互联网巨头,最终只能以48亿美元贱卖自己。 Sun Microsystems:全球最流行的编程语言——Java之父,市值曾超2000亿美元 1982年-2009年。 Sun,IT及互联网技术服务公司,孵化自斯坦福大学。 1986年,Sun在美国上市。1992年,Sun Microsystems还率先推出了全球第一台多处理器台式机——SPARCstation 10 system,一年后,成功跻身财富500强。 Sun闻名全球是在1995年,其开发的Java语言至今还是全球最流行的编程语言。巅峰状态时,其市值一度超2000亿美元。即便是谷歌,当前的市值也才1025亿美元。 但因互联网泡沫的破灭以及硬件出货量的减少,Sun的市值在一个月内下跌90%!加之与微软的官司,Sun支付了高达10亿美元的补偿金。 可以说,Sun用了近20年的时间成就其辉煌,却在一年间就迅速落败。2009年,Sun以74亿美元将自己卖给甲骨文,冷淡收场。 摩托罗拉:全球第一台手机和智能手机的制造者,却先后两次被卖 1928年-2011年。 摩托罗拉是一家历时近百年的科技巨头,也是一个历经二战的品牌。二战后到九十年代初,是摩托罗拉红火的年代。曾几何时,其就是无线通信的代名词,还是技术和品质的结晶。 而让摩托罗拉走进大众视线的,就是其手机业务。毫不夸张的说,手机行业的每一次革命性突破,几乎都来源于摩托罗拉:全球第一台手机、第一台支持JAVA功能的手机-、第一台支持WAP上网的手机、第一台智能手机等,都是摩托罗拉创造出来的。 但在科技工业快速发展的九十年代,摩托罗拉的第三代家族领导人卡尔文三世却能力有限,无法在大时代中肆意纵横捭阖、开拓疆土。2011年,摩托罗拉移动被谷歌以125亿美元的价格收购,后又被联想以29亿美元的价格从谷歌手中收购。经历多次倒卖,摩托罗拉几乎已彻底退出市场。 世界通信公司(WorldCom):仅次于AT&T的美国第二大长途电话公司 1983年-2003年。 美国世界通信公司的创始人伯尔尼·埃贝斯,在涉足商界之前曾当过美国密西西比州克林顿市一个酒吧的招待员。在每天迎来送往的招待活动中,他耳闻目睹了商界巨子们的财大气粗,立志成为以最少资本创业的富豪。 1983年伯尔尼·埃贝斯将自己的创业计划写在一张餐巾纸上,正式创立WorldCom。凭借着不羁的牛仔式冲动,在创业早期,伯尔尼·埃贝斯通过陆续并购70家中小型通信公司,一飞冲天地成为华尔街备受青睐的“明星企业”,后一度成为仅次于AT&T的美国第二大长途电话公司。 其实,在1999年,该公司曾宣布以1290亿美元的价格兼并Sprint公司,但该项交易因触犯垄断法未获美国及欧盟批准。如果合并成功,WorldCom将一举成为史上规模最大的通讯公司,把AT&T从此宝座拉下。 然而,在2002年6月的一次例行的资本支出检查中,该公司被发现财务造假,金额高38.52亿美元。随后,美国证券管理委员会(SEC)对其展开调查。不得已,WorldCom申请了破产保护,成为美国历史上最大的破产保护案。 2003年,WorldCom正式宣布破产,并于2006年1月被Verizon以76亿美元收购。 Palm:全球最大的手持设备制造商,卖身价只有12亿美元 1992年-2010年。 Palm曾是全球最大的手持设备制造商。 1993年,成立仅1年多时间,Palm就推出了旗下第一个产品Zoomer,但因产品不够轻巧、效率不佳,其销量并不理想。更糟的是,苹果早其两个月发布了同类产品NewtonPDA,抢占了绝大多数市场。 Palm并没有被打倒,而是在综合了Zoomer用户需求,于1994年推出了革命性的产品——touchdown(随后改名为Pilot),该产品推出十八个月内,销售量达一百万台,破天荒地超越了电器史上彩色电视机与录放影机的畅销记录。 2009年,Palm于CES发布全球第一个嵌入式操作系统webOS,犹如在智能手机市场投下一颗巨型炸弹。Palm此举似乎是在宣布,比拼硬件的时代已终结,软件为王的的时代已经到来。 但随着黑莓、苹果等厂商的崛起,Palm因其产品功能简单逐渐没落,于2010年卖身给惠普,卖身价戏剧性的只有12亿美元。 Theranos:仅次于特斯拉的创业公司,硅谷的严重教训 2003年-2017年。 伊丽莎白·霍尔姆斯辍学于斯坦福大学,创办Theranos时仅有19岁,创业资金是其大学期间省下的。 作为硅谷最瞩目的血液检测初创公司,Theranos的口号是“告别可怕的针头和采血试管”,宣称可以提供一种更快、更便宜的无痛验血,只需几滴血便可以完成在专业医疗实验室内的医疗检查,从胆固醇到癌症几乎都能检测,检测项目高达240余项。 2015年10月,《北大西洋月刊》邀请了硅谷101位CEO、投资人和智囊团成员对硅谷现在的技术、政治和文化进行了一次投票,在“哪一家创业公司会改变世界?”这一问题上,其投票结果显示,Theranos仅次于特斯拉位居第二,估值一度高达90亿美元。 2015年,有海外媒体曝Theranos存在欺诈行为,后经过调查,伊丽莎白被指控长年通过夸大或虚假陈述公司的技术、业务和财务业绩,从投资者那里募集了超过7亿美元的资金。目前,该公司已被查封。 对于硅谷来说,Theranos是对硅谷的一次严重教训。 3721:第一个,也是最大的流氓软件 1998年-2009年。 3721由周鸿祎创立,一直专注于中文上网服务,是中文上网服务的开创者和行业领导者,也开创了第三代中文上网方式。 即便是停止运营前,3721的注册企业用户也高达30万家,拥有近4000家的渠道合作伙伴。使用过程中,用户无需记忆复杂的域名,2003年,被雅虎斥资1.2亿美元收购。 但众所周知,3721是一个介于病毒和正规软件之间的灰色产物,具有强制安装、难以卸载、浏览器劫持、广告弹出、恶意收集用户信息、恶意卸载其它软件、恶意捆绑、恶意安装等特性,被公认为是我国第一个,也是最大的流氓软件。 2009年,雅虎宣布放弃3721,正式停止运营。有趣的是,作为这一流氓软件的开发者,周鸿祎在3721被收购后,竟创立了奇虎360,发布了以网络安全服务为核心的360杀毒软件。 快播:人人都欠“快播”一个会员 2007年-2014年。 很多人的视频播放器之旅都是从快播开始的。 2007年,王欣在深圳一个仅有10多平方米的民房里创立了快播。因搭上了P2P技术的快车,快播仅凭一己之身,就可支持MKV、RMVB、MPEG、AVI、WMV等多款主流音视频格式。 2010年到2012年,是快播的黄金时代,曾一度成为全国市场占有量第一的播放器,总安装量超3亿,中国网民数量达5.38亿。 可能是被成功冲昏了头脑,快播开始“剑走偏锋”,平台上线大量侵权及涉黄内容。2012年,快播遭乐视起诉;2013年2月,快播又遭中影集团起诉;2013年,优酷、土豆、腾讯、乐视等十余家公司炮轰快播网络视频盗版和盗链行为。 2013年底,快播管理的4台服务器中被发现存储3000余部淫秽色情视频。随后,有关部门对其展开调查,后王欣被以“网上传播淫秽色情信息”被依法抓捕。快播也停止了运营。 快播的停止运营,曾让不少粉丝大呼——人人都欠“快播”一个会员! 2018年2月,王欣刑满出狱。可笑的是,当年举报快播的乐视,如今正身处水深火热之中。 Lily Robotics:史上最大众筹项目,筹集金额高达3400万美元! 2013年-2017年。 Lily的寿命很短,只有四年的时间。 其实,Lily并不乐于称自己是无人机公司,而是更愿意别人称其产品为——飞行的照相机。 2014年春天,Lily成功拿到了纽约潜力股风头大牛沙娜-菲舍尔的投资,随后,Lily无人机炫酷的演示视频就迅速刷爆各个社交网络,也成功的站在了媒体的镁光灯下。 2015年,Lily登陆众筹平台,创下了高达3400万美元的众筹记录,成为了史上最大的众筹项目。 但这并不是Lily辉煌的开始,而是噩梦的源头。因融资遇阻,Lily自众筹之后连续三次跳票,最终于2017年1月向所有预订用户发送了一封主题为“旅程的结束”的电子邮件,坦言Lily已经竭尽全力,但仍然无法解决的产品难题,所以决定关闭公司。 与此同时,其也因虚假广告,伪造宣传视频等被旧金山地区检察官起诉。 小蓝:公认用户体验最好的共享单车 2016年-2017年。 不到一年时间,小蓝就如一阵风,先将大街小巷铺满蓝色,后黯然离场。 2016年,共享单车行业突然爆发,小蓝也就是在这样的情况下应运而生。 率先在深圳开始运营后,四个月时间,小蓝先后在广州、成都、南京、北京、旧金山落地,成为公认的继摩拜、ofo之后,共享单车第二梯队运营商。 和所有退场的共享单车一样,小蓝同样遭遇了融资不顺、资金链断裂等问题。从2017年9月末开始,关于小蓝单车退款难的消息不胫而走。在经历了两个月的“找李刚”之后,年底,小蓝宣布卖身于滴滴。 但相较于被美团收购的摩拜,小蓝的“卖身故事”似乎更得用户同情。因为与摩拜肆意扩张市场相比,小蓝所提供,是公认用户体验最好的共享单车。 原文发布时间:2018-04-05 19:40 本文作者:伶轩 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。
iphone 5s系统自带的是科学计算器,自带二进制换算功能。
iphone 5s科学计算器使用方法:
1、用手指从屏幕底部向上滑,调出【控制中心】,点击右上方【转屏】,关闭【竖排方向锁定】。
2、进入【附加程序】,选择【计算器】,此时还是简单的计算器。
3、旋转手机,手机屏幕变成横向的时候,系统就自动开启【科学计算器】了。
正整数转换二进制计算方法:不断除以二取余数直到商为1或0,再倒序书写。
按问题中的正整数18为例,计算方法如下:
18/2=9 余0;
9/2=4 余1;
4/2=2 余0;
2/2=1 余0;
1/2=0 余1;
倒序书写所有余数为:10010;
规范书写为:(18)10=(10010)2
结果为:十进制数18转换成二进制数为10010。
计算过程及结果如图中所示。
再以正整数48为例,计算如下:
48/2=24 余0;
24/2=12 余0;
12/2=6 余0;
6/2=3 余0;
3/2=1 余1;
1/2=0 余1;
倒序书写所有余数为110000;
规范书写为:(48)10=(110000)2
结果为:十进制数48转换成二进制数为110000。
如果是在计算机领域,由于计算机内部表示数的字节单位是定长的,如8位、16位、或32位。所以,位数不够时就需要高位补零,即18转换成二进制以后就是0010010。
-------------------------
12的二进制,052的二进制,进行与运算得001000,转换过来为8