资讯专栏INFORMATION COLUMN

ES6

AZmake / 436人阅读

摘要:情况一情况二这两种情况,根据的规定都是非法的。的作用域与命令相同只在声明所在的块级作用域内有效。因此,将一个对象声明为常量必须非常小心。顶层对象的属性与全局变量挂钩,被认为时语言最大的设计败笔之一。

这是ES6的入门篇教程的笔记,网址:链接描述,以下内容中粗体+斜体表示大标题,粗体是小标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~
上一篇es5的到最后令人崩溃,看来深层的东西还是不太熟,希望这次不要这样了!!!

ECMAScript 6简介
Babel转码器
以前构建Vue-cli的时候一直不明白为什么要添加babel的依赖,现在才知道。。。。
Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。

let 和 const 命令

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

{
    let a = 10;
    var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

// so for循环的计数器,就很合适使用let命令
for (let i = 0; i < 10; i++) {
    // ...
}
console.log(i); // ReferenceError: i is not defined

有一个重大发现!for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个多带带的子作用域。这是以前没注意的!!

for (let i = 0; i < 3; i++) {
    let i = "abc";
    console.log(i);
}
// abc
// abc
// abc 输出3次abc,这表明函数内部的变量i与循环变量i不在同一个作用域,有各自多带带的作用域。

不存在变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined;而let命令纠正了这种现象,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域

var tmp = 123;
if (true) {
    tmp = "abc"; // ReferenceError
    let tmp; // 在块级作用域内又声明了一个局部变量tmp,tmp绑定这个块级作用域,凡是在声明之前就使用这些变量,就会报错。
}

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的,这在语法上,称为“暂时性死区”(temporal dead zone, 简称TDZ)。

if (true) {
    // TDZ开始
    tmp = "abc"; // ReferenceError
    console.log(tmp); // ReferenceError
    
    let tmp; // TDZ结束
    console.log(tmp); // undefined
    
    tmp = 123;
    console.log(tmp); // 123
}

不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
    let a = 10;
    var a = 1;
}
// 报错
function func() {
    let a = 10;
    let a = 1;
}

2.块级作用域
为什么需要块级作用域?
ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。

var tmp = new Date();
function f() {
    console.log(tmp); // 原意是调用外层的tmp变量
    if (false) {
        var tmp = "hello world";
    }
}
f(); // undefined 变量提升,导致内层的tmp变量覆盖了外层的tmp变量

第二种场景,用来计数的循环变量泄露为全局变量。

var s = "hello";
for (var i = 0; i < s.length; i++) {
    console.log(s[i]);
}
console.log(i); // 5 变脸i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

ES6的块级作用域
let实际上为JavaScript新增了块级作用域。

function f1() {
    let n = 5;
    if (true) {
        let n = 10;
    }
    console.log(n); // 5
}

块级作用域与函数声明
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

// 情况一
if (true) {
    function f() {}
}
// 情况二
try {
    function f() {}
} catch(e) {
    // ...
}
// 这两种情况,根据ES5的规定都是非法的。

3.const命令
基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const foo; // SyntaxError: Missing initializer in const declaration 对于const来说,只声明不赋值,就会报错。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合雷公的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {}; // foo储存的是一个地址,这个地址指向一个对象,不可以变得只是这个地址,即不能把foo指向另一个地址,但对象本身是可变得,所以可以为其添加新属性。
// 为foo添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将foo指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is ready-only

如果真的想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({}); // 常量foo指向一个冻结得对象,所以下面得添加新属性不起作用,严格模式时还会报错。
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
foo.prop // undefined

ES6声明变量得六种方法
ES5只有两种声明变量得方法:var命令和function命令。ES6除了添加let和const命令,还有另外两种声明变量的方法:import命令和class命令。所以ES6一共有6种声明变量的方法。

4.顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。ES5之中,顶层对象的属性与全局变量时等价的。
顶层对象的属性与全局变量挂钩,被认为时JavaScript语言最大的设计败笔之一。

window.a = 1;
a // 1
a = 2;
window.a // 2
// 以上代码表示顶层对象的属性赋值与全局变量的赋值,是同一件事。会带来以下三个问题:
// 1)没法再编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层兑现的属性创造的,而属性的创造是动态的)。
// 2)程序员很容易不知不觉地就创建了全局变量
// 3)顶层对象的属性是到处可以读写的,这非常不利于模块化编程 
// 而且window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有尸体含义的对象。

ES6为了保持兼容性,规定,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的熟悉那个脱钩。

var a = 1;
window.a // 1
let b = 1;
window.b // undefined

变量的解构赋值

1.数组的解构赋值
基本用法
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被成为解构(Destructuring)。

let a = 1;
let b = 2;
let c = 3;
// 这是以前为变量赋值,只能直接指定值
// ES6允许写成下面这样
let [a, b, c] = [1, 2, 3]; // 表示可以从数组中提取值,按照对应位置,对变量赋值。

上面这种写法,本质上,是属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [,, third] = ["foo", "bar", "baz"];
third // "baz"

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ["a"];
x // "a"
y // undefined
z // []

// 如果解构不成功,变量的值就等于undefined
let [foo] = [];
let [bar, foo] = [1];
// 两种情况都属于解构不成功,foo的值都会等于undefined

默认值
解构赋值允许指定默认值。

let [foo = true] = [];
foo // true
let [x, y = "b"] = ["a"]; // x="a"",  y="b"
let [x, y = "b"] = ["a", undefined]; // x="a", y="b"

ES6内部使用严格相等运算符(===),判断一个位置是否有值,所以,只有当一个数组成员严格等于undefined。默认值才会生效,如下:

let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null 默认值没有生效,因为null不严格等于undefined

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

function f() {
    console.log("aaa");
}
let [x = f()] = [1]; // 因为x能取到值,所以函数f根本不会执行,等价于下面的代码

let x;
if ([1][0] === undefined) {
    x = f();
}
else {
    x = [1][0];
}

默认值可以应用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2   x=2是因为[2][0]不是undefined
let [x = 1, y = x] = [1, 2]; // x=1;y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined 因为x用y做默认值时,y还没有声明

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

let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

let { log, sin, cos } = Math; // 将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多
const { log } = console; // 将console.log赋值到log变量
log("hello") // hello

// 如果变量名与属性名不一致,必须写成下面这样
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
// 以上说明,对象的解构赋值是下面形式的简写。也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。**真正被赋值的是后者,而不是前者。**
let { foo: foo, bar: bar} = { foo: "aaa", bar: "bbb"};
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa" foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
foo // error: foo is not defined

// 与数组一样,解构也可以用于嵌套解构的对象。
let obj = {
    p: [
        "Hello",
        { y: "World" }
    ]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // error: p is not defined 因为p是模式,不是变量,不会被赋值

let obj = {
    p: [
        "Hello",
        { y: "World" }
    ]
};
let { p } = obj; // 这时p是变量
p // ["Hello", {y: "World"}]
x // error: x is not defined
y // error: y is not defined

// 如果想要p、x、y同时赋值
let obj = {
  p: [
    "Hello",
    { y: "World" }
  ]
};

let { p, p: [x, { y }] } = obj; // 第一个p是变量,可以将p赋值;第二个p是模式,将x、y赋值,因为p已经有值了,所以从p中获取(一般情况下,冒号‘:’表示模式)

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

// 报错
let {foo: {bar}} = {baz: "baz"} // 等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。这是因为foo此时等于undefined,再取子属性就会报错

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

// 错误的写法
let x;
{x} = {x: 1} // SyntaxError: syntex error

代码的写法报错,时因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。

// 正确的写法
let x;
({x} = {x: 1});

(2)解构赋值允许等号左边的模式之中,不防止任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false]);
({} = "abc");
({} = []);
// 上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

3.字符串的解构赋值
字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = "hello";
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length: len} = "hello";
len // 5

