原发布地址链接
参考链接:阮一峰ES6入门
基础
一个简单的示意图
ES5实现构造函数和实例对象
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);
ES6的class可以看作一个语法糖,他的绝大部分功能ES5都可以做到,class写法只是让对象原型的写法更加清晰,更接近面向对象编程的语法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
constructor构造方法,this代表实例对象,ES5的构造函数对应ES6的constructor方法,类中的所有方法都定义在类的prototype上。
b.constructor === B.prototype.constructor // true
↑↑↑ 在类的实例上调用方法就是调用原型上的方法
Object.assign方法可以很方便地一次向类添加多个方法
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
类内部的所有方法都是不可枚举的,但是用ES5在prototype定义的方法可枚举
Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
constructor方法
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法,默认返回实例对象(即this),完全可以指定返回另外一个对象。一个类必须有constructor方法,如果没有显式定义,
JavaScript 引擎会自动为它添加一个空的constructor方法
实例属性
实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。
这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
class foo {
bar = 'hello';
baz = 'world';
constructor() {
// ...
}}
静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class Foo {}
Foo.prop = 1;
Foo.prop // 1
目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性,
现在有一个提案提供了类的静态属性,写法是在实例属性的前面加上static关键字
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
static 静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
如果静态方法包含this关键字,这个this指的是类,而不是实例。
静态方法可以与非静态方法重名
父类的静态方法,可以被子类继承
静态方法也是可以从super对象上调用的,super.staticFun()
类的实例
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p1 = new Point(2, 3);
var p2 = new Point(3, 2);
point.toString() // (2, 3)
//x,y定义在this上
p1.hasOwnProperty('x') // true
p1.hasOwnProperty('y') // true
//toString()定义在原型上
p1.hasOwnProperty('toString') // false
p1.__proto__.hasOwnProperty('toString') // true
// p1 p2的原型都是Point
p1.__proto__ === p2.__proto__
//true
_ _ proto_ _ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
getter setter
与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
存值函数和取值函数是设置在属性的 Descriptor 对象上的。
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'
class表达式
采用 Class 表达式,可以写出立即执行的 Class。
person是class实例
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}}('张三');
person.sayName();
私有方法和属性
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现
- 命名加以区分,加“_”表示私有方法,但不能从根本区分
- 把私有方法拿到类外定义
- 利用Symbol的唯一性,把私有方法的名字命名为一个symbol值,
一般情况下无法获取到它们,但还是可以通过Reflect.ownKeys()拿到
目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用 # 表示
new target 属性
new是从构造函数生成实例对象的命令。
ES6 为new命令引入了一个new.target属性
该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。
如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
class继承
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
ES5的继承是先创造子类的实例对象this,再将父类的方法添加到this上Parent.apply(this)
,和ES6的机制不同,ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this
c父类的静态方法,也会被子类继承。
不管有没有显式定义,任何一个子类都有constructor方法。
class ColorPoint extends Point {}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}}
Object.getPrototypeOf
用来从子类上获取父类
可以使用这个方法判断一个类是否继承了另一个类
Object.getPrototypeOf(ChildPoint) === ParentPoint
// true
super关键字
当作函数调用时,代表调用父类的构造函数,但返回子类实例
super内部的this指向子类相当于A.prototype.constructor.call(this)
。子类构造函数必须执行一次super函数, super函数只能用在子类构造函数里
当作对象调用,在普通方法中指向父类的原型对象,在静态方法中指向父类
在普通方法中:
super.p()就相当于A.prototype.p(),
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的,super内部this指向当前子类实例,相当于
super.parentfun.call(this)
由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
在静态方法中:
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}}
Child.myMethod(1); ![659be1c89defe3031f7e5beb4f365621.png](evernotecid://BC415947-3551-4894-9F72-1E5DB96250EB/appyinxiangcom/25723750/ENResource/p728)
// static 1
var child = new Child();
child.myMethod(2); // instance 2
类的 prototype 属性和proto属性
Class 作为构造函数的语法糖,同时有prototype属性和proto属性,因此同时存在两条继承链。
子类的proto属性,表示构造函数的继承,总是指向父类。
B.__proto__ = A
-
子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。
B.prototype.__proto__ = A.prototype
这样的结果是因为,类的继承是按照下面的模式实现的。
class A {}
class B {}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
Object.setPrototypeOf方法的实现
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;}
可以这样理解:作为一个对象,子类(B)的原型(proto属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例
实例的 proto 属性
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。也就是说,子类实例的原型的原型,是父类实例的原型
因此,通过子类实例的__proto__.__proto__属性
,可以修改父类实例的行为。
p2.__proto__.__proto__.printName = function () {
console.log('Ha');};
p1.printName() // "Ha"
原生构造函数的继承
ECMAScript 的原生构造函数大致有下面这些。
-Boolean()
-Number()
-String()
-Array()
-Date()
-Function()
-RegExp()
-Error()
-Object()
按照ES5的规则,先创建子类的this,在将父类的属性添加到子类上,但由于原生构造函数内部属性无法获取,导致无法继承
ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。这意味着,ES6 可以自定义原生数据结构(比如Array、String等)的子类,这是 ES5 无法做到的。
extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined