面向对象
一种编程行业通用的写项目级
的代码的思维,引导我们如何编写高质量的代码,万物皆对象 - 看待事物的角度,(属性:数据,行为:动作(方法))。代码特点是封装和继承
对象在内存中的示意图
创建对象的几种方式
字面量-创建对象
- 简单粗暴
- 不适合创建多个同样类型的对象的场景
// 字面量-创建对象
const obj1 = { username: '悟空1', tel: 123321 };
const name1 = 123;
const obj2 = { username: '悟空2', tel: 123322 };
const name2 = 123;
const obj3 = { username: '悟空3', tel: 123323 };
const name3 = 123;
const obj4 = { username: '悟空4', tel: 123324 };
const name4 = 123;
const obj5 = { name: '悟空5', tel: 123325 };
// 好处 方便
// 不方便维护-修改
// 不想要name属性,修改 username
// 要一个一个修改麻烦
工厂函数
- 容易理解
- 失去
血缘关系
,无法简单分辨对象的特征 - 后期无法实现继承
// 分析代码 功能 或者任何事情 面向对象的思维
// 属性
// 行为
function createPerson(name, age, height){
return {
username:name,
age:age,
height:height
}
}
// 创建对象
const obj6 = createPerson('悟空', 18, 150);
const obj7 = createPerson('八戒', 20, 140);
const obj8 = createPerson('龙马', 30, 190);
console.log(obj6);
console.log(obj7);
console.log(obj8);
// 优点 容易维护
// name => username
// 弊端 无法实现 继承的作用!
构造函数
- 可以方便的创建对象
- 拥有血缘关系
- 还有后续更多的优势
// 专业术语
// 会把 createPerson 叫做 构造函数
// 会把 createPerson 创建出来的对象 叫做 实例
// 1 声明一个函数
function createPerson(name, age) {
// 2 给this赋值
this.name = name;
this.age = age;
// this.color="red";
}
// 2 通过new的方式来创建对象
const obj1 = new createPerson('悟空', 10);
console.log(obj1);
console.log(obj1.name);
console.log(obj1.age);
构造函数的工作原理:
- 开辟空间
- 将新的创建的对象对象构造函数中的this
- 为对象赋值
- 将创建好的对象的地址返回
构造函数的弊端
同一个 say 方法占据了两份内存
性能问题
function say() {
console.log('这个是say方法');
}
// 构造函数的首字母大写 - 行内编码规范
function CreatePerson(name) {
this.name = name; // 创建了name属性
// this.say = function () {
// console.log('这个是say方法');
// };
this.say = say; // say函数引用类型, 构造函数中的say 其实和外面的say内存地址一致的 同一个say方法
}
const obj1 = new CreatePerson('悟空');
const obj2 = new CreatePerson('八戒');
// obj1.say();
// obj2.say();
// 两个say的判断比较 是false 说明 两个say是在不同的内存空间上
// 两个say 占用了两个内存空间
// console.log(obj1.say === obj2.say); // false
console.log(obj1.say === obj2.say); // true
// 对于基本类型来说,= 就是复制了一份值
// let num=100;
// let num2=num;// 复制值 num和 num2占两给内存 各自不影响
// num2=1000;
// console.log(num);
// 对于引用类似 = 其实把地址拷贝了一份给新的对象 两个数据 公用一份数据
// let person1 = { name: '悟空' }; // person1 指向了 一个地址 0x1111 存放悟空
// let person2 = person1; // person2也指向了person1的地址 0x1111 person1和person2 通用一个数据
// 修改了person2 person1也会发生改变
// person2.name = '八戒';
// console.log(person1);
// person2 和person1 共同指向了同一个地址
// console.log(person1 === person2); // true
// let o={};// 开辟了一个内存
// let o2={};// 开辟了另外一个内存
// console.log(o===o2);// false 两个不同的内存地址
/*
构造函数想要解决 性能问题
1 一定会把方法-函数写在构造函数的外面
2 再通过this.say 指向外部的函数
*/
提取同一个 say
方法
- 解决了浪费内存的弊端
- 但是造成了
污染全局变量
的问题
性能问题2
// 构造函数的 方法 都会通过类似的这种方式 来实现 多个实例的方法共享
function say() {
console.log('你好');
}
function CreatePerson(name) {
this.nickname = name;
this.say = say;
}
// function say() {
// console.log("学生你好");
// }
// function createStudent(params) {
// }
const obj1 = new CreatePerson('悟空');
const obj2 = new CreatePerson('八戒');
console.log(obj1);
console.log(obj2);
console.log(obj1.say === obj2.say); // true 这个代码是合理的 优化过
console.log(obj1.say === obj2.say); // false 不够好 性能不够好 两个say占据了两个内存
// 函数函数 方法提取出去 这套代码
// 优点: 方便代码维护、也解决了性能 obj1.say === obj2.say
// 缺点: 代码不够优雅 污染了全局变量 以后不能写 say方法,很容易就覆盖()
原型对象
在构造函数的原型上 存放函数
- 解决了同一个
say
浪费 内存的问题 - 解决了污染全局变量的问题
// 原型对象 是任何构造函数对存在的一个对象 prototype
// 作用: 构造函数看是人, 原型对象 就是人的DNA
// 如果我们修改了DNA,那么通过构造函数创建实例都会一起发生修改
// 如果我们在DNA上新增了一些东西,对应实例一样会被新增
function CreatePerson(name) {
this.name = name;
}
// 原型对象
// console.log(CreatePerson.prototype);
// 在DNA上新增 东西
CreatePerson.prototype.say = function () {
console.log('你好');
};
const obj1 = new CreatePerson('悟空');
const obj2 = new CreatePerson('八戒');
// obj1.say();
// obj2.say();
function CreateStudent() {}
CreateStudent.prototype.say = function () {
console.log('学生你好');
};
// console.log(obj1.say === obj2.say);// 没有性能问题
// 有污染全局变量的问题吗 没有
// CreatePerson.prototype.say
// CreateStudent.prototype.say
// 小结 原型对象-构造函数 一般情况
// 构造函数内只放属性 name 、age、color、height
// 对应原型上 都是放 方法 = 函数
解释
- 原型的单词是
prototype
, 原型的这个名字是行业内共同认可的名字。 - 原型本质是一个对象,理解为
JavaScript
自动帮我们添加的 - 原型是
JavaScript
自动帮我们在定义构造函数
的时候添加的 - 所有构造函数的实例,共享一个原型
- 原型上一般是挂载函数
面向对象的案例
<button>修改颜色</button>
<script>
/*
需求:
1 通过行代码 const divModel = new Div("这个是普通的地址"); 页面中出现对应的一个div标签
2 给按钮绑定点击事件 触发了
divModel.changeColor("red"); 这个div的背景颜色变成红色
divModel.changeColor("yellow"); 这个div的背景颜色变成黄色
分析:
1 Div 是一个构造函数
2 Div 构造函数 在原型上有一个方法 changeColor
3 const divModel = new Div("这个是普通的地址");
这行代码实际的作用 页面中出现一个div
1 const div=document.createElement("div");
2 div.innerText="文本内容"
3 document.body.appendChild(div)
4 divModel.changeColor("yellow"); 这个div的背景颜色变成黄色
???.style.backgroundColor="yellow"
*/
function Div(text) {
const div = document.createElement('div');
div.innerText = text;
document.body.appendChild(div);
this.dom = div;
}
Div.prototype.changeColor = function (color) {
// this.dom = div;
this.dom.style.backgroundColor = color;
this.dom.style.fontSize = size;
};
const button=document.querySelector("button");
const divModel1 = new Div('这个是普通的div1');
button.addEventListener("click",function () {
divModel1.changeColor("red", "100px");
// 新功能
// 希望div中的文字 大小变为100px
})
</script>
<SCript>
/*
需求: 通过 new MyImg("图片地址") 页面上就会多一个图片标签
1 页面上多一个图片的本质代码
1 const img = document.createElement("img");
2 img.src="图片地址"
3 document.body.appendChild(img);
2 new的方式来创建图片
new MyImg 做了什么事情 => 调用了一个构造函数 MyImg
*/
// const img1 = new MyImg('./images/1.png'); // 页面上多一个图片标签
function MyImg(src) {
const img = document.createElement('img');
img.src = src;
document.body.appendChild(img);
}
const img1 = new MyImg('./images/gotop.png');
const img2 = new MyImg('./images/b_01.jpg');
const img3 = new MyImg('./images/b_02.jpg');
</SCript>
面向对象案例
需求
1 const imgModel =new MyImg("图片地址") (不要直接插入到body标签上)
2 imgModel.append("父元素的选择器"); 希望可以在父元素里面插入一个img
3 根据给到的需求
1 先拆分出 代码的结构 要定义构造函数吗 原型上要写什么方法
2 根据给到的代码具体需求 结合 底层js+webAPI的知识 来实现 封装的功能
3 在以前更多的是学习如何直接写代码问题 ,现在也是在写代码 - 直接解决问题,通过封装代码(提高的能力)->解决问题
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
function MyImg(src) {
const img = document.createElement('img')
img.src = src
this.dom = img
}
MyImg.prototype.append = function (parentSelector) {
// 通过 this.dom 获取到 构造函数中创建好的图片img对象
// 希望可以在父元素里面插入一个img 底层的webAPI代码是什么
// document.body 父元素
// 父元素.appendChild(子元素); // img 创建的元素
document.querySelector(parentSelector).appendChild(this.dom);
}
// 先创建一个图片标签
const imgModel = new MyImg('../项目-AlloyTeam/images/1.jpg')
// 指定位置来插入元素
imgModel.append('li:nth-child(3)') // 图片插入到指定的父元素中
</script>
原型-继承-call
call( ) 借调 可以让一个对象 来借用另外一个对象的方法
const car = {
name: '装甲车',
add: function (username, age) {
// 本质 可以给car添加新的属性
this.username = username;
this.age = age;
},
};
// car.add('发动机', 100);
// console.log(car);
const obj = {};
// car.add.call(obj); // obj 想要借用 添加属性的方法
car.add.call(obj, '发动机', 200); // call 传入参数 add.call(谁要借用,被借用的方法的形参1,被借用的方法形参2)
console.log(obj); // obj 有没有 username属性(不用管属性值)、有没有age属性(不用属性值)
// 可以实现 了 一个空对象 obj 通过call的使用 来实现了 借别人的方法add 来给obj添加新的属性
借调演示
function person (name, age, height){
this.username = name;
this.age = age
this.height = height
}
function Student(name, age, height, color){
// 借调call
person.call(this,name,age,height)
this.color = color
}
const s1 = new Student('迪迦', 2000, 100, 'yellow')
console.log(s1);
方法的继承
// 对象 两个大的特点
// 属性 - 属性的继承
// 行为-方法
// 让儿子也具有父亲的行为 => 把父亲的DNA 给到儿子即可
// 给儿子添加行为 儿子的原型上添加
// 父亲的行为 存放在父亲原型上
function Person(name, age) {
this.username = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.username, this.age); // 我是谁 我的年龄是
};
Person.prototype.show = function () {
console.log(this.username + ' 秀肌肉');
};
// const p1=new Person("父亲",50);
// p1.say();
function Student(name, age, color) {
// name age person里面已经有定义
// 借用一下
Person.call(this, name, age);
// color person里面没有的 需要自己来定义
this.color = color;
}
const s1 = new Student('三好学生', 10, 'yellow');
// 把父亲的行为 也设置到儿子上
Student.prototype.say = Person.prototype.say;
Student.prototype.show = Person.prototype.show;
s1.say();
s1.show();
/*
总结
想要实现继承的功能 分两个部分
1 属性的继承
Person.call(this,其他参数)
2 方法的继承
儿子的原型.say = 父亲的原型.say
*/
继承案例的重复演示
<script>
/*
封装 代码 实现以下的功能
1 父亲 Element
1 属性 dom this.dom
2 行为 实例.append(父元素标签的选择器)
2 儿子1 ElementDouble div
1 继承父亲 Element
属性 dom
行为 append
3 儿子2 ElementSingle img
属性 dom
行为 append
4 当执行以下代码时 出现对应效果
1 const divModel = new ElementDouble("div","div内的文字")
2 divModel.append("body") ; body标签可以出现 一个 div
1 const imgModel = new ElementSingle("img","图片地址")
2 imgModel.append("body"); body标签可以出现 一个图片
*/
// 父亲
function Element() {
this.dom = null;
}
// 父亲
Element.prototype.append = function () {};
// 儿子1
function ElementDouble(tagName, content) {
const dom = document.createElement(tagName);
dom.innerText = content;
this.dom = dom;
}
ElementDouble.prototype.append = function (parentSelector) {
// 真正的dom父元素.appendChild(子元素)
document.querySelector(parentSelector).appendChild(this.dom);
};
// 儿子2
function ElementSingle(tagName, src) {
const dom = document.createElement(tagName); // 图片
dom.src = src;
this.dom = dom;
}
ElementSingle.prototype.append = function (parentSelector) {
document.querySelector(parentSelector).appendChild(this.dom);
};
const divModel = new ElementDouble('div', 'div内的文字');
divModel.append('body');
const imgModel = new ElementSingle('img', './images/b_01.jpg');
imgModel.append('body');
// 如果代码重复实现了 很有可能就是我们要封装的时候
// 以前的封装 仅仅是封装一个小小的函数而已
// 现在的封装, 面相对象的思维来封装
// 封装 属性 父亲
// 封装 方法 父亲
</script>