天天看点

JS面向对象编程,原型与继承全面解析

面向对象编程的特点

  1. 封装:使用对象的人无需考虑内部实现,只考虑功能的使用。
  2. 继承:为了代码的可复用。
  3. 多态:不同对象 作用于同一操作产生不同结果。

JS如何创建对象

普通方式

const A = new Object()
A.attribute = '' // 定义属性
A.fn = function() { } // 定义方法      

工厂模式

function Creat(attr) {
    // .... 同上面普通方式 
    return A
}
const aa = Creat('...')
const bb = Creat('...')      

存在问题:往实例化对象上层找不到父类,只能知道是一个Object

构造函数/实例

function Player(name) {
    this.name = name
    this.run = function() {
        console.log('...');
    }
}

const x = new Player('a')
const y = new Player('b')
console.log(x.run === y.run) // false 独立内存      

缺点:通过this添加的属性和方法,总是指向当前对象(改变当前对象不会影响构造函数),实例化时通过this添加的属性和方法都会在内存当中复制一份。

原型对象

function Player(name) {
    this.name = name
}
Player.prototype.run = function() {
    console.log('...');
}

const x = new Player('a')
const y = new Player('b')
console.log(x.run === y.run) // true      

静态属性

function Player(name) {
    Player.count = 1
}

const p = new Player('a')
console.log(p.count) // undefined
console.log(Player.count) // 1      

原型及原型链

查找原型对象的方法

xxx.__proto__
Object.getPrototypeOf(xxx)      

new关键字做了什么

  1. 创建了新对象并将.__proto__指向构造函数的.prototype
  2. 将this指向新创建的对象
  3. 返回新对象(1. 有显式的返回值且是对象则返回这个对象 2. 其他情况返回this)
function newSimulator() {
    // 1. 创建新对象
    const obj = new Object()
    // 2. 设置__proto__为构造函数prototype
    const constructor = [].shift.call(arguments) // 取出参数第一项,并删除掉,剩余参数在下一步会用到
    obj.__proto__ = constructor.prototype
    // 3. this指向新对象,也就是改变this的指向:例如apply,call
    const ret = constructor.apply(obj, arguments)
    // 4. 返回新对象或this
    return typeof ret === 'object'      
function Player(name, type) {
    this.name = name
    this.say = () => { console.log(`i am ${name},i create by ${type}`) }
}

const a = new Player('a', 'new')
a.say() // i am a,i create by new
const b = newSimulator(Player, 'b', 'newSimulator')
b.say() // i am b,i create by newSimulator      

理解原型链

当读取实例对象的属性时,如果找不到,会查找对象原型中的属性,直到最上层为止。

Object.prototype.name = 'Object';
function Player() {}
Player.prototype.name = 'Player';

const p1 = new Player();
p1.name = 'p1';
console.log(p1.name); // p1

delete p1.name;
console.log(p1.name); // Player

delete Player.prototype.name;
console.log(p1.name); // Object      

JS实现继承

原型链继承

function Parent() {
    this.name = 'ParentName';
    this.actions = ['sing', 'jump', 'rap'];
}

function Child() {}

Child.prototype = new Parent(); // 通过实例化对象来拿到全部属性方法
Child.prototype.constructor = Child; // 但是直接赋值会覆盖掉Child,这一步是其将修改回来      

存在问题:引用类型被改变,所有实例共享,无法传参

const c1 = new Child();
c1.actions.push('basketball');
console.log(c1.actions); //[ 'sing', 'jump', 'rap', 'basketball' ]
const c2 = new Child();
console.log(c2.actions); // [ 'sing', 'jump', 'rap', 'basketball' ]      

构造函数继承

function Parent(name) {
    this.name = name;
    this.actions = ['sing', 'jump', 'rap'];
    this.say = function () {}
}

function Child() {
    Parent.apply(this, arguments); // 把Parent执行了一遍,解决了传参问题      
const c1 = new Child('c1');
const c2 = new Child('c2');
c1.actions.pop() // 引用类型的问题被解决
console.log(c1.name, c1.actions) // c1 [ 'sing', 'jump' ]
console.log(c2.name, c2.actions) // c2 [ 'sing', 'jump', 'rap' ]

console.log(c1.say === c2.say); // false 独立内存,构造函数的问题,消耗大      

组合继承

该继承同时解决以上两种继承存在的问题,副作用是会重复执行构造函数

// 即原型链继承 + 构造函数继承

function Parent(name, actions) {
    this.name = name;
    this.actions = actions;
}

Parent.prototype.say = function () {
    console.log(this.name + ' say');
}

function Child() {
    Parent.apply(this, arguments); // 第一次调用构造函数
}

Child.prototype = new Parent(); // 第二次调用构造函数
Child.prototype.constructor = Child;
// 组合继承中到这一步,是使用到开头原型链继承的方式,可以看做它将Child中的原型链方法变成了引用类型      
const c1 = new Child('c1', ['eat']);
const c2 = new Child('c2', ['run']);
c1.actions.pop() // 引用类型的问题被解决
console.log(c1.name, c1.actions) // c1 []
console.log(c2.name, c2.actions) // c2 [ 'run' ]

console.log(c1.say === c2.say); // true 内存消耗问题被解决      

寄生组合式继承

// 还是刚才的组合继承,改变的地方会被注释
function Parent(name, actions) {
    this.name = name;
    this.actions = actions;
}

Parent.prototype.say = function () {
    console.log(this.say + ' say');
}

function Child() {
    Parent.apply(this, arguments);
}

// Child.prototype = new Parent();
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;      
let TempFunction = function () {}; // 一个空的临时变量
TempFunction.prototype = Parent.prototype; // 将临时函数的原型指向Parent的原型
Child.prototype = new TempFunction(); // 这样就同样实现了原型链继承的优点,并且开销极低      

es6继承

class Parent {
    constructor() {
        this.name = 'aaa';
    }
    say() {
        console.log(this.name, 'say');
    }
}

class Child extends Parent {
    constructor() {
        super();
    }
}      

继续阅读