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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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' ? ret : obj
}
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;

解析其中 Object.create 等于以下写法(es5):

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

这也就是“寄生”的含义:用一个纯粹的中间函数去执行了new。

es6继承

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

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

JS:面向未来编程😋

相关文章
|
9天前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
17天前
|
JavaScript 前端开发
Javascript如何实现继承?
【10月更文挑战第24天】JavaScript 中实现继承的方式有很多种,每种方式都有其优缺点和适用场景。在实际开发中,我们需要根据具体的需求和情况选择合适的继承方式,以实现代码的复用和扩展。
|
11天前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
11天前
|
JavaScript 前端开发 开发者
js实现继承怎么实现
【10月更文挑战第26天】每种方式都有其优缺点和适用场景,开发者可以根据具体的需求和项目情况选择合适的继承方式来实现代码的复用和扩展。
27 1
|
2月前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
1月前
|
JavaScript 前端开发 Java
JavaScript中的面向对象编程(OOP) - 终极指南
本文介绍了 JavaScript 的面向对象编程 (OOP) 概念,包括继承、多态、封装和抽象等关键要素,并通过代码示例帮助开发者理解和应用 OOP 思维。
37 5
|
2月前
|
JavaScript 前端开发
js之class继承|27
js之class继承|27
|
2月前
|
JavaScript 前端开发 Java
js面向对象编程|24
js面向对象编程|24
|
2月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
2月前
|
JavaScript 前端开发 开发者
JavaScript 类继承
JavaScript 类继承
19 1

推荐镜像

更多