ES6知识汇总

基础知识:
  1. ECMAScript中ECMA实际上是一个标准化组织。
  2. ECMAScript 方言有Jscript和ActionScript。JavaScript和ActionScript都是以ECMAScript为标准,但ActionScript是运用在flash软件中,是flash的脚本语言。
  3. ES6名词是一个泛指,指5.1版本以后的JavaScript的下一代标准,涵盖了ES2015、ES2016、ES2017等等。
  4. 检测node环境下对ES6的支持情况:
/*安装工具:es-checker,启动即可*/
npm install -g es-checker
npm es-checker

运行效果图示:
ES6知识汇总_第1张图片
ES6知识汇总_第2张图片

Babel转码器的使用:

1.Babel的作用:Babel是一个ES6转码器,可以将ES6代码转化为ES5代码,从而可以在现有的环境中执行。在使用ES6编写代码时不用担心现有环境是否支持。

例:

//转码前
input.map(item => item + 1);
//转码后
input.map(function(item){
	return item + 1;
});

2.Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步就是配置该文件。

创建.babelrc文件的方法:

//在指定项目下打开cmd,输入
type null>.babelrc


初始内容:

{
  "presets": [],
  "plugins": []
}

presets字段设定转码规则(根据需求安装):

# 最新转码规则
$ npm install --save-dev babel-preset-latest

# react 转码规则
$ npm install --save-dev babel-preset-react

# 不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

更新后的内容:

  {
    "presets": [
      "latest",
      "react",
      "stage-2"
    ],
    "plugins": []
  }

3.工具
(1)babel-cli:Babel提供babel-cli工具,用于命令行转码。

//安装命令(全局安装)
npm install --global babel-cli
//或
npm install -g babel-cli

用法:

/*index.js*/
let arr = [1, 2, 3, 4];
let newArr = arr.map(item => item + 1);
console.log(arr);
console.log(newArr);
# 转码结果输出到标准输出
$ babel index.js

# 转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ babel index.js --out-file compiled.js
# 或者
$ babel index.js -o compiled.js

# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
# 或者
$ babel src -d lib

# -s 参数生成source map文件
$ babel src -d lib -s

ES6知识汇总_第3张图片
使用全局Babel的缺点:
①项目对环境产生依赖,即全局环境中必须有Babel。
②无法支持不同项目使用不同的Babel。

解决办法:在项目中安装babel-cli

npm install --save-dev babel-cli

改写package.json文件:

{
  "devDependencies": {
    "babel-cli": "^6.0.0"
  },
  "scripts": {
    "build": "babel index.js -o compile.js"
  },
}

运行时直接执行命令:npm run build
(2)babel-node:babel-cli工具自带的一个命令,提供一个支持ES6的REPL环境。支持Node的REPL环境的所有功能,可以直接运行ES6代码。

/*1.全局中安装babel-cli,进入REPL环境,执行babel-node即可*/
babel-node
>(x => x*2)(1)
2
/*2.项目中安装babel-cli,进入REPL环境*/
/*配置package.json文件*/
{
  "scripts": {
    "script-name": "babel-node"
  }
}
运行:npm run script-name

babel-node命令可以直接运行 ES6 脚本。将上面的代码放入脚本文件index.js,然后修改package.json。

{
  "scripts": {
    "script-name": "babel-node index.js"
  }
}
运行:npm run script-name

(3)babel-register:babel-register模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用 Babel 进行转码。

npm install --save-dev babel-register

使用时必须先加载babel-register。

require("babel-register");
require("./index.js");

不需要手动对index.js文件进行转码,由于它是实时转码,所以只适合在开发环境使用。

语法使用:

let和const:

①let和const定义的变量只在所属的代码块内有效,在代码块外部使用抛出ReferenceError,变量未定义。

②不存在变量提升,必须先声明后使用。

③会出现“暂时性死区”:

只要块级作用域内存在let命令,它所声明的变量就被绑定到这个区域,不再受外部影响。ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

var s = 1;
if (true) {
  s = 'abc'; // ReferenceError
  let s;
}

由于“暂时性死区”的存在使得 typeof 不再是百分之百安全的操作:

