不想写前端的后端开发工程师,不是好厨师(第一回)

##零、前奏

###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

转载于:https://my.oschina.net/UBW/blog/1248259

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值