如何获取图片的主色、辅色以及颜色统计

最近遇到一个不太常见的需求,希望我一个页面图片的背景色,能够随着图片的变化而变化,而且看上去要和谐。和谐这个词就比较抽象了,思索再三,也许使用图片的主色调,就会使得整个页面比较和谐了。而前端的canvas正好可以满足我们的需求。

获取图片数据

对于位图而言,图片是由一个个像素点组成的,而像素数据则由红、绿、蓝和alpha分量的位数四个元素组成。通过cavas的getImageData()方法,恰好可以获取图片的像素数据,具体数据格式为{data: [r, g, b, a, r, g, b, a…]}。

let imgObj = document.getElementById('yourId');

// 创建画布
let canvas = document.createElement('canvas');
canvas.setAttribute('width', imgObj.width);
canvas.setAttribute('height', imgObj.height);
let context = canvas.getContext('2d');
// 将图片画在画布上
context.drawImage(imgObj, 0, 0);
// 获取像素数据
let imgData = context.getImageData(0, 0, imgObj.width, imgObj.height);
let pixelData = imgData.data;

对图片数据进行处理

此时获取到了像素数据,因像素数据的特性,可以对这个数组进行分组,rgba四个为一组。而且在分组的同时,可以考虑对重复出现的颜色进行计数了。我们可以构建如下数据格式来完成分组与计数的需求。

// '像素数据': 数量
const COLORLIST = {
    'rgba1': 10,
    'rgba2': 22,
    ...
}

主要代码如下

let colorList = {};
let rgba = [];
let rgbaSt = '';

// 分组循环
for (let i = 0; i < pixelData.length; i += 4) {
    rgba[0] = pixelData[i];
    rgba[1] = pixelData[i + 1];
    rgba[2] = pixelData[i + 2];
    // 如果主色只要r,g,b不要a,请注释掉这句
    rgba[3] = pixelData[i + 3];

    // 避免undefined数据和alpha为0的数据
    if (rgba.indexOf(undefined) !== -1 || pixelData[i + 3] === 0) {
        continue;
    }
    // 转为字符串
    rgbaSt = rgba.join(',');

    if (rgbaSt in colorList) {
        ++colorList[rgbaSt];
    } else {
        colorList[rgbaSt] = 1;
    }
}

对颜色列表进行排序处理

至此已经获得了颜色的统计数据,我们只需要进行一下排序,就大功告成了!

let arr = [];

for (let prop in colorList) {
    arr.push({
        // 如果只获取rgb,则为`rgb(${prop})`
        color: `rgba(${prop})`,
        count: colorList[prop]
    });
}
// 数组排序
arr.sort((a, b) {
        return b.count - a.count;
});

好了,大功告成,arr就是我们排好顺序的颜色列表啦,你可以随便取里面你想取的颜色了。arr[0]和arr[1]肯定是我们需要的主辅色了。

注意:要考虑cavans处理的图片的跨域问题。
1.添加属性crossOrigin=”anonymous”。
2.转为base64

关于vue源码中发布订阅模式的探索

数据响应

开始

首先,我找到了实例instance文件夹下的vue.js。

// src/instance/vue.js
function Vue (options) {
  //初始化函数 跳转至./internal/init.js
  this._init(options)
}

显然vue的构造函数用一个this._init就解决了。接下来找到./internal/init.js,前面进行了一系列的参数初始化,之后有以下代码

// src/instance/internal/init.js
...
    // set ref
    this._updateRef()

    // initialize data as empty object.
    // it will be filled up in _initData().
    this._data = {}

    // call init hook
    this._callHook('init')

    // initialize data observation and scope inheritance.
    //初始化观察者模式数据 跳转至./state.js
    this._initState()

    // setup event system and option events.
    this._initEvents()

    // call created hook
    this._callHook('created')

    // if `el` option is passed, start compilation.
    if (options.el) {
      this.$mount(options.el)
    }
...

我在this._initState()这个函数中找到了数据响应初始化的相关内容。跳转至./state.js

