一步一步似爪牙。
前言
学习es6之前我们可能并不知道es6相比es5差距在哪, 但是这并不妨碍我们站在巨人的肩膀上; 程序员就是要乐于尝鲜;
学习es6最终目的是结合es5 一起进行工程项目开发, 而不是完全抛弃es5 ;
学习的方法是文档为主, 前人的blog为辅助, 实际console为最终标准 ;
注意!
号
留意TODO
关键词
ECMAScript 新功能 & 新特性
块的作用域-let
- 没有变量提升, 并且块级作用域(封闭作用域)
if (true) {
console.log(a) // 报错 : Uncaught ReferenceError: a is not defined(…)
let a = 'js'
}
// 这里也访问不到 a
- 一个作用域不能重复声明同一个值
if(true){
var a = 1;
if (true) {
let a = 'js' // a == 'js'
}
a = 3; // a == 3;
let a = 2 ; // 报错Uncaught SyntaxError: Identifier 'a' has already been declared ;
// 题外话, 在console中, 产生报错后a的内存指引被清除, 可以重新赋值
let a = 0; // a == 0
// 但实际开发中, 产生报错, 后面的代码不再执行, 所以不会有这种情况产生
}
恒量-const
- const声明一个只读的常量。一旦声明,常量的值就不能改变。
const a = 1;
a = 2; // 报错 : Uncaught TypeError: Assignment to constant variable.(…)
- const声明的数组, 对象, 还可以进行增改查, 但是不能重新定义
const a = [];
a.push(2) // a == [2]
const b = {};
b.a = a ; // 包含了a数组的对象
- 只在声明的作用域有效(与let一样)
if(true){
const c = 1;
}
// 此处访问会报错: Uncaught ReferenceError: c is not defined(…)
总结: 可以把const理解为let的常量版(不可改的let)
解构数组-Array Destructuring
数组的新语法
let [a, b, c] = [1, 2, 3];
// 即: let a = 1;let b = 2;let c = 3;
let [bar, foo] = [1];
// 解构失败: undefined
// 即: let bar = 1; let foo = undefined
let [a] = 1;
// 等号的右边不是数组 (或者严格地说,不是可遍历的解构)
// 报错:VM2799:2 Uncaught TypeError: undefined is not a function(…)
// 如果赋值的字面量全等undefined, 则默认值生效
let [x = 1] = [undefined];// x === 1
let [x = 1] = [];//x === 1
// null 不全等 undefined, 所以默认值不生效
let [x = 1] = [null];// x === null
- 进阶语法
// 解构赋值允许指定默认值。
let [foo = true] = []; // 即: foo === true
- 坑!小心
function f() {
console.log('aaa');
}
let [x = f()] = [1];// x === 1
// 此处f函数并没有执行; 如果是es5, 在赋值过程中, 肯定会先执行f()方法: log('aaa')
// es6 没有执行f方法, 经过粗略查阅文档, 百谷后, 并没有得到答案; 生命有限, 留待日后, TODO
// 在与`MeatHill`老师交流过后,得知 , 在此处后面有具体的值,就不需要计算默认值, 赋值是从右往左运行
- 奇奇怪怪的, 我猜可能会出面试题
let [x = 1, y = x] = []; // x == 1; y == 1;
let [x = 1, y = x] = [2]; // x == 2; y == 2; 别急, 一步一步来
// 第一步: x = 2(赋值从右到左, x = 1 不会计算);
// 第二步: y = x = 2;
let [x = 1, y = x] = [1, 2]; // 答案呢? 留着想
let [x = y, y = 1] = [];
// 第一步 x = y ; y未声明报错
解构对象-Object Destructuring
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
- 语法
let { foo, bar } = { foo: "aaa", bar: "bbb" };
// 变量必须与属性重名
// 以上其实是 let { foo : foo, bar : bar } = { foo: "aaa", bar: "bbb" };的简写;
// 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
// foo === "aaa"; bar === "bbb"
let {foo} = {bar: 'baz'};
// foo === undefined
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
// 数值也属于对象的一种表现形式
- 默认值
let {x = 3} = {x: undefined};
// x === 3
// 默认值生效的条件是,对象的属性值严格等于undefined。
- 注意
let { foo: baz } = { foo: "aaa", bar: "bbb" };
// 上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
- 高阶(解析嵌套结构)
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj
// 注意,这时p是模式,不是变量,因此不会被赋值。
- 疑难杂症
let {foo: {bar}} = {baz: 'baz'};
// 报错 : VM3721:2 Uncaught TypeError: Cannot match against 'undefined' or 'null'.(…)
// 报错 : 等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错
let x;
{x} = {x: 1};
// 报错: 因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。
// 应该不将大括号写在行首,避免JavaScript将其解释为代码块, 正确的写法:
let x;
({x} = {x: 1})
其他数据的解构-Object Destructuring
函数参数的解构(解构参数 - Destructured Parameters)
// 第三个参数默认值为 {}
function demo(a, b, {c , d} = {}){
console.log(a, b, c, d)
// 1 2 'c' 'd'
}
demo(1, 2, {c : "c", d:"d"})
字符串的解构赋值
let [a, b, c, d, e] = 'hello'
// a === 'h', b === 'e', ......
let {length} = 'hello';
// length === 5
// 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值
- 小总结
- 数组的解构赋值对应的是位置
// 所以可以交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
对象对应的是键名
- 解构的意义
- 数组结构可以方便快速的交换变量的值
- 接受函数return的值, 如果函数返回array或者object, 利用解构方便快捷
- 解析JSON数据
- 代替 ||
模版字符串-Template Strings
- 概念
${}
关键词- 反引号包裹字符串
- 语法
let name = "Bob",
time = "today";
console.log(`Hello ${name}, how are you ${time}?`)
// 模板字符串都是用反引号表示,如果在模板字符串中需要使用反引号,则前面需要用反斜杠转义
- 多行字符串
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"
带标签的模版字符串-Tagged Templates
参考
function a(b, ...c){
console.log(b)
// ["我的年龄是", ",我的性别是", "", raw: Array[3]]
console.log(c)
// Array : ["1", "nan"]
}
let d = "1";
a`我的年龄是${d},我的性别是${f}`
在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串
判断字符串里是否包含其他字符串
传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又为String对象扩展了三种新方法。
参考
- includes : https://developer.mozilla.org...
- startsWith : https://developer.mozilla.org...
- endsWith : https://developer.mozilla.org...
- includes():返回布尔值,表示是否找到了参数字符串。
'Blue Whale'.includes('blue'); // return false
'Blue Whale'.includes('Whale' , 2); // return true
- startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
'Blue Whale'.startsWith('blue'); // return false
'Blue Whale'.startsWith('Whale' , 2); // return false
'Blue Whale'.startsWith('Whale' , 5); // return true
- endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
'Blue Whale'.endsWith('blue'); // return false
'Blue Whale'.endsWith('Whale' , 10); // return true
'0Blue'.endsWith('Blue' , 5); // return true
默认参数 - Default Parameter Values
以前es5 :
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
// ...
}
现在es6 :
function makeRequest(url, timeout = 2000, callback = function(){}) {
// ...
}
如果不传入参数, 或者传入undefined, 则使用默认值;
展开操作符-Spread
语法
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5);
// 1 2 3 4 5
用于函数调用
function func(x, y, z, a, b) {
console.log(x, y, z, a, b);
}
var args = [1, 2];
func(0, ...args, 3, ...[4]); // 0 1 2 3 4
更好的 apply 方法
// es5使用数组作为参数:
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);
// es6使用数组作为参数:
function demo(x, y, z) {
console.log(x, y, z)
}
var args = [0, 1, 2];
demo(...args);
浅复制一个数组
let arr = [1, 2, 3];
let arr2 = [...arr]; // 就像是 arr.slice()
arr2.push(4);
console.log(arr2) // [1, 2, 3, 4]
// arr 不受影响
合并数组
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1 , ...arr2]
// arr3 : [0, 1, 2, 3, 4, 5]
将类数组对象转换成数组
var nodeList = document.querySelectorAll('div');
var array = [...nodeList];
// 实际: 360浏览器报错 : VM2386:3 Uncaught TypeError: nodeList[Symbol.iterator] is not a function(…)
// 版本: 8.7.0.306
// 火狐为一个空数组
// 版本:26.0.0.137
// google为undefined
// 版本:57.0.2987.133
剩余操作符Rest - 剩余参数(rest参数)
定义:
剩余参数只包含那些没有对应形参的实参
语法:
function(a, b, ...restArgs) {
// ...restArgs 即为剩余参数, 是一个Array对象
}
剩余参数和 arguments 对象的区别
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
- arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
- arguments 对象对象还有一些附加的属性 (比如callee属性)。
实例:
function fun1(...theArgs) {
alert(theArgs.length);
}
fun1(); // 弹出 "0", 因为theArgs没有元素
fun1(5); // 弹出 "1", 因为theArgs只有一个元素
fun1(5, 6, 7); // 弹出 "3", 因为theArgs有三个元素
Object.keys() - 返回对象可枚举的键
// TODO
函数的名字-name属性
函数的name属性,返回该函数的函数名。
这个属性早就被浏览器广泛支持,但是直到 ES6 ,才将其写入了标准。
实例:
function foo() {}
foo.name // "foo"
值得一提:
- 并不完全支持的ES6(360浏览器环境)
var a = function(){}
a.name
// 空字符串: ""
let b = function(){}
b.name
// 同样是 ""
解读:
ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量, ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
但实际上国内目前主流浏览器支持的还是es5为主, 在node下搭建的es6环境, 才会返回实际的函数名(Google测试已经支持)
- 函数名的优先级
let a = function d(demo){}
a.name // "d"
箭头函数-Arrow Fuctions
重点:
!箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值
!也就是可以理解为: 箭头函数没有自己的this, 如果上下文没有this, 则this指向Window对象
参考
- ES6-this[MDN} : https://developer.mozilla.org...
语法
- 基础语法
let demoFun = oArgument => oDessert
// demoFun 为函数名
// oArgument 为函数形参
// oDessert 为返回的值
let func = (x, y) => { return x + y; };
//常规编写 明确的返回值
- 高级语法
let demoFun = (oArgument, drink) => oArgument + drink
// 返回的是 oArgument + drink
let demoFun = params => ({foo: bar})
//返回一个对象时,函数体外要加圆括号
let demoFun = (param1, param2, ...rest) => { statements }
// 支持 剩余参数和默认参数:
描述
- 不绑定 this
箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,因此下面的代码将如期运行。
function Person() {
this.age = 0;
setInterval(() => {
// 回调里面的 `this` 变量就指向了期望的那个对象了
this.age++;
}, 3000);
}
var p = new Person();
- 箭头函数没有自己的 arguments
var arr = () => arguments;
arr(); // Uncaught ReferenceError: arguments is not defined(…)
- 箭头函数不能用作构造器,和 new 一起用就会抛出错误。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
- 箭头函数没有原型属性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
- 箭头不是操作符
在箭头函数中的箭头不是操作符(或者运算符,就像'+ -'那些), 但是箭头函数有特殊的解析规则就是:相比普通的函数,受操作符的优先级影响。
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {}; // SyntaxError:非法箭头函数属性
callback = callback || (() => {}); // ok
疑难杂症
- 像方法一样使用箭头函数
'use strict';
let obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b(); // undefined, Window对象, 此处或许有疑问, 可翻阅?提过的`!`
obj.c(); // 10, Object {...}
- 不能返回预期的
对象
let func = () => { foo: 1 };
// undefined
所以,记得用圆括号把文字表达式包起来:
var func = () => ({ foo: 1 });
箭头u函数在某些情况加还是很有便利的, 但是新的语法, 可读性有所转变, 要多使用, 多看才行
for..of
// TODO
symbol
// TODO
对比两个值是否相等-Object.is()
Object.is() 方法确定两个值是否是 相同的值。
语法
Object.is(value1, value2); // 返回一个布尔值
与ES5 的 全等操作符比较
- ES6:
Object.is(NaN, NaN); // true
Object.is(0, -0); // fasle
Object.is(-0, +0) // false
Object.is({},{}) // false
var a = function () {};
var b = function () {};
Object.is(a, b) //false
- ES5
NaN === NaN // false;
0 === -0 // true
-0 === +0 // true
{} === {} // false
var a = function () {};
var b = function () {};
a === b //false
兼容ES5 环境
if (!Object.is) {
Object.is = function(x, y) {
// SameValue algorithm
if (x === y) { // Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
};
}
把对象的值复制到另一个对象里 - Object.assign() - 浅拷贝
定义:
Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
语法:
// target目标对象。
// sources(多个)源对象。
Object.assign(target, ...sources)
实例:
var obj = { a: 1 };
var copy = Object.assign({}, obj); // {a: 1}
合并对象:
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign({},o1, o2, o3); // Object {a: 1, b: 2, c: 3}
注意
- 继承属性和不可枚举属性是不能拷贝的
- 原始类型会被包装为 object
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo")
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
- 异常会打断接下来的拷贝任务
设置对象的 prototype - Object.setPrototypeOf()
定义:
Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
语法:
Object.setPrototypeOf(obj, prototype)
// obj 要设置其原型的对象。.
// prototype 该对象的新原型(一个对象 或 null).
实例:
var dict = Object.setPrototypeOf({}, null); // Object {}
proto
es6 可以设置对象的__proto__
super
定义:
super 关键字用于调用一个对象的父对象上的函数。
迭代器 - Iterators
生成器 - Generators
参考:
- MDN -迭代器&生成器 : https://developer.mozilla.org...
Classes - 类
- JavaScript 现有的基于原型的继承的语法糖, 提供了一个更简单和更清晰的语法来创建对象并处理继承。
- 类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。
类声明
使用class
关键字声明类
// 声明一个类名为 Rectangle 的JS类
// constructor方法用于创建和初始化使用一个类创建的一个对象。只允许存在一个
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
类声明不会声明提升
类表达式
类表达式可以是被命名的或匿名的。
/* 匿名类 */
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
/* 命名的类 */
let Rectangle = class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
this
如果没有指定this, this值将为undefined。
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
let speak = obj.speak;
speak(); // undefined
let eat = Animal.eat;
eat(); // undefined
demo :
"use strict"
// 创建一个 Chef 类
class Chef {
// 初始化
// 接收参数
constructor(food){
this.food = food;
}
cook(){
console.log(this.food)
}
}
let qing = new Chef("?")
qing.cook(); // ?
类的 get 与 set
"use strict"
// 创建一个 Chef 类
class Chef {
// 初始化
// 接收参数
constructor(food){
this.food = food;
this.dishs = [];
}
get menu(){
return this.dishs;
}
set menu(dishs){
this.dishs.push(dish)
}
cook(){
console.log(this.food)
}
}
let qing = new Chef()
console.log(qing.menu = "?") //?
console.log(qing.menu = "❦") // ❦
类的静态方法-staitc
demo
"use strict"
// 创建一个 Chef 类
class Chef {
// 初始化
// 接收参数
constructor(food){
this.food = food;
this.dishs = [];
}
get menu(){
return this.dishs;
}
set menu(dishs){
this.dishs.push(dish)
}
static cook(food){
console.log(food)
}
}
// 不需要实例化Chef即可以直接使用
Chef.cook("?")
// 输出 : ?
类的继承-extends
demo
"use strict"
class Person{
constructor(name, birthday){
this.name = name
this.birthday = birthday
}
intro(){
return `${this.name}, ${this.birthday}`
}
}
// 使得Chef类 继承 Person类
class Chef extends Person{
constructor(name, birthday){
// 调用父类的constructor函数
super(name, birthday)
}
}
let qing = new Chef('qing', "1949-10-1");
// 实例化后才可使用 Chef类 的intro方法
qing.intro() // "qing, 1949-10-1"
Set方法
参考:
定义:
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
语法:
new Set([iterable]);
// iterable
// 如果传递一个可迭代对象,它的所有元素将被添加到新的 Set中。
// 如果不指定此参数或其值为null,则新的 Set为空
// 返回值
// 一个新的Set对象。
! 使用Set去重, Set中的元素只会出现一次, Set 中的元素是唯一的
DEMO:
- 使用Set对象
let mySet = new Set();
mySet.add(1); // {1}
mySet.add(5); // {1, 5}
mySet.add("some text"); //{1, 5, "some text"}
mySet.has(1); // true
mySet.has(3); // false
mySet.has(5); // true
mySet.has(Math.sqrt(25)); // true
mySet.has("Some Text".toLowerCase()); // true
mySet.size; // 返回Set对象的值的个数 : 3
mySet.delete(5); // 从set中移除5并返回true
mySet.delete(8); // 不存在8, 返回false
mySet.has(5); // false, 5已经被移除
mySet.size; // 2, 我们刚刚移除了一个值
// 按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。
mySet.forEach(callbackFn[, thisArg])
// 与values()方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
mySet.keys()
// 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
mySet.values()
mySet.clear() // 清空全部值
与 Array 的联系 , 互换
var myArray = ["value1", "value2", "value3"];
// 用Set构造器将Array转换为Set
var mySet = new Set(myArray);
mySet.has("value1"); // returns true
// 用...(展开操作符)操作符将Set转换为Array
console.log([...mySet]); // 与myArray完全一致
Map方法
参考:
定义:
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
实例:
var myMap = new Map();
var keyObj = {},
keyFunc = function () {},
keyString = "a string";
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");
myMap.size; // 3
// 读取值
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get(keyObj); // "和键keyObj关联的值"
myMap.get(keyFunc); // "和键keyFunc关联的值"
myMap.get("a string"); // "和键'a string'关联的值"
// 因为keyString === 'a string'
myMap.get({}); // undefined, 因为keyObj !== {}
myMap.get(function() {}) // undefined, 因为keyFunc !== function () {}
注意:
- Map 认为 NaN === NaN
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"
相比较:
- Map可以获得length, Object不可以
- Map的键可以是任意字, Object的只能为字符串, 或者变量
- Map 认为 NaN === NaN
- Map内置很多迭代, 查询, 操作方法
结语
- 花了半天时间学习了下ES6的部分新语法丶新特性。 不求精通, 但说涉猎。不实践怎么来的发言权 ;
- ES6新增了很多方法和功能参数, 但是其实相对于ES5来, 变革性并不是那么大, 学起来兴趣乏乏, 有点像鸡肋;
- ES6中我最感兴趣的是
Promise
,Class
, 期待ES8的异步函数(Async Functions)
; - 项目中打算深入实践ES6, 毕竟这波不亏 ? ;