typeof a;//undefined
typeof b; // ReferenceError
let b;
var x = x;//undefined
/*由于存在变量提升,因此x在赋值前已经被定义,故返回undefined*/

let x = x;// ReferenceError: x is not defined
/*从右向左执行,先查找x的值,之后发现x使用let定义,即未声明便使用,故报错*/

④不允许重复声明:在相同作用域内,不允许重复声明相同的变量,包括函数的参数。

const声明一个只读的常量,一旦声明值不可改变。因此在声明变量时必须立即初始化,即声明就必须赋值,否则报错。

const a;
a = 10;
//Uncaught SyntaxError: Missing initializer in const declaration

事实上,const保证的是变量指向的内存地址不能改动。对于基本数据类型则是指值不能改变,而对于复杂数据类型则是保证指针固定,至于它指向的数据结构是不是可变的,就完全不能控制。

const a = [];
a.push("hello");
a = ["world"];//Uncaught TypeError: Assignment to constant variable.
{
    let x = 1;
    var x = 2;
    //Uncaught SyntaxError: Identifier 'x' has already been declared
}
{
	let a = 1;
	let a = 2;
	//Uncaught SyntaxError: Identifier 'a' has already been declared
}
function func(arg) {
  let arg; // 报错
}

块级作用域:

由于块级作用域的出现使得立即执行函数表达式(IIFE)不再必要:

// IIFE 写法
(function () {
  var tmp = ...;
  ...
}());

// 块级作用域写法
{
  let tmp = ...;
  ...
}

ES6引入块级作用域,允许函数定义在块级作用域中,而ES5中函数只允许定义在顶层作用域或函数作用域中,不允许定义在块级作用域中。

ES6浏览器中处理在块级作用域中定义的函数的规则:

  1. 允许在块级作用域内声明函数。
  2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  3. 函数声明会提升到所在的块级作用域的头部。
/*ES6浏览器中运行*/
function f(){
    console.log("outside");
}
(function(){
    if(false){
        function f(){
             console.log("inside");
        }
    }
    f();//Uncaught TypeError: f is not a function
})();
/*行为等同于*/
function f(){
    console.log("outside");
}
(function(){
    var f = undefined;
    if(false){
        function f(){
             console.log("inside");
        }
    }
    f();//Uncaught TypeError: f is not a function
})();

因此由于环境的行为差异,避免在块级作用域中定义函数,如果需要定义函数建议使用函数表达式:

// 函数声明语句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

上述代码修改为:

function f(){
    console.log("outside");
}
(function(){
    if(false){
        let f = function(){
            console.log("inside");
        }
    }
    f();//outside
})();

附:ES6声明变量的6中方法:var、function、let、const、import、class。

顶层对象:在浏览器环境指的是window对象,在 Node 指的是global对象。

ES5 之中,顶层对象的属性与全局变量是等价的。

ES6中使用var和function声明的变量属于顶层对象的属性,而使用let、const和class声明的变量不属于顶层对象的属性。

var a = 10;
console.log(window.a);//10
let b = 10;
console.log(window.b);//undefined

顶层对象:

  1. 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  2. 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  3. Node 里面,顶层对象是global,但其他环境都不支持。

获取顶层对象的方法参考:http://es6.ruanyifeng.com/#docs/let

变量的解构赋值:

解构:在ES6中按照一定的模式,从数组和对象中提取值来给变量进行赋值。

(1)数组的解构赋值:

解构赋值允许指定默认值,只有当数组成员严格等于 undefined 时,默认值才会生效。

/**
undefined === undefined
true
NaN == NaN
false
undefined == null
true
undefined === null
false 
*/
let [x = 1] = [];//解构不成功,变量的值为undefined
console.log(x);	//1
let [y = 1] = [undefined];
console.log(y);	//1
let [z = 1] = [null];
console.log(z);	//null

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

function f() {
  console.log('aaa');
}
let [x = f()] = [1]; //x = 1 

/*等价于*/

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

(2)对象的解构赋值:

let {a: a, b: b} = {a: "a1", b: "b1"};
console.log(a);//a1
console.log(b);//b1

