GithubHelp home page GithubHelp logo

bblog's People

Contributors

lishengzxc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bblog's Issues

*-spacing

word-spacing: 20px; // 空格的长度
letter-spacing: 20px; // 字间距

white-spacing: normal; // 多个空格为1个,同时忽略换行符
white-spacing: pre; // 保留多个空格
white-spacing: nowrap; //忽略换行符
white-spacing: pre-wrap; // 保留所有空格和换行
white-spacing: pre-line; // 多个空格为1个,保留换行

国内移动浏览器的一些特殊 meta

<!-- UC 强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ 强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC 强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ 强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC 应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ 应用模式 -->
<meta name="x5-page-mode" content="app">

绑定 scroll 事件时候遇到的问题

以前一直用iScroll这一方案来处理移动端的滚动,这次需要做 PC 上做一个滚动加载更多的功能,因为是 PC 就考虑不用iScroll了,然后用 Dash 查了下 scroll,看到了以下例子

<!DOCTYPE html>
<html lang="en">
  <head>
  <meta charset="UTF-8" />
  <style>
  #container {
    position: absolute;
    height: auto;
    top: 0;
    bottom: 0;
    width: auto;
    left: 0;
    right: 0;
    overflow: auto;
  }

  #foo {
    height:1000px;
    width:1000px;
    background-color: #777;
    display: block;
  }

  </style>
  </head>
  <body>
    <div id="container">
      <div id="foo"></div>
    </div>

    <script type="text/javascript">
      document.getElementById('container').onscroll = function() {
        console.log("scrolling");
      };
    </script>
  </body>
</html>

然后我就想当然了在document.body.onscroll附上了回调函数,结果在Chrome下偶尔会出现页面滚动,但是回调不执行的情况下。然后通过Chrome的审查元素发现,回调不成功的情况是因为回调并没有绑定上去,然后在window.addEventListener('scroll', function() {}),发现成功了。

因为自己要一个刨根问底的心,并找到了一下相关资料:
http://www.w3help.org/zh-cn/causes/SD9013

所以,结论是:
请在window上绑定scroll事件

超简单的分页

  function showPages(page, total) {
    var result = [page];
    for (var i = 1; i <= 3; i++) {
      if (page - i > 1) {
        result.unshift(page - i)
      }

      if (page + i < total) {
        result.push(page + i);
      }
    }

    if (page - 4 > 1) {
      result = ['...'].concat(result)
    }

    if (page > 1) {
      result = ['上一页', 1].concat(result)
    }

    if (page + 4 < total) {
      result.push('...');
    }

    if (page < total) {
      result = result.concat([total, '下一页'])
    }
    
    return result.join(' ');
  }

  // test
  var total = 110;  
  for (var i = 1; i <= total; i++) {  
    var ret = showPages(i, total);
    console.log(`${i}:`, ret);
  }

利用`FileReader`和`FormData`实现图片预览和上传

更新

URL.createObjectURL 🐂 https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL


业务有个需求,要做图片预览上传,过去都是客户端上传给后端,后端返回url前端进行预览,现在其实可以不依赖后端做预览,最后在上传,这主要依赖FileReaderFormData这两个对象和 JavaScript 处理二进制的能力。

OK,Show code~,以下代码已注释掉具体业务逻辑和实现,如果需要了解 API 细节,可以请参考:

监听表单文件变化

文件表单的样式主要通过让它后面,通过别的DOM来美化它。

<input type="file">
input.on('change', preview);

预览

预览使用FileReader对象来读:

function preview(e) {
    var file = e.target.files[0];
    var reader = new FileReader();

    reader.onloadend = function () {
        // 图片的 base64 格式, 可以直接当成 img 的 src 属性值
        var dataURL = reader.result;
        var img = new Image();
        img.src = dataURL;
        // 插入到 DOM 中预览
        // ...
    };

    reader.readAsDataURL(file); // 读出 base64

}

提交图片文件(二进制文件 非 base64)

base64 转 二进制文件

/**
 * dataURL to blob, ref to https://gist.github.com/fupslot/5015897
 * @param dataURI
 * @returns {Blob}
 */
function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], {type: mimeString});
}

构造FormData填充二进制文件数据,通过ajax的方式进行提交:

var fd = new FormData();
var blob = dataURItoBlob(dataURL);
fd.append('file', blob);


$.ajax({
    type: 'POST',
    url: '/upload',
    data: fd,
    processData: false, // 不会将 data 参数序列化字符串
    contentType: false, // 根据表单 input 提交的数据使用其默认的 contentType
    xhr: function() {
        var xhr = new window.XMLHttpRequest();
        xhr.upload.addEventListener("progress", function(evt) {
            if (evt.lengthComputable) {
                var percentComplete = evt.loaded / evt.total;
                console.log('进度', percentComplete);
            }
        }, false);

        return xhr;
    }
}).success(function (res) {
    // 拿到提交的结果
}).error(function (err) {
    console.error(err);
});

注意:不要漏了指定processDatacontentTypefalse

压缩

业务中不需要前端不需要压缩,因为后端有更靠谱的压缩方案,但是前端其实也可以压缩,那就是用canvas把图画出适合的大小,然后上传。

主要流程:

  • new出来的Image对象,我们监听它的onload事件
  • 按照压缩比例,算出压缩后的图片尺寸
  • 创建canvas,尺寸设置成上一步骤算出来的压缩后的图片尺寸
  • 调用drawImage方法,把图片绘制到canvas
  • 调用canvastoDataURL,取出base64格式的数据
  • 后续的传图步骤和上面的原图上传一样
var img = new Image();

img.onload = function () {
    // 当图片宽度超过 400px 时, 就压缩成 400px, 高度按比例计算
    // 压缩质量可以根据实际情况调整
    var w = Math.min(400, img.width);
    var h = img.height * (w / img.width);
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');

    // 设置 canvas 的宽度和高度
    canvas.width = w;
    canvas.height = h;

    // 把图片绘制到 canvas 中
    ctx.drawImage(img, 0, 0, w, h);

    // 取出 base64 格式数据
    var dataURL = canvas.toDataURL('image/png');

    // ...
};

img.src = reader.result;

自己的OneWord客户端的上传组件就是这么做的:ow-image-uploader.vue

这样一看,好像除去业务逻辑的话,好像也没多少代码

**PS:**需要注意的是,通过canvas绘制的图片,低版本iOS会出现比例不正确的情况,请参考 https://github.com/stomita/ios-imagefile-megapixel

Javascript 的 Debounce 和 Throttle 的原理及实现

分析_.debounce.throttle

DOM 上有些事件是会频繁触发的,比如mouseoverscrollresize...。以前有个需求,是做一个图表,是用canvas画的,最初,如果图表画完,用户拖拽浏览器窗口,改变浏览器大小的话,图表并不会自适应的变化,所以就需要监听resize事件,每当窗口大小变化后,再重新绘制。但是resize是频繁触发的,这就导致了页面的明显的卡顿,因为每次resize后的回调要执行大量的计算。

当时比较急,遇到这个问题以后,直接就查了.debounce.throttle,就直接用了lodash。现在回过头了,看下源码,看看它的实现。

Debounce

英文释义:

n. 防反跳
按键防反跳(Debounce)为什么要去抖动呢?机械按键在按下时,并非按下就接触的很好,尤其是有簧片的机械开关,会在接触的瞬间反复的开合多次,直到开关状态完全改变。

我们希望开关只捕获到那次最后的精准的状态切换。在 Javascript 中,那些 DOM 频繁触发的事件,我们想在某个时间点上去执行我们的回调,而不是每次事件每次触发,我们就执行该回调。有点啰嗦,再简单一点的说,我们希望多次触发的相同事件的触发合并为一次触发(其实还是触发了好多次,只是我们只关注那一次)。

所以,在 Javascript 中,我们就希望频繁事件的回调函数在某段连续时间内,在事件触发后只执行一次

resize事件为例,在 2s 内的该事件会被触发多次(具体几次未知,不同浏览器并不一样)。我们需要对resize的回调函数做 Debounce 100ms 化,这样resize的回调会在 2.1s 后触发,之前 2s 以内的resize我就无视了。

我们先自己实现一个

/**
 *
 * @param fn {Function}   实际要执行的函数
 * @param delay {Number}  延迟时间,单位是毫秒(ms)
 *
 * @return {Function}     返回一个“防反跳”了的函数
 */