// src/instance/internal/state.js

  /**
   * Initialize the data.
   */

  Vue.prototype._initData = function () {
    // this.$options.data: 实例中的data() {return ...};
    var dataFn = this.$options.data
    var data = this._data = dataFn ? dataFn() : {}
    // isPlainObject: 来自../../util/lang;
    //判断是否为对象
    if (!isPlainObject(data)) {
      data = {}
      //这个就是data声明出错的警告了
      process.env.NODE_ENV !== 'production' && warn(
        'data functions should return an object.',
        this
      )
    }
    var props = this._props
    // proxy data on instance
    var keys = Object.keys(data)
    var i, key
    i = keys.length
    while (i--) {
      key = keys[i]
      // there are two scenarios where we can proxy a data key:
      // 1. it's not already defined as a prop
      // 2. it's provided via a instantiation option AND there are no
      //    template prop present
      //1.将data属性直接代理到vm上去,这样就可以直接访问属性了。
      //2.比如访问实例化vm对象data下的a属性,直接vm.a即可。
      if (!props || !hasOwn(props, key)) {
        //若不是props里的属性,则直接代理。
        //_proxy方法源码在下面,作用是vm直接访问_data里面的数据
        this._proxy(key)
      } else if (process.env.NODE_ENV !== 'production') {
        //发出警告。
        warn(
          'Data field "' + key + '" is already defined ' +
          'as a prop. To provide default value for a prop, use the "default" ' +
          'prop option; if you want to pass prop values to an instantiation ' +
          'call, use the "propsData" option.',
          this
        )
      }
    }
    // observe data
    //生成观察者模式的对象,跳转至../../observer/index
    observe(data, this)
  }

这个函数解决了我平时使用vue的几个疑问:

(1).无论是手抖还是什么原因。经常出现的几个警告和报错原来来自这里。
(2).为什么我明明写在vue data属性下的数据,可以直接通过vm.xxx来访问了。

第二个问题,主要依靠_proxy这个函数,将_data里的数据直接挂在到了vm下面。


/** * Proxy a property, so that * vm.prop === vm._data.prop * * @param {String} key */ Vue.prototype._proxy = function (key) { if (!isReserved(key)) { // need to store ref to self here // because these getter/setters might // be called by child scopes via // prototype inheritance. var self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) } }

然后_initData最后一句,observe(data, this)进入正菜

observe

// src/observer/index.js

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 *
 * @param {*} value
 * @param {Vue} [vm]
 * @return {Observer|undefined}
 * @static
 */

export function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    //判断是否为对象
    return
  }
  var ob
  if (
    //__ob__这个属性若存在,证明已经observe过。直接赋值ob
    hasOwn(value, '__ob__') &&
    value.__ob__ instanceof Observer
  ) {
    ob = value.__ob__
  } else if (
    //shouldConvert:开关,暂不知道其作用
    //Object.isExtensible:判断该对象是否可以添加新属性
    shouldConvert &&
    (isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    //构造函数Observer,看返回值。
    ob = new Observer(value)
  }
  if (ob && vm) {
    //vm: 当前实例
    //addVm:将vm添加到ob的vms属性中
    ob.addVm(vm)
  }
  //最后输出的ob应是一个可以get和set的对象
  return ob
}

很明显,observe这个函数,就是用来生成一个经过处理的,可以get和set的对象,所以有如下问题:

(1)get和set是什么?
(2)这么做的思路是什么?

问题1:js有个很神奇的方法,Object.defineProperty,用来设置对象的一些属性。其中就有get和set,照英文表面意思,一个‘获得’,一个’设置‘。
问题2:通过get,用来收集依赖于此条数据的订阅者。何为订阅者,比如此条数据为{ss: 1},某个dom展示{{ss}}即为它的订阅者,而通过set,当数据发生改变的时候,通知对应的订阅者。
如此就可以实现vue传说中的订阅功能。它的具体实现在Dep(dependence简写)这个构造函数中(src/observer/dep.js)

Observer

在observe这个函数中有一个构造函数Observer,是用来生成这种对象的核心函数。

// src/observer/index.js

/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 *
 * @param {Array|Object} value
 * @constructor
 */

export function Observer (value) {
  //将传参value挂载到this上
  this.value = value
  //将依赖收集对象实例化,挂载到this上
  //构造函数Dep跳转至./dep
  this.dep = new Dep()
  //Object.definePropert封装../util/lang
  //将Observer实例化后的对象挂载到value.__ob__下
  def(value, '__ob__', this)
  if (isArray(value)) {
    //由于数组的特殊性,若用Object.defineProperty,存在以下问题:
    //1.将数字作为属性存在性能问题
    //2.无法解决push pop等数组方法
    //解决方案:
    //重写数组方法,由于es5继承数组方法是返回新数组,所以得用特别的继承方式
    //(1):利用大部分高级浏览器的__proto__属性,指向Array.prototype里的方法
    //(2):遍历,将方法def到数组实例上
    //hasProto:from ../util/env
    //protoAugment:方案(1)
    //copyAugment:方案(2)
    //arrayMethods:重写方法 from ./array
    var augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    //walk:遍历
    this.walk(value)
  }
}

从代码逻辑上看,这个函数解决了数组这个特殊的Object所产生的问题。数组使用Object.defineProperty存在以下问题:

1.将数字作为属性存在性能问题
2.无法准确响应push pop等数组方法

重写数组方法,由于es5继承数组方法是返回新数组,所以得用特别的继承方式

(1):利用大部分高级浏览器的proto属性,指向Array.prototype里的方法
(2):遍历,将方法def到数组实例上(def是作者封装的一个方法,作用是把方法挂载到相应对象下)

所以,原来我们用的push和pop等,都是变异过的。具体改写在src/observer/array.js。
接下来,无论数组还是对象,最后都要对其设置getter和setter,其核心函数为这个:

/**
 * Define a reactive property on an Object.
 *
 * @param {Object} obj
 * @param {String} key
 * @param {*} val
 */

export function defineReactive (obj, key, val) {
  //dep:阅读完本段代码跳至./dep,是一个收集watcher的构造函数。很多资料中称之为依赖收集
  var dep = new Dep()
  //Object.getOwnPropertyDescriptor:获取属性描述符,如writable等
  var property = Object.getOwnPropertyDescriptor(obj, key)
  //configurable:false不可更改与扩展
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get
  var setter = property && property.set

  var childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      //判断value值是否有getter
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        //当watcher发生变化时,触发所有依赖的getter
        //然后存储依赖于此的订阅者dep.depend()相当于dep.addSub(Dep.target)
        //这样watcher成功的分发到了相关依赖中。
        //可以理解为解决getter不能传参但需要传参的情况
        dep.depend()
        if (childOb) {
          //Observer构造函数中已经声明this.dep = new Dep();
          childOb.dep.depend()
        }
        if (isArray(value)) {
          for (var e, i = 0, l = value.length; i < l; i++) {
            e = value[i]
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val
      if (newVal === value) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      //通知更新
      dep.notify()
    }
  })
}