4.数值和布尔值的解构赋值
解构赋值时,如果等号右边事数值和布尔值,则会先转为对象。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其传为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let {toString: s} = 123;
s === Number.prototype.toString // true *那s可以干什么吗?表示疑惑*

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

5.函数参数的解构赋值
函数的参数也可以使用解构赋值。

function add([x, y]) { // 表面上,该函数的参数是一个数组
    return x + y;
}
add([1, 2]); // 3 传入参数的那一刻,数组参数就被解构成变量x和y

// so on
[[1, 2], [3, 4]].map(([a, b]) => a + b); // [3, 7]

// 函数参数的解构也可以使用默认值
function move({x = 0, y = 0} = {}) { // 后面加"={}",就是防止什么都没传时,默认为{}
    return [x, y];
}
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move();  // [0, 0]
// 不信看下面的,哈哈哈
function move({x, y} = {x: 0, y: 0}) { // 这是给move的参数指定默认值,而不是为变量x和y指定默认值
    return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
// undefined会触发函数参数的默认值
[1, undefined, 3].map((x = "yes") => x); // [1, "yes", 3]

6.圆括号问题
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
由此带来的问题市,如果模式中出现圆括号怎么处理。ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此, 建议只要有可能,就不要在模式中放置圆括号。

不能使用圆括号的情况
1)变量声明语句

// 全部报错 它们都是变量声明语句,模式不能使用圆括号。
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};

2)函数参数
函数参数也属于变量声明,因此不能带有圆括号。

// 报错
function f([(z)]) { return z; }
function f([z, (x)]) { return x; }

3)赋值语句的模式

// 报错
({ p: a}) = {p: 42};
([a]) = [5];
[({ p: a }), { x: c }] = [{}, {}];
// 但是这样是可以的
({p: a} = {p: 42});

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
下面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不熟模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。

[(b)] = [3]; // 正确
({p: (d)} = {}); // 正确
({(parseInt.prop)] = [3]; // 正确

7.用途
变量的解构赋值用途很多。
1)交换变量的值

let x = 1;
let y = 2;
[x, y] = [y, x]; // 这个是数组赋值,不像对象赋值一样去找对应的名字

2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。

// 返回一个数组
function example() {
    return [1, 2, 3];
}
let [a, b , c] = example();

// 返回一个对象
function example() {
    return {
        foo: 1, 
        bar: 2
    };
}
let { foo, bar } = example();

3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来

// 参数是一组有次序的值
function f([x, y, z]) {...}
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) {...}
f({z: 3, y: 2, x: 1});

4)提取JSON数据
解构赋值对提取JSON对象中的数据,尤其有用。

let jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number); // 42, "OK", [867, 5309]

5)函数参数的默认值

jQuery.ajax = function (url, {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true, 
    // ... more config
} = {}) {
    // ... do stuff
};

6)遍历Map结构
之前知道有map遍历,现在又出来了Map结构,要好好区分,不然就晕了。
任何部署了Interator接口的对象,都可以用for...of循环遍历。Map结构原生支持Interator接口,配合变量的解构赋值,获取键名和键值就非常方便。
百度了一下,map与其他键值对集合的区别,发现map的“key”范围不仅限于字符串,而是各种类型的值都可以当作key。也就是说,object提供了“字符串-值”的对应结构,map则提供的是“值-值”的对应,是一种更加完善的hash结构。

const map = new Map();
map.set("first", "hello");
map.set("second", "world");
for (let [key, value] of map) {
    console.log(key + " is " + value);
}
// first is hello
// second is world
// 如果只想获取键名,或者只想获取键值,可以写成下面这样
// 获取键名
for (let [key] of map) {
    // ...
}
for (let [, value] of map) {
    // ...
}

7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。结构赋值使得输入语句非常清晰。

const { SourceMapConsumer, SourceNode } = require("source-map");

字符串的扩展

1.字符串的Unicode表示法
emm感觉用Unicode表示一个字符的用处不大,所以跳过这节吧~

2.字符串的遍历器接口
字符串可以被for...of循环遍历,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。