let { a: b } = { a: "a1", b: "b1" };
b // "a1"
a // Uncaught ReferenceError: a is not defined
//a是匹配的模式,b才是变量

let {a} = {a: "a1"};
console.log(a);//a1
/*事实上等价于*/
let {a: a} = {a: "a1"}

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

let {a : {a0}} = {b : "b0"};
console.log(a0);
//Cannot destructure property `a0` of 'undefined' or 'null'.
//原因:此时的a已经为undefined,再取子属性则会报错。

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

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

原因:JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。

解决方法:

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

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

let arr = [1, 2, 3];
let {0: x, [arr.length - 1]: y,} = arr;
console.log(x);//1
console.log(y);//3
//[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”

(3)字符串的解构赋值:

字符串被转换成了一个类似数组的对象。

//类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5

(4)数值和布尔值的解构赋值:

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

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

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

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

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

(5)函数参数的解构赋值:

function move({x = 0, y = 0} = {}) {
 console.log([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 }) {
  console.log([x, y]);
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move();//[0, 0]
move(undefined);//[0, 0]
//该代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值

解构赋值的用途:

  1. 用于交换变量的值。
  2. 函数返回的多个值方便获取。
  3. 可以为函数参数设置默认值。
  4. 获取JSON数据方便。

字符串方法的使用:

(1)includes()、startsWith()、endsWith()

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。

支持第二个参数,表示开始搜索的位置。
endsWith()方法是针对前n个字符,而其他两个方法是针对从第n个位置直到字符串结束。

let s = "hello world";
s.startsWith("hel");//true
s.endsWith("world");//true
s.includes("hel");//true
s.startsWith("world", 6);//true
s.endsWith("world",6);//false
s.endsWith("hello",5);//true
s.endsWith("hello w",7);//true
s.endsWith("hello w",8);//false
s.includes("hello",1);//false

(2)repeat():返回一个新字符串,表示将原字符串重复n次。

"ab".repeat(0) //""
"ab".repeat(2.9) // "abab",参数为小数会被取整
"ab".repeat("2") //"abab",参数为字符串会转化为数字
"ab".repeat("a") //"",参数为字符串转化为数字失败视为0
"ab".repeat("-1")//Uncaught RangeError,参数为负数或Infinity报错
"ab".repeat("-0.5")//"",参数为介于-1~0的小数会先取整,即视为0
"ab".repeat(NaN)//"",参数NaN视为0
"ab".repeat("NaN")//""

(3)padStart(),padEnd()

如果某个字符串不够指定长度,会在头部或尾部补全。

接受两个参数:
第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
//如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
//用来补全的字符串与原字符串的长度之和超过指定的长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789')// '0123456abc'
//如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '

(4)模板字符串:可以用来定义多行字符串,或者在字符串中嵌入变量。

①如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
②模板字符串中嵌入变量,需要将变量名写在${}之中。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
③模板字符串可以调用函数,如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

function f(){
    return "hello world";
}
console.log(`this is ${f()}`)

④模板字符串可以直接调用字符串的方法:

console.log(`
 hello
    world`.indexOf("hello"));//2(字符串hello前有一个空格)

⑤模板字符串中允许嵌入另一个模板字符串。

⑥引用模板字符串本身:

// 写法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 写法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"
(function(){console.log("hello")})(); //一般函数匿名调用
(() => console.log("hello"))(); //箭头函数匿名调用
(eval.call(null,'() => console.log("hello")'))(); //hello
(eval('(name) => console.log(`hello ${name}`)'))('world');//hello world
console.log((eval.call(null, '(name) => `hello ${name}!`'))('world'));//hello world
console.log((eval('(name) => `hello ${name}`'))('world'));//hello world

正则的使用:

正则中构造函数参数的两种使用方式:

//传入两个参数,如下第一个参数为字符串,第二个参数为修饰符
var reg = new RegExp('xyz', 'i');

//传入一个参数并且该参数为一个正则表达式(此时ES5中不允许第二个参数),
//返回一个原有正则表达式的拷贝
var reg = new RegExp(/xyz/i);

//上述两种方式均等价于
var reg = /xyz/i;

//ES6中允许第一个参数为正则表达式,第二个参数为修饰符
//该修饰符会覆盖原有正则对象的修饰符
new RegExp(/x/i).flags //"i"
new RegExp(/x/).flags //""
new RegExp(/x/ig,'i').flags //"i"

//附:使用正则匹配变量的用法
var a = "hello";
var reg = new RegExp(a);
console.log(reg.test("hello world"));//true
ES6新增的修饰符:

u:用来正确的处理四个字节的UTF-16编码。

y:和g类似,也是用来全局匹配。后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,即第一个位置必须匹配,否则匹配失败。

s:使得 . 可以匹配任意单个字符。

var str = "a1aa2";
var g = /a+/g;
var y = /a+/y;
console.log(g.exec(str)); //["a"]
console.log(g.exec(str)); //["aa"]
console.log(y.exec(str)); //["a"]
console.log(y.exec(str)); //null
//ES6的sticky属性检测是否设置了y修饰符
var r = /hello/y;
r.sticky // true

// ES5 的 source 属性,返回正则表达式的正文
/abc/ig.source // "abc"

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

.:匹配除换行符和其他Unicode行终止符之外的其他的任意字符。

[^…]:不在方括号内的任意字符。

console.log(/foo.bar/.test("foo\nbar")); //false
console.log(/foo[^]bar/.test("foo\nbar")); //true
console.log(/foo.bar/s.test("foo\nbar")); //true
//称为dotAll模式,即点(dot)代表一切字符
const re = /foo.bar/s;
console.log(re.dotAll); //true
后行断言:

JavaScript 语言的正则表达式,只支持先行断言和先行否定断言,不支持后行断言和后行否定断言,ES6中引入后行断言。

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

(?=p):零宽正向先行断言,要求接下来的字符都与p匹配,但结果中不能包括匹配p的那些字符。
(?!=p):零宽负向先行断言,要求接下来的字符不与p匹配。

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

/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill')  // ["100"]
/(?

“后行断言”的实现,需要先匹配/(?<=y)x/的x,然后再回到左边,匹配y的部分。这种“先右后左”的执行顺序,与所有其他正则操作相反,导致了一些不符合预期的行为。

首先,后行断言的组匹配,与正常情况下结果是不一样的。

/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]

//()中的内容是捕获组的概念,exec方法先返回匹配的元素,再返回捕获组匹配的元素

没有“后行断言”时,第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是105和3。而“后行断言”时,由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是1和053。

“后行断言”的反向引用,也与通常的顺序相反:

//\1表示引用的是第一个带圆括号的子表达式

/(?<=(o)d\1)r/.exec('hodor')  // null
/(?<=\1d(o))r/.exec('hodor')  // ["r", "o"]
具名组匹配:

目的:为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

格式:“问号 + 尖括号 + 组名”

const RE_DATE = /(?\d{4})-(?\d{2})-(?\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
//组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号
//(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。

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
let {groups: {one, two}} = /^(?.*):(?.*)$/u.exec('foo:bar');
one  // foo
two  // bar

字符串替换时,使用$<组名>引用具名组。

let re = /(?\d{4})-(?\d{2})-(?\d{2})/u;
'2015-01-02'.replace(re, '$/$/$') // '02/01/2015'

上面代码中,replace方法的第二个参数是一个字符串,而不是正则表达式。第二个参数也可以为函数,如下:

let re = /(?\d{4})-(?\d{2})-(?\d{2})/u;   
let str = '2015-01-02'.replace(re, (...args) => {
    let {day, month, year} = args[args.length - 1];
    return `${day}/${month}/${year}`;//或return day + "/" + month + "/" + year;
});
console.log(str); //02/01/2015
/**
...args中的元素:
matched, // 整个匹配结果 2015-01-02
capture1, // 第一个组匹配 2015
capture2, // 第二个组匹配 01
capture3, // 第三个组匹配 02
position, // 匹配开始的位置 0
S, // 原字符串 2015-01-02
groups // 具名组构成的一个对象 {year, month, day}
*/
//箭头函数没有arguments对象

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

const RE_TWICE = /^(?[a-z]+)!\k$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
//数字引用(\1)依然有效。
const RE_TWICE = /^(?[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
//同时使用
const RE_TWICE = /^(?[a-z]+)!\k!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false
String.prototype.matchAll

获取所有匹配,返回的是一个遍历器(Iterator),而不是一个数组。

const string = 'test1test2test3';

// g 修饰符加不加都可以
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"]

遍历器转为数组方法:

// 转为数组方法一
[...string.matchAll(regex)]

// 转为数组方法二
Array.from(string.matchAll(regex));

数值的用法:

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

从 ES5 开始,在严格模式之中,八进制就不再允许使用前缀0表示,ES6 进一步明确,要使用前缀0o表示。

// 非严格模式
(function(){
  console.log(0o11 === 011);
})() // true

// 严格模式
(function(){
  'use strict';
  console.log(0o11 === 011);
})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.
//转化为10进制,使用Number方法:
Number('0b111')  // 7
Number('0o10')  // 8

Number.isFinite():检测一个数值是否是有限的,参数类型必须是数值,否则返回false。

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

Number.isNaN(NaN) // true
Number.isNaN(15) // false
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
isFinite("25") // true
Number.isFinite(25) // true
Number.isFinite("25") // false

isNaN(NaN) // true
isNaN("NaN") // true
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false
Number.isNaN(1) // false
isNaN()的用法:
//非数字值返回true,是数字或能够转化为数字(通过Number方法)则返回false
isNaN("11") //false
isNaN("aa") //true
isNaN("true") //true
isNaN(true) //false
isNaN(false) //false
isNaN(null) //false
isNaN(undefined) //true
isNaN("") //false

Number(undefined) //NaN
Number(true) //1
Number(false) //0
Number("") //0
Number(null) //0
Number(NaN) //NaN

//扩展
//0/0返回NaN,而其它数值除以0却不会返回NaN
0/0 //NaN
1/0 //Infinity
-1/0 //-Infinity

在缺少Number.NaN()方法的情况下,可以使用以下方法替代:

var isNaN = function(val) {
    return val !== val;
};

Number.parseInt()、Number.parseFloat()用法和全局的一致。
目的:逐步减少全局性方法,使语言逐步模块化。

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

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

如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数,因为可能会误判。
如果一个数值的绝对值小于Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨的最小值,会被自动转为 0。

Number.isInteger(5E-324) // false
Number.isInteger(5E-325) // true

Math.trunc():去除一个数的小数部分,返回整数部分。

/*实现方式类似于:*/
Math.trunc = Math.trunc || function(x){
	return x < 0 ? Math.ceil(x) : Math.floor(x);
}

Math.sign():判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

/*实现方式类似于:*/
Math.sign = Math.sign || function(x) {
  x = +x; // convert to a number
  if (x === 0 || isNaN(x)) {
    return x;
  }
  return x > 0 ? 1 : -1;
};

//"+"的用法:
+"a" //NaN
+true //1
+false //0
+"" //0
+null //0
+NaN //NaN
+undefined //NaN

Math.cbrt():计算一个数的立方根。

/*实现方式类似于:*/
Math.cbrt = Math.cbrt || function(x) {
  var y = Math.pow(Math.abs(x), 1/3);
  return x < 0 ? -y : y;
};
/**
使用Math.abs()方法的原因是:
JavaScript 内部使用双精度浮点数运算。相关运算遵循 IEEE 754 标准。
IEEE 754 规定了 pow(x, y) 运算在 x 为 finite(即不是正负无穷大和NaN)且
x < 0,而 y 是 finite 且不是整数时,返回 NaN。
*/

Math.pow(-1, 1/3) //NaN
Math.pow(-1, 2.1) //NaN

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

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

let a = 2;
a **= 2; // 4 (等同于 a = a * a)

//指数运算符可以与等号结合,形成一个新的赋值运算符(**=),运算符中不能写成"** ="。
let b = 4;
b **= 3; // 64 (等同于 b = b * b * b)

函数的用法:

ES6 之前,不能直接为函数的参数指定默认值,替代的方法如下:

function fn(x, y){
	y = y || "hello";
	console.log(x);
	console.log(y);
	console.log(x + "" + y);
}
fn(3, 4); //3 4 34
fn(3, null); //3 hello 3hello
/**该方法的缺点:如果输入的y对应的布尔值为false则赋值无效。
即y为null、undefined、NaN、0、false、""无效。*/

解决方法:

//先判断y是否有值,未赋值则此时的y为undefined。但无法识别直接为y赋值为undefined
function fn2(x, y){
	if(typeof y === "undefined"){
		y = "hello";
	}
	...
}

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面:

//只有值严格等于undefined时,默认值才有效(null == undefined但null !== undefined)。
function fn(x, y = "hello") {
  console.log(x, y);
}
fn(1, null); //1 null
fn(1, undefined); // 1 "hello"
fn(1, NaN); // 1 NaN 
fn(1, ""); // 1 ""
fn(1, 0); //1 0
fn(1, false); //1 false
fn(1); //1 "hello"

参数变量是默认声明的,所以不能用let或const再次声明。

function fn(x){
	let x = 5;
}
fn();//调用时才会报错Uncaught SyntaxError: Identifier 'x' has already been declared

使用参数默认值时,函数不能有同名参数。

// 不报错
function foo(x, x, y) {
    ...
}
// 报错
function foo(x, x, y = 1) {
    ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

如果非尾部的参数设置默认值,实际上这个参数是没法省略的。

function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1],输入undefined触发默认值

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

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

// rest 参数也不会计入length属性
(function(...args) {}).length // 0

//如果设置默认值的参数不是尾参数,那么length属性也不再计入后面的参数。
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

var x = 10;
function f(x = x){
	console.log(x);
}
f(); //Uncaught ReferenceError: x is not defined
/**参数x = x形成一个单独作用域。实际执行的是let x = x,
由于暂时性死区的原因,这行代码会报错”x 未定义“。*/
//例1:
var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}
foo() // 3
x // 1
/**
上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,
y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。
函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,
所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。
如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,
与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。
*/
//例2:
var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}
foo() // 2
x // 1

指定某一个参数不可省略,否则报错:

function throwMissing(){
	throw new Error("Missing parameter");
}
function fn(param = throwMissing()){
	return param;
}
fn(); //Uncaught Error: Missing parameter
//注:参数的默认值不是在定义时执行,而是在运行时执行。

Symbol

  1. 使用Symbol的原因:解决对象因属性名相同而引起的冲突,防止属性名被覆盖。用Symbol声明的属性名都是独一无二的。
  2. 七种数据类型:String、Number、Boolean、null、undefined、Object、Symbol。
  3. 属性名类型:字符串或Symbol类型。
  4. Symbol值通过Symbol函数生成,即Symbol()。
  5. Symbol值不能与其它类型数据进行运算,也不能转化为数值类型。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false

// Symbol值转为字符串
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

// Symbol值转为布尔值
let sym = Symbol();
Boolean(sym) // true
!sym  // false
if (sym) {
  // ...
}

例:定义一个属性名为Symbol值的对象:

let mySymbol = Symbol();

// 方式一:
let a = {};
a[mySymbol] = 'Hello!';
// 不能使用 a.mySymbol = 'Hello!';原因:mySymbol会被当成字符串。

// 方式二:
let a = {
  [mySymbol]: 'Hello!'  
};
// 此处使用[]的原因:mySymbol为一个变量,否则会将其作为字符串解析

// 方式三:
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
Symbol属性名的获取:

Object.getOwnPropertySymbols():返回由Symbol类型的属性名构成的数组。

Symbol 属性名不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。

for...in:返回自身和继承的可枚举属性。
Object.keys():返回自身的可枚举属性。
Object.getOwnPropertyNames():返回自身键名构成的数组(包含不可枚举)。
Object.getOwnPropertySymbols():返回自身所有Symbol属性的键名。
Reflect.ownKeys():自身所有键名(包含不可枚举和Symbol属性)。
// 可以理解为等价于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。
Symbol.for(),Symbol.keyFor()

Symbol.for():使用同一个Symbol值,会先去检查给定的key值是否存在,不存在才回去创建。调用时会被登记在全局环境中供搜索。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true

Symbol.keyFor():返回一个已经登记的Symbol类型值的key。

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

你可能感兴趣的:(es6)