+
+想了解为什么会出现以上的结果,看两个 demo 便能明白:
+
+```js
+// demo1
+var arr = [1, 2, NaN];
+arr.indexOf(NaN); // -1
+```
+indexOf 底层还是使用 === 进行判断,因为 NaN ==== NaN的结果为 false,所以使用 indexOf 查找不到 NaN 元素
+
+```js
+// demo2
+function unique(array) {
+ return Array.from(new Set(array));
+}
+console.log(unique([NaN, NaN])) // [NaN]
+```
+Set 认为尽管 NaN === NaN 为 false,但是这两个元素是重复的。
+
+## 写在最后
+
+虽然去重的结果有所不同,但更重要的是让我们知道在合适的场景要选择合适的方法。
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
\ No newline at end of file
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\225\260\347\273\204\346\211\201\345\271\263\345\214\226.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\225\260\347\273\204\346\211\201\345\271\263\345\214\226.md"
new file mode 100644
index 00000000..eb1a940e
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\225\260\347\273\204\346\211\201\345\271\263\345\214\226.md"
@@ -0,0 +1,257 @@
+# JavaScript专题之数组扁平化
+
+## 扁平化
+
+数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。
+
+举个例子,假设有个名为 flatten 的函数可以做到数组扁平化,效果就会如下:
+
+```js
+var arr = [1, [2, [3, 4]]];
+console.log(flatten(arr)) // [1, 2, 3, 4]
+```
+
+知道了效果是什么样的了,我们可以去尝试着写这个 flatten 函数了
+
+## 递归
+
+我们最一开始能想到的莫过于循环数组元素,如果还是一个数组,就递归调用该方法:
+
+```js
+// 方法 1
+var arr = [1, [2, [3, 4]]];
+
+function flatten(arr) {
+ var result = [];
+ for (var i = 0, len = arr.length; i < len; i++) {
+ if (Array.isArray(arr[i])) {
+ result = result.concat(flatten(arr[i]))
+ }
+ else {
+ result.push(arr[i])
+ }
+ }
+ return result;
+}
+
+
+console.log(flatten(arr))
+```
+
+## toString
+
+如果数组的元素都是数字,那么我们可以考虑使用 toString 方法,因为:
+
+```js
+[1, [2, [3, 4]]].toString() // "1,2,3,4"
+```
+
+调用 toString 方法,返回了一个逗号分隔的扁平的字符串,这时候我们再 split,然后转成数字不就可以实现扁平化了吗?
+
+```js
+// 方法2
+var arr = [1, [2, [3, 4]]];
+
+function flatten(arr) {
+ return arr.toString().split(',').map(function(item){
+ return +item
+ })
+}
+
+console.log(flatten(arr))
+```
+
+然而这种方法使用的场景却非常有限,如果数组是 [1, '1', 2, '2'] 的话,这种方法就会产生错误的结果。
+
+## reduce
+
+既然是对数组进行处理,最终返回一个值,我们就可以考虑使用 reduce 来简化代码:
+
+```js
+// 方法3
+var arr = [1, [2, [3, 4]]];
+
+function flatten(arr) {
+ return arr.reduce(function(prev, next){
+ return prev.concat(Array.isArray(next) ? flatten(next) : next)
+ }, [])
+}
+
+console.log(flatten(arr))
+```
+
+## ...
+
+ES6 增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:
+
+```js
+var arr = [1, [2, [3, 4]]];
+console.log([].concat(...arr)); // [1, 2, [3, 4]]
+```
+
+我们用这种方法只可以扁平一层,但是顺着这个方法一直思考,我们可以写出这样的方法:
+
+```js
+// 方法4
+var arr = [1, [2, [3, 4]]];
+
+function flatten(arr) {
+
+ while (arr.some(item => Array.isArray(item))) {
+ arr = [].concat(...arr);
+ }
+
+ return arr;
+}
+
+console.log(flatten(arr))
+```
+
+## undercore
+
+那么如何写一个抽象的扁平函数,来方便我们的开发呢,所有又到了我们抄袭 underscore 的时候了~
+
+在这里直接给出源码和注释,但是要注意,这里的 flatten 函数并不是最终的 _.flatten,为了方便多个 API 进行调用,这里对扁平进行了更多的配置。
+
+```js
+/**
+ * 数组扁平化
+ * @param {Array} input 要处理的数组
+ * @param {boolean} shallow 是否只扁平一层
+ * @param {boolean} strict 是否严格处理元素,下面有解释
+ * @param {Array} output 这是为了方便递归而传递的参数
+ * 源码地址:https://github.com/jashkenas/underscore/blob/master/underscore.js#L528
+ */
+function flatten(input, shallow, strict, output) {
+
+ // 递归使用的时候会用到output
+ output = output || [];
+ var idx = output.length;
+
+ for (var i = 0, len = input.length; i < len; i++) {
+
+ var value = input[i];
+ // 如果是数组,就进行处理
+ if (Array.isArray(value)) {
+ // 如果是只扁平一层,遍历该数组,依此填入 output
+ if (shallow) {
+ var j = 0, len = value.length;
+ while (j < len) output[idx++] = value[j++];
+ }
+ // 如果是全部扁平就递归,传入已经处理的 output,递归中接着处理 output
+ else {
+ flatten(value, shallow, strict, output);
+ idx = output.length;
+ }
+ }
+ // 不是数组,根据 strict 的值判断是跳过不处理还是放入 output
+ else if (!strict){
+ output[idx++] = value;
+ }
+ }
+
+ return output;
+
+}
+```
+
+解释下 strict,在代码里我们可以看出,当遍历数组元素时,如果元素不是数组,就会对 strict 取反的结果进行判断,如果设置 strict 为 true,就会跳过不进行任何处理,这意味着可以过滤非数组的元素,举个例子:
+
+```js
+var arr = [1, 2, [3, 4]];
+console.log(flatten(arr, true, true)); // [3, 4]
+```
+
+那么设置 strict 到底有什么用呢?不急,我们先看下 shallow 和 strct 各种值对应的结果:
+
+* shallow true + strict false :正常扁平一层
+* shallow false + strict false :正常扁平所有层
+* shallow true + strict true :去掉非数组元素
+* shallow false + strict true : 返回一个[]
+
+我们看看 underscore 中哪些方法调用了 flatten 这个基本函数:
+
+## _.flatten
+
+首先就是 _.flatten:
+
+```js
+_.flatten = function(array, shallow) {
+ return flatten(array, shallow, false);
+};
+```
+
+在正常的扁平中,我们并不需要去掉非数组元素。
+
+## _.union
+
+接下来是 _.union:
+
+该函数传入多个数组,然后返回传入的数组的并集,
+
+举个例子:
+
+```js
+_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+=> [1, 2, 3, 101, 10]
+```
+
+如果传入的参数并不是数组,就会将该参数跳过:
+
+```js
+_.union([1, 2, 3], [101, 2, 1, 10], 4, 5);
+=> [1, 2, 3, 101, 10]
+```
+
+为了实现这个效果,我们可以将传入的所有数组扁平化,然后去重,因为只能传入数组,这时候我们直接设置 strict 为 true,就可以跳过传入的非数组的元素。
+
+```js
+// 关于 unique 可以查看《JavaScript专题之数组去重》[](https://github.com/mqyqingfeng/Blog/issues/27)
+function unique(array) {
+ return Array.from(new Set(array));
+}
+
+_.union = function() {
+ return unique(flatten(arguments, true, true));
+}
+```
+
+## _.difference
+
+是不是感觉折腾 strict 有点用处了,我们再看一个 _.difference:
+
+语法为:
+
+> _.difference(array, *others)
+
+效果是取出来自 array 数组,并且不存在于多个 other 数组的元素。跟 _.union 一样,都会排除掉不是数组的元素。
+
+举个例子:
+
+```js
+_.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 3);
+=> [1, 3]
+```
+
+实现方法也很简单,扁平 others 的数组,筛选出 array 中不在扁平化数组中的值:
+
+```js
+function difference(array, ...rest) {
+
+ rest = flatten(rest, true, true);
+
+ return array.filter(function(item){
+ return rest.indexOf(item) === -1;
+ })
+}
+```
+
+注意,以上实现的细节并不是完全按照 underscore,具体细节的实现感兴趣可以[查看源码](https://github.com/jashkenas/underscore/blob/master/underscore.js#L528)。
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
\ No newline at end of file
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\267\261\346\265\205\346\213\267\350\264\235.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\267\261\346\265\205\346\213\267\350\264\235.md"
new file mode 100644
index 00000000..e8940ac1
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\346\267\261\346\265\205\346\213\267\350\264\235.md"
@@ -0,0 +1,139 @@
+# JavaScript专题之深浅拷贝
+
+## 前言
+
+拷贝也是面试经典呐!
+
+## 数组的浅拷贝
+
+如果是数组,我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。
+
+比如:
+
+```js
+var arr = ['old', 1, true, null, undefined];
+
+var new_arr = arr.concat();
+
+new_arr[0] = 'new';
+
+console.log(arr) // ["old", 1, true, null, undefined]
+console.log(new_arr) // ["new", 1, true, null, undefined]
+```
+
+用 slice 可以这样做:
+
+```js
+var new_arr = arr.slice();
+```
+
+但是如果数组嵌套了对象或者数组的话,比如:
+
+```js
+var arr = [{old: 'old'}, ['old']];
+
+var new_arr = arr.concat();
+
+arr[0].old = 'new';
+arr[1][0] = 'new';
+
+console.log(arr) // [{old: 'new'}, ['new']]
+console.log(new_arr) // [{old: 'new'}, ['new']]
+```
+
+我们会发现,无论是新数组还是旧数组都发生了变化,也就是说使用 concat 方法,克隆的并不彻底。
+
+如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。
+
+我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
+
+所以我们可以看出使用 concat 和 slice 是一种浅拷贝。
+
+## 数组的深拷贝
+
+那如何深拷贝一个数组呢?这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:
+
+```js
+var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
+
+var new_arr = JSON.parse( JSON.stringify(arr) );
+
+console.log(new_arr);
+```
+
+是一个简单粗暴的好方法,就是有一个问题,不能拷贝函数,我们做个试验:
+
+```js
+var arr = [function(){
+ console.log(a)
+}, {
+ b: function(){
+ console.log(b)
+ }
+}]
+
+var new_arr = JSON.parse(JSON.stringify(arr));
+
+console.log(new_arr);
+```
+
+我们会发现 new_arr 变成了:
+
+
+
+## 浅拷贝的实现
+
+以上三个方法 concat、slice、JSON.stringify 都算是技巧类,可以根据实际项目情况选择使用,接下来我们思考下如何实现一个对象或者数组的浅拷贝。
+
+想一想,好像很简单,遍历对象,然后把属性和属性值都放在一个新的对象不就好了~
+
+嗯,就是这么简单,注意几个小点就可以了:
+
+```js
+var shallowCopy = function(obj) {
+ // 只拷贝对象
+ if (typeof obj !== 'object') return;
+ // 根据obj的类型判断是新建一个数组还是对象
+ var newObj = obj instanceof Array ? [] : {};
+ // 遍历obj,并且判断是obj的属性才拷贝
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ newObj[key] = obj[key];
+ }
+ }
+ return newObj;
+}
+```
+
+## 深拷贝的实现
+
+那如何实现一个深拷贝呢?说起来也好简单,我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数不就好了~
+
+```js
+var deepCopy = function(obj) {
+ if (typeof obj !== 'object') return;
+ var newObj = obj instanceof Array ? [] : {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
+ }
+ }
+ return newObj;
+}
+```
+
+## 性能问题
+
+尽管使用深拷贝会完全的克隆一个新对象,不会产生副作用,但是深拷贝因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。
+
+## 下期预告
+
+难道到这里就结束了?是的。然而本篇实际上是一个铺垫,我们真正要看的是 jquery 的 extend 函数的实现,下一篇,我们会讲一讲如何从零实现一个 jquery 的 extend 函数。
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\212).md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\212).md"
new file mode 100644
index 00000000..ab2f8d63
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\212).md"
@@ -0,0 +1,244 @@
+# JavaScript专题之类型判断(上)
+
+## 前言
+
+类型判断在 web 开发中有非常广泛的应用,简单的有判断数字还是字符串,进阶一点的有判断数组还是对象,再进阶一点的有判断日期、正则、错误类型,再再进阶一点还有比如判断 plainObject、空对象、Window 对象等等。
+
+以上都会讲,今天是上半场。
+
+## typeof
+
+我们最最常用的莫过于 typeof,注意,尽管我们会看到诸如:
+
+```js
+console.log(typeof('yayu')) // string
+```
+
+的写法,但是 typeof 可是一个正宗的运算符,就跟加减乘除一样!这就能解释为什么下面这种写法也是可行的:
+
+```js
+console.log(typeof 'yayu') // string
+```
+
+引用《JavaScript权威指南》中对 typeof 的介绍:
+
+>typeof 是一元操作符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串。
+
+那我们都知道,在 ES6 前,JavaScript 共六种数据类型,分别是:
+
+Undefined、Null、Boolean、Number、String、Object
+
+然而当我们使用 typeof 对这些数据类型的值进行操作的时候,返回的结果却不是一一对应,分别是:
+
+undefined、object、boolean、number、string、object
+
+注意以上都是小写的字符串。Null 和 Object 类型都返回了 object 字符串。
+
+尽管不能一一对应,但是 typeof 却能检测出函数类型:
+
+```js
+function a() {}
+
+console.log(typeof a); // function
+```
+
+所以 typeof 能检测出六种类型的值,但是,除此之外 Object 下还有很多细分的类型呐,如 Array、Function、Date、RegExp、Error 等。
+
+如果用 typeof 去检测这些类型,举个例子:
+
+```js
+var date = new Date();
+var error = new Error();
+console.log(typeof date); // object
+console.log(typeof error); // object
+```
+
+返回的都是 object 呐,这可怎么区分~ 所以有没有更好的方法呢?
+
+## Obejct.prototype.toString
+
+是的,当然有!这就是 Object.prototype.toString!
+
+那 Object.protototype.toString 究竟是一个什么样的方法呢?
+
+为了更加细致的讲解这个函数,让我先献上 ES5 规范地址:[https://es5.github.io/#x15.2.4.2](https://es5.github.io/#x15.2.4.2)。
+
+在第 15.2.4.2 节讲的就是 Object.prototype.toString(),为了不误导大家,我先奉上英文版:
+
+>When the toString method is called, the following steps are taken:
+
+>1. If the **this** value is **undefined**, return "**[object Undefined]**".
+>2. If the **this** value is **null**, return "**[object Null]**".
+>3. Let *O* be the result of calling ToObject passing the **this** value as the argument.
+>4. Let *class* be the value of the [[Class]] internal property of *O*.
+>5. Return the String value that is the result of concatenating the three Strings "**[object** ", *class*, and "**]**".
+
+凡是规范上加粗或者斜体的,在这里我也加粗或者斜体了,就是要让大家感受原汁原味的规范!
+
+如果没有看懂,就不妨看看我理解的:
+
+当 toString 方法被调用的时候,下面的步骤会被执行:
+
+1. 如果 this 值是 undefined,就返回 [object Undefined]
+2. 如果 this 的值是 null,就返回 [object Null]
+3. 让 O 成为 ToObject(this) 的结果
+4. 让 class 成为 O 的内部属性 [[Class]] 的值
+5. 最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串
+
+通过规范,我们至少知道了调用 Object.prototype.toString 会返回一个由 "[object " 和 class 和 "]" 组成的字符串,而 class 是要判断的对象的内部属性。
+
+让我们写个 demo:
+
+```js
+console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
+console.log(Object.prototype.toString.call(null)) // [object Null]
+
+var date = new Date();
+console.log(Object.prototype.toString.call(date)) // [object Date]
+```
+
+由此我们可以看到这个 class 值就是识别对象类型的关键!
+
+正是因为这种特性,我们可以用 Object.prototype.toString 方法识别出更多类型!
+
+那到底能识别多少种类型呢?
+
+至少 12 种!
+
+你咋知道的?
+
+我数的!
+
+……
+
+让我们看个 demo:
+
+```js
+// 以下是11种:
+var number = 1; // [object Number]
+var string = '123'; // [object String]
+var boolean = true; // [object Boolean]
+var und = undefined; // [object Undefined]
+var nul = null; // [object Null]
+var obj = {a: 1} // [object Object]
+var array = [1, 2, 3]; // [object Array]
+var date = new Date(); // [object Date]
+var error = new Error(); // [object Error]
+var reg = /a/g; // [object RegExp]
+var func = function a(){}; // [object Function]
+
+function checkType() {
+ for (var i = 0; i < arguments.length; i++) {
+ console.log(Object.prototype.toString.call(arguments[i]))
+ }
+}
+
+checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)
+
+```
+
+除了以上 11 种之外,还有:
+
+```js
+console.log(Object.prototype.toString.call(Math)); // [object Math]
+console.log(Object.prototype.toString.call(JSON)); // [object JSON]
+```
+
+除了以上 13 种之外,还有:
+
+```js
+function a() {
+ console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
+}
+a();
+```
+
+所以我们可以识别至少 14 种类型,当然我们也可以算出来,[[class]] 属性至少有 12 个。
+
+## type API
+
+既然有了 Object.prototype.toString 这个神器!那就让我们写个 type 函数帮助我们以后识别各种类型的值吧!
+
+我的设想:
+
+写一个 type 函数能检测各种类型的值,如果是基本类型,就使用 typeof,引用类型就使用 toString。此外鉴于 typeof 的结果是小写,我也希望所有的结果都是小写。
+
+考虑到实际情况下并不会检测 Math 和 JSON,所以去掉这两个类型的检测。
+
+我们来写一版代码:
+
+```js
+// 第一版
+var class2type = {};
+
+// 生成class2type映射
+"Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map(function(item, index) {
+ class2type["[object " + item + "]"] = item.toLowerCase();
+})
+
+function type(obj) {
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[Object.prototype.toString.call(obj)] || "object" :
+ typeof obj;
+}
+```
+
+嗯,看起来很完美的样子~~ 但是注意,在 IE6 中,null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!
+
+我去,竟然还有这个兼容性!有什么简单的方法可以解决吗?那我们再改写一版,绝对让你惊艳!
+
+```js
+// 第二版
+var class2type = {};
+
+// 生成class2type映射
+"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
+ class2type["[object " + item + "]"] = item.toLowerCase();
+})
+
+function type(obj) {
+ // 一箭双雕
+ if (obj == null) {
+ return obj + "";
+ }
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[Object.prototype.toString.call(obj)] || "object" :
+ typeof obj;
+}
+```
+
+## isFunction
+
+有了 type 函数后,我们可以对常用的判断直接封装,比如 isFunction:
+
+```js
+function isFunction(obj) {
+ return type(obj) === "function";
+}
+```
+
+## 数组
+
+jQuery 判断数组类型,旧版本是通过判断 Array.isArray 方法是否存在,如果存在就使用该方法,不存在就使用 type 函数。
+
+```js
+var isArray = Array.isArray || function( obj ) {
+ return type(obj) === "array";
+}
+```
+
+但是在 jQuery v3.0 中已经完全采用了 Array.isArray。
+
+## 结语
+
+到此,类型判断的上篇就结束了,我们已经可以判断日期、正则、错误类型啦,但是还有更复杂的判断比如 plainObject、空对象、Window对象、类数组对象等,路漫漫其修远兮,吾将上下而求索。
+
+哦, 对了,这个 type 函数抄的 jQuery,[点击查看 type 源码](https://github.com/jquery/jquery/blob/ac9e3016645078e1e42120822cfb2076151c8cbe/src/core.js#L269)。
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\213).md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\213).md"
new file mode 100644
index 00000000..27038e5c
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\347\261\273\345\236\213\345\210\244\346\226\255(\344\270\213).md"
@@ -0,0 +1,261 @@
+# JavaScript专题之类型判断(下)
+
+## 前言
+
+在上篇[《JavaScript专题之类型判断(上)》](https://github.com/mqyqingfeng/Blog/issues/28)中,我们抄袭 jQuery 写了一个 type 函数,可以检测出常见的数据类型,然而在开发中还有更加复杂的判断,比如 plainObject、空对象、Window 对象等,这一篇就让我们接着抄袭 jQuery 去看一下这些类型的判断。
+
+## plainObject
+
+plainObject 来自于 jQuery,可以翻译成纯粹的对象,所谓"纯粹的对象",就是该对象是通过 "{}" 或 "new Object" 创建的,该对象含有零个或者多个键值对。
+
+之所以要判断是不是 plainObject,是为了跟其他的 JavaScript对象如 null,数组,宿主对象(documents)等作区分,因为这些用 typeof 都会返回object。
+
+jQuery提供了 isPlainObject 方法进行判断,先让我们看看使用的效果:
+
+```js
+function Person(name) {
+ this.name = name;
+}
+
+console.log($.isPlainObject({})) // true
+
+console.log($.isPlainObject(new Object)) // true
+
+console.log($.isPlainObject(Object.create(null))); // true
+
+console.log($.isPlainObject(Object.assign({a: 1}, {b: 2}))); // true
+
+console.log($.isPlainObject(new Person('yayu'))); // false
+
+console.log($.isPlainObject(Object.create({}))); // false
+```
+
+由此我们可以看到,除了 {} 和 new Object 创建的之外,jQuery 认为一个没有原型的对象也是一个纯粹的对象。
+
+实际上随着 jQuery 版本的提升,isPlainObject 的实现也在变化,我们今天讲的是 3.0 版本下的 isPlainObject,我们直接看源码:
+
+```js
+// 上节中写 type 函数时,用来存放 toString 映射结果的对象
+var class2type = {};
+
+// 相当于 Object.prototype.toString
+var toString = class2type.toString;
+
+// 相当于 Object.prototype.hasOwnProperty
+var hasOwn = class2type.hasOwnProperty;
+
+function isPlainObject(obj) {
+ var proto, Ctor;
+
+ // 排除掉明显不是obj的以及一些宿主对象如Window
+ if (!obj || toString.call(obj) !== "[object Object]") {
+ return false;
+ }
+
+ /**
+ * getPrototypeOf es5 方法,获取 obj 的原型
+ * 以 new Object 创建的对象为例的话
+ * obj.__proto__ === Object.prototype
+ */
+ proto = Object.getPrototypeOf(obj);
+
+ // 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true
+ if (!proto) {
+ return true;
+ }
+
+ /**
+ * 以下判断通过 new Object 方式创建的对象
+ * 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor
+ * 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数
+ */
+ Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
+
+ // 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数
+ return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
+}
+```
+
+注意:我们判断 Ctor 构造函数是不是 Object 构造函数,用的是 hasOwn.toString.call(Ctor),这个方法可不是 Object.prototype.toString,不信我们在函数里加上下面这两句话:
+
+```js
+console.log(hasOwn.toString.call(Ctor)); // function Object() { [native code] }
+console.log(Object.prototype.toString.call(Ctor)); // [object Function]
+```
+
+发现返回的值并不一样,这是因为 hasOwn.toString 调用的其实是 Function.prototype.toString,毕竟 hasOwnProperty 可是一个函数!
+
+而且 Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法。函数的 toString 方法会返回一个表示函数源代码的字符串。具体来说,包括 function关键字,形参列表,大括号,以及函数体中的内容。
+
+## EmptyObject
+
+jQuery提供了 isEmptyObject 方法来判断是否是空对象,代码简单,我们直接看源码:
+
+```js
+function isEmptyObject( obj ) {
+
+ var name;
+
+ for ( name in obj ) {
+ return false;
+ }
+
+ return true;
+}
+```
+
+其实所谓的 isEmptyObject 就是判断是否有属性,for 循环一旦执行,就说明有属性,有属性就会返回 false。
+
+但是根据这个源码我们可以看出isEmptyObject实际上判断的并不仅仅是空对象。
+
+举个栗子:
+
+```js
+console.log(isEmptyObject({})); // true
+console.log(isEmptyObject([])); // true
+console.log(isEmptyObject(null)); // true
+console.log(isEmptyObject(undefined)); // true
+console.log(isEmptyObject(1)); // true
+console.log(isEmptyObject('')); // true
+console.log(isEmptyObject(true)); // true
+```
+
+以上都会返回 true。
+
+但是既然 jQuery 是这样写,可能是因为考虑到实际开发中 isEmptyObject 用来判断 {} 和 {a: 1} 是足够的吧。如果真的是只判断 {},完全可以结合上篇写的 type 函数筛选掉不适合的情况。
+
+## Window对象
+
+Window 对象作为客户端 JavaScript 的全局对象,它有一个 window 属性指向自身,这点在[《JavaScript深入之变量对象》](https://github.com/mqyqingfeng/Blog/issues/5)中讲到过。我们可以利用这个特性判断是否是 Window 对象。
+
+```js
+function isWindow( obj ) {
+ return obj != null && obj === obj.window;
+}
+```
+
+## isArrayLike
+
+isArrayLike,看名字可能会让我们觉得这是判断类数组对象的,其实不仅仅是这样,jQuery 实现的 isArrayLike,数组和类数组都会返回 true。
+
+因为源码比较简单,我们直接看源码:
+
+```js
+function isArrayLike(obj) {
+
+ // obj 必须有 length属性
+ var length = !!obj && "length" in obj && obj.length;
+ var typeRes = type(obj);
+
+ // 排除掉函数和 Window 对象
+ if (typeRes === "function" || isWindow(obj)) {
+ return false;
+ }
+
+ return typeRes === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && (length - 1) in obj;
+}
+```
+
+重点分析 return 这一行,使用了或语句,只要一个为 true,结果就返回 true。
+
+所以如果 isArrayLike 返回true,至少要满足三个条件之一:
+
+1. 是数组
+2. 长度为 0
+3. lengths 属性是大于 0 的数组,并且obj[length - 1]必须存在
+
+第一个就不说了,看第二个,为什么长度为 0 就可以直接判断为 true 呢?
+
+那我们写个对象:
+
+```js
+var obj = {a: 1, b: 2, length: 0}
+```
+
+isArrayLike 函数就会返回 true,那这个合理吗?
+
+回答合不合理之前,我们先看一个例子:
+
+```js
+function a(){
+ console.log(isArrayLike(arguments))
+}
+a();
+```
+
+如果我们去掉length === 0 这个判断,就会打印 false,然而我们都知道 arguments 是一个类数组对象,这里是应该返回 true 的。
+
+所以是不是为了放过空的 arguments 时也放过了一些存在争议的对象呢?
+
+第三个条件:length 是数字,并且 length > 0 且最后一个元素存在。
+
+为什么仅仅要求最后一个元素存在呢?
+
+让我们先想下数组是不是可以这样写:
+
+```js
+var arr = [,,3]
+```
+
+当我们写一个对应的类数组对象就是:
+
+```js
+var arrLike = {
+ 2: 3,
+ length: 3
+}
+```
+
+也就是说当我们在数组中用逗号直接跳过的时候,我们认为该元素是不存在的,类数组对象中也就不用写这个元素,但是最后一个元素是一定要写的,要不然 length 的长度就不会是最后一个元素的 key 值加 1。比如数组可以这样写
+
+```js
+var arr = [1,,];
+console.log(arr.length) // 2
+```
+
+但是类数组对象就只能写成:
+
+```js
+var arrLike = {
+ 0: 1,
+ length: 1
+}
+```
+
+所以符合条件的类数组对象是一定存在最后一个元素的!
+
+这就是满足 isArrayLike 的三个条件,其实除了 jQuery 之外,很多库都有对 isArrayLike 的实现,比如 underscore:
+
+```js
+var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+
+var isArrayLike = function(collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+};
+```
+
+## isElement
+
+isElement 判断是不是 DOM 元素。
+
+```js
+isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+};
+```
+
+## 结语
+
+这一篇我们介绍了 jQuery 的 isPlainObject、isEmptyObject、isWindow、isArrayLike、以及 underscore 的 isElement 实现。我们可以看到,即使是 jQuery 这样优秀的库,一些方法的实现也并不是非常完美和严密的,但是最后为什么这么做,其实也是一种权衡,权衡所失与所得,正如玉伯在《从 JavaScript 数组去重谈性能优化》中讲到:
+
+**所有这些点,都必须脚踏实地在具体应用场景下去分析、去选择,要让场景说话。**
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
\ No newline at end of file
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\247\243\350\257\273v8\346\216\222\345\272\217\346\272\220\347\240\201.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\247\243\350\257\273v8\346\216\222\345\272\217\346\272\220\347\240\201.md"
new file mode 100644
index 00000000..6fd999d1
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\247\243\350\257\273v8\346\216\222\345\272\217\346\272\220\347\240\201.md"
@@ -0,0 +1,479 @@
+# JavaScript专题之解读 v8 排序源码
+
+## 前言
+
+v8 是 Chrome 的 JavaScript 引擎,其中关于数组的排序完全采用了 JavaScript 实现。
+
+排序采用的算法跟数组的长度有关,当数组长度小于等于 10 时,采用插入排序,大于 10 的时候,采用快速排序。(当然了,这种说法并不严谨)。
+
+我们先来看看插入排序和快速排序。
+
+## 插入排序
+
+### 原理
+
+将第一个元素视为有序序列,遍历数组,将之后的元素依次插入这个构建的有序序列中。
+
+### 图示
+
+
+
+### 实现
+
+```js
+function insertionSort(arr) {
+ for (var i = 1; i < arr.length; i++) {
+ var element = arr[i];
+ for (var j = i - 1; j >= 0; j--) {
+ var tmp = arr[j];
+ var order = tmp - element;
+ if (order > 0) {
+ arr[j + 1] = tmp;
+ } else {
+ break;
+ }
+ }
+ arr[j + 1] = element;
+ }
+ return arr;
+}
+
+var arr = [6, 5, 4, 3, 2, 1];
+console.log(insertionSort(arr));
+```
+
+### 时间复杂度
+
+时间复杂度是指执行算法所需要的计算工作量,它考察当输入值大小趋近无穷时的情况,一般情况下,算法中基本操作重复执行的次数是问题规模 n 的某个函数。
+
+最好情况:数组升序排列,时间复杂度为:O(n)
+
+最坏情况:数组降序排列,时间复杂度为:O(n²)
+
+### 稳定性
+
+稳定性,是指相同的元素在排序后是否还保持相对的位置。
+
+要注意的是对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。
+
+比如 [3, 3, 1],排序后,还是 [3, 3, 1],但是其实是第二个 3 在 第一个 3 前,那这就是不稳定的排序算法。
+
+插入排序是稳定的算法。
+
+### 优势
+
+当数组是快要排序好的状态或者问题规模比较小的时候,插入排序效率更高。这也是为什么 v8 会在数组长度小于等于 10 的时候采用插入排序。
+
+## 快速排序
+
+### 原理
+
+1. 选择一个元素作为"基准"
+2. 小于"基准"的元素,都移到"基准"的左边;大于"基准"的元素,都移到"基准"的右边。
+3. 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
+
+### 示例
+
+示例和下面的实现方式来源于阮一峰老师的[《快速排序(Quicksort)的Javascript实现》](http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html)
+
+以数组 [85, 24, 63, 45, 17, 31, 96, 50] 为例:
+
+第一步,选择中间的元素 45 作为"基准"。(基准值可以任意选择,但是选择中间的值比较容易理解。)
+
+
+
+第二步,按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于45",另一个"大于等于45"。
+
+
+
+第三步,对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
+
+
+
+### 实现
+
+```js
+var quickSort = function(arr) {
+ if (arr.length <= 1) { return arr; }
+ // 取数组的中间元素作为基准
+ var pivotIndex = Math.floor(arr.length / 2);
+ var pivot = arr.splice(pivotIndex, 1)[0];
+
+ var left = [];
+ var right = [];
+
+ for (var i = 0; i < arr.length; i++){
+ if (arr[i] < pivot) {
+ left.push(arr[i]);
+ } else {
+ right.push(arr[i]);
+ }
+ }
+ return quickSort(left).concat([pivot], quickSort(right));
+};
+```
+
+然而这种实现方式需要额外的空间用来储存左右子集,所以还有一种原地(in-place)排序的实现方式。
+
+### 图示
+
+我们来看看原地排序的实现图示:
+
+
+
+为了让大家看明白快速排序的原理,我调慢了执行速度。
+
+在这张示意图里,基准的取值规则是取最左边的元素,黄色代表当前的基准,绿色代表小于基准的元素,紫色代表大于基准的元素。
+
+我们会发现,绿色的元素会紧挨在基准的右边,紫色的元素会被移到后面,然后交换基准和绿色的最后一个元素,此时,基准处于正确的位置,即前面的元素都小于基准值,后面的元素都大于基准值。然后再对前面的和后面的多个元素取基准,做排序。
+
+### in-place 实现
+
+```js
+function quickSort(arr) {
+ // 交换元素
+ function swap(arr, a, b) {
+ var temp = arr[a];
+ arr[a] = arr[b];
+ arr[b] = temp;
+ }
+
+ function partition(arr, left, right) {
+ var pivot = arr[left];
+ var storeIndex = left;
+
+ for (var i = left + 1; i <= right; i++) {
+ if (arr[i] < pivot) {
+ swap(arr, ++storeIndex, i);
+ }
+ }
+
+ swap(arr, left, storeIndex);
+
+ return storeIndex;
+ }
+
+ function sort(arr, left, right) {
+ if (left < right) {
+ var storeIndex = partition(arr, left, right);
+ sort(arr, left, storeIndex - 1);
+ sort(arr, storeIndex + 1, right);
+ }
+ }
+
+ sort(arr, 0, arr.length - 1);
+
+ return arr;
+}
+
+console.log(quickSort(6, 7, 3, 4, 1, 5, 9, 2, 8))
+```
+
+### 稳定性
+
+快速排序是不稳定的排序。如果要证明一个排序是不稳定的,你只用举出一个实例就行。
+
+所以我们举一个呗~
+
+就以数组 [1, 2, 3, 3, 4, 5] 为例,因为基准的选择不确定,假如选定了第三个元素(也就是第一个 3) 为基准,所有小于 3 的元素在前面,大于等于 3 的在后面,排序的结果没有问题。可是如果选择了第四个元素(也就是第二个 3 ),小于 3 的在基准前面,大于等于 3 的在基准后面,第一个 3 就会被移动到 第二个 3 后面,所以快速排序是不稳定的排序。
+
+### 时间复杂度
+
+阮一峰老师的实现中,基准取的是中间元素,而原地排序中基准取最左边的元素。快速排序的关键点就在于基准的选择,选取不同的基准时,会有不同性能表现。
+
+快速排序的时间复杂度最好为 O(nlogn),可是为什么是 nlogn 呢?来一个并不严谨的证明:
+
+在最佳情况下,每一次都平分整个数组。假设数组有 n 个元素,其递归的深度就为 log2n + 1,时间复杂度为 O(n)[(log2n + 1)],因为时间复杂度考察当输入值大小趋近无穷时的情况,所以会忽略低阶项,时间复杂度为:o(nlog2n)。
+
+如果一个程序的运行时间是对数级的,则随着 n 的增大程序会渐渐慢下来。如果底数是 10,lg1000 等于 3,如果 n 为 1000000,lgn 等于 6,仅为之前的两倍。如果底数为 2,log21000 的值约为 10,log21000000 的值约为 19,约为之前的两倍。我们可以发现任意底数的一个对数函数其实都相差一个常数倍而已。所以我们认为 O(logn)已经可以表达所有底数的对数了,所以时间复杂度最后为: O(nlogn)。
+
+而在最差情况下,如果对一个已经排序好的数组,每次选择基准元素时总是选择第一个元素或者最后一个元素,那么每次都会有一个子集是空的,递归的层数将达到 n,最后导致算法的时间复杂度退化为 O(n²)。
+
+这也充分说明了一个基准的选择是多么的重要,而 v8 为了提高性能,就对基准的选择做了很多优化。
+
+## v8 基准选择
+
+v8 选择基准的原理是从头和尾之外再选择一个元素,然后三个值排序取中间值。
+
+当数组长度大于 10 但是小于 1000 的时候,取中间位置的元素,实现代码为:
+
+```js
+// 基准的下标
+// >> 1 相当于除以 2 (忽略余数)
+third_index = from + ((to - from) >> 1);
+```
+
+当数组长度大于 1000 的时候,每隔 200 ~ 215 个元素取一个值,然后将这些值进行排序,取中间值的下标,实现的代码为:
+
+```js
+// 简单处理过
+function GetThirdIndex(a, from, to) {
+ var t_array = new Array();
+
+ // & 位运算符
+ var increment = 200 + ((to - from) & 15);
+
+ var j = 0;
+ from += 1;
+ to -= 1;
+
+ for (var i = from; i < to; i += increment) {
+ t_array[j] = [i, a[i]];
+ j++;
+ }
+ // 对随机挑选的这些值进行排序
+ t_array.sort(function(a, b) {
+ return comparefn(a[1], b[1]);
+ });
+ // 取中间值的下标
+ var third_index = t_array[t_array.length >> 1][0];
+ return third_index;
+}
+```
+
+也许你会好奇 `200 + ((to - from) & 15)` 是什么意思?
+
+`&` 表示是按位与,对整数操作数逐位执行布尔与操作。只有两个操作数中相对应的位都是 1,结果中的这一位才是 1。
+
+以 `15 & 127` 为例:
+
+15 二进制为: (0000 1111)
+
+127 二进制为:(1111 1111)
+
+按位与结果为:(0000 1111)= 15
+
+所以 `15 & 127` 的结果为 `15`。
+
+注意 15 的二进制为: `1111`,这就意味着任何和 15 按位与的结果都会小于或者等于 15,这才实现了每隔 200 ~ 215 个元素取一个值。
+
+## v8 源码
+
+终于到了看源码的时刻!源码地址为:[https://github.com/v8/v8/blob/master/src/js/array.js#L758](https://github.com/v8/v8/blob/master/src/js/array.js#L758)。
+
+```js
+function InsertionSort(a, from, to) {
+ for (var i = from + 1; i < to; i++) {
+ var element = a[i];
+ for (var j = i - 1; j >= from; j--) {
+ var tmp = a[j];
+ var order = comparefn(tmp, element);
+ if (order > 0) {
+ a[j + 1] = tmp;
+ } else {
+ break;
+ }
+ }
+ a[j + 1] = element;
+ }
+};
+
+
+function QuickSort(a, from, to) {
+
+ var third_index = 0;
+ while (true) {
+ // Insertion sort is faster for short arrays.
+ if (to - from <= 10) {
+ InsertionSort(a, from, to);
+ return;
+ }
+ if (to - from > 1000) {
+ third_index = GetThirdIndex(a, from, to);
+ } else {
+ third_index = from + ((to - from) >> 1);
+ }
+ // Find a pivot as the median of first, last and middle element.
+ var v0 = a[from];
+ var v1 = a[to - 1];
+ var v2 = a[third_index];
+
+ var c01 = comparefn(v0, v1);
+ if (c01 > 0) {
+ // v1 < v0, so swap them.
+ var tmp = v0;
+ v0 = v1;
+ v1 = tmp;
+ } // v0 <= v1.
+ var c02 = comparefn(v0, v2);
+ if (c02 >= 0) {
+ // v2 <= v0 <= v1.
+ var tmp = v0;
+ v0 = v2;
+ v2 = v1;
+ v1 = tmp;
+ } else {
+ // v0 <= v1 && v0 < v2
+ var c12 = comparefn(v1, v2);
+ if (c12 > 0) {
+ // v0 <= v2 < v1
+ var tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+ }
+ }
+
+ // v0 <= v1 <= v2
+ a[from] = v0;
+ a[to - 1] = v2;
+
+ var pivot = v1;
+
+ var low_end = from + 1; // Upper bound of elements lower than pivot.
+ var high_start = to - 1; // Lower bound of elements greater than pivot.
+
+ a[third_index] = a[low_end];
+ a[low_end] = pivot;
+
+ // From low_end to i are elements equal to pivot.
+ // From i to high_start are elements that haven't been compared yet.
+
+ partition: for (var i = low_end + 1; i < high_start; i++) {
+ var element = a[i];
+ var order = comparefn(element, pivot);
+ if (order < 0) {
+ a[i] = a[low_end];
+ a[low_end] = element;
+ low_end++;
+ } else if (order > 0) {
+ do {
+ high_start--;
+ if (high_start == i) break partition;
+ var top_elem = a[high_start];
+ order = comparefn(top_elem, pivot);
+ } while (order > 0);
+
+ a[i] = a[high_start];
+ a[high_start] = element;
+ if (order < 0) {
+ element = a[i];
+ a[i] = a[low_end];
+ a[low_end] = element;
+ low_end++;
+ }
+ }
+ }
+
+
+ if (to - high_start < low_end - from) {
+ QuickSort(a, high_start, to);
+ to = low_end;
+ } else {
+ QuickSort(a, from, low_end);
+ from = high_start;
+ }
+ }
+}
+
+var arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
+
+function comparefn(a, b) {
+ return a - b
+}
+
+QuickSort(arr, 0, arr.length)
+console.log(arr)
+```
+
+我们以数组 `[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]` 为例,分析执行的过程。
+
+1.执行 QuickSort 函数 参数 from 值为 0,参数 to 的值 11。
+
+2.10 < to - from < 1000 第三个基准元素的下标为 `(0 + 11 >> 1) = 5`,基准值 a[5] 为 5。
+
+3.比较 a[0] a[10] a[5] 的值,然后根据比较结果修改数组,数组此时为 [0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 10]
+
+4.将基准值和数组的第(from + 1)个即数组的第二个元素互换,此时数组为 [0, 5, 8, 7, 6, 9, 4, 3, 2, 1, 10],此时在基准值 5 前面的元素肯定是小于 5 的,因为第三步已经做了一次比较。后面的元素是未排序的。
+
+我们接下来要做的就是把后面的元素中小于 5 的全部移到 5 的前面。
+
+5.然后我们进入 partition 循环,我们依然以这个数组为例,单独抽出来写个 demo 讲一讲
+
+```js
+// 假设代码执行到这里,为了方便演示,我们直接设置 low_end 等变量的值
+// 可以直接复制到浏览器中查看数组变换效果
+var a = [0, 5, 8, 7, 6, 9, 4, 3, 2, 1, 10]
+var low_end = 1;
+var high_start = 10;
+var pivot = 5;
+
+console.log('起始数组为', a)
+
+partition: for (var i = low_end + 1; i < high_start; i++) {
+
+ var element = a[i];
+ console.log('循环当前的元素为:', a[i])
+ var order = element - pivot;
+
+ if (order < 0) {
+ a[i] = a[low_end];
+ a[low_end] = element;
+ low_end++;
+ console.log(a)
+ }
+ else if (order > 0) {
+ do {
+ high_start--;
+ if (high_start == i) break partition;
+ var top_elem = a[high_start];
+ order = top_elem - pivot;
+ } while (order > 0);
+
+ a[i] = a[high_start];
+ a[high_start] = element;
+
+ console.log(a)
+
+ if (order < 0) {
+ element = a[i];
+ a[i] = a[low_end];
+ a[low_end] = element;
+ low_end++;
+ }
+ console.log(a)
+ }
+}
+
+console.log('最后的结果为', a)
+console.log(low_end)
+console.log(high_start)
+```
+
+6.此时数组为 `[0, 5, 8, 7, 6, 9, 4, 3, 2, 1, 10]`,循环从第三个元素开始,a[i] 的值为 8,因为大于基准值 5,即 order > 0,开始执行 do while 循环,do while 循环的目的在于倒序查找元素,找到第一个小于基准值的元素,然后让这个元素跟 a[i] 的位置交换。
+第一个小于基准值的元素为 1,然后 1 与 8 交换,数组变成 `[0, 5, 1, 7, 6, 9, 4, 3, 2, 8, 10]`。high_start 的值是为了记录倒序查找到哪里了。
+
+7.此时 a[i] 的值变成了 1,然后让 1 跟 基准值 5 交换,数组变成了 `[0, 1, 5, 7, 6, 9, 4, 3, 2, 8, 10]`,low_end 的值加 1,low_end 的值是为了记录基准值的所在位置。
+
+8.循环接着执行,遍历第四个元素 7,跟第 6、7 的步骤一致,数组先变成 `[0, 1, 5, 2, 6, 9, 4, 3, 7, 8, 10]`,再变成 `[0, 1, 2, 5, 6, 9, 4, 3, 7, 8, 10]`
+
+9.遍历第五个元素 6,跟第 6、7 的步骤一致,数组先变成 `[0, 1, 2, 5, 3, 9, 4, 6, 7, 8, 10]`,再变成 `[0, 1, 2, 3, 5, 9, 4, 6, 7, 8, 10]`
+
+10.遍历第六个元素 9,跟第 6、7 的步骤一致,数组先变成 `[0, 1, 2, 3, 5, 4, 9, 6, 7, 8, 10]`,再变成 `[0, 1, 2, 3, 4, 5, 9, 6, 7, 8, 10]`
+
+11.在下一次遍历中,因为 i == high_start,意味着正序和倒序的查找终于找到一起了,后面的元素肯定都是大于基准值的,此时退出循环
+
+12.遍历后的结果为 `[0, 1, 2, 3, 4, 5, 9, 6, 7, 8, 10]`,在基准值 5 前面的元素都小于 5,后面的元素都大于 5,然后我们分别对两个子集进行 QuickSort
+
+13.此时 low_end 值为 5,high_start 值为 6,to 的值依然是 10,from 的值依然是 0,`to - high_start < low_end - from ` 的结果为 `true`,我们对 QuickSort(a, 6, 10),即对后面的元素进行排序,但是注意,在新的 QuickSort 中,因为 from - to 的值小于 10,所以这一次其实是采用了插入排序。所以准确的说,**当数组长度大于 10 的时候,v8 采用了快速排序和插入排序的混合排序方法。**
+
+14.然后 `to = low_end` 即设置 to 为 5,因为 while(true) 的原因,会再执行一遍,to - from 的值为 5,执行 InsertionSort(a, 0, 5),即对基准值前面的元素执行一次插入排序。
+
+15.因为在 to - from <= 10 的判断中,有 return 语句,所以 while 循环结束。
+
+16.v8 在对数组进行了一次快速排序后,然后对两个子集分别进行了插入排序,最终修改数组为正确排序后的数组。
+
+## 比较
+
+最后来张示意图感受下插入排序和快速排序:
+
+
+
+图片来自于 [https://www.toptal.com/developers/sorting-algorithms](https://www.toptal.com/developers/sorting-algorithms)
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
\ No newline at end of file
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\350\212\202\346\265\201.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\350\212\202\346\265\201.md"
new file mode 100644
index 00000000..546dbd83
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\350\212\202\346\265\201.md"
@@ -0,0 +1,236 @@
+# JavaScript专题之跟着 underscore 学节流
+
+## 前言
+
+在[《JavaScript专题之跟着underscore学防抖》](https://github.com/mqyqingfeng/Blog/issues/22)中,我们了解了为什么要限制事件的频繁触发,以及如何做限制:
+
+1. debounce 防抖
+2. throttle 节流
+
+今天重点讲讲节流的实现。
+
+## 节流
+
+节流的原理很简单:
+
+如果你持续触发事件,每隔一段时间,只执行一次事件。
+
+根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。
+我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
+
+关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
+
+## 使用时间戳
+
+让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
+
+看了这个表述,是不是感觉已经可以写出代码了…… 让我们来写第一版的代码:
+
+```js
+// 第一版
+function throttle(func, wait) {
+ var context, args;
+ var previous = 0;
+
+ return function() {
+ var now = +new Date();
+ context = this;
+ args = arguments;
+ if (now - previous > wait) {
+ func.apply(context, args);
+ previous = now;
+ }
+ }
+}
+```
+
+例子依然是用讲 debounce 中的例子,如果你要使用:
+
+```js
+container.onmousemove = throttle(getUserAction, 1000);
+```
+
+效果演示如下:
+
+
+
+我们可以看到:当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。
+
+## 使用定时器
+
+接下来,我们讲讲第二种实现方式,使用定时器。
+
+当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
+
+```js
+// 第二版
+function throttle(func, wait) {
+ var timeout;
+ var previous = 0;
+
+ return function() {
+ context = this;
+ args = arguments;
+ if (!timeout) {
+ timeout = setTimeout(function(){
+ timeout = null;
+ func.apply(context, args)
+ }, wait)
+ }
+
+ }
+}
+```
+
+为了让效果更加明显,我们设置 wait 的时间为 3s,效果演示如下:
+
+
+
+我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。
+
+所以比较两个方法:
+
+1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
+2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
+
+## 双剑合璧
+
+那我们想要一个什么样的呢?
+
+有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!
+
+所以我们综合两者的优势,然后双剑合璧,写一版代码:
+
+```js
+// 第三版
+function throttle(func, wait) {
+ var timeout, context, args, result;
+ var previous = 0;
+
+ var later = function() {
+ previous = +new Date();
+ timeout = null;
+ func.apply(context, args)
+ };
+
+ var throttled = function() {
+ var now = +new Date();
+ //下次触发 func 剩余的时间
+ var remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ // 如果没有剩余的时间了或者你改了系统时间
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null;
+ }
+ previous = now;
+ func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ };
+ return throttled;
+}
+```
+
+效果演示如下:
+
+
+
+我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。
+
+## 优化
+
+但是我有时也希望无头有尾,或者有头无尾,这个咋办?
+
+那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定:
+
+leading:false 表示禁用第一次执行
+trailing: false 表示禁用停止触发的回调
+
+我们来改一下代码:
+
+```js
+// 第四版
+function throttle(func, wait, options) {
+ var timeout, context, args, result;
+ var previous = 0;
+ if (!options) options = {};
+
+ var later = function() {
+ previous = options.leading === false ? 0 : new Date().getTime();
+ timeout = null;
+ func.apply(context, args);
+ if (!timeout) context = args = null;
+ };
+
+ var throttled = function() {
+ var now = new Date().getTime();
+ 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;
+ func.apply(context, args);
+ if (!timeout) context = args = null;
+ } else if (!timeout && options.trailing !== false) {
+ timeout = setTimeout(later, remaining);
+ }
+ };
+ return throttled;
+}
+```
+
+## 取消
+
+在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法:
+
+```js
+// 第五版 非完整代码,完整代码请查看最后的演示代码链接
+...
+throttled.cancel = function() {
+ clearTimeout(timeout);
+ previous = 0;
+ timeout = null;
+}
+...
+```
+
+## 注意
+
+我们要注意 underscore 的实现中有这样一个问题:
+
+那就是 `leading:false` 和 `trailing: false` 不能同时设置。
+
+如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了,所以,这个 throttle 只有三种用法:
+
+```js
+container.onmousemove = throttle(getUserAction, 1000);
+container.onmousemove = throttle(getUserAction, 1000, {
+ leading: false
+});
+container.onmousemove = throttle(getUserAction, 1000, {
+ trailing: false
+});
+```
+
+至此我们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!
+
+## 演示代码
+
+相关的代码可以在 [Github 博客仓库](https://github.com/mqyqingfeng/Blog/tree/master/demos/throttle) 中找到
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
\ No newline at end of file
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\351\230\262\346\212\226.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\351\230\262\346\212\226.md"
new file mode 100644
index 00000000..0f94309a
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\350\267\237\347\235\200underscore\345\255\246\351\230\262\346\212\226.md"
@@ -0,0 +1,329 @@
+# JavaScript专题之跟着underscore学防抖
+
+## 前言
+
+在前端开发中会遇到一些频繁的事件触发,比如:
+
+1. window 的 resize、scroll
+2. mousedown、mousemove
+3. keyup、keydown
+……
+
+为此,我们举个示例代码来了解事件如何频繁的触发:
+
+我们写个 `index.html` 文件:
+
+```html
+
+
+
+
+
+
+ debounce
+
+
+
+
+
+
+
+
+
+```
+
+`debounce.js` 文件的代码如下:
+
+```js
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction() {
+ container.innerHTML = count++;
+};
+
+container.onmousemove = getUserAction;
+```
+
+我们来看看效果:
+
+
+
+从左边滑到右边就触发了 165 次 getUserAction 函数!
+
+因为这个例子很简单,所以浏览器完全反应的过来,可是如果是复杂的回调函数或是 ajax 请求呢?假设 1 秒触发了 60 次,每个回调就必须在 1000 / 60 = 16.67ms 内完成,否则就会有卡顿出现。
+
+为了解决这个问题,一般有两种解决方案:
+
+1. debounce 防抖
+2. throttle 节流
+
+今天重点讲讲防抖的实现。
+
+## 防抖
+
+防抖的原理就是:你尽管触发事件,但是我一定在事件停止触发 n 秒后才执行。
+
+这意味着如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件触发的时间为准,在此时间 n 秒后才执行。
+
+总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!
+
+## 第一版
+
+根据这段表述,我们可以轻松写出第一版的代码:
+
+```js
+// 第一版
+function debounce(func, wait) {
+ var timeout;
+ return function () {
+ clearTimeout(timeout)
+ timeout = setTimeout(func, wait);
+ }
+}
+```
+
+如果我们要使用它,以最一开始的例子为例:
+
+```js
+container.onmousemove = debounce(getUserAction, 1000);
+```
+
+现在随你怎么移动,反正你移动完 1000ms 内不再触发,我才执行事件。看看使用效果:
+
+
+
+顿时就从 165 次降低成了 1 次!
+
+棒棒哒,我们接着完善它。
+
+## this
+
+如果我们在 `getUserAction` 函数中 `console.log(this)`,在不使用 `debounce` 函数的时候,`this` 的值为:
+
+```html
+
+```
+
+但是如果使用我们的 debounce 函数,this 就会指向 Window 对象!
+
+所以我们需要将 this 指向正确的对象。
+
+我们修改下代码:
+
+```js
+// 第二版
+function debounce(func, wait) {
+ var timeout;
+
+ return function () {
+ var context = this;
+
+ clearTimeout(timeout)
+ timeout = setTimeout(function(){
+ func.apply(context)
+ }, wait);
+ }
+}
+```
+
+现在 this 已经可以正确指向了。让我们看下个问题:
+
+## event 对象
+
+JavaScript 在事件处理函数中会提供事件对象 event,我们修改下 getUserAction 函数:
+
+```js
+function getUserAction(e) {
+ console.log(e);
+ container.innerHTML = count++;
+};
+```
+
+如果我们不使用 debouce 函数,这里会打印 MouseEvent 对象,如图所示:
+
+
+
+但是在我们实现的 debounce 函数中,却只会打印 undefined!
+
+所以我们再修改一下代码:
+
+```js
+// 第三版
+function debounce(func, wait) {
+ var timeout;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ clearTimeout(timeout)
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+}
+```
+
+到此为止,我们修复了两个小问题:
+
+1. this 指向
+2. event 对象
+
+## 立刻执行
+
+这个时候,代码已经很是完善了,但是为了让这个函数更加完善,我们接下来思考一个新的需求。
+
+这个需求就是:
+
+我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
+
+想想这个需求也是很有道理的嘛,那我们加个 immediate 参数判断是否是立刻执行。
+
+```js
+// 第四版
+function debounce(func, wait, immediate) {
+
+ var timeout, result;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+ }
+}
+```
+
+再来看看使用效果:
+
+
+
+## 返回值
+
+此时注意一点,就是 getUserAction 函数可能是有返回值的,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。
+
+```js
+// 第五版
+function debounce(func, wait, immediate) {
+
+ var timeout, result;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) result = func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+ return result;
+ }
+}
+```
+
+## 取消
+
+最后我们再思考一个小需求,我希望能取消 debounce 函数,比如说我 debounce 的时间间隔是 10 秒钟,immediate 为 true,这样的话,我只有等 10 秒后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行啦,是不是很开心?
+
+为了这个需求,我们写最后一版的代码:
+
+```js
+// 第六版
+function debounce(func, wait, immediate) {
+
+ var timeout, result;
+
+ var debounced = function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) result = func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+ return result;
+ };
+
+ debounced.cancel = function() {
+ clearTimeout(timeout);
+ timeout = null;
+ };
+
+ return debounced;
+}
+```
+
+那么该如何使用这个 cancel 函数呢?依然是以上面的 demo 为例:
+
+```js
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction(e) {
+ container.innerHTML = count++;
+};
+
+var setUseAction = debounce(getUserAction, 10000, true);
+
+container.onmousemove = setUseAction;
+
+document.getElementById("button").addEventListener('click', function(){
+ setUseAction.cancel();
+})
+```
+
+演示效果如下:
+
+
+
+至此我们已经完整实现了一个 underscore 中的 debounce 函数,恭喜,撒花!
+
+## 演示代码
+
+相关的代码可以在 [Github 博客仓库](https://github.com/mqyqingfeng/Blog/tree/master/demos/debounce) 中找到
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
diff --git "a/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\351\200\222\345\275\222.md" "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\351\200\222\345\275\222.md"
new file mode 100644
index 00000000..a44e905d
--- /dev/null
+++ "b/articles/\344\270\223\351\242\230\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\344\270\223\351\242\230\344\271\213\351\200\222\345\275\222.md"
@@ -0,0 +1,299 @@
+# JavaScript专题之递归
+
+## 定义
+
+程序调用自身的编程技巧称为递归(recursion)。
+
+## 阶乘
+
+以阶乘为例:
+
+```js
+function factorial(n) {
+ if (n == 1) return n;
+ return n * factorial(n - 1)
+}
+
+console.log(factorial(5)) // 5 * 4 * 3 * 2 * 1 = 120
+```
+
+示意图(图片来自 [wwww.penjee.com](wwww.penjee.com)):
+
+
+
+## 斐波那契数列
+
+在[《JavaScript专题之函数记忆》](https://github.com/mqyqingfeng/Blog/issues/46)中讲到过的斐波那契数列也使用了递归:
+
+```js
+function fibonacci(n){
+ return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+}
+
+console.log(fibonacci(5)) // 1 1 2 3 5
+```
+
+## 递归条件
+
+从这两个例子中,我们可以看出:
+
+构成递归需具备边界条件、递归前进段和递归返回段,当边界条件不满足时,递归前进,当边界条件满足时,递归返回。阶乘中的 `n == 1` 和 斐波那契数列中的 `n < 2` 都是边界条件。
+
+总结一下递归的特点:
+
+1. 子问题须与原始问题为同样的事,且更为简单;
+2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
+
+了解这些特点可以帮助我们更好的编写递归函数。
+
+## 执行上下文栈
+
+在[《JavaScript深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中,我们知道:
+
+当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
+
+试着对阶乘函数分析执行的过程,我们会发现,JavaScript 会不停的创建执行上下文压入执行上下文栈,对于内存而言,维护这么多的执行上下文也是一笔不小的开销呐!那么,我们该如何优化呢?
+
+答案就是尾调用。
+
+## 尾调用
+
+尾调用,是指函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函数。
+
+举个例子:
+
+```js
+// 尾调用
+function f(x){
+ return g(x);
+}
+```
+
+然而
+
+```js
+// 非尾调用
+function f(x){
+ return g(x) + 1;
+}
+```
+
+并不是尾调用,因为 g(x) 的返回值还需要跟 1 进行计算后,f(x)才会返回值。
+
+两者又有什么区别呢?答案就是执行上下文栈的变化不一样。
+
+为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:
+
+```js
+ ECStack = [];
+```
+
+我们模拟下第一个尾调用函数执行时的执行上下文栈变化:
+
+```js
+// 伪代码
+ECStack.push( functionContext);
+
+ECStack.pop();
+
+ECStack.push( functionContext);
+
+ECStack.pop();
+```
+
+我们再来模拟一下第二个非尾调用函数执行时的执行上下文栈变化:
+
+```js
+ECStack.push( functionContext);
+
+ECStack.push( functionContext);
+
+ECStack.pop();
+
+ECStack.pop();
+```
+
+也就说尾调用函数执行时,虽然也调用了一个函数,但是因为原来的的函数执行完毕,执行上下文会被弹出,执行上下文栈中相当于只多压入了一个执行上下文。然而非尾调用函数,就会创建多个执行上下文压入执行上下文栈。
+
+函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
+
+所以我们只用把阶乘函数改造成一个尾递归形式,就可以避免创建那么多的执行上下文。但是我们该怎么做呢?
+
+## 阶乘函数优化
+
+我们需要做的就是把所有用到的内部变量改写成函数的参数,以阶乘函数为例:
+
+```js
+function factorial(n, res) {
+ if (n == 1) return res;
+ return factorial2(n - 1, n * res)
+}
+
+console.log(factorial(4, 1)) // 24
+```
+
+然而这个很奇怪呐……我们计算 4 的阶乘,结果函数要传入 4 和 1,我就不能只传入一个 4 吗?
+
+这个时候就要用到我们在[《JavaScript专题之柯里化》](https://github.com/mqyqingfeng/Blog/issues/42)中编写的 curry 函数了:
+
+```js
+var newFactorial = curry(factorial, _, 1)
+
+newFactorial(5) // 24
+```
+
+## 应用
+
+如果你看过 [JavaScript 专题系列](https://github.com/mqyqingfeng/Blog)的文章,你会发现递归有着很多的应用。
+
+作为专题系列的第十八篇,我们来盘点下之前的文章中都有哪些涉及到了递归:
+
+1.[《JavaScript 专题之数组扁平化》](https://github.com/mqyqingfeng/Blog/issues/36):
+
+```js
+function flatten(arr) {
+ return arr.reduce(function(prev, next){
+ return prev.concat(Array.isArray(next) ? flatten(next) : next)
+ }, [])
+}
+```
+
+2.[《JavaScript 专题之深浅拷贝》](https://github.com/mqyqingfeng/Blog/issues/32):
+
+```js
+var deepCopy = function(obj) {
+ if (typeof obj !== 'object') return;
+ var newObj = obj instanceof Array ? [] : {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
+ }
+ }
+ return newObj;
+}
+```
+
+3.[JavaScript 专题之从零实现 jQuery 的 extend](https://github.com/mqyqingfeng/Blog/issues/33):
+
+```js
+// 非完整版本,完整版本请点击查看具体的文章
+function extend() {
+
+ ...
+
+ // 循环遍历要复制的对象们
+ for (; i < length; i++) {
+ // 获取当前对象
+ options = arguments[i];
+ // 要求不能为空 避免extend(a,,b)这种情况
+ if (options != null) {
+ for (name in options) {
+ // 目标属性值
+ src = target[name];
+ // 要复制的对象的属性值
+ copy = options[name];
+
+ if (deep && copy && typeof copy == 'object') {
+ // 递归调用
+ target[name] = extend(deep, src, copy);
+ }
+ else if (copy !== undefined){
+ target[name] = copy;
+ }
+ }
+ }
+ }
+
+ ...
+
+};
+```
+
+4.[《JavaScript 专题之如何判断两个对象相等》](https://github.com/mqyqingfeng/Blog/issues/41):
+
+```js
+// 非完整版本,完整版本请点击查看具体的文章
+// 属于间接调用
+function eq(a, b, aStack, bStack) {
+
+ ...
+
+ // 更复杂的对象使用 deepEq 函数进行深度比较
+ return deepEq(a, b, aStack, bStack);
+};
+
+function deepEq(a, b, aStack, bStack) {
+
+ ...
+
+ // 数组判断
+ if (areArrays) {
+
+ length = a.length;
+ if (length !== b.length) return false;
+
+ while (length--) {
+ if (!eq(a[length], b[length], aStack, bStack)) return false;
+ }
+ }
+ // 对象判断
+ else {
+
+ var keys = Object.keys(a),
+ key;
+ length = keys.length;
+
+ if (Object.keys(b).length !== length) return false;
+ while (length--) {
+
+ key = keys[length];
+ if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
+ }
+ }
+
+}
+```
+
+5.[《JavaScript 专题之函数柯里化》](https://github.com/mqyqingfeng/Blog/issues/42):
+
+```js
+// 非完整版本,完整版本请点击查看具体的文章
+function curry(fn, args) {
+ length = fn.length;
+
+ args = args || [];
+
+ return function() {
+
+ var _args = args.slice(0),
+
+ arg, i;
+
+ for (i = 0; i < arguments.length; i++) {
+
+ arg = arguments[i];
+
+ _args.push(arg);
+
+ }
+ if (_args.length < length) {
+ return curry.call(this, fn, _args);
+ }
+ else {
+ return fn.apply(this, _args);
+ }
+ }
+}
+```
+
+## 写在最后
+
+递归的内容远不止这些,比如还有汉诺塔、二叉树遍历等递归场景,本篇就不过多展开,真希望未来能写个算法系列。
+
+## 专题系列
+
+JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。
+
+JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
+
+如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
similarity index 79%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
index 72872584..68b514da 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213bind\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
@@ -39,12 +39,28 @@ bindFoo(); // 1
Function.prototype.bind2 = function (context) {
var self = this;
return function () {
- self.apply(context);
+ return self.apply(context);
}
}
```
+此外,之所以 `return self.apply(context)`,是考虑到绑定函数可能是有返回值的,依然是这个例子:
+
+```js
+var foo = {
+ value: 1
+};
+
+function bar() {
+ return this.value;
+}
+
+var bindFoo = bar.bind(foo);
+
+console.log(bindFoo()); // 1
+```
+
## 传参的模拟实现
接下来看第二点,可以传入参数。这个就有点让人费解了,我在 bind 的时候,是否可以传参呢?我在执行 bind 返回的函数的时候,可不可以传参呢?让我们看个例子:
@@ -83,7 +99,7 @@ Function.prototype.bind2 = function (context) {
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
- self.apply(context, args.concat(bindArgs));
+ return self.apply(context, args.concat(bindArgs));
}
}
@@ -137,16 +153,16 @@ Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
- var fbound = function () {
-
+ var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
- // 当作为构造函数时,this 指向实例,self 指向绑定函数,因为下面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。
- // 当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。
- self.apply(this instanceof self ? this : context, args.concat(bindArgs));
+ // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
+ // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
+ // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
+ return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
- // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
- fbound.prototype = this.prototype;
- return fbound;
+ // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
+ fBound.prototype = this.prototype;
+ return fBound;
}
```
@@ -155,7 +171,7 @@ Function.prototype.bind2 = function (context) {
## 构造函数效果的优化实现
-但是在这个写法中,我们直接将 fbound.prototype = this.prototype,我们直接修改 fbound.prototype 的时候,也会直接修改函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
+但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
```js
// 第四版
@@ -166,14 +182,14 @@ Function.prototype.bind2 = function (context) {
var fNOP = function () {};
- var fbound = function () {
+ var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
- self.apply(this instanceof self ? this : context, args.concat(bindArgs));
+ return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
- fNOP.prototype = this.prototype;
- fbound.prototype = new fNOP();
- return fbound;
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+ return fBound;
}
```
@@ -235,7 +251,7 @@ Function.prototype.bind = Function.prototype.bind || function () {
};
```
-当然最好是用[es5-shim](https://github.com/es-shims/es5-shim)啦。
+当然最好是用 [es5-shim](https://github.com/es-shims/es5-shim) 啦。
## 最终代码
@@ -250,17 +266,17 @@ Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
+
var fNOP = function () {};
- var fbound = function () {
- self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
+ var fBound = function () {
+ var bindArgs = Array.prototype.slice.call(arguments);
+ return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
- fbound.prototype = new fNOP();
-
- return fbound;
-
+ fBound.prototype = new fNOP();
+ return fBound;
}
```
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
similarity index 98%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
index d4eba37b..2f67ef3e 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213call\345\222\214apply\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
@@ -136,7 +136,7 @@ for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
-// 执行后 args为 [foo, 'kevin', 18]
+// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
```
不定长的参数问题解决了,我们接着要把这个参数数组放到要执行的函数的参数里面去。
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213new\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213new\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213new\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213new\347\232\204\346\250\241\346\213\237\345\256\236\347\216\260.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216ECMAScript\350\247\204\350\214\203\350\247\243\350\257\273this.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216ECMAScript\350\247\204\350\214\203\350\247\243\350\257\273this.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216ECMAScript\350\247\204\350\214\203\350\247\243\350\257\273this.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216ECMAScript\350\247\204\350\214\203\350\247\243\350\257\273this.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md"
similarity index 93%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md"
index 3ec618d1..b7135527 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\273\216\345\216\237\345\236\213\345\210\260\345\216\237\345\236\213\351\223\276.md"
@@ -150,15 +150,23 @@ console.log(obj.name) // Kevin
那 Object.prototype 的原型呢?
-null,不信我们可以打印:
+null,我们可以打印:
```js
console.log(Object.prototype.__proto__ === null) // true
```
-所以查到属性的时候查到 Object.prototype 就可以停止查找了。
+然而 null 究竟代表了什么呢?
-所以最后一张关系图就是
+引用阮一峰老师的 [《undefined与null的区别》](http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html) 就是:
+
+> null 表示“没有对象”,即该处不应该有值。
+
+所以 Object.prototype.\_\_proto\_\_ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
+
+所以查找属性的时候查到 Object.prototype 就可以停止查找了。
+
+最后一张关系图也可以更新为:

diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\275\234\347\224\250\345\237\237\351\223\276.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\275\234\347\224\250\345\237\237\351\223\276.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\275\234\347\224\250\345\237\237\351\223\276.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\344\275\234\347\224\250\345\237\237\351\223\276.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\210\233\345\273\272\345\257\271\350\261\241\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\344\273\245\345\217\212\344\274\230\347\274\272\347\202\271.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\210\233\345\273\272\345\257\271\350\261\241\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\344\273\245\345\217\212\344\274\230\347\274\272\347\202\271.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\210\233\345\273\272\345\257\271\350\261\241\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\344\273\245\345\217\212\344\274\230\347\274\272\347\202\271.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\210\233\345\273\272\345\257\271\350\261\241\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\344\273\245\345\217\212\344\274\230\347\274\272\347\202\271.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\202\346\225\260\346\214\211\345\200\274\344\274\240\351\200\222.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\202\346\225\260\346\214\211\345\200\274\344\274\240\351\200\222.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\202\346\225\260\346\214\211\345\200\274\344\274\240\351\200\222.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\202\346\225\260\346\214\211\345\200\274\344\274\240\351\200\222.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md"
similarity index 98%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md"
index 0c5720dc..fe4af81c 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\345\217\230\351\207\217\345\257\271\350\261\241.md"
@@ -22,7 +22,7 @@
## 全局上下文
-我们先了解一个概念,叫全局对象。在 [W3C school](http://www.w3school.com.cn/jsref/jsref_obj_global.asp) 中也有介绍:
+我们先了解一个概念,叫全局对象。在 [W3School](http://www.w3school.com.cn/jsref/jsref_obj_global.asp) 中也有介绍:
>全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md"
similarity index 99%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md"
index 83e3ef4c..3f81bd1c 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207.md"
@@ -4,7 +4,7 @@
# 前言
-在[《JavaScript深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中讲到,当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
+在[《JavaScript深入之执行上下文栈》](https://github.com/mqyqingfeng/Blog/issues/4)中讲到,当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution contexts)。
对于每个执行上下文,都有三个重要属性:
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md"
similarity index 98%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md"
index 3e63fdf2..ac2cb364 100644
--- "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md"
+++ "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\346\211\247\350\241\214\344\270\212\344\270\213\346\226\207\346\240\210.md"
@@ -60,7 +60,7 @@ foo(); // foo2
其实很简单,就三种,全局代码、函数代码、eval代码。
-举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution contexts)"。
+举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。
## 执行上下文栈
@@ -71,15 +71,15 @@ foo(); // foo2
为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:
```js
- ECStack = [];
+ECStack = [];
```
试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以 ECStack 最底部永远有个 globalContext:
```js
- ECStack = [
- globalContext
- ];
+ECStack = [
+ globalContext
+];
```
现在 JavaScript 遇到下面的这段代码了:
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\261\273\346\225\260\347\273\204\345\257\271\350\261\241\344\270\216arguments.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\261\273\346\225\260\347\273\204\345\257\271\350\261\241\344\270\216arguments.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\261\273\346\225\260\347\273\204\345\257\271\350\261\241\344\270\216arguments.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\261\273\346\225\260\347\273\204\345\257\271\350\261\241\344\270\216arguments.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\273\247\346\211\277\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\345\222\214\344\274\230\347\274\272\347\202\271.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\273\247\346\211\277\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\345\222\214\344\274\230\347\274\272\347\202\271.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\273\247\346\211\277\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\345\222\214\344\274\230\347\274\272\347\202\271.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\347\273\247\346\211\277\347\232\204\345\244\232\347\247\215\346\226\271\345\274\217\345\222\214\344\274\230\347\274\272\347\202\271.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\350\257\215\346\263\225\344\275\234\347\224\250\345\237\237\345\222\214\345\212\250\346\200\201\344\275\234\347\224\250\345\237\237.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\350\257\215\346\263\225\344\275\234\347\224\250\345\237\237\345\222\214\345\212\250\346\200\201\344\275\234\347\224\250\345\237\237.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\350\257\215\346\263\225\344\275\234\347\224\250\345\237\237\345\222\214\345\212\250\346\200\201\344\275\234\347\224\250\345\237\237.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\350\257\215\346\263\225\344\275\234\347\224\250\345\237\237\345\222\214\345\212\250\346\200\201\344\275\234\347\224\250\345\237\237.md"
diff --git "a/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\351\227\255\345\214\205.md" "b/articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\351\227\255\345\214\205.md"
similarity index 100%
rename from "\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\351\227\255\345\214\205.md"
rename to "articles/\346\267\261\345\205\245\347\263\273\345\210\227\346\226\207\347\253\240/JavaScript\346\267\261\345\205\245\344\271\213\351\227\255\345\214\205.md"
diff --git a/demos/ES6/generator/generator-es5.js b/demos/ES6/generator/generator-es5.js
new file mode 100644
index 00000000..4e79fe6b
--- /dev/null
+++ b/demos/ES6/generator/generator-es5.js
@@ -0,0 +1,757 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+!(function(global) {
+ "use strict";
+
+ var Op = Object.prototype;
+ var hasOwn = Op.hasOwnProperty;
+ var undefined; // More compressible than void 0.
+ var $Symbol = typeof Symbol === "function" ? Symbol : {};
+ var iteratorSymbol = $Symbol.iterator || "@@iterator";
+ var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
+ var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
+
+ var inModule = typeof module === "object";
+ var runtime = global.regeneratorRuntime;
+ if (runtime) {
+ if (inModule) {
+ // If regeneratorRuntime is defined globally and we're in a module,
+ // make the exports object identical to regeneratorRuntime.
+ module.exports = runtime;
+ }
+ // Don't bother evaluating the rest of this file if the runtime was
+ // already defined globally.
+ return;
+ }
+
+ // Define the runtime globally (as expected by generated code) as either
+ // module.exports (if we're in a module) or a new, empty object.
+ runtime = global.regeneratorRuntime = inModule ? module.exports : {};
+
+ function wrap(innerFn, outerFn, self, tryLocsList) {
+ // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
+ var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
+ var generator = Object.create(protoGenerator.prototype);
+ var context = new Context(tryLocsList || []);
+
+ // The ._invoke method unifies the implementations of the .next,
+ // .throw, and .return methods.
+ generator._invoke = makeInvokeMethod(innerFn, self, context);
+
+ return generator;
+ }
+ runtime.wrap = wrap;
+
+ // Try/catch helper to minimize deoptimizations. Returns a completion
+ // record like context.tryEntries[i].completion. This interface could
+ // have been (and was previously) designed to take a closure to be
+ // invoked without arguments, but in all the cases we care about we
+ // already have an existing method we want to call, so there's no need
+ // to create a new function object. We can even get away with assuming
+ // the method takes exactly one argument, since that happens to be true
+ // in every case, so we don't have to touch the arguments object. The
+ // only additional allocation required is the completion record, which
+ // has a stable shape and so hopefully should be cheap to allocate.
+ function tryCatch(fn, obj, arg) {
+ try {
+ return { type: "normal", arg: fn.call(obj, arg) };
+ } catch (err) {
+ return { type: "throw", arg: err };
+ }
+ }
+
+ var GenStateSuspendedStart = "suspendedStart";
+ var GenStateSuspendedYield = "suspendedYield";
+ var GenStateExecuting = "executing";
+ var GenStateCompleted = "completed";
+
+ // Returning this object from the innerFn has the same effect as
+ // breaking out of the dispatch switch statement.
+ var ContinueSentinel = {};
+
+ // Dummy constructor functions that we use as the .constructor and
+ // .constructor.prototype properties for functions that return Generator
+ // objects. For full spec compliance, you may wish to configure your
+ // minifier not to mangle the names of these two functions.
+ function Generator() {}
+ function GeneratorFunction() {}
+ function GeneratorFunctionPrototype() {}
+
+ // This is a polyfill for %IteratorPrototype% for environments that
+ // don't natively support it.
+ var IteratorPrototype = {};
+ IteratorPrototype[iteratorSymbol] = function () {
+ return this;
+ };
+
+ var getProto = Object.getPrototypeOf;
+ var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
+ if (NativeIteratorPrototype &&
+ NativeIteratorPrototype !== Op &&
+ hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
+ // This environment has a native %IteratorPrototype%; use it instead
+ // of the polyfill.
+ IteratorPrototype = NativeIteratorPrototype;
+ }
+
+ var Gp = GeneratorFunctionPrototype.prototype =
+ Generator.prototype = Object.create(IteratorPrototype);
+ GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
+ GeneratorFunctionPrototype.constructor = GeneratorFunction;
+ GeneratorFunctionPrototype[toStringTagSymbol] =
+ GeneratorFunction.displayName = "GeneratorFunction";
+
+ // Helper for defining the .next, .throw, and .return methods of the
+ // Iterator interface in terms of a single ._invoke method.
+ function defineIteratorMethods(prototype) {
+ ["next", "throw", "return"].forEach(function(method) {
+ prototype[method] = function(arg) {
+ return this._invoke(method, arg);
+ };
+ });
+ }
+
+ runtime.isGeneratorFunction = function(genFun) {
+ var ctor = typeof genFun === "function" && genFun.constructor;
+ return ctor
+ ? ctor === GeneratorFunction ||
+ // For the native GeneratorFunction constructor, the best we can
+ // do is to check its .name property.
+ (ctor.displayName || ctor.name) === "GeneratorFunction"
+ : false;
+ };
+
+ runtime.mark = function(genFun) {
+ if (Object.setPrototypeOf) {
+ Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
+ } else {
+ genFun.__proto__ = GeneratorFunctionPrototype;
+ if (!(toStringTagSymbol in genFun)) {
+ genFun[toStringTagSymbol] = "GeneratorFunction";
+ }
+ }
+ genFun.prototype = Object.create(Gp);
+ return genFun;
+ };
+
+ // Within the body of any async function, `await x` is transformed to
+ // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
+ // `hasOwn.call(value, "__await")` to determine if the yielded value is
+ // meant to be awaited.
+ runtime.awrap = function(arg) {
+ return { __await: arg };
+ };
+
+ function AsyncIterator(generator) {
+ function invoke(method, arg, resolve, reject) {
+ var record = tryCatch(generator[method], generator, arg);
+ if (record.type === "throw") {
+ reject(record.arg);
+ } else {
+ var result = record.arg;
+ var value = result.value;
+ if (value &&
+ typeof value === "object" &&
+ hasOwn.call(value, "__await")) {
+ return Promise.resolve(value.__await).then(function(value) {
+ invoke("next", value, resolve, reject);
+ }, function(err) {
+ invoke("throw", err, resolve, reject);
+ });
+ }
+
+ return Promise.resolve(value).then(function(unwrapped) {
+ // When a yielded Promise is resolved, its final value becomes
+ // the .value of the Promise<{value,done}> result for the
+ // current iteration.
+ result.value = unwrapped;
+ resolve(result);
+ }, function(error) {
+ // If a rejected Promise was yielded, throw the rejection back
+ // into the async generator function so it can be handled there.
+ return invoke("throw", error, resolve, reject);
+ });
+ }
+ }
+
+ var previousPromise;
+
+ function enqueue(method, arg) {
+ function callInvokeWithMethodAndArg() {
+ return new Promise(function(resolve, reject) {
+ invoke(method, arg, resolve, reject);
+ });
+ }
+
+ return previousPromise =
+ // If enqueue has been called before, then we want to wait until
+ // all previous Promises have been resolved before calling invoke,
+ // so that results are always delivered in the correct order. If
+ // enqueue has not been called before, then it is important to
+ // call invoke immediately, without waiting on a callback to fire,
+ // so that the async generator function has the opportunity to do
+ // any necessary setup in a predictable way. This predictability
+ // is why the Promise constructor synchronously invokes its
+ // executor callback, and why async functions synchronously
+ // execute code before the first await. Since we implement simple
+ // async functions in terms of async generators, it is especially
+ // important to get this right, even though it requires care.
+ previousPromise ? previousPromise.then(
+ callInvokeWithMethodAndArg,
+ // Avoid propagating failures to Promises returned by later
+ // invocations of the iterator.
+ callInvokeWithMethodAndArg
+ ) : callInvokeWithMethodAndArg();
+ }
+
+ // Define the unified helper method that is used to implement .next,
+ // .throw, and .return (see defineIteratorMethods).
+ this._invoke = enqueue;
+ }
+
+ defineIteratorMethods(AsyncIterator.prototype);
+ AsyncIterator.prototype[asyncIteratorSymbol] = function () {
+ return this;
+ };
+ runtime.AsyncIterator = AsyncIterator;
+
+ // Note that simple async functions are implemented on top of
+ // AsyncIterator objects; they just return a Promise for the value of
+ // the final result produced by the iterator.
+ runtime.async = function(innerFn, outerFn, self, tryLocsList) {
+ var iter = new AsyncIterator(
+ wrap(innerFn, outerFn, self, tryLocsList)
+ );
+
+ return runtime.isGeneratorFunction(outerFn)
+ ? iter // If outerFn is a generator, return the full iterator.
+ : iter.next().then(function(result) {
+ return result.done ? result.value : iter.next();
+ });
+ };
+
+ function makeInvokeMethod(innerFn, self, context) {
+ var state = GenStateSuspendedStart;
+
+ return function invoke(method, arg) {
+ if (state === GenStateExecuting) {
+ throw new Error("Generator is already running");
+ }
+
+ if (state === GenStateCompleted) {
+ if (method === "throw") {
+ throw arg;
+ }
+
+ // Be forgiving, per 25.3.3.3.3 of the spec:
+ // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
+ return doneResult();
+ }
+
+ context.method = method;
+ context.arg = arg;
+
+ while (true) {
+ var delegate = context.delegate;
+ if (delegate) {
+ var delegateResult = maybeInvokeDelegate(delegate, context);
+ if (delegateResult) {
+ if (delegateResult === ContinueSentinel) continue;
+ return delegateResult;
+ }
+ }
+
+ if (context.method === "next") {
+ // Setting context._sent for legacy support of Babel's
+ // function.sent implementation.
+ context.sent = context._sent = context.arg;
+
+ } else if (context.method === "throw") {
+ if (state === GenStateSuspendedStart) {
+ state = GenStateCompleted;
+ throw context.arg;
+ }
+
+ context.dispatchException(context.arg);
+
+ } else if (context.method === "return") {
+ context.abrupt("return", context.arg);
+ }
+
+ state = GenStateExecuting;
+
+ var record = tryCatch(innerFn, self, context);
+ if (record.type === "normal") {
+ // If an exception is thrown from innerFn, we leave state ===
+ // GenStateExecuting and loop back for another invocation.
+ state = context.done
+ ? GenStateCompleted
+ : GenStateSuspendedYield;
+
+ if (record.arg === ContinueSentinel) {
+ continue;
+ }
+
+ return {
+ value: record.arg,
+ done: context.done
+ };
+
+ } else if (record.type === "throw") {
+ state = GenStateCompleted;
+ // Dispatch the exception by looping back around to the
+ // context.dispatchException(context.arg) call above.
+ context.method = "throw";
+ context.arg = record.arg;
+ }
+ }
+ };
+ }
+
+ // Call delegate.iterator[context.method](context.arg) and handle the
+ // result, either by returning a { value, done } result from the
+ // delegate iterator, or by modifying context.method and context.arg,
+ // setting context.delegate to null, and returning the ContinueSentinel.
+ function maybeInvokeDelegate(delegate, context) {
+ var method = delegate.iterator[context.method];
+ if (method === undefined) {
+ // A .throw or .return when the delegate iterator has no .throw
+ // method always terminates the yield* loop.
+ context.delegate = null;
+
+ if (context.method === "throw") {
+ if (delegate.iterator.return) {
+ // If the delegate iterator has a return method, give it a
+ // chance to clean up.
+ context.method = "return";
+ context.arg = undefined;
+ maybeInvokeDelegate(delegate, context);
+
+ if (context.method === "throw") {
+ // If maybeInvokeDelegate(context) changed context.method from
+ // "return" to "throw", let that override the TypeError below.
+ return ContinueSentinel;
+ }
+ }
+
+ context.method = "throw";
+ context.arg = new TypeError(
+ "The iterator does not provide a 'throw' method");
+ }
+
+ return ContinueSentinel;
+ }
+
+ var record = tryCatch(method, delegate.iterator, context.arg);
+
+ if (record.type === "throw") {
+ context.method = "throw";
+ context.arg = record.arg;
+ context.delegate = null;
+ return ContinueSentinel;
+ }
+
+ var info = record.arg;
+
+ if (! info) {
+ context.method = "throw";
+ context.arg = new TypeError("iterator result is not an object");
+ context.delegate = null;
+ return ContinueSentinel;
+ }
+
+ if (info.done) {
+ // Assign the result of the finished delegate to the temporary
+ // variable specified by delegate.resultName (see delegateYield).
+ context[delegate.resultName] = info.value;
+
+ // Resume execution at the desired location (see delegateYield).
+ context.next = delegate.nextLoc;
+
+ // If context.method was "throw" but the delegate handled the
+ // exception, let the outer generator proceed normally. If
+ // context.method was "next", forget context.arg since it has been
+ // "consumed" by the delegate iterator. If context.method was
+ // "return", allow the original .return call to continue in the
+ // outer generator.
+ if (context.method !== "return") {
+ context.method = "next";
+ context.arg = undefined;
+ }
+
+ } else {
+ // Re-yield the result returned by the delegate method.
+ return info;
+ }
+
+ // The delegate iterator is finished, so forget it and continue with
+ // the outer generator.
+ context.delegate = null;
+ return ContinueSentinel;
+ }
+
+ // Define Generator.prototype.{next,throw,return} in terms of the
+ // unified ._invoke helper method.
+ defineIteratorMethods(Gp);
+
+ Gp[toStringTagSymbol] = "Generator";
+
+ // A Generator should always return itself as the iterator object when the
+ // @@iterator function is called on it. Some browsers' implementations of the
+ // iterator prototype chain incorrectly implement this, causing the Generator
+ // object to not be returned from this call. This ensures that doesn't happen.
+ // See https://github.com/facebook/regenerator/issues/274 for more details.
+ Gp[iteratorSymbol] = function() {
+ return this;
+ };
+
+ Gp.toString = function() {
+ return "[object Generator]";
+ };
+
+ function pushTryEntry(locs) {
+ var entry = { tryLoc: locs[0] };
+
+ if (1 in locs) {
+ entry.catchLoc = locs[1];
+ }
+
+ if (2 in locs) {
+ entry.finallyLoc = locs[2];
+ entry.afterLoc = locs[3];
+ }
+
+ this.tryEntries.push(entry);
+ }
+
+ function resetTryEntry(entry) {
+ var record = entry.completion || {};
+ record.type = "normal";
+ delete record.arg;
+ entry.completion = record;
+ }
+
+ function Context(tryLocsList) {
+ // The root entry object (effectively a try statement without a catch
+ // or a finally block) gives us a place to store values thrown from
+ // locations where there is no enclosing try statement.
+ this.tryEntries = [{ tryLoc: "root" }];
+ tryLocsList.forEach(pushTryEntry, this);
+ this.reset(true);
+ }
+
+ runtime.keys = function(object) {
+ var keys = [];
+ for (var key in object) {
+ keys.push(key);
+ }
+ keys.reverse();
+
+ // Rather than returning an object with a next method, we keep
+ // things simple and return the next function itself.
+ return function next() {
+ while (keys.length) {
+ var key = keys.pop();
+ if (key in object) {
+ next.value = key;
+ next.done = false;
+ return next;
+ }
+ }
+
+ // To avoid creating an additional object, we just hang the .value
+ // and .done properties off the next function object itself. This
+ // also ensures that the minifier will not anonymize the function.
+ next.done = true;
+ return next;
+ };
+ };
+
+ function values(iterable) {
+ if (iterable) {
+ var iteratorMethod = iterable[iteratorSymbol];
+ if (iteratorMethod) {
+ return iteratorMethod.call(iterable);
+ }
+
+ if (typeof iterable.next === "function") {
+ return iterable;
+ }
+
+ if (!isNaN(iterable.length)) {
+ var i = -1, next = function next() {
+ while (++i < iterable.length) {
+ if (hasOwn.call(iterable, i)) {
+ next.value = iterable[i];
+ next.done = false;
+ return next;
+ }
+ }
+
+ next.value = undefined;
+ next.done = true;
+
+ return next;
+ };
+
+ return next.next = next;
+ }
+ }
+
+ // Return an iterator with no values.
+ return { next: doneResult };
+ }
+ runtime.values = values;
+
+ function doneResult() {
+ return { value: undefined, done: true };
+ }
+
+ Context.prototype = {
+ constructor: Context,
+
+ reset: function(skipTempReset) {
+ this.prev = 0;
+ this.next = 0;
+ // Resetting context._sent for legacy support of Babel's
+ // function.sent implementation.
+ this.sent = this._sent = undefined;
+ this.done = false;
+ this.delegate = null;
+
+ this.method = "next";
+ this.arg = undefined;
+
+ this.tryEntries.forEach(resetTryEntry);
+
+ if (!skipTempReset) {
+ for (var name in this) {
+ // Not sure about the optimal order of these conditions:
+ if (name.charAt(0) === "t" &&
+ hasOwn.call(this, name) &&
+ !isNaN(+name.slice(1))) {
+ this[name] = undefined;
+ }
+ }
+ }
+ },
+
+ stop: function() {
+ this.done = true;
+
+ var rootEntry = this.tryEntries[0];
+ var rootRecord = rootEntry.completion;
+ if (rootRecord.type === "throw") {
+ throw rootRecord.arg;
+ }
+
+ return this.rval;
+ },
+
+ dispatchException: function(exception) {
+ if (this.done) {
+ throw exception;
+ }
+
+ var context = this;
+ function handle(loc, caught) {
+ record.type = "throw";
+ record.arg = exception;
+ context.next = loc;
+
+ if (caught) {
+ // If the dispatched exception was caught by a catch block,
+ // then let that catch block handle the exception normally.
+ context.method = "next";
+ context.arg = undefined;
+ }
+
+ return !! caught;
+ }
+
+ for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+ var entry = this.tryEntries[i];
+ var record = entry.completion;
+
+ if (entry.tryLoc === "root") {
+ // Exception thrown outside of any try block that could handle
+ // it, so set the completion value of the entire function to
+ // throw the exception.
+ return handle("end");
+ }
+
+ if (entry.tryLoc <= this.prev) {
+ var hasCatch = hasOwn.call(entry, "catchLoc");
+ var hasFinally = hasOwn.call(entry, "finallyLoc");
+
+ if (hasCatch && hasFinally) {
+ if (this.prev < entry.catchLoc) {
+ return handle(entry.catchLoc, true);
+ } else if (this.prev < entry.finallyLoc) {
+ return handle(entry.finallyLoc);
+ }
+
+ } else if (hasCatch) {
+ if (this.prev < entry.catchLoc) {
+ return handle(entry.catchLoc, true);
+ }
+
+ } else if (hasFinally) {
+ if (this.prev < entry.finallyLoc) {
+ return handle(entry.finallyLoc);
+ }
+
+ } else {
+ throw new Error("try statement without catch or finally");
+ }
+ }
+ }
+ },
+
+ abrupt: function(type, arg) {
+ for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+ var entry = this.tryEntries[i];
+ if (entry.tryLoc <= this.prev &&
+ hasOwn.call(entry, "finallyLoc") &&
+ this.prev < entry.finallyLoc) {
+ var finallyEntry = entry;
+ break;
+ }
+ }
+
+ if (finallyEntry &&
+ (type === "break" ||
+ type === "continue") &&
+ finallyEntry.tryLoc <= arg &&
+ arg <= finallyEntry.finallyLoc) {
+ // Ignore the finally entry if control is not jumping to a
+ // location outside the try/catch block.
+ finallyEntry = null;
+ }
+
+ var record = finallyEntry ? finallyEntry.completion : {};
+ record.type = type;
+ record.arg = arg;
+
+ if (finallyEntry) {
+ this.method = "next";
+ this.next = finallyEntry.finallyLoc;
+ return ContinueSentinel;
+ }
+
+ return this.complete(record);
+ },
+
+ complete: function(record, afterLoc) {
+ if (record.type === "throw") {
+ throw record.arg;
+ }
+
+ if (record.type === "break" ||
+ record.type === "continue") {
+ this.next = record.arg;
+ } else if (record.type === "return") {
+ this.rval = this.arg = record.arg;
+ this.method = "return";
+ this.next = "end";
+ } else if (record.type === "normal" && afterLoc) {
+ this.next = afterLoc;
+ }
+
+ return ContinueSentinel;
+ },
+
+ finish: function(finallyLoc) {
+ for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+ var entry = this.tryEntries[i];
+ if (entry.finallyLoc === finallyLoc) {
+ this.complete(entry.completion, entry.afterLoc);
+ resetTryEntry(entry);
+ return ContinueSentinel;
+ }
+ }
+ },
+
+ "catch": function(tryLoc) {
+ for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+ var entry = this.tryEntries[i];
+ if (entry.tryLoc === tryLoc) {
+ var record = entry.completion;
+ if (record.type === "throw") {
+ var thrown = record.arg;
+ resetTryEntry(entry);
+ }
+ return thrown;
+ }
+ }
+
+ // The context.catch method must only be called with a location
+ // argument that corresponds to a known catch block.
+ throw new Error("illegal catch attempt");
+ },
+
+ delegateYield: function(iterable, resultName, nextLoc) {
+ this.delegate = {
+ iterator: values(iterable),
+ resultName: resultName,
+ nextLoc: nextLoc
+ };
+
+ if (this.method === "next") {
+ // Deliberately forget the last sent value so that we don't
+ // accidentally pass it on to the delegate.
+ this.arg = undefined;
+ }
+
+ return ContinueSentinel;
+ }
+ };
+})(
+ // In sloppy mode, unbound `this` refers to the global object, fallback to
+ // Function constructor if we're in global strict mode. That is sadly a form
+ // of indirect eval which violates Content Security Policy.
+ (function() {
+ return this || (typeof self === "object" && self);
+ })() || Function("return this")()
+);
+
+var _marked =
+/*#__PURE__*/
+regeneratorRuntime.mark(helloWorldGenerator);
+
+function helloWorldGenerator() {
+ return regeneratorRuntime.wrap(function helloWorldGenerator$(_context) {
+ while (1) {
+ switch (_context.prev = _context.next) {
+ case 0:
+ _context.next = 2;
+ return 'hello';
+
+ case 2:
+ _context.next = 4;
+ return 'world';
+
+ case 4:
+ return _context.abrupt("return", 'ending');
+
+ case 5:
+ case "end":
+ return _context.stop();
+ }
+ }
+ }, _marked, this);
+}
+
+var hw = helloWorldGenerator();
+console.log(hw.next()); // {value: "hello", done: false}
+
+console.log(hw.next()); // {value: "world", done: false}
+
+console.log(hw.next()); // {value: "ending", done: true}
+
+console.log(hw.next()); // {value: undefined, done: true}
diff --git a/demos/ES6/module/ES6/index.html b/demos/ES6/module/ES6/index.html
new file mode 100755
index 00000000..83a1ed04
--- /dev/null
+++ b/demos/ES6/module/ES6/index.html
@@ -0,0 +1,10 @@
+
+
+
+ ES6
+
+
+
Content
+
+
+
\ No newline at end of file
diff --git a/demos/ES6/module/ES6/vender/add.js b/demos/ES6/module/ES6/vender/add.js
new file mode 100755
index 00000000..06736834
--- /dev/null
+++ b/demos/ES6/module/ES6/vender/add.js
@@ -0,0 +1,7 @@
+console.log('加载了 add 模块')
+
+var add = function(x, y) {
+ return x + y;
+};
+
+export {add}
\ No newline at end of file
diff --git a/demos/ES6/module/ES6/vender/main.js b/demos/ES6/module/ES6/vender/main.js
new file mode 100755
index 00000000..b4674732
--- /dev/null
+++ b/demos/ES6/module/ES6/vender/main.js
@@ -0,0 +1,5 @@
+import {add} from './add.js';
+console.log(add(1, 1))
+
+import {square} from './square.js';
+console.log(square(3))
\ No newline at end of file
diff --git a/demos/ES6/module/ES6/vender/multiply.js b/demos/ES6/module/ES6/vender/multiply.js
new file mode 100755
index 00000000..7c47104d
--- /dev/null
+++ b/demos/ES6/module/ES6/vender/multiply.js
@@ -0,0 +1,7 @@
+console.log('加载了 multiply 模块')
+
+var multiply = function(x, y) {
+ return x * y;
+};
+
+export {multiply}
\ No newline at end of file
diff --git a/demos/ES6/module/ES6/vender/square.js b/demos/ES6/module/ES6/vender/square.js
new file mode 100755
index 00000000..64acf468
--- /dev/null
+++ b/demos/ES6/module/ES6/vender/square.js
@@ -0,0 +1,9 @@
+console.log('加载了 square 模块')
+
+import {multiply} from './multiply.js';
+
+var square = function(num) {
+ return multiply(num, num);
+};
+
+export {square}
\ No newline at end of file
diff --git a/demos/ES6/module/commonJS/add.js b/demos/ES6/module/commonJS/add.js
new file mode 100755
index 00000000..9adc626f
--- /dev/null
+++ b/demos/ES6/module/commonJS/add.js
@@ -0,0 +1,7 @@
+console.log('加载了 add 模块')
+
+var add = function(x, y) {
+ return x + y;
+};
+
+module.exports.add = add;
\ No newline at end of file
diff --git a/demos/ES6/module/commonJS/main.js b/demos/ES6/module/commonJS/main.js
new file mode 100755
index 00000000..3dfc6e29
--- /dev/null
+++ b/demos/ES6/module/commonJS/main.js
@@ -0,0 +1,5 @@
+var add = require('./add.js');
+console.log(add.add(1, 1))
+
+var square = require('./square.js');
+console.log(square.square(3))
\ No newline at end of file
diff --git a/demos/ES6/module/commonJS/multiply.js b/demos/ES6/module/commonJS/multiply.js
new file mode 100755
index 00000000..661ae279
--- /dev/null
+++ b/demos/ES6/module/commonJS/multiply.js
@@ -0,0 +1,7 @@
+console.log('加载了 multiply 模块')
+
+var multiply = function(x, y) {
+ return x * y;
+};
+
+module.exports.multiply = multiply;
\ No newline at end of file
diff --git a/demos/ES6/module/commonJS/square.js b/demos/ES6/module/commonJS/square.js
new file mode 100755
index 00000000..2b4f77d8
--- /dev/null
+++ b/demos/ES6/module/commonJS/square.js
@@ -0,0 +1,10 @@
+console.log('加载了 square 模块')
+
+var multiply = require('./multiply.js');
+
+
+var square = function(num) {
+ return multiply.multiply(num, num);
+};
+
+module.exports.square = square;
\ No newline at end of file
diff --git a/demos/ES6/module/requirejs/index.html b/demos/ES6/module/requirejs/index.html
new file mode 100755
index 00000000..e2eceb85
--- /dev/null
+++ b/demos/ES6/module/requirejs/index.html
@@ -0,0 +1,10 @@
+
+
+
+ require.js
+
+
+
Content
+
+
+
\ No newline at end of file
diff --git a/demos/ES6/module/requirejs/vender/add.js b/demos/ES6/module/requirejs/vender/add.js
new file mode 100755
index 00000000..59041607
--- /dev/null
+++ b/demos/ES6/module/requirejs/vender/add.js
@@ -0,0 +1,12 @@
+define(function() {
+
+ console.log('加载了 add 模块')
+
+ var add = function(x, y) {
+ return x + y;
+ };
+
+ return {
+ add: add
+ };
+});
\ No newline at end of file
diff --git a/demos/ES6/module/requirejs/vender/main.js b/demos/ES6/module/requirejs/vender/main.js
new file mode 100755
index 00000000..c051f59e
--- /dev/null
+++ b/demos/ES6/module/requirejs/vender/main.js
@@ -0,0 +1,4 @@
+require(['./add', './square'], function(addModule, squareModule) {
+ console.log(addModule.add(1, 1))
+ console.log(squareModule.square(3))
+});
\ No newline at end of file
diff --git a/demos/ES6/module/requirejs/vender/multiply.js b/demos/ES6/module/requirejs/vender/multiply.js
new file mode 100755
index 00000000..f0f68855
--- /dev/null
+++ b/demos/ES6/module/requirejs/vender/multiply.js
@@ -0,0 +1,12 @@
+define(function() {
+
+ console.log('加载了 multiply 模块')
+
+ var multiply = function(x, y) {
+ return x * y;
+ };
+
+ return {
+ multiply: multiply
+ };
+});
\ No newline at end of file
diff --git a/demos/ES6/module/requirejs/vender/require.js b/demos/ES6/module/requirejs/vender/require.js
new file mode 100755
index 00000000..051e284b
--- /dev/null
+++ b/demos/ES6/module/requirejs/vender/require.js
@@ -0,0 +1,2145 @@
+/** vim: et:ts=4:sw=4:sts=4
+ * @license RequireJS 2.3.5 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
+ */
+//Not using strict: uneven strict support in browsers, #392, and causes
+//problems with requirejs.exec()/transpiler plugins that may not be strict.
+/*jslint regexp: true, nomen: true, sloppy: true */
+/*global window, navigator, document, importScripts, setTimeout, opera */
+
+var requirejs, require, define;
+(function (global, setTimeout) {
+ var req, s, head, baseElement, dataMain, src,
+ interactiveScript, currentlyAddingScript, mainScript, subPath,
+ version = '2.3.5',
+ commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,
+ cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+ jsSuffixRegExp = /\.js$/,
+ currDirRegExp = /^\.\//,
+ op = Object.prototype,
+ ostring = op.toString,
+ hasOwn = op.hasOwnProperty,
+ isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
+ isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
+ //PS3 indicates loaded and complete, but need to wait for complete
+ //specifically. Sequence is 'loading', 'loaded', execution,
+ // then 'complete'. The UA check is unfortunate, but not sure how
+ //to feature test w/o causing perf issues.
+ readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
+ /^complete$/ : /^(complete|loaded)$/,
+ defContextName = '_',
+ //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
+ isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
+ contexts = {},
+ cfg = {},
+ globalDefQueue = [],
+ useInteractive = false;
+
+ //Could match something like ')//comment', do not lose the prefix to comment.
+ function commentReplace(match, singlePrefix) {
+ return singlePrefix || '';
+ }
+
+ function isFunction(it) {
+ return ostring.call(it) === '[object Function]';
+ }
+
+ function isArray(it) {
+ return ostring.call(it) === '[object Array]';
+ }
+
+ /**
+ * Helper function for iterating over an array. If the func returns
+ * a true value, it will break out of the loop.
+ */
+ function each(ary, func) {
+ if (ary) {
+ var i;
+ for (i = 0; i < ary.length; i += 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function for iterating over an array backwards. If the func
+ * returns a true value, it will break out of the loop.
+ */
+ function eachReverse(ary, func) {
+ if (ary) {
+ var i;
+ for (i = ary.length - 1; i > -1; i -= 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ function hasProp(obj, prop) {
+ return hasOwn.call(obj, prop);
+ }
+
+ function getOwn(obj, prop) {
+ return hasProp(obj, prop) && obj[prop];
+ }
+
+ /**
+ * Cycles over properties in an object and calls a function for each
+ * property value. If the function returns a truthy value, then the
+ * iteration is stopped.
+ */
+ function eachProp(obj, func) {
+ var prop;
+ for (prop in obj) {
+ if (hasProp(obj, prop)) {
+ if (func(obj[prop], prop)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple function to mix in properties from source into target,
+ * but only if target does not already have a property of the same name.
+ */
+ function mixin(target, source, force, deepStringMixin) {
+ if (source) {
+ eachProp(source, function (value, prop) {
+ if (force || !hasProp(target, prop)) {
+ if (deepStringMixin && typeof value === 'object' && value &&
+ !isArray(value) && !isFunction(value) &&
+ !(value instanceof RegExp)) {
+
+ if (!target[prop]) {
+ target[prop] = {};
+ }
+ mixin(target[prop], value, force, deepStringMixin);
+ } else {
+ target[prop] = value;
+ }
+ }
+ });
+ }
+ return target;
+ }
+
+ //Similar to Function.prototype.bind, but the 'this' object is specified
+ //first, since it is easier to read/figure out what 'this' will be.
+ function bind(obj, fn) {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+ }
+
+ function scripts() {
+ return document.getElementsByTagName('script');
+ }
+
+ function defaultOnError(err) {
+ throw err;
+ }
+
+ //Allow getting a global that is expressed in
+ //dot notation, like 'a.b.c'.
+ function getGlobal(value) {
+ if (!value) {
+ return value;
+ }
+ var g = global;
+ each(value.split('.'), function (part) {
+ g = g[part];
+ });
+ return g;
+ }
+
+ /**
+ * Constructs an error with a pointer to an URL with more information.
+ * @param {String} id the error ID that maps to an ID on a web page.
+ * @param {String} message human readable error.
+ * @param {Error} [err] the original error, if there is one.
+ *
+ * @returns {Error}
+ */
+ function makeError(id, msg, err, requireModules) {
+ var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
+ e.requireType = id;
+ e.requireModules = requireModules;
+ if (err) {
+ e.originalError = err;
+ }
+ return e;
+ }
+
+ if (typeof define !== 'undefined') {
+ //If a define is already in play via another AMD loader,
+ //do not overwrite.
+ return;
+ }
+
+ if (typeof requirejs !== 'undefined') {
+ if (isFunction(requirejs)) {
+ //Do not overwrite an existing requirejs instance.
+ return;
+ }
+ cfg = requirejs;
+ requirejs = undefined;
+ }
+
+ //Allow for a require config object
+ if (typeof require !== 'undefined' && !isFunction(require)) {
+ //assume it is a config object.
+ cfg = require;
+ require = undefined;
+ }
+
+ function newContext(contextName) {
+ var inCheckLoaded, Module, context, handlers,
+ checkLoadedTimeoutId,
+ config = {
+ //Defaults. Do not set a default for map
+ //config to speed up normalize(), which
+ //will run faster if there is no default.
+ waitSeconds: 7,
+ baseUrl: './',
+ paths: {},
+ bundles: {},
+ pkgs: {},
+ shim: {},
+ config: {}
+ },
+ registry = {},
+ //registry of just enabled modules, to speed
+ //cycle breaking code when lots of modules
+ //are registered, but not activated.
+ enabledRegistry = {},
+ undefEvents = {},
+ defQueue = [],
+ defined = {},
+ urlFetched = {},
+ bundlesMap = {},
+ requireCounter = 1,
+ unnormalizedCounter = 1;
+
+ /**
+ * Trims the . and .. from an array of path segments.
+ * It will keep a leading path segment if a .. will become
+ * the first path segment, to help with module name lookups,
+ * which act like paths, but can be remapped. But the end result,
+ * all paths that use this function should look normalized.
+ * NOTE: this method MODIFIES the input array.
+ * @param {Array} ary the array of path segments.
+ */
+ function trimDots(ary) {
+ var i, part;
+ for (i = 0; i < ary.length; i++) {
+ part = ary[i];
+ if (part === '.') {
+ ary.splice(i, 1);
+ i -= 1;
+ } else if (part === '..') {
+ // If at the start, or previous value is still ..,
+ // keep them so that when converted to a path it may
+ // still work when converted to a path, even though
+ // as an ID it is less than ideal. In larger point
+ // releases, may be better to just kick out an error.
+ if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
+ continue;
+ } else if (i > 0) {
+ ary.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @param {Boolean} applyMap apply the map config to the value. Should
+ * only be done if this normalization is for a dependency ID.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName, applyMap) {
+ var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
+ foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
+ baseParts = (baseName && baseName.split('/')),
+ map = config.map,
+ starMap = map && map['*'];
+
+ //Adjust any relative paths.
+ if (name) {
+ name = name.split('/');
+ lastIndex = name.length - 1;
+
+ // If wanting node ID compatibility, strip .js from end
+ // of IDs. Have to do this here, and not in nameToUrl
+ // because node allows either .js or non .js to map
+ // to same file.
+ if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+ name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+ }
+
+ // Starts with a '.' so need the baseName
+ if (name[0].charAt(0) === '.' && baseParts) {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that 'directory' and not name of the baseName's
+ //module. For instance, baseName of 'one/two/three', maps to
+ //'one/two/three.js', but we want the directory, 'one/two' for
+ //this normalization.
+ normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+ name = normalizedBaseParts.concat(name);
+ }
+
+ trimDots(name);
+ name = name.join('/');
+ }
+
+ //Apply map config if available.
+ if (applyMap && map && (baseParts || starMap)) {
+ nameParts = name.split('/');
+
+ outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join('/');
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = getOwn(mapValue, nameSegment);
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ foundI = i;
+ break outerLoop;
+ }
+ }
+ }
+ }
+
+ //Check for a star map match, but just hold on to it,
+ //if there is a shorter segment match later in a matching
+ //config, then favor over this star map.
+ if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
+ foundStarMap = getOwn(starMap, nameSegment);
+ starI = i;
+ }
+ }
+
+ if (!foundMap && foundStarMap) {
+ foundMap = foundStarMap;
+ foundI = starI;
+ }
+
+ if (foundMap) {
+ nameParts.splice(0, foundI, foundMap);
+ name = nameParts.join('/');
+ }
+ }
+
+ // If the name points to a package's name, use
+ // the package main instead.
+ pkgMain = getOwn(config.pkgs, name);
+
+ return pkgMain ? pkgMain : name;
+ }
+
+ function removeScript(name) {
+ if (isBrowser) {
+ each(scripts(), function (scriptNode) {
+ if (scriptNode.getAttribute('data-requiremodule') === name &&
+ scriptNode.getAttribute('data-requirecontext') === context.contextName) {
+ scriptNode.parentNode.removeChild(scriptNode);
+ return true;
+ }
+ });
+ }
+ }
+
+ function hasPathFallback(id) {
+ var pathConfig = getOwn(config.paths, id);
+ if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
+ //Pop off the first array value, since it failed, and
+ //retry
+ pathConfig.shift();
+ context.require.undef(id);
+
+ //Custom require that does not do map translation, since
+ //ID is "absolute", already mapped/resolved.
+ context.makeRequire(null, {
+ skipMap: true
+ })([id]);
+
+ return true;
+ }
+ }
+
+ //Turns a plugin!resource to [plugin, resource]
+ //with the plugin being undefined if the name
+ //did not have a plugin prefix.
+ function splitPrefix(name) {
+ var prefix,
+ index = name ? name.indexOf('!') : -1;
+ if (index > -1) {
+ prefix = name.substring(0, index);
+ name = name.substring(index + 1, name.length);
+ }
+ return [prefix, name];
+ }
+
+ /**
+ * Creates a module mapping that includes plugin prefix, module
+ * name, and path. If parentModuleMap is provided it will
+ * also normalize the name via require.normalize()
+ *
+ * @param {String} name the module name
+ * @param {String} [parentModuleMap] parent module map
+ * for the module name, used to resolve relative names.
+ * @param {Boolean} isNormalized: is the ID already normalized.
+ * This is true if this call is done for a define() module ID.
+ * @param {Boolean} applyMap: apply the map config to the ID.
+ * Should only be true if this map is for a dependency.
+ *
+ * @returns {Object}
+ */
+ function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
+ var url, pluginModule, suffix, nameParts,
+ prefix = null,
+ parentName = parentModuleMap ? parentModuleMap.name : null,
+ originalName = name,
+ isDefine = true,
+ normalizedName = '';
+
+ //If no name, then it means it is a require call, generate an
+ //internal name.
+ if (!name) {
+ isDefine = false;
+ name = '_@r' + (requireCounter += 1);
+ }
+
+ nameParts = splitPrefix(name);
+ prefix = nameParts[0];
+ name = nameParts[1];
+
+ if (prefix) {
+ prefix = normalize(prefix, parentName, applyMap);
+ pluginModule = getOwn(defined, prefix);
+ }
+
+ //Account for relative paths if there is a base name.
+ if (name) {
+ if (prefix) {
+ if (isNormalized) {
+ normalizedName = name;
+ } else if (pluginModule && pluginModule.normalize) {
+ //Plugin is loaded, use its normalize method.
+ normalizedName = pluginModule.normalize(name, function (name) {
+ return normalize(name, parentName, applyMap);
+ });
+ } else {
+ // If nested plugin references, then do not try to
+ // normalize, as it will not normalize correctly. This
+ // places a restriction on resourceIds, and the longer
+ // term solution is not to normalize until plugins are
+ // loaded and all normalizations to allow for async
+ // loading of a loader plugin. But for now, fixes the
+ // common uses. Details in #1131
+ normalizedName = name.indexOf('!') === -1 ?
+ normalize(name, parentName, applyMap) :
+ name;
+ }
+ } else {
+ //A regular module.
+ normalizedName = normalize(name, parentName, applyMap);
+
+ //Normalized name may be a plugin ID due to map config
+ //application in normalize. The map config values must
+ //already be normalized, so do not need to redo that part.
+ nameParts = splitPrefix(normalizedName);
+ prefix = nameParts[0];
+ normalizedName = nameParts[1];
+ isNormalized = true;
+
+ url = context.nameToUrl(normalizedName);
+ }
+ }
+
+ //If the id is a plugin id that cannot be determined if it needs
+ //normalization, stamp it with a unique ID so two matching relative
+ //ids that may conflict can be separate.
+ suffix = prefix && !pluginModule && !isNormalized ?
+ '_unnormalized' + (unnormalizedCounter += 1) :
+ '';
+
+ return {
+ prefix: prefix,
+ name: normalizedName,
+ parentMap: parentModuleMap,
+ unnormalized: !!suffix,
+ url: url,
+ originalName: originalName,
+ isDefine: isDefine,
+ id: (prefix ?
+ prefix + '!' + normalizedName :
+ normalizedName) + suffix
+ };
+ }
+
+ function getModule(depMap) {
+ var id = depMap.id,
+ mod = getOwn(registry, id);
+
+ if (!mod) {
+ mod = registry[id] = new context.Module(depMap);
+ }
+
+ return mod;
+ }
+
+ function on(depMap, name, fn) {
+ var id = depMap.id,
+ mod = getOwn(registry, id);
+
+ if (hasProp(defined, id) &&
+ (!mod || mod.defineEmitComplete)) {
+ if (name === 'defined') {
+ fn(defined[id]);
+ }
+ } else {
+ mod = getModule(depMap);
+ if (mod.error && name === 'error') {
+ fn(mod.error);
+ } else {
+ mod.on(name, fn);
+ }
+ }
+ }
+
+ function onError(err, errback) {
+ var ids = err.requireModules,
+ notified = false;
+
+ if (errback) {
+ errback(err);
+ } else {
+ each(ids, function (id) {
+ var mod = getOwn(registry, id);
+ if (mod) {
+ //Set error on module, so it skips timeout checks.
+ mod.error = err;
+ if (mod.events.error) {
+ notified = true;
+ mod.emit('error', err);
+ }
+ }
+ });
+
+ if (!notified) {
+ req.onError(err);
+ }
+ }
+ }
+
+ /**
+ * Internal method to transfer globalQueue items to this context's
+ * defQueue.
+ */
+ function takeGlobalQueue() {
+ //Push all the globalDefQueue items into the context's defQueue
+ if (globalDefQueue.length) {
+ each(globalDefQueue, function(queueItem) {
+ var id = queueItem[0];
+ if (typeof id === 'string') {
+ context.defQueueMap[id] = true;
+ }
+ defQueue.push(queueItem);
+ });
+ globalDefQueue = [];
+ }
+ }
+
+ handlers = {
+ 'require': function (mod) {
+ if (mod.require) {
+ return mod.require;
+ } else {
+ return (mod.require = context.makeRequire(mod.map));
+ }
+ },
+ 'exports': function (mod) {
+ mod.usingExports = true;
+ if (mod.map.isDefine) {
+ if (mod.exports) {
+ return (defined[mod.map.id] = mod.exports);
+ } else {
+ return (mod.exports = defined[mod.map.id] = {});
+ }
+ }
+ },
+ 'module': function (mod) {
+ if (mod.module) {
+ return mod.module;
+ } else {
+ return (mod.module = {
+ id: mod.map.id,
+ uri: mod.map.url,
+ config: function () {
+ return getOwn(config.config, mod.map.id) || {};
+ },
+ exports: mod.exports || (mod.exports = {})
+ });
+ }
+ }
+ };
+
+ function cleanRegistry(id) {
+ //Clean up machinery used for waiting modules.
+ delete registry[id];
+ delete enabledRegistry[id];
+ }
+
+ function breakCycle(mod, traced, processed) {
+ var id = mod.map.id;
+
+ if (mod.error) {
+ mod.emit('error', mod.error);
+ } else {
+ traced[id] = true;
+ each(mod.depMaps, function (depMap, i) {
+ var depId = depMap.id,
+ dep = getOwn(registry, depId);
+
+ //Only force things that have not completed
+ //being defined, so still in the registry,
+ //and only if it has not been matched up
+ //in the module already.
+ if (dep && !mod.depMatched[i] && !processed[depId]) {
+ if (getOwn(traced, depId)) {
+ mod.defineDep(i, defined[depId]);
+ mod.check(); //pass false?
+ } else {
+ breakCycle(dep, traced, processed);
+ }
+ }
+ });
+ processed[id] = true;
+ }
+ }
+
+ function checkLoaded() {
+ var err, usingPathFallback,
+ waitInterval = config.waitSeconds * 1000,
+ //It is possible to disable the wait interval by using waitSeconds of 0.
+ expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
+ noLoads = [],
+ reqCalls = [],
+ stillLoading = false,
+ needCycleCheck = true;
+
+ //Do not bother if this call was a result of a cycle break.
+ if (inCheckLoaded) {
+ return;
+ }
+
+ inCheckLoaded = true;
+
+ //Figure out the state of all the modules.
+ eachProp(enabledRegistry, function (mod) {
+ var map = mod.map,
+ modId = map.id;
+
+ //Skip things that are not enabled or in error state.
+ if (!mod.enabled) {
+ return;
+ }
+
+ if (!map.isDefine) {
+ reqCalls.push(mod);
+ }
+
+ if (!mod.error) {
+ //If the module should be executed, and it has not
+ //been inited and time is up, remember it.
+ if (!mod.inited && expired) {
+ if (hasPathFallback(modId)) {
+ usingPathFallback = true;
+ stillLoading = true;
+ } else {
+ noLoads.push(modId);
+ removeScript(modId);
+ }
+ } else if (!mod.inited && mod.fetched && map.isDefine) {
+ stillLoading = true;
+ if (!map.prefix) {
+ //No reason to keep looking for unfinished
+ //loading. If the only stillLoading is a
+ //plugin resource though, keep going,
+ //because it may be that a plugin resource
+ //is waiting on a non-plugin cycle.
+ return (needCycleCheck = false);
+ }
+ }
+ }
+ });
+
+ if (expired && noLoads.length) {
+ //If wait time expired, throw error of unloaded modules.
+ err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
+ err.contextName = context.contextName;
+ return onError(err);
+ }
+
+ //Not expired, check for a cycle.
+ if (needCycleCheck) {
+ each(reqCalls, function (mod) {
+ breakCycle(mod, {}, {});
+ });
+ }
+
+ //If still waiting on loads, and the waiting load is something
+ //other than a plugin resource, or there are still outstanding
+ //scripts, then just try back later.
+ if ((!expired || usingPathFallback) && stillLoading) {
+ //Something is still waiting to load. Wait for it, but only
+ //if a timeout is not already in effect.
+ if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
+ checkLoadedTimeoutId = setTimeout(function () {
+ checkLoadedTimeoutId = 0;
+ checkLoaded();
+ }, 50);
+ }
+ }
+
+ inCheckLoaded = false;
+ }
+
+ Module = function (map) {
+ this.events = getOwn(undefEvents, map.id) || {};
+ this.map = map;
+ this.shim = getOwn(config.shim, map.id);
+ this.depExports = [];
+ this.depMaps = [];
+ this.depMatched = [];
+ this.pluginMaps = {};
+ this.depCount = 0;
+
+ /* this.exports this.factory
+ this.depMaps = [],
+ this.enabled, this.fetched
+ */
+ };
+
+ Module.prototype = {
+ init: function (depMaps, factory, errback, options) {
+ options = options || {};
+
+ //Do not do more inits if already done. Can happen if there
+ //are multiple define calls for the same module. That is not
+ //a normal, common case, but it is also not unexpected.
+ if (this.inited) {
+ return;
+ }
+
+ this.factory = factory;
+
+ if (errback) {
+ //Register for errors on this module.
+ this.on('error', errback);
+ } else if (this.events.error) {
+ //If no errback already, but there are error listeners
+ //on this module, set up an errback to pass to the deps.
+ errback = bind(this, function (err) {
+ this.emit('error', err);
+ });
+ }
+
+ //Do a copy of the dependency array, so that
+ //source inputs are not modified. For example
+ //"shim" deps are passed in here directly, and
+ //doing a direct modification of the depMaps array
+ //would affect that config.
+ this.depMaps = depMaps && depMaps.slice(0);
+
+ this.errback = errback;
+
+ //Indicate this module has be initialized
+ this.inited = true;
+
+ this.ignore = options.ignore;
+
+ //Could have option to init this module in enabled mode,
+ //or could have been previously marked as enabled. However,
+ //the dependencies are not known until init is called. So
+ //if enabled previously, now trigger dependencies as enabled.
+ if (options.enabled || this.enabled) {
+ //Enable this module and dependencies.
+ //Will call this.check()
+ this.enable();
+ } else {
+ this.check();
+ }
+ },
+
+ defineDep: function (i, depExports) {
+ //Because of cycles, defined callback for a given
+ //export can be called more than once.
+ if (!this.depMatched[i]) {
+ this.depMatched[i] = true;
+ this.depCount -= 1;
+ this.depExports[i] = depExports;
+ }
+ },
+
+ fetch: function () {
+ if (this.fetched) {
+ return;
+ }
+ this.fetched = true;
+
+ context.startTime = (new Date()).getTime();
+
+ var map = this.map;
+
+ //If the manager is for a plugin managed resource,
+ //ask the plugin to load it now.
+ if (this.shim) {
+ context.makeRequire(this.map, {
+ enableBuildCallback: true
+ })(this.shim.deps || [], bind(this, function () {
+ return map.prefix ? this.callPlugin() : this.load();
+ }));
+ } else {
+ //Regular dependency.
+ return map.prefix ? this.callPlugin() : this.load();
+ }
+ },
+
+ load: function () {
+ var url = this.map.url;
+
+ //Regular dependency.
+ if (!urlFetched[url]) {
+ urlFetched[url] = true;
+ context.load(this.map.id, url);
+ }
+ },
+
+ /**
+ * Checks if the module is ready to define itself, and if so,
+ * define it.
+ */
+ check: function () {
+ if (!this.enabled || this.enabling) {
+ return;
+ }
+
+ var err, cjsModule,
+ id = this.map.id,
+ depExports = this.depExports,
+ exports = this.exports,
+ factory = this.factory;
+
+ if (!this.inited) {
+ // Only fetch if not already in the defQueue.
+ if (!hasProp(context.defQueueMap, id)) {
+ this.fetch();
+ }
+ } else if (this.error) {
+ this.emit('error', this.error);
+ } else if (!this.defining) {
+ //The factory could trigger another require call
+ //that would result in checking this module to
+ //define itself again. If already in the process
+ //of doing that, skip this work.
+ this.defining = true;
+
+ if (this.depCount < 1 && !this.defined) {
+ if (isFunction(factory)) {
+ //If there is an error listener, favor passing
+ //to that instead of throwing an error. However,
+ //only do it for define()'d modules. require
+ //errbacks should not be called for failures in
+ //their callbacks (#699). However if a global
+ //onError is set, use that.
+ if ((this.events.error && this.map.isDefine) ||
+ req.onError !== defaultOnError) {
+ try {
+ exports = context.execCb(id, factory, depExports, exports);
+ } catch (e) {
+ err = e;
+ }
+ } else {
+ exports = context.execCb(id, factory, depExports, exports);
+ }
+
+ // Favor return value over exports. If node/cjs in play,
+ // then will not have a return value anyway. Favor
+ // module.exports assignment over exports object.
+ if (this.map.isDefine && exports === undefined) {
+ cjsModule = this.module;
+ if (cjsModule) {
+ exports = cjsModule.exports;
+ } else if (this.usingExports) {
+ //exports already set the defined value.
+ exports = this.exports;
+ }
+ }
+
+ if (err) {
+ err.requireMap = this.map;
+ err.requireModules = this.map.isDefine ? [this.map.id] : null;
+ err.requireType = this.map.isDefine ? 'define' : 'require';
+ return onError((this.error = err));
+ }
+
+ } else {
+ //Just a literal value
+ exports = factory;
+ }
+
+ this.exports = exports;
+
+ if (this.map.isDefine && !this.ignore) {
+ defined[id] = exports;
+
+ if (req.onResourceLoad) {
+ var resLoadMaps = [];
+ each(this.depMaps, function (depMap) {
+ resLoadMaps.push(depMap.normalizedMap || depMap);
+ });
+ req.onResourceLoad(context, this.map, resLoadMaps);
+ }
+ }
+
+ //Clean up
+ cleanRegistry(id);
+
+ this.defined = true;
+ }
+
+ //Finished the define stage. Allow calling check again
+ //to allow define notifications below in the case of a
+ //cycle.
+ this.defining = false;
+
+ if (this.defined && !this.defineEmitted) {
+ this.defineEmitted = true;
+ this.emit('defined', this.exports);
+ this.defineEmitComplete = true;
+ }
+
+ }
+ },
+
+ callPlugin: function () {
+ var map = this.map,
+ id = map.id,
+ //Map already normalized the prefix.
+ pluginMap = makeModuleMap(map.prefix);
+
+ //Mark this as a dependency for this plugin, so it
+ //can be traced for cycles.
+ this.depMaps.push(pluginMap);
+
+ on(pluginMap, 'defined', bind(this, function (plugin) {
+ var load, normalizedMap, normalizedMod,
+ bundleId = getOwn(bundlesMap, this.map.id),
+ name = this.map.name,
+ parentName = this.map.parentMap ? this.map.parentMap.name : null,
+ localRequire = context.makeRequire(map.parentMap, {
+ enableBuildCallback: true
+ });
+
+ //If current map is not normalized, wait for that
+ //normalized name to load instead of continuing.
+ if (this.map.unnormalized) {
+ //Normalize the ID if the plugin allows it.
+ if (plugin.normalize) {
+ name = plugin.normalize(name, function (name) {
+ return normalize(name, parentName, true);
+ }) || '';
+ }
+
+ //prefix and name should already be normalized, no need
+ //for applying map config again either.
+ normalizedMap = makeModuleMap(map.prefix + '!' + name,
+ this.map.parentMap,
+ true);
+ on(normalizedMap,
+ 'defined', bind(this, function (value) {
+ this.map.normalizedMap = normalizedMap;
+ this.init([], function () { return value; }, null, {
+ enabled: true,
+ ignore: true
+ });
+ }));
+
+ normalizedMod = getOwn(registry, normalizedMap.id);
+ if (normalizedMod) {
+ //Mark this as a dependency for this plugin, so it
+ //can be traced for cycles.
+ this.depMaps.push(normalizedMap);
+
+ if (this.events.error) {
+ normalizedMod.on('error', bind(this, function (err) {
+ this.emit('error', err);
+ }));
+ }
+ normalizedMod.enable();
+ }
+
+ return;
+ }
+
+ //If a paths config, then just load that file instead to
+ //resolve the plugin, as it is built into that paths layer.
+ if (bundleId) {
+ this.map.url = context.nameToUrl(bundleId);
+ this.load();
+ return;
+ }
+
+ load = bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true
+ });
+ });
+
+ load.error = bind(this, function (err) {
+ this.inited = true;
+ this.error = err;
+ err.requireModules = [id];
+
+ //Remove temp unnormalized modules for this module,
+ //since they will never be resolved otherwise now.
+ eachProp(registry, function (mod) {
+ if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
+ cleanRegistry(mod.map.id);
+ }
+ });
+
+ onError(err);
+ });
+
+ //Allow plugins to load other code without having to know the
+ //context or how to 'complete' the load.
+ load.fromText = bind(this, function (text, textAlt) {
+ /*jslint evil: true */
+ var moduleName = map.name,
+ moduleMap = makeModuleMap(moduleName),
+ hasInteractive = useInteractive;
+
+ //As of 2.1.0, support just passing the text, to reinforce
+ //fromText only being called once per resource. Still
+ //support old style of passing moduleName but discard
+ //that moduleName in favor of the internal ref.
+ if (textAlt) {
+ text = textAlt;
+ }
+
+ //Turn off interactive script matching for IE for any define
+ //calls in the text, then turn it back on at the end.
+ if (hasInteractive) {
+ useInteractive = false;
+ }
+
+ //Prime the system by creating a module instance for
+ //it.
+ getModule(moduleMap);
+
+ //Transfer any config to this other module.
+ if (hasProp(config.config, id)) {
+ config.config[moduleName] = config.config[id];
+ }
+
+ try {
+ req.exec(text);
+ } catch (e) {
+ return onError(makeError('fromtexteval',
+ 'fromText eval for ' + id +
+ ' failed: ' + e,
+ e,
+ [id]));
+ }
+
+ if (hasInteractive) {
+ useInteractive = true;
+ }
+
+ //Mark this as a dependency for the plugin
+ //resource
+ this.depMaps.push(moduleMap);
+
+ //Support anonymous modules.
+ context.completeLoad(moduleName);
+
+ //Bind the value of that module to the value for this
+ //resource ID.
+ localRequire([moduleName], load);
+ });
+
+ //Use parentName here since the plugin's name is not reliable,
+ //could be some weird string with no path that actually wants to
+ //reference the parentName's path.
+ plugin.load(map.name, localRequire, load, config);
+ }));
+
+ context.enable(pluginMap, this);
+ this.pluginMaps[pluginMap.id] = pluginMap;
+ },
+
+ enable: function () {
+ enabledRegistry[this.map.id] = this;
+ this.enabled = true;
+
+ //Set flag mentioning that the module is enabling,
+ //so that immediate calls to the defined callbacks
+ //for dependencies do not trigger inadvertent load
+ //with the depCount still being zero.
+ this.enabling = true;
+
+ //Enable each dependency
+ each(this.depMaps, bind(this, function (depMap, i) {
+ var id, mod, handler;
+
+ if (typeof depMap === 'string') {
+ //Dependency needs to be converted to a depMap
+ //and wired up to this module.
+ depMap = makeModuleMap(depMap,
+ (this.map.isDefine ? this.map : this.map.parentMap),
+ false,
+ !this.skipMap);
+ this.depMaps[i] = depMap;
+
+ handler = getOwn(handlers, depMap.id);
+
+ if (handler) {
+ this.depExports[i] = handler(this);
+ return;
+ }
+
+ this.depCount += 1;
+
+ on(depMap, 'defined', bind(this, function (depExports) {
+ if (this.undefed) {
+ return;
+ }
+ this.defineDep(i, depExports);
+ this.check();
+ }));
+
+ if (this.errback) {
+ on(depMap, 'error', bind(this, this.errback));
+ } else if (this.events.error) {
+ // No direct errback on this module, but something
+ // else is listening for errors, so be sure to
+ // propagate the error correctly.
+ on(depMap, 'error', bind(this, function(err) {
+ this.emit('error', err);
+ }));
+ }
+ }
+
+ id = depMap.id;
+ mod = registry[id];
+
+ //Skip special modules like 'require', 'exports', 'module'
+ //Also, don't call enable if it is already enabled,
+ //important in circular dependency cases.
+ if (!hasProp(handlers, id) && mod && !mod.enabled) {
+ context.enable(depMap, this);
+ }
+ }));
+
+ //Enable each plugin that is used in
+ //a dependency
+ eachProp(this.pluginMaps, bind(this, function (pluginMap) {
+ var mod = getOwn(registry, pluginMap.id);
+ if (mod && !mod.enabled) {
+ context.enable(pluginMap, this);
+ }
+ }));
+
+ this.enabling = false;
+
+ this.check();
+ },
+
+ on: function (name, cb) {
+ var cbs = this.events[name];
+ if (!cbs) {
+ cbs = this.events[name] = [];
+ }
+ cbs.push(cb);
+ },
+
+ emit: function (name, evt) {
+ each(this.events[name], function (cb) {
+ cb(evt);
+ });
+ if (name === 'error') {
+ //Now that the error handler was triggered, remove
+ //the listeners, since this broken Module instance
+ //can stay around for a while in the registry.
+ delete this.events[name];
+ }
+ }
+ };
+
+ function callGetModule(args) {
+ //Skip modules already defined.
+ if (!hasProp(defined, args[0])) {
+ getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
+ }
+ }
+
+ function removeListener(node, func, name, ieName) {
+ //Favor detachEvent because of IE9
+ //issue, see attachEvent/addEventListener comment elsewhere
+ //in this file.
+ if (node.detachEvent && !isOpera) {
+ //Probably IE. If not it will throw an error, which will be
+ //useful to know.
+ if (ieName) {
+ node.detachEvent(ieName, func);
+ }
+ } else {
+ node.removeEventListener(name, func, false);
+ }
+ }
+
+ /**
+ * Given an event from a script node, get the requirejs info from it,
+ * and then removes the event listeners on the node.
+ * @param {Event} evt
+ * @returns {Object}
+ */
+ function getScriptData(evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ var node = evt.currentTarget || evt.srcElement;
+
+ //Remove the listeners once here.
+ removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
+ removeListener(node, context.onScriptError, 'error');
+
+ return {
+ node: node,
+ id: node && node.getAttribute('data-requiremodule')
+ };
+ }
+
+ function intakeDefines() {
+ var args;
+
+ //Any defined modules in the global queue, intake them now.
+ takeGlobalQueue();
+
+ //Make sure any remaining defQueue items get properly processed.
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' +
+ args[args.length - 1]));
+ } else {
+ //args are id, deps, factory. Should be normalized by the
+ //define() function.
+ callGetModule(args);
+ }
+ }
+ context.defQueueMap = {};
+ }
+
+ context = {
+ config: config,
+ contextName: contextName,
+ registry: registry,
+ defined: defined,
+ urlFetched: urlFetched,
+ defQueue: defQueue,
+ defQueueMap: {},
+ Module: Module,
+ makeModuleMap: makeModuleMap,
+ nextTick: req.nextTick,
+ onError: onError,
+
+ /**
+ * Set a configuration for the context.
+ * @param {Object} cfg config object to integrate.
+ */
+ configure: function (cfg) {
+ //Make sure the baseUrl ends in a slash.
+ if (cfg.baseUrl) {
+ if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+ cfg.baseUrl += '/';
+ }
+ }
+
+ // Convert old style urlArgs string to a function.
+ if (typeof cfg.urlArgs === 'string') {
+ var urlArgs = cfg.urlArgs;
+ cfg.urlArgs = function(id, url) {
+ return (url.indexOf('?') === -1 ? '?' : '&') + urlArgs;
+ };
+ }
+
+ //Save off the paths since they require special processing,
+ //they are additive.
+ var shim = config.shim,
+ objs = {
+ paths: true,
+ bundles: true,
+ config: true,
+ map: true
+ };
+
+ eachProp(cfg, function (value, prop) {
+ if (objs[prop]) {
+ if (!config[prop]) {
+ config[prop] = {};
+ }
+ mixin(config[prop], value, true, true);
+ } else {
+ config[prop] = value;
+ }
+ });
+
+ //Reverse map the bundles
+ if (cfg.bundles) {
+ eachProp(cfg.bundles, function (value, prop) {
+ each(value, function (v) {
+ if (v !== prop) {
+ bundlesMap[v] = prop;
+ }
+ });
+ });
+ }
+
+ //Merge shim
+ if (cfg.shim) {
+ eachProp(cfg.shim, function (value, id) {
+ //Normalize the structure
+ if (isArray(value)) {
+ value = {
+ deps: value
+ };
+ }
+ if ((value.exports || value.init) && !value.exportsFn) {
+ value.exportsFn = context.makeShimExports(value);
+ }
+ shim[id] = value;
+ });
+ config.shim = shim;
+ }
+
+ //Adjust packages if necessary.
+ if (cfg.packages) {
+ each(cfg.packages, function (pkgObj) {
+ var location, name;
+
+ pkgObj = typeof pkgObj === 'string' ? {name: pkgObj} : pkgObj;
+
+ name = pkgObj.name;
+ location = pkgObj.location;
+ if (location) {
+ config.paths[name] = pkgObj.location;
+ }
+
+ //Save pointer to main module ID for pkg name.
+ //Remove leading dot in main, so main paths are normalized,
+ //and remove any trailing .js, since different package
+ //envs have different conventions: some use a module name,
+ //some use a file name.
+ config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
+ .replace(currDirRegExp, '')
+ .replace(jsSuffixRegExp, '');
+ });
+ }
+
+ //If there are any "waiting to execute" modules in the registry,
+ //update the maps for them, since their info, like URLs to load,
+ //may have changed.
+ eachProp(registry, function (mod, id) {
+ //If module already has init called, since it is too
+ //late to modify them, and ignore unnormalized ones
+ //since they are transient.
+ if (!mod.inited && !mod.map.unnormalized) {
+ mod.map = makeModuleMap(id, null, true);
+ }
+ });
+
+ //If a deps array or a config callback is specified, then call
+ //require with those args. This is useful when require is defined as a
+ //config object before require.js is loaded.
+ if (cfg.deps || cfg.callback) {
+ context.require(cfg.deps || [], cfg.callback);
+ }
+ },
+
+ makeShimExports: function (value) {
+ function fn() {
+ var ret;
+ if (value.init) {
+ ret = value.init.apply(global, arguments);
+ }
+ return ret || (value.exports && getGlobal(value.exports));
+ }
+ return fn;
+ },
+
+ makeRequire: function (relMap, options) {
+ options = options || {};
+
+ function localRequire(deps, callback, errback) {
+ var id, map, requireMod;
+
+ if (options.enableBuildCallback && callback && isFunction(callback)) {
+ callback.__requireJsBuild = true;
+ }
+
+ if (typeof deps === 'string') {
+ if (isFunction(callback)) {
+ //Invalid call
+ return onError(makeError('requireargs', 'Invalid require call'), errback);
+ }
+
+ //If require|exports|module are requested, get the
+ //value for them from the special handlers. Caveat:
+ //this only works while module is being defined.
+ if (relMap && hasProp(handlers, deps)) {
+ return handlers[deps](registry[relMap.id]);
+ }
+
+ //Synchronous access to one module. If require.get is
+ //available (as in the Node adapter), prefer that.
+ if (req.get) {
+ return req.get(context, deps, relMap, localRequire);
+ }
+
+ //Normalize module name, if it contains . or ..
+ map = makeModuleMap(deps, relMap, false, true);
+ id = map.id;
+
+ if (!hasProp(defined, id)) {
+ return onError(makeError('notloaded', 'Module name "' +
+ id +
+ '" has not been loaded yet for context: ' +
+ contextName +
+ (relMap ? '' : '. Use require([])')));
+ }
+ return defined[id];
+ }
+
+ //Grab defines waiting in the global queue.
+ intakeDefines();
+
+ //Mark all the dependencies as needing to be loaded.
+ context.nextTick(function () {
+ //Some defines could have been added since the
+ //require call, collect them.
+ intakeDefines();
+
+ requireMod = getModule(makeModuleMap(null, relMap));
+
+ //Store if map config should be applied to this require
+ //call for dependencies.
+ requireMod.skipMap = options.skipMap;
+
+ requireMod.init(deps, callback, errback, {
+ enabled: true
+ });
+
+ checkLoaded();
+ });
+
+ return localRequire;
+ }
+
+ mixin(localRequire, {
+ isBrowser: isBrowser,
+
+ /**
+ * Converts a module name + .extension into an URL path.
+ * *Requires* the use of a module name. It does not support using
+ * plain URLs like nameToUrl.
+ */
+ toUrl: function (moduleNamePlusExt) {
+ var ext,
+ index = moduleNamePlusExt.lastIndexOf('.'),
+ segment = moduleNamePlusExt.split('/')[0],
+ isRelative = segment === '.' || segment === '..';
+
+ //Have a file extension alias, and it is not the
+ //dots from a relative path.
+ if (index !== -1 && (!isRelative || index > 1)) {
+ ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+ moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
+ }
+
+ return context.nameToUrl(normalize(moduleNamePlusExt,
+ relMap && relMap.id, true), ext, true);
+ },
+
+ defined: function (id) {
+ return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
+ },
+
+ specified: function (id) {
+ id = makeModuleMap(id, relMap, false, true).id;
+ return hasProp(defined, id) || hasProp(registry, id);
+ }
+ });
+
+ //Only allow undef on top level require calls
+ if (!relMap) {
+ localRequire.undef = function (id) {
+ //Bind any waiting define() calls to this context,
+ //fix for #408
+ takeGlobalQueue();
+
+ var map = makeModuleMap(id, relMap, true),
+ mod = getOwn(registry, id);
+
+ mod.undefed = true;
+ removeScript(id);
+
+ delete defined[id];
+ delete urlFetched[map.url];
+ delete undefEvents[id];
+
+ //Clean queued defines too. Go backwards
+ //in array so that the splices do not
+ //mess up the iteration.
+ eachReverse(defQueue, function(args, i) {
+ if (args[0] === id) {
+ defQueue.splice(i, 1);
+ }
+ });
+ delete context.defQueueMap[id];
+
+ if (mod) {
+ //Hold on to listeners in case the
+ //module will be attempted to be reloaded
+ //using a different config.
+ if (mod.events.defined) {
+ undefEvents[id] = mod.events;
+ }
+
+ cleanRegistry(id);
+ }
+ };
+ }
+
+ return localRequire;
+ },
+
+ /**
+ * Called to enable a module if it is still in the registry
+ * awaiting enablement. A second arg, parent, the parent module,
+ * is passed in for context, when this method is overridden by
+ * the optimizer. Not shown here to keep code compact.
+ */
+ enable: function (depMap) {
+ var mod = getOwn(registry, depMap.id);
+ if (mod) {
+ getModule(depMap).enable();
+ }
+ },
+
+ /**
+ * Internal method used by environment adapters to complete a load event.
+ * A load event could be a script load or just a load pass from a synchronous
+ * load call.
+ * @param {String} moduleName the name of the module to potentially complete.
+ */
+ completeLoad: function (moduleName) {
+ var found, args, mod,
+ shim = getOwn(config.shim, moduleName) || {},
+ shExports = shim.exports;
+
+ takeGlobalQueue();
+
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ args[0] = moduleName;
+ //If already found an anonymous module and bound it
+ //to this name, then this is some other anon module
+ //waiting for its completeLoad to fire.
+ if (found) {
+ break;
+ }
+ found = true;
+ } else if (args[0] === moduleName) {
+ //Found matching define call for this script!
+ found = true;
+ }
+
+ callGetModule(args);
+ }
+ context.defQueueMap = {};
+
+ //Do this after the cycle of callGetModule in case the result
+ //of those calls/init calls changes the registry.
+ mod = getOwn(registry, moduleName);
+
+ if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
+ if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
+ if (hasPathFallback(moduleName)) {
+ return;
+ } else {
+ return onError(makeError('nodefine',
+ 'No define call for ' + moduleName,
+ null,
+ [moduleName]));
+ }
+ } else {
+ //A script that does not call define(), so just simulate
+ //the call for it.
+ callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
+ }
+ }
+
+ checkLoaded();
+ },
+
+ /**
+ * Converts a module name to a file path. Supports cases where
+ * moduleName may actually be just an URL.
+ * Note that it **does not** call normalize on the moduleName,
+ * it is assumed to have already been normalized. This is an
+ * internal API, not a public one. Use toUrl for the public API.
+ */
+ nameToUrl: function (moduleName, ext, skipExt) {
+ var paths, syms, i, parentModule, url,
+ parentPath, bundleId,
+ pkgMain = getOwn(config.pkgs, moduleName);
+
+ if (pkgMain) {
+ moduleName = pkgMain;
+ }
+
+ bundleId = getOwn(bundlesMap, moduleName);
+
+ if (bundleId) {
+ return context.nameToUrl(bundleId, ext, skipExt);
+ }
+
+ //If a colon is in the URL, it indicates a protocol is used and it is just
+ //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
+ //or ends with .js, then assume the user meant to use an url and not a module id.
+ //The slash is important for protocol-less URLs as well as full paths.
+ if (req.jsExtRegExp.test(moduleName)) {
+ //Just a plain path, not module name lookup, so just return it.
+ //Add extension if it is included. This is a bit wonky, only non-.js things pass
+ //an extension, this method probably needs to be reworked.
+ url = moduleName + (ext || '');
+ } else {
+ //A module that needs to be converted to a path.
+ paths = config.paths;
+
+ syms = moduleName.split('/');
+ //For each module name segment, see if there is a path
+ //registered for it. Start with most specific name
+ //and work up from it.
+ for (i = syms.length; i > 0; i -= 1) {
+ parentModule = syms.slice(0, i).join('/');
+
+ parentPath = getOwn(paths, parentModule);
+ if (parentPath) {
+ //If an array, it means there are a few choices,
+ //Choose the one that is desired
+ if (isArray(parentPath)) {
+ parentPath = parentPath[0];
+ }
+ syms.splice(0, i, parentPath);
+ break;
+ }
+ }
+
+ //Join the path parts together, then figure out if baseUrl is needed.
+ url = syms.join('/');
+ url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
+ url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+ }
+
+ return config.urlArgs && !/^blob\:/.test(url) ?
+ url + config.urlArgs(moduleName, url) : url;
+ },
+
+ //Delegates to req.load. Broken out as a separate function to
+ //allow overriding in the optimizer.
+ load: function (id, url) {
+ req.load(context, id, url);
+ },
+
+ /**
+ * Executes a module callback function. Broken out as a separate function
+ * solely to allow the build system to sequence the files in the built
+ * layer in the right sequence.
+ *
+ * @private
+ */
+ execCb: function (name, callback, args, exports) {
+ return callback.apply(exports, args);
+ },
+
+ /**
+ * callback for script loads, used to check status of loading.
+ *
+ * @param {Event} evt the event from the browser for the script
+ * that was loaded.
+ */
+ onScriptLoad: function (evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ if (evt.type === 'load' ||
+ (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
+ //Reset interactive script so a script node is not held onto for
+ //to long.
+ interactiveScript = null;
+
+ //Pull out the name of the module and the context.
+ var data = getScriptData(evt);
+ context.completeLoad(data.id);
+ }
+ },
+
+ /**
+ * Callback for script errors.
+ */
+ onScriptError: function (evt) {
+ var data = getScriptData(evt);
+ if (!hasPathFallback(data.id)) {
+ var parents = [];
+ eachProp(registry, function(value, key) {
+ if (key.indexOf('_@r') !== 0) {
+ each(value.depMaps, function(depMap) {
+ if (depMap.id === data.id) {
+ parents.push(key);
+ return true;
+ }
+ });
+ }
+ });
+ return onError(makeError('scripterror', 'Script error for "' + data.id +
+ (parents.length ?
+ '", needed by: ' + parents.join(', ') :
+ '"'), evt, [data.id]));
+ }
+ }
+ };
+
+ context.require = context.makeRequire();
+ return context;
+ }
+
+ /**
+ * Main entry point.
+ *
+ * If the only argument to require is a string, then the module that
+ * is represented by that string is fetched for the appropriate context.
+ *
+ * If the first argument is an array, then it will be treated as an array
+ * of dependency string names to fetch. An optional function callback can
+ * be specified to execute when all of those dependencies are available.
+ *
+ * Make a local req variable to help Caja compliance (it assumes things
+ * on a require that are not standardized), and to give a short
+ * name for minification/local scope use.
+ */
+ req = requirejs = function (deps, callback, errback, optional) {
+
+ //Find the right context, use default
+ var context, config,
+ contextName = defContextName;
+
+ // Determine if have config object in the call.
+ if (!isArray(deps) && typeof deps !== 'string') {
+ // deps is a config object
+ config = deps;
+ if (isArray(callback)) {
+ // Adjust args if there are dependencies
+ deps = callback;
+ callback = errback;
+ errback = optional;
+ } else {
+ deps = [];
+ }
+ }
+
+ if (config && config.context) {
+ contextName = config.context;
+ }
+
+ context = getOwn(contexts, contextName);
+ if (!context) {
+ context = contexts[contextName] = req.s.newContext(contextName);
+ }
+
+ if (config) {
+ context.configure(config);
+ }
+
+ return context.require(deps, callback, errback);
+ };
+
+ /**
+ * Support require.config() to make it easier to cooperate with other
+ * AMD loaders on globally agreed names.
+ */
+ req.config = function (config) {
+ return req(config);
+ };
+
+ /**
+ * Execute something after the current tick
+ * of the event loop. Override for other envs
+ * that have a better solution than setTimeout.
+ * @param {Function} fn function to execute later.
+ */
+ req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
+ setTimeout(fn, 4);
+ } : function (fn) { fn(); };
+
+ /**
+ * Export require as a global, but only if it does not already exist.
+ */
+ if (!require) {
+ require = req;
+ }
+
+ req.version = version;
+
+ //Used to filter out dependencies that are already paths.
+ req.jsExtRegExp = /^\/|:|\?|\.js$/;
+ req.isBrowser = isBrowser;
+ s = req.s = {
+ contexts: contexts,
+ newContext: newContext
+ };
+
+ //Create default context.
+ req({});
+
+ //Exports some context-sensitive methods on global require.
+ each([
+ 'toUrl',
+ 'undef',
+ 'defined',
+ 'specified'
+ ], function (prop) {
+ //Reference from contexts instead of early binding to default context,
+ //so that during builds, the latest instance of the default context
+ //with its config gets used.
+ req[prop] = function () {
+ var ctx = contexts[defContextName];
+ return ctx.require[prop].apply(ctx, arguments);
+ };
+ });
+
+ if (isBrowser) {
+ head = s.head = document.getElementsByTagName('head')[0];
+ //If BASE tag is in play, using appendChild is a problem for IE6.
+ //When that browser dies, this can be removed. Details in this jQuery bug:
+ //http://dev.jquery.com/ticket/2709
+ baseElement = document.getElementsByTagName('base')[0];
+ if (baseElement) {
+ head = s.head = baseElement.parentNode;
+ }
+ }
+
+ /**
+ * Any errors that require explicitly generates will be passed to this
+ * function. Intercept/override it if you want custom error handling.
+ * @param {Error} err the error object.
+ */
+ req.onError = defaultOnError;
+
+ /**
+ * Creates the node for the load command. Only used in browser envs.
+ */
+ req.createNode = function (config, moduleName, url) {
+ var node = config.xhtml ?
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
+ document.createElement('script');
+ node.type = config.scriptType || 'text/javascript';
+ node.charset = 'utf-8';
+ node.async = true;
+ return node;
+ };
+
+ /**
+ * Does the request to load a module for the browser case.
+ * Make this a separate function to allow other environments
+ * to override it.
+ *
+ * @param {Object} context the require context to find state.
+ * @param {String} moduleName the name of the module.
+ * @param {Object} url the URL to the module.
+ */
+ req.load = function (context, moduleName, url) {
+ var config = (context && context.config) || {},
+ node;
+ if (isBrowser) {
+ //In the browser so use a script tag
+ node = req.createNode(config, moduleName, url);
+
+ node.setAttribute('data-requirecontext', context.contextName);
+ node.setAttribute('data-requiremodule', moduleName);
+
+ //Set up load listener. Test attachEvent first because IE9 has
+ //a subtle issue in its addEventListener and script onload firings
+ //that do not match the behavior of all other browsers with
+ //addEventListener support, which fire the onload event for a
+ //script right after the script execution. See:
+ //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
+ //UNFORTUNATELY Opera implements attachEvent but does not follow the script
+ //script execution mode.
+ if (node.attachEvent &&
+ //Check if node.attachEvent is artificially added by custom script or
+ //natively supported by browser
+ //read https://github.com/requirejs/requirejs/issues/187
+ //if we can NOT find [native code] then it must NOT natively supported.
+ //in IE8, node.attachEvent does not have toString()
+ //Note the test for "[native code" with no closing brace, see:
+ //https://github.com/requirejs/requirejs/issues/273
+ !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
+ !isOpera) {
+ //Probably IE. IE (at least 6-8) do not fire
+ //script onload right after executing the script, so
+ //we cannot tie the anonymous define call to a name.
+ //However, IE reports the script as being in 'interactive'
+ //readyState at the time of the define call.
+ useInteractive = true;
+
+ node.attachEvent('onreadystatechange', context.onScriptLoad);
+ //It would be great to add an error handler here to catch
+ //404s in IE9+. However, onreadystatechange will fire before
+ //the error handler, so that does not help. If addEventListener
+ //is used, then IE will fire error before load, but we cannot
+ //use that pathway given the connect.microsoft.com issue
+ //mentioned above about not doing the 'script execute,
+ //then fire the script load event listener before execute
+ //next script' that other browsers do.
+ //Best hope: IE10 fixes the issues,
+ //and then destroys all installs of IE 6-9.
+ //node.attachEvent('onerror', context.onScriptError);
+ } else {
+ node.addEventListener('load', context.onScriptLoad, false);
+ node.addEventListener('error', context.onScriptError, false);
+ }
+ node.src = url;
+
+ //Calling onNodeCreated after all properties on the node have been
+ //set, but before it is placed in the DOM.
+ if (config.onNodeCreated) {
+ config.onNodeCreated(node, config, moduleName, url);
+ }
+
+ //For some cache cases in IE 6-8, the script executes before the end
+ //of the appendChild execution, so to tie an anonymous define
+ //call to the module name (which is stored on the node), hold on
+ //to a reference to this node, but clear after the DOM insertion.
+ currentlyAddingScript = node;
+ if (baseElement) {
+ head.insertBefore(node, baseElement);
+ } else {
+ head.appendChild(node);
+ }
+ currentlyAddingScript = null;
+
+ return node;
+ } else if (isWebWorker) {
+ try {
+ //In a web worker, use importScripts. This is not a very
+ //efficient use of importScripts, importScripts will block until
+ //its script is downloaded and evaluated. However, if web workers
+ //are in play, the expectation is that a build has been done so
+ //that only one script needs to be loaded anyway. This may need
+ //to be reevaluated if other use cases become common.
+
+ // Post a task to the event loop to work around a bug in WebKit
+ // where the worker gets garbage-collected after calling
+ // importScripts(): https://webkit.org/b/153317
+ setTimeout(function() {}, 0);
+ importScripts(url);
+
+ //Account for anonymous modules
+ context.completeLoad(moduleName);
+ } catch (e) {
+ context.onError(makeError('importscripts',
+ 'importScripts failed for ' +
+ moduleName + ' at ' + url,
+ e,
+ [moduleName]));
+ }
+ }
+ };
+
+ function getInteractiveScript() {
+ if (interactiveScript && interactiveScript.readyState === 'interactive') {
+ return interactiveScript;
+ }
+
+ eachReverse(scripts(), function (script) {
+ if (script.readyState === 'interactive') {
+ return (interactiveScript = script);
+ }
+ });
+ return interactiveScript;
+ }
+
+ //Look for a data-main script attribute, which could also adjust the baseUrl.
+ if (isBrowser && !cfg.skipDataMain) {
+ //Figure out baseUrl. Get it from the script tag with require.js in it.
+ eachReverse(scripts(), function (script) {
+ //Set the 'head' where we can append children by
+ //using the script's parent.
+ if (!head) {
+ head = script.parentNode;
+ }
+
+ //Look for a data-main attribute to set main script for the page
+ //to load. If it is there, the path to data main becomes the
+ //baseUrl, if it is not already set.
+ dataMain = script.getAttribute('data-main');
+ if (dataMain) {
+ //Preserve dataMain in case it is a path (i.e. contains '?')
+ mainScript = dataMain;
+
+ //Set final baseUrl if there is not already an explicit one,
+ //but only do so if the data-main value is not a loader plugin
+ //module ID.
+ if (!cfg.baseUrl && mainScript.indexOf('!') === -1) {
+ //Pull off the directory of data-main for use as the
+ //baseUrl.
+ src = mainScript.split('/');
+ mainScript = src.pop();
+ subPath = src.length ? src.join('/') + '/' : './';
+
+ cfg.baseUrl = subPath;
+ }
+
+ //Strip off any trailing .js since mainScript is now
+ //like a module name.
+ mainScript = mainScript.replace(jsSuffixRegExp, '');
+
+ //If mainScript is still a path, fall back to dataMain
+ if (req.jsExtRegExp.test(mainScript)) {
+ mainScript = dataMain;
+ }
+
+ //Put the data-main script in the files to load.
+ cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * The function that handles definitions of modules. Differs from
+ * require() in that a string for the module should be the first argument,
+ * and the function to execute after dependencies are loaded should
+ * return a value to define the module corresponding to the first argument's
+ * name.
+ */
+ define = function (name, deps, callback) {
+ var node, context;
+
+ //Allow for anonymous modules
+ if (typeof name !== 'string') {
+ //Adjust args appropriately
+ callback = deps;
+ deps = name;
+ name = null;
+ }
+
+ //This module may not have dependencies
+ if (!isArray(deps)) {
+ callback = deps;
+ deps = null;
+ }
+
+ //If no name, and callback is a function, then figure out if it a
+ //CommonJS thing with dependencies.
+ if (!deps && isFunction(callback)) {
+ deps = [];
+ //Remove comments from the callback string,
+ //look for require calls, and pull them into the dependencies,
+ //but only if there are function args.
+ if (callback.length) {
+ callback
+ .toString()
+ .replace(commentRegExp, commentReplace)
+ .replace(cjsRequireRegExp, function (match, dep) {
+ deps.push(dep);
+ });
+
+ //May be a CommonJS thing even without require calls, but still
+ //could use exports, and module. Avoid doing exports and module
+ //work though if it just needs require.
+ //REQUIRES the function to expect the CommonJS variables in the
+ //order listed below.
+ deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
+ }
+ }
+
+ //If in IE 6-8 and hit an anonymous define() call, do the interactive
+ //work.
+ if (useInteractive) {
+ node = currentlyAddingScript || getInteractiveScript();
+ if (node) {
+ if (!name) {
+ name = node.getAttribute('data-requiremodule');
+ }
+ context = contexts[node.getAttribute('data-requirecontext')];
+ }
+ }
+
+ //Always save off evaluating the def call until the script onload handler.
+ //This allows multiple modules to be in a file without prematurely
+ //tracing dependencies, and allows for anonymous module support,
+ //where the module name is not known until the script onload event
+ //occurs. If no context, use the global queue, and get it processed
+ //in the onscript load callback.
+ if (context) {
+ context.defQueue.push([name, deps, callback]);
+ context.defQueueMap[name] = true;
+ } else {
+ globalDefQueue.push([name, deps, callback]);
+ }
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+
+ /**
+ * Executes the text. Normally just uses eval, but can be modified
+ * to use a better, environment-specific call. Only used for transpiling
+ * loader plugins, not for plain JS modules.
+ * @param {String} text the text to execute/evaluate.
+ */
+ req.exec = function (text) {
+ /*jslint evil: true */
+ return eval(text);
+ };
+
+ //Set up with config info.
+ req(cfg);
+}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));
diff --git a/demos/ES6/module/requirejs/vender/square.js b/demos/ES6/module/requirejs/vender/square.js
new file mode 100755
index 00000000..b0e3a077
--- /dev/null
+++ b/demos/ES6/module/requirejs/vender/square.js
@@ -0,0 +1,8 @@
+define(['./multiply'], function(multiplyModule) {
+ console.log('加载了 square 模块')
+ return {
+ square: function(num) {
+ return multiplyModule.multiply(num, num)
+ }
+ };
+});
\ No newline at end of file
diff --git a/demos/ES6/module/seajs/index.html b/demos/ES6/module/seajs/index.html
new file mode 100755
index 00000000..b4ad2eb0
--- /dev/null
+++ b/demos/ES6/module/seajs/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ sea.js
+
+
+
+
Content
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/ES6/module/seajs/vender/add.js b/demos/ES6/module/seajs/vender/add.js
new file mode 100755
index 00000000..a039b74a
--- /dev/null
+++ b/demos/ES6/module/seajs/vender/add.js
@@ -0,0 +1,13 @@
+define(function(require, exports, module) {
+
+ console.log('加载了 add 模块')
+
+ var add = function(x, y) {
+ return x + y;
+ };
+
+ module.exports = {
+ add: add
+ };
+
+});
\ No newline at end of file
diff --git a/demos/ES6/module/seajs/vender/main.js b/demos/ES6/module/seajs/vender/main.js
new file mode 100755
index 00000000..5db86186
--- /dev/null
+++ b/demos/ES6/module/seajs/vender/main.js
@@ -0,0 +1,7 @@
+define(function(require, exports, module) {
+ var addModule = require('./add');
+ console.log(addModule.add(1, 1))
+
+ var squareModule = require('./square');
+ console.log(squareModule.square(3))
+});
\ No newline at end of file
diff --git a/demos/ES6/module/seajs/vender/multiply.js b/demos/ES6/module/seajs/vender/multiply.js
new file mode 100755
index 00000000..72eb3602
--- /dev/null
+++ b/demos/ES6/module/seajs/vender/multiply.js
@@ -0,0 +1,13 @@
+define(function(require, exports, module) {
+
+ console.log('加载了 multiply 模块')
+
+ var multiply = function(x, y) {
+ return x * y;
+ };
+
+ module.exports = {
+ multiply: multiply
+ };
+
+});
\ No newline at end of file
diff --git a/demos/ES6/module/seajs/vender/sea.js b/demos/ES6/module/seajs/vender/sea.js
new file mode 100755
index 00000000..57d5362e
--- /dev/null
+++ b/demos/ES6/module/seajs/vender/sea.js
@@ -0,0 +1,2 @@
+/*! Sea.js 3.0.0 | seajs.org/LICENSE.md */
+!function(a,b){function c(a){return function(b){return{}.toString.call(b)=="[object "+a+"]"}}function d(){return A++}function e(a){return a.match(D)[0]}function f(a){for(a=a.replace(E,"/"),a=a.replace(G,"$1/");a.match(F);)a=a.replace(F,"/");return a}function g(a){var b=a.length-1,c=a.charCodeAt(b);return 35===c?a.substring(0,b):".js"===a.substring(b-2)||a.indexOf("?")>0||47===c?a:a+".js"}function h(a){var b=v.alias;return b&&x(b[a])?b[a]:a}function i(a){var b=v.paths,c;return b&&(c=a.match(H))&&x(b[c[1]])&&(a=b[c[1]]+c[2]),a}function j(a){var b=v.vars;return b&&a.indexOf("{")>-1&&(a=a.replace(I,function(a,c){return x(b[c])?b[c]:a})),a}function k(a){var b=v.map,c=a;if(b)for(var d=0,e=b.length;e>d;d++){var f=b[d];if(c=z(f)?f(a)||a:a.replace(f[0],f[1]),c!==a)break}return c}function l(a,b){var c,d=a.charCodeAt(0);if(J.test(a))c=a;else if(46===d)c=(b?e(b):v.cwd)+a;else if(47===d){var g=v.cwd.match(K);c=g?g[0]+a.substring(1):a}else c=v.base+a;return 0===c.indexOf("//")&&(c=location.protocol+c),f(c)}function m(a,b){if(!a)return"";a=h(a),a=i(a),a=h(a),a=j(a),a=h(a),a=g(a),a=h(a);var c=l(a,b);return c=h(c),c=k(c)}function n(a){return a.hasAttribute?a.src:a.getAttribute("src",4)}function o(a,b,c){var d;try{importScripts(a)}catch(e){d=e}b(d)}function p(a,b,c){var d=Y.createElement("script");if(c){var e=z(c)?c(a):c;e&&(d.charset=e)}q(d,b,a),d.async=!0,d.src=a,bb=d,ab?_.insertBefore(d,ab):_.appendChild(d),bb=null}function q(a,b,c){function d(c){a.onload=a.onerror=a.onreadystatechange=null,v.debug||_.removeChild(a),a=null,b(c)}var e="onload"in a;e?(a.onload=d,a.onerror=function(){C("error",{uri:c,node:a}),d(!0)}):a.onreadystatechange=function(){/loaded|complete/.test(a.readyState)&&d()}}function r(){if(bb)return bb;if(cb&&"interactive"===cb.readyState)return cb;for(var a=_.getElementsByTagName("script"),b=a.length-1;b>=0;b--){var c=a[b];if("interactive"===c.readyState)return cb=c}}function s(a){function b(){l=a.charAt(k++)}function c(){return/\s/.test(l)}function d(){return'"'==l||"'"==l}function e(){var c=k,d=l,e=a.indexOf(d,c);if(-1==e)k=m;else if("\\"!=a.charAt(e-1))k=e+1;else for(;m>k;)if(b(),"\\"==l)k++;else if(l==d)break;o&&(r.push(a.slice(c,k-1)),o=0)}function f(){for(k--;m>k;)if(b(),"\\"==l)k++;else{if("/"==l)break;if("["==l)for(;m>k;)if(b(),"\\"==l)k++;else if("]"==l)break}}function g(){return/[a-z_$]/i.test(l)}function h(){var b=a.slice(k-1),c=/^[\w$]+/.exec(b)[0];p={"if":1,"for":1,"while":1,"with":1}[c],n={"break":1,"case":1,"continue":1,"debugger":1,"delete":1,"do":1,"else":1,"false":1,"if":1,"in":1,"instanceof":1,"return":1,"typeof":1,"void":1}[c],o=/^require\s*\(\s*(['"]).+?\1\s*\)/.test(b),o?(c=/^require\s*\(\s*['"]/.exec(b)[0],k+=c.length-2):k+=/^[\w$]+(?:\s*\.\s*[\w$]+)*/.exec(b)[0].length-1}function i(){return/\d/.test(l)||"."==l&&/\d/.test(a.charAt(k))}function j(){var b=a.slice(k-1),c;c="."==l?/^\.\d+(?:E[+-]?\d*)?\s*/i.exec(b)[0]:/^0x[\da-f]*/i.test(b)?/^0x[\da-f]*\s*/i.exec(b)[0]:/^\d+\.?\d*(?:E[+-]?\d*)?\s*/i.exec(b)[0],k+=c.length-1,n=0}if(-1==a.indexOf("require"))return[];for(var k=0,l,m=a.length,n=1,o=0,p=0,q=[],r=[];m>k;)b(),c()||(d()?(e(),n=1):"/"==l?(b(),"/"==l?(k=a.indexOf("\n",k),-1==k&&(k=a.length)):"*"==l?(k=a.indexOf("*/",k),-1==k?k=m:k+=2):n?(f(),n=0):(k--,n=1)):g()?h():i()?j():"("==l?(q.push(p),n=1):")"==l?n=q.pop():(n="]"!=l,o=0));return r}function t(a,b){this.uri=a,this.dependencies=b||[],this.deps={},this.status=0,this._entry=[]}if(!a.seajs){var u=a.seajs={version:"3.0.0"},v=u.data={},w=c("Object"),x=c("String"),y=Array.isArray||c("Array"),z=c("Function"),A=0,B=v.events={};u.on=function(a,b){var c=B[a]||(B[a]=[]);return c.push(b),u},u.off=function(a,b){if(!a&&!b)return B=v.events={},u;var c=B[a];if(c)if(b)for(var d=c.length-1;d>=0;d--)c[d]===b&&c.splice(d,1);else delete B[a];return u};var C=u.emit=function(a,b){var c=B[a];if(c){c=c.slice();for(var d=0,e=c.length;e>d;d++)c[d](b)}return u},D=/[^?#]*\//,E=/\/\.\//g,F=/\/[^/]+\/\.\.\//,G=/([^:/])\/+\//g,H=/^([^/:]+)(\/.+)$/,I=/{([^{]+)}/g,J=/^\/\/.|:\//,K=/^.*?\/\/.*?\//;u.resolve=m;var L="undefined"==typeof window&&"undefined"!=typeof importScripts&&z(importScripts),M=/^(about|blob):/,N,O,P=!location.href||M.test(location.href)?"":e(location.href);if(L){var Q;try{var R=Error();throw R}catch(S){Q=S.stack.split("\n")}Q.shift();for(var T,U=/.*?((?:http|https|file)(?::\/{2}[\w]+)(?:[\/|\.]?)(?:[^\s"]*)).*?/i,V=/(.*?):\d+:\d+\)?$/;Q.length>0;){var W=Q.shift();if(T=U.exec(W),null!=T)break}var X;if(null!=T)var X=V.exec(T[1])[1];O=X,N=e(X||P),""===P&&(P=N)}else{var Y=document,Z=Y.scripts,$=Y.getElementById("seajsnode")||Z[Z.length-1];O=n($),N=e(O||P)}if(L)u.request=o;else{var Y=document,_=Y.head||Y.getElementsByTagName("head")[0]||Y.documentElement,ab=_.getElementsByTagName("base")[0],bb;u.request=p}var cb,db=u.cache={},eb,fb={},gb={},hb={},ib=t.STATUS={FETCHING:1,SAVED:2,LOADING:3,LOADED:4,EXECUTING:5,EXECUTED:6,ERROR:7};t.prototype.resolve=function(){for(var a=this,b=a.dependencies,c=[],d=0,e=b.length;e>d;d++)c[d]=t.resolve(b[d],a.uri);return c},t.prototype.pass=function(){for(var a=this,b=a.dependencies.length,c=0;cf;f++){var g=a.deps[a.dependencies[f]];g.status0&&(d.remain+=e-1,a._entry.shift(),c--)}},t.prototype.load=function(){var a=this;if(!(a.status>=ib.LOADING)){a.status=ib.LOADING;var c=a.resolve();C("load",c);for(var d=0,e=c.length;e>d;d++)a.deps[a.dependencies[d]]=t.get(c[d]);if(a.pass(),a._entry.length)return a.onload(),b;var f={},g;for(d=0;e>d;d++)g=db[c[d]],g.statusb;b++){var d=a._entry[b];0===--d.remain&&d.callback()}delete a._entry},t.prototype.error=function(){var a=this;a.onload(),a.status=ib.ERROR},t.prototype.exec=function(){function a(b){var d=c.deps[b]||t.get(a.resolve(b));if(d.status==ib.ERROR)throw Error("module was broken: "+d.uri);return d.exec()}var c=this;if(c.status>=ib.EXECUTING)return c.exports;if(c.status=ib.EXECUTING,c._entry&&!c._entry.length&&delete c._entry,!c.hasOwnProperty("factory"))return c.non=!0,b;var e=c.uri;a.resolve=function(a){return t.resolve(a,e)},a.async=function(b,c){return t.use(b,c,e+"_async_"+d()),a};var f=c.factory,g=z(f)?f(a,c.exports={},c):f;return g===b&&(g=c.exports),delete c.factory,c.exports=g,c.status=ib.EXECUTED,C("exec",c),c.exports},t.prototype.fetch=function(a){function c(){u.request(g.requestUri,g.onRequest,g.charset)}function d(a){delete fb[h],gb[h]=!0,eb&&(t.save(f,eb),eb=null);var b,c=hb[h];for(delete hb[h];b=c.shift();)a===!0?b.error():b.load()}var e=this,f=e.uri;e.status=ib.FETCHING;var g={uri:f};C("fetch",g);var h=g.requestUri||f;return!h||gb.hasOwnProperty(h)?(e.load(),b):fb.hasOwnProperty(h)?(hb[h].push(e),b):(fb[h]=!0,hb[h]=[e],C("request",g={uri:f,requestUri:h,onRequest:d,charset:z(v.charset)?v.charset(h)||"utf-8":v.charset}),g.requested||(a?a[g.requestUri]=c:c()),b)},t.resolve=function(a,b){var c={id:a,refUri:b};return C("resolve",c),c.uri||u.resolve(c.id,b)},t.define=function(a,c,d){var e=arguments.length;1===e?(d=a,a=b):2===e&&(d=c,y(a)?(c=a,a=b):c=b),!y(c)&&z(d)&&(c=b===s?[]:s(""+d));var f={id:a,uri:t.resolve(a),deps:c,factory:d};if(!L&&!f.uri&&Y.attachEvent&&b!==r){var g=r();g&&(f.uri=g.src)}C("define",f),f.uri?t.save(f.uri,f):eb=f},t.save=function(a,b){var c=t.get(a);c.statusf;f++)b[f]=db[d[f]].exec();c&&c.apply(a,b),delete e.callback,delete e.history,delete e.remain,delete e._entry},e.load()},u.use=function(a,b){return t.use(a,b,v.cwd+"_use_"+d()),u},t.define.cmd={},a.define=t.define,u.Module=t,v.fetchedList=gb,v.cid=d,u.require=function(a){var b=t.get(t.resolve(a));return b.status
+
+
+ webpack 原理
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/VuePress/vuepress-plugin-code-copy/CodeCopy.vue b/demos/VuePress/vuepress-plugin-code-copy/CodeCopy.vue
new file mode 100644
index 00000000..0341c763
--- /dev/null
+++ b/demos/VuePress/vuepress-plugin-code-copy/CodeCopy.vue
@@ -0,0 +1,56 @@
+
+ {{ buttonText }}
+
+
+
+
+
diff --git a/demos/VuePress/vuepress-plugin-code-copy/clientRootMixin.js b/demos/VuePress/vuepress-plugin-code-copy/clientRootMixin.js
new file mode 100644
index 00000000..4bb91eb6
--- /dev/null
+++ b/demos/VuePress/vuepress-plugin-code-copy/clientRootMixin.js
@@ -0,0 +1,18 @@
+import CodeCopy from './CodeCopy.vue'
+import Vue from 'vue'
+
+export default {
+ updated() {
+ setTimeout(() => {
+ document.querySelectorAll('div[class*="language-"] pre').forEach(el => {
+ if (el.classList.contains('code-copy-added')) return
+ let ComponentClass = Vue.extend(CodeCopy)
+ let instance = new ComponentClass()
+ instance.code = el.innerText
+ instance.$mount()
+ el.classList.add('code-copy-added')
+ el.appendChild(instance.$el)
+ })
+ }, 100)
+ }
+}
diff --git a/demos/VuePress/vuepress-plugin-code-copy/index.js b/demos/VuePress/vuepress-plugin-code-copy/index.js
new file mode 100644
index 00000000..efa72e55
--- /dev/null
+++ b/demos/VuePress/vuepress-plugin-code-copy/index.js
@@ -0,0 +1,12 @@
+const path = require('path');
+
+module.exports = (options, ctx) => {
+ return {
+ name: 'vuepress-plugin-code-copy',
+ define: {
+ copybuttonText: options.copybuttonText || 'copy',
+ copiedButtonText: options.copiedButtonText || "copied!"
+ },
+ clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
+ }
+ }
\ No newline at end of file
diff --git a/demos/VuePress/vuepress-plugin-code-copy/package.json b/demos/VuePress/vuepress-plugin-code-copy/package.json
new file mode 100644
index 00000000..99195920
--- /dev/null
+++ b/demos/VuePress/vuepress-plugin-code-copy/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "vuepress-plugin-code-copy",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/demos/debounce/debounce1.js b/demos/debounce/debounce1.js
new file mode 100644
index 00000000..c4eb9a9b
--- /dev/null
+++ b/demos/debounce/debounce1.js
@@ -0,0 +1,14 @@
+/**
+ * 事件会被频繁的触发
+ */
+
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction() {
+ container.innerHTML = count++;
+};
+
+container.onmousemove = getUserAction;
+
+
diff --git a/demos/debounce/debounce2.js b/demos/debounce/debounce2.js
new file mode 100644
index 00000000..e38e44ef
--- /dev/null
+++ b/demos/debounce/debounce2.js
@@ -0,0 +1,21 @@
+/**
+ * 随你怎么移动,反正你移动完1000ms内不再触发,我再执行事件
+ */
+
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction() {
+ container.innerHTML = count++;
+};
+
+container.onmousemove = debounce(getUserAction, 1000);
+
+// 第一版
+function debounce(func, wait) {
+ var timeout;
+ return function () {
+ clearTimeout(timeout)
+ timeout = setTimeout(func, wait);
+ }
+}
\ No newline at end of file
diff --git a/demos/debounce/debounce3.js b/demos/debounce/debounce3.js
new file mode 100644
index 00000000..41b5054c
--- /dev/null
+++ b/demos/debounce/debounce3.js
@@ -0,0 +1,28 @@
+/**
+ * 使用正确的this指向
+ */
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction() {
+ console.log(this)
+ container.innerHTML = count++;
+};
+
+container.onmousemove = debounce(getUserAction, 1000);
+
+// 第二版
+function debounce(func, wait) {
+ var timeout;
+
+ return function () {
+ var context = this;
+
+ clearTimeout(timeout)
+ timeout = setTimeout(function(){
+ func.apply(context)
+ }, wait);
+ }
+}
+
+
diff --git a/demos/debounce/debounce4.js b/demos/debounce/debounce4.js
new file mode 100644
index 00000000..6317a7b3
--- /dev/null
+++ b/demos/debounce/debounce4.js
@@ -0,0 +1,27 @@
+/**
+ * 函数传参
+ */
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction(e) {
+ container.innerHTML = count++;
+ console.log(e)
+};
+
+container.onmousemove = debounce(getUserAction, 1000);
+
+// 第三版
+function debounce(func, wait) {
+ var timeout;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ clearTimeout(timeout)
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+}
diff --git a/demos/debounce/debounce5.js b/demos/debounce/debounce5.js
new file mode 100644
index 00000000..5d430987
--- /dev/null
+++ b/demos/debounce/debounce5.js
@@ -0,0 +1,37 @@
+/**
+ * 添加immediate参数,让函数能够立刻执行,仅当事件停止触发n秒后,才能重新触发
+ */
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction(e) {
+ container.innerHTML = count++;
+};
+
+container.onmousemove = debounce(getUserAction, 1000, true);
+
+// 第四版
+function debounce(func, wait, immediate) {
+
+ var timeout;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+ }
+}
diff --git a/demos/debounce/debounce6.js b/demos/debounce/debounce6.js
new file mode 100644
index 00000000..bd1f0558
--- /dev/null
+++ b/demos/debounce/debounce6.js
@@ -0,0 +1,44 @@
+/**
+ * 添加函数返回值
+ */
+
+// 第五版
+function debounce(func, wait, immediate) {
+
+ var timeout, result;
+
+ return function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) result = func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+ return result;
+ }
+}
+
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction() {
+ container.innerHTML = count++;
+ return '111'
+};
+
+var result = debounce(getUserAction, 1000, true)
+container.onmousemove = function(){
+ var res = result();
+ console.log(res)
+}
\ No newline at end of file
diff --git a/demos/debounce/debounce7.js b/demos/debounce/debounce7.js
new file mode 100644
index 00000000..3fb06429
--- /dev/null
+++ b/demos/debounce/debounce7.js
@@ -0,0 +1,54 @@
+/**
+ * debounce函数可以取消
+ * @type {Number}
+ */
+var count = 1;
+var container = document.getElementById('container');
+
+function getUserAction(e) {
+ container.innerHTML = count++;
+};
+
+var setUseAction = debounce(getUserAction, 10000, true);
+
+container.onmousemove = setUseAction;
+
+document.getElementById("button").addEventListener('click', function(){
+ setUseAction.cancel();
+})
+
+// 第六版
+function debounce(func, wait, immediate) {
+
+ var timeout, result;
+
+ var debounced = function () {
+ var context = this;
+ var args = arguments;
+
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ // 如果已经执行过,不再执行
+ var callNow = !timeout;
+ timeout = setTimeout(function(){
+ timeout = null;
+ }, wait)
+ if (callNow) result = func.apply(context, args)
+ }
+ else {
+ timeout = setTimeout(function(){
+ func.apply(context, args)
+ }, wait);
+ }
+
+
+ return result;
+ };
+
+ debounced.cancel = function() {
+ clearTimeout(timeout);
+ timeout = null;
+ };
+
+ return debounced;
+}
\ No newline at end of file
diff --git a/demos/debounce/index.html b/demos/debounce/index.html
new file mode 100644
index 00000000..79691f96
--- /dev/null
+++ b/demos/debounce/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ debounce
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/debounce/underscore.js b/demos/debounce/underscore.js
new file mode 100644
index 00000000..b29332f9
--- /dev/null
+++ b/demos/debounce/underscore.js
@@ -0,0 +1,1548 @@
+// Underscore.js 1.8.3
+// http://underscorejs.org
+// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` in the browser, or `exports` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var
+ push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind,
+ nativeCreate = Object.create;
+
+ // Naked function reference for surrogate-prototype-swapping.
+ var Ctor = function(){};
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
+
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object.
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.8.3';
+
+ // Internal function that returns an efficient (for current engines) version
+ // of the passed-in callback, to be repeatedly applied in other Underscore
+ // functions.
+ var optimizeCb = function(func, context, argCount) {
+ if (context === void 0) return func;
+ switch (argCount == null ? 3 : argCount) {
+ case 1: return function(value) {
+ return func.call(context, value);
+ };
+ case 2: return function(value, other) {
+ return func.call(context, value, other);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(context, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(context, accumulator, value, index, collection);
+ };
+ }
+ return function() {
+ return func.apply(context, arguments);
+ };
+ };
+
+ // A mostly-internal function to generate callbacks that can be applied
+ // to each element in a collection, returning the desired result — either
+ // identity, an arbitrary callback, a property matcher, or a property accessor.
+ var cb = function(value, context, argCount) {
+ if (value == null) return _.identity;
+ if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+ if (_.isObject(value)) return _.matcher(value);
+ return _.property(value);
+ };
+ _.iteratee = function(value, context) {
+ return cb(value, context, Infinity);
+ };
+
+ // An internal function for creating assigner functions.
+ var createAssigner = function(keysFunc, undefinedOnly) {
+ return function(obj) {
+ var length = arguments.length;
+ if (length < 2 || obj == null) return obj;
+ for (var index = 1; index < length; index++) {
+ var source = arguments[index],
+ keys = keysFunc(source),
+ l = keys.length;
+ for (var i = 0; i < l; i++) {
+ var key = keys[i];
+ if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
+ }
+ }
+ return obj;
+ };
+ };
+
+ // An internal function for creating a new object that inherits from another.
+ var baseCreate = function(prototype) {
+ if (!_.isObject(prototype)) return {};
+ if (nativeCreate) return nativeCreate(prototype);
+ Ctor.prototype = prototype;
+ var result = new Ctor;
+ Ctor.prototype = null;
+ return result;
+ };
+
+ var property = function(key) {
+ return function(obj) {
+ return obj == null ? void 0 : obj[key];
+ };
+ };
+
+ // Helper for collection methods to determine whether a collection
+ // should be iterated as an array or as an object
+ // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+ // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+ var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+ var getLength = property('length');
+ var isArrayLike = function(collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+ };
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles raw objects in addition to array-likes. Treats all
+ // sparse array-likes as if they were dense.
+ _.each = _.forEach = function(obj, iteratee, context) {
+ iteratee = optimizeCb(iteratee, context);
+ var i, length;
+ if (isArrayLike(obj)) {
+ for (i = 0, length = obj.length; i < length; i++) {
+ iteratee(obj[i], i, obj);
+ }
+ } else {
+ var keys = _.keys(obj);
+ for (i = 0, length = keys.length; i < length; i++) {
+ iteratee(obj[keys[i]], keys[i], obj);
+ }
+ }
+ return obj;
+ };
+
+ // Return the results of applying the iteratee to each element.
+ _.map = _.collect = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ results = Array(length);
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ results[index] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
+
+ // Create a reducing function iterating left or right.
+ function createReduce(dir) {
+ // Optimized iterator function as using arguments.length
+ // in the main function will deoptimize the, see #1991.
+ function iterator(obj, iteratee, memo, keys, index, length) {
+ for (; index >= 0 && index < length; index += dir) {
+ var currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
+ }
+ return memo;
+ }
+
+ return function(obj, iteratee, memo, context) {
+ iteratee = optimizeCb(iteratee, context, 4);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ index = dir > 0 ? 0 : length - 1;
+ // Determine the initial value if none is provided.
+ if (arguments.length < 3) {
+ memo = obj[keys ? keys[index] : index];
+ index += dir;
+ }
+ return iterator(obj, iteratee, memo, keys, index, length);
+ };
+ }
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`.
+ _.reduce = _.foldl = _.inject = createReduce(1);
+
+ // The right-associative version of reduce, also known as `foldr`.
+ _.reduceRight = _.foldr = createReduce(-1);
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, predicate, context) {
+ var key;
+ if (isArrayLike(obj)) {
+ key = _.findIndex(obj, predicate, context);
+ } else {
+ key = _.findKey(obj, predicate, context);
+ }
+ if (key !== void 0 && key !== -1) return obj[key];
+ };
+
+ // Return all the elements that pass a truth test.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, predicate, context) {
+ var results = [];
+ predicate = cb(predicate, context);
+ _.each(obj, function(value, index, list) {
+ if (predicate(value, index, list)) results.push(value);
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, predicate, context) {
+ return _.filter(obj, _.negate(cb(predicate)), context);
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (!predicate(obj[currentKey], currentKey, obj)) return false;
+ }
+ return true;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Aliased as `any`.
+ _.some = _.any = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (predicate(obj[currentKey], currentKey, obj)) return true;
+ }
+ return false;
+ };
+
+ // Determine if the array or object contains a given item (using `===`).
+ // Aliased as `includes` and `include`.
+ _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+ return _.indexOf(obj, item, fromIndex) >= 0;
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ var isFunc = _.isFunction(method);
+ return _.map(obj, function(value) {
+ var func = isFunc ? method : value[method];
+ return func == null ? func : func.apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, _.property(key));
+ };
+
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // containing specific `key:value` pairs.
+ _.where = function(obj, attrs) {
+ return _.filter(obj, _.matcher(attrs));
+ };
+
+ // Convenience version of a common use case of `find`: getting the first object
+ // containing specific `key:value` pairs.
+ _.findWhere = function(obj, attrs) {
+ return _.find(obj, _.matcher(attrs));
+ };
+
+ // Return the maximum element (or element-based computation).
+ _.max = function(obj, iteratee, context) {
+ var result = -Infinity, lastComputed = -Infinity,
+ value, computed;
+ if (iteratee == null && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
+ }
+ return result;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iteratee, context) {
+ var result = Infinity, lastComputed = Infinity,
+ value, computed;
+ if (iteratee == null && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(value, index, list) {
+ computed = iteratee(value, index, list);
+ if (computed < lastComputed || computed === Infinity && result === Infinity) {
+ result = value;
+ lastComputed = computed;
+ }
+ });
+ }
+ return result;
+ };
+
+ // Shuffle a collection, using the modern version of the
+ // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+ _.shuffle = function(obj) {
+ var set = isArrayLike(obj) ? obj : _.values(obj);
+ var length = set.length;
+ var shuffled = Array(length);
+ for (var index = 0, rand; index < length; index++) {
+ rand = _.random(0, index);
+ if (rand !== index) shuffled[index] = shuffled[rand];
+ shuffled[rand] = set[index];
+ }
+ return shuffled;
+ };
+
+ // Sample **n** random values from a collection.
+ // If **n** is not specified, returns a single random element.
+ // The internal `guard` argument allows it to work with `map`.
+ _.sample = function(obj, n, guard) {
+ if (n == null || guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ return obj[_.random(obj.length - 1)];
+ }
+ return _.shuffle(obj).slice(0, Math.max(0, n));
+ };
+
+ // Sort the object's values by a criterion produced by an iteratee.
+ _.sortBy = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value: value,
+ index: index,
+ criteria: iteratee(value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index - right.index;
+ }), 'value');
+ };
+
+ // An internal function used for aggregate "group by" operations.
+ var group = function(behavior) {
+ return function(obj, iteratee, context) {
+ var result = {};
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(value, index) {
+ var key = iteratee(value, index, obj);
+ behavior(result, value, key);
+ });
+ return result;
+ };
+ };
+
+ // Groups the object's values by a criterion. Pass either a string attribute
+ // to group by, or a function that returns the criterion.
+ _.groupBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+ });
+
+ // Indexes the object's values by a criterion, similar to `groupBy`, but for
+ // when you know that your index values will be unique.
+ _.indexBy = group(function(result, value, key) {
+ result[key] = value;
+ });
+
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = group(function(result, value, key) {
+ if (_.has(result, key)) result[key]++; else result[key] = 1;
+ });
+
+ // Safely create a real, live array from anything iterable.
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (isArrayLike(obj)) return _.map(obj, _.identity);
+ return _.values(obj);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ if (obj == null) return 0;
+ return isArrayLike(obj) ? obj.length : _.keys(obj).length;
+ };
+
+ // Split a collection into two arrays: one whose elements all satisfy the given
+ // predicate, and one whose elements all do not satisfy the predicate.
+ _.partition = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var pass = [], fail = [];
+ _.each(obj, function(value, key, obj) {
+ (predicate(value, key, obj) ? pass : fail).push(value);
+ });
+ return [pass, fail];
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
+ if (array == null) return void 0;
+ if (n == null || guard) return array[0];
+ return _.initial(array, array.length - n);
+ };
+
+ // Returns everything but the last entry of the array. Especially useful on
+ // the arguments object. Passing **n** will return all the values in
+ // the array, excluding the last N.
+ _.initial = function(array, n, guard) {
+ return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+ };
+
+ // Get the last element of an array. Passing **n** will return the last N
+ // values in the array.
+ _.last = function(array, n, guard) {
+ if (array == null) return void 0;
+ if (n == null || guard) return array[array.length - 1];
+ return _.rest(array, Math.max(0, array.length - n));
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array.
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, n == null || guard ? 1 : n);
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, _.identity);
+ };
+
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, strict, startIndex) {
+ var output = [], idx = 0;
+ for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
+ var value = input[i];
+ if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
+ //flatten current level of array or arguments object
+ if (!shallow) value = flatten(value, shallow, strict);
+ var j = 0, len = value.length;
+ output.length += len;
+ while (j < len) {
+ output[idx++] = value[j++];
+ }
+ } else if (!strict) {
+ output[idx++] = value;
+ }
+ }
+ return output;
+ };
+
+ // Flatten out an array, either recursively (by default), or just one level.
+ _.flatten = function(array, shallow) {
+ return flatten(array, shallow, false);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ return _.difference(array, slice.call(arguments, 1));
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+ if (!_.isBoolean(isSorted)) {
+ context = iteratee;
+ iteratee = isSorted;
+ isSorted = false;
+ }
+ if (iteratee != null) iteratee = cb(iteratee, context);
+ var result = [];
+ var seen = [];
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var value = array[i],
+ computed = iteratee ? iteratee(value, i, array) : value;
+ if (isSorted) {
+ if (!i || seen !== computed) result.push(value);
+ seen = computed;
+ } else if (iteratee) {
+ if (!_.contains(seen, computed)) {
+ seen.push(computed);
+ result.push(value);
+ }
+ } else if (!_.contains(result, value)) {
+ result.push(value);
+ }
+ }
+ return result;
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = function() {
+ return _.uniq(flatten(arguments, true, true));
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersection = function(array) {
+ var result = [];
+ var argsLength = arguments.length;
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var item = array[i];
+ if (_.contains(result, item)) continue;
+ for (var j = 1; j < argsLength; j++) {
+ if (!_.contains(arguments[j], item)) break;
+ }
+ if (j === argsLength) result.push(item);
+ }
+ return result;
+ };
+
+ // Take the difference between one array and a number of other arrays.
+ // Only the elements present in just the first array will remain.
+ _.difference = function(array) {
+ var rest = flatten(arguments, true, true, 1);
+ return _.filter(array, function(value){
+ return !_.contains(rest, value);
+ });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ return _.unzip(arguments);
+ };
+
+ // Complement of _.zip. Unzip accepts an array of arrays and groups
+ // each array's elements on shared indices
+ _.unzip = function(array) {
+ var length = array && _.max(array, getLength).length || 0;
+ var result = Array(length);
+
+ for (var index = 0; index < length; index++) {
+ result[index] = _.pluck(array, index);
+ }
+ return result;
+ };
+
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values.
+ _.object = function(list, values) {
+ var result = {};
+ for (var i = 0, length = getLength(list); i < length; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
+ // Generator function to create the findIndex and findLastIndex functions
+ function createPredicateIndexFinder(dir) {
+ return function(array, predicate, context) {
+ predicate = cb(predicate, context);
+ var length = getLength(array);
+ var index = dir > 0 ? 0 : length - 1;
+ for (; index >= 0 && index < length; index += dir) {
+ if (predicate(array[index], index, array)) return index;
+ }
+ return -1;
+ };
+ }
+
+ // Returns the first index on an array-like that passes a predicate test
+ _.findIndex = createPredicateIndexFinder(1);
+ _.findLastIndex = createPredicateIndexFinder(-1);
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iteratee, context) {
+ iteratee = cb(iteratee, context, 1);
+ var value = iteratee(obj);
+ var low = 0, high = getLength(array);
+ while (low < high) {
+ var mid = Math.floor((low + high) / 2);
+ if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+ }
+ return low;
+ };
+
+ // Generator function to create the indexOf and lastIndexOf functions
+ function createIndexFinder(dir, predicateFind, sortedIndex) {
+ return function(array, item, idx) {
+ var i = 0, length = getLength(array);
+ if (typeof idx == 'number') {
+ if (dir > 0) {
+ i = idx >= 0 ? idx : Math.max(idx + length, i);
+ } else {
+ length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+ }
+ } else if (sortedIndex && idx && length) {
+ idx = sortedIndex(array, item);
+ return array[idx] === item ? idx : -1;
+ }
+ if (item !== item) {
+ idx = predicateFind(slice.call(array, i, length), _.isNaN);
+ return idx >= 0 ? idx + i : -1;
+ }
+ for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+ if (array[idx] === item) return idx;
+ }
+ return -1;
+ };
+ }
+
+ // Return the position of the first occurrence of an item in an array,
+ // or -1 if the item is not included in the array.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
+ _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (stop == null) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = step || 1;
+
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
+ var range = Array(length);
+
+ for (var idx = 0; idx < length; idx++, start += step) {
+ range[idx] = start;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Determines whether to execute a function as a constructor
+ // or a normal function with the provided arguments
+ var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
+ if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+ var self = baseCreate(sourceFunc.prototype);
+ var result = sourceFunc.apply(self, args);
+ if (_.isObject(result)) return result;
+ return self;
+ };
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+ // available.
+ _.bind = function(func, context) {
+ if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+ var args = slice.call(arguments, 2);
+ var bound = function() {
+ return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
+ };
+ return bound;
+ };
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context. _ acts
+ // as a placeholder, allowing any combination of arguments to be pre-filled.
+ _.partial = function(func) {
+ var boundArgs = slice.call(arguments, 1);
+ var bound = function() {
+ var position = 0, length = boundArgs.length;
+ var args = Array(length);
+ for (var i = 0; i < length; i++) {
+ args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
+ }
+ while (position < arguments.length) args.push(arguments[position++]);
+ return executeBound(func, bound, this, this, args);
+ };
+ return bound;
+ };
+
+ // Bind a number of an object's methods to that object. Remaining arguments
+ // are the method names to be bound. Useful for ensuring that all callbacks
+ // defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var i, length = arguments.length, key;
+ if (length <= 1) throw new Error('bindAll must be passed function names');
+ for (i = 1; i < length; i++) {
+ key = arguments[i];
+ obj[key] = _.bind(obj[key], obj);
+ }
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memoize = function(key) {
+ var cache = memoize.cache;
+ var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+ if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+ return cache[address];
+ };
+ memoize.cache = {};
+ return memoize;
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){
+ return func.apply(null, args);
+ }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = _.partial(_.delay, _, 1);
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time. Normally, the throttled function will run
+ // as much as it can, without ever going more than once per `wait` duration;
+ // but if you'd like to disable the execution on the leading edge, pass
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
+ _.throttle = function(func, wait, options) {
+ var context, args, result;
+ var timeout = null;
+ 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;
+ };
+ return 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;
+ };
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, args, context, timestamp, result;
+
+ var later = function() {
+ var last = _.now() - timestamp;
+
+ if (last < wait && last >= 0) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ }
+ }
+ };
+
+ return function() {
+ context = this;
+ args = arguments;
+ timestamp = _.now();
+ var callNow = immediate && !timeout;
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+
+ return result;
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return _.partial(wrapper, func);
+ };
+
+ // Returns a negated version of the passed-in predicate.
+ _.negate = function(predicate) {
+ return function() {
+ return !predicate.apply(this, arguments);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var args = arguments;
+ var start = args.length - 1;
+ return function() {
+ var i = start;
+ var result = args[start].apply(this, arguments);
+ while (i--) result = args[i].call(this, result);
+ return result;
+ };
+ };
+
+ // Returns a function that will only be executed on and after the Nth call.
+ _.after = function(times, func) {
+ return function() {
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ };
+
+ // Returns a function that will only be executed up to (but not including) the Nth call.
+ _.before = function(times, func) {
+ var memo;
+ return function() {
+ if (--times > 0) {
+ memo = func.apply(this, arguments);
+ }
+ if (times <= 1) func = null;
+ return memo;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = _.partial(_.before, 2);
+
+ // Object Functions
+ // ----------------
+
+ // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+ var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+ var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+ 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+ function collectNonEnumProps(obj, keys) {
+ var nonEnumIdx = nonEnumerableProps.length;
+ var constructor = obj.constructor;
+ var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
+
+ // Constructor is a special case.
+ var prop = 'constructor';
+ if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+ while (nonEnumIdx--) {
+ prop = nonEnumerableProps[nonEnumIdx];
+ if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+ keys.push(prop);
+ }
+ }
+ }
+
+ // Retrieve the names of an object's own properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ if (nativeKeys) return nativeKeys(obj);
+ var keys = [];
+ for (var key in obj) if (_.has(obj, key)) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
+
+ // Retrieve all the property names of an object.
+ _.allKeys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ var keys = [];
+ for (var key in obj) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var values = Array(length);
+ for (var i = 0; i < length; i++) {
+ values[i] = obj[keys[i]];
+ }
+ return values;
+ };
+
+ // Returns the results of applying the iteratee to each element of the object
+ // In contrast to _.map it returns an object
+ _.mapObject = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = _.keys(obj),
+ length = keys.length,
+ results = {},
+ currentKey;
+ for (var index = 0; index < length; index++) {
+ currentKey = keys[index];
+ results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ _.pairs = function(obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var pairs = Array(length);
+ for (var i = 0; i < length; i++) {
+ pairs[i] = [keys[i], obj[keys[i]]];
+ }
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ var keys = _.keys(obj);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ result[obj[keys[i]]] = keys[i];
+ }
+ return result;
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = createAssigner(_.allKeys);
+
+ // Assigns a given object with all the own properties in the passed-in object(s)
+ // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+ _.extendOwn = _.assign = createAssigner(_.keys);
+
+ // Returns the first key on an object that passes a predicate test
+ _.findKey = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = _.keys(obj), key;
+ for (var i = 0, length = keys.length; i < length; i++) {
+ key = keys[i];
+ if (predicate(obj[key], key, obj)) return key;
+ }
+ };
+
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(object, oiteratee, context) {
+ var result = {}, obj = object, iteratee, keys;
+ if (obj == null) return result;
+ if (_.isFunction(oiteratee)) {
+ keys = _.allKeys(obj);
+ iteratee = optimizeCb(oiteratee, context);
+ } else {
+ keys = flatten(arguments, false, false, 1);
+ iteratee = function(value, key, obj) { return key in obj; };
+ obj = Object(obj);
+ }
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i];
+ var value = obj[key];
+ if (iteratee(value, key, obj)) result[key] = value;
+ }
+ return result;
+ };
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = function(obj, iteratee, context) {
+ if (_.isFunction(iteratee)) {
+ iteratee = _.negate(iteratee);
+ } else {
+ var keys = _.map(flatten(arguments, false, false, 1), String);
+ iteratee = function(value, key) {
+ return !_.contains(keys, key);
+ };
+ }
+ return _.pick(obj, iteratee, context);
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = createAssigner(_.allKeys, true);
+
+ // Creates an object that inherits from the given prototype object.
+ // If additional properties are provided then they will be added to the
+ // created object.
+ _.create = function(prototype, props) {
+ var result = baseCreate(prototype);
+ if (props) _.extendOwn(result, props);
+ return result;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Returns whether an object has a given set of `key:value` pairs.
+ _.isMatch = function(object, attrs) {
+ var keys = _.keys(attrs), length = keys.length;
+ if (object == null) return !length;
+ var obj = Object(object);
+ for (var i = 0; i < length; i++) {
+ var key = keys[i];
+ if (attrs[key] !== obj[key] || !(key in obj)) return false;
+ }
+ return true;
+ };
+
+
+ // Internal recursive comparison function for `isEqual`.
+ var eq = function(a, b, aStack, bStack) {
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) return a !== 0 || 1 / a === 1 / b;
+ // A strict comparison is necessary because `null == undefined`.
+ if (a == null || b == null) return a === b;
+ // Unwrap any wrapped objects.
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className !== toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+ case '[object RegExp]':
+ // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return '' + a === '' + b;
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive.
+ // Object(NaN) is equivalent to NaN
+ if (+a !== +a) return +b !== +b;
+ // An `egal` comparison is performed for other numeric values.
+ return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a === +b;
+ }
+
+ var areArrays = className === '[object Array]';
+ if (!areArrays) {
+ if (typeof a != 'object' || typeof b != 'object') return false;
+
+ // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+ _.isFunction(bCtor) && bCtor instanceof bCtor)
+ && ('constructor' in a && 'constructor' in b)) {
+ return false;
+ }
+ }
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+ // Initializing stack of traversed objects.
+ // It's done here since we only need them for objects and arrays comparison.
+ aStack = aStack || [];
+ bStack = bStack || [];
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] === a) return bStack[length] === b;
+ }
+
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+
+ // Recursively compare objects and arrays.
+ if (areArrays) {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ length = a.length;
+ if (length !== b.length) return false;
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (length--) {
+ if (!eq(a[length], b[length], aStack, bStack)) return false;
+ }
+ } else {
+ // Deep compare objects.
+ var keys = _.keys(a), key;
+ length = keys.length;
+ // Ensure that both objects contain the same number of properties before comparing deep equality.
+ if (_.keys(b).length !== length) return false;
+ while (length--) {
+ // Deep compare each member
+ key = keys[length];
+ if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+ return true;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ return eq(a, b);
+ };
+
+ // Is a given array, string, or object empty?
+ // An "empty" object has no enumerable own-properties.
+ _.isEmpty = function(obj) {
+ if (obj == null) return true;
+ if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+ return _.keys(obj).length === 0;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
+ };
+
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
+ _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) === '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE < 9), where
+ // there isn't any inspectable "Arguments" type.
+ if (!_.isArguments(arguments)) {
+ _.isArguments = function(obj) {
+ return _.has(obj, 'callee');
+ };
+ }
+
+ // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
+ // IE 11 (#1621), and in Safari 8 (#1929).
+ if (typeof /./ != 'function' && typeof Int8Array != 'object') {
+ _.isFunction = function(obj) {
+ return typeof obj == 'function' || false;
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return isFinite(obj) && !isNaN(parseFloat(obj));
+ };
+
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && obj !== +obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
+ _.has = function(obj, key) {
+ return obj != null && hasOwnProperty.call(obj, key);
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iteratees.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Predicate-generating functions. Often useful outside of Underscore.
+ _.constant = function(value) {
+ return function() {
+ return value;
+ };
+ };
+
+ _.noop = function(){};
+
+ _.property = property;
+
+ // Generates a function for a given object that returns a given property.
+ _.propertyOf = function(obj) {
+ return obj == null ? function(){} : function(key) {
+ return obj[key];
+ };
+ };
+
+ // Returns a predicate for checking whether an object has a given set of
+ // `key:value` pairs.
+ _.matcher = _.matches = function(attrs) {
+ attrs = _.extendOwn({}, attrs);
+ return function(obj) {
+ return _.isMatch(obj, attrs);
+ };
+ };
+
+ // Run a function **n** times.
+ _.times = function(n, iteratee, context) {
+ var accum = Array(Math.max(0, n));
+ iteratee = optimizeCb(iteratee, context, 1);
+ for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+ return accum;
+ };
+
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + Math.floor(Math.random() * (max - min + 1));
+ };
+
+ // A (possibly faster) way to get the current timestamp as an integer.
+ _.now = Date.now || function() {
+ return new Date().getTime();
+ };
+
+ // List of HTML entities for escaping.
+ var escapeMap = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '`': '`'
+ };
+ var unescapeMap = _.invert(escapeMap);
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ var createEscaper = function(map) {
+ var escaper = function(match) {
+ return map[match];
+ };
+ // Regexes for identifying a key that needs to be escaped
+ var source = '(?:' + _.keys(map).join('|') + ')';
+ var testRegexp = RegExp(source);
+ var replaceRegexp = RegExp(source, 'g');
+ return function(string) {
+ string = string == null ? '' : '' + string;
+ return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+ };
+ };
+ _.escape = createEscaper(escapeMap);
+ _.unescape = createEscaper(unescapeMap);
+
+ // If the value of the named `property` is a function then invoke it with the
+ // `object` as context; otherwise, return it.
+ _.result = function(object, property, fallback) {
+ var value = object == null ? void 0 : object[property];
+ if (value === void 0) {
+ value = fallback;
+ }
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = ++idCounter + '';
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g,
+ escape : /<%-([\s\S]+?)%>/g
+ };
+
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+ var escapeChar = function(match) {
+ return '\\' + escapes[match];
+ };
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ // NB: `oldSettings` only exists for backwards compatibility.
+ _.template = function(text, settings, oldSettings) {
+ if (!settings && oldSettings) settings = oldSettings;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset).replace(escaper, escapeChar);
+ index = offset + match.length;
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ } else if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ } else if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+
+ // Adobe VMs need the match returned to produce the correct offest.
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + 'return __p;\n';
+
+ try {
+ var render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled source as a convenience for precompilation.
+ var argument = settings.variable || 'obj';
+ template.source = 'function(' + argument + '){\n' + source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function. Start chaining a wrapped Underscore object.
+ _.chain = function(obj) {
+ var instance = _(obj);
+ instance._chain = true;
+ return instance;
+ };
+
+ // OOP
+ // ---------------
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(instance, obj) {
+ return instance._chain ? _(obj).chain() : obj;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ _.each(_.functions(obj), function(name) {
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result(this, func.apply(_, args));
+ };
+ });
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+ return result(this, obj);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ _.each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ return result(this, method.apply(this._wrapped, arguments));
+ };
+ });
+
+ // Extracts the result from a wrapped and chained object.
+ _.prototype.value = function() {
+ return this._wrapped;
+ };
+
+ // Provide unwrapping proxy for some methods used in engine operations
+ // such as arithmetic and JSON stringification.
+ _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
+
+ _.prototype.toString = function() {
+ return '' + this._wrapped;
+ };
+
+ // AMD registration happens at the end for compatibility with AMD loaders
+ // that may not enforce next-turn semantics on modules. Even though general
+ // practice for AMD registration is to be anonymous, underscore registers
+ // as a named module because, like jQuery, it is a base library that is
+ // popular enough to be bundled in a third party lib, but not be part of
+ // an AMD load request. Those cases could generate an error when an
+ // anonymous define() is called outside of a loader request.
+ if (typeof define === 'function' && define.amd) {
+ define('underscore', [], function() {
+ return _;
+ });
+ }
+}.call(this));
diff --git a/demos/node-vm/index.js b/demos/node-vm/index.js
new file mode 100644
index 00000000..a9564449
--- /dev/null
+++ b/demos/node-vm/index.js
@@ -0,0 +1,31 @@
+/**
+ * http://www.alloyteam.com/2015/04/xiang-jie-nodejs-di-vm-mo-kuai/
+ */
+var vm = require("vm");
+var util = require("util");
+
+var window = {
+ p: 2,
+ vm: vm,
+ console: console,
+ require: require
+};
+
+var p = 5;
+
+global.p = 11;
+
+vm.createContext(window);
+
+// global是 undefined
+// vm.runInContext('p = 3;console.log(global);', window);
+
+// 报错 window is not defined
+// vm.runInContext('p = 3;console.log(window);', window);
+
+// this 是有值的
+vm.runInContext('p = 3;console.log(this);', window);
+
+
+// console.log(window.p);// 被改变为3
+// console.log(util.inspect(window));
diff --git a/demos/qunit/index.html b/demos/qunit/index.html
new file mode 100755
index 00000000..3d8bc010
--- /dev/null
+++ b/demos/qunit/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ Set 的模拟实现
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/qunit/polyfill-set.js b/demos/qunit/polyfill-set.js
new file mode 100644
index 00000000..3e008261
--- /dev/null
+++ b/demos/qunit/polyfill-set.js
@@ -0,0 +1,117 @@
+(function(global) {
+
+ var NaNSymbol = Symbol('NaN');
+
+ var encodeVal = function(value) {
+ return value !== value ? NaNSymbol : value;
+ }
+
+ var decodeVal = function(value) {
+ return (value === NaNSymbol) ? NaN : value;
+ }
+
+ var makeIterator = function(array, iterator) {
+ var nextIndex = 0;
+
+ // new Set(new Set()) 会调用这里
+ var obj = {
+ next: function() {
+ return nextIndex < array.length ? { value: iterator(array[nextIndex++]), done: false } : { value: void 0, done: true };
+ }
+ };
+
+ // [...set.keys()] 会调用这里
+ obj[Symbol.iterator] = function() {
+ return obj
+ }
+
+ return obj
+ }
+
+ function forOf(obj, cb) {
+ let iterable, result;
+
+ if (typeof obj[Symbol.iterator] !== "function") throw new TypeError(obj + " is not iterable");
+ if (typeof cb !== "function") throw new TypeError('cb must be callable');
+
+ iterable = obj[Symbol.iterator]();
+
+ result = iterable.next();
+ while (!result.done) {
+ cb(result.value);
+ result = iterable.next();
+ }
+ }
+
+ function Set(data) {
+ this._values = [];
+ this.size = 0;
+
+ if (data) {
+ forOf(data, (item) => {
+ this.add(item);
+ })
+ }
+
+ }
+
+ Set.prototype['add'] = function(value) {
+ value = encodeVal(value);
+ if (this._values.indexOf(value) == -1) {
+ this._values.push(value);
+ ++this.size;
+ }
+ return this;
+ }
+
+ Set.prototype['has'] = function(value) {
+ return (this._values.indexOf(encodeVal(value)) !== -1);
+ }
+
+ Set.prototype['delete'] = function(value) {
+ var idx = this._values.indexOf(encodeVal(value));
+ if (idx == -1) return false;
+ this._values.splice(idx, 1);
+ --this.size;
+ return true;
+ }
+
+ Set.prototype['clear'] = function(value) {
+ this._values = [];
+ this.size = 0;
+ }
+
+ Set.prototype['forEach'] = function(callbackFn, thisArg) {
+ thisArg = thisArg || global;
+ for (var i = 0; i < this._values.length; i++) {
+ callbackFn.call(thisArg, this._values[i], this._values[i], this);
+ }
+ }
+
+ Set.prototype['values'] = Set.prototype['keys'] = function() {
+ return makeIterator(this._values, function(value) { return decodeVal(value); });
+ }
+
+ Set.prototype['entries'] = function() {
+ return makeIterator(this._values, function(value) { return [decodeVal(value), decodeVal(value)]; });
+ }
+
+ Set.prototype[Symbol.iterator] = function(){
+ return this.values();
+ }
+
+ Set.prototype['forEach'] = function(callbackFn, thisArg) {
+ thisArg = thisArg || global;
+ var iterator = this.entries();
+
+ forOf(iterator, (item) => {
+ callbackFn.call(thisArg, item[1], item[0], this);
+ })
+ }
+
+ Set.length = 0;
+
+ global.Set = Set;
+
+})(this);
+
diff --git a/demos/qunit/qunit-2.4.0.css b/demos/qunit/qunit-2.4.0.css
new file mode 100755
index 00000000..47492221
--- /dev/null
+++ b/demos/qunit/qunit-2.4.0.css
@@ -0,0 +1,436 @@
+/*!
+ * QUnit 2.4.0
+ * https://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2017-07-08T15:20Z
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header (excluding toolbar) */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699A4;
+ background-color: #0D3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: 400;
+
+ border-radius: 5px 5px 0 0;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #C2CCD1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #FFF;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-filteredTest {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #366097;
+ background-color: #F4FF77;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #FFF;
+ background-color: #2B81AF;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Toolbar */
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #5E740B;
+ background-color: #EEE;
+}
+
+#qunit-testrunner-toolbar .clearfix {
+ height: 0;
+ clear: both;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+}
+
+#qunit-testrunner-toolbar input[type=checkbox],
+#qunit-testrunner-toolbar input[type=radio] {
+ margin: 3px;
+ vertical-align: -2px;
+}
+
+#qunit-testrunner-toolbar input[type=text] {
+ box-sizing: border-box;
+ height: 1.6em;
+}
+
+.qunit-url-config,
+.qunit-filter,
+#qunit-modulefilter {
+ display: inline-block;
+ line-height: 2.1em;
+}
+
+.qunit-filter,
+#qunit-modulefilter {
+ float: right;
+ position: relative;
+ margin-left: 1em;
+}
+
+.qunit-url-config label {
+ margin-right: 0.5em;
+}
+
+#qunit-modulefilter-search {
+ box-sizing: border-box;
+ width: 400px;
+}
+
+#qunit-modulefilter-search-container:after {
+ position: absolute;
+ right: 0.3em;
+ content: "\25bc";
+ color: black;
+}
+
+#qunit-modulefilter-dropdown {
+ /* align with #qunit-modulefilter-search */
+ box-sizing: border-box;
+ width: 400px;
+ position: absolute;
+ right: 0;
+ top: 50%;
+ margin-top: 0.8em;
+
+ border: 1px solid #D3D3D3;
+ border-top: none;
+ border-radius: 0 0 .25em .25em;
+ color: #000;
+ background-color: #F5F5F5;
+ z-index: 99;
+}
+
+#qunit-modulefilter-dropdown a {
+ color: inherit;
+ text-decoration: none;
+}
+
+#qunit-modulefilter-dropdown .clickable.checked {
+ font-weight: bold;
+ color: #000;
+ background-color: #D2E0E6;
+}
+
+#qunit-modulefilter-dropdown .clickable:hover {
+ color: #FFF;
+ background-color: #0D3349;
+}
+
+#qunit-modulefilter-actions {
+ display: block;
+ overflow: auto;
+
+ /* align with #qunit-modulefilter-dropdown-list */
+ font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
+ box-sizing: border-box;
+ max-height: 2.8em;
+ display: block;
+ padding: 0.4em;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
+ float: right;
+ font: inherit;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
+ /* insert padding to align with checkbox margins */
+ padding-left: 3px;
+}
+
+#qunit-modulefilter-dropdown-list {
+ max-height: 200px;
+ overflow-y: auto;
+ margin: 0;
+ border-top: 2px groove threedhighlight;
+ padding: 0.4em 0 0;
+ font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown-list li {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#qunit-modulefilter-dropdown-list .clickable {
+ display: block;
+ padding-left: 0.15em;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 1em 0.4em 1em;
+ border-bottom: 1px solid #FFF;
+ list-style-position: inside;
+}
+
+#qunit-tests > li {
+ display: none;
+}
+
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped,
+#qunit-tests li.aborted {
+ display: list-item;
+}
+
+#qunit-tests.hidepass {
+ position: relative;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass:not(.todo) {
+ visibility: hidden;
+ position: absolute;
+ width: 0;
+ height: 0;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li.skipped strong {
+ cursor: default;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #C2CCD1;
+ text-decoration: none;
+}
+
+#qunit-tests li p a {
+ padding: 0.25em;
+ color: #6B6464;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #FFF;
+
+ border-radius: 5px;
+}
+
+.qunit-source {
+ margin: 0.6em 0 0.3em;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: 0.2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 0.5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ color: #374E0C;
+ background-color: #E0F2BE;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ color: #500;
+ background-color: #FFCACA;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: #000; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #FFF;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3C510C;
+ background-color: #FFF;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #FFF;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+}
+
+#qunit-tests .fail { color: #000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: #008000; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/*** Aborted tests */
+#qunit-tests .aborted { color: #000; background-color: orange; }
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+ background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-todo-label,
+#qunit-tests .qunit-skipped-label {
+ background-color: #F4FF77;
+ display: inline-block;
+ font-style: normal;
+ color: #366097;
+ line-height: 1.8em;
+ padding: 0 0.5em;
+ margin: -0.4em 0.4em -0.4em 0;
+}
+
+#qunit-tests .qunit-todo-label {
+ background-color: #EEE;
+}
+
+/** Result */
+
+#qunit-testresult {
+ color: #2B81AF;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid #FFF;
+}
+#qunit-testresult .clearfix {
+ height: 0;
+ clear: both;
+}
+#qunit-testresult .module-name {
+ font-weight: 700;
+}
+#qunit-testresult-display {
+ padding: 0.5em 1em 0.5em 1em;
+ width: 85%;
+ float:left;
+}
+#qunit-testresult-controls {
+ padding: 0.5em 1em 0.5em 1em;
+ width: 10%;
+ float:left;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/demos/qunit/qunit-2.4.0.js b/demos/qunit/qunit-2.4.0.js
new file mode 100755
index 00000000..bb8f31d6
--- /dev/null
+++ b/demos/qunit/qunit-2.4.0.js
@@ -0,0 +1,5048 @@
+/*!
+ * QUnit 2.4.0
+ * https://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2017-07-08T15:20Z
+ */
+(function (global$1) {
+ 'use strict';
+
+ global$1 = global$1 && 'default' in global$1 ? global$1['default'] : global$1;
+
+ var window = global$1.window;
+ var self$1 = global$1.self;
+ var console = global$1.console;
+ var setTimeout = global$1.setTimeout;
+ var clearTimeout = global$1.clearTimeout;
+
+ var document = window && window.document;
+ var navigator = window && window.navigator;
+
+ var localSessionStorage = function () {
+ var x = "qunit-test-string";
+ try {
+ global$1.sessionStorage.setItem(x, x);
+ global$1.sessionStorage.removeItem(x);
+ return global$1.sessionStorage;
+ } catch (e) {
+ return undefined;
+ }
+ }();
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+ } : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+
+
+
+
+
+
+
+
+
+
+
+ var classCallCheck = function (instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ };
+
+ var createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var toConsumableArray = function (arr) {
+ if (Array.isArray(arr)) {
+ for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
+
+ return arr2;
+ } else {
+ return Array.from(arr);
+ }
+ };
+
+ var toString = Object.prototype.toString;
+ var hasOwn = Object.prototype.hasOwnProperty;
+ var now = Date.now || function () {
+ return new Date().getTime();
+ };
+
+ var defined = {
+ document: window && window.document !== undefined,
+ setTimeout: setTimeout !== undefined
+ };
+
+ // Returns a new Array with the elements that are in a but not in b
+ function diff(a, b) {
+ var i,
+ j,
+ result = a.slice();
+
+ for (i = 0; i < result.length; i++) {
+ for (j = 0; j < b.length; j++) {
+ if (result[i] === b[j]) {
+ result.splice(i, 1);
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Determines whether an element exists in a given array or not.
+ *
+ * @method inArray
+ * @param {Any} elem
+ * @param {Array} array
+ * @return {Boolean}
+ */
+ function inArray(elem, array) {
+ return array.indexOf(elem) !== -1;
+ }
+
+ /**
+ * Makes a clone of an object using only Array or Object as base,
+ * and copies over the own enumerable properties.
+ *
+ * @param {Object} obj
+ * @return {Object} New object with only the own properties (recursively).
+ */
+ function objectValues(obj) {
+ var key,
+ val,
+ vals = is("array", obj) ? [] : {};
+ for (key in obj) {
+ if (hasOwn.call(obj, key)) {
+ val = obj[key];
+ vals[key] = val === Object(val) ? objectValues(val) : val;
+ }
+ }
+ return vals;
+ }
+
+ function extend(a, b, undefOnly) {
+ for (var prop in b) {
+ if (hasOwn.call(b, prop)) {
+ if (b[prop] === undefined) {
+ delete a[prop];
+ } else if (!(undefOnly && typeof a[prop] !== "undefined")) {
+ a[prop] = b[prop];
+ }
+ }
+ }
+
+ return a;
+ }
+
+ function objectType(obj) {
+ if (typeof obj === "undefined") {
+ return "undefined";
+ }
+
+ // Consider: typeof null === object
+ if (obj === null) {
+ return "null";
+ }
+
+ var match = toString.call(obj).match(/^\[object\s(.*)\]$/),
+ type = match && match[1];
+
+ switch (type) {
+ case "Number":
+ if (isNaN(obj)) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Set":
+ case "Map":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ case "Symbol":
+ return type.toLowerCase();
+ default:
+ return typeof obj === "undefined" ? "undefined" : _typeof(obj);
+ }
+ }
+
+ // Safe object type checking
+ function is(type, obj) {
+ return objectType(obj) === type;
+ }
+
+ // Based on Java's String.hashCode, a simple but not
+ // rigorously collision resistant hashing function
+ function generateHash(module, testName) {
+ var str = module + "\x1C" + testName;
+ var hash = 0;
+
+ for (var i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash |= 0;
+ }
+
+ // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+ // strictly necessary but increases user understanding that the id is a SHA-like hash
+ var hex = (0x100000000 + hash).toString(16);
+ if (hex.length < 8) {
+ hex = "0000000" + hex;
+ }
+
+ return hex.slice(-8);
+ }
+
+ // Test for equality any JavaScript type.
+ // Authors: Philippe Rathé , David Chan
+ var equiv = (function () {
+
+ // Value pairs queued for comparison. Used for breadth-first processing order, recursion
+ // detection and avoiding repeated comparison (see below for details).
+ // Elements are { a: val, b: val }.
+ var pairs = [];
+
+ var getProto = Object.getPrototypeOf || function (obj) {
+ return obj.__proto__;
+ };
+
+ function useStrictEquality(a, b) {
+
+ // This only gets called if a and b are not strict equal, and is used to compare on
+ // the primitive values inside object wrappers. For example:
+ // `var i = 1;`
+ // `var j = new Number(1);`
+ // Neither a nor b can be null, as a !== b and they have the same type.
+ if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
+ a = a.valueOf();
+ }
+ if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
+ b = b.valueOf();
+ }
+
+ return a === b;
+ }
+
+ function compareConstructors(a, b) {
+ var protoA = getProto(a);
+ var protoB = getProto(b);
+
+ // Comparing constructors is more strict than using `instanceof`
+ if (a.constructor === b.constructor) {
+ return true;
+ }
+
+ // Ref #851
+ // If the obj prototype descends from a null constructor, treat it
+ // as a null prototype.
+ if (protoA && protoA.constructor === null) {
+ protoA = null;
+ }
+ if (protoB && protoB.constructor === null) {
+ protoB = null;
+ }
+
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function getRegExpFlags(regexp) {
+ return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
+ }
+
+ function isContainer(val) {
+ return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
+ }
+
+ function breadthFirstCompareChild(a, b) {
+
+ // If a is a container not reference-equal to b, postpone the comparison to the
+ // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
+ // over the pair.
+ if (a === b) {
+ return true;
+ }
+ if (!isContainer(a)) {
+ return typeEquiv(a, b);
+ }
+ if (pairs.every(function (pair) {
+ return pair.a !== a || pair.b !== b;
+ })) {
+
+ // Not yet started comparing this pair
+ pairs.push({ a: a, b: b });
+ }
+ return true;
+ }
+
+ var callbacks = {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+ "symbol": useStrictEquality,
+ "date": useStrictEquality,
+
+ "nan": function nan() {
+ return true;
+ },
+
+ "regexp": function regexp(a, b) {
+ return a.source === b.source &&
+
+ // Include flags in the comparison
+ getRegExpFlags(a) === getRegExpFlags(b);
+ },
+
+ // abort (identical references / instance methods were skipped earlier)
+ "function": function _function() {
+ return false;
+ },
+
+ "array": function array(a, b) {
+ var i, len;
+
+ len = a.length;
+ if (len !== b.length) {
+
+ // Safe and faster
+ return false;
+ }
+
+ for (i = 0; i < len; i++) {
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ // Define sets a and b to be equivalent if for each element aVal in a, there
+ // is some element bVal in b such that aVal and bVal are equivalent. Element
+ // repetitions are not counted, so these are equivalent:
+ // a = new Set( [ {}, [], [] ] );
+ // b = new Set( [ {}, {}, [] ] );
+ "set": function set$$1(a, b) {
+ var innerEq,
+ outerEq = true;
+
+ if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) element to two equivalent sets can
+ // make them non-equivalent.
+ return false;
+ }
+
+ a.forEach(function (aVal) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Set is unused)
+ if (!outerEq) {
+ return;
+ }
+
+ innerEq = false;
+
+ b.forEach(function (bVal) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
+ if (innerEquiv(bVal, aVal)) {
+ innerEq = true;
+ }
+
+ // Replace the global pairs list
+ pairs = parentPairs;
+ });
+
+ if (!innerEq) {
+ outerEq = false;
+ }
+ });
+
+ return outerEq;
+ },
+
+ // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
+ // in a, there is some key-value pair (bKey, bVal) in b such that
+ // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
+ // counted, so these are equivalent:
+ // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
+ // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
+ "map": function map(a, b) {
+ var innerEq,
+ outerEq = true;
+
+ if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) key-value pair to two equivalent maps
+ // can make them non-equivalent.
+ return false;
+ }
+
+ a.forEach(function (aVal, aKey) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Map is unused)
+ if (!outerEq) {
+ return;
+ }
+
+ innerEq = false;
+
+ b.forEach(function (bVal, bKey) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
+ if (innerEquiv([bVal, bKey], [aVal, aKey])) {
+ innerEq = true;
+ }
+
+ // Replace the global pairs list
+ pairs = parentPairs;
+ });
+
+ if (!innerEq) {
+ outerEq = false;
+ }
+ });
+
+ return outerEq;
+ },
+
+ "object": function object(a, b) {
+ var i,
+ aProperties = [],
+ bProperties = [];
+
+ if (compareConstructors(a, b) === false) {
+ return false;
+ }
+
+ // Be strict: don't ensure hasOwnProperty and go deep
+ for (i in a) {
+
+ // Collect a's properties
+ aProperties.push(i);
+
+ // Skip OOP methods that look the same
+ if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
+ continue;
+ }
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
+
+ for (i in b) {
+
+ // Collect b's properties
+ bProperties.push(i);
+ }
+
+ // Ensures identical properties name
+ return typeEquiv(aProperties.sort(), bProperties.sort());
+ }
+ };
+
+ function typeEquiv(a, b) {
+ var type = objectType(a);
+
+ // Callbacks for containers will append to the pairs queue to achieve breadth-first
+ // search order. The pairs queue is also used to avoid reprocessing any pair of
+ // containers that are reference-equal to a previously visited pair (a special case
+ // this being recursion detection).
+ //
+ // Because of this approach, once typeEquiv returns a false value, it should not be
+ // called again without clearing the pair queue else it may wrongly report a visited
+ // pair as being equivalent.
+ return objectType(b) === type && callbacks[type](a, b);
+ }
+
+ function innerEquiv(a, b) {
+ var i, pair;
+
+ // We're done when there's nothing more to compare
+ if (arguments.length < 2) {
+ return true;
+ }
+
+ // Clear the global pair queue and add the top-level values being compared
+ pairs = [{ a: a, b: b }];
+
+ for (i = 0; i < pairs.length; i++) {
+ pair = pairs[i];
+
+ // Perform type-specific comparison on any pairs that are not strictly
+ // equal. For container types, that comparison will postpone comparison
+ // of any sub-container pair to the end of the pair queue. This gives
+ // breadth-first search order. It also avoids the reprocessing of
+ // reference-equal siblings, cousins etc, which can have a significant speed
+ // impact when comparing a container of small objects each of which has a
+ // reference to the same (singleton) large object.
+ if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
+ return false;
+ }
+ }
+
+ // ...across all consecutive argument pairs
+ return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
+ }
+
+ return function () {
+ var result = innerEquiv.apply(undefined, arguments);
+
+ // Release any retained objects
+ pairs.length = 0;
+ return result;
+ };
+ })();
+
+ /**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+ var config = {
+
+ // The queue of tests to run
+ queue: [],
+
+ // Block until document ready
+ blocking: true,
+
+ // By default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // By default, modify document.title when suite is done
+ altertitle: true,
+
+ // HTML Reporter: collapse every test except the first failing test
+ // If false, all failing tests will be expanded
+ collapse: true,
+
+ // By default, scroll to top of the page when suite is done
+ scrolltop: true,
+
+ // Depth up-to which object will be dumped
+ maxDepth: 5,
+
+ // When enabled, all tests must call expect()
+ requireExpects: false,
+
+ // Placeholder for user-configurable form-exposed URL parameters
+ urlConfig: [],
+
+ // Set of all modules.
+ modules: [],
+
+ // The first unnamed module
+ currentModule: {
+ name: "",
+ tests: [],
+ childModules: [],
+ testsRun: 0,
+ unskippedTestsRun: 0,
+ hooks: {
+ before: [],
+ beforeEach: [],
+ afterEach: [],
+ after: []
+ }
+ },
+
+ callbacks: {},
+
+ // The storage module to use for reordering tests
+ storage: localSessionStorage
+ };
+
+ // take a predefined QUnit.config and extend the defaults
+ var globalConfig = window && window.QUnit && window.QUnit.config;
+
+ // only extend the global config if there is no QUnit overload
+ if (window && window.QUnit && !window.QUnit.version) {
+ extend(config, globalConfig);
+ }
+
+ // Push a loose unnamed module to the modules collection
+ config.modules.push(config.currentModule);
+
+ // Based on jsDump by Ariel Flesler
+ // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+ var dump = (function () {
+ function quote(str) {
+ return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
+ }
+ function literal(o) {
+ return o + "";
+ }
+ function join(pre, arr, post) {
+ var s = dump.separator(),
+ base = dump.indent(),
+ inner = dump.indent(1);
+ if (arr.join) {
+ arr = arr.join("," + s + inner);
+ }
+ if (!arr) {
+ return pre + post;
+ }
+ return [pre, inner + arr, base + post].join(s);
+ }
+ function array(arr, stack) {
+ var i = arr.length,
+ ret = new Array(i);
+
+ if (dump.maxDepth && dump.depth > dump.maxDepth) {
+ return "[object Array]";
+ }
+
+ this.up();
+ while (i--) {
+ ret[i] = this.parse(arr[i], undefined, stack);
+ }
+ this.down();
+ return join("[", ret, "]");
+ }
+
+ function isArray(obj) {
+ return (
+
+ //Native Arrays
+ toString.call(obj) === "[object Array]" ||
+
+ // NodeList objects
+ typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
+ );
+ }
+
+ var reName = /^function (\w+)/,
+ dump = {
+
+ // The objType is used mostly internally, you can fix a (custom) type in advance
+ parse: function parse(obj, objType, stack) {
+ stack = stack || [];
+ var res,
+ parser,
+ parserType,
+ objIndex = stack.indexOf(obj);
+
+ if (objIndex !== -1) {
+ return "recursion(" + (objIndex - stack.length) + ")";
+ }
+
+ objType = objType || this.typeOf(obj);
+ parser = this.parsers[objType];
+ parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
+
+ if (parserType === "function") {
+ stack.push(obj);
+ res = parser.call(this, obj, stack);
+ stack.pop();
+ return res;
+ }
+ return parserType === "string" ? parser : this.parsers.error;
+ },
+ typeOf: function typeOf(obj) {
+ var type;
+
+ if (obj === null) {
+ type = "null";
+ } else if (typeof obj === "undefined") {
+ type = "undefined";
+ } else if (is("regexp", obj)) {
+ type = "regexp";
+ } else if (is("date", obj)) {
+ type = "date";
+ } else if (is("function", obj)) {
+ type = "function";
+ } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
+ type = "window";
+ } else if (obj.nodeType === 9) {
+ type = "document";
+ } else if (obj.nodeType) {
+ type = "node";
+ } else if (isArray(obj)) {
+ type = "array";
+ } else if (obj.constructor === Error.prototype.constructor) {
+ type = "error";
+ } else {
+ type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
+ }
+ return type;
+ },
+
+ separator: function separator() {
+ if (this.multiline) {
+ return this.HTML ? " " : "\n";
+ } else {
+ return this.HTML ? " " : " ";
+ }
+ },
+
+ // Extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function indent(extra) {
+ if (!this.multiline) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if (this.HTML) {
+ chr = chr.replace(/\t/g, " ").replace(/ /g, " ");
+ }
+ return new Array(this.depth + (extra || 0)).join(chr);
+ },
+ up: function up(a) {
+ this.depth += a || 1;
+ },
+ down: function down(a) {
+ this.depth -= a || 1;
+ },
+ setParser: function setParser(name, parser) {
+ this.parsers[name] = parser;
+ },
+
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ depth: 1,
+ maxDepth: config.maxDepth,
+
+ // This is the list of parsers, to modify them, use dump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function error(_error) {
+ return "Error(\"" + _error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": function _function(fn) {
+ var ret = "function",
+
+
+ // Functions never have name in IE
+ name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+
+ if (name) {
+ ret += " " + name;
+ }
+ ret += "(";
+
+ ret = [ret, dump.parse(fn, "functionArgs"), "){"].join("");
+ return join(ret, dump.parse(fn, "functionCode"), "}");
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function object(map, stack) {
+ var keys,
+ key,
+ val,
+ i,
+ nonEnumerableProperties,
+ ret = [];
+
+ if (dump.maxDepth && dump.depth > dump.maxDepth) {
+ return "[object Object]";
+ }
+
+ dump.up();
+ keys = [];
+ for (key in map) {
+ keys.push(key);
+ }
+
+ // Some properties are not always enumerable on Error objects.
+ nonEnumerableProperties = ["message", "name"];
+ for (i in nonEnumerableProperties) {
+ key = nonEnumerableProperties[i];
+ if (key in map && !inArray(key, keys)) {
+ keys.push(key);
+ }
+ }
+ keys.sort();
+ for (i = 0; i < keys.length; i++) {
+ key = keys[i];
+ val = map[key];
+ ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack));
+ }
+ dump.down();
+ return join("{", ret, "}");
+ },
+ node: function node(_node) {
+ var len,
+ i,
+ val,
+ open = dump.HTML ? "<" : "<",
+ close = dump.HTML ? ">" : ">",
+ tag = _node.nodeName.toLowerCase(),
+ ret = open + tag,
+ attrs = _node.attributes;
+
+ if (attrs) {
+ for (i = 0, len = attrs.length; i < len; i++) {
+ val = attrs[i].nodeValue;
+
+ // IE6 includes all attributes in .attributes, even ones not explicitly
+ // set. Those have values like undefined, null, 0, false, "" or
+ // "inherit".
+ if (val && val !== "inherit") {
+ ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute");
+ }
+ }
+ }
+ ret += close;
+
+ // Show content of TextNode or CDATASection
+ if (_node.nodeType === 3 || _node.nodeType === 4) {
+ ret += _node.nodeValue;
+ }
+
+ return ret + open + "/" + tag + close;
+ },
+
+ // Function calls it internally, it's the arguments part of the function
+ functionArgs: function functionArgs(fn) {
+ var args,
+ l = fn.length;
+
+ if (!l) {
+ return "";
+ }
+
+ args = new Array(l);
+ while (l--) {
+
+ // 97 is 'a'
+ args[l] = String.fromCharCode(97 + l);
+ }
+ return " " + args.join(", ") + " ";
+ },
+
+ // Object calls it internally, the key part of an item in a map
+ key: quote,
+
+ // Function calls it internally, it's the content of the function
+ functionCode: "[code]",
+
+ // Node calls it internally, it's a html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal,
+ symbol: function symbol(sym) {
+ return sym.toString();
+ }
+ },
+
+ // If true, entities are escaped ( <, >, \t, space and \n )
+ HTML: false,
+
+ // Indentation unit
+ indentChar: " ",
+
+ // If true, items in a collection, are separated by a \n, else just a space.
+ multiline: true
+ };
+
+ return dump;
+ })();
+
+ var LISTENERS = Object.create(null);
+ var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
+
+ /**
+ * Emits an event with the specified data to all currently registered listeners.
+ * Callbacks will fire in the order in which they are registered (FIFO). This
+ * function is not exposed publicly; it is used by QUnit internals to emit
+ * logging events.
+ *
+ * @private
+ * @method emit
+ * @param {String} eventName
+ * @param {Object} data
+ * @return {Void}
+ */
+ function emit(eventName, data) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when emitting an event");
+ }
+
+ // Clone the callbacks in case one of them registers a new callback
+ var originalCallbacks = LISTENERS[eventName];
+ var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
+
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](data);
+ }
+ }
+
+ /**
+ * Registers a callback as a listener to the specified event.
+ *
+ * @public
+ * @method on
+ * @param {String} eventName
+ * @param {Function} callback
+ * @return {Void}
+ */
+ function on(eventName, callback) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when registering a listener");
+ } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
+ var events = SUPPORTED_EVENTS.join(", ");
+ throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
+ } else if (objectType(callback) !== "function") {
+ throw new TypeError("callback must be a function when registering a listener");
+ }
+
+ if (!LISTENERS[eventName]) {
+ LISTENERS[eventName] = [];
+ }
+
+ // Don't register the same callback more than once
+ if (!inArray(callback, LISTENERS[eventName])) {
+ LISTENERS[eventName].push(callback);
+ }
+ }
+
+ // Register logging callbacks
+ function registerLoggingCallbacks(obj) {
+ var i,
+ l,
+ key,
+ callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
+
+ function registerLoggingCallback(key) {
+ var loggingCallback = function loggingCallback(callback) {
+ if (objectType(callback) !== "function") {
+ throw new Error("QUnit logging methods require a callback function as their first parameters.");
+ }
+
+ config.callbacks[key].push(callback);
+ };
+
+ return loggingCallback;
+ }
+
+ for (i = 0, l = callbackNames.length; i < l; i++) {
+ key = callbackNames[i];
+
+ // Initialize key collection of logging callback
+ if (objectType(config.callbacks[key]) === "undefined") {
+ config.callbacks[key] = [];
+ }
+
+ obj[key] = registerLoggingCallback(key);
+ }
+ }
+
+ function runLoggingCallbacks(key, args) {
+ var i, l, callbacks;
+
+ callbacks = config.callbacks[key];
+ for (i = 0, l = callbacks.length; i < l; i++) {
+ callbacks[i](args);
+ }
+ }
+
+ // Doesn't support IE9, it will return undefined on these browsers
+ // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+ var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
+
+ function extractStacktrace(e, offset) {
+ offset = offset === undefined ? 4 : offset;
+
+ var stack, include, i;
+
+ if (e && e.stack) {
+ stack = e.stack.split("\n");
+ if (/^error$/i.test(stack[0])) {
+ stack.shift();
+ }
+ if (fileName) {
+ include = [];
+ for (i = offset; i < stack.length; i++) {
+ if (stack[i].indexOf(fileName) !== -1) {
+ break;
+ }
+ include.push(stack[i]);
+ }
+ if (include.length) {
+ return include.join("\n");
+ }
+ }
+ return stack[offset];
+ }
+ }
+
+ function sourceFromStacktrace(offset) {
+ var error = new Error();
+
+ // Support: Safari <=7 only, IE <=10 - 11 only
+ // Not all browsers generate the `stack` property for `new Error()`, see also #636
+ if (!error.stack) {
+ try {
+ throw error;
+ } catch (err) {
+ error = err;
+ }
+ }
+
+ return extractStacktrace(error, offset);
+ }
+
+ var priorityCount = 0;
+ var unitSampler = void 0;
+
+ /**
+ * Advances the ProcessingQueue to the next item if it is ready.
+ * @param {Boolean} last
+ */
+ function advance() {
+ var start = now();
+ config.depth = (config.depth || 0) + 1;
+
+ while (config.queue.length && !config.blocking) {
+ var elapsedTime = now() - start;
+
+ if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
+ if (priorityCount > 0) {
+ priorityCount--;
+ }
+
+ config.queue.shift()();
+ } else {
+ setTimeout(advance, 13);
+ break;
+ }
+ }
+
+ config.depth--;
+
+ if (!config.blocking && !config.queue.length && config.depth === 0) {
+ done();
+ }
+ }
+
+ function addToQueueImmediate(callback) {
+ if (objectType(callback) === "array") {
+ while (callback.length) {
+ addToQueueImmediate(callback.pop());
+ }
+
+ return;
+ }
+
+ config.queue.unshift(callback);
+ priorityCount++;
+ }
+
+ /**
+ * Adds a function to the ProcessingQueue for execution.
+ * @param {Function|Array} callback
+ * @param {Boolean} priority
+ * @param {String} seed
+ */
+ function addToQueue(callback, prioritize, seed) {
+ if (prioritize) {
+ config.queue.splice(priorityCount++, 0, callback);
+ } else if (seed) {
+ if (!unitSampler) {
+ unitSampler = unitSamplerGenerator(seed);
+ }
+
+ // Insert into a random position after all prioritized items
+ var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
+ config.queue.splice(priorityCount + index, 0, callback);
+ } else {
+ config.queue.push(callback);
+ }
+ }
+
+ /**
+ * Creates a seeded "sample" generator which is used for randomizing tests.
+ */
+ function unitSamplerGenerator(seed) {
+
+ // 32-bit xorshift, requires only a nonzero seed
+ // http://excamera.com/sphinx/article-xorshift.html
+ var sample = parseInt(generateHash(seed), 16) || -1;
+ return function () {
+ sample ^= sample << 13;
+ sample ^= sample >>> 17;
+ sample ^= sample << 5;
+
+ // ECMAScript has no unsigned number type
+ if (sample < 0) {
+ sample += 0x100000000;
+ }
+
+ return sample / 0x100000000;
+ };
+ }
+
+ /**
+ * This function is called when the ProcessingQueue is done processing all
+ * items. It handles emitting the final run events.
+ */
+ function done() {
+ var storage = config.storage;
+
+ ProcessingQueue.finished = true;
+
+ var runtime = now() - config.started;
+ var passed = config.stats.all - config.stats.bad;
+
+ emit("runEnd", globalSuite.end(true));
+ runLoggingCallbacks("done", {
+ passed: passed,
+ failed: config.stats.bad,
+ total: config.stats.all,
+ runtime: runtime
+ });
+
+ // Clear own storage items if all tests passed
+ if (storage && config.stats.bad === 0) {
+ for (var i = storage.length - 1; i >= 0; i--) {
+ var key = storage.key(i);
+
+ if (key.indexOf("qunit-test-") === 0) {
+ storage.removeItem(key);
+ }
+ }
+ }
+ }
+
+ var ProcessingQueue = {
+ finished: false,
+ add: addToQueue,
+ addImmediate: addToQueueImmediate,
+ advance: advance
+ };
+
+ var TestReport = function () {
+ function TestReport(name, suite, options) {
+ classCallCheck(this, TestReport);
+
+ this.name = name;
+ this.suiteName = suite.name;
+ this.fullName = suite.fullName.concat(name);
+ this.runtime = 0;
+ this.assertions = [];
+
+ this.skipped = !!options.skip;
+ this.todo = !!options.todo;
+
+ this.valid = options.valid;
+
+ this._startTime = 0;
+ this._endTime = 0;
+
+ suite.pushTest(this);
+ }
+
+ createClass(TestReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ suiteName: this.suiteName,
+ fullName: this.fullName.slice()
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return extend(this.start(), {
+ runtime: this.getRuntime(),
+ status: this.getStatus(),
+ errors: this.getFailedAssertions(),
+ assertions: this.getAssertions()
+ });
+ }
+ }, {
+ key: "pushAssertion",
+ value: function pushAssertion(assertion) {
+ this.assertions.push(assertion);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ if (this.skipped) {
+ return "skipped";
+ }
+
+ var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
+
+ if (!testPassed) {
+ return "failed";
+ } else if (this.todo) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }, {
+ key: "getFailedAssertions",
+ value: function getFailedAssertions() {
+ return this.assertions.filter(function (assertion) {
+ return !assertion.passed;
+ });
+ }
+ }, {
+ key: "getAssertions",
+ value: function getAssertions() {
+ return this.assertions.slice();
+ }
+
+ // Remove actual and expected values from assertions. This is to prevent
+ // leaking memory throughout a test suite.
+
+ }, {
+ key: "slimAssertions",
+ value: function slimAssertions() {
+ this.assertions = this.assertions.map(function (assertion) {
+ delete assertion.actual;
+ delete assertion.expected;
+ return assertion;
+ });
+ }
+ }]);
+ return TestReport;
+ }();
+
+ var focused$1 = false;
+
+ function Test(settings) {
+ var i, l;
+
+ ++Test.count;
+
+ this.expected = null;
+ this.assertions = [];
+ this.semaphore = 0;
+ this.module = config.currentModule;
+ this.stack = sourceFromStacktrace(3);
+ this.steps = [];
+ this.timeout = undefined;
+
+ // If a module is skipped, all its tests and the tests of the child suites
+ // should be treated as skipped even if they are defined as `only` or `todo`.
+ // As for `todo` module, all its tests will be treated as `todo` except for
+ // tests defined as `skip` which will be left intact.
+ //
+ // So, if a test is defined as `todo` and is inside a skipped module, we should
+ // then treat that test as if was defined as `skip`.
+ if (this.module.skip) {
+ settings.skip = true;
+ settings.todo = false;
+
+ // Skipped tests should be left intact
+ } else if (this.module.todo && !settings.skip) {
+ settings.todo = true;
+ }
+
+ extend(this, settings);
+
+ this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
+ todo: settings.todo,
+ skip: settings.skip,
+ valid: this.valid()
+ });
+
+ // Register unique strings
+ for (i = 0, l = this.module.tests; i < l.length; i++) {
+ if (this.module.tests[i].name === this.testName) {
+ this.testName += " ";
+ }
+ }
+
+ this.testId = generateHash(this.module.name, this.testName);
+
+ this.module.tests.push({
+ name: this.testName,
+ testId: this.testId,
+ skip: !!settings.skip
+ });
+
+ if (settings.skip) {
+
+ // Skipped tests will fully ignore any sent callback
+ this.callback = function () {};
+ this.async = false;
+ this.expected = 0;
+ } else {
+ this.assert = new Assert(this);
+ }
+ }
+
+ Test.count = 0;
+
+ function getNotStartedModules(startModule) {
+ var module = startModule,
+ modules = [];
+
+ while (module && module.testsRun === 0) {
+ modules.push(module);
+ module = module.parentModule;
+ }
+
+ return modules;
+ }
+
+ Test.prototype = {
+ before: function before() {
+ var i,
+ startModule,
+ module = this.module,
+ notStartedModules = getNotStartedModules(module);
+
+ for (i = notStartedModules.length - 1; i >= 0; i--) {
+ startModule = notStartedModules[i];
+ startModule.stats = { all: 0, bad: 0, started: now() };
+ emit("suiteStart", startModule.suiteReport.start(true));
+ runLoggingCallbacks("moduleStart", {
+ name: startModule.name,
+ tests: startModule.tests
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({}, module.testEnvironment);
+
+ this.started = now();
+ emit("testStart", this.testReport.start(true));
+ runLoggingCallbacks("testStart", {
+ name: this.testName,
+ module: module.name,
+ testId: this.testId,
+ previousFailure: this.previousFailure
+ });
+
+ if (!config.pollution) {
+ saveGlobal();
+ }
+ },
+
+ run: function run() {
+ var promise;
+
+ config.current = this;
+
+ this.callbackStarted = now();
+
+ if (config.notrycatch) {
+ runTest(this);
+ return;
+ }
+
+ try {
+ runTest(this);
+ } catch (e) {
+ this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
+
+ // Else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if (config.blocking) {
+ internalRecover(this);
+ }
+ }
+
+ function runTest(test) {
+ promise = test.callback.call(test.testEnvironment, test.assert);
+ test.resolvePromise(promise);
+
+ // If the test has a "lock" on it, but the timeout is 0, then we push a
+ // failure as the test should be synchronous.
+ if (test.timeout === 0 && test.semaphore !== 0) {
+ pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2));
+ }
+ }
+ },
+
+ after: function after() {
+ checkPollution();
+ },
+
+ queueHook: function queueHook(hook, hookName, hookOwner) {
+ var _this = this;
+
+ var callHook = function callHook() {
+ var promise = hook.call(_this.testEnvironment, _this.assert);
+ _this.resolvePromise(promise, hookName);
+ };
+
+ var runHook = function runHook() {
+ if (hookName === "before") {
+ if (hookOwner.unskippedTestsRun !== 0) {
+ return;
+ }
+
+ _this.preserveEnvironment = true;
+ }
+
+ if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
+ return;
+ }
+
+ config.current = _this;
+ if (config.notrycatch) {
+ callHook();
+ return;
+ }
+ try {
+ callHook();
+ } catch (error) {
+ _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
+ }
+ };
+
+ return runHook;
+ },
+
+
+ // Currently only used for module level hooks, can be used to add global level ones
+ hooks: function hooks(handler) {
+ var hooks = [];
+
+ function processHooks(test, module) {
+ if (module.parentModule) {
+ processHooks(test, module.parentModule);
+ }
+
+ if (module.hooks[handler].length) {
+ for (var i = 0; i < module.hooks[handler].length; i++) {
+ hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
+ }
+ }
+ }
+
+ // Hooks are ignored on skipped tests
+ if (!this.skip) {
+ processHooks(this, this.module);
+ }
+
+ return hooks;
+ },
+
+
+ finish: function finish() {
+ config.current = this;
+ if (config.requireExpects && this.expected === null) {
+ this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
+ } else if (this.expected !== null && this.expected !== this.assertions.length) {
+ this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack);
+ } else if (this.expected === null && !this.assertions.length) {
+ this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack);
+ }
+
+ var i,
+ module = this.module,
+ moduleName = module.name,
+ testName = this.testName,
+ skipped = !!this.skip,
+ todo = !!this.todo,
+ bad = 0,
+ storage = config.storage;
+
+ this.runtime = now() - this.started;
+
+ config.stats.all += this.assertions.length;
+ module.stats.all += this.assertions.length;
+
+ for (i = 0; i < this.assertions.length; i++) {
+ if (!this.assertions[i].result) {
+ bad++;
+ config.stats.bad++;
+ module.stats.bad++;
+ }
+ }
+
+ notifyTestsRan(module, skipped);
+
+ // Store result when possible
+ if (storage) {
+ if (bad) {
+ storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
+ } else {
+ storage.removeItem("qunit-test-" + moduleName + "-" + testName);
+ }
+ }
+
+ // After emitting the js-reporters event we cleanup the assertion data to
+ // avoid leaking it. It is not used by the legacy testDone callbacks.
+ emit("testEnd", this.testReport.end(true));
+ this.testReport.slimAssertions();
+
+ runLoggingCallbacks("testDone", {
+ name: testName,
+ module: moduleName,
+ skipped: skipped,
+ todo: todo,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ runtime: skipped ? 0 : this.runtime,
+
+ // HTML Reporter use
+ assertions: this.assertions,
+ testId: this.testId,
+
+ // Source of Test
+ source: this.stack
+ });
+
+ if (module.testsRun === numberOfTests(module)) {
+ logSuiteEnd(module);
+
+ // Check if the parent modules, iteratively, are done. If that the case,
+ // we emit the `suiteEnd` event and trigger `moduleDone` callback.
+ var parent = module.parentModule;
+ while (parent && parent.testsRun === numberOfTests(parent)) {
+ logSuiteEnd(parent);
+ parent = parent.parentModule;
+ }
+ }
+
+ config.current = undefined;
+
+ function logSuiteEnd(module) {
+ emit("suiteEnd", module.suiteReport.end(true));
+ runLoggingCallbacks("moduleDone", {
+ name: module.name,
+ tests: module.tests,
+ failed: module.stats.bad,
+ passed: module.stats.all - module.stats.bad,
+ total: module.stats.all,
+ runtime: now() - module.stats.started
+ });
+ }
+ },
+
+ preserveTestEnvironment: function preserveTestEnvironment() {
+ if (this.preserveEnvironment) {
+ this.module.testEnvironment = this.testEnvironment;
+ this.testEnvironment = extend({}, this.module.testEnvironment);
+ }
+ },
+
+ queue: function queue() {
+ var test = this;
+
+ if (!this.valid()) {
+ return;
+ }
+
+ function runTest() {
+
+ // Each of these can by async
+ ProcessingQueue.addImmediate([function () {
+ test.before();
+ }, test.hooks("before"), function () {
+ test.preserveTestEnvironment();
+ }, test.hooks("beforeEach"), function () {
+ test.run();
+ }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () {
+ test.after();
+ }, function () {
+ test.finish();
+ }]);
+ }
+
+ var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
+
+ // Prioritize previously failed tests, detected from storage
+ var prioritize = config.reorder && !!previousFailCount;
+
+ this.previousFailure = !!previousFailCount;
+
+ ProcessingQueue.add(runTest, prioritize, config.seed);
+
+ // If the queue has already finished, we manually process the new test
+ if (ProcessingQueue.finished) {
+ ProcessingQueue.advance();
+ }
+ },
+
+
+ pushResult: function pushResult(resultInfo) {
+ if (this !== config.current) {
+ throw new Error("Assertion occured after test had finished.");
+ }
+
+ // Destructure of resultInfo = { result, actual, expected, message, negative }
+ var source,
+ details = {
+ module: this.module.name,
+ name: this.testName,
+ result: resultInfo.result,
+ message: resultInfo.message,
+ actual: resultInfo.actual,
+ expected: resultInfo.expected,
+ testId: this.testId,
+ negative: resultInfo.negative || false,
+ runtime: now() - this.started,
+ todo: !!this.todo
+ };
+
+ if (!resultInfo.result) {
+ source = resultInfo.source || sourceFromStacktrace();
+
+ if (source) {
+ details.source = source;
+ }
+ }
+
+ this.logAssertion(details);
+
+ this.assertions.push({
+ result: !!resultInfo.result,
+ message: resultInfo.message
+ });
+ },
+
+ pushFailure: function pushFailure(message, source, actual) {
+ if (!(this instanceof Test)) {
+ throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
+ }
+
+ this.pushResult({
+ result: false,
+ message: message || "error",
+ actual: actual || null,
+ expected: null,
+ source: source
+ });
+ },
+
+ /**
+ * Log assertion details using both the old QUnit.log interface and
+ * QUnit.on( "assertion" ) interface.
+ *
+ * @private
+ */
+ logAssertion: function logAssertion(details) {
+ runLoggingCallbacks("log", details);
+
+ var assertion = {
+ passed: details.result,
+ actual: details.actual,
+ expected: details.expected,
+ message: details.message,
+ stack: details.source,
+ todo: details.todo
+ };
+ this.testReport.pushAssertion(assertion);
+ emit("assertion", assertion);
+ },
+
+
+ resolvePromise: function resolvePromise(promise, phase) {
+ var then,
+ resume,
+ message,
+ test = this;
+ if (promise != null) {
+ then = promise.then;
+ if (objectType(then) === "function") {
+ resume = internalStop(test);
+ then.call(promise, function () {
+ resume();
+ }, function (error) {
+ message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
+ test.pushFailure(message, extractStacktrace(error, 0));
+
+ // Else next test will carry the responsibility
+ saveGlobal();
+
+ // Unblock
+ resume();
+ });
+ }
+ }
+ },
+
+ valid: function valid() {
+ var filter = config.filter,
+ regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
+ module = config.module && config.module.toLowerCase(),
+ fullName = this.module.name + ": " + this.testName;
+
+ function moduleChainNameMatch(testModule) {
+ var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
+ if (testModuleName === module) {
+ return true;
+ } else if (testModule.parentModule) {
+ return moduleChainNameMatch(testModule.parentModule);
+ } else {
+ return false;
+ }
+ }
+
+ function moduleChainIdMatch(testModule) {
+ return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
+ }
+
+ // Internally-generated tests are always valid
+ if (this.callback && this.callback.validTest) {
+ return true;
+ }
+
+ if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
+
+ return false;
+ }
+
+ if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
+
+ return false;
+ }
+
+ if (module && !moduleChainNameMatch(this.module)) {
+ return false;
+ }
+
+ if (!filter) {
+ return true;
+ }
+
+ return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
+ },
+
+ regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
+ var regex = new RegExp(pattern, flags);
+ var match = regex.test(fullName);
+
+ return match !== exclude;
+ },
+
+ stringFilter: function stringFilter(filter, fullName) {
+ filter = filter.toLowerCase();
+ fullName = fullName.toLowerCase();
+
+ var include = filter.charAt(0) !== "!";
+ if (!include) {
+ filter = filter.slice(1);
+ }
+
+ // If the filter matches, we need to honour include
+ if (fullName.indexOf(filter) !== -1) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+ }
+ };
+
+ function pushFailure() {
+ if (!config.current) {
+ throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
+ }
+
+ // Gets current test obj
+ var currentTest = config.current;
+
+ return currentTest.pushFailure.apply(currentTest, arguments);
+ }
+
+ function saveGlobal() {
+ config.pollution = [];
+
+ if (config.noglobals) {
+ for (var key in global$1) {
+ if (hasOwn.call(global$1, key)) {
+
+ // In Opera sometimes DOM element ids show up here, ignore them
+ if (/^qunit-test-output/.test(key)) {
+ continue;
+ }
+ config.pollution.push(key);
+ }
+ }
+ }
+ }
+
+ function checkPollution() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff(config.pollution, old);
+ if (newGlobals.length > 0) {
+ pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
+ }
+
+ deletedGlobals = diff(old, config.pollution);
+ if (deletedGlobals.length > 0) {
+ pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
+ }
+ }
+
+ // Will be exposed as QUnit.test
+ function test(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback
+ });
+
+ newTest.queue();
+ }
+
+ function todo(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback,
+ todo: true
+ });
+
+ newTest.queue();
+ }
+
+ // Will be exposed as QUnit.skip
+ function skip(testName) {
+ if (focused$1) {
+ return;
+ }
+
+ var test = new Test({
+ testName: testName,
+ skip: true
+ });
+
+ test.queue();
+ }
+
+ // Will be exposed as QUnit.only
+ function only(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ config.queue.length = 0;
+ focused$1 = true;
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback
+ });
+
+ newTest.queue();
+ }
+
+ // Put a hold on processing and return a function that will release it.
+ function internalStop(test) {
+ test.semaphore += 1;
+ config.blocking = true;
+
+ // Set a recovery timeout, if so configured.
+ if (defined.setTimeout) {
+ var timeoutDuration = void 0;
+
+ if (typeof test.timeout === "number") {
+ timeoutDuration = test.timeout;
+ } else if (typeof config.testTimeout === "number") {
+ timeoutDuration = config.testTimeout;
+ }
+
+ if (typeof timeoutDuration === "number" && timeoutDuration > 0) {
+ clearTimeout(config.timeout);
+ config.timeout = setTimeout(function () {
+ pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2));
+ internalRecover(test);
+ }, timeoutDuration);
+ }
+ }
+
+ var released = false;
+ return function resume() {
+ if (released) {
+ return;
+ }
+
+ released = true;
+ test.semaphore -= 1;
+ internalStart(test);
+ };
+ }
+
+ // Forcefully release all processing holds.
+ function internalRecover(test) {
+ test.semaphore = 0;
+ internalStart(test);
+ }
+
+ // Release a processing hold, scheduling a resumption attempt if no holds remain.
+ function internalStart(test) {
+
+ // If semaphore is non-numeric, throw error
+ if (isNaN(test.semaphore)) {
+ test.semaphore = 0;
+
+ pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
+ return;
+ }
+
+ // Don't start until equal number of stop-calls
+ if (test.semaphore > 0) {
+ return;
+ }
+
+ // Throw an Error if start is called more often than stop
+ if (test.semaphore < 0) {
+ test.semaphore = 0;
+
+ pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
+ return;
+ }
+
+ // Add a slight delay to allow more assertions etc.
+ if (defined.setTimeout) {
+ if (config.timeout) {
+ clearTimeout(config.timeout);
+ }
+ config.timeout = setTimeout(function () {
+ if (test.semaphore > 0) {
+ return;
+ }
+
+ if (config.timeout) {
+ clearTimeout(config.timeout);
+ }
+
+ begin();
+ }, 13);
+ } else {
+ begin();
+ }
+ }
+
+ function collectTests(module) {
+ var tests = [].concat(module.tests);
+ var modules = [].concat(toConsumableArray(module.childModules));
+
+ // Do a breadth-first traversal of the child modules
+ while (modules.length) {
+ var nextModule = modules.shift();
+ tests.push.apply(tests, nextModule.tests);
+ modules.push.apply(modules, toConsumableArray(nextModule.childModules));
+ }
+
+ return tests;
+ }
+
+ function numberOfTests(module) {
+ return collectTests(module).length;
+ }
+
+ function numberOfUnskippedTests(module) {
+ return collectTests(module).filter(function (test) {
+ return !test.skip;
+ }).length;
+ }
+
+ function notifyTestsRan(module, skipped) {
+ module.testsRun++;
+ if (!skipped) {
+ module.unskippedTestsRun++;
+ }
+ while (module = module.parentModule) {
+ module.testsRun++;
+ if (!skipped) {
+ module.unskippedTestsRun++;
+ }
+ }
+ }
+
+ /**
+ * Returns a function that proxies to the given method name on the globals
+ * console object. The proxy will also detect if the console doesn't exist and
+ * will appropriately no-op. This allows support for IE9, which doesn't have a
+ * console if the developer tools are not open.
+ */
+ function consoleProxy(method) {
+ return function () {
+ if (console) {
+ console[method].apply(console, arguments);
+ }
+ };
+ }
+
+ var Logger = {
+ warn: consoleProxy("warn")
+ };
+
+ var Assert = function () {
+ function Assert(testContext) {
+ classCallCheck(this, Assert);
+
+ this.test = testContext;
+ }
+
+ // Assert helpers
+
+ createClass(Assert, [{
+ key: "timeout",
+ value: function timeout(duration) {
+ if (typeof duration !== "number") {
+ throw new Error("You must pass a number as the duration to assert.timeout");
+ }
+
+ this.test.timeout = duration;
+ }
+
+ // Documents a "step", which is a string value, in a test as a passing assertion
+
+ }, {
+ key: "step",
+ value: function step(message) {
+ var result = !!message;
+
+ this.test.steps.push(message);
+
+ return this.pushResult({
+ result: result,
+ message: message || "You must provide a message to assert.step"
+ });
+ }
+
+ // Verifies the steps in a test match a given array of string values
+
+ }, {
+ key: "verifySteps",
+ value: function verifySteps(steps, message) {
+ this.deepEqual(this.test.steps, steps, message);
+ }
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+
+ }, {
+ key: "expect",
+ value: function expect(asserts) {
+ if (arguments.length === 1) {
+ this.test.expected = asserts;
+ } else {
+ return this.test.expected;
+ }
+ }
+
+ // Put a hold on processing and return a function that will release it a maximum of once.
+
+ }, {
+ key: "async",
+ value: function async(count) {
+ var test$$1 = this.test;
+
+ var popped = false,
+ acceptCallCount = count;
+
+ if (typeof acceptCallCount === "undefined") {
+ acceptCallCount = 1;
+ }
+
+ var resume = internalStop(test$$1);
+
+ return function done() {
+ if (config.current !== test$$1) {
+ throw Error("assert.async callback called after test finished.");
+ }
+
+ if (popped) {
+ test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
+ return;
+ }
+
+ acceptCallCount -= 1;
+ if (acceptCallCount > 0) {
+ return;
+ }
+
+ popped = true;
+ resume();
+ };
+ }
+
+ // Exports test.push() to the user API
+ // Alias of pushResult.
+
+ }, {
+ key: "push",
+ value: function push(result, actual, expected, message, negative) {
+ Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).");
+
+ var currentAssert = this instanceof Assert ? this : config.current.assert;
+ return currentAssert.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: negative
+ });
+ }
+ }, {
+ key: "pushResult",
+ value: function pushResult(resultInfo) {
+
+ // Destructure of resultInfo = { result, actual, expected, message, negative }
+ var assert = this;
+ var currentTest = assert instanceof Assert && assert.test || config.current;
+
+ // Backwards compatibility fix.
+ // Allows the direct use of global exported assertions and QUnit.assert.*
+ // Although, it's use is not recommended as it can leak assertions
+ // to other tests from async tests, because we only get a reference to the current test,
+ // not exactly the test where assertion were intended to be called.
+ if (!currentTest) {
+ throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
+ }
+
+ if (!(assert instanceof Assert)) {
+ assert = currentTest.assert;
+ }
+
+ return assert.test.pushResult(resultInfo);
+ }
+ }, {
+ key: "ok",
+ value: function ok(result, message) {
+ if (!message) {
+ message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
+ }
+
+ this.pushResult({
+ result: !!result,
+ actual: result,
+ expected: true,
+ message: message
+ });
+ }
+ }, {
+ key: "notOk",
+ value: function notOk(result, message) {
+ if (!message) {
+ message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
+ }
+
+ this.pushResult({
+ result: !result,
+ actual: result,
+ expected: false,
+ message: message
+ });
+ }
+ }, {
+ key: "equal",
+ value: function equal(actual, expected, message) {
+
+ // eslint-disable-next-line eqeqeq
+ var result = expected == actual;
+
+ this.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notEqual",
+ value: function notEqual(actual, expected, message) {
+
+ // eslint-disable-next-line eqeqeq
+ var result = expected != actual;
+
+ this.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "propEqual",
+ value: function propEqual(actual, expected, message) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+
+ this.pushResult({
+ result: equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notPropEqual",
+ value: function notPropEqual(actual, expected, message) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+
+ this.pushResult({
+ result: !equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "deepEqual",
+ value: function deepEqual(actual, expected, message) {
+ this.pushResult({
+ result: equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notDeepEqual",
+ value: function notDeepEqual(actual, expected, message) {
+ this.pushResult({
+ result: !equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "strictEqual",
+ value: function strictEqual(actual, expected, message) {
+ this.pushResult({
+ result: expected === actual,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notStrictEqual",
+ value: function notStrictEqual(actual, expected, message) {
+ this.pushResult({
+ result: expected !== actual,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "throws",
+ value: function throws(block, expected, message) {
+ var actual = void 0,
+ result = false;
+
+ var currentTest = this instanceof Assert && this.test || config.current;
+
+ // 'expected' is optional unless doing string comparison
+ if (objectType(expected) === "string") {
+ if (message == null) {
+ message = expected;
+ expected = null;
+ } else {
+ throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary.");
+ }
+ }
+
+ currentTest.ignoreGlobalErrors = true;
+ try {
+ block.call(currentTest.testEnvironment);
+ } catch (e) {
+ actual = e;
+ }
+ currentTest.ignoreGlobalErrors = false;
+
+ if (actual) {
+ var expectedType = objectType(expected);
+
+ // We don't want to validate thrown error
+ if (!expected) {
+ result = true;
+ expected = null;
+
+ // Expected is a regexp
+ } else if (expectedType === "regexp") {
+ result = expected.test(errorString(actual));
+
+ // Expected is a constructor, maybe an Error constructor
+ } else if (expectedType === "function" && actual instanceof expected) {
+ result = true;
+
+ // Expected is an Error object
+ } else if (expectedType === "object") {
+ result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
+
+ // Expected is a validation function which returns true if validation passed
+ } else if (expectedType === "function" && expected.call({}, actual) === true) {
+ expected = null;
+ result = true;
+ }
+ }
+
+ currentTest.assert.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }]);
+ return Assert;
+ }();
+
+ // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
+ // Known to us are: Closure Compiler, Narwhal
+ // eslint-disable-next-line dot-notation
+
+
+ Assert.prototype.raises = Assert.prototype["throws"];
+
+ /**
+ * Converts an error into a simple string for comparisons.
+ *
+ * @param {Error} error
+ * @return {String}
+ */
+ function errorString(error) {
+ var resultErrorString = error.toString();
+
+ if (resultErrorString.substring(0, 7) === "[object") {
+ var name = error.name ? error.name.toString() : "Error";
+ var message = error.message ? error.message.toString() : "";
+
+ if (name && message) {
+ return name + ": " + message;
+ } else if (name) {
+ return name;
+ } else if (message) {
+ return message;
+ } else {
+ return "Error";
+ }
+ } else {
+ return resultErrorString;
+ }
+ }
+
+ /* global module, exports, define */
+ function exportQUnit(QUnit) {
+
+ if (defined.document) {
+
+ // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
+ if (window.QUnit && window.QUnit.version) {
+ throw new Error("QUnit has already been defined.");
+ }
+
+ window.QUnit = QUnit;
+ }
+
+ // For nodejs
+ if (typeof module !== "undefined" && module && module.exports) {
+ module.exports = QUnit;
+
+ // For consistency with CommonJS environments' exports
+ module.exports.QUnit = QUnit;
+ }
+
+ // For CommonJS with exports, but without module.exports, like Rhino
+ if (typeof exports !== "undefined" && exports) {
+ exports.QUnit = QUnit;
+ }
+
+ if (typeof define === "function" && define.amd) {
+ define(function () {
+ return QUnit;
+ });
+ QUnit.config.autostart = false;
+ }
+
+ // For Web/Service Workers
+ if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
+ self$1.QUnit = QUnit;
+ }
+ }
+
+ var SuiteReport = function () {
+ function SuiteReport(name, parentSuite) {
+ classCallCheck(this, SuiteReport);
+
+ this.name = name;
+ this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
+
+ this.tests = [];
+ this.childSuites = [];
+
+ if (parentSuite) {
+ parentSuite.pushChildSuite(this);
+ }
+ }
+
+ createClass(SuiteReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.start();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.start();
+ }),
+ testCounts: {
+ total: this.getTestCounts().total
+ }
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.end();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.end();
+ }),
+ testCounts: this.getTestCounts(),
+ runtime: this.getRuntime(),
+ status: this.getStatus()
+ };
+ }
+ }, {
+ key: "pushChildSuite",
+ value: function pushChildSuite(suite) {
+ this.childSuites.push(suite);
+ }
+ }, {
+ key: "pushTest",
+ value: function pushTest(test) {
+ this.tests.push(test);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getTestCounts",
+ value: function getTestCounts() {
+ var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
+
+ counts = this.tests.reduce(function (counts, test) {
+ if (test.valid) {
+ counts[test.getStatus()]++;
+ counts.total++;
+ }
+
+ return counts;
+ }, counts);
+
+ return this.childSuites.reduce(function (counts, suite) {
+ return suite.getTestCounts(counts);
+ }, counts);
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ var _getTestCounts = this.getTestCounts(),
+ total = _getTestCounts.total,
+ failed = _getTestCounts.failed,
+ skipped = _getTestCounts.skipped,
+ todo = _getTestCounts.todo;
+
+ if (failed) {
+ return "failed";
+ } else {
+ if (skipped === total) {
+ return "skipped";
+ } else if (todo === total) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }
+ }]);
+ return SuiteReport;
+ }();
+
+ // Handle an unhandled exception. By convention, returns true if further
+ // error handling should be suppressed and false otherwise.
+ // In this case, we will only suppress further error handling if the
+ // "ignoreGlobalErrors" configuration option is enabled.
+ function onError(error) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ if (config.current) {
+ if (config.current.ignoreGlobalErrors) {
+ return true;
+ }
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ } else {
+ test("global failure", extend(function () {
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ }, { validTest: true }));
+ }
+
+ return false;
+ }
+
+ var focused = false;
+ var QUnit = {};
+ var globalSuite = new SuiteReport();
+
+ // The initial "currentModule" represents the global (or top-level) module that
+ // is not explicitly defined by the user, therefore we add the "globalSuite" to
+ // it since each module has a suiteReport associated with it.
+ config.currentModule.suiteReport = globalSuite;
+
+ var moduleStack = [];
+ var globalStartCalled = false;
+ var runStarted = false;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
+
+ // Expose the current QUnit version
+ QUnit.version = "2.4.0";
+
+ function createModule(name, testEnvironment, modifiers) {
+ var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
+ var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+ var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
+ var skip$$1 = parentModule !== null && parentModule.skip || modifiers.skip;
+ var todo$$1 = parentModule !== null && parentModule.todo || modifiers.todo;
+
+ var module = {
+ name: moduleName,
+ parentModule: parentModule,
+ tests: [],
+ moduleId: generateHash(moduleName),
+ testsRun: 0,
+ unskippedTestsRun: 0,
+ childModules: [],
+ suiteReport: new SuiteReport(name, parentSuite),
+
+ // Pass along `skip` and `todo` properties from parent module, in case
+ // there is one, to childs. And use own otherwise.
+ // This property will be used to mark own tests and tests of child suites
+ // as either `skipped` or `todo`.
+ skip: skip$$1,
+ todo: skip$$1 ? false : todo$$1
+ };
+
+ var env = {};
+ if (parentModule) {
+ parentModule.childModules.push(module);
+ extend(env, parentModule.testEnvironment);
+ }
+ extend(env, testEnvironment);
+ module.testEnvironment = env;
+
+ config.modules.push(module);
+ return module;
+ }
+
+ function processModule(name, options, executeNow) {
+ var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
+
+ var module = createModule(name, options, modifiers);
+
+ // Move any hooks to a 'hooks' object
+ var testEnvironment = module.testEnvironment;
+ var hooks = module.hooks = {};
+
+ setHookFromEnvironment(hooks, testEnvironment, "before");
+ setHookFromEnvironment(hooks, testEnvironment, "beforeEach");
+ setHookFromEnvironment(hooks, testEnvironment, "afterEach");
+ setHookFromEnvironment(hooks, testEnvironment, "after");
+
+ function setHookFromEnvironment(hooks, environment, name) {
+ var potentialHook = environment[name];
+ hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
+ delete environment[name];
+ }
+
+ var moduleFns = {
+ before: setHookFunction(module, "before"),
+ beforeEach: setHookFunction(module, "beforeEach"),
+ afterEach: setHookFunction(module, "afterEach"),
+ after: setHookFunction(module, "after")
+ };
+
+ var currentModule = config.currentModule;
+ if (objectType(executeNow) === "function") {
+ moduleStack.push(module);
+ config.currentModule = module;
+ executeNow.call(module.testEnvironment, moduleFns);
+ moduleStack.pop();
+ module = module.parentModule || currentModule;
+ }
+
+ config.currentModule = module;
+ }
+
+ // TODO: extract this to a new file alongside its related functions
+ function module$1(name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ if (arguments.length === 2) {
+ if (objectType(options) === "function") {
+ executeNow = options;
+ options = undefined;
+ }
+ }
+
+ processModule(name, options, executeNow);
+ }
+
+ module$1.only = function () {
+ if (focused) {
+ return;
+ }
+
+ config.modules.length = 0;
+ config.queue.length = 0;
+
+ module$1.apply(undefined, arguments);
+
+ focused = true;
+ };
+
+ module$1.skip = function (name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ if (arguments.length === 2) {
+ if (objectType(options) === "function") {
+ executeNow = options;
+ options = undefined;
+ }
+ }
+
+ processModule(name, options, executeNow, { skip: true });
+ };
+
+ module$1.todo = function (name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ if (arguments.length === 2) {
+ if (objectType(options) === "function") {
+ executeNow = options;
+ options = undefined;
+ }
+ }
+
+ processModule(name, options, executeNow, { todo: true });
+ };
+
+ extend(QUnit, {
+ on: on,
+
+ module: module$1,
+
+ test: test,
+
+ todo: todo,
+
+ skip: skip,
+
+ only: only,
+
+ start: function start(count) {
+ var globalStartAlreadyCalled = globalStartCalled;
+
+ if (!config.current) {
+ globalStartCalled = true;
+
+ if (runStarted) {
+ throw new Error("Called start() while test already started running");
+ } else if (globalStartAlreadyCalled || count > 1) {
+ throw new Error("Called start() outside of a test context too many times");
+ } else if (config.autostart) {
+ throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
+ } else if (!config.pageLoaded) {
+
+ // The page isn't completely loaded yet, so we set autostart and then
+ // load if we're in Node or wait for the browser's load event.
+ config.autostart = true;
+
+ // Starts from Node even if .load was not previously called. We still return
+ // early otherwise we'll wind up "beginning" twice.
+ if (!defined.document) {
+ QUnit.load();
+ }
+
+ return;
+ }
+ } else {
+ throw new Error("QUnit.start cannot be called inside a test context.");
+ }
+
+ scheduleBegin();
+ },
+
+ config: config,
+
+ is: is,
+
+ objectType: objectType,
+
+ extend: extend,
+
+ load: function load() {
+ config.pageLoaded = true;
+
+ // Initialize the configuration options
+ extend(config, {
+ stats: { all: 0, bad: 0 },
+ started: 0,
+ updateRate: 1000,
+ autostart: true,
+ filter: ""
+ }, true);
+
+ if (!runStarted) {
+ config.blocking = false;
+
+ if (config.autostart) {
+ scheduleBegin();
+ }
+ }
+ },
+
+ stack: function stack(offset) {
+ offset = (offset || 0) + 2;
+ return sourceFromStacktrace(offset);
+ },
+
+ onError: onError
+ });
+
+ QUnit.pushFailure = pushFailure;
+ QUnit.assert = Assert.prototype;
+ QUnit.equiv = equiv;
+ QUnit.dump = dump;
+
+ registerLoggingCallbacks(QUnit);
+
+ function scheduleBegin() {
+
+ runStarted = true;
+
+ // Add a slight delay to allow definition of more modules and tests.
+ if (defined.setTimeout) {
+ setTimeout(function () {
+ begin();
+ }, 13);
+ } else {
+ begin();
+ }
+ }
+
+ function begin() {
+ var i,
+ l,
+ modulesLog = [];
+
+ // If the test run hasn't officially begun yet
+ if (!config.started) {
+
+ // Record the time of the test run's beginning
+ config.started = now();
+
+ // Delete the loose unnamed module if unused.
+ if (config.modules[0].name === "" && config.modules[0].tests.length === 0) {
+ config.modules.shift();
+ }
+
+ // Avoid unnecessary information by not logging modules' test environments
+ for (i = 0, l = config.modules.length; i < l; i++) {
+ modulesLog.push({
+ name: config.modules[i].name,
+ tests: config.modules[i].tests
+ });
+ }
+
+ // The test run is officially beginning now
+ emit("runStart", globalSuite.start(true));
+ runLoggingCallbacks("begin", {
+ totalTests: Test.count,
+ modules: modulesLog
+ });
+ }
+
+ config.blocking = false;
+ ProcessingQueue.advance();
+ }
+
+ function setHookFunction(module, hookName) {
+ return function setHook(callback) {
+ module.hooks[hookName].push(callback);
+ };
+ }
+
+ exportQUnit(QUnit);
+
+ (function () {
+
+ if (typeof window === "undefined" || typeof document === "undefined") {
+ return;
+ }
+
+ var config = QUnit.config,
+ hasOwn = Object.prototype.hasOwnProperty;
+
+ // Stores fixture HTML for resetting later
+ function storeFixture() {
+
+ // Avoid overwriting user-defined values
+ if (hasOwn.call(config, "fixture")) {
+ return;
+ }
+
+ var fixture = document.getElementById("qunit-fixture");
+ if (fixture) {
+ config.fixture = fixture.innerHTML;
+ }
+ }
+
+ QUnit.begin(storeFixture);
+
+ // Resets the fixture DOM element if available.
+ function resetFixture() {
+ if (config.fixture == null) {
+ return;
+ }
+
+ var fixture = document.getElementById("qunit-fixture");
+ if (fixture) {
+ fixture.innerHTML = config.fixture;
+ }
+ }
+
+ QUnit.testStart(resetFixture);
+ })();
+
+ (function () {
+
+ // Only interact with URLs via window.location
+ var location = typeof window !== "undefined" && window.location;
+ if (!location) {
+ return;
+ }
+
+ var urlParams = getUrlParams();
+
+ QUnit.urlParams = urlParams;
+
+ // Match module/test by inclusion in an array
+ QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
+ QUnit.config.testId = [].concat(urlParams.testId || []);
+
+ // Exact case-insensitive match of the module name
+ QUnit.config.module = urlParams.module;
+
+ // Regular expression or case-insenstive substring match against "moduleName: testName"
+ QUnit.config.filter = urlParams.filter;
+
+ // Test order randomization
+ if (urlParams.seed === true) {
+
+ // Generate a random seed if the option is specified without a value
+ QUnit.config.seed = Math.random().toString(36).slice(2);
+ } else if (urlParams.seed) {
+ QUnit.config.seed = urlParams.seed;
+ }
+
+ // Add URL-parameter-mapped config values with UI form rendering data
+ QUnit.config.urlConfig.push({
+ id: "hidepassed",
+ label: "Hide passed tests",
+ tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+ }, {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
+ }, {
+ id: "notrycatch",
+ label: "No try-catch",
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
+ });
+
+ QUnit.begin(function () {
+ var i,
+ option,
+ urlConfig = QUnit.config.urlConfig;
+
+ for (i = 0; i < urlConfig.length; i++) {
+
+ // Options can be either strings or objects with nonempty "id" properties
+ option = QUnit.config.urlConfig[i];
+ if (typeof option !== "string") {
+ option = option.id;
+ }
+
+ if (QUnit.config[option] === undefined) {
+ QUnit.config[option] = urlParams[option];
+ }
+ }
+ });
+
+ function getUrlParams() {
+ var i, param, name, value;
+ var urlParams = Object.create(null);
+ var params = location.search.slice(1).split("&");
+ var length = params.length;
+
+ for (i = 0; i < length; i++) {
+ if (params[i]) {
+ param = params[i].split("=");
+ name = decodeQueryParam(param[0]);
+
+ // Allow just a key to turn on a flag, e.g., test.html?noglobals
+ value = param.length === 1 || decodeQueryParam(param.slice(1).join("="));
+ if (name in urlParams) {
+ urlParams[name] = [].concat(urlParams[name], value);
+ } else {
+ urlParams[name] = value;
+ }
+ }
+ }
+
+ return urlParams;
+ }
+
+ function decodeQueryParam(param) {
+ return decodeURIComponent(param.replace(/\+/g, "%20"));
+ }
+ })();
+
+ var stats = {
+ passedTests: 0,
+ failedTests: 0,
+ skippedTests: 0,
+ todoTests: 0
+ };
+
+ // Escape text for attribute or text content.
+ function escapeText(s) {
+ if (!s) {
+ return "";
+ }
+ s = s + "";
+
+ // Both single quotes and double quotes (for attributes)
+ return s.replace(/['"<>&]/g, function (s) {
+ switch (s) {
+ case "'":
+ return "'";
+ case "\"":
+ return """;
+ case "<":
+ return "<";
+ case ">":
+ return ">";
+ case "&":
+ return "&";
+ }
+ });
+ }
+
+ (function () {
+
+ // Don't load the HTML Reporter on non-browser environments
+ if (typeof window === "undefined" || !window.document) {
+ return;
+ }
+
+ var config = QUnit.config,
+ document$$1 = window.document,
+ collapseNext = false,
+ hasOwn = Object.prototype.hasOwnProperty,
+ unfilteredUrl = setUrl({ filter: undefined, module: undefined,
+ moduleId: undefined, testId: undefined }),
+ modulesList = [];
+
+ function addEvent(elem, type, fn) {
+ elem.addEventListener(type, fn, false);
+ }
+
+ function removeEvent(elem, type, fn) {
+ elem.removeEventListener(type, fn, false);
+ }
+
+ function addEvents(elems, type, fn) {
+ var i = elems.length;
+ while (i--) {
+ addEvent(elems[i], type, fn);
+ }
+ }
+
+ function hasClass(elem, name) {
+ return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
+ }
+
+ function addClass(elem, name) {
+ if (!hasClass(elem, name)) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+ }
+
+ function toggleClass(elem, name, force) {
+ if (force || typeof force === "undefined" && !hasClass(elem, name)) {
+ addClass(elem, name);
+ } else {
+ removeClass(elem, name);
+ }
+ }
+
+ function removeClass(elem, name) {
+ var set = " " + elem.className + " ";
+
+ // Class name may appear multiple times
+ while (set.indexOf(" " + name + " ") >= 0) {
+ set = set.replace(" " + name + " ", " ");
+ }
+
+ // Trim for prettiness
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+ }
+
+ function id(name) {
+ return document$$1.getElementById && document$$1.getElementById(name);
+ }
+
+ function abortTests() {
+ var abortButton = id("qunit-abort-tests-button");
+ if (abortButton) {
+ abortButton.disabled = true;
+ abortButton.innerHTML = "Aborting...";
+ }
+ QUnit.config.queue.length = 0;
+ return false;
+ }
+
+ function interceptNavigation(ev) {
+ applyUrlParams();
+
+ if (ev && ev.preventDefault) {
+ ev.preventDefault();
+ }
+
+ return false;
+ }
+
+ function getUrlConfigHtml() {
+ var i,
+ j,
+ val,
+ escaped,
+ escapedTooltip,
+ selection = false,
+ urlConfig = config.urlConfig,
+ urlConfigHtml = "";
+
+ for (i = 0; i < urlConfig.length; i++) {
+
+ // Options can be either strings or objects with nonempty "id" properties
+ val = config.urlConfig[i];
+ if (typeof val === "string") {
+ val = {
+ id: val,
+ label: val
+ };
+ }
+
+ escaped = escapeText(val.id);
+ escapedTooltip = escapeText(val.tooltip);
+
+ if (!val.value || typeof val.value === "string") {
+ urlConfigHtml += "";
+ } else {
+ urlConfigHtml += "";
+ }
+ }
+
+ return urlConfigHtml;
+ }
+
+ // Handle "click" events on toolbar checkboxes and "change" for select menus.
+ // Updates the URL with the new state of `config.urlConfig` values.
+ function toolbarChanged() {
+ var updatedUrl,
+ value,
+ tests,
+ field = this,
+ params = {};
+
+ // Detect if field is a select menu or a checkbox
+ if ("selectedIndex" in field) {
+ value = field.options[field.selectedIndex].value || undefined;
+ } else {
+ value = field.checked ? field.defaultValue || true : undefined;
+ }
+
+ params[field.name] = value;
+ updatedUrl = setUrl(params);
+
+ // Check if we can apply the change without a page refresh
+ if ("hidepassed" === field.name && "replaceState" in window.history) {
+ QUnit.urlParams[field.name] = value;
+ config[field.name] = value || false;
+ tests = id("qunit-tests");
+ if (tests) {
+ toggleClass(tests, "hidepass", value || false);
+ }
+ window.history.replaceState(null, "", updatedUrl);
+ } else {
+ window.location = updatedUrl;
+ }
+ }
+
+ function setUrl(params) {
+ var key,
+ arrValue,
+ i,
+ querystring = "?",
+ location = window.location;
+
+ params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
+
+ for (key in params) {
+
+ // Skip inherited or undefined properties
+ if (hasOwn.call(params, key) && params[key] !== undefined) {
+
+ // Output a parameter for each value of this key (but usually just one)
+ arrValue = [].concat(params[key]);
+ for (i = 0; i < arrValue.length; i++) {
+ querystring += encodeURIComponent(key);
+ if (arrValue[i] !== true) {
+ querystring += "=" + encodeURIComponent(arrValue[i]);
+ }
+ querystring += "&";
+ }
+ }
+ }
+ return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
+ }
+
+ function applyUrlParams() {
+ var i,
+ selectedModules = [],
+ modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
+ filter = id("qunit-filter-input").value;
+
+ for (i = 0; i < modulesList.length; i++) {
+ if (modulesList[i].checked) {
+ selectedModules.push(modulesList[i].value);
+ }
+ }
+
+ window.location = setUrl({
+ filter: filter === "" ? undefined : filter,
+ moduleId: selectedModules.length === 0 ? undefined : selectedModules,
+
+ // Remove module and testId filter
+ module: undefined,
+ testId: undefined
+ });
+ }
+
+ function toolbarUrlConfigContainer() {
+ var urlConfigContainer = document$$1.createElement("span");
+
+ urlConfigContainer.innerHTML = getUrlConfigHtml();
+ addClass(urlConfigContainer, "qunit-url-config");
+
+ addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
+ addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
+
+ return urlConfigContainer;
+ }
+
+ function abortTestsButton() {
+ var button = document$$1.createElement("button");
+ button.id = "qunit-abort-tests-button";
+ button.innerHTML = "Abort";
+ addEvent(button, "click", abortTests);
+ return button;
+ }
+
+ function toolbarLooseFilter() {
+ var filter = document$$1.createElement("form"),
+ label = document$$1.createElement("label"),
+ input = document$$1.createElement("input"),
+ button = document$$1.createElement("button");
+
+ addClass(filter, "qunit-filter");
+
+ label.innerHTML = "Filter: ";
+
+ input.type = "text";
+ input.value = config.filter || "";
+ input.name = "filter";
+ input.id = "qunit-filter-input";
+
+ button.innerHTML = "Go";
+
+ label.appendChild(input);
+
+ filter.appendChild(label);
+ filter.appendChild(document$$1.createTextNode(" "));
+ filter.appendChild(button);
+ addEvent(filter, "submit", interceptNavigation);
+
+ return filter;
+ }
+
+ function moduleListHtml() {
+ var i,
+ checked,
+ html = "";
+
+ for (i = 0; i < config.modules.length; i++) {
+ if (config.modules[i].name !== "") {
+ checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1;
+ html += "";
+ }
+ }
+
+ return html;
+ }
+
+ function toolbarModuleFilter() {
+ var allCheckbox,
+ commit,
+ reset,
+ moduleFilter = document$$1.createElement("form"),
+ label = document$$1.createElement("label"),
+ moduleSearch = document$$1.createElement("input"),
+ dropDown = document$$1.createElement("div"),
+ actions = document$$1.createElement("span"),
+ dropDownList = document$$1.createElement("ul"),
+ dirty = false;
+
+ moduleSearch.id = "qunit-modulefilter-search";
+ addEvent(moduleSearch, "input", searchInput);
+ addEvent(moduleSearch, "input", searchFocus);
+ addEvent(moduleSearch, "focus", searchFocus);
+ addEvent(moduleSearch, "click", searchFocus);
+
+ label.id = "qunit-modulefilter-search-container";
+ label.innerHTML = "Module: ";
+ label.appendChild(moduleSearch);
+
+ actions.id = "qunit-modulefilter-actions";
+ actions.innerHTML = "" + "" + "";
+ allCheckbox = actions.lastChild.firstChild;
+ commit = actions.firstChild;
+ reset = commit.nextSibling;
+ addEvent(commit, "click", applyUrlParams);
+
+ dropDownList.id = "qunit-modulefilter-dropdown-list";
+ dropDownList.innerHTML = moduleListHtml();
+
+ dropDown.id = "qunit-modulefilter-dropdown";
+ dropDown.style.display = "none";
+ dropDown.appendChild(actions);
+ dropDown.appendChild(dropDownList);
+ addEvent(dropDown, "change", selectionChange);
+ selectionChange();
+
+ moduleFilter.id = "qunit-modulefilter";
+ moduleFilter.appendChild(label);
+ moduleFilter.appendChild(dropDown);
+ addEvent(moduleFilter, "submit", interceptNavigation);
+ addEvent(moduleFilter, "reset", function () {
+
+ // Let the reset happen, then update styles
+ window.setTimeout(selectionChange);
+ });
+
+ // Enables show/hide for the dropdown
+ function searchFocus() {
+ if (dropDown.style.display !== "none") {
+ return;
+ }
+
+ dropDown.style.display = "block";
+ addEvent(document$$1, "click", hideHandler);
+ addEvent(document$$1, "keydown", hideHandler);
+
+ // Hide on Escape keydown or outside-container click
+ function hideHandler(e) {
+ var inContainer = moduleFilter.contains(e.target);
+
+ if (e.keyCode === 27 || !inContainer) {
+ if (e.keyCode === 27 && inContainer) {
+ moduleSearch.focus();
+ }
+ dropDown.style.display = "none";
+ removeEvent(document$$1, "click", hideHandler);
+ removeEvent(document$$1, "keydown", hideHandler);
+ moduleSearch.value = "";
+ searchInput();
+ }
+ }
+ }
+
+ // Processes module search box input
+ function searchInput() {
+ var i,
+ item,
+ searchText = moduleSearch.value.toLowerCase(),
+ listItems = dropDownList.children;
+
+ for (i = 0; i < listItems.length; i++) {
+ item = listItems[i];
+ if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) {
+ item.style.display = "";
+ } else {
+ item.style.display = "none";
+ }
+ }
+ }
+
+ // Processes selection changes
+ function selectionChange(evt) {
+ var i,
+ item,
+ checkbox = evt && evt.target || allCheckbox,
+ modulesList = dropDownList.getElementsByTagName("input"),
+ selectedNames = [];
+
+ toggleClass(checkbox.parentNode, "checked", checkbox.checked);
+
+ dirty = false;
+ if (checkbox.checked && checkbox !== allCheckbox) {
+ allCheckbox.checked = false;
+ removeClass(allCheckbox.parentNode, "checked");
+ }
+ for (i = 0; i < modulesList.length; i++) {
+ item = modulesList[i];
+ if (!evt) {
+ toggleClass(item.parentNode, "checked", item.checked);
+ } else if (checkbox === allCheckbox && checkbox.checked) {
+ item.checked = false;
+ removeClass(item.parentNode, "checked");
+ }
+ dirty = dirty || item.checked !== item.defaultChecked;
+ if (item.checked) {
+ selectedNames.push(item.parentNode.textContent);
+ }
+ }
+
+ commit.style.display = reset.style.display = dirty ? "" : "none";
+ moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent;
+ moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent);
+ }
+
+ return moduleFilter;
+ }
+
+ function appendToolbar() {
+ var toolbar = id("qunit-testrunner-toolbar");
+
+ if (toolbar) {
+ toolbar.appendChild(toolbarUrlConfigContainer());
+ toolbar.appendChild(toolbarModuleFilter());
+ toolbar.appendChild(toolbarLooseFilter());
+ toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
+ }
+ }
+
+ function appendHeader() {
+ var header = id("qunit-header");
+
+ if (header) {
+ header.innerHTML = "" + header.innerHTML + " ";
+ }
+ }
+
+ function appendBanner() {
+ var banner = id("qunit-banner");
+
+ if (banner) {
+ banner.className = "";
+ }
+ }
+
+ function appendTestResults() {
+ var tests = id("qunit-tests"),
+ result = id("qunit-testresult"),
+ controls;
+
+ if (result) {
+ result.parentNode.removeChild(result);
+ }
+
+ if (tests) {
+ tests.innerHTML = "";
+ result = document$$1.createElement("p");
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore(result, tests);
+ result.innerHTML = "