阮一峰ES6教程读书笔记(三)数值、函数的扩展

2019-08-03  本文已影响0人  前端艾希

About

本文只记录了一些我比较感兴趣的新增方法,以及特性,更加详细的介绍请参考阮一峰的《ECMAScript 6 入门》

一、数值的扩展

1. 进制表示法

如果要将其它进制字符串数值转为十进制,要使用Number方法。

Number('0b111')  // 7
Number('0o10')  // 8
Number(0x11) // 17

2. 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('123.45#') // 123.45

3. Number.isInteger()

Number.isInteger()用来判断一个数值是否为整数。JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false

如果传入的参数不是数值,这里不会发生自动转换,而是直接返回false

Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false

4. Number.EPSILON

Number对象新增了属性EPSILON,这个属性其实是一个数字即2的-52次方,用来表示JavaScript的最小精度。我们可以把它用在浮点运算中用来设置运算精度。

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2) // 误差范围2的-50次方
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true

1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true

二、Math对象上的扩展

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

1. Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0

如果参数不是数值,那么会先使用Number方法将其转为数值,然后在取整,如果无法转为数值,或者参数为空,就返回NaN,对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};

2. Math.hypot()

Math.hypot方法返回所有参数的平方和的平方根。用来做勾股定理不要太爽~

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

如果传入的参数有一个无法转为数字就会返回NaN

3. Math.log10(),Math.log2()

分别是求以10为底的对数和以2为底的对数,可以用以下方法替代:

Math.log2 = Math.log2 || function(x) {
  return Math.log(x) / Math.LN2;
};

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};

4. 新增指数运算符

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

2 ** 2 // 4
2 ** 3 // 8

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

三、函数的扩展

1. 函数参数的默认值

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。比如在函数体内通过typeOf检测形参是否为空,如果为空就赋给形参默认值,这样写起来不仅代码非常臃肿且十分丑陋,其他语言都能给参数默认值,而在JavaScript中却不能,所以ES6 允许我们给函数参数传入默认值。

function print (x, y = 'world') {
    console.log(`${x} ${y}`)
}
print('hello') // hello world

1.1 使用参数默认值时,变量不能重名

例如:

function foo(x, x, y) {
  console.log(x,x,y)
}
foo(1,2,3) // 2 2 3

function foo(x, x, y = 3) {
  console.log(x,x,y)
}
foo(1,2,3) // Uncaught SyntaxError: Duplicate parameter name not allowed in this context

1.2 参数默认值是惰性求值的

参数默认值不是传值的,而是每次都重新计算默认值表达式的值。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

形参p虽然有默认值,但是并非定义时就已经计算出来,而是等到调用时才计算该默认值。

1.3 与解构赋值结合使用

如果想在形参上使用结构赋值,那么必须要给他们赋一个默认值{}

function foo({x, y}) {
  console.log(x, y)
}

foo() // 报错

function foo({x, y} = {}) {
  console.log(x, y)
}

foo() // undefined undefined
foo({x:1, z:2}) // 1 undefined
foo({x:1, y:2}) // 1 2

为进一步加深理解,我们来看这样一个例子:

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

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

写法二与一不同的地方在于:写法一中虽然也使用了解构赋值,但是在解构之前形参xy都已经有默认值为0了,即使在调用函数时只传了一个值,那么另一个值也不会为undefined而是为0;而写法二中形参xy采用了惰性求值,也就是说其并没有真正的默认值,如果调用m2时,没有传入参数,那么启动解构赋值,形参xy0,但是如果只传了1个参数,那么就不会启动形参内的解构赋值,这样的话,另一个参数就为undefined

m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

1.4 带有默认值的参数位置

通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

1.5 函数的length属性

函数的length返回该函数的形参数量,但不包括带有默认值的参数,总的来说就是调用该函数时必须传入多少个参数。

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

1.5 默认参数的应用

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

function missparams () {
    throw new Error('Missing a paramater')
}

function foo (x = missparams(), y = 1) {
    console.log(x, y)
}

foo() // Error: Missing a paramater

2. rest参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

这其实很好理解,在其他语言中,无论是C还是Python中我们会在定义形参时为了提升函数的稳定性,会在形参后定义*args,如果有多的参数就会被放在数组中传入函数,而不会引发错误。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

值得注意的是,...rest只能是作为形参的最后一个参数,正如*args一样,必须放在最后,否则会报错。

3. name属性

这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准。

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

4. 箭头函数

我们先看个例子:

// 不使用箭头函数
function greet () {
    console.log(`hello ${this.name}`)
}

var name = 'yan' //即 window.name = 'yan'
person = {
    'name': 'bing',
    'greet': greet
}
greet()  // hello yan
person.greet()  // hello bing

//使用箭头函数
var greet = () => console.log(`hello ${this.name}`)
person = {
    'name': 'bing',
    'greet': greet
}
greet()  // hello yan
person.greet()  // hello yan
greet.call(person) // hello yan

通过上面的例子我们可以得知箭头函数的this的指向是不会改变的,它在哪里定义,哪里就是它的执行上下文,由此我们可以得到使用箭头函数的注意点:

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

出现上述情况的根本原因是箭头函数没有自己的this,它的this指向外层的this

不适用的场合:

  1. 定义对象的方法:
const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}

因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

  1. 需要动态this的时候:
var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

5. Function.prototype.toString()

toString()方法返回函数代码本身,以前会省略注释和空格。

function /* foo comment */ foo () {}

foo.toString()
// "function /* foo comment */ foo () {}"

参考链接

作者:阮一峰
链接:http://es6.ruanyifeng.com/#docs/destructuring

上一篇下一篇

猜你喜欢

热点阅读