其中的getter,主要依靠Dep这个依赖收集器,来收集依赖于此条字段的那些订阅者。并且存储到dep.subs这个数组中。个人认为如果getter能传参,就不需要如此大费周章了,可惜现实是不能。但这也是此段代码的神奇之处,真的很神奇!
然后setter,来判断数据是否发生了改变,并且通知更新,dep.notify()。
所以,所有的问题都指向了Dep这个构造函数。

Dep(src/observer/dep.js)

Dep这个类其实很简单,基本上就是实现watcher实例的增删改查。然后将其存储在subs这个数组中。其中Dep.target指向的是当前watcher,在afterwatch后,会重置为null。
至于watcher,是我接下来学习的对象。

 

rn踩坑纪

1.

Cannot find entry file index.ios.js in any of the roots:["/Users/xxx/Desktop/AwesomeProject"]

该问题一般都是因为当前已经开启某个项目的react-native server,所以只要关闭然后重新运行新项目就可以了。


2.

** BUILD FAILED **



The following commands produced analyzer issues:
    Analyze /Users/scdzs5/wh/project/hpApp/node_modules/react-native/ReactCommon/yoga/yoga/YGNodeList.c
    Analyze /Users/scdzs5/wh/project/hpApp/node_modules/react-native/ReactCommon/yoga/yoga/Yoga.c
(2 commands with analyzer issues)

The following build commands failed:
    PhaseScriptExecution Install\ Third\ Party /Users/scdzs5/wh/project/hpApp/ios/build/Build/Intermediates/React.build/Debug-iphonesimulator/double-conversion.build/Script-190EE32F1E6A43DE00A8543A.sh
(1 failure)

一般是版本升级不兼容导致的,reactive-native upgrade手动升级

安装ruby

# Ubuntu系统下安装ruby/rails必要的库和编译环境
sudo apt-get update
sudo apt-get install -y build-essential openssl curl libcurl3-dev libreadline6 libreadline6-dev git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libxml2-dev libxslt-dev autoconf automake libtool imagemagick libmagickwand-dev libpcre3-dev libsqlite3-dev
# rbenv环境安装
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc
type rbenv
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
# ruby环境安装,首先列出可安装的版本,然后选择后进行下载编译
rbenv install -l
rbenv install 2.2.3
# 设置当前使用的ruby版本并将gem的源改为淘宝镜像
rbenv global 2.2.3
rbenv rehash
gem sources --remove https://rubygems.org/
gem sources -a http://ruby.taobao.org/
# 安装rails
gem install bundler rails --no-rdoc --no-ri
# 检查安装后的软件版本
ruby -v
gem -v
rake -V
rails -v

