lishengzxc / bblog Goto Github PK
View Code? Open in Web Editor NEWMy Blog
Home Page: https://github.com/lishengzxc/bblog/issues
My Blog
Home Page: https://github.com/lishengzxc/bblog/issues
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个,保留换行
<!-- 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">
以前一直用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);
}
URL.createObjectURL 🐂 https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
业务有个需求,要做图片预览上传,过去都是客户端上传给后端,后端返回url
前端进行预览,现在其实可以不依赖后端做预览,最后在上传,这主要依赖FileReader
和FormData
这两个对象和 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 转 二进制文件
/**
* 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);
});
注意:不要漏了指定processData
和contentType
为false
。
业务中不需要前端不需要压缩,因为后端有更靠谱的压缩方案,但是前端其实也可以压缩,那就是用canvas
把图画出适合的大小,然后上传。
主要流程:
new
出来的Image
对象,我们监听它的onload
事件canvas
,尺寸设置成上一步骤算出来的压缩后的图片尺寸drawImage
方法,把图片绘制到canvas
中canvas
的toDataURL
,取出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
_
的.debounce
和.throttle
DOM 上有些事件是会频繁触发的,比如mouseover
、scroll
、resize
...。以前有个需求,是做一个图表,是用canvas
画的,最初,如果图表画完,用户拖拽浏览器窗口,改变浏览器大小的话,图表并不会自适应的变化,所以就需要监听resize
事件,每当窗口大小变化后,再重新绘制。但是resize
是频繁触发的,这就导致了页面的明显的卡顿,因为每次resize
后的回调要执行大量的计算。
当时比较急,遇到这个问题以后,直接就查了.debounce
和.throttle
,就直接用了lodash
。现在回过头了,看下源码,看看它的实现。
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
秒后,执行真正的回调。
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
,这个参数是用来配置回调函数是在一个时间区间的最开始执行(immediate
为true
),还是最后执行(immediate
为false
),如果immediate
为true
,意味着是一个同步的回调,可以传递返回值。
关键的地方是,单独拿出了一个later
函数通过控制timer
来觉得连续的时间除一开始后,是不是要执行回调。
loadsh
的debounce
,接受更多的配置:
[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.leading=true
等效于underscore
的immediate=true
。trailing
则正好相反。maxWait
设置了超时时间,在规定的超时间后,一定调用回调。(通过内部设置了两个setTimeout
,一个用来完成基础功能,让回调只执行一次,还有一个用来控制超时)
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
。它还接受leading
和trailing
来控制真正回调触发的时机,这和lodash
的_.debounce
差不太多。
这里有一个可视化分析页面:http://demo.nimius.net/debounce_throttle/,大家可以点开看看。
(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
hover
的那个<td>
高亮td:hover {
background-color: red !important;
}
一个css
搞定,主要后面的加的!important
,这是为了防止td
所在的tr
的样式覆盖td
。
hover
的那行<tr>
高亮tr:hover {
background-color: yellow;
}
同样一个css
搞定。
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>
进行事件代理。然后:
<td>
所在表格的所有的<tr>
<td>
所在<tr>
中的序号index
<td>
的高亮效果取消hover
的<td>
的所在列高亮,就是将每个<tr>
中的第index
个高亮最后,在鼠标离开table
的时候,清除所有高亮,具体的高亮<td>
和行高亮因为hover
的取消,自己没有了hover
伪类而取消,列高亮需要通过JavaScript
完成(将所有的<td>
高亮取消)。
在具体实现中,需要注意的一个细节是mouseover
和mouseleave
这两个事件:
mouseover
和mouseout
对应分别是鼠标移动到具体 DOM 上和移出具体 DOM 上,会冒泡。mouseenter
和mouseleave
也对应分别是鼠标移动到具体 DOM 上和移出具体 DOM 上,不会冒泡。因此,如果需要事件代理,就需要mouseover
和mouseout
,反之mouseenter
和mouseleave
。
上述代码仅是实现一个思路,还能很好的抽象,等业务中需要这么一个组件的话,我想我会抽象写出来的。
其实要是css
有父选择器的话,就太好解决了
先说遇到的问题:
有这样的数组:[{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
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为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 中也有一个新方法,可以代替indexOf
:includes()
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
表示围观。
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)
)
}
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-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 */})
)
}
Three.js 提供了以下一系列光源,每种光源都有其特殊的行为和用途。
光源名称 | 描述 |
---|---|
AmbientLight ['æmbɪənt] (环境光) | 这是最基础的光源,它的颜色会添加到整个场景和所有对象的当前颜色上 |
PointLight (点光源) | 空间中的一个点,朝所有的方向发射的光线 |
SpotLight (聚光灯光源) | 这种光源有聚光效果,类似手电筒 |
DirectionalLight (平行光) | 从这种光源发出的光线可以看作是平行的。例如:太阳光 |
HemisphereLight ['hemɪsfɪə](半球光) | 用来创建更加自然的室外光线,模拟反光面和光线微弱的天空 |
RectAreaLight(面光源) | 使用这种光源可以指定散发光线的平面,可不是空间中的一点 |
... | ... |
接下来我们具体看下各光源的用法和效果。
https://threejs.org/docs/index.html#api/lights/AmbientLight
var light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );
AmbientLight( color, 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.js 中的对象时,通常情况下,我们可以直接使用十六进制字符串(#0c0c0c)或者十六进制数指定颜色。然而,在对象构造完成之后,如要需要更改颜色,你就不得不使用 THREE.Color() 。 https://threejs.org/docs/index.html#api/math/Color
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 )
在场景中的特定位置创建一个光源。光线照射在各个方向上(好比一个灯泡)。
PointLight 是需要设定位置,在不同位置下的 PointLight 对其他对象都有着不同的影响,可以查看下面的 demo,除此以外,我们注意下 distance 属性,它的默认值是 0,表示光线的亮度不会随着距离的增加而递减,如果 distance 有具体值,那光线的亮度会在指定的距离处降至 0。我们可以在 demo 中将 intensity 设置比较高,然后设置 distance 为一个大于 0 的比较小的值比如 4。
https://lishengzxc.github.io/learning-threejs/chapter-03/02-point-light.html
光功率。
在“物理正确”模式中,表示以“流明(光通量单位)”为单位的光功率。
默认值 = 4PI。
沿着光照距离的衰退量。
在“物理正确”模式中,decay = 2 将实现现实世界的光衰减。
默认值 = 1。
此属性存储用来渲染光照阴影的所有相关信息。
什么是“物理正确”模式:https://threejs.org/docs/index.html#api/renderers/WebGLRenderer.physicallyCorrectLights
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 )
SpotLight 和 PointLight 的主要区别在于,我们需要为它设置 castShadow 属性为 true(如果我们需要阴影的话),以及它的 target 属性。
聚光灯的焦点位于 target.position 处。
默认值 — (0,0,0)。
注意:目前为了target属性正常工作,它必须是 scene 的一部分,也就是通过如下方法添加到场景中:
如果设置为 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 来调试它。
关于阴影,我们在开发过程中还有些是我们关心的,比如:
https://lishengzxc.github.io/learning-threejs/chapter-03/03-spot-light.html
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 的平行光特性,所以没有了光锥,取而代之的是一个方块,在这个方块内的对象都可以产生和接受阴影。
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 )
下面 demo 展示了 HemisphereLight 效果,可以通过打开或者关闭 HemisphereLight,从而可以观察到对象在 HemisphereLight 会反射来自天空和地面的颜色。
https://lishengzxc.github.io/learning-threejs/chapter-03/05-hemisphere-light.html
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 )
在下面这个 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 对象接受如下参数:
让我们看看如何创建这个对象
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
其实光源对对象产生的效果,还跟“材质”有着千丝万缕的联系,有些材质是完全不受光源影响的,比如 MeshBasicMaterial,它只会以指定的颜色渲染物体,我们会在下一篇章中,看看 Three.js 中的材质。
最后上面所有的 demo 都关联的 dat.GUI,因此通过它调整一些关键属性可以帮助你更好的理解上面提到的知识点。
参考:https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b
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 译为魔法注释(其实不想说这个的,看到魔法两个字,我就不喜欢,但是它是 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 接下来可能会带来:
因此,Webpack 1 的老铁们如果时间允许,有空的时间,我觉得可以升~
参考:https://medium.com/webpack/webpack-3-official-release-15fd2dd8f07b
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/
Did you just delete the library and run?
基础的 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 和闰年判断的逻辑没有就好了。
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是关键点)。
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)
具体的文档解释懒得再复制一下给大家看,参考链接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
这里主要和大家普及以下知识:
格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语: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(从英文「Coordinated Universal Time」/法文「Temps Universel Cordonné」而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平时
北京时间,China Standard Time,**标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。
不过这个CST这个缩写比较纠结的是它可以同时代表四个不同的时间:
总结就是,前后端去传时间的时候,尽量都用 UTC 时间。
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()
一样。
**该方法通常在 JavaScript 内部被调用,而不是在代码中显式调用。**什么意思?没有 valueOf
,那么 Date
的实例是不能进行运算的。
var obj = Object.create(null);
obj + 1; // Uncaught TypeError: Cannot convert object to primitive value(…)
直接看这个 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的指正)
不属于任何标准。在JavaScript 1.6中被实现。似乎也只有 Firefox 自持这个 API,其实正确姿势是用 .toLocaleDateString()
.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$&');
var date = new Date();
var result = date.toLocaleString('zh-CN', { hour12: false })
.replace(/\//g, '-').replace(/\b\d\b/g, '0$&');
在日常开发中遇到一个数字千分位格式化,所谓的数字千分位格式化,即从个位数起,每三位之间加一个逗号。例如“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)。此外,还要注意:
//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
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
的取整的例子,而不是String
转Number
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
的取整加速不少,但是它只适用于Number
,String
还是老老实实的吧,比如:
Math.ceil('1234.23'); //1235
parseInt('1234,23', 10); //1234
我举得例子肯定不全,欢迎大家头脑风暴~
运算符 | 用法 | 描述 |
---|---|---|
按位与(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()
返回true
orfalse
var arr = [...];
// 一般方法:
var isExist = function(array, value) {
return array.indexOf(value) > 0;
}
// 使用位运算的方法:将 -1 转变为 false
var isExist = function(array, value) {
return !!~array.indexOf(value);
}
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
// 一般方法:
if (toggle) {
toggle = 0;
} else {
toggle = 1;
}
// 一般方法的简写:
togle = toggle ? 0 : 1;
// 使用位运算的方法:
toggle ^= 1;
如果已经对 A 测试了 B,就没有必要再对 B 测试 A。我们需要维护一个二维数组保存每个碰撞对象的2个标志impactFlag
和impactFlags
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 按位与&
,就能得出目标用户对目标文件有什么权限了。
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;
}
无法理解的同学,可以单步调试下
repeat
方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart
用于头部补全,padEnd
用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
我们没要必要排斥在 JS 中使用位运算,欢迎大家补充更多的使用实例。
原文:https://threejs.org/docs/index.html#manual/introduction/Animation-system
在 three.js 的动画系统中,你可以为模型设置各种属性的动画,比如:蒙皮和绑定模型的骨骼,顶点动画,不同的材质属性(颜色,不透明度,布尔值),可见性和形变。动画属性可以淡入、淡出、交叉渐变与变形。同一时间的不同动画对同一对象以及不同对象的权重和时间尺度可以独立修改,同一对象和不同对象上个各种动画可以同步变化。
为了实现同类系统中的所有动画功能,three.js 的动画系统已经在2015年完全改变,现在它具有类似于 Unity / Unreal Engine 4 的结构体系。本章将简要概述改动画系统的主要组件以及它们如何协同工作。
如果你已经成功导入了一个动画 3D 对象(不用关心它是骨骼动画还是顶点动画或者它们都是)。举个例子,你在 Blender 中使用 Blender exporter 将模型导出,并使用 JSONLoader 将它加载到 three.js 的场景中,加载完成的几何体网格对象会有一个名为 "animations" 的数组,其中包含这个模型的 AnimationClips。
每个 AnimationClip 通常保存该对象的特定动画的数据。例如,如果网格是角色,则可能有一个用于步行循环的 AnimationClip,第二个用于跳转,第三个用于跳转等。
在 AnimationClip 中,每个动画属性的数据都存储在一个单独的 KeyframeTracks 对象中。假定一个角色模型具有骨架,一个 KeyframeTracks 可以存储随着时间推移下的骨骼位置变化的数据,另外一个用于存储骨骼的旋转变化,第三个用于旋转护着缩放另外一块骨骼。我们应该想象,AnimationClip 可以由多个这样的轨道组成。
再假设模型具有顶点动画(比如一个动画显示友好的脸部表情,另外一个动画显示愤怒的脸部表情),每个 KeyframeTracks 就保存如何影响动画片段在执行过程中的某个顶点动画。
那些被存储的数据仅仅是动画的基础,实际上动画的播放是用 AnimationMixer 来控制。你可以想象并认为它不仅是一个动画播放器,而是像一个真正的硬件混音器那般,可以用时控制几个动画并混合它们。
AnimationMixer 本身只有很少的(普通)属性与方法,因为它可以通过控制 AnimationActions。通过配置一个 AnimationAction,你可以指定它何时应该在其中一个 AnimationMixer 中播放,暂停或者停止,是否需要重复,如何重复,以及是否使用渐变还是调整动画的播放时间,还有些其他额外的东西,如交叉淡入淡出或是同步。
如果你希望一组对象接受并共享动画的状态,可以使用AnimationObjectGroup。
请注意,并非所有模型的格式都包含动画(.obj 显然不包含动画),只有以下 three.js 的加载器支持 AnimationClip:
注意,3ds max 和 Maya 目前无法将多个动画(即不在同一时间轴上的动画)直接导出到单个文件。
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();
} );
之前一段时间在做游戏,在游戏中,经常需要用到九宫图,用九宫图可以保证图片的边框不会因为拉伸而走形。在 CSS3 中也有对九宫图的支持 — border-image
。接下来就去说下它。
<'border-image-source'> || <'border-image-slice'> [ / <'border-image-width'> | / <'border-image-width'>? / <'border-image-outset'> ]? || <'border-image-repeat'>
这个没啥好说的...,真要说下,就是可以设置 base64。
这个可以好好唠个10块钱的了,先来找图,估计你看了也就明白了:
border-image-slice
就是去控制九宫图是怎么切的,根据上图,其中:
/* 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
。100px
,border-image-slice: 10% 60px;
,最后将会渲染出奇奇怪怪的东西这个可以聊5块钱,就是去设置图片边框的宽度的,顺时针上右下左。一般来说,border-image-width
和border-image-slice
的值是设置相同的(非百分比,因为border-image-width
百分比是相对于盒子的高宽的),但是border-image-width
和border-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
边框:
<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
的值可以设置的很大,但是设置很大话,又是奇奇怪怪的东西,虽然它也是有一定规则的,但是我不想去了解了(任性,以后要去好好研究看看)。
这个是用来设置边框图像可超出边框盒的大小。MDN上说是可以有设置百分比的代码DEMO,但是似乎并运行不起来。也不支持负值。
设置了border-image-outset
并不会改变最后盒子的大小。
默认值:stretch
.example {
border-image:url("/files/4127/border.png") 30 30 stretch;
}
round
.example {
border-image:url("/files/4127/border.png") 30 30 round;
}
repeat
.example {
border-image:url("/files/4127/border.png") 30 30 repeat;
}
说实话,stretch
是最常用的。
git push origin --delete tag tagname
git tag -d tagname
移动端高清屏的1px边框问题的解决方案很多,比较成熟的是用阿里无线的 https://github.com/amfe/lib-flexible 方案,它会去动态设置viewport
的initial-scale
,所以在遇到边框的时候,我们就不需要做各种的 hack 来解决(比如伪元素,border-image
)。
但是,我如果是一个比较老的项目,当时在开发的时候,并没有引入lib-flexible
,同时设计师,和产品经理都对你还原的页面吐槽不断,怎么办?我不可能重新这个是时候引入并使用lib-flexible
,同时我对 hack 方案一直都比较反对。
终于我想到一个简单的方案,同样还是1px
的单位,然后是两个选择:
rgba
的话,需要加上background-clip: padding-box;
好了,现在终于让产品和设计师满意了。
原文链接: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.target
,new
变成了一个虚拟的上下文,因此那个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"
部分中,我们了解了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.toStringTag
和Symbol.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
也就变得无意义了。
在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.foo
和proxy['foo']
、Reflect.get(..)
,返回类型不限。最后一个参数receiver
可选,当target
对象设置了propKey
属性的get
函数时,receiver
对象会绑定get
函数的this
对象。
set(target, propKey, value, receiver)
拦截对象属性的设置,比如proxy.foo = v
或proxy['foo'] = v
、Reflect.set(..)
,返回一个布尔值。
has(target, propKey)
拦截propKey in proxy
、Reflect.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()
后,不触发了。)
有些操作是不属于可拦截的。比如,对象的运算操作是不能被拦截的:
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]]")
的值了。
这种模式的一个重要的优点是,obj1
和obj2
的定义基本是不通过它们之间的这种循环关系侵入。虽然上面的代码为了说明白道理而有些琐碎,但如果你仔细观察,代理的处理程序逻辑是通用的(并不需要明确的obj1
和obj2
)。所以这些逻辑完全可以抽象出来,写成一个可以叫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
去查找了。
obj1
和obj2
代表了obj3
的两个平行的原型链。obj1
和obj2
本身可能还有自己正常的原型,也可以像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
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete 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
,参数为42
和bar
。
-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,对象的可枚举的键的顺序的算法被规定。
这个规则是:
String
创建排序。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..in
和JSON.stringify(..)
继续共享一个可观察的排序,但是排序不一定和Reflect.ownKeys(..)
,所以我们仍然需要小心。
标准盒模型与元素尺寸
元素尺寸
栗子
<div class="out">
<div class="in">
i am a block element
</div>
</div>
.out {
padding: 50px;
background: red;
}
// 调整 .in 的 margin
.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;
}
栗子
<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 是相对于第一个定位祖先元素的宽度
如何利用这一特性?
宽高比例保持的自适应容器
栗子
<div class="out">
<div class="box">
</div>
</div>
.out {
background: green;
overflow: hidden;
}
// margin 重叠
.box {
margin: 50%;
}
产生 margin 重叠的条件
margin 重叠的情形
栗子
<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-top 重叠
margin-bottom 重叠
栗子
<div class="p">
<div class="c"></div>
</div>
.p {
background: green;
overflow: hidden;
}
.c {
margin: 10px;
}
一些条件限制
最初
<h2>h2</h2>
<p>p1</p>
<p>p2</p>
<ul>
<li>l1</li>
<li>l2</li>
<li>l3</li>
</ul>
你已经知道的事实
div {
background: red;
}
div {
position: absolute;
left: 0;
right: 0;
}
div {
width: 200px;
}
// 此时 margin 是 0;
div {
width: 200px;
margin-right: 100px;
margin-left: 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;
}
<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;
}
.box {
overflow: hidden;
}
.cl, .cr {
float: left;
}
.cl {
background: red;
}
.cr {
background: yellow;
}
元素占据空间跟随 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" />
<!--测试例子5-->
<div style="height: 200px; background: yellow">
<img width="200" src="https://gw.alicdn.com/tps/TB1vbsxOXXXXXcUXFXXXXXXXXXX-499-463.png" />
</div>
当元素发生 margin 重叠的时候,具体该如何表现
完
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.prop
或super.method()
),super代表父类。注意,此时super即可以引用父类实例的属性和方法,也可以引用父类的静态方法。
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
看不懂的可以去搜关键字,😝
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])]
Weak 版本的数据类型是无法包含无引用的元素的,一旦数据结构内的任一元素的引用被解除,该元素就会被移除当前所在的数据结构。
const weakset = new WeakSet();
weakset.add(1)
weakset.add({ foo: 1 })
let bar = { baz: 1 };
weakset.add(bar);
bar = null
key 可以是任意类型
new Map([['foo', 1], ['bar', 2]])
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);
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>
/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;
},
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
};
之前写过一篇表格十字线聚焦效果的分享 #11 ,但是用到了 JS,后来发现可以完全利用 CSS 来实现。
通过td:hover
配合::before或::after
,并且将其高度设置无限高,同时改变::after
或::before的z-index
值为 -1(让高亮的背景色变为下层)。重要的一点,需要给table
设置一个overflow:hidden
,将伪元素溢出的高度截取掉。
show code~
<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>
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;
}
选择器 | 🌰 | 🌰描述 |
---|---|---|
: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>
ul li:nth-child(-n + 3) {
...
}
ul li:nth-last-child(-n + 3) {
...
}
ul li:nth-child(n + 4) {
...
}
ul li:nth-child(n + 2):nth-child(-n + 4) {
...
}
ul li:nth-child(n + 3):nth-last-child(n + 3) {
...
}
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) {
...
}
ul li:nth-child(3n):nth-child(n + 2):nth-child(-n + 4) {
...
}
ul li:not(:nth-child(3)) {
...
}
ul li:nth-last-child(2):first-child {
...
}
ul li:nth-last-child(n + 3),
ul li:nth-last-child(n + 3) ~ li {
...
}
ul li:nth-last-child(-n + 3):first-child,
ul li:nth-last-child(-n + 3):first-child ~ li {
...
}
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 {
...
}
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.