for (let codePoint of "foo") {
    console.log(codePoint)
}
// "f"
// "o"
// "o"

5.模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)表示。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量,需要将变量名写在${}之中。


大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。模板字符串之中还能调用函数。

let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}` // "1 + 2 = 3"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}` // "3"

function fn() {
    return "Hello World";
}
`foo ${fn()} bar` // foo Hello World bar

如果需要引用模板字符串本身,在需要时执行,可以写成函数。模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。

let func = (name) => `Hello ${name}!`;
func("Jack") // "Hello Jack!"

emmm 下面的看不懂了,也没在代码中看到过,看来后期的学习还有很长一段路呢。

字符串的新增方法
前面的那几种方法感觉并不常用,只是粗略看了一遍,并没有细看。

6.实例方法:repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。

"hello".repeat(2) // "hellohello"
"na".repeat(0) // ""
"na".repeat(2.9) // "nana" 小数会被取整,向下取整

正则的扩展

1、RegExp构造函数
在ES5中,RegExp构造函数的参数有两种情况。
第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。
第二种情况是,参数是一个正则表达式,这时会返回一个原有正则表达式的拷贝。

//  第一种
let regex = new RegExp("xyz", "i");
// 等价于
let regex = /xyz/i;

// 第二种
let regex = new RegExp(/xyz/i);
// 等价于
let regex = /xyz/i;

// 但是不允许此时使用第二个参数添加修饰符,否则会报错。
let regex = new RegExp(/xyz/, "i"); // Uncaught TypeError: Cannot supply flags when constructing one RegExp from another.

2、字符串的正则方法
字符串对象共有4个方法,可以使用正则表达式:match()、replace()、search()、split()。
ES6将这4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全部定义在RegExp对象上。

 - String.prototype.match调用RegExp.prototype[Symbol.match]
 - String.prototype.replace调用RegExp.prototype[Symbol.replace]
 - String.prototype.search调用RegExp.prototype[Symbol.search]
 - String.prototype.split调用RegExp.prototype[Symbol.split]

数值的扩展

2、Number.isFinite(),Number.isNaN()
ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
注:如果参数类型不是数值,Number.isFinite一律返回false。

Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite("foo"); // false
Number.isFinite("15"); // false
Number.isFinite(true); // false

Number.isNaN()用来检查一个值是否为NaN。
注:如果参数类型不是数值,Number.isFinite一律返回false。

Number.isNaN(NaN); // true
Number.isNaN(15); // true
Number.isNaN("15"); // false
Number.isNaN(true); // false
Number.isNaN(9 / NaN); // true
Number.isNaN("true" / 0); // true
Number.isNaN("true" / "true"); // true

它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,在进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false,Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。

isFinite("25"); // true
Number.isFinite("25"); // false

isNaN("NaN"); // false
Number.isNaN("NaN"); // true

3、Number.parseInt(),Number.parseFloat()
ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

// ES5的写法
parseInt("12.34") // 12
parseFloat("123.45#") // 123.45

// ES6的写法
Number.parseInt("12.34") // 12
Number.parseFloat("12.345#") // 123.45

4、Number.isInteger()
Number.isInteger()用来判断一个数值是否为整数,也就是说,是直接判断的。

Number.isInteger(25); // true
Number.isInteger(25.1); // false
Number.isInteger(); // false
Number.isInteger(null); // false
Number.isInteger("25"); // false
Number.isInteger(true); // false

5、Number.EPSILON
ES6在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示1与大于1的最小浮点数之间的差。(相当于2的-52次方。)
Number.EPSILON实际上是JavaScript能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。
Number.EPSILON可以用来设置“能够接受的误差范围”。比如。误差范围设为2的-50次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。

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

7、Math对象的扩展
ES6在Math对象上新增了17个与数学相关的方法。所有这些方法都静态方法,只能在Math对象上调用。

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