function debounce(fn, delay) {

  // 定时器,用来 setTimeout
  var timer

  // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数
  return function () {

    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments

    // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
    clearTimeout(timer)

    // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),
    // 再过 delay 毫秒就执行 fn
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

将 Debounce 化后的闭包函数作为频繁触发的事件的回调,其实还是频繁执行的,只不过,返回的闭包函数内部通过clearTimeout(),让真正需要执行的回调函数不执行了,只有在连续时间内,不在触发频繁事件后的delay秒后,执行真正的回调。

Demo

http://lishengzxc.github.io/bblog/Debounce.html

document.addEventListener('mousemove', debounce(() => console.log(new Date().getTime()), 1000), false);

再鼠标不移动后的 1000ms 后,执行了真正的回调,打印了当前时间戳。

比较合理的应用场景还有,在一个表单的输入框中(包括多行文本输入框),想当用户停止输入后,再ajax

input.addEventListener('keyup', debounce(() => ajax(...), 1000), false);

_.debounce()分析

先看看underscore的。

  _.debounce = function(func, wait, immediate) {
    var timeout, result;

    var later = function(context, args) {
      timeout = null;
      if (args) result = func.apply(context, args);
    };

    var debounced = restArgs(function(args) {
      if (timeout) clearTimeout(timeout);
      if (immediate) {
        var callNow = !timeout;
        timeout = setTimeout(later, wait);
        if (callNow) result = func.apply(this, args);
      } else {
        timeout = _.delay(later, wait, this, args);
      }

      return result;
    });

    debounced.cancel = function() {
      clearTimeout(timeout);
      timeout = null;
    };

    return debounced;
  };

它的debounce还接受第三个参数immediate,这个参数是用来配置回调函数是在一个时间区间的最开始执行(immediatetrue),还是最后执行(immediatefalse),如果immediatetrue,意味着是一个同步的回调,可以传递返回值。

关键的地方是,单独拿出了一个later函数通过控制timer来觉得连续的时间除一开始后,是不是要执行回调。

loadshdebounce,接受更多的配置:

  • [options.leading=false] (boolean): Specify invoking on the leading edge of the timeout.
  • [options.maxWait] (number): The maximum time func is allowed to be delayed before it’s invoked.
  • [options.trailing=true] (boolean): Specify invoking on the trailing edge of the timeout.

lodashdebounce源代码

leading=true等效于underscoreimmediate=truetrailing则正好相反。maxWait设置了超时时间,在规定的超时间后,一定调用回调。(通过内部设置了两个setTimeout,一个用来完成基础功能,让回调只执行一次,还有一个用来控制超时)

Throttle

英文释义:

n. 节流阀

throttle就是设置固定的函数执行速率,从而降低频繁事件回调的执行次数。前面提到的canvas绘制好的图表后,用户改变窗口大小后,重新绘制图表,就很适合使用throttle。最近在写一个 Tank 游戏,用户可以非常快的点击开火,但是我们需要通过 Throttle,来降低一些开火的频率。

我们先自己实现一个

/**
*
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  执行间隔,单位是毫秒(ms)
*
* @return {Function}     返回一个“节流”函数
*/

function throttle(fn, threshhold) {

  // 记录上次执行的时间
  var last

  // 定时器
  var timer

  // 默认间隔为 250ms
  threshhold || (threshhold = 250)

  // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数
  return function () {

    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments

    var now = +new Date()

    // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
    // 执行 fn,并重新计时
    if (last && now < last + threshhold) {
      clearTimeout(timer)

      // 保证在当前时间区间结束后,再执行一次 fn
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshhold)

    // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

代码中,比较关键的部分是最后部分的if .. else ..,每次回调执行以后,需要保存执行的函数的时间戳,为了计算以后的事件触发回调时与之前执行回调函数的时间戳的间隔,从而根据间隔判断要不要执行回调。

_.throttle()分析

直接看underscore的。因为lodash没有对其再进行包装。

 _.throttle = function(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };

    var throttled = function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  };

其中previous相当于自己实现代码中的last。它还接受leadingtrailing来控制真正回调触发的时机,这和lodash_.debounce 差不太多。

最后

这里有一个可视化分析页面:http://demo.nimius.net/debounce_throttle/,大家可以点开看看。

https://github.com/rollup/rollup-plugin-commonjs/issues/360

(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
	typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
	(factory((global.Helloworld = {}),global.React));
}(this, (function (exports,React) { 'use strict';

	var React__default = 'default' in React ? React['default'] : React;


  var id = 0;
	var px = Math.random();

	var _uid = function _uid(key) {
	  return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));
	};

	function createCommonjsModule(fn, module) {
		return module = { exports: {} }, fn(module, module.exports), module.exports;
	}

	var _core = createCommonjsModule(function (module) {
	  var core = module.exports = {
	    version: '2.5.7'
	  };
	  if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef
	});
	var _core_1 = _core.version;

	var _global = createCommonjsModule(function (module) {
	  // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
	  var global = module.exports = typeof window != 'undefined' && window.Math == Math ? window : typeof self != 'undefined' && self.Math == Math ? self // eslint-disable-next-line no-new-func
	  : Function('return this')();
	  if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef
	});

	var _library = false;

	var _shared = createCommonjsModule(function (module) {
	  var SHARED = '__core-js_shared__';
	  var store = _global[SHARED] || (_global[SHARED] = {});
	  (module.exports = function (key, value) {
	    return store[key] || (store[key] = value !== undefined ? value : {});
	  })('versions', []).push({
	    version: _core.version,
	    mode: _library ? 'pure' : 'global',
	    copyright: '© 2018 Denis Pushkarev (zloirock.ru)'
	  });
  });
  
  var _wks = createCommonjsModule(function (module) {
	  var store = _shared('wks');
	  var Symbol = _global.Symbol;
	  var USE_SYMBOL = typeof Symbol == 'function';

	  var $exports = module.exports = function (name) {
	    return store[name] || (store[name] = USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : _uid)('Symbol.' + name));
	  };

	  $exports.store = store;
	});

	var _isObject = function _isObject(it) {
	  return typeof it === 'object' ? it !== null : typeof it === 'function';
	};

	var _anObject = function _anObject(it) {
	  if (!_isObject(it)) throw TypeError(it + ' is not an object!');
	  return it;
	};

	var _aFunction = function _aFunction(it) {
	  if (typeof it != 'function') throw TypeError(it + ' is not a function!');
	  return it;
	};

	var _ctx = function _ctx(fn, that, length) {
	  _aFunction(fn);
	  if (that === undefined) return fn;

	  switch (length) {
	    case 1:
	      return function (a) {
	        return fn.call(that, a);
	      };

	    case 2:
	      return function (a, b) {
	        return fn.call(that, a, b);
	      };

	    case 3:
	      return function (a, b, c) {
	        return fn.call(that, a, b, c);
	      };
	  }

	  return function ()
	  /* ...args */
	  {
	    return fn.apply(that, arguments);
	  };
	};

	var f = {}.propertyIsEnumerable;
	var _objectPie = {
	  f: f
	};

	var _propertyDesc = function _propertyDesc(bitmap, value) {
	  return {
	    enumerable: !(bitmap & 1),
	    configurable: !(bitmap & 2),
	    writable: !(bitmap & 4),
	    value: value
	  };
	};

	var _fails = function _fails(exec) {
	  try {
	    return !!exec();
	  } catch (e) {
	    return true;
	  }
	};

	var _descriptors = !_fails(function () {
	  return Object.defineProperty({}, 'a', {
	    get: function get() {
	      return 7;
	    }
	  }).a != 7;
	});

	var document = _global.document; // typeof document.createElement is 'object' in old IE

	var is = _isObject(document) && _isObject(document.createElement);

	var _domCreate = function _domCreate(it) {
	  return is ? document.createElement(it) : {};
	};

	var _ie8DomDefine = !_descriptors && !_fails(function () {
	  return Object.defineProperty(_domCreate('div'), 'a', {
	    get: function get() {
	      return 7;
	    }
	  }).a != 7;
	});

	// instead of the ES6 spec version, we didn't implement @@toPrimitive case
	// and the second argument - flag - preferred type is a string

	var _toPrimitive = function _toPrimitive(it, S) {
	  if (!_isObject(it)) return it;
	  var fn, val;
	  if (S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
	  if (typeof (fn = it.valueOf) == 'function' && !_isObject(val = fn.call(it))) return val;
	  if (!S && typeof (fn = it.toString) == 'function' && !_isObject(val = fn.call(it))) return val;
	  throw TypeError("Can't convert object to primitive value");
	};

	var dP = Object.defineProperty;
	var f$1 = _descriptors ? Object.defineProperty : function defineProperty(O, P, Attributes) {
	  _anObject(O);
	  P = _toPrimitive(P, true);
	  _anObject(Attributes);
	  if (_ie8DomDefine) try {
	    return dP(O, P, Attributes);
	  } catch (e) {
	    /* empty */
	  }
	  if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!');
	  if ('value' in Attributes) O[P] = Attributes.value;
	  return O;
	};
	var _objectDp = {
	  f: f$1
	};

	var _hide = _descriptors ? function (object, key, value) {
	  return _objectDp.f(object, key, _propertyDesc(1, value));
	} : function (object, key, value) {
	  object[key] = value;
	  return object;
	};

	var hasOwnProperty = {}.hasOwnProperty;

	var _has = function _has(it, key) {
	  return hasOwnProperty.call(it, key);
	};

	var _redefine = createCommonjsModule(function (module) {
	  var SRC = _uid('src');
	  var TO_STRING = 'toString';
	  var $toString = Function[TO_STRING];
	  var TPL = ('' + $toString).split(TO_STRING);

	  _core.inspectSource = function (it) {
	    return $toString.call(it);
	  };

	  (module.exports = function (O, key, val, safe) {
	    var isFunction = typeof val == 'function';
	    if (isFunction) _has(val, 'name') || _hide(val, 'name', key);
	    if (O[key] === val) return;
	    if (isFunction) _has(val, SRC) || _hide(val, SRC, O[key] ? '' + O[key] : TPL.join(String(key)));

	    if (O === _global) {
	      O[key] = val;
	    } else if (!safe) {
	      delete O[key];
	      _hide(O, key, val);
	    } else if (O[key]) {
	      O[key] = val;
	    } else {
	      _hide(O, key, val);
	    } // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative

	  })(Function.prototype, TO_STRING, function toString() {
	    return typeof this == 'function' && this[SRC] || $toString.call(this);
	  });
	});

	// 7.2.1 RequireObjectCoercible(argument)
	var _defined = function _defined(it) {
	  if (it == undefined) throw TypeError("Can't call method on  " + it);
	  return it;
	};

	var _fixReWks = function _fixReWks(KEY, length, exec) {
	  var SYMBOL = _wks(KEY);
	  var fns = exec(_defined, SYMBOL, ''[KEY]);
	  var strfn = fns[0];
	  var rxfn = fns[1];

	  if (_fails(function () {
	    var O = {};

	    O[SYMBOL] = function () {
	      return 7;
	    };

	    return ''[KEY](O) != 7;
	  })) {
	    _redefine(String.prototype, KEY, strfn);
	    _hide(RegExp.prototype, SYMBOL, length == 2 // 21.2.5.8 RegExp.prototype[@@replace](string, replaceValue)
	    // 21.2.5.11 RegExp.prototype[@@split](string, limit)
	    ? function (string, arg) {
	      return rxfn.call(string, this, arg);
	    } // 21.2.5.6 RegExp.prototype[@@match](string)
	    // 21.2.5.9 RegExp.prototype[@@search](string)
	    : function (string) {
	      return rxfn.call(string, this);
	    });
	  }
	};

	_fixReWks('replace', 2, function (defined, REPLACE, $replace) {
	  // 21.1.3.14 String.prototype.replace(searchValue, replaceValue)
	  return [function replace(searchValue, replaceValue) {

	    var O = defined(this);
	    var fn = searchValue == undefined ? undefined : searchValue[REPLACE];
	    return fn !== undefined ? fn.call(searchValue, O, replaceValue) : $replace.call(String(O), searchValue, replaceValue);
	  }, $replace];
	});

	var toString = {}.toString;

	var _cof = function _cof(it) {
	  return toString.call(it).slice(8, -1);
	};

	var MATCH = _wks('match');

	var _isRegexp = function _isRegexp(it) {
	  var isRegExp;
	  return _isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : _cof(it) == 'RegExp');
	};

	_fixReWks('split', 2, function (defined, SPLIT, $split) {

	  var isRegExp = _isRegexp;
	  var _split = $split;
	  var $push = [].push;
	  var $SPLIT = 'split';
	  var LENGTH = 'length';
	  var LAST_INDEX = 'lastIndex';

	  if ('abbc'[$SPLIT](/(b)*/)[1] == 'c' || 'test'[$SPLIT](/(?:)/, -1)[LENGTH] != 4 || 'ab'[$SPLIT](/(?:ab)*/)[LENGTH] != 2 || '.'[$SPLIT](/(.?)(.?)/)[LENGTH] != 4 || '.'[$SPLIT](/()()/)[LENGTH] > 1 || ''[$SPLIT](/.?/)[LENGTH]) {
	    var NPCG = /()??/.exec('')[1] === undefined; // nonparticipating capturing group
	    // based on es5-shim implementation, need to rework it

	    $split = function $split(separator, limit) {
	      var string = String(this);
	      if (separator === undefined && limit === 0) return []; // If `separator` is not a regex, use native split

	      if (!isRegExp(separator)) return _split.call(string, separator, limit);
	      var output = [];
	      var flags = (separator.ignoreCase ? 'i' : '') + (separator.multiline ? 'm' : '') + (separator.unicode ? 'u' : '') + (separator.sticky ? 'y' : '');
	      var lastLastIndex = 0;
	      var splitLimit = limit === undefined ? 4294967295 : limit >>> 0; // Make `global` and avoid `lastIndex` issues by working with a copy

	      var separatorCopy = new RegExp(separator.source, flags + 'g');
	      var separator2, match, lastIndex, lastLength, i; // Doesn't need flags gy, but they don't hurt

	      if (!NPCG) separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\s)', flags);

	      while (match = separatorCopy.exec(string)) {
	        // `separatorCopy.lastIndex` is not reliable cross-browser
	        lastIndex = match.index + match[0][LENGTH];

	        if (lastIndex > lastLastIndex) {
	          output.push(string.slice(lastLastIndex, match.index)); // Fix browsers whose `exec` methods don't consistently return `undefined` for NPCG
	          // eslint-disable-next-line no-loop-func

	          if (!NPCG && match[LENGTH] > 1) match[0].replace(separator2, function () {
	            for (i = 1; i < arguments[LENGTH] - 2; i++) if (arguments[i] === undefined) match[i] = undefined;
	          });
	          if (match[LENGTH] > 1 && match.index < string[LENGTH]) $push.apply(output, match.slice(1));
	          lastLength = match[0][LENGTH];
	          lastLastIndex = lastIndex;
	          if (output[LENGTH] >= splitLimit) break;
	        }

	        if (separatorCopy[LAST_INDEX] === match.index) separatorCopy[LAST_INDEX]++; // Avoid an infinite loop
	      }

	      if (lastLastIndex === string[LENGTH]) {
	        if (lastLength || !separatorCopy.test('')) output.push('');
	      } else output.push(string.slice(lastLastIndex));

	      return output[LENGTH] > splitLimit ? output.slice(0, splitLimit) : output;
	    }; // Chakra, V8

	  } else if ('0'[$SPLIT](undefined, 0)[LENGTH]) {
	    $split = function $split(separator, limit) {
	      return separator === undefined && limit === 0 ? [] : _split.call(this, separator, limit);
	    };
	  } // 21.1.3.17 String.prototype.split(separator, limit)


	  return [function split(separator, limit) {
	    var O = defined(this);
	    var fn = separator == undefined ? undefined : separator[SPLIT];
	    return fn !== undefined ? fn.call(separator, O, limit) : $split.call(String(O), separator, limit);
	  }, $split];
	});

	// eslint-disable-next-line no-prototype-builtins

	var _iobject = Object('z').propertyIsEnumerable(0) ? Object : function (it) {
	  return _cof(it) == 'String' ? it.split('') : Object(it);
	};

	var _toIobject = function _toIobject(it) {
	  return _iobject(_defined(it));
	};

	var gOPD = Object.getOwnPropertyDescriptor;
	var f$2 = _descriptors ? gOPD : function getOwnPropertyDescriptor(O, P) {
	  O = _toIobject(O);
	  P = _toPrimitive(P, true);
	  if (_ie8DomDefine) try {
	    return gOPD(O, P);
	  } catch (e) {
	    /* empty */
	  }
	  if (_has(O, P)) return _propertyDesc(!_objectPie.f.call(O, P), O[P]);
	};
	var _objectGopd = {
	  f: f$2
	};

	/* eslint-disable no-proto */

	var check = function check(O, proto) {
	  _anObject(O);
	  if (!_isObject(proto) && proto !== null) throw TypeError(proto + ": can't set as prototype!");
	};

	var _setProto = {
	  set: Object.setPrototypeOf || ('__proto__' in {} ? // eslint-disable-line
	  function (test, buggy, set) {
	    try {
	      set = _ctx(Function.call, _objectGopd.f(Object.prototype, '__proto__').set, 2);
	      set(test, []);
	      buggy = !(test instanceof Array);
	    } catch (e) {
	      buggy = true;
	    }

	    return function setPrototypeOf(O, proto) {
	      check(O, proto);
	      if (buggy) O.__proto__ = proto;else set(O, proto);
	      return O;
	    };
	  }({}, false) : undefined),
	  check: check
	};

	var setPrototypeOf = _setProto.set;

	var _inheritIfRequired = function _inheritIfRequired(that, target, C) {
	  var S = target.constructor;
	  var P;

	  if (S !== C && typeof S == 'function' && (P = S.prototype) !== C.prototype && _isObject(P) && setPrototypeOf) {
	    setPrototypeOf(that, P);
	  }

	  return that;
	};

	// 7.1.4 ToInteger
	var ceil = Math.ceil;
	var floor = Math.floor;

	var _toInteger = function _toInteger(it) {
	  return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it);
	};

	var min = Math.min;

	var _toLength = function _toLength(it) {
	  return it > 0 ? min(_toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991
	};

	var max = Math.max;
	var min$1 = Math.min;

	var _toAbsoluteIndex = function _toAbsoluteIndex(index, length) {
	  index = _toInteger(index);
	  return index < 0 ? max(index + length, 0) : min$1(index, length);
	};

	// true  -> Array#includes

	var _arrayIncludes = function _arrayIncludes(IS_INCLUDES) {
	  return function ($this, el, fromIndex) {
	    var O = _toIobject($this);
	    var length = _toLength(O.length);
	    var index = _toAbsoluteIndex(fromIndex, length);
	    var value; // Array#includes uses SameValueZero equality algorithm
	    // eslint-disable-next-line no-self-compare

	    if (IS_INCLUDES && el != el) while (length > index) {
	      value = O[index++]; // eslint-disable-next-line no-self-compare

	      if (value != value) return true; // Array#indexOf ignores holes, Array#includes - not
	    } else for (; length > index; index++) if (IS_INCLUDES || index in O) {
	      if (O[index] === el) return IS_INCLUDES || index || 0;
	    }
	    return !IS_INCLUDES && -1;
	  };
	};

	var shared = _shared('keys');

	var _sharedKey = function _sharedKey(key) {
	  return shared[key] || (shared[key] = _uid(key));
	};

	var arrayIndexOf = _arrayIncludes(false);
	var IE_PROTO = _sharedKey('IE_PROTO');

	var _objectKeysInternal = function _objectKeysInternal(object, names) {
	  var O = _toIobject(object);
	  var i = 0;
	  var result = [];
	  var key;

	  for (key in O) if (key != IE_PROTO) _has(O, key) && result.push(key); // Don't enum bug & hidden keys


	  while (names.length > i) if (_has(O, key = names[i++])) {
	    ~arrayIndexOf(result, key) || result.push(key);
	  }

	  return result;
	};

	// IE 8- don't enum bug keys
	var _enumBugKeys = 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf'.split(',');

	var hiddenKeys = _enumBugKeys.concat('length', 'prototype');

	var f$3 = Object.getOwnPropertyNames || function getOwnPropertyNames(O) {
	  return _objectKeysInternal(O, hiddenKeys);
	};

	var _objectGopn = {
	  f: f$3
	};

	var _flags = function _flags() {
	  var that = _anObject(this);
	  var result = '';
	  if (that.global) result += 'g';
	  if (that.ignoreCase) result += 'i';
	  if (that.multiline) result += 'm';
	  if (that.unicode) result += 'u';
	  if (that.sticky) result += 'y';
	  return result;
	};

	var SPECIES = _wks('species');

	var _setSpecies = function _setSpecies(KEY) {
	  var C = _global[KEY];
	  if (_descriptors && C && !C[SPECIES]) _objectDp.f(C, SPECIES, {
	    configurable: true,
	    get: function get() {
	      return this;
	    }
	  });
	};

	var dP$1 = _objectDp.f;
	var gOPN = _objectGopn.f;
	var $RegExp = _global.RegExp;
	var Base = $RegExp;
	var proto = $RegExp.prototype;
	var re1 = /a/g;
	var re2 = /a/g; // "new" creates a new object, old webkit buggy here

	var CORRECT_NEW = new $RegExp(re1) !== re1;

	if (_descriptors && (!CORRECT_NEW || _fails(function () {
	  re2[_wks('match')] = false; // RegExp constructor can alter flags and IsRegExp works correct with @@match

	  return $RegExp(re1) != re1 || $RegExp(re2) == re2 || $RegExp(re1, 'i') != '/a/i';
	}))) {
	  $RegExp = function RegExp(p, f) {
	    var tiRE = this instanceof $RegExp;
	    var piRE = _isRegexp(p);
	    var fiU = f === undefined;
	    return !tiRE && piRE && p.constructor === $RegExp && fiU ? p : _inheritIfRequired(CORRECT_NEW ? new Base(piRE && !fiU ? p.source : p, f) : Base((piRE = p instanceof $RegExp) ? p.source : p, piRE && fiU ? _flags.call(p) : f), tiRE ? this : proto, $RegExp);
	  };

	  var proxy = function proxy(key) {
	    key in $RegExp || dP$1($RegExp, key, {
	      configurable: true,
	      get: function get() {
	        return Base[key];
	      },
	      set: function set(it) {
	        Base[key] = it;
	      }
	    });
	  };

	  for (var keys = gOPN(Base), i = 0; keys.length > i;) proxy(keys[i++]);

	  proto.constructor = $RegExp;
	  $RegExp.prototype = proto;
	  _redefine(_global, 'RegExp', $RegExp);
	}

	_setSpecies('RegExp');

	if (_descriptors && /./g.flags != 'g') _objectDp.f(RegExp.prototype, 'flags', {
	  configurable: true,
	  get: _flags
	});

	var TO_STRING = 'toString';
	var $toString = /./[TO_STRING];

	var define = function define(fn) {
	  _redefine(RegExp.prototype, TO_STRING, fn, true);
	}; // 21.2.5.14 RegExp.prototype.toString()


	if (_fails(function () {
	  return $toString.call({
	    source: 'a',
	    flags: 'b'
	  }) != '/a/b';
	})) {
	  define(function toString() {
	    var R = _anObject(this);
	    return '/'.concat(R.source, '/', 'flags' in R ? R.flags : !_descriptors && R instanceof RegExp ? _flags.call(R) : undefined);
	  }); // FF44- RegExp#toString has a wrong name
	} else if ($toString.name != TO_STRING) {
	  define(function toString() {
	    return $toString.call(this);
	  });
	}

	// var id = 0;
	// var px = Math.random();

	// var _uid = function _uid(key) {
	//   return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));
	// };

	// var _wks = createCommonjsModule(function (module) {
	//   var store = _shared('wks');
	//   var Symbol = _global.Symbol;
	//   var USE_SYMBOL = typeof Symbol == 'function';

	//   var $exports = module.exports = function (name) {
	//     return store[name] || (store[name] = USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : _uid)('Symbol.' + name));
	//   };

	//   $exports.store = store;
	// });

	var UNSCOPABLES = _wks('unscopables');
	var ArrayProto = Array.prototype;
	if (ArrayProto[UNSCOPABLES] == undefined) _hide(ArrayProto, UNSCOPABLES, {});

	var _addToUnscopables = function _addToUnscopables(key) {
	  ArrayProto[UNSCOPABLES][key] = true;
	};

	var _iterStep = function _iterStep(done, value) {
	  return {
	    value: value,
	    done: !!done
	  };
	};

	var _iterators = {};

	var PROTOTYPE = 'prototype';

	var $export = function $export(type, name, source) {
	  var IS_FORCED = type & $export.F;
	  var IS_GLOBAL = type & $export.G;
	  var IS_STATIC = type & $export.S;
	  var IS_PROTO = type & $export.P;
	  var IS_BIND = type & $export.B;
	  var target = IS_GLOBAL ? _global : IS_STATIC ? _global[name] || (_global[name] = {}) : (_global[name] || {})[PROTOTYPE];
	  var exports = IS_GLOBAL ? _core : _core[name] || (_core[name] = {});
	  var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});
	  var key, own, out, exp;
	  if (IS_GLOBAL) source = name;

	  for (key in source) {
	    // contains in native
	    own = !IS_FORCED && target && target[key] !== undefined; // export native or passed

	    out = (own ? target : source)[key]; // bind timers to global for call from export context

	    exp = IS_BIND && own ? _ctx(out, _global) : IS_PROTO && typeof out == 'function' ? _ctx(Function.call, out) : out; // extend global

	    if (target) _redefine(target, key, out, type & $export.U); // export

	    if (exports[key] != out) _hide(exports, key, exp);
	    if (IS_PROTO && expProto[key] != out) expProto[key] = out;
	  }
	};

	_global.core = _core; // type bitmap

	$export.F = 1; // forced

	$export.G = 2; // global

	$export.S = 4; // static

	$export.P = 8; // proto

	$export.B = 16; // bind

	$export.W = 32; // wrap

	$export.U = 64; // safe

	$export.R = 128; // real proto method for `library`

	var _export = $export;

	var _objectKeys = Object.keys || function keys(O) {
	  return _objectKeysInternal(O, _enumBugKeys);
	};

	var _objectDps = _descriptors ? Object.defineProperties : function defineProperties(O, Properties) {
	  _anObject(O);
	  var keys = _objectKeys(Properties);
	  var length = keys.length;
	  var i = 0;
	  var P;

	  while (length > i) _objectDp.f(O, P = keys[i++], Properties[P]);

	  return O;
	};

	var document$1 = _global.document;

	var _html = document$1 && document$1.documentElement;

	var IE_PROTO$1 = _sharedKey('IE_PROTO');

	var Empty = function Empty() {
	  /* empty */
	};

	var PROTOTYPE$1 = 'prototype'; // Create object with fake `null` prototype: use iframe Object with cleared prototype

	var _createDict = function createDict() {
	  // Thrash, waste and sodomy: IE GC bug
	  var iframe = _domCreate('iframe');
	  var i = _enumBugKeys.length;
	  var lt = '<';
	  var gt = '>';
	  var iframeDocument;
	  iframe.style.display = 'none';
	  _html.appendChild(iframe);
	  iframe.src = 'javascript:'; // eslint-disable-line no-script-url
	  // createDict = iframe.contentWindow.Object;
	  // html.removeChild(iframe);

	  iframeDocument = iframe.contentWindow.document;
	  iframeDocument.open();
	  iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt);
	  iframeDocument.close();
	  _createDict = iframeDocument.F;

	  while (i--) delete _createDict[PROTOTYPE$1][_enumBugKeys[i]];

	  return _createDict();
	};

	var _objectCreate = Object.create || function create(O, Properties) {
	  var result;

	  if (O !== null) {
	    Empty[PROTOTYPE$1] = _anObject(O);
	    result = new Empty();
	    Empty[PROTOTYPE$1] = null; // add "__proto__" for Object.getPrototypeOf polyfill

	    result[IE_PROTO$1] = O;
	  } else result = _createDict();

	  return Properties === undefined ? result : _objectDps(result, Properties);
	};

	var def = _objectDp.f;
	var TAG = _wks('toStringTag');

	var _setToStringTag = function _setToStringTag(it, tag, stat) {
	  if (it && !_has(it = stat ? it : it.prototype, TAG)) def(it, TAG, {
	    configurable: true,
	    value: tag
	  });
	};

	var IteratorPrototype = {}; // 25.1.2.1.1 %IteratorPrototype%[@@iterator]()

	_hide(IteratorPrototype, _wks('iterator'), function () {
	  return this;
	});

	var _iterCreate = function _iterCreate(Constructor, NAME, next) {
	  Constructor.prototype = _objectCreate(IteratorPrototype, {
	    next: _propertyDesc(1, next)
	  });
	  _setToStringTag(Constructor, NAME + ' Iterator');
	};

	var _toObject = function _toObject(it) {
	  return Object(_defined(it));
	};

	var IE_PROTO$2 = _sharedKey('IE_PROTO');
	var ObjectProto = Object.prototype;

	var _objectGpo = Object.getPrototypeOf || function (O) {
	  O = _toObject(O);
	  if (_has(O, IE_PROTO$2)) return O[IE_PROTO$2];

	  if (typeof O.constructor == 'function' && O instanceof O.constructor) {
	    return O.constructor.prototype;
	  }

	  return O instanceof Object ? ObjectProto : null;
	};

	var ITERATOR = _wks('iterator');
	var BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next`

	var FF_ITERATOR = '@@iterator';
	var KEYS = 'keys';
	var VALUES = 'values';

	var returnThis = function returnThis() {
	  return this;
	};

	var _iterDefine = function _iterDefine(Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) {
	  _iterCreate(Constructor, NAME, next);

	  var getMethod = function getMethod(kind) {
	    if (!BUGGY && kind in proto) return proto[kind];

	    switch (kind) {
	      case KEYS:
	        return function keys() {
	          return new Constructor(this, kind);
	        };

	      case VALUES:
	        return function values() {
	          return new Constructor(this, kind);
	        };
	    }

	    return function entries() {
	      return new Constructor(this, kind);
	    };
	  };

	  var TAG = NAME + ' Iterator';
	  var DEF_VALUES = DEFAULT == VALUES;
	  var VALUES_BUG = false;
	  var proto = Base.prototype;
	  var $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT];
	  var $default = $native || getMethod(DEFAULT);
	  var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined;
	  var $anyNative = NAME == 'Array' ? proto.entries || $native : $native;
	  var methods, key, IteratorPrototype; // Fix native

	  if ($anyNative) {
	    IteratorPrototype = _objectGpo($anyNative.call(new Base()));

	    if (IteratorPrototype !== Object.prototype && IteratorPrototype.next) {
	      // Set @@toStringTag to native iterators
	      _setToStringTag(IteratorPrototype, TAG, true); // fix for some old engines

	      if (!_library && typeof IteratorPrototype[ITERATOR] != 'function') _hide(IteratorPrototype, ITERATOR, returnThis);
	    }
	  } // fix Array#{values, @@iterator}.name in V8 / FF


	  if (DEF_VALUES && $native && $native.name !== VALUES) {
	    VALUES_BUG = true;

	    $default = function values() {
	      return $native.call(this);
	    };
	  } // Define iterator


	  if ((!_library || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])) {
	    _hide(proto, ITERATOR, $default);
	  } // Plug for library


	  _iterators[NAME] = $default;
	  _iterators[TAG] = returnThis;

	  if (DEFAULT) {
	    methods = {
	      values: DEF_VALUES ? $default : getMethod(VALUES),
	      keys: IS_SET ? $default : getMethod(KEYS),
	      entries: $entries
	    };
	    if (FORCED) for (key in methods) {
	      if (!(key in proto)) _redefine(proto, key, methods[key]);
	    } else _export(_export.P + _export.F * (BUGGY || VALUES_BUG), NAME, methods);
	  }

	  return methods;
	};

	// 22.1.3.13 Array.prototype.keys()
	// 22.1.3.29 Array.prototype.values()
	// 22.1.3.30 Array.prototype[@@iterator]()


	var es6_array_iterator = _iterDefine(Array, 'Array', function (iterated, kind) {
	  this._t = _toIobject(iterated); // target

	  this._i = 0; // next index

	  this._k = kind; // kind
	  // 22.1.5.2.1 %ArrayIteratorPrototype%.next()
	}, function () {
	  var O = this._t;
	  var kind = this._k;
	  var index = this._i++;

	  if (!O || index >= O.length) {
	    this._t = undefined;
	    return _iterStep(1);
	  }

	  if (kind == 'keys') return _iterStep(0, index);
	  if (kind == 'values') return _iterStep(0, O[index]);
	  return _iterStep(0, [index, O[index]]);
	}, 'values'); // argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7)

	_iterators.Arguments = _iterators.Array;
	_addToUnscopables('keys');
	_addToUnscopables('values');
	_addToUnscopables('entries');

	var ITERATOR$1 = _wks('iterator');
	var TO_STRING_TAG = _wks('toStringTag');
	var ArrayValues = _iterators.Array;
	var DOMIterables = {
	  CSSRuleList: true,
	  // TODO: Not spec compliant, should be false.
	  CSSStyleDeclaration: false,
	  CSSValueList: false,
	  ClientRectList: false,
	  DOMRectList: false,
	  DOMStringList: false,
	  DOMTokenList: true,
	  DataTransferItemList: false,
	  FileList: false,
	  HTMLAllCollection: false,
	  HTMLCollection: false,
	  HTMLFormElement: false,
	  HTMLSelectElement: false,
	  MediaList: true,
	  // TODO: Not spec compliant, should be false.
	  MimeTypeArray: false,
	  NamedNodeMap: false,
	  NodeList: true,
	  PaintRequestList: false,
	  Plugin: false,
	  PluginArray: false,
	  SVGLengthList: false,
	  SVGNumberList: false,
	  SVGPathSegList: false,
	  SVGPointList: false,
	  SVGStringList: false,
	  SVGTransformList: false,
	  SourceBufferList: false,
	  StyleSheetList: true,
	  // TODO: Not spec compliant, should be false.
	  TextTrackCueList: false,
	  TextTrackList: false,
	  TouchList: false
	};

	for (var collections = _objectKeys(DOMIterables), i$1 = 0; i$1 < collections.length; i$1++) {
	  var NAME = collections[i$1];
	  var explicit = DOMIterables[NAME];
	  var Collection = _global[NAME];
	  var proto$1 = Collection && Collection.prototype;
	  var key;

	  if (proto$1) {
	    if (!proto$1[ITERATOR$1]) _hide(proto$1, ITERATOR$1, ArrayValues);
	    if (!proto$1[TO_STRING_TAG]) _hide(proto$1, TO_STRING_TAG, NAME);
	    _iterators[NAME] = ArrayValues;
	    if (explicit) for (key in es6_array_iterator) if (!proto$1[key]) _redefine(proto$1, key, es6_array_iterator[key], true);
	  }
	}

	var domain; // This constructor is used to store event handlers. Instantiating this is
	// faster than explicitly calling `Object.create(null)` to get a "clean" empty
	// object (tested with v8 v4.9).

	function EventHandlers() {}

	EventHandlers.prototype = Object.create(null);

	function EventEmitter() {
	  EventEmitter.init.call(this);
	}
	// require('events') === require('events').EventEmitter

	EventEmitter.EventEmitter = EventEmitter;
	EventEmitter.usingDomains = false;
	EventEmitter.prototype.domain = undefined;
	EventEmitter.prototype._events = undefined;
	EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are
	// added to it. This is a useful default which helps finding memory leaks.

	EventEmitter.defaultMaxListeners = 10;

	EventEmitter.init = function () {
	  this.domain = null;

	  if (EventEmitter.usingDomains) {
	    // if there is an active domain, then attach to it.
	    if (domain.active && !(this instanceof domain.Domain)) ;
	  }

	  if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
	    this._events = new EventHandlers();
	    this._eventsCount = 0;
	  }

	  this._maxListeners = this._maxListeners || undefined;
	}; // Obviously not all Emitters should be limited to 10. This function allows
	// that to be increased. Set to zero for unlimited.


	EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
	  if (typeof n !== 'number' || n < 0 || isNaN(n)) throw new TypeError('"n" argument must be a positive number');
	  this._maxListeners = n;
	  return this;
	};

	function $getMaxListeners(that) {
	  if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners;
	  return that._maxListeners;
	}

	EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
	  return $getMaxListeners(this);
	}; // These standalone emit* functions are used to optimize calling of event
	// handlers for fast cases because emit() itself often has a variable number of
	// arguments and can be deoptimized because of that. These functions always have
	// the same number of arguments and thus do not get deoptimized, so the code
	// inside them can execute faster.


	function emitNone(handler, isFn, self) {
	  if (isFn) handler.call(self);else {
	    var len = handler.length;
	    var listeners = arrayClone(handler, len);

	    for (var i = 0; i < len; ++i) listeners[i].call(self);
	  }
	}

	function emitOne(handler, isFn, self, arg1) {
	  if (isFn) handler.call(self, arg1);else {
	    var len = handler.length;
	    var listeners = arrayClone(handler, len);

	    for (var i = 0; i < len; ++i) listeners[i].call(self, arg1);
	  }
	}

	function emitTwo(handler, isFn, self, arg1, arg2) {
	  if (isFn) handler.call(self, arg1, arg2);else {
	    var len = handler.length;
	    var listeners = arrayClone(handler, len);

	    for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2);
	  }
	}

	function emitThree(handler, isFn, self, arg1, arg2, arg3) {
	  if (isFn) handler.call(self, arg1, arg2, arg3);else {
	    var len = handler.length;
	    var listeners = arrayClone(handler, len);

	    for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2, arg3);
	  }
	}

	function emitMany(handler, isFn, self, args) {
	  if (isFn) handler.apply(self, args);else {
	    var len = handler.length;
	    var listeners = arrayClone(handler, len);

	    for (var i = 0; i < len; ++i) listeners[i].apply(self, args);
	  }
	}

	EventEmitter.prototype.emit = function emit(type) {
	  var er, handler, len, args, i, events, domain;
	  var doError = type === 'error';
	  events = this._events;
	  if (events) doError = doError && events.error == null;else if (!doError) return false;
	  domain = this.domain; // If there is no 'error' event listener then throw.

	  if (doError) {
	    er = arguments[1];

	    if (domain) {
	      if (!er) er = new Error('Uncaught, unspecified "error" event');
	      er.domainEmitter = this;
	      er.domain = domain;
	      er.domainThrown = false;
	      domain.emit('error', er);
	    } else if (er instanceof Error) {
	      throw er; // Unhandled 'error' event
	    } else {
	      // At least give some kind of context to the user
	      var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
	      err.context = er;
	      throw err;
	    }

	    return false;
	  }

	  handler = events[type];
	  if (!handler) return false;
	  var isFn = typeof handler === 'function';
	  len = arguments.length;

	  switch (len) {
	    // fast cases
	    case 1:
	      emitNone(handler, isFn, this);
	      break;

	    case 2:
	      emitOne(handler, isFn, this, arguments[1]);
	      break;

	    case 3:
	      emitTwo(handler, isFn, this, arguments[1], arguments[2]);
	      break;

	    case 4:
	      emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
	      break;
	    // slower

	    default:
	      args = new Array(len - 1);

	      for (i = 1; i < len; i++) args[i - 1] = arguments[i];

	      emitMany(handler, isFn, this, args);
	  }
	  return true;
	};

	function _addListener(target, type, listener, prepend) {
	  var m;
	  var events;
	  var existing;
	  if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');
	  events = target._events;

	  if (!events) {
	    events = target._events = new EventHandlers();
	    target._eventsCount = 0;
	  } else {
	    // To avoid recursion in the case that type === "newListener"! Before
	    // adding it to the listeners, first emit "newListener".
	    if (events.newListener) {
	      target.emit('newListener', type, listener.listener ? listener.listener : listener); // Re-assign `events` because a newListener handler could have caused the
	      // this._events to be assigned to a new object

	      events = target._events;
	    }

	    existing = events[type];
	  }

	  if (!existing) {
	    // Optimize the case of one listener. Don't need the extra array object.
	    existing = events[type] = listener;
	    ++target._eventsCount;
	  } else {
	    if (typeof existing === 'function') {
	      // Adding the second element, need to change to array.
	      existing = events[type] = prepend ? [listener, existing] : [existing, listener];
	    } else {
	      // If we've already got an array, just append.
	      if (prepend) {
	        existing.unshift(listener);
	      } else {
	        existing.push(listener);
	      }
	    } // Check for listener leak


	    if (!existing.warned) {
	      m = $getMaxListeners(target);

	      if (m && m > 0 && existing.length > m) {
	        existing.warned = true;
	        var w = new Error('Possible EventEmitter memory leak detected. ' + existing.length + ' ' + type + ' listeners added. ' + 'Use emitter.setMaxListeners() to increase limit');
	        w.name = 'MaxListenersExceededWarning';
	        w.emitter = target;
	        w.type = type;
	        w.count = existing.length;
	        emitWarning(w);
	      }
	    }
	  }

	  return target;
	}

	function emitWarning(e) {
	  typeof console.warn === 'function' ? console.warn(e) : console.log(e);
	}

	EventEmitter.prototype.addListener = function addListener(type, listener) {
	  return _addListener(this, type, listener, false);
	};

	EventEmitter.prototype.on = EventEmitter.prototype.addListener;

	EventEmitter.prototype.prependListener = function prependListener(type, listener) {
	  return _addListener(this, type, listener, true);
	};

	function _onceWrap(target, type, listener) {
	  var fired = false;

	  function g() {
	    target.removeListener(type, g);

	    if (!fired) {
	      fired = true;
	      listener.apply(target, arguments);
	    }
	  }

	  g.listener = listener;
	  return g;
	}

	EventEmitter.prototype.once = function once(type, listener) {
	  if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');
	  this.on(type, _onceWrap(this, type, listener));
	  return this;
	};

	EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
	  if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');
	  this.prependListener(type, _onceWrap(this, type, listener));
	  return this;
	}; // emits a 'removeListener' event iff the listener was removed


	EventEmitter.prototype.removeListener = function removeListener(type, listener) {
	  var list, events, position, i, originalListener;
	  if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');
	  events = this._events;
	  if (!events) return this;
	  list = events[type];
	  if (!list) return this;

	  if (list === listener || list.listener && list.listener === listener) {
	    if (--this._eventsCount === 0) this._events = new EventHandlers();else {
	      delete events[type];
	      if (events.removeListener) this.emit('removeListener', type, list.listener || listener);
	    }
	  } else if (typeof list !== 'function') {
	    position = -1;

	    for (i = list.length; i-- > 0;) {
	      if (list[i] === listener || list[i].listener && list[i].listener === listener) {
	        originalListener = list[i].listener;
	        position = i;
	        break;
	      }
	    }

	    if (position < 0) return this;

	    if (list.length === 1) {
	      list[0] = undefined;

	      if (--this._eventsCount === 0) {
	        this._events = new EventHandlers();
	        return this;
	      } else {
	        delete events[type];
	      }
	    } else {
	      spliceOne(list, position);
	    }

	    if (events.removeListener) this.emit('removeListener', type, originalListener || listener);
	  }

	  return this;
	};

	EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
	  var listeners, events;
	  events = this._events;
	  if (!events) return this; // not listening for removeListener, no need to emit

	  if (!events.removeListener) {
	    if (arguments.length === 0) {
	      this._events = new EventHandlers();
	      this._eventsCount = 0;
	    } else if (events[type]) {
	      if (--this._eventsCount === 0) this._events = new EventHandlers();else delete events[type];
	    }

	    return this;
	  } // emit removeListener for all listeners on all events


	  if (arguments.length === 0) {
	    var keys = Object.keys(events);

	    for (var i = 0, key; i < keys.length; ++i) {
	      key = keys[i];
	      if (key === 'removeListener') continue;
	      this.removeAllListeners(key);
	    }

	    this.removeAllListeners('removeListener');
	    this._events = new EventHandlers();
	    this._eventsCount = 0;
	    return this;
	  }

	  listeners = events[type];

	  if (typeof listeners === 'function') {
	    this.removeListener(type, listeners);
	  } else if (listeners) {
	    // LIFO order
	    do {
	      this.removeListener(type, listeners[listeners.length - 1]);
	    } while (listeners[0]);
	  }

	  return this;
	};

	EventEmitter.prototype.listeners = function listeners(type) {
	  var evlistener;
	  var ret;
	  var events = this._events;
	  if (!events) ret = [];else {
	    evlistener = events[type];
	    if (!evlistener) ret = [];else if (typeof evlistener === 'function') ret = [evlistener.listener || evlistener];else ret = unwrapListeners(evlistener);
	  }
	  return ret;
	};

	EventEmitter.listenerCount = function (emitter, type) {
	  if (typeof emitter.listenerCount === 'function') {
	    return emitter.listenerCount(type);
	  } else {
	    return listenerCount.call(emitter, type);
	  }
	};

	EventEmitter.prototype.listenerCount = listenerCount;

	function listenerCount(type) {
	  var events = this._events;

	  if (events) {
	    var evlistener = events[type];

	    if (typeof evlistener === 'function') {
	      return 1;
	    } else if (evlistener) {
	      return evlistener.length;
	    }
	  }

	  return 0;
	}

	EventEmitter.prototype.eventNames = function eventNames() {
	  return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
	}; // About 1.5x faster than the two-arg version of Array#splice().


	function spliceOne(list, index) {
	  for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) list[i] = list[k];

	  list.pop();
	}

	function arrayClone(arr, i) {
	  var copy = new Array(i);

	  while (i--) copy[i] = arr[i];

	  return copy;
	}

	function unwrapListeners(arr) {
	  var ret = new Array(arr.length);

	  for (var i = 0; i < ret.length; ++i) {
	    ret[i] = arr[i].listener || arr[i];
	  }

	  return ret;
	}

	// import debounce from 'lodash/debounce';
	// import moment from 'moment';

	function helloworld() {
	  console.log(React__default);
	  console.log(EventEmitter); // console.log(Component);
	  // console.log(debounce);
	  // console.log(_.throttle);
	  // console.log(moment);

	  console.log('helloworld');
	}

	function helloworld$1() {
	  console.log('helloworld');
	}

	class Apple {
	  constructor({
	    model
	  }) {
	    this.className = 'Apple';
	    this.model = model;
	  }

	  getModel() {
	    return this.model;
	  }

	}

	class Person {
	  constructor({
	    name,
	    age,
	    sex
	  }) {
	    this.className = 'Person';
	    this.name = name;
	    this.age = age;
	    this.sex = sex;
	  }

	  getName() {
	    return this.name;
	  }

	}

	exports.helloworld = helloworld;
	exports.Person = Person;
	exports.Apple = Apple;
	exports.h = helloworld$1;

	Object.defineProperty(exports, '__esModule', { value: true });

})));

表格十字线聚焦效果

腾讯体育的 NBA 直播中的球员数据,会根据鼠标的移动,在鼠标hover那一项的上下左右方向生成十字线,可以让用户更方便的阅读表格。

效果像这样:

看了下腾讯体育该页面的鼠标移动时候,表格上 DOM 和其变化,如图:

他应该是根据鼠标移动的时候拿到的data-coord,根据它的值对应的横竖列的tr设置特殊的className,我觉得这样的做法,会比较依赖data-coord的生成,不过这个也不难,利用后端模板语言,或者前端各种 view 框架也并不难,就算是自己手写也不费事情,但是可不可以不依赖这个data-coord呢?

看下我的实现

Demo 预览:http://codepen.io/lishengzxc/pen/oLzPxa

1. hover的那个<td>高亮
td:hover {
  background-color: red !important;
}

一个css搞定,主要后面的加的!important,这是为了防止td所在的tr的样式覆盖td

2. hover的那行<tr>高亮
tr:hover {
  background-color: yellow;
}

同样一个css搞定。

3. hover的那个td所在的那列高亮

这个需要利用的 JavaScript

table.addEventListener('mouseover', event => {
  var trs = Array.from(event.target.parentNode.parentNode.children)
  var index = [...event.target.parentNode.children].indexOf(event.target)

  trs.forEach(i => Array.from(i.children).forEach(j => j.style.backgroundColor = null))
  trs.forEach(k => k.children[index].style.backgroundColor = 'yellow')
}, false)

table.addEventListener('mouseleave', event => {
  var trs = Array.from(event.target.children)
  trs.forEach(i => Array.from(i.children).forEach(j => j.style.backgroundColor = null))
}, false)

其中在table是表的<tbody>,在其上面对<td>进行事件代理。然后:

  1. 获取到<td>所在表格的所有的<tr>
  2. 获取到<td>所在<tr>中的序号index
  3. 将该表格内的所有<td>的高亮效果取消
  4. 将被hover<td>的所在列高亮,就是将每个<tr>中的第index个高亮

最后,在鼠标离开table的时候,清除所有高亮,具体的高亮<td>和行高亮因为hover的取消,自己没有了hover伪类而取消,列高亮需要通过JavaScript完成(将所有的<td>高亮取消)。

在具体实现中,需要注意的一个细节是mouseovermouseleave这两个事件:

  • mouseovermouseout对应分别是鼠标移动到具体 DOM 上和移出具体 DOM 上,会冒泡。
  • mouseentermouseleave也对应分别是鼠标移动到具体 DOM 上和移出具体 DOM 上,不会冒泡。

因此,如果需要事件代理,就需要mouseovermouseout,反之mouseentermouseleave

上述代码仅是实现一个思路,还能很好的抽象,等业务中需要这么一个组件的话,我想我会抽象写出来的。

其实要是css有父选择器的话,就太好解决了

在 ES6/7 中比`Array.prototype.indexOf`更甜的几个 API

先说遇到的问题:
有这样的数组:[{id: 0}, {id: 1}, {id: 2}, {id: 3}, ...],我需要找出{id: 0},想想 ES5 中似乎只有一个查找的方法indexOf(哦,lastIndexOf从尾巴找),但是因为对象是引用类型,所以{id: 0} === {id: 0}返回的是false,因此[{id: 0}, {id: 1}, {id: 2}, {id: 3}, ...].indexOf({id: 0})-1

那怎么办,似乎可以用forEach

let index
let item
[{id: 0}, {id: 1}, {id: 2}, {id: 3}].forEach((v, i) => {
  if (i.id === 0) {
    item = v
    index = i
  }
})

但是这样还是很奇怪,对不对,原因就是forEach的语义化不够好。

幸运的是,在 ES6 中,数组的新拓展可以解决这类问题:

数组实例的find()和findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

[1, 4, -5, 10].find((n) => n < 0)
// -5

上面代码找出数组中第一个小于0的成员。

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。

[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0

上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。


除此以外在 ES7 中也有一个新方法,可以代替indexOfincludes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于 ES7 ,但 Babel 转码器已经支持。

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, NaN].includes(NaN); // true

该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但数组长度为 3),则会重置为从 0 开始。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。

if (arr.indexOf(el) !== -1) {
  // ...
}

indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于 -1,表达起来不够直观。二是,它内部使用严格相当运算符(===)进行判断,这会导致对NaN的误判。

[NaN].indexOf(NaN)
// -1

includes使用的是不一样的判断算法,就没有这个问题。

[NaN].includes(NaN)
// true

下面代码用来检查当前环境是否支持该方法,如果不支持,部署一个简易的替代版本。

const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(["foo", "bar"], "baz"); // => false

似乎indexOf很受打击,lastIndexOf表示围观。

Service Worker 缓存策略

Cache First & Fallback


self.onfetch = (e) => {
  const cached = caches.match(e.request)
  const fallback = caches.match('offline.html')
  const fetched = fetch(`${e.request.url}?${Date.now()}`)

  e.respondWith(
    cached
      .then(res => res || fetched)
      .catch(_ => fallback)
  )
}

Cache and Network Race (Fastest) & Fallback

self.onfetch = (e) => {
  const cached = caches.match(e.request)
  const fallback = caches.match('offline.html')
  const fetched = fetch(`${e.request.url}?${Date.now()}`)

  e.respondWith(
    Promise.race([fetched.catch(_ => cached), cached])
      .then(resp => resp || fetched)
      .catch(_ => fallback)
  )
}

Stale (or Fastest) While Revalidate

// Stale-W-Revalidate & Fallback
self.onfetch = (e) => {
  const cached = caches.match(e.request)
  const fallback = caches.match('offline.html')
  const fetched = fetch(`${e.request.url}?${Date.now()}`)
  const fetchedCopy = fetched.then(_ => _.clone())

  e.respondWith(
    cached
      .then(res => res || fetched)
      .catch(_ => fallback)
  )

  e.waitUntil(
    Promise.all([fetchedCopy, caches.open('runtime')])
      .then(([resp, cache]) => resp.ok && cache.put(e.request, resp))
      .catch(_ => {/* swallow */})
  )
}
// Fastest-W-Revalidate & Fallback
self.onfetch = (e) => {
  const cached = caches.match(e.request)
  const fallback = caches.match('offline.html')
  const fetched = fetch(`${e.request.url}?${Date.now()}`)
  const fetchedCopy = fetched.then(_ => _.clone())

  e.respondWith(
    Promise.race([fetched.catch(_ => cached), cached])
      .then(resp => resp || fetched)
      .catch(_ => fallback)
  )

  e.waitUntil(
    Promise.all([fetchedCopy, caches.open('runtime')])
      .then(([resp, cache]) => resp.ok && cache.put(e.request, resp))
      .catch(_ => {/* swallow */})
  )
}

Cache then Network (from Pages)


Reference

Three.js 光源

光源

各种光源

Three.js 提供了以下一系列光源,每种光源都有其特殊的行为和用途。

光源名称 描述
AmbientLight ['æmbɪənt] (环境光) 这是最基础的光源,它的颜色会添加到整个场景和所有对象的当前颜色上
PointLight (点光源) 空间中的一个点,朝所有的方向发射的光线
SpotLight (聚光灯光源) 这种光源有聚光效果,类似手电筒
DirectionalLight (平行光) 从这种光源发出的光线可以看作是平行的。例如:太阳光
HemisphereLight ['hemɪsfɪə](半球光) 用来创建更加自然的室外光线,模拟反光面和光线微弱的天空
RectAreaLight(面光源) 使用这种光源可以指定散发光线的平面,可不是空间中的一点
... ...

接下来我们具体看下各光源的用法和效果。

AmbientLight — 影响整个场景的光源

https://threejs.org/docs/index.html#api/lights/AmbientLight

var light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );

构造函数

AmbientLight( color, intensity )

  • color — 光源颜色的RGB数值。
  • intensity -- 光源强度的数值。

这将创建一个具有给定颜色和强度的环境光。

AmbientLight 的光线没有特点的来源,因此它也不会影响阴影的生产,一般来说你不会只是用 AmbientLight,你应该讲它去其他光源一起使用,目的是弱化其他光源造成的阴影或添加一些颜色。

我们在下面这个 demo 中给 AmbientLight 设置了 #0C0C0C 的光源颜色,(这是该颜色的十六进制表示法,如果你对十六进制表示颜色还不是很熟悉,可以参考这个链接十六进制数字表示方法了解它),来弱化对象在地面上产生的生硬的阴影,你可以尝试调整右上角的 dat.GUI 工具调整 AmbientLight 的颜色来观察 AmbientLight 对场景内所有对象的影响。(demo 中的阴影是通过 SpotLight 所产生的

https://lishengzxc.github.io/learning-threejs/chapter-03/01-ambient-light.html

// 修改 AmbientLight 的光源颜色
light.color = new THREE.Color(...);

使用 THREE.Color()

在讲述下一种光源之前,我们要先快速了解下 THREE.Color() 方法。因为当构造 Three.js 中的对象时,通常情况下,我们可以直接使用十六进制字符串(#0c0c0c)或者十六进制数指定颜色。然而,在对象构造完成之后,如要需要更改颜色,你就不得不使用 THREE.Color() 。 https://threejs.org/docs/index.html#api/math/Color

PointLight — 向所有方向发射光的光源

https://threejs.org/docs/index.html#api/lights/PointLight

var light = new THREE.PointLight( 0xff0000, 1, 100 );
light.position.set( 50, 50, 50 );
scene.add( light );

构造函数

PointLight( color, intensity, distance, decay )

  • color — 颜色的RBG数值。
  • intensity — 光强的数值。
  • distance - 光强为0处到光源的距离,0表示无穷大。
  • decay - 沿着光照距离的衰退量

在场景中的特定位置创建一个光源。光线照射在各个方向上(好比一个灯泡)。

PointLight 是需要设定位置,在不同位置下的 PointLight 对其他对象都有着不同的影响,可以查看下面的 demo,除此以外,我们注意下 distance 属性,它的默认值是 0,表示光线的亮度不会随着距离的增加而递减,如果 distance 有具体值,那光线的亮度会在指定的距离处降至 0。我们可以在 demo 中将 intensity 设置比较高,然后设置 distance 为一个大于 0 的比较小的值比如 4。

https://lishengzxc.github.io/learning-threejs/chapter-03/02-point-light.html

其他属性

.power

光功率。
在“物理正确”模式中,表示以“流明(光通量单位)”为单位的光功率。
默认值 = 4PI。

.decay

沿着光照距离的衰退量。
在“物理正确”模式中,decay = 2 将实现现实世界的光衰减。
默认值 = 1。

.shadow

此属性存储用来渲染光照阴影的所有相关信息。

什么是“物理正确”模式:https://threejs.org/docs/index.html#api/renderers/WebGLRenderer.physicallyCorrectLights

SpotLight — 具有锥形效果的光源

https://threejs.org/docs/index.html#api/lights/SpotLight

SpotLight 是很常用的光源,也是我们目前提到的第一个常用的可以产生阴影的光源。

// 白色聚光灯从侧面发光,投射阴影

var spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( 100, 1000, 100 );

spotLight.castShadow = true;

spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;

spotLight.shadow.camera.near = 500;
spotLight.shadow.camera.far = 4000;
spotLight.shadow.camera.fov = 30;

scene.add( spotLight );

构造函数

SpotLight( color, intensity, distance, angle, penumbra, decay )

  • color — 颜色的 RBG 数值。
  • intensity — 光强的数值。
  • distance - 光强为 0 处到光源的距离, 0 表示无穷大。
  • angle - 光线散射角度,最大为 Math.PI / 2。
  • penumbra - 聚光锥的半影衰减百分比。在 0 和 1 之间的值。默认为 0。
  • decay - 沿着光照距离的衰退量。

SpotLight 和 PointLight 的主要区别在于,我们需要为它设置 castShadow 属性为 true(如果我们需要阴影的话),以及它的 target 属性。

.target

聚光灯的焦点位于 target.position 处。

默认值 — (0,0,0)。

注意:目前为了target属性正常工作,它必须是 scene 的一部分,也就是通过如下方法添加到场景中:

.castShadow

如果设置为 true ,光照将投射动态阴影。

警告:该操作是很耗费计算资源的,并且需要精心调整才能正确工作。

默认值 — false.

在下面的这个 demo 中,我们试试设置 SpotLight 一些独有属性,如果把 target 设置为那个蓝色的小球(sphere 对象),那么这个光源就会一直瞄准这个球的中心,即使它在不断移动,当然我们也可以指定 SpotLight 瞄准空间中任意一点,此时我们需要使用 THREE.Object3D() 实例:

var target = new THREE.Object3D();
target.positon = new THREE.Vector3(5, 0, 0);

spotLight.target = target;

我们通过设置 distance 和 angle 设置光锥的形状,比如:

其中,光锥的宽度是通过 angle 和 distance 计算而得出的:

var coneLength = light.distance || 10000;
var coneWidth = coneLength * Math.tan(light / 2) * 2;

一般而言,我们不需要设置这些值,因为它们的默认值都是比较合适的,但是我们如果有需求,是可以方便的通过设置这些创建出一个比如光柱很细和光强递减很快的 SpotLight 。

在介绍下一个光源之前,我们再看看几个和阴影相关的熟悉(最新的 Three.js 已经将所有的阴影熟悉一直到了独立的 shadow 上)。要显示阴影,你需要设置 castShadow 为 true。我们可以通过 shadow.camera.near、shadow.camera.far、shadow.camera.fov 来微调阴影最后的展现形式,其工作原理和 PerspectiveCamera 一样,我们还可以设置 shadow.camera.visible 为 true 来调试它。

关于阴影,我们在开发过程中还有些是我们关心的,比如:

  • 如果阴影有点模糊,我们可以增加 shadow.mapSize.width 和 shadow.mapSize.height,或者调整 shadow.camera.near、shadow.camera.far、shadow.camera.fov 来使得生成阴影的区域紧密包裹着对象
  • renderer.shadowMapEnabled 设置为 true 开启阴影,一些物理上是否接受阴影 receiveShadow 为 true http://blog.csdn.net/jianleking/article/details/54377736

https://lishengzxc.github.io/learning-threejs/chapter-03/03-spot-light.html

DirectionalLight — 模拟远处类似太阳的光源

https://threejs.org/docs/index.html#api/lights/shadows/DirectionalLightShadow

太阳离我们很远,以至于它到达地球的时候几乎每一个光线互相都是平行的,DirectionalLight 和 SpotLight 的主要区别在于,被 DirectionalLight 照射的区域(距离相同的地方)接受到的光强是一样的。

和 SpotLight,你需要设置 position 和 target 来控制光源的方向,在设置 castShadow 为 true 后,来显示阴影。

// 从正上方照射过来的白色平行光,0.5的光强。

var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set( 0, 1, 0 );
scene.add( directionalLight );

因为 DirectionalLight 的平行光特性,所以没有了光锥,取而代之的是一个方块,在这个方块内的对象都可以产生和接受阴影。

HemisphereLight — 更加自然的光照效果

https://threejs.org/docs/#api/lights/HemisphereLight

如果我们需要模拟室外光照,我们可以使用一个 DirectionalLight 来模拟太阳,再添加一个 AmbientLight 来为场景提供基础色,但是这样看上去还是不怎么真实。其实当你在室外的时候,并不是所有的光照都来自上方;很多来自空气的散射,地面的反射,和各种其他物体的反射。HemisphereLight 就是用来处理这个情况的。

var light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 );
scene.add( light );
构造函数

HemisphereLight( skyColor, groundColor, intensity )

  • skyColor — 从天空发出的光线的颜色
  • groundColor — 从地面发出的光线的颜色
  • intensity

下面 demo 展示了 HemisphereLight 效果,可以通过打开或者关闭 HemisphereLight,从而可以观察到对象在 HemisphereLight 会反射来自天空和地面的颜色。

https://lishengzxc.github.io/learning-threejs/chapter-03/05-hemisphere-light.html

RectAreaLight — 一个平面散发出的光线

https://threejs.org/docs/#api/lights/RectAreaLight

在过去,RectAreaLight 并不在 Three.js 的标准库中,而是一个拓展。现在已被 Three.js 直接支持。

var width = 2;
var height = 10;
var rectLight = new THREE.RectAreaLight( 0xffffff, undefined,  width, height );
rectLight.intensity = 70.0;
rectLight.position.set( 5, 5, 0 );
rectLight.rotation.set( -Math.PI / 2, 0, 0 );
scene.add( rectLight )

rectLightHelper = new THREE.RectAreaLightHelper( rectLight );
scene.add( rectLightHelper );
构造函数

RectAreaLight( color, intensity, width, height )

  • color
  • intensity
  • width
  • height

在下面这个 demo 中,我们设置了光源颜色为 #ff0000,光强为 3,和其他光源一样,需要为其设置 position,同时在设置你自己想要的 rotation 来控制光源的方向,从而在 castShadow 为 true 的情况下来影响阴影。第一次使用该光源,你可能感到奇怪,你会发现你放置光源的地方什么都看不到,这是因为你不能看到光源本身,只能看到它发出来的光(阴影或者它对其他对象的影响),所以你可以在光源的相同位置放置一个平面来“显示”出光源本身。

https://lishengzxc.github.io/learning-threejs/chapter-03/06-area-light.html

镜头炫光效果

最后来实现一个镜头炫光效果,例如,当你直接朝向太阳拍照时出现的镜头炫光,虽然在拍照的时候,我们可能要尽量避免出现这种情况情形,但是对于游戏来说,它提供了一种很好的效果,让场景看上去更加的真实。

Three.js 也支持镜头炫光。可以通过实例化 THREE。LensFlare 对象创建镜头炫光。THREE.LensFlare 对象接受如下参数:

  • texture(纹理):决定了炫光的样子
  • size:可以指定炫光的大小。如果指定值为 -1,那就使用纹理本身的大小
  • distance:光源(0)到相机(1)的距离
  • blending(融合效果):默认值为 THREE.AdditiveBlending,为一个半透明的炫光,该属性决定了炫光和其他对象的融合效果
  • color:炫光的颜色

让我们看看如何创建这个对象

var light = new THREE.PointLight( 0xffffff, 1.5, 2000 );

var textureLoader = new THREE.TextureLoader();

var textureFlare = textureLoader.load( "textures/lensflare/lensflare.png" );

var flareColor = new THREE.Color( 0xffffff );
flareColor.setHSL( h, s, l + 0.5 );

var lensFlare = new THREE.LensFlare( textureFlare, 700, 0.0, THREE.AdditiveBlending, flareColor );
lensFlare.position.copy( light.position );

scene.add( lensFlare );

创建完 THREE.LensFlare 对象后,可以将它放在光源处。同时为了更加真实的模拟炫光的效果,我们再增加几个“炫光”,不过我们不需要重新实例化,只需要调用刚刚炫光对象上的 add() 方法就行了。

lensFlare.add(textureFlare, 60, 0.6);
...

https://lishengzxc.github.io/learning-threejs/chapter-03/07-lensflares.html

总结

  • AmbientLight 的颜色可以附件到场景中的每一个颜色上。它没有位置的概念。通常使用它来柔化那些硬生生的颜色和阴影。
  • PointLight 是朝向所有方向发射光线。类似于宇宙中的太阳,黑夜中的照明弹等。
  • SpotLight 类似于手电筒和现实中的聚光灯。它发射出来的光线是锥形的,而且它设置它随着具体的增加光强减弱。
  • SpotLight 和 DirectionalLight 都一个 debug 模式,用来微调阴影相机的熟悉(shadow.camera.visible)。
  • DirectionalLight 类似于对于地球来说的太阳光,它是场景中最常使用的光源。
  • 如果你想要更加自然的效果,可以使用 HemisphereLight,它可以将天空的光照和来自地面的反射光计算在内。
  • RectAreaLight 像是一个平面发出光,但是你不能直接观察到光源本身,只能观察到它对其他对象的影响。

其实光源对对象产生的效果,还跟“材质”有着千丝万缕的联系,有些材质是完全不受光源影响的,比如 MeshBasicMaterial,它只会以指定的颜色渲染物体,我们会在下一篇章中,看看 Three.js 中的材质。

最后上面所有的 demo 都关联的 dat.GUI,因此通过它调整一些关键属性可以帮助你更好的理解上面提到的知识点。

Webpack 3 更新了啥?

参考:https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b

Scope Hoisting

Scope Hoisting 译为 作用域提升,它是 Webpack 3 最大的一个新功能。曾经的 Webpack 是将每一个模块(每一个被 import 或者 require 的那个东西)bundle 到每一个一个独立的闭包函数里。这会导致 bundle 的文件里,每一个模块最外层都会有一些其特殊的闭包包装,导致文件体积增大,同时它也会让 JS 在浏览器执行变慢(因为多了一层函数的包装)。

Webpack 3 可以如此开启 Scope Hoisting

module.exports = {  
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};

上面图片是他人的结果,让我们自己实验下。

看看真实结果

实验文件

// app.js
import bar from './bar';

bar();
// bar.js
export default function bar() {
  console.log(1);
}
// webpack.config.js for webpack2
module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
  }
}
// webpack.config.js for webpack3
var webpack = require('webpack')

module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

构建结果

// webpack2
Hash: 60bba0d666fdbba6aacb
Version: webpack 2.6.1
Time: 96ms
    Asset     Size  Chunks             Chunk Names
bundle.js  3.13 kB       0  [emitted]  main
   [0] ./bar.js 51 bytes {0} [built]
   [1] ./app.js 32 bytes {0} [built]
// webpack3
Hash: d974d2a55c9d519d75fd
Version: webpack 3.0.0
Time: 119ms
    Asset     Size  Chunks             Chunk Names
bundle.js  2.71 kB       0  [emitted]  main
   [0] ./app.js + 1 modules 83 bytes {0} [built]

首先,我们可以看到 bundle.js 文件的体积有了明显的变化,从 3.13kb 缩小到了 2.71kb(我并没有开启丑化和压缩),我们在看看构建后的文件的具体差别:

这个 Webpack 2 构建后的结果,我们可以看到 Webpack 2 为两个模块(app.js 和 bar.js)分别为他们增加了两个闭包:

(function(module, __webpack_exports__, __webpack_require__) {
	...
})

我们再来看看 Webpack 3 的构建结果:

我们看到 Webpack 3 + ModuleConcatenationPlugin() 所构建出的文件,只为两个模块包装了一个闭包,少了一坨这么一个东西:

(function(module, __webpack_exports__, __webpack_require__) {
	...
})

十个模块就是十坨这些字符,Webpack 3 + ModuleConcatenationPlugin() 确实减少了这些东西,确实能够使得最后构建出的文件从体积上有比较明显的减少!

但是,那如果我们在某个模块里直接声明了一个变量,但是同时我又没有 export 出来,没有闭包的存在,似乎也许在其他模块(构建后的文件里)好像可以访问到其他模块中直接声明的变量?我们试试?

修改实验文件

// app.js
import bar from './bar';

bar();
console.log(a);
// bar.js
var a = 'a';
export default function bar() {
  console.log(1);
}

再次构建再看下构建后的结果:

Webpack 2 是通过闭包的方式,让 a 在其他模块下无法访问到的。那 Webpack 3 呢?

在 Webpack 3 + ModuleConcatenationPlugin(),虽然是一个闭包,但是它会自己根据模块的文件名从而唯一标识那个不可再其他模块中访问的变量,来避免在其他模块中访问到别的模块没有 export 出来的变量。具体查看这里https://github.com/webpack/webpack/blob/master/lib/optimize/ModuleConcatenationPlugin.js

有的时候可能因为某种原因会降级该功能,你可以在命令行中增加 --display-optimization-bailout 查看被降级的原因。

结论

Webpack 3 + ModuleConcatenationPlugin() 确实可以优化你 JS 代码的性能,至于生成环境中的使用,我可能还需要观望一阵(其实也是业务太紧,空下来就升一波~),我会时刻关注它的进展(官方声称 Webpack 2 可以直接升级 Webpack 3)

Magic Comments

Magic Comments 译为魔法注释(其实不想说这个的,看到魔法两个字,我就不喜欢,但是它是 Webpack 3 的重要功能还是说下吧)。Webpack 2 的时候大家都知道了动态引入语法 import(),有些用户就关心说它能否下过去的 require.ensure 那样给动态模块创建一个自有的名称。

Magic Comments 可以让你传入一个模块名给你的 import(),就像一个行内注释一样。

详细使用可以看这篇文章

通过使用注释,我们可以保存真实的加载信息,并且这也带来了你们喜爱的一个强大的模块命名功能。

import(/* webpackChunkName: "my-chunk-name" */ 'module');


可以查看最新代码拆分指南文档,获得更加详细的关于该功能的详细信息。

最后

从 Webpack 3 开始,他们开发者发起了这么一个投票 https://webpack.js.org/vote/,由社区的开发者们决定未来的新功能会优先开发哪个。

Webpack 3 接下来可能会带来:

  • 更好的构建缓存
  • 更快的初始化和增量编译
  • 更好的 TypeScript 体验
  • 修订长时间缓存机制
  • WASM 模块支持
  • 提升用户体验

因此,Webpack 1 的老铁们如果时间允许,有空的时间,我觉得可以升~

参考:https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b

【翻译】我在系统里找不到 San Francisco 字体

Apple 打算在 iOS 和 OS X 中使用了一款新的很好看的字体 San Francisco。我第一次看见它是在我的 Apple Watch 中,马上这个字体就可以在所有的 Apple 设备中看见。

作为一个开发者,我们经常会遇到需要在网页中使用系统字体的情况。这些网页可能会被嵌入到我们的应用和一些文档中。在这些环境下,网页需要匹配这些嵌入环境的字体对用户体验是十分重要的。想象一下,如果在 Yosemite 中跑的一个应用,它显示 Sparkle 在 Lucida Grande 的感觉。(这个地方我清楚这些名词,不过意思就是网页嵌入到了一个应用中,然后应用的字体和网页的字体不匹配的感觉)。

我们马上就会面对许许多多的内容需要用 San Francisco 字体显示,因此需要通过 CSS 来指定相同的字体。不过实际上,这不是简单的事情。

传统上,我们会去明确地指定字体。比如像这样就可以使用 San Francisco 字体:

body {
  font-family: "San Francisco", "Helvetica Neue", "Lucida Grande";
}

不幸运的是,最新的 OS X 10.11 中,并没有 San Francisco 字体。

但是这是系统字体啊,怎么可能找不到?

Apple 其实有个抽象系统字体的想法:不公开暴露系统字体的名字。他们也已经表示,任何私有的字体名字都可能被更改。这些私有的字体名已经被使用了一段时间:San Francisco 的超轻字体叫“.SFNSDisplay-Ultralight”。为了确定这个名字,你需要通过“NSFont”或者"UIFont"返回的实例中去仔细寻找。这显然不是一件简单的事。

抽象系统字体的动机是让操作系统在面对给定规则权重的时候可以做出更好的选择。Apple 还有研究其他的字体相关的新特性,比如“6”和“9”的仔细和一些不等宽的数字字体。我猜他们还会将这些特性带到网页上去。

由于抽象字体的原因,现在出现了一个新的通用的字体名:apple-system。这个语法类似于这样:

body {
  font-family: -apple-system, "Helvetica Neue", "Lucida Grande";
}

不幸的是,相关的官方文档简直太破了,大多数相关的信息,我都是从Webkit 源码中找到的,看上去仍然在开发中。

另一个让人失望的是,通用的字体名只能在 Safari 中工作。当然你不要希望 Google 会去提供“-apple”前缀,但是 Webkit 的源代码中有-webkit-system-font。由于最新的 Chrome 是基于 Webkit 的,因此它也不好意外的忽略了 Blink。

在 iOS 上的 Safari 增加了动态匹配自动字体的特性。这增加了一定的混乱。一下的关键字可以在 iOS 7+ 使用:

-apple-system-headline1
-apple-system-headline2
-apple-system-body
-apple-system-subheadline1
-apple-system-subheadline2
-apple-system-footnote
-apple-system-caption1
-apple-system-caption2
-apple-system-short-headline1
-apple-system-short-headline2
-apple-system-short-body
-apple-system-short-subheadline1
-apple-system-short-subheadline2
-apple-system-short-footnote
-apple-system-short-caption1
-apple-system-tall-body

由于 OS X 没有办法去处理动态字体类型,上面这些关键字在桌面的 Safari 上就没有用了,当然,Chrome也不会支持,别想了。

另外请注意,这些关键字不会在你的font-family中起作用,他们只会在font的定义中起作用。如果你想知道该功能更多的信息,可以阅读这篇教程

如果你是一个 Apple 设备的开发者或者设计师,可能已经手动安装了 San Francisco 字体。不要像我这么傻:大多数访问你网页的用户可能并没有这个字体。同时 San Francisco 是需要被许可才能使用的,所以它也不会提供 Web font。

所以,我们码农应该怎么做呢?

如果你能确定你的内容只会在 Apple 设备的浏览器或者 Webview 中呈现的话,那语法将会是极其简单:

body {
  font-family: -apple-system, "Helvetica Neue", "Lucida Grande";
}

这样,你将会得到最新的系统字体或者退化成“Helvetica Neue”、“Lucida Grande”或者更老的系统字体。

现在,Chrome 支持BlinkMacSystemFont字体,所以私有字体名称将不再需要。

body {
  font-family: system, -apple-system, BlinkMacSystemFont,
      "Helvetica Neue", "Lucida Grande";
}

“system”这个通用字体名现在还不存在,但我鼓励浏览器去支持这个关键字。对于码农在全平台的开发将会有很大的帮助。在 Android 上,可以按需选择 Roboto 和 Noto。在 Windows 这样的,可以选择系统字体的操作系统上,可以更简单的选择适合内容环境的字体。

在文章的最后,我还想给出一个关于本篇文字的一个研究例子。你可以在 Apple 设备上的不同浏览器打开看下。


原文地址:http://furbo.org/2015/07/09/i-left-my-system-fonts-in-san-francisco/

说说 Javascript 的 Date

基础的 Date() 就不说了~ :)

如何获得某个月的天数?

不知道大家遇到过这个问题吗?我想如果你们写过日期组件一定有这个问题,我当时的解决方案是这样的:

以下的三个方法,month 参数我都根据 JS 本身对于 Date 的月份定义,采用0为1月

最老实的办法

const EVERY_MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

function getDays(year, month) {
  if (month === 1 && isLeap(year)) return 29;
  return EVERY_MONTH_DAYS[month];
}

手动做了每个月天数的映射,如果是2月份并闰年,那天数+1
随便安利一个自己写的 osx 上的日历插件 https://github.com/lishengzxc/ng2-calendar

那没有更好的方法呢?手动 map 和闰年判断的逻辑没有就好了。

稍微 diao 一点的办法

function getDays(year, month) {
  if (month === 1) return new Date(year, month, 29).getMonth() === 1 ? 29 : 28;
  return new Date(year, month, 31).getMonth() === month ? 31 : 30;
}

我们发现,new Date()的第三个参数是可以大于我们所知的每个月的最后一天的的,比如:

new Date(2016, 0, 200) //Mon Jul 18 2016 00:00:00 GMT+0800 (CST)

这样,我们就利用这个 JS 的特性,用29和31这两个关键点,去判断除了那个月的最后一天+1还是那个月吗?(其实28和30是关键点)。

再稍微 diao 一点的方法

function getDays(year, month) {
  return new Date(year, month + 1, 0).getDate();
}

new Date()的第三个参数传小于1的值会怎么样了,比如传0,我们就获得了上个月的最后一天,当然传负数也没问题:

new Date(2016, 0, -200) //Sun Jun 14 2015 00:00:00 GMT+0800 (CST)

Date.prototype.各种String

具体的文档解释懒得再复制一下给大家看,参考链接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date

这里主要和大家普及以下知识:

GMT(格林尼治平时)

格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。

自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。

理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。

由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)。

所以我们也从 MDN 上的文档看到对于toGMTString()的解释是:

Returns a string representing the Date based on the GMT (UT) time zone. Use toUTCString() instead.

UTC(世界标准时间)

协调世界时,又称世界标准时间或世界协调时间,简称UTC(从英文「Coordinated Universal Time」/法文「Temps Universel Cordonné」而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平时

CST(北京时间)

北京时间,China Standard Time,**标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。

不过这个CST这个缩写比较纠结的是它可以同时代表四个不同的时间:

  • Central Standard Time (USA) UT-6:00
  • Central Standard Time (Australia) UT+9:30
  • China Standard Time UT+8:00
  • Cuba Standard Time UT-4:00

插一个**地区 JS 客户端时间和服务端时间不一致的问题

总结就是,前后端去传时间的时候,尽量都用 UTC 时间。

ISO 日期和时间的表示方法

if ( !Date.prototype.toISOString ) {
  ( function() {

    function pad(number) {
      if ( number < 10 ) {
        return '0' + number;
      }
      return number;
    }

    Date.prototype.toISOString = function() {
      return this.getUTCFullYear() +
        '-' + pad( this.getUTCMonth() + 1 ) +
        '-' + pad( this.getUTCDate() ) +
        'T' + pad( this.getUTCHours() ) +
        ':' + pad( this.getUTCMinutes() ) +
        ':' + pad( this.getUTCSeconds() ) +
        '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
        'Z';
    };

  }() );
}

通过 Polyfill 我们就能知道 ISO 是怎么表示时间的,最主要的特征是最后一位是“Z”,然后表示的总是 UTC 时间。

额外的补充

.valueOf() 和 .getTime()

.valueOf()的功能和.getTime()一样。
**该方法通常在 JavaScript 内部被调用,而不是在代码中显式调用。**什么意思?没有 valueOf,那么 Date的实例是不能进行运算的。

var obj = Object.create(null);
obj + 1; // Uncaught TypeError: Cannot convert object to primitive value(…)

.toJSON

直接看这个 API 的名字的时候,我以为会返回一个 JSON 格式的字符串,但其实是这么一个东西

new Date().toJSON() // "2016-05-05T06:03:28.130Z"

其实是这么回事

JSON.stringify(new Date()) // ""2016-05-05T06:06:02.615Z""

那结果能够被 parse 吗?

JSON.parse(JSON.stringify(new Date())) // "2016-05-05T06:19:24.766Z"
JSON.parse('"' + new Date().toJSON() + '"') // "2016-05-05T06:19:24.766Z"

但是结果只是字符串而已。需要再讲这个字符串交给 new Date() 才行。

注:

.toJSON方法,这里稍微有点逻辑问题,执行JSON.stringify的话,会自动在对象上搜索toJSON方法,而不是说date.toJSON内部调用的是JSON.stringify(date) (感谢@warjiang的指正)

.toLocaleFormat()

不属于任何标准。在JavaScript 1.6中被实现。似乎也只有 Firefox 自持这个 API,其实正确姿势是用 .toLocaleDateString()

.toLocale各种String()

.toLcale各种String(locales [, options]])
妈的这个 API 有点烦,看 MDN 的文档你就知道。这个 API 是用来本地化时间的。

这里稍微说下我对这些参数的理解:

locales

var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

// formats below assume the local time zone of the locale;
// America/Los_Angeles for the US

// US English uses month-day-year order
alert(date.toLocaleString("en-US"));
// → "12/19/2012, 7:00:00 PM"

// British English uses day-month-year order
alert(date.toLocaleString("en-GB"));
// → "20/12/2012 03:00:00"

// Korean uses year-month-day order
alert(date.toLocaleString("ko-KR"));
// → "2012. 12. 20. 오후 12:00:00"

// Arabic in most Arabic speaking countries uses real Arabic digits
alert(date.toLocaleString("ar-EG"));
// → "٢٠‏/١٢‏/٢٠١٢ ٥:٠٠:٠٠ ص"

// for Japanese, applications may want to use the Japanese calendar,
// where 2012 was the year 24 of the Heisei era
alert(date.toLocaleString("ja-JP-u-ca-japanese"));
// → "24/12/20 12:00:00"

// when requesting a language that may not be supported, such as
// Balinese, include a fallback language, in this case Indonesian
alert(date.toLocaleString(["ban", "id"]));
// → "20/12/2012 11.00.00"

locales所指的地区的时区和语言输出。

options

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString

  • localeMatcher 选择本地匹配的什么算法,似乎没什么大用
  • timeZone 再设置下 UTC 时区
  • hour12 是否12小时制
  • formatMatcher 各日期时间单元的格式化
    • weekday Possible values are "narrow", "short", "long".
    • era Possible values are "narrow", "short", "long".
    • year Possible values are "numeric", "2-digit".
    • month Possible values are "numeric", "2-digit", "narrow", "short", "long".
    • day Possible values are "numeric", "2-digit".
    • hour Possible values are "numeric", "2-digit".
    • minute Possible values are "numeric", "2-digit".
    • second Possible values are "numeric", "2-digit".
    • timeZoneName Possible values are "short", "long".

栗子:

var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

date.toLocaleString("en-US", {hour12: false}); // "12/19/2012, 19:00:00"

var options = {timeZoneName:'long',weekday: "long", year: "2-digit", month: "narrow", day: "numeric"};
date.toLocaleString("en-US", options); // "Thursday, D 20, 12, China Standard Time"

插一个JavaScript 显示 Y-m-d H:i:s 的日期时间格式

老实的方法

let date = new Date();
let result = [
  [
    date.getFullYear(),
    date.getMonth() + 1,
    date.getDate()
  ].join('-'),
  [
    date.getHours(),
    date.getMinutes(),
    date.getSeconds()
  ].join(':')
].join(' ').replace(/\b\d\b/g, '0$&');

diao 一点的方法

var date = new Date();
var result = date.toLocaleString('zh-CN', { hour12: false })
  .replace(/\//g, '-').replace(/\b\d\b/g, '0$&');

一些有用的时间库

JS货币格式化

在日常开发中遇到一个数字千分位格式化,所谓的数字千分位格式化,即从个位数起,每三位之间加一个逗号。例如“10,000”

通过google和自己的实践收集并整理了一下方法。

//1.
function toThousands(num) {
    var result = [ ], counter = 0;
    num = (num || 0).toString().split('');
    for (var i = num.length - 1; i >= 0; i--) {
        counter++;
        result.unshift(num[i]);
        if (!(counter % 3) && i != 0) { result.unshift(','); }
    }
    return result.join('');
}

方法一的执行过程就是把数字转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头。每插入一个元素,counter就计一次数(加1),当counter为3的倍数时,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果。

//2.
function toThousands(num) {
    var result = '', counter = 0;
    num = (num || 0).toString();
    for (var i = num.length - 1; i >= 0; i--) {
        counter++;
        result = num.charAt(i) + result;
        if (!(counter % 3) && i != 0) { result = ',' + result; }
    }
    return result;
}

方法二是方法一的改良版,不把字符串打散为数组,始终对字符串操作。

//3.
function toThousands(num) {
    var num = (num || 0).toString(), re = /\d{3}$/, result = '';
    while ( re.test(num) ) {
        result = RegExp.lastMatch + result;
        if (num !== RegExp.lastMatch) {
            result = ',' + result;
            num = RegExp.leftContext;
        } else {
            num = '';
            break;
        }
    }
    if (num) { result = num + result; }
    return result;
}

通过正则表达式循环匹配末尾的三个数字,每匹配一次,就把逗号和匹配到的内容插入到结果字符串的开头,然后把匹配目标(num)赋值为还没匹配的内容(RegExp.leftContext)。此外,还要注意:

  • 如果数字的位数是3的倍数时,最后一次匹配到的内容肯定是三个数字,但是最前面的三个数字前不需要加逗号;
  • 如果数字的位数不是3的倍数,那num变量最后肯定会剩下1到2个数字,循环过后,要把剩余的数字插入到结果字符串的开头。
//4
function toThousands(num) {
    var num = (num || 0).toString(), result = '';
    while (num.length > 3) {
        result = ',' + num.slice(-3) + result;
        num = num.slice(0, num.length - 3);
    }
    if (num) { result = num + result; }
    return result;
}

截取末尾三个字符的功能可以通过字符串类型的slice、substr或substring方法做到。这样就可以避免使用正则表达式。(正解)

//5
function toThousands(num) {
    var num = (num || 0).toString(), temp = num.length % 3;
    switch (temp) {
        case 1:
            num = '00' + num;
            break;
        case 2:
            num = '0' + num;
            break;
    }
    return num.match(/\d{3}/g).join(',').replace(/^0+/, '');
}

先把数字的位数补足为3的倍数,通过正则表达式,将其切割成每三个数字一个分组,再通过join方法添加逗号,最后还要把补的0移除。

//6
function toThousands(num) {
    return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}

正则表达式正向前瞻,(javascript是不支持后瞻)

总结:方法四最后,循环最少,没有正则性能肯定是最好的了。

参考 http://www.cnblogs.com/rubylouvre/archive/2010/03/09/1681222.html

我们要不要在 JS 使用二进制位运算?

Javascript 完全套用了 Java 的位运算符,包括按位与&、按位或|、按位异或^、按位非~、左移<<、带符号的右移>>和用0补足的右移>>>
这套运算符针对的是整数,所以对 Javascript 完全无用,因为 Javascript 内部,所有数字都保存为双精度浮点数。如果使用它们的话,Javascript 不得不将运算数先转为整数,然后再进行运算,这样就降低了速度。而且"按位与运算符"&同"逻辑与运算符"&&,很容易混淆。

上面的一段文字来源于一篇比较老的博文http://www.ruanyifeng.com/blog/2010/01/12_javascript_syntax_structures_you_should_not_use.html。说的并没太错,但是却没有告诉我们到底会降低多少速度,“"按位与运算符"&同"逻辑与运算符"&&,很容易混淆”,真的容易混淆吗?(博主是自己很钦佩的一个前辈,自己接下来主要是想分享一些 JS 可能不常用的技巧(理解了以后就常用了:p))

到底会慢多少?

先拿一个位运算左移<<等效*2的例子,别吐槽这么去测试有什么意义,自己真的写代码的时候基本没人会去使用位移操作。来,打开 Console 看例子:
http://lishengzxc.github.io/bblog/Bitwise-Shift.html

  console.time('*2');
  for (var i = 0; i < 10000000; i++) {
    1.1 * 2
  }
  console.timeEnd('*2');

  console.time('<<1');
  for (var i = 0; i < 10000000; i++) {
    1.1 << 1
  }
  console.timeEnd('<<1');

  console.time('/4');
  for (var i = 0; i < 10000000; i++) {
    1.1 / 4
  }
  console.timeEnd('/4');

  console.time('>>2');
  for (var i = 0; i < 10000000; i++) {
    1.1 >> 2
  }
  console.timeEnd('>>2');

结果:我的浏览器是 Chrome,MBP15(13年),发现循环10000000次也仅相差不到 5ms。而且你发现多刷新一次,发现直接整数乘法或除法和左移右移谁快谁慢是不确定的,反正相差无几。

*2: 29.870ms
<<1: 33.389ms
/4: 33.003ms
>>2: 27.841ms

再来看一个取整的例子:http://lishengzxc.github.io/bblog/Bitwise-GetInt.html

这是一个Number的取整的例子,而不是StringNumber

  console.time('| 0');
  for (var i = 0; i < 10000000; i++) {
    i / 3 | 0
  }
  console.timeEnd('| 0');

  console.time('Math.floor');
  for (var i = 0; i < 10000000; i++) {
    Math.floor(i / 3)
  }
  console.timeEnd('Math.floor');

  console.time('parseInt(x)');
  for (var i = 0; i < 10000000; i++) {
    parseInt(i / 3)
  }
  console.timeEnd('parseInt(x)');

  console.time('parseInt(x, 10)');
  for (var i = 0; i < 10000000; i++) {
    parseInt(i / 3, 10)
  }
  console.timeEnd('parseInt(x, 10)');

  console.time('(| 0) + 1');
  for (var i = 0; i < 10000000; i++) {
    (i / 3 | 0) + 1
  }
  console.timeEnd('(| 0) + 1');

  console.time('Math.ceil');
  for (var i = 0; i < 10000000; i++) {
    Math.ceil(i / 3)
  }
  console.timeEnd('Math.ceil');

结果:我们发现这个时候按位或|要快于其他 API,除了这点外,大家在注意下parseInt(),可以发现,不带第二个参数很重要,首先明确是了是十进制同时速度也快了不少。

| 0: 55.003ms
Math.floor: 55.196ms
parseInt(x): 197.627ms
parseInt(x, 10): 131.175ms
(| 0) + 1: 28.960ms
Math.ceil: 113.976ms

不过,虽然按位或|Number的取整加速不少,但是它只适用于NumberString还是老老实实的吧,比如:

Math.ceil('1234.23'); //1235
parseInt('1234,23', 10); //1234

JS 使用二进制位运算的一些例子

我举得例子肯定不全,欢迎大家头脑风暴~

运算符 用法 描述
按位与(AND) a & b 对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0。
按位或(OR) a | b 对于每一个比特位,当两个操作数相应的比特位至少有一个1时,结果为1,否则为0。
按位异或(XOR) a ^ b 对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
按位非(NOT) ~ a 反转操作数的比特位,即0变成1,1变成0。
左移(Left shift) a << b 将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充。
有符号右移 a >> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。
无符号右移 a >>> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。

随机获取数组某项

随机获取某项关键是随机拿到满足数组长度的索引

var arr = [...];
var len = arr.length;

// 一般方法:
var rIndex = Math.floor((Math.random() * len))

// 使用位运算的方法
var rIndex = Math.random() * len | 0

是不是简洁了许多?

indexOf()返回trueorfalse

var arr = [...];

// 一般方法:
var isExist = function(array, value) {
    return array.indexOf(value) > 0;
}

// 使用位运算的方法:将 -1 转变为 false
var isExist = function(array, value) {
    return !!~array.indexOf(value);
}

补充:ES6/7 和indexOf()类似的 API

// .find()
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10,未找到的话,返回 undefined

// .findIndex()
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2,未找到的话,返回 -1

// .includes()
[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, NaN].includes(NaN); // true

切换变量 0 或 1

// 一般方法:
if (toggle) {
    toggle = 0;
} else {
    toggle = 1;
}

// 一般方法的简写:
togle = toggle ? 0 : 1;

// 使用位运算的方法:
toggle ^= 1;

碰撞检测优化(标示我需不要和你检测碰撞)

如果已经对 A 测试了 B,就没有必要再对 B 测试 A。我们需要维护一个二维数组保存每个碰撞对象的2个标志impactFlagimpactFlags

Bullet Tank Wall
impactFlag 1 2 4
impactFlags 2+4 0 0
doCheckImpact = obj1.impactFlag & obj2.impactFlags // 非0标示需要进行碰撞检测(0 == false)

其实 *inux 的权限系统 7 = 1 + 2 + 4 也是这么设计的。

7 = 111

1 = 001
2 = 010
4 = 100

将权限值和 1、2、4 按位与&,就能得出目标用户对目标文件有什么权限了。

left-pad

https://www.npmjs.com/package/left-pad

最新的 left-pad,因为@左耳朵耗子的 PR 源码已经更改了。

// 以前的版本
function leftpad (str, len, ch) {
  str = String(str);

  var i = -1;

  if (!ch && ch !== 0) ch = " ";

  len = len - str.length;

  while (++i < len) {
    str = ch + str;
  }

  return str;
}

以前的版本中,补 n 位字符,字符串链接时间复杂度是 O(n) 的,字符串需要执行 n 次的链接操作,那可以降低时间复杂度吗?

// 左耳朵耗子的 PR
function leftpad (str, len, ch) {
  //convert the `str` to String
  str = str +''; 

  //needn't to pad
  len = len - str.length;
  if (len <= 0) return str;

  //convert the `ch` to String
  if (!ch && ch !== 0) ch = ' ';
  ch = ch + ''; 

  var pad = '';
  while (true) {
    if (len & 1) pad += ch;
    len >>= 1;
    if (len) ch += ch;
    else break;
  }
  return pad + str;
}

这里用了2个位运算,&>>,其实算法的逻辑就是去算需要填充多少位。
len和 1 去按位与是为了看右移后的len是奇数还是偶数,比如:

2 & 1 //0
3 & 1 //1
4 & 1 //0
5 & 1 //1

根据len的奇偶觉得填充字符串要不要补上一个。

循环中的len,每次右移动后,判断剩下的len,还能继续右移吗,如果可以,就将填充字符串pad倍增ch+=ch(字符串倍增)。

这样的逻辑是的left-pad更加高效了。

// 月影大神的
function leftpad(str, len, ch){
  str = "" + str;
  if(!ch && ch !== 0) ch = " ";
  ch = "" + ch;

  var padlen = len - str.length;
  if(padlen <= 0) return str;

  var padch = padlen & 1 ? ch : "";

  while(padlen >>= 1){
    ch += ch;
    if(padlen & 1){
        padch += ch;
    }
  }
  return padch + str;
}

无法理解的同学,可以单步调试下

left-pad-debugger

补充 ES6/7 相关字符串拓展

.repeat()

repeat方法返回一个新字符串,表示将原字符串重复n次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

.padStart(),.padEnd()

ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

最后

我们没要必要排斥在 JS 中使用位运算,欢迎大家补充更多的使用实例。

【翻译】Three.js — Animation system

原文:https://threejs.org/docs/index.html#manual/introduction/Animation-system

概述

在 three.js 的动画系统中,你可以为模型设置各种属性的动画,比如:蒙皮和绑定模型的骨骼顶点动画,不同的材质属性(颜色,不透明度,布尔值),可见性和形变。动画属性可以淡入、淡出、交叉渐变与变形。同一时间的不同动画对同一对象以及不同对象的权重和时间尺度可以独立修改,同一对象和不同对象上个各种动画可以同步变化。

为了实现同类系统中的所有动画功能,three.js 的动画系统已经在2015年完全改变,现在它具有类似于 Unity / Unreal Engine 4 的结构体系。本章将简要概述改动画系统的主要组件以及它们如何协同工作。

Animation Clips

如果你已经成功导入了一个动画 3D 对象(不用关心它是骨骼动画还是顶点动画或者它们都是)。举个例子,你在 Blender 中使用 Blender exporter 将模型导出,并使用 JSONLoader 将它加载到 three.js 的场景中,加载完成的几何体网格对象会有一个名为 "animations" 的数组,其中包含这个模型的 AnimationClips。

每个 AnimationClip 通常保存该对象的特定动画的数据。例如,如果网格是角色,则可能有一个用于步行循环的 AnimationClip,第二个用于跳转,第三个用于跳转等。

Keyframe Tracks

在 AnimationClip 中,每个动画属性的数据都存储在一个单独的 KeyframeTracks 对象中。假定一个角色模型具有骨架,一个 KeyframeTracks 可以存储随着时间推移下的骨骼位置变化的数据,另外一个用于存储骨骼的旋转变化,第三个用于旋转护着缩放另外一块骨骼。我们应该想象,AnimationClip 可以由多个这样的轨道组成。

再假设模型具有顶点动画(比如一个动画显示友好的脸部表情,另外一个动画显示愤怒的脸部表情),每个 KeyframeTracks 就保存如何影响动画片段在执行过程中的某个顶点动画。

Animation Mixer

那些被存储的数据仅仅是动画的基础,实际上动画的播放是用 AnimationMixer 来控制。你可以想象并认为它不仅是一个动画播放器,而是像一个真正的硬件混音器那般,可以用时控制几个动画并混合它们。

Animation Actions

AnimationMixer 本身只有很少的(普通)属性与方法,因为它可以通过控制 AnimationActions。通过配置一个 AnimationAction,你可以指定它何时应该在其中一个 AnimationMixer 中播放,暂停或者停止,是否需要重复,如何重复,以及是否使用渐变还是调整动画的播放时间,还有些其他额外的东西,如交叉淡入淡出或是同步。

Animation Object Groups

如果你希望一组对象接受并共享动画的状态,可以使用AnimationObjectGroup

已经支持的格式解析与加载器

请注意,并非所有模型的格式都包含动画(.obj 显然不包含动画),只有以下 three.js 的加载器支持 AnimationClip

  • THREE.JSONLoader
  • THREE.ObjectLoader
  • THREE.BVHLoader
  • THREE.ColladaLoader
  • THREE.FBXLoader
  • THREE.GLTFLoader
  • THREE.MMDLoader
  • THREE.SEA3DLoader

注意,3ds max 和 Maya 目前无法将多个动画(即不在同一时间轴上的动画)直接导出到单个文件。

Example

var mesh;

// Create an AnimationMixer, and get the list of AnimationClip instances
var mixer = new THREE.AnimationMixer( mesh );
var clips = mesh.animations;

// Update the mixer on each frame
function update () {
	mixer.update( deltaSeconds );
}

// Play a specific animation
var clip = THREE.AnimationClip.findByName( clips, 'dance' );
var action = mixer.clipAction( clip );
action.play();

// Play all animations
clips.forEach( function ( clip ) {
	mixer.clipAction( clip ).play();
} );

border-image-*

之前一段时间在做游戏,在游戏中,经常需要用到九宫图,用九宫图可以保证图片的边框不会因为拉伸而走形。在 CSS3 中也有对九宫图的支持 — border-image。接下来就去说下它。

语法

<'border-image-source'> || <'border-image-slice'> [ / <'border-image-width'> | / <'border-image-width'>? / <'border-image-outset'> ]? || <'border-image-repeat'>

border-image-source

这个没啥好说的...,真要说下,就是可以设置 base64。

border-image-slice

这个可以好好唠个10块钱的了,先来找图,估计你看了也就明白了:

https://developer.mozilla.org/files/3814/border-image-slice.png

border-image-slice就是去控制九宫图是怎么切的,根据上图,其中:

  • 1、2、3、4是绝对不会被拉伸了
  • 5、7会被横向拉伸
  • 6、8会被纵向拉伸
  • 9横向纵向都拉伸

语法

/* border-image-slice: slice */
border-image-slice: 30%; 

/* border-image-slice: horizontal vertical */
border-image-slice: 10% 30%;

/* border-image-slice: top horizontal bottom */
border-image-slice: 30 30% 45;

/* border-image-slice: top right bottom left */
border-image-slice: 7 12 14 5; 

/* border-image-slice: … fill */
/* The fill value can be placed between any value */
border-image-slice: 10% 32 7 12 fill;

/* Global values */
border-image-slice: inherit;
border-image-slice: initial;
border-image-slice: unset;

单位相关

  • 不加单位相当于px(只支持px%
  • 百分比相对于border-image-source图片的大小
  • 关键字fill指的是:图片中间部分是否需要保留,一般在游戏那样的场景中,中间部分会选择保留。如果不没有fill,如果有background就显示background
  • 相对的两个放下的值相加超过图片的大小会出现错误,比如图片宽度是100pxborder-image-slice: 10% 60px;,最后将会渲染出奇奇怪怪的东西
  • 不支持负值

border-image-width

这个可以聊5块钱,就是去设置图片边框的宽度的,顺时针上右下左。一般来说,border-image-widthborder-image-slice的值是设置相同的(非百分比,因为border-image-width百分比是相对于盒子的高宽的),但是border-image-widthborder-image-slice也可以设置的不同,不同的效果就是将border-image-slice切出来的边框塞到border-image-width再进行拉伸或缩放,什么意思:就是将slice出来的1、2、3、4、5、6、7、8块东东再拉伸或者缩放成border-image-width的大小。

再简单的说,就是slice出来的东西,填充到border-image-width的空间里!!!并撑满!

因此利用这个特性,我们可以模拟高清屏幕的1px边框:

  1. 准备一个图片边框,这个图片有颜色(灰色)的边框
    https://github.com/lishengzxc/Slider/blob/master/MobileWeb/demo/viewport/b.png?raw=true
  2. 图片里面黑色的上下两块在画的时候设置为1px,然后往里切割2px,然后在设置border-image-width1px
<style>
.border-image-1px {
  border-width: 1px 0px;
  border-image: url("border.jpg") 2 0 stretch;
}
</style>
<div class="border-image-1px"></div>

但是,这是一个极其傻的 hack 方式,因为边框它是去模拟边框,虽然修复了高清1px的问题,但是边框颜色并不能设置了。

PS:偷懒的高清1px边框方案:#2

除此以外,border-image-width的值可以设置的很大,但是设置很大话,又是奇奇怪怪的东西,虽然它也是有一定规则的,但是我不想去了解了(任性,以后要去好好研究看看)。

border-image-outset

这个是用来设置边框图像可超出边框盒的大小。MDN上说是可以有设置百分比的代码DEMO,但是似乎并运行不起来。也不支持负值。

设置了border-image-outset并不会改变最后盒子的大小。

border-image-repeat

默认值:stretch

https://developer.mozilla.org/files/4133/stretch.png

.example { 
  border-image:url("/files/4127/border.png") 30 30 stretch;
}

round

https://developer.mozilla.org/files/4131/round.png

.example { 
  border-image:url("/files/4127/border.png") 30 30 round;
}

repeat

https://developer.mozilla.org/files/4129/repeat.png

.example { 
  border-image:url("/files/4127/border.png") 30 30 repeat;
}

说实话,stretch是最常用的。

Git Tag 基础操作

删除远程 Tag

git push origin --delete tag tagname

删除本地 Tag

git tag -d tagname

奇怪的高清屏1px边框的问题解决方案(偷懒)

移动端高清屏的1px边框问题的解决方案很多,比较成熟的是用阿里无线的 https://github.com/amfe/lib-flexible 方案,它会去动态设置viewportinitial-scale,所以在遇到边框的时候,我们就不需要做各种的 hack 来解决(比如伪元素,border-image)。

但是,我如果是一个比较老的项目,当时在开发的时候,并没有引入lib-flexible,同时设计师,和产品经理都对你还原的页面吐槽不断,怎么办?我不可能重新这个是时候引入并使用lib-flexible,同时我对 hack 方案一直都比较反对。

终于我想到一个简单的方案,同样还是1px的单位,然后是两个选择:

  • 边框的颜色,我并不根据设计稿来还原,而是选择比设计稿更浅的颜色
  • 边框的颜色根据设计稿还原,加上透明度,0.1 或者 0.2 注意边框线的颜色要使用rgba的话,需要加上background-clip: padding-box;

好了,现在终于让产品和设计师满意了。

附:http://www.w3school.com.cn/cssref/pr_background-clip.asp

【翻译】You Don't Know JS: Meta Programming

Javascript 元编程

原文链接:https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/ch7.md

参考链接:http://es6.ruanyifeng.com/#docs/symbol


元编程是一种操作程序本身的编程方式。换句话来说,就是编写操作代码的代码,听起来是不是很酷?

举个例子,如果你想去探测一个对象a和另一个对象b他们的是不是在一条原型链上?可以用a.isPrototypeOf(b),这通常为认为是一种元编程的方式—自省。宏(Javascript 还没有宏)是另外一种在那些编译型语言中的元编程的方式。用for..in来迭代遍历一个对象的key,或者检查一个对象是不是一个class constructor的实例,这些都是另外一些元编程需要做的事情。

元编程关注以下这些规定:

  • 代码检查自身
  • 代码修改自身
  • 或者代码改变语言的默认行为导致其他代码受影响。

元编程的目标是利用语言自己本身的能力更好的让你的代码变得更加的可描述,有表现力和灵活。正是因为元编程的元性质,所以找一个更精确的定义给它是一件比较难的事情。理解元编程的最好的方法,我觉得就是通过一个个例子。

ES6为 Javascript 在原有的基础之上增加了一些新的特性来更好的支持元编程。

函数名

在有的时候,你的代码也许想要检查自己并想知道一些函数的名字是什么。如果你询问一个函数的名字是什么,答案会是出奇的模糊的。我们细想一下:

function daz() {}

var obj = {
    foo: function() { },
    bar: function baz() { },
    bam: daz,
    zim() { }
};

在上面的代码段中,想想看这么几个问题?

  • obj.foo()的名字是什么?是"foo"""underfined
  • obj.bar()的名字是什么?是"bar"或者"baz"
  • obj.bam()的名字是什么?是"bam"或者"daz"
  • obj.zim()的名字是什么?

除此之外,还有那些作为回调函数传递的函数呢?

function foo(cb) {
    // 这里的`cb()`的名字是什么?
}

foo( function(){
    // 我是匿名的!
} );

在程序中,我们有相当多的方法让那些函数名模糊的函数变得具有可描述性。

还有更重要的是,我们需要区分函数的名字("name" of a function)是不是来自于函数自己的"name"属性,或者说它来自于它的词法约束名,比如"bar"function bar() {}的关系?答案是是的。

函数的词法约束名是什么?在递归中,你应该用过:

function foo(i) {
    if (i < 10) return foo(i * 2);
    return i;
}

name属性是我们用元编程的关键之处,所以我们会集中在这上面讨论。

关于函数名的混淆问题是因为在默认情况下,函数的词法绑定名和函数的name属性是一样的。实际上在ES5(包括之前的版本)对其这样的行为都没有官方的需求和解释。name的属性设置是非标准的,但确实有需求,并可靠的。值得庆幸的是,从ES6开始,name属性已经变得是标准化了。

推断

但是一个没有词法绑定名的函数它的name属性是什么呢?

ES6开始有了标准的推断规则来确定一个合理的name属性的值来分配给一个函数,及时函数没有被赋予词法绑定名。

var abc = function() {
    // ..
};

abc.name; // "abc"

如果我们强制给定词法绑定名的话,像这样

var abc = function def() {
    // ..
}

abc.name; // "def"          

那么name属性的值将会理所当然的是"def"。在没有缺少词法绑定名的情况下,直观的"abc"看起来才是适当的name的值。

下面是在ES6中的其他形式的声明的函数推断出来name属性的值的结果:

(function(){ .. });                 // name:
(function*(){ .. });                // name:
window.foo = function(){ .. };      // name:

class Awesome {
    constructor() { .. }            // name: Awesome
    funny() { .. }                  // name: funny
}

var c = class Awesome { .. };       // name: Awesome

var o = {
    foo() { .. },                   // name: foo
    *bar() { .. },                  // name: bar
    baz: () => { .. },              // name: baz
    bam: function(){ .. },          // name: bam
    get qux() { .. },               // name: get qux
    set fuz() { .. },               // name: set fuz
    ["b" + "iz"]:
        function(){ .. },           // name: biz
    [Symbol( "buz" )]:
        function(){ .. }            // name: [buz]
};

var x = o.foo.bind( o );            // name: bound foo
(function(){ .. }).bind( o );       // name: bound

export default function() { .. }    // name: default

var y = new Function();             // name: anonymous
var GeneratorFunction =
    function*(){}.__proto__.constructor;
var z = new GeneratorFunction();    // name: anonymous

name属性是默认不可写的,但是是可配置的,这意味着如果你十分想重写它的话,可以用Object.defineProperty()来实现。

元属性

在本书在三章的new.target部分,我们引入了ES6一个新概念:元属性。元属性旨在提供特殊的元信息的访问方式。

在有new.target的情况下,关键字new被用作为一个属性访问的上下文。准确的来说,new自己本身并不是一个对象,只是它有特殊的能力而已(使得new看起来像一个对象了)。不过,在函数被构造调用(一个函数或者方法被new调用)的时候的内部用了new.targetnew变成了一个虚拟的上下文,因此那个new.target能够指向被new调用的构造函数。

一个很清楚的元编程操作的,其目的是从构造函数确定new的目标是什么,内省(检查输入/结构)或者静态属性的访问。

举个例子,如果你想要根据直接调用或者被一个子类调用产生不同的行为的话,可以这样做:

class Parent {
    constructor() {
        if (new.target === Parent) {
            console.log( "Parent instantiated" );
        }
        else {
            console.log( "A child instantiated" );
        }
    }
}

class Child extends Parent {}

var a = new Parent();
// Parent instantiated

var b = new Child();
// A child instantiated

这里有一些细小的差别,在父类中的构造函数是被实际赋予了词法绑定名的。

需要注意的是:如同所有的元编程技巧,要小心创建你未来的自己或他人维护你的代码,让其是能够被理解的,谨慎使用这些编程方法。

Symbols

在第二章的"Symbols"部分中,我们了解了ES6中新的原始类型symbol,除此之外,你可以在自己的程序中定义symbols,Javascript 预定义了些内置的symbols

这些内置的symbols主要是为了暴露一些特殊的元属性来让你对 Javascript 的行为有更多的控制权。

我们来简单介绍和讨论下它们的用处。

Symbol.iterator

在第二章和第三种中,我们已经介绍并使用过了@@iterator,它会在展开运算符...for..of循环中被自动使用。同时我们也看到了@@iterator也被 ES6 作为一个新的部分所添加。

Symbol.iterator指向该对象的默认遍历器方法,即该对象进行for..of循环时,会调用这个方法。

然而,我们还可以通过Symbol.iterator为任何对象定义自己想要的的迭代逻辑,甚至去重写默认的迭代器。元编程就是我们去定义 Javascript 的那些操作运算循环结构的行为。

看下这个例子:

var arr = [4,5,6,7,8,9];

for (var v of arr) {
    console.log( v );
}
// 4 5 6 7 8 9

// define iterator that only produces values
// from odd indexes
arr[Symbol.iterator] = function*() {
    var idx = 1;
    do {
        yield this[idx];
    } while ((idx += 2) < this.length);
};

for (var v of arr) {
    console.log( v );
}
// 5 7 9

Symbol.toStringTagSymbol.hasInstance

元编程中最常见的任务就是去内省一个值,判断一个类型,以此决定其业务逻辑。最常见的两种检验技术就是toString()instance of

比如:

function Foo() {}

var a = new Foo();

a.toString();               // [object Object]
a instanceof Foo;           // true

从 ES6 开始,你能够控制toString()instance of的行为:

function Foo(greeting) {
    this.greeting = greeting;
}

Foo.prototype[Symbol.toStringTag] = "Foo";

Object.defineProperty( Foo, Symbol.hasInstance, {
    value: function(inst) {
        return inst.greeting == "hello";
    }
} );

var a = new Foo( "hello" ),
    b = new Foo( "world" );

b[Symbol.toStringTag] = "cool";

a.toString();               // [object Foo]
String( b );                // [object cool]

a instanceof Foo;           // true
b instanceof Foo;           // false

对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instance of运算符,判断是否为该对象的实例时,会调用这个方法。

Symbol.species

在第三章中,我们介绍过了@@species,它指向一个方法,该对象作为构造函数创造实例时,会调用这个方法。

我们可以通过Symbol.species来改写构造函数默认行为:

class Cool {
    // Symbol.species属性默认的读取器
    static get [Symbol.species]() { return this; }

    again() {
        return new this.constructor[Symbol.species]();
    }
}

class Fun extends Cool {}

class Awesome extends Cool {
    // 强制将子类的构造行为变成父类的
    static get [Symbol.species]() { return Cool; }
}

var a = new Fun(),
    b = new Awesome(),
    c = a.again(),
    d = b.again();

c instanceof Fun;           // true
d instanceof Awesome;       // false
d instanceof Cool;          // true

如果this.constructor[Symbol.species]存在,就会使用这个属性作为构造函数,来创造新的实例对象。

如果你想定义一个方法生成一个新的实例,你应该使用new this.constructor[Symbol.species](..),而不是new this.constructor(..)或者new XYZ(..),这样子类是能通过Symbol.species来控制构造函数能生产你想要你的实例。

Symbol.toPrimitive

在类型和语法系列中,我们讨论过一些类型的显式隐式转换,在 ES6 之前,我们是没有办法去控制变量类型转换的行为的。

从 ES6 开始,对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。从此我们可以定义自己的类型转换的行为。

var arr = [1,2,3,4,5];

arr + 10;               // 1,2,3,4,510 原本的规则

arr[Symbol.toPrimitive] = function(hint) {
    if (hint == "default" || hint == "number") {
        // sum all numbers
        return this.reduce( function(acc,curr){
            return acc + curr;
        }, 0 );
    }
};

arr + 10;               // 25 更改后的效果

Symbol.toPrimitive被调用时,会接受一个字符串hint参数,表示当前运算的模式,一共有三种模式。

  • Number:该场合需要转成数值
  • String:该场合需要转成字符串
  • Default:该场合可以转成数值,也可以转成字符串
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error();
     }
   }
};

2 * obj // 246
3 + obj // '3default'
obj === 'default' // true
String(obj) // 'str'

上面的例子想告诉我们,+运算符是default模式,*运算符是数值模式String(..)是字符串模式。

注意: ==运算符是default模式,===运算符不会命中任何模式。

Regular Expression Symbols

下面是4个关于正则表达式对象的内置Symbols,它们能够控制字符串原型上的4个同名方法:

对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)

class MyMatcher {
  [Symbol.match](string) {
    return 'hello world'.indexOf(string);
  }
}

'e'.match(new MyMatcher()) // 1

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)

对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

class MySearch {
  constructor(value) {
    this.value = value;
  }
  [Symbol.search](string) {
    return string.indexOf(this.value);
  }
}
'foobar'.search(new MySearch('foo')) // 0

对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。

String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)

**注意:**覆盖内置的正则表达式算法,你需要十分的小心,因为 Javascript 自己的正则表达式引擎已经是高度优化过了,如果你使用自己的代码,可能会慢很多。元编程虽然强大,但是你需要在真的有必要的情况和油漆的情况下使用。

Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。

var a = [1,2,3],
    b = [4,5,6];

b[Symbol.isConcatSpreadable] = false;

[].concat( a, b );      // [1,2,3,[4,5,6]]

Symbol.unscopables

对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。

var o = { a:1, b:2, c:3 },
    a = 10, b = 20, c = 30;

o[Symbol.unscopables] = {
    a: false,
    b: true,
    c: false
};

with (o) {
    console.log( a, b, c );     // 1 20 3
}

true意味着对象上的这个属性会被with排除,因此从词法返回变量中过滤掉,由with环境外的值代替。false则相反。

**注意:**在 Javascript 的 strict模式下,with是被禁用的。因此我们考虑不要用它。在作用域和闭包章节,我们知道了,应该避免去使用with,所以Symbol.unscopables也就变得无意义了。

Proxies

ES6中一个最明显的为元编程提供的特性就是Proxy了。

Proxy是为其他的一些普通的对象最一层拦截,你能够其上注册一些特殊的回调,外界对该对象的访问,都会通过这层拦截,并触发回调。因此提供了一种机制,可以对外界的访问进行过滤和改写,为目标对象添加自己额外的逻辑。

下面这个例子,就是在对象的一个属性的get进行拦截:

var obj = { a: 1 };
var handlers = {
  get(target,key,context) {
    // note: target === obj,
    // context === pobj
    console.log( "accessing: ", key );
    return Reflect.get(
      target, key, context
    );
  }
};
pobj = new Proxy( obj, handlers );

obj.a; // 1

pobj.a;
// accessing: a
// 1

我们声明handlers,并给它定义了一个get方法(其实handlers就是Proxy()的第二个参数),它接受到目标对象obj,关键属性a和代理对象pobj

通过Reflect.get(),我们不仅执行了console.log(),还进入了obj中,对属性a就行了访问。我们将在下介绍Reflect,但是请注意,每一个可用的代理都会有一个对应的同名的反射函数。

这些同名的设计的映射是故意而为之的,执行一个相应的元编程的时候,代理对其进行拦截,同时反射在每个对象上执行元编程任务。每个代理都有其默认的定义,对应的反射会被自动调用。在绝大多数情况下,你是会同时用到代理和反射的。

下面是Proxy支持的拦截操作一览。

对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

get(target, propKey, receiver)

拦截对象属性的读取,比如proxy.fooproxy['foo']Reflect.get(..),返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。

set(target, propKey, value, receiver)

拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = vReflect.set(..),返回一个布尔值。

has(target, propKey)

拦截propKey in proxyReflect.has(..)的操作,返回一个布尔值。

deleteProperty(target, propKey)

拦截delete proxy[propKey]Reflect.deleteProperty(..)的操作,返回一个布尔值。

enumerate(target)

拦截for (var x in proxy)Reflect.enumerate(..),返回一个遍历器。

ownKeys(target)

Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)Reflect.get()Reflect.ownKeys(..),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。

getOwnPropertyDescriptor(target, propKey)

拦截Object.getOwnPropertyDescriptor(proxy, propKey)Reflect.getOwnPropertyDescriptor(..),返回属性的描述对象。

defineProperty(target, propKey, propDesc)

拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs)Reflect.defineProperty(..),返回一个布尔值。

preventExtensions(target)

拦截Object.preventExtensions(proxy)Reflect.preventExtensions(..),返回一个布尔值。

getPrototypeOf(target)

Object.getPrototypeOf(proxy)Reflect.preventExtensions(..)__proto__Object.isPrototypeOf()instanceof,返回一个对象。

isExtensible(target)

拦截Object.isExtensible(proxy)Reflect.isExtensible(..),返回一个布尔值。

setPrototypeOf(target, proto)

拦截Object.setPrototypeOf(proxy, proto)Reflect. setPrototypeOf(..),返回一个布尔值。

如果目标对象是函数,那么还有两种额外操作可以拦截。

apply(target, object, args)

拦截Proxy实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)Reflect.apply(..)

construct(target, args, proxy)

拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)Reflect.construct(..)

**注意:**需要更多以上的相关信息,需要你看接下来的Reflect章节。

除了以上列表给出的触发动作以外,还有些拦截可能是通过其他其他动作间接触发。比如:

var handlers = {
        getOwnPropertyDescriptor(target,prop) {
            console.log(
                "getOwnPropertyDescriptor"
            );
            return Object.getOwnPropertyDescriptor(
                target, prop
            );
        },
        defineProperty(target,prop,desc){
            console.log( "defineProperty" );
            return Object.defineProperty(
                target, prop, desc
            );
        }
    },
    proxy = new Proxy( {}, handlers );

proxy.a = 2;
// getOwnPropertyDescriptor
// defineProperty

getOwnPropertyDescriptor(..)defineProperty(..)被默认的set()触发了,如果你定义了自己set()的拦截,你可能会也可能不会使用上下文中的变量,这将触发这些代理(自己定义set()后,不触发了。)

Proxy 的局限

有些操作是不属于可拦截的。比如,对象的运算操作是不能被拦截的:

var obj = { a:1, b:2 },
    handlers = { .. },
    pobj = new Proxy( obj, handlers );

typeof obj;
String( obj );
obj + "";
obj == pobj;
obj === pobj

也许在未来,Javascript 能给我们这样的权利。

使用 Proxy

代理的前置与后置

这些代理处理程序的元编程的好处应该是显而易见的。几乎完全拦截我们可以(和覆盖)对象的行为,这意味着我们可以在一些非常强大的方式超出核心 Javascript 对象行为。我们来看看几个例子模式探索的可能性。

正如我们前面所提到的,通常认为代理就是“包装”的目标对象。在这个意义上,代理成为主对象的代码的接口,为实际的目标对象保持隐藏/保护。

有时参数接受变量,不一定是值得信赖,所以你需要强制执行一些特殊的规则。比如:

var messages = [],
    handlers = {
        get(target,key) {
            // string value?
            if (typeof target[key] == "string") {
                // filter out punctuation
                return target[key]
                    .replace( /[^\w]/g, "" );
            }

            // pass everything else through
            return target[key];
        },
        set(target,key,val) {
            // only set unique strings, lowercased
            if (typeof val == "string") {
                val = val.toLowerCase();
                if (target.indexOf( val ) == -1) {
                    target.push(val);
                }
            }
            return true;
        }
    },
    messages_proxy =
        new Proxy( messages, handlers );

// elsewhere:
messages_proxy.push(
    "heLLo...", 42, "wOrlD!!", "WoRld!!"
);

messages_proxy.forEach( function(val){
    console.log(val);
} );
// hello world

messages.forEach( function(val){
    console.log(val);
} );
// hello... world!!

我称这种设计为代理前置,这也是主要的代理设计。

我们强制定义了一些特殊的规则去影响messages_proxy,具体逻辑请阅读上面例子的注释。

或者,我们可以完全反转这个模式。当目标对象影响代理而不是代理影响目标对象的时候,代码真的只与主对象互相影响了。做到这一点最简单的方案是要在主要对象的[[Prototype]]链中代理对象。

比如:

var handlers = {
        get(target,key,context) {
            return function() {
                context.speak(key + "!");
            };
        }
    },
    catchall = new Proxy( {}, handlers ),
    greeter = {
        speak(who = "someone") {
            console.log( "hello", who );
        }
    };

// setup `greeter` to fall back to `catchall`
Object.setPrototypeOf( greeter, catchall );

greeter.speak();                // hello someone
greeter.speak( "world" );       // hello world

greeter.everyone();             // hello everyone!

我们直接和greeter而不是和catchall交互。当我们调用speak(),它是在greeter上发现被直接调用的。但是,当我们试图去调用像everyone()的方法的时候,改方法其实是不存在的。

默认的对象属性会去查找[[Prototype]]链,所以catchall会去参看对象上的每一个属性。代理的get()会去调用speak(),所以才有hello everyone!这一结果。

我称这种设计为代理后置,这是使用代理的最后手段。

没有找到这样的属性或者方法

关于 Javascipt 常见的抱怨是:设置不存在的属性的时候是没有报错的。你可能希望预定义所有的对象属性和方法,并且如果有一个不存在的属性名随后使用抛出一个错误。

我们可以通过使用Proxy来实现,代理的前置后置都可以,比如:

var obj = {
        a: 1,
        foo() {
            console.log( "a:", this.a );
        }
    },
    handlers = {
        get(target,key,context) {
            if (Reflect.has( target, key )) {
                return Reflect.get(
                    target, key, context
                );
            }
            else {
                throw "No such property/method!";
            }
        },
        set(target,key,val,context) {
            if (Reflect.has( target, key )) {
                return Reflect.set(
                    target, key, val, context
                );
            }
            else {
                throw "No such property/method!";
            }
        }
    },
    pobj = new Proxy( obj, handlers );

pobj.a = 3;
pobj.foo();         // a: 3

pobj.b = 4;         // Error: No such property/method!
pobj.bar();         // Error: No such property/method!

通过get()set()的代理,我们可以在真正的操作之前去判断对象的属性是否存在,如果是不存在的属性,就抛出错误。代理对象pobj是主要的与代码交互的对象,它为真是的对象提供了保护。

现在,我们看下代理后置:

var handlers = {
        get() {
            throw "No such property/method!";
        },
        set() {
            throw "No such property/method!";
        }
    },
    pobj = new Proxy( {}, handlers ),
    obj = {
        a: 1,
        foo() {
            console.log( "a:", this.a );
        }
    };

// setup `obj` to fall back to `pobj`
Object.setPrototypeOf( obj, pobj );

obj.a = 3;
obj.foo();          // a: 3

obj.b = 4;          // Error: No such property/method!
obj.bar();          // Error: No such property/method!

代理后置很简单,我们不需要去拦截[[Get]][[Set]],只要去期待他们,如果目标属性存在,我们就根据默认行为去访问它,如果不存在,就到了原型链上去找对应的属性,结果就被拦截了,抛出了错误。在代码里面,我们少些了很多的逻辑,很酷吧?

让代理去“改变”原型链

原型链的向上查找机制是我们所熟知的。在对象如果一个属性不存在,则会向其原型继续查找,这意味着你可以使用代理的get()去模拟或者拓展原型机制的概念。

我们第一个要改变的事情是去考虑创建两个通过原型循环链接的对象(或者,至少它们看起来是这样的。)你不能真正创造一个真正的双向循环的原型链,因为 Javascript 的引擎会抛出错误。但是Proxy可以伪造!

var handlers = {
        get(target,key,context) {
            if (Reflect.has( target, key )) {
                return Reflect.get(
                    target, key, context
                );
            }
            // fake circular `[[Prototype]]`
            else {
                return Reflect.get(
                    target[
                        Symbol.for( "[[Prototype]]" )
                    ],
                    key,
                    context
                );
            }
        }
    },
    obj1 = new Proxy(
        {
            name: "obj-1",
            foo() {
                console.log( "foo:", this.name );
            }
        },
        handlers
    ),
    obj2 = Object.assign(
        Object.create( obj1 ),
        {
            name: "obj-2",
            bar() {
                console.log( "bar:", this.name );
                this.foo();
            }
        }
    );

// fake circular `[[Prototype]]` link
obj1[ Symbol.for( "[[Prototype]]" ) ] = obj2;

obj1.bar();
// bar: obj-1 <-- through proxy faking [[Prototype]]
// foo: obj-1 <-- `this` context still preserved

obj2.foo();
// foo: obj-2 <-- through [[Prototype]]

**注意:**在这个例子中,我们不需要使用set()代理,我们想让例子变得简单。如果为了完全模仿原型链机制,你可能还要去实现一个set(),搜索原型链相匹配的属性并遵守对象属性的描述法行为。

在上面的代码片段中,obj2是通过以obj1为原型创建的。但要创建反向的联动的,我们就需要在obj1的属性上创建一个Symbol.for("[[Prototype]]"),这个symbol的设置看似十分的黑魔法,但事实并非如此,它只是让程序能方便的和要执行的任务所关联。

然后,代理的get()首先去查找,先判断目标对象上有没有该key,如果没有,就手动切换到存储在Symbol.for("[[Prototype]]"),接下来的任务就交给Symbol.for("[[Prototype]]")的值了。

这种模式的一个重要的优点是,obj1obj2的定义基本是不通过它们之间的这种循环关系侵入。虽然上面的代码为了说明白道理而有些琐碎,但如果你仔细观察,代理的处理程序逻辑是通用的(并不需要明确的obj1obj2)。所以这些逻辑完全可以抽象出来,写成一个可以叫setCircularPrototypeOf(..)的函数,我们把这个函数的完成读者。

现在,我们已经看到了如何通过代理get()去模拟原型链,让我们去想的更加远一点,那一个对象有多个原型(又名“多重继承”)?实现起来相当简单:

var obj1 = {
        name: "obj-1",
        foo() {
            console.log( "obj1.foo:", this.name );
        },
    },
    obj2 = {
        name: "obj-2",
        foo() {
            console.log( "obj2.foo:", this.name );
        },
        bar() {
            console.log( "obj2.bar:", this.name );
        }
    },
    handlers = {
        get(target,key,context) {
            if (Reflect.has( target, key )) {
                return Reflect.get(
                    target, key, context
                );
            }
            // fake multiple `[[Prototype]]`
            else {
                for (var P of target[
                    Symbol.for( "[[Prototype]]" )
                ]) {
                    if (Reflect.has( P, key )) {
                        return Reflect.get(
                            P, key, context
                        );
                    }
                }
            }
        }
    },
    obj3 = new Proxy(
        {
            name: "obj-3",
            baz() {
                this.foo();
                this.bar();
            }
        },
        handlers
    );

// fake multiple `[[Prototype]]` links
obj3[ Symbol.for( "[[Prototype]]" ) ] = [
    obj1, obj2
];

obj3.baz();
// obj1.foo: obj-3
// obj2.bar: obj-3

**注意:**和前面在实现循环原型链中提到的一样,我们没有去实现set()相关的处理,模拟[[Set]]的动作会很复杂。

obj3被设置看似是2个原型。在obj3.baz()中,会去先去请求obj1.foo()(因为先到先得,我们obj3[ Symbol.for("[[Prototype]]" )]数组中的第一个值是obj1)。如果我们改变顺序,让obj2排在第一个,可能结果就不一样了。

但是,如果在obj1中,没有找到bar(),它就会去obj2去查找了。

obj1obj2代表了obj3的两个平行的原型链。obj1obj2本身可能还有自己正常的原型,也可以像obj3那样是模拟的原型,可多委托。

正如前面的循环原型链一样,多重继承一样可以抽象出一个可以叫setPrototypesOf(..)的函数(注意这里有个“S”),这个任务同样留个读者吧!

使用 Reflect

Reflect对象和是一个普通的对象(像Math),而不是一个内置函数或者构造器。

它拥有对应的可以控制各种元编程任务的静态方法。这些功能和Proxy一一对应。

下面的这些名称你可能看起来很眼熟(因为他们也是Object上的方法):

  • Reflect.getOwnPropertyDescriptor(..)
  • Reflect.defineProperty(..)
  • Reflect.getPrototypeOf(..)
  • Reflect.setPrototypeOf(..)
  • Reflect.preventExtensions(..)
  • Reflect.isExtensible(..)

这些方法和在Object上的同名方法一样。然后,一个区别在于,Object上这么方法的第一个参数是一个对象,Reflect遇到这种情况会扔出一个错误。

补充:
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

  • Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。
  • 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  • Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

一个对象的键们可以这样被访问:

  • Reflect.ownKeys(..):返回对象自己的键(不是通过“继承”的),同样也返回Object.getOwnPropertyNames()Object.getOwnPropertySymbols()
  • Reflect.enumerate(..):返回对象上可枚举的属性(包括“继承”过来的)。
  • Reflect.has(..):和in操作符差不多。

函数的调用和构造调用可以手动通过下面 API 执行:

-Reflect.apply(..):举个例子,Reflect.apply(foo,thisObj,[42,"bar"])调用了foo()函数,上下文是thisObj,参数为42bar
-Reflect.construct(..):举个例子,Reflect.construct(foo,[42,"bar"])等于new foo(42,"bar")

对象属性访问,设置,删除可以手动通过下面 API 执行:

  • Reflect.get()Reflect.get(o,"foo")等于o.foo
  • Reflect.set()Reflect.set(o,"foo",42)等于o.foo = 42
  • Reflect.deleteProperty()Reflect.deleteProperty(o,"foo")等于delete o.foo

Reflect的元编程能力可以让你等效的模拟以前隐藏的各种语法特性。这样你就可以使用这些功能为特定语言(DSL)拓展新功能和 API。

在 ES6 之前,规范里面没有定义如何列出顺序的列出对象和列出对象的性能,一般来说,是大多数 Javascript 的引擎来创建对象属性的属性的,因此软件开发者们强烈建议不用根据依赖它排的顺序。

到了 ES6,对象的可枚举的键的顺序的算法被规定。

这个规则是:

  1. 首先,如果索引是整数,升序。
  2. 其次,根据来根据索引String创建排序。
  3. 最后,根据Symbol创建顺序。

例子:

var o = {};

o[Symbol("c")] = "yay";
o[2] = true;
o[1] = true;
o.b = "awesome";
o.a = "cool";

Reflect.ownKeys( o );               // [1,2,"b","a",Symbol(c)]
Object.getOwnPropertyNames( o );    // [1,2,"b","a"]
Object.getOwnPropertySymbols( o );  // [Symbol(c)]

如果还有原型上的键值呢?

var o = { a: 1, b: 2 };
var p = Object.create( o );
p.c = 3;
p.d = 4;

for (var prop of Reflect.enumerate( p )) {
    console.log( prop );
}
// c d a b

for (var prop in p) {
    console.log( prop );
}
// c d a b

JSON.stringify( p );
// {"c":3,"d":4}

Object.keys( p );
// ["c","d"]

在 ES6 中,Reflect.ownKeys(..)Object.getOwnPropertyNames(..)Object.getOwnPropertySymbols(..)的顺序是可靠可预测的,因此它是安全的。

Reflect.enumerate(..)Object.keys(..)for..inJSON.stringify(..)继续共享一个可观察的排序,但是排序不一定和Reflect.ownKeys(..),所以我们仍然需要小心。

END

我以前不知道的 margin

margin 与容器之间的关系

margin 可以改变容器尺寸

标准盒模型与元素尺寸

https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png

元素尺寸

  1. 可视尺寸 - clientWidth (实线)
  2. 占据尺寸 - outerWidth (虚线)

margin 与可视尺寸

  1. 适用于没有设定 width / height 的普通 block 元素 float absolute fixed inline table-cell
  2. 只适用于水平方向尺寸
  3. 正负值亦可

栗子

<div class="out">
	<div class="in">
	i am a block element
	</div>
</div>
.out {
	padding: 50px;
	background: red;
}

// 调整 .inmargin
.in {
	margin: 50px 20px;
	background: green
}

如何利用这一特性?

一侧定宽的自适应布局

栗子

<div class="out">
	<img width="100" style="float: left" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
	<p>我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字</p>
</div>
p {
	margin-left: 120px;
}

margin 与占据尺寸

  1. block / inline-block 水平元素都适用
  2. 与有没有设定 width / height 无关
  3. 适用于水平和垂直方向

栗子

<div class="out">
	<img width="150" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
</div>
// 修改 img 的 margin-bottom
img {
	margin-bottom: 50px;
}

.out {
	background: green;
  	text-align: center;
}

如何利用这一特性?

滚动容器内上下留白

栗子

<div class="out">
	<img width="500" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
</div>
.out {
	padding: 50px 0;
	height: 200px;
	overflow: auto;
}

img {
	margin: 50px 0;
}

margin 的百分比单位

  • 水平 / 垂直方向百分比
  • 普通 / 绝对定位元素百分比
  • 普通元素的百分比 margin 都是相对于容器的宽度
  • 绝对定位元素的百分比 margin 是相对于第一个定位祖先元素的宽度

如何利用这一特性?

宽高比例保持的自适应容器

栗子

<div class="out">
	<div class="box">
	</div>
</div>
.out {
	background: green;
	overflow: hidden;
}
// margin 重叠
.box {
	margin: 50%;
}

margin 重叠

产生 margin 重叠的条件

  1. 必须 block 元素
  2. 不考虑 writing-mode 的话,只发生在垂直方向

margin 重叠的情形

  1. 相邻兄弟元素
  2. 父级和第一个 / 最后一个子元素
  3. 空的 block 元素

相邻兄弟元素

栗子

<p>第一行</p>
<p>第二行</p>
p {
	line-height: 2em;
	margin: 1em 0;
	background: green;
}

父级和第一个 / 最后一个子元素

栗子

<div class="p" style="margin-top: 80px">
	<div class="c" style="margin-top: 80px">我是子</div>
</div>
.p {
	background: yellow;
}

父子 margin 重叠的其他条件

margin-top 重叠

  1. 父元素不是 BFC
  2. 父元素没有 border-top
  3. 父元素没有 padding-top
  4. 父元素和第一个子元素之间没有 inline 元素分割

margin-bottom 重叠

  1. 父元素不是 BFC
  2. 父元素没有 border-top
  3. 父元素没有 padding-top
  4. 父元素和第一个子元素之间没有 inline 元素分割
  5. 父元素没有设置 height min-height max-height

空的 block 元素

栗子

<div class="p">
	<div class="c"></div>
</div>
.p {
	background: green;
	overflow: hidden;
}

.c {
	margin: 10px;
}

一些条件限制

  1. 没 border
  2. 没 padding
  3. 内没 inline 元素
  4. 没 height / min-height

margin 重叠的计算规则

  1. 正正取大值
  2. 正负值相加
  3. 负负最小值

margin 重叠的意义

最初

<h2>h2</h2>
<p>p1</p>
<p>p2</p>
<ul>
  <li>l1</li>
  <li>l2</li>
  <li>l3</li>
</ul>

margin 的 auto

maring: auto 的作用机制

你已经知道的事实

  • 元素有时候,就算没有设置 width / height,也会自动填充;
div {
	background: red;
}
div {
	position: absolute;
	left: 0;
	right: 0;
}
  • 如果设置 width / height,自动填充特性就会被覆盖
div {
	width: 200px;
}
// 此时 margin 是 0;
  • 原来应该填充的尺寸被 width / height 强制变更,而 margin: auto 就是为了填充这个变更尺寸设计的;
div {
	width: 200px;
	margin-right: 100px;
	margin-left: auto;
}
  • 为什么图片 margin: 0 auto; 布局?解决~
  • 为什么明明容器定高,元素定高,margin: auto 0; 无法垂直居中?解决~
  • 那我们垂直方向的话,margin 就无解了吗?
  • writing-mode: vertical-lr
  • 绝对定位元素的 marin: auto; 的居中

例子

.p {
	position: relative;
	height: 500px;
}
.c {
	positon: absolute;
	top: 0;
	bottom: 0;
	right: 0;
	left: 0;
}
.p {
  display: flex;
  height: 500px;
  background: green;
}

.c {
  margin: auto;
  height: 100px;
  width: 100px;
  background: yellow;
}

margin 负值定位

1. 两端对齐

<div class="box">
  <div class="p">
    <div class="c"></div>
    <div class="c"></div>
    <div class="c"></div>
  </div>
</div>
.box {
  width: 1200px;
  background: red;
}

.p {
  margin-right: -20px;
  overflow: hidden;
}

.c {
  width: 386.66px;
  height: 300px;
  background: yellow;
  float: left;
  margin-right: 20px;
}

2. margin 负值下的等高布局

.box {
	overflow: hidden;
}
.cl, .cr {
	float: left;
}
.cl {
	background: red;
}
.cr {
	background: yellow;
}

3. margin 负值下的两栏自适应布局

元素占据空间跟随 margin 移动

<div class="out">
	<img width="100" style="float: right;" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
	<p>我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字</p>
</div>
p {
	margin-right: 120px;
}

DOM 顺序和视觉不符,那么这样~

<div style="float: left; width: 100%;">
	<p style="margin-right: 120px"></p>
</div>
<img width="100" style="float: left; margin-left: -150px" />

margin 无效情形解析

  1. inline 元素垂直 margin 天然无效
  2. 可能 margin 重叠了
  3. display: table-cell 与 margin https://developer.mozilla.org/en-US/docs/web/css/margin
  4. position: absolute 与 margin (绝对定位元素非定位放线的 margin 值“无效” relative)
  5. 内联特性导致的 margin 无效
<!--测试例子5-->
<div style="height: 200px; background: yellow">
	<img width="200" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
</div>

margin-start / margin-end 的了解

  • 正常的流向,margin-start 等同于 margin-left,两者重叠不累加;
  • 如果水平流是从右往左,margin-start 等同于 margin-right;
  • 垂直流(writing-mode: vertical-*;),margin-start 等同于 margin-top;

margin-before / after

margin-collapse

当元素发生 margin 重叠的时候,具体该如何表现

  • collapse
  • discard
  • separate

ES6 中 Class 的 super

class A { 
  constructor(a) {
    this.a = a
  }
}

class B extends A {
  constructor(a, b) {
    super(a)
    this.b = b
  }
}

var a = new A(a)
var b = new B(a, b)

在 ES6 中,这样的用法,我想已经总所周知了,但是super在使用的时候,我还需要知道一些它的其他特性。

在子类的构造函数中,只有调用super()之后,才可以使用this关键字

super同时还可以看作是一个对象,这个对象是父类(用 ES5 的概念说的话,是被构造调用的函数的原型)

所以我们还可以这样使用super

class A {
  foo() { 
    console.log(1)
  }
}

class B extends A {
  bar() {
    super.foo()
  }
}

var b = new B()
b.bar() // 1

这样我们就可以面向委托编程,至于什么是面向委托,可以参考《你不知道的 Javascript》

更新

(1)作为函数调用时(即super(...args)),super代表父类的构建函数。
(2)作为对象调用时(即super.propsuper.method()),super代表父类。注意,此时super即可以引用父类实例的属性和方法,也可以引用父类的静态方法。

HTTPS

https://certbot.eff.org/#ubuntuxenial-nginx

server {
  listen 443 ssl;
  server_name blog.lishengcn.cn;
  root /home/ubuntu/blog;

  ssl_certificate            /etc/letsencrypt/live/lishengcn.cn/fullchain.pem;
  ssl_certificate_key        /etc/letsencrypt/live/lishengcn.cn/privkey.pem;
  ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers  on;
  ssl_ciphers                AES256+EECDH:AES256+EDH:!aNULL;

  location /api {
    rewrite ^.+api/?(.*)$ /$1 break;
    proxy_pass http://127.0.0.1:8080;
  }
}
rewrite ^/(.*) https://$server_name$1 permanent;
letsencrypt certonly --standalone --email [email protected] -d b.c -d a.b.c

这是一篇没有太多解释的记录

看不懂的可以去搜关键字,😝

Set

Array Set
元素序列 有序 无序
元素可重复性 不可

遍历

const set = new Set([1, 2, 3]);
set.forEach(function(i) {
	console.log(i * this.foo)
}, { foo: 2 })

数组去重

[...new Set([1,1,2,2])]

WeakSet

Weak 版本的数据类型是无法包含无引用的元素的,一旦数据结构内的任一元素的引用被解除,该元素就会被移除当前所在的数据结构。

const weakset = new WeakSet();
weakset.add(1)
weakset.add({ foo: 1 })

let bar = { baz: 1 };
weakset.add(bar);

bar = null

Map

key 可以是任意类型

new Map([['foo', 1], ['bar', 2]])

WeakMap

const weakmap = new WeakMap();
let keyObj = { id: 1 };
let valObj = { score: 100 };

weakmap.set(keyObj, valObj);
keyObj = null

解构

多返回值

function getSome() {
	return {
		a: 1,
		b: 2,
		c: 3
	}
}

let { a, b, c} = getSome()

别名

function fetch() {
	return {
		res: [...]
	}
}

let { res: data } = fetch()

缺省

const { foo, bar } = { foo: 1 }
console.log(bar)

const [ a, b = 2 ] = [ 1 ]
console.log(a, b)

...

解构传参

Math.min.apply(null, [1,2,3])
Math.min(...[1,2,3])

对象拓展

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

ReactDOM.unstable_renderSubtreeIntoContainer

renderSubtreeIntoContainer(parentComponent, nextElement, container, callback);

class Lay extends Component {
    appendMaskIntoDoc() {
        ReactDOM.unstable_renderSubtreeIntoContainer(
            this,
            this.props.children,
            this.container
        )
    }

    componentDidMount() {
        this.container = document.createElement('div')
        document.body.appendChild(this.container)
        this.appendMaskIntoDoc()
    }

    componentDidUpdate() {
        this.appendMaskIntoDoc()
    }

    componentWillUnmount() {
        document.body.removeChild(this.container)
    }

    render() {
        return null
    }
}
<Lay>
	<Modal>
		我是内容
	</Modal>
</Lay>

*Dialog

/src/components/public/Dialog

let Alert = {
  show(config) {
    let instance;
    const container = document.createElement('div');
    const unMount = () => {
      if (config && config.afterClose && typeof config.afterClose === 'function') {
        config.afterClose();
      }
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
    };
    document.body.appendChild(container);
    ReactDOM.render(<AXDialog {...config} afterClose={unMount} />, container, function () {
      instance = this;
    });
    return instance;
  },

context

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
};

表格十字线聚焦效果续(纯CSS)

之前写过一篇表格十字线聚焦效果的分享 #11 ,但是用到了 JS,后来发现可以完全利用 CSS 来实现。

原理

通过td:hover配合::before或::after,并且将其高度设置无限高,同时改变::after::before的z-index值为 -1(让高亮的背景色变为下层)。重要的一点,需要给table设置一个overflow:hidden,将伪元素溢出的高度截取掉。

show code~

html

<main>
  <table>
    <thead>
      <tr>
        <th></th>
        <th class="col">50kg</th>
        <th class="col">55kg</th>
        <th class="col">60kg</th>
        <th class="col">65kg</th>
        <th class="col">70kg</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th class="row">160cm</th>
        <td>20</td>
        <td>21</td>
        <td>23</td>
        <td>25</td>
        <td>27</td>
      </tr>

      <tr>
        <th class="row">165cm</th>
        <td>18</td>
        <td>20</td>
        <td>22</td>
        <td>24</td>
        <td>26</td>
      </tr>

      <tr>
        <th class="row">170cm</th>
        <td>17</td>
        <td>19</td>
        <td>21</td>
        <td>23</td>
        <td>25</td>
      </tr>

      <tr>
        <th class="row">175cm</th>
        <td>16</td>
        <td>18</td>
        <td>20</td>
        <td>22</td>
        <td>24</td>
      </tr>
    <tbody>
  </table>
</main>

css

table {
  overflow: hidden;
}

td, th {
  padding: 10px;
  position: relative;
  outline: 0;
}

tbody tr:hover {
  background-color: red;
}

td:hover::after,
thead th:not(:empty):hover::after,
td:focus::after,
thead th:not(:empty):focus::after { 
  content: '';  
  height: 10000px;
  left: 0;
  position: absolute;  
  top: -5000px;
  width: 100%;
  z-index: -1;
}

td:hover::after,
th:hover::after {
  background-color: red;
}

refer

diaodiao 的 CSS child 选择器

diaodiao 的 CSS child 选择器

基础的概念和应用

选择器 🌰 🌰描述
:first-child p:first-child 选择属于父元素的第一个子元素的每个

元素。

:only-child p:only-child 选择属于其父元素的唯一子元素的每个

元素。

:nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个

元素。

:nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数。
:last-child p:last-child 选择属于其父元素最后一个子元素每个

元素。

OK,基础的东西就描述到这里,本身大家应该都了解,接下来说一些进阶的应用。

进阶应用

申明下面所有例子有应用于以下结构:

<ul>
  <li>...<li>
  ...
</ul>

选择前 3 个 li

ul li:nth-child(-n + 3) {
  ...
}

选择后 3 个 li

ul li:nth-last-child(-n + 3) {
  ...
}

选择从第 3 个开始的以后的 li

ul li:nth-child(n + 4) {
  ...
}

选择从第 2 个到第 4 个 li

ul li:nth-child(n + 2):nth-child(-n + 4) {
  ...
}

选择前后从第 3 个开始的全部

ul li:nth-child(n + 3):nth-last-child(n + 3) {
  ...
}

选择从第 2 个到 第 4 个中的奇(偶)数个 li

ul li:nth-child(odd):nth-child(n + 2):nth-child(-n + 4) {
  ...
}

or

ul li:nth-child(even):nth-child(n + 2):nth-child(-n + 4) {
  ...
}

选择从第 2 个到 第 4 个中的 3 倍数个 li

ul li:nth-child(3n):nth-child(n + 2):nth-child(-n + 4) {
  ...
}

选择除了第 3 个的全部 li

ul li:not(:nth-child(3)) {
  ...
}

选择只有 2 个 li 的第 1 个

ul li:nth-last-child(2):first-child {
  ...
}

选择 li 最少满足 3 个的情况

ul li:nth-last-child(n + 3),
ul li:nth-last-child(n + 3) ~ li {
  ...
}

选择 li 最多满足 3 个的情况

ul li:nth-last-child(-n + 3):first-child,
ul li:nth-last-child(-n + 3):first-child ~ li {
  ...
}

选择 li 满足最少 5 个最多 10 个的情况

ul li:nth-last-child(n + 5):nth-last-child(-n + 10):first-child, 
ul li:nth-last-child(n + 5):nth-last-child(-n + 10):first-child ~ li {
  ...
}

参考链接

URL.createObjectURL(new Blob[...]) CSV 乱码问题

http://blog.csdn.net/leonzhouwei/article/details/8447643

原因是 Excel 以 ANSI 格式打开,不会做编码识别。

解决方法

function downloadCSV(fileName, content) {
    let a = document.createElement('a');
    let file = URL.createObjectURL((new Blob(['\uFEFF' + content], { type: 'text/csv;charset=utf-8' })));

    a.href = file;
    a.download = `${fileName}.csv`;
    a.click();
}

http://stackoverflow.com/questions/19492846/javascript-to-csv-export-encoding-issue

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.