对js面向对象的思考(1)

32 阅读3分钟

又是一年年底,颓废了一整年的邹师傅也开始琢磨着输出点什么。这一年裁员的消息不知听了多少,不知道互联网的寒潮什么时候能够退去,还有新冠。。。我什么时候会🐐。

算了,不想了。时代的巨浪滔天,我怕你把握不住啊孩子🐶

不如一起学习ts吧。

什么?面向对象都搞不清楚?BBQ了,五年的老前端表示“俺也一样”👻👻👻羞愧ing😔

没办法,好好battle下吧。

Q:原型链

只要一说js面向对象,原型链就是绕不开的话题😓

划重点 划重点

  1. 每个实例对象(Object)都有一个私有属性(__proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为null。根据定义,null没有原型,并作为这个原型链中的最后一环。
  2. 所有的函数都有prototype属性指向由它创建出来的实例对象的__proto__
  3. 原型对象的constructor属性指向其构造函数

上代码

var a = new Object();
a.__proto__ === Object.prototype; // true
Object.__proto__ === Function.prototype; // true

发现了一个好用的方法Object.create(proto),用来创建一个新对象,使用传入的对象proto来作为新创建对象的原型

Q:es5如何实现类与继承?

邹师傅java虽学的不咋地,但是面向对象的三个特性(继承、多态、封装)可是刻在DNA里了🤪。如下所示,es6规范引入了class和extends来实现类与继承

class Person {
    constructor({firstName, lastName}) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.fullName = `${firstName} ${lastName}`   
    }
    sayName() {
        console.log(`My name is ${this.fullName}`)    
    }
}
class Student extends Person {
    constructor(props) {
        super(props)
        const { No, grade } = props;
        this.No = No;
        this.grade = grade;    
    }
    introduce() {
        console.log(`My name is ${this.fullName}, number ${this.No}, Grade ${this.grade}`)    
    }
}
var student1 = new Student({firstName: 'zhang', lastName: 'san', No: 1001, grade: 5})
student1.introduce() // My name is zhang san, number 1001, Grade 5

原型链继承首当其冲,直接修改子类的原型对象指向父类实例,当子类实例上找不到对应的属性和方法时,就会往他的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承。

但是,使用原型链继承时,所有的子类实例原型都指向同一个父类实例,因此当父类里面引用类型的变量修改时会影响所有的子类。如下所示:

// 原型链继承
function Parent() {
    this.name = ['csdn']
}
Parent.prototype.getName = function() {
    console.log(this.name)
}
function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child();
child1.getName() // ['csdn']
child1.name[0]='juejin'
const child2 = new Child()
child2.getName() // ['juejin']

然后就是构造函数继承,在子类的构造函数中执行父类构造函数,并为其绑定子类的this,把父类的成员属性和方法拷贝到子类上,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参。

但是,问题在于子类继承不了父类原型链上的方法,如下所示:

function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
    console.log(this.name)
}
function Child(name='csdn') {
    Parent.call(this, name)
}
const child1 = new Child()
const child2 = new Child('juejin')
child1.name[0] = 'jianshu'
console.log(child1.name) // ['jianshu']
console.log(child2.name) // ['juejin']
child1.getName() // Uncaught TypeError: child1.getName is not a function

结合以上问题,我们可以使用寄生式组合继承,在子类中执行父类构造函数并修改this指向,然后拷贝一个父类原型对象作为子类的原型对象

function Person({firstName, lastName}) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = `${firstName} ${lastName}`
}
Person.prototype.sayName = function() {
    console.log(`My name is ${this.fullName}`)
}
function Student(props) {
    const { No, grade } = props;
    this.No = No;
    this.grade = grade;
    Person.call(this, props)
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student;
Student.prototype.introduce = function() {
    console.log(`My name is ${this.fullName}, number ${this.No}, Grade ${this.grade}`)
}
var student1 = new Student({ firstName: 'zhang', lastName: 'san', No: 1001, grade: 5 })
student1.introduce()
var student2 = new Student({firstName: 'li', lastName: 'si', No: 1002, grade: 6})
student2.introduce()

Q: 手写new

都要手写了,咱总得知道new一个构造函数时,发生了点啥吧。

敲黑板

  1. 创建一个空对象({});
  2. 为这个对象添加属性__proto__,指向构造函数的原型对象
  3. 修改this指向这个空对象
  4. 执行构造函数里面的语句,返回这个对象

不说了,上代码

function MyNew(myConstructor, ...args) {
    let obj = {};
    obj.__proto__ = myConstructor.prototype;
    myConstructor.call(obj, ...args)
    return obj;
}
function MyConstructor(name) {
    this.name = [name]
    this.sayName = function () {
        console.log(this.name)
    }
}
let obj = MyNew(MyConstructor, 'zhangsan')
obj.sayName() // ['zhangsan']

前人栽树,后人乘凉,多亏了👉CSDN👈这位大佬的分享。将来我也要成为可以输出原创内容的程序猿🐵