简言
最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记消除自己以前学习知识点的误区
什么是面向对象?
要理解什么是面向对象,那么首先要知道什么是面向过程
面向过程
比如以做饭为例
graph TD 买菜 --> 切菜 --> 炒菜 --> 装盘在上述流程图中,我们主要的关注点是在具体步骤的实施,着重点在过程(这一步要怎么什么,下一步要做什么)
面向对象
而面向对象的关注点则不是下一步要做什么,而是谁来做,着重点是做事的对象
面向对象和面向过程的区别
- 面向过程关注步骤,每次执行一个任务都需要将过程预先安排,性能损耗高
- 面向对象则关注个体对象,将实施步骤的能力赋予对象,由对象灵活安排,简化了对于流程的岔路准备
面向对象的优点
- 逻辑迁移更加灵活
- 代码复用性更高
- 高度模块化
而基于上述优点,在实际场景中,对象又可以做以下的事情:
- 对内存储数据和方法
- 对外提供模块和接口
对象
对象又分为简单对象以及函数对象,当然创建也就分为对象字面量创建
以及构造函数创建
简单对象
const teacher = {
name: 'xx',
class: 'yuwen',
teach: function(name) {
return `开始教授${name}课程`
}
}
简单对象的特点
- 本身具有开放性,体现在外部对其具有读写能力
- 不具备复制性,因为简单对象的内容被存放在堆中,并且会在栈中生成一个指向堆内容的地址,因此只能改变对象中的内容,却无法改变对象本身(地址不能变)
函数对象
在js中,构造实例的函数对象 我们也称其为 构造函数
构造函数
=> 又可以理解成 constructor(构造器)
=> 构造 当前这个对象模板本身
的函数
主体是函数,用于构造对象模版
构造函数
在js,尤其是ES6之前,并没有类这个概念,因此我们创建一个对象,往往通过new
一个构造函数来创建
new的过程,new是什么,new的原理(4点)
- 结构上:首先new一个构造函数会创建一个空对象,用于承载返回的对象实例
- 属性上:空对象上的原型对象(proto)会指向构造函数的prototype属性
- 关系上:将当前实例对象赋值给内部的this
- 生命周期上:会执行构造函数的初始化代码
function myNew(constructor, ...args) {
// 边界处理
if(typeof constructor!== "function") {
throw new Error('请传入构造函数')
}
// 1. 结构上:首先new一个构造函数会创建一个空对象,用于承载返回的对象实例
const obj = Object.create();
// 2. 属性上:空对象上的原型对象(__proto__)会指向构造函数的prototype属性
obj.__proto__ = constructor.prototype;
// 3. 关系上:将当前实例对象赋值给内部的this
// 4. 生命周期上:会执行构造函数的初始化代码
const result = constructor.apply(obj, args);
// 返回实例化对象
return result instanceof Object ? result : obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = myNew(Person, 'Alice', 30);
console.log(person1); // Person { name: 'Alice', age: 30 }
构造函数的特性 - prototype
函数对象(构造函数)有一个特性叫做 prototype,他是在函数对象(构造函数)被创建之初所具有的属性和方法的集合,同时可以于后期再prototype上叠加新的属性和方法
当使用new创建一个空对象时,会将空对象的__proto__关联到构造函数的prototype,因此空对象会被赋予新的属性和方法
例如:
// 人物具有以下特征
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function(words) {
return words
}
}
const person1 = new Person('xx', 12)
console.log(person1.say('Hello World'))
但是上述代码有个问题就在于,创建函数比创建变量的性能消耗更高
假如我们使用Person创建了成千上万个不一样的独立person,那么这些person都会有自己独立的say,这会给系统带来巨大的性能损耗
因此我们会采用在prototype上叠加新的方法的方式,让所有的实例化对象都共享同一个say,这样能有效的节省内存,并且非常高效
// 人物具有以下特征
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function(words) {
return words
}
const person1 = new Person('xx', 12)
console.log(person1.say('Hello World'))
prototype 和 proto 有什么区别呢
- prototype 可以看成是父辈的优点
- proto 是每一个对象都具有的与父辈的血缘关系,通过__proto__,实例化的对象才能继承到父辈的优点
由__proto__承载构造函数的prototype,则prototype就被继承下来了,因此就有了原型和原型链的概念
原型和原型链
实例化对象的原型(__proto__的指向)就是用于创建实例化对象的函数对象(构造函数)的prototype(原型)
原型链就是通过每一层对象的__proto__不断承载上一层构造函数的prototype而形成的一条自上而下的链路
继承
有了原型链,那么就可以在js中很方便的去实现继承,也就相当于爷爷辈有什么关系(prototype)能够一代传几代
原型继承(按上述一代传几代)
function Game() {
this.name = 'LOL'
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL() {}
LOL.prototype = new Game()
// 这里可以将constructor认为是出生证明,如果不改变出生证明的话,LOL.prototype.constructor === Game 这显然是不对的,不可能说我是A的子孙,但是我继承了B的优点,我就要去认B做父
LOL.prototype.constructor = LoL
const lol1 = new LOL()
const lol2 = new LOL()
lol1.getName()
但原型继承有个很明显的缺点就是通过lol1.skin.push('ss')同时会令lol2.skin一起改变
虽然new一个构造函数能创建一个独立的实例化对象,但是在这里,由于LOL.prototype 通过new Game()获得了唯一的对象所拥有的特征。
而lol1 lol2虽然是不一样的两个对象,但由于 lol1.proto === lol2.proto === LOL.prototype,因此他们会共用原型(LOL.prototype)上的唯一属性,并且实例化lol1 lol2无法向LOL构造函数传参
构造函数继承
通过构造函数继承,解决子类属性共享的问题
function Game() {
this.name = 'LOL'
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(...args) {
// 因为new 创建对象时,会将实例化对象赋给构造函数内部的this,并且会按照声明周期执行构造函数的初始化代码
Game.call(this, ...arg)
}
const lol1 = new LOL()
const lol2 = new LOL()
lol1.skin.push('ss')
但构造函数也有问题:
子类只继承了构造函数内部的属性,但是构造函数原型链(constructor.prototype)上的属性和方法无法继承
组合继承(原型继承 + 构造函数继承)
function Game() {
this.name = 'LOL'
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(...args) {
Game.call(this, ...arg) // 这里的this就分别是lol1和lol2,互相不干扰
}
LOL.prototype = new Game() // 继承原型链上的方法
LOL.prototype.constructor = LoL
const lol1 = new LOL()
const lol2 = new LOL()
lol1.skin.push('ss')
但组合继承也有个小问题:
因为new会在生命周期中执行构造函数的初始化代码,因此在上述代码中
- Game.call()
- new Game()
都会执行构造函数Game的初始化代码,如果实例化对象过多会产生性能问题
因此new Game()可以替换成Object.create(Game.prototype)也可以实现继承Game原型上的属性和方法
寄生组合继承
function Game() {
this.name = 'LOL'
this.skin = ['s']
}
Game.prototype.getName = function() {
return this.name
}
function LOL(...args) {
Game.call(this, ...arg)
}
LOL.prototype = Object.create(Game.prototype) // 创建一个空对象承载Game原型上的属性和方法
LOL.prototype.constructor = LoL
const lol1 = new LOL()
const lol2 = new LOL()
lol1.skin.push('ss')
标签:对象,LOL,JS,面向对象,Game,原型,new,prototype,构造函数
From: https://www.cnblogs.com/laowangcoding/p/18572173