本篇文章记录的是ES6中的let
,class
,extend
,super
,这几个与react
编写过程遇到的知识
参考:阮一峰ES6
let命令
它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。例如在for
循环就适合用let
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"
;。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀0表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
不存在变量提升
var
命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
暂时性死区
ES6明确规定,如果区块中存在 let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
var tmp = 0;
if(true) {
tmp = 2;
let tmp;
}
tmp = 0;
在没有let之
前,typeof
运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
class的基本用法
在ES5中,大部分是通过构造函数的形式来定义类。
在es6中,新添加一种通过class
构造类的方法。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
在上述定义中,constructor
就是构造方法,相当与构造函数,this
就是构造的实例对象
Point
类除了构造方法,还定义了一个toString
方法。
注意,定义“类”的方法的时候,前面不需要加上
function
这个关键字,直接把函数定义放进去了就可以了。
另外,方法之间不需要逗号分隔,加了会报错。
使用的时候,也是直接对类使用new
命令,跟构造函数的用法完全一致。
class Point {
constructor(props) {
this.x = x;
this.y = y;
}
toString() {
return '(' +this.x + ', ' + this.y + ')';
}
}
var m = new Point(x = 2, y = 4);
console.log(m.toString());
//(2, 4)
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor
在这个例子中,b
是B
的实例,B.prototype
是原型,
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign
方法可以很方便地一次向类添加多个方法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Object.assign(Point.prototype, {
toValue() {},
toString() {}
});
prototype对象的constructor属性,直接指向“类”的本身
console.log(Point === Point.prototype.constructor); //true
类的内部所有定义的方法,都是不可枚举的(non-enumerable)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {}
}
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {
constructor(x, y) {
return Object.create(null);
}
toString() {}
}
var m = new Point();
console.log(m instanceof Point);
该例子显示定义了constructor
返回的创建一个空对象,所以输出为false
类的构造函数,不使用new
是没法调用的,会报错
class Point {
constructor(x, y) {
return Object.create(null);
}
toString() {}
}
var m = Point();
与ES5一样,实例的属性除非显式定义在其本身(即定义在this
对象上),否则都是定义在原型上,就是说实例输入的值是其本身属性,其他均为原型属性
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ',' + this.y + ')';
}
}
var m = new Point(1,2);
console.log(m.hasOwnProperty('x')); //true
console.log(m.hasOwnProperty('y')); //true
console.log(m.hasOwnProperty('toString')); //false
console.log(m.__proto__.hasOwnProperty('toString')); //true,记住了原型是这样的
可以通过实例的__proto__
属性为Class
添加方法
var m = new Point(1,2);
var n = new Point(2,8)
m.__proto__.playTime = function () {
return this.x + this.y + ' minutes';
}
console.log(m.playTime());
console.log(n.playTime());
这意味着,使用实例的proto属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。
Class不存在变量提升(hoist),这一点与ES5完全不同。要先定义后使用,否则报错。
因为ES6不会把类的声明提升到代码头部。
{
let Foo = class {};
class Bar extends Foo {
}
}
上面的代码不会报错,因为Bar
继承Foo
的时候,Foo
已经有定义了。但是,如果存在class
的提升,上面代码就会报错,因为class
会被提升到代码头部,而let
命令是不提升的,所以导致Bar
继承Foo
的时候,Foo
还没有定义。
Class表达式
类的名字是MyClass
,Me
只在内部有定义,所以Me
是可以省略的
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
console.log(inst.getClassName());
console.log(Me.name);
this的指向
类的方法内部如果含有this
,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
一个比较简单的解决方法是,在构造方法中绑定this
,这样就不会找不到print
方法了
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
另一种解决方法是使用箭头函数
class的继承
Class
之间可以通过extends
关键字实现继承
class ColorPoint extends Point {}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
super
关键字,它在这里表示父类的构造函数,用来新建父类的this
对象。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象。
如果子类没有定义constructor
方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方
法。
constructor(...args) {
super(...args);
}
另一个需要注意的地方是,在子类的构造函数中,只有调用 super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super
方法才能返回父类实例。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
super
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
-
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class A {} class B extends A { constructor() { super(); } }
上面代码中,子类B的构造函数之中的super()
,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
- 第二种情况,
super
作为对象时,指向父类的原型对象
这里需要注意,由于super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的
不过,如果属性定义在父类的原型对象上,super
就可以取到