1.ES6浏览器支持情况
一、桌面端浏览器对ES2015的支持情况
- Chrome:51 版起便可以支持 97% 的 ES6 新特性。
- Firefox:53 版起便可以支持 97% 的 ES6 新特性。
- Safari:10 版起便可以支持 99% 的 ES6 新特性。
- IE:Edge 15可以支持 96% 的 ES6 新特性。Edge 14 可以支持 93% 的 ES6 新特性。(IE7~11 基本不支持 ES6)
二、移动端浏览器对ES2015的支持情况
- iOS:10.0 版起便可以支持 99% 的 ES6 新特性。
- Android:基本不支持 ES6 新特性(5.1 仅支持 25%)
三、服务器对ES2015的支持情况
- Node.js:6.5 版起便可以支持 97% 的 ES6 新特性。(6.0 支持 92%)
附:如何使用ES6的新特性,又能保证浏览器的兼容?
针对 ES6 的兼容性问题,很多团队为此开发出了多种语法解析转换工具,把我们写的 ES6 语法转换成 ES5,相当于在 ES6 和浏览器之间做了一个翻译官。比较通用的工具方案有 babel,jsx,traceur,es6-shim 等。
2.ES6语法
去下载一个工程项目,来编写ES6代码,可以让我们方便调试
下载源码:
安装
npm i
npm i webpack -g
npm i webpack-dev-server -g
运行
npm start
该项目主要有一个html文件和js文件,查看webpack项目里面的package.json文件的内容,运行npm run watch命令后,当这2个文件内容发生变化的时候,界面也会相应发生变化
"scripts": {
"watch": "webpack --watch",
"start": "webpack-dev-server --open"
},
2.1 let 和const
ES6新增了2个关键字,let 和const
let 声明的变量只在let命令所在的代码块有效;
const声明一个只读的常量,一旦声明,常量的值不能修改
let只能声明一次,var可以声明多次
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。
2.1.1 let命令只在代码块内有效
如下图所示的代码
{
let a = 22;
}
console.log(a)
会报错:a is not defined
但是如果let修改成var的时候,如下代码,就不会有报错了。
{
var a = 22;
}
console.log(a)
从上面可以看出let对于块作用内的有效性很明显,而var的作用域在全局范围内有效,会有一些隐性的错误
2.1.2 let命令不能重复声明而var可以声明多次
{
let a = 22;
let a = 23;
}
index.js:4402 ./index.js
Module build failed: D:/zdj/es6-webpack-master/es6-webpack-master/index.js: Duplicate declaration "a"
11 | {
12 | let a = 22;
> 13 | let a = 23;
| ^
14 | }
15 | console.log(a)
16 |
@ multi (webpack)-dev-server/client?http://localhost:8080 babel-polyfill ./index.js
当运行上面的代码的时候,会报错,因为let声明的变量不能重复声明.
如果将let修改成var变量呢?则不会报错,下面的代码是不会操作的。
{
let a = 22;
var a = 3;
var a = 5;
}
当时在作用域外面打印a的值则会报错,是不是很神奇??
{
let a = 22;
var a = 3;
var a = 5;
}
console.log(a)
index.js:18616 Uncaught ReferenceError: a is not defined
at Object.<anonymous> (index.js:18616)
at __webpack_require__ (index.js:20)
at Object.<anonymous> (index.js:4228)
at __webpack_require__ (index.js:20)
at index.js:63
at index.js:66
2.1.3 for 循环计数器很适合用 let
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i)
})
}
//输出10个10
for (let j = 0; j < 10; j++) {
setTimeout(function () {
console.log(j);
})
}
//输出0123456789
查看上面代码结果不一样的原因是:变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
小心,for循环以后还是用let声明变量比较好。
2.1.4 const 命令
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错
const PI = "3.1415926";
PI // 3.1415926
const MY_AGE; // SyntaxError: Missing initializer in const declaration
下面有一种情况是暂时性死区
var PI = "a";
if(true){
console.log(PI); // ReferenceError: PI is not defined
const PI = "3.1415926";
}
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。
2.2 解析赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
2.1.1 数组模式的解析赋值
2.1.1.1 基本的解析赋值
let [a, b, c] = [1, 2, 3]
console.log("a = " + a);
console.log("b = " + b);
console.log("c = " + c);
//输出
//a = 1
//b = 2
//c = 3
2.1.1.2 可嵌套的解析赋值
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3
2.1.1.3 不完全的解析赋值
let [a = 1, b] = []; // a = 1, b = undefined
2.1.1.4 可忽略
let [a, , b] = [1, 2, 3]; // a = 1 // b = 3
2.1.1.5 剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
2.1.1.6 字符串等
let[a,...b]='hello
//a = h
//b = e,l,l,o
let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'
2.1.1.7 解构默认值
let [a = 2] = [undefined]; // a = 2
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1]; // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
2.1.2 对象模型的解析赋值
基本
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb'
let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd'
可嵌套可忽略
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj; // x = 'hello' // y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { }] } = obj; // x = 'hello'
不完全解构
let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj; // x = undefined // y = 'world'
剩余运算符
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; // a = 10 // b = 20 // rest = {c: 30, d: 40}
解构默认值
let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;
2.3 ES6 Symbol
ES6引入了一种新的数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
2.3.1 ES6 Symbol基本用法
Symbol函数栈不能用new命令,因为Symbol是原始数据类型,不是对象,可以接受一个字符串作为对象。
let sy = Symbol('kk');
console.log(sy); //Symbol(kk)
console.log(typeof (sy)) //symbol
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
console.log(sy === sy1); //false
2.3.2 ES6 Symbol使用场景
2.3.2.1 Symbol使用场景
由于每一个Symbol的值都是不相等的,所以Symbol作为对象的属性名,可以保证不重名。
let sy = Symbol("key1");
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
let syObject1 = {
[sy]: 'kk'
}
console.log(syObject1);
let syObject2 = {};
Object.defineProperties(syObject2, sy, { value: 'kk' });
console.log(syObject2);
另外,Symbol作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面字符串,所以取到的是字符串sy属性,而不是Symbol值sy属性。
let syObject = {};
syObject[sy] = "kk";
syObject[sy]; // "kk"
syObject.sy; // undefined
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
for (let i in syObject) {
console.log(i);
} // 无输出
Object.keys(syObject); // []
Object.getOwnPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]
2.3.2.2 Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
2.3.2.3 Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
2.4 ES6 Map 与 Set
Map对象保存键值对。任何值(对象或者原始值)都可以作为一个键或者一个值。
Map和Objects的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
2.4.1 Map中的key
2.4.1.1 key是字符串
let myMap = new Map();
let keyString = "a string";
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyString, "和键'a string'关联的值第2次赋值");
console.log(myMap.get(keyString)); // "和键'a string'关联的值2"
console.log(myMap.get("a string"));
// "和键'a string'关联的值2" // 因为 keyString === 'a string'
2.4.1.2 key是对象
let myMap = new Map();
var keyObj = {};
myMap.set(keyObj, "和键 keyObj 关联的值")
console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
console.log(myMap.get({})); // undefined, 因为 keyObj !== {}
2.4.1.3 key是函数
var myMap = new Map();
var keyFunc = function () {}, // 函数
myMap.set(keyFunc, "和键 keyFunc 关联的值");
myMap.get(keyFunc); // "和键 keyFunc 关联的值"
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}
2.4.1.4 key是NaN
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"
虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。
2.4.2 Map的迭代
2.4.2.1 for...of
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one"
for (let [key, value] of myMap) {
console.log(key + " = " + value);
}
/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
for(let [key,value] of myMap.entries()) {
console.log(key+" = "+value);
}
// 将会显示两个log。 一个是 "0" 另一个是 "1"
for(let key of myMap.keys()) {
console.log(key);
}
// 将会显示两个log。 一个是 "zero" 另一个是 "one"
for(let value of myMap.values()) {
console.log(value);
}
2.4.2.2 forEach()
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
// 将会显示两个 logs。 一个是 "0 = zero" 另一个是 "1 = one"
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
}, myMap)
2.4.3 Map对象的操作
2.4.3.1 Map与Array的转换
var kvArray = [["key1", "value1"], ["key2", "value2"]];
// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
var myMap = new Map(kvArray);
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
var outArray = Array.from(myMap);
2.4.3.2 Map的克隆
let myMap1 = new Map([["key", "value"], ["key1", "value1"]]);
let myMap2 = new Map(myMap1);
console.log(myMap1 === myMap2);
// 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。
2.4.3.3 Map的合并
...操作符将Map转换为Array
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
2.4.4 Set对象的操作
Set对象允许你存储任务类型的唯一值,无论是原始值或者是对象引用。
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
2.4.4.1 Set对象的基本操作
let mySet = new Set();
mySet.add(1); // Set(1) {1}
mySet.add(5); // Set(2) {1, 5}
mySet.add(5); // Set(2) {1, 5} 这里体现了值的唯一性
mySet.add("some text");
// Set(3) {1, 5, "some text"} 这里体现了类型的多样性
var o = {a: 1, b: 2};
mySet.add(o);
mySet.add({a: 1, b: 2});
// Set(5) {1, 5, "some text", {…}, {…}}
// 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储
2.4.4.2 类型转换
用...操作符,将 Set 转 Array
// Array 转 Set
var mySet = new Set(["value1", "value2", "value3"]);
// 用...操作符,将 Set 转 Array
var myArray = [...mySet];
String
// String 转 Set
var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"}
// 注:Set 中 toString 方法是不能将 Set 转换成 String
2.4.4.3 Set对象作用
2.4.4.3.1 数组去重
var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]
2.4.4.3.2 数组求并集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}
2.4.4.3.3 数组求交集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
2.4.4.3.4 数组求差集
var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var difference = new Set([...a].filter(x => !b.has(x))); // {1}
2.5 ES6对象
2.5.1 对象字面量
2.5.1.1 属性的简洁表示法
ES6允许对象的属性直接写变量,这时属性名是变量名,属性值是变量值。
const age = 12;
const name = "Amy";
const person = {age, name};
console.log(person) //{age: 12, name: "Amy"}
//等同于
const person = {age: age, name: name}
2.5.1.2 方法名也可以简写
const person = {
sayHi(){
console.log("Hi");
}
}
person.sayHi(); //"Hi"
//等同于
const person = {
sayHi:function(){
console.log("Hi");
}
}
person.sayHi();//"Hi"
如果Generator函数,则要在前面加一个星号:
const obj = {
* myGenerator() {
yield 'hello world';
}
};
//等同于
const obj = {
myGenerator: function* () {
yield 'hello world';
}
};
2.5.1.3 属性名表达式
ES6允许用表达式作为属性名,但是一定要将表达式作为方括号内。
const obj = {
["he"+"llo"](){
return "Hi";
}
}
obj.hello(); //"Hi"
注意点:属性的简洁表示法和属性名表达式不能同时使用,否则会报错。
const hello = "Hello";
const obj = {
[hello]
};
obj //SyntaxError: Unexpected token }
const hello = "Hello";
const obj = {
[hello+"2"]:"world"
};
obj //{Hello2: "world"}
2.5.2 对象的拓展运算符
拓展预算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。可参考下面的博客查看E6的拓展运算符(...)
https://www.jianshu.com/p/b3630e16b98a
2.5.3 对象的新方法
Object.assign(target, source_1, ···)
用于将源对象的所有可枚举属性复制到目标对象中。
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
// 第一个参数是目标对象,后面的参数是源对象
target; // {a: 1, b: 2, c: 3}
-
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
-
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
Object.assign(3); // Number {3} typeof Object.assign(3); // "object"
因为 null 和 undefined 不能转化为对象,所以会报错:
Object.assign(null); // TypeError: Cannot convert undefined or null to object
Object.assign(undefined); // TypeError: Cannot convert undefined or null to object
当参数不止一个时,null 和 undefined 不放第一个,即不为目标对象时,会跳过 null 和 undefined ,不报错
Object.assign(1,undefined); // Number {1}
Object.assign({a: 1},null); // {a: 1}
Object.assign(undefined,{a: 1}); // TypeError: Cannot convert undefined or null to object
注意点
assign 的属性拷贝是浅拷贝:
let sourceObj = { a: { b: 1}};
let targetObj = {c: 3};
Object.assign(targetObj, sourceObj);
targetObj.a.b = 2;
sourceObj.a.b; // 2
Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。
Object.is("q","q"); // true
Object.is(1,1); // true
Object.is([1],[1]); // false
Object.is({q:1},{q:1}); // false
与(===)的区别
//一是+0不等于-0
Object.is(+0,-0); //false
+0 === -0 //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN //false
2.6 函数
2.6.1 默认参数
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
注意点:使用函数默认参数时,不允许有同名参数。
// 不报错
function fn(name,name){
console.log(name);
}
// 报错
//SyntaxError: Duplicate parameter name not allowed in this context
function fn(name,name,age=17){
console.log(name+","+age);
}
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",null); // Amy,null
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
function f(x,y=x){
console.log(x,y);
}
f(1); // 1 1
function f(x=y){
console.log(x);
}
f(); // ReferenceError: y is not defined
2.6.2 不定参数
不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
2.6.3 箭头参数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:参数 => 函数体
基本用法
var f = v => v;
//等价于
var f = function(a){
return a;
}
f(1); //1
2.6.3.1 当箭头函数没有参数或者有多个参数,要用()括起来
var f = (a,b) => a+b;
f(6,2); //8
2.6.3.2 当箭头函数没有参数或者有多个参数,要用()括起来
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8
2.6.3.3 当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :
// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}
2.7 字符串
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
- includes():返回布尔值,判断是否找到参数字符串。
- startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。
模板字符串
let string = `Hello'
'world`;
console.log(string);
// "Hello'
// 'world"
字符串插入变量和表达式。
变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "Mike";
let age = 27;
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info);
// My Name is Mike,I am 28 years old next year.
字符串中调用函数:
function f(){
return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2); // Game start,have fun!