【转载】关于启用 HTTPS 的一些经验分享

HTTPS 网页中加载的 HTTP 资源被称之为 Mixed Content(混合内容),不同浏览器对 Mixed Content 有不一样的处理规则。

作者:来源:Jerry Qu的小站|2015-12-04 10:04

随着国内网络环境的持续恶化,各种篡改和劫持层出不穷,越来越多的网站选择了全站 HTTPS。HTTPS 通过 TLS 层和证书机制提供了内容加密、身份认证和数据完整性三大功能,可以有效防止数据被查看或篡改,以及防止中间人冒充。本文分享一些启用 HTTPS 过程中的经验,重点是如何与一些新出的安全规范配合使用。至于 HTTPS 的部署及优化,之前写过很多,本文不重复了。

理解 Mixed Content

HTTPS 网页中加载的 HTTP 资源被称之为 Mixed Content(混合内容),不同浏览器对 Mixed Content 有不一样的处理规则。

早期的 IE

早期的 IE 在发现 Mixed Content 请求时,会弹出「是否只查看安全传送的网页内容?」这样一个模态对话框,一旦用户选择「是」,所有 Mixed Content 资源都不会加载;选择「否」,所有资源都加载。

比较新的 IE

比较新的 IE 将模态对话框改为页面底部的提示条,没有之前那么干扰用户。而且默认会加载图片类 Mixed Content,其它如 JavaScript、CSS 等资源还是会根据用户选择来决定是否加载。

现代浏览器

现代浏览器(Chrome、Firefox、Safari、Microsoft Edge),基本上都遵守了 W3C 的 Mixed Content 规范,将 Mixed Content 分为Optionally-blockable 和 Blockable 两类:

Optionally-blockable 类 Mixed Content 包含那些危险较小,即使被中间人篡改也无大碍的资源。现代浏览器默认会加载这类资源,同时会在控制台打印警告信息。这类资源包括:

通过 标签加载的图片(包括 SVG 图片);

  • 通过 <img> 标签加载的图片(包括 SVG 图片);
  • 通过 <video> / <audio> 和 <source> 标签加载的视频或音频;
  • 预读的(Prefetched)资源;

预读的(Prefetched)资源;

除此之外所有的 Mixed Content 都是 Blockable,浏览器必须禁止加载这类资源。所以现代浏览器中,对于 HTTPS 页面中的 JavaScript、CSS 等 HTTP 资源,一律不加载,直接在控制台打印错误信息。

移动浏览器

前面所说都是桌面浏览器的行为,移动端情况比较复杂,当前大部分移动浏览器默认都允许加载 Mixed Content。也就是说,对于移动浏览器来说,HTTPS 中的 HTTP 资源,无论是图片还是 JavaScript、CSS,默认都会加载。

一般选择了全站 HTTPS,就要避免出现 Mixed Content,页面所有资源请求都走 HTTPS 协议才能保证所有平台所有浏览器下都没有问题。

合理使用 CSP

CSP,全称是 Content Security Policy,它有非常多的指令,用来实现各种各样与页面内容安全相关的功能。

block-all-mixed-content

前面说过,对于 HTTPS 中的图片等 Optionally-blockable 类 HTTP 资源,现代浏览器默认会加载。图片类资源被劫持,通常不会有太大的问题,但也有一些风险,例如很多网页按钮是用图片实现的,中间人把这些图片改掉,也会干扰用户使用。

通过 CSP 的 block-all-mixed-content 指令,可以让页面进入对混合内容的严格检测(Strict Mixed Content Checking)模式。在这种模式下,所有非 HTTPS 资源都不允许加载。跟其它所有 CSP 规则一样,可以通过以下两种方式启用这个指令:

HTTP 响应头方式:

  1. Content-Security-Policy: block-all-mixed-content

<meta>标签方式:

  1. <meta http-equiv=“Content-Security-Policy” content=“block-all-mixed-content”>

upgrade-insecure-requests

历史悠久的大站在往 HTTPS 迁移的过程中,工作量往往非常巨大,尤其是将所有资源都替换为 HTTPS 这一步,很容易产生疏漏。即使所有代码都确认没有问题,很可能某些从数据库读取的字段中还存在 HTTP 链接。

而通过 upgrade-insecure-requests 这个 CSP 指令,可以让浏览器帮忙做这个转换。启用这个策略后,有两个变化:

页面所有 HTTP 资源,会被替换为 HTTPS 地址再发起请求;

页面所有站内链接,点击后会被替换为 HTTPS 地址再跳转;

跟其它所有 CSP 规则一样,这个指令也有两种方式来启用,具体格式请参考上一节。需要注意的是 upgrade-insecure-requests 只替换协议部分,所以只适用于 HTTP/HTTPS 域名和路径完全一致的场景。

合理使用 HSTS

在网站全站 HTTPS 后,如果用户手动敲入网站的 HTTP 地址,或者从其它地方点击了网站的 HTTP 链接,依赖于服务端 301/302 跳转才能使用 HTTPS 服务。而第一次的 HTTP 请求就有可能被劫持,导致请求无法到达服务器,从而构成 HTTPS 降级劫持。

HSTS 基本使用

这个问题可以通过 HSTS(HTTP Strict Transport Security,RFC6797)来解决。HSTS 是一个响应头,格式如下:

  1. Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]

max-age,单位是秒,用来告诉浏览器在指定时间内,这个网站必须通过 HTTPS 协议来访问。也就是对于这个网站的 HTTP 地址,浏览器需要先在本地替换为 HTTPS 之后再发送请求。

includeSubDomains,可选参数,如果指定这个参数,表明这个网站所有子域名也必须通过 HTTPS 协议来访问。

preload,可选参数,后面再介绍它的作用。

HSTS 这个响应头只能用于 HTTPS 响应;网站必须使用默认的 443 端口;必须使用域名,不能是 IP。而且启用 HSTS 之后,一旦网站证书错误,用户无法选择忽略。

HSTS Preload List

可以看到 HSTS 可以很好的解决 HTTPS 降级攻击,但是对于 HSTS 生效前的首次 HTTP 请求,依然无法避免被劫持。浏览器厂商们为了解决这个问题,提出了 HSTS Preload List 方案:内置一份列表,对于列表中的域名,即使用户之前没有访问过,也会使用 HTTPS 协议;列表可以定期更新。

目前这个 Preload List 由 Google Chrome 维护,Chrome、Firefox、Safari、IE 11 和 Microsoft Edge 都在使用。如果要想把自己的域名加进这个列表,首先需要满足以下条件:

拥有合法的证书(如果使用 SHA-1 证书,过期时间必须早于 2016 年);

将所有 HTTP 流量重定向到 HTTPS;

确保所有子域名都启用了 HTTPS;

输出 HSTS 响应头:

max-age 不能低于 18 周(10886400 秒);

必须指定 includeSubdomains 参数;

必须指定 preload 参数;

即便满足了上述所有条件,也不一定能进入 HSTS Preload Lis。通过 Chrome 的 chrome://net-internals/#hsts 工具,可以查询某个网站是否在 Preload List 之中,还可以手动把某个域名加到本机 Preload List。

对于 HSTS 以及 HSTS Preload List,我的建议是只要你不能确保永远提供 HTTPS 服务,就不要启用。因为一旦 HSTS 生效,你再想把网站重定向为 HTTP,之前的老用户会被无限重定向,唯一的办法是换新域名。

CDN 安全

对于大站来说,全站迁移到 HTTPS 后还是得用 CDN,只是必须选择支持 HTTPS 的 CDN 了。如果使用第三方 CDN,安全方面有一些需要考虑的地方。

合理使用 SRI

HTTPS 可以防止数据在传输中被篡改,合法的证书也可以起到验证服务器身份的作用,但是如果 CDN 服务器被入侵,导致静态文件在服务器上被篡改,HTTPS 也无能为力。

W3C 的 SRI(Subresource Integrity)规范可以用来解决这个问题。SRI 通过在页面引用资源时指定资源的摘要签名,来实现让浏览器验证资源是否被篡改的目的。只要页面不被篡改,SRI 策略就是可靠的。

SRI 并不是 HTTPS 专用,但如果主页面被劫持,攻击者可以轻松去掉资源摘要,从而失去浏览器的 SRI 校验机制。

了解 Keyless SSL

另外一个问题是,在使用第三方 CDN 的 HTTPS 服务时,如果要使用自己的域名,需要把对应的证书私钥给第三方,这也是一件风险很高的事情。

CloudFlare 公司针对这种场景研发了 Keyless SSL 技术。你可以不把证书私钥给第三方,改为提供一台实时计算的 Key Server 即可。CDN 要用到私钥时,通过加密通道将必要的参数传给 Key Server,由 Key Server 算出结果并返回即可。整个过程中,私钥都保管在自己的 Key Server 之中,不会暴露给第三方。

