ES6
- ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准
- 目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言
- ES6 主要是为了解决 ES5 的先天不足,增加部分特性和功能
let与const
函数作用域 VS 块作用域
ES5只有全局作用域和函数作用域,没有块级作用域,常用的做法是使用闭包。
let和const是块级作用域,变量只能在let和const命令所在的代码块有效。
1
2for( var i = 0 ; i < 3 ; i++ ){ console.log(i) }; console.log(i); // 0 1 2 3
for( let i = 0 ; i < 3 ; i++ ){ console.log(i) }; console.log(i); // 0 1 2 ReferenceError变量提升 VS 暂时性死区
var存在变量提升,let和const存在暂时性死区。
1
2console.log(b); var b = "banana"; // undefined
console.log(b); let b = "banana"; // Uncaught ReferenceError: b is not defined不允许重复声明
var变量可以多次声明,而let和const不允许在相同作用域内,重复声明同一个变量。
1
2var d = 1; var d = 2; console.log(d); // 2
let b = 3; let b = 4; // Identifier 'b' has already been declared全局变量 VS 全局对象的属性
ES5中声明的全局变量属于全局对象( window/global )的属性。
ES6 中使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用
let
**,const
** 命令声明的全局变量不属于全局对象的属性。1
2var b = 3; console.log(window.b); // 3
let d = 5; console.log(window.d); // undefinedconst注意事项
const声明一个只读的常量,一旦声明,常量的值就不能改变。const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动( 简单数据类型/复杂数据类型 )。
1
2const names = []; names[0] = 1; console.log(names); // [1]
const names = []; names = [1]; console.log(names); // Assignment to constant variable.
解构赋值
解构模型
右边:解构的源
左边:解构的目标
解构(Array)
基本
1
2
3
4let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3可嵌套
1
2
3
4let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3可忽略
1
2
3let [a, , b] = [1, 2, 3];
// a = 1
// b = 3不完全解构
1
2
3let [a = 1, b] = [];
// a = 1,
// b = undefined剩余运算符
1
2
3let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
字符串解构(String)
- 字符串解构
1
2
3
4
5
6let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'
- 字符串解构
对象解构(Object)
基本
1
2
3
4
5
6let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'可嵌套
1
2
3
4let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'可忽略
1
2
3let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj;
// x = 'hello'不完全解构
1
2
3
4let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj;
// x = undefined
// y = 'world'剩余运算符
1
2
3
4let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}
Symbol
ES6引入新的数据类型,表示独一无二的值,常用来定义对象的唯一属性名
基本用法
1
2
3
4
5
6
7let sy = Symbol("KK");
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false属性用法(公有属性)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var mySymbol = Symbol();
//第一种写法
var a = {};
a[mySymbol] = "Hello!";
//第二种写法
var a = {
[mySymbol]: "Hellow!"
}
//第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: "Hellow!" });
//以上写法的结果都相同
a[mySymbol] // "Hellow!"
常量用法
1
2
3
4
5
6
7
8
9
10
11
12
13const COLOR_RED = Symbol();
const COLOR_GREEN = Symbol();
function getComponent(color) {
switch(color) {
case: COLOR_RED:
return "red";
case: COLOR_GREEN:
return "green";
default:
throw new Error("Undefind color")
}
}Symbol.for()
全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索
1 | let yellow = Symbol("Yellow"); |
- Symbol.keyFor()
返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记
1 | let yellow1 = Symbol.for("Yellow"); |
Map和Set
Map
Map中的key
- key 是字符串
1
2
3
4
5var myMap = new Map();
var keyString = "a string";
myMap.set(keyString, "和键'a string'关联的值"); // "和键'a string'关联的值"
myMap.get("a string"); // "和键'a string'关联的值"
myMap.get(keyString); // 因为 keyString === 'a string'- key 是对象
1
2
3
4
5var myMap = new Map();
var keyObj = {};
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.get({}); // "和键 keyObj 关联的值"
myMap.get(keyObj); // undefined, 因为 keyObj !== {}- key 是函数
1
2
3
4
5var myMap = new Map();
var keyFunc = function () {}; // 函数
myMap.set(keyFunc, "和键 keyFunc 关联的值");
myMap.get(keyFunc); // "和键 keyFunc 关联的值"
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}- key 是 NaN
1
2
3
4
5var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"Map的迭代
for…of
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one"
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
// 将会显示两个log。 一个是 "0" 另一个是 "1"
for (var key of myMap.keys()) {
console.log(key);
}
/* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */
// 将会显示两个log。 一个是 "zero" 另一个是 "one"
for (var value of myMap.values()) {
console.log(value);
}
/* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */forEach()
1
2
3
4
5
6
7
8var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
// 将会显示两个 logs。 一个是 "0 = zero" 另一个是 "1 = one"
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
}, myMap)
Map对象操作
Map 与 Array的转换
1
2
3
4
5
6
7var kvArray = [["key1", "value1"], ["key2", "value2"]];
// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
var myMap = new Map(kvArray);
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
var outArray = Array.from(myMap);Map 的克隆
1
2
3
4
5var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);
console.log(original === clone);
// 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。Map 的合并
1
2
3
4
5var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
Set
Set 对象存储的值总是唯一的
+0 与 -0 在存储判断唯一性的时候是恒等的,所以不能重复;
undefined 与 undefined 是恒等的,所以不能重复;
NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不能重复。
类型转换
Array转Set
1
2
3var mySet = new Set(["value1", "value2", "value3"]);
// 用...操作符,将 Set 转 Array
var myArray = [...mySet];String转Set
1
2var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"}
// 注:Set 中 toString 方法是不能将 Set 转换成 String
Set对象作用
- 数组去重
1
2var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4] - 并集
1
2
3var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4} - 交集
1
2
3
4
5var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set(
[...a].filter(x => b.has(x))
); // {2, 3} - 差集
1
2
3
4
5var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set(
[...a].filter(x => !b.has(x)
)
- 数组去重
Reflect与Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
字符串
子串的识别
1
2
3
4
5let string = "apple,banana,orange"; //均可接受两个参数,(需要搜索的字符串,可选的搜索起始位置索引)
string.includes("banana"); // true //返回布尔值,判断是否找到参数字符串
string.startsWith("apple"); // true //返回布尔值,判断参数字符串是否在原字符串的头部
string.endsWith("apple"); // false //返回布尔值,判断参数字符串是否在原字符串的尾部
string.startsWith("banana",6) // true //返回子串的位置使用indexOf、lastIndexOf字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14//一般使用
console.log("Hello,".repeat(2)); // "Hello,Hello,"
//如果参数是小数,向下取整
console.log("Hello,".repeat(3.2)); // "Hello,Hello,Hello,"
//如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
console.log("Hello,".repeat(-0.5)); // ""
//如果参数是 NaN,等同于 repeat 零次
console.log("Hello,".repeat(NaN)); // ""
//如果参数是负数或者 Infinity ,会报错:
console.log("Hello,".repeat(-1)); // RangeError: Invalid count value
console.log("Hello,".repeat(Infinity)); // RangeError: Invalid count value
//如果传入的参数是字符串,则会先将字符串转化为数字
console.log("Hello,".repeat("hh")); // ""
console.log("Hello,".repeat("2")); // "Hello,Hello,"字符串补全
padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
1
2
3
4
5
6
7
8
9
10//一般使用
console.log("h".padStart(5,"o")); // "ooooh"
console.log("h".padEnd(5,"o")); // "hoooo"
console.log("h".padStart(5)); // " h"
//如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
//如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
//常用于补全位数:
console.log("123".padStart(10,"0")); // "0000000123"模板字符串
模板字符串相当于加强版的字符串,用反引号 `
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//普通字符串
let string = `Hello'\n'world`;
console.log(string);
// "Hello'
// 'world"
//多行字符串:
let string1 = `Hey,
can you stop angry now?`;
console.log(string1);
// Hey,
// can you stop angry now?
//字符串插入变量和表达式,变量名和表达式写在 ${} 中
let name = "Mike";
let age = 27;
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info);
// My Name is Mike,I am 28 years old next year.
//字符串中调用函数:
function f(){
return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2); // Game start,have fun!标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19alert`Hello world!`;
// 等价于
alert('Hello world!');
function f(stringArr,...values){
let result = "";
for(let i=0;i<stringArr.length;i++){
result += stringArr[i];
if(values[i]){
result += String(values[i]).replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
}
return result;
}
name = '<Amy&MIke>';
f`<p>Hi, ${name}.I would like send you some message.</p>`;
// <p>Hi, <Amy&MIke>.I would like send you some message.</p>国际化处理
1
2i18n`Hello ${name}, you are visitor number ${visitorNumber}.`;
// 你好**,你是第**位访问者
数值
数值的表示
1
2
3
4
5
6
7
8
9
10//二进制表示法新写法: 前缀 0b 或 0B
console.log(0b11 === 3); // true
console.log(0B11 === 3); // true
//八进制表示法新写法: 前缀 0o 或 0O
console.log(0o11 === 9); // true
console.log(0O11 === 9); // true
//Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差(2 -52)
Number.EPSILONMath的扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Math.cbrt //用于计算一个数的立方根。
Math.imul //两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.hypot //用于计算所有参数的平方和的平方根。
Math.clz32 //用于返回数字的32 位无符号整数形式的前导0的个数
Math.trunc //用于返回数字的整数部分。
Math.fround //用于获取数字的32位单精度浮点数形式。
Math.sign //判断数字的符号(正、负、0)
Math.expm1()//用于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1 。
Math.log1p(x)//用于计算1 + x 的自然对数,即 Math.log(1 + x) 。
Math.log10(x)//用于计算以 10 为底的 x 的对数。
Math.log2() //用于计算 2 为底的 x 的对数。
//双曲函数方法
Math.sinh(x): //用于计算双曲正弦。
Math.cosh(x): //用于计算双曲余弦。
Math.tanh(x): //用于计算双曲正切。
Math.asinh(x): //用于计算反双曲正弦。
Math.acosh(x): //用于计算反双曲余弦。
Math.atanh(x): //用于计算反双曲正切。
//指数运算符
1 ** 2; // 1
// 右结合,从右至左计算
2 ** 2 ** 3; // 256
// **=
let exam = 2;
exam ** = 2; // 4
对象
对象字面量
属性的简洁表示
1
2
3
4
5
6const age = 12;
const name = "Amy";
const person = {age, name};
person //{age: 12, name: "Amy"}
//等同于
const person = {age: age, name: name}方法名的简写
1
2
3
4
5
6
7
8
9
10
11
12
13const person = {
sayHi(){
console.log("Hi");
}
}
person.sayHi(); //"Hi"
//等同于
const person = {
sayHi:function(){
console.log("Hi");
}
}
person.sayHi();//"Hi"属性名表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
const obj = {
["he"+"llo"](){
return "Hi";
}
}
obj.hello(); //"Hi"
//属性的简洁表示法和属性名表达式不能同时使用,否则会报错
const hello = "Hello";
const obj = {
[hello]
};
obj //SyntaxError: Unexpected token }
const hello = "Hello";
const obj = {[hello+"2"]:"world"};
obj //{Hello2: "world"}
对象扩展运算符
基本用法
1
2
3let person = {name: "Amy", age: 15};
let someone = { ...person };
someone; //{name: "Amy", age: 15}合并对象
1
2
3
4let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
person; //{age: 15, name: "Amy"}注意点
1
2
3
4//自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: "Amy", age: 15};
let someone = { ...person, name: "Mike", age: 17};
someone; //{name: "Mike", age: 17}
对象的新方法
Object.assign(target, source_1, ···)
用于将源对象的所有可枚举属性复制到目标对象中。
1
2
3
4
5
6let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
// 第一个参数是目标对象,后面的参数是源对象
target; // {a: 1, b: 2, c: 3如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回,因为 null 和 undefined 不能转化为对象,所以会报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//assign 的属性拷贝是浅拷贝:
let sourceObj = { a: { b: 1}};
let targetObj = {c: 3};
Object.assign(targetObj, sourceObj);
targetObj.a.b = 2;
sourceObj.a.b; // 2
//同名属性替换
targetObj = { a: { b: 1, c:2}};
sourceObj = { a: { b: "hh"}};
Object.assign(targetObj, sourceObj);
targetObj; // {a: {b: "hh"}}
//数组的处理会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
Object.assign([2,3], [5]); // [5,3]Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。
1
2
3
4
5
6
7
8
9
10
11
12
13//基本用法
Object.is("q","q"); // true
Object.is(1,1); // true
Object.is([1],[1]); // false
Object.is({q:1},{q:1}); // false
//与(===)的区别
//一是+0不等于-0
Object.is(+0,-0); //false
+0 === -0 //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN //false
数组
Array.of()
1
2
3
4
5
6
7
8//将参数中所有值作为元素形成数组。
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
// 参数为空时返回空数组
console.log(Array.of()); // []Array.from()
将类数组对象或可迭代对象转化为数组。
1
2
3console.log(Array.from([1, 2])); // [1, 2]
// 参数含空位
console.log(Array.from([1, , 3])); // [1, undefined, 3]参数 Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike
想要转换的类数组对象或可迭代对象。console.log(Array.from([1, 2, 3])); // [1, 2, 3]
mapFn
可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
thisArg
可选,用于指定 map 函数执行时的 this 对象。
1
2
3
4
5
6
7
8let map = {
do: function(n) {
return n * 2;
}}
let arrayLike = [1, 2, 3];
console.log(
Array.from(arrayLike, function (n){ return this.do(n);}, map)
); // [2, 4, 6]
扩展方法
查找
find()
查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素
findIndex()
查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
填充
fill()
将一定范围索引的数组元素内容填充为单个指定的值。
copyWithin()
将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。
遍历
entries()
遍历键值对。
keys()
遍历键名。
values()
遍历键值。
包含
includes()
数组是否包含指定值。
嵌套数组转一维数组
flat()
1
2
3
4
5
6
7console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
// 指定转换的嵌套层数
console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
// 不管嵌套多少层
console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
// 自动跳过空位
console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]1
2
3
4
5
6
7
- flatMap()
```js
// 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组
// 参数2:指定遍历函数中 this 的指向
console.log([1, 2, 3].flatMap(n => [n * 2])); // [2, 4, 6]
数组缓冲区
数组缓冲区是内存中的一段地址,是定型数组的基础,实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
创建数组缓冲区
1
2
3
4
5
6let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); // 10
分割已有数组缓冲区
let buffer = new ArrayBuffer(10);
let buffer1 = buffer.slice(1, 3);
console.log(buffer1.byteLength); // 2视图
1
2
3
4
5
6
7
8
9
10// 默认 DataView 可操作数组缓冲区全部内容
let buffer = new ArrayBuffer(10);
dataView = new DataView(buffer);
dataView.setInt8(0,1);
console.log(dataView.getInt8(0)); // 1
// 通过设定偏移量(参数2)与长度(参数3)指定 DataView 可操作的字节范围
let buffer1 = new ArrayBuffer(10);
dataView1 = new DataView(buffer1, 0, 3);
dataView1.setInt8(5,1); // RangeError
定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区。
创建
通过数组缓冲区生成
1
2
3let buffer = new ArrayBuffer(10),
view = new Int8Array(buffer);
console.log(view.byteLength); // 10
扩展运算符
复制数组
1
2
3
4
5
6
7
8let arr = [1, 2],
arr1 = [...arr];
console.log(arr1); // [1, 2]
// 数组含空位
let arr2 = [1, , 3],
arr3 = [...arr2];
console.log(arr3); [1, undefined, 3]合并数组
1
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
函数
函数参数的扩展
默认参数
1
2
3
4
5
6function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17不定参数
不定参数用来表示不确定参数个数,形如,…变量名,具名参数只能放在参数组的最后,并且有且只有一个不定参数。
1
2
3
4
5function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
箭头函数
- 参数 => 函数体
1
2
3
4
5
6
7
8
9
10
11
12
13
14var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8
//当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}- 箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
迭代器
terator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:
- 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。
- 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)
for…of循环遍历,可遍历Array、String、Map、Set、arguments
普通对象不可以迭代,数据结构类似于数组的形式,则可以借助 Array.from() 方法进行转换迭代。
1
2
3
4
5
6
7
8
9
10
11
12
13const arrayLink = {length: 2, 0: "zero", 1: "one"}
// 报 TypeError 异常
for (let item of arrayLink) {
console.log(item);
}
// 正常运行
// output:
// zero
// one
for (let item of Array.from(arrayLink)) {
console.log(item);
}
class类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类,class 的本质是 function。
基本用法
类定义
1
2
3
4
5
6
7
8
9
10
11
12// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
}类声明
1
2
3
4
5class Example {
constructor(a) {
this.a = a;
}
}注意要点
1
2
3类定义不会被提升,必须在访问前对类进行定义,类中方法不需要 function 关键字,方法间不能加分号。
new Example();
class Example {}类的实例化
1
2
3
4//class 的实例化必须通过 new 关键字。
class Example {}
let exam1 = Example();
// Class constructor Example cannot be invoked without 'new'
decorator
类修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//第一个参数 target,指向类本身。
function testable(target) {
target.isTestable = true;
}
@testable
class Example {}
Example.isTestable; // true
//多个参数——嵌套实现
function testable(isTestable) {
return function(target) {
target.isTestable=isTestable;
}
}
@testable(true)
class Example {}
Example.isTestable; // true方法修饰
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//3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)。
class Example {
@writable
sum(a, b) {
return a + b;
}
}
function writable(target, name, descriptor) {
descriptor.writable = false;
return descriptor; // 必须返回
}
//修饰器执行顺序,由外向内进入,由内向外执行。
class Example {
@logMethod(1)
@logMthod(2)
sum(a, b){
return a + b;
}
}
function logMethod(id) {
console.log('evaluated logMethod'+id);
return (target, name, desctiptor) => console.log('excuted logMethod '+id);
}
// evaluated logMethod 1
// evaluated logMethod 2
// excuted logMethod 2
// excuted logMethod 1
封装与继承
getter/setter
extends
class Child extends Father {...}
super
注意要点
模块
ES6 的模块化分为导出(export) @与导入(import)两个模块。
以对象属性的形式export和import
一般形式
1
2
3
4
5
6//export.js
export let x=1;
export let y=2;
import{x,y} from "./export.js"
console.log(x,y)//输出x=1,y=2函数名的形式
1
2
3
4
5
6//export.js
export function x(){
}
import {x} from "./export.js";
console.log(x)//输出的为x函数import as
1
2
3
4
5
6
7
8/export.js
export let x=1;
export let y=2;
//import.js
import * as myVar from "./export.js"
console.log(myVar.x)//输出为1
console.log(myVar.y)//输出为2
以模板的形式export和import
- export
1
2//export.js
export default let x=1; - import
1
2import x from "./export.js";
console.log(x) //输出的是x
- export
Promise
异步编程的一种解决方案,从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
1
2
3
4
5
6
7
8
9
10
11
12
13const p1 = new Promise(function(resolve,reject){
resolve('success1');
});
const p2 = new Promise(function(resolve,reject){
resolve('success2');
reject('reject');
});
p1.then(function(value){
console.log(value); // success1
});
p2.then(function(value){
console.log(value); // success2
});then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
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
27function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10); //生成1-10的随机数
if(num<=5){
resolve(num);
}
else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber()
.then(
function(data){
console.log('resolved');
console.log(data);
},
function(data){
console.log('rejected');
console.log(data);
}
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
- **catch类似于then的第二参数,并有try/catch语句的功能,注意总是返回或终止promise**
```js
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});all的用法
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调,all会把所有异步操作的结果放进一个数组中传给then。
1
2
3
4
5Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});race方法
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。
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//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
Generator
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
- Generator 有两个区分于普通函数的部分(* 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态)
- 一是在 function 后面,函数名之前有个 * ;
- 函数内部有 yield 表达式。
1
2
3
4
5
6
7
8function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
async
async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。
1
2
3
4//name: 函数名称。
//param: 要传递给函数的参数的名称。
//statements: 函数体语句。
async function name([param[, param[, ... param]]]) { statements }返回值
1
2
3
4
5
6
7
8
9async function helloAsync(){
return "helloAsync";
}
console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
console.log(v); // helloAsync
})awync表达式
async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function testAwait(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("testAwait");
resolve();
}, 1000);
});
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsyncawait针对所跟不同表达式的处理方式:
Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
非 Promise 对象:直接返回对应的值。
1
2
3
4
5
6
7
8
9
10function testAwait(){
console.log("testAwait");
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
补充
补充:闭包
闭包
闭包是指有权访问另一个函数作用域中的变量的函数
特点:
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包的作用
- 可以在函数的外部访问到函数内部的局部变量
- 让这些变量始终保存在内存中,直到访问变量的那个函数被销毁。
作用域链
JavaScript高级程序设计(第3版)
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
实现原理
函数A内部所在的其它函数B可以访问到当前函数A的内部变量,那么如果我们将其内部所在的其它函数B作为返回值将其返回,并在函数A的外部用一个变量C来接收到这个返回值。那么这样,在函数A外部操作这个变量C时,实际上就是在操作函数A的返回值,也就是函数A所生成的包含环境中所在的内部函数B。而这个内部函数B是有权访问到函数A的内部变量的,所以在函数A外部的变量也就可以访问到函数A内部的变量。
闭包的主要形式
函数作为返回值
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
43function a(){
var name = 'dov';
return function(){
return name;
}
}
var b = a();
console.log(b()) //dov
function fn(){
var num = 3;
return function(){
var n = 0;
console.log(++n);
console.log(++num);
}
}
var fn1 = fn()
fn1() //1 4
fn1() //1 5
for(var i = 0; i < 5; ++i){
setTimeout(function(){
console.log(i+' ');
},100);
}
//5 5 5 5 5
for(var i = 0; i < 5; ++i){
(function(i){
setTimeout(function(){
console.log(i+' ');
},3000);
}(i));
}
//0 1 2 3 4
for(var i = 0; i < 5; ++i){
(function(i){
setTimeout(function(){
console.log(i+' ');
},i*1000);
}(i));
}
//0 1 2 3 4闭包作为参数传递
1
2
3
4
5
6
7
8
9
10
11var num = 15;
var fn1 = function(x){
if(x>num){
console.log(x);
}
}
void function(fn2){
var num = 100
fn2(30)
}(fn1)
//30
闭包的优劣
好处
保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
匿名自执行函数可以减少内存消耗
坏处
其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
补充:堆与栈
堆与栈
栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间。(基本类型:String,Number,Boolean,Null,Undefined)
堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。(引用类型:Function,Array,Object)区分
栈:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用,这个对象将被保存到运行时数据区。堆与栈的溢出
栈:可以递归调用方法,这样随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,知道内存不够分配,产生栈溢出。
堆:循环创建对象,通俗点就是不断的new 一个对象。传值与传参
栈和堆的区别:即基本类型和引用类型的区别
1
2
3
4
5
6
7
8
9
10
11var a = [1,0,9,8,7];
var b = a;
b[0] = 3;
console.log(b); // [3, 0, 9, 8, 7]
console.log(a); // [3, 0, 9, 8, 7]
var c = 5;
var d = c;
d = 9;
console.log(d); // 9
console.log(c); // 5深浅拷贝
工作中会经常遇到操作数组、对象的情况,需要你将原数组、对象进行镜像(备份)。
基本数据类型
string、number、null、undefined、boolean、symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值
基本数据类型不存在拷贝引用类型
Object Function RegExp Math Date 值为对象,存放在堆内存中,在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据。
浅拷贝
简单的引用拷贝
1
2
3
4
5var a = [1,2,3,4];
var b = a;
b[0] = 0;
console.log(a); // [0, 2, 3, 4]
console.log(a); // [0, 2, 3, 4]数组的concat、slice拷贝
对于成员为基本类型的可以当成深拷贝
1
2
3
4
5
6var a = [1,2,3,4];
var b = a.concat();
// var b = a.slice(0);
b[0] = 0;
console.log(a); // [1, 2, 3, 4]
console.log(b); // [0, 2, 3, 4]对于成员为引用类型的依旧是浅拷贝
1
2
3
4
5
6var a = [1,2,[3,4],{name:'ccy'}];
var b = a.concat();
// var b = a.slice(0);
b[3].name = 'hs';
console.log(a[3]); // {name:'hs'}
console.log(b[3]); // {name:'hs'}对象的assign拷贝
对于对象的属性为基本类型的可以当成深拷贝。
1
2
3
4
5var a = {age:18,name:'cc',info:{address:'wuhan',interest:'playCards'}};
var b = Object.assign({},a);
b.name = 'dd';
console.log(a.name); // cc
console.log(b.name); // dd对于对象的属性为引用类型的依旧是浅拷贝。
1
2
3
4
5var a = {age:18,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
var b = Object.assign({},a);
b.info.address = 'shenzhen';
console.log(a.info); // {address:'shenzhen',interest:'playCards'}
console.log(b.info); // {address:'shenzhen',interest:'playCards'}
深拷贝
自定义深度拷贝函数
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
29function deepClone(o) {
// 判断如果不是引用类型,直接返回数据即可
if (typeof o === 'string' || typeof o === 'number' || typeof o === 'boolean' || typeof o === 'undefined') {
return o
} else if (Array.isArray(o)) { // 如果是数组,则定义一个新数组,完成复制后返回
// 注意,这里判断数组不能用typeof,因为typeof Array 返回的是object
console.log(typeof []) // --> object
var _arr = []
o.forEach(item => { _arr.push(item) })
return _arr
} else if (typeof o === 'object') {
var _o = {}
for (let key in o) {
_o[key] = deepClone(o[key])
}
return _o
}
}
var arr = [1, 2, 3, 5]
var cloneArr = deepClone(arr)
console.log(cloneArr) // --> [ 1, 2, 3, 5 ]
console.log(arr === cloneArr) // --> false
var obj = { name: 'ccc', age: 18 }
var cloneObj = deepClone(obj)
console.log(cloneObj) // --> { name: 'ccc', age: 18 }
console.log(obj === cloneObj) // false
obj.name = 'www'
console.log(obj) // --> { name: 'www', age: 18 }
console.log(cloneObj) // --> { name: 'ccc', age: 18 }函数库 lodash 的 _.cloneDeep、 JQuery 的 $.extend都实现了深拷贝