对象的创建方式
// 字面量
let obj = {}
// Object实例
obj = new Object()
复制代码
let o = {}
console.log(o) // Object{}
// 构造函数 --- 目的是为了区分不同的实例对象
function Person(name, age) {
this.name = name
this.age = age
}
// new 是一种特殊的函数调用,不看return, 内部this为生成的那个实例对象,并会将实例对象返回
const per = new Person('Klaus', 24)
console.log(per) // Person{}
复制代码
面向对象的三大特性
封装 + 继承 + 多态
封装
将零散的数据,封装起来包含在一个类型中,统一管理
使用者只能看见类中定义的公共变量和公共方法,
而看不见方法的具体实现细节,也不能对类中非公共的数据进行操作。
这样可以防止外部的干扰和误用。
当封装的数据和逻辑发生改变的时候,只要不修改输入和输出的内容,
封装体的修改并不会封装体外部的代码产生任何的影响,内聚度高
继承
function Person(name, age) {
this.name = name
this.age = age
}
// 将方法定义在原型上,避免实例共有的方法定义到实例对象上
// 而是定义到统一的空间(原型)中,以便于节省存储空间,避免重复定义
Person.prototype.eat = function() {
console.log('eating')
}
// 组合继承 = 原型链继承 + 构造函数继承
function Student(name, age, sno) {
// 构造函数继承 --- 继承属性
// 使用Student的实例对象来调用父类的构造函数
// 所以此时的name和age本质上是加在Student的实例对象上的
Person.call(this, name, age)
this.sno = sno // 定义子类特有的属性
}
// 原型链继承 --- 继承方法 --- 子类原型是父类的实例对象
// 即原本的原型是Object的实例对象,现修改为父类的实例对象
// 这样子类就可以顺着原型链去获取到定义在父类中的方法
Student.prototype = new Person()
// new Person() => 虽然改变了原型,但是这依旧是构造函数的调用
// 这必然会导致Student的实例上有实际使用的name属性和age属性
// Student原型,也就是Person的实例上也有name和age属性
// 其值都是undefined, 因为原型链,所以会先获取到Student实例上的name和age属性
// 而永远无法获取到Student实例对象原型上的name和age属性,所以这个问题可以暂时忽略
// 手动添加子类的原型中的构造方法是子类的构造方法
// 这里就是修改stu.constuctor对应的值
// 设置 --- => Student构造函数
// 不设置 ---- => Person构造函数 --- 不合理
Student.prototype.constructor = Student // ☆☆☆☆☆
const stu = new Student('Klaus', 24, 1800160)
复制代码
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function() {
console.log('eating')
}
function Student(name, age, sno) {
Person.call(this, name, age)
this.sno = sno
}
// Object.setPrototypeOf和new Person()都可以实现原型链继承
// Object.setPrototypeOf(子类, 父类) => 为子类设置原型
// 1. 这种设置,Student的原型依旧是Object实例对象
// 2. 子类的原型对象上不会存在值为undefined的name属性和age属性
// Object.setPrototypeOf(Student, Person)
// 1. 下面这种设置的时候,Student的原型是Person的实例对象
// 2. 子类的原型对象上会存在值为undefined的name属性和age属性
Student.prototype = new Person()
Student.prototype.constructor = Student
const per = new Person('Alex', 33)
const stu = new Student('Klaus', 24, 1800160)
// 推荐使用Object.setPrototypeOf(子类, 父类)的方式,实现原型链继承
// 获取stu的原型对象
// console.log(Object.getPrototypeOf(stu))
复制代码
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function() {
console.log('eating')
}
function Student(name, age, sno) {
Person.call(this, name, age)
this.sno = sno
}
// 不可以设置子类的原型对象为父类的原型对象
Student.prototype = Person.prototype
const per = new Person('Alex', 33)
const stu = new Student('Klaus', 24, 1800160)
stu.eat() // => 虽然这么做子类也可以继承父类的方法
console.log(stu.constructor) // => Person()
console.log(per.constructor) // => Person()
// 子类的原型和父类的原型是一个对象
// 所以此时无论怎么修改,stu.constructor === per.constructor
// 这是不合理的
Student.prototype.constructor = Student
console.log(stu.constructor) // => Student()
console.log(per.constructor) // => Student()
复制代码
多态
同一个方法,根据调用者的不同,产生不同的结果
js中多态有2种表现:方法重写 + 方法重载
重写(Override)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function() {
console.log('eating')
}
function Student(name, age, sno) {
Person.call(this, name, age)
this.sno = sno
}
Student.prototype = new Person()
// 子类定义和父类同名的方法,以便于覆盖父类的方法
// 这就是方法的重写
Student.prototype.eat = function() {
console.log('student eating')
}
const per = new Person('Klaus', 33)
const stu = new Student('Klaus', 24, 1800160)
// 同一个方法根据不同的调用者产生不同的结果
per.eat() // => eating
stu.eat() // student eating
复制代码
重载(Overload)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eat = function() {
console.log('eating')
}
function Student(name, age, sno) {
Person.call(this, name, age)
this.sno = sno
}
Student.prototype = new Person()
// 所谓方法重载 -- 就是依靠传入的参数的个数不同或类型不同
// 来实现不同的调用返回不同的结果
Student.prototype.eat = function(food) {
console.log(`eat ${food}`)
}
const per = new Person('Klaus', 33)
const stu = new Student('Klaus', 24, 1800160)
per.eat() // => eating
复制代码
类
ES6中的类就是对之前ES5的面向对象写法的语法糖
class Person {
// 如果没有指定constructor,会存在一个默认的迭代器
// 如果指定了,那么就会使用自定义的constructor,默认的就会失效
/*
默认的构造器:
constructor() {
}
*/
}
// 创建类的实例对象
const per = new Person()
复制代码
class Person { constructor(name, age) { // this -> Person的实例对象 this.name = name this.age = age } // 这里定义的方法会被定义到实例的原型上 eat() { console.log('eat') }}// 创建类的实例对象,并进行初始化操作const per = new Person('Klaus', 22)
复制代码
class Person { // 在这里写的属性是所有子类共有的公共属性 // 但是不推荐,因为公共的属性和方法应该被添加到Person实例对象的原型上 country = 'China'constructor(name, age) { this.name = name this.age = age}eat() { console.log('eat')}}
复制代码
class Person { // 属性和方法之前添加static // 那么这个属性和方法就是类的属性和方法,需要通过Person.xxx去调用 // 实例对象是无法调用静态属性或方法的 per.xxx -> error static country = 'China' constructor(name, age) { this.name = name this.age = age } static eat() { console.log('eat') }}
复制代码
class Person { constructor(name, age) { this.name = name this.age = age } eat() { console.log('eat') }}// 使用extends关键字来表示Student继承自Person类class Student extends Person { constructor(name, age, sno) { // super方法必须在子类构造器第一次使用this关键字之前被调用 // 实际上name和age属性是加载在Student的实例对象上的 // 只是借用了父类构造器的代码 // 即等价于 Student.prototype.costructor.call(this, ...) super(name, age) this.sno = sno // 定义子类特有的属性 }}const stu = new Student('Klaus', 22, 1810166)// 此时stu的原型上也不会像ES5一样存在值为undefined的name和age属性console.log(stu)
复制代码
class Person { constructor(name, age) { this.name = name this.age = age }}class Student extends Person { // 以下的构造方法是实现继承后,子类默认的构造方法 /* constructor(...args) { super(...args) } */}const per = new Person()const stu = new Student('Klaus', 18)console.log(stu.name, stu.age) // Klaus 18
复制代码
class Person { constructor(name, age) { this.name = name this.age = age } eat() { console.log('eat') }}class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno } // 方法重写 eat() { console.log('student eating') }}
复制代码
class Person { constructor(name, age) { this.name = name this.age = age } // 这个属性在每一个Person实例上都会存在 // 在继承的时候这个属性也会被继承 // 且是存放在子类的实例对象上,而不是子类实例的原型对象上 count = 23eat() { console.log('eat')}}class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno } eat() { console.log('student eating') }}const stu = new Student('Klaus', 23, 1800160)console.log(stu) // { name: 'Klaus', age: 23, sno: 1800160, count: 23 }
复制代码
class Person { constructor(name, age) { this.name = name this.age = age } eat() { console.log('eat') }}// foo定义在Person的实例的原型对象上,所以子类可以使用super关键字获取foo对应的值Person.prototype.foo = 'foo'class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno } // 子类有一个关键字super,表示的是父类的实例的原型对象 // 所以可以使用super关键字来获取父类的方法和定义在父类实例的原型上的公共属性 eat() { // console.log(super) // => error 无法输出super对象 console.log(super.foo) // foo console.log('student eating') super.eat() // => eat }}
复制代码
class Person { constructor(name, age) { this.name = name this.age = age }}class Student extends Person { /* 当一个普通的构造函数运行时,它会创建一个空对象作为 this,然后继续运行。 但是当派生类(子类)的构造函数运行时,在js编译的时候,子类的构造函数会被打上标记 [[ConstructorKind]]:“derived” 此时js认为创建实例时候创建对象并赋值给this的操作,也是所有类在初始化的时候共有的特征 所以也应该被抽取到父类中,由父类实现this的创建和指派 所有子类中必须在使用this之前优先调用super方法,以借用父类的构造函数来创建this */ constructor(name, age, sno) { // console.log(this) -> error 无法在super函数前调用this关键字 super(name, age) // 借用父类构造函数初始化this this.sno = sno // this可以被正常使用 }}
复制代码
class Person { static count = 30constructor(name, age) { this.name = name this.age = age}static eat() { console.log('eat')}}class Student extends Person { constructor(name, age, sno) { super(name, age) this.sno = sno }}Person.eat() // => eatconsole.log(Person.count) // => 30// 父类的静态方法和属性可以被子类继承Student.eat() // => eatconsole.log(Person.count) // => 30
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END