CloudFlare 的这套机制已经开源,如需了解详情,可以查看他们官方博客的这篇文章:Keyless SSL: The Nitty Gritty Technical Details。

好了,本文先就写到这里,需要注意的是本文提到的 CSP、HSTS 以及 SRI 等策略都只有最新的浏览器才支持,详细的支持度可以去 CanIUse 查。切换到 HTTPS 之后,在性能优化上有很多新工作要做,这部分内容我在之前的博客中写过很多,这里不再重复,只说最重要的一点:既然都 HTTPS 了,赶紧上 HTTP/2 才是正道。

一些常用正则

一、校验数字的表达式

1 数字:^[0-9]*$

2 n位的数字:^\d{n}$

3 至少n位的数字:^\d{n,}$

4 m-n位的数字:^\d{m,n}$

5 零和非零开头的数字:^(0|[1-9][0-9]*)$

6 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$

7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$

8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$

9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$

10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$

11 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]*$

12 非零的负整数:^-[1-9][]0-9″$ 或 ^-[1-9]\d$

13 非负整数:^\d+$ 或 ^[1-9]\d*|0$

14 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$

15 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$

16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$

17 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$

18 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$

19 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$

二、校验字符的表达式

1 汉字:^[\u4e00-\u9fa5]{0,}$

2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$

3 长度为3-20的所有字符:^.{3,20}$

4 由26个英文字母组成的字符串:^[A-Za-z]+$

5 由26个大写英文字母组成的字符串:^[A-Z]+$

6 由26个小写英文字母组成的字符串:^[a-z]+$

7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$

8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$

9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$

10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$

11 可以输入含有^%&’,;=?$\”等字符:[^%&’,;=?$\x22]+

12 禁止输入含有~的字符:[^~\x22]+

三、特殊需求表达式

1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$

2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?

3 InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$

4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$

6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

7 身份证号(15位、18位数字):^\d{15}|\d{18}$

8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$

9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$

11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$

12 日期格式:^\d{4}-\d{1,2}-\d{1,2}

13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$

14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

15 钱的输入格式:

16 1.有四种钱的表示形式我们可以接受:”10000.00″ 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]*$

17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0″不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$

18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$

19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$

20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$

21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$

22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$

23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$

24 备注:这就是最终结果了,别忘了”+”可以用”*”替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里

25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$

26 中文字符的正则表达式:[\u4e00-\u9fa5]

27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))

28 空白行的正则表达式:\n\s*\r (可以用来删除空白行)

29 HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)

30 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)

31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)

32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)

33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)

34 IP地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))

【转】使用localstorage和预加载做到webview秒开

原文地址:http://foio.github.io/localstorage-webview/?utm_source=tuicool&utm_medium=referral

提到网页加载速度优化,大家都会想到静态资源上CDN,CSS和JS文件合并,图片合并成雪碧图等常用手段;但是在某些特殊情况下这些常用方法也无法达到理想的效果。比如,在国际化场景下,很多国家还停留在2G网络阶段,无论如何优化,都无法避免过慢的网络请求。最近一直在做国际化(主要是印尼和泰国)背景下的webview性能优化,也算有一些经验。由于我们的产品是面向android用户的,而android手机对H5支持很好,因此我们主要是应用H5的新特性。

1.H5的menifest缓存的局限

H5的menifest缓存机制是大家经常提起的方法,这是一种使用起来很简单的机制,其基本原理就是:当menifest文件有更新时,就会更新整个应用,否则就不会请求网络而是使用本地缓存的资源。但是我们使用起来却有不少问题:比如即使menifest文件发生变化,应用也不能及时的更新。对于menifest机制我们的结论是:menifest可以用于缓存基本不发生变化的文件(比如基本样式表base.css,javascript类库jquery.js等),对于业务代码一定不要使用menifest缓存,否则可能导致业务代码无法更新

新浪微博中大量的使用了menifest缓存机制,但是他们也只缓存了类库和基本样式表,而没有缓存业务代码。这是一个例子http://card.weibo.com/article/h5/s#cid=1001603928694585472076,其menifest文件如下:

CACHE MANIFEST
# v = 8f708b371de7949de35b8cec4dac678b
CACHE:
# CSS and CSS resource image
http://u1.sinaimg.cn/apps/media/css/article_201511121505.css
http://u1.sinaimg.cn/apps/media/img/bg_cricle.png
http://u1.sinaimg.cn/apps/media/img/bg_criclein.png
http://u1.sinaimg.cn/apps/media/img/bg_imgslayer.png
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.eot
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.woff
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.ttf
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.svg
http://u1.sinaimg.cn/apps/media/img/lib/icons.svg
http://u1.sinaimg.cn/apps/media/img/lib/icon_SmartisanNote.svg
http://u1.sinaimg.cn/apps/media/img/lib/icon_Smart.svg

