Repository: mqyqingfeng/Blog
Branch: master
Commit: 497c42b27172
Files: 107
Total size: 568.0 KB
Directory structure:
gitextract_rnyuekng/
├── .gitignore
├── README.md
├── articles/
│ ├── 专题系列文章/
│ │ ├── JavaScript专题之jQuery通用遍历方法each的实现.md
│ │ ├── JavaScript专题之乱序.md
│ │ ├── JavaScript专题之从零实现jQuery的extend.md
│ │ ├── JavaScript专题之偏函数.md
│ │ ├── JavaScript专题之函数柯里化.md
│ │ ├── JavaScript专题之函数组合.md
│ │ ├── JavaScript专题之函数记忆.md
│ │ ├── JavaScript专题之在数组中查找指定元素.md
│ │ ├── JavaScript专题之如何判断两个对象相等.md
│ │ ├── JavaScript专题之如何求数组的最大值和最小值.md
│ │ ├── JavaScript专题之惰性函数.md
│ │ ├── JavaScript专题之数组去重.md
│ │ ├── JavaScript专题之数组扁平化.md
│ │ ├── JavaScript专题之深浅拷贝.md
│ │ ├── JavaScript专题之类型判断(上).md
│ │ ├── JavaScript专题之类型判断(下).md
│ │ ├── JavaScript专题之解读v8排序源码.md
│ │ ├── JavaScript专题之跟着underscore学节流.md
│ │ ├── JavaScript专题之跟着underscore学防抖.md
│ │ └── JavaScript专题之递归.md
│ └── 深入系列文章/
│ ├── JavaScript深入之bind的模拟实现.md
│ ├── JavaScript深入之call和apply的模拟实现.md
│ ├── JavaScript深入之new的模拟实现.md
│ ├── JavaScript深入之从ECMAScript规范解读this.md
│ ├── JavaScript深入之从原型到原型链.md
│ ├── JavaScript深入之作用域链.md
│ ├── JavaScript深入之创建对象的多种方式以及优缺点.md
│ ├── JavaScript深入之参数按值传递.md
│ ├── JavaScript深入之变量对象.md
│ ├── JavaScript深入之执行上下文.md
│ ├── JavaScript深入之执行上下文栈.md
│ ├── JavaScript深入之类数组对象与arguments.md
│ ├── JavaScript深入之继承的多种方式和优缺点.md
│ ├── JavaScript深入之词法作用域和动态作用域.md
│ └── JavaScript深入之闭包.md
└── demos/
├── ES6/
│ ├── generator/
│ │ └── generator-es5.js
│ └── module/
│ ├── ES6/
│ │ ├── index.html
│ │ └── vender/
│ │ ├── add.js
│ │ ├── main.js
│ │ ├── multiply.js
│ │ └── square.js
│ ├── commonJS/
│ │ ├── add.js
│ │ ├── main.js
│ │ ├── multiply.js
│ │ └── square.js
│ ├── requirejs/
│ │ ├── index.html
│ │ └── vender/
│ │ ├── add.js
│ │ ├── main.js
│ │ ├── multiply.js
│ │ ├── require.js
│ │ └── square.js
│ ├── seajs/
│ │ ├── index.html
│ │ └── vender/
│ │ ├── add.js
│ │ ├── main.js
│ │ ├── multiply.js
│ │ ├── sea.js
│ │ └── square.js
│ └── webpack.html
├── VuePress/
│ └── vuepress-plugin-code-copy/
│ ├── CodeCopy.vue
│ ├── clientRootMixin.js
│ ├── index.js
│ └── package.json
├── debounce/
│ ├── debounce1.js
│ ├── debounce2.js
│ ├── debounce3.js
│ ├── debounce4.js
│ ├── debounce5.js
│ ├── debounce6.js
│ ├── debounce7.js
│ ├── index.html
│ └── underscore.js
├── node-vm/
│ └── index.js
├── qunit/
│ ├── index.html
│ ├── polyfill-set.js
│ ├── qunit-2.4.0.css
│ ├── qunit-2.4.0.js
│ └── test.js
├── scope/
│ └── scope.bash
├── template/
│ ├── template1/
│ │ ├── index.html
│ │ └── template.js
│ ├── template2/
│ │ ├── index.html
│ │ └── template.js
│ ├── template3/
│ │ ├── index.html
│ │ └── template.js
│ ├── template4/
│ │ ├── index.html
│ │ └── template.js
│ ├── template4.1/
│ │ ├── index.html
│ │ └── template.js
│ ├── template5/
│ │ ├── index.html
│ │ └── template.js
│ ├── template6/
│ │ ├── index.html
│ │ └── template.js
│ ├── template7/
│ │ ├── index.html
│ │ └── template.js
│ └── template8/
│ ├── index.html
│ └── template.js
├── throttle/
│ ├── index.html
│ ├── throttle1.js
│ ├── throttle2.js
│ ├── throttle3.js
│ ├── throttle4.js
│ └── throttle5.js
├── web-worker/
│ ├── index.js
│ └── webworker.html
└── xss/
└── 06.28_sina_XSS.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules/
.travis.yml
*.swp
.sass-cache/
.DS_Store
.idea/
zip/
*.tmp
*.bak
tmp/
temp/
coverage/
build/
.happypack
.pagedata.json
.pagedata.json.lock
.vscode/
================================================
FILE: README.md
================================================
# 冴羽的博客
## 关于冴羽
* 博客:[https://yayujs.com/](https://yayujs.com/)
* 社群:[“低调务实优秀中国好青年”前端社群](https://www.yuque.com/yayu/nice-people/xou8qr)
* 成长:[社群茶话会](https://www.yuque.com/yayu/nice-people/shares)、[我的茶话会](https://www.yuque.com/yayu/blog/grow)、[微信读书](https://yayujs.com/grow)
* 翻译:[TypeScript 中文文档](http://ts.yayujs.com/)、[Svelte 中文文档](https://svelte.yayujs.com/)、[Solid 中文文档](https://solid.yayujs.com/)
* 小册:[《Next.js 开发指南》](https://s.juejin.cn/ds/i8kJo2o8/)、[《Svelte 开发指南》](https://s.juejin.cn/ds/iUurdrae/)、[《Astro 开发指南》](https://juejin.cn/book/7452253924608147467)
* 专栏:[掘金](https://juejin.cn/user/712139234359182/columns)、[思否](https://segmentfault.com/u/yayu)、[语雀](https://www.yuque.com/yayu)、公众号:[冴羽(#yayujs)](https://cdn.jsdelivr.net/gh/mqyqingfeng/picture/qrcode_for_gh_bd1a3dc9eafd_258.jpg)
* 星球:[冴羽·前端开发者的破局之路](https://t.zsxq.com/tn3PW),8 大专栏,10 年经验总结、100 篇深度长文,70W 字原创内容
## 公众号
| 方法 |
结果 |
说明 |
| for循环 |
[1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] |
对象和 NaN 不去重 |
| indexOf |
[1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] |
对象和 NaN 不去重 |
| sort |
[/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] |
对象和 NaN 不去重 数字 1 也不去重 |
| filter + indexOf |
[1, "1", null, undefined, String, String, /a/, /a/] |
对象不去重 NaN 会被忽略掉 |
| filter + sort |
[/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] |
对象和 NaN 不去重 数字 1 不去重 |
| 优化后的键值对方法 |
[1, "1", null, undefined, String, /a/, NaN] |
全部去重 |
| Set |
[1, "1", null, undefined, String, String, /a/, /a/, NaN] |
对象不去重 NaN 去重 |
想了解为什么会出现以上的结果,看两个 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,对作者也是一种鼓励。
================================================
FILE: articles/专题系列文章/JavaScript专题之数组扁平化.md
================================================
# 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,对作者也是一种鼓励。
================================================
FILE: articles/专题系列文章/JavaScript专题之深浅拷贝.md
================================================
# 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,对作者也是一种鼓励。
================================================
FILE: articles/专题系列文章/JavaScript专题之类型判断(上).md
================================================
# 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,对作者也是一种鼓励。
================================================
FILE: articles/专题系列文章/JavaScript专题之类型判断(下).md
================================================
# 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,对作者也是一种鼓励。
================================================
FILE: articles/专题系列文章/JavaScript专题之解读v8排序源码.md
================================================
# 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 个元素,其递归的深度就为 log