跳到主要内容

13-JavaScript面向对象

面向对象思想

  • 面向对象(Object Oriented,OO)是软件开发方法
  • 面向对象是一种对现实世界抽象的理解,是计算机编程技术发展到一定阶段后的产物
  • Object Oriented Programming-OOP ——面向对象编程

面向对象和面向过程区别

面向过程面向过程步骤面向对象
强调的是功能行为首先搞清楚我们要做什么将功能封装进对象,强调具备了功能的对象
关注的是解决问题需要哪些步骤然后分析怎么做关注的是解决问题需要哪些对象
一步一步去实现,上面每一个具体步骤中我们都是参与者,而具体的每一步都需要我们去实现和操作最后我用代码体现将每一个步骤封装在一起,用的时候找到对应的类即可

示例分析:

面向过程面向对象面向过程面向对象
了解电脑找班长买菜去饭店
了解自己的需求描述需求洗菜点菜
对比参数班长帮忙把电脑买回来切菜
去电脑城炒菜
砍价,付钱盛菜
买回电脑
被坑

⚠总结:买电脑/吃饭/洗衣服,找个对象解决

面向对象的特点

  1. 符合人们思考习惯的思想
  2. 可以将复杂的事情简单化
  3. 将程序员从执行者转换成了指挥者
  4. 完成需求时:
    • 先要去找具有所需的功能的对象来用
    • 如果该对象不存在,那么创建一个具有所需功能的对象
    • 这样简化开发并提高复用

类与对象的关系

面向对象的核心就是对象,现实生活中可以根据模板创建对象,编程语言也一样,也必须先有一个模板。

  • JavaScript中的类相当于图纸,用来描述一类事物。
  • JavaScript中可以自定义类, 而这个默认的类叫做Object

JavaScript默认对象

  1. JavaScript中提供了一个默认的类Object, 我们可以通过这个类来创建对象
  2. 由于我们是使用系统默认的类创建的对象, 所以系统不知道我们想要什么属性行为, 所以我们必须手动的添加我们想要的属性和行为

图《通过类创建对象》

创建默认对象

原始方式:

let obj = {};       // let obj = new Object();
obj.name = "lnj"; // 属性
obj.age = 33; // 属性
obj.say = function () { // 行为
console.log("hello world");
}
console.log(obj.name);
console.log(obj.age);
obj.say();

简写方式: 注意点: 属性名称和取值之间用冒号隔开, 属性和属性之间用逗号隔开

let obj = {
name: "lnj",
age: 33,
say: function () {
console.log("hello world");
}
};
console.log(obj.name);
console.log(obj.age);
obj.say();

函数与方法区别

什么是函数和方法

  1. 函数就是没有和其它的类显示的绑定在一起的, 我们就称之为函数
  2. 方法就是显示的和其它的类绑定在一起的, 我们就称之为方法
        函数 ↓                                     方法 ↓

function demo() { // let obj = {
// console.log("hello demo"); // name: "lnj",
console.log(this); // test: function () {
} // console.log(this);
// }
// }

函数和方法的区别:

  1. 函数可以直接调用, 但是方法不能直接调用, 只能通过对象来调用
函数可使用 demo(); 使用,方法只能通过 obj.test(); 使用
  1. 函数内部的this输出的是window, 方法内部的this输出的是当前调用的那个对象
  2. 无论是函数还是方法, ()内部都有一个叫做this的东西

工厂函数

工厂函数就是专门用于创建对象的函数, 我们就称之为工厂函数createPerson

输出的结果为obj

function createPerson(myName, myAge) {
let obj = new Object();
obj.name = myName;
obj.age = myAge;
obj.say = function () {
console.log("hello world");
}
return obj;
}
let obj1 = createPerson("lnj", 34);
let obj2 = createPerson("zs", 44);
console.log(obj1);
console.log(obj2);

构造函数

  1. 构造函数和工厂函数一样, 都是专门用于创建对象的
  2. 构造函数本质上是工厂函数的简写

规则:

  1. 构造函数的函数名称首字母必须大写
  2. 构造函数只能够通过new来调用
function createPerson(myName, myAge) {   //   function Person(myName, myAge) {
let obj = new Object(); // this.name = myName;
obj.name = myName; // this.age = myAge;
obj.age = myAge; // this.say = function () {
obj.say = function () { // console.log("hello world");
console.log("hello world"); // }
} //
return obj; // let obj1 = new Person("lnj", 34);
} // let obj2 = new Person("zs", 44);
let obj1 = createPerson("lnj", 34); // console.log(obj1);
let obj2 = createPerson("zs", 44); // console.log(obj2);
console.log(obj1); //
console.log(obj2); //

