深入了解ES6
语法特性,一共三篇。
let const
let
声明变量,带块级作用域;const
声明只读常量;
1 | { |
父级块域定义let
后,子级块域var
则不可定义同变量名1
2
3
4
5
6
7
8
9
10
11
12
13var b = 10
{
let b = 20;
b; //20
}
b //10
//相反:
let b = 10
{
var b = 20;
b; //Uncaught SyntaxError: Identifier 'b' has already been declared
}
b //Uncaught SyntaxError: Identifier 'b' has already been declared
常量不可更改1
2
3
4
5const b = 20;
{
b = 10;
b; //Uncaught TypeError: Assignment to constant variable.
}
JavaScript解释器是先捕捉变量,在执行函数 && 变量提升1
2
3
4
5let b = 20;
console.log('2、'+b); //2、20
b; //10
console.log('3、'+b); //3、20
b = 10;
Variable 解构赋值
ES6 可以按一定模式,从数组和对象中提取值,对变量进行赋值。
Array
列举部分用法:1
2
3
4
5
6
7
8let [a, b, c] = [{a:'a'},{b:'b'},{c:'c'}];
a; //{a:'a'}
b; //{b:'b'}
c; //{c:'c'}
let [a, ,c] = [{a:'a'},{b:'b'},{c:'c'}]
a; //{ a: 'a' }
c; //{ c: 'c' }
利用spread1
2
3let [a, ...c] = [{a:'a'},{b:'b'},{c:'c'}]
a; //{ a: 'a' }
c; //[ { b: 'b' }, { c: 'c' } ]
使用spread,值就为[]
1
2
3let [a,...c] = [{a:'a'}]
a; //{ a: 'a' }
c; //[]
解析不成功,值就为undefined1
2
3let [a,c] = [{a:'a'}]
a; //{ a: 'a' }
c; //undefined
使用默认值1
2
3let [a,c = {c:'c'}] = [{a:'a'}]
a; //{ a: 'a' }
c; //{ c: 'c' }b
只会取得当前[]
的第一个2
,不会取得整个[2,3]
1
2
3
4let [ a, [b], d ] = [ 1, [2, 3], 4 ];
a // 1
b // 2
d // 4
只要某种数据结构具有Set
结构和 Iterator
接口都可以用此方法赋值
Object
列举部分用法:1
2
3
4let { a, b , c } = { a: 'aaa', b: 'bbb', c: 'ccc' };
a; //"aaa"
b; //"bbb"
c; //"ccc"
从Object
中取出指定key
对应的value
1
2
3let { c } = { a: 'aaa', b: 'bbb', c: 'ccc' };
c; //"ccc"
d; //解构失败undefined
从Object
中取出指定方法1
2const { log } = console;
log('hello')
重命名取到的key
1
2
3let { b: d } = { a: 'aaa', b: 'bbb', c: 'ccc' };
d; //"bbb"
b; //Uncaught ReferenceError: b is not defined
使用默认值1
2
3
4
5
6
7
8
9
10
11let { a , d = 'ddd' } = { a: 'aaa', b: 'bbb', c: 'ccc' };
d; //"ddd"
let { x : y = 3} = {x: 4};
y; // 4
let {x = 3} = {x: undefined};
x; // 3
let {x = 3} = {x: null};
x; // null
注意1
2
3
4
5
6
7let x;
{x} = {x: 1};
// SyntaxError: syntax error
let x;
({x} = {x: 1});
x; //1
// JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误
String
列举部分用法:1
2
3
4
5
6const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
还可调用length
属性1
2let {length : len} = 'hello';
len // 5
function参数的解构赋值
列举部分用法:1
2
3
4
5function add([a, b]){
return a + b;
}
add([1, 2]); // 3
String的扩展
常用的新String
增扩展:
遍历器接口
String
可以被for..of
遍历1
2
3
4
5
6for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
模板字符串
列举部分用法:1
2
3let add = 'hello '
let foo = `${add}world`
foo; //hello world
调用函数1
2
3
4function fn() {
return "Hello World";
}
`foo ${fn()} bar` //foo Hello World bar
多层级嵌套1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },];
console.log(tmpl(data));
//<table>
//
//<tr><td><Jane></td></tr>
//<tr><td>Bond</td></tr>
//
//<tr><td>Lars</td></tr>
//<tr><td><Croft></td></tr>
//
//</table>
“标签模板”功能(tagged template)
紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串!
1 | let a = 5; |
Number的扩展
Number.isFinite(),Number.isNAN()
新增Number.isFinite()
和Number.isNAN()
,isFinite()
检查是否是有限(finite),和不是Infinity
。isNAN()
检查一个值是否为NaN。1
2
3
4
5
6
7
8
9
10
11
12Number.isFinite(1); // true
Number.isFinite(0.1); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('1'); // false
Number.isNaN(NaN) // true
Number.isNaN(1) // false
Number.isNaN('1') // false
Number.isNaN(true) // false
Number.isNaN('true' / 0) // true
Number.parseInt(), Number.parseFloat()
ES6把全局方法parseInt()
和parseFloat
,封装到了Number
对象下1
2
3
4
5
6
7// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
Number.isInteger()
Number.isInteger()
判断一个数值是否为整数1
2Number.isInteger(2.0) // true
Number.isInteger(2.1) // false
JavaScriptc采用IEEE 754标准。数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)在第54位以后会丢弃1
Number.isInteger(2.0000000000000002) // true
Function的扩展
ES6为Function
加了些新的特性
默认值
指定参数默认值1
2
3
4
5
6
7function test(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
指定参数必须,赋值1
2
3
4
5
6
7
8
9function a() {
throw new Error('Missing parameter');
}
function b(c = a()) {
return c
}b
b() // Error: Missing parameter
rest
rest
参数用于获取函数的多余参数;(...变量名
); 用rest
后不能有其他参数 ;
1 | function add (...values){ |
严格模式
函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
name
属性
返回该函数的函数名
1 | function foo(){} |
箭头函数
=>
箭头函数1
2
3
4
5var f = v => v;
// 等同于
var f = function (v) {
return v;
};
如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错1
2
3
4// 报错
let getUesr = id => { id: id, name: "X_x" };
// 不报错
let getUesr = id => ({ id: id, name: "X_x" });rest
参数与箭头函数结合1
2
3
4
5
6
7
8
9const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
注意点
1.函数内this
对象,指向调用它的function
;
2.不可以当作构造函数;
3.不可以使用arguments
对象,可以使用rest
参数
4.不能用作 Generator
函数
尾调用优化
尾调用(Tail Call),某函数在最后一步是调用另外一个函数;1
2
3function a(x){
b(x)
}
执行b()
时a()
未结束。
尾调用优化
🌰例子,用return
来结束a
方法。那么在执行过程中,调用栈的调用帧只有一条,这样就可以节省很大一部分内存;
注意必须使用严格模式1
2
3
4
5
6
7
8
function a(x){
return b(x)
}
function b(x){
console.log(x)
}
a();
尾递归
函数自身调用自身,称为归递。在尾部调用自己,称为尾归递;
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误。尾递归可以永远防止“栈溢出”错误。
1 | function factorial(n){ |
在return n * factorial( n - 1 )
中factorial()
需要不断下探直到满足if( n === 1 ) return 1
。保存多个n
的调用记录。
用尾递归,只会有一个调用记录。1
2
3
4function factorial(n,total){
if( n === 1 ) return total;
return factorial( n - 1 , n * total )
}
每一次调用factorial()
都会保存结果到total
,不会像上面不断在函数内部调用。
Array的扩展
扩展运算
扩展运算符:...
,将一个数组转化为用逗号分隔的参数序列。1
2
3
4console.log( ... [ 1 , 2 , 3 ] )
//1 2 3
console.log( 0 , ... [ 1 , ... [ 2 , 3 ] ])
//0 1 2 3
函数的调用:1
2
3
4
5
6
7
8function sum( a , b ){
return a + b
}
let arr = [ 3 , 4 ];
sum( ...arr );
// 7
扩展运算符的应用
复制
在js中数组是复合数据类型,直接复制,是指向底层数据地址的。而不是开辟新的底层数据地址。1
2
3
4const a1 = [1, 2];
const a2 = [...a1];
a1 === a2
// false
合并1
2
3
4
5
6const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
let arr4 = [...arr1 , ...arr2 , ...arr3]
//[ 'a', 'b', 'c', 'd', 'e' ]
字符串转化为数组
1 | [...'hello'] |
Array.from()
Array.from()
转成真正的数组
1 | let arrayLike = { |
Array.of()
Array.of()
将多个人数值,合并成数组。这个方法主要是弥补Array()
返回结果不一样。
1 | Array.of(1,23,341,) |
数组实例的function
copyWithin()
copyWithin()
指定位置的成员复制到其他位置上。1
2
3
4Array.prototype.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
find() & findIndex()
find()
用于找出第一个符合条件的数组成员。不满足undefined
findInde()
用于找出第一个满足条件成员位置。不满足-1
1 | [1, 5, 10, 15].find(function(value, index, arr) {return value > 9;}) |
fill()
fill()
覆盖,填充一个数组。
1 | ['a', 'b', 'c'].fill(2) |
includes()
includes()
判断某个数组是否包含给定的值。
1 | [1, 2, 3].includes(2) |
flat() & flatMap()
flat()
把数组‘拉平’展开。参数Number
&Infinity
无限展开。可自定义’拉平‘层速,默认为1
。
1 | [1, 2, [3, 4]].flat() |
flatMap()
对原数组的每个成员先执行map()
在执行flast()
。不过只能展开一层数组。
1 | [2, 3, 4].flatMap((x) => [x, x * 2]) |
object的扩展
简洁表示法
ES6可以直接在打括号里面,直接写入函数、变量,直接做对象属性方法。
属性简写1
2
3
4
5
6const foo = 'bar';
const bar = { foo }
// { foo : 'bar'}
//等同于
const bar = { foo : foo }
方法简写1
2
3
4
5
6
7
8
9let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
属性名表达式
JavaScript定义对象的属性,有两种方法,一种是识符作为属性名,一种是表达式作为属性名。
1 | //第一种 |
🌰例子:1
2
3
4
5
6
7
8
9
10
11
12let lastWord = 'last word';
const a = {
'firstword': 'hello',
[lastWord]: 'world',
['na' + 'me'](){ return this.firstword + lastWord}
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
a.name()//"hellolast word"
属性名表达式,不能是对象
1 | const key = {a: 1}; |
name
属性
name
返回当前函数名。1
2
3
4
5
6const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // 'sayName'
super
关键字
关键字super
,指向当前对象的原型对象。
1 | const proto = { |
新增方法
Object.is()
Object.is()
与严格比较运算符===
的行为基本一致。ES5中==
和===
,它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身。
1 | +0 === -0 //true |
Object.assign()
Object.assign()
合并对象。
将源对象sources
的所有可枚举属性,复制到目标对象target
。Object.assign(target, ...sources)
1 | const target = { a: 1 }; |
assign()
为浅拷贝,更改源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。同名属性的替换,遇到同名属性,assign()
不是添加,而是覆盖。
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
获取一个对象的所有自身属性的描述符。Object.getOwnPropertyDescriptors(obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
Object.keys(),Object.values(),Object.entries(),Object.fromEntries()
Object.keys()
返回一个数组,是所有可遍历(enumerable)属性的键名。1
2
3var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
Object.values()
返回一个数组,是所有可遍历(enumerable)属性的键值。
1 | var obj = { foo: 'bar', baz: 42 }; |
Object.entries()
返回一个数组,是所有可遍历(enumerable)属性的键值对数组。
1 | const obj = { foo: 'bar', baz: 42 }; |
Object.fromEntries()
将一个键值对数组转为对象。
1 | Object.fromEntries([ |