##零、前奏
###1. 项目的目录结构
/app
-- /js
---- app.js
---- util.js
-- /css
---- base.css
---- app.css
/dist
-- /js
---- app.js // 已经转码成了ES5
---- util.js // 已经转码成了ES5
---- bundle.js // 实际引用的js文件
-- /css
---- all.min.css // base.css + app.css 合并、压缩并重命名的文件
-- index.html
gulpfile.js
package.json
###2. 环境配置命令
- 初始化npm
$ npm init
- 安装gulp
$ npm install gulp --save-dev
- 安装babel
npm install --save-dev gulp-babel babel-preset-es2015
# ES2015转码规则
$ npm install --save-dev babel-preset-es2015
# react转码规则
$ npm install --save-dev babel-preset-react
# ES7不同阶段语法提案的转码规则(共有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
- 安装其他gulp工具
$ npm install gulp-rename gulp-concat gulp-uglify gulp-cssnano browserify vinyl-source-stream --save-dev
- gulp-rename 重命名文件
- gulp-concat 合并文件
- gulp-uglify 压缩js文件
- gulp-cssnano 压缩css文件
- browserify 让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码
- vinyl-source-stream 将Browserify的bundle()的输出转换为Gulp可用的vinyl(一种虚拟文件格式)流 至此环境已经搭建好了。接下来我们需要配置gulp,让我们的工作更有效率。
- 配置 gulpfile.js文件
const gulp = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');
const cssnano = require('gulp-cssnano');
const concat = require('gulp-concat');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
// 编译并压缩js
gulp.task('convertJS', function(){
return gulp.src('app/js/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(uglify())
.pipe(gulp.dest('dist/js'))
})
// 合并并压缩css
gulp.task('convertCSS', function(){
return gulp.src('app/css/*.css')
.pipe(concat('app.css'))
.pipe(cssnano())
.pipe(rename(function(path){
path.basename += '.min';
}))
.pipe(gulp.dest('dist/css'));
})
// 监视文件变化,自动执行任务
gulp.task('watch', function(){
gulp.watch('app/css/*.css', ['convertCSS']);
gulp.watch('app/js/*.js', ['convertJS', 'browserify']);
})
// browserify
gulp.task("browserify", function () {
var b = browserify({
entries: "dist/js/app.js"
});
return b.bundle()
.pipe(source("bundle.js"))
.pipe(gulp.dest("dist/js"));
});
gulp.task('start', ['convertJS', 'convertCSS', 'browserify', 'watch']);
一. 块级别变量的声明语句:let
1. 基础:
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
2. for循环相关的优化:
//(var变量)
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
//(let变量)
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
3. 必须先声明再使用:
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
4. 不允许重复声明:
//报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
const的用法:(也是一个声明块作用域的语法)
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
二.变量解构:(一个很重要性的变革)
1. 基本用法:
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 [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
只要符合Iterator接口的规范都可以用解构:
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
默认值的情况:
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
默认值的边界(默认值只认===undefined情况):
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
2. 对象的解构:
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
对象解构中的别名机制:
var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
别名中的一些小玄机:
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
let或者const必须与解构一体(不一体的情况下要使用圆括号):
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功
###3. 字符串也可以进行解构:
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
4. 参数传参的解构:
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
5. 解构的用途:
交换变量的值:
let x = 1;
let y = 2;
[x, y] = [y, x];
从函数返回多个值:
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
函数参数的定义:
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
提取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]
函数参数的默认值:
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
遍历Map结构:
var 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
二、字符串中的主要改进-模板:
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);
var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3
四、对函数的改进:
1. 可以有默认值了:
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p = new Point();
p // { x: 0, y: 0 }
2. 与解构联合使用默认值:
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined
3. undefined与null参数对默认值的影响:
function foo(x = 5, y = 6) {
console.log(x, y);
}
foo(undefined, null)
// 5 null
4. 函数作用域的问题:
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
var x = 1;
function foo(x = x) {
// ...
}
foo() // ReferenceError: x is not defined
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
5. 箭头函数基本:
var f = v => v;
相当于
var f = function(v) {
return v;
};
** 箭头函数注意事项:**<br> (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
**(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。 **
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
善于使用bind,call,apply函数来改变this
五、对对象的改进
1. 属性简洁的表示:
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同于
var baz = {foo: foo};
方法也可以简写啦:
var birth = '2000/01/01';
var Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
var ms = {};
function getItem (key) {
return key in ms ? ms[key] : null;
}
function setItem (key, value) {
ms[key] = value;
}
function clear () {
ms = {};
}
module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
2. 对象属性名的表达式写法:
var lastWord = 'last word';
var a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
3. Object.assign()方法使用:
用于对象的合并
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
4. ES6中对Object对象的遍历:
let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
六、全新的包装容器类:Set和Map
-
Set
1. Set基本用法
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
Set中的数据是没有重复值的,可以运用Set这个容器类进行去重操作
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs () {
return [...document.querySelectorAll('div')];
}
const set = new Set(divs());
set.size // 56
// 类似于
divs().forEach(div => set.add(div));
set.size // 56
2. Set实例属性和方法(基础)
- Set.prototype.constructor:构造函数,默认就是Set函数。
- Set.prototype.size:返回Set实例的成员总数。
- add(value):添加某个值,返回Set结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
// 对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
// do something
}
// Set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if (properties.has(someName)) {
// do something
}
// 遍历操作
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
3. Array.from方法可以将 Set 结构转为数组
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
//去重的方法
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
####4. 遍历的应用
使用(...)操作符
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
// 返回Set结构:{2, 4}
使用map与filter方法,同样可以作用于Set上面
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
实现并集、交集、差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
-
Map
####1. 基本含义与用法
JavaScript 的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
上面代码原意是将一个DOM节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object 更合适。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
2. 基本属性和方法
- size属性:size属性返回 Map 结构的成员总数。
- set(key, value):set方法设置键名key对应的键值为value,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
- get(key):get方法读取key对应的键值,如果找不到key,返回undefined。
- has(key):返回一个布尔值,表示该值是否为Map的成员。
- delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。
- clear():清除所有成员,没有返回值。
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
3. Map与其他类型进行相互转换
Map => Array
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
Array => Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
Map => Object
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
Object => Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
Map => JSON
// 键名都是字符串
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
//键名有非字符串
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
JSON => Map
//键名都是字符串
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
//整个JSON就是一个数组的情况
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
七、Iterator的梦幻加入
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
###1. Iterator遍历的过程
(1) 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2) 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3) 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4) 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
// 使用传统的方式,来构建一个遍历器
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
//使用最新的ES6中的标准
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- -Set
- String
- TypedArray
- 函数的 arguments 对象
注意:对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作Map结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。
2. 原型链上面的遍历器
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
###3. 通过遍历器实现指针结构的例子
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
} else {
return { done: true };
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
八、千呼万唤shi出来的类语法:Class
再曾经ES6还没有出来之前,就有一套js的类机制,人们使用了很久很久,发展的非常成熟。不过一直苦于没有在语法层面为js加入类机制
function Animal(family, special, hue) {
this.family = family;
this.special = special;
this.hue = hue;
}
Animal.prototype.yell = function(){
console.log(this.hue);
}
var dog = new Animal("Canidae","Canis lupus","Woung");
dog.yell(); //=> Woung
###1. 新的ES6中的类语法
class Animal{
constructor(family, special, hue){
this.family = family;
this.special = special;
this.hue = hue;
}
yell(){
console.log(this.hue);
}
}
const dog = new Animal("Canidae","Canis lupus","Woung");
dog.yell();//=> woung
###2. ES6中的函数与函数里面的箭头函数
类中定义的方法函数,都是带有作用域的普通函数,而不是箭头函数,方法内的第一层所引用的this都是指向当前实例的,如果实例方法内包含箭头函数,则引擎就会根据包含层级把箭头函数内引用的htis所指向的实际对象一直向上搜索,直到到达第一个函数作用域或是块级别作用域为止。如果一直搜索到达了运行环境的最上层,就会被指向
undefined
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
moveRight(step){
return new Promise(resolve => resolve({
x:this.x + step,
y:this.y
}));
}
}
const p = new Point(2,5);
p.moveRight(3).then(({x,y}) => console.log(`${x}, ${y}`));//=>5,5
3. 继承
吐槽:曾经的js中的继承是个什么鬼!
class Point2D{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return `jicheng`
}
}
class Point3D extends Point2D{
constructor(x,y,z){
super(x,y);
this.z = z;
}
toString(){
return `re jicheng`
}
}
注意:如果子类继承了父类,必须要在构造函数中调用父类的构造方法,使用super关键字,否则在子类的构造函数中无法使用this这个关键字!不过,如果没有使用super的话,在子类的其他成员方法中同样可以使用this,他们的this指向子类的实例
3. setter/getter方法
这种setter/getter方法是一种所谓的元编程的概念,元编程的特点在于,允许程序可以对运行时的对象进行读取与操作,从而使程序可以脱离代码从字面上为程序定义的一些限制,有了对对象的更高级别的操作权限,先举个简单的例子:
const Counter = {
_count:0,
get value(){
return ++this._count;
}
}
console.log(Count.value);
console.log(Count.value);
console.log(Count.value);
下面是一个ES6里面类的例子:
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
get d(){
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
}
}
const p = new Point(2,4);
console.log(p.d);
4. 修改对象默认的toString标签
如果一个类,没有自定义toString方法的话,使用toString方法返回的是该对象的对象名,例如
[Object Foo]
,如果想要修改这个默认的toString标签的话,可以使用Symbol.toStringTag作为键的这么个属性,下面是例子:
class Foo{
get [Symbol.toStringTag](){
return "Bar";
}
}
const obj = new Foo();
console.log(obj.toString()); //=>[Object Bar]
##九、已经成为请求的默认标准被广泛使用了:Promise对象
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
//传统的回调函数
animatel(()=>{
animate2(()=>{
animate3(()=>{
animate4(()=>{
})
})
})
})
//使用ES6中全新的Promise函数
var primise = new Promise(function(resolve, reject){
if(true){
resolve(value);
}else{
reject(error);
}
});
promise.then(value=>{
console.log(value);
}).catch(error=>{
console.log("错误是:"+error);
});
1. Promise特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
###2. Promise状态控制
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
###3. Promise具体应用举例
//下面是一个用Promise对象实现的 Ajax 操作的例子
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
###4. Promise.all()
var p = Promise.all([p1, p2, p3]);
p的状态由p1、p2、p3决定,分成两种情况 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
十、非常新颖的特性加入:Symbol对象
首先先来说说这个东西是个什么。Symbol其实叫做互不等价的概念的实体化的产物。通过Symbol创建的变量,即使是同一个描述值,如果使用==进行判断的话,也是不相等的,并且Symbol变量不能使用new来创建,要直接使用
Symbol(somthing)
这种模式来创建。
const symbol = Symbol();
const symbol2 = Symbol('something');
const symbol3 = Symbol('something');
console.log(symbol2==sombol3);//=>false
另外,还要强调的是,其实传入Symbol创建时候的这个值仅仅是起到了描述的作用,而不会对Symbol值本身起到任何的改变作用。
###1. 注册一个全局的key
可以使用Symbol.key()进行创建一个Symbol的值,这个创建方式和Symbol()这种不同的是,可以再全局创建一个key,如果全局没有这个key的话就会创建他,如果全局有这个key的话,就会使用已经有的这个key所对应的Symbol对象。
const symbol = Symbol.for('foo');
const obj = {};
obj[symbol] = 'bar';
const anotherSymbol = Symbol.for('foo');
console.log(symbol == anotherSymbol);//=>true
console.log(obj[anotherSymbol]);//=>bar
2. 一个比较重要的Symbol类型的值:Symbol.iterator
在ES6中新加入的for-of语句,是必须要要被循环的对象是可以迭代的对象,其实就是实现了Symbol.iterator这个方法的对象。如果要是自己实现自定义类的话,只有实现了这个方法,才能够被for-of遍历。 实现的过程中要注意的是,必须返回一个类似于迭代器的对象,对象里面要实现next()方法,以此来保证可以被for-of循环语句识别,下面是例子:
class Item{
constructor(value,prev=null,next=null){
this.value = value;
this.next = next;
}
get hasNext(){
return !this.next;
}
[Symbol.iterator](){
let currItem = {
next: this.head
};
return{
next(){
currItem = currItem.next;
currItem.next = currItem.next || {hasNext:false};
return {
value: currItem.value,
done:!currItem.hasNext
}
}
}
}
}
3. 另外一个比较好的实现:Symbol.hasInstance
ES6中提供了非常多的新的运算逻辑操作权限给开发者,其中Symbol.hasInstance就是其中一个。开发者可以使用这个方法作为关键字的静态类方法,改改变每次使用instanceof关键字的时候的行为,下面是栗子:
class Foo{
static [Symbol.hasInstance](obj){
console.log;
return true;
}
}
console.log({} instanceof Foo);//=>true