对象
在javascript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串,数值,数组,函数等。
对象是由属性和方法组成的:
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
类 class
在ES6中新增了类的概念,可以使用
class
关键字来声明一个类,之后使用这个类来实例化对象
类抽象了对象的公共部分,它泛指某一大类
对象特指某一个,通过类实例化一个具体的对象
class的基本语法
1.简介
JavaScript语言中,生成实例对象的传统方法是通过构造函数。
function Point(x,){
this.x = x
this.y = y
}
Point.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过
class
关键字,可以定义类。
基本上,ES6的
class
可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的
class
写法只是让对象原型的写法更加清晰,更像面向对象便形成的语法而已。
class Point {
constructor(x,){
this.x = x
this.y = y
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
ES6的类,完全可以看做构造函数的另一种写法
class Point {
//...
}
typeof Point //'function'
Point === Point.prototype.constructor
上面代码表明,类的数据类型就是函数,类本身就是指向构造函数
使用的时候也是直接使用
new
命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff')
}
}
var b = new Bar()
b.doStuff() //stuff
构造函数的
prototype
属性,在ES6的类上面继续存在。事实上,类的所有方法都定义在类的
prototype
属性上面。在类的实例上面调用方法,其实就是调用原型上的方法。
clas B {}
let b = new B
b.constructor === B.prototype.constructor
上面代码中,
b
是
B
类的实例,它的
constructor
方法就是
B
类原型的
constructor
方法
由于类的方法都定义在
prototype
对象上面,所以类的新方法可以添加在
prortotype
对象上面。
Object.assign
方法可以很方便地一次性添加多个方法。
class Point{
constructor(){
//...
}
}
Object.assign(Point.prototype,{
toString(){},
toValue(){}
})
prototype
对象的
constructor
属性,直接指向类的本身,这与ES5的行为是一致的。
Point.prototype.constructor === Point //true
另外,类的内部所有定义的方法,渎职不可枚举的(non-enumerable)
class Point{
constructor(x,){
//...
}
toString() {
//...
}
}
Object.keys(Point.prototype)
//[]
Object.getOwnPropertyNames(Point.prototype)
2. constructor方法
constructor
方法是类的默认方法,通过
new
命令生成对象实例,自动调用该方法。
一个类必须有
constructor
方法,如果没有显示定义,一个空的
constructor
方法会被默认添加。
class Point {
}
//等同于
class Point {
constructor(){
}
}
constructor
方法默认返回实例对象(即
this
),完全可以指定返回另外一个对象
类必须使用
new
调用,否则会报错。这是它跟普通函数的一个主要区别,后者不用
new
也可以执行。
3. 类的实例
生成类的实例的写法,与ES5完全一致,也是使用
new
命令,但必须使用
new
调用,否则报错。
实例的属性除非显示定义在其本身(即定义在
this
对象上),否则都是定义在原型上(即定义在
class
上)。
//定义类
class Point {
//构造器
constructor(name,){
//实例属性,这里的this指向Point的实例对象
this.name = name
this.age = age
}
//实例方法
sayName() {
console.log(this.name)
}
//静态方法
static say() {
console.log('静态方法')
}
}
//静态属性
Point.staticAttr = '静态属性'
//创建实例
let point = new Point('zhangsan', 18)
//调用实例方法
point.sayName() //'zhangsan'
//访问静态属性,静态方法
console.log(Point.staticAttr)
Point.say()
4. 静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上
static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Point{
//静态方法,只能通过类来调用,实例是不会继承静态方法的
static say(){
console.log('静态方法')
}
}
5. 实例方法
类的实例方法可以直接定义在类中,不用static修饰的方法都会被类的实例继承
class Point{
//实例方法
sayName(){
console.log('实例方法')
}
}
6. 静态属性
直接在类上定义的属性是类的静态属性,注意:并不是写在class里面,而是下面这种写法:
class Point{
}
Point.staticName = 'lisi'
7. 实例属性
类的实例属性可以定义在构造函数中(constructor)
class Point{
constructor(id,name){
this.id = id
this.name = name
}
}
class的继承
1. 简介
class可以通过
extends
关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。
class Point {
constructor(x,y){
this.x = x
this.y = y
}
toString(){
}
}
class ColorPoint extends Point {
constructor(x, y,){
super(x, y) //调用父类的constructor(x,y)
this.color = color
}
toString() {
return this.color + ' ' + super.toString() //调用父类的toString()
}
}
上面代码定义了一个
ColorPoint
类,该类通过
extends
关键字,继承了
Point
类的所有属性和方法(包括静态的和实例的)
子类必须在
constructor
方法中调用
super
方法,否则新建实例会报错。这是因为子类自己的
this
对象,必须先通过父类的构造函数完成塑造,得到与弗雷同样的实例属性和方法,然后再对其经行加工,加上子类自己的实例属性和方法。如果不调用
super
方法,子类就得不到
this
对象。
2. Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类
Object.getPrototypeOf(ColorPoint) ===
因此,可以用这个方法判断一个类是否继承了另一个类。
3. super关键字
super
这个关键字,既可以当做函数调用,也可以当做对象使用。在这两种情况下,它的用法完全不同。
- 第一种情况,
作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super
函数。super
class A {}
class B extends A {
constructor() {
super()
}
}
上面代码中,子类
B
的构造函数之中的
super()
,代表调用父类的构造函数。这是必须的,否则报错。
注意:
super
虽然代表了父类
A
的构造函数,但是返回的是子类
B
的实例,即
super
内部的
this
指的是
B
的实例,因此
super
在这里相当于
A.prototype.constructor.call(this)
作为函数时,
super()
只能用在子类的构造函数之中,用在其他地方就会报错。
- 第二种情况,
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。super
class A {
p(){
return 2
}
}
class B extends A {
constructor() {
super()
console.log(super.p()) //2 这里其实是调用了父类A的p方法
}
}
这里需要注意,由于
super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过
super
调用的。
class A {
constructor(){
this.p = 2
}
}
class B extends A {
get m() {
return super.p
}
}
let b = new B()
b.m //undefined
4. 类的prototype属性和__ptoto__属性
大多数浏览器的ES5实现之中,每一个对象都有
__proto__
属性,指向对应的构造函数的
prototype
属性。class作为构造函数的语法糖,同时有
prototype
属性和
__proto__
属性,因此同时存在两条继承链
class A {}
class B extends A {}
B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true
class A {}
class B {}
//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype)
//B 继承 A 的静态属性
Object.setPrototypeOf(B, A)
const b = new B()