# Placeholder image
#http://ww3.sinaimg.cn/small/6f21f059jw1e4vxadm276j20f008ca9w.jpg

# js file
http://u1.sinaimg.cn/apps/media/js/jquery.min.js
http://u1.sinaimg.cn/apps/media/js/page/article/show/offline_201511121505.js

# Emoticons top 10
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6a/laugh.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/tootha_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6d/lovea_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/c4/liwu_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/9d/sada_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/hearta_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/d9/ye_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/19/heia_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/8c/hsa_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/d0/z2_org.gif

NETWORK:
*

2.使用localstorage缓存js和css文件

localstorage设计之初是为了缓存应用数据的,关于是否应该使用它缓存js和css文件,知乎上有一篇讨论: 静态资源(JS/CSS)存储在localStorage有什么缺点?为什么没有被广泛应用?。其中得票最多的基本上概况了localstorage的适用场景以及优缺点:

PC上因为localstorage兼容性不好,而且网速较快,因此实用价值不大
移动端单页面应用(webapp),因为localstorage兼容性好,网速慢,所以值得尝试
localstorage的兼容性问题

UC的前端团队推出了基于localstorage存储前端静态资源的Scrat-webapp模块化开发体系,而且基于这个模块化体系开发了很多项目:神马视频NBA直播。大家可以通过chrome的开发者工具查看localstorage的使用情况。

基于以上的行业经验,我们也选用了localstorage作为自己的前端资源缓存,并实现了自己的基于localstorage的静态资源加载器,其实现原理可以参考我的博客:在网页中异步加载javascript 的XHR Eval方法。具体代码如下:

;(function (global) {
    'use strict';

    //检查文件类型
    var TYPE_RE = /\.(js|css)(?=[?&,]|$)/i;
    function fileType(str) {
        var ext = 'js';
        str.replace(TYPE_RE, function (m, $1) {
            ext = $1;
        });
        if (ext !== 'js' && ext !== 'css') ext = 'unknown';
        return ext;
    }

    //将js片段插入dom结构
    function evalGlobal(strScript){
        var scriptEl = document.createElement ("script");
        scriptEl.type= "text/javascript";
        scriptEl.text= strScript;
        document.getElementsByTagName("head")[0].appendChild(scriptEl) ;
    }

    //将css片段插入dom结构
    function createCss(strCss) {
        var styleEl = document.createElement('style');
        document.head.appendChild(styleEl);
        styleEl.appendChild(document.createTextNode(strCss));
    }

    // 在全局作用域执行js或插入style node
    function defineCode(url, str) {
        var type = fileType(url);
        if (type === "js"){
            //with(window)eval(str);
            evalGlobal(str);
        }else if(type === "css"){
            createCss(str);
        }
    }

    // 将数据写入localstorage
    var setLocalStorage = function(key, item){
        window.localStorage && window.localStorage.setItem(key, item);
    }

    // 从localstorage中读取数据
    var getLocalStorage = function(key){
        return window.localStorage && window.localStorage.getItem(key);
    }

    // 通过AJAX请求读取js和css文件内容,使用队列控制js的执行顺序
    var rawQ = [];
    var monkeyLoader = {
        loadInjection: function(url,onload,bOrder){
            var iQ = rawQ.length;
            if(bOrder){
                var qScript = {key: null, response: null, onload: onload, done: false};
                rawQ[iQ] = qScript;
            }
            //有localstorage 缓存
            var ls = getLocalStorage(url);
            if(ls !== null){
                if(bOrder){
                    rawQ[iQ].response = ls;
                    rawQ[iQ].key = url;
                    monkeyLoader.injectScripts();
                }else{
                    defineCode(url, ls)
                    if(onload){
                        onload();
                    }
                }
            } else {
                var xhrObj = monkeyLoader.getXHROject();
                xhrObj.open('GET', url, true);
                xhrObj.send(null);
                xhrObj.onreadystatechange = function(){
                    if(xhrObj.readyState == 4){
                        if(xhrObj.status == 200){
                            setLocalStorage(url, xhrObj.responseText);
                            if(bOrder){
                                rawQ[iQ].response = xhrObj.responseText;
                                rawQ[iQ].key = url;
                                monkeyLoader.injectScripts();
                            }else{
                                defineCode(url, xhrObj.responseText)
                                if(onload){
                                    onload();
                                }
                            }
                        }
                    }
                }
            }
        },

        injectScripts: function(){
            var len = rawQ.length;
            //按顺序执行队列中的脚本
            for (var i = 0; i < len; i++) {
                var qScript = rawQ[i];
                //没有执行
                if(!qScript.done){
                    //没有加载完成
                    if(!qScript.response){
                        console.error("raw code lost or not load!");
                        //停止,等待加载完成, 由于脚本是按顺序添加到队列的,因此这里保证了脚本的执行顺序
                        break;
                    }else{//已经加载完成了
                        defineCode(qScript.key, qScript.response)
                        if(qScript.onload){
                            qScript.onload();
                        }
                        delete qScript.response;
                        qScript.done = true;
                    }
                }
            }
        },

        getXHROject: function(){
            //创建XMLHttpRequest对象
            var xmlhttp;
            if (window.XMLHttpRequest)
                {
                    // code for IE7+, Firefox, Chrome, Opera, Safari
                    xmlhttp=new XMLHttpRequest();
                } else {
                    // code for IE6, IE5
                    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                }
                return xmlhttp;
        }
    };


    global.monkeyLoader = monkeyLoader.loadInjection;

})(this);

