数据结构
所谓数据结构,就是计算机存储和组织数据的方式。说得通俗一点,主要就是指将数据以什么样 的结构存储到计算机里面。在程序里面,最为常见的数据结构,就是数组,这种结构将多个数据 有序的排列在一起,形成了一个组合。除了数组以外,集合,映射等ES6新增加的数据结构
创建数组的方式大致可以分为两种:字面量创建数组和使用构造函数创建数组。
1. 字面量创建数组
2. let arr = [];
3. 构造函数创建数组
let arr = new Array();
1. 先声明再赋值
let arr = [];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
注意下标是从o开始的。
2. 声明时直接赋值
let arr = [1,2,3,4,5];
需要注意的是我们可以在数组的任意位置进行赋值,数组的长度会自动改变,空的位置使用
undefined来进行填充
let arr = [];
arr[0] = 1;
arr[4] = 10;
console.log(arr);
什么是集合
在ES6标准制定以前,可选的数据结构类型有限,可以说只有数组这种数据结构。而数组使用的 又是数值型索引,因而经常被用于来模拟队列和栈的行为。但是如果需要使用非数值型索引,就 会用非数组对象创建所需的数据结构,而这就是Set集合与后面一节要介绍的Map映射的早期实 现。
Set集合是一种无重复元素的列表,这是这种数据结构的最大的一个特点。
要创建一个集合,方法很简单,直接使用new就可以创建一个Set对象。如果想要集合在创建时 就包含初始值,那么我们可以传入一个数组进去。
let s1 = new Set();
let s2 = new Set([1,2,3]);
console.log(s1);//Set {}
console.log(s2);//Set { 1, 2, 3 }
4-3-3给集合添加值
使用add()方法可以给一个集合添加值,由于调用add()方法以后返回的又是一个Set对象,所 以我们能连续调用add()方法进行值的添加,这种像链条一样的方法调用方式被称为链式调 用。
let s1 = new Set();
s1.add(1);
console.log(s1);//Set { 1 }
s1.add(2).add(3).add(4);
console.log(s1);
//Set { 1, 2, 3, 4 } 我们还可以直接将一个数组传入add()方法里面
let s1 = new Set();
s1.add([1,2,3]);
console.log(s1);
//Set { [ 1, 2, 3 ] }
let s1 = new Set([1,2,3]);
console.log(s1);//Set { 1, 2, 3 } console.log(s1.size);//3
调用add()方法时传入数组,就是作为Set对象的一个元素
let s1 = new Set();
s1.add([1,2,3]);
console.log(s1);//Set { [ 1, 2, 3 ] } console.log(s1.size);//1
在Set对象中,不能够添加相同的元素,这是很重要的一个特性
let s1 = new Set();
s1.add(1).add(2).add(2).add(3);
console.log(s1);
//Set { 1, 2, 3 }
1. 用size属性获取元素个数
let s1 = new Set([1,2,3]);
console.log(s1.size);//3
2. 使用hasO方法来查看一个集合中是否包含某一个值
let s1 = new Set([1,2,3]);
console.log(s1.has(1));//true
3. 删除集合值
使用delete删除Set对象里面的某一个元素
let s1 = new Set([1,2,3]);
s1.delete(2);
console.log(s1);//Set { 1, 3 }
//没有的元素也不会报错
s1.delete("2");
console.log(s1);//Set { 1, 3 }
如果要一次性删除所有的元素,可以使用clear方法
let s1 = new Set([1,2,3]);
s1.clear()
console.log(s1);//Set {}
遍历集合
集合也是可以枚举的,我们同样可以使用for-o f来对集合进行遍历,如下:
let s = new Set([1,2,3,4,5]);
for(let i of s){ console.log(i);
}
// 1
// 2
// 3
// 4
// 5
除此之外,我们也可以使用集合里面自带的keys()
, values()以及ent ries()方法来对集合 进行遍历。顺便要说一下的是,在集合里面键和值是相同的。
keys()方法遍历集合的键
let s = new Set(["Bill","Lucy","David"]); for(let i of s.keys()){
console.log(i);
}
// Bill
// Lucy
// David
values()方法遍历集合的值
let s = new Set(["Bill","Lucy","David"]); for(let i of s.values()){
console.log(i);
}
// Bill
// Lucy
// David
ent ries()方法同时遍历集合的键与值
let s = new Set(["Bill","Lucy","David"]); for(let i of s.entries()){
console.log(i);
}
// [ 'Bill', 'Bill' ]
// [ 'Lucy', 'Lucy' ]
// [ 'David', 'David' ]
集合转数组
将集合转为数组,最快的方法就是使用前面所讲过的扩展运算符,如下:
let s1 = new Set([1,2,3]);
console.log(s1);//Set { 1, 2, 3 }
let arr = [...s1]; console.log(arr);//[ 1, 2, 3 ]
除此之外,我们还可以使用Arra y对象所提供的from。方法来进行转换
let s1 = new Set([1,2,3]); console.log(s1);//Set { 1, 2, 3 }
let arr = Array.from(s1); console.log(arr);//[ 1, 2, 3 ]
前面我们有提到过,Set对象里面是不能够存放相同的元素的,利用这个特性,我们可以快速的 为数组去重,如下:
let arr = [1,2,2,3,4,3,1,6,7,3,5,7];
let s1 = new Set(arr);
let arr2 = [...s1]; console.log(arr2);//[ 1, 2, 3, 4, 6, 7, 5 ]
什么是内存泄漏?
—个程序里面保留着已经不能在内存中访问的值时,就会发生内存泄露,也就是说占着空间却没 用,造成内存的浪费。
例如:
let arr = [1,2,3]; arr = null;
断开了arr对1, 2, 3的引用,现在1, 2, 3在内存里面已经是垃圾了。内存泄露会逐渐减少全部 可用内存,导致程序和系统的速度变慢甚至崩溃
那么怎样才能清空这些没用的数据呢?例如上例中的1, 2, 3。事实上在JavaScript中采用的是 动态内存管理技术,比如垃圾回收机制,会自动从内存中删除不再被程序需要的东西。而有些编 程语言,例如C++,则是需要程序员手动的管理内存,在某些东西完成任务之后,将其从内存中 删除。
那么,集合的问题就在于即使失去了引用,也不会被垃圾回收,这个时候我们可以使用弱集合来 避免这种状况。创建弱集合使用new运算符和WeakSet()
let weak = new WeakSet();
弱集合无法添加基本数据类型,也就是说无法像集合那样添加简单值进去。
let weak = new WeakSet();
weak.add(1);
//TypeError: Invalid value used in weak set
除了这个限制以外,弱集合和普通集合还有一些细微的区别,例如无法在创建弱集合时传入一个 数组进行初始化。不过弱集合也拥有has() , add() , delete()等方法。
let arr = [1,2,3,4,5];
let weak = new WeakSet(arr);
//TypeError: Invalid value used in weak set
//无法在创建弱集合时传入一个数组进行初始化
还需要注意一点的是,弱集合是对对象的弱引用,所以不能访问对象里面的值列表。这使得弱集 合看上去像是空的,但是并不是空的,证明如下:
let weak = new WeakSet();
let arr = [1,2,3];
weak.add(arr);
console.log(weak);//WeakSet {}
console.log(weak.has(arr));//true
console.log(s1.has("1"));//false
使用new关键字与Map()构造函数,就可以创建一个空的m ap对象。如果要向Map映射中添加 新的元素,可以调用set()方法并分别传入键名和对应值作为两个参数。如果要从集合中获取 信息,可以调用get()方法。
let m = new Map();
m.set("name","xiejie");
m.set("age",18);
console.log(m);
//Map { 'name' => 'xiejie', 'age' => 18 }
console.log(m.get("name"));
//xiejie
在对象中,无法用对象作为对象属性的键名。但是在Map映射中,却可以这样做,可以这么说, 在Map映射里面可以使用任意数据类型来作为键。
let m = new Map();
m.set({},"xiejie"); m.set([1,2,3],18);
m.set(3581,18);
console.log(m);
//Map { {} => 'xiejie', [ 1, 2, 3 ] => 18, 3581 => 18 }传入数组来初始化Map映射 可以向Map构造函数传入一个数组来初始化Map映射,这一点同样与Set集合相似。数组中的每 个元素都是一个子数组,子数组中包含一个键值对的键名与值两个元素。因此,整个Map映射中 包含的全是这样的两个元素的二维数组
let arr = [["name","xiejie"],["age",18]];
let m = new Map(arr);
console.log(m);
//Map { 'name' => 'xiejie', 'age' => 18 }
Map结构转为数组结构,比较快速的方法还是使用前面介绍过的扩展运算符...。
let arr = [["name","xiejie"],["age",18]];
let m = new Map(arr);
console.log([...m.keys()]);//[ 'name', 'age' ]
console.log([...m.values()]);//[ 'xiejie', 18 ]
console.log([...m.entries()]);//[ [ 'name', 'xiejie' ], [ 'age', 18 ] ]
console.log([...m]);//[ [ 'name', 'xiejie' ], [ 'age', 18 ] ] 或者使用Array对象的from。方法
let arr = [["name","xiejie"],["age",18]];
let m = new Map(arr);
console.log(Array.from(m));
//[ [ 'name', 'xiejie' ], [ 'age', 18 ] ]
函数
函数,是可以通过名称来引用,并且就像自包含了一个微型程序的代码块。利用函数,我们可以 实现对代码的复用,降低代码的重复,并且让代码更加容易阅读。在JavaScript中,函数显得尤 为的重要。因为函数在JavaScript中是一等公民,可以像参数一样传入和返回。所以说函数是 JavaScript中的一个重点,同时也是一个难点。
为什么需要函数
首先我们来看一下为什么需要函数。函数最大的好处就是可以对代码实现复用。相同的功能不用 再次书写,而是只用书写一次就够了。这其实就是编程里面所谓的DRY原则
所谓DRY原则,全称为Don‘t repeat yourself,翻译成中文就是不要重复你自己。什么意思 呢?也就是说一个程序的每个部分只被编写一次,这样做可以避免重复,不需要保持多个代 码段的更新和重复。
并且,我们可以把函数看作是一个暗箱,不需要知道函数的内部是怎么实现的,只需要知道函数 的功能,参数以及返回值即可。
声明函数的方式
在JavaScript中,声明函数的方式有多种,这里我们先来介绍这3种声明函数的方式:字面量声明 函数,函数表达式声明函数以及使用构造器方式来声明一个函数。
1. 字面量声明函数
这种方式是用得最为广泛的一种方式,使用关键字function来创建一个函数,具体的语法如 下:
function函数名(形式参数){
//函数体
}
函数名:就是我们调用函数时需要书写的标识符
形式参数:简称形参,是调用函数时需要接收的参数
实际参数:简称实参,是调用函数时实际传递过去的参数
function test(name){
console.log("Hello,"+name);
}
test("xiejie");//Hello,xiejie
2. 函数表达式声明函数
第二种方式是使用函数表达式来进行声明,具体的语法如下:
let 变量 =function(){
//函数体
}
函数表达式示例:
let test = function(name){
console.log("Hello,"+name);
} test("xiejie");//Hello,xiejie
我们也可以将一个带有名字的函数赋值给一个变量。这样的声明方式被称之为命名式函数 表达式
示例如下:
let test = function saySth(name){
console.log("Hello,"+name);
}
test("xiejie");//Hello,xiejie
注意:虽然这种方式的函数表达式拥有函数名,但是调用的时候还是必须要通过变量名来调 用,而不能够使用函数名来进行调用。
函数的调用
函数的调用在前面介绍函数声明的时候也已经见到过了,就是写上函数名或者变量名,后面加上 —对大括号即可。需要注意的是,一般来讲函数表示的是一个动作,所以在给函数命名的时候, —般都是以动词居多。
还一个地方需要注意,那就是如果要调用函数,那么就必须要有括号。这个括号要么在函数名后 面,要么在变量名后面,这样才能够将调用函数后的执行结果返回。如果缺少了括号,那就只是 引用函数本身。
let test = function(){ console.log("Hello");
}
let i = test;//没有调用函数,而是将test函数赋值给了i i();//Hello
函数的返回值
函数的返回值的关键字为ret urn。代表要从函数体内部返回给外部的值,示例如下:
let test = function(){ return "Hello";
}
let i = test();
console.log(i);//Hello
即使不写return ,函数本身也会有返回值undefined 例如:
let test = function(){
console.log("Hello");
}
let i = test();//Hello
console.log(i);//undefined
需要注意的是,retu rn后面的代码是不会执行的,因为在函数里面一旦执行到ret urn ,整个 函数的执行就结束了。
let test = function(){ return 1; console.log("Hello");
}
let i = test();
console.log(i);//1
retu rn关键字只能返回一个值,如果想要返回多个值,可以考虑返回一个数组。
函数的参数
函数的参数可以分为两种,一种是实际参数,另外一种是形式参数。这个我们在前面已经介绍过 了。接下来我们来详细看一下形式参数。形式参数简称形参,它就是一种变量,但是这种变量只 能被函数体内的语句使用,并在函数调用时被赋值。JavaScript中的形参的声明是不需要添加关 键字的,如果加上关键字反而会报错
示例:
function test(let i){ console.log(i);
}
test(5);
//SyntaxError: Unexpected identifier
JavaScript里面关于函数的形参,有以下几个注意点: 1•参数名可以重复,同名的参数取最后一个参数值
function test(x,x){
console.log(x);
}
test(3,5);//5
2•即使函数声明了参数,调用时也可以不传递参数值
function test(x){ console.log(x);
}
test();//undefined
3•调用函数时可以传递若干个参数值给函数,而不用管函数声明时有几个参数
function test(x){
console.log(x);//1
}
test(1,2,3);
那么,这究竟是怎么实现的呢?为什么实参和形参可以不用一一对应呢?
实际上,当一个函数要被执行的时候,系统会在执行函数体代码前做一些初始化工作,其中之一 就是为函数创建一个arguments的伪数组对象。这个伪数组对象将包含调用函数时传递的所有的 实际参数。因此,arguments的主要用途是就是用于保存传入到函数的实际参数的。
下面的例子演示了通过arguments伪数组对象来访问到所有传入到函数的实参
function test(x){
for( let i=0;i<arguments.length;i++){
console.log(arguments[i]);
}
}
test(1,2,3);
// 1
// 2
// 3
所谓伪数组对象,就是长得像数组的对象而已,但是并不是真的数组,我们可以证明这一点
function test(x){
ar guments .push( 100); //针对伪数组对象使用数组的方法
}
test(1,2,3);
//TypeError: arguments.push is not a function
不定参数
不定参数是从ES6开始新添加的功能,在最后一个形式参数前面添加3个点,会将所有的实参放 入到一个数组里面,示例如下:
function test(a,...b){
console.log(a);//1
console.log(b);//[2,3]
test(1,2,3);
这里的不定参数就是一个真正的数组,可以使用数组的相关方法
function test(a,...b){ console.log(a);//1 console.log(b);//[2,3] b.push(100);
console.log(b);//[ 2, 3, 100 ]
} test(1,2,3);
还有一点需要注意的是,不定参数都是放在形式参数的最后面,如果不是放在最后,则会报错。
function test(...a,b){ console.log(a); console.log(b);
} test(1,2,3);
//SyntaxError: Rest parameter must be last formal parameter
默认参数
从ES6开始,书写函数的时候可以给函数的形式参数一个默认值。这样如果在调用函数时没有传 递相应的实际参数,就使用默认值。如果传递了相应的实际参数,则使用传过去的参数。
function test(name = "world"){ console.log("Hello,"+name);
} test("xiejie");//Hello,xiejie test();//Hello,world
如果参数是一个数组,要为这个数组设置默认值的话,写法稍微有些不同,如下:
let fn = function([a = 1,b = 2] = []){ console.log(a,b);
}
fn(); // 1 2
fn([3,4]); // 3 4
包括后面我们要介绍的对象,也是可以设定默认值的,但是写法和上面类似,如下:
let fn = function({name = 'xiejie',age = 18} = {}){ console.log(name,age);
}
fn(); // xiejie 18
fn({name:"song",age:20}); // song 20