class Point{
}
target:掌握es6的class
相当于es5的语法糖 基于在函数的原型链(需要掌握js的继承和原型链的定义)
默认严格模式且不存在变量提升
toString 在es5上可以被枚举 Object.keys 但是在es6上不可以 因为它是定义在prototype上的property。
constructor 在class类中默认会添加 方法默认返回this实例对象
实例化class 必须使用new 否则报错
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上) 可以用hasOwnProperty 验证 proto不是语言本身的特性 可以使用Object.getPrototypeOf() 方法获取实例的原型 然后用Object.assign()为原型添加方法或者属性
实例之后的对象 公用一个原型对象 于es5一样
取值函数(getter)和存值函数(setter)
get 和 set 函数是设置在属性的Descriptor上的 可以用Object.getOwnPropertyDescriptor()调用
如果有相同名称的普通函数 会覆盖get和set函数 使用Object.assign()给类添加同名的函数 不会覆盖
类可以用定义变量来命名
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
Generator方法
如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。
(重点)this指向问题 默认指向类的实例
举例
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
文中 print的this指向该方法运行所在的环境 即全局 所以会报错
这种情况可以在constructor中 bind(this)或者箭头函数来绑定this
静态方法
类为es5的基础上创建的语法糖 相当于实例的原型 都会被实例继承
加上static 关键字 表示该方法不会被实例继承 而是直接通过类来调用 这就是'静态方法'
class Foo {
static classMethod() {
return 'hello';
}
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.classMethod() // 'hello'
Foo.bar() // hello
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
如果静态方法中包含this关键字 那么这个this指向的是类 而不是实例
静态方法也可以和非静态方法重名
静态方法虽然不可以实例继承 但是可以被子类继承 而且静态方法也是可以从super对象上调用
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // // "hello, too"
静态属性 指的是class本身的属性 而不是定义在实例对象(this)上的属性
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
新的定义静态属性的提案
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
实例属性的新写法 除了在constructor里面以外也可以写在类的顶层
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
私有属性的提案
带(#)
new.target 属性
用在构造函数之中 如果构造函数不是通过new命令调用的 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继承
子类自己的this对象必须先通过父类的构造函数完成塑造 所以子类必须再constructor方法中调用super方法
所以只有再super()之后才可以调用this关键字
父类的静态方法也会被子类继承
Object.getPrototypeOf() 可以用来从子类上获取父类
super 关键字可以用来当作对象和函数使用
当super当作函数的时候 this关键字 指向子类 因为super在子类是通过父类的构造函数完成塑造和子类this的绑定 相当于Point.prototype.constructor.call(this)
当super当作对象时 指向父类的原型对象 定义在prototype上则可以取到
在静态方法中 指向父类 在父类实例上的方法或者property 是无法通过super调用的
在子类普通方法中 super调用父类方法的时候 方法内部的this指向当前的子类的实例 说白了就是函数作用域
因为super() this关键字指向子类实例 所以如果通过super对某个属性赋值 赋值的属性会变成子类实例的属性
如果super作为对象 用在静态方法中 super指向父类 而不是父类的原型 因为静态方法本身就是类的直接调用
所以同样的道理 如果在子类的静态方法中存在this 那么这个this指向子类本身而不是子类的实例
使用super的时候 必须显式指定是作为函数还是作为对象使用,否则会报错
对象总是继承其他对象的 所以可以在任意一个对象中 使用super关键字
class Point{
constructor(){
this.p = 2
}
print(){
console.log(this.p)
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
this.p = 3
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
static m(){
super.print();// 静态方法中的this指向子类本身 而不是子类的实例 举例:ColorPoint.p = 3 则ColorPoint.m() //3 静态方法不可以实例化调用 应该为类的本身调用
}
m(){
super.print();
}
}
let b = new ColorPoint()
b.m()//undefined
//如果加上Point.prototype.p = 2;
b.m() //2
//如果加上 子类this.p = 3
b.m() //3
类的prototype属性和proto属性
这个是子类和父类之间继承的原型链
每个对象都有proto属性 指向对应构造函数的prototype属性 class作为构造函数的语法糖 同时有prototype属性和proto属性 因此同时存在两条继承链
子类的proto属性表示构造函数的继承 总是指向父类
子类的prototype属性的proto属性 表示方法的继承,总是指向父类的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;
}
//所以
Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;
Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
只要A是一个有prototype属性的函数 就能被B继承 但是由于函数都有prototype属性 所以A可以是任意函数
class A extends Object {
}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
如果不存在继承的情况
class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
原生构造函数的继承
原生构造函数是指语言内置的构造函数 通常用来生成数据结构
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
var colors = new MyArray();
colors[0] = "red";
colors.length // 0
colors.length = 0;
colors[0] // "red"
用原本的es5的继承会出现这种情况的原因是因为 子类无法获得原生构造函数的内部属性 通过Array.apply()或者分配给原型对象都不行 原生构造函数会忽略apply方法传入的this 也就是说 原生构造函数的this无法绑定,导致拿不到内部属性
es6 允许继承原生构造函数定义子类 因为es6是先新建父类的实例对象 this 然后在用子类的构造函数 修饰this 使得父类的所有行为都可以继承
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined
这个例子也说明 extends关键字不仅可以用来继承类 还可以用来继承原生的构造函数
Mixin 模式的实现
Mixin指的是 多个对象合成一个新的对象 新对象具有各个组成成员的接口 它的最简单实现方式
const a = {
a: 'a'
};
const b = {
b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}
//c对象是a对象和b对象的合成,具有两者的接口。