在页面中我们可以使用monkeyLoader加载自己的静态资源

monkeyLoader('a.js',callbackA,true);
monkeyLoader('b.js',callbackB,true);

其中monkeyLoader的三个参数分别是:资源文件路径、资源加载完成后的回调函数、以及资源是否需要有序。

3.基于fis3的localstorage集成解决方案

FIS3作为一个优秀的前端工程化解决方案,其在业界的影响无处不在。作为狼厂的一员,我们在项目中也大量使用FIS3,下面我们讨论一下如何将localstorage集成到FIS3的编译流程中。

我们的目的是将通过FIS3工程化处理后的文件列表传递到monkeyLoader,以实现基于localstorage的加载。FIS3提供了静态资源映射表,当某个文件包含字符 RESOURCE_MAP,就会用表结构数据替换此字符,而该表记录了各个原始静态资源经过工程化后的目标文件。基于此,我们只需要对monkeyLoader进行简单的封装即可。下面是具体的封装代码:

fisMonkeyLoader = function(resourceMap,resourceList){
        var pkgList = [];
        for(var idx in resourceList){
            var resourceItem = resourceList[idx];
            var pakItem = resourceMap['pkg'][resourceMap['res'][resourceItem]['pkg']]['uri'];
            pkgList[pakItem] =  1;
        }
        for(var pkgIdx in pkgList){
            monkeyLoader(pkgIdx,null,true);
        }

    }

而使用方法也很简单:

__inline('../js/lib/monkeyLoader.js');
fisMonkeyLoader(__RESOURCE_MAP__,[
    'css/animate.css',
    'css/app.css',
    'js/lib/zepto-1.1.3.min.js',
    'js/app/app.js',
]);

fisMonkeyLoader根据RESOURCE_MAP找到原始资源对应的经过工程化处理以后的资源,并通过monkeyLoader进行加载。

也许你已经注意到了,使用localstorage只能加载同域的js和css文件,因为我们没办法通过js代码读取其他域下的静态资源的内容。而且localstorage有大小限制,一般情况下每个域名下最多使用5M的本地存储空间,因此我们务必要处理超出大小限制是的异常情况,各个浏览器抛出的异常不太一致,这里给出了一个跨浏览器的异常检测方案

4.通过预加载解决首次加载过慢问题

针对目前常见的hybrid架构,我们可以通过让native客户端预加载webview从而提升首次速度。我们的实现是:客户端在启动时打开一个1px大小(用户不可见)的webview用于提前加载某些重要的页面,而这些页面通过上文中的localstorage机制存储了几乎全部js和css静态资源,从而得到webview秒开的体验。

Centos6.X下安装nodejs,提示gcc需要升级解决方法

node已经进入了v4版本,需要gcc4.8,而centos6.X由于版本过老,使用的是gcc4.4。因此通过网上大神推荐,可以通过安装绑定了gcc4.8的devtools-2来解决。具体解决方法如下:

PS:如今可以用node官网上直接编译好的包,或者通过nvm安装。

https://nodejs.org/dist/v4.2.5/node-v4.2.5-linux-x64.tar.gz

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo
yum install -y devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
scl enable devtoolset-2 bash