类的定义
- ES6中的
class
可看作是一个语法糖。 只是为了更像面向对象编程的语法而已。让对象的原型的写法更清晰。 - constructor方法、doThing方法都是定义在F.prototype对象上的。只不过constructor方法
只允许通过new F() 来调用
。 - constructor方法是
默认的方法
。new生成对象实例时,自动调用。一个类中必须有该方法。没有的话,一个空的constructor方法被默认添加
。 - 且constructor方法默认返回实例对象(即this)。
- 类内部的所有定义方法,都是
不可枚举
的。(这里与es5有区别。)
function F(name, age) {
this.name = name
this.age = age
}
F.prototype.doThing = function() {
console.log('something')
}
let f = new F('fjh', 20)
console.log(f)
f.doThing()
// 对比于 es6的class
class F {
constructor(name, age) {
this.name = name
this.age = age
}
doThing() {
console.log('something')
}
}
let f = new F('fjh', 20)
console.log(f)
f.doThing()
class F {
constructor(name, age) {
this.name = name
this.age = age
}
doThing() {
console.log('something')
}
}
console.log(Object.keys(F.prototype))
// []
console.log(Object.getOwnPropertyNames(F.prototype)
// ['constructor','doThing']
function F(name, age) {
this.name = name
this.age = age
}
F.prototype.doThing = function() {
console.log('something')
}
console.log(Object.keys(F.prototype))
//['doThing']
console.log(Object.getOwnPropertyNames(F.prototype))
//['constructor','doThing']
- 实例对象的__proto__指向当前实例对象的构造函数的原型对象。
- 这里使用的
getPrototypeOf()
。可以引申到原型链。(注意该方法获得的是__proto__所指向的)
class F {
constructor(name, age) {
this.name = name
this.age = age
}
}
let f = new F('fjh', 20)
Object.getPrototypeOf(f).doThing = function() {
console.log('something')
}
f.doThing()
//something
/* 相当于f.__proto__.doThing = functiong(){}
只不过__proto__是私有属性。不推荐对其操作。
*/
- 取值函数(getter)和存值函数(seter):
- 对某个属性设置。 这样访问时就可以
拦截
该属性的存取行为。 - 且这两个函数是定义在
属性的 Descriptor 对象上
. 通过Object.getOwnPropertyDescriptor()可以获取到。
- 对某个属性设置。 这样访问时就可以
class F {
get name() {
return 'fjh'
}
set name(value) {
console.log('set---' + value)
name = value
/*
这里不能用this.name = value 。否则会造成栈空间溢出。因为其相当于在set方法内调用了set方法。
*/
}
}
let f = new F()
console.log(f.name)
f.name = 'fff'
/*
fjh
set---fff
*/
- 属性表达式
- 可以用作定义对象的可迭代属性。十分灵活。
let func = 'doThing'
class F {
[func]() {
console.log('something')
}
}
let f = new F()
f.doThing()
//something
//通过Generator方法。可以和之前的Generator串起来!!!
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
/*
hello
world
*/
}
- this的指向
- 类的方法内部的this。 默认指向类的实例 。但是this的指向可能会改变。
class F {
f() {
console.log(this)
}
}
let f1 = new F()
let {
f
} = f1
f()
//undefined
/*
这里的f函数是通过对象解构获得的。而class内部是严格模式,所以在调用f函数会输出undefined
可以通过bind函数绑定this 或 使用箭头函数(箭头函数没有this,其this时函数定义时所处的上下文对象!!!)
*/
-
静态方法(这个涉及到继承)
- 在方法前,加上
static
关键词表示是个静态方法。不会被实例
所继承。而是通过类来调用。 - 且其this的指向,指向的是类,而不是实例。
- 父类的静态方法可以被
子类继承
。
- 在方法前,加上
-
静态属性(类本身的属性,而不是实例对象上的属性)
- 一种写法: 类名.属性名 = ‘…’ (在类外定义)
- 另一种写法: static 属性名=‘…’ (在类中定义)
-
私有方法和私有属性:
- 只能在类的内部访问的方法和属性。外部不能访问。有利于封装:
- 第一种:前加下划线(’ _ ') 。 表示只限于内部使用。 但是在类的外部还是可以调用的。
- 第二种: 将私有方法移出类 。 因为
类内部的所有方法都是对外可见的
。 (在类内部使用call方法去调用) - 第三种:使用
Symbol
的唯一性。 将私有方法的名字命名为一个Symbol值
- 只能在类的内部访问的方法和属性。外部不能访问。有利于封装:
-
new.target
- 属性一般
用在构造函数中
(用在函数外会报错) 。 返回new命令作用于的那个构造函数。确定构造函数如何调用的。 - 子类继承父类时,new.target会返回子类。
- 可以写出不能独立使用、必须继承后才能使用的类。
- 属性一般
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的继承
有两条原型链:
一条是子类继承了父类的静态属性和方法。
另一条是子类的实例继承了父类的原型上的方法。
class F {
static a = 1
static b = () => console.log('b')
c() {
console.log('c')
}
}
class S extends F {
}
let s = new S()
console.log(S.a) // 1
console.log(s.a) // undefined
S.b() //b
s.b() //报错。s.b is not a function
S.c() //报错。S.c is not a function
s.c() // c
- 通过
extends
关键词实现继承 . 还是通过原型去理解。(每个对象都有__proto__
属性,__proto__指向对应的构造函数的prototype属性 。 而构造函数对象在此基础上还有prototype属性
。)- 子类的__proto__属性,表示
构造函数的继承
,总是指向父类。 - 子类的prototype属性的__proto__属性,表示
方法的继承
,总是指向父类的prototype属性.
- 子类的__proto__属性,表示
class F {}
class S extends F {}
console.log(S.__proto__ === F)
console.log(S.prototype.__proto__ === F.prototype)
console.log(S.constructor === Function)
/*
true
true
true
*/
- 上述过程的模式实现:
//S的实例继承F的实例
Object.setPrototypeOf(S.prototype,F.prototype)
//B继承A的静态属性
Object.setPrototype(S,F)
- 子类
必须在constructor方法中调用super方法
。否则子类就得不到this对象。- es5中的继承:先创建子类的实例对象
this
。再将父类的方法添加到this上(father.apply(this)). - es6中的继承:先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this.
- es5中的继承:先创建子类的实例对象
function F(name) {
this.name = name
}
function S(name, age) {
F.call(this, name)
this.age = age
}
class F {
constructor(name) {
this.name = name
}
}
class S extends F {
constructor(name, age) {
super(name)
this.age = age
}
}
/*
结合上面的话。 意思是通过super方法来获得this对象,然后才在this对象上定义子类实例自身的属性。
*/
Object.getPrototypeOf(S) === F
// true
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关键词
- 可以当函数使用,也可当对象使用。
- 函数使用:
代表父类的构造函数
class F {}
class S extends F {
constructor() {
super();
}
}
/*
就和前面的类中方法定义串起来了。
super() 等同于 F.prototype.constructor.call(this)
this指向的是S的实例。
*/
- 当对象使用:
- 普通方法中,指向父类的原型对象 ; 静态方法中,指向父类。
- this指向。
- 子类
普通方法
中通过super调用父类的方法时,方法内部的this指向当前的子类实例
。 - 子类的
静态方法
中通过super调用父类的方法时,方法内部的this指向当前的子类
,而不是子类的实例。
- 子类