函数封装之数组去重

数组去重是编程中十分重要的方法,这篇文章将介绍多个数组去重的方法。

JS的数组去重

数组去重,顾名思义是要将数组中的重复数值去除,所以数值比对我们都需要使用 === 运算符。

然而去重我们还需要一些注意东西:

  1. NaN===NaNfalse
    在去重时,我们应该是希望 NaN 是只保留一个,所以 NaN 需要做额外判断。

测试数据

我们将JS中存在的一些基础数据类型都列出来,构建一个简单的测试用例。

1
2
3
4
5

var obj1={x:1};
var obj2={x:2};
var textArr=[true,false,undefined,1,null,'true','undefined','null','1',obj1,obj2,obj1,obj2];

双重循环去重

数组去重一般最先想到的可能就是双循环去重,建立一个新数组,将没有重复的放到新数组中,循环比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function unique_2loop(arr){//最简单的双循环去重
var i,j;
var result=[];
var l=arr.length;
loop1:
for(i=0;i<l;i++){
var rl=result.length;

for(j=0;j<rl;j++){
if(arr[i]===result[j]||typeof arr[i] ==='number' && typeof result[i] ==='number' && isNaN(arr[i])&&isNaN(result[i])){
continue loop1;
}
}
result.push(arr[i]);
}
return result;
}

