HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

单webview模式下下拉加载数据结果下拉页面失效(有问题的解决)

官方例子im-chat.html 修改成聊天页面,添加下拉方案实现加载聊天记录,结果添加下拉之后,页面数据却拉不下来了,查阅了各种数据,
参照http://ask.dcloud.net.cn/article/12686 的帖子查看了淘宝,知乎的源码,结果发现注释掉官方例子上的html,body的样式即可解决,但是有两个新的问题
1.页面下输入框弹出键盘时,聊天记录不能自动向上伸缩

  1. 进页面加载完数据页面不会跳到最下面的记录
继续阅读 »

官方例子im-chat.html 修改成聊天页面,添加下拉方案实现加载聊天记录,结果添加下拉之后,页面数据却拉不下来了,查阅了各种数据,
参照http://ask.dcloud.net.cn/article/12686 的帖子查看了淘宝,知乎的源码,结果发现注释掉官方例子上的html,body的样式即可解决,但是有两个新的问题
1.页面下输入框弹出键盘时,聊天记录不能自动向上伸缩

  1. 进页面加载完数据页面不会跳到最下面的记录
收起阅读 »

动态修改原生标题的方法——弯道超车---翻车了~看评论吧

原生标题

关于使用原生标题,官方给出的方案是,在manifest.json中配置首页原生标题
如果是打开新页面,则可以配置mui.openWindow里面的titleNView来设置标题与按钮
但是,不支持动态修改原生标题。

最近在开发一个app,需要底部tar,关于此效果最好的方法,官方已经给出了一个demo
具体可看:http://ask.dcloud.net.cn/article/12602
我的需求:点击下面的选项卡的时候,原生标题可以显示不同的文字 ,比如点击'分类'的时候,标题上显示的是’分类‘
但是通过找api,发现,确实不能动态修改标题,不过克想到一个弯道超车的方法:

通过找api,发现不能动态修改,但是可以在这个原生View上添加新的文字,于是就想到了下面的方法:
就是在manifest.json中,原生标题titletext的值为空,这样就首页的标题就是空的,啥也没有
然后在首页的代码中,对这个原生View对象进行绘制文字,并且在点击选项卡的时候,先执行View.reset(),即清空,再重新绘制新的文字
这样就算是动态修改标题了,相关代码如下:

function titleRedraw(id)  
{     
    var titleArr = ['首页','分类','购物车','个人中心'];  
    var scan = window.innerWidth - 34;  

     _self = plus.webview.currentWebview();  
     var titleView = _self.getNavigationbar();  
    var bitmap_menu = new plus.nativeObj.Bitmap("saoyisao");  
    titleView.reset();    
    titleView.drawText(titleArr[id], {}, {color:'#ffffff'});      

        //以下代码为在右上角添加扫一扫按钮,不需要的可以删除  
     bitmap_menu.load("images/saoyisao.png");  
     titleView.drawBitmap(bitmap_menu, {}, {  
            top: "10px",  
            right: "10px",  
        width: "24px",  
        height: "24px",  
        color:'#ffffff'  
     });   

     titleView.interceptTouchEvent(true);  
     titleView.addEventListener("click", function(e) {  
        var x = e.clientX;  
        if(x > scan) { //触发menu菜单  
              console.log('扫一扫');  
         }  
    }, false);  
}

在首页可第一时间执行的地方,先执行一遍titleRedraw(0);这样显示的就是首页,
然后在点击底部选项卡的地方,再执行一遍,并传入对应的下标就可以了

===========担心的问题===========
现在比较担心的就是,因为一直在重绘,所以对性能影响大不大?有没有大牛可以出来帮忙解释一下。
另外还有一个优化的地方,不知道可不可行:即不使用reset()来清空所有
比如我这个,所有页面都有右上角的按钮,那就只清除drawText所绘制的内容,是不是可行?

继续阅读 »

关于使用原生标题,官方给出的方案是,在manifest.json中配置首页原生标题
如果是打开新页面,则可以配置mui.openWindow里面的titleNView来设置标题与按钮
但是,不支持动态修改原生标题。

最近在开发一个app,需要底部tar,关于此效果最好的方法,官方已经给出了一个demo
具体可看:http://ask.dcloud.net.cn/article/12602
我的需求:点击下面的选项卡的时候,原生标题可以显示不同的文字 ,比如点击'分类'的时候,标题上显示的是’分类‘
但是通过找api,发现,确实不能动态修改标题,不过克想到一个弯道超车的方法:

通过找api,发现不能动态修改,但是可以在这个原生View上添加新的文字,于是就想到了下面的方法:
就是在manifest.json中,原生标题titletext的值为空,这样就首页的标题就是空的,啥也没有
然后在首页的代码中,对这个原生View对象进行绘制文字,并且在点击选项卡的时候,先执行View.reset(),即清空,再重新绘制新的文字
这样就算是动态修改标题了,相关代码如下:

function titleRedraw(id)  
{     
    var titleArr = ['首页','分类','购物车','个人中心'];  
    var scan = window.innerWidth - 34;  

     _self = plus.webview.currentWebview();  
     var titleView = _self.getNavigationbar();  
    var bitmap_menu = new plus.nativeObj.Bitmap("saoyisao");  
    titleView.reset();    
    titleView.drawText(titleArr[id], {}, {color:'#ffffff'});      

        //以下代码为在右上角添加扫一扫按钮,不需要的可以删除  
     bitmap_menu.load("images/saoyisao.png");  
     titleView.drawBitmap(bitmap_menu, {}, {  
            top: "10px",  
            right: "10px",  
        width: "24px",  
        height: "24px",  
        color:'#ffffff'  
     });   

     titleView.interceptTouchEvent(true);  
     titleView.addEventListener("click", function(e) {  
        var x = e.clientX;  
        if(x > scan) { //触发menu菜单  
              console.log('扫一扫');  
         }  
    }, false);  
}

在首页可第一时间执行的地方,先执行一遍titleRedraw(0);这样显示的就是首页,
然后在点击底部选项卡的地方,再执行一遍,并传入对应的下标就可以了

===========担心的问题===========
现在比较担心的就是,因为一直在重绘,所以对性能影响大不大?有没有大牛可以出来帮忙解释一下。
另外还有一个优化的地方,不知道可不可行:即不使用reset()来清空所有
比如我这个,所有页面都有右上角的按钮,那就只清除drawText所绘制的内容,是不是可行?

收起阅读 »

认识 V8 引擎

JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力。编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进行完全编译,而解释型语言一边编译一边执行,很明显解释型语言的执行速度是慢于编译型语言的,而JavaScript就是一种解释型脚本语言,支持动态类型、弱类型、基于原型的语言,内置支持类型。鉴于JavaScript都是在前端执行,而且需要及时响应用户,这就要求JavaScript可以快速的解析及执行。
随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。
1.渲染引擎及网页渲染
浏览器自从上世纪80年代后期90年代初期诞生以来,已经得到了长足的发展,其功能也越来越丰富,包括网络、资源管理、网页浏览、多页面管理、插件和扩展、书签管理、历史记录管理、设置管理、下载管理、账户和同步、安全机制、隐私管理、外观主题、开发者工具等。在这些功能中,为用户提供网页浏览服务无疑是最重要的功能,下面将对相关内容进行介绍。
1.1渲染引擎
渲染引擎:能够将HTML/CSS/JavaScript文本及相应的资源文件转换成图像结果。渲染引擎的主要作用是将资源文件转化为用户可见的结果。在浏览器的发展过程中,不同的厂商开发了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod浏览器)等。WebKit是由苹果2005年发起的一个开源项目,引起了众多公司的重视,几年间被很多公司所采用,在移动端更占据了垄断地位。更有甚者,开发出了基于WebKit的支持HTML5的web操作系统(如:Chrome OS、Web OS)。
下面是WebKit的大致结构:


上图中实线框内模块是所有移植的共有部分,虚线框内不同的厂商可以自己实现。下面进行介绍:
操作系统:是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。WebKit也是在操作系统上工作的。
第三方库,为了WebKit提供支持,如图形库、网络库、视频库等。
WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。WebKit Ports是WebKit中的非共享部分,由于平台差异、第三方库和需求的不同等原因,不同的移植导致了WebKit不同版本行为不一致,它是不同浏览器性能和功能差异的关键部分。
WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不同的移植有不同的接口规范。
测试用例,包括布局测试用例和性能测试用例,用来验证渲染结果的正确性。

1.2.网页渲染流程
上面介绍了渲染引擎的各个模块,那么一张网页,要经历怎样的过程,才能抵达用户面前?

首先是网页内容,输入到HTML解析器,HTML解析器解析,然后构建DOM树,在这期间如果遇到JavaScript代码则交给JavaScript引擎处理;如果来自CSS解析的样式信息,构建一个内部绘图模型。该模型由布局模块计算模型内部各个元素的位置和大小信息,最后由绘图模块完成从该模型到图像的绘制。在网页渲染的过程中,大致可以分为下面3个阶段。
1.2.1从输入URL到生成DOM树
1.地址栏输入URL,WebKit调用资源加载器加载相应资源;

  1. 加载器依赖网络模块建立连接,发送请求并接收答复;
  2. WebKit接收各种网页或者资源数据,其中某些资源可能同步或异步获取;
  3. 网页交给HTML解析器转变为词语;
  4. 解释器根据词语构建节点,形成DOM树;
  5. 如果节点是JavaScript代码,调用JavaScript引擎解释并执行;
  6. JavaScript代码可能会修改DOM树结构;
  7. 如果节点依赖其他资源,如图片\css、视频等,调用资源加载器加载它们,但这些是异步加载的,不会阻碍当前DOM树继续创建;如果是JavaScript资源URL(没有标记异步方式),则需要停止当前DOM树创建,直到JavaScript加载并被JavaScript引擎执行后才继续DOM树的创建。
    1.2.2.从DOM树到构建WebKit绘图上下文
    1.CSS文件被CSS解释器解释成内部表示;
  8. CSS解释器完成工作后,在DOM树上附加样式信息,生成RenderObject树;
  9. RenderObject节点在创建的同时,WebKit会根据网页层次结构构建RenderLayer树,同时构建一个虚拟绘图上下文。
    1.2.3.绘图上下文到最终图像呈现
    1.绘图上下文是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类;
  10. 绘图实现类也可能有简单的实现,也可能有复杂的实现,软件渲染、硬件渲染、合成渲染等;
  11. 绘图实现类将2D图形库或者3D图形库绘制结果保存,交给浏览器界面进行展示。
    上述是一个完整的渲染过程,现代网页很多都是动态的,随着网页与用户的交互,浏览器需要不断的重复渲染过程。
    1.3.JavaScript引擎

JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。JavaScript代码是在浏览器端解析和执行的,如果需要时间太长,会影响用户体验。那么提高JavaScript的解析速度就是当务之急。JavaScript引擎和渲染引擎的关系如下图所示:

JavaScript语言是解释型语言,为了提高性能,引入了Java虚拟机和C++编译器中的众多技术。现在JavaScript引擎的执行过程大致是:
源代码-→抽象语法树-→字节码-→JIT-→本地代码(V8引擎没有中间字节码)。一段代码的抽象语法树示例如下:

function demo(name) {  
    console.log(name);  
}

抽象语法树如下:

V8更加直接的将抽象语法树通过JIT技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。在V8生成本地代码后,也会通过Profiler采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化,但极大减少了转换时间。
但是在2017年4月底,v8 的 5.9 版本发布了,新增了一个 Ignition 字节码解释器,将默认启动,从此之后将与JSCore有大致相同的流程。做出这一改变的原因为:(主要动机)减轻机器码占用的内存空间,即牺牲时间换空间;提高代码的启动速度;对 v8 的代码进行重构,降低 v8 的代码复杂度(V8 Ignition:JS 引擎与字节码的不解之缘 - CNode技术社区)。
JavaScript的性能和C相比还有不小的距离,可预见的未来估计也只能接近它,而不是与它相比,这从语言类型上已经决定。下面将对V8引擎进行更为细致的介绍。
2.V8引擎
V8引擎是一个JavaScript引擎实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源。V8使用C++开发,,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。
V8项目代码结构如下:

2.1.数据表示
JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像c++或者java等静态类型语言,在编译时候就可以确切知道变量的类型。然而,在运行时计算和决定类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或者JAVA低很多的原因之一。
在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。由于JavaScript是无类型语言,那就不能像c++那样在执行时已经知道变量的类型和地址,需要临时确定。JavaScript 和C++有以下几个区别:
1.编译确定位置,C++编译阶段确定位置偏移信息,在执行时直接存取,JavaScript在执行阶段确定,而且执行期间可以修改对象属性;

  1. 偏移信息共享,C++有类型定义,执行时不能动态改变,可共享偏移信息,JavaScript每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中;
  2. 偏移信息查找,C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置偏移位置,JavaScript中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作。
    在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数两个汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。示例如下:

在JavaScript中,除了boolean,number,string,null,undefined这五个简单变量外,其它的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JavaScript的内部表示问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的。
除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他。

JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。

2.2.工作过程
前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会。

V8引擎编译本地代码时使用的主要类如下所示:

1.Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;

  1. Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
  2. AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
  3. AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
  4. FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码。


JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起。

总结
在过去几年,JavaScript在很多领域得到了广泛的应用,然而限于JavaScript语言本身的不足,执行效率不高。Google也推出了一些JavaScript网络应用,如Gmail、Google Maps及Google Docs office等。这些应用的性能不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。Google就开始了V8引擎的研究,将一系列新技术引入JavaScript引擎中,大大提高了JavaScript的执行效率。相信随着V8引擎的不断发展,JavaScript也会有更广泛的应用场景,前端工程师也会有更好的未来!
那么结合上面对于V8引擎的介绍,我们在编程中应注意:
1.类型。对于函数,JavaScript是一种动态类型语言,JavaScriptCore和V8都使用隐藏类和内嵌缓存来提高性能,为了保证缓存命中率,一个函数应该使用较少的数据类型;对于数组,应尽量存放相同类型的数据,这样就可以通过偏移位置来访问。

  1. 数据表示。简单类型数据(如整型)直接保存在句柄中,可以减少寻址时间和内存占用,如果可以使用整数表示的,尽量不要用浮点类型。
  2. 内存。虽然JavaScript语言会自己进行垃圾回收,但我们也应尽量做到及时回收不用的内存,对不再使用的对象设置为null或使用delete方法来删除(使用delete方法删除会触发隐藏类新建,需要更多的额外操作)。
    4.优化回滚。在执行多次之后,不要出现修改对象类型的语句,尽量不要触发优化回滚,否则会大幅度降低代码的性能。
  3. 新机制。使用JavaScript引擎或者渲染引擎提供的新机制和新接口提高性能。
继续阅读 »

JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力。编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进行完全编译,而解释型语言一边编译一边执行,很明显解释型语言的执行速度是慢于编译型语言的,而JavaScript就是一种解释型脚本语言,支持动态类型、弱类型、基于原型的语言,内置支持类型。鉴于JavaScript都是在前端执行,而且需要及时响应用户,这就要求JavaScript可以快速的解析及执行。
随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。
1.渲染引擎及网页渲染
浏览器自从上世纪80年代后期90年代初期诞生以来,已经得到了长足的发展,其功能也越来越丰富,包括网络、资源管理、网页浏览、多页面管理、插件和扩展、书签管理、历史记录管理、设置管理、下载管理、账户和同步、安全机制、隐私管理、外观主题、开发者工具等。在这些功能中,为用户提供网页浏览服务无疑是最重要的功能,下面将对相关内容进行介绍。
1.1渲染引擎
渲染引擎:能够将HTML/CSS/JavaScript文本及相应的资源文件转换成图像结果。渲染引擎的主要作用是将资源文件转化为用户可见的结果。在浏览器的发展过程中,不同的厂商开发了不同的渲染引擎,如Tridend(IE)、Gecko(FF)、WebKit(Safari,Chrome,Andriod浏览器)等。WebKit是由苹果2005年发起的一个开源项目,引起了众多公司的重视,几年间被很多公司所采用,在移动端更占据了垄断地位。更有甚者,开发出了基于WebKit的支持HTML5的web操作系统(如:Chrome OS、Web OS)。
下面是WebKit的大致结构:


上图中实线框内模块是所有移植的共有部分,虚线框内不同的厂商可以自己实现。下面进行介绍:
操作系统:是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。WebKit也是在操作系统上工作的。
第三方库,为了WebKit提供支持,如图形库、网络库、视频库等。
WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。WebKit Ports是WebKit中的非共享部分,由于平台差异、第三方库和需求的不同等原因,不同的移植导致了WebKit不同版本行为不一致,它是不同浏览器性能和功能差异的关键部分。
WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不同的移植有不同的接口规范。
测试用例,包括布局测试用例和性能测试用例,用来验证渲染结果的正确性。

1.2.网页渲染流程
上面介绍了渲染引擎的各个模块,那么一张网页,要经历怎样的过程,才能抵达用户面前?

首先是网页内容,输入到HTML解析器,HTML解析器解析,然后构建DOM树,在这期间如果遇到JavaScript代码则交给JavaScript引擎处理;如果来自CSS解析的样式信息,构建一个内部绘图模型。该模型由布局模块计算模型内部各个元素的位置和大小信息,最后由绘图模块完成从该模型到图像的绘制。在网页渲染的过程中,大致可以分为下面3个阶段。
1.2.1从输入URL到生成DOM树
1.地址栏输入URL,WebKit调用资源加载器加载相应资源;

  1. 加载器依赖网络模块建立连接,发送请求并接收答复;
  2. WebKit接收各种网页或者资源数据,其中某些资源可能同步或异步获取;
  3. 网页交给HTML解析器转变为词语;
  4. 解释器根据词语构建节点,形成DOM树;
  5. 如果节点是JavaScript代码,调用JavaScript引擎解释并执行;
  6. JavaScript代码可能会修改DOM树结构;
  7. 如果节点依赖其他资源,如图片\css、视频等,调用资源加载器加载它们,但这些是异步加载的,不会阻碍当前DOM树继续创建;如果是JavaScript资源URL(没有标记异步方式),则需要停止当前DOM树创建,直到JavaScript加载并被JavaScript引擎执行后才继续DOM树的创建。
    1.2.2.从DOM树到构建WebKit绘图上下文
    1.CSS文件被CSS解释器解释成内部表示;
  8. CSS解释器完成工作后,在DOM树上附加样式信息,生成RenderObject树;
  9. RenderObject节点在创建的同时,WebKit会根据网页层次结构构建RenderLayer树,同时构建一个虚拟绘图上下文。
    1.2.3.绘图上下文到最终图像呈现
    1.绘图上下文是一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类;
  10. 绘图实现类也可能有简单的实现,也可能有复杂的实现,软件渲染、硬件渲染、合成渲染等;
  11. 绘图实现类将2D图形库或者3D图形库绘制结果保存,交给浏览器界面进行展示。
    上述是一个完整的渲染过程,现代网页很多都是动态的,随着网页与用户的交互,浏览器需要不断的重复渲染过程。
    1.3.JavaScript引擎

JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。JavaScript代码是在浏览器端解析和执行的,如果需要时间太长,会影响用户体验。那么提高JavaScript的解析速度就是当务之急。JavaScript引擎和渲染引擎的关系如下图所示:

JavaScript语言是解释型语言,为了提高性能,引入了Java虚拟机和C++编译器中的众多技术。现在JavaScript引擎的执行过程大致是:
源代码-→抽象语法树-→字节码-→JIT-→本地代码(V8引擎没有中间字节码)。一段代码的抽象语法树示例如下:

function demo(name) {  
    console.log(name);  
}

抽象语法树如下:

V8更加直接的将抽象语法树通过JIT技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。在V8生成本地代码后,也会通过Profiler采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化,但极大减少了转换时间。
但是在2017年4月底,v8 的 5.9 版本发布了,新增了一个 Ignition 字节码解释器,将默认启动,从此之后将与JSCore有大致相同的流程。做出这一改变的原因为:(主要动机)减轻机器码占用的内存空间,即牺牲时间换空间;提高代码的启动速度;对 v8 的代码进行重构,降低 v8 的代码复杂度(V8 Ignition:JS 引擎与字节码的不解之缘 - CNode技术社区)。
JavaScript的性能和C相比还有不小的距离,可预见的未来估计也只能接近它,而不是与它相比,这从语言类型上已经决定。下面将对V8引擎进行更为细致的介绍。
2.V8引擎
V8引擎是一个JavaScript引擎实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源。V8使用C++开发,,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。
V8项目代码结构如下:

2.1.数据表示
JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定,这就不像c++或者java等静态类型语言,在编译时候就可以确切知道变量的类型。然而,在运行时计算和决定类型,会严重影响语言性能,这也就是JavaScript运行效率比C++或者JAVA低很多的原因之一。
在C++中,源代码需要经过编译才能执行,在生成本地代码的过程中,变量的地址和类型已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间。由于JavaScript是无类型语言,那就不能像c++那样在执行时已经知道变量的类型和地址,需要临时确定。JavaScript 和C++有以下几个区别:
1.编译确定位置,C++编译阶段确定位置偏移信息,在执行时直接存取,JavaScript在执行阶段确定,而且执行期间可以修改对象属性;

  1. 偏移信息共享,C++有类型定义,执行时不能动态改变,可共享偏移信息,JavaScript每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中;
  2. 偏移信息查找,C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置偏移位置,JavaScript中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作。
    在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数两个汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间。示例如下:

在JavaScript中,除了boolean,number,string,null,undefined这五个简单变量外,其它的数据都是对象,V8使用一种特殊的方式来表示他们,进而优化JavaScript的内部表示问题。

在V8中,数据的内部表示由数据的实际内容和数据的句柄构成。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的。
除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他。

JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。

2.2.工作过程
前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会。

V8引擎编译本地代码时使用的主要类如下所示:

1.Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;

  1. Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
  2. AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
  3. AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
  4. FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码。


JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起。

总结
在过去几年,JavaScript在很多领域得到了广泛的应用,然而限于JavaScript语言本身的不足,执行效率不高。Google也推出了一些JavaScript网络应用,如Gmail、Google Maps及Google Docs office等。这些应用的性能不仅受到服务器、网络、渲染引擎以及其他诸多因素的影响,同时也受到JavaScript本身执行速度的影响。然而既有的JavaScript引擎无法满足新的需求,而性能不佳一直是网络应用开发者最关心的。Google就开始了V8引擎的研究,将一系列新技术引入JavaScript引擎中,大大提高了JavaScript的执行效率。相信随着V8引擎的不断发展,JavaScript也会有更广泛的应用场景,前端工程师也会有更好的未来!
那么结合上面对于V8引擎的介绍,我们在编程中应注意:
1.类型。对于函数,JavaScript是一种动态类型语言,JavaScriptCore和V8都使用隐藏类和内嵌缓存来提高性能,为了保证缓存命中率,一个函数应该使用较少的数据类型;对于数组,应尽量存放相同类型的数据,这样就可以通过偏移位置来访问。

  1. 数据表示。简单类型数据(如整型)直接保存在句柄中,可以减少寻址时间和内存占用,如果可以使用整数表示的,尽量不要用浮点类型。
  2. 内存。虽然JavaScript语言会自己进行垃圾回收,但我们也应尽量做到及时回收不用的内存,对不再使用的对象设置为null或使用delete方法来删除(使用delete方法删除会触发隐藏类新建,需要更多的额外操作)。
    4.优化回滚。在执行多次之后,不要出现修改对象类型的语句,尽量不要触发优化回滚,否则会大幅度降低代码的性能。
  3. 新机制。使用JavaScript引擎或者渲染引擎提供的新机制和新接口提高性能。
收起阅读 »

官方的上拉下拉加载...无力吐槽

官方的上拉下拉加载...无力吐槽

例子和文档写的真心烂......

跪求用户体验!!!

官方的上拉下拉加载...无力吐槽

例子和文档写的真心烂......

跪求用户体验!!!

清除文件缓存

看论坛中有很多人在问这个问题,于是就写了一个读取和清除指定目录中文件缓存的方法,分享出来,希望能帮助到需要的人。

