【JavaScript】面向对象编程/原型及原型链

面向对象

为什么要面向对象?

  • 优势:简化对行为岔路的预备
  • 特点:逻辑迁移更加灵活、代码复用性高,高度模块化的体现

对象是什么?

  • 对单个物体的简单抽象
  • 对象是一个容器,封装了属性和方法,属性代表对象的状态,方法代表对象的行为
  • 模块、接口=>对象

简单对象

  • 对外开放,属性和方法可以被直接修改
const Course = {
     teacher :'张三',
     leader:'cc',
     startCourse : name =>{
         return `开始${name}课`
     }
 }
 // A
Course.teacher = '李四';
Course.startCourse('react')
// B
Course.startCourse('vue');
// 以上会修改对象里面的数据,为了防止修改,所以使用函数对象

函数对象

构造函数的实例化

  1. 需要一个模板,表征了一个类物品的共同特征,从而生成对象。
  2. 构造函数即对象模板,构造函数可以理解为类。
  3. JS本质上并不是直接基于类,基于构造函数+原型链=>constructor+prototype。
  4. 需要使用new来实例化对象。
  5. 构造函数体体内使用的this指向所要生成的实例。
  6. 可以初始化传参。
function Course(name){
     this.name = name;
     this.teacher = '张三';
     this.leader = 'xh';
     this.startCourse = function () {
    	return `开始${ this.name}课`
     }
 }
const course = new Course('vue');
console.log(course)

提问1:如果构造函数不实例化,可以作为对象生成器使用吗?

答案:不可以

function Course(name) {
  this.name = name
  this.teacher = '小明'
  this.teachestartCourse = function () {
    return `开始${this.name}课`
  }
}
console.log(Course.teacher) // undefined

提问2:如果项目需要使用,又不想被外界感知,通常如何解决?

答案:单例模式=>全局维护统一实例=>解决对象实例化生成问题

function Course() {
  const isClass = this instanceof Course
  if (!isClass) {
    return new Course()
  }
  this.name = '语文'
  this.teacher = '小明'
  this.teachestartCourse = function () {
    return `开始${this.name}课`
  }
}
console.log(Course().name) // 语文

提问3:new是什么?new的原理?new的时候做了什么?

  • 结构上:创建一个空的对象,作为返回的对象实例。
  • 属性上:将生成空对象的原型对象指向了构造函数的prototype属性。
  • 关系上:将当前实例对象赋给了内部的this。
  • 生命周期上:执行了构造函数的初始化代码。

手写代码模拟 new

function usernew(obj,...args){
    const newObj = Object.create(obj.prototype) 
    const result = obj.apply(newObj,args)
    return typeof result === 'object'? result:newObj
}

 function person(name, age) {
  this.name = name;
  this.age = age

}
const p = usernew(person '小明',10)
console.log(p)

提问4:constructor是什么?

构造器

  • 每个对象实例化时,都会自动拥有一个constructor属性。
  • constructor属性继承原型对象,并指向当前的构造函数。
  • constructor代表了实例化对象是被谁生成的。

提问5:使用构造函数继承属性就没有问题吗?会有什么性能问题?

  • 构造函数中的方法实例化后会存在于每个生成的实力内
  • 而方法往往需要统一模块化,重复挂载只会浪费资源
  • 如何进行优化:把方法挂在构造函数的prototype属性上,让实例对象自动通过自身的__proto__属性去引用方法

提问6:前面提到的原型对象又是什么? – prototype

 function Course() {}
 const course1 = new Course();
 const course2 = new Course();
 // 1. Course - 用来初始化创建对象的函数 | 类
 course1.__proto__ === Course.prorotype
 // 2. course1 - 根据原型创建出来的实例
    course1.constructor === Course

prototype
只有构造函数有prototype属性。

function Course() {}
const course1 = new Course()
  • 构造函数:用来初始化创建对象的函数|类 – Course
  • 自动给构造函数赋予了一个属性 prototype(原型对象),该属性等于实例对象的 __proto__ course1.__proto__ === Course.prototype // true
  • 每个对象实例化时,都会自动拥有一个 constructor 属性,(course1是根据原型创建出来的实例)course1.constructor => Course
  • constructor属性继承自原型对象,并指向当前构造函数
    course1.constructor => course1.__proto__.constructor =>
    Course.prototype.constructor => Course