unique_2loop(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

这样看起来我们可能觉的有点复杂,第二重循环我们用 indexOf 可以去掉这样代码更加直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

function unique_2loop_indexOf(arr){
var result=[];
var l=arr.length;
var haveNaN=false;//标记是否有了NaN

for(var i=0;i<l;i++){
var rl=result.length;
var data=arr[i];
if(result.indexOf(data)===-1||typeof data ==='number' && isNaN(data) && !haveNaN){
if(!haveNaN&&isNaN(data))haveNaN=true;
result.push(data);
}
}

return result;
}

unique_2loop_indexOf(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

甚至是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

function unique_2loop_indexOf_forEach(arr){
var result = [];
var haveNaN=false;//标记是否有了NaN

arr.forEach((data)=>{
if(result.indexOf(data)===-1||typeof data ==='number' && isNaN(data) && !haveNaN){
if(!haveNaN&&isNaN(data))haveNaN=true;
result.push(data);
}
});

return result;
}

unique_2loop_indexOf_forEach(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

但是这些方法,由于采用了双重循环,当处理大数据时耗时特别长,所以只能用来处理一些较小的数组。

Hash表快速去重

双重循环去重的时间较长,而快速提升去重速度的方法大都是采用构建Hash标识的方式。

比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

function unique_hash(arr){
var result = [];
var hashMap={};

arr.forEach((data)=>{
if(!hashMap[data]){
result.push(data);
hashMap[data]=true;
}
});

return result;
}

unique_hash(textArr);//[true, false, undefined, 1, null, obj1]

然而结果和我们想想的不一样!你会发现,并没有真正的完成去重,因为使用对象作为Hash表时键值对中的键值会转化为 string 类型,

比如 true 变成了 'true',

所以我们必须分数据类型进行Hash表存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function unique_hash_type(arr){
var result = [];
var hashMap={};

arr.forEach((data)=>{
var type=typeof data;
if(!hashMap[type])hashMap[type]={};
if(!hashMap[type][data]){
result.push(data);
hashMap[type][data]=true;
}
});

return result;
}

unique_hash_type(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1]

这里我们会发现 object 类型的数据依然没有正确的处理,因为 object 转换为 string 类型时,调用的 toString 都是获得的 [object Object]

同理我们来看 function 类型,虽然function 类型转化为 string 类型时,它展示的是源码,但是它的字符串过长,在比对时将会消耗大量时间,因为 function 类型的根源依然是 object 类型,所以我们可以将 function 类型当作 object 类型一同处理。

Hash表Object对象的处理

由于在JS不能获取Object对象的引用的特征,我们只能在Object对象的身上开刀,我们可以尝试在Object对象上进行标记来实现目的,然后在完成后去除标记即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

function unique_hash_type_sign(arr){
var result = [];
var objs = [];
var hashMap={};
var data,sign,type,l=arr.length;//标记

for(var i=0;i<l;i++){
data=arr[i];
type=typeof data;
if(type === 'object' || type === 'function'){
sign="__sign__";//标记的键值
while(true){
if(data[sign] !== undefined){
if(data[sign]===data){
break;//存在重复的
}else{//冲突避免
sign+='_';
}
}else{
objs.push({
obj:data,
str:sign
});
data[sign]=data;
result.push(data);
break;
}
}
}else{
if(!hashMap[type])hashMap[type]={};
if(!hashMap[type][data]){
hashMap[type][data]=true;
result.push(data);
}
}
}

l=objs.length;
for(i=0;i<l;i++){
var obj=objs[i];
delete obj.obj[obj.str];
}

return result;
}

unique_hash_type_sign(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

sort()排序后进行处理

使用Hash表会占据大量的存储空间,为了快速排序,我们可以使用sort()函数进行排序后在比较,这是我们只需要进行前后比较就可以实现去重,但是顺序还改变。

同时使用sort()对对象数组进行排序时,排序结果不确定,无法使用于含有对象的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function unique_hash_sort(arr){
if(arr.length===0)return [];
arr= arr.slice().sort();
var result=[];
var haveNaN=false;
result.push(arr[0]);
arr.reduce((last,data)=>{

if(data !== last||typeof data ==='number' && isNaN(data) && !haveNaN){
if(!haveNaN&&isNaN(data))haveNaN=true;
result.push(data);
}

return data
});

return result;
}

unique_hash_sort(textArr);//[1, "1", obj1, obj2, obj1, obj2, false, null, "null", "true", true, "undefined", undefined]

ES6新姿势

上面的方法都是利用ES5实现的,为嘛不使用ES6的Map之类的呢?,因为ES6只需要一行就可以啦~~

1
2
3
4
5
6
7

function unique_ES6_arrFrom(arr){
return Array.from(new Set(arr));
}

unique_ES6(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

或者

1
2
3
4
5
6
7

function unique_ES6_Set(arr){
return [...new Set(arr)];
}

unique_ES6(textArr);//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

性能比较

那么上述的各个函数性能如何呢?

函数\测试数据 10 100 1000 10000 100000 500000 1000000 备注
双重循环去重(unique_2loop) <0ms <0ms ≈2ms ≈200ms ?? ?? ??
双重循环去重(unique_2loop_indexOf) <0ms ≈0.03ms ≈0.3ms ≈11ms ≈1100ms ?? ??
双重循环去重(unique_2loop_indexOf_forEach) <0ms ≈0.03ms ≈0.3ms ≈11ms ≈1100ms ?? ??
Hash表快速去重(unique_hash_type_sign) <0ms ≈0.003ms ≈0.26ms ≈0.5ms ≈7.82ms ≈53.59ms ≈109.56ms
sort()排序后快速去重 ≈0.05ms ≈0.05ms ≈0.35ms ≈5ms ≈59ms ≈332ms ≈734ms 这个没有进行对对象数组的测试,因为无法去重对象
ES6新姿势(unique_ES6_arrFrom) ≈0.01ms ≈0.01ms ≈0.2ms ≈1.54ms ≈18ms ≈115ms ≈263ms
ES6新姿势(unique_ES6_Set) ≈0.01ms ≈0.013ms ≈0.13ms ≈1.14ms ≈14ms ≈93ms ≈229ms

PS:以上的数据都是采用通过处理10组(hash以及ES6为100组)相同数据,然后取平均值获得。

从这个表可以看出双循环是最慢的,慢到后面的数据我都等不下去了= =|||。

sort排序去重速度相较双循环要快的多,实现也比较简单。

Hash表快速去重的速度的ES6的去重很快,毕竟是O(n)的算法,但是在非ES6环境下实现较复杂。只有在数据多,而且含有大量对象的时候推荐使用。

当然ES6处理就简单的多啦~。

简单的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

;(function(){

Array.prototype.unique=Array.prototype.unique||
function(){
var arr=this;
if(window.Set&&Array.from){
return Array.from(new Set(arr));
}

var result = [];
var objs = [];
var hashMap={};
var data,sign,type,l=arr.length;//标记

for(var i=0;i<l;i++){
data=arr[i];
type=typeof data;
if(type === 'object' || type === 'function'){
sign="__sign__";//标记的键值
while(true){
if(data[sign] !== undefined){
if(data[sign]===data){
break;//存在重复的
}else{//冲突避免
sign+='_';
}
}else{
objs.push({
obj:data,
str:sign
});
data[sign]=data;
result.push(data);
break;
}
}
}else{
if(!hashMap[type])hashMap[type]={};
if(!hashMap[type][data]){
hashMap[type][data]=true;
result.push(data);
}
}
}

l=objs.length;
for(i=0;i<l;i++){
var obj=objs[i];
delete obj.obj[obj.str];
}
return result;
}

}())

var obj1={x:1};
var obj2={x:2};
[true,false,undefined,1,null,'true','undefined','null','1',obj1,obj2,obj1,obj2].unique();
//[true, false, undefined, 1, null, "true", "undefined", "null", "1", obj1, obj2]

END

2017-03-01 编写完成

2017-02-20 立项

JS详解之this? ha?

最近写代码被JS的this变量弄得一脸懵逼,这篇文章主要讲述JS的this变量。其实很简单ヾ(。 ̄□ ̄)ツ゜゜゜

简介

this 是Javascript的一个重要关键字,它用来传递程序运行时的环境,代表运行的环境本身。

在不同的运行环境下,this 也有一些不同。

全局上下文

在全局上下文运行的时候,this都是指代的全局对象,无论是否在严格模式下。

1
2
3
4
5

//在浏览器中,全局上下文环境下的this,就是window。
console.log(this);//window
console.log(this===window);//true

但是当函数是在严格模式下,那么它的 this 将为undefined。

1
2
3
4
5
6
7
8
9

function staticfoo(){
'use strict';
console.log(this);//undefined
console.log(this===window);//false
}

staticfoo();

函数上下文

在函数中 this 的值与函数如何调用有关。

直接调用

当将定义的函数直接调用时,函数内部的 this 将指代的全局环境。

1
2
3
4
5
6
7
8

function foo(){
console.log(this);//window
console.log(this===window);//true
}

foo();

对象方法调用

当以对象的方法调用时,函数内部的 this 将指代调用这个方法的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

var x=1;
var y='1';

var obj={
x:2,
foo:function(){
console.log(this.x,this.y);//2,undefined

function foo2(){
console.log(this.x,this.y);//1,'1' 函数内部直接调用函数,this依然是全局变量
}
foo2();
}
};

obj.foo();

注意
  1. 函数中 this 的绑定行为不与函数的申明方式和申明位置有关,只与被调用的方式有关。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    var x=1;
    var y='1';

    var obj={
    x:2,
    foo:function(){
    console.log(this.x,this.y);
    }
    };
    var foo=obj.foo;

    obj.foo();//2,undefined
    foo();//1,'1'

  2. this 的绑定只受最近的调用对象绑定,与调用对象的来源无关。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    var x=1;
    var y='1';

    function foo(){
    console.log(this.x,this.y);
    }

    var obj={
    x:2,
    foo:foo,
    obj2:{
    x:3,
    foo:foo
    }
    };

    foo();//1,'1'
    obj.foo();//2,undefined
    obj.obj2.foo();//3,undefined

构造函数调用

当函数作为构造函数调用时,this 绑定的即将创建的新的对象的引用,无论调用时所处的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function foo(){
console.log(this.x,this.y)
this.x=2;
this.y=2;
}

var obj={
x:1,
y:1,
foo:foo,
};

obj.foo();
//1 1
console.log(new obj.foo());
//undefined undefined
//{x: 2, y: 2}

call 和 apply 调用指定this

callapply 这两个函数是在Function.prototype上面,所以所有的函数都可以执行。

使用 callapply 可以指定函数运行的 this 环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

var obj={
x:1,
y:1,
foo(){
console.log(this.x,this.y);
}
};

var obj2={
x:2,
y:2
};

obj.foo();
//1 1
obj.foo.call(obj2);
//2 2
obj.foo.apply(obj2);
//2 2

bind方法生成固定函数this环境

bind() 方法会创建一个新函数。当这个新函数被调用时,bind()的第一个参数将作为它运行时的 this 环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

var obj={
x:1,
y:1,
foo(){
console.log(this.x,this.y);
}
};

obj2={
x:2,
y:2
};

var foo2=obj.foo.bind(obj2);

obj.foo();
//1 1
foo2();
//2 2

delete obj2;//移除obj2

foo2();
//2 2 引用依然还在

浏览器事件中的this

在浏览器的事件中 this 绑定的触发事件的DOM元素(包括内联事件)。

END

2017-02-24 编写完成

2017-02-08 立项

JS详解之数组Array的方法总结

数组这一数据结构在任何编程语言中都是十分重要的,不同的编程语言都会有一些数组的方法或者类,来方便使用者进行操作,这篇文章就主要介绍在JavaScript当中数组的一些方法。
这篇文章结合MDN完成。

ES5-

这一部分讲述在ES5及其以下版本JS所规定的函数。支持ES5的浏览器看这里

Array.isArray()

介绍

在JS中我们是不能使用 typeof 来区分数组和对象,因为 typeof [] === 'object'Array.isArray() 函数可以帮助我们来判断这个对象是否为数组。

语法

Array.isArray(obj)

参数

参数名 参数描述
obj 需要检测的对象

返回

返回Boolean值,如果对象为数组则返回 true ,否则返回 false

案例

1
2
3
4
5
6
7
8
9
10
11
12
13

let str="string";
let num=123;
let obj={};
let bool=true;
let array=[];

console.log(Array.isArray(str));//false
console.log(Array.isArray(num));//false
console.log(Array.isArray(obj));//false
console.log(Array.isArray(bool));//false
console.log(Array.isArray(array));//true

注意

isArray() 函数只有在Array对象上才能使用,因为该函数不在数组对象的原型链上,所以普通的数组对象会报错。

1
2
3

[].isArray([]);//error [].isArray is not a function

Array.of()

介绍

Array.of()函数是用来创建一个新的数组的方法。

语法

Array.of(element0[, element1[, …[, elementN]]])

参数

参数名 参数描述
elementN 构成数组的参数,按顺序排列

返回

返回构建好的新的数组实例

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

let arr1=[5];
let arr2=[1,2,3];
let arr3=Array(5);
let arr4=Array(1,2,3);
let arr5=Array.of(5);
let arr6=Array.of(1,2,3);

console.log(arr1);//[5]
console.log(arr2);//[1,2,3]
console.log(arr3);//[undefined,undefined,undefined,undefined,undefined]
console.log(arr4);//[1,2,3]
console.log(arr5);//[5]
console.log(arr6);//[1,2,3]

注意

Array.of()和Array()存在区别,Array.of()输入一个数值时只是将参数当作一个元素,而Array()会将这个数值当为数组的长度

1
2
3
4
5
6
7
8
9

let arr1=Array(5);
let arr2=Array.of(5);

console.log(arr1);//[undefined,undefined,undefined,undefined,undefined]
console.log(arr1.length);//5
console.log(arr2);//[5]
console.log(arr2.length);//1

Array.prototype.concat()

介绍

这个方法主要是将两个或多个值或数组合并为一个 的数组,他不会改变原有数组

语法

array.concat(value1[, value2[, …[, valueN]]])

参数

参数名 参数描述
valueN 需要与原数组合并的数组或值

返回

返回按顺序合并的新数组。

案例

1
2
3
4
5
6
7
8
9
10
11

let arr1=[1,2,3];
let arr2=[4,5,6];
let arr3=arr1.concat(arr2);
let arr4=arr3.concat(7);

console.log(arr1);//[1,2,3]
console.log(arr2);//[4,5,6]
console.log(arr3);//[1,2,3,4,5,6]
console.log(arr4);//[1,2,3,4,5,6,7]

注意

1.这个函数是返回的新数组

2.这个函数是进行浅拷贝

1
2
3
4
5
6
7
8

let arr1=[{x:1},{x:2},{x:3}];
let arr2=arr1.concat(arr1);

console.log(arr2);//[{x:1},{x:2},{x:3},{x:1},{x:2},{x:3}]
arr1[0].x=4;
console.log(arr2);//[{x:4},{x:2},{x:3},{x:4},{x:2},{x:3}]

Array.prototype.every()

介绍

Array.prototype.every() 方法测试数组的所有元素是否都通过了指定函数的测试。

语法

array.every(callback[, thisArg])

参数

参数名 参数描述
callback 用来检测每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回检测的结果,为布尔值

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function bigthan10(element,index,array){
if(this.x){
return element/this.x>10
}else{
return element>10
}
}

let arr1=[11,22,3];
console.log(arr1.every(bigthan10));//false
let arr2=[11,22,33];
console.log(arr2.every(bigthan10));//true

let obj={
x:11
};

let arr3=[99,111,222];
console.log(arr3.every(bigthan10,obj));//false

Array.prototype.some()

介绍

Array.prototype.some() 方法测试数组的是否有元素通过了指定函数的测试,与 Array.prototype.every() 方法相对。

语法

array.some(callback[, thisArg])

参数

参数名 参数描述
callback 用来检测每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回检测的结果,为布尔值

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function smallthan10(element,index,array){
if(this.x){
return element/this.x<10
}else{
return element<10
}
}

let arr1=[11,22,3];
console.log(arr1.some(smallthan10));//true
let arr2=[11,22,33];
console.log(arr2.some(smallthan10));//false

let obj={
x:11
};

let arr3=[99,111,222];
console.log(arr3.some(smallthan10,obj));//true

Array.prototype.filter()

介绍

filter() 方法使用指定的函数测试所有元素,并返回一个包含所有通过测试的元素的新数组。

语法

array.filter(callback[, thisArg])

参数

参数名 参数描述
callback 用来检测每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回由满足函数的元素组成的新的数组。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function smallthan10(element,index,array){
if(this.x){
return element/this.x<10
}else{
return element<10
}
}

let arr1=[11,22,3];
console.log(arr1.filter(smallthan10));//[3]
console.log(arr1);//[11,22,3] 原数组不变
let arr2=[11,22,33];
console.log(arr2.filter(smallthan10));//[]

let obj={
x:11
};

let arr3=[99,111,222];
console.log(arr3.filter(smallthan10,obj));//[99]

Array.prototype.map()

介绍

Array.prototype.map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

语法

array.map(callback[, thisArg])

参数

参数名 参数描述
callback 用来计算每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回由每个元素根据函数的出的值组成的新数组。

案例

1
2
3
4
5
6
7
8
9
10

function big(element,index,array){
return element*10;
}

let arr1=[1,2,3,4];
let arr2=arr1.map(big);
console.log(arr1);//[1, 2, 3, 4]
console.log(arr2);//[10, 20, 30, 40]

Array.prototype.reduce()

介绍

Array.prototype.reduce() 方法对累加器的数值和数组的每个值应用一个函数 (从左到右),计算出最终的一个值。

语法

array.reduce(callback,[initialValue])

参数

参数名 参数描述
callback 用来计算最终值的回调函数,callback 被调用时传入四个参数:上一次调用回调返回的值(或者初始值),数组中将要处理的元素,数据中将要处理的元素索引,原数组。
initialValue 其值用于第一次调用 callback 的第一个参数,如果没有,reduce将会从第二个元素开始

返回

返回计算的结果

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

let arr1=[1,2,3,4];
console.log(arr1.reduce((a,b)=>{
console.log(b);
return a+b;
}));
//2
//3
//4
//10
console.log(arr1.reduce((a,b)=>{
return a+b;
},10));//20

let arr2=[{x:1}];
console.log(arr2.reduce((a,b)=>{
console.log(a,b);
return a+b;
}));//{x:1}

注意

1.当没有 initialValue 时,函数会直接从第二个元素开始。

2.当数组只有一个元素时,且有 initialValue 时,函数直接返回那个元素,所以建议都加上 initialValue

Array.prototype.reduceRight()

介绍

Array.prototype.reduceRight() 方法对累加器的数值和数组的每个值应用一个函数 (从右到左,与reduce()相反),计算出最终的一个值。

语法

array.reduceRight(callback[, initialValue])

参数

参数名 参数描述
callback 用来计算最终值的回调函数,callback 被调用时传入四个参数:上一次调用回调返回的值(或者初始值),数组中将要处理的元素,数据中将要处理的元素索引,原数组。
initialValue 其值用于第一次调用 callback 的第一个参数,如果没有,reduceRight()将会从倒数第二个元素开始

返回

返回计算出的值

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

let arr1=[1,2,3,4];
console.log(arr1.reduceRight((a,b,index,arr)=>{
console.log(b);
return a+b;
}));
//3
//2
//1
//10

console.log(arr1.reduceRight((a,b,index,arr)=>{
return a+b;
},10));
//20

注意

1.当没有 initialValue 时,函数会直接从第二个元素开始。

2.当数组只有一个元素时,且有 initialValue 时,函数直接返回那个元素,所以建议都加上 initialValue

Array.prototype.forEach()

介绍

forEach() 方法对数组的每个元素执行一次提供的函数。

语法

array.forEach(callback[, thisArg])

参数

参数名 参数描述
callback 对每个参数进行处理的函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回 undefined

案例

1
2
3
4
5
6
7
8
9

function biger(element,index,array){
array[index]=element*10;
}

let arr1=[1,2,3];
console.log(arr1.forEach(biger));//undefined
console.log(arr1);//[10, 20, 30]

注意

1.forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function add(element,index,array){
array.push(index+'a');
console.log(element);
}

let arr1=[1,2,3];
console.log(arr1.forEach(add));
//1
//2
//3
//undefined
console.log(arr1);//[1, 2, 3, "0a", "1a", "2a"]

2.如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function del(element,index,array){
console.log(element);
// delete array[index];
}

let arr1=[1,2,3];
delete arr1[1];
console.log(arr1.forEach(del));
//1
//3
//undefined
console.log(arr1);//[1, 2:, 3]

Array.prototype.sort()

介绍

Array.prototype.sort() 方法在适当的位置对数组的元素进行排序,并返回数组。 sort 排序不一定是稳定的。默认排序顺序是根据字符串Unicode码点。

语法

array.sort(compareFunction)

参数

参数名 参数描述
compareFunction 用来排序的函数,传入相互比较的a、b对象,通过返回数字进行排序:负数表示不交换(a前b后),正数表示交换(b前a后),0表示不变。

返回

返回排序后的数组

案例

1
2
3
4
5
6
7
8
9

function comp(a,b){
return a-b;
}

let arr1=[11,22,3,1,323,443,5];
console.log(arr1.sort(comp));//[1, 3, 5, 11, 22, 323, 443]
console.log(arr1);//[1, 3, 5, 11, 22, 323, 443]

注意

1.当不输入compareFunction时,函数默认采用Unicode位点进行排序。

1
2
3
4
5

let arr1=[11,22,3,1,323,443,5];
console.log(arr1.sort());//[1, 11, 22, 3, 323, 443, 5]
//这里将数字类型转化为了字符串进行比较

Array.prototype.pop()

介绍

Array.prototype.pop() 方法从数组中删除最后一个元素,并返回该元素。该方法会将数组视为一个堆栈,pop为出栈函数。

语法

arr.pop()

返回

返回弹出的栈顶元素

案例

1
2
3
4
5
6
7

let arr1=[1,2,3,4,5];
let p=arr1.pop();

console.log(arr1);//[1,2,3,4]
console.log(p);//5

Array.prototype.push()

介绍

push() 方法将一个或多个元素添加到数组的末尾,并返回数组的新长度。该方法会将数组视为一个堆栈,push为入栈函数。

语法

arr.push(element1, …, elementN)

参数

参数名 参数描述
elementN 需要入栈的元素,按顺序入栈

返回

返回改变后的数组的长度

案例

1
2
3
4
5
6
7
8

let arr1=[1,2,3,4];
let p=arr1.push('a','b','c');

console.log(arr1);//[1,2,3,4,'a','b','c']
console.log(p);//7


Array.prototype.shift()

介绍

Array.prototype.shift() 方法从数组中弹出第一个元素,并返回该元素的值。这个方法类似于pop()函数。

语法

array.shift()

返回

返回弹出的元素

案例

1
2
3
4
5
6
7

let arr1=[1,2,3,4,5];
let p=arr1.shift();

console.log(arr1);//[2,3,4,5]
console.log(p);//1

Array.prototype.unshift()

介绍

Array.prototype.unshift() 方法将一个或多个元素添加到数组的开头,并返回新数组的长度。

语法

array.unshift(element1, …, elementN)

参数

参数名 参数描述
elementN 需要入栈的元素,按倒序入栈,像是两个数组拼接

返回

返回改变后的数组的长度

案例

1
2
3
4
5
6
7
8

let arr1=[1,2,3];
let p=arr1.unshift('a','b','c');

console.log(arr1);//['a','b','c',1,2,3]
console.log(6);//7


Array.prototype.reverse()

介绍

Array.prototype.reverse() 方法会颠倒数组中元素的位置。

语法

array.reverse()

返回

返回该数组

案例

1
2
3
4
5

let arr1=[1,2,3,4,5];
console.log(arr1.reverse());//[5, 4, 3, 2, 1]
console.log(arr1);//[5, 4, 3, 2, 1]

Array.prototype.indexOf()

介绍

Array.prototype.indexOf()方法返回在数组中可以找到给定元素的第一个索引,如果不存在,则返回-1。

语法

arr.indexOf(searchElement[, fromIndex = 0])

参数

参数名 参数描述
searchElement 需要查找的元素值。
fromIndex 从该索引处开始查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0

返回

返回首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1

案例

1
2
3
4
5
6
7

console.log([1,2,3].indexOf(2));//1
console.log([1,2,3].indexOf(2,1));//1
console.log([1,2,3].indexOf(2,2));//-1
console.log([1,2,3].indexOf(5));//-1
console.log([1,2,{1:2}].indexOf({1:2}));//-1

Array.prototype.lastIndexOf()

介绍

Array.prototype.lastIndexOf()方法返回在数组中可以找到给定元素的最后一个索引,如果不存在,则返回-1。

语法

array.lastIndexOf(searchElement[, fromIndex = array.length - 1])

参数

参数名 参数描述
searchElement 需要查找的元素值。
fromIndex 从该索引处开始逆向查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 array.length - 1

返回

返回首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1

案例

1
2
3
4
5
6

console.log([1,2,1].lastIndexOf(1));//2
console.log([1,2,1].lastIndexOf(1,1));//0
console.log([1,2,'1'].lastIndexOf(1));//0
console.log([1,2,1].lastIndexOf(3));//-1

Array.prototype.join()

介绍

Array.prototype.join() 方法将数组(或一个类数组对象)的所有元素连接到一个字符串中。

语法

array.join(separator)

参数

参数名 参数描述
separator 元素间间隔需要插入的字符串,默认为逗号(,)

返回

返回拼接好的字符串

案例

1
2
3
4
5
6
7

let arr=[1,2,3,'a','b',{x:1}];

console.log(arr.join());//1,2,3,a,b,[object Object]
console.log(arr.join(''));//123ab[object Object]
console.log(arr.join('---'));//1---2---3---a---b---[object Object]

Array.prototype.slice()

介绍

Array.prototype.slice() 方法将数组的一部分浅拷贝, 返回到从开始到结束(不包括结束)选择的新数组对象。原始数组不会被修改。

语法

array.slice(start,end)

参数

参数名 参数描述
start 浅拷贝的开始索引,默认为0
end 浅拷贝的结束索引,不会提取在结束索引位置的元素,默认为length

返回

返回提取出来的元素组成的新数组

案例

1
2
3
4
5
6
7
8

let arr=[1,2,3,'a','b'];

console.log(arr.slice());//[1, 2, 3, "a", "b"]
console.log(arr.slice()===arr);//false
console.log(arr.slice(1));//[2, 3, "a", "b"]
console.log(arr.slice(1,4));//[2, 3, "a"]

Array.prototype.splice()

介绍

Array.prototype.splice() 方法通过删除现有元素和/或添加新元素来更改数组的内容。

语法

array.splice(start[, deleteCount[, [item1, [item2,[itemN …]]]]])

参数

参数名 参数描述
start 修改的开始位置,如果是负值,则表示从数组末位开始的第几位。
deleteCount 整数,表示要移除的数组元素的个数。默认为arr.length - start
itemN 需要从开始位置插入的元素,按顺序插入

返回

由被删除的元素组成的一个数组。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

let arr1=[1,2,3,'a','b'];
console.log(arr1.splice());//[]
console.log(arr1);//[1,2,3,'a','b']

let arr2=[1,2,3,'a','b'];
console.log(arr2.splice(2));//[3, "a", "b"]
console.log(arr2);//[1, 2]

let arr3=[1,2,3,'a','b'];
console.log(arr3.splice(2,0));//[]
console.log(arr3);//[1,2,3,'a','b']

let arr4=[1,2,3,'a','b'];
console.log(arr4.splice(2,2));//[3, "a"]
console.log(arr4);//[1, 2, "b"]

let arr5=[1,2,3,'a','b'];
console.log(arr5.splice(2,1,'c'));//[3]
console.log(arr5);//[1, 2, "c", "a", "b"]

Array.prototype.toString()

介绍

toString() 返回一个字符串,表示指定的数组及其元素。

Array 对象覆盖了 Object 的 toString 方法。对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。

语法

array.toString()

返回

返回表示数组内容的字符串

案例

1
2
3
4

let arr1=[1,2,3,'a','b'];
console.log(arr1.toString());//1,2,3,a,b

Array.prototype.toLocaleString()

介绍

Array.prototype.toLocaleString() 返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 “,”)隔开。

语法

array.toLocaleString();

返回

返回表示数组内容的使用一个特定语言环境的字符串。

案例

1
2
3
4
5
6

var myArr = [1337, new Date(), "foo"];

console.log(myArr.toString());//1337,Sun Feb 19 2017 19:08:50 GMT+0800 (中国标准时间),foo
console.log(myArr.toLocaleString());//1337,Sun Feb 19 2017 19:08:50 GMT+0800 (中国标准时间),foo

ES6+

这一部分讲述在ES6及其以后版本JS所规定的函数。ES6的特性支持表看这里

Array.from()

介绍

Array.from() 方法从类似数组或可迭代对象创建一个新的数组实例。

语法

Array.from(arrayLike[, mapFn[, thisArg]])

参数

参数名 参数描述
arrayLike 想要转换成真实数组的类数组对象或可遍历对象。
mapFn 可选参数,如果指定了该参数,则最后生成的数组会经过该函数的加工处理后再返回。
thisArg 可选参数,执行 mapFn 函数时 this 的值。

返回

返回一个新的Array实例

案例

1
2
3
4
5
6
7
8
9
10
11

function fromtest(){
console.log(arguments,Array.isArray(arguments));
let args=Array.from(arguments);
console.log(args,Array.isArray(args));
}

fromtest(1,2,3,4,5);
//[1, 2, 3, 4, 5] false
//[1, 2, 3, 4, 5] true

Array.prototype.copyWithin()

介绍

Array.prototype.copyWithin()方法会浅拷贝数组的部分元素到同一数组的不同位置,且不改变数组的大小,然后返回该数组。

语法

array.copyWithin(target[, start[, end]])

参数

参数名 参数描述
target 序列复制到的索引位子。如果是负数,target 将从末尾开始计算,如果 target 大于等于 arr.length,将会不发生拷贝。如果 target 在 start 之后,复制的序列将被修改以符合 arr.length。
start 开始复制元素的起始位置。如果是负数,start 将从末尾开始计算。默认为0
end 开始复制元素的结束位置。copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。如果是负数, end 将从末尾开始计算。默认为array.length

返回

返回修改后的数组

案例

1
2
3
4
5
6
7
8
9
10

let arr1=[1,2,3,4,5];
console.log(arr1);//[1,2,3,4,5]
let arr2=arr1.copyWithin(1);
console.log(arr1);//[1,1,2,3,4] 会修改原始数字本身
console.log(arr2);//[1,1,2,3,4]

let arr3=[1,2,3,4,5];
console.log(arr3.copyWithin(1,2,4));//[1,3,4,4,5]

注意

1.该函数会修改原始数组,并且不是插入,是进行的覆盖操作。

2.start所在索引必须小于end

Array.prototype.entries()

介绍

Array.prototype.entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。

语法

arr.entries();

返回

返回一个新的 Array 迭代器对象。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

let arr = ['a','b','c',1,2,3];
let iterator = arr.entries();
console.log(iterator);//ArrayIterator {}

for (let data of iterator) {
console.log(data);
}
//[0, "a"]
//[1, "b"]
//[2, "c"]
//[3, 1]
//[4, 2]
//[5, 3]

Array.prototype.keys()

介绍

Array.prototype.keys() 方法返回一个新的Array迭代器,它包含数组中每个索引的键。

语法

arr.keys();

返回

返回一个新的Array迭代器,它包含数组中每个索引的键。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

let arr = ['a','b','c',1,2,3];
let iterator = arr.keys();
console.log(iterator);//ArrayIterator {}

for (let data of iterator) {
console.log(data);
}
//0
//1
//2
//3
//4
//5

注意

1.与Object的keys存在一定区别,索引迭代器会包含那些没有对应元素的索引。

1
2
3
4
5

let arr = [1,,3,,5];
console.log(Object.keys(arr));//["0", "2", "4"]
console.log([...arr.keys()])//[0, 1, 2, 3, 4]

Array.prototype.values()

介绍

Array.prototype.values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。

语法

array.values()

返回

返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

let arr = ['a','b','c',1,2,3];
let iterator = arr.values();
console.log(iterator);//ArrayIterator {}

for (let data of iterator) {
console.log(data);
}
//'a'
//'b'
//'c'
//1
//2
//3

Array.prototype.fill()

介绍

Array.prototype.fill() 方法将一个数组的所有元素从开始索引填充到具有静态值的结束索引

语法

arr.fill(value, start, end)

参数

参数名 参数描述
value 需要用来填充的元素
start 开始索引,默认为0,如果开始索引为负数则从后往前算
end 结束索引,默认为arr.length,结束索引所在的位置不会被覆盖

返回

返回改变后的数组本身

案例

1
2
3
4
5
6
7
8
9
10
11
12
13

let arr1 = [1,2,3,4,5];
let p = arr1.fill(0);
console.log(arr1);//[0,0,0,0,0]

let arr2 = [1,2,3,4,5];
p = arr2.fill(0,1,2);
console.log(arr2);//[1,0,3,4,5]

let arr3 = [1,2,3,4,5];
p = arr3.fill(0,-2,-1);
console.log(arr3);//[1,2,3,0,5]

Array.prototype.find()

介绍

Array.prototype.find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

语法

array.find(callback[, thisArg])

参数

参数名 参数描述
callback 用来检测每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回第一个满足函数的元素。如果没有则返回 undefined

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function smallthan10(element,index,array){
if(this.x){
return element/this.x<10
}else{
return element<10
}
}

let arr1=[11,22,3];
console.log(arr1.find(smallthan10));//3
let arr2=[11,22,33];
console.log(arr2.find(smallthan10));//undefined

let obj={
x:11
};

let arr3=[99,111,222];
console.log(arr3.find(smallthan10,obj));//99

Array.prototype.findIndex()

介绍

Array.prototype.findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

语法

array.findIndex(callback[, thisArg])

参数

参数名 参数描述
callback 用来检测每个值的回调函数,callback 被调用时传入三个参数:元素值,元素的索引,原数组。
thisArg 执行 callback 时使用的 this 值。

返回

返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

function smallthan10(element,index,array){
if(this.x){
return element/this.x<10
}else{
return element<10
}
}

let arr1=[11,22,3];
console.log(arr1.findIndex(smallthan10));//2
let arr2=[11,22,33];
console.log(arr2.findIndex(smallthan10));//-1

let obj={
x:11
};

let arr3=[99,111,222];
console.log(arr3.findIndex(smallthan10,obj));//0

Array.prototype.includes()

介绍

Array.prototype.includes() 方法用来判断当前数组是否包含某指定的值,如果是,则返回 true,否则返回 false。

语法

array.includes(searchElement[, fromIndex = 0])

参数

参数名 参数描述
searchElement 需要查找的元素值。
fromIndex 从该索引处开始查找 searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0

返回

返回布尔值,存在返回 true ,不存在返回 false

案例

1
2
3
4
5
6

console.log([1,2,3].includes(1));//true
console.log([1,2,3].includes(1,1));//false
console.log([1,2,3].includes(5));//false
console.log([1,2,{1:2}].includes({1:2}));//false

注意

  1. 对于对象的查找依然是按照引用查找

未解决

Array.prototype@@iterator

get Array[@@species]

END

2017-02-19 完成

2017-02-13 立项

nodejs环境windows下安装

这篇文章主要讲述nodejs在windows环境下的快速安装


从2016年接触node以来,从需要手动配置环境变量,到自动配置,node的安装流程越来越简单,同时发展出了可以快速切换系统node版本的工具nvm,在这里当然不会讲如何手动安装,手动配置node~~~

安装NVM

首先前往nvm-window的开源仓库下载下载最新版的windos-nvm包

可以下载免安装版也可以下载安装版(PS:感觉就是是不是自动帮你绑定nvm的环境变量)

下载后运行,next -> accept -> next我们就到了选择nvm安装路径的界面

再next就到了选择node安装路径的界面

之后就可以使用nvm啦

配置NVM

使用默认的NVM环境在国内下载很慢,慢的伤心,所以需要修改配置,这里使用国内的淘宝源

前往nvm的安装目录会有一个setting文件

修改其中的node_mirrorhttp://npm.taobao.org/mirrors/node/npm_mirrorhttps://npm.taobao.org/mirrors/npm/

改成这样后就可以去快速的安装node啦~~

安装node

使用命令安装9.4.0的node:

> nvm install 9.4.0

然后使用命令使用9.4.0的node

> nvm use 9.4.0

于是就安完了~~

END

2017-01-21 完成

2017-01-21 立项

功能实现之前端路由控制

这段时间用了用vue-router,了解了前端路由控制的一些东西,其中对浏览器的URL的修改和判断,我就觉得很神奇0、0,然后认真的去找实现的原理,发现了主要核心是对hashbang和history的使用,-、-看来果然高级的特性我们辣鸡还是了解的太少啊!

前端路由控制

HashBang

在HTML5 HistoryAPI没有出来前,前端的复应用的路由控制主要依靠HashBang,第一眼看到HashBang是一脸懵逼的,一副黑人脸(???)。

网上收集了一些资料了解到,就是利用的 # 这个URL锚点,没错我们一直以为这只是简单的跳到指定ID的地方的东西-、-。这个东西后面跟的东西就是URL的Hash。

URL的HASH

比如一串URL

http://www.cxyblogbiu.com/articlelist?imsearch#imhash

我们可以使用浏览器控制台查看location来详细查看这个URL的解析结果

key value
protocol http:
host www.cxyblogbiu.com
hostname www.cxyblogbiu.com
pathname /articlelist
search ?imsearch
hash #imhash

我们可以看出浏览器URL结构主要是这样的:

protocol//host:port/pathname?search#hash

其中的标志性符号是包含在内容当中的,同时顺序不可更改。

网页请求时不会附带hash值

我们访问这个地址会发现获取页面的请求并不会携带Hash

实现

hashbang的路由控制很好实现,只需要使用 # 来定义超链接或者对 loaction.href 附上开头为 # 的连接就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<a href="#/qwe">#/qwe</a>
<button type="button" name="button" onclick="changeURL('#/123')">changeURL('#/123')</button>
</body>
<script type="text/javascript">
function changeURL(url){
location.href=url;
}
</script>
</html>

对路由的控制,只需要解析URL的Hash的内容就可以了。控制的方法这次就不写了= =。

HTML5 History API

使用hash的方式只是HTML4的时代的东西了,前端之所以火起来就是因为HTML5+CSS3的出现,是web丰富起来,其中的 History 就是一个新的特性。

使用 History 来控制路由就比较简单了

history.pushState()

修改URL只需要使用 history.pushState() 就可以了,只需传入需要进入的URL值就行了。

执行前

执行 history.pushState(null,null,”/123”)

使用这个方法URL变得直观,没有那么混乱,但是前端配置的路由会附带到请求当中,需要后端配置一下,不然就会

404 not found !!!

END

2016-12-25 完成

2016-12-21 立项

功能实现之简单的模块化实现

一直在使用模块化,但是一直不知道如何利用JS自主实现,忽然一天我想到了其中的一个可行的方法,下面我就介绍一下。

模块化

因为HTML5 CSS3 的出现,web前端比过去要丰富强大的多。能开发越来越复杂的应用,但当应用越来越复杂的时候,如果还是用以前的方法来编程,前端工程维护成本会越来越高,最后集中到一个点就会一发不可收拾。

为此需要使用模块化来分解复杂的应用,使各个模块间最大限度的解耦,减少代码的重复开发,提高开发效率,降低维护成本。

模块化的JS实现

已有的实现

ES6 import/export

ES6(es2015)中新添加了import和export来实现模块化。

AMD(RequireJS)/CMD(SeaJS)

现在主流的模块化有两个规范AMDCMD,分别的代表为RequireJSSeaJS

模块化的简单实现

1.加载运行JS文件

模块化使各个模块JS存储在不同的文件当中,获取文件并加载运行JS文件是首要解决的问题。

在JS中使用文本实时进行允许的方法主要就两个:

eval()

eval函数是一个解析字符串为JS运行的函数。是最简单的允许JS代码的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//test.js
console.log('我来啦!');

//main.html
ajax({
reqURL:"./test.js",
reqHeader:{
"Accept":"text/plain"
},
reqSuccess:function(data){
eval(data);//输出我来了
}
});

但是eval函数有很多弊端

弊端之一:使用的全局环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//test.js
var x=123;

//main.html
ajax({
reqURL:"./test.js",
reqHeader:{
"Accept":"text/plain"
},
reqSuccess:function(data){
eval(data);
console.log(x);//123
}
});
弊端之二:使用的全局环境,同时无法被JS解析器进行解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//test.js
var x=123;

//main.html
ajax({
reqURL:"./test.js",
reqHeader:{
"Accept":"text/plain"
},
reqSuccess:function(data){
console.log(x);//报错没有x变量,没有进行声明提前
eval(data);
}
});

new Function()

除了 eval 函数,使用new Function()是很好的一个实现模块化的方法。因为它构造了一个函数,提供了函数的作用域,同时使用严格模式,内部的实现就避免了对全局污染。并且可以设置传入对象和传出对像,利于模块化的实现。

模拟实现的一个简单的requier函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//test.js
var x=123;
exports.biu=function(){
console.log('biubiubiu');
}

//main.html
function require(path){
var exports={};
ajax({
reqURL:path,
reqAsync:false,//这里采用同步请求,方便处理(正是同异步处理不同,才有了AMD和CMD两大模块化规范)
reqSuccess:function(data){
new Function('exports',data)(exports);
}
});
return exports;
}

var test=require('./test.js');
console.log(x);//会报错没有X 说明 X的定义没有污染到全局
test.biu();//输出 biubiubiu

这只是简单的实现,模块化考虑和东西还很多,比如就现在的代码还能简单的优化一些

加入严格模式

当我们的代码是这样的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//test.js
x=123;

//main.html
function require(path){
var exports={};
ajax({
reqURL:path,
reqAsync:false,//这里采用同步请求,方便处理(正是同异步处理不同,才有了AMD和CMD两大模块化规范)
reqSuccess:function(data){
new Function('exports',data)(exports);
}
});
return exports;
}

var test=require('./test.js');
console.log(x);//输出123

可见我们可能一时的疏忽没有写声明符就会导致变量变成全局的,导致对全局的污染。

这时我们就可以加入全局模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//test.js
x=123;

//main.html
function require(path){
var exports={};
ajax({
reqURL:path,
reqAsync:false,//这里采用同步请求,方便处理(正是同异步处理不同,才有了AMD和CMD两大模块化规范)
reqSuccess:function(data){
data='"use strict";'+data;
new Function('exports',data)(exports);//Uncaught ReferenceError: y is not defined
}
});
return exports;
}

var test=require('./test.js');

加入严格模式后我们就可以尽量避免对全局的污染,但写代码的时候还是得注意- -、

END

2016-12-19 完成

2016-12-12 立项

typeof null === 'object'

前一段时间写博客,发现了 typeof null === ‘object’ 结果为 TURE!!!

typeof

typeof 是一个JS的操作符,返回一个字符串,指示未经计算的操作数的类型。

用法

typeof operand

operand 是一个表达式,表示对象或原始值,其类型将被返回。

结果列表

类型 结果
Undefined “undefined”
Null “object” (见下方)
Boolean “boolean”
Number “number”
String “string”
Symbol(ECMAScript 6 新增) “symbol”
宿主对象(由JS环境提供) Implementation-dependent
函数对象 ( [[Call]] 在ECMA-262条款中实现了) “function”
任何其他对象 “object”

特殊的NULL

typeof null === ‘object’;

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是0。由于 null 代表的是空指针(大多数平台下值为0x00),因此,null的类型标签也成为了0,typeof null就错误的返回了”object”。

在ES6中有提议将这个问题修复,但被否决了,现在依然是 **typeof null === ‘object’**。

注意

1.typeof 的优先级高于运算

1
2
3
4

console.log(typeof 1+2);//number2
console.log(typeof (1+2));//number

参考资料

MDN-typeof

END

2016-12-13 完成

2016-12-12 立项

JS getter和setter 存取器属性

JS字面量定义数据时除了常用的键值对,还有一种是getter和setter,这篇文章主要就是讲述一下getter和setter的使用

简介

在JS中对象的属性都是由键、值、特性构成的。在 ES5 中,属性值可以由一个或者两个方法替代,这连个方法就是 gettersetter。由 gettersetter 定义的属性叫做 “存取器属性”, 它不同于 “数据属性” ,数据属性只是简单的值。

同时,存取器属性是不可写的。是无法通过简单的赋值操作来完成的,因为对该属性的赋值操作是调用的方法。

getter

getter方法是属性的读方法,当对属性进行读操作时,就会调用getter函数,返回函数返回的值。

如果只有setter方法,那么返回的属性就是 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

var obj={
_name:'123',
set name(value){
this._name=value;
},
get name(){
return this._name;
},
get name2(){
return 'hahh';
}
};

console.log(obj.name);//123
console.log(obj.name2);//hahh

var obj={
_name:'123',
set name(value){
this._name=value;
}
};

console.log(obj.name);//undefined

setter

getter方法是属性的写方法,当对属性进行写操作时,就会调用写函数,并且给函数传入写的数据。

如果只有getter方法,那么无法执行写操作,写操作没有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

var obj={
_name:'123',
set name(value){
this._name=value;
},
get name(){
return this._name;
}
};

console.log(obj._name);//123
obj.name='223'
console.log(obj._name);//223

var obj={
_name:'123',
get name(){
return this._name;
}
};

console.log(obj._name);//123
obj.name='223'
console.log(obj._name);//123

设置getter和setter的方法

字面量初始化

第一种就是在创建对象的时候通过 setget 来设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

var obj={
_name:'123',
set name(value){
this._name=value;
},
get name(){
return this._name;
}
};

console.log(obj.name);//123
obj.name='223'
console.log(obj.name);//223

这个方法可读性很高,但是不灵活,必须代码写好。

通过设置属性特性

通过设置属性特性,可以动态添加一些存取器属性。

Object.create()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

var obj=Object.create(Object.prototype,{
_name:{
value:'123'
},
name:{
get:function(){
return this._name;
},
set:function(value){
this._name=value;
}
}
})

console.log(obj);//123
console.log(obj.name);//123
obj.name='223'
console.log(obj.name);//223

使用这种方式的好处是可配置性高,但初学者容易迷糊。

Object.defineProperty()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

var obj={
_name:'123'
}

Object.defineProperty(obj,'name',{
get:function(){
return this._name;
},
set:function(value){
this._name=value;
}
})

console.log(obj);
console.log(obj.name);//123
obj.name='223'
console.log(obj.name);//223

使用该方法可以随时的添加或修改。修改时就是需要使用这个方法。

Object.defineProperties()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

var obj={
_name:'123'
}

Object.defineProperties(obj,{
name:{
get:function(){
return this._name;
},
set:function(value){
this._name=value;
}
}
})

console.log(obj);
console.log(obj.name);//123
obj.name='223'
console.log(obj.name);//223

这个方法可以一次添加多个存取器属性。

Object.prototype.__defineGetter__() 以及 Object.prototype.__defineSetter__()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var obj={
_name:'123'
}

obj.__defineGetter__('name',function(){
return this._name;
});
obj.__defineSetter__('name',function(value){
this._name=value;
})

console.log(obj);
console.log(obj.name);//123
obj.name='223'
console.log(obj.name);//223

这个方法你可以看成 Object.defineProperty() 的封装版。

END

2016-12-01 完成

2016-11-27 立项

JS原型链 prototype、\_\_proto\_\_ 、constructor

前端时间研究JS对象的深复制,想要保持原有的原型链,折腾了半天才弄出来,主要是对原型链不太熟悉,只是知道一些简单的东西,这篇文章就主要来研究一下 原型链中prototype、__proto__ 、constructor 三者的关系

一切皆对象

JS中的一切数据都是对象(Object),都可以使用 new 来建立,平时我们常用的写法都是语法糖,方便我们的开发。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

var num=new Number('123');
var num2=123;

console.log(num);//Number {[[PrimitiveValue]]: 123}
console.log(typeof num);//object
console.log(num2);//123
console.log(typeof num2);//number
console.log(typeof (num+num2));//number
console.log(num2==num);//true
console.log(num2===num);//false
console.log(num2===num+0);//true
console.log(num2.constructor===num.constructor);//true
console.log(num2.prototype===num.prototype);//true
console.log(num2.__proto__===num.__proto__);//true

我们可以使用 new Number(value) 来创建数字,这个数字对象依然可以参加数值运算,与直接赋值的方法看起来没有差别,但是使用 typeof 可以看到使用 new 创建的数字是一个Object对象,所以在 === 下比较时类型不同,直接为false。

那么是不是所有用 new 创建的对象都为 object 呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

var num=new Number('123');
var num2=123;
console.log(num);//Number {[[PrimitiveValue]]: 123}
console.log(typeof num);//object
console.log(num2);//123
console.log(typeof num2);//number

var str=new String('a');
var str2='a';
console.log(str);//String {0: "a", length: 1, [[PrimitiveValue]]: "a"}
console.log(typeof str);//object
console.log(str2);//a
console.log(typeof str2);//string

var bool=new Boolean(true);
var bool2=true;
console.log(bool);//Boolean {[[PrimitiveValue]]: true}
console.log(typeof bool);//object
console.log(bool2);//true
console.log(typeof bool2);//boolean

var arr=new Array();
var arr2=[];
console.log(arr);//[]
console.log(typeof arr);//object
console.log(arr2);//[]
console.log(typeof arr2);//object

var date=new Date();
console.log(date);//Sat Nov 26 2016 19:05:24 GMT+0800 (中国标准时间)
console.log(typeof date);//object

var nu=null;
var und=undefined;
console.log(typeof nu);//object
console.log(typeof und);//undefined;

var foo=new Function('a,b','return a+b;');
console.log(typeof foo);//function

我们可以看到几乎所有使用 new 创建的数据,的 typeof 都是对象,同时在经过运算后只有基础类型和函数对象的 typeof 是它们自己的类型名,其它系统的本地对象都是对象类型名。但即使是基础类型,也都是对象!,只有函数对象才和基础的对象有一些区别。

基础对象和函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var foo1=new Function('a,b','return a+b;');
var foo2=function(){this.name=1};
function foo3(){}

var obj1={name:1};
var obj2=new Object(obj1);
var obj3=new foo2();

console.log(typeof foo1);//function
console.log(typeof foo2);//function
console.log(typeof foo3);//function

console.log(typeof obj1);//object
console.log(typeof obj2);//object
console.log(typeof obj3);//object

上面的例子中我们可以快速的看到函数无论如何的方法创建,它的类型都是 function 所以平时我们判断这个变量是否为函数,都使用的 typeof 来进行判断。总之函数对象和普通对象是有一些区别的,下面我们慢慢来研究。

原型链

继承

平时我们写JS继承的方法大多是写一个构造函数或者使用 prototype,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

function fun1(){
this.name='cat';
this.age=12
}

fun1.prototype.getName=function(){
return this.name;
};

var o1=new fun1();
console.log(o1.name);//cat
console.log(o1.getName());//cat

function fun2(){}

fun2.prototype=o1;


var o2=new fun2();
console.log(o2.name);//cat
o2.name='dog';
console.log(o2.name);//dog
console.log(o2.getName());//dog

只要经过构造函数和prototype我们就可以实现继承的效果。而这个实现的经过就要了解 原型链 !!!

prototype 和 __proto__

结合上面的代码,我们看下面的代码:

1
2
3
4
5
6
7
8
9

console.log(o2.__proto__===fun2.prototype);//true
console.log(fun2.prototype===o1);//true
console.log(o2.__proto__===o1);//true

console.log(o1.__proto__===fun1.prototype);//true
console.log(fun1.__proto__===Function.prototype);//true
console.log(Function.__proto__===Object.__proto__);//true

fun2 作为o2的构造函数,它的 prototype 和 构造出来的对象的 __proto__ 是相同的,在JS中当两个对象相同时,就说明他们是同一个对象引用。可见使用构造函数后,新建的对象的 __proto__ 属性直接引用构造函数的 prototype 属性。

原型链构成

当一个对象查询自己的属性时,它会首先遍历自己所带的属性,当发现没有这个属性的时候,它遍会去查找 __proto__ 的属性,结合前面的内容可以知道, __proto__ 属性便是构造函数的 prototype,所以新建立的对象可以访问构造函数的 prototype 属性内的属性来达到继承的效果。然后如果 prototype 也没有需要查找的属性,会继续去查找 prototype__proto__ 属性,这样循环往复,直到 __proto__null

1
2
3
4
5
6
7

console.log(o2.__proto__);
console.log(o2.__proto__.__proto__);
console.log(o2.__proto__.__proto__.__proto__);
console.log(o2.__proto__.__proto__.__proto__.__proto__);
console.log(o2.__proto__.__proto__.__proto__.__proto__.__proto__);

注意

在JS中 __proto__ 属性是可以修改的,原型链的结束 null 的出现也只是将Object的 prototype 属性的 __proto__ 的属性设为获取时返回了 null ,可以之前的图中看到。在我们修改后就会失去这个屏障 导致无限循环的查找属性。

1
2
3
4
5
6
7

var a={};
var b={}
a.__proto__=b;
b.__proto__=Object;
Object.__proto__=a;//error!! 会导致无线循环

constructor

在继承中还有一个重要的角色 constructor,每个函数的 prototype 属性中都有一个 constructor 属性,这个属性是指向构造函数的。

1
2
3
4

console.log(fun1.prototype);
console.log(fun1.prototype.constructor===fun1);//true

注意

特别注意的是因为 prototype 属性也是可以编辑的,如果直接给 prototype 赋值覆盖掉过去的,那么就会丢失 constructor

1
2
3
4
5
6
7
8
9
10
11

function f1(){}
f1.prototype={getname:function(){}};

function f2(){}
f1.prototype.getname=function(){};

console.log(f1.prototype.constructor);
console.log(f1.prototype.constructor===Object.prototype.constructor);//true
console.log(f2.prototype.constructor);

可见f1的 constructor 是object的 constructor,因为直接使用了以恶搞对象覆盖 prototype,在实例化的对象的 __proto__ 中找不到 constructor,解释器会沿着原型链查找,导致找出来的是object的 constructor。所以尽量一个个属性复制上去,不要使用字面量的方式。

Object和Function (扯犊子)

1
2
3
4
5
6
7
8
9
10

console.log(Object.prototype.constructor);
console.log(Object.prototype.constructor===Object);//true
console.log(Function.prototype.constructor);
console.log(Function.prototype.constructor===Function);//true
console.log(Object.__proto__);//function(){}
console.log(Function.__proto__);//function(){}
console.log(Object.__proto__===Function.__proto__);//true
console.log(Object.__proto__===Object.prototype);//false

到这就是扯犊子的时候了~~~

为什么要将 Object和Function 明显的区分开呢,因为对象离不开函数,函数离不开对象吧~~~

函数必定是对象,但能实现对象所不能达到的功能~~~

这TM都是别人定义好的,想这么多羔皮= =

总结

  1. 原型链是沿着 __proto__ 对属性进行查找;
  2. 原型链的 __proto__ 继承至构造函数的 prototype;
  3. 可以利用 constructor 获取新的实例;
  4. Object 和 Function 很NB;

END

2016-11-26 完成

2016-11-19 立项

JS复制/拷贝

前端时间写代码,出现了一些恶心的BUG,后来发现是因为改变数据时没有进行深复制导致源数据被修改。于是想写一篇博客来研究如何进行高效的深度复制。

JS的赋值

在JS中我们给数据复制都是通过 = 来赋值,但在JS中除了基本类型外,所有赋值都是采用引用的方法(引用类型),类似于C++中的指针。

当你赋值一个基础类型时,JS解释器会复制一个新的内容给你复制的对象,当你改变其中一个的数据时,另一个是不会改变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

var a = 1;//number类型
var b = 'string';//string类型
var c = true;//布尔类型
var d = undefined;//未定义
var e = null;//未赋值

var a1 = a;
var b1 = b;
var c1 = b;
var d1 = d;
var e1 = e;

var a1 = 2;
console.log(a);//1
console.log(a1);//2

var b1 = 'string2';
console.log(b);//string
console.log(b1);//string2

var c1 = false;
console.log(c);//1
console.log(c1);//1

var d1 = null;
console.log(d);//undefined
console.log(d1);//null

var e1 = undefined;
console.log(e);//null
console.log(e1);//undefined

而当你赋值一个引用类型时,JS解释器只是将引用赋值给赋值对象,这时这两个变量操作的是同一个对象。修改其中一个,另一个也会被修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

var f = {
name:"string",
age:12
};

var f1 = f;

console.log(f);//{name:"string",age:12}
console.log(f1);//{name:"string",age:12}
f1.name="string22222";
console.log(f);//{name:"string22222",age:12}
console.log(f1);//{name:"string22222",age:12}

深拷贝的方法

纯数据拷贝的简单方法

当你需要拷贝的变量,是全有数据组成时可以使用一下简单的方法拷贝;

JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

var g={
a:"string",
b:123,
c:false,
d:null,
f:undefined,
g:[123123,"asdasd"],
h:{
hahah:234
}
};

var g1=JSON.parse(JSON.stringify(g));

console.log(g);
console.log(g1);

console.log(g.a)//string
console.log(g1.a)//string
g1.a="string233";
console.log(g.a)//string
console.log(g1.a)//string233

你可以从上图中看出两个变量引用的是不同的数据了,但值得注意的是克隆出来的数据并没有将 undefined 的数据以及方法克隆出来。

注意

1.只能复制纯数据,会丢失 undefined 以及 function 数据

2.整个数据对象都会被拷贝出来,不会保留任何引用

数组的深拷贝

数组的深度拷贝可以利用数组自带的函数进行, sliceconcat

下面以 slice 函数为例。

slice 函数是将一个数组的一部分返回为一个新的数组给我们,在这里我们直接将整个数组范围包括进去就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

var h=[
"string",
123,
false,
null,
undefined,
[123123,"asdasd"],
{
hah:234
},
function(){
return "asd";
}
];

var h1=h.slice(0,h.length);

console.log(h);
console.log(h1);

console.log(h[0])//string
console.log(h1[0])//string
h1[0]="string233";
console.log(h[0])//string
console.log(h1[0])//string233

console.log(h[6])//{hah:234}
console.log(h1[6])//{hah:234}
h1[6].hah="string233";
console.log(h[6])//{hah:string233}
console.log(h1[6])//{hah:string233}
console.log(h[6]===h1[6])//true
console.log(h[7]===h1[7])//true

这里我们可以看出这个方法拷贝出了全部的信息, undefinedfunction 都拷贝了出来。但是原来数组引用的对象依然是引用的,是引用的同一个对象的。

注意

1.更深一层的引用无法去除

对象的深拷贝

对象的深拷贝主要通过遍历的方法来实现,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

var obj=function(){
this.name="haha";
this.age=123;
this.obj={
newname:"biubiu"
}
}

obj.prototype={
foo:function(){return this.name;}
}

var i=new obj();
var i1=copy(i);

console.log(i);
console.log(i1);
i1.name="haha233";
console.log(i.name);//haha
console.log(i1.name);//haha233

function copy(obj){
var newobj={};
for(var name in obj){
if(typeof obj[name] === 'object'){
newobj[name]=copy(obj[name]);
}else{
newobj[name]=obj[name];
}
}
return newobj;
}

通过这样的简单的函数可以快速的拷贝出对象的数据,但有一些问题:

问题一:循环调用死循环

当我将函数内的一个属性指向这个对象构成相互引用,或者内部有这么一个循环,那么浏览器就会超出堆栈上限报错!

1
2
3
4
5
6
7
8
9

var obj=function(){
this.name="haha";
this.age=123;
this.obj={
newname:this
}
}

所以我们需要将 即将被拷贝拷贝后 的对象存储起来,方便变比对引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

function copy(obj){
var objlist=[];
return clone(obj);

function clone(oldobj){
var newobj={};

objlist.push({
old:oldobj,
new:newobj
});

for(var name in oldobj){
if(typeof oldobj[name] === 'object'){
newobj[name]=getCloned(oldobj[name]);
if(!newobj[name])
newobj[name]=clone(oldobj[name]);
}else{
newobj[name]=oldobj[name];
}
}
return newobj;
}

function getCloned(obj){
var x=false;
objlist.forEach(function(a){
if(obj===a.old) x=a.new;
})
return x;
}
}

这样就解决了这个问题,同时保证了数据内容的逻辑关联。

问题二:原型链继承

第一代码的运行结果中,我们可以看到遍历将原型链上的函数也拷贝下来了,如果原型有数据那么数据也会拷贝下来,然而在实际中我们需要继承原型而不是拷贝出来,不然这样的话原型的变动不会影响到我们克隆出来的对象。

这时我们就要用到原型链中的 proto 了,相关内容会在下一篇文章中讲述。

同时我们需要 hasOwnProperty 函数来判断属性是否为对象自己所有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

var obj=function(){
this.name="haha";
this.age=123;
this.obj={
newname:this
}
}

obj.prototype={
foo:function(){return this.name;},
num:123
}

var i=new obj();
var i1=copy(i);

console.log(i);
console.log(i1);

console.log(i.num);
console.log(i1.num);
obj.prototype.num="haha233";
console.log(i.num);
console.log(i1.num);

function copy(obj){
var objlist=[];
return clone(obj);

function clone(oldobj){
var Constructor=function(){};
Constructor.prototype=oldobj.__proto__;
var newobj=new Constructor();

objlist.push({
old:oldobj,
new:newobj
});

for(var name in oldobj){
if(!oldobj.hasOwnProperty(name)) continue;
if(typeof oldobj[name] === 'object'){
newobj[name]=getCloned(oldobj[name]);
if(!newobj[name])
newobj[name]=clone(oldobj[name]);
}else{
newobj[name]=oldobj[name];
}
}
return newobj;
}

function getCloned(obj){
var x=false;
objlist.forEach(function(a){
if(obj===a.old) x=a.new;
})
return x;
}
}

这样就差不多完成了一个对象的数据克隆,同时保留了内部的逻辑关联和原型链的继承。

刚开始学JS的时候以为这些都很简单,但回头一看这些东西其实很重要同时也很复杂,前端之路任重而道远~~~!!

END

2016-11-19 完成

2016-11-15 立项