当我们new Person("lnj", 34);系统做了什么事情:

  1. 会在构造函数中自动创建一个对象 // 系统自动添加的 let obj = new Object(); let this = obj;
  2. 会自动将刚才创建的对象赋值给this
  3. 会在构造函数的最后自动添加return this; // 系统自动添加的 return this;

构造函数的性能问题

function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
this.say = function () {
console.log("hello world");
}
}
let obj1 = new Person("lnj", 34);
let obj2 = new Person("zs", 44);

console.log(obj1.say === obj2.say); // false

这样调用方法运算,相同的方法存储在不同的内存空间,浪费内存空间。

方法推演上

通过定义一个全局变量作为方法局部变量的赋值来解决问题,同时亦产生另外的问题:全局变量增多,占用变量名称,内外增加无联系的代码,增加代码的阅读性。

function mySay() {
console.log("hello world");
}
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
this.say = mySay; // 将say赋值给mySay
}
let obj1 = new Person("lnj", 34);
let obj2 = new Person("zs", 44);
console.log(obj1.say === obj2.say); // true

方法推演中

let fns = {
mySay: function () {
console.log("hello world");
}
}
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
this.say = fns.mySay; // 将say赋值给fns.mySay 对象
}
let obj1 = new Person("lnj", 34);
let obj2 = new Person("zs", 44);
console.log(obj1.say === obj2.say); // true

方法推演下(最终)

function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype = {
say: function () {
console.log("hello world");
}
}
let obj1 = new Person("lnj", 34);
let obj2 = new Person("zs", 44);
console.log(obj1.say === obj2.say); // true

prototype特点

  1. 存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
  2. prototype中除了可以存储方法以外, 还可以存储属性
  3. prototype如果出现了和构造函数中同名的属性或者方法, 对象在访问的时候, 访问到的是构造函中的数据
应用场景
  1. prototype中一般情况下用于存储所有对象都相同的一些属性以及方法
  2. 如果是对象特有的属性或者方法, 我们会存储到构造函数中

对象三角恋关系

对象三角恋关系(上)

  1. 每个"构造函数"中都有一个默认的属性, 叫做prototype该属性保存着一个对象, 这个对象我们称之为"原型对象"
  2. 每个"原型对象"中都有一个默认的属性, 叫做constructor指向当前原型对象对应的那个"构造函数"
  3. 通过构造函数创建出来的对象我们称之为"实例对象"每个"实例对象"中都有一个默认的属性, 叫做__proto__,指向创建它的那个构造函数的"原型对象"

Function函数

对象三角恋关系(下)

  1. JavaScript中函数是引用类型(对象类型), 既然是对象,所以也是通过构造函数创建出来的,"所有函数"都是通过Function构造函数创建出来的对象
  2. JavaScript中只要是"函数"就有prototype属性Function函数prototype属性指向Function原型对象
  3. JavaScript中只要"原型对象"就有constructor属性 Function原型对象constructor指向它对应的构造函数
  4. Person构造函数Function构造函数的实例对象, 所以也有__proto__属性``Person构造函数__proto__属性指向Function原型对象

函数对象关系汇总

对象三角恋关系(汇总)

  1. 所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象
  2. 所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数
  3. 所有函数都是Function构造函数的实例对象
  4. 所有函数都是对象, 包括Function构造函数
  5. 所有对象都有__proto__属性
  6. 普通对象的__proto__属性指向创建它的那个构造函数对应的"原型对象"
  7. 所有对象的__proto__属性最终都会指向"Object原型对象"
  8. Object原型对象的__proto__属性指向NULL

原型链

  1. 对象中__proto__组成的链条我们称之为原型链
  2. 对象在查找属性和方法的时候, 会先在当前对象查找,如果当前对象中找不到想要的, 会依次去上一级原型对象中查找,如果找到Object原型对象都没有找到, 就会报错

根据__proto__的链条形成原型链 obj1实例对象 → Person原型对象 → Object原型对象 → NULL → 报错

⚠️注意 实际编写当中,为了不破坏原有的关系, 在给prototype赋值的时候, 需要在自定义的对象中手动的添加

function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype = {
constructor: Person, // 给prototype赋constructor属性, 手动指向它的构造函数
currentType: "人",
say: function () {
console.log("hello world");
}
}
let obj1 = new Person("lnj", 34);
// obj1.say();
console.log(obj1.currentType);

属性注意点

function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype = {
constructor: Person,
currentType: "人",
say: function () {
console.log("hello world");
}
}
let obj = new Person("lnj", 34);
// console.log(obj.currentType); // "人"
// console.log(obj.__proto__.currentType); // "人"
// 注意点: 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的
obj.currentType = "新设置的值";
console.log(obj.currentType); // 新设置的值
console.log(obj.__proto__.currentType); // "人"