//以下是读取指定目录下子目录以及文件的大小的方法  
function showCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) { //通过URL参数获取目录对象或文件对象  
        var fileSize = 0;  
        var directoryReader = entry.createReader();  
        directoryReader.readEntries(function(entries) {   //获取当前目录中的所有文件和子目录  
            for(var i = 0; i < entries.length; i++) {  
                if(entries[i].isFile) {  
                    entries[i].file(function(file) {  
                        fileSize += (file.size * 0.0009766);  
                    }, function(e) {  
                        mui.toast(e.message);  
                    });  
                } else {  
                    entries[i].getMetadata(function(metadata) {  
                        fileSize += (metadata.size * 0.0009766); //1字节=0.0009766kb  
                    }, function() {  
                        mui.toast(e.message);  
                    });  
                }  
            }  
        }, function(e) {  
            mui.toast('文件读取失败');  
        });  
        setTimeout(function() {  
            $('#size').text(Math.ceil(fileSize) + 'kb');  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}  

//以下是清除缓存在指定目录中文件的方法  
function clearCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) {  
        entry.removeRecursively(function(entry) { //递归删除其下的所有文件及子目录  
            mui.toast("缓存清理完成");  
        }, function(e) {  
            mui.toast(e.message);  
        });  
        setTimeout(function() {  
            showCache();  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}

好了,完成了,android和ios下都测试过,没有问题。

继续阅读 »

看论坛中有很多人在问这个问题,于是就写了一个读取和清除指定目录中文件缓存的方法,分享出来,希望能帮助到需要的人。

//以下是读取指定目录下子目录以及文件的大小的方法  
function showCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) { //通过URL参数获取目录对象或文件对象  
        var fileSize = 0;  
        var directoryReader = entry.createReader();  
        directoryReader.readEntries(function(entries) {   //获取当前目录中的所有文件和子目录  
            for(var i = 0; i < entries.length; i++) {  
                if(entries[i].isFile) {  
                    entries[i].file(function(file) {  
                        fileSize += (file.size * 0.0009766);  
                    }, function(e) {  
                        mui.toast(e.message);  
                    });  
                } else {  
                    entries[i].getMetadata(function(metadata) {  
                        fileSize += (metadata.size * 0.0009766); //1字节=0.0009766kb  
                    }, function() {  
                        mui.toast(e.message);  
                    });  
                }  
            }  
        }, function(e) {  
            mui.toast('文件读取失败');  
        });  
        setTimeout(function() {  
            $('#size').text(Math.ceil(fileSize) + 'kb');  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}  

//以下是清除缓存在指定目录中文件的方法  
function clearCache() {  
    plus.io.resolveLocalFileSystemURL('_doc/', function(entry) {  
        entry.removeRecursively(function(entry) { //递归删除其下的所有文件及子目录  
            mui.toast("缓存清理完成");  
        }, function(e) {  
            mui.toast(e.message);  
        });  
        setTimeout(function() {  
            showCache();  
        }, 500);  
    }, function(e) {  
        mui.toast('文件路径读取失败');  
    });  
}

好了,完成了,android和ios下都测试过,没有问题。

收起阅读 »

js new Date

new Date()对参数不管是格式还是内容都要求,且只返回字符串

new Date();  
//Fri Aug 21 2015 15:51:55 GMT+0800 (中国标准时间)  
new Date(1293879600000);  
new Date('2011-01-01T11:00:00')  
new Date('2011/01/01 11:00:00')  
new Date(2011,0,1,11,0,0)  
new Date('jan 01 2011,11 11:00:00')  
new Date('Sat Jan 01 2011 11:00:00')  
//Sat Jan 01 2011 11:00:00 GMT+0800 (中国标准时间)  
new Date('sss');  
new Date('2011/01/01T11:00:00');  
new Date('2011-01-01-11:00:00')  
new Date('1293879600000');  
//Invalid Date  
new Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')  
//596069988000

从上面几个测试结果可以很容易发现

new Date()在参数正常的情况只会返回当前时间的字符串(且是当前时区的时间)
new Date()在解析一个具体的时间的时候,对参数有较严格的格式要求,格式不正确的时候会直接返回Invalid Date,比如将number类的时间戳转换成string类的时候也会导致解析出错
虽然new Date()的返回值是字符串,然而两个new Date()的结果字符串是可以直接相减的,结果为相差的毫秒数。
那么,new Date()能接受的参数格式到底是什么标准呢?(相对于严格要求的多参数传值方法。非严格的单参数(数字日期表示格式)更常用且更容易出错,所以下文只考虑单参数数字时间字符串转换的情况)
参考:http://chitanda.me/2015/08/21/the-trivia-of-js-date-function/

继续阅读 »

new Date()对参数不管是格式还是内容都要求,且只返回字符串

new Date();  
//Fri Aug 21 2015 15:51:55 GMT+0800 (中国标准时间)  
new Date(1293879600000);  
new Date('2011-01-01T11:00:00')  
new Date('2011/01/01 11:00:00')  
new Date(2011,0,1,11,0,0)  
new Date('jan 01 2011,11 11:00:00')  
new Date('Sat Jan 01 2011 11:00:00')  
//Sat Jan 01 2011 11:00:00 GMT+0800 (中国标准时间)  
new Date('sss');  
new Date('2011/01/01T11:00:00');  
new Date('2011-01-01-11:00:00')  
new Date('1293879600000');  
//Invalid Date  
new Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')  
//596069988000

从上面几个测试结果可以很容易发现

new Date()在参数正常的情况只会返回当前时间的字符串(且是当前时区的时间)
new Date()在解析一个具体的时间的时候,对参数有较严格的格式要求,格式不正确的时候会直接返回Invalid Date,比如将number类的时间戳转换成string类的时候也会导致解析出错
虽然new Date()的返回值是字符串,然而两个new Date()的结果字符串是可以直接相减的,结果为相差的毫秒数。
那么,new Date()能接受的参数格式到底是什么标准呢?(相对于严格要求的多参数传值方法。非严格的单参数(数字日期表示格式)更常用且更容易出错,所以下文只考虑单参数数字时间字符串转换的情况)
参考:http://chitanda.me/2015/08/21/the-trivia-of-js-date-function/

收起阅读 »

如何使用HBuilderX开发微信小程序

mpvue 小程序 微信小程序

注意,本文讲的是使用HBuilderX开发原生微信小程序,不是uni-app。使用uni-app请在HBuilderX中新建uni-app项目

很多开发者需要开发小程序,但小程序的开发IDE却总被众多开发者吐槽。
很多开发者只把微信开发工具当模拟器用,代码编写仍然在其他专业编辑器里。

HBuilder作为专业的开发工具,近期也提供了对微信小程序的开发支持:

  • 强大的代码提示
  • 高效的字处理
  • 保存代码时自动刷新微信模拟器。

下文简单讲解使用步骤。

新建微信小程序

在HBuilderX中新建项目时,支持小程序类型,如下:

小程序项目创建后,默认工程目录如下:

]

导入已有小程序

若已存在微信小程序项目,则可以直接将工程目录拖到HBuidlerX中。

小程序语法提示

语法提示是HBuilder一贯的长项,在HBuilder中对小程序语法也有很好的提示。

小程序JS API提示:

小程序wxml标签提示:

同步到小程序模拟器

HBuilderX支持同步代码到微信开发者工具,如下图所示,点击“微信开发者工具”

系统会尝试检测并启动微信开发者工具。

若是已存在的微信项目(之前已使用微信开发者工具打开过的项目),则会直接打开并显示模拟器、编译器等截面,直接跳到下方第4步骤继续阅读即可。

若是新建的项目,则需要按照如下步骤进行小程序项目的初始化导入。

1、微信开发者工具成功启动后界面:

2、选择小程序项目,并在新打开的窗口中点击右下角的“+”,打开新项目向导:

3、项目目录设置为刚刚在HBuilderX中新建的工程根目录

4、在微信开发者工具中,点击左上角的“编译器”,关闭微信编译器;若暂时不需要调试,也可以将调试器关闭,仅保留模拟器。

5、拖一下HBuilderX和微信开发者工具的位置,像如下方式,左侧为HBuilderX的编辑器截面,右侧为小程序的模拟器截面

之后,在左侧HBuilderX中修改小程序代码,右侧模拟器会自动刷新,如下是一个实际录屏示例:

最后,微信小程序有自己的appid,向微信申请后,把appid填写在项目的project.config.json里,有个appid参数。
有appid才能正式发布上传。

继续阅读 »

注意,本文讲的是使用HBuilderX开发原生微信小程序,不是uni-app。使用uni-app请在HBuilderX中新建uni-app项目

很多开发者需要开发小程序,但小程序的开发IDE却总被众多开发者吐槽。
很多开发者只把微信开发工具当模拟器用,代码编写仍然在其他专业编辑器里。

HBuilder作为专业的开发工具,近期也提供了对微信小程序的开发支持:

  • 强大的代码提示
  • 高效的字处理
  • 保存代码时自动刷新微信模拟器。

下文简单讲解使用步骤。

新建微信小程序

在HBuilderX中新建项目时,支持小程序类型,如下:

小程序项目创建后,默认工程目录如下:

]

导入已有小程序

若已存在微信小程序项目,则可以直接将工程目录拖到HBuidlerX中。

小程序语法提示

语法提示是HBuilder一贯的长项,在HBuilder中对小程序语法也有很好的提示。

小程序JS API提示:

小程序wxml标签提示:

同步到小程序模拟器

HBuilderX支持同步代码到微信开发者工具,如下图所示,点击“微信开发者工具”

系统会尝试检测并启动微信开发者工具。

若是已存在的微信项目(之前已使用微信开发者工具打开过的项目),则会直接打开并显示模拟器、编译器等截面,直接跳到下方第4步骤继续阅读即可。

若是新建的项目,则需要按照如下步骤进行小程序项目的初始化导入。

1、微信开发者工具成功启动后界面:

2、选择小程序项目,并在新打开的窗口中点击右下角的“+”,打开新项目向导:

3、项目目录设置为刚刚在HBuilderX中新建的工程根目录

4、在微信开发者工具中,点击左上角的“编译器”,关闭微信编译器;若暂时不需要调试,也可以将调试器关闭,仅保留模拟器。

5、拖一下HBuilderX和微信开发者工具的位置,像如下方式,左侧为HBuilderX的编辑器截面,右侧为小程序的模拟器截面

之后,在左侧HBuilderX中修改小程序代码,右侧模拟器会自动刷新,如下是一个实际录屏示例:

最后,微信小程序有自己的appid,向微信申请后,把appid填写在项目的project.config.json里,有个appid参数。
有appid才能正式发布上传。

收起阅读 »

ajax如何带上cookie

ajax Cookie

之前都有这样一个理解:
ajax请求时是不会自动带上cookie的,要是想让他带上的话,必须哟啊设置withCredential为true。
这个说法会让人产生完全扭曲的误解,我就是其中之一。
完整的无歧义的表述应该是这样:
1.ajax会自动带上同源的cookie,不会带上不同源的cookie

  1. 可以通过前端设置withCredentials为true, 后端设置Header的方式让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响。会被自动忽略。
  2. 这是MDN对withCredentials的解释: MDN-withCredentials ,我接着解释一下同源。
    众所周知,ajax请求是有同源策略的,虽然可以应用CORS等手段来实现跨域,但是这并不是说这样就是“同源”了。ajax在请求时就会因为这个同源的问题而决定是否带上cookie,这样解释应该没有问题了吧,还不知道同源策略的,应该去谷歌一下看看。

实验
第一步: 建立一个本地服务器

1.新建一个demo文件夹,进入文件夹,用php -S localhost:9000开启一个本地服务器
2.在demo文件夹下新建一个index.php文件, 内容为:

<?php  
    header("Access-Control-Allow-Origin: http://localhost:9999");  
    header('Access-Control-Allow-Credentials:true');  
    $value = "something with cookie";  
    setcookie("testcookie", $value, time() + 3600);  
    echo "cookie has seted";

注意: Access-Control-Allow-Origin必须制定特定的URL,不能是*, 且需要加上Access-Control-Allow-Credentials

第二步: 编写请求测试代码

在桌面上新建一个test.html文件,内容为:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>  
</head>  
<body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000/",  
            type: 'GET',  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
</body>  
</html>
  1. 在desktop目录下起一个服务器,用php -S localhost:9999开启一个本地服务器
    第三步: 测试
    1.在浏览器中访问localhost:9999/test.html,打开调试工具->application->cookie可以看到cookie设置成功。
    2.打开调试工具->netwoek,刷新一下,可以看到一个localhost请求,检查这个localhost请求的Request Headers,发现没有cookie这个头部,说明不同源的请求时不会带上cookie的(即使有CORS)
    3.把test.html放到demo文件夹下,在访问localhost:9000/test.html,查看Request Headers,会发现cookie头部, 说明同源请求自动带上了cookie
    4.把test.html的内容更改为以下内容,请求时会有cookie头部。说明withCredentials起作用了
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="jquery-2.2.4.min.js"></script>  
    </head>  
    <body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000",  
            type: 'GET',  
            xhrFields: {  
                withCredentials: true // 这里设置了withCredentials  
            },  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
    </body>  
    </html>

    参考:https://zhuanlan.zhihu.com/p/28818954
    https://www.zhihu.com/question/25427931
    同源策略
    http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
    https://stackoverflow.com/questions/2870371/why-is-jquerys-ajax-method-not-sending-my-session-cookie
    服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

同源策略是浏览器最核心也最基本的安全功能首先:web是开放的世界, 需要互联链接. 你的网站, 可以使用别人的图片, img, 使用别人 script 做统计, 做广告联盟

假设没有同源, 互联网世界是什么样?链接跳转导致的问题. http://a.com , 放一个链接到 icbc.com, 然后 window.open来打开, 获取窗口句柄, 然后可以拥有对这个页面完全的控制权. 拦截表单,捕获数据,将账号密码上传到a.com.ajax请求, 要啥就有啥. 你登录jd.com; 然后打开a.com, 通过ajax 请求http://jd.com 的用户信息接口, 这时候因为访问的jd.com,所以浏览器自动带上了jd的cookie,然后获取到你的订单list ,昵称, 所有私密信息.所以,需要要同源策略

在同一个域内,客户端脚本可以任意读写同源内的资源,dom,cookie;但是不同的域,就不行.

继续阅读 »

之前都有这样一个理解:
ajax请求时是不会自动带上cookie的,要是想让他带上的话,必须哟啊设置withCredential为true。
这个说法会让人产生完全扭曲的误解,我就是其中之一。
完整的无歧义的表述应该是这样:
1.ajax会自动带上同源的cookie,不会带上不同源的cookie

  1. 可以通过前端设置withCredentials为true, 后端设置Header的方式让ajax自动带上不同源的cookie,但是这个属性对同源请求没有任何影响。会被自动忽略。
  2. 这是MDN对withCredentials的解释: MDN-withCredentials ,我接着解释一下同源。
    众所周知,ajax请求是有同源策略的,虽然可以应用CORS等手段来实现跨域,但是这并不是说这样就是“同源”了。ajax在请求时就会因为这个同源的问题而决定是否带上cookie,这样解释应该没有问题了吧,还不知道同源策略的,应该去谷歌一下看看。

实验
第一步: 建立一个本地服务器

1.新建一个demo文件夹,进入文件夹,用php -S localhost:9000开启一个本地服务器
2.在demo文件夹下新建一个index.php文件, 内容为:

<?php  
    header("Access-Control-Allow-Origin: http://localhost:9999");  
    header('Access-Control-Allow-Credentials:true');  
    $value = "something with cookie";  
    setcookie("testcookie", $value, time() + 3600);  
    echo "cookie has seted";

注意: Access-Control-Allow-Origin必须制定特定的URL,不能是*, 且需要加上Access-Control-Allow-Credentials

第二步: 编写请求测试代码

在桌面上新建一个test.html文件,内容为:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>  
</head>  
<body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000/",  
            type: 'GET',  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
</body>  
</html>
  1. 在desktop目录下起一个服务器,用php -S localhost:9999开启一个本地服务器
    第三步: 测试
    1.在浏览器中访问localhost:9999/test.html,打开调试工具->application->cookie可以看到cookie设置成功。
    2.打开调试工具->netwoek,刷新一下,可以看到一个localhost请求,检查这个localhost请求的Request Headers,发现没有cookie这个头部,说明不同源的请求时不会带上cookie的(即使有CORS)
    3.把test.html放到demo文件夹下,在访问localhost:9000/test.html,查看Request Headers,会发现cookie头部, 说明同源请求自动带上了cookie
    4.把test.html的内容更改为以下内容,请求时会有cookie头部。说明withCredentials起作用了
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Document</title>  
    <script src="jquery-2.2.4.min.js"></script>  
    </head>  
    <body>  
    <script>  
        $.ajax({  
            url: "http://localhost:9000",  
            type: 'GET',  
            xhrFields: {  
                withCredentials: true // 这里设置了withCredentials  
            },  
            success: function(data) {  
                console.log(data)  
            },  
            error: function(err) {  
                console.error(err)  
            }  
        })  
    </script>  
    </body>  
    </html>

    参考:https://zhuanlan.zhihu.com/p/28818954
    https://www.zhihu.com/question/25427931
    同源策略
    http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
    https://stackoverflow.com/questions/2870371/why-is-jquerys-ajax-method-not-sending-my-session-cookie
    服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

同源策略是浏览器最核心也最基本的安全功能首先:web是开放的世界, 需要互联链接. 你的网站, 可以使用别人的图片, img, 使用别人 script 做统计, 做广告联盟

假设没有同源, 互联网世界是什么样?链接跳转导致的问题. http://a.com , 放一个链接到 icbc.com, 然后 window.open来打开, 获取窗口句柄, 然后可以拥有对这个页面完全的控制权. 拦截表单,捕获数据,将账号密码上传到a.com.ajax请求, 要啥就有啥. 你登录jd.com; 然后打开a.com, 通过ajax 请求http://jd.com 的用户信息接口, 这时候因为访问的jd.com,所以浏览器自动带上了jd的cookie,然后获取到你的订单list ,昵称, 所有私密信息.所以,需要要同源策略

在同一个域内,客户端脚本可以任意读写同源内的资源,dom,cookie;但是不同的域,就不行.

收起阅读 »

深入浏览器cookie,安全与共享

Cookie

我们都知道,HTTP是无状态的,但我们的服务器有时需要有区别的对待不同的客服端,如何区分,其实就是一种状态的表现,Cookie应运而生,当然,本文主题并不是介绍Cookie的历史和作用。
cookie安全
最近和同事在写管理系统前端时,想到用测试机上的数据来测试本地代码,这样我们在本地也能非常友好的去复现测试机上的bug,而且代码还是非混淆,非压缩的。

之前其实内部是有相关的代理工具,基本原理是通过将测试机的的ip指向本地(在自己机器的host上配置),然后本地配置nginx server,将静态资源请求转向本地的dev server,接口请求转向测试机。但我们觉得,既然本地起来一个dev的server,我们直接给这个server配置转发,书写规则,让其转发到测试机上对应的地址即可。

理想很丰满,现实很骨感:

服务器上对应接口永远返回的是401(未授权),好像确实少了某些操作。

服务器如何识别客服端,我想有过服务端开发经验的小伙伴不会陌生,对,就是session,服务端的一种状态化解决方案,当然,session的实现需要客服端cookie的配合,因为session id会通过cookie的方式下发到客服端,后续的请求都会带上这个session id,然后服务端取出存储的用户状态。基于这一点,我们将Cookie分为会话Cookie和持久性Cookie。
Session cookies
会话性质的Cookie,不会指定Expires或者Max-Age,当浏览器被关闭的时候将会被删除。当我们看到浏览器Cookie面板中Cookie的Expires / Max-Age为1969-12-31T23:59:59.000Z,也就是计算机时间0的前一秒时(Chrome中),我们差不多就可以认为这是个session cookie了。
Permanent cookies
持久性Cookie,可通过Expires/Max-Age来设置Cookies的过期时间。
回到问题
那么回到我们上面的问题,直接访问本地开的dev server是肯定带不过去Cookie的,因为localhost.com或者127.0.0.1 和 test.somsite.com/contextName之间从domain方面来说是压根没有半毛线关系的。
那如果我们对local.test.somsite.com配置host,然后指向127.0.0.1呢?访问local.test.somsite.com/contextName,会发现有一部分cookie是带过去了的,唯独我们的session cookie没有被带上。

注:此处有朋友可能会问,为毛不直接将host设置为test.somsite.com,一了百了,其实不是不可以,只是如果和测试机设置一样的hostname的话,我们可能要不断在这两个host之间switch了。
为毛带不过去,谁定义的规则,在rfc6265中,我想我们能够找到答案。这里多说一句,在rfc6265之前,其实是还有一个rfc 2109的。然后,你懂得,在一些旧版本的浏览器,或者一些后端技术中,依然会去尊重RFC 2109的一些规则。博主也会在后面提到我所遇到的一些和这相关的。
host-only-flag in Cookie
在rfc6265的5.3节(Storage Model),定义了浏览器在接收到服务端的Set-Cookie之后的一个存储机制,其实浏览器针对服务端设置的每一个Cookie,都会存储如下字段: name, value, expiry-time, domain, path, creation-time, last-access-time, persistent-flag, host-only-flag, secure-only-flag, and http-only-flag.当然,这并不是要求服务端设置这么多个字段,而是有默认值的,这个缺省值,就是我们这儿要讨论的。我们定位到第六点:

 6.   If the domain-attribute is non-empty:  

       If the canonicalized request-host does not domain-match the  
       domain-attribute:  

          Ignore the cookie entirely and abort these steps.  

       Otherwise:  

          Set the cookie's host-only-flag to false.  

          Set the cookie's domain to the domain-attribute.  

    Otherwise:  

       Set the cookie's host-only-flag to true.  

       Set the cookie's domain to the canonicalized request-host.

dimain-attribute为空时,将host-only-flag标记为true。

而host-only-flag是干啥的呢?我们先不看它的定义,看浏览器时如何使用这个属性的,同样是rfc6265, 5.4节(The Cookie Header),我们可以看到User-Agent是如何去决定带Cookie的:

The user agent MUST use an algorithm equivalent to the following algorithm to compute the "cookie-string" from a cookie store and a request-uri: (也就是用户代理在发送请求时,在决定是否带上对应Cookie方面的一个决策算法,注意其语法: MUST)

1.  Let cookie-list be the set of cookies from the cookie store that  
       meets all of the following requirements:  

       *  Either:  

             The cookie's host-only-flag is true and the canonicalized  
             request-host is identical to the cookie's domain.  

          Or:  

             The cookie's host-only-flag is false and the canonicalized  
             request-host domain-matches the cookie's domain.  

       *  The request-uri's path path-matches the cookie's path.  

       *  If the cookie's secure-only-flag is true, then the request-  
          uri's scheme must denote a "secure" protocol (as defined by  
          the user agent).  

             NOTE: The notion of a "secure" protocol is not defined by  
             this document.  Typically, user agents consider a protocol  
             secure if the protocol makes use of transport-layer  
             security, such as SSL or TLS.  For example, most user  
             agents consider "https" to be a scheme that denotes a  
             secure protocol.  

       *  If the cookie's http-only-flag is true, then exclude the  
          cookie if the cookie-string is being generated for a "non-  
          HTTP" API (as defined by the user agent).

我们可以看到其中第一点的第一小点,当 host-only-flag 被设置为true是,请求的主机的域名就必须与cookie中设置的domain完全匹配,否则不会带上此Cookie。

回到我们的session-cookie, 我们看下这个JSESSIONID 的 Cookie是如何被设置的:


那我们上面的问题就很清晰了,没有设置cookie的domain属性,对于请求的cookie的携带使用强domain匹配规则,自然是像子域这种也不会被发送过去的,这也符合我们session id这样的高安全需求且独立操作的场景要求。

Cookie共享
说完安全谈共享(share),其实二者并不是独立的,而是共生。正是因为共享,我们才需要安全的规则去限制,Web所营造的一个共享的环境让人有机可趁,而随之而来的一些安全策略就是去确保我们的信息,个人隐私的安全。Cookie如此,Same-origin policy亦是如此。

那么说到Cookie的共享,官方并不是用源(origin)来进行描述,而是"third-party"这个词,第三方。而针对用户代理如何去对待第三方Cookie,rfc并未作出明确的规定。

User agents vary widely in their third-party cookie policies. This document grants user agents wide latitude to experiment with third-party cookie policies that balance the privacy and compatibility needs of their users. However, this document does not endorse any particular third-party cookie policy.

这里其实还需要补充一点的是,对于请求是否该携带Cookie,我们cookie的domain字段在set-cookie时就已经做了明确的限定了,当没有指定domain的时候,上面已经列出了相关的策略,那如果指定了domain呢(我们同样可以看到rfc6265的Domain Matching),有兴趣的读者可以点击前往看下,我这里简单介绍下细则:

一个字符串str如果满足匹配一个给定的domain的话,它至少应该满足下面条件中的一个:

强匹配,就是完全一样
或者同时满足下面的条件:
domain字段的值是这个str的后缀
str中最后一个不包含在domain值中的字符应该是个点(%x2E (".")),这个其实很好理解,domain: google.com and str: map.google.com => OK; domain: google.com and str: map.mgoogle.com => SAD
这个字符必须是个域名(主机名),而不能是IP地址。
读者需要注意的是,Domain match,也就是我们上面的这个匹配规则,只是浏览器在决定是否携带我们的Cookie项时诸多参考项中的一个,即必要不充分。浏览器还会参考诸如是否过期,以及Path-Match等项。

说完浏览器对于携带Cookie项的一个决策策略,其实整个流程(或者说是Cookie的生命周期)还有一部分没有涉及,就是一开始,我们设置Cookie的时候,此时,浏览器也会有一系列的约束。这在我们规范的5.3. Storage Model部分做了相关的说明,我们可以注意到其4,5,6点(第六点在本文的前面有部分提到):

4.   If the cookie-attribute-list contains an attribute with an  
    attribute-name of "Domain":  

       Let the domain-attribute be the attribute-value of the last  
       attribute in the cookie-attribute-list with an attribute-name  
       of "Domain".  

    Otherwise:  

       Let the domain-attribute be the empty string.  

5.   If the user agent is configured to reject "public suffixes" and  
    the domain-attribute is a public suffix:  

       If the domain-attribute is identical to the canonicalized  
       request-host:  

          Let the domain-attribute be the empty string.  
      Otherwise:  

          Ignore the cookie entirely and abort these steps.  

6.   If the domain-attribute is non-empty:  

       If the canonicalized request-host does not domain-match the  
       domain-attribute:  

          Ignore the cookie entirely and abort these steps.  

       Otherwise:  

          Set the cookie's host-only-flag to false.  

          Set the cookie's domain to the domain-attribute.  

    Otherwise:  

       Set the cookie's host-only-flag to true.  

       Set the cookie's domain to the canonicalized request-host.

总结来说,当浏览器决定是否存储Cookie项时,在Domain这方面的决策大致为:首先检验当前domain属性的设值是否命中了我们浏览器的公共前缀(public suffixes),如果命中且当前这个domain的值能够完全匹配当前请求的域名,将domain值重置为空,走我们前面提到的host-only-flag流程;如果命中但domain值不能匹配当前请求的域名,本Cookie项设置失败。前面的case还有一个前提是浏览器被配置为零容忍(不允许)public suffixes(is configured to reject "public suffixes")。如果上面步骤没凉的话,第六点当domain属性未被置空时,这里就会有一次的domain-match的检测,检测失败则设置失败呗。

Cookies use a separate definition of origins. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix. Firefox and Chrome use the Public Suffix List to determine if a domain is a public suffix. Internet Explorer uses its own internal method to determine if a domain is a public suffix. The browser will make a cookie available to the given domain including any sub-domains, no matter which protocol (HTTP/HTTPS) or port is used. When you set a cookie, you can limit its availability using the Domain, Path, Secure and Http-Only flags. When you read a cookie, you cannot see from where it was set. Even if you use only secure https connections, any cookie you see may have been set using an insecure connection.

同时,感兴趣的同学也可以浏览下紫云飞大大的这篇博文: SameSite Cookie,防止 CSRF 攻击
rfc2109 与 rfc6265
对,新旧版本,自然而然有时候服务端的一些Set-Cookie会做向前兼容的处理

Dot prefix in cookie domain
domain中前置的点(dot)意味着这个cookie对于子域也同样有效(The leading dot means that the cookie is valid for subdomains as well)。但这是rfc2109种的规则,在rfc6265中,我们看到了这句话:

The Domain attribute specifies those hosts to which the cookie will be sent. For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com. (Note that a leading %x2E ("."), if present, is ignored even though that character is not permitted, but a trailing %x2E ("."), if present, will cause the user agent to ignore the attribute.) If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.

rfc6265 会直接忽略前置的点,其实算是对老版本的一种兼容了。所以如果你在浏览器的Cookie面板中看到domain列的值有前置点,不要惊讶~

说在最后
其实在写这篇cookie安全相关的博文的时候,我脑袋了又冒出了同源策略这个词,加上我们这里的Cookie的安全策略,其实所谈到的都是关乎于源的共享的一个问题,那么其实cookie的话,还加上了一个源之间的干扰隔离(Set-Cookie domain字段限制)。谁决定源(resource)的访问权,当然是源的拥有者(服务端),这就不难理解,你想去hack同源策略,或者说CORS,自然也是需要服务端"答应"的,但这个策略实际由浏览器实施,作用于想去跨源访问资源的文档或脚本。
相关链接:https://lancelou.com/post/browser-cookie-deeping

https://www.jianshu.com/p/643427c17877

ajax跨域请求中的cookie问题
update 另一个问题
ajax在进行复杂请求如PUT,POST,DELETE等时,当请求为cross domain request是,会先发一个OPTIONS请求确认服务器的跨域支持情况,在发送原来的请求,所以对于服务器,需要对OPTIONS请求做一次xiang'yin

遇到的问题
对于前后端分离的应用,使用ajax跨域请求时,默认情况下是无法传输cookie的。具体的异常表现如下

客户端发送给服务器的请求中不包含cookie信息
服务器返回给客户端的响应中包含了Set Cookie 的信息,但是在浏览器的cookie中,没有记录词条cookie信息

解决方法
需要前后端都做一些小的改动

服务器端
以nodejs的后端为例,使用express框架,需要加上几行代码

app.all('*', function(req, res, next) {  
  res.header("Access-Control-Allow-Origin", config().allow_origin);  
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");  
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");  
  res.header("Access-Control-Allow-Credentials", "true");  
  res.header("X-Powered-By", ' 3.2.1')  
  res.header("Content-Type", "application/json;charset=utf-8");  
  next();  
});

注意这句话:
res.header("Access-Control-Allow-Credentials", "true");

这句话用来允许跨域访问时带上cookie信息,此外有一个问题,就是当我们"Access-Control-Allow-Origin"设置为的时候,上面这句话是无法使用的。所以不能够设置为,否则无法使用cookie。

浏览器端
以jquery的ajax请求为例

$.ajax({  
        url,  
        type: 'get',  
        dataType: 'json',  
        // 允许跨域  
        crossDomain: true,  
        // 下面这句话允许跨域的cookie访问  
        xhrFields: {  
          withCredentials: true  
        },  
        success: (res) => {  
          console.log(res);  
        }  
      });

总结
这个问题就是这样解决的,建议只允许自己前端网站的域名进行跨域访问,防止CSRF之类的攻击。

作者:cooody
链接:https://www.jianshu.com/p/643427c17877
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

继续阅读 »

我们都知道,HTTP是无状态的,但我们的服务器有时需要有区别的对待不同的客服端,如何区分,其实就是一种状态的表现,Cookie应运而生,当然,本文主题并不是介绍Cookie的历史和作用。
cookie安全
最近和同事在写管理系统前端时,想到用测试机上的数据来测试本地代码,这样我们在本地也能非常友好的去复现测试机上的bug,而且代码还是非混淆,非压缩的。

之前其实内部是有相关的代理工具,基本原理是通过将测试机的的ip指向本地(在自己机器的host上配置),然后本地配置nginx server,将静态资源请求转向本地的dev server,接口请求转向测试机。但我们觉得,既然本地起来一个dev的server,我们直接给这个server配置转发,书写规则,让其转发到测试机上对应的地址即可。

理想很丰满,现实很骨感:

服务器上对应接口永远返回的是401(未授权),好像确实少了某些操作。

服务器如何识别客服端,我想有过服务端开发经验的小伙伴不会陌生,对,就是session,服务端的一种状态化解决方案,当然,session的实现需要客服端cookie的配合,因为session id会通过cookie的方式下发到客服端,后续的请求都会带上这个session id,然后服务端取出存储的用户状态。基于这一点,我们将Cookie分为会话Cookie和持久性Cookie。
Session cookies
会话性质的Cookie,不会指定Expires或者Max-Age,当浏览器被关闭的时候将会被删除。当我们看到浏览器Cookie面板中Cookie的Expires / Max-Age为1969-12-31T23:59:59.000Z,也就是计算机时间0的前一秒时(Chrome中),我们差不多就可以认为这是个session cookie了。
Permanent cookies
持久性Cookie,可通过Expires/Max-Age来设置Cookies的过期时间。
回到问题
那么回到我们上面的问题,直接访问本地开的dev server是肯定带不过去Cookie的,因为localhost.com或者127.0.0.1 和 test.somsite.com/contextName之间从domain方面来说是压根没有半毛线关系的。
那如果我们对local.test.somsite.com配置host,然后指向127.0.0.1呢?访问local.test.somsite.com/contextName,会发现有一部分cookie是带过去了的,唯独我们的session cookie没有被带上。

注:此处有朋友可能会问,为毛不直接将host设置为test.somsite.com,一了百了,其实不是不可以,只是如果和测试机设置一样的hostname的话,我们可能要不断在这两个host之间switch了。
为毛带不过去,谁定义的规则,在rfc6265中,我想我们能够找到答案。这里多说一句,在rfc6265之前,其实是还有一个rfc 2109的。然后,你懂得,在一些旧版本的浏览器,或者一些后端技术中,依然会去尊重RFC 2109的一些规则。博主也会在后面提到我所遇到的一些和这相关的。
host-only-flag in Cookie
在rfc6265的5.3节(Storage Model),定义了浏览器在接收到服务端的Set-Cookie之后的一个存储机制,其实浏览器针对服务端设置的每一个Cookie,都会存储如下字段: name, value, expiry-time, domain, path, creation-time, last-access-time, persistent-flag, host-only-flag, secure-only-flag, and http-only-flag.当然,这并不是要求服务端设置这么多个字段,而是有默认值的,这个缺省值,就是我们这儿要讨论的。我们定位到第六点:

 6.   If the domain-attribute is non-empty:  

       If the canonicalized request-host does not domain-match the  
       domain-attribute:  

          Ignore the cookie entirely and abort these steps.  

       Otherwise:  

          Set the cookie's host-only-flag to false.  

          Set the cookie's domain to the domain-attribute.  

    Otherwise:  

       Set the cookie's host-only-flag to true.  

       Set the cookie's domain to the canonicalized request-host.

dimain-attribute为空时,将host-only-flag标记为true。

而host-only-flag是干啥的呢?我们先不看它的定义,看浏览器时如何使用这个属性的,同样是rfc6265, 5.4节(The Cookie Header),我们可以看到User-Agent是如何去决定带Cookie的:

The user agent MUST use an algorithm equivalent to the following algorithm to compute the "cookie-string" from a cookie store and a request-uri: (也就是用户代理在发送请求时,在决定是否带上对应Cookie方面的一个决策算法,注意其语法: MUST)

1.  Let cookie-list be the set of cookies from the cookie store that  
       meets all of the following requirements:  

       *  Either:  

             The cookie's host-only-flag is true and the canonicalized  
             request-host is identical to the cookie's domain.  

          Or:  

             The cookie's host-only-flag is false and the canonicalized  
             request-host domain-matches the cookie's domain.  

       *  The request-uri's path path-matches the cookie's path.  

       *  If the cookie's secure-only-flag is true, then the request-  
          uri's scheme must denote a "secure" protocol (as defined by  
          the user agent).  

             NOTE: The notion of a "secure" protocol is not defined by  
             this document.  Typically, user agents consider a protocol  
             secure if the protocol makes use of transport-layer  
             security, such as SSL or TLS.  For example, most user  
             agents consider "https" to be a scheme that denotes a  
             secure protocol.  

       *  If the cookie's http-only-flag is true, then exclude the  
          cookie if the cookie-string is being generated for a "non-  
          HTTP" API (as defined by the user agent).