Math.trunc(4.1); // 4
Math.trunc(4.9); // 4
Math.trunc(-4.9); // -4
Math.trunc("123.456"); // 123
Math.trunc(true); // 1
Math.trunc(null); // 0
Math.trunc(NaN); // NaN
Math.trunc("foo"); // NaN
Math.trunc(undefined); // NaN

// 实际意义
Math.trunc = Math.trunc || function(x) {
    return x < 0 ? Math.ceil(x) : Math.floor(x);
}

Math.sign()
Math.sign()方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值:

参数为正数,返回+1; Math.sign(5) // +1

参数为负数,返回-1; Math.sign(-5) // -1

参数为0, 返回0; Math.sign(0) // +0

参数为-0,返回-0; Math.sign(-0) // -0

其他值,返回NaN。 Math.sign(NaN) // NaN

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

Math.sign("") // 0
Math.sign(true) // +1
Math.sign(false) // 0
Math.sign(null) // 0
Math.sign("foo") // NaN
Math.sign("9") // +1
Math.sign() // NaN
Math.sign(undefined) // NaN

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

Math.cbrt(-1) // -1
Math.cbrt(2) // 1.2599210498948734
Math.cbrt("8") // 2
Math.cbrt("hello") // NaN

// 实质
Math.cbrt = Math.cbrt || function(x) {
    let y = Math.power(Math.abs(x), 1/3);
    return x < 0 ? -y : y;
}

8、指数运算符
ES2016新增了一个指数运算符(**)。该运算符是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

2 ** 3 // 8
2 ** 3 ** 2 // 512 相当于 2**(3**2)

指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

a **= 2 // 等同于 a = a * a
b **= 3 // 等同于 b = b * b *  b

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105438.html

相关文章

  • 使用ES6新特性开发微信小程序

    摘要:使用新特性开发微信小程序国际化与本地化新特性国际化与本地化新增了很多对于国际化的支持,比如时间格式,货币格式,数字格式等。 ECMAScript 6(简称ES6)是JavaScript语言的最新标准。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。 微信小程序支持绝大部分ES6的新增特性。 使用ES6新特性开发微信小程序(1) ES6新特性:Cons...

    Backache 评论0 收藏0
  • ES6-前世今生(0)

    摘要:更新了个版本,最新正式版是语言的下一代标准,早已在年月正式发布。基本不支持移动端浏览器对的支持情况版起便可以支持的新特性。比较通用的工具方案有,,,等。 1、ECMAScript是什么? 和 JavaScript 有着怎样的关系? 1996 年 11 月,Netscape 创造了javascript并将其提交给了标准化组织 ECMA,次年,ECMA 发布 262 号标准文件(ECMA-...

    LeviDing 评论0 收藏0
  • 给React初学者的10分钟ES6教程

    摘要:但是在中,可以通过关键字来实现类的继承的使用可以使得继承意义更加明确并且值得一提的是,如果你使用来定义的组件,那么可以在类的构造器里面,用简单的的声明方式来替代方法。 原文:The 10 min ES6 course for the beginner React Developer译者:Jim Xiao 著名的80/20定律可以用来解释React和ES6的关系。因为ES6增加了超过75...

    Awbeci 评论0 收藏0
  • 10个最佳ES6特性

    摘要:,正式名称是,但是这个名称更加简洁。已经不再是最新的标准,但是它已经广泛用于编程实践中。而制定了模块功能。自从年双十一正式上线,累计处理了亿错误事件,得到了金山软件等众多知名用户的认可。 译者按: 人生苦短,我用ES6。 原文: Top 10 ES6 Features Every Busy JavaScript Developer Must Know 译者: Fundebug 为了保...

    codeKK 评论0 收藏0
  • 为什么都说js 里面任何对象最终都继承了Object对象

    摘要:今天闲来无事,看见几行小字。又说所有对象,继承终是。强行押韵一波这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从对象继承而来的这句话。一时兴起,便去验证这句话。 今天闲来无事,看见几行小字。又说所有对象,继承终是Obj。—— 强行押韵一波 这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从Object对象继承而来的这句话。一时兴...

    Gemini 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<