JavaScript原型对象与原型链
在JavaScript实际项目中,对象是十分重要的一种类型
无论是作为数据表现的一种形式或配置成工厂模式制造实例等等,这些用途在实际项目开发中都使用得非常广泛
依靠原型对象的特性可以实现一些比较模式化或适配第三方定制化的功能流程
对象
在了解原型与原型链之前我们先了解一下对象的相关知识,了解对象是理解原型链的基础
对象创建
1、字面创建
const testObj = {
key1: 1,
key2: 2,
key3: 3,
objFun:function () {
console.log(this.name);
}
};
2、使用new Object()创建
const testObj = new Object(); // 创建对象
testObj.name = 'test'; // 为对象添加属性
3、构造函数创建
function Car(brand, model, price) {
this.brand = brand;
this.model = model;
this.price = price;
this.toBuy = function(){
// axios... 请求接口或函数逻辑
}
}
const c1 = new Person('梅赛德斯', 'C', 600000);
4、工厂模式化函数创建
function setCar(brand, model, price) {
// ......可添加逻辑或参数处理
let Car = new Object();
Car.brand = brand;
Car.model = model;
Car.price = price;
Car.toBuy = function(){
// axios... 请求接口或函数逻辑
}
return Car;
}
var c1 = setCar('张三', 22, 'actor');
一般偏于模式流程化的对象处理我们会用3或4,1和2一般用于定义处理数据
总结来说3和4可以将配置写入函数中,创造出多个对象
如车为例,可创造出多型号的车的 对象
new Person('梅赛德斯', 'C', 600000)、
new Person('大众', 'B', 90000)
...
对象流程化处理示例
function Car(brand, model, price) {
this.brand = brand;
this.model = model;
this.price = price;
this.toBuy = function(){
console.log(`you will buy this car ${this.brand}-${this.model}`)
// axios... 请求接口或函数逻辑
}
}
const c1 = new Car('梅赛德斯', 'C', 600000);
const c2 = new Car('大众', 'B', 90000);
c1.toBuy()
c2.toBuy()
console.log(c1.toBuy)
console.log(c2.toBuy)
我们可以看到这是根据不同的传入参数,
1、函数创建了不同的对象
2、属性实现了不同的结果
但这种创建模式存在一种性能问题,里面可以公共的函数方法同时也被创建了
等于说一个公用方法进行了多次创建
比如上图中的toBuy函数,虽然函数体一致,但其实指向的是不同的内存地址
此时存在性能浪费
基于以上性能问题,如果还想让该函数绑定在类中我们可以定义静态方法进行管理
我们可以不创建实例的情况下调用该函数,当然使用静态方法还有很多别的用处
function Car(brand, model, price) {
this.brand = brand;
this.model = model;
this.price = price;
// this.toBuy = function(){
// console.log(`you will buy this car ${this.brand}-${this.model}`)
// // axios... 请求接口或函数逻辑
// }
}
// 静态方法
Car.toBuy = function(brand,model){
console.log(`you will buy this car ${brand}-${model}`)
// axios... 请求接口或函数逻辑
}
// 效果与上等同,通过class与static语法糖调用
// class Car {
// static toBuy(brand,model) {
// return console.log(`you will buy this car ${brand}-${model}`);
// }
// }
// const c1 = new Car('梅赛德斯', 'C', 600000);
// const c2 = new Car('大众', 'B', 90000);
Car.toBuy('梅赛德斯', 'C');
Car.toBuy('大众', 'B');
// 静态方法无法通过实例调用,只能通过类调用
// c1.toBuy() 报错
// c2.toBuy() 报错
但针对于此是否有更好的方案,下面引出原型对象prototype
prototype
构造函数中有一个prototype属性,prototype指向另一个对象。prototype中定义的方法或属性可共享至构造函数。
我们可以将一些公共的方法定义在prototype上,通过实例调用它
总体意思即为公共函数和方法可以定义在原型对象prototype上
function Car(brand, model, price) {
this.brand = brand;
this.model = model;
this.price = price;
// this.toBuy = function(){
// console.log(`you will buy this car ${this.brand}-${this.model}`)
// // axios... 请求接口或函数逻辑
// }
}
Car.prototype.toBuy = function(){
// this 指向调用该函数的实例
console.log(`you will buy this car ${this.brand}-${this.model}`)
// axios... 请求接口或函数逻辑
}
const c1 = new Car('梅赛德斯', 'C', 600000);
const c2 = new Car('大众', 'B', 90000);
c1.toBuy()
c2.toBuy()
输出结果:
如此以来可以解决性能浪费的问题
但是实例对象为什么可以调用(父类)的prototype方法呢?我们接下来往下看
function Car(brand, model, price) {
this.brand = brand;
this.model = model;
this.price = price;
// this.toBuy = function(){
// console.log(`you will buy this car ${this.brand}-${this.model}`)
// // axios... 请求接口或函数逻辑
// }
}
Car.prototype.toBuy = function(){
console.log(`you will buy this car ${this.brand}-${this.model}`)
// axios... 请求接口或函数逻辑
}
const c1 = new Car('梅赛德斯', 'C', 600000);
const c2 = new Car('大众', 'B', 90000);
console.log('c1',c1)
console.log('c2',c2)
我们可以看见每个实例中有个属性都指向prototype(这个属性就是__proto__,新版本浏览器已经不支持显示)
我们可以打印一下:
由此我们可以知道每个实例都可以指向原型prototype,在此梳理一下三者关系:
在官方MDN中该属性已被标准除名,所以我们在开发时尽量不要对实例的[Prototype]进行更改,以免影响流程性能
MDN官方说明
根据以上的结论我们总体整理一下原型链的调用方式:
1、主类初始化实例 ① ,实例 ① 调用某个方法
2、实例本身是否继承了该方法 ? 执行该方法 : 根据__proto__指向去原型对象prototype寻找该方法
3、prototype是否存在该方法 ? 执行该方法 : 根据prototype(本身也是个对象)的__proto__指向去更上层的父类寻找该方法(Object)
…
Object也没有则指向null,最后都没有则会抛出我们最常见的 xxx is undefined 哈哈写到这我笑了
所以对于不确定的数组与对象一定要做数据保护
总结流程图: