JavaScript-进阶day3
(一)编程思想
一、面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
二、面向对象(oop)
面向对象是把事务分解为一个个对象,然后由对象之间分工与合作。
面向对象的特性:封装性、继承性、多态性。
面向过程编程 | 面向对象编程 | |
---|---|---|
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 |
缺点 | 没有面向对象易维护、易复用、易扩展 | 性能比面向过程低 |
提示:以下是本篇文章正文内容,下面案例可供参考
(二)构造函数
- 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
- 同样的将变量和函数结合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
▲ 总结:
- 构造函数体现了面向对象的封装特性。
- 构造函数实例创建的对象彼此独立、互不影响。
构造函数存在浪费内存的问题。
(三)原型
一、原型
原型(Prototype)是一个对象模板,当创建新对象时,新对象会“关联”到这个模板,从而可以共享模板上的属性和方法。
- 构造函数通过原型分配的函数是所有对象所共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象。
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的 this 都指向 实例化的对象。
▲ 反例:不使用原型——重复创建,浪费内存
<script>
// 构造函数:每次new都会执行内部代码
function Dog(name) {
this.name = name
this.bark = function() {
console.log(`${this.name}汪汪叫`)
}
}
// 创建两个实例
const dog1 = new Dog('大黄')
const dog2 = new Dog('小白')
// 验证:两个实例的bark是不同的函数(内存中存了两份相同逻辑)
console.log(dog1.bark === dog2.bark) //false(浪费内存!)
</script>
问题:100个狗实例就会创建100个bark方法,逻辑完全一样,却占用100份内存——这就是原型要解决的核心痛点。
▲ 正例:使用原型——共享方法,节省内存
<script>
function Dog(name) {
this.name = name
}
// 关键:把共享方法放在原型上(所有实例共用这一个bark方法)
Dog.prototype.bark = function() {
console.log(`${this.name} 汪汪叫`)
}
// 创建两个实例
const dog1 = new Dog('大黄')
const dog2 = new Dog('小白')
// 验证:两个实例的bark指向同一个函数(内存中只存一份)
console.log(dog1.bark === dog2.bark) //true(节省内存!)
// 实例调用方法:自身没有bark,就去原型 (Dog.prototype) 中找
dog1.bark() //大黄 汪汪叫
dog2.bark() //小白 汪汪叫
</script>
二、constructor 属性:指向构造函数
constructor 是原型对象上的一个属性,指向创建该对象的构造函数(用于标识对象的“类型”)。
案例:constructor 的作用
<script>
// 1.定义构造函数(用于创建对象的“工厂”)
function Person(name) {
this.name = name
}
// 2.创建实例
const person1 = new Person('大鱼')
// 3.访问constructor
console.log(person1.constructor) //输出: function Person(name) {...}
console.log(person1.constructor === Person) //true(证明是Person构造的)
</script>
🔺注意:
constructor 可被修改,因此不能完全依赖它判断类型(更可靠的是 Object.prototype.toString.call
)。
1. constructor 的默认指向
当你创建一个构造函数时,JS 会自动做两件事:
- 给构造函数添加 prototype 属性,指向一个“原型对象”。
- 给这个“原型对象”添加 constructor 属性,指向构造函数本身。
function Person() {}
// 自动关联:Person.prototype.constructor = Person
console.log(Person.prototype.constructor === Person) //true
2. 手动覆盖原型会丢失 constructor
若直接给 prototype 赋值一个新对象,会覆盖默认的原型对象,导致 constructor 指向错误(默认指向 Object)。
▲反例(错误):
function Person() {}
//直接覆盖原型对象
Person.prototype = {
sayHi: function() {}
}
const p = new Person()
console.log(p.constructor)
//输出:function Object() {...} (错误,应为Person)
▲正例(修复):
覆盖原型时,手动重置 constructor:
<script>
function Person() {}
Person.prototype = {
constructor: Person,
sayHi: function() {
console.log('Hi')
}
}
const p = new Person()
console.log(p.constructor === Person) //true
</script>
三、对象原型:__proto__
与prototype
prototype
:属于构造函数的属性,指向“原型对象”(模板)。__proto__
:属于实例对象的属性,指向其“原型对象"(即构造函数的prototype
)。
<script>
function Person(name) {
this.name = name
}
const person1 = new Person('张三')
// 关键等式:实例的 __proto__ === 构造函数的prototype
console.log(person1.__proto__ === Person.prototype) //true
</script>
▲ __proto__
是浏览器提供的“访问器”,
标准写法是 Object.getPrototypeOf (实例)
。
概念 | 归属 | 作用 | 标准性 |
---|---|---|---|
prototype | 构造函数 | 指向 “原型对象”,是实例的 “模板来源” | 标准属性 |
proto | 实例对象 | 指向实例的 “原型对象”,是查找链的入口 | 非标准访问器 |
Object.getPrototypeOf(实例) | 全局方法 | 标准方式获取实例的原型 (替代 proto) | 标准方法 |
四、原型继承
通过将方法定义在构造函数 prototype 上,所有实例可共享该方法(避免每个实例重复创建方法,节省内存)。
// 1. 构造函数(只定义实例自身的属性)
function Person(name) {
this.name = name;
}
// 2. 在原型上定义共享方法(所有实例共用)
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
// 3. 创建两个实例
const person1 = new Person("张三");
const person2 = new Person("李四");
// 4. 实例调用共享方法
person1.sayHi(); // 输出:你好,我是张三
person2.sayHi(); // 输出:你好,我是李四
// 证明方法是共享的(两个实例的方法指向同一个地址)
console.log(person1.sayHi === person2.sayHi); // 输出:true
五、原型链:属性查找的“链条”
当访问对象的某个属性时,JS 会先在对象自身查找;
若找不到,会沿着 __proto__
向上查找其“原型对象”;
若仍找不到,继续向上查找原型的原型,直到 Object.prototype
(顶层原型);
若最终找不到,返回 undefined
。
这个查找链条就是原型链。
function Person(name) {
this.name = name;
}
// 原型1:Person 的原型
Person.prototype.age = 18;
// 原型2:顶层原型(Object 的原型)
Object.prototype.gender = "未知";
const person1 = new Person("张三");
// 1. 查找自身属性:name(存在)
console.log(person1.name); // 输出:张三(自身有)
// 2. 自身没有 age,沿 __proto__ 找 Person.prototype(存在)
console.log(person1.age); // 输出:18(来自 Person.prototype)
// 3. 自身和 Person.prototype 都没有 gender,继续找 Object.prototype(存在)
console.log(person1.gender); // 输出:未知(来自 Object.prototype)
// 4. Object.prototype 没有 abc,查找结束,返回 undefined
console.log(person1.abc); // 输出:undefined