提问7:原型对象有自己的原型吗?有

function Course() {}
const course1 = new Course()

course1.__proto__.__proto__  ===  Object.prototype
Course.prototype.__proto__   ===  Object.prototype
course1.__proto__.__proto__.__proto__  === null

原型链

【JavaScript】面向对象编程/原型及原型链

原型&原型链

原型

  • prototype:Js通过构造函数来创建一个对象,每个构造函数内部都会有一个原型prototypr属性,它指向另外一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。
  • proto:当使用构造函数创建一个实例对象后,可以通过__proto__访问到prototype属性。
  • constructor:实例对象通过这个属性可以访问到构造函数

原型链

  • 每个实例对象都有一个__proto__属性指向它的构造函数的原型对象,而这个原型对象也会有自己的原型对象,一层一层向上,直到顶级原型对象null,这样就形成了一个原型链。
  • 当访问对象的一个属性或方法时,当对象身上不存在该属性方法时,就会沿着原型链向上查找,直到查找到该属性方法位置。
  • 原型链的顶层原型是Object.prototype,如果这里没有就只指向null

继承

重写原型对象继承

  • 在原型对象上的属性和方法,都能被实例共享
  • 本质:重写原型对象,将父类实例对象的属性和方法,作为子对象原型的属性和方法
  • 缺点:
  1. 父类属性一旦赋值给子类的原型属性,此时属性属于子类的共享属性了。( 子类的 prototype 来自父类的一个实例对象,其中一个子类实例对象改了继承而来属性或方法,其他子类实例对象继承而来的属性或方法也就被修改了 )
  2. 实例化子类时,无法向父类传参。
function Game() {
    this.name = 'lol';
    this.skin = ['s'];
}
Game.prototype.getName = function() {
    return this.name;
}

// LOL
function LOL() {};
LOL.prototype = new Game();
LOL.prototype.constructor = LOL; //重写构造函数,指回父
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');
//本质:重写原型对象,将父类实例对象的属性和方法,作为子对象原型的属性和方法,同时重写构造函数

构造函数继承/拷贝继承

  • 在子类的构造函数内部调用父类的构造函数
  • 缺点:无法通过原型链继承原型对象上共享的方法
function Game(arg) {
     this.name = 'lol';
     this.skin = ['s'];
}
Game.prototype.getName = function() {
    return this.name;
}
function LOL(arg) {
    Game.call(this, arg);
}
const game3 = new LOL('arg');
// 解决了共享属性问题 + 子向父传参的问题

组合继承

  • 组合继承:构造函数继承 + 重写原型对象继承
  • 缺点:父类被调用了两次
function Game(arg) {
    this.name = 'lol';
    this.skin = ['s'];
}
Game.prototype.getName = function() {
    return this.name;
}
function LOL(arg) {
    Game.call(this, arg);
}
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game4 = new LOL('arg');

寄生组合继承

Course.prototype为原型,通过Object.create()创建一个方新的对象赋值给LOL.prototype

function Game(arg) {
    this.name = 'lol';
    this.skin = ['s'];
}
Game.prototype.getName = function() {
    return this.name;
}

function LOL(arg) {
    Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;
const game5 = new LOL('arg');

多重继承

通过Object.assign()合并多个父类的原型对象给当前子类的原型对象

function Game(arg) {
    this.name = 'lol';
    this.skin = ['s'];
}
Game.prototype.getName = function() {
    return this.name;
}
function Store() {
    this.shop = 'steam';
}
Game.prototype.getPlatform = function() {
    return this.shop;
}
function LOL(arg) {
    Game.call(this, arg);
    Store.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(
    Store.prototype,
    LOL.prototype
);
LOL.prototype.constructor = LOL;

原文链接:https://juejin.cn/post/7343921947870937139 作者:nico

(0)
上一篇 2024年3月9日 下午4:05
下一篇 2024年3月9日 下午4:15

相关推荐

发表评论

登录后才能评论