ES6和ES5的区别和扩展

ES6和ES5的区别和扩展

一 let和const

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

1 let与var的区别

  • let不存在变量提升
  • 块级作用域:ES5 只有全局作用域和函数作用域,没有块级作用域(ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域)
  • let不允许在相同作用域内,重复声明同一个变量

2 const常量

const与let一样,唯一区别在于声明的常量不能被修改

3 标题函数声明

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。函数声明语句的行为类似于let,在块级作用域之外不可引用。

二 变量的解构赋值

1 数组解构

es6支持“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值,如果解构不成功,变量的值就等于undefined。

let [a, b, c] = [1, 2, 3] // a=1, b=2, c=3

let [a1, b1=2] = [1] // a1=1, b1=2 //指定默认值

let [d, [e], f] = [1, [2], 3] // 嵌套数组解构 d=1, e=2, f=3

let [g, ...h] = [1, 2, 3] // 数组拆分 g=1, h=[2, 3]

let [i,,j] = [1, 2, 3] // 不连续解构 i=1, j=3

let [k,l] = [1, 2, 3] // 不完全解构 k=1, l=2

事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值

2 对象解构

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

let {a, b} = {a:'aa', b:'bb'} //a='aa' b='bb'

//设置默认值
let {x, y = 5} = {x: 1}; //x= 1 y=5

//允许别名,a的值将失效
let {a:a1,b} = {a:'aa', b:'bb'} //a1='aa' b='bb' 

let obj = {a:'aa', b: {c:'c'}}
let {a, b:{c}} = obj // 嵌套解构 a='aa' c='c'

