ES6 - 部分整理

众所周知的ES6入门教程,此页主要是记录下.

prototype 与 proto

刚开始入门前端时,对这两个概念始终不是很了解,单纯是靠强行记忆来区分,以致时间长了就淡忘陌生,再提起就是一丝不安,到后来自是恐惧。
再翻阅阮一峰的文章,突然一个很直观的概念瞬间将两者做出了区分。
当我们不论使用ES6的 Class类,或者ES5以大写开头的构造函数方式创建原始继承对象时,我们默认会把他当做父集。那么问题来了,是否有爷爷辈?嗯,当然有.以下以创建Point对象为例:

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);

控制台打印Point.prototype:

{toString: ƒ, constructor: ƒ}

其中constructor就是Point,这说明什么?说明Point.prototype就是Point的父辈,Point的一举一动都在Point.prototype的掌控中。因为身份父辈,Point是不能直接称呼父亲名字的,所有他就尊称父亲为prototype;
我们看看new创建的实例对象p:

p.prototype //返回undefined
p.__prpto__ //返回{toString: ƒ, constructor: ƒ},即为其爷爷

那么这里从父辈Point的prototype和子辈p的__proto__都指向爷爷,说明什么?说明prototype__proto__是不同备份对祖辈的称呼。父亲对爷爷要喊爹(prototype),子辈当然要喊爷爷(proto),相信这么一来对这两个词就能分清了。无他,辈分称呼不同而已。

在这里肯定对子辈为什么不能像父辈直接获取上一辈耿耿于怀了,但是不要紧,我们不是还有instanceof嘛,这个至少能证明你属于哪个家族嘛...

另外孙辈获取爷爷的内容,可以通过Object.getPrototypeOf。父辈想要这么操作,也可以获取到父辈的爷爷,但是得到的结果是:

ƒ () { [native code] }

这就是告诉父辈你想找爷爷,我告诉你,就是函数本身(哈哈)...
当我们看到以下操作时:

p1.__proto__.printName = function () { return 'Oops' };

说明孙子不听话,跨过父辈直接找爷爷:我要做什么什么。于是孙子得到了,父辈也不得不按照爷爷说的做。

let和const命令

let
块级作用域,不存在变量提升,适用于for..in循环:

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

const
常量

  • const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
  • const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

变量的解构赋值

字符串扩展

includes\startsWith\endsWith

传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

这三个方法都支持第二个参数,表示开始搜索的位置。第二参数需要时可查看文档
repeat
repeat方法返回一个新字符串,表示将原字符串重复n次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // "

padStart(),padEnd()

  • ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
  • 上面代码中,padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串 ``` 'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

**模板字符串**

* 使用反引号标识```,所有的空格和缩进都会被保留在输出之中
* 变量`${}`,括号中可以是`变量`或`函数`

# 正则的扩展
略

# 数值的扩展
减少暴露全局性方法
**Number.isFinite(), Number.isNaN()**

* Number.isFinite()用来检查一个数值是否为有限的(finite)
* Number.isNaN()用来检查一个值是否为NaN。
> 它们与传统的全局方法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

**Number.parseInt(), Number.parseFloat()**
> 逐步减少全局性方法,使得语言逐步模块化。

Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

**Number.isInteger()**

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
Number.isInteger("15") // false
Number.isInteger(true) // false

**Math**

* Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4

* Math.sign方法用来判断一个数到底是正数、负数、还是零。

Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('foo'); // NaN
Math.sign(); // NaN

* Math.cbrt方法用于计算一个数的立方根。

* 指数运算符 `**`

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

let a = 1.5;
a **= 2;
// 等同于 a = a * a;

let b = 4;
b **= 3;
// 等同于 b = b * b * b;

# 数组的扩展

**Array.from**

* object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
* 扩展运算符(...)也可以将某些数据结构转为数组。
* 转换成的数组值为空,会用`undefined`占位
* Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

Array.from({ length: 3 });
// [ undefined, undefined, undefined ]

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], x => x * x)
// [1, 4, 9]

**Array.of()**

* Array.of方法用于将一组值,转换为数组。
* 这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

**数组其他**

* find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
* findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
* fill方法使用给定值,填充一个数组。

[1, 4, -5, 10].find((n) => n < 0)
// -5

[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

# Class
**Class基本语法**

//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
// 也可以通过如下增加方法
Point.prototype = {
toValue(){}
};

**__proto__与prototype**

class Point(){};
var p = new Point();
// 类的原型的构造函数指向 类本身
Point.prototype.constructor === Point

// proto:实例才有此属性,类只有prototype,指向类的原型
p.proto == Point.prototype // true
p.prototype // undefined

**类的属性枚举**

* 下面代码中,toString方法是Point类内部定义的方法,它是不可枚举的。这一点与ES5的行为不一致。可以使用`Object.getOwnPropertyNames`获取完整属性

class Point {
constructor(x, y) {
// ...
}

toString() {
// ...
}
}

Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]

// add
Point.prototype.toFun = function(){}

//再次展示
Object.keys(Point.prototype)
//["toFun"]

Object.getOwnPropertyNames(Point.prototype)
//"constructor", "toString", "toFun"]

**通过实例的__proto__属性为Class添加方法**

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.proto === p2.proto
//true

p1.proto.printName = function () { return 'Oops' };

p1.printName() // "Oops"
p2.printName() // "Oops"

var p3 = new Point(4,2);
p3.printName() // "Oops"

**私有方法**
私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。

class Widget {

// 公有方法
foo (baz) {
this._bar(baz);
}

// 私有方法
_bar(baz) {
return this.snaf = baz;
}

// ...
}

**Class继承:extends**

* Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class ColorPoint extends Point {}

* 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

**继承的类的prototype属性和__proto__属性***

class A {
}

class B extends A {
}

B.proto === A // true,说明继承是继承了父级的实例,所以就有了B的原型的原型为A
B.prototype.proto === A.prototype // true,可以理解为B.prototype为A的实例假设为a,则此题就是a.proto == A.prototoype

**super关键字**

* 代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。

class A {
p() {
return 2;
}
}

class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}

let b = new B();

**原生构造函数的继承**

class MyArray extends Array {
constructor(...args) {
super(...args);
}
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

**Class的取值函数(getter)和存值函数(setter)**

* 与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为

class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

@2017-05-13 15:53