2017年12月

来, 先看效果哈哈哈哈!

演示地址: http://ascii-picture.imlht.com/

             "` """        . "`"""""""""""""""""""w$@w"""""""""""""""""""                                      
                   """""""       `""""""""""""$$$$$$$$$00$$0"""""""""""""""""""                                
                            """"""""""""""""$$$$$$$$$$$$$$$$$$$$0""""""""""""""""""""                          
                                    """""""$$$$$$$$$$$$$$$$$$$$$$$$""""""0(""""""""""""""""                    
                                          $$$$$$$$$$$$$$$$$$$$$$$$$$00&0("""""""""""""""""""""""               
                         `               $$$$$$$$$$$$$$$$$$$$$$$$$$$$$&hLLLL(~~"""""""""""""""""""             
""".                             """""" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@0000000L""""""""""""""`           
""""""""""""""""                     ""0$$$$$$$$$0("(0$$$$$$$$$$$$$$$$$$$$$$$$$$$@&&h0000v"""""""""".          
"""""""""""""""""""""""""".          ""$$$$$$$$0"""""v00$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$000(""""""""            
"""""""""""""""""""""""""""""""""""""""$$$$$$0""""""""""(00h$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$w0"""               
"""""""""""""""""""""""""""""""""""""""$$$$00$$0""($$$$$$$"""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$h"            
""""""""""""""""""""""""""""""""""""""0$$$$$$$$0"v$$$$0"(&0"""$$$$$$$$$$$$$&""$$$$$$$0""h$$$&h0w&$$$$"         
"""""""""""""""(vv~~"""""""""""""""""00&$$"0""0&0"$$$$$$$""""""$$$$$$$$$$$$$""$"$$$$$v0""$0$$$$$h$$$$$""""w"""0
"""""""""""""""0"0(0000v"0000"""""""0w0w$$""$$$""""""v""~"""`"""$$$$$$$$$$$(""""$$$$$$$""00"0$$h$&"~$$v""""""$@
   """""""""""("0"000"0"0000v"""""""0$$w$0 ""$"""\` ""      """""$$$$$$$$$$$""\`"""$$$$$$$0""0$$$($$(&$h"""""""$h
       """"""""~"""""0(0v0"00"""""""L$$h0     \`   ""0"    .""""""0$$$v$$$$$""  "0""$$$$0"""w$$$$$w"~""`  """ @"
      """"""""""""~"""0v0000"""""""""$h&$    0 """"  $$""""""""""w$$0"$$$$$""""""0"" " """"v$$$$$$ .       " w"
      """""""""""0""""""""0""""""""""0$$@"""$$$$$$$$$""0&@&&"""""&0""$$$$$$$$$$$$$$$$$0"""L0"&$@""         " 0L
      ""  """"""""(0"""L~"""""""""""""0w$""L0"""$$" `"~0$$$&"""""$$$$$$$$$&$$$$$$$$$$$$$$$$$$"@""             "
      """""""""""""0""""""""""""""""""""""""""($$$$$$$00$hL~"""""$$$$$$$$$w@$$$$$$$$$$&$$$$$$0$&"              
""""""""""""""""""""""""""""""""""""""""""""""$$0@$$$$0w&0~vw&hwh@$$$$$$$""h$$ $$ $$$$"$$$$$~$"@"              
 """"""""""""""""""""""""""""""""""""""""""""" v"""""~"0h0$$$$@~v($$$$$$$""w$$$$   $$$0"$$$$&"h$"              
"""""""""""""""""""""""""""""""""""""""""""""""0"(@$$$wh&$$$$$@v""~$$$$$""""$$$$$jj$$$ "$$$$. 0 "      ;       
""""""""""""""""""""""""""""""""""""""""""` """~$$$0"""(&$$$$$$$"""$$$"     0$$$$$$$$$$ 0$$$$" ""     ;$$;"""""
"""""""""""""""""""""""""""""""""""""""""""" """""""L0$$$$$$$$$$$0""$               "L00w$$$$$.$L" """"$$,"""""
""""""""""""""""""""""""""""""""""""""""""""` "0w$$$$$$$$$$$$$$$$@"""                           "" """"$$$"$"`"
""""""""""""""""""""""""""""""""""""""""""""""  ""$$$Lh$$$$$$$$$$$&""                             "("0@@"""""""
"""""""""""""""""""""""""""""""""""""""""""""""   ." ""&$$$$$$$$$$$$$                              $$0"$$""""""
""""""""""""""""""""""""""""""""""""""""""""""""  .   "&$$$$$$$$$$$$$                              $$$$$$$""("$
"""""""""""""""""""""""""""""""""""""""""""""""""     "L$$$$$$$$$$$$w                               $$$$$$"@$$$
"""""""""""""""""""""""""""""""""""""""""""""          "$$$$$$$$$$$$"                                &$$$""@$$$
""""""""""""""""""""""""""""""""""""""""0""             $$$$$$$$$$$$                                 "$$~"""$$$
"""""""""""""""""""""""""""""""""""""""""              ."$$$$$$$$$$h                                  &$,$##$$0
"""""""""""""""""""""""""""""""""""""""                 ""$$$$$$$$@"                                   0~$-,$$$
"""""""""""""""""""""""""""""""""""""            ".     ..$$$$$$000"                         "     "   "$h""$0$
"""""""""""""""""""""""""""""""""""              ""        $$$$0h0$                         "" ""  "    $$$ """
""""""""""""""""""""""""""""""""""               ""        "$$$$$$$          "              """"  ""    $$$$0""
"""""""""""""""""""""""""""""""""""       "      ""         $$$$$$$         "              """  """     "$$$""0
"""""""""""""""""""""""""""&"""""""              ""         "$$$$$$       ""              ."".  .".      $$$00h

平时看代码会看到很多标点符号的字符拼起来的图案, 特别有趣, 像kong(一个高性能API网关), 除了源代码里面有图案, 命令行也藏了彩蛋:

Kong, the biggest ape in town

    /\  ____
    <> ( oo )
    <>_| ^^ |_
    <>   @    \
   /~~\ . . _ |
  /~~~~\    | |
 /~~~~~~\/ _| |
 |[][][]/ / [m]
 |[][][[m]
 |[][][]|
 |[][][]|
 |[][][]|
 |[][][]|
 |[][][]|
 |[][][]|
 |[][][]|
 |[][][]|
 |[|--|]|
 |[|  |]|
 ========
==========
|[[    ]]|
==========

上面这个图案, 只是停留在外形轮廓上, 而我今天要玩的会深入一点: 基于图片的灰度值来生成图案. 此时的图片不单单有轮廓, 还有光影效果, 也就是素描中提及的黑白灰.

原理实际上挺简单的, 在白色背景下, 字符 $ 会有比较大面积的黑, 而字符 + 相对就淡了很多, 毫无疑问, 空格就是纯白了. 所以, 只要把一些字符按照 , , 排序, 并把这些字符映射为 0-255 的灰度值, 就可以根据图片生成更生动的字符画了.

至于这些字符按照灰度排序, 已经有人帮我们做好了, 具体可以查看这个Demo, 是用 Python 写的:

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")

看到这里, 是时候拿起 Python 干起来了! 可以照着链接在自己电脑跑一下, 制作一些白色背景的表情包, 但如果是照片的话会发现很糊, 根本看不清, 于是我拿出神器 Photoshop 调整了 亮度对比度, 尽量调高点, 生成的图案会清晰一些.

每次都去 Photoshop 调整真是繁琐, 每次失败了, 得重新用命令行生成, 然后看生成的图案怎么样, 一直重复这个步骤...而且宽度和高度都需要手工指定...所以萌生了这个想法: 把这些重复繁琐的操作, 交给界面去处理好了! 所以后面的代码都是用 JavaScript 实现的.

OK, 我们先扯回来, 说下灰度的映射算法, 也是很容易理解的, 上面的字符一共有 69 个, 0-255 一共有 256 个字符, 计算出比率 ratio 然后直接把字符取出来即可:

/**
 * ASCII Charset
 *
 * @type {String}
 */
const charset = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ";

/**
 * 69/256
 *
 * @type {Number}
 */
const ratio = charset.length / 256;

/**
 * 颜色值转换为 ASCII 字符
 *
 * @param  {Number}   r        R
 * @param  {Number}   g        G
 * @param  {Number}   b        B
 * @param  {Number}   a        A
 * @param  {Number}   type     类型
 * @return {String}            ASCII 字符
 */
export const rgba_to_char = (r, g, b, a, type) => {
  if (a === 0) return ' ';
  r = Math.round(a / 255 * r);
  g = Math.round(a / 255 * g);
  b = Math.round(a / 255 * b);
  return charset[ Math.round( ratio * rgb_to_gray(r, g, b, type) ) ] || ' ';
};

根据灰度生成字符, 那灰度怎么来的? 扒了挺多资料, 总体来说有几个公式, 具体可以看这篇文章

Gray = R*0.299 + G*0.587 + B*0.114

上面的 Python 代码用的是这个公式, 参考知乎:

Gray = 0.2126 R' + 0.7152 G' + 0.0722 B'

还有另一种, 这个是我实验后发现的, 用这个方法生成的图案细节会多一些, 大家也可以试试看. 算法是比较复杂的, 基本原理是将 RGB 色彩转为 XYZ 色彩, 再从 XYZ 转到 Lab. Lab颜色空间中的L分量用于表示像素的亮度, 最小值是0(纯黑), 最大值是100(纯白), 而a表红绿, b表黄蓝. 我们需要的是灰度值算法, 所以只需L分量就可以了.

再加上平均值, 最大值, 只取绿色通道, 一共就有6种算法, 代码实现如下:

/**
 * 颜色值转换为灰度
 *
 * @param  {Number} r    R
 * @param  {Number} g    G
 * @param  {Number} b    B
 * @param  {Number} type 类型
 * @return {Number}      灰度值
 */
const rgb_to_gray = (r, g, b, type) => {
  switch (type) {
    case 1:
      return g;
    case 2:
      return Math.max(r, g, b);
    case 3:
      return Math.round((r + g + b) / 3);
    case 4:
      return Math.round(0.299 * r + 0.587 * g + 0.114 * b);
    case 5:
      return Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b);
    case 6:
      // https://github.com/antimatter15/rgb-lab/blob/master/color.js
      // https://github.com/markusn/color-diff/blob/master/lib/convert.js
      r /= 255;
      g /= 255;
      b /= 255;
      r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
      g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
      b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
      let x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
      let y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
      let z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
      x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
      y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
      z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
      return Math.round(255 / 100 * ((116 * y) - 16));
  }
};

OK, 目前我们已经实现了彩色的像素值变成ASCII字符, 接下来要解决一个问题, 调整图像的亮度和对比度, 同样也是有公式的, 参考链接:

bitmap() {
  return this.data.map((x, i) => {
    if ((i+1) % 4 === 0) {
      // alpha
      return x;
    }
    // http://blog.csdn.net/hbaizj/article/details/17376857
    const B = this.brightness / 100;
    const c = this.contrast / 100;
    const k = Math.tan( (45 + 44 * c) / 180 * 3.1416 );
    return [x - 127.5 * (1 - B)] * k + 127.5 * (1 + B);
  });
}

最后, 我们只需把用户选择的图片, 转换为 RGB 值, 加上亮度对比度, 宽度高度的变换, 就大功告成了:

onchange() {
  const files = document.getElementById('file').files;
  if (!files || files.length === 0) return;
  const that = this;
  let fr = new FileReader();
  fr.onload = function (event) {
    let img = new Image();
    img.onload = function () {
      let c = document.createElement('canvas');
      if (!that.width && !that.height) {
        that.width = img.width;
        that.height = img.height;
      } else if (!that.width) {
        that.width = Math.round(img.width * (that.height / img.height));
      } else if (!that.height) {
        that.height = Math.round(img.height * (that.width / img.width));
      }
      c.width = that.width;
      c.height = that.height;
      let ctx = c.getContext('2d');
      ctx.drawImage(img, 0, 0, that.width, that.height);
      that.data = ctx.getImageData(0, 0, that.width, that.height).data;
    }
    img.src = event.target.result;
  }
  fr.readAsDataURL(files[0]);
}

完整的源码, 我放到 GitHub 上了, 求Star求Star求Star! 代码是用 Vue2 写的(上面的代码都是再里面摘出来的), 结合了饿了么前端框架做界面, 目前先这样, 有时间再调整下界面吧.

演示地址: http://ascii-picture.imlht.com/

演示地址: http://json.imlht.com/vue-json-viewer-demo.html

常用的 JSON 格式化工具

JSON 是一种轻量级的数据交换格式, 相信大家用得比较多, 平时也用了很多格式化工具, 例如我最常用的 Json.cn, 还有这个 BeJson, 前者清爽无广告, 后者性能给力(有广告), 可以复制下面的 JSON 体验一下:

JSON 实例

{
  "name": "小明",
  "age": 24,
  "gender": true,
  "height": 1.85,
  "weight": null,
  "skills": [
    {
      "PHP": [
        "Laravel",
        "Composer"
      ]
    },
    {
      "JavaScript": [
        "jQuery",
        "Vue",
        "React"
      ]
    },
    "Golang",
    "Python",
    "Lua"
  ]
}

窥探 Json.cn 的实现

想自己实现一个 JSON 工具, 偷师是必不可少滴. 翻下 Json.cn源码, 发现是用 jQuery 写的, 代码量不多, 比较有用的就是缩进填充函数 indent_tab 还有类型判断函数 _typeof:

function indent_tab(indent_count) {
  return (new Array(indent_count + 1)).join('&nbsp;&nbsp;&nbsp;&nbsp;');
}

function _typeof(object) {
  var tf = typeof object,
    ts = _toString.call(object);
  return null === object ? 'Null' :
    'undefined' == tf ? 'Undefined'   :
      'boolean' == tf ? 'Boolean'   :
        'number' == tf ? 'Number'   :
          'string' == tf ? 'String'   :
            '[object Function]' == ts ? 'Function' :
              '[object Array]' == ts ? 'Array' :
                '[object Date]' == ts ? 'Date' : 'Object';
};

当然, 样式我也抄了, 折叠看数组长度这个酷炫的想法也抄了哈哈! 折叠展开的实现可以看下函数 showhide, 原理比较简单: 折叠的时候把 innerHTML 存进 data-inner, 展开的时候再写回去:

function hide(obj) {
  var data_type = obj.parentNode.getAttribute('data-type');
  var data_size = obj.parentNode.getAttribute('data-size');
  obj.parentNode.setAttribute('data-inner',obj.parentNode.innerHTML);
  if (data_type === 'array') {
    obj.parentNode.innerHTML = '<i style="cursor:pointer;" class="fa fa-plus-square-o" onclick="show(this)"></i>Array[<span class="json_number">' + data_size + '</span>]';
  } else {
    obj.parentNode.innerHTML = '<i style="cursor:pointer;" class="fa fa-plus-square-o" onclick="show(this)"></i>Object{...}';
  }
}

function show(obj) {
  var innerHtml = obj.parentNode.getAttribute('data-inner');
  obj.parentNode.innerHTML = innerHtml;
}

再看看函数 format: 根据值的类型和缩进层级返回字符串, 如果是 ArrayObject, 将会递归调用: format -> _format_array -> format -> _format_object -> ...

function format(object, indent_count) {
  var html_fragment = '';
  switch (_typeof(object)) {
  case 'Null':
    0 html_fragment = _format_null(object);
    break;
  case 'Boolean':
    html_fragment = _format_boolean(object);
    break;
  case 'Number':
    html_fragment = _format_number(object);
    break;
  case 'String':
    html_fragment = _format_string(object);
    break;
  case 'Array':
    html_fragment = _format_array(object, indent_count);
    break;
  case 'Object':
    html_fragment = _format_object(object, indent_count);
    break;
  }
  return html_fragment;
};

function _format_null(object) {
  return '<span class="json_null">null</span>';
}

function _format_boolean(object) {
  return '<span class="json_boolean">' + object + '</span>';
}

function _format_number(object) {
  return '<span class="json_number">' + object + '</span>';
}

function _format_string(object) {
  object = object.replace(/\</g, "&lt;");
  object = object.replace(/\>/g, "&gt;");
  if (0 <= object.search(/^http/)) {
    object = '<a href="' + object + '" target="_blank" class="json_link">' + object + '</a>'
  }
  return '<span class="json_string">"' + object + '"</span>';
}

function _format_array(object, indent_count) {
  var tmp_array = [];
  for (var i = 0,
  size = object.length; i < size; ++i) {
    tmp_array.push(indent_tab(indent_count) + format(object[i], indent_count + 1));
  }
  return '<span data-type="array" data-size="' + tmp_array.length + '"><i  style="cursor:pointer;" class="fa fa-minus-square-o" onclick="hide(this)"></i>[<br/>' + tmp_array.join(',<br/>') + '<br/>' + indent_tab(indent_count - 1) + ']</span>';
}

function _format_object(object, indent_count) {
  var tmp_array = [];
  for (var key in object) {
    tmp_array.push(indent_tab(indent_count) + '<span class="json_key">"' + key + '"</span>:' + format(object[key], indent_count + 1));
  }
  return '<span  data-type="object"><i  style="cursor:pointer;" class="fa fa-minus-square-o" onclick="hide(this)"></i>{<br/>' + tmp_array.join(',<br/>') + '<br/>' + indent_tab(indent_count - 1) + '}</span>';
}

递归返回组件

了解原理之后, 再回头想想该如何用 Vue.js 实现? 熟悉 Vue 官方文档的人应该会想到官方实例: 树形视图 Example, 它演示了组件的递归使用, 这次派上用场了! 因为格式化的原理是根据值的类型返回特定的字符串, 结合组件化的思想, 我们递归返回组件就可以了.

如何实现组件化

  1. JSON由key和val组成, 不妨将它们各自拆分为一个组件;
  2. JSON的每一行由key:val组成, key:val合并为item组件.

也就是说, item 父组件包含了 keyval 子组件, val 有多种类型, 如果是 ArrayObject, 递归展开为 item 组件. 至于为什么叫 val 不叫 value 组件, 因为我强迫症啊哈哈哈! 都是3个字母看起来很顺眼.

OK, 瞎哔哔了这么多, 是时候看代码了. 定义(呸)抄一下缩进字符串和类型判断函数:

// 缩进字符串
var padstr = '&nbsp;&nbsp;&nbsp;&nbsp;';

// 返回给定value的类型
function valueType(value) {
  var tf = typeof value;
  var ts = Object.prototype.toString.call(value);
  return value === null ? 'Null' :
    'boolean' === tf ? 'Boolean' :
      'number' === tf ? 'Number' :
        'string' === tf ? 'String' :
          '[object Array]' === ts ? 'Array' : 'Object';
}

什么鬼?! 第一个单词 var, 用 const 啊! 好吧我只是为了说明原理, 所以没有用 ES6/7 等高级特性, 没有 webpack 也没有 npm, 全部被我撸在一个 html 里了哈哈哈!

key 组件

组件 key 逻辑比较简单, key 用双引号 " 包起来, 如果是数组的 key, 那就不渲染. 另外再根据层级填充缩进字符即可:

<!-- key template -->
<script type="text/x-template" id="key-template">
  <span class="key">
    <span v-html="pad"></span><strong class="json_key" v-if="render">"{{jsonKey}}"</strong><template v-if="render">:</template>
  </span>
</script>
<script>
Vue.component('key', {
  template: '#key-template',
  props: ['json-key', 'current-depth'],
  computed: {
    pad: function () {
      return new Array(this.currentDepth+1).join(padstr);
    },
    render: function () {
      return isNaN(this.jsonKey);
    }
  },
});
</script>

val 组件

组件 val 模板复杂了些. 如果是 ArrayObject, 判断当前组件的 open 打开状态, 如果为 true, 渲染折叠 - 图标并递归渲染 item 组件, 否则渲染展开 + 图标, 并根据类型生成折叠后的字符串; 如果是 Null, String, NumberBoolean, 渲染带有样式的 span 标签, 如果不是最后一个元素渲染 , 逗号, 最后再渲染 <br> 标签:

<!-- val template -->
<script type="text/x-template" id="val-template">
  <span class="val">
    <template v-if="canToggle">
      <template v-if="open">
        <!-- Array -->
        <template v-if="type === 'Array'"><i class="fa fa-minus-square-o" @click="toggle"></i>[<br>
          <item class="item" :json-val="jsonVal" :current-depth="currentDepth+1" :max-depth="maxDepth"></item><span v-html="pad"></span>]<template v-if="!last">,</template><br>
        </template>
        <!-- Object -->
        <template v-else-if="type === 'Object'"><i class="fa fa-minus-square-o" @click="toggle"></i>{<br>
          <item class="item" :json-val="jsonVal" :current-depth="currentDepth+1" :max-depth="maxDepth"></item><span v-html="pad"></span>}<template v-if="!last">,</template><br>
        </template>
      </template>
      <template v-else>
        <!-- Array -->
        <template v-if="type === 'Array'">
          <i class="fa fa-plus-square-o" @click="toggle"></i><span class="json_hide">Array[<span class="json_number">{{jsonVal.length}}</span>]</span><template v-if="!last">,</template><br>
        </template>
        <!-- Object -->
        <template v-else-if="type === 'Object'">
          <i class="fa fa-plus-square-o" @click="toggle"></i><span class="json_hide">Object{<span class="json_string">...</span>}</span><template v-if="!last">,</template><br>
        </template>
      </template>
    </template>
    <template v-else>
      <!-- Null -->
      <template v-if="type === 'Null'">
        <span class="json_null">null</span><template v-if="!last">,</template><br>
      </template>
      <!-- String -->
      <template v-else-if="type === 'String'">
        <span class="json_string">"{{jsonVal}}"</span><template v-if="!last">,</template><br>
      </template>
      <!-- Number -->
      <template v-else-if="type === 'Number'">
        <span class="json_number">{{jsonVal}}</span><template v-if="!last">,</template><br>
      </template>
      <!-- Boolean -->
      <template v-else-if="type === 'Boolean'">
        <span class="json_boolean">{{jsonVal ? 'true' : 'false'}}</span><template v-if="!last">,</template><br>
      </template>
    </template>
  </span>
</script>
<script>
Vue.component('val', {
  template: '#val-template',
  props: ['json-val', 'current-depth', 'max-depth', 'last'],
  data: function () {
    return { open: this.currentDepth < this.maxDepth };
  },
  computed: {
    pad: function () {
      return new Array(this.currentDepth+1).join(padstr);
    },
    type: function () {
      return valueType(this.jsonVal);
    },
    canToggle: function () {
      return this.type === 'Array' || this.type === 'Object';
    }
  },
  methods: {
    toggle: function () {
      this.open = !this.open;
    }
  }
});
</script>

item 组件

item 组件把 keyval 组件合起来就OK了:

<!-- item template -->
<script type="text/x-template" id="item-template">
  <span>
    <template v-for="(key, i) in keys">
      <key :json-key="key" :current-depth="currentDepth"></key>
      <val :last="i === keys.length-1"
        :json-val="jsonVal[key]"
        :current-depth="currentDepth"
        :max-depth="maxDepth">
      </val>
    </template>
  </span>
</script>
<script>
Vue.component('item', {
  template: '#item-template',
  props: ['json-key', 'json-val', 'current-depth', 'max-depth'],
  computed: {
    pad: function () {
      return new Array(this.currentDepth).join(padstr);
    },
    type: function () {
      return valueType(this.jsonVal);
    },
    keys: function () {
      return Object.keys(this.jsonVal);
    }
  }
});
</script>

vm 实例

根组件没有 key, 所以 #vm 里面只有一个 val 组件. current-depth0, 表示根节点, 无缩进层级, max-depth 表示初始化之后展示到第几层, 这里设为 3:

<!-- vm -->
<div id="vm">
  <val :json-val="json" :current-depth="currentDepth" :max-depth="maxDepth" :last="true"></val>
</div>
<script>
var vm = new Vue({
  el: '#vm',
  data: {
    currentDepth: 0,
    maxDepth: 3,
    json: {
      "name": "小明",
      "age": 24,
      "gender": true,
      "height": 1.85,
      "weight": null,
      "skills": [
        {
          "PHP": [
            "Laravel",
            "Composer"
          ]
        },
        {
          "JavaScript": [
            "jQuery",
            "Vue",
            "React"
          ]
        },
        "Golang",
        "Python",
        "Lua"
      ]
    }
  },
  methods: {
    getJson: function () {
      return this.json;
    },
    setJson: function (json) {
      this.json = json;
    }
  }
});
</script>

#vm 提供了 getJsonsetJson 接口, getJson 返回当前实例的 JSON 对象, 看起来没什么卵用, 但它治好了我的强迫症; setJson 可以动态改变实例的 JSON 对象, 妈妈我再也不用 F5 刷新了, 按下键盘 F12 进入开发者工具的控制台, 然后 vm.setJson(...) 就可以看效果了.

存在的问题

目前没发现有 bug, 如果有的话麻烦告知, 谢谢! 性能上, 解析比较简单的 JSON 倒是可以, 层级多的或者体积大的 JSON 会特别慢, 可能消耗在递归上. 有兴趣的可以动手测试一下, 欢迎交流.

最后

分享一下自用的 JSON 解析工具, 绿色无广告, 解析速度飞快: http://json.imlht.com/index.html. 看了下时间, 凌晨1点! 睡觉睡觉! 晚安世界!

相关文章

上一篇文章 提到的哈希值, 将会在这篇文章揭晓, 看完这篇文章, 也将会清楚地了解 Composer 镜像的工作原理.

认识 Composer 镜像的工作原理之前, 先来认识一个非常好用的参数 -vvv:

composer require monolog/monolog -vvv

随便找个目录, 执行上面的命令, 会看到这些东西:

Downloading https://packagist.phpcomposer.com/packages.json
Writing C:/Users/Administrator/AppData/Local/Composer/repo/https---packagist.phpcomposer.com/packages.json into cache
Downloading https://packagist.phpcomposer.com/p/provider-2013%2427453ee820a0c569990f0e5c705651d4902266bfb0d66ac4a8675350cc8c3dee.json
Writing C:/Users/Administrator/AppData/Local/Composer/repo/https---packagist.phpcomposer.com/p-provider-2013.json into cache

OK, 这个链接 https://packagist.phpcomposer.com/packages.json 是我们关注的重点, 看看里面有什么:

{
    "packages":[

    ],
    "notify":"https://packagist.org/downloads/%package%",
    "notify-batch":"https://packagist.org/downloads/",
    "providers-url":"/p/%package%$%hash%.json",
    "search":"https://packagist.org/search.json?q=%query%&type=%type%",
    "provider-includes":{
        "p/provider-2013$%hash%.json":{
            "sha256":"27453ee820a0c569990f0e5c705651d4902266bfb0d66ac4a8675350cc8c3dee"
        },
        "p/provider-2014$%hash%.json":{
            "sha256":"54926c64d3af83e29338c05bf2e0b4273786b644493976b91ac521e48fcb0898"
        },
        "p/provider-2015$%hash%.json":{
            "sha256":"3bd588c60bfd7845a93af3f834dd2f45b975cd70a8cb8d4ea3b1dd40c9859454"
        },
        "p/provider-2016$%hash%.json":{
            "sha256":"430744185fc781f75b4e9c966b3954f374c24f200d8124a5a43b15bce115aded"
        },
        "p/provider-2017-01$%hash%.json":{
            "sha256":"443ade7677cb86f103d95725a043725bfeb6f80c65a7f28b7295afd2874642c6"
        },
        "p/provider-2017-04$%hash%.json":{
            "sha256":"9343747a3c94f2c54b8a0ac2b7df5fd01f6b38e377c21a10ea4af19dd7739088"
        },
        "p/provider-2017-07$%hash%.json":{
            "sha256":"5c17b5bf4ace4c44112b4e3db1dbc7efd891bd023e329afdb346753fd388596c"
        },
        "p/provider-2017-10$%hash%.json":{
            "sha256":"b04c29739124e5da0f39c48683f7ddcf2cc04483e6022967d0f5f3b8dc662ae2"
        },
        "p/provider-archived$%hash%.json":{
            "sha256":"97ce4ddebac5598dcb1c82c70de4254889755951bf988c34415fddfa09ad83b4"
        },
        "p/provider-latest$%hash%.json":{
            "sha256":"54d70158a7603fb1d47e64d1a11a4518244a4752039ae6d8dd1e9e3c07bf5665"
        }
    },
    "sync-time":"2017-12-02T03:37:21+00:00",
    "how-to-use-this-packagist-mirror":"https://pkg.phpcomposer.com/",
    "total-cached-packages":177423,
    "total-cached-zips":"Millions"
}

/packages.json 我们称它为 入口配置 吧, 里面有一个字段 providers-includes, 我们再回头看看这个(注意%24解码后是$):

Downloading https://packagist.phpcomposer.com/p/provider-2013%2427453ee820a0c569990f0e5c705651d4902266bfb0d66ac4a8675350cc8c3dee.json

是不是很神奇, 把里面的 sha256 替换到 %hash%, 再加上域名, 就是一个下载链接.

我们把它下载到本地, 发现里面是这样的结构(由于文件太大, 所以我删掉了很多包, 理解它结构就可以了):

{
    "providers":{
        "2085020/api_pingdom":{
            "sha256":"285e015fe97e2fa0b235f7052a9b642e41ae669c6c4b17116ae6585ebcfafa9d"
        },
        "3rd-party/smarty":{
            "sha256":"c7bd2d6b6927649c4c200f5ff9d65cadb32af2fe3330cd698a25930224337c1a"
        },
        "3rd-party/yaml":{
            "sha256":"d94f101c9c348f369890443aa5e6d4d5e6d911b7ecc4edcbcbc12fcb6494ed08"
        },

        "zucchi/pages":{
            "sha256":"40cb6ea861cc93f26ed6d6de6b284e76f7aadef690e3b14529538f5af82b33f0"
        }
    }
}

可以看到, 里面有个 providers 字段, 字段里面有很多很多个包(我拿了前三个和最后一个), 每个包都带有一个哈希值.

OK, 答案就在这里, 上一篇文章苦苦追寻的哈希值, 其实就是在保存在这些文件里面, 它的作用大家也应该猜到了, 其实就是校验而已, 也可以说是一个版本号的功能, 当包信息有更新时, 哈希会被更新, 旧的哈希随之失效.

provider-includes 里面的 JSON, 可以理解为哈希分片信息. 从 total-cached-packages 可以知道, 一共有 177423 个包, 如果这么多的包的哈希信息保存在一个 JSON 文件当中, PHP 加载起来会很吃力, 拆成多个分片, 如果在某个分片中找到哈希, 其他分片就不需要再找了. 我实验过, 一个包只能在一个分片中出现.


总结, 一个 Composer 镜像服务器, 由 入口配置, 组件哈希, composer.json 这三部分组成. 拆开看完发现蛮简单的.

相关文章

相关文章

有使用PHP组件的朋友, 应该会注意到组件里头会有一个文件 composer.json, 它描述了组件的信息: 名称, 描述, 关键词, 作者, GitHub仓库地址...还有它所依赖的子组件, 是 Composer 工作的核心.

拿一个大家都知道的日志组件 monologcomposer.json 为例, 我说下一些比较重要的字段:

{
    "name": "monolog/monolog",
    "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
    "keywords": ["log", "logging", "psr-3"],
    "homepage": "http://github.com/Seldaek/monolog",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Jordi Boggiano",
            "email": "j.boggiano@seld.be",
            "homepage": "http://seld.be"
        }
    ],
    "require": {
        "php": "^7.0",
        "psr/log": "^1.0.1"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.7",
        "graylog2/gelf-php": "^1.4.2",
        "sentry/sentry": "^0.13",
        "ruflin/elastica": ">=0.90 <3.0",
        "doctrine/couchdb": "~1.0@dev",
        "aws/aws-sdk-php": "^2.4.9 || ^3.0",
        "php-amqplib/php-amqplib": "~2.4",
        "swiftmailer/swiftmailer": "^5.3|^6.0",
        "php-console/php-console": "^3.1.3",
        "jakub-onderka/php-parallel-lint": "^0.9",
        "predis/predis": "^1.1",
        "phpspec/prophecy": "^1.6.1"
    },
    "suggest": {
        "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
        "sentry/sentry": "Allow sending log messages to a Sentry server",
        "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
        "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
        "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
        "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
        "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
        "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
        "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
        "rollbar/rollbar": "Allow sending log messages to Rollbar",
        "php-console/php-console": "Allow sending log messages to Google Chrome"
    },
    "autoload": {
        "psr-4": {"Monolog\\": "src/Monolog"}
    },
    "autoload-dev": {
        "psr-4": {"Monolog\\": "tests/Monolog"}
    },
    "provide": {
        "psr/log-implementation": "1.0.0"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    },
    "scripts": {
        "test": [
            "parallel-lint . --exclude vendor",
            "phpunit"
        ]
    }
}

安装依赖的时候, 最重要的字段是 name, require 以及 require-dev.

name

该字段标识了组件的名称, 在 所有 的组件中, 它是唯一的;

require

该字段列举出该组件 所需的运行环境 以及 依赖的子组件的版本, 安装该组件时, 会检测运行环境, 并安装该组件的子组件, 以及这些子组件的所有子组件...直到子组件不再依赖任何组件为止;

require-dev

该字段不是必须的, 一般来说不安装里面的依赖, 也是可以用的, 通常都是用来跑单元测试. 依赖的安装同 require 字段.

其他字段对于理解镜像的原理没什么帮助, 有兴趣可以看下 这篇文章.

安装依赖的过程, 其实就是请求服务器, 要求拿到该组件的 composer.json 文件, 然后 JSON 解析, 得到 requirerequire-dev 字段的组件, 一直遍历下去, 根据文件描述的仓库地址 git clone 到本地.

看过我 上一篇文章 的朋友就知道, 文章末尾我们配置了 国内的composer镜像, 用来加速我们安装组件的过程, 它缓存了所有包的 composer.json, 并把仓库的每一个分支源码, 打包为 zip 压缩包, 并结合 cdn 加速.

镜像服务器提供了让我们得到 composer.json 的接口, 我们只需提交一个包名, 还有请求结果的哈希值(是不是很懵逼, 我怎么知道结果的哈希值), 镜像服务器会返回一个 JSON, 它包含了很多 composer.json (至少一个), 这些 composer.json 里面就有我们要找的组件的 composer.json (根据name字段), 也包括了其他包的, 为什么会带有其他包的呢, 我捣鼓了挺多次, 发现是当 require 字段存在时, 它就顺带返回了, 不过也不是绝对的, 可能考虑体积关系, 也不会返回太多.

说了这么多, 看下 psr/log 组件的请求结果吧, 比较长, 它包含了5个包的信息:

hackification/log
mobio/target
notadd/wechat
psr/log
wedeto/log
{
    "packages":{
        "hackification/log":{
            "1.0.2":{
                "name":"hackification/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3",
                    "hack",
                    "hacklang"
                ],
                "homepage":"https://github.com/hackification/log",
                "version":"1.0.2",
                "version_normalized":"1.0.2.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/hackification/log.git",
                    "reference":"75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/hackification/log/75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7.zip",
                    "reference":"75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-11-08T15:32:34+00:00",
                "autoload":{
                    "psr-4":{
                        "Psr\Log\":"Psr/Log/"
                    }
                },
                "extra":{
                    "branch-alias":{
                        "dev-master":"1.0.x-dev"
                    }
                },
                "require":{
                    "hhvm":">=3.0.0"
                },
                "replace":{
                    "psr/log":"*"
                },
                "uid":1072563
            },
            "dev-master":{
                "name":"hackification/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3",
                    "hack",
                    "hacklang"
                ],
                "homepage":"https://github.com/hackification/log",
                "version":"dev-master",
                "version_normalized":"9999999-dev",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/hackification/log.git",
                    "reference":"75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/hackification/log/75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7.zip",
                    "reference":"75b02e14bd8e5b8ded75de91d7eb9df6046f7fa7",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-11-08T15:32:34+00:00",
                "autoload":{
                    "psr-4":{
                        "Psr\Log\":"Psr/Log/"
                    }
                },
                "extra":{
                    "branch-alias":{
                        "dev-master":"1.0.x-dev"
                    }
                },
                "require":{
                    "hhvm":">=3.0.0"
                },
                "replace":{
                    "psr/log":"*"
                },
                "uid":1072564
            }
        },
        "mobio/target":{
            "0.0.5":{
                "name":"mobio/target",
                "description":"PHP library for myTarget API",
                "keywords":[
                    "php",
                    "myTarget"
                ],
                "homepage":"",
                "version":"0.0.5",
                "version_normalized":"0.0.5.0",
                "license":[
                    "MIT"
                ],
                "authors":[

                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/MobioInc/target.git",
                    "reference":"5baeaae1aa7d85c5b5fd4e33a06608a1de93d73b"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/MobioInc/target/5baeaae1aa7d85c5b5fd4e33a06608a1de93d73b.zip",
                    "reference":"5baeaae1aa7d85c5b5fd4e33a06608a1de93d73b",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-10-31T08:52:52+00:00",
                "autoload":{
                    "psr-4":{
                        "Mobio\Target\":"src/"
                    }
                },
                "require":{
                    "php":">=5.5.0",
                    "psr/log":"~1.0",
                    "guzzlehttp/guzzle":"^6.1"
                },
                "require-dev":{
                    "phpunit/phpunit":"^5.5"
                },
                "provide":{
                    "psr/log":"1.0.0"
                },
                "uid":1060012
            },
            "dev-master":{
                "name":"mobio/target",
                "description":"PHP library for myTarget API",
                "keywords":[
                    "php",
                    "myTarget"
                ],
                "homepage":"",
                "version":"dev-master",
                "version_normalized":"9999999-dev",
                "license":[
                    "MIT"
                ],
                "authors":[

                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/MobioInc/target.git",
                    "reference":"70aa382ca6d3ba3b5a834bbe85d3fc2cbfec965f"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/MobioInc/target/70aa382ca6d3ba3b5a834bbe85d3fc2cbfec965f.zip",
                    "reference":"70aa382ca6d3ba3b5a834bbe85d3fc2cbfec965f",
                    "shasum":""
                },
                "type":"library",
                "time":"2017-02-13T18:53:10+00:00",
                "autoload":{
                    "psr-4":{
                        "Mobio\Target\":"src/"
                    }
                },
                "require":{
                    "php":">=5.5.0",
                    "guzzlehttp/guzzle":"^6.1",
                    "psr/log":"~1.0"
                },
                "require-dev":{
                    "phpunit/phpunit":"^5.5"
                },
                "provide":{
                    "psr/log":"1.0.0"
                },
                "uid":700331
            }
        },
        "notadd/wechat":{
            "dev-master":{
                "name":"notadd/wechat",
                "description":"Notadd's Wechat Module.",
                "keywords":[
                    "framework",
                    "cms",
                    "member",
                    "notadd"
                ],
                "homepage":"https://notadd.com",
                "version":"dev-master",
                "version_normalized":"9999999-dev",
                "license":[
                    "Apache-2.0"
                ],
                "authors":[
                    {
                        "name":"Notadd",
                        "email":"notadd@ibenchu.com"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/notadd/wechat.git",
                    "reference":"e3f684cd225f3fadf21953c0289cb8426baad0e5"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/notadd/wechat/e3f684cd225f3fadf21953c0289cb8426baad0e5.zip",
                    "reference":"e3f684cd225f3fadf21953c0289cb8426baad0e5",
                    "shasum":""
                },
                "type":"notadd-module",
                "time":"2017-11-13T04:23:05+00:00",
                "autoload":{
                    "psr-4":{
                        "Notadd\Wechat\":"src/"
                    }
                },
                "require":{
                    "php":">=7.0",
                    "overtrue/wechat":"~3.1"
                },
                "require-dev":{
                    "notadd/installers":"0.14.*",
                    "notadd/testing":"0.4.*",
                    "phpunit/phpunit":"~6.0"
                },
                "replace":{
                    "guzzlehttp/guzzle":"*",
                    "guzzlehttp/promises":"*",
                    "guzzlehttp/psr7":"*",
                    "monolog/monolog":"*",
                    "psr/container":"*",
                    "psr/http-message":"*",
                    "psr/log":"*",
                    "symfony/http-foundation":"*",
                    "symfony/polyfill-mbstring":"*",
                    "symfony/psr-http-message-bridge":"*"
                },
                "uid":1108963
            }
        },
        "psr/log":{
            "1.0.0":{
                "name":"psr/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3"
                ],
                "homepage":"",
                "version":"1.0.0",
                "version_normalized":"1.0.0.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/php-fig/log.git",
                    "reference":"fe0936ee26643249e916849d48e3a51d5f5e278b"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/php-fig/log/fe0936ee26643249e916849d48e3a51d5f5e278b.zip",
                    "reference":"fe0936ee26643249e916849d48e3a51d5f5e278b",
                    "shasum":""
                },
                "type":"library",
                "time":"2012-12-21T11:40:51+00:00",
                "autoload":{
                    "psr-0":{
                        "Psr\Log\":""
                    }
                },
                "uid":29358
            },
            "1.0.1":{
                "name":"psr/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3"
                ],
                "homepage":"https://github.com/php-fig/log",
                "version":"1.0.1",
                "version_normalized":"1.0.1.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/php-fig/log.git",
                    "reference":"5277094ed527a1c4477177d102fe4c53551953e0"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/php-fig/log/5277094ed527a1c4477177d102fe4c53551953e0.zip",
                    "reference":"5277094ed527a1c4477177d102fe4c53551953e0",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-09-19T16:02:08+00:00",
                "autoload":{
                    "psr-4":{
                        "Psr\Log\":"Psr/Log/"
                    }
                },
                "extra":{
                    "branch-alias":{
                        "dev-master":"1.0.x-dev"
                    }
                },
                "require":{
                    "php":">=5.3.0"
                },
                "uid":1000789
            },
            "1.0.2":{
                "name":"psr/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3"
                ],
                "homepage":"https://github.com/php-fig/log",
                "version":"1.0.2",
                "version_normalized":"1.0.2.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/php-fig/log.git",
                    "reference":"4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/php-fig/log/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d.zip",
                    "reference":"4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-10-10T12:19:37+00:00",
                "autoload":{
                    "psr-4":{
                        "Psr\Log\":"Psr/Log/"
                    }
                },
                "extra":{
                    "branch-alias":{
                        "dev-master":"1.0.x-dev"
                    }
                },
                "require":{
                    "php":">=5.3.0"
                },
                "uid":1029935
            },
            "dev-master":{
                "name":"psr/log",
                "description":"Common interface for logging libraries",
                "keywords":[
                    "log",
                    "psr",
                    "psr-3"
                ],
                "homepage":"https://github.com/php-fig/log",
                "version":"dev-master",
                "version_normalized":"9999999-dev",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"PHP-FIG",
                        "homepage":"http://www.php-fig.org/"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/php-fig/log.git",
                    "reference":"4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/php-fig/log/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d.zip",
                    "reference":"4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                    "shasum":""
                },
                "type":"library",
                "time":"2016-10-10T12:19:37+00:00",
                "autoload":{
                    "psr-4":{
                        "Psr\Log\":"Psr/Log/"
                    }
                },
                "extra":{
                    "branch-alias":{
                        "dev-master":"1.0.x-dev"
                    }
                },
                "require":{
                    "php":">=5.3.0"
                },
                "uid":29285
            }
        },
        "wedeto/log":{
            "v0.9.1":{
                "name":"wedeto/log",
                "description":"Wedeto Platform - Logger",
                "keywords":[

                ],
                "homepage":"https://wedeto.net/",
                "version":"v0.9.1",
                "version_normalized":"0.9.1.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"Egbert van der Wal",
                        "email":"ewal@pointpro.nl"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/Wedeto/Log.git",
                    "reference":"acc7f4aa66965dd2627a3886c9ac8b0d77b0a268"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/Wedeto/Log/acc7f4aa66965dd2627a3886c9ac8b0d77b0a268.zip",
                    "reference":"acc7f4aa66965dd2627a3886c9ac8b0d77b0a268",
                    "shasum":""
                },
                "type":"library",
                "time":"2017-04-10T12:31:55+00:00",
                "autoload":{
                    "psr-4":{
                        "Wedeto\Log\":"src/"
                    }
                },
                "require":{
                    "psr/log":"1.0.*",
                    "wedeto/util":"0.9.*",
                    "php":">=7.0.0"
                },
                "require-dev":{
                    "mikey179/vfsstream":"~1"
                },
                "provide":{
                    "psr/log":"1.0.*"
                },
                "uid":1336127
            },
            "v0.9.2":{
                "name":"wedeto/log",
                "description":"Wedeto Platform - Logger",
                "keywords":[

                ],
                "homepage":"https://wedeto.net/",
                "version":"v0.9.2",
                "version_normalized":"0.9.2.0",
                "license":[
                    "MIT"
                ],
                "authors":[
                    {
                        "name":"Egbert van der Wal",
                        "email":"ewal@pointpro.nl"
                    }
                ],
                "source":{
                    "type":"git",
                    "url":"https://github.com/Wedeto/Log.git",
                    "reference":"55e6b03f0b446b7054078fd8a680ad1499d26264"
                },
                "dist":{
                    "type":"zip",
                    "url":"https://files.phpcomposer.com/files/Wedeto/Log/55e6b03f0b446b7054078fd8a680ad1499d26264.zip",
                    "reference":"55e6b03f0b446b7054078fd8a680ad1499d26264",
                    "shasum":""
                },
                "type":"library",
                "time":"2017-04-10T18:15:36+00:00",
                "autoload":{
                    "psr-4":{
                        "Wedeto\Log\":"src/"
                    }
                },
                "require":{
                    "psr/log":"1.0.*",
                    "wedeto/util":"0.9.*",
                    "php":">=7.0.0"
                },
                "require-dev":{
                    "mikey179/vfsstream":"~1"
                },
                "provide":{
                    "psr/log":"1.0.*"
                },
                "uid":1336697
            }
        }
    }
}