我们可以看到其中第一点的第一小点,当 host-only-flag 被设置为true是,请求的主机的域名就必须与cookie中设置的domain完全匹配,否则不会带上此Cookie。

回到我们的session-cookie, 我们看下这个JSESSIONID 的 Cookie是如何被设置的:


那我们上面的问题就很清晰了,没有设置cookie的domain属性,对于请求的cookie的携带使用强domain匹配规则,自然是像子域这种也不会被发送过去的,这也符合我们session id这样的高安全需求且独立操作的场景要求。

Cookie共享
说完安全谈共享(share),其实二者并不是独立的,而是共生。正是因为共享,我们才需要安全的规则去限制,Web所营造的一个共享的环境让人有机可趁,而随之而来的一些安全策略就是去确保我们的信息,个人隐私的安全。Cookie如此,Same-origin policy亦是如此。

那么说到Cookie的共享,官方并不是用源(origin)来进行描述,而是"third-party"这个词,第三方。而针对用户代理如何去对待第三方Cookie,rfc并未作出明确的规定。

User agents vary widely in their third-party cookie policies. This document grants user agents wide latitude to experiment with third-party cookie policies that balance the privacy and compatibility needs of their users. However, this document does not endorse any particular third-party cookie policy.

这里其实还需要补充一点的是,对于请求是否该携带Cookie,我们cookie的domain字段在set-cookie时就已经做了明确的限定了,当没有指定domain的时候,上面已经列出了相关的策略,那如果指定了domain呢(我们同样可以看到rfc6265的Domain Matching),有兴趣的读者可以点击前往看下,我这里简单介绍下细则:

一个字符串str如果满足匹配一个给定的domain的话,它至少应该满足下面条件中的一个:

强匹配,就是完全一样
或者同时满足下面的条件:
domain字段的值是这个str的后缀
str中最后一个不包含在domain值中的字符应该是个点(%x2E (".")),这个其实很好理解,domain: google.com and str: map.google.com => OK; domain: google.com and str: map.mgoogle.com => SAD
这个字符必须是个域名(主机名),而不能是IP地址。
读者需要注意的是,Domain match,也就是我们上面的这个匹配规则,只是浏览器在决定是否携带我们的Cookie项时诸多参考项中的一个,即必要不充分。浏览器还会参考诸如是否过期,以及Path-Match等项。

说完浏览器对于携带Cookie项的一个决策策略,其实整个流程(或者说是Cookie的生命周期)还有一部分没有涉及,就是一开始,我们设置Cookie的时候,此时,浏览器也会有一系列的约束。这在我们规范的5.3. Storage Model部分做了相关的说明,我们可以注意到其4,5,6点(第六点在本文的前面有部分提到):

4.   If the cookie-attribute-list contains an attribute with an  
    attribute-name of "Domain":  

       Let the domain-attribute be the attribute-value of the last  
       attribute in the cookie-attribute-list with an attribute-name  
       of "Domain".  

    Otherwise:  

       Let the domain-attribute be the empty string.  

5.   If the user agent is configured to reject "public suffixes" and  
    the domain-attribute is a public suffix:  

       If the domain-attribute is identical to the canonicalized  
       request-host:  

          Let the domain-attribute be the empty string.  
      Otherwise:  

          Ignore the cookie entirely and abort these steps.  

6.   If the domain-attribute is non-empty:  

       If the canonicalized request-host does not domain-match the  
       domain-attribute:  

          Ignore the cookie entirely and abort these steps.  

       Otherwise:  

          Set the cookie's host-only-flag to false.  

          Set the cookie's domain to the domain-attribute.  

    Otherwise:  

       Set the cookie's host-only-flag to true.  

       Set the cookie's domain to the canonicalized request-host.

