目录
面对对象编程
面对对象和面向过程
🏆 //面向对象与面向过程,是解决问题的两种思路
//面向过程:先分析问题中的步骤,马上将分出来的步骤封装成函数,再按顺序依次调用即可解决问题
//面向对象:先分析问题中的步骤,再将步骤划分不同的不同主体上,最的将顺序调用主体上方法
//早晨起床上学
//面向过程
/*
//起床
function getUp(){}
//洗澡
function wash(){}
//吃饭
function eat(){}
//去上学
function toscholl(){}
getUp()
wash()
eat()
*/
//面向对象
let stu = {
getUp: function () { },
//洗涑
wash: function () { },
//吃饭
eat: function () { },
//去上学
toscholl: function () { }
}
stu.getUp()
stu.wash()
stu.eat()
优缺点
面向过程
优点:执行效率更高
缺点:当一个项目很大的时候,维护很困难,功能扩展几乎是没有
//面向对象
// 优点:对于应该后期维护,与功能扩展相对于面向过程更容易
// 缺点:执行效率没有面向过程高
面向对象的三大特性
//面向对象的三大特性:
//1.封装:严格的书写类,将方法与方法中的属性,封装到对象上(数据与操作数据的方法必须依附于一个主体)
//2.多态:多种形态,根据参数的个数不同,参数的类型不同,可以调用不同的方法
//3.继承:一个类继承另一个类
//提示:
//Js并不是纯正的面向对象语言,面向对象的三大特性,封装与继承体现的会多一点
构造函数
体现封装性
🏆 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
// 同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
// 总结:
// 1. 构造函数体现了面向对象的封装特性
// 2. 构造函数实例创建的对象彼此独立、互不影响
function Person(uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log('唱歌')
}
}
// new
const ldh = new Person('刘德华', 58)
// console.log(ldh)
const zxy = new Person('张学友', 59)
// console.log(zxy)
// ldh.sing()
// zxy.sing()
//构造函数里面的方法会存在浪费内存的问题
// 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
💎构造函数方法很好用,但是 存在浪费内存的问题
原型
原型
// 🏆 利用原型对象实现方法共享
// 构造函数通过原型分配的函数是所有对象所 共享的。
// JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
// 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
// 💎我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
// 💎构造函数和原型对象中的this 都指向 实例化的对象
💎js规定:如果要给构造函数添加方法不要再构造函数内部添加最好的方法就 是给构造函数的原型对象添加方法
function Star(uname, age) {
this.uname = uname
this.age = age
}
console.log(Star.prototype)//返回一个对象
//🏆每个构造函数 都有一个属性「prototype」属性的值是一个对象原型对象
//💎js规定:如果要给构造函数添加方法不要再构造函数内部添加最好的方法就 是给构造函数的原型对象添加方法
Star.prototype.sing = function () {
console.log('我会唱歌')
}
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 19)
console.log(ldh.sing === zxy.sing)//true
ldh.sing()
zxy.sing()
constructor 属性
//🏆 每个原型对象里面都有个constructor 属性(constructor 构造函数)
// 作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
//🏆 constructor属性的作用
// 使用场景:
// 如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
// 但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
// 此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
// function Star(name, age) {
// this.name = name
// this.age = age
// }
// Star.prototype = {
// sing: function () { console.log('唱歌') },
// dance: function () { console.log('跳舞') }
// }
// const ldh=new Star('刘德华',78)
// console.log(ldh);
// //构造函数的原型对象被重新赋值,constructor此时不是Star,而是新的Object
// console.log(ldh.__proto__.constructor===Star);//false
function Star(name) {
this.name = name
}
Star.prototype = {
// 我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
constructor: Star,
sing: function () { console.log('唱歌') },
dance: function () { console.log('跳舞') }
}
const ldh = new Star('刘德华', 78)
console.log(ldh);
console.log(ldh.__proto__.constructor === Star);//true
console.log(Star.prototype.constructor)//指向 Star
对象原型
//🏆对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,
// 之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,
// __proto__
//就是因为对象有 __proto__ 原型的存在。
// 💎注意:
// __proto__ 是JS非标准属性
// [[prototype]]和__proto__意义相同
// 用来表明当前实例对象指向哪个原型对象prototype
// __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
原型继承
属性继承借用父构造函数实现继承属性
//🏆子构造函数继承父构造的属性
//🏆子构造函数继承父构造的属性
function Product(name, price) {
this.name = name;
this.price = price;
console.log(this);//this是f1,因为call改变了this指向
}
function Food(name, price) {
//借用父构造函数将Product函数当成普通函数在调用
//
Product.call(this, name, price);
this.category = 'food';
}
// 实例化food构造函数
const f1 = new Food('rice', 6)
// expected output: "cheese"
console.log(f1)
函数的call()方法
//🏆 cal1()可以改变函数里面的this指向 函数名.cal1(对象)
// 函数名.call(对象,函数里面的参数,函数里面的参数)
//通过 var声明的全局变量相当于给 window对象添加属性
//函数相当于给window对象添加的方法
function f(x, y, z) {
console.log(this, x, y,z);
}
const obj = {
unname: '张三'
}
f.call(obj, 10, 20, 30)//this变成了obj
方法继承原型继承继承方法
//🏆子构造函数继承父构造的方法
function Product(name, price) {
this.name = name;
this.price = price;
// console.log(this);//this是f1,因为call改变了this指向
}
// 给Product 构造函数的原型对象添加方法
Product.prototype.sayHello = function () {
console.log('hello!!!');
}
function Food(name, price) {
//借用父构造函数将Product函数当成普通函数在调用
//
Product.call(this, name, price);
this.category = 'food';
}
// Food.prototype = Product.prototype
// 将实例化的父构造函数 的实例化对象 赋值给 子构造函数的原型对象
//可以避免改变子构造函数的原型对象,父元素的原型对象也会改变
Food.prototype = new Product()
Food.prototype.sss = function () {
console.log('我是food里的方法');
}
// 实例化food构造函数
const f1 = new Food('rice', 6)
// expected output: "cheese"
console.log(f1)
f1.sss()
注意:
1.如果使用Food.prototype = Product.prototype ,会出现改变子构造函数的原型对象,父元素的原型对象也会改变
2.如果使用 Food.prototype=new Product()
不会修改父元素的原型对象的方法,原因是,new出来的Product()实例化对象 作为子元素的原型对象,新增的方法在旧方法的原型外部
组合式继承属性和方法组合继承
//🏆组合式继承:借用构造函数继承(继承属性)+原型链继承(继承方法)
继承属性:子构造函数中使用函数call方法,调用父构造函数,传入(this,参数)
继承方法:
function Product(name, price) {
// this.name = name;
// this.price = price;
// console.log(this);//this是f1,因为call改变了this指向
}
// 父元素的原型对象的方法
function Food(name, price) {
//借用父构造函数将Product函数当成普通函数在调用
// Product.call(this, name, price);
// this.category = 'food';
}
// 父元素原型对象增加方法
Product.prototype.sayHello = function () {
console.log('hello');
}
Food.prototype=new Product()//之所以不会修改父元素的原型对象的方法,原因是,new出来的Product()实例化对象既包含属性和方法,只是属性没有传参,
// Food.prototype = Product.prototype
// Food.prototype = new Product().__proto__
// 对子元素原型对象增加方法
Food.prototype.dance = function () {
console.log('跳舞');
}
const s1 = new Food('周杰伦', 90)
console.log(s1);
s1.sayHello()
s1.dance()
const f1 = new Product('周杰伦', 90)
console.log(f1);
f1.sayHello()
f1.dance()
原型链
// 🏆 基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对
// 象的链状结构关系称为原型链
// 🏆原型链-查找规则
// ① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
// ② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象) ③ 如果还没有就查找原型对象的原型(Object的原型对象) ④ 依此类推一直找到 Object 为止(null) ⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
// 💎可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
// 判断实例对象和构造函数之间的关系
// 通过的方式是,判断构造函数.prototype是否在实例对象的原型链上
function Person() { }
let p1 = new Person();
function Mobile() { }
let m1 = new Mobile();
console.log(p1 instanceof Person);//true
console.log(m1 instanceof Mobile);//true
console.log(p1 instanceof Mobile);//false