可以看到, packages 字段里面有5个包, 里面的 psr/log 字段就是我们要找的, 而里面有各个分支的 composer.json, 以分支 1.0.0 为例, 里面有两个很关键的字段, sourcedist:

{
    "source":{
        "type":"git",
        "url":"https://github.com/php-fig/log.git",
        "reference":"fe0936ee26643249e916849d48e3a51d5f5e278b"
    },
    "dist":{
        "type":"zip",
        "url":"https://files.phpcomposer.com/files/php-fig/log/fe0936ee26643249e916849d48e3a51d5f5e278b.zip",
        "reference":"fe0936ee26643249e916849d48e3a51d5f5e278b",
        "shasum":""
    }
}

dist

该字段其实就是加速的 zip 压缩包, 无需 git clone, 只需把 zip 下载到本地, 解压完, 分支 1.0.0 就装好了.

source

这个字段的作用, 就是万一 dist 字段的 zip 下载不了, 不会马上中断整个安装流程, 而是接着 git clone. 也就是说, dist 字段失败, 或者压根就没有 dist 字段, 就走 source 字段.


看到这里, 对 Composer 的了解应该多了很多吧? 还记得 请求结果的哈希值 吗? 这个哈希哪里来的, 为什么我可以提前知道这个请求的 JSON 的哈希值? 还有, 接口在哪里? 镜像服务器的官方网站, 并没有提供啊...