总结来说,当浏览器决定是否存储Cookie项时,在Domain这方面的决策大致为:首先检验当前domain属性的设值是否命中了我们浏览器的公共前缀(public suffixes),如果命中且当前这个domain的值能够完全匹配当前请求的域名,将domain值重置为空,走我们前面提到的host-only-flag流程;如果命中但domain值不能匹配当前请求的域名,本Cookie项设置失败。前面的case还有一个前提是浏览器被配置为零容忍(不允许)public suffixes(is configured to reject "public suffixes")。如果上面步骤没凉的话,第六点当domain属性未被置空时,这里就会有一次的domain-match的检测,检测失败则设置失败呗。

Cookies use a separate definition of origins. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix. Firefox and Chrome use the Public Suffix List to determine if a domain is a public suffix. Internet Explorer uses its own internal method to determine if a domain is a public suffix. The browser will make a cookie available to the given domain including any sub-domains, no matter which protocol (HTTP/HTTPS) or port is used. When you set a cookie, you can limit its availability using the Domain, Path, Secure and Http-Only flags. When you read a cookie, you cannot see from where it was set. Even if you use only secure https connections, any cookie you see may have been set using an insecure connection.

同时,感兴趣的同学也可以浏览下紫云飞大大的这篇博文: SameSite Cookie,防止 CSRF 攻击
rfc2109 与 rfc6265
对,新旧版本,自然而然有时候服务端的一些Set-Cookie会做向前兼容的处理

Dot prefix in cookie domain
domain中前置的点(dot)意味着这个cookie对于子域也同样有效(The leading dot means that the cookie is valid for subdomains as well)。但这是rfc2109种的规则,在rfc6265中,我们看到了这句话:

The Domain attribute specifies those hosts to which the cookie will be sent. For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com. (Note that a leading %x2E ("."), if present, is ignored even though that character is not permitted, but a trailing %x2E ("."), if present, will cause the user agent to ignore the attribute.) If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.

rfc6265 会直接忽略前置的点,其实算是对老版本的一种兼容了。所以如果你在浏览器的Cookie面板中看到domain列的值有前置点,不要惊讶~

说在最后
其实在写这篇cookie安全相关的博文的时候,我脑袋了又冒出了同源策略这个词,加上我们这里的Cookie的安全策略,其实所谈到的都是关乎于源的共享的一个问题,那么其实cookie的话,还加上了一个源之间的干扰隔离(Set-Cookie domain字段限制)。谁决定源(resource)的访问权,当然是源的拥有者(服务端),这就不难理解,你想去hack同源策略,或者说CORS,自然也是需要服务端"答应"的,但这个策略实际由浏览器实施,作用于想去跨源访问资源的文档或脚本。
相关链接:https://lancelou.com/post/browser-cookie-deeping

https://www.jianshu.com/p/643427c17877

ajax跨域请求中的cookie问题
update 另一个问题
ajax在进行复杂请求如PUT,POST,DELETE等时,当请求为cross domain request是,会先发一个OPTIONS请求确认服务器的跨域支持情况,在发送原来的请求,所以对于服务器,需要对OPTIONS请求做一次xiang'yin

遇到的问题
对于前后端分离的应用,使用ajax跨域请求时,默认情况下是无法传输cookie的。具体的异常表现如下

客户端发送给服务器的请求中不包含cookie信息
服务器返回给客户端的响应中包含了Set Cookie 的信息,但是在浏览器的cookie中,没有记录词条cookie信息

解决方法
需要前后端都做一些小的改动

服务器端
以nodejs的后端为例,使用express框架,需要加上几行代码

app.all('*', function(req, res, next) {  
  res.header("Access-Control-Allow-Origin", config().allow_origin);  
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");  
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");  
  res.header("Access-Control-Allow-Credentials", "true");  
  res.header("X-Powered-By", ' 3.2.1')  
  res.header("Content-Type", "application/json;charset=utf-8");  
  next();  
});

注意这句话:
res.header("Access-Control-Allow-Credentials", "true");

这句话用来允许跨域访问时带上cookie信息,此外有一个问题,就是当我们"Access-Control-Allow-Origin"设置为的时候,上面这句话是无法使用的。所以不能够设置为,否则无法使用cookie。

浏览器端
以jquery的ajax请求为例

$.ajax({  
        url,  
        type: 'get',  
        dataType: 'json',  
        // 允许跨域  
        crossDomain: true,  
        // 下面这句话允许跨域的cookie访问  
        xhrFields: {  
          withCredentials: true  
        },  
        success: (res) => {  
          console.log(res);  
        }  
      });

总结
这个问题就是这样解决的,建议只允许自己前端网站的域名进行跨域访问,防止CSRF之类的攻击。

作者:cooody
链接:https://www.jianshu.com/p/643427c17877
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收起阅读 »

安卓离线打包的一定要看,少走弯路!!!

离线打包

看官方的教程做离线打包尝试,因为官方默认的教程是用eclipse来开发安卓程序,就下载最新SDK按照教程一步步操作,结果各种错误,完全无法便于运行。官方离线打包教程连接:http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/38

因为认为hbuilder也是用eclipse来修改的,官方教程默认也是讲eclipse怎么离线,那么eclipse一定是最好。按照官方的来操作肯定是没问题的。

但是。。。。测试两天,遇到各种问题,搜索后,还是无法解决打包,打包到手机上运行,连影都没有,死活不行。真是万念俱灰啊!!!

最终搜索看到Android Studio打包的教程,要不咱试试? 说干就干,Android Studio一路安装下来都比较顺利,安装好后导入H5+ SDK的DEMO,提示需要下载和升级什么的,全部选下载更新。 http://ask.dcloud.net.cn/article/508

最后,在使用官方DEOM情况下,基本上没遇到什么问题,成功实现在真机上离线打包。当然DEMO离线打包成功了,开发的项目替换就很简单了。

最终个人理解分析:官方应该是用Android Studio来做离线打包的,但是教程还是老的写的用eclipse打包。不然不会出这么多问题过不了。

总结:官方的教程真的很坑啊,然后百度搜索了一下,才明白现在基本上都是用Android Studio来做开发,eclipse开发安卓很少了,而且ADT谷歌也不更新了,但是官方教程还是让用eclipse来离线打包。可能有人会说:这不都是常识问题吗?我想说,咋们以前没做过原生开发,才来学dcloud,完全按照教程来就对了,结果这里情况并不是这个样式。官方教程也不可靠啊! 看得论坛里面很多人说DCLOUD文档写得很垃圾,平时查资料确实也觉得不是还好理解,但是慢慢的搜索还是能明白。不好理解是一会事,但是别挖坑是吧。我承认DCLOUD技术是不错,但是学DCLOUD98%的都是不懂原生的小白,所以能不能把文档这些做好一点,用点心。最终,还是希望DCLUD越来越好,才发的这篇算闹骚了。

继续阅读 »

看官方的教程做离线打包尝试,因为官方默认的教程是用eclipse来开发安卓程序,就下载最新SDK按照教程一步步操作,结果各种错误,完全无法便于运行。官方离线打包教程连接:http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/38

因为认为hbuilder也是用eclipse来修改的,官方教程默认也是讲eclipse怎么离线,那么eclipse一定是最好。按照官方的来操作肯定是没问题的。

但是。。。。测试两天,遇到各种问题,搜索后,还是无法解决打包,打包到手机上运行,连影都没有,死活不行。真是万念俱灰啊!!!

最终搜索看到Android Studio打包的教程,要不咱试试? 说干就干,Android Studio一路安装下来都比较顺利,安装好后导入H5+ SDK的DEMO,提示需要下载和升级什么的,全部选下载更新。 http://ask.dcloud.net.cn/article/508

最后,在使用官方DEOM情况下,基本上没遇到什么问题,成功实现在真机上离线打包。当然DEMO离线打包成功了,开发的项目替换就很简单了。

最终个人理解分析:官方应该是用Android Studio来做离线打包的,但是教程还是老的写的用eclipse打包。不然不会出这么多问题过不了。

总结:官方的教程真的很坑啊,然后百度搜索了一下,才明白现在基本上都是用Android Studio来做开发,eclipse开发安卓很少了,而且ADT谷歌也不更新了,但是官方教程还是让用eclipse来离线打包。可能有人会说:这不都是常识问题吗?我想说,咋们以前没做过原生开发,才来学dcloud,完全按照教程来就对了,结果这里情况并不是这个样式。官方教程也不可靠啊! 看得论坛里面很多人说DCLOUD文档写得很垃圾,平时查资料确实也觉得不是还好理解,但是慢慢的搜索还是能明白。不好理解是一会事,但是别挖坑是吧。我承认DCLOUD技术是不错,但是学DCLOUD98%的都是不懂原生的小白,所以能不能把文档这些做好一点,用点心。最终,还是希望DCLUD越来越好,才发的这篇算闹骚了。

收起阅读 »

HBuilder苹果APP推送通知之ios推送证书申请和配置使用

消息推送 iOS证书

很多人初次接触推送通知,不知道怎么去申请ios推送证书和配置推送。

很多人犯的错误就是用推送证书p12去打包ipa,推送不是用来打包的,下面详细介绍ios推证书的申请和配置使用。

ios推送证书分为测试调试用的iOS推送证书(开发环境)和上架到App Store的ios 推送证书!(生产环境)

APP要推送通知首先要在创建APPID时勾选推送服务。

推送证书是配置上传到推送平台的,如极光推送、个推、小米推送等,不是用来打包ipa的,下面会有介绍。

一、创建唯一标示符App IDs

首先打开开发者中心https://developer.apple.com/account,进入证书页面。

如果之前创建过appid,进去修改添加下推送服务就行了,不用重新创建。

1.1点击证书、ID及配件文件,进入设置。

1.2选择App IDs –>点击+创建一个新的App ID

其中有两项需要你自己填:

第一项Name,用来描述你的App ID,这个随便填,没有什么限制,最好是项目名称,这样方便自己辨识(不允许中文)

第二项Bundle ID (App ID Suffix),这是你App ID的后缀,需要仔细填写。用来标示我们的 app,使它有一个固定的身份,和你的程序直接相关。填写 Explicit App ID 的格式为:com.company.appName(要有两个点.)照着格式写,写个方便记的,后面很多地方要用到。

第三项配置服务权限,默认会选择2项,不能修改,其它常用的苹果支付,APP推送通知,这里要推送通知就勾选上,然后点击Continue确认,下一步。

Register后点击Done完成App ID的创建。

推送通知那项服务现在还是黄色的,因为还没创建ios推送证书,等下用Appuploader创建了ios推送证书,就会变成绿色,说明生效了。

二、iOS开发推送证书、开发真机调试用(开发环境、配合开发证书使用,用开发证书打包就能用开发环境ios推送证书测试推送通知iOS开发证书申请教程)