(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心

//将一个已经声明的变量用于解构赋值
let x;
({x} = {x: 1});//将一个已经声明的变量用于解构赋值 x=1
{x} = {x: 1};// SyntaxError: syntax error

(2)解构赋值允许等号左边的模式之中,不放置任何变量名

({} = [true, false]);
({} = 'abc');
({} = []);

(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;//first:1 last: 3

3 字符串的解构赋值

let [a, b, c] = 'hello' // a='h' b='e' c='l'
let {length : len} = 'hello'; //len: 5

4 数值和布尔值的解构赋值

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象

undefined和null无法转为对象,所以对它们进行解构赋值,都会报错

let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

5 函数参数的解构赋值

function say({name,age}){
    console.log(name + '今年' + age)
} 
say({name:'小明',age:18})

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

三、字符串的扩展

1 加强了对 Unicode 的支持

加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。(\u0000~\uFFFF之间)

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

  • ‘\172’ === ‘z’ // true
  • ‘\x7A’ === ‘z’ // true
  • ‘\u007A’ === ‘z’ // true
  • ‘\u{7A}’ === ‘z’ // true

2 JSON.stringify() 的改造

根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的JSON.stringify()方法有可能返回不符合 UTF-8 标准的字符串。

如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

3 模板字符串

用反引号(`)标识,字符串中嵌入变量用${}

4 新增方法

String.fromCodePoint()

  • ES5 提供String.fromCharCode()方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于0xFFFF的字符。
  • ES6 提供String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足
String.fromCharCode(0x20BB7)
//String.fromCharCode()不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符
String.fromCodePoint(0x20BB7)// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'// true
//如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。

fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。

String.raw()

该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义。

String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"

String.raw`Hi\u000A!`;
// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!

// `foo${1 + 2}bar`等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

实例方法:codePointAt()

,能够正确处理 4 个字节储存的字符,返回一个字符的码点。

let s = '𠮷a';//“𠮷a”视为三个字符
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
//测试一个字符由两个字节还是由四个字节组成的最简单方法。
function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}
is32Bit("𠮷") // true
is32Bit("a") // false

实例方法:normalize()

将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。

'\u01D1'.normalize() === '\u004F\u030C'.normalize()// true

实例方法:includes(), startsWith(), endsWith()

let str = 'hello world'
//返回布尔值,表示是否找到了参数字符串
str.includes('r')  //true 

//返回布尔值,表示参数字符串是否在原字符串的头部
str.startsWith('hello')  //true

//返回布尔值,表示参数字符串是否在原字符串的尾部
str.endsWith('d')  //true

实例方法:repeat()

//repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'na'.repeat(0) // ""

如果repeat的参数是负数或者Infinity,会报错。参数NaN等同于 0,如果repeat的参数是字符串,则会先转换成数字。

实例方法:padStart(),padEnd()

如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

如果省略第二个参数,默认使用空格补全长度。

实例方法:trimStart(),trimEnd()

对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

let str = '   hello world      '
//消除首尾的空格
str.trim()  //'hello world'

//消除字符串头部的空格
str.trimStart()  //'hello world   '

//消除尾部的空格
str.trimEnd()  //'   hello world'

实例方法:matchAll()

matchAll()方法返回一个正则表达式在当前字符串的所有匹配

四 正则的扩展

1 正则的扩展

ES5 中,RegExp构造函数的参数有两种情况

//两种方式
var regex = new RegExp(/xyz/i);
var regex = new RegExp('xyz', 'i');
var regex = new RegExp(/xyz/, 'i'); //error

ES6 :如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

new RegExp(/abc/ig, 'i').flags  // "i"

2 字符串的正则方法

字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。

3 u 修饰符

对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。

/^\uD83D/u.test('\uD83D\uDC2A') // false
/^\uD83D/.test('\uD83D\uDC2A') // true
//ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为tru

一旦加上u修饰符号,就会修改下面这些正则表达式的行为。

  • 对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符。
  • ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词
  • 使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。
  • u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的 Unicode 字符。
  • 没有u修饰符的情况下,正则中没有定义的转义(如逗号的转义,)无效,而在u模式会报错。
var s = '𠮷';

/^.$/.test(s) // false
/^.$/u.test(s) // true

/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true

/𠮷{2}/.test('𠮷𠮷') // false
/𠮷{2}/u.test('𠮷𠮷') // true
RegExp.prototy

//unicode属性,表示是否设置了u修饰符。
const r2 = /hello/u;
r2.unicode // true

4 y修饰符

ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。

y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null
//由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。

RegExp.prototype.sticky 属性
表示是否设置了y修饰符。

var r = /hello\d/y;
r.sticky // true

5 RegExp.prototype.flags 属性

ES6 为正则表达式新增了flags属性,会返回正则表达式的修饰符。

// ES5 的 source 属性
/abc/ig.source // "abc"

// ES6 的 flags 属性 返回正则表达式的修饰符
/abc/ig.flags // 'gi'

6 s 修饰符:dotAll 模式

正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(该字符表示一行的终结)

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)

dotAll模式,即点(dot)代表一切字符。

re=/foo.bar/s
re.test('foo\nbar') // true
/foo.bar/.test('foo\nbar') // false

re.dotAll // true

正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。

7 后行断言

  • “先行断言”指的是,x只有在y前面才匹配,必须写成/x(?=y)/

  • “先行否定断言”指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。

/\d+(?=%)/.exec('100% of US presidents have been male')  // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them')                 // ["44"]

ES2018 引入后行断言,V8 引擎 4.9 版(Chrome 62)已经支持。

  • x只有在y后面才匹配,必须写成**/(?<=y)x/**

  • “先行否定断言”相反,x只有不在y后面才匹配,必须写成**/(?<!y)x/**

/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill')  // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90')                // ["90"]

//字符串替换
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'

“后行断言”的实现,需要先匹配/(?<=y)x/x,然后再回到左边,匹配y的部分

8 Unicode 属性类

ES2018 引入了一种新的类的写法\p{...}\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。\p{Number}甚至能匹配罗马数字。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true

\P{…}\p{…}的反向匹配,即匹配不满足条件的字符。

9 具名组匹配

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法。

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

10 String.prototype.matchAll()

ES2020 增加了String.prototype.matchAll()方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。

const string = 'test1test2test3';
const regex = /t(e)(st(\d?))/g;

for (const match of string.matchAll(regex)) {
  console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

五 数值扩展

1 二进制和八进制表示法

ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。

2 扩展函数

Number.isFinite(),Number.isNaN()
  • Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity

  • Number.isNaN()用来检查一个值是否为NaN。

    它们与传统的全局方法isFinite()isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效。

    Number.isNaN(9/NaN) // true
    Number.isFinite(-Infinity); // false
    
Number.parseInt(),Number.parseFloat()

ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变

Number.isInteger() :判断一个数值是否为整数

由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。

Number.EPSILON

ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差

  • Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了

Number.EPSILON可以用来设置“能够接受的误差范围”。

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
安全整数和 Number.isSafeInteger()
  • S6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示的整数范围范围的上下限

  • Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值

Number.isSafeInteger(9007199254740993 - 990)// true
9007199254740993 - 990
// 返回结果 9007199254740002
// 正确答案应该是 9007199254740003

3 Math对象扩展

Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值,对于空值和无法截取整数的值,返回NaN

Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(null) // 0
Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN
Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};
Math.sign()
  • 参数为正数,返回+1

  • 参数为负数,返回-1

  • 参数为 0,返回0

  • 参数为-0,返回-0;

  • 其他值,返回NaN

  • 如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回NaN

Math.cbrt()

Math.cbrt()方法用于计算一个数的立方根。对于非数值,Math.cbrt()方法内部也是先使用Number()方法将其转为数值。

Math.clz32()

Math.clz32()方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。

Math.clz32(1000) // 22
Math.clz32(0b01000000000000000000000000000000) // 1
  • 对于小数,Math.clz32方法只考虑整数部分

  • 对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算

Math.imul()

Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。。之所以需要部署这个方法,是因为 JavaScript 有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)a * b的结果是相同的,即该方法等同于(a * b)|0的效果(超过 32 位的部分溢出)

Math.fround()

Math.fround方法返回一个数的32位单精度浮点数形式

  • Math.fround方法的主要作用,是将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致)

  • NaNInfinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数

Math.hypot()

Math.hypot方法返回所有参数的平方和的平方根。

Math.hypot(3, 4);        // 5

如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。

ES6新增对数方法
  • Math.expm1():返回 ex - 1,即Math.exp(x) - 1

  • Math.log1p():返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN

  • Math.log10():返回以 10 为底的x的对数。如果x小于 0,则返回 NaN

  • Math.log2():返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。

ES6 新增双曲函数方法
  • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)
指数运算符

ES2016 新增了一个指数运算符(**

2 ** 2 // 4
b **= 3;// 等同于 b = b * b * b;
BigInt 数据类型

BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

1234n // BigInt
1n + 2n // 3n

typeof运算符对于 BigInt 类!型的数据返回bigint

BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。

六 函数的扩展

1 函数默认参数

如果传入了参数就使用传入的值,如果没有就使用默认值

function sum(a=0, b=0){
    return a + b;
}
sum(); //0
sum(1); //1
sum(1,1) //2

2 箭头函数

ES6很有意思的一部分就是函数的快捷写法。也就是箭头函数。

箭头函数最直观的三个特点:

  • 不需要 function 关键字来创建函数
  • 省略 return 关键字
  • 继承当前上下文的 this 关键字,this指向外部作用域
  • 不可以new,也就是不能用作构造函数
  • 不可以使用argument对象,可以使用rest参数代替
var f = v => v; 
// 等同于 
var f = function (v) {
    return v; 
};
  • 箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
  • 箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
  • 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错

3 rest参数

ES6 引入 rest 参数(形式为...变量名)用于获取函数的多余参数,这样就不需要使用arguments对象

function add(...value){
    console.log(value)
    //[1,2,3,4]
}
add(1,2,3,4)

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用

// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

4 函数的 length 属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理, rest 参数也不会计入length属性。

name 属性 函数的name属性,返回该函数的函数名。

5 尾调用优化

尾调用(Tail Call)指某个函数的最后一步是调用另一个函数。

函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame)所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

  • “尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

  • 只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

  • 目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持

5.1 尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

3 函数参数的尾逗号

ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。

此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

七 数组的扩展

es5新增的方法:forEach、map、filter、some、every、reduce

1 扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5
[...document.querySelectorAll('div')]// [<div>, <div>, <div>]
//与正常的函数参数可以结合使用
function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

替代函数的 apply 方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

Math.max.apply(null, [14, 3, 77])// ES5 的写法
Math.max(...[14, 3, 77])// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);// ES5的 写法
arr1.push(...arr2);// ES6 的写法

扩展运算符还可以将字符串转为真正的数组。

[...'hello'] // [ "h", "e", "l", "l", "o" ]

任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。

扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]

2 Array.from()将类似数组的对象转为数组

//类似数组的对象
let obj = {
    0:'a',
    1:'b',
    2:'c',
    length:3
}
//es5写法
var arr = [].slice.call(obj);
//es6写法
var arr1 = Array.from(obj);

// arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。

扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

3 Array.of方法用于将一组值,转换为数组

    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]

4 数组实例的 copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

Array.prototype.copyWithin(target, start = 0, end = this.length)

[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]

它接受三个参数。

  • target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。

5 数组实例的 find() 和 findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

[1, 4, -5, 10].find((n) => n < 0)// -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

6 数组实例的 fill()

fill方法使用给定值,填充一个数组。

['a', 'b', 'c'].fill(7)// [7, 7, 7]

new Array(3).fill(7)// [7, 7, 7]

7 数组实例的 entries(),keys() 和 values()

ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

let arr = ['a','b','c']

//遍历数组的键名
for(let index of arr.keys()){
    console.log(index)
}
//0 1 2 

//遍历数组的键值
for(let value of arr.values()){
    console.log(value)
}
//'a' 'b' 'c'

//遍历数组的键值对
for(let [index,value] of arr.entries()){
    console.log(index,value)
}
//0 'a' 
//1 'b'
//2 'c'

8 includes()表示是否包含某个值

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

//第二个参数表示搜索的开始位置,负数表示倒数位置
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

9 数组实例的 flat(),flatMap()

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, 4]].flat() // [1, 2, 3, 4]
[1, 2, [3, [4, 5]]].flat()// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)// [1, 2, 3, 4, 5]

如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

[1, [2, [3]]].flat(Infinity)// [1, 2, 3]

如果原数组有空位flat()方法会跳过空位。

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组

// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])// [2, 4, 3, 6, 4, 8]
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]

八 对象扩展

1 对象的简写

  • 当属性名和属性值相同时,可以省略属性值
  • 方法可以省略function
  • 对象的属性名和方法名允许为变量或者表达式
let name = '小明'
let age ="age1"
let person = {
    name,
    [age]:18,
    ['hei'+'ght']:180,
    sayName(){
        console.log(this.name)
    }
}
person.sayName();
console.log(person)

2 对象的扩展运算符 …

同数组扩展运算符,支持对象解构剩余参数,对象合并,复制对象

3 Object.is()

用来比较两个值是否严格相等,等同于 “===”

//唯一不同之处
+0 === -0 //true
NaN === NaN //false
Object.is(+0, -0) // false
Object.is(NaN,NaN) //true

4 Object.assign()

  • 用于对象的合并,第一个参数是目标对象,后面的参数都是源对象
  • 如果目标对象与源对象有同名属性,则后面会覆盖前面的属性
var obj1 = {a:1,b:2}
var obj2 = {b:3,c:4}
console.log(Object.assign(obj1,obj2))
//{a:1, b:3, c:4}

let obj = {a:1, b:2}
Object.assign(obj) === obj // true

//参数不是对象会转成对象再返回
typeof Object.assign(2) // "object"

//undefined和null无法转成对象,作为第一个参数会报错
Object.assign(undefined) // 报错
Object.assign(null) // 报错

Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

5 Object.setPrototypeOf(),Object.getPrototypeOf()

  • Object.setPrototypeOf()用来设置一个对象的prototype对象
  • Object.getPrototypeOf()用于读取一个对象的原型对象
//设置obj对象上的__proto__原型对象为proto对象
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

6 Object.keys(),Object.values(),Object.entries()

使用for…of可以遍历对象的键名,键值,键值对

var obj = {
    a:1,
    b:2,
    c:3
}
//传统遍历对象
for(key in obj){
    console.log(key) //所有键值
    console.log(obj[key]) //所有键值
}

//es6遍历
//所有键名
for( let index of Object.keys(obj) ){
    console.log(index)
}
//所有键值
for( let value of Object.values(obj) ){
    console.log(value)
}
//所有键值对
for( let [index,value] of Object.entries(obj) ){
    console.log(index,value)
}

7 Object.fromEntries()

方法是Object.entries()的逆操作,用于将一个键值对数组转为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

// 特别适合Map结构
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

8 super 关键字

this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

9 解构赋值

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

上面代码中,变量z是解构赋值所在的对象。获取等号右边的所有尚未读取的键(ab),将它们连同值一起拷贝过来。

解构赋值必须是最后一个参数,否则会报错。

let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值