下一篇文章再告诉你.

相关文章

相关文章

何为 Composer

Composer 是 PHP 的依赖管理工具, 你可以在项目的 composer.json 文件中声明所依赖的组件, 它将自动为你安装.

安装 Composer

参考链接

命令行窗口下执行 php -v 检查是否已经正确安装 PHP, 如果没有安装, 安装它, 再把路径加到环境变量 PATH 中.

装完 PHP 再依次执行一下3条命令:

# 下载安装脚本 composer-setup.php 到当前目录
php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"

# 安装
php composer-setup.php

# 删除安装脚本
php -r "unlink('composer-setup.php');"

一般来说, Composer 我们使用全局安装, 其实就是放到环境变量 PATH 里面, 根据系统执行下面的步骤:

Windows

  1. 进入 PHP 的安装目录 (即 php.exe 所在位置);
  2. 复制 composer.phar 到上述目录;
  3. 新建 composer.bat 文件, 并将下列代码保存到该文件:
@php "%~dp0composer.phar" %*

Linux / Mac

只需移动或者复制 composer.phar 到目录 /usr/local/bin/ 即可, 即:

sudo mv composer.phar /usr/local/bin/composer

全局安装后, 执行以下命令, 如果输出了版本号, 说明 Composer 已经安装成功:

composer --version

配置 Composer 镜像

注意: 目前中国镜像已经失效, 请使用 Laravel China 社区提供的镜像服务.

中国镜像(已失效)

Laravel China 镜像服务

Composer 镜像我使用的是中国镜像, 因为速度快, 也非常稳定. 镜像的配置分为 全局项目 两种:

全局配置 (推荐)

# 已失效
# composer config -g repo.packagist composer https://packagist.phpcomposer.com
composer config -g repo.packagist composer https://packagist.laravel-china.org

项目配置

composer.json 所在目录, 执行以下命令:

# 已失效
# composer config repo.packagist composer https://packagist.phpcomposer.com
composer config  repo.packagist composer https://packagist.laravel-china.org

此时, composer.json 会多了 repositories 字段 (以 laravel 为例):

{
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.2.*"
    },
    "config": {
        "preferred-install": "dist"
    },
    "repositories": {
        "packagist": {
            "type": "composer",
            "url": "https://packagist.laravel-china.org"
        }
    }
}

OK, 镜像配置完成, 执行下面的命令, 体验飞一般的速度!

composer install

至于为什么变快了, 我会在后续的文章中解释.

相关文章