Appuploader可以实现是Windows电脑申请ios证书和上架APP。很方便的辅助ios上架工具!

Appuploader安装教程

1、打开Appuploader,用苹果开发者账号登录。

2、选择证书选项

3、点击+ADD\选择Apple Push Notification service SSL(Sandbox) —iOS开发环境推送证书

输入证书名称(随意)、邮箱(随意)、密码,选择你的APP对于的应用id,点击ok创建。

4、下载保存好.p12 iOS证书文件

推送证书是没描述文件的,只有一个p12,不用申请描述文件。

三、iOS发布推送证书、上架App Store用(生产环境、配合发布证书使用,开发环境推送证书测试好了推送,用发布证书上架成功了,就到推送后台切换为生成环境推送证书)

1、打开Appuploader,用苹果开发者账号登录。

2、选择证书选项

3、点击+ADD\选择Push Notification service SLL(Sandbox & Producyion)—iOS生产环境推送证书

输入证书名称(随意)、邮箱(随意)、密码,选择你的APP对于的appids,点击ok创建。

3、下载保存好.p12 iOS证书文件

四、配置ios推送证书p12

1、注册个推平台登录

http://www.getui.com/

点击左侧个推-消息推送,进入页面再点击右侧上方的登记应用

2、配置APP的基本信息

APP名称:你的APP名字

应用平台:安卓和苹果,ios开发环境(测试用的,配合开发证书使用)ios生产环境(上架用的,配合上架App Store使用)

如果你现在是测试选择ios开发环境,上传ios开发环境推送证书p12,输入证书密码确定。

应用标识:就是appid、应用id

3、配置好确定会生成推送接口参数,等下要配置到开发工具打包。

4、打开manifest.json配置文件,选择模块权限配置,选择消息推送模块。

5、然后再选择SDK配置,把刚才在个推生成的几个接口参数一一对应填上去。

6、用对应的开发证书打包APP安装到手机就能测试推送了

描述:随便写

消息内容:{title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"} 一定要用这个格式文字可以改

title:推送通知标题

body:推送内容

其他选项默认就行,

7、然后点击发送预览,再点击确定,推送通知就发送出去了,然后看手机系统栏有没有收到通知。接受到就说明测试成功了,推送正常使用,如没收到检查各项配置是否正确。

继续阅读 »

很多人初次接触推送通知,不知道怎么去申请ios推送证书和配置推送。

很多人犯的错误就是用推送证书p12去打包ipa,推送不是用来打包的,下面详细介绍ios推证书的申请和配置使用。

ios推送证书分为测试调试用的iOS推送证书(开发环境)和上架到App Store的ios 推送证书!(生产环境)

APP要推送通知首先要在创建APPID时勾选推送服务。

推送证书是配置上传到推送平台的,如极光推送、个推、小米推送等,不是用来打包ipa的,下面会有介绍。

一、创建唯一标示符App IDs

首先打开开发者中心https://developer.apple.com/account,进入证书页面。

如果之前创建过appid,进去修改添加下推送服务就行了,不用重新创建。

1.1点击证书、ID及配件文件,进入设置。

1.2选择App IDs –>点击+创建一个新的App ID

其中有两项需要你自己填:

第一项Name,用来描述你的App ID,这个随便填,没有什么限制,最好是项目名称,这样方便自己辨识(不允许中文)

第二项Bundle ID (App ID Suffix),这是你App ID的后缀,需要仔细填写。用来标示我们的 app,使它有一个固定的身份,和你的程序直接相关。填写 Explicit App ID 的格式为:com.company.appName(要有两个点.)照着格式写,写个方便记的,后面很多地方要用到。

第三项配置服务权限,默认会选择2项,不能修改,其它常用的苹果支付,APP推送通知,这里要推送通知就勾选上,然后点击Continue确认,下一步。

Register后点击Done完成App ID的创建。

推送通知那项服务现在还是黄色的,因为还没创建ios推送证书,等下用Appuploader创建了ios推送证书,就会变成绿色,说明生效了。

二、iOS开发推送证书、开发真机调试用(开发环境、配合开发证书使用,用开发证书打包就能用开发环境ios推送证书测试推送通知iOS开发证书申请教程)

Appuploader可以实现是Windows电脑申请ios证书和上架APP。很方便的辅助ios上架工具!

Appuploader安装教程

1、打开Appuploader,用苹果开发者账号登录。

2、选择证书选项

3、点击+ADD\选择Apple Push Notification service SSL(Sandbox) —iOS开发环境推送证书

输入证书名称(随意)、邮箱(随意)、密码,选择你的APP对于的应用id,点击ok创建。

4、下载保存好.p12 iOS证书文件

推送证书是没描述文件的,只有一个p12,不用申请描述文件。

三、iOS发布推送证书、上架App Store用(生产环境、配合发布证书使用,开发环境推送证书测试好了推送,用发布证书上架成功了,就到推送后台切换为生成环境推送证书)

1、打开Appuploader,用苹果开发者账号登录。

2、选择证书选项

3、点击+ADD\选择Push Notification service SLL(Sandbox & Producyion)—iOS生产环境推送证书

输入证书名称(随意)、邮箱(随意)、密码,选择你的APP对于的appids,点击ok创建。

3、下载保存好.p12 iOS证书文件

四、配置ios推送证书p12

1、注册个推平台登录

http://www.getui.com/

点击左侧个推-消息推送,进入页面再点击右侧上方的登记应用

2、配置APP的基本信息

APP名称:你的APP名字

应用平台:安卓和苹果,ios开发环境(测试用的,配合开发证书使用)ios生产环境(上架用的,配合上架App Store使用)

如果你现在是测试选择ios开发环境,上传ios开发环境推送证书p12,输入证书密码确定。

应用标识:就是appid、应用id

3、配置好确定会生成推送接口参数,等下要配置到开发工具打包。

4、打开manifest.json配置文件,选择模块权限配置,选择消息推送模块。

5、然后再选择SDK配置,把刚才在个推生成的几个接口参数一一对应填上去。

6、用对应的开发证书打包APP安装到手机就能测试推送了

描述:随便写

消息内容:{title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"} 一定要用这个格式文字可以改

title:推送通知标题

body:推送内容

其他选项默认就行,

7、然后点击发送预览,再点击确定,推送通知就发送出去了,然后看手机系统栏有没有收到通知。接受到就说明测试成功了,推送正常使用,如没收到检查各项配置是否正确。

收起阅读 »

mongoDB数据库基础操作命令分享

本地数据库

对于创建数据库可以说是大多数后端程序员都会的,但是其中的一些基本的操作命令很多人还是需要查看的,那么下面专业的app开发报价燚轩科技就来为大家分享一下mongoDB数据库基础操作命令:
创建数据库
> use runoob --->创建数据库runoob
> db ---> 查看当前数据库
> show dbs --> 查看所有数据库, 这时是看不见runoob这个数据库的,我们必须插入一点数据才能够给看见
admin (empty)
local 0.078GB
> db.runoob.insert({"name":"菜鸟教程"})
WriteResult({ "nInserted" : 1 }) ---> 成功
> show dbs --> 这时就能看见runoob这个数据库了
admin (empty)
local 0.078GB
runoob 0.078GB

删除数据库
> use runoob --> 切换到要删除的数据库
switched to db runoob
> db.dropDatabase() --> 删除数据库
{ "dropped" : "runoob", "ok" : 1 }

创建集合
> db.createCollection('runoob') -->创建集合runoob
{ "ok" : 1 }
> show collections --> 查看所有集合
runoob
system.indexes

创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。
> db.createCollection('runoob1', {capped:true,autoIndexID:true,size:6142800,max:10000})
{ "ok" : 1 }

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。
> db.runoob2.insert({'name':'弱鸡'})
WriteResult({ "nInserted" : 1 })
> show collections
runoob
runoob1
runoob2
system.indexes

> db.runoob2.drop() --->删除runoob2集合
true

向runoob2集合中插入文档, 他会自动生成id列
> db.runoob1.insert({'name':'lucy','age':18,'sex':'woman'})
WriteResult({ "nInserted" : 1 })

查看runoob2集合中的文档
> db.runoob1.find()
{ "_id" : ObjectId("5b0d4c0f81322a46cfc77208"), "name" : "lucy", "age" : 18, "sex" : "woman" }

更新runoob2中文档,将名字lucy该为tom
> db.runoob1.update({'name':'lucy'},{$set:{'name':'tom'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.runoob.insert({'xx':12})
WriteResult({ "nInserted" : 1 })
> db.runoob.remove({'xx':12})
WriteResult({ "nRemoved" : 1 })
如果大家想要了解更多的话,可以留言咨询我们。

继续阅读 »

对于创建数据库可以说是大多数后端程序员都会的,但是其中的一些基本的操作命令很多人还是需要查看的,那么下面专业的app开发报价燚轩科技就来为大家分享一下mongoDB数据库基础操作命令:
创建数据库
> use runoob --->创建数据库runoob
> db ---> 查看当前数据库
> show dbs --> 查看所有数据库, 这时是看不见runoob这个数据库的,我们必须插入一点数据才能够给看见
admin (empty)
local 0.078GB
> db.runoob.insert({"name":"菜鸟教程"})
WriteResult({ "nInserted" : 1 }) ---> 成功
> show dbs --> 这时就能看见runoob这个数据库了
admin (empty)
local 0.078GB
runoob 0.078GB

删除数据库
> use runoob --> 切换到要删除的数据库
switched to db runoob
> db.dropDatabase() --> 删除数据库
{ "dropped" : "runoob", "ok" : 1 }

创建集合
> db.createCollection('runoob') -->创建集合runoob
{ "ok" : 1 }
> show collections --> 查看所有集合
runoob
system.indexes

创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。
> db.createCollection('runoob1', {capped:true,autoIndexID:true,size:6142800,max:10000})
{ "ok" : 1 }

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。
> db.runoob2.insert({'name':'弱鸡'})
WriteResult({ "nInserted" : 1 })
> show collections
runoob
runoob1
runoob2
system.indexes

> db.runoob2.drop() --->删除runoob2集合
true

向runoob2集合中插入文档, 他会自动生成id列
> db.runoob1.insert({'name':'lucy','age':18,'sex':'woman'})
WriteResult({ "nInserted" : 1 })

查看runoob2集合中的文档
> db.runoob1.find()
{ "_id" : ObjectId("5b0d4c0f81322a46cfc77208"), "name" : "lucy", "age" : 18, "sex" : "woman" }

更新runoob2中文档,将名字lucy该为tom
> db.runoob1.update({'name':'lucy'},{$set:{'name':'tom'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.runoob.insert({'xx':12})
WriteResult({ "nInserted" : 1 })
> db.runoob.remove({'xx':12})
WriteResult({ "nRemoved" : 1 })
如果大家想要了解更多的话,可以留言咨询我们。

收起阅读 »